ocr3types

package
v0.0.0-...-fd3cab2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 19, 2024 License: MIT Imports: 6 Imported by: 14

Documentation

Index

Constants

View Source
const (
	MaxMaxMercuryObservationLength = 16 * 1024
	MaxMaxMercuryReportLength      = 2 * mib
)
View Source
const (
	MaxMaxQueryLength       = 5 * mib
	MaxMaxObservationLength = 1 * mib
	MaxMaxOutcomeLength     = 5 * mib
	MaxMaxReportLength      = 5 * mib
	MaxMaxReportCount       = 2000
)

It's much easier to increase these than to decrease them, so we start with conservative values. Talk to the maintainers if you need higher limits for your plugin.

Variables

This section is empty.

Functions

This section is empty.

Types

type ContractTransmitter

type ContractTransmitter[RI any] interface {

	// Transmit sends the report to the on-chain smart contract's Transmit
	// method.
	//
	// In most cases, implementations of this function should store the
	// transmission in a queue/database/..., but perform the actual
	// transmission (and potentially confirmation) of the transaction
	// asynchronously.
	Transmit(
		context.Context,
		types.ConfigDigest,
		uint64,
		ReportWithInfo[RI],
		[]types.AttributedOnchainSignature,
	) error

	// Account from which the transmitter invokes the contract
	FromAccount() (types.Account, error)
}

ContractTransmitter sends new reports to a smart contract or other system.

All its functions should be thread-safe.

type CountingMercuryPlugin

type CountingMercuryPlugin struct {
	// contains filtered or unexported fields
}

func (*CountingMercuryPlugin) Close

func (p *CountingMercuryPlugin) Close() error

func (*CountingMercuryPlugin) Observation

func (p *CountingMercuryPlugin) Observation(ctx context.Context, repts types.ReportTimestamp, previousReport types.Report) (types.Observation, error)

func (*CountingMercuryPlugin) Report

type CountingMercuryPluginFactory

type CountingMercuryPluginFactory struct{}

func (*CountingMercuryPluginFactory) NewMercuryPlugin

type Database

type Database interface {
	types.ConfigDatabase
	ProtocolStateDatabase
}

type MercuryPlugin

type MercuryPlugin interface {
	// Observation gets an observation from the underlying data source. Returns
	// a value or an error.
	//
	// You may assume that previousReport contains the last report that was
	// generated by the protocol instance, even if the MercuryPlugin instance
	// or the process hosting it were restarted in the meantime. The "genesis"
	// previousReport is empty.
	//
	// You may assume that the sequence of epochs and the sequence of rounds
	// within an epoch are monotonically increasing during the lifetime
	// of an instance of this interface.
	Observation(ctx context.Context, repts types.ReportTimestamp, previousReport types.Report) (types.Observation, error)

	// Decides whether a report (destined for the contract) should be generated
	// in this round. If yes, also constructs the report.
	//
	// You may assume that previousReport contains the last report that was
	// generated by the protocol instance, even if the MercuryPlugin instance
	// or the process hosting it were restarted in the meantime. The "genesis"
	// previousReport is empty.
	//
	// You may assume that the sequence of epochs and the sequence of rounds
	// within an epoch are monotonically increasing during the lifetime
	// of an instance of this interface.
	Report(repts types.ReportTimestamp, previousReport types.Report, aos []types.AttributedObservation) (bool, types.Report, error)

	// If Close is called a second time, it may return an error but must not
	// panic. This will always be called when a ReportingPlugin is no longer
	// needed, e.g. on shutdown of the protocol instance or shutdown of the
	// oracle node. This will only be called after any calls to other functions
	// of the ReportingPlugin will have completed.
	Close() error
}

A MercuryPlugin allows plugging custom logic into the OCR protocol. The OCR protocol handles cryptography, networking, ensuring that a sufficient number of nodes is in agreement about any report, transmitting the report to another system, etc... The MercuryPlugin handles application-specific logic. To do so, the MercuryPlugin defines a number of callbacks that are called by the OCR protocol logic at certain points in the protocol's execution flow. The report generated by the MercuryPlugin must be in a format understood by contract that the reports are transmitted to.

We assume that each correct node participating in the protocol instance will be running the same MercuryPlugin implementation. However, not all nodes may be correct; up to f nodes be faulty in arbitrary ways (aka byzantine faults). For example, faulty nodes could be down, have intermittent connectivity issues, send garbage messages, or be controlled by an adversary.

For a protocol round where everything is working correctly, followers will call Observation and Report. If a sufficient number of followers agree on a report, ShouldAcceptAttestedReport will be called as well. If ShouldAcceptAttestedReport returns true, ShouldTransmitAcceptedReport will be called. However, a MercuryPlugin must also correctly handle the case where faults occur.

In particular, a MercuryPlugin must deal with cases where:

- only a subset of the functions on the MercuryPlugin are invoked for a given round

- an arbitrary number of epochs and rounds has been skipped between invocations of the MercuryPlugin

- the observation returned by Observation is not included in the list of AttributedObservations passed to Report

- an observation is malformed. (For defense in depth, it is also strongly recommended that malformed reports are handled gracefully.)

- instances of the MercuryPlugin run by different oracles have different call traces. E.g., the MercuryPlugin's Observation function may have been invoked on node A, but not on node B.

All functions on a MercuryPlugin should be thread-safe.

All functions that take a context as their first argument may still do cheap computations after the context expires, but should stop any blocking interactions with outside services (APIs, database, ...) and return as quickly as possible. (Rough rule of thumb: any such computation should not take longer than a few ms.) A blocking function may block execution of the entire protocol instance!

For a given OCR protocol instance, there can be many (consecutive) instances of a MercuryPlugin, e.g. due to software restarts. If you need MercuryPlugin state to survive across restarts, you should persist it. A MercuryPlugin instance will only ever serve a single protocol instance. When we talk about "instance" below, we typically mean MercuryPlugin instances, not protocol instances.

type MercuryPluginConfig

type MercuryPluginConfig struct {
	ConfigDigest types.ConfigDigest

	// OracleID (index) of the oracle executing this plugin instance.
	OracleID commontypes.OracleID

	// N is the total number of nodes.
	N int

	// F is an upper bound on the number of faulty nodes.
	F int

	// Encoded configuration for the contract
	OnchainConfig []byte

	// Encoded configuration for the plugin disseminated through the
	// contract. This value is only passed through the contract, but otherwise
	// ignored by it.
	OffchainConfig []byte

	// Estimate of the duration between rounds. You should not rely on this
	// value being accurate. Rounds might occur more or less frequently than
	// estimated.
	//
	// This value is intended for estimating the load incurred by a
	// ReportingPlugin before running it and for configuring caches.
	EstimatedRoundInterval time.Duration

	// Maximum duration the plugin's functions are allowed to take
	MaxDurationObservation time.Duration
}

type MercuryPluginFactory

type MercuryPluginFactory interface {
	// Creates a new mercury-specific reporting plugin instance. The instance may have
	// associated goroutines or hold system resources, which should be
	// released when its Close() function is called.
	NewMercuryPlugin(MercuryPluginConfig) (MercuryPlugin, MercuryPluginInfo, error)
}

type MercuryPluginInfo

type MercuryPluginInfo struct {
	// Used for debugging purposes.
	Name string

	Limits MercuryPluginLimits
}

type MercuryPluginLimits

type MercuryPluginLimits struct {
	MaxObservationLength int
	MaxReportLength      int
}

Limits for data returned by the MercuryPlugin. Used for computing rate limits and defending against outsized messages. Messages are checked against these values during (de)serialization. Be careful when changing these values, they could lead to different versions of a MercuryPlugin being unable to communicate with each other.

type OnchainKeyring

type OnchainKeyring[RI any] interface {
	// PublicKey returns the public key of the keypair used by Sign.
	PublicKey() types.OnchainPublicKey

	// Sign returns a signature over Report.
	Sign(types.ConfigDigest, uint64, ReportWithInfo[RI]) (signature []byte, err error)

	// Verify verifies a signature over ReportContext and Report allegedly
	// created from OnchainPublicKey.
	//
	// Implementations of this function must gracefully handle malformed or
	// adversarially crafted inputs.
	Verify(_ types.OnchainPublicKey, _ types.ConfigDigest, seqNr uint64, _ ReportWithInfo[RI], signature []byte) bool

	// Maximum length of a signature
	MaxSignatureLength() int
}

OnchainKeyring provides cryptographic signatures that need to be verifiable on the targeted blockchain. The underlying cryptographic primitives may be different on each chain; for example, on Ethereum one would use ECDSA over secp256k1 and Keccak256, whereas on Solana one would use Ed25519 and SHA256.

All its functions should be thread-safe.

type Outcome

type Outcome []byte

type OutcomeContext

type OutcomeContext struct {
	// SeqNr of an OCR3 round/outcome. This is guaranteed to increase
	// in increments of one, i.e. for each SeqNr exactly one Outcome will
	// be generated.
	// The initial SeqNr value is 1. Its PreviousOutcome is nil.
	SeqNr uint64
	// This is guaranteed (!) to be the unique outcome with sequence number
	// (SeqNr-1).
	PreviousOutcome Outcome

	// Deprecated: exposed for legacy compatibility, do not rely on this
	// unless you have a really good reason.
	Epoch uint64
	// Deprecated: exposed for legacy compatibility, do not rely on this
	// unless you have a really good reason.
	Round uint64
}

type ProtocolStateDatabase

type ProtocolStateDatabase interface {
	// In case the key is not found, nil should be returned.
	ReadProtocolState(ctx context.Context, configDigest types.ConfigDigest, key string) ([]byte, error)
	// Writing with a nil value is the same as deleting.
	WriteProtocolState(ctx context.Context, configDigest types.ConfigDigest, key string, value []byte) error
}

ProtocolStateDatabase persistently stores protocol state to survive process restarts. Expect Write to be called far more frequently than Read.

All its functions should be thread-safe.

type Quorum

type Quorum int
const (

	// Guarantees at least one honest observation
	QuorumFPlusOne Quorum = 1_000_000 + iota
	// Guarantees an honest majority of observations
	QuorumTwoFPlusOne
	// Guarantees that all sets of observations overlap in at least one honest oracle
	QuorumByzQuorum
	// Maximal number of observations we can rely on being available
	QuorumNMinusF
)
const (
	// Deprecated: Old version of QuorumFPlusOne
	OldQuorumFPlusOne Quorum = types.MaxOracles + 1 + iota
	// Deprecated: Old version of QuorumTwoFPlusOne
	OldQuorumTwoFPlusOne
	// Deprecated: Old version of QuorumByzQuorum
	OldQuorumByzQuorum
	// Deprecated: Old version of QuorumNMinusF
	OldQuorumNMinusF
)

type ReportWithInfo

type ReportWithInfo[RI any] struct {
	Report types.Report
	// Metadata about the report passed to transmitter, keyring, etc..., e.g.
	// to trace flow of report through the system.
	Info RI
}

type ReportingPlugin

type ReportingPlugin[RI any] interface {
	// Query creates a Query that is sent from the leader to all follower nodes
	// as part of the request for an observation. Be careful! A malicious leader
	// could equivocate (i.e. send different queries to different followers.)
	// Many applications will likely be better off always using an empty query
	// if the oracles don't need to coordinate on what to observe (e.g. in case
	// of a price feed) or the underlying data source offers an (eventually)
	// consistent view to different oracles (e.g. in case of observing a
	// blockchain).
	//
	// You may assume that the outctx.SeqNr is increasing monotonically (though
	// *not* strictly) across the lifetime of a protocol instance and that
	// outctx.previousOutcome contains the consensus outcome with sequence
	// number (outctx.SeqNr-1).
	Query(ctx context.Context, outctx OutcomeContext) (types.Query, error)

	// Observation gets an observation from the underlying data source. Returns
	// a value or an error.
	//
	// You may assume that the outctx.SeqNr is increasing monotonically (though
	// *not* strictly) across the lifetime of a protocol instance and that
	// outctx.previousOutcome contains the consensus outcome with sequence
	// number (outctx.SeqNr-1).
	Observation(ctx context.Context, outctx OutcomeContext, query types.Query) (types.Observation, error)

	// Should return an error if an observation isn't well-formed.
	// Non-well-formed  observations will be discarded by the protocol. This
	// function should be pure. This is called for each observation, don't do
	// anything slow in here.
	//
	// You may assume that the outctx.SeqNr is increasing monotonically (though
	// *not* strictly) across the lifetime of a protocol instance and that
	// outctx.previousOutcome contains the consensus outcome with sequence
	// number (outctx.SeqNr-1).
	ValidateObservation(outctx OutcomeContext, query types.Query, ao types.AttributedObservation) error

	// ObservationQuorum returns the minimum number of valid (according to
	// ValidateObservation) observations needed to construct an outcome.
	//
	// This function should be pure. Don't do anything slow in here.
	//
	// This is an advanced feature. The "default" approach (what OCR1 & OCR2
	// did) is to have an empty ValidateObservation function and return
	// QuorumTwoFPlusOne from this function.
	ObservationQuorum(outctx OutcomeContext, query types.Query) (Quorum, error)

	// Generates an outcome for a seqNr, typically based on the previous
	// outcome, the current query, and the current set of attributed
	// observations.
	//
	// This function should be pure. Don't do anything slow in here.
	//
	// You may assume that the outctx.SeqNr is increasing monotonically (though
	// *not* strictly) across the lifetime of a protocol instance and that
	// outctx.previousOutcome contains the consensus outcome with sequence
	// number (outctx.SeqNr-1).
	//
	// You may assume that all provided observations have been validated by
	// ValidateObservation.
	Outcome(outctx OutcomeContext, query types.Query, aos []types.AttributedObservation) (Outcome, error)

	// Generates a (possibly empty) list of reports from an outcome. Each report
	// will be signed and possibly be transmitted to the contract. (Depending on
	// ShouldAcceptAttestedReport & ShouldTransmitAcceptedReport)
	//
	// This function should be pure. Don't do anything slow in here.
	//
	// This is likely to change in the future. It will likely be returning a
	// list of report batches, where each batch goes into its own Merkle tree.
	//
	// You may assume that the outctx.SeqNr is increasing monotonically (though
	// *not* strictly) across the lifetime of a protocol instance and that
	// outctx.previousOutcome contains the consensus outcome with sequence
	// number (outctx.SeqNr-1).
	Reports(seqNr uint64, outcome Outcome) ([]ReportWithInfo[RI], error)

	// Decides whether a report should be accepted for transmission. Any report
	// passed to this function will have been attested, i.e. signed by f+1
	// oracles.
	//
	// Don't make assumptions about the seqNr order in which this function
	// is called.
	ShouldAcceptAttestedReport(context.Context, uint64, ReportWithInfo[RI]) (bool, error)

	// Decides whether the given report should actually be broadcast to the
	// contract. This is invoked just before the broadcast occurs. Any report
	// passed to this function will have been signed by a quorum of oracles and
	// been accepted by ShouldAcceptAttestedReport.
	//
	// Don't make assumptions about the seqNr order in which this function
	// is called.
	//
	// As mentioned above, you should gracefully handle only a subset of a
	// ReportingPlugin's functions being invoked for a given report. For
	// example, due to reloading persisted pending transmissions from the
	// database upon oracle restart, this function  may be called with reports
	// that no other function of this instance of this interface has ever
	// been invoked on.
	ShouldTransmitAcceptedReport(context.Context, uint64, ReportWithInfo[RI]) (bool, error)

	// If Close is called a second time, it may return an error but must not
	// panic. This will always be called when a plugin is no longer
	// needed, e.g. on shutdown of the protocol instance or shutdown of the
	// oracle node. This will only be called after any calls to other functions
	// of the plugin have completed.
	Close() error
}

A ReportingPlugin allows plugging custom logic into the OCR3 protocol. The OCR protocol handles cryptography, networking, ensuring that a sufficient number of nodes is in agreement about any report, transmitting the report to the contract, etc... The ReportingPlugin handles application-specific logic. To do so, the ReportingPlugin defines a number of callbacks that are called by the OCR protocol logic at certain points in the protocol's execution flow. The report generated by the ReportingPlugin must be in a format understood by contract that the reports are transmitted to.

We assume that each correct node participating in the protocol instance will be running the same ReportingPlugin implementation. However, not all nodes may be correct; up to f nodes be faulty in arbitrary ways (aka byzantine faults). For example, faulty nodes could be down, have intermittent connectivity issues, send garbage messages, or be controlled by an adversary.

For a protocol round where everything is working correctly, followers will call Observation, ValidateObservation, Outcome, and Reports. For each report, ShouldAcceptAttestedReport will be called as well. If ShouldAcceptAttestedReport returns true, ShouldTransmitAcceptedReport will be called. However, an ReportingPlugin must also correctly handle the case where faults occur.

In particular, an ReportingPlugin must deal with cases where:

- only a subset of the functions on the ReportingPlugin are invoked for a given round

- an arbitrary number of seqnrs has been skipped between invocations of the ReportingPlugin

- the observation returned by Observation is not included in the list of AttributedObservations passed to Report

- a query or observation is malformed. (For defense in depth, it is also recommended that malformed outcomes are handled gracefully.)

- instances of the ReportingPlugin run by different oracles have different call traces. E.g., the ReportingPlugin's Observation function may have been invoked on node A, but not on node B.

All functions on an ReportingPlugin should be thread-safe.

All functions that take a context as their first argument may still do cheap computations after the context expires, but should stop any blocking interactions with outside services (APIs, database, ...) and return as quickly as possible. (Rough rule of thumb: any such computation should not take longer than a few ms.) A blocking function may block execution of the entire protocol instance on its node!

For a given OCR protocol instance, there can be many (consecutive) instances of an ReportingPlugin, e.g. due to software restarts. If you need ReportingPlugin state to survive across restarts, you should store it in the Outcome or persist it. An ReportingPlugin instance will only ever serve a single protocol instance. Outcomes and other state are are not preserved between protocol instances. A fresh protocol instance will start with a clean state. Carrying state between different protocol instances is up to the ReportingPlugin logic.

type ReportingPluginConfig

type ReportingPluginConfig struct {
	ConfigDigest types.ConfigDigest

	// OracleID (index) of the oracle executing this ReportingPlugin instance.
	OracleID commontypes.OracleID

	// N is the total number of nodes.
	N int

	// F is an upper bound on the number of faulty nodes, i.e. there are assumed
	// to be at most F faulty nodes.
	F int

	// Encoded configuration for the contract
	OnchainConfig []byte

	// Encoded configuration for the ORR3Plugin disseminated through the
	// contract. This value is only passed through the contract, but otherwise
	// ignored by it.
	OffchainConfig []byte

	// Estimate of the duration between rounds. You should not rely on this
	// value being accurate. Rounds might occur more or less frequently than
	// estimated.
	//
	// This value is intended for estimating the load incurred by a
	// ReportingPlugin before running it and for configuring caches.
	EstimatedRoundInterval time.Duration

	// Maximum duration the ReportingPlugin's functions are allowed to take
	MaxDurationQuery                        time.Duration
	MaxDurationObservation                  time.Duration
	MaxDurationShouldAcceptAttestedReport   time.Duration
	MaxDurationShouldTransmitAcceptedReport time.Duration
}

type ReportingPluginFactory

type ReportingPluginFactory[RI any] interface {
	// Creates a new reporting plugin instance. The instance may have
	// associated goroutines or hold system resources, which should be
	// released when its Close() function is called.
	NewReportingPlugin(ReportingPluginConfig) (ReportingPlugin[RI], ReportingPluginInfo, error)
}

type ReportingPluginInfo

type ReportingPluginInfo struct {
	// Used for debugging purposes.
	Name string

	Limits ReportingPluginLimits
}

type ReportingPluginLimits

type ReportingPluginLimits struct {
	MaxQueryLength       int
	MaxObservationLength int
	MaxOutcomeLength     int
	MaxReportLength      int
	MaxReportCount       int
}

Limits for data returned by the ReportingPlugin. Used for computing rate limits and defending against outsized messages. Messages are checked against these values during (de)serialization. Be careful when changing these values, they could lead to different versions of a ReportingPlugin being unable to communicate with each other.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL