diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 23df433ec..6eb4b73d5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: require_serial: true - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-added-large-files args: ["--maxkb=1000"] @@ -33,7 +33,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.1 + rev: v3.1.0 hooks: - id: prettier additional_dependencies: @@ -59,12 +59,12 @@ repos: ] - repo: https://github.com/pre-commit/mirrors-eslint - rev: "v8.4.1" + rev: "v8.56.0" hooks: - id: eslint - repo: https://github.com/shutter-network/pre-commit-go-hooks - rev: "7a66f5523b34139615a0c95f2b8a441dbc1778dc" + rev: "53239641ec106cda9a7acf9150c98be8d5ffa1ec" hooks: - id: shfmt args: ["-i", "4"] diff --git a/.tool-versions b/.tool-versions index 6089a4023..063c31a8c 100644 --- a/.tool-versions +++ b/.tool-versions @@ -7,7 +7,7 @@ golangci-lint 1.55.2 java temurin-17.0.5+8 nodejs 18.17.0 postgres 14.2 -pre-commit 3.3.3 +pre-commit 3.6.0 protoc 22.3 shfmt 3.7.0 solidity 0.8.9 diff --git a/docker/build-src/optimism/Dockerfile b/docker/build-src/optimism/Dockerfile new file mode 100644 index 000000000..cd20957af --- /dev/null +++ b/docker/build-src/optimism/Dockerfile @@ -0,0 +1,26 @@ +FROM golang:1.21 as builder +ENV GOMODCACHE=/root/.cache/mod + +# Fetch go modules separately to improve cache usage +RUN mkdir /gomod +COPY /rolling-shutter/go.* /gomod/ +WORKDIR /gomod +RUN --mount=type=cache,target=/root/.cache go mod download + +# Build binary +COPY / /src +WORKDIR /src/rolling-shutter + + +RUN go env +RUN --mount=type=cache,target=/root/.cache CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOFLAGS=-v make build + +FROM scratch as runner + +COPY --from=builder /src/rolling-shutter/bin/rolling-shutter /rolling-shutter + +# Use 'uclibc' flavor to avoid https://github.com/docker-library/busybox/issues/155#issuecomment-1344375664 +RUN --mount=from=busybox:uclibc,src=/bin,dst=/bin mkdir -p /etc/ssl +COPY --from=builder /etc/ssl/certs /etc/ssl/certs + +ENTRYPOINT ["/rolling-shutter"] diff --git a/rolling-shutter/app/app.go b/rolling-shutter/app/app.go index cb61cd4b9..0da1f98fd 100644 --- a/rolling-shutter/app/app.go +++ b/rolling-shutter/app/app.go @@ -306,7 +306,6 @@ func (ShutterApp) decodeTx(tx []byte) (signer common.Address, msg *shmsg.Message } msg, err = shmsg.GetMessage(signedMsg) - if err != nil { return } diff --git a/rolling-shutter/app/messages.go b/rolling-shutter/app/messages.go index 5f552a550..9e8262961 100644 --- a/rolling-shutter/app/messages.go +++ b/rolling-shutter/app/messages.go @@ -4,7 +4,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - bn256 "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" + "github.com/ethereum/go-ethereum/crypto/bls12381" "github.com/pkg/errors" "github.com/shutter-network/shutter/shlib/shcrypto" @@ -54,14 +54,17 @@ func ParsePolyEvalMsg(msg *shmsg.PolyEval, sender common.Address) (*PolyEval, er // ParsePolyCommitmentMsg converts a shmsg.PolyCommitmentMsg to an app.PolyCommitmentMsg. func ParsePolyCommitmentMsg(msg *shmsg.PolyCommitment, sender common.Address) (*PolyCommitment, error) { + g2 := bls12381.NewG2() gammas := shcrypto.Gammas{} for _, g := range msg.Gammas { - g2 := new(bn256.G2) - _, err := g2.Unmarshal(g) + p, err := g2.FromBytes(g) if err != nil { return nil, err } - gammas = append(gammas, g2) + if !g2.IsOnCurve(p) { + return nil, errors.Errorf("invalid gamma value %x", g) + } + gammas = append(gammas, p) } return &PolyCommitment{ Sender: sender, diff --git a/rolling-shutter/chainobserver/db/keyper/extend.go b/rolling-shutter/chainobserver/db/keyper/extend.go new file mode 100644 index 000000000..a3649898d --- /dev/null +++ b/rolling-shutter/chainobserver/db/keyper/extend.go @@ -0,0 +1,51 @@ +package database + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/shdb" +) + +// GetIndex returns the index of the given address in the KeyperSet. +func (s *KeyperSet) GetIndex(address common.Address) (uint64, error) { + encodedAddress := shdb.EncodeAddress(address) + for i, m := range s.Keypers { + if m == encodedAddress { + return uint64(i), nil + } + } + return 0, errors.Errorf("keyper %s not found", address.String()) +} + +// Contains checks if the given address is present in the KeyperSet. +// It returns true if the address is found, otherwise false. +func (s *KeyperSet) Contains(address common.Address) bool { + encodedAddress := shdb.EncodeAddress(address) + for _, m := range s.Keypers { + if m == encodedAddress { + return true + } + } + return false +} + +// GetSubset returns a subset of addresses from the KeyperSet based on the given indices. +// The return value is ordered according to the order of the given indices. If indices contains +// duplicates, the return value will do so as well. If at least one of the given indices is out of +// range, an error is returned. +func (s *KeyperSet) GetSubset(indices []uint64) ([]common.Address, error) { + subset := []common.Address{} + for _, i := range indices { + if i >= uint64(len(s.Keypers)) { + return nil, errors.Errorf("keyper index %d out of range (size %d)", i, len(s.Keypers)) + } + addressStr := s.Keypers[i] + address, err := shdb.DecodeAddress(addressStr) + if err != nil { + return nil, err + } + subset = append(subset, address) + } + return subset, nil +} diff --git a/rolling-shutter/chainobserver/db/keyper/extend_test.go b/rolling-shutter/chainobserver/db/keyper/extend_test.go new file mode 100644 index 000000000..526820551 --- /dev/null +++ b/rolling-shutter/chainobserver/db/keyper/extend_test.go @@ -0,0 +1,75 @@ +package database + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "gotest.tools/v3/assert" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/shdb" +) + +func makeTestKeyperSet() KeyperSet { + return KeyperSet{ + KeyperConfigIndex: 0, + ActivationBlockNumber: 0, + Keypers: []string{ + shdb.EncodeAddress(common.HexToAddress("0x0000000000000000000000000000000000000000")), + shdb.EncodeAddress(common.HexToAddress("0x5555555555555555555555555555555555555555")), + shdb.EncodeAddress(common.HexToAddress("0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa")), + }, + Threshold: 2, + } +} + +func TestKeyperSetGetIndex(t *testing.T) { + keyperSet := makeTestKeyperSet() + addresses, err := shdb.DecodeAddresses(keyperSet.Keypers) + assert.NilError(t, err) + + for i, address := range addresses { + index, err := keyperSet.GetIndex(address) + assert.NilError(t, err) + assert.Equal(t, uint64(i), index) + } + _, err = keyperSet.GetIndex(common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff")) + assert.ErrorContains(t, err, "keyper 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF not found") +} + +func TestKeyperSetContains(t *testing.T) { + keyperSet := makeTestKeyperSet() + addresses, err := shdb.DecodeAddresses(keyperSet.Keypers) + assert.NilError(t, err) + + for _, address := range addresses { + assert.Assert(t, keyperSet.Contains(address)) + } + assert.Assert(t, !keyperSet.Contains(common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff"))) +} + +func TestKeyperSetSubset(t *testing.T) { + keyperSet := makeTestKeyperSet() + testCases := []struct { + indices []uint64 + valid bool + }{ + {indices: []uint64{0, 1, 2}, valid: true}, + {indices: []uint64{}, valid: true}, + {indices: []uint64{1, 0}, valid: true}, + {indices: []uint64{0, 0}, valid: true}, + {indices: []uint64{0, 0, 0, 0}, valid: true}, + {indices: []uint64{3}, valid: false}, + } + + for _, tc := range testCases { + subset, err := keyperSet.GetSubset(tc.indices) + if tc.valid { + assert.Assert(t, len(subset) == len(tc.indices)) + for _, i := range tc.indices { + assert.Assert(t, shdb.EncodeAddress(subset[i]) == keyperSet.Keypers[tc.indices[i]]) + } + } else { + assert.Assert(t, err != nil) + } + } +} diff --git a/rolling-shutter/chainobserver/db/keyper/keyper.sqlc.gen.go b/rolling-shutter/chainobserver/db/keyper/keyper.sqlc.gen.go index 12d2bc318..353ec0a05 100644 --- a/rolling-shutter/chainobserver/db/keyper/keyper.sqlc.gen.go +++ b/rolling-shutter/chainobserver/db/keyper/keyper.sqlc.gen.go @@ -43,6 +43,36 @@ func (q *Queries) GetKeyperSetByKeyperConfigIndex(ctx context.Context, keyperCon return i, err } +const getKeyperSets = `-- name: GetKeyperSets :many +SELECT keyper_config_index, activation_block_number, keypers, threshold FROM keyper_set +ORDER BY activation_block_number ASC +` + +func (q *Queries) GetKeyperSets(ctx context.Context) ([]KeyperSet, error) { + rows, err := q.db.Query(ctx, getKeyperSets) + if err != nil { + return nil, err + } + defer rows.Close() + var items []KeyperSet + for rows.Next() { + var i KeyperSet + if err := rows.Scan( + &i.KeyperConfigIndex, + &i.ActivationBlockNumber, + &i.Keypers, + &i.Threshold, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const insertKeyperSet = `-- name: InsertKeyperSet :exec INSERT INTO keyper_set ( keyper_config_index, diff --git a/rolling-shutter/chainobserver/db/keyper/sql/queries/keyper.sql b/rolling-shutter/chainobserver/db/keyper/sql/queries/keyper.sql index 1e8bea09a..10fee50a1 100644 --- a/rolling-shutter/chainobserver/db/keyper/sql/queries/keyper.sql +++ b/rolling-shutter/chainobserver/db/keyper/sql/queries/keyper.sql @@ -15,3 +15,7 @@ SELECT * FROM keyper_set WHERE keyper_config_index=$1; SELECT * FROM keyper_set WHERE activation_block_number <= $1 ORDER BY activation_block_number DESC LIMIT 1; + +-- name: GetKeyperSets :many +SELECT * FROM keyper_set +ORDER BY activation_block_number ASC; \ No newline at end of file diff --git a/rolling-shutter/chainobserver/observer.go b/rolling-shutter/chainobserver/observer.go index cd66935da..fd339dc16 100644 --- a/rolling-shutter/chainobserver/observer.go +++ b/rolling-shutter/chainobserver/observer.go @@ -79,7 +79,7 @@ func (c *ChainObserver) Start(ctx context.Context, runner service.Runner) error log.Info().Uint64("from-block", fromBlock).Uint64("from-log-index", fromLogIndex). Msg("starting event syncing") syncer := eventsyncer.New(c.Client, finalityOffset, eventTypes, fromBlock, fromLogIndex) - handleSyncLoop := func(ctx context.Context) error { + handleSyncLoop := func(ctx context.Context, _ service.Runner) error { for { select { case <-ctx.Done(): @@ -102,5 +102,5 @@ func (c *ChainObserver) Start(ctx context.Context, runner service.Runner) error } } } - return runner.StartService(syncer, service.ServiceFn{Fn: handleSyncLoop}) + return runner.StartService(syncer, service.Function{Func: handleSyncLoop}) } diff --git a/rolling-shutter/cmd/bootstrap/bootstrap.go b/rolling-shutter/cmd/bootstrap/bootstrap.go index 0c4ce0ee3..8b4d6dcb3 100644 --- a/rolling-shutter/cmd/bootstrap/bootstrap.go +++ b/rolling-shutter/cmd/bootstrap/bootstrap.go @@ -83,6 +83,10 @@ chain's genesis config.`, return cmd } +// TODO: deprecate this command and split this up in 2 stages: +// 1. gather initial keyperset information e.g. from contracts +// (could be different from optimism, rollupshutter,snapshot) +// 2. send the batch-config / new block seen message based on that info. func bootstrap(config *Config) error { ctx := context.Background() ethereumClient, err := ethclient.DialContext(ctx, config.EthereumURL) diff --git a/rolling-shutter/cmd/chain/init.go b/rolling-shutter/cmd/chain/init.go index 3e1df6991..d77a28089 100644 --- a/rolling-shutter/cmd/chain/init.go +++ b/rolling-shutter/cmd/chain/init.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "strconv" "strings" "time" @@ -27,7 +28,12 @@ import ( "github.com/shutter-network/rolling-shutter/rolling-shutter/app" ) -const VALIDATOR = "validator" +const ( + VALIDATOR = "validator" + ISOLATEDVALIDATOR = "isolated-validator" + SENTRY = "sentry" + SEED = "seed" +) type Config struct { RootDir string `mapstructure:"root"` @@ -65,7 +71,7 @@ func initCmd() *cobra.Command { cmd.PersistentFlags().Float64("blocktime", 1.0, "block time in seconds") cmd.PersistentFlags().StringSlice("genesis-keyper", nil, "genesis keyper address") cmd.PersistentFlags().String("listen-address", "tcp://127.0.0.1:26657", "tendermint RPC listen address") - cmd.PersistentFlags().String("role", "validator", "tendermint node role (validator, sentry, seed)") + cmd.PersistentFlags().String("role", "validator", "tendermint node role (validator, isolated-validator, sentry, seed)") cmd.PersistentFlags().Uint64("initial-eon", 0, "initial eon") return cmd } @@ -95,7 +101,7 @@ func getArgFromViper[T interface{}](getter func(string) T, name string, required func initFiles(_ *cobra.Command, config *Config, _ []string) error { keypers := []common.Address{} - if config.Role == VALIDATOR { + if slices.Contains([]string{VALIDATOR, ISOLATEDVALIDATOR}, config.Role) { for _, a := range config.GenesisKeyper { if !common.IsHexAddress(a) { return errors.Errorf("--genesis-keyper argument '%s' is not an address", a) @@ -130,16 +136,18 @@ func initFiles(_ *cobra.Command, config *Config, _ []string) error { return errors.Wrap(err, "error in config file") } cfg.EnsureRoot(tendermintCfg.RootDir) - // set up according to the network role: https://docs.tendermint.com/v0.34/tendermint-core/validators.html switch config.Role { - case VALIDATOR: + case VALIDATOR: // standard validator mode, network exposed + tendermintCfg.P2P.PexReactor = true + tendermintCfg.P2P.AddrBookStrict = true + case ISOLATEDVALIDATOR: // validator mode behind a sentry node tendermintCfg.P2P.PexReactor = false tendermintCfg.P2P.AddrBookStrict = false - case "sentry": + case SENTRY: tendermintCfg.P2P.PexReactor = true tendermintCfg.P2P.AddrBookStrict = false - case "seed": + case SEED: tendermintCfg.P2P.PexReactor = true tendermintCfg.P2P.AddrBookStrict = false default: @@ -170,62 +178,64 @@ func adjustPort(address string, keyperIndex int) (string, error) { func initFilesWithConfig(tendermintConfig *cfg.Config, config *Config, appState app.GenesisAppState) error { var err error - // private validator - privValKeyFile := tendermintConfig.PrivValidatorKeyFile() - privValStateFile := tendermintConfig.PrivValidatorStateFile() - var pv *privval.FilePV - if tmos.FileExists(privValKeyFile) { - pv = privval.LoadFilePV(privValKeyFile, privValStateFile) - log.Info(). - Str("privValKeyFile", privValKeyFile). - Str("stateFile", privValStateFile). - Msg("Found private validator") - } else { - pv = privval.GenFilePV(privValKeyFile, privValStateFile) - pv.Save() - log.Info(). - Str("privValKeyFile", privValKeyFile). - Str("stateFile", privValStateFile). - Msg("Generated private validator") - } - - validatorPubKeyPath := filepath.Join(tendermintConfig.RootDir, "config", "priv_validator_pubkey.hex") - validatorPublicKeyHex := hex.EncodeToString(pv.Key.PubKey.Bytes()) - err = os.WriteFile(validatorPubKeyPath, []byte(validatorPublicKeyHex), 0o644) - if err != nil { - return errors.Wrapf(err, "Could not write to %s", validatorPubKeyPath) - } - log.Info().Str("path", validatorPubKeyPath).Str("validatorPublicKey", validatorPublicKeyHex).Msg("Saved private validator publickey") - - // genesis file - genFile := tendermintConfig.GenesisFile() - if tmos.FileExists(genFile) { - log.Info().Str("path", genFile).Msg("Found genesis file") - } else { - appStateBytes, err := amino.NewCodec().MarshalJSONIndent(appState, "", " ") - if err != nil { - return err - } - genDoc := types.GenesisDoc{ - ChainID: fmt.Sprintf("shutter-test-chain-%v", tmrand.Str(6)), - GenesisTime: time.Now(), - ConsensusParams: types.DefaultConsensusParams(), - AppState: appStateBytes, + if slices.Contains([]string{VALIDATOR, ISOLATEDVALIDATOR, SEED}, config.Role) { + // private validator + privValKeyFile := tendermintConfig.PrivValidatorKeyFile() + privValStateFile := tendermintConfig.PrivValidatorStateFile() + var pv *privval.FilePV + if tmos.FileExists(privValKeyFile) { + pv = privval.LoadFilePV(privValKeyFile, privValStateFile) + log.Info(). + Str("privValKeyFile", privValKeyFile). + Str("stateFile", privValStateFile). + Msg("Found private validator") + } else { + pv = privval.GenFilePV(privValKeyFile, privValStateFile) + pv.Save() + log.Info(). + Str("privValKeyFile", privValKeyFile). + Str("stateFile", privValStateFile). + Msg("Generated private validator") } - pubKey, err := pv.GetPubKey() + + validatorPubKeyPath := filepath.Join(tendermintConfig.RootDir, "config", "priv_validator_pubkey.hex") + validatorPublicKeyHex := hex.EncodeToString(pv.Key.PubKey.Bytes()) + err = os.WriteFile(validatorPubKeyPath, []byte(validatorPublicKeyHex), 0o644) if err != nil { - return errors.Wrap(err, "can't get pubkey") + return errors.Wrapf(err, "Could not write to %s", validatorPubKeyPath) } - genDoc.Validators = []types.GenesisValidator{{ - Address: pubKey.Address(), - PubKey: pubKey, - Power: 10, - }} + log.Info().Str("path", validatorPubKeyPath).Str("validatorPublicKey", validatorPublicKeyHex).Msg("Saved private validator publickey") - if err := genDoc.SaveAs(genFile); err != nil { - return err + // genesis file + genFile := tendermintConfig.GenesisFile() + if tmos.FileExists(genFile) { + log.Info().Str("path", genFile).Msg("Found genesis file") + } else { + appStateBytes, err := amino.NewCodec().MarshalJSONIndent(appState, "", " ") + if err != nil { + return err + } + genDoc := types.GenesisDoc{ + ChainID: fmt.Sprintf("shutter-test-chain-%v", tmrand.Str(6)), + GenesisTime: time.Now(), + ConsensusParams: types.DefaultConsensusParams(), + AppState: appStateBytes, + } + pubKey, err := pv.GetPubKey() + if err != nil { + return errors.Wrap(err, "can't get pubkey") + } + genDoc.Validators = []types.GenesisValidator{{ + Address: pubKey.Address(), + PubKey: pubKey, + Power: 10, + }} + + if err := genDoc.SaveAs(genFile); err != nil { + return err + } + log.Info().Str("path", genFile).Msg("Generated genesis file") } - log.Info().Str("path", genFile).Msg("Generated genesis file") } nodeKeyFile := tendermintConfig.NodeKeyFile() diff --git a/rolling-shutter/cmd/command.go b/rolling-shutter/cmd/command.go index da75b75ba..91cea7895 100644 --- a/rolling-shutter/cmd/command.go +++ b/rolling-shutter/cmd/command.go @@ -7,8 +7,10 @@ import ( "github.com/shutter-network/rolling-shutter/rolling-shutter/cmd/chain" "github.com/shutter-network/rolling-shutter/rolling-shutter/cmd/collator" "github.com/shutter-network/rolling-shutter/rolling-shutter/cmd/cryptocmd" + "github.com/shutter-network/rolling-shutter/rolling-shutter/cmd/gnosiskeyper" "github.com/shutter-network/rolling-shutter/rolling-shutter/cmd/mocknode" "github.com/shutter-network/rolling-shutter/rolling-shutter/cmd/mocksequencer" + "github.com/shutter-network/rolling-shutter/rolling-shutter/cmd/optimism" "github.com/shutter-network/rolling-shutter/rolling-shutter/cmd/p2pnode" "github.com/shutter-network/rolling-shutter/rolling-shutter/cmd/proxy" "github.com/shutter-network/rolling-shutter/rolling-shutter/cmd/rollupkeyper" @@ -22,10 +24,13 @@ func Subcommands() []*cobra.Command { bootstrap.Cmd(), chain.Cmd(), collator.Cmd(), + optimism.Cmd(), + optimism.OPBootstrapCmd(), rollupkeyper.Cmd(), mocknode.Cmd(), snapshot.Cmd(), snapshotkeyper.Cmd(), + gnosiskeyper.Cmd(), cryptocmd.Cmd(), proxy.Cmd(), mocksequencer.Cmd(), diff --git a/rolling-shutter/cmd/cryptocmd/jsontests.go b/rolling-shutter/cmd/cryptocmd/jsontests.go index 732b14487..15ac6acd3 100644 --- a/rolling-shutter/cmd/cryptocmd/jsontests.go +++ b/rolling-shutter/cmd/cryptocmd/jsontests.go @@ -10,7 +10,7 @@ import ( "os" "github.com/ethereum/go-ethereum/common/hexutil" - bn256 "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" + "github.com/ethereum/go-ethereum/crypto/bls12381" "github.com/spf13/cobra" "github.com/shutter-network/shutter/shlib/shcrypto" @@ -416,7 +416,7 @@ func verifyTestCase(tc *testCase) error { } func createEncryptionTest(keygen *testkeygen.KeyGenerator, message []byte) (*encryptionTest, error) { - epochID := keygen.RandomEpochID(make([]byte, 32)) + epochID := keygen.RandomEpochID(make([]byte, 52)) et := encryptionTest{} @@ -452,12 +452,13 @@ func createEncryptionTest(keygen *testkeygen.KeyGenerator, message []byte) (*enc // tamperEncryptedMessage changes the C1 value of EncryptedMessage, which allows to test for malleability issues. func tamperEncryptedMessage(keygen *testkeygen.KeyGenerator, et encryptionTest) encryptionTest { decryptionKey := keygen.EpochSecretKey(et.EpochID) - var c1 *bn256.G2 + g2 := bls12381.NewG2() + var c1 *bls12381.PointG2 var err error for i := 1; i <= 10000; i++ { c1 = et.Expected.C1 - c1.Add(c1, c1) + g2.Add(c1, c1, c1) et.Expected.C1 = c1 sigma := et.Expected.Sigma(decryptionKey) decryptedBlocks := shcrypto.DecryptBlocks(et.Expected.C3, sigma) @@ -500,8 +501,8 @@ func createVerificationTest(keygen *testkeygen.KeyGenerator, payload []byte) (ve func createFailedVerificationTest(keygen *testkeygen.KeyGenerator, _ []byte) (verificationTest, error) { var err error vt := verificationTest{} - epochID := keygen.RandomEpochID(make([]byte, 32)) - mismatch := keygen.RandomEpochID(make([]byte, 32)) + epochID := keygen.RandomEpochID(make([]byte, 52)) + mismatch := keygen.RandomEpochID(make([]byte, 52)) vt.EpochID = epochID vt.EpochSecretKey = *keygen.EpochSecretKey(epochID) vt.EonPublicKey = *keygen.EonPublicKey(mismatch) diff --git a/rolling-shutter/cmd/gnosiskeyper/gnosiskeyper.go b/rolling-shutter/cmd/gnosiskeyper/gnosiskeyper.go new file mode 100644 index 000000000..2c6fa02e1 --- /dev/null +++ b/rolling-shutter/cmd/gnosiskeyper/gnosiskeyper.go @@ -0,0 +1,65 @@ +package gnosiskeyper + +import ( + "context" + + "github.com/jackc/pgx/v4/pgxpool" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/cmd/shversion" + "github.com/shutter-network/rolling-shutter/rolling-shutter/gnosiskeyperwatcher" + keyper "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/gnosis" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/gnosis/database" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/configuration/command" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/db" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" +) + +func Cmd() *cobra.Command { + builder := command.Build( + main, + command.Usage( + "Run a Shutter keyper for Gnosis Chain", + `This command runs a keyper node. It will connect to both a Gnosis and a +Shuttermint node which have to be started separately in advance.`, + ), + command.WithGenerateConfigSubcommand(), + command.WithDumpConfigSubcommand(), + ) + builder.AddInitDBCommand(initDB) + builder.AddFunctionSubcommand( + watch, + "watch", + "Watch the keypers doing their work and log the generated decryption keys.", + cobra.NoArgs, + ) + return builder.Command() +} + +func main(config *keyper.Config) error { + log.Info(). + Str("version", shversion.Version()). + Str("address", config.GetAddress().Hex()). + Str("shuttermint", config.Shuttermint.ShuttermintURL). + Msg("starting gnosis keyper") + + kpr := keyper.New(config) + return service.RunWithSighandler(context.Background(), kpr) +} + +func initDB(cfg *keyper.Config) error { + ctx := context.Background() + dbpool, err := pgxpool.Connect(ctx, cfg.DatabaseURL) + if err != nil { + return errors.Wrap(err, "failed to connect to database") + } + defer dbpool.Close() + return db.InitDB(ctx, dbpool, database.Definition.Name(), database.Definition) +} + +func watch(cfg *keyper.Config) error { + log.Info().Msg("starting monitor") + return service.RunWithSighandler(context.Background(), gnosiskeyperwatcher.New(cfg)) +} diff --git a/rolling-shutter/cmd/optimism/bootstrap.go b/rolling-shutter/cmd/optimism/bootstrap.go new file mode 100644 index 000000000..45e616309 --- /dev/null +++ b/rolling-shutter/cmd/optimism/bootstrap.go @@ -0,0 +1,42 @@ +package optimism + +import ( + "context" + + "github.com/spf13/cobra" + + boot "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/optimism/bootstrap" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/configuration/command" +) + +// TODO: use this to replace the old bootstrap command. +// First writing the keyperset and then bootstrapping allows +// to support different contracts etc. +func OPBootstrapCmd() *cobra.Command { + builder := command.Build( + bootstrap, + command.Usage( + "Bootstrap validator utility functions for a shuttermint chain", + ``, + ), + command.WithGenerateConfigSubcommand(), + ) + + bootstrapCmd := &cobra.Command{ + Use: "fetch-keyperset", + Short: "fetch-keyperset", + Args: cobra.NoArgs, + RunE: builder.WrapFuncParseConfig(keyperSet), + } + builder.Command().AddCommand(bootstrapCmd) + return builder.Command() +} + +func keyperSet(cfg *boot.Config) error { + ctx := context.Background() + return boot.GetKeyperSet(ctx, cfg) +} + +func bootstrap(cfg *boot.Config) error { + return boot.BootstrapValidators(cfg) +} diff --git a/rolling-shutter/cmd/optimism/keyper.go b/rolling-shutter/cmd/optimism/keyper.go new file mode 100644 index 000000000..f23d091be --- /dev/null +++ b/rolling-shutter/cmd/optimism/keyper.go @@ -0,0 +1,56 @@ +package optimism + +import ( + "context" + + "github.com/jackc/pgx/v4/pgxpool" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/cmd/shversion" + keyper "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/optimism" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/optimism/config" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/optimism/database" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/configuration/command" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/db" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" +) + +func Cmd() *cobra.Command { + builder := command.Build( + main, + command.Usage( + "Run a Shutter optimism keyper node", + `This command runs a keyper node. It will connect to both an Optimism and a +Shuttermint node which have to be started separately in advance.`, + ), + command.WithGenerateConfigSubcommand(), + ) + builder.AddInitDBCommand(initDB) + return builder.Command() +} + +func main(cfg *config.Config) error { + log.Info(). + Str("version", shversion.Version()). + Str("address", cfg.GetAddress().Hex()). + Str("shuttermint", cfg.Shuttermint.ShuttermintURL). + Msg("starting keyper") + kpr, err := keyper.New(cfg) + if err != nil { + return err + } + return service.RunWithSighandler(context.Background(), kpr) +} + +func initDB(cfg *config.Config) error { + ctx := context.Background() + dbpool, err := pgxpool.Connect(ctx, cfg.DatabaseURL) + if err != nil { + return errors.Wrap(err, "failed to connect to database") + } + defer dbpool.Close() + + return db.InitDB(ctx, dbpool, database.Definition.Name(), database.Definition) +} diff --git a/rolling-shutter/cmd/optimism/keyper_test.go b/rolling-shutter/cmd/optimism/keyper_test.go new file mode 100644 index 000000000..e06cf7ff5 --- /dev/null +++ b/rolling-shutter/cmd/optimism/keyper_test.go @@ -0,0 +1,25 @@ +package optimism_test + +import ( + "testing" + + "gotest.tools/assert" + + keyper "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/rollup" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/configuration" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/configuration/test" +) + +func TestSmokeGenerateConfig(t *testing.T) { + config := keyper.NewConfig() + test.SmokeGenerateConfig(t, config) +} + +func TestParsedConfig(t *testing.T) { + config := keyper.NewConfig() + + err := configuration.SetExampleValuesRecursive(config) + assert.NilError(t, err) + parsedConfig := test.RoundtripParseConfig(t, config) + assert.DeepEqual(t, config, parsedConfig) +} diff --git a/rolling-shutter/collator/batcher/batcher.go b/rolling-shutter/collator/batcher/batcher.go index 27d016616..8d5969d0f 100644 --- a/rolling-shutter/collator/batcher/batcher.go +++ b/rolling-shutter/collator/batcher/batcher.go @@ -320,7 +320,6 @@ func (btchr *Batcher) CloseBatch(ctx context.Context) error { err = btchr.dbpool.BeginFunc(ctx, func(dbtx pgx.Tx) error { return btchr.closeBatchImpl(ctx, database.New(dbtx), int64(l1blockNumber)) }) - if err != nil { return err } diff --git a/rolling-shutter/collator/epochhandling.go b/rolling-shutter/collator/epochhandling.go index 4b452ae2b..5603cbb84 100644 --- a/rolling-shutter/collator/epochhandling.go +++ b/rolling-shutter/collator/epochhandling.go @@ -140,7 +140,7 @@ func (c *collator) sendDecryptionTriggers(ctx context.Context) error { err := c.p2p.SendMessage(ctx, msg, retry.Interval(time.Second), - retry.ExponentialBackoff(), + retry.ExponentialBackoff(nil), retry.NumberOfRetries(3), retry.LogIdentifier(msg.LogInfo()), ) diff --git a/rolling-shutter/contract/deployment/deployment.go b/rolling-shutter/contract/deployment/deployment.go index d982dcda2..68b0a4889 100644 --- a/rolling-shutter/contract/deployment/deployment.go +++ b/rolling-shutter/contract/deployment/deployment.go @@ -334,7 +334,8 @@ func LoadChainID(dir string) (uint64, error) { return 0, errors.Wrapf(err, "failed to load chain id file at %s", path) } - chainID, err := strconv.ParseInt(string(data), 10, 64) + chainIDStr := strings.TrimSpace(string(data)) + chainID, err := strconv.ParseInt(chainIDStr, 10, 64) if err != nil { return 0, errors.Wrapf(err, "failed to parse chain id in %s", path) } diff --git a/rolling-shutter/docs/rolling-shutter.md b/rolling-shutter/docs/rolling-shutter.md index 91022711d..1e092ddfa 100644 --- a/rolling-shutter/docs/rolling-shutter.md +++ b/rolling-shutter/docs/rolling-shutter.md @@ -17,9 +17,12 @@ A collection of commands to run and interact with Rolling Shutter nodes * [rolling-shutter chain](rolling-shutter_chain.md) - Run a node for Shutter's Tendermint chain * [rolling-shutter collator](rolling-shutter_collator.md) - Run a collator node * [rolling-shutter crypto](rolling-shutter_crypto.md) - CLI tool to access crypto functions +* [rolling-shutter gnosiskeyper](rolling-shutter_gnosiskeyper.md) - Run a Shutter keyper for Gnosis Chain * [rolling-shutter keyper](rolling-shutter_keyper.md) - Run a Shutter keyper node * [rolling-shutter mocknode](rolling-shutter_mocknode.md) - Run a Shutter mock node * [rolling-shutter mocksequencer](rolling-shutter_mocksequencer.md) - Run a Shutter mock sequencer +* [rolling-shutter op-bootstrap](rolling-shutter_op-bootstrap.md) - Bootstrap validator utility functions for a shuttermint chain +* [rolling-shutter op-keyper](rolling-shutter_op-keyper.md) - Run a Shutter optimism keyper node * [rolling-shutter p2pnode](rolling-shutter_p2pnode.md) - Run a Shutter p2p bootstrap node * [rolling-shutter proxy](rolling-shutter_proxy.md) - Run a Ethereum JSON RPC proxy * [rolling-shutter snapshot](rolling-shutter_snapshot.md) - Run the Snapshot Hub communication module diff --git a/rolling-shutter/docs/rolling-shutter_chain_init.md b/rolling-shutter/docs/rolling-shutter_chain_init.md index a6b11a425..ab8813509 100644 --- a/rolling-shutter/docs/rolling-shutter_chain_init.md +++ b/rolling-shutter/docs/rolling-shutter_chain_init.md @@ -16,7 +16,7 @@ rolling-shutter chain init [flags] --index int keyper index --initial-eon uint initial eon --listen-address string tendermint RPC listen address (default "tcp://127.0.0.1:26657") - --role string tendermint node role (validator, sentry, seed) (default "validator") + --role string tendermint node role (validator, isolated-validator, sentry, seed) (default "validator") --root string root directory ``` diff --git a/rolling-shutter/docs/rolling-shutter_gnosiskeyper.md b/rolling-shutter/docs/rolling-shutter_gnosiskeyper.md new file mode 100644 index 000000000..382f4c561 --- /dev/null +++ b/rolling-shutter/docs/rolling-shutter_gnosiskeyper.md @@ -0,0 +1,36 @@ +## rolling-shutter gnosiskeyper + +Run a Shutter keyper for Gnosis Chain + +### Synopsis + +This command runs a keyper node. It will connect to both a Gnosis and a +Shuttermint node which have to be started separately in advance. + +``` +rolling-shutter gnosiskeyper [flags] +``` + +### Options + +``` + --config string config file + -h, --help help for gnosiskeyper +``` + +### Options inherited from parent commands + +``` + --logformat string set log format, possible values: min, short, long, max (default "long") + --loglevel string set log level, possible values: warn, info, debug (default "info") + --no-color do not write colored logs +``` + +### SEE ALSO + +* [rolling-shutter](rolling-shutter.md) - A collection of commands to run and interact with Rolling Shutter nodes +* [rolling-shutter gnosiskeyper dump-config](rolling-shutter_gnosiskeyper_dump-config.md) - Dump a 'gnosiskeyper' configuration file, based on given config and env vars +* [rolling-shutter gnosiskeyper generate-config](rolling-shutter_gnosiskeyper_generate-config.md) - Generate a 'gnosiskeyper' configuration file +* [rolling-shutter gnosiskeyper initdb](rolling-shutter_gnosiskeyper_initdb.md) - Initialize the database of the 'gnosiskeyper' +* [rolling-shutter gnosiskeyper watch](rolling-shutter_gnosiskeyper_watch.md) - Watch the keypers doing their work and log the generated decryption keys. + diff --git a/rolling-shutter/docs/rolling-shutter_gnosiskeyper_dump-config.md b/rolling-shutter/docs/rolling-shutter_gnosiskeyper_dump-config.md new file mode 100644 index 000000000..7db677f4d --- /dev/null +++ b/rolling-shutter/docs/rolling-shutter_gnosiskeyper_dump-config.md @@ -0,0 +1,28 @@ +## rolling-shutter gnosiskeyper dump-config + +Dump a 'gnosiskeyper' configuration file, based on given config and env vars + +``` +rolling-shutter gnosiskeyper dump-config [flags] +``` + +### Options + +``` + --config string config file + -h, --help help for dump-config + --output string output file +``` + +### Options inherited from parent commands + +``` + --logformat string set log format, possible values: min, short, long, max (default "long") + --loglevel string set log level, possible values: warn, info, debug (default "info") + --no-color do not write colored logs +``` + +### SEE ALSO + +* [rolling-shutter gnosiskeyper](rolling-shutter_gnosiskeyper.md) - Run a Shutter keyper for Gnosis Chain + diff --git a/rolling-shutter/docs/rolling-shutter_gnosiskeyper_generate-config.md b/rolling-shutter/docs/rolling-shutter_gnosiskeyper_generate-config.md new file mode 100644 index 000000000..f35f9b490 --- /dev/null +++ b/rolling-shutter/docs/rolling-shutter_gnosiskeyper_generate-config.md @@ -0,0 +1,28 @@ +## rolling-shutter gnosiskeyper generate-config + +Generate a 'gnosiskeyper' configuration file + +``` +rolling-shutter gnosiskeyper generate-config [flags] +``` + +### Options + +``` + -h, --help help for generate-config + --output string output file +``` + +### Options inherited from parent commands + +``` + --config string config file + --logformat string set log format, possible values: min, short, long, max (default "long") + --loglevel string set log level, possible values: warn, info, debug (default "info") + --no-color do not write colored logs +``` + +### SEE ALSO + +* [rolling-shutter gnosiskeyper](rolling-shutter_gnosiskeyper.md) - Run a Shutter keyper for Gnosis Chain + diff --git a/rolling-shutter/docs/rolling-shutter_gnosiskeyper_initdb.md b/rolling-shutter/docs/rolling-shutter_gnosiskeyper_initdb.md new file mode 100644 index 000000000..e8bc1a5ab --- /dev/null +++ b/rolling-shutter/docs/rolling-shutter_gnosiskeyper_initdb.md @@ -0,0 +1,27 @@ +## rolling-shutter gnosiskeyper initdb + +Initialize the database of the 'gnosiskeyper' + +``` +rolling-shutter gnosiskeyper initdb [flags] +``` + +### Options + +``` + -h, --help help for initdb +``` + +### Options inherited from parent commands + +``` + --config string config file + --logformat string set log format, possible values: min, short, long, max (default "long") + --loglevel string set log level, possible values: warn, info, debug (default "info") + --no-color do not write colored logs +``` + +### SEE ALSO + +* [rolling-shutter gnosiskeyper](rolling-shutter_gnosiskeyper.md) - Run a Shutter keyper for Gnosis Chain + diff --git a/rolling-shutter/docs/rolling-shutter_gnosiskeyper_watch.md b/rolling-shutter/docs/rolling-shutter_gnosiskeyper_watch.md new file mode 100644 index 000000000..b81192149 --- /dev/null +++ b/rolling-shutter/docs/rolling-shutter_gnosiskeyper_watch.md @@ -0,0 +1,27 @@ +## rolling-shutter gnosiskeyper watch + +Watch the keypers doing their work and log the generated decryption keys. + +``` +rolling-shutter gnosiskeyper watch [flags] +``` + +### Options + +``` + -h, --help help for watch +``` + +### Options inherited from parent commands + +``` + --config string config file + --logformat string set log format, possible values: min, short, long, max (default "long") + --loglevel string set log level, possible values: warn, info, debug (default "info") + --no-color do not write colored logs +``` + +### SEE ALSO + +* [rolling-shutter gnosiskeyper](rolling-shutter_gnosiskeyper.md) - Run a Shutter keyper for Gnosis Chain + diff --git a/rolling-shutter/docs/rolling-shutter_op-bootstrap.md b/rolling-shutter/docs/rolling-shutter_op-bootstrap.md new file mode 100644 index 000000000..d6f2b918b --- /dev/null +++ b/rolling-shutter/docs/rolling-shutter_op-bootstrap.md @@ -0,0 +1,29 @@ +## rolling-shutter op-bootstrap + +Bootstrap validator utility functions for a shuttermint chain + +``` +rolling-shutter op-bootstrap [flags] +``` + +### Options + +``` + --config string config file + -h, --help help for op-bootstrap +``` + +### Options inherited from parent commands + +``` + --logformat string set log format, possible values: min, short, long, max (default "long") + --loglevel string set log level, possible values: warn, info, debug (default "info") + --no-color do not write colored logs +``` + +### SEE ALSO + +* [rolling-shutter](rolling-shutter.md) - A collection of commands to run and interact with Rolling Shutter nodes +* [rolling-shutter op-bootstrap fetch-keyperset](rolling-shutter_op-bootstrap_fetch-keyperset.md) - fetch-keyperset +* [rolling-shutter op-bootstrap generate-config](rolling-shutter_op-bootstrap_generate-config.md) - Generate a 'op-bootstrap' configuration file + diff --git a/rolling-shutter/docs/rolling-shutter_op-bootstrap_fetch-keyperset.md b/rolling-shutter/docs/rolling-shutter_op-bootstrap_fetch-keyperset.md new file mode 100644 index 000000000..c2c839899 --- /dev/null +++ b/rolling-shutter/docs/rolling-shutter_op-bootstrap_fetch-keyperset.md @@ -0,0 +1,27 @@ +## rolling-shutter op-bootstrap fetch-keyperset + +fetch-keyperset + +``` +rolling-shutter op-bootstrap fetch-keyperset [flags] +``` + +### Options + +``` + -h, --help help for fetch-keyperset +``` + +### Options inherited from parent commands + +``` + --config string config file + --logformat string set log format, possible values: min, short, long, max (default "long") + --loglevel string set log level, possible values: warn, info, debug (default "info") + --no-color do not write colored logs +``` + +### SEE ALSO + +* [rolling-shutter op-bootstrap](rolling-shutter_op-bootstrap.md) - Bootstrap validator utility functions for a shuttermint chain + diff --git a/rolling-shutter/docs/rolling-shutter_op-bootstrap_generate-config.md b/rolling-shutter/docs/rolling-shutter_op-bootstrap_generate-config.md new file mode 100644 index 000000000..1ef716e53 --- /dev/null +++ b/rolling-shutter/docs/rolling-shutter_op-bootstrap_generate-config.md @@ -0,0 +1,28 @@ +## rolling-shutter op-bootstrap generate-config + +Generate a 'op-bootstrap' configuration file + +``` +rolling-shutter op-bootstrap generate-config [flags] +``` + +### Options + +``` + -h, --help help for generate-config + --output string output file +``` + +### Options inherited from parent commands + +``` + --config string config file + --logformat string set log format, possible values: min, short, long, max (default "long") + --loglevel string set log level, possible values: warn, info, debug (default "info") + --no-color do not write colored logs +``` + +### SEE ALSO + +* [rolling-shutter op-bootstrap](rolling-shutter_op-bootstrap.md) - Bootstrap validator utility functions for a shuttermint chain + diff --git a/rolling-shutter/docs/rolling-shutter_op-keyper.md b/rolling-shutter/docs/rolling-shutter_op-keyper.md new file mode 100644 index 000000000..30ae10467 --- /dev/null +++ b/rolling-shutter/docs/rolling-shutter_op-keyper.md @@ -0,0 +1,34 @@ +## rolling-shutter op-keyper + +Run a Shutter optimism keyper node + +### Synopsis + +This command runs a keyper node. It will connect to both an Optimism and a +Shuttermint node which have to be started separately in advance. + +``` +rolling-shutter op-keyper [flags] +``` + +### Options + +``` + --config string config file + -h, --help help for op-keyper +``` + +### Options inherited from parent commands + +``` + --logformat string set log format, possible values: min, short, long, max (default "long") + --loglevel string set log level, possible values: warn, info, debug (default "info") + --no-color do not write colored logs +``` + +### SEE ALSO + +* [rolling-shutter](rolling-shutter.md) - A collection of commands to run and interact with Rolling Shutter nodes +* [rolling-shutter op-keyper generate-config](rolling-shutter_op-keyper_generate-config.md) - Generate a 'op-keyper' configuration file +* [rolling-shutter op-keyper initdb](rolling-shutter_op-keyper_initdb.md) - Initialize the database of the 'op-keyper' + diff --git a/rolling-shutter/docs/rolling-shutter_op-keyper_generate-config.md b/rolling-shutter/docs/rolling-shutter_op-keyper_generate-config.md new file mode 100644 index 000000000..0e1cf9d6f --- /dev/null +++ b/rolling-shutter/docs/rolling-shutter_op-keyper_generate-config.md @@ -0,0 +1,28 @@ +## rolling-shutter op-keyper generate-config + +Generate a 'op-keyper' configuration file + +``` +rolling-shutter op-keyper generate-config [flags] +``` + +### Options + +``` + -h, --help help for generate-config + --output string output file +``` + +### Options inherited from parent commands + +``` + --config string config file + --logformat string set log format, possible values: min, short, long, max (default "long") + --loglevel string set log level, possible values: warn, info, debug (default "info") + --no-color do not write colored logs +``` + +### SEE ALSO + +* [rolling-shutter op-keyper](rolling-shutter_op-keyper.md) - Run a Shutter optimism keyper node + diff --git a/rolling-shutter/docs/rolling-shutter_op-keyper_initdb.md b/rolling-shutter/docs/rolling-shutter_op-keyper_initdb.md new file mode 100644 index 000000000..a220fc42c --- /dev/null +++ b/rolling-shutter/docs/rolling-shutter_op-keyper_initdb.md @@ -0,0 +1,27 @@ +## rolling-shutter op-keyper initdb + +Initialize the database of the 'op-keyper' + +``` +rolling-shutter op-keyper initdb [flags] +``` + +### Options + +``` + -h, --help help for initdb +``` + +### Options inherited from parent commands + +``` + --config string config file + --logformat string set log format, possible values: min, short, long, max (default "long") + --loglevel string set log level, possible values: warn, info, debug (default "info") + --no-color do not write colored logs +``` + +### SEE ALSO + +* [rolling-shutter op-keyper](rolling-shutter_op-keyper.md) - Run a Shutter optimism keyper node + diff --git a/rolling-shutter/docs/rolling-shutter_optimismkeyper.md b/rolling-shutter/docs/rolling-shutter_optimismkeyper.md new file mode 100644 index 000000000..bf0a546e0 --- /dev/null +++ b/rolling-shutter/docs/rolling-shutter_optimismkeyper.md @@ -0,0 +1,34 @@ +## rolling-shutter optimismkeyper + +Run a Shutter optimism keyper node + +### Synopsis + +This command runs a keyper node. It will connect to both an Optimism and a +Shuttermint node which have to be started separately in advance. + +``` +rolling-shutter optimismkeyper [flags] +``` + +### Options + +``` + --config string config file + -h, --help help for optimismkeyper +``` + +### Options inherited from parent commands + +``` + --logformat string set log format, possible values: min, short, long, max (default "long") + --loglevel string set log level, possible values: warn, info, debug (default "info") + --no-color do not write colored logs +``` + +### SEE ALSO + +* [rolling-shutter](rolling-shutter.md) - A collection of commands to run and interact with Rolling Shutter nodes +* [rolling-shutter optimismkeyper generate-config](rolling-shutter_optimismkeyper_generate-config.md) - Generate a 'optimismkeyper' configuration file +* [rolling-shutter optimismkeyper initdb](rolling-shutter_optimismkeyper_initdb.md) - Initialize the database of the 'optimismkeyper' + diff --git a/rolling-shutter/docs/rolling-shutter_optimismkeyper_generate-config.md b/rolling-shutter/docs/rolling-shutter_optimismkeyper_generate-config.md new file mode 100644 index 000000000..55d179a08 --- /dev/null +++ b/rolling-shutter/docs/rolling-shutter_optimismkeyper_generate-config.md @@ -0,0 +1,28 @@ +## rolling-shutter optimismkeyper generate-config + +Generate a 'optimismkeyper' configuration file + +``` +rolling-shutter optimismkeyper generate-config [flags] +``` + +### Options + +``` + -h, --help help for generate-config + --output string output file +``` + +### Options inherited from parent commands + +``` + --config string config file + --logformat string set log format, possible values: min, short, long, max (default "long") + --loglevel string set log level, possible values: warn, info, debug (default "info") + --no-color do not write colored logs +``` + +### SEE ALSO + +* [rolling-shutter optimismkeyper](rolling-shutter_optimismkeyper.md) - Run a Shutter optimism keyper node + diff --git a/rolling-shutter/docs/rolling-shutter_optimismkeyper_initdb.md b/rolling-shutter/docs/rolling-shutter_optimismkeyper_initdb.md new file mode 100644 index 000000000..cec7e7bc0 --- /dev/null +++ b/rolling-shutter/docs/rolling-shutter_optimismkeyper_initdb.md @@ -0,0 +1,27 @@ +## rolling-shutter optimismkeyper initdb + +Initialize the database of the 'optimismkeyper' + +``` +rolling-shutter optimismkeyper initdb [flags] +``` + +### Options + +``` + -h, --help help for initdb +``` + +### Options inherited from parent commands + +``` + --config string config file + --logformat string set log format, possible values: min, short, long, max (default "long") + --loglevel string set log level, possible values: warn, info, debug (default "info") + --no-color do not write colored logs +``` + +### SEE ALSO + +* [rolling-shutter optimismkeyper](rolling-shutter_optimismkeyper.md) - Run a Shutter optimism keyper node + diff --git a/rolling-shutter/eonkeypublisher/eonkeypublisher.go b/rolling-shutter/eonkeypublisher/eonkeypublisher.go new file mode 100644 index 000000000..076bbf45f --- /dev/null +++ b/rolling-shutter/eonkeypublisher/eonkeypublisher.go @@ -0,0 +1,208 @@ +package eonkeypublisher + +import ( + "context" + "crypto/ecdsa" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/jackc/pgx/v4/pgxpool" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "github.com/shutter-network/shop-contracts/bindings" + + obskeyperdb "github.com/shutter-network/rolling-shutter/rolling-shutter/chainobserver/db/keyper" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper" + corekeyperdb "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/database" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/retry" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" + "github.com/shutter-network/rolling-shutter/rolling-shutter/shdb" +) + +const ( + eonKeyChannelSize = 32 + retryInterval = time.Second * 12 +) + +// EonKeyPublisher is a service that publishes eon keys via a eon key publisher contract. +type EonKeyPublisher struct { + dbpool *pgxpool.Pool + client *ethclient.Client + contract *bindings.EonKeyPublish + privateKey *ecdsa.PrivateKey + + keys chan keyper.EonPublicKey +} + +func NewEonKeyPublisher( + dbpool *pgxpool.Pool, + client *ethclient.Client, + eonKeyPublishAddress common.Address, + privateKey *ecdsa.PrivateKey, +) (*EonKeyPublisher, error) { + contract, err := bindings.NewEonKeyPublish(eonKeyPublishAddress, client) + if err != nil { + return nil, errors.Wrap(err, "failed to instantiate eon key publisher contract") + } + return &EonKeyPublisher{ + dbpool: dbpool, + client: client, + contract: contract, + privateKey: privateKey, + + keys: make(chan keyper.EonPublicKey, eonKeyChannelSize), + }, nil +} + +func (p *EonKeyPublisher) Start(ctx context.Context, runner service.Runner) error { //nolint: unparam + log.Info().Msg("starting eon key publisher") + runner.Go(func() error { + p.publishOldKeys(ctx) + for { + select { + case key := <-p.keys: + p.publishIfResponsible(ctx, key) + case <-ctx.Done(): + return ctx.Err() + } + } + }) + return nil +} + +// Publish schedules a eon key to be published. +func (p *EonKeyPublisher) Publish(key keyper.EonPublicKey) { + p.keys <- key +} + +// publishIfResponsible publishes a eon key if the keyper is part of the corresponding keyper +// set, unless the key is already confirmed or the keyper has already voted on it. +func (p *EonKeyPublisher) publishIfResponsible(ctx context.Context, key keyper.EonPublicKey) { + db := obskeyperdb.New(p.dbpool) + keyperSet, err := db.GetKeyperSetByKeyperConfigIndex(ctx, int64(key.Eon)) + if err != nil { + log.Error(). + Err(err). + Uint64("keyper-set-index", key.KeyperConfigIndex). + Hex("key", key.PublicKey). + Msg("failed to check if eon key should be published") + return + } + keyperAddress := ethcrypto.PubkeyToAddress(p.privateKey.PublicKey) + keyperIndex, err := keyperSet.GetIndex(keyperAddress) + if err != nil { + log.Info(). + Uint64("keyper-set-index", key.KeyperConfigIndex). + Str("keyper-address", keyperAddress.Hex()). + Hex("key", key.PublicKey). + Msg("not publishing eon key as keyper is not part of corresponding keyper set") + return + } + p.publish(ctx, key.PublicKey, key.KeyperConfigIndex, keyperIndex) +} + +// publishOldKeys publishes all eon keys that are already in the database, unless they're already +// confirmed or the keyper has already voted on them. +func (p *EonKeyPublisher) publishOldKeys(ctx context.Context) { + db := corekeyperdb.New(p.dbpool) + dkgResultsDB, err := db.GetAllDKGResults(ctx) + if err != nil { + err := errors.Wrap(err, "failed to query DKG results from db") + log.Error().Err(err).Msg("failed to publish old eon keys") + return + } + for _, dkgResultDB := range dkgResultsDB { + if !dkgResultDB.Success { + continue + } + dkgResult, err := shdb.DecodePureDKGResult(dkgResultDB.PureResult) + if err != nil { + err := errors.Wrapf(err, "failed to decode DKG result of eon %d", dkgResultDB.Eon) + log.Error().Err(err).Msg("failed to publish old eon keys") + continue + } + p.publish(ctx, dkgResult.PublicKey.Marshal(), dkgResult.Eon, dkgResult.Keyper) + } +} + +// publish publishes an eon key, unless it's already confirmed or the keyper has already voted on +// it. On errors, publishing will be retried a few times and eventually aborted. +func (p *EonKeyPublisher) publish(ctx context.Context, key []byte, keyperSetIndex uint64, keyperIndex uint64) { + _, err := retry.FunctionCall[struct{}](ctx, func(ctx context.Context) (struct{}, error) { + return struct{}{}, p.tryPublish(ctx, key, keyperSetIndex, keyperIndex) + }, retry.Interval(retryInterval)) + if err != nil { + log.Error(). + Err(err). + Uint64("keyper-set-index", keyperSetIndex). + Hex("key", key). + Msg("failed to publish eon key") + } +} + +func (p *EonKeyPublisher) tryPublish(ctx context.Context, key []byte, keyperSetIndex uint64, keyperIndex uint64) error { + keyperAddress := ethcrypto.PubkeyToAddress(p.privateKey.PublicKey) + hasAlreadyVoted, err := p.contract.HasKeyperVoted(&bind.CallOpts{}, keyperAddress) + if err != nil { + return errors.Wrap(err, "failed to query eon key publisher contract if keyper has already voted") + } + if hasAlreadyVoted { + log.Info(). + Uint64("keyper-set-index", keyperSetIndex). + Str("keyper-address", keyperAddress.Hex()). + Hex("key", key). + Msg("not publishing eon key as keyper has already voted") + return nil + } + isAlreadyConfirmed, err := p.contract.EonKeyConfirmed(&bind.CallOpts{}, key) + if err != nil { + return errors.Wrap(err, "failed to query eon key publisher contract if eon key is confirmed") + } + if isAlreadyConfirmed { + log.Info(). + Uint64("keyper-set-index", keyperSetIndex). + Hex("key", key). + Msg("not publishing eon key as it is already confirmed") + return nil + } + + chainID, err := p.client.ChainID(ctx) + if err != nil { + return errors.Wrap(err, "failed to get chain ID") + } + opts, err := bind.NewKeyedTransactorWithChainID(p.privateKey, chainID) + if err != nil { + return errors.Wrap(err, "failed to construct tx opts") + } + tx, err := p.contract.PublishEonKey(opts, key, keyperIndex) + if err != nil { + return errors.Wrap(err, "failed to send publish eon key tx") + } + log.Info(). + Uint64("keyper-set-index", keyperSetIndex). + Hex("key", key). + Hex("tx-hash", tx.Hash().Bytes()). + Msg("eon key publish tx sent") + receipt, err := bind.WaitMined(ctx, p.client, tx) + if err != nil { + log.Error().Err(err).Msg("error waiting for eon key publish tx to be mined") + return err + } + if receipt.Status != types.ReceiptStatusSuccessful { + log.Error(). + Hex("tx-hash", tx.Hash().Bytes()). + Interface("receipt", receipt). + Msg("eon key publish tx failed") + return errors.New("eon key publish tx failed") + } + log.Info(). + Uint64("keyper-set-index", keyperSetIndex). + Hex("key", key). + Hex("tx-hash", tx.Hash().Bytes()). + Msg("successfully published eon key") + return nil +} diff --git a/rolling-shutter/gnosiskeyperwatcher/blocks.go b/rolling-shutter/gnosiskeyperwatcher/blocks.go new file mode 100644 index 000000000..66ca62a41 --- /dev/null +++ b/rolling-shutter/gnosiskeyperwatcher/blocks.go @@ -0,0 +1,70 @@ +package gnosiskeyperwatcher + +import ( + "context" + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/rs/zerolog/log" + + keyper "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/gnosis" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" +) + +type BlocksWatcher struct { + config *keyper.Config + blocksChannel chan *BlockReceivedEvent +} + +type BlockReceivedEvent struct { + Header *types.Header + Time time.Time +} + +func NewBlocksWatcher(config *keyper.Config, blocksChannel chan *BlockReceivedEvent) *BlocksWatcher { + return &BlocksWatcher{ + config: config, + blocksChannel: blocksChannel, + } +} + +func (w *BlocksWatcher) Start(ctx context.Context, runner service.Runner) error { + runner.Go(func() error { + ethClient, err := ethclient.Dial(w.config.Gnosis.Node.EthereumURL) + if err != nil { + return err + } + + newHeads := make(chan *types.Header) + sub, err := ethClient.SubscribeNewHead(ctx, newHeads) + if err != nil { + return err + } + defer sub.Unsubscribe() + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case head := <-newHeads: + w.logNewHead(head) + ev := &BlockReceivedEvent{ + Header: head, + Time: time.Now(), + } + w.blocksChannel <- ev + case err := <-sub.Err(): + return err + } + } + }) + return nil +} + +func (w *BlocksWatcher) logNewHead(head *types.Header) { + log.Info(). + Int64("number", head.Number.Int64()). + Hex("hash", head.Hash().Bytes()). + Msg("new head") +} diff --git a/rolling-shutter/gnosiskeyperwatcher/keys.go b/rolling-shutter/gnosiskeyperwatcher/keys.go new file mode 100644 index 000000000..0ea8eedb4 --- /dev/null +++ b/rolling-shutter/gnosiskeyperwatcher/keys.go @@ -0,0 +1,126 @@ +package gnosiskeyperwatcher + +import ( + "context" + "fmt" + "sync" + "time" + + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/rs/zerolog/log" + + keyper "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/gnosis" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" + "github.com/shutter-network/rolling-shutter/rolling-shutter/p2p" + "github.com/shutter-network/rolling-shutter/rolling-shutter/p2pmsg" +) + +type KeysWatcher struct { + config *keyper.Config + blocksChannel chan *BlockReceivedEvent + + recentBlocksMux sync.Mutex + recentBlocks map[uint64]*BlockReceivedEvent + mostRecentBlock uint64 +} + +func NewKeysWatcher(config *keyper.Config, blocksChannel chan *BlockReceivedEvent) *KeysWatcher { + return &KeysWatcher{ + config: config, + blocksChannel: blocksChannel, + recentBlocksMux: sync.Mutex{}, + recentBlocks: make(map[uint64]*BlockReceivedEvent), + mostRecentBlock: 0, + } +} + +func (w *KeysWatcher) Start(ctx context.Context, runner service.Runner) error { + p2pService, err := p2p.New(w.config.P2P) + if err != nil { + return err + } + p2pService.AddMessageHandler(w) + + runner.Go(func() error { return w.insertBlocks(ctx) }) + + return runner.StartService(p2pService) +} + +func (w *KeysWatcher) MessagePrototypes() []p2pmsg.Message { + return []p2pmsg.Message{ + &p2pmsg.DecryptionKeys{}, + } +} + +func (w *KeysWatcher) ValidateMessage(_ context.Context, _ p2pmsg.Message) (pubsub.ValidationResult, error) { + return pubsub.ValidationAccept, nil +} + +func (w *KeysWatcher) HandleMessage(_ context.Context, msgUntyped p2pmsg.Message) ([]p2pmsg.Message, error) { + t := time.Now() + msg := msgUntyped.(*p2pmsg.DecryptionKeys) + extra := msg.Extra.(*p2pmsg.DecryptionKeys_Gnosis).Gnosis + + ev, ok := w.getRecentBlock(extra.Slot) + if !ok { + log.Warn(). + Uint64("keys-block", extra.Slot). + Uint64("most-recent-block", w.mostRecentBlock). + Msg("received keys for unknown block") + return []p2pmsg.Message{}, nil + } + + dt := t.Sub(ev.Time) + log.Info(). + Uint64("block", extra.Slot). + Int("num-keys", len(msg.Keys)). + Str("latency", fmt.Sprintf("%.2fs", dt.Seconds())). + Msg("new keys") + return []p2pmsg.Message{}, nil +} + +func (w *KeysWatcher) insertBlocks(ctx context.Context) error { + for { + select { + case <-ctx.Done(): + return ctx.Err() + case ev, ok := <-w.blocksChannel: + if !ok { + return nil + } + w.insertBlock(ev) + w.clearOldBlocks(ev) + } + } +} + +func (w *KeysWatcher) insertBlock(ev *BlockReceivedEvent) { + w.recentBlocksMux.Lock() + defer w.recentBlocksMux.Unlock() + w.recentBlocks[ev.Header.Number.Uint64()] = ev + if ev.Header.Number.Uint64() > w.mostRecentBlock { + w.mostRecentBlock = ev.Header.Number.Uint64() + } +} + +func (w *KeysWatcher) clearOldBlocks(latestEv *BlockReceivedEvent) { + w.recentBlocksMux.Lock() + defer w.recentBlocksMux.Unlock() + + tooOld := []uint64{} + for block := range w.recentBlocks { + if block < latestEv.Header.Number.Uint64()-100 { + tooOld = append(tooOld, block) + } + } + for _, block := range tooOld { + delete(w.recentBlocks, block) + } +} + +func (w *KeysWatcher) getRecentBlock(blockNumber uint64) (*BlockReceivedEvent, bool) { + w.recentBlocksMux.Lock() + defer w.recentBlocksMux.Unlock() + ev, ok := w.recentBlocks[blockNumber] + return ev, ok +} diff --git a/rolling-shutter/gnosiskeyperwatcher/watcher.go b/rolling-shutter/gnosiskeyperwatcher/watcher.go new file mode 100644 index 000000000..58f09e028 --- /dev/null +++ b/rolling-shutter/gnosiskeyperwatcher/watcher.go @@ -0,0 +1,25 @@ +package gnosiskeyperwatcher + +import ( + "context" + + keyper "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/gnosis" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" +) + +type Watcher struct { + config *keyper.Config +} + +func New(config *keyper.Config) *Watcher { + return &Watcher{ + config: config, + } +} + +func (w *Watcher) Start(_ context.Context, runner service.Runner) error { + blocksChannel := make(chan *BlockReceivedEvent) + blocksWatcher := NewBlocksWatcher(w.config, blocksChannel) + keysWatcher := NewKeysWatcher(w.config, blocksChannel) + return runner.StartService(blocksWatcher, keysWatcher) +} diff --git a/rolling-shutter/go.mod b/rolling-shutter/go.mod index b8c5f1801..6b25fa818 100644 --- a/rolling-shutter/go.mod +++ b/rolling-shutter/go.mod @@ -1,19 +1,20 @@ module github.com/shutter-network/rolling-shutter/rolling-shutter -go 1.21 +go 1.21.4 -toolchain go1.21.4 +//replace github.com/ethereum/go-ethereum v1.13.5 => ../../shop-geth/ require ( github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 github.com/benbjohnson/clock v1.3.5 github.com/bitwurx/jrpc2 v0.0.0-20220302204700-52c6dbbeb536 github.com/deepmap/oapi-codegen v1.9.1 - github.com/ethereum/go-ethereum v1.13.4 + github.com/ethereum/go-ethereum v1.13.11 + github.com/ferranbt/fastssz v0.1.3 github.com/getkin/kin-openapi v0.87.0 github.com/go-chi/chi/v5 v5.0.10 github.com/google/go-cmp v0.6.0 - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.4.0 github.com/hashicorp/go-multierror v1.1.1 github.com/ipfs/go-log/v2 v2.5.1 github.com/jackc/pgconn v1.14.1 @@ -25,16 +26,20 @@ require ( github.com/libp2p/go-libp2p-pubsub v0.10.0 github.com/mitchellh/mapstructure v1.5.0 github.com/multiformats/go-multiaddr v0.12.0 - github.com/pelletier/go-toml/v2 v2.0.9 + github.com/pelletier/go-toml/v2 v2.1.0 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.16.0 + github.com/prometheus/client_golang v1.17.0 github.com/rs/zerolog v1.28.0 - github.com/shutter-network/shutter/shlib v0.1.13 + github.com/shutter-network/gnosh-contracts v0.2.0 + github.com/shutter-network/shop-contracts v0.0.0-20240407151512-08ef5d8355b6 + github.com/shutter-network/shutter/shlib v0.1.18 github.com/shutter-network/txtypes v0.1.0 github.com/spf13/afero v1.8.2 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.13.0 + github.com/stretchr/testify v1.8.4 + github.com/supranational/blst v0.3.11 github.com/tendermint/go-amino v0.16.0 github.com/tendermint/tendermint v0.37.0-rc2 go.opentelemetry.io/otel v1.21.0 @@ -43,8 +48,9 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.21.0 go.opentelemetry.io/otel/trace v1.21.0 go.opentelemetry.io/proto/otlp v1.0.0 - golang.org/x/crypto v0.14.0 - golang.org/x/sync v0.4.0 + golang.org/x/crypto v0.17.0 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/sync v0.5.0 google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v3 v3.0.1 gotest.tools v2.2.0+incompatible @@ -53,20 +59,28 @@ require ( require ( github.com/DataDog/zstd v1.5.2 // indirect - github.com/VictoriaMetrics/fastcache v1.6.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/VictoriaMetrics/fastcache v1.12.1 // indirect + github.com/allegro/bigcache v1.2.1 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cockroachdb/errors v1.9.1 // indirect + github.com/cockroachdb/errors v1.11.1 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 // indirect - github.com/cockroachdb/redact v1.1.3 // indirect + github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 // indirect + github.com/cockroachdb/redact v1.1.5 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cosmos/gogoproto v1.4.1 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect + github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/creachadair/taskgroup v0.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect @@ -78,11 +92,13 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/elastic/gosigar v0.14.2 // indirect + github.com/ethereum/c-kzg-4844 v0.4.0 // indirect github.com/fjl/memsize v0.0.1 // indirect github.com/flynn/noise v1.0.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect + github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-kit/kit v0.12.0 // indirect @@ -90,10 +106,9 @@ require ( github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/swag v0.22.4 // indirect - github.com/go-stack/stack v1.8.1 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gofrs/flock v0.8.1 // indirect @@ -101,6 +116,7 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/btree v1.0.1 // indirect + github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect @@ -113,7 +129,7 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/holiman/uint256 v1.2.3 // indirect + github.com/holiman/uint256 v1.2.4 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/ipfs/boxo v0.10.0 // indirect @@ -165,6 +181,7 @@ require ( github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/minio/highwayhash v1.0.2 // indirect github.com/minio/sha256-simd v1.0.1 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect @@ -177,13 +194,15 @@ require ( github.com/multiformats/go-varint v0.0.7 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onsi/ginkgo/v2 v2.13.0 // indirect + github.com/onsi/gomega v1.30.0 // indirect github.com/opencontainers/runtime-spec v1.1.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/petermattis/goid v0.0.0-20230808133559-b036b712a89b // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect - github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect github.com/quic-go/qpack v0.4.0 // indirect @@ -192,9 +211,9 @@ require ( github.com/quic-go/webtransport-go v0.6.0 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - github.com/rivo/uniseg v0.2.0 // indirect + github.com/rivo/uniseg v0.4.3 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect - github.com/rs/cors v1.8.3 // indirect + github.com/rs/cors v1.9.0 // indirect github.com/rs/xid v1.4.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect @@ -202,7 +221,7 @@ require ( github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect - github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect github.com/tendermint/tm-db v0.6.7 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect @@ -216,21 +235,19 @@ require ( go.uber.org/mock v0.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect - golang.org/x/mod v0.13.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.14.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/tools v0.14.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.18.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.4.0 // indirect + golang.org/x/tools v0.15.0 // indirect gonum.org/v1/gonum v0.13.0 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/grpc v1.56.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect lukechampine.com/blake3 v1.2.1 // indirect + rsc.io/tmplfunc v0.0.3 // indirect ) replace github.com/bitwurx/jrpc2 => github.com/ulope/jrpc2 v0.0.0-20230706135348-a95cf3d96bd2 - -replace github.com/ethereum/go-ethereum => github.com/ethereum/go-ethereum v1.12.0 diff --git a/rolling-shutter/go.sum b/rolling-shutter/go.sum index 239a3f656..ff321cb8a 100644 --- a/rolling-shutter/go.sum +++ b/rolling-shutter/go.sum @@ -45,38 +45,32 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYsMyFh9qoE= github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo= -github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= -github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= -github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= +github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= +github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= 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/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= github.com/adlio/schema v1.3.3/go.mod h1:1EsRssiv9/Ce2CMzq5DoL7RiMshhuigQxrR4DMV9fHg= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= +github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= @@ -84,6 +78,8 @@ github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 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.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= @@ -100,7 +96,6 @@ github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -113,18 +108,22 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= -github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= -github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= -github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= +github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= -github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= -github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= -github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593/go.mod h1:6hk1eMY/u5t+Cf18q5lFMUA1Rc+Sm5I6Ra1QuPyxXCo= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= @@ -152,6 +151,10 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM= github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -172,7 +175,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etly github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/deepmap/oapi-codegen v1.9.1 h1:yHmEnA7jSTUMQgV+uN02WpZtwHnz2CBW3mZRIxr1vtI= github.com/deepmap/oapi-codegen v1.9.1/go.mod h1:PLqNAhdedP8ttRpBBkzLKU3bp+Fpy+tTgeAMlztR2cw= -github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= @@ -189,7 +191,6 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4= github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= @@ -198,19 +199,19 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/ethereum/go-ethereum v1.12.0 h1:bdnhLPtqETd4m3mS8BGMNvBTf36bO5bx/hxE2zljOa0= -github.com/ethereum/go-ethereum v1.12.0/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/go-ethereum v1.13.11 h1:b51Dsm+rEg7anFRUMGB8hODXHvNfcRKzz9vcj8wSdUs= +github.com/ethereum/go-ethereum v1.13.11/go.mod h1:gFtlVORuUcT+UUIcJ/veCNjkuOSujCi338uSHJrYAew= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= -github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/ferranbt/fastssz v0.1.3 h1:ZI+z3JH05h4kgmFXdHuR1aWYsgrg7o+Fw7/NCzM16Mo= +github.com/ferranbt/fastssz v0.1.3/go.mod h1:0Y9TEd/9XuFlh7mskMPfXiI2Dkw4Ddg9EyXt1W7MRvE= github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ= github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -224,24 +225,22 @@ github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0X github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= github.com/getkin/kin-openapi v0.87.0 h1:eeb0WBIgRiXra7ZY0Vo+jWloqvaF2kNEaxAyb+39N+E= github.com/getkin/kin-openapi v0.87.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= -github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= @@ -264,9 +263,9 @@ github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/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-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/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-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -282,14 +281,10 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -300,18 +295,14 @@ github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14j github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= -github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -350,7 +341,6 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= -github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= @@ -372,8 +362,8 @@ github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+u github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64= -github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8 h1:Ep/joEub9YwcjRY6ND3+Y/w0ncE540RtGatVhtZL0/Q= +github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8/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= @@ -391,12 +381,14 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0= github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -407,7 +399,6 @@ github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRid github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -423,7 +414,6 @@ github.com/hashicorp/go-bexpr v0.1.11 h1:6DqdA/KBjurGby9yTY0bmkathya0lfwF2SeuubC github.com/hashicorp/go-bexpr v0.1.11/go.mod h1:f03lAo0duBlDIUMGCuad8oLcgejw4m7U+N8T+6Kz1AE= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= @@ -432,17 +422,17 @@ github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvH github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZKHF9GxxWKDJGj8I0IqOUol//sw= +github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= -github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= -github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -463,11 +453,6 @@ github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= github.com/ipld/go-ipld-prime v0.20.0 h1:Ud3VwE9ClxpO2LkCYP7vWPc0Fo+dYdYzgxUJZ3uRG4g= github.com/ipld/go-ipld-prime v0.20.0/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M= -github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= -github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= -github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= -github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= -github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -539,21 +524,12 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= -github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= -github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= -github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= -github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 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.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -574,14 +550,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/echo/v4 v4.6.3/go.mod h1:Hk5OiHj0kDqmFq7aHe7eDqI7CUhuCrfpupQtLGGLm7A= github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= @@ -642,17 +618,13 @@ github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8 github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 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.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 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= @@ -661,13 +633,10 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= @@ -693,12 +662,14 @@ 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.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= 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/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= 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= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= @@ -726,27 +697,27 @@ github.com/multiformats/go-multistream v0.5.0/go.mod h1:n6tMZiwiP2wUsR8DgfDWw1dy github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= 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/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 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/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= @@ -766,8 +737,8 @@ github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhM github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= -github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20230808133559-b036b712a89b h1:vab8deKC4QoIfm9fJM59iuNz1ELGsuLoYYpiF+pHiG8= github.com/petermattis/goid v0.0.0-20230808133559-b036b712a89b/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= @@ -783,12 +754,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN 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 v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= @@ -807,17 +778,17 @@ github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtB github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= -github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= +github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -830,11 +801,9 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= @@ -864,8 +833,12 @@ 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/shutter-network/shutter/shlib v0.1.13 h1:9YloDJBdhFAKm2GMg4gBNeaJ+Mw9Qzeh5Kz9A2ayp1E= -github.com/shutter-network/shutter/shlib v0.1.13/go.mod h1:RlYNZjx+pfKAi0arH+jfdlxG4kQ75UFzDfVjgCVYaUw= +github.com/shutter-network/gnosh-contracts v0.2.0 h1:qH3gAhlh5VZzvJcbi044lxFWQ+MAR9GevKKUirWxSlU= +github.com/shutter-network/gnosh-contracts v0.2.0/go.mod h1:QB0d64ybbVFKMrLjrc1tldri87KNjTmKQjhk9jaso2E= +github.com/shutter-network/shop-contracts v0.0.0-20240407151512-08ef5d8355b6 h1:m6Ti1/IH+GBTtGqyAX3xbh+ruUKvC+m+/uzYDUa+JDQ= +github.com/shutter-network/shop-contracts v0.0.0-20240407151512-08ef5d8355b6/go.mod h1:LEWXLRruvxq9fe2oKtJI3xfzbauhfWTjOczHN61RU+4= +github.com/shutter-network/shutter/shlib v0.1.18 h1:ei1EWEavnlkwbX51aGKtgt7NydY0IPNV35J525vAfeo= +github.com/shutter-network/shutter/shlib v0.1.18/go.mod h1:RlYNZjx+pfKAi0arH+jfdlxG4kQ75UFzDfVjgCVYaUw= github.com/shutter-network/txtypes v0.1.0 h1:QqdiiiB9AiBCSJ/ke6z1ZoDGfu2+1Lgpz5vHzVN4FKc= github.com/shutter-network/txtypes v0.1.0/go.mod h1:gaLQWfEFH+3q5CmM3kkSgS+NspclEs8SCnj8QAozsaI= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -873,10 +846,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 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.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= @@ -917,14 +888,17 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= +github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= @@ -938,7 +912,6 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -946,36 +919,27 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= github.com/ulope/jrpc2 v0.0.0-20230706135348-a95cf3d96bd2 h1:rk0z/6CEJbstiHqv8+4ZIMv4Sm2zBZ5v5C56P8JXd+I= github.com/ulope/jrpc2 v0.0.0-20230706135348-a95cf3d96bd2/go.mod h1:bzOCUO4YLqjPZbPM4jeZzu/WIEauP/ouNWLRysNQdc0= +github.com/umbracle/gohashtree v0.0.2-alpha.0.20230207094856-5b775a815c10 h1:CQh33pStIp/E30b7TxDlXfM0145bn2e8boI30IxAhTg= +github.com/umbracle/gohashtree v0.0.2-alpha.0.20230207094856-5b775a815c10/go.mod h1:x/Pa0FF5Te9kdrlZKJK82YmAkvL8+f989USgz6Jiw7M= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 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.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q= -github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1050,10 +1014,8 @@ golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaE golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= @@ -1066,8 +1028,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1078,8 +1040,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1094,7 +1056,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -1107,19 +1068,17 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20181220203305-927f97764cc3/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-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1128,7 +1087,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1143,7 +1101,6 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -1153,12 +1110,14 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1183,8 +1142,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -1227,22 +1186,19 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1250,19 +1206,19 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/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-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/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= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1273,31 +1229,28 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY= +golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -1344,22 +1297,23 @@ golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4X golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= @@ -1393,7 +1347,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -1434,10 +1387,8 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -1457,7 +1408,6 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1482,18 +1432,12 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1503,7 +1447,6 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/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-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= @@ -1526,5 +1469,7 @@ lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/rolling-shutter/keyper/database/keyper.sqlc.gen.go b/rolling-shutter/keyper/database/keyper.sqlc.gen.go index 73b43b669..60d1b6c72 100644 --- a/rolling-shutter/keyper/database/keyper.sqlc.gen.go +++ b/rolling-shutter/keyper/database/keyper.sqlc.gen.go @@ -143,6 +143,36 @@ func (q *Queries) ExistsDecryptionKeyShare(ctx context.Context, arg ExistsDecryp return exists, err } +const getAllDKGResults = `-- name: GetAllDKGResults :many +SELECT eon, success, error, pure_result FROM dkg_result +ORDER BY eon ASC +` + +func (q *Queries) GetAllDKGResults(ctx context.Context) ([]DkgResult, error) { + rows, err := q.db.Query(ctx, getAllDKGResults) + if err != nil { + return nil, err + } + defer rows.Close() + var items []DkgResult + for rows.Next() { + var i DkgResult + if err := rows.Scan( + &i.Eon, + &i.Success, + &i.Error, + &i.PureResult, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getAllEons = `-- name: GetAllEons :many SELECT eon, height, activation_block_number, keyper_config_index FROM eons ORDER BY eon ` @@ -305,6 +335,23 @@ func (q *Queries) GetDKGResultForBlockNumber(ctx context.Context, blockNumber in return i, err } +const getDKGResultForKeyperConfigIndex = `-- name: GetDKGResultForKeyperConfigIndex :one +SELECT eon, success, error, pure_result FROM dkg_result +WHERE eon = (SELECT max(eon) FROM eons WHERE keyper_config_index = $1) +` + +func (q *Queries) GetDKGResultForKeyperConfigIndex(ctx context.Context, keyperConfigIndex int64) (DkgResult, error) { + row := q.db.QueryRow(ctx, getDKGResultForKeyperConfigIndex, keyperConfigIndex) + var i DkgResult + err := row.Scan( + &i.Eon, + &i.Success, + &i.Error, + &i.PureResult, + ) + return i, err +} + const getDecryptionKey = `-- name: GetDecryptionKey :one SELECT eon, epoch_id, decryption_key FROM decryption_key WHERE eon = $1 AND epoch_id = $2 @@ -541,6 +588,7 @@ func (q *Queries) InsertDecryptionKey(ctx context.Context, arg InsertDecryptionK const insertDecryptionKeyShare = `-- name: InsertDecryptionKeyShare :exec INSERT INTO decryption_key_share (eon, epoch_id, keyper_index, decryption_key_share) VALUES ($1, $2, $3, $4) +ON CONFLICT DO NOTHING ` type InsertDecryptionKeyShareParams struct { diff --git a/rolling-shutter/keyper/database/sql/queries/keyper.sql b/rolling-shutter/keyper/database/sql/queries/keyper.sql index 2cd066ab0..817068b96 100644 --- a/rolling-shutter/keyper/database/sql/queries/keyper.sql +++ b/rolling-shutter/keyper/database/sql/queries/keyper.sql @@ -16,7 +16,8 @@ SELECT EXISTS ( -- name: InsertDecryptionKeyShare :exec INSERT INTO decryption_key_share (eon, epoch_id, keyper_index, decryption_key_share) -VALUES ($1, $2, $3, $4); +VALUES ($1, $2, $3, $4) +ON CONFLICT DO NOTHING; -- name: SelectDecryptionKeyShares :many SELECT * FROM decryption_key_share @@ -168,6 +169,14 @@ WHERE eon = (SELECT eon FROM eons WHERE activation_block_number <= sqlc.arg(bloc ORDER BY activation_block_number DESC, height DESC LIMIT 1); +-- name: GetDKGResultForKeyperConfigIndex :one +SELECT * FROM dkg_result +WHERE eon = (SELECT max(eon) FROM eons WHERE keyper_config_index = $1); + +-- name: GetAllDKGResults :many +SELECT * FROM dkg_result +ORDER BY eon ASC; + -- name: InsertEonPublicKey :exec INSERT INTO outgoing_eon_keys (eon_public_key, eon) VALUES ($1, $2); diff --git a/rolling-shutter/keyper/epochkghandler/key.go b/rolling-shutter/keyper/epochkghandler/key.go index 5956a41c5..b25188a3a 100644 --- a/rolling-shutter/keyper/epochkghandler/key.go +++ b/rolling-shutter/keyper/epochkghandler/key.go @@ -43,7 +43,7 @@ func (handler *DecryptionKeyHandler) ValidateMessage(ctx context.Context, msg p2 return pubsub.ValidationReject, errors.Errorf("eon %d overflows int64", key.Eon) } - dkgResultDB, err := database.New(handler.dbpool).GetDKGResult(ctx, int64(key.Eon)) + dkgResultDB, err := database.New(handler.dbpool).GetDKGResultForKeyperConfigIndex(ctx, int64(key.Eon)) if err == pgx.ErrNoRows { return pubsub.ValidationReject, errors.Errorf("no DKG result found for eon %d", key.Eon) } diff --git a/rolling-shutter/keyper/epochkghandler/keyshare.go b/rolling-shutter/keyper/epochkghandler/keyshare.go index 1d260355d..a207298a4 100644 --- a/rolling-shutter/keyper/epochkghandler/keyshare.go +++ b/rolling-shutter/keyper/epochkghandler/keyshare.go @@ -46,7 +46,7 @@ func (handler *DecryptionKeyShareHandler) ValidateMessage(ctx context.Context, m return pubsub.ValidationReject, errors.Errorf("eon %d overflows int64", keyShare.Eon) } - dkgResultDB, err := database.New(handler.dbpool).GetDKGResult(ctx, int64(keyShare.Eon)) + dkgResultDB, err := database.New(handler.dbpool).GetDKGResultForKeyperConfigIndex(ctx, int64(keyShare.Eon)) if err == pgx.ErrNoRows { return pubsub.ValidationReject, errors.Errorf("no DKG result found for eon %d", keyShare.Eon) } @@ -122,13 +122,13 @@ func (handler *DecryptionKeyShareHandler) HandleMessage(ctx context.Context, m p } // fetch dkg result from db - dkgResultDB, err := db.GetDKGResult(ctx, int64(msg.Eon)) + dkgResultDB, err := db.GetDKGResultForKeyperConfigIndex(ctx, int64(msg.Eon)) if err != nil { return nil, errors.Wrapf(err, "failed to get dkg result for eon %d from db", msg.Eon) } if !dkgResultDB.Success { log.Info().Uint64("eon", msg.Eon). - Msg("ignoring decryption trigger: eon key generation failed") + Msg("ignoring decryption key share: eon key generation failed") return nil, nil } pureDKGResult, err := shdb.DecodePureDKGResult(dkgResultDB.PureResult) @@ -141,7 +141,12 @@ func (handler *DecryptionKeyShareHandler) HandleMessage(ctx context.Context, m p for _, share := range msg.GetShares() { identityPreimage := identitypreimage.IdentityPreimage(share.EpochID) - epochKG, err := handler.aggregateDecryptionKeySharesFromDB(ctx, pureDKGResult, identityPreimage) + epochKG, err := handler.aggregateDecryptionKeySharesFromDB( + ctx, + int64(msg.Eon), + pureDKGResult, + identityPreimage, + ) if err != nil { return nil, err } @@ -178,12 +183,13 @@ func (handler *DecryptionKeyShareHandler) HandleMessage(ctx context.Context, m p func (handler *DecryptionKeyShareHandler) aggregateDecryptionKeySharesFromDB( ctx context.Context, + keyperConfigIndex int64, pureDKGResult *puredkg.Result, identityPreimage identitypreimage.IdentityPreimage, ) (*epochkg.EpochKG, error) { db := database.New(handler.dbpool) shares, err := db.SelectDecryptionKeyShares(ctx, database.SelectDecryptionKeySharesParams{ - Eon: int64(pureDKGResult.Eon), + Eon: keyperConfigIndex, EpochID: identityPreimage.Bytes(), }) if err != nil { diff --git a/rolling-shutter/keyper/epochkghandler/sendkeyshare.go b/rolling-shutter/keyper/epochkghandler/sendkeyshare.go index e2c4d96c7..172857a96 100644 --- a/rolling-shutter/keyper/epochkghandler/sendkeyshare.go +++ b/rolling-shutter/keyper/epochkghandler/sendkeyshare.go @@ -45,6 +45,7 @@ var ( ErrSharesAlreadySent = errors.New("shares exist already") ) +//nolint:gocyclo func (ksh *KeyShareHandler) ConstructDecryptionKeyShares( ctx context.Context, eon database.Eon, @@ -121,7 +122,7 @@ func (ksh *KeyShareHandler) ConstructDecryptionKeyShares( }) } - eonUint, err := medley.Int64ToUint64Safe(eon.Eon) + keyperSetIndexUint, err := medley.Int64ToUint64Safe(eon.KeyperConfigIndex) if err != nil { return nil, err } @@ -131,7 +132,7 @@ func (ksh *KeyShareHandler) ConstructDecryptionKeyShares( } msg := &p2pmsg.DecryptionKeyShares{ InstanceID: ksh.InstanceID, - Eon: eonUint, + Eon: keyperSetIndexUint, KeyperIndex: keyperIndexUint, Shares: shares, } diff --git a/rolling-shutter/keyper/epochkghandler/trigger_test.go b/rolling-shutter/keyper/epochkghandler/trigger_test.go index a636b31b4..a07dc83eb 100644 --- a/rolling-shutter/keyper/epochkghandler/trigger_test.go +++ b/rolling-shutter/keyper/epochkghandler/trigger_test.go @@ -44,7 +44,7 @@ func TestHandleDecryptionTriggerIntegration(t *testing.T) { Messaging: messaging, Trigger: decrTrigChan, } - group := service.RunBackground( + group, cleanup := service.RunBackground( ctx, ksh, ) @@ -58,6 +58,7 @@ func TestHandleDecryptionTriggerIntegration(t *testing.T) { decrTrigChan <- broker.NewEvent(trig) close(decrTrigChan) err = group.Wait() + cleanup() assert.NilError(t, err) // send decryption key share when first trigger is received diff --git a/rolling-shutter/keyper/keyper.go b/rolling-shutter/keyper/keyper.go index 2af2a1723..680f669ce 100644 --- a/rolling-shutter/keyper/keyper.go +++ b/rolling-shutter/keyper/keyper.go @@ -166,7 +166,7 @@ func (kpr *KeyperCore) Start(ctx context.Context, runner service.Runner) error { func (kpr *KeyperCore) getServices() []service.Service { services := []service.Service{ kpr.messaging, - service.ServiceFn{Fn: kpr.operateShuttermint}, + service.Function{Func: kpr.operateShuttermint}, newEonPubKeyHandler(kpr), } keyTrigger := kpr.trigger @@ -265,7 +265,6 @@ func (kpr *KeyperCore) handleOnChainKeyperSetChanges( } else if err != nil { return err } - cq := obskeyper.New(tx) keyperSet, err := cq.GetKeyperSetByKeyperConfigIndex( ctx, @@ -335,7 +334,7 @@ func (kpr *KeyperCore) handleOnChainKeyperSetChanges( // TODO: we need a better block syncing mechanism! // Also this is doing too much work sequentially in one routine. -func (kpr *KeyperCore) operateShuttermint(ctx context.Context) error { +func (kpr *KeyperCore) operateShuttermint(ctx context.Context, _ service.Runner) error { for { syncBlockNumber, err := retry.FunctionCall(ctx, kpr.blockSyncClient.BlockNumber) if err != nil { diff --git a/rolling-shutter/keyper/kprapi/http.go b/rolling-shutter/keyper/kprapi/http.go index 1d54adfbd..df484804c 100644 --- a/rolling-shutter/keyper/kprapi/http.go +++ b/rolling-shutter/keyper/kprapi/http.go @@ -32,7 +32,7 @@ func (srv *Server) Ping(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("pong")) } -func (srv *Server) Shutdown(w http.ResponseWriter, _ *http.Request) { +func (srv *Server) Shutdown(_ http.ResponseWriter, _ *http.Request) { srv.shutdownSig <- struct{}{} // We still want to return here and thus return 200 to the caller after this. // Not immediately closing open connctions is taken care diff --git a/rolling-shutter/keyper/shutterevents/marshal.go b/rolling-shutter/keyper/shutterevents/marshal.go index 3baa8787c..1aefd341d 100644 --- a/rolling-shutter/keyper/shutterevents/marshal.go +++ b/rolling-shutter/keyper/shutterevents/marshal.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethcrypto "github.com/ethereum/go-ethereum/crypto" - bn256 "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" + "github.com/ethereum/go-ethereum/crypto/bls12381" "github.com/ethereum/go-ethereum/crypto/ecies" "github.com/pkg/errors" @@ -96,16 +96,18 @@ func decodePubkey(val string) (*ecdsa.PublicKey, error) { } func encodeGammas(gammas *shcrypto.Gammas) string { + g2 := bls12381.NewG2() var encoded []string if gammas != nil { for _, g := range *gammas { - encoded = append(encoded, hex.EncodeToString(g.Marshal())) + encoded = append(encoded, hex.EncodeToString(g2.ToBytes(g))) } } return strings.Join(encoded, ",") } func decodeGammas(eventValue string) (shcrypto.Gammas, error) { + g2 := bls12381.NewG2() parts := strings.Split(eventValue, ",") var res shcrypto.Gammas for _, p := range parts { @@ -113,11 +115,13 @@ func decodeGammas(eventValue string) (shcrypto.Gammas, error) { if err != nil { return shcrypto.Gammas{}, err } - g := new(bn256.G2) - _, err = g.Unmarshal(marshaledG2) + g, err := g2.FromBytes(marshaledG2) if err != nil { return shcrypto.Gammas{}, err } + if !g2.IsOnCurve(g) { + return shcrypto.Gammas{}, errors.Errorf("invalid gamma value %x", p) + } res = append(res, g) } return res, nil diff --git a/rolling-shutter/keyperimpl/gnosis/config.go b/rolling-shutter/keyperimpl/gnosis/config.go new file mode 100644 index 000000000..f650fdb85 --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/config.go @@ -0,0 +1,192 @@ +package gnosis + +import ( + "io" + "math" + + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/kprconfig" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/configuration" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/metricsserver" + "github.com/shutter-network/rolling-shutter/rolling-shutter/p2p" +) + +const ( + maxSecondsPerSlot = 60 * 60 + maxChainAge = 100 * 365 * 24 * 60 * 60 +) + +var ( + _ configuration.Config = &Config{} + _ configuration.Config = &GnosisConfig{} + _ configuration.Config = &GnosisContractsConfig{} +) + +func NewConfig() *Config { + c := &Config{} + c.Init() + return c +} + +func (c *Config) Init() { + c.P2P = p2p.NewConfig() + c.Gnosis = NewGnosisConfig() + c.Shuttermint = kprconfig.NewShuttermintConfig() + c.Metrics = metricsserver.NewConfig() +} + +type Config struct { + InstanceID uint64 `shconfig:",required"` + DatabaseURL string `shconfig:",required" comment:"If it's empty, we use the standard PG_ environment variables"` + BeaconAPIURL string `shconfig:",required"` + + HTTPEnabled bool + HTTPListenAddress string + + Gnosis *GnosisConfig + P2P *p2p.Config + Shuttermint *kprconfig.ShuttermintConfig + Metrics *metricsserver.MetricsConfig +} + +func (c *Config) Validate() error { + if c.Gnosis.SecondsPerSlot > maxSecondsPerSlot { + return errors.Errorf("seconds per slot is too big (%d > %d)", c.Gnosis.SecondsPerSlot, maxSecondsPerSlot) + } + maxGenesisSlotTime := uint64(math.MaxInt64 - maxChainAge) + if c.Gnosis.GenesisSlotTimestamp > maxGenesisSlotTime { + return errors.Errorf("genesis slot timestamp is too big (%d > %d)", c.Gnosis.GenesisSlotTimestamp, maxGenesisSlotTime) + } + return nil +} + +func (c *Config) Name() string { + return "gnosiskeyper" +} + +func (c *Config) SetDefaultValues() error { + c.HTTPEnabled = false + c.HTTPListenAddress = ":3000" + c.Gnosis.EncryptedGasLimit = 1_000_000 + c.Gnosis.MinGasPerTransaction = 21_000 + return nil +} + +func (c *Config) SetExampleValues() error { + err := c.SetDefaultValues() + if err != nil { + return err + } + c.InstanceID = 42 + c.DatabaseURL = "postgres://pguser:pgpassword@localhost:5432/shutter" + c.BeaconAPIURL = "http://localhost:5052" + + return nil +} + +func (c Config) TOMLWriteHeader(_ io.Writer) (int, error) { + return 0, nil +} + +func (c *Config) GetAddress() common.Address { + return c.Gnosis.Node.PrivateKey.EthereumAddress() +} + +type GnosisConfig struct { + Node *configuration.EthnodeConfig `shconfig:",required"` + Contracts *GnosisContractsConfig `shconfig:",required"` + EncryptedGasLimit uint64 `shconfig:",required"` + MinGasPerTransaction uint64 `shconfig:",required"` + SecondsPerSlot uint64 `shconfig:",required"` + SlotsPerEpoch uint64 `shconfig:",required"` + GenesisSlotTimestamp uint64 `shconfig:",required"` +} + +func NewGnosisConfig() *GnosisConfig { + c := &GnosisConfig{ + Node: configuration.NewEthnodeConfig(), + Contracts: NewGnosisContractsConfig(), + EncryptedGasLimit: 0, + MinGasPerTransaction: 0, + SecondsPerSlot: 0, + SlotsPerEpoch: 0, + GenesisSlotTimestamp: 0, + } + c.Init() + return c +} + +func (c *GnosisConfig) Init() { + c.Node.Init() + c.Contracts.Init() +} + +func (c *GnosisConfig) Name() string { + return "gnosis" +} + +func (c *GnosisConfig) Validate() error { + if c.SecondsPerSlot == 0 { + return errors.Errorf("seconds per slot must not be zero") + } + return nil +} + +func (c *GnosisConfig) SetDefaultValues() error { + return nil +} + +func (c *GnosisConfig) SetExampleValues() error { + c.EncryptedGasLimit = 1_000_000 + c.MinGasPerTransaction = 21_000 + c.SecondsPerSlot = 5 + c.SlotsPerEpoch = 16 + c.GenesisSlotTimestamp = 1665410700 + return nil +} + +func (c *GnosisConfig) TOMLWriteHeader(_ io.Writer) (int, error) { + return 0, nil +} + +type GnosisContractsConfig struct { + KeyperSetManager common.Address `shconfig:",required"` + KeyBroadcastContract common.Address `shconfig:",required"` + EonKeyPublish common.Address `shconfig:",required"` + Sequencer common.Address `shconfig:",required"` + ValidatorRegistry common.Address `shconfig:",required"` +} + +func NewGnosisContractsConfig() *GnosisContractsConfig { + return &GnosisContractsConfig{ + KeyperSetManager: common.Address{}, + KeyBroadcastContract: common.Address{}, + EonKeyPublish: common.Address{}, + Sequencer: common.Address{}, + ValidatorRegistry: common.Address{}, + } +} + +func (c *GnosisContractsConfig) Init() {} + +func (c *GnosisContractsConfig) Name() string { + return "gnosiscontracts" +} + +func (c *GnosisContractsConfig) Validate() error { + return nil +} + +func (c *GnosisContractsConfig) SetDefaultValues() error { + return nil +} + +func (c *GnosisContractsConfig) SetExampleValues() error { + return nil +} + +func (c *GnosisContractsConfig) TOMLWriteHeader(_ io.Writer) (int, error) { + return 0, nil +} diff --git a/rolling-shutter/keyperimpl/gnosis/database/db.sqlc.gen.go b/rolling-shutter/keyperimpl/gnosis/database/db.sqlc.gen.go new file mode 100644 index 000000000..06cf4d902 --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/database/db.sqlc.gen.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.22.0 + +package database + +import ( + "context" + + "github.com/jackc/pgconn" + "github.com/jackc/pgx/v4" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/rolling-shutter/keyperimpl/gnosis/database/definition.go b/rolling-shutter/keyperimpl/gnosis/database/definition.go new file mode 100644 index 000000000..e83d8021f --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/database/definition.go @@ -0,0 +1,29 @@ +package database + +import ( + "embed" + + "github.com/rs/zerolog/log" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/database" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/db" +) + +//go:generate sqlc generate --file sql/sqlc.yaml + +//go:embed sql +var files embed.FS + +var Definition db.Definition + +func init() { + def, err := db.NewSQLCDefinition(files, "sql/", "gnosiskeyper", 1) + if err != nil { + log.Fatal().Err(err).Msg("failed to initialize DB metadata") + } + Definition = db.NewAggregateDefinition( + "gnosiskeyper", + def, + database.Definition, + ) +} diff --git a/rolling-shutter/keyperimpl/gnosis/database/gnosiskeyper.sqlc.gen.go b/rolling-shutter/keyperimpl/gnosis/database/gnosiskeyper.sqlc.gen.go new file mode 100644 index 000000000..f14d0b4ac --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/database/gnosiskeyper.sqlc.gen.go @@ -0,0 +1,444 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.22.0 +// source: gnosiskeyper.sql + +package database + +import ( + "context" + + "github.com/jackc/pgconn" +) + +const getCurrentDecryptionTrigger = `-- name: GetCurrentDecryptionTrigger :one +SELECT eon, slot, tx_pointer, identities_hash FROM current_decryption_trigger +WHERE eon = $1 +` + +func (q *Queries) GetCurrentDecryptionTrigger(ctx context.Context, eon int64) (CurrentDecryptionTrigger, error) { + row := q.db.QueryRow(ctx, getCurrentDecryptionTrigger, eon) + var i CurrentDecryptionTrigger + err := row.Scan( + &i.Eon, + &i.Slot, + &i.TxPointer, + &i.IdentitiesHash, + ) + return i, err +} + +const getSlotDecryptionSignatures = `-- name: GetSlotDecryptionSignatures :many +SELECT eon, slot, keyper_index, tx_pointer, identities_hash, signature FROM slot_decryption_signatures +WHERE eon = $1 AND slot = $2 AND tx_pointer = $3 AND identities_hash = $4 +ORDER BY keyper_index ASC +LIMIT $5 +` + +type GetSlotDecryptionSignaturesParams struct { + Eon int64 + Slot int64 + TxPointer int64 + IdentitiesHash []byte + Limit int32 +} + +func (q *Queries) GetSlotDecryptionSignatures(ctx context.Context, arg GetSlotDecryptionSignaturesParams) ([]SlotDecryptionSignature, error) { + rows, err := q.db.Query(ctx, getSlotDecryptionSignatures, + arg.Eon, + arg.Slot, + arg.TxPointer, + arg.IdentitiesHash, + arg.Limit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []SlotDecryptionSignature + for rows.Next() { + var i SlotDecryptionSignature + if err := rows.Scan( + &i.Eon, + &i.Slot, + &i.KeyperIndex, + &i.TxPointer, + &i.IdentitiesHash, + &i.Signature, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getTransactionSubmittedEventCount = `-- name: GetTransactionSubmittedEventCount :one +SELECT event_count FROM transaction_submitted_event_count +WHERE eon = $1 +LIMIT 1 +` + +func (q *Queries) GetTransactionSubmittedEventCount(ctx context.Context, eon int64) (int64, error) { + row := q.db.QueryRow(ctx, getTransactionSubmittedEventCount, eon) + var event_count int64 + err := row.Scan(&event_count) + return event_count, err +} + +const getTransactionSubmittedEvents = `-- name: GetTransactionSubmittedEvents :many +SELECT index, block_number, block_hash, tx_index, log_index, eon, identity_prefix, sender, gas_limit FROM transaction_submitted_event +WHERE eon = $1 AND index >= $2 +ORDER BY index ASC +LIMIT $3 +` + +type GetTransactionSubmittedEventsParams struct { + Eon int64 + Index int64 + Limit int32 +} + +func (q *Queries) GetTransactionSubmittedEvents(ctx context.Context, arg GetTransactionSubmittedEventsParams) ([]TransactionSubmittedEvent, error) { + rows, err := q.db.Query(ctx, getTransactionSubmittedEvents, arg.Eon, arg.Index, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []TransactionSubmittedEvent + for rows.Next() { + var i TransactionSubmittedEvent + if err := rows.Scan( + &i.Index, + &i.BlockNumber, + &i.BlockHash, + &i.TxIndex, + &i.LogIndex, + &i.Eon, + &i.IdentityPrefix, + &i.Sender, + &i.GasLimit, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getTransactionSubmittedEventsSyncedUntil = `-- name: GetTransactionSubmittedEventsSyncedUntil :one +SELECT enforce_one_row, block_hash, block_number, slot FROM transaction_submitted_events_synced_until LIMIT 1 +` + +func (q *Queries) GetTransactionSubmittedEventsSyncedUntil(ctx context.Context) (TransactionSubmittedEventsSyncedUntil, error) { + row := q.db.QueryRow(ctx, getTransactionSubmittedEventsSyncedUntil) + var i TransactionSubmittedEventsSyncedUntil + err := row.Scan( + &i.EnforceOneRow, + &i.BlockHash, + &i.BlockNumber, + &i.Slot, + ) + return i, err +} + +const getTxPointer = `-- name: GetTxPointer :one +SELECT eon, slot, value FROM tx_pointer +WHERE eon = $1 +` + +func (q *Queries) GetTxPointer(ctx context.Context, eon int64) (TxPointer, error) { + row := q.db.QueryRow(ctx, getTxPointer, eon) + var i TxPointer + err := row.Scan(&i.Eon, &i.Slot, &i.Value) + return i, err +} + +const getValidatorRegistrationNonceBefore = `-- name: GetValidatorRegistrationNonceBefore :one +SELECT nonce FROM validator_registrations +WHERE validator_index = $1 AND block_number <= $2 AND tx_index <= $3 AND log_index <= $4 +ORDER BY block_number DESC, tx_index DESC, log_index DESC +LIMIT 1 +` + +type GetValidatorRegistrationNonceBeforeParams struct { + ValidatorIndex int64 + BlockNumber int64 + TxIndex int64 + LogIndex int64 +} + +func (q *Queries) GetValidatorRegistrationNonceBefore(ctx context.Context, arg GetValidatorRegistrationNonceBeforeParams) (int64, error) { + row := q.db.QueryRow(ctx, getValidatorRegistrationNonceBefore, + arg.ValidatorIndex, + arg.BlockNumber, + arg.TxIndex, + arg.LogIndex, + ) + var nonce int64 + err := row.Scan(&nonce) + return nonce, err +} + +const getValidatorRegistrationsSyncedUntil = `-- name: GetValidatorRegistrationsSyncedUntil :one +SELECT enforce_one_row, block_hash, block_number FROM validator_registrations_synced_until LIMIT 1 +` + +func (q *Queries) GetValidatorRegistrationsSyncedUntil(ctx context.Context) (ValidatorRegistrationsSyncedUntil, error) { + row := q.db.QueryRow(ctx, getValidatorRegistrationsSyncedUntil) + var i ValidatorRegistrationsSyncedUntil + err := row.Scan(&i.EnforceOneRow, &i.BlockHash, &i.BlockNumber) + return i, err +} + +const initTxPointer = `-- name: InitTxPointer :exec +INSERT INTO tx_pointer (eon, slot, value) +VALUES ($1, $2, 0) +ON CONFLICT DO NOTHING +` + +type InitTxPointerParams struct { + Eon int64 + Slot int64 +} + +func (q *Queries) InitTxPointer(ctx context.Context, arg InitTxPointerParams) error { + _, err := q.db.Exec(ctx, initTxPointer, arg.Eon, arg.Slot) + return err +} + +const insertSlotDecryptionSignature = `-- name: InsertSlotDecryptionSignature :exec +INSERT INTO slot_decryption_signatures (eon, slot, keyper_index, tx_pointer, identities_hash, signature) +VALUES ($1, $2, $3, $4, $5, $6) +` + +type InsertSlotDecryptionSignatureParams struct { + Eon int64 + Slot int64 + KeyperIndex int64 + TxPointer int64 + IdentitiesHash []byte + Signature []byte +} + +func (q *Queries) InsertSlotDecryptionSignature(ctx context.Context, arg InsertSlotDecryptionSignatureParams) error { + _, err := q.db.Exec(ctx, insertSlotDecryptionSignature, + arg.Eon, + arg.Slot, + arg.KeyperIndex, + arg.TxPointer, + arg.IdentitiesHash, + arg.Signature, + ) + return err +} + +const insertTransactionSubmittedEvent = `-- name: InsertTransactionSubmittedEvent :execresult +INSERT INTO transaction_submitted_event ( + index, + block_number, + block_hash, + tx_index, + log_index, + eon, + identity_prefix, + sender, + gas_limit +) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) +ON CONFLICT DO NOTHING +` + +type InsertTransactionSubmittedEventParams struct { + Index int64 + BlockNumber int64 + BlockHash []byte + TxIndex int64 + LogIndex int64 + Eon int64 + IdentityPrefix []byte + Sender string + GasLimit int64 +} + +func (q *Queries) InsertTransactionSubmittedEvent(ctx context.Context, arg InsertTransactionSubmittedEventParams) (pgconn.CommandTag, error) { + return q.db.Exec(ctx, insertTransactionSubmittedEvent, + arg.Index, + arg.BlockNumber, + arg.BlockHash, + arg.TxIndex, + arg.LogIndex, + arg.Eon, + arg.IdentityPrefix, + arg.Sender, + arg.GasLimit, + ) +} + +const insertValidatorRegistration = `-- name: InsertValidatorRegistration :exec +INSERT INTO validator_registrations ( + block_number, + block_hash, + tx_index, + log_index, + validator_index, + nonce, + is_registration +) VALUES ($1, $2, $3, $4, $5, $6, $7) +` + +type InsertValidatorRegistrationParams struct { + BlockNumber int64 + BlockHash []byte + TxIndex int64 + LogIndex int64 + ValidatorIndex int64 + Nonce int64 + IsRegistration bool +} + +func (q *Queries) InsertValidatorRegistration(ctx context.Context, arg InsertValidatorRegistrationParams) error { + _, err := q.db.Exec(ctx, insertValidatorRegistration, + arg.BlockNumber, + arg.BlockHash, + arg.TxIndex, + arg.LogIndex, + arg.ValidatorIndex, + arg.Nonce, + arg.IsRegistration, + ) + return err +} + +const isValidatorRegistered = `-- name: IsValidatorRegistered :one +SELECT is_registration FROM validator_registrations +WHERE validator_index = $1 AND block_number < $2 +ORDER BY block_number DESC, tx_index DESC, log_index DESC +LIMIT 1 +` + +type IsValidatorRegisteredParams struct { + ValidatorIndex int64 + BlockNumber int64 +} + +func (q *Queries) IsValidatorRegistered(ctx context.Context, arg IsValidatorRegisteredParams) (bool, error) { + row := q.db.QueryRow(ctx, isValidatorRegistered, arg.ValidatorIndex, arg.BlockNumber) + var is_registration bool + err := row.Scan(&is_registration) + return is_registration, err +} + +const setCurrentDecryptionTrigger = `-- name: SetCurrentDecryptionTrigger :exec +INSERT INTO current_decryption_trigger (eon, slot, tx_pointer, identities_hash) +VALUES ($1, $2, $3, $4) +ON CONFLICT (eon) DO UPDATE +SET slot = $2, tx_pointer = $3, identities_hash = $4 +` + +type SetCurrentDecryptionTriggerParams struct { + Eon int64 + Slot int64 + TxPointer int64 + IdentitiesHash []byte +} + +func (q *Queries) SetCurrentDecryptionTrigger(ctx context.Context, arg SetCurrentDecryptionTriggerParams) error { + _, err := q.db.Exec(ctx, setCurrentDecryptionTrigger, + arg.Eon, + arg.Slot, + arg.TxPointer, + arg.IdentitiesHash, + ) + return err +} + +const setTransactionSubmittedEventCount = `-- name: SetTransactionSubmittedEventCount :exec +INSERT INTO transaction_submitted_event_count (eon, event_count) +VALUES ($1, $2) +ON CONFLICT (eon) DO UPDATE +SET event_count = $2 +` + +type SetTransactionSubmittedEventCountParams struct { + Eon int64 + EventCount int64 +} + +func (q *Queries) SetTransactionSubmittedEventCount(ctx context.Context, arg SetTransactionSubmittedEventCountParams) error { + _, err := q.db.Exec(ctx, setTransactionSubmittedEventCount, arg.Eon, arg.EventCount) + return err +} + +const setTransactionSubmittedEventsSyncedUntil = `-- name: SetTransactionSubmittedEventsSyncedUntil :exec +INSERT INTO transaction_submitted_events_synced_until (block_hash, block_number, slot) VALUES ($1, $2, $3) +ON CONFLICT (enforce_one_row) DO UPDATE +SET block_hash = $1, block_number = $2, slot = $3 +` + +type SetTransactionSubmittedEventsSyncedUntilParams struct { + BlockHash []byte + BlockNumber int64 + Slot int64 +} + +func (q *Queries) SetTransactionSubmittedEventsSyncedUntil(ctx context.Context, arg SetTransactionSubmittedEventsSyncedUntilParams) error { + _, err := q.db.Exec(ctx, setTransactionSubmittedEventsSyncedUntil, arg.BlockHash, arg.BlockNumber, arg.Slot) + return err +} + +const setTxPointer = `-- name: SetTxPointer :exec +INSERT INTO tx_pointer (eon, slot, value) +VALUES ($1, $2, $3) +ON CONFLICT (eon) DO UPDATE +SET slot = $2, value = $3 +` + +type SetTxPointerParams struct { + Eon int64 + Slot int64 + Value int64 +} + +func (q *Queries) SetTxPointer(ctx context.Context, arg SetTxPointerParams) error { + _, err := q.db.Exec(ctx, setTxPointer, arg.Eon, arg.Slot, arg.Value) + return err +} + +const setTxPointerSlot = `-- name: SetTxPointerSlot :exec +UPDATE tx_pointer +SET slot = $2 +WHERE eon = $1 +` + +type SetTxPointerSlotParams struct { + Eon int64 + Slot int64 +} + +func (q *Queries) SetTxPointerSlot(ctx context.Context, arg SetTxPointerSlotParams) error { + _, err := q.db.Exec(ctx, setTxPointerSlot, arg.Eon, arg.Slot) + return err +} + +const setValidatorRegistrationsSyncedUntil = `-- name: SetValidatorRegistrationsSyncedUntil :exec +INSERT INTO validator_registrations_synced_until (block_hash, block_number) VALUES ($1, $2) +ON CONFLICT (enforce_one_row) DO UPDATE +SET block_hash = $1, block_number = $2 +` + +type SetValidatorRegistrationsSyncedUntilParams struct { + BlockHash []byte + BlockNumber int64 +} + +func (q *Queries) SetValidatorRegistrationsSyncedUntil(ctx context.Context, arg SetValidatorRegistrationsSyncedUntilParams) error { + _, err := q.db.Exec(ctx, setValidatorRegistrationsSyncedUntil, arg.BlockHash, arg.BlockNumber) + return err +} diff --git a/rolling-shutter/keyperimpl/gnosis/database/models.sqlc.gen.go b/rolling-shutter/keyperimpl/gnosis/database/models.sqlc.gen.go new file mode 100644 index 000000000..868cc4d5c --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/database/models.sqlc.gen.go @@ -0,0 +1,69 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.22.0 + +package database + +import () + +type CurrentDecryptionTrigger struct { + Eon int64 + Slot int64 + TxPointer int64 + IdentitiesHash []byte +} + +type SlotDecryptionSignature struct { + Eon int64 + Slot int64 + KeyperIndex int64 + TxPointer int64 + IdentitiesHash []byte + Signature []byte +} + +type TransactionSubmittedEvent struct { + Index int64 + BlockNumber int64 + BlockHash []byte + TxIndex int64 + LogIndex int64 + Eon int64 + IdentityPrefix []byte + Sender string + GasLimit int64 +} + +type TransactionSubmittedEventCount struct { + Eon int64 + EventCount int64 +} + +type TransactionSubmittedEventsSyncedUntil struct { + EnforceOneRow bool + BlockHash []byte + BlockNumber int64 + Slot int64 +} + +type TxPointer struct { + Eon int64 + Slot int64 + Value int64 +} + +type ValidatorRegistration struct { + BlockNumber int64 + BlockHash []byte + TxIndex int64 + LogIndex int64 + ValidatorIndex int64 + Nonce int64 + IsRegistration bool +} + +type ValidatorRegistrationsSyncedUntil struct { + EnforceOneRow bool + BlockHash []byte + BlockNumber int64 +} diff --git a/rolling-shutter/keyperimpl/gnosis/database/sql/queries/gnosiskeyper.sql b/rolling-shutter/keyperimpl/gnosis/database/sql/queries/gnosiskeyper.sql new file mode 100644 index 000000000..c5273cc69 --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/database/sql/queries/gnosiskeyper.sql @@ -0,0 +1,110 @@ +-- name: InsertTransactionSubmittedEvent :execresult +INSERT INTO transaction_submitted_event ( + index, + block_number, + block_hash, + tx_index, + log_index, + eon, + identity_prefix, + sender, + gas_limit +) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) +ON CONFLICT DO NOTHING; + +-- name: GetTransactionSubmittedEvents :many +SELECT * FROM transaction_submitted_event +WHERE eon = $1 AND index >= $2 +ORDER BY index ASC +LIMIT $3; + +-- name: SetTransactionSubmittedEventsSyncedUntil :exec +INSERT INTO transaction_submitted_events_synced_until (block_hash, block_number, slot) VALUES ($1, $2, $3) +ON CONFLICT (enforce_one_row) DO UPDATE +SET block_hash = $1, block_number = $2, slot = $3; + +-- name: GetTransactionSubmittedEventsSyncedUntil :one +SELECT * FROM transaction_submitted_events_synced_until LIMIT 1; + +-- name: SetTransactionSubmittedEventCount :exec +INSERT INTO transaction_submitted_event_count (eon, event_count) +VALUES ($1, $2) +ON CONFLICT (eon) DO UPDATE +SET event_count = $2; + +-- name: GetTransactionSubmittedEventCount :one +SELECT event_count FROM transaction_submitted_event_count +WHERE eon = $1 +LIMIT 1; + +-- name: GetTxPointer :one +SELECT * FROM tx_pointer +WHERE eon = $1; + +-- name: InitTxPointer :exec +INSERT INTO tx_pointer (eon, slot, value) +VALUES ($1, $2, 0) +ON CONFLICT DO NOTHING; + +-- name: SetTxPointer :exec +INSERT INTO tx_pointer (eon, slot, value) +VALUES ($1, $2, $3) +ON CONFLICT (eon) DO UPDATE +SET slot = $2, value = $3; + +-- name: SetTxPointerSlot :exec +UPDATE tx_pointer +SET slot = $2 +WHERE eon = $1; + +-- name: SetCurrentDecryptionTrigger :exec +INSERT INTO current_decryption_trigger (eon, slot, tx_pointer, identities_hash) +VALUES ($1, $2, $3, $4) +ON CONFLICT (eon) DO UPDATE +SET slot = $2, tx_pointer = $3, identities_hash = $4; + +-- name: GetCurrentDecryptionTrigger :one +SELECT * FROM current_decryption_trigger +WHERE eon = $1; + +-- name: InsertSlotDecryptionSignature :exec +INSERT INTO slot_decryption_signatures (eon, slot, keyper_index, tx_pointer, identities_hash, signature) +VALUES ($1, $2, $3, $4, $5, $6); + +-- name: GetSlotDecryptionSignatures :many +SELECT * FROM slot_decryption_signatures +WHERE eon = $1 AND slot = $2 AND tx_pointer = $3 AND identities_hash = $4 +ORDER BY keyper_index ASC +LIMIT $5; + +-- name: InsertValidatorRegistration :exec +INSERT INTO validator_registrations ( + block_number, + block_hash, + tx_index, + log_index, + validator_index, + nonce, + is_registration +) VALUES ($1, $2, $3, $4, $5, $6, $7); + +-- name: IsValidatorRegistered :one +SELECT is_registration FROM validator_registrations +WHERE validator_index = $1 AND block_number < $2 +ORDER BY block_number DESC, tx_index DESC, log_index DESC +LIMIT 1; + +-- name: SetValidatorRegistrationsSyncedUntil :exec +INSERT INTO validator_registrations_synced_until (block_hash, block_number) VALUES ($1, $2) +ON CONFLICT (enforce_one_row) DO UPDATE +SET block_hash = $1, block_number = $2; + +-- name: GetValidatorRegistrationsSyncedUntil :one +SELECT * FROM validator_registrations_synced_until LIMIT 1; + +-- name: GetValidatorRegistrationNonceBefore :one +SELECT nonce FROM validator_registrations +WHERE validator_index = $1 AND block_number <= $2 AND tx_index <= $3 AND log_index <= $4 +ORDER BY block_number DESC, tx_index DESC, log_index DESC +LIMIT 1; \ No newline at end of file diff --git a/rolling-shutter/keyperimpl/gnosis/database/sql/schemas/gnosiskeyper.sql b/rolling-shutter/keyperimpl/gnosis/database/sql/schemas/gnosiskeyper.sql new file mode 100644 index 000000000..ef551998c --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/database/sql/schemas/gnosiskeyper.sql @@ -0,0 +1,69 @@ +-- schema-version: gnosiskeyper-1 -- +-- Please change the version above if you make incompatible changes to +-- the schema. We'll use this to check we're using the right schema. + +CREATE TABLE transaction_submitted_event ( + index bigint CHECK (index >= 0), + block_number bigint NOT NULL CHECK (block_number >= 0), + block_hash bytea NOT NULL, + tx_index bigint NOT NULL CHECK (tx_index >= 0), + log_index bigint NOT NULL CHECK (log_index >= 0), + eon bigint NOT NULL CHECK (eon >= 0), + identity_prefix bytea NOT NULL, + sender text NOT NULL, + gas_limit bigint NOT NULL CHECK (gas_limit >= 0), + PRIMARY KEY (index, eon) +); + +CREATE TABLE transaction_submitted_events_synced_until( + enforce_one_row bool PRIMARY KEY DEFAULT true, + block_hash bytea NOT NULL, + block_number bigint NOT NULL CHECK (block_number >= 0), + slot bigint NOT NULL CHECK (slot >= 0) +); + +CREATE TABLE transaction_submitted_event_count( + eon bigint PRIMARY KEY, + event_count bigint NOT NULL DEFAULT 0 CHECK (event_count >= 0) +); + +CREATE TABLE tx_pointer( + eon bigint PRIMARY KEY, + slot bigint NOT NULL DEFAULT 0, + value bigint NOT NULL DEFAULT 0 +); + +CREATE TABLE current_decryption_trigger( + eon bigint PRIMARY KEY CHECK (eon >= 0), + slot bigint NOT NULL CHECK (slot >= 0), + tx_pointer bigint NOT NULL CHECK (tx_pointer >= 0), + identities_hash bytea NOT NULL +); + +CREATE TABLE slot_decryption_signatures( + eon bigint NOT NULL CHECK (eon >= 0), + slot bigint NOT NULL CHECK (slot >= 0), + keyper_index bigint NOT NULL, + tx_pointer bigint NOT NULL CHECK (tx_pointer >= 0), + identities_hash bytea NOT NULL, + signature bytea NOT NULL, + PRIMARY KEY (eon, slot, keyper_index) +); + +CREATE TABLE validator_registrations( + block_number bigint NOT NULL CHECK (block_number >= 0), + block_hash bytea NOT NULL, + tx_index bigint NOT NULL CHECK (tx_index >= 0), + log_index bigint NOT NULL CHECK (log_index >= 0), + validator_index bigint NOT NULL CHECK (validator_index >= 0), + nonce bigint NOT NULL CHECK (nonce >= 0), + is_registration bool NOT NULL, + PRIMARY KEY (block_number, tx_index, log_index) +); +CREATE INDEX idx_validator_index ON validator_registrations (validator_index); + +CREATE TABLE validator_registrations_synced_until( + enforce_one_row bool PRIMARY KEY DEFAULT true, + block_hash bytea NOT NULL, + block_number bigint NOT NULL CHECK (block_number >= 0) +); \ No newline at end of file diff --git a/rolling-shutter/keyperimpl/gnosis/database/sql/sqlc.yaml b/rolling-shutter/keyperimpl/gnosis/database/sql/sqlc.yaml new file mode 100644 index 000000000..63efe1093 --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/database/sql/sqlc.yaml @@ -0,0 +1,13 @@ +version: "2" +sql: + - schema: "schemas" + queries: "queries" + engine: "postgresql" + gen: + go: + package: "database" + out: "../" + sql_package: "pgx/v4" + output_db_file_name: "db.sqlc.gen.go" + output_models_file_name: "models.sqlc.gen.go" + output_files_suffix: "c.gen" diff --git a/rolling-shutter/keyperimpl/gnosis/gnosisssztypes/slotdecryptionsignatures.go b/rolling-shutter/keyperimpl/gnosis/gnosisssztypes/slotdecryptionsignatures.go new file mode 100644 index 000000000..2d8e20fc8 --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/gnosisssztypes/slotdecryptionsignatures.go @@ -0,0 +1,75 @@ +// This package contains SSZ-encodable types used by the Gnosis keyper. +// The encodings are automatically generated using FastSSZ (https://github.com/ferranbt/fastssz). +// Command: `$ go run sszgen/*.go --path ` +package gnosisssztypes + +import ( + "crypto/ecdsa" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/pkg/errors" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/identitypreimage" +) + +type IdentityPreimage struct { + Bytes []byte `ssz-size:"52"` +} + +type SlotDecryptionSignatureData struct { + InstanceID uint64 + Eon uint64 + Slot uint64 + TxPointer uint64 + IdentityPreimages []IdentityPreimage `ssz-max:"1024"` +} + +func NewSlotDecryptionSignatureData( + instanceID uint64, + eon uint64, + slot uint64, + txPointer uint64, + identityPreimages []identitypreimage.IdentityPreimage, +) (*SlotDecryptionSignatureData, error) { + if len(identityPreimages) > 1024 { + return nil, errors.New("too many identity preimages") + } + + wrappedPreimages := []IdentityPreimage{} + for _, preimage := range identityPreimages { + wrappedPreimage := IdentityPreimage{ + Bytes: preimage.Bytes(), + } + wrappedPreimages = append(wrappedPreimages, wrappedPreimage) + } + + return &SlotDecryptionSignatureData{ + InstanceID: instanceID, + Eon: eon, + Slot: slot, + TxPointer: txPointer, + IdentityPreimages: wrappedPreimages, + }, nil +} + +func (d *SlotDecryptionSignatureData) ComputeSignature(key *ecdsa.PrivateKey) ([]byte, error) { + h, err := d.HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, "failed to compute hash tree root of slot decryption signature data") + } + return crypto.Sign(h[:], key) +} + +func (d *SlotDecryptionSignatureData) CheckSignature(signature []byte, address common.Address) (bool, error) { + h, err := d.HashTreeRoot() + if err != nil { + return false, errors.Wrap(err, "failed to compute hash tree root of slot decryption signature data") + } + signerPubkey, err := crypto.SigToPub(h[:], signature) + if err != nil { + return false, errors.Wrap(err, "failed to recover public key from slot decryption signature") + } + signerAddress := crypto.PubkeyToAddress(*signerPubkey) + return signerAddress == address, nil +} diff --git a/rolling-shutter/keyperimpl/gnosis/gnosisssztypes/slotdecryptionsignatures_encoding.go b/rolling-shutter/keyperimpl/gnosis/gnosisssztypes/slotdecryptionsignatures_encoding.go new file mode 100644 index 000000000..3c758b472 --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/gnosisssztypes/slotdecryptionsignatures_encoding.go @@ -0,0 +1,219 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: f106749778e6a2208c4b5f0385bccdaa3d89bf4a4c5d96c0e60aa690e7c750be +// Version: 0.1.3 +package gnosisssztypes + +import ( + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the IdentityPreimage object +func (i *IdentityPreimage) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(i) +} + +// MarshalSSZTo ssz marshals the IdentityPreimage object to a target array +func (i *IdentityPreimage) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Bytes' + if size := len(i.Bytes); size != 52 { + err = ssz.ErrBytesLengthFn("IdentityPreimage.Bytes", size, 52) + return + } + dst = append(dst, i.Bytes...) + + return +} + +// UnmarshalSSZ ssz unmarshals the IdentityPreimage object +func (i *IdentityPreimage) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 52 { + return ssz.ErrSize + } + + // Field (0) 'Bytes' + if cap(i.Bytes) == 0 { + i.Bytes = make([]byte, 0, len(buf[0:52])) + } + i.Bytes = append(i.Bytes, buf[0:52]...) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the IdentityPreimage object +func (i *IdentityPreimage) SizeSSZ() (size int) { + size = 52 + return +} + +// HashTreeRoot ssz hashes the IdentityPreimage object +func (i *IdentityPreimage) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(i) +} + +// HashTreeRootWith ssz hashes the IdentityPreimage object with a hasher +func (i *IdentityPreimage) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Bytes' + if size := len(i.Bytes); size != 52 { + err = ssz.ErrBytesLengthFn("IdentityPreimage.Bytes", size, 52) + return + } + hh.PutBytes(i.Bytes) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the IdentityPreimage object +func (i *IdentityPreimage) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(i) +} + +// MarshalSSZ ssz marshals the SlotDecryptionSignatureData object +func (s *SlotDecryptionSignatureData) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(s) +} + +// MarshalSSZTo ssz marshals the SlotDecryptionSignatureData object to a target array +func (s *SlotDecryptionSignatureData) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(36) + + // Field (0) 'InstanceID' + dst = ssz.MarshalUint64(dst, s.InstanceID) + + // Field (1) 'Eon' + dst = ssz.MarshalUint64(dst, s.Eon) + + // Field (2) 'Slot' + dst = ssz.MarshalUint64(dst, s.Slot) + + // Field (3) 'TxPointer' + dst = ssz.MarshalUint64(dst, s.TxPointer) + + // Offset (4) 'IdentityPreimages' + dst = ssz.WriteOffset(dst, offset) + + // Field (4) 'IdentityPreimages' + if size := len(s.IdentityPreimages); size > 1024 { + err = ssz.ErrListTooBigFn("SlotDecryptionSignatureData.IdentityPreimages", size, 1024) + return + } + for ii := 0; ii < len(s.IdentityPreimages); ii++ { + if dst, err = s.IdentityPreimages[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + return +} + +// UnmarshalSSZ ssz unmarshals the SlotDecryptionSignatureData object +func (s *SlotDecryptionSignatureData) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 36 { + return ssz.ErrSize + } + + tail := buf + var o4 uint64 + + // Field (0) 'InstanceID' + s.InstanceID = ssz.UnmarshallUint64(buf[0:8]) + + // Field (1) 'Eon' + s.Eon = ssz.UnmarshallUint64(buf[8:16]) + + // Field (2) 'Slot' + s.Slot = ssz.UnmarshallUint64(buf[16:24]) + + // Field (3) 'TxPointer' + s.TxPointer = ssz.UnmarshallUint64(buf[24:32]) + + // Offset (4) 'IdentityPreimages' + if o4 = ssz.ReadOffset(buf[32:36]); o4 > size { + return ssz.ErrOffset + } + + if o4 < 36 { + return ssz.ErrInvalidVariableOffset + } + + // Field (4) 'IdentityPreimages' + { + buf = tail[o4:] + num, err := ssz.DivideInt2(len(buf), 52, 1024) + if err != nil { + return err + } + s.IdentityPreimages = make([]IdentityPreimage, num) + for ii := 0; ii < num; ii++ { + if err = s.IdentityPreimages[ii].UnmarshalSSZ(buf[ii*52 : (ii+1)*52]); err != nil { + return err + } + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the SlotDecryptionSignatureData object +func (s *SlotDecryptionSignatureData) SizeSSZ() (size int) { + size = 36 + + // Field (4) 'IdentityPreimages' + size += len(s.IdentityPreimages) * 52 + + return +} + +// HashTreeRoot ssz hashes the SlotDecryptionSignatureData object +func (s *SlotDecryptionSignatureData) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(s) +} + +// HashTreeRootWith ssz hashes the SlotDecryptionSignatureData object with a hasher +func (s *SlotDecryptionSignatureData) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'InstanceID' + hh.PutUint64(s.InstanceID) + + // Field (1) 'Eon' + hh.PutUint64(s.Eon) + + // Field (2) 'Slot' + hh.PutUint64(s.Slot) + + // Field (3) 'TxPointer' + hh.PutUint64(s.TxPointer) + + // Field (4) 'IdentityPreimages' + { + subIndx := hh.Index() + num := uint64(len(s.IdentityPreimages)) + if num > 1024 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range s.IdentityPreimages { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 1024) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the SlotDecryptionSignatureData object +func (s *SlotDecryptionSignatureData) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(s) +} diff --git a/rolling-shutter/keyperimpl/gnosis/handlers.go b/rolling-shutter/keyperimpl/gnosis/handlers.go new file mode 100644 index 000000000..9dfe35023 --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/handlers.go @@ -0,0 +1,301 @@ +package gnosis + +import ( + "context" + "math" + + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + + obskeyperdatabase "github.com/shutter-network/rolling-shutter/rolling-shutter/chainobserver/db/keyper" + corekeyperdatabase "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/database" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/gnosis/database" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/gnosis/gnosisssztypes" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/identitypreimage" + "github.com/shutter-network/rolling-shutter/rolling-shutter/p2pmsg" + "github.com/shutter-network/rolling-shutter/rolling-shutter/shdb" +) + +type DecryptionKeySharesHandler struct { + dbpool *pgxpool.Pool +} + +func (h *DecryptionKeySharesHandler) MessagePrototypes() []p2pmsg.Message { + return []p2pmsg.Message{&p2pmsg.DecryptionKeyShares{}} +} + +func (h *DecryptionKeySharesHandler) ValidateMessage(ctx context.Context, msg p2pmsg.Message) (pubsub.ValidationResult, error) { + keyShares := msg.(*p2pmsg.DecryptionKeyShares) + extra, ok := keyShares.Extra.(*p2pmsg.DecryptionKeyShares_Gnosis) + if !ok { + return pubsub.ValidationReject, errors.Errorf("unexpected extra type %T, expected Gnosis", keyShares.Extra) + } + if extra.Gnosis == nil { + return pubsub.ValidationReject, errors.New("missing extra Gnosis data") + } + + if extra.Gnosis.Slot > math.MaxInt64 { + return pubsub.ValidationReject, errors.New("slot number too large") + } + if extra.Gnosis.TxPointer > math.MaxInt64 { + return pubsub.ValidationReject, errors.New("tx pointer too large") + } + + keyperDB := corekeyperdatabase.New(h.dbpool) + eon, err := keyperDB.GetEon(ctx, int64(keyShares.Eon)) + if err != nil { + return pubsub.ValidationReject, errors.Wrapf(err, "failed to get eon from database for eon %d", keyShares.Eon) + } + obsKeyperDB := obskeyperdatabase.New(h.dbpool) + keyperSet, err := obsKeyperDB.GetKeyperSetByKeyperConfigIndex(ctx, eon.KeyperConfigIndex) + if err != nil { + return pubsub.ValidationReject, errors.Wrapf(err, + "failed to get keyper set from database for keyper set index %d (eon %d)", + eon.KeyperConfigIndex, + keyShares.Eon, + ) + } + if keyShares.KeyperIndex >= uint64(len(keyperSet.Keypers)) { + return pubsub.ValidationReject, errors.Errorf( + "keyper index %d out of range for keyper set %d (eon %d)", + keyShares.KeyperIndex, + eon.KeyperConfigIndex, + keyShares.Eon, + ) + } + keyperAddressStr := keyperSet.Keypers[keyShares.KeyperIndex] + keyperAddress, err := shdb.DecodeAddress(keyperAddressStr) + if err != nil { + return pubsub.ValidationReject, errors.Wrap(err, "failed to decode keyper address from database") + } + + identityPreimages := []identitypreimage.IdentityPreimage{} + for _, share := range keyShares.Shares { + identityPreimage := identitypreimage.IdentityPreimage(share.EpochID) + identityPreimages = append(identityPreimages, identityPreimage) + } + slotDecryptionSignatureData, err := gnosisssztypes.NewSlotDecryptionSignatureData( + keyShares.InstanceID, + keyShares.Eon, + extra.Gnosis.Slot, + extra.Gnosis.TxPointer, + identityPreimages, + ) + if err != nil { + return pubsub.ValidationReject, errors.Wrap(err, "failed to create slot decryption signature data object") + } + signatureValid, err := slotDecryptionSignatureData.CheckSignature(extra.Gnosis.Signature, keyperAddress) + if err != nil { + return pubsub.ValidationReject, errors.Wrap(err, "failed to check slot decryption signature") + } + if !signatureValid { + return pubsub.ValidationReject, errors.New("slot decryption signature invalid") + } + + return pubsub.ValidationAccept, nil +} + +func (h *DecryptionKeySharesHandler) HandleMessage(ctx context.Context, msg p2pmsg.Message) ([]p2pmsg.Message, error) { + keyShares := msg.(*p2pmsg.DecryptionKeyShares) + extra := keyShares.Extra.(*p2pmsg.DecryptionKeyShares_Gnosis).Gnosis + + gnosisDB := database.New(h.dbpool) + keyperCoreDB := corekeyperdatabase.New(h.dbpool) + obsKeyperDB := obskeyperdatabase.New(h.dbpool) + + identitiesHash := computeIdentitiesHashFromShares(keyShares.Shares) + err := gnosisDB.InsertSlotDecryptionSignature(ctx, database.InsertSlotDecryptionSignatureParams{ + Eon: int64(keyShares.Eon), + Slot: int64(extra.Slot), + KeyperIndex: int64(keyShares.KeyperIndex), + TxPointer: int64(extra.TxPointer), + IdentitiesHash: identitiesHash, + Signature: extra.Signature, + }) + if err != nil { + return []p2pmsg.Message{}, errors.Wrap(err, "failed to insert tx pointer vote") + } + + eonData, err := keyperCoreDB.GetEon(ctx, int64(keyShares.Eon)) + if err != nil { + return []p2pmsg.Message{}, errors.Wrapf(err, "failed to get eon data from database for eon %d", keyShares.Eon) + } + keyperSet, err := obsKeyperDB.GetKeyperSetByKeyperConfigIndex(ctx, eonData.KeyperConfigIndex) + if err != nil { + return []p2pmsg.Message{}, errors.Wrapf(err, "failed to get keyper set from database for eon %d", keyShares.Eon) + } + + signaturesDB, err := gnosisDB.GetSlotDecryptionSignatures(ctx, database.GetSlotDecryptionSignaturesParams{ + Eon: int64(keyShares.Eon), + Slot: int64(extra.Slot), + TxPointer: int64(extra.TxPointer), + IdentitiesHash: identitiesHash, + Limit: keyperSet.Threshold, + }) + if err != nil { + return []p2pmsg.Message{}, errors.Wrap(err, "failed to count slot decryption signatures") + } + + // send a keys message if we have reached the required number of both the signatures and the key shares + if len(signaturesDB) >= int(keyperSet.Threshold) { + keys := []*p2pmsg.Key{} + for _, share := range keyShares.GetShares() { + decryptionKeyDB, err := keyperCoreDB.GetDecryptionKey(ctx, corekeyperdatabase.GetDecryptionKeyParams{ + Eon: int64(keyShares.Eon), + EpochID: share.EpochID, + }) + if err == pgx.ErrNoRows { + return []p2pmsg.Message{}, nil + } + key := &p2pmsg.Key{ + Identity: share.EpochID, + Key: decryptionKeyDB.DecryptionKey, + } + keys = append(keys, key) + } + signerIndices := []uint64{} + signatures := [][]byte{} + for _, signature := range signaturesDB { + signerIndices = append(signerIndices, uint64(signature.KeyperIndex)) + signatures = append(signatures, signature.Signature) + } + decryptionKeysMsg := &p2pmsg.DecryptionKeys{ + InstanceID: keyShares.InstanceID, + Eon: keyShares.Eon, + Keys: keys, + Extra: &p2pmsg.DecryptionKeys_Gnosis{ + Gnosis: &p2pmsg.GnosisDecryptionKeysExtra{ + Slot: extra.Slot, + TxPointer: extra.TxPointer, + SignerIndices: signerIndices, + Signatures: signatures, + }, + }, + } + return []p2pmsg.Message{decryptionKeysMsg}, nil + } + + return []p2pmsg.Message{}, nil +} + +type DecryptionKeysHandler struct { + dbpool *pgxpool.Pool +} + +func (h *DecryptionKeysHandler) MessagePrototypes() []p2pmsg.Message { + return []p2pmsg.Message{&p2pmsg.DecryptionKeys{}} +} + +func validateSignerIndices(extra *p2pmsg.DecryptionKeys_Gnosis, n int) (pubsub.ValidationResult, error) { + for i, signerIndex := range extra.Gnosis.SignerIndices { + if i >= 1 { + prevSignerIndex := extra.Gnosis.SignerIndices[i-1] + if signerIndex == prevSignerIndex { + return pubsub.ValidationReject, errors.New("duplicate signer index found") + } + if signerIndex < prevSignerIndex { + return pubsub.ValidationReject, errors.New("signer indices not ordered") + } + } + if signerIndex >= uint64(n) { + return pubsub.ValidationReject, errors.New("signer index out of range") + } + } + return pubsub.ValidationAccept, nil +} + +func (h *DecryptionKeysHandler) ValidateMessage(ctx context.Context, msg p2pmsg.Message) (pubsub.ValidationResult, error) { + keys := msg.(*p2pmsg.DecryptionKeys) + extra, ok := keys.Extra.(*p2pmsg.DecryptionKeys_Gnosis) + if !ok { + return pubsub.ValidationReject, errors.Errorf("unexpected extra type %T, expected Gnosis", keys.Extra) + } + if extra.Gnosis == nil { + return pubsub.ValidationReject, errors.New("missing extra Gnosis data") + } + + if extra.Gnosis.Slot > math.MaxInt64 { + return pubsub.ValidationReject, errors.New("slot number too large") + } + if extra.Gnosis.TxPointer > math.MaxInt32 { // the pointer will have to be incremented + return pubsub.ValidationReject, errors.New("tx pointer too large") + } + if len(keys.Keys) == 0 { + return pubsub.ValidationReject, errors.New("msg does not contain any keys") + } + + obsKeyperDB := obskeyperdatabase.New(h.dbpool) + keyperSet, err := obsKeyperDB.GetKeyperSetByKeyperConfigIndex(ctx, int64(keys.Eon)) + if err != nil { + return pubsub.ValidationReject, errors.Wrapf(err, "failed to get keyper set from database for eon %d", keys.Eon) + } + + if int32(len(extra.Gnosis.SignerIndices)) != keyperSet.Threshold { + return pubsub.ValidationReject, errors.Errorf("expected %d signers, got %d", keyperSet.Threshold, len(extra.Gnosis.SignerIndices)) + } + + res, err := validateSignerIndices(extra, len(keyperSet.Keypers)) + if res != pubsub.ValidationAccept { + return res, err + } + signers, err := keyperSet.GetSubset(extra.Gnosis.SignerIndices) + if err != nil { + return pubsub.ValidationReject, err + } + + identityPreimages := []identitypreimage.IdentityPreimage{} + for _, key := range keys.Keys { + identityPreimage := identitypreimage.IdentityPreimage(key.Identity) + identityPreimages = append(identityPreimages, identityPreimage) + } + slotDecryptionSignatureData, err := gnosisssztypes.NewSlotDecryptionSignatureData( + keys.InstanceID, + keys.Eon, + extra.Gnosis.Slot, + extra.Gnosis.TxPointer, + identityPreimages, + ) + if err != nil { + return pubsub.ValidationReject, errors.Wrap(err, "failed to create slot decryption signature data object") + } + for signatureIndex := 0; signatureIndex < len(extra.Gnosis.Signatures); signatureIndex++ { + signature := extra.Gnosis.Signatures[signatureIndex] + signer := signers[signatureIndex] + signatureValid, err := slotDecryptionSignatureData.CheckSignature(signature, signer) + if err != nil { + return pubsub.ValidationReject, errors.Wrap(err, "failed to check slot decryption signature") + } + if !signatureValid { + return pubsub.ValidationReject, errors.New("slot decryption signature invalid") + } + } + + return pubsub.ValidationAccept, nil +} + +func (h *DecryptionKeysHandler) HandleMessage(ctx context.Context, msg p2pmsg.Message) ([]p2pmsg.Message, error) { + keys := msg.(*p2pmsg.DecryptionKeys) + extra := keys.Extra.(*p2pmsg.DecryptionKeys_Gnosis).Gnosis + gnosisDB := database.New(h.dbpool) + // the first key is the block key, only the rest are tx keys, so subtract 1 + newTxPointer := int64(extra.TxPointer) + int64(len(keys.Keys)) - 1 + log.Debug(). + Uint64("eon", keys.Eon). + Uint64("slot", extra.Slot). + Uint64("tx-pointer-msg", extra.TxPointer). + Int("num-keys", len(keys.Keys)). + Int64("tx-pointer-updated", newTxPointer). + Msg("updating tx pointer") + err := gnosisDB.SetTxPointer(ctx, database.SetTxPointerParams{ + Eon: int64(keys.Eon), + Slot: int64(extra.Slot), + Value: newTxPointer, + }) + if err != nil { + return []p2pmsg.Message{}, errors.Wrap(err, "failed to set tx pointer") + } + return []p2pmsg.Message{}, nil +} diff --git a/rolling-shutter/keyperimpl/gnosis/identitieshash.go b/rolling-shutter/keyperimpl/gnosis/identitieshash.go new file mode 100644 index 000000000..ba237c987 --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/identitieshash.go @@ -0,0 +1,24 @@ +package gnosis + +import ( + "github.com/ethereum/go-ethereum/crypto" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/identitypreimage" + "github.com/shutter-network/rolling-shutter/rolling-shutter/p2pmsg" +) + +func computeIdentitiesHash(identityPreimages []identitypreimage.IdentityPreimage) []byte { + identityPreimagesAsBytes := [][]byte{} + for _, preimage := range identityPreimages { + identityPreimagesAsBytes = append(identityPreimagesAsBytes, preimage) + } + return crypto.Keccak256(identityPreimagesAsBytes...) +} + +func computeIdentitiesHashFromShares(shares []*p2pmsg.KeyShare) []byte { + identityPreimges := []identitypreimage.IdentityPreimage{} + for _, share := range shares { + identityPreimges = append(identityPreimges, identitypreimage.IdentityPreimage(share.EpochID)) + } + return computeIdentitiesHash(identityPreimges) +} diff --git a/rolling-shutter/keyperimpl/gnosis/keyper.go b/rolling-shutter/keyperimpl/gnosis/keyper.go new file mode 100644 index 000000000..7d728b974 --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/keyper.go @@ -0,0 +1,284 @@ +package gnosis + +import ( + "context" + "time" + + "github.com/ethereum/go-ethereum/ethclient" + gethLog "github.com/ethereum/go-ethereum/log" + "github.com/jackc/pgx/v4/pgxpool" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + sequencerBindings "github.com/shutter-network/gnosh-contracts/gnoshcontracts/sequencer" + validatorRegistryBindings "github.com/shutter-network/gnosh-contracts/gnoshcontracts/validatorregistry" + "golang.org/x/exp/slog" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/eonkeypublisher" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/epochkghandler" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/kprconfig" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/gnosis/database" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/beaconapiclient" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/broker" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync" + syncevent "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/event" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/db" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/slotticker" + "github.com/shutter-network/rolling-shutter/rolling-shutter/p2p" +) + +var ErrParseKeyperSet = errors.New("cannot parse KeyperSet") + +// The relative proposal timeout specifies for how long we wait for a block proposal to appear in +// a block. If we don't receive one in this time, we assume the slot is empty. The timeout is +// given as a fraction of the slot duration. +const ( + relativeProposalTimeoutNumerator = 1 + relativeProposalTimeoutDenominator = 3 +) + +type Keyper struct { + core *keyper.KeyperCore + config *Config + dbpool *pgxpool.Pool + beaconAPIClient *beaconapiclient.Client + + chainSyncClient *chainsync.Client + sequencerSyncer *SequencerSyncer + validatorSyncer *ValidatorSyncer + eonKeyPublisher *eonkeypublisher.EonKeyPublisher + latestTriggeredSlot *uint64 + + // input events + newBlocks chan *syncevent.LatestBlock + newKeyperSets chan *syncevent.KeyperSet + newEonPublicKeys chan keyper.EonPublicKey + slotTicker *slotticker.SlotTicker + + // outputs + decryptionTriggerChannel chan *broker.Event[*epochkghandler.DecryptionTrigger] +} + +func New(c *Config) *Keyper { + return &Keyper{ + config: c, + } +} + +func (kpr *Keyper) Start(ctx context.Context, runner service.Runner) error { + var err error + + kpr.newBlocks = make(chan *syncevent.LatestBlock) + kpr.newKeyperSets = make(chan *syncevent.KeyperSet) + kpr.newEonPublicKeys = make(chan keyper.EonPublicKey) + kpr.decryptionTriggerChannel = make(chan *broker.Event[*epochkghandler.DecryptionTrigger]) + runner.Defer(func() { close(kpr.newBlocks) }) + runner.Defer(func() { close(kpr.newKeyperSets) }) + runner.Defer(func() { close(kpr.newEonPublicKeys) }) + runner.Defer(func() { close(kpr.decryptionTriggerChannel) }) + + kpr.latestTriggeredSlot = nil + + offset := -(time.Duration(kpr.config.Gnosis.SecondsPerSlot) * time.Second) * + (relativeProposalTimeoutDenominator - relativeProposalTimeoutNumerator) / + relativeProposalTimeoutDenominator + kpr.slotTicker = slotticker.NewSlotTicker( + time.Duration(kpr.config.Gnosis.SecondsPerSlot*uint64(time.Second)), + time.Unix(int64(kpr.config.Gnosis.GenesisSlotTimestamp), 0), + offset, + ) + + kpr.dbpool, err = db.Connect(ctx, runner, kpr.config.DatabaseURL, database.Definition.Name()) + if err != nil { + return errors.Wrap(err, "failed to connect to database") + } + kpr.beaconAPIClient, err = beaconapiclient.New(kpr.config.BeaconAPIURL) + if err != nil { + return errors.Wrap(err, "failed to initialize beacon API client") + } + + messageSender, err := p2p.New(kpr.config.P2P) + if err != nil { + return errors.Wrap(err, "failed to initialize p2p messaging") + } + messageSender.AddMessageHandler(&DecryptionKeySharesHandler{kpr.dbpool}) + messageSender.AddMessageHandler(&DecryptionKeysHandler{kpr.dbpool}) + messagingMiddleware := NewMessagingMiddleware(messageSender, kpr.dbpool, kpr.config) + + kpr.core, err = keyper.New( + &kprconfig.Config{ + InstanceID: kpr.config.InstanceID, + DatabaseURL: kpr.config.DatabaseURL, + HTTPEnabled: kpr.config.HTTPEnabled, + HTTPListenAddress: kpr.config.HTTPListenAddress, + P2P: kpr.config.P2P, + Ethereum: kpr.config.Gnosis.Node, + Shuttermint: kpr.config.Shuttermint, + Metrics: kpr.config.Metrics, + }, + kpr.decryptionTriggerChannel, + keyper.WithDBPool(kpr.dbpool), + keyper.NoBroadcastEonPublicKey(), + keyper.WithEonPublicKeyHandler(kpr.channelNewEonPublicKey), + keyper.WithMessaging(messagingMiddleware), + ) + if err != nil { + return errors.Wrap(err, "can't instantiate keyper core") + } + + kpr.chainSyncClient, err = chainsync.NewClient( + ctx, + chainsync.WithClientURL(kpr.config.Gnosis.Node.EthereumURL), + chainsync.WithKeyperSetManager(kpr.config.Gnosis.Contracts.KeyperSetManager), + chainsync.WithKeyBroadcastContract(kpr.config.Gnosis.Contracts.KeyBroadcastContract), + chainsync.WithSyncNewBlock(kpr.channelNewBlock), + chainsync.WithSyncNewKeyperSet(kpr.channelNewKeyperSet), + chainsync.WithPrivateKey(kpr.config.Gnosis.Node.PrivateKey.Key), + chainsync.WithLogger(gethLog.NewLogger(slog.Default().Handler())), + ) + if err != nil { + return err + } + + eonKeyPublisherClient, err := ethclient.DialContext(ctx, kpr.config.Gnosis.Node.EthereumURL) + if err != nil { + return errors.Wrapf(err, "failed to dial ethereum node at %s", kpr.config.Gnosis.Node.EthereumURL) + } + kpr.eonKeyPublisher, err = eonkeypublisher.NewEonKeyPublisher( + kpr.dbpool, + eonKeyPublisherClient, + kpr.config.Gnosis.Contracts.EonKeyPublish, + kpr.config.Gnosis.Node.PrivateKey.Key, + ) + if err != nil { + return errors.Wrap(err, "failed to initialize eon key publisher") + } + + err = kpr.initSequencerSyncer(ctx) + if err != nil { + return err + } + err = kpr.initValidatorSyncer(ctx) + if err != nil { + return err + } + + runner.Go(func() error { return kpr.processInputs(ctx) }) + return runner.StartService(kpr.core, kpr.chainSyncClient, kpr.slotTicker, kpr.eonKeyPublisher) +} + +// initSequencerSycer initializes the sequencer syncer if the keyper is known to be a member of a +// keyper set. Otherwise, the syncer will only be initialized once such a keyper set is observed to +// be added, as only then we will know which eon(s) we are responsible for. +func (kpr *Keyper) initSequencerSyncer(ctx context.Context) error { + client, err := ethclient.DialContext(ctx, kpr.config.Gnosis.Node.ContractsURL) + if err != nil { + return errors.Wrap(err, "failed to dial Ethereum execution node") + } + + log.Info(). + Str("contract-address", kpr.config.Gnosis.Contracts.KeyperSetManager.Hex()). + Msg("initializing sequencer syncer") + contract, err := sequencerBindings.NewSequencer(kpr.config.Gnosis.Contracts.Sequencer, client) + if err != nil { + return err + } + kpr.sequencerSyncer = &SequencerSyncer{ + Contract: contract, + DBPool: kpr.dbpool, + ExecutionClient: client, + GenesisSlotTimestamp: kpr.config.Gnosis.GenesisSlotTimestamp, + SecondsPerSlot: kpr.config.Gnosis.SecondsPerSlot, + } + + // Perform an initial sync now because it might take some time and doing so during regular + // slot processing might hold up things + latestHeader, err := client.HeaderByNumber(ctx, nil) + if err != nil { + return errors.Wrap(err, "failed to get latest block header") + } + err = kpr.sequencerSyncer.Sync(ctx, latestHeader) + if err != nil { + return err + } + + return nil +} + +func (kpr *Keyper) initValidatorSyncer(ctx context.Context) error { + validatorSyncerClient, err := ethclient.DialContext(ctx, kpr.config.Gnosis.Node.EthereumURL) + if err != nil { + return errors.Wrap(err, "failed to dial ethereum node") + } + chainID, err := validatorSyncerClient.ChainID(ctx) + if err != nil { + return errors.Wrap(err, "failed to get chain ID") + } + validatorRegistryContract, err := validatorRegistryBindings.NewValidatorregistry( + kpr.config.Gnosis.Contracts.ValidatorRegistry, + validatorSyncerClient, + ) + if err != nil { + return errors.Wrap(err, "failed to instantiate validator registry contract") + } + kpr.validatorSyncer = &ValidatorSyncer{ + Contract: validatorRegistryContract, + DBPool: kpr.dbpool, + BeaconAPIClient: kpr.beaconAPIClient, + ExecutionClient: validatorSyncerClient, + ChainID: chainID.Uint64(), + } + + // Perform an initial sync now because it might take some time and doing so during regular + // slot processing might hold up things + latestHeader, err := validatorSyncerClient.HeaderByNumber(ctx, nil) + if err != nil { + return errors.Wrap(err, "failed to get latest block header") + } + err = kpr.validatorSyncer.Sync(ctx, latestHeader) + if err != nil { + return err + } + return nil +} + +func (kpr *Keyper) processInputs(ctx context.Context) error { + var err error + for { + select { + case ev := <-kpr.newBlocks: + err = kpr.processNewBlock(ctx, ev) + case ev := <-kpr.newKeyperSets: + err = kpr.processNewKeyperSet(ctx, ev) + case ev := <-kpr.newEonPublicKeys: + err = kpr.processNewEonPublicKey(ctx, ev) + case slot := <-kpr.slotTicker.C: + err = kpr.processNewSlot(ctx, slot) + case <-ctx.Done(): + return ctx.Err() + } + if err != nil { + // TODO: Check if it's safe to drop those events. If not, we should store the + // ones that remain on the channel in the db and process them when we restart. + // TODO: also, should we stop the keyper or just log the error and continue? + // return err + log.Error().Err(err).Msg("error processing event") + } + } +} + +func (kpr *Keyper) channelNewBlock(_ context.Context, ev *syncevent.LatestBlock) error { + kpr.newBlocks <- ev + return nil +} + +func (kpr *Keyper) channelNewKeyperSet(_ context.Context, ev *syncevent.KeyperSet) error { + kpr.newKeyperSets <- ev + return nil +} + +func (kpr *Keyper) channelNewEonPublicKey(_ context.Context, key keyper.EonPublicKey) error { + kpr.newEonPublicKeys <- key + return nil +} diff --git a/rolling-shutter/keyperimpl/gnosis/messagingmiddleware.go b/rolling-shutter/keyperimpl/gnosis/messagingmiddleware.go new file mode 100644 index 000000000..2cc802d6f --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/messagingmiddleware.go @@ -0,0 +1,294 @@ +package gnosis + +import ( + "bytes" + "context" + + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "google.golang.org/protobuf/proto" + + obskeyperdatabase "github.com/shutter-network/rolling-shutter/rolling-shutter/chainobserver/db/keyper" + corekeyperdatabase "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/database" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/gnosis/database" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/gnosis/gnosisssztypes" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/identitypreimage" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/retry" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" + "github.com/shutter-network/rolling-shutter/rolling-shutter/p2p" + "github.com/shutter-network/rolling-shutter/rolling-shutter/p2pmsg" +) + +type MessagingMiddleware struct { + config *Config + messaging p2p.Messaging + dbpool *pgxpool.Pool +} + +type WrappedMessageHandler struct { + handler p2p.MessageHandler + middleware *MessagingMiddleware +} + +func (h *WrappedMessageHandler) MessagePrototypes() []p2pmsg.Message { + return h.handler.MessagePrototypes() +} + +func (h *WrappedMessageHandler) ValidateMessage(ctx context.Context, msg p2pmsg.Message) (pubsub.ValidationResult, error) { + return h.handler.ValidateMessage(ctx, msg) +} + +func (h *WrappedMessageHandler) HandleMessage(ctx context.Context, msg p2pmsg.Message) ([]p2pmsg.Message, error) { + msgs, err := h.handler.HandleMessage(ctx, msg) + if err != nil { + return []p2pmsg.Message{}, err + } + replacedMsgs := []p2pmsg.Message{} + for _, msg := range msgs { + replacedMsg, err := h.middleware.interceptMessage(ctx, msg) + if err != nil { + return []p2pmsg.Message{}, err + } + if replacedMsg != nil { + replacedMsgs = append(replacedMsgs, replacedMsg) + } + } + return replacedMsgs, nil +} + +func NewMessagingMiddleware(messaging p2p.Messaging, dbpool *pgxpool.Pool, config *Config) *MessagingMiddleware { + return &MessagingMiddleware{messaging: messaging, dbpool: dbpool, config: config} +} + +func (i *MessagingMiddleware) Start(_ context.Context, runner service.Runner) error { + return runner.StartService(i.messaging) +} + +func (i *MessagingMiddleware) interceptMessage(ctx context.Context, msg p2pmsg.Message) (p2pmsg.Message, error) { + switch msg := msg.(type) { + case *p2pmsg.DecryptionKeyShares: + return i.interceptDecryptionKeyShares(ctx, msg) + case *p2pmsg.DecryptionKeys: + return i.interceptDecryptionKeys(ctx, msg) + default: + return msg, nil + } +} + +func (i *MessagingMiddleware) SendMessage(ctx context.Context, msg p2pmsg.Message, opts ...retry.Option) error { + msgOut, err := i.interceptMessage(ctx, msg) + if err != nil { + return err + } + if msgOut != nil { + return i.messaging.SendMessage(ctx, msgOut, opts...) + } + return nil +} + +func (i *MessagingMiddleware) AddValidator(ctx p2p.ValidatorFunc, protos ...p2pmsg.Message) { + i.messaging.AddValidator(ctx, protos...) +} + +func (i *MessagingMiddleware) AddMessageHandler(mhs ...p2p.MessageHandler) { + for _, mh := range mhs { + wmh := &WrappedMessageHandler{handler: mh, middleware: i} + i.messaging.AddMessageHandler(wmh) + } +} + +func (i *MessagingMiddleware) interceptDecryptionKeyShares( + ctx context.Context, + originalMsg *p2pmsg.DecryptionKeyShares, +) (p2pmsg.Message, error) { + queries := database.New(i.dbpool) + + // We have to populate the outgoing message with slot and tx pointer information. We fetch + // this information from the database. It should have been inserted when the decryption + // trigger was produced. If creating the message takes unexpectedly long, it is possible that + // it was overridden with the following trigger. In this case, we drop the message. + currentDecryptionTrigger, err := queries.GetCurrentDecryptionTrigger(ctx, int64(originalMsg.Eon)) + if err == pgx.ErrNoRows { + log.Warn(). + Uint64("eon", originalMsg.Eon). + Msg("intercepted decryption key shares message with unknown corresponding decryption trigger") + return nil, nil + } else if err != nil { + return nil, errors.Wrapf(err, "failed to get current decryption trigger for eon %d", originalMsg.Eon) + } + if originalMsg.Eon != uint64(currentDecryptionTrigger.Eon) { + log.Warn(). + Uint64("eon-got", originalMsg.Eon). + Int64("eon-expected", currentDecryptionTrigger.Eon). + Msg("intercepted decryption key shares message with unexpected eon") + return nil, nil + } + identitiesHash := computeIdentitiesHashFromShares(originalMsg.Shares) + if !bytes.Equal(identitiesHash, currentDecryptionTrigger.IdentitiesHash) { + log.Warn(). + Uint64("eon", originalMsg.Eon). + Hex("expectedIdentitiesHash", currentDecryptionTrigger.IdentitiesHash). + Hex("actualIdentitiesHash", identitiesHash). + Msg("intercepted decryption key shares message with unexpected identities hash") + return nil, nil + } + + identityPreimages := []identitypreimage.IdentityPreimage{} + for _, share := range originalMsg.Shares { + identityPreimages = append(identityPreimages, identitypreimage.IdentityPreimage(share.EpochID)) + } + slotDecryptionSignatureData, err := gnosisssztypes.NewSlotDecryptionSignatureData( + i.config.InstanceID, + originalMsg.Eon, + uint64(currentDecryptionTrigger.Slot), + uint64(currentDecryptionTrigger.TxPointer), + identityPreimages, + ) + if err != nil { + return nil, err + } + signature, err := slotDecryptionSignatureData.ComputeSignature(i.config.Gnosis.Node.PrivateKey.Key) + if err != nil { + return nil, errors.Wrapf(err, "failed to compute slot decryption signature") + } + + err = queries.InsertSlotDecryptionSignature(ctx, database.InsertSlotDecryptionSignatureParams{ + Eon: currentDecryptionTrigger.Eon, + Slot: currentDecryptionTrigger.Slot, + KeyperIndex: int64(originalMsg.KeyperIndex), + TxPointer: currentDecryptionTrigger.TxPointer, + IdentitiesHash: identitiesHash, + Signature: signature, + }) + if err != nil { + return nil, errors.Wrapf(err, + "failed to insert slot decryption signature for eon %d and slot %d", + originalMsg.Eon, + currentDecryptionTrigger.Slot, + ) + } + + msg := proto.Clone(originalMsg).(*p2pmsg.DecryptionKeyShares) + msg.Extra = &p2pmsg.DecryptionKeyShares_Gnosis{ + Gnosis: &p2pmsg.GnosisDecryptionKeySharesExtra{ + Slot: uint64(currentDecryptionTrigger.Slot), + TxPointer: uint64(currentDecryptionTrigger.TxPointer), + Signature: signature, + }, + } + return msg, nil +} + +func (i *MessagingMiddleware) interceptDecryptionKeys( + ctx context.Context, + originalMsg *p2pmsg.DecryptionKeys, +) (p2pmsg.Message, error) { + if originalMsg.Extra != nil { + err := i.advanceTxPointer(ctx, originalMsg) + if err != nil { + return nil, err + } + return originalMsg, nil + } + + gnosisDB := database.New(i.dbpool) + keyperCoreDB := corekeyperdatabase.New(i.dbpool) + obsKeyperDB := obskeyperdatabase.New(i.dbpool) + + trigger, err := gnosisDB.GetCurrentDecryptionTrigger(ctx, int64(originalMsg.Eon)) + if err == pgx.ErrNoRows { + log.Warn(). + Uint64("eon", originalMsg.Eon). + Msg("unknown decryption trigger for intercepted keys message") + return nil, nil + } + if err != nil { + return nil, errors.Wrapf(err, "failed to get current decryption trigger for eon %d", originalMsg.Eon) + } + + eonData, err := keyperCoreDB.GetEon(ctx, int64(originalMsg.Eon)) + if err != nil { + return nil, errors.Wrapf(err, "failed to get eon data from database for eon %d", originalMsg.Eon) + } + keyperSet, err := obsKeyperDB.GetKeyperSetByKeyperConfigIndex(ctx, eonData.KeyperConfigIndex) + if err != nil { + return nil, errors.Wrapf(err, "failed to get keyper set from database for eon %d", originalMsg.Eon) + } + + signaturesDB, err := gnosisDB.GetSlotDecryptionSignatures(ctx, database.GetSlotDecryptionSignaturesParams{ + Eon: int64(originalMsg.Eon), + Slot: trigger.Slot, + TxPointer: trigger.TxPointer, + IdentitiesHash: trigger.IdentitiesHash, + Limit: keyperSet.Threshold, + }) + if err != nil { + return nil, errors.Wrapf(err, "failed to count slot decryption signatures for eon %d and slot %d", originalMsg.Eon, trigger.Slot) + } + if len(signaturesDB) < int(keyperSet.Threshold) { + log.Debug(). + Uint64("eon", originalMsg.Eon). + Int64("slot", trigger.Slot). + Int64("tx-pointer", trigger.TxPointer). + Hex("identities-hash", trigger.IdentitiesHash). + Int32("threshold", keyperSet.Threshold). + Int("num-signatures", len(signaturesDB)). + Msg("dropping intercepted keys message as signature count is not high enough yet") + return nil, nil + } + + signerIndices := []uint64{} + signatures := [][]byte{} + for _, signature := range signaturesDB { + signerIndices = append(signerIndices, uint64(signature.KeyperIndex)) + signatures = append(signatures, signature.Signature) + } + msg := proto.Clone(originalMsg).(*p2pmsg.DecryptionKeys) + extra := &p2pmsg.GnosisDecryptionKeysExtra{ + Slot: uint64(trigger.Slot), + TxPointer: uint64(trigger.TxPointer), + SignerIndices: signerIndices, + Signatures: signatures, + } + msg.Extra = &p2pmsg.DecryptionKeys_Gnosis{Gnosis: extra} + err = i.advanceTxPointer(ctx, msg) + if err != nil { + return nil, err + } + + log.Info(). + Uint64("block", extra.Slot). + Uint64("tx-pointer", extra.TxPointer). + Int("num-signatures", len(signaturesDB)). + Int("num-keys", len(msg.Keys)). + Msg("sending keys") + return msg, nil +} + +// advanceTxPointer updates the tx pointer in the database such that decryption will continue with +// the next transaction. Panics if the message does not have Gnosis extra data. +func (i *MessagingMiddleware) advanceTxPointer(ctx context.Context, msg *p2pmsg.DecryptionKeys) error { + extra := msg.Extra.(*p2pmsg.DecryptionKeys_Gnosis).Gnosis + + gnosisDB := database.New(i.dbpool) + newTxPointer := int64(extra.TxPointer) + int64(len(msg.Keys)) - 1 + log.Debug(). + Uint64("eon", msg.Eon). + Uint64("slot", extra.Slot). + Uint64("tx-pointer-msg", extra.TxPointer). + Int("num-keys", len(msg.Keys)). + Int64("tx-pointer-updated", newTxPointer). + Msg("updating tx pointer") + err := gnosisDB.SetTxPointer(ctx, database.SetTxPointerParams{ + Eon: int64(msg.Eon), + Slot: int64(extra.Slot), + Value: newTxPointer, + }) + if err != nil { + return errors.Wrap(err, "failed to set tx pointer") + } + return nil +} diff --git a/rolling-shutter/keyperimpl/gnosis/newblock.go b/rolling-shutter/keyperimpl/gnosis/newblock.go new file mode 100644 index 000000000..8a1af2400 --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/newblock.go @@ -0,0 +1,26 @@ +package gnosis + +import ( + "context" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley" + syncevent "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/event" +) + +func (kpr *Keyper) processNewBlock(ctx context.Context, ev *syncevent.LatestBlock) error { + if kpr.sequencerSyncer != nil { + if err := kpr.sequencerSyncer.Sync(ctx, ev.Header); err != nil { + return err + } + } + err := kpr.validatorSyncer.Sync(ctx, ev.Header) + if err != nil { + return err + } + slot := medley.BlockTimestampToSlot( + ev.Header.Time, + kpr.config.Gnosis.GenesisSlotTimestamp, + kpr.config.Gnosis.SecondsPerSlot, + ) + return kpr.maybeTriggerDecryption(ctx, slot+1) +} diff --git a/rolling-shutter/keyperimpl/gnosis/neweonpublickey.go b/rolling-shutter/keyperimpl/gnosis/neweonpublickey.go new file mode 100644 index 000000000..cdba7f693 --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/neweonpublickey.go @@ -0,0 +1,12 @@ +package gnosis + +import ( + "context" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper" +) + +func (kpr *Keyper) processNewEonPublicKey(_ context.Context, key keyper.EonPublicKey) error { //nolint: unparam + kpr.eonKeyPublisher.Publish(key) + return nil +} diff --git a/rolling-shutter/keyperimpl/gnosis/newkeyperset.go b/rolling-shutter/keyperimpl/gnosis/newkeyperset.go new file mode 100644 index 000000000..801c379de --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/newkeyperset.go @@ -0,0 +1,55 @@ +package gnosis + +import ( + "context" + + "github.com/jackc/pgx/v4" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + + obskeyper "github.com/shutter-network/rolling-shutter/rolling-shutter/chainobserver/db/keyper" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley" + syncevent "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/event" + "github.com/shutter-network/rolling-shutter/rolling-shutter/shdb" +) + +func (kpr *Keyper) processNewKeyperSet(ctx context.Context, ev *syncevent.KeyperSet) error { + isMember := false + for _, m := range ev.Members { + if m.Cmp(kpr.config.GetAddress()) == 0 { + isMember = true + break + } + } + log.Info(). + Uint64("activation-block", ev.ActivationBlock). + Uint64("eon", ev.Eon). + Int("num-members", len(ev.Members)). + Uint64("threshold", ev.Threshold). + Bool("is-member", isMember). + Msg("new keyper set added") + + return kpr.dbpool.BeginFunc(ctx, func(tx pgx.Tx) error { + obskeyperdb := obskeyper.New(tx) + + keyperConfigIndex, err := medley.Uint64ToInt64Safe(ev.Eon) + if err != nil { + return errors.Wrap(err, ErrParseKeyperSet.Error()) + } + activationBlockNumber, err := medley.Uint64ToInt64Safe(ev.ActivationBlock) + if err != nil { + return errors.Wrap(err, ErrParseKeyperSet.Error()) + } + threshold, err := medley.Uint64ToInt64Safe(ev.Threshold) + if err != nil { + return errors.Wrap(err, ErrParseKeyperSet.Error()) + } + + return obskeyperdb.InsertKeyperSet(ctx, obskeyper.InsertKeyperSetParams{ + KeyperConfigIndex: keyperConfigIndex, + ActivationBlockNumber: activationBlockNumber, + Keypers: shdb.EncodeAddresses(ev.Members), + Threshold: int32(threshold), + }) + }) +} diff --git a/rolling-shutter/keyperimpl/gnosis/newslot.go b/rolling-shutter/keyperimpl/gnosis/newslot.go new file mode 100644 index 000000000..defcee005 --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/newslot.go @@ -0,0 +1,340 @@ +package gnosis + +import ( + "bytes" + "context" + "fmt" + "math" + "math/big" + "sort" + + "github.com/ethereum/go-ethereum/common" + "github.com/jackc/pgx/v4" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + + obskeyper "github.com/shutter-network/rolling-shutter/rolling-shutter/chainobserver/db/keyper" + corekeyperdatabase "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/database" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/epochkghandler" + gnosisdatabase "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/gnosis/database" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/broker" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/identitypreimage" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/slotticker" + "github.com/shutter-network/rolling-shutter/rolling-shutter/shdb" +) + +// Maximum age of a tx pointer in blocks before it is considered outdated. +const maxTxPointerAge = 2 + +var errZeroTxPointerAge = errors.New("tx pointer has age 0") + +func (kpr *Keyper) processNewSlot(ctx context.Context, slot slotticker.Slot) error { + return kpr.maybeTriggerDecryption(ctx, slot.Number) +} + +// maybeTriggerDecryption triggers decryption for the given slot if +// - it hasn't been triggered for this slot before and +// - the keyper is part of the corresponding keyper set. +func (kpr *Keyper) maybeTriggerDecryption(ctx context.Context, slot uint64) error { + if kpr.latestTriggeredSlot != nil && slot <= *kpr.latestTriggeredSlot { + return nil + } + kpr.latestTriggeredSlot = &slot + + fmt.Println("") + fmt.Println("") + fmt.Println(slot) + fmt.Println("") + fmt.Println("") + + gnosisKeyperDB := gnosisdatabase.New(kpr.dbpool) + syncedUntil, err := gnosisKeyperDB.GetTransactionSubmittedEventsSyncedUntil(ctx) + if err != nil && err != pgx.ErrNoRows { + // pgx.ErrNoRows is expected if we're not part of the keyper set (which is checked later). + // That's because non-keypers don't sync transaction submitted events. + return errors.Wrap(err, "failed to query transaction submitted sync status from db") + } + if syncedUntil.Slot >= int64(slot) { + // If we already synced the block for slot n before this slot has started on our clock, + // either the previous block proposer proposed early (ie is malicious) or our clocks are + // out of sync. In any case, it does not make sense to produce keys as the block has + // already been built, so we return an error. + return errors.Errorf("processing slot %d for which a block has already been processed", slot) + } + nextBlock := syncedUntil.BlockNumber + 1 + + queries := obskeyper.New(kpr.dbpool) + keyperSet, err := queries.GetKeyperSet(ctx, nextBlock) + if err == pgx.ErrNoRows { + log.Debug(). + Uint64("slot", slot). + Int64("block-number", nextBlock). + Msg("skipping slot as no keyper set has been found for it") + return nil + } + if err != nil { + return errors.Wrapf(err, "failed to query keyper set for block %d", nextBlock) + } + + // don't trigger if we're not part of the keyper set + if !keyperSet.Contains(kpr.config.GetAddress()) { + log.Debug(). + Uint64("slot", slot). + Int64("block-number", nextBlock). + Int64("keyper-set-index", keyperSet.KeyperConfigIndex). + Str("address", kpr.config.GetAddress().Hex()). + Msg("skipping slot as not part of keyper set") + return nil + } + + // don't trigger if the block proposer is not part of the validator registry + isRegistered, proposerIndex, err := kpr.isProposerRegistered(ctx, slot, uint64(nextBlock)) + if err != nil { + return err + } + if !isRegistered { + log.Debug(). + Uint64("slot", slot). + Uint64("proposer-index", proposerIndex). + Msg("skipping slot as proposer is not registered") + // Even if we don't trigger decryption, we still need to update the tx pointer or it will + // become outdated. + err := gnosisKeyperDB.SetTxPointerSlot(ctx, gnosisdatabase.SetTxPointerSlotParams{ + Eon: keyperSet.KeyperConfigIndex, + Slot: int64(slot), + }) + if err != nil { + return errors.Wrap(err, "failed to update tx pointer slot") + } + return nil + } + + return kpr.triggerDecryption(ctx, slot, nextBlock, &keyperSet) +} + +func (kpr *Keyper) isProposerRegistered(ctx context.Context, slot uint64, block uint64) (bool, uint64, error) { + epoch := medley.SlotToEpoch(slot, kpr.config.Gnosis.SlotsPerEpoch) + proposerDuties, err := kpr.beaconAPIClient.GetProposerDutiesByEpoch(ctx, epoch) + if err != nil { + return false, 0, err + } + if proposerDuties == nil { + return false, 0, errors.Errorf("no proposer duties found for slot %d in epoch %d", slot, epoch) + } + proposerDuty, err := proposerDuties.GetDutyForSlot(slot) + if err != nil { + return false, 0, err + } + proposerIndex := proposerDuty.ValidatorIndex + if proposerIndex > math.MaxInt64 { + return false, 0, errors.New("proposer index too big") + } + + db := gnosisdatabase.New(kpr.dbpool) + isRegistered, err := db.IsValidatorRegistered(ctx, gnosisdatabase.IsValidatorRegisteredParams{ + ValidatorIndex: int64(proposerDuty.ValidatorIndex), + BlockNumber: int64(block), + }) + if err == pgx.ErrNoRows { + return false, proposerIndex, nil + } + if err != nil { + return false, 0, errors.Wrapf(err, "failed to query registration status for validator %d", proposerDuty.ValidatorIndex) + } + return isRegistered, proposerDuty.ValidatorIndex, nil +} + +func (kpr *Keyper) getTxPointer(ctx context.Context, eon int64, slot int64, keyperConfigIndex int64) (int64, error) { + gnosisKeyperDB := gnosisdatabase.New(kpr.dbpool) + var txPointer, txPointerAge int64 + txPointerDB, err := gnosisKeyperDB.GetTxPointer(ctx, eon) + if err == pgx.ErrNoRows { + // The tx pointer is expected to be missing from the db if the eon has just started. In + // this case, we should initialize it to zero with an age of 1, ie decrypt starting with + // the first transaction. + // The tx pointer may also be missing if the keyper has been started late and no decryption + // key has been generated or received yet (receiving the keys message would update the + // pointer). In this case, the true age is unknown, as we only know the start block but + // not the start slot of the eon. However, we can ignore this edge case as it will be + // resolved automatically when the first keys message is received. If key generation + // continues to fail, eventually our tx pointer age will exceed the maximum value and we + // will start participating in the recovery process, albeit a bit late. + err := gnosisKeyperDB.SetTxPointer(ctx, gnosisdatabase.SetTxPointerParams{ + Eon: eon, + Slot: slot, + Value: 0, + }) + if err != nil { + return 0, errors.Wrap(err, "failed to initialize tx pointer") + } + txPointer = 0 + txPointerAge = 1 + } else if err != nil { + return 0, errors.Wrap(err, "failed to query tx pointer from db") + } else { + txPointer = txPointerDB.Value + txPointerAge = slot - txPointerDB.Slot + } + if txPointerAge == 0 { + // A pointer of age 0 means we already received the pointer from a DecryptionKeys message + // even though we haven't sent our shares yet. In that case, sending our shares is + // unnecessary. + return 0, errZeroTxPointerAge + } + // If the tx pointer is outdated, the system has failed to generate decryption keys (or at + // least we haven't received them). This either means not enough keypers are online or they + // don't agree on the current value of the tx pointer. In order to recover, we choose the + // current length of the transaction queue as the new tx pointer, as this is a value + // everyone can agree on. + isOutdated := txPointerAge > maxTxPointerAge + if isOutdated { + log.Warn(). + Int64("slot", slot). + Int64("eon", eon). + Int64("tx-pointer", txPointer). + Int64("tx-pointer-age", txPointerAge). + Msg("outdated tx pointer") + txPointer, err = gnosisKeyperDB.GetTransactionSubmittedEventCount(ctx, keyperConfigIndex) + if err == pgx.ErrNoRows { + txPointer = 0 + } else if err != nil { + return 0, errors.Wrap(err, "failed to query transaction submitted event count from db") + } + } + return txPointer, nil +} + +func (kpr *Keyper) triggerDecryption( + ctx context.Context, + slot uint64, + nextBlock int64, + keyperSet *obskeyper.KeyperSet, +) error { + gnosisKeyperDB := gnosisdatabase.New(kpr.dbpool) + coreKeyperDB := corekeyperdatabase.New(kpr.dbpool) + + eonStruct, err := coreKeyperDB.GetEonForBlockNumber(ctx, nextBlock) + if err != nil { + return errors.Wrapf(err, "failed to query eon for block number %d from db", nextBlock) + } + keyperConfigIndex := eonStruct.KeyperConfigIndex + + txPointer, err := kpr.getTxPointer(ctx, keyperConfigIndex, int64(slot), keyperSet.KeyperConfigIndex) + if err == errZeroTxPointerAge { + log.Warn(). + Uint64("slot", slot). + Int64("block-number", nextBlock). + Int64("eon", keyperConfigIndex). + Int64("tx-pointer", txPointer). + Msg("skipping trigger as tx pointer age is 0") + return nil + } else if err != nil { + return err + } + + identityPreimages, err := kpr.getDecryptionIdentityPreimages(ctx, slot, keyperSet.KeyperConfigIndex, txPointer) + if err != nil { + return err + } + err = gnosisKeyperDB.SetCurrentDecryptionTrigger(ctx, gnosisdatabase.SetCurrentDecryptionTriggerParams{ + Eon: keyperConfigIndex, + Slot: int64(slot), + TxPointer: txPointer, + IdentitiesHash: computeIdentitiesHash(identityPreimages), + }) + if err != nil { + return errors.Wrap(err, "failed to insert published tx pointer into db") + } + trigger := epochkghandler.DecryptionTrigger{ + BlockNumber: uint64(nextBlock), + IdentityPreimages: identityPreimages, + } + event := broker.NewEvent(&trigger) + log.Debug(). + Uint64("slot", slot). + Uint64("block-number", uint64(nextBlock)). + Int("num-identities", len(trigger.IdentityPreimages)). + Int64("tx-pointer", txPointer). + Msg("sending decryption trigger") + kpr.decryptionTriggerChannel <- event + + return nil +} + +func (kpr *Keyper) getDecryptionIdentityPreimages( + ctx context.Context, slot uint64, eon int64, txPointer int64, +) ([]identitypreimage.IdentityPreimage, error) { + identityPreimages := []identitypreimage.IdentityPreimage{} + + queries := gnosisdatabase.New(kpr.dbpool) + limitUint64 := kpr.config.Gnosis.EncryptedGasLimit/kpr.config.Gnosis.MinGasPerTransaction + 1 + if limitUint64 > math.MaxInt32 { + return identityPreimages, errors.New("gas limit too big") + } + limit := int32(limitUint64) + + events, err := queries.GetTransactionSubmittedEvents(ctx, gnosisdatabase.GetTransactionSubmittedEventsParams{ + Eon: eon, + Index: txPointer, + Limit: limit, + }) + if err != nil { + return nil, errors.Wrapf(err, "failed to query transaction submitted events from index %d", txPointer) + } + + identityPreimages = []identitypreimage.IdentityPreimage{ + makeSlotIdentityPreimage(slot), + } + gas := uint64(0) + for _, event := range events { + gas += uint64(event.GasLimit) + if gas > kpr.config.Gnosis.EncryptedGasLimit { + break + } + identityPreimage, err := transactionSubmittedEventToIdentityPreimage(event) + if err != nil { + return []identitypreimage.IdentityPreimage{}, err + } + identityPreimages = append(identityPreimages, identityPreimage) + } + + sortedIdentityPreimages := sortIdentityPreimages(identityPreimages) + + return sortedIdentityPreimages, nil +} + +func transactionSubmittedEventToIdentityPreimage( + event gnosisdatabase.TransactionSubmittedEvent, +) (identitypreimage.IdentityPreimage, error) { + sender, err := shdb.DecodeAddress(event.Sender) + if err != nil { + return identitypreimage.IdentityPreimage{}, errors.Wrap(err, "failed to decode sender address of transaction submitted event from db") + } + + var buf bytes.Buffer + buf.Write(event.IdentityPrefix) + buf.Write(sender.Bytes()) + + return identitypreimage.IdentityPreimage(buf.Bytes()), nil +} + +func makeSlotIdentityPreimage(slot uint64) identitypreimage.IdentityPreimage { + // 32 bytes of zeros plus the block number as 20 byte big endian (ie starting with lots of + // zeros as well). This ensures the block identity preimage is always alphanumerically before + // any transaction identity preimages, because sender addresses cannot be that small. + var buf bytes.Buffer + buf.Write(common.BigToHash(common.Big0).Bytes()) + buf.Write(common.BigToHash(new(big.Int).SetUint64(slot)).Bytes()[12:]) + + return identitypreimage.IdentityPreimage(buf.Bytes()) +} + +func sortIdentityPreimages(identityPreimages []identitypreimage.IdentityPreimage) []identitypreimage.IdentityPreimage { + sorted := make([]identitypreimage.IdentityPreimage, len(identityPreimages)) + copy(sorted, identityPreimages) + sort.Slice(sorted, func(i, j int) bool { + return bytes.Compare(sorted[i], sorted[j]) < 0 + }) + return sorted +} diff --git a/rolling-shutter/keyperimpl/gnosis/sequencersyncer.go b/rolling-shutter/keyperimpl/gnosis/sequencersyncer.go new file mode 100644 index 000000000..96ee5a705 --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/sequencersyncer.go @@ -0,0 +1,200 @@ +package gnosis + +import ( + "context" + "math" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + sequencerBindings "github.com/shutter-network/gnosh-contracts/gnoshcontracts/sequencer" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/gnosis/database" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley" + "github.com/shutter-network/rolling-shutter/rolling-shutter/shdb" +) + +// SequencerSyncer inserts transaction submitted events from the sequencer contract into the database. +type SequencerSyncer struct { + Contract *sequencerBindings.Sequencer + DBPool *pgxpool.Pool + ExecutionClient *ethclient.Client + GenesisSlotTimestamp uint64 + SecondsPerSlot uint64 +} + +// Sync fetches transaction submitted events from the sequencer contract and inserts them into the +// database. It starts at the end point of the previous call to sync (or 0 if it is the first call) +// and ends at the given block number. +func (s *SequencerSyncer) Sync(ctx context.Context, header *types.Header) error { + queries := database.New(s.DBPool) + syncedUntil, err := queries.GetTransactionSubmittedEventsSyncedUntil(ctx) + if err != nil && err != pgx.ErrNoRows { + return errors.Wrap(err, "failed to query transaction submitted events sync status") + } + var start uint64 + if err == pgx.ErrNoRows { + start = 0 + } else { + start = uint64(syncedUntil.BlockNumber + 1) + } + endBlock := header.Number.Uint64() + log.Debug(). + Uint64("start-block", start). + Uint64("end-block", endBlock). + Msg("syncing sequencer contract") + + syncRanges := medley.GetSyncRanges(start, endBlock, maxRequestBlockRange) + for _, r := range syncRanges { + err = s.syncRange(ctx, r[0], r[1]) + if err != nil { + return err + } + } + return nil +} + +func (s *SequencerSyncer) syncRange( + ctx context.Context, + start, + end uint64, +) error { + events, err := s.fetchEvents(ctx, start, end) + if err != nil { + return err + } + filteredEvents := s.filterEvents(events) + + header, err := s.ExecutionClient.HeaderByNumber(ctx, new(big.Int).SetUint64(end)) + if err != nil { + return errors.Wrap(err, "failed to get execution block header by number") + } + err = s.DBPool.BeginFunc(ctx, func(tx pgx.Tx) error { + err = s.insertTransactionSubmittedEvents(ctx, tx, filteredEvents) + if err != nil { + return err + } + + slot := medley.BlockTimestampToSlot(header.Time, s.GenesisSlotTimestamp, s.SecondsPerSlot) + return database.New(tx).SetTransactionSubmittedEventsSyncedUntil(ctx, database.SetTransactionSubmittedEventsSyncedUntilParams{ + BlockNumber: int64(end), + BlockHash: header.Hash().Bytes(), + Slot: int64(slot), + }) + }) + log.Info(). + Uint64("start-block", start). + Uint64("end-block", end). + Int("num-inserted-events", len(filteredEvents)). + Int("num-discarded-events", len(events)-len(filteredEvents)). + Msg("synced sequencer contract") + return nil +} + +func (s *SequencerSyncer) fetchEvents( + ctx context.Context, + start, + end uint64, +) ([]*sequencerBindings.SequencerTransactionSubmitted, error) { + opts := bind.FilterOpts{ + Start: start, + End: &end, + Context: ctx, + } + it, err := s.Contract.SequencerFilterer.FilterTransactionSubmitted(&opts) + if err != nil { + return nil, errors.Wrap(err, "failed to query transaction submitted events") + } + events := []*sequencerBindings.SequencerTransactionSubmitted{} + for it.Next() { + events = append(events, it.Event) + } + if it.Error() != nil { + return nil, errors.Wrap(it.Error(), "failed to iterate transaction submitted events") + } + return events, nil +} + +func (s *SequencerSyncer) filterEvents( + events []*sequencerBindings.SequencerTransactionSubmitted, +) []*sequencerBindings.SequencerTransactionSubmitted { + filteredEvents := []*sequencerBindings.SequencerTransactionSubmitted{} + for _, event := range events { + if event.Eon > math.MaxInt64 || + !event.GasLimit.IsInt64() { + log.Debug(). + Uint64("eon", event.Eon). + Uint64("block-number", event.Raw.BlockNumber). + Str("block-hash", event.Raw.BlockHash.Hex()). + Uint("tx-index", event.Raw.TxIndex). + Uint("log-index", event.Raw.Index). + Msg("ignoring transaction submitted event with high eon") + continue + } + filteredEvents = append(filteredEvents, event) + } + return filteredEvents +} + +// insertTransactionSubmittedEvents inserts the given events into the database and updates the +// transaction submitted event number accordingly. +func (s *SequencerSyncer) insertTransactionSubmittedEvents( + ctx context.Context, + tx pgx.Tx, + events []*sequencerBindings.SequencerTransactionSubmitted, +) error { + queries := database.New(tx) + nextEventIndices := make(map[uint64]int64) + for _, event := range events { + nextEventIndex, ok := nextEventIndices[event.Eon] + if !ok { + nextEventIndexFromDB, err := queries.GetTransactionSubmittedEventCount(ctx, int64(event.Eon)) + if err == pgx.ErrNoRows { + nextEventIndexFromDB = 0 + } else if err != nil { + return errors.Wrapf(err, "failed to query count of transaction submitted events for eon %d", event.Eon) + } + nextEventIndices[event.Eon] = nextEventIndexFromDB + nextEventIndex = nextEventIndexFromDB + } + + _, err := queries.InsertTransactionSubmittedEvent(ctx, database.InsertTransactionSubmittedEventParams{ + Index: nextEventIndex, + BlockNumber: int64(event.Raw.BlockNumber), + BlockHash: event.Raw.BlockHash[:], + TxIndex: int64(event.Raw.TxIndex), + LogIndex: int64(event.Raw.Index), + Eon: int64(event.Eon), + IdentityPrefix: event.IdentityPrefix[:], + Sender: shdb.EncodeAddress(event.Sender), + GasLimit: event.GasLimit.Int64(), + }) + if err != nil { + return errors.Wrap(err, "failed to insert transaction submitted event into db") + } + nextEventIndices[event.Eon]++ + log.Debug(). + Int64("index", nextEventIndex). + Uint64("block", event.Raw.BlockNumber). + Uint64("eon", event.Eon). + Hex("identityPrefix", event.IdentityPrefix[:]). + Hex("sender", event.Sender.Bytes()). + Uint64("gasLimit", event.GasLimit.Uint64()). + Msg("synced new transaction submitted event") + } + for eon, nextEventIndex := range nextEventIndices { + err := queries.SetTransactionSubmittedEventCount(ctx, database.SetTransactionSubmittedEventCountParams{ + Eon: int64(eon), + EventCount: nextEventIndex, + }) + if err != nil { + return err + } + } + return nil +} diff --git a/rolling-shutter/keyperimpl/gnosis/validatorsyncer.go b/rolling-shutter/keyperimpl/gnosis/validatorsyncer.go new file mode 100644 index 000000000..f83a2e221 --- /dev/null +++ b/rolling-shutter/keyperimpl/gnosis/validatorsyncer.go @@ -0,0 +1,268 @@ +package gnosis + +import ( + "context" + "math" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" + "github.com/pkg/errors" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + validatorRegistryBindings "github.com/shutter-network/gnosh-contracts/gnoshcontracts/validatorregistry" + blst "github.com/supranational/blst/bindings/go" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/gnosis/database" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/beaconapiclient" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/validatorregistry" +) + +const ( + ValidatorRegistrationMessageVersion = 0 + maxRequestBlockRange = 100000 +) + +type ValidatorSyncer struct { + Contract *validatorRegistryBindings.Validatorregistry + DBPool *pgxpool.Pool + BeaconAPIClient *beaconapiclient.Client + ExecutionClient *ethclient.Client + ChainID uint64 +} + +func (v *ValidatorSyncer) Sync(ctx context.Context, header *types.Header) error { + db := database.New(v.DBPool) + syncedUntil, err := db.GetValidatorRegistrationsSyncedUntil(ctx) + if err != nil && err != pgx.ErrNoRows { + return errors.Wrap(err, "failed to query validator registration sync status") + } + var start uint64 + if err == pgx.ErrNoRows { + start = 0 + } else { + start = uint64(syncedUntil.BlockNumber + 1) + } + endBlock := header.Number.Uint64() + log.Debug(). + Uint64("start-block", start). + Uint64("end-block", endBlock). + Msg("syncing validator registry") + + syncRanges := medley.GetSyncRanges(start, endBlock, maxRequestBlockRange) + for _, r := range syncRanges { + err = v.syncRange(ctx, r[0], r[1]) + if err != nil { + return err + } + } + return nil +} + +func (v *ValidatorSyncer) syncRange(ctx context.Context, start, end uint64) error { + db := database.New(v.DBPool) + events, err := v.fetchEvents(ctx, start, end) + if err != nil { + return err + } + filteredEvents, err := v.filterEvents(ctx, events) + if err != nil { + return err + } + header, err := v.ExecutionClient.HeaderByNumber(ctx, new(big.Int).SetUint64(end)) + if err != nil { + return errors.Wrap(err, "failed to get execution block header by number") + } + err = v.DBPool.BeginFunc(ctx, func(tx pgx.Tx) error { + err = v.insertEvents(ctx, tx, filteredEvents) + if err != nil { + return err + } + err = db.SetValidatorRegistrationsSyncedUntil(ctx, database.SetValidatorRegistrationsSyncedUntilParams{ + BlockNumber: int64(end), + BlockHash: header.Hash().Bytes(), + }) + if err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + log.Info(). + Uint64("start-block", start). + Uint64("end-block", end). + Int("num-inserted-events", len(filteredEvents)). + Int("num-discarded-events", len(events)-len(filteredEvents)). + Msg("synced validator registry") + return nil +} + +func (v *ValidatorSyncer) fetchEvents( + ctx context.Context, + start, + end uint64, +) ([]*validatorRegistryBindings.ValidatorregistryUpdated, error) { + opts := bind.FilterOpts{ + Start: start, + End: &end, + Context: ctx, + } + it, err := v.Contract.ValidatorregistryFilterer.FilterUpdated(&opts) + if err != nil { + return nil, errors.Wrap(err, "failed to query validator registry update events") + } + events := []*validatorRegistryBindings.ValidatorregistryUpdated{} + for it.Next() { + events = append(events, it.Event) + } + if it.Error() != nil { + return nil, errors.Wrap(it.Error(), "failed to iterate validator registry update events") + } + return events, nil +} + +func (v *ValidatorSyncer) filterEvents( + ctx context.Context, + events []*validatorRegistryBindings.ValidatorregistryUpdated, +) ([]*validatorRegistryBindings.ValidatorregistryUpdated, error) { + db := database.New(v.DBPool) + filteredEvents := []*validatorRegistryBindings.ValidatorregistryUpdated{} + for _, event := range events { + evLog := log.With(). + Hex("block-hash", event.Raw.BlockHash.Bytes()). + Uint64("block-number", event.Raw.BlockNumber). + Uint("tx-index", event.Raw.TxIndex). + Uint("log-index", event.Raw.Index). + Logger() + + msg := new(validatorregistry.RegistrationMessage) + err := msg.Unmarshal(event.Message) + if err != nil { + evLog.Warn(). + Err(err). + Msg("failed to unmarshal registration message") + continue + } + evLog = evLog.With().Uint64("validator-index", msg.ValidatorIndex).Logger() + + if !checkStaticRegistrationMessageFields(msg, v.ChainID, event.Raw.Address, evLog) { + continue + } + + latestNonce, err := db.GetValidatorRegistrationNonceBefore(ctx, database.GetValidatorRegistrationNonceBeforeParams{ + ValidatorIndex: int64(msg.ValidatorIndex), + BlockNumber: int64(event.Raw.BlockNumber), + TxIndex: int64(event.Raw.TxIndex), + LogIndex: int64(event.Raw.Index), + }) + if err != nil && err != pgx.ErrNoRows { + return nil, errors.Wrapf(err, "failed to query latest nonce for validator %d", msg.ValidatorIndex) + } + if err == pgx.ErrNoRows { + latestNonce = -1 + } + if msg.Nonce > math.MaxInt64 || int64(msg.Nonce) <= latestNonce { + evLog.Warn(). + Uint64("nonce", msg.Nonce). + Int64("latest-nonce", latestNonce). + Msg("ignoring registration message with invalid nonce") + continue + } + + validator, err := v.BeaconAPIClient.GetValidatorByIndex(ctx, "head", msg.ValidatorIndex) + if err != nil { + return nil, errors.Wrapf(err, "failed to get validator %d", msg.ValidatorIndex) + } + if validator == nil { + evLog.Warn().Msg("ignoring registration message for unknown validator") + continue + } + pubkey, err := validator.Data.Validator.GetPubkey() + if err != nil { + return nil, errors.Wrapf(err, "failed to get pubkey of validator %d", msg.ValidatorIndex) + } + sig := new(blst.P2Affine).Uncompress(event.Signature) + if sig == nil { + evLog.Warn().Msg("ignoring registration message with undecodable signature") + continue + } + validSignature := validatorregistry.VerifySignature(sig, pubkey, msg) + if !validSignature { + evLog.Warn().Msg("ignoring registration message with invalid signature") + continue + } + + filteredEvents = append(filteredEvents, event) + } + return filteredEvents, nil +} + +func (v *ValidatorSyncer) insertEvents(ctx context.Context, tx pgx.Tx, events []*validatorRegistryBindings.ValidatorregistryUpdated) error { + db := database.New(tx) + for _, event := range events { + msg := new(validatorregistry.RegistrationMessage) + err := msg.Unmarshal(event.Message) + if err != nil { + return errors.Wrap(err, "failed to unmarshal registration message") + } + err = db.InsertValidatorRegistration(ctx, database.InsertValidatorRegistrationParams{ + BlockNumber: int64(event.Raw.BlockNumber), + BlockHash: event.Raw.BlockHash.Bytes(), + TxIndex: int64(event.Raw.TxIndex), + LogIndex: int64(event.Raw.Index), + ValidatorIndex: int64(msg.ValidatorIndex), + Nonce: int64(msg.Nonce), + IsRegistration: msg.IsRegistration, + }) + if err != nil { + return errors.Wrap(err, "failed to insert validator registration into db") + } + } + return nil +} + +func checkStaticRegistrationMessageFields( + msg *validatorregistry.RegistrationMessage, + chainID uint64, + validatorRegistryAddress common.Address, + logger zerolog.Logger, +) bool { + if msg.Version != ValidatorRegistrationMessageVersion { + logger.Warn(). + Uint8("version", msg.Version). + Uint8("expected-version", ValidatorRegistrationMessageVersion). + Uint64("validator-index", msg.ValidatorIndex). + Msg("ignoring registration message with invalid version") + return false + } + if msg.ChainID != chainID { + logger.Warn(). + Uint64("chain-id", msg.ChainID). + Uint64("expected-chain-id", chainID). + Uint64("validator-index", msg.ValidatorIndex). + Msg("ignoring registration message with invalid chain ID") + return false + } + if msg.ValidatorRegistryAddress != validatorRegistryAddress { + logger.Warn(). + Hex("validator-registry-address", msg.ValidatorRegistryAddress.Bytes()). + Hex("expected-validator-registry-address", validatorRegistryAddress.Bytes()). + Uint64("validator-index", msg.ValidatorIndex). + Msg("ignoring registration message with invalid validator registry address") + return false + } + if msg.ValidatorIndex > math.MaxInt64 { + logger.Warn(). + Uint64("validator-index", msg.ValidatorIndex). + Msg("ignoring registration message with invalid validator index") + return false + } + return true +} diff --git a/rolling-shutter/keyperimpl/optimism/bootstrap/bootstrap.go b/rolling-shutter/keyperimpl/optimism/bootstrap/bootstrap.go new file mode 100644 index 000000000..439f69cc4 --- /dev/null +++ b/rolling-shutter/keyperimpl/optimism/bootstrap/bootstrap.go @@ -0,0 +1,56 @@ +package bootstrap + +import ( + "context" + "encoding/json" + "os" + + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "github.com/tendermint/tendermint/rpc/client/http" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/fx" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/event" + "github.com/shutter-network/rolling-shutter/rolling-shutter/shmsg" +) + +func BootstrapValidators(config *Config) error { + file, err := os.ReadFile(config.KeyperSetFilePath) + if err != nil { + log.Fatal().Err(err).Msg("failed to read keyper-set file") + } + ks := &event.KeyperSet{} + + err = json.Unmarshal(file, ks) + if err != nil { + log.Fatal().Err(err).Msg("failed to read parse keyper-set") + } + + ctx := context.Background() + + shmcl, err := http.New(config.ShuttermintURL, "/websocket") + if err != nil { + log.Fatal().Err(err).Msg("failed to connect to Shuttermint node") + } + + ms := fx.NewRPCMessageSender(shmcl, config.SigningKey.Key) + batchConfigMsg := shmsg.NewBatchConfig( + ks.ActivationBlock, + ks.Members, + ks.Threshold, + ks.Eon, + ) + + err = ms.SendMessage(ctx, batchConfigMsg) + if err != nil { + return errors.Errorf("Failed to send batch config message: %v", err) + } + + blockSeenMsg := shmsg.NewBlockSeen(ks.ActivationBlock) + err = ms.SendMessage(ctx, blockSeenMsg) + if err != nil { + return errors.Errorf("Failed to send start message: %v", err) + } + + return nil +} diff --git a/rolling-shutter/keyperimpl/optimism/bootstrap/config.go b/rolling-shutter/keyperimpl/optimism/bootstrap/config.go new file mode 100644 index 000000000..5e1e55862 --- /dev/null +++ b/rolling-shutter/keyperimpl/optimism/bootstrap/config.go @@ -0,0 +1,73 @@ +package bootstrap + +import ( + "crypto/rand" + "io" + + "github.com/ethereum/go-ethereum/common" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/configuration" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/keys" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/number" +) + +var _ configuration.Config = &Config{} + +func NewConfig() *Config { + c := &Config{} + c.Init() + return c +} + +func (c *Config) Init() { + c.SigningKey = &keys.ECDSAPrivate{} + c.ByActivationBlockNumber = number.NewBlockNumber(nil) +} + +type Config struct { + InstanceID uint64 `shconfig:",required"` + + JSONRPCURL string ` comment:"The op-geth JSON RPC endpoint"` + ByActivationBlockNumber *number.BlockNumber + KeyperSetManager common.Address + ByIndex *uint64 + KeyperSetFilePath string + + ShuttermintURL string + SigningKey *keys.ECDSAPrivate `shconfig:",required"` +} + +func (c *Config) Validate() error { + return nil +} + +func (c *Config) Name() string { + return "op-bootstrap" +} + +func (c *Config) SetDefaultValues() error { + c.JSONRPCURL = "http://localhost:8545" + c.ShuttermintURL = "http://localhost:26657" + c.KeyperSetFilePath = "keyperset.json" + c.ByActivationBlockNumber = nil + i := uint64(1) + c.ByIndex = &i + return nil +} + +func (c *Config) SetExampleValues() error { + err := c.SetDefaultValues() + if err != nil { + return err + } + c.SigningKey, err = keys.GenerateECDSAKey(rand.Reader) + if err != nil { + return err + } + c.InstanceID = 42 + return nil +} + +func (c Config) TOMLWriteHeader(_ io.Writer) (int, error) { + return 0, nil +} diff --git a/rolling-shutter/keyperimpl/optimism/bootstrap/keyperset.go b/rolling-shutter/keyperimpl/optimism/bootstrap/keyperset.go new file mode 100644 index 000000000..cbf30cee3 --- /dev/null +++ b/rolling-shutter/keyperimpl/optimism/bootstrap/keyperset.go @@ -0,0 +1,41 @@ +package bootstrap + +import ( + "context" + "encoding/json" + "errors" + "os" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/event" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/number" +) + +func GetKeyperSet(ctx context.Context, config *Config) error { + sl2, err := chainsync.NewClient( + ctx, + chainsync.WithClientURL(config.JSONRPCURL), + chainsync.WithKeyperSetManager(config.KeyperSetManager), + ) + if err != nil { + return err + } + var keyperSet *event.KeyperSet + switch { + case config.ByIndex == nil && config.ByActivationBlockNumber == nil: + keyperSet, err = sl2.GetKeyperSetForBlock(ctx, number.LatestBlock) + case config.ByIndex == nil && config.ByActivationBlockNumber != nil: + keyperSet, err = sl2.GetKeyperSetForBlock(ctx, config.ByActivationBlockNumber) + case config.ByIndex != nil && config.ByActivationBlockNumber == nil: + keyperSet, err = sl2.GetKeyperSetByIndex(ctx, *config.ByIndex) + case config.ByIndex != nil && config.ByActivationBlockNumber != nil: + return errors.New("can only retrieve keyper set by either index or activation-blocknumber") + default: + return nil + } + if err != nil { + return err + } + file, _ := json.MarshalIndent(keyperSet, "", " ") + return os.WriteFile(config.KeyperSetFilePath, file, 0o644) +} diff --git a/rolling-shutter/keyperimpl/optimism/config/config.go b/rolling-shutter/keyperimpl/optimism/config/config.go new file mode 100644 index 000000000..d66824f29 --- /dev/null +++ b/rolling-shutter/keyperimpl/optimism/config/config.go @@ -0,0 +1,72 @@ +package config + +import ( + "io" + + "github.com/ethereum/go-ethereum/common" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/kprconfig" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/configuration" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/metricsserver" + "github.com/shutter-network/rolling-shutter/rolling-shutter/p2p" +) + +var _ configuration.Config = &Config{} + +func NewConfig() *Config { + c := &Config{} + c.Init() + return c +} + +func (c *Config) Init() { + c.P2P = p2p.NewConfig() + c.Optimism = NewEthnodeConfig() + c.Shuttermint = kprconfig.NewShuttermintConfig() + c.Metrics = metricsserver.NewConfig() +} + +type Config struct { + InstanceID uint64 `shconfig:",required"` + DatabaseURL string `shconfig:",required" comment:"If it's empty, we use the standard PG_ environment variables"` + + HTTPEnabled bool + HTTPListenAddress string + + P2P *p2p.Config + Optimism *OptimismConfig + Shuttermint *kprconfig.ShuttermintConfig + Metrics *metricsserver.MetricsConfig +} + +func (c *Config) Validate() error { + return nil +} + +func (c *Config) Name() string { + return "op-keyper" +} + +func (c *Config) SetDefaultValues() error { + c.HTTPEnabled = false + c.HTTPListenAddress = ":3000" + return nil +} + +func (c *Config) SetExampleValues() error { + err := c.SetDefaultValues() + if err != nil { + return err + } + c.InstanceID = 42 + c.DatabaseURL = "postgres://pguser:pgpassword@localhost:5432/shutter" + return nil +} + +func (c Config) TOMLWriteHeader(_ io.Writer) (int, error) { + return 0, nil +} + +func (c *Config) GetAddress() common.Address { + return c.Optimism.PrivateKey.EthereumAddress() +} diff --git a/rolling-shutter/keyperimpl/optimism/config/ethereum.go b/rolling-shutter/keyperimpl/optimism/config/ethereum.go new file mode 100644 index 000000000..c72dbdd46 --- /dev/null +++ b/rolling-shutter/keyperimpl/optimism/config/ethereum.go @@ -0,0 +1,57 @@ +package config + +import ( + "crypto/rand" + "fmt" + "io" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/configuration" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/keys" +) + +var _ configuration.Config = &OptimismConfig{} + +func NewEthnodeConfig() *OptimismConfig { + c := &OptimismConfig{} + c.Init() + return c +} + +type OptimismConfig struct { + PrivateKey *keys.ECDSAPrivate `shconfig:",required"` + JSONRPCURL string ` comment:"The op-geth JSON RPC endpoint"` +} + +func (c *OptimismConfig) Init() { + c.PrivateKey = &keys.ECDSAPrivate{} +} + +func (c *OptimismConfig) Name() string { + return "ethnode" +} + +func (c *OptimismConfig) Validate() error { + return nil +} + +func (c *OptimismConfig) SetDefaultValues() error { + c.JSONRPCURL = "http://127.0.0.1:8545/" + return nil +} + +func (c *OptimismConfig) SetExampleValues() error { + err := c.SetDefaultValues() + if err != nil { + return err + } + + c.PrivateKey, err = keys.GenerateECDSAKey(rand.Reader) + if err != nil { + return err + } + return nil +} + +func (c OptimismConfig) TOMLWriteHeader(w io.Writer) (int, error) { + return fmt.Fprintf(w, "# Ethereum address: %s\n", c.PrivateKey.EthereumAddress()) +} diff --git a/rolling-shutter/keyperimpl/optimism/database/definition.go b/rolling-shutter/keyperimpl/optimism/database/definition.go new file mode 100644 index 000000000..befb8550b --- /dev/null +++ b/rolling-shutter/keyperimpl/optimism/database/definition.go @@ -0,0 +1,11 @@ +package database + +import ( + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/database" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/db" +) + +var Definition = db.NewAggregateDefinition( + "opkeyper", + database.Definition, +) diff --git a/rolling-shutter/keyperimpl/optimism/keyper.go b/rolling-shutter/keyperimpl/optimism/keyper.go new file mode 100644 index 000000000..922b1e784 --- /dev/null +++ b/rolling-shutter/keyperimpl/optimism/keyper.go @@ -0,0 +1,177 @@ +package optimism + +import ( + "context" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + + obskeyper "github.com/shutter-network/rolling-shutter/rolling-shutter/chainobserver/db/keyper" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/epochkghandler" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/kprconfig" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/optimism/config" + "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/optimism/database" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/broker" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync" + syncevent "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/event" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/configuration" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/db" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/identitypreimage" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" + "github.com/shutter-network/rolling-shutter/rolling-shutter/shdb" +) + +var ErrParseKeyperSet = errors.New("can't parse KeyperSet") + +type Keyper struct { + core *keyper.KeyperCore + l2Client *chainsync.Client + dbpool *pgxpool.Pool + config *config.Config + + trigger chan<- *broker.Event[*epochkghandler.DecryptionTrigger] +} + +func New(c *config.Config) (*Keyper, error) { //nolint:unparam + return &Keyper{ + config: c, + }, nil +} + +func (kpr *Keyper) Start(ctx context.Context, runner service.Runner) error { + var err error + + dbpool, err := db.Connect(ctx, runner, kpr.config.DatabaseURL, database.Definition.Name()) + if err != nil { + return errors.Wrap(err, "failed to connect to database") + } + kpr.dbpool = dbpool + + trigger := make(chan *broker.Event[*epochkghandler.DecryptionTrigger]) + kpr.trigger = trigger + + ethConfig := configuration.NewEthnodeConfig() + ethConfig.EthereumURL = kpr.config.Optimism.JSONRPCURL + ethConfig.PrivateKey = kpr.config.Optimism.PrivateKey + kpr.core, err = keyper.New( + &kprconfig.Config{ + InstanceID: kpr.config.InstanceID, + DatabaseURL: kpr.config.DatabaseURL, + HTTPEnabled: kpr.config.HTTPEnabled, + HTTPListenAddress: kpr.config.HTTPListenAddress, + P2P: kpr.config.P2P, + Ethereum: ethConfig, + Shuttermint: kpr.config.Shuttermint, + Metrics: kpr.config.Metrics, + }, + trigger, + keyper.WithDBPool(dbpool), + keyper.NoBroadcastEonPublicKey(), + keyper.WithEonPublicKeyHandler(kpr.newEonPublicKey), + ) + if err != nil { + return errors.Wrap(err, "can't instantiate keyper core") + } + // TODO: wrap the logger and pass in + kpr.l2Client, err = chainsync.NewClient( + ctx, + chainsync.WithClientURL(kpr.config.Optimism.JSONRPCURL), + chainsync.WithSyncNewBlock(kpr.newBlock), + chainsync.WithSyncNewKeyperSet(kpr.newKeyperSet), + chainsync.WithPrivateKey(kpr.config.Optimism.PrivateKey.Key), + ) + if err != nil { + return err + } + return runner.StartService(kpr.core, kpr.l2Client) +} + +func (kpr *Keyper) newBlock(_ context.Context, ev *syncevent.LatestBlock) error { + log.Info(). + Int64("number", ev.Number.Int64()). + Str("hash", ev.BlockHash.Hex()). + Msg("new latest unsafe head on L2") + + // TODO: sanity checks + + latestBlockNumber := ev.Number.Uint64() + idPreimage := identitypreimage.Uint64ToIdentityPreimage(latestBlockNumber + 1) + trig := &epochkghandler.DecryptionTrigger{ + BlockNumber: latestBlockNumber + 1, + IdentityPreimages: []identitypreimage.IdentityPreimage{idPreimage}, + } + + // TODO: what to do if this blocks? + kpr.trigger <- broker.NewEvent(trig) + return nil +} + +func (kpr *Keyper) newKeyperSet(ctx context.Context, ev *syncevent.KeyperSet) error { + log.Info(). + Uint64("activation-block", ev.ActivationBlock). + Uint64("eon", ev.Eon). + Msg("new keyper set added") + return kpr.dbpool.BeginFunc(ctx, func(tx pgx.Tx) error { + obskeyperdb := obskeyper.New(tx) + + keyperConfigIndex, err := medley.Uint64ToInt64Safe(ev.Eon) + if err != nil { + return errors.Wrap(err, ErrParseKeyperSet.Error()) + } + activationBlockNumber, err := medley.Uint64ToInt64Safe(ev.ActivationBlock) + if err != nil { + return errors.Wrap(err, ErrParseKeyperSet.Error()) + } + threshold, err := medley.Uint64ToInt64Safe(ev.Threshold) + if err != nil { + return errors.Wrap(err, ErrParseKeyperSet.Error()) + } + // XXX: does this work when the memberset is empty? + return obskeyperdb.InsertKeyperSet(ctx, obskeyper.InsertKeyperSetParams{ + KeyperConfigIndex: keyperConfigIndex, + ActivationBlockNumber: activationBlockNumber, + Keypers: shdb.EncodeAddresses(ev.Members), + Threshold: int32(threshold), + }) + }) +} + +func (kpr *Keyper) newEonPublicKey(ctx context.Context, pubKey keyper.EonPublicKey) error { + log.Info(). + Uint64("eon", pubKey.Eon). + Uint64("activation-block", pubKey.ActivationBlock). + Bytes("pub-key", pubKey.PublicKey). + Msg("new eon pk") + // Currently all keypers call this and race to call this function first. + // For now this is fine, but a keyper should only send a transaction if + // the key is not set yet. + // Best would be a coordinatated leader election who will broadcast the key. + // FIXME: the syncer receives an empty key byte. + // Is this already + tx, err := kpr.l2Client.BroadcastEonKey(ctx, pubKey.Eon, pubKey.PublicKey) + if err != nil { + log.Error().Err(err).Msg("error broadcasting eon public key") + return errors.Wrap(err, "error broadcasting eon public-key") + } + log.Info(). + Str("hash", tx.Hash().Hex()). + Msg("sent eon pubkey transaction") + + receipt, err := bind.WaitMined(ctx, kpr.l2Client, tx) + if err != nil { + log.Error().Err(err).Msg("error waiting for pubkey tx mined") + return err + } + // NOCHECKIN: log the JSON receipt or only specific fields + log.Info(). + Interface("receipt", receipt). + Msg("eon pubkey transaction mined") + // TODO: + // wait / confirm of tx, otherwise resend + return nil +} diff --git a/rolling-shutter/keyperimpl/optimism/logger.go b/rolling-shutter/keyperimpl/optimism/logger.go new file mode 100644 index 000000000..874a91958 --- /dev/null +++ b/rolling-shutter/keyperimpl/optimism/logger.go @@ -0,0 +1,4 @@ +package optimism + +// TODO: wrap the zerolog logger. +type LoggerAdapter struct{} diff --git a/rolling-shutter/keyperimpl/optimism/setup_test.go b/rolling-shutter/keyperimpl/optimism/setup_test.go new file mode 100644 index 000000000..7881d0543 --- /dev/null +++ b/rolling-shutter/keyperimpl/optimism/setup_test.go @@ -0,0 +1,43 @@ +package optimism + +import ( + "crypto/ecdsa" + + "github.com/ethereum/go-ethereum/common" + ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/pkg/errors" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/testsetup" +) + +func init() { + var err error + testConfig.collatorKey, err = ethcrypto.GenerateKey() + if err != nil { + panic(errors.Wrap(err, "ethcrypto.GenerateKey failed")) + } +} + +type TestConfig struct { + collatorKey *ecdsa.PrivateKey +} + +var testConfig = &TestConfig{} + +func (TestConfig) GetAddress() common.Address { + return common.HexToAddress("0x2222222222222222222222222222222222222222") +} + +func (TestConfig) GetInstanceID() uint64 { + return 55 +} + +func (TestConfig) GetEon() uint64 { + return 22 +} + +func (c *TestConfig) GetCollatorKey() *ecdsa.PrivateKey { + return testConfig.collatorKey +} + +var _ testsetup.TestConfig = &TestConfig{} diff --git a/rolling-shutter/keyperimpl/rollup/trigger_test.go b/rolling-shutter/keyperimpl/rollup/trigger_test.go index 159a6ab30..6425b5bdc 100644 --- a/rolling-shutter/keyperimpl/rollup/trigger_test.go +++ b/rolling-shutter/keyperimpl/rollup/trigger_test.go @@ -47,10 +47,11 @@ func TestHandleDecryptionTriggerIntegration(t *testing.T) { assert.NilError(t, err) sender.AddMessageHandler(triggerHandler) - group := service.RunBackground( + group, cleanup := service.RunBackground( ctx, sender, ) + defer cleanup() testsetup.InitializeEon(ctx, t, dbpool, config, keyperIndex) diff --git a/rolling-shutter/medley/beaconapiclient/beaconapiclient.go b/rolling-shutter/medley/beaconapiclient/beaconapiclient.go new file mode 100644 index 000000000..58c94ef64 --- /dev/null +++ b/rolling-shutter/medley/beaconapiclient/beaconapiclient.go @@ -0,0 +1,22 @@ +package beaconapiclient + +import ( + "net/http" + "net/url" +) + +type Client struct { + c *http.Client + url *url.URL +} + +func New(rawURL string) (*Client, error) { + parsedURL, err := url.Parse(rawURL) + if err != nil { + return nil, err + } + return &Client{ + c: &http.Client{}, + url: parsedURL, + }, nil +} diff --git a/rolling-shutter/medley/beaconapiclient/getproposerduties.go b/rolling-shutter/medley/beaconapiclient/getproposerduties.go new file mode 100644 index 000000000..732166a95 --- /dev/null +++ b/rolling-shutter/medley/beaconapiclient/getproposerduties.go @@ -0,0 +1,69 @@ +package beaconapiclient + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/pkg/errors" +) + +type GetProposerDutiesResponse struct { + ExecutionOptimistic bool `json:"execution_optimistic"` + DependentRoot string `json:"dependent_root"` + Data []ProposerDuty `json:"data"` +} + +type ProposerDuty struct { + // Pubkey blst.P1Affine `json:"pubkey"` + Pubkey string `json:"pubkey"` + ValidatorIndex uint64 `json:"validator_index,string"` + Slot uint64 `json:"slot,string"` +} + +func (c *Client) GetProposerDutiesByEpoch( + ctx context.Context, + epoch uint64, +) (*GetProposerDutiesResponse, error) { + path := c.url.JoinPath("/eth/v1/validator/duties/proposer/", fmt.Sprint(epoch)) + req, err := http.NewRequestWithContext(ctx, "GET", path.String(), http.NoBody) + if err != nil { + return nil, err + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, errors.Wrapf(err, "failed to get proposer duties for epoch %d from consensus node", epoch) + } + defer res.Body.Close() + + if res.StatusCode == http.StatusNotFound { + return nil, nil + } + if res.StatusCode != http.StatusOK { + return nil, errors.Wrapf(err, "failed to get proposer duties for epoch %d from consensus node", epoch) + } + + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, errors.Wrapf(err, "failed to read consensus client response body") + } + + response := new(GetProposerDutiesResponse) + err = json.Unmarshal(body, response) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal consensus client response body") + } + + return response, nil +} + +func (r *GetProposerDutiesResponse) GetDutyForSlot(slot uint64) (ProposerDuty, error) { + for _, duty := range r.Data { + if duty.Slot == slot { + return duty, nil + } + } + return ProposerDuty{}, errors.Errorf("consensus client response does not contain duty for slot %d", slot) +} diff --git a/rolling-shutter/medley/beaconapiclient/getvalidator.go b/rolling-shutter/medley/beaconapiclient/getvalidator.go new file mode 100644 index 000000000..fb52cd92f --- /dev/null +++ b/rolling-shutter/medley/beaconapiclient/getvalidator.go @@ -0,0 +1,94 @@ +package beaconapiclient + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/pkg/errors" + blst "github.com/supranational/blst/bindings/go" +) + +type GetValidatorByIndexResponse struct { + ExecutionOptimistic bool `json:"execution_optimistic"` + Finalized bool `json:"finalized"` + Data ValidatorData +} + +type ValidatorData struct { + Index uint64 `json:"index,string"` + Balance uint64 `json:"balance,string"` + Status string `json:"status"` + Validator Validator +} + +type Validator struct { + PubkeyHex string `json:"pubkey"` + WithdrawalCredentials string `json:"withdrawal_credentials"` + EffectiveBalance uint64 `json:"effective_balance,string"` + Slashed bool `json:"slashed"` + ActivationEligibilityEpoch uint64 `json:"activation_eligibility_epoch,string"` + ActivationEpoch uint64 `json:"activation_epoch,string"` + ExitEpoch uint64 `json:"exit_epoch,string"` + WithdrawalEpoch uint64 `json:"withdrawal_epoch,string"` +} + +func (c *Client) GetValidatorByIndex( + ctx context.Context, + stateID string, + validatorIndex uint64, +) (*GetValidatorByIndexResponse, error) { + path := c.url.JoinPath("/eth/v1/beacon/states/", stateID, "/validators/", fmt.Sprint(validatorIndex)) + req, err := http.NewRequestWithContext(ctx, "GET", path.String(), http.NoBody) + if err != nil { + return nil, err + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, errors.Wrap(err, "failed to get validator by index from consensus node") + } + defer res.Body.Close() + + if res.StatusCode == http.StatusNotFound { + return nil, nil + } + if res.StatusCode != http.StatusOK { + return nil, errors.Wrap(err, "failed to get validator by index from consensus node") + } + + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, errors.Wrapf(err, "failed to read consensus client response body") + } + + response := new(GetValidatorByIndexResponse) + err = json.Unmarshal(body, response) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal consensus client response body") + } + + return response, nil +} + +func (v *Validator) GetPubkey() (*blst.P1Affine, error) { + pubkeyHex := v.PubkeyHex + if pubkeyHex[:2] == "0x" { + pubkeyHex = pubkeyHex[2:] + } + + pubkeyBytes, err := hex.DecodeString(pubkeyHex) + if err != nil { + return nil, errors.Wrap(err, "failed to hex decode validator pubkey") + } + + pubkey := new(blst.P1Affine) + pubkey = pubkey.Uncompress(pubkeyBytes) + if pubkey == nil { + return nil, errors.New("failed to deserialize pubkey") + } + + return pubkey, nil +} diff --git a/rolling-shutter/medley/chainsync/client.go b/rolling-shutter/medley/chainsync/client.go new file mode 100644 index 000000000..6bd674905 --- /dev/null +++ b/rolling-shutter/medley/chainsync/client.go @@ -0,0 +1,150 @@ +package chainsync + +import ( + "context" + "crypto/ecdsa" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/pkg/errors" + "github.com/shutter-network/shop-contracts/bindings" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/client" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/event" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/syncer" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/number" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/logger" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" +) + +var noopLogger = &logger.NoopLogger{} + +var ErrServiceNotInstantiated = errors.New("service is not instantiated, pass a handler function option") + +type Client struct { + client.SyncEthereumClient + log log.Logger + + options *options + chainID *big.Int + privKey *ecdsa.PrivateKey + + KeyperSetManager *bindings.KeyperSetManager + KeyBroadcast *bindings.KeyBroadcastContract + + sssync *syncer.ShutterStateSyncer + kssync *syncer.KeyperSetSyncer + uhsync *syncer.UnsafeHeadSyncer + epksync *syncer.EonPubKeySyncer + + services []service.Service +} + +func NewClient(ctx context.Context, options ...Option) (*Client, error) { + opts := defaultOptions() + for _, option := range options { + err := option(opts) + if err != nil { + return nil, err + } + } + + err := opts.verify() + if err != nil { + return nil, err + } + + c := &Client{ + log: noopLogger, + services: []service.Service{}, + } + err = opts.apply(ctx, c) + if err != nil { + return nil, err + } + c.options = opts + return c, nil +} + +func (s *Client) getServices() []service.Service { + return s.services +} + +func (s *Client) GetShutterState(ctx context.Context) (*event.ShutterState, error) { + if s.sssync == nil { + return nil, errors.Wrap(ErrServiceNotInstantiated, "ShutterStateSyncer service not instantiated") + } + opts := &bind.CallOpts{ + Context: ctx, + } + return s.sssync.GetShutterState(ctx, opts) +} + +func (s *Client) GetKeyperSetByIndex(ctx context.Context, index uint64) (*event.KeyperSet, error) { + if s.kssync == nil { + return nil, errors.Wrap(ErrServiceNotInstantiated, "KeyperSetSyncer service not instantiated") + } + opts := &bind.CallOpts{ + Context: ctx, + } + return s.kssync.GetKeyperSetByIndex(ctx, opts, index) +} + +func (s *Client) GetKeyperSetForBlock(ctx context.Context, b *number.BlockNumber) (*event.KeyperSet, error) { + if s.kssync == nil { + return nil, errors.Wrap(ErrServiceNotInstantiated, "KeyperSetSyncer service not instantiated") + } + opts := &bind.CallOpts{ + Context: ctx, + } + return s.kssync.GetKeyperSetForBlock(ctx, opts, b) +} + +func (s *Client) GetEonPubKeyForEon(ctx context.Context, eon uint64) (*event.EonPublicKey, error) { + if s.sssync == nil { + return nil, errors.Wrap(ErrServiceNotInstantiated, "ShutterStateSyncer service not instantiated") + } + opts := &bind.CallOpts{ + Context: ctx, + } + return s.epksync.GetEonPubKeyForEon(ctx, opts, eon) +} + +func (s *Client) BroadcastEonKey(ctx context.Context, eon uint64, eonPubKey []byte) (*types.Transaction, error) { + // TODO: first do a getEonKey. If we already have this key, do nothing, + // if we have a different key, error + // don't do a transaction + // s.KeyBroadcast.GetEonKey(eon) + if s.privKey == nil { + return nil, errors.New("can't broadcast eon public-key, client does not have a signer set") + } + chainID, err := s.ChainID(ctx) + if err != nil { + return nil, errors.Wrap(err, "retrieve chain id") + } + opts, err := bind.NewKeyedTransactorWithChainID(s.privKey, chainID) + if err != nil { + return nil, errors.Wrap(err, "construct signer transaction opts") + } + opts.Context = ctx + return s.KeyBroadcast.BroadcastEonKey(opts, eon, eonPubKey) +} + +// ChainID returns the chainid of the underlying L2 chain. +// This value is cached, since it is not expected to change. +func (s *Client) ChainID(ctx context.Context) (*big.Int, error) { + if s.chainID == nil { + cid, err := s.SyncEthereumClient.ChainID(ctx) + if err != nil { + return nil, err + } + s.chainID = cid + } + return s.chainID, nil +} + +func (s *Client) Start(_ context.Context, runner service.Runner) error { + return runner.StartService(s.getServices()...) +} diff --git a/rolling-shutter/medley/chainsync/client/client.go b/rolling-shutter/medley/chainsync/client/client.go new file mode 100644 index 000000000..4924bc8c9 --- /dev/null +++ b/rolling-shutter/medley/chainsync/client/client.go @@ -0,0 +1,60 @@ +package client + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +type FullEthereumClient interface { + Close() + ChainID(ctx context.Context) (*big.Int, error) + BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) + BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) + BlockNumber(ctx context.Context) (uint64, error) + PeerCount(ctx context.Context) (uint64, error) + HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) + HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) + TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) + TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) + TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) + TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) + TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) + SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) + SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) + NetworkID(ctx context.Context) (*big.Int, error) + BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) + StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) + CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) + NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) + FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) + SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) + PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) + PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error) + PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) + PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) + PendingTransactionCount(ctx context.Context) (uint, error) + CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) + CallContractAtHash(ctx context.Context, msg ethereum.CallMsg, blockHash common.Hash) ([]byte, error) + PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) + SuggestGasPrice(ctx context.Context) (*big.Int, error) + SuggestGasTipCap(ctx context.Context) (*big.Int, error) + EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) + SendTransaction(ctx context.Context, tx *types.Transaction) error +} + +type SyncEthereumClient interface { + Close() + ChainID(ctx context.Context) (*big.Int, error) + BlockNumber(ctx context.Context) (uint64, error) + HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) + HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) + SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) + FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) + SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) + CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) + TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) +} diff --git a/rolling-shutter/medley/chainsync/client/test.go b/rolling-shutter/medley/chainsync/client/test.go new file mode 100644 index 000000000..d436ac884 --- /dev/null +++ b/rolling-shutter/medley/chainsync/client/test.go @@ -0,0 +1,170 @@ +package client + +import ( + "context" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +var ErrNotImplemented = errors.New("not implemented") + +var _ SyncEthereumClient = &TestClient{} + +type TestClient struct { + headerChain []*types.Header + latestHeadIndex int + intialProgress bool + latestHeadEmitter []chan<- *types.Header + latestHeadSubscription []*Subscription +} + +func NewSubscription(idx int) *Subscription { + return &Subscription{ + idx: idx, + err: make(chan error, 1), + } +} + +type Subscription struct { + idx int + err chan error +} + +func (su *Subscription) Unsubscribe() { + // TODO: not implemented yet, but we don't want to panic +} + +func (su *Subscription) Err() <-chan error { + return su.err +} + +type TestClientController struct { + c *TestClient +} + +func NewTestClient() (*TestClient, *TestClientController) { + c := &TestClient{ + headerChain: []*types.Header{}, + latestHeadIndex: 0, + } + ctrl := &TestClientController{c} + return c, ctrl +} + +func (c *TestClientController) ProgressHead() bool { + if c.c.latestHeadIndex >= len(c.c.headerChain)-1 { + return false + } + c.c.latestHeadIndex++ + return true +} + +func (c *TestClientController) EmitEvents(ctx context.Context) error { + if len(c.c.latestHeadEmitter) == 0 { + return nil + } + h := c.c.getLatestHeader() + for _, em := range c.c.latestHeadEmitter { + select { + case em <- h: + case <-ctx.Done(): + return ctx.Err() + } + } + return nil +} + +func (c *TestClientController) AppendNextHeaders(h ...*types.Header) { + c.c.headerChain = append(c.c.headerChain, h...) +} + +func (t *TestClient) ChainID(_ context.Context) (*big.Int, error) { + return big.NewInt(42), nil +} + +func (t *TestClient) Close() { + // TODO: cleanup +} + +func (t *TestClient) getLatestHeader() *types.Header { + if len(t.headerChain) == 0 { + return nil + } + return t.headerChain[t.latestHeadIndex] +} + +func (t *TestClient) searchBlock(f func(*types.Header) bool) *types.Header { + for i := t.latestHeadIndex; i >= 0; i-- { + h := t.headerChain[i] + if f(h) { + return h + } + } + return nil +} + +func (t *TestClient) searchBlockByNumber(number *big.Int) *types.Header { + return t.searchBlock( + func(h *types.Header) bool { + return h.Number.Cmp(number) == 0 + }) +} + +func (t *TestClient) searchBlockByHash(hash common.Hash) *types.Header { + return t.searchBlock( + func(h *types.Header) bool { + return hash.Cmp(h.Hash()) == 0 + }) +} + +func (t *TestClient) BlockNumber(_ context.Context) (uint64, error) { + return t.getLatestHeader().Nonce.Uint64(), nil +} + +func (t *TestClient) HeaderByHash(_ context.Context, hash common.Hash) (*types.Header, error) { + h := t.searchBlockByHash(hash) + if h == nil { + return nil, errors.New("header not found") + } + return h, nil +} + +func (t *TestClient) HeaderByNumber(_ context.Context, number *big.Int) (*types.Header, error) { + if number == nil { + return t.getLatestHeader(), nil + } + h := t.searchBlockByNumber(number) + if h == nil { + return nil, errors.New("header not found") + } + return h, nil +} + +func (t *TestClient) SubscribeNewHead(_ context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { + t.latestHeadEmitter = append(t.latestHeadEmitter, ch) + su := NewSubscription(len(t.latestHeadSubscription) - 1) + t.latestHeadSubscription = append(t.latestHeadSubscription, su) + // TODO: unsubscribe and deleting from the array + // TODO: filling error promise in the subscription + return su, nil +} + +func (t *TestClient) FilterLogs(_ context.Context, _ ethereum.FilterQuery) ([]types.Log, error) { + panic(ErrNotImplemented) +} + +func (t *TestClient) SubscribeFilterLogs(_ context.Context, _ ethereum.FilterQuery, _ chan<- types.Log) (ethereum.Subscription, error) { + panic(ErrNotImplemented) +} + +func (t *TestClient) CodeAt(_ context.Context, _ common.Address, _ *big.Int) ([]byte, error) { + panic(ErrNotImplemented) +} + +func (t *TestClient) TransactionReceipt(_ context.Context, _ common.Hash) (*types.Receipt, error) { + panic(ErrNotImplemented) +} diff --git a/rolling-shutter/medley/chainsync/event/events.go b/rolling-shutter/medley/chainsync/event/events.go new file mode 100644 index 000000000..9f7c3b014 --- /dev/null +++ b/rolling-shutter/medley/chainsync/event/events.go @@ -0,0 +1,35 @@ +package event + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/number" +) + +type ( + KeyperSet struct { + ActivationBlock uint64 + Members []common.Address + Threshold uint64 + Eon uint64 + + AtBlockNumber *number.BlockNumber `json:",omitempty"` + } + EonPublicKey struct { + Eon uint64 + Key []byte + + AtBlockNumber *number.BlockNumber + } + ShutterState struct { + Active bool + + AtBlockNumber *number.BlockNumber `json:",omitempty"` + } + LatestBlock struct { + Number *number.BlockNumber + BlockHash common.Hash + Header *types.Header + } +) diff --git a/rolling-shutter/medley/chainsync/event/handler.go b/rolling-shutter/medley/chainsync/event/handler.go new file mode 100644 index 000000000..60f5758da --- /dev/null +++ b/rolling-shutter/medley/chainsync/event/handler.go @@ -0,0 +1,10 @@ +package event + +import "context" + +type ( + KeyperSetHandler func(context.Context, *KeyperSet) error + EonPublicKeyHandler func(context.Context, *EonPublicKey) error + BlockHandler func(context.Context, *LatestBlock) error + ShutterStateHandler func(context.Context, *ShutterState) error +) diff --git a/rolling-shutter/medley/chainsync/options.go b/rolling-shutter/medley/chainsync/options.go new file mode 100644 index 000000000..9a7e43473 --- /dev/null +++ b/rolling-shutter/medley/chainsync/options.go @@ -0,0 +1,262 @@ +package chainsync + +import ( + "context" + "crypto/ecdsa" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/pkg/errors" + "github.com/shutter-network/shop-contracts/bindings" + "github.com/shutter-network/shop-contracts/predeploy" + + syncclient "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/client" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/event" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/syncer" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/number" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" +) + +type Option func(*options) error + +type options struct { + keyperSetManagerAddress *common.Address + keyBroadcastContractAddress *common.Address + clientURL string + ethClient syncclient.FullEthereumClient + logger log.Logger + runner service.Runner + syncStart *number.BlockNumber + fetchActivesAtSyncStart bool + privKey *ecdsa.PrivateKey + + handlerShutterState event.ShutterStateHandler + handlerKeyperSet event.KeyperSetHandler + handlerEonPublicKey event.EonPublicKeyHandler + handlerBlock event.BlockHandler +} + +func (o *options) verify() error { + if o.clientURL != "" && o.ethClient != nil { + return errors.New("'WithClient' and 'WithClientURL' options are mutually exclusive") + } + if o.clientURL == "" && o.ethClient == nil { + return errors.New("either 'WithClient' or 'WithClientURL' options are expected") + } + // TODO: check for the existence of the contract addresses depending on + // what handlers are not nil + return nil +} + +func (o *options) applyHandler(c *Client) error { + var err error + syncedServices := []syncer.ManualFilterHandler{} + + c.KeyperSetManager, err = bindings.NewKeyperSetManager(*o.keyperSetManagerAddress, o.ethClient) + if err != nil { + return err + } + c.kssync = &syncer.KeyperSetSyncer{ + Client: o.ethClient, + Contract: c.KeyperSetManager, + Log: c.log, + Handler: o.handlerKeyperSet, + } + if o.handlerKeyperSet != nil { + syncedServices = append(syncedServices, c.kssync) + } + + c.KeyBroadcast, err = bindings.NewKeyBroadcastContract(*o.keyBroadcastContractAddress, o.ethClient) + if err != nil { + return err + } + c.epksync = &syncer.EonPubKeySyncer{ + Client: o.ethClient, + Log: c.log, + KeyBroadcast: c.KeyBroadcast, + KeyperSetManager: c.KeyperSetManager, + Handler: o.handlerEonPublicKey, + } + if o.handlerEonPublicKey != nil { + syncedServices = append(syncedServices, c.epksync) + } + c.sssync = &syncer.ShutterStateSyncer{ + Client: o.ethClient, + Contract: c.KeyperSetManager, + Log: c.log, + Handler: o.handlerShutterState, + } + if o.handlerShutterState != nil { + syncedServices = append(syncedServices, c.sssync) + } + + if o.handlerBlock == nil { + // Even if the user is not interested in handling new block events, + // the streaming block handler must be running in order to + // synchronize polling of new contract events. + // Since the handler function is always called, we need to + // inject a noop-handler + o.handlerBlock = func(ctx context.Context, lb *event.LatestBlock) error { + return nil + } + } + + c.uhsync = &syncer.UnsafeHeadSyncer{ + Client: o.ethClient, + Log: c.log, + Handler: o.handlerBlock, + SyncedHandler: syncedServices, + FetchActiveAtStart: o.fetchActivesAtSyncStart, + SyncStartBlock: o.syncStart, + } + if o.handlerBlock != nil { + c.services = append(c.services, c.uhsync) + } + return nil +} + +// initialize the shutter client and apply the options. +// the context is only the initialisation context, +// and should not be considered to handle the lifecycle +// of shutter clients background workers. +func (o *options) apply(ctx context.Context, c *Client) error { + var ( + client syncclient.SyncEthereumClient + err error + ) + if o.clientURL != "" { + o.ethClient, err = ethclient.DialContext(ctx, o.clientURL) + if err != nil { + return err + } + } + client = o.ethClient + c.SyncEthereumClient = client + + // the nil passthrough will use "latest" for each call, + // but we want to harmonize and fix the sync start to a specific block. + if o.syncStart.IsLatest() { + latestBlock, err := c.SyncEthereumClient.BlockNumber(ctx) + if err != nil { + return errors.Wrap(err, "polling latest block") + } + o.syncStart = number.NewBlockNumber(&latestBlock) + } + + if o.logger != nil { + c.log = o.logger + } + + c.privKey = o.privKey + return o.applyHandler(c) +} + +func defaultOptions() *options { + return &options{ + keyperSetManagerAddress: &predeploy.KeyperSetManagerAddr, + keyBroadcastContractAddress: &predeploy.KeyBroadcastContractAddr, + clientURL: "", + ethClient: nil, + logger: noopLogger, + runner: nil, + fetchActivesAtSyncStart: true, + syncStart: number.NewBlockNumber(nil), + } +} + +func WithNoFetchActivesBeforeStart() Option { + return func(o *options) error { + o.fetchActivesAtSyncStart = false + return nil + } +} + +func WithSyncStartBlock( + blockNumber *number.BlockNumber, +) Option { + if blockNumber == nil { + blockNumber = number.NewBlockNumber(nil) + } + return func(o *options) error { + o.syncStart = blockNumber + return nil + } +} + +func WithRunner(runner service.Runner) Option { + return func(o *options) error { + o.runner = runner + return nil + } +} + +func WithKeyBroadcastContract(address common.Address) Option { + return func(o *options) error { + o.keyBroadcastContractAddress = &address + return nil + } +} + +func WithKeyperSetManager(address common.Address) Option { + return func(o *options) error { + o.keyperSetManagerAddress = &address + return nil + } +} + +func WithClientURL(url string) Option { + return func(o *options) error { + o.clientURL = url + return nil + } +} + +func WithLogger(l log.Logger) Option { + return func(o *options) error { + o.logger = l + return nil + } +} + +func WithClient(client syncclient.FullEthereumClient) Option { + return func(o *options) error { + o.ethClient = client + return nil + } +} + +func WithPrivateKey(key *ecdsa.PrivateKey) Option { + return func(o *options) error { + o.privKey = key + return nil + } +} + +func WithSyncNewKeyperSet(handler event.KeyperSetHandler) Option { + return func(o *options) error { + o.handlerKeyperSet = handler + return nil + } +} + +func WithSyncNewBlock(handler event.BlockHandler) Option { + return func(o *options) error { + o.handlerBlock = handler + return nil + } +} + +func WithSyncNewEonKey(handler event.EonPublicKeyHandler) Option { + return func(o *options) error { + o.handlerEonPublicKey = handler + return nil + } +} + +func WithSyncNewShutterState(handler event.ShutterStateHandler) Option { + return func(o *options) error { + o.handlerShutterState = handler + return nil + } +} diff --git a/rolling-shutter/medley/chainsync/syncer/eonpubkey.go b/rolling-shutter/medley/chainsync/syncer/eonpubkey.go new file mode 100644 index 000000000..f055fc118 --- /dev/null +++ b/rolling-shutter/medley/chainsync/syncer/eonpubkey.go @@ -0,0 +1,155 @@ +package syncer + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/log" + "github.com/pkg/errors" + "github.com/shutter-network/shop-contracts/bindings" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/client" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/event" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/number" +) + +var _ ManualFilterHandler = &EonPubKeySyncer{} + +type EonPubKeySyncer struct { + Client client.SyncEthereumClient + Log log.Logger + KeyBroadcast *bindings.KeyBroadcastContract + KeyperSetManager *bindings.KeyperSetManager + Handler event.EonPublicKeyHandler +} + +func (s *EonPubKeySyncer) QueryAndHandle(ctx context.Context, block uint64) error { + s.Log.Info( + "pubsyncer query and handle called", + "block", + block, + ) + opts := &bind.FilterOpts{ + Start: block, + End: &block, + Context: ctx, + } + iter, err := s.KeyBroadcast.FilterEonKeyBroadcast(opts) + if err != nil { + return err + } + defer iter.Close() + + for iter.Next() { + err := s.handle(ctx, iter.Event) + if err != nil { + s.Log.Error( + "handler for `NewKeyperSet` errored", + "error", + err.Error(), + ) + } + } + if err := iter.Error(); err != nil { + return errors.Wrap(err, "filter iterator error") + } + return nil +} + +func (s *EonPubKeySyncer) handle(ctx context.Context, newEonKey *bindings.KeyBroadcastContractEonKeyBroadcast) error { + pubk := newEonKey.Key + bn := newEonKey.Raw.BlockNumber + ev := &event.EonPublicKey{ + Eon: newEonKey.Eon, + Key: pubk, + AtBlockNumber: number.NewBlockNumber(&bn), + } + err := s.Handler(ctx, ev) + if err != nil { + return err + } + return nil +} + +func (s *EonPubKeySyncer) HandleVirtualEvent(ctx context.Context, block *number.BlockNumber) error { + pubKs, err := s.getInitialPubKeys(ctx, block) + if err != nil { + return err + } + for _, k := range pubKs { + err := s.Handler(ctx, k) + if err != nil { + return err + } + } + return nil +} + +func (s *EonPubKeySyncer) getInitialPubKeys(ctx context.Context, block *number.BlockNumber) ([]*event.EonPublicKey, error) { + // This blocknumber specifies AT what state + // the contract is called + opts := &bind.CallOpts{ + Context: ctx, + BlockNumber: block.Int, + } + numKS, err := s.KeyperSetManager.GetNumKeyperSets(opts) + if err != nil { + return nil, err + } + // this blocknumber specifies the argument to the contract + // getter + activeEon, err := s.KeyperSetManager.GetKeyperSetIndexByBlock(opts, block.Uint64()) + if err != nil { + return nil, err + } + initialPubKeys := []*event.EonPublicKey{} + // NOTE: These are pubkeys that at the state of s.StartBlock + // are known to the contracts. + // That way we recreate older broadcast publickey events. + // We are only interested for keys that belong to keyper-set + // that are currently active or will become active in + // the future: + for i := activeEon; i < numKS; i++ { + e, err := s.GetEonPubKeyForEon(ctx, opts, i) + if err != nil { + return nil, err + } + // if e == nil, this means the keyperset did not broadcast a + // key (yet) + if e != nil { + initialPubKeys = append(initialPubKeys, e) + } + } + return initialPubKeys, nil +} + +func (s *EonPubKeySyncer) logCallError(attrName string, err error) { + s.Log.Error( + fmt.Sprintf("could not retrieve `%s` from contract", attrName), + "error", + err.Error(), + ) +} + +func (s *EonPubKeySyncer) GetEonPubKeyForEon(ctx context.Context, opts *bind.CallOpts, eon uint64) (*event.EonPublicKey, error) { + var err error + opts, _, err = fixCallOpts(ctx, s.Client, opts) + if err != nil { + return nil, err + } + key, err := s.KeyBroadcast.GetEonKey(opts, eon) + if err != nil { + return nil, err + } + // NOTE: Solidity returns the null value whenever + // one tries to access a key in mapping that doesn't exist + if len(key) == 0 { + return nil, nil + } + return &event.EonPublicKey{ + Eon: eon, + Key: key, + AtBlockNumber: number.BigToBlockNumber(opts.BlockNumber), + }, nil +} diff --git a/rolling-shutter/medley/chainsync/syncer/keyperset.go b/rolling-shutter/medley/chainsync/syncer/keyperset.go new file mode 100644 index 000000000..be9e3c976 --- /dev/null +++ b/rolling-shutter/medley/chainsync/syncer/keyperset.go @@ -0,0 +1,221 @@ +package syncer + +import ( + "context" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/pkg/errors" + "github.com/shutter-network/shop-contracts/bindings" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/client" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/event" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/number" +) + +func makeCallError(attrName string, err error) error { + return errors.Wrapf(err, "could not retrieve `%s` from contract", attrName) +} + +const channelSize = 10 + +var _ ManualFilterHandler = &KeyperSetSyncer{} + +type KeyperSetSyncer struct { + Client client.FullEthereumClient + Contract *bindings.KeyperSetManager + Log log.Logger + Handler event.KeyperSetHandler +} + +func (s *KeyperSetSyncer) QueryAndHandle(ctx context.Context, block uint64) error { + opts := &bind.FilterOpts{ + Start: block, + End: &block, + Context: ctx, + } + iter, err := s.Contract.FilterKeyperSetAdded(opts) + if err != nil { + return err + } + defer iter.Close() + + for iter.Next() { + err := s.handle(ctx, iter.Event) + if err != nil { + s.Log.Error( + "handler for `NewKeyperSet` errored", + "error", + err.Error(), + ) + } + } + if err := iter.Error(); err != nil { + return errors.Wrap(err, "filter iterator error") + } + return nil +} + +func (s *KeyperSetSyncer) HandleVirtualEvent(ctx context.Context, block *number.BlockNumber) error { + initial, err := s.getInitialKeyperSets(ctx, block) + if err != nil { + return err + } + for _, ks := range initial { + err = s.Handler(ctx, ks) + if err != nil { + s.Log.Error( + "handler for `NewKeyperSet` errored for initial sync", + "error", + err.Error(), + ) + } + } + return nil +} + +func (s *KeyperSetSyncer) getInitialKeyperSets(ctx context.Context, block *number.BlockNumber) ([]*event.KeyperSet, error) { + opts := &bind.CallOpts{ + Context: ctx, + BlockNumber: block.Int, + } + if err := guardCallOpts(opts, false); err != nil { + return nil, err + } + bn := block.ToUInt64Ptr() + if bn == nil { + // this should not be the case + return nil, errors.New("start block is 'latest'") + } + + initialKeyperSets := []*event.KeyperSet{} + // this blocknumber specifies the argument to the contract + // getter + ks, err := s.GetKeyperSetForBlock(ctx, opts, block) + if err != nil { + return nil, err + } + initialKeyperSets = append(initialKeyperSets, ks) + + numKS, err := s.Contract.GetNumKeyperSets(opts) + if err != nil { + return nil, err + } + + for i := ks.Eon + 1; i < numKS; i++ { + ks, err = s.GetKeyperSetByIndex(ctx, opts, i) + if err != nil { + return nil, err + } + initialKeyperSets = append(initialKeyperSets, ks) + } + + return initialKeyperSets, nil +} + +func (s *KeyperSetSyncer) GetKeyperSetByIndex(ctx context.Context, opts *bind.CallOpts, index uint64) (*event.KeyperSet, error) { + opts, _, err := fixCallOpts(ctx, s.Client, opts) + if err != nil { + return nil, err + } + actBl, err := s.Contract.GetKeyperSetActivationBlock(opts, index) + if err != nil { + return nil, errors.Wrap(err, "could not retrieve keyper set activation block") + } + addr, err := s.Contract.GetKeyperSetAddress(opts, index) + if err != nil { + return nil, errors.Wrap(err, "could not retrieve keyper set address") + } + return s.newEvent(ctx, opts, addr, actBl) +} + +func (s *KeyperSetSyncer) GetKeyperSetForBlock(ctx context.Context, opts *bind.CallOpts, b *number.BlockNumber) (*event.KeyperSet, error) { + var atBlock uint64 + var err error + + opts, latestFromFix, err := fixCallOpts(ctx, s.Client, opts) + if err != nil { + return nil, err + } + + if b.Equal(number.LatestBlock) { + if latestFromFix == nil { + atBlock, err = s.Client.BlockNumber(ctx) + if err != nil { + return nil, errors.Wrap(err, "get current block-number") + } + } else { + atBlock = *latestFromFix + } + } else { + atBlock = b.Uint64() + } + + idx, err := s.Contract.GetKeyperSetIndexByBlock(opts, atBlock) + if err != nil { + return nil, errors.Wrapf(err, "could not retrieve keyper set index at block %d", atBlock) + } + return s.GetKeyperSetByIndex(ctx, opts, idx) +} + +func (s *KeyperSetSyncer) newEvent( + _ context.Context, + opts *bind.CallOpts, + keyperSetContract common.Address, + activationBlock uint64, +) (*event.KeyperSet, error) { + if err := guardCallOpts(opts, false); err != nil { + return nil, err + } + ks, err := bindings.NewKeyperSet(keyperSetContract, s.Client) + if err != nil { + return nil, errors.Wrap(err, "could not bind to KeyperSet contract") + } + // the manager only accepts final keyper sets, + // so we expect this to be final now. + final, err := ks.IsFinalized(opts) + if err != nil { + return nil, makeCallError("IsFinalized", err) + } + if !final { + return nil, errors.New("contract did accept unfinalized keyper-sets") + } + members, err := ks.GetMembers(opts) + if err != nil { + return nil, makeCallError("Members", err) + } + threshold, err := ks.GetThreshold(opts) + if err != nil { + return nil, makeCallError("Threshold", err) + } + eon, err := s.Contract.GetKeyperSetIndexByBlock(opts, activationBlock) + if err != nil { + return nil, makeCallError("KeyperSetIndexByBlock", err) + } + return &event.KeyperSet{ + ActivationBlock: activationBlock, + Members: members, + Threshold: threshold, + Eon: eon, + AtBlockNumber: number.BigToBlockNumber(opts.BlockNumber), + }, nil +} + +func (s *KeyperSetSyncer) handle(ctx context.Context, ev *bindings.KeyperSetManagerKeyperSetAdded) error { + opts := logToCallOpts(ctx, &ev.Raw) + newKeyperSet, err := s.newEvent( + ctx, + opts, + ev.KeyperSetContract, + ev.ActivationBlock, + ) + if err != nil { + return errors.Wrap(err, "fetch new event") + } + err = s.Handler(ctx, newKeyperSet) + if err != nil { + return errors.Wrap(err, "call handler") + } + return nil +} diff --git a/rolling-shutter/medley/chainsync/syncer/shutterstate.go b/rolling-shutter/medley/chainsync/syncer/shutterstate.go new file mode 100644 index 000000000..fbd7773de --- /dev/null +++ b/rolling-shutter/medley/chainsync/syncer/shutterstate.go @@ -0,0 +1,107 @@ +package syncer + +import ( + "context" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/log" + "github.com/pkg/errors" + "github.com/shutter-network/shop-contracts/bindings" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/client" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/event" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/number" +) + +var _ ManualFilterHandler = &ShutterStateSyncer{} + +type ShutterStateSyncer struct { + Client client.SyncEthereumClient + Contract *bindings.KeyperSetManager + Log log.Logger + Handler event.ShutterStateHandler +} + +func (s *ShutterStateSyncer) GetShutterState(ctx context.Context, opts *bind.CallOpts) (*event.ShutterState, error) { + opts, _, err := fixCallOpts(ctx, s.Client, opts) + if err != nil { + return nil, err + } + isPaused, err := s.Contract.Paused(opts) + if err != nil { + return nil, errors.Wrap(err, "query paused state") + } + return &event.ShutterState{ + Active: !isPaused, + AtBlockNumber: number.BigToBlockNumber(opts.BlockNumber), + }, nil +} + +func (s *ShutterStateSyncer) QueryAndHandle(ctx context.Context, block uint64) error { + opts := &bind.FilterOpts{ + Start: block, + End: &block, + Context: ctx, + } + iterPaused, err := s.Contract.FilterPaused(opts) + if err != nil { + return err + } + defer iterPaused.Close() + + for iterPaused.Next() { + block := iterPaused.Event.Raw.BlockNumber + ev := &event.ShutterState{ + Active: false, + AtBlockNumber: number.NewBlockNumber(&block), + } + s.handle(ctx, ev) + } + if err := iterPaused.Error(); err != nil { + return errors.Wrap(err, "filter iterator error") + } + + iterUnpaused, err := s.Contract.FilterUnpaused(opts) + if err != nil { + return err + } + defer iterUnpaused.Close() + + for iterUnpaused.Next() { + block := iterUnpaused.Event.Raw.BlockNumber + ev := &event.ShutterState{ + Active: true, + AtBlockNumber: number.NewBlockNumber(&block), + } + s.handle(ctx, ev) + } + if err := iterUnpaused.Error(); err != nil { + return errors.Wrap(err, "filter iterator error") + } + return nil +} + +func (s *ShutterStateSyncer) HandleVirtualEvent(ctx context.Context, block *number.BlockNumber) error { + // query the initial state and re-construct a "virtual" event from the contract state + opts := &bind.CallOpts{ + BlockNumber: block.Int, + Context: ctx, + } + stateAtBlock, err := s.GetShutterState(ctx, opts) + if err != nil { + return err + } + s.handle(ctx, stateAtBlock) + return nil +} + +func (s *ShutterStateSyncer) handle(ctx context.Context, ev *event.ShutterState) { + err := s.Handler(ctx, ev) + if err != nil { + s.Log.Error( + "handler for `NewShutterState` errored", + "error", + err.Error(), + ) + } +} diff --git a/rolling-shutter/medley/chainsync/syncer/unsafehead.go b/rolling-shutter/medley/chainsync/syncer/unsafehead.go new file mode 100644 index 000000000..a90b9c69f --- /dev/null +++ b/rolling-shutter/medley/chainsync/syncer/unsafehead.go @@ -0,0 +1,308 @@ +package syncer + +import ( + "context" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/pkg/errors" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/client" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/event" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/number" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/retry" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" +) + +type UnsafeHeadSyncer struct { + Client client.SyncEthereumClient + Log log.Logger + Handler event.BlockHandler + // Handler to be manually triggered + // to handle their handler function + // before the own Handler is called: + SyncedHandler []ManualFilterHandler + SyncStartBlock *number.BlockNumber + FetchActiveAtStart bool + + newLatestHeadCh chan *types.Header + + syncHead *types.Header + nextSyncBlock *big.Int + latestHead *types.Header + headerCache map[uint64]*types.Header +} + +func (s *UnsafeHeadSyncer) Start(ctx context.Context, runner service.Runner) error { + s.Log.Info("unsafe head syncer started") + if s.Handler == nil { + return errors.New("no handler registered") + } + + s.headerCache = map[uint64]*types.Header{} + s.newLatestHeadCh = make(chan *types.Header, 1) + _, err := retry.FunctionCall( + ctx, + func(ctx context.Context) (bool, error) { + err := s.fetchInitialHeaders(ctx) + return err == nil, err + }, + ) + if err != nil { + return errors.Wrap(err, "fetch initial latest header and sync start header") + } + s.SyncStartBlock = &number.BlockNumber{Int: s.syncHead.Number} + if s.FetchActiveAtStart { + for _, h := range s.SyncedHandler { + err := h.HandleVirtualEvent(ctx, s.SyncStartBlock) + if err != nil { + s.Log.Error("synced handler call errored, skipping", "error", err) + } + } + } + err = s.handle(ctx, s.syncHead, false) + if err != nil { + return errors.Wrap(err, "handle initial sync block") + } + s.nextSyncBlock = new(big.Int).Add(s.syncHead.Number, big.NewInt(1)) + + subs, err := s.Client.SubscribeNewHead(ctx, s.newLatestHeadCh) + if err != nil { + return err + } + runner.Defer(subs.Unsubscribe) + runner.Defer(func() { + close(s.newLatestHeadCh) + }) + runner.Go(func() error { + return s.watchLatestUnsafeHead(ctx) + }) + return nil +} + +func (s *UnsafeHeadSyncer) fetchInitialHeaders(ctx context.Context) error { + latest, err := s.Client.HeaderByNumber(ctx, nil) + if err != nil { + return errors.Wrap(err, "fetch latest header") + } + s.latestHead = latest + if s.SyncStartBlock.IsLatest() { + s.syncHead = latest + return nil + } + + start, err := s.Client.HeaderByNumber(ctx, s.SyncStartBlock.Int) + if err != nil { + return errors.Wrap(err, "fetch sync start header") + } + s.syncHead = start + return nil +} + +func parseLogs() error { + return nil +} + +func (s *UnsafeHeadSyncer) handle(ctx context.Context, newHeader *types.Header, reorg bool) error { + blockNum := number.BigToBlockNumber(newHeader.Number) + if reorg { + prevNum := new(big.Int).Sub(newHeader.Number, big.NewInt(1)) + prevBlockNum := number.BigToBlockNumber(prevNum) + // Re-emit the previous block, to pre-emptively signal an + // incoming reorg. Like this a client is able to e.g. + // rewind changes first before processing the new + // events of the reorg + ev := &event.LatestBlock{ + Number: prevBlockNum, + BlockHash: newHeader.ParentHash, + Header: newHeader, + } + err := s.Handler(ctx, ev) + if err != nil { + // XXX: return or log? + // return err + s.Log.Error( + "handler for `NewLatestBlock` errored", + "error", + err.Error(), + ) + } + } + + // TODO: check bloom filter for topic of all + // synced handlers and only call them if + // the bloomfilter retrieves something. + for _, h := range s.SyncedHandler { + // NOTE: this has to be blocking! + // So whenever this returns, it is expected + // that the handlers Handle function + // has been called and it returned. + err := h.QueryAndHandle(ctx, blockNum.Uint64()) + if err != nil { + s.Log.Error("synced handler call errored, skipping", "error", err) + } + } + ev := &event.LatestBlock{ + Number: blockNum, + BlockHash: newHeader.Hash(), + } + err := s.Handler(ctx, ev) + if err != nil { + s.Log.Error( + "handler for `NewLatestBlock` errored", + "error", + err.Error(), + ) + } + return nil +} + +func (s *UnsafeHeadSyncer) fetchHeader(ctx context.Context, num *big.Int) (*types.Header, error) { + h, ok := s.headerCache[num.Uint64()] + if ok { + return h, nil + } + return s.Client.HeaderByNumber(ctx, num) +} + +func (s *UnsafeHeadSyncer) reset(ctx context.Context) error { + // this means the latest head was reset - check wether that + // concerns the current sync status + switch s.latestHead.Number.Cmp(s.syncHead.Number) { + case 1: + // we didn't catch up to the reorg position, so it's safe to ignore + // TODO: delete the forward caches + return nil + case 0: + // we already processed the head we re-orged to + if s.latestHead.Hash().Cmp(s.syncHead.Hash()) == 0 { + return nil + } + } + // definite reorg + if err := s.handle(ctx, s.latestHead, true); err != nil { + return err + } + s.syncHead = s.latestHead + s.nextSyncBlock = new(big.Int).Add(s.latestHead.Number, big.NewInt(1)) + return nil +} + +func (s *UnsafeHeadSyncer) sync(ctx context.Context) (syncing bool, delay time.Duration, err error) { + var newHead *types.Header + s.Log.Info("syncing chain") + + delta := new(big.Int).Sub(s.latestHead.Number, s.nextSyncBlock) + switch delta.Cmp(big.NewInt(0)) { + case 1: + // positive delta, we are still catching up + newHead, err = s.fetchHeader(ctx, s.nextSyncBlock) + syncing = true + delay = 1 * time.Second + case 0: + // next sync is latest head + // use the latest head + newHead = s.latestHead + syncing = false + case -1: + if delta.Cmp(big.NewInt(-1)) != 0 { + // next sync is more than one block further in the future. + // this could mean a reorg, but this should have been called before + // and it shouldn't come to this here + return false, delay, errors.New("unexpected reorg condition in sync") + } + // reorgs are handled outside, at the place new latest-head + // information arrives. + // next sync is 1 into the future. + // this means we called sync but are still waiting for the next latest head. + return false, delay, err + } + if err != nil { + return true, delay, err + } + + if handleErr := s.handle(ctx, newHead, false); handleErr != nil { + return true, delay, handleErr + } + s.syncHead = newHead + delete(s.headerCache, newHead.Number.Uint64()) + s.nextSyncBlock = new(big.Int).Add(newHead.Number, big.NewInt(1)) + + s.Log.Info("chain sync", + "synced-head-num", s.syncHead.Number.Uint64(), + "synced-head-hash", s.syncHead.Hash(), + "latest-head-num", s.latestHead.Number.Uint64(), + "latest-head-hash", s.latestHead.Hash(), + ) + return syncing, delay, err +} + +func (s *UnsafeHeadSyncer) watchLatestUnsafeHead(ctx context.Context) error { //nolint: gocyclo + t := time.NewTimer(0) + sync := t.C +work: + for { + select { + case <-ctx.Done(): + if sync != nil && !t.Stop() { + select { + case <-t.C: + // TODO: the non-blocking select is here as a + // precaution against an already emptied channel. + // This should not be necessary + default: + } + } + return ctx.Err() + case <-sync: + syncing, delay, err := s.sync(ctx) + if err != nil { + s.Log.Error("error during unsafe head sync", "error", err) + } + if !syncing { + s.Log.Info("stop syncing from sync") + sync = nil + continue work + } + t.Reset(delay) + case newHeader, ok := <-s.newLatestHeadCh: + if !ok { + s.Log.Info("latest head stream closed, exiting handler loop") + return nil + } + s.Log.Info("new latest head from l2 ws-stream", "block-number", newHeader.Number.Uint64()) + if newHeader.Number.Cmp(s.latestHead.Number) <= 0 { + // reorg + s.Log.Info("new latest head is re-orging", + "old-block-number", s.latestHead.Number.Uint64(), + "new-block-number", newHeader.Number.Uint64(), + ) + + s.latestHead = newHeader + if err := s.reset(ctx); err != nil { + s.Log.Error("error resetting reorg", "error", err) + } + continue work + } + s.headerCache[newHeader.Number.Uint64()] = newHeader + s.latestHead = newHeader + + if sync != nil && !t.Stop() { + // only if sync is still actively waiting, + // we need to drain the timer and reset it + select { + case <-t.C: + // TODO: the non-blocking select is here as a + // precaution against an already emptied channel. + // This should not be necessary + default: + } + } + t.Reset(0) + s.Log.Info("start syncing from latest head stream") + sync = t.C + } + } +} diff --git a/rolling-shutter/medley/chainsync/syncer/unsafehead_test.go b/rolling-shutter/medley/chainsync/syncer/unsafehead_test.go new file mode 100644 index 000000000..380a944f0 --- /dev/null +++ b/rolling-shutter/medley/chainsync/syncer/unsafehead_test.go @@ -0,0 +1,113 @@ +package syncer + +import ( + "context" + "fmt" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "gotest.tools/v3/assert" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/client" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/event" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/number" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" +) + +func MakeChain(start int64, startParent common.Hash, numHeader uint, seed int64) []*types.Header { + n := numHeader + parent := startParent + num := big.NewInt(start) + h := []*types.Header{} + + // change the hashes for different seeds + mixinh := common.BigToHash(big.NewInt(seed)) + for n > 0 { + head := &types.Header{ + ParentHash: parent, + Number: num, + MixDigest: mixinh, + } + h = append(h, head) + num = new(big.Int).Add(num, big.NewInt(1)) + parent = head.Hash() + n-- + } + return h +} + +func TestReorg(t *testing.T) { + headersBeforeReorg := MakeChain(1, common.BigToHash(big.NewInt(0)), 10, 42) + branchOff := headersBeforeReorg[5] + // block number 5 will be reorged + headersReorgBranch := MakeChain(branchOff.Number.Int64()+1, branchOff.Hash(), 10, 43) + clnt, ctl := client.NewTestClient() + ctl.AppendNextHeaders(headersBeforeReorg...) + ctl.AppendNextHeaders(headersReorgBranch...) + + handlerBlock := make(chan *event.LatestBlock, 1) + + h := &UnsafeHeadSyncer{ + Client: clnt, + Log: log.New(), + Handler: func(_ context.Context, ev *event.LatestBlock) error { + handlerBlock <- ev + return nil + }, + SyncedHandler: []ManualFilterHandler{}, + SyncStartBlock: number.NewBlockNumber(nil), + FetchActiveAtStart: false, + } + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + service.RunBackground(ctx, h) + + // intitial sync is independent of the subscription, + // this will get polled from the eth client + b := <-handlerBlock + assert.Assert(t, b.Number.Cmp(headersBeforeReorg[0].Number) == 0) + idx := 1 + for { + ok := ctl.ProgressHead() + assert.Assert(t, ok) + err := ctl.EmitEvents(ctx) + assert.NilError(t, err) + + b = <-handlerBlock + assert.Equal(t, b.Number.Uint64(), headersBeforeReorg[idx].Number.Uint64(), fmt.Sprintf("block number equal for idx %d", idx)) + assert.Equal(t, b.BlockHash, headersBeforeReorg[idx].Hash()) + idx++ + if idx == len(headersBeforeReorg) { + break + } + } + ok := ctl.ProgressHead() + assert.Assert(t, ok) + err := ctl.EmitEvents(ctx) + assert.NilError(t, err) + b = <-handlerBlock + // now the reorg should have happened. + // the handler should have emitted an "artificial" latest head + // event for the block BEFORE the re-orged block + assert.Equal(t, b.Number.Uint64(), headersReorgBranch[0].Number.Uint64()-1, "block number equal for reorg") + assert.Equal(t, b.BlockHash, headersReorgBranch[0].ParentHash) + idx = 0 + for ctl.ProgressHead() { + assert.Assert(t, ok) + err := ctl.EmitEvents(ctx) + assert.NilError(t, err) + + b := <-handlerBlock + assert.Equal(t, b.Number.Uint64(), headersReorgBranch[idx].Number.Uint64(), fmt.Sprintf("block number equal for idx %d", idx)) + assert.Equal(t, b.BlockHash, headersReorgBranch[idx].Hash()) + idx++ + if idx == len(headersReorgBranch) { + break + } + } +} diff --git a/rolling-shutter/medley/chainsync/syncer/util.go b/rolling-shutter/medley/chainsync/syncer/util.go new file mode 100644 index 000000000..8a470f06a --- /dev/null +++ b/rolling-shutter/medley/chainsync/syncer/util.go @@ -0,0 +1,71 @@ +package syncer + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/core/types" + "github.com/pkg/errors" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/client" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/number" +) + +var ( + errNilCallOpts = errors.New("nil call-opts") + errNilOptsBlockNumber = errors.New("opts block-number is nil, but 'latest' not allowed") + errLatestBlock = errors.New("'nil' latest block") +) + +type ManualFilterHandler interface { + QueryAndHandle(ctx context.Context, block uint64) error + HandleVirtualEvent(ctx context.Context, block *number.BlockNumber) error +} + +func logToCallOpts(ctx context.Context, log *types.Log) *bind.CallOpts { + block := new(big.Int) + block.SetUint64(log.BlockNumber) + return &bind.CallOpts{ + BlockNumber: block, + Context: ctx, + } +} + +func guardCallOpts(opts *bind.CallOpts, allowLatest bool) error { + if opts == nil { + return errNilCallOpts + } + if !allowLatest { + n := number.BigToBlockNumber(opts.BlockNumber) + if n.IsLatest() { + return errLatestBlock + } + } + return nil +} + +func fixCallOpts(ctx context.Context, c client.SyncEthereumClient, opts *bind.CallOpts) (*bind.CallOpts, *uint64, error) { + err := guardCallOpts(opts, false) + if err == nil { + return opts, nil, nil + } + // query the current latest block and fix it + latest, queryErr := c.BlockNumber(ctx) + if queryErr != nil { + return nil, nil, errors.Wrap(err, "query latest block-number") + } + blockNumber := number.NewBlockNumber(&latest) + if errors.Is(err, errNilCallOpts) { + opts = &bind.CallOpts{ + Context: ctx, + BlockNumber: blockNumber.Int, + } + return opts, &latest, nil + } + if errors.Is(err, errLatestBlock) { + opts.BlockNumber = blockNumber.Int + return opts, &latest, nil + } + return nil, nil, err +} diff --git a/rolling-shutter/medley/channel/fanin_test.go b/rolling-shutter/medley/channel/fanin_test.go index 46c984b30..b2879ba95 100644 --- a/rolling-shutter/medley/channel/fanin_test.go +++ b/rolling-shutter/medley/channel/fanin_test.go @@ -36,8 +36,8 @@ func TestFanIn(t *testing.T) { end := end step := numFns channels = append(channels, ch) - fnService := service.ServiceFn{ - Fn: func(ctx context.Context) error { + fnService := service.Function{ + Func: func(ctx context.Context, _ service.Runner) error { putInts(ctx, ch, start, end, step) return nil }, @@ -48,8 +48,8 @@ func TestFanIn(t *testing.T) { services = append(services, fanIn) result := []int{} - fanConsumer := service.ServiceFn{ - Fn: func(ctx context.Context) error { + fanConsumer := service.Function{ + Func: func(ctx context.Context, _ service.Runner) error { for val := range fanIn.C { result = append(result, val) } diff --git a/rolling-shutter/medley/configuration/command/command.go b/rolling-shutter/medley/configuration/command/command.go index 4bcb8782f..f06a22a1d 100644 --- a/rolling-shutter/medley/configuration/command/command.go +++ b/rolling-shutter/medley/configuration/command/command.go @@ -48,7 +48,7 @@ func newCommandBuilder(name string) *commandBuilderConfig { } } -func newConfigForFunc[T configuration.Config](fn ConfigurableFunc[T]) T { +func NewConfigForFunc[T configuration.Config](fn ConfigurableFunc[T]) T { typ := reflect.TypeOf(fn).In(0).Elem() nw, ok := reflect.New(typ).Interface().(T) if !ok { @@ -66,7 +66,7 @@ func Build[T configuration.Config]( main ConfigurableFunc[T], options ...Option, ) *CommandBuilder[T] { - cfg := newConfigForFunc(main) + cfg := NewConfigForFunc(main) cfg.Init() builder := newCommandBuilder(strings.ToLower(cfg.Name())) for _, opt := range options { @@ -84,7 +84,7 @@ func Build[T configuration.Config]( Long: builder.longUsage, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - cfg := newConfigForFunc(main) + cfg := NewConfigForFunc(main) cfg.Init() v := viper.GetViper() v.SetFs(builder.filesystem) @@ -107,7 +107,7 @@ func Build[T configuration.Config]( Short: fmt.Sprintf("Generate a '%s' configuration file", builder.name), Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - cfg := newConfigForFunc(main) + cfg := NewConfigForFunc(main) cfg.Init() err := configuration.SetExampleValuesRecursive(cfg) if err != nil { @@ -130,7 +130,7 @@ func Build[T configuration.Config]( Short: fmt.Sprintf("Dump a '%s' configuration file, based on given config and env vars", builder.name), Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - cfg := newConfigForFunc(main) + cfg := NewConfigForFunc(main) cfg.Init() v := viper.GetViper() v.SetFs(builder.filesystem) @@ -157,30 +157,51 @@ func Build[T configuration.Config]( return cb } +type CobraRunE func(cmd *cobra.Command, args []string) error + +// AddFunctionSubcommand attaches an additional subcommand to the command initially built by the +// Build method. The command executes the given function and takes the given arguments. +func (cb *CommandBuilder[T]) AddFunctionSubcommand( + fnc ConfigurableFunc[T], + use, short string, + args cobra.PositionalArgs, +) { + cb.cobraCommand.AddCommand(&cobra.Command{ + Use: use, + Short: short, + Args: args, + RunE: cb.WrapFuncParseConfig(fnc), + }) +} + +func (cb *CommandBuilder[T]) WrapFuncParseConfig(fnc ConfigurableFunc[T]) CobraRunE { + return func(cmd *cobra.Command, args []string) error { + cfg := NewConfigForFunc(fnc) + cfg.Init() + v := viper.GetViper() + v.SetFs(cb.builderConfig.filesystem) + err := ParseCLI(v, cmd, cfg) + if err != nil { + return errors.WithMessage(err, "Please check your configuration") + } + log.Debug(). + Interface("config", cfg). + Msg("got config") + return fnc(cfg) + } +} + // AddInitDBCommand attaches an additional subcommand // 'initdb' to the command initially built by the Build method. // The initDB function argument is structured in the same way than the "main" // function passed in to the Build method. func (cb *CommandBuilder[T]) AddInitDBCommand(initDB ConfigurableFunc[T]) { - cb.cobraCommand.AddCommand(&cobra.Command{ - Use: "initdb", - Short: fmt.Sprintf("Initialize the database of the '%s'", cb.builderConfig.name), - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - cfg := newConfigForFunc(initDB) - cfg.Init() - v := viper.GetViper() - v.SetFs(cb.builderConfig.filesystem) - err := ParseCLI(v, cmd, cfg) - if err != nil { - return errors.WithMessage(err, "Please check your configuration") - } - log.Debug(). - Interface("config", cfg). - Msg("got config") - return initDB(cfg) - }, - }) + cb.AddFunctionSubcommand( + initDB, + "initdb", + fmt.Sprintf("Initialize the database of the '%s'", cb.builderConfig.name), + cobra.NoArgs, + ) } func (cb *CommandBuilder[_]) Command() *cobra.Command { diff --git a/rolling-shutter/medley/encodeable/number/block.go b/rolling-shutter/medley/encodeable/number/block.go new file mode 100644 index 000000000..2f0475d91 --- /dev/null +++ b/rolling-shutter/medley/encodeable/number/block.go @@ -0,0 +1,89 @@ +package number + +import ( + "bytes" + "errors" + "math/big" +) + +var ( + LatestBlockInt int64 = -1 + LatestBlock = &BlockNumber{new(big.Int).SetInt64(LatestBlockInt)} + LatestStr = []byte("latest") +) + +var ErrLatestBlockToUint = errors.New("'latest' block can't be converted to uint64") + +func BigToBlockNumber(i *big.Int) *BlockNumber { + if i == nil { + return NewBlockNumber(nil) + } + return &BlockNumber{ + Int: i, + } +} + +func NewBlockNumber(u *uint64) *BlockNumber { + b := &BlockNumber{ + Int: &big.Int{}, + } + if u == nil { + b.SetInt64(LatestBlockInt) + } else { + b.SetUint64(*u) + } + return b +} + +type BlockNumber struct { + *big.Int +} + +func (k *BlockNumber) MarshalJSON() ([]byte, error) { + return k.MarshalText() +} + +func (k *BlockNumber) UnmarshalJSON(b []byte) error { + return k.UnmarshalText(b) +} + +func (k *BlockNumber) UnmarshalText(b []byte) error { + k.Int = new(big.Int) + if bytes.Equal(b, LatestStr) { + k.Int.SetInt64(LatestBlockInt) + return nil + } + + return k.Int.UnmarshalText(b) +} + +func (k *BlockNumber) IsLatest() bool { + return k.Equal(LatestBlock) +} + +func (k *BlockNumber) ToUInt64() (uint64, error) { + p := k.ToUInt64Ptr() + if p == nil { + return 0, ErrLatestBlockToUint + } + return *p, nil +} + +func (k *BlockNumber) ToUInt64Ptr() *uint64 { + if k.IsLatest() { + return nil + } + u := k.Uint64() + return &u +} + +func (k *BlockNumber) Equal(b *BlockNumber) bool { + return k.Int.Cmp(b.Int) == 0 +} + +func (k *BlockNumber) MarshalText() ([]byte, error) { + if k.IsLatest() { + return LatestStr, nil + } + return k.Int.MarshalText() +} diff --git a/rolling-shutter/medley/identitypreimage/identitypreimage.go b/rolling-shutter/medley/identitypreimage/identitypreimage.go index 5b1e34ecd..e1c679b34 100644 --- a/rolling-shutter/medley/identitypreimage/identitypreimage.go +++ b/rolling-shutter/medley/identitypreimage/identitypreimage.go @@ -9,7 +9,6 @@ import ( type IdentityPreimage []byte -// BigToIdentityPreimage converts n to an epoch id. It fails if n is too big. func BigToIdentityPreimage(n *big.Int) IdentityPreimage { return IdentityPreimage(n.Bytes()) } diff --git a/rolling-shutter/medley/logger/noop.go b/rolling-shutter/medley/logger/noop.go new file mode 100644 index 000000000..50d028365 --- /dev/null +++ b/rolling-shutter/medley/logger/noop.go @@ -0,0 +1,23 @@ +//nolint:revive +package logger + +import ( + "context" + + "github.com/ethereum/go-ethereum/log" + "golang.org/x/exp/slog" +) + +type NoopLogger struct{} + +func (n *NoopLogger) With(ctx ...interface{}) log.Logger { return n } +func (n *NoopLogger) New(ctx ...interface{}) log.Logger { return &NoopLogger{} } +func (n *NoopLogger) Log(level slog.Level, msg string, ctx ...interface{}) {} +func (n *NoopLogger) Trace(msg string, ctx ...interface{}) {} +func (n *NoopLogger) Debug(msg string, ctx ...interface{}) {} +func (n *NoopLogger) Info(msg string, ctx ...interface{}) {} +func (n *NoopLogger) Warn(msg string, ctx ...interface{}) {} +func (n *NoopLogger) Error(msg string, ctx ...interface{}) {} +func (n *NoopLogger) Crit(msg string, ctx ...interface{}) {} +func (n *NoopLogger) Write(level slog.Level, msg string, attrs ...any) {} +func (n *NoopLogger) Enabled(ctx context.Context, level slog.Level) bool { return false } diff --git a/rolling-shutter/medley/retry/options.go b/rolling-shutter/medley/retry/options.go index 035d32c50..dfa28c998 100644 --- a/rolling-shutter/medley/retry/options.go +++ b/rolling-shutter/medley/retry/options.go @@ -1,6 +1,7 @@ package retry import ( + "errors" "time" "github.com/benbjohnson/clock" @@ -13,43 +14,55 @@ import ( // `-1` is a special value that results in // infinite retries. func NumberOfRetries(n int) Option { - return func(r *retrier) { + return func(r *retrier) error { r.numRetries = n if n == -1 { r.infiniteRetries = true } + return nil } } func MaxInterval(t time.Duration) Option { - return func(r *retrier) { + return func(r *retrier) error { r.maxInterval = t + return nil } } func Interval(t time.Duration) Option { - return func(r *retrier) { + return func(r *retrier) error { r.interval = t + return nil } } func StopOnErrors(e ...error) Option { - return func(r *retrier) { + return func(r *retrier) error { r.cancelingErrors = e + return nil } } -func ExponentialBackoff() Option { - return func(r *retrier) { - // for now just use a fixed value - r.multiplier = 1.5 +func ExponentialBackoff(multiplier *float64) Option { + return func(r *retrier) error { + if multiplier == nil { + r.multiplier = 1.5 + return nil + } + r.multiplier = *multiplier + if r.multiplier <= 1.0 { + return errors.New("can't use value <=1.0 as exponential multiplier") + } + return nil } } func LogIdentifier(s string) Option { id := uuid.NewString() - return func(r *retrier) { + return func(r *retrier) error { r.zlogContext = r.zlogContext.Str("id", id+":"+s) + return nil } } @@ -57,7 +70,8 @@ func LogIdentifier(s string) Option { // implementation than the default `time` // wrapper. Mainly used for mocking. func UseClock(c clock.Clock) Option { - return func(r *retrier) { + return func(r *retrier) error { r.clock = c + return nil } } diff --git a/rolling-shutter/medley/retry/retry.go b/rolling-shutter/medley/retry/retry.go index b902e533d..0e594026f 100644 --- a/rolling-shutter/medley/retry/retry.go +++ b/rolling-shutter/medley/retry/retry.go @@ -5,6 +5,7 @@ import ( "time" "github.com/benbjohnson/clock" + "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -38,10 +39,14 @@ func newRetrier() *retrier { } } -func (r *retrier) option(opts []Option) { +func (r *retrier) applyOptions(opts []Option) error { for _, opt := range opts { - opt(r) + err := opt(r) + if err != nil { + return err + } } + return nil } func (r *retrier) iterator(next <-chan time.Time) <-chan time.Time { @@ -74,15 +79,20 @@ func (r *retrier) iterator(next <-chan time.Time) <-chan time.Time { } type ( - Option func(*retrier) + Option func(*retrier) error RetriableFunction[T any] func(ctx context.Context) (T, error) ) // FunctionCall calls the given function multiple times until it doesn't return an error // or one of any optional, user-defined specific errors is returned. func FunctionCall[T any](ctx context.Context, fn RetriableFunction[T], opts ...Option) (T, error) { + var err error + var null T + retrier := newRetrier() - retrier.option(opts) + if err := retrier.applyOptions(opts); err != nil { + return null, errors.Wrap(err, "apply options") + } funcName := introspection.GetFuncName(4) retrier.zlogContext = retrier.zlogContext.Str("funcName", funcName) logger := retrier.zlogContext.Logger() @@ -91,9 +101,6 @@ func FunctionCall[T any](ctx context.Context, fn RetriableFunction[T], opts ...O retry := retrier.iterator(next) - var err error - var null T - callCount := 0 for { diff --git a/rolling-shutter/medley/retry/retry_test.go b/rolling-shutter/medley/retry/retry_test.go index 5cbaf77b4..ddd612eeb 100644 --- a/rolling-shutter/medley/retry/retry_test.go +++ b/rolling-shutter/medley/retry/retry_test.go @@ -63,7 +63,7 @@ var testFlagTable = []testFlags{ []Option{ Interval(baseInterval), NumberOfRetries(5), - ExponentialBackoff(), + ExponentialBackoff(nil), }, errDefault, []time.Duration{ @@ -83,7 +83,7 @@ var testFlagTable = []testFlags{ MaxInterval( multDuration(baseInterval, math.Pow(1.5, 2)), ), - ExponentialBackoff(), + ExponentialBackoff(nil), }, errDefault, []time.Duration{ diff --git a/rolling-shutter/medley/service/service.go b/rolling-shutter/medley/service/service.go index 4f70ff6f1..13e4fc4e7 100644 --- a/rolling-shutter/medley/service/service.go +++ b/rolling-shutter/medley/service/service.go @@ -65,18 +65,22 @@ func (r *runner) runShutdownFuncs() { } func Run(ctx context.Context, services ...Service) error { - group := RunBackground(ctx, services...) + group, deferFn := RunBackground(ctx, services...) + defer deferFn() return group.Wait() } -func RunBackground(ctx context.Context, services ...Service) *errgroup.Group { +// RunBackground runs the services within the context of an errgroup.Group. +// It returns the errgroup.Group as well as the cleanup function to be deferred. +// This is a low-level executor of services, the defer function is expected +// to be called! +func RunBackground(ctx context.Context, services ...Service) (*errgroup.Group, func()) { group, ctx := errgroup.WithContext(ctx) r := runner{group: group, ctx: ctx} group.Go(func() error { return r.StartService(services...) }) - defer r.runShutdownFuncs() - return group + return group, r.runShutdownFuncs } // notifyTermination creates a context that is canceled, when the process receives SIGINT or @@ -117,13 +121,13 @@ func RunWithSighandler(ctx context.Context, services ...Service) error { return err } -type ServiceFn struct { - Fn func(ctx context.Context) error +type Function struct { + Func func(ctx context.Context, group Runner) error } -func (sf ServiceFn) Start(ctx context.Context, group Runner) error { //nolint:unparam +func (sf Function) Start(ctx context.Context, group Runner) error { //nolint:unparam group.Go(func() error { - return sf.Fn(ctx) + return sf.Func(ctx, group) }) return nil } diff --git a/rolling-shutter/medley/slots.go b/rolling-shutter/medley/slots.go new file mode 100644 index 000000000..41599c9b1 --- /dev/null +++ b/rolling-shutter/medley/slots.go @@ -0,0 +1,9 @@ +package medley + +func BlockTimestampToSlot(blockTimestamp uint64, genesisSlotTimestamp uint64, secondsPerSlot uint64) uint64 { + return (blockTimestamp - genesisSlotTimestamp) / secondsPerSlot +} + +func SlotToEpoch(slot uint64, slotsPerEpoch uint64) uint64 { + return slot / slotsPerEpoch +} diff --git a/rolling-shutter/medley/slotticker/slotticker.go b/rolling-shutter/medley/slotticker/slotticker.go new file mode 100644 index 000000000..a353e8217 --- /dev/null +++ b/rolling-shutter/medley/slotticker/slotticker.go @@ -0,0 +1,113 @@ +package slotticker + +import ( + "context" + "time" + + "github.com/rs/zerolog/log" + + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" +) + +type Slot struct { + Number uint64 + genesisSlotTime time.Time + slotDuration time.Duration +} + +func (s Slot) Start() time.Time { + return s.genesisSlotTime.Add(s.slotDuration * time.Duration(s.Number)) +} + +// SlotTicker is a ticker that ticks at the start of each slot. +type SlotTicker struct { + C chan Slot + slotDuration time.Duration + genesisSlotTime time.Time + offset time.Duration +} + +func NewSlotTicker(slotDuration time.Duration, genesisSlotTime time.Time, offset time.Duration) *SlotTicker { + c := make(chan Slot, 1) + return &SlotTicker{ + C: c, + slotDuration: slotDuration, + genesisSlotTime: genesisSlotTime, + offset: offset, + } +} + +func (t *SlotTicker) tick(ctx context.Context, n uint64) error { + s := Slot{ + Number: n, + genesisSlotTime: t.genesisSlotTime, + slotDuration: t.slotDuration, + } + select { + case <-ctx.Done(): + return ctx.Err() + case t.C <- s: + return nil + } +} + +//nolint:unparam +func (t *SlotTicker) Start(ctx context.Context, runner service.Runner) error { + runner.Go(func() error { + return t.run(ctx) + }) + return nil +} + +func (t *SlotTicker) run(ctx context.Context) error { + var prevSlotNumber *uint64 = nil + timer := time.NewTimer(0) + <-timer.C + + for { + now := time.Now() + nextSlotNumber, nextTickTime := calcNextTick(now, t.genesisSlotTime, t.slotDuration, t.offset) + + if prevSlotNumber != nil { + expectedNextSlotNumber := *prevSlotNumber + 1 + if nextSlotNumber < expectedNextSlotNumber { + // This should never happen unless the system clock changes. If it does, there's + // nothing we can do about it. + log.Error(). + Uint64("next-slot-number", nextSlotNumber). + Uint64("prev-slot-number", *prevSlotNumber). + Msg("slot ticker emitted slots in wrong order") + } else if nextSlotNumber > expectedNextSlotNumber { + log.Warn(). + Uint64("next-slot-number", nextSlotNumber). + Uint64("prev-slot-number", *prevSlotNumber). + Msg("missing slots due to slow slot processing") + for i := expectedNextSlotNumber; i < nextSlotNumber; i++ { + if err := t.tick(ctx, i); err != nil { + return err + } + } + } + } + + timeToNextSlot := nextTickTime.Sub(now) + timer.Reset(timeToNextSlot) + <-timer.C + + if err := t.tick(ctx, nextSlotNumber); err != nil { + return err + } + + prevSlotNumber = &nextSlotNumber + } +} + +func calcNextTick(now time.Time, genesisSlotTime time.Time, slotDuration time.Duration, offset time.Duration) (uint64, time.Time) { + firstTick := genesisSlotTime.Add(offset) + if now.Before(firstTick) { + return 0, firstTick + } + slot := uint64((now.Sub(genesisSlotTime) - offset + slotDuration - 1) / slotDuration) + tick := genesisSlotTime.Add(slotDuration * time.Duration(slot)).Add(offset) + return slot, tick +} diff --git a/rolling-shutter/medley/slotticker/slotticker_test.go b/rolling-shutter/medley/slotticker/slotticker_test.go new file mode 100644 index 000000000..a73790826 --- /dev/null +++ b/rolling-shutter/medley/slotticker/slotticker_test.go @@ -0,0 +1,44 @@ +package slotticker + +import ( + "testing" + "time" + + "gotest.tools/assert" +) + +func TestCalcNextSlot(t *testing.T) { + duration := time.Second * 5 + genesisTime := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) + + epsilon := time.Millisecond + + for _, testCase := range []struct { + timeSinceGenesis time.Duration + offset time.Duration + slot uint64 + }{ + {0, 0, 0}, + {-time.Second * 100, 0, 0}, + {epsilon, 0, 1}, + {duration / 2, 0, 1}, + {duration, 0, 1}, + {2 * duration, 0, 2}, + {100*duration - epsilon, 0, 100}, + + {-time.Second, -time.Second, 0}, + {0, -time.Second, 1}, + {4 * time.Second, -time.Second, 1}, + {4*time.Second + epsilon, -time.Second, 2}, + {100*duration - time.Second, -time.Second, 100}, + {100*duration - time.Second + epsilon, -time.Second, 101}, + } { + t.Run("", func(t *testing.T) { + now := genesisTime.Add(testCase.timeSinceGenesis) + slot, tick := calcNextTick(now, genesisTime, duration, testCase.offset) + assert.Equal(t, testCase.slot, slot) + expectedTick := genesisTime.Add(duration * time.Duration(testCase.slot)).Add(testCase.offset) + assert.Equal(t, tick, expectedTick) + }) + } +} diff --git a/rolling-shutter/medley/syncranges.go b/rolling-shutter/medley/syncranges.go new file mode 100644 index 000000000..61712075f --- /dev/null +++ b/rolling-shutter/medley/syncranges.go @@ -0,0 +1,15 @@ +package medley + +func GetSyncRanges(start, end, maxRange uint64) [][2]uint64 { + ranges := [][2]uint64{} + for i := start; i <= end; i += maxRange { + s := i + e := i + maxRange - 1 + ranges = append(ranges, [2]uint64{s, e}) + if e > end { + ranges[len(ranges)-1][1] = end + break + } + } + return ranges +} diff --git a/rolling-shutter/medley/syncranges_test.go b/rolling-shutter/medley/syncranges_test.go new file mode 100644 index 000000000..cb5e5ccf9 --- /dev/null +++ b/rolling-shutter/medley/syncranges_test.go @@ -0,0 +1,31 @@ +package medley + +import ( + "testing" + + "gotest.tools/v3/assert" +) + +func TestGetSyncRanges(t *testing.T) { + var maxRange uint64 = 3 + testCases := []struct { + start uint64 + end uint64 + ranges [][2]uint64 + }{ + {start: 0, end: 0, ranges: [][2]uint64{{0, 0}}}, + {start: 3, end: 3, ranges: [][2]uint64{{3, 3}}}, + {start: 0, end: 2, ranges: [][2]uint64{{0, 2}}}, + {start: 3, end: 5, ranges: [][2]uint64{{3, 5}}}, + {start: 0, end: 5, ranges: [][2]uint64{{0, 2}, {3, 5}}}, + {start: 3, end: 8, ranges: [][2]uint64{{3, 5}, {6, 8}}}, + {start: 0, end: 1, ranges: [][2]uint64{{0, 1}}}, + {start: 3, end: 4, ranges: [][2]uint64{{3, 4}}}, + {start: 0, end: 4, ranges: [][2]uint64{{0, 2}, {3, 4}}}, + {start: 1, end: 5, ranges: [][2]uint64{{1, 3}, {4, 5}}}, + } + for _, testCase := range testCases { + ranges := GetSyncRanges(testCase.start, testCase.end, maxRange) + assert.DeepEqual(t, ranges, testCase.ranges) + } +} diff --git a/rolling-shutter/medley/validatorregistry/signature.go b/rolling-shutter/medley/validatorregistry/signature.go new file mode 100644 index 000000000..309fef7f8 --- /dev/null +++ b/rolling-shutter/medley/validatorregistry/signature.go @@ -0,0 +1,18 @@ +package validatorregistry + +import ( + "github.com/ethereum/go-ethereum/crypto" + blst "github.com/supranational/blst/bindings/go" +) + +var dst = []byte("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_") + +func VerifySignature(sig *blst.P2Affine, pubkey *blst.P1Affine, msg *RegistrationMessage) bool { + msgHash := crypto.Keccak256(msg.Marshal()) + return sig.Verify(true, pubkey, true, msgHash, dst) +} + +func CreateSignature(sk *blst.SecretKey, msg *RegistrationMessage) *blst.P2Affine { + msgHash := crypto.Keccak256(msg.Marshal()) + return new(blst.P2Affine).Sign(sk, msgHash, dst) +} diff --git a/rolling-shutter/medley/validatorregistry/signature_test.go b/rolling-shutter/medley/validatorregistry/signature_test.go new file mode 100644 index 000000000..442ae4490 --- /dev/null +++ b/rolling-shutter/medley/validatorregistry/signature_test.go @@ -0,0 +1,32 @@ +package validatorregistry + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + blst "github.com/supranational/blst/bindings/go" +) + +func TestSignature(t *testing.T) { + msg := &RegistrationMessage{ + Version: 1, + ChainID: 2, + ValidatorRegistryAddress: common.HexToAddress("0x1234567890123456789012345678901234567890"), + ValidatorIndex: 3, + Nonce: 4, + IsRegistration: true, + } + + var ikm [32]byte + privkey := blst.KeyGen(ikm[:]) + pubkey := new(blst.P1Affine).From(privkey) + + sig := CreateSignature(privkey, msg) + check := VerifySignature(sig, pubkey, msg) + assert.True(t, check) + + msg.IsRegistration = false + check = VerifySignature(sig, pubkey, msg) + assert.False(t, check) +} diff --git a/rolling-shutter/medley/validatorregistry/validatorregistry.go b/rolling-shutter/medley/validatorregistry/validatorregistry.go new file mode 100644 index 000000000..1d5004a09 --- /dev/null +++ b/rolling-shutter/medley/validatorregistry/validatorregistry.go @@ -0,0 +1,54 @@ +package validatorregistry + +import ( + "encoding/binary" + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +type RegistrationMessage struct { + Version uint8 + ChainID uint64 + ValidatorRegistryAddress common.Address + ValidatorIndex uint64 + Nonce uint64 + IsRegistration bool +} + +func (m *RegistrationMessage) Marshal() []byte { + b := make([]byte, 0) + b = append(b, m.Version) + b = binary.BigEndian.AppendUint64(b, m.ChainID) + b = append(b, m.ValidatorRegistryAddress.Bytes()...) + b = binary.BigEndian.AppendUint64(b, m.ValidatorIndex) + b = binary.BigEndian.AppendUint64(b, m.Nonce) + if m.IsRegistration { + b = append(b, 1) + } else { + b = append(b, 0) + } + return b +} + +func (m *RegistrationMessage) Unmarshal(b []byte) error { + expectedLength := 1 + 8 + 20 + 8 + 8 + 1 + if len(b) != expectedLength { + return fmt.Errorf("invalid registration message length %d, expected %d", len(b), expectedLength) + } + + m.Version = b[0] + m.ChainID = binary.BigEndian.Uint64(b[1:9]) + m.ValidatorRegistryAddress = common.BytesToAddress(b[9:29]) + m.ValidatorIndex = binary.BigEndian.Uint64(b[29:37]) + m.Nonce = binary.BigEndian.Uint64(b[37:45]) + switch b[45] { + case 0: + m.IsRegistration = false + case 1: + m.IsRegistration = true + default: + return fmt.Errorf("invalid registration message type byte %d", b[45]) + } + return nil +} diff --git a/rolling-shutter/medley/validatorregistry/validatorregistry_test.go b/rolling-shutter/medley/validatorregistry/validatorregistry_test.go new file mode 100644 index 000000000..d49651095 --- /dev/null +++ b/rolling-shutter/medley/validatorregistry/validatorregistry_test.go @@ -0,0 +1,47 @@ +package validatorregistry + +import ( + "bytes" + "testing" + + "github.com/ethereum/go-ethereum/common" + "gotest.tools/v3/assert" +) + +func TestRegistrationMessageMarshalRoundtrip(t *testing.T) { + m := &RegistrationMessage{ + Version: 1, + ChainID: 2, + ValidatorRegistryAddress: common.HexToAddress("0x1234567890123456789012345678901234567890"), + ValidatorIndex: 3, + Nonce: 4, + IsRegistration: true, + } + marshaled := m.Marshal() + unmarshaled := new(RegistrationMessage) + err := unmarshaled.Unmarshal(marshaled) + assert.NilError(t, err) + assert.DeepEqual(t, m, unmarshaled) +} + +func TestRegistrationMessageInvalidUnmarshal(t *testing.T) { + base := bytes.Repeat([]byte{0}, 46) + assert.NilError(t, new(RegistrationMessage).Unmarshal(base)) + + for _, b := range [][]byte{ + {}, + bytes.Repeat([]byte{0}, 45), + bytes.Repeat([]byte{0}, 47), + bytes.Repeat([]byte{0}, 92), + } { + err := new(RegistrationMessage).Unmarshal(b) + assert.ErrorContains(t, err, "invalid registration message length") + } + + for _, isRegistrationByte := range []byte{2, 3, 255} { + b := bytes.Repeat([]byte{0}, 46) + b[45] = isRegistrationByte + err := new(RegistrationMessage).Unmarshal(b) + assert.ErrorContains(t, err, "invalid registration message type byte") + } +} diff --git a/rolling-shutter/mocknode/mocknode.go b/rolling-shutter/mocknode/mocknode.go index 8261fe917..b073c39fa 100644 --- a/rolling-shutter/mocknode/mocknode.go +++ b/rolling-shutter/mocknode/mocknode.go @@ -8,7 +8,6 @@ import ( "sync" "time" - bn256 "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" "github.com/pkg/errors" "github.com/rs/zerolog/log" txtypes "github.com/shutter-network/txtypes/types" @@ -107,7 +106,7 @@ func (m *MockNode) handleEonPublicKey( if err := m.eonPublicKey.Unmarshal(key.PublicKey); err != nil { log.Info().Err(err).Msg("failed to unmarshal eon public key") } - log.Info().Str("eon-public-key", (*bn256.G2)(m.eonPublicKey).String()). + log.Info().Hex("eon-public-key", m.eonPublicKey.Marshal()). Msg("updated eon public key from messages to %s") return make([]p2pmsg.Message, 0), nil } diff --git a/rolling-shutter/p2p/bootstrap.go b/rolling-shutter/p2p/bootstrap.go index 6319130fb..8f301494b 100644 --- a/rolling-shutter/p2p/bootstrap.go +++ b/rolling-shutter/p2p/bootstrap.go @@ -73,21 +73,23 @@ func bootstrap( f := func(c context.Context) (bool, error) { if err := connectBootstrapNodes(c, h, config.BootstrapPeers); err != nil { - return true, err + return false, err } - return false, nil + return true, nil } if config.IsBootstrapNode { // A bootstrap node is not required to connect to other bootstrap nodes. // If however we did configure a list of bootstrap nodes, // we should try a long time to connect to at least one other bootstrapper first. + backoffMult := float64(1.01) _, err := retry.FunctionCall( ctx, f, - retry.MaxInterval(5*time.Hour), + retry.MaxInterval(1*time.Minute), retry.StopOnErrors(errInsufficientBootstrpConfigured), - retry.Interval(2*time.Minute)) + retry.Interval(2*time.Second), + retry.ExponentialBackoff(&backoffMult)) if err != nil { log.Error().Err(err). Msg("failed to bootstrap, continuing without peer connections.") @@ -96,9 +98,8 @@ func bootstrap( _, err := retry.FunctionCall( ctx, f, - retry.MaxInterval(5*time.Minute), retry.StopOnErrors(errInsufficientBootstrpConfigured), - retry.Interval(30*time.Second)) + retry.Interval(2*time.Second)) if err != nil { // For normal peers, after trying some time it is reasonable to halt. // If we don't get an initial connection to a bootsrap node, diff --git a/rolling-shutter/p2p/config.go b/rolling-shutter/p2p/config.go index 716ced2ca..55bc4e2f6 100644 --- a/rolling-shutter/p2p/config.go +++ b/rolling-shutter/p2p/config.go @@ -45,6 +45,7 @@ type Config struct { ListenAddresses []*address.P2PAddress CustomBootstrapAddresses []*address.P2PAddress `comment:"Overwrite p2p boostrap nodes"` Environment env.Environment + DiscoveryNamespace string `shconfig:",required" comment:"Must be unique for each instance id."` } func (c *Config) Name() string { @@ -76,6 +77,7 @@ func (c *Config) SetExampleValues() error { ), } c.Environment = env.EnvironmentProduction + c.DiscoveryNamespace = "shutter-42" p2pkey, err := keys.GenerateLibp2pPrivate(rand.Reader) if err != nil { diff --git a/rolling-shutter/p2p/dht.go b/rolling-shutter/p2p/dht.go index b6b31c2a1..80fa0692b 100644 --- a/rolling-shutter/p2p/dht.go +++ b/rolling-shutter/p2p/dht.go @@ -1,9 +1,17 @@ package p2p import ( + "context" + "time" + dht "github.com/libp2p/go-libp2p-kad-dht" - "github.com/libp2p/go-libp2p/core/peer" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/discovery" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/protocol" + "github.com/libp2p/go-libp2p/p2p/discovery/util" + "github.com/rs/zerolog/log" "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/env" ) @@ -15,18 +23,24 @@ const ( dhtProtocolPrefix protocol.ID = "/shutter" dhtProtocolExtensionStaging protocol.ID = "/staging" dhtProtocolExtensionLocal protocol.ID = "/local" + + findPeerInterval = 10 * time.Second +) + +var ( + peerLow = pubsub.GossipSubDlo * 2 + peerTarget = pubsub.GossipSubDhi * 3 + peerHigh = pubsub.GossipSubDhi * 6 ) -func dhtRoutingOptions( - environment env.Environment, - bootstrapPeers ...peer.AddrInfo, -) []dht.Option { +func dhtRoutingOptions(config *p2pNodeConfig) []dht.Option { // options with higher index in the array will overwrite existing ones opts := []dht.Option{ dht.ProtocolPrefix(dhtProtocolPrefix), + dht.BootstrapPeers(config.BootstrapPeers...), } - switch environment { //nolint: exhaustive + switch config.Environment { //nolint: exhaustive case env.EnvironmentStaging: opts = append(opts, dht.ProtocolExtension(dhtProtocolExtensionStaging), @@ -42,10 +56,56 @@ func dhtRoutingOptions( default: } - if len(bootstrapPeers) > 0 { - // this overwrites the option set before - opts = append(opts, dht.BootstrapPeers(bootstrapPeers...)) + if config.IsBootstrapNode { + opts = append(opts, dht.Mode(dht.ModeServer)) } return opts } + +func findPeers(ctx context.Context, h host.Host, d discovery.Discoverer, ns string) error { + log.Info().Str("namespace", ns).Msg("starting peer discovery") + + ticker := time.NewTicker(findPeerInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + peersBefore := len(h.Network().Peers()) + if peersBefore >= peerTarget { + continue + } + + peers, err := util.FindPeers(ctx, d, ns) + if err != nil { + log.Error().Err(err).Msg("error finding peers") + } + + newConnections := 0 + failedDials := 0 + for _, p := range peers { + if p.ID == h.ID() { + continue + } + if h.Network().Connectedness(p.ID) != network.Connected { + _, err = h.Network().DialPeer(ctx, p.ID) + if err != nil { + log.Error().Err(err).Str("peer", p.ID.String()).Msg("error dialing peer") + failedDials++ + } + newConnections++ + } + } + + log.Debug(). + Int("peers-before", peersBefore). + Int("peer-target", peerTarget). + Int("new-connections", newConnections). + Int("failed-dials", failedDials). + Msg("looking for peers") + } + } +} diff --git a/rolling-shutter/p2p/messaging.go b/rolling-shutter/p2p/messaging.go index 6bece4c58..54b3c023f 100644 --- a/rolling-shutter/p2p/messaging.go +++ b/rolling-shutter/p2p/messaging.go @@ -25,9 +25,34 @@ type ( HandlerFunc func(context.Context, p2pmsg.Message) ([]p2pmsg.Message, error) HandlerRegistry map[protoreflect.FullName][]HandlerFunc ValidatorFunc func(context.Context, p2pmsg.Message) (pubsub.ValidationResult, error) - ValidatorRegistry map[string]pubsub.ValidatorEx + ValidatorRegistry map[string][]pubsub.ValidatorEx ) +func (r *ValidatorRegistry) GetCombinedValidator(topic string) pubsub.ValidatorEx { + validate := func(ctx context.Context, sender peer.ID, message *pubsub.Message) pubsub.ValidationResult { + ignored := false + for _, valFunc := range (*r)[topic] { + res := valFunc(ctx, sender, message) + switch res { + case pubsub.ValidationAccept: + continue + case pubsub.ValidationReject: + return pubsub.ValidationReject + case pubsub.ValidationIgnore: + ignored = true + default: + log.Warn().Str("topic", topic).Msg("unknown validation result %d, treating as reject") + return pubsub.ValidationReject + } + } + if ignored { + return pubsub.ValidationIgnore + } + return pubsub.ValidationAccept + } + return validate +} + const ( allowTraceContext = true // whether we allow the trace field to be set in the message envelope invalidResultType = pubsub.ValidationReject @@ -57,13 +82,10 @@ func New(config *Config) (*P2PMessaging, error) { listenAddresses = append(listenAddresses, addr.Multiaddr) } cfg := &p2pNodeConfig{ - ListenAddrs: listenAddresses, - PrivKey: *config.P2PKey, - Environment: config.Environment, - // for now, disable those features, since - // they are not stable from our side - DisableTopicDHT: true, - DisableRoutingDHT: true, + ListenAddrs: listenAddresses, + PrivKey: *config.P2PKey, + Environment: config.Environment, + DiscoveryNamespace: config.DiscoveryNamespace, } bootstrapAddresses := config.CustomBootstrapAddresses @@ -134,18 +156,8 @@ func (m *P2PMessaging) AddHandlerFunc(handlerFunc HandlerFunc, protos ...p2pmsg. func (m *P2PMessaging) addValidatorImpl(valFunc ValidatorFunc, messProto p2pmsg.Message) { topic := messProto.Topic() - _, exists := m.validatorRegistry[topic] - if exists { - // This is likely not intended and happens when different messages return the same P2PMessage.Topic(). - // Currently a topic is mapped 1 to 1 to a message type (instead of using an envelope for unmarshalling) - // (If feature needed, allow for chaining of successively registered validator functions per topic) - panic(errors.Errorf( - "can't register more than one validator per topic (topic: '%s', message-type: '%s')", - topic, - reflect.TypeOf(messProto))) - } handleError := func(err error) { - log.Info().Str("topic", topic).Err(err).Msg("received invalid message)") + log.Info().Str("topic", topic).Err(err).Msg("received invalid message") } validate := func(ctx context.Context, sender peer.ID, message *pubsub.Message) pubsub.ValidationResult { if message.GetTopic() != topic { @@ -176,8 +188,12 @@ func (m *P2PMessaging) addValidatorImpl(valFunc ValidatorFunc, messProto p2pmsg. } return valid } - m.validatorRegistry[topic] = validate - m.AddGossipTopic(topic) + + _, exists := m.validatorRegistry[topic] + if !exists { + m.AddGossipTopic(topic) + } + m.validatorRegistry[topic] = append(m.validatorRegistry[topic], validate) } // AddValidator will add a validator-function to a P2PHandler instance: @@ -217,7 +233,7 @@ func (m *P2PMessaging) Start( runner service.Runner, ) error { //nolint:unparam runner.Go(func() error { - return m.P2P.Run(ctx, m.topics(), m.validatorRegistry) + return m.P2P.Run(ctx, runner, m.topics(), m.validatorRegistry) }) if m.hasHandler() { runner.Go(func() error { diff --git a/rolling-shutter/p2p/p2p.go b/rolling-shutter/p2p/p2p.go index f42fb3327..4c7f25202 100644 --- a/rolling-shutter/p2p/p2p.go +++ b/rolling-shutter/p2p/p2p.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "sync" - "time" "github.com/libp2p/go-libp2p" dht "github.com/libp2p/go-libp2p-kad-dht" @@ -12,16 +11,17 @@ import ( "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/p2p/discovery/routing" + "github.com/libp2p/go-libp2p/p2p/discovery/util" rhost "github.com/libp2p/go-libp2p/p2p/host/routed" "github.com/libp2p/go-libp2p/p2p/net/connmgr" "github.com/multiformats/go-multiaddr" "github.com/pkg/errors" "github.com/rs/zerolog/log" - "golang.org/x/sync/errgroup" "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/address" "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/env" "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/keys" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" ) var DefaultBootstrapPeers []*address.P2PAddress @@ -51,10 +51,10 @@ type Notifee interface { type P2PNode struct { config p2pNodeConfig - connmngr *connmgr.BasicConnMgr mux sync.Mutex host host.Host dht *dht.IpfsDHT + discovery *routing.RoutingDiscovery pubSub *pubsub.PubSub gossipRooms map[string]*gossipRoom @@ -62,19 +62,17 @@ type P2PNode struct { } type p2pNodeConfig struct { - ListenAddrs []multiaddr.Multiaddr - BootstrapPeers []peer.AddrInfo - PrivKey keys.Libp2pPrivate - Environment env.Environment - IsBootstrapNode bool - DisableTopicDHT bool - DisableRoutingDHT bool + ListenAddrs []multiaddr.Multiaddr + BootstrapPeers []peer.AddrInfo + PrivKey keys.Libp2pPrivate + Environment env.Environment + IsBootstrapNode bool + DiscoveryNamespace string } func NewP2PNode(config p2pNodeConfig) *P2PNode { p := P2PNode{ config: config, - connmngr: nil, host: nil, pubSub: nil, gossipRooms: make(map[string]*gossipRoom), @@ -85,52 +83,53 @@ func NewP2PNode(config p2pNodeConfig) *P2PNode { func (p *P2PNode) Run( ctx context.Context, + runner service.Runner, topicNames []string, topicValidators ValidatorRegistry, ) error { - defer func() { - close(p.GossipMessages) - }() + p.mux.Lock() + defer p.mux.Unlock() - errorgroup, errorgroupctx := errgroup.WithContext(ctx) - errorgroup.Go(func() error { - p.mux.Lock() - defer p.mux.Unlock() - if err := p.init(ctx); err != nil { - return err - } + runner.Defer(func() { + close(p.GossipMessages) + }) - for topicName, validator := range topicValidators { - if err := p.pubSub.RegisterTopicValidator(topicName, validator); err != nil { - return err - } - } + if err := p.init(ctx); err != nil { + return err + } - if err := p.joinTopics(topicNames); err != nil { + for topicName := range topicValidators { + validator := topicValidators.GetCombinedValidator(topicName) + if err := p.pubSub.RegisterTopicValidator(topicName, validator); err != nil { return err } + } - // listen to gossip on all topics - for _, room := range p.gossipRooms { - room := room - errorgroup.Go(func() error { - return room.readLoop(errorgroupctx, p.GossipMessages) - }) - } - - err := bootstrap(ctx, p.host, p.config, p.dht) - if err != nil { - return err - } + if err := p.joinTopics(topicNames); err != nil { + return err + } - // block the function until the context is canceled - errorgroup.Go(func() error { - <-errorgroupctx.Done() - return ctx.Err() + err := bootstrap(ctx, p.host, p.config, p.dht) + if err != nil { + return err + } + // listen to gossip on all topics + for _, room := range p.gossipRooms { + room := room + runner.Go(func() error { + return room.readLoop(ctx, p.GossipMessages) }) - return nil + } + runner.Go(func() error { + log.Info().Str("namespace", p.config.DiscoveryNamespace).Msg("starting advertizing discovery node") + util.Advertise(ctx, p.discovery, p.config.DiscoveryNamespace) + <-ctx.Done() + return ctx.Err() }) - return errorgroup.Wait() + runner.Go(func() error { + return findPeers(ctx, p.host, p.discovery, p.config.DiscoveryNamespace) + }) + return nil } func (p *P2PNode) Publish(ctx context.Context, topic string, message []byte) error { @@ -149,43 +148,56 @@ func (p *P2PNode) init(ctx context.Context) error { if p.host != nil { return errors.New("Cannot create host on p2p with existing host") } - p2pHost, hashTable, connectionManager, err := createHost(ctx, p.config) + p2pHost, hashTable, err := createHost(ctx, p.config) if err != nil { return err } - p2pPubSub, err := createPubSub(ctx, p2pHost, p.config, hashTable) + discovery := routing.NewRoutingDiscovery(hashTable) + p2pPubSub, err := createPubSub(ctx, p2pHost, p.config, discovery) if err != nil { return err } p.host = p2pHost p.dht = hashTable - p.connmngr = connectionManager + p.discovery = discovery p.pubSub = p2pPubSub log.Info().Str("address", p.p2pAddress()).Msg("created libp2p host") return nil } +func createConnectionManager() (*connmgr.BasicConnMgr, error) { + // TODO: This starts a background goroutine. It works, but it's better to do that later in + // P2PNode.Run() when we have a proper context. + m, err := connmgr.NewConnManager(peerLow, peerHigh) + if err != nil { + return nil, errors.Wrap(err, "failed to create connection manager") + } + + return m, nil +} + func createHost( ctx context.Context, config p2pNodeConfig, -) (host.Host, *dht.IpfsDHT, *connmgr.BasicConnMgr, error) { +) (host.Host, *dht.IpfsDHT, error) { var err error - connectionManager, err := connmgr.NewConnManager( - 160, // Lowwater - 192, // HighWater, - connmgr.WithGracePeriod(time.Minute), - ) + // NOTE: + // Upon initialization, we are seeing log warnings: + // "rcmgr limit conflicts with connmgr limit: conn manager high watermark limit: 192, exceeds the system connection limit of: 1" + // + // This was a bug in the check function, reading the wrong config value to check against: + // https://github.com/libp2p/go-libp2p/issues/2628 + + connectionManager, err := createConnectionManager() if err != nil { - return nil, nil, nil, err + return nil, nil, err } options := []libp2p.Option{ libp2p.Identity(&config.PrivKey.Key), libp2p.ListenAddrs(config.ListenAddrs...), - libp2p.DefaultTransports, - libp2p.DefaultSecurity, libp2p.ConnectionManager(connectionManager), libp2p.ProtocolVersion(protocolVersion), } @@ -204,30 +216,26 @@ func createHost( p2pHost, err := libp2p.New(options...) if err != nil { - return nil, nil, nil, err + return nil, nil, err } - if config.DisableRoutingDHT { - return p2pHost, nil, connectionManager, err - } - - opts := dhtRoutingOptions(config.Environment, config.BootstrapPeers...) + opts := dhtRoutingOptions(&config) idht, err := dht.New(ctx, p2pHost, opts...) if err != nil { - return nil, nil, nil, err + return nil, nil, err } // the wrapped host will try to query the routing table (dht)/ // whenever it doesn't have the full routed address for a peer id routedHost := rhost.Wrap(p2pHost, idht) - return routedHost, idht, connectionManager, nil + return routedHost, idht, nil } func createPubSub( ctx context.Context, p2pHost host.Host, config p2pNodeConfig, - hashTable *dht.IpfsDHT, + discovery *routing.RoutingDiscovery, ) (*pubsub.PubSub, error) { gossipSubParams, peerScoreParams, peerScoreThresholds := makePubSubParams(pubSubParamsOptions{ isBootstrapNode: config.IsBootstrapNode, @@ -237,14 +245,9 @@ func createPubSub( pubsubOptions := []pubsub.Option{ pubsub.WithGossipSubParams(*gossipSubParams), pubsub.WithPeerScore(peerScoreParams, peerScoreThresholds), + pubsub.WithDiscovery(discovery), } - if !config.DisableTopicDHT { - pubsubOptions = append( - pubsubOptions, - pubsub.WithDiscovery(routing.NewRoutingDiscovery(hashTable)), - ) - } if config.IsBootstrapNode { // enables the pubsub v1.1 feature to handle discovery and // connection management over the PubSub protocol diff --git a/rolling-shutter/p2p/p2p_test.go b/rolling-shutter/p2p/p2p_test.go index c80e6a298..6071cf8a8 100644 --- a/rolling-shutter/p2p/p2p_test.go +++ b/rolling-shutter/p2p/p2p_test.go @@ -4,16 +4,17 @@ import ( "bytes" "context" "fmt" - "sync" "testing" "time" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/pkg/errors" "github.com/rs/zerolog/log" "gotest.tools/assert" "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable" "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/address" + "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service" "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/testlog" ) @@ -21,12 +22,14 @@ func init() { testlog.Setup() } +var ErrTestComplete = errors.New("test complete") + // TestStartNetworkNode test that we can init two p2p nodes and make them send/receive messages. func TestStartNetworkNodeIntegration(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } - ctx, cancel := context.WithTimeout(context.Background(), 1200*time.Millisecond) + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() numBootstrappers := 2 @@ -82,51 +85,54 @@ func TestStartNetworkNodeIntegration(t *testing.T) { gossipTopicNames := []string{"testTopic1", "testTopic2"} testMessage := []byte("test message") - runctx, stopRun := context.WithCancel(ctx) - - waitGroup := sync.WaitGroup{} p2ps := []*P2PNode{} - for _, cfg := range configs { + services := make([]service.Service, len(configs)) + for i, cfg := range configs { p2pHandler, err := New(cfg) assert.NilError(t, err) p2ps = append(p2ps, p2pHandler.P2P) - waitGroup.Add(1) - go func() { - defer waitGroup.Done() - err := p2pHandler.P2P.Run(runctx, gossipTopicNames, map[string]pubsub.ValidatorEx{}) - assert.Assert(t, err == context.Canceled) - }() + fn := func(ctx context.Context, runner service.Runner) error { + return p2pHandler.P2P.Run(ctx, runner, gossipTopicNames, map[string][]pubsub.ValidatorEx{}) + } + services[i] = service.Function{Func: fn} } - defer func() { - stopRun() - waitGroup.Wait() - }() + // The following loop publishes the same message over and over. Even though we did call // ConnectToPeer, libp2p takes some time until the peer receives the first message. - var message *pubsub.Message - topicName := gossipTopicNames[0] - for message == nil { - if err := p2ps[1].Publish(ctx, topicName, testMessage); err != nil { - t.Fatalf("error while publishing message: %v", err) - } + testFn := func(ctx context.Context, _ service.Runner) error { + // HACK: + time.Sleep(1 * time.Second) + var message *pubsub.Message + topicName := gossipTopicNames[0] + for message == nil { + if err := p2ps[1].Publish(ctx, topicName, testMessage); err != nil { + t.Fatalf("error while publishing message: %v", err) + } - select { - case message = <-p2ps[0].GossipMessages: - log.Info().Interface("message", message).Msg("got message") - if message == nil { - t.Fatalf("channel closed unexpectedly") + select { + case message = <-p2ps[0].GossipMessages: + log.Info().Interface("message", message).Msg("got message") + if message == nil { + t.Fatalf("channel closed unexpectedly") + } + case <-ctx.Done(): + t.Fatalf("waiting for message: %s", ctx.Err()) + case <-time.After(5 * time.Millisecond): } - case <-ctx.Done(): - t.Fatalf("waiting for message: %s", ctx.Err()) - case <-time.After(5 * time.Millisecond): } + assert.Equal(t, topicName, message.GetTopic(), "received message with wrong topic") + assert.Check(t, bytes.Equal(testMessage, message.GetData()), "received wrong message") + assert.Equal( + t, + p2ps[1].HostID(), + message.GetFrom().String(), + "received message with wrong sender", + ) + return ErrTestComplete } - assert.Equal(t, topicName, message.GetTopic(), "received message with wrong topic") - assert.Check(t, bytes.Equal(testMessage, message.GetData()), "received wrong message") - assert.Equal( - t, - p2ps[1].HostID(), - message.GetFrom().String(), - "received message with wrong sender", - ) + testService := service.Function{Func: testFn} + services = append(services, testService) + + err := service.Run(ctx, services...) + assert.Error(t, err, ErrTestComplete.Error()) } diff --git a/rolling-shutter/p2p/params.go b/rolling-shutter/p2p/params.go index c18a1f2d6..901d53817 100644 --- a/rolling-shutter/p2p/params.go +++ b/rolling-shutter/p2p/params.go @@ -17,6 +17,14 @@ func makePubSubParams( ) (*pubsub.GossipSubParams, *pubsub.PeerScoreParams, *pubsub.PeerScoreThresholds) { gsDefault := pubsub.DefaultGossipSubParams() gossipSubParams := &gsDefault + + // modified defaults from ethereum consensus spec + + //nolint:lll + // https://github.com/ethereum/consensus-specs/blob/5d80b1954a4b7a121aa36143d50b366727b66cbc/specs/phase0/p2p-interface.md#why-are-these-specific-gossip-parameters-chosen + gossipSubParams.HeartbeatInterval = 700 * time.Millisecond + gossipSubParams.HistoryLength = 6 + // From the spec: // to allow bootstrapping via PeerExchange (PX), // the bootstrappers should not form a mesh, thus D=D_lo=D_hi=D_out=0 diff --git a/rolling-shutter/p2pmsg/gossip.pb.go b/rolling-shutter/p2pmsg/gossip.pb.go index 648ffe0a1..851372560 100644 --- a/rolling-shutter/p2pmsg/gossip.pb.go +++ b/rolling-shutter/p2pmsg/gossip.pb.go @@ -155,6 +155,107 @@ func (x *KeyShare) GetShare() []byte { return nil } +type GnosisDecryptionKeySharesExtra struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Slot uint64 `protobuf:"varint,1,opt,name=slot,proto3" json:"slot,omitempty"` + TxPointer uint64 `protobuf:"varint,2,opt,name=tx_pointer,json=txPointer,proto3" json:"tx_pointer,omitempty"` + Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` +} + +func (x *GnosisDecryptionKeySharesExtra) Reset() { + *x = GnosisDecryptionKeySharesExtra{} + if protoimpl.UnsafeEnabled { + mi := &file_gossip_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GnosisDecryptionKeySharesExtra) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GnosisDecryptionKeySharesExtra) ProtoMessage() {} + +func (x *GnosisDecryptionKeySharesExtra) ProtoReflect() protoreflect.Message { + mi := &file_gossip_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GnosisDecryptionKeySharesExtra.ProtoReflect.Descriptor instead. +func (*GnosisDecryptionKeySharesExtra) Descriptor() ([]byte, []int) { + return file_gossip_proto_rawDescGZIP(), []int{2} +} + +func (x *GnosisDecryptionKeySharesExtra) GetSlot() uint64 { + if x != nil { + return x.Slot + } + return 0 +} + +func (x *GnosisDecryptionKeySharesExtra) GetTxPointer() uint64 { + if x != nil { + return x.TxPointer + } + return 0 +} + +func (x *GnosisDecryptionKeySharesExtra) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +type OptimismDecryptionKeySharesExtra struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *OptimismDecryptionKeySharesExtra) Reset() { + *x = OptimismDecryptionKeySharesExtra{} + if protoimpl.UnsafeEnabled { + mi := &file_gossip_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OptimismDecryptionKeySharesExtra) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OptimismDecryptionKeySharesExtra) ProtoMessage() {} + +func (x *OptimismDecryptionKeySharesExtra) ProtoReflect() protoreflect.Message { + mi := &file_gossip_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OptimismDecryptionKeySharesExtra.ProtoReflect.Descriptor instead. +func (*OptimismDecryptionKeySharesExtra) Descriptor() ([]byte, []int) { + return file_gossip_proto_rawDescGZIP(), []int{3} +} + type DecryptionKeyShares struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -164,12 +265,17 @@ type DecryptionKeyShares struct { Eon uint64 `protobuf:"varint,4,opt,name=eon,proto3" json:"eon,omitempty"` KeyperIndex uint64 `protobuf:"varint,5,opt,name=keyperIndex,proto3" json:"keyperIndex,omitempty"` Shares []*KeyShare `protobuf:"bytes,9,rep,name=shares,proto3" json:"shares,omitempty"` + // Types that are assignable to Extra: + // + // *DecryptionKeyShares_Gnosis + // *DecryptionKeyShares_Optimism + Extra isDecryptionKeyShares_Extra `protobuf_oneof:"extra"` } func (x *DecryptionKeyShares) Reset() { *x = DecryptionKeyShares{} if protoimpl.UnsafeEnabled { - mi := &file_gossip_proto_msgTypes[2] + mi := &file_gossip_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -182,7 +288,7 @@ func (x *DecryptionKeyShares) String() string { func (*DecryptionKeyShares) ProtoMessage() {} func (x *DecryptionKeyShares) ProtoReflect() protoreflect.Message { - mi := &file_gossip_proto_msgTypes[2] + mi := &file_gossip_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -195,7 +301,7 @@ func (x *DecryptionKeyShares) ProtoReflect() protoreflect.Message { // Deprecated: Use DecryptionKeyShares.ProtoReflect.Descriptor instead. func (*DecryptionKeyShares) Descriptor() ([]byte, []int) { - return file_gossip_proto_rawDescGZIP(), []int{2} + return file_gossip_proto_rawDescGZIP(), []int{4} } func (x *DecryptionKeyShares) GetInstanceID() uint64 { @@ -226,6 +332,43 @@ func (x *DecryptionKeyShares) GetShares() []*KeyShare { return nil } +func (m *DecryptionKeyShares) GetExtra() isDecryptionKeyShares_Extra { + if m != nil { + return m.Extra + } + return nil +} + +func (x *DecryptionKeyShares) GetGnosis() *GnosisDecryptionKeySharesExtra { + if x, ok := x.GetExtra().(*DecryptionKeyShares_Gnosis); ok { + return x.Gnosis + } + return nil +} + +func (x *DecryptionKeyShares) GetOptimism() *OptimismDecryptionKeySharesExtra { + if x, ok := x.GetExtra().(*DecryptionKeyShares_Optimism); ok { + return x.Optimism + } + return nil +} + +type isDecryptionKeyShares_Extra interface { + isDecryptionKeyShares_Extra() +} + +type DecryptionKeyShares_Gnosis struct { + Gnosis *GnosisDecryptionKeySharesExtra `protobuf:"bytes,10,opt,name=gnosis,proto3,oneof"` +} + +type DecryptionKeyShares_Optimism struct { + Optimism *OptimismDecryptionKeySharesExtra `protobuf:"bytes,11,opt,name=optimism,proto3,oneof"` +} + +func (*DecryptionKeyShares_Gnosis) isDecryptionKeyShares_Extra() {} + +func (*DecryptionKeyShares_Optimism) isDecryptionKeyShares_Extra() {} + type Key struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -238,7 +381,7 @@ type Key struct { func (x *Key) Reset() { *x = Key{} if protoimpl.UnsafeEnabled { - mi := &file_gossip_proto_msgTypes[3] + mi := &file_gossip_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -251,7 +394,7 @@ func (x *Key) String() string { func (*Key) ProtoMessage() {} func (x *Key) ProtoReflect() protoreflect.Message { - mi := &file_gossip_proto_msgTypes[3] + mi := &file_gossip_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -264,7 +407,7 @@ func (x *Key) ProtoReflect() protoreflect.Message { // Deprecated: Use Key.ProtoReflect.Descriptor instead. func (*Key) Descriptor() ([]byte, []int) { - return file_gossip_proto_rawDescGZIP(), []int{3} + return file_gossip_proto_rawDescGZIP(), []int{5} } func (x *Key) GetIdentity() []byte { @@ -281,6 +424,115 @@ func (x *Key) GetKey() []byte { return nil } +type GnosisDecryptionKeysExtra struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Slot uint64 `protobuf:"varint,1,opt,name=slot,proto3" json:"slot,omitempty"` + TxPointer uint64 `protobuf:"varint,2,opt,name=tx_pointer,json=txPointer,proto3" json:"tx_pointer,omitempty"` + SignerIndices []uint64 `protobuf:"varint,3,rep,packed,name=signerIndices,proto3" json:"signerIndices,omitempty"` + Signatures [][]byte `protobuf:"bytes,4,rep,name=signatures,proto3" json:"signatures,omitempty"` +} + +func (x *GnosisDecryptionKeysExtra) Reset() { + *x = GnosisDecryptionKeysExtra{} + if protoimpl.UnsafeEnabled { + mi := &file_gossip_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GnosisDecryptionKeysExtra) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GnosisDecryptionKeysExtra) ProtoMessage() {} + +func (x *GnosisDecryptionKeysExtra) ProtoReflect() protoreflect.Message { + mi := &file_gossip_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GnosisDecryptionKeysExtra.ProtoReflect.Descriptor instead. +func (*GnosisDecryptionKeysExtra) Descriptor() ([]byte, []int) { + return file_gossip_proto_rawDescGZIP(), []int{6} +} + +func (x *GnosisDecryptionKeysExtra) GetSlot() uint64 { + if x != nil { + return x.Slot + } + return 0 +} + +func (x *GnosisDecryptionKeysExtra) GetTxPointer() uint64 { + if x != nil { + return x.TxPointer + } + return 0 +} + +func (x *GnosisDecryptionKeysExtra) GetSignerIndices() []uint64 { + if x != nil { + return x.SignerIndices + } + return nil +} + +func (x *GnosisDecryptionKeysExtra) GetSignatures() [][]byte { + if x != nil { + return x.Signatures + } + return nil +} + +type OptimismDecryptionKeysExtra struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *OptimismDecryptionKeysExtra) Reset() { + *x = OptimismDecryptionKeysExtra{} + if protoimpl.UnsafeEnabled { + mi := &file_gossip_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OptimismDecryptionKeysExtra) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OptimismDecryptionKeysExtra) ProtoMessage() {} + +func (x *OptimismDecryptionKeysExtra) ProtoReflect() protoreflect.Message { + mi := &file_gossip_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OptimismDecryptionKeysExtra.ProtoReflect.Descriptor instead. +func (*OptimismDecryptionKeysExtra) Descriptor() ([]byte, []int) { + return file_gossip_proto_rawDescGZIP(), []int{7} +} + type DecryptionKeys struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -289,12 +541,17 @@ type DecryptionKeys struct { InstanceID uint64 `protobuf:"varint,1,opt,name=instanceID,proto3" json:"instanceID,omitempty"` Eon uint64 `protobuf:"varint,2,opt,name=eon,proto3" json:"eon,omitempty"` Keys []*Key `protobuf:"bytes,3,rep,name=keys,proto3" json:"keys,omitempty"` + // Types that are assignable to Extra: + // + // *DecryptionKeys_Gnosis + // *DecryptionKeys_Optimism + Extra isDecryptionKeys_Extra `protobuf_oneof:"extra"` } func (x *DecryptionKeys) Reset() { *x = DecryptionKeys{} if protoimpl.UnsafeEnabled { - mi := &file_gossip_proto_msgTypes[4] + mi := &file_gossip_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -307,7 +564,7 @@ func (x *DecryptionKeys) String() string { func (*DecryptionKeys) ProtoMessage() {} func (x *DecryptionKeys) ProtoReflect() protoreflect.Message { - mi := &file_gossip_proto_msgTypes[4] + mi := &file_gossip_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -320,7 +577,7 @@ func (x *DecryptionKeys) ProtoReflect() protoreflect.Message { // Deprecated: Use DecryptionKeys.ProtoReflect.Descriptor instead. func (*DecryptionKeys) Descriptor() ([]byte, []int) { - return file_gossip_proto_rawDescGZIP(), []int{4} + return file_gossip_proto_rawDescGZIP(), []int{8} } func (x *DecryptionKeys) GetInstanceID() uint64 { @@ -344,6 +601,43 @@ func (x *DecryptionKeys) GetKeys() []*Key { return nil } +func (m *DecryptionKeys) GetExtra() isDecryptionKeys_Extra { + if m != nil { + return m.Extra + } + return nil +} + +func (x *DecryptionKeys) GetGnosis() *GnosisDecryptionKeysExtra { + if x, ok := x.GetExtra().(*DecryptionKeys_Gnosis); ok { + return x.Gnosis + } + return nil +} + +func (x *DecryptionKeys) GetOptimism() *OptimismDecryptionKeysExtra { + if x, ok := x.GetExtra().(*DecryptionKeys_Optimism); ok { + return x.Optimism + } + return nil +} + +type isDecryptionKeys_Extra interface { + isDecryptionKeys_Extra() +} + +type DecryptionKeys_Gnosis struct { + Gnosis *GnosisDecryptionKeysExtra `protobuf:"bytes,4,opt,name=gnosis,proto3,oneof"` +} + +type DecryptionKeys_Optimism struct { + Optimism *OptimismDecryptionKeysExtra `protobuf:"bytes,5,opt,name=optimism,proto3,oneof"` +} + +func (*DecryptionKeys_Gnosis) isDecryptionKeys_Extra() {} + +func (*DecryptionKeys_Optimism) isDecryptionKeys_Extra() {} + // EonPublicKey is sent by the keypers to publish the EonPublicKey for a certain // eon. For those that observe it, e.g. the collator, it's a candidate until // the observer has seen at least threshold messages. @@ -363,7 +657,7 @@ type EonPublicKey struct { func (x *EonPublicKey) Reset() { *x = EonPublicKey{} if protoimpl.UnsafeEnabled { - mi := &file_gossip_proto_msgTypes[5] + mi := &file_gossip_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -376,7 +670,7 @@ func (x *EonPublicKey) String() string { func (*EonPublicKey) ProtoMessage() {} func (x *EonPublicKey) ProtoReflect() protoreflect.Message { - mi := &file_gossip_proto_msgTypes[5] + mi := &file_gossip_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -389,7 +683,7 @@ func (x *EonPublicKey) ProtoReflect() protoreflect.Message { // Deprecated: Use EonPublicKey.ProtoReflect.Descriptor instead. func (*EonPublicKey) Descriptor() ([]byte, []int) { - return file_gossip_proto_rawDescGZIP(), []int{5} + return file_gossip_proto_rawDescGZIP(), []int{9} } func (x *EonPublicKey) GetInstanceID() uint64 { @@ -448,7 +742,7 @@ type TraceContext struct { func (x *TraceContext) Reset() { *x = TraceContext{} if protoimpl.UnsafeEnabled { - mi := &file_gossip_proto_msgTypes[6] + mi := &file_gossip_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -461,7 +755,7 @@ func (x *TraceContext) String() string { func (*TraceContext) ProtoMessage() {} func (x *TraceContext) ProtoReflect() protoreflect.Message { - mi := &file_gossip_proto_msgTypes[6] + mi := &file_gossip_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -474,7 +768,7 @@ func (x *TraceContext) ProtoReflect() protoreflect.Message { // Deprecated: Use TraceContext.ProtoReflect.Descriptor instead. func (*TraceContext) Descriptor() ([]byte, []int) { - return file_gossip_proto_rawDescGZIP(), []int{6} + return file_gossip_proto_rawDescGZIP(), []int{10} } func (x *TraceContext) GetTraceID() []byte { @@ -518,7 +812,7 @@ type Envelope struct { func (x *Envelope) Reset() { *x = Envelope{} if protoimpl.UnsafeEnabled { - mi := &file_gossip_proto_msgTypes[7] + mi := &file_gossip_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -531,7 +825,7 @@ func (x *Envelope) String() string { func (*Envelope) ProtoMessage() {} func (x *Envelope) ProtoReflect() protoreflect.Message { - mi := &file_gossip_proto_msgTypes[7] + mi := &file_gossip_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -544,7 +838,7 @@ func (x *Envelope) ProtoReflect() protoreflect.Message { // Deprecated: Use Envelope.ProtoReflect.Descriptor instead. func (*Envelope) Descriptor() ([]byte, []int) { - return file_gossip_proto_rawDescGZIP(), []int{7} + return file_gossip_proto_rawDescGZIP(), []int{11} } func (x *Envelope) GetVersion() string { @@ -589,58 +883,97 @@ var file_gossip_proto_rawDesc = []byte{ 0x08, 0x4b, 0x65, 0x79, 0x53, 0x68, 0x61, 0x72, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x13, 0x44, 0x65, - 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x53, 0x68, 0x61, 0x72, 0x65, - 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, - 0x44, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, - 0x65, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x6b, 0x65, 0x79, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x64, - 0x65, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6b, 0x65, 0x79, 0x70, 0x65, 0x72, - 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x28, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x72, 0x65, 0x73, 0x18, - 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x32, 0x70, 0x6d, 0x73, 0x67, 0x2e, 0x4b, - 0x65, 0x79, 0x53, 0x68, 0x61, 0x72, 0x65, 0x52, 0x06, 0x73, 0x68, 0x61, 0x72, 0x65, 0x73, 0x22, - 0x33, 0x0a, 0x03, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x22, 0x63, 0x0a, 0x0e, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, - 0x63, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6f, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x03, 0x65, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, 0x32, 0x70, 0x6d, 0x73, 0x67, 0x2e, - 0x4b, 0x65, 0x79, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22, 0xd4, 0x01, 0x0a, 0x0c, 0x45, 0x6f, - 0x6e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, - 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x75, - 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x12, 0x2c, 0x0a, 0x11, 0x6b, 0x65, 0x79, 0x70, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x6b, - 0x65, 0x79, 0x70, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x6e, 0x64, 0x65, 0x78, - 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x65, - 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x22, 0x80, 0x01, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, - 0x74, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x70, 0x61, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x70, 0x61, - 0x6e, 0x49, 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x46, 0x6c, 0x61, 0x67, - 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x46, 0x6c, - 0x61, 0x67, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x22, 0x8f, 0x01, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, - 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x07, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, - 0x79, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2f, 0x0a, 0x05, 0x74, 0x72, - 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x32, 0x70, 0x6d, - 0x73, 0x67, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x48, - 0x00, 0x52, 0x05, 0x74, 0x72, 0x61, 0x63, 0x65, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, - 0x74, 0x72, 0x61, 0x63, 0x65, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x3b, 0x70, 0x32, 0x70, 0x6d, - 0x73, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x28, 0x0c, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x65, 0x22, 0x71, 0x0a, 0x1e, 0x47, 0x6e, 0x6f, + 0x73, 0x69, 0x73, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, + 0x53, 0x68, 0x61, 0x72, 0x65, 0x73, 0x45, 0x78, 0x74, 0x72, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x73, + 0x6c, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x74, 0x78, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x78, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x1c, + 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x22, 0x0a, 0x20, + 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x73, 0x6d, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x53, 0x68, 0x61, 0x72, 0x65, 0x73, 0x45, 0x78, 0x74, 0x72, 0x61, + 0x22, 0xa6, 0x02, 0x0a, 0x13, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4b, + 0x65, 0x79, 0x53, 0x68, 0x61, 0x72, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x69, 0x6e, + 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6f, 0x6e, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x65, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x6b, 0x65, + 0x79, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0b, 0x6b, 0x65, 0x79, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x28, 0x0a, 0x06, + 0x73, 0x68, 0x61, 0x72, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, + 0x32, 0x70, 0x6d, 0x73, 0x67, 0x2e, 0x4b, 0x65, 0x79, 0x53, 0x68, 0x61, 0x72, 0x65, 0x52, 0x06, + 0x73, 0x68, 0x61, 0x72, 0x65, 0x73, 0x12, 0x40, 0x0a, 0x06, 0x67, 0x6e, 0x6f, 0x73, 0x69, 0x73, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x32, 0x70, 0x6d, 0x73, 0x67, 0x2e, + 0x47, 0x6e, 0x6f, 0x73, 0x69, 0x73, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x4b, 0x65, 0x79, 0x53, 0x68, 0x61, 0x72, 0x65, 0x73, 0x45, 0x78, 0x74, 0x72, 0x61, 0x48, 0x00, + 0x52, 0x06, 0x67, 0x6e, 0x6f, 0x73, 0x69, 0x73, 0x12, 0x46, 0x0a, 0x08, 0x6f, 0x70, 0x74, 0x69, + 0x6d, 0x69, 0x73, 0x6d, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x32, 0x70, + 0x6d, 0x73, 0x67, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x73, 0x6d, 0x44, 0x65, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x53, 0x68, 0x61, 0x72, 0x65, 0x73, 0x45, + 0x78, 0x74, 0x72, 0x61, 0x48, 0x00, 0x52, 0x08, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x73, 0x6d, + 0x42, 0x07, 0x0a, 0x05, 0x65, 0x78, 0x74, 0x72, 0x61, 0x22, 0x33, 0x0a, 0x03, 0x4b, 0x65, 0x79, + 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x94, + 0x01, 0x0a, 0x19, 0x47, 0x6e, 0x6f, 0x73, 0x69, 0x73, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x73, 0x45, 0x78, 0x74, 0x72, 0x61, 0x12, 0x12, 0x0a, 0x04, + 0x73, 0x6c, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x6c, 0x6f, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x78, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x78, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x12, + 0x24, 0x0a, 0x0d, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0d, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x49, 0x6e, + 0x64, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x73, 0x22, 0x1d, 0x0a, 0x1b, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x73, + 0x6d, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x73, 0x45, + 0x78, 0x74, 0x72, 0x61, 0x22, 0xec, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x69, 0x6e, 0x73, + 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x65, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x04, 0x6b, 0x65, 0x79, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, 0x32, 0x70, 0x6d, 0x73, 0x67, + 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x3b, 0x0a, 0x06, 0x67, 0x6e, + 0x6f, 0x73, 0x69, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x32, 0x70, + 0x6d, 0x73, 0x67, 0x2e, 0x47, 0x6e, 0x6f, 0x73, 0x69, 0x73, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x73, 0x45, 0x78, 0x74, 0x72, 0x61, 0x48, 0x00, 0x52, + 0x06, 0x67, 0x6e, 0x6f, 0x73, 0x69, 0x73, 0x12, 0x41, 0x0a, 0x08, 0x6f, 0x70, 0x74, 0x69, 0x6d, + 0x69, 0x73, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x32, 0x70, 0x6d, + 0x73, 0x67, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x73, 0x6d, 0x44, 0x65, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x73, 0x45, 0x78, 0x74, 0x72, 0x61, 0x48, 0x00, + 0x52, 0x08, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x73, 0x6d, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x78, + 0x74, 0x72, 0x61, 0x22, 0xd4, 0x01, 0x0a, 0x0c, 0x45, 0x6f, 0x6e, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x49, 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2c, 0x0a, 0x11, + 0x6b, 0x65, 0x79, 0x70, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x6e, 0x64, 0x65, + 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x6b, 0x65, 0x79, 0x70, 0x65, 0x72, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6f, + 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x65, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x80, 0x01, 0x0a, 0x0c, 0x54, + 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x74, + 0x72, 0x61, 0x63, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x74, 0x72, + 0x61, 0x63, 0x65, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x70, 0x61, 0x6e, 0x49, 0x44, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x70, 0x61, 0x6e, 0x49, 0x44, 0x12, 0x1e, 0x0a, + 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x1e, 0x0a, + 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0x8f, 0x01, + 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x2f, 0x0a, 0x05, 0x74, 0x72, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x32, 0x70, 0x6d, 0x73, 0x67, 0x2e, 0x54, 0x72, 0x61, + 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x48, 0x00, 0x52, 0x05, 0x74, 0x72, 0x61, + 0x63, 0x65, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x42, + 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x3b, 0x70, 0x32, 0x70, 0x6d, 0x73, 0x67, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -655,28 +988,36 @@ func file_gossip_proto_rawDescGZIP() []byte { return file_gossip_proto_rawDescData } -var file_gossip_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_gossip_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_gossip_proto_goTypes = []interface{}{ - (*DecryptionTrigger)(nil), // 0: p2pmsg.DecryptionTrigger - (*KeyShare)(nil), // 1: p2pmsg.KeyShare - (*DecryptionKeyShares)(nil), // 2: p2pmsg.DecryptionKeyShares - (*Key)(nil), // 3: p2pmsg.Key - (*DecryptionKeys)(nil), // 4: p2pmsg.DecryptionKeys - (*EonPublicKey)(nil), // 5: p2pmsg.EonPublicKey - (*TraceContext)(nil), // 6: p2pmsg.TraceContext - (*Envelope)(nil), // 7: p2pmsg.Envelope - (*anypb.Any)(nil), // 8: google.protobuf.Any + (*DecryptionTrigger)(nil), // 0: p2pmsg.DecryptionTrigger + (*KeyShare)(nil), // 1: p2pmsg.KeyShare + (*GnosisDecryptionKeySharesExtra)(nil), // 2: p2pmsg.GnosisDecryptionKeySharesExtra + (*OptimismDecryptionKeySharesExtra)(nil), // 3: p2pmsg.OptimismDecryptionKeySharesExtra + (*DecryptionKeyShares)(nil), // 4: p2pmsg.DecryptionKeyShares + (*Key)(nil), // 5: p2pmsg.Key + (*GnosisDecryptionKeysExtra)(nil), // 6: p2pmsg.GnosisDecryptionKeysExtra + (*OptimismDecryptionKeysExtra)(nil), // 7: p2pmsg.OptimismDecryptionKeysExtra + (*DecryptionKeys)(nil), // 8: p2pmsg.DecryptionKeys + (*EonPublicKey)(nil), // 9: p2pmsg.EonPublicKey + (*TraceContext)(nil), // 10: p2pmsg.TraceContext + (*Envelope)(nil), // 11: p2pmsg.Envelope + (*anypb.Any)(nil), // 12: google.protobuf.Any } var file_gossip_proto_depIdxs = []int32{ - 1, // 0: p2pmsg.DecryptionKeyShares.shares:type_name -> p2pmsg.KeyShare - 3, // 1: p2pmsg.DecryptionKeys.keys:type_name -> p2pmsg.Key - 8, // 2: p2pmsg.Envelope.message:type_name -> google.protobuf.Any - 6, // 3: p2pmsg.Envelope.trace:type_name -> p2pmsg.TraceContext - 4, // [4:4] is the sub-list for method output_type - 4, // [4:4] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 1, // 0: p2pmsg.DecryptionKeyShares.shares:type_name -> p2pmsg.KeyShare + 2, // 1: p2pmsg.DecryptionKeyShares.gnosis:type_name -> p2pmsg.GnosisDecryptionKeySharesExtra + 3, // 2: p2pmsg.DecryptionKeyShares.optimism:type_name -> p2pmsg.OptimismDecryptionKeySharesExtra + 5, // 3: p2pmsg.DecryptionKeys.keys:type_name -> p2pmsg.Key + 6, // 4: p2pmsg.DecryptionKeys.gnosis:type_name -> p2pmsg.GnosisDecryptionKeysExtra + 7, // 5: p2pmsg.DecryptionKeys.optimism:type_name -> p2pmsg.OptimismDecryptionKeysExtra + 12, // 6: p2pmsg.Envelope.message:type_name -> google.protobuf.Any + 10, // 7: p2pmsg.Envelope.trace:type_name -> p2pmsg.TraceContext + 8, // [8:8] is the sub-list for method output_type + 8, // [8:8] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_gossip_proto_init() } @@ -710,7 +1051,7 @@ func file_gossip_proto_init() { } } file_gossip_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DecryptionKeyShares); i { + switch v := v.(*GnosisDecryptionKeySharesExtra); i { case 0: return &v.state case 1: @@ -722,7 +1063,7 @@ func file_gossip_proto_init() { } } file_gossip_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Key); i { + switch v := v.(*OptimismDecryptionKeySharesExtra); i { case 0: return &v.state case 1: @@ -734,7 +1075,7 @@ func file_gossip_proto_init() { } } file_gossip_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DecryptionKeys); i { + switch v := v.(*DecryptionKeyShares); i { case 0: return &v.state case 1: @@ -746,7 +1087,7 @@ func file_gossip_proto_init() { } } file_gossip_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EonPublicKey); i { + switch v := v.(*Key); i { case 0: return &v.state case 1: @@ -758,7 +1099,7 @@ func file_gossip_proto_init() { } } file_gossip_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TraceContext); i { + switch v := v.(*GnosisDecryptionKeysExtra); i { case 0: return &v.state case 1: @@ -770,6 +1111,54 @@ func file_gossip_proto_init() { } } file_gossip_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OptimismDecryptionKeysExtra); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gossip_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DecryptionKeys); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gossip_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EonPublicKey); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gossip_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TraceContext); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gossip_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Envelope); i { case 0: return &v.state @@ -782,14 +1171,22 @@ func file_gossip_proto_init() { } } } - file_gossip_proto_msgTypes[7].OneofWrappers = []interface{}{} + file_gossip_proto_msgTypes[4].OneofWrappers = []interface{}{ + (*DecryptionKeyShares_Gnosis)(nil), + (*DecryptionKeyShares_Optimism)(nil), + } + file_gossip_proto_msgTypes[8].OneofWrappers = []interface{}{ + (*DecryptionKeys_Gnosis)(nil), + (*DecryptionKeys_Optimism)(nil), + } + file_gossip_proto_msgTypes[11].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_gossip_proto_rawDesc, NumEnums: 0, - NumMessages: 8, + NumMessages: 12, NumExtensions: 0, NumServices: 0, }, diff --git a/rolling-shutter/p2pmsg/gossip.proto b/rolling-shutter/p2pmsg/gossip.proto index eb2323858..6d79ab9c1 100644 --- a/rolling-shutter/p2pmsg/gossip.proto +++ b/rolling-shutter/p2pmsg/gossip.proto @@ -19,11 +19,23 @@ message KeyShare { bytes share = 2; } +message GnosisDecryptionKeySharesExtra { + uint64 slot = 1; + uint64 tx_pointer = 2; + bytes signature = 3; +} + +message OptimismDecryptionKeySharesExtra {} + message DecryptionKeyShares { uint64 instanceID = 1; uint64 eon = 4; uint64 keyperIndex = 5; repeated KeyShare shares = 9; + oneof extra { + GnosisDecryptionKeySharesExtra gnosis = 10; + OptimismDecryptionKeySharesExtra optimism = 11; + } } message Key { @@ -31,10 +43,23 @@ message Key { bytes key = 2; } +message GnosisDecryptionKeysExtra { + uint64 slot = 1; + uint64 tx_pointer = 2; + repeated uint64 signerIndices = 3; + repeated bytes signatures = 4; +} + +message OptimismDecryptionKeysExtra {} + message DecryptionKeys { uint64 instanceID = 1; uint64 eon = 2; repeated Key keys = 3; + oneof extra { + GnosisDecryptionKeysExtra gnosis = 4; + OptimismDecryptionKeysExtra optimism = 5; + } } // EonPublicKey is sent by the keypers to publish the EonPublicKey for a certain diff --git a/rolling-shutter/shmsg/messages.go b/rolling-shutter/shmsg/messages.go index e78ba1a76..beaea1a96 100644 --- a/rolling-shutter/shmsg/messages.go +++ b/rolling-shutter/shmsg/messages.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/bls12381" "github.com/ethereum/go-ethereum/crypto/ecies" shcrypto "github.com/shutter-network/shutter/shlib/shcrypto" @@ -79,9 +80,10 @@ func NewAccusation(eon uint64, accused []common.Address) *Message { // NewPolyCommitment creates a new poly commitment message containing gamma values. func NewPolyCommitment(eon uint64, gammas *shcrypto.Gammas) *Message { + g2 := bls12381.NewG2() gammaBytes := [][]byte{} for _, gamma := range *gammas { - gammaBytes = append(gammaBytes, gamma.Marshal()) + gammaBytes = append(gammaBytes, g2.ToBytes(gamma)) } return &Message{ diff --git a/rolling-shutter/shmsg/messages_test.go b/rolling-shutter/shmsg/messages_test.go index 308dc8009..2d04ca03a 100644 --- a/rolling-shutter/shmsg/messages_test.go +++ b/rolling-shutter/shmsg/messages_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto/bls12381" "gotest.tools/v3/assert" shcrypto "github.com/shutter-network/shutter/shlib/shcrypto" @@ -24,9 +25,10 @@ func TestNewPolyCommitmentMsg(t *testing.T) { assert.Equal(t, eon, msg.Eon) assert.Equal(t, int(threshold)+1, len(msg.Gammas)) + g2 := bls12381.NewG2() for i := 0; i < int(threshold)+1; i++ { gammaBytes := msg.Gammas[i] - assert.DeepEqual(t, gammaBytes, (*gammas)[i].Marshal()) + assert.DeepEqual(t, gammaBytes, g2.ToBytes((*gammas)[i])) } }