watcher_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. package evm
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "testing"
  7. "github.com/certusone/wormhole/node/pkg/common"
  8. "github.com/certusone/wormhole/node/pkg/txverifier"
  9. "github.com/certusone/wormhole/node/pkg/watchers/evm/connectors"
  10. "github.com/certusone/wormhole/node/pkg/watchers/evm/connectors/ethabi"
  11. ethereum "github.com/ethereum/go-ethereum"
  12. eth_common "github.com/ethereum/go-ethereum/common"
  13. "github.com/ethereum/go-ethereum/core/types"
  14. "github.com/ethereum/go-ethereum/ethclient"
  15. "github.com/stretchr/testify/assert"
  16. "github.com/stretchr/testify/require"
  17. "github.com/wormhole-foundation/wormhole/sdk/vaa"
  18. "go.uber.org/zap"
  19. )
  20. func TestMsgIdFromLogEvent(t *testing.T) {
  21. evJson := `
  22. {
  23. "Sender": "0x45c140dd2526e4bfd1c2a5bb0aa6aa1db00b1744",
  24. "Sequence": 3685,
  25. "Nonce": 0,
  26. "Payload": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJxMAwy+TX7P/UQKg5Siin3wZuTKLmUV0DFAtns2oZ5XBIkIUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWnn535GP/6Gswr9FgWgmmMr6lsBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPd52OGwfx498UGoHE8ffWXAo4YRAAAAAAAAAAAAAAAHmBsAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvVKf9zDa4Cn6hbONmNYEZyEhX6QUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC9Up/3MNrgKfqFs42Y1gRnISFfpBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsblSHFxAb/NAsujjz79eA6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBmtsmDcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA40KOnP4ABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOZ6vaDUP3rI83h2u/ANHfrbuTqqAFU9+gAAAVQAAAArG5UhxcQG/zQLLo48+/XgOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAyL8tXA1r7IB8Ie9M7y8f078WlH4AAAAAAAAAAAAAAACUnABm1c8iBqanyHJ7Dwt3ceUclgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
  27. "ConsistencyLevel": 15,
  28. "Raw": {
  29. "address": "0x4a8bc80ed5a4067f1ccf107057b8270e0cc11a78",
  30. "topics": [
  31. "0x6eb224fb001ed210e379b335e35efe88672a8ce935d981a6896b27ffdf52a3b2",
  32. "0x00000000000000000000000045c140dd2526e4bfd1c2a5bb0aa6aa1db00b1744"
  33. ],
  34. "data": "0x0000000000000000000000000000000000000000000000000000000000000e6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000393000000000000000000000000000000000000000000000000000000000000271300c32f935fb3ff5102a0e528a29f7c19b9328b9945740c502d9ecda86795c12242140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000169e7e77e463ffe86b30afd1605a09a632bea5b0140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f779d8e1b07f1e3df141a81c4f1f7d65c0a38611000000000000000000000007981b00140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bd529ff730dae029fa85b38d98d6046721215fa4140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bd529ff730dae029fa85b38d98d6046721215fa41400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002b1b9521c5c406ff340b2e8e3cfbf5e03a000000000000000000000000000000000000000000000000000007066b6c983700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038d0a3a73f800140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e67abda0d43f7ac8f37876bbf00d1dfadbb93aaa00553dfa000001540000002b1b9521c5c406ff340b2e8e3cfbf5e03a0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c8bf2d5c0d6bec807c21ef4cef2f1fd3bf16947e000000000000000000000000949c0066d5cf2206a6a7c8727b0f0b7771e51c96000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  35. "blockNumber": "0x553dfa",
  36. "transactionHash": "0xb198a854efdae67684cd840795ddcadeabdfdba83bb1cbf14a3f2debac1fd1f6",
  37. "transactionIndex": "0x78",
  38. "blockHash": "0xfd4e19ca93de700470f2e6cdbd6fb67ba9e3e1508bd23289bc4f795ac641c375",
  39. "logIndex": "0x4d",
  40. "removed": false
  41. }
  42. }`
  43. var ev ethabi.AbiLogMessagePublished
  44. err := json.Unmarshal([]byte(evJson), &ev)
  45. require.NoError(t, err)
  46. msgId := msgIdFromLogEvent(vaa.ChainIDSepolia, &ev)
  47. assert.Equal(t, "10002/00000000000000000000000045c140dd2526e4bfd1c2a5bb0aa6aa1db00b1744/3685", msgId)
  48. }
  49. func Test_canRetryGetBlockTime(t *testing.T) {
  50. assert.True(t, canRetryGetBlockTime(ethereum.NotFound))
  51. assert.True(t, canRetryGetBlockTime(errors.New("not found")))
  52. assert.True(t, canRetryGetBlockTime(errors.New("Unknown block")))
  53. assert.True(t, canRetryGetBlockTime(errors.New("cannot query unfinalized data")))
  54. assert.False(t, canRetryGetBlockTime(errors.New("Hello, World!")))
  55. }
  56. // TestVerifyAndPublish checks the operation of the verifyAndPublish method of the watcher in
  57. // scenarios where the Transfer Verifier is disabled and when it's enabled. It covers much of
  58. // the behaviour of the verify() function.
  59. func TestVerifyAndPublish(t *testing.T) {
  60. msgC := make(chan *common.MessagePublication, 1)
  61. w := NewWatcherForTest(t, msgC)
  62. // Contents of the message don't matter for the sake of these tests.
  63. msg := common.MessagePublication{}
  64. ctx := context.TODO()
  65. // Check preconditions for the Transfer Verifier disabled case.
  66. require.Equal(t, 0, len(w.msgC))
  67. require.Equal(t, common.NotVerified.String(), msg.VerificationState().String())
  68. require.Nil(t, w.txVerifier)
  69. // Check nil message
  70. err := w.verifyAndPublish(nil, ctx, eth_common.Hash{}, &types.Receipt{})
  71. require.ErrorContains(t, err, "message publication cannot be nil")
  72. require.Equal(t, common.NotVerified.String(), msg.VerificationState().String())
  73. // Check transfer verifier not enabled case. The message should be published normally.
  74. msg = common.MessagePublication{}
  75. require.Nil(t, w.txVerifier)
  76. err = w.verifyAndPublish(&msg, ctx, eth_common.Hash{}, &types.Receipt{})
  77. require.NoError(t, err)
  78. require.Equal(t, 1, len(msgC))
  79. publishedMsg := <-msgC
  80. require.NotNil(t, publishedMsg)
  81. require.Equal(t, 0, len(msgC))
  82. require.Equal(t, common.NotVerified.String(), publishedMsg.VerificationState().String())
  83. tbAddr, byteErr := vaa.BytesToAddress([]byte{0x01})
  84. require.NoError(t, byteErr)
  85. // Check scenario where transfer verifier is enabled on the watcher level but
  86. // there is no Transfer Verifier instantiated. In this case, fail open and continue
  87. // to process messages. This shouldn't be possible in practice as the constructor
  88. // should return an error on startup if the Transfer Verifier can't be instantiated
  89. // when txVerifierEnabled is true.
  90. w.txVerifierEnabled = true
  91. msg = common.MessagePublication{}
  92. require.Nil(t, w.txVerifier)
  93. err = w.verifyAndPublish(&msg, ctx, eth_common.Hash{}, &types.Receipt{})
  94. require.NoError(t, err)
  95. require.Equal(t, 1, len(msgC))
  96. publishedMsg = <-msgC
  97. require.Equal(t, common.NotVerified.String(), publishedMsg.VerificationState().String())
  98. // Check that message status is not changed if it didn't come from token bridge.
  99. // The NotVerified status is used when Transfer Verification is not enabled.
  100. msg = common.MessagePublication{}
  101. require.Nil(t, w.txVerifier)
  102. err = w.verifyAndPublish(&msg, ctx, eth_common.Hash{}, &types.Receipt{})
  103. require.Nil(t, err)
  104. require.Equal(t, 1, len(msgC))
  105. publishedMsg = <-msgC
  106. require.Equal(t, common.NotVerified.String(), publishedMsg.VerificationState().String())
  107. // Check scenario where the message already has a verification status.
  108. failMock := &MockTransferVerifier[ethclient.Client, connectors.Connector]{false}
  109. w.txVerifier = failMock
  110. msg = common.MessagePublication{}
  111. setErr := msg.SetVerificationState(common.Anomalous)
  112. require.NoError(t, setErr)
  113. require.NotNil(t, w.txVerifier)
  114. err = w.verifyAndPublish(&msg, ctx, eth_common.Hash{}, &types.Receipt{})
  115. require.ErrorContains(t, err, "MessagePublication already has a non-default verification state")
  116. require.Equal(t, 0, len(msgC))
  117. require.Equal(t, common.Anomalous.String(), msg.VerificationState().String())
  118. // Check case where Transfer Verifier finds a dangerous transaction. Note that this case does
  119. // not return an error, but the published message should be marked as Rejected.
  120. failMock = &MockTransferVerifier[ethclient.Client, connectors.Connector]{false}
  121. w.txVerifier = failMock
  122. require.NotNil(t, w.txVerifier)
  123. msg = common.MessagePublication{
  124. EmitterAddress: tbAddr,
  125. }
  126. err = w.verifyAndPublish(&msg, ctx, eth_common.Hash{}, &types.Receipt{})
  127. require.Nil(t, err)
  128. require.Equal(t, 1, len(msgC))
  129. publishedMsg = <-msgC
  130. require.NotNil(t, publishedMsg)
  131. require.Equal(t, 0, len(msgC))
  132. require.Equal(t, common.Rejected.String(), publishedMsg.VerificationState().String())
  133. // Check that message status is not changed if it didn't come from token bridge.
  134. // The NotApplicable status is used when Transfer Verification is enabled.
  135. msg = common.MessagePublication{}
  136. require.NotNil(t, w.txVerifier)
  137. err = w.verifyAndPublish(&msg, ctx, eth_common.Hash{}, &types.Receipt{})
  138. require.Nil(t, err)
  139. require.Equal(t, 1, len(msgC))
  140. publishedMsg = <-msgC
  141. require.Equal(t, common.NotApplicable.String(), publishedMsg.VerificationState().String())
  142. // Check happy path where txverifier is enabled, initialized, and the message is from the token bridge.
  143. successMock := &MockTransferVerifier[ethclient.Client, connectors.Connector]{true}
  144. w.txVerifier = successMock
  145. require.NotNil(t, w.txVerifier)
  146. msg = common.MessagePublication{
  147. EmitterAddress: tbAddr,
  148. }
  149. err = w.verifyAndPublish(&msg, ctx, eth_common.Hash{}, &types.Receipt{})
  150. require.NoError(t, err)
  151. require.Equal(t, 1, len(msgC))
  152. publishedMsg = <-msgC
  153. require.NotNil(t, publishedMsg)
  154. require.Equal(t, 0, len(msgC))
  155. require.Equal(t, common.Valid.String(), publishedMsg.VerificationState().String())
  156. }
  157. // Helper function to set up a test Ethereum Watcher
  158. func NewWatcherForTest(t *testing.T, msgC chan<- *common.MessagePublication) *Watcher {
  159. t.Helper()
  160. logger := zap.NewNop()
  161. w := &Watcher{
  162. // this is implicit but added here for clarity
  163. txVerifierEnabled: false,
  164. msgC: msgC,
  165. logger: logger,
  166. }
  167. return w
  168. }
  169. type MockTransferVerifier[E ethclient.Client, C connectors.Connector] struct {
  170. success bool
  171. }
  172. // TransferIsValid simulates the evaluation made by the Transfer Verifier.
  173. // Always returns nil. The error should be non-nil only when a parsing or RPC error occurs.
  174. // For now, these are not included in the unit tests.
  175. func (m *MockTransferVerifier[E, C]) TransferIsValid(_ context.Context, _ eth_common.Hash, _ *types.Receipt) (bool, error) {
  176. return m.success, nil
  177. }
  178. func (m *MockTransferVerifier[E, C]) Addrs() *txverifier.TVAddresses {
  179. return &txverifier.TVAddresses{
  180. TokenBridgeAddr: eth_common.BytesToAddress([]byte{0x01}),
  181. }
  182. }
  183. func TestConsistencyLevelMatches(t *testing.T) {
  184. // Success cases.
  185. assert.True(t, consistencyLevelMatches(vaa.ConsistencyLevelPublishImmediately, vaa.ConsistencyLevelPublishImmediately))
  186. assert.True(t, consistencyLevelMatches(vaa.ConsistencyLevelSafe, vaa.ConsistencyLevelSafe))
  187. assert.True(t, consistencyLevelMatches(vaa.ConsistencyLevelFinalized, vaa.ConsistencyLevelFinalized))
  188. assert.True(t, consistencyLevelMatches(vaa.ConsistencyLevelFinalized, 0))
  189. assert.True(t, consistencyLevelMatches(vaa.ConsistencyLevelFinalized, 42))
  190. // Failure cases.
  191. assert.False(t, consistencyLevelMatches(vaa.ConsistencyLevelPublishImmediately, vaa.ConsistencyLevelSafe))
  192. assert.False(t, consistencyLevelMatches(vaa.ConsistencyLevelSafe, vaa.ConsistencyLevelFinalized))
  193. assert.False(t, consistencyLevelMatches(vaa.ConsistencyLevelFinalized, vaa.ConsistencyLevelPublishImmediately))
  194. assert.False(t, consistencyLevelMatches(vaa.ConsistencyLevelPublishImmediately, 0))
  195. assert.False(t, consistencyLevelMatches(vaa.ConsistencyLevelSafe, 0))
  196. }