diff --git a/.mockery.yaml b/.mockery.yaml index 8f139231cb..07d4fb0602 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -32,6 +32,13 @@ packages: dir: ./test/mocks pkgname: mocks filename: p2p.go + github.com/evstack/ev-node/pkg/raft: + interfaces: + sourceNode: + config: + dir: ./pkg/raft + pkgname: raft + filename: node_mock.go github.com/evstack/ev-node/pkg/store: interfaces: Store: diff --git a/apps/evm/cmd/run.go b/apps/evm/cmd/run.go index 8d79264042..1c615f0dfa 100644 --- a/apps/evm/cmd/run.go +++ b/apps/evm/cmd/run.go @@ -22,7 +22,6 @@ import ( "github.com/evstack/ev-node/pkg/config" "github.com/evstack/ev-node/pkg/genesis" genesispkg "github.com/evstack/ev-node/pkg/genesis" - "github.com/evstack/ev-node/pkg/p2p" "github.com/evstack/ev-node/pkg/p2p/key" "github.com/evstack/ev-node/pkg/store" "github.com/evstack/ev-node/sequencers/single" @@ -86,12 +85,7 @@ var RunCmd = &cobra.Command{ return err } - p2pClient, err := p2p.NewClient(nodeConfig.P2P, nodeKey.PrivKey, datastore, genesis.ChainID, logger, nil) - if err != nil { - return err - } - - return rollcmd.StartNode(logger, cmd, executor, sequencer, &daJrpc.DA, p2pClient, datastore, nodeConfig, genesis, node.NodeOptions{}) + return rollcmd.StartNode(logger, cmd, executor, sequencer, &daJrpc.DA, nodeKey, datastore, nodeConfig, genesis, node.NodeOptions{}) }, } diff --git a/apps/evm/go.mod b/apps/evm/go.mod index 126891fedc..0c5adf7f7a 100644 --- a/apps/evm/go.mod +++ b/apps/evm/go.mod @@ -27,9 +27,11 @@ require ( connectrpc.com/grpcreflect v1.3.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/StackExchange/wmi v1.2.1 // indirect + github.com/armon/go-metrics v0.4.1 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect + github.com/boltdb/bolt v1.3.1 // indirect github.com/celestiaorg/go-libp2p-messenger v0.2.2 // indirect github.com/celestiaorg/go-square/v3 v3.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -46,6 +48,7 @@ require ( github.com/emicklei/dot v1.6.2 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.3 // indirect github.com/ethereum/go-verkle v0.2.2 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect github.com/filecoin-project/go-clock v0.1.0 // indirect github.com/filecoin-project/go-jsonrpc v0.9.0 // indirect @@ -67,8 +70,14 @@ require ( github.com/google/gopacket v1.1.19 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/hashicorp/go-hclog v1.6.2 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-msgpack v0.5.5 // indirect + github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/raft v1.7.1 // indirect + github.com/hashicorp/raft-boltdb v0.0.0-20231211162105-6c830fa4535e // indirect github.com/holiman/uint256 v1.3.2 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/apps/evm/go.sum b/apps/evm/go.sum index 173fb3ba0d..12312254e5 100644 --- a/apps/evm/go.sum +++ b/apps/evm/go.sum @@ -12,6 +12,8 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -22,14 +24,25 @@ github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMG github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= +github.com/armon/go-metrics v0.3.8/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/celestiaorg/go-libp2p-messenger v0.2.2 h1:osoUfqjss7vWTIZrrDSy953RjQz+ps/vBFE7bychLEc= @@ -39,8 +52,11 @@ github.com/celestiaorg/go-square/v3 v3.0.2/go.mod h1:oFReMLsSDMRs82ICFEeFQFCqNvw github.com/celestiaorg/utils v0.1.0 h1:WsP3O8jF7jKRgLNFmlDCwdThwOFMFxg0MnqhkLFVxPo= github.com/celestiaorg/utils v0.1.0/go.mod h1:vQTh7MHnvpIeCQZ2/Ph+w7K1R2UerDheZbgJEJD2hSU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= @@ -105,6 +121,9 @@ github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cn github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= github.com/evstack/ev-node/execution/evm v1.0.0-beta.3 h1:xo0mZz3CJtntP1RPLFDBubBKpNkqStImt9H9N0xysj8= github.com/evstack/ev-node/execution/evm v1.0.0-beta.3/go.mod h1:yazCKZaVczYwizfHYSQ4KIYqW0d42M7q7e9AxuSXV3s= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= @@ -126,8 +145,12 @@ github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -136,6 +159,7 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= @@ -183,6 +207,7 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -201,10 +226,30 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I= +github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack/v2 v2.1.2 h1:4Ee8FTp834e+ewB71RDrQ0VKpyFdrKOjvYtnQ/ltVj0= +github.com/hashicorp/go-msgpack/v2 v2.1.2/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/raft v1.1.0/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= +github.com/hashicorp/raft v1.7.1 h1:ytxsNx4baHsRZrhUcbt3+79zc4ly8qm7pi0393pSchY= +github.com/hashicorp/raft v1.7.1/go.mod h1:hUeiEwQQR/Nk2iKDD0dkEhklSsu3jcAcqvPzPoZSAEM= +github.com/hashicorp/raft-boltdb v0.0.0-20231211162105-6c830fa4535e h1:SK4y8oR4ZMHPvwVHryKI88kJPJda4UyWYvG5A6iEQxc= +github.com/hashicorp/raft-boltdb v0.0.0-20231211162105-6c830fa4535e/go.mod h1:EMz/UIuG93P0MBeHh6CbXQAEe8ckVJLZjhD17lBzK5Q= github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db h1:IZUYC/xb3giYwBLMnr8d0TGTzPKFGNTCGgGLoyeX330= github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db/go.mod h1:xTEYN9KCHxuYHs+NmrmzFcnvHMzLLNiGFafCb1n3Mfg= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= @@ -239,19 +284,23 @@ github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABo github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienrbrt/go-header v0.0.0-20251008134330-747c8c192fa8 h1:F+gOiipBxG43s+Ho+ri9T8IwumjWjp1XUon4DLWjxfQ= github.com/julienrbrt/go-header v0.0.0-20251008134330-747c8c192fa8/go.mod h1:eX9iTSPthVEAlEDLux40ZT/olXPGhpxHd+mEzJeDhd0= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU= github.com/koron/go-ssdp v0.0.6/go.mod h1:0R9LfRJGek1zWTjN3JUNlm5INCDYGpRDfAptnct63fI= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -299,9 +348,13 @@ github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -327,7 +380,9 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= @@ -356,11 +411,14 @@ github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOo github.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -409,6 +467,7 @@ github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps= github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs= github.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54= github.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -418,16 +477,29 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4= @@ -480,6 +552,8 @@ github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go. github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= @@ -502,13 +576,16 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -526,6 +603,7 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= @@ -573,6 +651,7 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -607,11 +686,14 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -644,16 +726,25 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -747,7 +838,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -755,6 +848,8 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/apps/grpc/cmd/run.go b/apps/grpc/cmd/run.go index 484f51d7a5..0dab5f3b1e 100644 --- a/apps/grpc/cmd/run.go +++ b/apps/grpc/cmd/run.go @@ -19,7 +19,6 @@ import ( "github.com/evstack/ev-node/pkg/config" "github.com/evstack/ev-node/pkg/genesis" rollgenesis "github.com/evstack/ev-node/pkg/genesis" - "github.com/evstack/ev-node/pkg/p2p" "github.com/evstack/ev-node/pkg/p2p/key" "github.com/evstack/ev-node/pkg/store" "github.com/evstack/ev-node/sequencers/single" @@ -90,14 +89,8 @@ The execution client must implement the Evolve execution gRPC interface.`, return err } - // Create P2P client - p2pClient, err := p2p.NewClient(nodeConfig.P2P, nodeKey.PrivKey, datastore, genesis.ChainID, logger, nil) - if err != nil { - return err - } - // Start the node - return rollcmd.StartNode(logger, cmd, executor, sequencer, &daJrpc.DA, p2pClient, datastore, nodeConfig, genesis, node.NodeOptions{}) + return rollcmd.StartNode(logger, cmd, executor, sequencer, &daJrpc.DA, nodeKey, datastore, nodeConfig, genesis, node.NodeOptions{}) }, } diff --git a/apps/grpc/go.mod b/apps/grpc/go.mod index fdc2a9c21e..830efb8378 100644 --- a/apps/grpc/go.mod +++ b/apps/grpc/go.mod @@ -24,8 +24,10 @@ require ( require ( connectrpc.com/connect v1.19.1 // indirect connectrpc.com/grpcreflect v1.3.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/boltdb/bolt v1.3.1 // indirect github.com/celestiaorg/go-header v0.7.3 // indirect github.com/celestiaorg/go-libp2p-messenger v0.2.2 // indirect github.com/celestiaorg/go-square/v3 v3.0.2 // indirect @@ -36,6 +38,7 @@ require ( github.com/dgraph-io/badger/v4 v4.5.1 // indirect github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.13.0 // indirect github.com/filecoin-project/go-clock v0.1.0 // indirect github.com/filecoin-project/go-jsonrpc v0.9.0 // indirect github.com/flynn/noise v1.1.0 // indirect @@ -52,8 +55,14 @@ require ( github.com/google/gopacket v1.1.19 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/hashicorp/go-hclog v1.6.2 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-msgpack v0.5.5 // indirect + github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/raft v1.7.1 // indirect + github.com/hashicorp/raft-boltdb v0.0.0-20231211162105-6c830fa4535e // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/boxo v0.35.0 // indirect diff --git a/apps/grpc/go.sum b/apps/grpc/go.sum index c2e0e46a7d..3910c7422f 100644 --- a/apps/grpc/go.sum +++ b/apps/grpc/go.sum @@ -12,14 +12,27 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= +github.com/armon/go-metrics v0.3.8/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/celestiaorg/go-libp2p-messenger v0.2.2 h1:osoUfqjss7vWTIZrrDSy953RjQz+ps/vBFE7bychLEc= @@ -29,8 +42,11 @@ github.com/celestiaorg/go-square/v3 v3.0.2/go.mod h1:oFReMLsSDMRs82ICFEeFQFCqNvw github.com/celestiaorg/utils v0.1.0 h1:WsP3O8jF7jKRgLNFmlDCwdThwOFMFxg0MnqhkLFVxPo= github.com/celestiaorg/utils v0.1.0/go.mod h1:vQTh7MHnvpIeCQZ2/Ph+w7K1R2UerDheZbgJEJD2hSU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -60,6 +76,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs= github.com/filecoin-project/go-jsonrpc v0.9.0 h1:G47qEF52w7GholpI21vPSTVBFvsrip6geIoqNiqyZtQ= @@ -77,13 +95,18 @@ github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= @@ -123,6 +146,7 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -139,10 +163,30 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I= +github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack/v2 v2.1.2 h1:4Ee8FTp834e+ewB71RDrQ0VKpyFdrKOjvYtnQ/ltVj0= +github.com/hashicorp/go-msgpack/v2 v2.1.2/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/raft v1.1.0/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= +github.com/hashicorp/raft v1.7.1 h1:ytxsNx4baHsRZrhUcbt3+79zc4ly8qm7pi0393pSchY= +github.com/hashicorp/raft v1.7.1/go.mod h1:hUeiEwQQR/Nk2iKDD0dkEhklSsu3jcAcqvPzPoZSAEM= +github.com/hashicorp/raft-boltdb v0.0.0-20231211162105-6c830fa4535e h1:SK4y8oR4ZMHPvwVHryKI88kJPJda4UyWYvG5A6iEQxc= +github.com/hashicorp/raft-boltdb v0.0.0-20231211162105-6c830fa4535e/go.mod h1:EMz/UIuG93P0MBeHh6CbXQAEe8ckVJLZjhD17lBzK5Q= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -171,19 +215,23 @@ github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABo github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienrbrt/go-header v0.0.0-20251008134330-747c8c192fa8 h1:F+gOiipBxG43s+Ho+ri9T8IwumjWjp1XUon4DLWjxfQ= github.com/julienrbrt/go-header v0.0.0-20251008134330-747c8c192fa8/go.mod h1:eX9iTSPthVEAlEDLux40ZT/olXPGhpxHd+mEzJeDhd0= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU= github.com/koron/go-ssdp v0.0.6/go.mod h1:0R9LfRJGek1zWTjN3JUNlm5INCDYGpRDfAptnct63fI= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -229,9 +277,13 @@ github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -252,7 +304,9 @@ github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dz github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= @@ -281,9 +335,12 @@ github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOo github.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -330,6 +387,7 @@ github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps= github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs= github.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54= github.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -339,16 +397,29 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= @@ -391,6 +462,8 @@ github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go. github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= @@ -413,13 +486,16 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -429,6 +505,7 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= @@ -471,6 +548,7 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -505,11 +583,14 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -542,15 +623,24 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -643,12 +733,16 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/apps/testapp/cmd/run.go b/apps/testapp/cmd/run.go index c72d220cdd..b17e71e41a 100644 --- a/apps/testapp/cmd/run.go +++ b/apps/testapp/cmd/run.go @@ -13,7 +13,6 @@ import ( "github.com/evstack/ev-node/node" rollcmd "github.com/evstack/ev-node/pkg/cmd" genesispkg "github.com/evstack/ev-node/pkg/genesis" - "github.com/evstack/ev-node/pkg/p2p" "github.com/evstack/ev-node/pkg/p2p/key" "github.com/evstack/ev-node/pkg/store" "github.com/evstack/ev-node/sequencers/single" @@ -106,11 +105,6 @@ var RunCmd = &cobra.Command{ return err } - p2pClient, err := p2p.NewClient(nodeConfig.P2P, nodeKey.PrivKey, datastore, genesis.ChainID, logger, p2p.NopMetrics()) - if err != nil { - return err - } - - return rollcmd.StartNode(logger, cmd, executor, sequencer, &daJrpc.DA, p2pClient, datastore, nodeConfig, genesis, node.NodeOptions{}) + return rollcmd.StartNode(logger, cmd, executor, sequencer, &daJrpc.DA, nodeKey, datastore, nodeConfig, genesis, node.NodeOptions{}) }, } diff --git a/apps/testapp/go.mod b/apps/testapp/go.mod index 0e6ed2f892..345f5015f2 100644 --- a/apps/testapp/go.mod +++ b/apps/testapp/go.mod @@ -23,8 +23,10 @@ require ( require ( connectrpc.com/connect v1.19.1 // indirect connectrpc.com/grpcreflect v1.3.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/boltdb/bolt v1.3.1 // indirect github.com/celestiaorg/go-libp2p-messenger v0.2.2 // indirect github.com/celestiaorg/go-square/v3 v3.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -34,6 +36,7 @@ require ( github.com/dgraph-io/badger/v4 v4.5.1 // indirect github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.13.0 // indirect github.com/filecoin-project/go-clock v0.1.0 // indirect github.com/filecoin-project/go-jsonrpc v0.9.0 // indirect github.com/flynn/noise v1.1.0 // indirect @@ -50,8 +53,14 @@ require ( github.com/google/gopacket v1.1.19 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/hashicorp/go-hclog v1.6.2 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-msgpack v0.5.5 // indirect + github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/raft v1.7.1 // indirect + github.com/hashicorp/raft-boltdb v0.0.0-20231211162105-6c830fa4535e // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/boxo v0.35.0 // indirect diff --git a/apps/testapp/go.sum b/apps/testapp/go.sum index eeafba1a84..0bc402b13a 100644 --- a/apps/testapp/go.sum +++ b/apps/testapp/go.sum @@ -12,14 +12,27 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= +github.com/armon/go-metrics v0.3.8/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/celestiaorg/go-libp2p-messenger v0.2.2 h1:osoUfqjss7vWTIZrrDSy953RjQz+ps/vBFE7bychLEc= @@ -29,8 +42,11 @@ github.com/celestiaorg/go-square/v3 v3.0.2/go.mod h1:oFReMLsSDMRs82ICFEeFQFCqNvw github.com/celestiaorg/utils v0.1.0 h1:WsP3O8jF7jKRgLNFmlDCwdThwOFMFxg0MnqhkLFVxPo= github.com/celestiaorg/utils v0.1.0/go.mod h1:vQTh7MHnvpIeCQZ2/Ph+w7K1R2UerDheZbgJEJD2hSU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -60,6 +76,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs= github.com/filecoin-project/go-jsonrpc v0.9.0 h1:G47qEF52w7GholpI21vPSTVBFvsrip6geIoqNiqyZtQ= @@ -77,13 +95,18 @@ github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= @@ -123,6 +146,7 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -139,10 +163,30 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I= +github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack/v2 v2.1.2 h1:4Ee8FTp834e+ewB71RDrQ0VKpyFdrKOjvYtnQ/ltVj0= +github.com/hashicorp/go-msgpack/v2 v2.1.2/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/raft v1.1.0/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= +github.com/hashicorp/raft v1.7.1 h1:ytxsNx4baHsRZrhUcbt3+79zc4ly8qm7pi0393pSchY= +github.com/hashicorp/raft v1.7.1/go.mod h1:hUeiEwQQR/Nk2iKDD0dkEhklSsu3jcAcqvPzPoZSAEM= +github.com/hashicorp/raft-boltdb v0.0.0-20231211162105-6c830fa4535e h1:SK4y8oR4ZMHPvwVHryKI88kJPJda4UyWYvG5A6iEQxc= +github.com/hashicorp/raft-boltdb v0.0.0-20231211162105-6c830fa4535e/go.mod h1:EMz/UIuG93P0MBeHh6CbXQAEe8ckVJLZjhD17lBzK5Q= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -171,19 +215,23 @@ github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABo github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienrbrt/go-header v0.0.0-20251008134330-747c8c192fa8 h1:F+gOiipBxG43s+Ho+ri9T8IwumjWjp1XUon4DLWjxfQ= github.com/julienrbrt/go-header v0.0.0-20251008134330-747c8c192fa8/go.mod h1:eX9iTSPthVEAlEDLux40ZT/olXPGhpxHd+mEzJeDhd0= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU= github.com/koron/go-ssdp v0.0.6/go.mod h1:0R9LfRJGek1zWTjN3JUNlm5INCDYGpRDfAptnct63fI= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -229,8 +277,12 @@ github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -251,7 +303,9 @@ github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dz github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= @@ -280,9 +334,12 @@ github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOo github.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -329,6 +386,7 @@ github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps= github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs= github.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54= github.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -338,16 +396,29 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= @@ -390,6 +461,8 @@ github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go. github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= @@ -412,13 +485,16 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -428,6 +504,7 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= @@ -470,6 +547,7 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -504,11 +582,14 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -541,15 +622,24 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -642,12 +732,16 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/block/components.go b/block/components.go index 546cda62c3..822881a10e 100644 --- a/block/components.go +++ b/block/components.go @@ -119,6 +119,11 @@ func (bc *Components) Stop() error { errs = errors.Join(errs, fmt.Errorf("failed to stop submitter: %w", err)) } } + if bc.Cache != nil { + if err := bc.Cache.SaveToDisk(); err != nil { + errs = errors.Join(errs, fmt.Errorf("failed to save caches: %w", err)) + } + } return errs } @@ -137,8 +142,8 @@ func NewSyncComponents( logger zerolog.Logger, metrics *Metrics, blockOpts BlockOptions, + raftNode common.RaftNode, ) (*Components, error) { - logger.Info().Msg("Starting in sync-mode") cacheManager, err := cache.NewManager(config, store, logger) if err != nil { return nil, fmt.Errorf("failed to create cache manager: %w", err) @@ -162,6 +167,7 @@ func NewSyncComponents( logger, blockOpts, errorCh, + raftNode, ) // Create submitter for sync nodes (no signer, only DA inclusion processing) @@ -203,8 +209,8 @@ func NewAggregatorComponents( logger zerolog.Logger, metrics *Metrics, blockOpts BlockOptions, + raftNode common.RaftNode, ) (*Components, error) { - logger.Info().Msg("Starting in aggregator-mode") cacheManager, err := cache.NewManager(config, store, logger) if err != nil { return nil, fmt.Errorf("failed to create cache manager: %w", err) @@ -227,6 +233,7 @@ func NewAggregatorComponents( logger, blockOpts, errorCh, + raftNode, ) if err != nil { return nil, fmt.Errorf("failed to create executor: %w", err) diff --git a/block/components_test.go b/block/components_test.go index eadf45328c..5d0edd8186 100644 --- a/block/components_test.go +++ b/block/components_test.go @@ -107,6 +107,7 @@ func TestNewSyncComponents_Creation(t *testing.T) { zerolog.Nop(), NopMetrics(), DefaultBlockOptions(), + nil, ) require.NoError(t, err) @@ -158,6 +159,7 @@ func TestNewAggregatorComponents_Creation(t *testing.T) { zerolog.Nop(), NopMetrics(), DefaultBlockOptions(), + nil, ) require.NoError(t, err) @@ -233,6 +235,7 @@ func TestExecutor_RealExecutionClientFailure_StopsNode(t *testing.T) { zerolog.Nop(), NopMetrics(), DefaultBlockOptions(), + nil, ) require.NoError(t, err) diff --git a/block/internal/common/broadcaster_mock.go b/block/internal/common/broadcaster_mock.go index 2983478078..b638e6b491 100644 --- a/block/internal/common/broadcaster_mock.go +++ b/block/internal/common/broadcaster_mock.go @@ -39,6 +39,50 @@ func (_m *MockBroadcaster[H]) EXPECT() *MockBroadcaster_Expecter[H] { return &MockBroadcaster_Expecter[H]{mock: &_m.Mock} } +// Height provides a mock function for the type MockBroadcaster +func (_mock *MockBroadcaster[H]) Height() uint64 { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for Height") + } + + var r0 uint64 + if returnFunc, ok := ret.Get(0).(func() uint64); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(uint64) + } + return r0 +} + +// MockBroadcaster_Height_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Height' +type MockBroadcaster_Height_Call[H header.Header[H]] struct { + *mock.Call +} + +// Height is a helper method to define mock.On call +func (_e *MockBroadcaster_Expecter[H]) Height() *MockBroadcaster_Height_Call[H] { + return &MockBroadcaster_Height_Call[H]{Call: _e.mock.On("Height")} +} + +func (_c *MockBroadcaster_Height_Call[H]) Run(run func()) *MockBroadcaster_Height_Call[H] { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockBroadcaster_Height_Call[H]) Return(v uint64) *MockBroadcaster_Height_Call[H] { + _c.Call.Return(v) + return _c +} + +func (_c *MockBroadcaster_Height_Call[H]) RunAndReturn(run func() uint64) *MockBroadcaster_Height_Call[H] { + _c.Call.Return(run) + return _c +} + // Store provides a mock function for the type MockBroadcaster func (_mock *MockBroadcaster[H]) Store() header.Store[H] { ret := _mock.Called() diff --git a/block/internal/common/expected_interfaces.go b/block/internal/common/expected_interfaces.go index 8f36af6240..e4bc7e472b 100644 --- a/block/internal/common/expected_interfaces.go +++ b/block/internal/common/expected_interfaces.go @@ -8,8 +8,9 @@ import ( "github.com/celestiaorg/go-header" ) -// broadcaster interface for P2P broadcasting +// Broadcaster interface for P2P broadcasting type Broadcaster[H header.Header[H]] interface { WriteToStoreAndBroadcast(ctx context.Context, payload H, opts ...pubsub.PubOpt) error Store() header.Store[H] + Height() uint64 } diff --git a/block/internal/common/raft.go b/block/internal/common/raft.go new file mode 100644 index 0000000000..33d26e4c87 --- /dev/null +++ b/block/internal/common/raft.go @@ -0,0 +1,17 @@ +package common + +import ( + "context" + + "github.com/evstack/ev-node/pkg/raft" +) + +// RaftNode interface for raft consensus integration +type RaftNode interface { + IsLeader() bool + GetState() raft.RaftBlockState + + Broadcast(ctx context.Context, state *raft.RaftBlockState) error + + SetApplyCallback(ch chan<- raft.RaftApplyMsg) +} diff --git a/block/internal/executing/executor.go b/block/internal/executing/executor.go index be969b1a75..0e0ccaef8d 100644 --- a/block/internal/executing/executor.go +++ b/block/internal/executing/executor.go @@ -5,10 +5,12 @@ import ( "context" "errors" "fmt" + "reflect" "sync" "sync/atomic" "time" + "github.com/evstack/ev-node/pkg/raft" "github.com/ipfs/go-datastore" "github.com/rs/zerolog" "golang.org/x/sync/errgroup" @@ -45,6 +47,9 @@ type Executor struct { genesis genesis.Genesis options common.BlockOptions + // Raft consensus + raftNode common.RaftNode + // State management lastState *atomic.Pointer[types.State] @@ -81,6 +86,7 @@ func NewExecutor( logger zerolog.Logger, options common.BlockOptions, errorCh chan<- error, + raftNode common.RaftNode, ) (*Executor, error) { if signer == nil { return nil, errors.New("signer cannot be nil") @@ -94,6 +100,9 @@ func NewExecutor( if !bytes.Equal(addr, genesis.ProposerAddress) { return nil, common.ErrNotProposer } + if raftNode != nil && reflect.ValueOf(raftNode).IsNil() { + raftNode = nil + } return &Executor{ store: store, @@ -108,6 +117,7 @@ func NewExecutor( dataBroadcaster: dataBroadcaster, options: options, lastState: &atomic.Pointer[types.State]{}, + raftNode: raftNode, txNotifyCh: make(chan struct{}, 1), errorCh: errorCh, logger: logger.With().Str("component", "executor").Logger(), @@ -203,6 +213,12 @@ func (e *Executor) initializeState() error { } } + if e.raftNode != nil { + // ensure node is fully synced before producing any blocks + if raftState := e.raftNode.GetState(); raftState.Height != 0 && raftState.Height != state.LastBlockHeight { + return fmt.Errorf("invalid state: node is not synced with the chain: raft %d != %d state", raftState.Height, state.LastBlockHeight) + } + } e.setLastState(state) // Initialize store height using batch for atomicity @@ -310,6 +326,11 @@ func (e *Executor) produceBlock() error { } }() + // Check raft leadership if raft is enabled + if e.raftNode != nil && !e.raftNode.IsLeader() { + return errors.New("not raft leader") + } + currentState := e.getLastState() newHeight := currentState.LastBlockHeight + 1 @@ -410,6 +431,30 @@ func (e *Executor) produceBlock() error { return fmt.Errorf("failed to update state: %w", err) } + // Propose block to raft to share state in the cluster + if e.raftNode != nil { + headerBytes, err := header.MarshalBinary() + if err != nil { + return fmt.Errorf("failed to marshal header: %w", err) + } + dataBytes, err := data.MarshalBinary() + if err != nil { + return fmt.Errorf("failed to marshal data: %w", err) + } + + raftState := &raft.RaftBlockState{ + Height: newHeight, + Hash: header.Hash(), + Timestamp: header.BaseHeader.Time, + Header: headerBytes, + Data: dataBytes, + } + if err := e.raftNode.Broadcast(e.ctx, raftState); err != nil { + return fmt.Errorf("failed to propose block to raft: %w", err) + } + e.logger.Debug().Uint64("height", newHeight).Msg("proposed block to raft") + + } if err := batch.Commit(); err != nil { return fmt.Errorf("failed to commit batch: %w", err) } @@ -543,7 +588,7 @@ func (e *Executor) createBlock(ctx context.Context, height uint64, batchData *Ba } for i, tx := range batchData.Transactions { - data.Txs[i] = types.Tx(tx) + data.Txs[i] = tx } // Set data hash @@ -659,6 +704,15 @@ func (e *Executor) recordBlockMetrics(data *types.Data) { e.metrics.CommittedHeight.Set(float64(data.Metadata.Height)) } +// IsSynced checks if the last block height in the stored state matches the expected height and returns true if they are equal. +func (e *Executor) IsSynced(expHeight uint64) bool { + state, err := e.store.GetState(e.ctx) + if err != nil { + return false + } + return state.LastBlockHeight == expHeight +} + // BatchData represents batch data from sequencer type BatchData struct { *coresequencer.Batch diff --git a/block/internal/executing/executor_lazy_test.go b/block/internal/executing/executor_lazy_test.go index b72f0a856b..16d073023a 100644 --- a/block/internal/executing/executor_lazy_test.go +++ b/block/internal/executing/executor_lazy_test.go @@ -66,6 +66,7 @@ func TestLazyMode_ProduceBlockLogic(t *testing.T) { zerolog.Nop(), common.DefaultBlockOptions(), make(chan error, 1), + nil, ) require.NoError(t, err) @@ -176,6 +177,7 @@ func TestRegularMode_ProduceBlockLogic(t *testing.T) { zerolog.Nop(), common.DefaultBlockOptions(), make(chan error, 1), + nil, ) require.NoError(t, err) diff --git a/block/internal/executing/executor_logic_test.go b/block/internal/executing/executor_logic_test.go index 9aa79d0c43..8815b833aa 100644 --- a/block/internal/executing/executor_logic_test.go +++ b/block/internal/executing/executor_logic_test.go @@ -88,6 +88,7 @@ func TestProduceBlock_EmptyBatch_SetsEmptyDataHash(t *testing.T) { zerolog.Nop(), common.DefaultBlockOptions(), make(chan error, 1), + nil, ) require.NoError(t, err) @@ -175,6 +176,7 @@ func TestPendingLimit_SkipsProduction(t *testing.T) { zerolog.Nop(), common.DefaultBlockOptions(), make(chan error, 1), + nil, ) require.NoError(t, err) diff --git a/block/internal/executing/executor_restart_test.go b/block/internal/executing/executor_restart_test.go index 3f0e8b500c..bcec0f50d4 100644 --- a/block/internal/executing/executor_restart_test.go +++ b/block/internal/executing/executor_restart_test.go @@ -2,6 +2,7 @@ package executing import ( "context" + "fmt" "testing" "time" @@ -66,6 +67,7 @@ func TestExecutor_RestartUsesPendingHeader(t *testing.T) { zerolog.Nop(), common.DefaultBlockOptions(), make(chan error, 1), + nil, ) require.NoError(t, err) @@ -185,6 +187,7 @@ func TestExecutor_RestartUsesPendingHeader(t *testing.T) { zerolog.Nop(), common.DefaultBlockOptions(), make(chan error, 1), + nil, ) require.NoError(t, err) @@ -254,6 +257,8 @@ func TestExecutor_RestartNoPendingHeader(t *testing.T) { cfg.Node.BlockTime = config.DurationWrapper{Duration: 10 * time.Millisecond} cfg.Node.MaxPendingHeadersAndData = 1000 + const numBlocks = 5 + gen := genesis.Genesis{ ChainID: "test-chain", InitialHeight: 1, @@ -283,6 +288,7 @@ func TestExecutor_RestartNoPendingHeader(t *testing.T) { zerolog.Nop(), common.DefaultBlockOptions(), make(chan error, 1), + nil, ) require.NoError(t, err) @@ -291,24 +297,29 @@ func TestExecutor_RestartNoPendingHeader(t *testing.T) { Return(initStateRoot, uint64(1024), nil).Once() require.NoError(t, exec1.initializeState()) - exec1.ctx, exec1.cancel = context.WithCancel(context.Background()) + exec1.ctx, exec1.cancel = context.WithCancel(t.Context()) - // Produce first block + // Produce n blocks mockSeq1.EXPECT().GetNextBatch(mock.Anything, mock.AnythingOfType("sequencer.GetNextBatchRequest")). RunAndReturn(func(ctx context.Context, req coreseq.GetNextBatchRequest) (*coreseq.GetNextBatchResponse, error) { return &coreseq.GetNextBatchResponse{ Batch: &coreseq.Batch{ - Transactions: [][]byte{[]byte("tx1")}, + Transactions: [][]byte{[]byte("any_tx")}, }, Timestamp: time.Now(), }, nil - }).Once() + }).Times(numBlocks) - mockExec1.EXPECT().ExecuteTxs(mock.Anything, mock.Anything, uint64(1), mock.AnythingOfType("time.Time"), initStateRoot). - Return([]byte("new_root_1"), uint64(1024), nil).Once() + lastStateRoot := initStateRoot + for i := range numBlocks { + newStateRoot := []byte(fmt.Sprintf("new_root_%d", i+1)) + mockExec1.EXPECT().ExecuteTxs(mock.Anything, mock.Anything, gen.InitialHeight+uint64(i), mock.AnythingOfType("time.Time"), lastStateRoot). + Return(newStateRoot, uint64(1024), nil).Once() + lastStateRoot = newStateRoot - err = exec1.produceBlock() - require.NoError(t, err) + require.NoError(t, exec1.produceBlock()) + } + require.Equal(t, uint64(numBlocks), exec1.GetLastState().LastBlockHeight) // Stop first executor exec1.cancel() @@ -335,16 +346,17 @@ func TestExecutor_RestartNoPendingHeader(t *testing.T) { zerolog.Nop(), common.DefaultBlockOptions(), make(chan error, 1), + nil, ) require.NoError(t, err) require.NoError(t, exec2.initializeState()) - exec2.ctx, exec2.cancel = context.WithCancel(context.Background()) + exec2.ctx, exec2.cancel = context.WithCancel(t.Context()) defer exec2.cancel() // Verify state loaded correctly state := exec2.getLastState() - assert.Equal(t, uint64(1), state.LastBlockHeight) + require.Equal(t, uint64(numBlocks), state.LastBlockHeight) // Now produce next block - should go through normal sequencer flow since no pending block mockSeq2.EXPECT().GetNextBatch(mock.Anything, mock.AnythingOfType("sequencer.GetNextBatchRequest")). @@ -357,8 +369,8 @@ func TestExecutor_RestartNoPendingHeader(t *testing.T) { }, nil }).Once() - mockExec2.EXPECT().ExecuteTxs(mock.Anything, mock.Anything, uint64(2), mock.AnythingOfType("time.Time"), []byte("new_root_1")). - Return([]byte("new_root_2"), uint64(1024), nil).Once() + mockExec2.EXPECT().ExecuteTxs(mock.Anything, mock.Anything, uint64(numBlocks+1), mock.AnythingOfType("time.Time"), lastStateRoot). + Return([]byte("new_root_after_restart"), uint64(1024), nil).Once() err = exec2.produceBlock() require.NoError(t, err) @@ -366,7 +378,7 @@ func TestExecutor_RestartNoPendingHeader(t *testing.T) { // Verify normal operation h, err := memStore.Height(context.Background()) require.NoError(t, err) - assert.Equal(t, uint64(2), h) + assert.Equal(t, uint64(numBlocks+1), h) // Verify sequencer was called (normal flow) mockSeq2.AssertCalled(t, "GetNextBatch", mock.Anything, mock.AnythingOfType("sequencer.GetNextBatchRequest")) diff --git a/block/internal/executing/executor_test.go b/block/internal/executing/executor_test.go index e310c6d40d..26cf249854 100644 --- a/block/internal/executing/executor_test.go +++ b/block/internal/executing/executor_test.go @@ -58,6 +58,7 @@ func TestExecutor_BroadcasterIntegration(t *testing.T) { zerolog.Nop(), common.DefaultBlockOptions(), make(chan error, 1), + nil, ) require.NoError(t, err) @@ -108,6 +109,7 @@ func TestExecutor_NilBroadcasters(t *testing.T) { zerolog.Nop(), common.DefaultBlockOptions(), make(chan error, 1), + nil, ) require.NoError(t, err) diff --git a/block/internal/reaping/reaper_test.go b/block/internal/reaping/reaper_test.go index fac03bdd5d..bd710669e2 100644 --- a/block/internal/reaping/reaper_test.go +++ b/block/internal/reaping/reaper_test.go @@ -58,6 +58,7 @@ func newTestExecutor(t *testing.T) *executing.Executor { zerolog.Nop(), common.DefaultBlockOptions(), make(chan error, 1), // error channel + nil, ) require.NoError(t, err) diff --git a/block/internal/submitting/submitter.go b/block/internal/submitting/submitter.go index 2a34b45084..fedae5076e 100644 --- a/block/internal/submitting/submitter.go +++ b/block/internal/submitting/submitter.go @@ -104,6 +104,7 @@ func (s *Submitter) Start(ctx context.Context) error { // Start DA submission loop if signer is available (aggregator nodes only) if s.signer != nil { + s.logger.Info().Msg("starting DA submission loop") s.wg.Add(1) go func() { defer s.wg.Done() diff --git a/block/internal/syncing/assert.go b/block/internal/syncing/assert.go new file mode 100644 index 0000000000..02cf4040a0 --- /dev/null +++ b/block/internal/syncing/assert.go @@ -0,0 +1,43 @@ +package syncing + +import ( + "errors" + "fmt" + + "github.com/evstack/ev-node/pkg/genesis" + "github.com/evstack/ev-node/types" +) + +func assertExpectedProposer(genesis genesis.Genesis, proposerAddr []byte) error { + if string(proposerAddr) != string(genesis.ProposerAddress) { + return fmt.Errorf("unexpected proposer: got %x, expected %x", + proposerAddr, genesis.ProposerAddress) + } + return nil +} + +func assertValidSignedData(signedData *types.SignedData, genesis genesis.Genesis) error { + if signedData == nil || signedData.Txs == nil { + return errors.New("empty signed data") + } + + if err := assertExpectedProposer(genesis, signedData.Signer.Address); err != nil { + return err + } + + dataBytes, err := signedData.Data.MarshalBinary() + if err != nil { + return fmt.Errorf("failed to get signed data payload: %w", err) + } + + valid, err := signedData.Signer.PubKey.Verify(dataBytes, signedData.Signature) + if err != nil { + return fmt.Errorf("failed to verify signature: %w", err) + } + + if !valid { + return fmt.Errorf("invalid signature") + } + + return nil +} diff --git a/block/internal/syncing/da_retriever.go b/block/internal/syncing/da_retriever.go index 9325d4d3bd..c5a8fbc84f 100644 --- a/block/internal/syncing/da_retriever.go +++ b/block/internal/syncing/da_retriever.go @@ -298,38 +298,12 @@ func (r *daRetriever) tryDecodeData(bz []byte, daHeight uint64) *types.Data { // assertExpectedProposer validates the proposer address func (r *daRetriever) assertExpectedProposer(proposerAddr []byte) error { - if string(proposerAddr) != string(r.genesis.ProposerAddress) { - return fmt.Errorf("unexpected proposer: got %x, expected %x", - proposerAddr, r.genesis.ProposerAddress) - } - return nil + return assertExpectedProposer(r.genesis, proposerAddr) } // assertValidSignedData validates signed data using the configured signature provider func (r *daRetriever) assertValidSignedData(signedData *types.SignedData) error { - if signedData == nil || signedData.Txs == nil { - return errors.New("empty signed data") - } - - if err := r.assertExpectedProposer(signedData.Signer.Address); err != nil { - return err - } - - dataBytes, err := signedData.Data.MarshalBinary() - if err != nil { - return fmt.Errorf("failed to get signed data payload: %w", err) - } - - valid, err := signedData.Signer.PubKey.Verify(dataBytes, signedData.Signature) - if err != nil { - return fmt.Errorf("failed to verify signature: %w", err) - } - - if !valid { - return fmt.Errorf("invalid signature") - } - - return nil + return assertValidSignedData(signedData, r.genesis) } // isEmptyDataExpected checks if empty data is expected for a header diff --git a/block/internal/syncing/raft_retriever.go b/block/internal/syncing/raft_retriever.go new file mode 100644 index 0000000000..1db3bd417b --- /dev/null +++ b/block/internal/syncing/raft_retriever.go @@ -0,0 +1,133 @@ +package syncing + +import ( + "context" + "errors" + "fmt" + "sync" + + "github.com/evstack/ev-node/block/internal/common" + "github.com/evstack/ev-node/pkg/genesis" + "github.com/evstack/ev-node/pkg/raft" + "github.com/evstack/ev-node/types" + "github.com/rs/zerolog" +) + +type eventProcessor interface { + handle(ctx context.Context, event common.DAHeightEvent) error +} +type eventProcessorFn func(ctx context.Context, event common.DAHeightEvent) error + +func (e eventProcessorFn) handle(ctx context.Context, event common.DAHeightEvent) error { + return e(ctx, event) +} + +type raftRetriever struct { + raftNode common.RaftNode + wg sync.WaitGroup + logger zerolog.Logger + genesis genesis.Genesis + eventProcessor eventProcessor + + mtx sync.Mutex + cancel context.CancelFunc +} + +func newRaftRetriever( + raftNode common.RaftNode, + genesis genesis.Genesis, + logger zerolog.Logger, + eventProcessor eventProcessor, +) *raftRetriever { + return &raftRetriever{ + raftNode: raftNode, + genesis: genesis, + logger: logger, + eventProcessor: eventProcessor, + } +} + +// Start begins the syncing component +func (r *raftRetriever) Start(ctx context.Context) error { + r.mtx.Lock() + defer r.mtx.Unlock() + if r.cancel != nil { + return errors.New("syncer already started") + } + ctx, r.cancel = context.WithCancel(ctx) + applyCh := make(chan raft.RaftApplyMsg, 1) + r.raftNode.SetApplyCallback(applyCh) + + r.wg.Add(1) + go func() { + defer r.wg.Done() + r.raftApplyLoop(ctx, applyCh) + }() + return nil +} + +// Stop gracefully shuts down the raft retriever +func (r *raftRetriever) Stop() { + r.mtx.Lock() + if r.cancel != nil { + r.cancel() + r.cancel = nil + } + r.mtx.Unlock() + + r.wg.Wait() +} + +// raftApplyLoop processes blocks received from raft +func (r *raftRetriever) raftApplyLoop(ctx context.Context, applyCh <-chan raft.RaftApplyMsg) { + r.logger.Info().Msg("starting raft apply loop") + defer r.logger.Info().Msg("raft apply loop stopped") + + for { + select { + case <-ctx.Done(): + return + case msg := <-applyCh: + if err := r.consumeRaftBlock(ctx, msg.State); err != nil { + r.logger.Error().Err(err).Uint64("height", msg.State.Height).Msg("failed to apply raft block") + } + } + } +} + +// consumeRaftBlock applies a block received from raft consensus +func (r *raftRetriever) consumeRaftBlock(ctx context.Context, state *raft.RaftBlockState) error { + r.logger.Debug().Uint64("height", state.Height).Msg("applying raft block") + + // Unmarshal header and data + var header types.SignedHeader + if err := header.UnmarshalBinary(state.Header); err != nil { + return fmt.Errorf("unmarshal header: %w", err) + } + + if err := header.Header.ValidateBasic(); err != nil { + r.logger.Debug().Err(err).Msg("invalid header structure") + return nil + } + if err := assertExpectedProposer(r.genesis, header.ProposerAddress); err != nil { + r.logger.Debug().Err(err).Msg("unexpected proposer") + return nil + } + + var data types.Data + if err := data.UnmarshalBinary(state.Data); err != nil { + return fmt.Errorf("unmarshal data: %w", err) + } + + event := common.DAHeightEvent{ + Header: &header, + Data: &data, + DaHeight: 0, // raft events don't have DA height context + } + return r.eventProcessor.handle(ctx, event) +} + +// Height returns the current height of the raft node's state. +func (r *raftRetriever) Height() uint64 { + return r.raftNode.GetState().Height +} diff --git a/block/internal/syncing/syncer.go b/block/internal/syncing/syncer.go index ee69edea7f..f941517885 100644 --- a/block/internal/syncing/syncer.go +++ b/block/internal/syncing/syncer.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "errors" "fmt" + "reflect" "sync" "sync/atomic" "time" @@ -57,8 +58,9 @@ type Syncer struct { errorCh chan<- error // Channel to report critical execution client failures // Handlers - daRetriever DARetriever - p2pHandler p2pHandler + daRetriever DARetriever + p2pHandler p2pHandler + raftRetriever *raftRetriever // Logging logger zerolog.Logger @@ -86,8 +88,9 @@ func NewSyncer( logger zerolog.Logger, options common.BlockOptions, errorCh chan<- error, + raftNode common.RaftNode, ) *Syncer { - return &Syncer{ + s := &Syncer{ store: store, exec: exec, daClient: daClient, @@ -104,6 +107,10 @@ func NewSyncer( errorCh: errorCh, logger: logger.With().Str("component", "syncer").Logger(), } + if raftNode != nil && !reflect.ValueOf(raftNode).IsNil() { + s.raftRetriever = newRaftRetriever(raftNode, genesis, logger, eventProcessorFn(s.pipeEvent)) + } + return s } // Start begins the syncing component @@ -123,6 +130,12 @@ func (s *Syncer) Start(ctx context.Context) error { s.p2pHandler.SetProcessedHeight(currentHeight) } + if s.raftRetriever != nil { + if err := s.raftRetriever.Start(s.ctx); err != nil { + return fmt.Errorf("start raft retriever: %w", err) + } + } + if !s.waitForGenesis() { return nil } @@ -143,15 +156,32 @@ func (s *Syncer) Start(ctx context.Context) error { // Stop shuts down the syncing component func (s *Syncer) Stop() error { - if s.cancel != nil { - s.cancel() + if s.cancel == nil { + return nil } + s.cancel() s.cancelP2PWait(0) s.wg.Wait() s.logger.Info().Msg("syncer stopped") + close(s.heightInCh) + s.cancel = nil return nil } +// isCatchingUpState returns true if the syncer has pending events or is behind the current raft height +func (s *Syncer) isCatchingUpState() bool { + return len(s.heightInCh) != 0 || func() bool { + currentHeight, err := s.store.Height(s.ctx) + if err != nil { + s.logger.Error().Err(err).Msg("failed to get current height") + return false + } + return s.headerStore.Store().Height() > currentHeight || + s.dataStore.Store().Height() > currentHeight || + s.raftRetriever != nil && s.raftRetriever.Height() > currentHeight + }() +} + // GetLastState returns the current state func (s *Syncer) GetLastState() types.State { state := s.lastState.Load() @@ -230,8 +260,10 @@ func (s *Syncer) processLoop() { select { case <-s.ctx.Done(): return - case heightEvent := <-s.heightInCh: - s.processHeightEvent(&heightEvent) + case heightEvent, ok := <-s.heightInCh: + if ok { + s.processHeightEvent(&heightEvent) + } } } } @@ -304,10 +336,8 @@ func (s *Syncer) fetchDAUntilCaughtUp() error { // Process DA events for _, event := range events { - select { - case s.heightInCh <- event: - default: - s.cache.SetPendingEvent(event.Header.Height(), &event) + if err := s.pipeEvent(s.ctx, event); err != nil { + return err } } @@ -402,6 +432,19 @@ func (s *Syncer) waitForGenesis() bool { return true } +func (s *Syncer) pipeEvent(ctx context.Context, event common.DAHeightEvent) error { + select { + case s.heightInCh <- event: + return nil + case <-ctx.Done(): + s.cache.SetPendingEvent(event.Header.Height(), &event) + return ctx.Err() + default: + s.cache.SetPendingEvent(event.Header.Height(), &event) + } + return nil +} + func (s *Syncer) processHeightEvent(event *common.DAHeightEvent) { height := event.Header.Height() headerHash := event.Header.Hash().String() @@ -448,8 +491,11 @@ func (s *Syncer) processHeightEvent(event *common.DAHeightEvent) { // Try to sync the next block if err := s.trySyncNextBlock(event); err != nil { - s.logger.Error().Err(err).Msg("failed to sync next block") - // If the error is not due to an validation error, re-store the event as pending + s.logger.Error().Err(err). + Uint64("event-height", event.Header.Height()). + Uint64("state-height", s.GetLastState().LastBlockHeight). + Msg("failed to sync next block") + // If the error is not due to a validation error, re-store the event as pending switch { case errors.Is(err, errInvalidBlock): // do not reschedule @@ -780,3 +826,12 @@ func (s *Syncer) cancelP2PWait(height uint64) { state.cancel() } } + +// IsSynced checks if the last block height in the stored state matches the expected height and returns true if they are equal. +func (s *Syncer) IsSynced(expHeight uint64) bool { + state, err := s.store.GetState(s.ctx) + if err != nil { + return false + } + return state.LastBlockHeight == expHeight && !s.isCatchingUpState() +} diff --git a/block/internal/syncing/syncer_backoff_test.go b/block/internal/syncing/syncer_backoff_test.go index 65f2586966..3b7440a056 100644 --- a/block/internal/syncing/syncer_backoff_test.go +++ b/block/internal/syncing/syncer_backoff_test.go @@ -355,6 +355,7 @@ func setupTestSyncer(t *testing.T, daBlockTime time.Duration) *Syncer { zerolog.Nop(), common.DefaultBlockOptions(), make(chan error, 1), + nil, ) require.NoError(t, syncer.initializeState()) diff --git a/block/internal/syncing/syncer_benchmark_test.go b/block/internal/syncing/syncer_benchmark_test.go index e2b6f6e51f..c78d6da33a 100644 --- a/block/internal/syncing/syncer_benchmark_test.go +++ b/block/internal/syncing/syncer_benchmark_test.go @@ -114,6 +114,7 @@ func newBenchFixture(b *testing.B, totalHeights uint64, shuffledTx bool, daDelay zerolog.Nop(), common.DefaultBlockOptions(), make(chan error, 1), + nil, ) require.NoError(b, s.initializeState()) s.ctx, s.cancel = ctx, cancel diff --git a/block/internal/syncing/syncer_test.go b/block/internal/syncing/syncer_test.go index 5c16da4435..8ec36b181f 100644 --- a/block/internal/syncing/syncer_test.go +++ b/block/internal/syncing/syncer_test.go @@ -128,6 +128,7 @@ func TestSyncer_validateBlock_DataHashMismatch(t *testing.T) { zerolog.Nop(), common.DefaultBlockOptions(), make(chan error, 1), + nil, ) require.NoError(t, s.initializeState()) // Create header and data with correct hash @@ -179,6 +180,7 @@ func TestProcessHeightEvent_SyncsAndUpdatesState(t *testing.T) { zerolog.Nop(), common.DefaultBlockOptions(), errChan, + nil, ) require.NoError(t, s.initializeState()) @@ -233,6 +235,7 @@ func TestSequentialBlockSync(t *testing.T) { zerolog.Nop(), common.DefaultBlockOptions(), errChan, + nil, ) require.NoError(t, s.initializeState()) s.ctx = context.Background() @@ -366,6 +369,7 @@ func TestSyncLoopPersistState(t *testing.T) { zerolog.Nop(), common.DefaultBlockOptions(), errorCh, + nil, ) require.NoError(t, syncerInst1.initializeState()) @@ -455,6 +459,7 @@ func TestSyncLoopPersistState(t *testing.T) { zerolog.Nop(), common.DefaultBlockOptions(), make(chan error, 1), + nil, ) require.NoError(t, syncerInst2.initializeState()) diff --git a/block/public.go b/block/public.go index f084f2757f..2cefdd58cc 100644 --- a/block/public.go +++ b/block/public.go @@ -48,3 +48,6 @@ func NewDAClient( DataNamespace: config.DA.GetDataNamespace(), }) } + +// Expose Raft types for consensus integration +type RaftNode = common.RaftNode diff --git a/go.mod b/go.mod index e1495d2ece..0337d95fa9 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,8 @@ require ( github.com/evstack/ev-node/core v1.0.0-beta.5 github.com/go-kit/kit v0.13.0 github.com/goccy/go-yaml v1.18.0 + github.com/hashicorp/raft v1.7.1 + github.com/hashicorp/raft-boltdb v0.0.0-20231211162105-6c830fa4535e github.com/ipfs/go-datastore v0.9.0 github.com/ipfs/go-ds-badger4 v0.1.8 github.com/libp2p/go-libp2p v0.43.0 @@ -34,8 +36,10 @@ require ( ) require ( + github.com/armon/go-metrics v0.4.1 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/boltdb/bolt v1.3.1 // indirect github.com/celestiaorg/go-libp2p-messenger v0.2.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -44,6 +48,7 @@ require ( github.com/dgraph-io/badger/v4 v4.5.1 // indirect github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.13.0 // indirect github.com/filecoin-project/go-clock v0.1.0 // indirect github.com/flynn/noise v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect @@ -59,6 +64,10 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/hashicorp/go-hclog v1.6.2 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-msgpack v0.5.5 // indirect + github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/huin/goupnp v1.3.0 // indirect diff --git a/go.sum b/go.sum index 120f8e51d1..ac7c63967c 100644 --- a/go.sum +++ b/go.sum @@ -12,14 +12,27 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= +github.com/armon/go-metrics v0.3.8/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/celestiaorg/go-header v0.7.3 h1:3+kIa+YXT789gPGRh3a55qmdYq3yTTBIqTyum26AvN0= @@ -31,8 +44,11 @@ github.com/celestiaorg/go-square/v3 v3.0.2/go.mod h1:oFReMLsSDMRs82ICFEeFQFCqNvw github.com/celestiaorg/utils v0.1.0 h1:WsP3O8jF7jKRgLNFmlDCwdThwOFMFxg0MnqhkLFVxPo= github.com/celestiaorg/utils v0.1.0/go.mod h1:vQTh7MHnvpIeCQZ2/Ph+w7K1R2UerDheZbgJEJD2hSU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -64,6 +80,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evstack/ev-node/core v1.0.0-beta.5 h1:lgxE8XiF3U9pcFgh7xuKMgsOGvLBGRyd9kc9MR4WL0o= github.com/evstack/ev-node/core v1.0.0-beta.5/go.mod h1:n2w/LhYQTPsi48m6lMj16YiIqsaQw6gxwjyJvR+B3sY= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -79,13 +97,18 @@ github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= @@ -125,6 +148,7 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -141,10 +165,30 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I= +github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack/v2 v2.1.2 h1:4Ee8FTp834e+ewB71RDrQ0VKpyFdrKOjvYtnQ/ltVj0= +github.com/hashicorp/go-msgpack/v2 v2.1.2/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/raft v1.1.0/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= +github.com/hashicorp/raft v1.7.1 h1:ytxsNx4baHsRZrhUcbt3+79zc4ly8qm7pi0393pSchY= +github.com/hashicorp/raft v1.7.1/go.mod h1:hUeiEwQQR/Nk2iKDD0dkEhklSsu3jcAcqvPzPoZSAEM= +github.com/hashicorp/raft-boltdb v0.0.0-20231211162105-6c830fa4535e h1:SK4y8oR4ZMHPvwVHryKI88kJPJda4UyWYvG5A6iEQxc= +github.com/hashicorp/raft-boltdb v0.0.0-20231211162105-6c830fa4535e/go.mod h1:EMz/UIuG93P0MBeHh6CbXQAEe8ckVJLZjhD17lBzK5Q= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -173,17 +217,21 @@ github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABo github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU= github.com/koron/go-ssdp v0.0.6/go.mod h1:0R9LfRJGek1zWTjN3JUNlm5INCDYGpRDfAptnct63fI= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -229,8 +277,12 @@ github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -251,7 +303,9 @@ github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dz github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= @@ -280,9 +334,12 @@ github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOo github.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -329,6 +386,7 @@ github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps= github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs= github.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54= github.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -338,16 +396,29 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= @@ -390,6 +461,8 @@ github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go. github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= @@ -412,13 +485,16 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -428,6 +504,7 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= @@ -470,6 +547,7 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -504,11 +582,14 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -541,15 +622,24 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -640,12 +730,16 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/node/execution_test.go b/node/execution_test.go deleted file mode 100644 index ea4e67cce6..0000000000 --- a/node/execution_test.go +++ /dev/null @@ -1,123 +0,0 @@ -//go:build !integration - -package node - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - coreexecutor "github.com/evstack/ev-node/core/execution" - testmocks "github.com/evstack/ev-node/test/mocks" - "github.com/evstack/ev-node/types" -) - -func TestBasicExecutionFlow(t *testing.T) { - require := require.New(t) - - node, cleanup := createNodeWithCleanup(t, getTestConfig(t, 1)) - defer cleanup() - - // Wait for node initialization - err := waitForNodeInitialization(node) - require.NoError(err) - - // Get the original executor to retrieve transactions - originalExecutor := getExecutorFromNode(t, node) - txs := getTransactions(t, originalExecutor, t.Context()) - - // Use the generated mock executor for testing execution steps - mockExec := testmocks.NewMockExecutor(t) - - // Define expected state and parameters - expectedInitialStateRoot := []byte("initial state root") - expectedMaxBytes := uint64(1024) - expectedNewStateRoot := []byte("new state root") - blockHeight := uint64(1) - chainID := "test-chain" - - // Set expectations on the mock executor - mockExec.On("InitChain", mock.Anything, mock.AnythingOfType("time.Time"), blockHeight, chainID). - Return(expectedInitialStateRoot, expectedMaxBytes, nil).Once() - mockExec.On("ExecuteTxs", mock.Anything, txs, blockHeight, mock.AnythingOfType("time.Time"), expectedInitialStateRoot). - Return(expectedNewStateRoot, expectedMaxBytes, nil).Once() - mockExec.On("SetFinal", mock.Anything, blockHeight). - Return(nil).Once() - - // Call helper functions with the mock executor - stateRoot, maxBytes := initializeChain(t, mockExec, t.Context()) - require.Equal(expectedInitialStateRoot, stateRoot) - require.Equal(expectedMaxBytes, maxBytes) - - newStateRoot, newMaxBytes := executeTransactions(t, mockExec, t.Context(), txs, stateRoot, maxBytes) - require.Equal(expectedNewStateRoot, newStateRoot) - require.Equal(expectedMaxBytes, newMaxBytes) - - finalizeExecution(t, mockExec, t.Context()) - - require.NotEmpty(newStateRoot) -} - -func waitForNodeInitialization(node *FullNode) error { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - ticker := time.NewTicker(100 * time.Millisecond) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - if node.IsRunning() && node.blockComponents != nil { - return nil - } - case <-ctx.Done(): - return errors.New("timeout waiting for node initialization") - } - } -} - -func getExecutorFromNode(t *testing.T, node *FullNode) coreexecutor.Executor { - if node.blockComponents != nil && node.blockComponents.Executor != nil { - // Return the underlying core executor from the block executor - // This is a test-only access pattern - t.Skip("Direct executor access not available through block components") - return nil - } - t.Skip("getExecutorFromNode needs block components with executor") - return nil -} - -func getTransactions(t *testing.T, executor coreexecutor.Executor, ctx context.Context) [][]byte { - txs, err := executor.GetTxs(ctx) - require.NoError(t, err) - return txs -} - -func initializeChain(t *testing.T, executor coreexecutor.Executor, ctx context.Context) ([]byte, uint64) { - genesisTime := time.Now() - initialHeight := uint64(1) - chainID := "test-chain" - stateRoot, maxBytes, err := executor.InitChain(ctx, genesisTime, initialHeight, chainID) - require.NoError(t, err) - require.Greater(t, maxBytes, uint64(0)) - return stateRoot, maxBytes -} - -func executeTransactions(t *testing.T, executor coreexecutor.Executor, ctx context.Context, txs [][]byte, stateRoot types.Hash, maxBytes uint64) ([]byte, uint64) { - blockHeight := uint64(1) - timestamp := time.Now() - newStateRoot, newMaxBytes, err := executor.ExecuteTxs(ctx, txs, blockHeight, timestamp, stateRoot) - require.NoError(t, err) - require.Greater(t, newMaxBytes, uint64(0)) - return newStateRoot, newMaxBytes -} - -func finalizeExecution(t *testing.T, executor coreexecutor.Executor, ctx context.Context) { - err := executor.SetFinal(ctx, 1) - require.NoError(t, err) -} diff --git a/node/failover.go b/node/failover.go new file mode 100644 index 0000000000..141b151d4a --- /dev/null +++ b/node/failover.go @@ -0,0 +1,280 @@ +package node + +import ( + "context" + "errors" + "fmt" + "net/http" + "sync/atomic" + "time" + + "github.com/evstack/ev-node/block" + coreda "github.com/evstack/ev-node/core/da" + coreexecutor "github.com/evstack/ev-node/core/execution" + coresequencer "github.com/evstack/ev-node/core/sequencer" + "github.com/evstack/ev-node/pkg/config" + genesispkg "github.com/evstack/ev-node/pkg/genesis" + "github.com/evstack/ev-node/pkg/p2p" + "github.com/evstack/ev-node/pkg/p2p/key" + "github.com/evstack/ev-node/pkg/raft" + rpcserver "github.com/evstack/ev-node/pkg/rpc/server" + "github.com/evstack/ev-node/pkg/signer" + "github.com/evstack/ev-node/pkg/store" + evsync "github.com/evstack/ev-node/pkg/sync" + ds "github.com/ipfs/go-datastore" + "github.com/rs/zerolog" + "golang.org/x/sync/errgroup" +) + +// failoverState collect the components to reset when switching modes. +type failoverState struct { + logger zerolog.Logger + + p2pClient *p2p.Client + headerSyncService *evsync.HeaderSyncService + dataSyncService *evsync.DataSyncService + rpcServer *http.Server + bc *block.Components +} + +func newSyncMode( + nodeConfig config.Config, + nodeKey *key.NodeKey, + genesis genesispkg.Genesis, + database ds.Batching, + exec coreexecutor.Executor, + da coreda.DA, + logger zerolog.Logger, + rktStore store.Store, + mainKV ds.Batching, + blockMetrics *block.Metrics, + nodeOpts NodeOptions, + raftNode *raft.Node, +) (*failoverState, error) { + blockComponentsFn := func(headerSyncService *evsync.HeaderSyncService, dataSyncService *evsync.DataSyncService) (*block.Components, error) { + return block.NewSyncComponents( + nodeConfig, + genesis, + rktStore, + exec, + da, + headerSyncService, + dataSyncService, + logger, + blockMetrics, + nodeOpts.BlockOptions, + raftNode, + ) + } + return setupFailoverState(nodeConfig, nodeKey, database, genesis, logger, mainKV, rktStore, blockComponentsFn, raftNode) +} +func newAggregatorMode( + nodeConfig config.Config, + nodeKey *key.NodeKey, + signer signer.Signer, + genesis genesispkg.Genesis, + database ds.Batching, + exec coreexecutor.Executor, + sequencer coresequencer.Sequencer, + da coreda.DA, + logger zerolog.Logger, + rktStore store.Store, + mainKV ds.Batching, + blockMetrics *block.Metrics, + nodeOpts NodeOptions, + raftNode *raft.Node, +) (*failoverState, error) { + + blockComponentsFn := func(headerSyncService *evsync.HeaderSyncService, dataSyncService *evsync.DataSyncService) (*block.Components, error) { + return block.NewAggregatorComponents( + nodeConfig, + genesis, + rktStore, + exec, + sequencer, + da, + signer, + headerSyncService, + dataSyncService, + logger, + blockMetrics, + nodeOpts.BlockOptions, + raftNode, + ) + } + + return setupFailoverState(nodeConfig, nodeKey, database, genesis, logger, mainKV, rktStore, blockComponentsFn, raftNode) +} + +func setupFailoverState( + nodeConfig config.Config, + nodeKey *key.NodeKey, + database ds.Batching, + genesis genesispkg.Genesis, + logger zerolog.Logger, + mainKV ds.Batching, + rktStore store.Store, + buildComponentsFn func(headerSyncService *evsync.HeaderSyncService, dataSyncService *evsync.DataSyncService) (*block.Components, error), + raftNode *raft.Node, +) (*failoverState, error) { + p2pClient, err := p2p.NewClient(nodeConfig.P2P, nodeKey.PrivKey, database, genesis.ChainID, logger, nil) + if err != nil { + return nil, err + } + + headerSyncService, err := evsync.NewHeaderSyncService(mainKV, nodeConfig, genesis, p2pClient, logger.With().Str("component", "HeaderSyncService").Logger()) + if err != nil { + return nil, fmt.Errorf("error while initializing HeaderSyncService: %w", err) + } + + dataSyncService, err := evsync.NewDataSyncService(mainKV, nodeConfig, genesis, p2pClient, logger.With().Str("component", "DataSyncService").Logger()) + if err != nil { + return nil, fmt.Errorf("error while initializing DataSyncService: %w", err) + } + + bestKnownHeightProvider := func() uint64 { + hHeight := headerSyncService.Store().Height() + dHeight := dataSyncService.Store().Height() + return min(hHeight, dHeight) + } + handler, err := rpcserver.NewServiceHandler( + rktStore, + headerSyncService.Store(), + dataSyncService.Store(), + p2pClient, + genesis.ProposerAddress, + logger, + nodeConfig, + bestKnownHeightProvider, + raftNode, + ) + if err != nil { + return nil, fmt.Errorf("error creating RPC handler: %w", err) + } + + rpcServer := &http.Server{ + Addr: nodeConfig.RPC.Address, + Handler: handler, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 120 * time.Second, + } + bc, err := buildComponentsFn(headerSyncService, dataSyncService) + if err != nil { + return nil, fmt.Errorf("build follower components: %w", err) + } + + return &failoverState{ + logger: logger, + p2pClient: p2pClient, + headerSyncService: headerSyncService, + dataSyncService: dataSyncService, + rpcServer: rpcServer, + bc: bc, + }, nil +} + +func (f *failoverState) Run(pCtx context.Context) (multiErr error) { + stopService := func(stoppable func(context.Context) error, name string) { + // parent context is cancelled already, so we need to create a new one + shutdownCtx, done := context.WithTimeout(context.Background(), 3*time.Second) + defer done() + + if err := stoppable(shutdownCtx); err != nil && !errors.Is(err, context.Canceled) { + multiErr = errors.Join(multiErr, fmt.Errorf("stopping %s: %w", name, err)) + } + } + cCtx, cancel := context.WithCancel(pCtx) + defer cancel() + wg, ctx := errgroup.WithContext(cCtx) + wg.Go(func() (rerr error) { + defer func() { + if err := f.bc.Stop(); err != nil && !errors.Is(err, context.Canceled) { + rerr = errors.Join(rerr, fmt.Errorf("stopping block components: %w", err)) + } + }() + + f.logger.Info().Str("addr", f.rpcServer.Addr).Msg("Started RPC server") + if err := f.rpcServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + return err + } + return nil + }) + + if err := f.p2pClient.Start(ctx); err != nil { + return fmt.Errorf("start p2p: %w", err) + } + defer f.p2pClient.Close() // nolint: errcheck + + if err := f.headerSyncService.Start(ctx); err != nil { + return fmt.Errorf("error while starting header sync service: %w", err) + } + defer stopService(f.headerSyncService.Stop, "header sync") + + if err := f.dataSyncService.Start(ctx); err != nil { + return fmt.Errorf("error while starting data sync service: %w", err) + } + defer stopService(f.dataSyncService.Stop, "data sync") + + wg.Go(func() error { + defer func() { + shutdownCtx, done := context.WithTimeout(context.Background(), 3*time.Second) + defer done() + _ = f.rpcServer.Shutdown(shutdownCtx) + }() + if err := f.bc.Start(ctx); err != nil && !errors.Is(err, context.Canceled) { + return fmt.Errorf("components started with error: %w", err) + } + return nil + }) + + return wg.Wait() +} + +func (f *failoverState) IsSynced(s raft.RaftBlockState) bool { + if s.Height == 0 { + return true + } + if f.bc.Syncer != nil { + return f.bc.Syncer.IsSynced(s.Height) + } + if f.bc.Executor != nil { + return f.bc.Executor.IsSynced(s.Height) + } + return false +} + +var _ leaderElection = &singleRoleElector{} +var _ testSupportElection = &singleRoleElector{} + +// singleRoleElector implements leaderElection but with a static role. No switchover. +type singleRoleElector struct { + running atomic.Bool + runnable raft.Runnable +} + +func newSingleRoleElector(factory func() (raft.Runnable, error)) (*singleRoleElector, error) { + r, err := factory() + if err != nil { + return nil, err + } + return &singleRoleElector{runnable: r}, nil +} + +func (a *singleRoleElector) Run(ctx context.Context) error { + a.running.Store(true) + defer a.running.Store(false) + return a.runnable.Run(ctx) +} + +func (a *singleRoleElector) IsRunning() bool { + return a.running.Load() +} + +// for testing purposes only +func (a *singleRoleElector) state() *failoverState { + if v, ok := a.runnable.(*failoverState); ok { + return v + } + return nil +} diff --git a/node/full.go b/node/full.go index 6d03a87c04..577baf2ac1 100644 --- a/node/full.go +++ b/node/full.go @@ -8,30 +8,27 @@ import ( "fmt" "net/http" "net/http/pprof" + "strings" "time" - ds "github.com/ipfs/go-datastore" - ktds "github.com/ipfs/go-datastore/keytransform" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/rs/zerolog" - - "github.com/evstack/ev-node/block" - coreda "github.com/evstack/ev-node/core/da" coreexecutor "github.com/evstack/ev-node/core/execution" coresequencer "github.com/evstack/ev-node/core/sequencer" "github.com/evstack/ev-node/pkg/config" genesispkg "github.com/evstack/ev-node/pkg/genesis" - "github.com/evstack/ev-node/pkg/p2p" - rpcserver "github.com/evstack/ev-node/pkg/rpc/server" + "github.com/evstack/ev-node/pkg/p2p/key" + raftpkg "github.com/evstack/ev-node/pkg/raft" "github.com/evstack/ev-node/pkg/service" "github.com/evstack/ev-node/pkg/signer" "github.com/evstack/ev-node/pkg/store" - evsync "github.com/evstack/ev-node/pkg/sync" + ds "github.com/ipfs/go-datastore" + ktds "github.com/ipfs/go-datastore/keytransform" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/rs/zerolog" ) -// prefixes used in KV store to separate rollkit data from execution environment data (if the same data base is reused) +// EvPrefix used in KV store to separate rollkit data from execution environment data (if the same data base is reused) var EvPrefix = "0" const ( @@ -42,6 +39,11 @@ const ( var _ Node = &FullNode{} +type leaderElection interface { + Run(ctx context.Context) error + IsRunning() bool +} + // FullNode represents a client node in Rollkit network. // It connects all the components and orchestrates their work. type FullNode struct { @@ -55,21 +57,18 @@ type FullNode struct { da coreda.DA - p2pClient *p2p.Client - hSyncService *evsync.HeaderSyncService - dSyncService *evsync.DataSyncService - Store store.Store - blockComponents *block.Components + Store store.Store + raftNode *raftpkg.Node - prometheusSrv *http.Server - pprofSrv *http.Server - rpcServer *http.Server + prometheusSrv *http.Server + pprofSrv *http.Server + leaderElection leaderElection } // newFullNode creates a new Rollkit full node. func newFullNode( nodeConfig config.Config, - p2pClient *p2p.Client, + nodeKey *key.NodeKey, signer signer.Signer, genesis genesispkg.Genesis, database ds.Batching, @@ -87,59 +86,50 @@ func newFullNode( mainKV := newPrefixKV(database, EvPrefix) rktStore := store.New(mainKV) - headerSyncService, err := initHeaderSyncService(mainKV, nodeConfig, genesis, p2pClient, logger) - if err != nil { - return nil, err + var raftNode *raftpkg.Node + if nodeConfig.Node.Aggregator && nodeConfig.Raft.Enable { + raftNode, err = initRaftNode(nodeConfig, logger) + if err != nil { + return nil, fmt.Errorf("failed to initialize raft node: %w", err) + } } - dataSyncService, err := initDataSyncService(mainKV, nodeConfig, genesis, p2pClient, logger) - if err != nil { - return nil, err + leaderFactory := func() (raftpkg.Runnable, error) { + logger.Info().Msg("Starting aggregator-MODE") + nodeConfig.Node.Aggregator = true + nodeConfig.P2P.Peers = "" // peers are not supported in aggregator mode + return newAggregatorMode(nodeConfig, nodeKey, signer, genesis, database, exec, sequencer, da, logger, rktStore, mainKV, blockMetrics, nodeOpts, raftNode) } - - var blockComponents *block.Components - if nodeConfig.Node.Aggregator { - blockComponents, err = block.NewAggregatorComponents( - nodeConfig, - genesis, - rktStore, - exec, - sequencer, - da, - signer, - headerSyncService, - dataSyncService, - logger, - blockMetrics, - nodeOpts.BlockOptions, - ) - } else { - blockComponents, err = block.NewSyncComponents( - nodeConfig, - genesis, - rktStore, - exec, - da, - headerSyncService, - dataSyncService, - logger, - blockMetrics, - nodeOpts.BlockOptions, - ) + followerFactory := func() (raftpkg.Runnable, error) { + logger.Info().Msg("Starting sync-MODE") + nodeConfig.Node.Aggregator = false + return newSyncMode(nodeConfig, nodeKey, genesis, database, exec, da, logger, rktStore, mainKV, blockMetrics, nodeOpts, raftNode) } - if err != nil { - return nil, err + + // Initialize raft node if enabled (for both aggregator and sync nodes) + var leaderElection leaderElection + switch { + case nodeConfig.Node.Aggregator && nodeConfig.Raft.Enable: + leaderElection = raftpkg.NewDynamicLeaderElection(logger, leaderFactory, followerFactory, raftNode) + case nodeConfig.Node.Aggregator && !nodeConfig.Raft.Enable: + if leaderElection, err = newSingleRoleElector(leaderFactory); err != nil { + return + } + case !nodeConfig.Node.Aggregator && !nodeConfig.Raft.Enable: + if leaderElection, err = newSingleRoleElector(followerFactory); err != nil { + return + } + default: + return nil, fmt.Errorf("raft config must be used in sequencer setup only") } node := &FullNode{ - genesis: genesis, - nodeConfig: nodeConfig, - p2pClient: p2pClient, - blockComponents: blockComponents, - da: da, - Store: rktStore, - hSyncService: headerSyncService, - dSyncService: dataSyncService, + genesis: genesis, + nodeConfig: nodeConfig, + da: da, + Store: rktStore, + leaderElection: leaderElection, + raftNode: raftNode, } node.BaseService = *service.NewBaseService(logger, "Node", node) @@ -147,36 +137,32 @@ func newFullNode( return node, nil } -func initHeaderSyncService( - mainKV ds.Batching, - nodeConfig config.Config, - genesis genesispkg.Genesis, - p2pClient *p2p.Client, - logger zerolog.Logger, -) (*evsync.HeaderSyncService, error) { - componentLogger := logger.With().Str("component", "HeaderSyncService").Logger() +func initRaftNode(nodeConfig config.Config, logger zerolog.Logger) (*raftpkg.Node, error) { + raftCfg := &raftpkg.Config{ + NodeID: nodeConfig.Raft.NodeID, + RaftAddr: nodeConfig.Raft.RaftAddr, + RaftDir: nodeConfig.Raft.RaftDir, + Bootstrap: nodeConfig.Raft.Bootstrap, + SnapCount: nodeConfig.Raft.SnapCount, + SendTimeout: nodeConfig.Raft.SendTimeout, + HeartbeatTimeout: nodeConfig.Raft.HeartbeatTimeout, + } - headerSyncService, err := evsync.NewHeaderSyncService(mainKV, nodeConfig, genesis, p2pClient, componentLogger) + if nodeConfig.Raft.Peers != "" { + raftCfg.Peers = strings.Split(nodeConfig.Raft.Peers, ",") + } + raftNode, err := raftpkg.NewNode(raftCfg, logger) if err != nil { - return nil, fmt.Errorf("error while initializing HeaderSyncService: %w", err) + return nil, fmt.Errorf("create raft node: %w", err) } - return headerSyncService, nil -} -func initDataSyncService( - mainKV ds.Batching, - nodeConfig config.Config, - genesis genesispkg.Genesis, - p2pClient *p2p.Client, - logger zerolog.Logger, -) (*evsync.DataSyncService, error) { - componentLogger := logger.With().Str("component", "DataSyncService").Logger() + logger.Info(). + Str("node_id", nodeConfig.Raft.NodeID). + Str("addr", nodeConfig.Raft.RaftAddr). + Bool("bootstrap", nodeConfig.Raft.Bootstrap). + Msg("initialized raft node") - dataSyncService, err := evsync.NewDataSyncService(mainKV, nodeConfig, genesis, p2pClient, componentLogger) - if err != nil { - return nil, fmt.Errorf("error while initializing DataSyncService: %w", err) - } - return dataSyncService, nil + return raftNode, nil } // initGenesisChunks creates a chunked format of the genesis document to make it easier to @@ -283,65 +269,16 @@ func (n *FullNode) Run(parentCtx context.Context) error { (n.nodeConfig.Instrumentation.IsPrometheusEnabled() || n.nodeConfig.Instrumentation.IsPprofEnabled()) { n.prometheusSrv, n.pprofSrv = n.startInstrumentationServer() } - - // Start RPC server - bestKnownHeightProvider := func() uint64 { - hHeight := n.hSyncService.Store().Height() - dHeight := n.dSyncService.Store().Height() - return min(hHeight, dHeight) - } - - handler, err := rpcserver.NewServiceHandler( - n.Store, - n.hSyncService.Store(), - n.dSyncService.Store(), - n.p2pClient, - n.genesis.ProposerAddress, - n.Logger, - n.nodeConfig, - bestKnownHeightProvider, - ) - if err != nil { - return fmt.Errorf("error creating RPC handler: %w", err) - } - - n.rpcServer = &http.Server{ - Addr: n.nodeConfig.RPC.Address, - Handler: handler, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - IdleTimeout: 120 * time.Second, - } - - go func() { - n.Logger.Info().Str("addr", n.nodeConfig.RPC.Address).Msg("started RPC server") - if err := n.rpcServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - n.Logger.Error().Err(err).Msg("RPC server error") + // Start leader election + if n.raftNode != nil { + if err := n.raftNode.Start(ctx); err != nil { + return fmt.Errorf("error while starting leader election: %w", err) } - }() - - n.Logger.Info().Msg("starting P2P client") - err = n.p2pClient.Start(ctx) - if err != nil { - return fmt.Errorf("error while starting P2P client: %w", err) - } - - if err = n.hSyncService.Start(ctx); err != nil { - return fmt.Errorf("error while starting header sync service: %w", err) - } - - if err = n.dSyncService.Start(ctx); err != nil { - return fmt.Errorf("error while starting data sync service: %w", err) } var runtimeErr error - // Start the block components (blocking) - if err := n.blockComponents.Start(ctx); err != nil { - if !errors.Is(err, context.Canceled) { - runtimeErr = fmt.Errorf("running block components: %w", err) - } else { - n.Logger.Info().Msg("context canceled, stopping node") - } + if err := n.leaderElection.Run(ctx); err != nil && !errors.Is(err, context.Canceled) { + runtimeErr = err } // blocking components start exited, propagate shutdown to all other processes @@ -354,45 +291,18 @@ func (n *FullNode) Run(parentCtx context.Context) error { var shutdownMultiErr error // Variable to accumulate multiple errors - // Stop block components - if err := n.blockComponents.Stop(); err != nil { - n.Logger.Error().Err(err).Msg("error stopping block components") - shutdownMultiErr = errors.Join(shutdownMultiErr, fmt.Errorf("stopping block components: %w", err)) - } - - // Stop Header Sync Service - err = n.hSyncService.Stop(shutdownCtx) - if err != nil { - // Log context canceled errors at a lower level if desired, or handle specific non-cancel errors - if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { - n.Logger.Error().Err(err).Msg("error stopping header sync service") - shutdownMultiErr = errors.Join(shutdownMultiErr, fmt.Errorf("stopping header sync service: %w", err)) + // Stop leader election + if n.raftNode != nil { + if err := n.raftNode.Stop(); err != nil && !errors.Is(err, context.Canceled) { + shutdownMultiErr = errors.Join(shutdownMultiErr, fmt.Errorf("stopping leader election: %w", err)) } else { - n.Logger.Debug().Err(err).Msg("header sync service stop context ended") // Log cancellation as debug + n.Logger.Debug().Msg("leader election stopped") } } - // Stop Data Sync Service - err = n.dSyncService.Stop(shutdownCtx) - if err != nil { - // Log context canceled errors at a lower level if desired, or handle specific non-cancel errors - if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { - n.Logger.Error().Err(err).Msg("error stopping data sync service") - shutdownMultiErr = errors.Join(shutdownMultiErr, fmt.Errorf("stopping data sync service: %w", err)) - } else { - n.Logger.Debug().Err(err).Msg("data sync service stop context ended") // Log cancellation as debug - } - } - - // Stop P2P Client - err = n.p2pClient.Close() - if err != nil { - shutdownMultiErr = errors.Join(shutdownMultiErr, fmt.Errorf("closing P2P client: %w", err)) - } - // Shutdown Prometheus Server if n.prometheusSrv != nil { - err = n.prometheusSrv.Shutdown(shutdownCtx) + err := n.prometheusSrv.Shutdown(shutdownCtx) // http.ErrServerClosed is expected on graceful shutdown if err != nil && !errors.Is(err, http.ErrServerClosed) { shutdownMultiErr = errors.Join(shutdownMultiErr, fmt.Errorf("shutting down Prometheus server: %w", err)) @@ -403,7 +313,7 @@ func (n *FullNode) Run(parentCtx context.Context) error { // Shutdown Pprof Server if n.pprofSrv != nil { - err = n.pprofSrv.Shutdown(shutdownCtx) + err := n.pprofSrv.Shutdown(shutdownCtx) if err != nil && !errors.Is(err, http.ErrServerClosed) { shutdownMultiErr = errors.Join(shutdownMultiErr, fmt.Errorf("shutting down pprof server: %w", err)) } else { @@ -411,32 +321,13 @@ func (n *FullNode) Run(parentCtx context.Context) error { } } - // Shutdown RPC Server - if n.rpcServer != nil { - err = n.rpcServer.Shutdown(shutdownCtx) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - shutdownMultiErr = errors.Join(shutdownMultiErr, fmt.Errorf("shutting down RPC server: %w", err)) - } else { - n.Logger.Debug().Err(err).Msg("RPC server shutdown context ended") - } - } - // Ensure Store.Close is called last to maximize chance of data flushing - if err = n.Store.Close(); err != nil { + if err := n.Store.Close(); err != nil { shutdownMultiErr = errors.Join(shutdownMultiErr, fmt.Errorf("closing store: %w", err)) } else { n.Logger.Debug().Msg("store closed") } - // Save caches if needed - if n.blockComponents != nil && n.blockComponents.Cache != nil { - if err := n.blockComponents.Cache.SaveToDisk(); err != nil { - shutdownMultiErr = errors.Join(shutdownMultiErr, fmt.Errorf("saving caches: %w", err)) - } else { - n.Logger.Debug().Msg("caches saved") - } - } - // Log final status if shutdownMultiErr != nil { for _, err := range shutdownMultiErr.(interface{ Unwrap() []error }).Unwrap() { @@ -470,7 +361,7 @@ func (n *FullNode) GetGenesisChunks() ([]string, error) { // IsRunning returns true if the node is running. func (n *FullNode) IsRunning() bool { - return n.blockComponents != nil + return n.leaderElection.IsRunning() } func newPrefixKV(kvStore ds.Batching, prefix string) ds.Batching { diff --git a/node/full_node_integration_test.go b/node/full_node_integration_test.go index c13d6dc0d7..09e4bd21fc 100644 --- a/node/full_node_integration_test.go +++ b/node/full_node_integration_test.go @@ -49,20 +49,22 @@ func TestTxGossipingMultipleNodesNoDA(t *testing.T) { } // Inject a transaction into the sequencer's executor - if nodes[0].blockComponents != nil && nodes[0].blockComponents.Executor != nil { + if state := castState(t, nodes[0]); state.bc.Executor != nil { // Access the core executor from the block executor - coreExec := nodes[0].blockComponents.Executor.GetCoreExecutor() + coreExec := state.bc.Executor.GetCoreExecutor() if dummyExec, ok := coreExec.(interface{ InjectTx([]byte) }); ok { dummyExec.InjectTx([]byte("test tx")) } else { t.Fatal("Warning: Could not cast core executor to DummyExecutor") } + } else { + t.Fatal("executor empty") } blocksToWaitFor := uint64(3) // Wait for all nodes to reach at least blocksToWaitFor blocks - for _, nodeItem := range nodes { + for i, nodeItem := range nodes { requireEmptyChan(t, errChan) - require.NoError(waitForAtLeastNBlocks(nodeItem, blocksToWaitFor, Store)) + require.NoError(waitForAtLeastNBlocks(nodeItem, blocksToWaitFor, Store), "node %d", i) } // Shutdown all nodes and wait @@ -82,9 +84,6 @@ func TestTxGossipingMultipleNodesDAIncluded(t *testing.T) { numNodes := 4 nodes, cleanups := createNodesWithCleanup(t, numNodes, config) - for _, cleanup := range cleanups { - defer cleanup() - } ctxs, cancels := createNodeContexts(numNodes) var runningWg sync.WaitGroup @@ -92,6 +91,7 @@ func TestTxGossipingMultipleNodesDAIncluded(t *testing.T) { errChan := make(chan error, numNodes) // Start only the sequencer first startNodeInBackground(t, nodes, ctxs, &runningWg, 0, errChan) + t.Cleanup(func() { shutdownAndWait(t, cleanups, &runningWg, 10*time.Second) }) // Wait for the first block to be produced by the sequencer err := waitForFirstBlock(nodes[0], Header) @@ -113,9 +113,9 @@ func TestTxGossipingMultipleNodesDAIncluded(t *testing.T) { } // Inject transactions into the sequencer's executor - if nodes[0].blockComponents != nil && nodes[0].blockComponents.Executor != nil { + if state := castState(t, nodes[0]); state != nil && state.bc.Executor != nil { // Access the core executor from the block executor - coreExec := nodes[0].blockComponents.Executor.GetCoreExecutor() + coreExec := state.bc.Executor.GetCoreExecutor() if dummyExec, ok := coreExec.(interface{ InjectTx([]byte) }); ok { dummyExec.InjectTx([]byte("test tx 1")) dummyExec.InjectTx([]byte("test tx 2")) @@ -139,6 +139,14 @@ func TestTxGossipingMultipleNodesDAIncluded(t *testing.T) { assertAllNodesSynced(t, nodes, blocksToWaitFor) } +func castState(t *testing.T, node *FullNode) *failoverState { + v, ok := node.leaderElection.(testSupportElection) + require.True(t, ok) + state := v.state() + require.NotNil(t, state) + return state +} + // TestFastDASync verifies that a new node can quickly synchronize with the DA layer using fast sync. // // This test sets up two nodes with different block and DA block times. It starts the sequencer node, waits for it to produce and DA-include several blocks, @@ -155,16 +163,13 @@ func TestFastDASync(t *testing.T) { config.DA.BlockTime = evconfig.DurationWrapper{Duration: 200 * time.Millisecond} nodes, cleanups := createNodesWithCleanup(t, 2, config) - for _, cleanup := range cleanups { - defer cleanup() - } - ctxs, cancels := createNodeContexts(len(nodes)) var runningWg sync.WaitGroup errChan := make(chan error, len(nodes)) // Start only the first node startNodeInBackground(t, nodes, ctxs, &runningWg, 0, errChan) + t.Cleanup(func() { shutdownAndWait(t, cleanups, &runningWg, 10*time.Second) }) // Wait for the first node to produce a few blocks blocksToWaitFor := uint64(2) @@ -175,6 +180,7 @@ func TestFastDASync(t *testing.T) { // Now start the second node and time its sync startNodeInBackground(t, nodes, ctxs, &runningWg, 1, errChan) + start := time.Now() // Wait for the second node to catch up to the first node require.NoError(waitForAtLeastNBlocks(nodes[1], blocksToWaitFor, Store)) @@ -309,6 +315,7 @@ func testSingleSequencerSingleFullNode(t *testing.T, source Source) { // Start the sequencer first startNodeInBackground(t, nodes, ctxs, &runningWg, 0, errChan) + t.Cleanup(func() { shutdownAndWait(t, cancels, &runningWg, 10*time.Second) }) // Wait for the sequencer to produce at first block require.NoError(waitForFirstBlock(nodes[0], source)) @@ -328,9 +335,6 @@ func testSingleSequencerSingleFullNode(t *testing.T, source Source) { // Verify both nodes are synced using the helper require.NoError(verifyNodesSynced(nodes[0], nodes[1], source)) - - // Cancel all node contexts to signal shutdown and wait - shutdownAndWait(t, cancels, &runningWg, 5*time.Second) } // testSingleSequencerTwoFullNodes sets up a single sequencer and two full nodes, starts the sequencer, waits for it to produce a block, then starts the full nodes. @@ -352,6 +356,7 @@ func testSingleSequencerTwoFullNodes(t *testing.T, source Source) { // Start the sequencer first startNodeInBackground(t, nodes, ctxs, &runningWg, 0, errChan) + t.Cleanup(func() { shutdownAndWait(t, cancels, &runningWg, 10*time.Second) }) // Wait for the sequencer to produce at first block require.NoError(waitForFirstBlock(nodes[0], source)) diff --git a/node/helpers.go b/node/helpers.go index 0dc1a20cc3..c24f38ac38 100644 --- a/node/helpers.go +++ b/node/helpers.go @@ -3,9 +3,12 @@ package node import ( "context" + "encoding/binary" "errors" "fmt" "time" + + "github.com/evstack/ev-node/pkg/store" ) // Source is an enum representing different sources of height @@ -57,9 +60,15 @@ func getNodeHeight(node Node, source Source) (uint64, error) { } } +type testSupportElection interface { + state() *failoverState +} + func getNodeHeightFromHeader(node Node) (uint64, error) { if fn, ok := node.(*FullNode); ok { - return fn.hSyncService.Store().Height(), nil + if v, ok := fn.leaderElection.(testSupportElection); ok { + return v.state().headerSyncService.Store().Height(), nil + } } if ln, ok := node.(*LightNode); ok { return ln.hSyncService.Store().Height(), nil @@ -69,7 +78,9 @@ func getNodeHeightFromHeader(node Node) (uint64, error) { func getNodeHeightFromData(node Node) (uint64, error) { if fn, ok := node.(*FullNode); ok { - return fn.dSyncService.Store().Height(), nil + if v, ok := fn.leaderElection.(testSupportElection); ok { + return v.state().dataSyncService.Store().Height(), nil + } } return 0, errors.New("not a full node") } @@ -98,19 +109,20 @@ func waitForAtLeastNBlocks(node Node, n uint64, source Source) error { // waitForAtLeastNDAIncludedHeight waits for the DA included height to be at least n func waitForAtLeastNDAIncludedHeight(node Node, n uint64) error { return Retry(300, 100*time.Millisecond, func() error { - if fn, ok := node.(*FullNode); ok { - if fn.blockComponents != nil && fn.blockComponents.Submitter != nil { - nHeight := fn.blockComponents.Submitter.GetDAIncludedHeight() - if nHeight == 0 { - return fmt.Errorf("waiting for DA inclusion") - } - if nHeight >= n { - return nil - } - return fmt.Errorf("current DA inclusion height %d, expected %d", nHeight, n) - } + fn, ok := node.(*FullNode) + if !ok { + return fmt.Errorf("not a full node") + } + + bz, err := fn.Store.GetMetadata(context.Background(), store.DAIncludedHeightKey) + if err != nil || len(bz) != 8 { + return fmt.Errorf("waiting for DA inclusion") + } + nHeight := binary.LittleEndian.Uint64(bz) + if nHeight >= n { + return nil } - return fmt.Errorf("not a full node or submitter not initialized") + return fmt.Errorf("current DA inclusion height %d, expected %d", nHeight, n) }) } diff --git a/node/helpers_test.go b/node/helpers_test.go index e77744a4ec..e241d006f7 100644 --- a/node/helpers_test.go +++ b/node/helpers_test.go @@ -22,7 +22,6 @@ import ( coresequencer "github.com/evstack/ev-node/core/sequencer" evconfig "github.com/evstack/ev-node/pkg/config" - "github.com/evstack/ev-node/pkg/p2p" "github.com/evstack/ev-node/pkg/p2p/key" remote_signer "github.com/evstack/ev-node/pkg/signer/noop" "github.com/evstack/ev-node/types" @@ -43,7 +42,7 @@ const ( ) // createTestComponents creates test components for node initialization -func createTestComponents(t *testing.T, config evconfig.Config) (coreexecutor.Executor, coresequencer.Sequencer, coreda.DA, *p2p.Client, datastore.Batching, *key.NodeKey, func()) { +func createTestComponents(t *testing.T, config evconfig.Config) (coreexecutor.Executor, coresequencer.Sequencer, coreda.DA, datastore.Batching, *key.NodeKey, func()) { executor := coreexecutor.NewDummyExecutor() sequencer := coresequencer.NewDummySequencer() dummyDA := coreda.NewDummyDA(100_000, config.DA.BlockTime.Duration) @@ -59,20 +58,17 @@ func createTestComponents(t *testing.T, config evconfig.Config) (coreexecutor.Ex PrivKey: genesisValidatorKey, PubKey: genesisValidatorKey.GetPublic(), } - logger := zerolog.Nop() - p2pClient, err := p2p.NewClient(config.P2P, p2pKey.PrivKey, dssync.MutexWrap(datastore.NewMapDatastore()), "test-chain", logger, p2p.NopMetrics()) - require.NoError(t, err) - require.NotNil(t, p2pClient) ds := dssync.MutexWrap(datastore.NewMapDatastore()) - return executor, sequencer, dummyDA, p2pClient, ds, p2pKey, stopDAHeightTicker + return executor, sequencer, dummyDA, ds, p2pKey, stopDAHeightTicker } func getTestConfig(t *testing.T, n int) evconfig.Config { // Use a higher base port to reduce chances of conflicts with system services startPort := 40000 // Spread port ranges further apart return evconfig.Config{ - RootDir: t.TempDir(), + RootDir: t.TempDir(), + ClearCache: true, // Clear cache between tests to avoid interference with other tests and slow shutdown on serialization Node: evconfig.NodeConfig{ Aggregator: true, BlockTime: evconfig.DurationWrapper{Duration: 100 * time.Millisecond}, @@ -102,7 +98,7 @@ func newTestNode( executor coreexecutor.Executor, sequencer coresequencer.Sequencer, dac coreda.DA, - p2pClient *p2p.Client, + nodeKey *key.NodeKey, ds datastore.Batching, stopDAHeightTicker func(), ) (*FullNode, func()) { @@ -111,17 +107,21 @@ func newTestNode( remoteSigner, err := remote_signer.NewNoopSigner(genesisValidatorKey) require.NoError(t, err) + logger := zerolog.Nop() + if testing.Verbose() { + logger = zerolog.New(zerolog.NewTestWriter(t)) + } node, err := NewNode( config, executor, sequencer, dac, remoteSigner, - p2pClient, + nodeKey, genesis, ds, DefaultMetricsProvider(evconfig.DefaultInstrumentationConfig()), - zerolog.Nop(), + logger, NodeOptions{}, ) require.NoError(t, err) @@ -136,8 +136,8 @@ func newTestNode( } func createNodeWithCleanup(t *testing.T, config evconfig.Config) (*FullNode, func()) { - executor, sequencer, dac, p2pClient, ds, _, stopDAHeightTicker := createTestComponents(t, config) - return newTestNode(t, config, executor, sequencer, dac, p2pClient, ds, stopDAHeightTicker) + executor, sequencer, dac, ds, nodeKey, stopDAHeightTicker := createTestComponents(t, config) + return newTestNode(t, config, executor, sequencer, dac, nodeKey, ds, stopDAHeightTicker) } func createNodeWithCustomComponents( @@ -146,11 +146,11 @@ func createNodeWithCustomComponents( executor coreexecutor.Executor, sequencer coresequencer.Sequencer, dac coreda.DA, - p2pClient *p2p.Client, + nodeKey *key.NodeKey, ds datastore.Batching, stopDAHeightTicker func(), ) (*FullNode, func()) { - return newTestNode(t, config, executor, sequencer, dac, p2pClient, ds, stopDAHeightTicker) + return newTestNode(t, config, executor, sequencer, dac, nodeKey, ds, stopDAHeightTicker) } // Creates the given number of nodes the given nodes using the given wait group to synchronize them @@ -168,7 +168,7 @@ func createNodesWithCleanup(t *testing.T, num int, config evconfig.Config) ([]*F aggListenAddress := config.P2P.ListenAddress aggPeers := config.P2P.Peers - executor, sequencer, dac, p2pClient, ds, aggP2PKey, stopDAHeightTicker := createTestComponents(t, config) + executor, sequencer, dac, ds, aggP2PKey, stopDAHeightTicker := createTestComponents(t, config) aggPeerID, err := peer.IDFromPrivateKey(aggP2PKey.PrivKey) require.NoError(err) @@ -182,7 +182,7 @@ func createNodesWithCleanup(t *testing.T, num int, config evconfig.Config) ([]*F sequencer, dac, remoteSigner, - p2pClient, + aggP2PKey, genesis, ds, DefaultMetricsProvider(evconfig.DefaultInstrumentationConfig()), @@ -209,14 +209,14 @@ func createNodesWithCleanup(t *testing.T, num int, config evconfig.Config) ([]*F } config.P2P.ListenAddress = fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 40001+i) config.RPC.Address = fmt.Sprintf("127.0.0.1:%d", 8001+i) - executor, sequencer, _, p2pClient, _, nodeP2PKey, stopDAHeightTicker := createTestComponents(t, config) + executor, sequencer, _, _, nodeP2PKey, stopDAHeightTicker := createTestComponents(t, config) node, err := NewNode( config, executor, sequencer, dac, nil, - p2pClient, + nodeP2PKey, genesis, dssync.MutexWrap(datastore.NewMapDatastore()), DefaultMetricsProvider(evconfig.DefaultInstrumentationConfig()), @@ -266,7 +266,7 @@ func startNodeInBackground(t *testing.T, nodes []*FullNode, ctxs []context.Conte } // Helper to cancel all contexts and wait for goroutines with timeout -func shutdownAndWait(t *testing.T, cancels []context.CancelFunc, wg *sync.WaitGroup, timeout time.Duration) { +func shutdownAndWait[T ~func()](t *testing.T, cancels []T, wg *sync.WaitGroup, timeout time.Duration) { for _, cancel := range cancels { cancel() } diff --git a/node/light.go b/node/light.go index bdc4a92351..cc265c7c3f 100644 --- a/node/light.go +++ b/node/light.go @@ -7,6 +7,7 @@ import ( "net/http" "time" + "github.com/evstack/ev-node/pkg/p2p/key" ds "github.com/ipfs/go-datastore" "github.com/rs/zerolog" @@ -38,10 +39,15 @@ type LightNode struct { func newLightNode( conf config.Config, genesis genesis.Genesis, - p2pClient *p2p.Client, + nodeKey *key.NodeKey, database ds.Batching, logger zerolog.Logger, ) (ln *LightNode, err error) { + p2pClient, err := p2p.NewClient(conf.P2P, nodeKey.PrivKey, database, genesis.ChainID, logger, nil) + if err != nil { + return nil, err + } + componentLogger := logger.With().Str("component", "HeaderSyncService").Logger() headerSyncService, err := sync.NewHeaderSyncService(database, conf, genesis, p2pClient, componentLogger) if err != nil { @@ -93,6 +99,7 @@ func (ln *LightNode) Run(parentCtx context.Context) error { ln.Logger, ln.nodeConfig, bestKnown, + nil, ) if err != nil { return fmt.Errorf("error creating RPC handler: %w", err) diff --git a/node/light_test.go b/node/light_test.go index c6def936e6..f459ad54b0 100644 --- a/node/light_test.go +++ b/node/light_test.go @@ -13,7 +13,6 @@ import ( "github.com/evstack/ev-node/pkg/config" "github.com/evstack/ev-node/pkg/genesis" - "github.com/evstack/ev-node/pkg/p2p" p2p_key "github.com/evstack/ev-node/pkg/p2p/key" ) @@ -38,14 +37,9 @@ func TestLightNodeLifecycle(t *testing.T) { require.NoError(err) logger := zerolog.Nop() - p2pMetrics := p2p.NopMetrics() - db := ds_sync.MutexWrap(ds.NewMapDatastore()) - p2pClient, err := p2p.NewClient(conf.P2P, p2pKey.PrivKey, db, gen.ChainID, logger, p2pMetrics) - require.NoError(err) - - ln, err := newLightNode(conf, gen, p2pClient, db, logger) + ln, err := newLightNode(conf, gen, p2pKey, db, logger) require.NoError(err) require.NotNil(ln) diff --git a/node/node.go b/node/node.go index 4d780035aa..f0c2038df9 100644 --- a/node/node.go +++ b/node/node.go @@ -10,7 +10,7 @@ import ( coresequencer "github.com/evstack/ev-node/core/sequencer" "github.com/evstack/ev-node/pkg/config" "github.com/evstack/ev-node/pkg/genesis" - "github.com/evstack/ev-node/pkg/p2p" + "github.com/evstack/ev-node/pkg/p2p/key" "github.com/evstack/ev-node/pkg/service" "github.com/evstack/ev-node/pkg/signer" ) @@ -35,7 +35,7 @@ func NewNode( sequencer coresequencer.Sequencer, da coreda.DA, signer signer.Signer, - p2pClient *p2p.Client, + nodeKey *key.NodeKey, genesis genesis.Genesis, database ds.Batching, metricsProvider MetricsProvider, @@ -43,7 +43,7 @@ func NewNode( nodeOptions NodeOptions, ) (Node, error) { if conf.Node.Light { - return newLightNode(conf, genesis, p2pClient, database, logger) + return newLightNode(conf, genesis, nodeKey, database, logger) } if err := nodeOptions.BlockOptions.Validate(); err != nil { @@ -52,7 +52,7 @@ func NewNode( return newFullNode( conf, - p2pClient, + nodeKey, signer, genesis, database, diff --git a/node/single_sequencer_integration_test.go b/node/single_sequencer_integration_test.go index 22b2fd4506..9b5564b7ea 100644 --- a/node/single_sequencer_integration_test.go +++ b/node/single_sequencer_integration_test.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "io" "net/http" "sync" "testing" @@ -68,6 +69,7 @@ func (s *FullNodeTestSuite) SetupTest() { // Start the node in a goroutine using Run instead of Start s.startNodeInBackground(s.node) + s.T().Cleanup(func() { shutdownAndWait(s.T(), []context.CancelFunc{s.cancel}, &s.runningWg, 10*time.Second) }) // Verify that the node is running and producing blocks err := waitForFirstBlock(s.node, Header) @@ -78,7 +80,7 @@ func (s *FullNodeTestSuite) SetupTest() { require.NoError(err, "Failed to get DA inclusion") // Verify block components are properly initialized - require.NotNil(s.node.blockComponents, "Block components should be initialized") + require.NotNil(castState(s.T(), s.node).bc, "Block components should be initialized") } // TearDownTest cancels the test context and waits for the node to stop, ensuring proper cleanup after each test. @@ -125,13 +127,13 @@ func (s *FullNodeTestSuite) TestBlockProduction() { testTx := []byte("test transaction") // Inject transaction through the node's block components (same as integration tests) - if s.node.blockComponents != nil && s.node.blockComponents.Executor != nil { + if state := castState(s.T(), s.node); state.bc != nil && state.bc.Executor != nil { // Access the core executor from the block executor - coreExec := s.node.blockComponents.Executor.GetCoreExecutor() + coreExec := state.bc.Executor.GetCoreExecutor() if dummyExec, ok := coreExec.(interface{ InjectTx([]byte) }); ok { dummyExec.InjectTx(testTx) // Notify the executor about new transactions - s.node.blockComponents.Executor.NotifyNewTransactions() + state.bc.Executor.NotifyNewTransactions() } else { s.T().Fatalf("Could not cast core executor to DummyExecutor") } @@ -183,12 +185,12 @@ func (s *FullNodeTestSuite) TestBlockProduction() { // It injects a transaction, waits for several blocks to be produced and DA-included, and asserts that all blocks are DA included. func (s *FullNodeTestSuite) TestSubmitBlocksToDA() { // Inject transaction through the node's block components - if s.node.blockComponents != nil && s.node.blockComponents.Executor != nil { - coreExec := s.node.blockComponents.Executor.GetCoreExecutor() + if state := castState(s.T(), s.node); state.bc != nil && state.bc.Executor != nil { + coreExec := state.bc.Executor.GetCoreExecutor() if dummyExec, ok := coreExec.(interface{ InjectTx([]byte) }); ok { dummyExec.InjectTx([]byte("test transaction")) // Notify the executor about new transactions - s.node.blockComponents.Executor.NotifyNewTransactions() + state.bc.Executor.NotifyNewTransactions() } else { s.T().Fatalf("Could not cast core executor to DummyExecutor") } @@ -207,7 +209,7 @@ func (s *FullNodeTestSuite) TestSubmitBlocksToDA() { header, data, err := s.node.Store.GetBlockData(s.ctx, height) require.NoError(s.T(), err) - ok, err := s.node.blockComponents.Submitter.IsHeightDAIncluded(height, header, data) + ok, err := castState(s.T(), s.node).bc.Submitter.IsHeightDAIncluded(height, header, data) require.NoError(s.T(), err) require.True(s.T(), ok, "Block at height %d is not DA included", height) } @@ -219,7 +221,7 @@ func (s *FullNodeTestSuite) TestGenesisInitialization() { require := require.New(s.T()) // Verify genesis state - state := s.node.blockComponents.GetLastState() + state := castState(s.T(), s.node).bc.GetLastState() require.Equal(s.node.genesis.InitialHeight, state.InitialHeight) require.Equal(s.node.genesis.ChainID, state.ChainID) } @@ -231,8 +233,8 @@ func TestStateRecovery(t *testing.T) { // Set up one sequencer config := getTestConfig(t, 1) - executor, sequencer, dac, p2pClient, ds, _, stopDAHeightTicker := createTestComponents(t, config) - node, cleanup := createNodeWithCustomComponents(t, config, executor, sequencer, dac, p2pClient, ds, stopDAHeightTicker) + executor, sequencer, dac, ds, nodeKey, stopDAHeightTicker := createTestComponents(t, config) + node, cleanup := createNodeWithCustomComponents(t, config, executor, sequencer, dac, nodeKey, ds, stopDAHeightTicker) defer cleanup() var runningWg sync.WaitGroup @@ -242,6 +244,7 @@ func TestStateRecovery(t *testing.T) { // Start the sequencer first startNodeInBackground(t, []*FullNode{node}, []context.Context{ctx}, &runningWg, 0, nil) + t.Cleanup(func() { shutdownAndWait(t, []context.CancelFunc{cancel}, &runningWg, 10*time.Second) }) blocksToWaitFor := uint64(20) // Wait for the sequencer to produce at first block @@ -256,8 +259,8 @@ func TestStateRecovery(t *testing.T) { shutdownAndWait(t, []context.CancelFunc{cancel}, &runningWg, 60*time.Second) // Create a new node instance using the same components - executor, sequencer, dac, p2pClient, _, _, stopDAHeightTicker = createTestComponents(t, config) - node, cleanup = createNodeWithCustomComponents(t, config, executor, sequencer, dac, p2pClient, ds, stopDAHeightTicker) + executor, sequencer, dac, _, nodeKey, stopDAHeightTicker = createTestComponents(t, config) + node, cleanup = createNodeWithCustomComponents(t, config, executor, sequencer, dac, nodeKey, ds, stopDAHeightTicker) defer cleanup() // Verify state persistence @@ -285,6 +288,7 @@ func TestMaxPendingHeadersAndData(t *testing.T) { var runningWg sync.WaitGroup startNodeInBackground(t, []*FullNode{node}, []context.Context{ctx}, &runningWg, 0, nil) + t.Cleanup(func() { shutdownAndWait(t, []context.CancelFunc{cancel}, &runningWg, 10*time.Second) }) // Wait blocks to be produced up to max pending numExtraBlocks := uint64(5) @@ -294,9 +298,6 @@ func TestMaxPendingHeadersAndData(t *testing.T) { height, err := getNodeHeight(node, Store) require.NoError(err) require.LessOrEqual(height, config.Node.MaxPendingHeadersAndData) - - // Stop the node and wait for shutdown - shutdownAndWait(t, []context.CancelFunc{cancel}, &runningWg, 10*time.Second) } // TestBatchQueueThrottlingWithDAFailure tests that when DA layer fails and MaxPendingHeadersAndData @@ -313,7 +314,7 @@ func TestBatchQueueThrottlingWithDAFailure(t *testing.T) { config.DA.BlockTime = evconfig.DurationWrapper{Duration: 100 * time.Millisecond} // Longer DA time to ensure blocks are produced first // Create test components - executor, sequencer, dummyDA, p2pClient, ds, _, stopDAHeightTicker := createTestComponents(t, config) + executor, sequencer, dummyDA, ds, nodeKey, stopDAHeightTicker := createTestComponents(t, config) defer stopDAHeightTicker() // Cast executor to DummyExecutor so we can inject transactions @@ -325,14 +326,17 @@ func TestBatchQueueThrottlingWithDAFailure(t *testing.T) { require.True(ok, "Expected DummyDA implementation") // Create node with components - node, cleanup := createNodeWithCustomComponents(t, config, executor, sequencer, dummyDAImpl, p2pClient, ds, func() {}) + node, cleanup := createNodeWithCustomComponents(t, config, executor, sequencer, dummyDAImpl, nodeKey, ds, func() {}) defer cleanup() ctx, cancel := context.WithCancel(t.Context()) defer cancel() var runningWg sync.WaitGroup - startNodeInBackground(t, []*FullNode{node}, []context.Context{ctx}, &runningWg, 0, nil) + errChan := make(chan error, 1) + startNodeInBackground(t, []*FullNode{node}, []context.Context{ctx}, &runningWg, 0, errChan) + t.Cleanup(func() { shutdownAndWait(t, []context.CancelFunc{cancel}, &runningWg, 10*time.Second) }) + require.Len(errChan, 0, "Expected no errors when starting node") // Wait for the node to start producing blocks waitForBlockN(t, 1, node, config.Node.BlockTime.Duration) @@ -385,7 +389,7 @@ func TestBatchQueueThrottlingWithDAFailure(t *testing.T) { finalHeight, err := getNodeHeight(node, Store) require.NoError(err) t.Logf("Final height: %d", finalHeight) - + cancel() // stop the node // The height should not have increased much due to MaxPendingHeadersAndData limit // Allow at most 3 additional blocks due to timing and pending blocks in queue heightIncrease := finalHeight - heightAfterDAFailure @@ -419,6 +423,7 @@ func waitForBlockN(t *testing.T, n uint64, node *FullNode, blockInterval time.Du return got >= n }, timeout[0], blockInterval/2) } + func TestReadinessEndpointWhenBlockProductionStops(t *testing.T) { require := require.New(t) @@ -431,18 +436,24 @@ func TestReadinessEndpointWhenBlockProductionStops(t *testing.T) { node, cleanup := createNodeWithCleanup(t, config) defer cleanup() - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() var runningWg sync.WaitGroup - startNodeInBackground(t, []*FullNode{node}, []context.Context{ctx}, &runningWg, 0, nil) + errChan := make(chan error, 1) + startNodeInBackground(t, []*FullNode{node}, []context.Context{ctx}, &runningWg, 0, errChan) + t.Cleanup(func() { shutdownAndWait(t, []context.CancelFunc{cancel}, &runningWg, 10*time.Second) }) + require.Len(errChan, 0, "Expected no errors when starting node") - waitForBlockN(t, 1, node, config.Node.BlockTime.Duration) + waitForBlockN(t, 2, node, config.Node.BlockTime.Duration) + require.Len(errChan, 0, "Expected no errors when starting node") resp, err := http.Get("http://" + config.RPC.Address + "/health/ready") require.NoError(err) - require.Equal(http.StatusOK, resp.StatusCode, "Readiness should be READY while producing blocks") + body, err := io.ReadAll(resp.Body) + require.NoError(err) resp.Body.Close() + require.Equal(http.StatusOK, resp.StatusCode, "Readiness should be READY while producing blocks: %s", body) time.Sleep(time.Duration(config.Node.MaxPendingHeadersAndData+2) * config.Node.BlockTime.Duration) @@ -458,6 +469,4 @@ func TestReadinessEndpointWhenBlockProductionStops(t *testing.T) { defer resp.Body.Close() return resp.StatusCode == http.StatusServiceUnavailable }, 10*time.Second, 100*time.Millisecond, "Readiness should be UNREADY after aggregator stops producing blocks (5x block time)") - - shutdownAndWait(t, []context.CancelFunc{cancel}, &runningWg, 10*time.Second) } diff --git a/node/single_sequencer_test.go b/node/single_sequencer_test.go index b0bb077f38..5675435266 100644 --- a/node/single_sequencer_test.go +++ b/node/single_sequencer_test.go @@ -50,4 +50,5 @@ func TestStartup(t *testing.T) { // Run the cleanup function from setupTestNodeWithCleanup cleanup() + time.Sleep(time.Second) // shutdown takes some time to persist caches } diff --git a/pkg/cmd/run_node.go b/pkg/cmd/run_node.go index fe42707f85..8e0225115e 100644 --- a/pkg/cmd/run_node.go +++ b/pkg/cmd/run_node.go @@ -22,7 +22,7 @@ import ( "github.com/evstack/ev-node/node" rollconf "github.com/evstack/ev-node/pkg/config" genesispkg "github.com/evstack/ev-node/pkg/genesis" - "github.com/evstack/ev-node/pkg/p2p" + "github.com/evstack/ev-node/pkg/p2p/key" "github.com/evstack/ev-node/pkg/signer" "github.com/evstack/ev-node/pkg/signer/file" ) @@ -83,7 +83,7 @@ func StartNode( executor coreexecutor.Executor, sequencer coresequencer.Sequencer, da coreda.DA, - p2pClient *p2p.Client, + nodeKey *key.NodeKey, datastore datastore.Batching, nodeConfig rollconf.Config, genesis genesispkg.Genesis, @@ -139,7 +139,7 @@ func StartNode( sequencer, da, signer, - p2pClient, + nodeKey, genesis, datastore, metrics, diff --git a/pkg/cmd/run_node_test.go b/pkg/cmd/run_node_test.go index 16430ee450..74155b0fc9 100644 --- a/pkg/cmd/run_node_test.go +++ b/pkg/cmd/run_node_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/evstack/ev-node/pkg/p2p/key" "github.com/ipfs/go-datastore" "github.com/rs/zerolog" "github.com/spf13/cobra" @@ -19,14 +20,13 @@ import ( "github.com/evstack/ev-node/node" rollconf "github.com/evstack/ev-node/pkg/config" genesis "github.com/evstack/ev-node/pkg/genesis" - "github.com/evstack/ev-node/pkg/p2p" "github.com/evstack/ev-node/pkg/signer" filesigner "github.com/evstack/ev-node/pkg/signer/file" ) const MockDANamespace = "test" -func createTestComponents(_ context.Context, t *testing.T) (coreexecutor.Executor, coresequencer.Sequencer, coreda.DA, signer.Signer, *p2p.Client, datastore.Batching, func()) { +func createTestComponents(_ context.Context, t *testing.T) (coreexecutor.Executor, coresequencer.Sequencer, coreda.DA, signer.Signer, *key.NodeKey, datastore.Batching, func()) { executor := coreexecutor.NewDummyExecutor() sequencer := coresequencer.NewDummySequencer() dummyDA := coreda.NewDummyDA(100_000, 10*time.Second) @@ -40,10 +40,9 @@ func createTestComponents(_ context.Context, t *testing.T) (coreexecutor.Executo panic(err) } // Create a dummy P2P client and datastore for testing - p2pClient := &p2p.Client{} ds := datastore.NewMapDatastore() - return executor, sequencer, dummyDA, keyProvider, p2pClient, ds, stopDAHeightTicker + return executor, sequencer, dummyDA, keyProvider, nil, ds, stopDAHeightTicker } func TestParseFlags(t *testing.T) { @@ -78,13 +77,13 @@ func TestParseFlags(t *testing.T) { args := append([]string{"start"}, flags...) - executor, sequencer, dac, keyProvider, p2pClient, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) + executor, sequencer, dac, keyProvider, nodeKey, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) defer stopDAHeightTicker() nodeConfig := rollconf.DefaultConfig() nodeConfig.RootDir = t.TempDir() - newRunNodeCmd := newRunNodeCmd(t.Context(), executor, sequencer, dac, keyProvider, p2pClient, ds, nodeConfig) + newRunNodeCmd := newRunNodeCmd(t.Context(), executor, sequencer, dac, keyProvider, nodeKey, ds, nodeConfig) _ = newRunNodeCmd.Flags().Set(rollconf.FlagRootDir, "custom/root/dir") if err := newRunNodeCmd.ParseFlags(args); err != nil { t.Errorf("Error: %v", err) @@ -153,13 +152,13 @@ func TestAggregatorFlagInvariants(t *testing.T) { for i, flags := range flagVariants { args := append([]string{"start"}, flags...) - executor, sequencer, dac, keyProvider, p2pClient, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) + executor, sequencer, dac, keyProvider, nodeKey, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) defer stopDAHeightTicker() nodeConfig := rollconf.DefaultConfig() nodeConfig.RootDir = t.TempDir() - newRunNodeCmd := newRunNodeCmd(t.Context(), executor, sequencer, dac, keyProvider, p2pClient, ds, nodeConfig) + newRunNodeCmd := newRunNodeCmd(t.Context(), executor, sequencer, dac, keyProvider, nodeKey, ds, nodeConfig) _ = newRunNodeCmd.Flags().Set(rollconf.FlagRootDir, "custom/root/dir") if err := newRunNodeCmd.ParseFlags(args); err != nil { @@ -189,13 +188,13 @@ func TestDefaultAggregatorValue(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - executor, sequencer, dac, keyProvider, p2pClient, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) + executor, sequencer, dac, keyProvider, nodeKey, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) defer stopDAHeightTicker() nodeConfig := rollconf.DefaultConfig() nodeConfig.RootDir = t.TempDir() - newRunNodeCmd := newRunNodeCmd(t.Context(), executor, sequencer, dac, keyProvider, p2pClient, ds, nodeConfig) + newRunNodeCmd := newRunNodeCmd(t.Context(), executor, sequencer, dac, keyProvider, nodeKey, ds, nodeConfig) _ = newRunNodeCmd.Flags().Set(rollconf.FlagRootDir, "custom/root/dir") // Create a new command without specifying any flags @@ -271,10 +270,10 @@ func TestCentralizedAddresses(t *testing.T) { "--rollkit.da.address=http://central-da:26657", } - executor, sequencer, dac, keyProvider, p2pClient, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) + executor, sequencer, dac, keyProvider, nodeKey, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) defer stopDAHeightTicker() - cmd := newRunNodeCmd(t.Context(), executor, sequencer, dac, keyProvider, p2pClient, ds, nodeConfig) + cmd := newRunNodeCmd(t.Context(), executor, sequencer, dac, keyProvider, nodeKey, ds, nodeConfig) _ = cmd.Flags().Set(rollconf.FlagRootDir, "custom/root/dir") if err := cmd.ParseFlags(args); err != nil { t.Fatalf("ParseFlags error: %v", err) @@ -537,7 +536,7 @@ func TestStartNodeSignerPathResolution(t *testing.T) { func TestStartNodeErrors(t *testing.T) { baseCtx := context.Background() - executor, sequencer, dac, _, p2pClient, ds, stopDAHeightTicker := createTestComponents(baseCtx, t) + executor, sequencer, dac, _, nodeKey, ds, stopDAHeightTicker := createTestComponents(baseCtx, t) defer stopDAHeightTicker() tmpDir := t.TempDir() @@ -643,7 +642,7 @@ func TestStartNodeErrors(t *testing.T) { dummySigner, _ := filesigner.CreateFileSystemSigner(dummySignerPath, []byte("password")) - cmd := newRunNodeCmd(baseCtx, executor, sequencer, dac, dummySigner, p2pClient, ds, nodeConfig) + cmd := newRunNodeCmd(baseCtx, executor, sequencer, dac, dummySigner, nodeKey, ds, nodeConfig) cmd.SetContext(baseCtx) @@ -654,7 +653,7 @@ func TestStartNodeErrors(t *testing.T) { runFunc := func() { currentTestLogger := zerolog.Nop() - err := StartNode(currentTestLogger, cmd, executor, sequencer, dac, p2pClient, ds, nodeConfig, testGenesis, node.NodeOptions{}) + err := StartNode(currentTestLogger, cmd, executor, sequencer, dac, nodeKey, ds, nodeConfig, testGenesis, node.NodeOptions{}) if tc.expectedError != "" { assert.ErrorContains(t, err, tc.expectedError) } else { @@ -671,7 +670,7 @@ func TestStartNodeErrors(t *testing.T) { } else { assert.NotPanics(t, runFunc) checkLogger := zerolog.Nop() - err := StartNode(checkLogger, cmd, executor, sequencer, dac, p2pClient, ds, nodeConfig, testGenesis, node.NodeOptions{}) + err := StartNode(checkLogger, cmd, executor, sequencer, dac, nodeKey, ds, nodeConfig, testGenesis, node.NodeOptions{}) if tc.expectedError != "" { assert.ErrorContains(t, err, tc.expectedError) } @@ -687,7 +686,7 @@ func newRunNodeCmd( sequencer coresequencer.Sequencer, dac coreda.DA, remoteSigner signer.Signer, - p2pClient *p2p.Client, + nodeKey *key.NodeKey, datastore datastore.Batching, nodeConfig rollconf.Config, ) *cobra.Command { @@ -709,7 +708,7 @@ func newRunNodeCmd( Aliases: []string{"node", "run"}, Short: "Run the rollkit node", RunE: func(cmd *cobra.Command, args []string) error { - return StartNode(zerolog.Nop(), cmd, executor, sequencer, dac, p2pClient, datastore, nodeConfig, testGenesis, node.NodeOptions{}) + return StartNode(zerolog.Nop(), cmd, executor, sequencer, dac, nodeKey, datastore, nodeConfig, testGenesis, node.NodeOptions{}) }, } diff --git a/pkg/config/config.go b/pkg/config/config.go index d6b1f15539..dbb137be16 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -120,6 +120,27 @@ const ( FlagRPCAddress = FlagPrefixEvnode + "rpc.address" // FlagRPCEnableDAVisualization is a flag for enabling DA visualization endpoints FlagRPCEnableDAVisualization = FlagPrefixEvnode + "rpc.enable_da_visualization" + + // Raft configuration flags + + // FlagRaftEnable is a flag for enabling Raft consensus + FlagRaftEnable = FlagPrefixEvnode + "raft.enable" + // FlagRaftNodeID is a flag for specifying the Raft node ID + FlagRaftNodeID = FlagPrefixEvnode + "raft.node_id" + // FlagRaftAddr is a flag for specifying the Raft communication address + FlagRaftAddr = FlagPrefixEvnode + "raft.raft_addr" + // FlagRaftDir is a flag for specifying the Raft data directory + FlagRaftDir = FlagPrefixEvnode + "raft.raft_dir" + // FlagRaftBootstrap is a flag for bootstrapping a new Raft cluster + FlagRaftBootstrap = FlagPrefixEvnode + "raft.bootstrap" + // FlagRaftPeers is a flag for specifying Raft peer addresses + FlagRaftPeers = FlagPrefixEvnode + "raft.peers" + // FlagRaftSnapCount is a flag for specifying snapshot frequency + FlagRaftSnapCount = FlagPrefixEvnode + "raft.snap_count" + // FlagRaftSendTimeout max time to wait for a message to be sent to a peer + FlagRaftSendTimeout = FlagPrefixEvnode + "raft.send_timeout" + // FlagRaftHeartbeatTimeout is a flag for specifying heartbeat timeout + FlagRaftHeartbeatTimeout = FlagPrefixEvnode + "raft.heartbeat_timeout" ) // Config stores Rollkit configuration. @@ -149,6 +170,9 @@ type Config struct { // Remote signer configuration Signer SignerConfig `mapstructure:"signer" yaml:"signer"` + + // Raft consensus configuration + Raft RaftConfig `mapstructure:"raft" yaml:"raft"` } // DAConfig contains all Data Availability configuration parameters @@ -222,6 +246,45 @@ type RPCConfig struct { EnableDAVisualization bool `mapstructure:"enable_da_visualization" yaml:"enable_da_visualization" comment:"Enable DA visualization endpoints for monitoring blob submissions. Default: false"` } +// RaftConfig contains all Raft consensus configuration parameters +type RaftConfig struct { + Enable bool `mapstructure:"enable" yaml:"enable" comment:"Enable Raft consensus for leader election and state replication"` + NodeID string `mapstructure:"node_id" yaml:"node_id" comment:"Unique identifier for this node in the Raft cluster"` + RaftAddr string `mapstructure:"raft_addr" yaml:"raft_addr" comment:"Address for Raft communication (host:port)"` + RaftDir string `mapstructure:"raft_dir" yaml:"raft_dir" comment:"Directory for Raft logs and snapshots"` + Bootstrap bool `mapstructure:"bootstrap" yaml:"bootstrap" comment:"Bootstrap a new Raft cluster (only for the first node)"` + Peers string `mapstructure:"peers" yaml:"peers" comment:"Comma-separated list of peer Raft addresses (nodeID@host:port)"` + SnapCount uint64 `mapstructure:"snap_count" yaml:"snap_count" comment:"Number of log entries between snapshots"` + SendTimeout time.Duration `mapstructure:"send_timeout" yaml:"send_timeout" comment:"Max duration to wait for a message to be sent to a peer"` + HeartbeatTimeout time.Duration `mapstructure:"heartbeat_timeout" yaml:"heartbeat_timeout" comment:"Time between leader heartbeats to followers"` +} + +func (c RaftConfig) Validate() error { + if !c.Enable { + return nil + } + var multiErr error + if c.NodeID == "" { + multiErr = fmt.Errorf("node ID is required") + } + if c.RaftAddr == "" { + multiErr = errors.Join(multiErr, fmt.Errorf("raft address is required")) + } + if c.RaftDir == "" { + multiErr = errors.Join(multiErr, fmt.Errorf("raft directory is required")) + } + + if c.SendTimeout <= 0 { + multiErr = errors.Join(multiErr, fmt.Errorf("send timeout must be positive")) + } + + if c.HeartbeatTimeout <= 0 { + multiErr = errors.Join(multiErr, fmt.Errorf("heartbeat timeout must be positive")) + } + + return multiErr +} + // Validate validates the config and ensures that the root directory exists. // It creates the directory if it does not exist. func (c *Config) Validate() error { @@ -250,7 +313,9 @@ func (c *Config) Validate() error { return fmt.Errorf("LazyBlockInterval (%v) must be greater than BlockTime (%v) in lazy mode", c.Node.LazyBlockInterval.Duration, c.Node.BlockTime.Duration) } - + if err := c.Raft.Validate(); err != nil { + return err + } return nil } @@ -343,6 +408,17 @@ func AddFlags(cmd *cobra.Command) { cmd.Flags().String(FlagSignerType, def.Signer.SignerType, "type of signer to use (file, grpc)") cmd.Flags().String(FlagSignerPath, def.Signer.SignerPath, "path to the signer file or address") cmd.Flags().String(FlagSignerPassphraseFile, "", "path to file containing the signer passphrase (required for file signer and if aggregator is enabled)") + + // Raft configuration flags + cmd.Flags().Bool(FlagRaftEnable, def.Raft.Enable, "enable Raft consensus for leader election and state replication") + cmd.Flags().String(FlagRaftNodeID, def.Raft.NodeID, "unique identifier for this node in the Raft cluster") + cmd.Flags().String(FlagRaftAddr, def.Raft.RaftAddr, "address for Raft communication (host:port)") + cmd.Flags().String(FlagRaftDir, def.Raft.RaftDir, "directory for Raft logs and snapshots") + cmd.Flags().Bool(FlagRaftBootstrap, def.Raft.Bootstrap, "bootstrap a new Raft cluster (only for the first node)") + cmd.Flags().String(FlagRaftPeers, def.Raft.Peers, "comma-separated list of peer Raft addresses (nodeID@host:port)") + cmd.Flags().Uint64(FlagRaftSnapCount, def.Raft.SnapCount, "number of log entries between snapshots") + cmd.Flags().Duration(FlagRaftSendTimeout, def.Raft.SendTimeout, "max duration to wait for a message to be sent to a peer") + cmd.Flags().Duration(FlagRaftHeartbeatTimeout, def.Raft.HeartbeatTimeout, "time between leader heartbeats to followers") } // Load loads the node configuration in the following order of precedence: diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 7834e42aab..967b412a16 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -99,7 +99,7 @@ func TestAddFlags(t *testing.T) { assertFlagValue(t, flags, FlagRPCAddress, DefaultConfig().RPC.Address) // Count the number of flags we're explicitly checking - expectedFlagCount := 37 // Update this number if you add more flag checks above + expectedFlagCount := 46 // Update this number if you add more flag checks above // Get the actual number of flags (both regular and persistent) actualFlagCount := 0 @@ -348,6 +348,78 @@ func TestDAConfig_GetDataNamespace(t *testing.T) { } } +func TestRaftConfig_Validate(t *testing.T) { + // helper to build a valid base config per test (temp dir varies per subtest) + newValid := func() RaftConfig { + return RaftConfig{ + Enable: true, + NodeID: "node-1", + RaftAddr: "127.0.0.1:9000", + RaftDir: t.TempDir(), + Bootstrap: false, + Peers: "", + SnapCount: 1, + SendTimeout: 1 * time.Second, + HeartbeatTimeout: 1 * time.Second, + } + } + + specs := map[string]struct { + mutate func(c *RaftConfig) + expErr string + }{ + "disabled": { + mutate: func(c *RaftConfig) { c.Enable = false }, + }, + "valid": { + mutate: func(c *RaftConfig) {}, + }, + "empty node id": { + mutate: func(c *RaftConfig) { c.NodeID = "" }, + expErr: "node ID is required", + }, + "empty raft addr": { + mutate: func(c *RaftConfig) { c.RaftAddr = "" }, + expErr: "raft address is required", + }, + "empty raft dir": { + mutate: func(c *RaftConfig) { c.RaftDir = "" }, + expErr: "raft directory is required", + }, + "non-positive send timeout": { + mutate: func(c *RaftConfig) { c.SendTimeout = 0 }, + expErr: "send timeout must be positive", + }, + "non-positive heartbeat timeout": { + mutate: func(c *RaftConfig) { c.HeartbeatTimeout = 0 }, + expErr: "heartbeat timeout must be positive", + }, + "multiple invalid returns last": { + mutate: func(c *RaftConfig) { + c.NodeID = "" + c.RaftAddr = "" + c.HeartbeatTimeout = 0 + }, + expErr: "node ID is required\nraft address is required\nheartbeat timeout must be positive", + }, + } + + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + cfg := newValid() + spec.mutate(&cfg) + err := cfg.Validate() + + if spec.expErr != "" { + require.Error(t, err) + assert.Equal(t, spec.expErr, err.Error()) + return + } + require.NoError(t, err) + }) + } +} + func assertFlagValue(t *testing.T, flags *pflag.FlagSet, name string, expectedValue any) { flag := flags.Lookup(name) assert.NotNil(t, flag, "Flag %s should exist", name) diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index 6a6f813a3c..977ddeb032 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -90,6 +90,11 @@ func DefaultConfig() Config { Address: "127.0.0.1:7331", EnableDAVisualization: false, }, + Raft: RaftConfig{ + SendTimeout: 200 * time.Millisecond, + HeartbeatTimeout: 350 * time.Millisecond, + RaftDir: filepath.Join(DefaultRootDir, "raft"), + }, } } diff --git a/pkg/raft/election.go b/pkg/raft/election.go new file mode 100644 index 0000000000..d0d54acda5 --- /dev/null +++ b/pkg/raft/election.go @@ -0,0 +1,157 @@ +package raft + +import ( + "context" + "errors" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/hashicorp/raft" + "github.com/rs/zerolog" +) + +var ErrLeadershipLost = fmt.Errorf("leader lock lost") + +// Runnable represents a component that can be started and performs specific operations while running. +// Run runs the main logic of the component using the provided context and returns an error if it fails. +// IsSynced checks whether the component is synced with the given RaftBlockState. +type Runnable interface { + Run(ctx context.Context) error + IsSynced(RaftBlockState) bool +} + +type sourceNode interface { + Config() Config + leaderCh() <-chan bool + leaderID() string + NodeID() string + GetState() RaftBlockState + leadershipTransfer() error + waitForMsgsLanded(duration time.Duration) error +} + +type DynamicLeaderElection struct { + logger zerolog.Logger + leaderFactory, followerFactory func() (Runnable, error) + node sourceNode + running atomic.Bool +} + +// NewDynamicLeaderElection constructor +func NewDynamicLeaderElection( + logger zerolog.Logger, + leaderFactory func() (Runnable, error), + followerFactory func() (Runnable, error), + node *Node, +) *DynamicLeaderElection { + return &DynamicLeaderElection{logger: logger, leaderFactory: leaderFactory, followerFactory: followerFactory, node: node} +} + +// Run starts the leader election process and manages the lifecycle of leader or follower roles based on Raft events. +// This is a blocking call. +func (d *DynamicLeaderElection) Run(ctx context.Context) error { + var isStarted, isCurrentlyLeader bool + var workerCancel context.CancelFunc = func() {} // noop + var wg sync.WaitGroup + errCh := make(chan error, 1) + + defer func() { + workerCancel() + wg.Wait() + close(errCh) + }() + + startWorker := func(name string, workerFunc func(ctx context.Context) error) { + workerCancel() + workerCtx, cancel := context.WithCancel(ctx) + workerCancel = cancel + wg.Add(1) + + // call workerFunc in a separate goroutine + go func(childCtx context.Context) { + defer wg.Done() + if err := workerFunc(childCtx); err != nil && !errors.Is(err, context.Canceled) { + select { + case errCh <- fmt.Errorf(name+" worker exited unexpectedly: %s", err): + default: // do not block + } + } + }(workerCtx) + } + ticker := time.NewTicker(300 * time.Millisecond) + defer ticker.Stop() + d.running.Store(true) + defer d.running.Store(false) + var runnable Runnable + for { + select { + case becameLeader := <-d.node.leaderCh(): + d.logger.Info().Msg("Raft leader changed notification") + if becameLeader && !isCurrentlyLeader { // new leader + if isStarted { + d.logger.Info().Msg("became leader, stopping follower operations") + // wait for in flight raft msgs to land, this is critical to avoid double sign on old state + raftSynced := d.node.waitForMsgsLanded(d.node.Config().SendTimeout) == nil + if !raftSynced || !runnable.IsSynced(d.node.GetState()) { + d.logger.Info().Msg("became leader, but not synced. Pass on leadership") + if err := d.node.leadershipTransfer(); err != nil && !errors.Is(err, raft.ErrNotLeader) { + // the leadership transfer can fail due to no suitable leader. Better stop than double sign on old state + return err + } + continue + } + d.logger.Info().Msg("became leader, stopping follower operations") + workerCancel() + wg.Wait() + } + d.logger.Info().Msg("starting leader operations") + var err error + if runnable, err = d.leaderFactory(); err != nil { + return err + } + isStarted = true + isCurrentlyLeader = true + startWorker("leader", runnable.Run) + } else if !becameLeader && isCurrentlyLeader { // lost leadership + workerCancel() + d.logger.Info().Msg("lost leadership") + return ErrLeadershipLost + } else if !becameLeader && !isCurrentlyLeader && !isStarted { // start as a follower + d.logger.Info().Msg("starting follower operations") + isStarted = true + var err error + if runnable, err = d.followerFactory(); err != nil { + return err + } + startWorker("follower", runnable.Run) + } + case <-ticker.C: // LeaderCh fires only when leader changes not on initial election + if isStarted { + ticker.Stop() + ticker.C = nil + continue + } + if leaderID := d.node.leaderID(); leaderID != "" && leaderID != d.node.NodeID() { + ticker.Stop() + ticker.C = nil + d.logger.Info().Msg("starting follower operations") + isStarted = true + var err error + if runnable, err = d.followerFactory(); err != nil { + return err + } + startWorker("follower", runnable.Run) + } + case err := <-errCh: + return err + case <-ctx.Done(): + return ctx.Err() + } + } +} + +func (d *DynamicLeaderElection) IsRunning() bool { + return d.running.Load() +} diff --git a/pkg/raft/election_test.go b/pkg/raft/election_test.go new file mode 100644 index 0000000000..f2ee625340 --- /dev/null +++ b/pkg/raft/election_test.go @@ -0,0 +1,248 @@ +package raft + +import ( + "context" + "errors" + "strings" + "sync" + "testing" + "time" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDynamicLeaderElectionRun(t *testing.T) { + specs := map[string]struct { + setup func(t *testing.T) (*DynamicLeaderElection, context.Context, context.CancelFunc) + assertF func(t *testing.T, err error) + }{ + "start as follower via ticker": { + setup: func(t *testing.T) (*DynamicLeaderElection, context.Context, context.CancelFunc) { + m := newMocksourceNode(t) + leaderCh := make(chan bool, 1) + m.EXPECT().leaderCh().Return((<-chan bool)(leaderCh)) + m.EXPECT().leaderID().Return("other") + m.EXPECT().NodeID().Return("self") + + started := make(chan struct{}) + follower := &testRunnable{startedCh: started} + leader := &testRunnable{} + + logger := zerolog.Nop() + d := &DynamicLeaderElection{logger: logger, node: m, + leaderFactory: func() (Runnable, error) { return leader, nil }, + followerFactory: func() (Runnable, error) { return follower, nil }, + } + ctx, cancel := context.WithCancel(t.Context()) + + // Wait for follower to start via ticker path + var once sync.Once + go func() { + <-started + once.Do(cancel) + }() + return d, ctx, func() { once.Do(cancel) } + }, + assertF: func(t *testing.T, err error) { + require.Error(t, err) + assert.ErrorIs(t, err, context.Canceled) + }, + }, + "leader loss returns": { + setup: func(t *testing.T) (*DynamicLeaderElection, context.Context, context.CancelFunc) { + m := newMocksourceNode(t) + leaderCh := make(chan bool, 2) + m.EXPECT().leaderCh().Return((<-chan bool)(leaderCh)) + + leader := &testRunnable{runFn: func(ctx context.Context) error { + // block until canceled by election + <-ctx.Done() + return ctx.Err() + }} + logger := zerolog.Nop() + d := &DynamicLeaderElection{logger: logger, node: m, + leaderFactory: func() (Runnable, error) { return leader, nil }, + followerFactory: func() (Runnable, error) { return &testRunnable{}, nil }, + } + + ctx, cancel := context.WithCancel(t.Context()) + // Signal become leader then loss + go func() { + leaderCh <- true + time.Sleep(2 * time.Millisecond) + leaderCh <- false + }() + return d, ctx, cancel + }, + assertF: func(t *testing.T, err error) { + require.Error(t, err) + assert.ErrorIs(t, err, ErrLeadershipLost) + }, + }, + "worker error surfaces": { + setup: func(t *testing.T) (*DynamicLeaderElection, context.Context, context.CancelFunc) { + m := newMocksourceNode(t) + leaderCh := make(chan bool, 1) + m.EXPECT().leaderCh().Return((<-chan bool)(leaderCh)) + + wantErr := errors.New("boom") + leader := &testRunnable{runFn: func(ctx context.Context) error { return wantErr }} + logger := zerolog.Nop() + d := &DynamicLeaderElection{logger: logger, node: m, + leaderFactory: func() (Runnable, error) { return leader, nil }, + followerFactory: func() (Runnable, error) { return &testRunnable{}, nil }, + } + ctx, cancel := context.WithCancel(t.Context()) + go func() { leaderCh <- true }() + return d, ctx, cancel + }, + assertF: func(t *testing.T, err error) { + require.Error(t, err) + assert.True(t, strings.Contains(err.Error(), "leader worker exited unexpectedly: boom"), err.Error()) + }, + }, + "leadership transfer when not synced": { + setup: func(t *testing.T) (*DynamicLeaderElection, context.Context, context.CancelFunc) { + m := newMocksourceNode(t) + leaderCh := make(chan bool, 2) + m.EXPECT().leaderCh().Return((<-chan bool)(leaderCh)) + m.EXPECT().Config().Return(testCfg()) + m.EXPECT().waitForMsgsLanded(2 * time.Millisecond).Return(nil) + m.EXPECT().GetState().Return(RaftBlockState{Height: 1}) + m.EXPECT().leadershipTransfer().Return(nil) + + fStarted := make(chan struct{}) + follower := &testRunnable{startedCh: fStarted, isSyncedFn: func(RaftBlockState) bool { return false }} + leader := &testRunnable{runFn: func(ctx context.Context) error { + t.Fatal("leader should not be running") + return nil + }} + + logger := zerolog.Nop() + d := &DynamicLeaderElection{logger: logger, node: m, + leaderFactory: func() (Runnable, error) { return leader, nil }, + followerFactory: func() (Runnable, error) { return follower, nil }, + } + ctx, cancel := context.WithCancel(t.Context()) + // Start as follower via false, then signal leader true + go func() { + leaderCh <- false + <-fStarted // ensure follower started + leaderCh <- true + // allow time for SendTimeout sleep and transfer + time.Sleep(3 * time.Millisecond) + cancel() + }() + return d, ctx, cancel + }, + assertF: func(t *testing.T, err error) { + require.Error(t, err) + assert.ErrorIs(t, err, context.Canceled) + }, + }, + "follower starts then becomes leader": { + setup: func(t *testing.T) (*DynamicLeaderElection, context.Context, context.CancelFunc) { + m := newMocksourceNode(t) + leaderCh := make(chan bool, 2) + m.EXPECT().leaderCh().Return((<-chan bool)(leaderCh)) + // On leadership change to true, election will sleep SendTimeout, then check sync against state + m.EXPECT().Config().Return(testCfg()) + m.EXPECT().waitForMsgsLanded(2 * time.Millisecond).Return(nil) + m.EXPECT().GetState().Return(RaftBlockState{Height: 1}) + + fStarted := make(chan struct{}) + lStarted := make(chan struct{}) + follower := &testRunnable{startedCh: fStarted} + leader := &testRunnable{startedCh: lStarted} + + logger := zerolog.Nop() + d := &DynamicLeaderElection{logger: logger, node: m, + leaderFactory: func() (Runnable, error) { return leader, nil }, + followerFactory: func() (Runnable, error) { return follower, nil }, + } + ctx, cancel := context.WithCancel(t.Context()) + go func() { + // Start as follower first + leaderCh <- false + <-fStarted // ensure follower started + // Now become leader + leaderCh <- true + // Wait for transition to leader + select { + case <-lStarted: + case <-time.After(15 * time.Millisecond): + t.Log("timed out waiting for leader to start") + return + } + // give a tiny buffer then cancel + time.Sleep(2 * time.Millisecond) + cancel() + }() + return d, ctx, cancel + }, + assertF: func(t *testing.T, err error) { + require.Error(t, err) + assert.ErrorIs(t, err, context.Canceled) + }, + }, + } + + for name, spec := range specs { + name, spec := name, spec + t.Run(name, func(t *testing.T) { + d, runCtx, cancel := spec.setup(t) + defer cancel() + err := d.Run(runCtx) + spec.assertF(t, err) + }) + } +} + +// Helper to quickly build a Config with very short timeouts for tests +func testCfg() Config { + return Config{SendTimeout: 2 * time.Millisecond} +} + +// testRunnable is a small helper implementing Runnable for tests. +// Run behaviour is provided via runFn. IsSynced behaviour via isSyncedFn. +// When runFn is nil, it blocks until context is cancelled. +// When startedCh is non-nil, it will be closed once Run starts. +// When doneCh is non-nil, it will be closed right before Run returns. +// These channels are used only by tests to observe state. +type testRunnable struct { + runFn func(ctx context.Context) error + isSyncedFn func(RaftBlockState) bool + startedCh chan struct{} + doneCh chan struct{} +} + +func (t *testRunnable) Run(ctx context.Context) error { + if t.startedCh != nil { + select { + case <-t.startedCh: + default: + close(t.startedCh) + } + } + if t.runFn != nil { + err := t.runFn(ctx) + if t.doneCh != nil { + close(t.doneCh) + } + return err + } + <-ctx.Done() + if t.doneCh != nil { + close(t.doneCh) + } + return ctx.Err() +} + +func (t *testRunnable) IsSynced(s RaftBlockState) bool { + if t.isSyncedFn != nil { + return t.isSyncedFn(s) + } + return true +} diff --git a/pkg/raft/node.go b/pkg/raft/node.go new file mode 100644 index 0000000000..70719f3bd2 --- /dev/null +++ b/pkg/raft/node.go @@ -0,0 +1,380 @@ +package raft + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "os" + "path/filepath" + "strings" + "sync/atomic" + "time" + + "github.com/hashicorp/raft" + raftboltdb "github.com/hashicorp/raft-boltdb" + "github.com/rs/zerolog" +) + +// Node represents a raft consensus node +type Node struct { + raft *raft.Raft + fsm *FSM + config *Config + logger zerolog.Logger +} + +// Config holds raft node configuration +type Config struct { + NodeID string + RaftAddr string + RaftDir string + Bootstrap bool + Peers []string + SnapCount uint64 + SendTimeout time.Duration + HeartbeatTimeout time.Duration +} + +// FSM implements raft.FSM for block state +type FSM struct { + logger zerolog.Logger + state *atomic.Pointer[RaftBlockState] + applyCh chan<- RaftApplyMsg +} + +// NewNode creates a new raft node +func NewNode(cfg *Config, logger zerolog.Logger) (*Node, error) { + if err := os.MkdirAll(cfg.RaftDir, 0755); err != nil { + return nil, fmt.Errorf("create raft dir: %w", err) + } + + raftConfig := raft.DefaultConfig() + raftConfig.LocalID = raft.ServerID(cfg.NodeID) + raftConfig.LogLevel = "INFO" + raftConfig.HeartbeatTimeout = cfg.HeartbeatTimeout + raftConfig.LeaderLeaseTimeout = cfg.HeartbeatTimeout / 2 + + startPointer := new(atomic.Pointer[RaftBlockState]) + startPointer.Store(&RaftBlockState{}) + fsm := &FSM{ + logger: logger.With().Str("component", "raft-fsm").Logger(), + state: startPointer, + } + + logStore, err := raftboltdb.NewBoltStore(filepath.Join(cfg.RaftDir, "raft-log.db")) + if err != nil { + return nil, fmt.Errorf("create log store: %w", err) + } + + stableStore, err := raftboltdb.NewBoltStore(filepath.Join(cfg.RaftDir, "raft-stable.db")) + if err != nil { + return nil, fmt.Errorf("create stable store: %w", err) + } + + snapshotStore, err := raft.NewFileSnapshotStore(cfg.RaftDir, int(cfg.SnapCount), os.Stderr) + if err != nil { + return nil, fmt.Errorf("create snapshot store: %w", err) + } + + addr, err := net.ResolveTCPAddr("tcp", cfg.RaftAddr) + if err != nil { + return nil, fmt.Errorf("resolve raft addr: %w", err) + } + + transport, err := raft.NewTCPTransport(cfg.RaftAddr, addr, 3, 10*time.Second, os.Stderr) + if err != nil { + return nil, fmt.Errorf("create transport: %w", err) + } + + r, err := raft.NewRaft(raftConfig, fsm, logStore, stableStore, snapshotStore, transport) + if err != nil { + return nil, fmt.Errorf("create raft: %w", err) + } + + node := &Node{ + raft: r, + fsm: fsm, + config: cfg, + logger: logger.With().Str("component", "raft-node").Logger(), + } + + return node, nil +} + +func (n *Node) Start(_ context.Context) error { + if n == nil { + return nil + } + if !n.config.Bootstrap { + return fmt.Errorf("raft cluster requires bootstrap mode") + } + + if future := n.raft.GetConfiguration(); future.Error() == nil && len(future.Configuration().Servers) > 0 { + n.logger.Info().Msg("cluster already bootstrapped, skipping") + return nil + } + + n.logger.Info().Msg("Boostrap raft cluster") + thisNode := raft.Server{ID: raft.ServerID(n.config.NodeID), Address: raft.ServerAddress(n.config.RaftAddr)} + cfg := raft.Configuration{ + Servers: []raft.Server{ + thisNode, + }, + } + for _, peer := range n.config.Peers { + addr, err := splitPeerAddr(peer) + if err != nil { + return fmt.Errorf("peer %q : %w", peer, err) + } + if addr != thisNode { + cfg.Servers = append(cfg.Servers, addr) + } + } + + if svrs := deduplicateServers(cfg.Servers); len(svrs) != len(cfg.Servers) { + return fmt.Errorf("duplicate peers found in config: %v", cfg.Servers) + } + + if err := n.raft.BootstrapCluster(cfg).Error(); err != nil { + return fmt.Errorf("bootstrap cluster: %w", err) + } + n.logger.Info().Msg("bootstrapped raft cluster") + return nil +} + +func (n *Node) waitForMsgsLanded(timeout time.Duration) error { + if n == nil { + return nil + } + timeoutTicker := time.NewTicker(timeout) + defer timeoutTicker.Stop() + ticker := time.NewTicker(min(n.config.SendTimeout, timeout) / 2) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + if n.raft.AppliedIndex() >= n.raft.LastIndex() { + return nil + } + case <-timeoutTicker.C: + return errors.New("max wait time reached") + } + } +} + +func (n *Node) Stop() error { + if n == nil { + return nil + } + return n.raft.Shutdown().Error() +} + +// IsLeader returns true if this node is the raft leader +func (n *Node) IsLeader() bool { + if n == nil || n.raft == nil { + return false + } + return n.raft.State() == raft.Leader +} + +func (n *Node) NodeID() string { + return n.config.NodeID +} + +func (n *Node) leaderID() string { + _, id := n.raft.LeaderWithID() + return string(id) +} + +func (n *Node) leaderCh() <-chan bool { + return n.raft.LeaderCh() +} + +func (n *Node) leadershipTransfer() error { + return n.raft.LeadershipTransfer().Error() +} + +func (n *Node) Config() Config { + return *n.config +} + +// Broadcast proposes a block state to be replicated via raft +func (n *Node) Broadcast(_ context.Context, state *RaftBlockState) error { + if !n.IsLeader() { + return fmt.Errorf("not leader") + } + + data, err := json.Marshal(state) // todo:use protobuf + if err != nil { + return fmt.Errorf("marshal block state: %w", err) + } + + future := n.raft.Apply(data, n.config.SendTimeout) + if err := future.Error(); err != nil { + return fmt.Errorf("apply log: %w", err) + } + + return nil +} + +// GetState returns the current replicated state +func (n *Node) GetState() RaftBlockState { + return *n.fsm.state.Load() +} + +// AddPeer adds a peer to the raft cluster +func (n *Node) AddPeer(nodeID, addr string) error { + n.logger.Debug().Msgf("received join request for remote node %s at %s", nodeID, addr) + + configFuture := n.raft.GetConfiguration() + if err := configFuture.Error(); err != nil { + return err + } + + // remove first when node is already a member of the cluster + for _, srv := range configFuture.Configuration().Servers { + if srv.ID == raft.ServerID(nodeID) || srv.Address == raft.ServerAddress(addr) { + if srv.Address == raft.ServerAddress(addr) && srv.ID == raft.ServerID(nodeID) { + n.logger.Debug().Msgf("node %s at %s already member of cluster, ignoring join request", nodeID, addr) + return nil + } + future := n.raft.RemoveServer(srv.ID, 0, 0) + if err := future.Error(); err != nil { + return fmt.Errorf("removing existing node %s at %s: %w", nodeID, addr, err) + } + } + } + + f := n.raft.AddVoter(raft.ServerID(nodeID), raft.ServerAddress(addr), 0, 0) + if f.Error() != nil { + return f.Error() + } + n.logger.Debug().Msgf("node %s at %s joined successfully", nodeID, addr) + return nil +} + +// RemovePeer removes a peer from the raft cluster +func (n *Node) RemovePeer(nodeID string) error { + future := n.raft.RemoveServer(raft.ServerID(nodeID), 0, 0) + return future.Error() +} + +// Shutdown stops the raft node +func (n *Node) Shutdown() error { + return n.raft.Shutdown().Error() +} + +// SetApplyCallback sets a callback channel to receive notifications when a new block state is replicated. +// The channel must have sufficient buffer space since updates are published only once without blocking. +// If the channel is full, state updates will be skipped to prevent blocking the raft cluster. +func (n *Node) SetApplyCallback(ch chan<- RaftApplyMsg) { + n.fsm.applyCh = ch +} + +// Apply implements raft.FSM +func (f *FSM) Apply(log *raft.Log) interface{} { + var state RaftBlockState + if err := json.Unmarshal(log.Data, &state); err != nil { + f.logger.Error().Err(err).Msg("unmarshal block state") + return err + } + if err := f.state.Load().assertValid(state); err != nil { + return err + } + f.state.Store(&state) + f.logger.Debug().Uint64("height", state.Height).Msg("received block state") + + if f.applyCh != nil { + select { + case f.applyCh <- RaftApplyMsg{Index: log.Index, State: &state}: + default: + // on a slow consumer, the raft cluster should not be blocked. Followers can sync from DA or other peers, too. + f.logger.Warn().Msg("apply channel full, dropping message") + } + } + + return nil +} + +// Snapshot implements raft.FSM +func (f *FSM) Snapshot() (raft.FSMSnapshot, error) { + return &fsmSnapshot{state: *f.state.Load()}, nil +} + +// Restore implements raft.FSM +func (f *FSM) Restore(rc io.ReadCloser) error { + defer rc.Close() + + var state RaftBlockState + if err := json.NewDecoder(rc).Decode(&state); err != nil { + return fmt.Errorf("decode snapshot: %w", err) + } + + f.state.Store(&state) + f.logger.Info().Uint64("height", state.Height).Msg("restored from snapshot") + return nil +} + +type fsmSnapshot struct { + state RaftBlockState +} + +func (s *fsmSnapshot) Persist(sink raft.SnapshotSink) error { + err := func() error { + if err := json.NewEncoder(sink).Encode(s.state); err != nil { + return err + } + return sink.Close() + }() + + if err != nil { + _ = sink.Cancel() + return err + } + + return nil +} + +func (s *fsmSnapshot) Release() {} + +func splitPeerAddr(peer string) (raft.Server, error) { + parts := strings.Split(peer, "@") + if len(parts) != 2 { + return raft.Server{}, errors.New("expecting nodeID@address for peer") + } + + nodeID, address := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) + + if nodeID == "" { + return raft.Server{}, errors.New("nodeID cannot be empty") + } + if address == "" { + return raft.Server{}, errors.New("address cannot be empty") + } + // we can skip address validation as they come from a local configuration + + return raft.Server{ + ID: raft.ServerID(nodeID), + Address: raft.ServerAddress(address), + }, nil +} + +func deduplicateServers(servers []raft.Server) []raft.Server { + if len(servers) == 0 { + return []raft.Server{} + } + seen := make(map[raft.ServerID]struct{}) + unique := make([]raft.Server, 0, len(servers)) + for _, server := range servers { + key := server.ID + if _, exists := seen[key]; !exists { + seen[key] = struct{}{} + unique = append(unique, server) + } + } + return unique +} diff --git a/pkg/raft/node_mock.go b/pkg/raft/node_mock.go new file mode 100644 index 0000000000..2438f911ee --- /dev/null +++ b/pkg/raft/node_mock.go @@ -0,0 +1,355 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package raft + +import ( + "time" + + mock "github.com/stretchr/testify/mock" +) + +// newMocksourceNode creates a new instance of mocksourceNode. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMocksourceNode(t interface { + mock.TestingT + Cleanup(func()) +}) *mocksourceNode { + mock := &mocksourceNode{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// mocksourceNode is an autogenerated mock type for the sourceNode type +type mocksourceNode struct { + mock.Mock +} + +type mocksourceNode_Expecter struct { + mock *mock.Mock +} + +func (_m *mocksourceNode) EXPECT() *mocksourceNode_Expecter { + return &mocksourceNode_Expecter{mock: &_m.Mock} +} + +// Config provides a mock function for the type mocksourceNode +func (_mock *mocksourceNode) Config() Config { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for Config") + } + + var r0 Config + if returnFunc, ok := ret.Get(0).(func() Config); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(Config) + } + return r0 +} + +// mocksourceNode_Config_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Config' +type mocksourceNode_Config_Call struct { + *mock.Call +} + +// Config is a helper method to define mock.On call +func (_e *mocksourceNode_Expecter) Config() *mocksourceNode_Config_Call { + return &mocksourceNode_Config_Call{Call: _e.mock.On("Config")} +} + +func (_c *mocksourceNode_Config_Call) Run(run func()) *mocksourceNode_Config_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *mocksourceNode_Config_Call) Return(config Config) *mocksourceNode_Config_Call { + _c.Call.Return(config) + return _c +} + +func (_c *mocksourceNode_Config_Call) RunAndReturn(run func() Config) *mocksourceNode_Config_Call { + _c.Call.Return(run) + return _c +} + +// GetState provides a mock function for the type mocksourceNode +func (_mock *mocksourceNode) GetState() RaftBlockState { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for GetState") + } + + var r0 RaftBlockState + if returnFunc, ok := ret.Get(0).(func() RaftBlockState); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(RaftBlockState) + } + return r0 +} + +// mocksourceNode_GetState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetState' +type mocksourceNode_GetState_Call struct { + *mock.Call +} + +// GetState is a helper method to define mock.On call +func (_e *mocksourceNode_Expecter) GetState() *mocksourceNode_GetState_Call { + return &mocksourceNode_GetState_Call{Call: _e.mock.On("GetState")} +} + +func (_c *mocksourceNode_GetState_Call) Run(run func()) *mocksourceNode_GetState_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *mocksourceNode_GetState_Call) Return(raftBlockState RaftBlockState) *mocksourceNode_GetState_Call { + _c.Call.Return(raftBlockState) + return _c +} + +func (_c *mocksourceNode_GetState_Call) RunAndReturn(run func() RaftBlockState) *mocksourceNode_GetState_Call { + _c.Call.Return(run) + return _c +} + +// NodeID provides a mock function for the type mocksourceNode +func (_mock *mocksourceNode) NodeID() string { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for NodeID") + } + + var r0 string + if returnFunc, ok := ret.Get(0).(func() string); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(string) + } + return r0 +} + +// mocksourceNode_NodeID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NodeID' +type mocksourceNode_NodeID_Call struct { + *mock.Call +} + +// NodeID is a helper method to define mock.On call +func (_e *mocksourceNode_Expecter) NodeID() *mocksourceNode_NodeID_Call { + return &mocksourceNode_NodeID_Call{Call: _e.mock.On("NodeID")} +} + +func (_c *mocksourceNode_NodeID_Call) Run(run func()) *mocksourceNode_NodeID_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *mocksourceNode_NodeID_Call) Return(s string) *mocksourceNode_NodeID_Call { + _c.Call.Return(s) + return _c +} + +func (_c *mocksourceNode_NodeID_Call) RunAndReturn(run func() string) *mocksourceNode_NodeID_Call { + _c.Call.Return(run) + return _c +} + +// leaderCh provides a mock function for the type mocksourceNode +func (_mock *mocksourceNode) leaderCh() <-chan bool { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for leaderCh") + } + + var r0 <-chan bool + if returnFunc, ok := ret.Get(0).(func() <-chan bool); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan bool) + } + } + return r0 +} + +// mocksourceNode_leaderCh_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'leaderCh' +type mocksourceNode_leaderCh_Call struct { + *mock.Call +} + +// leaderCh is a helper method to define mock.On call +func (_e *mocksourceNode_Expecter) leaderCh() *mocksourceNode_leaderCh_Call { + return &mocksourceNode_leaderCh_Call{Call: _e.mock.On("leaderCh")} +} + +func (_c *mocksourceNode_leaderCh_Call) Run(run func()) *mocksourceNode_leaderCh_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *mocksourceNode_leaderCh_Call) Return(boolCh <-chan bool) *mocksourceNode_leaderCh_Call { + _c.Call.Return(boolCh) + return _c +} + +func (_c *mocksourceNode_leaderCh_Call) RunAndReturn(run func() <-chan bool) *mocksourceNode_leaderCh_Call { + _c.Call.Return(run) + return _c +} + +// leaderID provides a mock function for the type mocksourceNode +func (_mock *mocksourceNode) leaderID() string { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for leaderID") + } + + var r0 string + if returnFunc, ok := ret.Get(0).(func() string); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(string) + } + return r0 +} + +// mocksourceNode_leaderID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'leaderID' +type mocksourceNode_leaderID_Call struct { + *mock.Call +} + +// leaderID is a helper method to define mock.On call +func (_e *mocksourceNode_Expecter) leaderID() *mocksourceNode_leaderID_Call { + return &mocksourceNode_leaderID_Call{Call: _e.mock.On("leaderID")} +} + +func (_c *mocksourceNode_leaderID_Call) Run(run func()) *mocksourceNode_leaderID_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *mocksourceNode_leaderID_Call) Return(s string) *mocksourceNode_leaderID_Call { + _c.Call.Return(s) + return _c +} + +func (_c *mocksourceNode_leaderID_Call) RunAndReturn(run func() string) *mocksourceNode_leaderID_Call { + _c.Call.Return(run) + return _c +} + +// leadershipTransfer provides a mock function for the type mocksourceNode +func (_mock *mocksourceNode) leadershipTransfer() error { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for leadershipTransfer") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func() error); ok { + r0 = returnFunc() + } else { + r0 = ret.Error(0) + } + return r0 +} + +// mocksourceNode_leadershipTransfer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'leadershipTransfer' +type mocksourceNode_leadershipTransfer_Call struct { + *mock.Call +} + +// leadershipTransfer is a helper method to define mock.On call +func (_e *mocksourceNode_Expecter) leadershipTransfer() *mocksourceNode_leadershipTransfer_Call { + return &mocksourceNode_leadershipTransfer_Call{Call: _e.mock.On("leadershipTransfer")} +} + +func (_c *mocksourceNode_leadershipTransfer_Call) Run(run func()) *mocksourceNode_leadershipTransfer_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *mocksourceNode_leadershipTransfer_Call) Return(err error) *mocksourceNode_leadershipTransfer_Call { + _c.Call.Return(err) + return _c +} + +func (_c *mocksourceNode_leadershipTransfer_Call) RunAndReturn(run func() error) *mocksourceNode_leadershipTransfer_Call { + _c.Call.Return(run) + return _c +} + +// waitForMsgsLanded provides a mock function for the type mocksourceNode +func (_mock *mocksourceNode) waitForMsgsLanded(duration time.Duration) error { + ret := _mock.Called(duration) + + if len(ret) == 0 { + panic("no return value specified for waitForMsgsLanded") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(time.Duration) error); ok { + r0 = returnFunc(duration) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// mocksourceNode_waitForMsgsLanded_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'waitForMsgsLanded' +type mocksourceNode_waitForMsgsLanded_Call struct { + *mock.Call +} + +// waitForMsgsLanded is a helper method to define mock.On call +// - duration time.Duration +func (_e *mocksourceNode_Expecter) waitForMsgsLanded(duration interface{}) *mocksourceNode_waitForMsgsLanded_Call { + return &mocksourceNode_waitForMsgsLanded_Call{Call: _e.mock.On("waitForMsgsLanded", duration)} +} + +func (_c *mocksourceNode_waitForMsgsLanded_Call) Run(run func(duration time.Duration)) *mocksourceNode_waitForMsgsLanded_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 time.Duration + if args[0] != nil { + arg0 = args[0].(time.Duration) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *mocksourceNode_waitForMsgsLanded_Call) Return(err error) *mocksourceNode_waitForMsgsLanded_Call { + _c.Call.Return(err) + return _c +} + +func (_c *mocksourceNode_waitForMsgsLanded_Call) RunAndReturn(run func(duration time.Duration) error) *mocksourceNode_waitForMsgsLanded_Call { + _c.Call.Return(run) + return _c +} diff --git a/pkg/raft/node_test.go b/pkg/raft/node_test.go new file mode 100644 index 0000000000..9e3f8237a5 --- /dev/null +++ b/pkg/raft/node_test.go @@ -0,0 +1,110 @@ +package raft + +import ( + "errors" + "testing" + + "github.com/hashicorp/raft" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSplitPeerAddr(t *testing.T) { + specs := map[string]struct { + in string + exp raft.Server + expErr error + }{ + "valid": { + in: "node1@127.0.0.1:1234", + exp: raft.Server{ID: raft.ServerID("node1"), Address: raft.ServerAddress("127.0.0.1:1234")}, + }, + "trims whitespace": { + in: " node2 @ 10.0.0.2:9000 ", + exp: raft.Server{ID: raft.ServerID("node2"), Address: raft.ServerAddress("10.0.0.2:9000")}, + }, + "missing at": { + in: "node1", + expErr: errors.New("expecting nodeID@address for peer"), + }, + "empty node id": { + in: "@127.0.0.1:1234", + expErr: errors.New("nodeID cannot be empty"), + }, + "empty address": { + in: "node1@", + expErr: errors.New("address cannot be empty"), + }, + "multiple ats": { + in: "a@b@c", + expErr: errors.New("expecting nodeID@address for peer"), + }, + "only spaces": { + in: " @ ", + expErr: errors.New("nodeID cannot be empty"), + }, + } + + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + ctx := t.Context() + _ = ctx // keep to follow guideline to prefer t.Context; function under test doesn't use context + + got, err := splitPeerAddr(spec.in) + if spec.expErr != nil { + require.Error(t, err) + assert.Equal(t, spec.expErr.Error(), err.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, spec.exp, got) + }) + } +} + +func TestDeduplicateServers(t *testing.T) { + + specs := map[string]struct { + in []raft.Server + exp []raft.Server + }{ + "empty": { + in: nil, + exp: []raft.Server{}, + }, + "no duplicates": { + in: []raft.Server{ + {ID: raft.ServerID("n1"), Address: raft.ServerAddress("a1")}, + {ID: raft.ServerID("n2"), Address: raft.ServerAddress("a2")}, + }, + exp: []raft.Server{ + {ID: raft.ServerID("n1"), Address: raft.ServerAddress("a1")}, + {ID: raft.ServerID("n2"), Address: raft.ServerAddress("a2")}, + }, + }, + "duplicates keep first": { + in: []raft.Server{ + {ID: raft.ServerID("n1"), Address: raft.ServerAddress("a1")}, + {ID: raft.ServerID("n2"), Address: raft.ServerAddress("a2")}, + {ID: raft.ServerID("n1"), Address: raft.ServerAddress("a3")}, + {ID: raft.ServerID("n3"), Address: raft.ServerAddress("a4")}, + {ID: raft.ServerID("n2"), Address: raft.ServerAddress("a5")}, + }, + exp: []raft.Server{ + {ID: raft.ServerID("n1"), Address: raft.ServerAddress("a1")}, + {ID: raft.ServerID("n2"), Address: raft.ServerAddress("a2")}, + {ID: raft.ServerID("n3"), Address: raft.ServerAddress("a4")}, + }, + }, + } + + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + ctx := t.Context() + _ = ctx + + got := deduplicateServers(spec.in) + assert.Equal(t, spec.exp, got) + }) + } +} diff --git a/pkg/raft/types.go b/pkg/raft/types.go new file mode 100644 index 0000000000..93a8aef807 --- /dev/null +++ b/pkg/raft/types.go @@ -0,0 +1,29 @@ +package raft + +import "fmt" + +// RaftBlockState represents a replicated block state +type RaftBlockState struct { + Height uint64 + Hash []byte + Timestamp uint64 + Header []byte + Data []byte +} + +// assertValid checks basic constraints but does not ensure that no gaps exist or chain continuity +func (s RaftBlockState) assertValid(next RaftBlockState) error { + if s.Height > next.Height { + return fmt.Errorf("invalid height: %d > %d", s.Height, next.Height) + } + if s.Timestamp > next.Timestamp { + return fmt.Errorf("invalid timestamp: %d > %d", s.Timestamp, next.Timestamp) + } + return nil +} + +// RaftApplyMsg is sent when raft applies a log entry +type RaftApplyMsg struct { + Index uint64 + State *RaftBlockState +} diff --git a/pkg/rpc/example/example.go b/pkg/rpc/example/example.go index f3c020fa87..fd1f74f263 100644 --- a/pkg/rpc/example/example.go +++ b/pkg/rpc/example/example.go @@ -22,7 +22,7 @@ func StartStoreServer(s store.Store, address string, logger zerolog.Logger) { rpcAddr := fmt.Sprintf("%s:%d", "localhost", 8080) cfg := config.DefaultConfig() - handler, err := server.NewServiceHandler(s, nil, nil, nil, nil, logger, cfg, nil) + handler, err := server.NewServiceHandler(s, nil, nil, nil, nil, logger, cfg, nil, nil) if err != nil { panic(err) } @@ -82,7 +82,7 @@ func ExampleServer(s store.Store) { // Start RPC server rpcAddr := fmt.Sprintf("%s:%d", "localhost", 8080) cfg := config.DefaultConfig() - handler, err := server.NewServiceHandler(s, nil, nil, nil, nil, logger, cfg, nil) + handler, err := server.NewServiceHandler(s, nil, nil, nil, nil, logger, cfg, nil, nil) if err != nil { panic(err) } diff --git a/pkg/rpc/server/da_visualization_test.go b/pkg/rpc/server/da_visualization_test.go index 80b9a1408c..cf22e99f46 100644 --- a/pkg/rpc/server/da_visualization_test.go +++ b/pkg/rpc/server/da_visualization_test.go @@ -256,7 +256,7 @@ func TestRegisterCustomHTTPEndpointsDAVisualization(t *testing.T) { // Create mux and register endpoints mux := http.NewServeMux() nopLogger := zerolog.Nop() - RegisterCustomHTTPEndpoints(mux, nil, nil, config.DefaultConfig(), nil, nopLogger) + RegisterCustomHTTPEndpoints(mux, nil, nil, config.DefaultConfig(), nil, nopLogger, nil) // Test /da endpoint req, err := http.NewRequest("GET", "/da", nil) @@ -294,7 +294,7 @@ func TestRegisterCustomHTTPEndpointsWithoutServer(t *testing.T) { mux := http.NewServeMux() logger := zerolog.Nop() - RegisterCustomHTTPEndpoints(mux, nil, nil, config.DefaultConfig(), nil, logger) + RegisterCustomHTTPEndpoints(mux, nil, nil, config.DefaultConfig(), nil, logger, nil) // Test that endpoints return service unavailable when server is not set endpoints := []string{"/da", "/da/submissions", "/da/blob"} diff --git a/pkg/rpc/server/http.go b/pkg/rpc/server/http.go index 5d336e4c48..4e84f0a812 100644 --- a/pkg/rpc/server/http.go +++ b/pkg/rpc/server/http.go @@ -1,6 +1,7 @@ package server import ( + "encoding/json" "fmt" "net/http" "time" @@ -15,7 +16,7 @@ import ( type BestKnownHeightProvider func() uint64 // RegisterCustomHTTPEndpoints registers custom HTTP handlers on the mux. -func RegisterCustomHTTPEndpoints(mux *http.ServeMux, s store.Store, pm p2p.P2PRPC, cfg config.Config, bestKnownHeightProvider BestKnownHeightProvider, logger zerolog.Logger) { +func RegisterCustomHTTPEndpoints(mux *http.ServeMux, s store.Store, pm p2p.P2PRPC, cfg config.Config, bestKnownHeightProvider BestKnownHeightProvider, logger zerolog.Logger, raftNode RaftNodeSource) { // /health/live performs a basic liveness check to determine if the process is alive and responsive. // Returns 200 if the process can access its store, 503 otherwise. // This is a lightweight check suitable for Kubernetes liveness probes. @@ -130,6 +131,28 @@ func RegisterCustomHTTPEndpoints(mux *http.ServeMux, s store.Store, pm p2p.P2PRP fmt.Fprintln(w, "READY") }) + // optional Raft node details + if raftNode != nil { + mux.HandleFunc("/raft/node", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + rsp := struct { + IsLeader bool `json:"is_leader"` + NodeId string `json:"node_id"` + }{ + IsLeader: raftNode.IsLeader(), + NodeId: raftNode.NodeID(), + } + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(rsp); err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } + }) + + } + // DA Visualization endpoints mux.HandleFunc("/da", func(w http.ResponseWriter, r *http.Request) { server := GetDAVisualizationServer() diff --git a/pkg/rpc/server/http_test.go b/pkg/rpc/server/http_test.go index ab761a0a3e..a8e535a792 100644 --- a/pkg/rpc/server/http_test.go +++ b/pkg/rpc/server/http_test.go @@ -23,7 +23,7 @@ func TestRegisterCustomHTTPEndpoints(t *testing.T) { mockStore := mocks.NewMockStore(t) mockStore.On("Height", mock.Anything).Return(uint64(100), nil) - RegisterCustomHTTPEndpoints(mux, mockStore, nil, config.DefaultConfig(), nil, logger) + RegisterCustomHTTPEndpoints(mux, mockStore, nil, config.DefaultConfig(), nil, logger, nil) testServer := httptest.NewServer(mux) defer testServer.Close() @@ -113,7 +113,7 @@ func TestHealthReady_aggregatorBlockDelay(t *testing.T) { bestKnownHeightProvider := func() uint64 { return state.LastBlockHeight } - RegisterCustomHTTPEndpoints(mux, mockStore, nil, cfg, bestKnownHeightProvider, logger) + RegisterCustomHTTPEndpoints(mux, mockStore, nil, cfg, bestKnownHeightProvider, logger, nil) ts := httptest.NewServer(mux) t.Cleanup(ts.Close) diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index e0abed2de0..6f06a14d0e 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -367,6 +367,11 @@ func (p *P2PServer) GetNetInfo( }), nil } +type RaftNodeSource interface { + IsLeader() bool + NodeID() string +} + // NewServiceHandler creates a new HTTP handler for Store, P2P and Config services func NewServiceHandler( store store.Store, @@ -377,6 +382,7 @@ func NewServiceHandler( logger zerolog.Logger, config config.Config, bestKnown BestKnownHeightProvider, + raftNode RaftNodeSource, ) (http.Handler, error) { storeServer := NewStoreServer(store, headerStore, dataStore, logger) p2pServer := NewP2PServer(peerManager) @@ -405,7 +411,7 @@ func NewServiceHandler( mux.Handle(configPath, configHandler) // Register custom HTTP endpoints - RegisterCustomHTTPEndpoints(mux, store, peerManager, config, bestKnown, logger) + RegisterCustomHTTPEndpoints(mux, store, peerManager, config, bestKnown, logger, raftNode) // Use h2c to support HTTP/2 without TLS return h2c.NewHandler(mux, &http2.Server{ diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 32e9b0ebec..c9e2d6483d 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -416,7 +416,7 @@ func TestHealthLiveEndpoint(t *testing.T) { // Mock successful store access mockStore.On("Height", mock.Anything).Return(uint64(100), nil) - handler, err := NewServiceHandler(mockStore, nil, nil, mockP2PManager, nil, logger, testConfig, nil) + handler, err := NewServiceHandler(mockStore, nil, nil, mockP2PManager, nil, logger, testConfig, nil, nil) require.NoError(t, err) server := httptest.NewServer(handler) @@ -441,7 +441,7 @@ func TestHealthLiveEndpoint(t *testing.T) { // Mock store access failure mockStore.On("Height", mock.Anything).Return(uint64(0), fmt.Errorf("store unavailable")) - handler, err := NewServiceHandler(mockStore, nil, nil, mockP2PManager, nil, logger, testConfig, nil) + handler, err := NewServiceHandler(mockStore, nil, nil, mockP2PManager, nil, logger, testConfig, nil, nil) require.NoError(t, err) server := httptest.NewServer(handler) @@ -466,7 +466,7 @@ func TestHealthLiveEndpoint(t *testing.T) { // Mock successful store access at genesis mockStore.On("Height", mock.Anything).Return(uint64(0), nil) - handler, err := NewServiceHandler(mockStore, nil, nil, mockP2PManager, nil, logger, testConfig, nil) + handler, err := NewServiceHandler(mockStore, nil, nil, mockP2PManager, nil, logger, testConfig, nil, nil) require.NoError(t, err) server := httptest.NewServer(handler) @@ -543,7 +543,7 @@ func TestHealthReadyEndpoint(t *testing.T) { } bestKnown := func() uint64 { return tc.bestKnown } - handler, err := NewServiceHandler(mockStore, nil, nil, mockP2P, nil, logger, testConfig, bestKnown) + handler, err := NewServiceHandler(mockStore, nil, nil, mockP2P, nil, logger, testConfig, bestKnown, nil) require.NoError(t, err) server := httptest.NewServer(handler) defer server.Close() @@ -584,7 +584,7 @@ func TestHealthReadyEndpoint(t *testing.T) { mockStore.On("GetState", mock.Anything).Return(state, nil) bestKnown := func() uint64 { return 100 } - handler, err := NewServiceHandler(mockStore, nil, nil, mockP2P, nil, logger, testConfig, bestKnown) + handler, err := NewServiceHandler(mockStore, nil, nil, mockP2P, nil, logger, testConfig, bestKnown, nil) require.NoError(t, err) server := httptest.NewServer(handler) defer server.Close() @@ -614,7 +614,7 @@ func TestHealthReadyEndpoint(t *testing.T) { mockStore.On("GetState", mock.Anything).Return(state, nil) bestKnown := func() uint64 { return 100 } - handler, err := NewServiceHandler(mockStore, nil, nil, mockP2P, nil, logger, testConfig, bestKnown) + handler, err := NewServiceHandler(mockStore, nil, nil, mockP2P, nil, logger, testConfig, bestKnown, nil) require.NoError(t, err) server := httptest.NewServer(handler) defer server.Close() diff --git a/pkg/store/store.go b/pkg/store/store.go index 972b94e0e4..b36dd7f8eb 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -190,6 +190,11 @@ func (s *DefaultStore) GetMetadata(ctx context.Context, key string) ([]byte, err return data, nil } +// Sync flushes the store state to disk +func (s *DefaultStore) Sync(ctx context.Context) error { + return s.db.Sync(ctx, ds.NewKey("/")) +} + // Rollback rolls back block data until the given height from the store. // When aggregator is true, it will check the latest data included height and prevent rollback further than that. // NOTE: this function does not rollback metadata. Those should be handled separately if required. diff --git a/pkg/sync/sync_service.go b/pkg/sync/sync_service.go index 6a17a42a85..eb7ee25b3d 100644 --- a/pkg/sync/sync_service.go +++ b/pkg/sync/sync_service.go @@ -308,6 +308,11 @@ func (syncService *SyncService[H]) startSubscriber(ctx context.Context) error { return nil } +// Height returns the current height stored +func (s *SyncService[H]) Height() uint64 { + return s.store.Height() +} + // initFromP2PWithRetry initializes the syncer from P2P with a retry mechanism. // It inspects the local store to determine the first height to request: // - when the store already contains items, it reuses the latest height as the starting point; diff --git a/test/e2e/evm_test_common.go b/test/e2e/evm_test_common.go index 9d502cc218..0afaff039f 100644 --- a/test/e2e/evm_test_common.go +++ b/test/e2e/evm_test_common.go @@ -57,6 +57,14 @@ func getAvailablePort() (int, error) { return addr.Port, nil } +// same as getAvailablePort but fails test if not successful +func mustGetAvailablePort(t *testing.T) int { + t.Helper() + port, err := getAvailablePort() + require.NoError(t, err) + return port +} + // TestEndpoints holds unique port numbers for each test instance type TestEndpoints struct { DAPort string @@ -167,12 +175,6 @@ func generateTestEndpoints() (*TestEndpoints, error) { // Common constants used across EVM tests const ( - // Port configurations - DAPort = "7980" - RollkitRPCPort = "7331" - - DAAddress = "http://127.0.0.1:" + DAPort - RollkitRPCAddress = "http://127.0.0.1:" + RollkitRPCPort // Test configuration DefaultBlockTime = "150ms" @@ -187,6 +189,8 @@ const ( TestPassphrase = "secret" ) +var DefaultDANamespace = DefaultChainID + const ( SlowPollingInterval = 250 * time.Millisecond // Reduced from 500ms @@ -310,6 +314,7 @@ func setupSequencerNode(t *testing.T, sut *SystemUnderTest, sequencerHome, jwtSe // Use helper methods to get complete URLs args := []string{ "start", + "--evnode.log.format", "json", "--evm.jwt-secret-file", jwtSecretFile, "--evm.genesis-hash", genesisHash, "--evnode.node.block_time", DefaultBlockTime, @@ -351,6 +356,7 @@ func setupSequencerNodeLazy(t *testing.T, sut *SystemUnderTest, sequencerHome, j // Use helper methods to get complete URLs args := []string{ "start", + "--evnode.log.format", "json", "--evm.jwt-secret-file", jwtSecretFile, "--evm.genesis-hash", genesisHash, "--evnode.node.block_time", DefaultBlockTime, @@ -410,6 +416,7 @@ func setupFullNode(t *testing.T, sut *SystemUnderTest, fullNodeHome, sequencerHo // Use helper methods to get complete URLs args := []string{ "start", + "--evnode.log.format", "json", "--home", fullNodeHome, "--evm.jwt-secret-file", fullNodeJwtSecretFile, "--evm.genesis-hash", genesisHash, @@ -443,21 +450,25 @@ var globalNonce uint64 = 0 // // This is used in full node sync tests to verify that both nodes // include the same transaction in the same block number. -func submitTransactionAndGetBlockNumber(t *testing.T, sequencerClient *ethclient.Client) (common.Hash, uint64) { +func submitTransactionAndGetBlockNumber(t *testing.T, sequencerClients ...*ethclient.Client) (common.Hash, uint64) { t.Helper() // Submit transaction to sequencer EVM with unique nonce tx := evm.GetRandomTransaction(t, TestPrivateKey, TestToAddress, DefaultChainID, DefaultGasLimit, &globalNonce) - require.NoError(t, sequencerClient.SendTransaction(context.Background(), tx)) + for _, c := range sequencerClients { + require.NoError(t, c.SendTransaction(context.Background(), tx)) + } // Wait for transaction to be included and get block number ctx := context.Background() var txBlockNumber uint64 require.Eventually(t, func() bool { - receipt, err := sequencerClient.TransactionReceipt(ctx, tx.Hash()) - if err == nil && receipt != nil && receipt.Status == 1 { - txBlockNumber = receipt.BlockNumber.Uint64() - return true + for _, c := range sequencerClients { + receipt, err := c.TransactionReceipt(ctx, tx.Hash()) + if err == nil && receipt != nil && receipt.Status == 1 { + txBlockNumber = receipt.BlockNumber.Uint64() + return true + } } return false }, 8*time.Second, SlowPollingInterval) @@ -614,6 +625,7 @@ func restartDAAndSequencer(t *testing.T, sut *SystemUnderTest, sequencerHome, jw jwtSecretFile := filepath.Join(sequencerHome, "jwt-secret.hex") sut.ExecCmd(evmSingleBinaryPath, "start", + "--evnode.log.format", "json", "--evm.jwt-secret-file", jwtSecretFile, "--evm.genesis-hash", genesisHash, "--evnode.node.block_time", DefaultBlockTime, @@ -661,6 +673,7 @@ func restartDAAndSequencerLazy(t *testing.T, sut *SystemUnderTest, sequencerHome jwtSecretFile := filepath.Join(sequencerHome, "jwt-secret.hex") sut.ExecCmd(evmSingleBinaryPath, "start", + "--evnode.log.format", "json", "--evm.jwt-secret-file", jwtSecretFile, "--evm.genesis-hash", genesisHash, "--evnode.node.block_time", DefaultBlockTime, @@ -683,38 +696,6 @@ func restartDAAndSequencerLazy(t *testing.T, sut *SystemUnderTest, sequencerHome sut.AwaitNodeLive(t, endpoints.GetRollkitRPCAddress(), NodeStartupTimeout) } -// restartSequencerNode starts an existing sequencer node without initialization. -// This is used for restart scenarios where the node has already been initialized. -// -// Parameters: -// - sut: SystemUnderTest instance for managing test processes -// - sequencerHome: Directory path for sequencer node data -// - jwtSecret: JWT secret for sequencer's EVM engine authentication -// - genesisHash: Hash of the genesis block for chain validation -func restartSequencerNode(t *testing.T, sut *SystemUnderTest, sequencerHome, jwtSecret, genesisHash string) { - t.Helper() - - // Start sequencer node (without init - node already exists) - // The passphrase file and JWT secret file should still exist from the initial setup - passphraseFile := filepath.Join(sequencerHome, "passphrase.txt") - jwtSecretFile := filepath.Join(sequencerHome, "jwt-secret.hex") - sut.ExecCmd(evmSingleBinaryPath, - "start", - "--evm.jwt-secret-file", jwtSecretFile, - "--evm.genesis-hash", genesisHash, - "--evnode.node.block_time", DefaultBlockTime, - "--evnode.node.aggregator=true", - "--evnode.signer.passphrase_file", passphraseFile, - "--home", sequencerHome, - "--evnode.da.address", DAAddress, - "--evnode.da.block_time", DefaultDABlockTime, - ) - - time.Sleep(SlowPollingInterval) - - sut.AwaitNodeUp(t, RollkitRPCAddress, NodeStartupTimeout) -} - // verifyNoBlockProduction verifies that no new blocks are being produced over a specified duration. // This is used to test lazy mode behavior where blocks should only be produced when // transactions are submitted. diff --git a/test/e2e/failover_e2e_test.go b/test/e2e/failover_e2e_test.go new file mode 100644 index 0000000000..95bd8ef786 --- /dev/null +++ b/test/e2e/failover_e2e_test.go @@ -0,0 +1,632 @@ +//go:build e2e + +package e2e + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "maps" + "math/big" + "net/http" + "os" + "path/filepath" + "slices" + "strconv" + "strings" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + coreda "github.com/evstack/ev-node/core/da" + "github.com/evstack/ev-node/da/jsonrpc" + evmtest "github.com/evstack/ev-node/execution/evm/test" + rpcclient "github.com/evstack/ev-node/pkg/rpc/client" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + "github.com/evstack/ev-node/execution/evm" + "github.com/evstack/ev-node/pkg/rpc/client" + "github.com/evstack/ev-node/types" + pb "github.com/evstack/ev-node/types/pb/evnode/v1" +) + +// TestLeaseFailoverE2E runs three node binaries configured to use raft consensus. +// It forces a leader shutdown and verifies leadership failover occurs in the raft cluster. +func TestLeaseFailoverE2E(t *testing.T) { + flag.Parse() + sut := NewSystemUnderTest(t) + if testing.Verbose() { + os.Setenv("GOLOG_LOG_LEVEL", "DEBUG") + t.Cleanup(func() { + os.Unsetenv("GOLOG_LOG_LEVEL") + }) + } + + workDir := t.TempDir() + + // Get JWT secrets and setup common components first + jwtSecret, fullNodeJwtSecret, genesisHash, testEndpoints := setupCommonEVMTest(t, sut, true) + rethFn := evmtest.SetupTestRethNode(t) + jwtSecret3 := rethFn.JWTSecretHex() + fnInfo, err := rethFn.GetNetworkInfo(context.Background()) + require.NoError(t, err, "failed to get full node reth network info") + fullNode3EthPort := fnInfo.External.Ports.RPC + fullNode3EnginePort := fnInfo.External.Ports.Engine + + // Allocate raft ports for all nodes + node1RaftPort := mustGetAvailablePort(t) + node2RaftPort := mustGetAvailablePort(t) + node3RaftPort := mustGetAvailablePort(t) + + // Setup raft addresses + node1RaftAddr := fmt.Sprintf("127.0.0.1:%d", node1RaftPort) + node2RaftAddr := fmt.Sprintf("127.0.0.1:%d", node2RaftPort) + node3RaftAddr := fmt.Sprintf("127.0.0.1:%d", node3RaftPort) + raftCluster := []string{"node1@" + node1RaftAddr, "node2@" + node2RaftAddr, "node3@" + node3RaftAddr} + + bootstrapDir := filepath.Join(workDir, "bootstrap") + passphraseFile := initChain(t, sut, bootstrapDir) + + clusterNodes := &raftClusterNodes{ + nodes: make(map[string]*nodeDetails), + } + node1P2PAddr := testEndpoints.GetRollkitP2PAddress() + node2P2PAddr := testEndpoints.GetFullNodeP2PAddress() + node3P2PAddr := fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", mustGetAvailablePort(t)) + + // Start node1 (bootstrap mode) + go func() { + p2pPeers := node2P2PAddr + "," + node3P2PAddr + proc := setupRaftSequencerNode(t, sut, workDir, "node1", node1RaftAddr, jwtSecret, genesisHash, testEndpoints.GetDAAddress(), + bootstrapDir, raftCluster, p2pPeers, testEndpoints.GetRollkitRPCListen(), testEndpoints.GetRollkitP2PAddress(), + testEndpoints.GetSequencerEngineURL(), testEndpoints.GetSequencerEthURL(), true, passphraseFile) + clusterNodes.Set("node1", testEndpoints.GetRollkitRPCAddress(), proc, testEndpoints.GetSequencerEthURL(), node1RaftAddr, testEndpoints.GetRollkitP2PAddress(), testEndpoints.GetSequencerEngineURL(), testEndpoints.GetSequencerEthURL()) + t.Log("Node1 is up") + }() + + // Start node2 (bootstrap node) + go func() { + t.Log("Starting Node2") + p2pPeers := node1P2PAddr + "," + node3P2PAddr + proc := setupRaftSequencerNode(t, sut, workDir, "node2", node2RaftAddr, fullNodeJwtSecret, genesisHash, testEndpoints.GetDAAddress(), bootstrapDir, raftCluster, p2pPeers, testEndpoints.GetFullNodeRPCListen(), testEndpoints.GetFullNodeP2PAddress(), testEndpoints.GetFullNodeEngineURL(), testEndpoints.GetFullNodeEthURL(), true, passphraseFile) + clusterNodes.Set("node2", testEndpoints.GetFullNodeRPCAddress(), proc, testEndpoints.GetFullNodeEthURL(), node2RaftAddr, testEndpoints.GetFullNodeP2PAddress(), testEndpoints.GetFullNodeEngineURL(), testEndpoints.GetFullNodeEthURL()) + t.Log("Node2 is up") + }() + + // Start node3 (bootstrap node) + node3EthAddr := fmt.Sprintf("http://127.0.0.1:%s", fullNode3EthPort) + go func() { + t.Log("Starting Node3") + p2pPeers := node1P2PAddr + "," + node2P2PAddr + node3RPCListen := fmt.Sprintf("127.0.0.1:%d", mustGetAvailablePort(t)) + ethEngineURL := fmt.Sprintf("http://127.0.0.1:%s", fullNode3EnginePort) + proc := setupRaftSequencerNode(t, sut, workDir, "node3", node3RaftAddr, jwtSecret3, genesisHash, testEndpoints.GetDAAddress(), bootstrapDir, raftCluster, p2pPeers, node3RPCListen, node3P2PAddr, ethEngineURL, node3EthAddr, true, passphraseFile) + clusterNodes.Set("node3", "http://"+node3RPCListen, proc, node3EthAddr, node3RaftAddr, node3P2PAddr, ethEngineURL, node3EthAddr) + t.Log("Node3 is up") + }() + + var leaderNode string + require.EventuallyWithT(t, func(collect *assert.CollectT) { + leaderNode = clusterNodes.Leader(collect) + }, 5*time.Second, 200*time.Millisecond) + + sut.AwaitNodeUp(t, clusterNodes.Details(leaderNode).rpcAddr, NodeStartupTimeout) + // Wait for at least 2 blocks to be produced + sut.AwaitNBlocks(t, 2, clusterNodes.Details(leaderNode).rpcAddr, 6*time.Second) + + // Submit a tx and ensure it propagates to all nodes + txHash1, blk1 := submitTransactionAndGetBlockNumber(t, clusterNodes.Details(leaderNode).ethClient(t)) + + for node, details := range clusterNodes.Followers(t) { + require.Eventually(t, func() bool { + rec, err := details.ethClient(t).TransactionReceipt(t.Context(), txHash1) + return err == nil && rec != nil && rec.Status == 1 && rec.BlockNumber.Uint64() == blk1 + }, 20*time.Second, SlowPollingInterval, "tx1 not seen on "+node) + } + + oldLeader := leaderNode + t.Logf("+++ Killing current leader (%s)\n", oldLeader) + _ = clusterNodes.Details(oldLeader).Kill() + + const daStartHeight = 1 + lastDABlockOldLeader := queryLastDAHeight(t, daStartHeight, jwtSecret, testEndpoints.GetDAAddress()) + t.Log("+++ Last DA block by old leader: ", lastDABlockOldLeader) + leaderElectionStart := time.Now() + + // Wait for new leader election - submit tx to node2 + var newLeader string + require.EventuallyWithT(t, func(collect *assert.CollectT) { + newLeader = clusterNodes.Leader(collect) + }, 5*time.Second, 200*time.Millisecond) + require.NotEqual(t, oldLeader, newLeader) + t.Logf("+++ New leader: %s within ~%s\n", newLeader, time.Since(leaderElectionStart)) + + // submit TX to new leader + _, blk2 := submitTxToURL(t, clusterNodes.Details(newLeader).ethClient(t)) + require.Greater(t, blk2, blk1, "post-failover block should advance") + + // Verify DA progress + var lastDABlockNewLeader uint64 + require.Eventually(t, func() bool { + lastDABlockNewLeader = queryLastDAHeight(t, lastDABlockOldLeader, jwtSecret, testEndpoints.GetDAAddress()) + return lastDABlockNewLeader > lastDABlockOldLeader + }, 2*must(time.ParseDuration(DefaultDABlockTime)), 100*time.Millisecond) + t.Logf("+++ Last DA block by new leader: %d\n", lastDABlockNewLeader) + + // Restart oldLeader to rejoin cluster + var raftClusterRPCs []string + for _, f := range clusterNodes.AllNodes() { + if f.IsRunning() { + raftClusterRPCs = append(raftClusterRPCs, f.rpcAddr) + } + } + oldDetails := clusterNodes.Details(oldLeader) + restartedNodeProcess := setupRaftSequencerNode(t, sut, workDir, oldLeader, oldDetails.raftAddr, jwtSecret, genesisHash, testEndpoints.GetDAAddress(), "", raftCluster, clusterNodes.Details(newLeader).p2pAddr, oldDetails.rpcAddr, oldDetails.p2pAddr, oldDetails.engineURL, oldDetails.ethAddr, false, passphraseFile) + t.Log("Restarted old leader to sync with cluster: " + oldLeader) + + if IsNodeUp(t, oldDetails.rpcAddr, NodeStartupTimeout) { + clusterNodes.Set(oldLeader, oldDetails.rpcAddr, restartedNodeProcess, oldDetails.ethAddr, oldDetails.raftAddr, "", oldDetails.engineURL, oldDetails.ethAddr) + } else { + t.Log("+++ old leader did not recover on restart. Skipping node verification") + } + targetHeight := blk2 + 1 + // give node some time to catch up + for range 10 { + if bn, _ := clusterNodes.Details(oldLeader).ethClient(t).BlockNumber(t.Context()); bn > targetHeight { + break + } + time.Sleep(200 * time.Millisecond) + } + + // Ensure at least two nodes are actually producing/serving blocks beyond the last known height + minReady, ready := 2, 0 + running := make(map[string]struct{}) + require.Eventually(t, func() bool { + ready = 0 + for name, n := range clusterNodes.AllNodes() { + if !n.IsRunning() { + continue + } + bn, err := n.ethClient(t).BlockNumber(t.Context()) + if err != nil { + t.Logf("err node %s : %s", name, err) + continue + } + if bn >= targetHeight { + ready++ + running[name] = struct{}{} + } + } + return ready >= minReady + }, 20*time.Second, 200*time.Millisecond, "expected at least 2 raft nodes serving height >= %d", targetHeight) + t.Logf("+++ %d/3 nodes are up and running: %s / old: %s", ready, slices.Collect(maps.Keys(running)), oldLeader) + + t.Log("+++ Verifying no double-signing...") + var state *pb.State + require.Eventually(t, func() bool { + state, err = clusterNodes.Details(newLeader).rpcClient(t).GetState(t.Context()) + return err == nil + }, time.Second, 100*time.Millisecond) + + lastDABlockNewLeader = queryLastDAHeight(t, lastDABlockNewLeader, jwtSecret, testEndpoints.GetDAAddress()) + + genesisHeight := state.InitialHeight + verifyNoDoubleSigning(t, clusterNodes, genesisHeight, state.LastBlockHeight) + + // wait for the next DA block to ensure all blocks are propagated + require.Eventually(t, func() bool { + before := lastDABlockNewLeader + lastDABlockNewLeader = queryLastDAHeight(t, lastDABlockNewLeader, jwtSecret, testEndpoints.GetDAAddress()) + return before < lastDABlockNewLeader + }, 2*must(time.ParseDuration(DefaultDABlockTime)), 100*time.Millisecond) + + t.Log("+++ Verifying no DA gaps...") + verifyBlocks(t, daStartHeight, lastDABlockNewLeader, jwtSecret, testEndpoints.GetDAAddress(), genesisHeight, state.LastBlockHeight) + + // Cleanup processes + clusterNodes.killAll() + t.Logf("Completed leader change in: %s", time.Since(leaderElectionStart)) +} + +// verifyNoDoubleSigning checks that no two blocks at the same height have different hashes across nodes +func verifyNoDoubleSigning(t *testing.T, clusterNodes *raftClusterNodes, genesisHeight uint64, lastBlockHeight uint64) { + t.Helper() + // Compare block hashes across nodes + for height := genesisHeight; height <= lastBlockHeight; height++ { + nodeByHash := make(map[common.Hash][]string, 1) + for nodeName, node := range clusterNodes.AllNodes() { + if !node.running.Load() { + continue + } + var header *ethtypes.Header + require.Eventually(t, func() bool { + header, _ = node.ethClient(t).HeaderByNumber(t.Context(), big.NewInt(int64(height))) + return header != nil + }, 2*time.Second, 100*time.Millisecond, nodeName) + nodeByHash[header.Hash()] = append(nodeByHash[header.Hash()], nodeName) + } + if !assert.Len(t, nodeByHash, 1, "double signing detected at height %d: %v", height, nodeByHash) { + for _, nodes := range nodeByHash { + rsp, err := clusterNodes.Details(nodes[0]).rpcClient(t).GetBlockByHeight(t.Context(), height) + require.NoError(t, err) + t.Logf("%s: %v", nodes[0], rsp.Block) + } + //t.FailNow() + } + } +} + +// verifyBlocks checks that DA block heights form a continuous sequence without gaps +func verifyBlocks(t *testing.T, daStartHeight, lastDABlock uint64, jwtSecret string, daAddress string, genesisHeight, lastEVBlock uint64) { + t.Helper() + rpcClient, err := jsonrpc.NewClient(t.Context(), zerolog.Nop(), daAddress, jwtSecret, defaultMaxBlobSize) + require.NoError(t, err) + defer rpcClient.Close() + + namespace := coreda.NamespaceFromString(DefaultDANamespace).Bytes() + evHeightsToEvBlockParts := make(map[uint64]int) + deduplicationCache := make(map[string]uint64) // mixed header and data hashes + + lastEVHeight := genesisHeight + // Verify each block is present exactly once + for daHeight := daStartHeight; daHeight <= lastDABlock; daHeight++ { + res, err := rpcClient.DA.GetIDs(t.Context(), daHeight, namespace) + require.NoError(t, err, "height %d/%d", daHeight, lastDABlock) + require.NotEmpty(t, res.IDs) + + blobs, err := rpcClient.DA.Get(t.Context(), res.IDs, namespace) + require.NoError(t, err) + + for _, blob := range blobs { + if evHeight, hash := extractBlockHeight(t, blob); evHeight != 0 { + t.Logf("extracting block height from blob (da height %d): %x", daHeight, evHeight) + if height, ok := deduplicationCache[hash.String()]; ok { + require.Equal(t, evHeight, height) + continue + } + require.GreaterOrEqual(t, evHeight, lastEVHeight) + lastEVHeight = evHeight + deduplicationCache[hash.String()] = evHeight + evHeightsToEvBlockParts[evHeight]++ + } + } + } + + for h := genesisHeight; h <= lastEVBlock; h++ { + // can be 1 or 2 blobs per block if data is not empty + require.NotEmpty(t, evHeightsToEvBlockParts[h], "missing block on DA for height %d/%d", h, lastEVBlock) + require.Less(t, evHeightsToEvBlockParts[h], 3, "duplicate block on DA for height %d/%d", h, lastEVBlock) + } +} + +// extractBlockHeight attempts to decode a blob as SignedHeader or SignedData and extract the block height +func extractBlockHeight(t *testing.T, blob []byte) (uint64, types.Hash) { + t.Helper() + var headerPb pb.SignedHeader + if err := proto.Unmarshal(blob, &headerPb); err == nil { + var signedHeader types.SignedHeader + if err := signedHeader.FromProto(&headerPb); err == nil { + if err := signedHeader.Header.ValidateBasic(); err == nil { + return signedHeader.Height(), signedHeader.Hash() + } else { + t.Logf("invalid header: %v", err) + } + } else { + t.Logf("failed to unmarshal signed header: %v", err) + } + } else { + t.Logf("failed to unmarshal blob: %v", err) + } + + var signedData types.SignedData + if err := signedData.UnmarshalBinary(blob); err == nil { + if signedData.Metadata != nil { + return signedData.Height(), signedData.Hash() + } + } else { + t.Logf("failed to unmarshal signed data: %v", err) + } + return 0, nil +} + +func initChain(t *testing.T, sut *SystemUnderTest, workDir string) string { + passphraseFile := createPassphraseFile(t, workDir) + output, err := sut.RunCmd(evmSingleBinaryPath, + "init", + "--chain_id", DefaultChainID, + "--rollkit.node.aggregator=true", + "--evnode.signer.passphrase_file", passphraseFile, + "--home", workDir, + ) + require.NoError(t, err, "failed to init node", output) + return passphraseFile +} +func setupRaftSequencerNode( + t *testing.T, + sut *SystemUnderTest, + workDir, nodeID, raftAddr, jwtSecret, genesisHash, daAddress, bootstrapDir string, + allRaftClusterMembers []string, + p2pPeers, rpcAddr, p2pAddr, engineURL, ethURL string, + bootstrap bool, + passphraseFile string, +) *os.Process { + t.Helper() + nodeHome := filepath.Join(workDir, nodeID) + raftDir := filepath.Join(nodeHome, "raft") + + jwtSecretFile := filepath.Join(nodeHome, "jwt-secret.hex") + if bootstrap { + initChain(t, sut, nodeHome) + jwtSecretFile = createJWTSecretFile(t, nodeHome, jwtSecret) + + // Copy genesis and signer files for non-genGenesis nodes + MustCopyFile(t, filepath.Join(bootstrapDir, "config", "genesis.json"), + filepath.Join(nodeHome, "config", "genesis.json")) + MustCopyFile(t, filepath.Join(bootstrapDir, "config", "signer.json"), + filepath.Join(nodeHome, "config", "signer.json")) + } + if strings.HasPrefix(rpcAddr, "http://") { + rpcAddr = rpcAddr[7:] + } + raftPeers := slices.DeleteFunc(slices.Clone(allRaftClusterMembers), func(v string) bool { return strings.Contains(v, nodeID+"@") || strings.TrimSpace(v) == "" }) + + // Start node with raft configuration + process := sut.ExecCmdWithLogPrefix(nodeID, evmSingleBinaryPath, + "start", + "--evnode.log.format", "json", + //"--evnode.log.level", "DEBUG", + "--home", nodeHome, + "--evm.jwt-secret-file", jwtSecretFile, + "--evm.genesis-hash", genesisHash, + "--rollkit.da.address", daAddress, + "--rollkit.node.block_time", DefaultBlockTime, + "--rollkit.node.aggregator=true", + "--evnode.signer.passphrase_file", passphraseFile, + "--evnode.signer.signer_path", filepath.Join(nodeHome, "config"), + "--rollkit.da.block_time", (200 * time.Millisecond).String(), + "--rollkit.da.namespace", DefaultDANamespace, + + "--evnode.raft.enable=true", + "--evnode.raft.node_id="+nodeID, + "--evnode.raft.raft_addr="+raftAddr, + "--evnode.raft.raft_dir="+raftDir, + "--evnode.raft.bootstrap="+strconv.FormatBool(bootstrap), + "--evnode.raft.peers="+strings.Join(raftPeers, ","), + "--evnode.raft.snap_count=10", + "--evnode.raft.send_timeout=300ms", + "--evnode.raft.heartbeat_timeout=300ms", + + "--rollkit.p2p.peers", p2pPeers, + "--rollkit.rpc.address", rpcAddr, + "--rollkit.p2p.listen_address", p2pAddr, + "--evm.engine-url", engineURL, + "--evm.eth-url", ethURL, + ) + time.Sleep(SlowPollingInterval) + + //sut.AwaitNodeUp(t, "http://"+rpcAddr, NodeStartupTimeout) + return process +} + +// submitTxToURL submits a tx to the specified EVM endpoint and waits for inclusion. +func submitTxToURL(t *testing.T, client *ethclient.Client) (common.Hash, uint64) { + t.Helper() + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) + defer cancel() + + priv, err := crypto.HexToECDSA(TestPrivateKey) + require.NoError(t, err) + from := crypto.PubkeyToAddress(priv.PublicKey) + + nonce, err := client.PendingNonceAt(ctx, from) + require.NoError(t, err) + ln := nonce + + tx := evm.GetRandomTransaction(t, TestPrivateKey, TestToAddress, DefaultChainID, DefaultGasLimit, &ln) + require.NoError(t, client.SendTransaction(ctx, tx)) + + var blk uint64 + require.Eventually(t, func() bool { + rec, err := client.TransactionReceipt(t.Context(), tx.Hash()) + if err == nil && rec != nil && rec.Status == 1 { + blk = rec.BlockNumber.Uint64() + return true + } + return false + }, 20*time.Second, SlowPollingInterval) + + return tx.Hash(), blk +} + +const defaultMaxBlobSize = 2 * 1024 * 1024 // 2MB + +func queryLastDAHeight(t *testing.T, startHeight uint64, jwtSecret string, daAddress string) uint64 { + t.Helper() + logger := zerolog.Nop() + if testing.Verbose() { + logger = zerolog.New(zerolog.NewTestWriter(t)).Level(zerolog.DebugLevel) + } + client, err := jsonrpc.NewClient(t.Context(), logger, daAddress, jwtSecret, defaultMaxBlobSize) + require.NoError(t, err) + defer client.Close() + var lastDABlock = startHeight + for { + res, err := client.DA.GetIDs(t.Context(), lastDABlock, coreda.NamespaceFromString(DefaultDANamespace).Bytes()) + if err != nil { + if strings.Contains(err.Error(), "future") { + return lastDABlock - 1 + } + t.Fatal("failed to get IDs:", err) + } + if len(res.IDs) != 0 && testing.Verbose() { + t.Log("+++ DA block: ", lastDABlock, " ids: ", len(res.IDs)) + } + lastDABlock++ + } +} + +type nodeDetails struct { + raftAddr string + rpcAddr string + process *os.Process + ethAddr string + + extClientOnce sync.Once + xEthClient atomic.Pointer[ethclient.Client] + xRPCClient atomic.Pointer[rpcclient.Client] + running atomic.Bool + p2pAddr string + engineURL string + ethURL string +} + +func (d *nodeDetails) ethClient(t *testing.T) *ethclient.Client { + t.Helper() + d.initExtClients(t) + return d.xEthClient.Load() +} + +func (d *nodeDetails) rpcClient(t *testing.T) *rpcclient.Client { + t.Helper() + d.initExtClients(t) + return d.xRPCClient.Load() + +} + +func (d *nodeDetails) initExtClients(t *testing.T) { + require.NotNil(t, d) + d.extClientOnce.Do(func() { + client, err := ethclient.Dial(d.ethAddr) + require.NoError(t, err) + d.xEthClient.Store(client) + t.Cleanup(client.Close) + rpcClient := rpcclient.NewClient(d.rpcAddr) + require.NotNil(t, rpcClient) + d.xRPCClient.Store(rpcClient) + }) +} + +func (d *nodeDetails) IsRunning() bool { + return d.running.Load() +} + +func (d *nodeDetails) Kill() (err error) { + err = d.process.Kill() + d.running.Store(false) + return +} + +type raftClusterNodes struct { + mx sync.Mutex + nodes map[string]*nodeDetails +} + +func (c *raftClusterNodes) Set(node string, listen string, proc *os.Process, eth string, raftAddr string, p2pAddr string, engineURL string, ethURL string) { + c.mx.Lock() + defer c.mx.Unlock() + d := &nodeDetails{raftAddr: raftAddr, rpcAddr: listen, process: proc, ethAddr: eth, p2pAddr: p2pAddr, engineURL: engineURL, ethURL: ethURL} + d.running.Store(true) + c.nodes[node] = d +} + +func (c *raftClusterNodes) Leader(t require.TestingT) string { + node, _ := leader(t, c.AllNodes()) + return node +} + +func (c *raftClusterNodes) Details(node string) *nodeDetails { + c.mx.Lock() + defer c.mx.Unlock() + return c.nodes[node] +} + +func (c *raftClusterNodes) Followers(t require.TestingT) map[string]*nodeDetails { + all := c.AllNodes() + leader, _ := leader(t, all) + delete(all, leader) + return all +} + +func (c *raftClusterNodes) killAll() { + for _, d := range c.AllNodes() { + _ = d.Kill() + } +} + +// allNodes returns snapshot of nodes map +func (c *raftClusterNodes) AllNodes() map[string]*nodeDetails { + c.mx.Lock() + defer c.mx.Unlock() + return maps.Clone(c.nodes) +} + +// fails when no leader is found +func leader(t require.TestingT, nodes map[string]*nodeDetails) (string, *nodeDetails) { + client := &http.Client{Timeout: 1 * time.Second} + type nodeStatus struct { + IsLeader bool `json:"is_leader"` + } + for node, details := range nodes { + if !details.running.Load() { + continue + } + resp, err := client.Get(details.rpcAddr + "/raft/node") + require.NoError(t, err) + defer resp.Body.Close() + + var status nodeStatus + require.NoError(t, json.NewDecoder(resp.Body).Decode(&status)) + + if status.IsLeader { + return node, details + } + } + + t.Errorf("no leader found") + return "", nil +} + +func must[T any](r T, err error) T { + if err != nil { + panic(err) + } + return r +} + +// IsNodeUp waits until a node is operational by validating it produces blocks. +// Returns true if node is up within the specified timeout. +// Unlike AwaitNodeUp, this method Does not fail tests. +func IsNodeUp(t *testing.T, rpcAddr string, timeout time.Duration) bool { + t.Helper() + t.Logf("Query node is up: %s", rpcAddr) + ctx, done := context.WithTimeout(context.Background(), timeout) + defer done() + + ticker := time.Tick(min(timeout/10, 200*time.Millisecond)) + c := client.NewClient(rpcAddr) + require.NotNil(t, c) + var lastBlock uint64 + for { + select { + case <-ticker: + switch s, err := c.GetState(ctx); { + case err != nil: // ignore + case lastBlock == 0: + lastBlock = s.LastBlockHeight + case lastBlock < s.LastBlockHeight: + return true + } + case <-ctx.Done(): + return false + } + } +} diff --git a/test/e2e/go.mod b/test/e2e/go.mod index eda8633889..97d93e74f2 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -8,14 +8,19 @@ require ( github.com/celestiaorg/tastora v0.7.5 github.com/ethereum/go-ethereum v1.16.6 github.com/evstack/ev-node v0.0.0-00010101000000-000000000000 + github.com/evstack/ev-node/core v1.0.0-beta.5 + github.com/evstack/ev-node/da v1.0.0-beta.4 github.com/evstack/ev-node/execution/evm v0.0.0-20250602130019-2a732cf903a5 github.com/evstack/ev-node/execution/evm/test v0.0.0-00010101000000-000000000000 github.com/libp2p/go-libp2p v0.43.0 + github.com/rs/zerolog v1.34.0 github.com/stretchr/testify v1.11.1 + google.golang.org/protobuf v1.36.10 ) replace ( github.com/evstack/ev-node => ../../ + github.com/evstack/ev-node/da => ../../da github.com/evstack/ev-node/execution/evm => ../../execution/evm github.com/evstack/ev-node/execution/evm/test => ../../execution/evm/test ) @@ -82,9 +87,9 @@ require ( github.com/emicklei/dot v1.6.2 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.3 // indirect github.com/ethereum/go-verkle v0.2.2 // indirect - github.com/evstack/ev-node/core v1.0.0-beta.5 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect + github.com/filecoin-project/go-jsonrpc v0.9.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect github.com/go-kit/kit v0.13.0 // indirect @@ -164,7 +169,6 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/rs/zerolog v1.34.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.5 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect @@ -202,11 +206,11 @@ require ( golang.org/x/sys v0.38.0 // indirect golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/genproto v0.0.0-20240701130421-f6361c86f094 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/grpc v1.75.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.2 // indirect diff --git a/test/e2e/go.sum b/test/e2e/go.sum index 39e9fac2fc..1c86e37748 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -208,6 +208,8 @@ github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeD github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs= +github.com/filecoin-project/go-jsonrpc v0.9.0 h1:G47qEF52w7GholpI21vPSTVBFvsrip6geIoqNiqyZtQ= +github.com/filecoin-project/go-jsonrpc v0.9.0/go.mod h1:OG7kVBVh/AbDFHIwx7Kw0l9ARmKOS6gGOr0LbdBpbLc= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -334,8 +336,8 @@ github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NM github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I= +github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -884,6 +886,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= diff --git a/test/mocks/store.go b/test/mocks/store.go index 7f2aa180d0..ad05c76f5c 100644 --- a/test/mocks/store.go +++ b/test/mocks/store.go @@ -880,3 +880,54 @@ func (_c *MockStore_SetMetadata_Call) RunAndReturn(run func(ctx context.Context, _c.Call.Return(run) return _c } + +// Sync provides a mock function for the type MockStore +func (_mock *MockStore) Sync(ctx context.Context) error { + ret := _mock.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Sync") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = returnFunc(ctx) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockStore_Sync_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Sync' +type MockStore_Sync_Call struct { + *mock.Call +} + +// Sync is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockStore_Expecter) Sync(ctx interface{}) *MockStore_Sync_Call { + return &MockStore_Sync_Call{Call: _e.mock.On("Sync", ctx)} +} + +func (_c *MockStore_Sync_Call) Run(run func(ctx context.Context)) *MockStore_Sync_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockStore_Sync_Call) Return(err error) *MockStore_Sync_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockStore_Sync_Call) RunAndReturn(run func(ctx context.Context) error) *MockStore_Sync_Call { + _c.Call.Return(run) + return _c +}