Skip to content

Commit

Permalink
Add validate_observation tests
Browse files Browse the repository at this point in the history
  • Loading branch information
rstout committed Aug 5, 2024
1 parent 203095f commit b314524
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 38 deletions.
1 change: 1 addition & 0 deletions commit_rmn_ocb/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package commitrmnocb
65 changes: 27 additions & 38 deletions commit_rmn_ocb/validate_observation.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,29 @@ func (p *Plugin) ValidateObservation(_ ocr3types.OutcomeContext, _ types.Query,
return fmt.Errorf("failed to decode commit plugin observation: %w", err)
}

if err := p.validateFChain(obs.FChain); err != nil {
if err := validateFChain(obs.FChain); err != nil {
return fmt.Errorf("failed to validate FChain: %w", err)
}

if err := p.validateObservedMerkleRoots(obs.MerkleRoots, ao.Observer); err != nil {
observerSupportedChains, err := p.supportedChains(ao.Observer)
if err != nil {
return fmt.Errorf("failed to get supported chains: %w", err)
}

supportsDestChain, err := p.supportsDestChain(ao.Observer)
if err != nil {
return fmt.Errorf("call to supportsDestChain failed: %w", err)
}

if err := validateObservedMerkleRoots(obs.MerkleRoots, ao.Observer, observerSupportedChains); err != nil {
return fmt.Errorf("failed to validate MerkleRoots: %w", err)
}

if err := p.validateObservedOnRampMaxSeqNums(obs.OnRampMaxSeqNums, ao.Observer); err != nil {
if err := validateObservedOnRampMaxSeqNums(obs.OnRampMaxSeqNums, ao.Observer, observerSupportedChains); err != nil {
return fmt.Errorf("failed to validate OnRampMaxSeqNums: %w", err)
}

if err := p.validateObservedOffRampMaxSeqNums(obs.OffRampMaxSeqNums, ao.Observer); err != nil {
if err := validateObservedOffRampMaxSeqNums(obs.OffRampMaxSeqNums, ao.Observer, supportsDestChain); err != nil {
return fmt.Errorf("failed to validate OffRampMaxSeqNums: %w", err)
}

Expand All @@ -47,17 +57,15 @@ func (p *Plugin) ValidateObservation(_ ocr3types.OutcomeContext, _ types.Query,
return nil
}

func (p *Plugin) validateObservedMerkleRoots(merkleRoots []MerkleRoot, observer commontypes.OracleID) error {
func validateObservedMerkleRoots(
merkleRoots []MerkleRoot,
observer commontypes.OracleID,
observerSupportedChains mapset.Set[cciptypes.ChainSelector],
) error {
if len(merkleRoots) == 0 {
return nil
}

observerSupportedChains, err := p.supportedChains(observer)

if err != nil {
return fmt.Errorf("call to supportedChains failed: %w", err)
}

seenChains := mapset.NewSet[cciptypes.ChainSelector]()
for _, root := range merkleRoots {
if !observerSupportedChains.Contains(root.ChainSel) {
Expand All @@ -74,20 +82,15 @@ func (p *Plugin) validateObservedMerkleRoots(merkleRoots []MerkleRoot, observer
return nil
}

func (p *Plugin) validateObservedOnRampMaxSeqNums(
func validateObservedOnRampMaxSeqNums(
onRampMaxSeqNums []plugintypes.SeqNumChain,
observer commontypes.OracleID,
observerSupportedChains mapset.Set[cciptypes.ChainSelector],
) error {
if len(onRampMaxSeqNums) == 0 {
return nil
}

observerSupportedChains, err := p.supportedChains(observer)

if err != nil {
return fmt.Errorf("call to supportedChains failed: %w", err)
}

seenChains := mapset.NewSet[cciptypes.ChainSelector]()
for _, seqNumChain := range onRampMaxSeqNums {
if !observerSupportedChains.Contains(seqNumChain.ChainSel) {
Expand All @@ -104,21 +107,18 @@ func (p *Plugin) validateObservedOnRampMaxSeqNums(
return nil
}

func (p *Plugin) validateObservedOffRampMaxSeqNums(
func validateObservedOffRampMaxSeqNums(
offRampMaxSeqNums []plugintypes.SeqNumChain,
observer commontypes.OracleID,
supportsDestChain bool,
) error {
if len(offRampMaxSeqNums) == 0 {
return nil
}

supportsDestChain, err := p.supportsDestChain(observer)
if err != nil {
return fmt.Errorf("call to supportsDestChain failed: %w", err)
}

if !supportsDestChain {
return fmt.Errorf("observer %d does not support dest chain, but has observed offRampMaxSeqNums", observer)
return fmt.Errorf("observer %d does not support dest chain, but has observed %d offRampMaxSeqNums",
observer, len(offRampMaxSeqNums))
}

seenChains := mapset.NewSet[cciptypes.ChainSelector]()
Expand All @@ -132,22 +132,11 @@ func (p *Plugin) validateObservedOffRampMaxSeqNums(
return nil
}

func (p *Plugin) validateFChain(fchain map[cciptypes.ChainSelector]int) error {
if len(fchain) == 0 {
return fmt.Errorf("fchain map is empty")
}

seenChains := mapset.NewSet[cciptypes.ChainSelector]()
for chainSel, f := range fchain {
if seenChains.Contains(chainSel) {
return fmt.Errorf("duplicate fChain for chain %d", chainSel)
}

func validateFChain(fChain map[cciptypes.ChainSelector]int) error {
for _, f := range fChain {
if f < 0 {
return fmt.Errorf("fChain %d is negative", f)
}

seenChains.Add(chainSel)
}

return nil
Expand Down
211 changes: 211 additions & 0 deletions commit_rmn_ocb/validate_observation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package commitrmnocb

import (
"testing"

mapset "github.com/deckarep/golang-set/v2"
"github.com/smartcontractkit/libocr/commontypes"
"github.com/stretchr/testify/assert"

"github.com/smartcontractkit/chainlink-ccip/plugintypes"
cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3"
)

func Test_validateObservedMerkleRoots(t *testing.T) {
testCases := []struct {
name string
merkleRoots []MerkleRoot
observer commontypes.OracleID
observerSupportedChains mapset.Set[cciptypes.ChainSelector]
expErr bool
}{
{
name: "Chain not supported",
merkleRoots: []MerkleRoot{
{ChainSel: 1, SeqNumRange: [2]cciptypes.SeqNum{10, 20}, RootHash: [32]byte{1, 2, 3}},
{ChainSel: 2, SeqNumRange: [2]cciptypes.SeqNum{24, 45}, RootHash: [32]byte{1, 2, 3}},
},
observer: 10,
observerSupportedChains: mapset.NewSet[cciptypes.ChainSelector](1, 3, 4, 5),
expErr: true,
},
{
name: "Duplicate chains",
merkleRoots: []MerkleRoot{
{ChainSel: 1, SeqNumRange: [2]cciptypes.SeqNum{10, 20}, RootHash: [32]byte{1, 2, 3}},
{ChainSel: 2, SeqNumRange: [2]cciptypes.SeqNum{24, 45}, RootHash: [32]byte{1, 2, 3}},
{ChainSel: 2, SeqNumRange: [2]cciptypes.SeqNum{3, 7}, RootHash: [32]byte{1, 2, 3}},
},
observer: 10,
observerSupportedChains: mapset.NewSet[cciptypes.ChainSelector](1, 2),
expErr: true,
},
{
name: "Valid offRampMaxSeqNums",
merkleRoots: []MerkleRoot{
{ChainSel: 1, SeqNumRange: [2]cciptypes.SeqNum{10, 20}, RootHash: [32]byte{1, 2, 3}},
{ChainSel: 2, SeqNumRange: [2]cciptypes.SeqNum{24, 45}, RootHash: [32]byte{1, 2, 3}},
},
observer: 10,
observerSupportedChains: mapset.NewSet[cciptypes.ChainSelector](1, 2),
expErr: false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := validateObservedMerkleRoots(tc.merkleRoots, tc.observer, tc.observerSupportedChains)

if tc.expErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
})
}
}

func Test_validateObservedOnRampMaxSeqNums(t *testing.T) {
testCases := []struct {
name string
onRampMaxSeqNums []plugintypes.SeqNumChain
observer commontypes.OracleID
observerSupportedChains mapset.Set[cciptypes.ChainSelector]
expErr bool
}{
{
name: "Chain not supported",
onRampMaxSeqNums: []plugintypes.SeqNumChain{
{ChainSel: 1, SeqNum: 10},
{ChainSel: 2, SeqNum: 20},
},
observer: 10,
observerSupportedChains: mapset.NewSet[cciptypes.ChainSelector](1, 3, 4, 5),
expErr: true,
},
{
name: "Duplicate chains",
onRampMaxSeqNums: []plugintypes.SeqNumChain{
{ChainSel: 1, SeqNum: 10},
{ChainSel: 2, SeqNum: 20},
{ChainSel: 2, SeqNum: 33},
},
observer: 10,
observerSupportedChains: mapset.NewSet[cciptypes.ChainSelector](1, 2),
expErr: true,
},
{
name: "Valid offRampMaxSeqNums",
onRampMaxSeqNums: []plugintypes.SeqNumChain{
{ChainSel: 1, SeqNum: 10},
{ChainSel: 2, SeqNum: 20},
},
observer: 10,
observerSupportedChains: mapset.NewSet[cciptypes.ChainSelector](1, 2),
expErr: false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := validateObservedOnRampMaxSeqNums(tc.onRampMaxSeqNums, tc.observer, tc.observerSupportedChains)

if tc.expErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
})
}
}

func Test_validateObservedOffRampMaxSeqNums(t *testing.T) {
testCases := []struct {
name string
offRampMaxSeqNums []plugintypes.SeqNumChain
observer commontypes.OracleID
supportsDestChain bool
expErr bool
}{
{
name: "Dest chain not supported",
offRampMaxSeqNums: []plugintypes.SeqNumChain{
{ChainSel: 1, SeqNum: 10},
{ChainSel: 2, SeqNum: 20},
},
observer: 10,
supportsDestChain: false,
expErr: true,
},
{
name: "Duplicate chains",
offRampMaxSeqNums: []plugintypes.SeqNumChain{
{ChainSel: 1, SeqNum: 10},
{ChainSel: 2, SeqNum: 20},
{ChainSel: 2, SeqNum: 33},
},
observer: 10,
supportsDestChain: false,
expErr: true,
},
{
name: "Valid offRampMaxSeqNums",
offRampMaxSeqNums: []plugintypes.SeqNumChain{
{ChainSel: 1, SeqNum: 10},
{ChainSel: 2, SeqNum: 20},
},
observer: 10,
supportsDestChain: true,
expErr: false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := validateObservedOffRampMaxSeqNums(tc.offRampMaxSeqNums, tc.observer, tc.supportsDestChain)

if tc.expErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
})
}
}

func Test_validateFChain(t *testing.T) {
testCases := []struct {
name string
fChain map[cciptypes.ChainSelector]int
expErr bool
}{
{
name: "FChain contains negative values",
fChain: map[cciptypes.ChainSelector]int{
1: 11,
2: -4,
},
expErr: true,
},
{
name: "FChain valid",
fChain: map[cciptypes.ChainSelector]int{
12: 6,
7: 9,
},
expErr: false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := validateFChain(tc.fChain)

if tc.expErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
})
}
}

0 comments on commit b314524

Please sign in to comment.