From ac7eac4f28faf57dc3b979f931d4c64d85c345c5 Mon Sep 17 00:00:00 2001 From: kaleofduty <59616916+kaleofduty@users.noreply.github.com> Date: Thu, 3 Jul 2025 12:08:19 +0200 Subject: [PATCH 1/2] Update README --- README.md | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c655fd7b..71968884 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,36 @@ # libocr -libocr consists of a Go library and a set of Solidity smart contracts that implement the *Chainlink Offchain Reporting Protocol*, a [Byzantine fault tolerant](https://en.wikipedia.org/wiki/Byzantine_fault) protocol that allows a set of oracles to generate *offchain* an aggregate report of the oracles' observations of some underlying data source. This report is then transmitted to an onchain contract in a single transaction. +libocr consists of a Go library and a set of Solidity smart contracts that implements various versions of the *Chainlink Offchain Reporting Protocol*, a [Byzantine fault tolerant](https://en.wikipedia.org/wiki/Byzantine_fault) "consensus" protocol that allows a set of oracles to generate *offchain* an aggregate report of the oracles' observations of some underlying data source. This report is then transmitted to an onchain contract in a single transaction. -You may also be interested in [libocr's integration into the actual Chainlink node](https://github.com/smartcontractkit/chainlink/tree/develop/core/services/offchainreporting). +You may also be interested in libocr's integration into the actual Chainlink node. ([V1](https://github.com/smartcontractkit/chainlink/tree/develop/core/services/ocr) [V2](https://github.com/smartcontractkit/chainlink/tree/develop/core/services/ocr2) [V3](https://github.com/smartcontractkit/chainlink/tree/develop/core/services/ocr3)) ## Protocol Description -Protocol execution mostly happens offchain over a peer to peer network between Chainlink nodes. The nodes regularly elect a new leader node who drives the rest of the protocol. The protocol is designed to choose each leader fairly and quickly rotate away from leaders that aren’t making progress towards timely onchain reports. +Please see the whitepapers available at https://chainlinklabs.com/research for detailed protocol descriptions. -The leader regularly requests followers to provide freshly signed observations and aggregates them into a report. It then sends the aggregate report back to the followers and asks them to attest to the report's validity by signing it. If a quorum of followers approves the report, the leader assembles a final report with the quorum's signatures and broadcasts it to all followers. - -The nodes then attempt to transmit the final report to the smart contract according to a randomized schedule. Finally, the smart contract verifies that a quorum of nodes signed the report and exposes the median value to consumers. +## Protocol Versions +- OCR1 is deprecated and being phased out. +- OCR2 & OCR3 are in production. +- OCR3.1 is in alpha and excluded from any bug bounties at this time. ## Organization ``` -. -├── contract: Ethereum smart contracts +├── bigbigendian: helper package +├── commontypes: shared type definitions +├── contract: OCR1 Ethereum contracts +├── contract2: OCR2 Ethereum contracts +├── contract3: OCR3 Ethereum contracts ├── gethwrappers: go-ethereum bindings for the OCR1 contracts, generated with abigen ├── gethwrappers2: go-ethereum bindings for the OCR2 contracts, generated with abigen -├── networking: p2p networking layer -├── offchainreporting: offchain reporting protocol version 1 -├── offchainreporting2: offchain reporting protocol version 2 specific packages, not much here -├── offchainreporting2plus: offchain reporting protocol version 2 and beyond -├── permutation: helper package for generating permutations -└── subprocesses: helper package for managing go routines +├── gethwrappers3: go-ethereum bindings for the OCR3 contracts, generated with abigen +├── networking: OCR networking layer +├── offchainreporting: OCR1 +├── offchainreporting2: OCR2-specific +├── offchainreporting2plus: OCR2 and beyond (These versions share many interface definitions to make integration of new versions easier) +├── permutation: helper package +├── quorumhelper: helper package +├── ragep2p: p2p networking +└── subprocesses: helper package ``` From 3202604a6661233b09a4c335feb71a49f7cf5c50 Mon Sep 17 00:00:00 2001 From: kaleofduty <59616916+kaleofduty@users.noreply.github.com> Date: Thu, 3 Jul 2025 12:08:56 +0200 Subject: [PATCH 2/2] Improvements: - Move to go1.24 - OCR3.1 alpha Based on cb8e5eb65c417703b7d35c96a95b9e13a2d14020 Co-authored-by: Kostis Karantias Co-authored-by: Philipp Schindler <4274886+PhilippSchindler@users.noreply.github.com> Co-authored-by: stchrysa --- commontypes/types.go | 1 + go.mod | 8 +- go.sum | 8 +- internal/ringbuffer/ringbuffer.go | 93 + internal/util/generic.go | 8 + .../ocrendpointv3/responselimit/checker.go | 180 + .../ocrendpointv3/responselimit/policies.go | 55 + .../internal/ocrendpointv3/types/types.go | 31 + networking/ocr3_1_peer.go | 28 + networking/ocr_endpoint_v3.go | 604 +++ networking/peer_v2.go | 43 + .../{ocr3 => common}/minheap/minheap.go | 0 .../common.go => common/plugin_caller.go} | 8 +- .../{ocr3/protocol => common}/pool/pool.go | 0 .../ringbuffer/ringbuffer.go | 0 .../{ocr3 => common}/scheduler/scheduler.go | 2 +- .../internal/managed/limits/ocr3_1_limits.go | 232 ++ .../managed/managed_mercury_oracle.go | 3 +- .../internal/managed/managed_ocr3_1_oracle.go | 289 ++ .../internal/managed/managed_ocr3_oracle.go | 3 +- .../internal/ocr3/protocol/messagebuffer.go | 4 +- .../ocr3/protocol/outcome_generation.go | 5 +- .../protocol/outcome_generation_follower.go | 2 +- .../protocol/outcome_generation_leader.go | 2 +- .../ocr3/protocol/report_attestation.go | 5 +- .../internal/ocr3/protocol/transmission.go | 8 +- .../internal/ocr3_1/blobtypes/types.go | 259 ++ .../internal/ocr3_1/protocol/blob_endpoint.go | 147 + .../internal/ocr3_1/protocol/blob_exchange.go | 925 +++++ .../internal/ocr3_1/protocol/db.go | 32 + .../internal/ocr3_1/protocol/event.go | 241 ++ .../internal/ocr3_1/protocol/kv.go | 89 + .../internal/ocr3_1/protocol/message.go | 523 +++ .../internal/ocr3_1/protocol/messagebuffer.go | 32 + .../internal/ocr3_1/protocol/metrics.go | 100 + .../internal/ocr3_1/protocol/network.go | 84 + .../internal/ocr3_1/protocol/oracle.go | 462 +++ .../ocr3_1/protocol/outcome_generation.go | 421 ++ .../protocol/outcome_generation_follower.go | 1401 +++++++ .../protocol/outcome_generation_leader.go | 618 +++ .../internal/ocr3_1/protocol/pacemaker.go | 358 ++ .../internal/ocr3_1/protocol/queue/queue.go | 68 + .../ocr3_1/protocol/report_attestation.go | 696 ++++ .../internal/ocr3_1/protocol/round_context.go | 7 + .../internal/ocr3_1/protocol/signed_data.go | 770 ++++ .../protocol/state_block_synchronization.go | 367 ++ .../ocr3_1/protocol/state_persistence.go | 371 ++ .../protocol/state_tree_synchronization.go | 8 + .../internal/ocr3_1/protocol/telemetry.go | 16 + .../internal/ocr3_1/protocol/transmission.go | 289 ++ .../internal/ocr3_1/protocol/types.go | 28 + .../offchainreporting3_1_db.pb.go | 224 + .../offchainreporting3_1_messages.pb.go | 3677 +++++++++++++++++ .../offchainreporting3_1_telemetry.pb.go | 837 ++++ .../ocr3_1/serialization/serialization.go | 1239 ++++++ .../ocr3_1/serialization/telemetry.go | 1 + .../internal/shim/ocr3_1_database.go | 129 + .../internal/shim/ocr3_1_key_value_store.go | 419 ++ .../internal/shim/ocr3_1_reporting_plugin.go | 96 + .../shim/ocr3_1_serializing_endpoint.go | 402 ++ .../internal/shim/ocr3_1_telemetry_sender.go | 58 + offchainreporting2plus/ocr3_1types/blob.go | 34 + offchainreporting2plus/ocr3_1types/db.go | 27 + offchainreporting2plus/ocr3_1types/kv.go | 82 + offchainreporting2plus/ocr3_1types/plugin.go | 266 ++ offchainreporting2plus/oracle.go | 79 + offchainreporting2plus/types/network.go | 150 + offchainreporting2plus/types/types.go | 30 +- 68 files changed, 17658 insertions(+), 26 deletions(-) create mode 100644 internal/ringbuffer/ringbuffer.go create mode 100644 networking/internal/ocrendpointv3/responselimit/checker.go create mode 100644 networking/internal/ocrendpointv3/responselimit/policies.go create mode 100644 networking/internal/ocrendpointv3/types/types.go create mode 100644 networking/ocr3_1_peer.go create mode 100644 networking/ocr_endpoint_v3.go rename offchainreporting2plus/internal/{ocr3 => common}/minheap/minheap.go (100%) rename offchainreporting2plus/internal/{ocr3/protocol/common.go => common/plugin_caller.go} (94%) rename offchainreporting2plus/internal/{ocr3/protocol => common}/pool/pool.go (100%) rename offchainreporting2plus/internal/{ocr3/protocol => common}/ringbuffer/ringbuffer.go (100%) rename offchainreporting2plus/internal/{ocr3 => common}/scheduler/scheduler.go (99%) create mode 100644 offchainreporting2plus/internal/managed/limits/ocr3_1_limits.go create mode 100644 offchainreporting2plus/internal/managed/managed_ocr3_1_oracle.go create mode 100644 offchainreporting2plus/internal/ocr3_1/blobtypes/types.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/blob_endpoint.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/blob_exchange.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/db.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/event.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/kv.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/message.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/messagebuffer.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/metrics.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/network.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/oracle.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/outcome_generation.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/outcome_generation_follower.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/outcome_generation_leader.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/pacemaker.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/queue/queue.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/report_attestation.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/round_context.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/signed_data.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/state_block_synchronization.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/state_persistence.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/state_tree_synchronization.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/telemetry.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/transmission.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/types.go create mode 100644 offchainreporting2plus/internal/ocr3_1/serialization/offchainreporting3_1_db.pb.go create mode 100644 offchainreporting2plus/internal/ocr3_1/serialization/offchainreporting3_1_messages.pb.go create mode 100644 offchainreporting2plus/internal/ocr3_1/serialization/offchainreporting3_1_telemetry.pb.go create mode 100644 offchainreporting2plus/internal/ocr3_1/serialization/serialization.go create mode 100644 offchainreporting2plus/internal/ocr3_1/serialization/telemetry.go create mode 100644 offchainreporting2plus/internal/shim/ocr3_1_database.go create mode 100644 offchainreporting2plus/internal/shim/ocr3_1_key_value_store.go create mode 100644 offchainreporting2plus/internal/shim/ocr3_1_reporting_plugin.go create mode 100644 offchainreporting2plus/internal/shim/ocr3_1_serializing_endpoint.go create mode 100644 offchainreporting2plus/internal/shim/ocr3_1_telemetry_sender.go create mode 100644 offchainreporting2plus/ocr3_1types/blob.go create mode 100644 offchainreporting2plus/ocr3_1types/db.go create mode 100644 offchainreporting2plus/ocr3_1types/kv.go create mode 100644 offchainreporting2plus/ocr3_1types/plugin.go create mode 100644 offchainreporting2plus/types/network.go diff --git a/commontypes/types.go b/commontypes/types.go index ba5b35fe..37927613 100644 --- a/commontypes/types.go +++ b/commontypes/types.go @@ -6,6 +6,7 @@ import ( "net" "strings" + // TODO: is there a way to remove this dependency? ragetypes "github.com/smartcontractkit/libocr/ragep2p/types" ) diff --git a/go.mod b/go.mod index 5df5acaf..5c6ea6c9 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/smartcontractkit/libocr -go 1.23.0 +go 1.24 -toolchain go1.23.6 +toolchain go1.24.4 require ( github.com/ethereum/go-ethereum v1.15.3 @@ -65,7 +65,7 @@ require ( github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c // indirect github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -102,7 +102,7 @@ require ( github.com/urfave/cli/v2 v2.27.5 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect - golang.org/x/net v0.36.0 // indirect + golang.org/x/net v0.34.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect diff --git a/go.sum b/go.sum index d0671025..c512fde8 100644 --- a/go.sum +++ b/go.sum @@ -141,8 +141,8 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -312,8 +312,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= -golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/internal/ringbuffer/ringbuffer.go b/internal/ringbuffer/ringbuffer.go new file mode 100644 index 00000000..b19337ac --- /dev/null +++ b/internal/ringbuffer/ringbuffer.go @@ -0,0 +1,93 @@ +package ringbuffer + +import "fmt" + +// RingBuffer implements a fixed capacity ring buffer for items of type T. +// NOTE: THIS IMPLEMENTATION IS NOT SAFE FOR CONCURRENT USE. +type RingBuffer[T any] struct { + first int // index of the front (=oldest) element + size int // number of elements currently stored in this ring buffer + items []T // fixed size buffer holding the elements +} + +func NewRingBuffer[T any](cap int) *RingBuffer[T] { + if cap <= 0 { + panic(fmt.Sprintf("NewRingBuffer: cap must be positive, got %d", cap)) + } + return &RingBuffer[T]{ + 0, + 0, + make([]T, cap), + } +} + +func (rb *RingBuffer[T]) Size() int { + return rb.size +} + +func (rb *RingBuffer[T]) Cap() int { + return len(rb.items) +} + +func (rb *RingBuffer[T]) IsEmpty() bool { + return rb.size == 0 +} + +func (rb *RingBuffer[T]) IsFull() bool { + return rb.size == len(rb.items) +} + +// Peek returns the front (=oldest) item without removing it. +// Return false as second argument if there are no items in the ring buffer. +func (rb *RingBuffer[T]) Peek() (result T, ok bool) { + if rb.size > 0 { + ok = true + result = rb.items[rb.first] + } + return result, ok +} + +// Pop removes and returns the front (=oldest) item. +// Return false as second argument if there are no items in the ring buffer. +func (rb *RingBuffer[T]) Pop() (result T, ok bool) { + result, ok = rb.Peek() + if ok { + var zero T + rb.items[rb.first] = zero + rb.first = (rb.first + 1) % len(rb.items) + rb.size-- + } + return result, ok +} + +// Try to push a new item to the back of the ring buffer. +// Returns +// - true if the item was added, or +// - false if the item cannot be added because the buffer is currently full. +func (rb *RingBuffer[T]) TryPush(item T) (ok bool) { + if rb.IsFull() { + return false + } + rb.items[(rb.first+rb.size)%len(rb.items)] = item + rb.size++ + return true +} + +// Push new item to the back of the ring buffer. +// If the buffer is currently full, the front (=oldest) item is evicted and returned to make space for the new item. +func (rb *RingBuffer[T]) PushEvict(item T) (evicted T, didEvict bool) { + if rb.IsFull() { + // Evict the oldest item to be returned. + evicted = rb.items[rb.first] + didEvict = true + + // Push the new item to new empty space and update the first index to the next (oldest) item. + rb.items[rb.first] = item + rb.first = (rb.first + 1) % len(rb.items) + } else { + // Perform a normal push operation (which is known to be successful as the buffer is not full). + rb.items[(rb.first+rb.size)%len(rb.items)] = item + rb.size++ + } + return evicted, didEvict +} diff --git a/internal/util/generic.go b/internal/util/generic.go index ef97372e..95950699 100644 --- a/internal/util/generic.go +++ b/internal/util/generic.go @@ -11,3 +11,11 @@ func NilCoalesce[T any](maybe *T, default_ T) T { return default_ } } + +func NilCoalesceSlice[T any](maybe []T) []T { + if maybe != nil { + return maybe + } else { + return []T{} + } +} diff --git a/networking/internal/ocrendpointv3/responselimit/checker.go b/networking/internal/ocrendpointv3/responselimit/checker.go new file mode 100644 index 00000000..9ece7032 --- /dev/null +++ b/networking/internal/ocrendpointv3/responselimit/checker.go @@ -0,0 +1,180 @@ +package responselimit + +import ( + "math/rand" + "sync" + "time" + + "github.com/smartcontractkit/libocr/networking/internal/ocrendpointv3/types" +) + +type ResponseCheckResult byte + +// Enum specifying the list of return values for responseChecker.CheckResponse(...). +const ( + // A response is rejected if the policy + // (1) was not found, or + // (2) was expired, or + // (3) was found but decided to reject the request. + // + // As policies are automatically cleaned up (in some non-deterministic manner), there is no way to distinguish + // cases (1) and (2), and for simplicity also case (3) is handled identically. + // + // We intentionally use 0 as the first enum value for Reject as a safe default here. + ResponseCheckResultReject ResponseCheckResult = iota + + // A (non-expired) policy was found, and the policy did decide that the response should be allowed. + ResponseCheckResultAllow +) + +type responseCheckerMapEntry struct { + index int + policy ResponsePolicy + streamID types.StreamID +} + +// Data structure for keeping track of open requests until a set expiry date. +// +// Cleanup of expired entries is performed automatically. Whenever a new entry is added, two random entries are checked +// and removed if expired. This ensures that, on expectation, the number of tracked entries is approx. 2x the number +// of non-expired entries. +// +// SetPolicy(...) and CheckResponse(...) are O(1) operations. +type ResponseChecker struct { + mutex sync.Mutex + rids []types.RequestID + policies map[types.RequestID]responseCheckerMapEntry + rng *rand.Rand +} + +func NewResponseChecker() *ResponseChecker { + return &ResponseChecker{ + sync.Mutex{}, + make([]types.RequestID, 0), + make(map[types.RequestID]responseCheckerMapEntry), + rand.New(rand.NewSource(time.Now().UnixNano())), + } +} + +// Sets the policy for a given (fresh) request ID. After setting the policy, calling Pop(...) for the same ID before the +// policy expires returns the policy Set with this function. If a policy with the provided ID is already present, it +// will be overwritten. +func (c *ResponseChecker) SetPolicy(sid types.StreamID, rid types.RequestID, policy ResponsePolicy) { + c.mutex.Lock() + defer c.mutex.Unlock() + + // Lookup an existing policy for the provided request ID. + // If it exists, we override the policy, keeping its location at the prior index. + // Otherwise, we need use a new index and also track the request ID in the c.rids list. + entry, exists := c.policies[rid] + if exists { + entry = responseCheckerMapEntry{entry.index, policy, sid} + } else { + // We set entry.index = len(c.rids) to let it point to the request ID we will append to c.rids list. + entry = responseCheckerMapEntry{len(c.rids), policy, sid} + c.rids = append(c.rids, rid) + } + + // Actually save the policy update back to the c.policies map. + c.policies[rid] = entry + + // If the number of tracked policies increased, we check 2 random policies and remove them if expired. This way + // the number of tracked policies only grows to 2x the number of non-expired policies in expectation. + if !exists { + c.cleanupExpired() + } +} + +// Lookup the policy for a given response and check if it should be allowed or rejected. +// See responseCheckResult for additional documentation on the potential return values of this function. +func (c *ResponseChecker) CheckResponse(sid types.StreamID, rid types.RequestID, size int) ResponseCheckResult { + c.mutex.Lock() + defer c.mutex.Unlock() + + entry, exists := c.policies[rid] + if !exists { + return ResponseCheckResultReject + } + if entry.streamID != sid { + return ResponseCheckResultReject + } + + now := time.Now() + if entry.policy.isPolicyExpired(now) { + c.removeEntry(rid, entry.index) + return ResponseCheckResultReject + } + + policyResult := entry.policy.checkResponse(rid, size, now) + + // Recheck the policy of expiry, useful to cleanup one-time-use policies immediately. + if entry.policy.isPolicyExpired(now) { + c.removeEntry(rid, entry.index) + } + + return policyResult +} + +// Removes all currently tracked policies for the given stream ID. To ensure that responses sent to a stream cannot be +// accepted after this stream is closed and reopened, this function is called when the Stream is closed (and removed +// from the demuxer). +func (c *ResponseChecker) ClearPoliciesForStream(sid types.StreamID) { + c.mutex.Lock() + defer c.mutex.Unlock() + + for i := 0; i < len(c.rids); i++ { + rid := c.rids[i] + policy := c.policies[rid] + + if policy.streamID == sid { + // We found a policy which matches the given stream ID. + // So we remove the entry from the list of request IDs and policies. + c.removeEntry(rid, i) + + // The above removeEntry(...) removes c.rids[i], thus in the next iteration index its value is replaced + // by a different request ID. We decrement index i to ensure that we don't skip the new value at index i. + i-- + } + } +} + +// Check two random policies. A checked policy is removed if it is found to be expired. +func (c *ResponseChecker) cleanupExpired() { + now := time.Now() + + // At most 2 iterations, enter loop body only if c.rids is non empty. + for i := 0; i < 2 && len(c.rids) > 0; i++ { + // Select a random policy. + index := c.rng.Intn(len(c.rids)) + id := c.rids[index] + policy := c.policies[id].policy + + // Remove it if it is expired. + if policy.isPolicyExpired(now) { + c.removeEntry(id, index) + } + } +} + +// Remove the policy for a given request ID from (1) the map of policies and (2) the list of request IDs. +func (c *ResponseChecker) removeEntry(id types.RequestID, index int) { + // Remove the entry from the map of polices. + delete(c.policies, id) + + // Handle the "index == last-index" corner case separately. + // This avoids wrongfully reinserting the deleted policy. + if index == len(c.rids)-1 { + c.rids = c.rids[0 : len(c.rids)-1] + return + } + + // Swap the last entry's id to the position of the to be removed id, and remove the last value from the rids list. + lastID := c.rids[len(c.rids)-1] + c.rids[index] = lastID + c.rids = c.rids[0 : len(c.rids)-1] + + // Update the index point for the c.policies[lastId] to point to the now changed position. + lastEntry := c.policies[lastID] + lastEntry.index = index + c.policies[lastID] = lastEntry +} diff --git a/networking/internal/ocrendpointv3/responselimit/policies.go b/networking/internal/ocrendpointv3/responselimit/policies.go new file mode 100644 index 00000000..6c5449e4 --- /dev/null +++ b/networking/internal/ocrendpointv3/responselimit/policies.go @@ -0,0 +1,55 @@ +package responselimit + +import ( + "time" + + "github.com/smartcontractkit/libocr/networking/internal/ocrendpointv3/types" +) + +// Interface for specifying rate-limit exceptions for responses. +// +// When a request is made, a response policy is used to specify if a response (or in principle multiple responses) +// should be allowed or rejected. +// +// Policies have to be tracked internally. Therefore, to allow proper cleanup of resources, it is critical that +// IsPolicyExpired(...) returns true when no (additional) response is expected anymore. +type ResponsePolicy interface { + // Must return true as soon as the policy is no longer required, i.e., it must return true if the policy is expired + // at the provided timestamp (or any later point in time). + isPolicyExpired(timestamp time.Time) bool + + // Specifies whether a response for the given request ID should be allowed or rejected. + // Before and after checkResponse(...) is called internally, a policy is always checked for expiry. + // checkResponse(...) is never called on an expired policy. + checkResponse(requestID types.RequestID, responseSize int, responseTimestamp time.Time) ResponseCheckResult +} + +var _ ResponsePolicy = &SingleUseSizedLimitedResponsePolicy{} + +// A response policy, allowing at most one response subject to the following constraints: +// - the response's payload size is at most `MaxSize` +// - the response is received before `ExpiryTimestamp` +type SingleUseSizedLimitedResponsePolicy struct { + MaxSize int + ExpiryTimestamp time.Time +} + +func (p *SingleUseSizedLimitedResponsePolicy) isPolicyExpired(timestamp time.Time) bool { + return !timestamp.Before(p.ExpiryTimestamp) +} + +func (p *SingleUseSizedLimitedResponsePolicy) checkResponse( + requestID types.RequestID, + responseSize int, + responseTimestamp time.Time, +) ResponseCheckResult { + // As this is intended to be a single use policy only, we set the timestamp to its zero value. This is ensuring that + // any subsequent call to `isPolicyExpired(...)` returns false. As consequence `checkResponse(...)`, will not be + // called on again by the response checker, and the policy will be removed from the checker. + p.ExpiryTimestamp = time.Time{} + + if responseSize > p.MaxSize { + return ResponseCheckResultReject + } + return ResponseCheckResultAllow +} diff --git a/networking/internal/ocrendpointv3/types/types.go b/networking/internal/ocrendpointv3/types/types.go new file mode 100644 index 00000000..6856c169 --- /dev/null +++ b/networking/internal/ocrendpointv3/types/types.go @@ -0,0 +1,31 @@ +package types + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + + "github.com/smartcontractkit/libocr/commontypes" + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type StreamID struct { + OracleID commontypes.OracleID + Priority ocr2types.BinaryMessageOutboundPriority +} + +const RequestIDSize = 32 + +type RequestID [RequestIDSize]byte + +var _ fmt.Stringer = RequestID{} + +func (r RequestID) String() string { + return hex.EncodeToString(r[:]) +} + +func GetRandomRequestID() RequestID { + var b [RequestIDSize]byte + _, _ = rand.Read(b[:]) + return RequestID(b) +} diff --git a/networking/ocr3_1_peer.go b/networking/ocr3_1_peer.go new file mode 100644 index 00000000..26cdc38e --- /dev/null +++ b/networking/ocr3_1_peer.go @@ -0,0 +1,28 @@ +package networking + +import ( + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +var _ types.BinaryNetworkEndpoint2Factory = &ocr3_1BinaryNetworkEndpointFactory{} + +type ocr3_1BinaryNetworkEndpointFactory struct { + *concretePeerV2 +} + +func (o *ocr3_1BinaryNetworkEndpointFactory) NewEndpoint( + configDigest types.ConfigDigest, + pids []string, + v2bootstrappers []commontypes.BootstrapperLocator, + defaultPriorityConfig types.BinaryNetworkEndpoint2Config, + lowPriorityConfig types.BinaryNetworkEndpoint2Config, +) (types.BinaryNetworkEndpoint2, error) { + return o.newEndpoint3_1( + configDigest, + pids, + v2bootstrappers, + defaultPriorityConfig, + lowPriorityConfig, + ) +} diff --git a/networking/ocr_endpoint_v3.go b/networking/ocr_endpoint_v3.go new file mode 100644 index 00000000..7abaaa5b --- /dev/null +++ b/networking/ocr_endpoint_v3.go @@ -0,0 +1,604 @@ +package networking + +import ( + "encoding" + "errors" + "fmt" + "io" + "math" + "sync" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/networking/internal/ocrendpointv3/responselimit" + ocrendpointv3types "github.com/smartcontractkit/libocr/networking/internal/ocrendpointv3/types" + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/libocr/ragep2p" + ragetypes "github.com/smartcontractkit/libocr/ragep2p/types" + "github.com/smartcontractkit/libocr/subprocesses" + + "github.com/smartcontractkit/libocr/internal/loghelper" +) + +var ( + _ ocr2types.BinaryNetworkEndpoint2 = &ocrEndpointV3{} +) + +// ocrEndpointV3 represents a member of a particular feed oracle group +type ocrEndpointV3 struct { + // configuration and settings + defaultPriorityConfig ocr2types.BinaryNetworkEndpoint2Config + lowPriorityConfig ocr2types.BinaryNetworkEndpoint2Config + peerMapping map[commontypes.OracleID]ragetypes.PeerID + host *ragep2p.Host + configDigest ocr2types.ConfigDigest + ownOracleID commontypes.OracleID + + // internal and state management + chSendToSelf chan ocr2types.InboundBinaryMessageWithSender + chClose chan struct{} + streams map[commontypes.OracleID]priorityStreamGroup + registration io.Closer + state ocrEndpointState + + stateMu sync.RWMutex + subs subprocesses.Subprocesses + + responseChecker *responselimit.ResponseChecker + + // recv is exposed to clients of this network endpoint + recv chan ocr2types.InboundBinaryMessageWithSender + + logger loghelper.LoggerWithContext +} + +type priorityStreamGroup struct { + Low *ragep2p.Stream + Default *ragep2p.Stream +} + +//nolint:unused +func newOCREndpointV3( + logger loghelper.LoggerWithContext, + configDigest ocr2types.ConfigDigest, + peer *concretePeerV2, + peerIDs []ragetypes.PeerID, + v3bootstrappers []ragetypes.PeerInfo, + defaultPriorityConfig ocr2types.BinaryNetworkEndpoint2Config, + lowPriorityConfig ocr2types.BinaryNetworkEndpoint2Config, + registration io.Closer, +) (*ocrEndpointV3, error) { + peerMapping := make(map[commontypes.OracleID]ragetypes.PeerID) + for i, peerID := range peerIDs { + peerMapping[commontypes.OracleID(i)] = peerID + } + reversedPeerMapping := reverseMappingV2(peerMapping) + ownOracleID, ok := reversedPeerMapping[peer.peerID] + if !ok { + return nil, fmt.Errorf("host peer ID %s is not present in given peerMapping", peer.PeerID()) + } + + chSendToSelf := make(chan ocr2types.InboundBinaryMessageWithSender, sendToSelfBufferSize) + + logger = logger.MakeChild(commontypes.LogFields{ + "configDigest": configDigest.Hex(), + "oracleID": ownOracleID, + "id": "OCREndpointV3", + }) + + logger.Info("OCREndpointV3: Initialized", commontypes.LogFields{ + "bootstrappers": v3bootstrappers, + "oracles": peerIDs, + }) + + if len(v3bootstrappers) == 0 { + logger.Warn("OCREndpointV3: No bootstrappers were provided. Peer discovery might not work reliably for this instance.", nil) + } + + o := &ocrEndpointV3{ + defaultPriorityConfig, + lowPriorityConfig, + peerMapping, + peer.host, + configDigest, + ownOracleID, + chSendToSelf, + make(chan struct{}), + make(map[commontypes.OracleID]priorityStreamGroup), + registration, + ocrEndpointUnstarted, + sync.RWMutex{}, + subprocesses.Subprocesses{}, + responselimit.NewResponseChecker(), + make(chan ocr2types.InboundBinaryMessageWithSender), + logger, + } + err := o.start() + return o, err +} + +// Start the ocrEndpointV3. Called once at the end of the initialization code. +func (o *ocrEndpointV3) start() error { + succeeded := false + defer func() { + if !succeeded { + o.Close() + } + }() + + o.stateMu.Lock() + defer o.stateMu.Unlock() + + if o.state != ocrEndpointUnstarted { + return fmt.Errorf("cannot start ocrEndpointV3 that is not unstarted, state was: %d", o.state) + } + o.state = ocrEndpointStarted + + for oid, pid := range o.peerMapping { + if oid == o.ownOracleID { + continue + } + + noLimitsMaxMessageLength := ragep2p.MaxMessageLength + noLimitsTokenBucketParams := ragep2p.TokenBucketParams{ + math.MaxFloat64, + math.MaxUint32, + } + + // Initialize the underlying streams, one stream per priority level. + lowPriorityStream, err := o.host.NewStream( + pid, + streamNameFromConfigDigestAndPriority(o.configDigest, ocr2types.BinaryMessagePriorityLow), + o.lowPriorityConfig.OverrideOutgoingMessageBufferSize, + o.lowPriorityConfig.OverrideIncomingMessageBufferSize, + noLimitsMaxMessageLength, + noLimitsTokenBucketParams, + noLimitsTokenBucketParams, + ) + if err != nil { + return fmt.Errorf("failed to create (low priority) stream for oracle %v (peer id: %q): %w", oid, pid, err) + } + + defaultPriorityStream, err := o.host.NewStream( + pid, + streamNameFromConfigDigestAndPriority(o.configDigest, ocr2types.BinaryMessagePriorityDefault), + o.defaultPriorityConfig.OverrideOutgoingMessageBufferSize, + o.defaultPriorityConfig.OverrideIncomingMessageBufferSize, + noLimitsMaxMessageLength, + noLimitsTokenBucketParams, + noLimitsTokenBucketParams, + ) + if err != nil { + return fmt.Errorf("failed to create (default priority) stream for oracle %v (peer id: %q): %w", oid, pid, err) + } + + o.streams[oid] = priorityStreamGroup{lowPriorityStream, defaultPriorityStream} + } + + for oid := range o.streams { + o.subs.Go(func() { + o.runRecv(oid) + }) + } + o.subs.Go(func() { + o.runSendToSelf() + }) + + o.logger.Info("OCREndpointV3: Started listening", nil) + succeeded = true + return nil +} + +// Receive runloop is per-remote +// This means that each remote gets its own buffered channel, so even if one +// remote goes mad and sends us thousands of messages, we don't drop any +// messages from good remotes +func (o *ocrEndpointV3) runRecv(oid commontypes.OracleID) { + chRecv1 := o.streams[oid].Default.ReceiveMessages() + chRecv2 := o.streams[oid].Low.ReceiveMessages() + for { + var ( + msg []byte + priority ocr2types.BinaryMessageOutboundPriority + ) + + select { + case msg = <-chRecv1: + priority = ocr2types.BinaryMessagePriorityDefault + case msg = <-chRecv2: + priority = ocr2types.BinaryMessagePriorityLow + case <-o.chClose: + return + } + + inMsg, err := o.translateInboundMessage(msg, priority, oid) + if err != nil { + o.logger.Warn("Invalid inbound message", commontypes.LogFields{ + "remoteOracleID": oid, + "priority": priority, + "reason": err, + }) + continue + } + select { + case o.recv <- ocr2types.InboundBinaryMessageWithSender{inMsg, oid}: + continue + case <-o.chClose: + return + } + } +} + +type ocrEndpointV3PayloadType byte + +const ( + _ ocrEndpointV3PayloadType = iota + ocrEndpointV3PayloadTypePlain + ocrEndpointV3PayloadTypeRequest + ocrEndpointV3PayloadTypeResponse +) + +type ocrEndpointV3Payload struct { + sumType ocrEndpointV3PayloadSumType +} + +func (o *ocrEndpointV3Payload) MarshalBinary() ([]byte, error) { + var prefix byte + switch o.sumType.(type) { + case *ocrEndpointV3PayloadPlain: + prefix = byte(ocrEndpointV3PayloadTypePlain) + case *ocrEndpointV3PayloadRequest: + prefix = byte(ocrEndpointV3PayloadTypeRequest) + case *ocrEndpointV3PayloadResponse: + prefix = byte(ocrEndpointV3PayloadTypeResponse) + } + sumTypeBytes, err := o.sumType.MarshalBinary() + if err != nil { + return nil, err + } + return append([]byte{prefix}, sumTypeBytes...), nil +} + +func (o *ocrEndpointV3Payload) UnmarshalBinary(data []byte) error { + if len(data) < 1 { + return fmt.Errorf("data is too short to contain prefix") + } + prefix := ocrEndpointV3PayloadType(data[0]) + data = data[1:] + switch prefix { + case ocrEndpointV3PayloadTypePlain: + o.sumType = &ocrEndpointV3PayloadPlain{} + case ocrEndpointV3PayloadTypeRequest: + o.sumType = &ocrEndpointV3PayloadRequest{} + case ocrEndpointV3PayloadTypeResponse: + o.sumType = &ocrEndpointV3PayloadResponse{} + } + return o.sumType.UnmarshalBinary(data) +} + +//go-sumtype:decl ocrEndpointV3PayloadSumType + +type ocrEndpointV3PayloadSumType interface { + isOCREndpointV3PayloadSumType() + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler +} + +type ocrEndpointV3PayloadPlain struct { + payload []byte +} + +func (op ocrEndpointV3PayloadPlain) isOCREndpointV3PayloadSumType() {} + +func (op *ocrEndpointV3PayloadPlain) MarshalBinary() ([]byte, error) { + return op.payload, nil +} + +func (op *ocrEndpointV3PayloadPlain) UnmarshalBinary(data []byte) error { + op.payload = data + return nil +} + +type ocrEndpointV3PayloadRequest struct { + requestID ocrendpointv3types.RequestID + payload []byte +} + +func (oreq ocrEndpointV3PayloadRequest) isOCREndpointV3PayloadSumType() {} + +func (oreq *ocrEndpointV3PayloadRequest) MarshalBinary() ([]byte, error) { + return append(oreq.requestID[:], oreq.payload...), nil +} + +func (oreq *ocrEndpointV3PayloadRequest) UnmarshalBinary(data []byte) error { + if len(data) < len(oreq.requestID) { + return fmt.Errorf("data is too short to contain requestID") + } + oreq.requestID = ocrendpointv3types.RequestID(data[:len(oreq.requestID)]) + oreq.payload = data[len(oreq.requestID):] + return nil +} + +type ocrEndpointV3PayloadResponse struct { + requestID ocrendpointv3types.RequestID + payload []byte +} + +func (ores ocrEndpointV3PayloadResponse) isOCREndpointV3PayloadSumType() {} + +func (ores *ocrEndpointV3PayloadResponse) MarshalBinary() ([]byte, error) { + return append(ores.requestID[:], ores.payload...), nil +} + +func (ores *ocrEndpointV3PayloadResponse) UnmarshalBinary(data []byte) error { + if len(data) < len(ores.requestID) { + return fmt.Errorf("data is too short to contain requestID") + } + ores.requestID = ocrendpointv3types.RequestID(data[:len(ores.requestID)]) + ores.payload = data[len(ores.requestID):] + return nil +} + +func (o *ocrEndpointV3) translateInboundMessage(ragepayload []byte, priority ocr2types.BinaryMessageOutboundPriority, from commontypes.OracleID) (ocr2types.InboundBinaryMessage, error) { + var payload ocrEndpointV3Payload + if err := payload.UnmarshalBinary(ragepayload); err != nil { + return nil, err + } + + switch msg := payload.sumType.(type) { + case *ocrEndpointV3PayloadPlain: + return ocr2types.InboundBinaryMessagePlain{msg.payload, priority}, nil + + case *ocrEndpointV3PayloadRequest: + rid := msg.requestID + + return ocr2types.InboundBinaryMessageRequest{ + ocrEndpointV3RequestHandle{priority, rid}, + msg.payload, + priority, + }, nil + + case *ocrEndpointV3PayloadResponse: + sid := ocrendpointv3types.StreamID{from, priority} + rid := msg.requestID + + checkResult := o.responseChecker.CheckResponse(sid, rid, len(msg.payload)) + switch checkResult { + case responselimit.ResponseCheckResultReject: + return nil, fmt.Errorf("rejected response") + case responselimit.ResponseCheckResultAllow: + return ocr2types.InboundBinaryMessageResponse{msg.payload, priority}, nil + } + + panic(fmt.Sprintf("unexpected responselimit.ResponseCheckResult: %#v", checkResult)) + } + + panic("unknown ocrEndpointV3PayloadType") +} + +func (o *ocrEndpointV3) translateOutboundMessage(outMsg ocr2types.OutboundBinaryMessage, to commontypes.OracleID) ( + ragepayload []byte, + priority ocr2types.BinaryMessageOutboundPriority, + err error, +) { + var payload ocrEndpointV3Payload + switch msg := outMsg.(type) { + case ocr2types.OutboundBinaryMessagePlain: + payload.sumType = &ocrEndpointV3PayloadPlain{msg.Payload} + priority = msg.Priority + + case ocr2types.OutboundBinaryMessageRequest: + var ocrendpointv3responsepolicy responselimit.ResponsePolicy + switch responsepolicy := msg.ResponsePolicy.(type) { + case ocr2types.SingleUseSizedLimitedResponsePolicy: + ocrendpointv3responsepolicy = &responselimit.SingleUseSizedLimitedResponsePolicy{ + responsepolicy.MaxSize, + responsepolicy.ExpiryTimestamp, + } + } + + priority = msg.Priority + + sid := ocrendpointv3types.StreamID{to, priority} + rid := ocrendpointv3types.GetRandomRequestID() + o.responseChecker.SetPolicy(sid, rid, ocrendpointv3responsepolicy) + + payload.sumType = &ocrEndpointV3PayloadRequest{rid, msg.Payload} + + case ocr2types.OutboundBinaryMessageResponse: + requestHandle, ok := ocr2types.MustGetOutboundBinaryMessageResponseRequestHandle(msg).(ocrEndpointV3RequestHandle) + if !ok { + o.logger.Error( + "dropping OutboundBinaryMessageResponse with requestHandle of unknown type", + commontypes.LogFields{}, + ) + return + } + + requestID := requestHandle.requestID + payload.sumType = &ocrEndpointV3PayloadResponse{requestID, msg.Payload} + priority = msg.Priority + + default: + panic("unknown type of ocr2types.OutboundBinaryMessage") + } + ragepayload, err = payload.MarshalBinary() + return ragepayload, priority, err +} + +func (o *ocrEndpointV3) runSendToSelf() { + for { + select { + case <-o.chClose: + return + case m := <-o.chSendToSelf: + select { + case o.recv <- m: + case <-o.chClose: + return + } + } + } +} + +// Close should be called to clean up even if Start is never called. +func (o *ocrEndpointV3) Close() error { + o.stateMu.Lock() + defer o.stateMu.Unlock() + if o.state != ocrEndpointStarted { + return fmt.Errorf("cannot close ocrEndpointV3 that is not started, state was: %d", o.state) + } + o.state = ocrEndpointClosed + + o.logger.Debug("OCREndpointV3: Closing", nil) + + o.logger.Debug("OCREndpointV3: Closing streams", nil) + close(o.chClose) + o.subs.Wait() + + var allErrors error + for oid, priorityGroupStream := range o.streams { + { + sid := ocrendpointv3types.StreamID{oid, ocr2types.BinaryMessagePriorityDefault} + o.responseChecker.ClearPoliciesForStream(sid) + if err := priorityGroupStream.Default.Close(); err != nil { + allErrors = errors.Join(allErrors, fmt.Errorf("error while closing (default priority) stream with oracle %v: %w", oid, err)) + } + } + { + sid := ocrendpointv3types.StreamID{oid, ocr2types.BinaryMessagePriorityLow} + o.responseChecker.ClearPoliciesForStream(sid) + if err := priorityGroupStream.Low.Close(); err != nil { + allErrors = errors.Join(allErrors, fmt.Errorf("error while closing (low priority) stream with oracle %v: %w", oid, err)) + } + } + } + + o.logger.Debug("OCREndpointV3: Deregister", nil) + if err := o.registration.Close(); err != nil { + allErrors = errors.Join(allErrors, fmt.Errorf("error closing OCREndpointV3: could not deregister: %w", err)) + } + + o.logger.Debug("OCREndpointV3: Closing o.recv", nil) + close(o.recv) + + o.logger.Info("OCREndpointV3: Closed", nil) + return allErrors +} + +// SendTo sends a message to the given oracle. +// It makes a best effort delivery. If stream is unavailable for any +// reason, it will fill up to outgoingMessageBufferSize then drop +// old messages until the stream becomes available again. +// +// NOTE: If a stream connection is lost, the buffer will keep only the newest +// messages and drop older ones until the stream opens again. +func (o *ocrEndpointV3) SendTo(msg ocr2types.OutboundBinaryMessage, to commontypes.OracleID) { + o.stateMu.RLock() + state := o.state + o.stateMu.RUnlock() + if state != ocrEndpointStarted { + o.logger.Error("Send on non-started ocrEndpointV3", commontypes.LogFields{"state": state}) + return + } + + if to == o.ownOracleID { + o.sendToSelf(msg) + return + } + + ragemsg, priority, err := o.translateOutboundMessage(msg, to) + if err != nil { + o.logger.Error("Failed to translate outbound message", commontypes.LogFields{ + "error": err, + }) + return + } + switch priority { + case ocr2types.BinaryMessagePriorityDefault: + o.streams[to].Default.SendMessage(ragemsg) + case ocr2types.BinaryMessagePriorityLow: + o.streams[to].Low.SendMessage(ragemsg) + } +} + +type ocrEndpointV3RequestHandle struct { + priority ocr2types.BinaryMessageOutboundPriority + requestID ocrendpointv3types.RequestID +} + +func (rh ocrEndpointV3RequestHandle) MakeResponse(payload []byte) ocr2types.OutboundBinaryMessageResponse { + return ocr2types.MustMakeOutboundBinaryMessageResponse( + rh, + payload, + rh.priority, + ) +} + +type ocrEndpointV3RequestHandleSelf struct { + priority ocr2types.BinaryMessageOutboundPriority +} + +func (rh ocrEndpointV3RequestHandleSelf) MakeResponse(payload []byte) ocr2types.OutboundBinaryMessageResponse { + return ocr2types.MustMakeOutboundBinaryMessageResponse( + rh, + payload, + rh.priority, + ) +} + +func (o *ocrEndpointV3) sendToSelf(outboundMessage ocr2types.OutboundBinaryMessage) { + var inboundMessage ocr2types.InboundBinaryMessage + + switch msg := outboundMessage.(type) { + case ocr2types.OutboundBinaryMessagePlain: + inboundMessage = ocr2types.InboundBinaryMessagePlain(msg) + case ocr2types.OutboundBinaryMessageRequest: + inboundMessage = ocr2types.InboundBinaryMessageRequest{ + ocrEndpointV3RequestHandleSelf{msg.Priority}, + msg.Payload, + msg.Priority, + } + case ocr2types.OutboundBinaryMessageResponse: + // TODO: We may want to reconsider how self forwarding works in case of requests/responses, because + // with the updates to Stream2 and ResponseChecker extra checks are performed. Therefore, this "alternative" + // code path may not behave fully equivalent to how request/responses are handled in the normal flow. + inboundMessage = ocr2types.InboundBinaryMessageResponse{ + msg.Payload, + msg.Priority, + } + } + + select { + case o.chSendToSelf <- ocr2types.InboundBinaryMessageWithSender{inboundMessage, o.ownOracleID}: + default: + o.logger.Error("Send-to-self buffer is full, dropping message", commontypes.LogFields{ + "remoteOracleID": o.ownOracleID, + }) + } +} + +// Broadcast sends a msg to all oracles in the peer mapping +func (o *ocrEndpointV3) Broadcast(msg ocr2types.OutboundBinaryMessage) { + var subs subprocesses.Subprocesses + defer subs.Wait() + for oracleID := range o.peerMapping { + subs.Go(func() { + o.SendTo(msg, oracleID) + }) + } +} + +// Receive gives the channel to receive messages +func (o *ocrEndpointV3) Receive() <-chan ocr2types.InboundBinaryMessageWithSender { + return o.recv +} + +func streamNameFromConfigDigestAndPriority(cd ocr2types.ConfigDigest, priority ocr2types.BinaryMessageOutboundPriority) string { + switch priority { + case ocr2types.BinaryMessagePriorityLow: + return fmt.Sprintf("ocr/%s/priority=low", cd) + case ocr2types.BinaryMessagePriorityDefault: + return fmt.Sprintf("ocr/%s", cd) + } + panic("case implementation for ragep2p.StreamPriority missing") +} diff --git a/networking/peer_v2.go b/networking/peer_v2.go index 8908e0dc..5598e216 100644 --- a/networking/peer_v2.go +++ b/networking/peer_v2.go @@ -261,6 +261,45 @@ func (p2 *concretePeerV2) newEndpoint( return endpoint, nil } +func (p2 *concretePeerV2) newEndpoint3_1( + configDigest ocr2types.ConfigDigest, + v2peerIDs []string, + v2bootstrappers []commontypes.BootstrapperLocator, + defaultPriorityConfig ocr2types.BinaryNetworkEndpoint2Config, + lowPriorityConfig ocr2types.BinaryNetworkEndpoint2Config, +) (ocr2types.BinaryNetworkEndpoint2, error) { + decodedv2PeerIDs, err := decodev2PeerIDs(v2peerIDs) + if err != nil { + return nil, fmt.Errorf("could not decode v2 peer IDs: %w", err) + } + + decodedv2Bootstrappers, err := decodev2Bootstrappers(v2bootstrappers) + if err != nil { + return nil, fmt.Errorf("could not decode v2 bootstrappers: %w", err) + } + + registration, err := p2.register(configDigest, decodedv2PeerIDs, decodedv2Bootstrappers) + if err != nil { + return nil, err + } + + endpoint, err := newOCREndpointV3( + p2.logger, + configDigest, + p2, + decodedv2PeerIDs, + decodedv2Bootstrappers, + defaultPriorityConfig, + lowPriorityConfig, + registration, + ) + if err != nil { + // Important: we close registration in case newOCREndpointV2 failed to prevent zombie registrations. + return nil, errors.Join(err, registration.Close()) + } + return endpoint, nil +} + func (p2 *concretePeerV2) newBootstrapper( configDigest ocr2types.ConfigDigest, v2peerIDs []string, @@ -297,6 +336,10 @@ func (p2 *concretePeerV2) OCR2BinaryNetworkEndpointFactory() *ocr2BinaryNetworkE return &ocr2BinaryNetworkEndpointFactory{p2} } +func (p2 *concretePeerV2) OCR3_1BinaryNetworkEndpointFactory() *ocr3_1BinaryNetworkEndpointFactory { + return &ocr3_1BinaryNetworkEndpointFactory{p2} +} + func (p2 *concretePeerV2) OCR1BootstrapperFactory() *ocr1BootstrapperFactory { return &ocr1BootstrapperFactory{p2} } diff --git a/offchainreporting2plus/internal/ocr3/minheap/minheap.go b/offchainreporting2plus/internal/common/minheap/minheap.go similarity index 100% rename from offchainreporting2plus/internal/ocr3/minheap/minheap.go rename to offchainreporting2plus/internal/common/minheap/minheap.go diff --git a/offchainreporting2plus/internal/ocr3/protocol/common.go b/offchainreporting2plus/internal/common/plugin_caller.go similarity index 94% rename from offchainreporting2plus/internal/ocr3/protocol/common.go rename to offchainreporting2plus/internal/common/plugin_caller.go index b7cc9ce5..d3a6ac8e 100644 --- a/offchainreporting2plus/internal/ocr3/protocol/common.go +++ b/offchainreporting2plus/internal/common/plugin_caller.go @@ -1,4 +1,4 @@ -package protocol +package common import ( "context" @@ -11,7 +11,7 @@ import ( const ReportingPluginTimeoutWarningGracePeriod = 100 * time.Millisecond -func callPlugin[T any]( +func CallPlugin[T any]( ctx context.Context, logger loghelper.LoggerWithContext, logFields commontypes.LogFields, @@ -48,10 +48,10 @@ func callPlugin[T any]( return result, true } -// Unlike callPlugin, callPluginFromBackground only uses the "recommendedMaxDuration" to warn +// Unlike CallPlugin, CallPluginFromBackground only uses the "recommendedMaxDuration" to warn // if the call takes longer than recommended, but does not use it for context expiration // purposes. Context expiration is solely controlled by the passed ctx. -func callPluginFromBackground[T any]( +func CallPluginFromBackground[T any]( ctx context.Context, logger loghelper.LoggerWithContext, logFields commontypes.LogFields, diff --git a/offchainreporting2plus/internal/ocr3/protocol/pool/pool.go b/offchainreporting2plus/internal/common/pool/pool.go similarity index 100% rename from offchainreporting2plus/internal/ocr3/protocol/pool/pool.go rename to offchainreporting2plus/internal/common/pool/pool.go diff --git a/offchainreporting2plus/internal/ocr3/protocol/ringbuffer/ringbuffer.go b/offchainreporting2plus/internal/common/ringbuffer/ringbuffer.go similarity index 100% rename from offchainreporting2plus/internal/ocr3/protocol/ringbuffer/ringbuffer.go rename to offchainreporting2plus/internal/common/ringbuffer/ringbuffer.go diff --git a/offchainreporting2plus/internal/ocr3/scheduler/scheduler.go b/offchainreporting2plus/internal/common/scheduler/scheduler.go similarity index 99% rename from offchainreporting2plus/internal/ocr3/scheduler/scheduler.go rename to offchainreporting2plus/internal/common/scheduler/scheduler.go index f0be88a5..dc01f946 100644 --- a/offchainreporting2plus/internal/ocr3/scheduler/scheduler.go +++ b/offchainreporting2plus/internal/common/scheduler/scheduler.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3/minheap" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/minheap" "github.com/smartcontractkit/libocr/subprocesses" ) diff --git a/offchainreporting2plus/internal/managed/limits/ocr3_1_limits.go b/offchainreporting2plus/internal/managed/limits/ocr3_1_limits.go new file mode 100644 index 00000000..72220e24 --- /dev/null +++ b/offchainreporting2plus/internal/managed/limits/ocr3_1_limits.go @@ -0,0 +1,232 @@ +package limits + +import ( + "crypto/ed25519" + "fmt" + "math" + "math/big" + "time" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3config" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3_1/protocol" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type ocr3_1serializedLengthLimits struct { + maxLenMsgNewEpoch int + maxLenMsgEpochStartRequest int + maxLenMsgEpochStart int + maxLenMsgRoundStart int + maxLenMsgObservation int + maxLenMsgProposal int + maxLenMsgPrepare int + maxLenMsgCommit int + maxLenMsgReportSignatures int + maxLenMsgCertifiedCommitRequest int + maxLenMsgCertifiedCommit int + maxLenMsgBlockSyncSummary int + maxLenMsgBlockSyncRequest int + maxLenMsgBlockSync int + maxLenMsgBlobOffer int + maxLenMsgBlobChunkRequest int + maxLenMsgBlobChunkResponse int + maxLenMsgBlobAvailable int +} + +func ocr3_1limits(cfg ocr3config.PublicConfig, pluginLimits ocr3_1types.ReportingPluginLimits, maxSigLen int) (types.BinaryNetworkEndpointLimits, types.BinaryNetworkEndpointLimits, ocr3_1serializedLengthLimits, error) { + overflow := false + + // These two helper functions add/multiply together a bunch of numbers and set overflow to true if the result + // lies outside the range [0; math.MaxInt32]. We compare with int32 rather than int to be independent of + // the underlying architecture. + add := func(xs ...int) int { + sum := big.NewInt(0) + for _, x := range xs { + sum.Add(sum, big.NewInt(int64(x))) + } + if !(big.NewInt(0).Cmp(sum) <= 0 && sum.Cmp(big.NewInt(int64(math.MaxInt32))) <= 0) { + overflow = true + } + return int(sum.Int64()) + } + mul := func(xs ...int) int { + prod := big.NewInt(1) + for _, x := range xs { + prod.Mul(prod, big.NewInt(int64(x))) + } + if !(big.NewInt(0).Cmp(prod) <= 0 && prod.Cmp(big.NewInt(int64(math.MaxInt32))) <= 0) { + overflow = true + } + return int(prod.Int64()) + } + + const sigOverhead = 10 + const overhead = 256 + + maxLenStateTransitionOutputsAndReportsPlusPrecursor := add(mul(2, pluginLimits.MaxKeyValueModifiedKeysPlusValuesLength), pluginLimits.MaxReportsPlusPrecursorLength, overhead) + maxLenCertifiedPrepareOrCommit := add(mul(ed25519.SignatureSize+sigOverhead, cfg.ByzQuorumSize()), len(protocol.StateTransitionInputsDigest{}), maxLenStateTransitionOutputsAndReportsPlusPrecursor, overhead) + maxLenCertifiedCommittedReports := add(mul(ed25519.SignatureSize+sigOverhead, cfg.ByzQuorumSize()), len(protocol.StateTransitionInputsDigest{}), len(protocol.StateTransitionOutputDigest{}), pluginLimits.MaxReportsPlusPrecursorLength, overhead) + + maxLenMsgNewEpoch := overhead + maxLenMsgEpochStartRequest := add(maxLenCertifiedPrepareOrCommit, overhead) + maxLenMsgEpochStart := add(maxLenCertifiedPrepareOrCommit, mul(ed25519.SignatureSize+sigOverhead, cfg.ByzQuorumSize()), overhead) + maxLenMsgRoundStart := add(pluginLimits.MaxQueryLength, overhead) + maxLenMsgObservation := add(pluginLimits.MaxObservationLength, overhead) + maxLenMsgProposal := add(mul(add(pluginLimits.MaxObservationLength, ed25519.SignatureSize+sigOverhead), cfg.N()), overhead) + maxLenMsgPrepare := overhead + maxLenMsgCommit := overhead + maxLenMsgReportSignatures := add(mul(add(maxSigLen, sigOverhead), pluginLimits.MaxReportCount), overhead) + maxLenMsgCertifiedCommitRequest := overhead + maxLenMsgCertifiedCommit := add(maxLenCertifiedCommittedReports, overhead) + + // block sync messages + maxLenMsgBlockSyncSummary := overhead + maxLenMsgBlockSyncRequest := overhead + maxLenAttestedStateTransitionBlock := maxLenCertifiedPrepareOrCommit + maxLenMsgBlockSync := add(mul(protocol.MaxBlocksSent, maxLenAttestedStateTransitionBlock), overhead) + + // blob exchange messages + const blobChunkDigestSize = len(protocol.BlobChunkDigest{}) + maxNumBlobChunks := (pluginLimits.MaxBlobPayloadLength + protocol.BlobChunkSize - 1) / protocol.BlobChunkSize + maxLenMsgBlobOffer := add(mul(blobChunkDigestSize, maxNumBlobChunks), overhead) + maxLenMsgBlobChunkRequest := add(blobChunkDigestSize, overhead) + maxLenMsgBlobChunkResponse := add(blobChunkDigestSize, protocol.BlobChunkSize, overhead) + maxLenMsgBlobAvailable := add(blobChunkDigestSize, ed25519.SignatureSize+sigOverhead, overhead) + + maxDefaultPriorityMessageSize := max( + maxLenMsgNewEpoch, + maxLenMsgEpochStartRequest, + maxLenMsgEpochStart, + maxLenMsgRoundStart, + maxLenMsgObservation, + maxLenMsgProposal, + maxLenMsgPrepare, + maxLenMsgCommit, + maxLenMsgReportSignatures, + maxLenMsgCertifiedCommitRequest, + maxLenMsgCertifiedCommit, + maxLenMsgBlockSyncSummary, + maxLenMsgBlobOffer, + maxLenMsgBlobChunkRequest, + + maxLenMsgBlobAvailable, + ) + + maxLowPriorityMessageSize := max( + maxLenMsgBlockSyncSummary, + maxLenMsgBlockSyncRequest, + ) + + minEpochInterval := math.Min(float64(cfg.DeltaProgress), math.Min(float64(cfg.DeltaInitial), float64(cfg.RMax)*float64(cfg.DeltaRound))) + + defaultPriorityMessagesRate := (1.0*float64(time.Second)/float64(cfg.DeltaResend) + + 3.0*float64(time.Second)/minEpochInterval + + 8.0*float64(time.Second)/float64(cfg.DeltaRound)) * 1.2 + + lowPriorityMessagesRate := (1.0*float64(time.Second)/float64(protocol.DeltaMinBlockSyncRequest) + + 1.0*float64(time.Second)/float64(protocol.DeltaBlockSyncHeartbeat)) * 1.2 + + defaultPriorityMessagesCapacity := mul(15, 3) + lowPriorityMessagesCapacity := mul(2, 3) + + // we don't multiply bytesRate by a safetyMargin since we already have a generous overhead on each message + + defaultPriorityBytesRate := float64(time.Second)/float64(cfg.DeltaResend)*float64(maxLenMsgNewEpoch) + + float64(time.Second)/float64(minEpochInterval)*float64(maxLenMsgNewEpoch) + + float64(time.Second)/float64(cfg.DeltaRound)*float64(maxLenMsgPrepare) + + float64(time.Second)/float64(cfg.DeltaRound)*float64(maxLenMsgCommit) + + float64(time.Second)/float64(cfg.DeltaRound)*float64(maxLenMsgReportSignatures) + + float64(time.Second)/float64(minEpochInterval)*float64(maxLenMsgEpochStart) + + float64(time.Second)/float64(cfg.DeltaRound)*float64(maxLenMsgRoundStart) + + float64(time.Second)/float64(cfg.DeltaRound)*float64(maxLenMsgProposal) + + float64(time.Second)/float64(minEpochInterval)*float64(maxLenMsgEpochStartRequest) + + float64(time.Second)/float64(cfg.DeltaRound)*float64(maxLenMsgObservation) + + float64(time.Second)/float64(cfg.DeltaRound)*float64(maxLenMsgCertifiedCommitRequest) + + float64(time.Second)/float64(cfg.DeltaRound)*float64(maxLenMsgCertifiedCommit) + + float64(time.Second)/float64(protocol.DeltaBlobCertRequest)*float64(maxLenMsgBlobOffer) + // blob-related messages + float64(time.Second)/float64(protocol.DeltaBlobChunkRequest)*float64(maxLenMsgBlobChunkRequest) + + float64(time.Second)/float64(protocol.DeltaBlobCertRequest)*float64(maxLenMsgBlobAvailable) + + lowPriorityBytesRate := float64(time.Second)/float64(protocol.DeltaBlockSyncHeartbeat)*float64(maxLenMsgBlockSyncSummary) + + float64(time.Second)/float64(protocol.DeltaMinBlockSyncRequest)*float64(maxLenMsgBlockSyncRequest) + + defaultPriorityBytesCapacity := mul(add( + maxLenMsgNewEpoch, + maxLenMsgNewEpoch, + maxLenMsgEpochStartRequest, + maxLenMsgEpochStart, + maxLenMsgRoundStart, + maxLenMsgObservation, + maxLenMsgProposal, + maxLenMsgPrepare, + maxLenMsgCommit, + maxLenMsgReportSignatures, + maxLenMsgCertifiedCommitRequest, + maxLenMsgCertifiedCommit, + maxLenMsgBlobOffer, + maxLenMsgBlobChunkRequest, + + maxLenMsgBlobAvailable, + ), 3) + + lowPriorityBytesCapacity := mul(add( + maxLenMsgBlockSyncSummary, + maxLenMsgBlockSyncRequest, + ), 3) + + if overflow { + // this should not happen due to us checking the limits in types.go + return types.BinaryNetworkEndpointLimits{}, types.BinaryNetworkEndpointLimits{}, ocr3_1serializedLengthLimits{}, fmt.Errorf("int32 overflow while computing bandwidth limits") + } + + return types.BinaryNetworkEndpointLimits{ + maxDefaultPriorityMessageSize, + defaultPriorityMessagesRate, + defaultPriorityMessagesCapacity, + defaultPriorityBytesRate, + defaultPriorityBytesCapacity, + }, + types.BinaryNetworkEndpointLimits{ + maxLowPriorityMessageSize, + lowPriorityMessagesRate, + lowPriorityMessagesCapacity, + lowPriorityBytesRate, + lowPriorityBytesCapacity, + }, + ocr3_1serializedLengthLimits{ + maxLenMsgNewEpoch, + maxLenMsgEpochStartRequest, + maxLenMsgEpochStart, + maxLenMsgRoundStart, + maxLenMsgObservation, + maxLenMsgProposal, + maxLenMsgPrepare, + maxLenMsgCommit, + maxLenMsgReportSignatures, + maxLenMsgCertifiedCommitRequest, + maxLenMsgCertifiedCommit, + maxLenMsgBlockSyncSummary, + maxLenMsgBlockSyncRequest, + maxLenMsgBlockSync, + maxLenMsgBlobOffer, + maxLenMsgBlobChunkRequest, + maxLenMsgBlobChunkResponse, + maxLenMsgBlobAvailable, + }, + nil +} + +func OCR3_1Limits( + cfg ocr3config.PublicConfig, + pluginLimits ocr3_1types.ReportingPluginLimits, + maxSigLen int, +) ( + defaultLimits types.BinaryNetworkEndpointLimits, + lowPriorityLimits types.BinaryNetworkEndpointLimits, + err error, +) { + defaultLimits, lowPriorityLimits, _, err = ocr3_1limits(cfg, pluginLimits, maxSigLen) + return defaultLimits, lowPriorityLimits, err +} diff --git a/offchainreporting2plus/internal/managed/managed_mercury_oracle.go b/offchainreporting2plus/internal/managed/managed_mercury_oracle.go index 9142873b..08e68e96 100644 --- a/offchainreporting2plus/internal/managed/managed_mercury_oracle.go +++ b/offchainreporting2plus/internal/managed/managed_mercury_oracle.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common" "github.com/prometheus/client_golang/prometheus" "github.com/smartcontractkit/libocr/commontypes" @@ -98,7 +99,7 @@ func RunManagedMercuryOracle( defer initCancel() ins := loghelper.NewIfNotStopped( - maxDurationInitialization+protocol.ReportingPluginTimeoutWarningGracePeriod, + maxDurationInitialization+common.ReportingPluginTimeoutWarningGracePeriod, func() { logger.Error("ManagedMercuryOracle: MercuryPluginFactory.NewMercuryPlugin is taking too long", commontypes.LogFields{ "maxDuration": maxDurationInitialization, diff --git a/offchainreporting2plus/internal/managed/managed_ocr3_1_oracle.go b/offchainreporting2plus/internal/managed/managed_ocr3_1_oracle.go new file mode 100644 index 00000000..2944e2a1 --- /dev/null +++ b/offchainreporting2plus/internal/managed/managed_ocr3_1_oracle.go @@ -0,0 +1,289 @@ +package managed + +import ( + "context" + "errors" + "fmt" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common" + + "github.com/prometheus/client_golang/prometheus" + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/internal/metricshelper" + "github.com/smartcontractkit/libocr/internal/util" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3config" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/managed/limits" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3_1/protocol" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3_1/serialization" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/shim" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/libocr/subprocesses" +) + +const ( + defaultIncomingMessageBufferSize = 10 + defaultOutgoingMessageBufferSize = 10 + lowPriorityIncomingMessageBufferSize = 10 + lowPriorityOutgoingMessageBufferSize = 10 +) + +// RunManagedOCR3_1Oracle runs a "managed" version of protocol.RunOracle. It handles +// setting up telemetry, garbage collection, configuration updates, translating +// from types.BinaryNetworkEndpoint2 to protocol.NetworkEndpoint, and +// creation/teardown of reporting plugins. +func RunManagedOCR3_1Oracle[RI any]( + ctx context.Context, + + v2bootstrappers []commontypes.BootstrapperLocator, + configTracker types.ContractConfigTracker, + contractTransmitter ocr3types.ContractTransmitter[RI], + database ocr3_1types.Database, + keyValueDatabaseFactory ocr3_1types.KeyValueDatabaseFactory, + localConfig types.LocalConfig, + logger loghelper.LoggerWithContext, + metricsRegisterer prometheus.Registerer, + monitoringEndpoint commontypes.MonitoringEndpoint, + messageNetEndpointFactory types.BinaryNetworkEndpoint2Factory, + offchainConfigDigester types.OffchainConfigDigester, + offchainKeyring types.OffchainKeyring, + onchainKeyring ocr3types.OnchainKeyring[RI], + reportingPluginFactory ocr3_1types.ReportingPluginFactory[RI], +) { + subs := subprocesses.Subprocesses{} + defer subs.Wait() + + var chTelemetrySend chan<- *serialization.TelemetryWrapper + { + chTelemetry := make(chan *serialization.TelemetryWrapper, 100) + chTelemetrySend = chTelemetry + subs.Go(func() { + forwardTelemetry(ctx, logger, monitoringEndpoint, chTelemetry) + }) + } + + metricsRegistererWrapper := metricshelper.NewPrometheusRegistererWrapper(metricsRegisterer, logger) + + runWithContractConfig( + ctx, + + configTracker, + database, + func(ctx context.Context, logger loghelper.LoggerWithContext, contractConfig types.ContractConfig) (err error, retry bool) { + skipResourceExhaustionChecks := localConfig.DevelopmentMode == types.EnableDangerousDevelopmentMode + + fromAccount, err := contractTransmitter.FromAccount(ctx) + if err != nil { + return fmt.Errorf("ManagedOCR3_1Oracle: error getting FromAccount: %w", err), true + } + + sharedConfig, oid, err := ocr3config.SharedConfigFromContractConfig( + skipResourceExhaustionChecks, + contractConfig, + offchainKeyring, + onchainKeyring, + messageNetEndpointFactory.PeerID(), + fromAccount, + ) + if err != nil { + return fmt.Errorf("ManagedOCR3_1Oracle: error while decoding ContractConfig: %w", err), false + } + + registerer := prometheus.WrapRegistererWith( + prometheus.Labels{ + // disambiguate different protocol instances by configDigest + "config_digest": sharedConfig.ConfigDigest.String(), + // disambiguate different oracle instances by offchainPublicKey + "offchain_public_key": fmt.Sprintf("%x", offchainKeyring.OffchainPublicKey()), + }, + metricsRegistererWrapper, + ) + + // Run with new config + peerIDs := []string{} + for _, identity := range sharedConfig.OracleIdentities { + peerIDs = append(peerIDs, identity.PeerID) + } + + childLogger := logger.MakeChild(commontypes.LogFields{ + "oid": oid, + }) + + blobEndpointWrapper := protocol.BlobEndpointWrapper{} + + maxDurationInitialization := util.NilCoalesce(sharedConfig.MaxDurationInitialization, localConfig.DefaultMaxDurationInitialization) + initCtx, initCancel := context.WithTimeout(ctx, maxDurationInitialization) + defer initCancel() + + ins := loghelper.NewIfNotStopped( + maxDurationInitialization+common.ReportingPluginTimeoutWarningGracePeriod, + func() { + logger.Error("ManagedOCR3_1Oracle: ReportingPluginFactory.NewReportingPlugin is taking too long", commontypes.LogFields{ + "maxDuration": maxDurationInitialization, + }) + }, + ) + + reportingPlugin, reportingPluginInfo, err := reportingPluginFactory.NewReportingPlugin(initCtx, ocr3types.ReportingPluginConfig{ + sharedConfig.ConfigDigest, + oid, + sharedConfig.N(), + sharedConfig.F, + sharedConfig.OnchainConfig, + sharedConfig.ReportingPluginConfig, + sharedConfig.DeltaRound, + sharedConfig.MaxDurationQuery, + sharedConfig.MaxDurationObservation, + sharedConfig.MaxDurationShouldAcceptAttestedReport, + sharedConfig.MaxDurationShouldTransmitAcceptedReport, + }, &blobEndpointWrapper) + + ins.Stop() + + if err != nil { + return fmt.Errorf("ManagedOCR3_1Oracle: error during NewReportingPlugin(): %w", err), true + } + defer loghelper.CloseLogError( + reportingPlugin, + logger, + "ManagedOCR3_1Oracle: error during reportingPlugin.Close()", + ) + + if err := validateOCR3_1ReportingPluginLimits(reportingPluginInfo.Limits); err != nil { + logger.Error("ManagedOCR3_1Oracle: invalid ReportingPluginInfo", commontypes.LogFields{ + "error": err, + "reportingPluginInfo": reportingPluginInfo, + }) + return fmt.Errorf("ManagedOCR3_1Oracle: invalid MercuryPluginInfo"), false + } + + blobEndpointWrapper.SetLimits(reportingPluginInfo.Limits) + + maxSigLen := onchainKeyring.MaxSignatureLength() + defaultLims, lowPriorityLimits, err := limits.OCR3_1Limits(sharedConfig.PublicConfig, reportingPluginInfo.Limits, maxSigLen) + if err != nil { + logger.Error("ManagedOCR3_1Oracle: error during limits", commontypes.LogFields{ + "error": err, + "publicConfig": sharedConfig.PublicConfig, + "reportingPluginLimits": reportingPluginInfo.Limits, + "maxSigLen": maxSigLen, + }) + return fmt.Errorf("ManagedOCR3_1Oracle: error during limits"), false + } + defaultPriorityConfig := types.BinaryNetworkEndpoint2Config{ + defaultLims, + defaultIncomingMessageBufferSize, + defaultOutgoingMessageBufferSize, + } + lowPriorityConfig := types.BinaryNetworkEndpoint2Config{ + lowPriorityLimits, + lowPriorityIncomingMessageBufferSize, + lowPriorityOutgoingMessageBufferSize, + } + + binNetEndpoint, err := messageNetEndpointFactory.NewEndpoint( + sharedConfig.ConfigDigest, + peerIDs, + v2bootstrappers, + defaultPriorityConfig, + lowPriorityConfig, + ) + if err != nil { + logger.Error("ManagedOCR3_1Oracle: error during NewEndpoint", commontypes.LogFields{ + "error": err, + "peerIDs": peerIDs, + "v2bootstrappers": v2bootstrappers, + }) + return fmt.Errorf("ManagedOCR3_1Oracle: error during NewEndpoint"), true + } + defer loghelper.CloseLogError( + binNetEndpoint, + logger, + "ManagedOCR3_1Oracle: error during BinaryNetworkEndpoint2.Close()", + ) + + netEndpoint := shim.NewOCR3_1SerializingEndpoint[RI]( + chTelemetrySend, + sharedConfig.ConfigDigest, + binNetEndpoint, + maxSigLen, + childLogger, + registerer, + reportingPluginInfo.Limits, + sharedConfig.PublicConfig, + ) + err = netEndpoint.Start() + if err != nil { + return fmt.Errorf("ManagedOCR3_1Oracle: error during netEndpoint.Start(): %w", err), true + } + defer loghelper.CloseLogError( + netEndpoint, + logger, + "ManagedOCR3_1Oracle: error during netEndpoint.Close()", + ) + + keyValueDatabase, err := keyValueDatabaseFactory.NewKeyValueDatabase(sharedConfig.ConfigDigest) + if err != nil { + return fmt.Errorf("ManagedOCR3_1Oracle: error during NewKeyValueDatabase: %w", err), false + } + defer loghelper.CloseLogError( + keyValueDatabase, + logger, + "ManagedOCR3_1Oracle: error during keyValueDatabase.Close()", + ) + + protocol.RunOracle[RI]( + ctx, + &blobEndpointWrapper, + sharedConfig, + contractTransmitter, + &shim.SerializingOCR3_1Database{database}, + oid, + &shim.SemanticOCR3_1KeyValueStore{keyValueDatabase, reportingPluginInfo.Limits}, + localConfig, + childLogger, + registerer, + netEndpoint, + offchainKeyring, + onchainKeyring, + shim.LimitCheckOCR3_1ReportingPlugin[RI]{reportingPlugin, reportingPluginInfo.Limits}, + shim.NewOCR3_1TelemetrySender(chTelemetrySend, childLogger), + ) + + return nil, false + }, + localConfig, + logger, + offchainConfigDigester, + defaultRetryParams(), + ) +} + +func validateOCR3_1ReportingPluginLimits(limits ocr3_1types.ReportingPluginLimits) error { + var err error + if !(0 <= limits.MaxQueryLength && limits.MaxQueryLength <= ocr3_1types.MaxMaxQueryLength) { + err = errors.Join(err, fmt.Errorf("MaxQueryLength (%v) out of range. Should be between 0 and %v", limits.MaxQueryLength, ocr3_1types.MaxMaxQueryLength)) + } + if !(0 <= limits.MaxObservationLength && limits.MaxObservationLength <= ocr3_1types.MaxMaxObservationLength) { + err = errors.Join(err, fmt.Errorf("MaxObservationLength (%v) out of range. Should be between 0 and %v", limits.MaxObservationLength, ocr3_1types.MaxMaxObservationLength)) + } + if !(0 <= limits.MaxReportLength && limits.MaxReportLength <= ocr3_1types.MaxMaxReportLength) { + err = errors.Join(err, fmt.Errorf("MaxReportLength (%v) out of range. Should be between 0 and %v", limits.MaxReportLength, ocr3_1types.MaxMaxReportLength)) + } + if !(0 <= limits.MaxReportsPlusPrecursorLength && limits.MaxReportsPlusPrecursorLength <= ocr3_1types.MaxMaxReportsPlusPrecursorLength) { + err = errors.Join(err, fmt.Errorf("MaxReportInfoLength (%v) out of range. Should be between 0 and %v", limits.MaxReportsPlusPrecursorLength, ocr3_1types.MaxMaxReportsPlusPrecursorLength)) + } + if !(0 <= limits.MaxReportCount && limits.MaxReportCount <= ocr3_1types.MaxMaxReportCount) { + err = errors.Join(err, fmt.Errorf("MaxReportCount (%v) out of range. Should be between 0 and %v", limits.MaxReportCount, ocr3_1types.MaxMaxReportCount)) + } + + if !(0 <= limits.MaxKeyValueModifiedKeysPlusValuesLength && limits.MaxKeyValueModifiedKeysPlusValuesLength <= ocr3_1types.MaxMaxKeyValueModifiedKeysPlusValuesLength) { + err = errors.Join(err, fmt.Errorf("MaxKeyValueModifiedKeysPlusValuesLength (%v) out of range. Should be between 0 and %v", limits.MaxKeyValueModifiedKeysPlusValuesLength, ocr3_1types.MaxMaxKeyValueModifiedKeysPlusValuesLength)) + } + if !(0 <= limits.MaxBlobPayloadLength && limits.MaxBlobPayloadLength <= ocr3_1types.MaxMaxBlobPayloadLength) { + err = errors.Join(err, fmt.Errorf("MaxBlobPayloadLength (%v) out of range. Should be between 0 and %v", limits.MaxBlobPayloadLength, ocr3_1types.MaxMaxBlobPayloadLength)) + } + return err +} diff --git a/offchainreporting2plus/internal/managed/managed_ocr3_oracle.go b/offchainreporting2plus/internal/managed/managed_ocr3_oracle.go index d8d9ae1a..2f0040e6 100644 --- a/offchainreporting2plus/internal/managed/managed_ocr3_oracle.go +++ b/offchainreporting2plus/internal/managed/managed_ocr3_oracle.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common" "github.com/prometheus/client_golang/prometheus" "github.com/smartcontractkit/libocr/commontypes" @@ -105,7 +106,7 @@ func RunManagedOCR3Oracle[RI any]( defer initCancel() ins := loghelper.NewIfNotStopped( - maxDurationInitialization+protocol.ReportingPluginTimeoutWarningGracePeriod, + maxDurationInitialization+common.ReportingPluginTimeoutWarningGracePeriod, func() { logger.Error("ManagedOCR3Oracle: ReportingPluginFactory.NewReportingPlugin is taking too long", commontypes.LogFields{ "maxDuration": maxDurationInitialization, diff --git a/offchainreporting2plus/internal/ocr3/protocol/messagebuffer.go b/offchainreporting2plus/internal/ocr3/protocol/messagebuffer.go index 2f3ba8a2..1f414889 100644 --- a/offchainreporting2plus/internal/ocr3/protocol/messagebuffer.go +++ b/offchainreporting2plus/internal/ocr3/protocol/messagebuffer.go @@ -1,6 +1,8 @@ package protocol -import "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3/protocol/ringbuffer" +import ( + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/ringbuffer" +) // We have this wrapper to deal with what appears to be a bug in the Go compiler // that prevents us from using ringbuffer.RingBuffer in the outcome generation diff --git a/offchainreporting2plus/internal/ocr3/protocol/outcome_generation.go b/offchainreporting2plus/internal/ocr3/protocol/outcome_generation.go index f1fba0e9..84be6773 100644 --- a/offchainreporting2plus/internal/ocr3/protocol/outcome_generation.go +++ b/offchainreporting2plus/internal/ocr3/protocol/outcome_generation.go @@ -8,8 +8,9 @@ import ( "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/pool" "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3config" - "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3/protocol/pool" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/libocr/subprocesses" @@ -376,7 +377,7 @@ func callPluginFromOutcomeGenerationBackground[T any, RI any]( outctx ocr3types.OutcomeContext, f func(context.Context, ocr3types.OutcomeContext) (T, error), ) (T, bool) { - return callPluginFromBackground[T]( + return common.CallPluginFromBackground[T]( ctx, logger, commontypes.LogFields{ diff --git a/offchainreporting2plus/internal/ocr3/protocol/outcome_generation_follower.go b/offchainreporting2plus/internal/ocr3/protocol/outcome_generation_follower.go index 6e568a72..62e77177 100644 --- a/offchainreporting2plus/internal/ocr3/protocol/outcome_generation_follower.go +++ b/offchainreporting2plus/internal/ocr3/protocol/outcome_generation_follower.go @@ -5,7 +5,7 @@ import ( "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/internal/loghelper" - "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3/protocol/pool" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/pool" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ) diff --git a/offchainreporting2plus/internal/ocr3/protocol/outcome_generation_leader.go b/offchainreporting2plus/internal/ocr3/protocol/outcome_generation_leader.go index e49503cf..702262df 100644 --- a/offchainreporting2plus/internal/ocr3/protocol/outcome_generation_leader.go +++ b/offchainreporting2plus/internal/ocr3/protocol/outcome_generation_leader.go @@ -6,7 +6,7 @@ import ( "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/internal/loghelper" - "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3/protocol/pool" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/pool" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ) diff --git a/offchainreporting2plus/internal/ocr3/protocol/report_attestation.go b/offchainreporting2plus/internal/ocr3/protocol/report_attestation.go index 2de5dece..b15a53da 100644 --- a/offchainreporting2plus/internal/ocr3/protocol/report_attestation.go +++ b/offchainreporting2plus/internal/ocr3/protocol/report_attestation.go @@ -12,8 +12,9 @@ import ( "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/scheduler" "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3config" - "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3/scheduler" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/libocr/subprocesses" @@ -500,7 +501,7 @@ func (repatt *reportAttestationState[RI]) receivedVerifiedCertifiedCommit(certif } func (repatt *reportAttestationState[RI]) backgroundComputeReports(ctx context.Context, verifiedCertifiedCommit CertifiedCommit) { - reportsPlus, ok := callPluginFromBackground( + reportsPlus, ok := common.CallPluginFromBackground( ctx, repatt.logger, commontypes.LogFields{"seqNr": verifiedCertifiedCommit.SeqNr}, diff --git a/offchainreporting2plus/internal/ocr3/protocol/transmission.go b/offchainreporting2plus/internal/ocr3/protocol/transmission.go index 10d164b1..496f672b 100644 --- a/offchainreporting2plus/internal/ocr3/protocol/transmission.go +++ b/offchainreporting2plus/internal/ocr3/protocol/transmission.go @@ -7,10 +7,12 @@ import ( "encoding/binary" "time" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common" + "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/scheduler" "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3config" - "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3/scheduler" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/libocr/permutation" @@ -126,7 +128,7 @@ func (t *transmissionState[RI]) backgroundEventAttestedReport(ctx context.Contex return } - shouldAccept, ok := callPlugin[bool]( + shouldAccept, ok := common.CallPlugin[bool]( ctx, t.logger, commontypes.LogFields{ @@ -172,7 +174,7 @@ func (t *transmissionState[RI]) scheduled(ev EventAttestedReport[RI]) { } func (t *transmissionState[RI]) backgroundScheduled(ctx context.Context, ev EventAttestedReport[RI]) { - shouldTransmit, ok := callPlugin[bool]( + shouldTransmit, ok := common.CallPlugin[bool]( ctx, t.logger, commontypes.LogFields{ diff --git a/offchainreporting2plus/internal/ocr3_1/blobtypes/types.go b/offchainreporting2plus/internal/ocr3_1/blobtypes/types.go new file mode 100644 index 00000000..50ad09ff --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/blobtypes/types.go @@ -0,0 +1,259 @@ +package blobtypes + +import ( + "crypto/ed25519" + "crypto/sha256" + "encoding" + "encoding/binary" + "encoding/json" + "fmt" + "hash" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +// Returns a byte slice whose first four bytes are the string "ocr3" and the rest +// of which is the sum returned by h. Used for domain separation vs ocr2, where +// we just directly sign sha256 hashes. +// +// Any signatures made with the OffchainKeyring should use ocr3_1DomainSeparatedSum! +func ocr3_1DomainSeparatedSum(h hash.Hash) []byte { + result := make([]byte, 0, 6+32) + result = append(result, []byte("ocr3.1")...) + return h.Sum(result) +} + +type BlobChunkDigest [32]byte + +func MakeBlobChunkDigest(chunk []byte) BlobChunkDigest { + h := sha256.New() + h.Write(chunk) + var result BlobChunkDigest + h.Sum(result[:0]) + return result +} + +type BlobDigest [32]byte + +var _ fmt.Stringer = BlobDigest{} + +func (bd BlobDigest) String() string { + return fmt.Sprintf("%x", bd[:]) +} + +func MakeBlobDigest( + configDigest types.ConfigDigest, + chunkDigests []BlobChunkDigest, + payloadLength uint64, + expirySeqNr uint64, + submitter commontypes.OracleID, +) BlobDigest { + h := sha256.New() + + _, _ = h.Write(configDigest[:]) + + _ = binary.Write(h, binary.BigEndian, uint64(len(chunkDigests))) + for _, chunkDigest := range chunkDigests { + + _, _ = h.Write(chunkDigest[:]) + } + + _ = binary.Write(h, binary.BigEndian, payloadLength) + + _ = binary.Write(h, binary.BigEndian, expirySeqNr) + + _ = binary.Write(h, binary.BigEndian, uint64(submitter)) + + var result BlobDigest + h.Sum(result[:0]) + return result +} + +const blobAvailabilitySignatureDomainSeparator = "ocr3.1 BlobAvailabilitySignature" + +type BlobAvailabilitySignature []byte + +func MakeBlobAvailabilitySignature( + blobDigest BlobDigest, + signer func(msg []byte) ([]byte, error), +) (BlobAvailabilitySignature, error) { + return signer(blobAvailabilitySignatureMsg(blobDigest)) +} + +func (sig BlobAvailabilitySignature) Verify( + blobDigest BlobDigest, + publicKey types.OffchainPublicKey, +) error { + pk := ed25519.PublicKey(publicKey[:]) + + if len(pk) != ed25519.PublicKeySize { + return fmt.Errorf("ed25519 public key size mismatch, expected %v but got %v", ed25519.PublicKeySize, len(pk)) + } + + ok := ed25519.Verify(pk, blobAvailabilitySignatureMsg(blobDigest), sig) + if !ok { + return fmt.Errorf("BlobAvailabilitySignature failed to verify") + } + + return nil +} + +func blobAvailabilitySignatureMsg( + blobDigest BlobDigest, +) []byte { + h := sha256.New() + + _, _ = h.Write([]byte(blobAvailabilitySignatureDomainSeparator)) + + _, _ = h.Write(blobDigest[:]) + + return ocr3_1DomainSeparatedSum(h) +} + +type AttributedBlobAvailabilitySignature struct { + Signature BlobAvailabilitySignature + Signer commontypes.OracleID +} + +type LightCertifiedBlob struct { + ChunkDigests []BlobChunkDigest + PayloadLength uint64 + ExpirySeqNr uint64 + Submitter commontypes.OracleID + + AttributedBlobAvailabilitySignatures []AttributedBlobAvailabilitySignature +} + +func (lc *LightCertifiedBlob) Verify( + configDigest types.ConfigDigest, + oracleIdentities []config.OracleIdentity, + byzQuorumSize int, +) error { + if byzQuorumSize != len(lc.AttributedBlobAvailabilitySignatures) { + return fmt.Errorf("wrong number of signatures, expected %d for byz. quorum but got %d", byzQuorumSize, len(lc.AttributedBlobAvailabilitySignatures)) + } + + blobDigest := MakeBlobDigest( + configDigest, + lc.ChunkDigests, + lc.PayloadLength, + lc.ExpirySeqNr, + lc.Submitter, + ) + + seen := make(map[commontypes.OracleID]bool) + for i, abs := range lc.AttributedBlobAvailabilitySignatures { + if seen[abs.Signer] { + return fmt.Errorf("duplicate signature by %v", abs.Signer) + } + seen[abs.Signer] = true + if !(0 <= int(abs.Signer) && int(abs.Signer) < len(oracleIdentities)) { + return fmt.Errorf("signer out of bounds: %v", abs.Signer) + } + if err := abs.Signature.Verify(blobDigest, oracleIdentities[abs.Signer].OffchainPublicKey); err != nil { + return fmt.Errorf("%v-th signature by %v-th oracle with pubkey %x does not verify: %w", i, abs.Signer, oracleIdentities[abs.Signer].OffchainPublicKey, err) + } + } + + return nil +} + +var _ BlobHandleSumType = &LightCertifiedBlob{} +var _ encoding.BinaryMarshaler = LightCertifiedBlob{} +var _ encoding.BinaryAppender = LightCertifiedBlob{} +var _ encoding.BinaryUnmarshaler = &LightCertifiedBlob{} + +func (lc *LightCertifiedBlob) isBlobHandleSumType() {} + +func (lc LightCertifiedBlob) AppendBinary(b []byte) ([]byte, error) { + enc, err := json.Marshal(lc) + if err != nil { + return nil, fmt.Errorf("failed to marshal LightCertifiedBlob: %w", err) + } + return append(b, enc...), nil +} + +func (lc LightCertifiedBlob) MarshalBinary() ([]byte, error) { + return lc.AppendBinary(nil) +} + +func (lc *LightCertifiedBlob) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, &lc) +} + +// go-sumtype:decl BlobHandleSumType + +type BlobHandleSumType interface { + isBlobHandleSumType() + encoding.BinaryAppender + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler +} + +type BlobHandle struct { + _ [0]func() + + blobHandleSumType BlobHandleSumType +} + +func ExtractBlobHandleSumType(h BlobHandle) BlobHandleSumType { + return h.blobHandleSumType +} + +func MakeBlobHandle(b BlobHandleSumType) BlobHandle { + return BlobHandle{ + [0]func(){}, + b, + } +} + +var _ encoding.BinaryAppender = BlobHandle{} +var _ encoding.BinaryMarshaler = BlobHandle{} +var _ encoding.BinaryUnmarshaler = &BlobHandle{} + +type blobHandleSumTypeVariant byte + +const ( + _ blobHandleSumTypeVariant = iota + blobHandleSumTypeVariantLightCertifiedBlob +) + +func (h BlobHandle) AppendBinary(b []byte) ([]byte, error) { + var variant blobHandleSumTypeVariant + switch h.blobHandleSumType.(type) { + case *LightCertifiedBlob: + variant = blobHandleSumTypeVariantLightCertifiedBlob + } + + prefix := append(b, byte(variant)) + final, err := h.blobHandleSumType.AppendBinary(prefix) + if err != nil { + return nil, err + } + + return final, nil +} + +func (h BlobHandle) MarshalBinary() ([]byte, error) { + return h.AppendBinary(nil) +} + +func (h *BlobHandle) UnmarshalBinary(data []byte) error { + if len(data) < 1 { + return fmt.Errorf("data too short to unmarshal BlobHandle: %d bytes", len(data)) + } + variant, rest := blobHandleSumTypeVariant(data[0]), data[1:] + switch variant { + case blobHandleSumTypeVariantLightCertifiedBlob: + var lc LightCertifiedBlob + if err := lc.UnmarshalBinary(rest); err != nil { + return fmt.Errorf("failed to unmarshal LightCertifiedBlob: %w", err) + } + h.blobHandleSumType = &lc + default: + return fmt.Errorf("unknown BlobHandle version: %d", data[0]) + } + return nil +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/blob_endpoint.go b/offchainreporting2plus/internal/ocr3_1/protocol/blob_endpoint.go new file mode 100644 index 00000000..fa0ae660 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/blob_endpoint.go @@ -0,0 +1,147 @@ +package protocol + +import ( + "context" + "fmt" + "sync" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3_1/blobtypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" +) + +type BlobEndpointWrapper struct { + mu sync.Mutex + wrapped *BlobEndpoint + limits ocr3_1types.ReportingPluginLimits +} + +func (bew *BlobEndpointWrapper) locked() (*BlobEndpoint, ocr3_1types.ReportingPluginLimits) { + bew.mu.Lock() + wrapped := bew.wrapped + limits := bew.limits + bew.mu.Unlock() + return wrapped, limits +} + +var _ ocr3_1types.BlobBroadcaster = &BlobEndpointWrapper{} + +func (bew *BlobEndpointWrapper) BroadcastBlob(ctx context.Context, payload []byte, expirationHint ocr3_1types.BlobExpirationHint) (ocr3_1types.BlobHandle, error) { + wrapped, limits := bew.locked() + if wrapped == nil { + return ocr3_1types.BlobHandle{}, errBlobEndpointClosed + } + if len(payload) > limits.MaxBlobPayloadLength { + return ocr3_1types.BlobHandle{}, fmt.Errorf("blob payload length %d exceeds maximum allowed length %d", + len(payload), limits.MaxBlobPayloadLength) + } + return wrapped.BroadcastBlob(ctx, payload, expirationHint) +} + +var _ ocr3_1types.BlobFetcher = &BlobEndpointWrapper{} + +func (bew *BlobEndpointWrapper) FetchBlob(ctx context.Context, handle ocr3_1types.BlobHandle) ([]byte, error) { + wrapped, _ := bew.locked() + if wrapped == nil { + return nil, errBlobEndpointClosed + } + return wrapped.FetchBlob(ctx, handle) +} + +func (bew *BlobEndpointWrapper) setBlobEndpoint(wrapped *BlobEndpoint) { + bew.mu.Lock() + bew.wrapped = wrapped + bew.mu.Unlock() +} + +func (bew *BlobEndpointWrapper) SetLimits(limits ocr3_1types.ReportingPluginLimits) { + bew.mu.Lock() + bew.limits = limits + bew.mu.Unlock() +} + +type BlobEndpoint struct { + ctx context.Context + + chBlobBroadcastRequest chan<- blobBroadcastRequest + chBlobBroadcastResponse <-chan blobBroadcastResponse + + chBlobFetchRequest chan<- blobFetchRequest + chBlobFetchResponse <-chan blobFetchResponse +} + +var ( + errBlobEndpointClosed = fmt.Errorf("blob endpoint closed") + errReceivingChannelClosed = fmt.Errorf("receiving channel closed") +) + +func expirySeqNr(expirationHint ocr3_1types.BlobExpirationHint) uint64 { + switch beh := expirationHint.(type) { + case ocr3_1types.BlobExpirationHintSequenceNumber: + return beh.SeqNr + default: + panic(fmt.Sprintf("unexpected blob expiration hint type %T", beh)) + } +} + +func (be *BlobEndpoint) BroadcastBlob(_ context.Context, payload []byte, expirationHint ocr3_1types.BlobExpirationHint) (ocr3_1types.BlobHandle, error) { + chDone := be.ctx.Done() + + select { + case be.chBlobBroadcastRequest <- blobBroadcastRequest{ + payload, + expirySeqNr(expirationHint), + }: + response := <-be.chBlobBroadcastResponse + if response.err != nil { + return ocr3_1types.BlobHandle{}, response.err + } + + select { + case cert, ok := <-response.chCert: + if !ok { + return ocr3_1types.BlobHandle{}, errReceivingChannelClosed + } + return blobtypes.MakeBlobHandle(&cert), nil + case <-chDone: + return ocr3_1types.BlobHandle{}, errBlobEndpointClosed + } + case <-chDone: + return ocr3_1types.BlobHandle{}, errBlobEndpointClosed + } +} + +var _ ocr3_1types.BlobBroadcaster = &BlobEndpoint{} + +func (be *BlobEndpoint) FetchBlob(_ context.Context, handle ocr3_1types.BlobHandle) ([]byte, error) { + chDone := be.ctx.Done() + + blobHandleSumType := blobtypes.ExtractBlobHandleSumType(handle) + if blobHandleSumType == nil { + return nil, fmt.Errorf("zero value blob handle provided") + } + switch handle := blobHandleSumType.(type) { + case *LightCertifiedBlob: + select { + case be.chBlobFetchRequest <- blobFetchRequest{*handle}: + response := <-be.chBlobFetchResponse + if response.err != nil { + return nil, response.err + } + select { + case payload, ok := <-response.chPayload: + if !ok { + return nil, errReceivingChannelClosed + } + return payload, nil + case <-chDone: + return nil, errBlobEndpointClosed + } + case <-chDone: + return nil, errBlobEndpointClosed + } + default: + panic(fmt.Sprintf("unexpected blob handle type %T", handle)) + } +} + +var _ ocr3_1types.BlobFetcher = &BlobEndpoint{} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/blob_exchange.go b/offchainreporting2plus/internal/ocr3_1/protocol/blob_exchange.go new file mode 100644 index 00000000..cbdcd016 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/blob_exchange.go @@ -0,0 +1,925 @@ +package protocol + +import ( + "context" + "fmt" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/scheduler" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3config" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3_1/blobtypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/libocr/subprocesses" +) + +func RunBlobExchange[RI any]( + ctx context.Context, + + chNetToBlobExchange <-chan MessageToBlobExchangeWithSender[RI], + chOutcomeGenerationToBlobExchange <-chan EventToBlobExchange[RI], + + chBlobBroadcastRequest <-chan blobBroadcastRequest, + chBlobBroadcastResponse chan<- blobBroadcastResponse, + + chBlobFetchRequest <-chan blobFetchRequest, + chBlobFetchResponse chan<- blobFetchResponse, + + config ocr3config.SharedConfig, + kv KeyValueStore, + id commontypes.OracleID, + localConfig types.LocalConfig, + logger loghelper.LoggerWithContext, + metricsRegisterer prometheus.Registerer, + netSender NetworkSender[RI], + offchainKeyring types.OffchainKeyring, + telemetrySender TelemetrySender, +) { + missingChunkScheduler := scheduler.NewScheduler[EventMissingBlobChunk[RI]]() + defer missingChunkScheduler.Close() + + missingCertScheduler := scheduler.NewScheduler[EventMissingBlobCert[RI]]() + defer missingCertScheduler.Close() + + bex := makeBlobExchangeState[RI]( + ctx, chNetToBlobExchange, + chOutcomeGenerationToBlobExchange, + chBlobBroadcastRequest, chBlobBroadcastResponse, + chBlobFetchRequest, chBlobFetchResponse, + config, kv, + id, localConfig, logger, metricsRegisterer, netSender, offchainKeyring, + telemetrySender, + missingChunkScheduler, missingCertScheduler, + ) + bex.run() +} + +func makeBlobExchangeState[RI any]( + ctx context.Context, + + chNetToBlobExchange <-chan MessageToBlobExchangeWithSender[RI], + chOutcomeGenerationToBlobExchange <-chan EventToBlobExchange[RI], + + chBlobBroadcastRequest <-chan blobBroadcastRequest, + chBlobBroadcastResponse chan<- blobBroadcastResponse, + + chBlobFetchRequest <-chan blobFetchRequest, + chBlobFetchResponse chan<- blobFetchResponse, + + config ocr3config.SharedConfig, + kv KeyValueStore, + id commontypes.OracleID, + localConfig types.LocalConfig, + logger loghelper.LoggerWithContext, + metricsRegisterer prometheus.Registerer, + netSender NetworkSender[RI], + offchainKeyring types.OffchainKeyring, + telemetrySender TelemetrySender, + missingChunkScheduler *scheduler.Scheduler[EventMissingBlobChunk[RI]], + missingCertScheduler *scheduler.Scheduler[EventMissingBlobCert[RI]], +) blobExchangeState[RI] { + return blobExchangeState[RI]{ + ctx, + subprocesses.Subprocesses{}, + + make(chan EventToBlobExchange[RI]), + chNetToBlobExchange, + chOutcomeGenerationToBlobExchange, + + chBlobBroadcastRequest, + chBlobBroadcastResponse, + + chBlobFetchRequest, + chBlobFetchResponse, + + config, + kv, + id, + localConfig, + logger.MakeUpdated(commontypes.LogFields{"proto": "bex"}), + netSender, + offchainKeyring, + telemetrySender, + + missingChunkScheduler, + missingCertScheduler, + + make(map[BlobDigest]*blob), + } +} + +const ( + DeltaBlobChunkRequest = 1 * time.Second + DeltaBlobChunkRequestTimeout = 5 * time.Second + DeltaBlobCertRequest = 10 * time.Second +) + +type blobBroadcastRequest struct { + payload []byte + expirySeqNr uint64 +} + +type blobBroadcastResponse struct { + chCert chan LightCertifiedBlob + err error +} + +type blobFetchRequest struct { + cert LightCertifiedBlob +} + +type blobFetchResponse struct { + chPayload chan []byte + err error +} + +type blobExchangeState[RI any] struct { + ctx context.Context + subs subprocesses.Subprocesses + + chLocalEvent chan EventToBlobExchange[RI] + chNetToBlobExchange <-chan MessageToBlobExchangeWithSender[RI] + chOutcomeGenerationToBlobExchange <-chan EventToBlobExchange[RI] + + chBlobBroadcastRequest <-chan blobBroadcastRequest + chBlobBroadcastResponse chan<- blobBroadcastResponse + + chBlobFetchRequest <-chan blobFetchRequest + chBlobFetchResponse chan<- blobFetchResponse + + config ocr3config.SharedConfig + kv KeyValueStore + id commontypes.OracleID + localConfig types.LocalConfig + logger loghelper.LoggerWithContext + netSender NetworkSender[RI] + offchainKeyring types.OffchainKeyring + telemetrySender TelemetrySender + + missingChunkScheduler *scheduler.Scheduler[EventMissingBlobChunk[RI]] + missingCertScheduler *scheduler.Scheduler[EventMissingBlobCert[RI]] + blobs map[BlobDigest]*blob +} + +const BlobChunkSize = 1 << 22 // 4MiB + +type blob struct { + chNotifyCertAvailable chan struct{} + chNotifyPayloadAvailable chan struct{} + + // certOrNil == nil indicates that we're the submitter and want to collect a + // cert. + certOrNil *LightCertifiedBlob + + chunks []chunk + oracles []blobOracle + + payloadLength uint64 + expirySeqNr uint64 + submitter commontypes.OracleID +} + +type blobOracle struct { + signature BlobAvailabilitySignature +} + +type chunk struct { + have bool + digest BlobChunkDigest +} + +func (bex *blobExchangeState[RI]) run() { + bex.logger.Info("BlobExchange: running", nil) + + // Take a reference to the ctx.Done channel once, here, to avoid taking the + // context lock below. + chDone := bex.ctx.Done() + + // Event Loop + for { + select { + case ev := <-bex.chLocalEvent: + ev.processBlobExchange(bex) + + case msg := <-bex.chNetToBlobExchange: + msg.msg.processBlobExchange(bex, msg.sender) + case ev := <-bex.chOutcomeGenerationToBlobExchange: + + ev.processBlobExchange(bex) + + case req := <-bex.chBlobBroadcastRequest: + bex.processBlobBroadcastRequest(req) + case req := <-bex.chBlobFetchRequest: + bex.processBlobFetchRequest(req) + + case ev := <-bex.missingCertScheduler.Scheduled(): + ev.processBlobExchange(bex) + case ev := <-bex.missingChunkScheduler.Scheduled(): + ev.processBlobExchange(bex) + + case <-chDone: + } + + // ensure prompt exit + select { + case <-chDone: + bex.logger.Info("BlobExchange: winding down", nil) + bex.subs.Wait() + // bex.metrics.Close() + bex.logger.Info("BlobExchange: exiting", nil) + return + default: + } + } +} + +func (bex *blobExchangeState[RI]) messageBlobOffer(msg MessageBlobOffer[RI], sender commontypes.OracleID) { + if msg.PayloadLength == 0 { + bex.logger.Debug("dropping MessageBlobOffer with zero payload length", commontypes.LogFields{ + "sender": sender, + }) + return + } + + blobDigest := blobtypes.MakeBlobDigest( + bex.config.ConfigDigest, + msg.ChunkDigests, + msg.PayloadLength, + msg.ExpirySeqNr, + msg.Submitter, + ) + + // check if we maybe already have this blob in full + { + payload, err := bex.readBlobPayload(blobDigest) + if err != nil { + bex.logger.Warn("dropping MessageBlobOffer, failed to check if we already have the payload", commontypes.LogFields{ + "blobDigest": blobDigest, + "sender": sender, + }) + return + } + if payload != nil { + bex.logger.Debug("received MessageBlobOffer for which we already have the payload", commontypes.LogFields{ + "blobDigest": blobDigest, + "sender": sender, + }) + + bex.sendAvailabilitySignature(blobDigest, msg.Submitter) + return + } + } + + if _, ok := bex.blobs[blobDigest]; ok { + bex.logger.Debug("dropping duplicate MessageBlobOffer", commontypes.LogFields{ + "blobDigest": blobDigest, + "sender": sender, + }) + return + } + + // TODO: enforce rate limit based on sender / length + // TODO: check payload length against Max + // TODO: check Max against MaxMax (in plugin config) + + bex.logger.Debug("received MessageBlobOffer", commontypes.LogFields{ + "blobDigest": blobDigest, + "sender": sender, + "chunkDigests": msg.ChunkDigests, + "payloadLength": msg.PayloadLength, + "expirySeqNr": msg.ExpirySeqNr, + }) + + chunks := make([]chunk, len(msg.ChunkDigests)) + for i, chunkDigest := range msg.ChunkDigests { + chunks[i] = chunk{ + false, + chunkDigest, + } + } + + bex.blobs[blobDigest] = &blob{ + make(chan struct{}), + make(chan struct{}), + + nil, + + chunks, + make([]blobOracle, bex.config.N()), + msg.PayloadLength, + msg.ExpirySeqNr, + msg.Submitter, + } + + bex.missingChunkScheduler.ScheduleDelay(EventMissingBlobChunk[RI]{ + blobDigest, + }, 0) +} + +func (bex *blobExchangeState[RI]) readBlobPayload(blobDigest BlobDigest) ([]byte, error) { + tx, err := bex.kv.NewReadTransactionUnchecked() + if err != nil { + return nil, fmt.Errorf("failed to create read transaction") + } + defer tx.Discard() + return tx.ReadBlob(blobDigest) +} + +func (bex *blobExchangeState[RI]) messageBlobChunkRequest(msg MessageBlobChunkRequest[RI], sender commontypes.OracleID) { + blob, ok := bex.blobs[msg.BlobDigest] + if !ok { + bex.logger.Debug("dropping MessageBlobChunkRequest for unknown blob", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + }) + return + } + + chunkIndex := msg.ChunkIndex + + bex.logger.Debug("received MessageBlobChunkRequest", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + "chunkIndex": chunkIndex, + "payloadLength": blob.payloadLength, + }) + + tx, err := bex.kv.NewReadTransactionUnchecked() + defer tx.Discard() + if err != nil { + bex.logger.Error("failed to create read transaction for MessageBlobChunkRequest", commontypes.LogFields{ + "error": err, + }) + return + } + + chunk, err := tx.ReadBlobChunk(msg.BlobDigest, chunkIndex) + if err != nil { + bex.logger.Error("failed to read blob chunk for MessageBlobChunkRequest", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + "chunkIndex": chunkIndex, + "error": err, + }) + return + } + if chunk == nil { + bex.logger.Debug("dropping MessageBlobChunkRequest, do not have chunk", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + "chunkIndex": chunkIndex, + }) + return + } + + chunkDigest := blobtypes.MakeBlobChunkDigest(chunk) + expectedChunkDigest := blob.chunks[chunkIndex].digest + if chunkDigest != expectedChunkDigest { + bex.logger.Critical("assumption violation: chunk digest mismatch while preparing MessageBlobChunkResponse", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "expectedChunkDigest": expectedChunkDigest, + "actualChunkDigest": chunkDigest, + }) + return + } + + bex.logger.Debug("sending MessageBlobChunkResponse", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "chunkIndex": chunkIndex, + "to": sender, + }) + + bex.netSender.SendTo( + MessageBlobChunkResponse[RI]{ + msg.RequestHandle, + msg.BlobDigest, + chunkIndex, + chunk, + }, + sender, + ) +} + +func (bex *blobExchangeState[RI]) messageBlobChunkResponse(msg MessageBlobChunkResponse[RI], sender commontypes.OracleID) { + blob, ok := bex.blobs[msg.BlobDigest] + if !ok { + bex.logger.Debug("dropping MessageBlobChunkResponse for unknown blob", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + }) + return + } + + chunkIndex := msg.ChunkIndex + + if !(0 <= chunkIndex && chunkIndex < uint64(len(blob.chunks))) { + bex.logger.Warn("dropping MessageBlobChunkResponse, chunk index out of range", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + "chunkIndex": chunkIndex, + "chunkCount": len(blob.chunks), + }) + return + } + + if blob.chunks[chunkIndex].have { + bex.logger.Debug("dropping MessageBlobChunkResponse, already have chunk", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + "chunkIndex": chunkIndex, + }) + return + } + + expectedChunkDigest := blob.chunks[chunkIndex].digest + actualChunkDigest := blobtypes.MakeBlobChunkDigest(msg.Chunk) + if expectedChunkDigest != actualChunkDigest { + bex.logger.Debug("dropping MessageBlobChunkResponse, chunk digest mismatch", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + "chunkIndex": chunkIndex, + "expectedDigest": expectedChunkDigest, + "actualDigest": actualChunkDigest, + }) + return + } + + bex.logger.Debug("received MessageBlobChunkResponse", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + "chunkIndex": chunkIndex, + "payloadLength": blob.payloadLength, + }) + + tx, err := bex.kv.NewReadWriteTransactionUnchecked() + if err != nil { + bex.logger.Error("failed to create read-write transaction for MessageBlobChunkResponse", commontypes.LogFields{ + "error": err, + }) + return + } + defer tx.Discard() + + err = tx.WriteBlobChunk(msg.BlobDigest, chunkIndex, msg.Chunk) + if err != nil { + bex.logger.Error("failed to write blob chunk for MessageBlobChunkResponse", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + "chunkIndex": chunkIndex, + "error": err, + }) + return + } + + err = tx.WriteBlobMeta(msg.BlobDigest, blob.payloadLength) + if err != nil { + bex.logger.Error("failed to write blob meta for MessageBlobChunkResponse", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + "chunkIndex": chunkIndex, + "error": err, + }) + return + } + + err = tx.Commit() + if err != nil { + bex.logger.Error("failed to commit transaction for MessageBlobChunkResponse", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + "chunkIndex": chunkIndex, + "error": err, + }) + return + } + + blob.chunks[chunkIndex].have = true + + for _, chunk := range blob.chunks { + if !chunk.have { + return + } + } + + bex.logger.Debug("blob fully received", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + "payloadLength": blob.payloadLength, + }) + close(blob.chNotifyPayloadAvailable) + bex.sendAvailabilitySignature(msg.BlobDigest, blob.submitter) +} + +func (bex *blobExchangeState[RI]) messageBlobAvailable(msg MessageBlobAvailable[RI], sender commontypes.OracleID) { + blob, ok := bex.blobs[msg.BlobDigest] + if !ok { + bex.logger.Debug("dropping MessageBlobAvailable for unknown blob", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + }) + return + } + + if blob.submitter != bex.id { + bex.logger.Debug("dropping MessageBlobAvailable, not the submitter", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + "submitter": blob.submitter, + "localID": bex.id, + }) + return + } + + // check if we already have signature from oracle + if len(blob.oracles[sender].signature) != 0 { + bex.logger.Debug("dropping MessageBlobAvailable, already have signature from oracle", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + }) + return + } + + // check if we already have a certificate + if blob.certOrNil != nil { + bex.logger.Debug("dropping MessageBlobAvailable, already have certificate", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + }) + return + } + + // check signature + if err := msg.Signature.Verify(msg.BlobDigest, bex.config.OracleIdentities[sender].OffchainPublicKey); err != nil { + bex.logger.Debug("dropping MessageBlobAvailable, invalid signature", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + }) + return + } + + // save signature for oracle + blob.oracles[sender].signature = msg.Signature + + // when we obtain certificate, notify upcall + threshold := bex.config.N() - bex.config.F + signers := 0 + for _, oracle := range blob.oracles { + if len(oracle.signature) != 0 { + signers++ + } + } + + bex.logger.Debug("received MessageBlobAvailable", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "sender": sender, + "signers": signers, + "threshold": threshold, + }) + + if signers < threshold { + return + } + + { + var abass []AttributedBlobAvailabilitySignature + for i, oracle := range blob.oracles { + if len(oracle.signature) != 0 { + abass = append(abass, AttributedBlobAvailabilitySignature{ + oracle.signature, + commontypes.OracleID(i), + }) + } + } + + var chunkDigests []BlobChunkDigest + for _, chunk := range blob.chunks { + chunkDigests = append(chunkDigests, chunk.digest) + } + + lcb := LightCertifiedBlob{ + chunkDigests, + blob.payloadLength, + blob.expirySeqNr, + blob.submitter, + abass, + } + + if err := lcb.Verify(bex.config.ConfigDigest, bex.config.OracleIdentities, threshold); err != nil { + bex.logger.Critical("assumption violation: failed to verify own LightCertifiedBlob", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + "error": err, + }) + return + } + + bex.logger.Debug("assembled blob availability certificate", commontypes.LogFields{ + "blobDigest": msg.BlobDigest, + }) + + blob.certOrNil = &lcb + close(blob.chNotifyCertAvailable) + } +} + +func (bex *blobExchangeState[RI]) eventMissingChunk(ev EventMissingBlobChunk[RI]) { + blob, ok := bex.blobs[ev.BlobDigest] + if !ok { + bex.logger.Debug("dropping EventMissingBlobChunk, unknown blob", commontypes.LogFields{ + "blobDigest": ev.BlobDigest, + }) + return + } + + for i, chunk := range blob.chunks { + if !chunk.have { + chunkIndex := uint64(i) + + bex.logger.Debug("broadcasting MessageBlobChunkRequest", commontypes.LogFields{ + "blobDigest": ev.BlobDigest, + "chunkIndex": chunkIndex, + "payloadLength": blob.payloadLength, + }) + bex.netSender.Broadcast( + MessageBlobChunkRequest[RI]{ + nil, + ev.BlobDigest, + chunkIndex, + }, + ) + + bex.missingChunkScheduler.ScheduleDelay(EventMissingBlobChunk[RI]{ + ev.BlobDigest, + }, DeltaBlobChunkRequest) + return + } + } +} + +func (bex *blobExchangeState[RI]) eventMissingCert(ev EventMissingBlobCert[RI]) { + blob, ok := bex.blobs[ev.BlobDigest] + if !ok { + bex.logger.Debug("dropping EventMissingBlobCert, unknown blob", commontypes.LogFields{ + "blobDigest": ev.BlobDigest, + }) + return + } + + if blob.certOrNil != nil { + return + } + + var chunkDigests []BlobChunkDigest + for _, chnk := range blob.chunks { + chunkDigests = append(chunkDigests, chnk.digest) + } + + bex.netSender.Broadcast(MessageBlobOffer[RI]{ + chunkDigests, + blob.payloadLength, + blob.expirySeqNr, + blob.submitter, + }) + + bex.missingCertScheduler.ScheduleDelay(EventMissingBlobCert[RI]{ + ev.BlobDigest, + }, DeltaBlobCertRequest) +} + +func (bex *blobExchangeState[RI]) sendAvailabilitySignature(blobDigest BlobDigest, submitter commontypes.OracleID) { + + // delete(bex.blobs, blobDigest) + + bas, err := blobtypes.MakeBlobAvailabilitySignature(blobDigest, bex.offchainKeyring.OffchainSign) + if err != nil { + bex.logger.Error("failed to make blob availability signature", commontypes.LogFields{ + "blobDigest": blobDigest, + "error": err, + }) + return + } + + bex.logger.Debug("sending MessageBlobAvailable", commontypes.LogFields{ + "blobDigest": blobDigest, + "submitter": submitter, + }) + bex.netSender.SendTo( + MessageBlobAvailable[RI]{ + blobDigest, + bas, + }, + submitter, + ) +} + +func (bex *blobExchangeState[RI]) processBlobBroadcastRequest(req blobBroadcastRequest) { + var chunkDigests []BlobChunkDigest + var chunks []chunk + payload := req.payload + payloadLength := uint64(len(payload)) + + for i, chunkIdx := 0, 0; i < len(payload); i, chunkIdx = i+BlobChunkSize, chunkIdx+1 { + payloadChunk := payload[i:min(i+BlobChunkSize, len(payload))] + + // prepare for offer + chunkDigest := blobtypes.MakeBlobChunkDigest(payloadChunk) + chunkDigests = append(chunkDigests, chunkDigest) + + // for local accounting + chunk := chunk{true, chunkDigest} + chunks = append(chunks, chunk) + } + + expirySeqNr := req.expirySeqNr + submitter := bex.id + + blobDigest := blobtypes.MakeBlobDigest( + bex.config.ConfigDigest, + chunkDigests, + payloadLength, + expirySeqNr, + submitter, + ) + + var chNotifyCertAvailable chan struct{} + if existingBlob, ok := bex.blobs[blobDigest]; ok { + chNotifyCertAvailable = existingBlob.chNotifyCertAvailable + } else { + // if we haven't written the chunks to kv, we can't serve requests + if err := bex.writeBlob(blobDigest, payloadLength, payload); err != nil { + bex.chBlobBroadcastResponse <- blobBroadcastResponse{ + nil, + fmt.Errorf("failed to write blob: %w", err), + } + return + } + + // write in-memory state + chNotifyCertAvailable = make(chan struct{}) + + chNotifyPayloadAvailable := make(chan struct{}) + close(chNotifyPayloadAvailable) + + bex.blobs[blobDigest] = &blob{ + chNotifyCertAvailable, + chNotifyPayloadAvailable, + + nil, + + chunks, + make([]blobOracle, bex.config.N()), + payloadLength, + expirySeqNr, + submitter, + } + + bex.missingCertScheduler.ScheduleDelay(EventMissingBlobCert[RI]{ + blobDigest, + }, 0) + } + + chCert := make(chan LightCertifiedBlob) + bex.chBlobBroadcastResponse <- blobBroadcastResponse{chCert, nil} + + bex.subs.Go(func() { + chDone := bex.ctx.Done() + + select { + case <-chNotifyCertAvailable: + select { + case bex.chLocalEvent <- EventRespondWithBlobCert[RI]{blobDigest, chCert}: + case <-chDone: + } + case <-chDone: + } + }) +} + +func (bex *blobExchangeState[RI]) eventRespondWithBlobCert(ev EventRespondWithBlobCert[RI]) { + blob, ok := bex.blobs[ev.BlobDigest] + if !ok { + bex.logger.Warn("dropping EventRespondWithBlobCert, no such blob", commontypes.LogFields{ + "blobDigest": ev.BlobDigest, + }) + return + } + + if blob.certOrNil == nil { + close(ev.Channel) + bex.logger.Critical("assumption violation: dropping EventRespondWithBlobCert, no cert available", commontypes.LogFields{ + "blobDigest": ev.BlobDigest, + }) + return + } + + select { + case ev.Channel <- *blob.certOrNil: + case <-bex.ctx.Done(): + } +} + +func (bex *blobExchangeState[RI]) writeBlob(blobDigest BlobDigest, payloadLength uint64, payload []byte) error { + tx, err := bex.kv.NewReadWriteTransactionUnchecked() + if err != nil { + return fmt.Errorf("failed to create read/write transaction: %w", err) + } + defer tx.Discard() + for i, chunkIdx := 0, uint64(0); i < len(payload); i, chunkIdx = i+BlobChunkSize, chunkIdx+1 { + payloadChunk := payload[i:min(i+BlobChunkSize, len(payload))] + if err := tx.WriteBlobChunk(blobDigest, chunkIdx, payloadChunk); err != nil { + return fmt.Errorf("failed to write local blob chunk: %w", err) + } + } + + if err := tx.WriteBlobMeta(blobDigest, payloadLength); err != nil { + return fmt.Errorf("failed to write local blob meta: %w", err) + } + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit kv transaction: %w", err) + } + return nil +} + +func (bex *blobExchangeState[RI]) processBlobFetchRequest(req blobFetchRequest) { + threshold := bex.config.N() - bex.config.F + cert := req.cert + err := cert.Verify( + bex.config.ConfigDigest, + bex.config.OracleIdentities, + threshold, + ) + if err != nil { + bex.chBlobFetchResponse <- blobFetchResponse{nil, fmt.Errorf("invalid cert")} + } + + blobDigest := blobtypes.MakeBlobDigest( + bex.config.ConfigDigest, + cert.ChunkDigests, + cert.PayloadLength, + cert.ExpirySeqNr, + cert.Submitter, + ) + + var chNotifyPayloadAvailable chan struct{} + if existingBlob, ok := bex.blobs[blobDigest]; ok { + chNotifyPayloadAvailable = existingBlob.chNotifyPayloadAvailable + } else { + chNotifyPayloadAvailable = make(chan struct{}) + chNotifyCertAvailable := make(chan struct{}) + + var chunks []chunk + for _, chunkDigest := range cert.ChunkDigests { + chunks = append(chunks, chunk{false, chunkDigest}) + } + + bex.blobs[blobDigest] = &blob{ + chNotifyCertAvailable, + chNotifyPayloadAvailable, + + &req.cert, + + chunks, + make([]blobOracle, bex.config.N()), + cert.PayloadLength, + cert.ExpirySeqNr, + cert.Submitter, + } + + bex.missingChunkScheduler.ScheduleDelay(EventMissingBlobChunk[RI]{ + blobDigest, + }, 0) + } + + chPayload := make(chan []byte) + bex.chBlobFetchResponse <- blobFetchResponse{chPayload, nil} + + bex.subs.Go(func() { + chDone := bex.ctx.Done() + + select { + case <-chNotifyPayloadAvailable: + select { + case bex.chLocalEvent <- EventRespondWithBlobPayload[RI]{blobDigest, chPayload}: + case <-chDone: + } + case <-chDone: + } + }) +} + +func (bex *blobExchangeState[RI]) eventRespondWithBlobPayload(ev EventRespondWithBlobPayload[RI]) { + payload, err := bex.readBlobPayload(ev.BlobDigest) + if err != nil { + close(ev.Channel) + bex.logger.Warn("dropping EventRespondWithBlobPayload, failed to read payload", commontypes.LogFields{ + "blobDigest": ev.BlobDigest, + }) + return + } + + select { + case ev.Channel <- payload: + case <-bex.ctx.Done(): + } +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/db.go b/offchainreporting2plus/internal/ocr3_1/protocol/db.go new file mode 100644 index 00000000..76812cfb --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/db.go @@ -0,0 +1,32 @@ +package protocol + +import ( + "context" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type PacemakerState struct { + Epoch uint64 + HighestSentNewEpochWish uint64 +} + +type StatePersistenceState struct { + HighestPersistedStateTransitionBlockSeqNr uint64 +} + +type Database interface { + types.ConfigDatabase + + ReadPacemakerState(ctx context.Context, configDigest types.ConfigDigest) (PacemakerState, error) + WritePacemakerState(ctx context.Context, configDigest types.ConfigDigest, state PacemakerState) error + + ReadCert(ctx context.Context, configDigest types.ConfigDigest) (CertifiedPrepareOrCommit, error) + WriteCert(ctx context.Context, configDigest types.ConfigDigest, cert CertifiedPrepareOrCommit) error + + ReadStatePersistenceState(ctx context.Context, configDigest types.ConfigDigest) (StatePersistenceState, error) + WriteStatePersistenceState(ctx context.Context, configDigest types.ConfigDigest, state StatePersistenceState) error + + ReadAttestedStateTransitionBlock(ctx context.Context, configDigest types.ConfigDigest, seqNr uint64) (AttestedStateTransitionBlock, error) + WriteAttestedStateTransitionBlock(ctx context.Context, configDigest types.ConfigDigest, seqNr uint64, ast AttestedStateTransitionBlock) error +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/event.go b/offchainreporting2plus/internal/ocr3_1/protocol/event.go new file mode 100644 index 00000000..d55e3d7c --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/event.go @@ -0,0 +1,241 @@ +package protocol + +import ( + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type EventToPacemaker[RI any] interface { + processPacemaker(pace *pacemakerState[RI]) +} + +type EventProgress[RI any] struct{} + +var _ EventToPacemaker[struct{}] = (*EventProgress[struct{}])(nil) // implements EventToPacemaker + +func (ev EventProgress[RI]) processPacemaker(pace *pacemakerState[RI]) { + pace.eventProgress() +} + +type EventNewEpochRequest[RI any] struct{} + +var _ EventToPacemaker[struct{}] = (*EventNewEpochRequest[struct{}])(nil) // implements EventToPacemaker + +func (ev EventNewEpochRequest[RI]) processPacemaker(pace *pacemakerState[RI]) { + pace.eventNewEpochRequest() +} + +type EventToOutcomeGeneration[RI any] interface { + processOutcomeGeneration(outgen *outcomeGenerationState[RI]) +} + +type EventNewEpochStart[RI any] struct { + Epoch uint64 +} + +var _ EventToOutcomeGeneration[struct{}] = EventNewEpochStart[struct{}]{} + +func (ev EventNewEpochStart[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI]) { + outgen.eventNewEpochStart(ev) +} + +type EventComputedQuery[RI any] struct { + Epoch uint64 + SeqNr uint64 + Query types.Query +} + +var _ EventToOutcomeGeneration[struct{}] = EventComputedQuery[struct{}]{} + +func (ev EventComputedQuery[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI]) { + outgen.eventComputedQuery(ev) +} + +type EventComputedValidateVerifyObservation[RI any] struct { + Epoch uint64 + SeqNr uint64 + Sender commontypes.OracleID +} + +var _ EventToOutcomeGeneration[struct{}] = EventComputedValidateVerifyObservation[struct{}]{} + +func (ev EventComputedValidateVerifyObservation[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI]) { + outgen.eventComputedValidateVerifyObservation(ev) +} + +type EventComputedObservationQuorumSuccess[RI any] struct { + Epoch uint64 + SeqNr uint64 +} + +var _ EventToOutcomeGeneration[struct{}] = EventComputedObservationQuorumSuccess[struct{}]{} + +func (ev EventComputedObservationQuorumSuccess[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI]) { + outgen.eventComputedObservationQuorumSuccess(ev) +} + +type EventComputedObservation[RI any] struct { + Epoch uint64 + SeqNr uint64 + Query types.Query + Observation types.Observation +} + +var _ EventToOutcomeGeneration[struct{}] = EventComputedObservation[struct{}]{} + +func (ev EventComputedObservation[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI]) { + outgen.eventComputedObservation(ev) +} + +type EventComputedProposalStateTransition[RI any] struct { + Epoch uint64 + SeqNr uint64 + KeyValueStoreReadWriteTransaction KeyValueStoreReadWriteTransaction + stateTransitionInfo stateTransitionInfo +} + +var _ EventToOutcomeGeneration[struct{}] = EventComputedProposalStateTransition[struct{}]{} + +func (ev EventComputedProposalStateTransition[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI]) { + outgen.eventComputedProposalStateTransition(ev) +} + +type EventToReportAttestation[RI any] interface { + processReportAttestation(repatt *reportAttestationState[RI]) +} + +type EventToStatePersistence[RI any] interface { + processStatePersistence(state *statePersistenceState[RI]) +} + +type EventToBlobExchange[RI any] interface { + processBlobExchange(bex *blobExchangeState[RI]) +} + +type EventToTransmission[RI any] interface { + processTransmission(t *transmissionState[RI]) +} + +type EventMissingOutcome[RI any] struct { + SeqNr uint64 +} + +var _ EventToReportAttestation[struct{}] = EventMissingOutcome[struct{}]{} // implements EventToReportAttestation + +func (ev EventMissingOutcome[RI]) processReportAttestation(repatt *reportAttestationState[RI]) { + repatt.eventMissingOutcome(ev) +} + +type EventCertifiedCommit[RI any] struct { + CertifiedCommittedReports CertifiedCommittedReports[RI] +} + +var _ EventToReportAttestation[struct{}] = EventCertifiedCommit[struct{}]{} // implements EventToReportAttestation + +func (ev EventCertifiedCommit[RI]) processReportAttestation(repatt *reportAttestationState[RI]) { + repatt.eventCertifiedCommit(ev) +} + +type EventComputedReports[RI any] struct { + SeqNr uint64 + ReportsPlus []ocr3types.ReportPlus[RI] +} + +var _ EventToReportAttestation[struct{}] = EventComputedReports[struct{}]{} // implements EventToReportAttestation + +func (ev EventComputedReports[RI]) processReportAttestation(repatt *reportAttestationState[RI]) { + repatt.eventComputedReports(ev) +} + +type EventAttestedReport[RI any] struct { + SeqNr uint64 + Index int + AttestedReport AttestedReportMany[RI] + TransmissionScheduleOverride *ocr3types.TransmissionSchedule +} + +var _ EventToTransmission[struct{}] = EventAttestedReport[struct{}]{} // implements EventToTransmission + +func (ev EventAttestedReport[RI]) processTransmission(t *transmissionState[RI]) { + t.eventAttestedReport(ev) +} + +type EventStateSyncRequest[RI any] struct { + SeqNr uint64 +} + +var _ EventToStatePersistence[struct{}] = EventStateSyncRequest[struct{}]{} // implements EventToStatePersistence + +func (ev EventStateSyncRequest[RI]) processStatePersistence(state *statePersistenceState[RI]) { + state.eventStateSyncRequest(ev) +} + +type EventBlockSyncSummaryHeartbeat[RI any] struct{} + +var _ EventToStatePersistence[struct{}] = EventBlockSyncSummaryHeartbeat[struct{}]{} // implements EventToStatePersistence + +func (ev EventBlockSyncSummaryHeartbeat[RI]) processStatePersistence(state *statePersistenceState[RI]) { + state.eventEventBlockSyncSummaryHeartbeat(ev) +} + +type EventExpiredBlockSyncRequest[RI any] struct { + RequestedFrom commontypes.OracleID + Nonce uint64 +} + +var _ EventToStatePersistence[struct{}] = EventExpiredBlockSyncRequest[struct{}]{} // implements EventToStatePersistence + +func (ev EventExpiredBlockSyncRequest[RI]) processStatePersistence(state *statePersistenceState[RI]) { + state.eventExpiredBlockSyncRequest(ev) +} + +type EventReadyToSendNextBlockSyncRequest[RI any] struct{} + +var _ EventToStatePersistence[struct{}] = EventReadyToSendNextBlockSyncRequest[struct{}]{} // implements EventToStatePersistence + +func (ev EventReadyToSendNextBlockSyncRequest[RI]) processStatePersistence(state *statePersistenceState[RI]) { + state.eventReadyToSendNextBlockSyncRequest(ev) +} + +type EventMissingBlobChunk[RI any] struct { + BlobDigest BlobDigest +} + +var _ EventToBlobExchange[struct{}] = EventMissingBlobChunk[struct{}]{} // implements EventToBlobExchange + +func (ev EventMissingBlobChunk[RI]) processBlobExchange(bex *blobExchangeState[RI]) { + bex.eventMissingChunk(ev) +} + +type EventMissingBlobCert[RI any] struct { + BlobDigest BlobDigest +} + +var _ EventToBlobExchange[struct{}] = EventMissingBlobCert[struct{}]{} // implements EventToBlobExchange + +func (ev EventMissingBlobCert[RI]) processBlobExchange(bex *blobExchangeState[RI]) { + bex.eventMissingCert(ev) +} + +type EventRespondWithBlobCert[RI any] struct { + BlobDigest BlobDigest + Channel chan<- LightCertifiedBlob +} + +var _ EventToBlobExchange[struct{}] = EventRespondWithBlobCert[struct{}]{} // implements EventToBlobExchange + +func (ev EventRespondWithBlobCert[RI]) processBlobExchange(bex *blobExchangeState[RI]) { + bex.eventRespondWithBlobCert(ev) +} + +type EventRespondWithBlobPayload[RI any] struct { + BlobDigest BlobDigest + Channel chan<- []byte +} + +var _ EventToBlobExchange[struct{}] = EventRespondWithBlobPayload[struct{}]{} // implements EventToBlobExchange + +func (ev EventRespondWithBlobPayload[RI]) processBlobExchange(blobex *blobExchangeState[RI]) { + blobex.eventRespondWithBlobPayload(ev) +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/kv.go b/offchainreporting2plus/internal/ocr3_1/protocol/kv.go new file mode 100644 index 00000000..c293fc07 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/kv.go @@ -0,0 +1,89 @@ +package protocol + +import ( + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type KeyValueStoreReadTransaction interface { + // The only read part of the interface that the plugin might see. The rest + // of the methods might only be called by protocol code. + ocr3_1types.KeyValueReader + KeyValueStoreSemanticRead + Discard() +} + +type KeyValueStoreSemanticRead interface { + // Returns the sequence number of which the state the transaction + // represents. Really read from the database here, no cached values allowed. + ReadHighestCommittedSeqNr() (uint64, error) + + ReadBlob(BlobDigest) ([]byte, error) + ReadBlobMeta(BlobDigest) (uint64, error) + ReadBlobChunk(BlobDigest, uint64) ([]byte, error) +} + +type KeyValueStoreReadWriteTransaction interface { + KeyValueStoreReadTransaction + // The only write part of the interface that the plugin might see. The rest + // of the methods might only be called by protocol code. + ocr3_1types.KeyValueReadWriter + KeyValueStoreSemanticWrite + // Commit writes the new highest committed sequence number to the magic key + // (if the transaction is _not_ unchecked) and commits the transaction to + // the key value store, then discards the transaction. + Commit() error +} + +type KeyValueStoreSemanticWrite interface { + // GetWriteSet returns a map from keys in string encoding to values that + // have been written in this transaction. If the value of a key has been + // deleted, it is mapped to nil. + + GetWriteSet() ([]KeyValuePair, error) + + // WriteHighestCommittedSeqNr writes the given sequence number to the magic + // key. It is called before Commit on checked transactions. + WriteHighestCommittedSeqNr(seqNr uint64) error + + WriteBlobMeta(BlobDigest, uint64) error + WriteBlobChunk(BlobDigest, uint64, []byte) error +} + +type KeyValuePair struct { + Key []byte + Value []byte + Deleted bool +} + +type KeyValueStore interface { + // Must error if the key value store is not ready to apply state transition + // for the given sequence number. Must update the highest committed sequence + // number magic key upon commit. Convenience method for synchronization + // between outcome generation & state persistence. + NewReadWriteTransaction(postSeqNr uint64) (KeyValueStoreReadWriteTransaction, error) + // Must error if the key value store is not ready to apply state transition + // for the given sequence number. Convenience method for synchronization + // between outcome generation & state persistence. + NewReadTransaction(postSeqNr uint64) (KeyValueStoreReadTransaction, error) + + // Unchecked transactions are useful when you don't care that the + // transaction state represents the kv state as of some particular sequence + // number, mostly when writing auxiliary data to the kv store. Unchecked + // transactions do not update the highest committed sequence number magic + // key upon commit, as would checked transactions. + NewReadWriteTransactionUnchecked() (KeyValueStoreReadWriteTransaction, error) + // Unchecked transactions are useful when you don't care that the + // transaction state represents the kv state as of some particular sequence + // number, mostly when reading auxiliary data from the kv store. + NewReadTransactionUnchecked() (KeyValueStoreReadTransaction, error) + + // Deprecated: Kept for convenience/small diff, consider using + // [KeyValueStoreSemanticRead.ReadHighestCommittedSeqNr] instead. + HighestCommittedSeqNr() (uint64, error) + Close() error +} + +type KeyValueStoreFactory interface { + NewKeyValueStore(configDigest types.ConfigDigest) (KeyValueStore, error) +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/message.go b/offchainreporting2plus/internal/ocr3_1/protocol/message.go new file mode 100644 index 00000000..d93f8414 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/message.go @@ -0,0 +1,523 @@ +package protocol // + +import ( + "crypto/ed25519" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/byzquorum" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +// Message is the interface used to pass an inter-oracle message to the local +// oracle process. +type Message[RI any] interface { + // CheckSize checks whether the given message conforms to the limits imposed by + // reportingPluginLimits + CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool + + // process passes this Message instance to the oracle o, as a message from + // oracle with the given sender index + process(o *oracleState[RI], sender commontypes.OracleID) +} + +// MessageWithSender records a msg with the index of the sender oracle +type MessageWithSender[RI any] struct { + Msg Message[RI] + Sender commontypes.OracleID +} + +type MessageToPacemaker[RI any] interface { + Message[RI] + + processPacemaker(pace *pacemakerState[RI], sender commontypes.OracleID) +} + +type MessageToPacemakerWithSender[RI any] struct { + msg MessageToPacemaker[RI] + sender commontypes.OracleID +} + +type MessageToOutcomeGeneration[RI any] interface { + Message[RI] + + processOutcomeGeneration(outgen *outcomeGenerationState[RI], sender commontypes.OracleID) + + epoch() uint64 +} + +type MessageToOutcomeGenerationWithSender[RI any] struct { + msg MessageToOutcomeGeneration[RI] + sender commontypes.OracleID +} + +type MessageToReportAttestation[RI any] interface { + Message[RI] + + processReportAttestation(repatt *reportAttestationState[RI], sender commontypes.OracleID) +} + +type MessageToReportAttestationWithSender[RI any] struct { + msg MessageToReportAttestation[RI] + sender commontypes.OracleID +} + +type MessageToStatePersistence[RI any] interface { + Message[RI] + + processStatePersistence(state *statePersistenceState[RI], sender commontypes.OracleID) +} + +type MessageToStatePersistenceWithSender[RI any] struct { + msg MessageToStatePersistence[RI] + sender commontypes.OracleID +} + +type MessageToBlobExchange[RI any] interface { + Message[RI] + + processBlobExchange(bex *blobExchangeState[RI], sender commontypes.OracleID) +} + +type MessageToBlobExchangeWithSender[RI any] struct { + msg MessageToBlobExchange[RI] + sender commontypes.OracleID +} + +type MessageNewEpochWish[RI any] struct { + Epoch uint64 +} + +var _ MessageToPacemaker[struct{}] = (*MessageNewEpochWish[struct{}])(nil) + +func (msg MessageNewEpochWish[RI]) CheckSize(n int, f int, _ ocr3_1types.ReportingPluginLimits, _ int) bool { + return true +} + +func (msg MessageNewEpochWish[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToPacemaker <- MessageToPacemakerWithSender[RI]{msg, sender} +} + +func (msg MessageNewEpochWish[RI]) processPacemaker(pace *pacemakerState[RI], sender commontypes.OracleID) { + pace.messageNewEpochWish(msg, sender) +} + +type MessageEpochStartRequest[RI any] struct { + Epoch uint64 + HighestCertified CertifiedPrepareOrCommit + SignedHighestCertifiedTimestamp SignedHighestCertifiedTimestamp +} + +var _ MessageToOutcomeGeneration[struct{}] = (*MessageEpochStartRequest[struct{}])(nil) + +func (msg MessageEpochStartRequest[RI]) CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool { + if !msg.HighestCertified.CheckSize(n, f, limits, maxReportSigLen) { + return false + } + if len(msg.SignedHighestCertifiedTimestamp.Signature) != ed25519.SignatureSize { + return false + } + return true +} + +func (msg MessageEpochStartRequest[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToOutcomeGeneration <- MessageToOutcomeGenerationWithSender[RI]{ + msg, + sender, + } +} + +func (msg MessageEpochStartRequest[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI], sender commontypes.OracleID) { + outgen.messageEpochStartRequest(msg, sender) +} + +func (msg MessageEpochStartRequest[RI]) epoch() uint64 { + return msg.Epoch +} + +type MessageEpochStart[RI any] struct { + Epoch uint64 + EpochStartProof EpochStartProof +} + +var _ MessageToOutcomeGeneration[struct{}] = (*MessageEpochStart[struct{}])(nil) + +func (msg MessageEpochStart[RI]) CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool { + if !msg.EpochStartProof.HighestCertified.CheckSize(n, f, limits, maxReportSigLen) { + return false + } + if len(msg.EpochStartProof.HighestCertifiedProof) != byzquorum.Size(n, f) { + return false + } + for _, ashct := range msg.EpochStartProof.HighestCertifiedProof { + if len(ashct.SignedHighestCertifiedTimestamp.Signature) != ed25519.SignatureSize { + return false + } + } + return true +} + +func (msg MessageEpochStart[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToOutcomeGeneration <- MessageToOutcomeGenerationWithSender[RI]{ + msg, + sender, + } +} + +func (msg MessageEpochStart[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI], sender commontypes.OracleID) { + outgen.messageEpochStart(msg, sender) +} + +func (msg MessageEpochStart[RI]) epoch() uint64 { + return msg.Epoch +} + +type MessageRoundStart[RI any] struct { + Epoch uint64 + SeqNr uint64 + Query types.Query +} + +var _ MessageToOutcomeGeneration[struct{}] = (*MessageRoundStart[struct{}])(nil) + +func (msg MessageRoundStart[RI]) CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool { + return len(msg.Query) <= limits.MaxQueryLength +} + +func (msg MessageRoundStart[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToOutcomeGeneration <- MessageToOutcomeGenerationWithSender[RI]{ + msg, + sender, + } +} + +func (msg MessageRoundStart[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI], sender commontypes.OracleID) { + outgen.messageRoundStart(msg, sender) +} + +func (msg MessageRoundStart[RI]) epoch() uint64 { + return msg.Epoch +} + +type MessageObservation[RI any] struct { + Epoch uint64 + SeqNr uint64 + SignedObservation SignedObservation +} + +var _ MessageToOutcomeGeneration[struct{}] = (*MessageObservation[struct{}])(nil) + +func (msg MessageObservation[RI]) CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool { + return len(msg.SignedObservation.Observation) <= limits.MaxObservationLength && len(msg.SignedObservation.Signature) == ed25519.SignatureSize +} + +func (msg MessageObservation[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToOutcomeGeneration <- MessageToOutcomeGenerationWithSender[RI]{ + msg, + sender, + } +} + +func (msg MessageObservation[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI], sender commontypes.OracleID) { + outgen.messageObservation(msg, sender) +} + +func (msg MessageObservation[RI]) epoch() uint64 { + return msg.Epoch +} + +type MessageProposal[RI any] struct { + Epoch uint64 + SeqNr uint64 + AttributedSignedObservations []AttributedSignedObservation +} + +var _ MessageToOutcomeGeneration[struct{}] = MessageProposal[struct{}]{} + +func (msg MessageProposal[RI]) CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool { + if len(msg.AttributedSignedObservations) > n { + return false + } + for _, aso := range msg.AttributedSignedObservations { + if len(aso.SignedObservation.Observation) > limits.MaxObservationLength { + return false + } + if len(aso.SignedObservation.Signature) != ed25519.SignatureSize { + return false + } + } + return true +} + +func (msg MessageProposal[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToOutcomeGeneration <- MessageToOutcomeGenerationWithSender[RI]{ + msg, + sender, + } +} + +func (msg MessageProposal[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI], sender commontypes.OracleID) { + outgen.messageProposal(msg, sender) +} + +func (msg MessageProposal[RI]) epoch() uint64 { + return msg.Epoch +} + +type MessagePrepare[RI any] struct { + Epoch uint64 + SeqNr uint64 + Signature PrepareSignature +} + +var _ MessageToOutcomeGeneration[struct{}] = MessagePrepare[struct{}]{} + +func (msg MessagePrepare[RI]) CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool { + return len(msg.Signature) == ed25519.SignatureSize +} + +func (msg MessagePrepare[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToOutcomeGeneration <- MessageToOutcomeGenerationWithSender[RI]{ + msg, + sender, + } +} + +func (msg MessagePrepare[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI], sender commontypes.OracleID) { + outgen.messagePrepare(msg, sender) +} + +func (msg MessagePrepare[RI]) epoch() uint64 { + return msg.Epoch +} + +type MessageCommit[RI any] struct { + Epoch uint64 + SeqNr uint64 + Signature CommitSignature +} + +var _ MessageToOutcomeGeneration[struct{}] = MessageCommit[struct{}]{} + +func (msg MessageCommit[RI]) CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool { + return len(msg.Signature) == ed25519.SignatureSize +} + +func (msg MessageCommit[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToOutcomeGeneration <- MessageToOutcomeGenerationWithSender[RI]{ + msg, + sender, + } +} + +func (msg MessageCommit[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI], sender commontypes.OracleID) { + outgen.messageCommit(msg, sender) +} + +func (msg MessageCommit[RI]) epoch() uint64 { + return msg.Epoch +} + +type MessageReportSignatures[RI any] struct { + SeqNr uint64 + ReportSignatures [][]byte +} + +var _ MessageToReportAttestation[struct{}] = MessageReportSignatures[struct{}]{} + +func (msg MessageReportSignatures[RI]) CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool { + if len(msg.ReportSignatures) > limits.MaxReportCount { + return false + } + for _, sig := range msg.ReportSignatures { + if len(sig) > maxReportSigLen { + return false + } + } + + return true +} + +func (msg MessageReportSignatures[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToReportAttestation <- MessageToReportAttestationWithSender[RI]{msg, sender} +} + +func (msg MessageReportSignatures[RI]) processReportAttestation(repatt *reportAttestationState[RI], sender commontypes.OracleID) { + repatt.messageReportSignatures(msg, sender) +} + +type MessageCertifiedCommitRequest[RI any] struct { + SeqNr uint64 +} + +var _ MessageToReportAttestation[struct{}] = MessageCertifiedCommitRequest[struct{}]{} + +func (msg MessageCertifiedCommitRequest[RI]) CheckSize(n int, f int, _ ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool { + return true +} + +func (msg MessageCertifiedCommitRequest[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToReportAttestation <- MessageToReportAttestationWithSender[RI]{msg, sender} +} + +func (msg MessageCertifiedCommitRequest[RI]) processReportAttestation(repatt *reportAttestationState[RI], sender commontypes.OracleID) { + repatt.messageCertifiedCommitRequest(msg, sender) +} + +type MessageCertifiedCommit[RI any] struct { + CertifiedCommittedReports CertifiedCommittedReports[RI] +} + +var _ MessageToReportAttestation[struct{}] = MessageCertifiedCommit[struct{}]{} + +func (msg MessageCertifiedCommit[RI]) CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool { + return msg.CertifiedCommittedReports.CheckSize(n, f, limits, maxReportSigLen) +} + +func (msg MessageCertifiedCommit[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToReportAttestation <- MessageToReportAttestationWithSender[RI]{msg, sender} +} + +func (msg MessageCertifiedCommit[RI]) processReportAttestation(repatt *reportAttestationState[RI], sender commontypes.OracleID) { + repatt.messageCertifiedCommit(msg, sender) +} + +type MessageBlockSyncRequest[RI any] struct { + RequestHandle types.RequestHandle // actual handle for outbound message, sentinel for inbound + HighestCommittedSeqNr uint64 + Nonce uint64 +} + +var _ MessageToStatePersistence[struct{}] = MessageBlockSyncRequest[struct{}]{} + +func (msg MessageBlockSyncRequest[RI]) CheckSize(n int, f int, _ ocr3_1types.ReportingPluginLimits, _ int) bool { + return true +} + +func (msg MessageBlockSyncRequest[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToStatePersistence <- MessageToStatePersistenceWithSender[RI]{msg, sender} +} + +func (msg MessageBlockSyncRequest[RI]) processStatePersistence(state *statePersistenceState[RI], sender commontypes.OracleID) { + state.messageBlockSyncReq(msg, sender) +} + +type MessageBlockSyncSummary[RI any] struct { + LowestPersistedSeqNr uint64 +} + +var _ MessageToStatePersistence[struct{}] = MessageBlockSyncSummary[struct{}]{} + +func (msg MessageBlockSyncSummary[RI]) CheckSize(n int, f int, _ ocr3_1types.ReportingPluginLimits, _ int) bool { + return true +} + +func (msg MessageBlockSyncSummary[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToStatePersistence <- MessageToStatePersistenceWithSender[RI]{msg, sender} +} + +func (msg MessageBlockSyncSummary[RI]) processStatePersistence(state *statePersistenceState[RI], sender commontypes.OracleID) { + state.messageBlockSyncSummary(msg, sender) +} + +type MessageBlockSync[RI any] struct { + RequestHandle types.RequestHandle // actual handle for outbound message, sentinel for inbound + AttestedStateTransitionBlocks []AttestedStateTransitionBlock + Nonce uint64 +} + +var _ MessageToStatePersistence[struct{}] = MessageBlockSync[struct{}]{} + +func (msg MessageBlockSync[RI]) CheckSize(n int, f int, _ ocr3_1types.ReportingPluginLimits, _ int) bool { + return true +} + +func (msg MessageBlockSync[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToStatePersistence <- MessageToStatePersistenceWithSender[RI]{msg, sender} +} + +func (msg MessageBlockSync[RI]) processStatePersistence(state *statePersistenceState[RI], sender commontypes.OracleID) { + state.messageBlockSync(msg, sender) +} + +type MessageBlobOffer[RI any] struct { + ChunkDigests []BlobChunkDigest + PayloadLength uint64 + ExpirySeqNr uint64 + Submitter commontypes.OracleID +} + +var _ MessageToBlobExchange[struct{}] = MessageBlobOffer[struct{}]{} + +func (msg MessageBlobOffer[RI]) CheckSize(n int, f int, _ ocr3_1types.ReportingPluginLimits, _ int) bool { + return true // TODO: add proper size checks +} + +func (msg MessageBlobOffer[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToBlobExchange <- MessageToBlobExchangeWithSender[RI]{msg, sender} +} + +func (msg MessageBlobOffer[RI]) processBlobExchange(bex *blobExchangeState[RI], sender commontypes.OracleID) { + bex.messageBlobOffer(msg, sender) +} + +type MessageBlobChunkRequest[RI any] struct { + RequestHandle types.RequestHandle // actual handle for outbound message, sentinel for inbound + BlobDigest BlobDigest + ChunkIndex uint64 +} + +var _ MessageToBlobExchange[struct{}] = MessageBlobChunkRequest[struct{}]{} + +func (msg MessageBlobChunkRequest[RI]) CheckSize(n int, f int, _ ocr3_1types.ReportingPluginLimits, _ int) bool { + return true // TODO: add proper size checks +} + +func (msg MessageBlobChunkRequest[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToBlobExchange <- MessageToBlobExchangeWithSender[RI]{msg, sender} +} + +func (msg MessageBlobChunkRequest[RI]) processBlobExchange(bex *blobExchangeState[RI], sender commontypes.OracleID) { + bex.messageBlobChunkRequest(msg, sender) +} + +type MessageBlobChunkResponse[RI any] struct { + RequestHandle types.RequestHandle // actual handle for outbound message, sentinel for inbound + + BlobDigest BlobDigest + ChunkIndex uint64 + Chunk []byte +} + +var _ MessageToBlobExchange[struct{}] = MessageBlobChunkResponse[struct{}]{} + +func (msg MessageBlobChunkResponse[RI]) CheckSize(n int, f int, _ ocr3_1types.ReportingPluginLimits, _ int) bool { + return true // TODO: add proper size checks +} + +func (msg MessageBlobChunkResponse[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToBlobExchange <- MessageToBlobExchangeWithSender[RI]{msg, sender} +} + +func (msg MessageBlobChunkResponse[RI]) processBlobExchange(bex *blobExchangeState[RI], sender commontypes.OracleID) { + bex.messageBlobChunkResponse(msg, sender) +} + +type MessageBlobAvailable[RI any] struct { + BlobDigest BlobDigest + Signature BlobAvailabilitySignature +} + +var _ MessageToBlobExchange[struct{}] = MessageBlobAvailable[struct{}]{} + +func (msg MessageBlobAvailable[RI]) CheckSize(n int, f int, _ ocr3_1types.ReportingPluginLimits, _ int) bool { + return true // TODO: add proper size checks +} + +func (msg MessageBlobAvailable[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToBlobExchange <- MessageToBlobExchangeWithSender[RI]{msg, sender} +} + +func (msg MessageBlobAvailable[RI]) processBlobExchange(bex *blobExchangeState[RI], sender commontypes.OracleID) { + bex.messageBlobAvailable(msg, sender) +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/messagebuffer.go b/offchainreporting2plus/internal/ocr3_1/protocol/messagebuffer.go new file mode 100644 index 00000000..1f414889 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/messagebuffer.go @@ -0,0 +1,32 @@ +package protocol + +import ( + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/ringbuffer" +) + +// We have this wrapper to deal with what appears to be a bug in the Go compiler +// that prevents us from using ringbuffer.RingBuffer in the outcome generation +// protocol: +// offchainreporting2plus/internal/ocr3/protocol/outcome_generation.go:241:21: internal compiler error: (*ringbuffer.RingBuffer[go.shape.interface { github.com/smartcontractkit/offchain-reporting/lib/offchainreporting2plus/internal/ocr3/protocol.epoch() uint64; github.com/smartcontractkit/offchain-reporting/lib/offchainreporting2plus/internal/ocr3/protocol.processOutcomeGeneration(*github.com/smartcontractkit/offchain-reporting/lib/offchainreporting2plus/internal/ocr3/protocol.outcomeGenerationState[go.shape.struct {}], github.com/smartcontractkit/offchain-reporting/lib/commontypes.OracleID) }]).Peek(buffer, (*[9]uintptr)(.dict[3])) (type go.shape.interface { github.com/smartcontractkit/offchain-reporting/lib/offchainreporting2plus/internal/ocr3/protocol.epoch() uint64; github.com/smartcontractkit/offchain-reporting/lib/offchainreporting2plus/internal/ocr3/protocol.processOutcomeGeneration(*github.com/smartcontractkit/offchain-reporting/lib/offchainreporting2plus/internal/ocr3/protocol.outcomeGenerationState[go.shape.struct {}], github.com/smartcontractkit/offchain-reporting/lib/commontypes.OracleID) }) is not shape-identical to MessageToOutcomeGeneration[go.shape.struct {}] +// Consider removing it in a future release. +type MessageBuffer[RI any] ringbuffer.RingBuffer[MessageToOutcomeGeneration[RI]] + +func NewMessageBuffer[RI any](cap int) *MessageBuffer[RI] { + return (*MessageBuffer[RI])(ringbuffer.NewRingBuffer[MessageToOutcomeGeneration[RI]](cap)) +} + +func (rb *MessageBuffer[RI]) Length() int { + return (*ringbuffer.RingBuffer[MessageToOutcomeGeneration[RI]])(rb).Length() +} + +func (rb *MessageBuffer[RI]) Peek() MessageToOutcomeGeneration[RI] { + return (*ringbuffer.RingBuffer[MessageToOutcomeGeneration[RI]])(rb).Peek() +} + +func (rb *MessageBuffer[RI]) Pop() MessageToOutcomeGeneration[RI] { + return (*ringbuffer.RingBuffer[MessageToOutcomeGeneration[RI]])(rb).Pop() +} + +func (rb *MessageBuffer[RI]) Push(msg MessageToOutcomeGeneration[RI]) { + (*ringbuffer.RingBuffer[MessageToOutcomeGeneration[RI]])(rb).Push(msg) +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/metrics.go b/offchainreporting2plus/internal/ocr3_1/protocol/metrics.go new file mode 100644 index 00000000..4e8f9785 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/metrics.go @@ -0,0 +1,100 @@ +package protocol + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/metricshelper" +) + +type pacemakerMetrics struct { + registerer prometheus.Registerer + epoch prometheus.Gauge + leader prometheus.Gauge +} + +func newPacemakerMetrics(registerer prometheus.Registerer, + logger commontypes.Logger) *pacemakerMetrics { + + epoch := prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "ocr3_1_epoch", + Help: "The total number of initialized epochs", + }) + metricshelper.RegisterOrLogError(logger, registerer, epoch, "ocr3_1_epoch") + + leader := prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "ocr3_1_experimental_leader_oid", + Help: "The leader oracle id", + }) + metricshelper.RegisterOrLogError(logger, registerer, leader, "ocr3_1_experimental_leader_oid") + + return &pacemakerMetrics{ + registerer, + epoch, + leader, + } +} + +func (pm *pacemakerMetrics) Close() { + pm.registerer.Unregister(pm.epoch) + pm.registerer.Unregister(pm.leader) +} + +type outcomeGenerationMetrics struct { + registerer prometheus.Registerer + committedSeqNr prometheus.Gauge + sentObservationsTotal prometheus.Counter + includedObservationsTotal prometheus.Counter + ledCommittedRoundsTotal prometheus.Counter +} + +func newOutcomeGenerationMetrics(registerer prometheus.Registerer, + logger commontypes.Logger) *outcomeGenerationMetrics { + + committedSeqNr := prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "ocr3_1_committed_sequence_number", + Help: "The committed sequence number", + }) + metricshelper.RegisterOrLogError(logger, registerer, committedSeqNr, "ocr3_1_committed_sequence_number") + + sentObservationsTotal := prometheus.NewCounter(prometheus.CounterOpts{ + Name: "ocr3_1_sent_observations_total", + Help: "The total number of observations by this oracle sent to the leader. Note that a " + + "sent observation might not arrive at the leader in time, or not be included in a " + + "proposal for other reasons. This metric is useful for checking an oracle's ability " + + "to make observations.", + }) + metricshelper.RegisterOrLogError(logger, registerer, sentObservationsTotal, "ocr3_1_sent_observations_total") + + includedObservationsTotal := prometheus.NewCounter(prometheus.CounterOpts{ + Name: "ocr3_1_included_observations_total", + Help: "The total number of (valid) observations by this oracle included in a proposal " + + "from the leader. Note that there is no guarantee that the proposal will actually get " + + "committed; for instance, because the leader crashes or maliciously equivocates to " + + "make this oracle believe that an observation was included. This metric is useful in " + + "comparison with ocr3_1_sent_observations_total to check whether an oracle is able to " + + "regularly make observations that are included in proposals.", + }) + metricshelper.RegisterOrLogError(logger, registerer, includedObservationsTotal, "ocr3_1_included_observations_total") + + ledCommittedRoundsTotal := prometheus.NewCounter(prometheus.CounterOpts{ + Name: "ocr3_1_led_committed_rounds_total", + Help: "The total number of rounds committed that were led by this oracle. This metric is " + + "useful for checking an oracle's ability to act as leader.", + }) + metricshelper.RegisterOrLogError(logger, registerer, ledCommittedRoundsTotal, "ocr3_1_led_committed_rounds_total") + + return &outcomeGenerationMetrics{ + registerer, + committedSeqNr, + sentObservationsTotal, + includedObservationsTotal, + ledCommittedRoundsTotal, + } +} + +func (om *outcomeGenerationMetrics) Close() { + om.registerer.Unregister(om.committedSeqNr) + om.registerer.Unregister(om.sentObservationsTotal) + om.registerer.Unregister(om.includedObservationsTotal) + om.registerer.Unregister(om.ledCommittedRoundsTotal) +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/network.go b/offchainreporting2plus/internal/ocr3_1/protocol/network.go new file mode 100644 index 00000000..03f30120 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/network.go @@ -0,0 +1,84 @@ +package protocol + +import ( + "log" + + "github.com/smartcontractkit/libocr/commontypes" +) + +// NetworkSender sends messages to other oracles +type NetworkSender[RI any] interface { + // SendTo(msg, to) sends msg to "to" + SendTo(msg Message[RI], to commontypes.OracleID) + // Broadcast(msg) sends msg to all oracles + Broadcast(msg Message[RI]) +} + +// NetworkEndpoint sends & receives messages to/from other oracles +type NetworkEndpoint[RI any] interface { + NetworkSender[RI] + // Receive returns channel which carries all messages sent to this oracle + Receive() <-chan MessageWithSender[RI] + + // Close must be called before receive. Close can be called multiple times. + // Close can be called even on an unstarted NetworkEndpoint. + Close() error +} + +// SimpleNetwork is a strawman (in-memory) implementation of the Network +// interface. Network channels are buffered and can queue up to 100 messages +// before blocking. +type SimpleNetwork[RI any] struct { + chs []chan MessageWithSender[RI] // i'th channel models oracle i's network +} + +// NewSimpleNetwork returns a SimpleNetwork for n oracles +func NewSimpleNetwork[RI any](n int) *SimpleNetwork[RI] { + s := SimpleNetwork[RI]{} + for i := 0; i < n; i++ { + s.chs = append(s.chs, make(chan MessageWithSender[RI], 100)) + } + return &s +} + +// Endpoint returns the interface for oracle id's networking facilities +func (net *SimpleNetwork[RI]) Endpoint(id commontypes.OracleID) (NetworkEndpoint[RI], error) { + return SimpleNetworkEndpoint[RI]{ + net, + id, + }, nil +} + +// SimpleNetworkEndpoint is a strawman (in-memory) implementation of +// NetworkEndpoint, used by SimpleNetwork +type SimpleNetworkEndpoint[RI any] struct { + net *SimpleNetwork[RI] // Reference back to network for all participants + id commontypes.OracleID // Index of oracle this endpoint pertains to +} + +var _ NetworkEndpoint[struct{}] = (*SimpleNetworkEndpoint[struct{}])(nil) + +// SendTo sends msg to oracle "to" +func (end SimpleNetworkEndpoint[RI]) SendTo(msg Message[RI], to commontypes.OracleID) { + log.Printf("[%v] sending to %v: %T\n", end.id, to, msg) + end.net.chs[to] <- MessageWithSender[RI]{msg, end.id} +} + +// Broadcast sends msg to all participating oracles +func (end SimpleNetworkEndpoint[RI]) Broadcast(msg Message[RI]) { + log.Printf("[%v] broadcasting: %T\n", end.id, msg) + for _, ch := range end.net.chs { + ch <- MessageWithSender[RI]{msg, end.id} + } +} + +// Receive returns a channel which carries all messages sent to the oracle +func (end SimpleNetworkEndpoint[RI]) Receive() <-chan MessageWithSender[RI] { + return end.net.chs[end.id] +} + +// Start satisfies the interface +func (SimpleNetworkEndpoint[RI]) Start() error { return nil } + +// Close satisfies the interface +func (SimpleNetworkEndpoint[RI]) Close() error { return nil } diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/oracle.go b/offchainreporting2plus/internal/ocr3_1/protocol/oracle.go new file mode 100644 index 00000000..9d22b1e0 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/oracle.go @@ -0,0 +1,462 @@ +package protocol + +import ( + "context" + "fmt" + "time" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + + "github.com/prometheus/client_golang/prometheus" + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3config" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/libocr/subprocesses" +) + +// RunOracle runs one oracle instance of the offchain reporting protocol and manages +// the lifecycle of all underlying goroutines. +// +// RunOracle runs forever until ctx is cancelled. It will only shut down +// after all its sub-goroutines have exited. +func RunOracle[RI any]( + ctx context.Context, + + blobEndpointWrapper *BlobEndpointWrapper, + config ocr3config.SharedConfig, + contractTransmitter ocr3types.ContractTransmitter[RI], + database Database, + id commontypes.OracleID, + kvStore KeyValueStore, + localConfig types.LocalConfig, + logger loghelper.LoggerWithContext, + metricsRegisterer prometheus.Registerer, + netEndpoint NetworkEndpoint[RI], + offchainKeyring types.OffchainKeyring, + onchainKeyring ocr3types.OnchainKeyring[RI], + reportingPlugin ocr3_1types.ReportingPlugin[RI], + telemetrySender TelemetrySender, +) { + o := oracleState[RI]{ + ctx: ctx, + + blobEndpointWrapper: blobEndpointWrapper, + config: config, + contractTransmitter: contractTransmitter, + database: database, + id: id, + kvStore: kvStore, + localConfig: localConfig, + logger: logger, + metricsRegisterer: metricsRegisterer, + netEndpoint: netEndpoint, + offchainKeyring: offchainKeyring, + onchainKeyring: onchainKeyring, + reportingPlugin: reportingPlugin, + telemetrySender: telemetrySender, + } + o.run() +} + +type oracleState[RI any] struct { + ctx context.Context + + blobEndpointWrapper *BlobEndpointWrapper + config ocr3config.SharedConfig + contractTransmitter ocr3types.ContractTransmitter[RI] + database Database + id commontypes.OracleID + kvStore KeyValueStore + localConfig types.LocalConfig + logger loghelper.LoggerWithContext + metricsRegisterer prometheus.Registerer + netEndpoint NetworkEndpoint[RI] + offchainKeyring types.OffchainKeyring + onchainKeyring ocr3types.OnchainKeyring[RI] + reportingPlugin ocr3_1types.ReportingPlugin[RI] + telemetrySender TelemetrySender + + chNetToPacemaker chan<- MessageToPacemakerWithSender[RI] + chNetToOutcomeGeneration chan<- MessageToOutcomeGenerationWithSender[RI] + chNetToReportAttestation chan<- MessageToReportAttestationWithSender[RI] + chNetToStatePersistence chan<- MessageToStatePersistenceWithSender[RI] + chNetToBlobExchange chan<- MessageToBlobExchangeWithSender[RI] + childCancel context.CancelFunc + childCtx context.Context + epoch uint64 + subprocesses subprocesses.Subprocesses +} + +// run ensures safe shutdown of the Oracle's "child routines", +// (Pacemaker, OutcomeGeneration, Attestation, State, Transmission, and BlobExchange) upon +// o.ctx.Done() +// +// Here is a graph of the various channels involved and what they +// transport. +// +// state +// message +// ┌───────────────────────────────────────────────────────┐ +// │ ┌─────────┐ │ +// ├─────────────────►│Pacemaker│ │ +// │ pacemaker └──────┬──┘ │ +// │ message ▲ │ │ +// │ │ │ │ +// │ progress│ │epoch │ +// │ /change│ │start │ +// │ epoch│ │notification │ +// │ request│ │ │ +// ▼ │ ▼ ▼ +// ┌──────┐ ┌──┴───────────────┐ ┌─────────────────┐ +// │Oracle│◄────────────►│Outcome Generation│◄────────────►│State Persistence│ +// └──────┘ out.gen. └──────┬───────────┘ └─────────────────┘ +// ▲ message │ ▲ +// │ │certified │ +// │ │outcome │ +// │ │ │ +// │ ▼ │ +// │ ┌────────────┐ │ +// └─────────────────►│ Attestation│◄──────────────────────┘ +// rep.att. └──────┬─────┘ +// message │ +// │attested +// │report +// │ +// ▼ +// ┌────────────┐ +// │Transmission│ +// └────────────┘ +// +// All channels are unbuffered. +// +// Once o.ctx.Done() is closed, the Oracle runloop will enter the corresponding +// select case and no longer forward network messages to Pacemaker, +// OutcomeGeneration, etc... It will then cancel o.childCtx, making all children +// exit. To prevent deadlocks, all channel sends and receives in Oracle, +// Pacemaker, OutcomeGeneration, etc... are (1) contained in select{} statements +// that also contain a case for context cancellation or (2) guaranteed to occur +// before o.childCtx is cancelled. +// +// Finally, all sub-goroutines spawned in the protocol are attached to o.subprocesses +// This enables us to wait for their completion before exiting. +func (o *oracleState[RI]) run() { + o.logger.Info("Oracle: running", commontypes.LogFields{ + "localConfig": fmt.Sprintf("%+v", o.localConfig), + "publicConfig": fmt.Sprintf("%+v", o.config.PublicConfig), + }) + + chNetToPacemaker := make(chan MessageToPacemakerWithSender[RI]) + o.chNetToPacemaker = chNetToPacemaker + + chNetToOutcomeGeneration := make(chan MessageToOutcomeGenerationWithSender[RI]) + o.chNetToOutcomeGeneration = chNetToOutcomeGeneration + + chPacemakerToOutcomeGeneration := make(chan EventToOutcomeGeneration[RI]) + + chOutcomeGenerationToPacemaker := make(chan EventToPacemaker[RI]) + + chNetToReportAttestation := make(chan MessageToReportAttestationWithSender[RI]) + o.chNetToReportAttestation = chNetToReportAttestation + + chOutcomeGenerationToReportAttestation := make(chan EventToReportAttestation[RI]) + + chReportAttestationToTransmission := make(chan EventToTransmission[RI]) + + chNetToStatePersistence := make(chan MessageToStatePersistenceWithSender[RI]) + o.chNetToStatePersistence = chNetToStatePersistence + + chReportAttestationToStatePersistence := make(chan EventToStatePersistence[RI]) + + chNetToBlobExchange := make(chan MessageToBlobExchangeWithSender[RI]) + o.chNetToBlobExchange = chNetToBlobExchange + + chOutcomeGenerationToBlobExchange := make(chan EventToBlobExchange[RI]) + + // communication between blob exchange and blob endpoint + chBlobBroadcastRequest := make(chan blobBroadcastRequest) + chBlobBroadcastResponse := make(chan blobBroadcastResponse) + + chBlobFetchRequest := make(chan blobFetchRequest) + chBlobFetchResponse := make(chan blobFetchResponse) + + // be careful if you want to change anything here. + // chNetTo* sends in message.go assume that their recipients are running. + o.childCtx, o.childCancel = context.WithCancel(context.Background()) + defer o.childCancel() + + defer o.kvStore.Close() + + paceState, cert, statePersistenceState, err := o.restoreFromDatabase() + if err != nil { + o.logger.Error("restoreFromDatabase returned an error, exiting oracle", commontypes.LogFields{ + "error": err, + }) + return + } + highestCommittedToKVdSeqNr, err := o.kvStore.HighestCommittedSeqNr() + if err != nil { + o.logger.Error("cannot read highest committed seqNr from key value store, exiting oracle", + commontypes.LogFields{ + "error": err, + }) + return + } + + blobEndpoint := BlobEndpoint{ + o.childCtx, + + chBlobBroadcastRequest, + chBlobBroadcastResponse, + + chBlobFetchRequest, + chBlobFetchResponse, + } + o.blobEndpointWrapper.setBlobEndpoint(&blobEndpoint) // pass through to plugin + + o.subprocesses.Go(func() { + RunPacemaker[RI]( + o.childCtx, + + chNetToPacemaker, + chPacemakerToOutcomeGeneration, + chOutcomeGenerationToPacemaker, + o.config, + o.database, + o.id, + o.localConfig, + o.logger, + o.metricsRegisterer, + o.netEndpoint, + o.offchainKeyring, + o.telemetrySender, + + paceState, + ) + }) + o.subprocesses.Go(func() { + RunOutcomeGeneration[RI]( + o.childCtx, + + chNetToOutcomeGeneration, + chPacemakerToOutcomeGeneration, + chOutcomeGenerationToPacemaker, + chOutcomeGenerationToReportAttestation, + &blobEndpoint, + o.config, + o.database, + o.id, + o.kvStore, + o.localConfig, + o.logger, + o.metricsRegisterer, + o.netEndpoint, + o.offchainKeyring, + o.reportingPlugin, + o.telemetrySender, + + cert, + ) + }) + + o.subprocesses.Go(func() { + RunReportAttestation[RI]( + o.childCtx, + + chNetToReportAttestation, + chOutcomeGenerationToReportAttestation, + chReportAttestationToStatePersistence, + chReportAttestationToTransmission, + o.config, + o.contractTransmitter, + o.logger, + o.netEndpoint, + o.onchainKeyring, + o.reportingPlugin, + ) + }) + + o.subprocesses.Go(func() { + RunStatePersistence[RI]( + o.childCtx, + + chNetToStatePersistence, + chReportAttestationToStatePersistence, + o.config, + o.database, + o.id, + o.kvStore, + o.logger, + o.netEndpoint, + o.reportingPlugin, + statePersistenceState, + highestCommittedToKVdSeqNr, + ) + }) + + o.subprocesses.Go(func() { + RunTransmission( + o.childCtx, + + chReportAttestationToTransmission, + o.config, + o.contractTransmitter, + o.id, + o.localConfig, + o.logger, + o.reportingPlugin, + ) + }) + + o.subprocesses.Go(func() { + RunBlobExchange[RI]( + o.childCtx, + + chNetToBlobExchange, + chOutcomeGenerationToBlobExchange, + + chBlobBroadcastRequest, + chBlobBroadcastResponse, + + chBlobFetchRequest, + chBlobFetchResponse, + + o.config, + o.kvStore, + o.id, + o.localConfig, + o.logger, + o.metricsRegisterer, + o.netEndpoint, + o.offchainKeyring, + o.telemetrySender, + ) + }) + + publicConfigMetrics := ocr3config.NewPublicConfigMetrics(o.metricsRegisterer, o.logger, o.config.PublicConfig) + defer publicConfigMetrics.Close() + + chNet := o.netEndpoint.Receive() + + chDone := o.ctx.Done() + for { + select { + case msg := <-chNet: + // This bounds check should never trigger since it's the netEndpoint's + // responsibility to only provide valid senders. We perform it for + // defense-in-depth. + if 0 <= int(msg.Sender) && int(msg.Sender) < o.config.N() { + msg.Msg.process(o, msg.Sender) + } else { + o.logger.Critical("msg.Sender out of bounds. This should *never* happen.", commontypes.LogFields{ + "sender": msg.Sender, + "n": o.config.N(), + }) + } + case <-chDone: + } + + // ensure prompt exit + select { + case <-chDone: + o.logger.Debug("Oracle: winding down", nil) + o.childCancel() + o.subprocesses.Wait() + o.logger.Debug("Oracle: exiting", nil) + return + default: + } + } +} + +func tryUntilSuccess[T any](ctx context.Context, logger commontypes.Logger, retryPeriod time.Duration, fnTimeout time.Duration, fnName string, fn func(context.Context) (T, error)) (T, error) { + for { + var result T + var err error + func() { + fnCtx, cancel := context.WithTimeout(ctx, fnTimeout) + defer cancel() + result, err = fn(fnCtx) + }() + if err == nil { + return result, nil + } + logger.Error(fmt.Sprintf("error during %s, retrying", fnName), commontypes.LogFields{ + "error": err, + "retryPeriod": retryPeriod.String(), + }) + + select { + case <-time.After(retryPeriod): + case <-ctx.Done(): + var zero T + return zero, ctx.Err() + } + } +} + +func (o *oracleState[RI]) restoreFromDatabase() (PacemakerState, CertifiedPrepareOrCommit, StatePersistenceState, error) { + const retryPeriod = 5 * time.Second + + paceState, err := tryUntilSuccess[PacemakerState]( + o.ctx, + o.logger, + retryPeriod, + o.localConfig.DatabaseTimeout, + "Database.ReadPacemakerState", + func(ctx context.Context) (PacemakerState, error) { + return o.database.ReadPacemakerState(ctx, o.config.ConfigDigest) + }, + ) + if err != nil { + return PacemakerState{}, nil, StatePersistenceState{}, err + } + + o.logger.Info("restoreFromDatabase: successfully restored pacemaker state", commontypes.LogFields{ + "state": paceState, + }) + + cert, err := tryUntilSuccess[CertifiedPrepareOrCommit]( + o.ctx, + o.logger, + retryPeriod, + o.localConfig.DatabaseTimeout, + "Database.ReadCert", + func(ctx context.Context) (CertifiedPrepareOrCommit, error) { + return o.database.ReadCert(ctx, o.config.ConfigDigest) + }, + ) + if err != nil { + return PacemakerState{}, nil, StatePersistenceState{}, err + } + + if cert != nil { + o.logger.Info("restoreFromDatabase: successfully restored cert", commontypes.LogFields{ + "certTimestamp": cert.Timestamp(), + }) + } else { + o.logger.Info("restoreFromDatabase: did not find cert, starting at genesis", nil) + cert = &CertifiedCommit{} + } + + statePersistenceState, err := tryUntilSuccess[StatePersistenceState]( + o.ctx, + o.logger, + retryPeriod, + o.localConfig.DatabaseTimeout, + "Database.ReadStatePersistenceState", + func(ctx context.Context) (StatePersistenceState, error) { + return o.database.ReadStatePersistenceState(ctx, o.config.ConfigDigest) + }, + ) + if err != nil { + return PacemakerState{}, nil, StatePersistenceState{}, err + } + + o.logger.Info("restoreFromDatabase: successfully restored state persistence state", commontypes.LogFields{ + "state": statePersistenceState, + }) + + return paceState, cert, statePersistenceState, nil +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/outcome_generation.go b/offchainreporting2plus/internal/ocr3_1/protocol/outcome_generation.go new file mode 100644 index 00000000..640ff543 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/outcome_generation.go @@ -0,0 +1,421 @@ +package protocol + +import ( + "context" + "time" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common" + + "github.com/prometheus/client_golang/prometheus" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/pool" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3config" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/libocr/subprocesses" +) + +// Identifies an instance of the outcome generation protocol +type OutcomeGenerationID struct { + ConfigDigest types.ConfigDigest + Epoch uint64 +} + +const futureMessageBufferSize = 10 // big enough for a couple of full rounds of outgen protocol +const poolSize = 3 + +func RunOutcomeGeneration[RI any]( + ctx context.Context, + + chNetToOutcomeGeneration <-chan MessageToOutcomeGenerationWithSender[RI], + chPacemakerToOutcomeGeneration <-chan EventToOutcomeGeneration[RI], + chOutcomeGenerationToPacemaker chan<- EventToPacemaker[RI], + chOutcomeGenerationToReportAttestation chan<- EventToReportAttestation[RI], + blobBroadcastFetcher ocr3_1types.BlobBroadcastFetcher, + config ocr3config.SharedConfig, + database Database, + id commontypes.OracleID, + kvStore KeyValueStore, + localConfig types.LocalConfig, + logger loghelper.LoggerWithContext, + metricsRegisterer prometheus.Registerer, + netSender NetworkSender[RI], + offchainKeyring types.OffchainKeyring, + reportingPlugin ocr3_1types.ReportingPlugin[RI], + telemetrySender TelemetrySender, + + restoredCert CertifiedPrepareOrCommit, +) { + + outgen := outcomeGenerationState[RI]{ + ctx: ctx, + subs: subprocesses.Subprocesses{}, + + chLocalEvent: make(chan EventToOutcomeGeneration[RI]), + chNetToOutcomeGeneration: chNetToOutcomeGeneration, + chPacemakerToOutcomeGeneration: chPacemakerToOutcomeGeneration, + chOutcomeGenerationToPacemaker: chOutcomeGenerationToPacemaker, + chOutcomeGenerationToReportAttestation: chOutcomeGenerationToReportAttestation, + blobBroadcastFetcher: blobBroadcastFetcher, + config: config, + database: database, + id: id, + kvStore: kvStore, + localConfig: localConfig, + logger: logger.MakeUpdated(commontypes.LogFields{"proto": "outgen"}), + metrics: newOutcomeGenerationMetrics(metricsRegisterer, logger), + netSender: netSender, + offchainKeyring: offchainKeyring, + reportingPlugin: reportingPlugin, + telemetrySender: telemetrySender, + } + outgen.run(restoredCert) +} + +type outcomeGenerationState[RI any] struct { + ctx context.Context + subs subprocesses.Subprocesses + + chLocalEvent chan EventToOutcomeGeneration[RI] + chNetToOutcomeGeneration <-chan MessageToOutcomeGenerationWithSender[RI] + chPacemakerToOutcomeGeneration <-chan EventToOutcomeGeneration[RI] + chOutcomeGenerationToPacemaker chan<- EventToPacemaker[RI] + chOutcomeGenerationToReportAttestation chan<- EventToReportAttestation[RI] + blobBroadcastFetcher ocr3_1types.BlobBroadcastFetcher + config ocr3config.SharedConfig + database Database + id commontypes.OracleID + kvStore KeyValueStore + localConfig types.LocalConfig + logger loghelper.LoggerWithContext + metrics *outcomeGenerationMetrics + netSender NetworkSender[RI] + offchainKeyring types.OffchainKeyring + reportingPlugin ocr3_1types.ReportingPlugin[RI] + telemetrySender TelemetrySender + + epochCtx context.Context + epochCtxCancel context.CancelFunc + bufferedMessages []*MessageBuffer[RI] + leaderState leaderState[RI] + followerState followerState[RI] + sharedState sharedState +} + +type leaderState[RI any] struct { + phase outgenLeaderPhase + + epochStartRequests map[commontypes.OracleID]*epochStartRequest[RI] + + readyToStartRound bool + tRound <-chan time.Time + + query types.Query + observationPool *pool.Pool[SignedObservation] + tGrace <-chan time.Time +} + +type epochStartRequest[RI any] struct { + message MessageEpochStartRequest[RI] + bad bool +} + +type followerState[RI any] struct { + phase outgenFollowerPhase + + tInitial <-chan time.Time + + roundStartPool *pool.Pool[MessageRoundStart[RI]] + + query *types.Query + + proposalPool *pool.Pool[MessageProposal[RI]] + + stateTransitionInfo stateTransitionInfo + + openKVTxn KeyValueStoreReadWriteTransaction + + // lock + + cert CertifiedPrepareOrCommit + + preparePool *pool.Pool[PrepareSignature] + commitPool *pool.Pool[CommitSignature] +} + +type stateTransitionInfo struct { + InputsDigest StateTransitionInputsDigest + Outputs StateTransitionOutputs + OutputDigest StateTransitionOutputDigest + ReportsPlusPrecursor ocr3_1types.ReportsPlusPrecursor + ReportsPlusPrecursorDigest ReportsPlusPrecursorDigest +} + +type sharedState struct { + e uint64 // Current epoch number + l commontypes.OracleID // Current leader number + + firstSeqNrOfEpoch uint64 + seqNr uint64 + observationQuorum *int + + committedSeqNr uint64 +} + +func (outgen *outcomeGenerationState[RI]) run(restoredCert CertifiedPrepareOrCommit) { + var restoredCommitedSeqNr uint64 + if restoredCert != nil { + if commitQC, ok := restoredCert.(*CertifiedCommit); ok { + restoredCommitedSeqNr = commitQC.SeqNr() + } else if prepareQc, ok := restoredCert.(*CertifiedPrepare); ok { + if prepareQc.SeqNr() > 1 { + restoredCommitedSeqNr = prepareQc.SeqNr() - 1 + } + } + } + + outgen.logger.Info("OutcomeGeneration: running", commontypes.LogFields{ + "restoredCommittedSeqNr": restoredCommitedSeqNr, + }) + + // Initialization + outgen.epochCtx, outgen.epochCtxCancel = context.WithCancel(outgen.ctx) + + for i := 0; i < outgen.config.N(); i++ { + outgen.bufferedMessages = append(outgen.bufferedMessages, NewMessageBuffer[RI](futureMessageBufferSize)) + } + + outgen.leaderState = leaderState[RI]{ + outgenLeaderPhaseUnknown, + map[commontypes.OracleID]*epochStartRequest[RI]{}, + false, + nil, + nil, + nil, + nil, + } + + outgen.followerState = followerState[RI]{ + outgenFollowerPhaseUnknown, + nil, + nil, + nil, + nil, + stateTransitionInfo{}, + nil, + restoredCert, + nil, + nil, + } + + outgen.sharedState = sharedState{ + 0, + 0, + + 0, + restoredCommitedSeqNr, + nil, + restoredCommitedSeqNr, + } + + // Event Loop + chDone := outgen.ctx.Done() + for { + select { + case ev := <-outgen.chLocalEvent: + ev.processOutcomeGeneration(outgen) + case msg := <-outgen.chNetToOutcomeGeneration: + outgen.messageToOutcomeGeneration(msg) + case ev := <-outgen.chPacemakerToOutcomeGeneration: + ev.processOutcomeGeneration(outgen) + case <-outgen.followerState.tInitial: + outgen.eventTInitialTimeout() + case <-outgen.leaderState.tGrace: + outgen.eventTGraceTimeout() + case <-outgen.leaderState.tRound: + outgen.eventTRoundTimeout() + case <-chDone: + } + + // ensure prompt exit + select { + case <-chDone: + outgen.logger.Info("OutcomeGeneration: winding down", commontypes.LogFields{ + "e": outgen.sharedState.e, + "l": outgen.sharedState.l, + }) + outgen.subs.Wait() + outgen.metrics.Close() + outgen.logger.Info("OutcomeGeneration: exiting", commontypes.LogFields{ + "e": outgen.sharedState.e, + "l": outgen.sharedState.l, + }) + return + default: + } + } +} + +func (outgen *outcomeGenerationState[RI]) messageToOutcomeGeneration(msg MessageToOutcomeGenerationWithSender[RI]) { + msgEpoch := msg.msg.epoch() + if msgEpoch < outgen.sharedState.e { + // drop + outgen.logger.Debug("dropping message for past epoch", commontypes.LogFields{ + "epoch": outgen.sharedState.e, + "msgEpoch": msgEpoch, + "sender": msg.sender, + }) + } else if msgEpoch == outgen.sharedState.e { + msg.msg.processOutcomeGeneration(outgen, msg.sender) + } else { + outgen.bufferedMessages[msg.sender].Push(msg.msg) + outgen.logger.Trace("buffering message for future epoch", commontypes.LogFields{ + "msgEpoch": msgEpoch, + "sender": msg.sender, + }) + } +} + +func (outgen *outcomeGenerationState[RI]) unbufferMessages() { + outgen.logger.Trace("getting messages for new epoch", nil) + for i, buffer := range outgen.bufferedMessages { + sender := commontypes.OracleID(i) + for buffer.Length() > 0 { + msg := buffer.Peek() + msgEpoch := msg.epoch() + if msgEpoch < outgen.sharedState.e { + buffer.Pop() + outgen.logger.Debug("unbuffered and dropped message", commontypes.LogFields{ + "msgEpoch": msgEpoch, + "sender": sender, + }) + } else if msgEpoch == outgen.sharedState.e { + buffer.Pop() + outgen.logger.Trace("unbuffered message for new epoch", commontypes.LogFields{ + "msgEpoch": msgEpoch, + "sender": sender, + }) + msg.processOutcomeGeneration(outgen, sender) + } else { // msgEpoch > e + // this and all subsequent messages are for future epochs + // leave them in the buffer + break + } + } + } + outgen.logger.Trace("done unbuffering messages for new epoch", nil) +} + +func (outgen *outcomeGenerationState[RI]) eventNewEpochStart(ev EventNewEpochStart[RI]) { + // Initialization + outgen.logger.Info("starting new epoch", commontypes.LogFields{ + "epoch": ev.Epoch, + }) + + outgen.epochCtxCancel() + outgen.epochCtx, outgen.epochCtxCancel = context.WithCancel(outgen.ctx) + + outgen.sharedState.e = ev.Epoch + outgen.sharedState.l = Leader(outgen.sharedState.e, outgen.config.N(), outgen.config.LeaderSelectionKey()) + + outgen.logger = outgen.logger.MakeUpdated(commontypes.LogFields{ + "e": outgen.sharedState.e, + "l": outgen.sharedState.l, + }) + + outgen.sharedState.firstSeqNrOfEpoch = 0 + outgen.sharedState.seqNr = 0 + + outgen.followerState.phase = outgenFollowerPhaseNewEpoch + outgen.followerState.tInitial = time.After(outgen.config.DeltaInitial) + outgen.followerState.stateTransitionInfo = stateTransitionInfo{} + + outgen.followerState.roundStartPool = pool.NewPool[MessageRoundStart[RI]](poolSize) + outgen.followerState.proposalPool = pool.NewPool[MessageProposal[RI]](poolSize) + outgen.followerState.preparePool = pool.NewPool[PrepareSignature](poolSize) + outgen.followerState.commitPool = pool.NewPool[CommitSignature](poolSize) + + outgen.leaderState.phase = outgenLeaderPhaseNewEpoch + outgen.leaderState.epochStartRequests = map[commontypes.OracleID]*epochStartRequest[RI]{} + outgen.leaderState.readyToStartRound = false + outgen.leaderState.observationPool = pool.NewPool[SignedObservation](1) // only one observation per sender & round, and we do not need to worry about observations from the future + outgen.leaderState.tGrace = nil + + outgen.refreshCommittedSeqNrAndCert() + + var highestCertified CertifiedPrepareOrCommit + var highestCertifiedTimestamp HighestCertifiedTimestamp + highestCertified = outgen.followerState.cert + highestCertifiedTimestamp = outgen.followerState.cert.Timestamp() + + signedHighestCertifiedTimestamp, err := MakeSignedHighestCertifiedTimestamp( + outgen.ID(), + highestCertifiedTimestamp, + outgen.offchainKeyring.OffchainSign, + ) + if err != nil { + outgen.logger.Error("error signing timestamp", commontypes.LogFields{ + "error": err, + }) + return + } + + outgen.logger.Info("sending MessageEpochStartRequest to leader", commontypes.LogFields{ + "highestCertifiedTimestamp": highestCertifiedTimestamp, + }) + outgen.netSender.SendTo(MessageEpochStartRequest[RI]{ + outgen.sharedState.e, + highestCertified, + signedHighestCertifiedTimestamp, + }, outgen.sharedState.l) + + if outgen.id == outgen.sharedState.l { + outgen.leaderState.tRound = time.After(outgen.config.DeltaRound) + } + + outgen.unbufferMessages() +} + +func (outgen *outcomeGenerationState[RI]) ID() OutcomeGenerationID { + return OutcomeGenerationID{outgen.config.ConfigDigest, outgen.sharedState.e} +} + +func (outgen *outcomeGenerationState[RI]) RoundCtx(seqNr uint64) RoundContext { + if seqNr != outgen.sharedState.committedSeqNr+1 { + outgen.logger.Critical("assumption violation, seqNr isn't successor to committedSeqNr", commontypes.LogFields{ + "seqNr": seqNr, + "committedSeqNr": outgen.sharedState.committedSeqNr, + }) + panic("") + } + return RoundContext{ + seqNr, + outgen.sharedState.e, + seqNr - outgen.sharedState.firstSeqNrOfEpoch + 1, + } +} + +func callPluginFromOutcomeGenerationBackground[T any]( + ctx context.Context, + logger loghelper.LoggerWithContext, + name string, + recommendedMaxDuration time.Duration, + roundCtx RoundContext, + f func(context.Context, RoundContext) (T, error), +) (T, bool) { + return common.CallPluginFromBackground[T]( + ctx, + logger, + commontypes.LogFields{ + "seqNr": roundCtx.SeqNr, + "round": roundCtx.Round, // nolint: staticcheck + }, + name, + recommendedMaxDuration, + func(ctx context.Context) (T, error) { + return f(ctx, roundCtx) + }, + ) +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/outcome_generation_follower.go b/offchainreporting2plus/internal/ocr3_1/protocol/outcome_generation_follower.go new file mode 100644 index 00000000..64b27d7b --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/outcome_generation_follower.go @@ -0,0 +1,1401 @@ +package protocol + +import ( + "bytes" + "context" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/pool" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type outgenFollowerPhase string + +const ( + outgenFollowerPhaseUnknown outgenFollowerPhase = "unknown" + outgenFollowerPhaseNewEpoch outgenFollowerPhase = "newEpoch" + outgenFollowerPhaseNewRound outgenFollowerPhase = "newRound" + outgenFollowerPhaseBackgroundObservation outgenFollowerPhase = "backgroundObservation" + outgenFollowerPhaseSentObservation outgenFollowerPhase = "sentObservation" + outgenFollowerPhaseBackgroundProposalStateTransition outgenFollowerPhase = "backgroundProposalStateTransition" + outgenFollowerPhaseSentPrepare outgenFollowerPhase = "sentPrepare" + outgenFollowerPhaseSentCommit outgenFollowerPhase = "sentCommit" +) + +func (outgen *outcomeGenerationState[RI]) eventTInitialTimeout() { + outgen.logger.Debug("TInitial fired", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "deltaInitial": outgen.config.DeltaInitial.String(), + }) + select { + case outgen.chOutcomeGenerationToPacemaker <- EventNewEpochRequest[RI]{}: + case <-outgen.ctx.Done(): + return + } +} + +func (outgen *outcomeGenerationState[RI]) messageEpochStart(msg MessageEpochStart[RI], sender commontypes.OracleID) { + outgen.logger.Debug("received MessageEpochStart", commontypes.LogFields{ + "sender": sender, + "msgEpoch": msg.Epoch, + "msgHighestCertifiedTimestamp": msg.EpochStartProof.HighestCertified.Timestamp(), + }) + + if msg.Epoch != outgen.sharedState.e { + outgen.logger.Debug("dropping MessageEpochStart for wrong epoch", commontypes.LogFields{ + "sender": sender, + "msgEpoch": msg.Epoch, + }) + return + } + + if sender != outgen.sharedState.l { + outgen.logger.Warn("dropping MessageEpochStart from non-leader", commontypes.LogFields{ + "sender": sender, + }) + return + } + + if outgen.followerState.phase != outgenFollowerPhaseNewEpoch { + outgen.logger.Warn("dropping MessageEpochStart for wrong phase", commontypes.LogFields{ + "phase": outgen.followerState.phase, + }) + return + } + + { + err := msg.EpochStartProof.Verify( + outgen.ID(), + outgen.config.OracleIdentities, + outgen.config.ByzQuorumSize(), + ) + if err != nil { + outgen.logger.Warn("dropping MessageEpochStart containing invalid StartRoundQuorumCertificate", commontypes.LogFields{ + "error": err, + }) + return + } + } + + outgen.followerState.tInitial = nil + + outgen.refreshCommittedSeqNrAndCert() + if !outgen.ensureHighestCertifiedIsCompatible(msg.EpochStartProof.HighestCertified, "MessageEpochStart") { + return + } + + if msg.EpochStartProof.HighestCertified.IsGenesis() { + outgen.sharedState.firstSeqNrOfEpoch = outgen.sharedState.committedSeqNr + 1 + outgen.startSubsequentFollowerRound() + } else if commitQC, ok := msg.EpochStartProof.HighestCertified.(*CertifiedCommit); ok { + outgen.commit(*commitQC) + + outgen.sharedState.firstSeqNrOfEpoch = outgen.sharedState.committedSeqNr + 1 + outgen.startSubsequentFollowerRound() + } else { + // We're dealing with a re-proposal from a failed epoch + + prepareQc, ok := msg.EpochStartProof.HighestCertified.(*CertifiedPrepare) + if !ok { + outgen.logger.Critical("cast to CertifiedPrepare failed while processing MessageEpochStart", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + return + } + + prepareQcSeqNr := prepareQc.SeqNr() + + outgen.sharedState.firstSeqNrOfEpoch = prepareQcSeqNr + 1 + outgen.sharedState.seqNr = prepareQcSeqNr + outgen.sharedState.observationQuorum = nil + + outgen.followerState.query = nil + outgen.ensureOpenKVTransactionDiscarded() + + if prepareQcSeqNr == outgen.sharedState.committedSeqNr { + + stateTransitionInputsDigest := prepareQc.StateTransitionInputsDigest + + stateTransitionOutputDigest := MakeStateTransitionOutputDigest( + outgen.ID(), + prepareQcSeqNr, + prepareQc.StateTransitionOutputs.WriteSet, + ) + + reportPlusPrecursorDigest := MakeReportsPlusPrecursorDigest( + outgen.ID(), + prepareQcSeqNr, + prepareQc.ReportsPlusPrecursor, + ) + + prepareSignature, err := MakePrepareSignature( + outgen.ID(), + prepareQcSeqNr, + stateTransitionInputsDigest, + stateTransitionOutputDigest, + reportPlusPrecursorDigest, + outgen.offchainKeyring.OffchainSign, + ) + if err != nil { + outgen.logger.Critical("failed to sign Prepare", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "error": err, + }) + return + } + + outgen.followerState.phase = outgenFollowerPhaseSentPrepare + outgen.followerState.stateTransitionInfo = stateTransitionInfo{ + stateTransitionInputsDigest, + prepareQc.StateTransitionOutputs, + stateTransitionOutputDigest, + prepareQc.ReportsPlusPrecursor, + reportPlusPrecursorDigest, + } + outgen.logger.Debug("broadcasting MessagePrepare (reproposal where prepareQcSeqNr == committedSeqNr)", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + outgen.netSender.Broadcast(MessagePrepare[RI]{ + outgen.sharedState.e, + prepareQcSeqNr, + prepareSignature, + }) + return + } + + outgen.followerState.phase = outgenFollowerPhaseBackgroundProposalStateTransition + outgen.followerState.stateTransitionInfo = stateTransitionInfo{} + outgen.ensureOpenKVTransactionDiscarded() + + outgen.logger.Debug("re-executing StateTransition from MessagePrepare (reproposal where prepareQcSeqNr == committedSeqNr+1)", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + + kvReadWriteTxn, err := outgen.kvStore.NewReadWriteTransaction(prepareQcSeqNr) + if err != nil { + outgen.logger.Warn("could not create kv read/write transaction", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "err": err, + }) + return + } + + { + ctx := outgen.ctx + logger := outgen.logger + ogid := outgen.ID() + roundCtx := outgen.RoundCtx(prepareQcSeqNr) + + inputsDigest := prepareQc.StateTransitionInputsDigest + writeSet := prepareQc.StateTransitionOutputs.WriteSet + reportsPlusPrecursor := prepareQc.ReportsPlusPrecursor + + outgen.subs.Go(func() { + outgen.backgroundProposalStateTransition( + ctx, + logger, + ogid, + roundCtx, + + inputsDigest, + writeSet, + reportsPlusPrecursor, + + types.AttributedQuery{}, + nil, + + kvReadWriteTxn, + ) + }) + } + } +} + +func (outgen *outcomeGenerationState[RI]) startSubsequentFollowerRound() { + outgen.sharedState.seqNr = outgen.sharedState.committedSeqNr + 1 + outgen.sharedState.observationQuorum = nil + + outgen.followerState.phase = outgenFollowerPhaseNewRound + outgen.followerState.query = nil + outgen.followerState.stateTransitionInfo = stateTransitionInfo{} + outgen.ensureOpenKVTransactionDiscarded() + outgen.logger.Debug("starting new follower round", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + + outgen.tryProcessRoundStartPool() +} + +func (outgen *outcomeGenerationState[RI]) messageRoundStart(msg MessageRoundStart[RI], sender commontypes.OracleID) { + outgen.logger.Debug("received MessageRoundStart", commontypes.LogFields{ + "sender": sender, + "msgSeqNr": msg.SeqNr, + "msgEpoch": msg.Epoch, + }) + + if msg.Epoch != outgen.sharedState.e { + outgen.logger.Debug("dropping MessageRoundStart for wrong epoch", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "msgEpoch": msg.Epoch, + "msgSeqNr": msg.SeqNr, + }) + return + } + + if sender != outgen.sharedState.l { + outgen.logger.Warn("dropping MessageRoundStart from non-leader", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "msgSeqNr": msg.SeqNr, + }) + return + } + + if putResult := outgen.followerState.roundStartPool.Put(msg.SeqNr, sender, msg); putResult != pool.PutResultOK { + outgen.logger.Debug("dropping MessageRoundStart", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "msgSeqNr": msg.SeqNr, + "reason": putResult, + }) + return + } + + outgen.logger.Debug("pooled MessageRoundStart", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + + outgen.tryProcessRoundStartPool() +} + +func (outgen *outcomeGenerationState[RI]) tryProcessRoundStartPool() { + if outgen.followerState.phase != outgenFollowerPhaseNewRound { + outgen.logger.Debug("cannot process RoundStartPool, wrong phase", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "phase": outgen.followerState.phase, + }) + return + } + + poolEntries := outgen.followerState.roundStartPool.Entries(outgen.sharedState.seqNr) + + if poolEntries == nil || poolEntries[outgen.sharedState.l] == nil { + + outgen.logger.Debug("cannot process RoundStartPool, it's empty", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + return + } + + if outgen.followerState.query != nil { + outgen.logger.Warn("cannot process RoundStartPool, query already set", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + return + } + + msg := poolEntries[outgen.sharedState.l].Item + roundCtx := outgen.RoundCtx(outgen.sharedState.seqNr) + + outgen.followerState.phase = outgenFollowerPhaseBackgroundObservation + outgen.followerState.query = &msg.Query + + outgen.telemetrySender.RoundStarted( + outgen.config.ConfigDigest, + roundCtx.Epoch, + roundCtx.SeqNr, + roundCtx.Round, + outgen.sharedState.l, + ) + + { + ctx := outgen.epochCtx + logger := outgen.logger + aq := types.AttributedQuery{ + *outgen.followerState.query, + outgen.sharedState.l, + } + kvReadTxn, err := outgen.kvStore.NewReadTransaction(roundCtx.SeqNr) + if err != nil { + outgen.logger.Warn("failed to create new transaction, aborting tryProcessRoundStartPool", commontypes.LogFields{ + "seqNr": roundCtx.SeqNr, + "error": err, + }) + return + } + outgen.subs.Go(func() { + outgen.backgroundObservation(ctx, logger, roundCtx, aq, kvReadTxn) + }) + } +} + +func (outgen *outcomeGenerationState[RI]) backgroundObservation( + ctx context.Context, + logger loghelper.LoggerWithContext, + roundCtx RoundContext, + aq types.AttributedQuery, + kvReadTxn KeyValueStoreReadTransaction, +) { + observation, ok := callPluginFromOutcomeGenerationBackground[types.Observation]( + ctx, + logger, + "Observation", + outgen.config.MaxDurationObservation, + roundCtx, + func(ctx context.Context, roundCtx RoundContext) (types.Observation, error) { + return outgen.reportingPlugin.Observation(ctx, roundCtx.SeqNr, aq, kvReadTxn, outgen.blobBroadcastFetcher) + }, + ) + kvReadTxn.Discard() + if !ok { + return + } + + select { + case outgen.chLocalEvent <- EventComputedObservation[RI]{ + roundCtx.Epoch, + roundCtx.SeqNr, + aq.Query, + observation, + }: + case <-ctx.Done(): + } +} + +func (outgen *outcomeGenerationState[RI]) eventComputedObservation(ev EventComputedObservation[RI]) { + if ev.Epoch != outgen.sharedState.e || ev.SeqNr != outgen.sharedState.seqNr { + outgen.logger.Debug("discarding EventComputedObservation from old round", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "evEpoch": ev.Epoch, + "evSeqNr": ev.SeqNr, + }) + return + } + + if outgen.followerState.phase != outgenFollowerPhaseBackgroundObservation { + outgen.logger.Debug("discarding EventComputedObservation, wrong phase", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "phase": outgen.followerState.phase, + }) + return + } + + so, err := MakeSignedObservation(outgen.ID(), outgen.sharedState.seqNr, ev.Query, ev.Observation, outgen.offchainKeyring.OffchainSign) + if err != nil { + outgen.logger.Error("MakeSignedObservation returned error", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "error": err, + }) + return + } + + if err := so.Verify(outgen.ID(), outgen.sharedState.seqNr, ev.Query, outgen.offchainKeyring.OffchainPublicKey()); err != nil { + outgen.logger.Error("MakeSignedObservation produced invalid signature", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "error": err, + }) + return + } + + outgen.followerState.phase = outgenFollowerPhaseSentObservation + outgen.metrics.sentObservationsTotal.Inc() + outgen.logger.Debug("sent MessageObservation to leader", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + outgen.netSender.SendTo(MessageObservation[RI]{ + outgen.sharedState.e, + outgen.sharedState.seqNr, + so, + }, outgen.sharedState.l) + + outgen.tryProcessProposalPool() +} + +func (outgen *outcomeGenerationState[RI]) messageProposal(msg MessageProposal[RI], sender commontypes.OracleID) { + outgen.logger.Debug("received MessageProposal", commontypes.LogFields{ + "sender": sender, + "msgSeqNr": msg.SeqNr, + "msgEpoch": msg.Epoch, + }) + + if msg.Epoch != outgen.sharedState.e { + outgen.logger.Debug("dropping MessageProposal for wrong epoch", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "msgEpoch": msg.Epoch, + "msgSeqNr": msg.SeqNr, + }) + return + } + + if sender != outgen.sharedState.l { + outgen.logger.Warn("dropping MessageProposal from non-leader", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "msgSeqNr": msg.SeqNr, + }) + return + } + + if putResult := outgen.followerState.proposalPool.Put(msg.SeqNr, sender, msg); putResult != pool.PutResultOK { + outgen.logger.Debug("dropping MessageProposal", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "messageSeqNr": msg.SeqNr, + "reason": putResult, + }) + return + } + + outgen.logger.Debug("pooled MessageProposal", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + + outgen.tryProcessProposalPool() +} + +func (outgen *outcomeGenerationState[RI]) tryProcessProposalPool() { + if outgen.followerState.phase != outgenFollowerPhaseSentObservation { + outgen.logger.Debug("cannot process ProposalPool, wrong phase", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "phase": outgen.followerState.phase, + }) + return + } + + poolEntries := outgen.followerState.proposalPool.Entries(outgen.sharedState.seqNr) + + if poolEntries == nil || poolEntries[outgen.sharedState.l] == nil { + + return + } + + msg := poolEntries[outgen.sharedState.l].Item + + if msg.SeqNr <= outgen.sharedState.committedSeqNr { + outgen.logger.Critical("MessageProposal contains invalid SeqNr", commontypes.LogFields{ + "msgSeqNr": msg.SeqNr, + "committedSeqNr": outgen.sharedState.committedSeqNr, + }) + return + } + + outgen.followerState.phase = outgenFollowerPhaseBackgroundProposalStateTransition + + kvReadWriteTxn, err := outgen.kvStore.NewReadWriteTransaction(outgen.sharedState.seqNr) + if err != nil { + outgen.logger.Warn("could not create kv read/write transaction", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "err": err, + }) + return + } + + { + ctx := outgen.epochCtx + logger := outgen.logger + roundCtx := outgen.RoundCtx(outgen.sharedState.seqNr) + ogid := outgen.ID() + aq := types.AttributedQuery{ + *outgen.followerState.query, + outgen.sharedState.l, + } + + asos := msg.AttributedSignedObservations + + outgen.subs.Go(func() { + outgen.backgroundProposalStateTransition( + ctx, + logger, + ogid, + roundCtx, + + StateTransitionInputsDigest{}, + nil, + nil, + + aq, + asos, + kvReadWriteTxn, + ) + }) + } +} + +func (outgen *outcomeGenerationState[RI]) backgroundProposalStateTransition( + ctx context.Context, + logger loghelper.LoggerWithContext, + ogid OutcomeGenerationID, + roundCtx RoundContext, + + stateTransitionInputsDigest StateTransitionInputsDigest, + writeSet []KeyValuePair, + reportsPlusPrecursor ocr3_1types.ReportsPlusPrecursor, + + aq types.AttributedQuery, + asos []AttributedSignedObservation, + kvReadWriteTxn KeyValueStoreReadWriteTransaction, +) { + shouldDiscardKVTxn := true + defer func() { + if shouldDiscardKVTxn { + kvReadWriteTxn.Discard() + } + }() + + if asos != nil { + + aos, ok := outgen.checkAttributedSignedObservations(ctx, logger, ogid, roundCtx, aq, asos, kvReadWriteTxn) + if !ok { + return + } + reportsPlusPrecursor, ok = callPluginFromOutcomeGenerationBackground[ocr3_1types.ReportsPlusPrecursor]( + ctx, + logger, + "StateTransition", + 0, // StateTransition is a pure function and should finish "instantly" + roundCtx, + func(ctx context.Context, roundCtx RoundContext) (ocr3_1types.ReportsPlusPrecursor, error) { + return outgen.reportingPlugin.StateTransition(ctx, roundCtx.SeqNr, aq, aos, kvReadWriteTxn, outgen.blobBroadcastFetcher) + }, + ) + if !ok { + return + } + + stateTransitionInputsDigest = MakeStateTransitionInputsDigest( + ogid, + roundCtx.SeqNr, + aq.Query, + aos, + ) + + var err error + writeSet, err = kvReadWriteTxn.GetWriteSet() + if err != nil { + outgen.logger.Warn("failed to get write set from kv read/write transaction", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "error": err, + }) + return + } + } else { + + // apply write set instead of executing StateTransition + for _, m := range writeSet { + var err error + if m.Deleted { + err = kvReadWriteTxn.Delete(m.Key) + } else { + err = kvReadWriteTxn.Write(m.Key, m.Value) + } + if err != nil { + logger.Error("failed to write write-set modification", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "error": err, + }) + return + } + } + } + + stateTransitionOutputDigest := MakeStateTransitionOutputDigest(ogid, roundCtx.SeqNr, writeSet) + reportsPlusPrecursorDigest := MakeReportsPlusPrecursorDigest(ogid, roundCtx.SeqNr, reportsPlusPrecursor) + + stateTransitionOutputs := StateTransitionOutputs{writeSet} + + select { + case outgen.chLocalEvent <- EventComputedProposalStateTransition[RI]{ + roundCtx.Epoch, + roundCtx.SeqNr, + kvReadWriteTxn, + stateTransitionInfo{ + stateTransitionInputsDigest, + stateTransitionOutputs, + stateTransitionOutputDigest, + reportsPlusPrecursor, + reportsPlusPrecursorDigest, + }, + }: + shouldDiscardKVTxn = false + case <-ctx.Done(): + } +} + +func (outgen *outcomeGenerationState[RI]) eventComputedProposalStateTransition(ev EventComputedProposalStateTransition[RI]) { + if ev.Epoch != outgen.sharedState.e || ev.SeqNr != outgen.sharedState.seqNr { + outgen.logger.Debug("discarding EventComputedProposalStateTransition from old round", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "evEpoch": ev.Epoch, + "evSeqNr": ev.SeqNr, + }) + return + } + + if outgen.followerState.phase != outgenFollowerPhaseBackgroundProposalStateTransition { + outgen.logger.Debug("discarding EventComputedProposalStateTransition, wrong phase", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "phase": outgen.followerState.phase, + }) + return + } + + outgen.followerState.openKVTxn = ev.KeyValueStoreReadWriteTransaction + + prepareSignature, err := MakePrepareSignature( + outgen.ID(), + outgen.sharedState.seqNr, + ev.stateTransitionInfo.InputsDigest, + ev.stateTransitionInfo.OutputDigest, + ev.stateTransitionInfo.ReportsPlusPrecursorDigest, + outgen.offchainKeyring.OffchainSign, + ) + if err != nil { + outgen.logger.Critical("failed to sign Prepare", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "error": err, + }) + return + } + + outgen.followerState.phase = outgenFollowerPhaseSentPrepare + outgen.followerState.stateTransitionInfo = ev.stateTransitionInfo + + outgen.logger.Debug("broadcasting MessagePrepare", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + outgen.netSender.Broadcast(MessagePrepare[RI]{ + outgen.sharedState.e, + outgen.sharedState.seqNr, + prepareSignature, + }) +} + +func (outgen *outcomeGenerationState[RI]) messagePrepare(msg MessagePrepare[RI], sender commontypes.OracleID) { + outgen.logger.Debug("received MessagePrepare", commontypes.LogFields{ + "sender": sender, + "msgSeqNr": msg.SeqNr, + "msgEpoch": msg.Epoch, + }) + + if msg.Epoch != outgen.sharedState.e { + outgen.logger.Debug("dropping MessagePrepare for wrong epoch", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "msgEpoch": msg.Epoch, + "msgSeqNr": msg.SeqNr, + }) + return + } + + if putResult := outgen.followerState.preparePool.Put(msg.SeqNr, sender, msg.Signature); putResult != pool.PutResultOK { + outgen.logger.Debug("dropping MessagePrepare", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "msgSeqNr": msg.SeqNr, + "reason": putResult, + }) + return + } + + outgen.logger.Debug("pooled MessagePrepare", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "msgSeqNr": msg.SeqNr, + }) + + outgen.tryProcessPreparePool() +} + +func (outgen *outcomeGenerationState[RI]) tryProcessPreparePool() { + if outgen.followerState.phase != outgenFollowerPhaseSentPrepare { + outgen.logger.Debug("cannot process PreparePool, wrong phase", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "phase": outgen.followerState.phase, + }) + return + } + + poolEntries := outgen.followerState.preparePool.Entries(outgen.sharedState.seqNr) + if len(poolEntries) < outgen.config.ByzQuorumSize() { + + return + } + + for sender, preparePoolEntry := range poolEntries { + if preparePoolEntry.Verified != nil { + continue + } + err := preparePoolEntry.Item.Verify( + outgen.ID(), + outgen.sharedState.seqNr, + outgen.followerState.stateTransitionInfo.InputsDigest, + outgen.followerState.stateTransitionInfo.OutputDigest, + outgen.followerState.stateTransitionInfo.ReportsPlusPrecursorDigest, + outgen.config.OracleIdentities[sender].OffchainPublicKey, + ) + ok := err == nil + outgen.followerState.preparePool.StoreVerified(outgen.sharedState.seqNr, sender, ok) + if !ok { + outgen.logger.Warn("dropping invalid MessagePrepare", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "error": err, + }) + } + } + + var prepareQuorumCertificate []AttributedPrepareSignature + for sender, preparePoolEntry := range poolEntries { + if preparePoolEntry.Verified != nil && *preparePoolEntry.Verified { + prepareQuorumCertificate = append(prepareQuorumCertificate, AttributedPrepareSignature{ + preparePoolEntry.Item, + sender, + }) + if len(prepareQuorumCertificate) == outgen.config.ByzQuorumSize() { + break + } + } + } + + if len(prepareQuorumCertificate) < outgen.config.ByzQuorumSize() { + return + } + + commitSignature, err := MakeCommitSignature( + outgen.ID(), + outgen.sharedState.seqNr, + outgen.followerState.stateTransitionInfo.InputsDigest, + outgen.followerState.stateTransitionInfo.OutputDigest, + outgen.followerState.stateTransitionInfo.ReportsPlusPrecursorDigest, + outgen.offchainKeyring.OffchainSign, + ) + if err != nil { + outgen.logger.Critical("failed to sign Commit", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "error": err, + }) + return + } + + if !outgen.persistAndUpdateCertIfGreater(&CertifiedPrepare{ + outgen.sharedState.e, + outgen.sharedState.seqNr, + outgen.followerState.stateTransitionInfo.InputsDigest, + outgen.followerState.stateTransitionInfo.Outputs, + outgen.followerState.stateTransitionInfo.ReportsPlusPrecursor, + prepareQuorumCertificate, + }) { + return + } + + outgen.followerState.phase = outgenFollowerPhaseSentCommit + + outgen.logger.Debug("broadcasting MessageCommit", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + outgen.netSender.Broadcast(MessageCommit[RI]{ + outgen.sharedState.e, + outgen.sharedState.seqNr, + commitSignature, + }) +} + +func (outgen *outcomeGenerationState[RI]) messageCommit(msg MessageCommit[RI], sender commontypes.OracleID) { + outgen.logger.Debug("received MessageCommit", commontypes.LogFields{ + "sender": sender, + "msgSeqNr": msg.SeqNr, + "msgEpoch": msg.Epoch, + }) + + if msg.Epoch != outgen.sharedState.e { + outgen.logger.Debug("dropping MessageCommit for wrong epoch", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "msgEpoch": msg.Epoch, + "msgSeqNr": msg.SeqNr, + }) + return + } + + if putResult := outgen.followerState.commitPool.Put(msg.SeqNr, sender, msg.Signature); putResult != pool.PutResultOK { + outgen.logger.Debug("dropping MessageCommit", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "msgSeqNr": msg.SeqNr, + "reason": putResult, + }) + return + } + + outgen.logger.Debug("pooled MessageCommit", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "msgSeqNr": msg.SeqNr, + }) + + outgen.tryProcessCommitPool() +} + +func (outgen *outcomeGenerationState[RI]) tryProcessCommitPool() { + if outgen.followerState.phase != outgenFollowerPhaseSentCommit { + outgen.logger.Debug("cannot process CommitPool, wrong phase", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "phase": outgen.followerState.phase, + }) + return + } + + poolEntries := outgen.followerState.commitPool.Entries(outgen.sharedState.seqNr) + if len(poolEntries) < outgen.config.ByzQuorumSize() { + + return + } + + for sender, commitPoolEntry := range poolEntries { + if commitPoolEntry.Verified != nil { + continue + } + err := commitPoolEntry.Item.Verify( + outgen.ID(), + outgen.sharedState.seqNr, + outgen.followerState.stateTransitionInfo.InputsDigest, + outgen.followerState.stateTransitionInfo.OutputDigest, + outgen.followerState.stateTransitionInfo.ReportsPlusPrecursorDigest, + outgen.config.OracleIdentities[sender].OffchainPublicKey, + ) + ok := err == nil + commitPoolEntry.Verified = &ok + if !ok { + outgen.logger.Warn("dropping invalid MessageCommit", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "error": err, + }) + } + } + + var commitQuorumCertificate []AttributedCommitSignature + for sender, commitPoolEntry := range poolEntries { + if commitPoolEntry.Verified != nil && *commitPoolEntry.Verified { + commitQuorumCertificate = append(commitQuorumCertificate, AttributedCommitSignature{ + commitPoolEntry.Item, + sender, + }) + if len(commitQuorumCertificate) == outgen.config.ByzQuorumSize() { + break + } + } + } + + if len(commitQuorumCertificate) < outgen.config.ByzQuorumSize() { + return + } + + if outgen.id == outgen.sharedState.l { + outgen.metrics.ledCommittedRoundsTotal.Inc() + } + + persistedBlockAndCert := outgen.commit(CertifiedCommit{ + outgen.sharedState.e, + outgen.sharedState.seqNr, + outgen.followerState.stateTransitionInfo.InputsDigest, + outgen.followerState.stateTransitionInfo.Outputs, + outgen.followerState.stateTransitionInfo.ReportsPlusPrecursor, + commitQuorumCertificate, + }) + + if !persistedBlockAndCert { + outgen.logger.Error("failed to persist block/cert, stopping to not advance kv further than persisted blocks", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + + return + } + + if outgen.followerState.openKVTxn == nil { + outgen.logger.Critical("assumption violation, open kv transaction must exist in this phase", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "phase": outgen.followerState.phase, + }) + panic("") + } + err := outgen.followerState.openKVTxn.Commit() + outgen.followerState.openKVTxn.Discard() + outgen.followerState.openKVTxn = nil + if err != nil { + outgen.logger.Warn("failed to commit kv transaction", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "error": err, + }) + + { + kvSeqNr, err := outgen.kvStore.HighestCommittedSeqNr() + if err != nil { + outgen.logger.Error("failed to validate kv commit post-condition, upon kv commit failure", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "error": err, + }) + return + } + + if kvSeqNr < outgen.sharedState.seqNr { + outgen.logger.Error("kv commit failed and post-condition (seqNr <= kvSeqNr) is not satisfied", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "kvSeqNr": kvSeqNr, + }) + return + } + } + } + + if uint64(outgen.config.RMax) <= outgen.sharedState.seqNr-outgen.sharedState.firstSeqNrOfEpoch+1 { + outgen.logger.Debug("epoch has been going on for too long, sending EventChangeLeader to Pacemaker", commontypes.LogFields{ + "firstSeqNrOfEpoch": outgen.sharedState.firstSeqNrOfEpoch, + "seqNr": outgen.sharedState.seqNr, + "rMax": outgen.config.RMax, + }) + select { + case outgen.chOutcomeGenerationToPacemaker <- EventNewEpochRequest[RI]{}: + case <-outgen.ctx.Done(): + return + } + return + } else { + outgen.logger.Debug("sending EventProgress to Pacemaker", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + select { + case outgen.chOutcomeGenerationToPacemaker <- EventProgress[RI]{}: + case <-outgen.ctx.Done(): + return + } + } + + outgen.startSubsequentFollowerRound() + if outgen.id == outgen.sharedState.l { + outgen.startSubsequentLeaderRound() + } + + outgen.tryProcessRoundStartPool() +} + +func (outgen *outcomeGenerationState[RI]) commit(commit CertifiedCommit) (persistedBlockAndCert bool) { + if commit.SeqNr() < outgen.sharedState.committedSeqNr { + outgen.logger.Critical("assumption violation, commitSeqNr is less than committedSeqNr", commontypes.LogFields{ + "commitSeqNr": commit.SeqNr, + "committedSeqNr": outgen.sharedState.committedSeqNr, + }) + panic("") + } + + if commit.SeqNr() <= outgen.sharedState.committedSeqNr { + + outgen.logger.Debug("skipping commit of already committed seqNr", commontypes.LogFields{ + "commitSeqNr": commit.SeqNr(), + "committedSeqNr": outgen.sharedState.committedSeqNr, + }) + persistedBlockAndCert = true + goto ReapCompleted + } else { // commit.SeqNr >= outgen.sharedState.committedSeqNr + 1 + + if !outgen.persistCommitAsBlock(&commit) { + return + } + + if !outgen.persistAndUpdateCertIfGreater(&commit) { + return + } + + persistedBlockAndCert = true + + outgen.sharedState.committedSeqNr = commit.SeqNr() + outgen.metrics.committedSeqNr.Set(float64(commit.SeqNr())) + + outgen.logger.Debug("✅ committed", commontypes.LogFields{ + "seqNr": commit.SeqNr(), + }) + + if outgen.followerState.phase != outgenFollowerPhaseSentCommit { + outgen.logger.Debug("skipping notification of report attestation, we don't have the reports plus precursor locally", commontypes.LogFields{ + "committedSeqNr": outgen.sharedState.committedSeqNr, + "phase": outgen.followerState.phase, + }) + goto ReapCompleted + } + + reportsPlusPrecursor := outgen.followerState.stateTransitionInfo.ReportsPlusPrecursor + + if !bytes.Equal(reportsPlusPrecursor[:], commit.ReportsPlusPrecursor) { + outgen.logger.Critical("assumption violation, local reports plus precursor must match what is included in commit", commontypes.LogFields{ + "committedSeqNr": outgen.sharedState.committedSeqNr, + "reportsPlusPrecursorLocal": reportsPlusPrecursor, + "reportsPlusPrecursorCommit": commit.ReportsPlusPrecursor, + }) + panic("") + } + + select { + case outgen.chOutcomeGenerationToReportAttestation <- EventCertifiedCommit[RI]{ + + CertifiedCommittedReports[RI]{ + commit.Epoch(), + commit.SeqNr(), + commit.StateTransitionInputsDigest, + MakeStateTransitionOutputDigest( + OutcomeGenerationID{ + outgen.config.ConfigDigest, + commit.Epoch(), + }, + commit.SeqNr(), + commit.StateTransitionOutputs.WriteSet, + ), + reportsPlusPrecursor, + commit.CommitQuorumCertificate, + }, + }: + case <-outgen.ctx.Done(): + return + } + } + +ReapCompleted: + outgen.followerState.roundStartPool.ReapCompleted(outgen.sharedState.committedSeqNr) + outgen.followerState.proposalPool.ReapCompleted(outgen.sharedState.committedSeqNr) + outgen.followerState.preparePool.ReapCompleted(outgen.sharedState.committedSeqNr) + outgen.followerState.commitPool.ReapCompleted(outgen.sharedState.committedSeqNr) + return +} + +// Updates and persists cert if it is greater than the current cert. +// Returns false if the cert could not be persisted, in which case the round should be aborted. +func (outgen *outcomeGenerationState[RI]) persistAndUpdateCertIfGreater(cert CertifiedPrepareOrCommit) (ok bool) { + if outgen.followerState.cert.Timestamp().Less(cert.Timestamp()) { + ctx, cancel := context.WithTimeout(outgen.ctx, outgen.localConfig.DatabaseTimeout) + defer cancel() + if err := outgen.database.WriteCert(ctx, outgen.config.ConfigDigest, cert); err != nil { + outgen.logger.Error("error persisting cert to database, cannot safely continue current round", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "lastWrittenCertTimestamp": outgen.followerState.cert.Timestamp(), + "certTimestamp": cert.Timestamp(), + "error": err, + }) + return false + } + + outgen.followerState.cert = cert + } + return true +} + +// If the attributed signed observations have valid signature, and they satisfy ValidateObservation +// and ObservationQuorum plugin methods, this function returns the vector of corresponding +// AttributedObservations and true. +func (outgen *outcomeGenerationState[RI]) checkAttributedSignedObservations( + ctx context.Context, + logger loghelper.LoggerWithContext, + ogid OutcomeGenerationID, + roundCtx RoundContext, + aq types.AttributedQuery, + asos []AttributedSignedObservation, + kvReader ocr3_1types.KeyValueReader, +) ([]types.AttributedObservation, bool) { + + attributedObservations := []types.AttributedObservation{} + + seen := map[commontypes.OracleID]bool{} + + for _, aso := range asos { + if !(0 <= int(aso.Observer) && int(aso.Observer) <= outgen.config.N()) { + logger.Warn("dropping MessageProposal that contains signed observation with invalid observer", commontypes.LogFields{ + "seqNr": roundCtx.SeqNr, + "invalidObserver": aso.Observer, + }) + return nil, false + } + + if seen[aso.Observer] { + logger.Warn("dropping MessageProposal that contains duplicate signed observation", commontypes.LogFields{ + "seqNr": roundCtx.SeqNr, + }) + return nil, false + } + + seen[aso.Observer] = true + + if err := aso.SignedObservation.Verify(ogid, roundCtx.SeqNr, aq.Query, outgen.config.OracleIdentities[aso.Observer].OffchainPublicKey); err != nil { + logger.Warn("dropping MessageProposal that contains signed observation with invalid signature", commontypes.LogFields{ + "seqNr": roundCtx.SeqNr, + "error": err, + }) + return nil, false + } + + err, ok := callPluginFromOutcomeGenerationBackground[error]( + ctx, + logger, + "ValidateObservation", + 0, // ValidateObservation is a pure function and should finish "instantly" + roundCtx, + func(ctx context.Context, roundCtx RoundContext) (error, error) { + return outgen.reportingPlugin.ValidateObservation( + ctx, + roundCtx.SeqNr, + aq, + types.AttributedObservation{aso.SignedObservation.Observation, aso.Observer}, + kvReader, + outgen.blobBroadcastFetcher, + ), nil + }, + ) + // kvReader.Discard() must not happen here, because + // backgroundStateTransition (our caller) manages the lifecycle of the + // underlying transaction. + if !ok { + logger.Error("dropping MessageProposal containing observation that could not be validated", commontypes.LogFields{ + "seqNr": roundCtx.SeqNr, + "observer": aso.Observer, + }) + return nil, false + } + if err != nil { + logger.Warn("dropping MessageProposal that contains an invalid observation", commontypes.LogFields{ + "seqNr": roundCtx.SeqNr, + "error": err, + "observer": aso.Observer, + }) + return nil, false + } + + attributedObservations = append(attributedObservations, types.AttributedObservation{ + aso.SignedObservation.Observation, + aso.Observer, + }) + } + + observationQuorum, ok := callPluginFromOutcomeGenerationBackground[bool]( + ctx, + logger, + "ObservationQuorum", + 0, // ObservationQuorum is a pure function and should finish "instantly" + roundCtx, + func(ctx context.Context, roundCtx RoundContext) (bool, error) { + return outgen.reportingPlugin.ObservationQuorum(ctx, roundCtx.SeqNr, aq, attributedObservations, kvReader, outgen.blobBroadcastFetcher) + }, + ) + + if !ok { + return nil, false + } + + if !observationQuorum { + logger.Warn("dropping MessageProposal that doesn't achieve observation quorum", commontypes.LogFields{ + "seqNr": roundCtx.SeqNr, + }) + return nil, false + } + + if seen[outgen.id] { + outgen.metrics.includedObservationsTotal.Inc() + } + + return attributedObservations, true +} + +func (outgen *outcomeGenerationState[RI]) persistCommitAsBlock(commit *CertifiedCommit) bool { + ctx := outgen.ctx + configDigest := outgen.config.ConfigDigest + seqNr := commit.SeqNr() + astb := AttestedStateTransitionBlock{ + StateTransitionBlock{ + commit.Epoch(), + seqNr, + commit.StateTransitionInputsDigest, + commit.StateTransitionOutputs, + commit.ReportsPlusPrecursor, + }, + commit.CommitQuorumCertificate, + } + + werr := outgen.database.WriteAttestedStateTransitionBlock( + ctx, + configDigest, + seqNr, + astb, + ) + + if werr != nil { + + astb, rerr := outgen.database.ReadAttestedStateTransitionBlock( + ctx, + configDigest, + seqNr, + ) + if astb.StateTransitionBlock.SeqNr() == seqNr && rerr == nil { + // already persisted by someone else + return true + } else { + outgen.logger.Error("error persisting commit as attested state transition block", commontypes.LogFields{ + "seqNr": seqNr, + "error": werr, + }) + return false + } + } else { + // persited now by us + outgen.logger.Trace("persisted block", commontypes.LogFields{ + "seqNr": seqNr, + }) + return true + } +} + +func (outgen *outcomeGenerationState[RI]) refreshCommittedSeqNrAndCert() { + preRefreshCommittedSeqNr := outgen.sharedState.committedSeqNr + + postRefreshCommittedSeqNr, err := outgen.kvStore.HighestCommittedSeqNr() + if err != nil { + outgen.logger.Error("kvStore.HighestCommittedSeqNr() failed during refresh", commontypes.LogFields{ + "preRefreshCommittedSeqNr": preRefreshCommittedSeqNr, + "error": err, + }) + return + } + + logger := outgen.logger.MakeChild(commontypes.LogFields{ + "preRefreshCommittedSeqNr": preRefreshCommittedSeqNr, + "postRefreshCommittedSeqNr": postRefreshCommittedSeqNr, + }) + + if postRefreshCommittedSeqNr == preRefreshCommittedSeqNr { + return + } else if postRefreshCommittedSeqNr < preRefreshCommittedSeqNr { + logger.Critical("assumption violation, kv is behind what outgen knows as committed", nil) + panic("") + } + + ctx := outgen.ctx + configDigest := outgen.config.ConfigDigest + astb, err := outgen.database.ReadAttestedStateTransitionBlock( + ctx, + configDigest, + postRefreshCommittedSeqNr, + ) + if err != nil { + logger.Error("error reading attested state transition block during refresh", commontypes.LogFields{ + "error": err, + }) + return + } + if astb.StateTransitionBlock.SeqNr() == 0 { + logger.Critical("assumption violation, attested state transition block for kv committed seq nr does not exist", nil) + panic("") + } + if astb.StateTransitionBlock.SeqNr() != postRefreshCommittedSeqNr { + logger.Critical("assumption violation, attested state transition block has unexpected seq nr", commontypes.LogFields{ + "expectedSeqNr": postRefreshCommittedSeqNr, + "actualSeqNr": astb.StateTransitionBlock.SeqNr(), + }) + panic("") + } + stb := astb.StateTransitionBlock + + persistedBlockAndCert := outgen.commit(CertifiedCommit{ + stb.Epoch, + stb.SeqNr(), + stb.StateTransitionInputsDigest, + stb.StateTransitionOutputs, + stb.ReportsPlusPrecursor, + astb.AttributedSignatures, + }) + + if !persistedBlockAndCert { + logger.Warn("outgen.commit() failed, aborting refresh", nil) + return + } + + if outgen.sharedState.committedSeqNr != postRefreshCommittedSeqNr { + logger.Critical("assumption violation, outgen local committed seq nr did not progress even though we persisted block and cert", commontypes.LogFields{ + "expectedCommittedSeqNr": postRefreshCommittedSeqNr, + "actualCommittedSeqNr": outgen.sharedState.committedSeqNr, + }) + panic("") + } + + logger.Debug("refreshed cert", nil) +} + +func (outgen *outcomeGenerationState[RI]) ensureHighestCertifiedIsCompatible(highestCertified CertifiedPrepareOrCommit, name string) bool { + logger := outgen.logger + committedSeqNr := outgen.sharedState.committedSeqNr + + if highestCertified.IsGenesis() { + return true + } else if commitQC, ok := highestCertified.(*CertifiedCommit); ok { + commitQcSeqNr := commitQC.SeqNr() + if commitQcSeqNr == committedSeqNr { + + } else if commitQcSeqNr > committedSeqNr { + + logger.Warn("dropping "+name+" because we are behind (commitQc)", commontypes.LogFields{ + "commitQcSeqNr": commitQcSeqNr, + "committedSeqNr": committedSeqNr, + }) + + return false + } else { + + logger.Warn("dropping "+name+" because we are ahead (commitQc)", commontypes.LogFields{ + "commitQcSeqNr": commitQcSeqNr, + "committedSeqNr": committedSeqNr, + }) + return false + } + } else { + // We're dealing with a re-proposal from a failed epoch + + prepareQc, ok := highestCertified.(*CertifiedPrepare) + if !ok { + logger.Critical("cast to CertifiedPrepare failed while processing "+name, commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + return false + } + + committedSeqNr := outgen.sharedState.committedSeqNr + prepareQcSeqNr := prepareQc.SeqNr() + + if prepareQcSeqNr == committedSeqNr+1 { + + } else if prepareQcSeqNr == committedSeqNr { + + } else if prepareQcSeqNr > committedSeqNr+1 { + + logger.Warn("dropping "+name+" because we are behind (prepareQc)", commontypes.LogFields{ + "prepareQcSeqNr": prepareQcSeqNr, + "committedSeqNr": committedSeqNr, + }) + return false + } else { + + logger.Warn("dropping "+name+" because we are ahead (prepareQc)", commontypes.LogFields{ + "prepareQcSeqNr": prepareQcSeqNr, + "committedSeqNr": committedSeqNr, + }) + return false + } + } + return true +} + +func (outgen *outcomeGenerationState[RI]) ensureOpenKVTransactionDiscarded() { + if outgen.followerState.openKVTxn != nil { + outgen.logger.Warn("discarding open transaction from probably previously failed round", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "spicy": "🫑", + }) + outgen.followerState.openKVTxn.Discard() + outgen.followerState.openKVTxn = nil + } +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/outcome_generation_leader.go b/offchainreporting2plus/internal/ocr3_1/protocol/outcome_generation_leader.go new file mode 100644 index 00000000..d48fe44f --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/outcome_generation_leader.go @@ -0,0 +1,618 @@ +package protocol + +import ( + "context" + "time" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/pool" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type outgenLeaderPhase string + +const ( + outgenLeaderPhaseUnknown outgenLeaderPhase = "unknown" + outgenLeaderPhaseNewEpoch outgenLeaderPhase = "newEpoch" + outgenLeaderPhaseAbdicate outgenLeaderPhase = "abdicate" + outgenLeaderPhaseSentEpochStart outgenLeaderPhase = "sentEpochStart" + outgenLeaderPhaseSentRoundStart outgenLeaderPhase = "sentRoundStart" + outgenLeaderPhaseGrace outgenLeaderPhase = "grace" + outgenLeaderPhaseSentProposal outgenLeaderPhase = "sentProposal" +) + +func (outgen *outcomeGenerationState[RI]) messageEpochStartRequest(msg MessageEpochStartRequest[RI], sender commontypes.OracleID) { + + outgen.logger.Debug("received MessageEpochStartRequest", commontypes.LogFields{ + "sender": sender, + "msgHighestCertifiedTimestamp": msg.SignedHighestCertifiedTimestamp.HighestCertifiedTimestamp, + "msgEpoch": msg.Epoch, + }) + + if msg.Epoch != outgen.sharedState.e { + outgen.logger.Debug("dropping MessageEpochStartRequest for wrong epoch", commontypes.LogFields{ + "sender": sender, + "msgEpoch": msg.Epoch, + }) + return + } + + if outgen.sharedState.l != outgen.id { + outgen.logger.Warn("dropping MessageEpochStartRequest to non-leader", commontypes.LogFields{ + "sender": sender, + }) + return + } + + if outgen.leaderState.phase != outgenLeaderPhaseNewEpoch { + outgen.logger.Debug("dropping MessageEpochStartRequest for wrong phase", commontypes.LogFields{ + "sender": sender, + "phase": outgen.leaderState.phase, + }) + return + } + + if outgen.leaderState.epochStartRequests[sender] != nil { + outgen.logger.Warn("dropping duplicate MessageEpochStartRequest", commontypes.LogFields{ + "sender": sender, + }) + return + } + + outgen.leaderState.epochStartRequests[sender] = &epochStartRequest[RI]{} + + if err := msg.SignedHighestCertifiedTimestamp.Verify( + outgen.ID(), + outgen.config.OracleIdentities[sender].OffchainPublicKey, + ); err != nil { + outgen.leaderState.epochStartRequests[sender].bad = true + outgen.logger.Warn("MessageEpochStartRequest.SignedHighestCertifiedTimestamp is invalid", commontypes.LogFields{ + "sender": sender, + "error": err, + }) + return + } + + // Note that the MessageEpochStartRequest might still be invalid, e.g. if its HighestCertified is invalid. + outgen.logger.Debug("got MessageEpochStartRequest with valid SignedHighestCertifiedTimestamp", commontypes.LogFields{ + "sender": sender, + "msgHighestCertifiedTimestamp": msg.SignedHighestCertifiedTimestamp.HighestCertifiedTimestamp, + }) + + outgen.leaderState.epochStartRequests[sender].message = msg + + if len(outgen.leaderState.epochStartRequests) < outgen.config.ByzQuorumSize() { + return + } + + goodCount := 0 + var maxSender *commontypes.OracleID + for sender, epochStartRequest := range outgen.leaderState.epochStartRequests { + if epochStartRequest.bad { + continue + } + goodCount++ + + if maxSender == nil || outgen.leaderState.epochStartRequests[*maxSender].message.SignedHighestCertifiedTimestamp.HighestCertifiedTimestamp.Less(epochStartRequest.message.SignedHighestCertifiedTimestamp.HighestCertifiedTimestamp) { + sender := sender + maxSender = &sender + } + } + + if maxSender == nil || goodCount < outgen.config.ByzQuorumSize() { + return + } + + maxRequest := outgen.leaderState.epochStartRequests[*maxSender] + + if maxRequest.message.HighestCertified.Timestamp() != maxRequest.message.SignedHighestCertifiedTimestamp.HighestCertifiedTimestamp { + maxRequest.bad = true + outgen.logger.Warn("timestamp mismatch in MessageEpochStartRequest", commontypes.LogFields{ + "sender": *maxSender, + "highestCertified.Timestamp": maxRequest.message.HighestCertified.Timestamp(), + "signedHighestCertifiedTimestamp": maxRequest.message.SignedHighestCertifiedTimestamp.HighestCertifiedTimestamp, + }) + return + } + + if err := maxRequest.message.HighestCertified.Verify( + outgen.config.ConfigDigest, + outgen.config.OracleIdentities, + outgen.config.ByzQuorumSize(), + ); err != nil { + maxRequest.bad = true + outgen.logger.Warn("MessageEpochStartRequest.HighestCertified is invalid", commontypes.LogFields{ + "sender": *maxSender, + "error": err, + }) + return + } + + highestCertifiedProof := make([]AttributedSignedHighestCertifiedTimestamp, 0, outgen.config.ByzQuorumSize()) + contributors := make([]commontypes.OracleID, 0, outgen.config.ByzQuorumSize()) + for sender, epochStartRequest := range outgen.leaderState.epochStartRequests { + if epochStartRequest.bad { + continue + } + highestCertifiedProof = append(highestCertifiedProof, AttributedSignedHighestCertifiedTimestamp{ + epochStartRequest.message.SignedHighestCertifiedTimestamp, + sender, + }) + contributors = append(contributors, sender) + // not necessary, but hopefully helps with readability + if len(highestCertifiedProof) == outgen.config.ByzQuorumSize() { + break + } + } + + epochStartProof := EpochStartProof{ + maxRequest.message.HighestCertified, + highestCertifiedProof, + } + + outgen.refreshCommittedSeqNrAndCert() + if !outgen.ensureHighestCertifiedIsCompatible(maxRequest.message.HighestCertified, "potential broadcast of EpochStartProof") { + outgen.leaderState.phase = outgenLeaderPhaseAbdicate + + return + } + + // This is a sanity check to ensure that we only construct epochStartProofs that are actually valid. + // This should never fail. + if err := epochStartProof.Verify(outgen.ID(), outgen.config.OracleIdentities, outgen.config.ByzQuorumSize()); err != nil { + outgen.logger.Critical("EpochStartProof is invalid, very surprising!", commontypes.LogFields{ + "proof": epochStartProof, + "error": err, + }) + return + } + + outgen.leaderState.phase = outgenLeaderPhaseSentEpochStart + + outgen.logger.Info("broadcasting MessageEpochStart", commontypes.LogFields{ + "contributors": contributors, + "highestCertifiedTimestamp": epochStartProof.HighestCertified.Timestamp(), + "highestCertifiedQCSeqNr": epochStartProof.HighestCertified.SeqNr(), + }) + + outgen.netSender.Broadcast(MessageEpochStart[RI]{ + outgen.sharedState.e, + epochStartProof, + }) + + if epochStartProof.HighestCertified.IsGenesis() { + outgen.sharedState.firstSeqNrOfEpoch = outgen.sharedState.committedSeqNr + 1 + outgen.startSubsequentLeaderRound() + } else if commitQC, ok := epochStartProof.HighestCertified.(*CertifiedCommit); ok { + outgen.commit(*commitQC) + outgen.sharedState.firstSeqNrOfEpoch = outgen.sharedState.committedSeqNr + 1 + outgen.startSubsequentLeaderRound() + } else { + prepareQc, ok := epochStartProof.HighestCertified.(*CertifiedPrepare) + if !ok { + outgen.logger.Critical("cast to CertifiedPrepare failed while processing MessageEpochStartRequest", nil) + return + } + outgen.sharedState.firstSeqNrOfEpoch = prepareQc.SeqNr() + 1 + // We're dealing with a re-proposal from a failed epoch based on a + // prepare qc. + // We don't want to send MessageRoundStart. + } +} + +func (outgen *outcomeGenerationState[RI]) eventTRoundTimeout() { + outgen.logger.Debug("TRound fired", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "committedSeqNr": outgen.sharedState.committedSeqNr, + "deltaRound": outgen.config.DeltaRound.String(), + }) + outgen.startSubsequentLeaderRound() +} + +func (outgen *outcomeGenerationState[RI]) startSubsequentLeaderRound() { + outgen.logger.Debug("trying to start new leader round", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + + if !outgen.leaderState.readyToStartRound { + outgen.logger.Debug("not ready to start new leader round yet", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + outgen.leaderState.readyToStartRound = true + return + } + outgen.leaderState.readyToStartRound = false + outgen.logger.Debug("starting new leader round", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + }) + + { + ctx := outgen.epochCtx + logger := outgen.logger + roundCtx := outgen.RoundCtx(outgen.sharedState.committedSeqNr + 1) + kvReadTxn, err := outgen.kvStore.NewReadTransaction(roundCtx.SeqNr) + if err != nil { + outgen.logger.Warn("failed to create new transaction, aborting startSubsequentLeaderRound", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "error": err, + }) + return + } + outgen.subs.Go(func() { + outgen.backgroundQuery(ctx, logger, roundCtx, kvReadTxn) + }) + } +} + +func (outgen *outcomeGenerationState[RI]) backgroundQuery( + ctx context.Context, + logger loghelper.LoggerWithContext, + roundCtx RoundContext, + kvReadTxn KeyValueStoreReadTransaction, +) { + query, ok := callPluginFromOutcomeGenerationBackground[types.Query]( + ctx, + logger, + "Query", + outgen.config.MaxDurationQuery, + roundCtx, + func(ctx context.Context, outctx RoundContext) (types.Query, error) { + return outgen.reportingPlugin.Query(ctx, roundCtx.SeqNr, kvReadTxn, outgen.blobBroadcastFetcher) + }, + ) + kvReadTxn.Discard() + if !ok { + return + } + + select { + case outgen.chLocalEvent <- EventComputedQuery[RI]{ + roundCtx.Epoch, + roundCtx.SeqNr, + query, + }: + case <-ctx.Done(): + } +} + +func (outgen *outcomeGenerationState[RI]) eventComputedQuery(ev EventComputedQuery[RI]) { + if ev.Epoch != outgen.sharedState.e || ev.SeqNr != outgen.sharedState.committedSeqNr+1 { + outgen.logger.Debug("discarding EventComputedQuery from old round", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "committedSeqNr": outgen.sharedState.committedSeqNr, + "evEpoch": ev.Epoch, + "evSeqNr": ev.SeqNr, + }) + return + } + + outgen.leaderState.query = ev.Query + + outgen.leaderState.observationPool.ReapCompleted(outgen.sharedState.committedSeqNr) + + outgen.leaderState.tRound = time.After(outgen.config.DeltaRound) + + outgen.leaderState.phase = outgenLeaderPhaseSentRoundStart + outgen.logger.Debug("broadcasting MessageRoundStart", commontypes.LogFields{ + "seqNr": outgen.sharedState.committedSeqNr + 1, + }) + outgen.netSender.Broadcast(MessageRoundStart[RI]{ + outgen.sharedState.e, + outgen.sharedState.committedSeqNr + 1, + ev.Query, + }) +} + +func (outgen *outcomeGenerationState[RI]) messageObservation(msg MessageObservation[RI], sender commontypes.OracleID) { + + outgen.logger.Debug("received MessageObservation", commontypes.LogFields{ + "sender": sender, + "msgSeqNr": msg.SeqNr, + "msgEpoch": msg.Epoch, + }) + + if msg.Epoch != outgen.sharedState.e { + outgen.logger.Debug("dropping MessageObservation for wrong epoch", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "msgEpoch": msg.Epoch, + "msgSeqNr": msg.SeqNr, + }) + return + } + + if outgen.sharedState.l != outgen.id { + outgen.logger.Warn("dropping MessageObservation to non-leader", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "msgSeqNr": msg.SeqNr, + }) + return + } + + if outgen.leaderState.phase != outgenLeaderPhaseSentRoundStart && outgen.leaderState.phase != outgenLeaderPhaseGrace { + outgen.logger.Debug("dropping MessageObservation for wrong phase", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "msgSeqNr": msg.SeqNr, + "phase": outgen.leaderState.phase, + }) + return + } + + if msg.SeqNr != outgen.sharedState.seqNr { + outgen.logger.Debug("dropping MessageObservation with invalid SeqNr", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "msgSeqNr": msg.SeqNr, + }) + return + } + + if putResult := outgen.leaderState.observationPool.Put(msg.SeqNr, sender, msg.SignedObservation); putResult != pool.PutResultOK { + outgen.logger.Warn("dropping MessageObservation", commontypes.LogFields{ + "sender": sender, + "seqNr": outgen.sharedState.seqNr, + "msgSeqNr": msg.SeqNr, + "reason": putResult, + }) + return + } + + { + ctx := outgen.epochCtx + logger := outgen.logger + ogid := outgen.ID() + roundCtx := outgen.RoundCtx(outgen.sharedState.seqNr) + aq := types.AttributedQuery{ + outgen.leaderState.query, + outgen.sharedState.l, + } + kvReadTxn, err := outgen.kvStore.NewReadTransaction(roundCtx.SeqNr) + if err != nil { + outgen.logger.Warn("failed to create new transaction, aborting messageObservation", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "error": err, + }) + return + } + outgen.subs.Go(func() { + outgen.backgroundVerifyValidateObservation(ctx, logger, ogid, roundCtx, sender, msg.SignedObservation, aq, kvReadTxn) + }) + } +} + +func (outgen *outcomeGenerationState[RI]) backgroundVerifyValidateObservation( + ctx context.Context, + logger loghelper.LoggerWithContext, + ogid OutcomeGenerationID, + roundCtx RoundContext, + sender commontypes.OracleID, + signedObservation SignedObservation, + aq types.AttributedQuery, + kvReadTxn KeyValueStoreReadTransaction, +) { + if err := signedObservation.Verify( + ogid, + roundCtx.SeqNr, + aq.Query, + outgen.config.OracleIdentities[sender].OffchainPublicKey, + ); err != nil { + logger.Warn("dropping MessageObservation carrying invalid SignedObservation", commontypes.LogFields{ + "sender": sender, + "seqNr": roundCtx.SeqNr, + "error": err, + }) + return + } + + err, ok := callPluginFromOutcomeGenerationBackground[error]( + ctx, + logger, + "ValidateObservation", + 0, // ValidateObservation is a pure function and should finish "instantly" + roundCtx, + func(ctx context.Context, roundCtx RoundContext) (error, error) { + return outgen.reportingPlugin.ValidateObservation(ctx, + roundCtx.SeqNr, + aq, + types.AttributedObservation{signedObservation.Observation, sender}, + kvReadTxn, + outgen.blobBroadcastFetcher, + ), nil + }, + ) + kvReadTxn.Discard() + if !ok { + logger.Error("dropping MessageObservation carrying Observation that could not be validated", commontypes.LogFields{ + "sender": sender, + "seqNr": roundCtx.SeqNr, + }) + return + } + + if err != nil { + logger.Warn("dropping MessageObservation carrying invalid Observation", commontypes.LogFields{ + "sender": sender, + "seqNr": roundCtx.SeqNr, + "error": err, + }) + return + } + + select { + case outgen.chLocalEvent <- EventComputedValidateVerifyObservation[RI]{ + roundCtx.Epoch, + roundCtx.SeqNr, + sender, + }: + case <-ctx.Done(): + } +} + +func (outgen *outcomeGenerationState[RI]) eventComputedValidateVerifyObservation(ev EventComputedValidateVerifyObservation[RI]) { + if ev.Epoch != outgen.sharedState.e || ev.SeqNr != outgen.sharedState.seqNr { + outgen.logger.Debug("discarding EventComputedValidateVerifyObservation from old round", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "evEpoch": ev.Epoch, + "evSeqNr": ev.SeqNr, + }) + return + } + + if outgen.leaderState.phase != outgenLeaderPhaseSentRoundStart && outgen.leaderState.phase != outgenLeaderPhaseGrace { + outgen.logger.Debug("discarding EventComputedValidateVerifyObservation, wrong phase", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "phase": outgen.leaderState.phase, + }) + return + } + + outgen.logger.Debug("got valid MessageObservation", commontypes.LogFields{ + "sender": ev.Sender, + "seqNr": ev.SeqNr, + }) + + outgen.leaderState.observationPool.StoreVerified(outgen.sharedState.seqNr, ev.Sender, true) + + { + ctx := outgen.epochCtx + logger := outgen.logger + outctx := outgen.RoundCtx(outgen.sharedState.seqNr) + aq := types.AttributedQuery{ + outgen.leaderState.query, + outgen.sharedState.l, + } + aos := []types.AttributedObservation{} + for sender, observationPoolEntry := range outgen.leaderState.observationPool.Entries(outgen.sharedState.seqNr) { + if observationPoolEntry.Verified == nil || !*observationPoolEntry.Verified { + continue + } + aos = append(aos, types.AttributedObservation{observationPoolEntry.Item.Observation, sender}) + } + kvReadTxn, err := outgen.kvStore.NewReadTransaction(outctx.SeqNr) + if err != nil { + outgen.logger.Warn("failed to create new transaction, aborting eventComputedValidateVerifyObservation", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "error": err, + }) + return + } + + outgen.subs.Go(func() { + outgen.backgroundObservationQuorum( + ctx, + logger, + outctx, + aq, + aos, + kvReadTxn, + ) + }) + } +} + +func (outgen *outcomeGenerationState[RI]) backgroundObservationQuorum( + ctx context.Context, + logger loghelper.LoggerWithContext, + roundCtx RoundContext, + aq types.AttributedQuery, + aos []types.AttributedObservation, + kvReadTxn KeyValueStoreReadTransaction, +) { + observationQuorum, ok := callPluginFromOutcomeGenerationBackground[bool]( + ctx, + logger, + "ObservationQuorum", + 0, // ObservationQuorum is a pure function and should finish "instantly" + roundCtx, + func(ctx context.Context, roundCtx RoundContext) (bool, error) { + return outgen.reportingPlugin.ObservationQuorum(ctx, roundCtx.SeqNr, aq, aos, kvReadTxn, outgen.blobBroadcastFetcher) + }, + ) + kvReadTxn.Discard() + + if !ok { + return + } + + if !observationQuorum { + if len(aos) >= outgen.config.N()-outgen.config.F { + logger.Warn("ObservationQuorum returned false despite there being at least n-f valid observations. This is the maximum number of valid observations we are guaranteed to receive. Maybe there is a bug in the ReportingPlugin.", commontypes.LogFields{ + "attributedObservationCount": len(aos), + "nMinusF": outgen.config.N() - outgen.config.F, + "seqNr": roundCtx.SeqNr, + }) + } + return + } + + select { + case outgen.chLocalEvent <- EventComputedObservationQuorumSuccess[RI]{ + roundCtx.Epoch, + roundCtx.SeqNr, + }: + case <-ctx.Done(): + } +} + +func (outgen *outcomeGenerationState[RI]) eventComputedObservationQuorumSuccess(ev EventComputedObservationQuorumSuccess[RI]) { + if ev.Epoch != outgen.sharedState.e || ev.SeqNr != outgen.sharedState.seqNr { + outgen.logger.Debug("discarding EventComputedObservationQuorumSuccess from old round", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "evEpoch": ev.Epoch, + "evSeqNr": ev.SeqNr, + }) + return + } + + if outgen.leaderState.phase != outgenLeaderPhaseSentRoundStart { + outgen.logger.Debug("discarding EventComputedObservationQuorumSuccess, wrong phase", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "phase": outgen.leaderState.phase, + }) + return + } + + outgen.logger.Debug("reached observation quorum, starting observation grace period", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "deltaGrace": outgen.config.DeltaGrace.String(), + }) + outgen.leaderState.phase = outgenLeaderPhaseGrace + outgen.leaderState.tGrace = time.After(outgen.config.DeltaGrace) +} + +func (outgen *outcomeGenerationState[RI]) eventTGraceTimeout() { + if outgen.leaderState.phase != outgenLeaderPhaseGrace { + outgen.logger.Error("leader's phase conflicts TGrace timeout", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "phase": outgen.leaderState.phase, + }) + return + } + asos := make([]AttributedSignedObservation, 0, outgen.config.N()) + contributors := make([]commontypes.OracleID, 0, outgen.config.N()) + for sender, observationPoolEntry := range outgen.leaderState.observationPool.Entries(outgen.sharedState.seqNr) { + if observationPoolEntry.Verified == nil || !*observationPoolEntry.Verified { + continue + } + asos = append(asos, AttributedSignedObservation{SignedObservation: observationPoolEntry.Item, Observer: sender}) + contributors = append(contributors, sender) + } + + outgen.leaderState.phase = outgenLeaderPhaseSentProposal + + outgen.logger.Debug("broadcasting MessageProposal after TGrace fired", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "contributors": contributors, + "deltaGrace": outgen.config.DeltaGrace.String(), + }) + outgen.netSender.Broadcast(MessageProposal[RI]{ + outgen.sharedState.e, + outgen.sharedState.seqNr, + asos, + }) + + outgen.leaderState.observationPool.ReapCompleted(outgen.sharedState.seqNr) +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/pacemaker.go b/offchainreporting2plus/internal/ocr3_1/protocol/pacemaker.go new file mode 100644 index 00000000..a30b815f --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/pacemaker.go @@ -0,0 +1,358 @@ +package protocol + +import ( + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/binary" + "fmt" + "sort" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3config" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/libocr/permutation" +) + +func RunPacemaker[RI any]( + ctx context.Context, + + chNetToPacemaker <-chan MessageToPacemakerWithSender[RI], + chPacemakerToOutcomeGeneration chan<- EventToOutcomeGeneration[RI], + chOutcomeGenerationToPacemaker <-chan EventToPacemaker[RI], + config ocr3config.SharedConfig, + database Database, + id commontypes.OracleID, + localConfig types.LocalConfig, + logger loghelper.LoggerWithContext, + metricsRegisterer prometheus.Registerer, + netSender NetworkSender[RI], + offchainKeyring types.OffchainKeyring, + telemetrySender TelemetrySender, + + restoredState PacemakerState, +) { + pace := makePacemakerState[RI]( + ctx, chNetToPacemaker, + chPacemakerToOutcomeGeneration, chOutcomeGenerationToPacemaker, + config, database, + id, localConfig, logger, metricsRegisterer, netSender, offchainKeyring, + telemetrySender, + ) + pace.run(restoredState) +} + +func makePacemakerState[RI any]( + ctx context.Context, + chNetToPacemaker <-chan MessageToPacemakerWithSender[RI], + chPacemakerToOutcomeGeneration chan<- EventToOutcomeGeneration[RI], + chOutcomeGenerationToPacemaker <-chan EventToPacemaker[RI], + config ocr3config.SharedConfig, + database Database, id commontypes.OracleID, + localConfig types.LocalConfig, + logger loghelper.LoggerWithContext, + metricsRegisterer prometheus.Registerer, + netSender NetworkSender[RI], + offchainKeyring types.OffchainKeyring, + telemetrySender TelemetrySender, +) pacemakerState[RI] { + return pacemakerState[RI]{ + ctx: ctx, + + chNetToPacemaker: chNetToPacemaker, + chPacemakerToOutcomeGeneration: chPacemakerToOutcomeGeneration, + chOutcomeGenerationToPacemaker: chOutcomeGenerationToPacemaker, + config: config, + database: database, + id: id, + localConfig: localConfig, + logger: logger.MakeUpdated(commontypes.LogFields{"proto": "pacemaker"}), + metrics: newPacemakerMetrics(metricsRegisterer, logger), + netSender: netSender, + offchainKeyring: offchainKeyring, + telemetrySender: telemetrySender, + + newEpochWishes: make([]uint64, config.N()), + } +} + +type pacemakerState[RI any] struct { + ctx context.Context + + chNetToPacemaker <-chan MessageToPacemakerWithSender[RI] + chPacemakerToOutcomeGeneration chan<- EventToOutcomeGeneration[RI] + chOutcomeGenerationToPacemaker <-chan EventToPacemaker[RI] + config ocr3config.SharedConfig + database Database + id commontypes.OracleID + localConfig types.LocalConfig + logger loghelper.LoggerWithContext + metrics *pacemakerMetrics + netSender NetworkSender[RI] + offchainKeyring types.OffchainKeyring + telemetrySender TelemetrySender + // Test use only: send testBlocker an event to halt the pacemaker event loop, + // send testUnblocker an event to resume it. + testBlocker chan eventTestBlock + testUnblocker chan eventTestUnblock + + // ne is the highest epoch number this oracle has broadcast in a + // NewEpochWish message + ne uint64 + + // e is the number of the current epoch + e uint64 + + // l is the index of the leader for the current epoch + l commontypes.OracleID + + // newEpochWishes[j] is the highest epoch number oracle j has sent in a + // NewEpochWish message + newEpochWishes []uint64 + + // tResend is a timeout used to periodically resend the latest NewEpochWish + // message in order to guard against unreliable network conditions + tResend <-chan time.Time + + // tProgress is a timeout used by the protocol to track whether the current + // leader/epoch is making adequate progress. + tProgress <-chan time.Time + + notifyOutcomeGenerationOfNewEpoch bool +} + +func (pace *pacemakerState[RI]) run(restoredState PacemakerState) { + pace.logger.Info("Pacemaker: running", nil) + + // Initialization + + if restoredState == (PacemakerState{}) { + // seqNrs start with 1, so let's make epochs also start with 1 + pace.ne = 1 + pace.e = 1 + } else { + pace.ne = restoredState.HighestSentNewEpochWish + pace.e = restoredState.Epoch + } + pace.l = Leader(pace.e, pace.config.N(), pace.config.LeaderSelectionKey()) + + pace.tProgress = time.After(pace.config.DeltaProgress) + + pace.sendNewEpochWish() + + pace.notifyOutcomeGenerationOfNewEpoch = true + + // Initialization complete + + // Take a reference to the ctx.Done channel once, here, to avoid taking the + // context lock below. + chDone := pace.ctx.Done() + + // Event Loop + for { + var nilOrChPacemakerToOutcomeGeneration chan<- EventToOutcomeGeneration[RI] + if pace.notifyOutcomeGenerationOfNewEpoch { + nilOrChPacemakerToOutcomeGeneration = pace.chPacemakerToOutcomeGeneration + } else { + nilOrChPacemakerToOutcomeGeneration = nil + } + + select { + case nilOrChPacemakerToOutcomeGeneration <- EventNewEpochStart[RI]{pace.e}: + pace.notifyOutcomeGenerationOfNewEpoch = false + case msg := <-pace.chNetToPacemaker: + msg.msg.processPacemaker(pace, msg.sender) + case ev := <-pace.chOutcomeGenerationToPacemaker: + ev.processPacemaker(pace) + case <-pace.tResend: + pace.eventTResendTimeout() + case <-pace.tProgress: + pace.eventTProgressTimeout() + case <-pace.testBlocker: + <-pace.testUnblocker + case <-chDone: + } + + // ensure prompt exit + select { + case <-chDone: + pace.logger.Info("Pacemaker: winding down", nil) + pace.metrics.Close() + pace.logger.Info("Pacemaker: exiting", nil) + return + default: + } + } +} + +func (pace *pacemakerState[RI]) eventProgress() { + pace.tProgress = time.After(pace.config.DeltaProgress) +} + +func (pace *pacemakerState[RI]) sendNewEpochWish() { + pace.netSender.Broadcast(MessageNewEpochWish[RI]{pace.ne}) + pace.tResend = time.After(pace.config.DeltaResend) +} + +func (pace *pacemakerState[RI]) eventTResendTimeout() { + pace.sendNewEpochWish() +} + +func (pace *pacemakerState[RI]) eventTProgressTimeout() { + pace.logger.Debug("TProgress fired", commontypes.LogFields{ + "deltaProgress": pace.config.DeltaProgress.String(), + }) + pace.eventNewEpochRequest() +} + +func (pace *pacemakerState[RI]) eventNewEpochRequest() { + pace.tProgress = nil + epochPlusOne := pace.e + 1 + if epochPlusOne <= pace.e { + pace.logger.Critical("epoch overflows, cannot change leader", nil) + return + } + + if pace.ne < epochPlusOne { // ne ← max{e + 1, ne} + if err := pace.persist(PacemakerState{pace.e, epochPlusOne}); err != nil { + pace.logger.Error("could not persist pacemaker state in eventNewEpochRequest", commontypes.LogFields{ + "error": err, + }) + } + + pace.ne = epochPlusOne + } + pace.sendNewEpochWish() +} + +func (pace *pacemakerState[RI]) messageNewEpochWish(msg MessageNewEpochWish[RI], sender commontypes.OracleID) { + if pace.newEpochWishes[sender] < msg.Epoch { + pace.newEpochWishes[sender] = msg.Epoch + } + + var wishForEpoch uint64 + + // upon |{p_j ∈ P | newEpochWishes[j] > ne}| > f do + { + candidateEpochs := sortedGreaterThan(pace.newEpochWishes, pace.ne) + if len(candidateEpochs) > pace.config.F { + // ē ← max {e' | {p_j ∈ P | newEpochWishes[j] ≥ e' } > f} + wishForEpoch = candidateEpochs[len(candidateEpochs)-(pace.config.F+1)] + // ne ← max(ne, ē) is superfluous because ē is always greater or + // equal ne: this rule is only triggered if there are at least f+1 + // wishes greater than ne. ē is the greatest wish such that f+1 + // wishes are greater or equal ē. + + // see "if wishForEpoch != 0 {" for continuation below + } + } + + var switchToEpoch uint64 + + // upon |{p_j ∈ P | newEpochWishes[j] > e}| > 2f do + { + candidateEpochs := sortedGreaterThan(pace.newEpochWishes, pace.e) + if len(candidateEpochs) > 2*pace.config.F { + // ē ← max {e' | {p_j ∈ P | newEpochWishes[j] ≥ e' } > 2f} + // + // since candidateEpochs contains, in increasing order, the epochs + // from the received NewEpochWish messages, this value was sent by + // at least 2F+1 processes + switchToEpoch = candidateEpochs[len(candidateEpochs)-(2*pace.config.F+1)] + // see "if switchToEpoch != 0 {" for continuation below + } + } + + // persist wishForEpoch and switchToEpoch + if wishForEpoch != 0 || switchToEpoch != 0 { + persistState := PacemakerState{} + if wishForEpoch == 0 { + persistState.HighestSentNewEpochWish = pace.ne + } else { + persistState.HighestSentNewEpochWish = wishForEpoch + } + if switchToEpoch == 0 { + persistState.Epoch = pace.e + } else { + persistState.Epoch = switchToEpoch + } + + // needed so that persisted state is consistent with "ne ← max{ne, e}" + // statement in agreement rule + if persistState.HighestSentNewEpochWish < persistState.Epoch { + persistState.HighestSentNewEpochWish = persistState.Epoch + } + + if err := pace.persist(persistState); err != nil { + pace.logger.Error("could not persist pacemaker state in messageNewEpochWish", commontypes.LogFields{ + "error": err, + }) + } + } + + if wishForEpoch != 0 { + pace.ne = wishForEpoch + pace.sendNewEpochWish() + } + + if switchToEpoch != 0 { + pace.logger.Debug("moving to new epoch", commontypes.LogFields{ + "newEpoch": switchToEpoch, + }) + l := Leader(switchToEpoch, pace.config.N(), pace.config.LeaderSelectionKey()) + pace.e, pace.l = switchToEpoch, l // (e, l) ← (ē, leader(ē)) + if pace.ne < pace.e { // ne ← max{ne, e} + pace.ne = pace.e + } + pace.metrics.epoch.Set(float64(pace.e)) + pace.metrics.leader.Set(float64(pace.l)) + pace.tProgress = time.After(pace.config.DeltaProgress) // restart timer T_{progress} + + pace.notifyOutcomeGenerationOfNewEpoch = true // invoke event newEpochStart(e, l) + } +} + +func (pace *pacemakerState[RI]) persist(state PacemakerState) error { + writeCtx, writeCancel := context.WithTimeout(pace.ctx, pace.localConfig.DatabaseTimeout) + defer writeCancel() + err := pace.database.WritePacemakerState( + writeCtx, + pace.config.ConfigDigest, + state, + ) + if err != nil { + return fmt.Errorf("error while persisting pacemaker state: %w", err) + } + return nil +} + +// sortedGreaterThan returns the *sorted* elements of xs which are greater than y +func sortedGreaterThan(xs []uint64, y uint64) (rv []uint64) { + for _, x := range xs { + if x > y { + rv = append(rv, x) + } + } + sort.Slice(rv, func(i, j int) bool { return rv[i] < rv[j] }) + return rv +} + +// Leader will produce an oracle id for the given epoch. +func Leader(epoch uint64, n int, key [16]byte) (leader commontypes.OracleID) { + span := epoch / uint64(n) + epochInSpan := epoch % uint64(n) + + mac := hmac.New(sha256.New, key[:]) + _ = binary.Write(mac, binary.BigEndian, span) + + var permutationKey [16]byte + copy(permutationKey[:], mac.Sum(nil)) + pi := permutation.Permutation(n, permutationKey) + return commontypes.OracleID(pi[epochInSpan]) +} + +type eventTestBlock struct{} +type eventTestUnblock struct{} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/queue/queue.go b/offchainreporting2plus/internal/ocr3_1/protocol/queue/queue.go new file mode 100644 index 00000000..9a521740 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/queue/queue.go @@ -0,0 +1,68 @@ +package queue + +type Queue[T any] struct { + elements []T + maxCapacity *int +} + +// NewQueue returns a queue with infinite maxCapacity +func NewQueue[T any]() *Queue[T] { + return &Queue[T]{ + elements: make([]T, 0), + } +} + +// NewQueueWithMaxCapacity returns queue with maxCapacity cap. +// If the maxCapacity is reached the queue does not accept more elements. +func NewQueueWithMaxCapacity[T any](cap int) *Queue[T] { + return &Queue[T]{ + elements: make([]T, 0), + maxCapacity: &cap, + } +} + +func (q *Queue[T]) IsEmpty() bool { + return len(q.elements) == 0 +} + +func (q *Queue[T]) Size() int { + return len(q.elements) +} + +// Push returns false if the queue is at maxCapacity and the element is not added +func (q *Queue[T]) Push(element T) bool { + if q.maxCapacity == nil || len(q.elements) < *q.maxCapacity { + q.elements = append(q.elements, element) + return true + } + return false +} + +// Peek returns the first element without removing it. It returns false if the queue is empty. +func (q *Queue[T]) Peek() (*T, bool) { + if len(q.elements) == 0 { + return nil, false + } + return &q.elements[0], true +} + +// Pop returns the first element after removing it. It returns false if the queue is empty. +func (q *Queue[T]) Pop() (T, bool) { + if len(q.elements) == 0 { + var zero T + return zero, false + } + first := q.elements[0] + + q.elements = q.elements[1:len(q.elements)] + return first, true +} + +// PeekLast returns the last element without removing it. It returns false if the queue is empty. +func (q *Queue[T]) PeekLast() (T, bool) { + if len(q.elements) == 0 { + var zero T + return zero, false + } + return q.elements[len(q.elements)-1], true +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/report_attestation.go b/offchainreporting2plus/internal/ocr3_1/protocol/report_attestation.go new file mode 100644 index 00000000..879088a0 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/report_attestation.go @@ -0,0 +1,696 @@ +package protocol + +import ( + "context" + "crypto/rand" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/subprocesses" + "math" + "math/big" + "runtime" + "sort" + "sync" + "time" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/scheduler" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3config" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +func RunReportAttestation[RI any]( + ctx context.Context, + + chNetToReportAttestation <-chan MessageToReportAttestationWithSender[RI], + chOutcomeGenerationToReportAttestation <-chan EventToReportAttestation[RI], + chReportAttestationToStatePersistence chan<- EventToStatePersistence[RI], + chReportAttestationToTransmission chan<- EventToTransmission[RI], + config ocr3config.SharedConfig, + contractTransmitter ocr3types.ContractTransmitter[RI], + logger loghelper.LoggerWithContext, + netSender NetworkSender[RI], + onchainKeyring ocr3types.OnchainKeyring[RI], + reportingPlugin ocr3_1types.ReportingPlugin[RI], +) { + sched := scheduler.NewScheduler[EventMissingOutcome[RI]]() + defer sched.Close() + + newReportAttestationState(ctx, chNetToReportAttestation, + chOutcomeGenerationToReportAttestation, + chReportAttestationToStatePersistence, chReportAttestationToTransmission, + config, contractTransmitter, logger, netSender, onchainKeyring, + reportingPlugin, sched).run() +} + +const expiryMinRounds int = 10 +const expiryDuration = 1 * time.Minute +const expiryMaxRounds int = 50 + +const lookaheadMinRounds int = 4 +const lookaheadDuration = 30 * time.Second +const lookaheadMaxRounds int = 10 + +type reportAttestationState[RI any] struct { + ctx context.Context + subs subprocesses.Subprocesses + + chNetToReportAttestation <-chan MessageToReportAttestationWithSender[RI] + chOutcomeGenerationToReportAttestation <-chan EventToReportAttestation[RI] + chReportAttestationToStatePersistence chan<- EventToStatePersistence[RI] + chReportAttestationToTransmission chan<- EventToTransmission[RI] + config ocr3config.SharedConfig + contractTransmitter ocr3types.ContractTransmitter[RI] + logger loghelper.LoggerWithContext + netSender NetworkSender[RI] + onchainKeyring ocr3types.OnchainKeyring[RI] + reportingPlugin ocr3_1types.ReportingPlugin[RI] + + scheduler *scheduler.Scheduler[EventMissingOutcome[RI]] + chLocalEvent chan EventComputedReports[RI] + // reap() is used to prevent unbounded state growth of rounds + rounds map[uint64]*round[RI] + // highest sequence number for which we have attested reports + highestAttestedSeqNr uint64 + // highest sequence number for which we have received report signatures + // from each oracle + highestReportSignaturesSeqNr []uint64 +} + +type round[RI any] struct { + ctx context.Context // should always be initialized when a round[RI] is initiated + ctxCancel context.CancelFunc // should always be initialized when a round[RI] is initiated + verifiedCertifiedCommit *CertifiedCommittedReports[RI] // only stores certifiedCommit whose qc has been verified + reportsPlus *[]ocr3types.ReportPlus[RI] // cache result of ReportingPlugin.Reports(certifiedCommit.SeqNr, certifiedCommit.Outcome) + oracles []oracle // always initialized to be of length n + startedFetch bool + complete bool +} + +// oracle contains information about interactions with oracles (self & others) +type oracle struct { + signatures [][]byte + sentSignatures bool + validSignatures *bool + weRequested bool + theyServiced bool + weServiced bool +} + +func (repatt *reportAttestationState[RI]) run() { + repatt.logger.Info("ReportAttestation: running", nil) + + for { + select { + case ev := <-repatt.chLocalEvent: + ev.processReportAttestation(repatt) + case msg := <-repatt.chNetToReportAttestation: + msg.msg.processReportAttestation(repatt, msg.sender) + case ev := <-repatt.chOutcomeGenerationToReportAttestation: + ev.processReportAttestation(repatt) + case ev := <-repatt.scheduler.Scheduled(): + ev.processReportAttestation(repatt) + case <-repatt.ctx.Done(): + } + + // ensure prompt exit + select { + case <-repatt.ctx.Done(): + repatt.logger.Info("ReportAttestation: winding down", nil) + repatt.subs.Wait() + repatt.scheduler.Close() + repatt.logger.Info("ReportAttestation: exiting", nil) + return + default: + } + } +} + +func (repatt *reportAttestationState[RI]) messageReportSignatures( + msg MessageReportSignatures[RI], + sender commontypes.OracleID, +) { + repatt.logger.Debug("received MessageReportSignatures", commontypes.LogFields{ + "sender": sender, + "msgSeqNr": msg.SeqNr, + }) + if repatt.isBeyondExpiry(msg.SeqNr) { + repatt.logger.Debug("ignoring MessageReportSignatures for expired seqNr", commontypes.LogFields{ + "seqNr": msg.SeqNr, + "sender": sender, + }) + return + } + + if repatt.highestReportSignaturesSeqNr[sender] < msg.SeqNr { + repatt.highestReportSignaturesSeqNr[sender] = msg.SeqNr + } + + if repatt.isBeyondLookahead(msg.SeqNr) { + repatt.logger.Debug("ignoring MessageReportSignatures for seqNr beyond lookahead", commontypes.LogFields{ + "seqNr": msg.SeqNr, + "sender": sender, + }) + return + } + + if _, ok := repatt.rounds[msg.SeqNr]; !ok { + ctx, cancel := context.WithCancel(repatt.ctx) + repatt.rounds[msg.SeqNr] = &round[RI]{ + ctx, + cancel, + nil, + nil, + make([]oracle, repatt.config.N()), + false, + false, + } + } + + if repatt.rounds[msg.SeqNr].oracles[sender].sentSignatures { + repatt.logger.Debug("ignoring MessageReportSignatures with duplicate signature", commontypes.LogFields{ + "seqNr": msg.SeqNr, + "sender": sender, + }) + return + } + + repatt.rounds[msg.SeqNr].oracles[sender].signatures = msg.ReportSignatures + repatt.rounds[msg.SeqNr].oracles[sender].sentSignatures = true + + repatt.tryComplete(msg.SeqNr) +} + +func (repatt *reportAttestationState[RI]) eventMissingOutcome(ev EventMissingOutcome[RI]) { + repatt.logger.Debug("received EventMissingOutcome", commontypes.LogFields{ + "msgSeqNr": ev.SeqNr, + }) + if repatt.rounds[ev.SeqNr].verifiedCertifiedCommit != nil { + repatt.logger.Debug("dropping EventMissingOutcome, already have Outcome", commontypes.LogFields{ + "seqNr": ev.SeqNr, + }) + return + } + + repatt.tryRequestCertifiedCommit(ev.SeqNr) +} + +func (repatt *reportAttestationState[RI]) messageCertifiedCommitRequest(msg MessageCertifiedCommitRequest[RI], sender commontypes.OracleID) { + repatt.logger.Debug("received MessageCertifiedCommitRequest", commontypes.LogFields{ + "sender": sender, + "msgSeqNr": msg.SeqNr, + }) + if repatt.rounds[msg.SeqNr] == nil || repatt.rounds[msg.SeqNr].verifiedCertifiedCommit == nil { + repatt.logger.Debug("dropping MessageCertifiedCommitRequest for outcome with unknown certified commit", commontypes.LogFields{ + "seqNr": msg.SeqNr, + "sender": sender, + }) + return + } + + if repatt.rounds[msg.SeqNr].oracles[sender].weServiced { + repatt.logger.Warn("dropping duplicate MessageCertifiedCommitRequest", commontypes.LogFields{ + "seqNr": msg.SeqNr, + "sender": sender, + }) + return + } + + repatt.rounds[msg.SeqNr].oracles[sender].weServiced = true + + repatt.logger.Debug("sending MessageCertifiedCommit", commontypes.LogFields{ + "seqNr": msg.SeqNr, + "to": sender, + }) + repatt.netSender.SendTo(MessageCertifiedCommit[RI]{*repatt.rounds[msg.SeqNr].verifiedCertifiedCommit}, sender) +} + +func (repatt *reportAttestationState[RI]) messageCertifiedCommit(msg MessageCertifiedCommit[RI], sender commontypes.OracleID) { + repatt.logger.Debug("received MessageCertifiedCommit", commontypes.LogFields{ + "sender": sender, + "msgSeqNr": msg.CertifiedCommittedReports.SeqNr, + }) + if repatt.rounds[msg.CertifiedCommittedReports.SeqNr] == nil { + repatt.logger.Warn("dropping MessageCertifiedCommit for unknown seqNr", commontypes.LogFields{ + "seqNr": msg.CertifiedCommittedReports.SeqNr, + "sender": sender, + }) + return + } + + oracle := &repatt.rounds[msg.CertifiedCommittedReports.SeqNr].oracles[sender] + if !(oracle.weRequested && !oracle.theyServiced) { + repatt.logger.Warn("dropping unexpected MessageCertifiedCommit", commontypes.LogFields{ + "seqNr": msg.CertifiedCommittedReports.SeqNr, + "sender": sender, + "weRequested": oracle.weRequested, + "theyServiced": oracle.theyServiced, + }) + return + } + + oracle.theyServiced = true + + if repatt.rounds[msg.CertifiedCommittedReports.SeqNr].verifiedCertifiedCommit != nil { + repatt.logger.Debug("dropping redundant MessageCertifiedCommit", commontypes.LogFields{ + "seqNr": msg.CertifiedCommittedReports.SeqNr, + "sender": sender, + }) + return + } + + if err := msg.CertifiedCommittedReports.Verify(repatt.config.ConfigDigest, repatt.config.OracleIdentities, repatt.config.ByzQuorumSize()); err != nil { + repatt.logger.Warn("dropping MessageCertifiedCommit with invalid certified commit", commontypes.LogFields{ + "seqNr": msg.CertifiedCommittedReports.SeqNr, + "sender": sender, + "err": err, + }) + return + } + + repatt.logger.Debug("received valid MessageCertifiedCommit", commontypes.LogFields{ + "seqNr": msg.CertifiedCommittedReports.SeqNr, + "sender": sender, + }) + + repatt.receivedVerifiedCertifiedCommit(msg.CertifiedCommittedReports) + + //select { + //case repatt.chReportAttestationToOutcomeGeneration <- EventCertifiedCommit[RI]{msg.CertifiedCommit}: + //case <-repatt.ctx.Done(): + //} +} + +func (repatt *reportAttestationState[RI]) tryRequestCertifiedCommit(seqNr uint64) { + candidates := make([]commontypes.OracleID, 0, repatt.config.N()) + for oracleID, oracle := range repatt.rounds[seqNr].oracles { + // avoid duplicate requests + if oracle.weRequested { + continue + } + // avoid requesting from oracles that haven't sent MessageReportSignatures + if len(oracle.signatures) == 0 { + continue + } + candidates = append(candidates, commontypes.OracleID(oracleID)) + } + + if len(candidates) == 0 { + + return + } + + randomIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(candidates)))) + if err != nil { + repatt.logger.Critical("unexpected error returned by rand.Int", commontypes.LogFields{ + "error": err, + }) + return + } + randomCandidate := candidates[int(randomIndex.Int64())] + repatt.rounds[seqNr].oracles[randomCandidate].weRequested = true + repatt.logger.Debug("sending MessageCertifiedCommitRequest", commontypes.LogFields{ + "seqNr": seqNr, + "to": randomCandidate, + }) + repatt.netSender.SendTo(MessageCertifiedCommitRequest[RI]{seqNr}, randomCandidate) + repatt.scheduler.ScheduleDelay(EventMissingOutcome[RI]{seqNr}, repatt.config.DeltaCertifiedCommitRequest) +} + +func (repatt *reportAttestationState[RI]) tryComplete(seqNr uint64) { + if repatt.rounds[seqNr].complete { + repatt.logger.Debug("cannot complete, already completed", commontypes.LogFields{ + "seqNr": seqNr, + }) + return + } + + if repatt.rounds[seqNr].verifiedCertifiedCommit == nil { + oraclesThatSentSignatures := 0 + for _, oracle := range repatt.rounds[seqNr].oracles { + if !oracle.sentSignatures { + continue + } + oraclesThatSentSignatures++ + } + + if oraclesThatSentSignatures <= repatt.config.F { + repatt.logger.Debug("cannot complete, missing CertifiedCommit and signatures", commontypes.LogFields{ + "oraclesThatSentNonemptySignatures": oraclesThatSentSignatures, + "seqNr": seqNr, + "threshold": repatt.config.F + 1, + }) + } else if !repatt.rounds[seqNr].startedFetch { + repatt.logger.Debug("we have received f+1 MessageReportSignatures messages but we are still missing CertifiedCommit", commontypes.LogFields{ + "seqNr": seqNr, + }) + repatt.rounds[seqNr].startedFetch = true + repatt.scheduler.ScheduleDelay(EventMissingOutcome[RI]{seqNr}, repatt.config.DeltaCertifiedCommitRequest) + select { + case repatt.chReportAttestationToStatePersistence <- EventStateSyncRequest[RI]{seqNr}: + case <-repatt.ctx.Done(): + } + } + return + } + if repatt.rounds[seqNr].reportsPlus == nil { + repatt.logger.Debug("cannot complete, reportsPlus not computed yet", commontypes.LogFields{ + "seqNr": seqNr, + }) + return + } + + reportsPlus := *repatt.rounds[seqNr].reportsPlus + + goodSigs := 0 + var aossPerReport [][]types.AttributedOnchainSignature = make([][]types.AttributedOnchainSignature, len(reportsPlus)) + for oracleID := range repatt.rounds[seqNr].oracles { + oracle := &repatt.rounds[seqNr].oracles[oracleID] + if len(oracle.signatures) == 0 { + continue + } + if oracle.validSignatures == nil { + validSignatures := repatt.verifySignatures( + repatt.config.OracleIdentities[oracleID].OnchainPublicKey, + seqNr, + reportsPlus, + oracle.signatures, + ) + oracle.validSignatures = &validSignatures + if !validSignatures { + // Other less common causes include actually invalid signatures. + repatt.logger.Warn("report signatures failed to verify. This is commonly caused by non-determinism in the ReportingPlugin", commontypes.LogFields{ + "sender": oracleID, + "seqNr": seqNr, + "signaturesLen": len(oracle.signatures), + "reportsLen": len(reportsPlus), + }) + } + } + if oracle.validSignatures != nil && *oracle.validSignatures { + goodSigs++ + + for i := range reportsPlus { + aossPerReport[i] = append(aossPerReport[i], types.AttributedOnchainSignature{ + oracle.signatures[i], + commontypes.OracleID(oracleID), + }) + } + } + if goodSigs > repatt.config.F { + break + } + } + + if goodSigs <= repatt.config.F { + repatt.logger.Debug("cannot complete, insufficient number of signatures", commontypes.LogFields{ + "seqNr": seqNr, + "goodSigs": goodSigs, + "threshold": repatt.config.F + 1, + }) + return + } + + if repatt.highestAttestedSeqNr < seqNr { + repatt.highestAttestedSeqNr = seqNr + } + + repatt.rounds[seqNr].complete = true + + repatt.logger.Debug("sending attested reports to transmission protocol", commontypes.LogFields{ + "seqNr": seqNr, + "reports": len(reportsPlus), + }) + + for i := range reportsPlus { + select { + case repatt.chReportAttestationToTransmission <- EventAttestedReport[RI]{ + seqNr, + i, + AttestedReportMany[RI]{ + reportsPlus[i].ReportWithInfo, + aossPerReport[i], + }, + reportsPlus[i].TransmissionScheduleOverride, + }: + case <-repatt.ctx.Done(): + } + } + + repatt.reap() +} + +func (repatt *reportAttestationState[RI]) verifySignatures(publicKey types.OnchainPublicKey, seqNr uint64, reportsPlus []ocr3types.ReportPlus[RI], signatures [][]byte) bool { + if len(reportsPlus) != len(signatures) { + return false + } + + n := runtime.GOMAXPROCS(0) + if (len(reportsPlus)+3)/4 < n { + n = (len(reportsPlus) + 3) / 4 + } + + var wg sync.WaitGroup + wg.Add(n) + + var mutex sync.Mutex + allValid := true + + for k := 0; k < n; k++ { + go func() { + defer wg.Done() + for i := k; i < len(reportsPlus); i += n { + mutex.Lock() + allValidCopy := allValid + mutex.Unlock() + + if !allValidCopy { + return + } + + if !repatt.onchainKeyring.Verify(publicKey, repatt.config.ConfigDigest, seqNr, reportsPlus[i].ReportWithInfo, signatures[i]) { + mutex.Lock() + allValid = false + mutex.Unlock() + return + } + } + }() + } + + wg.Wait() + + return allValid +} + +func (repatt *reportAttestationState[RI]) eventCertifiedCommit(ev EventCertifiedCommit[RI]) { + repatt.receivedVerifiedCertifiedCommit(ev.CertifiedCommittedReports) +} + +func (repatt *reportAttestationState[RI]) receivedVerifiedCertifiedCommit(certifiedCommit CertifiedCommittedReports[RI]) { + if repatt.rounds[certifiedCommit.SeqNr] != nil && repatt.rounds[certifiedCommit.SeqNr].verifiedCertifiedCommit != nil { + repatt.logger.Debug("dropping redundant CertifiedCommit", commontypes.LogFields{ + "seqNr": certifiedCommit.SeqNr, + }) + return + } + + if _, ok := repatt.rounds[certifiedCommit.SeqNr]; !ok { + ctx, cancel := context.WithCancel(repatt.ctx) + repatt.rounds[certifiedCommit.SeqNr] = &round[RI]{ + ctx, + cancel, + nil, + nil, + make([]oracle, repatt.config.N()), + false, + false, + } + } + + repatt.rounds[certifiedCommit.SeqNr].verifiedCertifiedCommit = &certifiedCommit + + { + ctx := repatt.rounds[certifiedCommit.SeqNr].ctx + repatt.subs.Go(func() { + repatt.backgroundComputeReports(ctx, certifiedCommit) + }) + } +} + +func (repatt *reportAttestationState[RI]) backgroundComputeReports(ctx context.Context, verifiedCertifiedCommit CertifiedCommittedReports[RI]) { + reportsPlus, ok := common.CallPluginFromBackground( + ctx, + repatt.logger, + commontypes.LogFields{"seqNr": verifiedCertifiedCommit.SeqNr}, + "Reports", + 0, // Reports is a pure function and should finish "instantly" + func(ctx context.Context) ([]ocr3types.ReportPlus[RI], error) { + return repatt.reportingPlugin.Reports(ctx, verifiedCertifiedCommit.SeqNr, verifiedCertifiedCommit.ReportsPlusPrecursor) + }, + ) + if !ok { + return + } + + repatt.logger.Debug("successfully invoked ReportingPlugin.Reports", commontypes.LogFields{ + "seqNr": verifiedCertifiedCommit.SeqNr, + "reports": len(reportsPlus), + }) + + select { + case repatt.chLocalEvent <- EventComputedReports[RI]{verifiedCertifiedCommit.SeqNr, reportsPlus}: + case <-ctx.Done(): + return + } +} + +func (repatt *reportAttestationState[RI]) eventComputedReports(ev EventComputedReports[RI]) { + if repatt.rounds[ev.SeqNr] == nil { + repatt.logger.Debug("discarding EventComputedReports from old round", commontypes.LogFields{ + "evSeqNr": ev.SeqNr, + "highestAttestedSeqNr": repatt.highestAttestedSeqNr, + }) + return + } + + if len(ev.ReportsPlus) == 0 { + repatt.logger.Info("ReportingPlugin.Reports returned no reports", commontypes.LogFields{ + "seqNr": ev.SeqNr, + }) + } + + repatt.rounds[ev.SeqNr].reportsPlus = &ev.ReportsPlus + + var sigs [][]byte + for i, reportPlus := range ev.ReportsPlus { + sig, err := repatt.onchainKeyring.Sign(repatt.config.ConfigDigest, ev.SeqNr, reportPlus.ReportWithInfo) + if err != nil { + repatt.logger.Error("error while signing report", commontypes.LogFields{ + "seqNr": ev.SeqNr, + "index": i, + "error": err, + }) + return + } + sigs = append(sigs, sig) + } + + repatt.logger.Debug("broadcasting MessageReportSignatures", commontypes.LogFields{ + "seqNr": ev.SeqNr, + }) + + repatt.netSender.Broadcast(MessageReportSignatures[RI]{ + ev.SeqNr, + sigs, + }) + + // no need to call tryComplete since receipt of our own MessageReportSignatures will do so +} + +func (repatt *reportAttestationState[RI]) isBeyondExpiry(seqNr uint64) bool { + highest := repatt.highestAttestedSeqNr + expiry := uint64(repatt.expiryRounds()) + if highest <= expiry { + return false + } + return seqNr < highest-expiry +} + +func (repatt *reportAttestationState[RI]) isBeyondLookahead(seqNr uint64) bool { + highestReportSignaturesSeqNr := append([]uint64{}, repatt.highestReportSignaturesSeqNr...) + sort.Slice(highestReportSignaturesSeqNr, func(i, j int) bool { + return highestReportSignaturesSeqNr[i] > highestReportSignaturesSeqNr[j] + }) + highest := highestReportSignaturesSeqNr[repatt.config.F] // (f+1)th largest seqNr + lookahead := uint64(repatt.lookaheadRounds()) + if seqNr <= lookahead { + return false + } + return highest < seqNr-lookahead +} + +// reap expired entries from repatt.finalized to prevent unbounded state growth +func (repatt *reportAttestationState[RI]) reap() { + maxActiveRoundCount := repatt.expiryRounds() + repatt.lookaheadRounds() + // only reap if more than ~ a third of the rounds can be discarded + if 3*len(repatt.rounds) <= 4*maxActiveRoundCount { + return + } + // A long time ago in a galaxy far, far away, Go used to leak memory when + // repeatedly adding and deleting from the same map without ever exceeding + // some maximum length. Fortunately, this is no longer the case + // https://go-review.googlesource.com/c/go/+/25049/ + for seqNr := range repatt.rounds { + if repatt.isBeyondExpiry(seqNr) { + repatt.rounds[seqNr].ctxCancel() + delete(repatt.rounds, seqNr) + } + } +} + +// The age (denoted in rounds) after which a report is considered expired and +// will automatically be dropped +func (repatt *reportAttestationState[RI]) expiryRounds() int { + return repatt.roundWindowSize(expiryMinRounds, expiryMaxRounds, expiryDuration) +} + +// The lookahead (denoted in rounds) after which a report is considered too far in the future and +// will automatically be dropped +func (repatt *reportAttestationState[RI]) lookaheadRounds() int { + return repatt.roundWindowSize(lookaheadMinRounds, lookaheadMaxRounds, lookaheadDuration) +} + +func (repatt *reportAttestationState[RI]) roundWindowSize(minWindowSize int, maxWindowSize int, windowDuration time.Duration) int { + // number of rounds in a window of duration expirationAgeDuration + size := math.Ceil(windowDuration.Seconds() / repatt.config.MinRoundInterval().Seconds()) + + if size < float64(minWindowSize) { + size = float64(minWindowSize) + } + if math.IsNaN(size) || size > float64(maxWindowSize) { + size = float64(maxWindowSize) + } + + return int(math.Ceil(size)) +} + +func newReportAttestationState[RI any]( + ctx context.Context, + + chNetToReportAttestation <-chan MessageToReportAttestationWithSender[RI], + chOutcomeGenerationToReportAttestation <-chan EventToReportAttestation[RI], + chReportAttestationToStatePersistence chan<- EventToStatePersistence[RI], + chReportAttestationToTransmission chan<- EventToTransmission[RI], + config ocr3config.SharedConfig, + contractTransmitter ocr3types.ContractTransmitter[RI], + logger loghelper.LoggerWithContext, + netSender NetworkSender[RI], + onchainKeyring ocr3types.OnchainKeyring[RI], + reportingPlugin ocr3_1types.ReportingPlugin[RI], + sched *scheduler.Scheduler[EventMissingOutcome[RI]], +) *reportAttestationState[RI] { + return &reportAttestationState[RI]{ + ctx, + subprocesses.Subprocesses{}, + + chNetToReportAttestation, + chOutcomeGenerationToReportAttestation, + chReportAttestationToStatePersistence, + chReportAttestationToTransmission, + config, + contractTransmitter, + logger.MakeUpdated(commontypes.LogFields{"proto": "repatt"}), + netSender, + onchainKeyring, + reportingPlugin, + + sched, + make(chan EventComputedReports[RI]), + map[uint64]*round[RI]{}, + 0, + make([]uint64, config.N()), + } +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/round_context.go b/offchainreporting2plus/internal/ocr3_1/protocol/round_context.go new file mode 100644 index 00000000..0bdbcd59 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/round_context.go @@ -0,0 +1,7 @@ +package protocol + +type RoundContext struct { + SeqNr uint64 + Epoch uint64 + Round uint64 +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/signed_data.go b/offchainreporting2plus/internal/ocr3_1/protocol/signed_data.go new file mode 100644 index 00000000..a98ec216 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/signed_data.go @@ -0,0 +1,770 @@ +package protocol + +import ( + "crypto/ed25519" + "crypto/sha256" + "encoding/binary" + "fmt" + "hash" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/byzquorum" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3_1/blobtypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +// Returns a byte slice whose first four bytes are the string "ocr3" and the rest +// of which is the sum returned by h. Used for domain separation vs ocr2, where +// we just directly sign sha256 hashes. +// +// Any signatures made with the OffchainKeyring should use ocr3_1DomainSeparatedSum! +func ocr3_1DomainSeparatedSum(h hash.Hash) []byte { + result := make([]byte, 0, 6+32) + result = append(result, []byte("ocr3.1")...) + return h.Sum(result) +} + +const signedObservationDomainSeparator = "ocr3.1 SignedObservation" + +type SignedObservation struct { + Observation types.Observation + Signature []byte +} + +func MakeSignedObservation( + ogid OutcomeGenerationID, + seqNr uint64, + query types.Query, + observation types.Observation, + signer func(msg []byte) (sig []byte, err error), +) ( + SignedObservation, + error, +) { + payload := signedObservationMsg(ogid, seqNr, query, observation) + sig, err := signer(payload) + if err != nil { + return SignedObservation{}, err + } + return SignedObservation{observation, sig}, nil +} + +func (so SignedObservation) Verify(ogid OutcomeGenerationID, seqNr uint64, query types.Query, publicKey types.OffchainPublicKey) error { + pk := ed25519.PublicKey(publicKey[:]) + // should never trigger since types.OffchainPublicKey is an array with length ed25519.PublicKeySize + if len(pk) != ed25519.PublicKeySize { + return fmt.Errorf("ed25519 public key size mismatch, expected %v but got %v", ed25519.PublicKeySize, len(pk)) + } + + ok := ed25519.Verify(pk, signedObservationMsg(ogid, seqNr, query, so.Observation), so.Signature) + if !ok { + return fmt.Errorf("SignedObservation has invalid signature") + } + + return nil +} + +func signedObservationMsg(ogid OutcomeGenerationID, seqNr uint64, query types.Query, observation types.Observation) []byte { + h := sha256.New() + + _, _ = h.Write([]byte(signedObservationDomainSeparator)) + + // ogid + _, _ = h.Write(ogid.ConfigDigest[:]) + _ = binary.Write(h, binary.BigEndian, ogid.Epoch) + + // seqNr + _, _ = h.Write(ogid.ConfigDigest[:]) + _ = binary.Write(h, binary.BigEndian, seqNr) + + // query + _ = binary.Write(h, binary.BigEndian, uint64(len(query))) + _, _ = h.Write(query) + + // observation + _ = binary.Write(h, binary.BigEndian, uint64(len(observation))) + _, _ = h.Write(observation) + + return ocr3_1DomainSeparatedSum(h) +} + +type AttributedSignedObservation struct { + SignedObservation SignedObservation + Observer commontypes.OracleID +} + +type StateTransitionInputsDigest [32]byte + +func MakeStateTransitionInputsDigest( + ogid OutcomeGenerationID, + seqNr uint64, + query types.Query, + attributedObservations []types.AttributedObservation, +) StateTransitionInputsDigest { + h := sha256.New() + + _, _ = h.Write(ogid.ConfigDigest[:]) + _ = binary.Write(h, binary.BigEndian, ogid.Epoch) + + _ = binary.Write(h, binary.BigEndian, seqNr) + + _ = binary.Write(h, binary.BigEndian, uint64(len(query))) + _, _ = h.Write(query) + + _ = binary.Write(h, binary.BigEndian, uint64(len(attributedObservations))) + for _, ao := range attributedObservations { + + _ = binary.Write(h, binary.BigEndian, uint64(len(ao.Observation))) + _, _ = h.Write(ao.Observation) + + _ = binary.Write(h, binary.BigEndian, uint64(ao.Observer)) + } + + var result StateTransitionInputsDigest + h.Sum(result[:0]) + return result +} + +type StateTransitionOutputDigest [32]byte + +func MakeStateTransitionOutputDigest(ogid OutcomeGenerationID, seqNr uint64, output []KeyValuePair) StateTransitionOutputDigest { + h := sha256.New() + + _, _ = h.Write(ogid.ConfigDigest[:]) + _ = binary.Write(h, binary.BigEndian, ogid.Epoch) + + _ = binary.Write(h, binary.BigEndian, seqNr) + + _ = binary.Write(h, binary.BigEndian, uint64(len(output))) + for _, o := range output { + + _ = binary.Write(h, binary.BigEndian, uint64(len(o.Key))) + _, _ = h.Write(o.Key) + + _ = binary.Write(h, binary.BigEndian, uint64(len(o.Value))) + _, _ = h.Write(o.Value) + } + + var result StateTransitionOutputDigest + h.Sum(result[:0]) + return result +} + +type ReportsPlusPrecursorDigest [32]byte + +func MakeReportsPlusPrecursorDigest(ogid OutcomeGenerationID, seqNr uint64, precursor ocr3_1types.ReportsPlusPrecursor) ReportsPlusPrecursorDigest { + h := sha256.New() + + _, _ = h.Write(ogid.ConfigDigest[:]) + _ = binary.Write(h, binary.BigEndian, ogid.Epoch) + + _ = binary.Write(h, binary.BigEndian, seqNr) + + _ = binary.Write(h, binary.BigEndian, uint64(len(precursor))) + _, _ = h.Write(precursor) + + var result ReportsPlusPrecursorDigest + h.Sum(result[:0]) + return result +} + +const prepareSignatureDomainSeparator = "ocr3.1 PrepareSignature" + +type PrepareSignature []byte + +func MakePrepareSignature( + ogid OutcomeGenerationID, + seqNr uint64, + inputsDigest StateTransitionInputsDigest, + outputDigest StateTransitionOutputDigest, + reportsPlusPrecursorDigest ReportsPlusPrecursorDigest, + signer func(msg []byte) ([]byte, error), +) (PrepareSignature, error) { + return signer(prepareSignatureMsg(ogid, seqNr, inputsDigest, outputDigest, reportsPlusPrecursorDigest)) +} + +func (sig PrepareSignature) Verify( + ogid OutcomeGenerationID, + seqNr uint64, + inputsDigest StateTransitionInputsDigest, + outputDigest StateTransitionOutputDigest, + reportsPlusPrecursorDigest ReportsPlusPrecursorDigest, + publicKey types.OffchainPublicKey, +) error { + pk := ed25519.PublicKey(publicKey[:]) + + if len(pk) != ed25519.PublicKeySize { + return fmt.Errorf("ed25519 public key size mismatch, expected %v but got %v", ed25519.PublicKeySize, len(pk)) + } + msg := prepareSignatureMsg(ogid, seqNr, inputsDigest, outputDigest, reportsPlusPrecursorDigest) + ok := ed25519.Verify(pk, prepareSignatureMsg(ogid, seqNr, inputsDigest, outputDigest, reportsPlusPrecursorDigest), sig) + if !ok { + // Other less common causes include leader equivocation or actually invalid signatures. + return fmt.Errorf("PrepareSignature failed to verify. This is commonly caused by non-determinism in the ReportingPlugin msg: %x, sig: %x", msg, sig) + } + + return nil +} + +func prepareSignatureMsg( + ogid OutcomeGenerationID, + seqNr uint64, + inputsDigest StateTransitionInputsDigest, + outputDigest StateTransitionOutputDigest, + reportsPlusPrecursorDigest ReportsPlusPrecursorDigest, +) []byte { + h := sha256.New() + + _, _ = h.Write([]byte(prepareSignatureDomainSeparator)) + + _, _ = h.Write(ogid.ConfigDigest[:]) + _ = binary.Write(h, binary.BigEndian, ogid.Epoch) + + _ = binary.Write(h, binary.BigEndian, seqNr) + + _, _ = h.Write(inputsDigest[:]) + + _, _ = h.Write(outputDigest[:]) + + _, _ = h.Write(reportsPlusPrecursorDigest[:]) + + return ocr3_1DomainSeparatedSum(h) +} + +type AttributedPrepareSignature struct { + Signature PrepareSignature + Signer commontypes.OracleID +} + +const commitSignatureDomainSeparator = "ocr3.1 CommitSignature" + +type CommitSignature []byte + +func MakeCommitSignature( + ogid OutcomeGenerationID, + seqNr uint64, + inputsDigest StateTransitionInputsDigest, + outputDigest StateTransitionOutputDigest, + reportsPlusPrecursorDigest ReportsPlusPrecursorDigest, + signer func(msg []byte) ([]byte, error), +) (CommitSignature, error) { + return signer(commitSignatureMsg(ogid, seqNr, inputsDigest, outputDigest, reportsPlusPrecursorDigest)) +} + +func (sig CommitSignature) Verify( + ogid OutcomeGenerationID, + seqNr uint64, + inputsDigest StateTransitionInputsDigest, + outputDigest StateTransitionOutputDigest, + reportsPlusPrecursorDigest ReportsPlusPrecursorDigest, + publicKey types.OffchainPublicKey, +) error { + pk := ed25519.PublicKey(publicKey[:]) + + if len(pk) != ed25519.PublicKeySize { + return fmt.Errorf("ed25519 public key size mismatch, expected %v but got %v", ed25519.PublicKeySize, len(pk)) + } + + ok := ed25519.Verify(pk, commitSignatureMsg(ogid, seqNr, inputsDigest, outputDigest, reportsPlusPrecursorDigest), sig) + if !ok { + return fmt.Errorf("CommitSignature failed to verify") + } + + return nil +} + +func commitSignatureMsg( + ogid OutcomeGenerationID, + seqNr uint64, + inputsDigest StateTransitionInputsDigest, + outputDigest StateTransitionOutputDigest, + reportsPlusPrecursorDigest ReportsPlusPrecursorDigest, +) []byte { + h := sha256.New() + + _, _ = h.Write([]byte(commitSignatureDomainSeparator)) + + _, _ = h.Write(ogid.ConfigDigest[:]) + _ = binary.Write(h, binary.BigEndian, ogid.Epoch) + + _ = binary.Write(h, binary.BigEndian, seqNr) + + _, _ = h.Write(inputsDigest[:]) + + _, _ = h.Write(outputDigest[:]) + + _, _ = h.Write(reportsPlusPrecursorDigest[:]) + + return ocr3_1DomainSeparatedSum(h) +} + +type AttributedCommitSignature struct { + Signature CommitSignature + Signer commontypes.OracleID +} + +type HighestCertifiedTimestamp struct { + SeqNr uint64 + CommittedElsePrepared bool + Epoch uint64 +} + +func (t HighestCertifiedTimestamp) Less(t2 HighestCertifiedTimestamp) bool { + return t.SeqNr < t2.SeqNr || + t.SeqNr == t2.SeqNr && !t.CommittedElsePrepared && t2.CommittedElsePrepared || + t.SeqNr == t2.SeqNr && t.CommittedElsePrepared == t2.CommittedElsePrepared && t.Epoch < t2.Epoch +} + +const signedHighestCertifiedTimestampDomainSeparator = "ocr3.1 SignedHighestCertifiedTimestamp" + +type SignedHighestCertifiedTimestamp struct { + HighestCertifiedTimestamp HighestCertifiedTimestamp + Signature []byte +} + +func MakeSignedHighestCertifiedTimestamp( + ogid OutcomeGenerationID, + highestCertifiedTimestamp HighestCertifiedTimestamp, + signer func(msg []byte) ([]byte, error), +) (SignedHighestCertifiedTimestamp, error) { + sig, err := signer(signedHighestCertifiedTimestampMsg(ogid, highestCertifiedTimestamp)) + if err != nil { + return SignedHighestCertifiedTimestamp{}, err + } + + return SignedHighestCertifiedTimestamp{ + highestCertifiedTimestamp, + sig, + }, nil +} + +func (shct *SignedHighestCertifiedTimestamp) Verify(ogid OutcomeGenerationID, publicKey types.OffchainPublicKey) error { + pk := ed25519.PublicKey(publicKey[:]) + + if len(pk) != ed25519.PublicKeySize { + return fmt.Errorf("ed25519 public key size mismatch, expected %v but got %v", ed25519.PublicKeySize, len(pk)) + } + + ok := ed25519.Verify(pk, signedHighestCertifiedTimestampMsg(ogid, shct.HighestCertifiedTimestamp), shct.Signature) + if !ok { + return fmt.Errorf("SignedHighestCertifiedTimestamp signature failed to verify") + } + + return nil +} + +func signedHighestCertifiedTimestampMsg( + ogid OutcomeGenerationID, + highestCertifiedTimestamp HighestCertifiedTimestamp, +) []byte { + h := sha256.New() + + _, _ = h.Write([]byte(signedHighestCertifiedTimestampDomainSeparator)) + + _, _ = h.Write(ogid.ConfigDigest[:]) + _ = binary.Write(h, binary.BigEndian, ogid.Epoch) + + _ = binary.Write(h, binary.BigEndian, highestCertifiedTimestamp.SeqNr) + + var committedElsePreparedByte uint8 + if highestCertifiedTimestamp.CommittedElsePrepared { + committedElsePreparedByte = 1 + } else { + committedElsePreparedByte = 0 + } + _, _ = h.Write([]byte{byte(committedElsePreparedByte)}) + + _ = binary.Write(h, binary.BigEndian, highestCertifiedTimestamp.Epoch) + + return ocr3_1DomainSeparatedSum(h) +} + +type AttributedSignedHighestCertifiedTimestamp struct { + SignedHighestCertifiedTimestamp SignedHighestCertifiedTimestamp + Signer commontypes.OracleID +} + +type EpochStartProof struct { + HighestCertified CertifiedPrepareOrCommit + HighestCertifiedProof []AttributedSignedHighestCertifiedTimestamp +} + +func (qc *EpochStartProof) Verify( + ogid OutcomeGenerationID, + oracleIdentities []config.OracleIdentity, + byzQuorumSize int, +) error { + if byzQuorumSize != len(qc.HighestCertifiedProof) { + return fmt.Errorf("wrong length of HighestCertifiedProof, expected %v for byz. quorum and got %v", byzQuorumSize, len(qc.HighestCertifiedProof)) + } + + maximumTimestamp := qc.HighestCertifiedProof[0].SignedHighestCertifiedTimestamp.HighestCertifiedTimestamp + + seen := make(map[commontypes.OracleID]bool) + for i, ashct := range qc.HighestCertifiedProof { + if seen[ashct.Signer] { + return fmt.Errorf("duplicate signature by %v", ashct.Signer) + } + seen[ashct.Signer] = true + if !(0 <= int(ashct.Signer) && int(ashct.Signer) < len(oracleIdentities)) { + return fmt.Errorf("signer out of bounds: %v", ashct.Signer) + } + if err := ashct.SignedHighestCertifiedTimestamp.Verify(ogid, oracleIdentities[ashct.Signer].OffchainPublicKey); err != nil { + return fmt.Errorf("%v-th signature by %v-th oracle with pubkey %x does not verify: %w", i, ashct.Signer, oracleIdentities[ashct.Signer].OffchainPublicKey, err) + } + + if maximumTimestamp.Less(ashct.SignedHighestCertifiedTimestamp.HighestCertifiedTimestamp) { + maximumTimestamp = ashct.SignedHighestCertifiedTimestamp.HighestCertifiedTimestamp + } + } + + if qc.HighestCertified.Timestamp() != maximumTimestamp { + return fmt.Errorf("mismatch between timestamp of HighestCertified (%v) and the max from HighestCertifiedProof (%v)", qc.HighestCertified.Timestamp(), maximumTimestamp) + } + + if err := qc.HighestCertified.Verify(ogid.ConfigDigest, oracleIdentities, byzQuorumSize); err != nil { + return fmt.Errorf("failed to verify HighestCertified: %w", err) + } + + return nil +} + +type CertifiedPrepareOrCommit interface { + isCertifiedPrepareOrCommit() + Epoch() uint64 + SeqNr() uint64 + Timestamp() HighestCertifiedTimestamp + IsGenesis() bool + Verify( + _ types.ConfigDigest, + _ []config.OracleIdentity, + byzQuorumSize int, + ) error + CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool +} + +var _ CertifiedPrepareOrCommit = &CertifiedPrepare{} + +type CertifiedPrepare struct { + PrepareEpoch uint64 + PrepareSeqNr uint64 + StateTransitionInputsDigest StateTransitionInputsDigest + StateTransitionOutputs StateTransitionOutputs + ReportsPlusPrecursor ocr3_1types.ReportsPlusPrecursor + PrepareQuorumCertificate []AttributedPrepareSignature +} + +func (hc *CertifiedPrepare) isCertifiedPrepareOrCommit() {} + +func (hc *CertifiedPrepare) Epoch() uint64 { + return hc.PrepareEpoch +} + +func (hc *CertifiedPrepare) SeqNr() uint64 { + return hc.PrepareSeqNr +} + +func (hc *CertifiedPrepare) Timestamp() HighestCertifiedTimestamp { + return HighestCertifiedTimestamp{ + hc.SeqNr(), + false, + hc.Epoch(), + } +} + +func (hc *CertifiedPrepare) IsGenesis() bool { + return false +} + +func (hc *CertifiedPrepare) Verify( + configDigest types.ConfigDigest, + oracleIdentities []config.OracleIdentity, + byzQuorumSize int, +) error { + if byzQuorumSize != len(hc.PrepareQuorumCertificate) { + return fmt.Errorf("wrong number of signatures, expected %v for byz. quorum and got %v", byzQuorumSize, len(hc.PrepareQuorumCertificate)) + } + + ogid := OutcomeGenerationID{ + configDigest, + hc.Epoch(), + } + + seen := make(map[commontypes.OracleID]bool) + for i, aps := range hc.PrepareQuorumCertificate { + if seen[aps.Signer] { + return fmt.Errorf("duplicate signature by %v", aps.Signer) + } + seen[aps.Signer] = true + if !(0 <= int(aps.Signer) && int(aps.Signer) < len(oracleIdentities)) { + return fmt.Errorf("signer out of bounds: %v", aps.Signer) + } + outputDigest := MakeStateTransitionOutputDigest( + ogid, + hc.SeqNr(), + hc.StateTransitionOutputs.WriteSet, + ) + reportsPlusPrecursorDigest := MakeReportsPlusPrecursorDigest( + ogid, + hc.SeqNr(), + hc.ReportsPlusPrecursor, + ) + if err := aps.Signature.Verify( + ogid, hc.SeqNr(), hc.StateTransitionInputsDigest, outputDigest, + reportsPlusPrecursorDigest, oracleIdentities[aps.Signer].OffchainPublicKey); err != nil { + return fmt.Errorf("%v-th signature by %v-th oracle with pubkey %x does not verify: %w", i, aps.Signer, oracleIdentities[aps.Signer].OffchainPublicKey, err) + } + } + return nil +} +func (hc *CertifiedPrepare) CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool { + + if len(hc.PrepareQuorumCertificate) != byzquorum.Size(n, f) { + return false + } + for _, aps := range hc.PrepareQuorumCertificate { + if len(aps.Signature) != ed25519.SignatureSize { + return false + } + } + return true +} + +var _ CertifiedPrepareOrCommit = &CertifiedCommit{} + +// The empty CertifiedCommit{} is the genesis value +type CertifiedCommit struct { + CommitEpoch uint64 + CommitSeqNr uint64 + StateTransitionInputsDigest StateTransitionInputsDigest + StateTransitionOutputs StateTransitionOutputs + ReportsPlusPrecursor ocr3_1types.ReportsPlusPrecursor + CommitQuorumCertificate []AttributedCommitSignature +} + +func (hc *CertifiedCommit) isCertifiedPrepareOrCommit() {} + +func (hc *CertifiedCommit) Epoch() uint64 { + return uint64(hc.CommitEpoch) +} + +func (hc *CertifiedCommit) SeqNr() uint64 { + return uint64(hc.CommitSeqNr) +} + +func (hc *CertifiedCommit) Timestamp() HighestCertifiedTimestamp { + return HighestCertifiedTimestamp{ + hc.SeqNr(), + true, + hc.Epoch(), + } +} + +func (hc *CertifiedCommit) IsGenesis() bool { + // We intentionally don't just compare with CertifiedCommit{}, because after + // protobuf deserialization, we might end up with hc.Outcome = []byte{} + return hc.Epoch() == uint64(0) && + hc.SeqNr() == uint64(0) && + hc.StateTransitionInputsDigest == StateTransitionInputsDigest{} && + len(hc.ReportsPlusPrecursor) == 0 && + len(hc.CommitQuorumCertificate) == 0 +} + +func (hc *CertifiedCommit) Verify( + configDigest types.ConfigDigest, + oracleIdentities []config.OracleIdentity, + byzQuorumSize int, +) error { + if hc.IsGenesis() { + return nil + } + + if byzQuorumSize != len(hc.CommitQuorumCertificate) { + return fmt.Errorf("wrong number of signatures, expected %d for byz. quorum but got %d", byzQuorumSize, len(hc.CommitQuorumCertificate)) + } + + ogid := OutcomeGenerationID{ + configDigest, + hc.Epoch(), + } + + seen := make(map[commontypes.OracleID]bool) + for i, acs := range hc.CommitQuorumCertificate { + if seen[acs.Signer] { + return fmt.Errorf("duplicate signature by %v", acs.Signer) + } + seen[acs.Signer] = true + if !(0 <= int(acs.Signer) && int(acs.Signer) < len(oracleIdentities)) { + return fmt.Errorf("signer out of bounds: %v", acs.Signer) + } + outputDigest := MakeStateTransitionOutputDigest( + ogid, + hc.SeqNr(), + hc.StateTransitionOutputs.WriteSet, + ) + reportsPlusPrecursorDigest := MakeReportsPlusPrecursorDigest( + ogid, + hc.SeqNr(), + hc.ReportsPlusPrecursor, + ) + if err := acs.Signature.Verify(ogid, + hc.SeqNr(), + hc.StateTransitionInputsDigest, + outputDigest, + reportsPlusPrecursorDigest, + oracleIdentities[acs.Signer].OffchainPublicKey); err != nil { + return fmt.Errorf("%v-th signature by %v-th oracle does not verify: %w", i, acs.Signer, err) + } + } + return nil +} + +func (hc *CertifiedCommit) CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool { + if hc.IsGenesis() { + return true + } + + if len(hc.CommitQuorumCertificate) != byzquorum.Size(n, f) { + return false + } + for _, aps := range hc.CommitQuorumCertificate { + if len(aps.Signature) != ed25519.SignatureSize { + return false + } + } + return true +} + +type AttestedStateTransitionBlock struct { + StateTransitionBlock StateTransitionBlock + AttributedSignatures []AttributedCommitSignature +} + +func (astb *AttestedStateTransitionBlock) Verify( + configDigest types.ConfigDigest, + oracleIdentities []config.OracleIdentity, + byzQuorumSize int, +) error { + if byzQuorumSize != len(astb.AttributedSignatures) { + return fmt.Errorf("wrong number of signatures, expected %d for byz. quorum but got %d", byzQuorumSize, len(astb.AttributedSignatures)) + } + + ogid := OutcomeGenerationID{ + configDigest, + astb.StateTransitionBlock.Epoch, + } + seqNr := astb.StateTransitionBlock.SeqNr() + + seen := make(map[commontypes.OracleID]bool) + for i, sig := range astb.AttributedSignatures { + if seen[sig.Signer] { + return fmt.Errorf("duplicate signature by %v", sig.Signer) + } + seen[sig.Signer] = true + if !(0 <= int(sig.Signer) && int(sig.Signer) < len(oracleIdentities)) { + return fmt.Errorf("signer out of bounds: %v", sig.Signer) + } + if err := sig.Signature.Verify( + ogid, + seqNr, + astb.StateTransitionBlock.StateTransitionInputsDigest, + MakeStateTransitionOutputDigest( + ogid, + seqNr, + astb.StateTransitionBlock.StateTransitionOutputs.WriteSet, + ), + MakeReportsPlusPrecursorDigest( + ogid, + seqNr, + astb.StateTransitionBlock.ReportsPlusPrecursor, + ), + oracleIdentities[sig.Signer].OffchainPublicKey, + ); err != nil { + return fmt.Errorf("%v-th signature by %v-th oracle with pubkey %x does not verify: %w", + i, sig.Signer, oracleIdentities[sig.Signer].OffchainPublicKey, err) + } + } + return nil +} + +type CertifiedCommittedReports[RI any] struct { + CommitEpoch uint64 + SeqNr uint64 + StateTransitionInputsDigest StateTransitionInputsDigest + StateTransitionOutputDigest StateTransitionOutputDigest + ReportsPlusPrecursor ocr3_1types.ReportsPlusPrecursor + CommitQuorumCertificate []AttributedCommitSignature +} + +func (ccrs *CertifiedCommittedReports[RI]) isGenesis() bool { + return ccrs.CommitEpoch == uint64(0) && ccrs.SeqNr == uint64(0) && + ccrs.StateTransitionInputsDigest == StateTransitionInputsDigest{} && + ccrs.StateTransitionOutputDigest == StateTransitionOutputDigest{} && + len(ccrs.ReportsPlusPrecursor) == 0 && + len(ccrs.CommitQuorumCertificate) == 0 +} + +func (ccrs *CertifiedCommittedReports[RI]) CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool { + // Check if we this is a genesis certificate + if ccrs.isGenesis() { + return true + } + + if len(ccrs.ReportsPlusPrecursor) > limits.MaxReportsPlusPrecursorLength { + return false + } + + if len(ccrs.CommitQuorumCertificate) != byzquorum.Size(n, f) { + return false + } + for _, acs := range ccrs.CommitQuorumCertificate { + if len(acs.Signature) != ed25519.SignatureSize { + return false + } + } + return true +} + +func (ccrs *CertifiedCommittedReports[RI]) Verify( + configDigest types.ConfigDigest, + oracleIdentities []config.OracleIdentity, + byzQuorumSize int, +) error { + if byzQuorumSize != len(ccrs.CommitQuorumCertificate) { + return fmt.Errorf("wrong number of signatures, expected %d for byz. quorum but got %d", byzQuorumSize, len(ccrs.CommitQuorumCertificate)) + } + + ogid := OutcomeGenerationID{ + configDigest, + ccrs.CommitEpoch, + } + + seen := make(map[commontypes.OracleID]bool) + for i, acs := range ccrs.CommitQuorumCertificate { + if seen[acs.Signer] { + return fmt.Errorf("duplicate signature by %v", acs.Signer) + } + seen[acs.Signer] = true + if !(0 <= int(acs.Signer) && int(acs.Signer) < len(oracleIdentities)) { + return fmt.Errorf("signer out of bounds: %v", acs.Signer) + } + reportsPlusPrecursorDigest := MakeReportsPlusPrecursorDigest(ogid, ccrs.SeqNr, ccrs.ReportsPlusPrecursor) + if err := acs.Signature.Verify(ogid, + ccrs.SeqNr, + ccrs.StateTransitionInputsDigest, + ccrs.StateTransitionOutputDigest, + reportsPlusPrecursorDigest, + oracleIdentities[acs.Signer].OffchainPublicKey); err != nil { + return fmt.Errorf("%v-th signature by %v-th oracle does not verify: %w", i, acs.Signer, err) + } + } + return nil +} + +type BlobDigest = blobtypes.BlobDigest +type BlobChunkDigest = blobtypes.BlobChunkDigest +type BlobAvailabilitySignature = blobtypes.BlobAvailabilitySignature +type AttributedBlobAvailabilitySignature = blobtypes.AttributedBlobAvailabilitySignature +type LightCertifiedBlob = blobtypes.LightCertifiedBlob diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/state_block_synchronization.go b/offchainreporting2plus/internal/ocr3_1/protocol/state_block_synchronization.go new file mode 100644 index 00000000..d296ffc7 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/state_block_synchronization.go @@ -0,0 +1,367 @@ +package protocol + +import ( + "crypto/rand" + "math" + "math/big" + "time" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/scheduler" +) + +const ( + MaxBlocksSent int = 10 + + // Maximum delay between a BLOCK-SYNC-REQ and a BLOCK-SYNC response. We'll try + // with another oracle if we don't get a response in this time. + + DeltaMaxBlockSyncRequest time.Duration = 1 * time.Second + + // Minimum delay between two consecutive BLOCK-SYNC-REQ requests + DeltaMinBlockSyncRequest = 10 * time.Millisecond + + // An oracle sends a BLOCK-SYNC-SUMMARY message every DeltaBlockSyncHeartbeat + DeltaBlockSyncHeartbeat time.Duration = time.Duration(math.MaxInt64) + + MaxBlockSyncSize int = 50000000 +) + +type blockSyncState[RI any] struct { + logger commontypes.Logger + oracles []*blockSyncTargetOracle[RI] + scheduler *scheduler.Scheduler[EventToStatePersistence[RI]] +} + +func (state *statePersistenceState[RI]) numInflightRequests() int { + count := 0 + for _, oracle := range state.blockSyncState.oracles { + if oracle.inFlightRequest != nil { + count++ + } + } + return count +} + +type blockSyncTargetOracle[RI any] struct { + // lowestPersistedSeqNr is the lowest sequence number the oracle still has an attested + // state transition block for + + lowestPersistedSeqNr uint64 + lastSummaryReceivedAt time.Time + // whether it is viable to send the next block sync request to this oracle + candidate bool + // the current inflight request to this oracle, nil otherwise + inFlightRequest *inFlightRequest[RI] +} + +type inFlightRequest[RI any] struct { + message MessageBlockSyncRequest[RI] + requestedFrom commontypes.OracleID +} + +func (state *statePersistenceState[RI]) highestHeardIncreased() { + state.trySendNextRequest() +} + +func (state *statePersistenceState[RI]) clearStaleBlockSyncRequests() { + state.refreshHighestPersistedStateTransitionBlockSeqNr() + nowPersistedSeqNr := state.highestPersistedStateTransitionBlockSeqNr + for _, oracle := range state.blockSyncState.oracles { + req := oracle.inFlightRequest + if req == nil { + continue + } + thenPersistedSeqNr := req.message.HighestCommittedSeqNr + if thenPersistedSeqNr < nowPersistedSeqNr { + // this is a stale request + state.blockSyncState.logger.Debug("removing stale BlockSyncRequest", commontypes.LogFields{ + "requestedFrom": req.requestedFrom, + "thenPersistedSeqNr": thenPersistedSeqNr, + "nowPersistedSeqNr": nowPersistedSeqNr, + }) + oracle.inFlightRequest = nil + } + } +} + +func (state *statePersistenceState[RI]) trySendNextRequest() { + if !state.readyToSendBlockSyncReq { + state.blockSyncState.logger.Trace("trySendNextRequest: not marked as ready to send BlockSyncRequest, dropping", nil) + return + } + if state.numInflightRequests() != 0 { + // if numInflightRequests > 0, we are already waiting for a response which + // we'll either receive or timeout, but regardless it will carry us over + // until state.highestHeard is retrieved + state.blockSyncState.logger.Debug("we are already fetching blocks", commontypes.LogFields{ + "numInflightRequests": state.numInflightRequests(), + }) + return + } + + state.refreshHighestPersistedStateTransitionBlockSeqNr() + reqSeqNr := state.highestPersistedStateTransitionBlockSeqNr + if state.highestHeardSeqNr > reqSeqNr { + state.blockSyncState.logger.Trace("trySendNextRequest: highestHeardSeqNr > highestPersistedStateTransitionBlockSeqNr, sending BlockSyncRequest", commontypes.LogFields{ + "highestHeardSeqNr": state.highestHeardSeqNr, + "highestPersistedSeqNr": state.highestPersistedStateTransitionBlockSeqNr, + }) + state.sendBlockSyncReq(reqSeqNr) + } +} + +func (state *statePersistenceState[RI]) tryComplete() { + state.clearStaleBlockSyncRequests() + state.trySendNextRequest() +} + +func (state *statePersistenceState[RI]) processBlockSyncSummaryHeartbeat() { + defer state.blockSyncState.scheduler.ScheduleDelay(EventBlockSyncSummaryHeartbeat[RI]{}, DeltaBlockSyncHeartbeat) + lowestPersistedSeqNr := 0 + state.refreshHighestPersistedStateTransitionBlockSeqNr() + if state.highestPersistedStateTransitionBlockSeqNr >= 1 { + lowestPersistedSeqNr = 1 + } + state.netSender.Broadcast(MessageBlockSyncSummary[RI]{ + uint64(lowestPersistedSeqNr), + }) +} + +func (state *statePersistenceState[RI]) messageBlockSyncSummary(msg MessageBlockSyncSummary[RI], sender commontypes.OracleID) { + state.blockSyncState.logger.Debug("received messageBlockSyncSummary", commontypes.LogFields{ + "sender": sender, + "msgLowestPersistedSeqNr": msg.LowestPersistedSeqNr, + }) + oracle := state.blockSyncState.oracles[sender] + oracle.lowestPersistedSeqNr = msg.LowestPersistedSeqNr + oracle.lastSummaryReceivedAt = time.Now() +} + +func (state *statePersistenceState[RI]) processExpiredBlockSyncRequest(requestedFrom commontypes.OracleID, nonce uint64) { + oracle := state.blockSyncState.oracles[requestedFrom] + if oracle.inFlightRequest == nil { + return + } + if oracle.inFlightRequest.message.Nonce == nonce { + oracle.inFlightRequest = nil + oracle.candidate = false + } + state.tryComplete() +} + +func (state *statePersistenceState[RI]) sendBlockSyncReq(seqNr uint64) { + candidates := make([]commontypes.OracleID, 0, state.config.N()) + for oracleID, oracle := range state.blockSyncState.oracles { + if commontypes.OracleID(oracleID) == state.id { + continue + } + if oracle.candidate { + + candidates = append(candidates, commontypes.OracleID(oracleID)) + } + } + + if len(candidates) == 0 { + + state.blockSyncState.logger.Debug("not candidate oracles for MessageBlockSyncRequest, restarting from scratch", nil) + candidates = make([]commontypes.OracleID, 0, state.config.N()) + for oracleID, oracle := range state.blockSyncState.oracles { + oracle.candidate = true + candidates = append(candidates, commontypes.OracleID(oracleID)) + } + } + randomIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(candidates)))) + if err != nil { + state.blockSyncState.logger.Critical("unexpected error returned by rand.Int", commontypes.LogFields{ + "error": err, + }) + return + } + target := candidates[int(randomIndex.Int64())] + nonce, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 64)) + if err != nil { + state.blockSyncState.logger.Critical("unexpected error returned by rand.Int", commontypes.LogFields{ + "error": err, + }) + return + } + state.blockSyncState.logger.Debug("sending MessageBlockSyncRequest", commontypes.LogFields{ + "highestCommittedSeqNr": seqNr, + "target": target, + }) + msg := MessageBlockSyncRequest[RI]{ + nil, // TODO: consider using a sentinel value here, e.g. "EmptyRequestHandleForInboundResponse" + seqNr, + nonce.Uint64(), + } + state.netSender.SendTo(msg, target) + if !(0 <= int(target) && int(target) < len(state.blockSyncState.oracles)) { + state.blockSyncState.logger.Critical("target oracle out of bounds", commontypes.LogFields{ + "target": target, + "N": state.config.N(), + }) + return + } + state.blockSyncState.oracles[target].inFlightRequest = &inFlightRequest[RI]{msg, target} + state.blockSyncState.scheduler.ScheduleDelay(EventExpiredBlockSyncRequest[RI]{target, nonce.Uint64()}, DeltaMaxBlockSyncRequest) + state.readyToSendBlockSyncReq = false + state.blockSyncState.scheduler.ScheduleDelay(EventReadyToSendNextBlockSyncRequest[RI]{}, DeltaMinBlockSyncRequest) +} + +func (state *statePersistenceState[RI]) messageBlockSyncReq(msg MessageBlockSyncRequest[RI], sender commontypes.OracleID) { + state.blockSyncState.logger.Debug("received MessageBlockSyncRequest", commontypes.LogFields{ + "sender": sender, + "msgHighestCommittedSeqNr": msg.HighestCommittedSeqNr, + }) + loSeqNr := msg.HighestCommittedSeqNr + 1 + + state.refreshHighestPersistedStateTransitionBlockSeqNr() + + var ( + astbs []AttestedStateTransitionBlock + hiSeqNr uint64 + ) + for seqNr := loSeqNr; len(astbs) < MaxBlocksSent && seqNr <= state.highestPersistedStateTransitionBlockSeqNr; seqNr++ { + + astb, err := state.database.ReadAttestedStateTransitionBlock(state.ctx, state.config.ConfigDigest, seqNr) + if err != nil { + state.blockSyncState.logger.Error("Database.ReadAttestedStateTransitionBlock failed while producing MessageBlockSync", commontypes.LogFields{ + "seqNr": seqNr, + "error": err, + }) + break // Stopping to not produce a gap. + } + + if astb.StateTransitionBlock.SeqNr() != seqNr { + break // Stopping to not produce a gap. + } + astbs = append(astbs, astb) + hiSeqNr = seqNr + } + + if len(astbs) > 0 { + state.blockSyncState.logger.Debug("sending MessageBlockSync", commontypes.LogFields{ + "highestPersisted": state.highestPersistedStateTransitionBlockSeqNr, + "loSeqNr": loSeqNr, + "hiSeqNr": hiSeqNr, + "to": sender, + }) + state.netSender.SendTo(MessageBlockSync[RI]{ + msg.RequestHandle, + astbs, + msg.Nonce, + }, sender) + } else { + state.blockSyncState.logger.Debug("no blocks to send, not responding to MessageBlockSyncRequest", commontypes.LogFields{ + "highestPersisted": state.highestPersistedStateTransitionBlockSeqNr, + "loSeqNr": loSeqNr, + "to": sender, + }) + } +} + +func (state *statePersistenceState[RI]) messageBlockSync(msg MessageBlockSync[RI], sender commontypes.OracleID) { + state.blockSyncState.logger.Debug("received MessageBlockSync", commontypes.LogFields{ + "sender": sender, + }) + req := state.blockSyncState.oracles[sender].inFlightRequest + if req == nil { + state.blockSyncState.logger.Warn("dropping unexpected MessageBlockSync", commontypes.LogFields{ + "nonce": msg.Nonce, + "sender": sender, + }) + return + } + + if msg.Nonce != req.message.Nonce { + state.blockSyncState.logger.Warn("dropping MessageBlockSync with unexpected nonce", commontypes.LogFields{ + "expectedNonce": req.message.Nonce, + "actualNonce": msg.Nonce, + "sender": sender, + }) + return + } + + // so that any future response with the same nonce will become invalid + state.blockSyncState.oracles[sender].inFlightRequest = nil + + // at this point we know we've received a response from the correct oracle + + // 1. if any of the following logic errors out, we will immediately notice + // and start re-requesting from where we left off, even if we partially + // persist the blocks in this response + // 2. if the logic succeeds, we'll move to requesting for the next sequence + // number, until we reach highestHeardSeqNr + defer state.tryComplete() + if len(msg.AttestedStateTransitionBlocks) > MaxBlocksSent { + state.blockSyncState.logger.Warn("dropping MessageBlockSync with more blocks than the maximum allowed number", commontypes.LogFields{ + "blockNum": len(msg.AttestedStateTransitionBlocks), + "expectedBlockNum": MaxBlocksSent, + "sender": sender, + }) + return + } + for i, astb := range msg.AttestedStateTransitionBlocks { + if astb.StateTransitionBlock.SeqNr() != req.message.HighestCommittedSeqNr+uint64(i)+1 { + state.blockSyncState.logger.Warn("dropping MessageBlockSync with out of order state transition blocks", commontypes.LogFields{ + "stateTransitionBlockSeqNr": astb.StateTransitionBlock.SeqNr(), + "sender": sender, + }) + return + } + } + for _, astb := range msg.AttestedStateTransitionBlocks { + if err := astb.Verify(state.config.ConfigDigest, state.config.OracleIdentities, state.config.ByzQuorumSize()); err != nil { + state.blockSyncState.logger.Warn("dropping MessageBlockSync with invalid attestation", commontypes.LogFields{ + "stateTransitionBlockSeqNr": astb.StateTransitionBlock.SeqNr(), + "sender": sender, + "error": err, + }) + return + } + } + + for _, astb := range msg.AttestedStateTransitionBlocks { + state.refreshHighestPersistedStateTransitionBlockSeqNr() + expectedSeqNr := state.highestPersistedStateTransitionBlockSeqNr + 1 + seqNr := astb.StateTransitionBlock.SeqNr() + + state.blockSyncState.logger.Debug("retrieved state transition block", commontypes.LogFields{ + "stateTransitionBlockSeqNr": seqNr, + }) + + if seqNr > expectedSeqNr { + + state.blockSyncState.logger.Warn("dropping MessageBlockSync which creates gaps in persisted blocks", commontypes.LogFields{ + "stateTransitionBlockSeqNr": astb.StateTransitionBlock.SeqNr(), + "highestPersistedStateTransitionBlockSeqNr": state.highestPersistedStateTransitionBlockSeqNr, + "sender": sender, + }) + return + } else if seqNr < expectedSeqNr { + state.blockSyncState.logger.Debug("no need to persist this block, we have done so already", commontypes.LogFields{ + "stateTransitionBlockSeqNr": astb.StateTransitionBlock.SeqNr(), + "highestPersistedStateTransitionBlock": state.highestPersistedStateTransitionBlockSeqNr, + }) + } else { + werr := state.persist(astb) + if werr != nil { + + { + rastb, rerr := state.database.ReadAttestedStateTransitionBlock(state.ctx, state.config.ConfigDigest, seqNr) + if rerr == nil && rastb.StateTransitionBlock.SeqNr() == seqNr { + + continue + } + } + + state.blockSyncState.logger.Error("error persisting state transition block", commontypes.LogFields{ + "stateTransitionBlockSeqNr": astb.StateTransitionBlock.SeqNr(), + "error": werr, + }) + return + } + } + } +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/state_persistence.go b/offchainreporting2plus/internal/ocr3_1/protocol/state_persistence.go new file mode 100644 index 00000000..a8710e53 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/state_persistence.go @@ -0,0 +1,371 @@ +package protocol + +import ( + "context" + "fmt" + "math" + "time" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/scheduler" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3config" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" +) + +func RunStatePersistence[RI any]( + ctx context.Context, + + chNetToStatePersistence <-chan MessageToStatePersistenceWithSender[RI], + chReportAttestationToStatePersistence <-chan EventToStatePersistence[RI], + config ocr3config.SharedConfig, + database Database, + id commontypes.OracleID, + kvStore KeyValueStore, + logger loghelper.LoggerWithContext, + netSender NetworkSender[RI], + reportingPlugin ocr3_1types.ReportingPlugin[RI], + restoredState StatePersistenceState, + restoredHighestCommittedToKVSeqNr uint64, +) { + sched := scheduler.NewScheduler[EventToStatePersistence[RI]]() + defer sched.Close() + + newStatePersistenceState(ctx, chNetToStatePersistence, + chReportAttestationToStatePersistence, + config, database, id, kvStore, logger, netSender, reportingPlugin, sched).run(restoredState) +} + +const maxPersistedAttestedStateTransitionBlocks int = math.MaxInt + +type statePersistenceState[RI any] struct { + ctx context.Context + + chNetToStatePersistence <-chan MessageToStatePersistenceWithSender[RI] + chReportAttestationToStatePersistence <-chan EventToStatePersistence[RI] + tTryReplay <-chan time.Time + config ocr3config.SharedConfig + database Database + id commontypes.OracleID + kvStore KeyValueStore + logger loghelper.LoggerWithContext + netSender NetworkSender[RI] + reportingPlugin ocr3_1types.ReportingPlugin[RI] + + highestPersistedStateTransitionBlockSeqNr uint64 + highestHeardSeqNr uint64 + readyToSendBlockSyncReq bool + + blockSyncState blockSyncState[RI] + treeSyncState treeSyncState +} + +func (state *statePersistenceState[RI]) run(restoredState StatePersistenceState) { + state.highestPersistedStateTransitionBlockSeqNr = restoredState.HighestPersistedStateTransitionBlockSeqNr + state.logger.Info("StatePersistence: running", commontypes.LogFields{ + "restoredHighestPersistedStateTransitionBlockSeqNr": restoredState.HighestPersistedStateTransitionBlockSeqNr, + }) + + for { + select { + case msg := <-state.chNetToStatePersistence: + msg.msg.processStatePersistence(state, msg.sender) + case ev := <-state.chReportAttestationToStatePersistence: + ev.processStatePersistence(state) + case ev := <-state.blockSyncState.scheduler.Scheduled(): + ev.processStatePersistence(state) + case <-state.tTryReplay: + state.eventTTryReplay() + case <-state.ctx.Done(): + } + + // ensure prompt exit + select { + case <-state.ctx.Done(): + state.logger.Info("StatePersistence: exiting", nil) + // state.scheduler.Close() + return + default: + } + } +} + +func (state *statePersistenceState[RI]) eventTTryReplay() { + state.logger.Trace("TTryReplay fired", nil) + + progressed := state.tryReplay() + + _, haveNext, retry := state.nextBlockToReplay() + if progressed || haveNext || retry { + + state.tTryReplay = time.After(0) + } +} + +func (state *statePersistenceState[RI]) tryReplay() bool { + block, ok, _ := state.nextBlockToReplay() + if !ok { + return false + } + return state.replayVerifiedBlock(block) +} + +func (state *statePersistenceState[RI]) replayVerifiedBlock(stb StateTransitionBlock) (success bool) { + writeSet := stb.StateTransitionOutputs.WriteSet + + seqNr := stb.SeqNr() + logger := state.logger.MakeChild(commontypes.LogFields{ + "replay": "YES", + "seqNr": seqNr, + }) + + logger.Trace("replaying state transition block", nil) + kvReadWriteTxn, err := state.kvStore.NewReadWriteTransaction(seqNr) + if err != nil { + logger.Error("could not open new kv transaction", commontypes.LogFields{ + "err": err, + }) + return + } + defer kvReadWriteTxn.Discard() + + for _, m := range writeSet { + + var err error + if m.Deleted { + err = kvReadWriteTxn.Delete(m.Key) + } else { + err = kvReadWriteTxn.Write(m.Key, m.Value) + } + if err != nil { + logger.Error("failed to write write-set modification", commontypes.LogFields{ + "error": err, + "seqNr": seqNr, + }) + return + } + } + + werr := kvReadWriteTxn.Commit() + kvReadWriteTxn.Discard() + + if werr != nil { + kvSeqNr, rerr := state.highestCommittedToKVSeqNr() + if rerr != nil { + logger.Error("failed to commit kv transaction, and then failed to read highest committed to kv seq nr", commontypes.LogFields{ + "werror": werr, + "rerror": rerr, + "seqNr": seqNr, + }) + return + } + if kvSeqNr < seqNr { + logger.Error("failed to commit kv transaction, but not due to conflict without outcome generation", commontypes.LogFields{ + "seqNr": seqNr, + "kvSeqNr": kvSeqNr, + "error": werr, + }) + + return + } else { + + return + } + } + success = true + return +} + +func (state *statePersistenceState[RI]) eventStateSyncRequest(ev EventStateSyncRequest[RI]) { + state.logger.Debug("received EventStateSyncRequest", commontypes.LogFields{ + "heardSeqNr": ev.SeqNr, + }) + state.heardSeqNr(ev.SeqNr) +} + +func (state *statePersistenceState[RI]) heardSeqNr(seqNr uint64) { + if seqNr > state.highestHeardSeqNr { + state.logger.Debug("highest heard sequence number increased", commontypes.LogFields{ + "old": state.highestHeardSeqNr, + "new": seqNr, + }) + state.highestHeardSeqNr = seqNr + state.highestHeardIncreased() + } +} + +func (state *statePersistenceState[RI]) refreshHighestPersistedStateTransitionBlockSeqNr() { + highestCommittedToKVSeqNr, err := state.highestCommittedToKVSeqNr() + if err != nil { + state.logger.Error("failed to get highest committed to kv seq nr during refresh", commontypes.LogFields{ + "error": err, + }) + return + } + if highestCommittedToKVSeqNr > state.highestPersistedStateTransitionBlockSeqNr { + + state.highestPersistedStateTransitionBlockSeqNr = highestCommittedToKVSeqNr + } +} + +func (state *statePersistenceState[RI]) persist(verifiedAstb AttestedStateTransitionBlock) error { + state.refreshHighestPersistedStateTransitionBlockSeqNr() + expectedSeqNr := state.highestPersistedStateTransitionBlockSeqNr + 1 + seqNr := verifiedAstb.StateTransitionBlock.SeqNr() + + if seqNr != expectedSeqNr { + + return fmt.Errorf("cannot persist out of order state transition block: expected %d, got %d", + expectedSeqNr, + seqNr, + ) + } + + err := state.database.WriteAttestedStateTransitionBlock( + state.ctx, + state.config.ConfigDigest, + seqNr, + verifiedAstb, + ) + if err != nil { + return fmt.Errorf("failed to write attested state transition block %d: %w", seqNr, err) + } + + state.highestPersistedStateTransitionBlockSeqNr = seqNr + state.logger.Trace("persisted block", commontypes.LogFields{ + "seqNr": seqNr, + }) + state.tTryReplay = time.After(0) + + err = state.database.WriteStatePersistenceState( + state.ctx, state.config.ConfigDigest, + StatePersistenceState{ + seqNr, + }, + ) + if err != nil { + return fmt.Errorf("failed to write state persistence state %d: %w", seqNr, err) + } + return nil +} + +func (state *statePersistenceState[RI]) highestCommittedToKVSeqNr() (uint64, error) { + return state.kvStore.HighestCommittedSeqNr() +} + +func (state *statePersistenceState[RI]) nextBlockToReplay() (block StateTransitionBlock, found bool, retry bool) { + committedToKVSeqNr, err := state.highestCommittedToKVSeqNr() + if err != nil { + state.logger.Error("failed to get highest committed to kv seq nr", commontypes.LogFields{ + "error": err, + }) + retry = true + return + } + nextSeqNr := committedToKVSeqNr + 1 + + astb, err := state.database.ReadAttestedStateTransitionBlock(state.ctx, state.config.ConfigDigest, nextSeqNr) + if err != nil { + state.logger.Error("failed to read attested state transition block from database", commontypes.LogFields{ + "nextSeqNr": nextSeqNr, + "error": err, + }) + retry = true + return + } + seqNr := astb.StateTransitionBlock.SeqNr() + if seqNr == 0 { + // block not found + state.logger.Trace("wanted next block to replay not found", commontypes.LogFields{ + "nextSeqNr": nextSeqNr, + }) + + return + } else if seqNr == nextSeqNr { + state.logger.Debug("next state transition block to replay", commontypes.LogFields{ + "nextSeqNr": nextSeqNr, + }) + + block = astb.StateTransitionBlock + found = true + return + } else { + state.logger.Critical("assumption violation, block in database has inconsistent seq nr", commontypes.LogFields{ + "expectedSeqNr": nextSeqNr, + "actualSeqNr": seqNr, + "block": astb, + }) + panic("") + } +} + +func (state *statePersistenceState[RI]) eventEventBlockSyncSummaryHeartbeat(ev EventBlockSyncSummaryHeartbeat[RI]) { + state.processBlockSyncSummaryHeartbeat() +} + +func (state *statePersistenceState[RI]) eventExpiredBlockSyncRequest(ev EventExpiredBlockSyncRequest[RI]) { + state.blockSyncState.logger.Debug("received eventExpiredBlockSyncRequest", commontypes.LogFields{ + "requestedFrom": ev.RequestedFrom, + }) + state.processExpiredBlockSyncRequest(ev.RequestedFrom, ev.Nonce) +} + +func (state *statePersistenceState[RI]) eventReadyToSendNextBlockSyncRequest(ev EventReadyToSendNextBlockSyncRequest[RI]) { + state.logger.Debug("received eventReadyToSendNextBlockSyncRequest", commontypes.LogFields{}) + state.readyToSendBlockSyncReq = true + state.trySendNextRequest() +} + +func newStatePersistenceState[RI any]( + ctx context.Context, + chNetToStatePersistence <-chan MessageToStatePersistenceWithSender[RI], + + chReportAttestationToStatePersistence <-chan EventToStatePersistence[RI], + config ocr3config.SharedConfig, + database Database, + id commontypes.OracleID, + kvStore KeyValueStore, + logger loghelper.LoggerWithContext, + netSender NetworkSender[RI], + reportingPlugin ocr3_1types.ReportingPlugin[RI], + scheduler *scheduler.Scheduler[EventToStatePersistence[RI]], +) *statePersistenceState[RI] { + oracles := make([]*blockSyncTargetOracle[RI], 0) + for i := 0; i < config.N(); i++ { + oracles = append(oracles, &blockSyncTargetOracle[RI]{ + 0, + time.Time{}, + true, + nil, + }) + } + + scheduler.ScheduleDelay(EventBlockSyncSummaryHeartbeat[RI]{}, DeltaBlockSyncHeartbeat) + + tTryReplay := time.After(0) + + return &statePersistenceState[RI]{ + ctx, + + chNetToStatePersistence, + chReportAttestationToStatePersistence, + tTryReplay, + config, + database, + id, + kvStore, + logger.MakeUpdated(commontypes.LogFields{"proto": "state"}), + netSender, + reportingPlugin, + 0, + 0, + true, + + blockSyncState[RI]{ + logger.MakeUpdated(commontypes.LogFields{"proto": "stateBlockSync"}), + oracles, + scheduler, + }, + treeSyncState{}, + } +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/state_tree_synchronization.go b/offchainreporting2plus/internal/ocr3_1/protocol/state_tree_synchronization.go new file mode 100644 index 00000000..8607f90c --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/state_tree_synchronization.go @@ -0,0 +1,8 @@ +package protocol + +type treeSyncState struct{} + +func (state *statePersistenceState[RI]) startTreeSync() { + //TODO implement me + panic("implement me") +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/telemetry.go b/offchainreporting2plus/internal/ocr3_1/protocol/telemetry.go new file mode 100644 index 00000000..d38f6a4e --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/telemetry.go @@ -0,0 +1,16 @@ +package protocol + +import ( + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type TelemetrySender interface { + RoundStarted( + configDigest types.ConfigDigest, + epoch uint64, + seqNr uint64, + round uint64, + leader commontypes.OracleID, + ) +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/transmission.go b/offchainreporting2plus/internal/ocr3_1/protocol/transmission.go new file mode 100644 index 00000000..ac662435 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/transmission.go @@ -0,0 +1,289 @@ +package protocol + +import ( + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/binary" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common" + "slices" + "time" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/scheduler" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3config" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/libocr/permutation" + "github.com/smartcontractkit/libocr/subprocesses" +) + +const ContractTransmitterTimeoutWarningGracePeriod = 50 * time.Millisecond + +func RunTransmission[RI any]( + ctx context.Context, + + chReportAttestationToTransmission <-chan EventToTransmission[RI], + config ocr3config.SharedConfig, + contractTransmitter ocr3types.ContractTransmitter[RI], + id commontypes.OracleID, + localConfig types.LocalConfig, + logger loghelper.LoggerWithContext, + reportingPlugin ocr3_1types.ReportingPlugin[RI], +) { + sched := scheduler.NewScheduler[EventAttestedReport[RI]]() + defer sched.Close() + + t := transmissionState[RI]{ + ctx, + subprocesses.Subprocesses{}, + + chReportAttestationToTransmission, + config, + contractTransmitter, + id, + localConfig, + logger.MakeUpdated(commontypes.LogFields{"proto": "transmission"}), + reportingPlugin, + + sched, + } + t.run() +} + +type transmissionState[RI any] struct { + ctx context.Context + subs subprocesses.Subprocesses + + chReportAttestationToTransmission <-chan EventToTransmission[RI] + config ocr3config.SharedConfig + contractTransmitter ocr3types.ContractTransmitter[RI] + id commontypes.OracleID + localConfig types.LocalConfig + logger loghelper.LoggerWithContext + reportingPlugin ocr3_1types.ReportingPlugin[RI] + + scheduler *scheduler.Scheduler[EventAttestedReport[RI]] +} + +// run runs the event loop for the local transmission protocol +func (t *transmissionState[RI]) run() { + t.logger.Info("Transmission: running", nil) + + chDone := t.ctx.Done() + for { + select { + case ev := <-t.chReportAttestationToTransmission: + ev.processTransmission(t) + case ev := <-t.scheduler.Scheduled(): + t.scheduled(ev) + case <-chDone: + } + + // ensure prompt exit + select { + case <-chDone: + t.logger.Info("Transmission: winding down", nil) + t.subs.Wait() + t.logger.Info("Transmission: exiting", nil) + return + default: + } + } +} + +func (t *transmissionState[RI]) eventAttestedReport(ev EventAttestedReport[RI]) { + now := time.Now() + + t.subs.Go(func() { + t.backgroundEventAttestedReport(t.ctx, now, ev) + }) +} + +func (t *transmissionState[RI]) backgroundEventAttestedReport(ctx context.Context, start time.Time, ev EventAttestedReport[RI]) { + var delay time.Duration + { + delayMaybe := t.transmitDelay(ev.SeqNr, ev.Index, ev.TransmissionScheduleOverride) + if delayMaybe == nil { + t.logger.Debug("dropping EventAttestedReport because we're not included in transmission schedule", commontypes.LogFields{ + "seqNr": ev.SeqNr, + "index": ev.Index, + "transmissionScheduleOverride": ev.TransmissionScheduleOverride != nil, + }) + return + } + delay = *delayMaybe + } + + shouldAccept, ok := common.CallPlugin[bool]( + ctx, + t.logger, + commontypes.LogFields{ + "seqNr": ev.SeqNr, + "index": ev.Index, + }, + "ShouldAcceptAttestedReport", + t.config.MaxDurationShouldAcceptAttestedReport, + func(ctx context.Context) (bool, error) { + return t.reportingPlugin.ShouldAcceptAttestedReport( + ctx, + ev.SeqNr, + ev.AttestedReport.ReportWithInfo, + ) + }, + ) + if !ok { + return + } + + if !shouldAccept { + t.logger.Debug("ReportingPlugin.ShouldAcceptAttestedReport returned false", commontypes.LogFields{ + "seqNr": ev.SeqNr, + "index": ev.Index, + }) + return + } + + t.logger.Debug("accepted AttestedReport for transmission", commontypes.LogFields{ + "seqNr": ev.SeqNr, + "index": ev.Index, + "delay": delay.String(), + "transmissionScheduleOverride": ev.TransmissionScheduleOverride != nil, + }) + t.scheduler.ScheduleDeadline(ev, start.Add(delay)) +} + +func (t *transmissionState[RI]) scheduled(ev EventAttestedReport[RI]) { + t.subs.Go(func() { + t.backgroundScheduled(t.ctx, ev) + }) +} + +func (t *transmissionState[RI]) backgroundScheduled(ctx context.Context, ev EventAttestedReport[RI]) { + shouldTransmit, ok := common.CallPlugin[bool]( + ctx, + t.logger, + commontypes.LogFields{ + "seqNr": ev.SeqNr, + "index": ev.Index, + }, + "ShouldTransmitAcceptedReport", + t.config.MaxDurationShouldTransmitAcceptedReport, + func(ctx context.Context) (bool, error) { + return t.reportingPlugin.ShouldTransmitAcceptedReport( + ctx, + ev.SeqNr, + ev.AttestedReport.ReportWithInfo, + ) + }, + ) + if !ok { + return + } + + if !shouldTransmit { + t.logger.Info("ReportingPlugin.ShouldTransmitAcceptedReport returned false", commontypes.LogFields{ + "seqNr": ev.SeqNr, + "index": ev.Index, + }) + return + } + + t.logger.Debug("transmitting report", commontypes.LogFields{ + "seqNr": ev.SeqNr, + "index": ev.Index, + }) + + { + transmitCtx, transmitCancel := context.WithTimeout( + ctx, + t.localConfig.ContractTransmitterTransmitTimeout, + ) + defer transmitCancel() + + ins := loghelper.NewIfNotStopped( + t.localConfig.ContractTransmitterTransmitTimeout+ContractTransmitterTimeoutWarningGracePeriod, + func() { + t.logger.Error("ContractTransmitter.Transmit is taking too long", commontypes.LogFields{ + "maxDuration": t.localConfig.ContractTransmitterTransmitTimeout.String(), + "seqNr": ev.SeqNr, + "index": ev.Index, + }) + }, + ) + + err := t.contractTransmitter.Transmit( + transmitCtx, + t.config.ConfigDigest, + ev.SeqNr, + ev.AttestedReport.ReportWithInfo, + ev.AttestedReport.AttributedSignatures, + ) + + ins.Stop() + + if err != nil { + t.logger.Error("ContractTransmitter.Transmit error", commontypes.LogFields{"error": err}) + return + } + + } + + t.logger.Info("🚀 successfully invoked ContractTransmitter.Transmit", commontypes.LogFields{ + "seqNr": ev.SeqNr, + "index": ev.Index, + }) +} + +func (t *transmissionState[RI]) transmitPermutationKey(seqNr uint64, index int) [16]byte { + transmissionOrderKey := t.config.TransmissionOrderKey() + mac := hmac.New(sha256.New, transmissionOrderKey[:]) + _ = binary.Write(mac, binary.BigEndian, seqNr) + _ = binary.Write(mac, binary.BigEndian, uint64(index)) + + var key [16]byte + _ = copy(key[:], mac.Sum(nil)) + return key +} + +func (t *transmissionState[RI]) transmitDelayFromOverride(seqNr uint64, index int, transmissionScheduleOverride ocr3types.TransmissionSchedule) *time.Duration { + if len(transmissionScheduleOverride.TransmissionDelays) != len(transmissionScheduleOverride.Transmitters) { + t.logger.Error("invalid TransmissionScheduleOverride, cannot compute delay", commontypes.LogFields{ + "seqNr": seqNr, + "index": index, + "transmissionScheduleOverride": transmissionScheduleOverride, + }) + return nil + } + + oracleIndex := slices.Index(transmissionScheduleOverride.Transmitters, t.id) + if oracleIndex < 0 { + return nil + } + pi := permutation.Permutation(len(transmissionScheduleOverride.TransmissionDelays), t.transmitPermutationKey(seqNr, index)) + delay := transmissionScheduleOverride.TransmissionDelays[pi[oracleIndex]] + return &delay +} + +func (t *transmissionState[RI]) transmitDelayDefault(seqNr uint64, index int) *time.Duration { + pi := permutation.Permutation(t.config.N(), t.transmitPermutationKey(seqNr, index)) + sum := 0 + for i, s := range t.config.S { + sum += s + if pi[t.id] < sum { + result := time.Duration(i) * t.config.DeltaStage + return &result + } + } + return nil +} + +func (t *transmissionState[RI]) transmitDelay(seqNr uint64, index int, transmissionScheduleOverride *ocr3types.TransmissionSchedule) *time.Duration { + if transmissionScheduleOverride != nil { + return t.transmitDelayFromOverride(seqNr, index, *transmissionScheduleOverride) + } else { + return t.transmitDelayDefault(seqNr, index) + } +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/types.go b/offchainreporting2plus/internal/ocr3_1/protocol/types.go new file mode 100644 index 00000000..c949c0c9 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/types.go @@ -0,0 +1,28 @@ +package protocol + +import ( + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type AttestedReportMany[RI any] struct { + ReportWithInfo ocr3types.ReportWithInfo[RI] + AttributedSignatures []types.AttributedOnchainSignature +} + +type StateTransitionBlock struct { + Epoch uint64 + BlockSeqNr uint64 + StateTransitionInputsDigest StateTransitionInputsDigest + StateTransitionOutputs StateTransitionOutputs + ReportsPlusPrecursor ocr3_1types.ReportsPlusPrecursor +} + +func (stb *StateTransitionBlock) SeqNr() uint64 { + return stb.BlockSeqNr +} + +type StateTransitionOutputs struct { + WriteSet []KeyValuePair +} diff --git a/offchainreporting2plus/internal/ocr3_1/serialization/offchainreporting3_1_db.pb.go b/offchainreporting2plus/internal/ocr3_1/serialization/offchainreporting3_1_db.pb.go new file mode 100644 index 00000000..27f51d7e --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/serialization/offchainreporting3_1_db.pb.go @@ -0,0 +1,224 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v4.25.1 +// source: offchainreporting3_1_db.proto + +package serialization + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PacemakerState struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` + HighestSentNewEpochWish uint64 `protobuf:"varint,2,opt,name=highest_sent_new_epoch_wish,json=highestSentNewEpochWish,proto3" json:"highest_sent_new_epoch_wish,omitempty"` +} + +func (x *PacemakerState) Reset() { + *x = PacemakerState{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_db_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PacemakerState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PacemakerState) ProtoMessage() {} + +func (x *PacemakerState) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_db_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PacemakerState.ProtoReflect.Descriptor instead. +func (*PacemakerState) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_db_proto_rawDescGZIP(), []int{0} +} + +func (x *PacemakerState) GetEpoch() uint64 { + if x != nil { + return x.Epoch + } + return 0 +} + +func (x *PacemakerState) GetHighestSentNewEpochWish() uint64 { + if x != nil { + return x.HighestSentNewEpochWish + } + return 0 +} + +type StatePersistenceState struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + HighestPersistedStateTransitionBlockSeqNr uint64 `protobuf:"varint,1,opt,name=highest_persisted_state_transition_block_seq_nr,json=highestPersistedStateTransitionBlockSeqNr,proto3" json:"highest_persisted_state_transition_block_seq_nr,omitempty"` +} + +func (x *StatePersistenceState) Reset() { + *x = StatePersistenceState{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_db_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatePersistenceState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatePersistenceState) ProtoMessage() {} + +func (x *StatePersistenceState) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_db_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatePersistenceState.ProtoReflect.Descriptor instead. +func (*StatePersistenceState) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_db_proto_rawDescGZIP(), []int{1} +} + +func (x *StatePersistenceState) GetHighestPersistedStateTransitionBlockSeqNr() uint64 { + if x != nil { + return x.HighestPersistedStateTransitionBlockSeqNr + } + return 0 +} + +var File_offchainreporting3_1_db_proto protoreflect.FileDescriptor + +var file_offchainreporting3_1_db_proto_rawDesc = []byte{ + 0x0a, 0x1d, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x5f, 0x64, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x14, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x22, 0x64, 0x0a, 0x0e, 0x50, 0x61, 0x63, 0x65, 0x6d, 0x61, 0x6b, + 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x3c, 0x0a, + 0x1b, 0x68, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x65, + 0x77, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x77, 0x69, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x17, 0x68, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x53, 0x65, 0x6e, 0x74, 0x4e, + 0x65, 0x77, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x57, 0x69, 0x73, 0x68, 0x22, 0x7b, 0x0a, 0x15, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x62, 0x0a, 0x2f, 0x68, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x5f, + 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x5f, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x29, 0x68, + 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x53, 0x65, 0x71, 0x4e, 0x72, 0x42, 0x11, 0x5a, 0x0f, 0x2e, 0x3b, 0x73, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_offchainreporting3_1_db_proto_rawDescOnce sync.Once + file_offchainreporting3_1_db_proto_rawDescData = file_offchainreporting3_1_db_proto_rawDesc +) + +func file_offchainreporting3_1_db_proto_rawDescGZIP() []byte { + file_offchainreporting3_1_db_proto_rawDescOnce.Do(func() { + file_offchainreporting3_1_db_proto_rawDescData = protoimpl.X.CompressGZIP(file_offchainreporting3_1_db_proto_rawDescData) + }) + return file_offchainreporting3_1_db_proto_rawDescData +} + +var file_offchainreporting3_1_db_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_offchainreporting3_1_db_proto_goTypes = []interface{}{ + (*PacemakerState)(nil), // 0: offchainreporting3_1.PacemakerState + (*StatePersistenceState)(nil), // 1: offchainreporting3_1.StatePersistenceState +} +var file_offchainreporting3_1_db_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_offchainreporting3_1_db_proto_init() } +func file_offchainreporting3_1_db_proto_init() { + if File_offchainreporting3_1_db_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_offchainreporting3_1_db_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PacemakerState); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_db_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatePersistenceState); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_offchainreporting3_1_db_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_offchainreporting3_1_db_proto_goTypes, + DependencyIndexes: file_offchainreporting3_1_db_proto_depIdxs, + MessageInfos: file_offchainreporting3_1_db_proto_msgTypes, + }.Build() + File_offchainreporting3_1_db_proto = out.File + file_offchainreporting3_1_db_proto_rawDesc = nil + file_offchainreporting3_1_db_proto_goTypes = nil + file_offchainreporting3_1_db_proto_depIdxs = nil +} diff --git a/offchainreporting2plus/internal/ocr3_1/serialization/offchainreporting3_1_messages.pb.go b/offchainreporting2plus/internal/ocr3_1/serialization/offchainreporting3_1_messages.pb.go new file mode 100644 index 00000000..4493cd5d --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/serialization/offchainreporting3_1_messages.pb.go @@ -0,0 +1,3677 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v4.25.1 +// source: offchainreporting3_1_messages.proto + +package serialization + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type MessageWrapper struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Msg: + // + // *MessageWrapper_MessageNewEpochWish + // *MessageWrapper_MessageEpochStartRequest + // *MessageWrapper_MessageEpochStart + // *MessageWrapper_MessageRoundStart + // *MessageWrapper_MessageObservation + // *MessageWrapper_MessageProposal + // *MessageWrapper_MessagePrepare + // *MessageWrapper_MessageCommit + // *MessageWrapper_MessageReportSignatures + // *MessageWrapper_MessageCertifiedCommitRequest + // *MessageWrapper_MessageCertifiedCommit + // *MessageWrapper_MessageBlockSyncRequest + // *MessageWrapper_MessageBlockSync + // *MessageWrapper_MessageBlockSyncSummary + // *MessageWrapper_MessageBlobOffer + // *MessageWrapper_MessageBlobChunkRequest + // *MessageWrapper_MessageBlobChunkResponse + // *MessageWrapper_MessageBlobAvailable + Msg isMessageWrapper_Msg `protobuf_oneof:"msg"` +} + +func (x *MessageWrapper) Reset() { + *x = MessageWrapper{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageWrapper) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageWrapper) ProtoMessage() {} + +func (x *MessageWrapper) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageWrapper.ProtoReflect.Descriptor instead. +func (*MessageWrapper) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{0} +} + +func (m *MessageWrapper) GetMsg() isMessageWrapper_Msg { + if m != nil { + return m.Msg + } + return nil +} + +func (x *MessageWrapper) GetMessageNewEpochWish() *MessageNewEpochWish { + if x, ok := x.GetMsg().(*MessageWrapper_MessageNewEpochWish); ok { + return x.MessageNewEpochWish + } + return nil +} + +func (x *MessageWrapper) GetMessageEpochStartRequest() *MessageEpochStartRequest { + if x, ok := x.GetMsg().(*MessageWrapper_MessageEpochStartRequest); ok { + return x.MessageEpochStartRequest + } + return nil +} + +func (x *MessageWrapper) GetMessageEpochStart() *MessageEpochStart { + if x, ok := x.GetMsg().(*MessageWrapper_MessageEpochStart); ok { + return x.MessageEpochStart + } + return nil +} + +func (x *MessageWrapper) GetMessageRoundStart() *MessageRoundStart { + if x, ok := x.GetMsg().(*MessageWrapper_MessageRoundStart); ok { + return x.MessageRoundStart + } + return nil +} + +func (x *MessageWrapper) GetMessageObservation() *MessageObservation { + if x, ok := x.GetMsg().(*MessageWrapper_MessageObservation); ok { + return x.MessageObservation + } + return nil +} + +func (x *MessageWrapper) GetMessageProposal() *MessageProposal { + if x, ok := x.GetMsg().(*MessageWrapper_MessageProposal); ok { + return x.MessageProposal + } + return nil +} + +func (x *MessageWrapper) GetMessagePrepare() *MessagePrepare { + if x, ok := x.GetMsg().(*MessageWrapper_MessagePrepare); ok { + return x.MessagePrepare + } + return nil +} + +func (x *MessageWrapper) GetMessageCommit() *MessageCommit { + if x, ok := x.GetMsg().(*MessageWrapper_MessageCommit); ok { + return x.MessageCommit + } + return nil +} + +func (x *MessageWrapper) GetMessageReportSignatures() *MessageReportSignatures { + if x, ok := x.GetMsg().(*MessageWrapper_MessageReportSignatures); ok { + return x.MessageReportSignatures + } + return nil +} + +func (x *MessageWrapper) GetMessageCertifiedCommitRequest() *MessageCertifiedCommitRequest { + if x, ok := x.GetMsg().(*MessageWrapper_MessageCertifiedCommitRequest); ok { + return x.MessageCertifiedCommitRequest + } + return nil +} + +func (x *MessageWrapper) GetMessageCertifiedCommit() *MessageCertifiedCommit { + if x, ok := x.GetMsg().(*MessageWrapper_MessageCertifiedCommit); ok { + return x.MessageCertifiedCommit + } + return nil +} + +func (x *MessageWrapper) GetMessageBlockSyncRequest() *MessageBlockSyncRequest { + if x, ok := x.GetMsg().(*MessageWrapper_MessageBlockSyncRequest); ok { + return x.MessageBlockSyncRequest + } + return nil +} + +func (x *MessageWrapper) GetMessageBlockSync() *MessageBlockSync { + if x, ok := x.GetMsg().(*MessageWrapper_MessageBlockSync); ok { + return x.MessageBlockSync + } + return nil +} + +func (x *MessageWrapper) GetMessageBlockSyncSummary() *MessageBlockSyncSummary { + if x, ok := x.GetMsg().(*MessageWrapper_MessageBlockSyncSummary); ok { + return x.MessageBlockSyncSummary + } + return nil +} + +func (x *MessageWrapper) GetMessageBlobOffer() *MessageBlobOffer { + if x, ok := x.GetMsg().(*MessageWrapper_MessageBlobOffer); ok { + return x.MessageBlobOffer + } + return nil +} + +func (x *MessageWrapper) GetMessageBlobChunkRequest() *MessageBlobChunkRequest { + if x, ok := x.GetMsg().(*MessageWrapper_MessageBlobChunkRequest); ok { + return x.MessageBlobChunkRequest + } + return nil +} + +func (x *MessageWrapper) GetMessageBlobChunkResponse() *MessageBlobChunkResponse { + if x, ok := x.GetMsg().(*MessageWrapper_MessageBlobChunkResponse); ok { + return x.MessageBlobChunkResponse + } + return nil +} + +func (x *MessageWrapper) GetMessageBlobAvailable() *MessageBlobAvailable { + if x, ok := x.GetMsg().(*MessageWrapper_MessageBlobAvailable); ok { + return x.MessageBlobAvailable + } + return nil +} + +type isMessageWrapper_Msg interface { + isMessageWrapper_Msg() +} + +type MessageWrapper_MessageNewEpochWish struct { + MessageNewEpochWish *MessageNewEpochWish `protobuf:"bytes,17,opt,name=message_new_epoch_wish,json=messageNewEpochWish,proto3,oneof"` +} + +type MessageWrapper_MessageEpochStartRequest struct { + MessageEpochStartRequest *MessageEpochStartRequest `protobuf:"bytes,18,opt,name=message_epoch_start_request,json=messageEpochStartRequest,proto3,oneof"` +} + +type MessageWrapper_MessageEpochStart struct { + MessageEpochStart *MessageEpochStart `protobuf:"bytes,19,opt,name=message_epoch_start,json=messageEpochStart,proto3,oneof"` +} + +type MessageWrapper_MessageRoundStart struct { + MessageRoundStart *MessageRoundStart `protobuf:"bytes,20,opt,name=message_round_start,json=messageRoundStart,proto3,oneof"` +} + +type MessageWrapper_MessageObservation struct { + MessageObservation *MessageObservation `protobuf:"bytes,21,opt,name=message_observation,json=messageObservation,proto3,oneof"` +} + +type MessageWrapper_MessageProposal struct { + MessageProposal *MessageProposal `protobuf:"bytes,22,opt,name=message_proposal,json=messageProposal,proto3,oneof"` +} + +type MessageWrapper_MessagePrepare struct { + MessagePrepare *MessagePrepare `protobuf:"bytes,23,opt,name=message_prepare,json=messagePrepare,proto3,oneof"` +} + +type MessageWrapper_MessageCommit struct { + MessageCommit *MessageCommit `protobuf:"bytes,24,opt,name=message_commit,json=messageCommit,proto3,oneof"` +} + +type MessageWrapper_MessageReportSignatures struct { + MessageReportSignatures *MessageReportSignatures `protobuf:"bytes,25,opt,name=message_report_signatures,json=messageReportSignatures,proto3,oneof"` +} + +type MessageWrapper_MessageCertifiedCommitRequest struct { + MessageCertifiedCommitRequest *MessageCertifiedCommitRequest `protobuf:"bytes,26,opt,name=message_certified_commit_request,json=messageCertifiedCommitRequest,proto3,oneof"` +} + +type MessageWrapper_MessageCertifiedCommit struct { + MessageCertifiedCommit *MessageCertifiedCommit `protobuf:"bytes,27,opt,name=message_certified_commit,json=messageCertifiedCommit,proto3,oneof"` +} + +type MessageWrapper_MessageBlockSyncRequest struct { + MessageBlockSyncRequest *MessageBlockSyncRequest `protobuf:"bytes,28,opt,name=message_block_sync_request,json=messageBlockSyncRequest,proto3,oneof"` +} + +type MessageWrapper_MessageBlockSync struct { + MessageBlockSync *MessageBlockSync `protobuf:"bytes,29,opt,name=message_block_sync,json=messageBlockSync,proto3,oneof"` +} + +type MessageWrapper_MessageBlockSyncSummary struct { + MessageBlockSyncSummary *MessageBlockSyncSummary `protobuf:"bytes,30,opt,name=message_block_sync_summary,json=messageBlockSyncSummary,proto3,oneof"` +} + +type MessageWrapper_MessageBlobOffer struct { + MessageBlobOffer *MessageBlobOffer `protobuf:"bytes,31,opt,name=message_blob_offer,json=messageBlobOffer,proto3,oneof"` +} + +type MessageWrapper_MessageBlobChunkRequest struct { + MessageBlobChunkRequest *MessageBlobChunkRequest `protobuf:"bytes,32,opt,name=message_blob_chunk_request,json=messageBlobChunkRequest,proto3,oneof"` +} + +type MessageWrapper_MessageBlobChunkResponse struct { + MessageBlobChunkResponse *MessageBlobChunkResponse `protobuf:"bytes,33,opt,name=message_blob_chunk_response,json=messageBlobChunkResponse,proto3,oneof"` +} + +type MessageWrapper_MessageBlobAvailable struct { + MessageBlobAvailable *MessageBlobAvailable `protobuf:"bytes,34,opt,name=message_blob_available,json=messageBlobAvailable,proto3,oneof"` +} + +func (*MessageWrapper_MessageNewEpochWish) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageEpochStartRequest) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageEpochStart) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageRoundStart) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageObservation) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageProposal) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessagePrepare) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageCommit) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageReportSignatures) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageCertifiedCommitRequest) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageCertifiedCommit) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageBlockSyncRequest) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageBlockSync) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageBlockSyncSummary) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageBlobOffer) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageBlobChunkRequest) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageBlobChunkResponse) isMessageWrapper_Msg() {} + +func (*MessageWrapper_MessageBlobAvailable) isMessageWrapper_Msg() {} + +type MessageNewEpochWish struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` +} + +func (x *MessageNewEpochWish) Reset() { + *x = MessageNewEpochWish{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageNewEpochWish) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageNewEpochWish) ProtoMessage() {} + +func (x *MessageNewEpochWish) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageNewEpochWish.ProtoReflect.Descriptor instead. +func (*MessageNewEpochWish) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{1} +} + +func (x *MessageNewEpochWish) GetEpoch() uint64 { + if x != nil { + return x.Epoch + } + return 0 +} + +type MessageEpochStartRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` + HighestCertified *CertifiedPrepareOrCommit `protobuf:"bytes,2,opt,name=highest_certified,json=highestCertified,proto3" json:"highest_certified,omitempty"` + SignedHighestCertifiedTimestamp *SignedHighestCertifiedTimestamp `protobuf:"bytes,3,opt,name=signed_highest_certified_timestamp,json=signedHighestCertifiedTimestamp,proto3" json:"signed_highest_certified_timestamp,omitempty"` +} + +func (x *MessageEpochStartRequest) Reset() { + *x = MessageEpochStartRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageEpochStartRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageEpochStartRequest) ProtoMessage() {} + +func (x *MessageEpochStartRequest) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageEpochStartRequest.ProtoReflect.Descriptor instead. +func (*MessageEpochStartRequest) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{2} +} + +func (x *MessageEpochStartRequest) GetEpoch() uint64 { + if x != nil { + return x.Epoch + } + return 0 +} + +func (x *MessageEpochStartRequest) GetHighestCertified() *CertifiedPrepareOrCommit { + if x != nil { + return x.HighestCertified + } + return nil +} + +func (x *MessageEpochStartRequest) GetSignedHighestCertifiedTimestamp() *SignedHighestCertifiedTimestamp { + if x != nil { + return x.SignedHighestCertifiedTimestamp + } + return nil +} + +type MessageEpochStart struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` + EpochStartProof *EpochStartProof `protobuf:"bytes,2,opt,name=epoch_start_proof,json=epochStartProof,proto3" json:"epoch_start_proof,omitempty"` +} + +func (x *MessageEpochStart) Reset() { + *x = MessageEpochStart{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageEpochStart) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageEpochStart) ProtoMessage() {} + +func (x *MessageEpochStart) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageEpochStart.ProtoReflect.Descriptor instead. +func (*MessageEpochStart) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{3} +} + +func (x *MessageEpochStart) GetEpoch() uint64 { + if x != nil { + return x.Epoch + } + return 0 +} + +func (x *MessageEpochStart) GetEpochStartProof() *EpochStartProof { + if x != nil { + return x.EpochStartProof + } + return nil +} + +type MessageRoundStart struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` + SeqNr uint64 `protobuf:"varint,2,opt,name=seq_nr,json=seqNr,proto3" json:"seq_nr,omitempty"` + Query []byte `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"` +} + +func (x *MessageRoundStart) Reset() { + *x = MessageRoundStart{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageRoundStart) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageRoundStart) ProtoMessage() {} + +func (x *MessageRoundStart) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageRoundStart.ProtoReflect.Descriptor instead. +func (*MessageRoundStart) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{4} +} + +func (x *MessageRoundStart) GetEpoch() uint64 { + if x != nil { + return x.Epoch + } + return 0 +} + +func (x *MessageRoundStart) GetSeqNr() uint64 { + if x != nil { + return x.SeqNr + } + return 0 +} + +func (x *MessageRoundStart) GetQuery() []byte { + if x != nil { + return x.Query + } + return nil +} + +type MessageObservation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` + SeqNr uint64 `protobuf:"varint,2,opt,name=seq_nr,json=seqNr,proto3" json:"seq_nr,omitempty"` + SignedObservation *SignedObservation `protobuf:"bytes,3,opt,name=signed_observation,json=signedObservation,proto3" json:"signed_observation,omitempty"` +} + +func (x *MessageObservation) Reset() { + *x = MessageObservation{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageObservation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageObservation) ProtoMessage() {} + +func (x *MessageObservation) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageObservation.ProtoReflect.Descriptor instead. +func (*MessageObservation) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{5} +} + +func (x *MessageObservation) GetEpoch() uint64 { + if x != nil { + return x.Epoch + } + return 0 +} + +func (x *MessageObservation) GetSeqNr() uint64 { + if x != nil { + return x.SeqNr + } + return 0 +} + +func (x *MessageObservation) GetSignedObservation() *SignedObservation { + if x != nil { + return x.SignedObservation + } + return nil +} + +type MessageProposal struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` + SeqNr uint64 `protobuf:"varint,2,opt,name=seq_nr,json=seqNr,proto3" json:"seq_nr,omitempty"` + AttributedSignedObservations []*AttributedSignedObservation `protobuf:"bytes,3,rep,name=attributed_signed_observations,json=attributedSignedObservations,proto3" json:"attributed_signed_observations,omitempty"` +} + +func (x *MessageProposal) Reset() { + *x = MessageProposal{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageProposal) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageProposal) ProtoMessage() {} + +func (x *MessageProposal) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageProposal.ProtoReflect.Descriptor instead. +func (*MessageProposal) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{6} +} + +func (x *MessageProposal) GetEpoch() uint64 { + if x != nil { + return x.Epoch + } + return 0 +} + +func (x *MessageProposal) GetSeqNr() uint64 { + if x != nil { + return x.SeqNr + } + return 0 +} + +func (x *MessageProposal) GetAttributedSignedObservations() []*AttributedSignedObservation { + if x != nil { + return x.AttributedSignedObservations + } + return nil +} + +type MessagePrepare struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` + SeqNr uint64 `protobuf:"varint,2,opt,name=seq_nr,json=seqNr,proto3" json:"seq_nr,omitempty"` + Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` +} + +func (x *MessagePrepare) Reset() { + *x = MessagePrepare{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessagePrepare) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessagePrepare) ProtoMessage() {} + +func (x *MessagePrepare) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessagePrepare.ProtoReflect.Descriptor instead. +func (*MessagePrepare) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{7} +} + +func (x *MessagePrepare) GetEpoch() uint64 { + if x != nil { + return x.Epoch + } + return 0 +} + +func (x *MessagePrepare) GetSeqNr() uint64 { + if x != nil { + return x.SeqNr + } + return 0 +} + +func (x *MessagePrepare) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +type MessageCommit struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` + SeqNr uint64 `protobuf:"varint,2,opt,name=seq_nr,json=seqNr,proto3" json:"seq_nr,omitempty"` + Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` +} + +func (x *MessageCommit) Reset() { + *x = MessageCommit{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageCommit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageCommit) ProtoMessage() {} + +func (x *MessageCommit) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageCommit.ProtoReflect.Descriptor instead. +func (*MessageCommit) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{8} +} + +func (x *MessageCommit) GetEpoch() uint64 { + if x != nil { + return x.Epoch + } + return 0 +} + +func (x *MessageCommit) GetSeqNr() uint64 { + if x != nil { + return x.SeqNr + } + return 0 +} + +func (x *MessageCommit) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +type MessageReportSignatures struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SeqNr uint64 `protobuf:"varint,1,opt,name=seq_nr,json=seqNr,proto3" json:"seq_nr,omitempty"` + ReportSignatures [][]byte `protobuf:"bytes,2,rep,name=report_signatures,json=reportSignatures,proto3" json:"report_signatures,omitempty"` +} + +func (x *MessageReportSignatures) Reset() { + *x = MessageReportSignatures{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageReportSignatures) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageReportSignatures) ProtoMessage() {} + +func (x *MessageReportSignatures) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageReportSignatures.ProtoReflect.Descriptor instead. +func (*MessageReportSignatures) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{9} +} + +func (x *MessageReportSignatures) GetSeqNr() uint64 { + if x != nil { + return x.SeqNr + } + return 0 +} + +func (x *MessageReportSignatures) GetReportSignatures() [][]byte { + if x != nil { + return x.ReportSignatures + } + return nil +} + +type MessageCertifiedCommitRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SeqNr uint64 `protobuf:"varint,1,opt,name=seq_nr,json=seqNr,proto3" json:"seq_nr,omitempty"` +} + +func (x *MessageCertifiedCommitRequest) Reset() { + *x = MessageCertifiedCommitRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageCertifiedCommitRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageCertifiedCommitRequest) ProtoMessage() {} + +func (x *MessageCertifiedCommitRequest) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageCertifiedCommitRequest.ProtoReflect.Descriptor instead. +func (*MessageCertifiedCommitRequest) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{10} +} + +func (x *MessageCertifiedCommitRequest) GetSeqNr() uint64 { + if x != nil { + return x.SeqNr + } + return 0 +} + +type MessageCertifiedCommit struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CertifiedCommittedReports *CertifiedCommittedReports `protobuf:"bytes,1,opt,name=certified_committed_reports,json=certifiedCommittedReports,proto3" json:"certified_committed_reports,omitempty"` +} + +func (x *MessageCertifiedCommit) Reset() { + *x = MessageCertifiedCommit{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageCertifiedCommit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageCertifiedCommit) ProtoMessage() {} + +func (x *MessageCertifiedCommit) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageCertifiedCommit.ProtoReflect.Descriptor instead. +func (*MessageCertifiedCommit) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{11} +} + +func (x *MessageCertifiedCommit) GetCertifiedCommittedReports() *CertifiedCommittedReports { + if x != nil { + return x.CertifiedCommittedReports + } + return nil +} + +type MessageBlockSyncRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + HighestCommittedSeqNr uint64 `protobuf:"varint,1,opt,name=highest_committed_seq_nr,json=highestCommittedSeqNr,proto3" json:"highest_committed_seq_nr,omitempty"` + Nonce uint64 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"` +} + +func (x *MessageBlockSyncRequest) Reset() { + *x = MessageBlockSyncRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageBlockSyncRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageBlockSyncRequest) ProtoMessage() {} + +func (x *MessageBlockSyncRequest) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageBlockSyncRequest.ProtoReflect.Descriptor instead. +func (*MessageBlockSyncRequest) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{12} +} + +func (x *MessageBlockSyncRequest) GetHighestCommittedSeqNr() uint64 { + if x != nil { + return x.HighestCommittedSeqNr + } + return 0 +} + +func (x *MessageBlockSyncRequest) GetNonce() uint64 { + if x != nil { + return x.Nonce + } + return 0 +} + +type MessageBlockSync struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AttestedStateTransitionBlocks []*AttestedStateTransitionBlock `protobuf:"bytes,1,rep,name=attested_state_transition_blocks,json=attestedStateTransitionBlocks,proto3" json:"attested_state_transition_blocks,omitempty"` + Nonce uint64 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"` +} + +func (x *MessageBlockSync) Reset() { + *x = MessageBlockSync{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageBlockSync) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageBlockSync) ProtoMessage() {} + +func (x *MessageBlockSync) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageBlockSync.ProtoReflect.Descriptor instead. +func (*MessageBlockSync) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{13} +} + +func (x *MessageBlockSync) GetAttestedStateTransitionBlocks() []*AttestedStateTransitionBlock { + if x != nil { + return x.AttestedStateTransitionBlocks + } + return nil +} + +func (x *MessageBlockSync) GetNonce() uint64 { + if x != nil { + return x.Nonce + } + return 0 +} + +type MessageBlockSyncSummary struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + LowestPersistedSeqNr uint64 `protobuf:"varint,1,opt,name=lowest_persisted_seq_nr,json=lowestPersistedSeqNr,proto3" json:"lowest_persisted_seq_nr,omitempty"` +} + +func (x *MessageBlockSyncSummary) Reset() { + *x = MessageBlockSyncSummary{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageBlockSyncSummary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageBlockSyncSummary) ProtoMessage() {} + +func (x *MessageBlockSyncSummary) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageBlockSyncSummary.ProtoReflect.Descriptor instead. +func (*MessageBlockSyncSummary) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{14} +} + +func (x *MessageBlockSyncSummary) GetLowestPersistedSeqNr() uint64 { + if x != nil { + return x.LowestPersistedSeqNr + } + return 0 +} + +type EpochStartProof struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + HighestCertified *CertifiedPrepareOrCommit `protobuf:"bytes,1,opt,name=highest_certified,json=highestCertified,proto3" json:"highest_certified,omitempty"` + HighestCertifiedProof []*AttributedSignedHighestCertifiedTimestamp `protobuf:"bytes,2,rep,name=highest_certified_proof,json=highestCertifiedProof,proto3" json:"highest_certified_proof,omitempty"` +} + +func (x *EpochStartProof) Reset() { + *x = EpochStartProof{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EpochStartProof) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EpochStartProof) ProtoMessage() {} + +func (x *EpochStartProof) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EpochStartProof.ProtoReflect.Descriptor instead. +func (*EpochStartProof) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{15} +} + +func (x *EpochStartProof) GetHighestCertified() *CertifiedPrepareOrCommit { + if x != nil { + return x.HighestCertified + } + return nil +} + +func (x *EpochStartProof) GetHighestCertifiedProof() []*AttributedSignedHighestCertifiedTimestamp { + if x != nil { + return x.HighestCertifiedProof + } + return nil +} + +type CertifiedPrepareOrCommit struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to PrepareOrCommit: + // + // *CertifiedPrepareOrCommit_Prepare + // *CertifiedPrepareOrCommit_Commit + PrepareOrCommit isCertifiedPrepareOrCommit_PrepareOrCommit `protobuf_oneof:"prepare_or_commit"` +} + +func (x *CertifiedPrepareOrCommit) Reset() { + *x = CertifiedPrepareOrCommit{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CertifiedPrepareOrCommit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CertifiedPrepareOrCommit) ProtoMessage() {} + +func (x *CertifiedPrepareOrCommit) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CertifiedPrepareOrCommit.ProtoReflect.Descriptor instead. +func (*CertifiedPrepareOrCommit) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{16} +} + +func (m *CertifiedPrepareOrCommit) GetPrepareOrCommit() isCertifiedPrepareOrCommit_PrepareOrCommit { + if m != nil { + return m.PrepareOrCommit + } + return nil +} + +func (x *CertifiedPrepareOrCommit) GetPrepare() *CertifiedPrepare { + if x, ok := x.GetPrepareOrCommit().(*CertifiedPrepareOrCommit_Prepare); ok { + return x.Prepare + } + return nil +} + +func (x *CertifiedPrepareOrCommit) GetCommit() *CertifiedCommit { + if x, ok := x.GetPrepareOrCommit().(*CertifiedPrepareOrCommit_Commit); ok { + return x.Commit + } + return nil +} + +type isCertifiedPrepareOrCommit_PrepareOrCommit interface { + isCertifiedPrepareOrCommit_PrepareOrCommit() +} + +type CertifiedPrepareOrCommit_Prepare struct { + Prepare *CertifiedPrepare `protobuf:"bytes,1,opt,name=prepare,proto3,oneof"` +} + +type CertifiedPrepareOrCommit_Commit struct { + Commit *CertifiedCommit `protobuf:"bytes,2,opt,name=commit,proto3,oneof"` +} + +func (*CertifiedPrepareOrCommit_Prepare) isCertifiedPrepareOrCommit_PrepareOrCommit() {} + +func (*CertifiedPrepareOrCommit_Commit) isCertifiedPrepareOrCommit_PrepareOrCommit() {} + +type CertifiedPrepare struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` + SeqNr uint64 `protobuf:"varint,2,opt,name=seq_nr,json=seqNr,proto3" json:"seq_nr,omitempty"` + StateTransitionInputsDigest []byte `protobuf:"bytes,3,opt,name=state_transition_inputs_digest,json=stateTransitionInputsDigest,proto3" json:"state_transition_inputs_digest,omitempty"` + StateTransitionOutputs *StateTransitionOutputs `protobuf:"bytes,4,opt,name=state_transition_outputs,json=stateTransitionOutputs,proto3" json:"state_transition_outputs,omitempty"` + ReportsPlusPrecursor []byte `protobuf:"bytes,5,opt,name=reports_plus_precursor,json=reportsPlusPrecursor,proto3" json:"reports_plus_precursor,omitempty"` + PrepareQuorumCertificate []*AttributedPrepareSignature `protobuf:"bytes,6,rep,name=prepare_quorum_certificate,json=prepareQuorumCertificate,proto3" json:"prepare_quorum_certificate,omitempty"` +} + +func (x *CertifiedPrepare) Reset() { + *x = CertifiedPrepare{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CertifiedPrepare) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CertifiedPrepare) ProtoMessage() {} + +func (x *CertifiedPrepare) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CertifiedPrepare.ProtoReflect.Descriptor instead. +func (*CertifiedPrepare) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{17} +} + +func (x *CertifiedPrepare) GetEpoch() uint64 { + if x != nil { + return x.Epoch + } + return 0 +} + +func (x *CertifiedPrepare) GetSeqNr() uint64 { + if x != nil { + return x.SeqNr + } + return 0 +} + +func (x *CertifiedPrepare) GetStateTransitionInputsDigest() []byte { + if x != nil { + return x.StateTransitionInputsDigest + } + return nil +} + +func (x *CertifiedPrepare) GetStateTransitionOutputs() *StateTransitionOutputs { + if x != nil { + return x.StateTransitionOutputs + } + return nil +} + +func (x *CertifiedPrepare) GetReportsPlusPrecursor() []byte { + if x != nil { + return x.ReportsPlusPrecursor + } + return nil +} + +func (x *CertifiedPrepare) GetPrepareQuorumCertificate() []*AttributedPrepareSignature { + if x != nil { + return x.PrepareQuorumCertificate + } + return nil +} + +type CertifiedCommit struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` + SeqNr uint64 `protobuf:"varint,2,opt,name=seq_nr,json=seqNr,proto3" json:"seq_nr,omitempty"` + StateTransitionInputsDigest []byte `protobuf:"bytes,3,opt,name=state_transition_inputs_digest,json=stateTransitionInputsDigest,proto3" json:"state_transition_inputs_digest,omitempty"` + StateTransitionOutputs *StateTransitionOutputs `protobuf:"bytes,4,opt,name=state_transition_outputs,json=stateTransitionOutputs,proto3" json:"state_transition_outputs,omitempty"` + ReportsPlusPrecursor []byte `protobuf:"bytes,5,opt,name=reports_plus_precursor,json=reportsPlusPrecursor,proto3" json:"reports_plus_precursor,omitempty"` + CommitQuorumCertificate []*AttributedCommitSignature `protobuf:"bytes,6,rep,name=commit_quorum_certificate,json=commitQuorumCertificate,proto3" json:"commit_quorum_certificate,omitempty"` +} + +func (x *CertifiedCommit) Reset() { + *x = CertifiedCommit{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CertifiedCommit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CertifiedCommit) ProtoMessage() {} + +func (x *CertifiedCommit) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CertifiedCommit.ProtoReflect.Descriptor instead. +func (*CertifiedCommit) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{18} +} + +func (x *CertifiedCommit) GetEpoch() uint64 { + if x != nil { + return x.Epoch + } + return 0 +} + +func (x *CertifiedCommit) GetSeqNr() uint64 { + if x != nil { + return x.SeqNr + } + return 0 +} + +func (x *CertifiedCommit) GetStateTransitionInputsDigest() []byte { + if x != nil { + return x.StateTransitionInputsDigest + } + return nil +} + +func (x *CertifiedCommit) GetStateTransitionOutputs() *StateTransitionOutputs { + if x != nil { + return x.StateTransitionOutputs + } + return nil +} + +func (x *CertifiedCommit) GetReportsPlusPrecursor() []byte { + if x != nil { + return x.ReportsPlusPrecursor + } + return nil +} + +func (x *CertifiedCommit) GetCommitQuorumCertificate() []*AttributedCommitSignature { + if x != nil { + return x.CommitQuorumCertificate + } + return nil +} + +type CertifiedCommittedReports struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CommitEpoch uint64 `protobuf:"varint,1,opt,name=commit_epoch,json=commitEpoch,proto3" json:"commit_epoch,omitempty"` + SeqNr uint64 `protobuf:"varint,2,opt,name=seq_nr,json=seqNr,proto3" json:"seq_nr,omitempty"` + StateTransitionInputsDigest []byte `protobuf:"bytes,3,opt,name=state_transition_inputs_digest,json=stateTransitionInputsDigest,proto3" json:"state_transition_inputs_digest,omitempty"` + StateTransitionOutputDigest []byte `protobuf:"bytes,4,opt,name=state_transition_output_digest,json=stateTransitionOutputDigest,proto3" json:"state_transition_output_digest,omitempty"` + ReportsPlusPrecursor []byte `protobuf:"bytes,5,opt,name=reports_plus_precursor,json=reportsPlusPrecursor,proto3" json:"reports_plus_precursor,omitempty"` + CommitQuorumCertificate []*AttributedCommitSignature `protobuf:"bytes,6,rep,name=commit_quorum_certificate,json=commitQuorumCertificate,proto3" json:"commit_quorum_certificate,omitempty"` +} + +func (x *CertifiedCommittedReports) Reset() { + *x = CertifiedCommittedReports{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CertifiedCommittedReports) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CertifiedCommittedReports) ProtoMessage() {} + +func (x *CertifiedCommittedReports) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CertifiedCommittedReports.ProtoReflect.Descriptor instead. +func (*CertifiedCommittedReports) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{19} +} + +func (x *CertifiedCommittedReports) GetCommitEpoch() uint64 { + if x != nil { + return x.CommitEpoch + } + return 0 +} + +func (x *CertifiedCommittedReports) GetSeqNr() uint64 { + if x != nil { + return x.SeqNr + } + return 0 +} + +func (x *CertifiedCommittedReports) GetStateTransitionInputsDigest() []byte { + if x != nil { + return x.StateTransitionInputsDigest + } + return nil +} + +func (x *CertifiedCommittedReports) GetStateTransitionOutputDigest() []byte { + if x != nil { + return x.StateTransitionOutputDigest + } + return nil +} + +func (x *CertifiedCommittedReports) GetReportsPlusPrecursor() []byte { + if x != nil { + return x.ReportsPlusPrecursor + } + return nil +} + +func (x *CertifiedCommittedReports) GetCommitQuorumCertificate() []*AttributedCommitSignature { + if x != nil { + return x.CommitQuorumCertificate + } + return nil +} + +type HighestCertifiedTimestamp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SeqNr uint64 `protobuf:"varint,1,opt,name=seq_nr,json=seqNr,proto3" json:"seq_nr,omitempty"` + CommittedElsePrepared bool `protobuf:"varint,2,opt,name=committed_else_prepared,json=committedElsePrepared,proto3" json:"committed_else_prepared,omitempty"` + Epoch uint64 `protobuf:"varint,3,opt,name=epoch,proto3" json:"epoch,omitempty"` +} + +func (x *HighestCertifiedTimestamp) Reset() { + *x = HighestCertifiedTimestamp{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HighestCertifiedTimestamp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HighestCertifiedTimestamp) ProtoMessage() {} + +func (x *HighestCertifiedTimestamp) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HighestCertifiedTimestamp.ProtoReflect.Descriptor instead. +func (*HighestCertifiedTimestamp) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{20} +} + +func (x *HighestCertifiedTimestamp) GetSeqNr() uint64 { + if x != nil { + return x.SeqNr + } + return 0 +} + +func (x *HighestCertifiedTimestamp) GetCommittedElsePrepared() bool { + if x != nil { + return x.CommittedElsePrepared + } + return false +} + +func (x *HighestCertifiedTimestamp) GetEpoch() uint64 { + if x != nil { + return x.Epoch + } + return 0 +} + +type AttributedSignedHighestCertifiedTimestamp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SignedHighestCertifiedTimestamp *SignedHighestCertifiedTimestamp `protobuf:"bytes,1,opt,name=signed_highest_certified_timestamp,json=signedHighestCertifiedTimestamp,proto3" json:"signed_highest_certified_timestamp,omitempty"` + Signer uint32 `protobuf:"varint,2,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (x *AttributedSignedHighestCertifiedTimestamp) Reset() { + *x = AttributedSignedHighestCertifiedTimestamp{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AttributedSignedHighestCertifiedTimestamp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributedSignedHighestCertifiedTimestamp) ProtoMessage() {} + +func (x *AttributedSignedHighestCertifiedTimestamp) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributedSignedHighestCertifiedTimestamp.ProtoReflect.Descriptor instead. +func (*AttributedSignedHighestCertifiedTimestamp) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{21} +} + +func (x *AttributedSignedHighestCertifiedTimestamp) GetSignedHighestCertifiedTimestamp() *SignedHighestCertifiedTimestamp { + if x != nil { + return x.SignedHighestCertifiedTimestamp + } + return nil +} + +func (x *AttributedSignedHighestCertifiedTimestamp) GetSigner() uint32 { + if x != nil { + return x.Signer + } + return 0 +} + +type SignedHighestCertifiedTimestamp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + HighestCertifiedTimestamp *HighestCertifiedTimestamp `protobuf:"bytes,1,opt,name=highest_certified_timestamp,json=highestCertifiedTimestamp,proto3" json:"highest_certified_timestamp,omitempty"` + Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` +} + +func (x *SignedHighestCertifiedTimestamp) Reset() { + *x = SignedHighestCertifiedTimestamp{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SignedHighestCertifiedTimestamp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignedHighestCertifiedTimestamp) ProtoMessage() {} + +func (x *SignedHighestCertifiedTimestamp) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignedHighestCertifiedTimestamp.ProtoReflect.Descriptor instead. +func (*SignedHighestCertifiedTimestamp) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{22} +} + +func (x *SignedHighestCertifiedTimestamp) GetHighestCertifiedTimestamp() *HighestCertifiedTimestamp { + if x != nil { + return x.HighestCertifiedTimestamp + } + return nil +} + +func (x *SignedHighestCertifiedTimestamp) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +type AttributedObservation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Observation []byte `protobuf:"bytes,1,opt,name=observation,proto3" json:"observation,omitempty"` + Observer uint32 `protobuf:"varint,2,opt,name=observer,proto3" json:"observer,omitempty"` +} + +func (x *AttributedObservation) Reset() { + *x = AttributedObservation{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AttributedObservation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributedObservation) ProtoMessage() {} + +func (x *AttributedObservation) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributedObservation.ProtoReflect.Descriptor instead. +func (*AttributedObservation) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{23} +} + +func (x *AttributedObservation) GetObservation() []byte { + if x != nil { + return x.Observation + } + return nil +} + +func (x *AttributedObservation) GetObserver() uint32 { + if x != nil { + return x.Observer + } + return 0 +} + +type AttributedSignedObservation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SignedObservation *SignedObservation `protobuf:"bytes,1,opt,name=signed_observation,json=signedObservation,proto3" json:"signed_observation,omitempty"` + Observer uint32 `protobuf:"varint,2,opt,name=observer,proto3" json:"observer,omitempty"` +} + +func (x *AttributedSignedObservation) Reset() { + *x = AttributedSignedObservation{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AttributedSignedObservation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributedSignedObservation) ProtoMessage() {} + +func (x *AttributedSignedObservation) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributedSignedObservation.ProtoReflect.Descriptor instead. +func (*AttributedSignedObservation) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{24} +} + +func (x *AttributedSignedObservation) GetSignedObservation() *SignedObservation { + if x != nil { + return x.SignedObservation + } + return nil +} + +func (x *AttributedSignedObservation) GetObserver() uint32 { + if x != nil { + return x.Observer + } + return 0 +} + +type SignedObservation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Observation []byte `protobuf:"bytes,1,opt,name=observation,proto3" json:"observation,omitempty"` + Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` +} + +func (x *SignedObservation) Reset() { + *x = SignedObservation{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SignedObservation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignedObservation) ProtoMessage() {} + +func (x *SignedObservation) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignedObservation.ProtoReflect.Descriptor instead. +func (*SignedObservation) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{25} +} + +func (x *SignedObservation) GetObservation() []byte { + if x != nil { + return x.Observation + } + return nil +} + +func (x *SignedObservation) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +type AttributedPrepareSignature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Signature []byte `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` + Signer uint32 `protobuf:"varint,2,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (x *AttributedPrepareSignature) Reset() { + *x = AttributedPrepareSignature{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AttributedPrepareSignature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributedPrepareSignature) ProtoMessage() {} + +func (x *AttributedPrepareSignature) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributedPrepareSignature.ProtoReflect.Descriptor instead. +func (*AttributedPrepareSignature) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{26} +} + +func (x *AttributedPrepareSignature) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *AttributedPrepareSignature) GetSigner() uint32 { + if x != nil { + return x.Signer + } + return 0 +} + +type AttributedCommitSignature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Signature []byte `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` + Signer uint32 `protobuf:"varint,2,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (x *AttributedCommitSignature) Reset() { + *x = AttributedCommitSignature{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AttributedCommitSignature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributedCommitSignature) ProtoMessage() {} + +func (x *AttributedCommitSignature) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributedCommitSignature.ProtoReflect.Descriptor instead. +func (*AttributedCommitSignature) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{27} +} + +func (x *AttributedCommitSignature) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *AttributedCommitSignature) GetSigner() uint32 { + if x != nil { + return x.Signer + } + return 0 +} + +type AttestedStateTransitionBlock struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StateTransitionBlock *StateTransitionBlock `protobuf:"bytes,1,opt,name=state_transition_block,json=stateTransitionBlock,proto3" json:"state_transition_block,omitempty"` + AttributedSignatures []*AttributedCommitSignature `protobuf:"bytes,2,rep,name=attributed_signatures,json=attributedSignatures,proto3" json:"attributed_signatures,omitempty"` +} + +func (x *AttestedStateTransitionBlock) Reset() { + *x = AttestedStateTransitionBlock{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AttestedStateTransitionBlock) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttestedStateTransitionBlock) ProtoMessage() {} + +func (x *AttestedStateTransitionBlock) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttestedStateTransitionBlock.ProtoReflect.Descriptor instead. +func (*AttestedStateTransitionBlock) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{28} +} + +func (x *AttestedStateTransitionBlock) GetStateTransitionBlock() *StateTransitionBlock { + if x != nil { + return x.StateTransitionBlock + } + return nil +} + +func (x *AttestedStateTransitionBlock) GetAttributedSignatures() []*AttributedCommitSignature { + if x != nil { + return x.AttributedSignatures + } + return nil +} + +type StateTransitionBlock struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` + SeqNr uint64 `protobuf:"varint,2,opt,name=seq_nr,json=seqNr,proto3" json:"seq_nr,omitempty"` + StateTransitionInputsDigest []byte `protobuf:"bytes,3,opt,name=state_transition_inputs_digest,json=stateTransitionInputsDigest,proto3" json:"state_transition_inputs_digest,omitempty"` + StateTransitionOutputs *StateTransitionOutputs `protobuf:"bytes,4,opt,name=state_transition_outputs,json=stateTransitionOutputs,proto3" json:"state_transition_outputs,omitempty"` + ReportsPlusPrecursor []byte `protobuf:"bytes,5,opt,name=reports_plus_precursor,json=reportsPlusPrecursor,proto3" json:"reports_plus_precursor,omitempty"` +} + +func (x *StateTransitionBlock) Reset() { + *x = StateTransitionBlock{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StateTransitionBlock) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StateTransitionBlock) ProtoMessage() {} + +func (x *StateTransitionBlock) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StateTransitionBlock.ProtoReflect.Descriptor instead. +func (*StateTransitionBlock) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{29} +} + +func (x *StateTransitionBlock) GetEpoch() uint64 { + if x != nil { + return x.Epoch + } + return 0 +} + +func (x *StateTransitionBlock) GetSeqNr() uint64 { + if x != nil { + return x.SeqNr + } + return 0 +} + +func (x *StateTransitionBlock) GetStateTransitionInputsDigest() []byte { + if x != nil { + return x.StateTransitionInputsDigest + } + return nil +} + +func (x *StateTransitionBlock) GetStateTransitionOutputs() *StateTransitionOutputs { + if x != nil { + return x.StateTransitionOutputs + } + return nil +} + +func (x *StateTransitionBlock) GetReportsPlusPrecursor() []byte { + if x != nil { + return x.ReportsPlusPrecursor + } + return nil +} + +type StateTransitionOutputs struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + WriteSet []*KeyValueModification `protobuf:"bytes,1,rep,name=write_set,json=writeSet,proto3" json:"write_set,omitempty"` +} + +func (x *StateTransitionOutputs) Reset() { + *x = StateTransitionOutputs{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StateTransitionOutputs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StateTransitionOutputs) ProtoMessage() {} + +func (x *StateTransitionOutputs) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StateTransitionOutputs.ProtoReflect.Descriptor instead. +func (*StateTransitionOutputs) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{30} +} + +func (x *StateTransitionOutputs) GetWriteSet() []*KeyValueModification { + if x != nil { + return x.WriteSet + } + return nil +} + +type KeyValueModification struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + Deleted bool `protobuf:"varint,3,opt,name=deleted,proto3" json:"deleted,omitempty"` +} + +func (x *KeyValueModification) Reset() { + *x = KeyValueModification{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *KeyValueModification) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KeyValueModification) ProtoMessage() {} + +func (x *KeyValueModification) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KeyValueModification.ProtoReflect.Descriptor instead. +func (*KeyValueModification) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{31} +} + +func (x *KeyValueModification) GetKey() []byte { + if x != nil { + return x.Key + } + return nil +} + +func (x *KeyValueModification) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +func (x *KeyValueModification) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} + +type StateTransitionInputs struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SeqNr uint64 `protobuf:"varint,1,opt,name=seq_nr,json=seqNr,proto3" json:"seq_nr,omitempty"` + Epoch uint64 `protobuf:"varint,2,opt,name=epoch,proto3" json:"epoch,omitempty"` + Round uint64 `protobuf:"varint,3,opt,name=round,proto3" json:"round,omitempty"` + Query []byte `protobuf:"bytes,4,opt,name=query,proto3" json:"query,omitempty"` + AttributedObservations []*AttributedObservation `protobuf:"bytes,5,rep,name=attributed_observations,json=attributedObservations,proto3" json:"attributed_observations,omitempty"` +} + +func (x *StateTransitionInputs) Reset() { + *x = StateTransitionInputs{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StateTransitionInputs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StateTransitionInputs) ProtoMessage() {} + +func (x *StateTransitionInputs) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[32] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StateTransitionInputs.ProtoReflect.Descriptor instead. +func (*StateTransitionInputs) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{32} +} + +func (x *StateTransitionInputs) GetSeqNr() uint64 { + if x != nil { + return x.SeqNr + } + return 0 +} + +func (x *StateTransitionInputs) GetEpoch() uint64 { + if x != nil { + return x.Epoch + } + return 0 +} + +func (x *StateTransitionInputs) GetRound() uint64 { + if x != nil { + return x.Round + } + return 0 +} + +func (x *StateTransitionInputs) GetQuery() []byte { + if x != nil { + return x.Query + } + return nil +} + +func (x *StateTransitionInputs) GetAttributedObservations() []*AttributedObservation { + if x != nil { + return x.AttributedObservations + } + return nil +} + +type MessageBlobOffer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ChunkDigests [][]byte `protobuf:"bytes,1,rep,name=chunk_digests,json=chunkDigests,proto3" json:"chunk_digests,omitempty"` + PayloadLength uint64 `protobuf:"varint,2,opt,name=payload_length,json=payloadLength,proto3" json:"payload_length,omitempty"` + ExpirySeqNr uint64 `protobuf:"varint,3,opt,name=expiry_seq_nr,json=expirySeqNr,proto3" json:"expiry_seq_nr,omitempty"` + Submitter uint32 `protobuf:"varint,4,opt,name=submitter,proto3" json:"submitter,omitempty"` +} + +func (x *MessageBlobOffer) Reset() { + *x = MessageBlobOffer{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageBlobOffer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageBlobOffer) ProtoMessage() {} + +func (x *MessageBlobOffer) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[33] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageBlobOffer.ProtoReflect.Descriptor instead. +func (*MessageBlobOffer) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{33} +} + +func (x *MessageBlobOffer) GetChunkDigests() [][]byte { + if x != nil { + return x.ChunkDigests + } + return nil +} + +func (x *MessageBlobOffer) GetPayloadLength() uint64 { + if x != nil { + return x.PayloadLength + } + return 0 +} + +func (x *MessageBlobOffer) GetExpirySeqNr() uint64 { + if x != nil { + return x.ExpirySeqNr + } + return 0 +} + +func (x *MessageBlobOffer) GetSubmitter() uint32 { + if x != nil { + return x.Submitter + } + return 0 +} + +type MessageBlobChunkRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BlobDigest []byte `protobuf:"bytes,1,opt,name=blob_digest,json=blobDigest,proto3" json:"blob_digest,omitempty"` + ChunkIndex uint64 `protobuf:"varint,2,opt,name=chunk_index,json=chunkIndex,proto3" json:"chunk_index,omitempty"` +} + +func (x *MessageBlobChunkRequest) Reset() { + *x = MessageBlobChunkRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageBlobChunkRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageBlobChunkRequest) ProtoMessage() {} + +func (x *MessageBlobChunkRequest) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[34] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageBlobChunkRequest.ProtoReflect.Descriptor instead. +func (*MessageBlobChunkRequest) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{34} +} + +func (x *MessageBlobChunkRequest) GetBlobDigest() []byte { + if x != nil { + return x.BlobDigest + } + return nil +} + +func (x *MessageBlobChunkRequest) GetChunkIndex() uint64 { + if x != nil { + return x.ChunkIndex + } + return 0 +} + +type MessageBlobChunkResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BlobDigest []byte `protobuf:"bytes,1,opt,name=blob_digest,json=blobDigest,proto3" json:"blob_digest,omitempty"` + ChunkIndex uint64 `protobuf:"varint,2,opt,name=chunk_index,json=chunkIndex,proto3" json:"chunk_index,omitempty"` + Chunk []byte `protobuf:"bytes,3,opt,name=chunk,proto3" json:"chunk,omitempty"` +} + +func (x *MessageBlobChunkResponse) Reset() { + *x = MessageBlobChunkResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageBlobChunkResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageBlobChunkResponse) ProtoMessage() {} + +func (x *MessageBlobChunkResponse) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[35] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageBlobChunkResponse.ProtoReflect.Descriptor instead. +func (*MessageBlobChunkResponse) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{35} +} + +func (x *MessageBlobChunkResponse) GetBlobDigest() []byte { + if x != nil { + return x.BlobDigest + } + return nil +} + +func (x *MessageBlobChunkResponse) GetChunkIndex() uint64 { + if x != nil { + return x.ChunkIndex + } + return 0 +} + +func (x *MessageBlobChunkResponse) GetChunk() []byte { + if x != nil { + return x.Chunk + } + return nil +} + +type MessageBlobAvailable struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BlobDigest []byte `protobuf:"bytes,1,opt,name=blob_digest,json=blobDigest,proto3" json:"blob_digest,omitempty"` + Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` +} + +func (x *MessageBlobAvailable) Reset() { + *x = MessageBlobAvailable{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageBlobAvailable) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageBlobAvailable) ProtoMessage() {} + +func (x *MessageBlobAvailable) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[36] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageBlobAvailable.ProtoReflect.Descriptor instead. +func (*MessageBlobAvailable) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{36} +} + +func (x *MessageBlobAvailable) GetBlobDigest() []byte { + if x != nil { + return x.BlobDigest + } + return nil +} + +func (x *MessageBlobAvailable) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +type AttributedBlobAvailabilitySignature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Signature []byte `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` + Signer uint32 `protobuf:"varint,2,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (x *AttributedBlobAvailabilitySignature) Reset() { + *x = AttributedBlobAvailabilitySignature{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AttributedBlobAvailabilitySignature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributedBlobAvailabilitySignature) ProtoMessage() {} + +func (x *AttributedBlobAvailabilitySignature) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_messages_proto_msgTypes[37] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributedBlobAvailabilitySignature.ProtoReflect.Descriptor instead. +func (*AttributedBlobAvailabilitySignature) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{37} +} + +func (x *AttributedBlobAvailabilitySignature) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *AttributedBlobAvailabilitySignature) GetSigner() uint32 { + if x != nil { + return x.Signer + } + return 0 +} + +var File_offchainreporting3_1_messages_proto protoreflect.FileDescriptor + +var file_offchainreporting3_1_messages_proto_rawDesc = []byte{ + 0x0a, 0x23, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x22, 0xa2, 0x0e, 0x0a, 0x0e, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x12, 0x60, + 0x0a, 0x16, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x65, 0x77, 0x5f, 0x65, 0x70, + 0x6f, 0x63, 0x68, 0x5f, 0x77, 0x69, 0x73, 0x68, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, + 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4e, 0x65, 0x77, + 0x45, 0x70, 0x6f, 0x63, 0x68, 0x57, 0x69, 0x73, 0x68, 0x48, 0x00, 0x52, 0x13, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x4e, 0x65, 0x77, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x57, 0x69, 0x73, 0x68, + 0x12, 0x6f, 0x0a, 0x1b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x65, 0x70, 0x6f, 0x63, + 0x68, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, + 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x18, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x45, 0x70, 0x6f, 0x63, 0x68, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x59, 0x0a, 0x13, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x65, 0x70, 0x6f, + 0x63, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, + 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x45, 0x70, 0x6f, + 0x63, 0x68, 0x53, 0x74, 0x61, 0x72, 0x74, 0x48, 0x00, 0x52, 0x11, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x59, 0x0a, 0x13, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x66, 0x66, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, + 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, + 0x72, 0x74, 0x48, 0x00, 0x52, 0x11, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x6f, 0x75, + 0x6e, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x5b, 0x0a, 0x13, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x15, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, + 0x52, 0x12, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x52, 0x0a, 0x10, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, + 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, + 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x50, 0x72, 0x6f, + 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x48, 0x00, 0x52, 0x0f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x4f, 0x0a, 0x0f, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x5f, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x12, 0x4c, 0x0a, 0x0e, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x18, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x23, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x6b, 0x0a, 0x19, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6f, 0x66, 0x66, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, + 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x48, 0x00, 0x52, 0x17, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x12, 0x7e, 0x0a, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, + 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, + 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x1d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x68, 0x0a, 0x18, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, + 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x18, 0x1b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x43, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x48, 0x00, 0x52, 0x16, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x6c, + 0x0a, 0x1a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, + 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x1c, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x48, 0x00, 0x52, 0x17, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x56, 0x0a, 0x12, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x73, 0x79, + 0x6e, 0x63, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x79, 0x6e, 0x63, + 0x48, 0x00, 0x52, 0x10, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x53, 0x79, 0x6e, 0x63, 0x12, 0x6c, 0x0a, 0x1a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x79, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x79, 0x6e, 0x63, + 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x48, 0x00, 0x52, 0x17, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x79, 0x12, 0x56, 0x0a, 0x12, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x6c, + 0x6f, 0x62, 0x5f, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, + 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, + 0x62, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x48, 0x00, 0x52, 0x10, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x12, 0x6c, 0x0a, 0x1a, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x63, 0x68, 0x75, 0x6e, 0x6b, + 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x20, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, + 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, + 0x62, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, + 0x17, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x43, 0x68, 0x75, 0x6e, + 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x6f, 0x0a, 0x1b, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x72, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x21, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, + 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, + 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x62, + 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, + 0x18, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x43, 0x68, 0x75, 0x6e, + 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x16, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, + 0x62, 0x6c, 0x65, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6f, 0x66, 0x66, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, + 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x41, 0x76, 0x61, 0x69, + 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x48, 0x00, 0x52, 0x14, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x42, 0x6c, 0x6f, 0x62, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x05, 0x0a, + 0x03, 0x6d, 0x73, 0x67, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x09, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x11, + 0x22, 0x2b, 0x0a, 0x13, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4e, 0x65, 0x77, 0x45, 0x70, + 0x6f, 0x63, 0x68, 0x57, 0x69, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x22, 0x92, 0x02, + 0x0a, 0x18, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, + 0x6f, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, + 0x12, 0x5b, 0x0a, 0x11, 0x68, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6f, 0x66, + 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, + 0x5f, 0x31, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x50, 0x72, 0x65, 0x70, + 0x61, 0x72, 0x65, 0x4f, 0x72, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x10, 0x68, 0x69, 0x67, + 0x68, 0x65, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x82, 0x01, + 0x0a, 0x22, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x68, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, + 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6f, 0x66, 0x66, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, + 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x48, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x1f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x48, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x22, 0x7c, 0x0a, 0x11, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x45, 0x70, 0x6f, + 0x63, 0x68, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x51, 0x0a, + 0x11, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x70, 0x72, 0x6f, + 0x6f, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, + 0x45, 0x70, 0x6f, 0x63, 0x68, 0x53, 0x74, 0x61, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, + 0x0f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x53, 0x74, 0x61, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, + 0x22, 0x56, 0x0a, 0x11, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x6f, 0x75, 0x6e, 0x64, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x15, 0x0a, 0x06, 0x73, + 0x65, 0x71, 0x5f, 0x6e, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x65, 0x71, + 0x4e, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x99, 0x01, 0x0a, 0x12, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, + 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x72, 0x12, 0x56, 0x0a, 0x12, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, + 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x11, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb7, 0x01, 0x0a, 0x0f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x15, + 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, + 0x73, 0x65, 0x71, 0x4e, 0x72, 0x12, 0x77, 0x0a, 0x1e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x64, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, + 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, + 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x53, + 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x1c, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x53, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x5b, + 0x0a, 0x0e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x72, 0x12, 0x1c, 0x0a, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x5a, 0x0a, 0x0d, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, + 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x65, 0x70, 0x6f, + 0x63, 0x68, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x72, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x5d, 0x0a, 0x17, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0c, 0x52, 0x10, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x22, 0x36, 0x0a, 0x1d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x72, 0x22, 0x89, + 0x01, 0x0a, 0x16, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x6f, 0x0a, 0x1b, 0x63, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, + 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, + 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x43, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, + 0x19, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x74, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x22, 0x68, 0x0a, 0x17, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x18, 0x68, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, + 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x71, 0x5f, 0x6e, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x68, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, + 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x53, 0x65, 0x71, 0x4e, 0x72, 0x12, 0x14, + 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6e, + 0x6f, 0x6e, 0x63, 0x65, 0x22, 0xa5, 0x01, 0x0a, 0x10, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x7b, 0x0a, 0x20, 0x61, 0x74, 0x74, + 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, + 0x74, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x1d, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, + 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x50, 0x0a, 0x17, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x79, 0x6e, 0x63, + 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x35, 0x0a, 0x17, 0x6c, 0x6f, 0x77, 0x65, 0x73, + 0x74, 0x5f, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x71, 0x5f, + 0x6e, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x6c, 0x6f, 0x77, 0x65, 0x73, 0x74, + 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x53, 0x65, 0x71, 0x4e, 0x72, 0x22, 0xe7, + 0x01, 0x0a, 0x0f, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x53, 0x74, 0x61, 0x72, 0x74, 0x50, 0x72, 0x6f, + 0x6f, 0x66, 0x12, 0x5b, 0x0a, 0x11, 0x68, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, + 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, + 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x50, 0x72, + 0x65, 0x70, 0x61, 0x72, 0x65, 0x4f, 0x72, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x10, 0x68, + 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, + 0x77, 0x0a, 0x17, 0x68, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x3f, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x48, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x15, 0x68, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0xb4, 0x01, 0x0a, 0x18, 0x43, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x4f, 0x72, 0x43, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x42, 0x0a, 0x07, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x48, 0x00, + 0x52, 0x07, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6f, 0x66, 0x66, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, + 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x42, 0x13, 0x0a, 0x11, 0x70, 0x72, + 0x65, 0x70, 0x61, 0x72, 0x65, 0x5f, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x22, + 0x92, 0x03, 0x0a, 0x10, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x50, 0x72, 0x65, + 0x70, 0x61, 0x72, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, + 0x71, 0x5f, 0x6e, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, + 0x72, 0x12, 0x43, 0x0a, 0x1e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x5f, 0x64, 0x69, 0x67, + 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x1b, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, + 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x66, 0x0a, 0x18, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x16, 0x73, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x34, + 0x0a, 0x16, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x5f, 0x70, 0x6c, 0x75, 0x73, 0x5f, 0x70, + 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x50, 0x6c, 0x75, 0x73, 0x50, 0x72, 0x65, 0x63, 0x75, + 0x72, 0x73, 0x6f, 0x72, 0x12, 0x6e, 0x0a, 0x1a, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x5f, + 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, + 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, + 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x18, 0x70, 0x72, 0x65, 0x70, + 0x61, 0x72, 0x65, 0x51, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x22, 0x8e, 0x03, 0x0a, 0x0f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x15, + 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, + 0x73, 0x65, 0x71, 0x4e, 0x72, 0x12, 0x43, 0x0a, 0x1e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, + 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x1b, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, + 0x70, 0x75, 0x74, 0x73, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x66, 0x0a, 0x18, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6f, + 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, + 0x33, 0x5f, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x16, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x5f, 0x70, 0x6c, + 0x75, 0x73, 0x5f, 0x70, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x14, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x50, 0x6c, 0x75, 0x73, 0x50, + 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x12, 0x6b, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x5f, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6f, 0x66, + 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, + 0x5f, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x17, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x22, 0x82, 0x03, 0x0a, 0x19, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x65, 0x70, + 0x6f, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x72, 0x12, 0x43, 0x0a, + 0x1e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x1b, 0x73, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x44, 0x69, 0x67, 0x65, + 0x73, 0x74, 0x12, 0x43, 0x0a, 0x1e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x69, + 0x67, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x1b, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x73, 0x5f, 0x70, 0x6c, 0x75, 0x73, 0x5f, 0x70, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x6f, + 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, + 0x50, 0x6c, 0x75, 0x73, 0x50, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x12, 0x6b, 0x0a, + 0x19, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x5f, 0x63, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x2f, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x52, 0x17, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x22, 0x80, 0x01, 0x0a, 0x19, 0x48, + 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, + 0x6e, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x72, 0x12, + 0x36, 0x0a, 0x17, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x5f, 0x65, 0x6c, 0x73, + 0x65, 0x5f, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x15, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x45, 0x6c, 0x73, 0x65, 0x50, + 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x22, 0xc8, 0x01, + 0x0a, 0x29, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x53, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x48, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x82, 0x01, 0x0a, 0x22, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x68, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x5f, 0x63, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, + 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x48, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x43, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x1f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x48, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x22, 0xb0, 0x01, 0x0a, 0x1f, 0x53, 0x69, 0x67, + 0x6e, 0x65, 0x64, 0x48, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x6f, 0x0a, 0x1b, + 0x68, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, + 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2f, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x48, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x19, 0x68, 0x69, 0x67, 0x68, 0x65, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1c, 0x0a, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x55, 0x0a, 0x15, 0x41, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x22, 0x91, 0x01, 0x0a, 0x1b, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x64, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x56, 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x6f, 0x62, 0x73, + 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, + 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x62, 0x73, 0x65, + 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, + 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x53, 0x0a, 0x11, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x6f, + 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x52, 0x0a, 0x1a, 0x41, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, + 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x22, + 0x51, 0x0a, 0x19, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x69, + 0x67, 0x6e, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x72, 0x22, 0xe6, 0x01, 0x0a, 0x1c, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x12, 0x60, 0x0a, 0x16, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, + 0x14, 0x73, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x64, 0x0a, 0x15, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x64, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x14, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x22, 0xa6, 0x02, 0x0a, 0x14, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, + 0x71, 0x5f, 0x6e, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, + 0x72, 0x12, 0x43, 0x0a, 0x1e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x5f, 0x64, 0x69, 0x67, + 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x1b, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, + 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x66, 0x0a, 0x18, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x16, 0x73, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x34, + 0x0a, 0x16, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x5f, 0x70, 0x6c, 0x75, 0x73, 0x5f, 0x70, + 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x50, 0x6c, 0x75, 0x73, 0x50, 0x72, 0x65, 0x63, 0x75, + 0x72, 0x73, 0x6f, 0x72, 0x22, 0x61, 0x0a, 0x16, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x47, + 0x0a, 0x09, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x2a, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x77, + 0x72, 0x69, 0x74, 0x65, 0x53, 0x65, 0x74, 0x22, 0x58, 0x0a, 0x14, 0x4b, 0x65, 0x79, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x64, 0x22, 0xd6, 0x01, 0x0a, 0x15, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x73, + 0x65, 0x71, 0x5f, 0x6e, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x65, 0x71, + 0x4e, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x14, + 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x12, 0x64, 0x0a, 0x17, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x64, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x41, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x16, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x4f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xa0, 0x01, 0x0a, 0x10, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x12, + 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x44, 0x69, 0x67, + 0x65, 0x73, 0x74, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, + 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x70, 0x61, + 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x22, 0x0a, 0x0d, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x72, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x53, 0x65, 0x71, 0x4e, 0x72, 0x12, + 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x09, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x22, 0x5b, 0x0a, + 0x17, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x43, 0x68, 0x75, 0x6e, + 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x62, + 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x62, + 0x6c, 0x6f, 0x62, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x68, 0x75, + 0x6e, 0x6b, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, + 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x72, 0x0a, 0x18, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x64, + 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x62, 0x6c, 0x6f, + 0x62, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x68, 0x75, 0x6e, 0x6b, + 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x63, 0x68, + 0x75, 0x6e, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x75, 0x6e, + 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x55, + 0x0a, 0x14, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x41, 0x76, 0x61, + 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x64, + 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x62, 0x6c, 0x6f, + 0x62, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x5b, 0x0a, 0x23, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x64, 0x42, 0x6c, 0x6f, 0x62, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x69, 0x6c, + 0x69, 0x74, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x69, + 0x67, 0x6e, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x72, 0x42, 0x11, 0x5a, 0x0f, 0x2e, 0x3b, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_offchainreporting3_1_messages_proto_rawDescOnce sync.Once + file_offchainreporting3_1_messages_proto_rawDescData = file_offchainreporting3_1_messages_proto_rawDesc +) + +func file_offchainreporting3_1_messages_proto_rawDescGZIP() []byte { + file_offchainreporting3_1_messages_proto_rawDescOnce.Do(func() { + file_offchainreporting3_1_messages_proto_rawDescData = protoimpl.X.CompressGZIP(file_offchainreporting3_1_messages_proto_rawDescData) + }) + return file_offchainreporting3_1_messages_proto_rawDescData +} + +var file_offchainreporting3_1_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 38) +var file_offchainreporting3_1_messages_proto_goTypes = []interface{}{ + (*MessageWrapper)(nil), // 0: offchainreporting3_1.MessageWrapper + (*MessageNewEpochWish)(nil), // 1: offchainreporting3_1.MessageNewEpochWish + (*MessageEpochStartRequest)(nil), // 2: offchainreporting3_1.MessageEpochStartRequest + (*MessageEpochStart)(nil), // 3: offchainreporting3_1.MessageEpochStart + (*MessageRoundStart)(nil), // 4: offchainreporting3_1.MessageRoundStart + (*MessageObservation)(nil), // 5: offchainreporting3_1.MessageObservation + (*MessageProposal)(nil), // 6: offchainreporting3_1.MessageProposal + (*MessagePrepare)(nil), // 7: offchainreporting3_1.MessagePrepare + (*MessageCommit)(nil), // 8: offchainreporting3_1.MessageCommit + (*MessageReportSignatures)(nil), // 9: offchainreporting3_1.MessageReportSignatures + (*MessageCertifiedCommitRequest)(nil), // 10: offchainreporting3_1.MessageCertifiedCommitRequest + (*MessageCertifiedCommit)(nil), // 11: offchainreporting3_1.MessageCertifiedCommit + (*MessageBlockSyncRequest)(nil), // 12: offchainreporting3_1.MessageBlockSyncRequest + (*MessageBlockSync)(nil), // 13: offchainreporting3_1.MessageBlockSync + (*MessageBlockSyncSummary)(nil), // 14: offchainreporting3_1.MessageBlockSyncSummary + (*EpochStartProof)(nil), // 15: offchainreporting3_1.EpochStartProof + (*CertifiedPrepareOrCommit)(nil), // 16: offchainreporting3_1.CertifiedPrepareOrCommit + (*CertifiedPrepare)(nil), // 17: offchainreporting3_1.CertifiedPrepare + (*CertifiedCommit)(nil), // 18: offchainreporting3_1.CertifiedCommit + (*CertifiedCommittedReports)(nil), // 19: offchainreporting3_1.CertifiedCommittedReports + (*HighestCertifiedTimestamp)(nil), // 20: offchainreporting3_1.HighestCertifiedTimestamp + (*AttributedSignedHighestCertifiedTimestamp)(nil), // 21: offchainreporting3_1.AttributedSignedHighestCertifiedTimestamp + (*SignedHighestCertifiedTimestamp)(nil), // 22: offchainreporting3_1.SignedHighestCertifiedTimestamp + (*AttributedObservation)(nil), // 23: offchainreporting3_1.AttributedObservation + (*AttributedSignedObservation)(nil), // 24: offchainreporting3_1.AttributedSignedObservation + (*SignedObservation)(nil), // 25: offchainreporting3_1.SignedObservation + (*AttributedPrepareSignature)(nil), // 26: offchainreporting3_1.AttributedPrepareSignature + (*AttributedCommitSignature)(nil), // 27: offchainreporting3_1.AttributedCommitSignature + (*AttestedStateTransitionBlock)(nil), // 28: offchainreporting3_1.AttestedStateTransitionBlock + (*StateTransitionBlock)(nil), // 29: offchainreporting3_1.StateTransitionBlock + (*StateTransitionOutputs)(nil), // 30: offchainreporting3_1.StateTransitionOutputs + (*KeyValueModification)(nil), // 31: offchainreporting3_1.KeyValueModification + (*StateTransitionInputs)(nil), // 32: offchainreporting3_1.StateTransitionInputs + (*MessageBlobOffer)(nil), // 33: offchainreporting3_1.MessageBlobOffer + (*MessageBlobChunkRequest)(nil), // 34: offchainreporting3_1.MessageBlobChunkRequest + (*MessageBlobChunkResponse)(nil), // 35: offchainreporting3_1.MessageBlobChunkResponse + (*MessageBlobAvailable)(nil), // 36: offchainreporting3_1.MessageBlobAvailable + (*AttributedBlobAvailabilitySignature)(nil), // 37: offchainreporting3_1.AttributedBlobAvailabilitySignature +} +var file_offchainreporting3_1_messages_proto_depIdxs = []int32{ + 1, // 0: offchainreporting3_1.MessageWrapper.message_new_epoch_wish:type_name -> offchainreporting3_1.MessageNewEpochWish + 2, // 1: offchainreporting3_1.MessageWrapper.message_epoch_start_request:type_name -> offchainreporting3_1.MessageEpochStartRequest + 3, // 2: offchainreporting3_1.MessageWrapper.message_epoch_start:type_name -> offchainreporting3_1.MessageEpochStart + 4, // 3: offchainreporting3_1.MessageWrapper.message_round_start:type_name -> offchainreporting3_1.MessageRoundStart + 5, // 4: offchainreporting3_1.MessageWrapper.message_observation:type_name -> offchainreporting3_1.MessageObservation + 6, // 5: offchainreporting3_1.MessageWrapper.message_proposal:type_name -> offchainreporting3_1.MessageProposal + 7, // 6: offchainreporting3_1.MessageWrapper.message_prepare:type_name -> offchainreporting3_1.MessagePrepare + 8, // 7: offchainreporting3_1.MessageWrapper.message_commit:type_name -> offchainreporting3_1.MessageCommit + 9, // 8: offchainreporting3_1.MessageWrapper.message_report_signatures:type_name -> offchainreporting3_1.MessageReportSignatures + 10, // 9: offchainreporting3_1.MessageWrapper.message_certified_commit_request:type_name -> offchainreporting3_1.MessageCertifiedCommitRequest + 11, // 10: offchainreporting3_1.MessageWrapper.message_certified_commit:type_name -> offchainreporting3_1.MessageCertifiedCommit + 12, // 11: offchainreporting3_1.MessageWrapper.message_block_sync_request:type_name -> offchainreporting3_1.MessageBlockSyncRequest + 13, // 12: offchainreporting3_1.MessageWrapper.message_block_sync:type_name -> offchainreporting3_1.MessageBlockSync + 14, // 13: offchainreporting3_1.MessageWrapper.message_block_sync_summary:type_name -> offchainreporting3_1.MessageBlockSyncSummary + 33, // 14: offchainreporting3_1.MessageWrapper.message_blob_offer:type_name -> offchainreporting3_1.MessageBlobOffer + 34, // 15: offchainreporting3_1.MessageWrapper.message_blob_chunk_request:type_name -> offchainreporting3_1.MessageBlobChunkRequest + 35, // 16: offchainreporting3_1.MessageWrapper.message_blob_chunk_response:type_name -> offchainreporting3_1.MessageBlobChunkResponse + 36, // 17: offchainreporting3_1.MessageWrapper.message_blob_available:type_name -> offchainreporting3_1.MessageBlobAvailable + 16, // 18: offchainreporting3_1.MessageEpochStartRequest.highest_certified:type_name -> offchainreporting3_1.CertifiedPrepareOrCommit + 22, // 19: offchainreporting3_1.MessageEpochStartRequest.signed_highest_certified_timestamp:type_name -> offchainreporting3_1.SignedHighestCertifiedTimestamp + 15, // 20: offchainreporting3_1.MessageEpochStart.epoch_start_proof:type_name -> offchainreporting3_1.EpochStartProof + 25, // 21: offchainreporting3_1.MessageObservation.signed_observation:type_name -> offchainreporting3_1.SignedObservation + 24, // 22: offchainreporting3_1.MessageProposal.attributed_signed_observations:type_name -> offchainreporting3_1.AttributedSignedObservation + 19, // 23: offchainreporting3_1.MessageCertifiedCommit.certified_committed_reports:type_name -> offchainreporting3_1.CertifiedCommittedReports + 28, // 24: offchainreporting3_1.MessageBlockSync.attested_state_transition_blocks:type_name -> offchainreporting3_1.AttestedStateTransitionBlock + 16, // 25: offchainreporting3_1.EpochStartProof.highest_certified:type_name -> offchainreporting3_1.CertifiedPrepareOrCommit + 21, // 26: offchainreporting3_1.EpochStartProof.highest_certified_proof:type_name -> offchainreporting3_1.AttributedSignedHighestCertifiedTimestamp + 17, // 27: offchainreporting3_1.CertifiedPrepareOrCommit.prepare:type_name -> offchainreporting3_1.CertifiedPrepare + 18, // 28: offchainreporting3_1.CertifiedPrepareOrCommit.commit:type_name -> offchainreporting3_1.CertifiedCommit + 30, // 29: offchainreporting3_1.CertifiedPrepare.state_transition_outputs:type_name -> offchainreporting3_1.StateTransitionOutputs + 26, // 30: offchainreporting3_1.CertifiedPrepare.prepare_quorum_certificate:type_name -> offchainreporting3_1.AttributedPrepareSignature + 30, // 31: offchainreporting3_1.CertifiedCommit.state_transition_outputs:type_name -> offchainreporting3_1.StateTransitionOutputs + 27, // 32: offchainreporting3_1.CertifiedCommit.commit_quorum_certificate:type_name -> offchainreporting3_1.AttributedCommitSignature + 27, // 33: offchainreporting3_1.CertifiedCommittedReports.commit_quorum_certificate:type_name -> offchainreporting3_1.AttributedCommitSignature + 22, // 34: offchainreporting3_1.AttributedSignedHighestCertifiedTimestamp.signed_highest_certified_timestamp:type_name -> offchainreporting3_1.SignedHighestCertifiedTimestamp + 20, // 35: offchainreporting3_1.SignedHighestCertifiedTimestamp.highest_certified_timestamp:type_name -> offchainreporting3_1.HighestCertifiedTimestamp + 25, // 36: offchainreporting3_1.AttributedSignedObservation.signed_observation:type_name -> offchainreporting3_1.SignedObservation + 29, // 37: offchainreporting3_1.AttestedStateTransitionBlock.state_transition_block:type_name -> offchainreporting3_1.StateTransitionBlock + 27, // 38: offchainreporting3_1.AttestedStateTransitionBlock.attributed_signatures:type_name -> offchainreporting3_1.AttributedCommitSignature + 30, // 39: offchainreporting3_1.StateTransitionBlock.state_transition_outputs:type_name -> offchainreporting3_1.StateTransitionOutputs + 31, // 40: offchainreporting3_1.StateTransitionOutputs.write_set:type_name -> offchainreporting3_1.KeyValueModification + 23, // 41: offchainreporting3_1.StateTransitionInputs.attributed_observations:type_name -> offchainreporting3_1.AttributedObservation + 42, // [42:42] is the sub-list for method output_type + 42, // [42:42] is the sub-list for method input_type + 42, // [42:42] is the sub-list for extension type_name + 42, // [42:42] is the sub-list for extension extendee + 0, // [0:42] is the sub-list for field type_name +} + +func init() { file_offchainreporting3_1_messages_proto_init() } +func file_offchainreporting3_1_messages_proto_init() { + if File_offchainreporting3_1_messages_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_offchainreporting3_1_messages_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageWrapper); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageNewEpochWish); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageEpochStartRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageEpochStart); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageRoundStart); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageObservation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageProposal); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessagePrepare); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageCommit); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageReportSignatures); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageCertifiedCommitRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageCertifiedCommit); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageBlockSyncRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageBlockSync); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageBlockSyncSummary); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EpochStartProof); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CertifiedPrepareOrCommit); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CertifiedPrepare); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CertifiedCommit); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CertifiedCommittedReports); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HighestCertifiedTimestamp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AttributedSignedHighestCertifiedTimestamp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignedHighestCertifiedTimestamp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AttributedObservation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AttributedSignedObservation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignedObservation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AttributedPrepareSignature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AttributedCommitSignature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AttestedStateTransitionBlock); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StateTransitionBlock); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StateTransitionOutputs); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeyValueModification); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StateTransitionInputs); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageBlobOffer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageBlobChunkRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageBlobChunkResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageBlobAvailable); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_messages_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AttributedBlobAvailabilitySignature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_offchainreporting3_1_messages_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*MessageWrapper_MessageNewEpochWish)(nil), + (*MessageWrapper_MessageEpochStartRequest)(nil), + (*MessageWrapper_MessageEpochStart)(nil), + (*MessageWrapper_MessageRoundStart)(nil), + (*MessageWrapper_MessageObservation)(nil), + (*MessageWrapper_MessageProposal)(nil), + (*MessageWrapper_MessagePrepare)(nil), + (*MessageWrapper_MessageCommit)(nil), + (*MessageWrapper_MessageReportSignatures)(nil), + (*MessageWrapper_MessageCertifiedCommitRequest)(nil), + (*MessageWrapper_MessageCertifiedCommit)(nil), + (*MessageWrapper_MessageBlockSyncRequest)(nil), + (*MessageWrapper_MessageBlockSync)(nil), + (*MessageWrapper_MessageBlockSyncSummary)(nil), + (*MessageWrapper_MessageBlobOffer)(nil), + (*MessageWrapper_MessageBlobChunkRequest)(nil), + (*MessageWrapper_MessageBlobChunkResponse)(nil), + (*MessageWrapper_MessageBlobAvailable)(nil), + } + file_offchainreporting3_1_messages_proto_msgTypes[16].OneofWrappers = []interface{}{ + (*CertifiedPrepareOrCommit_Prepare)(nil), + (*CertifiedPrepareOrCommit_Commit)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_offchainreporting3_1_messages_proto_rawDesc, + NumEnums: 0, + NumMessages: 38, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_offchainreporting3_1_messages_proto_goTypes, + DependencyIndexes: file_offchainreporting3_1_messages_proto_depIdxs, + MessageInfos: file_offchainreporting3_1_messages_proto_msgTypes, + }.Build() + File_offchainreporting3_1_messages_proto = out.File + file_offchainreporting3_1_messages_proto_rawDesc = nil + file_offchainreporting3_1_messages_proto_goTypes = nil + file_offchainreporting3_1_messages_proto_depIdxs = nil +} diff --git a/offchainreporting2plus/internal/ocr3_1/serialization/offchainreporting3_1_telemetry.pb.go b/offchainreporting2plus/internal/ocr3_1/serialization/offchainreporting3_1_telemetry.pb.go new file mode 100644 index 00000000..3d648d1d --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/serialization/offchainreporting3_1_telemetry.pb.go @@ -0,0 +1,837 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v4.25.1 +// source: offchainreporting3_1_telemetry.proto + +package serialization + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type TelemetryWrapper struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Wrapped: + // + // *TelemetryWrapper_MessageReceived + // *TelemetryWrapper_MessageBroadcast + // *TelemetryWrapper_MessageSent + // *TelemetryWrapper_AssertionViolation + // *TelemetryWrapper_RoundStarted + Wrapped isTelemetryWrapper_Wrapped `protobuf_oneof:"wrapped"` + UnixTimeNanoseconds int64 `protobuf:"varint,6,opt,name=unix_time_nanoseconds,json=unixTimeNanoseconds,proto3" json:"unix_time_nanoseconds,omitempty"` +} + +func (x *TelemetryWrapper) Reset() { + *x = TelemetryWrapper{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_telemetry_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TelemetryWrapper) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TelemetryWrapper) ProtoMessage() {} + +func (x *TelemetryWrapper) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_telemetry_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TelemetryWrapper.ProtoReflect.Descriptor instead. +func (*TelemetryWrapper) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_telemetry_proto_rawDescGZIP(), []int{0} +} + +func (m *TelemetryWrapper) GetWrapped() isTelemetryWrapper_Wrapped { + if m != nil { + return m.Wrapped + } + return nil +} + +func (x *TelemetryWrapper) GetMessageReceived() *TelemetryMessageReceived { + if x, ok := x.GetWrapped().(*TelemetryWrapper_MessageReceived); ok { + return x.MessageReceived + } + return nil +} + +func (x *TelemetryWrapper) GetMessageBroadcast() *TelemetryMessageBroadcast { + if x, ok := x.GetWrapped().(*TelemetryWrapper_MessageBroadcast); ok { + return x.MessageBroadcast + } + return nil +} + +func (x *TelemetryWrapper) GetMessageSent() *TelemetryMessageSent { + if x, ok := x.GetWrapped().(*TelemetryWrapper_MessageSent); ok { + return x.MessageSent + } + return nil +} + +func (x *TelemetryWrapper) GetAssertionViolation() *TelemetryAssertionViolation { + if x, ok := x.GetWrapped().(*TelemetryWrapper_AssertionViolation); ok { + return x.AssertionViolation + } + return nil +} + +func (x *TelemetryWrapper) GetRoundStarted() *TelemetryRoundStarted { + if x, ok := x.GetWrapped().(*TelemetryWrapper_RoundStarted); ok { + return x.RoundStarted + } + return nil +} + +func (x *TelemetryWrapper) GetUnixTimeNanoseconds() int64 { + if x != nil { + return x.UnixTimeNanoseconds + } + return 0 +} + +type isTelemetryWrapper_Wrapped interface { + isTelemetryWrapper_Wrapped() +} + +type TelemetryWrapper_MessageReceived struct { + MessageReceived *TelemetryMessageReceived `protobuf:"bytes,1,opt,name=message_received,json=messageReceived,proto3,oneof"` +} + +type TelemetryWrapper_MessageBroadcast struct { + MessageBroadcast *TelemetryMessageBroadcast `protobuf:"bytes,2,opt,name=message_broadcast,json=messageBroadcast,proto3,oneof"` +} + +type TelemetryWrapper_MessageSent struct { + MessageSent *TelemetryMessageSent `protobuf:"bytes,3,opt,name=message_sent,json=messageSent,proto3,oneof"` +} + +type TelemetryWrapper_AssertionViolation struct { + AssertionViolation *TelemetryAssertionViolation `protobuf:"bytes,4,opt,name=assertion_violation,json=assertionViolation,proto3,oneof"` +} + +type TelemetryWrapper_RoundStarted struct { + RoundStarted *TelemetryRoundStarted `protobuf:"bytes,5,opt,name=round_started,json=roundStarted,proto3,oneof"` +} + +func (*TelemetryWrapper_MessageReceived) isTelemetryWrapper_Wrapped() {} + +func (*TelemetryWrapper_MessageBroadcast) isTelemetryWrapper_Wrapped() {} + +func (*TelemetryWrapper_MessageSent) isTelemetryWrapper_Wrapped() {} + +func (*TelemetryWrapper_AssertionViolation) isTelemetryWrapper_Wrapped() {} + +func (*TelemetryWrapper_RoundStarted) isTelemetryWrapper_Wrapped() {} + +type TelemetryMessageReceived struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ConfigDigest []byte `protobuf:"bytes,1,opt,name=config_digest,json=configDigest,proto3" json:"config_digest,omitempty"` + Msg *MessageWrapper `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` + Sender uint32 `protobuf:"varint,3,opt,name=sender,proto3" json:"sender,omitempty"` +} + +func (x *TelemetryMessageReceived) Reset() { + *x = TelemetryMessageReceived{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_telemetry_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TelemetryMessageReceived) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TelemetryMessageReceived) ProtoMessage() {} + +func (x *TelemetryMessageReceived) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_telemetry_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TelemetryMessageReceived.ProtoReflect.Descriptor instead. +func (*TelemetryMessageReceived) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_telemetry_proto_rawDescGZIP(), []int{1} +} + +func (x *TelemetryMessageReceived) GetConfigDigest() []byte { + if x != nil { + return x.ConfigDigest + } + return nil +} + +func (x *TelemetryMessageReceived) GetMsg() *MessageWrapper { + if x != nil { + return x.Msg + } + return nil +} + +func (x *TelemetryMessageReceived) GetSender() uint32 { + if x != nil { + return x.Sender + } + return 0 +} + +type TelemetryMessageBroadcast struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ConfigDigest []byte `protobuf:"bytes,1,opt,name=config_digest,json=configDigest,proto3" json:"config_digest,omitempty"` + Msg *MessageWrapper `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` + SerializedMsg []byte `protobuf:"bytes,3,opt,name=serialized_msg,json=serializedMsg,proto3" json:"serialized_msg,omitempty"` +} + +func (x *TelemetryMessageBroadcast) Reset() { + *x = TelemetryMessageBroadcast{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_telemetry_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TelemetryMessageBroadcast) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TelemetryMessageBroadcast) ProtoMessage() {} + +func (x *TelemetryMessageBroadcast) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_telemetry_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TelemetryMessageBroadcast.ProtoReflect.Descriptor instead. +func (*TelemetryMessageBroadcast) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_telemetry_proto_rawDescGZIP(), []int{2} +} + +func (x *TelemetryMessageBroadcast) GetConfigDigest() []byte { + if x != nil { + return x.ConfigDigest + } + return nil +} + +func (x *TelemetryMessageBroadcast) GetMsg() *MessageWrapper { + if x != nil { + return x.Msg + } + return nil +} + +func (x *TelemetryMessageBroadcast) GetSerializedMsg() []byte { + if x != nil { + return x.SerializedMsg + } + return nil +} + +type TelemetryMessageSent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ConfigDigest []byte `protobuf:"bytes,1,opt,name=config_digest,json=configDigest,proto3" json:"config_digest,omitempty"` + Msg *MessageWrapper `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` + SerializedMsg []byte `protobuf:"bytes,3,opt,name=serialized_msg,json=serializedMsg,proto3" json:"serialized_msg,omitempty"` + Receiver uint32 `protobuf:"varint,4,opt,name=receiver,proto3" json:"receiver,omitempty"` +} + +func (x *TelemetryMessageSent) Reset() { + *x = TelemetryMessageSent{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_telemetry_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TelemetryMessageSent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TelemetryMessageSent) ProtoMessage() {} + +func (x *TelemetryMessageSent) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_telemetry_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TelemetryMessageSent.ProtoReflect.Descriptor instead. +func (*TelemetryMessageSent) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_telemetry_proto_rawDescGZIP(), []int{3} +} + +func (x *TelemetryMessageSent) GetConfigDigest() []byte { + if x != nil { + return x.ConfigDigest + } + return nil +} + +func (x *TelemetryMessageSent) GetMsg() *MessageWrapper { + if x != nil { + return x.Msg + } + return nil +} + +func (x *TelemetryMessageSent) GetSerializedMsg() []byte { + if x != nil { + return x.SerializedMsg + } + return nil +} + +func (x *TelemetryMessageSent) GetReceiver() uint32 { + if x != nil { + return x.Receiver + } + return 0 +} + +type TelemetryAssertionViolation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Violation: + // + // *TelemetryAssertionViolation_InvalidSerialization + Violation isTelemetryAssertionViolation_Violation `protobuf_oneof:"violation"` +} + +func (x *TelemetryAssertionViolation) Reset() { + *x = TelemetryAssertionViolation{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_telemetry_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TelemetryAssertionViolation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TelemetryAssertionViolation) ProtoMessage() {} + +func (x *TelemetryAssertionViolation) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_telemetry_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TelemetryAssertionViolation.ProtoReflect.Descriptor instead. +func (*TelemetryAssertionViolation) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_telemetry_proto_rawDescGZIP(), []int{4} +} + +func (m *TelemetryAssertionViolation) GetViolation() isTelemetryAssertionViolation_Violation { + if m != nil { + return m.Violation + } + return nil +} + +func (x *TelemetryAssertionViolation) GetInvalidSerialization() *TelemetryAssertionViolationInvalidSerialization { + if x, ok := x.GetViolation().(*TelemetryAssertionViolation_InvalidSerialization); ok { + return x.InvalidSerialization + } + return nil +} + +type isTelemetryAssertionViolation_Violation interface { + isTelemetryAssertionViolation_Violation() +} + +type TelemetryAssertionViolation_InvalidSerialization struct { + InvalidSerialization *TelemetryAssertionViolationInvalidSerialization `protobuf:"bytes,2,opt,name=invalid_serialization,json=invalidSerialization,proto3,oneof"` +} + +func (*TelemetryAssertionViolation_InvalidSerialization) isTelemetryAssertionViolation_Violation() {} + +type TelemetryAssertionViolationInvalidSerialization struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ConfigDigest []byte `protobuf:"bytes,1,opt,name=config_digest,json=configDigest,proto3" json:"config_digest,omitempty"` + SerializedMsg []byte `protobuf:"bytes,2,opt,name=serialized_msg,json=serializedMsg,proto3" json:"serialized_msg,omitempty"` + Sender uint32 `protobuf:"varint,3,opt,name=sender,proto3" json:"sender,omitempty"` +} + +func (x *TelemetryAssertionViolationInvalidSerialization) Reset() { + *x = TelemetryAssertionViolationInvalidSerialization{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_telemetry_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TelemetryAssertionViolationInvalidSerialization) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TelemetryAssertionViolationInvalidSerialization) ProtoMessage() {} + +func (x *TelemetryAssertionViolationInvalidSerialization) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_telemetry_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TelemetryAssertionViolationInvalidSerialization.ProtoReflect.Descriptor instead. +func (*TelemetryAssertionViolationInvalidSerialization) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_telemetry_proto_rawDescGZIP(), []int{5} +} + +func (x *TelemetryAssertionViolationInvalidSerialization) GetConfigDigest() []byte { + if x != nil { + return x.ConfigDigest + } + return nil +} + +func (x *TelemetryAssertionViolationInvalidSerialization) GetSerializedMsg() []byte { + if x != nil { + return x.SerializedMsg + } + return nil +} + +func (x *TelemetryAssertionViolationInvalidSerialization) GetSender() uint32 { + if x != nil { + return x.Sender + } + return 0 +} + +type TelemetryRoundStarted struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ConfigDigest []byte `protobuf:"bytes,1,opt,name=config_digest,json=configDigest,proto3" json:"config_digest,omitempty"` + Epoch uint64 `protobuf:"varint,2,opt,name=epoch,proto3" json:"epoch,omitempty"` + Round uint64 `protobuf:"varint,3,opt,name=round,proto3" json:"round,omitempty"` + Leader uint64 `protobuf:"varint,4,opt,name=leader,proto3" json:"leader,omitempty"` + Time uint64 `protobuf:"varint,5,opt,name=time,proto3" json:"time,omitempty"` + SeqNr uint64 `protobuf:"varint,6,opt,name=seq_nr,json=seqNr,proto3" json:"seq_nr,omitempty"` +} + +func (x *TelemetryRoundStarted) Reset() { + *x = TelemetryRoundStarted{} + if protoimpl.UnsafeEnabled { + mi := &file_offchainreporting3_1_telemetry_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TelemetryRoundStarted) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TelemetryRoundStarted) ProtoMessage() {} + +func (x *TelemetryRoundStarted) ProtoReflect() protoreflect.Message { + mi := &file_offchainreporting3_1_telemetry_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TelemetryRoundStarted.ProtoReflect.Descriptor instead. +func (*TelemetryRoundStarted) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_telemetry_proto_rawDescGZIP(), []int{6} +} + +func (x *TelemetryRoundStarted) GetConfigDigest() []byte { + if x != nil { + return x.ConfigDigest + } + return nil +} + +func (x *TelemetryRoundStarted) GetEpoch() uint64 { + if x != nil { + return x.Epoch + } + return 0 +} + +func (x *TelemetryRoundStarted) GetRound() uint64 { + if x != nil { + return x.Round + } + return 0 +} + +func (x *TelemetryRoundStarted) GetLeader() uint64 { + if x != nil { + return x.Leader + } + return 0 +} + +func (x *TelemetryRoundStarted) GetTime() uint64 { + if x != nil { + return x.Time + } + return 0 +} + +func (x *TelemetryRoundStarted) GetSeqNr() uint64 { + if x != nil { + return x.SeqNr + } + return 0 +} + +var File_offchainreporting3_1_telemetry_proto protoreflect.FileDescriptor + +var file_offchainreporting3_1_telemetry_proto_rawDesc = []byte{ + 0x0a, 0x24, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x5f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x1a, 0x23, 0x6f, 0x66, + 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, + 0x5f, 0x31, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0x99, 0x04, 0x0a, 0x10, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x57, + 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x12, 0x5b, 0x0a, 0x10, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2e, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, + 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, + 0x48, 0x00, 0x52, 0x0f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, + 0x76, 0x65, 0x64, 0x12, 0x5e, 0x0a, 0x11, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x62, + 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, + 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x48, + 0x00, 0x52, 0x10, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, + 0x61, 0x73, 0x74, 0x12, 0x4f, 0x0a, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x73, + 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6f, 0x66, 0x66, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, + 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x53, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x53, 0x65, 0x6e, 0x74, 0x12, 0x64, 0x0a, 0x13, 0x61, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x76, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x31, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, + 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x6f, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x12, 0x61, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, + 0x6e, 0x56, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x52, 0x0a, 0x0d, 0x72, 0x6f, + 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2b, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, + 0x72, 0x79, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x48, 0x00, + 0x52, 0x0c, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x32, + 0x0a, 0x15, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, + 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x75, + 0x6e, 0x69, 0x78, 0x54, 0x69, 0x6d, 0x65, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x65, 0x63, 0x6f, 0x6e, + 0x64, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, 0x22, 0x8f, 0x01, + 0x0a, 0x18, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, + 0x36, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, + 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, + 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, + 0x65, 0x72, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x22, + 0x9f, 0x01, 0x0a, 0x19, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x23, 0x0a, + 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x67, 0x65, + 0x73, 0x74, 0x12, 0x36, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x24, 0x2e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x72, + 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0d, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x4d, 0x73, + 0x67, 0x22, 0xb6, 0x01, 0x0a, 0x14, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, + 0x36, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, + 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, + 0x33, 0x5f, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, + 0x65, 0x72, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0d, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x4d, 0x73, 0x67, 0x12, 0x1a, + 0x0a, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x22, 0xae, 0x01, 0x0a, 0x1b, 0x54, + 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, + 0x6e, 0x56, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x7c, 0x0a, 0x15, 0x69, 0x6e, + 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x45, 0x2e, 0x6f, 0x66, 0x66, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x33, 0x5f, 0x31, + 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, + 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x48, 0x00, 0x52, 0x14, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x53, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x76, 0x69, 0x6f, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x95, 0x01, 0x0a, 0x2f, + 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, + 0x6f, 0x6e, 0x56, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x76, 0x61, 0x6c, + 0x69, 0x64, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, + 0x67, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, + 0x65, 0x64, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x73, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x4d, 0x73, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x73, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x22, 0xab, 0x01, 0x0a, 0x15, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, + 0x79, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x23, 0x0a, + 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x67, 0x65, + 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, + 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, + 0x71, 0x5f, 0x6e, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, + 0x72, 0x42, 0x11, 0x5a, 0x0f, 0x2e, 0x3b, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_offchainreporting3_1_telemetry_proto_rawDescOnce sync.Once + file_offchainreporting3_1_telemetry_proto_rawDescData = file_offchainreporting3_1_telemetry_proto_rawDesc +) + +func file_offchainreporting3_1_telemetry_proto_rawDescGZIP() []byte { + file_offchainreporting3_1_telemetry_proto_rawDescOnce.Do(func() { + file_offchainreporting3_1_telemetry_proto_rawDescData = protoimpl.X.CompressGZIP(file_offchainreporting3_1_telemetry_proto_rawDescData) + }) + return file_offchainreporting3_1_telemetry_proto_rawDescData +} + +var file_offchainreporting3_1_telemetry_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_offchainreporting3_1_telemetry_proto_goTypes = []interface{}{ + (*TelemetryWrapper)(nil), // 0: offchainreporting3_1.TelemetryWrapper + (*TelemetryMessageReceived)(nil), // 1: offchainreporting3_1.TelemetryMessageReceived + (*TelemetryMessageBroadcast)(nil), // 2: offchainreporting3_1.TelemetryMessageBroadcast + (*TelemetryMessageSent)(nil), // 3: offchainreporting3_1.TelemetryMessageSent + (*TelemetryAssertionViolation)(nil), // 4: offchainreporting3_1.TelemetryAssertionViolation + (*TelemetryAssertionViolationInvalidSerialization)(nil), // 5: offchainreporting3_1.TelemetryAssertionViolationInvalidSerialization + (*TelemetryRoundStarted)(nil), // 6: offchainreporting3_1.TelemetryRoundStarted + (*MessageWrapper)(nil), // 7: offchainreporting3_1.MessageWrapper +} +var file_offchainreporting3_1_telemetry_proto_depIdxs = []int32{ + 1, // 0: offchainreporting3_1.TelemetryWrapper.message_received:type_name -> offchainreporting3_1.TelemetryMessageReceived + 2, // 1: offchainreporting3_1.TelemetryWrapper.message_broadcast:type_name -> offchainreporting3_1.TelemetryMessageBroadcast + 3, // 2: offchainreporting3_1.TelemetryWrapper.message_sent:type_name -> offchainreporting3_1.TelemetryMessageSent + 4, // 3: offchainreporting3_1.TelemetryWrapper.assertion_violation:type_name -> offchainreporting3_1.TelemetryAssertionViolation + 6, // 4: offchainreporting3_1.TelemetryWrapper.round_started:type_name -> offchainreporting3_1.TelemetryRoundStarted + 7, // 5: offchainreporting3_1.TelemetryMessageReceived.msg:type_name -> offchainreporting3_1.MessageWrapper + 7, // 6: offchainreporting3_1.TelemetryMessageBroadcast.msg:type_name -> offchainreporting3_1.MessageWrapper + 7, // 7: offchainreporting3_1.TelemetryMessageSent.msg:type_name -> offchainreporting3_1.MessageWrapper + 5, // 8: offchainreporting3_1.TelemetryAssertionViolation.invalid_serialization:type_name -> offchainreporting3_1.TelemetryAssertionViolationInvalidSerialization + 9, // [9:9] is the sub-list for method output_type + 9, // [9:9] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name +} + +func init() { file_offchainreporting3_1_telemetry_proto_init() } +func file_offchainreporting3_1_telemetry_proto_init() { + if File_offchainreporting3_1_telemetry_proto != nil { + return + } + file_offchainreporting3_1_messages_proto_init() + if !protoimpl.UnsafeEnabled { + file_offchainreporting3_1_telemetry_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TelemetryWrapper); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_telemetry_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TelemetryMessageReceived); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_telemetry_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TelemetryMessageBroadcast); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_telemetry_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TelemetryMessageSent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_telemetry_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TelemetryAssertionViolation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_telemetry_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TelemetryAssertionViolationInvalidSerialization); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_offchainreporting3_1_telemetry_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TelemetryRoundStarted); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_offchainreporting3_1_telemetry_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*TelemetryWrapper_MessageReceived)(nil), + (*TelemetryWrapper_MessageBroadcast)(nil), + (*TelemetryWrapper_MessageSent)(nil), + (*TelemetryWrapper_AssertionViolation)(nil), + (*TelemetryWrapper_RoundStarted)(nil), + } + file_offchainreporting3_1_telemetry_proto_msgTypes[4].OneofWrappers = []interface{}{ + (*TelemetryAssertionViolation_InvalidSerialization)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_offchainreporting3_1_telemetry_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_offchainreporting3_1_telemetry_proto_goTypes, + DependencyIndexes: file_offchainreporting3_1_telemetry_proto_depIdxs, + MessageInfos: file_offchainreporting3_1_telemetry_proto_msgTypes, + }.Build() + File_offchainreporting3_1_telemetry_proto = out.File + file_offchainreporting3_1_telemetry_proto_rawDesc = nil + file_offchainreporting3_1_telemetry_proto_goTypes = nil + file_offchainreporting3_1_telemetry_proto_depIdxs = nil +} diff --git a/offchainreporting2plus/internal/ocr3_1/serialization/serialization.go b/offchainreporting2plus/internal/ocr3_1/serialization/serialization.go new file mode 100644 index 00000000..d778e102 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/serialization/serialization.go @@ -0,0 +1,1239 @@ +package serialization + +import ( + "fmt" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3_1/protocol" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "google.golang.org/protobuf/proto" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +// Serialize encodes a protocol.Message into a binary payload +func Serialize[RI any](m protocol.Message[RI]) ([]byte, *MessageWrapper, error) { + tpm := toProtoMessage[RI]{} + pb, err := tpm.messageWrapper(m) + if err != nil { + return nil, nil, err + } + b, err := proto.Marshal(pb) + if err != nil { + return nil, nil, err + } + return b, pb, nil +} + +func SerializeCertifiedPrepareOrCommit(cpoc protocol.CertifiedPrepareOrCommit) ([]byte, error) { + if cpoc == nil { + return nil, fmt.Errorf("cannot serialize nil CertifiedPrepareOrCommit") + } + + tpm := toProtoMessage[struct{}]{} + + return proto.Marshal(tpm.certifiedPrepareOrCommit(cpoc)) +} + +func SerializePacemakerState(m protocol.PacemakerState) ([]byte, error) { + pb := PacemakerState{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + m.Epoch, + m.HighestSentNewEpochWish, + } + + return proto.Marshal(&pb) +} + +func SerializeStatePersistenceState(m protocol.StatePersistenceState) ([]byte, error) { + pb := StatePersistenceState{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + m.HighestPersistedStateTransitionBlockSeqNr, + } + return proto.Marshal(&pb) +} + +func SerializeAttestedStateTransitionBlock(astb protocol.AttestedStateTransitionBlock) ([]byte, error) { + tpm := toProtoMessage[struct{}]{} + + return proto.Marshal(tpm.attestedStateTransitionBlock(astb)) +} + +// Deserialize decodes a binary payload into a protocol.Message +func Deserialize[RI any](n int, b []byte, requestHandle types.RequestHandle) (protocol.Message[RI], *MessageWrapper, error) { + pb := &MessageWrapper{} + if err := proto.Unmarshal(b, pb); err != nil { + return nil, nil, fmt.Errorf("could not unmarshal protobuf: %w", err) + } + + fpm := fromProtoMessage[RI]{n, requestHandle} + m, err := fpm.messageWrapper(pb) + if err != nil { + return nil, nil, fmt.Errorf("could not translate protobuf to protocol.Message: %w", err) + } + return m, pb, nil +} + +func DeserializeTrustedPrepareOrCommit(b []byte) (protocol.CertifiedPrepareOrCommit, error) { + pb := CertifiedPrepareOrCommit{} + if err := proto.Unmarshal(b, &pb); err != nil { + return nil, err + } + + // We trust the PrepareOrCommit we deserialize here, so we can simply use the maximum number + // of oracles for n. + n := types.MaxOracles + // we intentionally leave this as nil since prepare and commits don't carry a request handle + var requestHandle types.RequestHandle + fpm := fromProtoMessage[struct{}]{n, requestHandle} + return fpm.certifiedPrepareOrCommit(&pb) +} + +func DeserializePacemakerState(b []byte) (protocol.PacemakerState, error) { + pb := PacemakerState{} + if err := proto.Unmarshal(b, &pb); err != nil { + return protocol.PacemakerState{}, err + } + + return protocol.PacemakerState{ + pb.Epoch, + pb.HighestSentNewEpochWish, + }, nil +} + +func DeserializeStatePersistenceState(b []byte) (protocol.StatePersistenceState, error) { + pb := StatePersistenceState{} + if err := proto.Unmarshal(b, &pb); err != nil { + return protocol.StatePersistenceState{}, err + } + return protocol.StatePersistenceState{ + pb.HighestPersistedStateTransitionBlockSeqNr, + }, nil +} + +func DeserializeAttestedStateTransitionBlock(b []byte) (protocol.AttestedStateTransitionBlock, error) { + pb := AttestedStateTransitionBlock{} + if err := proto.Unmarshal(b, &pb); err != nil { + return protocol.AttestedStateTransitionBlock{}, err + } + + n := types.MaxOracles + // we intentionally leave this as nil since attested state transition blocks don't carry a request handle + var requestHandle types.RequestHandle + fpm := fromProtoMessage[struct{}]{n, requestHandle} + return fpm.attestedStateTransitionBlock(&pb) +} + +// +// *toProtoMessage +// + +type toProtoMessage[RI any] struct{} + +func (tpm *toProtoMessage[RI]) messageWrapper(m protocol.Message[RI]) (*MessageWrapper, error) { + msgWrapper := MessageWrapper{} + switch v := m.(type) { + case protocol.MessageNewEpochWish[RI]: + pm := &MessageNewEpochWish{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + uint64(v.Epoch), + } + msgWrapper.Msg = &MessageWrapper_MessageNewEpochWish{pm} + case protocol.MessageEpochStartRequest[RI]: + pm := &MessageEpochStartRequest{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + uint64(v.Epoch), + tpm.certifiedPrepareOrCommit(v.HighestCertified), + tpm.signedHighestCertifiedTimestamp(v.SignedHighestCertifiedTimestamp), + } + msgWrapper.Msg = &MessageWrapper_MessageEpochStartRequest{pm} + case protocol.MessageEpochStart[RI]: + pm := &MessageEpochStart{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + uint64(v.Epoch), + tpm.epochStartProof(v.EpochStartProof), + } + msgWrapper.Msg = &MessageWrapper_MessageEpochStart{pm} + case protocol.MessageRoundStart[RI]: + pm := &MessageRoundStart{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + uint64(v.Epoch), + v.SeqNr, + v.Query, + } + msgWrapper.Msg = &MessageWrapper_MessageRoundStart{pm} + case protocol.MessageObservation[RI]: + pm := &MessageObservation{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + uint64(v.Epoch), + v.SeqNr, + tpm.signedObservation(v.SignedObservation), + } + msgWrapper.Msg = &MessageWrapper_MessageObservation{pm} + + case protocol.MessageProposal[RI]: + pbasos := make([]*AttributedSignedObservation, 0, len(v.AttributedSignedObservations)) + for _, aso := range v.AttributedSignedObservations { + pbasos = append(pbasos, tpm.attributedSignedObservation(aso)) + } + pm := &MessageProposal{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + uint64(v.Epoch), + v.SeqNr, + pbasos, + } + msgWrapper.Msg = &MessageWrapper_MessageProposal{pm} + case protocol.MessagePrepare[RI]: + pm := &MessagePrepare{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + uint64(v.Epoch), + v.SeqNr, + v.Signature, + } + msgWrapper.Msg = &MessageWrapper_MessagePrepare{pm} + case protocol.MessageCommit[RI]: + pm := &MessageCommit{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + uint64(v.Epoch), + v.SeqNr, + v.Signature, + } + msgWrapper.Msg = &MessageWrapper_MessageCommit{pm} + case protocol.MessageReportSignatures[RI]: + pm := &MessageReportSignatures{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + v.SeqNr, + v.ReportSignatures, + } + msgWrapper.Msg = &MessageWrapper_MessageReportSignatures{pm} + case protocol.MessageCertifiedCommitRequest[RI]: + pm := &MessageCertifiedCommitRequest{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + v.SeqNr, + } + msgWrapper.Msg = &MessageWrapper_MessageCertifiedCommitRequest{pm} + case protocol.MessageCertifiedCommit[RI]: + pm := &MessageCertifiedCommit{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + tpm.CertifiedCommittedReports(v.CertifiedCommittedReports), + } + msgWrapper.Msg = &MessageWrapper_MessageCertifiedCommit{pm} + case protocol.MessageBlockSyncRequest[RI]: + pm := &MessageBlockSyncRequest{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + v.HighestCommittedSeqNr, + v.Nonce, + } + msgWrapper.Msg = &MessageWrapper_MessageBlockSyncRequest{pm} + case protocol.MessageBlockSync[RI]: + astbs := make([]*AttestedStateTransitionBlock, 0, len(v.AttestedStateTransitionBlocks)) + for _, astb := range v.AttestedStateTransitionBlocks { + astbs = append(astbs, tpm.attestedStateTransitionBlock(astb)) + } + pm := &MessageBlockSync{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + astbs, + v.Nonce, + } + msgWrapper.Msg = &MessageWrapper_MessageBlockSync{pm} + case protocol.MessageBlockSyncSummary[RI]: + pm := &MessageBlockSyncSummary{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + v.LowestPersistedSeqNr, + } + msgWrapper.Msg = &MessageWrapper_MessageBlockSyncSummary{pm} + + case protocol.MessageBlobOffer[RI]: + pm := &MessageBlobOffer{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + tpm.chunkDigests(v.ChunkDigests), + v.PayloadLength, + v.ExpirySeqNr, + uint32(v.Submitter), + } + msgWrapper.Msg = &MessageWrapper_MessageBlobOffer{pm} + case protocol.MessageBlobChunkRequest[RI]: + pm := &MessageBlobChunkRequest{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + v.BlobDigest[:], + v.ChunkIndex, + } + msgWrapper.Msg = &MessageWrapper_MessageBlobChunkRequest{pm} + case protocol.MessageBlobChunkResponse[RI]: + pm := &MessageBlobChunkResponse{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + v.BlobDigest[:], + v.ChunkIndex, + v.Chunk, + } + msgWrapper.Msg = &MessageWrapper_MessageBlobChunkResponse{pm} + case protocol.MessageBlobAvailable[RI]: + pm := &MessageBlobAvailable{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + v.BlobDigest[:], + v.Signature[:], + } + msgWrapper.Msg = &MessageWrapper_MessageBlobAvailable{pm} + + default: + return nil, fmt.Errorf("unable to serialize message of type %T", m) + + } + return &msgWrapper, nil +} + +func (tpm *toProtoMessage[RI]) chunkDigests(chunkDigests []protocol.BlobChunkDigest) [][]byte { + cds := make([][]byte, 0, len(chunkDigests)) + for _, chunkDigest := range chunkDigests { + cds = append(cds, chunkDigest[:]) + } + return cds +} + +func (tpm *toProtoMessage[RI]) certifiedPrepareOrCommit(cpoc protocol.CertifiedPrepareOrCommit) *CertifiedPrepareOrCommit { + switch v := cpoc.(type) { + case *protocol.CertifiedPrepare: + prepareQuorumCertificate := make([]*AttributedPrepareSignature, 0, len(v.PrepareQuorumCertificate)) + for _, aps := range v.PrepareQuorumCertificate { + prepareQuorumCertificate = append(prepareQuorumCertificate, &AttributedPrepareSignature{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + aps.Signature, + uint32(aps.Signer), + }) + } + return &CertifiedPrepareOrCommit{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + &CertifiedPrepareOrCommit_Prepare{&CertifiedPrepare{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + v.Epoch(), + v.SeqNr(), + v.StateTransitionInputsDigest[:], + tpm.stateTransitionOutputs(v.StateTransitionOutputs), + v.ReportsPlusPrecursor[:], + prepareQuorumCertificate, + }}, + } + case *protocol.CertifiedCommit: + return &CertifiedPrepareOrCommit{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + &CertifiedPrepareOrCommit_Commit{tpm.CertifiedCommit(*v)}, + } + default: + // It's safe to crash here since the "protocol.*" versions of these values + // come from the trusted, local environment. + panic("unrecognized protocol.CertifiedPrepareOrCommit implementation") + } +} + +func (tpm *toProtoMessage[RI]) CertifiedCommit(cpocc protocol.CertifiedCommit) *CertifiedCommit { + commitQuorumCertificate := make([]*AttributedCommitSignature, 0, len(cpocc.CommitQuorumCertificate)) + for _, aps := range cpocc.CommitQuorumCertificate { + commitQuorumCertificate = append(commitQuorumCertificate, &AttributedCommitSignature{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + aps.Signature, + uint32(aps.Signer), + }) + } + return &CertifiedCommit{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + cpocc.Epoch(), + cpocc.SeqNr(), + cpocc.StateTransitionInputsDigest[:], + tpm.stateTransitionOutputs(cpocc.StateTransitionOutputs), + cpocc.ReportsPlusPrecursor[:], + commitQuorumCertificate, + } +} + +func (tpm *toProtoMessage[RI]) CertifiedCommittedReports(ccr protocol.CertifiedCommittedReports[RI]) *CertifiedCommittedReports { + commitQuorumCertificate := make([]*AttributedCommitSignature, 0, len(ccr.CommitQuorumCertificate)) + for _, aps := range ccr.CommitQuorumCertificate { + commitQuorumCertificate = append(commitQuorumCertificate, &AttributedCommitSignature{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + aps.Signature, + uint32(aps.Signer), + }) + } + return &CertifiedCommittedReports{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + uint64(ccr.CommitEpoch), + ccr.SeqNr, + ccr.StateTransitionInputsDigest[:], + ccr.StateTransitionOutputDigest[:], + ccr.ReportsPlusPrecursor[:], + commitQuorumCertificate, + } +} + +func (tpm *toProtoMessage[RI]) attributedSignedHighestCertifiedTimestamp(ashct protocol.AttributedSignedHighestCertifiedTimestamp) *AttributedSignedHighestCertifiedTimestamp { + return &AttributedSignedHighestCertifiedTimestamp{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + tpm.signedHighestCertifiedTimestamp(ashct.SignedHighestCertifiedTimestamp), + uint32(ashct.Signer), + } +} + +func (tpm *toProtoMessage[RI]) signedHighestCertifiedTimestamp(shct protocol.SignedHighestCertifiedTimestamp) *SignedHighestCertifiedTimestamp { + return &SignedHighestCertifiedTimestamp{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + tpm.highestCertifiedTimestamp(shct.HighestCertifiedTimestamp), + shct.Signature, + } +} + +func (tpm *toProtoMessage[RI]) highestCertifiedTimestamp(hct protocol.HighestCertifiedTimestamp) *HighestCertifiedTimestamp { + return &HighestCertifiedTimestamp{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + hct.SeqNr, + hct.CommittedElsePrepared, + hct.Epoch, + } +} + +func (tpm *toProtoMessage[RI]) epochStartProof(srqc protocol.EpochStartProof) *EpochStartProof { + highestCertifiedProof := make([]*AttributedSignedHighestCertifiedTimestamp, 0, len(srqc.HighestCertifiedProof)) + for _, ashct := range srqc.HighestCertifiedProof { + highestCertifiedProof = append(highestCertifiedProof, tpm.attributedSignedHighestCertifiedTimestamp(ashct)) + } + return &EpochStartProof{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + tpm.certifiedPrepareOrCommit(srqc.HighestCertified), + highestCertifiedProof, + } +} + +func (tpm *toProtoMessage[RI]) signedObservation(o protocol.SignedObservation) *SignedObservation { + return &SignedObservation{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + o.Observation, + o.Signature, + } +} + +func (tpm *toProtoMessage[RI]) attributedSignedObservation(aso protocol.AttributedSignedObservation) *AttributedSignedObservation { + return &AttributedSignedObservation{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + tpm.signedObservation(aso.SignedObservation), + uint32(aso.Observer), + } +} + +func (tpm *toProtoMessage[RI]) attestedStateTransitionBlock(astb protocol.AttestedStateTransitionBlock) *AttestedStateTransitionBlock { + attributedSignatures := make([]*AttributedCommitSignature, 0, len(astb.AttributedSignatures)) + for _, as := range astb.AttributedSignatures { + attributedSignatures = append(attributedSignatures, &AttributedCommitSignature{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + as.Signature, + uint32(as.Signer), + }) + } + return &AttestedStateTransitionBlock{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + tpm.stateTransitionBlock(astb.StateTransitionBlock), + attributedSignatures, + } +} + +func (tpm *toProtoMessage[RI]) stateTransitionBlock(stb protocol.StateTransitionBlock) *StateTransitionBlock { + return &StateTransitionBlock{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + stb.Epoch, + stb.SeqNr(), + stb.StateTransitionInputsDigest[:], + tpm.stateTransitionOutputs(stb.StateTransitionOutputs), + stb.ReportsPlusPrecursor, + } +} + +func (tpm *toProtoMessage[RI]) stateTransitionOutputs(sto protocol.StateTransitionOutputs) *StateTransitionOutputs { + pbWriteSet := make([]*KeyValueModification, 0, len(sto.WriteSet)) + for _, kvmod := range sto.WriteSet { + pbWriteSet = append(pbWriteSet, &KeyValueModification{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + kvmod.Key, + kvmod.Value, + kvmod.Deleted, + }) + } + return &StateTransitionOutputs{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + pbWriteSet, + } +} + +// +// *fromProtoMessage +// + +type fromProtoMessage[RI any] struct { + n int + requestHandle types.RequestHandle +} + +func (fpm *fromProtoMessage[RI]) messageWrapper(wrapper *MessageWrapper) (protocol.Message[RI], error) { + switch msg := wrapper.Msg.(type) { + case *MessageWrapper_MessageNewEpochWish: + return fpm.messageNewEpochWish(wrapper.GetMessageNewEpochWish()) + case *MessageWrapper_MessageEpochStartRequest: + return fpm.messageEpochStartRequest(wrapper.GetMessageEpochStartRequest()) + case *MessageWrapper_MessageEpochStart: + return fpm.messageEpochStart(wrapper.GetMessageEpochStart()) + case *MessageWrapper_MessageRoundStart: + return fpm.messageRoundStart(wrapper.GetMessageRoundStart()) + case *MessageWrapper_MessageObservation: + return fpm.messageObservation(wrapper.GetMessageObservation()) + case *MessageWrapper_MessageProposal: + return fpm.messageProposal(wrapper.GetMessageProposal()) + case *MessageWrapper_MessagePrepare: + return fpm.messagePrepare(wrapper.GetMessagePrepare()) + case *MessageWrapper_MessageCommit: + return fpm.messageCommit(wrapper.GetMessageCommit()) + case *MessageWrapper_MessageReportSignatures: + return fpm.messageReportSignatures(wrapper.GetMessageReportSignatures()) + case *MessageWrapper_MessageCertifiedCommitRequest: + return fpm.messageCertifiedCommitRequest(wrapper.GetMessageCertifiedCommitRequest()) + case *MessageWrapper_MessageCertifiedCommit: + return fpm.messageCertifiedCommit(wrapper.GetMessageCertifiedCommit()) + case *MessageWrapper_MessageBlockSyncRequest: + return fpm.messageBlockSyncRequest(wrapper.GetMessageBlockSyncRequest()) + case *MessageWrapper_MessageBlockSync: + return fpm.messageBlockSync(wrapper.GetMessageBlockSync()) + case *MessageWrapper_MessageBlockSyncSummary: + return fpm.messageBlockSyncSummary(wrapper.GetMessageBlockSyncSummary()) + + case *MessageWrapper_MessageBlobOffer: + return fpm.messageBlobOffer(wrapper.GetMessageBlobOffer()) + case *MessageWrapper_MessageBlobChunkRequest: + return fpm.messageBlobChunkRequest(wrapper.GetMessageBlobChunkRequest()) + case *MessageWrapper_MessageBlobChunkResponse: + return fpm.messageBlobChunkResponse(wrapper.GetMessageBlobChunkResponse()) + case *MessageWrapper_MessageBlobAvailable: + return fpm.messageBlobAvailable(wrapper.GetMessageBlobAvailable()) + + default: + return nil, fmt.Errorf("unrecognized Msg type %T", msg) + } +} + +func (fpm *fromProtoMessage[RI]) messageNewEpochWish(m *MessageNewEpochWish) (protocol.MessageNewEpochWish[RI], error) { + if m == nil { + return protocol.MessageNewEpochWish[RI]{}, fmt.Errorf("unable to extract a MessageNewEpochWish value") + } + return protocol.MessageNewEpochWish[RI]{ + m.Epoch, + }, nil +} + +func (fpm *fromProtoMessage[RI]) messageEpochStartRequest(m *MessageEpochStartRequest) (protocol.MessageEpochStartRequest[RI], error) { + if m == nil { + return protocol.MessageEpochStartRequest[RI]{}, fmt.Errorf("unable to extract a MessageEpochStartRequest value") + } + hc, err := fpm.certifiedPrepareOrCommit(m.HighestCertified) + if err != nil { + return protocol.MessageEpochStartRequest[RI]{}, err + } + shct, err := fpm.signedHighestCertifiedTimestamp(m.SignedHighestCertifiedTimestamp) + if err != nil { + return protocol.MessageEpochStartRequest[RI]{}, err + } + return protocol.MessageEpochStartRequest[RI]{ + m.Epoch, + hc, + shct, + }, nil +} + +func (fpm *fromProtoMessage[RI]) messageEpochStart(m *MessageEpochStart) (protocol.MessageEpochStart[RI], error) { + if m == nil { + return protocol.MessageEpochStart[RI]{}, fmt.Errorf("unable to extract a MessageEpochStart value") + } + srqc, err := fpm.epochStartProof(m.EpochStartProof) + if err != nil { + return protocol.MessageEpochStart[RI]{}, err + } + return protocol.MessageEpochStart[RI]{ + m.Epoch, + srqc, + }, nil +} + +func (fpm *fromProtoMessage[RI]) messageProposal(m *MessageProposal) (protocol.MessageProposal[RI], error) { + if m == nil { + return protocol.MessageProposal[RI]{}, fmt.Errorf("unable to extract a MessageProposal value") + } + asos, err := fpm.attributedSignedObservations(m.AttributedSignedObservations) + if err != nil { + return protocol.MessageProposal[RI]{}, err + } + return protocol.MessageProposal[RI]{ + m.Epoch, + m.SeqNr, + asos, + }, nil +} + +func (fpm *fromProtoMessage[RI]) messagePrepare(m *MessagePrepare) (protocol.MessagePrepare[RI], error) { + if m == nil { + return protocol.MessagePrepare[RI]{}, fmt.Errorf("unable to extract a MessagePrepare value") + } + return protocol.MessagePrepare[RI]{ + m.Epoch, + m.SeqNr, + m.Signature, + }, nil +} + +func (fpm *fromProtoMessage[RI]) messageCommit(m *MessageCommit) (protocol.MessageCommit[RI], error) { + if m == nil { + return protocol.MessageCommit[RI]{}, fmt.Errorf("unable to extract a MessageCommit value") + } + return protocol.MessageCommit[RI]{ + m.Epoch, + m.SeqNr, + m.Signature, + }, nil +} + +func (fpm *fromProtoMessage[RI]) certifiedPrepareOrCommit(m *CertifiedPrepareOrCommit) (protocol.CertifiedPrepareOrCommit, error) { + if m == nil { + return nil, fmt.Errorf("unable to extract a CertifiedPrepareOrCommit value") + } + switch poc := m.PrepareOrCommit.(type) { + case *CertifiedPrepareOrCommit_Prepare: + cpocp, err := fpm.certifiedPrepare(poc.Prepare) + if err != nil { + return nil, err + } + return &cpocp, nil + case *CertifiedPrepareOrCommit_Commit: + cpocc, err := fpm.certifiedCommit(poc.Commit) + if err != nil { + return nil, err + } + return &cpocc, nil + default: + return nil, fmt.Errorf("unknown case of CertifiedPrepareOrCommit") + } +} + +func (fpm *fromProtoMessage[RI]) certifiedPrepare(m *CertifiedPrepare) (protocol.CertifiedPrepare, error) { + if m == nil { + return protocol.CertifiedPrepare{}, fmt.Errorf("unable to extract a CertifiedPrepare value") + } + outputs, err := fpm.stateTransitionOutputs(m.StateTransitionOutputs) + if err != nil { + return protocol.CertifiedPrepare{}, err + } + var inputsDigest protocol.StateTransitionInputsDigest + copy(inputsDigest[:], m.StateTransitionInputsDigest) + + prepareQuorumCertificate := make([]protocol.AttributedPrepareSignature, 0, len(m.PrepareQuorumCertificate)) + for _, aps := range m.PrepareQuorumCertificate { + signer, err := fpm.oracleID(aps.GetSigner()) + if err != nil { + return protocol.CertifiedPrepare{}, err + } + prepareQuorumCertificate = append(prepareQuorumCertificate, protocol.AttributedPrepareSignature{ + aps.GetSignature(), + signer, + }) + } + return protocol.CertifiedPrepare{ + m.Epoch, + m.SeqNr, + inputsDigest, + outputs, + m.ReportsPlusPrecursor, + prepareQuorumCertificate, + }, nil + +} + +func (fpm *fromProtoMessage[RI]) certifiedCommit(m *CertifiedCommit) (protocol.CertifiedCommit, error) { + if m == nil { + return protocol.CertifiedCommit{}, fmt.Errorf("unable to extract a CertifiedCommit value") + } + outputs, err := fpm.stateTransitionOutputs(m.StateTransitionOutputs) + if err != nil { + return protocol.CertifiedCommit{}, err + } + var inputsDigest protocol.StateTransitionInputsDigest + copy(inputsDigest[:], m.StateTransitionInputsDigest) + + commitQuorumCertificate := make([]protocol.AttributedCommitSignature, 0, len(m.CommitQuorumCertificate)) + for _, aps := range m.CommitQuorumCertificate { + signer, err := fpm.oracleID(aps.GetSigner()) + if err != nil { + return protocol.CertifiedCommit{}, err + } + commitQuorumCertificate = append(commitQuorumCertificate, protocol.AttributedCommitSignature{ + aps.GetSignature(), + signer, + }) + } + return protocol.CertifiedCommit{ + m.Epoch, + m.SeqNr, + inputsDigest, + outputs, + m.ReportsPlusPrecursor, + commitQuorumCertificate, + }, nil +} + +func (fpm *fromProtoMessage[RI]) certifiedCommittedReports(m *CertifiedCommittedReports) (protocol.CertifiedCommittedReports[RI], error) { + if m == nil { + return protocol.CertifiedCommittedReports[RI]{}, fmt.Errorf("unable to extract a CertifiedCommittedReports value") + } + var inputsDigest protocol.StateTransitionInputsDigest + copy(inputsDigest[:], m.StateTransitionInputsDigest) + var outputsDigest protocol.StateTransitionOutputDigest + copy(outputsDigest[:], m.StateTransitionOutputDigest) + + commitQuorumCertificate := make([]protocol.AttributedCommitSignature, 0, len(m.CommitQuorumCertificate)) + for _, aps := range m.CommitQuorumCertificate { + signer, err := fpm.oracleID(aps.GetSigner()) + if err != nil { + return protocol.CertifiedCommittedReports[RI]{}, err + } + commitQuorumCertificate = append(commitQuorumCertificate, protocol.AttributedCommitSignature{ + aps.GetSignature(), + signer, + }) + } + return protocol.CertifiedCommittedReports[RI]{ + m.CommitEpoch, + m.SeqNr, + inputsDigest, + outputsDigest, + m.ReportsPlusPrecursor, + commitQuorumCertificate, + }, nil +} + +func (fpm *fromProtoMessage[RI]) signedHighestCertifiedTimestamp(m *SignedHighestCertifiedTimestamp) (protocol.SignedHighestCertifiedTimestamp, error) { + if m == nil { + return protocol.SignedHighestCertifiedTimestamp{}, fmt.Errorf("unable to extract a SignedHighestCertifiedTimestamp value") + } + hct, err := fpm.highestCertifiedTimestamp(m.HighestCertifiedTimestamp) + if err != nil { + return protocol.SignedHighestCertifiedTimestamp{}, err + } + return protocol.SignedHighestCertifiedTimestamp{ + hct, + m.Signature, + }, nil +} + +func (fpm *fromProtoMessage[RI]) highestCertifiedTimestamp(m *HighestCertifiedTimestamp) (protocol.HighestCertifiedTimestamp, error) { + if m == nil { + return protocol.HighestCertifiedTimestamp{}, fmt.Errorf("unable to extract a HighestCertifiedTimestamp value") + } + return protocol.HighestCertifiedTimestamp{ + m.SeqNr, + m.CommittedElsePrepared, + m.Epoch, + }, nil +} + +func (fpm *fromProtoMessage[RI]) epochStartProof(m *EpochStartProof) (protocol.EpochStartProof, error) { + if m == nil { + return protocol.EpochStartProof{}, fmt.Errorf("unable to extract a EpochStartProof value") + } + hc, err := fpm.certifiedPrepareOrCommit(m.HighestCertified) + if err != nil { + return protocol.EpochStartProof{}, err + } + hctqc := make([]protocol.AttributedSignedHighestCertifiedTimestamp, 0, len(m.HighestCertifiedProof)) + for _, ashct := range m.HighestCertifiedProof { + signer, err := fpm.oracleID(ashct.GetSigner()) + if err != nil { + return protocol.EpochStartProof{}, err + } + hctqc = append(hctqc, protocol.AttributedSignedHighestCertifiedTimestamp{ + protocol.SignedHighestCertifiedTimestamp{ + protocol.HighestCertifiedTimestamp{ + ashct.GetSignedHighestCertifiedTimestamp().GetHighestCertifiedTimestamp().GetSeqNr(), + ashct.GetSignedHighestCertifiedTimestamp().GetHighestCertifiedTimestamp().GetCommittedElsePrepared(), + ashct.GetSignedHighestCertifiedTimestamp().GetHighestCertifiedTimestamp().GetEpoch(), + }, + ashct.GetSignedHighestCertifiedTimestamp().GetSignature(), + }, + signer, + }) + } + + return protocol.EpochStartProof{ + hc, + hctqc, + }, nil +} + +func (fpm *fromProtoMessage[RI]) messageRoundStart(m *MessageRoundStart) (protocol.MessageRoundStart[RI], error) { + if m == nil { + return protocol.MessageRoundStart[RI]{}, fmt.Errorf("unable to extract a MessageRoundStart value") + } + return protocol.MessageRoundStart[RI]{ + m.Epoch, + m.SeqNr, + m.Query, + }, nil +} + +func (fpm *fromProtoMessage[RI]) messageObservation(m *MessageObservation) (protocol.MessageObservation[RI], error) { + if m == nil { + return protocol.MessageObservation[RI]{}, fmt.Errorf("unable to extract a MessageObservation value") + } + so, err := fpm.signedObservation(m.SignedObservation) + if err != nil { + return protocol.MessageObservation[RI]{}, err + } + return protocol.MessageObservation[RI]{ + m.Epoch, + m.SeqNr, + so, + }, nil +} + +func (fpm *fromProtoMessage[RI]) messageReportSignatures(m *MessageReportSignatures) (protocol.MessageReportSignatures[RI], error) { + if m == nil { + return protocol.MessageReportSignatures[RI]{}, fmt.Errorf("unable to extract a MessageReportSignatures value") + } + return protocol.MessageReportSignatures[RI]{ + m.SeqNr, + m.ReportSignatures, + }, nil +} + +func (fpm *fromProtoMessage[RI]) messageCertifiedCommitRequest(m *MessageCertifiedCommitRequest) (protocol.MessageCertifiedCommitRequest[RI], error) { + if m == nil { + return protocol.MessageCertifiedCommitRequest[RI]{}, fmt.Errorf("unable to extract a MessageCertifiedCommitRequest value") + } + return protocol.MessageCertifiedCommitRequest[RI]{ + m.SeqNr, + }, nil +} + +func (fpm *fromProtoMessage[RI]) messageCertifiedCommit(m *MessageCertifiedCommit) (protocol.MessageCertifiedCommit[RI], error) { + if m == nil { + return protocol.MessageCertifiedCommit[RI]{}, fmt.Errorf("unable to extract a MessageCertifiedCommit value") + } + cpocc, err := fpm.certifiedCommittedReports(m.CertifiedCommittedReports) + if err != nil { + return protocol.MessageCertifiedCommit[RI]{}, err + } + return protocol.MessageCertifiedCommit[RI]{ + cpocc, + }, nil +} + +func (fpm *fromProtoMessage[RI]) attributedSignedObservations(pbasos []*AttributedSignedObservation) ([]protocol.AttributedSignedObservation, error) { + asos := make([]protocol.AttributedSignedObservation, 0, len(pbasos)) + for _, pbaso := range pbasos { + aso, err := fpm.attributedSignedObservation(pbaso) + if err != nil { + return nil, err + } + asos = append(asos, aso) + } + return asos, nil +} + +func (fpm *fromProtoMessage[RI]) attributedSignedObservation(m *AttributedSignedObservation) (protocol.AttributedSignedObservation, error) { + if m == nil { + return protocol.AttributedSignedObservation{}, fmt.Errorf("unable to extract an AttributedSignedObservation value") + } + + signedObservation, err := fpm.signedObservation(m.SignedObservation) + if err != nil { + return protocol.AttributedSignedObservation{}, err + } + + observer, err := fpm.oracleID(m.Observer) + if err != nil { + return protocol.AttributedSignedObservation{}, err + } + + return protocol.AttributedSignedObservation{ + signedObservation, + observer, + }, nil +} + +func (fpm *fromProtoMessage[RI]) signedObservation(m *SignedObservation) (protocol.SignedObservation, error) { + if m == nil { + return protocol.SignedObservation{}, fmt.Errorf("unable to extract a SignedObservation value") + } + + return protocol.SignedObservation{ + m.Observation, + m.Signature, + }, nil +} + +func (fpm *fromProtoMessage[RI]) oracleID(m uint32) (commontypes.OracleID, error) { + oid := commontypes.OracleID(m) + if int(oid) >= fpm.n { + return 0, fmt.Errorf("invalid OracleID: %d", m) + } + return oid, nil +} + +func (fpm *fromProtoMessage[RI]) messageBlockSyncRequest(m *MessageBlockSyncRequest) (protocol.MessageBlockSyncRequest[RI], error) { + if m == nil { + return protocol.MessageBlockSyncRequest[RI]{}, fmt.Errorf("unable to extract a MessageBlockSyncRequest value") + } + return protocol.MessageBlockSyncRequest[RI]{ + fpm.requestHandle, + m.HighestCommittedSeqNr, + m.Nonce, + }, nil +} + +func (fpm *fromProtoMessage[RI]) messageBlockSync(m *MessageBlockSync) (protocol.MessageBlockSync[RI], error) { + if m == nil { + return protocol.MessageBlockSync[RI]{}, fmt.Errorf("unable to extract a MessageBlockSync value") + } + astbs, err := fpm.attestedStateTransitionBlocks(m.AttestedStateTransitionBlocks) + if err != nil { + return protocol.MessageBlockSync[RI]{}, err + } + return protocol.MessageBlockSync[RI]{ + nil, // TODO: consider using a sentinel value here, e.g. "EmptyRequestHandleForInboundResponse" + astbs, + m.Nonce, + }, nil +} + +func (fpm *fromProtoMessage[RI]) messageBlockSyncSummary(m *MessageBlockSyncSummary) (protocol.MessageBlockSyncSummary[RI], error) { + if m == nil { + return protocol.MessageBlockSyncSummary[RI]{}, fmt.Errorf("unable to extract a MessageBlockSyncSummary value") + } + return protocol.MessageBlockSyncSummary[RI]{ + m.LowestPersistedSeqNr, + }, nil +} +func (fpm *fromProtoMessage[RI]) attestedStateTransitionBlocks(pbastbs []*AttestedStateTransitionBlock) ([]protocol.AttestedStateTransitionBlock, error) { + astbs := make([]protocol.AttestedStateTransitionBlock, 0, len(pbastbs)) + for _, pbastb := range pbastbs { + astb, err := fpm.attestedStateTransitionBlock(pbastb) + if err != nil { + return nil, err + } + astbs = append(astbs, astb) + } + return astbs, nil +} + +func (fpm *fromProtoMessage[RI]) attestedStateTransitionBlock(m *AttestedStateTransitionBlock) (protocol.AttestedStateTransitionBlock, error) { + if m == nil { + return protocol.AttestedStateTransitionBlock{}, fmt.Errorf("unable to extract a AttestedStateTransitionBlock value") + } + stb, err := fpm.stateTransitionBlock(m.StateTransitionBlock) + if err != nil { + return protocol.AttestedStateTransitionBlock{}, err + } + asigs, err := fpm.attributedCommitSignatures(m.AttributedSignatures) + if err != nil { + return protocol.AttestedStateTransitionBlock{}, err + } + return protocol.AttestedStateTransitionBlock{ + stb, + asigs, + }, nil +} + +func (fpm *fromProtoMessage[RI]) stateTransitionBlock(m *StateTransitionBlock) (protocol.StateTransitionBlock, error) { + if m == nil { + return protocol.StateTransitionBlock{}, fmt.Errorf("unable to extract a StateTransitionBlock value") + } + var inputsDigest protocol.StateTransitionInputsDigest + copy(inputsDigest[:], m.StateTransitionInputsDigest) + + outputs, err := fpm.stateTransitionOutputs(m.StateTransitionOutputs) + if err != nil { + return protocol.StateTransitionBlock{}, err + } + return protocol.StateTransitionBlock{ + m.Epoch, + m.SeqNr, + inputsDigest, + outputs, + m.ReportsPlusPrecursor, + }, nil +} + +func (fpm *fromProtoMessage[RI]) attributedCommitSignatures(pbasigs []*AttributedCommitSignature) ([]protocol.AttributedCommitSignature, error) { + asigs := make([]protocol.AttributedCommitSignature, 0, len(pbasigs)) + for _, pbasig := range pbasigs { + asig, err := fpm.attributedCommitSignature(pbasig) + if err != nil { + return nil, err + } + asigs = append(asigs, asig) + } + return asigs, nil +} + +func (fpm *fromProtoMessage[RI]) attributedCommitSignature(m *AttributedCommitSignature) (protocol.AttributedCommitSignature, error) { + if m == nil { + return protocol.AttributedCommitSignature{}, fmt.Errorf("unable to extract an AttributedCommitSignature value") + } + signer, err := fpm.oracleID(m.GetSigner()) + if err != nil { + return protocol.AttributedCommitSignature{}, err + } + return protocol.AttributedCommitSignature{ + m.Signature, + signer, + }, nil +} + +func (fpm *fromProtoMessage[RI]) stateTransitionOutputs(m *StateTransitionOutputs) (protocol.StateTransitionOutputs, error) { + if m == nil { + return protocol.StateTransitionOutputs{}, fmt.Errorf("unable to extract an StateTransitionOutputs value") + } + + writeSet := make([]protocol.KeyValuePair, 0, len(m.WriteSet)) + for _, pbkvmod := range m.WriteSet { + writeSet = append(writeSet, protocol.KeyValuePair{ + pbkvmod.Key, + pbkvmod.Value, + pbkvmod.Deleted, + }) + } + + return protocol.StateTransitionOutputs{writeSet}, nil +} + +func (fpm *fromProtoMessage[RI]) messageBlobOffer(m *MessageBlobOffer) (protocol.MessageBlobOffer[RI], error) { + if m == nil { + return protocol.MessageBlobOffer[RI]{}, fmt.Errorf("unable to extract a MessageBlobOffer value") + } + chunkDigests, err := fpm.chunkDigests(m.ChunkDigests) + if err != nil { + return protocol.MessageBlobOffer[RI]{}, err + } + submitter, err := fpm.oracleID(m.Submitter) + if err != nil { + return protocol.MessageBlobOffer[RI]{}, err + } + return protocol.MessageBlobOffer[RI]{ + chunkDigests, + m.PayloadLength, + m.ExpirySeqNr, + submitter, + }, nil +} + +func (fpm *fromProtoMessage[RI]) chunkDigests(pbcds [][]byte) ([]protocol.BlobChunkDigest, error) { + if pbcds == nil { + return nil, fmt.Errorf("unable to extract a ChunkDigests value") + } + cds := make([]protocol.BlobChunkDigest, 0, len(pbcds)) + for _, pbcd := range pbcds { + cds = append(cds, protocol.BlobChunkDigest(pbcd)) + } + return cds, nil +} + +func (fpm *fromProtoMessage[RI]) messageBlobChunkRequest(m *MessageBlobChunkRequest) (protocol.MessageBlobChunkRequest[RI], error) { + if m == nil { + return protocol.MessageBlobChunkRequest[RI]{}, fmt.Errorf("unable to extract a MessageBlobChunkRequest value") + } + + var blobDigest protocol.BlobDigest + copy(blobDigest[:], m.BlobDigest) + + return protocol.MessageBlobChunkRequest[RI]{ + fpm.requestHandle, + blobDigest, + m.ChunkIndex, + }, nil +} + +func (fpm *fromProtoMessage[RI]) messageBlobChunkResponse(m *MessageBlobChunkResponse) (protocol.MessageBlobChunkResponse[RI], error) { + if m == nil { + return protocol.MessageBlobChunkResponse[RI]{}, fmt.Errorf("unable to extract a MessageBlobChunkResponse value") + } + + var blobDigest protocol.BlobDigest + copy(blobDigest[:], m.BlobDigest) + + return protocol.MessageBlobChunkResponse[RI]{ + fpm.requestHandle, + blobDigest, + m.ChunkIndex, + m.Chunk, + }, nil +} + +func (fpm *fromProtoMessage[RI]) messageBlobAvailable(m *MessageBlobAvailable) (protocol.MessageBlobAvailable[RI], error) { + if m == nil { + return protocol.MessageBlobAvailable[RI]{}, fmt.Errorf("unable to extract a MessageBlobAvailable value") + } + + var blobDigest protocol.BlobDigest + copy(blobDigest[:], m.BlobDigest) + + return protocol.MessageBlobAvailable[RI]{ + blobDigest, + m.Signature, + }, nil +} diff --git a/offchainreporting2plus/internal/ocr3_1/serialization/telemetry.go b/offchainreporting2plus/internal/ocr3_1/serialization/telemetry.go new file mode 100644 index 00000000..957a87f2 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/serialization/telemetry.go @@ -0,0 +1 @@ +package serialization diff --git a/offchainreporting2plus/internal/shim/ocr3_1_database.go b/offchainreporting2plus/internal/shim/ocr3_1_database.go new file mode 100644 index 00000000..5035de05 --- /dev/null +++ b/offchainreporting2plus/internal/shim/ocr3_1_database.go @@ -0,0 +1,129 @@ +package shim + +import ( + "context" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3_1/protocol" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3_1/serialization" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "google.golang.org/protobuf/proto" +) + +type SerializingOCR3_1Database struct { + BinaryDb ocr3_1types.Database +} + +var _ protocol.Database = (*SerializingOCR3_1Database)(nil) + +const statePersistenceKey = "state" + +func (db *SerializingOCR3_1Database) ReadConfig(ctx context.Context) (*types.ContractConfig, error) { + return db.BinaryDb.ReadConfig(ctx) +} + +func (db *SerializingOCR3_1Database) WriteConfig(ctx context.Context, config types.ContractConfig) error { + return db.BinaryDb.WriteConfig(ctx, config) +} + +func (db *SerializingOCR3_1Database) ReadPacemakerState(ctx context.Context, configDigest types.ConfigDigest) (protocol.PacemakerState, error) { + raw, err := db.BinaryDb.ReadProtocolState(ctx, configDigest, pacemakerKey) + if err != nil { + return protocol.PacemakerState{}, err + } + + if len(raw) == 0 { + return protocol.PacemakerState{}, nil + } + + return serialization.DeserializePacemakerState(raw) +} + +func (db *SerializingOCR3_1Database) WritePacemakerState(ctx context.Context, configDigest types.ConfigDigest, state protocol.PacemakerState) error { + raw, err := serialization.SerializePacemakerState(state) + if err != nil { + return err + } + + return db.BinaryDb.WriteProtocolState(ctx, configDigest, pacemakerKey, raw) +} + +func (db *SerializingOCR3_1Database) ReadCert(ctx context.Context, configDigest types.ConfigDigest) (protocol.CertifiedPrepareOrCommit, error) { + raw, err := db.BinaryDb.ReadProtocolState(ctx, configDigest, certKey) + if err != nil { + return nil, err + } + + if len(raw) == 0 { + return nil, nil + } + + // This oracle wrote the PrepareOrCommit, so it's fine to trust the value. + return serialization.DeserializeTrustedPrepareOrCommit(raw) +} + +// Writing with an empty value is the same as deleting. +func (db *SerializingOCR3_1Database) WriteCert(ctx context.Context, configDigest types.ConfigDigest, cert protocol.CertifiedPrepareOrCommit) error { + if cert == nil { + return db.BinaryDb.WriteProtocolState(ctx, configDigest, certKey, nil) + } + + raw, err := serialization.SerializeCertifiedPrepareOrCommit(cert) + if err != nil { + return err + } + + return db.BinaryDb.WriteProtocolState(ctx, configDigest, certKey, raw) +} + +func (db *SerializingOCR3_1Database) ReadStatePersistenceState(ctx context.Context, configDigest types.ConfigDigest) (protocol.StatePersistenceState, error) { + raw, err := db.BinaryDb.ReadProtocolState(ctx, configDigest, statePersistenceKey) + if err != nil { + return protocol.StatePersistenceState{}, err + } + + if len(raw) == 0 { + return protocol.StatePersistenceState{}, nil + } + + return serialization.DeserializeStatePersistenceState(raw) +} + +// Writing with an empty value is the same as deleting. +func (db *SerializingOCR3_1Database) WriteStatePersistenceState(ctx context.Context, configDigest types.ConfigDigest, state protocol.StatePersistenceState) error { + raw, err := serialization.SerializeStatePersistenceState(state) + if err != nil { + return err + } + + return db.BinaryDb.WriteProtocolState(ctx, configDigest, statePersistenceKey, raw) +} + +func (db *SerializingOCR3_1Database) ReadAttestedStateTransitionBlock(ctx context.Context, configDigest types.ConfigDigest, seqNr uint64) (protocol.AttestedStateTransitionBlock, error) { + raw, err := db.BinaryDb.ReadBlock(ctx, configDigest, seqNr) + if err != nil { + return protocol.AttestedStateTransitionBlock{}, err + } + + if len(raw) == 0 { + return protocol.AttestedStateTransitionBlock{}, nil + } + + astb := serialization.AttestedStateTransitionBlock{} + if err := proto.Unmarshal(raw, &astb); err != nil { + return protocol.AttestedStateTransitionBlock{}, err + } + + return serialization.DeserializeAttestedStateTransitionBlock(raw) +} + +// Writing with an empty value is the same as deleting. +func (db *SerializingOCR3_1Database) WriteAttestedStateTransitionBlock(ctx context.Context, configDigest types.ConfigDigest, seqNr uint64, astb protocol.AttestedStateTransitionBlock) error { + raw, err := serialization.SerializeAttestedStateTransitionBlock(astb) + if err != nil { + return err + } + + return db.BinaryDb.WriteBlock(ctx, configDigest, seqNr, raw) +} diff --git a/offchainreporting2plus/internal/shim/ocr3_1_key_value_store.go b/offchainreporting2plus/internal/shim/ocr3_1_key_value_store.go new file mode 100644 index 00000000..b1298553 --- /dev/null +++ b/offchainreporting2plus/internal/shim/ocr3_1_key_value_store.go @@ -0,0 +1,419 @@ +package shim + +import ( + "bytes" + "encoding/binary" + "fmt" + "sort" + "sync" + + "github.com/smartcontractkit/libocr/internal/util" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3_1/protocol" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" +) + +type SemanticOCR3_1KeyValueStore struct { + KeyValueDatabase ocr3_1types.KeyValueDatabase + Limits ocr3_1types.ReportingPluginLimits +} + +var _ protocol.KeyValueStore = &SemanticOCR3_1KeyValueStore{} + +func (s *SemanticOCR3_1KeyValueStore) Close() error { + return s.KeyValueDatabase.Close() +} + +func (s *SemanticOCR3_1KeyValueStore) HighestCommittedSeqNr() (uint64, error) { + tx, err := s.NewReadTransactionUnchecked() + if err != nil { + return 0, fmt.Errorf("failed to create read transaction: %w", err) + } + defer tx.Discard() + return tx.ReadHighestCommittedSeqNr() +} + +func (s *SemanticOCR3_1KeyValueStore) NewReadWriteTransaction(postSeqNr uint64) (protocol.KeyValueStoreReadWriteTransaction, error) { + tx, err := s.NewReadWriteTransactionUnchecked() + if err != nil { + return nil, fmt.Errorf("failed to create read write transaction: %w", err) + } + highestCommittedSeqNr, err := tx.ReadHighestCommittedSeqNr() + if err != nil { + tx.Discard() + return nil, fmt.Errorf("failed to get highest committed seq nr: %w", err) + } + if highestCommittedSeqNr+1 != postSeqNr { + tx.Discard() + return nil, fmt.Errorf("post seq nr %d must be equal to highest committed seq nr + 1 (%d)", postSeqNr, highestCommittedSeqNr+1) + } + return &SemanticOCR3_1KeyValueStoreReadWriteTransactionWithPreCommitHook{ + tx, + func() error { + if err := tx.WriteHighestCommittedSeqNr(postSeqNr); err != nil { + return fmt.Errorf("WriteHighestCommittedSeqNr: %w", err) + } + return nil + }, + }, nil +} + +func (s *SemanticOCR3_1KeyValueStore) NewReadWriteTransactionUnchecked() (protocol.KeyValueStoreReadWriteTransaction, error) { + tx, err := s.KeyValueDatabase.NewReadWriteTransaction() + if err != nil { + return nil, fmt.Errorf("failed to create read write transaction: %w", err) + } + return &SemanticOCR3_1KeyValueStoreReadWriteTransaction{ + &SemanticOCR3_1KeyValueStoreReadTransaction{tx, s.Limits}, + tx, + sync.Mutex{}, + newLimitCheckWriteSet(s.Limits.MaxKeyValueModifiedKeysPlusValuesLength), + }, nil +} + +func (s *SemanticOCR3_1KeyValueStore) NewReadTransaction(postSeqNr uint64) (protocol.KeyValueStoreReadTransaction, error) { + tx, err := s.NewReadTransactionUnchecked() + if err != nil { + return nil, fmt.Errorf("failed to create read transaction: %w", err) + } + highestCommittedSeqNr, err := tx.ReadHighestCommittedSeqNr() + if err != nil { + tx.Discard() + return nil, fmt.Errorf("failed to get highest committed seq nr: %w", err) + } + if highestCommittedSeqNr+1 != postSeqNr { + tx.Discard() + return nil, fmt.Errorf("post seq nr %d must be equal to highest committed seq nr + 1 (%d)", postSeqNr, highestCommittedSeqNr+1) + } + return tx, nil +} + +func (s *SemanticOCR3_1KeyValueStore) NewReadTransactionUnchecked() (protocol.KeyValueStoreReadTransaction, error) { + tx, err := s.KeyValueDatabase.NewReadTransaction() + if err != nil { + return nil, fmt.Errorf("failed to create read transaction: %w", err) + } + return &SemanticOCR3_1KeyValueStoreReadTransaction{tx, s.Limits}, nil +} + +type SemanticOCR3_1KeyValueStoreReadWriteTransaction struct { + protocol.KeyValueStoreReadTransaction // inherit all read implementations + + rawTransaction ocr3_1types.KeyValueReadWriteTransaction + + mu sync.Mutex + nilOrWriteSet *limitCheckWriteSet +} + +var _ protocol.KeyValueStoreReadWriteTransaction = &SemanticOCR3_1KeyValueStoreReadWriteTransaction{} + +type SemanticOCR3_1KeyValueStoreReadWriteTransactionWithPreCommitHook struct { + protocol.KeyValueStoreReadWriteTransaction + preCommitHook func() error // must be idempotent +} + +var _ protocol.KeyValueStoreReadWriteTransaction = &SemanticOCR3_1KeyValueStoreReadWriteTransactionWithPreCommitHook{} + +func (s *SemanticOCR3_1KeyValueStoreReadWriteTransactionWithPreCommitHook) Commit() error { + if err := s.preCommitHook(); err != nil { + return fmt.Errorf("failed while executing preCommit: %w", err) + } + return s.KeyValueStoreReadWriteTransaction.Commit() +} + +func (s *SemanticOCR3_1KeyValueStoreReadWriteTransaction) Commit() error { + err := s.rawTransaction.Commit() + // Transactions might persistently fail to commit, due to another txn having + // gone in before that causes a conflict, so we need to discard in any case + // to avoid memory leaks. + s.Discard() + return err +} + +func (s *SemanticOCR3_1KeyValueStoreReadWriteTransaction) Delete(key []byte) error { + if !(len(key) <= ocr3_1types.MaxMaxKeyValueKeyLength) { + return fmt.Errorf("key length %d exceeds maximum %d", len(key), ocr3_1types.MaxMaxKeyValueKeyLength) + } + + s.mu.Lock() + if s.nilOrWriteSet == nil { + s.mu.Unlock() + return fmt.Errorf("transaction has been discarded") + } + if err := s.nilOrWriteSet.Delete(key); err != nil { + + s.mu.Unlock() + return fmt.Errorf("failed to delete key %s from write set: %w", key, err) + } + s.mu.Unlock() + + return s.rawTransaction.Delete(pluginPrefixedKey(key)) +} + +func (s *SemanticOCR3_1KeyValueStoreReadWriteTransaction) Discard() { + s.mu.Lock() + s.nilOrWriteSet = nil // tombstone + s.mu.Unlock() + + s.rawTransaction.Discard() +} + +// GetWriteSet returns a map from keys in string encoding to values that have been written in +// this transaction. If the value of a key has been deleted, it is mapped to nil. The write set +// must fit in memory. + +func (s *SemanticOCR3_1KeyValueStoreReadWriteTransaction) GetWriteSet() ([]protocol.KeyValuePair, error) { + s.mu.Lock() + if s.nilOrWriteSet == nil { + s.mu.Unlock() + return nil, fmt.Errorf("transaction has been discarded") + } + writeSet := s.nilOrWriteSet.Pairs() + s.mu.Unlock() + + sort.Slice(writeSet, func(i, j int) bool { + return bytes.Compare(writeSet[i].Key, writeSet[j].Key) < 0 + }) + return writeSet, nil +} + +func (s *SemanticOCR3_1KeyValueStoreReadWriteTransaction) Write(key []byte, value []byte) error { + if !(len(key) <= ocr3_1types.MaxMaxKeyValueKeyLength) { + return fmt.Errorf("key length %d exceeds maximum %d", len(key), ocr3_1types.MaxMaxKeyValueKeyLength) + } + if !(len(value) <= ocr3_1types.MaxMaxKeyValueValueLength) { + return fmt.Errorf("value length %d exceeds maximum %d", len(value), ocr3_1types.MaxMaxKeyValueValueLength) + } + + value = util.NilCoalesceSlice(value) + + s.mu.Lock() + if s.nilOrWriteSet == nil { + s.mu.Unlock() + return fmt.Errorf("transaction has been discarded") + } + if err := s.nilOrWriteSet.Write(key, value); err != nil { + s.mu.Unlock() + return fmt.Errorf("failed to write key %s to write set: %w", key, err) + } + s.mu.Unlock() + + return s.rawTransaction.Write(pluginPrefixedKey(key), value) +} + +type SemanticOCR3_1KeyValueStoreReadTransaction struct { + rawTransaction ocr3_1types.KeyValueReadTransaction + limits ocr3_1types.ReportingPluginLimits +} + +var _ protocol.KeyValueStoreReadTransaction = &SemanticOCR3_1KeyValueStoreReadTransaction{} + +func (s *SemanticOCR3_1KeyValueStoreReadTransaction) Discard() { + s.rawTransaction.Discard() +} + +func (s *SemanticOCR3_1KeyValueStoreReadTransaction) Read(key []byte) ([]byte, error) { + if !(len(key) <= ocr3_1types.MaxMaxKeyValueKeyLength) { + return nil, fmt.Errorf("key length %d exceeds maximum %d", len(key), ocr3_1types.MaxMaxKeyValueKeyLength) + } + return s.rawTransaction.Read(pluginPrefixedKey(key)) +} + +func (s *SemanticOCR3_1KeyValueStoreReadTransaction) ReadHighestCommittedSeqNr() (uint64, error) { + seqNrRaw, err := s.rawTransaction.Read(highestCommittedSeqNrKey()) + if err != nil { + return 0, err + } + if seqNrRaw == nil { // indicates that we are starting from scratch + return 0, nil + } + if len(seqNrRaw) != 8 { + return 0, fmt.Errorf("expected 8 bytes for seqNr, got %d", len(seqNrRaw)) + } + return binary.BigEndian.Uint64(seqNrRaw), nil +} + +func (s *SemanticOCR3_1KeyValueStoreReadWriteTransaction) WriteHighestCommittedSeqNr(seqNr uint64) error { + return s.rawTransaction.Write(highestCommittedSeqNrKey(), binary.BigEndian.AppendUint64(nil, seqNr)) +} + +func (s *SemanticOCR3_1KeyValueStoreReadTransaction) ReadBlob(blobDigest protocol.BlobDigest) ([]byte, error) { + var blob []byte + + length, err := s.ReadBlobMeta(blobDigest) + if err != nil { + return nil, fmt.Errorf("error reading blob meta for %s: %w", blobDigest, err) + } + if length == 0 { + + return nil, nil + } + + it := s.rawTransaction.Range(blobChunkPrefixedKey(blobDigest), nil) + defer it.Close() + + residualLength := length + + for i := uint64(0); residualLength > 0 && it.Next(); i++ { + key := it.Key() + if !bytes.Equal(key, blobChunkKey(blobDigest, i)) { + // gap in keys, we're missing a chunk + return nil, nil + } + + value, err := it.Value() + if err != nil { + return nil, fmt.Errorf("error reading value for key %s: %w", key, err) + } + + expectedChunkSize := min(protocol.BlobChunkSize, residualLength) + actualChunkSize := uint64(len(value)) + if actualChunkSize != expectedChunkSize { + // we don't have the full blob yet + return nil, nil + } + + residualLength -= actualChunkSize + blob = append(blob, value...) + } + + err = it.Err() + if err != nil { + return nil, fmt.Errorf("error iterating over blob chunks: %w", err) + } + + if residualLength != 0 { + // we somehow don't have the full blob yet, defensive + return nil, nil + } + + return blob, nil +} + +func (s *SemanticOCR3_1KeyValueStoreReadTransaction) ReadBlobChunk(blobDigest protocol.BlobDigest, chunkIndex uint64) ([]byte, error) { + return s.rawTransaction.Read(blobChunkKey(blobDigest, chunkIndex)) +} + +func (s *SemanticOCR3_1KeyValueStoreReadWriteTransaction) WriteBlobChunk(blobDigest protocol.BlobDigest, chunkIndex uint64, chunk []byte) error { + return s.rawTransaction.Write(blobChunkKey(blobDigest, chunkIndex), chunk) +} + +func (s *SemanticOCR3_1KeyValueStoreReadTransaction) ReadBlobMeta(blobDigest protocol.BlobDigest) (uint64, error) { + lengthBytes, err := s.rawTransaction.Read(blobMetaPrefixKey(blobDigest)) + if err != nil { + return 0, fmt.Errorf("error reading blob meta for %s: %w", blobDigest, err) + } + if lengthBytes == nil { + // no record of the blob at all + return 0, nil + } + if len(lengthBytes) != 8 { + return 0, fmt.Errorf("expected 8 bytes for blob meta length, got %d", len(lengthBytes)) + } + return binary.BigEndian.Uint64(lengthBytes), nil +} + +func (s *SemanticOCR3_1KeyValueStoreReadWriteTransaction) WriteBlobMeta(blobDigest protocol.BlobDigest, length uint64) error { + if length == 0 { + return fmt.Errorf("cannot write blob meta with length 0 for blob %s", blobDigest) + } + lengthBytes := binary.BigEndian.AppendUint64(nil, length) + return s.rawTransaction.Write(blobMetaPrefixKey(blobDigest), lengthBytes) +} + +const ( + protocolPrefix = byte(0) + pluginPrefix = byte(1) + + blobChunkSuffix = "blob chunk" + blobMetaSuffix = "blob meta" + + highestCommittedSeqNrKeySuffix = "highestCommittedSeqNo" +) + +func highestCommittedSeqNrKey() []byte { + return protocolPrefixedKey([]byte(highestCommittedSeqNrKeySuffix)) +} + +func pluginPrefixedKey(key []byte) []byte { + return prefixKey(pluginPrefix, key) +} + +func protocolPrefixedKey(key []byte) []byte { + return prefixKey(protocolPrefix, key) +} + +func blobChunkPrefixedKey(blobDigest protocol.BlobDigest) []byte { + return append(protocolPrefixedKey([]byte(blobChunkSuffix)), blobDigest[:]...) +} + +func blobChunkKey(blobDigest protocol.BlobDigest, chunkIndex uint64) []byte { + chunkIndexBytes := binary.BigEndian.AppendUint64(nil, chunkIndex) + return append(blobChunkPrefixedKey(blobDigest), chunkIndexBytes...) +} + +func blobMetaPrefixKey(blobDigest protocol.BlobDigest) []byte { + return append(protocolPrefixedKey([]byte(blobMetaSuffix)), blobDigest[:]...) +} + +func prefixKey(prefix byte, key []byte) []byte { + return append([]byte{prefix}, key...) +} + +type limitCheckWriteSet struct { + m map[string][]byte + keysPlusValuesLength int + keysPlusValuesLengthLimit int +} + +func newLimitCheckWriteSet(keysPlusValuesLengthLimit int) *limitCheckWriteSet { + return &limitCheckWriteSet{ + make(map[string][]byte), + 0, + keysPlusValuesLengthLimit, + } +} + +func (l *limitCheckWriteSet) modify(key []byte, value []byte) error { + + add, sub := 0, 0 + if prevValue, ok := l.m[string(key)]; ok { + add = len(value) + sub = len(prevValue) + } else { + if len(key)+len(value) < len(key) { + return fmt.Errorf("key + value length overflow") + } + add = len(key) + len(value) + } + + keysPlusValuesLengthMinusExistingValue := l.keysPlusValuesLength - sub + if keysPlusValuesLengthMinusExistingValue+add < keysPlusValuesLengthMinusExistingValue { + return fmt.Errorf("keys + values length overflow") + } + if keysPlusValuesLengthMinusExistingValue+add > l.keysPlusValuesLengthLimit { + return fmt.Errorf("keys + values length %d exceeds limit %d", keysPlusValuesLengthMinusExistingValue+add, l.keysPlusValuesLengthLimit) + } + l.m[string(key)] = bytes.Clone(value) + l.keysPlusValuesLength = keysPlusValuesLengthMinusExistingValue + add + return nil +} + +func (l *limitCheckWriteSet) Write(key []byte, value []byte) error { + return l.modify(key, value) +} + +func (l *limitCheckWriteSet) Delete(key []byte) error { + return l.modify(key, nil) +} + +func (l *limitCheckWriteSet) Pairs() []protocol.KeyValuePair { + pairs := make([]protocol.KeyValuePair, 0, len(l.m)) + for k, v := range l.m { + pairs = append(pairs, protocol.KeyValuePair{ + []byte(k), + v, + v == nil, + }) + } + return pairs +} diff --git a/offchainreporting2plus/internal/shim/ocr3_1_reporting_plugin.go b/offchainreporting2plus/internal/shim/ocr3_1_reporting_plugin.go new file mode 100644 index 00000000..5e1ed8eb --- /dev/null +++ b/offchainreporting2plus/internal/shim/ocr3_1_reporting_plugin.go @@ -0,0 +1,96 @@ +package shim + +import ( + "context" + "fmt" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +// LimitCheckOCR3_1ReportingPlugin wraps another plugin and checks that its outputs respect +// limits. We use it to surface violations to authors of plugins as early as +// possible. +// +// It does not check inputs since those are checked by the SerializingEndpoint. +type LimitCheckOCR3_1ReportingPlugin[RI any] struct { + Plugin ocr3_1types.ReportingPlugin[RI] + Limits ocr3_1types.ReportingPluginLimits +} + +var _ ocr3_1types.ReportingPlugin[struct{}] = LimitCheckOCR3_1ReportingPlugin[struct{}]{} + +func (rp LimitCheckOCR3_1ReportingPlugin[RI]) Query(ctx context.Context, seqNr uint64, kvReader ocr3_1types.KeyValueReader, blobBroadcastFetcher ocr3_1types.BlobBroadcastFetcher) (types.Query, error) { + query, err := rp.Plugin.Query(ctx, seqNr, kvReader, blobBroadcastFetcher) + if err != nil { + return nil, err + } + if !(len(query) <= rp.Limits.MaxQueryLength) { + return nil, fmt.Errorf("LimitCheckOCR3Plugin: underlying plugin returned oversize query (%v vs %v)", len(query), rp.Limits.MaxQueryLength) + } + return query, nil +} + +func (rp LimitCheckOCR3_1ReportingPlugin[RI]) ObservationQuorum(ctx context.Context, seqNr uint64, aq types.AttributedQuery, aos []types.AttributedObservation, kvReader ocr3_1types.KeyValueReader, blobFetcher ocr3_1types.BlobFetcher) (bool, error) { + return rp.Plugin.ObservationQuorum(ctx, seqNr, aq, aos, kvReader, blobFetcher) +} + +func (rp LimitCheckOCR3_1ReportingPlugin[RI]) Observation(ctx context.Context, seqNr uint64, aq types.AttributedQuery, kvReader ocr3_1types.KeyValueReader, blobBroadcastFetcher ocr3_1types.BlobBroadcastFetcher) (types.Observation, error) { + observation, err := rp.Plugin.Observation(ctx, seqNr, aq, kvReader, blobBroadcastFetcher) + if err != nil { + return nil, err + } + if !(len(observation) <= rp.Limits.MaxObservationLength) { + return nil, fmt.Errorf("LimitCheckOCR3Plugin: underlying plugin returned oversize observation (%v vs %v)", len(observation), rp.Limits.MaxObservationLength) + } + return observation, nil +} + +func (rp LimitCheckOCR3_1ReportingPlugin[RI]) ValidateObservation(ctx context.Context, seqNr uint64, aq types.AttributedQuery, ao types.AttributedObservation, kvReader ocr3_1types.KeyValueReader, blobFetcher ocr3_1types.BlobFetcher) error { + return rp.Plugin.ValidateObservation(ctx, seqNr, aq, ao, kvReader, blobFetcher) +} + +func (rp LimitCheckOCR3_1ReportingPlugin[RI]) StateTransition(ctx context.Context, seqNr uint64, aq types.AttributedQuery, aos []types.AttributedObservation, kvReadWriter ocr3_1types.KeyValueReadWriter, blobFetcher ocr3_1types.BlobFetcher) (ocr3_1types.ReportsPlusPrecursor, error) { + reportsPlusPrecursor, err := rp.Plugin.StateTransition(ctx, seqNr, aq, aos, kvReadWriter, blobFetcher) + if err != nil { + return nil, err + } + + //if !(len(reportsPlusPrecursor) <= rp.Limits.MaxReportsPlusPrecursorLength) { + // return nil, fmt.Errorf("LimitCheckOCR3Plugin: underlying plugin returned oversize reportsPlus (%v vs %v)", len(reportsPlusPrecursor), rp.Limits.MaxReportsPlusPrecursorLength) + //} + return reportsPlusPrecursor, nil +} + +func (rp LimitCheckOCR3_1ReportingPlugin[RI]) Committed(ctx context.Context, seqNr uint64, keyValueReader ocr3_1types.KeyValueReader) error { + return rp.Plugin.Committed(ctx, seqNr, keyValueReader) +} + +func (rp LimitCheckOCR3_1ReportingPlugin[RI]) Reports(ctx context.Context, seqNr uint64, reportsPlusPrecursor ocr3_1types.ReportsPlusPrecursor) ([]ocr3types.ReportPlus[RI], error) { + reports, err := rp.Plugin.Reports(ctx, seqNr, reportsPlusPrecursor) + if err != nil { + return nil, err + } + if !(len(reports) <= rp.Limits.MaxReportCount) { + return nil, fmt.Errorf("LimitCheckOCR3Plugin: underlying plugin returned too many reports (%v vs %v)", len(reports), rp.Limits.MaxReportCount) + } + for i, reportPlus := range reports { + if !(len(reportPlus.ReportWithInfo.Report) <= rp.Limits.MaxReportLength) { + return nil, fmt.Errorf("LimitCheckOCR3Plugin: underlying plugin returned oversize report at index %v (%v vs %v)", i, len(reportPlus.ReportWithInfo.Report), rp.Limits.MaxReportLength) + } + } + return reports, nil +} + +func (rp LimitCheckOCR3_1ReportingPlugin[RI]) ShouldAcceptAttestedReport(ctx context.Context, seqNr uint64, report ocr3types.ReportWithInfo[RI]) (bool, error) { + return rp.Plugin.ShouldAcceptAttestedReport(ctx, seqNr, report) +} + +func (rp LimitCheckOCR3_1ReportingPlugin[RI]) ShouldTransmitAcceptedReport(ctx context.Context, seqNr uint64, report ocr3types.ReportWithInfo[RI]) (bool, error) { + return rp.Plugin.ShouldTransmitAcceptedReport(ctx, seqNr, report) +} + +func (rp LimitCheckOCR3_1ReportingPlugin[RI]) Close() error { + return rp.Plugin.Close() +} diff --git a/offchainreporting2plus/internal/shim/ocr3_1_serializing_endpoint.go b/offchainreporting2plus/internal/shim/ocr3_1_serializing_endpoint.go new file mode 100644 index 00000000..310587e8 --- /dev/null +++ b/offchainreporting2plus/internal/shim/ocr3_1_serializing_endpoint.go @@ -0,0 +1,402 @@ +// Package shim contains implementations of internal types in terms of the external types +package shim + +import ( + "fmt" + "math" + "sync" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3config" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3_1/protocol" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3_1/serialization" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/libocr/subprocesses" +) + +type OCR3_1SerializingEndpoint[RI any] struct { + chTelemetry chan<- *serialization.TelemetryWrapper + configDigest types.ConfigDigest + endpoint types.BinaryNetworkEndpoint2 + maxSigLen int + logger commontypes.Logger + metrics *serializingEndpointMetrics + pluginLimits ocr3_1types.ReportingPluginLimits + publicConfig ocr3config.PublicConfig + + mutex sync.Mutex + subprocesses subprocesses.Subprocesses + started bool + closed bool + closedChOut bool + chCancel chan struct{} + chOut chan protocol.MessageWithSender[RI] + taper loghelper.LogarithmicTaper +} + +var _ protocol.NetworkEndpoint[struct{}] = (*OCR3_1SerializingEndpoint[struct{}])(nil) + +func NewOCR3_1SerializingEndpoint[RI any]( + chTelemetry chan<- *serialization.TelemetryWrapper, + configDigest types.ConfigDigest, + endpoint types.BinaryNetworkEndpoint2, + maxSigLen int, + logger commontypes.Logger, + metricsRegisterer prometheus.Registerer, + pluginLimits ocr3_1types.ReportingPluginLimits, + publicConfig ocr3config.PublicConfig, +) *OCR3_1SerializingEndpoint[RI] { + return &OCR3_1SerializingEndpoint[RI]{ + chTelemetry, + configDigest, + endpoint, + maxSigLen, + logger, + newSerializingEndpointMetrics(metricsRegisterer, logger), + pluginLimits, + publicConfig, + + sync.Mutex{}, + subprocesses.Subprocesses{}, + false, + false, + false, + make(chan struct{}), + make(chan protocol.MessageWithSender[RI]), + loghelper.LogarithmicTaper{}, + } +} + +func (n *OCR3_1SerializingEndpoint[RI]) sendTelemetry(t *serialization.TelemetryWrapper) { + select { + case n.chTelemetry <- t: + n.metrics.sentMessagesTotal.Inc() + n.taper.Reset(func(oldCount uint64) { + n.logger.Info("OCR3_1SerializingEndpoint: stopped dropping telemetry", commontypes.LogFields{ + "droppedCount": oldCount, + }) + }) + default: + n.metrics.droppedMessagesTotal.Inc() + n.taper.Trigger(func(newCount uint64) { + n.logger.Warn("OCR3_1SerializingEndpoint: dropping telemetry", commontypes.LogFields{ + "droppedCount": newCount, + }) + }) + } +} + +func (n *OCR3_1SerializingEndpoint[RI]) toOutboundBinaryMessage(msg protocol.Message[RI]) (types.OutboundBinaryMessage, *serialization.MessageWrapper) { + if !msg.CheckSize(n.publicConfig.N(), n.publicConfig.F, n.pluginLimits, n.maxSigLen) { + n.logger.Error("OCR3_1SerializingEndpoint: Dropping outgoing message because it fails size check", commontypes.LogFields{ + "limits": n.pluginLimits, + }) + return nil, nil + } + payload, pbm, err := serialization.Serialize(msg) + if err != nil { + n.logger.Error("OCR3_1SerializingEndpoint: Failed to serialize", commontypes.LogFields{ + "message": msg, + }) + return nil, nil + } + + // Convert message into OutboundBinaryMessage. We can do this here because + // for every protocol message type we know the corresponding + // OutboundBinaryMessage type and priority. + switch msg := msg.(type) { + case protocol.MessageNewEpochWish[RI]: + return types.OutboundBinaryMessagePlain{payload, types.BinaryMessagePriorityDefault}, pbm + case protocol.MessageEpochStartRequest[RI]: + return types.OutboundBinaryMessagePlain{payload, types.BinaryMessagePriorityDefault}, pbm + case protocol.MessageEpochStart[RI]: + return types.OutboundBinaryMessagePlain{payload, types.BinaryMessagePriorityDefault}, pbm + case protocol.MessageRoundStart[RI]: + return types.OutboundBinaryMessagePlain{payload, types.BinaryMessagePriorityDefault}, pbm + case protocol.MessageObservation[RI]: + return types.OutboundBinaryMessagePlain{payload, types.BinaryMessagePriorityDefault}, pbm + case protocol.MessageProposal[RI]: + return types.OutboundBinaryMessagePlain{payload, types.BinaryMessagePriorityDefault}, pbm + case protocol.MessagePrepare[RI]: + return types.OutboundBinaryMessagePlain{payload, types.BinaryMessagePriorityDefault}, pbm + case protocol.MessageCommit[RI]: + return types.OutboundBinaryMessagePlain{payload, types.BinaryMessagePriorityDefault}, pbm + case protocol.MessageReportSignatures[RI]: + return types.OutboundBinaryMessagePlain{payload, types.BinaryMessagePriorityDefault}, pbm + case protocol.MessageCertifiedCommitRequest[RI]: + return types.OutboundBinaryMessagePlain{payload, types.BinaryMessagePriorityDefault}, pbm + case protocol.MessageCertifiedCommit[RI]: + return types.OutboundBinaryMessagePlain{payload, types.BinaryMessagePriorityDefault}, pbm + case protocol.MessageBlockSyncRequest[RI]: + return types.OutboundBinaryMessageRequest{ + types.SingleUseSizedLimitedResponsePolicy{ + math.MaxInt, + time.Now().Add(protocol.DeltaMaxBlockSyncRequest), + }, + payload, + types.BinaryMessagePriorityLow, + }, pbm + case protocol.MessageBlockSync[RI]: + return msg.RequestHandle.MakeResponse(payload), pbm + case protocol.MessageBlockSyncSummary[RI]: + return types.OutboundBinaryMessagePlain{payload, types.BinaryMessagePriorityLow}, pbm + + case protocol.MessageBlobOffer[RI]: + return types.OutboundBinaryMessagePlain{payload, types.BinaryMessagePriorityDefault}, pbm + case protocol.MessageBlobChunkRequest[RI]: + return types.OutboundBinaryMessageRequest{ + types.SingleUseSizedLimitedResponsePolicy{ + math.MaxInt, + time.Now().Add(protocol.DeltaBlobChunkRequestTimeout), + }, + payload, + types.BinaryMessagePriorityDefault, + }, pbm + case protocol.MessageBlobChunkResponse[RI]: + return msg.RequestHandle.MakeResponse(payload), pbm + case protocol.MessageBlobAvailable[RI]: + return types.OutboundBinaryMessagePlain{payload, types.BinaryMessagePriorityDefault}, pbm + } + + panic("unreachable") +} + +func (n *OCR3_1SerializingEndpoint[RI]) fromInboundBinaryMessage(inboundBinaryMessage types.InboundBinaryMessage) (protocol.Message[RI], *serialization.MessageWrapper, error) { + var payload []byte + var requestHandle types.RequestHandle + switch m := inboundBinaryMessage.(type) { + case types.InboundBinaryMessagePlain: + payload = m.Payload + case types.InboundBinaryMessageRequest: + payload = m.Payload + requestHandle = m.RequestHandle + case types.InboundBinaryMessageResponse: + payload = m.Payload + } + + m, pbm, err := serialization.Deserialize[RI](n.publicConfig.N(), payload, requestHandle) + if err != nil { + return nil, nil, err + } + + // Check InboundBinaryMessage type and priority. We can do this here because + // for every protocol message type we know the corresponding + // InboundBinaryMessage type and priority. + switch m.(type) { + case protocol.MessageNewEpochWish[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessagePlain); !ok || ibm.Priority != types.BinaryMessagePriorityDefault { + return protocol.MessageNewEpochWish[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageNewEpochWish") + } + case protocol.MessageEpochStartRequest[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessagePlain); !ok || ibm.Priority != types.BinaryMessagePriorityDefault { + return protocol.MessageEpochStartRequest[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageEpochStartRequest") + } + case protocol.MessageEpochStart[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessagePlain); !ok || ibm.Priority != types.BinaryMessagePriorityDefault { + return protocol.MessageEpochStart[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageEpochStart") + } + case protocol.MessageRoundStart[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessagePlain); !ok || ibm.Priority != types.BinaryMessagePriorityDefault { + return protocol.MessageRoundStart[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageRoundStart") + } + case protocol.MessageObservation[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessagePlain); !ok || ibm.Priority != types.BinaryMessagePriorityDefault { + return protocol.MessageObservation[RI]{}, pbm, fmt.Errorf("wrong type or request ID for MessageObservation") + } + case protocol.MessageProposal[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessagePlain); !ok || ibm.Priority != types.BinaryMessagePriorityDefault { + return protocol.MessageProposal[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageProposal") + } + case protocol.MessagePrepare[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessagePlain); !ok || ibm.Priority != types.BinaryMessagePriorityDefault { + return protocol.MessagePrepare[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessagePrepare") + } + case protocol.MessageCommit[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessagePlain); !ok || ibm.Priority != types.BinaryMessagePriorityDefault { + return protocol.MessageCommit[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageCommit") + } + case protocol.MessageReportSignatures[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessagePlain); !ok || ibm.Priority != types.BinaryMessagePriorityDefault { + return protocol.MessageReportSignatures[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageReportSignatures") + } + case protocol.MessageCertifiedCommitRequest[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessagePlain); !ok || ibm.Priority != types.BinaryMessagePriorityDefault { + return protocol.MessageCertifiedCommitRequest[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageCertifiedCommitRequest") + } + case protocol.MessageCertifiedCommit[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessagePlain); !ok || ibm.Priority != types.BinaryMessagePriorityDefault { + return protocol.MessageCertifiedCommit[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageCertifiedCommit") + } + case protocol.MessageBlockSyncRequest[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessageRequest); !ok || ibm.Priority != types.BinaryMessagePriorityLow { + return protocol.MessageBlockSyncRequest[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageBlockSyncRequest") + } + case protocol.MessageBlockSync[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessageResponse); !ok || ibm.Priority != types.BinaryMessagePriorityLow { + return protocol.MessageBlockSync[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageBlockSync") + } + case protocol.MessageBlockSyncSummary[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessagePlain); !ok || ibm.Priority != types.BinaryMessagePriorityLow { + return protocol.MessageBlockSyncSummary[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageBlockSyncSummary") + } + + case protocol.MessageBlobOffer[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessagePlain); !ok || ibm.Priority != types.BinaryMessagePriorityDefault { + return protocol.MessageBlobOffer[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageBlobOffer") + } + case protocol.MessageBlobAvailable[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessagePlain); !ok || ibm.Priority != types.BinaryMessagePriorityDefault { + return protocol.MessageBlobAvailable[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageBlobAvailable") + } + case protocol.MessageBlobChunkRequest[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessageRequest); !ok || ibm.Priority != types.BinaryMessagePriorityDefault { + return protocol.MessageBlobChunkRequest[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageBlobChunkRequest") + } + case protocol.MessageBlobChunkResponse[RI]: + if ibm, ok := inboundBinaryMessage.(types.InboundBinaryMessageResponse); !ok || ibm.Priority != types.BinaryMessagePriorityDefault { + return protocol.MessageBlobChunkResponse[RI]{}, pbm, fmt.Errorf("wrong type or priority for MessageBlobChunkResponse") + } + } + + if !m.CheckSize(n.publicConfig.N(), n.publicConfig.F, n.pluginLimits, n.maxSigLen) { + return nil, nil, fmt.Errorf("message failed size check") + } + + return m, pbm, nil +} + +// Start starts the SerializingEndpoint. It will also start the underlying endpoint. +func (n *OCR3_1SerializingEndpoint[RI]) Start() error { + n.mutex.Lock() + defer n.mutex.Unlock() + + if n.started { + return fmt.Errorf("cannot start already started SerializingEndpoint") + } + n.started = true + + // irrelevant detail: Start() is not needed for BinaryNetworkEndpoint2 + // if err := n.endpoint.Start(); err != nil { + // return fmt.Errorf("error while starting OCR3_1SerializingEndpoint: %w", err) + // } + + n.subprocesses.Go(func() { + chRaw := n.endpoint.Receive() + for { + select { + case raw, ok := <-chRaw: + if !ok { + n.mutex.Lock() + defer n.mutex.Unlock() + n.closedChOut = true + close(n.chOut) + return + } + + m, pbm, err := n.fromInboundBinaryMessage(raw.InboundBinaryMessage) + if err != nil { + n.logger.Error("OCR3_1SerializingEndpoint: Failed to deserialize", commontypes.LogFields{ + "error": err, + }) + // TODO: This will falsely report a deserialization error (without relevant details) if priority or message type + // don't match + n.sendTelemetry(&serialization.TelemetryWrapper{ + Wrapped: &serialization.TelemetryWrapper_AssertionViolation{&serialization.TelemetryAssertionViolation{ + Violation: &serialization.TelemetryAssertionViolation_InvalidSerialization{&serialization.TelemetryAssertionViolationInvalidSerialization{ + ConfigDigest: n.configDigest[:], + SerializedMsg: raw.InboundBinaryMessage.GetPayload(), + Sender: uint32(raw.Sender), + }}, + }}, + UnixTimeNanoseconds: time.Now().UnixNano(), + }) + break + } + + n.sendTelemetry(&serialization.TelemetryWrapper{ + Wrapped: &serialization.TelemetryWrapper_MessageReceived{&serialization.TelemetryMessageReceived{ + ConfigDigest: n.configDigest[:], + Msg: pbm, + Sender: uint32(raw.Sender), + }}, + UnixTimeNanoseconds: time.Now().UnixNano(), + }) + + select { + case n.chOut <- protocol.MessageWithSender[RI]{m, raw.Sender}: + case <-n.chCancel: + return + } + case <-n.chCancel: + return + } + } + }) + + return nil +} + +// Close closes the SerializingEndpoint. It will also close the underlying endpoint. +func (n *OCR3_1SerializingEndpoint[RI]) Close() error { + n.mutex.Lock() + defer n.mutex.Unlock() + + if n.started && !n.closed { + n.closed = true + close(n.chCancel) + n.subprocesses.Wait() + + if !n.closedChOut { + n.closedChOut = true + close(n.chOut) + } + + err := n.endpoint.Close() + n.metrics.Close() + + return err + } + + return nil +} + +func (n *OCR3_1SerializingEndpoint[RI]) SendTo(msg protocol.Message[RI], to commontypes.OracleID) { + oMsg, pbm := n.toOutboundBinaryMessage(msg) + if oMsg != nil { + n.endpoint.SendTo(oMsg, to) + n.sendTelemetry(&serialization.TelemetryWrapper{ + Wrapped: &serialization.TelemetryWrapper_MessageSent{&serialization.TelemetryMessageSent{ + ConfigDigest: n.configDigest[:], + Msg: pbm, + SerializedMsg: oMsg.GetPayload(), + // TODO: What about priority or message type? + Receiver: uint32(to), + }}, + UnixTimeNanoseconds: time.Now().UnixNano(), + }) + } +} + +func (n *OCR3_1SerializingEndpoint[RI]) Broadcast(msg protocol.Message[RI]) { + oMsg, pbm := n.toOutboundBinaryMessage(msg) + if oMsg != nil { + n.endpoint.Broadcast(oMsg) + n.sendTelemetry(&serialization.TelemetryWrapper{ + Wrapped: &serialization.TelemetryWrapper_MessageBroadcast{&serialization.TelemetryMessageBroadcast{ + ConfigDigest: n.configDigest[:], + Msg: pbm, + // TODO: What about priority or message type? + SerializedMsg: oMsg.GetPayload(), + }}, + UnixTimeNanoseconds: time.Now().UnixNano(), + }) + } +} + +func (n *OCR3_1SerializingEndpoint[RI]) Receive() <-chan protocol.MessageWithSender[RI] { + return n.chOut +} diff --git a/offchainreporting2plus/internal/shim/ocr3_1_telemetry_sender.go b/offchainreporting2plus/internal/shim/ocr3_1_telemetry_sender.go new file mode 100644 index 00000000..740a7bc9 --- /dev/null +++ b/offchainreporting2plus/internal/shim/ocr3_1_telemetry_sender.go @@ -0,0 +1,58 @@ +package shim + +import ( + "time" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3_1/serialization" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type OCR3_1TelemetrySender struct { + chTelemetry chan<- *serialization.TelemetryWrapper + logger commontypes.Logger + taper loghelper.LogarithmicTaper +} + +func NewOCR3_1TelemetrySender(chTelemetry chan<- *serialization.TelemetryWrapper, logger commontypes.Logger) *OCR3_1TelemetrySender { + return &OCR3_1TelemetrySender{chTelemetry, logger, loghelper.LogarithmicTaper{}} +} + +func (ts *OCR3_1TelemetrySender) send(t *serialization.TelemetryWrapper) { + select { + case ts.chTelemetry <- t: + ts.taper.Reset(func(oldCount uint64) { + ts.logger.Info("NewOCR3_1TelemetrySender: stopped dropping telemetry", commontypes.LogFields{ + "droppedCount": oldCount, + }) + }) + default: + ts.taper.Trigger(func(newCount uint64) { + ts.logger.Warn("NewOCR3_1TelemetrySender: dropping telemetry", commontypes.LogFields{ + "droppedCount": newCount, + }) + }) + } +} + +func (ts *OCR3_1TelemetrySender) RoundStarted( + configDigest types.ConfigDigest, + epoch uint64, + seqNr uint64, + round uint64, + leader commontypes.OracleID, +) { + t := time.Now().UnixNano() + ts.send(&serialization.TelemetryWrapper{ + Wrapped: &serialization.TelemetryWrapper_RoundStarted{&serialization.TelemetryRoundStarted{ + ConfigDigest: configDigest[:], + Epoch: epoch, + Round: round, + Leader: uint64(leader), + Time: uint64(t), + SeqNr: seqNr, + }}, + UnixTimeNanoseconds: t, + }) +} diff --git a/offchainreporting2plus/ocr3_1types/blob.go b/offchainreporting2plus/ocr3_1types/blob.go new file mode 100644 index 00000000..6367f05b --- /dev/null +++ b/offchainreporting2plus/ocr3_1types/blob.go @@ -0,0 +1,34 @@ +package ocr3_1types + +import ( + "context" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3_1/blobtypes" +) + +type BlobHandle = blobtypes.BlobHandle + +//go-sumtype:decl BlobExpirationHint + +type BlobExpirationHint interface { + isBlobExpirationHint() +} + +var _ BlobExpirationHint = BlobExpirationHintSequenceNumber{} + +type BlobExpirationHintSequenceNumber struct{ SeqNr uint64 } + +func (BlobExpirationHintSequenceNumber) isBlobExpirationHint() {} + +type BlobBroadcaster interface { + BroadcastBlob(ctx context.Context, payload []byte, expirationHint BlobExpirationHint) (BlobHandle, error) +} + +type BlobFetcher interface { + FetchBlob(ctx context.Context, handle BlobHandle) ([]byte, error) +} + +type BlobBroadcastFetcher interface { + BlobBroadcaster + BlobFetcher +} diff --git a/offchainreporting2plus/ocr3_1types/db.go b/offchainreporting2plus/ocr3_1types/db.go new file mode 100644 index 00000000..096a5525 --- /dev/null +++ b/offchainreporting2plus/ocr3_1types/db.go @@ -0,0 +1,27 @@ +package ocr3_1types + +import ( + "context" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type Database interface { + ocr3types.Database + BlockDatabase +} + +// BlockDatabase persistently stores state transition blocks to support state transfer requests +// Expect Write to be called far more frequently than Read. +// +// All its functions should be thread-safe. + +type BlockDatabase interface { + // ReadBlock retrieves a block from the database. + // If the block is not found, ErrBlockNotFound should be returned. + ReadBlock(ctx context.Context, configDigest types.ConfigDigest, seqNr uint64) ([]byte, error) + // WriteBlock writes a block to the database. + // Writing with a nil value is the same as deleting. + WriteBlock(ctx context.Context, configDigest types.ConfigDigest, seqNr uint64, block []byte) error +} diff --git a/offchainreporting2plus/ocr3_1types/kv.go b/offchainreporting2plus/ocr3_1types/kv.go new file mode 100644 index 00000000..bff92925 --- /dev/null +++ b/offchainreporting2plus/ocr3_1types/kv.go @@ -0,0 +1,82 @@ +package ocr3_1types + +import ( + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type KeyValueReadWriteTransaction interface { + KeyValueReadTransaction + // 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 KeyValueReadTransaction 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. 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) KeyValueIterator + Discard() +} + +// KeyValueIterator 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 KeyValueIterator 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, [KeyValueIterator.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 [KeyValueIterator.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., [KeyValueIterator.Next] will return + // false. Must be called in any case, even if the iteration encountered any + // error through [KeyValueIterator.Value] or [KeyValueIterator.Err]. + Close() error +} + +type KeyValueDatabase interface { + NewReadWriteTransaction() (KeyValueReadWriteTransaction, error) + NewReadTransaction() (KeyValueReadTransaction, error) + + Close() error +} + +type KeyValueDatabaseFactory interface { + NewKeyValueDatabase(configDigest types.ConfigDigest) (KeyValueDatabase, error) +} diff --git a/offchainreporting2plus/ocr3_1types/plugin.go b/offchainreporting2plus/ocr3_1types/plugin.go new file mode 100644 index 00000000..8938bc9d --- /dev/null +++ b/offchainreporting2plus/ocr3_1types/plugin.go @@ -0,0 +1,266 @@ +package ocr3_1types + +import ( + "context" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type ReportsPlusPrecursor []byte + +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 KeyValueReader interface { + // A return value of nil indicates that the key does not exist. + Read(key []byte) ([]byte, error) +} + +type KeyValueReadWriter interface { + KeyValueReader + Write(key []byte, value []byte) error + Delete(key []byte) 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, follower oracles +// will call Observation, ValidateObservation, ObservationQuorum, StateTransition, +// 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 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. +// +// 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 ocr3config.PublicConfig for more information on how +// to configure timeouts. +// +// 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 +// persist it in the key-value store. 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 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 KeyValueReader gives read access to the key-value store in the state + // that it is after seqNr - 1 is committed. + Query(ctx context.Context, seqNr uint64, keyValueReader KeyValueReader, 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 KeyValueReader gives read access to the key-value store in the state + // that it is after seqNr - 1 is committed. + Observation(ctx context.Context, seqNr uint64, aq types.AttributedQuery, keyValueReader KeyValueReader, 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 should be pure. This is called for each observation, don't do + // anything slow in here. + // + // You may assume that the seqNr is increasing strictly monotonically + // across the lifetime of a protocol instance. + // + // The KeyValueReader gives read access to the key-value store in the state + // that it is after seqNr - 1 is committed. + ValidateObservation(ctx context.Context, seqNr uint64, aq types.AttributedQuery, ao types.AttributedObservation, keyValueReader KeyValueReader, blobFetcher BlobFetcher) error + + // ObservationQuorum indicates whether the provided valid (according to + // ValidateObservation) observations are sufficient 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 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 KeyValueReader gives read access to the key-value store in the state + // that it is after seqNr - 1 is committed. + ObservationQuorum(ctx context.Context, seqNr uint64, aq types.AttributedQuery, aos []types.AttributedObservation, keyValueReader KeyValueReader, 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, as a side effect. + // + // This function should be pure. Don't do anything slow in here. + // + // + // 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, and (2) checked + // by ObservationQuorum to have reached quorum. + // + // The KeyValueReadWriter gives read and write access to the key-value store in the state + // that it is after seqNr - 1 is committed. + StateTransition(ctx context.Context, seqNr uint64, aq types.AttributedQuery, aos []types.AttributedObservation, keyValueReadWriter KeyValueReadWriter, blobFetcher BlobFetcher) (ReportsPlusPrecursor, error) + + // Committed notifies the plugin that a sequence number has been committed. + // It might or might not be preceded by a StateTransition call for the same + // sequence number. + // TODO: This function is not called by the protocol yet. + Committed(ctx context.Context, seqNr uint64, keyValueReader KeyValueReader) 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 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. + 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 oracle + // 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 +} + +// 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. +const ( + mib = 1024 * 1024 + + MaxMaxQueryLength = mib / 2 + MaxMaxObservationLength = mib / 2 + MaxMaxReportsPlusPrecursorLength = 5 * mib + MaxMaxReportLength = 5 * mib + MaxMaxReportCount = 2000 + + MaxMaxKeyValueKeyLength = 1 * mib + MaxMaxKeyValueValueLength = 2 * mib + + MaxMaxKeyValueModifiedKeysPlusValuesLength = 10 * mib + + MaxMaxBlobPayloadLength = 5 * mib +) + +// 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 ReportingPluginLimits struct { + MaxQueryLength int + MaxObservationLength int + MaxReportsPlusPrecursorLength int + MaxReportLength int + MaxReportCount int + + // This limit concerns modifications to key-values inside the + // StateTransition method. Write(k, v) and Delete(k) count as modifications. + // A modification that resets the value of a key to its original value at + // the start of StateTransition will still count towards the limit. + MaxKeyValueModifiedKeysPlusValuesLength int + + MaxBlobPayloadLength int + + // Mandatory blob rate limits will be introduced in a future release. +} + +type ReportingPluginInfo struct { + // Used for debugging purposes. + Name string + + Limits ReportingPluginLimits +} diff --git a/offchainreporting2plus/oracle.go b/offchainreporting2plus/oracle.go index 92f6a33f..9c318bda 100644 --- a/offchainreporting2plus/oracle.go +++ b/offchainreporting2plus/oracle.go @@ -5,6 +5,8 @@ import ( "fmt" "sync" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + "github.com/prometheus/client_golang/prometheus" "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/internal/loghelper" @@ -243,6 +245,83 @@ func (args OCR3OracleArgs[RI]) runManaged(ctx context.Context) { ) } +type OCR3_1OracleArgs[RI any] struct { + // A factory for producing network endpoints. A network endpoints consists of + // networking methods a consumer must implement to allow a node to + // communicate with other participating nodes. + BinaryNetworkEndpointFactory types.BinaryNetworkEndpoint2Factory + + // V2Bootstrappers is the list of bootstrap node addresses and IDs for the v2 stack. + V2Bootstrappers []commontypes.BootstrapperLocator + + // Tracks configuration changes. + ContractConfigTracker types.ContractConfigTracker + + // Transmit reports to the targeted system (e.g. a blockchain) + ContractTransmitter ocr3types.ContractTransmitter[RI] + + // Database provides persistent storage. + Database ocr3_1types.Database + + // KeyValueDatabaseFactory produces KeyValueDatabase for keeping the reporting plugins' state consistently across oracles. + KeyValueDatabaseFactory ocr3_1types.KeyValueDatabaseFactory + + // LocalConfig contains oracle-specific configuration details which are not + // mandated by the on-chain configuration specification via OffchainAggregatoo.SetConfig. + LocalConfig types.LocalConfig + + // Logger logs stuff. + Logger commontypes.Logger + + // Enables adding metrics to track. This may be nil. + MetricsRegisterer prometheus.Registerer + + // Used to send logs to a monitor. + MonitoringEndpoint commontypes.MonitoringEndpoint + + // Computes a config digest using purely offchain logic. + OffchainConfigDigester types.OffchainConfigDigester + + // OffchainKeyring contains the secret keys needed for the OCR protocol, and methods + // which use those keys without exposing them to the rest of the application. + OffchainKeyring types.OffchainKeyring + + // OnchainKeyring is used to sign reports that can be validated + // offchain and by the target contract. + OnchainKeyring ocr3types.OnchainKeyring[RI] + + // PluginFactory creates Plugins that determine the "application logic" used + // in a protocol instance. + ReportingPluginFactory ocr3_1types.ReportingPluginFactory[RI] +} + +func (OCR3_1OracleArgs[RI]) oracleArgsMarker() {} + +func (args OCR3_1OracleArgs[RI]) localConfig() types.LocalConfig { return args.LocalConfig } + +func (args OCR3_1OracleArgs[RI]) runManaged(ctx context.Context) { + logger := loghelper.MakeRootLoggerWithContext(args.Logger) + + managed.RunManagedOCR3_1Oracle( + ctx, + + args.V2Bootstrappers, + args.ContractConfigTracker, + args.ContractTransmitter, + args.Database, + args.KeyValueDatabaseFactory, + args.LocalConfig, + logger, + args.MetricsRegisterer, + args.MonitoringEndpoint, + args.BinaryNetworkEndpointFactory, + args.OffchainConfigDigester, + args.OffchainKeyring, + args.OnchainKeyring, + args.ReportingPluginFactory, + ) +} + type oracleState int const ( diff --git a/offchainreporting2plus/types/network.go b/offchainreporting2plus/types/network.go new file mode 100644 index 00000000..16620113 --- /dev/null +++ b/offchainreporting2plus/types/network.go @@ -0,0 +1,150 @@ +package types + +import ( + "time" + + "github.com/smartcontractkit/libocr/commontypes" +) + +type BinaryMessageOutboundPriority byte + +const ( + _ BinaryMessageOutboundPriority = iota + BinaryMessagePriorityLow + BinaryMessagePriorityDefault +) + +type ResponsePolicy interface { + isResponsePolicy() +} + +type SingleUseSizedLimitedResponsePolicy struct { + MaxSize int // TODO the name must demonstrate what size is measured in + ExpiryTimestamp time.Time +} + +func (SingleUseSizedLimitedResponsePolicy) isResponsePolicy() {} + +type RequestHandle interface { + MakeResponse(payload []byte) OutboundBinaryMessageResponse +} + +type OutboundBinaryMessage interface { + isOutboundBinaryMessage() + GetPayload() []byte +} + +var _ OutboundBinaryMessage = OutboundBinaryMessagePlain{} +var _ OutboundBinaryMessage = OutboundBinaryMessageRequest{} +var _ OutboundBinaryMessage = OutboundBinaryMessageResponse{} + +type OutboundBinaryMessagePlain struct { + Payload []byte + Priority BinaryMessageOutboundPriority +} + +func (OutboundBinaryMessagePlain) isOutboundBinaryMessage() {} + +func (o OutboundBinaryMessagePlain) GetPayload() []byte { + return o.Payload +} + +type OutboundBinaryMessageRequest struct { + ResponsePolicy ResponsePolicy + Payload []byte + Priority BinaryMessageOutboundPriority +} + +func (OutboundBinaryMessageRequest) isOutboundBinaryMessage() {} + +func (o OutboundBinaryMessageRequest) GetPayload() []byte { + return o.Payload +} + +type OutboundBinaryMessageResponse struct { + // By making the request handle private, we want to discourage folks from creating + // this structure directly (unless they're implementing a BinaryNetworkEndpoint). + // Note that, with a ragep2p backend (in its current version), we need the Response + // priority to match the Request priority. Otherwise, responses would be dropped. + // We try to protect a user of the interface from this sharp edge. + requestHandle RequestHandle + Payload []byte + Priority BinaryMessageOutboundPriority +} + +// Don't use this function unless you're implementing a BinaryNetworkEndpoint! +// The purpose of this function is to enable implementers of a RequestHandle instance to +// generate a OutboundBinaryMessageResponse in RequestHandle.MakeResponse() +func MustMakeOutboundBinaryMessageResponse(requestHandle RequestHandle, payload []byte, priority BinaryMessageOutboundPriority) OutboundBinaryMessageResponse { + return OutboundBinaryMessageResponse{ + requestHandle, + payload, + priority, + } +} + +// Don't use this function unless you're implementing a BinaryNetworkEndpoint! +func MustGetOutboundBinaryMessageResponseRequestHandle(msg OutboundBinaryMessageResponse) RequestHandle { + return msg.requestHandle +} + +func (OutboundBinaryMessageResponse) isOutboundBinaryMessage() {} + +func (o OutboundBinaryMessageResponse) GetPayload() []byte { + return o.Payload +} + +type InboundBinaryMessage interface { + isInboundBinaryMessage() + GetPayload() []byte +} + +var _ InboundBinaryMessage = InboundBinaryMessagePlain{} +var _ InboundBinaryMessage = InboundBinaryMessageRequest{} +var _ InboundBinaryMessage = InboundBinaryMessageResponse{} + +type InboundBinaryMessagePlain struct { + Payload []byte + Priority BinaryMessageOutboundPriority // the priority the sender used for transmitting this message +} + +func (InboundBinaryMessagePlain) isInboundBinaryMessage() {} + +func (i InboundBinaryMessagePlain) GetPayload() []byte { + return i.Payload +} + +type InboundBinaryMessageRequest struct { + RequestHandle RequestHandle + Payload []byte + Priority BinaryMessageOutboundPriority // the priority the sender used for transmitting this request +} + +func (InboundBinaryMessageRequest) isInboundBinaryMessage() {} + +func (i InboundBinaryMessageRequest) GetPayload() []byte { + return i.Payload +} + +type InboundBinaryMessageResponse struct { + Payload []byte + Priority BinaryMessageOutboundPriority // the priority the sender used for transmitting this response +} + +func (InboundBinaryMessageResponse) isInboundBinaryMessage() {} + +func (i InboundBinaryMessageResponse) GetPayload() []byte { + return i.Payload +} + +type InboundBinaryMessageWithSender struct { + InboundBinaryMessage + Sender commontypes.OracleID +} + +type BinaryNetworkEndpoint2 interface { + SendTo(msg OutboundBinaryMessage, to commontypes.OracleID) + Broadcast(msg OutboundBinaryMessage) + Receive() <-chan InboundBinaryMessageWithSender + Close() error +} diff --git a/offchainreporting2plus/types/types.go b/offchainreporting2plus/types/types.go index 96b061eb..3905d7f7 100644 --- a/offchainreporting2plus/types/types.go +++ b/offchainreporting2plus/types/types.go @@ -19,7 +19,16 @@ type BinaryNetworkEndpointLimits struct { BytesCapacityPerOracle int } -// BinaryNetworkEndpointFactory creates permissioned BinaryNetworkEndpoints. +// 2x one per priority +type BinaryNetworkEndpoint2Config struct { + BinaryNetworkEndpointLimits + + // Buffer sizes specified below override the values set in PeerConfig. + OverrideIncomingMessageBufferSize int + OverrideOutgoingMessageBufferSize int +} + +// BinaryNetworkEndpointFactory creates permissioned BinaryNetworkEndpoint instances. // // All its functions should be thread-safe. type BinaryNetworkEndpointFactory interface { @@ -34,6 +43,20 @@ type BinaryNetworkEndpointFactory interface { PeerID() string } +// BinaryNetworkEndpoint2Factory creates permissioned BinaryNetworkEndpoint2 instances. +// +// All its functions should be thread-safe. +type BinaryNetworkEndpoint2Factory interface { + NewEndpoint( + cd ConfigDigest, + peerIDs []string, + v2bootstrappers []commontypes.BootstrapperLocator, + defaultPriorityConfig BinaryNetworkEndpoint2Config, + lowPriorityConfig BinaryNetworkEndpoint2Config, + ) (BinaryNetworkEndpoint2, error) + PeerID() string +} + // BootstrapperFactory creates permissioned Bootstrappers. // // All its functions should be thread-safe. @@ -47,6 +70,11 @@ type BootstrapperFactory interface { type Query []byte +type AttributedQuery struct { + Query Query + Proposer commontypes.OracleID +} + type Observation []byte type AttributedObservation struct {