diff --git a/cmd/tendermint/default_block_store.go b/cmd/tendermint/default_block_store.go new file mode 100644 index 000000000000..abbb836166ee --- /dev/null +++ b/cmd/tendermint/default_block_store.go @@ -0,0 +1,137 @@ +package main + +import ( + "encoding/binary" + "fmt" + + "github.com/ethereum/go-ethereum/consensus/tendermint/consensus" + "github.com/ethereum/go-ethereum/rlp" + "github.com/syndtr/goleveldb/leveldb" +) + +type DefaultBlockStore struct { + db *leveldb.DB +} + +func NewDefaultBlockStore(db *leveldb.DB) consensus.BlockStore { + return &DefaultBlockStore{ + db: db, + } +} + +func (bs *DefaultBlockStore) Height() uint64 { + data, err := bs.db.Get([]byte("height"), nil) + if err != nil { + return 0 + } + + if len(data) != 8 { + return 0 + } + + return binary.BigEndian.Uint64(data) +} + +func (bs *DefaultBlockStore) Base() uint64 { + return 0 +} + +func (bs *DefaultBlockStore) Size() uint64 { + height := bs.Height() + if height == 0 { + return 0 + } + return height + 1 - bs.Base() +} + +func (bs *DefaultBlockStore) LoadBlock(height uint64) *consensus.FullBlock { + hd := make([]byte, 8) + binary.BigEndian.PutUint64(hd, height) + bk := []byte("block") + bk = append(bk, hd...) + + blockData, err := bs.db.Get(bk, nil) + if err != nil { + return nil + } + + b := &consensus.FullBlock{} + if rlp.DecodeBytes(blockData, b) != nil { + panic(fmt.Errorf("error from block: %w", err)) + } + return b +} + +func (bs *DefaultBlockStore) LoadBlockCommit(height uint64) *consensus.Commit { + hd := make([]byte, 8) + binary.BigEndian.PutUint64(hd, height) + ck := []byte("commit") + ck = append(ck, hd...) + + commitData, err := bs.db.Get(ck, nil) + if err != nil { + return nil + } + + c := &consensus.Commit{} + if rlp.DecodeBytes(commitData, c) != nil { + panic(fmt.Errorf("error from block: %w", err)) + } + return c +} + +func (bs *DefaultBlockStore) SaveBlock(b *consensus.FullBlock, c *consensus.Commit) { + // sanity check? + if b.NumberU64() != bs.Height()+1 { + panic(fmt.Sprintf("BlockStore can only save contiguous blocks. Wanted %v, got %v", bs.Height()+1, b.NumberU64())) + } + + hd := make([]byte, 8) + binary.BigEndian.PutUint64(hd, b.NumberU64()) + bk := []byte("block") + bk = append(bk, hd...) + + blockData, err := rlp.EncodeToBytes(b) + if err != nil { + // error? + return + } + + if err := bs.db.Put(bk, blockData, nil); err != nil { + return + } + + commitData, err := rlp.EncodeToBytes(c) + if err != nil { + return + } + + ck := []byte("commit") + ck = append(ck, hd...) + if err := bs.db.Put(ck, commitData, nil); err != nil { + return + } + + data := make([]byte, 8) + binary.BigEndian.PutUint64(data, b.NumberU64()) + if err := bs.db.Put([]byte("height"), data, nil); err != nil { + return + } +} + +// LoadSeenCommit returns the last locally seen Commit before being +// cannonicalized. This is useful when we've seen a commit, but there +// has not yet been a new block at `height + 1` that includes this +// commit in its block.LastCommit. +func (bs *DefaultBlockStore) LoadSeenCommit() *consensus.Commit { + commitData, err := bs.db.Get([]byte("seen_commit"), nil) + if err != nil { + return nil + } + + c := &consensus.Commit{} + if rlp.DecodeBytes(commitData, c) != nil { + panic(fmt.Errorf("error from block: %w", err)) + } + return c +} diff --git a/cmd/tendermint/keygen.go b/cmd/tendermint/keygen.go new file mode 100644 index 000000000000..f8b5f0e852b9 --- /dev/null +++ b/cmd/tendermint/keygen.go @@ -0,0 +1,90 @@ +package main + +import ( + "context" + "crypto/ecdsa" + "errors" + "fmt" + "io/ioutil" + "os" + + "github.com/ethereum/go-ethereum/consensus/tendermint/consensus" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/spf13/cobra" +) + +var keyDescription *string +var nolock *bool + +const ( + ValidatorKeyArmoredBlock = "VALIDATOR PRIVATE KEY" +) + +var KeygenCmd = &cobra.Command{ + Use: "keygen [KEYFILE]", + Short: "Create validator key at the specified path", + Run: runKeygen, + Args: cobra.ExactArgs(1), +} + +func init() { + keyDescription = KeygenCmd.Flags().String("desc", "", "Human-readable key description (optional)") + nolock = KeygenCmd.Flags().Bool("nolock", false, "Do not lock memory (less safer)") +} + +func runKeygen(cmd *cobra.Command, args []string) { + if !*nolock { + lockMemory() + } else { + log.Info("Skipping lockMemory()") + } + setRestrictiveUmask() + + log.Info("Creating new key", "location", args[0]) + + gk := consensus.GeneratePrivValidatorLocal().(*consensus.PrivValidatorLocal) + pk, err := gk.GetPubKey(context.Background()) + if err != nil { + log.Error("Failed to get pub key", "err", err) + return + } + + log.Info("Key generated", "address", pk.Address()) + + err = writeValidatorKey(gk.PrivKey, *keyDescription, args[0], false) + if err != nil { + log.Error("Failed to write key", "err", err) + return + } +} + +// loadValidatorKey loads a serialized guardian key from disk. +func loadValidatorKey(filename string) (*ecdsa.PrivateKey, error) { + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("failed to open file: %w", err) + } + + gk, err := crypto.ToECDSA(b) + if err != nil { + return nil, fmt.Errorf("failed to deserialize raw key data: %w", err) + } + + return gk, nil +} + +// writeValidatorKey serializes a guardian key and writes it to disk. +func writeValidatorKey(key *ecdsa.PrivateKey, description string, filename string, unsafe bool) error { + if _, err := os.Stat(filename); !os.IsNotExist(err) { + return errors.New("refusing to override existing key") + } + + b := crypto.FromECDSA(key) + + err := ioutil.WriteFile(filename, b, 0600) + if err != nil { + return fmt.Errorf("failed to write file: %w", err) + } + return nil +} diff --git a/cmd/tendermint/main.go b/cmd/tendermint/main.go new file mode 100644 index 000000000000..a68d80580338 --- /dev/null +++ b/cmd/tendermint/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "fmt" + "os" + "syscall" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "golang.org/x/sys/unix" + + homedir "github.com/mitchellh/go-homedir" +) + +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "mpbft", + Short: "Minimal PBFT node server", +} + +func init() { + cobra.OnInitialize(initConfig) + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.guardiand.yaml)") + rootCmd.AddCommand(NodeCmd) + rootCmd.AddCommand(KeygenCmd) +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Search config in home directory with name ".guardiand" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".guardiand.yaml") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } +} + +// lockMemory locks current and future pages in memory to protect secret keys from being swapped out to disk. +// It's possible (and strongly recommended) to deploy Wormhole such that keys are only ever +// stored in memory and never touch the disk. This is a privileged operation and requires CAP_IPC_LOCK. +func lockMemory() { + err := unix.Mlockall(syscall.MCL_CURRENT | syscall.MCL_FUTURE) + if err != nil { + fmt.Printf("Failed to lock memory: %v (CAP_IPC_LOCK missing?)\n", err) + os.Exit(1) + } +} + +// setRestrictiveUmask masks the group and world bits. This ensures that key material +// and sockets we create aren't accidentally group- or world-readable. +func setRestrictiveUmask() { + syscall.Umask(0077) // cannot fail +} + +func main() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/tendermint/node.go b/cmd/tendermint/node.go new file mode 100644 index 000000000000..9f4098f02e05 --- /dev/null +++ b/cmd/tendermint/node.go @@ -0,0 +1,288 @@ +package main + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "os" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/tendermint/consensus" + "github.com/ethereum/go-ethereum/consensus/tendermint/p2p" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" + "github.com/spf13/cobra" + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/opt" + + p2pcrypto "github.com/libp2p/go-libp2p-core/crypto" +) + +var ( + p2pNetworkID *string + p2pPort *uint + p2pBootstrap *string + nodeKeyPath *string + valKeyPath *string + nodeName *string + verbosity *int + datadir *string + validatorSet *[]string + genesisTimeMs *uint64 + skipBlockSync *bool + powerStr *string + + timeoutCommitMs *uint64 + consensusSyncMs *uint64 + proposerRepetition *uint64 +) + +var NodeCmd = &cobra.Command{ + Use: "node", + Short: "Run the bridge server", + Run: runNode, +} + +func init() { + p2pNetworkID = NodeCmd.Flags().String("network", "/mpbft/dev", "P2P network identifier") + p2pPort = NodeCmd.Flags().Uint("port", 8999, "P2P UDP listener port") + p2pBootstrap = NodeCmd.Flags().String("bootstrap", "", "P2P bootstrap peers (comma-separated)") + + nodeName = NodeCmd.Flags().String("nodeName", "", "Node name to announce in gossip heartbeats") + nodeKeyPath = NodeCmd.Flags().String("nodeKey", "", "Path to node key (will be generated if it doesn't exist)") + + verbosity = NodeCmd.Flags().Int("verbosity", 3, "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail") + + valKeyPath = NodeCmd.Flags().String("valKey", "", "Path to validator key (empty if not a validator)") + + datadir = NodeCmd.Flags().String("datadir", "./datadir", "Path to database") + + validatorSet = NodeCmd.Flags().StringArray("validatorSet", []string{}, "List of validators") + genesisTimeMs = NodeCmd.Flags().Uint64("genesisTimeMs", 0, "Genesis block timestamp") + skipBlockSync = NodeCmd.Flags().Bool("skipBlockSync", false, "Skip block sync") + powerStr = NodeCmd.Flags().String("valPowers", "", "comma seperated voting powers") + + timeoutCommitMs = NodeCmd.Flags().Uint64("timeoutCommitMs", 5000, "Timeout commit in ms") + consensusSyncMs = NodeCmd.Flags().Uint64("consensusSyncMs", 500, "Consensus sync in ms") + proposerRepetition = NodeCmd.Flags().Uint64("proposerRepetition", 8, "proposer repetition") + +} + +func runNode(cmd *cobra.Command, args []string) { + glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) + glogger.Verbosity(log.Lvl(*verbosity)) + log.Root().SetHandler(glogger) + + // setup logger + var ostream log.Handler + output := io.Writer(os.Stderr) + + usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb" + if usecolor { + output = colorable.NewColorableStderr() + } + ostream = log.StreamHandler(output, log.TerminalFormat(usecolor)) + + glogger.SetHandler(ostream) + + // Node's main lifecycle context. + rootCtx, rootCtxCancel := context.WithCancel(context.Background()) + defer rootCtxCancel() + + // Outbound gossip message queue + sendC := make(chan consensus.Message, 1000) + + // Inbound observations + obsvC := make(chan consensus.MsgInfo, 1000) + + // Load p2p private key + var p2pPriv p2pcrypto.PrivKey + p2pPriv, err := getOrCreateNodeKey(*nodeKeyPath) + if err != nil { + log.Error("Failed to load node key", "err", err) + return + } + + if *nodeKeyPath == "" { + log.Error("Please specify --nodeKey") + return + } + + // Check genesis timestamp + if *genesisTimeMs == 0 { + log.Error("Please specify --genesisTimeMs") + return + } + + // Read private key if the node is a validator + var privVal consensus.PrivValidator + var pubVal consensus.PubKey + + if *valKeyPath != "" { + valKey, err := loadValidatorKey(*valKeyPath) + if err != nil { + log.Error("Failed to load validator key", "err", err) + return + } + privVal = consensus.NewPrivValidatorLocal(valKey) + pubVal, err = privVal.GetPubKey(rootCtx) + if err != nil { + log.Error("Failed to load valiator pub key", "err", err) + return + } + log.Info("Running validator", "addr", pubVal.Address()) + } + + // Update validators + vals := make([]common.Address, len(*validatorSet)) + found := false + for i, addrStr := range *validatorSet { + addr := common.HexToAddress(addrStr) + if pubVal != nil && addr == pubVal.Address() { + found = true + } + vals[i] = addr + } + + powers := make([]int64, len(vals)) + if *powerStr == "" { + log.Info("Set all validator power = 1") + for i := 0; i < len(powers); i++ { + powers[i] = 1 + } + } else { + ss := strings.Split(*powerStr, ",") + if len(ss) != len(powers) { + log.Error("Invalid power string", "str", *powerStr) + return + } + for i, s := range ss { + p, err := strconv.Atoi(s) + powers[i] = int64(p) + if err != nil { + log.Error("Invalid power string", "err", err) + return + } + } + } + + if pubVal != nil && !found { + log.Error("Current validator is not in validator set") + return + } else { + log.Info("Validators", "vals", vals, "powers", powers) + } + + gcs := consensus.MakeGenesisChainState("test", *genesisTimeMs, vals, powers, 128, int64(*proposerRepetition)) + + db, err := leveldb.OpenFile(*datadir, &opt.Options{ErrorIfExist: true}) + if err != nil { + log.Error("Failed to create db", "err", err) + return + } + + bs := NewDefaultBlockStore(db) + executor := consensus.NewDefaultBlockExecutor(db) + + p2pserver, err := p2p.NewP2PServer(rootCtx, bs, obsvC, sendC, p2pPriv, *p2pPort, *p2pNetworkID, *p2pBootstrap, *nodeName, rootCtxCancel) + + go func() { + p2pserver.Run(rootCtx) + }() + + // TODO: make sure we have sufficient peer node to sync + time.Sleep(time.Second) + + if len(vals) == 1 && pubVal != nil && vals[0] == pubVal.Address() { + log.Info("Running in self validator mode, skipping block sync") + } else if *skipBlockSync { + log.Info("Skipping block sync by config") + } else { + bs := p2p.NewBlockSync(p2pserver.Host, *gcs, bs, executor, obsvC) + bs.Start(rootCtx) + err := bs.WaitDone() + if err != nil { + log.Error("Failed in block sync", "err", err) + return + } + + *gcs = bs.LastChainState() + } + + p := params.NewDefaultConsesusConfig() + p.TimeoutCommit = time.Duration(*timeoutCommitMs) * time.Millisecond + p.ConsensusSyncRequestDuration = time.Duration(*consensusSyncMs) * time.Millisecond + + // Block sync is done, now entering consensus stage + consensusState := consensus.NewConsensusState( + rootCtx, + p, + *gcs, + executor, + bs, + obsvC, + sendC, + ) + + consensusState.SetPrivValidator(privVal) + + p2pserver.SetConsensusState(consensusState) + + consensusState.Start(rootCtx) + + // Running the node + log.Info("Running the node") + + <-rootCtx.Done() +} + +func getOrCreateNodeKey(path string) (p2pcrypto.PrivKey, error) { + b, err := ioutil.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + log.Info("No node key found, generating a new one...", "path", path) + + priv, _, err := p2pcrypto.GenerateKeyPair(p2pcrypto.Ed25519, -1) + if err != nil { + panic(err) + } + + s, err := p2pcrypto.MarshalPrivateKey(priv) + if err != nil { + panic(err) + } + + err = ioutil.WriteFile(path, s, 0600) + if err != nil { + return nil, fmt.Errorf("failed to write node key: %w", err) + } + + return priv, nil + } else { + return nil, fmt.Errorf("failed to read node key: %w", err) + } + } + + priv, err := p2pcrypto.UnmarshalPrivateKey(b) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal node key: %w", err) + } + + peerID, err := peer.IDFromPrivateKey(priv) + if err != nil { + panic(err) + } + + log.Info("Found existing node key", + "path", path, + "peerID", peerID) + + return priv, nil +} diff --git a/consensus/tendermint/adapter/store.go b/consensus/tendermint/adapter/store.go index 49859f1565d9..831673a7de94 100644 --- a/consensus/tendermint/adapter/store.go +++ b/consensus/tendermint/adapter/store.go @@ -5,9 +5,9 @@ import ( "errors" "fmt" - pbft "github.com/QuarkChain/go-minimal-pbft/consensus" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" + pbft "github.com/ethereum/go-ethereum/consensus/tendermint/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" diff --git a/consensus/tendermint/consensus/chain_state.go b/consensus/tendermint/consensus/chain_state.go new file mode 100644 index 000000000000..f411e70eb8f2 --- /dev/null +++ b/consensus/tendermint/consensus/chain_state.go @@ -0,0 +1,157 @@ +package consensus + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie" +) + +// State is a short description of the latest committed block of the Tendermint consensus. +// It keeps all information necessary to validate new blocks, +// including the last validator set and the consensus params. +// All fields are exposed so the struct can be easily serialized, +// but none of them should be mutated directly. +// Instead, use state.Copy() or updateState(...). +// NOTE: not goroutine-safe. +type ChainState struct { + // immutable + ChainID string + InitialHeight uint64 // should be 1, not 0, when starting from height 1 + + // LastBlockHeight=0 at genesis (ie. block(H=0) does not exist) + LastBlockHeight uint64 + LastBlockID common.Hash + LastBlockTime uint64 + + // LastValidators is used to validate block.LastCommit. + // Validators are persisted to the database separately every time they change, + // so we can query for historical validator sets. + // Note that if s.LastBlockHeight causes a valset change, + // we set s.LastHeightValidatorsChanged = s.LastBlockHeight + 1 + 1 + // Extra +1 due to nextValSet delay. + Validators *ValidatorSet + LastValidators *ValidatorSet + LastHeightValidatorsChanged int64 + + // Consensus parameters used for validating blocks. + // Changes returned by EndBlock and updated after Commit. + // ConsensusParams types.ConsensusParams + // LastHeightConsensusParamsChanged int64 + Epoch uint64 + + // Merkle root of the results from executing prev block + // LastResultsHash []byte + + // the latest AppHash we've received from calling abci.Commit() + AppHash []byte +} + +// IsEmpty returns true if the State is equal to the empty State. +func (state ChainState) IsEmpty() bool { + return state.Validators == nil // XXX can't compare to Empty +} + +func (state ChainState) Copy() ChainState { + return ChainState{ + ChainID: state.ChainID, + InitialHeight: state.InitialHeight, + + LastBlockHeight: state.LastBlockHeight, + LastBlockID: state.LastBlockID, + LastBlockTime: state.LastBlockTime, + + Validators: state.Validators.Copy(), + LastValidators: state.LastValidators.Copy(), + LastHeightValidatorsChanged: state.LastHeightValidatorsChanged, + + Epoch: state.Epoch, + + // ConsensusParams: state.ConsensusParams, + // LastHeightConsensusParamsChanged: state.LastHeightConsensusParamsChanged, + + AppHash: state.AppHash, + + // LastResultsHash: state.LastResultsHash, + } +} + +func MakeGenesisChainState(chainID string, genesisTimeMs uint64, validatorAddrs []common.Address, votingPowers []int64, epoch uint64, proposerReptition int64) *ChainState { + vs := NewValidatorSet(validatorAddrs, votingPowers, proposerReptition) + return &ChainState{ + ChainID: chainID, + InitialHeight: 1, + LastBlockHeight: 0, + LastBlockID: common.Hash{}, + LastBlockTime: genesisTimeMs, + Validators: vs, + LastValidators: &ValidatorSet{}, // not exist + LastHeightValidatorsChanged: 1, + Epoch: epoch, + } +} + +func MakeChainState( + chainID string, + parentHeight uint64, + parentHash common.Hash, + parentTimeMs uint64, + lastValSet *ValidatorSet, + valSet *ValidatorSet, + epoch uint64, +) *ChainState { + return &ChainState{ + ChainID: chainID, + InitialHeight: 1, + LastBlockHeight: parentHeight, + LastBlockID: parentHash, + LastBlockTime: parentTimeMs, + Validators: valSet, + LastValidators: lastValSet, + LastHeightValidatorsChanged: 1, + Epoch: epoch, + } +} + +//------------------------------------------------------------------------ +// Create a block from the latest state + +// MakeBlock builds a block from the current state with the given txs, commit, +// and evidence. Note it also takes a proposerAddress because the state does not +// track rounds, and hence does not know the correct proposer. TODO: fix this! +func (state ChainState) MakeBlock( + height uint64, + // txs []types.Tx, + commit *Commit, + // evidence []types.Evidence, + proposerAddress common.Address, +) *FullBlock { + + // Set time. + var timestamp uint64 + if height == state.InitialHeight { + timestamp = state.LastBlockTime // genesis time + } else { + timestamp = MedianTime(commit, state.LastValidators) + } + + // Build base block with block data. + block := &FullBlock{ + Block: types.NewBlock( + &Header{ + ParentHash: state.LastBlockID, + Number: big.NewInt(int64(height)), + Time: timestamp / 1000, + TimeMs: timestamp, + Coinbase: proposerAddress, + LastCommitHash: commit.Hash(), + Difficulty: big.NewInt(int64(height)), + Extra: []byte{}, + BaseFee: big.NewInt(0), // TODO: update base fee + }, + nil, nil, nil, trie.NewStackTrie(nil), + ), LastCommit: commit} + + return block +} diff --git a/consensus/tendermint/consensus/consensus_state.go b/consensus/tendermint/consensus/consensus_state.go new file mode 100644 index 000000000000..a21db5608d12 --- /dev/null +++ b/consensus/tendermint/consensus/consensus_state.go @@ -0,0 +1,2109 @@ +package consensus + +import ( + "context" + "errors" + "fmt" + "runtime/debug" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +// Message defines an interface that the consensus domain types implement. When +// a proto message is received on a consensus p2p Channel, it is wrapped and then +// converted to a Message via MsgFromProto. +type Message interface { + ValidateBasic() error +} + +// msgs from the reactor which may update the state +type MsgInfo struct { + Msg Message `json:"msg"` + PeerID string `json:"peer_key"` +} + +// SignedMsgType is a type of signed message in the consensus. +type SignedMsgType = types.SignedMsgType + +const ( + UnknownType SignedMsgType = types.UnknownType + // Votes + PrevoteType SignedMsgType = types.PrevoteType + PrecommitType SignedMsgType = types.PrecommitType + // Proposals + ProposalType SignedMsgType = types.ProposalType +) + +// IsVoteTypeValid returns true if t is a valid vote type. +func IsVoteTypeValid(t SignedMsgType) bool { + switch t { + case PrevoteType, PrecommitType: + return true + default: + return false + } +} + +type BlockStore interface { + Base() uint64 // first known contiguous block height + Height() uint64 // last known contiguous block height + Size() uint64 // return number of blocks in the store + + LoadBlock(height uint64) *FullBlock + LoadBlockCommit(height uint64) *Commit + LoadSeenCommit() *Commit + + SaveBlock(*FullBlock, *Commit) +} + +type BlockExecutor interface { + ValidateBlock(ChainState, *FullBlock) error // validate the block by tentatively executing it + ApplyBlock(context.Context, ChainState, *FullBlock) (ChainState, error) // apply the block + MakeBlock(chainState *ChainState, height uint64, commit *Commit, proposerAddress common.Address) *FullBlock +} + +// Consensus sentinel errors +var ( + ErrInvalidProposalSignature = errors.New("error invalid proposal signature") + ErrInvalidProposalPOLRound = errors.New("error invalid proposal POL round") + ErrAddingVote = errors.New("error adding vote") + ErrSignatureFoundInPastBlocks = errors.New("found signature from the same key") + + errPubKeyIsNotSet = errors.New("pubkey is not set. Look for \"Can't get private validator pubkey\" errors") +) + +var msgQueueSize = 1000 + +// State handles execution of the consensus algorithm. +// It processes votes and proposals, and upon reaching agreement, +// commits blocks to the chain and executes them against the application. +// The internal state machine receives input from peers, the internal validator, and from a timer. +type ConsensusState struct { + BaseService + + // config details + config *ConsensusConfig + privValidator PrivValidator // for signing votes + privValidatorType PrivValidatorType + + // store blocks and commits + blockStore BlockStore + + // create and execute blocks + blockExec BlockExecutor + + // add evidence to the pool + // when it's detected + // evpool evidencePool + + // internal state + mtx sync.RWMutex + RoundState + chainState ChainState // State until height-1. + // privValidator pubkey, memoized for the duration of one block + // to avoid extra requests to HSM + privValidatorPubKey PubKey + + // state changes may be triggered by: msgs from peers, + // msgs from ourself, or by timeouts + peerInMsgQueue chan MsgInfo + internalMsgQueue chan MsgInfo + timeoutTicker TimeoutTicker + peerOutMsgQueue chan Message + + // a Write-Ahead Log ensures we can recover from any kind of crash + // and helps us avoid signing conflicting votes + // wal WAL + replayMode bool // so we don't log signing errors during replay + doWALCatchup bool // determines if we even try to do the catchup + + // for tests where we want to limit the number of transitions the state makes + nSteps int + + // some functions can be overwritten for testing + decideProposal func(height uint64, round int32) + doPrevote func(ctx context.Context, height uint64, round int32) + setProposal func(ctx context.Context, proposal *Proposal) (bool, error) + createProposalBlockFunc func( + height uint64, + commit *Commit, + proposerAddr common.Address, + ) *FullBlock + + // closed when we finish shutting down + done chan struct{} + + // wait the channel event happening for shutting down the state gracefully + onStopCh chan *RoundState + + consensusSyncRequestAsyncChan chan *consensusSyncRequestAsync + committedBlockChan chan *FullBlock +} + +// NewState returns a new State. +func NewConsensusState( + ctx context.Context, + cfg *ConsensusConfig, + state ChainState, + blockExec BlockExecutor, + blockStore BlockStore, + peerInMsgQueue chan MsgInfo, + peerOutMsgQueue chan Message, + // txNotifier txNotifier, + // evpool evidencePool, + // options ...StateOption, +) *ConsensusState { + cs := &ConsensusState{ + config: cfg, + blockExec: blockExec, + blockStore: blockStore, + peerInMsgQueue: peerInMsgQueue, + peerOutMsgQueue: peerOutMsgQueue, + internalMsgQueue: make(chan MsgInfo, msgQueueSize), + timeoutTicker: NewTimeoutTicker(), + consensusSyncRequestAsyncChan: make(chan *consensusSyncRequestAsync, msgQueueSize), + committedBlockChan: make(chan *FullBlock, msgQueueSize), + done: make(chan struct{}), + doWALCatchup: true, + // wal: nilWAL{}, + // evpool: evpool, + onStopCh: make(chan *RoundState), + } + + // set function defaults (may be overwritten before calling Start) + cs.decideProposal = cs.defaultDecideProposal + cs.doPrevote = cs.defaultDoPrevote + cs.setProposal = cs.defaultSetProposal + cs.createProposalBlockFunc = cs.defaultCreateBlock + + // We have no votes, so reconstruct LastCommit from SeenCommit. + if state.LastBlockHeight > 0 { + cs.reconstructLastCommit(state) + } + + cs.updateToState(ctx, state) + + // NOTE: we do not call scheduleRound0 yet, we do that upon Start() + + cs.BaseService = *NewBaseService("State", cs) + + return cs +} + +func (cs *ConsensusState) defaultCreateBlock(height uint64, commit *Commit, proposerAddr common.Address) *FullBlock { + return cs.blockExec.MakeBlock(&cs.chainState, height, commit, proposerAddr) +} + +// String returns a string. +func (cs *ConsensusState) String() string { + // better not to access shared variables + return "ConsensusState" +} + +// GetState returns a copy of the chain state. +// func (cs *SimpleState) GetState() sm.State { +// cs.mtx.RLock() +// defer cs.mtx.RUnlock() +// return cs.state.Copy() +// } + +// GetLastHeight returns the last height committed. +// If there were no blocks, returns 0. +func (cs *ConsensusState) GetLastHeight() uint64 { + cs.mtx.RLock() + defer cs.mtx.RUnlock() + return cs.RoundState.Height - 1 +} + +// GetRoundState returns a shallow copy of the internal consensus state. +func (cs *ConsensusState) GetRoundState() *RoundState { + cs.mtx.RLock() + defer cs.mtx.RUnlock() + + // NOTE: this might be dodgy, as RoundState itself isn't thread + // safe as it contains a number of pointers and is explicitly + // not thread safe. + rs := cs.RoundState // copy + return &rs +} + +// GetRoundStateJSON returns a json of RoundState. +// func (cs *SimpleState) GetRoundStateJSON() ([]byte, error) { +// cs.mtx.RLock() +// defer cs.mtx.RUnlock() +// return tmjson.Marshal(cs.RoundState) +// } + +// GetValidators returns a copy of the current validators. +// func (cs *SimpleState) GetValidators() (int64, []*Validator) { +// cs.mtx.RLock() +// defer cs.mtx.RUnlock() +// return cs.state.LastBlockHeight, cs.state.Validators.Copy().Validators +// } + +// SetPrivValidator sets the private validator account for signing votes. It +// immediately requests pubkey and caches it. +func (cs *ConsensusState) SetPrivValidator(priv PrivValidator) { + cs.mtx.Lock() + defer cs.mtx.Unlock() + + // Set type + cs.privValidator = priv + + if priv != nil { + // TODO: validate privValidator + } + + if err := cs.updatePrivValidatorPubKey(); err != nil { + log.Error("failed to get private validator pubkey", "err", err) + } +} + +// SetTimeoutTicker sets the local timer. It may be useful to overwrite for +// testing. +func (cs *ConsensusState) SetTimeoutTicker(timeoutTicker TimeoutTicker) { + cs.mtx.Lock() + cs.timeoutTicker = timeoutTicker + cs.mtx.Unlock() +} + +// LoadCommit loads the commit for a given height. +func (cs *ConsensusState) LoadCommit(height uint64) *Commit { + cs.mtx.RLock() + defer cs.mtx.RUnlock() + + if height == cs.blockStore.Height() { + commit := cs.blockStore.LoadSeenCommit() + // NOTE: Retrieving the height of the most recent block and retrieving + // the most recent commit does not currently occur as an atomic + // operation. We check the height and commit here in case a more recent + // commit has arrived since retrieving the latest height. + if commit != nil && commit.Height == height { + return commit + } + } + + return cs.blockStore.LoadBlockCommit(height) +} + +// OnStart loads the latest state via the WAL, and starts the timeout and +// receive routines. +func (cs *ConsensusState) OnStart(ctx context.Context) error { + // // We may set the WAL in testing before calling Start, so only OpenWAL if its + // // still the nilWAL. + // if _, ok := cs.wal.(nilWAL); ok { + // if err := cs.loadWalFile(ctx); err != nil { + // return err + // } + // } + + // // We may have lost some votes if the process crashed reload from consensus + // // log to catchup. + // if cs.doWALCatchup { + // repairAttempted := false + + // LOOP: + // for { + // err := cs.catchupReplay(ctx, cs.Height) + // switch { + // case err == nil: + // break LOOP + + // case !IsDataCorruptionError(err): + // log.Error("error on catchup replay; proceeding to start state anyway", "err", err) + // break LOOP + + // case repairAttempted: + // return err + // } + + // log.Error("the WAL file is corrupted; attempting repair", "err", err) + + // // 1) prep work + // if err := cs.wal.Stop(); err != nil { + + // return err + // } + + // repairAttempted = true + + // // 2) backup original WAL file + // corruptedFile := fmt.Sprintf("%s.CORRUPTED", cs.config.WalFile()) + // if err := tmos.CopyFile(cs.config.WalFile(), corruptedFile); err != nil { + // return err + // } + + // log.Debug("backed up WAL file", "src", cs.config.WalFile(), "dst", corruptedFile) + + // // 3) try to repair (WAL file will be overwritten!) + // if err := repairWalFile(corruptedFile, cs.config.WalFile()); err != nil { + // log.Error("the WAL repair failed", "err", err) + // return err + // } + + // log.Info("successful WAL repair") + + // // reload WAL file + // if err := cs.loadWalFile(ctx); err != nil { + // return err + // } + // } + // } + + // we need the timeoutRoutine for replay so + // we don't block on the tick chan. + // NOTE: we will get a build up of garbage go routines + // firing on the tockChan until the receiveRoutine is started + // to deal with them (by that point, at most one will be valid) + if err := cs.timeoutTicker.Start(ctx); err != nil { + return err + } + + // Double Signing Risk Reduction + if err := cs.checkDoubleSigningRisk(cs.Height); err != nil { + return err + } + + // now start the receiveRoutine + go cs.receiveRoutine(ctx, 0) + + // schedule the first round! + // use GetRoundState so we don't race the receiveRoutine for access + cs.scheduleRound0(cs.GetRoundState()) + + return nil +} + +// timeoutRoutine: receive requests for timeouts on tickChan and fire timeouts on tockChan +// receiveRoutine: serializes processing of proposoals, block parts, votes; coordinates state transitions +// +// this is only used in tests. +func (cs *ConsensusState) startRoutines(ctx context.Context, maxSteps int) { + err := cs.timeoutTicker.Start(ctx) + if err != nil { + log.Error("failed to start timeout ticker", "err", err) + return + } + + go cs.receiveRoutine(ctx, maxSteps) +} + +// loadWalFile loads WAL data from file. It overwrites cs.wal. +// func (cs *SimpleState) loadWalFile(ctx context.Context) error { +// wal, err := cs.OpenWAL(ctx, cs.config.WalFile) +// if err != nil { +// log.Error("failed to load state WAL", "err", err) +// return err +// } + +// cs.wal = wal +// return nil +// } + +// OnStop implements service.Service. +func (cs *ConsensusState) OnStop() { + // If the node is committing a new block, wait until it is finished! + if cs.GetRoundState().Step == RoundStepCommit { + select { + case <-cs.onStopCh: + case <-time.After(cs.config.TimeoutCommit): + log.Error("OnStop: timeout waiting for commit to finish", "time", cs.config.TimeoutCommit) + } + } + + close(cs.onStopCh) + + if cs.timeoutTicker.IsRunning() { + if err := cs.timeoutTicker.Stop(); err != nil { + // if !errors.Is(err, service.ErrAlreadyStopped) { + log.Error("failed trying to stop timeoutTicket", "error", err) + // } + } + } + // WAL is stopped in receiveRoutine. +} + +// Wait waits for the the main routine to return. +// NOTE: be sure to Stop() the event switch and drain +// any event channels or this may deadlock +func (cs *ConsensusState) Wait() { + <-cs.done +} + +// OpenWAL opens a file to log all consensus messages and timeouts for +// deterministic accountability. +// func (cs *SimpleState) OpenWAL(ctx context.Context, walFile string) (WAL, error) { +// wal, err := NewWAL(log.With("wal", walFile), walFile) +// if err != nil { +// log.Error("failed to open WAL", "file", walFile, "err", err) +// return nil, err +// } + +// if err := wal.Start(ctx); err != nil { +// log.Error("failed to start WAL", "err", err) +// return nil, err +// } + +// return wal, nil +// } + +//------------------------------------------------------------ +// internal functions for managing the state + +func (cs *ConsensusState) updateHeight(height uint64) { + cs.Height = height +} + +func (cs *ConsensusState) updateRoundStep(round int32, step RoundStepType) { + cs.Round = round + cs.Step = step +} + +// enterNewRound(height, 0) at cs.StartTime. +func (cs *ConsensusState) scheduleRound0(rs *RoundState) { + // log.Info("scheduleRound0", "now", tmtime.Now(), "startTime", cs.StartTime) + sleepDuration := rs.StartTime.Sub(CanonicalNow()) + cs.scheduleTimeout(sleepDuration, rs.Height, 0, RoundStepNewHeight) +} + +// Attempt to schedule a timeout (by sending timeoutInfo on the tickChan) +func (cs *ConsensusState) scheduleTimeout(duration time.Duration, height uint64, round int32, step RoundStepType) { + cs.timeoutTicker.ScheduleTimeout(timeoutInfo{duration, height, round, step}) +} + +// send a msg into the receiveRoutine regarding our own proposal, block part, or vote +func (cs *ConsensusState) sendInternalMessage(ctx context.Context, mi MsgInfo) { + select { + case <-ctx.Done(): + case cs.internalMsgQueue <- mi: + default: + // NOTE: using the go-routine means our votes can + // be processed out of order. + // TODO: use CList here for strict determinism and + // attempt push to internalMsgQueue in receiveRoutine + log.Debug("internal msg queue is full; using a go-routine") + go func() { + select { + case <-ctx.Done(): + case cs.internalMsgQueue <- mi: + } + }() + } +} + +func (cs *ConsensusState) createSyncRequest() *ConsensusSyncRequest { + round := cs.Round + if cs.Proposal != nil && cs.Proposal.POLRound != -1 && !cs.isProposalComplete() { + // make sure we collect enough prevotes for POLRound + // so that we could vote in this round + round = cs.LockedRound + } + + hasProposal := uint8(0) + if cs.Proposal != nil { + hasProposal = 1 + } + + return &ConsensusSyncRequest{ + Height: cs.Height, + Round: uint32(round), + HasProposal: hasProposal, + PrevotesBitmap: cs.Votes.Prevotes(round).BitArray().Copy().Elems, + PrecommitsBitmap: cs.Votes.Precommits(round).BitArray().Copy().Elems, + } +} + +func (cs *ConsensusState) ProcessCommittedBlock(fb *FullBlock) { + cs.committedBlockChan <- fb +} + +func (cs *ConsensusState) processCommitedBlock(ctx context.Context, block *FullBlock) { + height := block.NumberU64() + if block.NumberU64() != cs.Height { + return + } + + // fast-path for commit + if err := cs.blockExec.ValidateBlock(cs.chainState, block); err != nil { + log.Info("processCommitBlock validate err", "err", err) + return + } + + if err := cs.chainState.Validators.VerifyCommit( + cs.chainState.ChainID, block.Hash(), block.NumberU64(), block.Header().Commit); err != nil { + log.Info("processCommitBlock verify error", "err", err) + return + } + + cs.updateRoundStep(cs.Round, RoundStepCommit) + + // calculate last commit vote set directly + // note that updateToState() will check existing cs.LastCommit if CommitRound is -1. + cs.CommitRound = -1 // no votes + cs.LastCommit = CommitToVoteSet(cs.chainState.ChainID, block.Commit(), cs.chainState.Validators) + cs.CommitTime = CanonicalNow() + + log.Info( + "Finalizing commit of block", + "height", block.NumberU64(), + "hash", block.Hash(), + // "root", block.AppHash, + // "num_txs", len(block.Txs), + ) + + log.Debug(fmt.Sprintf("%v", block), "height", block.NumberU64()) + + // Save to blockStore. + if cs.blockStore.Height() < block.NumberU64() { + // NOTE: the seenCommit is local justification to commit this block, + // but may differ from the LastCommit included in the next block + cs.blockStore.SaveBlock(block, block.Commit()) + } else { + // Happens during replay if we already saved the block but didn't commit + log.Debug("calling finalizeCommit on already stored block", "height", block.Number) + } + + // Create a copy of the state for staging and an event cache for txs. + stateCopy := cs.chainState.Copy() + + // Execute and commit the block, update and save the state, and update the mempool. + // NOTE The block.AppHash wont reflect these txs until the next block. + stateCopy, err := cs.blockExec.ApplyBlock(ctx, stateCopy, block) + if err != nil { + log.Error("failed to apply block", "height", height, "err", err) + return + } + + // NewHeightStep! + cs.updateToState(ctx, stateCopy) + + // Private validator might have changed it's key pair => refetch pubkey. + if err := cs.updatePrivValidatorPubKey(); err != nil { + log.Error("failed to get private validator pubkey", "height", height, "err", err) + } + + // if we can skip timeoutCommit and have all the votes now, + if cs.config.SkipTimeoutCommit && cs.LastCommit.HasAll() { + // go straight to new round (skip timeout commit) + // cs.scheduleTimeout(time.Duration(0), cs.Height, 0, RoundStepNewHeight) + cs.enterNewRound(ctx, cs.Height, 0) + } else { + // cs.StartTime is already set. + // Schedule Round0 to start soon. + cs.scheduleRound0(&cs.RoundState) + } + + // By here, + // * cs.Height has been increment to height+1 + // * cs.Step is now RoundStepNewHeight + // * cs.StartTime is set to when we will start round0. +} + +func (cs *ConsensusState) ProcessSyncRequest(csq *ConsensusSyncRequest) ([]interface{}, error) { + log.Debug("Processing sync req", "req", csq) + respChan := make(chan []interface{}) + + reqAsync := &consensusSyncRequestAsync{ + req: csq, + respChan: respChan, + } + + cs.consensusSyncRequestAsyncChan <- reqAsync + + // TODO: cancel + select { + case msgs := <-respChan: + log.Debug("Processed req", "msgs", msgs) + + return msgs, nil + } +} + +func appendDiffVotes(vs *VoteSet, bm []uint64, msgs []interface{}) []interface{} { + if len(bm) != 0 { + votesBA, err := types.NewBitArrayFromUint64(vs.Size(), bm) + if err != nil { + // somewrong with bitmap size + return msgs + } + for i := 0; i < votesBA.Size(); i++ { + if votesBA.GetIndex(i) { + continue + } + + if !vs.BitArray().GetIndex(i) { + continue + } + + msgs = append(msgs, &types.VoteMessage{Vote: vs.GetByIndex(int32(i))}) + } + } + return msgs +} + +func (cs *ConsensusState) processSyncRequest(csq *ConsensusSyncRequest) []interface{} { + var msgs []interface{} + if csq.Height < cs.chainState.InitialHeight { + // nothing to have + return msgs + } else if csq.Height < cs.Height { + // return commit + if csq.HasProposal == 0 { + // return block and commit directly + fb := cs.blockStore.LoadBlock(csq.Height) + fb = fb.WithCommit(cs.blockStore.LoadBlockCommit(csq.Height)) + return []interface{}{fb} + } else { + // we are ahead and the peer has proposal, only return precommits + commit := cs.blockStore.LoadBlockCommit(csq.Height) + for i := 0; i < commit.Size(); i++ { + if commit.BitArray().GetIndex(i) { + msgs = append(msgs, &types.VoteMessage{Vote: commit.GetVote(int32(i))}) + } + } + return msgs + } + } else if csq.Height > cs.Height || csq.Round > uint32(cs.Round) { + return []interface{}{} + } + + // now csq.height == cs.height and csq.round <= cs.round + if csq.HasProposal == 0 && cs.Proposal != nil { + msgs = append(msgs, &types.ProposalMessage{Proposal: cs.Proposal}) + } + msgs = appendDiffVotes(cs.Votes.Prevotes(int32(csq.Round)), csq.PrevotesBitmap, msgs) + msgs = appendDiffVotes(cs.Votes.Precommits(int32(csq.Round)), csq.PrecommitsBitmap, msgs) + + return msgs +} + +func (cs *ConsensusState) broadcastMessageToPeers(ctx context.Context, msg Message) { + select { + case <-ctx.Done(): + case cs.peerOutMsgQueue <- msg: + default: + log.Debug("broadcast msg queue is full; using a go-routine") + go func() { + select { + case <-ctx.Done(): + case cs.peerOutMsgQueue <- msg: + } + }() + } +} + +// Reconstruct LastCommit from SeenCommit, which we saved along with the block, +// (which happens even before saving the state) +func (cs *ConsensusState) reconstructLastCommit(state ChainState) { + commit := cs.blockStore.LoadSeenCommit() + if commit == nil || commit.Height != state.LastBlockHeight { + commit = cs.blockStore.LoadBlockCommit(state.LastBlockHeight) + } + + if commit == nil { + panic(fmt.Sprintf( + "failed to reconstruct last commit; commit for height %v not found", + state.LastBlockHeight, + )) + } + + // TODO: read from state + // lastPrecommits := &VoteSet{} + lastPrecommits := CommitToVoteSet(state.ChainID, commit, state.LastValidators) + // if !lastPrecommits.HasTwoThirdsMajority() { + // panic("failed to reconstruct last commit; does not have +2/3 maj") + // } + + cs.LastCommit = lastPrecommits +} + +// Updates State and increments height to match that of state. +// The round becomes 0 and cs.Step becomes RoundStepNewHeight. +func (cs *ConsensusState) updateToState(ctx context.Context, state ChainState) { + if cs.CommitRound > -1 && 0 < cs.Height && cs.Height != state.LastBlockHeight { + panic(fmt.Sprintf( + "updateToState() expected state height of %v but found %v", + cs.Height, state.LastBlockHeight, + )) + } + + if !cs.chainState.IsEmpty() { + if cs.chainState.LastBlockHeight > 0 && cs.chainState.LastBlockHeight+1 != cs.Height { + // This might happen when someone else is mutating cs.state. + // Someone forgot to pass in state.Copy() somewhere?! + panic(fmt.Sprintf( + "inconsistent cs.state.LastBlockHeight+1 %v vs cs.Height %v", + cs.chainState.LastBlockHeight+1, cs.Height, + )) + } + if cs.chainState.LastBlockHeight > 0 && cs.Height == cs.chainState.InitialHeight { + panic(fmt.Sprintf( + "inconsistent cs.state.LastBlockHeight %v, expected 0 for initial height %v", + cs.chainState.LastBlockHeight, cs.chainState.InitialHeight, + )) + } + + // If state isn't further out than cs.state, just ignore. + // This happens when SwitchToConsensus() is called in the reactor. + // We don't want to reset e.g. the Votes, but we still want to + // signal the new round step, because other services (eg. txNotifier) + // depend on having an up-to-date peer state! + if state.LastBlockHeight <= cs.chainState.LastBlockHeight { + log.Debug( + "ignoring updateToState()", + "new_height", state.LastBlockHeight+1, + "old_height", cs.chainState.LastBlockHeight+1, + ) + cs.newStep(ctx) + return + } + } + + // Reset fields based on state. + validators := state.Validators + + switch { + case state.LastBlockHeight == 0: // Very first commit should be empty. + cs.LastCommit = (*VoteSet)(nil) + case cs.CommitRound > -1 && cs.Votes != nil: // Otherwise, use cs.Votes + if !cs.Votes.Precommits(cs.CommitRound).HasTwoThirdsMajority() { + panic(fmt.Sprintf( + "wanted to form a commit, but precommits (H/R: %d/%d) didn't have 2/3+: %v", + state.LastBlockHeight, cs.CommitRound, cs.Votes.Precommits(cs.CommitRound), + )) + } + + cs.LastCommit = cs.Votes.Precommits(cs.CommitRound) + + case cs.LastCommit == nil: + // NOTE: when Tendermint starts, it has no votes. reconstructLastCommit + // must be called to reconstruct LastCommit from SeenCommit. + panic(fmt.Sprintf( + "last commit cannot be empty after initial block (H:%d)", + state.LastBlockHeight+1, + )) + } + + // Next desired block height + height := state.LastBlockHeight + 1 + if height == 1 { + height = state.InitialHeight + } + + // RoundState fields + cs.updateHeight(height) + cs.updateRoundStep(0, RoundStepNewHeight) + + if cs.CommitTime.IsZero() { + // "Now" makes it easier to sync up dev nodes. + // We add timeoutCommit to allow transactions + // to be gathered for the first block. + // And alternative solution that relies on clocks: + // cs.StartTime = state.LastBlockTime.Add(timeoutCommit) + cs.StartTime = cs.config.Commit(CanonicalNow()) + } else { + cs.StartTime = cs.config.Commit(cs.CommitTime) + } + + cs.Validators = validators + cs.Proposal = nil + cs.ProposalBlock = nil + cs.LockedRound = -1 + cs.LockedBlock = nil + cs.ValidRound = -1 + cs.ValidBlock = nil + cs.Votes = NewHeightVoteSet(state.ChainID, height, validators) + cs.CommitRound = -1 + cs.LastValidators = state.LastValidators + cs.TriggeredTimeoutPrecommit = false + + cs.chainState = state + + // Finally, broadcast RoundState + cs.newStep(ctx) +} + +func (cs *ConsensusState) newStep(ctx context.Context) { + // rs := cs.RoundStateEvent() + // if err := cs.wal.Write(rs); err != nil { + // log.Error("failed writing to WAL", "err", err) + // } + + cs.nSteps++ +} + +//----------------------------------------- +// the main go routines + +// receiveRoutine handles messages which may cause state transitions. +// it's argument (n) is the number of messages to process before exiting - use 0 to run forever +// It keeps the RoundState and is the only thing that updates it. +// Updates (state transitions) happen on timeouts, complete proposals, and 2/3 majorities. +// State must be locked before any internal state is updated. +func (cs *ConsensusState) receiveRoutine(ctx context.Context, maxSteps int) { + onExit := func(cs *ConsensusState) { + // NOTE: the internalMsgQueue may have signed messages from our + // priv_val that haven't hit the WAL, but its ok because + // priv_val tracks LastSig + + // close wal now that we're done writing to it + // if err := cs.wal.Stop(); err != nil { + // // if !errors.Is(err, service.ErrAlreadyStopped) { + // log.Error("failed trying to stop WAL", "error", err) + // // } + // } + + // cs.wal.Wait() + close(cs.done) + } + + defer func() { + if r := recover(); r != nil { + log.Error("CONSENSUS FAILURE!!!", "err", r, "stack", string(debug.Stack())) + // stop gracefully + // + // NOTE: We most probably shouldn't be running any further when there is + // some unexpected panic. Some unknown error happened, and so we don't + // know if that will result in the validator signing an invalid thing. It + // might be worthwhile to explore a mechanism for manual resuming via + // some console or secure RPC system, but for now, halting the chain upon + // unexpected consensus bugs sounds like the better option. + onExit(cs) + } + }() + + consensusSyncRequestTimer := time.NewTimer(cs.config.ConsensusSyncRequestDuration) + defer consensusSyncRequestTimer.Stop() + + for { + if maxSteps > 0 { + if cs.nSteps >= maxSteps { + log.Debug("reached max steps; exiting receive routine") + cs.nSteps = 0 + return + } + } + + rs := cs.RoundState + var mi MsgInfo + + select { + + case mi = <-cs.peerInMsgQueue: + // if err := cs.wal.Write(mi); err != nil { + // log.Error("failed writing to WAL", "err", err) + // } + + // handles proposals, block parts, votes + // may generate internal events (votes, complete proposals, 2/3 majorities) + cs.handleMsg(ctx, mi) + + case mi = <-cs.internalMsgQueue: + // err := cs.wal.WriteSync(mi) // NOTE: fsync + // if err != nil { + // panic(fmt.Sprintf( + // "failed to write %v msg to consensus WAL due to %v; check your file system and restart the node", + // mi, err, + // )) + // } + + // if _, ok := mi.Msg.(*VoteMessage); ok { + // we actually want to simulate failing during + // the previous WriteSync, but this isn't easy to do. + // Equivalent would be to fail here and manually remove + // some bytes from the end of the wal. + // fail.Fail() // XXX + // } + + // handles proposals, block parts, votes + cs.handleMsg(ctx, mi) + + case ti := <-cs.timeoutTicker.Chan(): // tockChan: + // if err := cs.wal.Write(ti); err != nil { + // log.Error("failed writing to WAL", "err", err) + // } + + // if the timeout is relevant to the rs + // go to the next step + cs.handleTimeout(ctx, ti, rs) + + case <-consensusSyncRequestTimer.C: + msg := cs.createSyncRequest() + cs.broadcastMessageToPeers(ctx, msg) + + consensusSyncRequestTimer.Reset(cs.config.ConsensusSyncRequestDuration) + case syncReqAsync := <-cs.consensusSyncRequestAsyncChan: + // respChan is supposed to be non-blocking + syncReqAsync.respChan <- cs.processSyncRequest(syncReqAsync.req) + case commitedBlock := <-cs.committedBlockChan: + cs.processCommitedBlock(ctx, commitedBlock) + case <-ctx.Done(): + onExit(cs) + return + + } + // TODO should we handle context cancels here? + } +} + +// state transitions on complete-proposal, 2/3-any, 2/3-one +func (cs *ConsensusState) handleMsg(ctx context.Context, mi MsgInfo) { + cs.mtx.Lock() + defer cs.mtx.Unlock() + + var ( + added bool + err error + ) + + msg, peerID := mi.Msg, mi.PeerID + + switch msg := msg.(type) { + case *ProposalMessage: + // will not cause transition. + // once proposal is set, we can receive block parts + added, err = cs.setProposal(ctx, msg.Proposal) + + if added { + // broadcast the proposal to peer + cs.broadcastMessageToPeers(ctx, msg) + } + + case *VoteMessage: + // attempt to add the vote and dupeout the validator if its a duplicate signature + // if the vote gives us a 2/3-any or 2/3-one, we transition + added, err = cs.tryAddVote(ctx, msg.Vote, string(peerID)) + if added { + // broadcast the vote to peer + cs.broadcastMessageToPeers(ctx, msg) + } + + // if err == ErrAddingVote { + // TODO: punish peer + // We probably don't want to stop the peer here. The vote does not + // necessarily comes from a malicious peer but can be just broadcasted by + // a typical peer. + // https://github.com/tendermint/tendermint/issues/1281 + // } + + // NOTE: the vote is broadcast to peers by the reactor listening + // for vote events + + // TODO: If rs.Height == vote.Height && rs.Round < vote.Round, + // the peer is sending us CatchupCommit precommits. + // We could make note of this and help filter in broadcastHasVoteMessage(). + + default: + log.Error("unknown msg type", "type", fmt.Sprintf("%T", msg)) + return + } + + if err != nil { + log.Error( + "failed to process message", + "height", cs.Height, + "round", cs.Round, + "peer", peerID, + "msg_type", fmt.Sprintf("%T", msg), + "err", err, + ) + } +} + +func (cs *ConsensusState) handleTimeout( + ctx context.Context, + ti timeoutInfo, + rs RoundState, +) { + log.Debug("received tock", "timeout", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step) + + // timeouts must be for current height, round, step + if ti.Height != rs.Height || ti.Round < rs.Round || (ti.Round == rs.Round && RoundStepType(ti.Step) < rs.Step) { + log.Debug("ignoring tock because we are ahead", "height", rs.Height, "round", rs.Round, "step", rs.Step) + return + } + + // the timeout will now cause a state transition + cs.mtx.Lock() + defer cs.mtx.Unlock() + + switch RoundStepType(ti.Step) { + case RoundStepNewHeight: + // NewRound event fired from enterNewRound. + // XXX: should we fire timeout here (for timeout commit)? + cs.enterNewRound(ctx, ti.Height, 0) + + case RoundStepNewRound: + cs.enterPropose(ctx, ti.Height, 0) + + case RoundStepPropose: + cs.enterPrevote(ctx, ti.Height, ti.Round) + + case RoundStepPrevoteWait: + cs.enterPrecommit(ctx, ti.Height, ti.Round) + + case RoundStepPrecommitWait: + cs.enterPrecommit(ctx, ti.Height, ti.Round) + cs.enterNewRound(ctx, ti.Height, ti.Round+1) + + default: + panic(fmt.Sprintf("invalid timeout step: %v", ti.Step)) + } + +} + +//----------------------------------------------------------------------------- +// State functions +// Used internally by handleTimeout and handleMsg to make state transitions + +// Enter: `timeoutNewHeight` by startTime (commitTime+timeoutCommit), +// or, if SkipTimeoutCommit==true, after receiving all precommits from (height,round-1) +// Enter: `timeoutPrecommits` after any +2/3 precommits from (height,round-1) +// Enter: +2/3 precommits for nil at (height,round-1) +// Enter: +2/3 prevotes any or +2/3 precommits for block or any from (height, round) +// NOTE: cs.StartTime was already set for height. +func (cs *ConsensusState) enterNewRound(ctx context.Context, height uint64, round int32) { + if cs.Height != height || round < cs.Round || (cs.Round == round && cs.Step != RoundStepNewHeight) { + log.Debug( + "entering new round with invalid args", + "height", height, "round", round, + "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step), + ) + return + } + + if now := CanonicalNow(); cs.StartTime.After(now) { + log.Debug("need to set a buffer and log message here for sanity", "start_time", "height", height, "round", round, cs.StartTime, "now", now) + } + + log.Debug("entering new round", "height", height, "round", round, "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step)) + + // increment validators if necessary + validators := cs.Validators + if cs.Round < round { + validators = validators.Copy() + for i := uint64(0); i < uint64(validators.ProposerReptition); i++ { + validators.IncrementProposerPriority(SafeSubInt32(round, cs.Round)) + } + } + + // Setup new round + // we don't fire newStep for this step, + // but we fire an event, so update the round step first + cs.updateRoundStep(round, RoundStepNewRound) + cs.Validators = validators + if round == 0 { + // We've already reset these upon new height, + // and meanwhile we might have received a proposal + // for round 0. + } else { + log.Debug("resetting proposal info", "height", height, "round", round) + cs.Proposal = nil + cs.ProposalBlock = nil + } + + cs.Votes.SetRound(SafeAddInt32(round, 1)) // also track next round (round+1) to allow round-skipping + cs.TriggeredTimeoutPrecommit = false + + cs.enterPropose(ctx, height, round) +} + +// Enter (CreateEmptyBlocks): from enterNewRound(height,round) +// Enter (CreateEmptyBlocks, CreateEmptyBlocksInterval > 0 ): +// after enterNewRound(height,round), after timeout of CreateEmptyBlocksInterval +// Enter (!CreateEmptyBlocks) : after enterNewRound(height,round), once txs are in the mempool +func (cs *ConsensusState) enterPropose(ctx context.Context, height uint64, round int32) { + if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPropose <= cs.Step) { + log.Debug( + "entering propose step with invalid args", + "height", height, "round", round, + "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step), + ) + return + } + + log.Debug("entering propose step", "height", height, "round", round, "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step)) + + defer func() { + // Done enterPropose: + cs.updateRoundStep(round, RoundStepPropose) + cs.newStep(ctx) + + // If we have the whole proposal + POL, then goto Prevote now. + // else, we'll enterPrevote when the rest of the proposal is received (in AddProposalBlockPart), + // or else after timeoutPropose + if cs.isProposalComplete() { + cs.enterPrevote(ctx, height, cs.Round) + } + }() + + // If we don't get the proposal and all block parts quick enough, enterPrevote + cs.scheduleTimeout(cs.config.Propose(round), height, round, RoundStepPropose) + + // Nothing more to do if we're not a validator + if cs.privValidator == nil { + log.Debug("node is not a validator", "height", height, "round", round) + return + } + + log.Debug("node is a validator", "height", height, "round", round) + + if cs.privValidatorPubKey == nil { + // If this node is a validator & proposer in the current round, it will + // miss the opportunity to create a block. + log.Error("propose step; empty priv validator public key", "err", errPubKeyIsNotSet, "height", height, "round", round) + return + } + + address := cs.privValidatorPubKey.Address() + + // if not a validator, we're done + if !cs.Validators.HasAddress(address) { + log.Debug("node is not a validator", "height", height, "round", round, "addr", address, "vals", cs.Validators) + return + } + + if cs.isProposer(address) { + log.Debug( + "propose step; our turn to propose", + "height", height, "round", round, + "proposer", address, + ) + + cs.decideProposal(height, round) + } else { + log.Debug( + "propose step; not our turn to propose", + "height", height, "round", round, + "proposer", cs.Validators.GetProposer().Address, + ) + } +} + +func (cs *ConsensusState) isProposer(address common.Address) bool { + return cs.Validators.GetProposer().Address == address +} + +func (cs *ConsensusState) defaultDecideProposal(height uint64, round int32) { + var block *FullBlock + + // Decide on block + if cs.ValidBlock != nil { + // If there is valid block, choose that. + block = cs.ValidBlock + } else { + // Create a new proposal block from state/txs from the mempool. + block = cs.createProposalBlock() + if block == nil { + return + } + } + + // Flush the WAL. Otherwise, we may not recompute the same proposal to sign, + // and the privValidator will refuse to sign anything. + // if err := cs.wal.FlushAndSync(); err != nil { + // log.Error("failed flushing WAL to disk") + // } + + // Make proposal + proposal := NewProposal(height, round, cs.ValidRound, block) + + // wait the max amount we would wait for a proposal + ctx, cancel := context.WithTimeout(context.TODO(), cs.config.TimeoutPropose) + defer cancel() + if err := cs.privValidator.SignProposal(ctx, cs.chainState.ChainID, proposal); err == nil { + // send proposal and block parts on internal msg queue + cs.sendInternalMessage(ctx, MsgInfo{&ProposalMessage{proposal}, ""}) + + log.Debug("signed proposal", "height", height, "round", round, "proposal", proposal) + } else if !cs.replayMode { + log.Error("propose step; failed signing proposal", "height", height, "round", round, "err", err) + } +} + +// Returns true if the proposal block is complete && +// (if POLRound was proposed, we have +2/3 prevotes from there). +func (cs *ConsensusState) isProposalComplete() bool { + if cs.Proposal == nil || cs.ProposalBlock == nil { + return false + } + // we have the proposal. if there's a POLRound, + // make sure we have the prevotes from it too + if cs.Proposal.POLRound < 0 { + return true + } + // if this is false the proposer is lying or we haven't received the POL yet + return cs.Votes.Prevotes(cs.Proposal.POLRound).HasTwoThirdsMajority() +} + +// Create the next block to propose and return it. Returns nil block upon error. +// +// We really only need to return the parts, but the block is returned for +// convenience so we can log the proposal block. +// +// NOTE: keep it side-effect free for clarity. +// CONTRACT: cs.privValidator is not nil. +func (cs *ConsensusState) createProposalBlock() (block *FullBlock) { + if cs.privValidator == nil { + panic("entered createProposalBlock with privValidator being nil") + } + + var commit *Commit + switch { + case cs.Height == cs.chainState.InitialHeight: + // We're creating a proposal for the first block. + // The commit is empty, but not nil. + commit = NewCommit(0, 0, common.Hash{}, nil) + + case cs.LastCommit.HasTwoThirdsMajority(): + // Make the commit from LastCommit + commit = cs.LastCommit.MakeCommit() + + default: // This shouldn't happen. + log.Error("propose step; cannot propose anything without commit for the previous block") + return + } + + if cs.privValidatorPubKey == nil { + // If this node is a validator & proposer in the current round, it will + // miss the opportunity to create a block. + log.Error("propose step; empty priv validator public key", "err", errPubKeyIsNotSet) + return + } + + proposerAddr := cs.privValidatorPubKey.Address() + + return cs.createProposalBlockFunc(cs.Height, commit, proposerAddr) +} + +// Enter: `timeoutPropose` after entering Propose. +// Enter: proposal block and POL is ready. +// Prevote for LockedBlock if we're locked, or ProposalBlock if valid. +// Otherwise vote nil. +func (cs *ConsensusState) enterPrevote(ctx context.Context, height uint64, round int32) { + if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrevote <= cs.Step) { + log.Debug( + "entering prevote step with invalid args", + "height", height, "round", round, + "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step), + ) + return + } + + defer func() { + // Done enterPrevote: + cs.updateRoundStep(round, RoundStepPrevote) + cs.newStep(ctx) + }() + + log.Debug("entering prevote step", "height", height, "round", round, "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step)) + + // Sign and broadcast vote as necessary + cs.doPrevote(ctx, height, round) + + // Once `addVote` hits any +2/3 prevotes, we will go to PrevoteWait + // (so we have more time to try and collect +2/3 prevotes for a single block) +} + +func (cs *ConsensusState) defaultDoPrevote(ctx context.Context, height uint64, round int32) { + // If a block is locked, prevote that. + if cs.LockedBlock != nil { + log.Debug("prevote step; already locked on a block; prevoting locked block", "height", height, "round", round) + cs.signAddVote(ctx, PrevoteType, cs.LockedBlock.Hash()) + return + } + + // If ProposalBlock is nil, prevote nil. + if cs.ProposalBlock == nil { + log.Debug("prevote step: ProposalBlock is nil", "height", height, "round", round) + cs.signAddVote(ctx, PrevoteType, common.Hash{}) + return + } + + // Validate proposal block + err := cs.blockExec.ValidateBlock(cs.chainState, cs.ProposalBlock) + if err != nil { + // ProposalBlock is invalid, prevote nil. + log.Error("prevote step: ProposalBlock is invalid", "height", height, "round", round, "err", err) + cs.signAddVote(ctx, PrevoteType, common.Hash{}) + return + } + + // Prevote cs.ProposalBlock + // NOTE: the proposal signature is validated when it is received, + // and the proposal block parts are validated as they are received (against the merkle hash in the proposal) + log.Debug("prevote step: ProposalBlock is valid", "height", height, "round", round) + cs.signAddVote(ctx, PrevoteType, cs.ProposalBlock.Hash()) +} + +// Enter: any +2/3 prevotes at next round. +func (cs *ConsensusState) enterPrevoteWait(ctx context.Context, height uint64, round int32) { + if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrevoteWait <= cs.Step) { + log.Debug( + "entering prevote wait step with invalid args", + "height", height, "round", round, + "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step), + ) + return + } + + if !cs.Votes.Prevotes(round).HasTwoThirdsAny() { + panic(fmt.Sprintf( + "entering prevote wait step (%v/%v), but prevotes does not have any +2/3 votes", + height, round, + )) + } + + log.Debug("entering prevote wait step", "height", height, "round", round, "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step)) + + defer func() { + // Done enterPrevoteWait: + cs.updateRoundStep(round, RoundStepPrevoteWait) + cs.newStep(ctx) + }() + + // Wait for some more prevotes; enterPrecommit + cs.scheduleTimeout(cs.config.Prevote(round), height, round, RoundStepPrevoteWait) +} + +// Enter: `timeoutPrevote` after any +2/3 prevotes. +// Enter: `timeoutPrecommit` after any +2/3 precommits. +// Enter: +2/3 precomits for block or nil. +// Lock & precommit the ProposalBlock if we have enough prevotes for it (a POL in this round) +// else, unlock an existing lock and precommit nil if +2/3 of prevotes were nil, +// else, precommit nil otherwise. +func (cs *ConsensusState) enterPrecommit(ctx context.Context, height uint64, round int32) { + if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrecommit <= cs.Step) { + log.Debug( + "entering precommit step with invalid args", + "height", height, "round", round, + "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step), + ) + return + } + + log.Debug("entering precommit step", "height", height, "round", round, "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step)) + + defer func() { + // Done enterPrecommit: + cs.updateRoundStep(round, RoundStepPrecommit) + cs.newStep(ctx) + }() + + // check for a polka + blockID, ok := cs.Votes.Prevotes(round).TwoThirdsMajority() + + // If we don't have a polka, we must precommit nil. + if !ok { + if cs.LockedBlock != nil { + log.Debug("precommit step; no +2/3 prevotes during enterPrecommit while we are locked; precommitting nil", "height", height, "round", round) + } else { + log.Debug("precommit step; no +2/3 prevotes during enterPrecommit; precommitting nil", "height", height, "round", round) + } + + cs.signAddVote(ctx, PrecommitType, common.Hash{}) + return + } + + // the latest POLRound should be this round. + polRound, _ := cs.Votes.POLInfo() + if polRound < round { + panic(fmt.Sprintf("this POLRound should be %v but got %v", round, polRound)) + } + + // +2/3 prevoted nil. Unlock and precommit nil. + if (blockID == common.Hash{}) { + if cs.LockedBlock == nil { + log.Debug("precommit step; +2/3 prevoted for nil", "height", height, "round", round) + } else { + log.Debug("precommit step; +2/3 prevoted for nil; unlocking", "height", height, "round", round) + cs.LockedRound = -1 + cs.LockedBlock = nil + } + + cs.signAddVote(ctx, PrecommitType, common.Hash{}) + return + } + + // At this point, +2/3 prevoted for a particular block. + + // If we're already locked on that block, precommit it, and update the LockedRound + if cs.LockedBlock.HashTo(blockID) { + log.Debug("precommit step; +2/3 prevoted locked block; relocking", "height", height, "round", round) + cs.LockedRound = round + + cs.signAddVote(ctx, PrecommitType, blockID) + return + } + + // If +2/3 prevoted for proposal block, stage and precommit it + if cs.ProposalBlock.HashTo(blockID) { + log.Debug("precommit step; +2/3 prevoted proposal block; locking", "height", height, "round", round, "hash", blockID) + + // Validate the block. + if err := cs.blockExec.ValidateBlock(cs.chainState, cs.ProposalBlock); err != nil { + panic(fmt.Sprintf("precommit step; +2/3 prevoted for an invalid block: %v", err)) + } + + cs.LockedRound = round + cs.LockedBlock = cs.ProposalBlock + + cs.signAddVote(ctx, PrecommitType, blockID) + return + } + + // There was a polka in this round for a block we don't have. + // Fetch that block, unlock, and precommit nil. + // The +2/3 prevotes for this round is the POL for our unlock. + log.Debug("precommit step; +2/3 prevotes for a block we do not have; voting nil", "height", height, "round", round, "block_id", blockID) + + cs.LockedRound = -1 + cs.LockedBlock = nil + + cs.signAddVote(ctx, PrecommitType, common.Hash{}) +} + +// Enter: any +2/3 precommits for next round. +func (cs *ConsensusState) enterPrecommitWait(ctx context.Context, height uint64, round int32) { + if cs.Height != height || round < cs.Round || (cs.Round == round && cs.TriggeredTimeoutPrecommit) { + log.Debug( + "entering precommit wait step with invalid args", + "height", height, "round", round, + "triggered_timeout", cs.TriggeredTimeoutPrecommit, + "current", fmt.Sprintf("%v/%v", cs.Height, cs.Round), + ) + return + } + + if !cs.Votes.Precommits(round).HasTwoThirdsAny() { + panic(fmt.Sprintf( + "entering precommit wait step (%v/%v), but precommits does not have any +2/3 votes", + height, round, + )) + } + + log.Debug("entering precommit wait step", "height", height, "round", round, "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step)) + + defer func() { + // Done enterPrecommitWait: + cs.TriggeredTimeoutPrecommit = true + cs.newStep(ctx) + }() + + // wait for some more precommits; enterNewRound + cs.scheduleTimeout(cs.config.Precommit(round), height, round, RoundStepPrecommitWait) +} + +// Enter: +2/3 precommits for block +func (cs *ConsensusState) enterCommit(ctx context.Context, height uint64, commitRound int32) { + if cs.Height != height || RoundStepCommit <= cs.Step { + log.Debug( + "entering commit step with invalid args", + "height", height, "commit_round", commitRound, + "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step), + ) + return + } + + log.Debug("entering commit step", "height", height, "commit_round", commitRound, "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step)) + + defer func() { + // Done enterCommit: + // keep cs.Round the same, commitRound points to the right Precommits set. + cs.updateRoundStep(cs.Round, RoundStepCommit) + cs.CommitRound = commitRound + cs.CommitTime = CanonicalNow() + cs.newStep(ctx) + + // Maybe finalize immediately. + cs.tryFinalizeCommit(ctx, height) + }() + + blockID, ok := cs.Votes.Precommits(commitRound).TwoThirdsMajority() + if !ok { + panic("RunActionCommit() expects +2/3 precommits") + } + + // The Locked* fields no longer matter. + // Move them over to ProposalBlock if they match the commit hash, + // otherwise they'll be cleared in updateToState. + if cs.LockedBlock.HashTo(blockID) { + log.Debug("commit is for a locked block; set ProposalBlock=LockedBlock", "height", height, "commit_round", commitRound, "block_hash", blockID) + cs.ProposalBlock = cs.LockedBlock + } + + // If we don't have the block being committed, set up to get it. + if !cs.ProposalBlock.HashTo(blockID) { + log.Info( + "commit is for a block we do not know about; set ProposalBlock=nil", + "height", height, "commit_round", commitRound, + "proposal", cs.ProposalBlock.NilableHash(), + "commit", blockID[:], + ) + + // We're getting the wrong block. + // Set up ProposalBlockParts and keep waiting. + cs.ProposalBlock = nil + } +} + +// If we have the block AND +2/3 commits for it, finalize. +func (cs *ConsensusState) tryFinalizeCommit(ctx context.Context, height uint64) { + if cs.Height != height { + panic(fmt.Sprintf("tryFinalizeCommit() cs.Height: %v vs height: %v", cs.Height, height)) + } + + blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority() + if (!ok || blockID == common.Hash{}) { + log.Error("failed attempt to finalize commit; there was no +2/3 majority or +2/3 was for nil", "height", height) + return + } + + if !cs.ProposalBlock.HashTo(blockID) { + // TODO: this happens every time if we're not a validator (ugly logs) + // TODO: ^^ wait, why does it matter that we're a validator? + log.Debug( + "failed attempt to finalize commit; we do not have the commit block", + "height", height, + "proposal_block", cs.ProposalBlock.NilableHash(), + "commit_block", blockID, + ) + return + } + + cs.finalizeCommit(ctx, height) +} + +// Increment height and goto RoundStepNewHeight +func (cs *ConsensusState) finalizeCommit(ctx context.Context, height uint64) { + if cs.Height != height || cs.Step != RoundStepCommit { + log.Debug( + "entering finalize commit step", + "height", height, + "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step), + ) + return + } + + blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority() + block := cs.ProposalBlock + + if !ok { + panic("cannot finalize commit; commit does not have 2/3 majority") + } + if !block.HashTo(blockID) { + panic("cannot finalize commit; proposal block does not hash to commit hash") + } + + if err := cs.blockExec.ValidateBlock(cs.chainState, block); err != nil { + panic(fmt.Errorf("+2/3 committed an invalid block: %w", err)) + } + + log.Info( + "Finalizing commit of block", + "height", height, + "hash", block.Hash(), + // "root", block.AppHash, + // "num_txs", len(block.Txs), + ) + + log.Debug(fmt.Sprintf("%v", block), "height", height) + + // fail.Fail() // XXX + + // Save to blockStore. + if cs.blockStore.Height() < block.NumberU64() { + // NOTE: the seenCommit is local justification to commit this block, + // but may differ from the LastCommit included in the next block + precommits := cs.Votes.Precommits(cs.CommitRound) + seenCommit := precommits.MakeCommit() + cs.blockStore.SaveBlock(block, seenCommit) + } else { + // Happens during replay if we already saved the block but didn't commit + log.Debug("calling finalizeCommit on already stored block", "height", block.Number) + } + + // fail.Fail() // XXX + + // Write EndHeightMessage{} for this height, implying that the blockstore + // has saved the block. + // + // If we crash before writing this EndHeightMessage{}, we will recover by + // running ApplyBlock during the ABCI handshake when we restart. If we + // didn't save the block to the blockstore before writing + // EndHeightMessage{}, we'd have to change WAL replay -- currently it + // complains about replaying for heights where an #ENDHEIGHT entry already + // exists. + // + // Either way, the State should not be resumed until we + // successfully call ApplyBlock (ie. later here, or in Handshake after + // restart). + // endMsg := EndHeightMessage{height} + // if err := cs.wal.WriteSync(endMsg); err != nil { // NOTE: fsync + // panic(fmt.Sprintf( + // "failed to write %v msg to consensus WAL due to %v; check your file system and restart the node", + // endMsg, err, + // )) + // } + + // fail.Fail() // XXX + + // Create a copy of the state for staging and an event cache for txs. + stateCopy := cs.chainState.Copy() + + // Execute and commit the block, update and save the state, and update the mempool. + // NOTE The block.AppHash wont reflect these txs until the next block. + stateCopy, err := cs.blockExec.ApplyBlock(ctx, stateCopy, block) + if err != nil { + log.Error("failed to apply block", "height", height, "err", err) + return + } + + // fail.Fail() // XXX + + // NewHeightStep! + cs.updateToState(ctx, stateCopy) + + // fail.Fail() // XXX + + // Private validator might have changed it's key pair => refetch pubkey. + if err := cs.updatePrivValidatorPubKey(); err != nil { + log.Error("failed to get private validator pubkey", "height", height, "err", err) + } + + // cs.StartTime is already set. + // Schedule Round0 to start soon. + cs.scheduleRound0(&cs.RoundState) + + // By here, + // * cs.Height has been increment to height+1 + // * cs.Step is now RoundStepNewHeight + // * cs.StartTime is set to when we will start round0. +} + +//----------------------------------------------------------------------------- + +func (cs *ConsensusState) defaultSetProposal(ctx context.Context, proposal *Proposal) (bool, error) { + // Already have one + // TODO: possibly catch double proposals + if cs.Proposal != nil { + return false, nil + } + + // Does not apply + if proposal.Height != cs.Height || proposal.Round != cs.Round { + return false, nil + } + + // Verify POLRound, which must be -1 or in range [0, proposal.Round). + if proposal.POLRound < -1 || + (proposal.POLRound >= 0 && proposal.POLRound >= proposal.Round) { + return false, ErrInvalidProposalPOLRound + } + + // Verify signature + if !cs.Validators.GetProposer().PubKey.VerifySignature(proposal.ProposalSignBytes(cs.chainState.ChainID), proposal.Signature) { + return false, ErrInvalidProposalSignature + } + + cs.Proposal = proposal + cs.ProposalBlock = proposal.Block + log.Info("Received proposal", "height", cs.Height, "round", cs.Round, "from", cs.Validators.GetProposer().Address) + + // Update Valid* if we can. + prevotes := cs.Votes.Prevotes(cs.Round) + blockID, hasTwoThirds := prevotes.TwoThirdsMajority() + if hasTwoThirds && (blockID != common.Hash{}) && (cs.ValidRound < cs.Round) { + if cs.ProposalBlock.HashTo(blockID) { + log.Debug( + "updating valid block to new proposal block", + "valid_round", cs.Round, + "valid_block_hash", cs.ProposalBlock.Hash(), + ) + + cs.ValidRound = cs.Round + cs.ValidBlock = cs.ProposalBlock + } + // TODO: In case there is +2/3 majority in Prevotes set for some + // block and cs.ProposalBlock contains different block, either + // proposer is faulty or voting power of faulty processes is more + // than 1/3. We should trigger in the future accountability + // procedure at this point. + } + + if cs.Step <= RoundStepPropose && cs.isProposalComplete() { + // Move onto the next step + cs.enterPrevote(ctx, cs.Height, cs.Round) + if hasTwoThirds { // this is optimisation as this will be triggered when prevote is added + cs.enterPrecommit(ctx, cs.Height, cs.Round) + } + } else if cs.Step == RoundStepCommit { + // If we're waiting on the proposal block... + cs.tryFinalizeCommit(ctx, cs.Height) + } + + return true, nil +} + +// Attempt to add the vote. if its a duplicate signature, dupeout the validator +func (cs *ConsensusState) tryAddVote(ctx context.Context, vote *Vote, peerID string) (bool, error) { + added, err := cs.addVote(ctx, vote, peerID) + if err != nil { + // If the vote height is off, we'll just ignore it, + // But if it's a conflicting sig, add it to the cs.evpool. + // If it's otherwise invalid, punish peer. + // nolint: gocritic + if voteErr, ok := err.(*ErrVoteConflictingVotes); ok { + if cs.privValidatorPubKey == nil { + return false, errPubKeyIsNotSet + } + + if vote.ValidatorAddress == cs.privValidatorPubKey.Address() { + log.Error( + "found conflicting vote from ourselves; did you unsafe_reset a validator?", + "height", vote.Height, + "round", vote.Round, + "type", vote.Type, + ) + + return added, err + } + + // TODO: report conflicting votes to the evidence pool + // cs.evpool.ReportConflictingVotes(voteErr.VoteA, voteErr.VoteB) + log.Debug( + "found and sent conflicting votes to the evidence pool", + "vote_a", voteErr.VoteA, + "vote_b", voteErr.VoteB, + ) + + return added, err + } else if errors.Is(err, ErrVoteNonDeterministicSignature) { + log.Debug("vote has non-deterministic signature", "err", err) + } else { + // Either + // 1) bad peer OR + // 2) not a bad peer? this can also err sometimes with "Unexpected step" OR + // 3) tmkms use with multiple validators connecting to a single tmkms instance + // (https://github.com/tendermint/tendermint/issues/3839). + log.Info("failed attempting to add vote", "err", err) + return added, ErrAddingVote + } + } + + return added, nil +} + +func (cs *ConsensusState) addVote( + ctx context.Context, + vote *Vote, + peerID string, +) (added bool, err error) { + log.Debug( + "adding vote", + "vote_height", vote.Height, + "vote_type", vote.Type, + "val_index", vote.ValidatorIndex, + "cs_height", cs.Height, + ) + + // A precommit for the previous height? + // These come in while we wait timeoutCommit + if vote.Height+1 == cs.Height && vote.Type == PrecommitType { + if cs.Step != RoundStepNewHeight { + // Late precommit at prior height is ignored + log.Debug("precommit vote came in after commit timeout and has been ignored", "vote", vote) + return + } + + added, err = cs.LastCommit.AddVote(vote) + if !added { + return + } + + // if we can skip timeoutCommit and have all the votes now, + if cs.config.SkipTimeoutCommit && cs.LastCommit.HasAll() { + // go straight to new round (skip timeout commit) + // cs.scheduleTimeout(time.Duration(0), cs.Height, 0, RoundStepNewHeight) + cs.enterNewRound(ctx, cs.Height, 0) + } + + return + } + + // Height mismatch is ignored. + // Not necessarily a bad peer, but not favorable behavior. + if vote.Height != cs.Height { + log.Debug("vote ignored and not added", "vote_height", vote.Height, "cs_height", cs.Height, "peer", peerID) + return + } + + height := cs.Height + added, err = cs.Votes.AddVote(vote, peerID) + if !added { + // Either duplicate, or error upon cs.Votes.AddByIndex() + return + } + + switch vote.Type { + case PrevoteType: + prevotes := cs.Votes.Prevotes(vote.Round) + log.Debug("added vote to prevote", "vote", vote, "prevotes", prevotes.StringShort()) + + // If +2/3 prevotes for a block or nil for *any* round: + if blockID, ok := prevotes.TwoThirdsMajority(); ok { + // There was a polka! + // If we're locked but this is a recent polka, unlock. + // If it matches our ProposalBlock, update the ValidBlock + + // Unlock if `cs.LockedRound < vote.Round <= cs.Round` + // NOTE: If vote.Round > cs.Round, we'll deal with it when we get to vote.Round + if (cs.LockedBlock != nil) && + (cs.LockedRound < vote.Round) && + (vote.Round <= cs.Round) && + !cs.LockedBlock.HashTo(blockID) { + + log.Debug("unlocking because of POL", "locked_round", cs.LockedRound, "pol_round", vote.Round) + + cs.LockedRound = -1 + cs.LockedBlock = nil + } + + // Update Valid* if we can. + // NOTE: our proposal block may be nil or not what received a polka.. + if (blockID != common.Hash{}) && (cs.ValidRound < vote.Round) && (vote.Round == cs.Round) { + if cs.ProposalBlock.HashTo(blockID) { + log.Debug("updating valid block because of POL", "valid_round", cs.ValidRound, "pol_round", vote.Round) + cs.ValidRound = vote.Round + cs.ValidBlock = cs.ProposalBlock + } else { + log.Debug( + "valid block we do not know about; set ProposalBlock=nil", + "proposal", cs.ProposalBlock, + "block_id", blockID, + ) + + // we're getting the wrong block + cs.ProposalBlock = nil + } + } + } + + // If +2/3 prevotes for *anything* for future round: + switch { + case cs.Round < vote.Round && prevotes.HasTwoThirdsAny(): + // Round-skip if there is any 2/3+ of votes ahead of us + cs.enterNewRound(ctx, height, vote.Round) + + case cs.Round == vote.Round && RoundStepPrevote <= cs.Step: // current round + blockID, ok := prevotes.TwoThirdsMajority() + if ok && (cs.isProposalComplete() || blockID == common.Hash{}) { + cs.enterPrecommit(ctx, height, vote.Round) + } else if prevotes.HasTwoThirdsAny() { + cs.enterPrevoteWait(ctx, height, vote.Round) + } + + case cs.Proposal != nil && 0 <= cs.Proposal.POLRound && cs.Proposal.POLRound == vote.Round: + // If the proposal is now complete, enter prevote of cs.Round. + if cs.isProposalComplete() { + cs.enterPrevote(ctx, height, cs.Round) + } + } + + case PrecommitType: + precommits := cs.Votes.Precommits(vote.Round) + log.Debug("added vote to precommit", + "height", vote.Height, + "round", vote.Round, + "validator", vote.ValidatorAddress.String(), + "vote_timestamp", vote.TimestampMs, + "data", precommits.LogString()) + + blockID, ok := precommits.TwoThirdsMajority() + if ok { + // Executed as TwoThirdsMajority could be from a higher round + cs.enterNewRound(ctx, height, vote.Round) + cs.enterPrecommit(ctx, height, vote.Round) + + if (blockID != common.Hash{}) { + cs.enterCommit(ctx, height, vote.Round) + if cs.config.SkipTimeoutCommit && precommits.HasAll() { + cs.enterNewRound(ctx, cs.Height, 0) + } + } else { + cs.enterPrecommitWait(ctx, height, vote.Round) + } + } else if cs.Round <= vote.Round && precommits.HasTwoThirdsAny() { + cs.enterNewRound(ctx, height, vote.Round) + cs.enterPrecommitWait(ctx, height, vote.Round) + } + + default: + panic(fmt.Sprintf("unexpected vote type %v", vote.Type)) + } + + return added, err +} + +// CONTRACT: cs.privValidator is not nil. +func (cs *ConsensusState) signVote( + msgType SignedMsgType, + blockID common.Hash, +) (*Vote, error) { + // Flush the WAL. Otherwise, we may not recompute the same vote to sign, + // and the privValidator will refuse to sign anything. + // if err := cs.wal.FlushAndSync(); err != nil { + // return nil, err + // } + + if cs.privValidatorPubKey == nil { + return nil, errPubKeyIsNotSet + } + + addr := cs.privValidatorPubKey.Address() + valIdx, _ := cs.Validators.GetByAddress(addr) + + vote := &Vote{ + ValidatorAddress: addr, + ValidatorIndex: valIdx, + Height: cs.Height, + Round: cs.Round, + TimestampMs: cs.voteTime(), + Type: msgType, + BlockID: blockID, + } + + // If the signedMessageType is for precommit, + // use our local precommit Timeout as the max wait time for getting a singed commit. The same goes for prevote. + var timeout time.Duration + + switch msgType { + case PrecommitType: + timeout = cs.config.TimeoutPrecommit + case PrevoteType: + timeout = cs.config.TimeoutPrevote + default: + timeout = time.Second + } + + ctx, cancel := context.WithTimeout(context.TODO(), timeout) + defer cancel() + + err := cs.privValidator.SignVote(ctx, cs.chainState.ChainID, vote) + + return vote, err +} + +// voteTime ensures monotonicity of the time a validator votes on. +// It ensures that for a prior block with a BFT-timestamp of T, +// any vote from this validator will have time at least time T + 1ms. +// This is needed, as monotonicity of time is a guarantee that BFT time provides. +func (cs *ConsensusState) voteTime() uint64 { + now := uint64(CanonicalNowMs()) + minVoteTime := now + // Minimum time increment between blocks + timeIota := uint64(1) // in milli + // TODO: We should remove next line in case we don't vote for v in case cs.ProposalBlock == nil, + // even if cs.LockedBlock != nil. See https://docs.tendermint.com/master/spec/. + if cs.LockedBlock != nil { + // See the BFT time spec https://docs.tendermint.com/master/spec/consensus/bft-time.html + minVoteTime = cs.LockedBlock.TimeMs() + timeIota + } else if cs.ProposalBlock != nil { + minVoteTime = cs.ProposalBlock.TimeMs() + timeIota + } + + if now > minVoteTime { + return now + } + return minVoteTime +} + +// sign the vote and publish on internalMsgQueue +func (cs *ConsensusState) signAddVote(ctx context.Context, msgType SignedMsgType, blockID common.Hash) *Vote { + if cs.privValidator == nil { // the node does not have a key + return nil + } + + if cs.privValidatorPubKey == nil { + // Vote won't be signed, but it's not critical. + log.Error(fmt.Sprintf("signAddVote: %v", errPubKeyIsNotSet)) + return nil + } + + // If the node not in the validator set, do nothing. + if !cs.Validators.HasAddress(cs.privValidatorPubKey.Address()) { + return nil + } + + // TODO: pass pubKey to signVote + vote, err := cs.signVote(msgType, blockID) + if err == nil { + cs.sendInternalMessage(ctx, MsgInfo{&VoteMessage{Vote: vote}, ""}) + log.Debug("signed and pushed vote", "height", cs.Height, "round", cs.Round, "vote", vote) + return vote + } + + log.Error("failed signing vote", "height", cs.Height, "round", cs.Round, "vote", vote, "err", err) + return nil +} + +// updatePrivValidatorPubKey get's the private validator public key and +// memoizes it. This func returns an error if the private validator is not +// responding or responds with an error. +func (cs *ConsensusState) updatePrivValidatorPubKey() error { + if cs.privValidator == nil { + return nil + } + + var timeout time.Duration + if cs.config.TimeoutPrecommit > cs.config.TimeoutPrevote { + timeout = cs.config.TimeoutPrecommit + } else { + timeout = cs.config.TimeoutPrevote + } + + // no GetPubKey retry beyond the proposal/voting in RetrySignerClient + if cs.Step >= RoundStepPrecommit && cs.privValidatorType == RetrySignerClient { + timeout = 0 + } + + // set context timeout depending on the configuration and the State step, + // this helps in avoiding blocking of the remote signer connection. + ctx, cancel := context.WithTimeout(context.TODO(), timeout) + defer cancel() + pubKey, err := cs.privValidator.GetPubKey(ctx) + if err != nil { + return err + } + cs.privValidatorPubKey = pubKey + return nil +} + +// look back to check existence of the node's consensus votes before joining consensus +func (cs *ConsensusState) checkDoubleSigningRisk(height uint64) error { + if cs.privValidator != nil && cs.privValidatorPubKey != nil && cs.config.DoubleSignCheckHeight > 0 && height > 0 { + valAddr := cs.privValidatorPubKey.Address() + doubleSignCheckHeight := cs.config.DoubleSignCheckHeight + if doubleSignCheckHeight > height { + doubleSignCheckHeight = height + } + + for i := uint64(1); i < doubleSignCheckHeight; i++ { + lastCommit := cs.LoadCommit(height - i) + if lastCommit != nil { + for sigIdx, s := range lastCommit.Signatures { + if s.BlockIDFlag == BlockIDFlagCommit && s.ValidatorAddress == valAddr { + log.Info("found signature from the same key", "sig", s, "idx", sigIdx, "height", height-i) + return ErrSignatureFoundInPastBlocks + } + } + } + } + } + + return nil +} diff --git a/consensus/tendermint/consensus/default_block_executor.go b/consensus/tendermint/consensus/default_block_executor.go new file mode 100644 index 000000000000..6b1b1520ba27 --- /dev/null +++ b/consensus/tendermint/consensus/default_block_executor.go @@ -0,0 +1,221 @@ +package consensus + +import ( + "context" + "errors" + "fmt" + "sort" + + "github.com/ethereum/go-ethereum/common" + "github.com/syndtr/goleveldb/leveldb" +) + +type DefaultBlockExecutor struct { + db *leveldb.DB +} + +func NewDefaultBlockExecutor(db *leveldb.DB) BlockExecutor { + return &DefaultBlockExecutor{} +} + +func (be *DefaultBlockExecutor) ValidateBlock(state ChainState, b *FullBlock) error { + return validateBlock(state, b) +} + +func validateBlock(state ChainState, block *FullBlock) error { + + // Validate basic info. + + if state.LastBlockHeight == 0 && block.NumberU64() != state.InitialHeight { + return fmt.Errorf("wrong Block.Header.Height. Expected %v for initial block, got %v", + block.NumberU64(), state.InitialHeight) + } + if state.LastBlockHeight > 0 && block.NumberU64() != state.LastBlockHeight+1 { + return fmt.Errorf("wrong Block.Header.Height. Expected %v, got %v", + state.LastBlockHeight+1, + block.NumberU64(), + ) + } + // Validate prev block info. + if block.ParentHash() != state.LastBlockID { + return fmt.Errorf("wrong Block.Header.LastBlockID. Expected %v, got %v", + state.LastBlockID, + block.ParentHash(), + ) + } + + // Validate block LastCommit. + if block.NumberU64() == state.InitialHeight { + if len(block.LastCommit.Signatures) != 0 { + return errors.New("initial block can't have LastCommit signatures") + } + } else { + // LastCommit.Signatures length is checked in VerifyCommit. + if err := state.LastValidators.VerifyCommit( + state.ChainID, state.LastBlockID, block.NumberU64()-1, block.LastCommit); err != nil { + return err + } + } + + // Don't allow validator change within the epoch + if block.NumberU64()%state.Epoch != 0 && len(block.NextValidators()) != 0 { + return errors.New("cannot change validators within epoch") + } + + // NOTE: We can't actually verify it's the right proposer because we don't + // know what round the block was first proposed. So just check that it's + // a legit address and a known validator. + // The length is checked in ValidateBasic above. + if !state.Validators.HasAddress(block.Coinbase()) { + return fmt.Errorf("block.Header.ProposerAddress %X is not a validator", + block.Coinbase(), + ) + } + + if block.TimeMs()/1000 != block.Time() { + return fmt.Errorf("inccorect timestamp") + } + + // Validate block Time + switch { + case block.NumberU64() > state.InitialHeight: + if block.TimeMs() <= state.LastBlockTime { + return fmt.Errorf("block time %v not greater than last block time %v", + block.TimeMs(), + state.LastBlockTime, + ) + } + medianTime := MedianTime(block.LastCommit, state.LastValidators) + if block.TimeMs() != medianTime { + return fmt.Errorf("invalid block time. Expected %v, got %v", + medianTime, + block.TimeMs(), + ) + } + + case block.NumberU64() == state.InitialHeight: + genesisTime := state.LastBlockTime + if block.TimeMs() != genesisTime { + return fmt.Errorf("block time %v is not equal to genesis time %v", + block.TimeMs(), + genesisTime, + ) + } + + default: + return fmt.Errorf("block height %v lower than initial height %v", + block.NumberU64(), state.InitialHeight) + } + + return nil +} + +// weightedTime for computing a median. +type weightedTime struct { + TimeMs uint64 + Weight int64 +} + +// MedianTime computes a median time for a given Commit (based on Timestamp field of votes messages) and the +// corresponding validator set. The computed time is always between timestamps of +// the votes sent by honest processes, i.e., a faulty processes can not arbitrarily increase or decrease the +// computed value. +func MedianTime(commit *Commit, validators *ValidatorSet) uint64 { + weightedTimes := make([]*weightedTime, len(commit.Signatures)) + totalVotingPower := int64(0) + + for i, commitSig := range commit.Signatures { + if commitSig.Absent() { + continue + } + _, validator := validators.GetByAddress(commitSig.ValidatorAddress) + // If there's no condition, TestValidateBlockCommit panics; not needed normally. + if validator != nil { + totalVotingPower += validator.VotingPower + weightedTimes[i] = &weightedTime{TimeMs: commitSig.TimestampMs, Weight: validator.VotingPower} + } + } + + return weightedMedian(weightedTimes, totalVotingPower) +} + +// weightedMedian computes weighted median time for a given array of WeightedTime and the total voting power. +func weightedMedian(weightedTimes []*weightedTime, totalVotingPower int64) (res uint64) { + median := totalVotingPower / 2 + + sort.Slice(weightedTimes, func(i, j int) bool { + if weightedTimes[i] == nil { + return false + } + if weightedTimes[j] == nil { + return true + } + return weightedTimes[i].TimeMs < weightedTimes[j].TimeMs + }) + + for _, weightedTime := range weightedTimes { + if weightedTime != nil { + if median <= weightedTime.Weight { + res = weightedTime.TimeMs + break + } + median -= weightedTime.Weight + } + } + return +} + +func (be *DefaultBlockExecutor) ApplyBlock(ctx context.Context, state ChainState, block *FullBlock) (ChainState, error) { + // TOOD: execute the block & new validator change + // Update the state with the block and responses. + state, err := updateState(state, block.Hash(), block, []common.Address{}, []int64{}) + if err != nil { + return state, fmt.Errorf("commit failed for application: %v", err) + } + + return state, nil +} + +func updateState( + state ChainState, + blockID common.Hash, + block *FullBlock, + nextValidators []common.Address, + nextVotingPowers []int64, +) (ChainState, error) { + + var nValSet *ValidatorSet + + if len(nextValidators) != 0 { + if len(nextValidators) != len(nextVotingPowers) { + panic("len(nextValidators) != len(nextVotingPowers)") + } + nValSet = NewValidatorSet(nextValidators, nextVotingPowers, nValSet.ProposerReptition) + } else { + nValSet = state.Validators.Copy() + // Update validator proposer priority and set state variables. + nValSet.IncrementProposerPriority(1) + } + + return ChainState{ + ChainID: state.ChainID, + InitialHeight: state.InitialHeight, + LastBlockHeight: block.NumberU64(), + LastBlockID: blockID, + LastBlockTime: block.TimeMs(), + Validators: nValSet, + LastValidators: state.Validators.Copy(), + AppHash: nil, + Epoch: state.Epoch, + }, nil +} + +func (be *DefaultBlockExecutor) MakeBlock( + chainState *ChainState, height uint64, + // txs []types.Tx, + commit *Commit, + // evidence []types.Evidence, + proposerAddress common.Address) *FullBlock { + + return chainState.MakeBlock(height, commit, proposerAddress) +} diff --git a/consensus/tendermint/consensus/priv_validator_local.go b/consensus/tendermint/consensus/priv_validator_local.go new file mode 100644 index 000000000000..a89fa2ffd8ee --- /dev/null +++ b/consensus/tendermint/consensus/priv_validator_local.go @@ -0,0 +1,88 @@ +package consensus + +import ( + "context" + "crypto/ecdsa" + "crypto/rand" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// PrivValidator defines the functionality of a local Tendermint validator +// that signs votes and proposals, and never double signs. +type PrivValidator interface { + GetPubKey(context.Context) (PubKey, error) + + SignVote(ctx context.Context, chainID string, vote *Vote) error + SignProposal(ctx context.Context, chainID string, proposal *Proposal) error +} + +// PrivValidatorType defines the implemtation types. +type PrivValidatorType uint8 + +const ( + MockSignerClient = PrivValidatorType(0x00) // mock signer + FileSignerClient = PrivValidatorType(0x01) // signer client via file + RetrySignerClient = PrivValidatorType(0x02) // signer client with retry via socket + SignerSocketClient = PrivValidatorType(0x03) // signer client via socket + ErrorMockSignerClient = PrivValidatorType(0x04) // error mock signer + SignerGRPCClient = PrivValidatorType(0x05) // signer client via gRPC +) + +type PrivValidatorLocal struct { + PrivKey *ecdsa.PrivateKey +} + +// generate a local priv validator with random key. +func GeneratePrivValidatorLocal() PrivValidator { + pk, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader) + if err != nil { + panic("failed to generate key") + } + + return &PrivValidatorLocal{ + PrivKey: pk, + } +} + +func NewPrivValidatorLocal(privKey *ecdsa.PrivateKey) *PrivValidatorLocal { + return &PrivValidatorLocal{PrivKey: privKey} +} + +func pubKeyToAddress(pub []byte) common.Address { + var addr common.Address + // the first byte of pubkey is bitcoin heritage + copy(addr[:], crypto.Keccak256(pub[1:])[12:]) + return addr +} + +func (pv *PrivValidatorLocal) Address() common.Address { + return crypto.PubkeyToAddress(pv.PrivKey.PublicKey) +} + +func (pv *PrivValidatorLocal) GetPubKey(context.Context) (PubKey, error) { + return &EcdsaPubKey{address: pv.Address()}, nil +} + +func (pv *PrivValidatorLocal) SignVote(ctx context.Context, chainId string, vote *Vote) error { + vote.TimestampMs = uint64(CanonicalNowMs()) + b := vote.VoteSignBytes(chainId) + + h := crypto.Keccak256Hash(b) + sign, err := crypto.Sign(h[:], pv.PrivKey) + vote.Signature = sign + + return err +} + +func (pv *PrivValidatorLocal) SignProposal(ctx context.Context, chainID string, proposal *Proposal) error { + // TODO: sanity check + b := proposal.ProposalSignBytes(chainID) + + h := crypto.Keccak256Hash(b) + + sign, err := crypto.Sign(h[:], pv.PrivKey) + proposal.Signature = sign + return err +} diff --git a/consensus/tendermint/consensus/pubkey.go b/consensus/tendermint/consensus/pubkey.go new file mode 100644 index 000000000000..c7c1c22277d1 --- /dev/null +++ b/consensus/tendermint/consensus/pubkey.go @@ -0,0 +1,46 @@ +package consensus + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +type PubKey interface { + Address() common.Address + // Bytes() []byte + VerifySignature(msg []byte, sig []byte) bool + // Equals(PubKey) bool + Type() string +} + +type EcdsaPubKey struct { + address common.Address +} + +func NewEcdsaPubKey(addr common.Address) PubKey { + return &EcdsaPubKey{address: addr} +} + +func (pubkey *EcdsaPubKey) Type() string { + return "ECDSA_PUBKEY" +} + +func (pubkey *EcdsaPubKey) Address() common.Address { + return pubkey.address +} + +func (pubkey *EcdsaPubKey) VerifySignature(msg []byte, sig []byte) bool { + h := crypto.Keccak256Hash(msg) + + pub, err := crypto.Ecrecover(h[:], sig) + if err != nil { + return false + } + + if len(pub) == 0 || pub[0] != 4 { + return false + } + + addr := pubKeyToAddress(pub) + return addr == pubkey.address +} diff --git a/consensus/tendermint/consensus/round_state.go b/consensus/tendermint/consensus/round_state.go new file mode 100644 index 000000000000..dcfe2e4e6776 --- /dev/null +++ b/consensus/tendermint/consensus/round_state.go @@ -0,0 +1,100 @@ +package consensus + +import ( + "time" +) + +// RoundState defines the internal consensus state. +// NOTE: Not thread safe. Should only be manipulated by functions downstream +// of the cs.receiveRoutine +type RoundState struct { + Height uint64 `json:"height"` // Height we are working on + Round int32 `json:"round"` + Step RoundStepType `json:"step"` + StartTime time.Time `json:"start_time"` + + // Subjective time when +2/3 precommits for Block at Round were found + CommitTime time.Time `json:"commit_time"` + Validators *ValidatorSet `json:"validators"` + Proposal *Proposal `json:"proposal"` + ProposalBlock *FullBlock `json:"proposal_block"` + LockedRound int32 `json:"locked_round"` + LockedBlock *FullBlock `json:"locked_block"` + + // Last known round with POL for non-nil valid block. + ValidRound int32 `json:"valid_round"` + ValidBlock *FullBlock `json:"valid_block"` // Last known block of POL mentioned above. + + // Last known block parts of POL mentioned above. + Votes *HeightVoteSet `json:"votes"` + CommitRound int32 `json:"commit_round"` // + LastCommit *VoteSet `json:"last_commit"` // Last precommits at Height-1 + LastValidators *ValidatorSet `json:"last_validators"` + TriggeredTimeoutPrecommit bool `json:"triggered_timeout_precommit"` +} + +// NOTE: This goes into the replay WAL +type EventDataRoundState struct { + Height uint64 `json:"height"` + Round int32 `json:"round"` + Step string `json:"step"` +} + +// RoundStateEvent returns the H/R/S of the RoundState as an event. +func (rs *RoundState) RoundStateEvent() EventDataRoundState { + return EventDataRoundState{ + Height: rs.Height, + Round: rs.Round, + Step: rs.Step.String(), + } +} + +//----------------------------------------------------------------------------- +// RoundStepType enum type + +// RoundStepType enumerates the state of the consensus state machine +type RoundStepType uint8 // These must be numeric, ordered. + +// RoundStepType +const ( + RoundStepNewHeight = RoundStepType(0x01) // Wait til CommitTime + timeoutCommit + RoundStepNewRound = RoundStepType(0x02) // Setup new round and go to RoundStepPropose + RoundStepPropose = RoundStepType(0x03) // Did propose, gossip proposal + RoundStepPrevote = RoundStepType(0x04) // Did prevote, gossip prevotes + RoundStepPrevoteWait = RoundStepType(0x05) // Did receive any +2/3 prevotes, start timeout + RoundStepPrecommit = RoundStepType(0x06) // Did precommit, gossip precommits + RoundStepPrecommitWait = RoundStepType(0x07) // Did receive any +2/3 precommits, start timeout + RoundStepCommit = RoundStepType(0x08) // Entered commit state machine + // NOTE: RoundStepNewHeight acts as RoundStepCommitWait. + + // NOTE: Update IsValid method if you change this! +) + +// IsValid returns true if the step is valid, false if unknown/undefined. +func (rs RoundStepType) IsValid() bool { + return uint8(rs) >= 0x01 && uint8(rs) <= 0x08 +} + +// String returns a string +func (rs RoundStepType) String() string { + switch rs { + case RoundStepNewHeight: + return "RoundStepNewHeight" + case RoundStepNewRound: + return "RoundStepNewRound" + case RoundStepPropose: + return "RoundStepPropose" + case RoundStepPrevote: + return "RoundStepPrevote" + case RoundStepPrevoteWait: + return "RoundStepPrevoteWait" + case RoundStepPrecommit: + return "RoundStepPrecommit" + case RoundStepPrecommitWait: + return "RoundStepPrecommitWait" + case RoundStepCommit: + return "RoundStepCommit" + default: + return "RoundStepUnknown" // Cannot panic. + } +} diff --git a/consensus/tendermint/consensus/safe_math.go b/consensus/tendermint/consensus/safe_math.go new file mode 100644 index 000000000000..6d227c76070c --- /dev/null +++ b/consensus/tendermint/consensus/safe_math.go @@ -0,0 +1,80 @@ +package consensus + +import ( + "errors" + "math" +) + +var ErrOverflowInt32 = errors.New("int32 overflow") +var ErrOverflowUint8 = errors.New("uint8 overflow") +var ErrOverflowInt8 = errors.New("int8 overflow") + +// SafeAddInt32 adds two int32 integers +// If there is an overflow this will panic +func SafeAddInt32(a, b int32) int32 { + if b > 0 && (a > math.MaxInt32-b) { + panic(ErrOverflowInt32) + } else if b < 0 && (a < math.MinInt32-b) { + panic(ErrOverflowInt32) + } + return a + b +} + +// SafeSubInt32 subtracts two int32 integers +// If there is an overflow this will panic +func SafeSubInt32(a, b int32) int32 { + if b > 0 && (a < math.MinInt32+b) { + panic(ErrOverflowInt32) + } else if b < 0 && (a > math.MaxInt32+b) { + panic(ErrOverflowInt32) + } + return a - b +} + +// SafeConvertInt32 takes a int and checks if it overflows +// If there is an overflow this will panic +func SafeConvertInt32(a int64) int32 { + if a > math.MaxInt32 { + panic(ErrOverflowInt32) + } else if a < math.MinInt32 { + panic(ErrOverflowInt32) + } + return int32(a) +} + +// SafeConvertUint8 takes an int64 and checks if it overflows +// If there is an overflow it returns an error +func SafeConvertUint8(a int64) (uint8, error) { + if a > math.MaxUint8 { + return 0, ErrOverflowUint8 + } else if a < 0 { + return 0, ErrOverflowUint8 + } + return uint8(a), nil +} + +// SafeConvertInt8 takes an int64 and checks if it overflows +// If there is an overflow it returns an error +func SafeConvertInt8(a int64) (int8, error) { + if a > math.MaxInt8 { + return 0, ErrOverflowInt8 + } else if a < math.MinInt8 { + return 0, ErrOverflowInt8 + } + return int8(a), nil +} + +func SafeConvertInt32FromUint32(a uint32) int32 { + b := int32(a) + if b < 0 { + panic(ErrOverflowInt32) + } + return b +} + +func SafeConvertUint32FromInt32(a int32) uint32 { + if a < 0 { + panic(ErrOverflowInt32) + } + return uint32(a) +} diff --git a/consensus/tendermint/consensus/service.go b/consensus/tendermint/consensus/service.go new file mode 100644 index 000000000000..961fb703744e --- /dev/null +++ b/consensus/tendermint/consensus/service.go @@ -0,0 +1,198 @@ +package consensus + +import ( + "context" + "errors" + "sync/atomic" + + "github.com/ethereum/go-ethereum/log" +) + +var ( + // ErrAlreadyStarted is returned when somebody tries to start an already + // running service. + ErrAlreadyStarted = errors.New("already started") + // ErrAlreadyStopped is returned when somebody tries to stop an already + // stopped service (without resetting it). + ErrAlreadyStopped = errors.New("already stopped") + // ErrNotStarted is returned when somebody tries to stop a not running + // service. + ErrNotStarted = errors.New("not started") +) + +// Service defines a service that can be started, stopped, and reset. +type Service interface { + // Start is called to start the service, which should run until + // the context terminates. If the service is already running, Start + // must report an error. + Start(context.Context) error + + // Return true if the service is running + IsRunning() bool + + // String representation of the service + String() string + + // Wait blocks until the service is stopped. + Wait() +} + +// Implementation describes the implementation that the +// BaseService implementation wraps. +type Implementation interface { + Service + + // Called by the Services Start Method + OnStart(context.Context) error + + // Called when the service's context is canceled. + OnStop() +} + +/* +Classical-inheritance-style service declarations. Services can be started, then +stopped, then optionally restarted. + +Users can override the OnStart/OnStop methods. In the absence of errors, these +methods are guaranteed to be called at most once. If OnStart returns an error, +service won't be marked as started, so the user can call Start again. + +A call to Reset will panic, unless OnReset is overwritten, allowing +OnStart/OnStop to be called again. + +The caller must ensure that Start and Stop are not called concurrently. + +It is ok to call Stop without calling Start first. + +Typical usage: + + type FooService struct { + BaseService + // private fields + } + + func NewFooService() *FooService { + fs := &FooService{ + // init + } + fs.BaseService = *NewBaseService(log, "FooService", fs) + return fs + } + + func (fs *FooService) OnStart(ctx context.Context) error { + fs.BaseService.OnStart() // Always call the overridden method. + // initialize private fields + // start subroutines, etc. + } + + func (fs *FooService) OnStop() error { + fs.BaseService.OnStop() // Always call the overridden method. + // close/destroy private fields + // stop subroutines, etc. + } +*/ +type BaseService struct { + name string + started uint32 // atomic + stopped uint32 // atomic + quit chan struct{} + + // The "subclass" of BaseService + impl Implementation +} + +// NewBaseService creates a new BaseService. +func NewBaseService(name string, impl Implementation) *BaseService { + return &BaseService{ + name: name, + quit: make(chan struct{}), + impl: impl, + } +} + +// Start starts the Service and calls its OnStart method. An error will be +// returned if the service is already running or stopped. To restart a +// stopped service, call Reset. +func (bs *BaseService) Start(ctx context.Context) error { + if atomic.CompareAndSwapUint32(&bs.started, 0, 1) { + if atomic.LoadUint32(&bs.stopped) == 1 { + log.Error("not starting service; already stopped", "service", bs.name, "impl", bs.impl.String()) + atomic.StoreUint32(&bs.started, 0) + return ErrAlreadyStopped + } + + log.Info("starting service", "service", bs.name, "impl", bs.impl.String()) + + if err := bs.impl.OnStart(ctx); err != nil { + // revert flag + atomic.StoreUint32(&bs.started, 0) + return err + } + + go func(ctx context.Context) { + select { + case <-bs.quit: + // someone else explicitly called stop + // and then we shouldn't. + return + case <-ctx.Done(): + // if nothing is running, no need to + // shut down again. + if !bs.impl.IsRunning() { + return + } + + // the context was cancel and we + // should stop. + if err := bs.Stop(); err != nil { + log.Error("stopped service", + "err", err.Error(), + "service", bs.name, + "impl", bs.impl.String()) + } + + log.Info("stopped service", + "service", bs.name, + "impl", bs.impl.String()) + } + }(ctx) + + return nil + } + + log.Debug("not starting service; already started", "service", bs.name, "impl", bs.impl.String()) + return ErrAlreadyStarted +} + +// Stop implements Service by calling OnStop (if defined) and closing quit +// channel. An error will be returned if the service is already stopped. +func (bs *BaseService) Stop() error { + if atomic.CompareAndSwapUint32(&bs.stopped, 0, 1) { + if atomic.LoadUint32(&bs.started) == 0 { + log.Error("not stopping service; not started yet", "service", bs.name, "impl", bs.impl.String()) + atomic.StoreUint32(&bs.stopped, 0) + return ErrNotStarted + } + + log.Info("stopping service", "service", bs.name, "impl", bs.impl.String()) + bs.impl.OnStop() + close(bs.quit) + + return nil + } + + log.Debug("not stopping service; already stopped", "service", bs.name, "impl", bs.impl.String()) + return ErrAlreadyStopped +} + +// IsRunning implements Service by returning true or false depending on the +// service's state. +func (bs *BaseService) IsRunning() bool { + return atomic.LoadUint32(&bs.started) == 1 && atomic.LoadUint32(&bs.stopped) == 0 +} + +// Wait blocks until the service is stopped. +func (bs *BaseService) Wait() { <-bs.quit } + +// String implements Service by returning a string representation of the service. +func (bs *BaseService) String() string { return bs.name } diff --git a/consensus/tendermint/consensus/timeout_ticker.go b/consensus/tendermint/consensus/timeout_ticker.go new file mode 100644 index 000000000000..26f2528c88a7 --- /dev/null +++ b/consensus/tendermint/consensus/timeout_ticker.go @@ -0,0 +1,142 @@ +package consensus + +import ( + "context" + "time" + + "github.com/ethereum/go-ethereum/log" +) + +var ( + tickTockBufferSize = 10 +) + +// internally generated messages which may update the state +type timeoutInfo struct { + Duration time.Duration `json:"duration"` + Height uint64 `json:"height"` + Round int32 `json:"round"` + Step RoundStepType `json:"step"` +} + +// TimeoutTicker is a timer that schedules timeouts +// conditional on the height/round/step in the timeoutInfo. +// The timeoutInfo.Duration may be non-positive. +type TimeoutTicker interface { + Start(context.Context) error + Stop() error + IsRunning() bool + Chan() <-chan timeoutInfo // on which to receive a timeout + ScheduleTimeout(ti timeoutInfo) // reset the timer +} + +// timeoutTicker wraps time.Timer, +// scheduling timeouts only for greater height/round/step +// than what it's already seen. +// Timeouts are scheduled along the tickChan, +// and fired on the tockChan. +type timeoutTicker struct { + BaseService + + timer *time.Timer + tickChan chan timeoutInfo // for scheduling timeouts + tockChan chan timeoutInfo // for notifying about them +} + +// NewTimeoutTicker returns a new TimeoutTicker. +func NewTimeoutTicker() TimeoutTicker { + tt := &timeoutTicker{ + timer: time.NewTimer(0), + tickChan: make(chan timeoutInfo, tickTockBufferSize), + tockChan: make(chan timeoutInfo, tickTockBufferSize), + } + tt.BaseService = *NewBaseService("TimeoutTicker", tt) + tt.stopTimer() // don't want to fire until the first scheduled timeout + return tt +} + +// OnStart implements service.Service. It starts the timeout routine. +func (t *timeoutTicker) OnStart(ctx context.Context) error { + go t.timeoutRoutine(ctx) + + return nil +} + +// OnStop implements service.Service. It stops the timeout routine. +func (t *timeoutTicker) OnStop() { t.stopTimer() } + +// Chan returns a channel on which timeouts are sent. +func (t *timeoutTicker) Chan() <-chan timeoutInfo { + return t.tockChan +} + +// ScheduleTimeout schedules a new timeout by sending on the internal tickChan. +// The timeoutRoutine is always available to read from tickChan, so this won't block. +// The scheduling may fail if the timeoutRoutine has already scheduled a timeout for a later height/round/step. +func (t *timeoutTicker) ScheduleTimeout(ti timeoutInfo) { + t.tickChan <- ti +} + +//------------------------------------------------------------- + +// stop the timer and drain if necessary +func (t *timeoutTicker) stopTimer() { + // Stop() returns false if it was already fired or was stopped + if !t.timer.Stop() { + select { + case <-t.timer.C: + default: + log.Debug("Timer already stopped") + } + } +} + +// send on tickChan to start a new timer. +// timers are interupted and replaced by new ticks from later steps +// timeouts of 0 on the tickChan will be immediately relayed to the tockChan +func (t *timeoutTicker) timeoutRoutine(ctx context.Context) { + log.Debug("Starting timeout routine") + var ti timeoutInfo + for { + select { + case newti := <-t.tickChan: + log.Debug("Received tick", "old_ti", ti, "new_ti", newti) + + // ignore tickers for old height/round/step + if newti.Height < ti.Height { + continue + } else if newti.Height == ti.Height { + if newti.Round < ti.Round { + continue + } else if newti.Round == ti.Round { + if ti.Step > 0 && newti.Step <= ti.Step { + continue + } + } + } + + // stop the last timer + t.stopTimer() + + // update timeoutInfo and reset timer + // NOTE time.Timer allows duration to be non-positive + ti = newti + t.timer.Reset(ti.Duration) + log.Debug("Scheduled timeout", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step) + case <-t.timer.C: + log.Info("Timed out", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step) + // go routine here guarantees timeoutRoutine doesn't block. + // Determinism comes from playback in the receiveRoutine. + // We can eliminate it by merging the timeoutRoutine into receiveRoutine + // and managing the timeouts ourselves with a millisecond ticker + go func(toi timeoutInfo) { + select { + case t.tockChan <- toi: + case <-ctx.Done(): + } + }(ti) + case <-ctx.Done(): + return + } + } +} diff --git a/consensus/tendermint/consensus/types.go b/consensus/tendermint/consensus/types.go new file mode 100644 index 000000000000..7231850283b1 --- /dev/null +++ b/consensus/tendermint/consensus/types.go @@ -0,0 +1,96 @@ +package consensus + +import ( + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +type ( + Vote = types.Vote + VoteMessage = types.VoteMessage + Validator = types.Validator + ValidatorSet = types.ValidatorSet + VoteSet = types.VoteSet + HeightVoteSet = types.HeightVoteSet + FullBlock = types.FullBlock + Commit = types.Commit + // BlockIDFlag indicates which BlockID the signature is for. + BlockIDFlag = types.BlockIDFlag + CommitSig = types.CommitSig + Proposal = types.Proposal + ProposalMessage = types.ProposalMessage + ConsensusConfig = params.ConsensusConfig + + Header = types.Header + + ErrVoteConflictingVotes = types.ErrVoteConflictingVotes +) + +var ( + NewVoteSet = types.NewVoteSet + NewValidatorSet = types.NewValidatorSet + VerifyCommit = types.VerifyCommit + NewHeightVoteSet = types.NewHeightVoteSet + ErrVoteNonDeterministicSignature = types.ErrVoteNonDeterministicSignature + // BlockIDFlagAbsent - no vote was received from a validator. + BlockIDFlagAbsent BlockIDFlag = types.BlockIDFlagAbsent + // BlockIDFlagCommit - voted for the Commit.BlockID. + BlockIDFlagCommit = types.BlockIDFlagCommit + // BlockIDFlagNil - voted for nil. + BlockIDFlagNil = types.BlockIDFlagAbsent + + NewCommit = types.NewCommit + CommitToVoteSet = types.CommitToVoteSet + NewProposal = types.NewProposal + NewCommitSigAbsent = types.NewCommitSigAbsent +) + +// func NewBlock(header *types.Header, body *types.Body, lastCommit *Commit) *Block { +// header.LastCommitHash = lastCommit.Hash() +// return &Block{Header: header, Body: body, LastCommit: lastCommit} +// } + +var MaxSignatureSize = 65 + +// Now returns the current time in UTC with no monotonic component. +func CanonicalNow() time.Time { + return Canonical(time.Now()) +} + +func CanonicalNowMs() int64 { + return time.Now().UnixMilli() +} + +// Canonical returns UTC time with no monotonic component. +// Stripping the monotonic component is for time equality. +// See https://github.com/tendermint/tendermint/pull/2203#discussion_r215064334 +func Canonical(t time.Time) time.Time { + return t.Round(0).UTC() +} + +// Ask for consensus sync to another peer +// The consensus will response with a list of votes and possible proposal +type ConsensusSyncRequest struct { + Height uint64 + Round uint32 + HasProposal uint8 // whether the proposal is received + PrevotesBitmap []uint64 + PrecommitsBitmap []uint64 +} + +// Internal struct to request async +type consensusSyncRequestAsync struct { + req *ConsensusSyncRequest + respChan chan []interface{} +} + +type ConsensusSyncResponse struct { + IsCommited uint8 // whether return a commited block or proposal/vote messages + MessageData [][]byte +} + +func (csr *ConsensusSyncRequest) ValidateBasic() error { + return nil +} diff --git a/consensus/tendermint/consensus/types_test.go b/consensus/tendermint/consensus/types_test.go new file mode 100644 index 000000000000..37ec0b659d34 --- /dev/null +++ b/consensus/tendermint/consensus/types_test.go @@ -0,0 +1,264 @@ +package consensus + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/stretchr/testify/assert" +) + +func TestSerdeCommitSig(t *testing.T) { + c := CommitSig{ + BlockIDFlag: BlockIDFlagCommit, + ValidatorAddress: common.Address{}, + TimestampMs: 1133423, + Signature: []byte{}, + } + + data, err := rlp.EncodeToBytes(c) + assert.NoError(t, err) + nc := CommitSig{} + err = rlp.DecodeBytes(data, &nc) + assert.NoError(t, err) + assert.Equal(t, c, nc) +} + +func TestSerdeCommit(t *testing.T) { + c := CommitSig{ + BlockIDFlag: BlockIDFlagCommit, + ValidatorAddress: common.Address{}, + TimestampMs: 1133423, + Signature: []byte{}, + } + cm := NewCommit(5, 6, common.Hash{}, []CommitSig{c}) + + data, err := rlp.EncodeToBytes(cm) + assert.NoError(t, err) + ncm := Commit{} + err = rlp.DecodeBytes(data, &ncm) + assert.NoError(t, err) + assert.Equal(t, *cm, ncm) +} + +func TestSerdeBlock(t *testing.T) { + c := CommitSig{ + BlockIDFlag: BlockIDFlagCommit, + ValidatorAddress: common.Address{}, + TimestampMs: 1133423, + Signature: []byte{}, + } + cm := NewCommit(5, 6, common.Hash{}, []CommitSig{c}) + + b := &FullBlock{Block: types.NewBlock( + &Header{ + ParentHash: common.Hash{}, + Number: big.NewInt(6), + TimeMs: 34534, + Coinbase: common.Address{}, + LastCommitHash: common.Hash{}, + Difficulty: big.NewInt(1), + Extra: []byte{}, + BaseFee: big.NewInt(2), // TODO + NextValidators: []common.Address{}, + }, + []*types.Transaction{}, + []*types.Header{}, + []*types.Receipt{}, + trie.NewStackTrie(nil)), + LastCommit: cm, + } + + var buf bytes.Buffer + err := b.EncodeRLP(&buf) + assert.NoError(t, err) + nb := FullBlock{} + bs := buf.Bytes() + err = nb.DecodeRLP(rlp.NewStream(bytes.NewReader(bs), 0)) + assert.NoError(t, err) + assert.Equal(t, b.Hash(), nb.Hash()) + // assert.Equal(t, b.Body(), nb.Body()) + assert.Equal(t, b.LastCommit, nb.LastCommit) +} + +func TestSerdeConsensusSyncRequest(t *testing.T) { + req := &ConsensusSyncRequest{ + Height: 152, + Round: 1356, + HasProposal: 1, + PrevotesBitmap: []uint64{3}, + PrecommitsBitmap: []uint64{2}, + } + + bs, _ := rlp.EncodeToBytes(req) + nreq := &ConsensusSyncRequest{} + rlp.DecodeBytes(bs, nreq) + assert.Equal(t, req, nreq) +} + +func TestVoteSignBytes(t *testing.T) { + v := Vote{ + Type: PrecommitType, + Height: 20, + Round: 2, + BlockID: common.Hash{}, + TimestampMs: 352353, + ValidatorAddress: common.Address{}, + ValidatorIndex: 3, + Signature: []byte{}, + } + + bs0 := v.VoteSignBytes("aaa") + bs1 := v.VoteSignBytes("bbb") + assert.NotEqual(t, bs0, bs1) + + v.Height = 21 + bs2 := v.VoteSignBytes("aaa") + assert.NotEqual(t, bs0, bs2) +} + +func TestSignVote(t *testing.T) { + pv := GeneratePrivValidatorLocal() + pubKey, err := pv.GetPubKey(context.Background()) + assert.NoError(t, err) + + v := Vote{ + Type: PrecommitType, + Height: 20, + Round: 2, + BlockID: common.Hash{}, + TimestampMs: 352353, + ValidatorAddress: pubKey.Address(), + ValidatorIndex: 3, + Signature: []byte{}, + } + + pv.SignVote(context.Background(), "aaa", &v) + assert.NoError(t, v.Verify("aaa", pubKey)) +} + +func TestSignProposal(t *testing.T) { + pv := GeneratePrivValidatorLocal() + pubKey, err := pv.GetPubKey(context.Background()) + assert.NoError(t, err) + + c := CommitSig{ + BlockIDFlag: BlockIDFlagCommit, + ValidatorAddress: common.Address{}, + TimestampMs: 1133423, + Signature: []byte{}, + } + + cm := NewCommit(5, 6, common.Hash{}, []CommitSig{c}) + + proposal := Proposal{ + Height: 20, + Round: 2, + POLRound: -1, + TimestampMs: 352353, + Signature: []byte{}, + Block: &FullBlock{Block: types.NewBlock( + &Header{ + ParentHash: common.Hash{}, + Number: big.NewInt(6), + TimeMs: 34534, + Coinbase: common.Address{}, + LastCommitHash: common.Hash{}, + Difficulty: big.NewInt(1), + Extra: []byte{}, + BaseFee: big.NewInt(2), // TODO + NextValidators: []common.Address{}, + }, + []*types.Transaction{}, + []*types.Header{}, + []*types.Receipt{}, + trie.NewStackTrie(nil)), + LastCommit: cm, + }, + } + + pv.SignProposal(context.Background(), "aaa", &proposal) + assert.True(t, pubKey.VerifySignature(proposal.ProposalSignBytes("aaa"), proposal.Signature)) +} + +func TestSignNilVote(t *testing.T) { + pv := GeneratePrivValidatorLocal() + pubKey, err := pv.GetPubKey(context.Background()) + assert.NoError(t, err) + + vote := &Vote{ + ValidatorAddress: pubKey.Address(), + ValidatorIndex: 0, + Height: 6, + Round: 1, + TimestampMs: 1234, + Type: PrecommitType, + BlockID: common.Hash{}, + } + + assert.NoError(t, pv.SignVote(context.Background(), "test", vote)) + + commitSig := vote.CommitSig() + assert.False(t, commitSig.ForBlock()) + + assert.True(t, pubKey.VerifySignature(vote.VoteSignBytes("test"), commitSig.Signature)) +} + +func TestSignCommitWithNil(t *testing.T) { + n := 4 + pv := make([]PrivValidator, n) + pk := make([]PubKey, n) + addrs := make([]common.Address, n) + powers := make([]int64, n) + + for i := 0; i < n; i++ { + pv[i] = GeneratePrivValidatorLocal() + p, err := pv[i].GetPubKey(context.Background()) + assert.NoError(t, err) + pk[i] = p + + powers[i] = 1 + addrs[i] = p.Address() + } + + vals := NewValidatorSet(addrs, powers, 4) + vs := NewVoteSet("test", 1, 2, PrecommitType, vals) + blockHash := common.BytesToHash([]byte{1, 2, 3}) + for i := 0; i < n; i++ { + var blockId common.Hash + if i != 0 { + blockId = blockHash + } + vote := &Vote{ + ValidatorAddress: pk[i].Address(), + ValidatorIndex: int32(i), + Height: 1, + Round: 2, + TimestampMs: 1234, + Type: PrecommitType, + BlockID: blockId, + } + + assert.NoError(t, pv[i].SignVote(context.Background(), "test", vote)) + + if i == 0 { + fmt.Println(hex.EncodeToString(vote.VoteSignBytes("test"))) + } + + added, err := vs.AddVote(vote) + assert.NoError(t, err) + assert.True(t, added) + } + + commit := vs.MakeCommit() + + assert.NoError(t, vals.VerifyCommit( + "test", blockHash, 1, commit)) +} diff --git a/consensus/tendermint/p2p/block_sync.go b/consensus/tendermint/p2p/block_sync.go new file mode 100644 index 000000000000..13ac2649906d --- /dev/null +++ b/consensus/tendermint/p2p/block_sync.go @@ -0,0 +1,113 @@ +package p2p + +import ( + "context" + "sync" + + "github.com/ethereum/go-ethereum/consensus/tendermint/consensus" + "github.com/ethereum/go-ethereum/log" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" +) + +type BlockSync struct { + h host.Host + wg sync.WaitGroup + executor consensus.BlockExecutor + blockStore consensus.BlockStore + chainState consensus.ChainState + err error + obsvC chan consensus.MsgInfo +} + +func NewBlockSync(h host.Host, chainState consensus.ChainState, blockStore consensus.BlockStore, executor consensus.BlockExecutor, obsvC chan consensus.MsgInfo) *BlockSync { + return &BlockSync{h: h, executor: executor, blockStore: blockStore, chainState: chainState, obsvC: obsvC} +} + +func (bs *BlockSync) Start(ctx context.Context) { + bs.wg.Add(1) + + go bs.runRoutine(ctx) +} + +func (bs *BlockSync) runRoutine(ctx context.Context) { + bs.err = bs.sync(ctx) + bs.wg.Done() +} + +func (bs *BlockSync) sync(ctx context.Context) error { + ps := bs.h.Network().Peers() + + var maxPeer peer.ID + var maxHeight uint64 + + for { + for _, p := range ps { + req := &HelloRequest{} + resp := &HelloResponse{} + err := SendRPC(context.Background(), bs.h, p, TopicHello, req, resp) + if err != nil { + continue + } + + log.Info("Find peer", "peer", p, "last_height", resp.LastHeight) + if resp.LastHeight > maxHeight { + maxHeight = resp.LastHeight + maxPeer = p + } else if maxHeight == 0 { + // random pick up one to sync latest message + maxPeer = p + } + } + + localLastHeight := bs.blockStore.Height() + + if maxHeight < localLastHeight { + // TODO: may return error + return nil + } else if maxHeight == localLastHeight { + break + } + + log.Info("Sycning block", "from", localLastHeight, "to", maxHeight) + + for height := localLastHeight + 1; height <= maxHeight; height++ { + req := &GetFullBlockRequest{Height: height} + var vb consensus.FullBlock + if err := SendRPC(ctx, bs.h, maxPeer, TopicFullBlock, req, &vb); err != nil { + return err + } + + if err := bs.executor.ValidateBlock(bs.chainState, &vb); err != nil { + return err + } + + if err := bs.chainState.Validators.VerifyCommit( + bs.chainState.ChainID, vb.Hash(), vb.NumberU64(), vb.Header().Commit); err != nil { + return err + } + + newChainState, err := bs.executor.ApplyBlock(ctx, bs.chainState, &vb) + if err != nil { + return err + } + + bs.blockStore.SaveBlock(&vb, vb.Header().Commit) + bs.chainState = newChainState + } + log.Info("Sycned block", "from", localLastHeight, "to", maxHeight) + + } + log.Info("Finished syncing") + + return nil +} + +func (bs *BlockSync) LastChainState() consensus.ChainState { + return bs.chainState +} + +func (bs *BlockSync) WaitDone() error { + bs.wg.Wait() + return bs.err +} diff --git a/consensus/tendermint/p2p/consensus_sync.go b/consensus/tendermint/p2p/consensus_sync.go new file mode 100644 index 000000000000..d91502897b89 --- /dev/null +++ b/consensus/tendermint/p2p/consensus_sync.go @@ -0,0 +1,79 @@ +package p2p + +import ( + "math/rand" + + "github.com/ethereum/go-ethereum/consensus/tendermint/consensus" + "github.com/ethereum/go-ethereum/log" +) + +func (s *Server) consensSyncRoutine() { + for { + select { + case <-s.ctx.Done(): + return + case localSyncReq := <-s.consensusSyncChan: + // randomly pick one peer to send + // TODO: may find the multiple peers with better information + ps := s.Host.Network().Peers() + + if len(ps) == 0 { + continue + } + + p := ps[rand.Intn(len(ps))] + + resp := &consensus.ConsensusSyncResponse{} + + // Do not block the goroutine + go func() { + log.Debug("sending consensus sync", "req", localSyncReq, "peer", p) + if err := SendRPC(s.ctx, s.Host, p, TopicConsensusSync, localSyncReq, resp); err != nil { + log.Warn("failed to send consensus sync", "err", err) + // Mark as bad peer and disconnect from peer? + return + } + + if resp.IsCommited == 1 { + if len(resp.MessageData) != 1 { + // Mark as bad peer and disconnect from peer? + log.Warn("failed to decode committed block", "err", "unexpected data received") + return + } + + block := &consensus.FullBlock{} + err := block.DecodeFromRLPBytes(resp.MessageData[0]) + if err != nil { + log.Warn("failed to decode committed block", "err", err) + // Mark as bad peer and disconnect from peer? + return + } + s.consensusState.ProcessCommittedBlock(block) + return + } + + for _, msgData := range resp.MessageData { + msg, err := decode(msgData) + if err != nil { + log.Debug("cannot decode", "msgData", msgData) + // Mark as bad peer and disconnect from peer? + continue + } + + log.Debug("add consensus sync message", "msg", msg) + + // TODO: add in goroutine if full + switch m := msg.(type) { + case *consensus.Proposal: + s.obsvC <- consensus.MsgInfo{Msg: &consensus.ProposalMessage{Proposal: m}, PeerID: string(p)} + case *consensus.Vote: + s.obsvC <- consensus.MsgInfo{Msg: &consensus.VoteMessage{Vote: m}, PeerID: string(p)} + default: + // Mark as bad peer and disconnect from peer? + continue + } + } + }() + } + } +} diff --git a/consensus/tendermint/p2p/decoder_test.go b/consensus/tendermint/p2p/decoder_test.go new file mode 100644 index 000000000000..d03798f3f6d5 --- /dev/null +++ b/consensus/tendermint/p2p/decoder_test.go @@ -0,0 +1,74 @@ +package p2p + +import ( + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/tendermint/consensus" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie" + "github.com/stretchr/testify/assert" +) + +func TestSerdeProposal(t *testing.T) { + c := consensus.CommitSig{ + BlockIDFlag: consensus.BlockIDFlagCommit, + ValidatorAddress: common.Address{}, + TimestampMs: 1133423, + Signature: []byte{}, + } + cm := consensus.NewCommit(5, 6, common.Hash{}, []consensus.CommitSig{c}) + + p := &consensus.Proposal{ + Height: 4, + Round: 3, + POLRound: -1, + TimestampMs: time.Now().UnixMilli(), + Signature: []byte{'1'}, + Block: &consensus.FullBlock{Block: types.NewBlock( + &consensus.Header{ + ParentHash: common.Hash{}, + Number: big.NewInt(6), + TimeMs: 34534, + Coinbase: common.Address{}, + LastCommitHash: common.Hash{}, + Difficulty: big.NewInt(2), + Extra: []byte{}, + BaseFee: big.NewInt(7), + NextValidators: []common.Address{}, + }, + []*types.Transaction{}, + []*types.Header{}, + []*types.Receipt{}, + trie.NewStackTrie(nil), + ), + LastCommit: cm, + }} + + data, err := encodeProposal(p) + assert.NoError(t, err) + np, err := decodeProposal(data) + assert.NoError(t, err) + assert.Equal(t, p.Block.Hash(), np.(*consensus.Proposal).Block.Hash()) +} + +func TestSerdeVote(t *testing.T) { + v := &consensus.Vote{ + Type: consensus.PrevoteType, + Height: 4, + Round: 3, + TimestampMs: uint64(time.Now().UnixMilli()), + BlockID: common.BytesToHash([]byte{1, 2}), + ValidatorAddress: common.BigToAddress(big.NewInt(12345)), + ValidatorIndex: 5, + Signature: []byte{'2'}, + } + + data, err := encodeVote(v) + assert.NoError(t, err) + nv, err := decodeVote(data) + assert.NoError(t, err) + assert.Equal(t, v, nv) +} diff --git a/consensus/tendermint/p2p/p2p.go b/consensus/tendermint/p2p/p2p.go new file mode 100644 index 000000000000..9df24af90438 --- /dev/null +++ b/consensus/tendermint/p2p/p2p.go @@ -0,0 +1,695 @@ +package p2p + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "io" + "strings" + "time" + + "github.com/ethereum/go-ethereum/consensus/tendermint/consensus" + "github.com/prometheus/client_golang/prometheus" + + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-quic-transport/integrationtests/stream" + "github.com/multiformats/go-multiaddr" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/libp2p/go-libp2p" + connmgr "github.com/libp2p/go-libp2p-connmgr" + "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/protocol" + "github.com/libp2p/go-libp2p-core/routing" + dht "github.com/libp2p/go-libp2p-kad-dht" + pubsub "github.com/libp2p/go-libp2p-pubsub" + libp2pquic "github.com/libp2p/go-libp2p-quic-transport" + libp2ptls "github.com/libp2p/go-libp2p-tls" + "go.uber.org/zap" +) + +var ( + p2pHeartbeatsSent = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "p2p_heartbeats_sent_total", + Help: "Total number of p2p heartbeats sent", + }) + p2pMessagesSent = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "p2p_broadcast_messages_sent_total", + Help: "Total number of p2p pubsub broadcast messages sent", + }) + p2pMessagesReceived = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "p2p_broadcast_messages_received_total", + Help: "Total number of p2p pubsub broadcast messages received", + }, []string{"type"}) + decoder = make(map[byte]func([]byte) (interface{}, error)) +) + +const ( + MsgProposal = 0x01 + MsgVote = 0x02 + MsgVerifiedBlock = 0x03 + MsgHelloRequest = 0x04 + MsgHelloResponse = 0x05 + TopicHello = "/mpbft/dev/hello/1.0.0" + TopicFullBlock = "/mpbft/dev/fullblock/1.0.0" + TopicConsensusSync = "/mpbft/dev/consensus_sync/1.0.0" +) + +func init() { + prometheus.MustRegister(p2pHeartbeatsSent) + prometheus.MustRegister(p2pMessagesSent) + prometheus.MustRegister(p2pMessagesReceived) + decoder[1] = decodeProposal + decoder[2] = decodeVote + decoder[3] = decodeFullBlock + decoder[4] = decodeHelloRequest + decoder[5] = decodeHelloResponse + decoder[6] = decodeGetFullBlockRequest +} + +type HelloRequest struct { + LastHeight uint64 +} + +type HelloResponse struct { + LastHeight uint64 +} + +type GetLatestMessagesRequest struct { +} + +type GetLatestMessagesResponse struct { + MessageData [][]byte +} + +type GetFullBlockRequest struct { + Height uint64 +} + +func decodeHelloRequest(data []byte) (interface{}, error) { + var h HelloRequest + err := rlp.DecodeBytes(data, &h) + return h, err +} + +func decodeHelloResponse(data []byte) (interface{}, error) { + var h HelloResponse + err := rlp.DecodeBytes(data, &h) + return h, err +} + +func (req *HelloRequest) ValidateBasic() error { + return nil +} + +func (req *HelloResponse) ValidateBasic() error { + return nil +} + +func decodeGetFullBlockRequest(data []byte) (interface{}, error) { + var req GetFullBlockRequest + err := rlp.DecodeBytes(data, &req) + return req, err +} + +func decodeVote(data []byte) (interface{}, error) { + v := &consensus.Vote{} + err := v.DecodeRLP(rlp.NewStream(bytes.NewReader(data), 0)) + if err != nil { + return nil, err + } + return v, v.ValidateBasic() +} + +func encodeVote(v *consensus.Vote) ([]byte, error) { + var buf bytes.Buffer + if err := v.EncodeRLP(&buf); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func decodeProposal(data []byte) (interface{}, error) { + p := &consensus.Proposal{} + err := p.DecodeRLP(rlp.NewStream(bytes.NewReader(data), 0)) + if err != nil { + return nil, err + } + return p, p.ValidateBasic() +} + +func encodeProposal(p *consensus.Proposal) ([]byte, error) { + var buf bytes.Buffer + if err := p.EncodeRLP(&buf); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func decodeFullBlock(data []byte) (interface{}, error) { + var b consensus.FullBlock + err := b.DecodeFromRLPBytes(data) + return b, err +} + +func decode(data []byte) (interface{}, error) { + if len(data) == 0 { + return nil, fmt.Errorf("incorrect msg") + } + + if decoder[data[0]] == nil { + return nil, fmt.Errorf("deocder not found") + } + + return decoder[data[0]](data[1:]) +} + +func encodeRaw(msg interface{}) ([]byte, error) { + var data []byte + var err error + switch m := msg.(type) { + case *consensus.Proposal: + data, err = encodeProposal(m) + case *consensus.Vote: + data, err = encodeVote(m) + case *consensus.FullBlock: + data, err = rlp.EncodeToBytes(m) + case *HelloRequest: + data, err = rlp.EncodeToBytes(m) + case *HelloResponse: + data, err = rlp.EncodeToBytes(m) + } + return data, err +} + +func encode(msg interface{}) ([]byte, error) { + var data []byte + var err error + var typeData []byte + switch msg.(type) { + case *consensus.Proposal: + typeData = []byte{1} + case *consensus.Vote: + typeData = []byte{2} + case *consensus.FullBlock: + typeData = []byte{3} + case *HelloRequest: + typeData = []byte{4} + case *HelloResponse: + typeData = []byte{5} + case *GetFullBlockRequest: + typeData = []byte{6} + } + + data, err = encodeRaw(msg) + + if err != nil { + return nil, err + } + return append(typeData, data...), nil +} + +func WriteMsgWithPrependedSize(stream network.Stream, msg []byte) error { + sizeBytes := make([]byte, 4) + binary.BigEndian.PutUint32(sizeBytes, uint32(len(msg))) + n, err := stream.Write(sizeBytes) + if err != nil { + return err + } + if n != len(sizeBytes) { + return fmt.Errorf("not fully write") + } + + n, err = stream.Write(msg) + if err != nil { + return err + } + if n != len(msg) { + return fmt.Errorf("not fully write") + } + return nil +} + +func ReadMsgWithPrependedSize(stream stream.Stream) ([]byte, error) { + sizeBytes := make([]byte, 4) + _, err := io.ReadFull(stream, sizeBytes) + if err != nil { + return nil, err + } + + size := binary.BigEndian.Uint32(sizeBytes) + + msg := make([]byte, size) + _, err = io.ReadFull(stream, msg) + return msg, err +} + +func Send(ctx context.Context, h host.Host, peer peer.ID, topic string, msg interface{}) (stream.Stream, error) { + data, err := rlp.EncodeToBytes(msg) + if err != nil { + return nil, err + } + + stream, err := h.NewStream(ctx, peer, protocol.ID(topic)) + if err != nil { + return nil, err + } + + err = WriteMsgWithPrependedSize(stream, data) + if err != nil { + stream.Close() + return nil, err + } + + if err := stream.CloseWrite(); err != nil { + stream.Reset() + return nil, err + } + return stream, err +} + +func SendRPC(ctx context.Context, h host.Host, peer peer.ID, topic string, req interface{}, resp interface{}) error { + s, err := Send(ctx, h, peer, topic, req) + if err != nil { + return err + } + + // TODO: timeout? + data, err := ReadMsgWithPrependedSize(s) + if err != nil { + return err + } + + return rlp.DecodeBytes(data, resp) +} + +type Server struct { + Host host.Host + ctx context.Context + consensusState *consensus.ConsensusState + consensusSyncChan chan *consensus.ConsensusSyncRequest + blockStore consensus.BlockStore + obsvC chan consensus.MsgInfo + sendC chan consensus.Message + priv crypto.PrivKey + port uint + networkID string + nodeName string + rootCtxCancel context.CancelFunc +} + +var TestMode bool + +func NewP2PServer( + ctx context.Context, + blockStore consensus.BlockStore, + obsvC chan consensus.MsgInfo, + sendC chan consensus.Message, + priv crypto.PrivKey, + port uint, + networkID string, + bootstrapPeers string, + nodeName string, + rootCtxCancel context.CancelFunc, +) (*Server, error) { + h, err := libp2p.New(ctx, + // Use the keypair we generated + libp2p.Identity(priv), + + // Multiple listen addresses + libp2p.ListenAddrStrings( + // Listen on QUIC only. + // https://github.com/libp2p/go-libp2p/issues/688 + fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic", port), + fmt.Sprintf("/ip6/::/udp/%d/quic", port), + ), + + // Enable TLS security as the only security protocol. + libp2p.Security(libp2ptls.ID, libp2ptls.New), + + // Enable QUIC transport as the only transport. + libp2p.Transport(libp2pquic.NewTransport), + + // Let's prevent our peer from having too many + // connections by attaching a connection manager. + libp2p.ConnectionManager(connmgr.NewConnManager( + 100, // Lowwater + 400, // HighWater, + time.Minute, // GracePeriod + )), + + // Let this host use the DHT to find other hosts + libp2p.Routing(func(h host.Host) (routing.PeerRouting, error) { + // TODO(leo): Persistent data store (i.e. address book) + idht, err := dht.New(ctx, h, dht.Mode(dht.ModeServer), + // TODO(leo): This intentionally makes us incompatible with the global IPFS DHT + dht.ProtocolPrefix(protocol.ID("/"+networkID)), + ) + return idht, err + }), + ) + + if err != nil { + return nil, err + } + + log.Info("Connecting to bootstrap peers", "bootstrap_peers", bootstrapPeers) + + // Add our own bootstrap nodes + + // Count number of successful connection attempts. If we fail to connect to any bootstrap peer, kill + // the service and have supervisor retry it. + successes := 0 + // Are we a bootstrap node? If so, it's okay to not have any peers. + bootstrapNode := false + + for _, addr := range strings.Split(bootstrapPeers, ",") { + if addr == "" { + continue + } + ma, err := multiaddr.NewMultiaddr(addr) + if err != nil { + log.Error("Invalid bootstrap address", "peer", addr, "err", err) + continue + } + pi, err := peer.AddrInfoFromP2pAddr(ma) + if err != nil { + log.Error("Invalid bootstrap address", "peer", addr, "err", err) + continue + } + + if pi.ID == h.ID() { + log.Info("We're a bootstrap node") + bootstrapNode = true + continue + } + + if err = h.Connect(ctx, *pi); err != nil { + log.Error("Failed to connect to bootstrap peer", "peer", addr, "err", err) + } else { + successes += 1 + } + } + + h.SetStreamHandler(TopicHello, func(stream network.Stream) { + defer stream.Close() + + data, err := ReadMsgWithPrependedSize(stream) + if err != nil { + return + } + + var msg HelloRequest + + err = rlp.DecodeBytes(data, &msg) + if err != nil { + return + } + + log.Debug("received hello", + "payload", data, + "raw", data) + + resp, err := rlp.EncodeToBytes(&HelloResponse{blockStore.Height()}) + if err != nil { + return + } + + err = WriteMsgWithPrependedSize(stream, resp) + if err != nil { + return + } + }) + + h.SetStreamHandler(TopicFullBlock, func(stream network.Stream) { + defer stream.Close() + + data, err := ReadMsgWithPrependedSize(stream) + if err != nil { + return + } + + var msg GetFullBlockRequest + + err = rlp.DecodeBytes(data, &msg) + if err != nil { + return + } + + log.Debug("received fullblock_req", + "payload", data, + "raw", data) + + // TODO: check height correctness + vb := blockStore.LoadBlock(msg.Height) + commit := blockStore.LoadBlockCommit(msg.Height) + vb = vb.WithCommit(commit) + + resp, err := vb.EncodeToRLPBytes() + if err != nil { + return + } + + err = WriteMsgWithPrependedSize(stream, resp) + if err != nil { + return + } + }) + + // TODO: continually reconnect to bootstrap nodes? + if !TestMode && successes == 0 && !bootstrapNode { + return nil, fmt.Errorf("failed to connect to any bootstrap peer") + } else { + log.Info("Connected to bootstrap peers", "num", successes) + } + + log.Info("Node has been started", "peer_id", h.ID().String(), + "addrs", fmt.Sprintf("%v", h.Addrs())) + + return &Server{ + Host: h, + ctx: ctx, + consensusSyncChan: make(chan *consensus.ConsensusSyncRequest), + blockStore: blockStore, + obsvC: obsvC, + sendC: sendC, + priv: priv, + port: port, + networkID: networkID, + nodeName: nodeName, + rootCtxCancel: rootCtxCancel, + }, nil +} + +func (server *Server) Run(ctx context.Context) error { + + var err error + + defer func() { + // TODO: libp2p cannot be cleanly restarted (https://github.com/libp2p/go-libp2p/issues/992) + log.Error("p2p routine has exited, cancelling root context...", "err", err) + server.rootCtxCancel() + }() + + topic := fmt.Sprintf("%s/%s", server.networkID, "broadcast") + + log.Info("Subscribing pubsub topic", "topic", topic) + ps, err := pubsub.NewGossipSub(ctx, server.Host) + if err != nil { + panic(err) + } + + th, err := ps.Join(topic) + if err != nil { + return fmt.Errorf("failed to join topic: %w", err) + } + + sub, err := th.Subscribe() + if err != nil { + return fmt.Errorf("failed to subscribe topic: %w", err) + } + + // TODO: create a thread to send heartbeat? + + // h.Network().Notify(&network.NotifyBundle{ConnectedF: func(net network.Network, conn network.Conn) { + // // Must be in goroutine to prevent blocking the callback + // go func() { + // s, err := h.NewStream(context.Background(), conn.RemotePeer(), "/go-minimal-pbft/steam/1.0.0") + // if err != nil { + // log.Error("Cannot create stream", "peer", conn.RemotePeer(), "err", err) + // } + + // req, err := encode(&HelloRequest{}) + // if err != nil { + // log.Error("Cannot create stream", "peer", conn.RemotePeer(), "err", err) + // } + + // WriteMsgWithPrependedSize(s, req) + + // defer s.Close() + // }() + // }}) + + go func() { + for { + select { + case <-ctx.Done(): + return + case msg := <-server.sendC: + var err error + var data []byte + switch m := (msg).(type) { + case *consensus.ProposalMessage: + data, err = encode(m.Proposal) + if err == nil { + err = th.Publish(ctx, data) + p2pMessagesSent.Inc() + } + case *consensus.VoteMessage: + data, err = encode(m.Vote) + if err == nil { + err = th.Publish(ctx, data) + p2pMessagesSent.Inc() + } + case *consensus.ConsensusSyncRequest: + server.consensusSyncChan <- m + default: + log.Error("unrecognized data to sent") + } + + if err != nil { + log.Error("failed to publish message from queue", zap.Error(err)) + } + } + } + }() + + for { + envelope, err := sub.Next(ctx) + if err != nil { + return fmt.Errorf("failed to receive pubsub message: %w", err) + } + + if envelope.GetFrom() == server.Host.ID() { + log.Trace("received message from ourselves, ignoring", + "payload", envelope.Data) + p2pMessagesReceived.WithLabelValues("loopback").Inc() + continue + } + + msg, err := decode(envelope.Data) + + if err != nil { + log.Info("received invalid message", + "err", err, + "data", envelope.Data, + "from", envelope.GetFrom().String()) + p2pMessagesReceived.WithLabelValues("invalid").Inc() + continue + } + + log.Debug("received message", + "payload", msg, + "raw", envelope.Data, + "from", envelope.GetFrom().String()) + + switch m := msg.(type) { + case *consensus.Proposal: + server.obsvC <- consensus.MsgInfo{Msg: &consensus.ProposalMessage{Proposal: m}, PeerID: string(envelope.GetFrom())} + p2pMessagesReceived.WithLabelValues("observation").Inc() + case *consensus.Vote: + server.obsvC <- consensus.MsgInfo{Msg: &consensus.VoteMessage{Vote: m}, PeerID: string(envelope.GetFrom())} + p2pMessagesReceived.WithLabelValues("observation").Inc() + case *HelloRequest: + case *HelloResponse: + case *GetFullBlockRequest: + default: + p2pMessagesReceived.WithLabelValues("unknown").Inc() + log.Warn("received unknown message type (running outdated software?)", + "payload", msg, + "raw", envelope.Data, + "from", envelope.GetFrom().String()) + } + } +} + +func (server *Server) SetConsensusState(cs *consensus.ConsensusState) { + server.consensusState = cs + + server.Host.SetStreamHandler(TopicConsensusSync, func(stream network.Stream) { + defer stream.Close() + + data, err := ReadMsgWithPrependedSize(stream) + if err != nil { + return + } + + var req consensus.ConsensusSyncRequest + + err = rlp.DecodeBytes(data, &req) + if err != nil { + return + } + + log.Debug("received consensus_sync_req", + "req", &req, + "payload", data) + + msgs, err := cs.ProcessSyncRequest(&req) + + if err != nil { + return + } + + resp := consensus.ConsensusSyncResponse{} + + if len(msgs) == 1 { + fb, ok0 := msgs[0].(*consensus.FullBlock) + if ok0 { + resp.IsCommited = 1 + bs0, err := fb.EncodeToRLPBytes() + if err != nil { + return + } + resp.MessageData = append(resp.MessageData, bs0) + } + } + + if resp.IsCommited == 0 { + for _, lmsg := range msgs { + var data []byte + var err error + + switch m := (lmsg).(type) { + case *consensus.ProposalMessage: + data, err = encode(m.Proposal) + case *consensus.VoteMessage: + data, err = encode(m.Vote) + } + if err != nil { + return + } + resp.MessageData = append(resp.MessageData, data) + } + } + + respData, err := rlp.EncodeToBytes(&resp) + if err != nil { + return + } + + err = WriteMsgWithPrependedSize(stream, respData) + if err != nil { + return + } + }) + + go server.consensSyncRoutine() +} diff --git a/consensus/tendermint/priv_validator.go b/consensus/tendermint/priv_validator.go index 00df84ee21d3..8ff973dbbebd 100644 --- a/consensus/tendermint/priv_validator.go +++ b/consensus/tendermint/priv_validator.go @@ -3,9 +3,9 @@ package tendermint import ( "context" - pbft "github.com/QuarkChain/go-minimal-pbft/consensus" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" + pbft "github.com/ethereum/go-ethereum/consensus/tendermint/consensus" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/consensus/tendermint/tendermint.go b/consensus/tendermint/tendermint.go index 3f22374eb130..5669787de03f 100644 --- a/consensus/tendermint/tendermint.go +++ b/consensus/tendermint/tendermint.go @@ -27,14 +27,14 @@ import ( "sync" "time" - pbftconsensus "github.com/QuarkChain/go-minimal-pbft/consensus" - libp2p "github.com/QuarkChain/go-minimal-pbft/p2p" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/tendermint/adapter" + pbftconsensus "github.com/ethereum/go-ethereum/consensus/tendermint/consensus" "github.com/ethereum/go-ethereum/consensus/tendermint/gov" + libp2p "github.com/ethereum/go-ethereum/consensus/tendermint/p2p" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -184,7 +184,6 @@ func (c *Tendermint) Init(chain *core.BlockChain, makeBlock func(parent common.H last, current, c.config.Epoch, - int64(c.config.ProposerRepetition), ) // consensus diff --git a/go.mod b/go.mod index 5788b8049ebf..c071abf0c3cf 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,10 @@ module github.com/ethereum/go-ethereum go 1.15 require ( + github.com/Azure/azure-pipeline-go v0.2.2 // indirect github.com/Azure/azure-storage-blob-go v0.7.0 - github.com/QuarkChain/go-minimal-pbft v0.0.0-20220310232643-dcc46ad089a1 + github.com/Azure/go-autorest/autorest/adal v0.8.0 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect github.com/VictoriaMetrics/fastcache v1.6.0 github.com/aws/aws-sdk-go-v2 v1.2.0 github.com/aws/aws-sdk-go-v2/config v1.1.1 @@ -12,19 +14,25 @@ require ( github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1 github.com/btcsuite/btcd v0.22.0-beta github.com/cespare/cp v0.1.0 + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cloudflare/cloudflare-go v0.14.0 github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v1.8.0 + github.com/deepmap/oapi-codegen v1.8.2 // indirect github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48 github.com/edsrzf/mmap-go v1.0.0 github.com/fatih/color v1.9.0 github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 + github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-stack/stack v1.8.0 + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 github.com/golang/snappy v0.0.4 + github.com/google/go-cmp v0.5.6 // indirect github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.2 @@ -36,32 +44,60 @@ require ( github.com/huin/goupnp v1.0.2 github.com/influxdata/influxdb v1.8.3 github.com/influxdata/influxdb-client-go/v2 v2.4.0 + github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect github.com/jackpal/go-nat-pmp v1.0.2 github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e github.com/julienschmidt/httprouter v1.3.0 github.com/karalabe/usb v0.0.2 + github.com/klauspost/compress v1.13.6 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/libp2p/go-libp2p v0.14.4 + github.com/libp2p/go-libp2p-connmgr v0.2.4 github.com/libp2p/go-libp2p-core v0.8.6 + github.com/libp2p/go-libp2p-kad-dht v0.12.2 + github.com/libp2p/go-libp2p-pubsub v0.5.0 + github.com/libp2p/go-libp2p-quic-transport v0.11.2 + github.com/libp2p/go-libp2p-tls v0.1.3 github.com/mattn/go-colorable v0.1.12 github.com/mattn/go-isatty v0.0.14 + github.com/miekg/dns v1.1.43 // indirect + github.com/mitchellh/go-homedir v1.1.0 + github.com/mitchellh/mapstructure v1.4.2 // indirect github.com/multiformats/go-multiaddr v0.3.3 + github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/olekukonko/tablewriter v0.0.5 + github.com/onsi/gomega v1.16.0 // indirect github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 + github.com/prometheus/client_golang v1.11.0 + github.com/prometheus/common v0.30.0 // indirect + github.com/prometheus/procfs v0.7.3 // indirect github.com/prometheus/tsdb v0.7.1 github.com/rjeczalik/notify v0.9.1 github.com/rs/cors v1.7.0 github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible + github.com/spf13/cobra v0.0.5 + github.com/spf13/viper v1.3.2 github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 + github.com/tklauser/go-sysconf v0.3.9 // indirect github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.7.0 // indirect + go.uber.org/zap v1.19.1 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 + golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect + golang.org/x/net v0.0.0-20211005001312-d4b1ae081e3b // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20211013075003-97ac67df715c golang.org/x/text v0.3.7 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba golang.org/x/tools v0.1.7 + google.golang.org/grpc v1.41.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 gopkg.in/urfave/cli.v1 v1.20.0 + gotest.tools v2.2.0+incompatible // indirect ) diff --git a/go.sum b/go.sum index 9a252fd83960..af836be8d71d 100644 --- a/go.sum +++ b/go.sum @@ -17,16 +17,6 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -36,8 +26,6 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7 cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -77,25 +65,15 @@ github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1Gn github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 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/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/QuarkChain/go-minimal-pbft v0.0.0-20220307211413-49a75ab4c3f2 h1:ChaJ8RHQxVn9aSm5ttGz15GEFKzwrhC52GKZnT1U7Ec= -github.com/QuarkChain/go-minimal-pbft v0.0.0-20220307211413-49a75ab4c3f2/go.mod h1:nC/gDMz8G2h+K5KGDiEwEGJ//D70G8mwOBgHraRUab8= -github.com/QuarkChain/go-minimal-pbft v0.0.0-20220308184838-5baea63c2394 h1:HaaWeP1ut3XJ1/dSQY9/dRX+CSbK0ZGYd8twCF/wCV0= -github.com/QuarkChain/go-minimal-pbft v0.0.0-20220308184838-5baea63c2394/go.mod h1:nC/gDMz8G2h+K5KGDiEwEGJ//D70G8mwOBgHraRUab8= -github.com/QuarkChain/go-minimal-pbft v0.0.0-20220309025448-1a13edd735c1 h1:z/d1/Numk5zfU+VJGtSJ7j200zs7LoWRAWldHZrwTQI= -github.com/QuarkChain/go-minimal-pbft v0.0.0-20220309025448-1a13edd735c1/go.mod h1:nC/gDMz8G2h+K5KGDiEwEGJ//D70G8mwOBgHraRUab8= -github.com/QuarkChain/go-minimal-pbft v0.0.0-20220309221209-4a8b73a5d901 h1:2FhIbg26oKAl10utfICfNpnEUzgeSJRiYTXN9DrZXGM= -github.com/QuarkChain/go-minimal-pbft v0.0.0-20220309221209-4a8b73a5d901/go.mod h1:nC/gDMz8G2h+K5KGDiEwEGJ//D70G8mwOBgHraRUab8= -github.com/QuarkChain/go-minimal-pbft v0.0.0-20220310232643-dcc46ad089a1 h1:/JDSKKPtlsXOb4mkZlALE4SRPfwsWkya7chHvoxuZHA= -github.com/QuarkChain/go-minimal-pbft v0.0.0-20220310232643-dcc46ad089a1/go.mod h1:nC/gDMz8G2h+K5KGDiEwEGJ//D70G8mwOBgHraRUab8= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= @@ -121,7 +99,6 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -153,7 +130,6 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce 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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= @@ -198,9 +174,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudflare/cloudflare-go v0.14.0 h1:gFqGlGl/5f9UGXAaKapCGUfaTCgRKKnzu2VvzMZlOFA= github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= @@ -214,11 +188,9 @@ github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmf github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= @@ -266,13 +238,9 @@ github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4s github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/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/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/go-ethereum v1.10.16/go.mod h1:Anj6cxczl+AHy63o4X9O8yWNHuN5wMpfb8MAnHkWn7Y= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= @@ -314,7 +282,6 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 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= @@ -328,7 +295,6 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -356,7 +322,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -375,7 +340,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -393,7 +357,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -409,8 +372,6 @@ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF 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= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -418,18 +379,10 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -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-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/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= @@ -437,7 +390,6 @@ github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk 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= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -457,18 +409,13 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= @@ -476,7 +423,6 @@ github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+ 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-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -488,14 +434,12 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +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/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= 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.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= @@ -507,7 +451,7 @@ github.com/huin/goupnp v1.0.2 h1:RfGLP+h3mvisuWEyybxNq5Eft3NWhHLPeUN72kpKZoI= github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= 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/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= github.com/influxdata/influxdb v1.8.3 h1:WEypI1BQFTT4teLM+1qkEcvUi0dAvopAI/ir0vAiBg8= @@ -630,7 +574,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d h1:68u9r4wEvL3gYg2jvAOgROwZ3H+Y3hIDk4tbbmIjcYQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -879,8 +822,8 @@ github.com/lucas-clemente/quic-go v0.21.2 h1:8LqqL7nBQFDUINadW0fHV/xSaCQJgmJC0Gv github.com/lucas-clemente/quic-go v0.21.2/go.mod h1:vF5M1XqhBAHgbjKcJOXY3JZz3GP0T3FQhz/uyOUS38Q= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -902,7 +845,6 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO 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.4/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.7/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.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= @@ -915,7 +857,6 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.5/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.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= @@ -932,7 +873,6 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyex github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= @@ -954,8 +894,8 @@ github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= @@ -1093,9 +1033,8 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= @@ -1108,12 +1047,10 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= @@ -1167,7 +1104,6 @@ github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= @@ -1213,22 +1149,20 @@ github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+ github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= @@ -1245,16 +1179,13 @@ 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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= 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/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= -github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -1293,9 +1224,6 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= @@ -1305,7 +1233,6 @@ go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= @@ -1332,7 +1259,6 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= @@ -1353,9 +1279,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/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-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1368,7 +1292,6 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1398,7 +1321,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 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 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= 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= @@ -1409,8 +1331,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -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 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1457,9 +1377,7 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R 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= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -1467,7 +1385,6 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211005001312-d4b1ae081e3b h1:SXy8Ld8oKlcogOvUAh0J5Pm5RKzgYBMMxLxt6n5XW50= @@ -1479,17 +1396,7 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1533,11 +1440,9 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1548,7 +1453,6 @@ golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1566,42 +1470,28 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w 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-20200826173525-f9321e4c35a6/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-20201214210602-f9fddec55a1e/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/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-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/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-20211013075003-97ac67df715c h1:taxlMj0D/1sOAuv/CbSD+MMDof2vbyPTqz5FNYKpXt8= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1646,13 +1536,11 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1682,18 +1570,10 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -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.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= @@ -1728,18 +1608,6 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1748,7 +1616,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 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-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= @@ -1786,28 +1653,6 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= 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= @@ -1831,19 +1676,9 @@ google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 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.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1869,8 +1704,6 @@ 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/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0=