From 0fcfbcb19303950c868be24131cb5a67656096a2 Mon Sep 17 00:00:00 2001 From: kaleofduty <59616916+kaleofduty@users.noreply.github.com> Date: Wed, 19 Mar 2025 15:40:37 +0100 Subject: [PATCH] alpha based on 722d48b48dc9234402e103e46cf41c7ddc623a90 --- commontypes/types.go | 1 + go.mod | 4 +- go.sum | 8 +- internal/ringbuffer/ringbuffer.go | 93 + networking/ocr3_1_peer.go | 28 + networking/ocr_endpoint_v2.go | 25 +- networking/ocr_endpoint_v3.go | 441 +++ networking/peer_group.go | 4 + networking/peer_v2.go | 43 + networking/ragedisco/ragep2p_discoverer.go | 18 +- networking/rageping/service.go | 43 +- offchainreporting2plus/chains/evmutil/evm.go | 1 - .../{ocr3 => common}/minheap/minheap.go | 0 .../common.go => common/plugin_caller.go} | 8 +- .../{ocr3/protocol => common}/pool/pool.go | 0 .../ringbuffer/ringbuffer.go | 10 + .../{ocr3 => common}/scheduler/scheduler.go | 2 +- .../internal/config/ocr2config/serialize.go | 2 - .../internal/config/ocr3config/serialize.go | 2 - .../internal/managed/limits/ocr3_1_limits.go | 205 ++ .../managed/managed_mercury_oracle.go | 3 +- .../internal/managed/managed_ocr3_1_oracle.go | 276 ++ .../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 | 7 +- .../internal/ocr3/protocol/transmission.go | 7 +- .../internal/ocr3_1/protocol/db.go | 31 + .../internal/ocr3_1/protocol/event.go | 286 ++ .../internal/ocr3_1/protocol/message.go | 759 +++++ .../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 | 414 +++ .../ocr3_1/protocol/outcome_generation.go | 528 +++ .../protocol/outcome_generation_follower.go | 1157 +++++++ .../protocol/outcome_generation_leader.go | 589 ++++ .../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/signed_data.go | 765 +++++ .../protocol/state_block_synchronization.go | 365 ++ .../ocr3_1/protocol/state_persistence.go | 517 +++ .../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 | 34 + .../offchainreporting3_1_db.pb.go | 224 ++ .../offchainreporting3_1_messages.pb.go | 2976 +++++++++++++++++ .../offchainreporting3_1_telemetry.pb.go | 837 +++++ .../ocr3_1/serialization/serialization.go | 1108 ++++++ .../ocr3_1/serialization/telemetry.go | 1 + .../internal/shim/ocr3_1_database.go | 128 + .../internal/shim/ocr3_1_reporting_plugin.go | 92 + .../shim/ocr3_1_serializing_endpoint.go | 308 ++ .../internal/shim/ocr3_1_telemetry_sender.go | 58 + offchainreporting2plus/ocr3_1types/db.go | 34 + offchainreporting2plus/ocr3_1types/plugin.go | 242 ++ offchainreporting2plus/ocr3_1types/types.go | 88 + offchainreporting2plus/oracle.go | 78 + offchainreporting2plus/types/network.go | 124 + offchainreporting2plus/types/types.go | 25 +- ragep2p/conn_rate_limiter.go | 4 +- ragep2p/demuxer.go | 53 +- ragep2p/frame.go | 291 +- ragep2p/internal/msgbuf/ringbuffer.go | 55 - .../ratelimitedconn/rate_limited_conn.go | 102 +- ragep2p/internal/responselimit/checker.go | 180 + ragep2p/internal/responselimit/policies.go | 55 + ragep2p/internal/types/types.go | 25 + ragep2p/ragep2p.go | 715 ++-- ragep2p/stream.go | 89 + ragep2p/stream2.go | 316 ++ 75 files changed, 16025 insertions(+), 526 deletions(-) create mode 100644 internal/ringbuffer/ringbuffer.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 (72%) 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/protocol/db.go create mode 100644 offchainreporting2plus/internal/ocr3_1/protocol/event.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/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_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/db.go create mode 100644 offchainreporting2plus/ocr3_1types/plugin.go create mode 100644 offchainreporting2plus/ocr3_1types/types.go create mode 100644 offchainreporting2plus/types/network.go delete mode 100644 ragep2p/internal/msgbuf/ringbuffer.go create mode 100644 ragep2p/internal/responselimit/checker.go create mode 100644 ragep2p/internal/responselimit/policies.go create mode 100644 ragep2p/internal/types/types.go create mode 100644 ragep2p/stream.go create mode 100644 ragep2p/stream2.go diff --git a/commontypes/types.go b/commontypes/types.go index ba5b35f..3792761 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 0d51e2e..861ab62 100644 --- a/go.mod +++ b/go.mod @@ -67,7 +67,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 @@ -101,7 +101,7 @@ require ( github.com/urfave/cli/v2 v2.25.7 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect - golang.org/x/net v0.25.0 // indirect + golang.org/x/net v0.30.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.20.0 // indirect diff --git a/go.sum b/go.sum index 962c3a4..58f73af 100644 --- a/go.sum +++ b/go.sum @@ -163,8 +163,8 @@ github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlT 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/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -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= @@ -317,8 +317,8 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= 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 0000000..b19337a --- /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/networking/ocr3_1_peer.go b/networking/ocr3_1_peer.go new file mode 100644 index 0000000..26cdc38 --- /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_v2.go b/networking/ocr_endpoint_v2.go index 9fa9272..107ad7b 100644 --- a/networking/ocr_endpoint_v2.go +++ b/networking/ocr_endpoint_v2.go @@ -57,7 +57,7 @@ type ocrEndpointV2 struct { // internal and state management chSendToSelf chan commontypes.BinaryMessageWithSender chClose chan struct{} - streams map[commontypes.OracleID]*ragep2p.Stream + streams map[commontypes.OracleID]*ragep2p.Stream2 registration io.Closer state ocrEndpointState @@ -131,7 +131,7 @@ func newOCREndpointV2( ownOracleID, chSendToSelf, make(chan struct{}), - make(map[commontypes.OracleID]*ragep2p.Stream), + make(map[commontypes.OracleID]*ragep2p.Stream2), registration, ocrEndpointUnstarted, sync.RWMutex{}, @@ -168,9 +168,10 @@ func (o *ocrEndpointV2) Start() error { continue } streamName := streamNameFromConfigDigest(o.configDigest) - stream, err := o.host.NewStream( + stream, err := o.host.NewStream2( pid, streamName, + ragep2p.StreamPriorityDefault, o.config.OutgoingMessageBufferSize, o.config.IncomingMessageBufferSize, o.limits.MaxMessageLength, @@ -190,7 +191,6 @@ func (o *ocrEndpointV2) Start() error { } for oid := range o.streams { - oid := oid o.subs.Go(func() { o.runRecv(oid) }) @@ -209,16 +209,20 @@ func (o *ocrEndpointV2) Start() error { // remote goes mad and sends us thousands of messages, we don't drop any // messages from good remotes func (o *ocrEndpointV2) runRecv(oid commontypes.OracleID) { - chRecv := o.streams[oid].ReceiveMessages() + chRecv := o.streams[oid].Receive() for { select { - case payload := <-chRecv: - msg := commontypes.BinaryMessageWithSender{ - Msg: payload, + case msg := <-chRecv: + msgPlain, ok := msg.(ragep2p.InboundBinaryMessagePlain) + if !ok { + o.logger.Warn("dropping message ", nil) // TODO + } + msgWithSender := commontypes.BinaryMessageWithSender{ + Msg: msgPlain.Payload, Sender: oid, } select { - case o.recv <- msg: + case o.recv <- msgWithSender: continue case <-o.chClose: return @@ -299,7 +303,7 @@ func (o *ocrEndpointV2) SendTo(payload []byte, to commontypes.OracleID) { return } - o.streams[to].SendMessage(payload) + o.streams[to].Send(ragep2p.OutboundBinaryMessagePlain{payload}) } func (o *ocrEndpointV2) sendToSelf(payload []byte) { @@ -322,7 +326,6 @@ func (o *ocrEndpointV2) Broadcast(payload []byte) { var subs subprocesses.Subprocesses defer subs.Wait() for oracleID := range o.peerMapping { - oracleID := oracleID subs.Go(func() { o.SendTo(payload, oracleID) }) diff --git a/networking/ocr_endpoint_v3.go b/networking/ocr_endpoint_v3.go new file mode 100644 index 0000000..05f58e0 --- /dev/null +++ b/networking/ocr_endpoint_v3.go @@ -0,0 +1,441 @@ +package networking + +import ( + "errors" + "fmt" + "io" + "sync" + + "github.com/smartcontractkit/libocr/commontypes" + 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 + + // recv is exposed to clients of this network endpoint + recv chan ocr2types.InboundBinaryMessageWithSender + + logger loghelper.LoggerWithContext +} + +type priorityStreamGroup struct { + Low *ragep2p.Stream2 + Default *ragep2p.Stream2 +} + +//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{}, + 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 + } + + // Initialize the underlying streams, one stream per priority level. + lowPriorityStream, err := o.host.NewStream2( + pid, + streamNameFromConfigDigestAndPriority(o.configDigest, ragep2p.StreamPriorityLow), + ragep2p.StreamPriorityLow, + o.lowPriorityConfig.OverrideOutgoingMessageBufferSize, + o.lowPriorityConfig.OverrideIncomingMessageBufferSize, + o.lowPriorityConfig.MaxMessageLength, + ragep2p.TokenBucketParams{ + o.lowPriorityConfig.MessagesRatePerOracle, + uint32(o.lowPriorityConfig.MessagesCapacityPerOracle), + }, + ragep2p.TokenBucketParams{ + o.lowPriorityConfig.BytesRatePerOracle, + uint32(o.lowPriorityConfig.BytesCapacityPerOracle), + }, + ) + 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.NewStream2( + pid, + streamNameFromConfigDigestAndPriority(o.configDigest, ragep2p.StreamPriorityDefault), + ragep2p.StreamPriorityDefault, + o.defaultPriorityConfig.OverrideOutgoingMessageBufferSize, + o.defaultPriorityConfig.OverrideIncomingMessageBufferSize, + o.defaultPriorityConfig.MaxMessageLength, + ragep2p.TokenBucketParams{ + o.defaultPriorityConfig.MessagesRatePerOracle, + uint32(o.defaultPriorityConfig.MessagesCapacityPerOracle), + }, + ragep2p.TokenBucketParams{ + o.defaultPriorityConfig.BytesRatePerOracle, + uint32(o.defaultPriorityConfig.BytesCapacityPerOracle), + }, + ) + 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.Receive() + chRecv2 := o.streams[oid].Low.Receive() + for { + select { + case msg := <-chRecv1: + select { + case o.recv <- ocr2types.InboundBinaryMessageWithSender{o.translateInboundMessage(msg, ocr2types.BinaryMessagePriorityDefault), oid}: + continue + case <-o.chClose: + return + } + case msg := <-chRecv2: + select { + case o.recv <- ocr2types.InboundBinaryMessageWithSender{o.translateInboundMessage(msg, ocr2types.BinaryMessagePriorityLow), oid}: + continue + case <-o.chClose: + return + } + case <-o.chClose: + return + } + } +} + +func (o *ocrEndpointV3) translateInboundMessage(inMsg ragep2p.InboundBinaryMessage, priority ocr2types.BinaryMessageOutboundPriority) ocr2types.InboundBinaryMessage { + switch msg := inMsg.(type) { + case ragep2p.InboundBinaryMessagePlain: + return ocr2types.InboundBinaryMessagePlain{msg.Payload, priority} + + case ragep2p.InboundBinaryMessageRequest: + return ocr2types.InboundBinaryMessageRequest{ + ocrEndpointV3RequestHandle{priority, msg.RequestHandle}, + msg.Payload, + priority, + } + + case ragep2p.InboundBinaryMessageResponse: + return ocr2types.InboundBinaryMessageResponse{msg.Payload, priority} + } + panic("unknown type of ragep2p.InboundBinaryMessage") +} + +func (o *ocrEndpointV3) translateOutboundMessage(outMsg ocr2types.OutboundBinaryMessage) ( + ragemsg ragep2p.OutboundBinaryMessage, + priority ocr2types.BinaryMessageOutboundPriority, +) { + switch msg := outMsg.(type) { + case ocr2types.OutboundBinaryMessagePlain: + ragemsg = ragep2p.OutboundBinaryMessagePlain{msg.Payload} + priority = msg.Priority + + case ocr2types.OutboundBinaryMessageRequest: + var rageresponsepolicy ragep2p.ResponsePolicy + switch responsepolicy := msg.ResponsePolicy.(type) { + case ocr2types.SingleUseSizedLimitedResponsePolicy: + rageresponsepolicy = &ragep2p.SingleUseSizedLimitedResponsePolicy{ + responsepolicy.MaxSize, + responsepolicy.ExpiryTimestamp, + } + } + ragemsg = ragep2p.OutboundBinaryMessageRequest{rageresponsepolicy, msg.Payload} + priority = msg.Priority + + case ocr2types.OutboundBinaryMessageResponse: + requestHandle, ok := ocr2types.MustGetOutboundBinaryMessageResponseRequestHandle(msg).(ocrEndpointV3RequestHandle) + if !ok { + o.logger.Error( + "dropping OutboundBinaryMessageResponse with requestHandle of unknown type", + commontypes.LogFields{}, + ) + return + } + ragemsg = requestHandle.rageRequestHandle.MakeResponse(msg.Payload) + priority = msg.Priority + + default: + panic("unknown type of commontypes.OutboundBinaryMessage") + } + + return ragemsg, priority +} + +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 { + 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)) + } + 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 := o.translateOutboundMessage(msg) + switch priority { + case ocr2types.BinaryMessagePriorityDefault: + o.streams[to].Default.Send(ragemsg) + case ocr2types.BinaryMessagePriorityLow: + o.streams[to].Low.Send(ragemsg) + } +} + +type ocrEndpointV3RequestHandle struct { + priority ocr2types.BinaryMessageOutboundPriority + rageRequestHandle ragep2p.RequestHandle +} + +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 ragep2p.StreamPriority) string { + switch priority { + case ragep2p.StreamPriorityLow: + return fmt.Sprintf("ocr/%s/priority=low", cd) + case ragep2p.StreamPriorityDefault: + return fmt.Sprintf("ocr/%s", cd) + } + panic("case implementation for ragep2p.StreamPriority missing") +} diff --git a/networking/peer_group.go b/networking/peer_group.go index db04536..5573e39 100644 --- a/networking/peer_group.go +++ b/networking/peer_group.go @@ -47,6 +47,7 @@ type Stream interface { Close() error } +//nolint:staticcheck // SA1019 Stream is deprecated, to be upgraded in a new PR. var _ Stream = &ragep2p.Stream{} //sumtype:decl @@ -147,6 +148,7 @@ type peerGroup struct { // managedStream is a wrapper around ragep2p.Stream that removes the stream from // peerGroup upon Close. type managedStream struct { + //nolint:staticcheck // SA1019 Stream is deprecated, to be upgraded in a new PR. stream *ragep2p.Stream onClose func() } @@ -187,6 +189,7 @@ func (f *peerGroup) NewStream(remotePeerID string, newStreamArgs NewStreamArgs) return nil, fmt.Errorf("peer ID %s is not in the set of peer IDs registered with this peer group", other) } + //nolint:staticcheck // SA1019 f.host.NewStream is deprecated stream, err := f.host.NewStream( other, args.StreamName, @@ -228,6 +231,7 @@ func (f *peerGroup) Close() error { // defensive continue } + //nolint:staticcheck // SA1019 Stream is deprecated, to be upgraded in a new PR. stream, ok := e.Value.(*ragep2p.Stream) if !ok { // defensive diff --git a/networking/peer_v2.go b/networking/peer_v2.go index e124b92..6688e9e 100644 --- a/networking/peer_v2.go +++ b/networking/peer_v2.go @@ -243,6 +243,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, @@ -279,6 +318,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/networking/ragedisco/ragep2p_discoverer.go b/networking/ragedisco/ragep2p_discoverer.go index 4e0493c..b8b1c35 100644 --- a/networking/ragedisco/ragep2p_discoverer.go +++ b/networking/ragedisco/ragep2p_discoverer.go @@ -41,7 +41,7 @@ type Ragep2pDiscoverer struct { state ragep2pDiscovererState streamsMu sync.Mutex - streams map[ragetypes.PeerID]*ragep2p.Stream + streams map[ragetypes.PeerID]*ragep2p.Stream2 chIncomingMessages chan incomingMessage chOutgoingMessages chan outgoingMessage @@ -70,7 +70,7 @@ func NewRagep2pDiscoverer( sync.Mutex{}, ragep2pDiscovererUnstarted, sync.Mutex{}, - make(map[ragetypes.PeerID]*ragep2p.Stream), + make(map[ragetypes.PeerID]*ragep2p.Stream2), make(chan incomingMessage), make(chan outgoingMessage), make(chan connectivityMsg), @@ -157,9 +157,10 @@ func (r *Ragep2pDiscoverer) connectivityLoop() { messagesLimit.Rate * maxMessageLength, messagesLimit.Capacity * maxMessageLength, } - s, err := r.host.NewStream( + s, err := r.host.NewStream2( c.peerID, "ragedisco/v1", + ragep2p.StreamPriorityDefault, bufferSize, bufferSize, maxMessageLength, @@ -178,11 +179,16 @@ func (r *Ragep2pDiscoverer) connectivityLoop() { chDone := r.ctx.Done() for { select { - case m, ok := <-s.ReceiveMessages(): + case m, ok := <-s.Receive(): if !ok { // stream Close() will signal us when it's time to go return } - w, err := fromProtoWrappedBytes(m) + plainMsg, ok := m.(ragep2p.InboundBinaryMessagePlain) + if !ok { + logger.Warn("InboundBinaryMessage is not InboundBinaryMessagePlain", reason(err)) + break + } + w, err := fromProtoWrappedBytes(plainMsg.Payload) if err != nil { logger.Warn("Failed to unwrap incoming message", reason(err)) break @@ -234,7 +240,7 @@ func (r *Ragep2pDiscoverer) writeLoop() { r.logger.Warn("Failed to convert message to bytes", commontypes.LogFields{"message": m.payload}) break } - s.SendMessage(bs) + s.Send(ragep2p.OutboundBinaryMessagePlain{bs}) case <-r.ctx.Done(): return } diff --git a/networking/rageping/service.go b/networking/rageping/service.go index 27e0945..b4a3b62 100644 --- a/networking/rageping/service.go +++ b/networking/rageping/service.go @@ -61,7 +61,7 @@ type latencyMetricsPeerState struct { metrics *latencyMetrics // Main stream used for sending/receiving PING/PONG messages. - stream *ragep2p.Stream + stream *ragep2p.Stream2 // A reference counter for the number of times this particular peer has been registered. Only a single state is // kept per peer (and config). Registering the same peer multiple times does not create a new ping/pong protocol @@ -251,15 +251,16 @@ func (sg *latencyMetricsServiceGroup) Close() { } } -func (s *latencyMetricsService) initStream(peerID ragetypes.PeerID) (*ragep2p.Stream, error) { +func (s *latencyMetricsService) initStream(peerID ragetypes.PeerID) (*ragep2p.Stream2, error) { // Get a unique stream name for each configuration. streamName := fmt.Sprintf( "ping-pong-(%v|%v|%v|%v)", s.config.PingSize, s.config.MinPeriod, s.config.MaxPeriod, s.config.Timeout, ) - return s.host.NewStream( + return s.host.NewStream2( peerID, streamName, + ragep2p.StreamPriorityDefault, s.streamConfig.outgoingBufferSize, s.streamConfig.incomingBufferSize, s.streamConfig.maxMessageLength, @@ -377,12 +378,17 @@ func (s *latencyMetricsService) run(remotePeerID ragetypes.PeerID, peerState *la ticker.Reset(s.getNextDelay()) } - case msg, ok := <-stream.ReceiveMessages(): + case msg, ok := <-stream.Receive(): if !ok { // Stream was closed, so we are shutting down. return } + plainMsg, ok := msg.(ragep2p.InboundBinaryMessagePlain) + if !ok { + return + } + // Some message was received from the remote peer. // - For an incoming (valid) PING message: respond with the corresponding PONG message. // - For an incoming (valid) PONG message: @@ -390,14 +396,15 @@ func (s *latencyMetricsService) run(remotePeerID ragetypes.PeerID, peerState *la // 2. Measure latency and update metrics. // 3. Reschedule the ticker for sending a new PING message. // - Log invalid messages. - if len(msg) >= 4 { - msgType := binary.BigEndian.Uint32(msg) - if msgType == msgTypePing && len(msg) == s.config.PingSize { - s.processIncomingPingMessage(msg, remotePeerID, stream, metrics) + payload := plainMsg.Payload + if len(payload) >= 4 { + msgType := binary.BigEndian.Uint32(payload) + if msgType == msgTypePing && len(payload) == s.config.PingSize { + s.processIncomingPingMessage(payload, remotePeerID, stream, metrics) break } - if msgType == msgTypePong && len(msg) == pongSize { - if s.processIncomingPongMessage(msg, expectedPongMsg, lastPingSentAt, remotePeerID, metrics) { + if msgType == msgTypePong && len(payload) == pongSize { + if s.processIncomingPongMessage(payload, expectedPongMsg, lastPingSentAt, remotePeerID, metrics) { expectedPongMsg = nil ticker.Reset(s.getNextDelay()) } @@ -407,14 +414,14 @@ func (s *latencyMetricsService) run(remotePeerID ragetypes.PeerID, peerState *la // Truncate long messages before logging them. Using minPingSize here is just some suitable value, // other small values are equally good. - msgPrefix := msg - if len(msg) > minPingSize { - msgPrefix = msg[:minPingSize] + msgPrefix := payload + if len(payload) > minPingSize { + msgPrefix = payload[:minPingSize] } s.logger.Warn( "invalid message received", - commontypes.LogFields{"remotePeerID": remotePeerID, "msgPrefix": msgPrefix, "msgLen": len(msg)}, + commontypes.LogFields{"remotePeerID": remotePeerID, "msgPrefix": msgPrefix, "msgLen": len(payload)}, ) metrics.invalidMessagesReceivedTotal.Inc() } @@ -422,7 +429,7 @@ func (s *latencyMetricsService) run(remotePeerID ragetypes.PeerID, peerState *la } func (s *latencyMetricsService) sendPing( - remotePeerID ragetypes.PeerID, stream *ragep2p.Stream, metrics *latencyMetrics, + remotePeerID ragetypes.PeerID, stream *ragep2p.Stream2, metrics *latencyMetrics, ) (lastPingSentAt time.Time, expectedPongMsg []byte) { // Generate a new random PING message to be sent to the remote peer. pingMsg, err := s.preparePingMessage() @@ -439,7 +446,7 @@ func (s *latencyMetricsService) sendPing( // Actually send the PING message and keep track of the current time to compute the latency when we // receive corresponding PONG message. lastPingSentAt = time.Now() - stream.SendMessage(pingMsg) + stream.Send(ragep2p.OutboundBinaryMessagePlain{pingMsg}) metrics.sentRequestsTotal.Inc() s.logger.Trace( "sending PING", @@ -463,7 +470,7 @@ func (s *latencyMetricsService) processTimedOutPing(remotePeerID ragetypes.PeerI func (s *latencyMetricsService) processIncomingPingMessage( pingMsg []byte, remotePeerID ragetypes.PeerID, - stream *ragep2p.Stream, + stream *ragep2p.Stream2, metrics *latencyMetrics, ) { // Some valid PING message was received from the remote peer. @@ -482,7 +489,7 @@ func (s *latencyMetricsService) processIncomingPingMessage( } s.logger.Trace("sending PONG", commontypes.LogFields{"remotePeerID": remotePeerID, "msg": pongMsg}) - stream.SendMessage(pongMsg) + stream.Send(ragep2p.OutboundBinaryMessagePlain{pongMsg}) } func (s *latencyMetricsService) processIncomingPongMessage( diff --git a/offchainreporting2plus/chains/evmutil/evm.go b/offchainreporting2plus/chains/evmutil/evm.go index 1de028f..9b16993 100644 --- a/offchainreporting2plus/chains/evmutil/evm.go +++ b/offchainreporting2plus/chains/evmutil/evm.go @@ -35,7 +35,6 @@ func ContractConfigFromConfigSetEvent(changed ocr2aggregator.OCR2AggregatorConfi } signers := []types.OnchainPublicKey{} for _, addr := range changed.Signers { - addr := addr signers = append(signers, types.OnchainPublicKey(addr[:])) } return types.ContractConfig{ 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 b7cc9ce..d3a6ac8 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 72% rename from offchainreporting2plus/internal/ocr3/protocol/ringbuffer/ringbuffer.go rename to offchainreporting2plus/internal/common/ringbuffer/ringbuffer.go index a4a6466..d5f5791 100644 --- a/offchainreporting2plus/internal/ocr3/protocol/ringbuffer/ringbuffer.go +++ b/offchainreporting2plus/internal/common/ringbuffer/ringbuffer.go @@ -2,6 +2,16 @@ package ringbuffer import "fmt" +// TODO +// +// A new generic ring buffer implementation, which can gracefully handle Pop(), Peek() on empty buffers or +// Push() on full buffers is available in lib/internal/ringbuffer. +// +// Usages of this ringbuffer should be updated to the new implementation. +// +// CAUTION: When updating, first rename all Push() usages if this implementation to PushEvict() to be logically +// compatible with the new implementation! + // RingBuffer implements a fixed capacity ringbuffer for items of type // T type RingBuffer[T any] struct { 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 f0be88a..dc01f94 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/config/ocr2config/serialize.go b/offchainreporting2plus/internal/config/ocr2config/serialize.go index 8c560d0..1e62549 100644 --- a/offchainreporting2plus/internal/config/ocr2config/serialize.go +++ b/offchainreporting2plus/internal/config/ocr2config/serialize.go @@ -164,7 +164,6 @@ func enprotoOffchainConfig(o offchainConfig) OffchainConfigProto { } offchainPublicKeys := make([][]byte, 0, len(o.OffchainPublicKeys)) for _, k := range o.OffchainPublicKeys { - k := k // have to copy or we append the same key over and over offchainPublicKeys = append(offchainPublicKeys, k[:]) } maxDurationInitializationNanoseconds := (*uint64)(nil) @@ -202,7 +201,6 @@ func enprotoOffchainConfig(o offchainConfig) OffchainConfigProto { func enprotoSharedSecretEncryptions(e config.SharedSecretEncryptions) SharedSecretEncryptionsProto { encs := make([][]byte, 0, len(e.Encryptions)) for _, enc := range e.Encryptions { - enc := enc encs = append(encs, enc[:]) } return SharedSecretEncryptionsProto{ diff --git a/offchainreporting2plus/internal/config/ocr3config/serialize.go b/offchainreporting2plus/internal/config/ocr3config/serialize.go index cb041a4..e57c368 100644 --- a/offchainreporting2plus/internal/config/ocr3config/serialize.go +++ b/offchainreporting2plus/internal/config/ocr3config/serialize.go @@ -166,7 +166,6 @@ func enprotoOffchainConfig(o offchainConfig) OffchainConfigProto { } offchainPublicKeys := make([][]byte, 0, len(o.OffchainPublicKeys)) for _, k := range o.OffchainPublicKeys { - k := k // have to copy or we append the same key over and over offchainPublicKeys = append(offchainPublicKeys, k[:]) } maxDurationInitializationNanoseconds := (*uint64)(nil) @@ -205,7 +204,6 @@ func enprotoOffchainConfig(o offchainConfig) OffchainConfigProto { func enprotoSharedSecretEncryptions(e config.SharedSecretEncryptions) SharedSecretEncryptionsProto { encs := make([][]byte, 0, len(e.Encryptions)) for _, enc := range e.Encryptions { - enc := enc encs = append(encs, enc[:]) } return SharedSecretEncryptionsProto{ 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 0000000..aa73582 --- /dev/null +++ b/offchainreporting2plus/internal/managed/limits/ocr3_1_limits.go @@ -0,0 +1,205 @@ +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 + maxLenMsgBlockSyncRequest int + maxLenMsgBlockSync int + maxLenMsgBlockSyncSummary 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 + + maxStateTransitionInputs := add(pluginLimits.MaxQueryLength, mul(pluginLimits.MaxObservationLength, cfg.N()), overhead) + maxLenCertifiedPrepareOrCommit := add(mul(ed25519.SignatureSize+sigOverhead, cfg.ByzQuorumSize()), maxStateTransitionInputs, overhead) + maxLenCertifiedCommittedReports := add(mul(ed25519.SignatureSize+sigOverhead, cfg.ByzQuorumSize()), pluginLimits.MaxReportsPlusPrecursorLength, overhead) + maxStateTransitionBlock := add(maxStateTransitionInputs, overhead) + maxAttestedStateTransitionBlock := add(mul(ed25519.SignatureSize+sigOverhead, cfg.ByzQuorumSize()), maxStateTransitionBlock, 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) + maxLenMsgBlockSyncRequest := overhead + maxLenMsgBlockSync := add(protocol.MaxBlocksSent*maxAttestedStateTransitionBlock, overhead) + maxLenMsgBlockSyncSummary := overhead + + maxDefaultPriorityMessageSize := max( + maxLenMsgNewEpoch, + maxLenMsgEpochStartRequest, + maxLenMsgEpochStart, + maxLenMsgRoundStart, + maxLenMsgObservation, + maxLenMsgProposal, + maxLenMsgPrepare, + maxLenMsgCommit, + maxLenMsgReportSignatures, + maxLenMsgCertifiedCommitRequest, + maxLenMsgCertifiedCommit, + maxLenMsgBlockSyncSummary, + ) + + maxLowPriorityMessageSize := max( + maxLenMsgBlockSyncRequest, + maxLenMsgBlockSync, + ) + + 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.0*float64(time.Second)/float64(protocol.DeltaBlockSyncHeartbeat)) * 1.2 + + lowPriorityMessagesRate := + 1.0 * float64(time.Second) / float64(protocol.DeltaMinBlockSyncRequest) * 1.2 + + defaultPriorityMessagesCapacity := mul(13, 3) + lowPriorityMessagesCapacity := mul(1, 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.DeltaBlockSyncHeartbeat)*float64(maxLenMsgBlockSyncSummary) + + lowPriorityBytesRate := float64(time.Second) / float64(protocol.DeltaMinBlockSyncRequest) * float64(maxLenMsgBlockSyncRequest) + + defaultPriorityBytesCapacity := mul(add( + maxLenMsgNewEpoch, + maxLenMsgNewEpoch, + maxLenMsgEpochStartRequest, + maxLenMsgEpochStart, + maxLenMsgRoundStart, + maxLenMsgObservation, + maxLenMsgProposal, + maxLenMsgPrepare, + maxLenMsgCommit, + maxLenMsgReportSignatures, + maxLenMsgCertifiedCommitRequest, + maxLenMsgCertifiedCommit, + maxLenMsgBlockSyncSummary, + ), 3) + + lowPriorityBytesCapacity := mul(add( + 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, + maxLenMsgBlockSyncRequest, + maxLenMsgBlockSync, + maxLenMsgBlockSyncSummary, + }, + 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 6949705..df471ff 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 0000000..c2e1a76 --- /dev/null +++ b/offchainreporting2plus/internal/managed/managed_ocr3_1_oracle.go @@ -0,0 +1,276 @@ +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, + kvStoreFactory ocr3_1types.KeyValueStoreFactory, + 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, + }) + + 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, + }) + + 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 + } + + 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, err := shim.NewOCR3_1SerializingEndpoint[RI]( + chTelemetrySend, + sharedConfig.ConfigDigest, + binNetEndpoint, + maxSigLen, + childLogger, + registerer, + reportingPluginInfo.Limits, + sharedConfig.N(), + sharedConfig.F, + ) + 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()", + ) + + kvStore, err := kvStoreFactory.NewKeyValueStore(sharedConfig.ConfigDigest) + if err != nil { + return fmt.Errorf("ManagedOCR3_1Oracle: failed to instantiate a KeyValueStore"), false + } + defer loghelper.CloseLogError( + kvStore, + logger, + "ManagedOCR3_1Oracle: error during kvStore.Close()", + ) + + protocol.RunOracle[RI]( + ctx, + sharedConfig, + contractTransmitter, + &shim.SerializingOCR3_1Database{database}, + oid, + kvStore, + 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)) + } + return err +} diff --git a/offchainreporting2plus/internal/managed/managed_ocr3_oracle.go b/offchainreporting2plus/internal/managed/managed_ocr3_oracle.go index 366c18c..e338e8c 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 2f3ba8a..1f41488 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 b54b362..d050b84 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" @@ -378,7 +379,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 24c198a..b6c21b5 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 6ef1541..7d85359 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 f0a760c..b767cff 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" @@ -423,8 +424,6 @@ func (repatt *reportAttestationState[RI]) verifySignatures(publicKey types.Oncha allValid := true for k := 0; k < n; k++ { - k := k - go func() { defer wg.Done() for i := k; i < len(reportsPlus); i += n { @@ -487,7 +486,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 44522c4..2e6d2e0 100644 --- a/offchainreporting2plus/internal/ocr3/protocol/transmission.go +++ b/offchainreporting2plus/internal/ocr3/protocol/transmission.go @@ -5,13 +5,14 @@ import ( "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/internal/ocr3/scheduler" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/libocr/permutation" @@ -115,7 +116,7 @@ func (t *transmissionState[RI]) backgroundEventAttestedReport(ctx context.Contex delay = *delayMaybe } - shouldAccept, ok := callPlugin[bool]( + shouldAccept, ok := common.CallPlugin[bool]( ctx, t.logger, commontypes.LogFields{ @@ -160,7 +161,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/protocol/db.go b/offchainreporting2plus/internal/ocr3_1/protocol/db.go new file mode 100644 index 0000000..5e52734 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/db.go @@ -0,0 +1,31 @@ +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 0000000..beeaf3a --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/event.go @@ -0,0 +1,286 @@ +package protocol + +import ( + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" + "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 EventKVTransactionRequest[RI any] struct { + RoundCtx ocr3_1types.RoundContext + Query types.Query + Asos []AttributedSignedObservation + Prepared bool + StateTransitionOutputDigest StateTransitionOutputDigest + ReportsPlusPrecursorDigest ReportsPlusPrecursorDigest + CommitQC []AttributedCommitSignature +} + +var _ EventToStatePersistence[struct{}] = EventKVTransactionRequest[struct{}]{} // implements EventToStatePersistence + +func (ev EventKVTransactionRequest[RI]) processStatePersistence(state *statePersistenceState[RI]) { + state.eventKVTransactionRequest(ev) +} + +type EventProduceStateTransition[RI any] struct { + RoundCtx ocr3_1types.RoundContext + Txn kvStoreTxn + Query types.Query + Asos []AttributedSignedObservation + Prepared bool + StateTransitionOutputDigest StateTransitionOutputDigest + ReportsPlusPrecursorDigest ReportsPlusPrecursorDigest + CommitQC []AttributedCommitSignature +} + +var _ EventToOutcomeGeneration[struct{}] = EventProduceStateTransition[struct{}]{} + +func (ev EventProduceStateTransition[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI]) { + outgen.eventProduceStateTransition(ev) +} + +type EventComputedStateTransition[RI any] struct { + SeqNr uint64 + Epoch uint64 +} + +var _ EventToStatePersistence[struct{}] = EventComputedStateTransition[struct{}]{} // implements EventToStatePersistence + +func (ev EventComputedStateTransition[RI]) processStatePersistence(state *statePersistenceState[RI]) { + state.eventComputedStateTransition(ev) +} + +type EventToReportAttestation[RI any] interface { + processReportAttestation(repatt *reportAttestationState[RI]) +} + +type EventToStatePersistence[RI any] interface { + processStatePersistence(state *statePersistenceState[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 EventAttestedStateTransitionBlock[RI any] struct { + AttestedStateTransitionBlock AttestedStateTransitionBlock +} + +var _ EventToStatePersistence[struct{}] = EventAttestedStateTransitionBlock[struct{}]{} // implements EventToStatePersistence + +func (ev EventAttestedStateTransitionBlock[RI]) processStatePersistence(state *statePersistenceState[RI]) { + state.eventAttestedStateTransitionBlock(ev) +} + +type EventAcknowledgedCommittedKVTransaction[RI any] struct { + SeqNr uint64 +} + +var _ EventToStatePersistence[struct{}] = EventAcknowledgedCommittedKVTransaction[struct{}]{} // implements EventToStatePersistence + +func (ev EventAcknowledgedCommittedKVTransaction[RI]) processStatePersistence(state *statePersistenceState[RI]) { + state.eventAcknowledgedCommittedKVTransaction(ev) +} + +type EventAcknowledgedComputedStateTransition[RI any] struct { + SeqNr uint64 +} + +var _ EventToOutcomeGeneration[struct{}] = EventAcknowledgedComputedStateTransition[struct{}]{} // implements EventToOutcomeGeneration + +func (ev EventAcknowledgedComputedStateTransition[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI]) { + outgen.eventAcknowledgedComputedStateTransition(ev) +} + +type EventCommittedKVTransaction[RI any] struct { + SeqNr uint64 +} + +var _ EventToOutcomeGeneration[struct{}] = EventCommittedKVTransaction[struct{}]{} // implements EventToOutcomeGeneration + +func (ev EventCommittedKVTransaction[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI]) { + outgen.eventCommittedKVTransaction(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 EventReplayVerifiedStateTransition[RI any] struct { + AttestedStateTransitionBlock AttestedStateTransitionBlock +} + +var _ EventToOutcomeGeneration[struct{}] = EventReplayVerifiedStateTransition[struct{}]{} // implements EventToOutcomeGeneration + +func (ev EventReplayVerifiedStateTransition[RI]) processOutcomeGeneration(outgen *outcomeGenerationState[RI]) { + outgen.eventReplayVerifiedStateTransition(ev) +} + +type EventDiscardKVTransaction[RI any] struct { + SeqNr uint64 +} + +var _ EventToStatePersistence[struct{}] = EventDiscardKVTransaction[struct{}]{} // implements EventToStatePersistence + +func (ev EventDiscardKVTransaction[RI]) processStatePersistence(state *statePersistenceState[RI]) { + state.eventDiscardKVTransaction(ev) +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/message.go b/offchainreporting2plus/internal/ocr3_1/protocol/message.go new file mode 100644 index 0000000..efb32d2 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/message.go @@ -0,0 +1,759 @@ +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 + + CheckPriority(priority types.BinaryMessageOutboundPriority) bool + + CheckMessageType(inboundMessageType MessageType) bool // check in here with some switch if it is the expected type + + GetOutboundBinaryMessage(sMsg []byte) types.OutboundBinaryMessage + + // 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) +} + +type SerializableRequestMessage[RI any] interface { + Message[RI] + NewInboundRequestMessage(handle types.RequestHandle) Message[RI] +} + +type SerializableResponseMessage[RI any] interface { + Message[RI] + NewInboundResponseMessage() Message[RI] +} + +type RequestMessage[RI any] interface { + Message[RI] + GetSerializableRequestMessage() Message[RI] + GetRequestHandle() types.RequestHandle +} + +type ResponseMessage[RI any] interface { + Message[RI] + GetSerializableResponseMessage() Message[RI] +} + +type MessageType int + +const ( + MessageTypePlain = 1 + MessageTypeRequest = 2 + MessageTypeResponse = 3 +) + +// 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 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]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return priority == types.BinaryMessagePriorityDefault +} + +func (msg MessageNewEpochWish[RI]) CheckMessageType(messageType MessageType) bool { + return messageType == MessageTypePlain +} + +func (msg MessageNewEpochWish[RI]) GetOutboundBinaryMessage(sMsg []byte) types.OutboundBinaryMessage { + return types.OutboundBinaryMessagePlain{ + sMsg, + types.BinaryMessagePriorityDefault, + } +} + +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]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return priority == types.BinaryMessagePriorityDefault +} + +func (msg MessageEpochStartRequest[RI]) CheckMessageType(inboundMessageType MessageType) bool { + return inboundMessageType == MessageTypePlain +} + +func (msg MessageEpochStartRequest[RI]) GetOutboundBinaryMessage(sMsg []byte) types.OutboundBinaryMessage { + return types.OutboundBinaryMessagePlain{ + sMsg, + types.BinaryMessagePriorityDefault, + } +} + +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]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return priority == types.BinaryMessagePriorityDefault +} + +func (msg MessageEpochStart[RI]) CheckMessageType(inboundMessageType MessageType) bool { + return inboundMessageType == MessageTypePlain +} + +func (msg MessageEpochStart[RI]) GetOutboundBinaryMessage(sMsg []byte) types.OutboundBinaryMessage { + return types.OutboundBinaryMessagePlain{ + sMsg, + types.BinaryMessagePriorityDefault, + } +} + +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]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return priority == types.BinaryMessagePriorityDefault +} + +func (msg MessageRoundStart[RI]) CheckMessageType(inboundMessageType MessageType) bool { + return inboundMessageType == MessageTypePlain +} + +func (msg MessageRoundStart[RI]) GetOutboundBinaryMessage(sMsg []byte) types.OutboundBinaryMessage { + return types.OutboundBinaryMessagePlain{ + sMsg, + types.BinaryMessagePriorityDefault, + } +} + +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]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return priority == types.BinaryMessagePriorityDefault +} + +func (msg MessageObservation[RI]) CheckMessageType(inboundMessageType MessageType) bool { + return inboundMessageType == MessageTypePlain +} + +func (msg MessageObservation[RI]) GetOutboundBinaryMessage(sMsg []byte) types.OutboundBinaryMessage { + return types.OutboundBinaryMessagePlain{ + sMsg, + types.BinaryMessagePriorityDefault, + } +} + +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]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return priority == types.BinaryMessagePriorityDefault +} + +func (msg MessageProposal[RI]) CheckMessageType(inboundMessageType MessageType) bool { + return inboundMessageType == MessageTypePlain +} + +func (msg MessageProposal[RI]) GetOutboundBinaryMessage(sMsg []byte) types.OutboundBinaryMessage { + return types.OutboundBinaryMessagePlain{ + sMsg, + types.BinaryMessagePriorityDefault, + } +} + +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]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return priority == types.BinaryMessagePriorityDefault +} + +func (msg MessagePrepare[RI]) CheckMessageType(inboundMessageType MessageType) bool { + return inboundMessageType == MessageTypePlain +} + +func (msg MessagePrepare[RI]) GetOutboundBinaryMessage(sMsg []byte) types.OutboundBinaryMessage { + return types.OutboundBinaryMessagePlain{ + sMsg, + types.BinaryMessagePriorityDefault, + } +} + +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]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return priority == types.BinaryMessagePriorityDefault +} + +func (msg MessageCommit[RI]) CheckMessageType(inboundMessageType MessageType) bool { + return inboundMessageType == MessageTypePlain +} + +func (msg MessageCommit[RI]) GetOutboundBinaryMessage(sMsg []byte) types.OutboundBinaryMessage { + return types.OutboundBinaryMessagePlain{ + sMsg, + types.BinaryMessagePriorityDefault, + } +} + +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]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return priority == types.BinaryMessagePriorityDefault +} + +func (msg MessageReportSignatures[RI]) CheckMessageType(inboundMessageType MessageType) bool { + return inboundMessageType == MessageTypePlain +} + +func (msg MessageReportSignatures[RI]) GetOutboundBinaryMessage(sMsg []byte) types.OutboundBinaryMessage { + return types.OutboundBinaryMessagePlain{ + sMsg, + types.BinaryMessagePriorityDefault, + } +} + +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]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return priority == types.BinaryMessagePriorityDefault +} + +func (msg MessageCertifiedCommitRequest[RI]) CheckMessageType(inboundMessageType MessageType) bool { + return inboundMessageType == MessageTypePlain +} + +func (msg MessageCertifiedCommitRequest[RI]) GetOutboundBinaryMessage(sMsg []byte) types.OutboundBinaryMessage { + return types.OutboundBinaryMessagePlain{ + sMsg, + types.BinaryMessagePriorityDefault, + } +} + +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]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return priority == types.BinaryMessagePriorityDefault +} + +func (msg MessageCertifiedCommit[RI]) CheckMessageType(inboundMessageType MessageType) bool { + return inboundMessageType == MessageTypePlain +} + +func (msg MessageCertifiedCommit[RI]) GetOutboundBinaryMessage(sMsg []byte) types.OutboundBinaryMessage { + return types.OutboundBinaryMessagePlain{ + sMsg, + types.BinaryMessagePriorityDefault, + } +} + +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 MessageBlockSyncRequestWrapper[RI any] struct { + SerializableMessage Message[RI] + RequestHandle types.RequestHandle + ResponsePolicy types.ResponsePolicy +} + +type MessageBlockSyncRequest[RI any] struct { + HighestCommittedSeqNr uint64 + Nonce uint64 +} + +var _ MessageToStatePersistence[struct{}] = (*MessageBlockSyncRequestWrapper[struct{}])(nil) +var _ Message[struct{}] = (*MessageBlockSyncRequestWrapper[struct{}])(nil) +var _ RequestMessage[struct{}] = (*MessageBlockSyncRequestWrapper[struct{}])(nil) + +var _ SerializableRequestMessage[struct{}] = (*MessageBlockSyncRequest[struct{}])(nil) + +func (msg MessageBlockSyncRequest[RI]) CheckSize(n int, f int, _ ocr3_1types.ReportingPluginLimits, _ int) bool { + return true +} + +func (msg MessageBlockSyncRequest[RI]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return priority == types.BinaryMessagePriorityLow +} + +func (msg MessageBlockSyncRequest[RI]) CheckMessageType(messageType MessageType) bool { + return messageType == MessageTypeRequest +} + +func (msg MessageBlockSyncRequest[RI]) GetOutboundBinaryMessage([]byte) types.OutboundBinaryMessage { + panic("should have called the process method on the MessageBlockSyncRequestWrapper instead") +} + +func (msg MessageBlockSyncRequest[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + panic("should have called the process method on the MessageBlockSyncRequestWrapper instead") +} + +func (msg MessageBlockSyncRequest[RI]) NewInboundRequestMessage(handle types.RequestHandle) Message[RI] { + return MessageBlockSyncRequestWrapper[RI]{ + SerializableMessage: msg, + RequestHandle: handle, + } +} + +func (msg MessageBlockSyncRequestWrapper[RI]) CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxReportSigLen int) bool { + return msg.SerializableMessage.CheckSize(n, f, limits, maxReportSigLen) +} + +func (msg MessageBlockSyncRequestWrapper[RI]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return msg.SerializableMessage.CheckPriority(priority) +} + +func (msg MessageBlockSyncRequestWrapper[RI]) CheckMessageType(inboundMessageType MessageType) bool { + return msg.SerializableMessage.CheckMessageType(inboundMessageType) +} + +func (msg MessageBlockSyncRequestWrapper[RI]) GetOutboundBinaryMessage(sMsg []byte) types.OutboundBinaryMessage { + return types.OutboundBinaryMessageRequest{ + msg.ResponsePolicy, + sMsg, + types.BinaryMessagePriorityLow, + } +} + +func (msg MessageBlockSyncRequestWrapper[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToStatePersistence <- MessageToStatePersistenceWithSender[RI]{msg, sender} +} + +func (msg MessageBlockSyncRequestWrapper[RI]) processStatePersistence(state *statePersistenceState[RI], sender commontypes.OracleID) { + state.messageBlockSyncReq(msg, sender) +} + +func (msg MessageBlockSyncRequestWrapper[RI]) GetRequestHandle() types.RequestHandle { + return msg.RequestHandle +} + +func (msg MessageBlockSyncRequestWrapper[RI]) GetSerializableRequestMessage() Message[RI] { + return msg.SerializableMessage +} + +type MessageBlockSyncSummary[RI any] struct { + LowestPersistedSeqNr uint64 +} + +var _ MessageToStatePersistence[struct{}] = (*MessageBlockSyncSummary[struct{}])(nil) + +func (msg MessageBlockSyncSummary[RI]) CheckSize(n int, f int, _ ocr3_1types.ReportingPluginLimits, _ int) bool { + return true +} + +func (msg MessageBlockSyncSummary[RI]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return priority == types.BinaryMessagePriorityDefault +} + +func (msg MessageBlockSyncSummary[RI]) CheckMessageType(inboundMessageType MessageType) bool { + return inboundMessageType == MessageTypePlain +} + +func (msg MessageBlockSyncSummary[RI]) GetOutboundBinaryMessage(sMsg []byte) types.OutboundBinaryMessage { + return types.OutboundBinaryMessagePlain{ + sMsg, + types.BinaryMessagePriorityDefault, + } +} + +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 MessageBlockSyncWrapper[RI any] struct { + SerializableMessage Message[RI] + RequestHandle types.RequestHandle +} + +type MessageBlockSync[RI any] struct { + AttestedStateTransitionBlocks []AttestedStateTransitionBlock + Nonce uint64 +} + +var _ MessageToStatePersistence[struct{}] = (*MessageBlockSyncWrapper[struct{}])(nil) +var _ Message[struct{}] = (*MessageBlockSyncWrapper[struct{}])(nil) +var _ ResponseMessage[struct{}] = (*MessageBlockSyncWrapper[struct{}])(nil) + +var _ SerializableResponseMessage[struct{}] = (*MessageBlockSync[struct{}])(nil) + +func (msg MessageBlockSync[RI]) CheckSize(n int, f int, _ ocr3_1types.ReportingPluginLimits, _ int) bool { + return true +} + +func (msg MessageBlockSync[RI]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return priority == types.BinaryMessagePriorityLow +} + +func (msg MessageBlockSync[RI]) CheckMessageType(messageType MessageType) bool { + return messageType == MessageTypeResponse +} + +func (msg MessageBlockSync[RI]) GetOutboundBinaryMessage([]byte) types.OutboundBinaryMessage { + panic("should have called the process method on MessageBlockSyncWrapper instead") +} + +func (msg MessageBlockSync[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + panic("should have called the process method on MessageBlockSyncWrapper instead") +} + +func (msg MessageBlockSync[RI]) NewInboundResponseMessage() Message[RI] { + return MessageBlockSyncWrapper[RI]{ + SerializableMessage: msg, + } +} + +func (msg MessageBlockSyncWrapper[RI]) CheckSize(n int, f int, limits ocr3_1types.ReportingPluginLimits, maxSigLen int) bool { + return msg.SerializableMessage.CheckSize(n, f, limits, maxSigLen) +} + +func (msg MessageBlockSyncWrapper[RI]) CheckPriority(priority types.BinaryMessageOutboundPriority) bool { + return msg.SerializableMessage.CheckPriority(priority) +} + +func (msg MessageBlockSyncWrapper[RI]) CheckMessageType(messageType MessageType) bool { + return msg.SerializableMessage.CheckMessageType(messageType) +} + +func (msg MessageBlockSyncWrapper[RI]) GetOutboundBinaryMessage(sMsg []byte) types.OutboundBinaryMessage { + return types.MustMakeOutboundBinaryMessageResponse( + msg.RequestHandle, + sMsg, + types.BinaryMessagePriorityLow, + ) +} + +func (msg MessageBlockSyncWrapper[RI]) process(o *oracleState[RI], sender commontypes.OracleID) { + o.chNetToStatePersistence <- MessageToStatePersistenceWithSender[RI]{msg, sender} +} + +func (msg MessageBlockSyncWrapper[RI]) processStatePersistence(state *statePersistenceState[RI], sender commontypes.OracleID) { + state.messageBlockSync(msg, sender) +} + +func (msg MessageBlockSyncWrapper[RI]) GetSerializableResponseMessage() Message[RI] { + return msg.SerializableMessage +} diff --git a/offchainreporting2plus/internal/ocr3_1/protocol/messagebuffer.go b/offchainreporting2plus/internal/ocr3_1/protocol/messagebuffer.go new file mode 100644 index 0000000..1f41488 --- /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 0000000..4e8f978 --- /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 0000000..03f3012 --- /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 0000000..97818d8 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/oracle.go @@ -0,0 +1,414 @@ +package protocol + +import ( + "context" + "fmt" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "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/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, + + config ocr3config.SharedConfig, + contractTransmitter ocr3types.ContractTransmitter[RI], + database Database, + id commontypes.OracleID, + kvStore ocr3_1types.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, + + 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 + + config ocr3config.SharedConfig + contractTransmitter ocr3types.ContractTransmitter[RI] + database Database + id commontypes.OracleID + kvStore ocr3_1types.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] + 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 and Transmission) 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 + + chStatePersistenceToOutcomeGeneration := make(chan EventToOutcomeGeneration[RI]) + + chOutcomeGenerationToStatePersistence := make(chan EventToStatePersistence[RI]) + chReportAttestationToStatePersistence := make(chan EventToStatePersistence[RI]) + + // 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 + } + 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, + chOutcomeGenerationToStatePersistence, + chStatePersistenceToOutcomeGeneration, + o.config, + o.database, + o.id, + o.kvStore, + o.localConfig, + o.logger, + o.metricsRegisterer, + o.netEndpoint, + o.offchainKeyring, + o.reportingPlugin, + o.telemetrySender, + + cert, + highestCommittedToKVdSeqNr, + ) + }) + + 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, + chOutcomeGenerationToStatePersistence, + chReportAttestationToStatePersistence, + chStatePersistenceToOutcomeGeneration, + o.config, + o.database, + o.id, + o.kvStore, + o.logger, + o.netEndpoint, + statePersistenceState, + highestCommittedToKVdSeqNr, + ) + }) + + o.subprocesses.Go(func() { + RunTransmission( + o.childCtx, + + chReportAttestationToTransmission, + o.config, + o.contractTransmitter, + o.id, + o.localConfig, + o.logger, + o.reportingPlugin, + ) + }) + + 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 0000000..53c12c9 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/outcome_generation.go @@ -0,0 +1,528 @@ +package protocol + +import ( + "context" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common" + "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/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], + chOutcomeGenerationToStatePersistence chan<- EventToStatePersistence[RI], + chStatePersistenceToOutcomeGeneration <-chan EventToOutcomeGeneration[RI], + config ocr3config.SharedConfig, + database Database, + id commontypes.OracleID, + kvStore ocr3_1types.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, + restoredHighestCommittedToKVSeqNr uint64, +) { + + outgen := outcomeGenerationState[RI]{ + ctx: ctx, + subs: subprocesses.Subprocesses{}, + + chLocalEvent: make(chan EventToOutcomeGeneration[RI]), + chNetToOutcomeGeneration: chNetToOutcomeGeneration, + chPacemakerToOutcomeGeneration: chPacemakerToOutcomeGeneration, + chOutcomeGenerationToPacemaker: chOutcomeGenerationToPacemaker, + chOutcomeGenerationToReportAttestation: chOutcomeGenerationToReportAttestation, + chOutcomeGenerationToStatePersistence: chOutcomeGenerationToStatePersistence, + chStatePersistenceToOutcomeGeneration: chStatePersistenceToOutcomeGeneration, + 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, restoredHighestCommittedToKVSeqNr) +} + +type outcomeGenerationState[RI any] struct { + ctx context.Context + subs subprocesses.Subprocesses + + chLocalEvent chan EventToOutcomeGeneration[RI] + chNetToOutcomeGeneration <-chan MessageToOutcomeGenerationWithSender[RI] + chPacemakerToOutcomeGeneration <-chan EventToOutcomeGeneration[RI] + chReportAttestationToOutcomeGeneration <-chan EventToOutcomeGeneration[RI] + chOutcomeGenerationToPacemaker chan<- EventToPacemaker[RI] + chOutcomeGenerationToReportAttestation chan<- EventToReportAttestation[RI] + chOutcomeGenerationToStatePersistence chan<- EventToStatePersistence[RI] + chStatePersistenceToOutcomeGeneration <-chan EventToOutcomeGeneration[RI] + config ocr3config.SharedConfig + database Database + id commontypes.OracleID + kvStore ocr3_1types.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]] + + roundInfo roundInfo[RI] + + // lock + cert CertifiedPrepareOrCommit + + preparePool *pool.Pool[PrepareSignature] + commitPool *pool.Pool[CommitSignature] +} + +type roundInfo[RI any] struct { + inputs StateTransitionInputs + reportsPlusPrecursor ocr3_1types.ReportsPlusPrecursor + inputsDigest StateTransitionInputsDigest + outputDigest StateTransitionOutputDigest + reportsPlusPrecursorDigest ReportsPlusPrecursorDigest + commitQuorumCertificate []AttributedCommitSignature +} + +type sharedState struct { + e uint64 // Current epoch number + l commontypes.OracleID // Current leader number + + firstSeqNrOfEpoch uint64 + seqNr uint64 + observationQuorum *int + committedSeqNr uint64 + + kvStoreTxn *kvStoreTxn + committedToKVStoreSeqNr uint64 // The sequence number of the key-value store +} + +func (outgen *outcomeGenerationState[RI]) run(restoredCert CertifiedPrepareOrCommit, restoredCommittedToKVStoreSeqNr uint64) { + 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, + "restoredCommittedToKVStoreSeqNr": restoredCommittedToKVStoreSeqNr, + }) + + // 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, + roundInfo[RI]{}, + restoredCert, + nil, + nil, + } + + outgen.sharedState = sharedState{ + 0, + 0, + + 0, + restoredCommitedSeqNr, + nil, + restoredCommitedSeqNr, + nil, + restoredCommittedToKVStoreSeqNr, + } + + // 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 ev := <-outgen.chReportAttestationToOutcomeGeneration: + ev.processOutcomeGeneration(outgen) + case ev := <-outgen.chStatePersistenceToOutcomeGeneration: + 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]) eventReplayVerifiedStateTransition(ev EventReplayVerifiedStateTransition[RI]) { + outgen.logger.Debug("received EventReplayVerifiedStateTransition", commontypes.LogFields{ + "stateTransitionBlockSeqNr": ev.AttestedStateTransitionBlock.StateTransitionBlock.SeqNr(), + }) + + outgen.replayStateTransition( + ev.AttestedStateTransitionBlock.StateTransitionBlock.StateTransitionInputs, + ev.AttestedStateTransitionBlock.StateTransitionBlock.StateTransitionOutputDigest, + ev.AttestedStateTransitionBlock.StateTransitionBlock.ReportsPrecursorDigest, + ev.AttestedStateTransitionBlock.AttributedSignatures) +} + +func (outgen *outcomeGenerationState[RI]) eventProduceStateTransition(ev EventProduceStateTransition[RI]) { + outgen.logger.Debug("received EventProduceStateTransition", commontypes.LogFields{ + "seqNr": ev.RoundCtx.SeqNr, + }) + outgen.sharedState.kvStoreTxn = &ev.Txn + outgen.produceStateTransition( + ev.RoundCtx, + ev.Txn.transaction, + ev.Query, + ev.Asos, + ev.Prepared, + ev.StateTransitionOutputDigest, + ev.ReportsPlusPrecursorDigest, + ev.CommitQC) +} + +func (outgen *outcomeGenerationState[RI]) eventAcknowledgedComputedStateTransition(ev EventAcknowledgedComputedStateTransition[RI]) { + outgen.logger.Debug("received EventAcknowledgedComputedStateTransition", commontypes.LogFields{ + "stateTransitionSeqNr": ev.SeqNr, + }) + outgen.processAcknowledgedComputedStateTransition(ev) +} + +// state as of ev.SeqNr has been written to the key-value store +func (outgen *outcomeGenerationState[RI]) eventCommittedKVTransaction(ev EventCommittedKVTransaction[RI]) { + outgen.logger.Debug("received EventCommittedKVTransaction", commontypes.LogFields{ + "evSeqNr": ev.SeqNr, + }) + if ev.SeqNr != outgen.sharedState.committedSeqNr { + outgen.logger.Critical("we received a CommittedKVTransaction event out of order", commontypes.LogFields{ + "eventSeqNr": ev.SeqNr, + "committedSeqNr": outgen.sharedState.committedSeqNr, + }) + } + outgen.sharedState.committedToKVStoreSeqNr = ev.SeqNr + outgen.sharedState.kvStoreTxn = nil + select { + case outgen.chOutcomeGenerationToStatePersistence <- EventAcknowledgedCommittedKVTransaction[RI]{outgen.sharedState.committedToKVStoreSeqNr}: + case <-outgen.ctx.Done(): + return + } + + if uint64(outgen.config.RMax) <= outgen.sharedState.committedToKVStoreSeqNr-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]) 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 + + // In case we have an open transaction that is not being replayed we should discard it. + // Note that relying on cancelling the epoch context in order to discard the transaction + // through backgroundStateTransition is not enough. The state transition might have been + // already computed but the transaction might have not been committed yet. + // If the transaction is being replayed it will commit anyway so we don't have to cancel it. + if outgen.sharedState.kvStoreTxn != nil && !outgen.sharedState.kvStoreTxn.replayed { + select { + case outgen.chOutcomeGenerationToStatePersistence <- EventDiscardKVTransaction[RI]{outgen.sharedState.kvStoreTxn.transaction.SeqNr()}: + case <-outgen.ctx.Done(): + return + } + outgen.sharedState.kvStoreTxn = nil + } + + outgen.followerState.phase = outgenFollowerPhaseNewEpoch + outgen.followerState.tInitial = time.After(outgen.config.DeltaInitial) + outgen.followerState.roundInfo = roundInfo[RI]{} + + 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 + + var highestCertified CertifiedPrepareOrCommit + var highestCertifiedTimestamp HighestCertifiedTimestamp + highestCertified = outgen.followerState.cert + highestCertifiedTimestamp = outgen.followerState.cert.Timestamp() + + signedHighestCertifiedTimestamp, err := MakeSignedHighestCertifiedTimestamp( + outgen.ID(outgen.sharedState.e), + 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(epoch uint64) OutcomeGenerationID { + return OutcomeGenerationID{outgen.config.ConfigDigest, epoch} +} + +func (outgen *outcomeGenerationState[RI]) RoundCtx(seqNr uint64) ocr3_1types.RoundContext { + if seqNr != outgen.sharedState.committedToKVStoreSeqNr+1 { + outgen.logger.Critical("assumption violation, seqNr isn't successor to committedSeqToKVSeqNr", commontypes.LogFields{ + "seqNr": seqNr, + "committedToKVStoreSeqNr": outgen.sharedState.committedToKVStoreSeqNr, + }) + panic("") + } + return ocr3_1types.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 ocr3_1types.RoundContext, + f func(context.Context, ocr3_1types.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 0000000..c83edc1 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/outcome_generation_follower.go @@ -0,0 +1,1157 @@ +package protocol + +import ( + "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" + outgenFollowerPhaseBackgroundStateTransition outgenFollowerPhase = "backgroundProposalOutcome" + 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, + }) + + 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.sharedState.e), + 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 + + if msg.EpochStartProof.HighestCertified.IsGenesis() { + outgen.sharedState.firstSeqNrOfEpoch = outgen.sharedState.committedToKVStoreSeqNr + 1 + outgen.startSubsequentFollowerRound() + } else if commitQC, ok := msg.EpochStartProof.HighestCertified.(*CertifiedCommit); ok { + outgen.sharedState.firstSeqNrOfEpoch = commitQC.SeqNr() + 1 + if commitQC.SeqNr() == outgen.sharedState.committedToKVStoreSeqNr { + outgen.logger.Debug("starting new epoch gracefully", + commontypes.LogFields{ + "committedToKVStoreSeqNr": outgen.sharedState.committedToKVStoreSeqNr, + "committedSeqNr": outgen.sharedState.committedSeqNr, + "commitQCSeqNr": commitQC.SeqNr(), + }) + outgen.startSubsequentFollowerRound() + outgen.tryProcessRoundStartPool() + } else { + outgen.logger.Debug("trying to start a new epoch after replaying the highest committed state transition", + commontypes.LogFields{ + "committedToKVStoreSeqNr": outgen.sharedState.committedToKVStoreSeqNr, + "committedSeqNr": outgen.sharedState.committedSeqNr, + "commitQCSeqNr": commitQC.SeqNr(), + }) + outgen.initRound() + outgen.replayStateTransition( + commitQC.StateTransitionInputs, + commitQC.StateTransitionOutputDigest, + commitQC.ReportsPlusPrecursorDigest, + commitQC.CommitQuorumCertificate, + ) + } + } 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 + } + outgen.sharedState.firstSeqNrOfEpoch = prepareQc.SeqNr() + 1 + if outgen.sharedState.committedToKVStoreSeqNr+1 != prepareQc.SeqNr() { + outgen.logger.Debug("cannot start new epoch, we need to state sync first", + commontypes.LogFields{ + "committedToKVStoreSeqNr": outgen.sharedState.committedToKVStoreSeqNr, + "committedSeqNr": outgen.sharedState.committedSeqNr, + "prepareQCSeqNr": prepareQc.SeqNr(), + }) + select { + case outgen.chOutcomeGenerationToStatePersistence <- EventStateSyncRequest[RI]{prepareQc.SeqNr()}: + case <-outgen.ctx.Done(): + } + return + } + outgen.logger.Debug("starting new epoch with a re-proposal", + commontypes.LogFields{ + "committedToKVStoreSeqNr": outgen.sharedState.committedToKVStoreSeqNr, + "committedSeqNr": outgen.sharedState.committedSeqNr, + "prepareQCSeqNr": prepareQc.SeqNr(), + }) + outgen.initRound() + select { + case outgen.chOutcomeGenerationToStatePersistence <- EventKVTransactionRequest[RI]{ + ocr3_1types.RoundContext{ + prepareQc.StateTransitionInputs.SeqNr, + prepareQc.StateTransitionInputs.Epoch, + prepareQc.StateTransitionInputs.Round, + }, + prepareQc.StateTransitionInputs.Query, + attributedSignedObservationsFromAttributedObservations(prepareQc.StateTransitionInputs.AttributedObservations), + true, + prepareQc.StateTransitionOutputDigest, + prepareQc.ReportsPlusPrecursorDigest, + nil, + }: + case <-outgen.ctx.Done(): + } + } +} + +func (outgen *outcomeGenerationState[RI]) initRound() { + outgen.sharedState.seqNr = outgen.sharedState.committedToKVStoreSeqNr + 1 + outgen.sharedState.observationQuorum = nil + outgen.followerState.query = nil + outgen.followerState.roundInfo = roundInfo[RI]{} +} + +func (outgen *outcomeGenerationState[RI]) startSubsequentFollowerRound() { + outgen.initRound() + outgen.followerState.phase = outgenFollowerPhaseNewRound + 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 + query := *outgen.followerState.query + kvReader := outgen.kvStore.GetReader() + outgen.subs.Go(func() { + outgen.backgroundObservation(ctx, logger, roundCtx, query, kvReader) + }) + } +} + +func (outgen *outcomeGenerationState[RI]) backgroundObservation( + ctx context.Context, + logger loghelper.LoggerWithContext, + roundCtx ocr3_1types.RoundContext, + query types.Query, + kvReader ocr3_1types.KeyValueReaderDiscardable, +) { + observation, ok := callPluginFromOutcomeGenerationBackground[types.Observation]( + ctx, + logger, + "Observation", + outgen.config.MaxDurationObservation, + roundCtx, + func(ctx context.Context, rondCtx ocr3_1types.RoundContext) (types.Observation, error) { + return outgen.reportingPlugin.Observation(ctx, roundCtx, query, kvReader) + }, + ) + kvReader.Discard() + if !ok { + return + } + + select { + case outgen.chLocalEvent <- EventComputedObservation[RI]{ + roundCtx.Epoch, + roundCtx.SeqNr, + 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.e), 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.e), 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 + } + + roundCtx := outgen.RoundCtx(msg.SeqNr) + + select { + case outgen.chOutcomeGenerationToStatePersistence <- EventKVTransactionRequest[RI]{ + roundCtx, + *outgen.followerState.query, + msg.AttributedSignedObservations, + false, + StateTransitionOutputDigest{}, + ReportsPlusPrecursorDigest{}, + nil, + }: + case <-outgen.ctx.Done(): + } +} + +func (outgen *outcomeGenerationState[RI]) produceStateTransition( + roundCtx ocr3_1types.RoundContext, + kvStoreTxn ocr3_1types.KeyValueStoreTransaction, + query types.Query, + asos []AttributedSignedObservation, + prepared bool, + commitOutputDigest StateTransitionOutputDigest, + commitReportsPlusPrecursorDigest ReportsPlusPrecursorDigest, + commitQC []AttributedCommitSignature, +) { + outgen.followerState.phase = outgenFollowerPhaseBackgroundStateTransition + + { + ctx := outgen.epochCtx + logger := outgen.logger + ogid := outgen.ID(roundCtx.Epoch) + query := query + kvTxn := kvStoreTxn + outgen.subs.Go(func() { + outgen.backgroundStateTransition( + ctx, + logger, + ogid, + roundCtx, + query, + asos, + kvTxn, + prepared, + commitOutputDigest, + commitReportsPlusPrecursorDigest, + commitQC, + ) + }) + } +} + +func (outgen *outcomeGenerationState[RI]) backgroundStateTransition( + ctx context.Context, + logger loghelper.LoggerWithContext, + ogid OutcomeGenerationID, + roundCtx ocr3_1types.RoundContext, + query types.Query, + asos []AttributedSignedObservation, + kvTxn ocr3_1types.KeyValueStoreTransaction, + prepared bool, + commitOutputDigest StateTransitionOutputDigest, + commitReportsPlusPrecursorDigest ReportsPlusPrecursorDigest, + commitQC []AttributedCommitSignature, +) { + shouldDiscardKVTxn := true + defer func() { + if shouldDiscardKVTxn { + select { + case outgen.chOutcomeGenerationToStatePersistence <- EventDiscardKVTransaction[RI]{kvTxn.SeqNr()}: + case <-outgen.ctx.Done(): + return + } + } + }() + + kvReadWriter, err := kvTxn.GetReadWriter() + if err != nil { + outgen.logger.Error("could not get kv transaction read writer", commontypes.LogFields{ + "seqNr": roundCtx.SeqNr, + "err": err, + }) + return + } + + var attributedObservations []types.AttributedObservation + // If we have previously prepared this sequence number more >= f correct oracles have + // checked that the observation quorum is satisfied, the observations are valid. + // Moreover, they have checked that the attributed observations signatures are valid, + // and we have not included signatures in asos. + if !prepared { + aos, ok := outgen.checkAttributedSignedObservations(ctx, logger, ogid, roundCtx, query, asos, kvReadWriter) + if !ok { + return + } + attributedObservations = aos + } else { + attributedObservations = attributedObservationsFromAttributedSignedObservations(asos) + } + + 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 ocr3_1types.RoundContext) (ocr3_1types.ReportsPlusPrecursor, error) { + return outgen.reportingPlugin.StateTransition(ctx, roundCtx, query, attributedObservations, kvReadWriter) + }, + ) + if !ok { + return + } + + stateTransitionInputs := StateTransitionInputs{ + roundCtx.SeqNr, + roundCtx.Epoch, + roundCtx.Round, + query, + attributedObservations, + } + inputsDigest := MakeStateTransitionInputsDigest( + ogid, + roundCtx.SeqNr, + query, + attributedObservations, + ) + + outputDigest := MakeStateTransitionOutputDigest(ogid, roundCtx.SeqNr, kvTxn.GetWriteSet()) + reportsPlusPrecursorDigest, err := MakeReportsPlusPrecursorDigest(ogid, roundCtx.SeqNr, reportsPlusPrecursor) + if err != nil { + outgen.logger.Error("failed to compute reports digest", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "error": err, + }) + return + } + + // If we are dealing with a replayed state transition we need to verify that the return values + // of the StateTransition are consistent. + if commitQC != nil { + if commitOutputDigest != outputDigest { + outgen.logger.Error("StateTransitionOutput digests do not match. "+ + "This is commonly caused by non-determinism in the ReportingPlugin.", commontypes.LogFields{ + "replayedSeqNr": roundCtx.SeqNr, + "seqNr": outgen.sharedState.seqNr, + }) + return + } + if commitReportsPlusPrecursorDigest != reportsPlusPrecursorDigest { + outgen.logger.Error("ReportsPlusPrecursor digests do not match. "+ + "This is commonly caused by non-determinism in the ReportingPlugin.", commontypes.LogFields{ + "replayedSeqNr": roundCtx.SeqNr, + "seqNr": outgen.sharedState.seqNr, + }) + return + } + } + + outgen.followerState.roundInfo = roundInfo[RI]{ + stateTransitionInputs, + reportsPlusPrecursor, + inputsDigest, + outputDigest, + reportsPlusPrecursorDigest, + commitQC, + } + select { + case outgen.chOutcomeGenerationToStatePersistence <- EventComputedStateTransition[RI]{ + kvTxn.SeqNr(), + outgen.sharedState.e, + }: + shouldDiscardKVTxn = false + case <-ctx.Done(): + } +} + +func (outgen *outcomeGenerationState[RI]) processAcknowledgedComputedStateTransition(ev EventAcknowledgedComputedStateTransition[RI]) { + if ev.SeqNr != outgen.followerState.roundInfo.inputs.SeqNr { + outgen.logger.Error("event AcknowledgedComputedStateTransition seqNr does not match round info", commontypes.LogFields{ + "seqNr": outgen.followerState.roundInfo.inputs.SeqNr, + "eventSeqNr": ev.SeqNr, + }) + return + } + + if outgen.followerState.phase != outgenFollowerPhaseBackgroundStateTransition { + outgen.logger.Debug("discarding event AcknowledgedComputedStateTransition, wrong phase", commontypes.LogFields{ + "seqNr": outgen.sharedState.seqNr, + "phase": outgen.followerState.phase, + }) + return + } + + // We are in state-sync + if outgen.followerState.roundInfo.commitQuorumCertificate != nil { + outgen.commit(CertifiedCommit{ + outgen.followerState.roundInfo.inputs, + outgen.followerState.roundInfo.outputDigest, + outgen.followerState.roundInfo.reportsPlusPrecursorDigest, + outgen.followerState.roundInfo.commitQuorumCertificate, + }) + return + } + + prepareSignature, err := MakePrepareSignature( + outgen.ID(outgen.sharedState.e), + outgen.followerState.roundInfo.inputs.SeqNr, + outgen.followerState.roundInfo.inputsDigest, + outgen.followerState.roundInfo.outputDigest, + outgen.followerState.roundInfo.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.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.e), + outgen.sharedState.seqNr, + outgen.followerState.roundInfo.inputsDigest, + outgen.followerState.roundInfo.outputDigest, + outgen.followerState.roundInfo.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.e), + outgen.sharedState.seqNr, + outgen.followerState.roundInfo.inputsDigest, + outgen.followerState.roundInfo.outputDigest, + outgen.followerState.roundInfo.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.followerState.roundInfo.inputs, + outgen.followerState.roundInfo.outputDigest, + outgen.followerState.roundInfo.reportsPlusPrecursorDigest, + 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.e), + outgen.sharedState.seqNr, + outgen.followerState.roundInfo.inputsDigest, + outgen.followerState.roundInfo.outputDigest, + outgen.followerState.roundInfo.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 + } + + outgen.commit(CertifiedCommit{ + outgen.followerState.roundInfo.inputs, + outgen.followerState.roundInfo.outputDigest, + outgen.followerState.roundInfo.reportsPlusPrecursorDigest, + commitQuorumCertificate, + }) + if outgen.id == outgen.sharedState.l { + outgen.metrics.ledCommittedRoundsTotal.Inc() + } +} + +func (outgen *outcomeGenerationState[RI]) commit(commit CertifiedCommit) { + if commit.SeqNr() < outgen.sharedState.committedToKVStoreSeqNr { + outgen.logger.Critical("assumption violation, commitSeqNr is less than committedToKVStoreSeqNr", commontypes.LogFields{ + "commitSeqNr": commit.SeqNr(), + "committedToKVStoreSeqNr": outgen.sharedState.committedToKVStoreSeqNr, + }) + return + } + + if commit.SeqNr() == outgen.sharedState.committedSeqNr { + + outgen.logger.Debug("skipping commit of already committed seqNr", commontypes.LogFields{ + "commitSeqNr ": commit.SeqNr(), + "committedSeqNr": outgen.sharedState.committedSeqNr, + }) + } else { // commit.SeqNr == outgen.sharedState.committedSeqNr + 1 + + if commit.SeqNr() != outgen.sharedState.committedSeqNr+1 { + outgen.logger.Critical("assumption violation, committing out of order", commontypes.LogFields{ + "commitSeqNr": commit.SeqNr(), + "committedSeqNr": outgen.sharedState.committedSeqNr, + }) + return + } + + if !outgen.persistAndUpdateCertIfGreater(&commit) { + return + } + + outgen.sharedState.committedSeqNr = commit.SeqNr() + outgen.metrics.committedSeqNr.Set(float64(commit.SeqNr())) + + outgen.logger.Debug("✅ committed", commontypes.LogFields{ + "seqNr": commit.SeqNr(), + }) + + select { + case outgen.chOutcomeGenerationToReportAttestation <- EventCertifiedCommit[RI]{ + CertifiedCommittedReports[RI]{ + commit.Epoch(), + commit.SeqNr(), + MakeStateTransitionInputsDigest( + outgen.ID(commit.Epoch()), + commit.SeqNr(), + commit.StateTransitionInputs.Query, + commit.StateTransitionInputs.AttributedObservations, + ), + commit.StateTransitionOutputDigest, + outgen.followerState.roundInfo.reportsPlusPrecursor, + commit.CommitQuorumCertificate, + }, + }: + case <-outgen.ctx.Done(): + return + } + } + + // We might have already processed the commit before a crash, but not persisted to KV + if outgen.sharedState.committedToKVStoreSeqNr+1 == outgen.sharedState.committedSeqNr { + select { + case outgen.chOutcomeGenerationToStatePersistence <- EventAttestedStateTransitionBlock[RI]{ + AttestedStateTransitionBlock{ + StateTransitionBlock{ + outgen.followerState.roundInfo.inputs, + outgen.followerState.roundInfo.outputDigest, + outgen.followerState.roundInfo.reportsPlusPrecursorDigest, + }, + commit.CommitQuorumCertificate, + }, + }: + case <-outgen.ctx.Done(): + return + } + } + + 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) +} + +func (outgen *outcomeGenerationState[RI]) replayStateTransition( + inputs StateTransitionInputs, + outputDigest StateTransitionOutputDigest, + reportsPlusPrecursorDigest ReportsPlusPrecursorDigest, + commitQC []AttributedCommitSignature, +) { + if outgen.sharedState.committedToKVStoreSeqNr+1 != inputs.SeqNr { + outgen.logger.Warn("cannot replay out of order state transitions", + commontypes.LogFields{ + "committedToKVStoreSeqNr": outgen.sharedState.committedToKVStoreSeqNr, + "replayedSeqNr": inputs.SeqNr, + }) + select { + case outgen.chOutcomeGenerationToStatePersistence <- EventStateSyncRequest[RI]{inputs.SeqNr}: + case <-outgen.ctx.Done(): + } + return + } + + select { + case outgen.chOutcomeGenerationToStatePersistence <- EventKVTransactionRequest[RI]{ + ocr3_1types.RoundContext{ + inputs.SeqNr, + inputs.Epoch, + inputs.Round, + }, + inputs.Query, + attributedSignedObservationsFromAttributedObservations(inputs.AttributedObservations), + true, + outputDigest, + reportsPlusPrecursorDigest, + commitQC, + }: + case <-outgen.ctx.Done(): + } +} + +// 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 ocr3_1types.RoundContext, + query types.Query, + 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, 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 ocr3_1types.RoundContext) (error, error) { + return outgen.reportingPlugin.ValidateObservation( + ctx, + roundCtx, + query, + types.AttributedObservation{aso.SignedObservation.Observation, aso.Observer}, + kvReader, + ), 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 ocr3_1types.RoundContext) (bool, error) { + return outgen.reportingPlugin.ObservationQuorum(ctx, roundCtx, query, attributedObservations, kvReader) + }, + ) + + 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 attributedObservationsFromAttributedSignedObservations(asos []AttributedSignedObservation) []types.AttributedObservation { + aos := make([]types.AttributedObservation, len(asos)) + for i, aso := range asos { + aos[i] = types.AttributedObservation{ + aso.SignedObservation.Observation, + aso.Observer, + } + } + return aos +} + +func attributedSignedObservationsFromAttributedObservations(aos []types.AttributedObservation) []AttributedSignedObservation { + asos := make([]AttributedSignedObservation, len(aos)) + for i, ao := range aos { + asos[i] = AttributedSignedObservation{ + SignedObservation{ + ao.Observation, + nil, + }, + ao.Observer, + } + } + return asos +} 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 0000000..d3514d2 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/outcome_generation_leader.go @@ -0,0 +1,589 @@ +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/ocr3_1types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type outgenLeaderPhase string + +const ( + outgenLeaderPhaseUnknown outgenLeaderPhase = "unknown" + outgenLeaderPhaseNewEpoch outgenLeaderPhase = "newEpoch" + 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.sharedState.e), + 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, + } + + // 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.sharedState.e), outgen.config.OracleIdentities, outgen.config.ByzQuorumSize()); err != nil { + outgen.logger.Critical("EpochStartProof is invalid, very surprising!", commontypes.LogFields{ + "proof": epochStartProof, + }) + 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.committedToKVStoreSeqNr + 1 + outgen.startSubsequentLeaderRound() + } else if commitQC, ok := epochStartProof.HighestCertified.(*CertifiedCommit); ok { + outgen.sharedState.firstSeqNrOfEpoch = commitQC.SeqNr() + 1 + if commitQC.SeqNr() == outgen.sharedState.committedToKVStoreSeqNr { + outgen.startSubsequentLeaderRound() + } else { + outgen.logger.Debug("leader cannot start subsequent round yet, will try to state sync first", commontypes.LogFields{ + "commitQCSeqNr": outgen.sharedState.seqNr, + "committedToKVStoreSeqNr": commitQC.SeqNr(), + }) + // The leader will try to state sync in the follower code path and + } + } 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.committedToKVStoreSeqNr + 1) + kvReader := outgen.kvStore.GetReader() + outgen.subs.Go(func() { + outgen.backgroundQuery(ctx, logger, roundCtx, kvReader) + }) + } +} + +func (outgen *outcomeGenerationState[RI]) backgroundQuery( + ctx context.Context, + logger loghelper.LoggerWithContext, + roundCtx ocr3_1types.RoundContext, + kvReader ocr3_1types.KeyValueReaderDiscardable, +) { + query, ok := callPluginFromOutcomeGenerationBackground[types.Query]( + ctx, + logger, + "Query", + outgen.config.MaxDurationQuery, + roundCtx, + func(ctx context.Context, outctx ocr3_1types.RoundContext) (types.Query, error) { + return outgen.reportingPlugin.Query(ctx, roundCtx, kvReader) + }, + ) + kvReader.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(outgen.sharedState.e) + roundCtx := outgen.RoundCtx(outgen.sharedState.seqNr) + query := outgen.leaderState.query + kvReader := outgen.kvStore.GetReader() + outgen.subs.Go(func() { + outgen.backgroundVerifyValidateObservation(ctx, logger, ogid, roundCtx, sender, msg.SignedObservation, query, kvReader) + }) + } +} + +func (outgen *outcomeGenerationState[RI]) backgroundVerifyValidateObservation( + ctx context.Context, + logger loghelper.LoggerWithContext, + ogid OutcomeGenerationID, + roundCtx ocr3_1types.RoundContext, + sender commontypes.OracleID, + signedObservation SignedObservation, + query types.Query, + kvReader ocr3_1types.KeyValueReaderDiscardable, +) { + + if err := signedObservation.Verify( + ogid, + roundCtx.SeqNr, + 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 ocr3_1types.RoundContext) (error, error) { + return outgen.reportingPlugin.ValidateObservation(ctx, + roundCtx, + query, + types.AttributedObservation{signedObservation.Observation, sender}, + kvReader, + ), nil + }, + ) + kvReader.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) + query := outgen.leaderState.query + 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}) + } + kvReader := outgen.kvStore.GetReader() + + outgen.subs.Go(func() { + outgen.backgroundObservationQuorum( + ctx, + logger, + outctx, + query, + aos, + kvReader, + ) + }) + } +} + +func (outgen *outcomeGenerationState[RI]) backgroundObservationQuorum( + ctx context.Context, + logger loghelper.LoggerWithContext, + roundCtx ocr3_1types.RoundContext, + query types.Query, + aos []types.AttributedObservation, + kvReader ocr3_1types.KeyValueReaderDiscardable, +) { + observationQuorum, ok := callPluginFromOutcomeGenerationBackground[bool]( + ctx, + logger, + "ObservationQuorum", + 0, // ObservationQuorum is a pure function and should finish "instantly" + roundCtx, + func(ctx context.Context, roundCtx ocr3_1types.RoundContext) (bool, error) { + return outgen.reportingPlugin.ObservationQuorum(ctx, roundCtx, query, aos, kvReader) + }, + ) + kvReader.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 0000000..a30b815 --- /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 0000000..9a52174 --- /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 0000000..879088a --- /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/signed_data.go b/offchainreporting2plus/internal/ocr3_1/protocol/signed_data.go new file mode 100644 index 0000000..c30195a --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/signed_data.go @@ -0,0 +1,765 @@ +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/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 []ocr3_1types.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, error) { + 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, nil +} + +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 +} + +func (t HighestCertifiedTimestamp) Less(t2 HighestCertifiedTimestamp) bool { + return t.SeqNr < t2.SeqNr || t.SeqNr == t2.SeqNr && !t.CommittedElsePrepared && t2.CommittedElsePrepared +} + +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)}) + + 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 { + StateTransitionInputs StateTransitionInputs + StateTransitionOutputDigest StateTransitionOutputDigest + ReportsPlusPrecursorDigest ReportsPlusPrecursorDigest + PrepareQuorumCertificate []AttributedPrepareSignature +} + +func (hc *CertifiedPrepare) isCertifiedPrepareOrCommit() {} + +func (hc *CertifiedPrepare) Epoch() uint64 { + return uint64(hc.StateTransitionInputs.Epoch) +} + +func (hc *CertifiedPrepare) SeqNr() uint64 { + return uint64(hc.StateTransitionInputs.SeqNr) +} + +func (hc *CertifiedPrepare) Timestamp() HighestCertifiedTimestamp { + return HighestCertifiedTimestamp{ + hc.SeqNr(), + false, + } +} + +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) + } + inputsDigest := MakeStateTransitionInputsDigest( + ogid, + hc.SeqNr(), + hc.StateTransitionInputs.Query, + hc.StateTransitionInputs.AttributedObservations) + if err := aps.Signature.Verify( + ogid, hc.SeqNr(), inputsDigest, hc.StateTransitionOutputDigest, + hc.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.StateTransitionInputs.Query) > limits.MaxQueryLength { + return false + } + + if len(hc.StateTransitionInputs.AttributedObservations) > n { + return false + } + for _, ao := range hc.StateTransitionInputs.AttributedObservations { + if len(ao.Observation) > limits.MaxObservationLength { + return false + } + } + + 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 { + StateTransitionInputs StateTransitionInputs + StateTransitionOutputDigest StateTransitionOutputDigest + ReportsPlusPrecursorDigest ReportsPlusPrecursorDigest + CommitQuorumCertificate []AttributedCommitSignature +} + +func (hc *CertifiedCommit) isCertifiedPrepareOrCommit() {} + +func (hc *CertifiedCommit) Epoch() uint64 { + return uint64(hc.StateTransitionInputs.Epoch) +} + +func (hc *CertifiedCommit) SeqNr() uint64 { + return uint64(hc.StateTransitionInputs.SeqNr) +} + +func (hc *CertifiedCommit) Timestamp() HighestCertifiedTimestamp { + return HighestCertifiedTimestamp{ + hc.SeqNr(), + true, + } +} + +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.StateTransitionInputs.isGenesis() && + hc.StateTransitionOutputDigest == StateTransitionOutputDigest{} && + hc.ReportsPlusPrecursorDigest == ReportsPlusPrecursorDigest{} && + 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) + } + inputsDigest := MakeStateTransitionInputsDigest( + ogid, + hc.SeqNr(), + hc.StateTransitionInputs.Query, + hc.StateTransitionInputs.AttributedObservations) + if err := acs.Signature.Verify(ogid, + hc.SeqNr(), + inputsDigest, + hc.StateTransitionOutputDigest, + hc.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.StateTransitionInputs.Query) > limits.MaxQueryLength { + return false + } + + if len(hc.StateTransitionInputs.AttributedObservations) > n { + return false + } + for _, ao := range hc.StateTransitionInputs.AttributedObservations { + if len(ao.Observation) > limits.MaxObservationLength { + return false + } + } + + 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.StateTransitionInputs.Epoch, + } + + 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, + astb.StateTransitionBlock.SeqNr(), + MakeStateTransitionInputsDigest( + ogid, + astb.StateTransitionBlock.SeqNr(), + astb.StateTransitionBlock.StateTransitionInputs.Query, + astb.StateTransitionBlock.StateTransitionInputs.AttributedObservations, + ), + astb.StateTransitionBlock.StateTransitionOutputDigest, + astb.StateTransitionBlock.ReportsPrecursorDigest, + 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, err := MakeReportsPlusPrecursorDigest(ogid, ccrs.SeqNr, ccrs.ReportsPlusPrecursor) + if err != nil { + return fmt.Errorf("cannot produce reports digest") + } + 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 +} 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 0000000..b2ad6e9 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/state_block_synchronization.go @@ -0,0 +1,365 @@ +package protocol + +import ( + "crypto/rand" + "math/big" + "time" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/internal/common/scheduler" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +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 = 5 * time.Second + + 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() { + for _, oracle := range state.blockSyncState.oracles { + req := oracle.inFlightRequest + if req == nil { + continue + } + if req.message.HighestCommittedSeqNr < state.highestCommittedToKVSeqNr { + // this is a stale request + state.blockSyncState.logger.Debug("removing stale BlockSyncRequest", commontypes.LogFields{ + "requestedFrom": req.requestedFrom, + "requestedFromSeqNr": req.message.HighestCommittedSeqNr, + "highestCommittedToKVSeqNr": state.highestCommittedToKVSeqNr, + }) + oracle.inFlightRequest = nil + } + } +} + +func (state *statePersistenceState[RI]) trySendNextRequest() { + if !state.readyToSendBlockSyncReq { + 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 + } + + if state.highestHeardSeqNr > state.highestCommittedToKVSeqNr { + state.sendBlockSyncReq(state.highestCommittedToKVSeqNr) + } +} + +func (state *statePersistenceState[RI]) tryComplete() { + state.clearStaleBlockSyncRequests() + state.trySendNextRequest() +} + +func (state *statePersistenceState[RI]) processBlockSyncSummaryHeartbeat() { + defer state.blockSyncState.scheduler.ScheduleDelay(EventBlockSyncSummaryHeartbeat[RI]{}, DeltaBlockSyncHeartbeat) + lowestPersistedSeqNr := 0 + 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) <= state.config.F { + + state.blockSyncState.logger.Debug("not enough 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]{seqNr, nonce.Uint64()} + state.netSender.SendTo(MessageBlockSyncRequestWrapper[RI]{ + msg, + nil, + types.SingleUseSizedLimitedResponsePolicy{ + maxBlockSyncSize, + time.Now().Add(DeltaMaxBlockSyncRequest), + }, + }, 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(msgWrapper RequestMessage[RI], sender commontypes.OracleID) { + if _, ok := msgWrapper.GetSerializableRequestMessage().(MessageBlockSyncRequest[RI]); !ok { + state.blockSyncState.logger.Error("message type assertion failed", commontypes.LogFields{ + "sender": sender, + }) + } + msg := msgWrapper.GetSerializableRequestMessage().(MessageBlockSyncRequest[RI]) + state.blockSyncState.logger.Debug("received MessageBlockSyncRequest", commontypes.LogFields{ + "sender": sender, + "msgHighestCommittedSeqNr": msg.HighestCommittedSeqNr, + }) + loSeqNr := msg.HighestCommittedSeqNr + 1 + + 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. + } + 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(MessageBlockSyncWrapper[RI]{ + MessageBlockSync[RI]{ + astbs, + msg.Nonce, + }, + msgWrapper.GetRequestHandle(), + }, 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(msgWrapper ResponseMessage[RI], sender commontypes.OracleID) { + if _, ok := msgWrapper.GetSerializableResponseMessage().(MessageBlockSync[RI]); !ok { + state.blockSyncState.logger.Error("message type assertion failed", commontypes.LogFields{ + "sender": sender, + }) + } + msg := msgWrapper.GetSerializableResponseMessage().(MessageBlockSync[RI]) + 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.blockSyncState.logger.Debug("retrieved state transition block", commontypes.LogFields{ + "seqNr": astb.StateTransitionBlock.SeqNr(), + }) + if astb.StateTransitionBlock.SeqNr() <= state.highestCommittedToKVSeqNr { + state.blockSyncState.logger.Debug("no need to replay this state transition", commontypes.LogFields{ + "stateTransitionBlockSeqNr": astb.StateTransitionBlock.SeqNr(), + "highestPersistedToKvSeqNr": state.highestCommittedToKVSeqNr, + }) + continue + } + if astb.StateTransitionBlock.SeqNr() > state.highestPersistedStateTransitionBlockSeqNr+1 { + + 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 astb.StateTransitionBlock.SeqNr() <= state.highestPersistedStateTransitionBlockSeqNr { + 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 if astb.StateTransitionBlock.SeqNr() == state.highestPersistedStateTransitionBlockSeqNr+1 { + err := state.persist(astb) + if err != nil { + state.blockSyncState.logger.Error("error persisting state transition block", commontypes.LogFields{ + "stateTransitionBlockSeqNr": astb.StateTransitionBlock.SeqNr(), + "error": err, + }) + return + } + } + // we might have already persisted the block but we might have not committed it to KV yet + if last, isNotEmpty := state.retrievedStateTransitionBlockSeqNrQueue.PeekLast(); !isNotEmpty || last < state.highestPersistedStateTransitionBlockSeqNr { + state.blockSyncState.logger.Debug("pushing to retrievedStateTransitionBlockSeqNrQueue", commontypes.LogFields{ + "stateTransitionBlockSeqNr": astb.StateTransitionBlock.SeqNr(), + "lastSeqNr": last, + }) + state.retrievedStateTransitionBlockSeqNrQueue.Push(astb.StateTransitionBlock.SeqNr()) + } + } +} 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 0000000..7fd2fff --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/state_persistence.go @@ -0,0 +1,517 @@ +package protocol + +import ( + "context" + "errors" + "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/internal/ocr3_1/protocol/queue" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" +) + +func RunStatePersistence[RI any]( + ctx context.Context, + + chNetToStatePersistence <-chan MessageToStatePersistenceWithSender[RI], + chOutcomeGenerationToStatePersistence <-chan EventToStatePersistence[RI], + chReportAttestationToStatePersistence <-chan EventToStatePersistence[RI], + chStatePersistenceToOutcomeGeneration chan<- EventToOutcomeGeneration[RI], + config ocr3config.SharedConfig, + database Database, + id commontypes.OracleID, + kvStore ocr3_1types.KeyValueStore, + logger loghelper.LoggerWithContext, + netSender NetworkSender[RI], + restoredState StatePersistenceState, + restoredHighestCommittedToKVSeqNr uint64, +) { + sched := scheduler.NewScheduler[EventToStatePersistence[RI]]() + defer sched.Close() + + newStatePersistenceState(ctx, chNetToStatePersistence, + chOutcomeGenerationToStatePersistence, + chReportAttestationToStatePersistence, + chStatePersistenceToOutcomeGeneration, + config, database, id, kvStore, logger, netSender, sched).run(restoredState, restoredHighestCommittedToKVSeqNr) +} + +const maxPersistedAttestedStateTransitionBlocks int = math.MaxInt + +type statePersistenceState[RI any] struct { + ctx context.Context + + chNetToStatePersistence <-chan MessageToStatePersistenceWithSender[RI] + chOutcomeGenerationToStatePersistence <-chan EventToStatePersistence[RI] + chReportAttestationToStatePersistence <-chan EventToStatePersistence[RI] + chStatePersistenceToOutcomeGeneration chan<- EventToOutcomeGeneration[RI] + config ocr3config.SharedConfig + database Database + id commontypes.OracleID + kvStore ocr3_1types.KeyValueStore + logger loghelper.LoggerWithContext + netSender NetworkSender[RI] + + kvStoreTxn *kvStoreTxn + highestComputedStateTransitionSeqNr uint64 + highestPersistedStateTransitionBlockSeqNr uint64 + highestCommittedToKVSeqNr uint64 + highestAcknowledgedCommittedToKVSeqNr uint64 + highestHeardSeqNr uint64 + readyToSendBlockSyncReq bool + retrievedStateTransitionBlockSeqNrQueue *queue.Queue[uint64] + eventProduceStateTransition EventProduceStateTransition[RI] + notifyOutcomeGenerationOfOpenedKVTransaction bool + notifyOutcomeGenerationOfReceivedKVTransaction bool + notifyOutcomeGenerationOfCommittedKVTransaction bool + + blockSyncState blockSyncState[RI] + treeSyncState treeSyncState +} + +type kvStoreTxn struct { + transaction ocr3_1types.KeyValueStoreTransaction + replayed bool +} + +func (state *statePersistenceState[RI]) run(restoredState StatePersistenceState, restoredCommittedToKVStoreSeqNr uint64) { + state.highestPersistedStateTransitionBlockSeqNr = restoredState.HighestPersistedStateTransitionBlockSeqNr + state.highestCommittedToKVSeqNr = restoredCommittedToKVStoreSeqNr + state.highestAcknowledgedCommittedToKVSeqNr = restoredCommittedToKVStoreSeqNr + state.highestComputedStateTransitionSeqNr = restoredCommittedToKVStoreSeqNr + state.logger.Info("StatePersistence: running", commontypes.LogFields{ + "restoredHighestPersistedStateTransitionBlockSeqNr": restoredState.HighestPersistedStateTransitionBlockSeqNr, + "restoredCommittedToKVStoreSeqNr": restoredCommittedToKVStoreSeqNr, + }) + + for { + var nilOrEventProduceStateTransition chan<- EventToOutcomeGeneration[RI] + if state.notifyOutcomeGenerationOfOpenedKVTransaction { + nilOrEventProduceStateTransition = state.chStatePersistenceToOutcomeGeneration + } else { + nilOrEventProduceStateTransition = nil + } + + var nilOrEventReplayStateTransition chan<- EventToOutcomeGeneration[RI] + nextBlock, ok := state.nextStateTransitionBlockToReplay() + if ok { + nilOrEventReplayStateTransition = state.chStatePersistenceToOutcomeGeneration + } else { + nilOrEventReplayStateTransition = nil + } + + var nilOrEventReceivedKVTransaction chan<- EventToOutcomeGeneration[RI] + if state.notifyOutcomeGenerationOfReceivedKVTransaction { + nilOrEventReceivedKVTransaction = state.chStatePersistenceToOutcomeGeneration + } else { + nilOrEventReceivedKVTransaction = nil + } + + var nilOrEventCommittedKVStateTransaction chan<- EventToOutcomeGeneration[RI] + if state.notifyOutcomeGenerationOfCommittedKVTransaction { + nilOrEventCommittedKVStateTransaction = state.chStatePersistenceToOutcomeGeneration + } else { + nilOrEventCommittedKVStateTransaction = nil + } + + select { + case nilOrEventProduceStateTransition <- EventProduceStateTransition[RI]{ + state.eventProduceStateTransition.RoundCtx, + state.eventProduceStateTransition.Txn, + state.eventProduceStateTransition.Query, + state.eventProduceStateTransition.Asos, + state.eventProduceStateTransition.Prepared, + state.eventProduceStateTransition.StateTransitionOutputDigest, + state.eventProduceStateTransition.ReportsPlusPrecursorDigest, + state.eventProduceStateTransition.CommitQC, + }: + state.notifyOutcomeGenerationOfOpenedKVTransaction = false + case nilOrEventReplayStateTransition <- EventReplayVerifiedStateTransition[RI]{nextBlock}: + case nilOrEventReceivedKVTransaction <- EventAcknowledgedComputedStateTransition[RI]{state.highestComputedStateTransitionSeqNr}: + state.notifyOutcomeGenerationOfReceivedKVTransaction = false + case nilOrEventCommittedKVStateTransaction <- EventCommittedKVTransaction[RI]{state.highestCommittedToKVSeqNr}: + state.notifyOutcomeGenerationOfCommittedKVTransaction = false + case msg := <-state.chNetToStatePersistence: + msg.msg.processStatePersistence(state, msg.sender) + case ev := <-state.chOutcomeGenerationToStatePersistence: + ev.processStatePersistence(state) + case ev := <-state.chReportAttestationToStatePersistence: + ev.processStatePersistence(state) + case ev := <-state.blockSyncState.scheduler.Scheduled(): + ev.processStatePersistence(state) + 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]) eventKVTransactionRequest(ev EventKVTransactionRequest[RI]) { + state.logger.Debug("StatePersistence: receivedEventKVTransactionRequest", commontypes.LogFields{ + "seqNr": ev.RoundCtx.SeqNr, + }) + if ev.RoundCtx.SeqNr <= state.highestCommittedToKVSeqNr { + state.logger.Debug("StatePersistence: dropping KV transaction request, we have already committed this transaction", commontypes.LogFields{ + "seqNr": ev.RoundCtx.SeqNr, + "committedToKVSeqNr": state.highestCommittedToKVSeqNr, + }) + return + } + + txn, err := state.kvStore.NewTransaction(ev.RoundCtx.SeqNr) + if err != nil && errors.Is(err, ocr3_1types.ErrDuplicateTransaction) { + state.logger.Debug("could not open new kv transaction", commontypes.LogFields{ + "seqNr": ev.RoundCtx.SeqNr, + "err": err, + }) + return + } else if err != nil { + state.logger.Error("could not open new kv transaction", commontypes.LogFields{ + "seqNr": ev.RoundCtx.SeqNr, + "err": err, + }) + return + } + + // if ev.CommitQC != nil we are dealing with a replay + if ev.CommitQC != nil { + state.kvStoreTxn = &kvStoreTxn{ + txn, + true, + } + } else { + state.kvStoreTxn = &kvStoreTxn{ + txn, + false, + } + } + + state.logger.Debug("new transaction", commontypes.LogFields{ + "txnSeqNr": txn.SeqNr(), + "committedToKVSeqNr": state.highestCommittedToKVSeqNr, + }) + state.eventProduceStateTransition = EventProduceStateTransition[RI]{ + ev.RoundCtx, + *state.kvStoreTxn, + ev.Query, + ev.Asos, + ev.Prepared, + ev.StateTransitionOutputDigest, + ev.ReportsPlusPrecursorDigest, + ev.CommitQC, + } + state.notifyOutcomeGenerationOfOpenedKVTransaction = true +} + +func (state *statePersistenceState[RI]) eventComputedStateTransition(ev EventComputedStateTransition[RI]) { + state.logger.Debug("received EventComputedStateTransition", commontypes.LogFields{ + "txnSeqNr": ev.SeqNr, + }) + if state.kvStoreTxn != nil && state.kvStoreTxn.transaction != nil && ev.SeqNr != state.kvStoreTxn.transaction.SeqNr() { + state.logger.Debug("StatePersistence: discarding EventComputedStateTransition, invalid seqNr", commontypes.LogFields{ + "evSeqNr": ev.SeqNr, + "txnSeqNr": state.kvStoreTxn.transaction.SeqNr(), + }) + return + } + state.highestComputedStateTransitionSeqNr = ev.SeqNr + state.notifyOutcomeGenerationOfReceivedKVTransaction = true + +} + +func (state *statePersistenceState[RI]) eventAttestedStateTransitionBlock(ev EventAttestedStateTransitionBlock[RI]) { + seqNr := ev.AttestedStateTransitionBlock.StateTransitionBlock.SeqNr() + state.logger.Debug("received EventAttestedStateTransitionBlock", commontypes.LogFields{ + "evSeqNr": seqNr, + }) + + if ev.AttestedStateTransitionBlock.StateTransitionBlock.SeqNr() != state.highestComputedStateTransitionSeqNr { + state.logger.Critical("we received an attestedStateTransitionBlock out of order", commontypes.LogFields{ + "attestedStateTransitionBlockSeqNr": ev.AttestedStateTransitionBlock.StateTransitionBlock.SeqNr(), + "highestComputedStateTransitionSeqNr": state.highestComputedStateTransitionSeqNr, + }) + } + + defer state.heardSeqNr(seqNr) + + if seqNr == state.highestPersistedStateTransitionBlockSeqNr+1 { + if err := state.persist(ev.AttestedStateTransitionBlock); err != nil { + state.logger.Error("failed to persist attested state transition", commontypes.LogFields{ + "error": err, + "seqNr": seqNr, + }) + } + + err := state.database.WriteStatePersistenceState( + state.ctx, state.config.ConfigDigest, + StatePersistenceState{ + state.highestPersistedStateTransitionBlockSeqNr, + }) + if err != nil { + state.logger.Error("Failed to persist the key-value sequence number", commontypes.LogFields{ + "SeqNr": seqNr, + "err": err, + }) + return + } + } + + if state.kvStoreTxn == nil || state.kvStoreTxn.transaction == nil { + state.logger.Error("State persistence protocol has not created a kv transaction yet", commontypes.LogFields{ + "highestComputedStateTransitionSeqNr": state.highestComputedStateTransitionSeqNr, + }) + return + } + + if state.kvStoreTxn.transaction.SeqNr() != seqNr { + state.logger.Error("Cannot commit to the key value store, committed sequence number is different from the sequence number of the state persistence key value store transaction", + commontypes.LogFields{ + "committedSeqNr": seqNr, + "txnSeqNr": state.kvStoreTxn.transaction.SeqNr(), + }) + return + } + + err := state.kvStoreTxn.transaction.Commit() + if err != nil { + state.logger.Warn("Failed to commit to the key value store", commontypes.LogFields{ + "committedSeqNr": seqNr, + "err": err, + }) + state.kvStoreTxn.transaction.Discard() + state.kvStoreTxn = nil + return + } + state.kvStoreTxn = nil + + if first, isNotEmpty := state.retrievedStateTransitionBlockSeqNrQueue.Peek(); isNotEmpty { + if *first == seqNr { + if popped, ok := state.retrievedStateTransitionBlockSeqNrQueue.Pop(); ok { + state.logger.Debug("popped from queue committed to KV seqNr", commontypes.LogFields{ + "committedSeqNr": seqNr, + "poppedSeqNr": popped, + }) + } + } + } + + state.highestCommittedToKVSeqNr = seqNr + state.logger.Debug("persisted transaction to KV store", commontypes.LogFields{ + "highestCommittedToKVSeqNr": state.highestCommittedToKVSeqNr, + }) + state.clearStaleBlockSyncRequests() + state.notifyOutcomeGenerationOfCommittedKVTransaction = true +} + +func (state *statePersistenceState[RI]) eventAcknowledgedCommittedKVTransaction(ev EventAcknowledgedCommittedKVTransaction[RI]) { + state.logger.Debug("received EventAcknowledgedCommittedKVTransaction", commontypes.LogFields{ + "txnSeqNr": ev.SeqNr, + }) + if ev.SeqNr != state.highestCommittedToKVSeqNr { + state.logger.Critical("we received an AcknowledgedCommittedKVTransaction event out of order", commontypes.LogFields{ + "eventSeqNr": ev.SeqNr, + "highestCommittedToKVSeqNr": state.highestCommittedToKVSeqNr, + }) + } + state.highestAcknowledgedCommittedToKVSeqNr = ev.SeqNr +} + +// This event is triggered upon outgen enters a new epoch, and it's purpose is to discard a transaction +// that did not get committed in case the epoch had timed out. We should only discard transactions +// from the common path, i.e. transactions that are not being replayed. +func (state *statePersistenceState[RI]) eventDiscardKVTransaction(ev EventDiscardKVTransaction[RI]) { + state.logger.Debug("received EventDiscardKVTransaction", commontypes.LogFields{ + "evSeqNr": ev.SeqNr, + }) + if state.kvStoreTxn != nil && state.kvStoreTxn.transaction != nil && + ev.SeqNr == state.kvStoreTxn.transaction.SeqNr() { + state.kvStoreTxn.transaction.Discard() + state.kvStoreTxn = nil + } +} + +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.highestHeardSeqNr = seqNr + state.logger.Debug("increased highestHeardSeqNr seqNr", commontypes.LogFields{ + "heardSeqNr": seqNr, + }) + state.highestHeardIncreased() + } +} + +func (state *statePersistenceState[RI]) persist(astb AttestedStateTransitionBlock) error { + if astb.StateTransitionBlock.SeqNr() != state.highestPersistedStateTransitionBlockSeqNr+1 { + return fmt.Errorf("cannot persist out of order state transition block: expected %d, got %d", + state.highestPersistedStateTransitionBlockSeqNr+1, + astb.StateTransitionBlock.SeqNr(), + ) + } + err := state.database.WriteAttestedStateTransitionBlock(state.ctx, + state.config.ConfigDigest, + astb.StateTransitionBlock.SeqNr(), + astb, + ) + if err != nil { + return err + } + err = state.database.WriteStatePersistenceState( + state.ctx, state.config.ConfigDigest, + StatePersistenceState{ + astb.StateTransitionBlock.SeqNr(), + }) + if err != nil { + return err + } + state.highestPersistedStateTransitionBlockSeqNr = astb.StateTransitionBlock.SeqNr() + state.logger.Debug("persisted state transition block", commontypes.LogFields{ + "highestPersisted": state.highestPersistedStateTransitionBlockSeqNr, + }) + return nil +} + +func (state *statePersistenceState[RI]) nextStateTransitionBlockToReplay() (AttestedStateTransitionBlock, bool) { + if next, isNotEmpty := state.retrievedStateTransitionBlockSeqNrQueue.Peek(); isNotEmpty { + if *next <= state.highestAcknowledgedCommittedToKVSeqNr { + state.logger.Debug("no need to replay state transition block", commontypes.LogFields{ + "nextSeqNr": *next, + "highestCommittedToKVSeqNr": state.highestCommittedToKVSeqNr, + "highestAcknowledgedCommittedToKVSeqNr": state.highestAcknowledgedCommittedToKVSeqNr, + }) + state.retrievedStateTransitionBlockSeqNrQueue.Pop() + return AttestedStateTransitionBlock{}, false + } + if *next > state.highestAcknowledgedCommittedToKVSeqNr+1 { + state.logger.Error("cannot replay state transition block, missing state transitions in between", commontypes.LogFields{ + "nextBlockSeqNr": *next, + "highestCommittedToKVSeqNr": state.highestCommittedToKVSeqNr, + "highestAcknowledgedCommittedToKVSeqNr": state.highestAcknowledgedCommittedToKVSeqNr, + }) + return AttestedStateTransitionBlock{}, false + } + // else nextSeqNr = state.highestAcknowledgedCommittedToKVSeqNr + 1 + if state.kvStoreTxn != nil { + state.logger.Trace("no need to replay state transition block yet, we have an open transaction in progress", commontypes.LogFields{ + "nextSeqNr": *next, + "highestCommittedToKVSeqNr": state.highestCommittedToKVSeqNr, + }) + return AttestedStateTransitionBlock{}, false + } + nextBlock, err := state.database.ReadAttestedStateTransitionBlock(state.ctx, state.config.ConfigDigest, *next) + if err != nil { + state.logger.Error("failed to read attested state transition block from database", commontypes.LogFields{ + "nextSeqNr": *next, + "error": err, + }) + return AttestedStateTransitionBlock{}, false + } + if *next != nextBlock.StateTransitionBlock.SeqNr() { + state.logger.Error("read from database block with unexpected sequence number", commontypes.LogFields{ + "nextSeqNr": *next, + "stateTransitionBlockSeqNr": nextBlock.StateTransitionBlock.SeqNr(), + }) + return AttestedStateTransitionBlock{}, false + } + state.logger.Debug("next state transition block to replay", commontypes.LogFields{ + "nextSeqNr": *next, + }) + return nextBlock, true + } + return AttestedStateTransitionBlock{}, false +} + +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], + chOutcomeGenerationToStatePersistence <-chan EventToStatePersistence[RI], + chReportAttestationToStatePersistence <-chan EventToStatePersistence[RI], + chStatePersistenceToOutcomeGeneration chan<- EventToOutcomeGeneration[RI], + config ocr3config.SharedConfig, + database Database, + id commontypes.OracleID, + kvStore ocr3_1types.KeyValueStore, + logger loghelper.LoggerWithContext, + netSender NetworkSender[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) + return &statePersistenceState[RI]{ + ctx, + + chNetToStatePersistence, + chOutcomeGenerationToStatePersistence, + chReportAttestationToStatePersistence, + chStatePersistenceToOutcomeGeneration, + config, + database, + id, + kvStore, + logger.MakeUpdated(commontypes.LogFields{"proto": "state"}), + netSender, + nil, + 0, + 0, + 0, + 0, + 0, + true, + queue.NewQueue[uint64](), + + EventProduceStateTransition[RI]{}, + false, + false, + false, + 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 0000000..8607f90 --- /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 0000000..d38f6a4 --- /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 0000000..ac66243 --- /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 0000000..01e0e61 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/protocol/types.go @@ -0,0 +1,34 @@ +package protocol + +import ( + "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 { + StateTransitionInputs StateTransitionInputs + StateTransitionOutputDigest StateTransitionOutputDigest + ReportsPrecursorDigest ReportsPlusPrecursorDigest +} + +func (stb *StateTransitionBlock) SeqNr() uint64 { + return stb.StateTransitionInputs.SeqNr +} + +type StateTransitionInputs struct { + SeqNr uint64 + Epoch uint64 + Round uint64 + Query types.Query + AttributedObservations []types.AttributedObservation +} + +func (stis *StateTransitionInputs) isGenesis() bool { + return stis.SeqNr == 0 && stis.Epoch == 0 && stis.Round == 0 && + len(stis.Query) == 0 && len(stis.AttributedObservations) == 0 +} 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 0000000..27f51d7 --- /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 0000000..066d8ae --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/serialization/offchainreporting3_1_messages.pb.go @@ -0,0 +1,2976 @@ +// 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 + 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 +} + +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"` +} + +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() {} + +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 + + StateTransitionInputs *StateTransitionInputs `protobuf:"bytes,1,opt,name=state_transition_inputs,json=stateTransitionInputs,proto3" json:"state_transition_inputs,omitempty"` + StateTransitionOutputDigest []byte `protobuf:"bytes,2,opt,name=state_transition_output_digest,json=stateTransitionOutputDigest,proto3" json:"state_transition_output_digest,omitempty"` + ReportsPlusPrecursorDigest []byte `protobuf:"bytes,3,opt,name=reports_plus_precursor_digest,json=reportsPlusPrecursorDigest,proto3" json:"reports_plus_precursor_digest,omitempty"` + PrepareQuorumCertificate []*AttributedPrepareSignature `protobuf:"bytes,4,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) GetStateTransitionInputs() *StateTransitionInputs { + if x != nil { + return x.StateTransitionInputs + } + return nil +} + +func (x *CertifiedPrepare) GetStateTransitionOutputDigest() []byte { + if x != nil { + return x.StateTransitionOutputDigest + } + return nil +} + +func (x *CertifiedPrepare) GetReportsPlusPrecursorDigest() []byte { + if x != nil { + return x.ReportsPlusPrecursorDigest + } + 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 + + StateTransitionInputs *StateTransitionInputs `protobuf:"bytes,1,opt,name=state_transition_inputs,json=stateTransitionInputs,proto3" json:"state_transition_inputs,omitempty"` + StateTransitionOutputDigest []byte `protobuf:"bytes,2,opt,name=state_transition_output_digest,json=stateTransitionOutputDigest,proto3" json:"state_transition_output_digest,omitempty"` + ReportsPlusPrecursorDigest []byte `protobuf:"bytes,3,opt,name=reports_plus_precursor_digest,json=reportsPlusPrecursorDigest,proto3" json:"reports_plus_precursor_digest,omitempty"` + CommitQuorumCertificate []*AttributedCommitSignature `protobuf:"bytes,4,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) GetStateTransitionInputs() *StateTransitionInputs { + if x != nil { + return x.StateTransitionInputs + } + return nil +} + +func (x *CertifiedCommit) GetStateTransitionOutputDigest() []byte { + if x != nil { + return x.StateTransitionOutputDigest + } + return nil +} + +func (x *CertifiedCommit) GetReportsPlusPrecursorDigest() []byte { + if x != nil { + return x.ReportsPlusPrecursorDigest + } + 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"` +} + +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 +} + +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 + + StateTransitionInputs *StateTransitionInputs `protobuf:"bytes,1,opt,name=state_transition_inputs,json=stateTransitionInputs,proto3" json:"state_transition_inputs,omitempty"` + StateTransitionOutputDigest []byte `protobuf:"bytes,2,opt,name=state_transition_output_digest,json=stateTransitionOutputDigest,proto3" json:"state_transition_output_digest,omitempty"` + ReportsPlusPrecursorDigest []byte `protobuf:"bytes,3,opt,name=reports_plus_precursor_digest,json=reportsPlusPrecursorDigest,proto3" json:"reports_plus_precursor_digest,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) GetStateTransitionInputs() *StateTransitionInputs { + if x != nil { + return x.StateTransitionInputs + } + return nil +} + +func (x *StateTransitionBlock) GetStateTransitionOutputDigest() []byte { + if x != nil { + return x.StateTransitionOutputDigest + } + return nil +} + +func (x *StateTransitionBlock) GetReportsPlusPrecursorDigest() []byte { + if x != nil { + return x.ReportsPlusPrecursorDigest + } + return nil +} + +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[30] + 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[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 StateTransitionInputs.ProtoReflect.Descriptor instead. +func (*StateTransitionInputs) Descriptor() ([]byte, []int) { + return file_offchainreporting3_1_messages_proto_rawDescGZIP(), []int{30} +} + +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 +} + +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, 0x87, 0x0b, 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, 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, 0xef, 0x02, 0x0a, 0x10, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x64, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x12, 0x63, 0x0a, 0x17, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, + 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 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, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x52, 0x15, 0x73, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 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, 0x02, 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, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x5f, 0x70, + 0x6c, 0x75, 0x73, 0x5f, 0x70, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x5f, 0x64, 0x69, + 0x67, 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x1a, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x73, 0x50, 0x6c, 0x75, 0x73, 0x50, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, + 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 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, 0x04, 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, 0xeb, 0x02, 0x0a, 0x0f, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x63, 0x0a, 0x17, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 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, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x52, 0x15, 0x73, 0x74, 0x61, 0x74, 0x65, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 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, 0x02, 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, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x5f, + 0x70, 0x6c, 0x75, 0x73, 0x5f, 0x70, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x5f, 0x64, + 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x1a, 0x72, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x73, 0x50, 0x6c, 0x75, 0x73, 0x50, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x6f, + 0x72, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 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, 0x04, 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, 0x6a, 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, 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, 0x83, 0x02, 0x0a, 0x14, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x63, 0x0a, 0x17, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 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, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x52, 0x15, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, + 0x73, 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, 0x02, 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, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x73, 0x5f, 0x70, 0x6c, 0x75, 0x73, 0x5f, 0x70, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, + 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x1a, 0x72, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x50, 0x6c, 0x75, 0x73, 0x50, 0x72, 0x65, 0x63, 0x75, 0x72, + 0x73, 0x6f, 0x72, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 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, 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, 31) +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 + (*StateTransitionInputs)(nil), // 30: offchainreporting3_1.StateTransitionInputs +} +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 + 16, // 14: offchainreporting3_1.MessageEpochStartRequest.highest_certified:type_name -> offchainreporting3_1.CertifiedPrepareOrCommit + 22, // 15: offchainreporting3_1.MessageEpochStartRequest.signed_highest_certified_timestamp:type_name -> offchainreporting3_1.SignedHighestCertifiedTimestamp + 15, // 16: offchainreporting3_1.MessageEpochStart.epoch_start_proof:type_name -> offchainreporting3_1.EpochStartProof + 25, // 17: offchainreporting3_1.MessageObservation.signed_observation:type_name -> offchainreporting3_1.SignedObservation + 24, // 18: offchainreporting3_1.MessageProposal.attributed_signed_observations:type_name -> offchainreporting3_1.AttributedSignedObservation + 19, // 19: offchainreporting3_1.MessageCertifiedCommit.certified_committed_reports:type_name -> offchainreporting3_1.CertifiedCommittedReports + 28, // 20: offchainreporting3_1.MessageBlockSync.attested_state_transition_blocks:type_name -> offchainreporting3_1.AttestedStateTransitionBlock + 16, // 21: offchainreporting3_1.EpochStartProof.highest_certified:type_name -> offchainreporting3_1.CertifiedPrepareOrCommit + 21, // 22: offchainreporting3_1.EpochStartProof.highest_certified_proof:type_name -> offchainreporting3_1.AttributedSignedHighestCertifiedTimestamp + 17, // 23: offchainreporting3_1.CertifiedPrepareOrCommit.prepare:type_name -> offchainreporting3_1.CertifiedPrepare + 18, // 24: offchainreporting3_1.CertifiedPrepareOrCommit.commit:type_name -> offchainreporting3_1.CertifiedCommit + 30, // 25: offchainreporting3_1.CertifiedPrepare.state_transition_inputs:type_name -> offchainreporting3_1.StateTransitionInputs + 26, // 26: offchainreporting3_1.CertifiedPrepare.prepare_quorum_certificate:type_name -> offchainreporting3_1.AttributedPrepareSignature + 30, // 27: offchainreporting3_1.CertifiedCommit.state_transition_inputs:type_name -> offchainreporting3_1.StateTransitionInputs + 27, // 28: offchainreporting3_1.CertifiedCommit.commit_quorum_certificate:type_name -> offchainreporting3_1.AttributedCommitSignature + 27, // 29: offchainreporting3_1.CertifiedCommittedReports.commit_quorum_certificate:type_name -> offchainreporting3_1.AttributedCommitSignature + 22, // 30: offchainreporting3_1.AttributedSignedHighestCertifiedTimestamp.signed_highest_certified_timestamp:type_name -> offchainreporting3_1.SignedHighestCertifiedTimestamp + 20, // 31: offchainreporting3_1.SignedHighestCertifiedTimestamp.highest_certified_timestamp:type_name -> offchainreporting3_1.HighestCertifiedTimestamp + 25, // 32: offchainreporting3_1.AttributedSignedObservation.signed_observation:type_name -> offchainreporting3_1.SignedObservation + 29, // 33: offchainreporting3_1.AttestedStateTransitionBlock.state_transition_block:type_name -> offchainreporting3_1.StateTransitionBlock + 27, // 34: offchainreporting3_1.AttestedStateTransitionBlock.attributed_signatures:type_name -> offchainreporting3_1.AttributedCommitSignature + 30, // 35: offchainreporting3_1.StateTransitionBlock.state_transition_inputs:type_name -> offchainreporting3_1.StateTransitionInputs + 23, // 36: offchainreporting3_1.StateTransitionInputs.attributed_observations:type_name -> offchainreporting3_1.AttributedObservation + 37, // [37:37] is the sub-list for method output_type + 37, // [37:37] is the sub-list for method input_type + 37, // [37:37] is the sub-list for extension type_name + 37, // [37:37] is the sub-list for extension extendee + 0, // [0:37] 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.(*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[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), + } + 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: 31, + 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 0000000..3d648d1 --- /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 0000000..d6f7551 --- /dev/null +++ b/offchainreporting2plus/internal/ocr3_1/serialization/serialization.go @@ -0,0 +1,1108 @@ +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) (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} + 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 + fpm := fromProtoMessage[struct{}]{n} + 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 + fpm := fromProtoMessage[struct{}]{n} + 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} + default: + return nil, fmt.Errorf("unable to serialize message of type %T", m) + + } + return &msgWrapper, nil +} + +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 + tpm.stateTransitionInputs(v.StateTransitionInputs), + v.StateTransitionOutputDigest[:], + v.ReportsPlusPrecursorDigest[:], + 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 + tpm.stateTransitionInputs(cpocc.StateTransitionInputs), + cpocc.StateTransitionOutputDigest[:], + cpocc.ReportsPlusPrecursorDigest[:], + 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, + } +} + +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 + tpm.stateTransitionInputs(stb.StateTransitionInputs), + stb.StateTransitionOutputDigest[:], + stb.ReportsPrecursorDigest[:], + } +} + +func (tpm *toProtoMessage[RI]) stateTransitionInputs(parameters protocol.StateTransitionInputs) *StateTransitionInputs { + attributedObservations := make([]*AttributedObservation, 0, len(parameters.AttributedObservations)) + for _, ao := range parameters.AttributedObservations { + attributedObservations = append(attributedObservations, &AttributedObservation{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + ao.Observation, + uint32(ao.Observer), + }) + } + return &StateTransitionInputs{ + // zero-initialize protobuf built-ins + protoimpl.MessageState{}, + 0, + nil, + // fields + parameters.SeqNr, + parameters.Epoch, + parameters.Round, + parameters.Query, + attributedObservations, + } +} + +// +// *fromProtoMessage +// + +type fromProtoMessage[RI any] struct { + n int +} + +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()) + 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") + } + inputs, err := fpm.stateTransitionInputs(m.StateTransitionInputs) + if err != nil { + return protocol.CertifiedPrepare{}, err + } + var outputsDigest protocol.StateTransitionOutputDigest + copy(outputsDigest[:], m.StateTransitionOutputDigest) + var reportsPlusDigest protocol.ReportsPlusPrecursorDigest + copy(reportsPlusDigest[:], m.ReportsPlusPrecursorDigest) + + 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{ + inputs, + outputsDigest, + reportsPlusDigest, + 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") + } + inputs, err := fpm.stateTransitionInputs(m.StateTransitionInputs) + if err != nil { + return protocol.CertifiedCommit{}, err + } + var outputsDigest protocol.StateTransitionOutputDigest + copy(outputsDigest[:], m.StateTransitionOutputDigest) + var reportsPlusDigest protocol.ReportsPlusPrecursorDigest + copy(reportsPlusDigest[:], m.ReportsPlusPrecursorDigest) + + 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{ + inputs, + outputsDigest, + reportsPlusDigest, + 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, + }, 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().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]{ + 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]{ + 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") + } + sti, err := fpm.stateTransitionInputs(m.StateTransitionInputs) + if err != nil { + return protocol.StateTransitionBlock{}, err + } + var outputsDigest protocol.StateTransitionOutputDigest + copy(outputsDigest[:], m.StateTransitionOutputDigest) + var reportsDigest protocol.ReportsPlusPrecursorDigest + copy(reportsDigest[:], m.ReportsPlusPrecursorDigest) + return protocol.StateTransitionBlock{ + sti, + outputsDigest, + reportsDigest, + }, 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]) stateTransitionInputs(m *StateTransitionInputs) (protocol.StateTransitionInputs, error) { + if m == nil { + return protocol.StateTransitionInputs{}, fmt.Errorf("unable to extract an StateTransitionInputs value") + } + aos, err := fpm.attributedObservations(m.AttributedObservations) + if err != nil { + return protocol.StateTransitionInputs{}, err + } + return protocol.StateTransitionInputs{ + m.SeqNr, + m.Epoch, + m.Round, + m.Query, + aos, + }, nil +} + +func (fpm *fromProtoMessage[RI]) attributedObservations(pbaos []*AttributedObservation) ([]types.AttributedObservation, error) { + aos := make([]types.AttributedObservation, 0, len(pbaos)) + for _, pbao := range pbaos { + ao, err := fpm.attributedObservation(pbao) + if err != nil { + return nil, err + } + aos = append(aos, ao) + } + return aos, nil +} + +func (fpm *fromProtoMessage[RI]) attributedObservation(m *AttributedObservation) (types.AttributedObservation, error) { + if m == nil { + return types.AttributedObservation{}, fmt.Errorf("unable to extract an AttributedObservation value") + } + observer, err := fpm.oracleID(m.Observer) + if err != nil { + return types.AttributedObservation{}, err + } + return types.AttributedObservation{ + m.Observation, + observer, + }, nil +} diff --git a/offchainreporting2plus/internal/ocr3_1/serialization/telemetry.go b/offchainreporting2plus/internal/ocr3_1/serialization/telemetry.go new file mode 100644 index 0000000..957a87f --- /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 0000000..b7dd8a6 --- /dev/null +++ b/offchainreporting2plus/internal/shim/ocr3_1_database.go @@ -0,0 +1,128 @@ +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_reporting_plugin.go b/offchainreporting2plus/internal/shim/ocr3_1_reporting_plugin.go new file mode 100644 index 0000000..c30bf97 --- /dev/null +++ b/offchainreporting2plus/internal/shim/ocr3_1_reporting_plugin.go @@ -0,0 +1,92 @@ +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, roundCtx ocr3_1types.RoundContext, kvReader ocr3_1types.KeyValueReader) (types.Query, error) { + query, err := rp.Plugin.Query(ctx, roundCtx, kvReader) + 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, roundContext ocr3_1types.RoundContext, query types.Query, aos []types.AttributedObservation, kvReader ocr3_1types.KeyValueReader) (bool, error) { + return rp.Plugin.ObservationQuorum(ctx, roundContext, query, aos, kvReader) +} + +func (rp LimitCheckOCR3_1ReportingPlugin[RI]) Observation(ctx context.Context, roundCtx ocr3_1types.RoundContext, query types.Query, kvReader ocr3_1types.KeyValueReader) (types.Observation, error) { + observation, err := rp.Plugin.Observation(ctx, roundCtx, query, kvReader) + 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, roundCtx ocr3_1types.RoundContext, query types.Query, ao types.AttributedObservation, kvReader ocr3_1types.KeyValueReader) error { + return rp.Plugin.ValidateObservation(ctx, roundCtx, query, ao, kvReader) +} + +func (rp LimitCheckOCR3_1ReportingPlugin[RI]) StateTransition(ctx context.Context, roundCtx ocr3_1types.RoundContext, query types.Query, aos []types.AttributedObservation, kvReadWriter ocr3_1types.KeyValueReadWriter) (ocr3_1types.ReportsPlusPrecursor, error) { + reportsPlusPrecursor, err := rp.Plugin.StateTransition(ctx, roundCtx, query, aos, kvReadWriter) + 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]) 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 0000000..a8b3910 --- /dev/null +++ b/offchainreporting2plus/internal/shim/ocr3_1_serializing_endpoint.go @@ -0,0 +1,308 @@ +// Package shim contains implementations of internal types in terms of the external types +package shim + +import ( + "fmt" + "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/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 + n, f int + + closeOnce sync.Once + subprocesses subprocesses.Subprocesses + chCancel chan struct{} + chOut chan protocol.MessageWithSender[RI] + taper loghelper.LogarithmicTaper +} + +var _ protocol.NetworkEndpoint[struct{}] = (*OCR3_1SerializingEndpoint[struct{}])(nil) + +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]) serialize(msg protocol.Message[RI]) ([]byte, *serialization.MessageWrapper) { + if !msg.CheckSize(n.n, n.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 + } + sMsg, pbm, err := serialization.Serialize(msg) + if err != nil { + n.logger.Error("OCR3_1SerializingEndpoint: Failed to serialize", commontypes.LogFields{ + "message": msg, + }) + return nil, nil + } + return sMsg, pbm +} + +func (n *OCR3_1SerializingEndpoint[RI]) deserialize(raw []byte, msgPriority types.BinaryMessageOutboundPriority, msgType protocol.MessageType) (protocol.Message[RI], *serialization.MessageWrapper, error) { + m, pbm, err := serialization.Deserialize[RI](n.n, raw) + if err != nil { + return nil, nil, err + } + + if !m.CheckSize(n.n, n.f, n.pluginLimits, n.maxSigLen) { + return nil, nil, fmt.Errorf("message failed size check") + } + + if !m.CheckPriority(msgPriority) { + return nil, nil, fmt.Errorf("message failed priority check") + + } + if !m.CheckMessageType(msgType) { + return nil, nil, fmt.Errorf("message failed message type") + } + + return m, pbm, 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, + n, f int, +) (*OCR3_1SerializingEndpoint[RI], error) { + serializing_endpoint := &OCR3_1SerializingEndpoint[RI]{ + chTelemetry, + configDigest, + endpoint, + maxSigLen, + logger, + newSerializingEndpointMetrics(metricsRegisterer, logger), + pluginLimits, + n, f, + + sync.Once{}, + subprocesses.Subprocesses{}, + make(chan struct{}), + make(chan protocol.MessageWithSender[RI]), + loghelper.LogarithmicTaper{}, + } + serializing_endpoint.subprocesses.Go(serializing_endpoint.run) + return serializing_endpoint, nil +} + +func (n *OCR3_1SerializingEndpoint[RI]) run() { + // we close chOut here rather than in Close() so as to forward the closure + // of the underlyig BinaryNetworkEndpoint2 immediately + defer close(n.chOut) + + chInboundMsgWithSender := n.endpoint.Receive() + for { + select { + case inboundMsgWithSender, ok := <-chInboundMsgWithSender: + if !ok { + return + } + var msgToChOut protocol.Message[RI] + switch msg := inboundMsgWithSender.InboundBinaryMessage.(type) { + case types.InboundBinaryMessagePlain: + m, pbm, err := n.deserialize(msg.Payload, msg.Priority, protocol.MessageTypePlain) + if err != nil { + n.logger.Error("OCR3_1SerializingEndpoint: Failed to deserialize", commontypes.LogFields{ + "error": err, + "type": protocol.MessageTypePlain, + }) + n.sendTelemetryMessageAssertionViolation(msg.Payload, inboundMsgWithSender.Sender) + break + } + msgToChOut = m + n.sendTelemetryMessageReceived(pbm, inboundMsgWithSender.Sender) + case types.InboundBinaryMessageRequest: + m, pbm, err := n.deserialize(msg.Payload, msg.Priority, protocol.MessageTypeRequest) + if err != nil { + n.logger.Error("OCR3_1SerializingEndpoint: Failed to deserialize", commontypes.LogFields{ + "error": err, + "type": protocol.MessageTypeRequest, + }) + n.sendTelemetryMessageAssertionViolation(msg.Payload, inboundMsgWithSender.Sender) + break + } + if _, ok := m.(protocol.SerializableRequestMessage[RI]); !ok { + n.logger.Error("OCR3_1SerializingEndpoint: message type assertion failed", commontypes.LogFields{}) + n.sendTelemetryMessageAssertionViolation(msg.Payload, inboundMsgWithSender.Sender) + break + } + msgToChOut = m.(protocol.SerializableRequestMessage[RI]).NewInboundRequestMessage(msg.RequestHandle) + n.sendTelemetryMessageReceived(pbm, inboundMsgWithSender.Sender) + case types.InboundBinaryMessageResponse: + m, pbm, err := n.deserialize(msg.Payload, msg.Priority, protocol.MessageTypeResponse) + if err != nil { + n.logger.Error("OCR3_1SerializingEndpoint: Failed to deserialize", commontypes.LogFields{ + "error": err, + "type": protocol.MessageTypeResponse, + }) + n.sendTelemetryMessageAssertionViolation(msg.Payload, inboundMsgWithSender.Sender) + break + } + if _, ok := m.(protocol.SerializableResponseMessage[RI]); !ok { + n.logger.Error("OCR3_1SerializingEndpoint: message type assertion failed", commontypes.LogFields{}) + n.sendTelemetryMessageAssertionViolation(msg.Payload, inboundMsgWithSender.Sender) + break + } + msgToChOut = m.(protocol.SerializableResponseMessage[RI]).NewInboundResponseMessage() + n.sendTelemetryMessageReceived(pbm, inboundMsgWithSender.Sender) + default: + panic(fmt.Sprintf("OCR3_1SerializingEndpoint: Unexpected inbound binary message type type %T", msg)) + } + if msgToChOut != nil { + select { + case n.chOut <- protocol.MessageWithSender[RI]{msgToChOut, inboundMsgWithSender.Sender}: + case <-n.chCancel: + return + } + } + case <-n.chCancel: + return + } + } +} + +// Close closes the SerializingEndpoint. It does *not* close the underlying endpoint. +func (n *OCR3_1SerializingEndpoint[RI]) Close() error { + n.closeOnce.Do(func() { + close(n.chCancel) + n.metrics.Close() + n.subprocesses.Wait() + }) + return nil +} + +func (n *OCR3_1SerializingEndpoint[RI]) SendTo(msg protocol.Message[RI], to commontypes.OracleID) { + var sMsg []byte + var pbm *serialization.MessageWrapper + if msg.CheckMessageType(protocol.MessageTypeRequest) { + if _, ok := msg.(protocol.RequestMessage[RI]); !ok { + n.logger.Error("OCR3_1SerializingEndpoint: message type assertion failed", commontypes.LogFields{}) + } + sMsg, pbm = n.serialize(msg.(protocol.RequestMessage[RI]).GetSerializableRequestMessage()) + } else if msg.CheckMessageType(protocol.MessageTypeResponse) { + if _, ok := msg.(protocol.ResponseMessage[RI]); !ok { + n.logger.Error("OCR3_1SerializingEndpoint: message type assertion failed", commontypes.LogFields{}) + } + sMsg, pbm = n.serialize(msg.(protocol.ResponseMessage[RI]).GetSerializableResponseMessage()) + } else if msg.CheckMessageType(protocol.MessageTypePlain) { + sMsg, pbm = n.serialize(msg) + } else { + panic(fmt.Sprintf("OCR3_1SerializingEndpoint: Unexpected message type %T", msg)) + } + if sMsg != nil { + n.endpoint.SendTo(msg.GetOutboundBinaryMessage(sMsg), to) + n.sendTelemetryMessageSent(pbm, sMsg, to) + } +} + +func (n *OCR3_1SerializingEndpoint[RI]) Broadcast(msg protocol.Message[RI]) { + var sMsg []byte + var pbm *serialization.MessageWrapper + if msg.CheckMessageType(protocol.MessageTypeRequest) { + if _, ok := msg.(protocol.RequestMessage[RI]); !ok { + n.logger.Error("OCR3_1SerializingEndpoint: message type assertion failed", commontypes.LogFields{}) + } + sMsg, pbm = n.serialize(msg.(protocol.RequestMessage[RI]).GetSerializableRequestMessage()) + } else if msg.CheckMessageType(protocol.MessageTypeResponse) { + if _, ok := msg.(protocol.ResponseMessage[RI]); !ok { + n.logger.Error("OCR3_1SerializingEndpoint: message type assertion failed", commontypes.LogFields{}) + } + sMsg, pbm = n.serialize(msg.(protocol.ResponseMessage[RI]).GetSerializableResponseMessage()) + } else if msg.CheckMessageType(protocol.MessageTypePlain) { + sMsg, pbm = n.serialize(msg) + } else { + panic(fmt.Sprintf("OCR3_1SerializingEndpoint: Unexpected message type %T", msg)) + } + if sMsg != nil { + n.endpoint.Broadcast(msg.GetOutboundBinaryMessage(sMsg)) + n.sendTelemetryMessageBroadcast(pbm, sMsg) + } +} + +func (n *OCR3_1SerializingEndpoint[RI]) Receive() <-chan protocol.MessageWithSender[RI] { + return n.chOut +} + +func (n *OCR3_1SerializingEndpoint[RI]) sendTelemetryMessageReceived(pbm *serialization.MessageWrapper, from commontypes.OracleID) { + n.sendTelemetry(&serialization.TelemetryWrapper{ + Wrapped: &serialization.TelemetryWrapper_MessageReceived{&serialization.TelemetryMessageReceived{ + ConfigDigest: n.configDigest[:], + Msg: pbm, + Sender: uint32(from), + }}, + UnixTimeNanoseconds: time.Now().UnixNano(), + }) +} + +func (n *OCR3_1SerializingEndpoint[RI]) sendTelemetryMessageBroadcast(pbm *serialization.MessageWrapper, sMsg []byte) { + n.sendTelemetry(&serialization.TelemetryWrapper{ + Wrapped: &serialization.TelemetryWrapper_MessageBroadcast{&serialization.TelemetryMessageBroadcast{ + ConfigDigest: n.configDigest[:], + Msg: pbm, + SerializedMsg: sMsg, + }}, + UnixTimeNanoseconds: time.Now().UnixNano(), + }) +} + +func (n *OCR3_1SerializingEndpoint[RI]) sendTelemetryMessageSent(pbm *serialization.MessageWrapper, sMsg []byte, to commontypes.OracleID) { + n.sendTelemetry(&serialization.TelemetryWrapper{ + Wrapped: &serialization.TelemetryWrapper_MessageSent{&serialization.TelemetryMessageSent{ + ConfigDigest: n.configDigest[:], + Msg: pbm, + SerializedMsg: sMsg, + Receiver: uint32(to), + }}, + UnixTimeNanoseconds: time.Now().UnixNano(), + }) +} + +func (n *OCR3_1SerializingEndpoint[RI]) sendTelemetryMessageAssertionViolation(sMsg []byte, from commontypes.OracleID) { + n.sendTelemetry(&serialization.TelemetryWrapper{ + Wrapped: &serialization.TelemetryWrapper_AssertionViolation{&serialization.TelemetryAssertionViolation{ + Violation: &serialization.TelemetryAssertionViolation_InvalidSerialization{&serialization.TelemetryAssertionViolationInvalidSerialization{ + ConfigDigest: n.configDigest[:], + SerializedMsg: sMsg, + Sender: uint32(from), + }}, + }}, + UnixTimeNanoseconds: time.Now().UnixNano(), + }) +} 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 0000000..740a7bc --- /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/db.go b/offchainreporting2plus/ocr3_1types/db.go new file mode 100644 index 0000000..a7c103a --- /dev/null +++ b/offchainreporting2plus/ocr3_1types/db.go @@ -0,0 +1,34 @@ +package ocr3_1types + +import ( + "context" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type Database interface { + ocr3types.Database + BlockDatabase +} + +type BlockNotFoundError string + +func (e BlockNotFoundError) Error() string { + return string(e) +} + +const ErrBlockNotFound BlockNotFoundError = "block not found" + +// 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/plugin.go b/offchainreporting2plus/ocr3_1types/plugin.go new file mode 100644 index 0000000..570a03b --- /dev/null +++ b/offchainreporting2plus/ocr3_1types/plugin.go @@ -0,0 +1,242 @@ +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) (ReportingPlugin[RI], ReportingPluginInfo, error) +} + +type RoundContext struct { + // SeqNr of an OCR3 round/outcome. This is guaranteed to increase + // in increments of one, i.e. for each SeqNr exactly one Outcome will + // be generated. + // The initial SeqNr value is 1. Its PreviousOutcome is nil. + SeqNr uint64 + + // Deprecated: exposed for legacy compatibility, do not rely on this + // unless you have a really good reason. + Epoch uint64 + // Deprecated: exposed for legacy compatibility, do not rely on this + // unless you have a really good reason. + Round uint64 +} + +// 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 roundCtx.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 RoundContext.SeqNr - 1 is committed. + Query(ctx context.Context, roundCtx RoundContext, keyValueReader KeyValueReader) (types.Query, error) + + // Observation gets an observation from the underlying data source. Returns + // a value or an error. + // + // You may assume that the roundCtx.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 RoundContext.SeqNr - 1 is committed. + Observation(ctx context.Context, roundCtx RoundContext, query types.Query, keyValueReader KeyValueReader) (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 roundCtx.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 RoundContext.SeqNr - 1 is committed. + ValidateObservation(ctx context.Context, roundCtx RoundContext, query types.Query, ao types.AttributedObservation, keyValueReader KeyValueReader) 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 RoundContext.SeqNr - 1 is committed. + ObservationQuorum(ctx context.Context, roundCtx RoundContext, query types.Query, aos []types.AttributedObservation, keyValueReader KeyValueReader) (quorumReached bool, err error) + + // StateTransition modifies the state of the Reporting Plugin, typically based + // the 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 roundCtx.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 RoundContext.SeqNr - 1 is committed. + StateTransition(ctx context.Context, roundCtx RoundContext, query types.Query, aos []types.AttributedObservation, keyValueReadWriter KeyValueReadWriter) (ReportsPlusPrecursor, 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 = 5 * mib + MaxMaxObservationLength = 1 * mib + MaxMaxReportsPlusPrecursorLength = 5 * mib + MaxMaxReportLength = 5 * mib + MaxMaxReportCount = 2000 +) + +// 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 +} + +type ReportingPluginInfo struct { + // Used for debugging purposes. + Name string + + Limits ReportingPluginLimits +} diff --git a/offchainreporting2plus/ocr3_1types/types.go b/offchainreporting2plus/ocr3_1types/types.go new file mode 100644 index 0000000..d695404 --- /dev/null +++ b/offchainreporting2plus/ocr3_1types/types.go @@ -0,0 +1,88 @@ +package ocr3_1types + +import ( + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type KeyNotFoundError string + +func (e KeyNotFoundError) Error() string { + return string(e) +} + +const ErrKeyNotFound KeyNotFoundError = "key not found" + +type DuplicateTransactionError string + +func (e DuplicateTransactionError) Error() string { + return string(e) +} + +const ErrDuplicateTransaction DuplicateTransactionError = "a kv stored transaction is already open" + +type KeyValueReader interface { + // Read returns the value of the given key. If the key is not found, ErrKeyNotFound should be returned. + Read(key []byte) ([]byte, error) + + // The addition of the following methods is still being evaluated + + // ReadRange returns values for keys in the range [key1, key2] + //ReadRange(key1 []byte, key2 []byte) ([][]byte, error) + // KeyRange returns the keys in the range [key1, key2] + //KeyRange(key1 []byte, key2 []byte) ([][]byte, error) + // ReadBatch returns the values for the specified keys. If some key is not found ErrKeyNotFound should be returned. + //ReadBatch(keys [][]byte) ([][]byte, error) + + // Returns the maximum allowed key size + //MaxKeySize() int + // Returns the maximum allowed value size + //MaxValueSize() int +} + +type KeyValueReaderDiscardable interface { + KeyValueReader + Discard() +} + +type KeyValueWriter interface { + // Write sets a value to the given key. Creates a key if the key does not already exist. + Write(key []byte, value []byte) error + // Delete deletes the key. + Delete(key []byte) error +} + +type KeyValueReadWriter interface { + KeyValueReader + KeyValueWriter +} + +type KeyValuePair struct { + Key []byte + Value []byte +} + +type KeyValueStoreTransaction interface { + // SeqNr returns the sequence number of the OCR3 round for which this transaction was created + SeqNr() uint64 + // GetReadWriter returns a KeyValueReadWriter implementation for this transaction + GetReadWriter() (KeyValueReadWriter, error) + // 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 + // Commit commits the transaction to the key value store and then discards the transaction. + Commit() error + // Discard discards the transaction. + Discard() +} + +// We assume that all reads and writes in a transaction commit atomically +type KeyValueStore interface { + NewTransaction(seqNr uint64) (KeyValueStoreTransaction, error) + GetReader() KeyValueReaderDiscardable + HighestCommittedSeqNr() (uint64, error) + Close() error +} + +type KeyValueStoreFactory interface { + NewKeyValueStore(configDigest types.ConfigDigest) (KeyValueStore, error) +} diff --git a/offchainreporting2plus/oracle.go b/offchainreporting2plus/oracle.go index 92f6a33..d0d3b0e 100644 --- a/offchainreporting2plus/oracle.go +++ b/offchainreporting2plus/oracle.go @@ -3,6 +3,7 @@ package offchainreporting2plus import ( "context" "fmt" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types" "sync" "github.com/prometheus/client_golang/prometheus" @@ -243,6 +244,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 + + // KeyValueStoreFactory produces KeyValueStores for keeping the reporting plugins' state consistently across oracles. + KeyValueStoreFactory ocr3_1types.KeyValueStoreFactory + + // 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.KeyValueStoreFactory, + 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 0000000..ee2d499 --- /dev/null +++ b/offchainreporting2plus/types/network.go @@ -0,0 +1,124 @@ +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() +} + +var _ OutboundBinaryMessage = OutboundBinaryMessagePlain{} +var _ OutboundBinaryMessage = OutboundBinaryMessageRequest{} +var _ OutboundBinaryMessage = OutboundBinaryMessageResponse{} + +type OutboundBinaryMessagePlain struct { + Payload []byte + Priority BinaryMessageOutboundPriority +} + +func (OutboundBinaryMessagePlain) isOutboundBinaryMessage() {} + +type OutboundBinaryMessageRequest struct { + ResponsePolicy ResponsePolicy + Payload []byte + Priority BinaryMessageOutboundPriority +} + +func (OutboundBinaryMessageRequest) isOutboundBinaryMessage() {} + +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() {} + +type InboundBinaryMessage interface { + isInboundBinaryMessage() +} + +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() {} + +type InboundBinaryMessageRequest struct { + RequestHandle RequestHandle + Payload []byte + Priority BinaryMessageOutboundPriority // the priority the sender used for transmitting this request +} + +func (InboundBinaryMessageRequest) isInboundBinaryMessage() {} + +type InboundBinaryMessageResponse struct { + Payload []byte + Priority BinaryMessageOutboundPriority // the priority the sender used for transmitting this response +} + +func (InboundBinaryMessageResponse) isInboundBinaryMessage() {} + +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 96b061e..e0552b8 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. diff --git a/ragep2p/conn_rate_limiter.go b/ragep2p/conn_rate_limiter.go index 86e2164..420aef2 100644 --- a/ragep2p/conn_rate_limiter.go +++ b/ragep2p/conn_rate_limiter.go @@ -128,14 +128,14 @@ func (crl *connRateLimiter) addRemoveStream(add bool, messagesLimit TokenBucketP func delta(messagesLimit TokenBucketParams, bytesLimit TokenBucketParams) (deltaRate ratelimit.MillitokensPerSecond, deltaCapacity uint32, ok bool) { var deltaRateF float64 - deltaRateF = messagesLimit.Rate * frameHeaderEncodedSize + deltaRateF = messagesLimit.Rate * maxFrameHeaderSize deltaRateF += bytesLimit.Rate deltaRateF *= tlsFactor deltaRateF = math.Ceil(deltaRateF * 1000) deltaRate = ratelimit.MillitokensPerSecond(deltaRateF) var deltaCapacityF float64 - deltaCapacityF = float64(messagesLimit.Capacity) * frameHeaderEncodedSize + deltaCapacityF = float64(messagesLimit.Capacity) * maxFrameHeaderSize deltaCapacityF += float64(bytesLimit.Capacity) deltaCapacityF *= tlsFactor deltaCapacityF = math.Ceil(deltaCapacityF) diff --git a/ragep2p/demuxer.go b/ragep2p/demuxer.go index 806e31e..7f27747 100644 --- a/ragep2p/demuxer.go +++ b/ragep2p/demuxer.go @@ -1,11 +1,13 @@ package ragep2p import ( + "fmt" "math" "sync" - "github.com/smartcontractkit/libocr/ragep2p/internal/msgbuf" + "github.com/smartcontractkit/libocr/internal/ringbuffer" "github.com/smartcontractkit/libocr/ragep2p/internal/ratelimit" + "github.com/smartcontractkit/libocr/ragep2p/internal/responselimit" ) type shouldPushResult int @@ -17,6 +19,7 @@ const ( shouldPushResultMessagesLimitExceeded shouldPushResultBytesLimitExceeded shouldPushResultUnknownStream + shouldPushResultResponseRejected ) type pushResult int @@ -38,7 +41,7 @@ const ( ) type demuxerStream struct { - buffer *msgbuf.MessageBuffer + buffer *ringbuffer.RingBuffer[InboundBinaryMessage] chSignal chan struct{} maxMessageSize int messagesLimiter ratelimit.TokenBucket @@ -46,14 +49,16 @@ type demuxerStream struct { } type demuxer struct { - mutex sync.Mutex - streams map[streamID]*demuxerStream + mutex sync.Mutex + streams map[streamID]*demuxerStream + responseChecker *responselimit.ResponseChecker } func newDemuxer() *demuxer { return &demuxer{ sync.Mutex{}, map[streamID]*demuxerStream{}, + responselimit.NewResponseChecker(), } } @@ -80,7 +85,7 @@ func (d *demuxer) AddStream( } d.streams[sid] = &demuxerStream{ - msgbuf.NewMessageBuffer(incomingBufferSize), + ringbuffer.NewRingBuffer[InboundBinaryMessage](incomingBufferSize), make(chan struct{}, 1), maxMessageSize, makeRateLimiter(messagesLimit), @@ -94,6 +99,7 @@ func (d *demuxer) RemoveStream(sid streamID) { defer d.mutex.Unlock() delete(d.streams, sid) + d.responseChecker.ClearPoliciesForStream(sid) } func (d *demuxer) ShouldPush(sid streamID, size int) shouldPushResult { @@ -123,7 +129,28 @@ func (d *demuxer) ShouldPush(sid streamID, size int) shouldPushResult { return shouldPushResultYes } -func (d *demuxer) PushMessage(sid streamID, msg []byte) pushResult { +func (d *demuxer) ShouldPushResponse(sid streamID, rid requestID, size int) shouldPushResult { + d.mutex.Lock() + defer d.mutex.Unlock() + + _, ok := d.streams[sid] + if !ok { + return shouldPushResultUnknownStream + } + + checkResult := d.responseChecker.CheckResponse(sid, rid, size) + switch checkResult { + case responselimit.ResponseCheckResultReject: + return shouldPushResultResponseRejected + case responselimit.ResponseCheckResultAllow: + return shouldPushResultYes + } + + // The above switch should be exhaustive. If it is not the linter is expected to catch this. + panic(fmt.Sprintf("unexpected ragep2p.responseCheckResult: %#v", checkResult)) +} + +func (d *demuxer) PushMessage(sid streamID, msg InboundBinaryMessage) pushResult { d.mutex.Lock() defer d.mutex.Unlock() @@ -132,10 +159,8 @@ func (d *demuxer) PushMessage(sid streamID, msg []byte) pushResult { return pushResultUnknownStream } - var result pushResult - if s.buffer.Push(msg) == nil { - result = pushResultSuccess - } else { + result := pushResultSuccess + if _, evicted := s.buffer.PushEvict(msg); evicted { result = pushResultDropped } @@ -149,7 +174,7 @@ func (d *demuxer) PushMessage(sid streamID, msg []byte) pushResult { // Pops a message from the underlying stream's buffer. // Returns a non-nil value iff popResult == popResultSuccess. -func (d *demuxer) PopMessage(sid streamID) ([]byte, popResult) { +func (d *demuxer) PopMessage(sid streamID) (InboundBinaryMessage, popResult) { d.mutex.Lock() defer d.mutex.Unlock() @@ -158,12 +183,12 @@ func (d *demuxer) PopMessage(sid streamID) ([]byte, popResult) { return nil, popResultUnknownStream } - result := s.buffer.Pop() - if result == nil { + result, ok := s.buffer.Pop() + if !ok { return nil, popResultEmpty } - if s.buffer.Peek() != nil { + if !s.buffer.IsEmpty() { select { case s.chSignal <- struct{}{}: default: diff --git a/ragep2p/frame.go b/ragep2p/frame.go index 7eaae58..699a8dc 100644 --- a/ragep2p/frame.go +++ b/ragep2p/frame.go @@ -2,61 +2,274 @@ package ragep2p import ( "bytes" + "crypto/rand" "crypto/sha256" "encoding/binary" "fmt" "github.com/smartcontractkit/libocr/ragep2p/types" + + internal_types "github.com/smartcontractkit/libocr/ragep2p/internal/types" +) + +const ( + streamIDSize = internal_types.StreamIDSize + requestIDSize = internal_types.RequestIDSize ) -var errWrongLength = fmt.Errorf("frameHeader must have exactly %v bytes", frameHeaderEncodedSize) -var errUnknownFrameType = fmt.Errorf("frameHeader has unknown frameType") +var errMaxMessageSizeExceeded = fmt.Errorf("the message size must not exceed %v bytes", MaxMessageLength) +var errOpenStreamPayloadSizeExceeded = fmt.Errorf( + "the payload size specified in a 'OpenStream' frame header must be at most %v bytes", + MaxStreamNameLength, +) +var errCloseStreamPayloadSizeInvalid = fmt.Errorf("the payload size specified in a 'CloseStream' frame header must be 0") +var errFrameHeaderSizeInvalid = fmt.Errorf("frame decoding error, invalid header length") +var errInvalidFrameType = fmt.Errorf("frame decoding error, invalid frame type") -type frameType uint8 +type frameType byte const ( _ frameType = iota - frameTypeOpen - frameTypeClose - frameTypeData + frameTypeOpenStream + frameTypeCloseStream + frameTypeMessage + frameTypeRequest + frameTypeResponse ) -type frameHeader struct { - Type frameType - StreamID streamID - PayloadLength uint32 +type frameHeader interface { + Type() frameType + Encode() []byte + PayloadSize() int + StreamID() streamID +} + +const ( + baseFrameHeaderSize = 1 + 32 + 4 + openStreamFrameHeaderSize = baseFrameHeaderSize + closeStreamFrameHeaderSize = baseFrameHeaderSize + messageFrameHeaderSize = baseFrameHeaderSize + requestFrameHeaderSize = baseFrameHeaderSize + requestIDSize + responseFrameHeaderSize = baseFrameHeaderSize + requestIDSize + maxFrameHeaderSize = baseFrameHeaderSize + requestIDSize // whatever value is the largest header size from above +) + +var frameHeaderSizes = map[frameType]int{ + frameTypeOpenStream: openStreamFrameHeaderSize, + frameTypeCloseStream: closeStreamFrameHeaderSize, + frameTypeMessage: messageFrameHeaderSize, + frameTypeRequest: requestFrameHeaderSize, + frameTypeResponse: responseFrameHeaderSize, +} + +// The different frame header types must be wire-compatible with the previously used frame header structure. +// See encodeBaseFrameHeader(...) and decodeBaseFrameHeader(). +// +// type frameHeader struct { +// Type frameType +// StreamID streamID +// PayloadSize uint32 +// } + +type openStreamFrameHeader struct { + streamID streamID + payloadSize int +} + +type closeStreamFrameHeader struct { + streamID streamID +} + +type messageFrameHeader struct { + streamID streamID + payloadSize int +} + +type requestFrameHeader struct { + streamID streamID + payloadSize int + requestID requestID +} + +type responseFrameHeader struct { + streamID streamID + payloadSize int + requestID requestID } -const frameHeaderEncodedSize = 1 + 32 + 4 +func (_ openStreamFrameHeader) Type() frameType { + return frameTypeOpenStream +} +func (_ closeStreamFrameHeader) Type() frameType { + return frameTypeCloseStream +} +func (_ messageFrameHeader) Type() frameType { + return frameTypeMessage +} +func (_ requestFrameHeader) Type() frameType { + return frameTypeRequest +} +func (_ responseFrameHeader) Type() frameType { + return frameTypeResponse +} + +func (h openStreamFrameHeader) PayloadSize() int { + return h.payloadSize +} +func (h closeStreamFrameHeader) PayloadSize() int { + return 0 +} +func (h messageFrameHeader) PayloadSize() int { + return h.payloadSize +} +func (h requestFrameHeader) PayloadSize() int { + return h.payloadSize +} +func (h responseFrameHeader) PayloadSize() int { + return h.payloadSize +} -func (fh frameHeader) Encode() []byte { - buf := bytes.NewBuffer(make([]byte, 0, frameHeaderEncodedSize)) - buf.WriteByte(byte(fh.Type)) - buf.Write(fh.StreamID[:]) - binary.Write(buf, binary.BigEndian, fh.PayloadLength) //nolint:errcheck - return buf.Bytes() +func (h openStreamFrameHeader) StreamID() streamID { + return h.streamID +} +func (h closeStreamFrameHeader) StreamID() streamID { + return h.streamID +} +func (h messageFrameHeader) StreamID() streamID { + return h.streamID +} +func (h requestFrameHeader) StreamID() streamID { + return h.streamID +} +func (h responseFrameHeader) StreamID() streamID { + return h.streamID +} + +func encodeBaseFrameHeader(frameType frameType, streamID streamID, payloadSize int, extraBufferCapacity int) []byte { + buffer := make([]byte, 0, baseFrameHeaderSize+extraBufferCapacity) + buffer = append(buffer, byte(frameType)) + buffer = append(buffer, streamID[:]...) + buffer = binary.BigEndian.AppendUint32(buffer, uint32(payloadSize)) + return buffer +} + +func decodeBaseFrameHeader(encoded []byte, expectedType frameType, expectedSize int) (streamID, int, error) { + var streamID streamID + + if len(encoded) != expectedSize { + return streamID, 0, errFrameHeaderSizeInvalid + } + if frameType(encoded[0]) != expectedType { + return streamID, 0, errInvalidFrameType + } + + payloadSize := binary.BigEndian.Uint32(encoded[1+streamIDSize:]) + if payloadSize > MaxMessageLength { + return streamID, 0, errMaxMessageSizeExceeded + } + + copy(streamID[:], encoded[1:streamIDSize+1]) + + return streamID, int(payloadSize), nil +} + +func (h openStreamFrameHeader) Encode() []byte { + return encodeBaseFrameHeader(frameTypeOpenStream, h.streamID, h.payloadSize, 0) +} + +func (h closeStreamFrameHeader) Encode() []byte { + return encodeBaseFrameHeader(frameTypeCloseStream, h.streamID, 0, 0) +} + +func (h messageFrameHeader) Encode() []byte { + return encodeBaseFrameHeader(frameTypeMessage, h.streamID, h.payloadSize, 0) +} + +func (h requestFrameHeader) Encode() []byte { + buffer := encodeBaseFrameHeader(frameTypeRequest, h.streamID, h.payloadSize, requestIDSize) + buffer = append(buffer, h.requestID[:]...) + return buffer +} + +func (h responseFrameHeader) Encode() []byte { + buffer := encodeBaseFrameHeader(frameTypeResponse, h.streamID, h.payloadSize, requestIDSize) + buffer = append(buffer, h.requestID[:]...) + return buffer } func decodeFrameHeader(encoded []byte) (frameHeader, error) { - if len(encoded) != frameHeaderEncodedSize { - return frameHeader{}, errWrongLength + if len(encoded) == 0 { + return nil, errFrameHeaderSizeInvalid } - typ := frameType(encoded[0]) - switch typ { - case frameTypeOpen: - case frameTypeClose: - case frameTypeData: + + switch frameType(encoded[0]) { + case frameTypeOpenStream: + return decodeOpenStreamFrameHeader(encoded) + case frameTypeCloseStream: + return decodeCloseStreamFrameHeader(encoded) + case frameTypeMessage: + return decodeMessageFrameHeader(encoded) + case frameTypeRequest: + return decodeRequestFrameHeader(encoded) + case frameTypeResponse: + return decodeResponseFrameHeader(encoded) default: - return frameHeader{}, errUnknownFrameType + return nil, errInvalidFrameType } - var streamId streamID - copy(streamId[:], encoded[1:33]) - payloadLength := binary.BigEndian.Uint32(encoded[33:frameHeaderEncodedSize]) - return frameHeader{ - typ, - streamId, - payloadLength, - }, nil +} + +func decodeOpenStreamFrameHeader(encoded []byte) (openStreamFrameHeader, error) { + streamID, payloadSize, err := decodeBaseFrameHeader(encoded, frameTypeOpenStream, openStreamFrameHeaderSize) + if err != nil { + return openStreamFrameHeader{}, err + } + if payloadSize > MaxStreamNameLength { + return openStreamFrameHeader{}, errOpenStreamPayloadSizeExceeded + } + + return openStreamFrameHeader{streamID, payloadSize}, nil +} + +func decodeCloseStreamFrameHeader(encoded []byte) (closeStreamFrameHeader, error) { + streamID, payloadSize, err := decodeBaseFrameHeader(encoded, frameTypeCloseStream, closeStreamFrameHeaderSize) + if err != nil { + return closeStreamFrameHeader{}, err + } + if payloadSize != 0 { + return closeStreamFrameHeader{}, errCloseStreamPayloadSizeInvalid + } + + return closeStreamFrameHeader{streamID}, nil +} + +func decodeMessageFrameHeader(encoded []byte) (messageFrameHeader, error) { + streamID, payloadSize, err := decodeBaseFrameHeader(encoded, frameTypeMessage, messageFrameHeaderSize) + if err != nil { + return messageFrameHeader{}, err + } + return messageFrameHeader{streamID, payloadSize}, err +} + +func decodeRequestFrameHeader(encoded []byte) (requestFrameHeader, error) { + var requestID requestID + streamID, payloadSize, err := decodeBaseFrameHeader(encoded, frameTypeRequest, requestFrameHeaderSize) + if err != nil { + return requestFrameHeader{}, err + } + + copy(requestID[:], encoded[baseFrameHeaderSize:]) + return requestFrameHeader{streamID, payloadSize, requestID}, nil +} + +func decodeResponseFrameHeader(encoded []byte) (responseFrameHeader, error) { + var requestID requestID + streamID, payloadSize, err := decodeBaseFrameHeader(encoded, frameTypeResponse, responseFrameHeaderSize) + if err != nil { + return responseFrameHeader{}, err + } + + copy(requestID[:], encoded[baseFrameHeaderSize:]) + return responseFrameHeader{streamID, payloadSize, requestID}, nil } func getStreamID(self types.PeerID, other types.PeerID, name string) streamID { @@ -76,3 +289,15 @@ func getStreamID(self types.PeerID, other types.PeerID, name string) streamID { copy(result[:], h.Sum(nil)) return result } + +func getRandomRequestID() (requestID, error) { + requestID := requestID{} + _, err := rand.Read(requestID[:]) + return requestID, err +} + +func getRandomStreamID() (streamID, error) { + streamID := streamID{} + _, err := rand.Read(streamID[:]) + return streamID, err +} diff --git a/ragep2p/internal/msgbuf/ringbuffer.go b/ragep2p/internal/msgbuf/ringbuffer.go deleted file mode 100644 index 93a9108..0000000 --- a/ragep2p/internal/msgbuf/ringbuffer.go +++ /dev/null @@ -1,55 +0,0 @@ -package msgbuf - -// MessageBuffer implements a fixed capacity ringbuffer for items of type -// []byte. -type MessageBuffer struct { - start int - length int - buffer [][]byte -} - -func NewMessageBuffer(cap int) *MessageBuffer { - return &MessageBuffer{ - 0, - 0, - make([][]byte, cap), - } -} - -// Peek at the front item -func (rb *MessageBuffer) Peek() []byte { - if rb.length == 0 { - return nil - } else { - return rb.buffer[rb.start] - } -} - -// Pop front item -func (rb *MessageBuffer) Pop() []byte { - result := rb.Peek() - if result != nil { - rb.buffer[rb.start] = nil - rb.start = (rb.start + 1) % len(rb.buffer) - rb.length-- - } - return result -} - -// Push new item to back. If the additional item would lead to the capacity -// being exceeded, remove the front item first. -// -// Returns the removed front item, or nil. -func (rb *MessageBuffer) Push(msg []byte) []byte { - var result []byte - - if msg == nil { - panic("cannot push nil") - } - if rb.length == len(rb.buffer) { - result = rb.Pop() - } - rb.buffer[(rb.start+rb.length)%len(rb.buffer)] = msg - rb.length++ - return result -} diff --git a/ragep2p/internal/ratelimitedconn/rate_limited_conn.go b/ragep2p/internal/ratelimitedconn/rate_limited_conn.go index 52a2dea..e317800 100644 --- a/ragep2p/internal/ratelimitedconn/rate_limited_conn.go +++ b/ragep2p/internal/ratelimitedconn/rate_limited_conn.go @@ -20,6 +20,7 @@ type RateLimitedConn struct { readBytesTotal prometheus.Counter writtenBytesTotal prometheus.Counter rateLimitingEnabled bool + transientCapacity int } var _ net.Conn = (*RateLimitedConn)(nil) @@ -38,6 +39,7 @@ func NewRateLimitedConn( readBytesTotal, writtenBytesTotal, false, + 0, } } @@ -46,19 +48,68 @@ func (r *RateLimitedConn) EnableRateLimiting() { r.rateLimitingEnabled = true } +// Allow a message of the specified size to be read during the subsequent calls to Read(...). The caller should specify +// the message size directly, and may use addTLSOverhead=true to account for TLS overhead. After fully reading the +// message, ClearTransientCapacity() must be called. +func (r *RateLimitedConn) AllowTransientCapacity(expectedPayloadSize int, addTLSOverhead bool) { + r.transientCapacity = expectedPayloadSize + if addTLSOverhead { + // In principle, TLS 1.3 uses record of size 16 KiB (when transmitting sufficiently large messages). + // Per record, we have an overhead of 21 bytes. + // + // Lets be a bit more generous here and assume: + // - a max record size of 1400 byte which does fit into all MTUs, and + // - a per record overhead of 64 bytes. + // + // This comes out to: 64/1400 < 5% (= 1/20). + + r.transientCapacity += max(64, expectedPayloadSize/20) + } +} + +func (r *RateLimitedConn) ClearTransientCapacity() { + r.transientCapacity = 0 +} + func (r *RateLimitedConn) Read(b []byte) (n int, err error) { - n, err = r.Conn.Read(b) - r.readBytesTotal.Add(float64(n)) if !r.rateLimitingEnabled { + n, err = r.Conn.Read(b) + r.readBytesTotal.Add(float64(n)) return n, err } + // Check if we have (remaining) transient capacity. + if r.transientCapacity > 0 { + // If so, we are processing a response. + // We read up to the number of remaining transient capacity bytes from the underlying connection for that + // response and deduct the number of bytes read from the transient capacity. + + // Setting a upper bound (i.e., r.transientCapacity) for the number of bytes to read, and not using the + // internally [and dynamically adjusted] TLS buffer size len(b), is important to ensure responses fit within the + // calculated transient capacity. Without that consideration, extra available data (non-belong to the response) + // could be wrongfully counted towards the responses size due to the way this Read() function is called from + // the TLS code - wrongfully exceeding the rate limit set for the response (if the normal bandwidth limiter's + // capacity is close to zero.) + numBytesToRead := min(r.transientCapacity, len(b)) + + n, err = r.Conn.Read(b[:numBytesToRead]) + r.readBytesTotal.Add(float64(n)) + r.transientCapacity -= n + return n, err + } + + // If we have no (remaining) transient capacity, we apply the normal rate limits. + + n, err = r.Conn.Read(b) + r.readBytesTotal.Add(float64(n)) nBytesAllowed := r.bandwidthLimiter.Allow(n) if nBytesAllowed { return n, err } + // kill the conn: close it and emit an error _ = r.Conn.Close() // ignore error, there's not much we can with it here + // TODO: log the limits here r.logger.Error("inbound data exceeded rate limit, connection closed", commontypes.LogFields{ // "tokenBucketRefillRate": r.bandwidthLimiter.Limit(), @@ -66,9 +117,56 @@ func (r *RateLimitedConn) Read(b []byte) (n int, err error) { "bytesRead": n, "readError": err, // This error may not be null, we're adding it here to not miss it. }) + return 0, fmt.Errorf("inbound data exceeded rate limit, connection closed") } +// func (r *RateLimitedConn) Read(b []byte) (n int, err error) { +// n, err = r.Conn.Read(b) +// r.readBytesTotal.Add(float64(n)) +// if !r.rateLimitingEnabled { +// return n, err +// } + +// // Hold the number of bytes read minus any potentially deducted allowance from the transient capacity. +// // We use a separate variable for this and keep the number of read bytes unmodified in `n`. +// nd := n + +// // Check if a transient capacity was set. +// if r.transientCapacity != 0 { +// // If so, we use it, but we have to distinguish two cases: either we have enough capacity left or not. +// // Handle case 1: The (remaining) transient capacity is sufficient, so we deduct the number of read bytes from +// // it and return immediately. +// if n <= r.transientCapacity { +// r.transientCapacity -= n +// return n, err +// } + +// // Handle case 2: The (remaining) transient capacity is not sufficient, so we use up all the remaining capacity, +// // and fallthrough to apply the normal bandwidth limiter for the remaining number of read bytes. +// nd = n - r.transientCapacity +// r.transientCapacity = 0 +// } + +// ndBytesAllowed := r.bandwidthLimiter.Allow(nd) +// if ndBytesAllowed { +// return n, err +// } + +// // kill the conn: close it and emit an error +// _ = r.Conn.Close() // ignore error, there's not much we can with it here + +// // TODO: log the limits here +// r.logger.Error("inbound data exceeded rate limit, connection closed", commontypes.LogFields{ +// // "tokenBucketRefillRate": r.bandwidthLimiter.Limit(), +// // "tokenBucketSize": r.bandwidthLimiter.Burst(), +// "bytesRead": n, +// "readError": err, // This error may not be null, we're adding it here to not miss it. +// }) + +// return 0, fmt.Errorf("inbound data exceeded rate limit, connection closed") +// } + func (r *RateLimitedConn) Write(b []byte) (n int, err error) { n, err = r.Conn.Write(b) r.writtenBytesTotal.Add(float64(n)) diff --git a/ragep2p/internal/responselimit/checker.go b/ragep2p/internal/responselimit/checker.go new file mode 100644 index 0000000..0602280 --- /dev/null +++ b/ragep2p/internal/responselimit/checker.go @@ -0,0 +1,180 @@ +package responselimit + +import ( + "math/rand" + "sync" + "time" + + "github.com/smartcontractkit/libocr/ragep2p/internal/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/ragep2p/internal/responselimit/policies.go b/ragep2p/internal/responselimit/policies.go new file mode 100644 index 0000000..aac0885 --- /dev/null +++ b/ragep2p/internal/responselimit/policies.go @@ -0,0 +1,55 @@ +package responselimit + +import ( + "time" + + "github.com/smartcontractkit/libocr/ragep2p/internal/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/ragep2p/internal/types/types.go b/ragep2p/internal/types/types.go new file mode 100644 index 0000000..b371320 --- /dev/null +++ b/ragep2p/internal/types/types.go @@ -0,0 +1,25 @@ +package types + +import ( + "encoding/hex" + "fmt" +) + +const ( + StreamIDSize = 32 + RequestIDSize = 32 +) + +type StreamID [StreamIDSize]byte +type RequestID [RequestIDSize]byte + +var _ fmt.Stringer = StreamID{} +var _ fmt.Stringer = RequestID{} + +func (s StreamID) String() string { + return hex.EncodeToString(s[:]) +} + +func (r RequestID) String() string { + return hex.EncodeToString(r[:]) +} diff --git a/ragep2p/ragep2p.go b/ragep2p/ragep2p.go index 1a7dc08..09828cb 100644 --- a/ragep2p/ragep2p.go +++ b/ragep2p/ragep2p.go @@ -4,7 +4,6 @@ import ( "context" "crypto/ed25519" "crypto/tls" - "encoding/hex" "fmt" "io" "math/rand" @@ -18,12 +17,14 @@ import ( "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/internal/loghelper" "github.com/smartcontractkit/libocr/ragep2p/internal/knock" - "github.com/smartcontractkit/libocr/ragep2p/internal/msgbuf" "github.com/smartcontractkit/libocr/ragep2p/internal/mtls" "github.com/smartcontractkit/libocr/ragep2p/internal/ratelimit" "github.com/smartcontractkit/libocr/ragep2p/internal/ratelimitedconn" + "github.com/smartcontractkit/libocr/ragep2p/types" "github.com/smartcontractkit/libocr/subprocesses" + + internal_types "github.com/smartcontractkit/libocr/ragep2p/internal/types" ) // Maximum number of streams with another peer that can be opened on a host @@ -35,10 +36,10 @@ const MaxStreamNameLength = 256 // Maximum length of messages sent with ragep2p const MaxMessageLength = 1024 * 1024 * 1024 // 1 GiB. This must be smaller than INT32_MAX -const newConnTokens = MaxStreamsPerPeer * (frameHeaderEncodedSize + MaxStreamNameLength) +const newConnTokens = MaxStreamsPerPeer * (maxFrameHeaderSize + MaxStreamNameLength) // assumes we re-open every stream every ten minutes during regular operation -const controlRate = MaxStreamsPerPeer / (10.0 * 60) * (frameHeaderEncodedSize + MaxStreamNameLength) +const controlRate = MaxStreamsPerPeer / (10.0 * 60) * (maxFrameHeaderSize + MaxStreamNameLength) // The 5 second value is cribbed from go standard library's tls package as of version 1.16 and later // https://cs.opensource.google/go/go/+/master:src/crypto/tls/conn.go;drc=059a9eedf45f4909db6a24242c106be15fb27193;l=1454 @@ -53,13 +54,8 @@ const ( hostStateClosed ) -type streamID [32]byte - -var _ fmt.Stringer = streamID{} - -func (s streamID) String() string { - return hex.EncodeToString(s[:]) -} +type streamID = internal_types.StreamID +type requestID = internal_types.RequestID type peerStreamOpenRequest struct { streamID streamID @@ -95,9 +91,37 @@ type streamStateNotification struct { open bool } -type streamIDAndData struct { +type streamIDAndMessage struct { StreamID streamID - Data []byte + Message OutboundBinaryMessage +} + +type priorityChannelGroup[T any] struct { + Default chan T + Low chan T +} + +type prioritySendChannelGroup[T any] struct { + Default chan<- T + Low chan<- T +} + +type priorityRecvChannelGroup[T any] struct { + Default <-chan T + Low <-chan T +} + +func (pg priorityChannelGroup[T]) AsSendGroup() prioritySendChannelGroup[T] { + return prioritySendChannelGroup[T]{ + pg.Default, // convert chan T to chan<- T + pg.Low, // convert chan T to chan<- T + } +} +func (pg priorityChannelGroup[T]) AsRecvGroup() priorityRecvChannelGroup[T] { + return priorityRecvChannelGroup[T]{ + pg.Default, // convert chan T to <-chan T + pg.Low, // convert chan T to <-chan T + } } type peerConnLifeCycle struct { @@ -122,8 +146,8 @@ type peer struct { connLifeCycleMu sync.Mutex connLifeCycle peerConnLifeCycle - chStreamToConn chan streamIDAndData - demuxer *demuxer + chGroupStreamToConn priorityChannelGroup[streamIDAndMessage] + demuxer *demuxer chNewConnNotification chan<- newConnNotification @@ -320,7 +344,13 @@ func (ho *Host) findOrCreatePeer(other types.PeerID) *peer { chConnTerminated, }, - make(chan streamIDAndData), + // Here, we use a small buffered channel for the default priority messages. If a non-buffered channel would + // be used instead, default priority messages would still be preferred to low priority ones, but this + // behavior would depend more on how the individual producer go-routines are scheduled. + priorityChannelGroup[streamIDAndMessage]{ + make(chan streamIDAndMessage, 4), + make(chan streamIDAndMessage), + }, demuxer, chNewConnNotification, @@ -597,7 +627,6 @@ func (ho *Host) dialLoop() { } ho.peersMu.Unlock() for _, p := range peers { - p := p // copy for goroutine ds := dialStates[p.other] dialProcesses.Go(func() { p.connLifeCycleMu.Lock() @@ -850,11 +879,12 @@ func (ho *Host) handleConnection(incoming bool, rlConn *ratelimitedconn.RateLimi defer connCancel() authenticatedConnectionLoop( connCtx, + rlConn, tlsConn, peer.chOtherStreamStateNotification, peer.chSelfStreamStateNotification, peer.demuxer, - peer.chStreamToConn, + peer.chGroupStreamToConn.AsRecvGroup(), chConnTerminated, logger, peer.metrics, @@ -881,15 +911,48 @@ type TokenBucketParams struct { // NewStream creates a new bidirectional stream with peer other for streamName. // It is parameterized with a maxMessageLength, the maximum size of a message in // bytes and two parameters for rate limiting. +// +// Deprecated: Please switch to NewStream2. func (ho *Host) NewStream( other types.PeerID, streamName string, outgoingBufferSize int, // number of messages that fit in the outgoing buffer incomingBufferSize int, // number of messages that fit in the incoming buffer maxMessageLength int, - messagesLimit TokenBucketParams, // rate limit for incoming messages - bytesLimit TokenBucketParams, // rate limit for incoming messages + messagesLimit TokenBucketParams, // rate limit for (the number of) incoming messages + bytesLimit TokenBucketParams, // rate limit for (the accumulated size in bytes of) incoming messages ) (*Stream, error) { + stream2, err := ho.NewStream2( + other, + streamName, + StreamPriorityDefault, + outgoingBufferSize, + incomingBufferSize, + maxMessageLength, + messagesLimit, + bytesLimit, + ) + if err != nil { + return nil, err + } + + return newStreamFromStream2(stream2) +} + +// NewStream2 creates a new bidirectional stream with peer other for streamName. +// It is parameterized with a maxMessageLength, the maximum size of a message in +// bytes and two parameters for rate limiting. Compared to Stream, Stream2 +// introduces an additional parameter: the message priority level. +func (ho *Host) NewStream2( + other types.PeerID, + streamName string, + priority StreamPriority, + outgoingBufferSize int, // number of messages that fit in the outgoing buffer + incomingBufferSize int, // number of messages that fit in the incoming buffer + maxMessageLength int, + messagesLimit TokenBucketParams, // rate limit for (the number of) incoming messages + bytesLimit TokenBucketParams, // rate limit for (the accumulated size in bytes of) incoming messages +) (*Stream2, error) { if other == ho.id { return nil, fmt.Errorf("stream with self is forbidden") } @@ -931,13 +994,24 @@ func (ho *Host) NewStream( return nil, fmt.Errorf("host shut down") } - ctx, cancel := context.WithCancel(ho.ctx) streamID := getStreamID(ho.id, other, streamName) streamLogger := loghelper.MakeRootLoggerWithContext(p.logger).MakeChild(commontypes.LogFields{ "streamID": streamID, "streamName": streamName, }) - s := Stream{ + + var chStreamToConn chan streamIDAndMessage + switch priority { + case StreamPriorityLow: + chStreamToConn = p.chGroupStreamToConn.Low + case StreamPriorityDefault: + chStreamToConn = p.chGroupStreamToConn.Default + default: + return nil, fmt.Errorf("stream initialization failed, invalid priority (%v) specified", priority) + } + + ctx, cancel := context.WithCancel(ho.ctx) + s := Stream2{ sync.Mutex{}, false, @@ -953,10 +1027,10 @@ func (ho *Host) NewStream( ctx, cancel, streamLogger, - make(chan []byte, 1), - make(chan []byte, 1), + make(chan OutboundBinaryMessage, 1), + make(chan InboundBinaryMessage, 1), - p.chStreamToConn, + chStreamToConn, response.demux, response.chSendOnOff, @@ -971,7 +1045,7 @@ func (ho *Host) NewStream( s.sendLoop() }) - streamLogger.Info("NewStream succeeded", commontypes.LogFields{ + streamLogger.Info("NewStream2 succeeded", commontypes.LogFields{ "incomingBufferSize": incomingBufferSize, "maxMessageLength": maxMessageLength, "messagesLimit": messagesLimit, @@ -981,215 +1055,18 @@ func (ho *Host) NewStream( return &s, nil } -// Stream is an over-the-network channel between two peers. Two peers may share -// multiple disjoint streams with different names. Streams are persistent and -// agnostic to the state of the connection. They completely abstract the -// underlying connection. Messages are delivered on a best effort basis. -type Stream struct { - closedMu sync.Mutex - closed bool - - name string - other types.PeerID - streamID streamID - - outgoingBufferSize int - maxMessageLength int - - host *Host - - subprocesses subprocesses.Subprocesses - ctx context.Context - cancel context.CancelFunc - logger loghelper.LoggerWithContext - chSend chan []byte - chReceive chan []byte - - chStreamToConn chan<- streamIDAndData - demux *demuxer - chStreamOnOff <-chan bool - - chStreamCloseRequest chan<- peerStreamCloseRequest - chStreamCloseResponse <-chan peerStreamCloseResponse -} - -// Other returns the peer ID of the stream counterparty. -func (st *Stream) Other() types.PeerID { - return st.other -} - -// Name returns the name of the stream. -func (st *Stream) Name() string { - return st.name -} - -// Best effort sending of messages. May fail without returning an error. -func (st *Stream) SendMessage(data []byte) { - if len(data) > st.maxMessageLength { - st.logger.Warn("dropping outbound message that is too large", commontypes.LogFields{ - "size": len(data), - "max": st.maxMessageLength, - }) - return - } - select { - case st.chSend <- data: - case <-st.ctx.Done(): - } -} - -// Best effort receiving of messages. The returned channel will be closed when -// the stream is closed. Note that this function may return the same channel -// across invocations. -func (st *Stream) ReceiveMessages() <-chan []byte { - return st.chReceive -} - -// Close the stream. This closes any channel returned by ReceiveMessages earlier. -// After close the stream cannot be reopened. If the stream is needed in the -// future it should be created again through NewStream. -// After close, any messages passed to SendMessage will be dropped. -func (st *Stream) Close() error { - st.closedMu.Lock() - defer st.closedMu.Unlock() - host := st.host - - if st.closed { - return fmt.Errorf("already closed stream") - } - - st.logger.Info("Stream winding down", nil) - - err := func() error { - // Grab peersMu in case the peer has no streams left and we need to - // delete it - host.peersMu.Lock() - defer host.peersMu.Unlock() - - select { - case st.chStreamCloseRequest <- peerStreamCloseRequest{st.streamID}: - resp := <-st.chStreamCloseResponse - if resp.err != nil { - st.logger.Error("Unexpected error during stream Close()", commontypes.LogFields{ - "error": resp.err, - }) - return resp.err - } - if resp.peerHasNoStreams { - st.logger.Trace("Garbage collecting peer", nil) - peer := host.peers[st.other] - host.subprocesses.Go(func() { - peer.connLifeCycleMu.Lock() - defer peer.connLifeCycleMu.Unlock() - peer.connLifeCycle.connCancel() - peer.connLifeCycle.connSubs.Wait() - }) - delete(host.peers, st.other) - } - case <-st.ctx.Done(): - } - return nil - }() - if err != nil { - return err - } - - st.closed = true - st.cancel() - st.subprocesses.Wait() - close(st.chReceive) - st.logger.Info("Stream exiting", nil) - return nil -} - -func (st *Stream) receiveLoop() { - chSignalMaybePending := st.demux.SignalMaybePending(st.streamID) - chDone := st.ctx.Done() - for { - select { - case <-chSignalMaybePending: - msg, popResult := st.demux.PopMessage(st.streamID) - switch popResult { - case popResultEmpty: - st.logger.Debug("Demuxer buffer is empty", nil) - case popResultUnknownStream: - // Closing of streams does not happen in a single step, and so - // it could be that in the process of closing, the stream has - // been removed from demuxer, but receiveLoop has not stopped - // yet (but should stop soon). - st.logger.Info("Demuxer does not know of the stream, it is likely we are in the process of closing the stream", nil) - case popResultSuccess: - if msg != nil { - select { - case st.chReceive <- msg: - case <-chDone: - } - } else { - st.logger.Error("Demuxer indicated success but we received nil msg, this should not happen", nil) - } - } - case <-chDone: - return - } - } -} - -func (st *Stream) sendLoop() { - var chStreamToPeerOrNil chan<- streamIDAndData - var pending streamIDAndData - var onOff bool - pendingFilled := false - - ringBuffer := msgbuf.NewMessageBuffer(st.outgoingBufferSize) - - for { - select { - case onOff = <-st.chStreamOnOff: - if onOff { - if pendingFilled { - chStreamToPeerOrNil = st.chStreamToConn - } - st.logger.Info("Turned on stream", nil) - } else { - chStreamToPeerOrNil = nil - st.logger.Info("Turned off stream", nil) - } - - case msg := <-st.chSend: - if ringBuffer.Push(msg) != nil || !pendingFilled { - pending = streamIDAndData{st.streamID, ringBuffer.Peek()} - pendingFilled = true - if onOff { - chStreamToPeerOrNil = st.chStreamToConn - } - } - - case chStreamToPeerOrNil <- pending: - ringBuffer.Pop() - if p := ringBuffer.Peek(); p != nil { - pending = streamIDAndData{st.streamID, p} - } else { - pendingFilled = false - chStreamToPeerOrNil = nil - } - - case <-st.ctx.Done(): - return - } - } -} - ///////////////////////////////////////////// // authenticated connection handling ////////////////////////////////////////////// func authenticatedConnectionLoop( ctx context.Context, + rlConn *ratelimitedconn.RateLimitedConn, conn net.Conn, chOtherStreamStateNotification chan<- streamStateNotification, chSelfStreamStateNotification <-chan streamStateNotification, demux *demuxer, - chWriteData <-chan streamIDAndData, + chGroupWriteData priorityRecvChannelGroup[streamIDAndMessage], chTerminated chan<- struct{}, logger loghelper.LoggerWithContext, metrics *peerMetrics, @@ -1215,6 +1092,7 @@ func authenticatedConnectionLoop( subs.Go(func() { authenticatedConnectionReadLoop( childCtx, + rlConn, conn, chOtherStreamStateNotification, demux, @@ -1230,7 +1108,8 @@ func authenticatedConnectionLoop( childCtx, conn, chSelfStreamStateNotification, - chWriteData, + demux, // added for request/response tracking + chGroupWriteData, chWriteTerminated, logger, metrics, @@ -1248,6 +1127,7 @@ func authenticatedConnectionLoop( func authenticatedConnectionReadLoop( ctx context.Context, + rlConn *ratelimitedconn.RateLimitedConn, conn net.Conn, chOtherStreamStateNotification chan<- streamStateNotification, demux *demuxer, @@ -1267,7 +1147,7 @@ func authenticatedConnectionReadLoop( return true } - skipInternal := func(n uint32) bool { + skipInternal := func(n int) bool { r, err := io.Copy(io.Discard, io.LimitReader(conn, int64(n))) if err != nil || r != int64(n) { logger.Warn("Error reading from connection", commontypes.LogFields{"error": err}) @@ -1290,9 +1170,9 @@ func authenticatedConnectionReadLoop( logWithHeader := func(header frameHeader) commontypes.Logger { return logger.MakeChild(commontypes.LogFields{ - "payloadLength": header.PayloadLength, - "streamID": header.StreamID, - "remoteStreamName": remoteStreamNameByID[header.StreamID], + "payloadLength": header.PayloadSize(), + "streamID": header.StreamID(), + "remoteStreamName": remoteStreamNameByID[header.StreamID()], }) } @@ -1300,128 +1180,212 @@ func authenticatedConnectionReadLoop( openCloseFramesReceived := 0 const maxOpenCloseFramesReceived = 2 * MaxStreamsPerPeer - rawHeader := make([]byte, frameHeaderEncodedSize) + maxOpenCloseFramesExceededInternal := func(streamID streamID, payloadSize int) bool { + if openCloseFramesReceived <= maxOpenCloseFramesReceived { + return false + } - for { - if !readInternal(rawHeader) { - return + childLogger := logger.MakeChild(commontypes.LogFields{ + "payloadLength": payloadSize, + "streamID": streamID, + "remoteStreamName": remoteStreamNameByID[streamID], + }) + childLogger.Warn("authenticatedConnectionReadLoop: peer received too many open/close frames, closing connection", + commontypes.LogFields{ + "maxOpenCloseFramesReceived": maxOpenCloseFramesReceived, + }) + return true + } + + frameHeaderBuffer := make([]byte, maxFrameHeaderSize) + readFrameHeader := func() (header frameHeader, ok bool) { + // Read frame type. + if !readInternal(frameHeaderBuffer[:1]) { + return nil, false } - header, err := decodeFrameHeader(rawHeader) + // Get the length of the frame header for the given type. Abort if the type is invalid. + frameType := frameType(frameHeaderBuffer[0]) + headerSize, ok := frameHeaderSizes[frameType] + if !ok { + logger.Warn("Error decoding frame, invalid frame type", commontypes.LogFields{"frameType": frameType}) + return nil, false + } + if !readInternal(frameHeaderBuffer[1:headerSize]) { + return nil, false + } + + // Decode the frame header. + header, err := decodeFrameHeader(frameHeaderBuffer[:headerSize]) if err != nil { - logger.Warn("Error decoding header", commontypes.LogFields{"error": err}) + if errors.Is(err, errMaxMessageSizeExceeded) { + logWithHeader(header).Warn( + "authenticatedConnectionReadLoop: message exceeds ragep2p message length limit, closing connection", + commontypes.LogFields{ + "payloadLength": header.PayloadSize(), + "ragep2pMaxMessageLength": MaxMessageLength, + }) + } else { + logger.Warn("Error decoding header", commontypes.LogFields{"error": err}) + } + return nil, false + } + return header, true + } + + for { + header, ok := readFrameHeader() + if !ok { return } + frameType := header.Type() + streamID := header.StreamID() + payloadSize := header.PayloadSize() - switch header.Type { - case frameTypeOpen: + if frameType == frameTypeOpenStream { openCloseFramesReceived++ - if header.PayloadLength == 0 || header.PayloadLength > MaxStreamNameLength { - return - } - streamName := make([]byte, header.PayloadLength) + + streamName := make([]byte, payloadSize) if !readInternal(streamName) { return } - remoteStreamNameByID[header.StreamID] = string(streamName) + remoteStreamNameByID[streamID] = string(streamName) + select { - case chOtherStreamStateNotification <- streamStateNotification{ - header.StreamID, - string(streamName), - true, - }: + case chOtherStreamStateNotification <- streamStateNotification{streamID, string(streamName), true}: case <-ctx.Done(): return } - case frameTypeClose: - openCloseFramesReceived++ - if header.PayloadLength != 0 { - logWithHeader(header).Warn("Frame close payload length is not zero", nil) + + if maxOpenCloseFramesExceededInternal(streamID, payloadSize) { return } - delete(remoteStreamNameByID, header.StreamID) + continue + } + + if frameType == frameTypeCloseStream { + openCloseFramesReceived++ + + delete(remoteStreamNameByID, streamID) select { - case chOtherStreamStateNotification <- streamStateNotification{ - header.StreamID, - "", - false, - }: + case chOtherStreamStateNotification <- streamStateNotification{streamID, "", false}: case <-ctx.Done(): return } - case frameTypeData: - if MaxMessageLength < header.PayloadLength { - logWithHeader(header).Warn("authenticatedConnectionReadLoop: message exceeds ragep2p message length limit, closing connection", commontypes.LogFields{ - "payloadLength": header.PayloadLength, - "ragep2pMaxMessageLength": MaxMessageLength, + + if maxOpenCloseFramesExceededInternal(streamID, 0) { + return + } + continue + } + + // Here, frameType is either frameTypeMessage, frameTypeRequest, or frameTypeResponse. + + // The demuxer handles the rate limits for the three types. + // For responses a different rate limiting approach is applied. + var demuxShouldPushResult shouldPushResult + if frameType == frameTypeResponse { + // The type conversion here cannot panic, as decodeFrameHeader (for a frame of type frameTypeResponse), + // always returns an instance of responseFrameHeader. + requestID := (header.(responseFrameHeader)).requestID + demuxShouldPushResult = demux.ShouldPushResponse(streamID, requestID, payloadSize) + } else { + demuxShouldPushResult = demux.ShouldPush(streamID, payloadSize) + } + + switch demuxShouldPushResult { + case shouldPushResultMessageTooBig: + logWithHeader(header).Warn("authenticatedConnectionReadLoop: message too big, closing connection", commontypes.LogFields{}) + return + + case shouldPushResultMessagesLimitExceeded: + limitsExceededTaper.Trigger(func(count uint64) { + logWithHeader(header).Warn("authenticatedConnectionReadLoop: message limit exceeded, dropping message", commontypes.LogFields{ + "limitsExceededDroppedCount": count, }) + }) + if !skipInternal(payloadSize) { return } - // Cast to int is safe since header.PayloadLength <= MaxMessageLength <= INT_MAX - switch demux.ShouldPush(header.StreamID, int(header.PayloadLength)) { - case shouldPushResultMessageTooBig: - logWithHeader(header).Warn("authenticatedConnectionReadLoop: message too big, closing connection", commontypes.LogFields{ - "payloadLength": header.PayloadLength, + case shouldPushResultBytesLimitExceeded: + limitsExceededTaper.Trigger(func(count uint64) { + logWithHeader(header).Warn("authenticatedConnectionReadLoop: bytes limit exceeded, dropping message", commontypes.LogFields{ + "limitsExceededDroppedCount": count, }) + }) + if !skipInternal(payloadSize) { return - case shouldPushResultMessagesLimitExceeded: - limitsExceededTaper.Trigger(func(count uint64) { - logWithHeader(header).Warn("authenticatedConnectionReadLoop: message limit exceeded, dropping message", commontypes.LogFields{ + } + + case shouldPushResultUnknownStream: + unknownStreamIDTaper.Trigger(func(count uint64) { + logWithHeader(header).Warn("authenticatedConnectionReadLoop: unknown stream id, dropping message", commontypes.LogFields{ + "unknownStreamIDDroppedCount": count, + }) + }) + if !skipInternal(payloadSize) { + return + } + + case shouldPushResultResponseRejected: + limitsExceededTaper.Trigger(func(count uint64) { + logWithHeader(header).Warn( + "authenticatedConnectionReadLoop: response rejected, dropping message", commontypes.LogFields{ "limitsExceededDroppedCount": count, - }) + }, + ) + }) + if !skipInternal(payloadSize) { + return + } + + case shouldPushResultYes: + limitsExceededTaper.Reset(func(oldCount uint64) { + logWithHeader(header).Info("authenticatedConnectionReadLoop: limits are no longer being exceeded", commontypes.LogFields{ + "droppedCount": oldCount, }) - if !skipInternal(header.PayloadLength) { + }) + payload := make([]byte, payloadSize) + + // Add a one-time exception for the connection rate limit, in case we want to read the payload of a response + // which was allowed by policy decision. + if frameType == frameTypeResponse { + rlConn.AllowTransientCapacity(payloadSize, true) + ok = readInternal(payload) + rlConn.ClearTransientCapacity() + if !ok { return } - case shouldPushResultBytesLimitExceeded: - limitsExceededTaper.Trigger(func(count uint64) { - logWithHeader(header).Warn("authenticatedConnectionReadLoop: bytes limit exceeded, dropping message", commontypes.LogFields{ - "limitsExceededDroppedCount": count, - }) - }) - if !skipInternal(header.PayloadLength) { + } else { + if !readInternal(payload) { return } - case shouldPushResultUnknownStream: + } + + var message InboundBinaryMessage + switch header := header.(type) { + case messageFrameHeader: + message = InboundBinaryMessagePlain{payload} + case requestFrameHeader: + message = InboundBinaryMessageRequest{RequestHandle(header.requestID), payload} + case responseFrameHeader: + message = InboundBinaryMessageResponse{payload} + default: + panic("unknown type of ragep2p.frameHeader") + } + + switch demux.PushMessage(streamID, message) { + case pushResultSuccess: + case pushResultDropped: + logWithHeader(header).Trace("authenticatedConnectionReadLoop: demuxer is overflowing for stream, dropping oldest message", nil) + case pushResultUnknownStream: unknownStreamIDTaper.Trigger(func(count uint64) { logWithHeader(header).Warn("authenticatedConnectionReadLoop: unknown stream id, dropping message", commontypes.LogFields{ "unknownStreamIDDroppedCount": count, }) }) - if !skipInternal(header.PayloadLength) { - return - } - case shouldPushResultYes: - limitsExceededTaper.Reset(func(oldCount uint64) { - logWithHeader(header).Info("authenticatedConnectionReadLoop: limits are no longer being exceeded", commontypes.LogFields{ - "droppedCount": oldCount, - }) - }) - data := make([]byte, header.PayloadLength) - if !readInternal(data) { - return - } - switch demux.PushMessage(header.StreamID, data) { - case pushResultSuccess: - case pushResultDropped: - logWithHeader(header).Trace("authenticatedConnectionReadLoop: demuxer is overflowing for stream, dropping oldest message", nil) - case pushResultUnknownStream: - unknownStreamIDTaper.Trigger(func(count uint64) { - logWithHeader(header).Warn("authenticatedConnectionReadLoop: unknown stream id, dropping message", commontypes.LogFields{ - "unknownStreamIDDroppedCount": count, - }) - }) - } - } } - - if openCloseFramesReceived > maxOpenCloseFramesReceived { - logWithHeader(header).Warn("authenticatedConnectionReadLoop: peer received too many open/close frames, closing connection", commontypes.LogFields{ - "maxOpenCloseFramesReceived": maxOpenCloseFramesReceived, - }) - return - } } } @@ -1429,7 +1393,8 @@ func authenticatedConnectionWriteLoop( ctx context.Context, conn net.Conn, chSelfStreamStateNotification <-chan streamStateNotification, - chWriteData <-chan streamIDAndData, + demux *demuxer, + chGroupWriteData priorityRecvChannelGroup[streamIDAndMessage], chWriteTerminated chan<- struct{}, logger loghelper.LoggerWithContext, metrics *peerMetrics, @@ -1449,54 +1414,96 @@ func authenticatedConnectionWriteLoop( return true } - for { - select { - case data := <-chWriteData: - if err := conn.SetWriteDeadline(time.Now().Add(netTimeout)); err != nil { - logger.Warn("Closing connection, error during SetWriteDeadline", commontypes.LogFields{"error": err}) - return - } - header := frameHeader{ - frameTypeData, - data.StreamID, - uint32(len(data.Data)), - } - if !writeInternal(header.Encode()) { - return - } - if !writeInternal(data.Data) { - return - } - metrics.messageBytes.Observe(float64(len(data.Data))) - case notification := <-chSelfStreamStateNotification: - if err := conn.SetWriteDeadline(time.Now().Add(netTimeout)); err != nil { - logger.Warn("Closing connection, error during SetWriteDeadline", commontypes.LogFields{"error": err}) - return + sendInternal := func(data streamIDAndMessage) bool { + if err := conn.SetWriteDeadline(time.Now().Add(netTimeout)); err != nil { + logger.Warn("Closing connection, error during SetWriteDeadline", commontypes.LogFields{"error": err}) + return false + } + + streamID := data.StreamID + message := data.Message + + // The header's value is set based on the type of message in the switch statement below. + var header []byte + var payload []byte + + switch m := message.(type) { + // Requests: Generate a new random request ID, track it, and the request header. + case OutboundBinaryMessageRequest: + requestID, err := getRandomRequestID() + if err != nil { + logger.Error("Error while sending request (failed to generate random request id)", commontypes.LogFields{}) + return false } - var header frameHeader + demux.responseChecker.SetPolicy(streamID, requestID, m.ResponsePolicy) + header = requestFrameHeader{streamID, len(m.Payload), requestID}.Encode() + payload = m.Payload + + // Responses: The concrete type `outboundResponse` (as returned by PrepareResponse(...)) is used to get access + // to the `Id` field which needs to be added to the header for all responses. + case OutboundBinaryMessageResponse: + header = responseFrameHeader{streamID, len(m.Payload), m.requestID}.Encode() + payload = m.Payload + + // Normal messages: + case OutboundBinaryMessagePlain: + header = messageFrameHeader{streamID, len(m.Payload)}.Encode() + payload = m.Payload + } + + if !writeInternal(header) { + return false + } + if !writeInternal(payload) { + return false + } + metrics.messageBytes.Observe(float64(len(payload))) + return true + } + + handleStreamStateNotifications := func(notification streamStateNotification) bool { + if err := conn.SetWriteDeadline(time.Now().Add(netTimeout)); err != nil { + logger.Warn("Closing connection, error during SetWriteDeadline", commontypes.LogFields{"error": err}) + return false + } + if notification.open { streamName := []byte(notification.streamName) - if notification.open { - header = frameHeader{ - frameTypeOpen, - notification.streamID, - uint32(len(streamName)), - } - } else { - header = frameHeader{ - frameTypeClose, - notification.streamID, - uint32(0), - } + if !writeInternal(openStreamFrameHeader{notification.streamID, len(streamName)}.Encode()) { + return false } - if !writeInternal(header.Encode()) { - return + if !writeInternal(streamName) { + return false } - if notification.open && !writeInternal(streamName) { - return + } else { + if !writeInternal(closeStreamFrameHeader{notification.streamID}.Encode()) { + return false } + } + return true + } + ok := true + for ok { + // Priority select statement: chGroupWriteData.Default is always drained before chGroupWriteData.Low + // Note: This is (and must be) also considered on the sending side of the channels. + select { + case streamIDAndMessage := <-chGroupWriteData.Default: + ok = sendInternal(streamIDAndMessage) + case notification := <-chSelfStreamStateNotification: + ok = handleStreamStateNotifications(notification) case <-ctx.Done(): return + default: + select { + case streamIDAndMessage := <-chGroupWriteData.Default: + ok = sendInternal(streamIDAndMessage) + case streamIDAndMessage := <-chGroupWriteData.Low: + ok = sendInternal(streamIDAndMessage) + case notification := <-chSelfStreamStateNotification: + ok = handleStreamStateNotifications(notification) + case <-ctx.Done(): + return + } } } } diff --git a/ragep2p/stream.go b/ragep2p/stream.go new file mode 100644 index 0000000..f4c3724 --- /dev/null +++ b/ragep2p/stream.go @@ -0,0 +1,89 @@ +package ragep2p + +import ( + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/ragep2p/types" +) + +// Deprecated: Please switch to Stream2. +type Stream struct { + stream2 *Stream2 + chReceive chan []byte +} + +// Helper function for initializing a legacy Stream using a new Stream2. +func newStreamFromStream2(stream2 *Stream2) (*Stream, error) { + stream := &Stream{ + stream2, + make(chan []byte, 5), + } + go stream.receiveForwardingLoop() + return stream, nil +} + +// Other returns the peer ID of the stream counterparty. +func (st *Stream) Other() types.PeerID { + return st.stream2.Other() +} + +// Name returns the name of the stream. +func (st *Stream) Name() string { + return st.stream2.Name() +} + +// Best effort sending of messages. May fail without returning an error. +func (st *Stream) SendMessage(data []byte) { + st.stream2.Send(OutboundBinaryMessagePlain{data}) +} + +// Best effort receiving of messages. The returned channel will be closed when +// the stream is closed. Note that this function may return the same channel +// across invocations. +func (st *Stream) ReceiveMessages() <-chan []byte { + // Here, return the st.chReceive (instead of type incompatible st.stream2.chReceive). + // See NewStream(...), which starts a go-routines forwarding all messages from st.stream2.chReceive to st.chReceive. + return st.chReceive +} + +// Close the stream. This closes any channel returned by ReceiveMessages earlier. +// After close the stream cannot be reopened. If the stream is needed in the +// future it should be created again through NewStream. +// After close, any messages passed to SendMessage will be dropped. +func (st *Stream) Close() error { + return st.stream2.Close() +} + +// Implements forwarding of received messages as workaround for incompatibility of the chReceive types. +func (st *Stream) receiveForwardingLoop() { + defer close(st.chReceive) + logTaper := loghelper.LogarithmicTaper{} + + for { + select { + case msg := <-st.stream2.Receive(): + if msg == nil { + // stream2 was closed, so we stop the forwarding loop + return + } + + if msg, ok := msg.(InboundBinaryMessagePlain); ok { + select { + case st.chReceive <- msg.Payload: + case <-st.stream2.ctx.Done(): + return + } + } else { + logTaper.Trigger(func(newCount uint64) { + st.stream2.logger.Warn( + "Stream: dropping InboundBinaryMessage that is not InboundBinaryMessagePlain. Use Stream2 for support of these.", + commontypes.LogFields{}, + ) + }) + } + + case <-st.stream2.ctx.Done(): + return + } + } +} diff --git a/ragep2p/stream2.go b/ragep2p/stream2.go new file mode 100644 index 0000000..ec59b4f --- /dev/null +++ b/ragep2p/stream2.go @@ -0,0 +1,316 @@ +package ragep2p + +import ( + "context" + "fmt" + "sync" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/internal/loghelper" + "github.com/smartcontractkit/libocr/internal/ringbuffer" + "github.com/smartcontractkit/libocr/ragep2p/internal/responselimit" + internaltypes "github.com/smartcontractkit/libocr/ragep2p/internal/types" + "github.com/smartcontractkit/libocr/ragep2p/types" + "github.com/smartcontractkit/libocr/subprocesses" +) + +type StreamPriority byte + +const ( + _ StreamPriority = iota + StreamPriorityLow + StreamPriorityDefault +) + +// Stream2 is an over-the-network channel between two peers. Two peers may share +// multiple disjoint streams with different names. Streams are persistent and +// agnostic to the state of the connection. They abstract the underlying +// connection. Messages are delivered on a best effort basis. +type Stream2 struct { + closedMu sync.Mutex + closed bool + + name string + other types.PeerID + streamID streamID + + outgoingBufferSize int + maxMessageLength int + + host *Host + + subprocesses subprocesses.Subprocesses + ctx context.Context + cancel context.CancelFunc + logger loghelper.LoggerWithContext + chSend chan OutboundBinaryMessage + chReceive chan InboundBinaryMessage + + chStreamToConn chan<- streamIDAndMessage + demux *demuxer + chStreamOnOff <-chan bool + + chStreamCloseRequest chan<- peerStreamCloseRequest + chStreamCloseResponse <-chan peerStreamCloseResponse +} + +// Other returns the peer ID of the stream's counterparty. +func (st *Stream2) Other() types.PeerID { + return st.other +} + +// Name returns the name of the stream. +func (st *Stream2) Name() string { + return st.name +} + +// Best effort sending of messages. May fail without returning an error. +func (st *Stream2) Send(msg OutboundBinaryMessage) { + var ( + ok bool + payloadLength int + ) + switch msg := msg.(type) { + case OutboundBinaryMessagePlain: + ok = len(msg.Payload) <= st.maxMessageLength + payloadLength = len(msg.Payload) + case OutboundBinaryMessageRequest: + ok = len(msg.Payload) <= st.maxMessageLength + payloadLength = len(msg.Payload) + case OutboundBinaryMessageResponse: + // Response size is limited by the policy of the corresponding request + // and may exceed the stream's default max message length. + // Responses must never exceed the global ragep2p max message length. + ok = len(msg.Payload) <= MaxMessageLength + payloadLength = len(msg.Payload) + default: + panic(fmt.Sprintf("unknown OutboundBinaryMessage type: %T", msg)) + } + + if !ok { + st.logger.Warn("dropping outbound message that is too large", commontypes.LogFields{ + "messagePayloadLength": payloadLength, + "streamMaxMessageLength": st.maxMessageLength, + "ragep2pMaxMessageLength": MaxMessageLength, + }) + return + } + + select { + case st.chSend <- msg: + case <-st.ctx.Done(): + } +} + +// Best effort receiving of messages. The returned channel will be closed when +// the stream is closed. Note that this function may return the same channel +// across invocations. +func (st *Stream2) Receive() <-chan InboundBinaryMessage { + return st.chReceive +} + +// Close the stream. This closes any channel returned by ReceiveMessages earlier. +// After close the stream cannot be reopened. If the stream is needed in the +// future it should be created again through NewStream2. +// After close, any messages passed to SendMessage will be dropped. +func (st *Stream2) Close() error { + st.closedMu.Lock() + defer st.closedMu.Unlock() + host := st.host + + if st.closed { + return fmt.Errorf("already closed stream") + } + + st.logger.Info("Stream winding down", nil) + + err := func() error { + // Grab peersMu in case the peer has no streams left and we need to + // delete it + host.peersMu.Lock() + defer host.peersMu.Unlock() + + select { + case st.chStreamCloseRequest <- peerStreamCloseRequest{st.streamID}: + resp := <-st.chStreamCloseResponse + if resp.err != nil { + st.logger.Error("Unexpected error during stream Close()", commontypes.LogFields{ + "error": resp.err, + }) + return resp.err + } + if resp.peerHasNoStreams { + st.logger.Trace("Garbage collecting peer", nil) + peer := host.peers[st.other] + host.subprocesses.Go(func() { + peer.connLifeCycleMu.Lock() + defer peer.connLifeCycleMu.Unlock() + peer.connLifeCycle.connCancel() + peer.connLifeCycle.connSubs.Wait() + }) + delete(host.peers, st.other) + } + case <-st.ctx.Done(): + } + return nil + }() + if err != nil { + return err + } + + st.closed = true + st.cancel() + st.subprocesses.Wait() + close(st.chReceive) + st.logger.Info("Stream exiting", nil) + return nil +} + +func (st *Stream2) receiveLoop() { + chSignalMaybePending := st.demux.SignalMaybePending(st.streamID) + chDone := st.ctx.Done() + for { + select { + case <-chSignalMaybePending: + msg, popResult := st.demux.PopMessage(st.streamID) + switch popResult { + case popResultEmpty: + st.logger.Debug("Demuxer buffer is empty", nil) + case popResultUnknownStream: + // Closing of streams does not happen in a single step, and so + // it could be that in the process of closing, the stream has + // been removed from demuxer, but receiveLoop has not stopped + // yet (but should stop soon). + st.logger.Info("Demuxer does not know of the stream, it is likely we are in the process of closing the stream", nil) + case popResultSuccess: + if msg != nil { + select { + case st.chReceive <- msg: + case <-chDone: + } + } else { + st.logger.Error("Demuxer indicated success but we received nil msg, this should not happen", nil) + } + } + case <-chDone: + return + } + } +} + +func (st *Stream2) sendLoop() { + var chStreamToPeerOrNil chan<- streamIDAndMessage + var pending streamIDAndMessage // invariant: `pending` equals the item returned by `ringBuffer.Peek()` + var onOff bool + pendingFilled := false + + ringBuffer := ringbuffer.NewRingBuffer[OutboundBinaryMessage](st.outgoingBufferSize) + + for { + select { + case onOff = <-st.chStreamOnOff: + if onOff { + if pendingFilled { + chStreamToPeerOrNil = st.chStreamToConn + } + st.logger.Info("Turned on stream", nil) + } else { + chStreamToPeerOrNil = nil + st.logger.Info("Turned off stream", nil) + } + + case msg := <-st.chSend: + if _, didEvict := ringBuffer.PushEvict(msg); didEvict || !pendingFilled { + pendingMsg, _ := ringBuffer.Peek() + pending = streamIDAndMessage{st.streamID, pendingMsg} + pendingFilled = true + if onOff { + chStreamToPeerOrNil = st.chStreamToConn + } + } + + case chStreamToPeerOrNil <- pending: + ringBuffer.Pop() + if p, ok := ringBuffer.Peek(); ok { + pending = streamIDAndMessage{st.streamID, p} + } else { + pendingFilled = false + chStreamToPeerOrNil = nil + } + + case <-st.ctx.Done(): + return + } + } +} + +//////////////////////////////////////////////////////// +// Types for "new" messages +//////////////////////////////////////////////////////// + +type ResponsePolicy = responselimit.ResponsePolicy +type SingleUseSizedLimitedResponsePolicy = responselimit.SingleUseSizedLimitedResponsePolicy + +type RequestHandle internaltypes.RequestID + +func (r *RequestHandle) MakeResponse(payload []byte) OutboundBinaryMessageResponse { + return OutboundBinaryMessageResponse{ + internaltypes.RequestID(*r), + payload, + } +} + +type InboundBinaryMessage interface { + isInboundBinaryMessage() +} + +var _ InboundBinaryMessage = InboundBinaryMessagePlain{} +var _ InboundBinaryMessage = InboundBinaryMessageRequest{} +var _ InboundBinaryMessage = InboundBinaryMessageResponse{} + +type InboundBinaryMessagePlain struct { + Payload []byte +} + +func (InboundBinaryMessagePlain) isInboundBinaryMessage() {} + +type InboundBinaryMessageRequest struct { + RequestHandle RequestHandle + Payload []byte +} + +func (InboundBinaryMessageRequest) isInboundBinaryMessage() {} + +type InboundBinaryMessageResponse struct { + Payload []byte +} + +func (InboundBinaryMessageResponse) isInboundBinaryMessage() {} + +type OutboundBinaryMessage interface { + isOutboundBinaryMessage() +} + +var _ OutboundBinaryMessage = OutboundBinaryMessagePlain{} +var _ OutboundBinaryMessage = OutboundBinaryMessageRequest{} +var _ OutboundBinaryMessage = OutboundBinaryMessageResponse{} + +type OutboundBinaryMessagePlain struct { + Payload []byte +} + +func (OutboundBinaryMessagePlain) isOutboundBinaryMessage() {} + +type OutboundBinaryMessageRequest struct { + ResponsePolicy ResponsePolicy + Payload []byte +} + +func (OutboundBinaryMessageRequest) isOutboundBinaryMessage() {} + +type OutboundBinaryMessageResponse struct { + requestID internaltypes.RequestID + Payload []byte +} + +func (OutboundBinaryMessageResponse) isOutboundBinaryMessage() {}