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

Solana plugin #15820

Merged
merged 7 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions core/capabilities/ccip/delegate.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey"
"github.com/smartcontractkit/chainlink/v2/core/services/ocr2"
"github.com/smartcontractkit/chainlink/v2/core/services/pipeline"
"github.com/smartcontractkit/chainlink/v2/core/services/relay"
"github.com/smartcontractkit/chainlink/v2/core/services/telemetry"
"github.com/smartcontractkit/chainlink/v2/plugins"
)
Expand Down Expand Up @@ -251,10 +250,6 @@ func (d *Delegate) OnDeleteJob(ctx context.Context, spec job.Job) error {
func (d *Delegate) getOCRKeys(ocrKeyBundleIDs job.JSONConfig) (map[string]ocr2key.KeyBundle, error) {
ocrKeys := make(map[string]ocr2key.KeyBundle)
for networkType, bundleIDRaw := range ocrKeyBundleIDs {
if networkType != relay.NetworkEVM {
return nil, fmt.Errorf("unsupported chain type: %s", networkType)
}
Comment on lines -254 to -256
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check can still be performed by the oracle creator using something like _, supported := oraclecreator.plugins[networkType]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the check because it should be fine to declare OCR2 keys for chains that are currently unsupported, those keys will just be unused (a similar change is needed in transmitters). This way we won't even need to redeploy jobspecs when new chains with existing keys add CCIP support.

The jobspec code further down also works similarly: it simply exposes all available key types


bundleID, ok := bundleIDRaw.(string)
if !ok {
return nil, fmt.Errorf("OCRKeyBundleIDs must be a map of chain types to OCR key bundle IDs, got: %T", bundleIDRaw)
Expand Down
1 change: 1 addition & 0 deletions core/capabilities/ccip/oraclecreator/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"time"

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

logger2 "github.com/smartcontractkit/chainlink-common/pkg/logger"

"github.com/ethereum/go-ethereum/common/hexutil"
Expand Down
112 changes: 54 additions & 58 deletions core/capabilities/ccip/oraclecreator/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"io"
"math/big"
"time"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -35,7 +34,6 @@ import (
evmconfig "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/configs/evm"
"github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ocrimpls"
cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml"
"github.com/smartcontractkit/chainlink/v2/core/logger"
"github.com/smartcontractkit/chainlink/v2/core/services/job"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key"
Expand All @@ -44,7 +42,6 @@ import (
evmrelaytypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types"
"github.com/smartcontractkit/chainlink/v2/core/services/synchronization"
"github.com/smartcontractkit/chainlink/v2/core/services/telemetry"
"github.com/smartcontractkit/chainlink/v2/evm/assets"
)

var _ cctypes.OracleCreator = &pluginOracleCreator{}
Expand Down Expand Up @@ -216,6 +213,37 @@ func (i *pluginOracleCreator) Create(ctx context.Context, donID uint32, config c
return newWrappedOracle(oracle, closers), nil
}

type plugin struct {
CommitPluginCodec cciptypes.CommitPluginCodec
ExecutePluginCodec cciptypes.ExecutePluginCodec
ExtraArgsCodec cciptypes.ExtraDataCodec
MessageHasher func(lggr logger.Logger) cciptypes.MessageHasher
TokenDataEncoder cciptypes.TokenDataEncoder
GasEstimateProvider cciptypes.EstimateProvider
RMNCrypto func(lggr logger.Logger) cciptypes.RMNCrypto
}

var plugins = map[string]plugin{
chainsel.FamilyEVM: {
CommitPluginCodec: ccipevm.NewCommitPluginCodecV1(),
ExecutePluginCodec: ccipevm.NewExecutePluginCodecV1(),
ExtraArgsCodec: ccipevm.ExtraArgsCodec{},
MessageHasher: func(lggr logger.Logger) cciptypes.MessageHasher { return ccipevm.NewMessageHasherV1(lggr) },
TokenDataEncoder: ccipevm.NewEVMTokenDataEncoder(),
GasEstimateProvider: ccipevm.NewGasEstimateProvider(),
RMNCrypto: func(lggr logger.Logger) cciptypes.RMNCrypto { return ccipevm.NewEVMRMNCrypto(lggr) },
},
chainsel.FamilySolana: {
CommitPluginCodec: nil,
ExecutePluginCodec: nil,
ExtraArgsCodec: nil,
MessageHasher: func(lggr logger.Logger) cciptypes.MessageHasher { return nil },
TokenDataEncoder: nil,
GasEstimateProvider: nil,
RMNCrypto: func(lggr logger.Logger) cciptypes.RMNCrypto { return nil },
},
}

func (i *pluginOracleCreator) createFactoryAndTransmitter(
donID uint32,
config cctypes.OCR3ConfigWithMeta,
Expand All @@ -234,6 +262,16 @@ func (i *pluginOracleCreator) createFactoryAndTransmitter(
return nil, nil, fmt.Errorf("unsupported chain selector %d %w", config.Config.ChainSelector, err)
}

chainFamily, err := chainsel.GetSelectorFamily(uint64(config.Config.ChainSelector))
if err != nil {
return nil, nil, fmt.Errorf("unsupported chain selector %d %w", config.Config.ChainSelector, err)
}
plugin, exists := plugins[chainFamily]
if !exists {
return nil, nil, fmt.Errorf("unsupported chain %v", chainFamily)
}
messageHasher := plugin.MessageHasher(i.lggr.Named(chainFamily).Named("MessageHasherV1"))

if config.Config.PluginType == uint8(cctypes.PluginTypeCCIPCommit) {
if !i.peerWrapper.IsStarted() {
return nil, nil, fmt.Errorf("peer wrapper is not started")
Expand All @@ -249,7 +287,7 @@ func (i *pluginOracleCreator) createFactoryAndTransmitter(
publicConfig.DeltaRound,
)

rmnCrypto := ccipevm.NewEVMRMNCrypto(i.lggr.Named("EVMRMNCrypto"))
rmnCrypto := plugin.RMNCrypto(i.lggr.Named(chainFamily).Named("RMNCrypto"))

factory = commitocr3.NewPluginFactory(
i.lggr.
Expand All @@ -259,9 +297,9 @@ func (i *pluginOracleCreator) createFactoryAndTransmitter(
Named(hexutil.Encode(config.Config.OfframpAddress)),
donID,
ccipreaderpkg.OCR3ConfigWithMeta(config),
ccipevm.NewCommitPluginCodecV1(),
ccipevm.NewMessageHasherV1(i.lggr.Named("MessageHasherV1")),
ccipevm.NewExtraArgsCodec(),
plugin.CommitPluginCodec,
messageHasher,
plugin.ExtraArgsCodec,
i.homeChainReader,
i.homeChainSelector,
contractReaders,
Expand All @@ -282,12 +320,12 @@ func (i *pluginOracleCreator) createFactoryAndTransmitter(
Named(hexutil.Encode(config.Config.OfframpAddress)),
donID,
ccipreaderpkg.OCR3ConfigWithMeta(config),
ccipevm.NewExecutePluginCodecV1(),
ccipevm.NewMessageHasherV1(i.lggr.Named("MessageHasherV1")),
ccipevm.NewExtraArgsCodec(),
plugin.ExecutePluginCodec,
messageHasher,
plugin.ExtraArgsCodec,
i.homeChainReader,
ccipevm.NewEVMTokenDataEncoder(),
ccipevm.NewGasEstimateProvider(),
plugin.TokenDataEncoder,
plugin.GasEstimateProvider,
contractReaders,
chainWriters,
)
Expand Down Expand Up @@ -328,17 +366,18 @@ func (i *pluginOracleCreator) createReadersAndWriters(
execBatchGasLimit = defaultExecGasLimit
}

homeChainID, err := i.getChainID(i.homeChainSelector)
homeChainID, err := chainsel.GetChainIDFromSelector(uint64(i.homeChainSelector))
if err != nil {
return nil, nil, err
return nil, nil, fmt.Errorf("failed to get chain ID from chain selector %d: %w", i.homeChainSelector, err)
}

contractReaders := make(map[cciptypes.ChainSelector]types.ContractReader)
chainWriters := make(map[cciptypes.ChainSelector]types.ContractWriter)
for relayID, relayer := range i.relayers {
chainID := relayID.ChainID
relayChainFamily := relayID.Network
chainSelector, err1 := i.getChainSelector(chainID, relayChainFamily)
chainDetails, err1 := chainsel.GetChainDetailsByChainIDAndFamily(chainID, relayChainFamily)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check err1 prior to using chainDetails though I guess in this scenario its not a huge issue

chainSelector := cciptypes.ChainSelector(chainDetails.ChainSelector)
if err1 != nil {
return nil, nil, fmt.Errorf("failed to get chain selector from chain ID %s: %w", chainID, err1)
}
Expand Down Expand Up @@ -421,22 +460,6 @@ func decodeAndValidateOffchainConfig(
return ofc, nil
}

func (i *pluginOracleCreator) getChainSelector(chainID string, chainFamily string) (cciptypes.ChainSelector, error) {
chainDetails, err := chainsel.GetChainDetailsByChainIDAndFamily(chainID, chainFamily)
if err != nil {
return 0, fmt.Errorf("failed to get chain selector from chain ID %s and family %s", chainID, chainFamily)
}
return cciptypes.ChainSelector(chainDetails.ChainSelector), nil
}

func (i *pluginOracleCreator) getChainID(chainSelector cciptypes.ChainSelector) (string, error) {
chainID, err := chainsel.GetChainIDFromSelector(uint64(chainSelector))
if err != nil {
return "", fmt.Errorf("failed to get chain ID from chain selector %d: %w", chainSelector, err)
}
return chainID, nil
}

func getChainReaderConfig(
lggr logger.Logger,
chainID string,
Expand Down Expand Up @@ -520,33 +543,6 @@ func createChainWriter(
return cw, nil
}

func getKeySpecificMaxGasPrice(evmConfigs toml.EVMConfigs, chainID *big.Int, fromAddress common.Address) *assets.Wei {
var maxGasPrice *assets.Wei

// If a chain is enabled it should have some configuration in the TOML config
// of the chainlink node.
for _, config := range evmConfigs {
if config.ChainID.ToInt().Cmp(chainID) != 0 {
continue
}

// find the key-specific max gas price for the given fromAddress.
for _, keySpecific := range config.KeySpecific {
if keySpecific.Key.Address() == fromAddress {
maxGasPrice = keySpecific.GasEstimator.PriceMax
}
}

// if we didn't find a key-specific max gas price, use the one specified
// in the gas estimator config, which should have a default value.
if maxGasPrice == nil {
maxGasPrice = config.GasEstimator.PriceMax
}
}

return maxGasPrice
}

type offChainConfig struct {
commitOffchainConfig *pluginconfig.CommitOffchainConfig
execOffchainConfig *pluginconfig.ExecuteOffchainConfig
Expand Down
14 changes: 11 additions & 3 deletions core/services/keystore/keys/ocr2key/solana_keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"io"

"github.com/ethereum/go-ethereum/crypto"
"github.com/pkg/errors"
"github.com/smartcontractkit/libocr/offchainreporting2/types"
"github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil"
ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types"
Expand Down Expand Up @@ -49,7 +48,15 @@ func (skr *solanaKeyring) Sign(reportCtx ocrtypes.ReportContext, report ocrtypes
}

func (skr *solanaKeyring) Sign3(digest types.ConfigDigest, seqNr uint64, r ocrtypes.Report) (signature []byte, err error) {
return nil, errors.New("not implemented")
return skr.signBlob(skr.reportToSigData3(digest, seqNr, r))
}

func (skr *solanaKeyring) reportToSigData3(digest types.ConfigDigest, seqNr uint64, r ocrtypes.Report) []byte {
rawReportContext := RawReportContext3(digest, seqNr)
sigData := crypto.Keccak256(r)
sigData = append(sigData, rawReportContext[0][:]...)
sigData = append(sigData, rawReportContext[1][:]...)
return crypto.Keccak256(sigData)
}

func (skr *solanaKeyring) signBlob(b []byte) (sig []byte, err error) {
Expand All @@ -62,7 +69,8 @@ func (skr *solanaKeyring) Verify(publicKey ocrtypes.OnchainPublicKey, reportCtx
}

func (skr *solanaKeyring) Verify3(publicKey ocrtypes.OnchainPublicKey, cd ocrtypes.ConfigDigest, seqNr uint64, r ocrtypes.Report, signature []byte) bool {
return false
hash := skr.reportToSigData3(cd, seqNr, r)
return skr.verifyBlob(publicKey, hash, signature)
}

func (skr *solanaKeyring) verifyBlob(pubkey types.OnchainPublicKey, b, sig []byte) bool {
Expand Down
39 changes: 27 additions & 12 deletions deployment/ccip/changeset/cs_jobspec.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import (
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"
"github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job"

chainsel "github.com/smartcontractkit/chain-selectors"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal"
"github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/validate"
corejob "github.com/smartcontractkit/chainlink/v2/core/services/job"
"github.com/smartcontractkit/chainlink/v2/core/services/relay"
)

var _ deployment.ChangeSet[any] = CCIPCapabilityJobspecChangeset
Expand Down Expand Up @@ -54,31 +55,45 @@ func CCIPCapabilityJobspecChangeset(env deployment.Environment, _ any) (deployme
// Find the bootstrap nodes
nodesToJobSpecs := make(map[string][]string)
for _, node := range nodes {
// pick first keybundle of each type (by default nodes will auto create one key of each type for each defined chain family)
keyBundles := map[string]string{}
for details, config := range node.SelToOCRConfig {
family, err := chainsel.GetSelectorFamily(details.ChainSelector)
if err != nil {
env.Logger.Warnf("skipping unknown/invalid chain family for selector %v", details.ChainSelector)
continue
}
_, exists := keyBundles[family]
if exists {
if keyBundles[family] != config.KeyBundleID {
return deployment.ChangesetOutput{}, fmt.Errorf("multiple different %v OCR keys found for node %v", family, node.PeerID)
}
continue
}
keyBundles[family] = config.KeyBundleID
}

var spec string
var err error
if !node.IsBootstrap {
spec, err = validate.NewCCIPSpecToml(validate.SpecArgs{
P2PV2Bootstrappers: nodes.BootstrapLocators(),
CapabilityVersion: internal.CapabilityVersion,
CapabilityLabelledName: internal.CapabilityLabelledName,
OCRKeyBundleIDs: map[string]string{
// TODO: Validate that that all EVM chains are using the same keybundle.
relay.NetworkEVM: node.FirstOCRKeybundle().KeyBundleID,
},
P2PKeyID: node.PeerID.String(),
RelayConfigs: nil,
PluginConfig: map[string]any{},
OCRKeyBundleIDs: keyBundles,
P2PKeyID: node.PeerID.String(),
RelayConfigs: nil,
PluginConfig: map[string]any{},
})
} else {
spec, err = validate.NewCCIPSpecToml(validate.SpecArgs{
P2PV2Bootstrappers: []string{}, // Intentionally empty for bootstraps.
CapabilityVersion: internal.CapabilityVersion,
CapabilityLabelledName: internal.CapabilityLabelledName,
OCRKeyBundleIDs: map[string]string{},
// TODO: validate that all EVM chains are using the same keybundle
P2PKeyID: node.PeerID.String(),
RelayConfigs: nil,
PluginConfig: map[string]any{},
P2PKeyID: node.PeerID.String(),
RelayConfigs: nil,
PluginConfig: map[string]any{},
})
}
if err != nil {
Expand Down
7 changes: 0 additions & 7 deletions deployment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,13 +316,6 @@ func (n Node) OCRConfigForChainSelector(chainSel uint64) (OCRConfig, bool) {
return c, ok
}

func (n Node) FirstOCRKeybundle() OCRConfig {
for _, ocrConfig := range n.SelToOCRConfig {
return ocrConfig
}
return OCRConfig{}
}

func MustPeerIDFromString(s string) p2pkey.PeerID {
p := p2pkey.PeerID{}
if err := p.UnmarshalString(s); err != nil {
Expand Down
Loading