Skip to content

Commit

Permalink
reorg client setup
Browse files Browse the repository at this point in the history
improve HttpError
goclient fixes
nodeClient sends x-aptos-client: header
  • Loading branch information
brianolson committed May 2, 2024
1 parent 743e904 commit 326d2d9
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 142 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Aptos Go SDK
2. sequence number reading
- Done. [RestClient.Account()](client.go)
3. Account / object resource listing
- Rough. Bulk of data is still map[string]any rather than struct.
- Good. JSON map[string]any mode and BCS mode.
4. Transaction waiting / reads (by hash)
- Rough. TransactionByHash() returns map[string]any rather than struct.

Expand Down
178 changes: 70 additions & 108 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,55 @@ import (
"time"
)

// For Content-Type header
const APTOS_SIGNED_BCS = "application/x.aptos.signed_transaction+bcs"

const (
Localnet = "localnet"
Devnet = "devnet"
Testnet = "testnet"
Mainnet = "mainnet"
)

const (
localnet_api = "http://localhost:8080/v1"
devnet_api = "https://api.devnet.aptoslabs.com/v1"
testnet_api = "https://api.testnet.aptoslabs.com/v1"
mainnet_api = "https://api.mainnet.aptoslabs.com/v1"
)

const (
localnet_faucet = "http://localhost:8081/v1"
devnet_faucet = "https://faucet.devnet.aptoslabs.com/"
testnet_faucet = "https://faucet.testnet.aptoslabs.com/"
)

const (
localnet_chain_id = 4
testnet_chain_id = 2
mainnet_chain_id = 1
)

type NetworkConfig struct {
network *string
api *string
faucet *string
indexer *string
Name string
ChainId uint8
NodeUrl string
IndexerUrl string
FaucetUrl string
}

var LocalnetConfig = NetworkConfig{
Name: "localnet",
ChainId: 4,
NodeUrl: "http://localhost:8080/v1",
IndexerUrl: "",
FaucetUrl: "http://localhost:8081/v1",
}
var DevnetConfig = NetworkConfig{
Name: "devnet",
ChainId: 3,
NodeUrl: "https://api.devnet.aptoslabs.com/v1",
IndexerUrl: "",
FaucetUrl: "https://faucet.devnet.aptoslabs.com/",
}
var TestnetConfig = NetworkConfig{
Name: "testnet",
ChainId: 2,
NodeUrl: "https://api.testnet.aptoslabs.com/v1",
IndexerUrl: "",
FaucetUrl: "https://faucet.testnet.aptoslabs.com/",
}
var MainnetConfig = NetworkConfig{
Name: "mainnet",
ChainId: 1,
NodeUrl: "https://api.mainnet.aptoslabs.com/v1",
IndexerUrl: "",
FaucetUrl: "",
}

// Map from network name to NetworkConfig
var NamedNetworks map[string]NetworkConfig

func init() {
NamedNetworks = make(map[string]NetworkConfig, 4)
setNN := func(nc NetworkConfig) {
NamedNetworks[nc.Name] = nc
}
setNN(LocalnetConfig)
setNN(DevnetConfig)
setNN(TestnetConfig)
setNN(MainnetConfig)
}

// Client is a facade over the multiple types of underlying clients, as the user doesn't actually care where the data
Expand All @@ -50,94 +65,41 @@ type Client struct {
// TODO: Add indexer client
}

var ErrUnknownNetworkName = errors.New("Unknown network name")

// NewClientFromNetworkName Creates a new client for a specific network name
func NewClientFromNetworkName(network *string) (client *Client, err error) {
config := NetworkConfig{network: network}
client, err = NewClient(config)
return
func NewClientFromNetworkName(networkName string) (client *Client, err error) {
config, ok := NamedNetworks[networkName]
if !ok {
return nil, ErrUnknownNetworkName

}
return NewClient(config)
}

// NewClient Creates a new client with a specific network config that can be extended in the future
func NewClient(config NetworkConfig) (client *Client, err error) {
var apiUrl *url.URL = nil

switch {
case config.api == nil && config.network == nil:
err = errors.New("aptos api url or network is required")
return
case config.api != nil:
apiUrl, err = url.Parse(*config.api)
if err != nil {
return
}
case *config.network == Localnet:
apiUrl, err = url.Parse(localnet_api)
if err != nil {
return
}
case *config.network == Devnet:
apiUrl, err = url.Parse(devnet_api)
if err != nil {
return
}
case *config.network == Testnet:
apiUrl, err = url.Parse(testnet_api)
if err != nil {
return
}
case *config.network == Mainnet:
apiUrl, err = url.Parse(mainnet_api)
if err != nil {
return
}
default:
err = errors.New("network name is unknown, please put Localnet, Devnet, Testnet, or Mainnet")
return
nodeUrl, err := url.Parse(config.NodeUrl)
if err != nil {
return nil, err
}
var faucetUrl *url.URL = nil

switch {
case config.faucet == nil && config.network == nil:
err = errors.New("aptos faucet url or network is required")
return
case config.faucet != nil:
faucetUrl, err = url.Parse(*config.faucet)
if err != nil {
return
}
case *config.network == Localnet:
faucetUrl, err = url.Parse(localnet_faucet)
if err != nil {
return
}
case *config.network == Devnet:
faucetUrl, err = url.Parse(devnet_faucet)
if err != nil {
return
}
case *config.network == Testnet:
faucetUrl, err = url.Parse(testnet_faucet)
if err != nil {
return
}
case *config.network == Mainnet:
faucetUrl = nil
default:
err = errors.New("network name is unknown, please put Localnet, Devnet, Testnet, or Mainnet")
return

faucetUrl, err := url.Parse(config.FaucetUrl)
if err != nil {
return nil, err
}

// TODO: add indexer

restClient := new(NodeClient)
restClient.baseUrl = *apiUrl
restClient.client.Timeout = 60 * time.Second // TODO: Make configurable
nodeClient := new(NodeClient)
nodeClient.baseUrl = *nodeUrl
nodeClient.client.Timeout = 60 * time.Second // TODO: Make configurable
faucetClient := &FaucetClient{
restClient,
faucetUrl,
nodeClient,
*faucetUrl,
}
client = &Client{
*restClient,
*nodeClient,
*faucetClient,
}
return
Expand Down
27 changes: 21 additions & 6 deletions client_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
package aptos

import (
"github.com/stretchr/testify/assert"
"strconv"
"testing"

"github.com/stretchr/testify/assert"
)

func TestNamedConfig(t *testing.T) {
names := []string{"mainnet", "devnet", "testnet", "localnet"}
for _, name := range names {
assert.Equal(t, name, NamedNetworks[name].Name)
}
}

func TestAptosClientHeaderValue(t *testing.T) {
assert.Greater(t, len(AptosClientHeaderValue), 0)
assert.NotEqual(t, "aptos-go-sdk/unk", AptosClientHeaderValue)
}

func Test_Flow(t *testing.T) {
// Create a client
network := Devnet
config := NetworkConfig{
network: &network,
if testing.Short() {
// TODO: only run this in some integration mode set by environment variable?
// TODO: allow this to be harmlessly flakey if devnet is down?
// TODO: write a framework to optionally run things against `aptos node run-localnet`
t.Skip("integration test expects network connection to devnet in cloud")
}
client, err := NewClient(config)
// Create a client
client, err := NewClient(DevnetConfig)
assert.NoError(t, err)

// Verify chain id retrieval works
Expand Down
46 changes: 46 additions & 0 deletions client_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package aptos

import (
"fmt"
"net/url"
"runtime/debug"
)

const APTOS_CLIENT_HEADER = "x-aptos-client"

var AptosClientHeaderValue = "aptos-go-sdk/unk"

func init() {
vcsRevision := "unk"
vcsMod := ""
goArch := ""
goOs := ""
buildInfo, ok := debug.ReadBuildInfo()
if ok {
for _, setting := range buildInfo.Settings {
switch setting.Key {
case "vcs.revision":
vcsRevision = setting.Value
case "vcs.modified":
vcsMod = setting.Value
case "GOARCH":
goArch = setting.Value
case "GOOS":
goOs = setting.Value
default:
}
}
}
params := url.Values{}
if vcsMod == "true" {
params.Set("m", "t")
}
params.Set("go", buildInfo.GoVersion)
if goArch != "" {
params.Set("a", goArch)
}
if goOs != "" {
params.Set("os", goOs)
}
AptosClientHeaderValue = fmt.Sprintf("aptos-go-sdk/%s;%s", vcsRevision, params.Encode())
}
35 changes: 21 additions & 14 deletions cmd/goclient/goclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
var (
verbose bool = false
accountStr string = ""
network string = aptos.Devnet
network string = ""
txnHash string = ""
)

Expand Down Expand Up @@ -79,6 +79,8 @@ func main() {
var misc []string

network = getenv("APTOS_NETWORK", network)
nodeUrl := getenv("APTOS_NODE_URL", "")
faucetUrl := getenv("APTOS_FAUCET_URL", "")

// there may be better command frameworks, but in a pinch I can write what I want faster than I can learn one
argi := 0
Expand All @@ -93,6 +95,12 @@ func main() {
} else if arg == "-n" || arg == "--network" {
network = args[argi+1]
argi++
} else if arg == "-u" || arg == "--node" {
nodeUrl = args[argi+1]
argi++
} else if arg == "-F" || arg == "--faucet" {
faucetUrl = args[argi+1]
argi++
} else if arg == "-t" || arg == "--txn" {
txnHash = args[argi+1]
argi++
Expand All @@ -102,20 +110,19 @@ func main() {
argi++
}

// TODO: some of this info will be useful for putting in client HTTP headers
if verbose {
buildInfo, ok := debug.ReadBuildInfo()
if ok {
fmt.Printf("built with %s\n", buildInfo.GoVersion)
for _, setting := range buildInfo.Settings {
fmt.Printf("%s=%s\n", setting.Key, setting.Value)
}
}
fmt.Printf("will send \"%s: %s\"\n", APTOS_CLIENT_HEADER, AptosClientHeaderValue)
}
var client *aptos.Client
var err error

client, err := aptos.NewClientFromNetworkName(&network)
maybefail(err, "client error: %s", err)
if network != "" {
client, err = aptos.NewClientFromNetworkName(network)
maybefail(err, "client error: %s", err)
} else {
client, err = aptos.NewClient(aptos.NetworkConfig{
NodeUrl: nodeUrl,
FaucetUrl: faucetUrl,
})
maybefail(err, "client error: %s", err)
}

var account aptos.AccountAddress
if accountStr != "" {
Expand Down
12 changes: 6 additions & 6 deletions faucet.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ func truthy(x any) bool {
}

type FaucetClient struct {
restClient *NodeClient
url *url.URL
nodeClient *NodeClient
url url.URL
}

// Fund account with the given amount of AptosCoin
func (faucetClient *FaucetClient) Fund(address AccountAddress, amount uint64) error {
if faucetClient.restClient == nil {
return errors.New("faucet client not initialized")
if faucetClient.nodeClient == nil {
return errors.New("faucet's node-client not initialized")
}
mintUrl := faucetClient.url
mintUrl.Path = path.Join(mintUrl.Path, "mint")
Expand All @@ -79,11 +79,11 @@ func (faucetClient *FaucetClient) Fund(address AccountAddress, amount uint64) er
if err != nil {
return fmt.Errorf("response json decode error, %w", err)
}
if faucetClient.restClient == nil {
if faucetClient.nodeClient == nil {
slog.Debug("FundAccount no txns to wait for")
// no Aptos client to wait on txn completion
return nil
}
slog.Debug("FundAccount wait for txns", "ntxn", len(txnHashes))
return faucetClient.restClient.WaitForTransactions(txnHashes)
return faucetClient.nodeClient.WaitForTransactions(txnHashes)
}
Loading

0 comments on commit 326d2d9

Please sign in to comment.