Skip to content

Commit 7b0d2d7

Browse files
gtklockerkaleofduty
andcommitted
Improvements:
- OCR3.1 polishing - Handle ContractConfigTracker.Notify channel rotation Based on 27d60332a83ba0d6c33ccf02410d38a26301956f. Co-authored-by: kaleofduty <59616916+kaleofduty@users.noreply.github.com>
1 parent 0a5e2f9 commit 7b0d2d7

File tree

6 files changed

+62
-40
lines changed

6 files changed

+62
-40
lines changed

internal/jmt/jmt.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ import (
77
"math"
88
"slices"
99
"sort"
10+
11+
"github.com/smartcontractkit/libocr/internal/util"
1012
)
1113

1214
type KeyValue struct {
13-
// Key length must be greater than 0.
1415
Key []byte
1516
// Value of nil is permitted and indicates a desire to delete Key.
1617
Value []byte
@@ -67,10 +68,6 @@ func BatchUpdate(
6768
seenDigestedKeys := make(map[Digest]struct{}, len(keyValueUpdates))
6869

6970
for i, keyValue := range keyValueUpdates {
70-
if len(keyValue.Key) == 0 {
71-
return NodeKey{}, fmt.Errorf("%d-th keyValueUpdate: key is empty", i)
72-
}
73-
7471
keyDigest := DigestKey(keyValue.Key)
7572
if _, ok := seenDigestedKeys[keyDigest]; ok {
7673
return NodeKey{}, fmt.Errorf("%d-th keyValueUpdate: duplicate key %v in keyValueUpdates", i, keyValue.Key)
@@ -599,7 +596,7 @@ func Read(
599596

600597
if n, ok := rootNode.(*LeafNode); ok {
601598
if n.KeyDigest == DigestKey(key) {
602-
return n.Value, nil
599+
return util.NilCoalesceSlice(n.Value), nil
603600
} else {
604601
return nil, nil
605602
}
@@ -788,7 +785,10 @@ func readRange(
788785
truncated = true
789786
} else {
790787
keysPlusValuesBytes += len(n.Key) + len(n.Value)
791-
keyValues = append(keyValues, KeyValue{n.Key, n.Value})
788+
keyValues = append(keyValues, KeyValue{
789+
util.NilCoalesceSlice(n.Key),
790+
util.NilCoalesceSlice(n.Value),
791+
})
792792
}
793793
}
794794
}

offchainreporting2plus/internal/managed/track_config.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,25 @@ func (state *trackConfigState) run() {
2828
// Check immediately after startup
2929
tCheckLatestConfigDetails := time.After(0)
3030

31-
chNotify := state.configTracker.Notify()
31+
ignoreNotify := false
3232

3333
for {
34+
var chNotify <-chan struct{}
35+
if ignoreNotify {
36+
chNotify = nil
37+
} else {
38+
chNotify = state.configTracker.Notify()
39+
}
40+
3441
select {
3542
case _, ok := <-chNotify:
3643
if ok {
3744
// Check immediately for new config
3845
tCheckLatestConfigDetails = time.After(0 * time.Second)
3946
state.logger.Info("TrackConfig: ContractConfigTracker.Notify() fired", nil)
4047
} else {
41-
chNotify = nil
48+
ignoreNotify = true
49+
chNotify = nil //nolint:ineffassign
4250
state.logger.Error("TrackConfig: ContractConfigTracker.Notify() was closed, which should never happen. Will ignore ContractConfigTracker.Notify() from now", nil)
4351
}
4452
case <-tCheckLatestConfigDetails:

offchainreporting2plus/internal/ocr3_1/protocol/state_sync_tree.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ func (stasy *stateSyncState[RI]) messageTreeSyncChunkResponse(msg MessageTreeSyn
479479
stasy.treeSyncState.logger.Warn("could not create kv read/write transaction", commontypes.LogFields{
480480
"err": err,
481481
})
482+
return
482483
}
483484
defer kvReadWriteTxn.Discard()
484485

offchainreporting2plus/internal/shim/ocr3_1_key_value_store.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@ func (s *SemanticOCR3_1KeyValueDatabaseReadWriteTransaction) Commit() error {
211211
}
212212

213213
func (s *SemanticOCR3_1KeyValueDatabaseReadWriteTransaction) Delete(key []byte) error {
214+
key = util.NilCoalesceSlice(key)
215+
214216
if !(len(key) <= ocr3_1types.MaxMaxKeyValueKeyBytes) {
215217
return fmt.Errorf("key length %d exceeds maximum %d", len(key), ocr3_1types.MaxMaxKeyValueKeyBytes)
216218
}
@@ -434,6 +436,9 @@ func (s *SemanticOCR3_1KeyValueDatabaseReadWriteTransaction) ApplyWriteSet(write
434436
}
435437

436438
func (s *SemanticOCR3_1KeyValueDatabaseReadWriteTransaction) Write(key []byte, value []byte) error {
439+
key = util.NilCoalesceSlice(key)
440+
value = util.NilCoalesceSlice(value)
441+
437442
start := time.Now()
438443
defer func() {
439444
s.metrics.txWriteDurationSeconds.Observe(float64(time.Since(start).Seconds()))
@@ -446,8 +451,6 @@ func (s *SemanticOCR3_1KeyValueDatabaseReadWriteTransaction) Write(key []byte, v
446451
return fmt.Errorf("value length %d exceeds maximum %d", len(value), ocr3_1types.MaxMaxKeyValueValueBytes)
447452
}
448453

449-
value = util.NilCoalesceSlice(value)
450-
451454
s.mu.Lock()
452455
if s.closedForWriting {
453456
s.mu.Unlock()
@@ -482,6 +485,8 @@ func (s *SemanticOCR3_1KeyValueDatabaseReadTransaction) Discard() {
482485
}
483486

484487
func (s *SemanticOCR3_1KeyValueDatabaseReadTransaction) Read(key []byte) ([]byte, error) {
488+
key = util.NilCoalesceSlice(key)
489+
485490
if !(len(key) <= ocr3_1types.MaxMaxKeyValueKeyBytes) {
486491
return nil, fmt.Errorf("key length %d exceeds maximum %d", len(key), ocr3_1types.MaxMaxKeyValueKeyBytes)
487492
}
@@ -714,7 +719,8 @@ func (s *SemanticOCR3_1KeyValueDatabaseReadWriteTransaction) VerifyAndWriteTreeS
714719
prevIdx := startIndex
715720
for i, kv := range keyValues {
716721
if kv.Value == nil {
717-
return protocol.VerifyAndWriteTreeSyncChunkResultByzantine, fmt.Errorf("leaf %v has nil value", kv)
722+
723+
keyValues[i].Value = []byte{}
718724
}
719725
idx := hashPluginKey(kv.Key)
720726
if bytes.Compare(idx[:], startIndex[:]) < 0 {

offchainreporting2plus/ocr3_1types/plugin.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,27 @@ type ReportingPluginFactory[RI any] interface {
2424
// Deprecated: Use KeyValueStateReader instead.
2525
type KeyValueReader = KeyValueStateReader
2626

27-
// Provides read access to the replicated KeyValueState.
27+
// KeyValueStateReader provides read access to the replicated KeyValueState.
2828
type KeyValueStateReader interface {
29-
// A return value of nil indicates that the key does not exist.
29+
// Read reads the value associated with the given key.
30+
// A nil key is interpreted as an empty slice.
31+
// If the key exists, the returned value will be non-nil.
32+
// If the key does not exist, it returns (nil, nil).
3033
Read(key []byte) ([]byte, error)
3134
}
3235

3336
// Deprecated: Use KeyValueStateReadWriter instead.
3437
type KeyValueReadWriter = KeyValueStateReadWriter
3538

36-
// Provides read and write access to the replicated KeyValueState.
39+
// KeyValueStateReadWriter provides read and write access to the replicated KeyValueState.
3740
type KeyValueStateReadWriter interface {
3841
KeyValueStateReader
42+
// Write writes the value for the provided key.
43+
// A nil key or value is interpreted as an empty slice.
44+
// To delete a key, use the Delete method.
3945
Write(key []byte, value []byte) error
46+
// Delete deletes the provided key and its associated value.
47+
// A nil key is interpreted as an empty slice.
4048
Delete(key []byte) error
4149
}
4250

@@ -67,10 +75,10 @@ type KeyValueStateReadWriter interface {
6775
// the round. For each report, ShouldAcceptAttestedReport will be called, iff
6876
// the oracle is in the set of transmitters for the report. If
6977
// ShouldAcceptAttestedReport returns true, ShouldTransmitAcceptedReport will be
70-
// called. However, an ReportingPlugin must also correctly handle the case where
78+
// called. However, a ReportingPlugin must also correctly handle the case where
7179
// faults occur.
7280
//
73-
// In particular, an ReportingPlugin must deal with cases where:
81+
// In particular, a ReportingPlugin must deal with cases where:
7482
// - only a subset of the functions on the ReportingPlugin are invoked for a
7583
// given round
7684
// - the observation returned by Observation is not included in the list of
@@ -83,7 +91,7 @@ type KeyValueStateReadWriter interface {
8391
//
8492
// # Engineering requirements for ReportingPlugin implementations
8593
//
86-
// All functions on an ReportingPlugin must be thread-safe.
94+
// All functions on a ReportingPlugin must be thread-safe.
8795
//
8896
// The execution of the functions in the ReportingPlugin is on the critical path
8997
// of the protocol's execution. A blocking function may block the oracle from
@@ -111,7 +119,7 @@ type KeyValueStateReadWriter interface {
111119
// a function from returning an error on context expiration.
112120
//
113121
// For a given OCR protocol instance, there can be many (consecutive) instances
114-
// of an ReportingPlugin, e.g. due to software restarts. If you need
122+
// of a ReportingPlugin, e.g. due to software restarts. If you need
115123
// ReportingPlugin state to survive across restarts, you should probably
116124
// persist it in the KeyValueState. A ReportingPlugin instance will only ever serve a
117125
// single protocol instance. State is not preserved between protocol instances.
@@ -282,8 +290,8 @@ type ReportingPlugin[RI any] interface {
282290
//
283291
// Don't do anything slow in here.
284292
//
285-
// The KeyValueReader gives read access to the key-value store in the state
286-
// that it is after the StateTransition for seqNr is computed.
293+
// The keyValueStateReader gives read access to the replicated KeyValueState
294+
// in the state that it is after the StateTransition for seqNr is computed.
287295
Committed(
288296
ctx context.Context,
289297
seqNr uint64,
@@ -327,7 +335,7 @@ type ReportingPlugin[RI any] interface {
327335
// As mentioned above, you should gracefully handle only a subset of a
328336
// ReportingPlugin's functions being invoked for a given report. For
329337
// example, due to reloading persisted pending transmissions from the
330-
// database upon oracle restart, this function may be called with reports
338+
// database upon oracle restart, this function may be called with reports
331339
// that no other function of this instance of this interface has ever
332340
// been invoked on.
333341
ShouldTransmitAcceptedReport(

offchainreporting2plus/ocr3types/plugin.go

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ type ReportingPluginConfig struct {
5555

5656
type ReportWithInfo[RI any] struct {
5757
Report types.Report
58-
// Metadata about the report passed to transmitter, keyring, etc..., e.g.
58+
// Metadata about the report passed to transmitter, keyring, etc., e.g.
5959
// to trace flow of report through the system.
6060
Info RI
6161
}
@@ -108,15 +108,15 @@ type OutcomeContext struct {
108108
// A ReportingPlugin allows plugging custom logic into the OCR3 protocol. The
109109
// OCR protocol handles cryptography, networking, ensuring that a sufficient
110110
// number of nodes is in agreement about any report, transmitting the report to
111-
// the contract, etc... The ReportingPlugin handles application-specific logic.
111+
// the contract, etc. The ReportingPlugin handles application-specific logic.
112112
// To do so, the ReportingPlugin defines a number of callbacks that are called
113113
// by the OCR protocol logic at certain points in the protocol's execution flow.
114114
// The report generated by the ReportingPlugin must be in a format understood by
115-
// contract that the reports are transmitted to.
115+
// the contract that the reports are transmitted to.
116116
//
117117
// We assume that each correct node participating in the protocol instance will
118118
// be running the same ReportingPlugin implementation. However, not all nodes
119-
// may be correct; up to f nodes be faulty in arbitrary ways (aka byzantine
119+
// may be correct; up to f nodes may be faulty in arbitrary ways (aka byzantine
120120
// faults). For example, faulty nodes could be down, have intermittent
121121
// connectivity issues, send garbage messages, or be controlled by an adversary.
122122
//
@@ -126,10 +126,10 @@ type OutcomeContext struct {
126126
// the round. For each report, ShouldAcceptAttestedReport will be called, iff
127127
// the oracle is in the set of transmitters for the report. If
128128
// ShouldAcceptAttestedReport returns true, ShouldTransmitAcceptedReport will be
129-
// called. However, an ReportingPlugin must also correctly handle the case where
129+
// called. However, a ReportingPlugin must also correctly handle the case where
130130
// faults occur.
131131
//
132-
// In particular, an ReportingPlugin must deal with cases where:
132+
// In particular, a ReportingPlugin must deal with cases where:
133133
//
134134
// - only a subset of the functions on the ReportingPlugin are invoked for a
135135
// given round
@@ -147,7 +147,7 @@ type OutcomeContext struct {
147147
// call traces. E.g., the ReportingPlugin's Observation function may have been
148148
// invoked on node A, but not on node B.
149149
//
150-
// All functions on an ReportingPlugin should be thread-safe.
150+
// All functions on a ReportingPlugin should be thread-safe.
151151
//
152152
// The execution of the functions in the ReportingPlugin is on the critical path
153153
// of the protocol's execution. A blocking function may block the oracle from
@@ -160,10 +160,10 @@ type OutcomeContext struct {
160160
// to configure timeouts.
161161
//
162162
// For a given OCR protocol instance, there can be many (consecutive) instances
163-
// of an ReportingPlugin, e.g. due to software restarts. If you need
163+
// of a ReportingPlugin, e.g. due to software restarts. If you need
164164
// ReportingPlugin state to survive across restarts, you should store it in the
165165
// Outcome or persist it. A ReportingPlugin instance will only ever serve a
166-
// single protocol instance. Outcomes and other state are are not preserved
166+
// single protocol instance. Outcomes and other state are not preserved
167167
// between protocol instances. A fresh protocol instance will start with a clean
168168
// state. Carrying state between different protocol instances is up to the
169169
// ReportingPlugin logic.
@@ -179,7 +179,7 @@ type ReportingPlugin[RI any] interface {
179179
//
180180
// You may assume that the outctx.SeqNr is increasing monotonically (though
181181
// *not* strictly) across the lifetime of a protocol instance and that
182-
// outctx.previousOutcome contains the consensus outcome with sequence
182+
// outctx.PreviousOutcome contains the consensus outcome with sequence
183183
// number (outctx.SeqNr-1).
184184
Query(ctx context.Context, outctx OutcomeContext) (types.Query, error)
185185

@@ -188,18 +188,18 @@ type ReportingPlugin[RI any] interface {
188188
//
189189
// You may assume that the outctx.SeqNr is increasing monotonically (though
190190
// *not* strictly) across the lifetime of a protocol instance and that
191-
// outctx.previousOutcome contains the consensus outcome with sequence
191+
// outctx.PreviousOutcome contains the consensus outcome with sequence
192192
// number (outctx.SeqNr-1).
193193
Observation(ctx context.Context, outctx OutcomeContext, query types.Query) (types.Observation, error)
194194

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

@@ -227,7 +227,7 @@ type ReportingPlugin[RI any] interface {
227227
//
228228
// You may assume that the outctx.SeqNr is increasing monotonically (though
229229
// *not* strictly) across the lifetime of a protocol instance and that
230-
// outctx.previousOutcome contains the consensus outcome with sequence
230+
// outctx.PreviousOutcome contains the consensus outcome with sequence
231231
// number (outctx.SeqNr-1).
232232
//
233233
// You may assume that the provided list of attributed observations has been
@@ -244,10 +244,9 @@ type ReportingPlugin[RI any] interface {
244244
// This is likely to change in the future. It will likely be returning a
245245
// list of report batches, where each batch goes into its own Merkle tree.
246246
//
247-
// You may assume that the outctx.SeqNr is increasing monotonically (though
247+
// You may assume that seqNr is increasing monotonically (though
248248
// *not* strictly) across the lifetime of a protocol instance and that
249-
// outctx.previousOutcome contains the consensus outcome with sequence
250-
// number (outctx.SeqNr-1).
249+
// outcome contains the consensus outcome with sequence number (seqNr-1).
251250
Reports(ctx context.Context, seqNr uint64, outcome Outcome) ([]ReportPlus[RI], error)
252251

253252
// Decides whether a report should be accepted for transmission. Any report
@@ -269,7 +268,7 @@ type ReportingPlugin[RI any] interface {
269268
// As mentioned above, you should gracefully handle only a subset of a
270269
// ReportingPlugin's functions being invoked for a given report. For
271270
// example, due to reloading persisted pending transmissions from the
272-
// database upon oracle restart, this function may be called with reports
271+
// database upon oracle restart, this function may be called with reports
273272
// that no other function of this instance of this interface has ever
274273
// been invoked on.
275274
ShouldTransmitAcceptedReport(ctx context.Context, seqNr uint64, reportWithInfo ReportWithInfo[RI]) (bool, error)

0 commit comments

Comments
 (0)