diff --git a/go.mod b/go.mod index aff1f815c3..8341c0bddf 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,10 @@ require ( gorm.io/gorm v1.24.6 ) -require github.com/binance-chain/tss-lib v0.0.0-20201118045712-70b2cb4bf916 +require ( + github.com/binance-chain/tss-lib v0.0.0-20201118045712-70b2cb4bf916 + github.com/onrik/ethrpc v1.2.0 +) require ( github.com/DataDog/zstd v1.5.2 // indirect diff --git a/go.sum b/go.sum index 96ac6b81eb..be10cf47d8 100644 --- a/go.sum +++ b/go.sum @@ -1869,6 +1869,7 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea/go.mod h1:QMdK4dGB3YhEW2BmA1wgGpPYI3HZy/5gD705PXKUVSg= github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= @@ -2358,6 +2359,8 @@ github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1 github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onrik/ethrpc v1.2.0 h1:BBcr1iWxW1RBP/eyZfzvSKtGgeqexq5qS0yyf4pmKbc= +github.com/onrik/ethrpc v1.2.0/go.mod h1:uvyqpn8+WbsTgBYfouImgEfpIMb0hR8fWGjwdgPHtFU= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= diff --git a/zetaclient/ethrpc/ethrpc.go b/zetaclient/ethrpc/ethrpc.go deleted file mode 100644 index 88283254f2..0000000000 --- a/zetaclient/ethrpc/ethrpc.go +++ /dev/null @@ -1,137 +0,0 @@ -package ethrpc - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" -) - -// EthError - ethereum error -type EthError struct { - Code int `json:"code"` - Message string `json:"message"` -} - -func (err EthError) Error() string { - return fmt.Sprintf("Error %d (%s)", err.Code, err.Message) -} - -type ethResponse struct { - ID int `json:"id"` - JSONRPC string `json:"jsonrpc"` - Result json.RawMessage `json:"result"` - Error *EthError `json:"error"` -} - -type ethRequest struct { - ID int `json:"id"` - JSONRPC string `json:"jsonrpc"` - Method string `json:"method"` - Params []interface{} `json:"params"` -} - -// EthRPC - Ethereum rpc client -type EthRPC struct { - url string - client *http.Client -} - -// New create new rpc client with given url -func New(url string, options ...func(rpc *EthRPC)) *EthRPC { - rpc := &EthRPC{ - url: url, - client: http.DefaultClient, - } - for _, option := range options { - option(rpc) - } - - return rpc -} - -// NewEthRPC create new rpc client with given url -func NewEthRPC(url string, options ...func(rpc *EthRPC)) *EthRPC { - return New(url, options...) -} - -// URL returns client url -func (rpc *EthRPC) URL() string { - return rpc.url -} - -// Call returns raw response of method call -func (rpc *EthRPC) Call(method string, params ...interface{}) (json.RawMessage, error) { - request := ethRequest{ - ID: 1, - JSONRPC: "2.0", - Method: method, - Params: params, - } - - body, err := json.Marshal(request) - if err != nil { - return nil, err - } - - response, err := rpc.client.Post(rpc.url, "application/json", bytes.NewBuffer(body)) - if response != nil { - defer response.Body.Close() - } - if err != nil { - return nil, err - } - - data, err := io.ReadAll(response.Body) - if err != nil { - return nil, err - } - - resp := new(ethResponse) - if err := json.Unmarshal(data, resp); err != nil { - return nil, err - } - - if resp.Error != nil { - return nil, *resp.Error - } - - return resp.Result, nil - -} - -// RawCall returns raw response of method call (Deprecated) -func (rpc *EthRPC) RawCall(method string, params ...interface{}) (json.RawMessage, error) { - return rpc.Call(method, params...) -} - -func (rpc *EthRPC) getBlock(method string, withTransactions bool, params ...interface{}) (*Block, error) { - result, err := rpc.RawCall(method, params...) - if err != nil { - return nil, err - } - if bytes.Equal(result, []byte("null")) { - return nil, nil - } - - var response proxyBlock - if withTransactions { - response = new(proxyBlockWithTransactions) - } else { - response = new(proxyBlockWithoutTransactions) - } - - err = json.Unmarshal(result, response) - if err != nil { - return nil, err - } - - block := response.toBlock() - return &block, nil -} - -// EthGetBlockByNumber returns information about a block by block number. -func (rpc *EthRPC) EthGetBlockByNumber(number uint64, withTransactions bool) (*Block, error) { - return rpc.getBlock("eth_getBlockByNumber", withTransactions, IntToHex(number), withTransactions) -} diff --git a/zetaclient/ethrpc/helper.go b/zetaclient/ethrpc/helper.go deleted file mode 100644 index c826bf94cd..0000000000 --- a/zetaclient/ethrpc/helper.go +++ /dev/null @@ -1,40 +0,0 @@ -package ethrpc - -import ( - "fmt" - "math/big" - "strconv" - "strings" -) - -// ParseInt parse hex string value to uint64 -func ParseInt(value string) (uint64, error) { - i, err := strconv.ParseUint(strings.TrimPrefix(value, "0x"), 16, 64) - if err != nil { - return 0, err - } - - return i, nil -} - -// ParseBigInt parse hex string value to big.Int -func ParseBigInt(value string) (big.Int, error) { - i := big.Int{} - _, err := fmt.Sscan(value, &i) - - return i, err -} - -// IntToHex convert int to hexadecimal representation -func IntToHex(i uint64) string { - return fmt.Sprintf("0x%x", i) -} - -// BigToHex covert big.Int to hexadecimal representation -func BigToHex(bigInt big.Int) string { - if bigInt.BitLen() == 0 { - return "0x0" - } - - return "0x" + strings.TrimPrefix(fmt.Sprintf("%x", bigInt.Bytes()), "0") -} diff --git a/zetaclient/ethrpc/types.go b/zetaclient/ethrpc/types.go deleted file mode 100644 index d756fbfde8..0000000000 --- a/zetaclient/ethrpc/types.go +++ /dev/null @@ -1,162 +0,0 @@ -package ethrpc - -import ( - "bytes" - "math/big" - "unsafe" -) - -// Transaction - transaction object -type Transaction struct { - Hash string - Nonce int - BlockHash string - BlockNumber *int - TransactionIndex *int - From string - To string - Value big.Int - Gas int - GasPrice big.Int - Input string -} - -// Block - block object -type Block struct { - Number uint64 - Hash string - ParentHash string - Nonce string - Sha3Uncles string - LogsBloom string - TransactionsRoot string - StateRoot string - Miner string - Difficulty big.Int - TotalDifficulty big.Int - ExtraData string - Size int - GasLimit int - GasUsed int - Timestamp int - Uncles []string - Transactions []Transaction -} - -type hexInt uint64 - -func (i *hexInt) UnmarshalJSON(data []byte) error { - result, err := ParseInt(string(bytes.Trim(data, `"`))) - *i = hexInt(result) - - return err -} - -type hexBig big.Int - -func (i *hexBig) UnmarshalJSON(data []byte) error { - result, err := ParseBigInt(string(bytes.Trim(data, `"`))) - *i = hexBig(result) - - return err -} - -type proxyBlock interface { - toBlock() Block -} - -type proxyBlockWithTransactions struct { - Number hexInt `json:"number"` - Hash string `json:"hash"` - ParentHash string `json:"parentHash"` - Nonce string `json:"nonce"` - Sha3Uncles string `json:"sha3Uncles"` - LogsBloom string `json:"logsBloom"` - TransactionsRoot string `json:"transactionsRoot"` - StateRoot string `json:"stateRoot"` - Miner string `json:"miner"` - Difficulty hexBig `json:"difficulty"` - TotalDifficulty hexBig `json:"totalDifficulty"` - ExtraData string `json:"extraData"` - Size hexInt `json:"size"` - GasLimit hexInt `json:"gasLimit"` - GasUsed hexInt `json:"gasUsed"` - Timestamp hexInt `json:"timestamp"` - Uncles []string `json:"uncles"` - Transactions []proxyTransaction `json:"transactions"` -} - -type proxyBlockWithoutTransactions struct { - Number hexInt `json:"number"` - Hash string `json:"hash"` - ParentHash string `json:"parentHash"` - Nonce string `json:"nonce"` - Sha3Uncles string `json:"sha3Uncles"` - LogsBloom string `json:"logsBloom"` - TransactionsRoot string `json:"transactionsRoot"` - StateRoot string `json:"stateRoot"` - Miner string `json:"miner"` - Difficulty hexBig `json:"difficulty"` - TotalDifficulty hexBig `json:"totalDifficulty"` - ExtraData string `json:"extraData"` - Size hexInt `json:"size"` - GasLimit hexInt `json:"gasLimit"` - GasUsed hexInt `json:"gasUsed"` - Timestamp hexInt `json:"timestamp"` - Uncles []string `json:"uncles"` - Transactions []string `json:"transactions"` -} - -func (proxy *proxyBlockWithoutTransactions) toBlock() Block { - block := Block{ - Number: uint64(proxy.Number), - Hash: proxy.Hash, - ParentHash: proxy.ParentHash, - Nonce: proxy.Nonce, - Sha3Uncles: proxy.Sha3Uncles, - LogsBloom: proxy.LogsBloom, - TransactionsRoot: proxy.TransactionsRoot, - StateRoot: proxy.StateRoot, - Miner: proxy.Miner, - Difficulty: big.Int(proxy.Difficulty), - TotalDifficulty: big.Int(proxy.TotalDifficulty), - ExtraData: proxy.ExtraData, - // #nosec G701 - copied file from 3rd library, always in range - Size: int(proxy.Size), - // #nosec G701 - copied file from 3rd library, always in range - GasLimit: int(proxy.GasLimit), - // #nosec G701 - copied file from 3rd library, always in range - GasUsed: int(proxy.GasUsed), - // #nosec G701 - copied file from 3rd library, always in range - Timestamp: int(proxy.Timestamp), - Uncles: proxy.Uncles, - } - - block.Transactions = make([]Transaction, len(proxy.Transactions)) - for i := range proxy.Transactions { - block.Transactions[i] = Transaction{ - Hash: proxy.Transactions[i], - } - } - - return block -} - -type proxyTransaction struct { - Hash string `json:"hash"` - Nonce hexInt `json:"nonce"` - BlockHash string `json:"blockHash"` - BlockNumber *hexInt `json:"blockNumber"` - TransactionIndex *hexInt `json:"transactionIndex"` - From string `json:"from"` - To string `json:"to"` - Value hexBig `json:"value"` - Gas hexInt `json:"gas"` - GasPrice hexBig `json:"gasPrice"` - Input string `json:"input"` -} - -func (proxy *proxyBlockWithTransactions) toBlock() Block { - // #nosec G103 - copied file from 3rd library, should be safe enough - return *(*Block)(unsafe.Pointer(proxy)) -} diff --git a/zetaclient/evm_client.go b/zetaclient/evm_client.go index f745b713d5..8d5b78655e 100644 --- a/zetaclient/evm_client.go +++ b/zetaclient/evm_client.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rlp" lru "github.com/hashicorp/golang-lru" + "github.com/onrik/ethrpc" "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -33,7 +34,6 @@ import ( "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" - "github.com/zeta-chain/zetacore/zetaclient/ethrpc" metricsPkg "github.com/zeta-chain/zetacore/zetaclient/metrics" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" "gorm.io/driver/sqlite" @@ -73,7 +73,7 @@ type EVMChainClient struct { *ChainMetrics chain common.Chain evmClient EVMRPCClient - rpcClient *ethrpc.EthRPC // a fallback rpc client + evmClientAlternate *ethrpc.EthRPC // a fallback rpc client KlaytnClient KlaytnRPCClient zetaClient ZetaCoreBridger Tss TSSSigner @@ -96,8 +96,9 @@ type EVMChainClient struct { params observertypes.ChainParams ts *TelemetryServer - BlockCache *lru.Cache - HeaderCache *lru.Cache + blockCache *lru.Cache + blockCacheV3 *lru.Cache // blockCacheV3 caches blocks containing type-3 (BlobTxType) transactions + headerCache *lru.Cache } var _ ChainClient = (*EVMChainClient)(nil) @@ -151,15 +152,20 @@ func NewEVMChainClient( return nil, err } ob.evmClient = client - ob.rpcClient = ethrpc.NewEthRPC(evmCfg.Endpoint) + ob.evmClientAlternate = ethrpc.NewEthRPC(evmCfg.Endpoint) // create block header and block caches - ob.BlockCache, err = lru.New(1000) + ob.blockCache, err = lru.New(1000) if err != nil { ob.logger.ChainLogger.Error().Err(err).Msg("failed to create block cache") return nil, err } - ob.HeaderCache, err = lru.New(1000) + ob.blockCacheV3, err = lru.New(1000) + if err != nil { + ob.logger.ChainLogger.Error().Err(err).Msg("failed to create block cache v3") + return nil, err + } + ob.headerCache, err = lru.New(1000) if err != nil { ob.logger.ChainLogger.Error().Err(err).Msg("failed to create header cache") return nil, err @@ -1221,7 +1227,7 @@ func (ob *EVMChainClient) observeTssRecvd(startBlock, toBlock uint64, flags obse } return bn - 1 // we have to re-scan this block next time } - if ok := ob.processIntxToTss(tx, blockRPC.Number, ethcommon.HexToHash(blockRPC.Hash)); !ok { + if ok := ob.processIntxToTss(tx, bn, ethcommon.HexToHash(blockRPC.Hash)); !ok { return bn - 1 // we have to re-scan this block next time } } @@ -1477,37 +1483,44 @@ func (ob *EVMChainClient) GetTxID(nonce uint64) string { } func (ob *EVMChainClient) GetBlockHeaderCached(blockNumber uint64) (*ethtypes.Header, error) { - if header, ok := ob.HeaderCache.Get(blockNumber); ok { + if header, ok := ob.headerCache.Get(blockNumber); ok { return header.(*ethtypes.Header), nil } header, err := ob.evmClient.HeaderByNumber(context.Background(), new(big.Int).SetUint64(blockNumber)) if err != nil { return nil, err } - ob.HeaderCache.Add(blockNumber, header) + ob.headerCache.Add(blockNumber, header) return header, nil } // GetBlockByNumberCached get block by number from cache // returns block, ethrpc.Block, isFallback, isSkip, error func (ob *EVMChainClient) GetBlockByNumberCached(blockNumber uint64) (*ethtypes.Block, *ethrpc.Block, bool, bool, error) { - if block, ok := ob.BlockCache.Get(blockNumber); ok { + if block, ok := ob.blockCache.Get(blockNumber); ok { return block.(*ethtypes.Block), nil, false, false, nil } + if block, ok := ob.blockCacheV3.Get(blockNumber); ok { + return nil, block.(*ethrpc.Block), true, false, nil + } block, err := ob.evmClient.BlockByNumber(context.Background(), new(big.Int).SetUint64(blockNumber)) if err != nil { if strings.Contains(err.Error(), "block header indicates no transactions") { return nil, nil, false, true, err // it's ok skip empty block } else if strings.Contains(err.Error(), "transaction type not supported") { - rpcBlock, err := ob.rpcClient.EthGetBlockByNumber(blockNumber, true) + if blockNumber > math.MaxInt32 { + return nil, nil, true, false, fmt.Errorf("block number %d is too large", blockNumber) + } + // #nosec G701 always in range, checked above + rpcBlock, err := ob.evmClientAlternate.EthGetBlockByNumber(int(blockNumber), true) if err != nil { return nil, nil, true, false, err // fall back on ethRPC but still fail } + ob.blockCacheV3.Add(blockNumber, rpcBlock) return nil, rpcBlock, true, false, nil // fall back on ethRPC without error } return nil, nil, false, false, err } - ob.BlockCache.Add(blockNumber, block) - ob.BlockCache.Add(block.Hash(), block) + ob.blockCache.Add(blockNumber, block) return block, nil, false, false, nil } diff --git a/zetaclient/tss_signer.go b/zetaclient/tss_signer.go index d1c10e31de..2bb59a5550 100644 --- a/zetaclient/tss_signer.go +++ b/zetaclient/tss_signer.go @@ -32,6 +32,10 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/metrics" ) +const ( + envFlagPostBlame = "POST_BLAME" +) + type TSSKey struct { PubkeyInBytes []byte // FIXME: compressed pubkey? PubkeyInBech32 string // FIXME: same above @@ -183,7 +187,7 @@ func (tss *TSS) Pubkey() []byte { // Sign signs a digest // digest should be Hashes of some data // NOTE: Specify optionalPubkey to use a different pubkey than the current pubkey set during keygen -func (tss *TSS) Sign(digest []byte, height uint64, _ uint64, _ *common.Chain, optionalPubKey string) ([65]byte, error) { +func (tss *TSS) Sign(digest []byte, height uint64, nonce uint64, chain *common.Chain, optionalPubKey string) ([65]byte, error) { H := digest log.Debug().Msgf("hash of digest is %s", H) @@ -200,16 +204,17 @@ func (tss *TSS) Sign(digest []byte, height uint64, _ uint64, _ *common.Chain, op if ksRes.Status == thorcommon.Fail { log.Warn().Msgf("keysign status FAIL posting blame to core, blaming node(s): %#v", ksRes.Blame.BlameNodes) - //digest := hex.EncodeToString(digest) - //index := observertypes.GetBlameIndex(chain.ChainId, nonce, digest, height) - // - //zetaHash, err := tss.CoreBridge.PostBlameData(&ksRes.Blame, chain.ChainId, index) - //if err != nil { - // log.Error().Err(err).Msg("error sending blame data to core") - // return [65]byte{}, err - //} - - //log.Info().Msgf("keysign posted blame data tx hash: %s", zetaHash) + // post blame data if enabled + if IsEnvFlagEnabled(envFlagPostBlame) { + digest := hex.EncodeToString(digest) + index := observertypes.GetBlameIndex(chain.ChainId, nonce, digest, height) + zetaHash, err := tss.CoreBridge.PostBlameData(&ksRes.Blame, chain.ChainId, index) + if err != nil { + log.Error().Err(err).Msg("error sending blame data to core") + return [65]byte{}, err + } + log.Info().Msgf("keysign posted blame data tx hash: %s", zetaHash) + } // Increment Blame counter for _, node := range ksRes.Blame.BlameNodes { @@ -257,7 +262,7 @@ func (tss *TSS) Sign(digest []byte, height uint64, _ uint64, _ *common.Chain, op // SignBatch is hash of some data // digest should be batch of hashes of some data -func (tss *TSS) SignBatch(digests [][]byte, height uint64, _ uint64, _ *common.Chain) ([][65]byte, error) { +func (tss *TSS) SignBatch(digests [][]byte, height uint64, nonce uint64, chain *common.Chain) ([][65]byte, error) { tssPubkey := tss.CurrentPubkey digestBase64 := make([]string, len(digests)) for i, digest := range digests { @@ -273,16 +278,18 @@ func (tss *TSS) SignBatch(digests [][]byte, height uint64, _ uint64, _ *common.C if ksRes.Status == thorcommon.Fail { log.Warn().Msg("keysign status FAIL posting blame to core") - //digest := combineDigests(digestBase64) - //index := observertypes.GetBlameIndex(chain.ChainId, nonce, hex.EncodeToString(digest), height) - //zetaHash, err := tss.CoreBridge.PostBlameData(&ksRes.Blame, chain.ChainId, index) - //if err != nil { - // log.Error().Err(err).Msg("error sending blame data to core") - // return [][65]byte{}, err - //} - // - //log.Info().Msgf("keysign posted blame data tx hash: %s", zetaHash) + // post blame data if enabled + if IsEnvFlagEnabled(envFlagPostBlame) { + digest := combineDigests(digestBase64) + index := observertypes.GetBlameIndex(chain.ChainId, nonce, hex.EncodeToString(digest), height) + zetaHash, err := tss.CoreBridge.PostBlameData(&ksRes.Blame, chain.ChainId, index) + if err != nil { + log.Error().Err(err).Msg("error sending blame data to core") + return [][65]byte{}, err + } + log.Info().Msgf("keysign posted blame data tx hash: %s", zetaHash) + } // Increment Blame counter for _, node := range ksRes.Blame.BlameNodes { @@ -580,7 +587,7 @@ func verifySignature(tssPubkey string, signature []keysign.Signature, H []byte) return bytes.Equal(pubkey.Bytes(), compressedPubkey) } -func CombineDigests(digestList []string) []byte { +func combineDigests(digestList []string) []byte { digestConcat := strings.Join(digestList[:], "") digestBytes := chainhash.DoubleHashH([]byte(digestConcat)) return digestBytes.CloneBytes() diff --git a/zetaclient/utils.go b/zetaclient/utils.go index 5a0fcd9ab2..9fa5eab264 100644 --- a/zetaclient/utils.go +++ b/zetaclient/utils.go @@ -9,6 +9,7 @@ import ( "fmt" "math" "math/big" + "os" "strings" "time" @@ -54,6 +55,11 @@ func init() { BtcDepositorFeeMin = DepositorFee(5) // 0.00000745 (5 * 149B / 100000000), the minimum deposit fee in BTC for 5 sat/byte } +func IsEnvFlagEnabled(flag string) bool { + value := os.Getenv(flag) + return value == "true" || value == "1" +} + func PrettyPrintStruct(val interface{}) (string, error) { prettyStruct, err := json.MarshalIndent( val,