@@ -22,13 +22,18 @@ import (
2222 "github.com/certusone/wormhole/node/pkg/supervisor"
2323 "github.com/mr-tron/base58"
2424 "github.com/stretchr/testify/require"
25+ "github.com/wormhole-foundation/wormhole/sdk/vaa"
2526 "go.uber.org/zap"
2627)
2728
2829// TestLiveSuiWatcher runs the real Sui watcher against a live gRPC endpoint (mainnet by
2930// default) and, for every observed Wormhole message, cross-checks the gRPC/BCS-decoded fields
30- // against the node's own JSON-RPC `parsedJson` rendering of the same event. A field mismatch
31- // fails the test. It is only compiled with the `integration` build tag.
31+ // against the node's own JSON-RPC `parsedJson` rendering of the same event. It also issues a
32+ // real re-observation request for each observed transaction and asserts that the re-observed
33+ // message has the same VAA hash as the original observation — VAAHash is what the guardian uses
34+ // to identify a message, so a reobservation that hashed differently would never reach consensus
35+ // with the original. A field mismatch or hash mismatch fails the test. It is only compiled with
36+ // the `integration` build tag.
3237//
3338// Run it (streams logs; Ctrl-C to stop early):
3439//
@@ -77,18 +82,46 @@ func TestLiveSuiWatcher(t *testing.T) {
7782
7883 observed := 0
7984 verified := 0
85+ reobserved := 0
86+ reobsVerified := 0
87+ // VAA hash of each original (non-reobservation) message, keyed by its message ID. A
88+ // re-observed message is looked up here and its hash compared against the original.
89+ originalHashes := make (map [string ]string )
8090 var wg sync.WaitGroup
8191 wg .Add (1 )
8292
8393 supervisor .New (rootCtx , logger , func (ctx context.Context ) error {
84- // Drain the message channel; log and cross-check every observation.
94+ // Drain the message channel; log and cross-check every observation. Only this goroutine
95+ // touches originalHashes / the counters, so no locking is needed.
8596 go func () {
8697 defer wg .Done ()
8798 for {
8899 select {
89100 case <- ctx .Done ():
90101 return
91102 case msg := <- msgChan :
103+ if msg .IsReobservation {
104+ reobserved ++
105+ origHash , ok := originalHashes [msg .MessageIDString ()]
106+ if ! ok {
107+ logger .Warn ("reobs: no original observation recorded for re-observed message (skipping)" ,
108+ zap .String ("msgID" , msg .MessageIDString ()),
109+ )
110+ continue
111+ }
112+ if origHash != msg .VAAHash () {
113+ t .Errorf ("REOBSERVATION HASH MISMATCH msgID=%s: original=%s reobservation=%s" ,
114+ msg .MessageIDString (), origHash , msg .VAAHash ())
115+ continue
116+ }
117+ reobsVerified ++
118+ logger .Info ("VERIFIED reobservation hash matches original" ,
119+ zap .String ("msgID" , msg .MessageIDString ()),
120+ zap .String ("vaaHash" , msg .VAAHash ()),
121+ )
122+ continue
123+ }
124+
92125 observed ++
93126 logger .Info ("OBSERVED WORMHOLE MESSAGE" ,
94127 append ([]zap.Field {
@@ -100,6 +133,21 @@ func TestLiveSuiWatcher(t *testing.T) {
100133 if verifyMessageAgainstJSONRPC (ctx , t , logger , jsonRPC , eventType , msg ) {
101134 verified ++
102135 }
136+
137+ // Record the original hash and request a real re-observation of this transaction
138+ // so we can confirm the reobservation path produces an identical message hash.
139+ originalHashes [msg .MessageIDString ()] = msg .VAAHash ()
140+ req := & gossipv1.ObservationRequest {
141+ ChainId : uint32 (vaa .ChainIDSui ),
142+ TxHash : msg .TxID ,
143+ }
144+ select {
145+ case obsvReqC <- req :
146+ default :
147+ logger .Warn ("reobs: observation request channel full, skipping reobservation" ,
148+ zap .String ("msgID" , msg .MessageIDString ()),
149+ )
150+ }
103151 }
104152 }
105153 }()
@@ -116,7 +164,12 @@ func TestLiveSuiWatcher(t *testing.T) {
116164 // Wait for the drain goroutine to finish any in-flight verification before the test returns,
117165 // so it never calls t.Errorf after the test function has returned.
118166 wg .Wait ()
119- logger .Info ("live Sui watcher finished" , zap .Int ("messagesObserved" , observed ), zap .Int ("messagesVerified" , verified ))
167+ logger .Info ("live Sui watcher finished" ,
168+ zap .Int ("messagesObserved" , observed ),
169+ zap .Int ("messagesVerified" , verified ),
170+ zap .Int ("reobservations" , reobserved ),
171+ zap .Int ("reobservationsVerified" , reobsVerified ),
172+ )
120173}
121174
122175// suiParsedJson mirrors the `parsedJson` of a WormholeMessage event as rendered by the Sui
0 commit comments