From d1e65c1c920b7012fe6281485edef827cc8e00aa Mon Sep 17 00:00:00 2001 From: petricadaipegsp <155911522+petricadaipegsp@users.noreply.github.com> Date: Mon, 2 Dec 2024 23:25:10 +0100 Subject: [PATCH] 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 --- client/go.mod | 4 +- client/go.sum | 10 +- dashboards/grafana/BlossomSub.json | 443 ++++++++++++++++- node/config/engine.go | 67 +++ node/consensus/data/broadcast_messaging.go | 113 ++++- .../data/data_clock_consensus_engine.go | 134 ++--- .../data/fragmentation/clock_frame.go | 371 ++++++++++++++ .../data/fragmentation/clock_frame_test.go | 414 ++++++++++++++++ node/consensus/data/fragmentation/hash.go | 10 + .../consensus/data/fragmentation/hash_test.go | 100 ++++ node/consensus/data/main_data_loop.go | 12 +- node/consensus/data/message_handler.go | 125 ++++- node/consensus/data/message_validators.go | 27 + node/consensus/data/token_handle_mint_test.go | 37 +- .../token/token_execution_engine.go | 12 +- node/go.mod | 5 +- node/go.sum | 10 +- node/protobufs/clock.pb.go | 467 ++++++++++++++---- node/protobufs/clock.proto | 27 + node/protobufs/protobufs.go | 1 + node/protobufs/validation.go | 112 +++++ node/protobufs/validation_test.go | 137 +++++ 22 files changed, 2426 insertions(+), 212 deletions(-) create mode 100644 node/consensus/data/fragmentation/clock_frame.go create mode 100644 node/consensus/data/fragmentation/clock_frame_test.go create mode 100644 node/consensus/data/fragmentation/hash.go create mode 100644 node/consensus/data/fragmentation/hash_test.go diff --git a/client/go.mod b/client/go.mod index cf7c94e..1ae2420 100644 --- a/client/go.mod +++ b/client/go.mod @@ -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 diff --git a/client/go.sum b/client/go.sum index 23e5817..3e21f86 100644 --- a/client/go.sum +++ b/client/go.sum @@ -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= diff --git a/dashboards/grafana/BlossomSub.json b/dashboards/grafana/BlossomSub.json index 400e769..ba29d10 100644 --- a/dashboards/grafana/BlossomSub.json +++ b/dashboards/grafana/BlossomSub.json @@ -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": "" } \ No newline at end of file diff --git a/node/config/engine.go b/node/config/engine.go index 3078f31..09b237a 100644 --- a/node/config/engine.go +++ b/node/config/engine.go @@ -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"` } diff --git a/node/consensus/data/broadcast_messaging.go b/node/consensus/data/broadcast_messaging.go index 6d0ed2a..312306b 100644 --- a/node/consensus/data/broadcast_messaging.go +++ b/node/consensus/data/broadcast_messaging.go @@ -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 } diff --git a/node/consensus/data/data_clock_consensus_engine.go b/node/consensus/data/data_clock_consensus_engine.go index 5371ae3..66a8804 100644 --- a/node/consensus/data/data_clock_consensus_engine.go +++ b/node/consensus/data/data_clock_consensus_engine.go @@ -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) diff --git a/node/consensus/data/fragmentation/clock_frame.go b/node/consensus/data/fragmentation/clock_frame.go new file mode 100644 index 0000000..f0a3d53 --- /dev/null +++ b/node/consensus/data/fragmentation/clock_frame.go @@ -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 +} diff --git a/node/consensus/data/fragmentation/clock_frame_test.go b/node/consensus/data/fragmentation/clock_frame_test.go new file mode 100644 index 0000000..12fdcd6 --- /dev/null +++ b/node/consensus/data/fragmentation/clock_frame_test.go @@ -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") + } + } + }) + } +} diff --git a/node/consensus/data/fragmentation/hash.go b/node/consensus/data/fragmentation/hash.go new file mode 100644 index 0000000..c4f5c30 --- /dev/null +++ b/node/consensus/data/fragmentation/hash.go @@ -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[:] +} diff --git a/node/consensus/data/fragmentation/hash_test.go b/node/consensus/data/fragmentation/hash_test.go new file mode 100644 index 0000000..87e4e3b --- /dev/null +++ b/node/consensus/data/fragmentation/hash_test.go @@ -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) + } + }) + } +} diff --git a/node/consensus/data/main_data_loop.go b/node/consensus/data/main_data_loop.go index 0dd9cbf..7167b2a 100644 --- a/node/consensus/data/main_data_loop.go +++ b/node/consensus/data/main_data_loop.go @@ -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)) + } } } } diff --git a/node/consensus/data/message_handler.go b/node/consensus/data/message_handler.go index 91d3e83..3ea9d38 100644 --- a/node/consensus/data/message_handler.go +++ b/node/consensus/data/message_handler.go @@ -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") } diff --git a/node/consensus/data/message_validators.go b/node/consensus/data/message_validators.go index 993eede..f06596d 100644 --- a/node/consensus/data/message_validators.go +++ b/node/consensus/data/message_validators.go @@ -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 { diff --git a/node/consensus/data/token_handle_mint_test.go b/node/consensus/data/token_handle_mint_test.go index 4bc9a90..6a12591 100644 --- a/node/consensus/data/token_handle_mint_test.go +++ b/node/consensus/data/token_handle_mint_test.go @@ -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{ diff --git a/node/execution/intrinsics/token/token_execution_engine.go b/node/execution/intrinsics/token/token_execution_engine.go index b1288ec..49d0ebb 100644 --- a/node/execution/intrinsics/token/token_execution_engine.go +++ b/node/execution/intrinsics/token/token_execution_engine.go @@ -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 { diff --git a/node/go.mod b/node/go.mod index 9da7c47..6978181 100644 --- a/node/go.mod +++ b/node/go.mod @@ -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 diff --git a/node/go.sum b/node/go.sum index 888035b..12f568b 100644 --- a/node/go.sum +++ b/node/go.sum @@ -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= diff --git a/node/protobufs/clock.pb.go b/node/protobufs/clock.pb.go index a377ed9..68a3e85 100644 --- a/node/protobufs/clock.pb.go +++ b/node/protobufs/clock.pb.go @@ -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, }, diff --git a/node/protobufs/clock.proto b/node/protobufs/clock.proto index 5b8815e..63ce4a6 100644 --- a/node/protobufs/clock.proto +++ b/node/protobufs/clock.proto @@ -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 { diff --git a/node/protobufs/protobufs.go b/node/protobufs/protobufs.go index 90638b6..ed9b2eb 100644 --- a/node/protobufs/protobufs.go +++ b/node/protobufs/protobufs.go @@ -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" diff --git a/node/protobufs/validation.go b/node/protobufs/validation.go index f4a7860..eb80801 100644 --- a/node/protobufs/validation.go +++ b/node/protobufs/validation.go @@ -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. diff --git a/node/protobufs/validation_test.go b/node/protobufs/validation_test.go index a10f9c1..2dc2f6f 100644 --- a/node/protobufs/validation_test.go +++ b/node/protobufs/validation_test.go @@ -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 {