diff --git a/changelog.md b/changelog.md index 726b94d57d..c27d675a10 100644 --- a/changelog.md +++ b/changelog.md @@ -51,6 +51,7 @@ * [2317](https://github.com/zeta-chain/node/pull/2317) - add ValidateOutbound method for cctx orchestrator * [2340](https://github.com/zeta-chain/node/pull/2340) - add ValidateInbound method for cctx orchestrator * [2344](https://github.com/zeta-chain/node/pull/2344) - group common data of EVM/Bitcoin signer and observer using base structs +* [2357](https://github.com/zeta-chain/node/pull/2357) - integrate base Signer structure into EVM/Bitcoin Signer ### Tests @@ -76,6 +77,7 @@ * [2243](https://github.com/zeta-chain/node/pull/2243) - fix incorrect bitcoin outbound height in the CCTX outbound parameter * [2256](https://github.com/zeta-chain/node/pull/2256) - fix rate limiter falsely included reverted non-withdraw cctxs * [2327](https://github.com/zeta-chain/node/pull/2327) - partially cherry picked the fix to Bitcoin outbound dust amount +* [2362](https://github.com/zeta-chain/node/pull/2362) - set 1000 satoshis as minimum BTC amount that can be withdrawn from zEVM ### CI diff --git a/cmd/zetaclientd/debug.go b/cmd/zetaclientd/debug.go index 881997d0ec..4e10b24c25 100644 --- a/cmd/zetaclientd/debug.go +++ b/cmd/zetaclientd/debug.go @@ -3,7 +3,6 @@ package main import ( "context" "fmt" - "io" "strconv" "strings" "sync" @@ -13,7 +12,6 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/onrik/ethrpc" - "github.com/rs/zerolog" "github.com/spf13/cobra" "github.com/zeta-chain/zetacore/pkg/chains" @@ -61,7 +59,6 @@ func DebugCmd() *cobra.Command { } inboundHash := args[0] var ballotIdentifier string - chainLogger := zerolog.New(io.Discard).Level(zerolog.Disabled) // create a new zetacore client client, err := zetacore.NewClient( @@ -93,7 +90,6 @@ func DebugCmd() *cobra.Command { Mu: &sync.Mutex{}, } evmObserver.WithZetacoreClient(client) - evmObserver.WithLogger(chainLogger) var ethRPC *ethrpc.EthRPC var client *ethclient.Client coinType := coin.CoinType_Cmd @@ -172,7 +168,6 @@ func DebugCmd() *cobra.Command { Mu: &sync.Mutex{}, } btcObserver.WithZetacoreClient(client) - btcObserver.WithLogger(chainLogger) btcObserver.WithChain(*chains.GetChainFromChainID(chainID)) connCfg := &rpcclient.ConnConfig{ Host: cfg.BitcoinConfig.RPCHost, diff --git a/cmd/zetaclientd/utils.go b/cmd/zetaclientd/utils.go index 521c6dc858..64db1c3efa 100644 --- a/cmd/zetaclientd/utils.go +++ b/cmd/zetaclientd/utils.go @@ -54,13 +54,14 @@ func CreateZetacoreClient( return client, nil } +// CreateSignerMap creates a map of ChainSigners for all chains in the config func CreateSignerMap( appContext *context.AppContext, tss interfaces.TSSSigner, logger base.Logger, ts *metrics.TelemetryServer, ) (map[int64]interfaces.ChainSigner, error) { - coreContext := appContext.ZetacoreContext() + zetacoreContext := appContext.ZetacoreContext() signerMap := make(map[int64]interfaces.ChainSigner) // EVM signers @@ -68,7 +69,7 @@ func CreateSignerMap( if evmConfig.Chain.IsZetaChain() { continue } - evmChainParams, found := coreContext.GetEVMChainParams(evmConfig.Chain.ChainId) + evmChainParams, found := zetacoreContext.GetEVMChainParams(evmConfig.Chain.ChainId) if !found { logger.Std.Error().Msgf("ChainParam not found for chain %s", evmConfig.Chain.String()) continue @@ -77,15 +78,15 @@ func CreateSignerMap( erc20CustodyAddress := ethcommon.HexToAddress(evmChainParams.Erc20CustodyContractAddress) signer, err := evmsigner.NewSigner( evmConfig.Chain, - evmConfig.Endpoint, + zetacoreContext, tss, + ts, + logger, + evmConfig.Endpoint, config.GetConnectorABI(), config.GetERC20CustodyABI(), mpiAddress, - erc20CustodyAddress, - coreContext, - logger, - ts) + erc20CustodyAddress) if err != nil { logger.Std.Error().Err(err).Msgf("NewEVMSigner error for chain %s", evmConfig.Chain.String()) continue @@ -95,7 +96,7 @@ func CreateSignerMap( // BTC signer btcChain, btcConfig, enabled := appContext.GetBTCChainAndConfig() if enabled { - signer, err := btcsigner.NewSigner(btcConfig, tss, logger, ts, coreContext) + signer, err := btcsigner.NewSigner(btcChain, zetacoreContext, tss, ts, logger, btcConfig) if err != nil { logger.Std.Error().Err(err).Msgf("NewBTCSigner error for chain %s", btcChain.String()) } else { diff --git a/testutil/sample/os.go b/testutil/sample/os.go new file mode 100644 index 0000000000..b6519adf69 --- /dev/null +++ b/testutil/sample/os.go @@ -0,0 +1,15 @@ +package sample + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +// create a temporary directory for testing +func CreateTempDir(t *testing.T) string { + tempPath, err := os.MkdirTemp("", "tempdir-") + require.NoError(t, err) + return tempPath +} diff --git a/x/crosschain/keeper/evm_hooks.go b/x/crosschain/keeper/evm_hooks.go index dbc2d2dbb1..2fc6310800 100644 --- a/x/crosschain/keeper/evm_hooks.go +++ b/x/crosschain/keeper/evm_hooks.go @@ -21,6 +21,7 @@ import ( "github.com/zeta-chain/zetacore/cmd/zetacored/config" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/pkg/constant" "github.com/zeta-chain/zetacore/x/crosschain/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -286,15 +287,20 @@ func ValidateZrc20WithdrawEvent(event *zrc20.ZRC20Withdrawal, chainID int64) err // The event was parsed; that means the user has deposited tokens to the contract. if chains.IsBitcoinChain(chainID) { - if event.Value.Cmp(big.NewInt(0)) <= 0 { - return fmt.Errorf("ParseZRC20WithdrawalEvent: invalid amount %s", event.Value.String()) + if event.Value.Cmp(big.NewInt(constant.BTCWithdrawalDustAmount)) < 0 { + return errorsmod.Wrapf( + types.ErrInvalidWithdrawalAmount, + "withdraw amount %s is less than minimum amount %d", + event.Value.String(), + constant.BTCWithdrawalDustAmount, + ) } addr, err := chains.DecodeBtcAddress(string(event.To), chainID) if err != nil { - return fmt.Errorf("ParseZRC20WithdrawalEvent: invalid address %s: %s", event.To, err) + return errorsmod.Wrapf(types.ErrInvalidAddress, "invalid address %s", string(event.To)) } if !chains.IsBtcAddressSupported(addr) { - return fmt.Errorf("ParseZRC20WithdrawalEvent: unsupported address %s", string(event.To)) + return errorsmod.Wrapf(types.ErrInvalidAddress, "unsupported address %s", string(event.To)) } } return nil diff --git a/x/crosschain/keeper/evm_hooks_test.go b/x/crosschain/keeper/evm_hooks_test.go index bc67f34a99..20b1c87e47 100644 --- a/x/crosschain/keeper/evm_hooks_test.go +++ b/x/crosschain/keeper/evm_hooks_test.go @@ -15,6 +15,7 @@ import ( "github.com/zeta-chain/zetacore/cmd/zetacored/config" "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/constant" keepertest "github.com/zeta-chain/zetacore/testutil/keeper" "github.com/zeta-chain/zetacore/testutil/sample" crosschainkeeper "github.com/zeta-chain/zetacore/x/crosschain/keeper" @@ -164,14 +165,21 @@ func TestValidateZrc20WithdrawEvent(t *testing.T) { require.NoError(t, err) }) - t.Run("unable to validate a event with an invalid amount", func(t *testing.T) { + t.Run("unable to validate a btc withdrawal event with an invalid amount", func(t *testing.T) { btcMainNetWithdrawalEvent, err := crosschainkeeper.ParseZRC20WithdrawalEvent( *sample.GetValidZRC20WithdrawToBTC(t).Logs[3], ) require.NoError(t, err) - btcMainNetWithdrawalEvent.Value = big.NewInt(0) + + // 1000 satoshis is the minimum amount that can be withdrawn + btcMainNetWithdrawalEvent.Value = big.NewInt(constant.BTCWithdrawalDustAmount) err = crosschainkeeper.ValidateZrc20WithdrawEvent(btcMainNetWithdrawalEvent, chains.BitcoinMainnet.ChainId) - require.ErrorContains(t, err, "ParseZRC20WithdrawalEvent: invalid amount") + require.NoError(t, err) + + // 999 satoshis cannot be withdrawn + btcMainNetWithdrawalEvent.Value = big.NewInt(constant.BTCWithdrawalDustAmount - 1) + err = crosschainkeeper.ValidateZrc20WithdrawEvent(btcMainNetWithdrawalEvent, chains.BitcoinMainnet.ChainId) + require.ErrorContains(t, err, "less than minimum amount") }) t.Run("unable to validate a event with an invalid chain ID", func(t *testing.T) { @@ -822,7 +830,7 @@ func TestKeeper_ProcessLogs(t *testing.T) { } err := k.ProcessLogs(ctx, block.Logs, sample.EthAddress(), "") - require.ErrorContains(t, err, "ParseZRC20WithdrawalEvent: invalid address") + require.ErrorContains(t, err, "invalid address") cctxList := k.GetAllCrossChainTx(ctx) require.Len(t, cctxList, 0) }) diff --git a/x/crosschain/types/errors.go b/x/crosschain/types/errors.go index 232bf229db..6f430e5e1a 100644 --- a/x/crosschain/types/errors.go +++ b/x/crosschain/types/errors.go @@ -49,4 +49,5 @@ var ( ErrInvalidRateLimiterFlags = errorsmod.Register(ModuleName, 1152, "invalid rate limiter flags") ErrMaxTxOutTrackerHashesReached = errorsmod.Register(ModuleName, 1153, "max tx out tracker hashes reached") ErrInitiatitingOutbound = errorsmod.Register(ModuleName, 1154, "cannot initiate outbound") + ErrInvalidWithdrawalAmount = errorsmod.Register(ModuleName, 1155, "invalid withdrawal amount") ) diff --git a/zetaclient/chains/base/logger_test.go b/zetaclient/chains/base/logger_test.go index 07c2859b0e..07c0941f5a 100644 --- a/zetaclient/chains/base/logger_test.go +++ b/zetaclient/chains/base/logger_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/testutil/sample" "github.com/zeta-chain/zetacore/zetaclient/chains/base" "github.com/zeta-chain/zetacore/zetaclient/config" ) @@ -23,7 +24,7 @@ func TestInitLogger(t *testing.T) { LogFormat: "json", LogLevel: 1, // zerolog.InfoLevel, ComplianceConfig: config.ComplianceConfig{ - LogPath: createTempDir(t), + LogPath: sample.CreateTempDir(t), }, }, fail: false, @@ -34,7 +35,7 @@ func TestInitLogger(t *testing.T) { LogFormat: "text", LogLevel: 2, // zerolog.WarnLevel, ComplianceConfig: config.ComplianceConfig{ - LogPath: createTempDir(t), + LogPath: sample.CreateTempDir(t), }, }, fail: false, @@ -45,7 +46,7 @@ func TestInitLogger(t *testing.T) { LogFormat: "unknown", LogLevel: 3, // zerolog.ErrorLevel, ComplianceConfig: config.ComplianceConfig{ - LogPath: createTempDir(t), + LogPath: sample.CreateTempDir(t), }, }, fail: false, @@ -57,7 +58,7 @@ func TestInitLogger(t *testing.T) { LogLevel: 4, // zerolog.DebugLevel, LogSampler: true, ComplianceConfig: config.ComplianceConfig{ - LogPath: createTempDir(t), + LogPath: sample.CreateTempDir(t), }, }, }, diff --git a/zetaclient/chains/base/observer_test.go b/zetaclient/chains/base/observer_test.go index 7dd2f18081..6972478190 100644 --- a/zetaclient/chains/base/observer_test.go +++ b/zetaclient/chains/base/observer_test.go @@ -18,13 +18,6 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" ) -// create a temporary directory for testing -func createTempDir(t *testing.T) string { - tempPath, err := os.MkdirTemp("", "tempdir-") - require.NoError(t, err) - return tempPath -} - // createObserver creates a new observer for testing func createObserver(t *testing.T, dbPath string) *base.Observer { // constructor parameters @@ -62,7 +55,7 @@ func TestNewObserver(t *testing.T) { tss := mocks.NewTSSMainnet() blockCacheSize := base.DefaultBlockCacheSize headersCacheSize := base.DefaultHeadersCacheSize - dbPath := createTempDir(t) + dbPath := sample.CreateTempDir(t) // test cases tests := []struct { @@ -159,7 +152,7 @@ func TestNewObserver(t *testing.T) { } func TestObserverGetterAndSetter(t *testing.T) { - dbPath := createTempDir(t) + dbPath := sample.CreateTempDir(t) t.Run("should be able to update chain", func(t *testing.T) { ob := createObserver(t, dbPath) @@ -258,7 +251,7 @@ func TestObserverGetterAndSetter(t *testing.T) { } func TestOpenDB(t *testing.T) { - dbPath := createTempDir(t) + dbPath := sample.CreateTempDir(t) ob := createObserver(t, dbPath) t.Run("should be able to open db", func(t *testing.T) { @@ -277,7 +270,7 @@ func TestLoadLastBlockScanned(t *testing.T) { t.Run("should be able to load last block scanned", func(t *testing.T) { // create db and write 100 as last block scanned - dbPath := createTempDir(t) + dbPath := sample.CreateTempDir(t) ob := createObserver(t, dbPath) ob.WriteLastBlockScannedToDB(100) @@ -289,7 +282,7 @@ func TestLoadLastBlockScanned(t *testing.T) { }) t.Run("should use latest block if last block scanned not found", func(t *testing.T) { // create empty db - dbPath := createTempDir(t) + dbPath := sample.CreateTempDir(t) ob := createObserver(t, dbPath) // read last block scanned @@ -299,7 +292,7 @@ func TestLoadLastBlockScanned(t *testing.T) { }) t.Run("should overwrite last block scanned if env var is set", func(t *testing.T) { // create db and write 100 as last block scanned - dbPath := createTempDir(t) + dbPath := sample.CreateTempDir(t) ob := createObserver(t, dbPath) ob.WriteLastBlockScannedToDB(100) @@ -322,7 +315,7 @@ func TestLoadLastBlockScanned(t *testing.T) { }) t.Run("should return error on invalid env var", func(t *testing.T) { // create db and write 100 as last block scanned - dbPath := createTempDir(t) + dbPath := sample.CreateTempDir(t) ob := createObserver(t, dbPath) // set invalid env var @@ -338,7 +331,7 @@ func TestLoadLastBlockScanned(t *testing.T) { func TestReadWriteLastBlockScannedToDB(t *testing.T) { t.Run("should be able to write and read last block scanned to db", func(t *testing.T) { // create db and write 100 as last block scanned - dbPath := createTempDir(t) + dbPath := sample.CreateTempDir(t) ob := createObserver(t, dbPath) err := ob.WriteLastBlockScannedToDB(100) require.NoError(t, err) @@ -349,7 +342,7 @@ func TestReadWriteLastBlockScannedToDB(t *testing.T) { }) t.Run("should return error when last block scanned not found in db", func(t *testing.T) { // create empty db - dbPath := createTempDir(t) + dbPath := sample.CreateTempDir(t) ob := createObserver(t, dbPath) lastScannedBlock, err := ob.ReadLastBlockScannedFromDB() diff --git a/zetaclient/chains/base/signer.go b/zetaclient/chains/base/signer.go index 2585218767..a33f116e48 100644 --- a/zetaclient/chains/base/signer.go +++ b/zetaclient/chains/base/signer.go @@ -1,6 +1,8 @@ package base import ( + "sync" + "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" "github.com/zeta-chain/zetacore/zetaclient/context" @@ -24,6 +26,10 @@ type Signer struct { // logger contains the loggers used by signer logger Logger + + // mu protects fields from concurrent access + // Note: base signer simply provides the mutex. It's the sub-struct's responsibility to use it to be thread-safe + mu *sync.Mutex } // NewSigner creates a new base signer @@ -43,6 +49,7 @@ func NewSigner( Std: logger.Std.With().Int64("chain", chain.ChainId).Str("module", "signer").Logger(), Compliance: logger.Compliance, }, + mu: &sync.Mutex{}, } } @@ -94,3 +101,8 @@ func (s *Signer) WithTelemetryServer(ts *metrics.TelemetryServer) *Signer { func (s *Signer) Logger() *Logger { return &s.logger } + +// Mu returns the mutex for the signer +func (s *Signer) Mu() *sync.Mutex { + return s.mu +} diff --git a/zetaclient/chains/base/signer_test.go b/zetaclient/chains/base/signer_test.go index 960c508d6e..3de1d18d4a 100644 --- a/zetaclient/chains/base/signer_test.go +++ b/zetaclient/chains/base/signer_test.go @@ -71,4 +71,8 @@ func TestSignerGetterAndSetter(t *testing.T) { logger.Std.Info().Msg("print standard log") logger.Compliance.Info().Msg("print compliance log") }) + t.Run("should be able to get mutex", func(t *testing.T) { + signer := createSigner(t) + require.NotNil(t, signer.Mu()) + }) } diff --git a/zetaclient/chains/bitcoin/signer/signer.go b/zetaclient/chains/bitcoin/signer/signer.go index f2d47fd988..d39b8a8f9d 100644 --- a/zetaclient/chains/bitcoin/signer/signer.go +++ b/zetaclient/chains/bitcoin/signer/signer.go @@ -15,7 +15,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/rs/zerolog" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" @@ -30,7 +29,6 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/context" "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/outboundprocessor" - "github.com/zeta-chain/zetacore/zetaclient/tss" ) const ( @@ -45,20 +43,25 @@ var _ interfaces.ChainSigner = &Signer{} // Signer deals with signing BTC transactions and implements the ChainSigner interface type Signer struct { - tssSigner interfaces.TSSSigner - rpcClient interfaces.BTCRPCClient - logger zerolog.Logger - loggerCompliance zerolog.Logger - ts *metrics.TelemetryServer - coreContext *context.ZetacoreContext + // base.Signer implements the base chain signer + base.Signer + + // client is the RPC client to interact with the Bitcoin chain + client interfaces.BTCRPCClient } +// NewSigner creates a new Bitcoin signer func NewSigner( - cfg config.BTCConfig, - tssSigner interfaces.TSSSigner, - logger base.Logger, + chain chains.Chain, + zetacoreContext *context.ZetacoreContext, + tss interfaces.TSSSigner, ts *metrics.TelemetryServer, - coreContext *context.ZetacoreContext) (*Signer, error) { + logger base.Logger, + cfg config.BTCConfig) (*Signer, error) { + // create base signer + baseSigner := base.NewSigner(chain, zetacoreContext, tss, ts, logger) + + // create the bitcoin rpc client using the provided config connCfg := &rpcclient.ConnConfig{ Host: cfg.RPCHost, User: cfg.RPCUsername, @@ -73,12 +76,8 @@ func NewSigner( } return &Signer{ - tssSigner: tssSigner, - rpcClient: client, - logger: logger.Std.With().Str("chain", "BTC").Str("module", "BTCSigner").Logger(), - loggerCompliance: logger.Compliance, - ts: ts, - coreContext: coreContext, + Signer: *baseSigner, + client: client, }, nil } @@ -130,12 +129,12 @@ func (signer *Signer) AddWithdrawTxOutputs( if remainingSats < 0 { return fmt.Errorf("remainder value is negative: %d", remainingSats) } else if remainingSats == nonceMark { - signer.logger.Info().Msgf("adjust remainder value to avoid duplicate nonce-mark: %d", remainingSats) + signer.Logger().Std.Info().Msgf("adjust remainder value to avoid duplicate nonce-mark: %d", remainingSats) remainingSats-- } // 1st output: the nonce-mark btc to TSS self - tssAddrP2WPKH := signer.tssSigner.BTCAddressWitnessPubkeyHash() + tssAddrP2WPKH := signer.TSS().BTCAddressWitnessPubkeyHash() payToSelfScript, err := bitcoin.PayToAddrScript(tssAddrP2WPKH) if err != nil { return err @@ -182,7 +181,10 @@ func (signer *Signer) SignWithdrawTx( // refresh unspent UTXOs and continue with keysign regardless of error err := observer.FetchUTXOs() if err != nil { - signer.logger.Error().Err(err).Msgf("SignWithdrawTx: FetchUTXOs error: nonce %d chain %d", nonce, chain.ChainId) + signer.Logger(). + Std.Error(). + Err(err). + Msgf("SignWithdrawTx: FetchUTXOs error: nonce %d chain %d", nonce, chain.ChainId) } // select N UTXOs to cover the total expense @@ -216,16 +218,16 @@ func (signer *Signer) SignWithdrawTx( return nil, err } if sizeLimit < bitcoin.BtcOutboundBytesWithdrawer { // ZRC20 'withdraw' charged less fee from end user - signer.logger.Info(). + signer.Logger().Std.Info(). Msgf("sizeLimit %d is less than BtcOutboundBytesWithdrawer %d for nonce %d", sizeLimit, txSize, nonce) } if txSize < bitcoin.OutboundBytesMin { // outbound shouldn't be blocked a low sizeLimit - signer.logger.Warn(). + signer.Logger().Std.Warn(). Msgf("txSize %d is less than outboundBytesMin %d; use outboundBytesMin", txSize, bitcoin.OutboundBytesMin) txSize = bitcoin.OutboundBytesMin } if txSize > bitcoin.OutboundBytesMax { // in case of accident - signer.logger.Warn(). + signer.Logger().Std.Warn(). Msgf("txSize %d is greater than outboundBytesMax %d; use outboundBytesMax", txSize, bitcoin.OutboundBytesMax) txSize = bitcoin.OutboundBytesMax } @@ -233,8 +235,10 @@ func (signer *Signer) SignWithdrawTx( // fee calculation // #nosec G701 always in range (checked above) fees := new(big.Int).Mul(big.NewInt(int64(txSize)), gasPrice) - signer.logger.Info().Msgf("bitcoin outbound nonce %d gasPrice %s size %d fees %s consolidated %d utxos of value %v", - nonce, gasPrice.String(), txSize, fees.String(), consolidatedUtxo, consolidatedValue) + signer.Logger(). + Std.Info(). + Msgf("bitcoin outbound nonce %d gasPrice %s size %d fees %s consolidated %d utxos of value %v", + nonce, gasPrice.String(), txSize, fees.String(), consolidatedUtxo, consolidatedValue) // add tx outputs err = signer.AddWithdrawTxOutputs(tx, to, total, amount, nonceMark, fees, cancelTx) @@ -260,11 +264,7 @@ func (signer *Signer) SignWithdrawTx( } } - tssSigner, ok := signer.tssSigner.(*tss.TSS) - if !ok { - return nil, fmt.Errorf("tssSigner is not a TSS") - } - sig65Bs, err := tssSigner.SignBatch(witnessHashes, height, nonce, &chain) + sig65Bs, err := signer.TSS().SignBatch(witnessHashes, height, nonce, chain.ChainId) if err != nil { return nil, fmt.Errorf("SignBatch error: %v", err) } @@ -278,7 +278,7 @@ func (signer *Signer) SignWithdrawTx( S: S, } - pkCompressed := signer.tssSigner.PubKeyCompressedBytes() + pkCompressed := signer.TSS().PubKeyCompressedBytes() hashType := txscript.SigHashAll txWitness := wire.TxWitness{append(sig.Serialize(), byte(hashType)), pkCompressed} tx.TxIn[ix].Witness = txWitness @@ -298,12 +298,12 @@ func (signer *Signer) Broadcast(signedTx *wire.MsgTx) error { str := hex.EncodeToString(outBuff.Bytes()) fmt.Printf("BTCSigner: Transaction Data: %s\n", str) - hash, err := signer.rpcClient.SendRawTransaction(signedTx, true) + hash, err := signer.client.SendRawTransaction(signedTx, true) if err != nil { return err } - signer.logger.Info().Msgf("Broadcasting BTC tx , hash %s ", hash) + signer.Logger().Std.Info().Msgf("Broadcasting BTC tx , hash %s ", hash) return nil } @@ -318,11 +318,11 @@ func (signer *Signer) TryProcessOutbound( defer func() { outboundProcessor.EndTryProcess(outboundID) if err := recover(); err != nil { - signer.logger.Error().Msgf("BTC TryProcessOutbound: %s, caught panic error: %v", cctx.Index, err) + signer.Logger().Std.Error().Msgf("BTC TryProcessOutbound: %s, caught panic error: %v", cctx.Index, err) } }() - logger := signer.logger.With(). + logger := signer.Logger().Std.With(). Str("OutboundID", outboundID). Str("SendHash", cctx.Index). Logger() @@ -341,7 +341,7 @@ func (signer *Signer) TryProcessOutbound( logger.Error().Msgf("chain observer is not a bitcoin observer") return } - flags := signer.coreContext.GetCrossChainFlags() + flags := signer.ZetacoreContext().GetCrossChainFlags() if !flags.IsOutboundEnabled { logger.Info().Msgf("outbound is disabled") return @@ -375,7 +375,7 @@ func (signer *Signer) TryProcessOutbound( amount := float64(params.Amount.Uint64()) / 1e8 // Add 1 satoshi/byte to gasPrice to avoid minRelayTxFee issue - networkInfo, err := signer.rpcClient.GetNetworkInfo() + networkInfo, err := signer.client.GetNetworkInfo() if err != nil { logger.Error().Err(err).Msgf("cannot get bitcoin network info") return @@ -386,7 +386,7 @@ func (signer *Signer) TryProcessOutbound( // compliance check cancelTx := compliance.IsCctxRestricted(cctx) if cancelTx { - compliance.PrintComplianceLog(logger, signer.loggerCompliance, + compliance.PrintComplianceLog(logger, signer.Logger().Compliance, true, chain.ChainId, cctx.Index, cctx.InboundParams.Sender, params.Receiver, "BTC") amount = 0.0 // zero out the amount to cancel the tx } diff --git a/zetaclient/chains/bitcoin/signer/signer_keysign_test.go b/zetaclient/chains/bitcoin/signer/signer_keysign_test.go index 8cc90cf3eb..2506f57059 100644 --- a/zetaclient/chains/bitcoin/signer/signer_keysign_test.go +++ b/zetaclient/chains/bitcoin/signer/signer_keysign_test.go @@ -15,7 +15,6 @@ import ( "github.com/btcsuite/btcutil" "github.com/stretchr/testify/suite" - "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" @@ -147,7 +146,7 @@ func getTSSTX( return "", err } - sig65B, err := tss.Sign(witnessHash, 10, 10, &chains.Chain{}, "") + sig65B, err := tss.Sign(witnessHash, 10, 10, 0, "") R := big.NewInt(0).SetBytes(sig65B[:32]) S := big.NewInt(0).SetBytes(sig65B[32:64]) sig := btcec.Signature{ diff --git a/zetaclient/chains/bitcoin/signer/signer_test.go b/zetaclient/chains/bitcoin/signer/signer_test.go index 74628211a4..e351d4a65c 100644 --- a/zetaclient/chains/bitcoin/signer/signer_test.go +++ b/zetaclient/chains/bitcoin/signer/signer_test.go @@ -21,8 +21,6 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/chains/base" "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin" "github.com/zeta-chain/zetacore/zetaclient/config" - "github.com/zeta-chain/zetacore/zetaclient/context" - "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" ) @@ -48,13 +46,14 @@ func (s *BTCSignerSuite) SetUpTest(c *C) { tss := &mocks.TSS{ PrivKey: privateKey, } - cfg := config.NewConfig() s.btcSigner, err = NewSigner( - config.BTCConfig{}, + chains.Chain{}, + nil, tss, + nil, base.DefaultLogger(), - &metrics.TelemetryServer{}, - context.NewZetacoreContext(cfg)) + config.BTCConfig{}, + ) c.Assert(err, IsNil) } @@ -231,16 +230,17 @@ func (s *BTCSignerSuite) TestP2WPH(c *C) { func TestAddWithdrawTxOutputs(t *testing.T) { // Create test signer and receiver address signer, err := NewSigner( - config.BTCConfig{}, + chains.Chain{}, + nil, mocks.NewTSSMainnet(), - base.DefaultLogger(), - &metrics.TelemetryServer{}, nil, + base.DefaultLogger(), + config.BTCConfig{}, ) require.NoError(t, err) // tss address and script - tssAddr := signer.tssSigner.BTCAddressWitnessPubkeyHash() + tssAddr := signer.TSS().BTCAddressWitnessPubkeyHash() tssScript, err := bitcoin.PayToAddrScript(tssAddr) require.NoError(t, err) fmt.Printf("tss address: %s", tssAddr.EncodeAddress()) @@ -392,13 +392,13 @@ func TestNewBTCSigner(t *testing.T) { tss := &mocks.TSS{ PrivKey: privateKey, } - cfg := config.NewConfig() btcSigner, err := NewSigner( - config.BTCConfig{}, + chains.Chain{}, + nil, tss, + nil, base.DefaultLogger(), - &metrics.TelemetryServer{}, - context.NewZetacoreContext(cfg)) + config.BTCConfig{}) require.NoError(t, err) require.NotNil(t, btcSigner) } diff --git a/zetaclient/chains/evm/signer/signer.go b/zetaclient/chains/evm/signer/signer.go index d49895e2a3..4fa29a015b 100644 --- a/zetaclient/chains/evm/signer/signer.go +++ b/zetaclient/chains/evm/signer/signer.go @@ -5,10 +5,8 @@ import ( "encoding/hex" "fmt" "math/big" - "math/rand" "strconv" "strings" - "sync" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -48,39 +46,54 @@ var ( // Signer deals with the signing EVM transactions and implements the ChainSigner interface type Signer struct { - client interfaces.EVMRPCClient - chain *chains.Chain - tssSigner interfaces.TSSSigner - ethSigner ethtypes.Signer - logger base.Logger - ts *metrics.TelemetryServer - coreContext *clientcontext.ZetacoreContext - - // mu protects below fields from concurrent access - mu *sync.Mutex - zetaConnectorABI abi.ABI - erc20CustodyABI abi.ABI - zetaConnectorAddress ethcommon.Address - er20CustodyAddress ethcommon.Address + // base.Signer implements the base chain signer + base.Signer + + // client is the EVM RPC client to interact with the EVM chain + client interfaces.EVMRPCClient + + // ethSigner encapsulates EVM transaction signature handling + ethSigner ethtypes.Signer + + // zetaConnectorABI is the ABI of the ZetaConnector contract + zetaConnectorABI abi.ABI + + // erc20CustodyABI is the ABI of the ERC20Custody contract + erc20CustodyABI abi.ABI + + // zetaConnectorAddress is the address of the ZetaConnector contract + zetaConnectorAddress ethcommon.Address + + // er20CustodyAddress is the address of the ERC20Custody contract + er20CustodyAddress ethcommon.Address + + // outboundHashBeingReported is a map of outboundHash being reported outboundHashBeingReported map[string]bool } +// NewSigner creates a new EVM signer func NewSigner( chain chains.Chain, + zetacoreContext *clientcontext.ZetacoreContext, + tss interfaces.TSSSigner, + ts *metrics.TelemetryServer, + logger base.Logger, endpoint string, - tssSigner interfaces.TSSSigner, zetaConnectorABI string, erc20CustodyABI string, zetaConnectorAddress ethcommon.Address, erc20CustodyAddress ethcommon.Address, - coreContext *clientcontext.ZetacoreContext, - logger base.Logger, - ts *metrics.TelemetryServer, ) (*Signer, error) { + // create base signer + baseSigner := base.NewSigner(chain, zetacoreContext, tss, ts, logger) + + // create EVM client client, ethSigner, err := getEVMRPC(endpoint) if err != nil { return nil, err } + + // prepare ABIs connectorABI, err := abi.JSON(strings.NewReader(zetaConnectorABI)) if err != nil { return nil, err @@ -91,50 +104,42 @@ func NewSigner( } return &Signer{ - client: client, - chain: &chain, - tssSigner: tssSigner, - ethSigner: ethSigner, - zetaConnectorABI: connectorABI, - erc20CustodyABI: custodyABI, - zetaConnectorAddress: zetaConnectorAddress, - er20CustodyAddress: erc20CustodyAddress, - coreContext: coreContext, - logger: base.Logger{ - Std: logger.Std.With().Str("chain", chain.ChainName.String()).Str("module", "EVMSigner").Logger(), - Compliance: logger.Compliance, - }, - ts: ts, - mu: &sync.Mutex{}, + Signer: *baseSigner, + client: client, + ethSigner: ethSigner, + zetaConnectorABI: connectorABI, + erc20CustodyABI: custodyABI, + zetaConnectorAddress: zetaConnectorAddress, + er20CustodyAddress: erc20CustodyAddress, outboundHashBeingReported: make(map[string]bool), }, nil } // SetZetaConnectorAddress sets the zeta connector address func (signer *Signer) SetZetaConnectorAddress(addr ethcommon.Address) { - signer.mu.Lock() - defer signer.mu.Unlock() + signer.Mu().Lock() + defer signer.Mu().Unlock() signer.zetaConnectorAddress = addr } // SetERC20CustodyAddress sets the erc20 custody address func (signer *Signer) SetERC20CustodyAddress(addr ethcommon.Address) { - signer.mu.Lock() - defer signer.mu.Unlock() + signer.Mu().Lock() + defer signer.Mu().Unlock() signer.er20CustodyAddress = addr } // GetZetaConnectorAddress returns the zeta connector address func (signer *Signer) GetZetaConnectorAddress() ethcommon.Address { - signer.mu.Lock() - defer signer.mu.Unlock() + signer.Mu().Lock() + defer signer.Mu().Unlock() return signer.zetaConnectorAddress } // GetERC20CustodyAddress returns the erc20 custody address func (signer *Signer) GetERC20CustodyAddress() ethcommon.Address { - signer.mu.Lock() - defer signer.mu.Unlock() + signer.Mu().Lock() + defer signer.Mu().Unlock() return signer.er20CustodyAddress } @@ -149,15 +154,14 @@ func (signer *Signer) Sign( nonce uint64, height uint64, ) (*ethtypes.Transaction, []byte, []byte, error) { - log.Debug().Msgf("TSS SIGNER: %s", signer.tssSigner.Pubkey()) + log.Debug().Msgf("Sign: TSS signer: %s", signer.TSS().Pubkey()) // TODO: use EIP-1559 transaction type // https://github.com/zeta-chain/node/issues/1952 tx := ethtypes.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data) - hashBytes := signer.ethSigner.Hash(tx).Bytes() - sig, err := signer.tssSigner.Sign(hashBytes, height, nonce, signer.chain, "") + sig, err := signer.TSS().Sign(hashBytes, height, nonce, signer.Chain().ChainId, "") if err != nil { return nil, nil, nil, err } @@ -165,11 +169,11 @@ func (signer *Signer) Sign( log.Debug().Msgf("Sign: Signature: %s", hex.EncodeToString(sig[:])) pubk, err := crypto.SigToPub(hashBytes, sig[:]) if err != nil { - signer.logger.Std.Error().Err(err).Msgf("SigToPub error") + signer.Logger().Std.Error().Err(err).Msgf("SigToPub error") } addr := crypto.PubkeyToAddress(*pubk) - signer.logger.Std.Info().Msgf("Sign: Ecrecovery of signature: %s", addr.Hex()) + signer.Logger().Std.Info().Msgf("Sign: Ecrecovery of signature: %s", addr.Hex()) signedTX, err := tx.WithSignature(signer.ethSigner, sig[:]) if err != nil { return nil, nil, nil, err @@ -269,7 +273,7 @@ func (signer *Signer) SignRevertTx(txData *OutboundData) (*ethtypes.Transaction, func (signer *Signer) SignCancelTx(txData *OutboundData) (*ethtypes.Transaction, error) { tx, _, _, err := signer.Sign( nil, - signer.tssSigner.EVMAddress(), + signer.TSS().EVMAddress(), zeroValue, // zero out the amount to cancel the tx evm.EthTransferGasLimit, txData.gasPrice, @@ -326,7 +330,7 @@ func (signer *Signer) TryProcessOutbound( zetacoreClient interfaces.ZetacoreClient, height uint64, ) { - logger := signer.logger.Std.With(). + logger := signer.Logger().Std.With(). Str("outboundID", outboundID). Str("SendHash", cctx.Index). Logger() @@ -363,16 +367,16 @@ func (signer *Signer) TryProcessOutbound( toChain := chains.GetChainFromChainID(txData.toChainID.Int64()) // Get cross-chain flags - crossChainflags := signer.coreContext.GetCrossChainFlags() + crossChainflags := signer.ZetacoreContext().GetCrossChainFlags() // https://github.com/zeta-chain/node/issues/2050 var tx *ethtypes.Transaction // compliance check goes first if compliance.IsCctxRestricted(cctx) { compliance.PrintComplianceLog( logger, - signer.logger.Compliance, + signer.Logger().Compliance, true, - signer.chain.ChainId, + signer.Chain().ChainId, cctx.Index, cctx.InboundParams.Sender, txData.to.Hex(), @@ -529,22 +533,21 @@ func (signer *Signer) BroadcastOutbound( if tx == nil { logger.Warn().Msgf("BroadcastOutbound: no tx to broadcast %s", cctx.Index) } - // Try to broadcast transaction + + // broadcast transaction if tx != nil { outboundHash := tx.Hash().Hex() - logger.Info(). - Msgf("on chain %s nonce %d, outboundHash %s signer %s", signer.chain, cctx.GetCurrentOutboundParam().TssNonce, outboundHash, myID) - //if len(signers) == 0 || myid == signers[send.OutboundParams.Broadcaster] || myid == signers[int(send.OutboundParams.Broadcaster+1)%len(signers)] { + + // try broacasting tx with increasing backoff (1s, 2s, 4s, 8s, 16s) in case of RPC error backOff := 1000 * time.Millisecond - // retry loop: 1s, 2s, 4s, 8s, 16s in case of RPC error for i := 0; i < 5; i++ { - logger.Info(). - Msgf("broadcasting tx %s to chain %s: nonce %d, retry %d", outboundHash, toChain, cctx.GetCurrentOutboundParam().TssNonce, i) - // #nosec G404 randomness is not a security issue here - time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond) // FIXME: use backoff + time.Sleep(backOff) err := signer.Broadcast(tx) if err != nil { - log.Warn().Err(err).Msgf("Outbound Broadcast error") + log.Warn(). + Err(err). + Msgf("BroadcastOutbound: error broadcasting tx %s on chain %d nonce %d retry %d signer %s", + outboundHash, toChain.ChainId, cctx.GetCurrentOutboundParam().TssNonce, i, myID) retry, report := zetacore.HandleBroadcastError( err, strconv.FormatUint(cctx.GetCurrentOutboundParam().TssNonce, 10), @@ -560,8 +563,8 @@ func (signer *Signer) BroadcastOutbound( backOff *= 2 continue } - logger.Info(). - Msgf("Broadcast success: nonce %d to chain %s outboundHash %s", cctx.GetCurrentOutboundParam().TssNonce, toChain, outboundHash) + logger.Info().Msgf("BroadcastOutbound: broadcasted tx %s on chain %d nonce %d signer %s", + outboundHash, toChain.ChainId, cctx.GetCurrentOutboundParam().TssNonce, myID) signer.reportToOutboundTracker(zetacoreClient, toChain.ChainId, tx.Nonce(), outboundHash, logger) break // successful broadcast; no need to retry } @@ -686,8 +689,8 @@ func (signer *Signer) reportToOutboundTracker( logger zerolog.Logger, ) { // skip if already being reported - signer.mu.Lock() - defer signer.mu.Unlock() + signer.Mu().Lock() + defer signer.Mu().Unlock() if _, found := signer.outboundHashBeingReported[outboundHash]; found { logger.Info(). Msgf("reportToOutboundTracker: outboundHash %s for chain %d nonce %d is being reported", outboundHash, chainID, nonce) @@ -698,9 +701,9 @@ func (signer *Signer) reportToOutboundTracker( // report to outbound tracker with goroutine go func() { defer func() { - signer.mu.Lock() + signer.Mu().Lock() delete(signer.outboundHashBeingReported, outboundHash) - signer.mu.Unlock() + signer.Mu().Unlock() }() // try monitoring tx inclusion status for 10 minutes diff --git a/zetaclient/chains/evm/signer/signer_test.go b/zetaclient/chains/evm/signer/signer_test.go index 186582dd24..410ea5adf3 100644 --- a/zetaclient/chains/evm/signer/signer_test.go +++ b/zetaclient/chains/evm/signer/signer_test.go @@ -43,20 +43,19 @@ func getNewEvmSigner(tss interfaces.TSSSigner) (*Signer, error) { mpiAddress := ConnectorAddress erc20CustodyAddress := ERC20CustodyAddress logger := base.Logger{} - ts := &metrics.TelemetryServer{} cfg := config.NewConfig() return NewSigner( chains.BscMainnet, - mocks.EVMRPCEnabled, + context.NewZetacoreContext(cfg), tss, + nil, + logger, + mocks.EVMRPCEnabled, config.GetConnectorABI(), config.GetERC20CustodyABI(), mpiAddress, - erc20CustodyAddress, - context.NewZetacoreContext(cfg), - logger, - ts) + erc20CustodyAddress) } // getNewEvmChainObserver creates a new EVM chain observer for testing @@ -256,7 +255,7 @@ func TestSigner_SignCancelTx(t *testing.T) { // Verify tx body basics // Note: Cancel tx sends 0 gas token to TSS self address - verifyTxBodyBasics(t, tx, evmSigner.tssSigner.EVMAddress(), txData.nonce, big.NewInt(0)) + verifyTxBodyBasics(t, tx, evmSigner.TSS().EVMAddress(), txData.nonce, big.NewInt(0)) }) t.Run("SignCancelTx - should fail if keysign fails", func(t *testing.T) { // Pause tss to make keysign fail diff --git a/zetaclient/chains/interfaces/interfaces.go b/zetaclient/chains/interfaces/interfaces.go index 1d901d4f53..1ef94ec8de 100644 --- a/zetaclient/chains/interfaces/interfaces.go +++ b/zetaclient/chains/interfaces/interfaces.go @@ -167,7 +167,10 @@ type TSSSigner interface { // Note: it specifies optionalPubkey to use a different pubkey than the current pubkey set during keygen // TODO: check if optionalPubkey is needed // https://github.com/zeta-chain/node/issues/2085 - Sign(data []byte, height uint64, nonce uint64, chain *chains.Chain, optionalPubkey string) ([65]byte, error) + Sign(data []byte, height uint64, nonce uint64, chainID int64, optionalPubkey string) ([65]byte, error) + + // SignBatch signs the data in batch + SignBatch(digests [][]byte, height uint64, nonce uint64, chainID int64) ([][65]byte, error) EVMAddress() ethcommon.Address BTCAddress() string diff --git a/zetaclient/testutils/mocks/btc_rpc.go b/zetaclient/testutils/mocks/btc_rpc.go index cfd63ef87b..01d286d31a 100644 --- a/zetaclient/testutils/mocks/btc_rpc.go +++ b/zetaclient/testutils/mocks/btc_rpc.go @@ -17,7 +17,9 @@ var _ interfaces.BTCRPCClient = &MockBTCRPCClient{} // MockBTCRPCClient is a mock implementation of the BTCRPCClient interface type MockBTCRPCClient struct { - Txs []*btcutil.Tx + err error + blockCount int64 + Txs []*btcutil.Tx } // NewMockBTCRPCClient creates a new mock BTC RPC client @@ -28,6 +30,10 @@ func NewMockBTCRPCClient() *MockBTCRPCClient { // Reset clears the mock data func (c *MockBTCRPCClient) Reset() *MockBTCRPCClient { + if c.err != nil { + return nil + } + c.Txs = []*btcutil.Tx{} return c } @@ -95,7 +101,10 @@ func (c *MockBTCRPCClient) GetRawTransactionVerbose(_ *chainhash.Hash) (*btcjson } func (c *MockBTCRPCClient) GetBlockCount() (int64, error) { - return 0, errors.New("not implemented") + if c.err != nil { + return 0, c.err + } + return c.blockCount, nil } func (c *MockBTCRPCClient) GetBlockHash(_ int64) (*chainhash.Hash, error) { @@ -118,6 +127,16 @@ func (c *MockBTCRPCClient) GetBlockHeader(_ *chainhash.Hash) (*wire.BlockHeader, // Feed data to the mock BTC RPC client for testing // ---------------------------------------------------------------------------- +func (c *MockBTCRPCClient) WithError(err error) *MockBTCRPCClient { + c.err = err + return c +} + +func (c *MockBTCRPCClient) WithBlockCount(blkCnt int64) *MockBTCRPCClient { + c.blockCount = blkCnt + return c +} + func (c *MockBTCRPCClient) WithRawTransaction(tx *btcutil.Tx) *MockBTCRPCClient { c.Txs = append(c.Txs, tx) return c diff --git a/zetaclient/testutils/mocks/evm_rpc.go b/zetaclient/testutils/mocks/evm_rpc.go index 926be6ec28..fa40357592 100644 --- a/zetaclient/testutils/mocks/evm_rpc.go +++ b/zetaclient/testutils/mocks/evm_rpc.go @@ -31,7 +31,9 @@ func (s subscription) Err() <-chan error { var _ interfaces.EVMRPCClient = &MockEvmClient{} type MockEvmClient struct { - Receipts []*ethtypes.Receipt + err error + blockNumber uint64 + Receipts []*ethtypes.Receipt } func NewMockEvmClient() *MockEvmClient { @@ -44,56 +46,92 @@ func (e *MockEvmClient) SubscribeFilterLogs( _ ethereum.FilterQuery, _ chan<- ethtypes.Log, ) (ethereum.Subscription, error) { + if e.err != nil { + return subscription{}, e.err + } return subscription{}, nil } func (e *MockEvmClient) CodeAt(_ context.Context, _ ethcommon.Address, _ *big.Int) ([]byte, error) { + if e.err != nil { + return nil, e.err + } return []byte{}, nil } func (e *MockEvmClient) CallContract(_ context.Context, _ ethereum.CallMsg, _ *big.Int) ([]byte, error) { + if e.err != nil { + return nil, e.err + } return []byte{}, nil } func (e *MockEvmClient) HeaderByNumber(_ context.Context, _ *big.Int) (*ethtypes.Header, error) { + if e.err != nil { + return nil, e.err + } return ðtypes.Header{}, nil } func (e *MockEvmClient) PendingCodeAt(_ context.Context, _ ethcommon.Address) ([]byte, error) { + if e.err != nil { + return nil, e.err + } return []byte{}, nil } func (e *MockEvmClient) PendingNonceAt(_ context.Context, _ ethcommon.Address) (uint64, error) { + if e.err != nil { + return 0, e.err + } return 0, nil } func (e *MockEvmClient) SuggestGasPrice(_ context.Context) (*big.Int, error) { + if e.err != nil { + return nil, e.err + } return big.NewInt(0), nil } func (e *MockEvmClient) SuggestGasTipCap(_ context.Context) (*big.Int, error) { + if e.err != nil { + return nil, e.err + } return big.NewInt(0), nil } func (e *MockEvmClient) EstimateGas(_ context.Context, _ ethereum.CallMsg) (gas uint64, err error) { + if e.err != nil { + return 0, e.err + } gas = 0 err = nil return } func (e *MockEvmClient) SendTransaction(_ context.Context, _ *ethtypes.Transaction) error { - return nil + return e.err } func (e *MockEvmClient) FilterLogs(_ context.Context, _ ethereum.FilterQuery) ([]ethtypes.Log, error) { + if e.err != nil { + return nil, e.err + } return []ethtypes.Log{}, nil } func (e *MockEvmClient) BlockNumber(_ context.Context) (uint64, error) { - return 0, nil + if e.err != nil { + return 0, e.err + } + return e.blockNumber, nil } func (e *MockEvmClient) BlockByNumber(_ context.Context, _ *big.Int) (*ethtypes.Block, error) { + if e.err != nil { + return nil, e.err + } return ðtypes.Block{}, nil } @@ -101,10 +139,17 @@ func (e *MockEvmClient) TransactionByHash( _ context.Context, _ ethcommon.Hash, ) (tx *ethtypes.Transaction, isPending bool, err error) { + if e.err != nil { + return nil, false, e.err + } return ðtypes.Transaction{}, false, nil } func (e *MockEvmClient) TransactionReceipt(_ context.Context, _ ethcommon.Hash) (*ethtypes.Receipt, error) { + if e.err != nil { + return nil, e.err + } + // pop a receipt from the list if len(e.Receipts) > 0 { receipt := e.Receipts[len(e.Receipts)-1] @@ -120,6 +165,9 @@ func (e *MockEvmClient) TransactionSender( _ ethcommon.Hash, _ uint, ) (ethcommon.Address, error) { + if e.err != nil { + return ethcommon.Address{}, e.err + } return ethcommon.Address{}, nil } @@ -131,6 +179,16 @@ func (e *MockEvmClient) Reset() *MockEvmClient { // ---------------------------------------------------------------------------- // Feed data to the mock evm client for testing // ---------------------------------------------------------------------------- +func (e *MockEvmClient) WithError(err error) *MockEvmClient { + e.err = err + return e +} + +func (e *MockEvmClient) WithBlockNumber(blockNumber uint64) *MockEvmClient { + e.blockNumber = blockNumber + return e +} + func (e *MockEvmClient) WithReceipt(receipt *ethtypes.Receipt) *MockEvmClient { e.Receipts = append(e.Receipts, receipt) return e diff --git a/zetaclient/testutils/mocks/tss_signer.go b/zetaclient/testutils/mocks/tss_signer.go index 87d2f2ac3b..ea439e23b6 100644 --- a/zetaclient/testutils/mocks/tss_signer.go +++ b/zetaclient/testutils/mocks/tss_signer.go @@ -67,7 +67,7 @@ func (s *TSS) WithPrivKey(privKey *ecdsa.PrivateKey) *TSS { } // Sign uses test key unrelated to any tss key in production -func (s *TSS) Sign(data []byte, _ uint64, _ uint64, _ *chains.Chain, _ string) ([65]byte, error) { +func (s *TSS) Sign(data []byte, _ uint64, _ uint64, _ int64, _ string) ([65]byte, error) { // return error if tss is paused if s.paused { return [65]byte{}, fmt.Errorf("tss is paused") @@ -83,6 +83,17 @@ func (s *TSS) Sign(data []byte, _ uint64, _ uint64, _ *chains.Chain, _ string) ( return sigbyte, nil } +// SignBatch uses test key unrelated to any tss key in production +func (s *TSS) SignBatch(_ [][]byte, _ uint64, _ uint64, _ int64) ([][65]byte, error) { + // return error if tss is paused + if s.paused { + return nil, fmt.Errorf("tss is paused") + } + + // mock not implemented yet + return nil, fmt.Errorf("not implemented") +} + func (s *TSS) Pubkey() []byte { publicKeyBytes := crypto.FromECDSAPub(&s.PrivKey.PublicKey) return publicKeyBytes diff --git a/zetaclient/tss/tss_signer.go b/zetaclient/tss/tss_signer.go index 2915dd6d0a..8fdd0384ee 100644 --- a/zetaclient/tss/tss_signer.go +++ b/zetaclient/tss/tss_signer.go @@ -218,7 +218,7 @@ func (tss *TSS) Sign( digest []byte, height uint64, nonce uint64, - chain *chains.Chain, + chainID int64, optionalPubKey string, ) ([65]byte, error) { H := digest @@ -250,8 +250,8 @@ func (tss *TSS) Sign( // post blame data if enabled if IsEnvFlagEnabled(envFlagPostBlame) { digest := hex.EncodeToString(digest) - index := observertypes.GetBlameIndex(chain.ChainId, nonce, digest, height) - zetaHash, err := tss.ZetacoreClient.PostBlameData(&ksRes.Blame, chain.ChainId, index) + index := observertypes.GetBlameIndex(chainID, nonce, digest, height) + zetaHash, err := tss.ZetacoreClient.PostBlameData(&ksRes.Blame, chainID, index) if err != nil { log.Error().Err(err).Msg("error sending blame data to core") return [65]byte{}, err @@ -304,7 +304,7 @@ func (tss *TSS) Sign( // SignBatch is hash of some data // digest should be batch of hashes of some data -func (tss *TSS) SignBatch(digests [][]byte, height uint64, nonce uint64, chain *chains.Chain) ([][65]byte, error) { +func (tss *TSS) SignBatch(digests [][]byte, height uint64, nonce uint64, chainID int64) ([][65]byte, error) { tssPubkey := tss.CurrentPubkey digestBase64 := make([]string, len(digests)) for i, digest := range digests { @@ -326,8 +326,8 @@ func (tss *TSS) SignBatch(digests [][]byte, height uint64, nonce uint64, chain * // post blame data if enabled if IsEnvFlagEnabled(envFlagPostBlame) { digest := combineDigests(digestBase64) - index := observertypes.GetBlameIndex(chain.ChainId, nonce, hex.EncodeToString(digest), height) - zetaHash, err := tss.ZetacoreClient.PostBlameData(&ksRes.Blame, chain.ChainId, index) + index := observertypes.GetBlameIndex(chainID, nonce, hex.EncodeToString(digest), height) + zetaHash, err := tss.ZetacoreClient.PostBlameData(&ksRes.Blame, chainID, index) if err != nil { log.Error().Err(err).Msg("error sending blame data to core") return [][65]byte{}, err