Ver código fonte

node/pkg/watchers/evm: fix ccl bug

Paul Noel 1 mês atrás
pai
commit
f75c7f8884

+ 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) {
 func (w *Watcher) cclHandleMessage(parentCtx context.Context, pe *pendingMessage, emitterAddr ethCommon.Address) {
 	if !w.cclEnabled {
 	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()))
 		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
 		return
 	}
 	}
 
 
@@ -153,14 +153,16 @@ func (w *Watcher) cclHandleMessage(parentCtx context.Context, pe *pendingMessage
 	r, err := w.cclReadAndParseConfig(parentCtx, emitterAddr)
 	r, err := w.cclReadAndParseConfig(parentCtx, emitterAddr)
 	if err != nil {
 	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))
 		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
 		return
 	}
 	}
 
 
 	switch req := r.(type) {
 	switch req := r.(type) {
 	case *NothingSpecial:
 	case *NothingSpecial:
 		w.cclLogger.Info("received an observation with the nothing special specifier, treating as finalized", zap.String("msgId", pe.message.MessageIDString()))
 		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:
 	case *AdditionalBlocks:
 		if req.consistencyLevel != vaa.ConsistencyLevelFinalized && req.consistencyLevel != vaa.ConsistencyLevelSafe && req.consistencyLevel != vaa.ConsistencyLevelPublishImmediately {
 		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",
 			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.Uint8("consistencyLevel", req.consistencyLevel),
 				zap.Uint16("additionalBlocks", req.additionalBlocks),
 				zap.Uint16("additionalBlocks", req.additionalBlocks),
 			)
 			)
-			pe.message.ConsistencyLevel = vaa.ConsistencyLevelFinalized
+			pe.effectiveCL = vaa.ConsistencyLevelFinalized
 			return
 			return
 		}
 		}
 
 
@@ -177,11 +179,11 @@ func (w *Watcher) cclHandleMessage(parentCtx context.Context, pe *pendingMessage
 			zap.Uint8("consistencyLevel", req.consistencyLevel),
 			zap.Uint8("consistencyLevel", req.consistencyLevel),
 			zap.Uint16("additionalBlocks", req.additionalBlocks),
 			zap.Uint16("additionalBlocks", req.additionalBlocks),
 		)
 		)
-		pe.message.ConsistencyLevel = req.consistencyLevel
+		pe.effectiveCL = req.consistencyLevel
 		pe.additionalBlocks = uint64(req.additionalBlocks)
 		pe.additionalBlocks = uint64(req.additionalBlocks)
 	default:
 	default:
 		w.cclLogger.Error("invalid custom handling type, treating as finalized", zap.Stringer("emitterAddress", emitterAddr), zap.Uint8("reqType", uint8(req.Type())), zap.Error(err))
 		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 (
 import (
 	"bytes"
 	"bytes"
+	"context"
 	"encoding/binary"
 	"encoding/binary"
 	"encoding/hex"
 	"encoding/hex"
 	"testing"
 	"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/assert"
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/require"
+	"github.com/wormhole-foundation/wormhole/sdk/vaa"
+	"go.uber.org/zap"
 )
 )
 
 
 func TestCclParseConfigInvalidType(t *testing.T) {
 func TestCclParseConfigInvalidType(t *testing.T) {
@@ -92,3 +98,94 @@ func testCclParseAdditionalBlocksConfig(t *testing.T, str string) error {
 	_, err = cclParseAdditionalBlocksConfig(reader)
 	_, err = cclParseAdditionalBlocksConfig(reader)
 	return err
 	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 {
 	pendingMessage struct {
 		message          *common.MessagePublication
 		message          *common.MessagePublication
 		height           uint64
 		height           uint64
+		effectiveCL      uint8
 		additionalBlocks uint64
 		additionalBlocks uint64
 	}
 	}
 )
 )
@@ -525,7 +526,7 @@ func (w *Watcher) Run(parentCtx context.Context) error {
 				w.pendingMu.Lock()
 				w.pendingMu.Lock()
 				for key, pLock := range w.pending {
 				for key, pLock := range w.pending {
 					// Don't process the observation if it is waiting on a different consistency level.
 					// 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
 						continue
 					}
 					}
 
 
@@ -843,8 +844,9 @@ func (w *Watcher) postMessage(
 	}
 	}
 
 
 	pendingEntry := &pendingMessage{
 	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 {
 	if msg.ConsistencyLevel == vaa.ConsistencyLevelCustom {
@@ -862,8 +864,9 @@ func (w *Watcher) postMessage(
 		zap.Stringer("blockHash", ev.Raw.BlockHash),
 		zap.Stringer("blockHash", ev.Raw.BlockHash),
 		zap.Uint64("blockTime", blockTime),
 		zap.Uint64("blockTime", blockTime),
 		zap.Uint32("Nonce", ev.Nonce),
 		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),
 		zap.Uint64("AdditionalBlocks", pendingEntry.additionalBlocks),
 	)
 	)