Skip to content

Commit

Permalink
RMN home reader test (#14789)
Browse files Browse the repository at this point in the history
* rmn home reader test

* go mod tidy

* get latest ccip commit

* lint

* changeset

* use final commit

* test fixing

* cleaup method

* fix lint

* addressing comments

* using latest ccip commit

* change frequency

* use latest ccip commit

* bump ccip

* fix test

* err check

* tidy

* increase eventually time

* tidy

* change chainlink-ccip

* mod change

* script go.mod change

* fix encoding

* 100ms
  • Loading branch information
0xnogo authored Oct 21, 2024
1 parent 072bfb6 commit 5dfb130
Show file tree
Hide file tree
Showing 3 changed files with 272 additions and 23 deletions.
5 changes: 5 additions & 0 deletions .changeset/silver-pens-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

#internal: Adding test for rmn home reader
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,27 @@ package integrationhelpers

import (
"context"
"crypto/ed25519"
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"sort"
"testing"
"time"

mapset "github.com/deckarep/golang-set/v2"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/stretchr/testify/require"

"github.com/smartcontractkit/chainlink-ccip/pkg/consts"
ccipreader "github.com/smartcontractkit/chainlink-ccip/pkg/reader"
"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
"github.com/smartcontractkit/chainlink-common/pkg/types"
"github.com/stretchr/testify/require"

configsevm "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/configs/evm"
cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types"
Expand All @@ -25,6 +31,7 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home"
kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest"
Expand Down Expand Up @@ -80,15 +87,16 @@ const (
)

type TestUniverse struct {
Transactor *bind.TransactOpts
Backend *backends.SimulatedBackend
CapReg *kcr.CapabilitiesRegistry
CCIPHome *ccip_home.CCIPHome
TestingT *testing.T
LogPoller logpoller.LogPoller
HeadTracker logpoller.HeadTracker
SimClient client.Client
HomeChainReader ccipreader.HomeChain
Transactor *bind.TransactOpts
Backend *backends.SimulatedBackend
CapReg *kcr.CapabilitiesRegistry
CCIPHome *ccip_home.CCIPHome
TestingT *testing.T
LogPoller logpoller.LogPoller
HeadTracker logpoller.HeadTracker
SimClient client.Client
HomeChainReader ccipreader.HomeChain
HomeContractReader types.ContractReader
}

func NewTestUniverse(ctx context.Context, t *testing.T, lggr logger.Logger) TestUniverse {
Expand Down Expand Up @@ -128,17 +136,19 @@ func NewTestUniverse(ctx context.Context, t *testing.T, lggr logger.Logger) Test
require.NoError(t, lp.Start(ctx))
t.Cleanup(func() { require.NoError(t, lp.Close()) })

hcr := NewHomeChainReader(t, lp, headTracker, cl, ccAddress)
cr := NewReader(t, lp, headTracker, cl, ccAddress, configsevm.HomeChainReaderConfigRaw)
hcr := NewHomeChainReader(t, cr, ccAddress)
return TestUniverse{
Transactor: transactor,
Backend: backend,
CapReg: capReg,
CCIPHome: cc,
TestingT: t,
SimClient: cl,
LogPoller: lp,
HeadTracker: headTracker,
HomeChainReader: hcr,
Transactor: transactor,
Backend: backend,
CapReg: capReg,
CCIPHome: cc,
TestingT: t,
SimClient: cl,
LogPoller: lp,
HeadTracker: headTracker,
HomeChainReader: hcr,
HomeContractReader: cr,
}
}

Expand Down Expand Up @@ -227,9 +237,7 @@ func (t *TestUniverse) AddCapability(p2pIDs [][32]byte) {
}
}

func NewHomeChainReader(t *testing.T, logPoller logpoller.LogPoller, headTracker logpoller.HeadTracker, client client.Client, ccAddress common.Address) ccipreader.HomeChain {
cr := NewReader(t, logPoller, headTracker, client, ccAddress, configsevm.HomeChainReaderConfigRaw)

func NewHomeChainReader(t *testing.T, cr types.ContractReader, ccAddress common.Address) ccipreader.HomeChain {
hcr := ccipreader.NewHomeChainReader(cr, logger.TestLogger(t), 50*time.Millisecond, types.BoundContract{
Address: ccAddress.String(),
Name: consts.ContractNameCCIPConfig,
Expand Down Expand Up @@ -365,3 +373,65 @@ func SetupConfigInfo(chainSelector uint64, readers [][32]byte, fChain uint8, cfg
},
}
}

func GenerateRMNHomeConfigs(
peerID string,
offchainPK string,
offchainCfg string,
chainSelector uint64,
minObservers uint64,
observerBitmap *big.Int) (rmn_home.RMNHomeStaticConfig, rmn_home.RMNHomeDynamicConfig, error) {
peerIDByte, _ := hex.DecodeString(peerID)
var peerIDBytes [32]byte
copy(peerIDBytes[:], peerIDByte)

offchainPublicKey, err := hex.DecodeString(offchainPK)

if err != nil {
return rmn_home.RMNHomeStaticConfig{}, rmn_home.RMNHomeDynamicConfig{}, fmt.Errorf("error decoding offchain public key: %w", err)
}

var offchainPublicKeyBytes [32]byte
copy(offchainPublicKeyBytes[:], offchainPublicKey)

staticConfig := rmn_home.RMNHomeStaticConfig{
Nodes: []rmn_home.RMNHomeNode{
{
PeerId: peerIDBytes,
OffchainPublicKey: offchainPublicKeyBytes,
},
},
OffchainConfig: []byte(offchainCfg),
}

dynamicConfig := rmn_home.RMNHomeDynamicConfig{
SourceChains: []rmn_home.RMNHomeSourceChain{
{
ChainSelector: chainSelector,
MinObservers: minObservers,
ObserverNodesBitmap: observerBitmap,
},
},
OffchainConfig: []byte(offchainCfg),
}
return staticConfig, dynamicConfig, nil
}

func GenerateExpectedRMNHomeNodesInfo(staticConfig rmn_home.RMNHomeStaticConfig, chainID int) []ccipreader.HomeNodeInfo {
expectedCandidateNodesInfo := make([]ccipreader.HomeNodeInfo, 0)

supportedCandidateSourceChains := mapset.NewSet(ccipocr3.ChainSelector(chainID))

var counter uint32
for _, n := range staticConfig.Nodes {
pk := ed25519.PublicKey(n.OffchainPublicKey[:])
expectedCandidateNodesInfo = append(expectedCandidateNodesInfo, ccipreader.HomeNodeInfo{
ID: ccipreader.NodeID(counter),
PeerID: n.PeerId,
SupportedSourceChains: supportedCandidateSourceChains,
OffchainPublicKey: &pk,
})
counter++
}
return expectedCandidateNodesInfo
}
174 changes: 174 additions & 0 deletions core/capabilities/ccip/ccip_integration_tests/rmn/rmn_home_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package rmn

import (
"bytes"
"math/big"
"slices"
"testing"
"time"

"github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccip_integration_tests/integrationhelpers"

"github.com/smartcontractkit/chainlink-ccip/pkg/consts"
"github.com/smartcontractkit/chainlink-common/pkg/types"

"github.com/stretchr/testify/require"

"github.com/ethereum/go-ethereum/accounts/abi/bind"

readerpkg "github.com/smartcontractkit/chainlink-ccip/pkg/reader"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
"github.com/smartcontractkit/chainlink/v2/core/logger"

"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home"
)

func TestRMNHomeReader_GetRMNNodesInfo(t *testing.T) {
ctx := testutils.Context(t)
lggr := logger.TestLogger(t)
uni := integrationhelpers.NewTestUniverse(ctx, t, lggr)
zeroBytes := [32]byte{0}

const (
chainID1 = 1
minObservers1 = 1
observerBitmap1 = 1

chainID2 = 2
minObservers2 = 0
observerBitmap2 = 1
)

//================================Deploy and configure RMNHome===============================
rmnHomeAddress, _, rmnHome, err := rmn_home.DeployRMNHome(uni.Transactor, uni.Backend)
require.NoError(t, err)
uni.Backend.Commit()

staticConfig, dynamicConfig, err := integrationhelpers.GenerateRMNHomeConfigs(
"PeerID1",
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"This is a sample offchain configuration in the static config",
chainID1,
minObservers1,
big.NewInt(observerBitmap1),
)
require.NoError(t, err)

_, err = rmnHome.SetCandidate(uni.Transactor, staticConfig, dynamicConfig, zeroBytes)
require.NoError(t, err)
uni.Backend.Commit()

configDigest, err := rmnHome.GetCandidateDigest(&bind.CallOpts{})
require.NoError(t, err)

_, err = rmnHome.PromoteCandidateAndRevokeActive(uni.Transactor, configDigest, zeroBytes)
require.NoError(t, err)
uni.Backend.Commit()

rmnHomeBoundContract := types.BoundContract{
Address: rmnHomeAddress.String(),
Name: consts.ContractNameRMNHome,
}

err = uni.HomeContractReader.Bind(testutils.Context(t), []types.BoundContract{rmnHomeBoundContract})
require.NoError(t, err)

rmnHomeReader := readerpkg.NewRMNHomePoller(uni.HomeContractReader, rmnHomeBoundContract, lggr, 100*time.Millisecond)

err = rmnHomeReader.Start(testutils.Context(t))
require.NoError(t, err)

t.Cleanup(func() {
err1 := rmnHomeReader.Close()
require.NoError(t, err1)
})

//================================Test RMNHome Reader===============================
expectedNodesInfo := integrationhelpers.GenerateExpectedRMNHomeNodesInfo(staticConfig, chainID1)

require.Eventually(
t,
assertRMNHomeNodesInfo(t, rmnHomeReader, configDigest, expectedNodesInfo, nil),
5*time.Second,
100*time.Millisecond,
)

// Add a new candidate config
staticConfig2, dynamicConfig2, err := integrationhelpers.GenerateRMNHomeConfigs(
"PeerID2",
"1123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"This is a sample offchain configuration in the static config 2",
chainID2,
minObservers2,
big.NewInt(observerBitmap2),
)
require.NoError(t, err)

_, err = rmnHome.SetCandidate(uni.Transactor, staticConfig2, dynamicConfig2, zeroBytes)
require.NoError(t, err)
uni.Backend.Commit()

candidateConfigDigest, err := rmnHome.GetCandidateDigest(&bind.CallOpts{})
require.NoError(t, err)

expectedCandidateNodesInfo := integrationhelpers.GenerateExpectedRMNHomeNodesInfo(staticConfig2, chainID2)

require.Eventually(
t,
assertRMNHomeNodesInfo(t, rmnHomeReader, candidateConfigDigest, expectedCandidateNodesInfo, nil),
5*time.Second,
100*time.Millisecond,
)

// Promote the candidate config
_, err = rmnHome.PromoteCandidateAndRevokeActive(uni.Transactor, candidateConfigDigest, configDigest)
require.NoError(t, err)
uni.Backend.Commit()

require.Eventually(
t,
assertRMNHomeNodesInfo(t, rmnHomeReader, candidateConfigDigest, expectedCandidateNodesInfo, &configDigest),
5*time.Second,
100*time.Millisecond,
)
}

func assertRMNHomeNodesInfo(
t *testing.T,
rmnHomeReader readerpkg.RMNHome,
configDigest [32]byte,
expectedNodesInfo []readerpkg.HomeNodeInfo,
prevConfigDigest *[32]byte,
) func() bool {
return func() bool {
nodesInfo, err := rmnHomeReader.GetRMNNodesInfo(configDigest)
if err != nil {
t.Logf("Error getting RMN nodes info: %v", err)
return false
}

equal := slices.EqualFunc(expectedNodesInfo, nodesInfo, func(a, b readerpkg.HomeNodeInfo) bool {
return a.ID == b.ID &&
a.PeerID == b.PeerID &&
bytes.Equal(*a.OffchainPublicKey, *b.OffchainPublicKey) &&
a.SupportedSourceChains.Equal(b.SupportedSourceChains)
})

if !equal {
t.Logf("Expected nodes info doesn't match actual nodes info")
t.Logf("Expected: %+v", expectedNodesInfo)
t.Logf("Actual: %+v", nodesInfo)
return false
}

if prevConfigDigest != nil {
isPrevConfigStillSet := rmnHomeReader.IsRMNHomeConfigDigestSet(*prevConfigDigest)
if isPrevConfigStillSet {
t.Logf("Previous config is still set")
return false
}
}

return true
}
}

0 comments on commit 5dfb130

Please sign in to comment.