diff --git a/changelog.md b/changelog.md index 21fd879a02..4042d50755 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # CHANGELOG +## v19.3.0 + +### Refactor + +* [2838](https://github.com/zeta-chain/node/pull/2838) - disable Bitcoin witness support for mainnet + +### Fixes + +* [2837](https://github.com/zeta-chain/node/pull/2837) - prevent division by 0 in ScheduleCctxEVM + ## V19.2.1 ### Features diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index 546d23277f..6d1b17dc75 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -265,12 +265,12 @@ func (ob *Observer) CheckReceiptForBtcTxHash(ctx context.Context, txHash string, return "", err } - // #nosec G115 always positive - event, err := GetBtcEventWithWitness( + // read the events + event, err := GetBtcEvent( ob.btcClient, *tx, tss, - uint64(blockVb.Height), + uint64(blockVb.Height), // #nosec G115 always positive ob.logger.Inbound, ob.netParams, depositorFee, @@ -328,7 +328,7 @@ func FilterAndParseIncomingTx( continue // the first tx is coinbase; we do not process coinbase tx } - inbound, err := GetBtcEventWithWitness(rpcClient, tx, tssAddress, blockNumber, logger, netParams, depositorFee) + inbound, err := GetBtcEvent(rpcClient, tx, tssAddress, blockNumber, logger, netParams, depositorFee) if err != nil { // unable to parse the tx, the caller should retry return nil, errors.Wrapf(err, "error getting btc event for tx %s in block %d", tx.Txid, blockNumber) @@ -390,10 +390,28 @@ func (ob *Observer) DoesInboundContainsRestrictedAddress(inTx *BTCInboundEvent) return false } -// GetBtcEvent either returns a valid BTCInboundEvent or nil +// GetBtcEvent returns a valid BTCInboundEvent or nil +// it uses witness data to extract the sender address, except for mainnet +func GetBtcEvent( + rpcClient interfaces.BTCRPCClient, + tx btcjson.TxRawResult, + tssAddress string, + blockNumber uint64, + logger zerolog.Logger, + netParams *chaincfg.Params, + depositorFee float64, +) (*BTCInboundEvent, error) { + if netParams.Name == chaincfg.MainNetParams.Name { + return GetBtcEventWithoutWitness(rpcClient, tx, tssAddress, blockNumber, logger, netParams, depositorFee) + } + return GetBtcEventWithWitness(rpcClient, tx, tssAddress, blockNumber, logger, netParams, depositorFee) +} + +// GetBtcEventWithoutWitness either returns a valid BTCInboundEvent or nil +// This is the legacy version of GetBtcEvent that does not support witness data // Note: the caller should retry the tx on error (e.g., GetSenderAddressByVin failed) // TODO(revamp): simplify this function -func GetBtcEvent( +func GetBtcEventWithoutWitness( rpcClient interfaces.BTCRPCClient, tx btcjson.TxRawResult, tssAddress string, @@ -437,7 +455,7 @@ func GetBtcEvent( // deposit amount has to be no less than the minimum depositor fee if vout0.Value < depositorFee { logger.Info(). - Msgf("GetBtcEvent: btc deposit amount %v in txid %s is less than depositor fee %v", vout0.Value, tx.Txid, depositorFee) + Msgf("GetBtcEventWithoutWitness: btc deposit amount %v in txid %s is less than depositor fee %v", vout0.Value, tx.Txid, depositorFee) return nil, nil } value = vout0.Value - depositorFee @@ -446,7 +464,9 @@ func GetBtcEvent( vout1 := tx.Vout[1] memo, found, err = bitcoin.DecodeOpReturnMemo(vout1.ScriptPubKey.Hex, tx.Txid) if err != nil { - logger.Error().Err(err).Msgf("GetBtcEvent: error decoding OP_RETURN memo: %s", vout1.ScriptPubKey.Hex) + logger.Error(). + Err(err). + Msgf("GetBtcEventWithoutWitness: error decoding OP_RETURN memo: %s", vout1.ScriptPubKey.Hex) return nil, nil } } @@ -454,7 +474,7 @@ func GetBtcEvent( // event found, get sender address if found { if len(tx.Vin) == 0 { // should never happen - return nil, fmt.Errorf("GetBtcEvent: no input found for inbound: %s", tx.Txid) + return nil, fmt.Errorf("GetBtcEventWithoutWitness: no input found for inbound: %s", tx.Txid) } fromAddress, err := GetSenderAddressByVin(rpcClient, tx.Vin[0], netParams) diff --git a/zetaclient/chains/bitcoin/observer/inbound_test.go b/zetaclient/chains/bitcoin/observer/inbound_test.go index ec121bd9af..2fe57c3334 100644 --- a/zetaclient/chains/bitcoin/observer/inbound_test.go +++ b/zetaclient/chains/bitcoin/observer/inbound_test.go @@ -312,13 +312,13 @@ func TestGetSenderAddressByVinErrors(t *testing.T) { }) } -func TestGetBtcEvent(t *testing.T) { +func TestGetBtcEventWithoutWitness(t *testing.T) { // load archived inbound P2WPKH raw result // https://mempool.space/tx/847139aa65aa4a5ee896375951cbf7417cfc8a4d6f277ec11f40cd87319f04aa txHash := "847139aa65aa4a5ee896375951cbf7417cfc8a4d6f277ec11f40cd87319f04aa" chain := chains.BitcoinMainnet - // GetBtcEvent arguments + // GetBtcEventWithoutWitness arguments tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash, false) tssAddress := testutils.TSSAddressBTCMainnet blockNumber := uint64(835640) @@ -348,7 +348,15 @@ func TestGetBtcEvent(t *testing.T) { rpcClient := createRPCClientAndLoadTx(t, chain.ChainId, preHash) // get BTC event - event, err := observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + event, err := observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) require.NoError(t, err) require.Equal(t, eventExpected, event) }) @@ -363,7 +371,15 @@ func TestGetBtcEvent(t *testing.T) { rpcClient := createRPCClientAndLoadTx(t, chain.ChainId, preHash) // get BTC event - event, err := observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + event, err := observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) require.NoError(t, err) require.Equal(t, eventExpected, event) }) @@ -378,7 +394,15 @@ func TestGetBtcEvent(t *testing.T) { rpcClient := createRPCClientAndLoadTx(t, chain.ChainId, preHash) // get BTC event - event, err := observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + event, err := observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) require.NoError(t, err) require.Equal(t, eventExpected, event) }) @@ -393,7 +417,15 @@ func TestGetBtcEvent(t *testing.T) { rpcClient := createRPCClientAndLoadTx(t, chain.ChainId, preHash) // get BTC event - event, err := observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + event, err := observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) require.NoError(t, err) require.Equal(t, eventExpected, event) }) @@ -408,7 +440,15 @@ func TestGetBtcEvent(t *testing.T) { rpcClient := createRPCClientAndLoadTx(t, chain.ChainId, preHash) // get BTC event - event, err := observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + event, err := observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) require.NoError(t, err) require.Equal(t, eventExpected, event) }) @@ -419,7 +459,15 @@ func TestGetBtcEvent(t *testing.T) { // get BTC event rpcClient := mocks.NewMockBTCRPCClient() - event, err := observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + event, err := observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) require.NoError(t, err) require.Nil(t, event) }) @@ -430,13 +478,29 @@ func TestGetBtcEvent(t *testing.T) { // modify the tx to have Vout[0] a P2SH output tx.Vout[0].ScriptPubKey.Hex = strings.Replace(tx.Vout[0].ScriptPubKey.Hex, "0014", "a914", 1) - event, err := observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + event, err := observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) require.NoError(t, err) require.Nil(t, event) // append 1 byte to script to make it longer than 22 bytes tx.Vout[0].ScriptPubKey.Hex = tx.Vout[0].ScriptPubKey.Hex + "00" - event, err = observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + event, err = observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) require.NoError(t, err) require.Nil(t, event) }) @@ -447,7 +511,15 @@ func TestGetBtcEvent(t *testing.T) { // get BTC event rpcClient := mocks.NewMockBTCRPCClient() - event, err := observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + event, err := observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) require.NoError(t, err) require.Nil(t, event) }) @@ -458,7 +530,15 @@ func TestGetBtcEvent(t *testing.T) { // get BTC event rpcClient := mocks.NewMockBTCRPCClient() - event, err := observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + event, err := observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) require.NoError(t, err) require.Nil(t, event) }) @@ -469,7 +549,15 @@ func TestGetBtcEvent(t *testing.T) { // get BTC event rpcClient := mocks.NewMockBTCRPCClient() - event, err := observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + event, err := observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) require.NoError(t, err) require.Nil(t, event) }) @@ -480,7 +568,15 @@ func TestGetBtcEvent(t *testing.T) { // get BTC event rpcClient := mocks.NewMockBTCRPCClient() - event, err := observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + event, err := observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) require.NoError(t, err) require.Nil(t, event) }) @@ -503,7 +599,15 @@ func TestGetBtcEventErrors(t *testing.T) { // get BTC event rpcClient := mocks.NewMockBTCRPCClient() - event, err := observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + event, err := observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) require.Error(t, err) require.Nil(t, event) }) @@ -514,7 +618,15 @@ func TestGetBtcEventErrors(t *testing.T) { // get BTC event rpcClient := mocks.NewMockBTCRPCClient() - event, err := observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + event, err := observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) require.Error(t, err) require.Nil(t, event) }) @@ -524,8 +636,89 @@ func TestGetBtcEventErrors(t *testing.T) { rpcClient := mocks.NewMockBTCRPCClient() // get BTC event - event, err := observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + event, err := observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) require.Error(t, err) require.Nil(t, event) }) } + +func TestGetBtcEvent(t *testing.T) { + t.Run("should not decode inbound event with witness with mainnet chain", func(t *testing.T) { + // load archived inbound P2WPKH raw result + // https://mempool.space/tx/847139aa65aa4a5ee896375951cbf7417cfc8a4d6f277ec11f40cd87319f04aa + chain := chains.BitcoinMainnet + + tssAddress := testutils.TSSAddressBTCMainnet + blockNumber := uint64(835640) + net := &chaincfg.MainNetParams + // 2.992e-05, see avgFeeRate https://mempool.space/api/v1/blocks/835640 + depositorFee := bitcoin.DepositorFee(22 * clientcommon.BTCOutboundGasPriceMultiplier) + + txHash2 := "37777defed8717c581b4c0509329550e344bdc14ac38f71fc050096887e535c8" + tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash2, false) + + preHash := "c5d224963832fc0b9a597251c2342a17b25e481a88cc9119008e8f8296652697" + rpcClient := createRPCClientAndLoadTx(t, chain.ChainId, preHash) + + // get BTC event + event, err := observer.GetBtcEvent( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) + require.NoError(t, err) + require.Equal(t, (*observer.BTCInboundEvent)(nil), event) + }) + + t.Run("should support legacy BTC inbound event parsing for mainnet", func(t *testing.T) { + // load archived inbound P2WPKH raw result + // https://mempool.space/tx/847139aa65aa4a5ee896375951cbf7417cfc8a4d6f277ec11f40cd87319f04aa + txHash := "847139aa65aa4a5ee896375951cbf7417cfc8a4d6f277ec11f40cd87319f04aa" + chain := chains.BitcoinMainnet + + // GetBtcEventWithoutWitness arguments + tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash, false) + tssAddress := testutils.TSSAddressBTCMainnet + blockNumber := uint64(835640) + net := &chaincfg.MainNetParams + // 2.992e-05, see avgFeeRate https://mempool.space/api/v1/blocks/835640 + depositorFee := bitcoin.DepositorFee(22 * clientcommon.BTCOutboundGasPriceMultiplier) + + // expected result + memo, err := hex.DecodeString(tx.Vout[1].ScriptPubKey.Hex[4:]) + require.NoError(t, err) + eventExpected := &observer.BTCInboundEvent{ + FromAddress: "bc1q68kxnq52ahz5vd6c8czevsawu0ux9nfrzzrh6e", + ToAddress: tssAddress, + Value: tx.Vout[0].Value - depositorFee, // 7008 sataoshis + MemoBytes: memo, + BlockNumber: blockNumber, + TxHash: tx.Txid, + } + + // https://mempool.space/tx/c5d224963832fc0b9a597251c2342a17b25e481a88cc9119008e8f8296652697 + preHash := "c5d224963832fc0b9a597251c2342a17b25e481a88cc9119008e8f8296652697" + tx.Vin[0].Txid = preHash + tx.Vin[0].Vout = 2 + eventExpected.FromAddress = "bc1q68kxnq52ahz5vd6c8czevsawu0ux9nfrzzrh6e" + // load previous raw tx so so mock rpc client can return it + rpcClient := createRPCClientAndLoadTx(t, chain.ChainId, preHash) + + // get BTC event + event, err := observer.GetBtcEvent(rpcClient, *tx, tssAddress, blockNumber, log.Logger, net, depositorFee) + require.NoError(t, err) + require.Equal(t, eventExpected, event) + }) +} diff --git a/zetaclient/chains/bitcoin/observer/witness_test.go b/zetaclient/chains/bitcoin/observer/witness_test.go index 4e93fb5cf1..3dbcb330e8 100644 --- a/zetaclient/chains/bitcoin/observer/witness_test.go +++ b/zetaclient/chains/bitcoin/observer/witness_test.go @@ -45,7 +45,7 @@ func TestParseScriptFromWitness(t *testing.T) { }) } -func TestGetBtcEventFromInscription(t *testing.T) { +func TestGetBtcEventWithWitness(t *testing.T) { // load archived inbound P2WPKH raw result // https://mempool.space/tx/847139aa65aa4a5ee896375951cbf7417cfc8a4d6f277ec11f40cd87319f04aa txHash := "847139aa65aa4a5ee896375951cbf7417cfc8a4d6f277ec11f40cd87319f04aa" diff --git a/zetaclient/chains/solana/observer/inbound.go b/zetaclient/chains/solana/observer/inbound.go index 3a905e83e9..ed44e89f75 100644 --- a/zetaclient/chains/solana/observer/inbound.go +++ b/zetaclient/chains/solana/observer/inbound.go @@ -288,7 +288,9 @@ func (ob *Observer) ParseInboundAsDeposit( // get the sender address (skip if unable to parse signer address) sender, err := ob.GetSignerDeposit(tx, &instruction) if err != nil { - ob.Logger().Inbound.Err(err).Msgf("unable to get signer for sig %s instruction %d", tx.Signatures[0], instructionIndex) + ob.Logger(). + Inbound.Err(err). + Msgf("unable to get signer for sig %s instruction %d", tx.Signatures[0], instructionIndex) return nil, nil }