diff --git a/changelog.md b/changelog.md index 9830e2acea..e06eb2aabf 100644 --- a/changelog.md +++ b/changelog.md @@ -27,6 +27,7 @@ * [4561](https://github.com/zeta-chain/node/pull/4561) - add signet to BTC client `resolveParams` to fix e2e `unknown chain params` error * [4569](https://github.com/zeta-chain/node/pull/4569) - tighten MtA proof bounds in tss-lib to fix Alpha-Rays / TSSHOCK vulnerability * [4597](https://github.com/zeta-chain/node/pull/4597) - respect `DisableTssBlockScan` in Bitcoin observer to align inbound observation behavior with EVM observer +* [4644](https://github.com/zeta-chain/node/pull/4644) - return early in Bitcoin observer `refreshPendingNonce` when the pending nonces query fails, instead of dereferencing a nil pointer ### Tests diff --git a/zetaclient/chains/bitcoin/observer/outbound.go b/zetaclient/chains/bitcoin/observer/outbound.go index 01cbe78e7e..f4d4b87db6 100644 --- a/zetaclient/chains/bitcoin/observer/outbound.go +++ b/zetaclient/chains/bitcoin/observer/outbound.go @@ -215,6 +215,7 @@ func (ob *Observer) refreshPendingNonce(ctx context.Context) { p, err := ob.ZetaRepo().GetPendingNonces(ctx) if err != nil { logger.Error().Err(err).Send() + return } // increase pending nonce if lagged behind diff --git a/zetaclient/chains/bitcoin/observer/utxos_test.go b/zetaclient/chains/bitcoin/observer/utxos_test.go index 9b796bff18..1a42abebd0 100644 --- a/zetaclient/chains/bitcoin/observer/utxos_test.go +++ b/zetaclient/chains/bitcoin/observer/utxos_test.go @@ -2,6 +2,7 @@ package observer import ( "context" + "errors" "math" "testing" "time" @@ -24,6 +25,70 @@ func Test_FetchUTXOs(t *testing.T) { require.Equal(t, len(utxos), ob.TelemetryServer().GetNumberOfUTXOs()) } +func Test_FetchUTXOsPendingNoncesError(t *testing.T) { + // create test observer + ob := newTestSuite(t, chains.BitcoinMainnet) + ob.setPendingNonce(3) + + tssAddress, err := ob.TSS().PubKey().AddressBTC(ob.Chain().ChainId) + require.NoError(t, err) + utxos := getTestUTXOs(tssAddress.EncodeAddress()) + + // zetacore fails to return the pending nonces + ob.zetacore.On("GetPendingNoncesByChain", mock.Anything, mock.Anything). + Return(observertypes.PendingNonces{}, errors.New("failed to get pending nonces")) + ob.client.On("ListUnspentMinMaxAddresses", mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(utxos, nil) + + // should neither panic nor fail, and UTXOs should still be fetched + err = ob.FetchUTXOs(context.Background()) + require.NoError(t, err) + require.Equal(t, len(utxos), ob.TelemetryServer().GetNumberOfUTXOs()) + + // pending nonce should be left untouched + require.Equal(t, uint64(3), ob.GetPendingNonce()) +} + +func Test_RefreshPendingNonce(t *testing.T) { + tests := []struct { + name string + pendingNonce uint64 + nonces observertypes.PendingNonces + err error + expectedNonce uint64 + }{ + { + name: "should increase pending nonce if lagged behind", + pendingNonce: 3, + nonces: observertypes.PendingNonces{NonceLow: 5, NonceHigh: 8}, + expectedNonce: 5, + }, + { + name: "should not decrease pending nonce", + pendingNonce: 7, + nonces: observertypes.PendingNonces{NonceLow: 5, NonceHigh: 8}, + expectedNonce: 7, + }, + { + name: "should keep pending nonce if GetPendingNoncesByChain fails", + pendingNonce: 3, + err: errors.New("failed to get pending nonces"), + expectedNonce: 3, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ob := newTestSuite(t, chains.BitcoinMainnet) + ob.setPendingNonce(tt.pendingNonce) + ob.zetacore.On("GetPendingNoncesByChain", mock.Anything, mock.Anything).Return(tt.nonces, tt.err) + + ob.refreshPendingNonce(context.Background()) + require.Equal(t, tt.expectedNonce, ob.GetPendingNonce()) + }) + } +} + func Test_SelectUTXOs(t *testing.T) { ctx := context.Background() dummyTxID := "6e6f71d281146c1fc5c755b35908ee449f26786c84e2ae18f98b268de40b7ec4"