Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ocr offchainConfig and chain config structs #36

Merged
merged 11 commits into from
Aug 6, 2024
68 changes: 68 additions & 0 deletions chainconfig/chainconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package chainconfig

import (
"encoding/json"
"errors"
"math/big"

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

// ChainConfig holds configuration that is stored in the onchain CCIPConfig.sol
// configuration contract, specifically the `bytes config` field of the ChainConfig
// solidity struct.
type ChainConfig struct {
// GasPriceDeviationPPB is the maximum deviation in parts per billion that the
// gas price of this chain is allowed to deviate from the last written gas price
// on-chain before we write a new gas price.
GasPriceDeviationPPB cciptypes.BigInt `json:"gasPriceDeviationPPB"`

// DAGasPriceDeviationPPB is the maximum deviation in parts per billion that the
// data-availability gas price of this chain is allowed to deviate from the last
// written data-availability gas price on-chain before we write a new data-availability
// gas price.
// This is only applicable for some chains, such as L2's.
DAGasPriceDeviationPPB cciptypes.BigInt `json:"daGasPriceDeviationPPB"`

// FinalityDepth is the number of confirmations before a block is considered finalized.
// If set to -1, finality tags will be used.
// 0 is not a valid value.
FinalityDepth int64 `json:"finalityDepth"`

// OptimisticConfirmations is the number of confirmations of a chain event before
// it is considered optimistically confirmed (i.e not necessarily finalized).
OptimisticConfirmations uint32 `json:"optimisticConfirmations"`
}

func (cc ChainConfig) Validate() error {
if cc.GasPriceDeviationPPB.Int.Cmp(big.NewInt(0)) <= 0 {
return errors.New("GasPriceDeviationPPB not set or negative")
}

// No validation for DAGasPriceDeviationPPB as it is optional
// and only applicable to L2's.

if cc.FinalityDepth == 0 {
return errors.New("FinalityDepth not set")
}

if cc.OptimisticConfirmations == 0 {
return errors.New("OptimisticConfirmations not set")
}

return nil
}

// EncodeChainConfig encodes a ChainConfig into bytes using JSON.
func EncodeChainConfig(cc ChainConfig) ([]byte, error) {
return json.Marshal(cc)
}

// DecodeChainConfig JSON decodes a ChainConfig from bytes.
func DecodeChainConfig(encodedChainConfig []byte) (ChainConfig, error) {
var cc ChainConfig
if err := json.Unmarshal(encodedChainConfig, &cc); err != nil {
return cc, err
}
return cc, nil
}
86 changes: 86 additions & 0 deletions chainconfig/chainconfig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package chainconfig

import (
"math/big"
"testing"

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

func TestChainConfig_Validate(t *testing.T) {
type fields struct {
GasPriceDeviationPPB cciptypes.BigInt
DAGasPriceDeviationPPB cciptypes.BigInt
FinalityDepth int64
OptimisticConfirmations uint32
}
tests := []struct {
name string
fields fields
wantErr bool
}{
{
"valid",
fields{
GasPriceDeviationPPB: cciptypes.BigInt{Int: big.NewInt(1)},
DAGasPriceDeviationPPB: cciptypes.BigInt{Int: big.NewInt(1)},
FinalityDepth: 1,
OptimisticConfirmations: 1,
},
false,
},
{
"valid, finality tags enabled",
fields{
GasPriceDeviationPPB: cciptypes.BigInt{Int: big.NewInt(1)},
DAGasPriceDeviationPPB: cciptypes.BigInt{Int: big.NewInt(1)},
FinalityDepth: -1,
OptimisticConfirmations: 1,
},
false,
},
{
"invalid, gas price deviation not set",
fields{
GasPriceDeviationPPB: cciptypes.BigInt{Int: big.NewInt(0)},
DAGasPriceDeviationPPB: cciptypes.BigInt{Int: big.NewInt(1)},
FinalityDepth: 1,
OptimisticConfirmations: 1,
},
true,
},
{
"invalid, finality depth not set",
fields{
GasPriceDeviationPPB: cciptypes.BigInt{Int: big.NewInt(1)},
DAGasPriceDeviationPPB: cciptypes.BigInt{Int: big.NewInt(1)},
FinalityDepth: 0,
OptimisticConfirmations: 1,
},
true,
},
{
"invalid, optimistic confirmations not set",
fields{
GasPriceDeviationPPB: cciptypes.BigInt{Int: big.NewInt(1)},
DAGasPriceDeviationPPB: cciptypes.BigInt{Int: big.NewInt(1)},
FinalityDepth: 1,
OptimisticConfirmations: 0,
},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cc := ChainConfig{
GasPriceDeviationPPB: tt.fields.GasPriceDeviationPPB,
DAGasPriceDeviationPPB: tt.fields.DAGasPriceDeviationPPB,
FinalityDepth: tt.fields.FinalityDepth,
OptimisticConfirmations: tt.fields.OptimisticConfirmations,
}
if err := cc.Validate(); (err != nil) != tt.wantErr {
t.Errorf("ChainConfig.Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
11 changes: 11 additions & 0 deletions commit/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package commit
import (
"context"
"errors"
"fmt"
"math/big"

"google.golang.org/grpc"
Expand Down Expand Up @@ -79,6 +80,15 @@ func NewPluginFactory(

func (p *PluginFactory) NewReportingPlugin(config ocr3types.ReportingPluginConfig,
) (ocr3types.ReportingPlugin[[]byte], ocr3types.ReportingPluginInfo, error) {
offchainConfig, err := pluginconfig.DecodeCommitOffchainConfig(config.OffchainConfig)
if err != nil {
return nil, ocr3types.ReportingPluginInfo{}, fmt.Errorf("failed to decode commit offchain config: %w", err)
}

if err = offchainConfig.Validate(); err != nil {
return nil, ocr3types.ReportingPluginInfo{}, fmt.Errorf("failed to validate commit offchain config: %w", err)
}

var oracleIDToP2PID = make(map[commontypes.OracleID]ragep2ptypes.PeerID)
for oracleID, p2pID := range p.ocrConfig.Config.P2PIds {
oracleIDToP2PID[commontypes.OracleID(oracleID)] = p2pID
Expand All @@ -103,6 +113,7 @@ func (p *PluginFactory) NewReportingPlugin(config ocr3types.ReportingPluginConfi
pluginconfig.CommitPluginConfig{
DestChain: p.ocrConfig.Config.ChainSelector,
NewMsgScanBatchSize: merklemulti.MaxNumberTreeLeaves,
OffchainConfig: offchainConfig,
},
ccipReader,
onChainTokenPricesReader,
Expand Down
16 changes: 14 additions & 2 deletions commit/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

mapset "github.com/deckarep/golang-set/v2"
"golang.org/x/exp/maps"

"github.com/smartcontractkit/chainlink-ccip/internal/plugincommon"
"github.com/smartcontractkit/chainlink-ccip/internal/reader"
Expand Down Expand Up @@ -123,12 +124,14 @@ func (p *Plugin) Observation(
return types.Observation{}, fmt.Errorf("observe latest committed sequence numbers: %w", err)
}

// observe token prices if the node supports the token price chain
// otherwise move on to gas prices.
var tokenPrices []cciptypes.TokenPrice
if p.cfg.TokenPricesObserver {
if supportTPChain, err := p.supportsTokenPriceChain(); err == nil && supportTPChain {
tokenPrices, err = observeTokenPrices(
ctx,
p.tokenPricesReader,
p.cfg.PricedTokens,
maps.Keys(p.cfg.OffchainConfig.PriceSources),
)
if err != nil {
return types.Observation{}, fmt.Errorf("observe token prices: %w", err)
Expand Down Expand Up @@ -413,6 +416,15 @@ func (p *Plugin) supportsDestChain() (bool, error) {
return destChainConfig.SupportedNodes.Contains(p.oracleIDToP2pID[p.nodeID]), nil
}

func (p *Plugin) supportsTokenPriceChain() (bool, error) {
tokPriceChainConfig, err := p.homeChain.GetChainConfig(
cciptypes.ChainSelector(p.cfg.OffchainConfig.TokenPriceChainSelector))
if err != nil {
return false, fmt.Errorf("get token price chain config: %w", err)
}
return tokPriceChainConfig.SupportedNodes.Contains(p.oracleIDToP2pID[p.nodeID]), nil
}

func syncFrequency(configuredValue time.Duration) time.Duration {
if configuredValue.Milliseconds() == 0 {
return 10 * time.Second
Expand Down
27 changes: 19 additions & 8 deletions commit/plugin_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import (

"github.com/smartcontractkit/libocr/commontypes"
"github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types"
"github.com/smartcontractkit/libocr/offchainreporting2plus/types"
libocrtypes "github.com/smartcontractkit/libocr/ragep2p/types"

"github.com/smartcontractkit/chainlink-common/pkg/logger"
cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3"

"github.com/smartcontractkit/chainlink-ccip/chainconfig"
helpers "github.com/smartcontractkit/chainlink-ccip/internal/libs/testhelpers"
"github.com/smartcontractkit/chainlink-ccip/internal/mocks"
"github.com/smartcontractkit/chainlink-ccip/internal/reader"
Expand Down Expand Up @@ -458,13 +458,25 @@ func mockMsgsBetweenSeqNums(
).Return(msgs, nil)
}

func mustEncodeChainConfig(cfg chainconfig.ChainConfig) []byte {
b, err := chainconfig.EncodeChainConfig(cfg)
if err != nil {
panic(err)
}
return b
}

var (
chainA = cciptypes.ChainSelector(1)
cfgA = []byte("ChainA")
chainA = cciptypes.ChainSelector(1)
cfgA = mustEncodeChainConfig(chainconfig.ChainConfig{
FinalityDepth: 1,
})
lastCommittedSeqNumA = cciptypes.SeqNum(10)
seqNumA = cciptypes.SeqNum(11)
chainB = cciptypes.ChainSelector(2)
cfgB = []byte("ChainB")
cfgB = mustEncodeChainConfig(chainconfig.ChainConfig{
FinalityDepth: 1,
})
lastCommittedSeqNumB = cciptypes.SeqNum(20)
seqNumB = cciptypes.SeqNum(21)

Expand All @@ -490,16 +502,15 @@ var (
}

destChain = cciptypes.ChainSelector(3)
cfgC = []byte("destChain")
cfgC = mustEncodeChainConfig(chainconfig.ChainConfig{
FinalityDepth: 1,
})
fChainOne = uint8(1)

pIDs1_2_3 = []libocrtypes.PeerID{{1}, {2}, {3}}
tokenX = types.Account("tk_xxx")

destCfg = pluginconfig.CommitPluginConfig{
DestChain: destChain,
PricedTokens: []types.Account{tokenX},
TokenPricesObserver: false,
NewMsgScanBatchSize: 256,
}
)
15 changes: 12 additions & 3 deletions execute/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package execute
import (
"context"
"errors"
"time"
"fmt"

"google.golang.org/grpc"

Expand Down Expand Up @@ -79,6 +79,15 @@ func NewPluginFactory(
func (p PluginFactory) NewReportingPlugin(
config ocr3types.ReportingPluginConfig,
) (ocr3types.ReportingPlugin[[]byte], ocr3types.ReportingPluginInfo, error) {
offchainConfig, err := pluginconfig.DecodeExecuteOffchainConfig(config.OffchainConfig)
if err != nil {
return nil, ocr3types.ReportingPluginInfo{}, fmt.Errorf("failed to decode exec offchain config: %w", err)
}

if err = offchainConfig.Validate(); err != nil {
return nil, ocr3types.ReportingPluginInfo{}, fmt.Errorf("failed to validate exec offchain config: %w", err)
}

var oracleIDToP2PID = make(map[commontypes.OracleID]ragep2ptypes.PeerID)
for oracleID, p2pID := range p.ocrConfig.Config.P2PIds {
oracleIDToP2PID[commontypes.OracleID(oracleID)] = p2pID
Expand All @@ -94,8 +103,8 @@ func (p PluginFactory) NewReportingPlugin(
return NewPlugin(
config,
pluginconfig.ExecutePluginConfig{
DestChain: p.ocrConfig.Config.ChainSelector,
MessageVisibilityInterval: 8 * time.Hour,
DestChain: p.ocrConfig.Config.ChainSelector,
OffchainConfig: offchainConfig,
},
oracleIDToP2PID,
ccipReader,
Expand Down
2 changes: 1 addition & 1 deletion execute/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func (p *Plugin) Observation(
}
}

fetchFrom := time.Now().Add(-p.cfg.MessageVisibilityInterval).UTC()
fetchFrom := time.Now().Add(-p.cfg.OffchainConfig.MessageVisibilityInterval.Duration()).UTC()
p.lggr.Infow("decoded previous outcome", "previousOutcome", previousOutcome)

// Phase 1: Gather commit reports from the destination chain and determine which messages are required to build a
Expand Down
24 changes: 20 additions & 4 deletions execute/plugin_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
"github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types"
libocrtypes "github.com/smartcontractkit/libocr/ragep2p/types"

commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3"

"github.com/smartcontractkit/chainlink-ccip/chainconfig"
"github.com/smartcontractkit/chainlink-ccip/execute/report"
"github.com/smartcontractkit/chainlink-ccip/execute/types"
"github.com/smartcontractkit/chainlink-ccip/internal/libs/slicelib"
Expand Down Expand Up @@ -172,8 +174,10 @@ func setupSimpleTest(
}

cfg := pluginconfig.ExecutePluginConfig{
MessageVisibilityInterval: 8 * time.Hour,
DestChain: dstSelector,
OffchainConfig: pluginconfig.ExecuteOffchainConfig{
MessageVisibilityInterval: *commonconfig.MustNewDuration(8 * time.Hour),
},
DestChain: dstSelector,
}
chainConfigInfos := []reader.ChainConfigInfo{
{
Expand All @@ -183,7 +187,9 @@ func setupSimpleTest(
Readers: []libocrtypes.PeerID{
{1}, {2}, {3},
},
Config: []byte{0},
Config: mustEncodeChainConfig(chainconfig.ChainConfig{
FinalityDepth: 1,
}),
},
}, {
ChainSelector: dstSelector,
Expand All @@ -192,7 +198,9 @@ func setupSimpleTest(
Readers: []libocrtypes.PeerID{
{1}, {2}, {3},
},
Config: []byte{0},
Config: mustEncodeChainConfig(chainconfig.ChainConfig{
FinalityDepth: 1,
}),
},
},
}
Expand Down Expand Up @@ -263,3 +271,11 @@ func GetP2pIDs(ids ...int) map[commontypes.OracleID]libocrtypes.PeerID {
}
return res
}

func mustEncodeChainConfig(cc chainconfig.ChainConfig) []byte {
encoded, err := chainconfig.EncodeChainConfig(cc)
if err != nil {
panic(err)
}
return encoded
}
Loading
Loading