Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -104,15 +103,16 @@ 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 {
targetChainSelector chainSelector
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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
Expand All @@ -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)
}
}

Expand All @@ -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)

Expand All @@ -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
}

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
})
}
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
},
"encoder": {
"type": "string",
"enum": ["EVM", "ValueMap"]
Copy link
Contributor

@krehermann krehermann Sep 4, 2025

Choose a reason for hiding this comment

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

why did this change? where is this changed used in this pr?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's a batch of codependant PRs actually (from description)
Solana PR introduces ocr3cap borsh encoder
Core PR bumps solana + common and resolves breaking changes.
But I can separate them even further (1 PR for aggregator and 1 for encoder) but they are pretty small so I thought it's okay to do it in one PR.

"enum": ["EVM","Borsh","ValueMap"]
},
"encoder_config": {
"type": "object",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading