Support frame fragmentation and dissemination (#396)

* Add clock frame fragment message

* Add clock frame fragment validation

* Add clock frame fragmentation utilities

* Add clock frame fragmentation message handling

* Report publication errors

* Publish info list after frame

* Add frame publish configuration

* Publish clock frame fragments

* Update BlossomSub dashboard

* Publish clock frame fragments in parallel
This commit is contained in:
petricadaipegsp 2024-12-02 23:25:10 +01:00 committed by GitHub
parent f5f7eb243b
commit d1e65c1c92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 2426 additions and 212 deletions

View File

@ -85,7 +85,7 @@ require (
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/jbenet/goprocess v0.1.4 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/koron/go-ssdp v0.0.4 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
@ -164,7 +164,7 @@ require (
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gonum.org/v1/gonum v0.13.0 // indirect

View File

@ -248,8 +248,10 @@ github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/klauspost/reedsolomon v1.12.4 h1:5aDr3ZGoJbgu/8+j45KtUJxzYm8k08JGtB9Wx1VQ4OA=
github.com/klauspost/reedsolomon v1.12.4/go.mod h1:d3CzOMOt0JXGIFZm1StgkyF14EYr3xneR2rNWo7NcMU=
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -664,8 +666,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

View File

@ -192,6 +192,13 @@
"regex": "AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAABAA(.*)",
"renamePattern": "Data Peer Announcements$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAEA(.*)",
"renamePattern": "Data Frame Fragments$1"
}
}
],
"type": "timeseries"
@ -323,6 +330,13 @@
"regex": "AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAABAA(.*)",
"renamePattern": "Data Peer Announcements$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAEA(.*)",
"renamePattern": "Data Frame Fragments$1"
}
}
],
"type": "timeseries"
@ -454,6 +468,13 @@
"regex": "AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAABAA(.*)",
"renamePattern": "Data Peer Announcements$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAEA(.*)",
"renamePattern": "Data Frame Fragments$1"
}
}
],
"type": "timeseries"
@ -585,6 +606,13 @@
"regex": "AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAABAA(.*)",
"renamePattern": "Data Peer Announcements$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAEA(.*)",
"renamePattern": "Data Frame Fragments$1"
}
}
],
"type": "timeseries"
@ -894,6 +922,27 @@
"renamePattern": "Data Peer Announcements Shard 3$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA(.*)",
"renamePattern": "Data Frame Fragments Shard 1$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAA(.*)",
"renamePattern": "Data Frame Fragments Shard 2$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA(.*)",
"renamePattern": "Data Frame Fragments Shard 3$1"
}
},
{
"id": "renameByRegex",
"options": {
@ -1196,6 +1245,27 @@
"renamePattern": "Data Peer Announcements Shard 3$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA(.*)",
"renamePattern": "Data Frame Fragments Shard 1$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAA(.*)",
"renamePattern": "Data Frame Fragments Shard 2$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA(.*)",
"renamePattern": "Data Frame Fragments Shard 3$1"
}
},
{
"id": "renameByRegex",
"options": {
@ -1497,6 +1567,27 @@
"renamePattern": "Data Peer Announcements Shard 3$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA(.*)",
"renamePattern": "Data Frame Fragments Shard 1$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAA(.*)",
"renamePattern": "Data Frame Fragments Shard 2$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA(.*)",
"renamePattern": "Data Frame Fragments Shard 3$1"
}
},
{
"id": "renameByRegex",
"options": {
@ -1507,6 +1598,264 @@
],
"type": "timeseries"
},
{
"datasource": {
"default": false,
"type": "prometheus",
"uid": "${datasource}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "pps"
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 47
},
"id": 23,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"min",
"max",
"mean"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true,
"sortBy": "Name",
"sortDesc": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(rate(blossomsub_iwant_messages_count{job=~\"$job\", instance=~\"$host\", direction=\"drop\"}[$__rate_interval]))",
"hide": false,
"instant": false,
"legendFormat": "Dropped",
"range": true,
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"editorMode": "code",
"expr": "sum(rate(blossomsub_iwant_messages_count{job=~\"$job\", instance=~\"$host\", direction=\"recv\"}[$__rate_interval]))",
"hide": false,
"instant": false,
"legendFormat": "Received",
"range": true,
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(rate(blossomsub_iwant_messages_count{job=~\"$job\", instance=~\"$host\", direction=\"send\"}[$__rate_interval]))",
"hide": false,
"instant": false,
"legendFormat": "Sent",
"range": true,
"refId": "C"
}
],
"title": "IWANT message rates",
"type": "timeseries"
},
{
"datasource": {
"default": false,
"type": "prometheus",
"uid": "${datasource}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "pps"
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 47
},
"id": 24,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"min",
"max",
"mean"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true,
"sortBy": "Name",
"sortDesc": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(rate(blossomsub_ihave_messages_count{job=~\"$job\", instance=~\"$host\", direction=\"drop\"}[$__rate_interval]))",
"hide": false,
"instant": false,
"legendFormat": "Dropped",
"range": true,
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"editorMode": "code",
"expr": "sum(rate(blossomsub_ihave_messages_count{job=~\"$job\", instance=~\"$host\", direction=\"recv\"}[$__rate_interval]))",
"hide": false,
"instant": false,
"legendFormat": "Received",
"range": true,
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(rate(blossomsub_ihave_messages_count{job=~\"$job\", instance=~\"$host\", direction=\"send\"}[$__rate_interval]))",
"hide": false,
"instant": false,
"legendFormat": "Sent",
"range": true,
"refId": "C"
}
],
"title": "IHAVE message rates",
"type": "timeseries"
},
{
"datasource": {
"default": false,
@ -1570,7 +1919,7 @@
"h": 9,
"w": 12,
"x": 0,
"y": 47
"y": 56
},
"id": 19,
"options": {
@ -1698,7 +2047,7 @@
"h": 9,
"w": 12,
"x": 12,
"y": 47
"y": 56
},
"id": 20,
"options": {
@ -1825,7 +2174,7 @@
"h": 9,
"w": 12,
"x": 0,
"y": 56
"y": 65
},
"id": 21,
"options": {
@ -1953,7 +2302,7 @@
"h": 9,
"w": 12,
"x": 12,
"y": 56
"y": 65
},
"id": 22,
"options": {
@ -1982,7 +2331,7 @@
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "rate(blossomsub_idontwant_messages_count{job=~\"$job\", instance=~\"$host\", direction=\"drop\"}[$__rate_interval])",
"expr": "sum(rate(blossomsub_idontwant_messages_count{job=~\"$job\", instance=~\"$host\", direction=\"drop\"}[$__rate_interval]))",
"hide": false,
"instant": false,
"legendFormat": "Dropped",
@ -1995,7 +2344,7 @@
"uid": "${datasource}"
},
"editorMode": "code",
"expr": "rate(blossomsub_idontwant_messages_count{job=~\"$job\", instance=~\"$host\", direction=\"recv\"}[$__rate_interval])",
"expr": "sum(rate(blossomsub_idontwant_messages_count{job=~\"$job\", instance=~\"$host\", direction=\"recv\"}[$__rate_interval]))",
"hide": false,
"instant": false,
"legendFormat": "Received",
@ -2008,7 +2357,7 @@
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "rate(blossomsub_idontwant_messages_count{job=~\"$job\", instance=~\"$host\", direction=\"send\"}[$__rate_interval])",
"expr": "sum(rate(blossomsub_idontwant_messages_count{job=~\"$job\", instance=~\"$host\", direction=\"send\"}[$__rate_interval]))",
"hide": false,
"instant": false,
"legendFormat": "Sent",
@ -2025,7 +2374,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 65
"y": 74
},
"id": 8,
"panels": [],
@ -2093,7 +2442,7 @@
"h": 9,
"w": 24,
"x": 0,
"y": 66
"y": 75
},
"id": 11,
"options": {
@ -2194,6 +2543,27 @@
"renamePattern": "Data Peer Announcements Shard 3$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA(.*)",
"renamePattern": "Data Frame Fragments Shard 1$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAA(.*)",
"renamePattern": "Data Frame Fragments Shard 2$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA(.*)",
"renamePattern": "Data Frame Fragments Shard 3$1"
}
},
{
"id": "renameByRegex",
"options": {
@ -2266,7 +2636,7 @@
"h": 9,
"w": 12,
"x": 0,
"y": 75
"y": 84
},
"id": 9,
"options": {
@ -2367,6 +2737,27 @@
"renamePattern": "Data Peer Announcements Shard 3$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA(.*)",
"renamePattern": "Data Frame Fragments Shard 1$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAA(.*)",
"renamePattern": "Data Frame Fragments Shard 2$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA(.*)",
"renamePattern": "Data Frame Fragments Shard 3$1"
}
},
{
"id": "renameByRegex",
"options": {
@ -2439,7 +2830,7 @@
"h": 9,
"w": 12,
"x": 12,
"y": 75
"y": 84
},
"id": 10,
"options": {
@ -2540,6 +2931,27 @@
"renamePattern": "Data Peer Announcements Shard 3$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA(.*)",
"renamePattern": "Data Frame Fragments Shard 1$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAA(.*)",
"renamePattern": "Data Frame Fragments Shard 2$1"
}
},
{
"id": "renameByRegex",
"options": {
"regex": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA(.*)",
"renamePattern": "Data Frame Fragments Shard 3$1"
}
},
{
"id": "renameByRegex",
"options": {
@ -2556,7 +2968,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 84
"y": 93
},
"id": 6,
"panels": [],
@ -2612,8 +3024,7 @@
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
"color": "green"
}
]
},
@ -2625,7 +3036,7 @@
"h": 9,
"w": 24,
"x": 0,
"y": 85
"y": 94
},
"id": 7,
"options": {
@ -2768,6 +3179,6 @@
"timezone": "browser",
"title": "BlossomSub",
"uid": "ee47pcfax962ob",
"version": 45,
"version": 55,
"weekStart": ""
}

View File

@ -2,6 +2,70 @@ package config
import "time"
type FramePublishFragmentationReedSolomonConfig struct {
// The number of data shards to use for Reed-Solomon encoding and decoding.
DataShards int `yaml:"dataShards"`
// The number of parity shards to use for Reed-Solomon encoding and decoding.
ParityShards int `yaml:"parityShards"`
}
// WithDefaults sets default values for any fields that are not set.
func (c FramePublishFragmentationReedSolomonConfig) WithDefaults() FramePublishFragmentationReedSolomonConfig {
cpy := c
if cpy.DataShards == 0 {
cpy.DataShards = 224
}
if cpy.ParityShards == 0 {
cpy.ParityShards = 32
}
return cpy
}
type FramePublishFragmentationConfig struct {
// The algorithm to use for fragmenting and reassembling frames.
// Options: "reed-solomon".
Algorithm string `yaml:"algorithm"`
// The configuration for Reed-Solomon fragmentation.
ReedSolomon FramePublishFragmentationReedSolomonConfig `yaml:"reedSolomon"`
}
// WithDefaults sets default values for any fields that are not set.
func (c FramePublishFragmentationConfig) WithDefaults() FramePublishFragmentationConfig {
cpy := c
if cpy.Algorithm == "" {
cpy.Algorithm = "reed-solomon"
}
cpy.ReedSolomon = cpy.ReedSolomon.WithDefaults()
return cpy
}
type FramePublishConfig struct {
// The publish mode to use for the node.
// Options: "full", "fragmented", "dual", "threshold".
Mode string `yaml:"mode"`
// The threshold for switching between full and fragmented frame publishing.
Threshold int `yaml:"threshold"`
// The configuration for frame fragmentation.
Fragmentation FramePublishFragmentationConfig `yaml:"fragmentation"`
// The size of the ballast added to a frame.
// NOTE: This option exists solely for testing purposes and should not be
// modified in production.
BallastSize int `yaml:"ballastSize"`
}
// WithDefaults sets default values for any fields that are not set.
func (c FramePublishConfig) WithDefaults() FramePublishConfig {
cpy := c
if cpy.Mode == "" {
cpy.Mode = "full"
}
if cpy.Threshold == 0 {
cpy.Threshold = 1 * 1024 * 1024
}
cpy.Fragmentation = cpy.Fragmentation.WithDefaults()
return cpy
}
type EngineConfig struct {
ProvingKeyId string `yaml:"provingKeyId"`
Filter string `yaml:"filter"`
@ -36,4 +100,7 @@ type EngineConfig struct {
Difficulty uint32 `yaml:"difficulty"`
// Whether to allow GOMAXPROCS values above the number of physical cores.
AllowExcessiveGOMAXPROCS bool `yaml:"allowExcessiveGOMAXPROCS"`
// EXPERIMENTAL: The configuration for frame publishing.
FramePublish FramePublishConfig `yaml:"framePublish"`
}

View File

@ -1,7 +1,11 @@
package data
import (
"crypto"
"crypto/rand"
mrand "math/rand"
"strings"
"sync"
"time"
"github.com/iden3/go-iden3-crypto/poseidon"
@ -11,6 +15,8 @@ import (
"google.golang.org/protobuf/types/known/anypb"
"source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub/pb"
"source.quilibrium.com/quilibrium/monorepo/node/config"
"source.quilibrium.com/quilibrium/monorepo/node/consensus/data/fragmentation"
qruntime "source.quilibrium.com/quilibrium/monorepo/node/internal/runtime"
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
)
@ -27,6 +33,19 @@ func (e *DataClockConsensusEngine) handleFrameMessage(
return nil
}
func (e *DataClockConsensusEngine) handleFrameFragmentMessage(
message *pb.Message,
) error {
select {
case <-e.ctx.Done():
return e.ctx.Err()
case e.frameFragmentMessageProcessorCh <- message:
default:
e.logger.Warn("dropping frame fragment message")
}
return nil
}
func (e *DataClockConsensusEngine) handleTxMessage(
message *pb.Message,
) error {
@ -77,6 +96,95 @@ func (e *DataClockConsensusEngine) publishProof(
),
reachability: reachability,
}
e.peerMapMx.Unlock()
cfg := e.config.Engine.FramePublish.WithDefaults()
if cfg.BallastSize > 0 {
frame = proto.Clone(frame).(*protobufs.ClockFrame)
frame.Padding = make([]byte, cfg.BallastSize)
}
publishFragmented := func() error {
var splitter fragmentation.ClockFrameSplitter
switch cfg := cfg.Fragmentation; cfg.Algorithm {
case "reed-solomon":
var err error
splitter, err = fragmentation.NewReedSolomonClockFrameSplitter(
cfg.ReedSolomon.DataShards,
cfg.ReedSolomon.ParityShards,
)
if err != nil {
return errors.Wrap(err, "creating reed-solomon splitter")
}
default:
return errors.Errorf("unsupported fragmentation algorithm: %s", cfg.Algorithm)
}
fragments, err := splitter.SplitClockFrame(frame)
if err != nil {
return errors.Wrap(err, "splitting clock frame")
}
mrand.Shuffle(len(fragments), func(i, j int) {
fragments[i], fragments[j] = fragments[j], fragments[i]
})
sign := func(b []byte) ([]byte, error) {
return e.provingKey.Sign(rand.Reader, b, crypto.Hash(0))
}
var wg sync.WaitGroup
defer wg.Wait()
throttle := make(chan struct{}, qruntime.WorkerCount(0, false))
for _, fragment := range fragments {
throttle <- struct{}{}
wg.Add(1)
go func(fragment *protobufs.ClockFrameFragment) {
defer func() { <-throttle }()
defer wg.Done()
if err := fragment.SignED448(e.provingKeyBytes, sign); err != nil {
e.logger.Error("error signing clock frame fragment", zap.Error(err))
return
}
if err := e.publishMessage(e.frameFragmentFilter, fragment); err != nil {
e.logger.Error("error publishing clock frame fragment", zap.Error(err))
}
}(fragment)
}
return nil
}
publishFull := func() error {
if err := e.publishMessage(e.frameFilter, frame); err != nil {
e.logger.Error("error publishing clock frame", zap.Error(err))
}
return nil
}
switch cfg.Mode {
case "full":
if err := publishFull(); err != nil {
return err
}
case "fragmented":
if err := publishFragmented(); err != nil {
return err
}
case "dual":
if err := publishFragmented(); err != nil {
return err
}
if err := publishFull(); err != nil {
return err
}
case "threshold":
if proto.Size(frame) >= cfg.Threshold {
if err := publishFragmented(); err != nil {
return err
}
} else {
if err := publishFull(); err != nil {
return err
}
}
default:
return errors.Errorf("unsupported frame publish mode: %s", cfg.Mode)
}
list := &protobufs.DataPeerListAnnounce{
Peer: &protobufs.DataPeer{
PeerId: nil,
@ -91,13 +199,10 @@ func (e *DataClockConsensusEngine) publishProof(
ExternallyReachable: reachability,
},
}
e.peerMapMx.Unlock()
if err := e.publishMessage(e.infoFilter, list); err != nil {
e.logger.Debug("error publishing message", zap.Error(err))
e.logger.Debug("error publishing data peer list announce", zap.Error(err))
}
e.publishMessage(e.frameFilter, frame)
return nil
}

View File

@ -25,6 +25,7 @@ import (
"source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub/pb"
"source.quilibrium.com/quilibrium/monorepo/node/config"
"source.quilibrium.com/quilibrium/monorepo/node/consensus"
"source.quilibrium.com/quilibrium/monorepo/node/consensus/data/fragmentation"
qtime "source.quilibrium.com/quilibrium/monorepo/node/consensus/time"
qcrypto "source.quilibrium.com/quilibrium/monorepo/node/crypto"
"source.quilibrium.com/quilibrium/monorepo/node/execution"
@ -107,39 +108,42 @@ type DataClockConsensusEngine struct {
currentReceivingSyncPeers int
announcedJoin int
frameChan chan *protobufs.ClockFrame
executionEngines map[string]execution.ExecutionEngine
filter []byte
txFilter []byte
infoFilter []byte
frameFilter []byte
input []byte
parentSelector []byte
syncingStatus SyncStatusType
syncingTarget []byte
previousHead *protobufs.ClockFrame
engineMx sync.Mutex
dependencyMapMx sync.Mutex
stagedTransactions *protobufs.TokenRequests
stagedTransactionsSet map[string]struct{}
stagedTransactionsMx sync.Mutex
validationFilter map[string]struct{}
validationFilterMx sync.Mutex
peerMapMx sync.RWMutex
peerAnnounceMapMx sync.Mutex
lastKeyBundleAnnouncementFrame uint64
peerMap map[string]*peerInfo
uncooperativePeersMap map[string]*peerInfo
frameMessageProcessorCh chan *pb.Message
txMessageProcessorCh chan *pb.Message
infoMessageProcessorCh chan *pb.Message
report *protobufs.SelfTestReport
clients []protobufs.DataIPCServiceClient
grpcRateLimiter *RateLimiter
previousFrameProven *protobufs.ClockFrame
previousTree *mt.MerkleTree
clientReconnectTest int
requestSyncCh chan struct{}
frameChan chan *protobufs.ClockFrame
executionEngines map[string]execution.ExecutionEngine
filter []byte
txFilter []byte
infoFilter []byte
frameFilter []byte
frameFragmentFilter []byte
input []byte
parentSelector []byte
syncingStatus SyncStatusType
syncingTarget []byte
previousHead *protobufs.ClockFrame
engineMx sync.Mutex
dependencyMapMx sync.Mutex
stagedTransactions *protobufs.TokenRequests
stagedTransactionsSet map[string]struct{}
stagedTransactionsMx sync.Mutex
validationFilter map[string]struct{}
validationFilterMx sync.Mutex
peerMapMx sync.RWMutex
peerAnnounceMapMx sync.Mutex
lastKeyBundleAnnouncementFrame uint64
peerMap map[string]*peerInfo
uncooperativePeersMap map[string]*peerInfo
frameMessageProcessorCh chan *pb.Message
frameFragmentMessageProcessorCh chan *pb.Message
txMessageProcessorCh chan *pb.Message
infoMessageProcessorCh chan *pb.Message
report *protobufs.SelfTestReport
clients []protobufs.DataIPCServiceClient
grpcRateLimiter *RateLimiter
previousFrameProven *protobufs.ClockFrame
previousTree *mt.MerkleTree
clientReconnectTest int
requestSyncCh chan struct{}
clockFrameFragmentBuffer fragmentation.ClockFrameFragmentBuffer
}
var _ consensus.DataConsensusEngine = (*DataClockConsensusEngine)(nil)
@ -229,6 +233,14 @@ func NewDataClockConsensusEngine(
rateLimit = 10
}
clockFrameFragmentBuffer, err := fragmentation.NewClockFrameFragmentCircularBuffer(
fragmentation.NewReedSolomonClockFrameFragmentBuffer,
16,
)
if err != nil {
panic(err)
}
ctx, cancel := context.WithCancel(context.Background())
e := &DataClockConsensusEngine{
ctx: ctx,
@ -251,30 +263,32 @@ func NewDataClockConsensusEngine(
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
currentReceivingSyncPeers: 0,
lastFrameReceivedAt: time.Time{},
frameProverTries: []*tries.RollingFrecencyCritbitTrie{},
inclusionProver: inclusionProver,
syncingStatus: SyncStatusNotSyncing,
peerMap: map[string]*peerInfo{},
uncooperativePeersMap: map[string]*peerInfo{},
minimumPeersRequired: minimumPeersRequired,
report: report,
frameProver: frameProver,
masterTimeReel: masterTimeReel,
dataTimeReel: dataTimeReel,
peerInfoManager: peerInfoManager,
frameMessageProcessorCh: make(chan *pb.Message, 65536),
txMessageProcessorCh: make(chan *pb.Message, 65536),
infoMessageProcessorCh: make(chan *pb.Message, 65536),
config: cfg,
preMidnightMint: map[string]struct{}{},
currentReceivingSyncPeers: 0,
lastFrameReceivedAt: time.Time{},
frameProverTries: []*tries.RollingFrecencyCritbitTrie{},
inclusionProver: inclusionProver,
syncingStatus: SyncStatusNotSyncing,
peerMap: map[string]*peerInfo{},
uncooperativePeersMap: map[string]*peerInfo{},
minimumPeersRequired: minimumPeersRequired,
report: report,
frameProver: frameProver,
masterTimeReel: masterTimeReel,
dataTimeReel: dataTimeReel,
peerInfoManager: peerInfoManager,
frameMessageProcessorCh: make(chan *pb.Message, 65536),
frameFragmentMessageProcessorCh: make(chan *pb.Message, 65536),
txMessageProcessorCh: make(chan *pb.Message, 65536),
infoMessageProcessorCh: make(chan *pb.Message, 65536),
config: cfg,
preMidnightMint: map[string]struct{}{},
grpcRateLimiter: NewRateLimiter(
rateLimit,
time.Minute,
),
requestSyncCh: make(chan struct{}, 1),
validationFilter: map[string]struct{}{},
requestSyncCh: make(chan struct{}, 1),
validationFilter: map[string]struct{}{},
clockFrameFragmentBuffer: clockFrameFragmentBuffer,
}
logger.Info("constructing consensus engine")
@ -287,6 +301,7 @@ func NewDataClockConsensusEngine(
e.txFilter = append([]byte{0x00}, e.filter...)
e.infoFilter = append([]byte{0x00, 0x00}, e.filter...)
e.frameFilter = append([]byte{0x00, 0x00, 0x00}, e.filter...)
e.frameFragmentFilter = append([]byte{0x00, 0x00, 0x00, 0x00}, e.filter...)
e.input = seed
e.provingKey = signer
e.provingKeyType = keyType
@ -319,16 +334,19 @@ func (e *DataClockConsensusEngine) Start() <-chan error {
panic(err)
}
e.wg.Add(3)
e.wg.Add(4)
go e.runFrameMessageHandler()
go e.runFrameFragmentMessageHandler()
go e.runTxMessageHandler()
go e.runInfoMessageHandler()
e.logger.Info("subscribing to pubsub messages")
e.pubSub.RegisterValidator(e.frameFilter, e.validateFrameMessage, true)
e.pubSub.RegisterValidator(e.frameFragmentFilter, e.validateFrameFragmentMessage, true)
e.pubSub.RegisterValidator(e.txFilter, e.validateTxMessage, true)
e.pubSub.RegisterValidator(e.infoFilter, e.validateInfoMessage, true)
e.pubSub.Subscribe(e.frameFilter, e.handleFrameMessage)
e.pubSub.Subscribe(e.frameFragmentFilter, e.handleFrameFragmentMessage)
e.pubSub.Subscribe(e.txFilter, e.handleTxMessage)
e.pubSub.Subscribe(e.infoFilter, e.handleInfoMessage)
go func() {
@ -504,7 +522,7 @@ func (e *DataClockConsensusEngine) Start() <-chan error {
)
if err := e.publishMessage(e.infoFilter, list); err != nil {
e.logger.Debug("error publishing message", zap.Error(err))
e.logger.Debug("error publishing data peer list announce", zap.Error(err))
}
if thresholdBeforeConfirming > 0 {
@ -662,7 +680,9 @@ func (e *DataClockConsensusEngine) Stop(force bool) <-chan error {
panic(err)
}
e.publishMessage(e.txFilter, pause.TokenRequest())
if err := e.publishMessage(e.txFilter, pause.TokenRequest()); err != nil {
e.logger.Warn("error publishing prover pause", zap.Error(err))
}
wg := sync.WaitGroup{}
wg.Add(len(e.executionEngines))
@ -684,9 +704,11 @@ func (e *DataClockConsensusEngine) Stop(force bool) <-chan error {
}
e.pubSub.Unsubscribe(e.frameFilter, false)
e.pubSub.Unsubscribe(e.frameFragmentFilter, false)
e.pubSub.Unsubscribe(e.txFilter, false)
e.pubSub.Unsubscribe(e.infoFilter, false)
e.pubSub.UnregisterValidator(e.frameFilter)
e.pubSub.UnregisterValidator(e.frameFragmentFilter)
e.pubSub.UnregisterValidator(e.txFilter)
e.pubSub.UnregisterValidator(e.infoFilter)

View File

@ -0,0 +1,371 @@
package fragmentation
import (
"bytes"
"errors"
"github.com/klauspost/reedsolomon"
"google.golang.org/protobuf/proto"
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
)
// ClockFrameSplitter is an interface for splitting a ClockFrame into fragments.
type ClockFrameSplitter interface {
// SplitClockFrame splits a ClockFrame into fragments.
// The fragments are unsigned, and must be signed before being sent.
SplitClockFrame(frame *protobufs.ClockFrame) ([]*protobufs.ClockFrameFragment, error)
}
type reedSolomonClockFrameSplitter struct {
dataShardCount int
parityShardCount int
}
// NewReedSolomonClockFrameSplitter creates a new ReedSolomonClockFrameSplitter.
func NewReedSolomonClockFrameSplitter(
dataShardCount int,
parityShardCount int,
) (ClockFrameSplitter, error) {
if dataShardCount == 0 {
return nil, errors.New("dataShardCount must be greater than 0")
}
if parityShardCount == 0 {
return nil, errors.New("parityShardCount must be greater than 0")
}
if dataShardCount+parityShardCount > 256 {
return nil, errors.New("dataShardCount + parityShardCount must be less than or equal to 256")
}
return &reedSolomonClockFrameSplitter{
dataShardCount: dataShardCount,
parityShardCount: parityShardCount,
}, nil
}
// SplitClockFrame implements ClockFrameSplitter.
func (r *reedSolomonClockFrameSplitter) SplitClockFrame(frame *protobufs.ClockFrame) ([]*protobufs.ClockFrameFragment, error) {
bs, err := proto.Marshal(frame)
if err != nil {
return nil, err
}
fragmentSize := len(bs) / r.dataShardCount
if len(bs)%r.dataShardCount != 0 {
fragmentSize++
}
if fragmentSize == 0 {
return nil, errors.New("ClockFrame is too small")
}
if n := fragmentSize % 64; n != 0 {
fragmentSize += 64 - n
}
shards := make([][]byte, r.dataShardCount+r.parityShardCount)
for i := 0; i < len(bs); i += fragmentSize {
shard := bs[i:]
if len(shard) > fragmentSize {
shard = shard[:fragmentSize]
}
shards[i/fragmentSize] = shard
}
for i := len(bs) / fragmentSize; i < r.dataShardCount; i++ {
if n := len(shards[i]); n < fragmentSize {
shards[i] = append(shards[i], make([]byte, fragmentSize-n)...)
}
}
for i := r.dataShardCount; i < r.dataShardCount+r.parityShardCount; i++ {
shards[i] = make([]byte, fragmentSize)
}
enc, err := reedsolomon.New(
r.dataShardCount,
r.parityShardCount,
reedsolomon.WithAutoGoroutines(fragmentSize),
)
if err != nil {
return nil, err
}
if err := enc.Encode(shards); err != nil {
return nil, err
}
h := hash(bs)
fragments := make([]*protobufs.ClockFrameFragment, r.dataShardCount+r.parityShardCount)
for i, shard := range shards {
fragments[i] = &protobufs.ClockFrameFragment{
Filter: frame.Filter,
FrameNumber: frame.FrameNumber,
Timestamp: frame.Timestamp,
FrameHash: h,
Encoding: &protobufs.ClockFrameFragment_ReedSolomon{
ReedSolomon: &protobufs.ClockFrameFragment_ReedSolomonEncoding{
FrameSize: uint64(len(bs)),
FragmentShard: uint64(i),
FragmentDataShardCount: uint64(r.dataShardCount),
FragmentParityShardCount: uint64(r.parityShardCount),
FragmentData: shard,
},
},
}
}
return fragments, nil
}
// ClockFrameAssembler is an interface for assembling a ClockFrame from fragments.
type ClockFrameAssembler interface {
// AssembleClockFrame assembles a ClockFrame from fragments.
AssembleClockFrame(fragments []*protobufs.ClockFrameFragment) (*protobufs.ClockFrame, error)
}
type reedSolomonClockFrameAssembler struct{}
// NewReedSolomonClockFrameAssembler creates a new ReedSolomonClockFrameAssembler.
func NewReedSolomonClockFrameAssembler() ClockFrameAssembler {
return &reedSolomonClockFrameAssembler{}
}
// AssembleClockFrame implements ClockFrameAssembler.
func (r *reedSolomonClockFrameAssembler) AssembleClockFrame(fragments []*protobufs.ClockFrameFragment) (*protobufs.ClockFrame, error) {
if len(fragments) == 0 {
return nil, errors.New("no fragments")
}
var (
frameNumber uint64
filter []byte
timestamp int64
frameHash []byte
dataShardCount, parityShardCount int
fragmentSize int
frameSize int
)
for _, fragment := range fragments {
if fragment == nil {
return nil, errors.New("fragment is nil")
}
switch {
case frameNumber == 0:
frameNumber = fragment.FrameNumber
case frameNumber != fragment.FrameNumber:
return nil, errors.New("inconsistent frame number")
case len(filter) == 0:
filter = fragment.Filter
case !bytes.Equal(filter, fragment.Filter):
return nil, errors.New("inconsistent filter")
case timestamp == 0:
timestamp = fragment.Timestamp
case timestamp != fragment.Timestamp:
return nil, errors.New("inconsistent timestamp")
case len(frameHash) == 0:
frameHash = fragment.FrameHash
case !bytes.Equal(frameHash, fragment.FrameHash):
return nil, errors.New("inconsistent frame hash")
}
fragment := fragment.GetReedSolomon()
if fragment == nil {
return nil, errors.New("fragment is not ReedSolomon")
}
switch {
case dataShardCount == 0:
dataShardCount = int(fragment.FragmentDataShardCount)
parityShardCount = int(fragment.FragmentParityShardCount)
case dataShardCount != int(fragment.FragmentDataShardCount):
return nil, errors.New("inconsistent data shard count")
case parityShardCount != int(fragment.FragmentParityShardCount):
return nil, errors.New("inconsistent parity shard count")
case dataShardCount+parityShardCount <= int(fragment.FragmentShard):
return nil, errors.New("shard out of bounds")
case fragmentSize == 0:
fragmentSize = len(fragment.FragmentData)
case len(fragment.FragmentData) != fragmentSize:
return nil, errors.New("inconsistent fragment size")
case frameSize == 0:
frameSize = int(fragment.FrameSize)
case int(fragment.FrameSize) != frameSize:
return nil, errors.New("inconsistent frame size")
}
}
shards := make([][]byte, dataShardCount+parityShardCount)
for _, fragment := range fragments {
fragment := fragment.GetReedSolomon()
shard := fragment.FragmentShard
if shards[shard] != nil {
return nil, errors.New("duplicate shard")
}
shards[shard] = fragment.FragmentData
}
enc, err := reedsolomon.New(
dataShardCount,
parityShardCount,
reedsolomon.WithAutoGoroutines(fragmentSize),
)
if err != nil {
return nil, err
}
if err := enc.ReconstructData(shards); err != nil {
return nil, err
}
bs := make([]byte, 0, dataShardCount*fragmentSize)
for _, shard := range shards[:dataShardCount] {
bs = append(bs, shard...)
}
bs = bs[:frameSize]
if h := hash(bs); !bytes.Equal(h, frameHash) {
return nil, errors.New("frame hash mismatch")
}
frame := &protobufs.ClockFrame{}
if err := proto.Unmarshal(bs, frame); err != nil {
return nil, err
}
return frame, nil
}
// ClockFrameFragmentBuffer is an interface for buffering ClockFrameFragments and assembling ClockFrames.
type ClockFrameFragmentBuffer interface {
// AccumulateClockFrameFragment accumulates a ClockFrameFragment.
// If sufficient fragments are available, the ClockFrame is returned.
// How fragments from different frames are handled is implementation-specific.
AccumulateClockFrameFragment(fragment *protobufs.ClockFrameFragment) (*protobufs.ClockFrame, error)
}
type clockFrameFragmentCircularBuffer struct {
newBuffer func() ClockFrameFragmentBuffer
maxSize int
buffers map[[hashSize]byte]ClockFrameFragmentBuffer
keys [][hashSize]byte
built map[[hashSize]byte]struct{}
builtKeys [][hashSize]byte
}
// NewClockFrameFragmentCircularBuffer creates a new ClockFrameFragmentBuffer.
// The newBuffer function is called to create a new ClockFrameFragmentBuffer.
// The maxSize parameter specifies the maximum number of buffers to keep.
// If maxSize buffers are already in use, the oldest buffer is removed.
func NewClockFrameFragmentCircularBuffer(
newBuffer func() ClockFrameFragmentBuffer,
maxSize int,
) (ClockFrameFragmentBuffer, error) {
if newBuffer == nil {
return nil, errors.New("newBuffer is nil")
}
if maxSize <= 0 {
return nil, errors.New("maxSize must be greater than 0")
}
return &clockFrameFragmentCircularBuffer{
newBuffer: newBuffer,
maxSize: maxSize,
buffers: make(map[[hashSize]byte]ClockFrameFragmentBuffer, maxSize),
keys: make([][hashSize]byte, 0, maxSize),
built: make(map[[hashSize]byte]struct{}, maxSize),
builtKeys: make([][hashSize]byte, 0, maxSize),
}, nil
}
// AccumulateClockFrameFragment implements ClockFrameFragmentBuffer.
func (c *clockFrameFragmentCircularBuffer) AccumulateClockFrameFragment(fragment *protobufs.ClockFrameFragment) (*protobufs.ClockFrame, error) {
if fragment == nil {
return nil, errors.New("fragment is nil")
}
if len(fragment.FrameHash) != hashSize {
return nil, errors.New("invalid frame hash size")
}
key := [hashSize]byte(fragment.FrameHash)
if _, ok := c.built[key]; ok {
return nil, nil
}
buffer, ok := c.buffers[key]
if !ok {
if len(c.buffers) == c.maxSize {
delete(c.buffers, c.keys[0])
c.keys = append(c.keys[:0], c.keys[1:]...)
}
buffer = c.newBuffer()
c.buffers[key] = buffer
c.keys = append(c.keys, key)
}
frame, err := buffer.AccumulateClockFrameFragment(fragment)
if err != nil {
return nil, err
}
if frame != nil {
delete(c.buffers, key)
for i, k := range c.keys {
if k == key {
c.keys = append(c.keys[:i], c.keys[i+1:]...)
break
}
}
if len(c.built) == c.maxSize {
delete(c.built, c.builtKeys[0])
c.builtKeys = append(c.builtKeys[:0], c.builtKeys[1:]...)
}
c.built[key] = struct{}{}
c.builtKeys = append(c.builtKeys, key)
}
return frame, nil
}
type reedSolomonClockFrameFragmentBuffer struct {
fragments []*protobufs.ClockFrameFragment
have map[uint64]struct{}
}
// NewReedSolomonClockFrameFragmentBuffer creates a new ReedSolomonClockFrameFragmentBuffer.
func NewReedSolomonClockFrameFragmentBuffer() ClockFrameFragmentBuffer {
return &reedSolomonClockFrameFragmentBuffer{
fragments: make([]*protobufs.ClockFrameFragment, 0, 256),
have: make(map[uint64]struct{}, 256),
}
}
// AccumulateClockFrameFragment implements ClockFrameFragmentBuffer.
func (r *reedSolomonClockFrameFragmentBuffer) AccumulateClockFrameFragment(fragment *protobufs.ClockFrameFragment) (*protobufs.ClockFrame, error) {
if fragment == nil {
return nil, errors.New("fragment is nil")
}
if fragment.GetReedSolomon() == nil {
return nil, errors.New("fragment is not ReedSolomon")
}
var templateRS *protobufs.ClockFrameFragment_ReedSolomonEncoding
if len(r.fragments) == 0 {
templateRS = fragment.GetReedSolomon()
} else {
template := r.fragments[0]
if !bytes.Equal(template.Filter, fragment.Filter) {
return nil, errors.New("inconsistent filter")
}
if template.FrameNumber != fragment.FrameNumber {
return nil, errors.New("inconsistent frame number")
}
if template.Timestamp != fragment.Timestamp {
return nil, errors.New("inconsistent timestamp")
}
if !bytes.Equal(template.FrameHash, fragment.FrameHash) {
return nil, errors.New("inconsistent frame hash")
}
templateRS = template.GetReedSolomon()
fragmentRS := fragment.GetReedSolomon()
if templateRS.FrameSize != fragmentRS.FrameSize {
return nil, errors.New("inconsistent frame size")
}
if templateRS.FragmentDataShardCount+templateRS.FragmentParityShardCount <= fragmentRS.FragmentShard {
return nil, errors.New("shard out of bounds")
}
if _, ok := r.have[fragmentRS.FragmentShard]; ok {
return nil, errors.New("duplicate shard")
}
if templateRS.FragmentDataShardCount != fragmentRS.FragmentDataShardCount {
return nil, errors.New("inconsistent data shard count")
}
if templateRS.FragmentParityShardCount != fragmentRS.FragmentParityShardCount {
return nil, errors.New("inconsistent parity shard count")
}
if len(templateRS.FragmentData) != len(fragmentRS.FragmentData) {
return nil, errors.New("inconsistent fragment size")
}
}
r.fragments = append(r.fragments, fragment)
r.have[templateRS.FragmentShard] = struct{}{}
if len(r.fragments) < int(templateRS.FragmentDataShardCount) {
return nil, nil
}
assembler := NewReedSolomonClockFrameAssembler()
frame, err := assembler.AssembleClockFrame(r.fragments)
r.fragments = r.fragments[:0]
clear(r.have)
return frame, err
}

View File

@ -0,0 +1,414 @@
package fragmentation_test
import (
"bytes"
"crypto/rand"
"fmt"
mrand "math/rand"
"slices"
"testing"
"google.golang.org/protobuf/proto"
"source.quilibrium.com/quilibrium/monorepo/node/consensus/data/fragmentation"
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
)
func BenchmarkReedSolomonClockFrameFragmentation(b *testing.B) {
frame := &protobufs.ClockFrame{
Filter: bytes.Repeat([]byte{0x01}, 32),
FrameNumber: 123,
Timestamp: 456,
Padding: make([]byte, 20*1024*1024),
}
if _, err := rand.Read(frame.Padding); err != nil {
b.Fatal(err)
}
benchmarkCases := []struct {
dataShardCount int
parityShardCount int
}{
{
dataShardCount: 4,
parityShardCount: 2,
},
{
dataShardCount: 8,
parityShardCount: 4,
},
{
dataShardCount: 16,
parityShardCount: 8,
},
{
dataShardCount: 32,
parityShardCount: 16,
},
{
dataShardCount: 48,
parityShardCount: 16,
},
{
dataShardCount: 64,
parityShardCount: 32,
},
{
dataShardCount: 128,
parityShardCount: 64,
},
{
dataShardCount: 192,
parityShardCount: 64,
},
{
dataShardCount: 224,
parityShardCount: 32,
},
}
b.Run("Splitter", func(b *testing.B) {
for _, bc := range benchmarkCases {
b.Run(fmt.Sprintf("DS_%d/PS_%d", bc.dataShardCount, bc.parityShardCount), func(b *testing.B) {
for i := 0; i < b.N; i++ {
splitter, err := fragmentation.NewReedSolomonClockFrameSplitter(bc.dataShardCount, bc.parityShardCount)
if err != nil {
b.Fatal(err)
}
if _, err := splitter.SplitClockFrame(frame); err != nil {
b.Fatal(err)
}
}
})
}
})
b.Run("Assembler", func(b *testing.B) {
for _, bc := range benchmarkCases {
b.Run(fmt.Sprintf("DS_%d/PS_%d", bc.dataShardCount, bc.parityShardCount), func(b *testing.B) {
splitter, err := fragmentation.NewReedSolomonClockFrameSplitter(bc.dataShardCount, bc.parityShardCount)
if err != nil {
b.Fatal(err)
}
fragments, err := splitter.SplitClockFrame(frame)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
assembler := fragmentation.NewReedSolomonClockFrameAssembler()
if _, err := assembler.AssembleClockFrame(fragments); err != nil {
b.Fatal(err)
}
}
})
}
})
}
func TestReedSolomonClockFrameFragmentation(t *testing.T) {
splitter, err := fragmentation.NewReedSolomonClockFrameSplitter(4, 2)
if err != nil {
t.Fatal(err)
}
originalFrame := &protobufs.ClockFrame{
Filter: bytes.Repeat([]byte{0x01}, 32),
FrameNumber: 123,
Timestamp: 456,
Padding: make([]byte, 20*1024*1024),
}
if _, err := rand.Read(originalFrame.Padding); err != nil {
t.Fatal(err)
}
fragments, err := splitter.SplitClockFrame(originalFrame)
if err != nil {
t.Fatal(err)
}
if len(fragments) != 6 {
t.Fatalf("fragment count mismatch: %d, expected %d", len(fragments), 5)
}
for _, fragment := range fragments {
if fragment.FrameNumber != 123 {
t.Fatalf("frame number mismatch: %d, expected %d", fragment.FrameNumber, 123)
}
if !bytes.Equal(fragment.Filter, bytes.Repeat([]byte{0x01}, 32)) {
t.Fatalf("filter mismatch")
}
if fragment.Timestamp != 456 {
t.Fatalf("timestamp mismatch: %d, expected %d", fragment.Timestamp, 456)
}
}
for _, tc := range []struct {
name string
erase []int
expectError bool
}{
{
name: "no erasures",
erase: nil,
expectError: false,
},
{
name: "one erasure",
erase: []int{0},
expectError: false,
},
{
name: "two erasures",
erase: []int{2, 0},
expectError: false,
},
{
name: "three erasures",
erase: []int{2, 4, 0},
expectError: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
fragments := slices.Clone(fragments)
for _, idx := range tc.erase {
fragments[idx] = nil
}
for i, fragment := range fragments {
if fragment == nil {
fragments = append(fragments[:i], fragments[i+1:]...)
}
}
assembler := fragmentation.NewReedSolomonClockFrameAssembler()
assembledFrame, err := assembler.AssembleClockFrame(fragments)
switch {
case tc.expectError:
if err == nil {
t.Fatal("expected error")
}
return
case err != nil:
t.Fatal(err)
}
if !proto.Equal(assembledFrame, originalFrame) {
t.Fatalf("frame mismatch")
}
})
}
}
func TestClockFrameFragmentCircularBuffer(t *testing.T) {
t.Parallel()
splitter, err := fragmentation.NewReedSolomonClockFrameSplitter(4, 2)
if err != nil {
t.Fatal(err)
}
originalFrames := []*protobufs.ClockFrame{
{
Filter: bytes.Repeat([]byte{0x01}, 32),
FrameNumber: 123,
Timestamp: 456,
Padding: make([]byte, 20*1024*1024),
},
{
Filter: bytes.Repeat([]byte{0x02}, 32),
FrameNumber: 124,
Timestamp: 457,
Padding: make([]byte, 20*1024*1024),
},
{
Filter: bytes.Repeat([]byte{0x03}, 32),
FrameNumber: 125,
Timestamp: 458,
Padding: make([]byte, 20*1024*1024),
},
}
fragments := make([][]*protobufs.ClockFrameFragment, len(originalFrames))
for i, originalFrame := range originalFrames {
if _, err := rand.Read(originalFrame.Padding); err != nil {
t.Fatal(err)
}
fragments[i], err = splitter.SplitClockFrame(originalFrame)
if err != nil {
t.Fatal(err)
}
}
allFragments := slices.Concat(fragments...)
mrand.Shuffle(len(allFragments), func(i, j int) {
allFragments[i], allFragments[j] = allFragments[j], allFragments[i]
})
buffer, err := fragmentation.NewClockFrameFragmentCircularBuffer(
fragmentation.NewReedSolomonClockFrameFragmentBuffer,
3,
)
if err != nil {
t.Fatal(err)
}
var seen [3]bool
for _, fragment := range allFragments {
frame, err := buffer.AccumulateClockFrameFragment(fragment)
if err != nil {
t.Fatal(err)
}
if frame == nil {
continue
}
if !proto.Equal(frame, originalFrames[frame.FrameNumber-123]) {
t.Fatalf("frame mismatch")
}
if seen[frame.FrameNumber-123] {
t.Fatal("duplicate frame")
}
seen[frame.FrameNumber-123] = true
}
for i := range seen {
if !seen[i] {
t.Fatalf("missing frame: %d", i+123)
}
}
buffer, err = fragmentation.NewClockFrameFragmentCircularBuffer(
fragmentation.NewReedSolomonClockFrameFragmentBuffer,
2,
)
if err != nil {
t.Fatal(err)
}
clear(seen[:])
for _, fragments := range fragments {
for _, fragment := range fragments {
frame, err := buffer.AccumulateClockFrameFragment(fragment)
if err != nil {
t.Fatal(err)
}
if frame == nil {
continue
}
if !proto.Equal(frame, originalFrames[frame.FrameNumber-123]) {
t.Fatalf("frame mismatch")
}
if seen[frame.FrameNumber-123] {
t.Fatal("duplicate frame")
}
seen[frame.FrameNumber-123] = true
}
}
for i := range seen {
if !seen[i] {
t.Fatalf("missing frame: %d", i+123)
}
}
}
func TestReedSolomonClockFrameFragmentBuffer(t *testing.T) {
splitter, err := fragmentation.NewReedSolomonClockFrameSplitter(4, 2)
if err != nil {
t.Fatal(err)
}
originalFrame := &protobufs.ClockFrame{
Filter: bytes.Repeat([]byte{0x01}, 32),
FrameNumber: 123,
Timestamp: 456,
Padding: make([]byte, 20*1024*1024),
}
if _, err := rand.Read(originalFrame.Padding); err != nil {
t.Fatal(err)
}
fragments, err := splitter.SplitClockFrame(originalFrame)
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
name string
fragments []*protobufs.ClockFrameFragment
errorIdx int
frameIdx int
}{
{
name: "one insert",
fragments: []*protobufs.ClockFrameFragment{
fragments[0],
},
errorIdx: -1,
frameIdx: -1,
},
{
name: "two insert",
fragments: []*protobufs.ClockFrameFragment{
fragments[0], fragments[2],
},
errorIdx: -1,
frameIdx: -1,
},
{
name: "three insert",
fragments: []*protobufs.ClockFrameFragment{
fragments[0], fragments[4], fragments[2],
},
errorIdx: -1,
frameIdx: -1,
},
{
name: "four insert",
fragments: []*protobufs.ClockFrameFragment{
fragments[0], fragments[4], fragments[1], fragments[2],
},
errorIdx: -1,
frameIdx: 3,
},
{
name: "one insert, one bogus",
fragments: []*protobufs.ClockFrameFragment{
fragments[0],
{
FrameNumber: 123,
Filter: bytes.Repeat([]byte{0x01}, 32),
Timestamp: 456,
},
},
errorIdx: 1,
frameIdx: -1,
},
{
name: "one insert, one duplicate",
fragments: []*protobufs.ClockFrameFragment{
fragments[0], fragments[0],
},
errorIdx: 1,
frameIdx: -1,
},
{
name: "four insert, one bogus",
fragments: []*protobufs.ClockFrameFragment{
fragments[0], fragments[2], fragments[4],
{
FrameNumber: 123,
Filter: bytes.Repeat([]byte{0x01}, 32),
Timestamp: 456,
},
fragments[1],
},
errorIdx: 3,
frameIdx: 4,
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
buffer := fragmentation.NewReedSolomonClockFrameFragmentBuffer()
for i, fragment := range tc.fragments {
frame, err := buffer.AccumulateClockFrameFragment(fragment)
switch {
case tc.errorIdx == i:
if err == nil {
t.Fatal("expected error")
}
continue
case err != nil:
t.Fatal(err)
}
switch {
case tc.frameIdx == i:
if frame == nil {
t.Fatal("expected frame")
}
if !proto.Equal(frame, originalFrame) {
t.Fatalf("frame mismatch")
}
case frame != nil:
t.Fatal("unexpected frame")
}
}
})
}
}

View File

@ -0,0 +1,10 @@
package fragmentation
import "crypto/sha256"
const hashSize = 32
func hash(b []byte) []byte {
var h [hashSize]byte = sha256.Sum256(b)
return h[:]
}

View File

@ -0,0 +1,100 @@
package fragmentation_test
import (
"crypto/rand"
"crypto/sha256"
"testing"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/blake2s"
"golang.org/x/crypto/sha3"
)
func BenchmarkHashFunctions(b *testing.B) {
data := make([]byte, 20*1024*1024)
if _, err := rand.Read(data); err != nil {
b.Fatal(err)
}
for _, bc := range []struct {
name string
f func([]byte) []byte
}{
{
name: "SHA256-224",
f: func(data []byte) []byte {
b := sha256.Sum224(data)
return b[:]
},
},
{
name: "SHA256-256",
f: func(data []byte) []byte {
b := sha256.Sum256(data)
return b[:]
},
},
{
name: "SHA3-224",
f: func(data []byte) []byte {
b := sha3.Sum224(data)
return b[:]
},
},
{
name: "SHA3-256",
f: func(data []byte) []byte {
b := sha3.Sum256(data)
return b[:]
},
},
{
name: "SHA3-384",
f: func(data []byte) []byte {
b := sha3.Sum384(data)
return b[:]
},
},
{
name: "SHA3-512",
f: func(data []byte) []byte {
b := sha3.Sum512(data)
return b[:]
},
},
{
name: "BLAKE2b-256",
f: func(data []byte) []byte {
b := blake2b.Sum256(data)
return b[:]
},
},
{
name: "BLAKE2b-384",
f: func(data []byte) []byte {
b := blake2b.Sum384(data)
return b[:]
},
},
{
name: "BLAKE2b-512",
f: func(data []byte) []byte {
b := blake2b.Sum512(data)
return b[:]
},
},
{
name: "BLAKE2s-256",
f: func(data []byte) []byte {
b := blake2s.Sum256(data)
return b[:]
},
},
} {
b.Run(bc.name, func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = bc.f(data)
}
})
}
}

View File

@ -191,8 +191,8 @@ func (e *DataClockConsensusEngine) runLoop() {
e.validationFilter = make(map[string]struct{}, len(e.validationFilter))
e.validationFilterMx.Unlock()
if e.FrameProverTrieContains(0, e.provingKeyAddress) {
if err = e.publishProof(dataFrame); err != nil {
e.logger.Error("could not publish", zap.Error(err))
if err := e.publishProof(dataFrame); err != nil {
e.logger.Error("could not publish proof", zap.Error(err))
e.stateMx.Lock()
if e.state < consensus.EngineStateStopping {
e.state = consensus.EngineStateCollecting
@ -375,7 +375,9 @@ func (e *DataClockConsensusEngine) processFrame(
zap.Duration("frame_age", frametime.Since(latestFrame)),
)
e.publishMessage(e.txFilter, mint.TokenRequest())
if err := e.publishMessage(e.txFilter, mint.TokenRequest()); err != nil {
e.logger.Error("could not publish mint", zap.Error(err))
}
if e.config.Engine.AutoMergeCoins {
_, addrs, _, err := e.coinStore.GetCoinsForOwner(
@ -412,7 +414,9 @@ func (e *DataClockConsensusEngine) processFrame(
return latestFrame
}
e.publishMessage(e.txFilter, merge.TokenRequest())
if err := e.publishMessage(e.txFilter, merge.TokenRequest()); err != nil {
e.logger.Warn("could not publish merge", zap.Error(err))
}
}
}
}

View File

@ -30,13 +30,13 @@ func (e *DataClockConsensusEngine) runFrameMessageHandler() {
msg := &protobufs.Message{}
if err := proto.Unmarshal(message.Data, msg); err != nil {
e.logger.Debug("bad message")
e.logger.Debug("cannot unmarshal data", zap.Error(err))
continue
}
a := &anypb.Any{}
if err := proto.Unmarshal(msg.Payload, a); err != nil {
e.logger.Error("error while unmarshaling", zap.Error(err))
e.logger.Debug("cannot unmarshal payload", zap.Error(err))
continue
}
@ -46,7 +46,6 @@ func (e *DataClockConsensusEngine) runFrameMessageHandler() {
message.From,
msg.Address,
a,
false,
); err != nil {
e.logger.Debug("could not handle clock frame data", zap.Error(err))
}
@ -55,6 +54,41 @@ func (e *DataClockConsensusEngine) runFrameMessageHandler() {
}
}
func (e *DataClockConsensusEngine) runFrameFragmentMessageHandler() {
defer e.wg.Done()
for {
select {
case <-e.ctx.Done():
return
case message := <-e.frameFragmentMessageProcessorCh:
e.logger.Debug("handling frame fragment message")
msg := &protobufs.Message{}
if err := proto.Unmarshal(message.Data, msg); err != nil {
e.logger.Debug("cannot unmarshal data", zap.Error(err))
continue
}
a := &anypb.Any{}
if err := proto.Unmarshal(msg.Payload, a); err != nil {
e.logger.Debug("cannot unmarshal payload", zap.Error(err))
continue
}
switch a.TypeUrl {
case protobufs.ClockFrameFragmentType:
if err := e.handleClockFrameFragmentData(
message.From,
msg.Address,
a,
); err != nil {
e.logger.Debug("could not handle clock frame fragment data", zap.Error(err))
}
}
}
}
}
func (e *DataClockConsensusEngine) runTxMessageHandler() {
defer e.wg.Done()
for {
@ -66,12 +100,13 @@ func (e *DataClockConsensusEngine) runTxMessageHandler() {
msg := &protobufs.Message{}
if err := proto.Unmarshal(message.Data, msg); err != nil {
e.logger.Debug("bad message")
e.logger.Debug("could not unmarshal data", zap.Error(err))
continue
}
a := &anypb.Any{}
if err := proto.Unmarshal(msg.Payload, a); err != nil {
e.logger.Debug("could not unmarshal payload", zap.Error(err))
continue
}
@ -142,13 +177,13 @@ func (e *DataClockConsensusEngine) runInfoMessageHandler() {
msg := &protobufs.Message{}
if err := proto.Unmarshal(message.Data, msg); err != nil {
e.logger.Debug("bad message")
e.logger.Debug("could not unmarshal data", zap.Error(err))
continue
}
a := &anypb.Any{}
if err := proto.Unmarshal(msg.Payload, a); err != nil {
e.logger.Error("error while unmarshaling", zap.Error(err))
e.logger.Debug("could not unmarshal payload", zap.Error(err))
continue
}
@ -224,35 +259,103 @@ func (e *DataClockConsensusEngine) handleClockFrame(
return nil
}
func (e *DataClockConsensusEngine) handleClockFrameFragment(
peerID []byte,
address []byte,
fragment *protobufs.ClockFrameFragment,
) error {
if fragment == nil {
return errors.Wrap(errors.New("fragment is nil"), "handle clock frame fragment")
}
addr, err := poseidon.HashBytes(
fragment.GetPublicKeySignatureEd448().PublicKey.KeyValue,
)
if err != nil {
return errors.Wrap(err, "handle clock frame fragment data")
}
if !e.FrameProverTrieContains(0, addr.FillBytes(make([]byte, 32))) {
e.logger.Debug(
"prover not in trie at frame fragment, address may be in fork",
zap.Binary("address", address),
zap.Binary("filter", fragment.Filter),
zap.Uint64("frame_number", fragment.FrameNumber),
)
return nil
}
e.logger.Debug(
"got clock frame fragment",
zap.Binary("address", address),
zap.Binary("filter", fragment.Filter),
zap.Uint64("frame_number", fragment.FrameNumber),
)
frame, err := e.clockFrameFragmentBuffer.AccumulateClockFrameFragment(fragment)
if err != nil {
e.logger.Debug("could not accumulate clock frame fragment", zap.Error(err))
return errors.Wrap(err, "handle clock frame fragment data")
}
if frame == nil {
return nil
}
e.logger.Info(
"accumulated clock frame",
zap.Binary("address", address),
zap.Binary("filter", frame.Filter),
zap.Uint64("frame_number", frame.FrameNumber),
)
return e.handleClockFrame(peerID, address, frame)
}
func (e *DataClockConsensusEngine) handleClockFrameData(
peerID []byte,
address []byte,
any *anypb.Any,
isSync bool,
a *anypb.Any,
) error {
if bytes.Equal(peerID, e.pubSub.GetPeerID()) {
return nil
}
frame := &protobufs.ClockFrame{}
if err := any.UnmarshalTo(frame); err != nil {
if err := a.UnmarshalTo(frame); err != nil {
return errors.Wrap(err, "handle clock frame data")
}
return e.handleClockFrame(peerID, address, frame)
}
func (e *DataClockConsensusEngine) handleClockFrameFragmentData(
peerID []byte,
address []byte,
a *anypb.Any,
) error {
if bytes.Equal(peerID, e.pubSub.GetPeerID()) {
return nil
}
fragment := &protobufs.ClockFrameFragment{}
if err := a.UnmarshalTo(fragment); err != nil {
return errors.Wrap(err, "handle clock frame fragment data")
}
return e.handleClockFrameFragment(peerID, address, fragment)
}
func (e *DataClockConsensusEngine) handleDataPeerListAnnounce(
peerID []byte,
address []byte,
any *anypb.Any,
a *anypb.Any,
) error {
if bytes.Equal(peerID, e.pubSub.GetPeerID()) {
return nil
}
announce := &protobufs.DataPeerListAnnounce{}
if err := any.UnmarshalTo(announce); err != nil {
if err := a.UnmarshalTo(announce); err != nil {
return errors.Wrap(err, "handle data peer list announce")
}

View File

@ -37,6 +37,33 @@ func (e *DataClockConsensusEngine) validateFrameMessage(peerID peer.ID, message
}
}
func (e *DataClockConsensusEngine) validateFrameFragmentMessage(peerID peer.ID, message *pb.Message) p2p.ValidationResult {
msg := &protobufs.Message{}
if err := proto.Unmarshal(message.Data, msg); err != nil {
return p2p.ValidationResultReject
}
a := &anypb.Any{}
if err := proto.Unmarshal(msg.Payload, a); err != nil {
return p2p.ValidationResultReject
}
switch a.TypeUrl {
case protobufs.ClockFrameFragmentType:
fragment := &protobufs.ClockFrameFragment{}
if err := proto.Unmarshal(a.Value, fragment); err != nil {
return p2p.ValidationResultReject
}
if err := fragment.Validate(); err != nil {
return p2p.ValidationResultReject
}
if ts := time.UnixMilli(fragment.Timestamp); time.Since(ts) > 2*time.Minute {
return p2p.ValidationResultIgnore
}
return p2p.ValidationResultAccept
default:
return p2p.ValidationResultReject
}
}
func (e *DataClockConsensusEngine) validateTxMessage(peerID peer.ID, message *pb.Message) p2p.ValidationResult {
msg := &protobufs.Message{}
if err := proto.Unmarshal(message.Data, msg); err != nil {

View File

@ -126,24 +126,25 @@ func TestHandlePreMidnightMint(t *testing.T) {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
currentReceivingSyncPeers: 0,
lastFrameReceivedAt: time.Time{},
frameProverTries: []*tries.RollingFrecencyCritbitTrie{},
inclusionProver: qcrypto.NewKZGInclusionProver(log),
syncingStatus: SyncStatusNotSyncing,
peerMap: map[string]*peerInfo{},
uncooperativePeersMap: map[string]*peerInfo{},
minimumPeersRequired: 0,
report: nil,
frameProver: qcrypto.NewWesolowskiFrameProver(log),
masterTimeReel: nil,
dataTimeReel: &qtime.DataTimeReel{},
peerInfoManager: nil,
frameMessageProcessorCh: make(chan *pb.Message),
txMessageProcessorCh: make(chan *pb.Message),
infoMessageProcessorCh: make(chan *pb.Message),
config: nil,
preMidnightMint: map[string]struct{}{},
currentReceivingSyncPeers: 0,
lastFrameReceivedAt: time.Time{},
frameProverTries: []*tries.RollingFrecencyCritbitTrie{},
inclusionProver: qcrypto.NewKZGInclusionProver(log),
syncingStatus: SyncStatusNotSyncing,
peerMap: map[string]*peerInfo{},
uncooperativePeersMap: map[string]*peerInfo{},
minimumPeersRequired: 0,
report: nil,
frameProver: qcrypto.NewWesolowskiFrameProver(log),
masterTimeReel: nil,
dataTimeReel: &qtime.DataTimeReel{},
peerInfoManager: nil,
frameMessageProcessorCh: make(chan *pb.Message),
frameFragmentMessageProcessorCh: make(chan *pb.Message),
txMessageProcessorCh: make(chan *pb.Message),
infoMessageProcessorCh: make(chan *pb.Message),
config: nil,
preMidnightMint: map[string]struct{}{},
}
d.dataTimeReel.SetHead(&protobufs.ClockFrame{

View File

@ -391,10 +391,12 @@ func NewTokenExecutionEngine(
}
}
}
e.publishMessage(
if err := e.publishMessage(
append([]byte{0x00}, e.intrinsicFilter...),
resume.TokenRequest(),
)
); err != nil {
e.logger.Warn("error while publishing resume message", zap.Error(err))
}
}
}()
@ -1437,10 +1439,12 @@ func (e *TokenExecutionEngine) AnnounceProverJoin() {
panic(err)
}
e.publishMessage(
if err := e.publishMessage(
append([]byte{0x00}, e.intrinsicFilter...),
join.TokenRequest(),
)
); err != nil {
e.logger.Warn("error publishing join message", zap.Error(err))
}
}
func (e *TokenExecutionEngine) GetRingPosition() int {

View File

@ -27,6 +27,7 @@ require (
github.com/cockroachdb/pebble v0.0.0-20231210175920-b4d301aeb46a
github.com/deiu/rdf2go v0.0.0-20240619132609-81222e324bb9
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1
github.com/klauspost/reedsolomon v1.12.4
github.com/libp2p/go-libp2p v0.35.4
github.com/libp2p/go-libp2p-kad-dht v0.23.0
github.com/shopspring/decimal v1.4.0
@ -142,7 +143,7 @@ require (
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/jbenet/goprocess v0.1.4 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/koron/go-ssdp v0.0.4 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-cidranger v1.1.0 // indirect
@ -199,7 +200,7 @@ require (
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0
golang.org/x/sys v0.27.0
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gonum.org/v1/gonum v0.13.0 // indirect

View File

@ -260,8 +260,10 @@ github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/klauspost/reedsolomon v1.12.4 h1:5aDr3ZGoJbgu/8+j45KtUJxzYm8k08JGtB9Wx1VQ4OA=
github.com/klauspost/reedsolomon v1.12.4/go.mod h1:d3CzOMOt0JXGIFZm1StgkyF14EYr3xneR2rNWo7NcMU=
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -693,8 +695,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.30.0
// protoc v3.21.12
// protoc-gen-go v1.34.1
// protoc v5.27.0
// source: clock.proto
package protobufs
@ -78,6 +78,8 @@ type ClockFrame struct {
//
// *ClockFrame_PublicKeySignatureEd448
PublicKeySignature isClockFrame_PublicKeySignature `protobuf_oneof:"public_key_signature"`
// Padding is used in tests in order to simulate large clock frames.
Padding []byte `protobuf:"bytes,99,opt,name=padding,proto3" json:"padding,omitempty"`
}
func (x *ClockFrame) Reset() {
@ -182,6 +184,13 @@ func (x *ClockFrame) GetPublicKeySignatureEd448() *Ed448Signature {
return nil
}
func (x *ClockFrame) GetPadding() []byte {
if x != nil {
return x.Padding
}
return nil
}
type isClockFrame_PublicKeySignature interface {
isClockFrame_PublicKeySignature()
}
@ -192,6 +201,138 @@ type ClockFrame_PublicKeySignatureEd448 struct {
func (*ClockFrame_PublicKeySignatureEd448) isClockFrame_PublicKeySignature() {}
// Represents a clock frame fragment for a given filter. Clock frame fragments
// are used to disseminate clock frame data across the network in a more
// efficient manner. This is particularly useful for large clock frames, where
// the frame data can be split into smaller fragments and sent across the
// network in parallel.
type ClockFrameFragment struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Filter []byte `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"`
FrameNumber uint64 `protobuf:"varint,2,opt,name=frame_number,json=frameNumber,proto3" json:"frame_number,omitempty"`
Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
FrameHash []byte `protobuf:"bytes,4,opt,name=frame_hash,json=frameHash,proto3" json:"frame_hash,omitempty"`
// Types that are assignable to Encoding:
//
// *ClockFrameFragment_ReedSolomon
Encoding isClockFrameFragment_Encoding `protobuf_oneof:"encoding"`
// Types that are assignable to PublicKeySignature:
//
// *ClockFrameFragment_PublicKeySignatureEd448
PublicKeySignature isClockFrameFragment_PublicKeySignature `protobuf_oneof:"public_key_signature"`
}
func (x *ClockFrameFragment) Reset() {
*x = ClockFrameFragment{}
if protoimpl.UnsafeEnabled {
mi := &file_clock_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ClockFrameFragment) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ClockFrameFragment) ProtoMessage() {}
func (x *ClockFrameFragment) ProtoReflect() protoreflect.Message {
mi := &file_clock_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 ClockFrameFragment.ProtoReflect.Descriptor instead.
func (*ClockFrameFragment) Descriptor() ([]byte, []int) {
return file_clock_proto_rawDescGZIP(), []int{1}
}
func (x *ClockFrameFragment) GetFilter() []byte {
if x != nil {
return x.Filter
}
return nil
}
func (x *ClockFrameFragment) GetFrameNumber() uint64 {
if x != nil {
return x.FrameNumber
}
return 0
}
func (x *ClockFrameFragment) GetTimestamp() int64 {
if x != nil {
return x.Timestamp
}
return 0
}
func (x *ClockFrameFragment) GetFrameHash() []byte {
if x != nil {
return x.FrameHash
}
return nil
}
func (m *ClockFrameFragment) GetEncoding() isClockFrameFragment_Encoding {
if m != nil {
return m.Encoding
}
return nil
}
func (x *ClockFrameFragment) GetReedSolomon() *ClockFrameFragment_ReedSolomonEncoding {
if x, ok := x.GetEncoding().(*ClockFrameFragment_ReedSolomon); ok {
return x.ReedSolomon
}
return nil
}
func (m *ClockFrameFragment) GetPublicKeySignature() isClockFrameFragment_PublicKeySignature {
if m != nil {
return m.PublicKeySignature
}
return nil
}
func (x *ClockFrameFragment) GetPublicKeySignatureEd448() *Ed448Signature {
if x, ok := x.GetPublicKeySignature().(*ClockFrameFragment_PublicKeySignatureEd448); ok {
return x.PublicKeySignatureEd448
}
return nil
}
type isClockFrameFragment_Encoding interface {
isClockFrameFragment_Encoding()
}
type ClockFrameFragment_ReedSolomon struct {
ReedSolomon *ClockFrameFragment_ReedSolomonEncoding `protobuf:"bytes,5,opt,name=reed_solomon,json=reedSolomon,proto3,oneof"`
}
func (*ClockFrameFragment_ReedSolomon) isClockFrameFragment_Encoding() {}
type isClockFrameFragment_PublicKeySignature interface {
isClockFrameFragment_PublicKeySignature()
}
type ClockFrameFragment_PublicKeySignatureEd448 struct {
PublicKeySignatureEd448 *Ed448Signature `protobuf:"bytes,6,opt,name=public_key_signature_ed448,json=publicKeySignatureEd448,proto3,oneof"`
}
func (*ClockFrameFragment_PublicKeySignatureEd448) isClockFrameFragment_PublicKeySignature() {}
type ClockFrameParentSelectors struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -204,7 +345,7 @@ type ClockFrameParentSelectors struct {
func (x *ClockFrameParentSelectors) Reset() {
*x = ClockFrameParentSelectors{}
if protoimpl.UnsafeEnabled {
mi := &file_clock_proto_msgTypes[1]
mi := &file_clock_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -217,7 +358,7 @@ func (x *ClockFrameParentSelectors) String() string {
func (*ClockFrameParentSelectors) ProtoMessage() {}
func (x *ClockFrameParentSelectors) ProtoReflect() protoreflect.Message {
mi := &file_clock_proto_msgTypes[1]
mi := &file_clock_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -230,7 +371,7 @@ func (x *ClockFrameParentSelectors) ProtoReflect() protoreflect.Message {
// Deprecated: Use ClockFrameParentSelectors.ProtoReflect.Descriptor instead.
func (*ClockFrameParentSelectors) Descriptor() ([]byte, []int) {
return file_clock_proto_rawDescGZIP(), []int{1}
return file_clock_proto_rawDescGZIP(), []int{2}
}
func (x *ClockFrameParentSelectors) GetFrameNumber() uint64 {
@ -272,7 +413,7 @@ type ClockFramesRequest struct {
func (x *ClockFramesRequest) Reset() {
*x = ClockFramesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_clock_proto_msgTypes[2]
mi := &file_clock_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -285,7 +426,7 @@ func (x *ClockFramesRequest) String() string {
func (*ClockFramesRequest) ProtoMessage() {}
func (x *ClockFramesRequest) ProtoReflect() protoreflect.Message {
mi := &file_clock_proto_msgTypes[2]
mi := &file_clock_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -298,7 +439,7 @@ func (x *ClockFramesRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ClockFramesRequest.ProtoReflect.Descriptor instead.
func (*ClockFramesRequest) Descriptor() ([]byte, []int) {
return file_clock_proto_rawDescGZIP(), []int{2}
return file_clock_proto_rawDescGZIP(), []int{3}
}
func (x *ClockFramesRequest) GetFilter() []byte {
@ -347,7 +488,7 @@ type ClockFramesPreflight struct {
func (x *ClockFramesPreflight) Reset() {
*x = ClockFramesPreflight{}
if protoimpl.UnsafeEnabled {
mi := &file_clock_proto_msgTypes[3]
mi := &file_clock_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -360,7 +501,7 @@ func (x *ClockFramesPreflight) String() string {
func (*ClockFramesPreflight) ProtoMessage() {}
func (x *ClockFramesPreflight) ProtoReflect() protoreflect.Message {
mi := &file_clock_proto_msgTypes[3]
mi := &file_clock_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -373,7 +514,7 @@ func (x *ClockFramesPreflight) ProtoReflect() protoreflect.Message {
// Deprecated: Use ClockFramesPreflight.ProtoReflect.Descriptor instead.
func (*ClockFramesPreflight) Descriptor() ([]byte, []int) {
return file_clock_proto_rawDescGZIP(), []int{3}
return file_clock_proto_rawDescGZIP(), []int{4}
}
func (x *ClockFramesPreflight) GetRangeParentSelectors() []*ClockFrameParentSelectors {
@ -404,7 +545,7 @@ type ClockFramesResponse struct {
func (x *ClockFramesResponse) Reset() {
*x = ClockFramesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_clock_proto_msgTypes[4]
mi := &file_clock_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -417,7 +558,7 @@ func (x *ClockFramesResponse) String() string {
func (*ClockFramesResponse) ProtoMessage() {}
func (x *ClockFramesResponse) ProtoReflect() protoreflect.Message {
mi := &file_clock_proto_msgTypes[4]
mi := &file_clock_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -430,7 +571,7 @@ func (x *ClockFramesResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ClockFramesResponse.ProtoReflect.Descriptor instead.
func (*ClockFramesResponse) Descriptor() ([]byte, []int) {
return file_clock_proto_rawDescGZIP(), []int{4}
return file_clock_proto_rawDescGZIP(), []int{5}
}
func (x *ClockFramesResponse) GetFilter() []byte {
@ -461,6 +602,85 @@ func (x *ClockFramesResponse) GetClockFrames() []*ClockFrame {
return nil
}
type ClockFrameFragment_ReedSolomonEncoding struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
FrameSize uint64 `protobuf:"varint,1,opt,name=frame_size,json=frameSize,proto3" json:"frame_size,omitempty"`
FragmentShard uint64 `protobuf:"varint,2,opt,name=fragment_shard,json=fragmentShard,proto3" json:"fragment_shard,omitempty"`
FragmentDataShardCount uint64 `protobuf:"varint,3,opt,name=fragment_data_shard_count,json=fragmentDataShardCount,proto3" json:"fragment_data_shard_count,omitempty"`
FragmentParityShardCount uint64 `protobuf:"varint,4,opt,name=fragment_parity_shard_count,json=fragmentParityShardCount,proto3" json:"fragment_parity_shard_count,omitempty"`
FragmentData []byte `protobuf:"bytes,5,opt,name=fragment_data,json=fragmentData,proto3" json:"fragment_data,omitempty"`
}
func (x *ClockFrameFragment_ReedSolomonEncoding) Reset() {
*x = ClockFrameFragment_ReedSolomonEncoding{}
if protoimpl.UnsafeEnabled {
mi := &file_clock_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ClockFrameFragment_ReedSolomonEncoding) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ClockFrameFragment_ReedSolomonEncoding) ProtoMessage() {}
func (x *ClockFrameFragment_ReedSolomonEncoding) ProtoReflect() protoreflect.Message {
mi := &file_clock_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 ClockFrameFragment_ReedSolomonEncoding.ProtoReflect.Descriptor instead.
func (*ClockFrameFragment_ReedSolomonEncoding) Descriptor() ([]byte, []int) {
return file_clock_proto_rawDescGZIP(), []int{1, 0}
}
func (x *ClockFrameFragment_ReedSolomonEncoding) GetFrameSize() uint64 {
if x != nil {
return x.FrameSize
}
return 0
}
func (x *ClockFrameFragment_ReedSolomonEncoding) GetFragmentShard() uint64 {
if x != nil {
return x.FragmentShard
}
return 0
}
func (x *ClockFrameFragment_ReedSolomonEncoding) GetFragmentDataShardCount() uint64 {
if x != nil {
return x.FragmentDataShardCount
}
return 0
}
func (x *ClockFrameFragment_ReedSolomonEncoding) GetFragmentParityShardCount() uint64 {
if x != nil {
return x.FragmentParityShardCount
}
return 0
}
func (x *ClockFrameFragment_ReedSolomonEncoding) GetFragmentData() []byte {
if x != nil {
return x.FragmentData
}
return nil
}
var File_clock_proto protoreflect.FileDescriptor
var file_clock_proto_rawDesc = []byte{
@ -468,7 +688,7 @@ var file_clock_proto_rawDesc = []byte{
0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x63,
0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x70, 0x62, 0x1a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0a, 0x6b, 0x65, 0x79, 0x73, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x22, 0xbc, 0x03, 0x0a, 0x0a, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d,
0x74, 0x6f, 0x22, 0xd6, 0x03, 0x0a, 0x0a, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d,
0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x72, 0x61,
0x6d, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
@ -494,58 +714,99 @@ var file_clock_proto_rawDesc = []byte{
0x75, 0x6d, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6b, 0x65, 0x79, 0x73, 0x2e, 0x70, 0x62, 0x2e,
0x45, 0x64, 0x34, 0x34, 0x38, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x48, 0x00,
0x52, 0x17, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61,
0x74, 0x75, 0x72, 0x65, 0x45, 0x64, 0x34, 0x34, 0x38, 0x42, 0x16, 0x0a, 0x14, 0x70, 0x75, 0x62,
0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72,
0x65, 0x22, 0x67, 0x0a, 0x19, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x50,
0x61, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x21,
0x0a, 0x0c, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01,
0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65,
0x72, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x6c, 0x65,
0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x70, 0x61, 0x72, 0x65,
0x6e, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x94, 0x02, 0x0a, 0x12, 0x43,
0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x72, 0x6f,
0x6d, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02,
0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x66, 0x72, 0x6f, 0x6d, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x4e,
0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x6f, 0x5f, 0x66, 0x72, 0x61, 0x6d,
0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d,
0x74, 0x6f, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x27, 0x0a,
0x0f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72,
0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x65,
0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x69, 0x0a, 0x16, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f,
0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73,
0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72,
0x69, 0x75, 0x6d, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x70,
0x62, 0x2e, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x72, 0x65,
0x6e, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x14, 0x72, 0x61, 0x6e,
0x67, 0x65, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72,
0x73, 0x22, 0x81, 0x01, 0x0a, 0x14, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65,
0x73, 0x50, 0x72, 0x65, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x12, 0x69, 0x0a, 0x16, 0x72, 0x61,
0x6e, 0x67, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63,
0x74, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x71, 0x75, 0x69,
0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x63, 0x6c, 0x6f,
0x63, 0x6b, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65,
0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x52,
0x14, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x6c, 0x65,
0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0xca, 0x01, 0x0a, 0x13, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x46,
0x72, 0x61, 0x6d, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a,
0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66,
0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x66, 0x72,
0x74, 0x75, 0x72, 0x65, 0x45, 0x64, 0x34, 0x34, 0x38, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64,
0x64, 0x69, 0x6e, 0x67, 0x18, 0x63, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64,
0x69, 0x6e, 0x67, 0x42, 0x16, 0x0a, 0x14, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65,
0x79, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xfc, 0x04, 0x0a, 0x12,
0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65,
0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x72,
0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04,
0x52, 0x0f, 0x66, 0x72, 0x6f, 0x6d, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65,
0x72, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x6f, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x75,
0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x74, 0x6f, 0x46, 0x72,
0x61, 0x6d, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x47, 0x0a, 0x0c, 0x63, 0x6c, 0x6f,
0x63, 0x6b, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x24, 0x2e, 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x6e, 0x6f, 0x64,
0x52, 0x0b, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1c, 0x0a,
0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x66,
0x72, 0x61, 0x6d, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x09, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x65, 0x0a, 0x0c, 0x72, 0x65,
0x65, 0x64, 0x5f, 0x73, 0x6f, 0x6c, 0x6f, 0x6d, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x40, 0x2e, 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x6e, 0x6f,
0x64, 0x65, 0x2e, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x6c, 0x6f, 0x63,
0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52,
0x65, 0x65, 0x64, 0x53, 0x6f, 0x6c, 0x6f, 0x6d, 0x6f, 0x6e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69,
0x6e, 0x67, 0x48, 0x00, 0x52, 0x0b, 0x72, 0x65, 0x65, 0x64, 0x53, 0x6f, 0x6c, 0x6f, 0x6d, 0x6f,
0x6e, 0x12, 0x66, 0x0a, 0x1a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f,
0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x65, 0x64, 0x34, 0x34, 0x38, 0x18,
0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69,
0x75, 0x6d, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6b, 0x65, 0x79, 0x73, 0x2e, 0x70, 0x62, 0x2e,
0x45, 0x64, 0x34, 0x34, 0x38, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x48, 0x01,
0x52, 0x17, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61,
0x74, 0x75, 0x72, 0x65, 0x45, 0x64, 0x34, 0x34, 0x38, 0x1a, 0xfa, 0x01, 0x0a, 0x13, 0x52, 0x65,
0x65, 0x64, 0x53, 0x6f, 0x6c, 0x6f, 0x6d, 0x6f, 0x6e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e,
0x67, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x69, 0x7a, 0x65,
0x12, 0x25, 0x0a, 0x0e, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x68, 0x61,
0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65,
0x6e, 0x74, 0x53, 0x68, 0x61, 0x72, 0x64, 0x12, 0x39, 0x0a, 0x19, 0x66, 0x72, 0x61, 0x67, 0x6d,
0x65, 0x6e, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x64, 0x5f, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16, 0x66, 0x72, 0x61, 0x67,
0x6d, 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x53, 0x68, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x75,
0x6e, 0x74, 0x12, 0x3d, 0x0a, 0x1b, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x70,
0x61, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x18, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e,
0x74, 0x50, 0x61, 0x72, 0x69, 0x74, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x75, 0x6e,
0x74, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x61,
0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65,
0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x42, 0x0a, 0x0a, 0x08, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69,
0x6e, 0x67, 0x42, 0x16, 0x0a, 0x14, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79,
0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x67, 0x0a, 0x19, 0x43, 0x6c,
0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x65,
0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x72, 0x61, 0x6d, 0x65,
0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x66,
0x72, 0x61, 0x6d, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61,
0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63,
0x74, 0x6f, 0x72, 0x22, 0x94, 0x02, 0x0a, 0x12, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61,
0x6d, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69,
0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74,
0x65, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x66,
0x72, 0x6f, 0x6d, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26,
0x0a, 0x0f, 0x74, 0x6f, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65,
0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x74, 0x6f, 0x46, 0x72, 0x61, 0x6d, 0x65,
0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74,
0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12,
0x69, 0x0a, 0x16, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f,
0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x33, 0x2e, 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x6e, 0x6f, 0x64,
0x65, 0x2e, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x6c, 0x6f, 0x63, 0x6b,
0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d,
0x65, 0x73, 0x42, 0x3a, 0x5a, 0x38, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x71, 0x75, 0x69,
0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x71, 0x75, 0x69, 0x6c,
0x69, 0x62, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x6d, 0x6f, 0x6e, 0x6f, 0x72, 0x65, 0x70, 0x6f, 0x2f,
0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x73, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x46, 0x72, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63,
0x74, 0x6f, 0x72, 0x73, 0x52, 0x14, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x72, 0x65, 0x6e,
0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x81, 0x01, 0x0a, 0x14, 0x43,
0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x50, 0x72, 0x65, 0x66, 0x6c, 0x69,
0x67, 0x68, 0x74, 0x12, 0x69, 0x0a, 0x16, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x70, 0x61, 0x72,
0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, 0x6d,
0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x70, 0x62, 0x2e, 0x43,
0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x53,
0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x14, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x50,
0x61, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0xca,
0x01, 0x0a, 0x13, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2a,
0x0a, 0x11, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x75, 0x6d,
0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x66, 0x72, 0x6f, 0x6d, 0x46,
0x72, 0x61, 0x6d, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x6f,
0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, 0x20,
0x01, 0x28, 0x04, 0x52, 0x0d, 0x74, 0x6f, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x4e, 0x75, 0x6d, 0x62,
0x65, 0x72, 0x12, 0x47, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x66, 0x72, 0x61, 0x6d,
0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x71, 0x75, 0x69, 0x6c, 0x69,
0x62, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x63, 0x6c, 0x6f, 0x63, 0x6b,
0x2e, 0x70, 0x62, 0x2e, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x0b,
0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x42, 0x3a, 0x5a, 0x38, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, 0x6d,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, 0x6d, 0x2f,
0x6d, 0x6f, 0x6e, 0x6f, 0x72, 0x65, 0x70, 0x6f, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -560,27 +821,31 @@ func file_clock_proto_rawDescGZIP() []byte {
return file_clock_proto_rawDescData
}
var file_clock_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_clock_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_clock_proto_goTypes = []interface{}{
(*ClockFrame)(nil), // 0: quilibrium.node.clock.pb.ClockFrame
(*ClockFrameParentSelectors)(nil), // 1: quilibrium.node.clock.pb.ClockFrameParentSelectors
(*ClockFramesRequest)(nil), // 2: quilibrium.node.clock.pb.ClockFramesRequest
(*ClockFramesPreflight)(nil), // 3: quilibrium.node.clock.pb.ClockFramesPreflight
(*ClockFramesResponse)(nil), // 4: quilibrium.node.clock.pb.ClockFramesResponse
(*InclusionAggregateProof)(nil), // 5: quilibrium.node.channel.pb.InclusionAggregateProof
(*Ed448Signature)(nil), // 6: quilibrium.node.keys.pb.Ed448Signature
(*ClockFrame)(nil), // 0: quilibrium.node.clock.pb.ClockFrame
(*ClockFrameFragment)(nil), // 1: quilibrium.node.clock.pb.ClockFrameFragment
(*ClockFrameParentSelectors)(nil), // 2: quilibrium.node.clock.pb.ClockFrameParentSelectors
(*ClockFramesRequest)(nil), // 3: quilibrium.node.clock.pb.ClockFramesRequest
(*ClockFramesPreflight)(nil), // 4: quilibrium.node.clock.pb.ClockFramesPreflight
(*ClockFramesResponse)(nil), // 5: quilibrium.node.clock.pb.ClockFramesResponse
(*ClockFrameFragment_ReedSolomonEncoding)(nil), // 6: quilibrium.node.clock.pb.ClockFrameFragment.ReedSolomonEncoding
(*InclusionAggregateProof)(nil), // 7: quilibrium.node.channel.pb.InclusionAggregateProof
(*Ed448Signature)(nil), // 8: quilibrium.node.keys.pb.Ed448Signature
}
var file_clock_proto_depIdxs = []int32{
5, // 0: quilibrium.node.clock.pb.ClockFrame.aggregate_proofs:type_name -> quilibrium.node.channel.pb.InclusionAggregateProof
6, // 1: quilibrium.node.clock.pb.ClockFrame.public_key_signature_ed448:type_name -> quilibrium.node.keys.pb.Ed448Signature
1, // 2: quilibrium.node.clock.pb.ClockFramesRequest.range_parent_selectors:type_name -> quilibrium.node.clock.pb.ClockFrameParentSelectors
1, // 3: quilibrium.node.clock.pb.ClockFramesPreflight.range_parent_selectors:type_name -> quilibrium.node.clock.pb.ClockFrameParentSelectors
0, // 4: quilibrium.node.clock.pb.ClockFramesResponse.clock_frames:type_name -> quilibrium.node.clock.pb.ClockFrame
5, // [5:5] is the sub-list for method output_type
5, // [5:5] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
7, // 0: quilibrium.node.clock.pb.ClockFrame.aggregate_proofs:type_name -> quilibrium.node.channel.pb.InclusionAggregateProof
8, // 1: quilibrium.node.clock.pb.ClockFrame.public_key_signature_ed448:type_name -> quilibrium.node.keys.pb.Ed448Signature
6, // 2: quilibrium.node.clock.pb.ClockFrameFragment.reed_solomon:type_name -> quilibrium.node.clock.pb.ClockFrameFragment.ReedSolomonEncoding
8, // 3: quilibrium.node.clock.pb.ClockFrameFragment.public_key_signature_ed448:type_name -> quilibrium.node.keys.pb.Ed448Signature
2, // 4: quilibrium.node.clock.pb.ClockFramesRequest.range_parent_selectors:type_name -> quilibrium.node.clock.pb.ClockFrameParentSelectors
2, // 5: quilibrium.node.clock.pb.ClockFramesPreflight.range_parent_selectors:type_name -> quilibrium.node.clock.pb.ClockFrameParentSelectors
0, // 6: quilibrium.node.clock.pb.ClockFramesResponse.clock_frames:type_name -> quilibrium.node.clock.pb.ClockFrame
7, // [7:7] is the sub-list for method output_type
7, // [7:7] is the sub-list for method input_type
7, // [7:7] is the sub-list for extension type_name
7, // [7:7] is the sub-list for extension extendee
0, // [0:7] is the sub-list for field type_name
}
func init() { file_clock_proto_init() }
@ -604,7 +869,7 @@ func file_clock_proto_init() {
}
}
file_clock_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ClockFrameParentSelectors); i {
switch v := v.(*ClockFrameFragment); i {
case 0:
return &v.state
case 1:
@ -616,7 +881,7 @@ func file_clock_proto_init() {
}
}
file_clock_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ClockFramesRequest); i {
switch v := v.(*ClockFrameParentSelectors); i {
case 0:
return &v.state
case 1:
@ -628,7 +893,7 @@ func file_clock_proto_init() {
}
}
file_clock_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ClockFramesPreflight); i {
switch v := v.(*ClockFramesRequest); i {
case 0:
return &v.state
case 1:
@ -640,6 +905,18 @@ func file_clock_proto_init() {
}
}
file_clock_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ClockFramesPreflight); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_clock_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ClockFramesResponse); i {
case 0:
return &v.state
@ -651,17 +928,33 @@ func file_clock_proto_init() {
return nil
}
}
file_clock_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ClockFrameFragment_ReedSolomonEncoding); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_clock_proto_msgTypes[0].OneofWrappers = []interface{}{
(*ClockFrame_PublicKeySignatureEd448)(nil),
}
file_clock_proto_msgTypes[1].OneofWrappers = []interface{}{
(*ClockFrameFragment_ReedSolomon)(nil),
(*ClockFrameFragment_PublicKeySignatureEd448)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_clock_proto_rawDesc,
NumEnums: 0,
NumMessages: 5,
NumMessages: 7,
NumExtensions: 0,
NumServices: 0,
},

View File

@ -59,6 +59,33 @@ message ClockFrame {
oneof public_key_signature {
quilibrium.node.keys.pb.Ed448Signature public_key_signature_ed448 = 9;
}
// Padding is used in tests in order to simulate large clock frames.
bytes padding = 99;
}
// Represents a clock frame fragment for a given filter. Clock frame fragments
// are used to disseminate clock frame data across the network in a more
// efficient manner. This is particularly useful for large clock frames, where
// the frame data can be split into smaller fragments and sent across the
// network in parallel.
message ClockFrameFragment {
bytes filter = 1;
uint64 frame_number = 2;
int64 timestamp = 3;
bytes frame_hash = 4;
message ReedSolomonEncoding {
uint64 frame_size = 1;
uint64 fragment_shard = 2;
uint64 fragment_data_shard_count = 3;
uint64 fragment_parity_shard_count = 4;
bytes fragment_data = 5;
}
oneof encoding {
ReedSolomonEncoding reed_solomon = 5;
}
oneof public_key_signature {
quilibrium.node.keys.pb.Ed448Signature public_key_signature_ed448 = 6;
}
}
message ClockFrameParentSelectors {

View File

@ -37,6 +37,7 @@ const (
IdentityKeyType = ChannelPrefix + "IdentityKey"
SignedPreKeyType = ChannelPrefix + "SignedPreKey"
ClockFrameType = ClockPrefix + "ClockFrame"
ClockFrameFragmentType = ClockPrefix + "ClockFrameFragment"
ClockFramesRequestType = ClockPrefix + "ClockFramesRequest"
ClockFramesResponseType = ClockPrefix + "ClockFramesResponse"
Ed448PublicKeyType = KeysPrefix + "Ed448PublicKey"

View File

@ -1,6 +1,7 @@
package protobufs
import (
"crypto/sha256"
"encoding/binary"
"github.com/pkg/errors"
@ -10,6 +11,33 @@ type signatureMessage interface {
signatureMessage() []byte
}
var _ signatureMessage = (*ClockFrameFragment_ReedSolomonEncoding)(nil)
func (c *ClockFrameFragment_ReedSolomonEncoding) signatureMessage() []byte {
payload := []byte("reed-solomon-fragment")
payload = binary.BigEndian.AppendUint64(payload, c.FrameSize)
payload = binary.BigEndian.AppendUint64(payload, c.FragmentShard)
payload = binary.BigEndian.AppendUint64(payload, c.FragmentDataShardCount)
payload = binary.BigEndian.AppendUint64(payload, c.FragmentParityShardCount)
h := sha256.Sum256(c.FragmentData)
payload = append(payload, h[:]...)
return payload
}
var _ signatureMessage = (*ClockFrameFragment)(nil)
func (c *ClockFrameFragment) signatureMessage() []byte {
payload := []byte("fragment")
payload = binary.BigEndian.AppendUint64(payload, c.FrameNumber)
payload = append(payload, c.Filter...)
payload = binary.BigEndian.AppendUint64(payload, uint64(c.Timestamp))
payload = append(payload, c.FrameHash...)
if reedSolomon := c.GetReedSolomon(); reedSolomon != nil {
payload = append(payload, reedSolomon.signatureMessage()...)
}
return payload
}
var _ signatureMessage = (*TransferCoinRequest)(nil)
func (t *TransferCoinRequest) signatureMessage() []byte {
@ -99,6 +127,21 @@ type SignedMessage interface {
ValidateSignature() error
}
var _ SignedMessage = (*ClockFrameFragment)(nil)
// ValidateSignature checks the signature of the clock frame fragment.
func (c *ClockFrameFragment) ValidateSignature() error {
switch {
case c.GetPublicKeySignatureEd448() != nil:
if err := c.GetPublicKeySignatureEd448().verifyUnsafe(c.signatureMessage()); err != nil {
return errors.Wrap(err, "validate signature")
}
return nil
default:
return errors.New("invalid signature")
}
}
var _ SignedMessage = (*TransferCoinRequest)(nil)
// ValidateSignature checks the signature of the transfer coin request.
@ -204,6 +247,61 @@ type ValidatableMessage interface {
Validate() error
}
var _ ValidatableMessage = (*ClockFrameFragment_ReedSolomonEncoding)(nil)
// Validate checks the Reed-Solomon encoding.
func (c *ClockFrameFragment_ReedSolomonEncoding) Validate() error {
if c == nil {
return errors.New("nil Reed-Solomon encoding")
}
if c.FrameSize == 0 {
return errors.New("invalid frame size")
}
if c.FragmentDataShardCount == 0 {
return errors.New("invalid fragment data shard count")
}
if c.FragmentParityShardCount == 0 {
return errors.New("invalid fragment parity shard count")
}
if c.FragmentShard >= c.FragmentDataShardCount+c.FragmentParityShardCount {
return errors.New("invalid fragment shard")
}
if len(c.FragmentData) == 0 {
return errors.New("invalid fragment data")
}
return nil
}
var _ ValidatableMessage = (*ClockFrameFragment)(nil)
// Validate checks the clock frame fragment.
func (c *ClockFrameFragment) Validate() error {
if c == nil {
return errors.New("nil clock frame fragment")
}
if len(c.Filter) != 32 {
return errors.New("invalid filter")
}
if c.Timestamp == 0 {
return errors.New("invalid timestamp")
}
if n := len(c.FrameHash); n < 28 || n > 64 {
return errors.New("invalid frame hash")
}
switch {
case c.GetReedSolomon() != nil:
if err := c.GetReedSolomon().Validate(); err != nil {
return errors.Wrap(err, "reed-solomon encoding")
}
default:
return errors.New("missing encoding")
}
if err := c.ValidateSignature(); err != nil {
return errors.Wrap(err, "signature")
}
return nil
}
var _ ValidatableMessage = (*Ed448PublicKey)(nil)
// Validate checks the Ed448 public key.
@ -573,6 +671,20 @@ func newED448Signature(publicKey, signature []byte) *Ed448Signature {
}
}
var _ SignableED448Message = (*ClockFrameFragment)(nil)
// SignED448 signs the clock frame fragment with the given key.
func (c *ClockFrameFragment) SignED448(publicKey []byte, sign func([]byte) ([]byte, error)) error {
signature, err := sign(c.signatureMessage())
if err != nil {
return errors.Wrap(err, "sign")
}
c.PublicKeySignature = &ClockFrameFragment_PublicKeySignatureEd448{
PublicKeySignatureEd448: newED448Signature(publicKey, signature),
}
return nil
}
var _ SignableED448Message = (*TransferCoinRequest)(nil)
// SignED448 signs the transfer coin request with the given key.

View File

@ -41,6 +41,70 @@ func metaAppend[T any](bs ...[]T) []T {
return result
}
func TestClockFrameFragmentSignatureRoundtrip(t *testing.T) {
t.Parallel()
message := &protobufs.ClockFrameFragment{
Filter: bytes.Repeat([]byte{0x01}, 32),
FrameNumber: 1,
Timestamp: 2,
FrameHash: bytes.Repeat([]byte{0x03}, 28),
Encoding: &protobufs.ClockFrameFragment_ReedSolomon{
ReedSolomon: &protobufs.ClockFrameFragment_ReedSolomonEncoding{
FrameSize: 3,
FragmentShard: 4,
FragmentDataShardCount: 5,
FragmentParityShardCount: 6,
FragmentData: bytes.Repeat([]byte{0x02}, 6),
},
},
PublicKeySignature: &protobufs.ClockFrameFragment_PublicKeySignatureEd448{
PublicKeySignatureEd448: &protobufs.Ed448Signature{
Signature: bytes.Repeat([]byte{0x02}, 114),
PublicKey: &protobufs.Ed448PublicKey{
KeyValue: bytes.Repeat([]byte{0x03}, 57),
},
},
},
}
if !bytes.Equal(
protobufs.SignatureMessageOf(message),
metaAppend(
[]byte("fragment"),
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
bytes.Repeat([]byte{0x01}, 32),
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02},
bytes.Repeat([]byte{0x03}, 28),
metaAppend(
[]byte("reed-solomon-fragment"),
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03},
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04},
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05},
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06},
[]byte{
0x2e, 0xbd, 0x9a, 0x4e, 0x48, 0x8b, 0x47, 0x1c,
0xd7, 0x0a, 0x25, 0xae, 0xcc, 0xb2, 0xdb, 0x50,
0xaa, 0xbd, 0xa7, 0x3c, 0x92, 0xce, 0x8e, 0xe0,
0xe2, 0x15, 0xcd, 0x89, 0x32, 0x0f, 0x6b, 0x9a,
},
),
),
) {
t.Fatal("unexpected signature message")
}
if err := message.ValidateSignature(); err == nil {
t.Fatal("expected error")
}
if err := message.SignED448(primaryPublicKeyBytes, primaryPrivateKey.Sign); err != nil {
t.Fatal(err)
}
if !bytes.Equal(message.GetPublicKeySignatureEd448().PublicKey.KeyValue, primaryPublicKeyBytes) {
t.Fatal("unexpected public key")
}
if err := message.ValidateSignature(); err != nil {
t.Fatal(err)
}
}
func TestTransferCoinRequestSignatureRoundtrip(t *testing.T) {
t.Parallel()
message := &protobufs.TransferCoinRequest{
@ -422,6 +486,79 @@ func TestAnnounceProverResumeSignatureRoundtrip(t *testing.T) {
}
}
func TestClockFrameFragmentReedSolomonEncodingValidate(t *testing.T) {
t.Parallel()
if err := (*protobufs.ClockFrameFragment_ReedSolomonEncoding)(nil).Validate(); err == nil {
t.Fatal("expected error")
}
message := &protobufs.ClockFrameFragment_ReedSolomonEncoding{}
if err := message.Validate(); err == nil {
t.Fatal("expected error")
}
message.FrameSize = 1
if err := message.Validate(); err == nil {
t.Fatal("expected error")
}
message.FragmentShard = 2
if err := message.Validate(); err == nil {
t.Fatal("expected error")
}
message.FragmentDataShardCount = 3
if err := message.Validate(); err == nil {
t.Fatal("expected error")
}
message.FragmentParityShardCount = 4
if err := message.Validate(); err == nil {
t.Fatal("expected error")
}
message.FragmentData = bytes.Repeat([]byte{0x01}, 6)
if err := message.Validate(); err != nil {
t.Fatal(err)
}
}
func TestClockFrameFragmentValidate(t *testing.T) {
t.Parallel()
if err := (*protobufs.ClockFrameFragment)(nil).Validate(); err == nil {
t.Fatal("expected error")
}
message := &protobufs.ClockFrameFragment{}
if err := message.Validate(); err == nil {
t.Fatal("expected error")
}
message.Filter = bytes.Repeat([]byte{0x01}, 32)
if err := message.Validate(); err == nil {
t.Fatal("expected error")
}
message.FrameNumber = 1
if err := message.Validate(); err == nil {
t.Fatal("expected error")
}
message.Timestamp = 2
if err := message.Validate(); err == nil {
t.Fatal("expected error")
}
message.FrameHash = bytes.Repeat([]byte{0x03}, 28)
if err := message.Validate(); err == nil {
t.Fatal("expected error")
}
message.Encoding = &protobufs.ClockFrameFragment_ReedSolomon{
ReedSolomon: &protobufs.ClockFrameFragment_ReedSolomonEncoding{
FrameSize: 2,
FragmentShard: 3,
FragmentDataShardCount: 4,
FragmentParityShardCount: 5,
FragmentData: bytes.Repeat([]byte{0x02}, 6),
},
}
if err := message.SignED448(primaryPublicKeyBytes, primaryPrivateKey.Sign); err != nil {
t.Fatal(err)
}
if err := message.Validate(); err != nil {
t.Fatal(err)
}
}
func TestEd448PublicKeyValidate(t *testing.T) {
t.Parallel()
if err := (*protobufs.Ed448PublicKey)(nil).Validate(); err == nil {