diff --git a/consensus/parlia/stakehub.go b/consensus/parlia/stakehub.go index 8b9694d61f..d605ac07d7 100644 --- a/consensus/parlia/stakehub.go +++ b/consensus/parlia/stakehub.go @@ -222,3 +222,60 @@ func (p *Parlia) GetNodeIDsMap() (map[common.Address][]enode.ID, error) { return nodeIDsMap, nil } + +// RemoveNodeIDs creates a signed transaction to remove node IDs from the StakeHub contract +func (p *Parlia) RemoveNodeIDs(nodeIDs []enode.ID, nonce uint64) (*types.Transaction, error) { + log.Debug("Removing node IDs", "count", len(nodeIDs), "nonce", nonce) + + p.lock.RLock() + signTxFn := p.signTxFn + val := p.val + p.lock.RUnlock() + + if signTxFn == nil { + log.Error("Signing function not set") + return nil, fmt.Errorf("signing function not set, call Authorize first") + } + + // Create the call data for removeNodeIDs + data, err := p.stakeHubABI.Pack("removeNodeIDs", nodeIDs) + if err != nil { + log.Error("Failed to pack removeNodeIDs", "error", err) + return nil, fmt.Errorf("failed to pack removeNodeIDs: %v", err) + } + + to := common.HexToAddress(systemcontracts.StakeHubContract) + hexData := hexutil.Bytes(data) + hexNonce := hexutil.Uint64(nonce) + gas, err := p.ethAPI.EstimateGas(context.Background(), ethapi.TransactionArgs{ + From: &val, + To: &to, + Nonce: &hexNonce, + Data: &hexData, + }, nil, nil, nil) + if err != nil { + log.Error("Failed to estimate gas", "error", err) + return nil, fmt.Errorf("failed to estimate gas: %v", err) + } + + // Create the transaction + tx := types.NewTransaction( + nonce, + common.HexToAddress(systemcontracts.StakeHubContract), + common.Big0, + uint64(gas), + big.NewInt(1000000000), + data, + ) + + // Sign the transaction with the node's private key + log.Debug("Signing transaction", "validator", val) + signedTx, err := signTxFn(accounts.Account{Address: val}, tx, p.chainConfig.ChainID) + if err != nil { + log.Error("Failed to sign transaction", "error", err) + return nil, fmt.Errorf("failed to sign transaction: %v", err) + } + + log.Debug("Successfully created signed transaction", "hash", signedTx.Hash()) + return signedTx, nil +} diff --git a/eth/backend.go b/eth/backend.go index d608c2df2b..bfae4a71d9 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -544,61 +544,127 @@ func (s *Ethereum) waitForSyncAndMaxwell(parlia *parlia.Parlia) { continue } log.Info("Node is synced and Maxwell fork is active, proceeding with node ID registration") - err := s.registerNodeID(parlia) + err := s.updateNodeID(parlia) if err == nil { return } retryCount++ if retryCount > 3 { - log.Error("Failed to register node ID exceed max retry count", "retryCount", retryCount, "err", err) + log.Error("Failed to update node ID exceed max retry count", "retryCount", retryCount, "err", err) return } } } } -// registerNodeID registers the node ID with the StakeHub contract -func (s *Ethereum) registerNodeID(parlia *parlia.Parlia) error { - // Check if node ID is already registered - nodeIDs, err := parlia.GetNodeIDs() +// updateNodeID registers the node ID with the StakeHub contract +func (s *Ethereum) updateNodeID(parlia *parlia.Parlia) error { + nonce, err := s.APIBackend.GetPoolNonce(context.Background(), s.etherbase) if err != nil { - log.Error("Failed to get registered node IDs", "err", err) + return fmt.Errorf("failed to get nonce: %v", err) + } + + // Handle removals first + if err := s.handleRemovals(parlia, nonce); err != nil { return err } + nonce++ + + // Handle additions + return s.handleAdditions(parlia, nonce) +} + +func (s *Ethereum) handleRemovals(parlia *parlia.Parlia, nonce uint64) error { + if len(s.config.EVNNodeIDsToRemove) == 0 { + return nil + } - // Check which node IDs need to be registered - nodeIDsToAdd := make([]enode.ID, 0) - for _, idToRegister := range s.config.ValidatorNodeIDsToAdd { - isRegistered := false - for _, id := range nodeIDs { - if id == idToRegister { - isRegistered = true - break + // Handle wildcard removal + if len(s.config.EVNNodeIDsToRemove) == 1 { + var zeroID enode.ID // This will be all zeros + if s.config.EVNNodeIDsToRemove[0] == zeroID { + trx, err := parlia.RemoveNodeIDs([]enode.ID{}, nonce) + if err != nil { + return fmt.Errorf("failed to create node ID removal transaction: %v", err) } - } - if !isRegistered { - nodeIDsToAdd = append(nodeIDsToAdd, idToRegister) + if err := s.txPool.Add([]*types.Transaction{trx}, false); err != nil { + return fmt.Errorf("failed to add node ID removal transaction to pool: %v", err) + } + log.Info("Submitted node ID removal transaction for all node IDs") + return nil } } - if len(nodeIDsToAdd) > 0 { - // Get the current nonce for the validator address - nonce, err := s.APIBackend.GetPoolNonce(context.Background(), s.etherbase) - if err != nil { - return fmt.Errorf("failed to get nonce: %v", err) - } + // Create a set of node IDs to add for quick lookup + addSet := make(map[enode.ID]struct{}, len(s.config.EVNNodeIDsToAdd)) + for _, id := range s.config.EVNNodeIDsToAdd { + addSet[id] = struct{}{} + } - trx, err := parlia.AddNodeIDs(nodeIDsToAdd, nonce) - if err != nil { - return fmt.Errorf("failed to create node ID registration transaction: %v", err) + // Filter out node IDs that are in the add set + nodeIDsToRemove := make([]enode.ID, 0, len(s.config.EVNNodeIDsToRemove)) + for _, id := range s.config.EVNNodeIDsToRemove { + if _, exists := addSet[id]; !exists { + nodeIDsToRemove = append(nodeIDsToRemove, id) + } else { + log.Debug("Skipping node ID removal", "id", id, "reason", "also in EVNNodeIDsToAdd") } - if err := s.txPool.Add([]*types.Transaction{trx}, false); err != nil { - return fmt.Errorf("failed to add node ID registration transaction to pool: %v", err) + } + + if len(nodeIDsToRemove) == 0 { + return nil + } + + trx, err := parlia.RemoveNodeIDs(nodeIDsToRemove, nonce) + if err != nil { + return fmt.Errorf("failed to create node ID removal transaction: %v", err) + } + if err := s.txPool.Add([]*types.Transaction{trx}, false); err != nil { + return fmt.Errorf("failed to add node ID removal transaction to pool: %v", err) + } + log.Info("Submitted node ID removal transaction", "nodeIDs", nodeIDsToRemove) + return nil +} + +func (s *Ethereum) handleAdditions(parlia *parlia.Parlia, nonce uint64) error { + if len(s.config.EVNNodeIDsToAdd) == 0 { + return nil + } + + // Get currently registered node IDs + registeredIDs, err := parlia.GetNodeIDs() + if err != nil { + log.Error("Failed to get registered node IDs", "err", err) + return err + } + + // Create a set of registered IDs for quick lookup + registeredSet := make(map[enode.ID]struct{}, len(registeredIDs)) + for _, id := range registeredIDs { + registeredSet[id] = struct{}{} + } + + // Filter out already registered IDs in a single pass + nodeIDsToAdd := make([]enode.ID, 0, len(s.config.EVNNodeIDsToAdd)) + for _, id := range s.config.EVNNodeIDsToAdd { + if _, exists := registeredSet[id]; !exists { + nodeIDsToAdd = append(nodeIDsToAdd, id) } - log.Info("Submitted node ID registration transaction", "nodeIDs", nodeIDsToAdd) - } else { - log.Info("All node IDs already registered", "nodeIDs", s.config.ValidatorNodeIDsToAdd) } + + if len(nodeIDsToAdd) == 0 { + log.Info("No new node IDs to register after deduplication") + return nil + } + + trx, err := parlia.AddNodeIDs(nodeIDsToAdd, nonce) + if err != nil { + return fmt.Errorf("failed to create node ID registration transaction: %v", err) + } + if err := s.txPool.Add([]*types.Transaction{trx}, false); err != nil { + return fmt.Errorf("failed to add node ID registration transaction to pool: %v", err) + } + log.Info("Submitted node ID registration transaction", "nodeIDs", nodeIDsToAdd) return nil } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index e383da3f60..1ec64a122d 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -94,7 +94,8 @@ type Config struct { // transactions) or is continuously under high pressure (e.g., mempool is always full), then you can consider // to turn it on. DisablePeerTxBroadcast bool - ValidatorNodeIDsToAdd []enode.ID + EVNNodeIDsToAdd []enode.ID + EVNNodeIDsToRemove []enode.ID // This can be set to list of enrtree:// URLs which will be queried for // nodes to connect to. EthDiscoveryURLs []string diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index d8ec782a22..8089b03538 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -21,7 +21,8 @@ func (c Config) MarshalTOML() (interface{}, error) { NetworkId uint64 SyncMode SyncMode DisablePeerTxBroadcast bool - ValidatorNodeIDsToAdd []enode.ID + EVNNodeIDsToAdd []enode.ID + EVNNodeIDsToRemove []enode.ID EthDiscoveryURLs []string SnapDiscoveryURLs []string TrustDiscoveryURLs []string @@ -76,7 +77,8 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.NetworkId = c.NetworkId enc.SyncMode = c.SyncMode enc.DisablePeerTxBroadcast = c.DisablePeerTxBroadcast - enc.ValidatorNodeIDsToAdd = c.ValidatorNodeIDsToAdd + enc.EVNNodeIDsToAdd = c.EVNNodeIDsToAdd + enc.EVNNodeIDsToRemove = c.EVNNodeIDsToRemove enc.EthDiscoveryURLs = c.EthDiscoveryURLs enc.SnapDiscoveryURLs = c.SnapDiscoveryURLs enc.TrustDiscoveryURLs = c.TrustDiscoveryURLs @@ -135,7 +137,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { NetworkId *uint64 SyncMode *SyncMode DisablePeerTxBroadcast *bool - ValidatorNodeIDsToAdd []enode.ID + EVNNodeIDsToAdd []enode.ID + EVNNodeIDsToRemove []enode.ID EthDiscoveryURLs []string SnapDiscoveryURLs []string TrustDiscoveryURLs []string @@ -201,8 +204,11 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.DisablePeerTxBroadcast != nil { c.DisablePeerTxBroadcast = *dec.DisablePeerTxBroadcast } - if dec.ValidatorNodeIDsToAdd != nil { - c.ValidatorNodeIDsToAdd = dec.ValidatorNodeIDsToAdd + if dec.EVNNodeIDsToAdd != nil { + c.EVNNodeIDsToAdd = dec.EVNNodeIDsToAdd + } + if dec.EVNNodeIDsToRemove != nil { + c.EVNNodeIDsToRemove = dec.EVNNodeIDsToRemove } if dec.EthDiscoveryURLs != nil { c.EthDiscoveryURLs = dec.EthDiscoveryURLs