diff --git a/pkg/capabilities/consensus/ocr3/datafeeds/securemint_aggregator.go b/pkg/capabilities/consensus/ocr3/datafeeds/securemint_aggregator.go index 9c12b3b6d3..69a99179ab 100644 --- a/pkg/capabilities/consensus/ocr3/datafeeds/securemint_aggregator.go +++ b/pkg/capabilities/consensus/ocr3/datafeeds/securemint_aggregator.go @@ -68,21 +68,20 @@ type secureMintReport struct { Mintable *big.Int `json:"mintable"` } +type wrappedMintReport struct { + Report secureMintReport `json:"report"` + SolanaAccountContext solana.AccountMetaSlice `json:"solanaAccountContext,omitempty"` +} + // chainSelector represents the chain selector type, mimics the ChainSelector type in the SM plugin repo type chainSelector uint64 -type SolanaConfig struct { - // Add Solana-specific configuration fields here - AccountContext solana.AccountMetaSlice `mapstructure:"remaining_accounts"` -} - // SecureMintAggregatorConfig is the config for the SecureMint aggregator. // This aggregator is designed to pick out reports for a specific chain selector. type SecureMintAggregatorConfig struct { // TargetChainSelector is the chain selector to look for TargetChainSelector chainSelector `mapstructure:"targetChainSelector"` DataID [16]byte `mapstructure:"dataID"` - Solana SolanaConfig `mapstructure:"solana"` } // ToMap converts the SecureMintAggregatorConfig to a values.Map, which is suitable for the @@ -104,7 +103,7 @@ type SecureMintAggregator struct { } type chainReportFormatter interface { - packReport(lggr logger.Logger, report *secureMintReport) (*values.Map, error) + packReport(lggr logger.Logger, report *wrappedMintReport) (*values.Map, error) } type evmReportFormatter struct { @@ -112,7 +111,8 @@ type evmReportFormatter struct { dataID [16]byte } -func (f *evmReportFormatter) packReport(lggr logger.Logger, report *secureMintReport) (*values.Map, error) { +func (f *evmReportFormatter) packReport(lggr logger.Logger, wreport *wrappedMintReport) (*values.Map, error) { + report := wreport.Report smReportAsAnswer, err := packSecureMintReportIntoUint224ForEVM(report.Mintable, report.Block) if err != nil { return nil, fmt.Errorf("failed to pack secure mint report for evm into uint224: %w", err) @@ -147,10 +147,10 @@ func newEVMReportFormatter(chainSelector chainSelector, config SecureMintAggrega type solanaReportFormatter struct { targetChainSelector chainSelector dataID [16]byte - onReportAccounts solana.AccountMetaSlice } -func (f *solanaReportFormatter) packReport(lggr logger.Logger, report *secureMintReport) (*values.Map, error) { +func (f *solanaReportFormatter) packReport(lggr logger.Logger, wreport *wrappedMintReport) (*values.Map, error) { + report := wreport.Report // pack answer smReportAsAnswer, err := packSecureMintReportIntoU128ForSolana(report.Mintable, report.Block) if err != nil { @@ -160,9 +160,10 @@ func (f *solanaReportFormatter) packReport(lggr logger.Logger, report *secureMin // hash account contexts var accounts = make([]byte, 0) - for _, acc := range f.onReportAccounts { + for _, acc := range wreport.SolanaAccountContext { accounts = append(accounts, acc.PublicKey[:]...) } + lggr.Debugf("accounts length: %d", len(wreport.SolanaAccountContext)) accountContextHash := sha256.Sum256(accounts) lggr.Debugw("calculated account context hash", "accountContextHash", accountContextHash) @@ -191,7 +192,7 @@ func (f *solanaReportFormatter) packReport(lggr logger.Logger, report *secureMin } func newSolanaReportFormatter(chainSelector chainSelector, config SecureMintAggregatorConfig) chainReportFormatter { - return &solanaReportFormatter{targetChainSelector: chainSelector, onReportAccounts: config.Solana.AccountContext, dataID: config.DataID} + return &solanaReportFormatter{targetChainSelector: chainSelector, dataID: config.DataID} } // chainReportFormatterBuilder is a function that returns a chainReportFormatter for a given chain selector and config @@ -291,9 +292,14 @@ func (a *SecureMintAggregator) Aggregate(lggr logger.Logger, previousOutcome *ty return outcome, nil } +type ObsWithCtx struct { + Event capabilities.OCRTriggerEvent `mapstructure:"event"` + Solana solana.AccountMetaSlice `mapstructure:"solana"` +} + // extractAndValidateReports extracts OCRTriggerEvent from observations and validates them -func (a *SecureMintAggregator) extractAndValidateReports(lggr logger.Logger, observations map[ocrcommon.OracleID][]values.Value, previousOutcome *types.AggregationOutcome) ([]*secureMintReport, error) { - var validReports []*secureMintReport +func (a *SecureMintAggregator) extractAndValidateReports(lggr logger.Logger, observations map[ocrcommon.OracleID][]values.Value, previousOutcome *types.AggregationOutcome) ([]*wrappedMintReport, error) { + var validReports []*wrappedMintReport var foundMatchingChainSelector bool for nodeID, nodeObservations := range observations { @@ -302,18 +308,20 @@ func (a *SecureMintAggregator) extractAndValidateReports(lggr logger.Logger, obs for _, observation := range nodeObservations { lggr.Debugw("processing observation", "observation", observation) - // Extract OCRTriggerEvent from the observation - triggerEvent := &capabilities.OCRTriggerEvent{} - if err := observation.UnwrapTo(triggerEvent); err != nil { + // Extract OCRTriggerEvent from the observations + + obsWithContext := &ObsWithCtx{} + + if err := observation.UnwrapTo(obsWithContext); err != nil { lggr.Warnw("could not unwrap OCRTriggerEvent", "err", err, "observation", observation) continue } - lggr.Debugw("triggerEvent", "triggerEvent", triggerEvent) + lggr.Debugw("Obs with context", "obs with ctx", obsWithContext) // Deserialize the ReportWithInfo var reportWithInfo ocr3types.ReportWithInfo[chainSelector] - if err := json.Unmarshal(triggerEvent.Report, &reportWithInfo); err != nil { + if err := json.Unmarshal(obsWithContext.Event.Report, &reportWithInfo); err != nil { lggr.Errorw("failed to unmarshal ReportWithInfo", "err", err) continue } @@ -333,8 +341,12 @@ func (a *SecureMintAggregator) extractAndValidateReports(lggr logger.Logger, obs lggr.Errorw("failed to unmarshal secureMintReport", "err", err) continue } + report := &wrappedMintReport{ + Report: innerReport, + SolanaAccountContext: obsWithContext.Solana, + } - validReports = append(validReports, &innerReport) + validReports = append(validReports, report) } } @@ -348,7 +360,7 @@ func (a *SecureMintAggregator) extractAndValidateReports(lggr logger.Logger, obs } // createOutcome creates the final aggregation outcome which can be sent to the KeystoneForwarder -func (a *SecureMintAggregator) createOutcome(lggr logger.Logger, report *secureMintReport) (*types.AggregationOutcome, error) { +func (a *SecureMintAggregator) createOutcome(lggr logger.Logger, report *wrappedMintReport) (*types.AggregationOutcome, error) { lggr = logger.Named(lggr, "SecureMintAggregator") lggr.Debugw("createOutcome called", "report", report) @@ -369,12 +381,12 @@ func (a *SecureMintAggregator) createOutcome(lggr logger.Logger, report *secureM reportsProto := values.Proto(wrappedReport) // Store the sequence number in metadata for next round - metadata := []byte{byte(report.SeqNr)} // Simple metadata for now + metadata := []byte{byte(report.Report.SeqNr)} // Simple metadata for now aggOutcome := &types.AggregationOutcome{ EncodableOutcome: reportsProto.GetMapValue(), Metadata: metadata, - LastSeenAt: report.SeqNr, + LastSeenAt: report.Report.SeqNr, ShouldReport: true, // Always report since we found and verified the target report } @@ -385,9 +397,8 @@ func (a *SecureMintAggregator) createOutcome(lggr logger.Logger, report *secureM // parseSecureMintConfig parses the user-facing, type-less, SecureMint aggregator config into the internal typed config. func parseSecureMintConfig(config values.Map) (SecureMintAggregatorConfig, error) { type rawConfig struct { - TargetChainSelector string `mapstructure:"targetChainSelector"` - DataID string `mapstructure:"dataID"` - Solana SolanaConfig `mapstructure:"solana"` + TargetChainSelector string `mapstructure:"targetChainSelector"` + DataID string `mapstructure:"dataID"` } var rawCfg rawConfig @@ -420,18 +431,9 @@ func parseSecureMintConfig(config values.Map) (SecureMintAggregatorConfig, error return SecureMintAggregatorConfig{}, fmt.Errorf("dataID must be 16 bytes, got %d", len(decodedDataID)) } - if len(rawCfg.Solana.AccountContext) > 0 { - for _, acc := range rawCfg.Solana.AccountContext { - if acc.PublicKey == [32]byte{} { - return SecureMintAggregatorConfig{}, errors.New("solana account context public key must not be all zeros") - } - } - } - parsedConfig := SecureMintAggregatorConfig{ TargetChainSelector: chainSelector(sel), DataID: [16]byte(decodedDataID), - Solana: rawCfg.Solana, } return parsedConfig, nil diff --git a/pkg/capabilities/consensus/ocr3/datafeeds/securemint_aggregator_test.go b/pkg/capabilities/consensus/ocr3/datafeeds/securemint_aggregator_test.go index da871d80a9..11acb5e81b 100644 --- a/pkg/capabilities/consensus/ocr3/datafeeds/securemint_aggregator_test.go +++ b/pkg/capabilities/consensus/ocr3/datafeeds/securemint_aggregator_test.go @@ -185,6 +185,7 @@ func TestSecureMintAggregator_Aggregate(t *testing.T) { Block: 1000, Mintable: big.NewInt(99), }, + accCtx: solana.AccountMetaSlice{&solana.AccountMeta{PublicKey: acc1}, &solana.AccountMeta{PublicKey: acc2}}, }, { chainSelector: bnbTestnetChainSelector, @@ -195,6 +196,7 @@ func TestSecureMintAggregator_Aggregate(t *testing.T) { Block: 1100, Mintable: big.NewInt(200), }, + accCtx: solana.AccountMetaSlice{&solana.AccountMeta{PublicKey: acc1}, &solana.AccountMeta{PublicKey: acc2}}, }, }), f: 1, @@ -336,14 +338,6 @@ func TestSecureMintAggregatorConfig_Validation(t *testing.T) { expectError: true, errorMsg: "dataID must be 16 bytes", }, - { - name: "solana account context with invalid public key", - chainSelector: "1", - dataID: "0x01c508f42b0201320000000000000000", - solanaAccounts: solana.AccountMetaSlice{&solana.AccountMeta{PublicKey: [32]byte{}}}, - expectError: true, - errorMsg: "solana account context public key must not be all zeros", - }, } for _, tt := range tests { @@ -373,7 +367,6 @@ func TestSecureMintAggregatorConfig_Validation(t *testing.T) { require.NoError(t, err) assert.Equal(t, tt.expectedChainSelector, aggregator.(*SecureMintAggregator).config.TargetChainSelector) assert.Equal(t, tt.expectedDataID, aggregator.(*SecureMintAggregator).config.DataID) - assert.Equal(t, tt.solanaAccounts, aggregator.(*SecureMintAggregator).config.Solana.AccountContext) }) } } @@ -384,6 +377,7 @@ type ocrTriggerEventData struct { chainSelector chainSelector seqNr uint64 report *secureMintReport + accCtx solana.AccountMetaSlice } func createSecureMintObservations(t *testing.T, events []ocrTriggerEventData) map[ocrcommon.OracleID][]values.Value { @@ -421,10 +415,12 @@ func createSecureMintObservations(t *testing.T, events []ocrTriggerEventData) ma }, } - // Wrap in values.Value - val, err := values.Wrap(triggerEvent) + // wrap with account context if present + val, err := values.Wrap(map[string]any{ + "event": triggerEvent, + "solana": event.accCtx, + }) require.NoError(t, err) - oracleObservations = append(oracleObservations, val) } diff --git a/pkg/capabilities/consensus/ocr3/ocr3cap/ocr3cap_common-schema.json b/pkg/capabilities/consensus/ocr3/ocr3cap/ocr3cap_common-schema.json index dd4b9b6992..4318a2b5bd 100644 --- a/pkg/capabilities/consensus/ocr3/ocr3cap/ocr3cap_common-schema.json +++ b/pkg/capabilities/consensus/ocr3/ocr3cap/ocr3cap_common-schema.json @@ -46,7 +46,7 @@ }, "encoder": { "type": "string", - "enum": ["EVM", "ValueMap"] + "enum": ["EVM","Borsh","ValueMap"] }, "encoder_config": { "type": "object", diff --git a/pkg/capabilities/consensus/ocr3/ocr3cap/ocr3cap_common_generated.go b/pkg/capabilities/consensus/ocr3/ocr3cap/ocr3cap_common_generated.go index 6d58692968..505f876c58 100644 --- a/pkg/capabilities/consensus/ocr3/ocr3cap/ocr3cap_common_generated.go +++ b/pkg/capabilities/consensus/ocr3/ocr3cap/ocr3cap_common_generated.go @@ -11,6 +11,8 @@ import ( type Encoder string +const EncoderBorsh Encoder = "Borsh" + type EncoderConfig map[string]interface{} const EncoderEVM Encoder = "EVM" @@ -18,6 +20,7 @@ const EncoderValueMap Encoder = "ValueMap" var enumValues_Encoder = []interface{}{ "EVM", + "Borsh", "ValueMap", }