Skip to content

Commit

Permalink
Merge pull request #6603 from spacemeshos/athena-test-client
Browse files Browse the repository at this point in the history
athena devnet testing client
  • Loading branch information
poszu authored Jan 10, 2025
2 parents 148208e + 0f123bb commit 9bdf70e
Show file tree
Hide file tree
Showing 13 changed files with 619 additions and 0 deletions.
1 change: 1 addition & 0 deletions vm/cmd/client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.hex
19 changes: 19 additions & 0 deletions vm/cmd/client/api/connection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package api

import (
"fmt"

"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

func connect(address string) (*grpc.ClientConn, error) {
conn, err := grpc.NewClient(
address,
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
return nil, fmt.Errorf("connecting to %s: %w", address, err)
}
return conn, nil
}
28 changes: 28 additions & 0 deletions vm/cmd/client/api/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package api

import (
"context"

spacemeshv2alpha1 "github.com/spacemeshos/api/release/go/spacemesh/v2alpha1"

"github.com/spacemeshos/go-spacemesh/common/types"
)

func AccountInfo(apiAddress string, accountAddress types.Address) (*spacemeshv2alpha1.Account, error) {
conn, err := connect(apiAddress)
if err != nil {
return nil, err
}

req := spacemeshv2alpha1.AccountRequest{
Addresses: []string{accountAddress.String()},
Limit: 1,
}

resp, err := spacemeshv2alpha1.NewAccountServiceClient(conn).List(context.Background(), &req)
if err != nil {
return nil, err
}

return resp.Accounts[0], nil
}
29 changes: 29 additions & 0 deletions vm/cmd/client/api/submit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package api

import (
"context"
"encoding/hex"
"fmt"

spacemeshv2alpha1 "github.com/spacemeshos/api/release/go/spacemesh/v2alpha1"
"go.uber.org/zap"
)

func Submit(address string, tx []byte, logger *zap.Logger) error {
conn, err := connect(address)
if err != nil {
return err
}

req := spacemeshv2alpha1.SubmitTransactionRequest{
Transaction: tx,
}

resp, err := spacemeshv2alpha1.NewTransactionServiceClient(conn).SubmitTransaction(context.Background(), &req)
if err != nil {
return fmt.Errorf("submitting TX: %w", err)
}
logger.Info("submitted", zap.String("ID", hex.EncodeToString(resp.TxId)))

return nil
}
41 changes: 41 additions & 0 deletions vm/cmd/client/cmd/accountInfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package cmd

import (
"errors"
"fmt"

"github.com/spf13/cobra"

"github.com/spacemeshos/go-spacemesh/common/types"
"github.com/spacemeshos/go-spacemesh/vm/cmd/client/api"
)

var accountAddress string

var accountInfoCmd = &cobra.Command{
Use: "accountInfo",
Short: "A brief description of your command",
RunE: func(cmd *cobra.Command, args []string) error {
if accountAddress == "" {
return errors.New("please provide account address to check with --account")
}
accountAddress, err := types.StringToAddress(accountAddress)
if err != nil {
return fmt.Errorf("cannot parse recipient address %q: %w", to, err)
}
account, err := api.AccountInfo(address, accountAddress)
if err != nil {
return err
}
fmt.Printf("template : %s\n", account.Template)
fmt.Printf("current state : %s\n", account.Current.String())
fmt.Printf("projected state: %s\n", account.Projected.String())

return nil
},
}

func init() {
rootCmd.AddCommand(accountInfoCmd)
accountInfoCmd.Flags().StringVar(&accountAddress, "account", "", "account address to check")
}
29 changes: 29 additions & 0 deletions vm/cmd/client/cmd/coinbase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package cmd

import (
"fmt"

"github.com/spf13/cobra"

"github.com/spacemeshos/go-spacemesh/signing"
"github.com/spacemeshos/go-spacemesh/vm/sdk/wallet"
)

var coinbaseCmd = &cobra.Command{
Use: "coinbase",
Short: "print coinbase for the chosen key (assuming the singlesig wallet template)",
RunE: func(cmd *cobra.Command, args []string) error {
key, err := getKey(0)
if err != nil {
return err
}
address := wallet.Address(signing.Public(key))

fmt.Printf("coinbase: %s\n", address.String())
return nil
},
}

func init() {
rootCmd.AddCommand(coinbaseCmd)
}
70 changes: 70 additions & 0 deletions vm/cmd/client/cmd/deploy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package cmd

import (
"errors"
"fmt"
"math"
"os"

"github.com/spf13/cobra"
"go.uber.org/zap"

"github.com/spacemeshos/go-spacemesh/common/types"
"github.com/spacemeshos/go-spacemesh/signing"
"github.com/spacemeshos/go-spacemesh/vm/cmd/client/api"
"github.com/spacemeshos/go-spacemesh/vm/core"
"github.com/spacemeshos/go-spacemesh/vm/sdk/wallet"
)

var path string

// deployCmd represents the deploy command.
var deployCmd = &cobra.Command{
Use: "deploy",
Short: "A brief description of your command",
RunE: func(cmd *cobra.Command, args []string) error {
if nonce == math.MaxUint64 {
return errors.New("provide nonce counter with --nonce")
}
key, err := getKey(0)
if err != nil {
return err
}
return deploy(key, nonce, path)
},
}

func init() {
rootCmd.AddCommand(deployCmd)
deployCmd.Flags().StringVar(&path, "path", "", "path to contract template to deploy")
}

func deploy(principal signing.PrivateKey, nonce uint64, path string) error {
logger, err := zap.NewDevelopment()
if err != nil {
return err
}
principialPubkey := signing.Public(principal)
principialAddress := wallet.Address(principialPubkey)

blob, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("reading contract from %s: %w", path, err)
}
tx, err := wallet.Deploy(principal, nonce, blob)
if err != nil {
return err
}

logger.Info("deploying contract",
zap.Stringer("ID", types.NewRawTx(tx).ID),
zap.Stringer("principal", principialAddress),
zap.Stringer("template address", core.TemplateAddress(blob)),
)

if draft {
logger.Info("not submitting TX in draft mode")
return nil
}
return api.Submit(address, tx, logger)
}
28 changes: 28 additions & 0 deletions vm/cmd/client/cmd/generateKey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cmd

import (
"log"

"github.com/spf13/cobra"

"github.com/spacemeshos/go-spacemesh/signing"
)

var file string

var generateKeyCmd = &cobra.Command{
Use: "generateKey",
Short: "Generate a keypair for signing transacations",
Run: func(cmd *cobra.Command, args []string) {
_, err := signing.NewEdSigner(signing.ToFile(file))
if err != nil {
log.Fatalf("failed to create keys: %s", err.Error())
}
},
}

func init() {
rootCmd.AddCommand(generateKeyCmd)

generateKeyCmd.Flags().StringVar(&file, "file", "key.hex", "file to save the keys in")
}
58 changes: 58 additions & 0 deletions vm/cmd/client/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package cmd

import (
"errors"
"fmt"
"math"
"os"

"github.com/spf13/cobra"

"github.com/spacemeshos/go-spacemesh/signing"
)

var (
draft bool
nonce uint64
address string
keyFiles []string
keys = make(map[int]signing.PrivateKey)
)

func getKey(i int) (signing.PrivateKey, error) {
if k, ok := keys[i]; ok {
return k, nil
}
if len(keyFiles) < i {
return nil, errors.New("key file was not provided")
}
signer, err := signing.NewEdSigner(signing.FromFile(keyFiles[i]))
if err != nil {
return nil, fmt.Errorf("reading the key: %s", err)
}
keys[i] = signer.PrivateKey()
return signer.PrivateKey(), nil
}

var rootCmd = &cobra.Command{
Use: "client",
Short: "application to interact with transactions on an Athena network",
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}

func init() {
rootCmd.PersistentFlags().BoolVar(&draft, "draft", false, "prepare and print but do not submit the TX")
rootCmd.PersistentFlags().Uint64Var(&nonce, "nonce", math.MaxUint64, "Nonce for the spawn transaction")
rootCmd.PersistentFlags().
StringVar(&address, "address", "localhost:9092", "Address of the node to submit the TX to")
rootCmd.PersistentFlags().
StringSliceVarP(&keyFiles, "principal-key", "p", []string{"key.hex"}, "path to file with the principal key")
}
58 changes: 58 additions & 0 deletions vm/cmd/client/cmd/spawn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package cmd

import (
"errors"
"math"

"github.com/spf13/cobra"
"go.uber.org/zap"

"github.com/spacemeshos/go-spacemesh/common/types"
"github.com/spacemeshos/go-spacemesh/signing"
"github.com/spacemeshos/go-spacemesh/vm/cmd/client/api"
"github.com/spacemeshos/go-spacemesh/vm/sdk/wallet"
)

// spawnCmd represents the spawn command.
var spawnCmd = &cobra.Command{
Use: "spawn",
Short: "spawn a wallet account",
RunE: func(cmd *cobra.Command, args []string) error {
if nonce == math.MaxUint64 {
return errors.New("provide nonce counter with --nonce")
}
key, err := getKey(0)
if err != nil {
return err
}
return spawn(address, key, nonce)
},
}

func init() {
rootCmd.AddCommand(spawnCmd)
}

func spawn(address string, privKey signing.PrivateKey, nonce uint64) error {
logger, err := zap.NewDevelopment()
if err != nil {
return err
}
principialPubkey := signing.Public(privKey)
principalAddress := wallet.Address(principialPubkey)
tx, err := wallet.Spawn(privKey, nonce)
if err != nil {
return err
}
logger.Info(
"spawning account",
zap.Stringer("address", principalAddress),
zap.Stringer("id", types.NewRawTx(tx).ID),
)

if draft {
logger.Info("not submitting TX in draft mode")
return nil
}
return api.Submit(address, tx, logger)
}
Loading

0 comments on commit 9bdf70e

Please sign in to comment.