Skip to content

Commit 6cdbae0

Browse files
authored
CCIP-5449 Backgroung caching of config data (#713)
1 parent d91ef00 commit 6cdbae0

File tree

10 files changed

+1258
-369
lines changed

10 files changed

+1258
-369
lines changed

commit/plugin.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,7 @@ func (p *Plugin) Close() error {
548548
p.tokenPriceProcessor,
549549
p.chainFeeProcessor,
550550
p.discoveryProcessor,
551+
p.ccipReader,
551552
}
552553

553554
// Chains without RMN don't initialize the RMNHomeReader

execute/plugin.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/hex"
88
"errors"
99
"fmt"
10+
"io"
1011
"slices"
1112
"sort"
1213
"time"
@@ -20,6 +21,8 @@ import (
2021
"github.com/smartcontractkit/libocr/quorumhelper"
2122
libocrtypes "github.com/smartcontractkit/libocr/ragep2p/types"
2223

24+
"github.com/smartcontractkit/chainlink-common/pkg/services"
25+
2326
"github.com/smartcontractkit/chainlink-common/pkg/logger"
2427

2528
"github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives"
@@ -744,7 +747,14 @@ func (p *Plugin) ShouldTransmitAcceptedReport(
744747
}
745748

746749
func (p *Plugin) Close() error {
747-
return p.tokenDataObserver.Close()
750+
p.lggr.Infow("closing exec plugin")
751+
752+
closeable := []io.Closer{
753+
p.tokenDataObserver,
754+
p.ccipReader,
755+
}
756+
757+
return services.CloseAll(closeable...)
748758
}
749759

750760
func (p *Plugin) supportedChains(id commontypes.OracleID) (mapset.Set[cciptypes.ChainSelector], error) {

execute/plugin_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,8 +632,18 @@ func Test_getPendingReportsForExecution(t *testing.T) {
632632
}
633633

634634
func TestPlugin_Close(t *testing.T) {
635-
p := &Plugin{tokenDataObserver: &tokendata.NoopTokenDataObserver{}}
635+
lggr := logger.Test(t)
636+
mockReader := readerpkg_mock.NewMockCCIPReader(t)
637+
638+
// Set up expectation for Close() method
639+
mockReader.On("Close").Return(nil)
640+
p := &Plugin{
641+
lggr: lggr,
642+
tokenDataObserver: &tokendata.NoopTokenDataObserver{},
643+
ccipReader: mockReader}
644+
636645
require.NoError(t, p.Close())
646+
mockReader.AssertExpectations(t)
637647
}
638648

639649
func TestPlugin_Query(t *testing.T) {

internal/mocks/inmem/ccipreader_inmem.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,5 +210,12 @@ func (r InMemoryCCIPReader) GetOffRampSourceChainsConfig(ctx context.Context, ch
210210
return nil, nil
211211
}
212212

213+
// Close implements the reader.CCIPReader interface
214+
func (r InMemoryCCIPReader) Close() error {
215+
// Since this is an in-memory implementation with no persistent connections
216+
// or resources to clean up, we can simply return nil
217+
return nil
218+
}
219+
213220
// Interface compatibility check.
214221
var _ reader.CCIPReader = InMemoryCCIPReader{}

mocks/pkg/reader/ccip_reader.go

Lines changed: 45 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/reader/ccip.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ func newCCIPChainReaderInternal(
8686
lggr.Errorw("failed to sync contracts", "err", err)
8787
}
8888

89+
// After contracts are synced, start the background polling
90+
lggr.Info("Starting config background polling")
91+
if err := reader.configPoller.Start(ctx); err != nil {
92+
// Log the error but don't fail - we can still function without background polling
93+
// by fetching configs on demand
94+
lggr.Errorw("failed to start config background polling", "err", err)
95+
}
96+
8997
return reader
9098
}
9199

@@ -96,6 +104,15 @@ func (r *ccipChainReader) WithExtendedContractReader(
96104
return r
97105
}
98106

107+
func (r *ccipChainReader) Close() error {
108+
if err := r.configPoller.Close(); err != nil {
109+
r.lggr.Warnw("Error closing config poller", "err", err)
110+
// Continue with shutdown even if there's an error
111+
}
112+
r.lggr.Info("Stopped CCIP chain reader")
113+
return nil
114+
}
115+
99116
// ---------------------------------------------------
100117
// The following types are used to decode the events
101118
// but should be replaced by chain-reader modifiers and use the base cciptypes.CommitReport type.
@@ -2041,5 +2058,39 @@ func validateSendRequestedEvent(
20412058
return nil
20422059
}
20432060

2061+
// ccipReaderInternal defines the interface that ConfigPoller needs from the ccipChainReader
2062+
// This allows for better encapsulation and easier testing through mocking
2063+
type ccipReaderInternal interface {
2064+
// getDestChain returns the destination chain selector
2065+
getDestChain() cciptypes.ChainSelector
2066+
2067+
// getContractReader returns the contract reader for the specified chain
2068+
getContractReader(chain cciptypes.ChainSelector) (contractreader.Extended, bool)
2069+
2070+
// prepareBatchConfigRequests prepares the batch requests for fetching chain configuration
2071+
prepareBatchConfigRequests(chainSel cciptypes.ChainSelector) contractreader.ExtendedBatchGetLatestValuesRequest
2072+
2073+
// processConfigResults processes the batch results into a ChainConfigSnapshot
2074+
processConfigResults(
2075+
chainSel cciptypes.ChainSelector,
2076+
batchResult types.BatchGetLatestValuesResult) (ChainConfigSnapshot, error)
2077+
2078+
// fetchFreshSourceChainConfigs fetches source chain configurations from the specified destination chain
2079+
fetchFreshSourceChainConfigs(
2080+
ctx context.Context, destChain cciptypes.ChainSelector,
2081+
sourceChains []cciptypes.ChainSelector) (map[cciptypes.ChainSelector]SourceChainConfig, error)
2082+
}
2083+
2084+
// getDestChain returns the destination chain selector
2085+
func (r *ccipChainReader) getDestChain() cciptypes.ChainSelector {
2086+
return r.destChain
2087+
}
2088+
2089+
// getContractReader returns the contract reader for the specified chain
2090+
func (r *ccipChainReader) getContractReader(chain cciptypes.ChainSelector) (contractreader.Extended, bool) {
2091+
reader, exists := r.contractReaders[chain]
2092+
return reader, exists
2093+
}
2094+
20442095
// Interface compliance check
20452096
var _ CCIPReader = (*ccipChainReader)(nil)

pkg/reader/ccip_interface.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,4 +271,6 @@ type CCIPReader interface {
271271
// If a config was not found it will be missing from the returned map.
272272
GetOffRampSourceChainsConfig(ctx context.Context, sourceChains []cciptypes.ChainSelector,
273273
) (map[cciptypes.ChainSelector]StaticSourceChainConfig, error)
274+
275+
Close() error
274276
}

pkg/reader/ccip_test.go

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ func TestCCIPChainReader_getSourceChainsConfig(t *testing.T) {
9090
}, nil, chainC, offrampAddress, mockAddrCodec,
9191
)
9292

93+
// Add cleanup to ensure resources are released
94+
t.Cleanup(func() {
95+
err := ccipReader.Close()
96+
if err != nil {
97+
t.Logf("Error closing ccipReader: %v", err)
98+
}
99+
})
100+
93101
addrStr, err := mockAddrCodec.AddressBytesToString(offrampAddress, 111_111)
94102
require.NoError(t, err)
95103

@@ -814,6 +822,14 @@ func TestCCIPChainReader_getFeeQuoterTokenPriceUSD(t *testing.T) {
814822
}, nil, chainC, offrampAddress, mockAddrCodec,
815823
)
816824

825+
// Add cleanup to properly shut down the background polling
826+
t.Cleanup(func() {
827+
err := ccipReader.Close()
828+
if err != nil {
829+
t.Logf("Error closing ccipReader: %v", err)
830+
}
831+
})
832+
817833
feeQuoterAddressStr, err := mockAddrCodec.AddressBytesToString(feeQuoterAddress, 111_111)
818834
require.NoError(t, err)
819835
require.NoError(t, ccipReader.contractReaders[chainC].Bind(
@@ -850,6 +866,14 @@ func TestCCIPFeeComponents_HappyPath(t *testing.T) {
850866
internal.NewMockAddressCodecHex(t),
851867
)
852868

869+
// Add cleanup to ensure resources are released
870+
t.Cleanup(func() {
871+
err := ccipReader.Close()
872+
if err != nil {
873+
t.Logf("Error closing ccipReader: %v", err)
874+
}
875+
})
876+
853877
ctx := context.Background()
854878
feeComponents := ccipReader.GetChainsFeeComponents(ctx, []cciptypes.ChainSelector{chainA, chainB, chainC})
855879
assert.Len(t, feeComponents, 2)
@@ -879,6 +903,14 @@ func TestCCIPFeeComponents_NotFoundErrors(t *testing.T) {
879903
internal.NewMockAddressCodecHex(t),
880904
)
881905

906+
// Add cleanup to ensure resources are released
907+
t.Cleanup(func() {
908+
err := ccipReader.Close()
909+
if err != nil {
910+
t.Logf("Error closing ccipReader: %v", err)
911+
}
912+
})
913+
882914
ctx := context.Background()
883915
_, err := ccipReader.GetDestChainFeeComponents(ctx)
884916
require.Error(t, err)
@@ -1532,13 +1564,6 @@ func (m *mockConfigCache) GetChainConfig(
15321564
return args.Get(0).(ChainConfigSnapshot), args.Error(1)
15331565
}
15341566

1535-
func (m *mockConfigCache) RefreshChainConfig(
1536-
ctx context.Context,
1537-
chainSel cciptypes.ChainSelector) (ChainConfigSnapshot, error) {
1538-
args := m.Called(ctx, chainSel)
1539-
return args.Get(0).(ChainConfigSnapshot), args.Error(1)
1540-
}
1541-
15421567
func (m *mockConfigCache) GetOfframpSourceChainConfigs(
15431568
ctx context.Context,
15441569
destChain cciptypes.ChainSelector,
@@ -1547,10 +1572,26 @@ func (m *mockConfigCache) GetOfframpSourceChainConfigs(
15471572
return args.Get(0).(map[cciptypes.ChainSelector]StaticSourceChainConfig), args.Error(1)
15481573
}
15491574

1550-
func (m *mockConfigCache) RefreshSourceChainConfigs(
1551-
ctx context.Context,
1552-
destChain cciptypes.ChainSelector,
1553-
sourceChains []cciptypes.ChainSelector) (map[cciptypes.ChainSelector]StaticSourceChainConfig, error) {
1554-
args := m.Called(ctx, destChain, sourceChains)
1555-
return args.Get(0).(map[cciptypes.ChainSelector]StaticSourceChainConfig), args.Error(1)
1575+
// Update Start method to accept context parameter
1576+
func (m *mockConfigCache) Start(ctx context.Context) error {
1577+
return m.Called(ctx).Error(0)
1578+
}
1579+
1580+
func (m *mockConfigCache) Close() error {
1581+
return m.Called().Error(0)
1582+
}
1583+
1584+
// Implement HealthReport method for services.Service interface
1585+
func (m *mockConfigCache) HealthReport() map[string]error {
1586+
args := m.Called()
1587+
return args.Get(0).(map[string]error)
1588+
}
1589+
1590+
// Implement Name method for the Service interface
1591+
func (m *mockConfigCache) Name() string {
1592+
return m.Called().String(0)
1593+
}
1594+
1595+
func (m *mockConfigCache) Ready() error {
1596+
return m.Called().Error(0)
15561597
}

0 commit comments

Comments
 (0)