From cc8dbbeb480cfc34a5f8d4fe15f285b825087211 Mon Sep 17 00:00:00 2001 From: Martin Kysel Date: Fri, 11 Oct 2024 10:07:23 -0700 Subject: [PATCH] Fix CLI commands to handle blockchain operations (#217) - add 3 new commands, mark healthy, mark unhealthy and update address - print address in get-pub-key for debugging purposes - change local keys to funded anvil key pairs - get-nodes no longer needs a key since its accessing public info --- cmd/cli/main.go | 109 ++++++++++++++++++++++++++++---- cmd/replication/main.go | 1 + dev/local.env | 14 +++- dev/register-local-node | 4 +- dev/register-local-node-2 | 7 +- dev/run-2 | 4 +- pkg/blockchain/registryAdmin.go | 92 ++++++++++++++++++++++++++- pkg/config/options.go | 13 +++- pkg/utils/keys.go | 4 ++ pkg/utils/log.go | 2 - 10 files changed, 224 insertions(+), 26 deletions(-) diff --git a/cmd/cli/main.go b/cmd/cli/main.go index d2ecc030..e226ce04 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -22,11 +22,13 @@ type globalOptions struct { type CLI struct { globalOptions - Command string - GetPubKey config.GetPubKeyOptions - GenerateKey config.GenerateKeyOptions - RegisterNode config.RegisterNodeOptions - GetAllNodes config.GetAllNodesOptions + Command string + GetPubKey config.GetPubKeyOptions + GenerateKey config.GenerateKeyOptions + RegisterNode config.RegisterNodeOptions + GetAllNodes config.GetAllNodesOptions + UpdateHealth config.UpdateHealthOptions + UpdateAddress config.UpdateAddressOptions } /* @@ -44,6 +46,8 @@ func parseOptions(args []string) (*CLI, error) { var registerNodeOptions config.RegisterNodeOptions var getPubKeyOptions config.GetPubKeyOptions var getAllNodesOptions config.GetAllNodesOptions + var updateHealthOptions config.UpdateHealthOptions + var updateAddressOptions config.UpdateAddressOptions parser := flags.NewParser(&options, flags.Default) if _, err := parser.AddCommand("generate-key", "Generate a public/private keypair", "", &generateKeyOptions); err != nil { @@ -58,6 +62,15 @@ func parseOptions(args []string) (*CLI, error) { if _, err := parser.AddCommand("get-all-nodes", "Get all nodes from the registry", "", &getAllNodesOptions); err != nil { return nil, fmt.Errorf("Could not add get-all-nodes command: %s", err) } + if _, err := parser.AddCommand("mark-healthy", "Mark a node as healthy in the registry", "", &updateHealthOptions); err != nil { + return nil, fmt.Errorf("Could not add mark-healthy command: %s", err) + } + if _, err := parser.AddCommand("mark-unhealthy", "Mark a node as unhealthy in the registry", "", &updateHealthOptions); err != nil { + return nil, fmt.Errorf("Could not add mark-unhealthy command: %s", err) + } + if _, err := parser.AddCommand("update-address", "Update HTTP address of a node", "", &updateAddressOptions); err != nil { + return nil, fmt.Errorf("Could not add update-address command: %s", err) + } if _, err := parser.ParseArgs(args); err != nil { if err, ok := err.(*flags.Error); !ok || err.Type != flags.ErrHelp { return nil, fmt.Errorf("Could not parse options: %s", err) @@ -76,6 +89,8 @@ func parseOptions(args []string) (*CLI, error) { generateKeyOptions, registerNodeOptions, getAllNodesOptions, + updateHealthOptions, + updateAddressOptions, }, nil } @@ -87,7 +102,9 @@ func getPubKey(logger *zap.Logger, options *CLI) { logger.Info( "parsed private key", zap.String("pub-key", utils.EcdsaPublicKeyToString(privKey.Public().(*ecdsa.PublicKey))), + zap.String("address", utils.EcdsaPublicKeyToAddress(privKey.Public().(*ecdsa.PublicKey))), ) + privKey.Public() } func registerNode(logger *zap.Logger, options *CLI) { ctx := context.Background() @@ -156,8 +173,36 @@ func getAllNodes(logger *zap.Logger, options *CLI) { logger.Fatal("could not create chain client", zap.Error(err)) } + caller, err := blockchain.NewNodeRegistryCaller( + logger, + chainClient, + options.Contracts, + ) + if err != nil { + logger.Fatal("could not create registry admin", zap.Error(err)) + } + + nodes, err := caller.GetAllNodes(ctx) + if err != nil { + logger.Fatal("could not retrieve nodes from registry", zap.Error(err)) + } + + logger.Info( + "got nodes", + zap.Int("size", len(nodes)), + zap.Any("nodes", nodes), + ) +} + +func updateHealth(logger *zap.Logger, options *CLI, health bool) { + ctx := context.Background() + chainClient, err := blockchain.NewClient(ctx, options.Contracts.RpcUrl) + if err != nil { + logger.Fatal("could not create chain client", zap.Error(err)) + } + signer, err := blockchain.NewPrivateKeySigner( - options.GetAllNodes.AdminPrivateKey, + options.UpdateHealth.AdminPrivateKey, options.Contracts.ChainID, ) @@ -175,15 +220,46 @@ func getAllNodes(logger *zap.Logger, options *CLI) { logger.Fatal("could not create registry admin", zap.Error(err)) } - nodes, err := registryAdmin.GetAllNodes(ctx) + err = registryAdmin.UpdateHealth(ctx, options.UpdateHealth.NodeId, health) if err != nil { - logger.Fatal("could not retrieve nodes from registry", zap.Error(err)) + logger.Fatal("could not update node health in registry", zap.Error(err)) } - logger.Info( - "got nodes", - zap.Int("size", len(nodes)), - zap.Any("nodes", nodes), +} + +func updateAddress(logger *zap.Logger, options *CLI) { + ctx := context.Background() + chainClient, err := blockchain.NewClient(ctx, options.Contracts.RpcUrl) + if err != nil { + logger.Fatal("could not create chain client", zap.Error(err)) + } + + signer, err := blockchain.NewPrivateKeySigner( + options.UpdateAddress.PrivateKey, + options.Contracts.ChainID, + ) + + if err != nil { + logger.Fatal("could not create signer", zap.Error(err)) + } + + registryAdmin, err := blockchain.NewNodeRegistryAdmin( + logger, + chainClient, + signer, + options.Contracts, + ) + if err != nil { + logger.Fatal("could not create registry admin", zap.Error(err)) + } + + err = registryAdmin.UpdateHttpAddress( + ctx, + options.UpdateAddress.NodeId, + options.UpdateAddress.Address, ) + if err != nil { + logger.Fatal("could not update node address in registry", zap.Error(err)) + } } func main() { @@ -212,6 +288,15 @@ func main() { case "get-all-nodes": getAllNodes(logger, options) return + case "mark-healthy": + updateHealth(logger, options, true) + return + case "mark-unhealthy": + updateHealth(logger, options, false) + return + case "update-address": + updateAddress(logger, options) + return } } diff --git a/cmd/replication/main.go b/cmd/replication/main.go index ebe8223c..23531699 100644 --- a/cmd/replication/main.go +++ b/cmd/replication/main.go @@ -38,6 +38,7 @@ func main() { if err != nil { fatal("Could not build logger: %s", err) } + logger = logger.Named("replication") logger.Info(fmt.Sprintf("Version: %s", Commit)) if options.Tracing.Enable { diff --git a/dev/local.env b/dev/local.env index 389d2407..31df3a9e 100755 --- a/dev/local.env +++ b/dev/local.env @@ -14,10 +14,18 @@ export XMTPD_CONTRACTS_MESSAGES_ADDRESS XMTPD_CONTRACTS_IDENTITY_UPDATES_ADDRESS="$(jq -r '.deployedTo' build/IdentityUpdates.json)" # Built by contracts/deploy-local export XMTPD_CONTRACTS_IDENTITY_UPDATES_ADDRESS +export ANVIL_ACC_1_PRIVATE_KEY="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" +export ANVIL_ACC_1_PUBLIC_KEY="0x02ba5734d8f7091719471e7f7ed6b9df170dc70cc661ca05e688601ad984f068b0" +export ANVIL_ACC_1_ADDRESS="0x70997970C51812dc3A010C7d01b50e0d17dc79C8" + +export ANVIL_ACC_2_PRIVATE_KEY="0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" +export ANVIL_ACC_2_PUBLIC_KEY="0x039d9031e97dd78ff8c15aa86939de9b1e791066a0224e331bc962a2099a7b1f04" +export ANVIL_ACC_2_ADDRESS="0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" + + # Top Level Options Default Node -# {"private-key": "0x5742e57f960fbe62d0823b678df466b9e7d6c0c52888eacf3c31c132c645fd34", "public-key": "0x0202fd5e60ea5b9324431ec567a6e2655c4dc7160bba2ee5f8cc07ffc86a28342b"} -export XMTPD_SIGNER_PRIVATE_KEY="0x5742e57f960fbe62d0823b678df466b9e7d6c0c52888eacf3c31c132c645fd34" # From contracts/.env +export XMTPD_SIGNER_PRIVATE_KEY=$ANVIL_ACC_1_PRIVATE_KEY export XMTPD_PAYER_PRIVATE_KEY=$XMTPD_SIGNER_PRIVATE_KEY export XMTPD_MLS_VALIDATION_GRPC_ADDRESS="localhost:60051" -export XMTPD_SIGNER_PUBLIC_KEY="0x0202fd5e60ea5b9324431ec567a6e2655c4dc7160bba2ee5f8cc07ffc86a28342b" \ No newline at end of file +export XMTPD_SIGNER_PUBLIC_KEY=$ANVIL_ACC_1_PUBLIC_KEY \ No newline at end of file diff --git a/dev/register-local-node b/dev/register-local-node index a082dc5c..29b019dd 100755 --- a/dev/register-local-node +++ b/dev/register-local-node @@ -4,8 +4,10 @@ set -eu . dev/local.env +export NODE_ADDRESS=$ANVIL_ACC_1_ADDRESS + dev/cli register-node \ --http-address=http://localhost:5050 \ - --owner-address=0xf0490b45884803924Ca84C2051ef435991D7350D \ + --owner-address=$NODE_ADDRESS \ --admin-private-key=$PRIVATE_KEY \ --signing-key-pub=$XMTPD_SIGNER_PUBLIC_KEY \ No newline at end of file diff --git a/dev/register-local-node-2 b/dev/register-local-node-2 index f651c4ff..c3356e85 100755 --- a/dev/register-local-node-2 +++ b/dev/register-local-node-2 @@ -4,8 +4,11 @@ set -eu . dev/local.env +export XMTPD_SIGNER_PUBLIC_KEY=$ANVIL_ACC_2_PUBLIC_KEY +export NODE_ADDRESS=$ANVIL_ACC_2_ADDRESS + dev/cli register-node \ --http-address=http://localhost:5051 \ - --owner-address=0xf0490b45884803924Ca84C2051ef435991D7350D \ + --owner-address=$NODE_ADDRESS \ --admin-private-key=$PRIVATE_KEY \ - --signing-key-pub=0x03da7f733d870237f6dfd0074aea27edaf7b840d68e88641fb2a687de16bbe6a2b \ No newline at end of file + --signing-key-pub=$XMTPD_SIGNER_PUBLIC_KEY \ No newline at end of file diff --git a/dev/run-2 b/dev/run-2 index 5256212d..9ad3ac37 100755 --- a/dev/run-2 +++ b/dev/run-2 @@ -4,9 +4,7 @@ set -eu . dev/local.env -# second node keys -# {"private-key": "0x3c3040ee266ccd22f14c555b71093ea586e46226f1ed8d37cf9fb239d2b4ad79", "public-key": "0x03da7f733d870237f6dfd0074aea27edaf7b840d68e88641fb2a687de16bbe6a2b"} -export XMTPD_SIGNER_PRIVATE_KEY="0x3c3040ee266ccd22f14c555b71093ea586e46226f1ed8d37cf9fb239d2b4ad79" +export XMTPD_SIGNER_PRIVATE_KEY=$ANVIL_ACC_2_PRIVATE_KEY export XMTPD_PAYER_PRIVATE_KEY=$XMTPD_SIGNER_PRIVATE_KEY export XMTPD_DB_WRITER_CONNECTION_STRING="postgres://postgres:xmtp@localhost:8766/postgres?sslmode=disable" diff --git a/pkg/blockchain/registryAdmin.go b/pkg/blockchain/registryAdmin.go index 4b621d1a..8aab162b 100644 --- a/pkg/blockchain/registryAdmin.go +++ b/pkg/blockchain/registryAdmin.go @@ -4,6 +4,7 @@ import ( "context" "crypto/ecdsa" "fmt" + "math/big" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -62,6 +63,9 @@ func (n *NodeRegistryAdmin) AddNode( ownerAddress := common.HexToAddress(owner) signingKey := crypto.FromECDSAPub(signingKeyPub) + if n.signer == nil { + return fmt.Errorf("No signer provided") + } tx, err := n.contract.AddNode(&bind.TransactOpts{ Context: ctx, From: n.signer.FromAddress(), @@ -81,7 +85,39 @@ func (n *NodeRegistryAdmin) AddNode( tx.Hash(), ) } -func (n *NodeRegistryAdmin) GetAllNodes( + +/* +* +A NodeRegistryCaller is a struct responsible for calling public functions on the node registry +* +*/ +type NodeRegistryCaller struct { + client *ethclient.Client + contract *abis.NodesCaller + logger *zap.Logger +} + +func NewNodeRegistryCaller( + logger *zap.Logger, + client *ethclient.Client, + contractsOptions config.ContractsOptions, +) (*NodeRegistryCaller, error) { + contract, err := abis.NewNodesCaller( + common.HexToAddress(contractsOptions.NodesContractAddress), + client, + ) + if err != nil { + return nil, err + } + + return &NodeRegistryCaller{ + client: client, + logger: logger.Named("NodeRegistryAdmin"), + contract: contract, + }, nil +} + +func (n *NodeRegistryCaller) GetAllNodes( ctx context.Context, ) ([]abis.NodesNodeWithId, error) { @@ -89,3 +125,57 @@ func (n *NodeRegistryAdmin) GetAllNodes( Context: ctx, }) } + +func (n *NodeRegistryAdmin) UpdateHealth( + ctx context.Context, nodeId int64, health bool, +) error { + tx, err := n.contract.UpdateHealth( + &bind.TransactOpts{ + Context: ctx, + From: n.signer.FromAddress(), + Signer: n.signer.SignerFunc(), + }, + big.NewInt(nodeId), + health, + ) + + if err != nil { + return err + } + + return WaitForTransaction( + ctx, + n.logger, + n.client, + 2*time.Second, + 250*time.Millisecond, + tx.Hash(), + ) +} + +func (n *NodeRegistryAdmin) UpdateHttpAddress( + ctx context.Context, nodeId int64, address string, +) error { + tx, err := n.contract.UpdateHttpAddress( + &bind.TransactOpts{ + Context: ctx, + From: n.signer.FromAddress(), + Signer: n.signer.SignerFunc(), + }, + big.NewInt(nodeId), + address, + ) + + if err != nil { + return err + } + + return WaitForTransaction( + ctx, + n.logger, + n.client, + 2*time.Second, + 250*time.Millisecond, + tx.Hash(), + ) +} diff --git a/pkg/config/options.go b/pkg/config/options.go index b670c2f8..83f324b2 100644 --- a/pkg/config/options.go +++ b/pkg/config/options.go @@ -62,8 +62,17 @@ type SignerOptions struct { type GenerateKeyOptions struct{} -type GetAllNodesOptions struct { - AdminPrivateKey string `long:"admin-private-key" description:"Private key of the admin to register the node"` +type GetAllNodesOptions struct{} + +type UpdateHealthOptions struct { + AdminPrivateKey string `long:"admin-private-key" description:"Private key of the admin to administer the node"` + NodeId int64 `long:"node-id" description:"NodeId to update"` +} + +type UpdateAddressOptions struct { + PrivateKey string `long:"private-key" description:"Private key of node to be updated"` + NodeId int64 `long:"node-id" description:"NodeId to update"` + Address string `long:"address" description:"New HTTP address"` } type GetPubKeyOptions struct { diff --git a/pkg/utils/keys.go b/pkg/utils/keys.go index c8f2ce6f..df451443 100644 --- a/pkg/utils/keys.go +++ b/pkg/utils/keys.go @@ -30,3 +30,7 @@ func ParseEcdsaPublicKey(key string) (*ecdsa.PublicKey, error) { func EcdsaPublicKeyToString(key *ecdsa.PublicKey) string { return "0x" + common.Bytes2Hex(crypto.CompressPubkey(key)) } + +func EcdsaPublicKeyToAddress(key *ecdsa.PublicKey) string { + return "0x" + common.Bytes2Hex(crypto.PubkeyToAddress(*key).Bytes()) +} diff --git a/pkg/utils/log.go b/pkg/utils/log.go index 13e19bfb..6221aa5e 100644 --- a/pkg/utils/log.go +++ b/pkg/utils/log.go @@ -35,7 +35,5 @@ func BuildLogger(options config.LogOptions) (*zap.Logger, *zap.Config, error) { return nil, nil, err } - logger = logger.Named("replication") - return logger, &cfg, nil }