Skip to content

Commit 9cc2ad7

Browse files
kaleofdutygtklockerstchrysa
committed
Improvements:
- OCR3.1 polishing Based on 9af8d7d0dc5ae8d7821e59d89fbede40bd964a70. Co-authored-by: Kostis Karantias <[email protected]> Co-authored-by: stchrysa <[email protected]>
1 parent eb1a9e0 commit 9cc2ad7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+6556
-2431
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ require (
1414
github.com/prometheus/client_golang v1.14.0
1515
github.com/sirupsen/logrus v1.9.3
1616
golang.org/x/crypto v0.41.0
17+
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
1718
google.golang.org/protobuf v1.36.6
1819
)
1920

@@ -103,7 +104,6 @@ require (
103104
github.com/tklauser/numcpus v0.6.1 // indirect
104105
github.com/urfave/cli/v2 v2.27.5 // indirect
105106
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
106-
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
107107
golang.org/x/mod v0.27.0 // indirect
108108
golang.org/x/net v0.43.0 // indirect
109109
golang.org/x/sync v0.16.0 // indirect

internal/mt/mt.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// In-memory Merkle Tree which implements an authenticated list.
2+
package mt
3+
4+
import (
5+
"crypto/sha256"
6+
"fmt"
7+
"iter"
8+
)
9+
10+
type Digest = [sha256.Size]byte
11+
12+
var MerklePlaceholderDigest = Digest([]byte("MERKLE_PLACEHOLDER_HASH_________"))
13+
var LeafSeparator = []byte("MT::LeafNode")
14+
var InternalSeparator = []byte("MT::InternalNode")
15+
16+
func digestInternal(leftChildDigest Digest, rightChildDigest Digest) Digest {
17+
hash := sha256.New()
18+
hash.Write(InternalSeparator)
19+
hash.Write(leftChildDigest[:])
20+
hash.Write(rightChildDigest[:])
21+
return Digest(hash.Sum(nil))
22+
}
23+
24+
func digestLeaf(preimage []byte) Digest {
25+
hash := sha256.New()
26+
hash.Write(LeafSeparator)
27+
hash.Write(preimage)
28+
return Digest(hash.Sum(nil))
29+
}
30+
31+
func buildTreeLevels(leafPreimages [][]byte) iter.Seq[[]Digest] {
32+
return func(yield func([]Digest) bool) {
33+
leafCount := len(leafPreimages)
34+
if leafCount == 0 {
35+
if !yield([]Digest{MerklePlaceholderDigest}) {
36+
return
37+
}
38+
}
39+
40+
// Start with the leaf digests
41+
currentLayer := make([]Digest, leafCount)
42+
for i, leafPreimage := range leafPreimages {
43+
currentLayer[i] = digestLeaf(leafPreimage)
44+
}
45+
if !yield(currentLayer) {
46+
return
47+
}
48+
49+
// Build the tree upwards, padding with placeholders when odd number of nodes
50+
for len(currentLayer) > 1 {
51+
nextLayerSize := (len(currentLayer) + 1) / 2 // Ceiling division
52+
nextLayer := make([]Digest, 0, nextLayerSize)
53+
54+
for i := 0; i < len(currentLayer); i += 2 {
55+
leftDigest := currentLayer[i]
56+
rightDigest := MerklePlaceholderDigest
57+
if i+1 < len(currentLayer) {
58+
rightDigest = currentLayer[i+1]
59+
}
60+
nextLayer = append(nextLayer, digestInternal(leftDigest, rightDigest))
61+
}
62+
currentLayer = nextLayer
63+
if !yield(currentLayer) {
64+
return
65+
}
66+
}
67+
}
68+
}
69+
70+
// Root computes the Merkle tree root from leaf preimages.
71+
func Root(leafPreimages [][]byte) Digest {
72+
var rootDigest Digest
73+
for treeLevel := range buildTreeLevels(leafPreimages) {
74+
if len(treeLevel) == 1 {
75+
rootDigest = treeLevel[0]
76+
}
77+
}
78+
return rootDigest
79+
}
80+
81+
// Prove generates a Merkle inclusion proof that the leafPreimage at index is
82+
// included in the tree rooted at Root(leafPreimages).
83+
func Prove(leafPreimages [][]byte, index uint64) ([]Digest, error) {
84+
leafCount := len(leafPreimages)
85+
if leafCount == 0 {
86+
return nil, fmt.Errorf("cannot prove inclusion in empty tree")
87+
}
88+
if index >= uint64(leafCount) {
89+
return nil, fmt.Errorf("index %d is out of bounds for %d leaves", index, leafCount)
90+
}
91+
92+
var proof []Digest
93+
currentIndex := index
94+
95+
for treeLevel := range buildTreeLevels(leafPreimages) {
96+
if len(treeLevel) <= 1 {
97+
break
98+
}
99+
siblingIndex := currentIndex ^ 1
100+
siblingDigest := MerklePlaceholderDigest
101+
if siblingIndex < uint64(len(treeLevel)) {
102+
siblingDigest = treeLevel[siblingIndex]
103+
}
104+
proof = append(proof, siblingDigest)
105+
currentIndex /= 2
106+
}
107+
108+
return proof, nil
109+
}
110+
111+
// Verify verifies that leafPreimage is preimage of the index-th leaf in the
112+
// Merkle tree rooted at expectedRootDigest.
113+
func Verify(expectedRootDigest Digest, index uint64, leafPreimage []byte, proof []Digest) error {
114+
currentDigest := digestLeaf(leafPreimage)
115+
currentIndex := index
116+
117+
for _, siblingDigest := range proof {
118+
if currentIndex%2 == 0 {
119+
// Current node is left child, sibling is right
120+
currentDigest = digestInternal(currentDigest, siblingDigest)
121+
} else {
122+
// Current node is right child, sibling is left
123+
currentDigest = digestInternal(siblingDigest, currentDigest)
124+
}
125+
currentIndex /= 2
126+
}
127+
128+
if currentDigest != expectedRootDigest {
129+
return fmt.Errorf("computed root digest mismatch: computed %x, expected %x", currentDigest, expectedRootDigest)
130+
}
131+
132+
return nil
133+
}

internal/util/generic.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
package util
22

3+
import "golang.org/x/exp/constraints"
4+
35
func PointerTo[T any](v T) *T {
46
return &v
57
}
68

9+
func PointerIntegerCast[U constraints.Integer, T constraints.Integer](p *T) *U {
10+
if p == nil {
11+
return nil
12+
}
13+
v := U(*p)
14+
return &v
15+
}
16+
717
func NilCoalesce[T any](maybe *T, default_ T) T {
818
if maybe != nil {
919
return *maybe

networking/ragedisco/discovery_protocol.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
// Maximum number of distinct oracles that we can have across groups.
2121
// The exact number is chosen arbitrarily. Better to have an arbitrary limit
2222
// than no limit.
23+
// See also [ragetypes.MaxPeersPerHost].
2324
const MaxOracles = 165
2425

2526
type incomingMessage struct {

offchainreporting2plus/internal/config/netconfig/netconfig.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config"
77
"github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr2config"
8+
"github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3_1config"
89
"github.com/smartcontractkit/libocr/offchainreporting2plus/internal/config/ocr3config"
910
"github.com/smartcontractkit/libocr/offchainreporting2plus/types"
1011
)
@@ -37,6 +38,16 @@ func NetConfigFromContractConfig(contractConfig types.ContractConfig) (NetConfig
3738
publicConfig.F,
3839
peerIDs(publicConfig.OracleIdentities),
3940
}, nil
41+
case config.OCR3_1OffchainConfigVersion:
42+
publicConfig, err := ocr3_1config.PublicConfigFromContractConfig(true, contractConfig)
43+
if err != nil {
44+
return NetConfig{}, err
45+
}
46+
return NetConfig{
47+
publicConfig.ConfigDigest,
48+
publicConfig.F,
49+
peerIDs(publicConfig.OracleIdentities),
50+
}, nil
4051
default:
4152
return NetConfig{}, fmt.Errorf("NetConfigFromContractConfig received OffchainConfigVersion %v", contractConfig.OffchainConfigVersion)
4253
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package ocr3_1config
2+
3+
import (
4+
"math"
5+
"time"
6+
7+
"github.com/smartcontractkit/libocr/offchainreporting2plus/internal/ocr3_1/maxmaxserializationlimits"
8+
"github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3_1types"
9+
"github.com/smartcontractkit/libocr/offchainreporting2plus/types"
10+
)
11+
12+
const (
13+
defaultSmallRequestSizeMinRequestToSameOracleInterval = 10 * time.Millisecond
14+
15+
assumedRTT = 500 * time.Millisecond
16+
assumedBandwidthBitsPerSecond = 100e6 // 100Mbit
17+
assumedBandwidthBytesPerSecond = assumedBandwidthBitsPerSecond / 8
18+
)
19+
20+
// transferDuration calculates the duration required to transfer the given
21+
// number of bytes at the assumed bandwidth
22+
func transferDuration(bytes int) time.Duration {
23+
seconds := float64(bytes) / float64(assumedBandwidthBytesPerSecond)
24+
return time.Duration(seconds * float64(time.Second))
25+
}
26+
27+
func roundUpToTenthOfSecond(duration time.Duration) time.Duration {
28+
tenthsOfSecond := float64(duration.Milliseconds()) / 100
29+
return time.Duration(math.Ceil(tenthsOfSecond)) * 100 * time.Millisecond
30+
}
31+
32+
func DefaultDeltaInitial() time.Duration {
33+
return roundUpToTenthOfSecond(
34+
3*assumedRTT/2 +
35+
transferDuration(maxmaxserializationlimits.MaxMaxEpochStartRequestBytes*types.MaxOracles+maxmaxserializationlimits.MaxMaxEpochStartBytes))
36+
}
37+
38+
func DefaultDeltaReportsPlusPrecursorRequest() time.Duration {
39+
return roundUpToTenthOfSecond(
40+
assumedRTT +
41+
transferDuration(maxmaxserializationlimits.MaxMaxReportsPlusPrecursorRequestBytes+maxmaxserializationlimits.MaxMaxReportsPlusPrecursorBytes))
42+
}
43+
44+
func DefaultDeltaBlockSyncResponseTimeout() time.Duration {
45+
return roundUpToTenthOfSecond(
46+
assumedRTT +
47+
transferDuration(maxmaxserializationlimits.MaxMaxBlockSyncRequestBytes+maxmaxserializationlimits.MaxMaxBlockSyncResponseBytes))
48+
}
49+
50+
func DefaultDeltaTreeSyncResponseTimeout() time.Duration {
51+
return roundUpToTenthOfSecond(
52+
assumedRTT +
53+
transferDuration(maxmaxserializationlimits.MaxMaxTreeSyncChunkRequestBytes+maxmaxserializationlimits.MaxMaxTreeSyncChunkResponseBytes))
54+
}
55+
56+
func DefaultDeltaBlobChunkResponseTimeout() time.Duration {
57+
return roundUpToTenthOfSecond(
58+
assumedRTT +
59+
transferDuration(maxmaxserializationlimits.MaxMaxBlobChunkRequestBytes+maxmaxserializationlimits.MaxMaxBlobChunkResponseBytes))
60+
}
61+
62+
const (
63+
DefaultDeltaResend = 5 * time.Second
64+
65+
DefaultDeltaStateSyncSummaryInterval = 5 * time.Second
66+
DefaultDeltaBlockSyncMinRequestToSameOracleInterval = defaultSmallRequestSizeMinRequestToSameOracleInterval
67+
68+
DefaultMaxBlocksPerBlockSyncResponse = 2
69+
DefaultMaxParallelRequestedBlocks = 100
70+
71+
DefaultDeltaTreeSyncMinRequestToSameOracleInterval = defaultSmallRequestSizeMinRequestToSameOracleInterval
72+
73+
DefaultMaxTreeSyncChunkKeys = 1024
74+
75+
// A tree sync chunk must always fit at least 1 maximally sized (using maxmax) key-value pair
76+
DefaultMaxTreeSyncChunkKeysPlusValuesBytes = ocr3_1types.MaxMaxKeyValueKeyBytes + ocr3_1types.MaxMaxKeyValueValueBytes
77+
78+
DefaultMaxParallelTreeSyncChunkFetches = 8
79+
80+
DefaultSnapshotInterval = 10_000
81+
DefaultMaxHistoricalSnapshotsRetained = 10
82+
83+
DefaultDeltaBlobOfferMinRequestToSameOracleInterval = defaultSmallRequestSizeMinRequestToSameOracleInterval
84+
DefaultDeltaBlobOfferResponseTimeout = 10 * time.Second
85+
86+
DefaultDeltaBlobBroadcastGrace = 10 * time.Millisecond
87+
88+
DefaultDeltaBlobChunkMinRequestToSameOracleInterval = defaultSmallRequestSizeMinRequestToSameOracleInterval
89+
90+
DefaultBlobChunkBytes = 1_000_000 // 1MB
91+
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package ocr3_1config
2+
3+
const (
4+
MaxMaxBlocksPerBlockSyncResponse = 2
5+
MaxMaxTreeSyncChunkKeys = 10_000
6+
MaxMaxTreeSyncChunkKeysPlusValuesBytes = 50_000_000 // 50MB
7+
MaxMaxBlobChunkBytes = 10_000_000 // 10MB
8+
)

0 commit comments

Comments
 (0)