From 1cbc298b11908c2d3da820221a4469768fbcfa09 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Wed, 10 Jul 2024 00:17:34 -0700 Subject: [PATCH 01/29] Init rocketpool-cli pdao status --- rocketpool-cli/commands/pdao/status.go | 51 +++++++++++++++----------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/rocketpool-cli/commands/pdao/status.go b/rocketpool-cli/commands/pdao/status.go index 6aa966606..a16385d4e 100644 --- a/rocketpool-cli/commands/pdao/status.go +++ b/rocketpool-cli/commands/pdao/status.go @@ -16,9 +16,10 @@ import ( ) const ( - colorBlue string = "\033[36m" - colorReset string = "\033[0m" - colorGreen string = "\033[32m" + colorBlue string = "\033[36m" + colorReset string = "\033[0m" + colorGreen string = "\033[32m" + signallingAddressLink string = "https://docs.rocketpool.net/guides/houston/participate#setting-your-snapshot-signalling-address" ) func getStatus(c *cli.Context) error { @@ -48,34 +49,36 @@ func getStatus(c *cli.Context) error { } claimableBonds := claimableBondsResponse.Data.ClaimableBonds - // Snapshot voting status - fmt.Printf("%s=== Snapshot Voting ===%s\n", colorGreen, colorReset) + // Signalling Status + fmt.Printf("%s=== Signalling on Snapshot ===%s\n", colorGreen, colorReset) blankAddress := common.Address{} - if response.Data.SnapshotVotingDelegate == blankAddress { - fmt.Println("The node does not currently have a voting delegate set, which means it can only vote directly on Snapshot proposals (using a hardware wallet with the node mnemonic loaded).\nRun `rocketpool n sv
` to vote from a different wallet or have a delegate represent you. (See https://delegates.rocketpool.net for options)") + if response.Data.signallingAddress == blankAddress { + fmt.Printf("The node does not currently have a snapshot signalling address set.\nTo learn more about snapshot signalling, please visit %s.\n", signallingAddressLink) } else { - fmt.Printf("The node has a voting delegate of %s%s%s which can represent it when voting on Rocket Pool Snapshot governance proposals.\n", colorBlue, response.Data.SnapshotVotingDelegateFormatted, colorReset) + fmt.Printf("The node has a signalling address of %s%s%s which can represent it when voting on Rocket Pool Snapshot governance proposals.\n", colorBlue, response.SignallingAddressFormatted, colorReset) } if response.Data.SnapshotResponse.Error != "" { fmt.Printf("Unable to fetch latest voting information from snapshot.org: %s\n", response.Data.SnapshotResponse.Error) } else { voteCount := 0 + //todo calculate voteCount if len(response.Data.SnapshotResponse.ActiveSnapshotProposals) == 0 { - fmt.Print("Rocket Pool has no Snapshot governance proposals being voted on.\n") + fmt.Printf("Rocket Pool has no Snapshot governance proposals being voted on.") } else { fmt.Printf("Rocket Pool has %d Snapshot governance proposal(s) being voted on. You have voted on %d of those. See details using 'rocketpool network dao-proposals'.\n", len(response.Data.SnapshotResponse.ActiveSnapshotProposals), voteCount) } - fmt.Println("") + fmt.Println() } // Onchain Voting Status fmt.Printf("%s=== Onchain Voting ===%s\n", colorGreen, colorReset) if response.Data.IsVotingInitialized { - fmt.Println("The node has been initialized for onchain voting.") - + fmt.Printf("The node %s%s%s has been initialized for onchain voting.", colorBlue, response.Data.AccountAddressFormatted, colorReset) + fmt.Println() } else { - fmt.Println("The node has NOT been initialized for onchain voting. You need to run `rocketpool pdao initialize-voting` to participate in onchain votes.") + fmt.Printf("The node %s%s%s has NOT been initialized for onchain voting. You need to run `rocketpool pdao initialize-voting` to participate in onchain votes.", colorBlue, response.Data.AccountAddressFormatted, colorReset) + fmt.Println() } if response.Data.OnchainVotingDelegate == blankAddress { @@ -83,25 +86,29 @@ func getStatus(c *cli.Context) error { } else if response.Data.OnchainVotingDelegate == response.Data.AccountAddress { fmt.Println("The node doesn't have a delegate, which means it can vote directly on onchain proposals. You can have another node represent you by running `rocketpool p svd
`.") } else { - fmt.Printf("The node has a voting delegate of %s%s%s which can represent it when voting on Rocket Pool onchain governance proposals.\n", colorBlue, response.Data.OnchainVotingDelegateFormatted, colorReset) + fmt.Printf("The node has a voting delegate of %s%s%s which can represent it when voting on Rocket Pool onchain governance proposals.", colorBlue, response.Data.OnchainVotingDelegateFormatted, colorReset) + fmt.Println() } fmt.Printf("The node's local voting power: %.10f\n", eth.WeiToEth(response.Data.VotingPower)) - - fmt.Printf("Total voting power delegated to the node: %.10f\n", eth.WeiToEth(response.Data.TotalDelegatedVp)) - + if response.Data.IsNodeRegistered { + fmt.Printf("Total voting power delegated to the node: %.10f", eth.WeiToEth(response.Data.TotalDelegatedVp)) + fmt.Println() + } else { + fmt.Print("The node must register uising 'rocketpool node register' to be eligible to receive delegated voting power") + } fmt.Printf("Network total initialized voting power: %.10f\n", eth.WeiToEth(response.Data.SumVotingPower)) - fmt.Println("") // Claimable Bonds Status: - fmt.Printf("%s=== Claimable RPL Bonds ===%s\n", colorGreen, colorReset) + fmt.Printf("%s=== Claimable RPL Bonds ===%s", colorGreen, colorReset) + fmt.Println() if response.Data.IsRPLLockingAllowed { - fmt.Print("The node is allowed to lock RPL to create governance proposals/challenges.\n") + fmt.Println("The node is allowed to lock RPL to create governance proposals/challenges.") if response.Data.NodeRPLLocked.Cmp(big.NewInt(0)) != 0 { - fmt.Printf("The node currently has %.6f RPL locked.\n", + fmt.Printf("The node currently has %.6f RPL locked.", utilsMath.RoundDown(eth.WeiToEth(response.Data.NodeRPLLocked), 6)) + fmt.Println() } - } else { fmt.Print("The node is NOT allowed to lock RPL to create governance proposals/challenges. Use 'rocketpool node allow-rpl-locking, to allow RPL locking.\n") } From 7f264f9caba88499999db6250a1c89787b7efaee Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Wed, 10 Jul 2024 15:55:44 -0700 Subject: [PATCH 02/29] Init rocketpool-daemon pdao status --- rocketpool-daemon/api/pdao/status.go | 61 +++++++++++++++++++--------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/rocketpool-daemon/api/pdao/status.go b/rocketpool-daemon/api/pdao/status.go index 6ec184ec2..1b423da61 100644 --- a/rocketpool-daemon/api/pdao/status.go +++ b/rocketpool-daemon/api/pdao/status.go @@ -2,6 +2,7 @@ package pdao import ( "fmt" + "math/big" "net/url" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -13,6 +14,7 @@ import ( "github.com/rocket-pool/node-manager-core/api/server" "github.com/rocket-pool/node-manager-core/api/types" "github.com/rocket-pool/node-manager-core/beacon" + "github.com/rocket-pool/node-manager-core/eth" "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/proposals" "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/utils" "github.com/rocket-pool/smartnode/v2/shared/config" @@ -48,66 +50,85 @@ type protocolDaoGetStatusContext struct { handler *ProtocolDaoHandler cfg *config.SmartNodeConfig rp *rocketpool.RocketPool + ec eth.IExecutionClient bc beacon.IBeaconClient - propMgr *proposals.ProposalManager + node *node.Node + propMgr *proposals.ProposalManager + totalDelegatedVP *big.Int + blockNumber uint64 } -func (c *protocolDaoGetStatusContext) PrepareData(data *api.ProtocolDAOStatusResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { +func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) { sp := c.handler.serviceProvider - rp := sp.GetRocketPool() - ec := sp.GetEthClient() + c.rp = sp.GetRocketPool() + c.ec = sp.GetEthClient() c.cfg = sp.GetConfig() c.rp = sp.GetRocketPool() c.bc = sp.GetBeaconClient() - ctx := c.handler.ctx - nodeAddress, _ := sp.GetWallet().GetAddress() // Requirements + err := sp.RequireNodeAddress() + if err != nil { + return types.ResponseStatus_AddressNotPresent, err + } status, err := sp.RequireNodeRegistered(c.handler.ctx) if err != nil { return status, err } // Bindings - node, err := node.NewNode(rp, nodeAddress) + c.node, err = node.NewNode(c.rp, nodeAddress) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error creating node %s binding: %w", nodeAddress.Hex(), err) } - c.propMgr, err = proposals.NewProposalManager(ctx, c.handler.logger.Logger, c.cfg, c.rp, c.bc) + c.propMgr, err = proposals.NewProposalManager(c.handler.ctx, c.handler.logger.Logger, c.cfg, c.rp, c.bc) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error creating proposal manager: %w", err) } - // Get the latest block - blockNumber, err := ec.BlockNumber(c.handler.ctx) + c.blockNumber, err = c.ec.BlockNumber(c.handler.ctx) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error getting latest block number: %w", err) } - data.BlockNumber = uint32(blockNumber) - totalDelegatedVP, _, _, err := c.propMgr.GetArtifactsForVoting(uint32(blockNumber), nodeAddress) + c.totalDelegatedVP, _, _, err = c.propMgr.GetArtifactsForVoting(uint32(c.blockNumber), nodeAddress) if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("error getting voting artifacts for node %s at block %d: %w", nodeAddress.Hex(), blockNumber, err) + return types.ResponseStatus_Error, fmt.Errorf("error getting voting artifacts for node %s at block %d: %w", nodeAddress.Hex(), c.blockNumber, err) } - data.TotalDelegatedVp = totalDelegatedVP - votingTree, err := c.propMgr.GetNetworkTree(uint32(blockNumber), nil) + return types.ResponseStatus_Success, nil +} + +func (c *protocolDaoGetStatusContext) GetState(mc *batch.MultiCaller) { + eth.AddQueryablesToMulticall(mc, + // Node + c.node.Exists, + c.node.IsVotingInitialized, + ) +} + +func (c *protocolDaoGetStatusContext) PrepareData(data *api.ProtocolDAOStatusResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { + + data.BlockNumber = uint32(c.blockNumber) + data.TotalDelegatedVp = c.totalDelegatedVP + + votingTree, err := c.propMgr.GetNetworkTree(uint32(c.blockNumber), nil) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error getting network tree") } data.SumVotingPower = votingTree.Nodes[0].Sum // Get the voting power and delegate at that block - err = rp.Query(func(mc *batch.MultiCaller) error { - node.GetVotingPowerAtBlock(mc, &data.VotingPower, data.BlockNumber) - node.GetVotingDelegateAtBlock(mc, &data.OnchainVotingDelegate, data.BlockNumber) + err = c.rp.Query(func(mc *batch.MultiCaller) error { + c.node.GetVotingPowerAtBlock(mc, &data.VotingPower, data.BlockNumber) + c.node.GetVotingDelegateAtBlock(mc, &data.OnchainVotingDelegate, data.BlockNumber) return nil }, nil) if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("error getting voting info for block %d: %w", blockNumber, err) + return types.ResponseStatus_Error, fmt.Errorf("error getting voting info for block %d: %w", c.blockNumber, err) } - data.OnchainVotingDelegateFormatted = utils.GetFormattedAddress(ec, data.OnchainVotingDelegate) + data.OnchainVotingDelegateFormatted = utils.GetFormattedAddress(c.ec, data.OnchainVotingDelegate) return types.ResponseStatus_Success, nil } From 05b8a8f226ea76d86e7d93c798036cb668e3cd74 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Wed, 10 Jul 2024 16:53:07 -0700 Subject: [PATCH 03/29] Init RocketSignerRegistry binding --- .../contracts/rocket-signer-registry.go | 88 +++++++++++++++++++ shared/config/resources.go | 5 ++ 2 files changed, 93 insertions(+) create mode 100644 rocketpool-daemon/common/contracts/rocket-signer-registry.go diff --git a/rocketpool-daemon/common/contracts/rocket-signer-registry.go b/rocketpool-daemon/common/contracts/rocket-signer-registry.go new file mode 100644 index 000000000..784b87976 --- /dev/null +++ b/rocketpool-daemon/common/contracts/rocket-signer-registry.go @@ -0,0 +1,88 @@ +package contracts + +import ( + "fmt" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + batch "github.com/rocket-pool/batch-query" + "github.com/rocket-pool/node-manager-core/eth" +) + +const ( + rocketSignerRegistryAbiString string = "[{\"type\":\"function\",\"name\":\"clearSigner\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"nodeToSigner\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setSigner\",\"inputs\":[{\"name\":\"_signer\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_v\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"_r\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_s\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"signerToNode\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"SignerSet\",\"inputs\":[{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"signerAddress\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"StringsInsufficientHexLength\",\"inputs\":[{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"length\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}]" +) + +// ABI cache +var rocketSignerRegistryAbi abi.ABI +var rocketSignerRegistryOnce sync.Once + +// =============== +// === Structs === +// =============== + +// Binding for Rocket Signer Registry +type RocketSignerRegistry struct { + contract *eth.Contract + txMgr *eth.TransactionManager +} + +// ==================== +// === Constructors === +// ==================== + +// Creates a new Rocket Signer Registry contract binding +func NewRocketSignerRegistry(address common.Address, client eth.IExecutionClient, txMgr *eth.TransactionManager) (*RocketSignerRegistry, error) { + // Parse the ABI + var err error + snapshotOnce.Do(func() { + var parsedAbi abi.ABI + parsedAbi, err = abi.JSON(strings.NewReader(rocketSignerRegistryAbiString)) + if err == nil { + snapshotAbi = parsedAbi + } + }) + if err != nil { + return nil, fmt.Errorf("error parsing rocket signer registry ABI: %w", err) + } + + // Create the contract + contract := ð.Contract{ + ContractImpl: bind.NewBoundContract(address, rocketSignerRegistryAbi, client, client, client), + Address: address, + ABI: &rocketSignerRegistryAbi, + } + + return &RocketSignerRegistry{ + contract: contract, + }, nil +} + +// ============= +// === Calls === +// ============= + +// Get the delegate for the provided address +func (c *RocketSignerRegistry) NodeToSigner(mc *batch.MultiCaller, out *common.Address, address common.Address, id common.Hash) { + eth.AddCallToMulticaller(mc, c.contract, out, "nodeToSigner", address, id) +} +func (c *RocketSignerRegistry) SignerToNode(mc *batch.MultiCaller, out *common.Address, address common.Address, id common.Hash) { + eth.AddCallToMulticaller(mc, c.contract, out, "signerToNode", address, id) +} + +// ==================== +// === Transactions === +// ==================== + +// Get info for setting the snapshot delegate +func (c *SnapshotDelegation) SetSigner(id common.Hash, delegate common.Address, opts *bind.TransactOpts) (*eth.TransactionInfo, error) { + return c.txMgr.CreateTransactionInfo(c.contract, "setSigner", opts, id, delegate) +} + +// Get info for clearing the snapshot delegate +func (c *SnapshotDelegation) clearSigner(id common.Hash, opts *bind.TransactOpts) (*eth.TransactionInfo, error) { + return c.txMgr.CreateTransactionInfo(c.contract, "clearSigner", opts, id) +} diff --git a/shared/config/resources.go b/shared/config/resources.go index 7ecd123ec..dee5520f8 100644 --- a/shared/config/resources.go +++ b/shared/config/resources.go @@ -56,6 +56,9 @@ type RocketPoolResources struct { // The contract address of rocketNetworkBalances from v1.2.0 V1_2_0_NetworkBalancesAddress *common.Address + // The contract Address for Rocket Signer Registry + RocketSignerRegistryAddress *common.Address + // The contract address for Snapshot delegation SnapshotDelegationAddress *common.Address @@ -121,6 +124,7 @@ func newRocketPoolResources(network config.Network) *RocketPoolResources { PreviousRewardsPoolAddresses: []common.Address{ common.HexToAddress("0x594Fb75D3dc2DFa0150Ad03F99F97817747dd4E1"), }, + RocketSignerRegistryAddress: hexToAddressPtr("0xc1062617d10Ae99E09D941b60746182A87eAB38F"), PreviousProtocolDaoVerifierAddresses: []common.Address{}, OptimismPriceMessengerAddress: hexToAddressPtr("0xdddcf2c25d50ec22e67218e873d46938650d03a7"), PolygonPriceMessengerAddress: hexToAddressPtr("0xb1029Ac2Be4e08516697093e2AFeC435057f3511"), @@ -156,6 +160,7 @@ func newRocketPoolResources(network config.Network) *RocketPoolResources { PreviousRewardsPoolAddresses: []common.Address{ common.HexToAddress("0x4a625C617a44E60F74E3fe3bf6d6333b63766e91"), }, + RocketSignerRegistryAddress: hexToAddressPtr("0x657FDE6B4764E26A81A323dbb79791A11B90dD91"), PreviousProtocolDaoVerifierAddresses: nil, OptimismPriceMessengerAddress: nil, PolygonPriceMessengerAddress: nil, From 5c5fc4b840825c9b71d0a3ac480ee13ee67dcb01 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Wed, 10 Jul 2024 17:03:35 -0700 Subject: [PATCH 04/29] Add RocketSignerRegistry to ServiceProvider Add RocketSignerRegistry to ServiceProvider --- .../common/services/service-provider.go | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/rocketpool-daemon/common/services/service-provider.go b/rocketpool-daemon/common/services/service-provider.go index 6e91b57be..aaa83922a 100644 --- a/rocketpool-daemon/common/services/service-provider.go +++ b/rocketpool-daemon/common/services/service-provider.go @@ -22,11 +22,12 @@ type ServiceProvider struct { *services.ServiceProvider // Services - cfg *config.SmartNodeConfig - rocketPool *rocketpool.RocketPool - validatorManager *validator.ValidatorManager - snapshotDelegation *contracts.SnapshotDelegation - watchtowerLog *log.Logger + cfg *config.SmartNodeConfig + rocketPool *rocketpool.RocketPool + validatorManager *validator.ValidatorManager + snapshotDelegation *contracts.SnapshotDelegation + rocketSignerRegistry *contracts.RocketSignerRegistry + watchtowerLog *log.Logger // Internal use loadedContractVersion *version.Version @@ -109,6 +110,15 @@ func CreateServiceProviderFromComponents(cfg *config.SmartNodeConfig, sp *servic return nil, fmt.Errorf("error creating snapshot delegation binding: %w", err) } } + // Rocket Signer Registry + var rocketSignerRegistry *contracts.RocketSignerRegistry + registryAddress := resources.RocketSignerRegistryAddress + if registryAddress != nil { + rocketSignerRegistry, err = contracts.NewRocketSignerRegistry(*registryAddress, sp.GetEthClient(), sp.GetTransactionManager()) + if err != nil { + return nil, fmt.Errorf("error creating rocket signer registry binding: %w", err) + } + } // Create the provider defaultVersion, _ := version.NewSemver("0.0.0") @@ -119,6 +129,7 @@ func CreateServiceProviderFromComponents(cfg *config.SmartNodeConfig, sp *servic rocketPool: rp, validatorManager: vMgr, snapshotDelegation: snapshotDelegation, + rocketSignerRegistry: rocketSignerRegistry, watchtowerLog: watchtowerLogger, loadedContractVersion: defaultVersion, refreshLock: &sync.Mutex{}, @@ -154,6 +165,10 @@ func (p *ServiceProvider) GetSnapshotDelegation() *contracts.SnapshotDelegation return p.snapshotDelegation } +func (p *ServiceProvider) GetRocketSignerRegistry() *contracts.RocketSignerRegistry { + return p.rocketSignerRegistry +} + func (p *ServiceProvider) GetWatchtowerLogger() *log.Logger { return p.watchtowerLog } From 59cf6952d4f563f1208887c1478801ee7ed59ef0 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Thu, 11 Jul 2024 14:26:01 -0700 Subject: [PATCH 05/29] Add registry binding --- rocketpool-cli/commands/pdao/status.go | 5 +- rocketpool-daemon/api/pdao/status.go | 78 +++++++++++-------- .../contracts/rocket-signer-registry.go | 18 ++--- shared/types/api/pdao.go | 23 +++--- 4 files changed, 70 insertions(+), 54 deletions(-) diff --git a/rocketpool-cli/commands/pdao/status.go b/rocketpool-cli/commands/pdao/status.go index a16385d4e..864b7d0ca 100644 --- a/rocketpool-cli/commands/pdao/status.go +++ b/rocketpool-cli/commands/pdao/status.go @@ -49,13 +49,14 @@ func getStatus(c *cli.Context) error { } claimableBonds := claimableBondsResponse.Data.ClaimableBonds + fmt.Printf("block number: %d\n", response.Data.BlockNumber) // Signalling Status fmt.Printf("%s=== Signalling on Snapshot ===%s\n", colorGreen, colorReset) blankAddress := common.Address{} - if response.Data.signallingAddress == blankAddress { + if response.Data.SignallingAddress == blankAddress { fmt.Printf("The node does not currently have a snapshot signalling address set.\nTo learn more about snapshot signalling, please visit %s.\n", signallingAddressLink) } else { - fmt.Printf("The node has a signalling address of %s%s%s which can represent it when voting on Rocket Pool Snapshot governance proposals.\n", colorBlue, response.SignallingAddressFormatted, colorReset) + fmt.Printf("The node has a signalling address of %s%s%s which can represent it when voting on Rocket Pool Snapshot governance proposals.\n", colorBlue, response.Data.SignallingAddressFormatted, colorReset) } if response.Data.SnapshotResponse.Error != "" { diff --git a/rocketpool-daemon/api/pdao/status.go b/rocketpool-daemon/api/pdao/status.go index 1b423da61..8bd91bbce 100644 --- a/rocketpool-daemon/api/pdao/status.go +++ b/rocketpool-daemon/api/pdao/status.go @@ -6,6 +6,7 @@ import ( "net/url" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" "github.com/gorilla/mux" batch "github.com/rocket-pool/batch-query" "github.com/rocket-pool/rocketpool-go/v2/node" @@ -15,8 +16,8 @@ import ( "github.com/rocket-pool/node-manager-core/api/types" "github.com/rocket-pool/node-manager-core/beacon" "github.com/rocket-pool/node-manager-core/eth" + "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/contracts" "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/proposals" - "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/utils" "github.com/rocket-pool/smartnode/v2/shared/config" "github.com/rocket-pool/smartnode/v2/shared/types/api" ) @@ -37,7 +38,7 @@ func (f *protocolDaoGetStatusContextFactory) Create(args url.Values) (*protocolD } func (f *protocolDaoGetStatusContextFactory) RegisterRoute(router *mux.Router) { - server.RegisterQuerylessGet[*protocolDaoGetStatusContext, api.ProtocolDAOStatusResponse]( + server.RegisterSingleStageRoute[*protocolDaoGetStatusContext, api.ProtocolDAOStatusResponse]( router, "get-status", f, f.handler.logger.Logger, f.handler.serviceProvider.ServiceProvider, ) } @@ -47,26 +48,30 @@ func (f *protocolDaoGetStatusContextFactory) RegisterRoute(router *mux.Router) { // =============== type protocolDaoGetStatusContext struct { - handler *ProtocolDaoHandler - cfg *config.SmartNodeConfig - rp *rocketpool.RocketPool - ec eth.IExecutionClient - bc beacon.IBeaconClient - - node *node.Node - propMgr *proposals.ProposalManager - totalDelegatedVP *big.Int - blockNumber uint64 + handler *ProtocolDaoHandler + cfg *config.SmartNodeConfig + rp *rocketpool.RocketPool + ec eth.IExecutionClient + bc beacon.IBeaconClient + registry *contracts.RocketSignerRegistry + + node *node.Node + propMgr *proposals.ProposalManager + totalDelegatedVP *big.Int + blockNumber uint64 + isNodeRegistered bool + signallingAddress common.Address + votingTree *proposals.NetworkVotingTree } func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) { sp := c.handler.serviceProvider - c.rp = sp.GetRocketPool() - c.ec = sp.GetEthClient() c.cfg = sp.GetConfig() c.rp = sp.GetRocketPool() + c.ec = sp.GetEthClient() c.bc = sp.GetBeaconClient() nodeAddress, _ := sp.GetWallet().GetAddress() + c.registry = sp.GetRocketSignerRegistry() // Requirements err := sp.RequireNodeAddress() @@ -92,13 +97,18 @@ func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error getting latest block number: %w", err) } - c.totalDelegatedVP, _, _, err = c.propMgr.GetArtifactsForVoting(uint32(c.blockNumber), nodeAddress) if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("error getting voting artifacts for node %s at block %d: %w", nodeAddress.Hex(), c.blockNumber, err) + return types.ResponseStatus_Error, fmt.Errorf("error getting voting artifacts for node %s at block %d: %w", nodeAddress.Hex(), blockNumber, err) + } + + c.votingTree, err = c.propMgr.GetNetworkTree(uint32(c.blockNumber), nil) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("error getting network tree") } return types.ResponseStatus_Success, nil + } func (c *protocolDaoGetStatusContext) GetState(mc *batch.MultiCaller) { @@ -107,28 +117,32 @@ func (c *protocolDaoGetStatusContext) GetState(mc *batch.MultiCaller) { c.node.Exists, c.node.IsVotingInitialized, ) + + // Snapshot Registry + if c.registry != nil { + c.registry.NodeToSigner(mc, &c.signallingAddress, c.node.Address) + } } func (c *protocolDaoGetStatusContext) PrepareData(data *api.ProtocolDAOStatusResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { data.BlockNumber = uint32(c.blockNumber) - data.TotalDelegatedVp = c.totalDelegatedVP - - votingTree, err := c.propMgr.GetNetworkTree(uint32(c.blockNumber), nil) - if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("error getting network tree") - } - data.SumVotingPower = votingTree.Nodes[0].Sum + // data.TotalDelegatedVp = c.totalDelegatedVP + // data.IsNodeRegistered = c.isNodeRegistered + // data.SignallingAddress = c.signallingAddress + data.AccountAddress = c.node.Address + data.IsVotingInitialized = c.node.IsVotingInitialized.Get() + // data.SumVotingPower = c.votingTree.Nodes[0].Sum // Get the voting power and delegate at that block - err = c.rp.Query(func(mc *batch.MultiCaller) error { - c.node.GetVotingPowerAtBlock(mc, &data.VotingPower, data.BlockNumber) - c.node.GetVotingDelegateAtBlock(mc, &data.OnchainVotingDelegate, data.BlockNumber) - return nil - }, nil) - if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("error getting voting info for block %d: %w", c.blockNumber, err) - } - data.OnchainVotingDelegateFormatted = utils.GetFormattedAddress(c.ec, data.OnchainVotingDelegate) + // var err = c.rp.Query(func(mc *batch.MultiCaller) error { + // c.node.GetVotingPowerAtBlock(mc, &data.VotingPower, data.BlockNumber) + // c.node.GetVotingDelegateAtBlock(mc, &data.OnchainVotingDelegate, data.BlockNumber) + // return nil + // }, nil) + // if err != nil { + // return types.ResponseStatus_Error, fmt.Errorf("error getting voting info for block %d: %w", c.blockNumber, err) + // } + // data.OnchainVotingDelegateFormatted = utils.GetFormattedAddress(c.ec, data.OnchainVotingDelegate) return types.ResponseStatus_Success, nil } diff --git a/rocketpool-daemon/common/contracts/rocket-signer-registry.go b/rocketpool-daemon/common/contracts/rocket-signer-registry.go index 784b87976..ed9e9b745 100644 --- a/rocketpool-daemon/common/contracts/rocket-signer-registry.go +++ b/rocketpool-daemon/common/contracts/rocket-signer-registry.go @@ -66,23 +66,23 @@ func NewRocketSignerRegistry(address common.Address, client eth.IExecutionClient // ============= // Get the delegate for the provided address -func (c *RocketSignerRegistry) NodeToSigner(mc *batch.MultiCaller, out *common.Address, address common.Address, id common.Hash) { - eth.AddCallToMulticaller(mc, c.contract, out, "nodeToSigner", address, id) +func (c *RocketSignerRegistry) NodeToSigner(mc *batch.MultiCaller, out *common.Address, address common.Address) { + eth.AddCallToMulticaller(mc, c.contract, out, "nodeToSigner", address) } -func (c *RocketSignerRegistry) SignerToNode(mc *batch.MultiCaller, out *common.Address, address common.Address, id common.Hash) { - eth.AddCallToMulticaller(mc, c.contract, out, "signerToNode", address, id) +func (c *RocketSignerRegistry) SignerToNode(mc *batch.MultiCaller, out *common.Address, address common.Address) { + eth.AddCallToMulticaller(mc, c.contract, out, "signerToNode", address) } // ==================== // === Transactions === // ==================== -// Get info for setting the snapshot delegate -func (c *SnapshotDelegation) SetSigner(id common.Hash, delegate common.Address, opts *bind.TransactOpts) (*eth.TransactionInfo, error) { - return c.txMgr.CreateTransactionInfo(c.contract, "setSigner", opts, id, delegate) +// Get info for setting the signalling address +func (c *SnapshotDelegation) SetSigner(id common.Hash, signer common.Address, opts *bind.TransactOpts) (*eth.TransactionInfo, error) { + return c.txMgr.CreateTransactionInfo(c.contract, "setSigner", opts, signer) } -// Get info for clearing the snapshot delegate +// Get info for clearing the signalling address func (c *SnapshotDelegation) clearSigner(id common.Hash, opts *bind.TransactOpts) (*eth.TransactionInfo, error) { - return c.txMgr.CreateTransactionInfo(c.contract, "clearSigner", opts, id) + return c.txMgr.CreateTransactionInfo(c.contract, "clearSigner", opts) } diff --git a/shared/types/api/pdao.go b/shared/types/api/pdao.go index bc41acb57..b7a65975a 100644 --- a/shared/types/api/pdao.go +++ b/shared/types/api/pdao.go @@ -301,17 +301,17 @@ type ProtocolDaoCurrentVotingDelegateData struct { } type ProtocolDAOStatusResponse struct { - Status string `json:"status"` - Error string `json:"error"` - VotingPower *big.Int `json:"votingPower"` - OnchainVotingDelegate common.Address `json:"onchainVotingDelegate"` - OnchainVotingDelegateFormatted string `json:"onchainVotingDelegateFormatted"` - BlockNumber uint32 `json:"blockNumber"` - VerifyEnabled bool `json:"verifyEnabled"` - IsVotingInitialized bool `json:"isVotingInitialized"` - SnapshotVotingDelegate common.Address `json:"snapshotVotingDelegate"` - SnapshotVotingDelegateFormatted string `json:"snapshotVotingDelegateFormatted"` - SnapshotResponse struct { + Status string `json:"status"` + Error string `json:"error"` + VotingPower *big.Int `json:"votingPower"` + OnchainVotingDelegate common.Address `json:"onchainVotingDelegate"` + OnchainVotingDelegateFormatted string `json:"onchainVotingDelegateFormatted"` + BlockNumber uint32 `json:"blockNumber"` + VerifyEnabled bool `json:"verifyEnabled"` + IsVotingInitialized bool `json:"isVotingInitialized"` + SignallingAddress common.Address `json:"signallingAddress"` + SignallingAddressFormatted string `json:"signallingAddressFormatted"` + SnapshotResponse struct { Error string `json:"error"` ActiveSnapshotProposals []*sharedtypes.SnapshotProposal `json:"activeSnapshotProposals"` } `json:"snapshotResponse"` @@ -321,4 +321,5 @@ type ProtocolDAOStatusResponse struct { AccountAddressFormatted string `json:"accountAddressFormatted"` TotalDelegatedVp *big.Int `json:"totalDelegatedVp"` SumVotingPower *big.Int `json:"sumVotingPower"` + IsNodeRegistered bool `json:"isNodeRegistered"` } From f0c1ddcbdbded79c8369b8937d625b706351a024 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Thu, 11 Jul 2024 21:05:45 -0700 Subject: [PATCH 06/29] Populating fields in API response --- rocketpool-cli/commands/pdao/status.go | 13 ++++- rocketpool-daemon/api/pdao/status.go | 48 +++++++++++-------- .../common/proposals/proposal-manager.go | 3 ++ 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/rocketpool-cli/commands/pdao/status.go b/rocketpool-cli/commands/pdao/status.go index 864b7d0ca..78b16ee35 100644 --- a/rocketpool-cli/commands/pdao/status.go +++ b/rocketpool-cli/commands/pdao/status.go @@ -50,6 +50,15 @@ func getStatus(c *cli.Context) error { claimableBonds := claimableBondsResponse.Data.ClaimableBonds fmt.Printf("block number: %d\n", response.Data.BlockNumber) + fmt.Printf("Voting initialized: %t\n", response.Data.IsVotingInitialized) + fmt.Printf("Node Registered: %t\n", response.Data.IsNodeRegistered) + fmt.Printf("Voting power %d\n", response.Data.VotingPower) + fmt.Printf("Delegated voting power %d\n", response.Data.TotalDelegatedVp) + fmt.Printf("On chain delegate formatted: %s\n", response.Data.OnchainVotingDelegateFormatted) + fmt.Printf("Account Address formatted: %s\n", response.Data.AccountAddressFormatted) + fmt.Printf("Signalling Address formatted %s\n", response.Data.SignallingAddressFormatted) + fmt.Printf("Is RPL locking allowed %t\n", response.Data.IsRPLLockingAllowed) + // Signalling Status fmt.Printf("%s=== Signalling on Snapshot ===%s\n", colorGreen, colorReset) blankAddress := common.Address{} @@ -66,6 +75,7 @@ func getStatus(c *cli.Context) error { //todo calculate voteCount if len(response.Data.SnapshotResponse.ActiveSnapshotProposals) == 0 { fmt.Printf("Rocket Pool has no Snapshot governance proposals being voted on.") + fmt.Println() } else { fmt.Printf("Rocket Pool has %d Snapshot governance proposal(s) being voted on. You have voted on %d of those. See details using 'rocketpool network dao-proposals'.\n", len(response.Data.SnapshotResponse.ActiveSnapshotProposals), voteCount) } @@ -95,7 +105,8 @@ func getStatus(c *cli.Context) error { fmt.Printf("Total voting power delegated to the node: %.10f", eth.WeiToEth(response.Data.TotalDelegatedVp)) fmt.Println() } else { - fmt.Print("The node must register uising 'rocketpool node register' to be eligible to receive delegated voting power") + fmt.Print("The node must register using 'rocketpool node register' to be eligible to receive delegated voting power") + fmt.Println() } fmt.Printf("Network total initialized voting power: %.10f\n", eth.WeiToEth(response.Data.SumVotingPower)) fmt.Println("") diff --git a/rocketpool-daemon/api/pdao/status.go b/rocketpool-daemon/api/pdao/status.go index 8bd91bbce..9c529f481 100644 --- a/rocketpool-daemon/api/pdao/status.go +++ b/rocketpool-daemon/api/pdao/status.go @@ -18,6 +18,7 @@ import ( "github.com/rocket-pool/node-manager-core/eth" "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/contracts" "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/proposals" + "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/utils" "github.com/rocket-pool/smartnode/v2/shared/config" "github.com/rocket-pool/smartnode/v2/shared/types/api" ) @@ -81,6 +82,8 @@ func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) status, err := sp.RequireNodeRegistered(c.handler.ctx) if err != nil { return status, err + } else { + c.isNodeRegistered = true } // Bindings @@ -99,9 +102,8 @@ func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) } c.totalDelegatedVP, _, _, err = c.propMgr.GetArtifactsForVoting(uint32(c.blockNumber), nodeAddress) if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("error getting voting artifacts for node %s at block %d: %w", nodeAddress.Hex(), blockNumber, err) + return types.ResponseStatus_Error, fmt.Errorf("error getting voting artifacts for node %s at block %d: %w", nodeAddress.Hex(), c.blockNumber, err) } - c.votingTree, err = c.propMgr.GetNetworkTree(uint32(c.blockNumber), nil) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error getting network tree") @@ -116,33 +118,39 @@ func (c *protocolDaoGetStatusContext) GetState(mc *batch.MultiCaller) { // Node c.node.Exists, c.node.IsVotingInitialized, + c.node.IsRplLockingAllowed, + c.node.RplLocked, ) - // Snapshot Registry - if c.registry != nil { - c.registry.NodeToSigner(mc, &c.signallingAddress, c.node.Address) - } + // // Snapshot Registry + // if c.registry != nil { + // c.registry.NodeToSigner(mc, &c.signallingAddress, c.node.Address) + // } } func (c *protocolDaoGetStatusContext) PrepareData(data *api.ProtocolDAOStatusResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { data.BlockNumber = uint32(c.blockNumber) - // data.TotalDelegatedVp = c.totalDelegatedVP - // data.IsNodeRegistered = c.isNodeRegistered + data.IsNodeRegistered = c.isNodeRegistered // data.SignallingAddress = c.signallingAddress data.AccountAddress = c.node.Address + data.AccountAddressFormatted = utils.GetFormattedAddress(c.ec, data.AccountAddress) data.IsVotingInitialized = c.node.IsVotingInitialized.Get() - // data.SumVotingPower = c.votingTree.Nodes[0].Sum - - // Get the voting power and delegate at that block - // var err = c.rp.Query(func(mc *batch.MultiCaller) error { - // c.node.GetVotingPowerAtBlock(mc, &data.VotingPower, data.BlockNumber) - // c.node.GetVotingDelegateAtBlock(mc, &data.OnchainVotingDelegate, data.BlockNumber) - // return nil - // }, nil) - // if err != nil { - // return types.ResponseStatus_Error, fmt.Errorf("error getting voting info for block %d: %w", c.blockNumber, err) - // } - // data.OnchainVotingDelegateFormatted = utils.GetFormattedAddress(c.ec, data.OnchainVotingDelegate) + data.SumVotingPower = c.votingTree.Nodes[0].Sum + data.TotalDelegatedVp = c.totalDelegatedVP + data.IsRPLLockingAllowed = c.node.IsRplLockingAllowed.Get() + data.NodeRPLLocked = c.node.RplLocked.Get() + data.VerifyEnabled = c.cfg.VerifyProposals.Value + + //Get the voting power and delegate at that block + var err = c.rp.Query(func(mc *batch.MultiCaller) error { + c.node.GetVotingPowerAtBlock(mc, &data.VotingPower, data.BlockNumber) + c.node.GetVotingDelegateAtBlock(mc, &data.OnchainVotingDelegate, data.BlockNumber) + return nil + }, nil) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("error getting voting info for block %d: %w", c.blockNumber, err) + } + data.OnchainVotingDelegateFormatted = utils.GetFormattedAddress(c.ec, data.OnchainVotingDelegate) return types.ResponseStatus_Success, nil } diff --git a/rocketpool-daemon/common/proposals/proposal-manager.go b/rocketpool-daemon/common/proposals/proposal-manager.go index 09c07037c..df68e4b3b 100644 --- a/rocketpool-daemon/common/proposals/proposal-manager.go +++ b/rocketpool-daemon/common/proposals/proposal-manager.go @@ -216,6 +216,9 @@ func (m *ProposalManager) GetArtifactsForVoting(blockNumber uint32, nodeAddress // Get the artifacts totalDelegatedVp := nodeTree.Nodes[0].Sum + if totalDelegatedVp == nil { + totalDelegatedVp = big.NewInt(0) + } treeIndex := getTreeNodeIndexFromRPNodeIndex(snapshot, nodeIndex) proofPtrs := networkTree.generateMerkleProof(treeIndex) From 9cecead1aee3ac3ad6e8bf3ebccf4e993f8d2eb5 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Sat, 13 Jul 2024 16:50:48 -0700 Subject: [PATCH 07/29] Init ParseEIP712 helper function --- rocketpool-daemon/api/pdao/status.go | 15 ++++--- .../contracts/rocket-signer-registry.go | 6 +-- shared/utils/input.go | 41 +++++++++++++++++++ 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/rocketpool-daemon/api/pdao/status.go b/rocketpool-daemon/api/pdao/status.go index 9c529f481..f723ca72c 100644 --- a/rocketpool-daemon/api/pdao/status.go +++ b/rocketpool-daemon/api/pdao/status.go @@ -72,7 +72,6 @@ func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) c.ec = sp.GetEthClient() c.bc = sp.GetBeaconClient() nodeAddress, _ := sp.GetWallet().GetAddress() - c.registry = sp.GetRocketSignerRegistry() // Requirements err := sp.RequireNodeAddress() @@ -85,6 +84,10 @@ func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) } else { c.isNodeRegistered = true } + c.registry = sp.GetRocketSignerRegistry() + if c.registry == nil { + return types.ResponseStatus_ResourceNotFound, err + } // Bindings c.node, err = node.NewNode(c.rp, nodeAddress) @@ -95,7 +98,6 @@ func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error creating proposal manager: %w", err) } - // Get the latest block c.blockNumber, err = c.ec.BlockNumber(c.handler.ctx) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error getting latest block number: %w", err) @@ -108,6 +110,7 @@ func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error getting network tree") } + // todo add https://github.com/rocket-pool/smartnode/blob/master/rocketpool/api/pdao/status.go#L132 return types.ResponseStatus_Success, nil @@ -122,10 +125,10 @@ func (c *protocolDaoGetStatusContext) GetState(mc *batch.MultiCaller) { c.node.RplLocked, ) - // // Snapshot Registry - // if c.registry != nil { - // c.registry.NodeToSigner(mc, &c.signallingAddress, c.node.Address) - // } + // Snapshot Registry + if c.registry != nil { + c.registry.NodeToSigner(mc, &c.signallingAddress, c.node.Address) + } } func (c *protocolDaoGetStatusContext) PrepareData(data *api.ProtocolDAOStatusResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { diff --git a/rocketpool-daemon/common/contracts/rocket-signer-registry.go b/rocketpool-daemon/common/contracts/rocket-signer-registry.go index ed9e9b745..56e354c6e 100644 --- a/rocketpool-daemon/common/contracts/rocket-signer-registry.go +++ b/rocketpool-daemon/common/contracts/rocket-signer-registry.go @@ -78,11 +78,11 @@ func (c *RocketSignerRegistry) SignerToNode(mc *batch.MultiCaller, out *common.A // ==================== // Get info for setting the signalling address -func (c *SnapshotDelegation) SetSigner(id common.Hash, signer common.Address, opts *bind.TransactOpts) (*eth.TransactionInfo, error) { - return c.txMgr.CreateTransactionInfo(c.contract, "setSigner", opts, signer) +func (c *SnapshotDelegation) SetSigner(id common.Hash, _signer common.Address, opts *bind.TransactOpts, _v uint8, _r [32]byte, _s [32]byte) (*eth.TransactionInfo, error) { + return c.txMgr.CreateTransactionInfo(c.contract, "setSigner", opts, _signer, _v, _r, _s) } // Get info for clearing the signalling address -func (c *SnapshotDelegation) clearSigner(id common.Hash, opts *bind.TransactOpts) (*eth.TransactionInfo, error) { +func (c *SnapshotDelegation) clearSigner(opts *bind.TransactOpts) (*eth.TransactionInfo, error) { return c.txMgr.CreateTransactionInfo(c.contract, "clearSigner", opts) } diff --git a/shared/utils/input.go b/shared/utils/input.go index b60328814..14d1653b8 100644 --- a/shared/utils/input.go +++ b/shared/utils/input.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "os" + "regexp" + "strconv" "strings" "github.com/urfave/cli/v2" @@ -21,6 +23,12 @@ const ( MinPasswordLength int = 12 ) +type EIP712Components struct { + V uint8 `json:"v"` + R [32]byte `json:"r"` + S [32]byte `json:"s"` +} + // Validate command argument count - only used by the CLI // TODO: refactor CLI arg validation and move it out of shared func ValidateArgCount(c *cli.Context, expectedCount int) { @@ -105,3 +113,36 @@ func ValidateVoteDirection(name, value string) (types.VoteDirection, error) { } return val, nil } + +// Expects a 129 byte 0x-prefixed EIP-712 signature and returns v/r/s as v uint8 and r, s [32]byte +func ParseEIP712(signature string) (*EIP712Components, error) { + if len(signature) != 132 || signature[:2] != "0x" { + return nil, fmt.Errorf("Invalid 129 byte 0x-prefixed EIP-712 signature while parsing: '%s'", signature) + } + signature = signature[2:] + if !regexp.MustCompile("^[A-Fa-f0-9]+$").MatchString(signature) { + return &EIP712Components{}, fmt.Errorf("Invalid 129 byte 0x-prefixed EIP-712 signature while parsing: '%s'", signature) + } + + // Slice signature string into v, r, s component of a signature giving node permission to use the given signer + str_v := signature[len(signature)-2:] + str_r := signature[:64] + str_s := signature[64:128] + + // Convert v to uint8 and v,s to [32]byte + bytes_r, err := hex.DecodeString(str_r) + if err != nil { + return &EIP712Components{}, fmt.Errorf("error decoding r: %v", err) + } + bytes_s, err := hex.DecodeString(str_s) + if err != nil { + return &EIP712Components{}, fmt.Errorf("error decoding s: %v", err) + } + + int_v, err := strconv.ParseUint(str_v, 16, 8) + if err != nil { + return &EIP712Components{}, fmt.Errorf("error parsing v: %v", err) + } + + return &EIP712Components{uint8(int_v), ([32]byte)(bytes_r), ([32]byte)(bytes_s)}, nil +} From 83b731828b3910e3d6be82335a0bf3d85c19219b Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Sat, 13 Jul 2024 17:21:12 -0700 Subject: [PATCH 08/29] Init signalling address commands --- rocketpool-cli/commands/pdao/commands.go | 82 +++++++++++++------ .../commands/pdao/signalling-address.go | 28 +++++++ 2 files changed, 83 insertions(+), 27 deletions(-) create mode 100644 rocketpool-cli/commands/pdao/signalling-address.go diff --git a/rocketpool-cli/commands/pdao/commands.go b/rocketpool-cli/commands/pdao/commands.go index 79e8d3a2a..8505a2838 100644 --- a/rocketpool-cli/commands/pdao/commands.go +++ b/rocketpool-cli/commands/pdao/commands.go @@ -154,6 +154,61 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { }, }, + { + Name: "initialize-voting", + Aliases: []string{"iv"}, + Usage: "Unlocks a node operator's voting power (only required for node operators who registered before governance structure was in place)", + Action: func(c *cli.Context) error { + // Run + return initializeVoting(c) + }, + }, + + { + Name: "set-signalling-address", + Aliases: []string{"ssa"}, + Usage: "Set the address you want to use to represent your node on Snapshot", + ArgsUsage: "signalling-address signature", + Flags: []cli.Flag{ + cliutils.YesFlag, + }, + Action: func(c *cli.Context) error { + // Validate args + utils.ValidateArgCount(c, 2) + signallingAddress := c.Args().Get(0) + signature := c.Args().Get(1) + // Run + return setSignallingAddress(c, signallingAddress, signature) + }, + }, + + { + Name: "clear-signalling-address", + Aliases: []string{"csa"}, + Usage: "Clear the node's signalling address", + Action: func(c *cli.Context) error { + // Run + return clearSignallingAddress(c) + }, + }, + + { + Name: "set-voting-delegate", + Aliases: []string{"svd"}, + Usage: "Set the address you want to use when voting on Rocket Pool on-chain governance proposals, or the address you want to delegate your voting power to.", + ArgsUsage: "address", + Flags: []cli.Flag{ + cliutils.YesFlag, + }, + Action: func(c *cli.Context) error { + // Validate args + utils.ValidateArgCount(c, 1) + delegate := c.Args().Get(0) + // Run + return setVotingDelegate(c, delegate) + }, + }, + { Name: "claim-bonds", Aliases: []string{"cb"}, @@ -469,33 +524,6 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { }, }, }, - - { - Name: "initialize-voting", - Aliases: []string{"iv"}, - Usage: "Unlocks a node operator's voting power (only required for node operators who registered before governance structure was in place)", - Action: func(c *cli.Context) error { - // Run - return initializeVoting(c) - }, - }, - - { - Name: "set-voting-delegate", - Aliases: []string{"svd"}, - Usage: "Set the address you want to use when voting on Rocket Pool on-chain governance proposals, or the address you want to delegate your voting power to.", - ArgsUsage: "address", - Flags: []cli.Flag{ - cliutils.YesFlag, - }, - Action: func(c *cli.Context) error { - // Validate args - utils.ValidateArgCount(c, 1) - delegate := c.Args().Get(0) - // Run - return setVotingDelegate(c, delegate) - }, - }, }, }) } diff --git a/rocketpool-cli/commands/pdao/signalling-address.go b/rocketpool-cli/commands/pdao/signalling-address.go new file mode 100644 index 000000000..873b91e2f --- /dev/null +++ b/rocketpool-cli/commands/pdao/signalling-address.go @@ -0,0 +1,28 @@ +package pdao + +import ( + "github.com/urfave/cli/v2" +) + +func setSignallingAddress(c *cli.Context, signallingAddressString string, signature string) error { + + // // Input Validation + // var err error + // signallingAddress, err := input.ValidateAddress("signalling-address", signallingAddressString) + // if err != nil { + // return err + // } + // // Todo: input.ValidateSignature + // sig, err := input.ValidateSignature("signature", signature) + // if err != nil { + // return err + // } + + return nil + +} + +func clearSignallingAddress(c *cli.Context) error { + + return nil +} From 209558701fd15f80af81638a99dfc6e85cb3c5f6 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Sun, 14 Jul 2024 21:32:06 -0700 Subject: [PATCH 09/29] Register routes for set/clear signalling address --- client/pdao.go | 14 +++++ rocketpool-cli/commands/pdao/commands.go | 14 ++++- .../commands/pdao/signalling-address.go | 26 ++++++---- .../api/pdao/clear-signalling-address.go | 52 +++++++++++++++++++ .../api/pdao/set-signalling-address.go | 52 +++++++++++++++++++ shared/types/api/pdao.go | 8 +++ 6 files changed, 154 insertions(+), 12 deletions(-) create mode 100644 rocketpool-daemon/api/pdao/clear-signalling-address.go create mode 100644 rocketpool-daemon/api/pdao/set-signalling-address.go diff --git a/client/pdao.go b/client/pdao.go index da48d6731..53155c1c2 100644 --- a/client/pdao.go +++ b/client/pdao.go @@ -220,3 +220,17 @@ func (r *PDaoRequester) GetCurrentVotingDelegate() (*types.ApiResponse[api.Proto func (r *PDaoRequester) GetStatus() (*types.ApiResponse[api.ProtocolDAOStatusResponse], error) { return client.SendGetRequest[api.ProtocolDAOStatusResponse](r, "get-status", "GetStatus", nil) } + +// Set the signalling address for the node +func (r *PDaoRequester) SetSignallingAddress(signallingAddress common.Address, signature string) (*types.ApiResponse[types.TxInfoData], error) { + args := map[string]string{ + "signallingAddress": signallingAddress.Hex(), + "signature": string(signature), + } + return client.SendGetRequest[types.TxInfoData](r, "set-signalling-address", "SetSignallingAddress", args) +} + +// Set the signalling address for the node +func (r *PDaoRequester) ClearSignallingAddress() (*types.ApiResponse[types.TxInfoData], error) { + return client.SendGetRequest[types.TxInfoData](r, "clear-signalling-address", "ClearSignallingAddress", nil) +} diff --git a/rocketpool-cli/commands/pdao/commands.go b/rocketpool-cli/commands/pdao/commands.go index 8505a2838..b1b431bf2 100644 --- a/rocketpool-cli/commands/pdao/commands.go +++ b/rocketpool-cli/commands/pdao/commands.go @@ -175,8 +175,18 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { Action: func(c *cli.Context) error { // Validate args utils.ValidateArgCount(c, 2) - signallingAddress := c.Args().Get(0) - signature := c.Args().Get(1) + + signallingAddress, err := input.ValidateAddress("signalling-address", c.Args().Get(0)) + if err != nil { + return err + } + // leaving this out until https://github.com/rocket-pool/node-manager-core/pull/17 is merged + // signature, err := input.ValidateSignature("signature", c.Args().Get(1)) + signature := c.Args().Get(0) + if err != nil { + return err + } + // Run return setSignallingAddress(c, signallingAddress, signature) }, diff --git a/rocketpool-cli/commands/pdao/signalling-address.go b/rocketpool-cli/commands/pdao/signalling-address.go index 873b91e2f..375cc7200 100644 --- a/rocketpool-cli/commands/pdao/signalling-address.go +++ b/rocketpool-cli/commands/pdao/signalling-address.go @@ -1,28 +1,34 @@ package pdao import ( + "github.com/ethereum/go-ethereum/common" "github.com/urfave/cli/v2" ) -func setSignallingAddress(c *cli.Context, signallingAddressString string, signature string) error { - - // // Input Validation - // var err error - // signallingAddress, err := input.ValidateAddress("signalling-address", signallingAddressString) - // if err != nil { - // return err - // } - // // Todo: input.ValidateSignature - // sig, err := input.ValidateSignature("signature", signature) +func setSignallingAddress(c *cli.Context, signallingAddress common.Address, signature string) error { + // // Get RP client + // rp, err := client.NewClientFromCtx(c) // if err != nil { // return err // } + // // Build the TX + // response, err := rp.Api.PDao.SetSignallingAddress() + return nil } func clearSignallingAddress(c *cli.Context) error { + // // Get RP client + // rp, err := client.NewClientFromCtx(c) + // if err != nil { + // return err + // } + + // // Build the TX + // response, err := rp.Api.PDao.ClearSignallingAddress() + return nil } diff --git a/rocketpool-daemon/api/pdao/clear-signalling-address.go b/rocketpool-daemon/api/pdao/clear-signalling-address.go new file mode 100644 index 000000000..b2da70835 --- /dev/null +++ b/rocketpool-daemon/api/pdao/clear-signalling-address.go @@ -0,0 +1,52 @@ +package pdao + +import ( + "net/url" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/gorilla/mux" + batch "github.com/rocket-pool/batch-query" + "github.com/rocket-pool/node-manager-core/api/server" + "github.com/rocket-pool/node-manager-core/api/types" + "github.com/rocket-pool/smartnode/v2/shared/types/api" +) + +// =============== +// === Factory === +// =============== + +type protocolDaoClearSignallingAddressFactory struct { + handler *ProtocolDaoHandler +} + +func (f *protocolDaoClearSignallingAddressFactory) Create(args url.Values) (*protocolDaoClearSignallingAddressContext, error) { + c := &protocolDaoClearSignallingAddressContext{ + handler: f.handler, + } + return c, nil +} + +func (f *protocolDaoClearSignallingAddressFactory) RegisterRoute(router *mux.Router) { + server.RegisterSingleStageRoute[*protocolDaoClearSignallingAddressContext, api.ProtocolDAOClearSignallingAddressResponse]( + router, "set-signalling-address", f, f.handler.logger.Logger, f.handler.serviceProvider.ServiceProvider, + ) +} + +// =============== +// === Context === +// =============== + +type protocolDaoClearSignallingAddressContext struct { + handler *ProtocolDaoHandler +} + +func (c *protocolDaoClearSignallingAddressContext) Initialize() (types.ResponseStatus, error) { + return types.ResponseStatus_Success, nil +} + +func (c *protocolDaoClearSignallingAddressContext) GetState(mc *batch.MultiCaller) { +} + +func (c *protocolDaoClearSignallingAddressContext) PrepareData(data *api.ProtocolDAOClearSignallingAddressResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { + return types.ResponseStatus_Success, nil +} diff --git a/rocketpool-daemon/api/pdao/set-signalling-address.go b/rocketpool-daemon/api/pdao/set-signalling-address.go new file mode 100644 index 000000000..1fa4b34c4 --- /dev/null +++ b/rocketpool-daemon/api/pdao/set-signalling-address.go @@ -0,0 +1,52 @@ +package pdao + +import ( + "net/url" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/gorilla/mux" + batch "github.com/rocket-pool/batch-query" + "github.com/rocket-pool/node-manager-core/api/server" + "github.com/rocket-pool/node-manager-core/api/types" + "github.com/rocket-pool/smartnode/v2/shared/types/api" +) + +// =============== +// === Factory === +// =============== + +type protocolDaoSetSignallingAddressFactory struct { + handler *ProtocolDaoHandler +} + +func (f *protocolDaoSetSignallingAddressFactory) Create(args url.Values) (*protocolDaoSetSignallingAddressContext, error) { + c := &protocolDaoSetSignallingAddressContext{ + handler: f.handler, + } + return c, nil +} + +func (f *protocolDaoSetSignallingAddressFactory) RegisterRoute(router *mux.Router) { + server.RegisterSingleStageRoute[*protocolDaoSetSignallingAddressContext, api.ProtocolDAOSetSignallingAddressResponse]( + router, "set-signalling-address", f, f.handler.logger.Logger, f.handler.serviceProvider.ServiceProvider, + ) +} + +// =============== +// === Context === +// =============== + +type protocolDaoSetSignallingAddressContext struct { + handler *ProtocolDaoHandler +} + +func (c *protocolDaoSetSignallingAddressContext) Initialize() (types.ResponseStatus, error) { + return types.ResponseStatus_Success, nil +} + +func (c *protocolDaoSetSignallingAddressContext) GetState(mc *batch.MultiCaller) { +} + +func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *api.ProtocolDAOSetSignallingAddressResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { + return types.ResponseStatus_Success, nil +} diff --git a/shared/types/api/pdao.go b/shared/types/api/pdao.go index b7a65975a..d28aa016f 100644 --- a/shared/types/api/pdao.go +++ b/shared/types/api/pdao.go @@ -323,3 +323,11 @@ type ProtocolDAOStatusResponse struct { SumVotingPower *big.Int `json:"sumVotingPower"` IsNodeRegistered bool `json:"isNodeRegistered"` } + +type ProtocolDAOSetSignallingAddressResponse struct { + Error string `json:"error"` +} + +type ProtocolDAOClearSignallingAddressResponse struct { + Error string `json:"error"` +} From 4606c10e97cea0945e68a0bbd8f3b5c11981df3b Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Sun, 14 Jul 2024 21:58:25 -0700 Subject: [PATCH 10/29] Add voteCount response --- rocketpool-cli/commands/pdao/status.go | 3 +- shared/types/api/pdao.go | 66 ++++++++++++++++++-------- shared/types/voting.go | 7 ++- 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/rocketpool-cli/commands/pdao/status.go b/rocketpool-cli/commands/pdao/status.go index 78b16ee35..19ae55bdd 100644 --- a/rocketpool-cli/commands/pdao/status.go +++ b/rocketpool-cli/commands/pdao/status.go @@ -71,8 +71,7 @@ func getStatus(c *cli.Context) error { if response.Data.SnapshotResponse.Error != "" { fmt.Printf("Unable to fetch latest voting information from snapshot.org: %s\n", response.Data.SnapshotResponse.Error) } else { - voteCount := 0 - //todo calculate voteCount + voteCount := response.Data.SnapshotResponse.VoteCount() if len(response.Data.SnapshotResponse.ActiveSnapshotProposals) == 0 { fmt.Printf("Rocket Pool has no Snapshot governance proposals being voted on.") fmt.Println() diff --git a/shared/types/api/pdao.go b/shared/types/api/pdao.go index d28aa016f..c7db7c842 100644 --- a/shared/types/api/pdao.go +++ b/shared/types/api/pdao.go @@ -301,27 +301,51 @@ type ProtocolDaoCurrentVotingDelegateData struct { } type ProtocolDAOStatusResponse struct { - Status string `json:"status"` - Error string `json:"error"` - VotingPower *big.Int `json:"votingPower"` - OnchainVotingDelegate common.Address `json:"onchainVotingDelegate"` - OnchainVotingDelegateFormatted string `json:"onchainVotingDelegateFormatted"` - BlockNumber uint32 `json:"blockNumber"` - VerifyEnabled bool `json:"verifyEnabled"` - IsVotingInitialized bool `json:"isVotingInitialized"` - SignallingAddress common.Address `json:"signallingAddress"` - SignallingAddressFormatted string `json:"signallingAddressFormatted"` - SnapshotResponse struct { - Error string `json:"error"` - ActiveSnapshotProposals []*sharedtypes.SnapshotProposal `json:"activeSnapshotProposals"` - } `json:"snapshotResponse"` - IsRPLLockingAllowed bool `json:"isRPLLockingAllowed"` - NodeRPLLocked *big.Int `json:"nodeRPLLocked"` - AccountAddress common.Address `json:"accountAddress"` - AccountAddressFormatted string `json:"accountAddressFormatted"` - TotalDelegatedVp *big.Int `json:"totalDelegatedVp"` - SumVotingPower *big.Int `json:"sumVotingPower"` - IsNodeRegistered bool `json:"isNodeRegistered"` + Status string `json:"status"` + Error string `json:"error"` + VotingPower *big.Int `json:"votingPower"` + OnchainVotingDelegate common.Address `json:"onchainVotingDelegate"` + OnchainVotingDelegateFormatted string `json:"onchainVotingDelegateFormatted"` + BlockNumber uint32 `json:"blockNumber"` + VerifyEnabled bool `json:"verifyEnabled"` + IsVotingInitialized bool `json:"isVotingInitialized"` + SignallingAddress common.Address `json:"signallingAddress"` + SignallingAddressFormatted string `json:"signallingAddressFormatted"` + SnapshotResponse SnapshotResponseStruct `json:"snapshotResponse"` + IsRPLLockingAllowed bool `json:"isRPLLockingAllowed"` + NodeRPLLocked *big.Int `json:"nodeRPLLocked"` + AccountAddress common.Address `json:"accountAddress"` + AccountAddressFormatted string `json:"accountAddressFormatted"` + TotalDelegatedVp *big.Int `json:"totalDelegatedVp"` + SumVotingPower *big.Int `json:"sumVotingPower"` + IsNodeRegistered bool `json:"isNodeRegistered"` +} + +type snapshotProposalVote struct { + Choice interface{} `json:"choice"` + Voter common.Address `json:"voter"` + Proposal struct { + Id string `json:"id"` + State string `json:"state"` + } `json:"proposal"` +} +type SnapshotResponseStruct struct { + Error string `json:"error"` + ProposalVotes []snapshotProposalVote `json:"proposalVotes"` + ActiveSnapshotProposals []*sharedtypes.SnapshotProposal `json:"activeSnapshotProposals"` +} + +func (s *SnapshotResponseStruct) VoteCount() uint { + voteCount := uint(0) + for _, activeProposal := range s.ActiveSnapshotProposals { + for _, votedProposal := range s.ProposalVotes { + if votedProposal.Proposal.Id == activeProposal.Id { + voteCount++ + break + } + } + } + return voteCount } type ProtocolDAOSetSignallingAddressResponse struct { diff --git a/shared/types/voting.go b/shared/types/voting.go index 2578cfe98..60d613550 100644 --- a/shared/types/voting.go +++ b/shared/types/voting.go @@ -11,12 +11,17 @@ const ( ) type SnapshotProposal struct { + Id string `json:"id"` Title string `json:"title"` - State ProposalState `json:"state"` Start time.Time `json:"start"` End time.Time `json:"end"` + State ProposalState `json:"state"` + Snapshot string `json:"snapshot"` + Author string `json:"author"` Choices []string `json:"choices"` Scores []float64 `json:"scores"` + ScoresTotal float64 `json:"score_total"` + ScoresUpdated int64 `json:"scores_updated"` Quorum float64 `json:"quorum"` Link string `json:"link"` UserVotes []int `json:"userVotes"` From 701c37aae706e9fefefe66862b471f23270825e1 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Mon, 15 Jul 2024 17:47:06 -0700 Subject: [PATCH 11/29] Fixed issue in registry binding --- rocketpool-cli/commands/pdao/status.go | 2 +- rocketpool-daemon/api/pdao/status.go | 3 ++- rocketpool-daemon/common/contracts/rocket-signer-registry.go | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/rocketpool-cli/commands/pdao/status.go b/rocketpool-cli/commands/pdao/status.go index 19ae55bdd..ad2800ce3 100644 --- a/rocketpool-cli/commands/pdao/status.go +++ b/rocketpool-cli/commands/pdao/status.go @@ -121,7 +121,7 @@ func getStatus(c *cli.Context) error { fmt.Println() } } else { - fmt.Print("The node is NOT allowed to lock RPL to create governance proposals/challenges. Use 'rocketpool node allow-rpl-locking, to allow RPL locking.\n") + fmt.Print("The node is NOT allowed to lock RPL to create governance proposals/challenges. Use 'rocketpool node allow-rpl-locking, to allow RPL locking.\n") } if len(claimableBonds) == 0 { fmt.Println("You do not have any unlockable bonds or claimable rewards.") diff --git a/rocketpool-daemon/api/pdao/status.go b/rocketpool-daemon/api/pdao/status.go index f723ca72c..3f1a4af36 100644 --- a/rocketpool-daemon/api/pdao/status.go +++ b/rocketpool-daemon/api/pdao/status.go @@ -135,7 +135,8 @@ func (c *protocolDaoGetStatusContext) PrepareData(data *api.ProtocolDAOStatusRes data.BlockNumber = uint32(c.blockNumber) data.IsNodeRegistered = c.isNodeRegistered - // data.SignallingAddress = c.signallingAddress + data.SignallingAddress = c.signallingAddress + data.SignallingAddressFormatted = utils.GetFormattedAddress(c.ec, data.SignallingAddress) data.AccountAddress = c.node.Address data.AccountAddressFormatted = utils.GetFormattedAddress(c.ec, data.AccountAddress) data.IsVotingInitialized = c.node.IsVotingInitialized.Get() diff --git a/rocketpool-daemon/common/contracts/rocket-signer-registry.go b/rocketpool-daemon/common/contracts/rocket-signer-registry.go index 56e354c6e..af9dbc6ef 100644 --- a/rocketpool-daemon/common/contracts/rocket-signer-registry.go +++ b/rocketpool-daemon/common/contracts/rocket-signer-registry.go @@ -38,11 +38,11 @@ type RocketSignerRegistry struct { func NewRocketSignerRegistry(address common.Address, client eth.IExecutionClient, txMgr *eth.TransactionManager) (*RocketSignerRegistry, error) { // Parse the ABI var err error - snapshotOnce.Do(func() { + rocketSignerRegistryOnce.Do(func() { var parsedAbi abi.ABI parsedAbi, err = abi.JSON(strings.NewReader(rocketSignerRegistryAbiString)) if err == nil { - snapshotAbi = parsedAbi + rocketSignerRegistryAbi = parsedAbi } }) if err != nil { From 3f4e01d0fee8788de419aa0ed699b83af500205c Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Mon, 15 Jul 2024 23:36:31 -0700 Subject: [PATCH 12/29] Add api route for clear signalling address --- client/pdao.go | 4 +- rocketpool-cli/commands/pdao/commands.go | 8 +- .../commands/pdao/signalling-address.go | 54 +++++- rocketpool-cli/commands/pdao/status.go | 2 + .../api/pdao/clear-signalling-address.go | 155 +++++++++++++++++- rocketpool-daemon/api/pdao/handler.go | 1 + .../api/pdao/set-signalling-address.go | 4 +- rocketpool-daemon/api/pdao/status.go | 4 +- .../contracts/rocket-signer-registry.go | 4 +- shared/types/api/pdao.go | 12 +- 10 files changed, 215 insertions(+), 33 deletions(-) diff --git a/client/pdao.go b/client/pdao.go index 53155c1c2..6e807a898 100644 --- a/client/pdao.go +++ b/client/pdao.go @@ -217,8 +217,8 @@ func (r *PDaoRequester) GetCurrentVotingDelegate() (*types.ApiResponse[api.Proto } // Get the pDAO status -func (r *PDaoRequester) GetStatus() (*types.ApiResponse[api.ProtocolDAOStatusResponse], error) { - return client.SendGetRequest[api.ProtocolDAOStatusResponse](r, "get-status", "GetStatus", nil) +func (r *PDaoRequester) GetStatus() (*types.ApiResponse[api.ProtocolDaoStatusResponse], error) { + return client.SendGetRequest[api.ProtocolDaoStatusResponse](r, "get-status", "GetStatus", nil) } // Set the signalling address for the node diff --git a/rocketpool-cli/commands/pdao/commands.go b/rocketpool-cli/commands/pdao/commands.go index b1b431bf2..1cb0de2ef 100644 --- a/rocketpool-cli/commands/pdao/commands.go +++ b/rocketpool-cli/commands/pdao/commands.go @@ -182,10 +182,10 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { } // leaving this out until https://github.com/rocket-pool/node-manager-core/pull/17 is merged // signature, err := input.ValidateSignature("signature", c.Args().Get(1)) - signature := c.Args().Get(0) - if err != nil { - return err - } + // if err != nil { + // return err + // } + signature := c.Args().Get(1) // Run return setSignallingAddress(c, signallingAddress, signature) diff --git a/rocketpool-cli/commands/pdao/signalling-address.go b/rocketpool-cli/commands/pdao/signalling-address.go index 375cc7200..e0e21cfea 100644 --- a/rocketpool-cli/commands/pdao/signalling-address.go +++ b/rocketpool-cli/commands/pdao/signalling-address.go @@ -1,7 +1,13 @@ package pdao import ( + "encoding/hex" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/rocket-pool/smartnode/v2/rocketpool-cli/client" + "github.com/rocket-pool/smartnode/v2/rocketpool-cli/utils/tx" + "github.com/rocket-pool/smartnode/v2/shared/utils" "github.com/urfave/cli/v2" ) @@ -15,20 +21,54 @@ func setSignallingAddress(c *cli.Context, signallingAddress common.Address, sign // // Build the TX // response, err := rp.Api.PDao.SetSignallingAddress() + // Test Strings + fmt.Printf("Signalling Address: %s\n", signallingAddress) + fmt.Printf("Signature: %s\n", signature) + fmt.Println() + + sig, err := utils.ParseEIP712(signature) + if err != nil { + return err + } + + fmt.Println("EIP712Components:") + fmt.Printf("V: %d\n", sig.V) + fmt.Printf("R: %s\n", hex.EncodeToString(sig.R[:])) + fmt.Printf("S: %s\n", hex.EncodeToString(sig.S[:])) + return nil } func clearSignallingAddress(c *cli.Context) error { - // // Get RP client - // rp, err := client.NewClientFromCtx(c) - // if err != nil { - // return err - // } + // Get RP client + rp, err := client.NewClientFromCtx(c) + if err != nil { + return err + } - // // Build the TX - // response, err := rp.Api.PDao.ClearSignallingAddress() + // Build the TX + response, err := rp.Api.PDao.ClearSignallingAddress() + + // Test Strings + fmt.Printf("Test\n") + fmt.Printf("Response %v\n", response) // response returns nil + fmt.Printf("Txinfo %v\n", response.Data.TxInfo) + + validated, err := tx.HandleTx(c, rp, response.Data.TxInfo, + "Are you sure you want to clear your current snapshot address?", + "clearing snapshot address", + "Clearing snapshot address...", + ) + if err != nil { + return err + } + if !validated { + return nil + } + // Log & return + fmt.Println("The node's signalling address has been sucessfully cleared.") return nil } diff --git a/rocketpool-cli/commands/pdao/status.go b/rocketpool-cli/commands/pdao/status.go index ad2800ce3..db26becbc 100644 --- a/rocketpool-cli/commands/pdao/status.go +++ b/rocketpool-cli/commands/pdao/status.go @@ -49,6 +49,8 @@ func getStatus(c *cli.Context) error { } claimableBonds := claimableBondsResponse.Data.ClaimableBonds + // Test Strings + fmt.Printf("Resp %v\n", response) fmt.Printf("block number: %d\n", response.Data.BlockNumber) fmt.Printf("Voting initialized: %t\n", response.Data.IsVotingInitialized) fmt.Printf("Node Registered: %t\n", response.Data.IsNodeRegistered) diff --git a/rocketpool-daemon/api/pdao/clear-signalling-address.go b/rocketpool-daemon/api/pdao/clear-signalling-address.go index b2da70835..882cc41d4 100644 --- a/rocketpool-daemon/api/pdao/clear-signalling-address.go +++ b/rocketpool-daemon/api/pdao/clear-signalling-address.go @@ -1,14 +1,16 @@ package pdao import ( + "fmt" "net/url" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" "github.com/gorilla/mux" batch "github.com/rocket-pool/batch-query" "github.com/rocket-pool/node-manager-core/api/server" "github.com/rocket-pool/node-manager-core/api/types" - "github.com/rocket-pool/smartnode/v2/shared/types/api" + "github.com/rocket-pool/rocketpool-go/v2/rocketpool" ) // =============== @@ -27,8 +29,8 @@ func (f *protocolDaoClearSignallingAddressFactory) Create(args url.Values) (*pro } func (f *protocolDaoClearSignallingAddressFactory) RegisterRoute(router *mux.Router) { - server.RegisterSingleStageRoute[*protocolDaoClearSignallingAddressContext, api.ProtocolDAOClearSignallingAddressResponse]( - router, "set-signalling-address", f, f.handler.logger.Logger, f.handler.serviceProvider.ServiceProvider, + server.RegisterQuerylessGet[*protocolDaoClearSignallingAddressContext, types.TxInfoData]( + router, "clear-signalling-address", f, f.handler.logger.Logger, f.handler.serviceProvider.ServiceProvider, ) } @@ -38,15 +40,150 @@ func (f *protocolDaoClearSignallingAddressFactory) RegisterRoute(router *mux.Rou type protocolDaoClearSignallingAddressContext struct { handler *ProtocolDaoHandler -} + rp *rocketpool.RocketPool -func (c *protocolDaoClearSignallingAddressContext) Initialize() (types.ResponseStatus, error) { - return types.ResponseStatus_Success, nil + signallingAddress common.Address } -func (c *protocolDaoClearSignallingAddressContext) GetState(mc *batch.MultiCaller) { -} +func (c *protocolDaoClearSignallingAddressContext) PrepareData(data *types.TxInfoData, opts *bind.TransactOpts) (types.ResponseStatus, error) { + sp := c.handler.serviceProvider + c.rp = sp.GetRocketPool() + nodeAddress, _ := sp.GetWallet().GetAddress() + + // Requirements + err := sp.RequireNodeAddress() + if err != nil { + return types.ResponseStatus_AddressNotPresent, err + } + registry := sp.GetRocketSignerRegistry() + if registry == nil { + return types.ResponseStatus_ResourceNotFound, err + } + + // Snapshot Registry + err = c.rp.Query(func(mc *batch.MultiCaller) error { + registry.NodeToSigner(mc, &c.signallingAddress, nodeAddress) + return nil + }, nil) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("error getting the registry contract: %w", err) + } -func (c *protocolDaoClearSignallingAddressContext) PrepareData(data *api.ProtocolDAOClearSignallingAddressResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { + // Return if there if no signalling address is set + if c.signallingAddress == (common.Address{}) { + return types.ResponseStatus_Error, nil + } else { + data.TxInfo, err = registry.ClearSigner(opts) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("Error getting the TX info for ClearSigner: %w", err) + } + } return types.ResponseStatus_Success, nil } + +// was initially trying it this way before I decided using server.RegisterQuerylessGet would be more appropriate + +// package pdao + +// import ( +// "fmt" +// "net/url" + +// "github.com/ethereum/go-ethereum/accounts/abi/bind" +// "github.com/ethereum/go-ethereum/common" +// "github.com/gorilla/mux" +// batch "github.com/rocket-pool/batch-query" +// "github.com/rocket-pool/node-manager-core/api/server" +// "github.com/rocket-pool/node-manager-core/api/types" +// "github.com/rocket-pool/rocketpool-go/v2/node" +// "github.com/rocket-pool/rocketpool-go/v2/rocketpool" +// "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/contracts" +// "github.com/rocket-pool/smartnode/v2/shared/types/api" +// ) + +// // =============== +// // === Factory === +// // =============== + +// type protocolDaoClearSignallingAddressFactory struct { +// handler *ProtocolDaoHandler +// } + +// func (f *protocolDaoClearSignallingAddressFactory) Create(args url.Values) (*protocolDaoClearSignallingAddressContext, error) { +// c := &protocolDaoClearSignallingAddressContext{ +// handler: f.handler, +// } +// return c, nil +// } + +// func (f *protocolDaoClearSignallingAddressFactory) RegisterRoute(router *mux.Router) { +// server.RegisterSingleStageRoute[*protocolDaoClearSignallingAddressContext, api.ProtocolDaoClearSignallingAddressResponse]( +// router, "clear-signalling-address", f, f.handler.logger.Logger, f.handler.serviceProvider.ServiceProvider, +// ) +// } + +// // =============== +// // === Context === +// // =============== + +// type protocolDaoClearSignallingAddressContext struct { +// handler *ProtocolDaoHandler +// rp *rocketpool.RocketPool +// registry *contracts.RocketSignerRegistry + +// node *node.Node +// signallingAddress common.Address +// } + +// func (c *protocolDaoClearSignallingAddressContext) Initialize() (types.ResponseStatus, error) { +// sp := c.handler.serviceProvider +// c.rp = sp.GetRocketPool() +// nodeAddress, _ := sp.GetWallet().GetAddress() + +// // Requirements +// err := sp.RequireNodeAddress() +// if err != nil { +// return types.ResponseStatus_AddressNotPresent, err +// } +// c.registry = sp.GetRocketSignerRegistry() +// if c.registry == nil { +// return types.ResponseStatus_ResourceNotFound, err +// } + +// // Bindings +// c.node, err = node.NewNode(c.rp, nodeAddress) +// if err != nil { +// return types.ResponseStatus_Error, fmt.Errorf("error creating node %s binding: %w", nodeAddress.Hex(), err) +// } + +// return types.ResponseStatus_Success, nil +// } + +// func (c *protocolDaoClearSignallingAddressContext) GetState(mc *batch.MultiCaller) { + +// // Snapshot Registry +// if c.registry != nil { +// c.registry.NodeToSigner(mc, &c.signallingAddress, c.node.Address) +// } + +// } + +// func (c *protocolDaoClearSignallingAddressContext) PrepareData(data *api.ProtocolDaoClearSignallingAddressResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { + +// // Return if there if no signalling address is set +// if c.signallingAddress == (common.Address{}) { +// data.CanClear = false +// return types.ResponseStatus_Error, nil +// } else { +// data.CanClear = true +// } + +// if data.CanClear { +// txInfo, err := c.registry.ClearSigner(opts) +// if err != nil { +// return types.ResponseStatus_Error, fmt.Errorf("Error getting the TX info for ClearSigner: %w", err) +// } +// data.TxInfo = txInfo +// } +// return types.ResponseStatus_Success, nil +// } diff --git a/rocketpool-daemon/api/pdao/handler.go b/rocketpool-daemon/api/pdao/handler.go index cae166597..398840ca6 100644 --- a/rocketpool-daemon/api/pdao/handler.go +++ b/rocketpool-daemon/api/pdao/handler.go @@ -47,6 +47,7 @@ func NewProtocolDaoHandler(logger *log.Logger, ctx context.Context, serviceProvi &protocolDaoSetVotingDelegateContextFactory{h}, &protocolDaoCurrentVotingDelegateContextFactory{h}, &protocolDaoGetStatusContextFactory{h}, + &protocolDaoClearSignallingAddressFactory{h}, } return h } diff --git a/rocketpool-daemon/api/pdao/set-signalling-address.go b/rocketpool-daemon/api/pdao/set-signalling-address.go index 1fa4b34c4..c9855cc5e 100644 --- a/rocketpool-daemon/api/pdao/set-signalling-address.go +++ b/rocketpool-daemon/api/pdao/set-signalling-address.go @@ -27,7 +27,7 @@ func (f *protocolDaoSetSignallingAddressFactory) Create(args url.Values) (*proto } func (f *protocolDaoSetSignallingAddressFactory) RegisterRoute(router *mux.Router) { - server.RegisterSingleStageRoute[*protocolDaoSetSignallingAddressContext, api.ProtocolDAOSetSignallingAddressResponse]( + server.RegisterSingleStageRoute[*protocolDaoSetSignallingAddressContext, api.ProtocolDaoSetSignallingAddressResponse]( router, "set-signalling-address", f, f.handler.logger.Logger, f.handler.serviceProvider.ServiceProvider, ) } @@ -47,6 +47,6 @@ func (c *protocolDaoSetSignallingAddressContext) Initialize() (types.ResponseSta func (c *protocolDaoSetSignallingAddressContext) GetState(mc *batch.MultiCaller) { } -func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *api.ProtocolDAOSetSignallingAddressResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { +func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *api.ProtocolDaoSetSignallingAddressResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { return types.ResponseStatus_Success, nil } diff --git a/rocketpool-daemon/api/pdao/status.go b/rocketpool-daemon/api/pdao/status.go index 3f1a4af36..b36d40d16 100644 --- a/rocketpool-daemon/api/pdao/status.go +++ b/rocketpool-daemon/api/pdao/status.go @@ -39,7 +39,7 @@ func (f *protocolDaoGetStatusContextFactory) Create(args url.Values) (*protocolD } func (f *protocolDaoGetStatusContextFactory) RegisterRoute(router *mux.Router) { - server.RegisterSingleStageRoute[*protocolDaoGetStatusContext, api.ProtocolDAOStatusResponse]( + server.RegisterSingleStageRoute[*protocolDaoGetStatusContext, api.ProtocolDaoStatusResponse]( router, "get-status", f, f.handler.logger.Logger, f.handler.serviceProvider.ServiceProvider, ) } @@ -131,7 +131,7 @@ func (c *protocolDaoGetStatusContext) GetState(mc *batch.MultiCaller) { } } -func (c *protocolDaoGetStatusContext) PrepareData(data *api.ProtocolDAOStatusResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { +func (c *protocolDaoGetStatusContext) PrepareData(data *api.ProtocolDaoStatusResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { data.BlockNumber = uint32(c.blockNumber) data.IsNodeRegistered = c.isNodeRegistered diff --git a/rocketpool-daemon/common/contracts/rocket-signer-registry.go b/rocketpool-daemon/common/contracts/rocket-signer-registry.go index af9dbc6ef..97dd033bd 100644 --- a/rocketpool-daemon/common/contracts/rocket-signer-registry.go +++ b/rocketpool-daemon/common/contracts/rocket-signer-registry.go @@ -78,11 +78,11 @@ func (c *RocketSignerRegistry) SignerToNode(mc *batch.MultiCaller, out *common.A // ==================== // Get info for setting the signalling address -func (c *SnapshotDelegation) SetSigner(id common.Hash, _signer common.Address, opts *bind.TransactOpts, _v uint8, _r [32]byte, _s [32]byte) (*eth.TransactionInfo, error) { +func (c *RocketSignerRegistry) SetSigner(id common.Hash, _signer common.Address, opts *bind.TransactOpts, _v uint8, _r [32]byte, _s [32]byte) (*eth.TransactionInfo, error) { return c.txMgr.CreateTransactionInfo(c.contract, "setSigner", opts, _signer, _v, _r, _s) } // Get info for clearing the signalling address -func (c *SnapshotDelegation) clearSigner(opts *bind.TransactOpts) (*eth.TransactionInfo, error) { +func (c *RocketSignerRegistry) ClearSigner(opts *bind.TransactOpts) (*eth.TransactionInfo, error) { return c.txMgr.CreateTransactionInfo(c.contract, "clearSigner", opts) } diff --git a/shared/types/api/pdao.go b/shared/types/api/pdao.go index c7db7c842..14ff4abae 100644 --- a/shared/types/api/pdao.go +++ b/shared/types/api/pdao.go @@ -300,7 +300,7 @@ type ProtocolDaoCurrentVotingDelegateData struct { VotingDelegate common.Address `json:"votingDelegate"` } -type ProtocolDAOStatusResponse struct { +type ProtocolDaoStatusResponse struct { Status string `json:"status"` Error string `json:"error"` VotingPower *big.Int `json:"votingPower"` @@ -348,10 +348,12 @@ func (s *SnapshotResponseStruct) VoteCount() uint { return voteCount } -type ProtocolDAOSetSignallingAddressResponse struct { +type ProtocolDaoSetSignallingAddressResponse struct { Error string `json:"error"` } -type ProtocolDAOClearSignallingAddressResponse struct { - Error string `json:"error"` -} +// type ProtocolDaoClearSignallingAddressResponse struct { +// Error string `json:"error"` +// CanClear bool `json:"canClear"` +// TxInfo *eth.TransactionInfo `json:"txInfo"` +// } From 4dddd2040bd2ff5bf4d490e39c37230fa4254f20 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Tue, 16 Jul 2024 22:46:29 -0700 Subject: [PATCH 13/29] Add cli and api route for set/clear signalling address --- .../commands/pdao/signalling-address.go | 52 ++++---- .../api/pdao/clear-signalling-address.go | 117 +----------------- rocketpool-daemon/api/pdao/handler.go | 1 + .../api/pdao/set-signalling-address.go | 80 +++++++++++- rocketpool-daemon/api/pdao/status.go | 3 +- .../contracts/rocket-signer-registry.go | 3 +- 6 files changed, 110 insertions(+), 146 deletions(-) diff --git a/rocketpool-cli/commands/pdao/signalling-address.go b/rocketpool-cli/commands/pdao/signalling-address.go index e0e21cfea..39e35f07f 100644 --- a/rocketpool-cli/commands/pdao/signalling-address.go +++ b/rocketpool-cli/commands/pdao/signalling-address.go @@ -1,40 +1,38 @@ package pdao import ( - "encoding/hex" "fmt" "github.com/ethereum/go-ethereum/common" "github.com/rocket-pool/smartnode/v2/rocketpool-cli/client" "github.com/rocket-pool/smartnode/v2/rocketpool-cli/utils/tx" - "github.com/rocket-pool/smartnode/v2/shared/utils" "github.com/urfave/cli/v2" ) func setSignallingAddress(c *cli.Context, signallingAddress common.Address, signature string) error { - // // Get RP client - // rp, err := client.NewClientFromCtx(c) - // if err != nil { - // return err - // } - - // // Build the TX - // response, err := rp.Api.PDao.SetSignallingAddress() + // Get RP client + rp, err := client.NewClientFromCtx(c) + if err != nil { + return err + } - // Test Strings - fmt.Printf("Signalling Address: %s\n", signallingAddress) - fmt.Printf("Signature: %s\n", signature) - fmt.Println() + // Build the TX + response, err := rp.Api.PDao.SetSignallingAddress(signallingAddress, signature) + if err != nil { + return fmt.Errorf("Error setting the signalling address: %w", err) + } - sig, err := utils.ParseEIP712(signature) + validated, err := tx.HandleTx(c, rp, response.Data.TxInfo, + "Are you sure you want to set your signalling address?", + "setting signalling", + "Setting signalling address...", + ) if err != nil { return err } - - fmt.Println("EIP712Components:") - fmt.Printf("V: %d\n", sig.V) - fmt.Printf("R: %s\n", hex.EncodeToString(sig.R[:])) - fmt.Printf("S: %s\n", hex.EncodeToString(sig.S[:])) + if !validated { + return nil + } return nil @@ -50,16 +48,14 @@ func clearSignallingAddress(c *cli.Context) error { // Build the TX response, err := rp.Api.PDao.ClearSignallingAddress() - - // Test Strings - fmt.Printf("Test\n") - fmt.Printf("Response %v\n", response) // response returns nil - fmt.Printf("Txinfo %v\n", response.Data.TxInfo) + if err != nil { + return fmt.Errorf("Error clearing the signalling address: %w", err) + } validated, err := tx.HandleTx(c, rp, response.Data.TxInfo, - "Are you sure you want to clear your current snapshot address?", - "clearing snapshot address", - "Clearing snapshot address...", + "Are you sure you want to clear the current signalling address?", + "clearing signalling address", + "Clearing signalling address...", ) if err != nil { return err diff --git a/rocketpool-daemon/api/pdao/clear-signalling-address.go b/rocketpool-daemon/api/pdao/clear-signalling-address.go index 882cc41d4..78b726ed2 100644 --- a/rocketpool-daemon/api/pdao/clear-signalling-address.go +++ b/rocketpool-daemon/api/pdao/clear-signalling-address.go @@ -49,6 +49,8 @@ func (c *protocolDaoClearSignallingAddressContext) PrepareData(data *types.TxInf sp := c.handler.serviceProvider c.rp = sp.GetRocketPool() nodeAddress, _ := sp.GetWallet().GetAddress() + cfg := sp.GetConfig() + network := cfg.GetNetworkResources().Network // Requirements err := sp.RequireNodeAddress() @@ -57,21 +59,21 @@ func (c *protocolDaoClearSignallingAddressContext) PrepareData(data *types.TxInf } registry := sp.GetRocketSignerRegistry() if registry == nil { - return types.ResponseStatus_ResourceNotFound, err + return types.ResponseStatus_Error, fmt.Errorf("Network [%v] does not have a signer registry contract", network) } - // Snapshot Registry + // Query registry contract err = c.rp.Query(func(mc *batch.MultiCaller) error { registry.NodeToSigner(mc, &c.signallingAddress, nodeAddress) return nil }, nil) if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("error getting the registry contract: %w", err) + return types.ResponseStatus_Error, fmt.Errorf("Error getting the registry contract: %w", err) } // Return if there if no signalling address is set if c.signallingAddress == (common.Address{}) { - return types.ResponseStatus_Error, nil + return types.ResponseStatus_Error, fmt.Errorf("No signalling address set") } else { data.TxInfo, err = registry.ClearSigner(opts) if err != nil { @@ -80,110 +82,3 @@ func (c *protocolDaoClearSignallingAddressContext) PrepareData(data *types.TxInf } return types.ResponseStatus_Success, nil } - -// was initially trying it this way before I decided using server.RegisterQuerylessGet would be more appropriate - -// package pdao - -// import ( -// "fmt" -// "net/url" - -// "github.com/ethereum/go-ethereum/accounts/abi/bind" -// "github.com/ethereum/go-ethereum/common" -// "github.com/gorilla/mux" -// batch "github.com/rocket-pool/batch-query" -// "github.com/rocket-pool/node-manager-core/api/server" -// "github.com/rocket-pool/node-manager-core/api/types" -// "github.com/rocket-pool/rocketpool-go/v2/node" -// "github.com/rocket-pool/rocketpool-go/v2/rocketpool" -// "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/contracts" -// "github.com/rocket-pool/smartnode/v2/shared/types/api" -// ) - -// // =============== -// // === Factory === -// // =============== - -// type protocolDaoClearSignallingAddressFactory struct { -// handler *ProtocolDaoHandler -// } - -// func (f *protocolDaoClearSignallingAddressFactory) Create(args url.Values) (*protocolDaoClearSignallingAddressContext, error) { -// c := &protocolDaoClearSignallingAddressContext{ -// handler: f.handler, -// } -// return c, nil -// } - -// func (f *protocolDaoClearSignallingAddressFactory) RegisterRoute(router *mux.Router) { -// server.RegisterSingleStageRoute[*protocolDaoClearSignallingAddressContext, api.ProtocolDaoClearSignallingAddressResponse]( -// router, "clear-signalling-address", f, f.handler.logger.Logger, f.handler.serviceProvider.ServiceProvider, -// ) -// } - -// // =============== -// // === Context === -// // =============== - -// type protocolDaoClearSignallingAddressContext struct { -// handler *ProtocolDaoHandler -// rp *rocketpool.RocketPool -// registry *contracts.RocketSignerRegistry - -// node *node.Node -// signallingAddress common.Address -// } - -// func (c *protocolDaoClearSignallingAddressContext) Initialize() (types.ResponseStatus, error) { -// sp := c.handler.serviceProvider -// c.rp = sp.GetRocketPool() -// nodeAddress, _ := sp.GetWallet().GetAddress() - -// // Requirements -// err := sp.RequireNodeAddress() -// if err != nil { -// return types.ResponseStatus_AddressNotPresent, err -// } -// c.registry = sp.GetRocketSignerRegistry() -// if c.registry == nil { -// return types.ResponseStatus_ResourceNotFound, err -// } - -// // Bindings -// c.node, err = node.NewNode(c.rp, nodeAddress) -// if err != nil { -// return types.ResponseStatus_Error, fmt.Errorf("error creating node %s binding: %w", nodeAddress.Hex(), err) -// } - -// return types.ResponseStatus_Success, nil -// } - -// func (c *protocolDaoClearSignallingAddressContext) GetState(mc *batch.MultiCaller) { - -// // Snapshot Registry -// if c.registry != nil { -// c.registry.NodeToSigner(mc, &c.signallingAddress, c.node.Address) -// } - -// } - -// func (c *protocolDaoClearSignallingAddressContext) PrepareData(data *api.ProtocolDaoClearSignallingAddressResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { - -// // Return if there if no signalling address is set -// if c.signallingAddress == (common.Address{}) { -// data.CanClear = false -// return types.ResponseStatus_Error, nil -// } else { -// data.CanClear = true -// } - -// if data.CanClear { -// txInfo, err := c.registry.ClearSigner(opts) -// if err != nil { -// return types.ResponseStatus_Error, fmt.Errorf("Error getting the TX info for ClearSigner: %w", err) -// } -// data.TxInfo = txInfo -// } -// return types.ResponseStatus_Success, nil -// } diff --git a/rocketpool-daemon/api/pdao/handler.go b/rocketpool-daemon/api/pdao/handler.go index 398840ca6..c43536c38 100644 --- a/rocketpool-daemon/api/pdao/handler.go +++ b/rocketpool-daemon/api/pdao/handler.go @@ -48,6 +48,7 @@ func NewProtocolDaoHandler(logger *log.Logger, ctx context.Context, serviceProvi &protocolDaoCurrentVotingDelegateContextFactory{h}, &protocolDaoGetStatusContextFactory{h}, &protocolDaoClearSignallingAddressFactory{h}, + &protocolDaoSetSignallingAddressFactory{h}, } return h } diff --git a/rocketpool-daemon/api/pdao/set-signalling-address.go b/rocketpool-daemon/api/pdao/set-signalling-address.go index c9855cc5e..663122853 100644 --- a/rocketpool-daemon/api/pdao/set-signalling-address.go +++ b/rocketpool-daemon/api/pdao/set-signalling-address.go @@ -1,14 +1,22 @@ package pdao import ( + "errors" + "fmt" "net/url" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" "github.com/gorilla/mux" batch "github.com/rocket-pool/batch-query" "github.com/rocket-pool/node-manager-core/api/server" "github.com/rocket-pool/node-manager-core/api/types" - "github.com/rocket-pool/smartnode/v2/shared/types/api" + "github.com/rocket-pool/node-manager-core/eth" + "github.com/rocket-pool/node-manager-core/utils/input" + "github.com/rocket-pool/rocketpool-go/v2/node" + "github.com/rocket-pool/rocketpool-go/v2/rocketpool" + "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/contracts" + "github.com/rocket-pool/smartnode/v2/shared/utils" ) // =============== @@ -23,11 +31,17 @@ func (f *protocolDaoSetSignallingAddressFactory) Create(args url.Values) (*proto c := &protocolDaoSetSignallingAddressContext{ handler: f.handler, } - return c, nil + inputErrs := []error{ + server.ValidateArg("signallingAddress", args, input.ValidateAddress, &c.signallingAddress), + // leaving this out until ValidateArg() is added to NMC + // server.ValidateArg("signature", args, input.ValidateSignature, &c.signature), + server.GetStringFromVars("signature", args, &c.signature), + } + return c, errors.Join(inputErrs...) } func (f *protocolDaoSetSignallingAddressFactory) RegisterRoute(router *mux.Router) { - server.RegisterSingleStageRoute[*protocolDaoSetSignallingAddressContext, api.ProtocolDaoSetSignallingAddressResponse]( + server.RegisterSingleStageRoute[*protocolDaoSetSignallingAddressContext, types.TxInfoData]( router, "set-signalling-address", f, f.handler.logger.Logger, f.handler.serviceProvider.ServiceProvider, ) } @@ -37,16 +51,72 @@ func (f *protocolDaoSetSignallingAddressFactory) RegisterRoute(router *mux.Route // =============== type protocolDaoSetSignallingAddressContext struct { - handler *ProtocolDaoHandler + handler *ProtocolDaoHandler + rp *rocketpool.RocketPool + registry *contracts.RocketSignerRegistry + + node *node.Node + signallingAddress common.Address + nodeToSigner common.Address + signature string } func (c *protocolDaoSetSignallingAddressContext) Initialize() (types.ResponseStatus, error) { + sp := c.handler.serviceProvider + c.rp = sp.GetRocketPool() + nodeAddress, _ := sp.GetWallet().GetAddress() + cfg := sp.GetConfig() + network := cfg.GetNetworkResources().Network + + // Requirements + err := sp.RequireNodeAddress() + if err != nil { + return types.ResponseStatus_AddressNotPresent, err + } + c.registry = sp.GetRocketSignerRegistry() + if c.registry == nil { + return types.ResponseStatus_Error, fmt.Errorf("Network [%v] does not have a signer registry contract.", network) + } + + // Binding + c.node, err = node.NewNode(c.rp, nodeAddress) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("Error creating node %s binding: %w", nodeAddress.Hex(), err) + } + return types.ResponseStatus_Success, nil } func (c *protocolDaoSetSignallingAddressContext) GetState(mc *batch.MultiCaller) { + eth.AddQueryablesToMulticall(mc, + c.node.Exists, + c.node.IsVotingInitialized, + ) + // Check if the node already has a signer + if c.registry != nil { + c.registry.NodeToSigner(mc, &c.nodeToSigner, c.node.Address) + } } -func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *api.ProtocolDaoSetSignallingAddressResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { +func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *types.TxInfoData, opts *bind.TransactOpts) (types.ResponseStatus, error) { + + if c.signallingAddress == c.nodeToSigner { + return types.ResponseStatus_Error, fmt.Errorf("Signer address already in use") + } + + if !c.node.IsVotingInitialized.Get() { + return types.ResponseStatus_Error, fmt.Errorf("Voting must be initialized to set a signalling address. Use 'rocketpool pdao initialize-voting' to initialize voting first") + } + + sig, err := utils.ParseEIP712(c.signature) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("Error splitting EIP-712 signature") + } + + data.TxInfo, err = c.registry.SetSigner(c.signallingAddress, opts, sig.V, sig.R, sig.S) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("Error getting the TX info for SetSigner: %w", err) + } + return types.ResponseStatus_Success, nil } diff --git a/rocketpool-daemon/api/pdao/status.go b/rocketpool-daemon/api/pdao/status.go index b36d40d16..5b02b2a09 100644 --- a/rocketpool-daemon/api/pdao/status.go +++ b/rocketpool-daemon/api/pdao/status.go @@ -72,6 +72,7 @@ func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) c.ec = sp.GetEthClient() c.bc = sp.GetBeaconClient() nodeAddress, _ := sp.GetWallet().GetAddress() + network := c.cfg.GetNetworkResources().Network // Requirements err := sp.RequireNodeAddress() @@ -86,7 +87,7 @@ func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) } c.registry = sp.GetRocketSignerRegistry() if c.registry == nil { - return types.ResponseStatus_ResourceNotFound, err + return types.ResponseStatus_ResourceNotFound, fmt.Errorf("Network [%v] does not have a signer registry contract.", network) } // Bindings diff --git a/rocketpool-daemon/common/contracts/rocket-signer-registry.go b/rocketpool-daemon/common/contracts/rocket-signer-registry.go index 97dd033bd..7b519b17d 100644 --- a/rocketpool-daemon/common/contracts/rocket-signer-registry.go +++ b/rocketpool-daemon/common/contracts/rocket-signer-registry.go @@ -58,6 +58,7 @@ func NewRocketSignerRegistry(address common.Address, client eth.IExecutionClient return &RocketSignerRegistry{ contract: contract, + txMgr: txMgr, }, nil } @@ -78,7 +79,7 @@ func (c *RocketSignerRegistry) SignerToNode(mc *batch.MultiCaller, out *common.A // ==================== // Get info for setting the signalling address -func (c *RocketSignerRegistry) SetSigner(id common.Hash, _signer common.Address, opts *bind.TransactOpts, _v uint8, _r [32]byte, _s [32]byte) (*eth.TransactionInfo, error) { +func (c *RocketSignerRegistry) SetSigner(_signer common.Address, opts *bind.TransactOpts, _v uint8, _r [32]byte, _s [32]byte) (*eth.TransactionInfo, error) { return c.txMgr.CreateTransactionInfo(c.contract, "setSigner", opts, _signer, _v, _r, _s) } From 5ff02e2fc943448d6904d6f522965b6ddaebddbf Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Tue, 16 Jul 2024 22:49:08 -0700 Subject: [PATCH 14/29] Remove unused structs --- shared/types/api/pdao.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/shared/types/api/pdao.go b/shared/types/api/pdao.go index 14ff4abae..dd2e65b73 100644 --- a/shared/types/api/pdao.go +++ b/shared/types/api/pdao.go @@ -347,13 +347,3 @@ func (s *SnapshotResponseStruct) VoteCount() uint { } return voteCount } - -type ProtocolDaoSetSignallingAddressResponse struct { - Error string `json:"error"` -} - -// type ProtocolDaoClearSignallingAddressResponse struct { -// Error string `json:"error"` -// CanClear bool `json:"canClear"` -// TxInfo *eth.TransactionInfo `json:"txInfo"` -// } From c03878e14b31ae74a95bbd84028546c5627e8b23 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Wed, 17 Jul 2024 18:33:03 -0700 Subject: [PATCH 15/29] Fix print statements and terminal escape codes --- .../commands/pdao/signalling-address.go | 2 +- rocketpool-cli/commands/pdao/status.go | 66 +++++++------------ 2 files changed, 24 insertions(+), 44 deletions(-) diff --git a/rocketpool-cli/commands/pdao/signalling-address.go b/rocketpool-cli/commands/pdao/signalling-address.go index 39e35f07f..2093dc236 100644 --- a/rocketpool-cli/commands/pdao/signalling-address.go +++ b/rocketpool-cli/commands/pdao/signalling-address.go @@ -24,7 +24,7 @@ func setSignallingAddress(c *cli.Context, signallingAddress common.Address, sign validated, err := tx.HandleTx(c, rp, response.Data.TxInfo, "Are you sure you want to set your signalling address?", - "setting signalling", + "setting signalling address", "Setting signalling address...", ) if err != nil { diff --git a/rocketpool-cli/commands/pdao/status.go b/rocketpool-cli/commands/pdao/status.go index db26becbc..4eb918698 100644 --- a/rocketpool-cli/commands/pdao/status.go +++ b/rocketpool-cli/commands/pdao/status.go @@ -12,13 +12,15 @@ import ( "github.com/rocket-pool/rocketpool-go/v2/types" "github.com/rocket-pool/smartnode/v2/rocketpool-cli/client" "github.com/rocket-pool/smartnode/v2/rocketpool-cli/utils" + "github.com/rocket-pool/smartnode/v2/rocketpool-cli/utils/terminal" "github.com/rocket-pool/smartnode/v2/shared/types/api" ) const ( - colorBlue string = "\033[36m" - colorReset string = "\033[0m" - colorGreen string = "\033[32m" + colorBlue string = terminal.ColorBlue + colorReset string = terminal.ColorReset + colorGreen string = terminal.ColorGreen + signallingAddressLink string = "https://docs.rocketpool.net/guides/houston/participate#setting-your-snapshot-signalling-address" ) @@ -49,88 +51,66 @@ func getStatus(c *cli.Context) error { } claimableBonds := claimableBondsResponse.Data.ClaimableBonds - // Test Strings - fmt.Printf("Resp %v\n", response) - fmt.Printf("block number: %d\n", response.Data.BlockNumber) - fmt.Printf("Voting initialized: %t\n", response.Data.IsVotingInitialized) - fmt.Printf("Node Registered: %t\n", response.Data.IsNodeRegistered) - fmt.Printf("Voting power %d\n", response.Data.VotingPower) - fmt.Printf("Delegated voting power %d\n", response.Data.TotalDelegatedVp) - fmt.Printf("On chain delegate formatted: %s\n", response.Data.OnchainVotingDelegateFormatted) - fmt.Printf("Account Address formatted: %s\n", response.Data.AccountAddressFormatted) - fmt.Printf("Signalling Address formatted %s\n", response.Data.SignallingAddressFormatted) - fmt.Printf("Is RPL locking allowed %t\n", response.Data.IsRPLLockingAllowed) - // Signalling Status fmt.Printf("%s=== Signalling on Snapshot ===%s\n", colorGreen, colorReset) blankAddress := common.Address{} if response.Data.SignallingAddress == blankAddress { - fmt.Printf("The node does not currently have a snapshot signalling address set.\nTo learn more about snapshot signalling, please visit %s.\n", signallingAddressLink) + fmt.Println("The node does not currently have a snapshot signalling address set.") + fmt.Printf("To learn more about snapshot signalling, please visit %s.\n", signallingAddressLink) } else { fmt.Printf("The node has a signalling address of %s%s%s which can represent it when voting on Rocket Pool Snapshot governance proposals.\n", colorBlue, response.Data.SignallingAddressFormatted, colorReset) } - if response.Data.SnapshotResponse.Error != "" { fmt.Printf("Unable to fetch latest voting information from snapshot.org: %s\n", response.Data.SnapshotResponse.Error) } else { voteCount := response.Data.SnapshotResponse.VoteCount() if len(response.Data.SnapshotResponse.ActiveSnapshotProposals) == 0 { - fmt.Printf("Rocket Pool has no Snapshot governance proposals being voted on.") - fmt.Println() + fmt.Println("Rocket Pool has no Snapshot governance proposals being voted on.") } else { fmt.Printf("Rocket Pool has %d Snapshot governance proposal(s) being voted on. You have voted on %d of those. See details using 'rocketpool network dao-proposals'.\n", len(response.Data.SnapshotResponse.ActiveSnapshotProposals), voteCount) } - fmt.Println() } + fmt.Println() // Onchain Voting Status fmt.Printf("%s=== Onchain Voting ===%s\n", colorGreen, colorReset) if response.Data.IsVotingInitialized { - fmt.Printf("The node %s%s%s has been initialized for onchain voting.", colorBlue, response.Data.AccountAddressFormatted, colorReset) - fmt.Println() + fmt.Printf("The node %s%s%s has been initialized for onchain voting.\n", colorBlue, response.Data.AccountAddressFormatted, colorReset) } else { - fmt.Printf("The node %s%s%s has NOT been initialized for onchain voting. You need to run `rocketpool pdao initialize-voting` to participate in onchain votes.", colorBlue, response.Data.AccountAddressFormatted, colorReset) - fmt.Println() + fmt.Printf("The node %s%s%s has NOT been initialized for onchain voting. You need to run `rocketpool pdao initialize-voting` to participate in onchain votes.\n", colorBlue, response.Data.AccountAddressFormatted, colorReset) } - if response.Data.OnchainVotingDelegate == blankAddress { fmt.Println("The node doesn't have a delegate, which means it can vote directly on onchain proposals after it initializes voting.") } else if response.Data.OnchainVotingDelegate == response.Data.AccountAddress { fmt.Println("The node doesn't have a delegate, which means it can vote directly on onchain proposals. You can have another node represent you by running `rocketpool p svd
`.") } else { - fmt.Printf("The node has a voting delegate of %s%s%s which can represent it when voting on Rocket Pool onchain governance proposals.", colorBlue, response.Data.OnchainVotingDelegateFormatted, colorReset) - fmt.Println() + fmt.Printf("The node has a voting delegate of %s%s%s which can represent it when voting on Rocket Pool onchain governance proposals.\n", colorBlue, response.Data.OnchainVotingDelegateFormatted, colorReset) } fmt.Printf("The node's local voting power: %.10f\n", eth.WeiToEth(response.Data.VotingPower)) if response.Data.IsNodeRegistered { - fmt.Printf("Total voting power delegated to the node: %.10f", eth.WeiToEth(response.Data.TotalDelegatedVp)) - fmt.Println() + fmt.Printf("Total voting power delegated to the node: %.10f\n", eth.WeiToEth(response.Data.TotalDelegatedVp)) } else { - fmt.Print("The node must register using 'rocketpool node register' to be eligible to receive delegated voting power") - fmt.Println() + fmt.Println("The node must register using 'rocketpool node register' to be eligible to receive delegated voting power") } fmt.Printf("Network total initialized voting power: %.10f\n", eth.WeiToEth(response.Data.SumVotingPower)) - fmt.Println("") + fmt.Println() // Claimable Bonds Status: - fmt.Printf("%s=== Claimable RPL Bonds ===%s", colorGreen, colorReset) - fmt.Println() + fmt.Printf("%s=== Claimable RPL Bonds ===%s\n", colorGreen, colorReset) if response.Data.IsRPLLockingAllowed { fmt.Println("The node is allowed to lock RPL to create governance proposals/challenges.") if response.Data.NodeRPLLocked.Cmp(big.NewInt(0)) != 0 { - fmt.Printf("The node currently has %.6f RPL locked.", - utilsMath.RoundDown(eth.WeiToEth(response.Data.NodeRPLLocked), 6)) - fmt.Println() + fmt.Printf("The node currently has %.6f RPL locked.\n", utilsMath.RoundDown(eth.WeiToEth(response.Data.NodeRPLLocked), 6)) } } else { - fmt.Print("The node is NOT allowed to lock RPL to create governance proposals/challenges. Use 'rocketpool node allow-rpl-locking, to allow RPL locking.\n") + fmt.Println("The node is NOT allowed to lock RPL to create governance proposals/challenges. Use 'rocketpool node allow-rpl-locking, to allow RPL locking.") } if len(claimableBonds) == 0 { - fmt.Println("You do not have any unlockable bonds or claimable rewards.") + fmt.Println("The node does not have any unlockable bonds or claimable rewards.") } else { fmt.Println("The node has unlockable bonds or claimable rewards available. Use 'rocketpool pdao claim-bonds' to view and claim.") } - fmt.Println("") + fmt.Println() // Check if PDAO proposal checking duty is enabled fmt.Printf("%s=== PDAO Proposal Checking Duty ===%s\n", colorGreen, colorReset) @@ -140,7 +120,7 @@ func getStatus(c *cli.Context) error { } else { fmt.Println("The node does not have PDAO proposal checking duties enabled (See https://docs.rocketpool.net/guides/houston/pdao#challenge-process to learn more about this duty).") } - fmt.Println("") + fmt.Println() // Claimable Bonds Status: fmt.Printf("%s=== Pending, Active and Succeeded Proposals ===%s\n", colorGreen, colorReset) @@ -175,12 +155,12 @@ func getStatus(c *cli.Context) error { // Print message for Succeeded Proposals if stateName == "Succeeded" { succeededExists = true - fmt.Printf("%sThe following proposal(s) have succeeded and are waiting to be executed. Use `rocketpool pdao proposals execute` to execute.%s\n\n", colorBlue, colorReset) + fmt.Printf("%sThe following proposal(s) have succeeded and are waiting to be executed. Use `rocketpool pdao proposals execute` to execute.%s\n", colorBlue, colorReset) } // Proposal state count fmt.Printf("%d %s proposal(s):\n", len(proposals), stateName) - fmt.Println("") + fmt.Println() // Proposals for _, proposal := range proposals { From 8a91b72353d7d647fa02202a1e5908f4a57b634f Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Wed, 17 Jul 2024 20:07:25 -0700 Subject: [PATCH 16/29] Add PrintNetwork and error handling for unregistered nodes --- rocketpool-cli/commands/pdao/status.go | 37 +++++++++++++++++++---- rocketpool-daemon/api/pdao/status.go | 41 +++++++++++++------------- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/rocketpool-cli/commands/pdao/status.go b/rocketpool-cli/commands/pdao/status.go index 4eb918698..0d53f0317 100644 --- a/rocketpool-cli/commands/pdao/status.go +++ b/rocketpool-cli/commands/pdao/status.go @@ -1,6 +1,7 @@ package pdao import ( + "errors" "fmt" "math/big" @@ -32,6 +33,29 @@ func getStatus(c *cli.Context) error { return err } + // Get the config + cfg, isNew, err := rp.LoadConfig() + if err != nil { + return fmt.Errorf("error loading configuration: %w", err) + } + + // Get wallet status + statusResponse, err := rp.Api.Wallet.Status() + if err != nil { + return err + } + walletStatus := statusResponse.Data.WalletStatus + + // Print what network we're on + err = utils.PrintNetwork(cfg.Network.Value, isNew) + if err != nil { + return err + } + // rp.rp.Api.PDao.GetStatus() will fail with an error, but we can short-circuit it here. + if !walletStatus.Address.HasAddress { + return errors.New("No node address is loaded.") + } + // Get PDAO status at the latest block response, err := rp.Api.PDao.GetStatus() if err != nil { @@ -45,11 +69,14 @@ func getStatus(c *cli.Context) error { } // Get protocol DAO proposals - claimableBondsResponse, err := rp.Api.PDao.GetClaimableBonds() - if err != nil { - return fmt.Errorf("error checking for claimable bonds: %w", err) + var claimableBonds []api.BondClaimResult + if response.Data.IsNodeRegistered { + claimableBondsResponse, err := rp.Api.PDao.GetClaimableBonds() + if err != nil { + return fmt.Errorf("error checking for claimable bonds: %w", err) + } + claimableBonds = claimableBondsResponse.Data.ClaimableBonds } - claimableBonds := claimableBondsResponse.Data.ClaimableBonds // Signalling Status fmt.Printf("%s=== Signalling on Snapshot ===%s\n", colorGreen, colorReset) @@ -103,7 +130,7 @@ func getStatus(c *cli.Context) error { fmt.Printf("The node currently has %.6f RPL locked.\n", utilsMath.RoundDown(eth.WeiToEth(response.Data.NodeRPLLocked), 6)) } } else { - fmt.Println("The node is NOT allowed to lock RPL to create governance proposals/challenges. Use 'rocketpool node allow-rpl-locking, to allow RPL locking.") + fmt.Println("The node is NOT allowed to lock RPL to create governance proposals/challenges. Use 'rocketpool node allow-rpl-locking` to allow RPL locking.") } if len(claimableBonds) == 0 { fmt.Println("The node does not have any unlockable bonds or claimable rewards.") diff --git a/rocketpool-daemon/api/pdao/status.go b/rocketpool-daemon/api/pdao/status.go index 5b02b2a09..334913bcd 100644 --- a/rocketpool-daemon/api/pdao/status.go +++ b/rocketpool-daemon/api/pdao/status.go @@ -57,6 +57,7 @@ type protocolDaoGetStatusContext struct { registry *contracts.RocketSignerRegistry node *node.Node + nodeAddress common.Address propMgr *proposals.ProposalManager totalDelegatedVP *big.Int blockNumber uint64 @@ -71,7 +72,7 @@ func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) c.rp = sp.GetRocketPool() c.ec = sp.GetEthClient() c.bc = sp.GetBeaconClient() - nodeAddress, _ := sp.GetWallet().GetAddress() + c.nodeAddress, _ = sp.GetWallet().GetAddress() network := c.cfg.GetNetworkResources().Network // Requirements @@ -79,21 +80,15 @@ func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) if err != nil { return types.ResponseStatus_AddressNotPresent, err } - status, err := sp.RequireNodeRegistered(c.handler.ctx) - if err != nil { - return status, err - } else { - c.isNodeRegistered = true - } c.registry = sp.GetRocketSignerRegistry() if c.registry == nil { return types.ResponseStatus_ResourceNotFound, fmt.Errorf("Network [%v] does not have a signer registry contract.", network) } // Bindings - c.node, err = node.NewNode(c.rp, nodeAddress) + c.node, err = node.NewNode(c.rp, c.nodeAddress) if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("error creating node %s binding: %w", nodeAddress.Hex(), err) + return types.ResponseStatus_Error, fmt.Errorf("error creating node %s binding: %w", c.nodeAddress.Hex(), err) } c.propMgr, err = proposals.NewProposalManager(c.handler.ctx, c.handler.logger.Logger, c.cfg, c.rp, c.bc) if err != nil { @@ -103,15 +98,11 @@ func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error getting latest block number: %w", err) } - c.totalDelegatedVP, _, _, err = c.propMgr.GetArtifactsForVoting(uint32(c.blockNumber), nodeAddress) - if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("error getting voting artifacts for node %s at block %d: %w", nodeAddress.Hex(), c.blockNumber, err) - } c.votingTree, err = c.propMgr.GetNetworkTree(uint32(c.blockNumber), nil) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error getting network tree") } - // todo add https://github.com/rocket-pool/smartnode/blob/master/rocketpool/api/pdao/status.go#L132 + //TODO add https://github.com/rocket-pool/smartnode/blob/master/rocketpool/api/pdao/status.go#L132 return types.ResponseStatus_Success, nil @@ -125,30 +116,38 @@ func (c *protocolDaoGetStatusContext) GetState(mc *batch.MultiCaller) { c.node.IsRplLockingAllowed, c.node.RplLocked, ) - // Snapshot Registry - if c.registry != nil { - c.registry.NodeToSigner(mc, &c.signallingAddress, c.node.Address) - } + c.registry.NodeToSigner(mc, &c.signallingAddress, c.node.Address) } func (c *protocolDaoGetStatusContext) PrepareData(data *api.ProtocolDaoStatusResponse, opts *bind.TransactOpts) (types.ResponseStatus, error) { + var err error + + data.IsVotingInitialized = c.node.IsVotingInitialized.Get() + if data.IsVotingInitialized { + data.TotalDelegatedVp, _, _, err = c.propMgr.GetArtifactsForVoting(uint32(c.blockNumber), c.nodeAddress) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("error getting voting artifacts for node %s at block %d: %w", c.nodeAddress.Hex(), c.blockNumber, err) + } + } else { + data.TotalDelegatedVp = nil + } + + data.IsNodeRegistered = c.node.Exists.Get() data.BlockNumber = uint32(c.blockNumber) - data.IsNodeRegistered = c.isNodeRegistered data.SignallingAddress = c.signallingAddress data.SignallingAddressFormatted = utils.GetFormattedAddress(c.ec, data.SignallingAddress) data.AccountAddress = c.node.Address data.AccountAddressFormatted = utils.GetFormattedAddress(c.ec, data.AccountAddress) data.IsVotingInitialized = c.node.IsVotingInitialized.Get() data.SumVotingPower = c.votingTree.Nodes[0].Sum - data.TotalDelegatedVp = c.totalDelegatedVP data.IsRPLLockingAllowed = c.node.IsRplLockingAllowed.Get() data.NodeRPLLocked = c.node.RplLocked.Get() data.VerifyEnabled = c.cfg.VerifyProposals.Value //Get the voting power and delegate at that block - var err = c.rp.Query(func(mc *batch.MultiCaller) error { + err = c.rp.Query(func(mc *batch.MultiCaller) error { c.node.GetVotingPowerAtBlock(mc, &data.VotingPower, data.BlockNumber) c.node.GetVotingDelegateAtBlock(mc, &data.OnchainVotingDelegate, data.BlockNumber) return nil From 33a089cf22856173aa136880e036f45ddfd52764 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Wed, 17 Jul 2024 20:38:58 -0700 Subject: [PATCH 17/29] Remove else after return and unused fields --- rocketpool-daemon/api/pdao/clear-signalling-address.go | 10 +++++----- rocketpool-daemon/api/pdao/status.go | 10 ++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/rocketpool-daemon/api/pdao/clear-signalling-address.go b/rocketpool-daemon/api/pdao/clear-signalling-address.go index 78b726ed2..13bc4637f 100644 --- a/rocketpool-daemon/api/pdao/clear-signalling-address.go +++ b/rocketpool-daemon/api/pdao/clear-signalling-address.go @@ -74,11 +74,11 @@ func (c *protocolDaoClearSignallingAddressContext) PrepareData(data *types.TxInf // Return if there if no signalling address is set if c.signallingAddress == (common.Address{}) { return types.ResponseStatus_Error, fmt.Errorf("No signalling address set") - } else { - data.TxInfo, err = registry.ClearSigner(opts) - if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("Error getting the TX info for ClearSigner: %w", err) - } + } + data.TxInfo, err = registry.ClearSigner(opts) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("Error getting the TX info for ClearSigner: %w", err) + } return types.ResponseStatus_Success, nil } diff --git a/rocketpool-daemon/api/pdao/status.go b/rocketpool-daemon/api/pdao/status.go index 334913bcd..7ebac693f 100644 --- a/rocketpool-daemon/api/pdao/status.go +++ b/rocketpool-daemon/api/pdao/status.go @@ -2,7 +2,6 @@ package pdao import ( "fmt" - "math/big" "net/url" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -59,9 +58,7 @@ type protocolDaoGetStatusContext struct { node *node.Node nodeAddress common.Address propMgr *proposals.ProposalManager - totalDelegatedVP *big.Int blockNumber uint64 - isNodeRegistered bool signallingAddress common.Address votingTree *proposals.NetworkVotingTree } @@ -125,13 +122,14 @@ func (c *protocolDaoGetStatusContext) PrepareData(data *api.ProtocolDaoStatusRes var err error data.IsVotingInitialized = c.node.IsVotingInitialized.Get() - if data.IsVotingInitialized { + + if !data.IsVotingInitialized { + data.TotalDelegatedVp = nil + } else { data.TotalDelegatedVp, _, _, err = c.propMgr.GetArtifactsForVoting(uint32(c.blockNumber), c.nodeAddress) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error getting voting artifacts for node %s at block %d: %w", c.nodeAddress.Hex(), c.blockNumber, err) } - } else { - data.TotalDelegatedVp = nil } data.IsNodeRegistered = c.node.Exists.Get() From 32a7c963de108068481258e045e452b7bef4cbb5 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Thu, 18 Jul 2024 14:13:40 -0700 Subject: [PATCH 18/29] Remove const, fix message and nil pointer Remove const, fix message and nil pointer --- rocketpool-cli/commands/node/status.go | 4 +-- rocketpool-cli/commands/pdao/status.go | 35 +++++++++++--------------- rocketpool-daemon/api/pdao/status.go | 3 ++- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/rocketpool-cli/commands/node/status.go b/rocketpool-cli/commands/node/status.go index 5bd08ea44..709ba8bb8 100644 --- a/rocketpool-cli/commands/node/status.go +++ b/rocketpool-cli/commands/node/status.go @@ -67,9 +67,9 @@ func getStatus(c *cli.Context) error { return err } - // rp.NodeStatus() will fail with an error, but we can short-circuit it here. + // rp.Api.Node.Status() will fail with an error, but we can short-circuit it here. if !walletStatus.Address.HasAddress { - return errors.New("No node address is loaded.") + return errors.New("Node Wallet is not initialized.") } // Get node status diff --git a/rocketpool-cli/commands/pdao/status.go b/rocketpool-cli/commands/pdao/status.go index 0d53f0317..b95576505 100644 --- a/rocketpool-cli/commands/pdao/status.go +++ b/rocketpool-cli/commands/pdao/status.go @@ -17,14 +17,6 @@ import ( "github.com/rocket-pool/smartnode/v2/shared/types/api" ) -const ( - colorBlue string = terminal.ColorBlue - colorReset string = terminal.ColorReset - colorGreen string = terminal.ColorGreen - - signallingAddressLink string = "https://docs.rocketpool.net/guides/houston/participate#setting-your-snapshot-signalling-address" -) - func getStatus(c *cli.Context) error { // Get RP client @@ -51,9 +43,10 @@ func getStatus(c *cli.Context) error { if err != nil { return err } - // rp.rp.Api.PDao.GetStatus() will fail with an error, but we can short-circuit it here. + + // rp.Api.PDao.GetStatus() will fail with an error, but we can short-circuit it here. if !walletStatus.Address.HasAddress { - return errors.New("No node address is loaded.") + return errors.New("Node Wallet is not initialized.") } // Get PDAO status at the latest block @@ -79,13 +72,13 @@ func getStatus(c *cli.Context) error { } // Signalling Status - fmt.Printf("%s=== Signalling on Snapshot ===%s\n", colorGreen, colorReset) + fmt.Printf("%s=== Signalling on Snapshot ===%s\n", terminal.ColorGreen, terminal.ColorReset) blankAddress := common.Address{} if response.Data.SignallingAddress == blankAddress { fmt.Println("The node does not currently have a snapshot signalling address set.") - fmt.Printf("To learn more about snapshot signalling, please visit %s.\n", signallingAddressLink) + fmt.Println("To learn more about snapshot signalling, please visit https://docs.rocketpool.net/guides/houston/participate#setting-your-snapshot-signalling-address/") } else { - fmt.Printf("The node has a signalling address of %s%s%s which can represent it when voting on Rocket Pool Snapshot governance proposals.\n", colorBlue, response.Data.SignallingAddressFormatted, colorReset) + fmt.Printf("The node has a signalling address of %s%s%s which can represent it when voting on Rocket Pool Snapshot governance proposals.\n", terminal.ColorBlue, response.Data.SignallingAddressFormatted, terminal.ColorReset) } if response.Data.SnapshotResponse.Error != "" { fmt.Printf("Unable to fetch latest voting information from snapshot.org: %s\n", response.Data.SnapshotResponse.Error) @@ -100,18 +93,18 @@ func getStatus(c *cli.Context) error { fmt.Println() // Onchain Voting Status - fmt.Printf("%s=== Onchain Voting ===%s\n", colorGreen, colorReset) + fmt.Printf("%s=== Onchain Voting ===%s\n", terminal.ColorGreen, terminal.ColorReset) if response.Data.IsVotingInitialized { - fmt.Printf("The node %s%s%s has been initialized for onchain voting.\n", colorBlue, response.Data.AccountAddressFormatted, colorReset) + fmt.Printf("The node %s%s%s has been initialized for onchain voting.\n", terminal.ColorBlue, response.Data.AccountAddressFormatted, terminal.ColorReset) } else { - fmt.Printf("The node %s%s%s has NOT been initialized for onchain voting. You need to run `rocketpool pdao initialize-voting` to participate in onchain votes.\n", colorBlue, response.Data.AccountAddressFormatted, colorReset) + fmt.Printf("The node %s%s%s has NOT been initialized for onchain voting. You need to run `rocketpool pdao initialize-voting` to participate in onchain votes.\n", terminal.ColorBlue, response.Data.AccountAddressFormatted, terminal.ColorReset) } if response.Data.OnchainVotingDelegate == blankAddress { fmt.Println("The node doesn't have a delegate, which means it can vote directly on onchain proposals after it initializes voting.") } else if response.Data.OnchainVotingDelegate == response.Data.AccountAddress { fmt.Println("The node doesn't have a delegate, which means it can vote directly on onchain proposals. You can have another node represent you by running `rocketpool p svd
`.") } else { - fmt.Printf("The node has a voting delegate of %s%s%s which can represent it when voting on Rocket Pool onchain governance proposals.\n", colorBlue, response.Data.OnchainVotingDelegateFormatted, colorReset) + fmt.Printf("The node has a voting delegate of %s%s%s which can represent it when voting on Rocket Pool onchain governance proposals.\n", terminal.ColorBlue, response.Data.OnchainVotingDelegateFormatted, terminal.ColorReset) } fmt.Printf("The node's local voting power: %.10f\n", eth.WeiToEth(response.Data.VotingPower)) if response.Data.IsNodeRegistered { @@ -123,7 +116,7 @@ func getStatus(c *cli.Context) error { fmt.Println() // Claimable Bonds Status: - fmt.Printf("%s=== Claimable RPL Bonds ===%s\n", colorGreen, colorReset) + fmt.Printf("%s=== Claimable RPL Bonds ===%s\n", terminal.ColorGreen, terminal.ColorReset) if response.Data.IsRPLLockingAllowed { fmt.Println("The node is allowed to lock RPL to create governance proposals/challenges.") if response.Data.NodeRPLLocked.Cmp(big.NewInt(0)) != 0 { @@ -140,7 +133,7 @@ func getStatus(c *cli.Context) error { fmt.Println() // Check if PDAO proposal checking duty is enabled - fmt.Printf("%s=== PDAO Proposal Checking Duty ===%s\n", colorGreen, colorReset) + fmt.Printf("%s=== PDAO Proposal Checking Duty ===%s\n", terminal.ColorGreen, terminal.ColorReset) // Make sure the user opted into this duty if response.Data.VerifyEnabled { fmt.Println("The node has PDAO proposal checking duties enabled. It will periodically check for proposals to challenge.") @@ -150,7 +143,7 @@ func getStatus(c *cli.Context) error { fmt.Println() // Claimable Bonds Status: - fmt.Printf("%s=== Pending, Active and Succeeded Proposals ===%s\n", colorGreen, colorReset) + fmt.Printf("%s=== Pending, Active and Succeeded Proposals ===%s\n", terminal.ColorGreen, terminal.ColorReset) // Get proposals by state stateProposals := map[string][]api.ProtocolDaoProposalDetails{} for _, proposal := range allProposals.Data.Proposals { @@ -182,7 +175,7 @@ func getStatus(c *cli.Context) error { // Print message for Succeeded Proposals if stateName == "Succeeded" { succeededExists = true - fmt.Printf("%sThe following proposal(s) have succeeded and are waiting to be executed. Use `rocketpool pdao proposals execute` to execute.%s\n", colorBlue, colorReset) + fmt.Printf("%sThe following proposal(s) have succeeded and are waiting to be executed. Use `rocketpool pdao proposals execute` to execute.%s\n", terminal.ColorBlue, terminal.ColorReset) } // Proposal state count diff --git a/rocketpool-daemon/api/pdao/status.go b/rocketpool-daemon/api/pdao/status.go index 7ebac693f..781ce7fc1 100644 --- a/rocketpool-daemon/api/pdao/status.go +++ b/rocketpool-daemon/api/pdao/status.go @@ -2,6 +2,7 @@ package pdao import ( "fmt" + "math/big" "net/url" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -124,7 +125,7 @@ func (c *protocolDaoGetStatusContext) PrepareData(data *api.ProtocolDaoStatusRes data.IsVotingInitialized = c.node.IsVotingInitialized.Get() if !data.IsVotingInitialized { - data.TotalDelegatedVp = nil + data.TotalDelegatedVp = big.NewInt(0) } else { data.TotalDelegatedVp, _, _, err = c.propMgr.GetArtifactsForVoting(uint32(c.blockNumber), c.nodeAddress) if err != nil { From 276d4ef2d715cbd233ac7a1199d981304d1bcba5 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Thu, 18 Jul 2024 21:52:52 -0700 Subject: [PATCH 19/29] Add api call to grab active snapshot proposals --- rocketpool-cli/commands/pdao/status.go | 8 +++- rocketpool-daemon/api/pdao/status.go | 20 +++++++-- shared/types/api/pdao.go | 60 ++++++++------------------ 3 files changed, 42 insertions(+), 46 deletions(-) diff --git a/rocketpool-cli/commands/pdao/status.go b/rocketpool-cli/commands/pdao/status.go index b95576505..016d5f837 100644 --- a/rocketpool-cli/commands/pdao/status.go +++ b/rocketpool-cli/commands/pdao/status.go @@ -83,7 +83,13 @@ func getStatus(c *cli.Context) error { if response.Data.SnapshotResponse.Error != "" { fmt.Printf("Unable to fetch latest voting information from snapshot.org: %s\n", response.Data.SnapshotResponse.Error) } else { - voteCount := response.Data.SnapshotResponse.VoteCount() + voteCount := 0 + for _, activeProposal := range response.Data.SnapshotResponse.ActiveSnapshotProposals { + if len(activeProposal.DelegateVotes) > 0 || len(activeProposal.UserVotes) > 0 { + voteCount++ + break + } + } if len(response.Data.SnapshotResponse.ActiveSnapshotProposals) == 0 { fmt.Println("Rocket Pool has no Snapshot governance proposals being voted on.") } else { diff --git a/rocketpool-daemon/api/pdao/status.go b/rocketpool-daemon/api/pdao/status.go index 781ce7fc1..c415ddc5b 100644 --- a/rocketpool-daemon/api/pdao/status.go +++ b/rocketpool-daemon/api/pdao/status.go @@ -19,6 +19,7 @@ import ( "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/contracts" "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/proposals" "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/utils" + "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/voting" "github.com/rocket-pool/smartnode/v2/shared/config" "github.com/rocket-pool/smartnode/v2/shared/types/api" ) @@ -100,7 +101,6 @@ func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error getting network tree") } - //TODO add https://github.com/rocket-pool/smartnode/blob/master/rocketpool/api/pdao/status.go#L132 return types.ResponseStatus_Success, nil @@ -135,8 +135,6 @@ func (c *protocolDaoGetStatusContext) PrepareData(data *api.ProtocolDaoStatusRes data.IsNodeRegistered = c.node.Exists.Get() data.BlockNumber = uint32(c.blockNumber) - data.SignallingAddress = c.signallingAddress - data.SignallingAddressFormatted = utils.GetFormattedAddress(c.ec, data.SignallingAddress) data.AccountAddress = c.node.Address data.AccountAddressFormatted = utils.GetFormattedAddress(c.ec, data.AccountAddress) data.IsVotingInitialized = c.node.IsVotingInitialized.Get() @@ -145,7 +143,7 @@ func (c *protocolDaoGetStatusContext) PrepareData(data *api.ProtocolDaoStatusRes data.NodeRPLLocked = c.node.RplLocked.Get() data.VerifyEnabled = c.cfg.VerifyProposals.Value - //Get the voting power and delegate at that block + // Get the voting power and delegate at that block err = c.rp.Query(func(mc *batch.MultiCaller) error { c.node.GetVotingPowerAtBlock(mc, &data.VotingPower, data.BlockNumber) c.node.GetVotingDelegateAtBlock(mc, &data.OnchainVotingDelegate, data.BlockNumber) @@ -155,5 +153,19 @@ func (c *protocolDaoGetStatusContext) PrepareData(data *api.ProtocolDaoStatusRes return types.ResponseStatus_Error, fmt.Errorf("error getting voting info for block %d: %w", c.blockNumber, err) } data.OnchainVotingDelegateFormatted = utils.GetFormattedAddress(c.ec, data.OnchainVotingDelegate) + + // Get the signalling address and active snapshot proposals + emptyAddress := common.Address{} + data.SignallingAddress = c.signallingAddress + if data.SignallingAddress != emptyAddress { + data.SignallingAddressFormatted = utils.GetFormattedAddress(c.ec, c.signallingAddress) + } + props, err := voting.GetSnapshotProposals(c.cfg, c.node.Address, c.signallingAddress, true) + if err != nil { + data.SnapshotResponse.Error = fmt.Sprintf("error getting snapshot proposals: %s", err.Error()) + } else { + data.SnapshotResponse.ActiveSnapshotProposals = props + } + return types.ResponseStatus_Success, nil } diff --git a/shared/types/api/pdao.go b/shared/types/api/pdao.go index dd2e65b73..7d5b28774 100644 --- a/shared/types/api/pdao.go +++ b/shared/types/api/pdao.go @@ -301,49 +301,27 @@ type ProtocolDaoCurrentVotingDelegateData struct { } type ProtocolDaoStatusResponse struct { - Status string `json:"status"` - Error string `json:"error"` - VotingPower *big.Int `json:"votingPower"` - OnchainVotingDelegate common.Address `json:"onchainVotingDelegate"` - OnchainVotingDelegateFormatted string `json:"onchainVotingDelegateFormatted"` - BlockNumber uint32 `json:"blockNumber"` - VerifyEnabled bool `json:"verifyEnabled"` - IsVotingInitialized bool `json:"isVotingInitialized"` - SignallingAddress common.Address `json:"signallingAddress"` - SignallingAddressFormatted string `json:"signallingAddressFormatted"` - SnapshotResponse SnapshotResponseStruct `json:"snapshotResponse"` - IsRPLLockingAllowed bool `json:"isRPLLockingAllowed"` - NodeRPLLocked *big.Int `json:"nodeRPLLocked"` - AccountAddress common.Address `json:"accountAddress"` - AccountAddressFormatted string `json:"accountAddressFormatted"` - TotalDelegatedVp *big.Int `json:"totalDelegatedVp"` - SumVotingPower *big.Int `json:"sumVotingPower"` - IsNodeRegistered bool `json:"isNodeRegistered"` + Status string `json:"status"` + Error string `json:"error"` + VotingPower *big.Int `json:"votingPower"` + OnchainVotingDelegate common.Address `json:"onchainVotingDelegate"` + OnchainVotingDelegateFormatted string `json:"onchainVotingDelegateFormatted"` + BlockNumber uint32 `json:"blockNumber"` + VerifyEnabled bool `json:"verifyEnabled"` + IsVotingInitialized bool `json:"isVotingInitialized"` + SignallingAddress common.Address `json:"signallingAddress"` + SignallingAddressFormatted string `json:"signallingAddressFormatted"` + SnapshotResponse SnapshotResponseData `json:"snapshotResponse"` + IsRPLLockingAllowed bool `json:"isRPLLockingAllowed"` + NodeRPLLocked *big.Int `json:"nodeRPLLocked"` + AccountAddress common.Address `json:"accountAddress"` + AccountAddressFormatted string `json:"accountAddressFormatted"` + TotalDelegatedVp *big.Int `json:"totalDelegatedVp"` + SumVotingPower *big.Int `json:"sumVotingPower"` + IsNodeRegistered bool `json:"isNodeRegistered"` } -type snapshotProposalVote struct { - Choice interface{} `json:"choice"` - Voter common.Address `json:"voter"` - Proposal struct { - Id string `json:"id"` - State string `json:"state"` - } `json:"proposal"` -} -type SnapshotResponseStruct struct { +type SnapshotResponseData struct { Error string `json:"error"` - ProposalVotes []snapshotProposalVote `json:"proposalVotes"` ActiveSnapshotProposals []*sharedtypes.SnapshotProposal `json:"activeSnapshotProposals"` } - -func (s *SnapshotResponseStruct) VoteCount() uint { - voteCount := uint(0) - for _, activeProposal := range s.ActiveSnapshotProposals { - for _, votedProposal := range s.ProposalVotes { - if votedProposal.Proposal.Id == activeProposal.Id { - voteCount++ - break - } - } - } - return voteCount -} From 6fce08cdde6df06fc85e5d741a318efe3b53bacd Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Thu, 18 Jul 2024 22:02:25 -0700 Subject: [PATCH 20/29] Edit response to improve error output --- rocketpool-daemon/api/pdao/status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpool-daemon/api/pdao/status.go b/rocketpool-daemon/api/pdao/status.go index c415ddc5b..5ab9d5c93 100644 --- a/rocketpool-daemon/api/pdao/status.go +++ b/rocketpool-daemon/api/pdao/status.go @@ -81,7 +81,7 @@ func (c *protocolDaoGetStatusContext) Initialize() (types.ResponseStatus, error) } c.registry = sp.GetRocketSignerRegistry() if c.registry == nil { - return types.ResponseStatus_ResourceNotFound, fmt.Errorf("Network [%v] does not have a signer registry contract.", network) + return types.ResponseStatus_Error, fmt.Errorf("Network [%v] does not have a signer registry contract.", network) } // Bindings From fee1e68ab5909ffd2cdf88ab15a9853718f28183 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Thu, 18 Jul 2024 23:23:08 -0700 Subject: [PATCH 21/29] Moved eip712 signature validation logic into its own package --- rocketpool-cli/commands/pdao/commands.go | 5 -- .../api/pdao/set-signalling-address.go | 15 ++-- shared/eip712/eip712.go | 69 +++++++++++++++++++ shared/utils/input.go | 41 ----------- 4 files changed, 78 insertions(+), 52 deletions(-) create mode 100644 shared/eip712/eip712.go diff --git a/rocketpool-cli/commands/pdao/commands.go b/rocketpool-cli/commands/pdao/commands.go index 1cb0de2ef..0d66caa55 100644 --- a/rocketpool-cli/commands/pdao/commands.go +++ b/rocketpool-cli/commands/pdao/commands.go @@ -180,11 +180,6 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { if err != nil { return err } - // leaving this out until https://github.com/rocket-pool/node-manager-core/pull/17 is merged - // signature, err := input.ValidateSignature("signature", c.Args().Get(1)) - // if err != nil { - // return err - // } signature := c.Args().Get(1) // Run diff --git a/rocketpool-daemon/api/pdao/set-signalling-address.go b/rocketpool-daemon/api/pdao/set-signalling-address.go index 663122853..4c4831b0b 100644 --- a/rocketpool-daemon/api/pdao/set-signalling-address.go +++ b/rocketpool-daemon/api/pdao/set-signalling-address.go @@ -16,7 +16,7 @@ import ( "github.com/rocket-pool/rocketpool-go/v2/node" "github.com/rocket-pool/rocketpool-go/v2/rocketpool" "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/contracts" - "github.com/rocket-pool/smartnode/v2/shared/utils" + "github.com/rocket-pool/smartnode/v2/shared/eip712" ) // =============== @@ -33,8 +33,6 @@ func (f *protocolDaoSetSignallingAddressFactory) Create(args url.Values) (*proto } inputErrs := []error{ server.ValidateArg("signallingAddress", args, input.ValidateAddress, &c.signallingAddress), - // leaving this out until ValidateArg() is added to NMC - // server.ValidateArg("signature", args, input.ValidateSignature, &c.signature), server.GetStringFromVars("signature", args, &c.signature), } return c, errors.Join(inputErrs...) @@ -108,12 +106,17 @@ func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *types.TxInfoD return types.ResponseStatus_Error, fmt.Errorf("Voting must be initialized to set a signalling address. Use 'rocketpool pdao initialize-voting' to initialize voting first") } - sig, err := utils.ParseEIP712(c.signature) + signatureSanitized, err := eip712.SanitizeEIP712String(c.signature) if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("Error splitting EIP-712 signature") + return types.ResponseStatus_Error, fmt.Errorf("Error sanitizing signature input") } - data.TxInfo, err = c.registry.SetSigner(c.signallingAddress, opts, sig.V, sig.R, sig.S) + components, err := eip712.ParseEIP712Components(signatureSanitized) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("Error parsing EIP-712 components") + } + + data.TxInfo, err = c.registry.SetSigner(c.signallingAddress, opts, components.V, components.R, components.S) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("Error getting the TX info for SetSigner: %w", err) } diff --git a/shared/eip712/eip712.go b/shared/eip712/eip712.go new file mode 100644 index 000000000..764c7f890 --- /dev/null +++ b/shared/eip712/eip712.go @@ -0,0 +1,69 @@ +package eip712 + +import ( + "encoding/hex" + "errors" + "fmt" + "strings" +) + +type EIP712Components struct { + V uint8 `json:"v"` + R [32]byte `json:"r"` + S [32]byte `json:"s"` +} + +const EIP712Length = 65 + +// Pretty print for EIP712Components +func (e EIP712Components) String() { + fmt.Printf("EIP712 Components:\n") + fmt.Printf("V: %d\n", e.V) + fmt.Printf("R: %x\n", e.R) + fmt.Printf("S: %x\n", e.S) +} + +// SanitizeHexInput checks for and strips a 0x prefix, returning an error if one is not found. +// It returns the byte slice representing the decoded hex, or a decoding error +func SanitizeHex(inp string) ([]byte, error) { + if !strings.HasPrefix(inp, "0x") { + return nil, errors.New("expected hex prefix") + } + return hex.DecodeString(strings.TrimPrefix(inp, "0x")) +} + +// SanitizeEIP712String checks that a 0x-prefixed hex string is the proper length to be +// parsed as a EIP-712 signature, and parses it, returning an error if the length is invalid +// or the hex cannot be decoded. +func SanitizeEIP712String(inp string) ([]byte, error) { + decoded, err := SanitizeHex(inp) + if err != nil { + return nil, fmt.Errorf("error sanitizing EIP-712 signature string as hex: %w", err) + } + if len(decoded) != EIP712Length { + return nil, fmt.Errorf("error sanitizing EIP-712 signature string: invalid length %d (expected %d)", len(decoded), EIP712Length) + } + return decoded, nil +} + +// ParseEIP712Components expects a sanitized 65 byte EIP-712 signature +// and returns v/r/s: v as uint8 and r, s as [32]byte +func ParseEIP712Components(data []byte) (EIP712Components, error) { + if len(data) != EIP712Length { + return EIP712Components{}, fmt.Errorf("error parsing EIP-712 signature string: invalid length %d (expected %d)", len(data), EIP712Length) + } + + var v uint8 + var r [32]byte + var s [32]byte + + v = data[64] + copy(r[:], data[0:32]) + copy(s[:], data[32:64]) + + return EIP712Components{ + V: v, + R: r, + S: s, + }, nil +} diff --git a/shared/utils/input.go b/shared/utils/input.go index 14d1653b8..b60328814 100644 --- a/shared/utils/input.go +++ b/shared/utils/input.go @@ -5,8 +5,6 @@ import ( "errors" "fmt" "os" - "regexp" - "strconv" "strings" "github.com/urfave/cli/v2" @@ -23,12 +21,6 @@ const ( MinPasswordLength int = 12 ) -type EIP712Components struct { - V uint8 `json:"v"` - R [32]byte `json:"r"` - S [32]byte `json:"s"` -} - // Validate command argument count - only used by the CLI // TODO: refactor CLI arg validation and move it out of shared func ValidateArgCount(c *cli.Context, expectedCount int) { @@ -113,36 +105,3 @@ func ValidateVoteDirection(name, value string) (types.VoteDirection, error) { } return val, nil } - -// Expects a 129 byte 0x-prefixed EIP-712 signature and returns v/r/s as v uint8 and r, s [32]byte -func ParseEIP712(signature string) (*EIP712Components, error) { - if len(signature) != 132 || signature[:2] != "0x" { - return nil, fmt.Errorf("Invalid 129 byte 0x-prefixed EIP-712 signature while parsing: '%s'", signature) - } - signature = signature[2:] - if !regexp.MustCompile("^[A-Fa-f0-9]+$").MatchString(signature) { - return &EIP712Components{}, fmt.Errorf("Invalid 129 byte 0x-prefixed EIP-712 signature while parsing: '%s'", signature) - } - - // Slice signature string into v, r, s component of a signature giving node permission to use the given signer - str_v := signature[len(signature)-2:] - str_r := signature[:64] - str_s := signature[64:128] - - // Convert v to uint8 and v,s to [32]byte - bytes_r, err := hex.DecodeString(str_r) - if err != nil { - return &EIP712Components{}, fmt.Errorf("error decoding r: %v", err) - } - bytes_s, err := hex.DecodeString(str_s) - if err != nil { - return &EIP712Components{}, fmt.Errorf("error decoding s: %v", err) - } - - int_v, err := strconv.ParseUint(str_v, 16, 8) - if err != nil { - return &EIP712Components{}, fmt.Errorf("error parsing v: %v", err) - } - - return &EIP712Components{uint8(int_v), ([32]byte)(bytes_r), ([32]byte)(bytes_s)}, nil -} From 0d690b670b1aa37bba196fb677c7325705c96eca Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Sun, 21 Jul 2024 21:29:39 -0700 Subject: [PATCH 22/29] Refactor package eip712 to use Decode and Encode, added unit tests for eip712 --- .../api/pdao/set-signalling-address.go | 12 ++-- shared/eip712/eip712.go | 67 +++++++------------ shared/eip712/eip712_test.go | 48 +++++++++++++ 3 files changed, 78 insertions(+), 49 deletions(-) create mode 100644 shared/eip712/eip712_test.go diff --git a/rocketpool-daemon/api/pdao/set-signalling-address.go b/rocketpool-daemon/api/pdao/set-signalling-address.go index 4c4831b0b..8902ad392 100644 --- a/rocketpool-daemon/api/pdao/set-signalling-address.go +++ b/rocketpool-daemon/api/pdao/set-signalling-address.go @@ -106,17 +106,13 @@ func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *types.TxInfoD return types.ResponseStatus_Error, fmt.Errorf("Voting must be initialized to set a signalling address. Use 'rocketpool pdao initialize-voting' to initialize voting first") } - signatureSanitized, err := eip712.SanitizeEIP712String(c.signature) + eip712Components := new(eip712.EIP712Components) + err := eip712Components.Decode(c.signature) if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("Error sanitizing signature input") + return types.ResponseStatus_Error, fmt.Errorf("Error decoding signature: %w", err) } - components, err := eip712.ParseEIP712Components(signatureSanitized) - if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("Error parsing EIP-712 components") - } - - data.TxInfo, err = c.registry.SetSigner(c.signallingAddress, opts, components.V, components.R, components.S) + data.TxInfo, err = c.registry.SetSigner(c.signallingAddress, opts, eip712Components.V, eip712Components.R, eip712Components.S) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("Error getting the TX info for SetSigner: %w", err) } diff --git a/shared/eip712/eip712.go b/shared/eip712/eip712.go index 764c7f890..e1c73ea2b 100644 --- a/shared/eip712/eip712.go +++ b/shared/eip712/eip712.go @@ -1,16 +1,15 @@ package eip712 import ( - "encoding/hex" - "errors" "fmt" - "strings" + + "github.com/ethereum/go-ethereum/common/hexutil" ) type EIP712Components struct { - V uint8 `json:"v"` R [32]byte `json:"r"` S [32]byte `json:"s"` + V uint8 `json:"v"` } const EIP712Length = 65 @@ -18,52 +17,38 @@ const EIP712Length = 65 // Pretty print for EIP712Components func (e EIP712Components) String() { fmt.Printf("EIP712 Components:\n") - fmt.Printf("V: %d\n", e.V) fmt.Printf("R: %x\n", e.R) fmt.Printf("S: %x\n", e.S) + fmt.Printf("V: %d\n", e.V) } -// SanitizeHexInput checks for and strips a 0x prefix, returning an error if one is not found. -// It returns the byte slice representing the decoded hex, or a decoding error -func SanitizeHex(inp string) ([]byte, error) { - if !strings.HasPrefix(inp, "0x") { - return nil, errors.New("expected hex prefix") - } - return hex.DecodeString(strings.TrimPrefix(inp, "0x")) -} - -// SanitizeEIP712String checks that a 0x-prefixed hex string is the proper length to be -// parsed as a EIP-712 signature, and parses it, returning an error if the length is invalid -// or the hex cannot be decoded. -func SanitizeEIP712String(inp string) ([]byte, error) { - decoded, err := SanitizeHex(inp) +// Decode decodes a hex-encoded EIP-712 signature string, verifies the length +// and assigns the appropriate bytes to R/S/V +func (e *EIP712Components) Decode(inp string) error { + decodedBytes, err := hexutil.Decode(inp) if err != nil { - return nil, fmt.Errorf("error sanitizing EIP-712 signature string as hex: %w", err) - } - if len(decoded) != EIP712Length { - return nil, fmt.Errorf("error sanitizing EIP-712 signature string: invalid length %d (expected %d)", len(decoded), EIP712Length) + return err } - return decoded, nil -} -// ParseEIP712Components expects a sanitized 65 byte EIP-712 signature -// and returns v/r/s: v as uint8 and r, s as [32]byte -func ParseEIP712Components(data []byte) (EIP712Components, error) { - if len(data) != EIP712Length { - return EIP712Components{}, fmt.Errorf("error parsing EIP-712 signature string: invalid length %d (expected %d)", len(data), EIP712Length) + if len(decodedBytes) != EIP712Length { + return fmt.Errorf("error decoding EIP-712 signature string: invalid length %d bytes (expected %d bytes)", len(decodedBytes), EIP712Length) } - var v uint8 - var r [32]byte - var s [32]byte + copy(e.R[:], decodedBytes[0:32]) + copy(e.S[:], decodedBytes[32:64]) + e.V = decodedBytes[64] + + return nil +} + +// Encode initializes an empty byte slice, copies fields R/S/V into it, +// encodes the byte slice into a hex string then returns it +func (e *EIP712Components) Encode() string { + encodedBytes := make([]byte, EIP712Length) - v = data[64] - copy(r[:], data[0:32]) - copy(s[:], data[32:64]) + copy(encodedBytes[0:32], e.R[:]) + copy(encodedBytes[32:64], e.S[:]) + encodedBytes[64] = e.V - return EIP712Components{ - V: v, - R: r, - S: s, - }, nil + return hexutil.Encode(encodedBytes) } diff --git a/shared/eip712/eip712_test.go b/shared/eip712/eip712_test.go new file mode 100644 index 000000000..bf207ffb6 --- /dev/null +++ b/shared/eip712/eip712_test.go @@ -0,0 +1,48 @@ +package eip712 + +import ( + "testing" +) + +func TestDecodeAndEncode(t *testing.T) { + + // signer := "0x18eea3fbe5008d6f7a95d963a4be403e82d35758" + signature := "0xba283b21f7168e53b082ad552d974591abe0f4db5b7032374abbcdcf09e0eadc2c0530ff4ac1d63e19c1ceca2d14b374c86b6c84f46bbd57747b48c21388c4e71c" + eip712Components := new(EIP712Components) + + // Decode string + err := eip712Components.Decode(signature) + if err != nil { + t.Fatalf("Failed to decode signature: %v", err) + } + + eip712Components.String() + + // Encode eip712Components back to a string + encodedSig := eip712Components.Encode() + + if encodedSig != signature { + t.Fatalf("Expected %s but got %s", signature, encodedSig) + } + +} + +func TestInvalidSignature(t *testing.T) { + invalidSignature := "0xinvalidsignature" + eip712Components := new(EIP712Components) + + err := eip712Components.Decode(invalidSignature) + if err == nil { + t.Fatal("Expected error for invalid signature but got none") + } +} + +func TestEmptySignature(t *testing.T) { + emptySignature := "" + eip712Components := new(EIP712Components) + + err := eip712Components.Decode(emptySignature) + if err == nil { + t.Fatal("Expected error for empty signature but got none") + } +} From 05e2934be033e6a2b26569a13c6eeeac6bdc8103 Mon Sep 17 00:00:00 2001 From: Thomas Pan <43927782+thomaspanf@users.noreply.github.com> Date: Sun, 21 Jul 2024 22:45:16 -0700 Subject: [PATCH 23/29] Update shared/eip712/eip712.go Co-authored-by: Jacob Shufro <116244+jshufro@users.noreply.github.com> --- shared/eip712/eip712.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/eip712/eip712.go b/shared/eip712/eip712.go index e1c73ea2b..06b9d71cf 100644 --- a/shared/eip712/eip712.go +++ b/shared/eip712/eip712.go @@ -16,7 +16,7 @@ const EIP712Length = 65 // Pretty print for EIP712Components func (e EIP712Components) String() { - fmt.Printf("EIP712 Components:\n") + fmt.Println("EIP712 Components:") fmt.Printf("R: %x\n", e.R) fmt.Printf("S: %x\n", e.S) fmt.Printf("V: %d\n", e.V) From cab82dd3e3759c34acaf8091f213d5a5c8ed6f2f Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Mon, 22 Jul 2024 12:58:55 -0700 Subject: [PATCH 24/29] Add Validate to eip712 package --- .../api/pdao/set-signalling-address.go | 22 +++++++++-- shared/eip712/eip712.go | 38 +++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/rocketpool-daemon/api/pdao/set-signalling-address.go b/rocketpool-daemon/api/pdao/set-signalling-address.go index 8902ad392..2b0619964 100644 --- a/rocketpool-daemon/api/pdao/set-signalling-address.go +++ b/rocketpool-daemon/api/pdao/set-signalling-address.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/url" + "strings" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -54,6 +55,7 @@ type protocolDaoSetSignallingAddressContext struct { registry *contracts.RocketSignerRegistry node *node.Node + nodeAddress common.Address signallingAddress common.Address nodeToSigner common.Address signature string @@ -62,7 +64,7 @@ type protocolDaoSetSignallingAddressContext struct { func (c *protocolDaoSetSignallingAddressContext) Initialize() (types.ResponseStatus, error) { sp := c.handler.serviceProvider c.rp = sp.GetRocketPool() - nodeAddress, _ := sp.GetWallet().GetAddress() + c.nodeAddress, _ = sp.GetWallet().GetAddress() cfg := sp.GetConfig() network := cfg.GetNetworkResources().Network @@ -77,9 +79,9 @@ func (c *protocolDaoSetSignallingAddressContext) Initialize() (types.ResponseSta } // Binding - c.node, err = node.NewNode(c.rp, nodeAddress) + c.node, err = node.NewNode(c.rp, c.nodeAddress) if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("Error creating node %s binding: %w", nodeAddress.Hex(), err) + return types.ResponseStatus_Error, fmt.Errorf("Error creating node %s binding: %w", c.nodeAddress.Hex(), err) } return types.ResponseStatus_Success, nil @@ -98,6 +100,8 @@ func (c *protocolDaoSetSignallingAddressContext) GetState(mc *batch.MultiCaller) func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *types.TxInfoData, opts *bind.TransactOpts) (types.ResponseStatus, error) { + message, err := constructMessage(strings.ToLower(c.nodeAddress.Hex())) + if c.signallingAddress == c.nodeToSigner { return types.ResponseStatus_Error, fmt.Errorf("Signer address already in use") } @@ -107,11 +111,16 @@ func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *types.TxInfoD } eip712Components := new(eip712.EIP712Components) - err := eip712Components.Decode(c.signature) + err = eip712Components.Decode(c.signature) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("Error decoding signature: %w", err) } + err = eip712Components.Validate(message, c.signallingAddress) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("Error validating signature: %w", err) + } + data.TxInfo, err = c.registry.SetSigner(c.signallingAddress, opts, eip712Components.V, eip712Components.R, eip712Components.S) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("Error getting the TX info for SetSigner: %w", err) @@ -119,3 +128,8 @@ func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *types.TxInfoD return types.ResponseStatus_Success, nil } + +func constructMessage(nodeAddress string) ([]byte, error) { + message := fmt.Sprintf("%s may delegate to me for Rocket Pool governance", nodeAddress) + return []byte(message), nil +} diff --git a/shared/eip712/eip712.go b/shared/eip712/eip712.go index 06b9d71cf..215491388 100644 --- a/shared/eip712/eip712.go +++ b/shared/eip712/eip712.go @@ -3,7 +3,10 @@ package eip712 import ( "fmt" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" ) type EIP712Components struct { @@ -52,3 +55,38 @@ func (e *EIP712Components) Encode() string { return hexutil.Encode(encodedBytes) } + +// Validate recovers the address of a signer from a message and signature and +// compares the recovered signer address to the expected signer address +func (e *EIP712Components) Validate(msg []byte, expectedSigner common.Address) error { + + hash := accounts.TextHash(msg) + + // Convert the EIP712Components to a signature + sig := make([]byte, 65) + copy(sig[0:32], e.R[:]) + copy(sig[32:64], e.S[:]) + sig[64] = e.V + + // V (Recovery ID) must by 27 or 28, so we subtract 27 from 0 or 1 to get the recovery ID. + sig[crypto.RecoveryIDOffset] -= 27 + + // Recover the public key from the signature + pubKey, err := crypto.SigToPub(hash, sig) + if err != nil { + return fmt.Errorf("error recovering public key: %v", err) + } + + // Restore V to its original value + sig[crypto.RecoveryIDOffset] += 27 + + // Derive the address from the public key + recoveredAddr := crypto.PubkeyToAddress(*pubKey) + + // Compare the recovered address with the expected address + if recoveredAddr != expectedSigner { + return fmt.Errorf("signature does not match the expected signer: got %s, expected %s", recoveredAddr.Hex(), expectedSigner.Hex()) + } + + return nil +} From 8b4317d3bf1ee0f5037113dacd16f79908b96701 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Mon, 22 Jul 2024 13:18:56 -0700 Subject: [PATCH 25/29] Add test for decoding invalid length signature, fix linter issues --- .../api/pdao/set-signalling-address.go | 8 +++---- shared/eip712/eip712_test.go | 23 +++++++++++++++---- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/rocketpool-daemon/api/pdao/set-signalling-address.go b/rocketpool-daemon/api/pdao/set-signalling-address.go index 2b0619964..ea91a330c 100644 --- a/rocketpool-daemon/api/pdao/set-signalling-address.go +++ b/rocketpool-daemon/api/pdao/set-signalling-address.go @@ -100,7 +100,7 @@ func (c *protocolDaoSetSignallingAddressContext) GetState(mc *batch.MultiCaller) func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *types.TxInfoData, opts *bind.TransactOpts) (types.ResponseStatus, error) { - message, err := constructMessage(strings.ToLower(c.nodeAddress.Hex())) + message := constructMessage(strings.ToLower(c.nodeAddress.Hex())) if c.signallingAddress == c.nodeToSigner { return types.ResponseStatus_Error, fmt.Errorf("Signer address already in use") @@ -111,7 +111,7 @@ func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *types.TxInfoD } eip712Components := new(eip712.EIP712Components) - err = eip712Components.Decode(c.signature) + err := eip712Components.Decode(c.signature) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("Error decoding signature: %w", err) } @@ -129,7 +129,7 @@ func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *types.TxInfoD return types.ResponseStatus_Success, nil } -func constructMessage(nodeAddress string) ([]byte, error) { +func constructMessage(nodeAddress string) []byte { message := fmt.Sprintf("%s may delegate to me for Rocket Pool governance", nodeAddress) - return []byte(message), nil + return []byte(message) } diff --git a/shared/eip712/eip712_test.go b/shared/eip712/eip712_test.go index bf207ffb6..653249e5e 100644 --- a/shared/eip712/eip712_test.go +++ b/shared/eip712/eip712_test.go @@ -1,12 +1,11 @@ package eip712 import ( + "fmt" "testing" ) func TestDecodeAndEncode(t *testing.T) { - - // signer := "0x18eea3fbe5008d6f7a95d963a4be403e82d35758" signature := "0xba283b21f7168e53b082ad552d974591abe0f4db5b7032374abbcdcf09e0eadc2c0530ff4ac1d63e19c1ceca2d14b374c86b6c84f46bbd57747b48c21388c4e71c" eip712Components := new(EIP712Components) @@ -27,7 +26,7 @@ func TestDecodeAndEncode(t *testing.T) { } -func TestInvalidSignature(t *testing.T) { +func TestDecodeInvalid712Hex(t *testing.T) { invalidSignature := "0xinvalidsignature" eip712Components := new(EIP712Components) @@ -37,7 +36,7 @@ func TestInvalidSignature(t *testing.T) { } } -func TestEmptySignature(t *testing.T) { +func TestDecodeEmptySignature(t *testing.T) { emptySignature := "" eip712Components := new(EIP712Components) @@ -46,3 +45,19 @@ func TestEmptySignature(t *testing.T) { t.Fatal("Expected error for empty signature but got none") } } + +func TestDecodeInvalidLength(t *testing.T) { + // Create a hex-encoded signature with an invalid length (not 65 bytes) + invalidLengthSignature := "0xba283b21f7168e53b082ad552d974591abe0f4db5b7032374abbcdcf09e0eadc" // 64 characters (32 bytes) + eip712Components := new(EIP712Components) + + err := eip712Components.Decode(invalidLengthSignature) + if err == nil { + t.Fatal("Expected error for signature with invalid length but got none") + } + + expectedErrMsg := fmt.Sprintf("error decoding EIP-712 signature string: invalid length %d bytes (expected %d bytes)", 32, EIP712Length) + if err.Error() != expectedErrMsg { + t.Fatalf("Expected error message: '%s' but got: '%s'", expectedErrMsg, err.Error()) + } +} From a6cde7945cefe5700291dfb43b534c387ddc7f43 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Mon, 22 Jul 2024 20:26:57 -0700 Subject: [PATCH 26/29] Refactor Decode/Encode to fulfil TextMarshaler interface --- .../api/pdao/set-signalling-address.go | 15 +++++-- shared/eip712/eip712.go | 40 ++++++++----------- shared/eip712/eip712_test.go | 34 +++++++++++----- 3 files changed, 52 insertions(+), 37 deletions(-) diff --git a/rocketpool-daemon/api/pdao/set-signalling-address.go b/rocketpool-daemon/api/pdao/set-signalling-address.go index ea91a330c..f9d4634df 100644 --- a/rocketpool-daemon/api/pdao/set-signalling-address.go +++ b/rocketpool-daemon/api/pdao/set-signalling-address.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/gorilla/mux" batch "github.com/rocket-pool/batch-query" "github.com/rocket-pool/node-manager-core/api/server" @@ -92,6 +93,7 @@ func (c *protocolDaoSetSignallingAddressContext) GetState(mc *batch.MultiCaller) c.node.Exists, c.node.IsVotingInitialized, ) + // Check if the node already has a signer if c.registry != nil { c.registry.NodeToSigner(mc, &c.nodeToSigner, c.node.Address) @@ -100,8 +102,6 @@ func (c *protocolDaoSetSignallingAddressContext) GetState(mc *batch.MultiCaller) func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *types.TxInfoData, opts *bind.TransactOpts) (types.ResponseStatus, error) { - message := constructMessage(strings.ToLower(c.nodeAddress.Hex())) - if c.signallingAddress == c.nodeToSigner { return types.ResponseStatus_Error, fmt.Errorf("Signer address already in use") } @@ -110,17 +110,24 @@ func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *types.TxInfoD return types.ResponseStatus_Error, fmt.Errorf("Voting must be initialized to set a signalling address. Use 'rocketpool pdao initialize-voting' to initialize voting first") } + decodedSignature, err := hexutil.Decode(c.signature) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("Failed to decode hex string: %w", err) + } + eip712Components := new(eip712.EIP712Components) - err := eip712Components.Decode(c.signature) + err = eip712Components.UnmarshallText(decodedSignature) if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("Error decoding signature: %w", err) + return types.ResponseStatus_Error, fmt.Errorf("Error unmarshalling signature: %w", err) } + message := constructMessage(strings.ToLower(c.nodeAddress.Hex())) err = eip712Components.Validate(message, c.signallingAddress) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("Error validating signature: %w", err) } + // Get the tx data.TxInfo, err = c.registry.SetSigner(c.signallingAddress, opts, eip712Components.V, eip712Components.R, eip712Components.S) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("Error getting the TX info for SetSigner: %w", err) diff --git a/shared/eip712/eip712.go b/shared/eip712/eip712.go index 215491388..30ee0fc66 100644 --- a/shared/eip712/eip712.go +++ b/shared/eip712/eip712.go @@ -5,7 +5,6 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" ) @@ -18,45 +17,38 @@ type EIP712Components struct { const EIP712Length = 65 // Pretty print for EIP712Components -func (e EIP712Components) String() { +func (e *EIP712Components) Print() { fmt.Println("EIP712 Components:") fmt.Printf("R: %x\n", e.R) fmt.Printf("S: %x\n", e.S) fmt.Printf("V: %d\n", e.V) } -// Decode decodes a hex-encoded EIP-712 signature string, verifies the length -// and assigns the appropriate bytes to R/S/V -func (e *EIP712Components) Decode(inp string) error { - decodedBytes, err := hexutil.Decode(inp) - if err != nil { - return err - } - - if len(decodedBytes) != EIP712Length { - return fmt.Errorf("error decoding EIP-712 signature string: invalid length %d bytes (expected %d bytes)", len(decodedBytes), EIP712Length) +// UnmarshallText verifies the length of a decoded EIP-712 signature and assigns the appropriate bytes to R/S/V +func (e *EIP712Components) UnmarshallText(inp []byte) error { + if len(inp) != EIP712Length { + return fmt.Errorf("error decoding EIP-712 signature string: invalid length %d bytes (expected %d bytes)", len(inp), EIP712Length) } - copy(e.R[:], decodedBytes[0:32]) - copy(e.S[:], decodedBytes[32:64]) - e.V = decodedBytes[64] + copy(e.R[:], inp[0:32]) + copy(e.S[:], inp[32:64]) + e.V = inp[64] return nil } -// Encode initializes an empty byte slice, copies fields R/S/V into it, -// encodes the byte slice into a hex string then returns it -func (e *EIP712Components) Encode() string { - encodedBytes := make([]byte, EIP712Length) +// MarshallText initializes an empty byte slice, copies fields R/S/V into signatureBytes then returns it +func (e *EIP712Components) MarshallText() ([]byte, error) { + signatureBytes := make([]byte, EIP712Length) - copy(encodedBytes[0:32], e.R[:]) - copy(encodedBytes[32:64], e.S[:]) - encodedBytes[64] = e.V + copy(signatureBytes[0:32], e.R[:]) + copy(signatureBytes[32:64], e.S[:]) + signatureBytes[64] = e.V - return hexutil.Encode(encodedBytes) + return signatureBytes, nil } -// Validate recovers the address of a signer from a message and signature and +// Validate recovers the address of a signer from a message and signature then // compares the recovered signer address to the expected signer address func (e *EIP712Components) Validate(msg []byte, expectedSigner common.Address) error { diff --git a/shared/eip712/eip712_test.go b/shared/eip712/eip712_test.go index 653249e5e..8e5e578b3 100644 --- a/shared/eip712/eip712_test.go +++ b/shared/eip712/eip712_test.go @@ -3,6 +3,8 @@ package eip712 import ( "fmt" "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" ) func TestDecodeAndEncode(t *testing.T) { @@ -10,18 +12,26 @@ func TestDecodeAndEncode(t *testing.T) { eip712Components := new(EIP712Components) // Decode string - err := eip712Components.Decode(signature) + decoded, err := hexutil.Decode(signature) + if err != nil { + t.Fatalf("Failed to decode hex string: %v", err) + } + + err = eip712Components.UnmarshallText(decoded) if err != nil { t.Fatalf("Failed to decode signature: %v", err) } - eip712Components.String() + decoded, err = eip712Components.MarshallText() + if err != nil { + t.Fatalf("Failed to encode signature: %v", err) + } - // Encode eip712Components back to a string - encodedSig := eip712Components.Encode() + // Convert the encoded byte slice back to a hex string + encodedHexSig := hexutil.Encode(decoded) - if encodedSig != signature { - t.Fatalf("Expected %s but got %s", signature, encodedSig) + if encodedHexSig != signature { + t.Fatalf("Expected %s but got %s", signature, decoded) } } @@ -30,7 +40,7 @@ func TestDecodeInvalid712Hex(t *testing.T) { invalidSignature := "0xinvalidsignature" eip712Components := new(EIP712Components) - err := eip712Components.Decode(invalidSignature) + err := eip712Components.UnmarshallText([]byte(invalidSignature)) if err == nil { t.Fatal("Expected error for invalid signature but got none") } @@ -40,7 +50,7 @@ func TestDecodeEmptySignature(t *testing.T) { emptySignature := "" eip712Components := new(EIP712Components) - err := eip712Components.Decode(emptySignature) + err := eip712Components.UnmarshallText([]byte(emptySignature)) if err == nil { t.Fatal("Expected error for empty signature but got none") } @@ -51,7 +61,13 @@ func TestDecodeInvalidLength(t *testing.T) { invalidLengthSignature := "0xba283b21f7168e53b082ad552d974591abe0f4db5b7032374abbcdcf09e0eadc" // 64 characters (32 bytes) eip712Components := new(EIP712Components) - err := eip712Components.Decode(invalidLengthSignature) + // Decode string + decoded, err := hexutil.Decode(invalidLengthSignature) + if err != nil { + t.Fatalf("Failed to decode hex string: %v", err) + } + + err = eip712Components.UnmarshallText(decoded) if err == nil { t.Fatal("Expected error for signature with invalid length but got none") } From edb9fc3aaeaee6aba47e093fdf2f7bed2ff73189 Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Mon, 22 Jul 2024 22:58:28 -0700 Subject: [PATCH 27/29] Add String method and unit tests for Validate --- .../api/pdao/set-signalling-address.go | 7 +- shared/eip712/eip712.go | 29 ++++-- shared/eip712/eip712_test.go | 94 +++++++++++++++++-- 3 files changed, 111 insertions(+), 19 deletions(-) diff --git a/rocketpool-daemon/api/pdao/set-signalling-address.go b/rocketpool-daemon/api/pdao/set-signalling-address.go index f9d4634df..cb3b40442 100644 --- a/rocketpool-daemon/api/pdao/set-signalling-address.go +++ b/rocketpool-daemon/api/pdao/set-signalling-address.go @@ -116,12 +116,12 @@ func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *types.TxInfoD } eip712Components := new(eip712.EIP712Components) - err = eip712Components.UnmarshallText(decodedSignature) + err = eip712Components.UnmarshalText(decodedSignature) if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("Error unmarshalling signature: %w", err) + return types.ResponseStatus_Error, fmt.Errorf("Failed to unmarshal signature: %w", err) } - message := constructMessage(strings.ToLower(c.nodeAddress.Hex())) + message := constructMessage(c.nodeAddress.Hex()) err = eip712Components.Validate(message, c.signallingAddress) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("Error validating signature: %w", err) @@ -137,6 +137,7 @@ func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *types.TxInfoD } func constructMessage(nodeAddress string) []byte { + nodeAddress = strings.ToLower(nodeAddress) message := fmt.Sprintf("%s may delegate to me for Rocket Pool governance", nodeAddress) return []byte(message) } diff --git a/shared/eip712/eip712.go b/shared/eip712/eip712.go index 30ee0fc66..583a3e65d 100644 --- a/shared/eip712/eip712.go +++ b/shared/eip712/eip712.go @@ -24,8 +24,19 @@ func (e *EIP712Components) Print() { fmt.Printf("V: %d\n", e.V) } -// UnmarshallText verifies the length of a decoded EIP-712 signature and assigns the appropriate bytes to R/S/V -func (e *EIP712Components) UnmarshallText(inp []byte) error { +// String returns a hexadecimal string representation of EIP712Components +func (e *EIP712Components) String() string { + out, err := e.MarshalText() + if err != nil { + // MarshalText should never return an error + panic(err) + } + return fmt.Sprintf("%x", out) + +} + +// UnmarshalText verifies the length of a decoded EIP-712 signature and assigns the appropriate bytes to R/S/V +func (e *EIP712Components) UnmarshalText(inp []byte) error { if len(inp) != EIP712Length { return fmt.Errorf("error decoding EIP-712 signature string: invalid length %d bytes (expected %d bytes)", len(inp), EIP712Length) } @@ -37,8 +48,8 @@ func (e *EIP712Components) UnmarshallText(inp []byte) error { return nil } -// MarshallText initializes an empty byte slice, copies fields R/S/V into signatureBytes then returns it -func (e *EIP712Components) MarshallText() ([]byte, error) { +// MarshalText initializes an empty byte slice, copies fields R/S/V into signatureBytes then returns it +func (e *EIP712Components) MarshalText() ([]byte, error) { signatureBytes := make([]byte, EIP712Length) copy(signatureBytes[0:32], e.R[:]) @@ -72,12 +83,12 @@ func (e *EIP712Components) Validate(msg []byte, expectedSigner common.Address) e // Restore V to its original value sig[crypto.RecoveryIDOffset] += 27 - // Derive the address from the public key - recoveredAddr := crypto.PubkeyToAddress(*pubKey) + // Derive the signer address from the public key + recoveredSigner := crypto.PubkeyToAddress(*pubKey) - // Compare the recovered address with the expected address - if recoveredAddr != expectedSigner { - return fmt.Errorf("signature does not match the expected signer: got %s, expected %s", recoveredAddr.Hex(), expectedSigner.Hex()) + // Compare the recovered signer address with the expected address + if recoveredSigner != expectedSigner { + return fmt.Errorf("signature does not match the expected signer: got %s, expected %s", recoveredSigner.Hex(), expectedSigner.Hex()) } return nil diff --git a/shared/eip712/eip712_test.go b/shared/eip712/eip712_test.go index 8e5e578b3..04b7b1314 100644 --- a/shared/eip712/eip712_test.go +++ b/shared/eip712/eip712_test.go @@ -4,6 +4,7 @@ import ( "fmt" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ) @@ -17,14 +18,14 @@ func TestDecodeAndEncode(t *testing.T) { t.Fatalf("Failed to decode hex string: %v", err) } - err = eip712Components.UnmarshallText(decoded) + err = eip712Components.UnmarshalText(decoded) if err != nil { - t.Fatalf("Failed to decode signature: %v", err) + t.Fatalf("Failed to unmarshall signature: %v", err) } - decoded, err = eip712Components.MarshallText() + decoded, err = eip712Components.MarshalText() if err != nil { - t.Fatalf("Failed to encode signature: %v", err) + t.Fatalf("Failed to marshall signature: %v", err) } // Convert the encoded byte slice back to a hex string @@ -40,7 +41,7 @@ func TestDecodeInvalid712Hex(t *testing.T) { invalidSignature := "0xinvalidsignature" eip712Components := new(EIP712Components) - err := eip712Components.UnmarshallText([]byte(invalidSignature)) + err := eip712Components.UnmarshalText([]byte(invalidSignature)) if err == nil { t.Fatal("Expected error for invalid signature but got none") } @@ -50,7 +51,7 @@ func TestDecodeEmptySignature(t *testing.T) { emptySignature := "" eip712Components := new(EIP712Components) - err := eip712Components.UnmarshallText([]byte(emptySignature)) + err := eip712Components.UnmarshalText([]byte(emptySignature)) if err == nil { t.Fatal("Expected error for empty signature but got none") } @@ -67,7 +68,7 @@ func TestDecodeInvalidLength(t *testing.T) { t.Fatalf("Failed to decode hex string: %v", err) } - err = eip712Components.UnmarshallText(decoded) + err = eip712Components.UnmarshalText(decoded) if err == nil { t.Fatal("Expected error for signature with invalid length but got none") } @@ -77,3 +78,82 @@ func TestDecodeInvalidLength(t *testing.T) { t.Fatalf("Expected error message: '%s' but got: '%s'", expectedErrMsg, err.Error()) } } + +func TestValidateSuccess(t *testing.T) { + // Create a valid EIP-712 signature + signature := "0xba283b21f7168e53b082ad552d974591abe0f4db5b7032374abbcdcf09e0eadc2c0530ff4ac1d63e19c1ceca2d14b374c86b6c84f46bbd57747b48c21388c4e71c" + decoded, err := hexutil.Decode(signature) + if err != nil { + t.Fatalf("Failed to decode hex string: %v", err) + } + + eip712Components := new(EIP712Components) + err = eip712Components.UnmarshalText(decoded) + if err != nil { + t.Fatalf("Failed to unmarshal signature: %v", err) + } + + // Message to be signed + message := "0xe8325f5f4486c2ff2ac7b522fbc9eb249d46c936 may delegate to me for Rocket Pool governance" + msg := []byte(message) + + // Expected signer + expectedSigner := common.HexToAddress("0x18eea3fBe5008d6f7a95d963a4BE403E82d35758") + + // Validate + err = eip712Components.Validate(msg, expectedSigner) + if err != nil { + t.Fatalf("Validation failed: %v", err) + } +} + +func TestValidateInvalidSignature(t *testing.T) { + // Create an invalid EIP-712 signature + invalidSignature := "0xba283b21f7168e53b082ad552d974591abe0f4db5b7032374abbcdcf09e0eadc2c0530ff4ac1d63e19c1ceca2d14b374c86b6c84f46bbd57747b48c21388c4e71f" // Last byte is invalid + decoded, _ := hexutil.Decode(invalidSignature) + eip712Components := new(EIP712Components) + err := eip712Components.UnmarshalText(decoded) + if err != nil { + t.Fatalf("Failed to unmarshal signature: %v", err) + } + + // Message to be signed + msg := []byte("0xe8325f5f4486c2ff2ac7b522fbc9eb249d46c936 may delegate to me for Rocket Pool governance") + + // Some arbitrary expected signer + expectedSigner := common.HexToAddress("0x7f0bfc4a2d057dc75a7a2d3c9dc7eae2b3881e3e") + + // Validate + err = eip712Components.Validate(msg, expectedSigner) + if err == nil { + t.Fatal("Expected error for invalid signature but got none") + } +} + +func TestValidateSignerMismatch(t *testing.T) { + // Create a valid EIP-712 signature + signature := "0xba283b21f7168e53b082ad552d974591abe0f4db5b7032374abbcdcf09e0eadc2c0530ff4ac1d63e19c1ceca2d14b374c86b6c84f46bbd57747b48c21388c4e71c" + decoded, err := hexutil.Decode(signature) + if err != nil { + t.Fatalf("Failed to decode hex string: %v", err) + } + + eip712Components := new(EIP712Components) + err = eip712Components.UnmarshalText(decoded) + if err != nil { + t.Fatalf("Failed to unmarshal signature: %v", err) + } + + // Message to be signed + message := "0xe8325f5f4486c2ff2ac7b522fbc9eb249d46c936 may delegate to me for Rocket Pool governance" + msg := []byte(message) + + // Provide a mismatched expected signer + expectedSigner := common.HexToAddress("0x0000000000000000000000000000000000000000") + + // Validate + err = eip712Components.Validate(msg, expectedSigner) + if err == nil { + t.Fatalf("Expected validation to fail, but it succeeded") + } +} From 5895fce61ca0036ba5d4e7ca31b324cca79fb24c Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Mon, 22 Jul 2024 23:06:23 -0700 Subject: [PATCH 28/29] Fix test case names --- shared/eip712/eip712_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shared/eip712/eip712_test.go b/shared/eip712/eip712_test.go index 04b7b1314..fc2643873 100644 --- a/shared/eip712/eip712_test.go +++ b/shared/eip712/eip712_test.go @@ -8,7 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ) -func TestDecodeAndEncode(t *testing.T) { +func TestUnmarshalAndMarshal(t *testing.T) { signature := "0xba283b21f7168e53b082ad552d974591abe0f4db5b7032374abbcdcf09e0eadc2c0530ff4ac1d63e19c1ceca2d14b374c86b6c84f46bbd57747b48c21388c4e71c" eip712Components := new(EIP712Components) @@ -20,12 +20,12 @@ func TestDecodeAndEncode(t *testing.T) { err = eip712Components.UnmarshalText(decoded) if err != nil { - t.Fatalf("Failed to unmarshall signature: %v", err) + t.Fatalf("Failed to unmarshal signature: %v", err) } decoded, err = eip712Components.MarshalText() if err != nil { - t.Fatalf("Failed to marshall signature: %v", err) + t.Fatalf("Failed to marshal signature: %v", err) } // Convert the encoded byte slice back to a hex string @@ -37,7 +37,7 @@ func TestDecodeAndEncode(t *testing.T) { } -func TestDecodeInvalid712Hex(t *testing.T) { +func TestUnmarshalInvalid712Hex(t *testing.T) { invalidSignature := "0xinvalidsignature" eip712Components := new(EIP712Components) @@ -47,7 +47,7 @@ func TestDecodeInvalid712Hex(t *testing.T) { } } -func TestDecodeEmptySignature(t *testing.T) { +func TestUnmarshalEmptySignature(t *testing.T) { emptySignature := "" eip712Components := new(EIP712Components) @@ -57,7 +57,7 @@ func TestDecodeEmptySignature(t *testing.T) { } } -func TestDecodeInvalidLength(t *testing.T) { +func TestUnmarshalInvalidLength(t *testing.T) { // Create a hex-encoded signature with an invalid length (not 65 bytes) invalidLengthSignature := "0xba283b21f7168e53b082ad552d974591abe0f4db5b7032374abbcdcf09e0eadc" // 64 characters (32 bytes) eip712Components := new(EIP712Components) From b669d2e2086b955da43eda2b471e23dab8b140ef Mon Sep 17 00:00:00 2001 From: thomaspanf Date: Tue, 23 Jul 2024 14:10:27 -0700 Subject: [PATCH 29/29] Refactor UnmarshalText and MarshalText --- .../api/pdao/set-signalling-address.go | 8 +--- shared/eip712/eip712.go | 31 +++++++++---- shared/eip712/eip712_test.go | 46 ++++--------------- 3 files changed, 31 insertions(+), 54 deletions(-) diff --git a/rocketpool-daemon/api/pdao/set-signalling-address.go b/rocketpool-daemon/api/pdao/set-signalling-address.go index cb3b40442..17feeaa29 100644 --- a/rocketpool-daemon/api/pdao/set-signalling-address.go +++ b/rocketpool-daemon/api/pdao/set-signalling-address.go @@ -8,7 +8,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/gorilla/mux" batch "github.com/rocket-pool/batch-query" "github.com/rocket-pool/node-manager-core/api/server" @@ -110,13 +109,8 @@ func (c *protocolDaoSetSignallingAddressContext) PrepareData(data *types.TxInfoD return types.ResponseStatus_Error, fmt.Errorf("Voting must be initialized to set a signalling address. Use 'rocketpool pdao initialize-voting' to initialize voting first") } - decodedSignature, err := hexutil.Decode(c.signature) - if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("Failed to decode hex string: %w", err) - } - eip712Components := new(eip712.EIP712Components) - err = eip712Components.UnmarshalText(decodedSignature) + err := eip712Components.UnmarshalText([]byte(c.signature)) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("Failed to unmarshal signature: %w", err) } diff --git a/shared/eip712/eip712.go b/shared/eip712/eip712.go index 583a3e65d..5a86c1deb 100644 --- a/shared/eip712/eip712.go +++ b/shared/eip712/eip712.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" ) @@ -31,24 +32,32 @@ func (e *EIP712Components) String() string { // MarshalText should never return an error panic(err) } - return fmt.Sprintf("%x", out) - + return string(out) } -// UnmarshalText verifies the length of a decoded EIP-712 signature and assigns the appropriate bytes to R/S/V +// UnmarshalText expects an EIP-712 signature as a []byte, decodes the signature, verifies the length, +// then assigns the appropriate bytes to R/S/V func (e *EIP712Components) UnmarshalText(inp []byte) error { - if len(inp) != EIP712Length { - return fmt.Errorf("error decoding EIP-712 signature string: invalid length %d bytes (expected %d bytes)", len(inp), EIP712Length) + // Cast to string then decode + signatureString := string(inp) + decodedSignature, err := hexutil.Decode(signatureString) + if err != nil { + return fmt.Errorf("Failed to decode hex string: %w", err) } - copy(e.R[:], inp[0:32]) - copy(e.S[:], inp[32:64]) - e.V = inp[64] + if len(decodedSignature) != EIP712Length { + return fmt.Errorf("Failed to unmarshal EIP-712 signature string: invalid length %d bytes (expected %d bytes)", len(decodedSignature), EIP712Length) + } + + copy(e.R[:], decodedSignature[0:32]) + copy(e.S[:], decodedSignature[32:64]) + e.V = decodedSignature[64] return nil } -// MarshalText initializes an empty byte slice, copies fields R/S/V into signatureBytes then returns it +// MarshalText initializes an empty byte slice, copies fields R/S/V into signatureBytes, +// then returns the encoded signature as a []byte func (e *EIP712Components) MarshalText() ([]byte, error) { signatureBytes := make([]byte, EIP712Length) @@ -56,7 +65,9 @@ func (e *EIP712Components) MarshalText() ([]byte, error) { copy(signatureBytes[32:64], e.S[:]) signatureBytes[64] = e.V - return signatureBytes, nil + encodedSignature := hexutil.Encode(signatureBytes) + + return []byte(encodedSignature), nil } // Validate recovers the address of a signer from a message and signature then diff --git a/shared/eip712/eip712_test.go b/shared/eip712/eip712_test.go index fc2643873..55b77d069 100644 --- a/shared/eip712/eip712_test.go +++ b/shared/eip712/eip712_test.go @@ -5,36 +5,23 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" ) func TestUnmarshalAndMarshal(t *testing.T) { signature := "0xba283b21f7168e53b082ad552d974591abe0f4db5b7032374abbcdcf09e0eadc2c0530ff4ac1d63e19c1ceca2d14b374c86b6c84f46bbd57747b48c21388c4e71c" eip712Components := new(EIP712Components) - // Decode string - decoded, err := hexutil.Decode(signature) - if err != nil { - t.Fatalf("Failed to decode hex string: %v", err) - } - - err = eip712Components.UnmarshalText(decoded) + err := eip712Components.UnmarshalText([]byte(signature)) if err != nil { t.Fatalf("Failed to unmarshal signature: %v", err) } - decoded, err = eip712Components.MarshalText() - if err != nil { - t.Fatalf("Failed to marshal signature: %v", err) - } - - // Convert the encoded byte slice back to a hex string - encodedHexSig := hexutil.Encode(decoded) + // Convert the components back to a hex string + encodedHexSig := eip712Components.String() if encodedHexSig != signature { - t.Fatalf("Expected %s but got %s", signature, decoded) + t.Fatalf("Expected %s but got %s", signature, encodedHexSig) } - } func TestUnmarshalInvalid712Hex(t *testing.T) { @@ -62,18 +49,12 @@ func TestUnmarshalInvalidLength(t *testing.T) { invalidLengthSignature := "0xba283b21f7168e53b082ad552d974591abe0f4db5b7032374abbcdcf09e0eadc" // 64 characters (32 bytes) eip712Components := new(EIP712Components) - // Decode string - decoded, err := hexutil.Decode(invalidLengthSignature) - if err != nil { - t.Fatalf("Failed to decode hex string: %v", err) - } - - err = eip712Components.UnmarshalText(decoded) + err := eip712Components.UnmarshalText([]byte(invalidLengthSignature)) if err == nil { t.Fatal("Expected error for signature with invalid length but got none") } - expectedErrMsg := fmt.Sprintf("error decoding EIP-712 signature string: invalid length %d bytes (expected %d bytes)", 32, EIP712Length) + expectedErrMsg := fmt.Sprintf("Failed to unmarshal EIP-712 signature string: invalid length %d bytes (expected %d bytes)", 32, EIP712Length) if err.Error() != expectedErrMsg { t.Fatalf("Expected error message: '%s' but got: '%s'", expectedErrMsg, err.Error()) } @@ -82,13 +63,9 @@ func TestUnmarshalInvalidLength(t *testing.T) { func TestValidateSuccess(t *testing.T) { // Create a valid EIP-712 signature signature := "0xba283b21f7168e53b082ad552d974591abe0f4db5b7032374abbcdcf09e0eadc2c0530ff4ac1d63e19c1ceca2d14b374c86b6c84f46bbd57747b48c21388c4e71c" - decoded, err := hexutil.Decode(signature) - if err != nil { - t.Fatalf("Failed to decode hex string: %v", err) - } eip712Components := new(EIP712Components) - err = eip712Components.UnmarshalText(decoded) + err := eip712Components.UnmarshalText([]byte(signature)) if err != nil { t.Fatalf("Failed to unmarshal signature: %v", err) } @@ -110,9 +87,8 @@ func TestValidateSuccess(t *testing.T) { func TestValidateInvalidSignature(t *testing.T) { // Create an invalid EIP-712 signature invalidSignature := "0xba283b21f7168e53b082ad552d974591abe0f4db5b7032374abbcdcf09e0eadc2c0530ff4ac1d63e19c1ceca2d14b374c86b6c84f46bbd57747b48c21388c4e71f" // Last byte is invalid - decoded, _ := hexutil.Decode(invalidSignature) eip712Components := new(EIP712Components) - err := eip712Components.UnmarshalText(decoded) + err := eip712Components.UnmarshalText([]byte(invalidSignature)) if err != nil { t.Fatalf("Failed to unmarshal signature: %v", err) } @@ -133,13 +109,9 @@ func TestValidateInvalidSignature(t *testing.T) { func TestValidateSignerMismatch(t *testing.T) { // Create a valid EIP-712 signature signature := "0xba283b21f7168e53b082ad552d974591abe0f4db5b7032374abbcdcf09e0eadc2c0530ff4ac1d63e19c1ceca2d14b374c86b6c84f46bbd57747b48c21388c4e71c" - decoded, err := hexutil.Decode(signature) - if err != nil { - t.Fatalf("Failed to decode hex string: %v", err) - } eip712Components := new(EIP712Components) - err = eip712Components.UnmarshalText(decoded) + err := eip712Components.UnmarshalText([]byte(signature)) if err != nil { t.Fatalf("Failed to unmarshal signature: %v", err) }