ocr3_1types

package
v0.0.0-...-0a5e2f9 Latest Latest
Warning

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

Go to latest
Published: Dec 12, 2025 License: MIT Imports: 5 Imported by: 7

Documentation

Index

Constants

View Source
const (
	MaxMaxQueryBytes                = mib / 2
	MaxMaxObservationBytes          = mib / 2
	MaxMaxReportsPlusPrecursorBytes = 5 * mib
	MaxMaxReportBytes               = 5 * mib
	MaxMaxReportCount               = 2000

	MaxMaxKeyValueKeyBytes   = 1 * mib
	MaxMaxKeyValueValueBytes = 2 * mib

	MaxMaxKeyValueModifiedKeys                = 10_000
	MaxMaxKeyValueModifiedKeysPlusValuesBytes = 10 * mib

	MaxMaxBlobPayloadBytes = 5 * mib
)

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

View Source
var ErrKeyValueDatabaseDoesNotExist = fmt.Errorf("key value database does not exist")

Functions

This section is empty.

Types

type BlobBroadcastFetcher

type BlobBroadcastFetcher interface {
	BlobBroadcaster
	BlobFetcher
}

type BlobBroadcaster

type BlobBroadcaster interface {
	BroadcastBlob(ctx context.Context, payload []byte, expirationHint BlobExpirationHint) (BlobHandle, error)
}

type BlobExpirationHint

type BlobExpirationHint interface {
	// contains filtered or unexported methods
}

type BlobExpirationHintSequenceNumber

type BlobExpirationHintSequenceNumber struct{ SeqNr uint64 }

type BlobFetcher

type BlobFetcher interface {
	FetchBlob(ctx context.Context, handle BlobHandle) ([]byte, error)
}

type BlobHandle

type BlobHandle = blobtypes.BlobHandle

type Database

type Database = ocr3types.Database

type KeyValueDatabase

type KeyValueDatabase interface {
	NewReadWriteTransaction() (KeyValueDatabaseReadWriteTransaction, error)
	NewReadTransaction() (KeyValueDatabaseReadTransaction, error)

	Close() error
}

type KeyValueDatabaseFactory

type KeyValueDatabaseFactory interface {
	NewKeyValueDatabase(configDigest types.ConfigDigest) (KeyValueDatabase, error)
	NewKeyValueDatabaseIfExists(configDigest types.ConfigDigest) (KeyValueDatabase, error)
}

type KeyValueDatabaseIterator

type KeyValueDatabaseIterator interface {
	// Next prepares the next key-value pair for reading. It returns true on
	// success, or false if there is no next key-value pair or an error occurred
	// while preparing it.
	Next() bool
	// Key returns the key of the current key-value pair.
	Key() []byte
	// Value returns the value of the current key-value pair. An error value
	// indicates a failure to retrieve the value, and the caller is responsible
	// for handling it. Even if all errors are nil, [KeyValueDatabaseIterator.Err] must
	// be checked after iteration is completed.
	Value() ([]byte, error)
	// Err returns any error encountered during iteration. Must be checked after
	// the end of the iteration, to ensure that no key-value pairs were missed
	// due to iteration errors. Errors in [KeyValueDatabaseIterator.Value] are distinct
	// and will not cause a non-nil error.
	Err() error
	// Close closes the iterator and releases any resources associated with it.
	// Further iteration is prevented, i.e., [KeyValueDatabaseIterator.Next] will return
	// false. Must be called in any case, even if the iteration encountered any
	// error through [KeyValueDatabaseIterator.Value] or [KeyValueDatabaseIterator.Err].
	Close() error
}

KeyValueDatabaseIterator is a iterator over key-value pairs, in ascending order of keys.

Example usage:

it := kvReader.Range(loKey, hiKeyExcl)
defer it.Close()
for it.Next() {
    key := it.Key()
    value, err := it.Value()
    if err != nil {
        // handle error
    }
    // process key and value
}
if err := it.Err(); err != nil {
    // handle error
}

type KeyValueDatabaseReadTransaction

type KeyValueDatabaseReadTransaction interface {
	// If the key exists, the returned value must not be nil!
	Read(key []byte) ([]byte, error)
	// Range iterates over the key-value pairs with keys in the range [loKey,
	// hiKeyExcl), in ascending order of key. Key-value stores typically store
	// keys in a sorted order, making this a fast operation. loKey can be set to
	// 0 length or nil for iteration without a lower bound. hiKeyExcl can be set
	// to 0 length or nil for iteration without an upper bound.
	//
	// WARNING: DO NOT perform any writes/deletes to the key-value store while
	// the iterator is opened.
	Range(loKey []byte, hiKeyExcl []byte) KeyValueDatabaseIterator
	Discard()
}

type KeyValueDatabaseReadWriteTransaction

type KeyValueDatabaseReadWriteTransaction interface {
	KeyValueDatabaseReadTransaction
	// A value of nil is interpreted as an empty slice, and does *not* delete
	// the key. For deletions you must use the Delete method.
	Write(key []byte, value []byte) error
	Delete(key []byte) error

	Commit() error
}

type KeyValueIterator deprecated

type KeyValueIterator = KeyValueDatabaseIterator

Deprecated: Use KeyValueDatabaseIterator instead.

type KeyValueReadTransaction deprecated

type KeyValueReadTransaction = KeyValueDatabaseReadTransaction

Deprecated: Use KeyValueDatabaseReadTransaction instead.

type KeyValueReadWriteTransaction deprecated

type KeyValueReadWriteTransaction = KeyValueDatabaseReadWriteTransaction

Deprecated: Use KeyValueDatabaseReadWriteTransaction instead.

type KeyValueReadWriter deprecated

type KeyValueReadWriter = KeyValueStateReadWriter

Deprecated: Use KeyValueStateReadWriter instead.

type KeyValueReader deprecated

type KeyValueReader = KeyValueStateReader

Deprecated: Use KeyValueStateReader instead.

type KeyValueStateReadWriter

type KeyValueStateReadWriter interface {
	KeyValueStateReader
	Write(key []byte, value []byte) error
	Delete(key []byte) error
}

Provides read and write access to the replicated KeyValueState.

type KeyValueStateReader

type KeyValueStateReader interface {
	// A return value of nil indicates that the key does not exist.
	Read(key []byte) ([]byte, error)
}

Provides read access to the replicated KeyValueState.

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 seqNr is increasing strictly monotonically
	// across the lifetime of a protocol instance.
	//
	// The keyValueStateReader gives read access to the replicated KeyValueState
	// at the point after seqNr - 1 is committed. It must not be used outside the execution
	// of this function. (It's okay to use it anywhere in the call tree rooted at this function
	// and to pass it to separate goroutines, but they must stop using it before this
	// function returns.)
	//
	// The blobBroadcastFetcher enables broadcasting and fetching blobs. Broadcasting blobs
	// can be a more efficient data dissemination method than direct use of Query/Observation
	// when data is larger. It must not be used outside the execution
	// of this function. (It's okay to use it anywhere in the call tree rooted
	// at this function and to pass it to separate goroutines, but they must stop using it before this
	// function returns.)
	Query(
		ctx context.Context,
		seqNr uint64,
		keyValueStateReader KeyValueStateReader,
		blobBroadcastFetcher BlobBroadcastFetcher,
	) (types.Query, error)

	// Observation gets an observation from the underlying data source. Returns
	// a value or an error.
	//
	// You may assume that the seqNr is increasing strictly monotonically
	// across the lifetime of a protocol instance.
	//
	// The keyValueStateReader gives read access to the replicated KeyValueState
	// at the point after seqNr - 1 is committed. It must not be used outside the execution
	// of this function. (It's okay to use it anywhere in the call tree rooted at this function
	// and to pass it to separate goroutines, but they must stop using it before this
	// function returns.)
	//
	// The blobBroadcastFetcher enables broadcasting and fetching blobs. Broadcasting blobs
	// can be a more efficient data dissemination method than direct use of Query/Observation
	// when data is larger. It must not be used outside the execution
	// of this function. (It's okay to use it anywhere in the call tree rooted
	// at this function and to pass it to separate goroutines, but they must stop using it before this
	// function returns.)
	Observation(
		ctx context.Context,
		seqNr uint64,
		aq types.AttributedQuery,
		keyValueStateReader KeyValueStateReader,
		blobBroadcastFetcher BlobBroadcastFetcher,
	) (types.Observation, error)

	// ValidateObservation should return an error if an observation isn't well-formed.
	// Non-well-formed observations will be discarded by the protocol.
	//
	// This function must be pure‡.
	//
	// You may assume that the seqNr is increasing strictly monotonically
	// across the lifetime of a protocol instance.
	//
	// The keyValueStateReader gives read access to the replicated KeyValueState
	// at the point after seqNr - 1 is committed. It must not be used outside the execution
	// of this function. (It's okay to use it anywhere in the call tree rooted at this function
	// and to pass it to separate goroutines, but they must stop using it before this
	// function returns.)
	//
	// The blobFetcher enables fetching blobs. It must not be used outside the execution
	// of this function. (It's okay to use it anywhere in the call tree rooted at this function
	// and to pass it to separate goroutines, but they must stop using it before this
	// function returns.)
	ValidateObservation(
		ctx context.Context,
		seqNr uint64,
		aq types.AttributedQuery,
		ao types.AttributedObservation,
		keyValueStateReader KeyValueStateReader,
		blobFetcher BlobFetcher,
	) error

	// ObservationQuorum indicates whether the provided valid (according to
	// ValidateObservation) observations are sufficient to construct an outcome.
	//
	// This function must be pure‡.
	//
	// This is an advanced feature. The "default" approach (what OCR1 & OCR2
	// did) is to have this function call
	// quorumhelper.ObservationCountReachesObservationQuorum(QuorumTwoFPlusOne, ...)
	//
	// If you write a custom implementation, be sure to consider that Byzantine
	// oracles may not contribute valid observations, and you still want your
	// plugin to remain live. This function must be monotone in aos, i.e. if
	// it returns true for aos, it must also return true for any
	// superset of aos.
	//
	// The AttributedObservations are guaranteed to be from distinct oracles.
	//
	// The keyValueStateReader gives read access to the replicated KeyValueState
	// at the point after seqNr - 1 is committed. It must not be used outside the execution
	// of this function. (It's okay to use it anywhere in the call tree rooted at this function
	// and to pass it to separate goroutines, but they must stop using it before this
	// function returns.)
	//
	// The blobFetcher enables fetching blobs. It must not be used outside the execution
	// of this function. (It's okay to use it anywhere in the call tree rooted at this function
	// and to pass it to separate goroutines, but they must stop using it before this
	// function returns.)
	ObservationQuorum(
		ctx context.Context,
		seqNr uint64,
		aq types.AttributedQuery,
		aos []types.AttributedObservation,
		keyValueStateReader KeyValueStateReader,
		blobFetcher BlobFetcher,
	) (quorumReached bool, err error)

	// StateTransition modifies the state of the Reporting Plugin, based on
	// the attributed query and the set of attributed observations of the round.
	// Generates ReportsPlusPrecursor, which encodes a possibly empty list of
	// reports.
	//
	// This function must be pure‡.
	//
	// You may assume that the seqNr is increasing strictly monotonically
	// across the lifetime of a protocol instance.
	//
	// You may assume that the provided list of attributed observations has been
	// (1) validated by ValidateObservation on each element, (2) checked
	// by ObservationQuorum to have reached quorum, and (3) all observations are
	// from distinct oracles.
	//
	// The keyValueStateReadWriter gives read and write access to the replicated KeyValueState
	// from the point after seqNr - 1 is committed. Writing to the keyValueStateReadWriter allows
	// a ReportingPlugin to modify the replicated KeyValueState. The keyValueStateReadWriter must not be
	// used outside the execution of this function. (It's okay to use it anywhere in the call tree rooted
	// at this function and to pass it to separate goroutines, but they must stop using it before this
	// function returns.)
	//
	// The blobFetcher enables fetching blobs. It must not be used outside the execution
	// of this function. (It's okay to use it anywhere in the call tree rooted at this function
	// and to pass it to separate goroutines, but they must stop using it before this
	// function returns.)
	StateTransition(
		ctx context.Context,
		seqNr uint64,
		aq types.AttributedQuery,
		aos []types.AttributedObservation,
		keyValueStateReadWriter KeyValueStateReadWriter,
		blobFetcher BlobFetcher,
	) (ReportsPlusPrecursor, error)

	// Committed notifies the plugin that seqNr has been committed.
	// It might not be called for all sequence numbers.
	// This method return an error != nil will not lead to the round being aborted/not committed.
	//
	// Don't do anything slow in here.
	//
	// The KeyValueReader gives read access to the key-value store in the state
	// that it is after the StateTransition for seqNr is computed.
	Committed(
		ctx context.Context,
		seqNr uint64,
		keyValueStateReader KeyValueStateReader,
	) error

	// Reports generates a (possibly empty) list of reports from a ReportsPlusPrecursor. Each report
	// will be signed and possibly be transmitted to the contract. (Depending on
	// ShouldAcceptAttestedReport & ShouldTransmitAcceptedReport)
	//
	// This function must be pure‡.
	//
	// 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.
	Reports(
		ctx context.Context,
		seqNr uint64,
		reportsPlusPrecursor ReportsPlusPrecursor,
	) ([]ocr3types.ReportPlus[RI], error)

	// ShouldAcceptAttestedReport 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(
		ctx context.Context,
		seqNr uint64,
		reportWithInfo ocr3types.ReportWithInfo[RI],
	) (bool, error)

	// ShouldTransmitAcceptedReport 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(
		ctx context.Context,
		seqNr uint64,
		reportWithInfo ocr3types.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
}

Overview

A ReportingPlugin 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 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 (or offchain target system) that the reports are transmitted to.

Byzantine Fault Tolerance

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.

Execution Flow

For a protocol round where everything is working correctly, follower oracles will call Observation, ValidateObservation, ObservationQuorum, StateTransition, Committed, and Reports. The leader oracle will additionally call Query at the beginning of the round. For each report, ShouldAcceptAttestedReport will be called, iff the oracle is in the set of transmitters for the report. 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
  • the observation returned by Observation is not included in the list of AttributedObservations passed to StateTransition
  • a [Query] or [Observation] is malformed. (For defense in depth, it is also recommended that malformed elements in the key value state 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.

Engineering requirements for ReportingPlugin implementations

All functions on an ReportingPlugin must be thread-safe.

The execution of the functions in the ReportingPlugin is on the critical path of the protocol's execution. A blocking function may block the oracle from participating in the protocol. Functions should be designed to generally return as quickly as possible and honor context expiration. Context expiration may occur for a number of reasons, including (1) shutdown of the protocol instance, (2) the protocol's progression through epochs (whether they're abandoned or completed successfully), and (3) timeout parameters. See the documentation on ocr3_1config.PublicConfig for more information on how to configure timeouts.

Many functions on the ReportingPlugin are marked as pure‡. This is because they may be invoked in arbitrary order, and because they must return deterministic results in order to ensure agreement across different oracles. This means that they must act as pure functions, with a few important clarifications and exceptions:

  • In cases where the function returns an error, the determinism requirement is lifted. (But beware: if too many oracles encounter errors, the protocol may fail to progress.)
  • Side effects arising from calling methods on a KeyValueStateReader argument are allowed.
  • Side effects arising from calling methods on a KeyValueStateReadWriter argument are allowed.
  • Side effects arising from calling methods on a BlobFetcher argument are allowed.
  • Side effects arising from calling methods on a BlobBroadcastFetcher argument are allowed.
  • Unobservable side effects (e.g. memoization of expensive computations) are allowed. Be careful!
  • In cases where the function does not return an error, the context.Context argument must not affect the observable behavior of the function. Note that this does not preclude a function from returning an error on context expiration.

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 probably persist it in the KeyValueState. A ReportingPlugin instance will only ever serve a single protocol instance. State is 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 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(
		context.Context,
		ocr3types.ReportingPluginConfig,
		BlobBroadcastFetcher,
	) (ReportingPlugin[RI], ReportingPluginInfo, error)
}

type ReportingPluginInfo

type ReportingPluginInfo interface {
	// contains filtered or unexported methods
}

type ReportingPluginInfo1

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

	Limits ReportingPluginLimits
}

type ReportingPluginLimits

type ReportingPluginLimits struct {
	MaxQueryBytes                int
	MaxObservationBytes          int
	MaxReportsPlusPrecursorBytes int
	MaxReportBytes               int
	MaxReportCount               int

	// MaxKeyValueModifiedKeys upper bounds the number of modified keys inside
	// the StateTransition method. A key might be modified multiple times within
	// a single StateTransition, but will only count once towards the limit. A
	// modification that resets the value of a key to its original value at the
	// start of StateTransition will still count towards the limit.
	MaxKeyValueModifiedKeys int
	// MaxKeyValueModifiedKeysPlusValuesBytes upper bounds the cumulative bytes
	// for all modifications to key-values inside the StateTransition method.
	// For a write modification, both the key and the value count towards the
	// limit. For a delete modification, only the key counts towards the limit.
	// For each key, only its last modification counts towards the limit. A
	// modification that resets the value of a key to its original value at the
	// start of StateTransition will still count towards the limit.
	MaxKeyValueModifiedKeysPlusValuesBytes int

	// MaxBlobPayloadBytes upper bounds the payload bytes for a single blob. A
	// broadcast with a larger payload will be rejected.
	MaxBlobPayloadBytes int
	// MaxPerOracleUnexpiredBlobCumulativePayloadBytes upper bounds the
	// cumulative payload length for all unreaped blobs from a single oracle.
	// Inbound broadcasts that would violate this upper bound will be rejected.
	// Due to the asynchronous nature of blob reaping, an expired blob might not
	// be reaped yet, and thus might still be counted towards this upper bound.
	// The number of unreaped blobs eventually approximates the number of
	// unexpired blobs. Consequently, you must set this value loosely,
	// multiplying by a factor that accounts for a blob reaping interval in the
	// tens of seconds.
	MaxPerOracleUnexpiredBlobCumulativePayloadBytes int
	// MaxPerOracleUnexpiredBlobCount is upper bounds the number of unreaped
	// blobs from a single oracle. Inbound broadcasts that would violate this
	// upper bound will be rejected. Due to the asynchronous nature of blob
	// reaping, an expired blob might not be reaped yet, and thus might still be
	// counted towards this upper bound. The number of unreaped blobs eventually
	// approximates the number of unexpired blobs. Consequently, you must set
	// this value loosely, multiplying by a factor that accounts for a blob
	// reaping interval in the tens of seconds.
	MaxPerOracleUnexpiredBlobCount 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.

type ReportsPlusPrecursor

type ReportsPlusPrecursor []byte

The precursor value from which a list of ocr3types.ReportPlus is generated

Jump to

Keyboard shortcuts

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