|
| 1 | +package finalizer |
| 2 | + |
| 3 | +import ( |
| 4 | + "math/big" |
| 5 | + "testing" |
| 6 | + |
| 7 | + "github.com/ethereum/go-ethereum/common" |
| 8 | + "github.com/stretchr/testify/require" |
| 9 | +) |
| 10 | + |
| 11 | +// TestRelayedKey_LookupMatchesWithUnrelatedQueueItemFields pins the |
| 12 | +// fix for the duplicate-suppression bug in processDelayedQueue: the |
| 13 | +// lookup key built from a ProtocolMessageRelayed event must match the |
| 14 | +// lookup performed against a queueItem regardless of that item's seed |
| 15 | +// (*big.Int, pointer-compared by maps) and msgHash. |
| 16 | +// |
| 17 | +// Pre-fix, ProtocolMessageRelayed used queueItem as the map key with |
| 18 | +// only protocolID + votingRoundID populated, so the lookup with a |
| 19 | +// fully-populated queueItem never matched and every delayed item was |
| 20 | +// re-sent (the relay contract returns the non-fatal "Already relayed" |
| 21 | +// error, so the bug only burned gas). |
| 22 | +func TestRelayedKey_LookupMatchesWithUnrelatedQueueItemFields(t *testing.T) { |
| 23 | + relayed := map[relayedKey]bool{ |
| 24 | + {protocolID: 100, votingRoundID: 42}: true, |
| 25 | + {protocolID: 200, votingRoundID: 99}: true, |
| 26 | + } |
| 27 | + |
| 28 | + item := &queueItem{ |
| 29 | + seed: big.NewInt(0xdeadbeef), // non-nil pointer — would have broken the old lookup |
| 30 | + votingRoundID: 42, |
| 31 | + protocolID: 100, |
| 32 | + msgHash: common.HexToHash("0xabc"), // non-zero — would have broken the old lookup |
| 33 | + } |
| 34 | + |
| 35 | + require.True( |
| 36 | + t, |
| 37 | + relayed[relayedKey{protocolID: item.protocolID, votingRoundID: item.votingRoundID}], |
| 38 | + "already-relayed lookup must match by (protocolID, votingRoundID) regardless of seed/msgHash", |
| 39 | + ) |
| 40 | + |
| 41 | + miss := &queueItem{ |
| 42 | + seed: big.NewInt(1), |
| 43 | + votingRoundID: 43, // different round |
| 44 | + protocolID: 100, |
| 45 | + msgHash: common.HexToHash("0xabc"), |
| 46 | + } |
| 47 | + require.False( |
| 48 | + t, |
| 49 | + relayed[relayedKey{protocolID: miss.protocolID, votingRoundID: miss.votingRoundID}], |
| 50 | + "different (protocolID, votingRoundID) must not match", |
| 51 | + ) |
| 52 | +} |
| 53 | + |
| 54 | +// TestRelayedKey_TwoNonNilSeedsDoNotCollide makes the failure mode of |
| 55 | +// the pre-fix code explicit: under the old shape, two queueItems |
| 56 | +// holding distinct *big.Int seeds compared as DIFFERENT map keys even |
| 57 | +// when their (protocolID, votingRoundID) were equal — that's exactly |
| 58 | +// why the producer-built map (seed=nil) never matched the |
| 59 | +// consumer-side lookup (seed != nil). |
| 60 | +func TestRelayedKey_TwoNonNilSeedsDoNotCollide(t *testing.T) { |
| 61 | + a := queueItem{seed: big.NewInt(1), votingRoundID: 1, protocolID: 1} |
| 62 | + b := queueItem{seed: big.NewInt(1), votingRoundID: 1, protocolID: 1} |
| 63 | + |
| 64 | + m := map[queueItem]bool{a: true} |
| 65 | + require.False(t, m[b], "*big.Int is pointer-compared in map keys — distinct allocations of equal values DO NOT match") |
| 66 | + |
| 67 | + // And the fix: relayedKey excludes the pointer field entirely. |
| 68 | + ra := relayedKey{protocolID: 1, votingRoundID: 1} |
| 69 | + rb := relayedKey{protocolID: 1, votingRoundID: 1} |
| 70 | + rm := map[relayedKey]bool{ra: true} |
| 71 | + require.True(t, rm[rb], "relayedKey is pure value type — equal values match") |
| 72 | +} |
0 commit comments