From f809a0f1a2e5cf3b74a790ae788409dbf07ef957 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Thu, 13 Oct 2022 10:15:55 +0200 Subject: [PATCH] implement vote extensions --- consensus/byzantine_test.go | 70 ++- consensus/common_test.go | 468 ++++++++-------- consensus/invalid_test.go | 3 +- consensus/mempool_test.go | 25 +- consensus/metrics.gen.go | 7 + consensus/metrics.go | 14 + consensus/reactor_test.go | 73 +-- consensus/replay_test.go | 84 ++- consensus/state.go | 83 ++- consensus/state_test.go | 710 ++++++++++++++++++++----- evidence/verify_test.go | 16 +- internal/test/block.go | 2 +- internal/test/commit.go | 13 +- internal/test/genesis.go | 15 +- internal/test/validator.go | 7 +- node/node_test.go | 10 +- privval/file.go | 78 ++- proto/tendermint/types/canonical.pb.go | 345 ++++++++++-- proto/tendermint/types/canonical.proto | 9 + proto/tendermint/types/params.pb.go | 496 ++++++++++++----- proto/tendermint/types/params.proto | 27 +- proto/tendermint/types/types.pb.go | 292 ++++++---- proto/tendermint/types/types.proto | 11 +- proxy/mocks/app_conn_consensus.go | 46 ++ state/execution.go | 115 +++- state/execution_test.go | 15 +- state/helpers_test.go | 44 +- state/mocks/block_store.go | 5 + state/services.go | 1 + state/validation_test.go | 26 +- store/store.go | 132 +++-- store/store_test.go | 199 +++++-- test/e2e/runner/evidence.go | 12 +- types/block.go | 377 ++++++++++++- types/block_test.go | 162 +++++- types/canonical.go | 14 +- types/evidence_test.go | 9 +- types/params.go | 74 ++- types/part_set.go | 6 + types/priv_validator.go | 14 + types/test_util.go | 6 +- types/validator_set_test.go | 15 +- types/vote.go | 245 +++++++-- types/vote_set.go | 50 +- types/vote_set_test.go | 28 +- types/vote_test.go | 10 +- 46 files changed, 3400 insertions(+), 1063 deletions(-) diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index f134af5ac..18ae1d909 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -15,6 +15,7 @@ import ( dbm "github.com/tendermint/tm-db" abcicli "github.com/tendermint/tendermint/abci/client" + "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/evidence" "github.com/tendermint/tendermint/libs/log" @@ -23,7 +24,7 @@ import ( mempl "github.com/tendermint/tendermint/mempool" "github.com/tendermint/tendermint/proxy" - cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/config" mempoolv0 "github.com/tendermint/tendermint/mempool/v0" mempoolv1 "github.com/tendermint/tendermint/mempool/v1" "github.com/tendermint/tendermint/p2p" @@ -42,10 +43,8 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { const byzantineNode = 0 const prevoteHeight = int64(2) testName := "consensus_byzantine_test" - tickerFunc := newMockTickerFunc(true) - appFunc := newKVStore - genDoc, privVals := randGenesisDoc(nValidators, false, 30) + state, privVals := makeGenesisState(t, genesisStateArgs{validators: nValidators}) css := make([]*State, nValidators) for i := 0; i < nValidators; i++ { @@ -54,11 +53,10 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { stateStore := sm.NewStore(stateDB, sm.StoreOptions{ DiscardFinalizeBlockResponses: false, }) - state, _ := stateStore.LoadFromDBOrGenesisDoc(genDoc) - thisConfig := ResetConfig(fmt.Sprintf("%s_%d", testName, i)) - defer os.RemoveAll(thisConfig.RootDir) - ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal - app := appFunc() + cfg := ResetConfig(fmt.Sprintf("%s_%d", testName, i)) + defer os.RemoveAll(cfg.RootDir) + ensureDir(path.Dir(cfg.Consensus.WalFile()), 0700) // dir for wal + app := kvstore.NewInMemoryApplication() vals := types.TM2PB.ValidatorUpdates(state.Validators) _, err := app.InitChain(context.Background(), &abci.RequestInitChain{Validators: vals}) require.NoError(t, err) @@ -74,16 +72,16 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { // Make Mempool var mempool mempl.Mempool - switch thisConfig.Mempool.Version { - case cfg.MempoolV0: - mempool = mempoolv0.NewCListMempool(config.Mempool, + switch cfg.Mempool.Version { + case config.MempoolV0: + mempool = mempoolv0.NewCListMempool(cfg.Mempool, proxyAppConnMem, state.LastBlockHeight, mempoolv0.WithPreCheck(sm.TxPreCheck(state)), mempoolv0.WithPostCheck(sm.TxPostCheck(state))) - case cfg.MempoolV1: + case config.MempoolV1: mempool = mempoolv1.NewTxMempool(logger, - config.Mempool, + cfg.Mempool, proxyAppConnMem, state.LastBlockHeight, mempoolv1.WithPreCheck(sm.TxPreCheck(state)), @@ -91,7 +89,7 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { ) } - if thisConfig.Consensus.WaitForTxs() { + if cfg.Consensus.WaitForTxs() { mempool.EnableTxsAvailable() } @@ -103,7 +101,7 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { // Make State blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyAppConnCon, mempool, evpool) - cs := NewState(thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool) + cs := NewState(cfg.Consensus, state, blockExec, blockStore, mempool, evpool) cs.SetLogger(cs.Logger) // set private validator pv := privVals[i] @@ -115,7 +113,7 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { require.NoError(t, err) cs.SetEventBus(eventBus) - cs.SetTimeoutTicker(tickerFunc()) + cs.SetTimeoutTicker(newMockTickerFunc(true)()) cs.SetLogger(logger) css[i] = cs @@ -143,7 +141,7 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { } } // make connected switches and start all reactors - p2p.MakeConnectedSwitches(config.P2P, nValidators, func(i int, s *p2p.Switch) *p2p.Switch { + p2p.MakeConnectedSwitches(config.TestP2PConfig(), nValidators, func(i int, s *p2p.Switch) *p2p.Switch { s.AddReactor("CONSENSUS", reactors[i]) s.SetLogger(reactors[i].conS.Logger.With("module", "p2p")) return s @@ -190,22 +188,22 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { panic("entered createProposalBlock with privValidator being nil") } - var commit *types.Commit + var commit *types.ExtendedCommit switch { case lazyProposer.Height == lazyProposer.state.InitialHeight: // We're creating a proposal for the first block. // The commit is empty, but not nil. - commit = types.NewCommit(0, 0, types.BlockID{}, nil) + commit = &types.ExtendedCommit{} case lazyProposer.LastCommit.HasTwoThirdsMajority(): // Make the commit from LastCommit - commit = lazyProposer.LastCommit.MakeCommit() + commit = lazyProposer.LastCommit.MakeExtendedCommit() default: // This shouldn't happen. lazyProposer.Logger.Error("enterPropose: Cannot propose anything: No commit for the previous block") return } // omit the last signature in the commit - commit.Signatures[len(commit.Signatures)-1] = types.NewCommitSigAbsent() + commit.ExtendedSignatures[len(commit.ExtendedSignatures)-1] = types.NewExtendedCommitSigAbsent() if lazyProposer.privValidatorPubKey == nil { // If this node is a validator & proposer in the current round, it will @@ -303,22 +301,20 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { // B sees a commit, A doesn't. // Heal partition and ensure A sees the commit func TestByzantineConflictingProposalsWithPartition(t *testing.T) { - N := 4 logger := consensusLogger().With("test", "byzantine") - app := newKVStore - css, cleanup := randConsensusNet(t, N, "consensus_byzantine_test", newMockTickerFunc(false), app) - defer cleanup() + css, _, cfg := makeNetwork(t, makeNetworkArgs{}) + n := len(css) // give the byzantine validator a normal ticker ticker := NewTimeoutTicker() ticker.SetLogger(css[0].Logger) css[0].SetTimeoutTicker(ticker) - switches := make([]*p2p.Switch, N) + switches := make([]*p2p.Switch, n) p2pLogger := logger.With("module", "p2p") - for i := 0; i < N; i++ { + for i := 0; i < n; i++ { switches[i] = p2p.MakeSwitch( - config.P2P, + cfg.P2P, i, "foo", "1.0.0", func(i int, sw *p2p.Switch) *p2p.Switch { @@ -327,9 +323,9 @@ func TestByzantineConflictingProposalsWithPartition(t *testing.T) { switches[i].SetLogger(p2pLogger.With("validator", i)) } - blocksSubs := make([]types.Subscription, N) - reactors := make([]p2p.Reactor, N) - for i := 0; i < N; i++ { + blocksSubs := make([]types.Subscription, n) + reactors := make([]p2p.Reactor, n) + for i := 0; i < n; i++ { // enable txs so we can create different proposals assertMempool(css[i].txNotifier).EnableTxsAvailable() @@ -383,7 +379,7 @@ func TestByzantineConflictingProposalsWithPartition(t *testing.T) { } }() - p2p.MakeConnectedSwitches(config.P2P, N, func(i int, s *p2p.Switch) *p2p.Switch { + p2p.MakeConnectedSwitches(cfg.P2P, n, func(i int, s *p2p.Switch) *p2p.Switch { // ignore new switch s, we already made ours switches[i].AddReactor("CONSENSUS", reactors[i]) return switches[i] @@ -397,7 +393,7 @@ func TestByzantineConflictingProposalsWithPartition(t *testing.T) { // start the non-byz state machines. // note these must be started before the byz - for i := 1; i < N; i++ { + for i := 1; i < n; i++ { cr := reactors[i].(*Reactor) cr.SwitchToConsensus(cr.conS.GetState(), false) } @@ -430,7 +426,7 @@ func TestByzantineConflictingProposalsWithPartition(t *testing.T) { // wait till everyone makes the first new block // (one of them already has) wg := new(sync.WaitGroup) - for i := 1; i < N-1; i++ { + for i := 1; i < n-1; i++ { wg.Add(1) go func(j int) { <-blocksSubs[j].Out() @@ -448,10 +444,6 @@ func TestByzantineConflictingProposalsWithPartition(t *testing.T) { select { case <-done: case <-tick.C: - for i, reactor := range reactors { - t.Logf("Consensus Reactor %v", i) - t.Logf("%v", reactor) - } t.Fatalf("Timed out waiting for all validators to commit first block") } } diff --git a/consensus/common_test.go b/consensus/common_test.go index c9a685b67..853a0c7e5 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -6,7 +6,6 @@ import ( "fmt" "os" "path/filepath" - "sort" "sync" "testing" "time" @@ -15,15 +14,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "path" - dbm "github.com/tendermint/tm-db" abcicli "github.com/tendermint/tendermint/abci/client" "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" - cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/config" cstypes "github.com/tendermint/tendermint/consensus/types" + "github.com/tendermint/tendermint/internal/test" tmbytes "github.com/tendermint/tendermint/libs/bytes" "github.com/tendermint/tendermint/libs/log" tmos "github.com/tendermint/tendermint/libs/os" @@ -44,18 +42,38 @@ import ( const ( testSubscriber = "test-client" + + // genesis, chain_id, priv_val + ensureTimeout = time.Millisecond * 200 ) // A cleanupFunc cleans up any config / test files created for a particular // test. type cleanupFunc func() -// genesis, chain_id, priv_val -var ( - config *cfg.Config // NOTE: must be reset for each _test.go file - consensusReplayConfig *cfg.Config - ensureTimeout = time.Millisecond * 200 -) +func configSetup(t *testing.T) *config.Config { + t.Helper() + + cfg := ResetConfig("consensus_reactor_test") + t.Cleanup(func() { os.RemoveAll(cfg.RootDir) }) + + consensusReplayConfig := ResetConfig("consensus_replay_test") + t.Cleanup(func() { os.RemoveAll(consensusReplayConfig.RootDir) }) + + configStateTest := ResetConfig("consensus_state_test") + t.Cleanup(func() { os.RemoveAll(configStateTest.RootDir) }) + + configMempoolTest := ResetConfig("consensus_mempool_test") + t.Cleanup(func() { os.RemoveAll(configMempoolTest.RootDir) }) + + configByzantineTest := ResetConfig("consensus_byzantine_test") + t.Cleanup(func() { os.RemoveAll(configByzantineTest.RootDir) }) + + walDir := filepath.Dir(cfg.Consensus.WalFile()) + ensureDir(walDir, 0700) + + return cfg +} func ensureDir(dir string, mode os.FileMode) { if err := tmos.EnsureDir(dir, mode); err != nil { @@ -63,8 +81,8 @@ func ensureDir(dir string, mode os.FileMode) { } } -func ResetConfig(name string) *cfg.Config { - return cfg.ResetTestRoot(name) +func ResetConfig(name string) *config.Config { + return config.ResetTestRoot(name) } //------------------------------------------------------------------------------- @@ -91,8 +109,10 @@ func newValidatorStub(privValidator types.PrivValidator, valIndex int32) *valida func (vs *validatorStub) signVote( voteType tmproto.SignedMsgType, - hash []byte, - header types.PartSetHeader) (*types.Vote, error) { + chainID string, + blockID types.BlockID, + voteExtension []byte, +) (*types.Vote, error) { pubKey, err := vs.PrivValidator.GetPubKey() if err != nil { @@ -106,10 +126,11 @@ func (vs *validatorStub) signVote( Round: vs.Round, Timestamp: tmtime.Now(), Type: voteType, - BlockID: types.BlockID{Hash: hash, PartSetHeader: header}, + BlockID: blockID, + Extension: voteExtension, } v := vote.ToProto() - if err := vs.PrivValidator.SignVote(config.ChainID(), v); err != nil { + if err := vs.PrivValidator.SignVote(chainID, v); err != nil { return nil, fmt.Errorf("sign vote failed: %w", err) } @@ -117,20 +138,31 @@ func (vs *validatorStub) signVote( if signDataIsEqual(vs.lastVote, v) { v.Signature = vs.lastVote.Signature v.Timestamp = vs.lastVote.Timestamp + v.ExtensionSignature = vs.lastVote.ExtensionSignature } vote.Signature = v.Signature vote.Timestamp = v.Timestamp + vote.ExtensionSignature = v.ExtensionSignature return vote, err } // Sign vote for type/hash/header -func signVote(vs *validatorStub, voteType tmproto.SignedMsgType, hash []byte, header types.PartSetHeader) *types.Vote { - v, err := vs.signVote(voteType, hash, header) - if err != nil { - panic(fmt.Errorf("failed to sign vote: %v", err)) +func signVote( + t *testing.T, + vs *validatorStub, + voteType tmproto.SignedMsgType, + chainID string, + blockID types.BlockID) *types.Vote { + + var ext []byte + // Only non-nil precommits are allowed to carry vote extensions. + if voteType == tmproto.PrecommitType && !blockID.IsNil() { + ext = []byte("extension") } + v, err := vs.signVote(voteType, chainID, blockID, ext) + require.NoError(t, err, "failed to sign vote") vs.lastVote = v @@ -138,17 +170,30 @@ func signVote(vs *validatorStub, voteType tmproto.SignedMsgType, hash []byte, he } func signVotes( + t *testing.T, voteType tmproto.SignedMsgType, - hash []byte, - header types.PartSetHeader, - vss ...*validatorStub) []*types.Vote { + chainID string, + blockID types.BlockID, + vss ...*validatorStub, +) []*types.Vote { votes := make([]*types.Vote, len(vss)) for i, vs := range vss { - votes[i] = signVote(vs, voteType, hash, header) + votes[i] = signVote(t, vs, voteType, chainID, blockID) } return votes } +func signAddPrecommitWithExtension( + t *testing.T, + cs *State, + blockID types.BlockID, + extension []byte, + stub *validatorStub) { + v, err := stub.signVote(tmproto.PrecommitType, test.DefaultTestChainID, blockID, extension) + require.NoError(t, err, "failed to sign vote") + addVotes(cs, v) +} + func incrementHeight(vss ...*validatorStub) { for _, vs := range vss { vs.Height++ @@ -239,14 +284,13 @@ func addVotes(to *State, votes ...*types.Vote) { } func signAddVotes( + t *testing.T, to *State, voteType tmproto.SignedMsgType, - hash []byte, - header types.PartSetHeader, + blockID types.BlockID, vss ...*validatorStub, ) { - votes := signVotes(voteType, hash, header, vss...) - addVotes(to, votes...) + addVotes(to, signVotes(t, voteType, test.DefaultTestChainID, blockID, vss...)...) } func validatePrevote(t *testing.T, cs *State, round int32, privVal *validatorStub, blockHash []byte) { @@ -369,30 +413,62 @@ func subscribeToVoter(cs *State, addr []byte) <-chan tmpubsub.Message { //------------------------------------------------------------------------------- // consensus states -func newState(state sm.State, pv types.PrivValidator, app abci.Application) *State { - config := cfg.ResetTestRoot("consensus_state_test") - return newStateWithConfig(config, state, pv, app) +type makeStateArgs struct { + config *config.Config + consensusParams *types.ConsensusParams + validators int + application abci.Application } -func newStateWithConfig( - thisConfig *cfg.Config, +func makeState(t *testing.T, args makeStateArgs) (*State, []*validatorStub) { + t.Helper() + // Get State + validators := 4 + if args.validators != 0 { + validators = args.validators + } + var app abci.Application + app = kvstore.NewInMemoryApplication() + if args.application != nil { + app = args.application + } + if args.config == nil { + args.config = configSetup(t) + } + cp := test.ConsensusParams() + if args.consensusParams != nil { + cp = args.consensusParams + } + + state, privVals := makeGenesisState(t, genesisStateArgs{ + params: cp, + validators: validators, + }) + + vss := make([]*validatorStub, validators) + + cs := newState(t, args.config, state, privVals[0], app) + + for i := 0; i < validators; i++ { + vss[i] = newValidatorStub(privVals[i], int32(i)) + } + // since cs1 starts at 1 + incrementHeight(vss[1:]...) + + return cs, vss +} + +func newState( + t *testing.T, + cfg *config.Config, state sm.State, pv types.PrivValidator, app abci.Application, ) *State { - blockDB := dbm.NewMemDB() - return newStateWithConfigAndBlockStore(thisConfig, state, pv, app, blockDB) -} + t.Helper() -func newStateWithConfigAndBlockStore( - thisConfig *cfg.Config, - state sm.State, - pv types.PrivValidator, - app abci.Application, - blockDB dbm.DB, -) *State { // Get BlockStore - blockStore := store.NewBlockStore(blockDB) + blockStore := store.NewBlockStore(dbm.NewMemDB()) // one for mempool, one for consensus mtx := new(tmsync.Mutex) @@ -405,18 +481,18 @@ func newStateWithConfigAndBlockStore( // Make Mempool var mempool mempl.Mempool - switch config.Mempool.Version { - case cfg.MempoolV0: - mempool = mempoolv0.NewCListMempool(config.Mempool, + switch cfg.Mempool.Version { + case config.MempoolV0: + mempool = mempoolv0.NewCListMempool(cfg.Mempool, proxyAppConnMem, state.LastBlockHeight, mempoolv0.WithMetrics(memplMetrics), mempoolv0.WithPreCheck(sm.TxPreCheck(state)), mempoolv0.WithPostCheck(sm.TxPostCheck(state))) - case cfg.MempoolV1: + case config.MempoolV1: logger := consensusLogger() mempool = mempoolv1.NewTxMempool(logger, - config.Mempool, + cfg.Mempool, proxyAppConnMem, state.LastBlockHeight, mempoolv1.WithMetrics(memplMetrics), @@ -424,38 +500,34 @@ func newStateWithConfigAndBlockStore( mempoolv1.WithPostCheck(sm.TxPostCheck(state)), ) } - if thisConfig.Consensus.WaitForTxs() { + if cfg.Consensus.WaitForTxs() { mempool.EnableTxsAvailable() } evpool := sm.EmptyEvidencePool{} // Make State - stateDB := blockDB - stateStore := sm.NewStore(stateDB, sm.StoreOptions{ + stateStore := sm.NewStore(dbm.NewMemDB(), sm.StoreOptions{ DiscardFinalizeBlockResponses: false, }) - if err := stateStore.Save(state); err != nil { // for save height 1's validators info - panic(err) - } + err := stateStore.Save(state) // for save height 1's validators info + require.NoError(t, err) blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyAppConnCon, mempool, evpool) - cs := NewState(thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool) + cs := NewState(cfg.Consensus, state, blockExec, blockStore, mempool, evpool) cs.SetLogger(log.TestingLogger().With("module", "consensus")) cs.SetPrivValidator(pv) eventBus := types.NewEventBus() eventBus.SetLogger(log.TestingLogger().With("module", "events")) - err := eventBus.Start() - if err != nil { - panic(err) - } + err = eventBus.Start() + require.NoError(t, err) cs.SetEventBus(eventBus) return cs } -func loadPrivValidator(config *cfg.Config) *privval.FilePV { +func loadPrivValidator(config *config.Config) *privval.FilePV { privValidatorKeyFile := config.PrivValidatorKeyFile() ensureDir(filepath.Dir(privValidatorKeyFile), 0700) privValidatorStateFile := config.PrivValidatorStateFile() @@ -464,25 +536,115 @@ func loadPrivValidator(config *cfg.Config) *privval.FilePV { return privValidator } -func randState(nValidators int) (*State, []*validatorStub) { - return randStateWithApp(nValidators, kvstore.NewInMemoryApplication()) +//------------------------------------------------------------------------------- +// consensus nets + +// consensusLogger is a TestingLogger which uses a different +// color for each validator ("validator" key must exist). +func consensusLogger() log.Logger { + return log.TestingLoggerWithColorFn(func(keyvals ...interface{}) term.FgBgColor { + for i := 0; i < len(keyvals)-1; i += 2 { + if keyvals[i] == "validator" { + return term.FgBgColor{Fg: term.Color(uint8(keyvals[i+1].(int) + 1))} + } + } + return term.FgBgColor{} + }).With("module", "consensus") } -func randStateWithApp(nValidators int, app abci.Application) (*State, []*validatorStub) { - // Get State - state, privVals := randGenesisState(nValidators, false, 10) +type makeNetworkArgs struct { + config *config.Config + params *types.ConsensusParams + validators int + nonValidators int + tickerFunc func() TimeoutTicker + appfactory func() abci.Application +} - vss := make([]*validatorStub, nValidators) +func makeNetwork(t *testing.T, args makeNetworkArgs) ([]*State, []types.PrivValidator, *config.Config) { + t.Helper() - cs := newState(state, privVals[0], app) - - for i := 0; i < nValidators; i++ { - vss[i] = newValidatorStub(privVals[i], int32(i)) + if args.config == nil { + args.config = config.TestConfig() } - // since cs1 starts at 1 - incrementHeight(vss[1:]...) - return cs, vss + if args.appfactory == nil { + args.appfactory = func() abci.Application { + return kvstore.NewInMemoryApplication() + } + } + + if args.tickerFunc == nil { + args.tickerFunc = newMockTickerFunc(true) + } + + if args.validators == 0 { + args.validators = 4 + } + + state, privVals := makeGenesisState(t, genesisStateArgs{ + validators: args.validators, + params: args.params, + }) + + for i := 0; i < args.nonValidators; i++ { + privVals = append(privVals, types.NewMockPV()) + } + + css := make([]*State, args.validators+args.nonValidators) + logger := consensusLogger() + for i := 0; i < args.validators+args.nonValidators; i++ { + app := args.appfactory() + vals := types.TM2PB.ValidatorUpdates(state.Validators) + _, err := app.InitChain(context.Background(), &abci.RequestInitChain{Validators: vals}) + require.NoError(t, err) + + css[i] = newState(t, args.config, state, privVals[i], app) + css[i].SetTimeoutTicker(args.tickerFunc()) + css[i].SetLogger(logger.With("validator", i, "module", "consensus")) + } + + return css, privVals, args.config +} + +func getSwitchIndex(switches []*p2p.Switch, peer p2p.Peer) int { + for i, s := range switches { + if peer.NodeInfo().ID() == s.NodeInfo().ID() { + return i + } + } + panic("didnt find peer in switches") +} + +//------------------------------------------------------------------------------- +// genesis + +type genesisStateArgs struct { + validators int + power int64 + params *types.ConsensusParams + time time.Time +} + +func makeGenesisState(t *testing.T, args genesisStateArgs) (sm.State, []types.PrivValidator) { + t.Helper() + if args.power == 0 { + args.power = 1 + } + if args.validators == 0 { + args.validators = 4 + } + valSet, privValidators := test.ValidatorSet(t, args.validators, args.power) + if args.params == nil { + args.params = test.ConsensusParams() + } + if args.time.IsZero() { + args.time = time.Now() + } + genDoc := test.GenesisDoc("", args.time, valSet.Validators, args.params) + state, err := sm.MakeGenesisState(genDoc) + require.NoError(t, err) + return state, privValidators } //------------------------------------------------------------------------------- @@ -694,6 +856,11 @@ func ensurePrevoteMatch(t *testing.T, voteCh <-chan tmpubsub.Message, height int ensureVoteMatch(t, voteCh, height, round, hash, tmproto.PrevoteType) } +func ensurePrecommitMatch(t *testing.T, voteCh <-chan tmpubsub.Message, height int64, round int32, hash []byte) { + t.Helper() + ensureVoteMatch(t, voteCh, height, round, hash, tmproto.PrecommitType) +} + func ensureVoteMatch(t *testing.T, voteCh <-chan tmpubsub.Message, height int64, round int32, hash []byte, voteType tmproto.SignedMsgType) { t.Helper() select { @@ -732,159 +899,6 @@ func ensureNewEventOnChannel(ch <-chan tmpubsub.Message) { } } -//------------------------------------------------------------------------------- -// consensus nets - -// consensusLogger is a TestingLogger which uses a different -// color for each validator ("validator" key must exist). -func consensusLogger() log.Logger { - return log.TestingLoggerWithColorFn(func(keyvals ...interface{}) term.FgBgColor { - for i := 0; i < len(keyvals)-1; i += 2 { - if keyvals[i] == "validator" { - return term.FgBgColor{Fg: term.Color(uint8(keyvals[i+1].(int) + 1))} - } - } - return term.FgBgColor{} - }).With("module", "consensus") -} - -func randConsensusNet(t *testing.T, nValidators int, testName string, tickerFunc func() TimeoutTicker, - appFunc func() abci.Application, configOpts ...func(*cfg.Config)) ([]*State, cleanupFunc) { - t.Helper() - genDoc, privVals := randGenesisDoc(nValidators, false, 30) - css := make([]*State, nValidators) - logger := consensusLogger() - configRootDirs := make([]string, 0, nValidators) - for i := 0; i < nValidators; i++ { - stateDB := dbm.NewMemDB() // each state needs its own db - stateStore := sm.NewStore(stateDB, sm.StoreOptions{ - DiscardFinalizeBlockResponses: false, - }) - state, _ := stateStore.LoadFromDBOrGenesisDoc(genDoc) - thisConfig := ResetConfig(fmt.Sprintf("%s_%d", testName, i)) - configRootDirs = append(configRootDirs, thisConfig.RootDir) - for _, opt := range configOpts { - opt(thisConfig) - } - ensureDir(filepath.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal - app := appFunc() - vals := types.TM2PB.ValidatorUpdates(state.Validators) - _, err := app.InitChain(context.Background(), &abci.RequestInitChain{Validators: vals}) - require.NoError(t, err) - - css[i] = newStateWithConfigAndBlockStore(thisConfig, state, privVals[i], app, stateDB) - css[i].SetTimeoutTicker(tickerFunc()) - css[i].SetLogger(logger.With("validator", i, "module", "consensus")) - } - return css, func() { - for _, dir := range configRootDirs { - os.RemoveAll(dir) - } - } -} - -// nPeers = nValidators + nNotValidator -func randConsensusNetWithPeers( - t *testing.T, - nValidators, - nPeers int, - testName string, - tickerFunc func() TimeoutTicker, - appFunc func(string) abci.Application, -) ([]*State, *types.GenesisDoc, *cfg.Config, cleanupFunc) { - genDoc, privVals := randGenesisDoc(nValidators, false, testMinPower) - css := make([]*State, nPeers) - logger := consensusLogger() - var peer0Config *cfg.Config - configRootDirs := make([]string, 0, nPeers) - for i := 0; i < nPeers; i++ { - stateDB := dbm.NewMemDB() // each state needs its own db - stateStore := sm.NewStore(stateDB, sm.StoreOptions{ - DiscardFinalizeBlockResponses: false, - }) - t.Cleanup(func() { _ = stateStore.Close() }) - state, _ := stateStore.LoadFromDBOrGenesisDoc(genDoc) - thisConfig := ResetConfig(fmt.Sprintf("%s_%d", testName, i)) - configRootDirs = append(configRootDirs, thisConfig.RootDir) - ensureDir(filepath.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal - if i == 0 { - peer0Config = thisConfig - } - var privVal types.PrivValidator - if i < nValidators { - privVal = privVals[i] - } else { - tempKeyFile, err := os.CreateTemp("", "priv_validator_key_") - if err != nil { - panic(err) - } - tempStateFile, err := os.CreateTemp("", "priv_validator_state_") - if err != nil { - panic(err) - } - - privVal = privval.GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) - } - - app := appFunc(path.Join(config.DBDir(), fmt.Sprintf("%s_%d", testName, i))) - vals := types.TM2PB.ValidatorUpdates(state.Validators) - if _, ok := app.(*kvstore.Application); ok { - // simulate handshake, receive app version. If don't do this, replay test will fail - state.Version.Consensus.App = kvstore.AppVersion - } - _, err := app.InitChain(context.Background(), &abci.RequestInitChain{Validators: vals}) - require.NoError(t, err) - - css[i] = newStateWithConfig(thisConfig, state, privVal, app) - css[i].SetTimeoutTicker(tickerFunc()) - css[i].SetLogger(logger.With("validator", i, "module", "consensus")) - } - return css, genDoc, peer0Config, func() { - for _, dir := range configRootDirs { - os.RemoveAll(dir) - } - } -} - -func getSwitchIndex(switches []*p2p.Switch, peer p2p.Peer) int { - for i, s := range switches { - if peer.NodeInfo().ID() == s.NodeInfo().ID() { - return i - } - } - panic("didnt find peer in switches") -} - -//------------------------------------------------------------------------------- -// genesis - -func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.GenesisDoc, []types.PrivValidator) { - validators := make([]types.GenesisValidator, numValidators) - privValidators := make([]types.PrivValidator, numValidators) - for i := 0; i < numValidators; i++ { - val, privVal := types.RandValidator(randPower, minPower) - validators[i] = types.GenesisValidator{ - PubKey: val.PubKey, - Power: val.VotingPower, - } - privValidators[i] = privVal - } - sort.Sort(types.PrivValidatorsByAddress(privValidators)) - - return &types.GenesisDoc{ - GenesisTime: tmtime.Now(), - InitialHeight: 1, - ChainID: config.ChainID(), - Validators: validators, - }, privValidators -} - -func randGenesisState(numValidators int, randPower bool, minPower int64) (sm.State, []types.PrivValidator) { - genDoc, privValidators := randGenesisDoc(numValidators, randPower, minPower) - s0, _ := sm.MakeGenesisState(genDoc) - return s0, privValidators -} - //------------------------------------ // mock ticker diff --git a/consensus/invalid_test.go b/consensus/invalid_test.go index fa70ff468..3fac34e7e 100644 --- a/consensus/invalid_test.go +++ b/consensus/invalid_test.go @@ -18,8 +18,7 @@ import ( // Ensure a testnet makes blocks func TestReactorInvalidPrecommit(t *testing.T) { N := 4 - css, cleanup := randConsensusNet(t, N, "consensus_reactor_test", newMockTickerFunc(true), newKVStore) - defer cleanup() + css, _, _ := makeNetwork(t, makeNetworkArgs{}) for i := 0; i < 4; i++ { ticker := NewTimeoutTicker() diff --git a/consensus/mempool_test.go b/consensus/mempool_test.go index 4b26e0d7a..e16b0eca0 100644 --- a/consensus/mempool_test.go +++ b/consensus/mempool_test.go @@ -30,12 +30,12 @@ func TestMempoolNoProgressUntilTxsAvailable(t *testing.T) { config := ResetConfig("consensus_mempool_txs_available_test") defer os.RemoveAll(config.RootDir) config.Consensus.CreateEmptyBlocks = false - state, privVals := randGenesisState(1, false, 10) + state, privVals := makeGenesisState(t, genesisStateArgs{validators: 1}) app := kvstore.NewInMemoryApplication() resp, err := app.Info(context.Background(), proxy.RequestInfo) require.NoError(t, err) state.AppHash = resp.LastBlockAppHash - cs := newStateWithConfig(config, state, privVals[0], app) + cs := newState(t, config, state, privVals[0], app) assertMempool(cs.txNotifier).EnableTxsAvailable() height, round := cs.Height, cs.Round newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock) @@ -54,12 +54,12 @@ func TestMempoolProgressAfterCreateEmptyBlocksInterval(t *testing.T) { defer os.RemoveAll(config.RootDir) config.Consensus.CreateEmptyBlocksInterval = ensureTimeout - state, privVals := randGenesisState(1, false, 10) + state, privVals := makeGenesisState(t, genesisStateArgs{validators: 1}) app := kvstore.NewInMemoryApplication() resp, err := app.Info(context.Background(), proxy.RequestInfo) require.NoError(t, err) state.AppHash = resp.LastBlockAppHash - cs := newStateWithConfig(config, state, privVals[0], app) + cs := newState(t, config, state, privVals[0], app) assertMempool(cs.txNotifier).EnableTxsAvailable() @@ -75,8 +75,8 @@ func TestMempoolProgressInHigherRound(t *testing.T) { config := ResetConfig("consensus_mempool_txs_available_test") defer os.RemoveAll(config.RootDir) config.Consensus.CreateEmptyBlocks = false - state, privVals := randGenesisState(1, false, 10) - cs := newStateWithConfig(config, state, privVals[0], kvstore.NewInMemoryApplication()) + state, privVals := makeGenesisState(t, genesisStateArgs{validators: 1}) + cs := newState(t, config, state, privVals[0], kvstore.NewInMemoryApplication()) assertMempool(cs.txNotifier).EnableTxsAvailable() height, round := cs.Height, cs.Round newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock) @@ -117,10 +117,10 @@ func deliverTxsRange(t *testing.T, cs *State, start, end int) { } func TestMempoolTxConcurrentWithCommit(t *testing.T) { - state, privVals := randGenesisState(1, false, 10) - blockDB := dbm.NewMemDB() - stateStore := sm.NewStore(blockDB, sm.StoreOptions{DiscardFinalizeBlockResponses: false}) - cs := newStateWithConfigAndBlockStore(config, state, privVals[0], kvstore.NewInMemoryApplication(), blockDB) + cfg := ResetConfig(t.Name()) + state, privVals := makeGenesisState(t, genesisStateArgs{validators: 1}) + stateStore := sm.NewStore(dbm.NewMemDB(), sm.StoreOptions{DiscardFinalizeBlockResponses: false}) + cs := newState(t, cfg, state, privVals[0], kvstore.NewInMemoryApplication()) err := stateStore.Save(state) require.NoError(t, err) newBlockEventsCh := subscribe(cs.eventBus, types.EventQueryNewBlockEvents) @@ -141,11 +141,12 @@ func TestMempoolTxConcurrentWithCommit(t *testing.T) { } func TestMempoolRmBadTx(t *testing.T) { - state, privVals := randGenesisState(1, false, 10) + cfg := ResetConfig(t.Name()) + state, privVals := makeGenesisState(t, genesisStateArgs{validators: 1}) app := kvstore.NewInMemoryApplication() blockDB := dbm.NewMemDB() stateStore := sm.NewStore(blockDB, sm.StoreOptions{DiscardFinalizeBlockResponses: false}) - cs := newStateWithConfigAndBlockStore(config, state, privVals[0], app, blockDB) + cs := newState(t, cfg, state, privVals[0], app) err := stateStore.Save(state) require.NoError(t, err) diff --git a/consensus/metrics.gen.go b/consensus/metrics.gen.go index bb9b068dd..a27820609 100644 --- a/consensus/metrics.gen.go +++ b/consensus/metrics.gen.go @@ -162,6 +162,12 @@ func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { Name: "full_prevote_delay", Help: "Interval in seconds between the proposal timestamp and the timestamp of the latest prevote in a round where all validators voted.", }, append(labels, "proposer_address")).With(labelsAndValues...), + VoteExtensionReceiveCount: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "vote_extension_receive_count", + Help: "Number of vote extensions received labeled by application response status.", + }, append(labels, "status")).With(labelsAndValues...), ProposalReceiveCount: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, @@ -215,6 +221,7 @@ func NopMetrics() *Metrics { BlockGossipPartsReceived: discard.NewCounter(), QuorumPrevoteDelay: discard.NewGauge(), FullPrevoteDelay: discard.NewGauge(), + VoteExtensionReceiveCount: discard.NewCounter(), ProposalReceiveCount: discard.NewCounter(), ProposalCreateCount: discard.NewCounter(), RoundVotingPowerPercent: discard.NewGauge(), diff --git a/consensus/metrics.go b/consensus/metrics.go index a2ee039d1..f13bcbdd1 100644 --- a/consensus/metrics.go +++ b/consensus/metrics.go @@ -95,6 +95,12 @@ type Metrics struct { //metrics:Interval in seconds between the proposal timestamp and the timestamp of the latest prevote in a round where all validators voted. FullPrevoteDelay metrics.Gauge `metrics_labels:"proposer_address"` + // VoteExtensionReceiveCount is the number of vote extensions received by this + // node. The metric is annotated by the status of the vote extension from the + // application, either 'accepted' or 'rejected'. + //metrics:Number of vote extensions received labeled by application response status. + VoteExtensionReceiveCount metrics.Counter `metrics_labels:"status"` + // ProposalReceiveCount is the total number of proposals received by this node // since process start. // The metric is annotated by the status of the proposal from the application, @@ -132,6 +138,14 @@ func (m *Metrics) MarkProposalProcessed(accepted bool) { m.ProposalReceiveCount.With("status", status).Add(1) } +func (m *Metrics) MarkVoteExtensionReceived(accepted bool) { + status := "accepted" + if !accepted { + status = "rejected" + } + m.VoteExtensionReceiveCount.With("status", status).Add(1) +} + func (m *Metrics) MarkVoteReceived(vt tmproto.SignedMsgType, power, totalPower int64) { p := float64(power) / float64(totalPower) n := strings.ToLower(strings.TrimPrefix(vt.String(), "SIGNED_MSG_TYPE_")) diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index a329b304f..e65648379 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -18,10 +18,11 @@ import ( abcicli "github.com/tendermint/tendermint/abci/client" "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" - cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/config" cstypes "github.com/tendermint/tendermint/consensus/types" cryptoenc "github.com/tendermint/tendermint/crypto/encoding" "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/internal/test" "github.com/tendermint/tendermint/libs/bits" "github.com/tendermint/tendermint/libs/bytes" "github.com/tendermint/tendermint/libs/log" @@ -74,7 +75,7 @@ func startConsensusNet(t *testing.T, css []*State, n int) ( } } // make connected switches and start all reactors - p2p.MakeConnectedSwitches(config.P2P, n, func(i int, s *p2p.Switch) *p2p.Switch { + p2p.MakeConnectedSwitches(config.TestP2PConfig(), n, func(i int, s *p2p.Switch) *p2p.Switch { s.AddReactor("CONSENSUS", reactors[i]) s.SetLogger(reactors[i].conS.Logger.With("module", "p2p")) return s @@ -111,8 +112,10 @@ func stopConsensusNet(logger log.Logger, reactors []*Reactor, eventBuses []*type // Ensure a testnet makes blocks func TestReactorBasic(t *testing.T) { N := 4 - css, cleanup := randConsensusNet(t, N, "consensus_reactor_test", newMockTickerFunc(true), newKVStore) - defer cleanup() + css, _, _ := makeNetwork(t, makeNetworkArgs{ + tickerFunc: newMockTickerFunc(true), + validators: N, + }) reactors, blocksSubs, eventBuses := startConsensusNet(t, css, N) defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses) // wait till everyone makes the first new block @@ -132,7 +135,7 @@ func TestReactorWithEvidence(t *testing.T) { // to unroll unwieldy abstractions. Here we duplicate the code from: // css := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newKVStore) - genDoc, privVals := randGenesisDoc(nValidators, false, 30) + state, privVals := makeGenesisState(t, genesisStateArgs{validators: nValidators, power: 30}) css := make([]*State, nValidators) logger := consensusLogger() for i := 0; i < nValidators; i++ { @@ -140,10 +143,9 @@ func TestReactorWithEvidence(t *testing.T) { stateStore := sm.NewStore(stateDB, sm.StoreOptions{ DiscardFinalizeBlockResponses: false, }) - state, _ := stateStore.LoadFromDBOrGenesisDoc(genDoc) - thisConfig := ResetConfig(fmt.Sprintf("%s_%d", testName, i)) - defer os.RemoveAll(thisConfig.RootDir) - ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal + cfg := ResetConfig(fmt.Sprintf("%s_%d", testName, i)) + defer os.RemoveAll(cfg.RootDir) + ensureDir(path.Dir(cfg.Consensus.WalFile()), 0700) // dir for wal app := appFunc() vals := types.TM2PB.ValidatorUpdates(state.Validators) _, err := app.InitChain(context.Background(), &abci.RequestInitChain{Validators: vals}) @@ -165,17 +167,17 @@ func TestReactorWithEvidence(t *testing.T) { // Make Mempool var mempool mempl.Mempool - switch config.Mempool.Version { - case cfg.MempoolV0: - mempool = mempoolv0.NewCListMempool(config.Mempool, + switch cfg.Mempool.Version { + case config.MempoolV0: + mempool = mempoolv0.NewCListMempool(cfg.Mempool, proxyAppConnMem, state.LastBlockHeight, mempoolv0.WithMetrics(memplMetrics), mempoolv0.WithPreCheck(sm.TxPreCheck(state)), mempoolv0.WithPostCheck(sm.TxPostCheck(state))) - case cfg.MempoolV1: + case config.MempoolV1: mempool = mempoolv1.NewTxMempool(logger, - config.Mempool, + cfg.Mempool, proxyAppConnMem, state.LastBlockHeight, mempoolv1.WithMetrics(memplMetrics), @@ -183,14 +185,14 @@ func TestReactorWithEvidence(t *testing.T) { mempoolv1.WithPostCheck(sm.TxPostCheck(state)), ) } - if thisConfig.Consensus.WaitForTxs() { + if cfg.Consensus.WaitForTxs() { mempool.EnableTxsAvailable() } // mock the evidence pool // everyone includes evidence of another double signing vIdx := (i + 1) % nValidators - ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultTestTime, privVals[vIdx], config.ChainID()) + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultTestTime, privVals[vIdx], test.DefaultTestChainID) require.NoError(t, err) evpool := &statemocks.EvidencePool{} evpool.On("CheckEvidence", mock.AnythingOfType("types.EvidenceList")).Return(nil) @@ -202,7 +204,7 @@ func TestReactorWithEvidence(t *testing.T) { // Make State blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyAppConnCon, mempool, evpool) - cs := NewState(thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool2) + cs := NewState(cfg.Consensus, state, blockExec, blockStore, mempool, evpool2) cs.SetLogger(log.TestingLogger().With("module", "consensus")) cs.SetPrivValidator(pv) @@ -236,11 +238,7 @@ func TestReactorWithEvidence(t *testing.T) { // Ensure a testnet makes blocks when there are txs func TestReactorCreatesBlockWhenEmptyBlocksFalse(t *testing.T) { N := 4 - css, cleanup := randConsensusNet(t, N, "consensus_reactor_test", newMockTickerFunc(true), newKVStore, - func(c *cfg.Config) { - c.Consensus.CreateEmptyBlocks = false - }) - defer cleanup() + css, _, _ := makeNetwork(t, makeNetworkArgs{validators: N}) reactors, blocksSubs, eventBuses := startConsensusNet(t, css, N) defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses) @@ -259,8 +257,7 @@ func TestReactorCreatesBlockWhenEmptyBlocksFalse(t *testing.T) { func TestReactorReceiveDoesNotPanicIfAddPeerHasntBeenCalledYet(t *testing.T) { N := 1 - css, cleanup := randConsensusNet(t, N, "consensus_reactor_test", newMockTickerFunc(true), newKVStore) - defer cleanup() + css, _, _ := makeNetwork(t, makeNetworkArgs{validators: N}) reactors, _, eventBuses := startConsensusNet(t, css, N) defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses) @@ -282,8 +279,7 @@ func TestReactorReceiveDoesNotPanicIfAddPeerHasntBeenCalledYet(t *testing.T) { func TestReactorReceivePanicsIfInitPeerHasntBeenCalledYet(t *testing.T) { N := 1 - css, cleanup := randConsensusNet(t, N, "consensus_reactor_test", newMockTickerFunc(true), newKVStore) - defer cleanup() + css, _, _ := makeNetwork(t, makeNetworkArgs{validators: N}) reactors, _, eventBuses := startConsensusNet(t, css, N) defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses) @@ -305,8 +301,7 @@ func TestReactorReceivePanicsIfInitPeerHasntBeenCalledYet(t *testing.T) { // Test we record stats about votes and block parts from other peers. func TestReactorRecordsVotesAndBlockParts(t *testing.T) { N := 4 - css, cleanup := randConsensusNet(t, N, "consensus_reactor_test", newMockTickerFunc(true), newKVStore) - defer cleanup() + css, _, _ := makeNetwork(t, makeNetworkArgs{validators: N}) reactors, blocksSubs, eventBuses := startConsensusNet(t, css, N) defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses) @@ -330,13 +325,7 @@ func TestReactorRecordsVotesAndBlockParts(t *testing.T) { func TestReactorVotingPowerChange(t *testing.T) { nVals := 4 logger := log.TestingLogger() - css, cleanup := randConsensusNet( - t, - nVals, - "consensus_voting_power_changes_test", - newMockTickerFunc(true), - newPersistentKVStore) - defer cleanup() + css, _, _ := makeNetwork(t, makeNetworkArgs{validators: nVals}) reactors, blocksSubs, eventBuses := startConsensusNet(t, css, nVals) defer stopConsensusNet(logger, reactors, eventBuses) @@ -411,15 +400,7 @@ func TestReactorVotingPowerChange(t *testing.T) { func TestReactorValidatorSetChanges(t *testing.T) { nPeers := 7 nVals := 4 - css, _, _, cleanup := randConsensusNetWithPeers( - t, - nVals, - nPeers, - "consensus_val_set_changes_test", - newMockTickerFunc(true), - newPersistentKVStoreWithPath) - - defer cleanup() + css, _, _ := makeNetwork(t, makeNetworkArgs{validators: nVals, nonValidators: nPeers - nVals}) logger := log.TestingLogger() reactors, blocksSubs, eventBuses := startConsensusNet(t, css, nPeers) @@ -526,8 +507,7 @@ func TestReactorValidatorSetChanges(t *testing.T) { // Check we can make blocks with skip_timeout_commit=false func TestReactorWithTimeoutCommit(t *testing.T) { N := 4 - css, cleanup := randConsensusNet(t, N, "consensus_reactor_with_timeout_commit_test", newMockTickerFunc(false), newKVStore) - defer cleanup() + css, _, _ := makeNetwork(t, makeNetworkArgs{validators: N, tickerFunc: newMockTickerFunc(false)}) // override default SkipTimeoutCommit == true for tests for i := 0; i < N; i++ { css[i].config.SkipTimeoutCommit = false @@ -562,7 +542,6 @@ func waitForAndValidateBlock( for _, tx := range txs { err := assertMempool(css[j].txNotifier).CheckTx(tx, func(resp *abci.ResponseCheckTx) { require.False(t, resp.IsErr()) - fmt.Println(resp) }, mempl.TxInfo{}) require.NoError(t, err) } diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 83769ba9d..42df9327b 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -20,7 +20,7 @@ import ( "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/abci/types/mocks" - cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/internal/test" "github.com/tendermint/tendermint/libs/log" @@ -33,21 +33,6 @@ import ( "github.com/tendermint/tendermint/types" ) -func TestMain(m *testing.M) { - config = ResetConfig("consensus_reactor_test") - consensusReplayConfig = ResetConfig("consensus_replay_test") - configStateTest := ResetConfig("consensus_state_test") - configMempoolTest := ResetConfig("consensus_mempool_test") - configByzantineTest := ResetConfig("consensus_byzantine_test") - code := m.Run() - os.RemoveAll(config.RootDir) - os.RemoveAll(consensusReplayConfig.RootDir) - os.RemoveAll(configStateTest.RootDir) - os.RemoveAll(configMempoolTest.RootDir) - os.RemoveAll(configByzantineTest.RootDir) - os.Exit(code) -} - // These tests ensure we can always recover from failure at any part of the consensus process. // There are two general failure scenarios: failure during consensus, and failure while applying the block. // Only the latter interacts with the app and store, @@ -63,17 +48,17 @@ func TestMain(m *testing.M) { // and which ones we need the wal for - then we'd also be able to only flush the // wal writer when we need to, instead of with every message. -func startNewStateAndWaitForBlock(t *testing.T, consensusReplayConfig *cfg.Config, - lastBlockHeight int64, blockDB dbm.DB, stateStore sm.Store) { +func startNewStateAndWaitForBlock(t *testing.T, consensusReplayConfig *config.Config, + lastBlockHeight int64, stateStore sm.Store) { logger := log.TestingLogger() state, _ := stateStore.LoadFromDBOrGenesisFile(consensusReplayConfig.GenesisFile()) privValidator := loadPrivValidator(consensusReplayConfig) - cs := newStateWithConfigAndBlockStore( + cs := newState( + t, consensusReplayConfig, state, privValidator, kvstore.NewInMemoryApplication(), - blockDB, ) cs.SetLogger(logger) @@ -148,7 +133,7 @@ func TestWALCrash(t *testing.T) { } } -func crashWALandCheckLiveness(t *testing.T, consensusReplayConfig *cfg.Config, +func crashWALandCheckLiveness(t *testing.T, cfg *config.Config, initFn func(dbm.DB, *State, context.Context), heightToStop int64) { walPanicked := make(chan error) crashingWal := &crashingWAL{panicCh: walPanicked, heightToStop: heightToStop} @@ -160,20 +145,19 @@ LOOP: // create consensus state from a clean slate logger := log.NewNopLogger() - blockDB := dbm.NewMemDB() - stateDB := blockDB + stateDB := dbm.NewMemDB() stateStore := sm.NewStore(stateDB, sm.StoreOptions{ DiscardFinalizeBlockResponses: false, }) - state, err := sm.MakeGenesisStateFromFile(consensusReplayConfig.GenesisFile()) + state, err := sm.MakeGenesisStateFromFile(cfg.GenesisFile()) require.NoError(t, err) - privValidator := loadPrivValidator(consensusReplayConfig) - cs := newStateWithConfigAndBlockStore( - consensusReplayConfig, + privValidator := loadPrivValidator(cfg) + cs := newState( + t, + cfg, state, privValidator, kvstore.NewInMemoryApplication(), - blockDB, ) cs.SetLogger(logger) @@ -205,7 +189,7 @@ LOOP: t.Logf("WAL panicked: %v", err) // make sure we can make blocks after a crash - startNewStateAndWaitForBlock(t, consensusReplayConfig, cs.Height, blockDB, stateStore) + startNewStateAndWaitForBlock(t, cfg, cs.Height, stateStore) // stop consensus state and transactions sender (initFn) cs.Stop() //nolint:errcheck // Logging this error causes failure @@ -311,28 +295,28 @@ var modes = []uint{0, 1, 2, 3} // Sync from scratch func TestHandshakeReplayAll(t *testing.T) { for _, m := range modes { - testHandshakeReplay(t, config, 0, m) + testHandshakeReplay(t, 0, m) } } // Sync many, not from scratch func TestHandshakeReplaySome(t *testing.T) { for _, m := range modes { - testHandshakeReplay(t, config, 2, m) + testHandshakeReplay(t, 2, m) } } // Sync from lagging by one func TestHandshakeReplayOne(t *testing.T) { for _, m := range modes { - testHandshakeReplay(t, config, numBlocks-1, m) + testHandshakeReplay(t, numBlocks-1, m) } } // Sync from caught up func TestHandshakeReplayNone(t *testing.T) { for _, m := range modes { - testHandshakeReplay(t, config, numBlocks, m) + testHandshakeReplay(t, numBlocks, m) } } @@ -353,7 +337,7 @@ func tempWALWithData(data []byte) string { // Make some blocks. Start a fresh app and apply nBlocks blocks. // Then restart the app and sync it up with the remaining blocks -func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uint) { +func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) { var ( chain []*types.Block commits []*types.Commit @@ -364,16 +348,16 @@ func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uin evpool = sm.EmptyEvidencePool{} ) - testConfig := ResetConfig(fmt.Sprintf("%d_%d_s", nBlocks, mode)) + testConfig := ResetConfig(fmt.Sprintf("handhsake_%d_%d", nBlocks, mode)) t.Cleanup(func() { _ = os.RemoveAll(testConfig.RootDir) }) walBody, err := WALWithNBlocks(t, numBlocks) require.NoError(t, err) walFile := tempWALWithData(walBody) - config.Consensus.SetWalFile(walFile) + testConfig.Consensus.SetWalFile(walFile) - privVal := privval.LoadFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()) + privVal := privval.LoadFilePV(testConfig.PrivValidatorKeyFile(), testConfig.PrivValidatorStateFile()) wal, err := NewWAL(walFile) require.NoError(t, err) @@ -389,7 +373,7 @@ func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uin require.NoError(t, err) pubKey, err := privVal.GetPubKey() require.NoError(t, err) - stateDB, genesisState, store = stateAndStore(t, config, pubKey, kvstore.AppVersion) + stateDB, genesisState, store = stateAndStore(t, testConfig, pubKey, kvstore.AppVersion) stateStore := sm.NewStore(stateDB, sm.StoreOptions{ DiscardFinalizeBlockResponses: false, @@ -402,12 +386,12 @@ func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uin state := genesisState.Copy() // run the chain through state.ApplyBlock to build up the tendermint state - state = buildTMStateFromChain(t, config, stateStore, mempool, evpool, state, chain, nBlocks, mode) + state = buildTMStateFromChain(t, testConfig, stateStore, mempool, evpool, state, chain, nBlocks, mode) latestAppHash := state.AppHash // make a new client creator kvstoreApp := kvstore.NewPersistentApplication( - filepath.Join(config.DBDir(), fmt.Sprintf("replay_test_%d_%d_a", nBlocks, mode))) + filepath.Join(testConfig.DBDir(), fmt.Sprintf("replay_test_%d_%d_a", nBlocks, mode))) t.Cleanup(func() { _ = kvstoreApp.Close() }) @@ -436,7 +420,7 @@ func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uin } // now start the app using the handshake - it should sync - genDoc, _ := sm.MakeGenesisDocFromFile(config.GenesisFile()) + genDoc, _ := sm.MakeGenesisDocFromFile(testConfig.GenesisFile()) handshaker := NewHandshaker(stateStore, state, store, genDoc) proxyApp := proxy.NewAppConns(clientCreator2, proxy.NopMetrics()) if err := proxyApp.Start(); err != nil { @@ -538,7 +522,7 @@ func buildAppStateFromChain(t *testing.T, proxyApp proxy.AppConns, stateStore sm func buildTMStateFromChain( t *testing.T, - config *cfg.Config, + cfg *config.Config, stateStore sm.Store, mempool mempool.Mempool, evpool sm.EvidencePool, @@ -549,7 +533,7 @@ func buildTMStateFromChain( // run the whole chain against this client to build up the tendermint state clientCreator := proxy.NewLocalClientCreator( kvstore.NewPersistentApplication( - filepath.Join(config.DBDir(), fmt.Sprintf("replay_test_%d_%d_t", nBlocks, mode)))) + filepath.Join(cfg.DBDir(), fmt.Sprintf("replay_test_%d_%d_t", nBlocks, mode)))) proxyApp := proxy.NewAppConns(clientCreator, proxy.NopMetrics()) if err := proxyApp.Start(); err != nil { panic(err) @@ -814,7 +798,7 @@ func readPieceFromWAL(msg *TimedWALMessage) interface{} { // fresh state and mock store func stateAndStore( t *testing.T, - config *cfg.Config, + cfg *config.Config, pubKey crypto.PubKey, appVersion uint64, ) (dbm.DB, sm.State, *mockBlockStore) { @@ -822,10 +806,10 @@ func stateAndStore( stateStore := sm.NewStore(stateDB, sm.StoreOptions{ DiscardFinalizeBlockResponses: false, }) - state, err := sm.MakeGenesisStateFromFile(config.GenesisFile()) + state, err := sm.MakeGenesisStateFromFile(cfg.GenesisFile()) require.NoError(t, err) state.Version.Consensus.App = appVersion - store := newMockBlockStore(t, config, state.ConsensusParams) + store := newMockBlockStore(t, cfg, state.ConsensusParams) require.NoError(t, stateStore.Save(state)) return stateDB, state, store @@ -835,7 +819,7 @@ func stateAndStore( // mock block store type mockBlockStore struct { - config *cfg.Config + cfg *config.Config params types.ConsensusParams chain []*types.Block commits []*types.Commit @@ -844,9 +828,9 @@ type mockBlockStore struct { } // TODO: NewBlockStore(db.NewMemDB) ... -func newMockBlockStore(t *testing.T, config *cfg.Config, params types.ConsensusParams) *mockBlockStore { +func newMockBlockStore(t *testing.T, cfg *config.Config, params types.ConsensusParams) *mockBlockStore { return &mockBlockStore{ - config: config, + cfg: cfg, params: params, t: t, } @@ -873,6 +857,8 @@ func (bs *mockBlockStore) LoadBlockMeta(height int64) *types.BlockMeta { func (bs *mockBlockStore) LoadBlockPart(height int64, index int) *types.Part { return nil } func (bs *mockBlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) { } +func (bs *mockBlockStore) SaveBlockWithExtendedCommit(block *types.Block, blockParts *types.PartSet, seenExtCommit *types.ExtendedCommit) { +} func (bs *mockBlockStore) LoadBlockCommit(height int64) *types.Commit { return bs.commits[height-1] } diff --git a/consensus/state.go b/consensus/state.go index e2e21a3d7..1c2607351 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -2,6 +2,7 @@ package consensus import ( "bytes" + "context" "errors" "fmt" "io" @@ -1211,16 +1212,16 @@ func (cs *State) createProposalBlock() (*types.Block, error) { return nil, errors.New("entered createProposalBlock with privValidator being nil") } - var commit *types.Commit + var lastExtCommit *types.ExtendedCommit switch { case cs.Height == cs.state.InitialHeight: // We're creating a proposal for the first block. // The commit is empty, but not nil. - commit = types.NewCommit(0, 0, types.BlockID{}, nil) + lastExtCommit = &types.ExtendedCommit{} case cs.LastCommit.HasTwoThirdsMajority(): // Make the commit from LastCommit - commit = cs.LastCommit.MakeCommit() + lastExtCommit = cs.LastCommit.MakeExtendedCommit() default: // This shouldn't happen. return nil, errors.New("propose step; cannot propose anything without commit for the previous block") @@ -1234,7 +1235,7 @@ func (cs *State) createProposalBlock() (*types.Block, error) { proposerAddr := cs.privValidatorPubKey.Address() - ret, err := cs.blockExec.CreateProposalBlock(cs.Height, cs.state, commit, proposerAddr, cs.LastCommit.GetVotes()) + ret, err := cs.blockExec.CreateProposalBlock(cs.Height, cs.state, lastExtCommit, proposerAddr, cs.LastCommit.GetVotes()) if err != nil { panic(err) } @@ -1656,9 +1657,12 @@ func (cs *State) finalizeCommit(height int64) { if cs.blockStore.Height() < block.Height { // 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, blockParts, seenCommit) + seenExtendedCommit := cs.Votes.Precommits(cs.CommitRound).MakeExtendedCommit() + if cs.state.ConsensusParams.ABCI.VoteExtensionsEnabled(block.Height) { + cs.blockStore.SaveBlockWithExtendedCommit(block, blockParts, seenExtendedCommit) + } else { + cs.blockStore.SaveBlock(block, blockParts, seenExtendedCommit.ToCommit()) + } } else { // Happens during replay if we already saved the block but didn't commit logger.Debug("calling finalizeCommit on already stored block", "height", block.Height) @@ -1968,7 +1972,7 @@ func (cs *State) handleCompleteProposal(blockHeight int64) { // Update Valid* if we can. prevotes := cs.Votes.Prevotes(cs.Round) blockID, hasTwoThirds := prevotes.TwoThirdsMajority() - if hasTwoThirds && !blockID.IsZero() && (cs.ValidRound < cs.Round) { + if hasTwoThirds && !blockID.IsNil() && (cs.ValidRound < cs.Round) { if cs.ProposalBlock.HashesTo(blockID.Hash) { cs.Logger.Debug( "updating valid block to new proposal block", @@ -2099,6 +2103,48 @@ func (cs *State) addVote(vote *types.Vote, peerID p2p.ID) (added bool, err error return } + // Check to see if the chain is configured to extend votes. + if cs.state.ConsensusParams.ABCI.VoteExtensionsEnabled(cs.Height) { + // The chain is configured to extend votes, check that the vote is + // not for a nil block and verify the extensions signature against the + // corresponding public key. + + var myAddr []byte + if cs.privValidatorPubKey != nil { + myAddr = cs.privValidatorPubKey.Address() + } + // Verify VoteExtension if precommit and not nil + // https://github.com/tendermint/tendermint/issues/8487 + if vote.Type == tmproto.PrecommitType && !vote.BlockID.IsNil() && + !bytes.Equal(vote.ValidatorAddress, myAddr) { // Skip the VerifyVoteExtension call if the vote was issued by this validator. + + // The core fields of the vote message were already validated in the + // consensus reactor when the vote was received. + // Here, we verify the signature of the vote extension included in the vote + // message. + _, val := cs.state.Validators.GetByIndex(vote.ValidatorIndex) + if err := vote.VerifyExtension(cs.state.ChainID, val.PubKey); err != nil { + return false, fmt.Errorf("invalid vote extension: %w; vote: %v", err, vote) + } + + err := cs.blockExec.VerifyVoteExtension(context.TODO(), vote) + cs.metrics.MarkVoteExtensionReceived(err == nil) + if err != nil { + return false, fmt.Errorf("application rejected vote extension: %w", err) + } + } + } else { + // Vote extensions are not enabled on the network. + // strip the extension data from the vote in case any is present. + // + // TODO punish a peer if it sent a vote with an extension when the feature + // is disabled on the network. + // https://github.com/tendermint/tendermint/issues/8565 + if hadExtension := vote.StripExtension(); hadExtension { + cs.Logger.Error("vote included extension data but vote extensions are not enabled; extension ignored", "peer", peerID) + } + } + height := cs.Height added, err = cs.Votes.AddVote(vote, peerID) if !added { @@ -2260,9 +2306,30 @@ func (cs *State) signVote( BlockID: types.BlockID{Hash: hash, PartSetHeader: header}, } + if msgType == tmproto.PrecommitType && !vote.BlockID.IsNil() { + // if the signedMessage type is for a non-nil precommit, add + // VoteExtension + if cs.state.ConsensusParams.ABCI.VoteExtensionsEnabled(cs.Height) { + ext, err := cs.blockExec.ExtendVote(context.TODO(), vote) + if err != nil { + return nil, err + } + vote.Extension = ext + } + } + v := vote.ToProto() err := cs.privValidator.SignVote(cs.state.ChainID, v) vote.Signature = v.Signature + // append the extension signature if expect it + if msgType == tmproto.PrecommitType && !vote.BlockID.IsNil() && + cs.state.ConsensusParams.ABCI.VoteExtensionsEnabled(cs.Height) { + // defensively check that the signer correctly signed the vote extension + if len(v.ExtensionSignature) == 0 { + panic(fmt.Sprintf("expected signer to sign vote extension but empty signature returned (%d/%d)", vote.Height, vote.Round)) + } + vote.ExtensionSignature = v.ExtensionSignature + } vote.Timestamp = v.Timestamp return vote, err diff --git a/consensus/state_test.go b/consensus/state_test.go index 17f8e8364..05db202da 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -14,8 +14,10 @@ import ( "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" abcimocks "github.com/tendermint/tendermint/abci/types/mocks" + "github.com/tendermint/tendermint/config" cstypes "github.com/tendermint/tendermint/consensus/types" "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/internal/test" tmbytes "github.com/tendermint/tendermint/libs/bytes" "github.com/tendermint/tendermint/libs/log" tmpubsub "github.com/tendermint/tendermint/libs/pubsub" @@ -60,7 +62,7 @@ x * TestHalt1 - if we see +2/3 precommits after timing out into new round, we sh // ProposeSuite func TestStateProposerSelection0(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{}) height, round := cs1.Height, cs1.Round newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) @@ -84,7 +86,7 @@ func TestStateProposerSelection0(t *testing.T) { ensureNewProposal(proposalCh, height, round) rs := cs1.GetRoundState() - signAddVotes(cs1, tmproto.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:]...) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NewBlockID(rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header()), vss[1:]...) // Wait for new round so next validator is set. ensureNewRound(newRoundCh, height+1, 0) @@ -100,7 +102,7 @@ func TestStateProposerSelection0(t *testing.T) { // Now let's do it all again, but starting from round 2 instead of 0 func TestStateProposerSelection2(t *testing.T) { - cs1, vss := randState(4) // test needs more work for more than 3 validators + cs1, vss := makeState(t, makeStateArgs{}) height := cs1.Height newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) @@ -128,7 +130,7 @@ func TestStateProposerSelection2(t *testing.T) { } rs := cs1.GetRoundState() - signAddVotes(cs1, tmproto.PrecommitType, nil, rs.ProposalBlockParts.Header(), vss[1:]...) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NewBlockID(nil, rs.ProposalBlockParts.Header()), vss[1:]...) ensureNewRound(newRoundCh, height, i+round+1) // wait for the new round event each round incrementRound(vss[1:]...) } @@ -137,7 +139,7 @@ func TestStateProposerSelection2(t *testing.T) { // a non-validator should timeout into the prevote round func TestStateEnterProposeNoPrivValidator(t *testing.T) { - cs, _ := randState(1) + cs, _ := makeState(t, makeStateArgs{validators: 1}) cs.SetPrivValidator(nil) height, round := cs.Height, cs.Round @@ -156,7 +158,7 @@ func TestStateEnterProposeNoPrivValidator(t *testing.T) { // a validator should not timeout of the prevote round (TODO: unless the block is really big!) func TestStateEnterProposeYesPrivValidator(t *testing.T) { - cs, _ := randState(1) + cs, _ := makeState(t, makeStateArgs{validators: 1}) height, round := cs.Height, cs.Round // Listen for propose timeout event @@ -186,7 +188,7 @@ func TestStateEnterProposeYesPrivValidator(t *testing.T) { } func TestStateBadProposal(t *testing.T) { - cs1, vss := randState(2) + cs1, vss := makeState(t, makeStateArgs{validators: 2}) height, round := cs1.Height, cs1.Round vs2 := vss[1] @@ -214,7 +216,7 @@ func TestStateBadProposal(t *testing.T) { blockID := types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()} proposal := types.NewProposal(vs2.Height, round, -1, blockID) p := proposal.ToProto() - if err := vs2.SignProposal(config.ChainID(), p); err != nil { + if err := vs2.SignProposal(test.DefaultTestChainID, p); err != nil { t.Fatal("failed to sign bad proposal", err) } @@ -239,7 +241,11 @@ func TestStateBadProposal(t *testing.T) { bps, err := propBlock.MakePartSet(partSize) require.NoError(t, err) - signAddVotes(cs1, tmproto.PrevoteType, propBlock.Hash(), bps.Header(), vs2) + blockID = types.BlockID{ + Hash: propBlock.Hash(), + PartSetHeader: bps.Header(), + } + signAddVotes(t, cs1, tmproto.PrevoteType, blockID, vs2) ensurePrevote(voteCh, height, round) // wait for precommit @@ -248,11 +254,12 @@ func TestStateBadProposal(t *testing.T) { bps2, err := propBlock.MakePartSet(partSize) require.NoError(t, err) - signAddVotes(cs1, tmproto.PrecommitType, propBlock.Hash(), bps2.Header(), vs2) + blockID.PartSetHeader = bps2.Header() + signAddVotes(t, cs1, tmproto.PrecommitType, blockID, vs2) } func TestStateOversizedBlock(t *testing.T) { - cs1, vss := randState(2) + cs1, vss := makeState(t, makeStateArgs{validators: 2}) cs1.state.ConsensusParams.Block.MaxBytes = 2000 height, round := cs1.Height, cs1.Round vs2 := vss[1] @@ -276,7 +283,7 @@ func TestStateOversizedBlock(t *testing.T) { blockID := types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()} proposal := types.NewProposal(height, round, -1, blockID) p := proposal.ToProto() - if err := vs2.SignProposal(config.ChainID(), p); err != nil { + if err := vs2.SignProposal(test.DefaultTestChainID, p); err != nil { t.Fatal("failed to sign bad proposal", err) } proposal.Signature = p.Signature @@ -305,17 +312,10 @@ func TestStateOversizedBlock(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], nil) - bps, err := propBlock.MakePartSet(partSize) - require.NoError(t, err) - - signAddVotes(cs1, tmproto.PrevoteType, propBlock.Hash(), bps.Header(), vs2) + signAddVotes(t, cs1, tmproto.PrevoteType, blockID, vs2) ensurePrevote(voteCh, height, round) ensurePrecommit(voteCh, height, round) validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) - - bps2, err := propBlock.MakePartSet(partSize) - require.NoError(t, err) - signAddVotes(cs1, tmproto.PrecommitType, propBlock.Hash(), bps2.Header(), vs2) } //---------------------------------------------------------------------------------------------------- @@ -323,7 +323,7 @@ func TestStateOversizedBlock(t *testing.T) { // propose, prevote, and precommit a block func TestStateFullRound1(t *testing.T) { - cs, vss := randState(1) + cs, vss := makeState(t, makeStateArgs{validators: 1}) height, round := cs.Height, cs.Round // NOTE: buffer capacity of 0 ensures we can validate prevote and last commit @@ -363,7 +363,7 @@ func TestStateFullRound1(t *testing.T) { // nil is proposed, so prevote and precommit nil func TestStateFullRoundNil(t *testing.T) { - cs, vss := randState(1) + cs, vss := makeState(t, makeStateArgs{validators: 1}) height, round := cs.Height, cs.Round voteCh := subscribeUnBuffered(cs.eventBus, types.EventQueryVote) @@ -381,7 +381,7 @@ func TestStateFullRoundNil(t *testing.T) { // run through propose, prevote, precommit commit with two validators // where the first validator has to wait for votes from the second func TestStateFullRound2(t *testing.T) { - cs1, vss := randState(2) + cs1, vss := makeState(t, makeStateArgs{validators: 2}) vs2 := vss[1] height, round := cs1.Height, cs1.Round @@ -396,9 +396,10 @@ func TestStateFullRound2(t *testing.T) { // we should be stuck in limbo waiting for more prevotes rs := cs1.GetRoundState() propBlockHash, propPartSetHeader := rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header() + blockID := types.BlockID{Hash: propBlockHash, PartSetHeader: propPartSetHeader} // prevote arrives from vs2: - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, propPartSetHeader, vs2) + signAddVotes(t, cs1, tmproto.PrevoteType, blockID, vs2) ensurePrevote(voteCh, height, round) // prevote ensurePrecommit(voteCh, height, round) // precommit @@ -408,7 +409,7 @@ func TestStateFullRound2(t *testing.T) { // we should be stuck in limbo waiting for more precommits // precommit arrives from vs2: - signAddVotes(cs1, tmproto.PrecommitType, propBlockHash, propPartSetHeader, vs2) + signAddVotes(t, cs1, tmproto.PrecommitType, blockID, vs2) ensurePrecommit(voteCh, height, round) // wait to finish commit, propose in next height @@ -421,7 +422,7 @@ func TestStateFullRound2(t *testing.T) { // two validators, 4 rounds. // two vals take turns proposing. val1 locks on first one, precommits nil on everything else func TestStateLockNoPOL(t *testing.T) { - cs1, vss := randState(2) + cs1, vss := makeState(t, makeStateArgs{validators: 2}) vs2 := vss[1] height, round := cs1.Height, cs1.Round @@ -447,12 +448,13 @@ func TestStateLockNoPOL(t *testing.T) { roundState := cs1.GetRoundState() theBlockHash := roundState.ProposalBlock.Hash() thePartSetHeader := roundState.ProposalBlockParts.Header() + blockID := types.BlockID{Hash: theBlockHash, PartSetHeader: thePartSetHeader} ensurePrevote(voteCh, height, round) // prevote // we should now be stuck in limbo forever, waiting for more prevotes // prevote arrives from vs2: - signAddVotes(cs1, tmproto.PrevoteType, theBlockHash, thePartSetHeader, vs2) + signAddVotes(t, cs1, tmproto.PrevoteType, blockID, vs2) ensurePrevote(voteCh, height, round) // prevote ensurePrecommit(voteCh, height, round) // precommit @@ -464,7 +466,8 @@ func TestStateLockNoPOL(t *testing.T) { hash := make([]byte, len(theBlockHash)) copy(hash, theBlockHash) hash[0] = (hash[0] + 1) % 255 - signAddVotes(cs1, tmproto.PrecommitType, hash, thePartSetHeader, vs2) + blockID.Hash = hash + signAddVotes(t, cs1, tmproto.PrecommitType, blockID, vs2) ensurePrecommit(voteCh, height, round) // precommit // (note we're entering precommit for a second time this round) @@ -497,8 +500,9 @@ func TestStateLockNoPOL(t *testing.T) { // add a conflicting prevote from the other validator bps, err := rs.LockedBlock.MakePartSet(partSize) require.NoError(t, err) + blockID.PartSetHeader = bps.Header() - signAddVotes(cs1, tmproto.PrevoteType, hash, bps.Header(), vs2) + signAddVotes(t, cs1, tmproto.PrevoteType, blockID, vs2) ensurePrevote(voteCh, height, round) // now we're going to enter prevote again, but with invalid args @@ -513,7 +517,8 @@ func TestStateLockNoPOL(t *testing.T) { // add conflicting precommit from vs2 bps2, err := rs.LockedBlock.MakePartSet(partSize) require.NoError(t, err) - signAddVotes(cs1, tmproto.PrecommitType, hash, bps2.Header(), vs2) + blockID.PartSetHeader = bps2.Header() + signAddVotes(t, cs1, tmproto.PrecommitType, blockID, vs2) ensurePrecommit(voteCh, height, round) // (note we're entering precommit for a second time this round, but with invalid args @@ -545,7 +550,8 @@ func TestStateLockNoPOL(t *testing.T) { bps0, err := rs.ProposalBlock.MakePartSet(partSize) require.NoError(t, err) - signAddVotes(cs1, tmproto.PrevoteType, hash, bps0.Header(), vs2) + blockID.PartSetHeader = bps0.Header() + signAddVotes(t, cs1, tmproto.PrevoteType, blockID, vs2) ensurePrevote(voteCh, height, round) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds()) @@ -555,17 +561,17 @@ func TestStateLockNoPOL(t *testing.T) { bps1, err := rs.ProposalBlock.MakePartSet(partSize) require.NoError(t, err) - signAddVotes( + blockID.PartSetHeader = bps1.Header() + signAddVotes(t, cs1, tmproto.PrecommitType, - hash, - bps1.Header(), + blockID, vs2) // NOTE: conflicting precommits at same height ensurePrecommit(voteCh, height, round) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) - cs2, _ := randState(2) // needed so generated block is different than locked block + cs2, _ := makeState(t, makeStateArgs{validators: 2}) // needed so generated block is different than locked block // before we time out into new round, set next proposal block prop, propBlock := decideProposal(t, cs2, vs2, vs2.Height, vs2.Round+1) if prop == nil || propBlock == nil { @@ -598,7 +604,7 @@ func TestStateLockNoPOL(t *testing.T) { bps4, err := propBlock.MakePartSet(partSize) require.NoError(t, err) - signAddVotes(cs1, tmproto.PrevoteType, propBlock.Hash(), bps4.Header(), vs2) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(propBlock.Hash(), bps4.Header()), vs2) ensurePrevote(voteCh, height, round) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds()) @@ -608,10 +614,10 @@ func TestStateLockNoPOL(t *testing.T) { bps5, err := propBlock.MakePartSet(partSize) require.NoError(t, err) signAddVotes( + t, cs1, tmproto.PrecommitType, - propBlock.Hash(), - bps5.Header(), + types.NewBlockID(propBlock.Hash(), bps5.Header()), vs2) // NOTE: conflicting precommits at same height ensurePrecommit(voteCh, height, round) } @@ -621,7 +627,7 @@ func TestStateLockNoPOL(t *testing.T) { // in round two: v1 prevotes the same block that the node is locked on // the others prevote a new block hence v1 changes lock and precommits the new block with the others func TestStateLockPOLRelock(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{validators: 4}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -655,17 +661,17 @@ func TestStateLockPOLRelock(t *testing.T) { ensurePrevote(voteCh, height, round) // prevote - signAddVotes(cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(theBlockHash, theBlockParts), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // our precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) // add precommits from the rest - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NewBlockID(nil, types.PartSetHeader{}), vs2, vs3, vs4) // before we timeout to the new round set the new proposal - cs2 := newState(cs1.state, vs2, kvstore.NewInMemoryApplication()) + cs2 := newState(t, config.TestConfig(), cs1.state, vs2, kvstore.NewInMemoryApplication()) prop, propBlock := decideProposal(t, cs2, vs2, vs2.Height, vs2.Round+1) if prop == nil || propBlock == nil { t.Fatal("Failed to create proposal block with vs2") @@ -705,14 +711,14 @@ func TestStateLockPOLRelock(t *testing.T) { validatePrevote(t, cs1, round, vss[0], theBlockHash) // now lets add prevotes from everyone else for the new block - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(propBlockHash, propBlockParts.Header()), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // we should have unlocked and locked on the new block, sending a precommit for this new block validatePrecommit(t, cs1, round, round, vss[0], propBlockHash, propBlockHash) // more prevote creating a majority on the new block and this is then committed - signAddVotes(cs1, tmproto.PrecommitType, propBlockHash, propBlockParts.Header(), vs2, vs3) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NewBlockID(propBlockHash, propBlockParts.Header()), vs2, vs3) ensureNewBlockHeader(newBlockCh, height, propBlockHash) ensureNewRound(newRoundCh, height+1, 0) @@ -720,7 +726,7 @@ func TestStateLockPOLRelock(t *testing.T) { // 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka func TestStateLockPOLUnlock(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{validators: 4}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -754,15 +760,15 @@ func TestStateLockPOLUnlock(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], theBlockHash) - signAddVotes(cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(theBlockHash, theBlockParts), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) // add precommits from the rest - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs4) - signAddVotes(cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs3) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NilBlockID(), vs2, vs4) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NewBlockID(theBlockHash, theBlockParts), vs3) // before we time out into new round, set next proposal block prop, propBlock := decideProposal(t, cs1, vs2, vs2.Height, vs2.Round+1) @@ -794,7 +800,7 @@ func TestStateLockPOLUnlock(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], lockedBlockHash) // now lets add prevotes from everyone else for nil (a polka!) - signAddVotes(cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NilBlockID(), vs2, vs3, vs4) // the polka makes us unlock and precommit nil ensureNewUnlock(unlockCh, height, round) @@ -804,7 +810,7 @@ func TestStateLockPOLUnlock(t *testing.T) { // NOTE: since we don't relock on nil, the lock round is -1 validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NilBlockID(), vs2, vs3) ensureNewRound(newRoundCh, height, round+1) } @@ -813,7 +819,7 @@ func TestStateLockPOLUnlock(t *testing.T) { // v1 should unlock and precommit nil. In the third round another block is proposed, all vals // prevote and now v1 can lock onto the third block and precommit that func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{validators: 4}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -843,17 +849,17 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { ensurePrevote(voteCh, height, round) // prevote - signAddVotes(cs1, tmproto.PrevoteType, firstBlockHash, firstBlockParts, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(firstBlockHash, firstBlockParts), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // our precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], firstBlockHash, firstBlockHash) // add precommits from the rest - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NilBlockID(), vs2, vs3, vs4) // before we timeout to the new round set the new proposal - cs2 := newState(cs1.state, vs2, kvstore.NewInMemoryApplication()) + cs2 := newState(t, config.TestConfig(), cs1.state, vs2, kvstore.NewInMemoryApplication()) prop, propBlock := decideProposal(t, cs2, vs2, vs2.Height, vs2.Round+1) if prop == nil || propBlock == nil { t.Fatal("Failed to create proposal block with vs2") @@ -885,7 +891,7 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { validatePrevote(t, cs1, round, vss[0], firstBlockHash) // now lets add prevotes from everyone else for the new block - signAddVotes(cs1, tmproto.PrevoteType, secondBlockHash, secondBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(secondBlockHash, secondBlockParts.Header()), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // we should have unlocked and locked on the new block, sending a precommit for this new block @@ -896,10 +902,10 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { } // more prevote creating a majority on the new block and this is then committed - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NilBlockID(), vs2, vs3, vs4) // before we timeout to the new round set the new proposal - cs3 := newState(cs1.state, vs3, kvstore.NewInMemoryApplication()) + cs3 := newState(t, config.TestConfig(), cs1.state, vs3, kvstore.NewInMemoryApplication()) prop, propBlock = decideProposal(t, cs3, vs3, vs3.Height, vs3.Round+1) if prop == nil || propBlock == nil { t.Fatal("Failed to create proposal block with vs2") @@ -930,7 +936,7 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { // we are no longer locked to the first block so we should be able to prevote validatePrevote(t, cs1, round, vss[0], thirdPropBlockHash) - signAddVotes(cs1, tmproto.PrevoteType, thirdPropBlockHash, thirdPropBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(thirdPropBlockHash, thirdPropBlockParts.Header()), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // we have a majority, now vs1 can change lock to the third block @@ -942,7 +948,7 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { // then a polka at round 2 that we lock on // then we see the polka from round 1 but shouldn't unlock func TestStateLockPOLSafety1(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{validators: 4}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -972,12 +978,12 @@ func TestStateLockPOLSafety1(t *testing.T) { bps, err := propBlock.MakePartSet(partSize) require.NoError(t, err) - prevotes := signVotes(tmproto.PrevoteType, propBlock.Hash(), bps.Header(), vs2, vs3, vs4) + prevotes := signVotes(t, tmproto.PrevoteType, test.DefaultTestChainID, types.NewBlockID(propBlock.Hash(), bps.Header()), vs2, vs3, vs4) t.Logf("old prop hash %v", fmt.Sprintf("%X", propBlock.Hash())) // we do see them precommit nil - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NilBlockID(), vs2, vs3, vs4) // cs1 precommit nil ensurePrecommit(voteCh, height, round) @@ -1018,13 +1024,13 @@ func TestStateLockPOLSafety1(t *testing.T) { validatePrevote(t, cs1, round, vss[0], propBlockHash) // now we see the others prevote for it, so we should lock on it - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(propBlockHash, propBlockParts.Header()), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // we should have precommitted validatePrecommit(t, cs1, round, round, vss[0], propBlockHash, propBlockHash) - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NilBlockID(), vs2, vs3, vs4) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) @@ -1065,7 +1071,7 @@ func TestStateLockPOLSafety1(t *testing.T) { // What we want: // dont see P0, lock on P1 at R1, dont unlock using P0 at R2 func TestStateLockPOLSafety2(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{validators: 4}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1089,7 +1095,7 @@ func TestStateLockPOLSafety2(t *testing.T) { propBlockID0 := types.BlockID{Hash: propBlockHash0, PartSetHeader: propBlockParts0.Header()} // the others sign a polka but we don't see it - prevotes := signVotes(tmproto.PrevoteType, propBlockHash0, propBlockParts0.Header(), vs2, vs3, vs4) + prevotes := signVotes(t, tmproto.PrevoteType, test.DefaultTestChainID, types.NewBlockID(propBlockHash0, propBlockParts0.Header()), vs2, vs3, vs4) // the block for round 1 prop1, propBlock1 := decideProposal(t, cs1, vs2, vs2.Height, vs2.Round+1) @@ -1113,15 +1119,15 @@ func TestStateLockPOLSafety2(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], propBlockHash1) - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash1, propBlockParts1.Header(), vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(propBlockHash1, propBlockParts1.Header()), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], propBlockHash1, propBlockHash1) // add precommits from the rest - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs4) - signAddVotes(cs1, tmproto.PrecommitType, propBlockHash1, propBlockParts1.Header(), vs3) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NilBlockID(), vs2, vs4) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NewBlockID(propBlockHash1, propBlockParts1.Header()), vs3) incrementRound(vs2, vs3, vs4) @@ -1132,7 +1138,7 @@ func TestStateLockPOLSafety2(t *testing.T) { // in round 2 we see the polkad block from round 0 newProp := types.NewProposal(height, round, 0, propBlockID0) p := newProp.ToProto() - if err := vs3.SignProposal(config.ChainID(), p); err != nil { + if err := vs3.SignProposal(test.DefaultTestChainID, p); err != nil { t.Fatal(err) } @@ -1164,7 +1170,7 @@ func TestStateLockPOLSafety2(t *testing.T) { // What we want: // P0 proposes B0 at R3. func TestProposeValidBlock(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1195,13 +1201,13 @@ func TestProposeValidBlock(t *testing.T) { // the others sign a polka bps, err := propBlock.MakePartSet(partSize) require.NoError(t, err) - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, bps.Header(), vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(propBlockHash, bps.Header()), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // we should have precommitted validatePrecommit(t, cs1, round, round, vss[0], propBlockHash, propBlockHash) - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NilBlockID(), vs2, vs3, vs4) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) @@ -1218,7 +1224,7 @@ func TestProposeValidBlock(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], propBlockHash) - signAddVotes(cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NilBlockID(), vs2, vs3, vs4) ensureNewUnlock(unlockCh, height, round) @@ -1229,7 +1235,7 @@ func TestProposeValidBlock(t *testing.T) { incrementRound(vs2, vs3, vs4) incrementRound(vs2, vs3, vs4) - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NilBlockID(), vs2, vs3, vs4) round += 2 // moving to the next round @@ -1256,7 +1262,7 @@ func TestProposeValidBlock(t *testing.T) { // What we want: // P0 miss to lock B but set valid block to B after receiving delayed prevote. func TestSetValidBlockOnDelayedPrevote(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{validators: 4}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1286,10 +1292,10 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { validatePrevote(t, cs1, round, vss[0], propBlockHash) // vs2 send prevote for propBlock - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs2) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(propBlockHash, propBlockParts.Header()), vs2) // vs3 send prevote nil - signAddVotes(cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs3) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NilBlockID(), vs3) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds()) @@ -1304,7 +1310,7 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { assert.True(t, rs.ValidRound == -1) // vs2 send (delayed) prevote for propBlock - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(propBlockHash, propBlockParts.Header()), vs4) ensureNewValidBlock(validBlockCh, height, round) @@ -1319,7 +1325,7 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { // P0 miss to lock B as Proposal Block is missing, but set valid block to B after // receiving delayed Block Proposal. func TestSetValidBlockOnDelayedProposal(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{validators: 4}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1352,7 +1358,7 @@ func TestSetValidBlockOnDelayedProposal(t *testing.T) { require.NoError(t, err) // vs2, vs3 and vs4 send prevote for propBlock - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(propBlockHash, propBlockParts.Header()), vs2, vs3, vs4) ensureNewValidBlock(validBlockCh, height, round) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds()) @@ -1397,7 +1403,7 @@ func TestProcessProposalAccept(t *testing.T) { } m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{Status: status}, nil) m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil).Maybe() - cs1, _ := randStateWithApp(4, m) + cs1, _ := makeState(t, makeStateArgs{application: m}) height, round := cs1.Height, cs1.Round proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) @@ -1421,11 +1427,459 @@ func TestProcessProposalAccept(t *testing.T) { } } +func TestFinalizeBlockCalled(t *testing.T) { + for _, testCase := range []struct { + name string + voteNil bool + expectCalled bool + }{ + { + name: "finalize block called when block committed", + voteNil: false, + expectCalled: true, + }, + { + name: "not called when block not committed", + voteNil: true, + expectCalled: false, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + m := abcimocks.NewApplication(t) + m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{ + Status: abci.ResponseProcessProposal_ACCEPT, + }, nil) + m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil) + // We only expect VerifyVoteExtension to be called on non-nil precommits. + // https://github.com/tendermint/tendermint/issues/8487 + if !testCase.voteNil { + m.On("ExtendVote", mock.Anything, mock.Anything).Return(&abci.ResponseExtendVote{}, nil) + m.On("VerifyVoteExtension", mock.Anything, mock.Anything).Return(&abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_ACCEPT, + }, nil) + } + r := &abci.ResponseFinalizeBlock{AgreedAppData: []byte("the_hash")} + m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(r, nil).Maybe() + m.On("Commit", mock.Anything, mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() + + cs1, vss := makeState(t, makeStateArgs{application: m}) + height, round := cs1.Height, cs1.Round + + proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + pv1, err := cs1.privValidator.GetPubKey() + require.NoError(t, err) + addr := pv1.Address() + voteCh := subscribeToVoter(cs1, addr) + + startTestRound(cs1, cs1.Height, round) + ensureNewRound(newRoundCh, height, round) + ensureNewProposal(proposalCh, height, round) + rs := cs1.GetRoundState() + + blockID := types.NilBlockID() + nextRound := round + 1 + nextHeight := height + if !testCase.voteNil { + nextRound = 0 + nextHeight = height + 1 + blockID = types.BlockID{ + Hash: rs.ProposalBlock.Hash(), + PartSetHeader: rs.ProposalBlockParts.Header(), + } + } + + signAddVotes(t, cs1, tmproto.PrevoteType, blockID, vss[1:]...) + ensurePrevoteMatch(t, voteCh, height, round, rs.ProposalBlock.Hash()) + + signAddVotes(t, cs1, tmproto.PrecommitType, blockID, vss[1:]...) + ensurePrecommit(voteCh, height, round) + + ensureNewRound(newRoundCh, nextHeight, nextRound) + m.AssertExpectations(t) + + if testCase.expectCalled { + m.AssertCalled(t, "FinalizeBlock", context.TODO(), mock.Anything) + } else { + m.AssertNotCalled(t, "FinalizeBlock", mock.Anything, mock.Anything) + } + }) + } +} + +// TestExtendVoteCalledWhenEnabled tests that the vote extension methods are called at the +// correct point in the consensus algorithm when vote extensions are enabled. +func TestExtendVoteCalledWhenEnabled(t *testing.T) { + for _, testCase := range []struct { + name string + enabled bool + }{ + { + name: "enabled", + enabled: true, + }, + { + name: "disabled", + enabled: false, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + m := abcimocks.NewApplication(t) + m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil) + m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil) + if testCase.enabled { + m.On("ExtendVote", mock.Anything, mock.Anything).Return(&abci.ResponseExtendVote{ + VoteExtension: []byte("extension"), + }, nil) + m.On("VerifyVoteExtension", mock.Anything, mock.Anything).Return(&abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_ACCEPT, + }, nil) + } + m.On("Commit", mock.Anything, mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() + r := &abci.ResponseFinalizeBlock{AgreedAppData: []byte("myHash")} + m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(r, nil).Maybe() + cs1, vss := makeState(t, makeStateArgs{application: m}) + if !testCase.enabled { + cs1.state.ConsensusParams.ABCI.VoteExtensionsEnableHeight = 0 + } + height, round := cs1.Height, cs1.Round + + proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + pv1, err := cs1.privValidator.GetPubKey() + require.NoError(t, err) + addr := pv1.Address() + voteCh := subscribeToVoter(cs1, addr) + + startTestRound(cs1, cs1.Height, round) + ensureNewRound(newRoundCh, height, round) + ensureNewProposal(proposalCh, height, round) + + m.AssertNotCalled(t, "ExtendVote", mock.Anything, mock.Anything) + + rs := cs1.GetRoundState() + + blockID := types.BlockID{ + Hash: rs.ProposalBlock.Hash(), + PartSetHeader: rs.ProposalBlockParts.Header(), + } + signAddVotes(t, cs1, tmproto.PrevoteType, blockID, vss[1:]...) + ensurePrevoteMatch(t, voteCh, height, round, blockID.Hash) + + ensurePrecommit(voteCh, height, round) + + if testCase.enabled { + m.AssertCalled(t, "ExtendVote", context.TODO(), &abci.RequestExtendVote{ + Height: height, + BlockHash: blockID.Hash, + }) + } else { + m.AssertNotCalled(t, "ExtendVote", mock.Anything, mock.Anything) + } + + signAddVotes(t, cs1, tmproto.PrecommitType, blockID, vss[1:]...) + ensureNewRound(newRoundCh, height+1, 0) + m.AssertExpectations(t) + + // Only 3 of the vote extensions are seen, as consensus proceeds as soon as the +2/3 threshold + // is observed by the consensus engine. + for _, pv := range vss[1:3] { + pv, err := pv.GetPubKey() + require.NoError(t, err) + addr := pv.Address() + if testCase.enabled { + m.AssertCalled(t, "VerifyVoteExtension", context.TODO(), &abci.RequestVerifyVoteExtension{ + BlockHash: blockID.Hash, + ValidatorAddress: addr, + Height: height, + VoteExtension: []byte("extension"), + }) + } else { + m.AssertNotCalled(t, "VerifyVoteExtension", mock.Anything, mock.Anything) + } + } + }) + } + +} + +// TestVerifyVoteExtensionNotCalledOnAbsentPrecommit tests that the VerifyVoteExtension +// method is not called for a validator's vote that is never delivered. +func TestVerifyVoteExtensionNotCalledOnAbsentPrecommit(t *testing.T) { + m := abcimocks.NewApplication(t) + m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil) + m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil) + m.On("ExtendVote", mock.Anything, mock.Anything).Return(&abci.ResponseExtendVote{ + VoteExtension: []byte("extension"), + }, nil) + m.On("VerifyVoteExtension", mock.Anything, mock.Anything).Return(&abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_ACCEPT, + }, nil) + m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(&abci.ResponseFinalizeBlock{}, nil).Maybe() + cs1, vss := makeState(t, makeStateArgs{application: m}) + height, round := cs1.Height, cs1.Round + cs1.state.ConsensusParams.ABCI.VoteExtensionsEnableHeight = cs1.Height + + proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + pv1, err := cs1.privValidator.GetPubKey() + require.NoError(t, err) + addr := pv1.Address() + voteCh := subscribeToVoter(cs1, addr) + + startTestRound(cs1, cs1.Height, round) + ensureNewRound(newRoundCh, height, round) + ensureNewProposal(proposalCh, height, round) + rs := cs1.GetRoundState() + + blockID := types.BlockID{ + Hash: rs.ProposalBlock.Hash(), + PartSetHeader: rs.ProposalBlockParts.Header(), + } + signAddVotes(t, cs1, tmproto.PrevoteType, blockID, vss...) + ensurePrevoteMatch(t, voteCh, height, round, blockID.Hash) + + ensurePrecommit(voteCh, height, round) + + m.AssertCalled(t, "ExtendVote", mock.Anything, &abci.RequestExtendVote{ + Height: height, + BlockHash: blockID.Hash, + }) + + m.On("Commit", mock.Anything, mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() + signAddVotes(t, cs1, tmproto.PrecommitType, blockID, vss[2:]...) + ensureNewRound(newRoundCh, height+1, 0) + m.AssertExpectations(t) + + // vss[1] did not issue a precommit for the block, ensure that a vote extension + // for its address was not sent to the application. + pv, err := vss[1].GetPubKey() + require.NoError(t, err) + addr = pv.Address() + + m.AssertNotCalled(t, "VerifyVoteExtension", context.TODO(), &abci.RequestVerifyVoteExtension{ + BlockHash: blockID.Hash, + ValidatorAddress: addr, + Height: height, + VoteExtension: []byte("extension"), + }) + +} + +// TestPrepareProposalReceivesVoteExtensions tests that the PrepareProposal method +// is called with the vote extensions from the previous height. The test functions +// be completing a consensus height with a mock application as the proposer. The +// test then proceeds to fail sever rounds of consensus until the mock application +// is the proposer again and ensures that the mock application receives the set of +// vote extensions from the previous consensus instance. +func TestPrepareProposalReceivesVoteExtensions(t *testing.T) { + // create a list of vote extensions, one for each validator. + voteExtensions := [][]byte{ + []byte("extension 0"), + []byte("extension 1"), + []byte("extension 2"), + []byte("extension 3"), + } + + // m := abcimocks.NewApplication(t) + m := &abcimocks.Application{} + m.On("ExtendVote", mock.Anything, mock.Anything).Return(&abci.ResponseExtendVote{ + VoteExtension: voteExtensions[0], + }, nil) + m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil) + + // capture the prepare proposal request. + rpp := &abci.RequestPrepareProposal{} + m.On("PrepareProposal", mock.Anything, mock.MatchedBy(func(r *abci.RequestPrepareProposal) bool { + rpp = r + return true + })).Return(&abci.ResponsePrepareProposal{}, nil) + + m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil).Once() + m.On("VerifyVoteExtension", mock.Anything, mock.Anything).Return(&abci.ResponseVerifyVoteExtension{Status: abci.ResponseVerifyVoteExtension_ACCEPT}, nil) + m.On("Commit", mock.Anything, mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() + m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(&abci.ResponseFinalizeBlock{}, nil) + + cs1, vss := makeState(t, makeStateArgs{application: m}) + height, round := cs1.Height, cs1.Round + + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) + pv1, err := cs1.privValidator.GetPubKey() + require.NoError(t, err) + addr := pv1.Address() + voteCh := subscribeToVoter(cs1, addr) + + startTestRound(cs1, height, round) + ensureNewRound(newRoundCh, height, round) + ensureNewProposal(proposalCh, height, round) + + rs := cs1.GetRoundState() + blockID := types.BlockID{ + Hash: rs.ProposalBlock.Hash(), + PartSetHeader: rs.ProposalBlockParts.Header(), + } + signAddVotes(t, cs1, tmproto.PrevoteType, blockID, vss[1:]...) + + // create a precommit for each validator with the associated vote extension. + for i, vs := range vss[1:] { + signAddPrecommitWithExtension(t, cs1, blockID, voteExtensions[i+1], vs) + } + + ensurePrevote(voteCh, height, round) + + // ensure that the height is committed. + ensurePrecommitMatch(t, voteCh, height, round, blockID.Hash) + incrementHeight(vss[1:]...) + + height++ + round = 0 + ensureNewRound(newRoundCh, height, round) + incrementRound(vss[1:]...) + incrementRound(vss[1:]...) + incrementRound(vss[1:]...) + round = 3 + + signAddVotes(t, cs1, tmproto.PrecommitType, types.NilBlockID(), vss[1:]...) + ensureNewRound(newRoundCh, height, round) + ensureNewProposal(proposalCh, height, round) + + // ensure that the proposer received the list of vote extensions from the + // previous height. + require.Len(t, rpp.LocalLastCommit.Votes, len(vss)) + for i := range vss { + require.Equal(t, voteExtensions[i], rpp.LocalLastCommit.Votes[i].VoteExtension) + } +} + +// TestVoteExtensionEnableHeight tests that 'ExtensionRequireHeight' correctly +// enforces that vote extensions be present in consensus for heights greater than +// or equal to the configured value. +func TestVoteExtensionEnableHeight(t *testing.T) { + for _, testCase := range []struct { + name string + enableHeight int64 + hasExtension bool + expectExtendCalled bool + expectVerifyCalled bool + expectSuccessfulRound bool + }{ + { + name: "extension present but not enabled", + hasExtension: true, + enableHeight: 0, + expectExtendCalled: false, + expectVerifyCalled: false, + expectSuccessfulRound: true, + }, + { + name: "extension absent but not required", + hasExtension: false, + enableHeight: 0, + expectExtendCalled: false, + expectVerifyCalled: false, + expectSuccessfulRound: true, + }, + { + name: "extension present and required", + hasExtension: true, + enableHeight: 1, + expectExtendCalled: true, + expectVerifyCalled: true, + expectSuccessfulRound: true, + }, + { + name: "extension absent but required", + hasExtension: false, + enableHeight: 1, + expectExtendCalled: true, + expectVerifyCalled: false, + expectSuccessfulRound: false, + }, + { + name: "extension absent but required in future height", + hasExtension: false, + enableHeight: 2, + expectExtendCalled: false, + expectVerifyCalled: false, + expectSuccessfulRound: true, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + numValidators := 3 + m := abcimocks.NewApplication(t) + m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{ + Status: abci.ResponseProcessProposal_ACCEPT, + }, nil) + m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil) + if testCase.expectExtendCalled { + m.On("ExtendVote", mock.Anything, mock.Anything).Return(&abci.ResponseExtendVote{}, nil) + } + if testCase.expectVerifyCalled { + m.On("VerifyVoteExtension", mock.Anything, mock.Anything).Return(&abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_ACCEPT, + }, nil).Times(numValidators - 1) + } + r := &abci.ResponseFinalizeBlock{AgreedAppData: []byte("hashyHash")} + m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(r, nil).Maybe() + m.On("Commit", mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() + cs1, vss := makeState(t, makeStateArgs{application: m}) + cs1.state.ConsensusParams.ABCI.VoteExtensionsEnableHeight = testCase.enableHeight + height, round := cs1.Height, cs1.Round + + timeoutCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) + proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + pv1, err := cs1.privValidator.GetPubKey() + require.NoError(t, err) + addr := pv1.Address() + voteCh := subscribeToVoter(cs1, addr) + + startTestRound(cs1, cs1.Height, round) + ensureNewRound(newRoundCh, height, round) + ensureNewProposal(proposalCh, height, round) + rs := cs1.GetRoundState() + + blockID := types.BlockID{ + Hash: rs.ProposalBlock.Hash(), + PartSetHeader: rs.ProposalBlockParts.Header(), + } + + // sign all of the votes + signAddVotes(t, cs1, tmproto.PrevoteType, blockID, vss[1:]...) + ensurePrevoteMatch(t, voteCh, height, round, rs.ProposalBlock.Hash()) + + var ext []byte + if testCase.hasExtension { + ext = []byte("extension") + } + + for _, vs := range vss[1:] { + vote, err := vs.signVote(tmproto.PrecommitType, test.DefaultTestChainID, blockID, ext) + if !testCase.hasExtension { + vote.ExtensionSignature = nil + } + require.NoError(t, err) + addVotes(cs1, vote) + } + if testCase.expectSuccessfulRound { + ensurePrecommit(voteCh, height, round) + height++ + ensureNewRound(newRoundCh, height, round) + } else { + ensureNoNewTimeout(timeoutCh, cs1.config.Precommit(round).Nanoseconds()) + } + + m.AssertExpectations(t) + }) + } +} + // 4 vals, 3 Nil Precommits at P0 // What we want: // P0 waits for timeoutPrecommit before starting next round func TestWaitingTimeoutOnNilPolka(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{validators: 4}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1436,7 +1890,7 @@ func TestWaitingTimeoutOnNilPolka(t *testing.T) { startTestRound(cs1, height, round) ensureNewRound(newRoundCh, height, round) - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NilBlockID(), vs2, vs3, vs4) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) ensureNewRound(newRoundCh, height, round+1) @@ -1446,7 +1900,7 @@ func TestWaitingTimeoutOnNilPolka(t *testing.T) { // What we want: // P0 waits for timeoutPropose in the next round before entering prevote func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{validators: 4}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1464,7 +1918,7 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { ensurePrevote(voteCh, height, round) incrementRound(vss[1:]...) - signAddVotes(cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NilBlockID(), vs2, vs3, vs4) round++ // moving to the next round ensureNewRound(newRoundCh, height, round) @@ -1482,7 +1936,7 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { // What we want: // P0 jump to higher round, precommit and start precommit wait func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{validators: 4}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1500,7 +1954,7 @@ func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) { ensurePrevote(voteCh, height, round) incrementRound(vss[1:]...) - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NilBlockID(), vs2, vs3, vs4) round++ // moving to the next round ensureNewRound(newRoundCh, height, round) @@ -1518,7 +1972,7 @@ func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) { // What we want: // P0 wait for timeoutPropose to expire before sending prevote. func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{validators: 4}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, int32(1) @@ -1534,7 +1988,7 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) { ensureNewRound(newRoundCh, height, round) incrementRound(vss[1:]...) - signAddVotes(cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NilBlockID(), vs2, vs3, vs4) ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds()) @@ -1545,7 +1999,7 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) { // What we want: // P0 emit NewValidBlock event upon receiving 2/3+ Precommit for B but hasn't received block B yet func TestEmitNewValidBlockEventOnCommitWithoutBlock(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{validators: 4}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, int32(1) @@ -1566,7 +2020,7 @@ func TestEmitNewValidBlockEventOnCommitWithoutBlock(t *testing.T) { ensureNewRound(newRoundCh, height, round) // vs2, vs3 and vs4 send precommit for propBlock - signAddVotes(cs1, tmproto.PrecommitType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NewBlockID(propBlockHash, propBlockParts.Header()), vs2, vs3, vs4) ensureNewValidBlock(validBlockCh, height, round) rs := cs1.GetRoundState() @@ -1580,7 +2034,7 @@ func TestEmitNewValidBlockEventOnCommitWithoutBlock(t *testing.T) { // P0 receives 2/3+ Precommit for B for round 0, while being in round 1. It emits NewValidBlock event. // After receiving block, it executes block and moves to the next height. func TestCommitFromPreviousRound(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{validators: 4}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, int32(1) @@ -1600,7 +2054,7 @@ func TestCommitFromPreviousRound(t *testing.T) { ensureNewRound(newRoundCh, height, round) // vs2, vs3 and vs4 send precommit for propBlock for the previous round - signAddVotes(cs1, tmproto.PrecommitType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NewBlockID(propBlockHash, propBlockParts.Header()), vs2, vs3, vs4) ensureNewValidBlock(validBlockCh, height, round) @@ -1634,8 +2088,9 @@ func (n *fakeTxNotifier) Notify() { // and third precommit arrives which leads to the commit of that header and the correct // start of the next round func TestStartNextHeightCorrectlyAfterTimeout(t *testing.T) { - config.Consensus.SkipTimeoutCommit = false - cs1, vss := randState(4) + cfg := config.TestConfig() + cfg.Consensus.SkipTimeoutCommit = false + cs1, vss := makeState(t, makeStateArgs{config: cfg, validators: 4}) cs1.txNotifier = &fakeTxNotifier{ch: make(chan struct{})} vs2, vs3, vs4 := vss[1], vss[2], vss[3] @@ -1664,15 +2119,15 @@ func TestStartNextHeightCorrectlyAfterTimeout(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], theBlockHash) - signAddVotes(cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(theBlockHash, theBlockParts), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) // add precommits - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2) - signAddVotes(cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs3) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NilBlockID(), vs2) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NewBlockID(theBlockHash, theBlockParts), vs3) // wait till timeout occurs ensurePrecommitTimeout(precommitTimeoutCh) @@ -1680,7 +2135,7 @@ func TestStartNextHeightCorrectlyAfterTimeout(t *testing.T) { ensureNewRound(newRoundCh, height, round+1) // majority is now reached - signAddVotes(cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs4) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NewBlockID(theBlockHash, theBlockParts), vs4) ensureNewBlockHeader(newBlockHeader, height, theBlockHash) @@ -1695,8 +2150,9 @@ func TestStartNextHeightCorrectlyAfterTimeout(t *testing.T) { } func TestResetTimeoutPrecommitUponNewHeight(t *testing.T) { - config.Consensus.SkipTimeoutCommit = false - cs1, vss := randState(4) + cfg := config.TestConfig() + cfg.Consensus.SkipTimeoutCommit = false + cs1, vss := makeState(t, makeStateArgs{config: cfg, validators: 4}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1724,15 +2180,15 @@ func TestResetTimeoutPrecommitUponNewHeight(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], theBlockHash) - signAddVotes(cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(theBlockHash, theBlockParts), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) // add precommits - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2) - signAddVotes(cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs3) - signAddVotes(cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs4) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NilBlockID(), vs2) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NewBlockID(theBlockHash, theBlockParts), vs3) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NewBlockID(theBlockHash, theBlockParts), vs4) ensureNewBlockHeader(newBlockHeader, height, theBlockHash) @@ -1838,7 +2294,7 @@ func TestStateSlashingPrecommits(t *testing.T) { // 4 vals. // we receive a final precommit after going into next round, but others might have gone to commit already! func TestStateHalt1(t *testing.T) { - cs1, vss := randState(4) + cs1, vss := makeState(t, makeStateArgs{validators: 4}) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round partSize := types.BlockPartSizeBytes @@ -1864,17 +2320,17 @@ func TestStateHalt1(t *testing.T) { ensurePrevote(voteCh, height, round) - signAddVotes(cs1, tmproto.PrevoteType, propBlock.Hash(), propBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(t, cs1, tmproto.PrevoteType, types.NewBlockID(propBlock.Hash(), propBlockParts.Header()), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], propBlock.Hash(), propBlock.Hash()) // add precommits from the rest - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2) // didnt receive proposal - signAddVotes(cs1, tmproto.PrecommitType, propBlock.Hash(), propBlockParts.Header(), vs3) + signAddVotes(t, cs1, tmproto.PrecommitType, types.NilBlockID(), vs2) // didnt receive proposal + signAddVotes(t, cs1, tmproto.PrecommitType, types.NewBlockID(propBlock.Hash(), propBlockParts.Header()), vs3) // we receive this later, but vs3 might receive it earlier and with ours will go to commit! - precommit4 := signVote(vs4, tmproto.PrecommitType, propBlock.Hash(), propBlockParts.Header()) + precommit4 := signVote(t, vs4, tmproto.PrecommitType, test.DefaultTestChainID, types.NewBlockID(propBlock.Hash(), propBlockParts.Header())) incrementRound(vs2, vs3, vs4) @@ -1907,7 +2363,7 @@ func TestStateHalt1(t *testing.T) { func TestStateOutputsBlockPartsStats(t *testing.T) { // create dummy peer - cs, _ := randState(1) + cs, _ := makeState(t, makeStateArgs{validators: 1}) peer := p2pmock.NewPeer(nil) // 1) new block part @@ -1949,32 +2405,32 @@ func TestStateOutputsBlockPartsStats(t *testing.T) { } func TestStateOutputVoteStats(t *testing.T) { - cs, vss := randState(2) + cs1, vss := makeState(t, makeStateArgs{validators: 2}) // create dummy peer peer := p2pmock.NewPeer(nil) randBytes := tmrand.Bytes(tmhash.Size) - vote := signVote(vss[1], tmproto.PrecommitType, randBytes, types.PartSetHeader{}) + vote := signVote(t, vss[1], tmproto.PrecommitType, test.DefaultTestChainID, types.NewBlockID(randBytes, types.PartSetHeader{})) voteMessage := &VoteMessage{vote} - cs.handleMsg(msgInfo{voteMessage, peer.ID()}) + cs1.handleMsg(msgInfo{voteMessage, peer.ID()}) - statsMessage := <-cs.statsMsgQueue + statsMessage := <-cs1.statsMsgQueue require.Equal(t, voteMessage, statsMessage.Msg, "") require.Equal(t, peer.ID(), statsMessage.PeerID, "") // sending the same part from different peer - cs.handleMsg(msgInfo{&VoteMessage{vote}, "peer2"}) + cs1.handleMsg(msgInfo{&VoteMessage{vote}, "peer2"}) // sending the vote for the bigger height incrementHeight(vss[1]) - vote = signVote(vss[1], tmproto.PrecommitType, randBytes, types.PartSetHeader{}) + vote = signVote(t, vss[1], tmproto.PrecommitType, test.DefaultTestChainID, types.NewBlockID(randBytes, types.PartSetHeader{})) - cs.handleMsg(msgInfo{&VoteMessage{vote}, peer.ID()}) + cs1.handleMsg(msgInfo{&VoteMessage{vote}, peer.ID()}) select { - case <-cs.statsMsgQueue: + case <-cs1.statsMsgQueue: t.Errorf("should not output stats message after receiving the known vote or vote from bigger height") case <-time.After(50 * time.Millisecond): } @@ -1982,20 +2438,22 @@ func TestStateOutputVoteStats(t *testing.T) { } func TestSignSameVoteTwice(t *testing.T) { - _, vss := randState(2) + _, vss := makeState(t, makeStateArgs{validators: 1}) randBytes := tmrand.Bytes(tmhash.Size) - vote := signVote(vss[1], + blockID := types.NewBlockID(randBytes, types.PartSetHeader{Total: 10, Hash: randBytes}) + + vote := signVote(t, vss[0], tmproto.PrecommitType, - randBytes, - types.PartSetHeader{Total: 10, Hash: randBytes}, + test.DefaultTestChainID, + blockID, ) - vote2 := signVote(vss[1], + vote2 := signVote(t, vss[0], tmproto.PrecommitType, - randBytes, - types.PartSetHeader{Total: 10, Hash: randBytes}, + test.DefaultTestChainID, + blockID, ) require.Equal(t, vote, vote2) diff --git a/evidence/verify_test.go b/evidence/verify_test.go index f8021f436..ff50a8e60 100644 --- a/evidence/verify_test.go +++ b/evidence/verify_test.go @@ -14,6 +14,7 @@ import ( "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/evidence" "github.com/tendermint/tendermint/evidence/mocks" + "github.com/tendermint/tendermint/internal/test" "github.com/tendermint/tendermint/libs/log" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmversion "github.com/tendermint/tendermint/proto/tendermint/version" @@ -207,7 +208,7 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) { // except the last validator vote twice blockID := makeBlockID(conflictingHeader.Hash(), 1000, []byte("partshash")) voteSet := types.NewVoteSet(evidenceChainID, 10, 1, tmproto.SignedMsgType(2), conflictingVals) - commit, err := types.MakeCommit(blockID, 10, 1, voteSet, conflictingPrivVals[:4], defaultEvidenceTime) + commit, err := test.MakeCommitFromVoteSet(blockID, voteSet, conflictingPrivVals[:4], defaultEvidenceTime) require.NoError(t, err) ev := &types.LightClientAttackEvidence{ ConflictingBlock: &types.LightBlock{ @@ -225,8 +226,9 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) { trustedBlockID := makeBlockID(trustedHeader.Hash(), 1000, []byte("partshash")) trustedVoteSet := types.NewVoteSet(evidenceChainID, 10, 1, tmproto.SignedMsgType(2), conflictingVals) - trustedCommit, err := types.MakeCommit(trustedBlockID, 10, 1, trustedVoteSet, conflictingPrivVals, defaultEvidenceTime) + trustedExtCommit, err := test.MakeExtendedCommitFromVoteSet(trustedBlockID, trustedVoteSet, conflictingPrivVals, defaultEvidenceTime) require.NoError(t, err) + trustedCommit := trustedExtCommit.ToCommit() trustedSignedHeader := &types.SignedHeader{ Header: trustedHeader, Commit: trustedCommit, @@ -291,7 +293,7 @@ func TestVerifyLightClientAttack_Amnesia(t *testing.T) { // except the last validator vote twice. However this time the commits are of different rounds. blockID := makeBlockID(conflictingHeader.Hash(), 1000, []byte("partshash")) voteSet := types.NewVoteSet(evidenceChainID, 10, 0, tmproto.SignedMsgType(2), conflictingVals) - commit, err := types.MakeCommit(blockID, 10, 0, voteSet, conflictingPrivVals, defaultEvidenceTime) + commit, err := test.MakeCommitFromVoteSet(blockID, voteSet, conflictingPrivVals, defaultEvidenceTime) require.NoError(t, err) ev := &types.LightClientAttackEvidence{ ConflictingBlock: &types.LightBlock{ @@ -309,7 +311,7 @@ func TestVerifyLightClientAttack_Amnesia(t *testing.T) { trustedBlockID := makeBlockID(trustedHeader.Hash(), 1000, []byte("partshash")) trustedVoteSet := types.NewVoteSet(evidenceChainID, 10, 1, tmproto.SignedMsgType(2), conflictingVals) - trustedCommit, err := types.MakeCommit(trustedBlockID, 10, 1, trustedVoteSet, conflictingPrivVals, defaultEvidenceTime) + trustedCommit, err := test.MakeCommitFromVoteSet(trustedBlockID, trustedVoteSet, conflictingPrivVals, defaultEvidenceTime) require.NoError(t, err) trustedSignedHeader := &types.SignedHeader{ Header: trustedHeader, @@ -483,8 +485,10 @@ func makeLunaticEvidence( blockID := makeBlockID(conflictingHeader.Hash(), 1000, []byte("partshash")) voteSet := types.NewVoteSet(evidenceChainID, height, 1, tmproto.SignedMsgType(2), conflictingVals) - commit, err := types.MakeCommit(blockID, height, 1, voteSet, conflictingPrivVals, defaultEvidenceTime) + extCommit, err := test.MakeExtendedCommitFromVoteSet(blockID, voteSet, conflictingPrivVals, defaultEvidenceTime) require.NoError(t, err) + commit := extCommit.ToCommit() + ev = &types.LightClientAttackEvidence{ ConflictingBlock: &types.LightBlock{ SignedHeader: &types.SignedHeader{ @@ -510,7 +514,7 @@ func makeLunaticEvidence( trustedBlockID := makeBlockID(trustedHeader.Hash(), 1000, []byte("partshash")) trustedVals, privVals := types.RandValidatorSet(totalVals, defaultVotingPower) trustedVoteSet := types.NewVoteSet(evidenceChainID, height, 1, tmproto.SignedMsgType(2), trustedVals) - trustedCommit, err := types.MakeCommit(trustedBlockID, height, 1, trustedVoteSet, privVals, defaultEvidenceTime) + trustedCommit, err := test.MakeCommitFromVoteSet(trustedBlockID, trustedVoteSet, privVals, defaultEvidenceTime) require.NoError(t, err) trusted = &types.LightBlock{ SignedHeader: &types.SignedHeader{ diff --git a/internal/test/block.go b/internal/test/block.go index 70e4d607e..05ce56b95 100644 --- a/internal/test/block.go +++ b/internal/test/block.go @@ -53,7 +53,7 @@ func MakeHeader(t *testing.T, h *types.Header) *types.Header { if h.Height == 0 { h.Height = 1 } - if h.LastBlockID.IsZero() { + if h.LastBlockID.IsNil() { h.LastBlockID = MakeBlockID() } if h.ChainID == "" { diff --git a/internal/test/commit.go b/internal/test/commit.go index fd3a8ac5a..681116f9c 100644 --- a/internal/test/commit.go +++ b/internal/test/commit.go @@ -9,7 +9,7 @@ import ( "github.com/tendermint/tendermint/types" ) -func MakeCommitFromVoteSet(blockID types.BlockID, voteSet *types.VoteSet, validators []types.PrivValidator, now time.Time) (*types.Commit, error) { +func MakeExtendedCommitFromVoteSet(blockID types.BlockID, voteSet *types.VoteSet, validators []types.PrivValidator, now time.Time) (*types.ExtendedCommit, error) { // all sign for i := 0; i < len(validators); i++ { pubKey, err := validators[i].GetPubKey() @@ -32,12 +32,21 @@ func MakeCommitFromVoteSet(blockID types.BlockID, voteSet *types.VoteSet, valida return nil, err } vote.Signature = v.Signature + vote.ExtensionSignature = v.ExtensionSignature if _, err := voteSet.AddVote(vote); err != nil { return nil, err } } - return voteSet.MakeCommit(), nil + return voteSet.MakeExtendedCommit(), nil +} + +func MakeCommitFromVoteSet(blockID types.BlockID, voteSet *types.VoteSet, validators []types.PrivValidator, now time.Time) (*types.Commit, error) { + extCommit, err := MakeExtendedCommitFromVoteSet(blockID, voteSet, validators, now) + if err != nil { + return nil, err + } + return extCommit.ToCommit(), nil } func MakeVoteSet(lastState sm.State, round int32) *types.VoteSet { diff --git a/internal/test/genesis.go b/internal/test/genesis.go index 732adf5a3..580623933 100644 --- a/internal/test/genesis.go +++ b/internal/test/genesis.go @@ -3,16 +3,25 @@ package test import ( "time" - cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/types" ) +func ConsensusParams() *types.ConsensusParams { + c := types.DefaultConsensusParams() + // enable vote extensions + c.ABCI.VoteExtensionsEnableHeight = 1 + return c +} + func GenesisDoc( - config *cfg.Config, + chainID string, time time.Time, validators []*types.Validator, consensusParams *types.ConsensusParams, ) *types.GenesisDoc { + if chainID == "" { + chainID = DefaultTestChainID + } genesisValidators := make([]types.GenesisValidator, len(validators)) @@ -26,7 +35,7 @@ func GenesisDoc( return &types.GenesisDoc{ GenesisTime: time, InitialHeight: 1, - ChainID: config.ChainID(), + ChainID: chainID, Validators: genesisValidators, ConsensusParams: consensusParams, } diff --git a/internal/test/validator.go b/internal/test/validator.go index d0fdf8007..8e736770c 100644 --- a/internal/test/validator.go +++ b/internal/test/validator.go @@ -1,7 +1,6 @@ package test import ( - "context" "sort" "testing" @@ -10,7 +9,7 @@ import ( "github.com/tendermint/tendermint/types" ) -func Validator(ctx context.Context, votingPower int64) (*types.Validator, types.PrivValidator, error) { +func Validator(votingPower int64) (*types.Validator, types.PrivValidator, error) { privVal := types.NewMockPV() pubKey, err := privVal.GetPubKey() if err != nil { @@ -21,7 +20,7 @@ func Validator(ctx context.Context, votingPower int64) (*types.Validator, types. return val, privVal, nil } -func ValidatorSet(ctx context.Context, t *testing.T, numValidators int, votingPower int64) (*types.ValidatorSet, []types.PrivValidator) { +func ValidatorSet(t *testing.T, numValidators int, votingPower int64) (*types.ValidatorSet, []types.PrivValidator) { var ( valz = make([]*types.Validator, numValidators) privValidators = make([]types.PrivValidator, numValidators) @@ -29,7 +28,7 @@ func ValidatorSet(ctx context.Context, t *testing.T, numValidators int, votingPo t.Helper() for i := 0; i < numValidators; i++ { - val, privValidator, err := Validator(ctx, votingPower) + val, privValidator, err := Validator(votingPower) require.NoError(t, err) valz[i] = val privValidators[i] = privValidator diff --git a/node/node_test.go b/node/node_test.go index 006f80c05..dfe11fcb8 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -307,10 +307,11 @@ func TestCreateProposalBlock(t *testing.T) { evidencePool, ) - commit := types.NewCommit(height-1, 0, types.BlockID{}, nil) + extCommit := &types.ExtendedCommit{Height: height - 1} block, err := blockExec.CreateProposalBlock( height, - state, commit, + state, + extCommit, proposerAddr, nil, ) @@ -390,10 +391,11 @@ func TestMaxProposalBlockSize(t *testing.T) { sm.EmptyEvidencePool{}, ) - commit := types.NewCommit(height-1, 0, types.BlockID{}, nil) + extCommit := &types.ExtendedCommit{Height: height - 1} block, err := blockExec.CreateProposalBlock( height, - state, commit, + state, + extCommit, proposerAddr, nil, ) diff --git a/privval/file.go b/privval/file.go index ad5623915..5f3d70851 100644 --- a/privval/file.go +++ b/privval/file.go @@ -82,6 +82,14 @@ type FilePVLastSignState struct { filePath string } +func (lss *FilePVLastSignState) reset() { + lss.Height = 0 + lss.Round = 0 + lss.Step = 0 + lss.Signature = nil + lss.SignBytes = nil +} + // CheckHRS checks the given height, round, step (HRS) against that of the // FilePVLastSignState. It returns an error if the arguments constitute a regression, // or if they match but the SignBytes are empty. @@ -124,7 +132,7 @@ func (lss *FilePVLastSignState) CheckHRS(height int64, round int32, step int8) ( } // Save persists the FilePvLastSignState to its filePath. -func (lss *FilePVLastSignState) Save() { +func (lss *FilePVLastSignState) Save() error { outFile := lss.filePath if outFile == "" { panic("cannot save FilePVLastSignState: filePath not set") @@ -133,10 +141,7 @@ func (lss *FilePVLastSignState) Save() { if err != nil { panic(err) } - err = tempfile.WriteFileAtomic(outFile, jsonBytes, 0600) - if err != nil { - panic(err) - } + return tempfile.WriteFileAtomic(outFile, jsonBytes, 0600) } //------------------------------------------------------------------------------- @@ -276,12 +281,7 @@ func (pv *FilePV) Save() { // Reset resets all fields in the FilePV. // NOTE: Unsafe! func (pv *FilePV) Reset() { - var sig []byte - pv.LastSignState.Height = 0 - pv.LastSignState.Round = 0 - pv.LastSignState.Step = 0 - pv.LastSignState.Signature = sig - pv.LastSignState.SignBytes = nil + pv.LastSignState.reset() pv.Save() } @@ -301,6 +301,7 @@ func (pv *FilePV) String() string { // signVote checks if the vote is good to sign and sets the vote signature. // It may need to set the timestamp as well if the vote is otherwise the same as // a previously signed vote (ie. we crashed after signing but before the vote hit the WAL). +// extension signatures are aways signed for non-nil precommits (even if the data is empty) func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { height, round, step := vote.Height, vote.Round, voteToStep(vote) @@ -313,6 +314,22 @@ func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { signBytes := types.VoteSignBytes(chainID, vote) + // Vote extensions are non-deterministic, so it is possible that an + // application may have created a different extension. We therefore always + // re-sign the vote extensions of precommits. For prevotes and nil + // precommits, the extension signature will always be empty. + // Even if the signed over data is empty, we still add the signature + var extSig []byte + if vote.Type == tmproto.PrecommitType && !types.IsProtoBlockIDNil(&vote.BlockID) { + extSignBytes := types.VoteExtensionSignBytes(chainID, vote) + extSig, err = pv.Key.PrivKey.Sign(extSignBytes) + if err != nil { + return err + } + } else if len(vote.Extension) > 0 { + return errors.New("unexpected vote extension - extensions are only allowed in non-nil precommits") + } + // We might crash before writing to the wal, // causing us to try to re-sign for the same HRS. // If signbytes are the same, use the last signature. @@ -321,13 +338,24 @@ func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { if sameHRS { if bytes.Equal(signBytes, lss.SignBytes) { vote.Signature = lss.Signature - } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok { + } else { + // Compares the canonicalized votes (i.e. without vote extensions + // or vote extension signatures). + timestamp, ok, err := checkVotesOnlyDifferByTimestamp(lss.SignBytes, signBytes) + if err != nil { + return err + } + if !ok { + return errors.New("conflicting data") + } + vote.Timestamp = timestamp vote.Signature = lss.Signature - } else { - err = fmt.Errorf("conflicting data") } - return err + + vote.ExtensionSignature = extSig + + return nil } // It passed the checks. Sign the vote @@ -335,8 +363,14 @@ func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { if err != nil { return err } - pv.saveSigned(height, round, step, signBytes, sig) + + if err := pv.saveSigned(height, round, step, signBytes, sig); err != nil { + return err + } + vote.Signature = sig + vote.ExtensionSignature = extSig + return nil } @@ -384,27 +418,27 @@ func (pv *FilePV) signProposal(chainID string, proposal *tmproto.Proposal) error // Persist height/round/step and signature func (pv *FilePV) saveSigned(height int64, round int32, step int8, - signBytes []byte, sig []byte) { + signBytes []byte, sig []byte) error { pv.LastSignState.Height = height pv.LastSignState.Round = round pv.LastSignState.Step = step pv.LastSignState.Signature = sig pv.LastSignState.SignBytes = signBytes - pv.LastSignState.Save() + return pv.LastSignState.Save() } //----------------------------------------------------------------------------------------- // returns the timestamp from the lastSignBytes. // returns true if the only difference in the votes is their timestamp. -func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) { +func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool, error) { var lastVote, newVote tmproto.CanonicalVote if err := protoio.UnmarshalDelimited(lastSignBytes, &lastVote); err != nil { - panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err)) + return time.Time{}, false, fmt.Errorf("LastSignBytes cannot be unmarshalled into vote: %w", err) } if err := protoio.UnmarshalDelimited(newSignBytes, &newVote); err != nil { - panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err)) + return time.Time{}, false, fmt.Errorf("signBytes cannot be unmarshalled into vote: %w", err) } lastTime := lastVote.Timestamp @@ -413,7 +447,7 @@ func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.T lastVote.Timestamp = now newVote.Timestamp = now - return lastTime, proto.Equal(&newVote, &lastVote) + return lastTime, proto.Equal(&newVote, &lastVote), nil } // returns the timestamp from the lastSignBytes. diff --git a/proto/tendermint/types/canonical.pb.go b/proto/tendermint/types/canonical.pb.go index 5d3d4ce3a..4cb9ccd02 100644 --- a/proto/tendermint/types/canonical.pb.go +++ b/proto/tendermint/types/canonical.pb.go @@ -308,48 +308,121 @@ func (m *CanonicalVote) GetChainID() string { return "" } +// CanonicalVoteExtension provides us a way to serialize a vote extension from +// a particular validator such that we can sign over those serialized bytes. +type CanonicalVoteExtension struct { + Extension []byte `protobuf:"bytes,1,opt,name=extension,proto3" json:"extension,omitempty"` + Height int64 `protobuf:"fixed64,2,opt,name=height,proto3" json:"height,omitempty"` + Round int64 `protobuf:"fixed64,3,opt,name=round,proto3" json:"round,omitempty"` + ChainId string `protobuf:"bytes,4,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` +} + +func (m *CanonicalVoteExtension) Reset() { *m = CanonicalVoteExtension{} } +func (m *CanonicalVoteExtension) String() string { return proto.CompactTextString(m) } +func (*CanonicalVoteExtension) ProtoMessage() {} +func (*CanonicalVoteExtension) Descriptor() ([]byte, []int) { + return fileDescriptor_8d1a1a84ff7267ed, []int{4} +} +func (m *CanonicalVoteExtension) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *CanonicalVoteExtension) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_CanonicalVoteExtension.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *CanonicalVoteExtension) XXX_Merge(src proto.Message) { + xxx_messageInfo_CanonicalVoteExtension.Merge(m, src) +} +func (m *CanonicalVoteExtension) XXX_Size() int { + return m.Size() +} +func (m *CanonicalVoteExtension) XXX_DiscardUnknown() { + xxx_messageInfo_CanonicalVoteExtension.DiscardUnknown(m) +} + +var xxx_messageInfo_CanonicalVoteExtension proto.InternalMessageInfo + +func (m *CanonicalVoteExtension) GetExtension() []byte { + if m != nil { + return m.Extension + } + return nil +} + +func (m *CanonicalVoteExtension) GetHeight() int64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *CanonicalVoteExtension) GetRound() int64 { + if m != nil { + return m.Round + } + return 0 +} + +func (m *CanonicalVoteExtension) GetChainId() string { + if m != nil { + return m.ChainId + } + return "" +} + func init() { proto.RegisterType((*CanonicalBlockID)(nil), "tendermint.types.CanonicalBlockID") proto.RegisterType((*CanonicalPartSetHeader)(nil), "tendermint.types.CanonicalPartSetHeader") proto.RegisterType((*CanonicalProposal)(nil), "tendermint.types.CanonicalProposal") proto.RegisterType((*CanonicalVote)(nil), "tendermint.types.CanonicalVote") + proto.RegisterType((*CanonicalVoteExtension)(nil), "tendermint.types.CanonicalVoteExtension") } func init() { proto.RegisterFile("tendermint/types/canonical.proto", fileDescriptor_8d1a1a84ff7267ed) } var fileDescriptor_8d1a1a84ff7267ed = []byte{ - // 487 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x53, 0x3d, 0x6f, 0xd3, 0x40, - 0x18, 0xce, 0xa5, 0x4e, 0xe2, 0x5c, 0x1b, 0x08, 0xa7, 0xaa, 0xb2, 0x22, 0x64, 0x5b, 0x1e, 0x90, - 0x59, 0x6c, 0xa9, 0x1d, 0xd8, 0x5d, 0x06, 0x82, 0x40, 0x94, 0x6b, 0xd5, 0x81, 0x25, 0xba, 0xd8, - 0x87, 0x6d, 0xe1, 0xf8, 0x4e, 0xf6, 0x65, 0xe8, 0xc2, 0x6f, 0xe8, 0xef, 0xe0, 0x97, 0x74, 0xec, - 0x08, 0x4b, 0x40, 0xce, 0x1f, 0x41, 0x77, 0x4e, 0xec, 0xa8, 0x01, 0x16, 0x10, 0xcb, 0xe9, 0xfd, - 0x78, 0xee, 0x79, 0x1f, 0x3d, 0xaf, 0x5e, 0x68, 0x0b, 0x9a, 0x47, 0xb4, 0x58, 0xa4, 0xb9, 0xf0, - 0xc5, 0x0d, 0xa7, 0xa5, 0x1f, 0x92, 0x9c, 0xe5, 0x69, 0x48, 0x32, 0x8f, 0x17, 0x4c, 0x30, 0x34, - 0x6e, 0x11, 0x9e, 0x42, 0x4c, 0x8e, 0x63, 0x16, 0x33, 0xd5, 0xf4, 0x65, 0x54, 0xe3, 0x26, 0x4f, - 0xf7, 0x98, 0xd4, 0xbb, 0xe9, 0x5a, 0x31, 0x63, 0x71, 0x46, 0x7d, 0x95, 0xcd, 0x97, 0x1f, 0x7d, - 0x91, 0x2e, 0x68, 0x29, 0xc8, 0x82, 0xd7, 0x00, 0xe7, 0x33, 0x1c, 0x9f, 0x6f, 0x27, 0x07, 0x19, - 0x0b, 0x3f, 0x4d, 0x5f, 0x22, 0x04, 0xb5, 0x84, 0x94, 0x89, 0x01, 0x6c, 0xe0, 0x1e, 0x61, 0x15, - 0xa3, 0x6b, 0xf8, 0x98, 0x93, 0x42, 0xcc, 0x4a, 0x2a, 0x66, 0x09, 0x25, 0x11, 0x2d, 0x8c, 0xae, - 0x0d, 0xdc, 0xc3, 0x53, 0xd7, 0x7b, 0x28, 0xd4, 0x6b, 0x08, 0x2f, 0x48, 0x21, 0x2e, 0xa9, 0x78, - 0xa5, 0xf0, 0x81, 0x76, 0xb7, 0xb2, 0x3a, 0x78, 0xc4, 0x77, 0x8b, 0x4e, 0x00, 0x4f, 0x7e, 0x0d, - 0x47, 0xc7, 0xb0, 0x27, 0x98, 0x20, 0x99, 0x92, 0x31, 0xc2, 0x75, 0xd2, 0x68, 0xeb, 0xb6, 0xda, - 0x9c, 0x6f, 0x5d, 0xf8, 0xa4, 0x25, 0x29, 0x18, 0x67, 0x25, 0xc9, 0xd0, 0x19, 0xd4, 0xa4, 0x1c, - 0xf5, 0xfd, 0xd1, 0xa9, 0xb5, 0x2f, 0xf3, 0x32, 0x8d, 0x73, 0x1a, 0xbd, 0x2d, 0xe3, 0xab, 0x1b, - 0x4e, 0xb1, 0x02, 0xa3, 0x13, 0xd8, 0x4f, 0x68, 0x1a, 0x27, 0x42, 0x0d, 0x18, 0xe3, 0x4d, 0x26, - 0xc5, 0x14, 0x6c, 0x99, 0x47, 0xc6, 0x81, 0x2a, 0xd7, 0x09, 0x7a, 0x0e, 0x87, 0x9c, 0x65, 0xb3, - 0xba, 0xa3, 0xd9, 0xc0, 0x3d, 0x08, 0x8e, 0xaa, 0x95, 0xa5, 0x5f, 0xbc, 0x7b, 0x83, 0x65, 0x0d, - 0xeb, 0x9c, 0x65, 0x2a, 0x42, 0xaf, 0xa1, 0x3e, 0x97, 0xf6, 0xce, 0xd2, 0xc8, 0xe8, 0x29, 0xe3, - 0x9c, 0x3f, 0x18, 0xb7, 0xd9, 0x44, 0x70, 0x58, 0xad, 0xac, 0xc1, 0x26, 0xc1, 0x03, 0x45, 0x30, - 0x8d, 0x50, 0x00, 0x87, 0xcd, 0x1a, 0x8d, 0xbe, 0x22, 0x9b, 0x78, 0xf5, 0xa2, 0xbd, 0xed, 0xa2, - 0xbd, 0xab, 0x2d, 0x22, 0xd0, 0xa5, 0xef, 0xb7, 0xdf, 0x2d, 0x80, 0xdb, 0x6f, 0xe8, 0x19, 0xd4, - 0xc3, 0x84, 0xa4, 0xb9, 0xd4, 0x33, 0xb0, 0x81, 0x3b, 0xac, 0x67, 0x9d, 0xcb, 0x9a, 0x9c, 0xa5, - 0x9a, 0xd3, 0xc8, 0xf9, 0xd2, 0x85, 0xa3, 0x46, 0xd6, 0x35, 0x13, 0xf4, 0x7f, 0xf8, 0xba, 0x6b, - 0x96, 0xf6, 0x2f, 0xcd, 0xea, 0xfd, 0xbd, 0x59, 0xfd, 0xdf, 0x9b, 0x15, 0xbc, 0xbf, 0xab, 0x4c, - 0x70, 0x5f, 0x99, 0xe0, 0x47, 0x65, 0x82, 0xdb, 0xb5, 0xd9, 0xb9, 0x5f, 0x9b, 0x9d, 0xaf, 0x6b, - 0xb3, 0xf3, 0xe1, 0x45, 0x9c, 0x8a, 0x64, 0x39, 0xf7, 0x42, 0xb6, 0xf0, 0x77, 0x0f, 0xb6, 0x0d, - 0xeb, 0xc3, 0x7e, 0x78, 0xcc, 0xf3, 0xbe, 0xaa, 0x9f, 0xfd, 0x0c, 0x00, 0x00, 0xff, 0xff, 0x6d, - 0xdd, 0x12, 0x5d, 0x31, 0x04, 0x00, 0x00, + // 523 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0xc1, 0x6e, 0xd3, 0x40, + 0x10, 0x8d, 0x53, 0x27, 0xb1, 0xb7, 0x0d, 0x84, 0x55, 0x55, 0x99, 0xa8, 0xb2, 0x2d, 0x1f, 0x90, + 0xb9, 0xd8, 0x52, 0x7b, 0xe0, 0xee, 0x82, 0x44, 0x10, 0x88, 0xe2, 0x56, 0x3d, 0x70, 0x89, 0x36, + 0xf6, 0x62, 0x5b, 0x38, 0xde, 0x95, 0xbd, 0x91, 0xe8, 0x05, 0x7e, 0xa1, 0xdf, 0xc1, 0x97, 0xf4, + 0xd8, 0x23, 0x5c, 0x02, 0x72, 0x7e, 0x04, 0xed, 0xda, 0xb1, 0xad, 0x16, 0x55, 0x42, 0x20, 0x2e, + 0xd1, 0xcc, 0x9b, 0xb7, 0x33, 0x4f, 0x6f, 0xe2, 0x01, 0x26, 0xc3, 0x59, 0x88, 0xf3, 0x65, 0x92, + 0x31, 0x97, 0x5d, 0x52, 0x5c, 0xb8, 0x01, 0xca, 0x48, 0x96, 0x04, 0x28, 0x75, 0x68, 0x4e, 0x18, + 0x81, 0x93, 0x96, 0xe1, 0x08, 0xc6, 0x74, 0x3f, 0x22, 0x11, 0x11, 0x45, 0x97, 0x47, 0x15, 0x6f, + 0x7a, 0x78, 0xa7, 0x93, 0xf8, 0xad, 0xab, 0x46, 0x44, 0x48, 0x94, 0x62, 0x57, 0x64, 0x8b, 0xd5, + 0x07, 0x97, 0x25, 0x4b, 0x5c, 0x30, 0xb4, 0xa4, 0x15, 0xc1, 0xfa, 0x0c, 0x26, 0x27, 0xdb, 0xc9, + 0x5e, 0x4a, 0x82, 0x8f, 0xb3, 0xe7, 0x10, 0x02, 0x39, 0x46, 0x45, 0xac, 0x49, 0xa6, 0x64, 0xef, + 0xf9, 0x22, 0x86, 0x17, 0xe0, 0x21, 0x45, 0x39, 0x9b, 0x17, 0x98, 0xcd, 0x63, 0x8c, 0x42, 0x9c, + 0x6b, 0x7d, 0x53, 0xb2, 0x77, 0x8f, 0x6c, 0xe7, 0xb6, 0x50, 0xa7, 0x69, 0x78, 0x8a, 0x72, 0x76, + 0x86, 0xd9, 0x4b, 0xc1, 0xf7, 0xe4, 0xeb, 0xb5, 0xd1, 0xf3, 0xc7, 0xb4, 0x0b, 0x5a, 0x1e, 0x38, + 0xf8, 0x3d, 0x1d, 0xee, 0x83, 0x01, 0x23, 0x0c, 0xa5, 0x42, 0xc6, 0xd8, 0xaf, 0x92, 0x46, 0x5b, + 0xbf, 0xd5, 0x66, 0x7d, 0xef, 0x83, 0x47, 0x6d, 0x93, 0x9c, 0x50, 0x52, 0xa0, 0x14, 0x1e, 0x03, + 0x99, 0xcb, 0x11, 0xcf, 0x1f, 0x1c, 0x19, 0x77, 0x65, 0x9e, 0x25, 0x51, 0x86, 0xc3, 0x37, 0x45, + 0x74, 0x7e, 0x49, 0xb1, 0x2f, 0xc8, 0xf0, 0x00, 0x0c, 0x63, 0x9c, 0x44, 0x31, 0x13, 0x03, 0x26, + 0x7e, 0x9d, 0x71, 0x31, 0x39, 0x59, 0x65, 0xa1, 0xb6, 0x23, 0xe0, 0x2a, 0x81, 0x4f, 0x81, 0x4a, + 0x49, 0x3a, 0xaf, 0x2a, 0xb2, 0x29, 0xd9, 0x3b, 0xde, 0x5e, 0xb9, 0x36, 0x94, 0xd3, 0xb7, 0xaf, + 0x7d, 0x8e, 0xf9, 0x0a, 0x25, 0xa9, 0x88, 0xe0, 0x2b, 0xa0, 0x2c, 0xb8, 0xbd, 0xf3, 0x24, 0xd4, + 0x06, 0xc2, 0x38, 0xeb, 0x1e, 0xe3, 0xea, 0x4d, 0x78, 0xbb, 0xe5, 0xda, 0x18, 0xd5, 0x89, 0x3f, + 0x12, 0x0d, 0x66, 0x21, 0xf4, 0x80, 0xda, 0xac, 0x51, 0x1b, 0x8a, 0x66, 0x53, 0xa7, 0x5a, 0xb4, + 0xb3, 0x5d, 0xb4, 0x73, 0xbe, 0x65, 0x78, 0x0a, 0xf7, 0xfd, 0xea, 0x87, 0x21, 0xf9, 0xed, 0x33, + 0xf8, 0x04, 0x28, 0x41, 0x8c, 0x92, 0x8c, 0xeb, 0x19, 0x99, 0x92, 0xad, 0x56, 0xb3, 0x4e, 0x38, + 0xc6, 0x67, 0x89, 0xe2, 0x2c, 0xb4, 0xbe, 0xf6, 0xc1, 0xb8, 0x91, 0x75, 0x41, 0x18, 0xfe, 0x1f, + 0xbe, 0x76, 0xcd, 0x92, 0xff, 0xa5, 0x59, 0x83, 0xbf, 0x37, 0x6b, 0x78, 0x8f, 0x59, 0x5f, 0x3a, + 0x7f, 0x66, 0xee, 0xd5, 0x8b, 0x4f, 0x0c, 0x67, 0x45, 0x42, 0x32, 0x78, 0x08, 0x54, 0xbc, 0x4d, + 0xea, 0xef, 0xaa, 0x05, 0xfe, 0xd0, 0x9d, 0xc7, 0x1d, 0x35, 0xdc, 0x1d, 0xb5, 0x11, 0xe0, 0xbd, + 0xbb, 0x2e, 0x75, 0xe9, 0xa6, 0xd4, 0xa5, 0x9f, 0xa5, 0x2e, 0x5d, 0x6d, 0xf4, 0xde, 0xcd, 0x46, + 0xef, 0x7d, 0xdb, 0xe8, 0xbd, 0xf7, 0xcf, 0xa2, 0x84, 0xc5, 0xab, 0x85, 0x13, 0x90, 0xa5, 0xdb, + 0xbd, 0x18, 0x6d, 0x58, 0x5d, 0x96, 0xdb, 0xd7, 0x64, 0x31, 0x14, 0xf8, 0xf1, 0xaf, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x2b, 0x89, 0x89, 0x5b, 0xb2, 0x04, 0x00, 0x00, } func (m *CanonicalBlockID) Marshal() (dAtA []byte, err error) { @@ -566,6 +639,55 @@ func (m *CanonicalVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *CanonicalVoteExtension) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CanonicalVoteExtension) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CanonicalVoteExtension) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ChainId) > 0 { + i -= len(m.ChainId) + copy(dAtA[i:], m.ChainId) + i = encodeVarintCanonical(dAtA, i, uint64(len(m.ChainId))) + i-- + dAtA[i] = 0x22 + } + if m.Round != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.Round)) + i-- + dAtA[i] = 0x19 + } + if m.Height != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.Height)) + i-- + dAtA[i] = 0x11 + } + if len(m.Extension) > 0 { + i -= len(m.Extension) + copy(dAtA[i:], m.Extension) + i = encodeVarintCanonical(dAtA, i, uint64(len(m.Extension))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintCanonical(dAtA []byte, offset int, v uint64) int { offset -= sovCanonical(v) base := offset @@ -667,6 +789,29 @@ func (m *CanonicalVote) Size() (n int) { return n } +func (m *CanonicalVoteExtension) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Extension) + if l > 0 { + n += 1 + l + sovCanonical(uint64(l)) + } + if m.Height != 0 { + n += 9 + } + if m.Round != 0 { + n += 9 + } + l = len(m.ChainId) + if l > 0 { + n += 1 + l + sovCanonical(uint64(l)) + } + return n +} + func sovCanonical(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1292,6 +1437,142 @@ func (m *CanonicalVote) Unmarshal(dAtA []byte) error { } return nil } +func (m *CanonicalVoteExtension) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCanonical + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CanonicalVoteExtension: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CanonicalVoteExtension: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Extension", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCanonical + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCanonical + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthCanonical + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Extension = append(m.Extension[:0], dAtA[iNdEx:postIndex]...) + if m.Extension == nil { + m.Extension = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.Height = int64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 3: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Round", wireType) + } + m.Round = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.Round = int64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChainId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCanonical + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCanonical + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCanonical + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChainId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCanonical(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthCanonical + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipCanonical(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/proto/tendermint/types/canonical.proto b/proto/tendermint/types/canonical.proto index e88fd6ffe..46cd0fe33 100644 --- a/proto/tendermint/types/canonical.proto +++ b/proto/tendermint/types/canonical.proto @@ -35,3 +35,12 @@ message CanonicalVote { google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; string chain_id = 6 [(gogoproto.customname) = "ChainID"]; } + +// CanonicalVoteExtension provides us a way to serialize a vote extension from +// a particular validator such that we can sign over those serialized bytes. +message CanonicalVoteExtension { + bytes extension = 1; + sfixed64 height = 2; + sfixed64 round = 3; + string chain_id = 4; +} \ No newline at end of file diff --git a/proto/tendermint/types/params.pb.go b/proto/tendermint/types/params.pb.go index 3d57b852c..6b96ee9e5 100644 --- a/proto/tendermint/types/params.pb.go +++ b/proto/tendermint/types/params.pb.go @@ -34,6 +34,7 @@ type ConsensusParams struct { Evidence *EvidenceParams `protobuf:"bytes,2,opt,name=evidence,proto3" json:"evidence,omitempty"` Validator *ValidatorParams `protobuf:"bytes,3,opt,name=validator,proto3" json:"validator,omitempty"` Version *VersionParams `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"` + Abci *ABCIParams `protobuf:"bytes,5,opt,name=abci,proto3" json:"abci,omitempty"` } func (m *ConsensusParams) Reset() { *m = ConsensusParams{} } @@ -97,6 +98,68 @@ func (m *ConsensusParams) GetVersion() *VersionParams { return nil } +func (m *ConsensusParams) GetAbci() *ABCIParams { + if m != nil { + return m.Abci + } + return nil +} + +// HashedParams is a subset of ConsensusParams. +// +// It is hashed into the Header.ConsensusHash. +type HashedParams struct { + BlockMaxBytes int64 `protobuf:"varint,1,opt,name=block_max_bytes,json=blockMaxBytes,proto3" json:"block_max_bytes,omitempty"` + BlockMaxGas int64 `protobuf:"varint,2,opt,name=block_max_gas,json=blockMaxGas,proto3" json:"block_max_gas,omitempty"` +} + +func (m *HashedParams) Reset() { *m = HashedParams{} } +func (m *HashedParams) String() string { return proto.CompactTextString(m) } +func (*HashedParams) ProtoMessage() {} +func (*HashedParams) Descriptor() ([]byte, []int) { + return fileDescriptor_e12598271a686f57, []int{1} +} +func (m *HashedParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *HashedParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_HashedParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *HashedParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_HashedParams.Merge(m, src) +} +func (m *HashedParams) XXX_Size() int { + return m.Size() +} +func (m *HashedParams) XXX_DiscardUnknown() { + xxx_messageInfo_HashedParams.DiscardUnknown(m) +} + +var xxx_messageInfo_HashedParams proto.InternalMessageInfo + +func (m *HashedParams) GetBlockMaxBytes() int64 { + if m != nil { + return m.BlockMaxBytes + } + return 0 +} + +func (m *HashedParams) GetBlockMaxGas() int64 { + if m != nil { + return m.BlockMaxGas + } + return 0 +} + // BlockParams contains limits on the block size. type BlockParams struct { // Max block size, in bytes. @@ -111,7 +174,7 @@ func (m *BlockParams) Reset() { *m = BlockParams{} } func (m *BlockParams) String() string { return proto.CompactTextString(m) } func (*BlockParams) ProtoMessage() {} func (*BlockParams) Descriptor() ([]byte, []int) { - return fileDescriptor_e12598271a686f57, []int{1} + return fileDescriptor_e12598271a686f57, []int{2} } func (m *BlockParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -177,7 +240,7 @@ func (m *EvidenceParams) Reset() { *m = EvidenceParams{} } func (m *EvidenceParams) String() string { return proto.CompactTextString(m) } func (*EvidenceParams) ProtoMessage() {} func (*EvidenceParams) Descriptor() ([]byte, []int) { - return fileDescriptor_e12598271a686f57, []int{2} + return fileDescriptor_e12598271a686f57, []int{3} } func (m *EvidenceParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -237,7 +300,7 @@ func (m *ValidatorParams) Reset() { *m = ValidatorParams{} } func (m *ValidatorParams) String() string { return proto.CompactTextString(m) } func (*ValidatorParams) ProtoMessage() {} func (*ValidatorParams) Descriptor() ([]byte, []int) { - return fileDescriptor_e12598271a686f57, []int{3} + return fileDescriptor_e12598271a686f57, []int{4} } func (m *ValidatorParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -282,7 +345,7 @@ func (m *VersionParams) Reset() { *m = VersionParams{} } func (m *VersionParams) String() string { return proto.CompactTextString(m) } func (*VersionParams) ProtoMessage() {} func (*VersionParams) Descriptor() ([]byte, []int) { - return fileDescriptor_e12598271a686f57, []int{4} + return fileDescriptor_e12598271a686f57, []int{5} } func (m *VersionParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -318,26 +381,32 @@ func (m *VersionParams) GetApp() uint64 { return 0 } -// HashedParams is a subset of ConsensusParams. -// -// It is hashed into the Header.ConsensusHash. -type HashedParams struct { - BlockMaxBytes int64 `protobuf:"varint,1,opt,name=block_max_bytes,json=blockMaxBytes,proto3" json:"block_max_bytes,omitempty"` - BlockMaxGas int64 `protobuf:"varint,2,opt,name=block_max_gas,json=blockMaxGas,proto3" json:"block_max_gas,omitempty"` +// ABCIParams configure functionality specific to the Application Blockchain Interface. +type ABCIParams struct { + // vote_extensions_enable_height configures the first height during which + // vote extensions will be enabled. During this specified height, and for all + // subsequent heights, precommit messages that do not contain valid extension data + // will be considered invalid. Prior to this height, vote extensions will not + // be used or accepted by validators on the network. + // + // Once enabled, vote extensions will be created by the application in ExtendVote, + // passed to the application for validation in VerifyVoteExtension and given + // to the application to use when proposing a block during PrepareProposal. + VoteExtensionsEnableHeight int64 `protobuf:"varint,1,opt,name=vote_extensions_enable_height,json=voteExtensionsEnableHeight,proto3" json:"vote_extensions_enable_height,omitempty"` } -func (m *HashedParams) Reset() { *m = HashedParams{} } -func (m *HashedParams) String() string { return proto.CompactTextString(m) } -func (*HashedParams) ProtoMessage() {} -func (*HashedParams) Descriptor() ([]byte, []int) { - return fileDescriptor_e12598271a686f57, []int{5} +func (m *ABCIParams) Reset() { *m = ABCIParams{} } +func (m *ABCIParams) String() string { return proto.CompactTextString(m) } +func (*ABCIParams) ProtoMessage() {} +func (*ABCIParams) Descriptor() ([]byte, []int) { + return fileDescriptor_e12598271a686f57, []int{6} } -func (m *HashedParams) XXX_Unmarshal(b []byte) error { +func (m *ABCIParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *HashedParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *ABCIParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_HashedParams.Marshal(b, m, deterministic) + return xxx_messageInfo_ABCIParams.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -347,78 +416,75 @@ func (m *HashedParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) return b[:n], nil } } -func (m *HashedParams) XXX_Merge(src proto.Message) { - xxx_messageInfo_HashedParams.Merge(m, src) +func (m *ABCIParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_ABCIParams.Merge(m, src) } -func (m *HashedParams) XXX_Size() int { +func (m *ABCIParams) XXX_Size() int { return m.Size() } -func (m *HashedParams) XXX_DiscardUnknown() { - xxx_messageInfo_HashedParams.DiscardUnknown(m) +func (m *ABCIParams) XXX_DiscardUnknown() { + xxx_messageInfo_ABCIParams.DiscardUnknown(m) } -var xxx_messageInfo_HashedParams proto.InternalMessageInfo +var xxx_messageInfo_ABCIParams proto.InternalMessageInfo -func (m *HashedParams) GetBlockMaxBytes() int64 { +func (m *ABCIParams) GetVoteExtensionsEnableHeight() int64 { if m != nil { - return m.BlockMaxBytes - } - return 0 -} - -func (m *HashedParams) GetBlockMaxGas() int64 { - if m != nil { - return m.BlockMaxGas + return m.VoteExtensionsEnableHeight } return 0 } func init() { proto.RegisterType((*ConsensusParams)(nil), "tendermint.types.ConsensusParams") + proto.RegisterType((*HashedParams)(nil), "tendermint.types.HashedParams") proto.RegisterType((*BlockParams)(nil), "tendermint.types.BlockParams") proto.RegisterType((*EvidenceParams)(nil), "tendermint.types.EvidenceParams") proto.RegisterType((*ValidatorParams)(nil), "tendermint.types.ValidatorParams") proto.RegisterType((*VersionParams)(nil), "tendermint.types.VersionParams") - proto.RegisterType((*HashedParams)(nil), "tendermint.types.HashedParams") + proto.RegisterType((*ABCIParams)(nil), "tendermint.types.ABCIParams") } func init() { proto.RegisterFile("tendermint/types/params.proto", fileDescriptor_e12598271a686f57) } var fileDescriptor_e12598271a686f57 = []byte{ - // 513 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x93, 0x4f, 0x6f, 0xd3, 0x30, - 0x18, 0xc6, 0xeb, 0xa5, 0x6c, 0xed, 0x5b, 0xba, 0x56, 0x16, 0x12, 0x61, 0x68, 0x49, 0xc9, 0x01, - 0x4d, 0x9a, 0x94, 0x48, 0xec, 0x80, 0x40, 0x48, 0x13, 0x05, 0x34, 0xfe, 0x68, 0x08, 0x45, 0xc0, - 0x61, 0x97, 0xc8, 0x69, 0x4c, 0x16, 0xad, 0x8e, 0xa3, 0x38, 0xa9, 0xda, 0x1b, 0x1f, 0x81, 0x23, - 0xc7, 0x1d, 0xe1, 0x1b, 0xf0, 0x11, 0x76, 0xdc, 0x91, 0x13, 0xa0, 0xf6, 0xc2, 0xc7, 0x40, 0x71, - 0x62, 0xd2, 0x76, 0xbb, 0x39, 0x7e, 0x7e, 0x8f, 0x9d, 0xe7, 0xb1, 0x5e, 0xd8, 0xcd, 0x68, 0x1c, - 0xd0, 0x94, 0x45, 0x71, 0xe6, 0x64, 0xb3, 0x84, 0x0a, 0x27, 0x21, 0x29, 0x61, 0xc2, 0x4e, 0x52, - 0x9e, 0x71, 0xdc, 0xaf, 0x65, 0x5b, 0xca, 0x3b, 0xb7, 0x42, 0x1e, 0x72, 0x29, 0x3a, 0xc5, 0xaa, - 0xe4, 0x76, 0x8c, 0x90, 0xf3, 0x70, 0x4c, 0x1d, 0xf9, 0xe5, 0xe7, 0x9f, 0x9c, 0x20, 0x4f, 0x49, - 0x16, 0xf1, 0xb8, 0xd4, 0xad, 0xcf, 0x1b, 0xd0, 0x7b, 0xc6, 0x63, 0x41, 0x63, 0x91, 0x8b, 0x77, - 0xf2, 0x06, 0x7c, 0x00, 0x37, 0xfc, 0x31, 0x1f, 0x9d, 0xe9, 0x68, 0x80, 0xf6, 0x3a, 0x0f, 0x76, - 0xed, 0xf5, 0xbb, 0xec, 0x61, 0x21, 0x97, 0xb4, 0x5b, 0xb2, 0xf8, 0x09, 0xb4, 0xe8, 0x24, 0x0a, - 0x68, 0x3c, 0xa2, 0xfa, 0x86, 0xf4, 0x0d, 0xae, 0xfa, 0x5e, 0x54, 0x44, 0x65, 0xfd, 0xef, 0xc0, - 0x87, 0xd0, 0x9e, 0x90, 0x71, 0x14, 0x90, 0x8c, 0xa7, 0xba, 0x26, 0xed, 0xf7, 0xae, 0xda, 0x3f, - 0x2a, 0xa4, 0xf2, 0xd7, 0x1e, 0xfc, 0x08, 0xb6, 0x26, 0x34, 0x15, 0x11, 0x8f, 0xf5, 0xa6, 0xb4, - 0x9b, 0xd7, 0xd8, 0x4b, 0xa0, 0x32, 0x2b, 0xde, 0x7a, 0x05, 0x9d, 0xa5, 0x3c, 0xf8, 0x2e, 0xb4, - 0x19, 0x99, 0x7a, 0xfe, 0x2c, 0xa3, 0x42, 0x36, 0xa0, 0xb9, 0x2d, 0x46, 0xa6, 0xc3, 0xe2, 0x1b, - 0xdf, 0x86, 0xad, 0x42, 0x0c, 0x89, 0x90, 0x21, 0x35, 0x77, 0x93, 0x91, 0xe9, 0x11, 0x11, 0xaf, - 0x9b, 0x2d, 0xad, 0xdf, 0xb4, 0xbe, 0x23, 0xd8, 0x5e, 0xcd, 0x88, 0xf7, 0x01, 0x17, 0x0e, 0x12, - 0x52, 0x2f, 0xce, 0x99, 0x27, 0xcb, 0x52, 0xe7, 0xf6, 0x18, 0x99, 0x3e, 0x0d, 0xe9, 0xdb, 0x9c, - 0xc9, 0x1f, 0x10, 0xf8, 0x18, 0xfa, 0x0a, 0x56, 0xef, 0x54, 0x95, 0x79, 0xc7, 0x2e, 0x1f, 0xd2, - 0x56, 0x0f, 0x69, 0x3f, 0xaf, 0x80, 0x61, 0xeb, 0xe2, 0x97, 0xd9, 0xf8, 0xfa, 0xdb, 0x44, 0xee, - 0x76, 0x79, 0x9e, 0x52, 0x56, 0xa3, 0x68, 0xab, 0x51, 0xac, 0x43, 0xe8, 0xad, 0xf5, 0x89, 0x2d, - 0xe8, 0x26, 0xb9, 0xef, 0x9d, 0xd1, 0x99, 0x27, 0x1b, 0xd3, 0xd1, 0x40, 0xdb, 0x6b, 0xbb, 0x9d, - 0x24, 0xf7, 0xdf, 0xd0, 0xd9, 0xfb, 0x62, 0xeb, 0x71, 0xeb, 0xc7, 0xb9, 0x89, 0xfe, 0x9e, 0x9b, - 0xc8, 0xda, 0x87, 0xee, 0x4a, 0xa3, 0xb8, 0x0f, 0x1a, 0x49, 0x12, 0x99, 0xad, 0xe9, 0x16, 0xcb, - 0x25, 0xf8, 0x04, 0x6e, 0xbe, 0x24, 0xe2, 0x94, 0x06, 0x15, 0x7b, 0x1f, 0x7a, 0xb2, 0x0a, 0x6f, - 0xbd, 0xeb, 0xae, 0xdc, 0x3e, 0x56, 0x85, 0x5b, 0xd0, 0xad, 0xb9, 0xba, 0xf6, 0x8e, 0xa2, 0x8e, - 0x88, 0x18, 0x7e, 0xf8, 0x36, 0x37, 0xd0, 0xc5, 0xdc, 0x40, 0x97, 0x73, 0x03, 0xfd, 0x99, 0x1b, - 0xe8, 0xcb, 0xc2, 0x68, 0x5c, 0x2e, 0x8c, 0xc6, 0xcf, 0x85, 0xd1, 0x38, 0x79, 0x18, 0x46, 0xd9, - 0x69, 0xee, 0xdb, 0x23, 0xce, 0x9c, 0xe5, 0x99, 0xaa, 0x97, 0xe5, 0xd0, 0xac, 0xcf, 0x9b, 0xbf, - 0x29, 0xf7, 0x0f, 0xfe, 0x05, 0x00, 0x00, 0xff, 0xff, 0x48, 0xd2, 0x61, 0x14, 0x8a, 0x03, 0x00, - 0x00, + // 569 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x93, 0x3f, 0x6f, 0xd3, 0x40, + 0x14, 0xc0, 0x73, 0x75, 0xda, 0x26, 0x2f, 0xa4, 0x89, 0x4e, 0x48, 0x98, 0x40, 0x9c, 0xe0, 0x01, + 0x55, 0xaa, 0x64, 0x23, 0x3a, 0x20, 0x10, 0x52, 0x95, 0x94, 0x88, 0x16, 0x54, 0x40, 0x16, 0x30, + 0x74, 0xb1, 0xce, 0xc9, 0xe1, 0x58, 0x8d, 0x7d, 0x96, 0xef, 0x1c, 0x25, 0xdf, 0x82, 0x91, 0xb1, + 0x23, 0xac, 0x4c, 0x7c, 0x84, 0x8e, 0x1d, 0x99, 0x00, 0x25, 0x0b, 0x1f, 0x03, 0xf9, 0x6c, 0x37, + 0xff, 0xd8, 0xce, 0xef, 0xfd, 0x7e, 0xef, 0x7c, 0xef, 0xdd, 0x41, 0x53, 0xd0, 0x60, 0x40, 0x23, + 0xdf, 0x0b, 0x84, 0x29, 0xa6, 0x21, 0xe5, 0x66, 0x48, 0x22, 0xe2, 0x73, 0x23, 0x8c, 0x98, 0x60, + 0xb8, 0xbe, 0x48, 0x1b, 0x32, 0xdd, 0xb8, 0xed, 0x32, 0x97, 0xc9, 0xa4, 0x99, 0xac, 0x52, 0xae, + 0xa1, 0xb9, 0x8c, 0xb9, 0x23, 0x6a, 0xca, 0x2f, 0x27, 0xfe, 0x64, 0x0e, 0xe2, 0x88, 0x08, 0x8f, + 0x05, 0x69, 0x5e, 0xff, 0xbe, 0x05, 0xb5, 0x63, 0x16, 0x70, 0x1a, 0xf0, 0x98, 0xbf, 0x93, 0x3b, + 0xe0, 0x43, 0xd8, 0x76, 0x46, 0xac, 0x7f, 0xa1, 0xa2, 0x36, 0xda, 0xaf, 0x3c, 0x6e, 0x1a, 0xeb, + 0x7b, 0x19, 0xdd, 0x24, 0x9d, 0xd2, 0x56, 0xca, 0xe2, 0xe7, 0x50, 0xa2, 0x63, 0x6f, 0x40, 0x83, + 0x3e, 0x55, 0xb7, 0xa4, 0xd7, 0xde, 0xf4, 0x7a, 0x19, 0x91, 0xa9, 0x37, 0x06, 0x3e, 0x82, 0xf2, + 0x98, 0x8c, 0xbc, 0x01, 0x11, 0x2c, 0x52, 0x15, 0xa9, 0x3f, 0xd8, 0xd4, 0x3f, 0xe6, 0x48, 0xe6, + 0x2f, 0x1c, 0xfc, 0x14, 0x76, 0xc7, 0x34, 0xe2, 0x1e, 0x0b, 0xd4, 0xa2, 0xd4, 0x5b, 0xff, 0xd1, + 0x53, 0x20, 0x93, 0x73, 0x1e, 0x3f, 0x82, 0x22, 0x71, 0xfa, 0x9e, 0xba, 0x2d, 0xbd, 0xfb, 0x9b, + 0x5e, 0xa7, 0x7b, 0x7c, 0x9a, 0x49, 0x92, 0xd4, 0xcf, 0xe1, 0xd6, 0x09, 0xe1, 0x43, 0x3a, 0xc8, + 0x1a, 0xf6, 0x10, 0x6a, 0xb2, 0x09, 0xb6, 0x4f, 0x26, 0xb6, 0x33, 0x15, 0x94, 0xcb, 0xd6, 0x29, + 0x56, 0x55, 0x86, 0xcf, 0xc8, 0xa4, 0x9b, 0x04, 0xb1, 0x0e, 0xd5, 0x05, 0xe7, 0x12, 0x2e, 0x1b, + 0xa5, 0x58, 0x95, 0x9c, 0x7a, 0x49, 0xb8, 0x7e, 0x0a, 0x95, 0xa5, 0xee, 0xe2, 0x7b, 0x50, 0x5e, + 0x2f, 0x5a, 0xf2, 0xf3, 0x7a, 0x77, 0x60, 0x77, 0xb5, 0xd2, 0x8e, 0x2f, 0x8b, 0xbc, 0x2a, 0x96, + 0x94, 0x7a, 0x51, 0xff, 0x86, 0x60, 0x6f, 0xb5, 0xe3, 0xf8, 0x00, 0x70, 0x62, 0x10, 0x97, 0xda, + 0x41, 0xec, 0xdb, 0x72, 0xe3, 0xbc, 0x6e, 0xcd, 0x27, 0x93, 0x8e, 0x4b, 0xdf, 0xc4, 0xbe, 0xfc, + 0x01, 0x8e, 0xcf, 0xa0, 0x9e, 0xc3, 0xf9, 0xad, 0xc9, 0x46, 0x7b, 0xd7, 0x48, 0xaf, 0x95, 0x91, + 0x5f, 0x2b, 0xe3, 0x45, 0x06, 0x74, 0x4b, 0x57, 0xbf, 0x5a, 0x85, 0x2f, 0xbf, 0x5b, 0xc8, 0xda, + 0x4b, 0xeb, 0xe5, 0x99, 0xd5, 0xa3, 0x28, 0xab, 0x47, 0xd1, 0x8f, 0xa0, 0xb6, 0x36, 0xdd, 0xa4, + 0x5b, 0x61, 0xec, 0xd8, 0x17, 0x74, 0x6a, 0xcb, 0x39, 0xa8, 0xa8, 0xad, 0xec, 0x97, 0xad, 0x4a, + 0x18, 0x3b, 0xaf, 0xe9, 0xf4, 0x7d, 0x12, 0x7a, 0x56, 0xfa, 0x71, 0xd9, 0x42, 0x7f, 0x2f, 0x5b, + 0x48, 0x3f, 0x80, 0xea, 0xca, 0x7c, 0x71, 0x1d, 0x14, 0x12, 0x86, 0xf2, 0x6c, 0x45, 0x2b, 0x59, + 0x2e, 0xc1, 0x6f, 0x01, 0x16, 0x43, 0xc5, 0x1d, 0x68, 0x8e, 0x99, 0xa0, 0x36, 0x9d, 0x08, 0x1a, + 0x24, 0x15, 0xb8, 0x4d, 0x03, 0xe2, 0x8c, 0xa8, 0x3d, 0xa4, 0x9e, 0x3b, 0x14, 0x59, 0x7f, 0x1a, + 0x09, 0xd4, 0xbb, 0x61, 0x7a, 0x12, 0x39, 0x91, 0x44, 0xf7, 0xc3, 0xd7, 0x99, 0x86, 0xae, 0x66, + 0x1a, 0xba, 0x9e, 0x69, 0xe8, 0xcf, 0x4c, 0x43, 0x9f, 0xe7, 0x5a, 0xe1, 0x7a, 0xae, 0x15, 0x7e, + 0xce, 0xb5, 0xc2, 0xf9, 0x13, 0xd7, 0x13, 0xc3, 0xd8, 0x31, 0xfa, 0xcc, 0x37, 0x97, 0x9f, 0xf5, + 0x62, 0x99, 0xbe, 0xdb, 0xf5, 0x27, 0xef, 0xec, 0xc8, 0xf8, 0xe1, 0xbf, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xda, 0x5f, 0x18, 0x9a, 0x0d, 0x04, 0x00, 0x00, } func (this *ConsensusParams) Equal(that interface{}) bool { @@ -452,6 +518,36 @@ func (this *ConsensusParams) Equal(that interface{}) bool { if !this.Version.Equal(that1.Version) { return false } + if !this.Abci.Equal(that1.Abci) { + return false + } + return true +} +func (this *HashedParams) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*HashedParams) + if !ok { + that2, ok := that.(HashedParams) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.BlockMaxBytes != that1.BlockMaxBytes { + return false + } + if this.BlockMaxGas != that1.BlockMaxGas { + return false + } return true } func (this *BlockParams) Equal(that interface{}) bool { @@ -564,14 +660,14 @@ func (this *VersionParams) Equal(that interface{}) bool { } return true } -func (this *HashedParams) Equal(that interface{}) bool { +func (this *ABCIParams) Equal(that interface{}) bool { if that == nil { return this == nil } - that1, ok := that.(*HashedParams) + that1, ok := that.(*ABCIParams) if !ok { - that2, ok := that.(HashedParams) + that2, ok := that.(ABCIParams) if ok { that1 = &that2 } else { @@ -583,10 +679,7 @@ func (this *HashedParams) Equal(that interface{}) bool { } else if this == nil { return false } - if this.BlockMaxBytes != that1.BlockMaxBytes { - return false - } - if this.BlockMaxGas != that1.BlockMaxGas { + if this.VoteExtensionsEnableHeight != that1.VoteExtensionsEnableHeight { return false } return true @@ -611,6 +704,18 @@ func (m *ConsensusParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Abci != nil { + { + size, err := m.Abci.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } if m.Version != nil { { size, err := m.Version.MarshalToSizedBuffer(dAtA[:i]) @@ -662,6 +767,39 @@ func (m *ConsensusParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *HashedParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *HashedParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *HashedParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.BlockMaxGas != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.BlockMaxGas)) + i-- + dAtA[i] = 0x10 + } + if m.BlockMaxBytes != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.BlockMaxBytes)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *BlockParams) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -720,12 +858,12 @@ func (m *EvidenceParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x18 } - n5, err5 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.MaxAgeDuration, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.MaxAgeDuration):]) - if err5 != nil { - return 0, err5 + n6, err6 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.MaxAgeDuration, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.MaxAgeDuration):]) + if err6 != nil { + return 0, err6 } - i -= n5 - i = encodeVarintParams(dAtA, i, uint64(n5)) + i -= n6 + i = encodeVarintParams(dAtA, i, uint64(n6)) i-- dAtA[i] = 0x12 if m.MaxAgeNumBlocks != 0 { @@ -796,7 +934,7 @@ func (m *VersionParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *HashedParams) Marshal() (dAtA []byte, err error) { +func (m *ABCIParams) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -806,23 +944,18 @@ func (m *HashedParams) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *HashedParams) MarshalTo(dAtA []byte) (int, error) { +func (m *ABCIParams) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *HashedParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ABCIParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.BlockMaxGas != 0 { - i = encodeVarintParams(dAtA, i, uint64(m.BlockMaxGas)) - i-- - dAtA[i] = 0x10 - } - if m.BlockMaxBytes != 0 { - i = encodeVarintParams(dAtA, i, uint64(m.BlockMaxBytes)) + if m.VoteExtensionsEnableHeight != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.VoteExtensionsEnableHeight)) i-- dAtA[i] = 0x8 } @@ -954,6 +1087,25 @@ func (m *ConsensusParams) Size() (n int) { l = m.Version.Size() n += 1 + l + sovParams(uint64(l)) } + if m.Abci != nil { + l = m.Abci.Size() + n += 1 + l + sovParams(uint64(l)) + } + return n +} + +func (m *HashedParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BlockMaxBytes != 0 { + n += 1 + sovParams(uint64(m.BlockMaxBytes)) + } + if m.BlockMaxGas != 0 { + n += 1 + sovParams(uint64(m.BlockMaxGas)) + } return n } @@ -1016,17 +1168,14 @@ func (m *VersionParams) Size() (n int) { return n } -func (m *HashedParams) Size() (n int) { +func (m *ABCIParams) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.BlockMaxBytes != 0 { - n += 1 + sovParams(uint64(m.BlockMaxBytes)) - } - if m.BlockMaxGas != 0 { - n += 1 + sovParams(uint64(m.BlockMaxGas)) + if m.VoteExtensionsEnableHeight != 0 { + n += 1 + sovParams(uint64(m.VoteExtensionsEnableHeight)) } return n } @@ -1210,6 +1359,130 @@ func (m *ConsensusParams) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Abci", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Abci == nil { + m.Abci = &ABCIParams{} + } + if err := m.Abci.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipParams(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthParams + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *HashedParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: HashedParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: HashedParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BlockMaxBytes", wireType) + } + m.BlockMaxBytes = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BlockMaxBytes |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BlockMaxGas", wireType) + } + m.BlockMaxGas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BlockMaxGas |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) @@ -1591,7 +1864,7 @@ func (m *VersionParams) Unmarshal(dAtA []byte) error { } return nil } -func (m *HashedParams) Unmarshal(dAtA []byte) error { +func (m *ABCIParams) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1614,17 +1887,17 @@ func (m *HashedParams) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: HashedParams: wiretype end group for non-group") + return fmt.Errorf("proto: ABCIParams: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: HashedParams: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ABCIParams: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field BlockMaxBytes", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field VoteExtensionsEnableHeight", wireType) } - m.BlockMaxBytes = 0 + m.VoteExtensionsEnableHeight = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowParams @@ -1634,26 +1907,7 @@ func (m *HashedParams) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.BlockMaxBytes |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field BlockMaxGas", wireType) - } - m.BlockMaxGas = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowParams - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.BlockMaxGas |= int64(b&0x7F) << shift + m.VoteExtensionsEnableHeight |= int64(b&0x7F) << shift if b < 0x80 { break } diff --git a/proto/tendermint/types/params.proto b/proto/tendermint/types/params.proto index 195aa8d17..359798b88 100644 --- a/proto/tendermint/types/params.proto +++ b/proto/tendermint/types/params.proto @@ -15,6 +15,15 @@ message ConsensusParams { EvidenceParams evidence = 2; ValidatorParams validator = 3; VersionParams version = 4; + ABCIParams abci = 5; +} + +// HashedParams is a subset of ConsensusParams. +// +// It is hashed into the Header.ConsensusHash. +message HashedParams { + int64 block_max_bytes = 1; + int64 block_max_gas = 2; } // BlockParams contains limits on the block size. @@ -68,10 +77,16 @@ message VersionParams { uint64 app = 1; } -// HashedParams is a subset of ConsensusParams. -// -// It is hashed into the Header.ConsensusHash. -message HashedParams { - int64 block_max_bytes = 1; - int64 block_max_gas = 2; +// ABCIParams configure functionality specific to the Application Blockchain Interface. +message ABCIParams { + // vote_extensions_enable_height configures the first height during which + // vote extensions will be enabled. During this specified height, and for all + // subsequent heights, precommit messages that do not contain valid extension data + // will be considered invalid. Prior to this height, vote extensions will not + // be used or accepted by validators on the network. + // + // Once enabled, vote extensions will be created by the application in ExtendVote, + // passed to the application for validation in VerifyVoteExtension and given + // to the application to use when proposing a block during PrepareProposal. + int64 vote_extensions_enable_height = 1; } diff --git a/proto/tendermint/types/types.pb.go b/proto/tendermint/types/types.pb.go index 08d9be5d4..edc1a3910 100644 --- a/proto/tendermint/types/types.pb.go +++ b/proto/tendermint/types/types.pb.go @@ -463,7 +463,7 @@ func (m *Data) GetTxs() [][]byte { return nil } -// Vote represents a prevote, precommit, or commit vote from validators for +// Vote represents a prevote or precommit vote from validators for // consensus. type Vote struct { Type SignedMsgType `protobuf:"varint,1,opt,name=type,proto3,enum=tendermint.types.SignedMsgType" json:"type,omitempty"` @@ -473,7 +473,14 @@ type Vote struct { Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime" json:"timestamp"` ValidatorAddress []byte `protobuf:"bytes,6,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` ValidatorIndex int32 `protobuf:"varint,7,opt,name=validator_index,json=validatorIndex,proto3" json:"validator_index,omitempty"` - Signature []byte `protobuf:"bytes,8,opt,name=signature,proto3" json:"signature,omitempty"` + // signature of + Signature []byte `protobuf:"bytes,8,opt,name=signature,proto3" json:"signature,omitempty"` + // Vote extension provided by the application. Only valid for precommit + // messages. + Extension []byte `protobuf:"bytes,9,opt,name=extension,proto3" json:"extension,omitempty"` + // separate signature over the extension bytes. This enables backwards compatibility. + // Only valid for precommit messages. + ExtensionSignature []byte `protobuf:"bytes,10,opt,name=extension_signature,json=extensionSignature,proto3" json:"extension_signature,omitempty"` } func (m *Vote) Reset() { *m = Vote{} } @@ -565,6 +572,20 @@ func (m *Vote) GetSignature() []byte { return nil } +func (m *Vote) GetExtension() []byte { + if m != nil { + return m.Extension + } + return nil +} + +func (m *Vote) GetExtensionSignature() []byte { + if m != nil { + return m.ExtensionSignature + } + return nil +} + // Commit contains the evidence that a block was committed by a set of validators. type Commit struct { Height int64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` @@ -1208,94 +1229,95 @@ func init() { func init() { proto.RegisterFile("tendermint/types/types.proto", fileDescriptor_d3a6e55e2345de56) } var fileDescriptor_d3a6e55e2345de56 = []byte{ - // 1387 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x57, 0x4f, 0x6f, 0x1a, 0xd7, - 0x16, 0xf7, 0xc0, 0x60, 0xe0, 0x00, 0x36, 0xbe, 0xcf, 0x49, 0x08, 0x89, 0x31, 0x22, 0x7a, 0xef, - 0x39, 0x79, 0x4f, 0x38, 0x75, 0xaa, 0xfe, 0x59, 0x74, 0x01, 0x98, 0x24, 0x28, 0x36, 0xa6, 0x03, - 0x49, 0xd5, 0x6c, 0x46, 0x03, 0x73, 0x03, 0xd3, 0xc0, 0xcc, 0x68, 0xe6, 0xe2, 0xda, 0xf9, 0x04, - 0x95, 0x57, 0x59, 0x75, 0xe7, 0x55, 0xbb, 0xe8, 0xbe, 0xfd, 0x00, 0x55, 0x57, 0x59, 0x66, 0xd7, - 0x6e, 0x9a, 0xb6, 0x8e, 0x54, 0xf5, 0x63, 0x54, 0xf7, 0xdc, 0xcb, 0x30, 0x18, 0xbb, 0x8d, 0xa2, - 0xa8, 0x95, 0xba, 0x41, 0x73, 0xcf, 0xf9, 0x9d, 0x7b, 0xcf, 0xfd, 0x9d, 0xdf, 0x1c, 0xce, 0xc0, - 0x55, 0x46, 0x6d, 0x93, 0x7a, 0x23, 0xcb, 0x66, 0x9b, 0xec, 0xd0, 0xa5, 0xbe, 0xf8, 0x2d, 0xbb, - 0x9e, 0xc3, 0x1c, 0x92, 0x9d, 0x7a, 0xcb, 0x68, 0xcf, 0xaf, 0xf6, 0x9d, 0xbe, 0x83, 0xce, 0x4d, - 0xfe, 0x24, 0x70, 0xf9, 0xf5, 0xbe, 0xe3, 0xf4, 0x87, 0x74, 0x13, 0x57, 0xdd, 0xf1, 0xa3, 0x4d, - 0x66, 0x8d, 0xa8, 0xcf, 0x8c, 0x91, 0x2b, 0x01, 0x6b, 0xa1, 0x63, 0x7a, 0xde, 0xa1, 0xcb, 0x1c, - 0x8e, 0x75, 0x1e, 0x49, 0x77, 0x21, 0xe4, 0xde, 0xa7, 0x9e, 0x6f, 0x39, 0x76, 0x38, 0x8f, 0x7c, - 0x71, 0x2e, 0xcb, 0x7d, 0x63, 0x68, 0x99, 0x06, 0x73, 0x3c, 0x81, 0x28, 0xbd, 0x0f, 0x99, 0x96, - 0xe1, 0xb1, 0x36, 0x65, 0x77, 0xa9, 0x61, 0x52, 0x8f, 0xac, 0x42, 0x8c, 0x39, 0xcc, 0x18, 0xe6, - 0x94, 0xa2, 0xb2, 0x91, 0xd1, 0xc4, 0x82, 0x10, 0x50, 0x07, 0x86, 0x3f, 0xc8, 0x45, 0x8a, 0xca, - 0x46, 0x5a, 0xc3, 0xe7, 0xd2, 0x00, 0x54, 0x1e, 0xca, 0x23, 0x2c, 0xdb, 0xa4, 0x07, 0x93, 0x08, - 0x5c, 0x70, 0x6b, 0xf7, 0x90, 0x51, 0x5f, 0x86, 0x88, 0x05, 0x79, 0x1b, 0x62, 0x98, 0x7f, 0x2e, - 0x5a, 0x54, 0x36, 0x52, 0x5b, 0xb9, 0x72, 0x88, 0x28, 0x71, 0xbf, 0x72, 0x8b, 0xfb, 0xab, 0xea, - 0xb3, 0x17, 0xeb, 0x0b, 0x9a, 0x00, 0x97, 0x86, 0x10, 0xaf, 0x0e, 0x9d, 0xde, 0xe3, 0xc6, 0x76, - 0x90, 0x88, 0x32, 0x4d, 0x84, 0xec, 0xc2, 0xb2, 0x6b, 0x78, 0x4c, 0xf7, 0x29, 0xd3, 0x07, 0x78, - 0x0b, 0x3c, 0x34, 0xb5, 0xb5, 0x5e, 0x3e, 0x5d, 0x87, 0xf2, 0xcc, 0x65, 0xe5, 0x29, 0x19, 0x37, - 0x6c, 0x2c, 0xfd, 0xaa, 0xc2, 0xa2, 0x24, 0xe3, 0x03, 0x88, 0x4b, 0x5a, 0xf1, 0xc0, 0xd4, 0xd6, - 0x5a, 0x78, 0x47, 0xe9, 0x2a, 0xd7, 0x1c, 0xdb, 0xa7, 0xb6, 0x3f, 0xf6, 0xe5, 0x7e, 0x93, 0x18, - 0xf2, 0x1f, 0x48, 0xf4, 0x06, 0x86, 0x65, 0xeb, 0x96, 0x89, 0x19, 0x25, 0xab, 0xa9, 0x93, 0x17, - 0xeb, 0xf1, 0x1a, 0xb7, 0x35, 0xb6, 0xb5, 0x38, 0x3a, 0x1b, 0x26, 0xb9, 0x08, 0x8b, 0x03, 0x6a, - 0xf5, 0x07, 0x0c, 0x69, 0x89, 0x6a, 0x72, 0x45, 0xde, 0x03, 0x95, 0x0b, 0x22, 0xa7, 0xe2, 0xd9, - 0xf9, 0xb2, 0x50, 0x4b, 0x79, 0xa2, 0x96, 0x72, 0x67, 0xa2, 0x96, 0x6a, 0x82, 0x1f, 0xfc, 0xf4, - 0xa7, 0x75, 0x45, 0xc3, 0x08, 0x52, 0x83, 0xcc, 0xd0, 0xf0, 0x99, 0xde, 0xe5, 0xb4, 0xf1, 0xe3, - 0x63, 0xb8, 0xc5, 0xe5, 0x79, 0x42, 0x24, 0xb1, 0x32, 0xf5, 0x14, 0x8f, 0x12, 0x26, 0x93, 0x6c, - 0x40, 0x16, 0x37, 0xe9, 0x39, 0xa3, 0x91, 0xc5, 0x74, 0xe4, 0x7d, 0x11, 0x79, 0x5f, 0xe2, 0xf6, - 0x1a, 0x9a, 0xef, 0xf2, 0x0a, 0x5c, 0x81, 0xa4, 0x69, 0x30, 0x43, 0x40, 0xe2, 0x08, 0x49, 0x70, - 0x03, 0x3a, 0xff, 0x0b, 0xcb, 0x81, 0xea, 0x7c, 0x01, 0x49, 0x88, 0x5d, 0xa6, 0x66, 0x04, 0xde, - 0x84, 0x55, 0x9b, 0x1e, 0x30, 0xfd, 0x34, 0x3a, 0x89, 0x68, 0xc2, 0x7d, 0x0f, 0x66, 0x23, 0xfe, - 0x0d, 0x4b, 0xbd, 0x09, 0xf9, 0x02, 0x0b, 0x88, 0xcd, 0x04, 0x56, 0x84, 0x5d, 0x86, 0x84, 0xe1, - 0xba, 0x02, 0x90, 0x42, 0x40, 0xdc, 0x70, 0x5d, 0x74, 0xdd, 0x80, 0x15, 0xbc, 0xa3, 0x47, 0xfd, - 0xf1, 0x90, 0xc9, 0x4d, 0xd2, 0x88, 0x59, 0xe6, 0x0e, 0x4d, 0xd8, 0x11, 0x7b, 0x0d, 0x32, 0x74, - 0xdf, 0x32, 0xa9, 0xdd, 0xa3, 0x02, 0x97, 0x41, 0x5c, 0x7a, 0x62, 0x44, 0xd0, 0x75, 0xc8, 0xba, - 0x9e, 0xe3, 0x3a, 0x3e, 0xf5, 0x74, 0xc3, 0x34, 0x3d, 0xea, 0xfb, 0xb9, 0x25, 0xb1, 0xdf, 0xc4, - 0x5e, 0x11, 0xe6, 0x52, 0x0e, 0xd4, 0x6d, 0x83, 0x19, 0x24, 0x0b, 0x51, 0x76, 0xe0, 0xe7, 0x94, - 0x62, 0x74, 0x23, 0xad, 0xf1, 0xc7, 0xd2, 0x6f, 0x11, 0x50, 0x1f, 0x38, 0x8c, 0x92, 0x5b, 0xa0, - 0xf2, 0x32, 0xa1, 0xfa, 0x96, 0xce, 0xd2, 0x73, 0xdb, 0xea, 0xdb, 0xd4, 0xdc, 0xf5, 0xfb, 0x9d, - 0x43, 0x97, 0x6a, 0x08, 0x0e, 0xc9, 0x29, 0x32, 0x23, 0xa7, 0x55, 0x88, 0x79, 0xce, 0xd8, 0x36, - 0x51, 0x65, 0x31, 0x4d, 0x2c, 0x48, 0x1d, 0x12, 0x81, 0x4a, 0xd4, 0x3f, 0x53, 0xc9, 0x32, 0x57, - 0x09, 0xd7, 0xb0, 0x34, 0x68, 0xf1, 0xae, 0x14, 0x4b, 0x15, 0x92, 0x41, 0xf3, 0x92, 0x6a, 0x7b, - 0x35, 0xc1, 0x4e, 0xc3, 0xc8, 0xff, 0x60, 0x25, 0xa8, 0x7d, 0x40, 0x9e, 0x50, 0x5c, 0x36, 0x70, - 0x48, 0xf6, 0x66, 0x64, 0xa5, 0x8b, 0x06, 0x14, 0xc7, 0x7b, 0x4d, 0x65, 0xd5, 0xc0, 0x4e, 0x74, - 0x15, 0x92, 0xbe, 0xd5, 0xb7, 0x0d, 0x36, 0xf6, 0xa8, 0x54, 0xde, 0xd4, 0x50, 0xfa, 0x56, 0x81, - 0x45, 0xa1, 0xe4, 0x10, 0x6f, 0xca, 0xd9, 0xbc, 0x45, 0xce, 0xe3, 0x2d, 0xfa, 0xfa, 0xbc, 0x55, - 0x00, 0x82, 0x64, 0xfc, 0x9c, 0x5a, 0x8c, 0x6e, 0xa4, 0xb6, 0xae, 0xcc, 0x6f, 0x24, 0x52, 0x6c, - 0x5b, 0x7d, 0xf9, 0xa2, 0x86, 0x82, 0x4a, 0x3f, 0x2a, 0x90, 0x0c, 0xfc, 0xa4, 0x02, 0x99, 0x49, - 0x5e, 0xfa, 0xa3, 0xa1, 0xd1, 0x97, 0xda, 0x59, 0x3b, 0x37, 0xb9, 0xdb, 0x43, 0xa3, 0xaf, 0xa5, - 0x64, 0x3e, 0x7c, 0x71, 0x76, 0x1d, 0x22, 0xe7, 0xd4, 0x61, 0xa6, 0xf0, 0xd1, 0xd7, 0x2b, 0xfc, - 0x4c, 0x89, 0xd4, 0xd3, 0x25, 0xfa, 0x45, 0x81, 0xa5, 0xfa, 0x01, 0xa6, 0x6f, 0xfe, 0x9d, 0xa5, - 0x7a, 0x08, 0xff, 0xa2, 0x32, 0x0d, 0x7d, 0xae, 0x66, 0xd7, 0xe6, 0x77, 0x9c, 0xcd, 0x79, 0x5a, - 0x3b, 0x32, 0xd9, 0xa5, 0x3d, 0xad, 0xe1, 0x37, 0x11, 0x58, 0x99, 0xc3, 0xff, 0xf3, 0x6a, 0xc9, - 0xbd, 0x78, 0x7b, 0xfc, 0x4f, 0x8d, 0x09, 0x6f, 0x60, 0x20, 0x9b, 0x92, 0x61, 0xbe, 0x98, 0x52, - 0x2c, 0x5b, 0x00, 0x09, 0x5c, 0x01, 0x6f, 0xa5, 0xaf, 0x23, 0x90, 0x68, 0x61, 0x5b, 0x35, 0x86, - 0x7f, 0x45, 0xb3, 0xbc, 0x02, 0x49, 0xd7, 0x19, 0xea, 0xc2, 0xa3, 0xa2, 0x27, 0xe1, 0x3a, 0x43, - 0x6d, 0x4e, 0x66, 0xb1, 0x37, 0xd4, 0x49, 0x17, 0xdf, 0x40, 0x11, 0xe2, 0xa7, 0x5f, 0x28, 0x0f, - 0xd2, 0x82, 0x0a, 0x39, 0xe6, 0xdc, 0xe4, 0x1c, 0xe0, 0xdc, 0xa4, 0xcc, 0x8f, 0x65, 0x22, 0x6d, - 0x81, 0xd4, 0x24, 0x8e, 0x47, 0x88, 0xa9, 0x40, 0x4e, 0x5a, 0xb9, 0xf3, 0x3a, 0x96, 0x26, 0x71, - 0xa5, 0xcf, 0x15, 0x80, 0x1d, 0xce, 0x2c, 0xde, 0x97, 0x0f, 0x28, 0x3e, 0xa6, 0xa0, 0xcf, 0x9c, - 0x5c, 0x38, 0xaf, 0x68, 0xf2, 0xfc, 0xb4, 0x1f, 0xce, 0xbb, 0x06, 0x99, 0xa9, 0xb6, 0x7d, 0x3a, - 0x49, 0xe6, 0x8c, 0x4d, 0x82, 0xb9, 0xa1, 0x4d, 0x99, 0x96, 0xde, 0x0f, 0xad, 0x4a, 0xdf, 0x29, - 0x90, 0xc4, 0x9c, 0x76, 0x29, 0x33, 0x66, 0x6a, 0xa8, 0xbc, 0x7e, 0x0d, 0xd7, 0x00, 0xc4, 0x36, - 0xbe, 0xf5, 0x84, 0x4a, 0x65, 0x25, 0xd1, 0xd2, 0xb6, 0x9e, 0x50, 0xf2, 0x4e, 0x40, 0x78, 0xf4, - 0x8f, 0x09, 0x97, 0x1d, 0x63, 0x42, 0xfb, 0x25, 0x88, 0xdb, 0xe3, 0x91, 0xce, 0xa7, 0x05, 0x55, - 0xa8, 0xd5, 0x1e, 0x8f, 0x3a, 0x07, 0x7e, 0xe9, 0x13, 0x88, 0x77, 0x0e, 0x70, 0x72, 0xe6, 0x12, - 0xf5, 0x1c, 0x47, 0x8e, 0x6b, 0x62, 0x4c, 0x4e, 0x70, 0x03, 0x4e, 0x27, 0x04, 0x54, 0x3e, 0x97, - 0x4d, 0xe6, 0x78, 0xfe, 0x4c, 0xca, 0xaf, 0x38, 0x93, 0xcb, 0x69, 0xfc, 0xc6, 0xf7, 0x0a, 0xa4, - 0x42, 0xed, 0x86, 0xbc, 0x05, 0x17, 0xaa, 0x3b, 0x7b, 0xb5, 0x7b, 0x7a, 0x63, 0x5b, 0xbf, 0xbd, - 0x53, 0xb9, 0xa3, 0xdf, 0x6f, 0xde, 0x6b, 0xee, 0x7d, 0xd4, 0xcc, 0x2e, 0xe4, 0x2f, 0x1e, 0x1d, - 0x17, 0x49, 0x08, 0x7b, 0xdf, 0x7e, 0x6c, 0x3b, 0x9f, 0xf2, 0xf7, 0x7c, 0x75, 0x36, 0xa4, 0x52, - 0x6d, 0xd7, 0x9b, 0x9d, 0xac, 0x92, 0xbf, 0x70, 0x74, 0x5c, 0x5c, 0x09, 0x45, 0x54, 0xba, 0x3e, - 0xb5, 0xd9, 0x7c, 0x40, 0x6d, 0x6f, 0x77, 0xb7, 0xd1, 0xc9, 0x46, 0xe6, 0x02, 0xe4, 0x1f, 0xc4, - 0x75, 0x58, 0x99, 0x0d, 0x68, 0x36, 0x76, 0xb2, 0xd1, 0x3c, 0x39, 0x3a, 0x2e, 0x2e, 0x85, 0xd0, - 0x4d, 0x6b, 0x98, 0x4f, 0x7c, 0xf6, 0x45, 0x61, 0xe1, 0xab, 0x2f, 0x0b, 0x0a, 0xbf, 0x59, 0x66, - 0xa6, 0x47, 0x90, 0xff, 0xc3, 0xa5, 0x76, 0xe3, 0x4e, 0xb3, 0xbe, 0xad, 0xef, 0xb6, 0xef, 0xe8, - 0x9d, 0x8f, 0x5b, 0xf5, 0xd0, 0xed, 0x96, 0x8f, 0x8e, 0x8b, 0x29, 0x79, 0xa5, 0xf3, 0xd0, 0x2d, - 0xad, 0xfe, 0x60, 0xaf, 0x53, 0xcf, 0x2a, 0x02, 0xdd, 0xf2, 0xe8, 0xbe, 0xc3, 0x28, 0xa2, 0x6f, - 0xc2, 0xe5, 0x33, 0xd0, 0xc1, 0xc5, 0x56, 0x8e, 0x8e, 0x8b, 0x99, 0x96, 0x47, 0xc5, 0xfb, 0x83, - 0x11, 0x65, 0xc8, 0xcd, 0x47, 0xec, 0xb5, 0xf6, 0xda, 0x95, 0x9d, 0x6c, 0x31, 0x9f, 0x3d, 0x3a, - 0x2e, 0xa6, 0x27, 0xcd, 0x90, 0xe3, 0xa7, 0x37, 0xab, 0x7e, 0xf8, 0xec, 0xa4, 0xa0, 0x3c, 0x3f, - 0x29, 0x28, 0x3f, 0x9f, 0x14, 0x94, 0xa7, 0x2f, 0x0b, 0x0b, 0xcf, 0x5f, 0x16, 0x16, 0x7e, 0x78, - 0x59, 0x58, 0x78, 0xf8, 0x6e, 0xdf, 0x62, 0x83, 0x71, 0xb7, 0xdc, 0x73, 0x46, 0x9b, 0xe1, 0xaf, - 0xc5, 0xe9, 0xa3, 0xf8, 0x6a, 0x3d, 0xfd, 0x25, 0xd9, 0x5d, 0x44, 0xfb, 0xad, 0xdf, 0x03, 0x00, - 0x00, 0xff, 0xff, 0x47, 0xd6, 0xc3, 0xba, 0x0a, 0x0f, 0x00, 0x00, + // 1396 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x57, 0x4b, 0x6f, 0x1b, 0xd5, + 0x17, 0xcf, 0xd8, 0xe3, 0xd7, 0xb1, 0x9d, 0x38, 0xf7, 0x9f, 0xb6, 0xae, 0xdb, 0x38, 0x96, 0xab, + 0x3f, 0xa4, 0x05, 0x39, 0x25, 0x45, 0x3c, 0x16, 0x2c, 0x6c, 0xc7, 0x6d, 0xad, 0x26, 0x8e, 0x19, + 0xbb, 0x45, 0x74, 0x33, 0x1a, 0x7b, 0x6e, 0xed, 0xa1, 0xf6, 0xcc, 0x68, 0xe6, 0x3a, 0x38, 0xfd, + 0x04, 0x28, 0xab, 0xae, 0xd8, 0x65, 0x05, 0x0b, 0xf6, 0x20, 0xb1, 0x45, 0xac, 0xba, 0xec, 0x0e, + 0x36, 0x14, 0x48, 0x25, 0x3e, 0x07, 0xba, 0x8f, 0x19, 0xcf, 0xc4, 0x31, 0x54, 0x51, 0x05, 0x12, + 0x9b, 0x68, 0xee, 0x39, 0xbf, 0x73, 0xee, 0x79, 0xfc, 0xee, 0xc9, 0x31, 0x5c, 0x25, 0xd8, 0xd4, + 0xb1, 0x33, 0x36, 0x4c, 0xb2, 0x45, 0x0e, 0x6d, 0xec, 0xf2, 0xbf, 0x15, 0xdb, 0xb1, 0x88, 0x85, + 0x72, 0x33, 0x6d, 0x85, 0xc9, 0x0b, 0x6b, 0x03, 0x6b, 0x60, 0x31, 0xe5, 0x16, 0xfd, 0xe2, 0xb8, + 0xc2, 0xc6, 0xc0, 0xb2, 0x06, 0x23, 0xbc, 0xc5, 0x4e, 0xbd, 0xc9, 0xa3, 0x2d, 0x62, 0x8c, 0xb1, + 0x4b, 0xb4, 0xb1, 0x2d, 0x00, 0xeb, 0x81, 0x6b, 0xfa, 0xce, 0xa1, 0x4d, 0x2c, 0x8a, 0xb5, 0x1e, + 0x09, 0x75, 0x31, 0xa0, 0x3e, 0xc0, 0x8e, 0x6b, 0x58, 0x66, 0x30, 0x8e, 0x42, 0x69, 0x2e, 0xca, + 0x03, 0x6d, 0x64, 0xe8, 0x1a, 0xb1, 0x1c, 0x8e, 0x28, 0x7f, 0x08, 0xd9, 0xb6, 0xe6, 0x90, 0x0e, + 0x26, 0x77, 0xb1, 0xa6, 0x63, 0x07, 0xad, 0x41, 0x8c, 0x58, 0x44, 0x1b, 0xe5, 0xa5, 0x92, 0xb4, + 0x99, 0x55, 0xf8, 0x01, 0x21, 0x90, 0x87, 0x9a, 0x3b, 0xcc, 0x47, 0x4a, 0xd2, 0x66, 0x46, 0x61, + 0xdf, 0xe5, 0x21, 0xc8, 0xd4, 0x94, 0x5a, 0x18, 0xa6, 0x8e, 0xa7, 0x9e, 0x05, 0x3b, 0x50, 0x69, + 0xef, 0x90, 0x60, 0x57, 0x98, 0xf0, 0x03, 0x7a, 0x17, 0x62, 0x2c, 0xfe, 0x7c, 0xb4, 0x24, 0x6d, + 0xa6, 0xb7, 0xf3, 0x95, 0x40, 0xa1, 0x78, 0x7e, 0x95, 0x36, 0xd5, 0xd7, 0xe4, 0x67, 0x2f, 0x36, + 0x96, 0x14, 0x0e, 0x2e, 0x8f, 0x20, 0x51, 0x1b, 0x59, 0xfd, 0xc7, 0xcd, 0x1d, 0x3f, 0x10, 0x69, + 0x16, 0x08, 0xda, 0x83, 0x15, 0x5b, 0x73, 0x88, 0xea, 0x62, 0xa2, 0x0e, 0x59, 0x16, 0xec, 0xd2, + 0xf4, 0xf6, 0x46, 0xe5, 0x74, 0x1f, 0x2a, 0xa1, 0x64, 0xc5, 0x2d, 0x59, 0x3b, 0x28, 0x2c, 0xff, + 0x21, 0x43, 0x5c, 0x14, 0xe3, 0x23, 0x48, 0x88, 0xb2, 0xb2, 0x0b, 0xd3, 0xdb, 0xeb, 0x41, 0x8f, + 0x42, 0x55, 0xa9, 0x5b, 0xa6, 0x8b, 0x4d, 0x77, 0xe2, 0x0a, 0x7f, 0x9e, 0x0d, 0x7a, 0x03, 0x92, + 0xfd, 0xa1, 0x66, 0x98, 0xaa, 0xa1, 0xb3, 0x88, 0x52, 0xb5, 0xf4, 0xc9, 0x8b, 0x8d, 0x44, 0x9d, + 0xca, 0x9a, 0x3b, 0x4a, 0x82, 0x29, 0x9b, 0x3a, 0xba, 0x08, 0xf1, 0x21, 0x36, 0x06, 0x43, 0xc2, + 0xca, 0x12, 0x55, 0xc4, 0x09, 0x7d, 0x00, 0x32, 0x25, 0x44, 0x5e, 0x66, 0x77, 0x17, 0x2a, 0x9c, + 0x2d, 0x15, 0x8f, 0x2d, 0x95, 0xae, 0xc7, 0x96, 0x5a, 0x92, 0x5e, 0xfc, 0xf4, 0xd7, 0x0d, 0x49, + 0x61, 0x16, 0xa8, 0x0e, 0xd9, 0x91, 0xe6, 0x12, 0xb5, 0x47, 0xcb, 0x46, 0xaf, 0x8f, 0x31, 0x17, + 0x97, 0xe7, 0x0b, 0x22, 0x0a, 0x2b, 0x42, 0x4f, 0x53, 0x2b, 0x2e, 0xd2, 0xd1, 0x26, 0xe4, 0x98, + 0x93, 0xbe, 0x35, 0x1e, 0x1b, 0x44, 0x65, 0x75, 0x8f, 0xb3, 0xba, 0x2f, 0x53, 0x79, 0x9d, 0x89, + 0xef, 0xd2, 0x0e, 0x5c, 0x81, 0x94, 0xae, 0x11, 0x8d, 0x43, 0x12, 0x0c, 0x92, 0xa4, 0x02, 0xa6, + 0x7c, 0x13, 0x56, 0x7c, 0xd6, 0xb9, 0x1c, 0x92, 0xe4, 0x5e, 0x66, 0x62, 0x06, 0xbc, 0x09, 0x6b, + 0x26, 0x9e, 0x12, 0xf5, 0x34, 0x3a, 0xc5, 0xd0, 0x88, 0xea, 0x1e, 0x84, 0x2d, 0xfe, 0x0f, 0xcb, + 0x7d, 0xaf, 0xf8, 0x1c, 0x0b, 0x0c, 0x9b, 0xf5, 0xa5, 0x0c, 0x76, 0x19, 0x92, 0x9a, 0x6d, 0x73, + 0x40, 0x9a, 0x01, 0x12, 0x9a, 0x6d, 0x33, 0xd5, 0x0d, 0x58, 0x65, 0x39, 0x3a, 0xd8, 0x9d, 0x8c, + 0x88, 0x70, 0x92, 0x61, 0x98, 0x15, 0xaa, 0x50, 0xb8, 0x9c, 0x61, 0xaf, 0x41, 0x16, 0x1f, 0x18, + 0x3a, 0x36, 0xfb, 0x98, 0xe3, 0xb2, 0x0c, 0x97, 0xf1, 0x84, 0x0c, 0x74, 0x1d, 0x72, 0xb6, 0x63, + 0xd9, 0x96, 0x8b, 0x1d, 0x55, 0xd3, 0x75, 0x07, 0xbb, 0x6e, 0x7e, 0x99, 0xfb, 0xf3, 0xe4, 0x55, + 0x2e, 0x2e, 0xe7, 0x41, 0xde, 0xd1, 0x88, 0x86, 0x72, 0x10, 0x25, 0x53, 0x37, 0x2f, 0x95, 0xa2, + 0x9b, 0x19, 0x85, 0x7e, 0x96, 0xbf, 0x8f, 0x82, 0xfc, 0xc0, 0x22, 0x18, 0xdd, 0x02, 0x99, 0xb6, + 0x89, 0xb1, 0x6f, 0xf9, 0x2c, 0x3e, 0x77, 0x8c, 0x81, 0x89, 0xf5, 0x3d, 0x77, 0xd0, 0x3d, 0xb4, + 0xb1, 0xc2, 0xc0, 0x01, 0x3a, 0x45, 0x42, 0x74, 0x5a, 0x83, 0x98, 0x63, 0x4d, 0x4c, 0x9d, 0xb1, + 0x2c, 0xa6, 0xf0, 0x03, 0x6a, 0x40, 0xd2, 0x67, 0x89, 0xfc, 0x77, 0x2c, 0x59, 0xa1, 0x2c, 0xa1, + 0x1c, 0x16, 0x02, 0x25, 0xd1, 0x13, 0x64, 0xa9, 0x41, 0xca, 0x1f, 0x5e, 0x82, 0x6d, 0xaf, 0x46, + 0xd8, 0x99, 0x19, 0x7a, 0x0b, 0x56, 0xfd, 0xde, 0xfb, 0xc5, 0xe3, 0x8c, 0xcb, 0xf9, 0x0a, 0x51, + 0xbd, 0x10, 0xad, 0x54, 0x3e, 0x80, 0x12, 0x2c, 0xaf, 0x19, 0xad, 0x9a, 0x6c, 0x12, 0x5d, 0x85, + 0x94, 0x6b, 0x0c, 0x4c, 0x8d, 0x4c, 0x1c, 0x2c, 0x98, 0x37, 0x13, 0x50, 0x2d, 0x9e, 0x12, 0x6c, + 0xb2, 0x47, 0xce, 0x99, 0x36, 0x13, 0xa0, 0x2d, 0xf8, 0x9f, 0x7f, 0x50, 0x67, 0x5e, 0x38, 0xcb, + 0x90, 0xaf, 0xea, 0x78, 0x9a, 0xf2, 0x0f, 0x12, 0xc4, 0xf9, 0xc3, 0x08, 0xb4, 0x41, 0x3a, 0xbb, + 0x0d, 0x91, 0x45, 0x6d, 0x88, 0x9e, 0xbf, 0x0d, 0x55, 0x00, 0x3f, 0x4c, 0x37, 0x2f, 0x97, 0xa2, + 0x9b, 0xe9, 0xed, 0x2b, 0xf3, 0x8e, 0x78, 0x88, 0x1d, 0x63, 0x20, 0xde, 0x7d, 0xc0, 0xa8, 0xfc, + 0x8b, 0x04, 0x29, 0x5f, 0x8f, 0xaa, 0x90, 0xf5, 0xe2, 0x52, 0x1f, 0x8d, 0xb4, 0x81, 0xa0, 0xe2, + 0xfa, 0xc2, 0xe0, 0x6e, 0x8f, 0xb4, 0x81, 0x92, 0x16, 0xf1, 0xd0, 0xc3, 0xd9, 0x6d, 0x8d, 0x2c, + 0x68, 0x6b, 0x88, 0x47, 0xd1, 0xf3, 0xf1, 0x28, 0xd4, 0x71, 0xf9, 0x54, 0xc7, 0xcb, 0xbf, 0x4b, + 0xb0, 0xdc, 0x98, 0xb2, 0xf0, 0xf5, 0x7f, 0xb3, 0x55, 0x0f, 0x05, 0xb7, 0x74, 0xac, 0xab, 0x73, + 0x3d, 0xbb, 0x36, 0xef, 0x31, 0x1c, 0xf3, 0xac, 0x77, 0xc8, 0xf3, 0xd2, 0x99, 0xf5, 0xf0, 0xbb, + 0x08, 0xac, 0xce, 0xe1, 0xff, 0x7b, 0xbd, 0x0c, 0xbf, 0xde, 0xd8, 0x2b, 0xbe, 0xde, 0xf8, 0xc2, + 0xd7, 0xfb, 0x6d, 0x04, 0x92, 0x6d, 0x36, 0xa5, 0xb5, 0xd1, 0x3f, 0x31, 0x7b, 0xaf, 0x40, 0xca, + 0xb6, 0x46, 0x2a, 0xd7, 0xc8, 0x4c, 0x93, 0xb4, 0xad, 0x91, 0x32, 0x47, 0xb3, 0xd8, 0x6b, 0x1a, + 0xcc, 0xf1, 0xd7, 0xd0, 0x84, 0xc4, 0xe9, 0x07, 0xe5, 0x40, 0x86, 0x97, 0x42, 0x6c, 0x4d, 0x37, + 0x69, 0x0d, 0xd8, 0x1a, 0x26, 0xcd, 0x6f, 0x79, 0x3c, 0x6c, 0x8e, 0x54, 0x04, 0x8e, 0x5a, 0xf0, + 0x25, 0x43, 0x2c, 0x6e, 0xf9, 0x45, 0x13, 0x4b, 0x11, 0xb8, 0xf2, 0x97, 0x12, 0xc0, 0x2e, 0xad, + 0x2c, 0xcb, 0x97, 0xee, 0x3b, 0x2e, 0x0b, 0x41, 0x0d, 0xdd, 0x5c, 0x5c, 0xd4, 0x34, 0x71, 0x7f, + 0xc6, 0x0d, 0xc6, 0x5d, 0x87, 0xec, 0x8c, 0xdb, 0x2e, 0xf6, 0x82, 0x39, 0xc3, 0x89, 0xbf, 0x86, + 0x74, 0x30, 0x51, 0x32, 0x07, 0x81, 0x53, 0xf9, 0x47, 0x09, 0x52, 0x2c, 0xa6, 0x3d, 0x4c, 0xb4, + 0x50, 0x0f, 0xa5, 0xf3, 0xf7, 0x70, 0x1d, 0x80, 0xbb, 0x71, 0x8d, 0x27, 0x58, 0x30, 0x2b, 0xc5, + 0x24, 0x1d, 0xe3, 0x09, 0x46, 0xef, 0xf9, 0x05, 0x8f, 0xfe, 0x75, 0xc1, 0xc5, 0xc4, 0xf0, 0xca, + 0x7e, 0x09, 0x12, 0xe6, 0x64, 0xac, 0xd2, 0xe5, 0x43, 0xe6, 0x6c, 0x35, 0x27, 0xe3, 0xee, 0xd4, + 0x2d, 0x7f, 0x06, 0x89, 0xee, 0x94, 0x2d, 0xe2, 0x94, 0xa2, 0x8e, 0x65, 0x89, 0xed, 0x8f, 0x6f, + 0xdd, 0x49, 0x2a, 0x60, 0xcb, 0x0e, 0x02, 0x99, 0xae, 0x79, 0xde, 0xcf, 0x02, 0xfa, 0x8d, 0x2a, + 0xaf, 0xb8, 0xe2, 0x8b, 0xe5, 0xfe, 0xc6, 0x4f, 0x12, 0xa4, 0x03, 0xe3, 0x06, 0xbd, 0x03, 0x17, + 0x6a, 0xbb, 0xfb, 0xf5, 0x7b, 0x6a, 0x73, 0x47, 0xbd, 0xbd, 0x5b, 0xbd, 0xa3, 0xde, 0x6f, 0xdd, + 0x6b, 0xed, 0x7f, 0xd2, 0xca, 0x2d, 0x15, 0x2e, 0x1e, 0x1d, 0x97, 0x50, 0x00, 0x7b, 0xdf, 0x7c, + 0x6c, 0x5a, 0x9f, 0xd3, 0x77, 0xbe, 0x16, 0x36, 0xa9, 0xd6, 0x3a, 0x8d, 0x56, 0x37, 0x27, 0x15, + 0x2e, 0x1c, 0x1d, 0x97, 0x56, 0x03, 0x16, 0xd5, 0x9e, 0x8b, 0x4d, 0x32, 0x6f, 0x50, 0xdf, 0xdf, + 0xdb, 0x6b, 0x76, 0x73, 0x91, 0x39, 0x03, 0xf1, 0x0f, 0xe2, 0x3a, 0xac, 0x86, 0x0d, 0x5a, 0xcd, + 0xdd, 0x5c, 0xb4, 0x80, 0x8e, 0x8e, 0x4b, 0xcb, 0x01, 0x74, 0xcb, 0x18, 0x15, 0x92, 0x5f, 0x7c, + 0x55, 0x5c, 0xfa, 0xe6, 0xeb, 0xa2, 0x44, 0x33, 0xcb, 0x86, 0x66, 0x04, 0x7a, 0x1b, 0x2e, 0x75, + 0x9a, 0x77, 0x5a, 0x8d, 0x1d, 0x75, 0xaf, 0x73, 0x47, 0xed, 0x7e, 0xda, 0x6e, 0x04, 0xb2, 0x5b, + 0x39, 0x3a, 0x2e, 0xa5, 0x45, 0x4a, 0x8b, 0xd0, 0x6d, 0xa5, 0xf1, 0x60, 0xbf, 0xdb, 0xc8, 0x49, + 0x1c, 0xdd, 0x76, 0xf0, 0x81, 0x45, 0x30, 0x43, 0xdf, 0x84, 0xcb, 0x67, 0xa0, 0xfd, 0xc4, 0x56, + 0x8f, 0x8e, 0x4b, 0xd9, 0xb6, 0x83, 0xf9, 0xfb, 0x61, 0x16, 0x15, 0xc8, 0xcf, 0x5b, 0xec, 0xb7, + 0xf7, 0x3b, 0xd5, 0xdd, 0x5c, 0xa9, 0x90, 0x3b, 0x3a, 0x2e, 0x65, 0xbc, 0x61, 0x48, 0xf1, 0xb3, + 0xcc, 0x6a, 0x1f, 0x3f, 0x3b, 0x29, 0x4a, 0xcf, 0x4f, 0x8a, 0xd2, 0x6f, 0x27, 0x45, 0xe9, 0xe9, + 0xcb, 0xe2, 0xd2, 0xf3, 0x97, 0xc5, 0xa5, 0x9f, 0x5f, 0x16, 0x97, 0x1e, 0xbe, 0x3f, 0x30, 0xc8, + 0x70, 0xd2, 0xab, 0xf4, 0xad, 0xf1, 0x56, 0xf0, 0xc7, 0xe7, 0xec, 0x93, 0xff, 0x08, 0x3e, 0xfd, + 0xc3, 0xb4, 0x17, 0x67, 0xf2, 0x5b, 0x7f, 0x06, 0x00, 0x00, 0xff, 0xff, 0x46, 0xcf, 0x37, 0x28, + 0x59, 0x0f, 0x00, 0x00, } func (m *PartSetHeader) Marshal() (dAtA []byte, err error) { @@ -1596,6 +1618,20 @@ func (m *Vote) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.ExtensionSignature) > 0 { + i -= len(m.ExtensionSignature) + copy(dAtA[i:], m.ExtensionSignature) + i = encodeVarintTypes(dAtA, i, uint64(len(m.ExtensionSignature))) + i-- + dAtA[i] = 0x52 + } + if len(m.Extension) > 0 { + i -= len(m.Extension) + copy(dAtA[i:], m.Extension) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Extension))) + i-- + dAtA[i] = 0x4a + } if len(m.Signature) > 0 { i -= len(m.Signature) copy(dAtA[i:], m.Signature) @@ -2306,6 +2342,14 @@ func (m *Vote) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } + l = len(m.Extension) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + l = len(m.ExtensionSignature) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } return n } @@ -3699,6 +3743,74 @@ func (m *Vote) Unmarshal(dAtA []byte) error { m.Signature = []byte{} } iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Extension", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Extension = append(m.Extension[:0], dAtA[iNdEx:postIndex]...) + if m.Extension == nil { + m.Extension = []byte{} + } + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExtensionSignature", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExtensionSignature = append(m.ExtensionSignature[:0], dAtA[iNdEx:postIndex]...) + if m.ExtensionSignature == nil { + m.ExtensionSignature = []byte{} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) diff --git a/proto/tendermint/types/types.proto b/proto/tendermint/types/types.proto index 1986d0a6c..cb0903ef2 100644 --- a/proto/tendermint/types/types.proto +++ b/proto/tendermint/types/types.proto @@ -89,7 +89,7 @@ message Data { repeated bytes txs = 1; } -// Vote represents a prevote, precommit, or commit vote from validators for +// Vote represents a prevote or precommit vote from validators for // consensus. message Vote { SignedMsgType type = 1; @@ -101,7 +101,14 @@ message Vote { [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; bytes validator_address = 6; int32 validator_index = 7; - bytes signature = 8; + // signature of + bytes signature = 8; + // Vote extension provided by the application. Only valid for precommit + // messages. + bytes extension = 9; + // separate signature over the extension bytes. This enables backwards compatibility. + // Only valid for precommit messages. + bytes extension_signature = 10; } // Commit contains the evidence that a block was committed by a set of validators. diff --git a/proxy/mocks/app_conn_consensus.go b/proxy/mocks/app_conn_consensus.go index 6c1abcef6..3b30c3cdd 100644 --- a/proxy/mocks/app_conn_consensus.go +++ b/proxy/mocks/app_conn_consensus.go @@ -52,6 +52,29 @@ func (_m *AppConnConsensus) Error() error { return r0 } +// ExtendVote provides a mock function with given fields: _a0, _a1 +func (_m *AppConnConsensus) ExtendVote(_a0 context.Context, _a1 *types.RequestExtendVote) (*types.ResponseExtendVote, error) { + ret := _m.Called(_a0, _a1) + + var r0 *types.ResponseExtendVote + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestExtendVote) *types.ResponseExtendVote); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseExtendVote) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestExtendVote) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // FinalizeBlock provides a mock function with given fields: _a0, _a1 func (_m *AppConnConsensus) FinalizeBlock(_a0 context.Context, _a1 *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { ret := _m.Called(_a0, _a1) @@ -144,6 +167,29 @@ func (_m *AppConnConsensus) ProcessProposal(_a0 context.Context, _a1 *types.Requ return r0, r1 } +// VerifyVoteExtension provides a mock function with given fields: _a0, _a1 +func (_m *AppConnConsensus) VerifyVoteExtension(_a0 context.Context, _a1 *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { + ret := _m.Called(_a0, _a1) + + var r0 *types.ResponseVerifyVoteExtension + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestVerifyVoteExtension) *types.ResponseVerifyVoteExtension); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseVerifyVoteExtension) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestVerifyVoteExtension) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + type mockConstructorTestingTNewAppConnConsensus interface { mock.TestingT Cleanup(func()) diff --git a/state/execution.go b/state/execution.go index 7db82dc31..415e87fce 100644 --- a/state/execution.go +++ b/state/execution.go @@ -1,7 +1,9 @@ package state import ( + "bytes" "context" + "errors" "fmt" "time" @@ -94,7 +96,7 @@ func (blockExec *BlockExecutor) SetEventBus(eventBus types.BlockEventPublisher) func (blockExec *BlockExecutor) CreateProposalBlock( height int64, state State, - commit *types.Commit, + lastExtCommit *types.ExtendedCommit, proposerAddr []byte, votes []*types.Vote, ) (*types.Block, error) { @@ -108,14 +110,14 @@ func (blockExec *BlockExecutor) CreateProposalBlock( maxDataBytes := types.MaxDataBytes(maxBytes, evSize, state.Validators.Size()) txs := blockExec.mempool.ReapMaxBytesMaxGas(maxDataBytes, maxGas) + commit := lastExtCommit.ToCommit() block := state.MakeBlock(height, txs, commit, evidence, proposerAddr) - localLastCommit := buildLastCommitInfo(block, blockExec.store, state.InitialHeight) rpp, err := blockExec.proxyApp.PrepareProposal(context.TODO(), &abci.RequestPrepareProposal{ MaxTxBytes: maxDataBytes, Txs: block.Txs.ToSliceOfBytes(), - LocalLastCommit: extendedCommitInfo(localLastCommit, votes), + LocalLastCommit: buildExtendedCommitInfo(lastExtCommit, blockExec.store, state.InitialHeight, state.ConsensusParams.ABCI), Misbehavior: block.Evidence.Evidence.ToABCI(), Height: block.Height, Time: block.Time, @@ -309,6 +311,35 @@ func (blockExec *BlockExecutor) Commit( return res.RetainHeight, err } +func (blockExec *BlockExecutor) ExtendVote(ctx context.Context, vote *types.Vote) ([]byte, error) { + resp, err := blockExec.proxyApp.ExtendVote(ctx, &abci.RequestExtendVote{ + BlockHash: vote.BlockID.Hash, + Height: vote.Height, + }) + if err != nil { + panic(fmt.Errorf("ExtendVote call failed: %w", err)) + } + return resp.VoteExtension, nil +} + +func (blockExec *BlockExecutor) VerifyVoteExtension(ctx context.Context, vote *types.Vote) error { + resp, err := blockExec.proxyApp.VerifyVoteExtension(ctx, &abci.RequestVerifyVoteExtension{ + BlockHash: vote.BlockID.Hash, + ValidatorAddress: vote.ValidatorAddress, + Height: vote.Height, + VoteExtension: vote.Extension, + }) + if err != nil { + panic(fmt.Errorf("VerifyVoteExtension call failed: %w", err)) + } + + if !resp.IsOK() { + return errors.New("invalid vote extension") + } + + return nil +} + //--------------------------------------------------------- // Helper functions for executing blocks and updating state @@ -387,21 +418,75 @@ func buildLastCommitInfo(block *types.Block, store Store, initialHeight int64) a } } -func extendedCommitInfo(c abci.CommitInfo, votes []*types.Vote) abci.ExtendedCommitInfo { - vs := make([]abci.ExtendedVoteInfo, len(c.Votes)) - for i := range vs { - vs[i] = abci.ExtendedVoteInfo{ - Validator: c.Votes[i].Validator, - SignedLastBlock: c.Votes[i].SignedLastBlock, - /* - TODO: Include vote extensions information when implementing vote extensions. - VoteExtension: []byte{}, - */ +// buildExtendedCommitInfo populates an ABCI extended commit from the +// corresponding Tendermint extended commit ec, using the stored validator set +// from ec. It requires ec to include the original precommit votes along with +// the vote extensions from the last commit. +// +// For heights below the initial height, for which we do not have the required +// data, it returns an empty record. +// +// Assumes that the commit signatures are sorted according to validator index. +func buildExtendedCommitInfo(ec *types.ExtendedCommit, store Store, initialHeight int64, ap types.ABCIParams) abci.ExtendedCommitInfo { + if ec.Height < initialHeight { + // There are no extended commits for heights below the initial height. + return abci.ExtendedCommitInfo{} + } + + valSet, err := store.LoadValidators(ec.Height) + if err != nil { + panic(fmt.Errorf("failed to load validator set at height %d, initial height %d: %w", ec.Height, initialHeight, err)) + } + + var ( + ecSize = ec.Size() + valSetLen = len(valSet.Validators) + ) + + // Ensure that the size of the validator set in the extended commit matches + // the size of the validator set in the state store. + if ecSize != valSetLen { + panic(fmt.Errorf( + "extended commit size (%d) does not match validator set length (%d) at height %d\n\n%v\n\n%v", + ecSize, valSetLen, ec.Height, ec.ExtendedSignatures, valSet.Validators, + )) + } + + votes := make([]abci.ExtendedVoteInfo, ecSize) + for i, val := range valSet.Validators { + ecs := ec.ExtendedSignatures[i] + + // Absent signatures have empty validator addresses, but otherwise we + // expect the validator addresses to be the same. + if ecs.BlockIDFlag != types.BlockIDFlagAbsent && !bytes.Equal(ecs.ValidatorAddress, val.Address) { + panic(fmt.Errorf("validator address of extended commit signature in position %d (%s) does not match the corresponding validator's at height %d (%s)", + i, ecs.ValidatorAddress, ec.Height, val.Address, + )) + } + + var ext []byte + // Check if vote extensions were enabled during the commit's height: ec.Height. + // ec is the commit from the previous height, so if extensions were enabled + // during that height, we ensure they are present and deliver the data to + // the proposer. If they were not enabled during this previous height, we + // will not deliver extension data. + if ap.VoteExtensionsEnabled(ec.Height) && ecs.BlockIDFlag == types.BlockIDFlagCommit { + if err := ecs.EnsureExtension(); err != nil { + panic(fmt.Errorf("commit at height %d received with missing vote extensions data", ec.Height)) + } + ext = ecs.Extension + } + + votes[i] = abci.ExtendedVoteInfo{ + Validator: types.TM2PB.Validator(val), + SignedLastBlock: ecs.BlockIDFlag != types.BlockIDFlagAbsent, + VoteExtension: ext, } } + return abci.ExtendedCommitInfo{ - Round: c.Round, - Votes: vs, + Round: ec.Round, + Votes: votes, } } diff --git a/state/execution_test.go b/state/execution_test.go index 949d98601..3991cbdbd 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -622,8 +622,7 @@ func TestEmptyPrepareProposal(t *testing.T) { sm.EmptyEvidencePool{}, ) pa, _ := state.Validators.GetByIndex(0) - commit, err := makeValidCommit(height, types.BlockID{}, state.Validators, privVals) - require.NoError(t, err) + commit, _ := makeValidExtendedCommit(t, height, types.BlockID{}, state.Validators, privVals) _, err = blockExec.CreateProposalBlock(height, state, commit, pa, nil) require.NoError(t, err) } @@ -663,8 +662,7 @@ func TestPrepareProposalTxsAllIncluded(t *testing.T) { evpool, ) pa, _ := state.Validators.GetByIndex(0) - commit, err := makeValidCommit(height, types.BlockID{}, state.Validators, privVals) - require.NoError(t, err) + commit, _ := makeValidExtendedCommit(t, height, types.BlockID{}, state.Validators, privVals) block, err := blockExec.CreateProposalBlock(height, state, commit, pa, nil) require.NoError(t, err) @@ -714,8 +712,7 @@ func TestPrepareProposalReorderTxs(t *testing.T) { evpool, ) pa, _ := state.Validators.GetByIndex(0) - commit, err := makeValidCommit(height, types.BlockID{}, state.Validators, privVals) - require.NoError(t, err) + commit, _ := makeValidExtendedCommit(t, height, types.BlockID{}, state.Validators, privVals) block, err := blockExec.CreateProposalBlock(height, state, commit, pa, nil) require.NoError(t, err) for i, tx := range block.Data.Txs { @@ -767,8 +764,7 @@ func TestPrepareProposalErrorOnTooManyTxs(t *testing.T) { evpool, ) pa, _ := state.Validators.GetByIndex(0) - commit, err := makeValidCommit(height, types.BlockID{}, state.Validators, privVals) - require.NoError(t, err) + commit, _ := makeValidExtendedCommit(t, height, types.BlockID{}, state.Validators, privVals) block, err := blockExec.CreateProposalBlock(height, state, commit, pa, nil) require.Nil(t, block) @@ -815,8 +811,7 @@ func TestPrepareProposalErrorOnPrepareProposalError(t *testing.T) { evpool, ) pa, _ := state.Validators.GetByIndex(0) - commit, err := makeValidCommit(height, types.BlockID{}, state.Validators, privVals) - require.NoError(t, err) + commit, _ := makeValidExtendedCommit(t, height, types.BlockID{}, state.Validators, privVals) block, err := blockExec.CreateProposalBlock(height, state, commit, pa, nil) require.Nil(t, block) diff --git a/state/helpers_test.go b/state/helpers_test.go index 0ae279327..d5ad0879a 100644 --- a/state/helpers_test.go +++ b/state/helpers_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" dbm "github.com/tendermint/tm-db" abci "github.com/tendermint/tendermint/abci/types" @@ -32,25 +33,25 @@ func newTestApp() proxy.AppConns { } func makeAndCommitGoodBlock( + t *testing.T, state sm.State, height int64, lastCommit *types.Commit, proposerAddr []byte, blockExec *sm.BlockExecutor, privVals map[string]types.PrivValidator, - evidence []types.Evidence) (sm.State, types.BlockID, *types.Commit, error) { + evidence []types.Evidence) (sm.State, types.BlockID, *types.ExtendedCommit) { + t.Helper() + // A good block passes state, blockID, err := makeAndApplyGoodBlock(state, height, lastCommit, proposerAddr, blockExec, evidence) - if err != nil { - return state, types.BlockID{}, nil, err - } + require.NoError(t, err) // Simulate a lastCommit for this block from all validators for the next height - commit, err := makeValidCommit(height, blockID, state.Validators, privVals) - if err != nil { - return state, types.BlockID{}, nil, err - } - return state, blockID, commit, nil + extCommit, _ := makeValidExtendedCommit(t, height, blockID, state.Validators, privVals) + require.NoError(t, err) + + return state, blockID, extCommit } func makeAndApplyGoodBlock(state sm.State, height int64, lastCommit *types.Commit, proposerAddr []byte, @@ -83,22 +84,29 @@ func makeBlock(state sm.State, height int64, c *types.Commit) *types.Block { ) } -func makeValidCommit( +func makeValidExtendedCommit( + t *testing.T, height int64, blockID types.BlockID, vals *types.ValidatorSet, privVals map[string]types.PrivValidator, -) (*types.Commit, error) { - sigs := make([]types.CommitSig, 0) +) (*types.ExtendedCommit, []*types.Vote) { + t.Helper() + sigs := make([]types.ExtendedCommitSig, vals.Size()) + votes := make([]*types.Vote, vals.Size()) for i := 0; i < vals.Size(); i++ { _, val := vals.GetByIndex(int32(i)) - vote, err := types.MakeVote(height, blockID, vals, privVals[val.Address.String()], chainID, time.Now()) - if err != nil { - return nil, err - } - sigs = append(sigs, vote.CommitSig()) + vote, err := test.MakeVote(privVals[val.Address.String()], chainID, int32(i), height, 0, 2, blockID, time.Now()) + require.NoError(t, err) + sigs[i] = vote.ExtendedCommitSig() + votes[i] = vote } - return types.NewCommit(height, 0, blockID, sigs), nil + + return &types.ExtendedCommit{ + Height: height, + BlockID: blockID, + ExtendedSignatures: sigs, + }, votes } func makeState(nVals, height int) (sm.State, dbm.DB, map[string]types.PrivValidator) { diff --git a/state/mocks/block_store.go b/state/mocks/block_store.go index f93f45447..440480087 100644 --- a/state/mocks/block_store.go +++ b/state/mocks/block_store.go @@ -195,6 +195,11 @@ func (_m *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, s _m.Called(block, blockParts, seenCommit) } +// SaveBlockWithExtendedCommit provides a mock function with given fields: block, blockParts, seenExtendedCommit +func (_m *BlockStore) SaveBlockWithExtendedCommit(block *types.Block, blockParts *types.PartSet, seenExtendedCommit *types.ExtendedCommit) { + _m.Called(block, blockParts, seenExtendedCommit) +} + // Size provides a mock function with given fields: func (_m *BlockStore) Size() int64 { ret := _m.Called() diff --git a/state/services.go b/state/services.go index 6e24af036..7c9020d9d 100644 --- a/state/services.go +++ b/state/services.go @@ -25,6 +25,7 @@ type BlockStore interface { LoadBlock(height int64) *types.Block SaveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) + SaveBlockWithExtendedCommit(block *types.Block, blockParts *types.PartSet, seenExtendedCommit *types.ExtendedCommit) PruneBlocks(height int64) (uint64, error) diff --git a/state/validation_test.go b/state/validation_test.go index 705b13997..65977630a 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -52,6 +52,7 @@ func TestValidateBlockHeader(t *testing.T) { sm.EmptyEvidencePool{}, ) lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil) + var lastExtCommit *types.ExtendedCommit // some bad values wrongHash := tmhash.Sum([]byte("this hash is wrong")) @@ -101,11 +102,17 @@ func TestValidateBlockHeader(t *testing.T) { /* A good block passes */ - var err error - state, _, lastCommit, err = makeAndCommitGoodBlock( + state, _, lastExtCommit = makeAndCommitGoodBlock(t, state, height, lastCommit, state.Validators.GetProposer().Address, blockExec, privVals, nil) - require.NoError(t, err, "height %d", height) + lastCommit = lastExtCommit.ToCommit() } + + nextHeight := validationTestsStopHeight + block := makeBlock(state, nextHeight, lastCommit) + state.InitialHeight = nextHeight + 1 + err := blockExec.ValidateBlock(state, block) + require.Error(t, err, "expected an error when state is ahead of block") + assert.Contains(t, err.Error(), "lower than initial height") } func TestValidateBlockCommit(t *testing.T) { @@ -137,6 +144,7 @@ func TestValidateBlockCommit(t *testing.T) { sm.EmptyEvidencePool{}, ) lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil) + var lastExtCommit *types.ExtendedCommit wrongSigsCommit := types.NewCommit(1, 0, types.BlockID{}, nil) badPrivVal := types.NewMockPV() @@ -188,7 +196,8 @@ func TestValidateBlockCommit(t *testing.T) { */ var err error var blockID types.BlockID - state, blockID, lastCommit, err = makeAndCommitGoodBlock( + state, blockID, lastExtCommit = makeAndCommitGoodBlock( + t, state, height, lastCommit, @@ -197,7 +206,7 @@ func TestValidateBlockCommit(t *testing.T) { privVals, nil, ) - require.NoError(t, err, "height %d", height) + lastCommit = lastExtCommit.ToCommit() /* wrongSigsCommit is fine except for the extra bad precommit @@ -276,6 +285,7 @@ func TestValidateBlockEvidence(t *testing.T) { evpool, ) lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil) + var lastExtCommit *types.ExtendedCommit for height := int64(1); height < validationTestsStopHeight; height++ { proposerAddr := state.Validators.GetProposer().Address @@ -320,8 +330,8 @@ func TestValidateBlockEvidence(t *testing.T) { evidence = append(evidence, newEv) } - var err error - state, _, lastCommit, err = makeAndCommitGoodBlock( + state, _, lastExtCommit = makeAndCommitGoodBlock( + t, state, height, lastCommit, @@ -330,6 +340,6 @@ func TestValidateBlockEvidence(t *testing.T) { privVals, evidence, ) - require.NoError(t, err, "height %d", height) + lastCommit = lastExtCommit.ToCommit() } } diff --git a/store/store.go b/store/store.go index 866965ac6..568b1e140 100644 --- a/store/store.go +++ b/store/store.go @@ -1,6 +1,7 @@ package store import ( + "errors" "fmt" "strconv" @@ -240,6 +241,29 @@ func (bs *BlockStore) LoadBlockCommit(height int64) *types.Commit { return commit } +// LoadExtendedCommit returns the ExtendedCommit for the given height. +// The extended commit is not guaranteed to contain the same +2/3 precommits data +// as the commit in the block. +func (bs *BlockStore) LoadBlockExtendedCommit(height int64) *types.ExtendedCommit { + pbec := new(tmproto.ExtendedCommit) + bz, err := bs.db.Get(calcExtCommitKey(height)) + if err != nil { + panic(fmt.Errorf("fetching extended commit: %w", err)) + } + if len(bz) == 0 { + return nil + } + err = proto.Unmarshal(bz, pbec) + if err != nil { + panic(fmt.Errorf("decoding extended commit: %w", err)) + } + extCommit, err := types.ExtendedCommitFromProto(pbec) + if err != nil { + panic(fmt.Errorf("converting extended commit: %w", err)) + } + return extCommit +} + // LoadSeenCommit returns the locally seen Commit for the given height. // 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. @@ -353,15 +377,69 @@ func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, s if block == nil { panic("BlockStore can only save a non-nil block") } + batch := bs.db.NewBatch() + if err := bs.saveBlockToBatch(batch, block, blockParts, seenCommit); err != nil { + panic(err) + } + + if err := batch.WriteSync(); err != nil { + panic(err) + } + + if err := batch.Close(); err != nil { + panic(err) + } +} + +// SaveBlockWithExtendedCommit persists the given block, blockParts, and +// seenExtendedCommit to the underlying db. seenExtendedCommit is stored under +// two keys in the database: as the seenCommit and as the ExtendedCommit data for the +// height. This allows the vote extension data to be persisted for all blocks +// that are saved. +func (bs *BlockStore) SaveBlockWithExtendedCommit(block *types.Block, blockParts *types.PartSet, seenExtendedCommit *types.ExtendedCommit) { + if block == nil { + panic("BlockStore can only save a non-nil block") + } + if err := seenExtendedCommit.EnsureExtensions(); err != nil { + panic(fmt.Errorf("saving block with extensions: %w", err)) + } + batch := bs.db.NewBatch() + if err := bs.saveBlockToBatch(batch, block, blockParts, seenExtendedCommit.ToCommit()); err != nil { + panic(err) + } + height := block.Height + + pbec := seenExtendedCommit.ToProto() + extCommitBytes := mustEncode(pbec) + if err := batch.Set(calcExtCommitKey(height), extCommitBytes); err != nil { + panic(err) + } + + if err := batch.WriteSync(); err != nil { + panic(err) + } + + if err := batch.Close(); err != nil { + panic(err) + } +} + +func (bs *BlockStore) saveBlockToBatch(batch dbm.Batch, block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) error { + if block == nil { + panic("BlockStore can only save a non-nil block") + } height := block.Height hash := block.Hash() if g, w := height, bs.Height()+1; bs.Base() > 0 && g != w { - panic(fmt.Sprintf("BlockStore can only save contiguous blocks. Wanted %v, got %v", w, g)) + return fmt.Errorf("BlockStore can only save contiguous blocks. Wanted %v, got %v", w, g) } if !blockParts.IsComplete() { - panic("BlockStore can only save complete block part sets") + return errors.New("BlockStore can only save complete block part sets") + } + if height != seenCommit.Height { + return fmt.Errorf("BlockStore cannot save seen commit of a different height (block: %d, commit: %d)", height, seenCommit.Height) } // Save block parts. This must be done before the block meta, since callers @@ -370,57 +448,47 @@ func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, s // complete as soon as the block meta is written. for i := 0; i < int(blockParts.Total()); i++ { part := blockParts.GetPart(i) - bs.saveBlockPart(height, i, part) + bs.saveBlockPart(height, i, part, batch) } - // Save block meta blockMeta := types.NewBlockMeta(block, blockParts) pbm := blockMeta.ToProto() if pbm == nil { - panic("nil blockmeta") - } - metaBytes := mustEncode(pbm) - if err := bs.db.Set(calcBlockMetaKey(height), metaBytes); err != nil { - panic(err) - } - if err := bs.db.Set(calcBlockHashKey(hash), []byte(fmt.Sprintf("%d", height))); err != nil { - panic(err) + return errors.New("nil blockmeta") + } + + metaBytes := mustEncode(pbm) + if err := batch.Set(calcBlockMetaKey(height), metaBytes); err != nil { + return err + } + + if err := batch.Set(calcBlockHashKey(hash), []byte(fmt.Sprintf("%d", height))); err != nil { + return err } - // Save block commit (duplicate and separate from the Block) pbc := block.LastCommit.ToProto() blockCommitBytes := mustEncode(pbc) - if err := bs.db.Set(calcBlockCommitKey(height-1), blockCommitBytes); err != nil { - panic(err) + if err := batch.Set(calcBlockCommitKey(height-1), blockCommitBytes); err != nil { + return err } // Save seen commit (seen +2/3 precommits for block) - // NOTE: we can delete this at a later height pbsc := seenCommit.ToProto() seenCommitBytes := mustEncode(pbsc) - if err := bs.db.Set(calcSeenCommitKey(height), seenCommitBytes); err != nil { - panic(err) + if err := batch.Set(calcSeenCommitKey(height), seenCommitBytes); err != nil { + return err } - // Done! - bs.mtx.Lock() - bs.height = height - if bs.base == 0 { - bs.base = height - } - bs.mtx.Unlock() - - // Save new BlockStoreState descriptor. This also flushes the database. - bs.saveState() + return nil } -func (bs *BlockStore) saveBlockPart(height int64, index int, part *types.Part) { +func (bs *BlockStore) saveBlockPart(height int64, index int, part *types.Part, batch dbm.Batch) { pbp, err := part.ToProto() if err != nil { panic(fmt.Errorf("unable to make part into proto: %w", err)) } partBytes := mustEncode(pbp) - if err := bs.db.Set(calcBlockPartKey(height, index), partBytes); err != nil { + if err := batch.Set(calcBlockPartKey(height, index), partBytes); err != nil { panic(err) } } @@ -471,6 +539,10 @@ func calcBlockHashKey(hash []byte) []byte { return []byte(fmt.Sprintf("BH:%x", hash)) } +func calcExtCommitKey(height int64) []byte { + return []byte(fmt.Sprintf("EC:%v", height)) +} + //----------------------------------------------------------------------------- var blockStoreKey = []byte("blockStore") diff --git a/store/store_test.go b/store/store_test.go index e77d9ead8..57b34d63c 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -1,9 +1,7 @@ package store import ( - "bytes" "fmt" - stdlog "log" "os" "runtime/debug" "strings" @@ -18,7 +16,6 @@ import ( cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/internal/test" - "github.com/tendermint/tendermint/libs/log" tmrand "github.com/tendermint/tendermint/libs/rand" tmstore "github.com/tendermint/tendermint/proto/tendermint/store" tmversion "github.com/tendermint/tendermint/proto/tendermint/version" @@ -32,22 +29,32 @@ import ( // test. type cleanupFunc func() -// make a Commit with a single vote containing just the height and a timestamp -func makeTestCommit(height int64, timestamp time.Time) *types.Commit { - commitSigs := []types.CommitSig{{ - BlockIDFlag: types.BlockIDFlagCommit, - ValidatorAddress: tmrand.Bytes(crypto.AddressSize), - Timestamp: timestamp, - Signature: []byte("Signature"), +// make an extended commit with a single vote containing just the height and a +// timestamp +func makeTestExtCommit(height int64, timestamp time.Time) *types.ExtendedCommit { + extCommitSigs := []types.ExtendedCommitSig{{ + CommitSig: types.CommitSig{ + BlockIDFlag: types.BlockIDFlagCommit, + ValidatorAddress: tmrand.Bytes(crypto.AddressSize), + Timestamp: timestamp, + Signature: []byte("Signature"), + }, + ExtensionSignature: []byte("ExtensionSignature"), }} - return types.NewCommit(height, 0, - types.BlockID{Hash: []byte(""), PartSetHeader: types.PartSetHeader{Hash: []byte(""), Total: 2}}, commitSigs) + return &types.ExtendedCommit{ + Height: height, + BlockID: types.BlockID{ + Hash: crypto.CRandBytes(32), + PartSetHeader: types.PartSetHeader{Hash: crypto.CRandBytes(32), Total: 2}, + }, + ExtendedSignatures: extCommitSigs, + } } -func makeStateAndBlockStore(logger log.Logger) (sm.State, *BlockStore, cleanupFunc) { +func makeStateAndBlockStore(t *testing.T) (sm.State, *BlockStore) { config := cfg.ResetTestRoot("blockchain_reactor_test") - // blockDB := dbm.NewDebugDB("blockDB", dbm.NewMemDB()) - // stateDB := dbm.NewDebugDB("stateDB", dbm.NewMemDB()) + t.Cleanup(func() { os.RemoveAll(config.RootDir) }) + blockDB := dbm.NewMemDB() stateDB := dbm.NewMemDB() stateStore := sm.NewStore(stateDB, sm.StoreOptions{ @@ -57,7 +64,7 @@ func makeStateAndBlockStore(logger log.Logger) (sm.State, *BlockStore, cleanupFu if err != nil { panic(fmt.Errorf("error constructing state from genesis file: %w", err)) } - return state, NewBlockStore(blockDB), func() { os.RemoveAll(config.RootDir) } + return state, NewBlockStore(blockDB) } func TestLoadBlockStoreState(t *testing.T) { @@ -134,29 +141,28 @@ var ( seenCommit1 *types.Commit ) -func TestMain(m *testing.M) { - var cleanup cleanupFunc - var err error - state, _, cleanup = makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) - block = state.MakeBlock(state.LastBlockHeight+1, test.MakeNTxs(state.LastBlockHeight+1, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) +// func TestMain(m *testing.M) { +// var cleanup cleanupFunc +// var err error +// state, _, cleanup = makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) +// block = state.MakeBlock(state.LastBlockHeight+1, test.MakeNTxs(state.LastBlockHeight+1, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) - partSet, err = block.MakePartSet(2) - if err != nil { - stdlog.Fatal(err) - } - part1 = partSet.GetPart(0) - part2 = partSet.GetPart(1) - seenCommit1 = makeTestCommit(10, tmtime.Now()) - code := m.Run() - cleanup() - os.Exit(code) -} +// partSet, err = block.MakePartSet(2) +// if err != nil { +// stdlog.Fatal(err) +// } +// part1 = partSet.GetPart(0) +// part2 = partSet.GetPart(1) +// seenCommit1 = makeTestExtCommit(10, tmtime.Now()) +// code := m.Run() +// cleanup() +// os.Exit(code) +// } // TODO: This test should be simplified ... func TestBlockStoreSaveLoadBlock(t *testing.T) { - state, bs, cleanup := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) - defer cleanup() + state, bs := makeStateAndBlockStore(t) require.Equal(t, bs.Base(), int64(0), "initially the base should be zero") require.Equal(t, bs.Height(), int64(0), "initially the height should be zero") @@ -172,8 +178,8 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { block := state.MakeBlock(bs.Height()+1, nil, new(types.Commit), nil, state.Validators.GetProposer().Address) validPartSet, err := block.MakePartSet(2) require.NoError(t, err) - seenCommit := makeTestCommit(10, tmtime.Now()) - bs.SaveBlock(block, partSet, seenCommit) + seenCommit := makeTestExtCommit(10, tmtime.Now()) + bs.SaveBlockWithExtendedCommit(block, partSet, seenCommit) require.EqualValues(t, 1, bs.Base(), "expecting the new height to be changed") require.EqualValues(t, block.Header.Height, bs.Height(), "expecting the new height to be changed") @@ -192,11 +198,11 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { // End of setup, test data - commitAtH10 := makeTestCommit(10, tmtime.Now()) + commitAtH10 := makeTestExtCommit(10, tmtime.Now()).ToCommit() tuples := []struct { block *types.Block parts *types.PartSet - seenCommit *types.Commit + seenCommit *types.ExtendedCommit wantPanic string wantErr bool @@ -209,7 +215,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { { block: newBlock(header1, commitAtH10), parts: validPartSet, - seenCommit: seenCommit1, + seenCommit: seenCommit, }, { @@ -225,10 +231,10 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { ChainID: "block_test", Time: tmtime.Now(), ProposerAddress: tmrand.Bytes(crypto.AddressSize)}, - makeTestCommit(5, tmtime.Now()), + makeTestExtCommit(5, tmtime.Now()).ToCommit(), ), parts: validPartSet, - seenCommit: makeTestCommit(5, tmtime.Now()), + seenCommit: makeTestExtCommit(5, tmtime.Now()), }, { @@ -240,7 +246,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { { block: newBlock(header1, commitAtH10), parts: validPartSet, - seenCommit: seenCommit1, + seenCommit: seenCommit, corruptCommitInDB: true, // Corrupt the DB's commit entry wantPanic: "error reading block commit", }, @@ -248,7 +254,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { { block: newBlock(header1, commitAtH10), parts: validPartSet, - seenCommit: seenCommit1, + seenCommit: seenCommit, wantPanic: "unmarshal to tmproto.BlockMeta", corruptBlockInDB: true, // Corrupt the DB's block entry }, @@ -256,7 +262,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { { block: newBlock(header1, commitAtH10), parts: validPartSet, - seenCommit: seenCommit1, + seenCommit: seenCommit, // Expecting no error and we want a nil back eraseSeenCommitInDB: true, @@ -265,7 +271,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { { block: newBlock(header1, commitAtH10), parts: validPartSet, - seenCommit: seenCommit1, + seenCommit: seenCommit, corruptSeenCommitInDB: true, wantPanic: "error reading block seen commit", @@ -274,7 +280,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { { block: newBlock(header1, commitAtH10), parts: validPartSet, - seenCommit: seenCommit1, + seenCommit: seenCommit, // Expecting no error and we want a nil back eraseCommitInDB: true, @@ -294,7 +300,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { bs, db := freshBlockStore() // SaveBlock res, err, panicErr := doFn(func() (interface{}, error) { - bs.SaveBlock(tuple.block, tuple.parts, tuple.seenCommit) + bs.SaveBlockWithExtendedCommit(tuple.block, tuple.parts, tuple.seenCommit) if tuple.block == nil { return nil, nil } @@ -364,6 +370,86 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { } } +// TestSaveBlockWithExtendedCommitPanicOnAbsentExtension tests that saving a +// block with an extended commit panics when the extension data is absent. +func TestSaveBlockWithExtendedCommitPanicOnAbsentExtension(t *testing.T) { + for _, testCase := range []struct { + name string + malleateCommit func(*types.ExtendedCommit) + shouldPanic bool + }{ + { + name: "basic save", + malleateCommit: func(_ *types.ExtendedCommit) {}, + shouldPanic: false, + }, + { + name: "save commit with no extensions", + malleateCommit: func(c *types.ExtendedCommit) { + c.StripExtensions() + }, + shouldPanic: true, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + state, bs := makeStateAndBlockStore(t) + block := test.MakeBlock(state) + seenCommit := makeTestExtCommit(block.Header.Height, tmtime.Now()) + ps, err := block.MakePartSet(2) + require.NoError(t, err) + testCase.malleateCommit(seenCommit) + if testCase.shouldPanic { + require.Panics(t, func() { + bs.SaveBlockWithExtendedCommit(block, ps, seenCommit) + }) + } else { + bs.SaveBlockWithExtendedCommit(block, ps, seenCommit) + } + }) + } +} + +// TestLoadBlockExtendedCommit tests loading the extended commit for a previously +// saved block. The load method should return nil when only a commit was saved and +// return the extended commit otherwise. +func TestLoadBlockExtendedCommit(t *testing.T) { + for _, testCase := range []struct { + name string + saveExtended bool + expectResult bool + }{ + { + name: "save commit", + saveExtended: false, + expectResult: false, + }, + { + name: "save extended commit", + saveExtended: true, + expectResult: true, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + state, bs := makeStateAndBlockStore(t) + block := test.MakeBlock(state) + seenCommit := makeTestExtCommit(block.Header.Height, tmtime.Now()) + ps, err := block.MakePartSet(2) + require.NoError(t, err) + if testCase.saveExtended { + bs.SaveBlockWithExtendedCommit(block, ps, seenCommit) + } else { + bs.SaveBlock(block, ps, seenCommit.ToCommit()) + } + res := bs.LoadBlockExtendedCommit(block.Height) + if testCase.expectResult { + require.Equal(t, seenCommit, res) + } else { + require.Nil(t, res) + } + }) + } +} + func TestLoadBaseMeta(t *testing.T) { config := cfg.ResetTestRoot("blockchain_reactor_test") defer os.RemoveAll(config.RootDir) @@ -378,8 +464,8 @@ func TestLoadBaseMeta(t *testing.T) { block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) partSet, err := block.MakePartSet(2) require.NoError(t, err) - seenCommit := makeTestCommit(h, tmtime.Now()) - bs.SaveBlock(block, partSet, seenCommit) + seenCommit := makeTestExtCommit(h, tmtime.Now()) + bs.SaveBlockWithExtendedCommit(block, partSet, seenCommit) } _, err = bs.PruneBlocks(4) @@ -449,8 +535,8 @@ func TestPruneBlocks(t *testing.T) { block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) partSet, err := block.MakePartSet(2) require.NoError(t, err) - seenCommit := makeTestCommit(h, tmtime.Now()) - bs.SaveBlock(block, partSet, seenCommit) + seenCommit := makeTestExtCommit(h, tmtime.Now()) + bs.SaveBlockWithExtendedCommit(block, partSet, seenCommit) } assert.EqualValues(t, 1, bs.Base()) @@ -566,8 +652,8 @@ func TestLoadBlockMetaByHash(t *testing.T) { b1 := state.MakeBlock(state.LastBlockHeight+1, test.MakeNTxs(state.LastBlockHeight+1, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) partSet, err := b1.MakePartSet(2) require.NoError(t, err) - seenCommit := makeTestCommit(1, tmtime.Now()) - bs.SaveBlock(b1, partSet, seenCommit) + seenCommit := makeTestExtCommit(1, tmtime.Now()) + bs.SaveBlockWithExtendedCommit(b1, partSet, seenCommit) baseBlock := bs.LoadBlockMetaByHash(b1.Hash()) assert.EqualValues(t, b1.Header.Height, baseBlock.Header.Height) @@ -576,15 +662,14 @@ func TestLoadBlockMetaByHash(t *testing.T) { } func TestBlockFetchAtHeight(t *testing.T) { - state, bs, cleanup := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) - defer cleanup() + state, bs := makeStateAndBlockStore(t) require.Equal(t, bs.Height(), int64(0), "initially the height should be zero") block := state.MakeBlock(bs.Height()+1, nil, new(types.Commit), nil, state.Validators.GetProposer().Address) partSet, err := block.MakePartSet(2) require.NoError(t, err) - seenCommit := makeTestCommit(10, tmtime.Now()) - bs.SaveBlock(block, partSet, seenCommit) + seenCommit := makeTestExtCommit(10, tmtime.Now()) + bs.SaveBlockWithExtendedCommit(block, partSet, seenCommit) require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed") blockAtHeight := bs.LoadBlock(bs.Height()) diff --git a/test/e2e/runner/evidence.go b/test/e2e/runner/evidence.go index 8edfaa4ba..50552f084 100644 --- a/test/e2e/runner/evidence.go +++ b/test/e2e/runner/evidence.go @@ -91,11 +91,11 @@ func InjectEvidence(ctx context.Context, r *rand.Rand, testnet *e2e.Testnet, amo for i := 1; i <= amount; i++ { if i%lightClientEvidenceRatio == 0 { ev, err = generateLightClientAttackEvidence( - ctx, privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time, + privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time, ) } else { ev, err = generateDuplicateVoteEvidence( - ctx, privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time, + privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time, ) } if err != nil { @@ -142,7 +142,6 @@ func getPrivateValidatorKeys(testnet *e2e.Testnet) ([]types.MockPV, error) { // creates evidence of a lunatic attack. The height provided is the common height. // The forged height happens 2 blocks later. func generateLightClientAttackEvidence( - ctx context.Context, privVals []types.MockPV, height int64, vals *types.ValidatorSet, @@ -157,7 +156,7 @@ func generateLightClientAttackEvidence( // add a new bogus validator and remove an existing one to // vary the validator set slightly - pv, conflictingVals, err := mutateValidatorSet(ctx, privVals, vals) + pv, conflictingVals, err := mutateValidatorSet(privVals, vals) if err != nil { return nil, err } @@ -193,7 +192,6 @@ func generateLightClientAttackEvidence( // generateDuplicateVoteEvidence picks a random validator from the val set and // returns duplicate vote evidence against the validator func generateDuplicateVoteEvidence( - ctx context.Context, privVals []types.MockPV, height int64, vals *types.ValidatorSet, @@ -286,9 +284,9 @@ func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.Bloc } } -func mutateValidatorSet(ctx context.Context, privVals []types.MockPV, vals *types.ValidatorSet, +func mutateValidatorSet(privVals []types.MockPV, vals *types.ValidatorSet, ) ([]types.PrivValidator, *types.ValidatorSet, error) { - newVal, newPrivVal, err := test.Validator(ctx, 10) + newVal, newPrivVal, err := test.Validator(10) if err != nil { return nil, nil, err } diff --git a/types/block.go b/types/block.go index 5bf1cbfdb..b30ca829d 100644 --- a/types/block.go +++ b/types/block.go @@ -875,7 +875,7 @@ func (commit *Commit) ValidateBasic() error { } if commit.Height >= 1 { - if commit.BlockID.IsZero() { + if commit.BlockID.IsNil() { return errors.New("commit cannot be for nil block") } @@ -987,6 +987,360 @@ func CommitFromProto(cp *tmproto.Commit) (*Commit, error) { return commit, commit.ValidateBasic() } +//------------------------------------- + +// ExtendedCommitSig contains a commit signature along with its corresponding +// vote extension and vote extension signature. +type ExtendedCommitSig struct { + CommitSig // Commit signature + Extension []byte // Vote extension + ExtensionSignature []byte // Vote extension signature +} + +// NewExtendedCommitSigAbsent returns new ExtendedCommitSig with +// BlockIDFlagAbsent. Other fields are all empty. +func NewExtendedCommitSigAbsent() ExtendedCommitSig { + return ExtendedCommitSig{CommitSig: NewCommitSigAbsent()} +} + +// String returns a string representation of an ExtendedCommitSig. +// +// 1. commit sig +// 2. first 6 bytes of vote extension +// 3. first 6 bytes of vote extension signature +func (ecs ExtendedCommitSig) String() string { + return fmt.Sprintf("ExtendedCommitSig{%s with %X %X}", + ecs.CommitSig, + tmbytes.Fingerprint(ecs.Extension), + tmbytes.Fingerprint(ecs.ExtensionSignature), + ) +} + +// ValidateBasic checks whether the structure is well-formed. +func (ecs ExtendedCommitSig) ValidateBasic() error { + if err := ecs.CommitSig.ValidateBasic(); err != nil { + return err + } + + if ecs.BlockIDFlag == BlockIDFlagCommit { + if len(ecs.Extension) > MaxVoteExtensionSize { + return fmt.Errorf("vote extension is too big (max: %d)", MaxVoteExtensionSize) + } + if len(ecs.ExtensionSignature) > MaxSignatureSize { + return fmt.Errorf("vote extension signature is too big (max: %d)", MaxSignatureSize) + } + return nil + } + + if len(ecs.ExtensionSignature) == 0 && len(ecs.Extension) != 0 { + return errors.New("vote extension signature absent on vote with extension") + } + return nil +} + +// EnsureExtensions validates that a vote extensions signature is present for +// this ExtendedCommitSig. +func (ecs ExtendedCommitSig) EnsureExtension() error { + if ecs.BlockIDFlag == BlockIDFlagCommit && len(ecs.ExtensionSignature) == 0 { + return errors.New("vote extension data is missing") + } + return nil +} + +// ToProto converts the ExtendedCommitSig to its Protobuf representation. +func (ecs *ExtendedCommitSig) ToProto() *tmproto.ExtendedCommitSig { + if ecs == nil { + return nil + } + + return &tmproto.ExtendedCommitSig{ + BlockIdFlag: tmproto.BlockIDFlag(ecs.BlockIDFlag), + ValidatorAddress: ecs.ValidatorAddress, + Timestamp: ecs.Timestamp, + Signature: ecs.Signature, + Extension: ecs.Extension, + ExtensionSignature: ecs.ExtensionSignature, + } +} + +// FromProto populates the ExtendedCommitSig with values from the given +// Protobuf representation. Returns an error if the ExtendedCommitSig is +// invalid. +func (ecs *ExtendedCommitSig) FromProto(ecsp tmproto.ExtendedCommitSig) error { + ecs.BlockIDFlag = BlockIDFlag(ecsp.BlockIdFlag) + ecs.ValidatorAddress = ecsp.ValidatorAddress + ecs.Timestamp = ecsp.Timestamp + ecs.Signature = ecsp.Signature + ecs.Extension = ecsp.Extension + ecs.ExtensionSignature = ecsp.ExtensionSignature + + return ecs.ValidateBasic() +} + +//------------------------------------- + +// ExtendedCommit is similar to Commit, except that its signatures also retain +// their corresponding vote extensions and vote extension signatures. +type ExtendedCommit struct { + Height int64 + Round int32 + BlockID BlockID + ExtendedSignatures []ExtendedCommitSig + + bitArray *bits.BitArray +} + +// Clone creates a deep copy of this extended commit. +func (ec *ExtendedCommit) Clone() *ExtendedCommit { + sigs := make([]ExtendedCommitSig, len(ec.ExtendedSignatures)) + copy(sigs, ec.ExtendedSignatures) + ecc := *ec + ecc.ExtendedSignatures = sigs + return &ecc +} + +// ToExtendedVoteSet constructs a VoteSet from the Commit and validator set. +// Panics if signatures from the ExtendedCommit can't be added to the voteset. +// Panics if any of the votes have invalid or absent vote extension data. +// Inverse of VoteSet.MakeExtendedCommit(). +func (ec *ExtendedCommit) ToExtendedVoteSet(chainID string, vals *ValidatorSet) *VoteSet { + voteSet := NewExtendedVoteSet(chainID, ec.Height, ec.Round, tmproto.PrecommitType, vals) + ec.addSigsToVoteSet(voteSet) + return voteSet +} + +// ToVoteSet constructs a VoteSet from the Commit and validator set. +// Panics if signatures from the ExtendedCommit can't be added to the voteset. +// Inverse of VoteSet.MakeExtendedCommit(). +func (ec *ExtendedCommit) ToVoteSet(chainID string, vals *ValidatorSet) *VoteSet { + voteSet := NewVoteSet(chainID, ec.Height, ec.Round, tmproto.PrecommitType, vals) + ec.addSigsToVoteSet(voteSet) + return voteSet +} + +// addSigsToVoteSet adds all of the signature to voteSet. +func (ec *ExtendedCommit) addSigsToVoteSet(voteSet *VoteSet) { + for idx, ecs := range ec.ExtendedSignatures { + if ecs.BlockIDFlag == BlockIDFlagAbsent { + continue // OK, some precommits can be missing. + } + vote := ec.GetExtendedVote(int32(idx)) + if err := vote.ValidateBasic(); err != nil { + panic(fmt.Errorf("failed to validate vote reconstructed from LastCommit: %w", err)) + } + added, err := voteSet.AddVote(vote) + if !added || err != nil { + panic(fmt.Errorf("failed to reconstruct vote set from extended commit: %w", err)) + } + } +} + +// ToVoteSet constructs a VoteSet from the Commit and validator set. +// Panics if signatures from the commit can't be added to the voteset. +// Inverse of VoteSet.MakeCommit(). +func (commit *Commit) ToVoteSet(chainID string, vals *ValidatorSet) *VoteSet { + voteSet := NewVoteSet(chainID, commit.Height, commit.Round, tmproto.PrecommitType, vals) + for idx, cs := range commit.Signatures { + if cs.BlockIDFlag == BlockIDFlagAbsent { + continue // OK, some precommits can be missing. + } + vote := commit.GetVote(int32(idx)) + if err := vote.ValidateBasic(); err != nil { + panic(fmt.Errorf("failed to validate vote reconstructed from commit: %w", err)) + } + added, err := voteSet.AddVote(vote) + if !added || err != nil { + panic(fmt.Errorf("failed to reconstruct vote set from commit: %w", err)) + } + } + return voteSet +} + +// EnsureExtensions validates that a vote extensions signature is present for +// every ExtendedCommitSig in the ExtendedCommit. +func (ec *ExtendedCommit) EnsureExtensions() error { + for _, ecs := range ec.ExtendedSignatures { + if err := ecs.EnsureExtension(); err != nil { + return err + } + } + return nil +} + +// StripExtensions removes all VoteExtension data from an ExtendedCommit. This +// is useful when dealing with an ExendedCommit but vote extension data is +// expected to be absent. +func (ec *ExtendedCommit) StripExtensions() bool { + stripped := false + for idx := range ec.ExtendedSignatures { + if len(ec.ExtendedSignatures[idx].Extension) > 0 || len(ec.ExtendedSignatures[idx].ExtensionSignature) > 0 { + stripped = true + } + ec.ExtendedSignatures[idx].Extension = nil + ec.ExtendedSignatures[idx].ExtensionSignature = nil + } + return stripped +} + +// ToCommit converts an ExtendedCommit to a Commit by removing all vote +// extension-related fields. +func (ec *ExtendedCommit) ToCommit() *Commit { + cs := make([]CommitSig, len(ec.ExtendedSignatures)) + for idx, ecs := range ec.ExtendedSignatures { + cs[idx] = ecs.CommitSig + } + return &Commit{ + Height: ec.Height, + Round: ec.Round, + BlockID: ec.BlockID, + Signatures: cs, + } +} + +// GetExtendedVote converts the ExtendedCommitSig for the given validator +// index to a Vote with a vote extensions. +// It panics if valIndex is out of range. +func (ec *ExtendedCommit) GetExtendedVote(valIndex int32) *Vote { + ecs := ec.ExtendedSignatures[valIndex] + return &Vote{ + Type: tmproto.PrecommitType, + Height: ec.Height, + Round: ec.Round, + BlockID: ecs.BlockID(ec.BlockID), + Timestamp: ecs.Timestamp, + ValidatorAddress: ecs.ValidatorAddress, + ValidatorIndex: valIndex, + Signature: ecs.Signature, + Extension: ecs.Extension, + ExtensionSignature: ecs.ExtensionSignature, + } +} + +// Type returns the vote type of the extended commit, which is always +// VoteTypePrecommit +// Implements VoteSetReader. +func (ec *ExtendedCommit) Type() byte { return byte(tmproto.PrecommitType) } + +// GetHeight returns height of the extended commit. +// Implements VoteSetReader. +func (ec *ExtendedCommit) GetHeight() int64 { return ec.Height } + +// GetRound returns height of the extended commit. +// Implements VoteSetReader. +func (ec *ExtendedCommit) GetRound() int32 { return ec.Round } + +// Size returns the number of signatures in the extended commit. +// Implements VoteSetReader. +func (ec *ExtendedCommit) Size() int { + if ec == nil { + return 0 + } + return len(ec.ExtendedSignatures) +} + +// BitArray returns a BitArray of which validators voted for BlockID or nil in +// this extended commit. +// Implements VoteSetReader. +func (ec *ExtendedCommit) BitArray() *bits.BitArray { + if ec.bitArray == nil { + ec.bitArray = bits.NewBitArray(len(ec.ExtendedSignatures)) + for i, extCommitSig := range ec.ExtendedSignatures { + // TODO: need to check the BlockID otherwise we could be counting conflicts, + // not just the one with +2/3 ! + ec.bitArray.SetIndex(i, extCommitSig.BlockIDFlag != BlockIDFlagAbsent) + } + } + return ec.bitArray +} + +// GetByIndex returns the vote corresponding to a given validator index. +// Panics if `index >= extCommit.Size()`. +// Implements VoteSetReader. +func (ec *ExtendedCommit) GetByIndex(valIdx int32) *Vote { + return ec.GetExtendedVote(valIdx) +} + +// IsCommit returns true if there is at least one signature. +// Implements VoteSetReader. +func (ec *ExtendedCommit) IsCommit() bool { + return len(ec.ExtendedSignatures) != 0 +} + +// ValidateBasic checks whether the extended commit is well-formed. Does not +// actually check the cryptographic signatures. +func (ec *ExtendedCommit) ValidateBasic() error { + if ec.Height < 0 { + return errors.New("negative Height") + } + if ec.Round < 0 { + return errors.New("negative Round") + } + + if ec.Height >= 1 { + if ec.BlockID.IsNil() { + return errors.New("commit cannot be for nil block") + } + + if len(ec.ExtendedSignatures) == 0 { + return errors.New("no signatures in commit") + } + for i, extCommitSig := range ec.ExtendedSignatures { + if err := extCommitSig.ValidateBasic(); err != nil { + return fmt.Errorf("wrong ExtendedCommitSig #%d: %v", i, err) + } + } + } + return nil +} + +// ToProto converts ExtendedCommit to protobuf +func (ec *ExtendedCommit) ToProto() *tmproto.ExtendedCommit { + if ec == nil { + return nil + } + + c := new(tmproto.ExtendedCommit) + sigs := make([]tmproto.ExtendedCommitSig, len(ec.ExtendedSignatures)) + for i := range ec.ExtendedSignatures { + sigs[i] = *ec.ExtendedSignatures[i].ToProto() + } + c.ExtendedSignatures = sigs + + c.Height = ec.Height + c.Round = ec.Round + c.BlockID = ec.BlockID.ToProto() + + return c +} + +// ExtendedCommitFromProto constructs an ExtendedCommit from the given Protobuf +// representation. It returns an error if the extended commit is invalid. +func ExtendedCommitFromProto(ecp *tmproto.ExtendedCommit) (*ExtendedCommit, error) { + if ecp == nil { + return nil, errors.New("nil ExtendedCommit") + } + + extCommit := new(ExtendedCommit) + + bi, err := BlockIDFromProto(&ecp.BlockID) + if err != nil { + return nil, err + } + + sigs := make([]ExtendedCommitSig, len(ecp.ExtendedSignatures)) + for i := range ecp.ExtendedSignatures { + if err := sigs[i].FromProto(ecp.ExtendedSignatures[i]); err != nil { + return nil, err + } + } + extCommit.ExtendedSignatures = sigs + extCommit.Height = ecp.Height + extCommit.Round = ecp.Round + extCommit.BlockID = *bi + + return extCommit, extCommit.ValidateBasic() +} + //----------------------------------------------------------------------------- // Data contains the set of transactions included in the block @@ -1167,6 +1521,17 @@ type BlockID struct { PartSetHeader PartSetHeader `json:"parts"` } +func NewBlockID(hash []byte, header PartSetHeader) BlockID { + return BlockID{ + Hash: hash, + PartSetHeader: header, + } +} + +func NilBlockID() BlockID { + return NewBlockID(nil, PartSetHeader{}) +} + // Equals returns true if the BlockID matches the given BlockID func (blockID BlockID) Equals(other BlockID) bool { return bytes.Equal(blockID.Hash, other.Hash) && @@ -1196,8 +1561,8 @@ func (blockID BlockID) ValidateBasic() error { return nil } -// IsZero returns true if this is the BlockID of a nil block. -func (blockID BlockID) IsZero() bool { +// IsNil returns true if this is the BlockID of a nil block. +func (blockID BlockID) IsNil() bool { return len(blockID.Hash) == 0 && blockID.PartSetHeader.IsZero() } @@ -1249,3 +1614,9 @@ func BlockIDFromProto(bID *tmproto.BlockID) (*BlockID, error) { return blockID, blockID.ValidateBasic() } + +// IsProtoBlockIDNil is similar to the IsNil function on BlockID, but for the +// Protobuf representation. +func IsProtoBlockIDNil(bID *tmproto.BlockID) bool { + return len(bID.Hash) == 0 && IsProtoPartSetHeaderZero(&bID.PartSetHeader) +} diff --git a/types/block_test.go b/types/block_test.go index cbc4cf65a..d11c2f288 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -38,14 +38,14 @@ func TestBlockAddEvidence(t *testing.T) { h := int64(3) voteSet, _, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) - commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) + extCommit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") require.NoError(t, err) evList := []Evidence{ev} - block := MakeBlock(h, txs, commit, evList) + block := MakeBlock(h, txs, extCommit.ToCommit(), evList) require.NotNil(t, block) require.Equal(t, 1, len(block.Evidence.Evidence)) require.NotNil(t, block.EvidenceHash) @@ -59,8 +59,9 @@ func TestBlockValidateBasic(t *testing.T) { h := int64(3) voteSet, valSet, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) - commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) + extCommit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) + commit := extCommit.ToCommit() ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") require.NoError(t, err) @@ -131,14 +132,14 @@ func TestBlockMakePartSetWithEvidence(t *testing.T) { h := int64(3) voteSet, _, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) - commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) + extCommit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") require.NoError(t, err) evList := []Evidence{ev} - partSet, err := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList).MakePartSet(512) + partSet, err := MakeBlock(h, []Tx{Tx("Hello World")}, extCommit.ToCommit(), evList).MakePartSet(512) require.NoError(t, err) assert.NotNil(t, partSet) @@ -151,14 +152,14 @@ func TestBlockHashesTo(t *testing.T) { lastID := makeBlockIDRandom() h := int64(3) voteSet, valSet, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) - commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) + extCommit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") require.NoError(t, err) evList := []Evidence{ev} - block := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList) + block := MakeBlock(h, []Tx{Tx("Hello World")}, extCommit.ToCommit(), evList) block.ValidatorsHash = valSet.Hash() assert.False(t, block.HashesTo([]byte{})) assert.False(t, block.HashesTo([]byte("something else"))) @@ -230,7 +231,7 @@ func TestCommit(t *testing.T) { lastID := makeBlockIDRandom() h := int64(3) voteSet, _, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) - commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) + commit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) assert.Equal(t, h-1, commit.Height) @@ -438,11 +439,11 @@ func randCommit(now time.Time) *Commit { lastID := makeBlockIDRandom() h := int64(3) voteSet, _, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) - commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, now) + extCommit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, now) if err != nil { panic(err) } - return commit + return extCommit.ToCommit() } func hexBytesFromString(s string) bytes.HexBytes { @@ -514,13 +515,144 @@ func TestBlockMaxDataBytesNoEvidence(t *testing.T) { } } +// TestVoteSetToExtendedCommit tests that the extended commit produced from a +// vote set contains the same vote information as the vote set. The test ensures +// that the MakeExtendedCommit method behaves as expected, whether vote extensions +// are present in the original votes or not. +func TestVoteSetToExtendedCommit(t *testing.T) { + for _, testCase := range []struct { + name string + includeExtension bool + }{ + { + name: "no extensions", + includeExtension: false, + }, + { + name: "with extensions", + includeExtension: true, + }, + } { + + t.Run(testCase.name, func(t *testing.T) { + blockID := makeBlockIDRandom() + + _, valSet, vals := randVoteSet(10, 1, tmproto.PrecommitType, 10, 1) + var voteSet *VoteSet + if testCase.includeExtension { + voteSet = NewExtendedVoteSet("test_chain_id", 3, 1, tmproto.PrecommitType, valSet) + } else { + voteSet = NewVoteSet("test_chain_id", 3, 1, tmproto.PrecommitType, valSet) + } + for i := 0; i < len(vals); i++ { + pubKey, err := vals[i].GetPubKey() + require.NoError(t, err) + vote := &Vote{ + ValidatorAddress: pubKey.Address(), + ValidatorIndex: int32(i), + Height: 3, + Round: 1, + Type: tmproto.PrecommitType, + BlockID: blockID, + Timestamp: time.Now(), + } + v := vote.ToProto() + err = vals[i].SignVote(voteSet.ChainID(), v) + require.NoError(t, err) + vote.Signature = v.Signature + if testCase.includeExtension { + vote.ExtensionSignature = v.ExtensionSignature + } + added, err := voteSet.AddVote(vote) + require.NoError(t, err) + require.True(t, added) + } + ec := voteSet.MakeExtendedCommit() + + for i := int32(0); int(i) < len(vals); i++ { + vote1 := voteSet.GetByIndex(i) + vote2 := ec.GetExtendedVote(i) + + vote1bz, err := vote1.ToProto().Marshal() + require.NoError(t, err) + vote2bz, err := vote2.ToProto().Marshal() + require.NoError(t, err) + assert.Equal(t, vote1bz, vote2bz) + } + }) + } +} + +// TestExtendedCommitToVoteSet tests that the vote set produced from an extended commit +// contains the same vote information as the extended commit. The test ensures +// that the ToVoteSet method behaves as expected, whether vote extensions +// are present in the original votes or not. +func TestExtendedCommitToVoteSet(t *testing.T) { + for _, testCase := range []struct { + name string + includeExtension bool + }{ + { + name: "no extensions", + includeExtension: false, + }, + { + name: "with extensions", + includeExtension: true, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + lastID := makeBlockIDRandom() + h := int64(3) + + voteSet, valSet, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) + extCommit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, time.Now()) + assert.NoError(t, err) + + if !testCase.includeExtension { + for i := 0; i < len(vals); i++ { + v := voteSet.GetByIndex(int32(i)) + v.Extension = nil + v.ExtensionSignature = nil + extCommit.ExtendedSignatures[i].Extension = nil + extCommit.ExtendedSignatures[i].ExtensionSignature = nil + } + } + + chainID := voteSet.ChainID() + var voteSet2 *VoteSet + if testCase.includeExtension { + voteSet2 = extCommit.ToExtendedVoteSet(chainID, valSet) + } else { + voteSet2 = extCommit.ToVoteSet(chainID, valSet) + } + + for i := int32(0); int(i) < len(vals); i++ { + vote1 := voteSet.GetByIndex(i) + vote2 := voteSet2.GetByIndex(i) + vote3 := extCommit.GetExtendedVote(i) + + vote1bz, err := vote1.ToProto().Marshal() + require.NoError(t, err) + vote2bz, err := vote2.ToProto().Marshal() + require.NoError(t, err) + vote3bz, err := vote3.ToProto().Marshal() + require.NoError(t, err) + assert.Equal(t, vote1bz, vote2bz) + assert.Equal(t, vote1bz, vote3bz) + } + }) + } +} + func TestCommitToVoteSet(t *testing.T) { lastID := makeBlockIDRandom() h := int64(3) voteSet, valSet, vals := randVoteSet(h-1, 1, tmproto.PrecommitType, 10, 1) - commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) + extCommit, err := makeExtCommit(lastID, h-1, 1, voteSet, vals, time.Now()) assert.NoError(t, err) + commit := extCommit.ToCommit() chainID := voteSet.ChainID() voteSet2 := CommitToVoteSet(chainID, commit, valSet) @@ -587,12 +719,12 @@ func TestCommitToVoteSetWithVotesForNilBlock(t *testing.T) { } if tc.valid { - commit := voteSet.MakeCommit() // panics without > 2/3 valid votes - assert.NotNil(t, commit) - err := valSet.VerifyCommit(voteSet.ChainID(), blockID, height-1, commit) + extCommit := voteSet.MakeExtendedCommit() // panics without > 2/3 valid votes + assert.NotNil(t, extCommit) + err := valSet.VerifyCommit(voteSet.ChainID(), blockID, height-1, extCommit.ToCommit()) assert.Nil(t, err) } else { - assert.Panics(t, func() { voteSet.MakeCommit() }) + assert.Panics(t, func() { voteSet.MakeExtendedCommit() }) } } } diff --git a/types/canonical.go b/types/canonical.go index 49d98405d..a98fd5632 100644 --- a/types/canonical.go +++ b/types/canonical.go @@ -21,7 +21,7 @@ func CanonicalizeBlockID(bid tmproto.BlockID) *tmproto.CanonicalBlockID { panic(err) } var cbid *tmproto.CanonicalBlockID - if rbid == nil || rbid.IsZero() { + if rbid == nil || rbid.IsNil() { cbid = nil } else { cbid = &tmproto.CanonicalBlockID{ @@ -64,6 +64,18 @@ func CanonicalizeVote(chainID string, vote *tmproto.Vote) tmproto.CanonicalVote } } +// CanonicalizeVoteExtension extracts the vote extension from the given vote +// and constructs a CanonicalizeVoteExtension struct, whose representation in +// bytes is what is signed in order to produce the vote extension's signature. +func CanonicalizeVoteExtension(chainID string, vote *tmproto.Vote) tmproto.CanonicalVoteExtension { + return tmproto.CanonicalVoteExtension{ + Extension: vote.Extension, + Height: vote.Height, + Round: int64(vote.Round), + ChainId: chainID, + } +} + // CanonicalTime can be used to stringify time in a canonical way. func CanonicalTime(t time.Time) string { // Note that sending time over amino resets it to diff --git a/types/evidence_test.go b/types/evidence_test.go index feb0b0195..2b68d85d6 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -99,8 +99,9 @@ func TestLightClientAttackEvidenceBasic(t *testing.T) { header := makeHeaderRandom() header.Height = height blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - commit, err := MakeCommit(blockID, height, 1, voteSet, privVals, defaultVoteTime) + extCommit, err := makeExtCommit(blockID, height, 1, voteSet, privVals, defaultVoteTime) require.NoError(t, err) + commit := extCommit.ToCommit() lcae := &LightClientAttackEvidence{ ConflictingBlock: &LightBlock{ SignedHeader: &SignedHeader{ @@ -159,13 +160,13 @@ func TestLightClientAttackEvidenceValidation(t *testing.T) { header.Height = height header.ValidatorsHash = valSet.Hash() blockID := makeBlockID(header.Hash(), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - commit, err := MakeCommit(blockID, height, 1, voteSet, privVals, time.Now()) + extCommit, err := makeExtCommit(blockID, height, 1, voteSet, privVals, time.Now()) require.NoError(t, err) lcae := &LightClientAttackEvidence{ ConflictingBlock: &LightBlock{ SignedHeader: &SignedHeader{ Header: header, - Commit: commit, + Commit: extCommit.ToCommit(), }, ValidatorSet: valSet, }, @@ -205,7 +206,7 @@ func TestLightClientAttackEvidenceValidation(t *testing.T) { ConflictingBlock: &LightBlock{ SignedHeader: &SignedHeader{ Header: header, - Commit: commit, + Commit: extCommit.ToCommit(), }, ValidatorSet: valSet, }, diff --git a/types/params.go b/types/params.go index 246037d85..1b8385980 100644 --- a/types/params.go +++ b/types/params.go @@ -8,6 +8,7 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/tendermint/tendermint/crypto/tmhash" + tmstrings "github.com/tendermint/tendermint/libs/strings" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -37,6 +38,7 @@ type ConsensusParams struct { Evidence EvidenceParams `json:"evidence"` Validator ValidatorParams `json:"validator"` Version VersionParams `json:"version"` + ABCI ABCIParams `json:"abci"` } // BlockParams define limits on the block size and gas plus minimum time @@ -63,6 +65,22 @@ type VersionParams struct { App uint64 `json:"app"` } +// ABCIParams configure ABCI functionality specific to the Application Blockchain +// Interface. +type ABCIParams struct { + VoteExtensionsEnableHeight int64 `json:"vote_extensions_enable_height"` + RecheckTx bool `json:"recheck_tx"` +} + +// VoteExtensionsEnabled returns true if vote extensions are enabled at height h +// and false otherwise. +func (a ABCIParams) VoteExtensionsEnabled(h int64) bool { + if a.VoteExtensionsEnableHeight == 0 { + return false + } + return a.VoteExtensionsEnableHeight <= h +} + // DefaultConsensusParams returns a default ConsensusParams. func DefaultConsensusParams() *ConsensusParams { return &ConsensusParams{ @@ -70,6 +88,7 @@ func DefaultConsensusParams() *ConsensusParams { Evidence: DefaultEvidenceParams(), Validator: DefaultValidatorParams(), Version: DefaultVersionParams(), + ABCI: DefaultABCIParams(), } } @@ -104,6 +123,13 @@ func DefaultVersionParams() VersionParams { } } +func DefaultABCIParams() ABCIParams { + return ABCIParams{ + // When set to 0, vote extensions are not required. + VoteExtensionsEnableHeight: 0, + } +} + func IsValidPubkeyType(params ValidatorParams, pubkeyType string) bool { for i := 0; i < len(params.PubKeyTypes); i++ { if params.PubKeyTypes[i] == pubkeyType { @@ -150,6 +176,10 @@ func (params ConsensusParams) ValidateBasic() error { params.Evidence.MaxBytes) } + if params.ABCI.VoteExtensionsEnableHeight < 0 { + return fmt.Errorf("ABCI.VoteExtensionsEnableHeight cannot be negative. Got: %d", params.ABCI.VoteExtensionsEnableHeight) + } + if len(params.Validator.PubKeyTypes) == 0 { return errors.New("len(Validator.PubKeyTypes) must be greater than 0") } @@ -166,6 +196,30 @@ func (params ConsensusParams) ValidateBasic() error { return nil } +func (params ConsensusParams) ValidateUpdate(updated *tmproto.ConsensusParams, h int64) error { + if updated.Abci == nil { + return nil + } + if params.ABCI.VoteExtensionsEnableHeight == updated.Abci.VoteExtensionsEnableHeight { + return nil + } + if params.ABCI.VoteExtensionsEnableHeight != 0 && updated.Abci.VoteExtensionsEnableHeight == 0 { + return errors.New("vote extensions cannot be disabled once enabled") + } + if updated.Abci.VoteExtensionsEnableHeight <= h { + return fmt.Errorf("VoteExtensionsEnableHeight cannot be updated to a past height, "+ + "initial height: %d, current height %d", + params.ABCI.VoteExtensionsEnableHeight, h) + } + if params.ABCI.VoteExtensionsEnableHeight <= h { + return fmt.Errorf("VoteExtensionsEnableHeight cannot be updated modified once"+ + "the initial height has occurred, "+ + "initial height: %d, current height %d", + params.ABCI.VoteExtensionsEnableHeight, h) + } + return nil +} + // Hash returns a hash of a subset of the parameters to store in the block header. // Only the Block.MaxBytes and Block.MaxGas are included in the hash. // This allows the ConsensusParams to evolve more without breaking the block @@ -190,6 +244,14 @@ func (params ConsensusParams) Hash() []byte { return hasher.Sum(nil) } +func (params *ConsensusParams) Equals(params2 *ConsensusParams) bool { + return params.Block == params2.Block && + params.Evidence == params2.Evidence && + params.Version == params2.Version && + params.ABCI == params2.ABCI && + tmstrings.StringSliceEqual(params.Validator.PubKeyTypes, params2.Validator.PubKeyTypes) +} + // Update returns a copy of the params with updates from the non-zero fields of p2. // NOTE: note: must not modify the original func (params ConsensusParams) Update(params2 *tmproto.ConsensusParams) ConsensusParams { @@ -217,6 +279,9 @@ func (params ConsensusParams) Update(params2 *tmproto.ConsensusParams) Consensus if params2.Version != nil { res.Version.App = params2.Version.App } + if params2.Abci != nil { + res.ABCI.VoteExtensionsEnableHeight = params2.Abci.GetVoteExtensionsEnableHeight() + } return res } @@ -237,11 +302,14 @@ func (params *ConsensusParams) ToProto() tmproto.ConsensusParams { Version: &tmproto.VersionParams{ App: params.Version.App, }, + Abci: &tmproto.ABCIParams{ + VoteExtensionsEnableHeight: params.ABCI.VoteExtensionsEnableHeight, + }, } } func ConsensusParamsFromProto(pbParams tmproto.ConsensusParams) ConsensusParams { - return ConsensusParams{ + c := ConsensusParams{ Block: BlockParams{ MaxBytes: pbParams.Block.MaxBytes, MaxGas: pbParams.Block.MaxGas, @@ -258,4 +326,8 @@ func ConsensusParamsFromProto(pbParams tmproto.ConsensusParams) ConsensusParams App: pbParams.Version.App, }, } + if pbParams.Abci != nil { + c.ABCI.VoteExtensionsEnableHeight = pbParams.Abci.GetVoteExtensionsEnableHeight() + } + return c } diff --git a/types/part_set.go b/types/part_set.go index 5e76b57fa..7f9fb1dba 100644 --- a/types/part_set.go +++ b/types/part_set.go @@ -145,6 +145,12 @@ func PartSetHeaderFromProto(ppsh *tmproto.PartSetHeader) (*PartSetHeader, error) return psh, psh.ValidateBasic() } +// IsProtoPartSetHeaderZero is similar to the IsZero function for +// PartSetHeader, but for the Protobuf representation. +func IsProtoPartSetHeaderZero(ppsh *tmproto.PartSetHeader) bool { + return ppsh.Total == 0 && len(ppsh.Hash) == 0 +} + //------------------------------------- type PartSet struct { diff --git a/types/priv_validator.go b/types/priv_validator.go index 49211773a..2de98cfa6 100644 --- a/types/priv_validator.go +++ b/types/priv_validator.go @@ -82,6 +82,20 @@ func (pv MockPV) SignVote(chainID string, vote *tmproto.Vote) error { return err } vote.Signature = sig + + var extSig []byte + // We only sign vote extensions for non-nil precommits + // We always sign extensions, even if they are empty + if vote.Type == tmproto.PrecommitType && !IsProtoBlockIDNil(&vote.BlockID) { + extSignBytes := VoteExtensionSignBytes(useChainID, vote) + extSig, err = pv.PrivKey.Sign(extSignBytes) + if err != nil { + return err + } + } else if len(vote.Extension) > 0 { + return errors.New("unexpected vote extension - vote extensions are only allowed in non-nil precommits") + } + vote.ExtensionSignature = extSig return nil } diff --git a/types/test_util.go b/types/test_util.go index 0481c1388..9d5b49e0b 100644 --- a/types/test_util.go +++ b/types/test_util.go @@ -9,8 +9,8 @@ import ( "github.com/tendermint/tendermint/version" ) -func MakeCommit(blockID BlockID, height int64, round int32, - voteSet *VoteSet, validators []PrivValidator, now time.Time) (*Commit, error) { +func makeExtCommit(blockID BlockID, height int64, round int32, + voteSet *VoteSet, validators []PrivValidator, now time.Time) (*ExtendedCommit, error) { // all sign for i := 0; i < len(validators); i++ { @@ -34,7 +34,7 @@ func MakeCommit(blockID BlockID, height int64, round int32, } } - return voteSet.MakeCommit(), nil + return voteSet.MakeExtendedCommit(), nil } func signAddVote(privVal PrivValidator, vote *Vote, voteSet *VoteSet) (signed bool, err error) { diff --git a/types/validator_set_test.go b/types/validator_set_test.go index 6fbbb0885..510faf9c9 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -751,8 +751,9 @@ func TestValidatorSet_VerifyCommit_CheckAllSignatures(t *testing.T) { ) voteSet, valSet, vals := randVoteSet(h, 0, tmproto.PrecommitType, 4, 10) - commit, err := MakeCommit(blockID, h, 0, voteSet, vals, time.Now()) + extCommit, err := makeExtCommit(blockID, h, 0, voteSet, vals, time.Now()) require.NoError(t, err) + commit := extCommit.ToCommit() // malleate 4th signature vote := voteSet.GetByIndex(3) @@ -776,8 +777,9 @@ func TestValidatorSet_VerifyCommitLight_ReturnsAsSoonAsMajorityOfVotingPowerSign ) voteSet, valSet, vals := randVoteSet(h, 0, tmproto.PrecommitType, 4, 10) - commit, err := MakeCommit(blockID, h, 0, voteSet, vals, time.Now()) + extCommit, err := makeExtCommit(blockID, h, 0, voteSet, vals, time.Now()) require.NoError(t, err) + commit := extCommit.ToCommit() // malleate 4th signature (3 signatures are enough for 2/3+) vote := voteSet.GetByIndex(3) @@ -799,8 +801,9 @@ func TestValidatorSet_VerifyCommitLightTrusting_ReturnsAsSoonAsTrustLevelOfVotin ) voteSet, valSet, vals := randVoteSet(h, 0, tmproto.PrecommitType, 4, 10) - commit, err := MakeCommit(blockID, h, 0, voteSet, vals, time.Now()) + extCommit, err := makeExtCommit(blockID, h, 0, voteSet, vals, time.Now()) require.NoError(t, err) + commit := extCommit.ToCommit() // malleate 3rd signature (2 signatures are enough for 1/3+ trust level) vote := voteSet.GetByIndex(2) @@ -1521,10 +1524,11 @@ func TestValidatorSet_VerifyCommitLightTrusting(t *testing.T) { var ( blockID = makeBlockIDRandom() voteSet, originalValset, vals = randVoteSet(1, 1, tmproto.PrecommitType, 6, 1) - commit, err = MakeCommit(blockID, 1, 1, voteSet, vals, time.Now()) + extCommit, err = makeExtCommit(blockID, 1, 1, voteSet, vals, time.Now()) newValSet, _ = RandValidatorSet(2, 1) ) require.NoError(t, err) + commit := extCommit.ToCommit() testCases := []struct { valSet *ValidatorSet @@ -1562,9 +1566,10 @@ func TestValidatorSet_VerifyCommitLightTrustingErrorsOnOverflow(t *testing.T) { var ( blockID = makeBlockIDRandom() voteSet, valSet, vals = randVoteSet(1, 1, tmproto.PrecommitType, 1, MaxTotalVotingPower) - commit, err = MakeCommit(blockID, 1, 1, voteSet, vals, time.Now()) + extCommit, err = makeExtCommit(blockID, 1, 1, voteSet, vals, time.Now()) ) require.NoError(t, err) + commit := extCommit.ToCommit() err = valSet.VerifyCommitLightTrusting("test_chain_id", commit, tmmath.Fraction{Numerator: 25, Denominator: 55}) diff --git a/types/vote.go b/types/vote.go index 02b3cad3d..78b6492a7 100644 --- a/types/vote.go +++ b/types/vote.go @@ -13,7 +13,11 @@ import ( ) const ( - nilVoteStr string = "nil-Vote" + absentVoteStr string = "Vote{absent}" + nilVoteStr string = "nil" + + // The maximum supported number of bytes in a vote extension. + MaxVoteExtensionSize int = 1024 * 1024 ) var ( @@ -24,6 +28,7 @@ var ( ErrVoteInvalidBlockHash = errors.New("invalid block hash") ErrVoteNonDeterministicSignature = errors.New("non-deterministic signature") ErrVoteNil = errors.New("nil vote") + ErrVoteExtensionAbsent = errors.New("expected vote extension is absent") ) type ErrVoteConflictingVotes struct { @@ -48,14 +53,16 @@ type Address = crypto.Address // Vote represents a prevote, precommit, or commit vote from validators for // consensus. type Vote struct { - Type tmproto.SignedMsgType `json:"type"` - Height int64 `json:"height"` - Round int32 `json:"round"` // assume there will not be greater than 2_147_483_647 rounds - BlockID BlockID `json:"block_id"` // zero if vote is nil. - Timestamp time.Time `json:"timestamp"` - ValidatorAddress Address `json:"validator_address"` - ValidatorIndex int32 `json:"validator_index"` - Signature []byte `json:"signature"` + Type tmproto.SignedMsgType `json:"type"` + Height int64 `json:"height"` + Round int32 `json:"round"` // assume there will not be greater than 2_147_483_647 rounds + BlockID BlockID `json:"block_id"` // zero if vote is nil. + Timestamp time.Time `json:"timestamp"` + ValidatorAddress Address `json:"validator_address"` + ValidatorIndex int32 `json:"validator_index"` + Signature []byte `json:"signature"` + Extension []byte `json:"extension"` + ExtensionSignature []byte `json:"extension_signature"` } // CommitSig converts the Vote to a CommitSig. @@ -68,7 +75,7 @@ func (vote *Vote) CommitSig() CommitSig { switch { case vote.BlockID.IsComplete(): blockIDFlag = BlockIDFlagCommit - case vote.BlockID.IsZero(): + case vote.BlockID.IsNil(): blockIDFlag = BlockIDFlagNil default: panic(fmt.Sprintf("Invalid vote %v - expected BlockID to be either empty or complete", vote)) @@ -82,6 +89,31 @@ func (vote *Vote) CommitSig() CommitSig { } } +// StripExtension removes any extension data from the vote. Useful if the +// chain has not enabled vote extensions. +// Returns true if extension data was present before stripping and false otherwise. +func (vote *Vote) StripExtension() bool { + stripped := len(vote.Extension) > 0 || len(vote.ExtensionSignature) > 0 + vote.Extension = nil + vote.ExtensionSignature = nil + return stripped +} + +// ExtendedCommitSig attempts to construct an ExtendedCommitSig from this vote. +// Panics if either the vote extension signature is missing or if the block ID +// is not either empty or complete. +func (vote *Vote) ExtendedCommitSig() ExtendedCommitSig { + if vote == nil { + return NewExtendedCommitSigAbsent() + } + + return ExtendedCommitSig{ + CommitSig: vote.CommitSig(), + Extension: vote.Extension, + ExtensionSignature: vote.ExtensionSignature, + } +} + // VoteSignBytes returns the proto-encoding of the canonicalized Vote, for // signing. Panics is the marshaling fails. // @@ -100,6 +132,21 @@ func VoteSignBytes(chainID string, vote *tmproto.Vote) []byte { return bz } +// VoteExtensionSignBytes returns the proto-encoding of the canonicalized vote +// extension for signing. Panics if the marshaling fails. +// +// Similar to VoteSignBytes, the encoded Protobuf message is varint +// length-prefixed for backwards-compatibility with the Amino encoding. +func VoteExtensionSignBytes(chainID string, vote *tmproto.Vote) []byte { + pb := CanonicalizeVoteExtension(chainID, vote) + bz, err := protoio.MarshalDelimited(&pb) + if err != nil { + panic(err) + } + + return bz +} + func (vote *Vote) Copy() *Vote { voteCopy := *vote return &voteCopy @@ -118,38 +165,91 @@ func (vote *Vote) Copy() *Vote { // 9. timestamp func (vote *Vote) String() string { if vote == nil { - return nilVoteStr + return absentVoteStr + } + + var blockHashString string + if len(vote.BlockID.Hash) > 0 { + blockHashString = fmt.Sprintf("%X", tmbytes.Fingerprint(vote.BlockID.Hash)) + } else { + blockHashString = nilVoteStr } - var typeString string switch vote.Type { case tmproto.PrevoteType: - typeString = "Prevote" + return fmt.Sprintf("Prevote{%v/%02d by %v:%X for %X <%X> @ %s}", + vote.Height, + vote.Round, + vote.ValidatorIndex, + tmbytes.Fingerprint(vote.ValidatorAddress), + blockHashString, + tmbytes.Fingerprint(vote.Signature), + CanonicalTime(vote.Timestamp), + ) case tmproto.PrecommitType: - typeString = "Precommit" + return fmt.Sprintf("Precommit{%v/%02d by %v:%X for %X <%X> & %d <%X> @ %s}", + vote.Height, + vote.Round, + vote.ValidatorIndex, + tmbytes.Fingerprint(vote.ValidatorAddress), + blockHashString, + tmbytes.Fingerprint(vote.Signature), + len(vote.Extension), + tmbytes.Fingerprint(vote.Extension), + CanonicalTime(vote.Timestamp), + ) default: panic("Unknown vote type") } - - return fmt.Sprintf("Vote{%v:%X %v/%02d/%v(%v) %X %X @ %s}", - vote.ValidatorIndex, - tmbytes.Fingerprint(vote.ValidatorAddress), - vote.Height, - vote.Round, - vote.Type, - typeString, - tmbytes.Fingerprint(vote.BlockID.Hash), - tmbytes.Fingerprint(vote.Signature), - CanonicalTime(vote.Timestamp), - ) } -func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error { +func (vote *Vote) verifyAndReturnProto(chainID string, pubKey crypto.PubKey) (*tmproto.Vote, error) { if !bytes.Equal(pubKey.Address(), vote.ValidatorAddress) { - return ErrVoteInvalidValidatorAddress + return nil, ErrVoteInvalidValidatorAddress } v := vote.ToProto() if !pubKey.VerifySignature(VoteSignBytes(chainID, v), vote.Signature) { + return nil, ErrVoteInvalidSignature + } + return v, nil +} + +// Verify checks whether the signature associated with this vote corresponds to +// the given chain ID and public key. This function does not validate vote +// extension signatures - to do so, use VerifyWithExtension instead. +func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error { + _, err := vote.verifyAndReturnProto(chainID, pubKey) + return err +} + +// VerifyVoteAndExtension performs the same verification as Verify, but +// additionally checks whether the vote extension signature corresponds to the +// given chain ID and public key. We only verify vote extension signatures for +// precommits. +func (vote *Vote) VerifyVoteAndExtension(chainID string, pubKey crypto.PubKey) error { + v, err := vote.verifyAndReturnProto(chainID, pubKey) + if err != nil { + return err + } + // We only verify vote extension signatures for non-nil precommits. + if vote.Type == tmproto.PrecommitType && !IsProtoBlockIDNil(&v.BlockID) { + extSignBytes := VoteExtensionSignBytes(chainID, v) + if !pubKey.VerifySignature(extSignBytes, vote.ExtensionSignature) { + return ErrVoteInvalidSignature + } + } + return nil +} + +// VerifyExtension checks whether the vote extension signature corresponds to the +// given chain ID and public key. +func (vote *Vote) VerifyExtension(chainID string, pubKey crypto.PubKey) error { + if vote.Type != tmproto.PrecommitType || vote.BlockID.IsNil() { + return nil + } + v := vote.ToProto() + extSignBytes := VoteExtensionSignBytes(chainID, v) + if !pubKey.VerifySignature(extSignBytes, vote.ExtensionSignature) { return ErrVoteInvalidSignature } return nil @@ -161,8 +261,8 @@ func (vote *Vote) ValidateBasic() error { return errors.New("invalid Type") } - if vote.Height < 0 { - return errors.New("negative Height") + if vote.Height <= 0 { + return errors.New("negative or zero Height") } if vote.Round < 0 { @@ -177,7 +277,7 @@ func (vote *Vote) ValidateBasic() error { // BlockID.ValidateBasic would not err if we for instance have an empty hash but a // non-empty PartsSetHeader: - if !vote.BlockID.IsZero() && !vote.BlockID.IsComplete() { + if !vote.BlockID.IsNil() && !vote.BlockID.IsComplete() { return fmt.Errorf("blockID must be either empty or complete, got: %v", vote.BlockID) } @@ -198,9 +298,52 @@ func (vote *Vote) ValidateBasic() error { return fmt.Errorf("signature is too big (max: %d)", MaxSignatureSize) } + // We should only ever see vote extensions in non-nil precommits, otherwise + // this is a violation of the specification. + // https://github.com/tendermint/tendermint/issues/8487 + if vote.Type != tmproto.PrecommitType || (vote.Type == tmproto.PrecommitType && vote.BlockID.IsNil()) { + if len(vote.Extension) > 0 { + return errors.New("unexpected vote extension") + } + if len(vote.ExtensionSignature) > 0 { + return errors.New("unexpected vote extension signature") + } + } else { + // It's possible that this vote has vote extensions but + // they could also be disabled and thus not present thus + // we can't do all checks + if len(vote.ExtensionSignature) > MaxSignatureSize { + return fmt.Errorf("vote extension signature is too big (max: %d)", MaxSignatureSize) + } + + // NOTE: extended votes should have a signature regardless of + // of whether there is any data in the extension or not however + // we don't know if extensions are enabled so we can only + // enforce the signature when extension size is no nil + if len(vote.ExtensionSignature) == 0 && len(vote.Extension) != 0 { + return fmt.Errorf("vote extension signature absent on vote with extension") + } + } + return nil } +// EnsureExtension checks for the presence of extensions signature data +// on precommit vote types. +func (vote *Vote) EnsureExtension() error { + // We should always see vote extension signatures in non-nil precommits + if vote.Type != tmproto.PrecommitType { + return nil + } + if vote.BlockID.IsNil() { + return nil + } + if len(vote.ExtensionSignature) > 0 { + return nil + } + return ErrVoteExtensionAbsent +} + // ToProto converts the handwritten type to proto generated type // return type, nil if everything converts safely, otherwise nil, error func (vote *Vote) ToProto() *tmproto.Vote { @@ -209,14 +352,16 @@ func (vote *Vote) ToProto() *tmproto.Vote { } return &tmproto.Vote{ - Type: vote.Type, - Height: vote.Height, - Round: vote.Round, - BlockID: vote.BlockID.ToProto(), - Timestamp: vote.Timestamp, - ValidatorAddress: vote.ValidatorAddress, - ValidatorIndex: vote.ValidatorIndex, - Signature: vote.Signature, + Type: vote.Type, + Height: vote.Height, + Round: vote.Round, + BlockID: vote.BlockID.ToProto(), + Timestamp: vote.Timestamp, + ValidatorAddress: vote.ValidatorAddress, + ValidatorIndex: vote.ValidatorIndex, + Signature: vote.Signature, + Extension: vote.Extension, + ExtensionSignature: vote.ExtensionSignature, } } @@ -248,15 +393,17 @@ func VoteFromProto(pv *tmproto.Vote) (*Vote, error) { return nil, err } - vote := new(Vote) - vote.Type = pv.Type - vote.Height = pv.Height - vote.Round = pv.Round - vote.BlockID = *blockID - vote.Timestamp = pv.Timestamp - vote.ValidatorAddress = pv.ValidatorAddress - vote.ValidatorIndex = pv.ValidatorIndex - vote.Signature = pv.Signature - + vote := &Vote{ + Type: pv.Type, + Height: pv.Height, + Round: pv.Round, + BlockID: *blockID, + Timestamp: pv.Timestamp, + ValidatorAddress: pv.ValidatorAddress, + ValidatorIndex: pv.ValidatorIndex, + Signature: pv.Signature, + Extension: pv.Extension, + ExtensionSignature: pv.ExtensionSignature, + } return vote, vote.ValidateBasic() } diff --git a/types/vote_set.go b/types/vote_set.go index 2fec82348..0135e89d4 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -59,11 +59,12 @@ there's only a limited number of peers. NOTE: Assumes that the sum total of voting power does not exceed MaxUInt64. */ type VoteSet struct { - chainID string - height int64 - round int32 - signedMsgType tmproto.SignedMsgType - valSet *ValidatorSet + chainID string + height int64 + round int32 + signedMsgType tmproto.SignedMsgType + valSet *ValidatorSet + extensionsEnabled bool mtx tmsync.Mutex votesBitArray *bits.BitArray @@ -95,6 +96,16 @@ func NewVoteSet(chainID string, height int64, round int32, } } +// NewExtendedVoteSet constructs a vote set with additional vote verification logic. +// The VoteSet constructed with NewExtendedVoteSet verifies the vote extension +// data for every vote added to the set. +func NewExtendedVoteSet(chainID string, height int64, round int32, + signedMsgType tmproto.SignedMsgType, valSet *ValidatorSet) *VoteSet { + vs := NewVoteSet(chainID, height, round, signedMsgType, valSet) + vs.extensionsEnabled = true + return vs +} + func (voteSet *VoteSet) ChainID() string { return voteSet.chainID } @@ -611,36 +622,41 @@ func (voteSet *VoteSet) sumTotalFrac() (int64, int64, float64) { //-------------------------------------------------------------------------------- // Commit -// MakeCommit constructs a Commit from the VoteSet. It only includes precommits -// for the block, which has 2/3+ majority, and nil. +// MakeExtendedCommit constructs a Commit from the VoteSet. It only includes +// precommits for the block, which has 2/3+ majority, and nil. // // Panics if the vote type is not PrecommitType or if there's no +2/3 votes for // a single block. -func (voteSet *VoteSet) MakeCommit() *Commit { +func (voteSet *VoteSet) MakeExtendedCommit() *ExtendedCommit { if voteSet.signedMsgType != tmproto.PrecommitType { - panic("Cannot MakeCommit() unless VoteSet.Type is PrecommitType") + panic("Cannot MakeExtendCommit() unless VoteSet.Type is PrecommitType") } voteSet.mtx.Lock() defer voteSet.mtx.Unlock() // Make sure we have a 2/3 majority if voteSet.maj23 == nil { - panic("Cannot MakeCommit() unless a blockhash has +2/3") + panic("Cannot MakeExtendCommit() unless a blockhash has +2/3") } - // For every validator, get the precommit - commitSigs := make([]CommitSig, len(voteSet.votes)) + // For every validator, get the precommit with extensions + sigs := make([]ExtendedCommitSig, len(voteSet.votes)) for i, v := range voteSet.votes { - commitSig := v.CommitSig() + sig := v.ExtendedCommitSig() // if block ID exists but doesn't match, exclude sig - if commitSig.ForBlock() && !v.BlockID.Equals(*voteSet.maj23) { - commitSig = NewCommitSigAbsent() + if sig.BlockIDFlag == BlockIDFlagCommit && !v.BlockID.Equals(*voteSet.maj23) { + sig = NewExtendedCommitSigAbsent() } - commitSigs[i] = commitSig + sigs[i] = sig } - return NewCommit(voteSet.GetHeight(), voteSet.GetRound(), *voteSet.maj23, commitSigs) + return &ExtendedCommit{ + Height: voteSet.GetHeight(), + Round: voteSet.GetRound(), + BlockID: *voteSet.maj23, + ExtendedSignatures: sigs, + } } //-------------------------------------------------------------------------------- diff --git a/types/vote_set_test.go b/types/vote_set_test.go index 4899b04b2..c23c5092a 100644 --- a/types/vote_set_test.go +++ b/types/vote_set_test.go @@ -25,7 +25,7 @@ func TestVoteSet_AddVote_Good(t *testing.T) { assert.Nil(t, voteSet.GetByAddress(val0Addr)) assert.False(t, voteSet.BitArray().GetIndex(0)) blockID, ok := voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority") + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority") vote := &Vote{ ValidatorAddress: val0Addr, @@ -42,7 +42,7 @@ func TestVoteSet_AddVote_Good(t *testing.T) { assert.NotNil(t, voteSet.GetByAddress(val0Addr)) assert.True(t, voteSet.BitArray().GetIndex(0)) blockID, ok = voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority") + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority") } func TestVoteSet_AddVote_Bad(t *testing.T) { @@ -143,7 +143,7 @@ func TestVoteSet_2_3Majority(t *testing.T) { require.NoError(t, err) } blockID, ok := voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority") + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority") // 7th validator voted for some blockhash { @@ -154,7 +154,7 @@ func TestVoteSet_2_3Majority(t *testing.T) { _, err = signAddVote(privValidators[6], withBlockHash(vote, tmrand.Bytes(32)), voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority") + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority") } // 8th validator voted for nil. @@ -166,7 +166,7 @@ func TestVoteSet_2_3Majority(t *testing.T) { _, err = signAddVote(privValidators[7], vote, voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() - assert.True(t, ok || blockID.IsZero(), "there should be 2/3 majority for nil") + assert.True(t, ok || blockID.IsNil(), "there should be 2/3 majority for nil") } } @@ -198,7 +198,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { require.NoError(t, err) } blockID, ok := voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority") // 67th validator voted for nil @@ -210,7 +210,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { _, err = signAddVote(privValidators[66], withBlockHash(vote, nil), voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority: last vote added was nil") } @@ -224,7 +224,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { _, err = signAddVote(privValidators[67], withBlockPartSetHeader(vote, blockPartsHeader), voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority: last vote added had different PartSetHeader Hash") } @@ -238,7 +238,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { _, err = signAddVote(privValidators[68], withBlockPartSetHeader(vote, blockPartsHeader), voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority: last vote added had different PartSetHeader Total") } @@ -251,7 +251,7 @@ func TestVoteSet_2_3MajorityRedux(t *testing.T) { _, err = signAddVote(privValidators[69], withBlockHash(vote, tmrand.Bytes(32)), voteSet) require.NoError(t, err) blockID, ok = voteSet.TwoThirdsMajority() - assert.False(t, ok || !blockID.IsZero(), + assert.False(t, ok || !blockID.IsNil(), "there should be no 2/3 majority: last vote added had different BlockHash") } @@ -426,7 +426,7 @@ func TestVoteSet_MakeCommit(t *testing.T) { } // MakeCommit should fail. - assert.Panics(t, func() { voteSet.MakeCommit() }, "Doesn't have +2/3 majority") + assert.Panics(t, func() { voteSet.MakeExtendedCommit() }, "Doesn't have +2/3 majority") // 7th voted for some other block. { @@ -463,13 +463,13 @@ func TestVoteSet_MakeCommit(t *testing.T) { require.NoError(t, err) } - commit := voteSet.MakeCommit() + extCommit := voteSet.MakeExtendedCommit() // Commit should have 10 elements - assert.Equal(t, 10, len(commit.Signatures)) + assert.Equal(t, 10, len(extCommit.ExtendedSignatures)) // Ensure that Commit is good. - if err := commit.ValidateBasic(); err != nil { + if err := extCommit.ValidateBasic(); err != nil { t.Errorf("error in Commit.ValidateBasic(): %v", err) } } diff --git a/types/vote_test.go b/types/vote_test.go index 927b9abc5..aa22e9eb0 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -219,13 +219,13 @@ func TestVoteVerify(t *testing.T) { func TestVoteString(t *testing.T) { str := examplePrecommit().String() - expected := `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PRECOMMIT(Precommit) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests + expected := `Precommit{12345/02 by 56789:6AF1F4111082 for 384230313032333338364333 <000000000000> & 0 <000000000000> @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests if str != expected { t.Errorf("got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str) } str2 := examplePrevote().String() - expected = `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PREVOTE(Prevote) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests + expected = `Prevote{12345/02 by 56789:6AF1F4111082 for 384230313032333338364333 <000000000000> @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests if str2 != expected { t.Errorf("got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str2) } @@ -259,7 +259,11 @@ func TestVoteValidateBasic(t *testing.T) { vote.Signature = v.Signature require.NoError(t, err) tc.malleateVote(vote) - assert.Equal(t, tc.expectErr, vote.ValidateBasic() != nil, "Validate Basic had an unexpected result") + if tc.expectErr { + require.Error(t, vote.ValidateBasic()) + } else { + require.NoError(t, vote.ValidateBasic()) + } }) } }