Преглед на файлове

node/pkg/watchers/evm: fix ccl bug

Paul Noel преди 1 месец
родител
ревизия
f75c7f8884
променени са 3 файла, в които са добавени 113 реда и са изтрити 11 реда
  1. 8 6
      node/pkg/watchers/evm/custom_consistency_level.go
  2. 97 0
      node/pkg/watchers/evm/custom_consistency_level_test.go
  3. 8 5
      node/pkg/watchers/evm/watcher.go

+ 8 - 6
node/pkg/watchers/evm/custom_consistency_level.go

@@ -138,7 +138,7 @@ func (w *Watcher) cclEnable(ctx context.Context) error {
 func (w *Watcher) cclHandleMessage(parentCtx context.Context, pe *pendingMessage, emitterAddr ethCommon.Address) {
 	if !w.cclEnabled {
 		w.cclLogger.Error("received an observation with custom handling but the feature is not enabled, treating as finalized", zap.String("msgId", pe.message.MessageIDString()))
-		pe.message.ConsistencyLevel = vaa.ConsistencyLevelFinalized
+		pe.effectiveCL = vaa.ConsistencyLevelFinalized
 		return
 	}
 
@@ -153,14 +153,16 @@ func (w *Watcher) cclHandleMessage(parentCtx context.Context, pe *pendingMessage
 	r, err := w.cclReadAndParseConfig(parentCtx, emitterAddr)
 	if err != nil {
 		w.cclLogger.Error("failed to look up config for custom handling, treating as finalized", zap.String("msgId", pe.message.MessageIDString()), zap.Error(err))
-		pe.message.ConsistencyLevel = vaa.ConsistencyLevelFinalized
+		// If one guardian has an error reading the config, but others do not, they will produce different VAA hashes.
+		// To avoid that, we set the effectiveCL to finalized, but leave the message.ConsistencyLevel as Custom.
+		pe.effectiveCL = vaa.ConsistencyLevelFinalized
 		return
 	}
 
 	switch req := r.(type) {
 	case *NothingSpecial:
 		w.cclLogger.Info("received an observation with the nothing special specifier, treating as finalized", zap.String("msgId", pe.message.MessageIDString()))
-		pe.message.ConsistencyLevel = vaa.ConsistencyLevelFinalized
+		pe.effectiveCL = vaa.ConsistencyLevelFinalized
 	case *AdditionalBlocks:
 		if req.consistencyLevel != vaa.ConsistencyLevelFinalized && req.consistencyLevel != vaa.ConsistencyLevelSafe && req.consistencyLevel != vaa.ConsistencyLevelPublishImmediately {
 			w.cclLogger.Error("received an observation with an additional blocks specifier but the configured consistency level is invalid, treating as finalized",
@@ -168,7 +170,7 @@ func (w *Watcher) cclHandleMessage(parentCtx context.Context, pe *pendingMessage
 				zap.Uint8("consistencyLevel", req.consistencyLevel),
 				zap.Uint16("additionalBlocks", req.additionalBlocks),
 			)
-			pe.message.ConsistencyLevel = vaa.ConsistencyLevelFinalized
+			pe.effectiveCL = vaa.ConsistencyLevelFinalized
 			return
 		}
 
@@ -177,11 +179,11 @@ func (w *Watcher) cclHandleMessage(parentCtx context.Context, pe *pendingMessage
 			zap.Uint8("consistencyLevel", req.consistencyLevel),
 			zap.Uint16("additionalBlocks", req.additionalBlocks),
 		)
-		pe.message.ConsistencyLevel = req.consistencyLevel
+		pe.effectiveCL = req.consistencyLevel
 		pe.additionalBlocks = uint64(req.additionalBlocks)
 	default:
 		w.cclLogger.Error("invalid custom handling type, treating as finalized", zap.Stringer("emitterAddress", emitterAddr), zap.Uint8("reqType", uint8(req.Type())), zap.Error(err))
-		pe.message.ConsistencyLevel = vaa.ConsistencyLevelFinalized
+		pe.effectiveCL = vaa.ConsistencyLevelFinalized
 	}
 }
 

+ 97 - 0
node/pkg/watchers/evm/custom_consistency_level_test.go

@@ -2,12 +2,18 @@ package evm
 
 import (
 	"bytes"
+	"context"
 	"encoding/binary"
 	"encoding/hex"
 	"testing"
+	"time"
 
+	"github.com/certusone/wormhole/node/pkg/common"
+	ethCommon "github.com/ethereum/go-ethereum/common"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"github.com/wormhole-foundation/wormhole/sdk/vaa"
+	"go.uber.org/zap"
 )
 
 func TestCclParseConfigInvalidType(t *testing.T) {
@@ -92,3 +98,94 @@ func testCclParseAdditionalBlocksConfig(t *testing.T, str string) error {
 	_, err = cclParseAdditionalBlocksConfig(reader)
 	return err
 }
+
+// TestCclHandleMessageSetsEffectiveCL verifies that when cclHandleMessage processes
+// an AdditionalBlocks request, it sets pe.effectiveCL (not pe.message.ConsistencyLevel).
+func TestCclHandleMessageSetsEffectiveCL(t *testing.T) {
+	logger := zap.NewNop()
+
+	// Create a watcher with CCL enabled
+	w := &Watcher{
+		cclEnabled: true,
+		cclLogger:  logger,
+		cclCache:   make(CCLCache),
+	}
+
+	// Create a message with ConsistencyLevelCustom
+	msg := &common.MessagePublication{
+		ConsistencyLevel: vaa.ConsistencyLevelCustom,
+	}
+
+	// Create a pendingMessage with initial effectiveCL = 0
+	pe := &pendingMessage{
+		message:     msg,
+		effectiveCL: 0,
+	}
+
+	// Mock the contract response by pre-populating the cache
+	// This simulates the contract returning Finalized (200) with 42 additional blocks
+	emitterAddr := ethCommon.HexToAddress("0x1234567890123456789012345678901234567890")
+	buf, err := hex.DecodeString("01c8002a00000000000000000000000000000000000000000000000000000000")
+	require.NoError(t, err)
+	require.Equal(t, 32, len(buf))
+	w.cclCache[emitterAddr] = CCLCacheEntry{
+		data:     *(*[32]byte)(buf),
+		readTime: time.Now(), // Set timestamp to ensure cache is valid
+	}
+
+	// Call cclHandleMessage - this is what we're testing
+	ctx := context.Background()
+	w.cclHandleMessage(ctx, pe, emitterAddr)
+
+	// CRITICAL ASSERTION: pe.effectiveCL should be set to the contract's consistency level
+	assert.Equal(t, uint8(200), pe.effectiveCL, "effectiveCL should be set from contract")
+
+	// CRITICAL ASSERTION: pe.message.ConsistencyLevel should REMAIN unchanged
+	assert.Equal(t, vaa.ConsistencyLevelCustom, pe.message.ConsistencyLevel, "message.ConsistencyLevel must stay Custom for VAA hash consistency")
+}
+
+// TestCclHandleMessageInvalidCLSetsEffectiveCL verifies that when the contract returns
+// an INVALID consistency level for AdditionalBlocks, we set pe.effectiveCL (not pe.message.ConsistencyLevel).
+func TestCclHandleMessageInvalidCLSetsEffectiveCL(t *testing.T) {
+	logger := zap.NewNop()
+
+	// Create a watcher with CCL enabled
+	w := &Watcher{
+		cclEnabled: true,
+		cclLogger:  logger,
+		cclCache:   make(CCLCache),
+	}
+
+	// Create a message with ConsistencyLevelCustom
+	msg := &common.MessagePublication{
+		ConsistencyLevel: vaa.ConsistencyLevelCustom,
+	}
+
+	// Create a pendingMessage with initial effectiveCL = 0
+	pe := &pendingMessage{
+		message:     msg,
+		effectiveCL: 0,
+	}
+
+	// Mock the contract response with an INVALID consistency level (99)
+	// Valid values are: Finalized (200), Safe (201), PublishImmediately (1)
+	emitterAddr := ethCommon.HexToAddress("0x1234567890123456789012345678901234567890")
+	buf, err := hex.DecodeString("0163002a00000000000000000000000000000000000000000000000000000000")
+	require.NoError(t, err)
+	require.Equal(t, 32, len(buf))
+	w.cclCache[emitterAddr] = CCLCacheEntry{
+		data:     *(*[32]byte)(buf),
+		readTime: time.Now(), // Set timestamp to ensure cache is valid
+	}
+
+	// Call cclHandleMessage - this should handle the invalid CL gracefully
+	ctx := context.Background()
+	w.cclHandleMessage(ctx, pe, emitterAddr)
+
+	// CRITICAL ASSERTION: pe.effectiveCL should be set to Finalized (fallback for invalid CL)
+	assert.Equal(t, vaa.ConsistencyLevelFinalized, pe.effectiveCL, "effectiveCL should be set to Finalized for invalid CL")
+
+	// CRITICAL ASSERTION: pe.message.ConsistencyLevel should REMAIN unchanged
+	// This ensures all Guardians produce the same VAA hash even when contract returns invalid data
+	assert.Equal(t, vaa.ConsistencyLevelCustom, pe.message.ConsistencyLevel, "message.ConsistencyLevel must stay Custom for VAA hash consistency")
+}

+ 8 - 5
node/pkg/watchers/evm/watcher.go

@@ -166,6 +166,7 @@ type (
 	pendingMessage struct {
 		message          *common.MessagePublication
 		height           uint64
+		effectiveCL      uint8
 		additionalBlocks uint64
 	}
 )
@@ -525,7 +526,7 @@ func (w *Watcher) Run(parentCtx context.Context) error {
 				w.pendingMu.Lock()
 				for key, pLock := range w.pending {
 					// Don't process the observation if it is waiting on a different consistency level.
-					if !consistencyLevelMatches(thisConsistencyLevel, pLock.message.ConsistencyLevel) {
+					if !consistencyLevelMatches(thisConsistencyLevel, pLock.effectiveCL) {
 						continue
 					}
 
@@ -843,8 +844,9 @@ func (w *Watcher) postMessage(
 	}
 
 	pendingEntry := &pendingMessage{
-		message: msg,
-		height:  ev.Raw.BlockNumber,
+		message:     msg,
+		height:      ev.Raw.BlockNumber,
+		effectiveCL: ev.ConsistencyLevel, // Initially from event; may be overridden by CCL contract
 	}
 
 	if msg.ConsistencyLevel == vaa.ConsistencyLevelCustom {
@@ -862,8 +864,9 @@ func (w *Watcher) postMessage(
 		zap.Stringer("blockHash", ev.Raw.BlockHash),
 		zap.Uint64("blockTime", blockTime),
 		zap.Uint32("Nonce", ev.Nonce),
-		zap.Uint8("OrigConsistencyLevel", ev.ConsistencyLevel),
-		zap.Uint8("ConsistencyLevel", pendingEntry.message.ConsistencyLevel),
+		zap.Uint8("OrigConsistencyLevel", ev.ConsistencyLevel),               // What was in the event
+		zap.Uint8("ConsistencyLevel", pendingEntry.message.ConsistencyLevel), // What goes into the observation
+		zap.Uint8("effectiveCL", pendingEntry.effectiveCL),                   // What was in the contract
 		zap.Uint64("AdditionalBlocks", pendingEntry.additionalBlocks),
 	)