diff --git a/app/node/build.go b/app/node/build.go index 2cb9977c5..eb049a854 100644 --- a/app/node/build.go +++ b/app/node/build.go @@ -509,12 +509,6 @@ func buildConsensusEngine(_ context.Context, d *coreDependencies, db *pg.DB, } func buildErc20RWignerMgr(d *coreDependencies) *signersvc.ServiceMgr { - cfg := d.cfg.Erc20Bridge - - if err := cfg.Validate(); err != nil { - failBuild(err, "invalid erc20 bridge config") - } - // create shared state stateFile := signersvc.StateFilePath(d.rootDir) @@ -533,7 +527,7 @@ func buildErc20RWignerMgr(d *coreDependencies) *signersvc.ServiceMgr { rpcUrl := "http://" + d.cfg.RPC.ListenAddress - mgr, err := signersvc.NewServiceMgr(rpcUrl, cfg, state, d.logger.New("EVMRW")) + mgr, err := signersvc.NewServiceMgr(rpcUrl, d.cfg.Erc20Bridge, state, d.logger.New("EVMRW")) if err != nil { failBuild(err, "Failed to create erc20 bridge signer service manager") } diff --git a/config/config.go b/config/config.go index 028d3c011..d77fd06fc 100644 --- a/config/config.go +++ b/config/config.go @@ -13,12 +13,13 @@ import ( "strings" "time" + "github.com/pelletier/go-toml/v2" + "github.com/kwilteam/kwil-db/core/crypto" "github.com/kwilteam/kwil-db/core/crypto/auth" "github.com/kwilteam/kwil-db/core/log" "github.com/kwilteam/kwil-db/core/types" - - "github.com/pelletier/go-toml/v2" + "github.com/kwilteam/kwil-db/node/exts/evm-sync/chains" ) var ( @@ -450,15 +451,24 @@ type Checkpoint struct { } type ERC20BridgeConfig struct { - RPC map[string]string `toml:"rpc" comment:"evm RPC; format: chain_name='rpc_url'"` - BlockSyncChuckSize map[string]string `toml:"block_sync_chuck_size" comment:"block sync chunk size; format: chain_name='chunk_size'"` - Signer map[string]string `toml:"signer" comment:"signer service configuration; format: chain_name='target:file_path_to_private_key'"` -} + RPC map[string]string `toml:"rpc" comment:"evm websocket RPC; format: chain_name='rpc_url'"` + BlockSyncChuckSize map[string]string `toml:"block_sync_chuck_size" comment:"rpc option block sync chunk size; format: chain_name='chunk_size'"` + Signer map[string]string `toml:"signer" comment:"signer service configuration; format: target='chain_name:file_path_to_private_key'"` +} + +// ValidateRpc validates the bridge rpc config, other validations will be performed +// when correspond components derive config from it. +// BlockSyncChuckSize config will be validated by evm-sync listener. +// Signer config will be validated by erc20 signerSvc. +func (cfg ERC20BridgeConfig) ValidateRpc() error { + for chain, rpc := range cfg.RPC { + if err := chains.Chain(chain).Valid(); err != nil { + return fmt.Errorf("erc20_bridge.rpc: %s", chain) + } -func (cfg ERC20BridgeConfig) Validate() error { - for chain := range cfg.Signer { - if _, ok := cfg.RPC[chain]; !ok { - return fmt.Errorf("signer service: chain '%s' is not in rpc", chain) + // enforce websocket + if !strings.HasPrefix(rpc, "wss://") && !strings.HasPrefix(rpc, "ws://") { + return fmt.Errorf("erc20_bridge.rpc: must start with wss:// or ws://") } } diff --git a/node/exts/erc20-bridge/signersvc/signer.go b/node/exts/erc20-bridge/signersvc/signer.go index 763d987cd..b1ee8410f 100644 --- a/node/exts/erc20-bridge/signersvc/signer.go +++ b/node/exts/erc20-bridge/signersvc/signer.go @@ -317,53 +317,76 @@ func (s *rewardSigner) sync(ctx context.Context) { s.lastVoteBlock = finalizedEpoch.EndHeight // update after all operations succeed } -// ServiceMgr manages multiple rewardSigner instances running in parallel. -type ServiceMgr struct { - syncInterval time.Duration - signers []*rewardSigner - logger log.Logger +type signerConfig struct { + target string // erc20 bridge target name + chainRPC string + privateKeyPath string // file path to private key } -func NewServiceMgr( - kwilRpc string, - cfg config.ERC20BridgeConfig, - state *State, - logger log.Logger) (*ServiceMgr, error) { +// GetSignerCfgs verifies config and returns a list of config for erc20 signerSvc. +func getSignerCfgs(cfg config.ERC20BridgeConfig) ([]signerConfig, error) { + if err := cfg.ValidateRpc(); err != nil { + return nil, err + } signerCfgDelimiter := ":" - var signers []*rewardSigner - for chain, value := range cfg.Signer { - chainRpc, ok := cfg.RPC[chain] - if !ok { - return nil, fmt.Errorf("chain %s not found in rpc config", chain) - } - - // we need websocket endpoint - if !strings.HasPrefix(chainRpc, "wss://") && !strings.HasPrefix(chainRpc, "ws://") { - return nil, fmt.Errorf("chain %s rpc must start with wss:// or ws://", chain) - } + var signerCfg []signerConfig + for target, value := range cfg.Signer { if !strings.Contains(value, signerCfgDelimiter) { return nil, fmt.Errorf("invalid signer config: %s", value) } segs := strings.SplitN(value, signerCfgDelimiter, 2) - - target := segs[0] + chain := segs[0] pkPath := segs[1] + chainRpc, ok := cfg.RPC[chain] + if !ok { + return nil, fmt.Errorf("chain '%s' not found in erc20_bridge.rpc config", chain) + } + if !ethCommon.FileExist(pkPath) { return nil, fmt.Errorf("private key file %s not found", pkPath) } - pkBytes, err := os.ReadFile(pkPath) + signerCfg = append(signerCfg, signerConfig{ + target: target, + chainRPC: chainRpc, + privateKeyPath: pkPath, + }) + } + + return signerCfg, nil +} + +// ServiceMgr manages multiple rewardSigner instances running in parallel. +type ServiceMgr struct { + syncInterval time.Duration + signers []*rewardSigner + logger log.Logger +} + +func NewServiceMgr( + kwilRpc string, + cfg config.ERC20BridgeConfig, + state *State, + logger log.Logger) (*ServiceMgr, error) { + signerCfgs, err := getSignerCfgs(cfg) + if err != nil { + return nil, fmt.Errorf("get erc20 bridge signer config failed: %w", err) + } + + var signers []*rewardSigner + for _, cfg := range signerCfgs { + pkBytes, err := os.ReadFile(cfg.privateKeyPath) if err != nil { - return nil, fmt.Errorf("read private key file %s failed: %w", pkPath, err) + return nil, fmt.Errorf("read private key file %s failed: %w", cfg.privateKeyPath, err) } - svc, err := newRewardSigner(kwilRpc, target, chainRpc, strings.TrimSpace(string(pkBytes)), - state, logger.New("EVMRW."+target)) + svc, err := newRewardSigner(kwilRpc, cfg.target, cfg.chainRPC, strings.TrimSpace(string(pkBytes)), + state, logger.New("EVMRW."+cfg.target)) if err != nil { return nil, fmt.Errorf("create erc20 bridge signer service failed: %w", err) } diff --git a/node/exts/evm-sync/listener.go b/node/exts/evm-sync/listener.go index cace115c6..4e6e4b9ef 100644 --- a/node/exts/evm-sync/listener.go +++ b/node/exts/evm-sync/listener.go @@ -9,7 +9,6 @@ import ( "io" "sort" "strconv" - "strings" "time" ethcommon "github.com/ethereum/go-ethereum/common" @@ -107,6 +106,11 @@ func (c *syncConfig) load(m map[string]string) error { // getChainConf gets the chain config from the node's local configuration. func getChainConf(cfg config.ERC20BridgeConfig, chain chains.Chain) (*chainConfig, error) { + err := cfg.ValidateRpc() + if err != nil { + return nil, err + } + var ok bool var provider string var syncChunk string @@ -114,12 +118,7 @@ func getChainConf(cfg config.ERC20BridgeConfig, chain chains.Chain) (*chainConfi case chains.Ethereum, chains.Sepolia: provider, ok = cfg.RPC[chain.String()] if !ok { - return nil, errors.New("local configuration does not have an ethereum_sync config") - } - - // we need websocket endpoint - if !strings.HasPrefix(provider, "wss://") && !strings.HasPrefix(provider, "ws://") { - return nil, fmt.Errorf("chain %s rpc must start with wss:// or ws://", chain) + return nil, fmt.Errorf("local configuration does not have an '%s' config", chain.String()) } syncChunk, ok = cfg.BlockSyncChuckSize[chains.Ethereum.String()]