-
Notifications
You must be signed in to change notification settings - Fork 56
Expand file tree
/
Copy pathtypes.go
More file actions
448 lines (391 loc) · 18.2 KB
/
types.go
File metadata and controls
448 lines (391 loc) · 18.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
// Package types contains the types and interfaces a consumer of the OCR library needs to be aware of
package types
import (
"bytes"
"context"
"crypto/ed25519"
"time"
"github.com/smartcontractkit/libocr/commontypes"
"golang.org/x/crypto/curve25519"
)
type BinaryNetworkEndpointLimits struct {
MaxMessageLength int
MessagesRatePerOracle float64
MessagesCapacityPerOracle int
BytesRatePerOracle float64
BytesCapacityPerOracle int
}
// BinaryNetworkEndpointFactory creates permissioned BinaryNetworkEndpoints.
//
// All its functions should be thread-safe.
type BinaryNetworkEndpointFactory interface {
// f is a remnant of P2Pv1 and is ignored.
NewEndpoint(
cd ConfigDigest,
peerIDs []string,
v2bootstrappers []commontypes.BootstrapperLocator,
f int,
limits BinaryNetworkEndpointLimits,
) (commontypes.BinaryNetworkEndpoint, error)
PeerID() string
}
// BootstrapperFactory creates permissioned Bootstrappers.
//
// All its functions should be thread-safe.
type BootstrapperFactory interface {
// f is a remnant of P2Pv1 and is ignored.
NewBootstrapper(cd ConfigDigest, peerIDs []string,
v2bootstrappers []commontypes.BootstrapperLocator,
f int,
) (commontypes.Bootstrapper, error)
}
type Query []byte
type Observation []byte
type AttributedObservation struct {
Observation Observation
Observer commontypes.OracleID
}
func (ao AttributedObservation) Equal(other AttributedObservation) bool {
return bytes.Equal(ao.Observation, other.Observation) && ao.Observer == other.Observer
}
// ReportTimestamp is the logical timestamp of a report.
type ReportTimestamp struct {
ConfigDigest ConfigDigest
Epoch uint32
Round uint8
}
// ReportContext is the contextual data sent to contract along with the report
// itself.
type ReportContext struct {
ReportTimestamp
// A hash over some data that is exchanged during execution of the offchain
// protocol. The data itself is not needed onchain, but we still want to
// include it in the signature that goes onchain.
ExtraHash [32]byte
}
type Report []byte
type AttributedOnchainSignature struct {
Signature []byte
Signer commontypes.OracleID
}
func (as AttributedOnchainSignature) Equal(other AttributedOnchainSignature) bool {
return bytes.Equal(as.Signature, other.Signature) && as.Signer == other.Signer
}
type ReportingPluginFactory 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, ReportingPluginConfig) (ReportingPlugin, ReportingPluginInfo, error)
}
type ReportingPluginConfig struct {
ConfigDigest ConfigDigest
// OracleID (index) of the oracle executing this ReportingPlugin instance.
OracleID commontypes.OracleID
// N is the total number of nodes.
N int
// F is an upper bound on the number of faulty nodes.
F int
// Encoded configuration for the contract
OnchainConfig []byte
// Encoded configuration for the ReportingPlugin disseminated through the
// contract. This value is only passed through the contract, but otherwise
// ignored by it.
OffchainConfig []byte
// Estimate of the duration between rounds. You should not rely on this
// value being accurate. Rounds might occur more or less frequently than
// estimated.
//
// This value is intended for estimating the load incurred by a
// ReportingPlugin before running it and for configuring caches.
EstimatedRoundInterval time.Duration
// Maximum duration the ReportingPlugin's functions are allowed to take
MaxDurationQuery time.Duration
MaxDurationObservation time.Duration
MaxDurationReport time.Duration
MaxDurationShouldAcceptFinalizedReport time.Duration
MaxDurationShouldTransmitAcceptedReport time.Duration
}
// A ReportingPlugin allows plugging custom logic into the OCR protocol. The OCR
// protocol handles cryptography, networking, ensuring that a sufficient number
// of nodes is in agreement about any report, transmitting the report to the
// contract, etc... The ReportingPlugin handles application-specific logic. To
// do so, the ReportingPlugin defines a number of callbacks that are called by
// the OCR protocol logic at certain points in the protocol's execution flow.
// The report generated by the ReportingPlugin must be in a format understood by
// contract that the reports are transmitted to.
//
// Roughly speaking, the protocol works as follows: A designated leader (fixed
// per epoch) broadcasts a request for observations containing an
// application-specific query to all followers. (Note the leader is also a
// follower of itself, i.e. one node acts as leader and all nodes act as
// followers.) Followers make signed observations and send them back to the
// leader. The leader collects the observations and broadcasts a report request
// containing the original query and the collected signed observations to all
// followers. At this stage, followers decide whether a report should be
// created. If followers decide not to create a report, the round ends here.
// Otherwise, followers construct a signed report (ultimately destined for the
// target contract) and send it to the leader. The leader collects the signed
// report(s) and sends out a final message containing a report together with a
// sufficient number of signatures. The followers echo the final message amongst
// each other prevent a malicious leader from selectively excluding particular
// nodes. Each follower independently decides whether it wishes to accept the
// final report for transmission. If it accepts it for transmission, the
// follower will wait for some time (according to a shared schedule) before
// attempting to transmit the report. After this time, the follower will check
// one last time whether to broadcast the transmit transaction before sending
// it. (Due to its brevity, this description skips over lots of details and edge
// cases. This is just to give a rough idea of the protocol flow.)
//
// 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, the leader will
// start by invoking Query. Followers will call Observation and Report. If a
// sufficient number of followers agree on a report, ShouldAcceptFinalizedReport
// will be called as well. If ShouldAcceptFinalizedReport returns true,
// ShouldTransmitAcceptedReport will be called. However, a ReportingPlugin must
// also correctly handle the case where faults occur.
//
// In particular, a ReportingPlugin must deal with cases where:
//
// - only a subset of the functions on the ReportingPlugin are invoked for a
// given round
//
// - an arbitrary number of epochs and rounds has been skipped between
// invocations of the ReportingPlugin
//
// - the observation returned by Observation is not included in the list of
// AttributedObservations passed to Report
//
// - a query or observation is malformed. (For defense in depth, it is also
// strongly recommended that malformed reports 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. Or Observation may have been invoked on
// A and B, but with different queries.
//
// All functions on a ReportingPlugin should be thread-safe.
//
// All functions that take a context as their first argument may still do cheap
// computations after the context expires, but should stop any blocking
// interactions with outside services (APIs, database, ...) and return as
// quickly as possible. (Rough rule of thumb: any such computation should not
// take longer than a few ms.) A blocking function may block execution of the
// entire protocol instance!
//
// For a given OCR2 protocol instance, there can be many (consecutive) instances
// of a ReportingPlugin, e.g. due to software restarts. If you need
// ReportingPlugin state to survive across restarts, you should persist it. A
// ReportingPlugin instance will only ever serve a single protocol instance.
// When we talk about "instance" below, we typically mean ReportingPlugin
// instances, not protocol instances.
type ReportingPlugin 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 sequence of epochs and the sequence of rounds
// within an epoch are strictly monotonically increasing during the lifetime
// of an instance of this interface.
Query(context.Context, ReportTimestamp) (Query, error)
// Observation gets an observation from the underlying data source. Returns
// a value or an error.
//
// You may assume that the sequence of epochs and the sequence of rounds
// within an epoch are strictly monotonically increasing during the lifetime
// of an instance of this interface.
Observation(context.Context, ReportTimestamp, Query) (Observation, error)
// Decides whether a report (destined for the contract) should be generated
// in this round. If yes, also constructs the report.
//
// You may assume that the sequence of epochs and the sequence of rounds
// within an epoch are strictly monotonically increasing during the lifetime
// of an instance of this interface. This function will always be called
// with at least 2f+1 AttributedObservations from distinct oracles.
Report(context.Context, ReportTimestamp, Query, []AttributedObservation) (bool, Report, error)
// Decides whether a report should be accepted for transmission. Any report
// passed to this function will have been signed by a quorum of oracles.
//
// Don't make assumptions about the epoch/round order in which this function
// is called.
ShouldAcceptFinalizedReport(context.Context, ReportTimestamp, Report) (bool, error)
// Decides whether the given report should actually be broadcast to the
// contract. This is invoked just before the broadcast occurs. Any report
// passed to this function will have been signed by a quorum of oracles and
// been accepted by ShouldAcceptFinalizedReport.
//
// Don't make assumptions about the epoch/round order in which this function
// is called.
//
// As mentioned above, you should gracefully handle only a subset of a
// ReportingPlugin's functions being invoked for a given report. For
// example, due to reloading persisted pending transmissions from the
// database upon oracle restart, this function may be called with reports
// that no other function of this instance of this interface has ever
// been invoked on.
ShouldTransmitAcceptedReport(context.Context, ReportTimestamp, Report) (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 ReportingPlugin is no longer
// needed, e.g. on shutdown of the protocol instance or shutdown of the
// oracle node. This will only be called after any calls to other functions
// of the ReportingPlugin will have completed.
Close() error
}
const (
twoHundredFiftySixMiB = 256 * 1024 * 1024 // 256 MiB
MaxMaxQueryLength = twoHundredFiftySixMiB // 256 MiB
MaxMaxObservationLength = twoHundredFiftySixMiB // 256 MiB
MaxMaxReportLength = twoHundredFiftySixMiB // 256 MiB
)
// Limits for data returned by the ReportingPlugin.
// Used for computing rate limits and defending against outsized messages.
// Messages are checked against these values during (de)serialization. Be
// careful when changing these values, they could lead to different versions
// of a ReportingPlugin being unable to communicate with each other.
type ReportingPluginLimits struct {
MaxQueryLength int
MaxObservationLength int
MaxReportLength int
}
type ReportingPluginInfo struct {
// Used for debugging purposes.
Name string
// If true, quorum requirements are adjusted so that only a single report
// will reach a quorum of signatures for any (epoch, round) tuple.
UniqueReports bool
Limits ReportingPluginLimits
}
// Account is a human-readable account identifier, e.g. an Ethereum address
type Account string
// ContractTransmitter sends new reports to the OCR2Aggregator smart contract.
//
// All its functions should be thread-safe.
type ContractTransmitter interface {
// Transmit sends the report to the on-chain OCR2Aggregator smart
// contract's Transmit method.
//
// In most cases, implementations of this function should store the
// transmission in a queue/database/..., but perform the actual
// transmission (and potentially confirmation) of the transaction
// asynchronously.
Transmit(
context.Context,
ReportContext,
Report,
[]AttributedOnchainSignature,
) error
// LatestConfigDigestAndEpoch returns the logically latest configDigest and
// epoch for which a report was successfully transmitted.
LatestConfigDigestAndEpoch(
context.Context,
) (
configDigest ConfigDigest,
epoch uint32,
err error,
)
// Account from which the transmitter invokes the contract
FromAccount(context.Context) (Account, error)
}
// ContractConfigTracker tracks configuration changes of the OCR contract
// (on-chain).
//
// All its functions should be thread-safe.
type ContractConfigTracker interface {
// Notify may optionally emit notification events when the contract's
// configuration changes. This is purely used as an optimization reducing
// the delay between a configuration change and its enactment. Implementors
// who don't care about this may simply return a nil channel.
//
// The returned channel should never be closed.
Notify() <-chan struct{}
// LatestConfigDetails returns information about the latest configuration,
// but not the configuration itself.
LatestConfigDetails(ctx context.Context) (changedInBlock uint64, configDigest ConfigDigest, err error)
// LatestConfig returns the latest configuration.
LatestConfig(ctx context.Context, changedInBlock uint64) (ContractConfig, error)
// LatestBlockHeight returns the height of the most recent block in the chain.
LatestBlockHeight(ctx context.Context) (blockHeight uint64, err error)
}
type ContractConfig struct {
ConfigDigest ConfigDigest
ConfigCount uint64
Signers []OnchainPublicKey
Transmitters []Account
F uint8
OnchainConfig []byte
OffchainConfigVersion uint64
OffchainConfig []byte
}
// OffchainPublicKey is the public key used to cryptographically identify an
// oracle in inter-oracle communications.
type OffchainPublicKey [ed25519.PublicKeySize]byte
// OnchainPublicKey is the public key used to cryptographically identify an
// oracle to the on-chain smart contract.
type OnchainPublicKey []byte
func (opk OnchainPublicKey) Equal(other OnchainPublicKey) bool {
return bytes.Equal(opk, other)
}
// ConfigEncryptionPublicKey is the public key used to receive an encrypted
// version of the secret shared amongst all oracles on a common contract.
type ConfigEncryptionPublicKey [curve25519.PointSize]byte // X25519
// 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.
// There are two key pairs to track, here:
//
// First, the off-chain key signing key pair (Ed25519), used to sign observations.
//
// Second, the config encryption key (X25519), used to decrypt the symmetric
// key which encrypts the offchain configuration data passed through the OCR2Aggregator
// smart contract.
//
// All its functions should be thread-safe.
type OffchainKeyring interface {
// OffchainSign returns an EdDSA-Ed25519 signature on msg produced using the
// standard library's ed25519.Sign function.
OffchainSign(msg []byte) (signature []byte, err error)
// ConfigDiffieHellman multiplies point with the secret key (i.e. scalar)
// that ConfigEncryptionPublicKey corresponds to.
ConfigDiffieHellman(point [curve25519.PointSize]byte) (sharedPoint [curve25519.PointSize]byte, err error)
// OffchainPublicKey returns the public component of the keypair used in SignOffchain.
OffchainPublicKey() OffchainPublicKey
// ConfigEncryptionPublicKey returns the public component of the keypair used in ConfigDiffieHellman.
ConfigEncryptionPublicKey() ConfigEncryptionPublicKey
// OffchainKeyring has no Verify method because we always use ed25519 and
// can thus use the standard library's ed25519.Verify. OffchainKeyring does
// include a Sign function to prevent passing the private key itself to this
// library.
}
// OnchainKeyring provides cryptographic signatures that need to be verifiable
// on the targeted blockchain. The underlying cryptographic primitives may be
// different on each chain; for example, on Ethereum one would use ECDSA over
// secp256k1 and Keccak256, whereas on Solana one would use Ed25519 and SHA256.
//
// All its functions should be thread-safe.
type OnchainKeyring interface {
// PublicKey returns the public key of the keypair used by Sign.
PublicKey() OnchainPublicKey
// Sign returns a signature over ReportContext and Report.
//
// Reports may contain secret information.
// Implementations of this function should be careful to not leak
// the report's contents, e.g. by logging them or including them in
// returned errors.
Sign(ReportContext, Report) (signature []byte, err error)
// Verify verifies a signature over ReportContext and Report allegedly
// created from OnchainPublicKey.
//
// Implementations of this function must gracefully handle malformed or
// adversarially crafted inputs.
Verify(_ OnchainPublicKey, _ ReportContext, _ Report, signature []byte) bool
// Maximum length of a signature
MaxSignatureLength() int
}