Skip to content

Commit 9b64c85

Browse files
committed
node: Extend sui watcher live test to validate reobservations
1 parent e2c8b7d commit 9b64c85

1 file changed

Lines changed: 57 additions & 4 deletions

File tree

node/pkg/watchers/sui/watcher_live_test.go

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)