From 7939d62ef0706ea0affac1b5e3a5c4ee90c412be Mon Sep 17 00:00:00 2001 From: Emmanuel Odeke Date: Fri, 22 Sep 2017 18:17:21 -0600 Subject: [PATCH 1/6] all, state: unexpose GenesisDoc, ChainID fields make them accessor methods Fixes #671 Unexpose GenesisDoc and ChainID fields to avoid them being serialized to the DB on every block write/state.Save() A GenesisDoc can now be alternatively written to the state's database, by serializing its JSON as a value of key "genesis-doc". There are now accessors and a setter for these attributes: - state.GenesisDoc() (*types.GenesisDoc, error) - state.ChainID() (string, error) - state.SetGenesisDoc(*types.GenesisDoc) This is a breaking change since it changes how the state's serialization and requires that if loading the GenesisDoc entirely from the database, you'll need to set its value in the database as the GenesisDoc's JSON marshaled bytes. --- blockchain/reactor.go | 10 +++- blockchain/store.go | 4 +- consensus/state.go | 40 ++++++++++++--- node/node.go | 12 ++++- state/execution.go | 8 ++- state/state.go | 73 +++++++++++++++++++++++----- state/state_test.go | 110 +++++++++++++++++++++++++++++++++++++++++- 7 files changed, 229 insertions(+), 28 deletions(-) diff --git a/blockchain/reactor.go b/blockchain/reactor.go index 91f0adede..3001206f7 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -242,8 +242,14 @@ FOR_LOOP: // NOTE: we can probably make this more efficient, but note that calling // first.Hash() doesn't verify the tx contents, so MakePartSet() is // currently necessary. - err := bcR.state.Validators.VerifyCommit( - bcR.state.ChainID, types.BlockID{first.Hash(), firstPartsHeader}, first.Height, second.LastCommit) + chainID, err := bcR.state.ChainID() + if err != nil { + bcR.Logger.Info("error in retrieving chainID", "err", err) + bcR.pool.RedoRequest(first.Height) + break SYNC_LOOP + } + err = bcR.state.Validators.VerifyCommit( + chainID, types.BlockID{first.Hash(), firstPartsHeader}, first.Height, second.LastCommit) if err != nil { bcR.Logger.Error("Error in validation", "err", err) bcR.pool.RedoRequest(first.Height) diff --git a/blockchain/store.go b/blockchain/store.go index a96aa0fb7..a37fda930 100644 --- a/blockchain/store.go +++ b/blockchain/store.go @@ -7,10 +7,10 @@ import ( "io" "sync" - . "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/go-wire" "github.com/tendermint/tendermint/types" + . "github.com/tendermint/tmlibs/common" + dbm "github.com/tendermint/tmlibs/db" ) /* diff --git a/consensus/state.go b/consensus/state.go index f0fbad811..ac457f490 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -389,8 +389,12 @@ func (cs *ConsensusState) reconstructLastCommit(state *sm.State) { if state.LastBlockHeight == 0 { return } + chainID, err := cs.state.ChainID() + if err != nil { + cmn.PanicCrisis(cmn.Fmt("Failed to retrieve ChainID: %v", err)) + } seenCommit := cs.blockStore.LoadSeenCommit(state.LastBlockHeight) - lastPrecommits := types.NewVoteSet(cs.state.ChainID, state.LastBlockHeight, seenCommit.Round(), types.VoteTypePrecommit, state.LastValidators) + lastPrecommits := types.NewVoteSet(chainID, state.LastBlockHeight, seenCommit.Round(), types.VoteTypePrecommit, state.LastValidators) for _, precommit := range seenCommit.Precommits { if precommit == nil { continue @@ -720,7 +724,11 @@ func (cs *ConsensusState) proposalHeartbeat(height, round int) { ValidatorAddress: addr, ValidatorIndex: valIndex, } - cs.privValidator.SignHeartbeat(cs.state.ChainID, heartbeat) + chainID, err := cs.state.ChainID() + if err != nil { + return + } + cs.privValidator.SignHeartbeat(chainID, heartbeat) heartbeatEvent := types.EventDataProposalHeartbeat{heartbeat} types.FireEventProposalHeartbeat(cs.evsw, heartbeatEvent) counter += 1 @@ -797,8 +805,11 @@ func (cs *ConsensusState) defaultDecideProposal(height, round int) { // Make proposal polRound, polBlockID := cs.Votes.POLInfo() proposal := types.NewProposal(height, round, blockParts.Header(), polRound, polBlockID) - err := cs.privValidator.SignProposal(cs.state.ChainID, proposal) - if err == nil { + chainID, err := cs.state.ChainID() + if err != nil { + return + } + if err := cs.privValidator.SignProposal(chainID, proposal); err == nil { // Set fields /* fields set by setProposal and addBlockPart cs.Proposal = proposal @@ -857,8 +868,12 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts // Mempool validated transactions txs := cs.mempool.Reap(cs.config.MaxBlockSizeTxs) - - return types.MakeBlock(cs.Height, cs.state.ChainID, txs, commit, + chainID, err := cs.state.ChainID() + if err != nil { + cs.Logger.Error("chainID err", err) + return + } + return types.MakeBlock(cs.Height, chainID, txs, commit, cs.state.LastBlockID, cs.state.Validators.Hash(), cs.state.AppHash, cs.state.Params().BlockPartSizeBytes) } @@ -1263,8 +1278,13 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error { return ErrInvalidProposalPOLRound } + chainID, err := cs.state.ChainID() + if err != nil { + return err + } + // Verify signature - if !cs.Validators.GetProposer().PubKey.VerifyBytes(types.SignBytes(cs.state.ChainID, proposal), proposal.Signature) { + if !cs.Validators.GetProposer().PubKey.VerifyBytes(types.SignBytes(chainID, proposal), proposal.Signature) { return ErrInvalidProposalSignature } @@ -1449,6 +1469,10 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool, } func (cs *ConsensusState) signVote(type_ byte, hash []byte, header types.PartSetHeader) (*types.Vote, error) { + chainID, err := cs.state.ChainID() + if err != nil { + return nil, err + } addr := cs.privValidator.GetAddress() valIndex, _ := cs.Validators.GetByAddress(addr) vote := &types.Vote{ @@ -1459,7 +1483,7 @@ func (cs *ConsensusState) signVote(type_ byte, hash []byte, header types.PartSet Type: type_, BlockID: types.BlockID{hash, header}, } - err := cs.privValidator.SignVote(cs.state.ChainID, vote) + err = cs.privValidator.SignVote(chainID, vote) return vote, err } diff --git a/node/node.go b/node/node.go index 824a0926c..9a49cd596 100644 --- a/node/node.go +++ b/node/node.go @@ -146,6 +146,10 @@ func NewNode(config *cfg.Config, } state.SetLogger(stateLogger) + genesisDoc, err := state.GenesisDoc() + if err != nil { + return nil, err + } // Create the proxyApp, which manages connections (consensus, mempool, query) // and sync tendermint and the app by replaying any necessary blocks @@ -286,7 +290,7 @@ func NewNode(config *cfg.Config, node := &Node{ config: config, - genesisDoc: state.GenesisDoc, + genesisDoc: genesisDoc, privValidator: privValidator, privKey: privKey, @@ -485,11 +489,15 @@ func (n *Node) makeNodeInfo() *p2p.NodeInfo { if _, ok := n.txIndexer.(*null.TxIndex); ok { txIndexerStatus = "off" } + chainID, err := n.consensusState.GetState().ChainID() + if err != nil { + cmn.PanicSanity(cmn.Fmt("failed ot get chainID: %v", err)) + } nodeInfo := &p2p.NodeInfo{ PubKey: n.privKey.PubKey().Unwrap().(crypto.PubKeyEd25519), Moniker: n.config.Moniker, - Network: n.consensusState.GetState().ChainID, + Network: chainID, Version: version.Version, Other: []string{ cmn.Fmt("wire_version=%v", wire.Version), diff --git a/state/execution.go b/state/execution.go index b917bfbe8..e4992565d 100644 --- a/state/execution.go +++ b/state/execution.go @@ -180,7 +180,11 @@ func (s *State) ValidateBlock(block *types.Block) error { func (s *State) validateBlock(block *types.Block) error { // Basic block validation. - err := block.ValidateBasic(s.ChainID, s.LastBlockHeight, s.LastBlockID, s.LastBlockTime, s.AppHash) + chainID, err := s.ChainID() + if err != nil { + return err + } + err = block.ValidateBasic(chainID, s.LastBlockHeight, s.LastBlockID, s.LastBlockTime, s.AppHash) if err != nil { return err } @@ -196,7 +200,7 @@ func (s *State) validateBlock(block *types.Block) error { s.LastValidators.Size(), len(block.LastCommit.Precommits))) } err := s.LastValidators.VerifyCommit( - s.ChainID, s.LastBlockID, block.Height-1, block.LastCommit) + chainID, s.LastBlockID, block.Height-1, block.LastCommit) if err != nil { return err } diff --git a/state/state.go b/state/state.go index 53ec8cc03..baf3b4616 100644 --- a/state/state.go +++ b/state/state.go @@ -2,6 +2,7 @@ package state import ( "bytes" + "errors" "fmt" "io/ioutil" "sync" @@ -38,9 +39,11 @@ type State struct { mtx sync.Mutex db dbm.DB - // should not change - GenesisDoc *types.GenesisDoc - ChainID string + // genesisDoc is the memoized genesisDoc to cut down + // the number of unnecessary DB lookups since we no longer + // directly serialize the GenesisDoc in state. + // See https://github.com/tendermint/tendermint/issues/671. + genesisDoc *types.GenesisDoc // These fields are updated by SetBlockAndValidators. // LastBlockHeight=0 at genesis (ie. block(H=0) does not exist) @@ -100,7 +103,6 @@ func loadState(db dbm.DB, key []byte) *State { } // TODO: ensure that buf is completely read. } - return s } @@ -113,8 +115,6 @@ func (s *State) SetLogger(l log.Logger) { func (s *State) Copy() *State { return &State{ db: s.db, - GenesisDoc: s.GenesisDoc, - ChainID: s.ChainID, LastBlockHeight: s.LastBlockHeight, LastBlockID: s.LastBlockID, LastBlockTime: s.LastBlockTime, @@ -127,12 +127,57 @@ func (s *State) Copy() *State { } } +var ( + errNilGenesisDoc = errors.New("no genesisDoc was found") + + genesisDBKey = []byte("genesis-doc") +) + +// GenesisDoc is the accessor to retrieve the genesisDoc associated +// with a state. If the state has no set GenesisDoc, it fetches from +// its database the JSON marshaled bytes keyed by "genesis-doc", and +// parses the GenesisDoc from that memoizing it for later use. +// If you'd like to change the value of the GenesisDoc, invoke SetGenesisDoc. +func (s *State) GenesisDoc() (*types.GenesisDoc, error) { + s.mtx.Lock() + defer s.mtx.Unlock() + + if s.genesisDoc == nil { + retrGenesisDocBytes := s.db.Get(genesisDBKey) + if len(retrGenesisDocBytes) == 0 { + return nil, errNilGenesisDoc + } + genDoc, err := types.GenesisDocFromJSON(retrGenesisDocBytes) + if err != nil { + return nil, err + } + s.genesisDoc = genDoc + } + return s.genesisDoc, nil +} + +func (s *State) ChainID() (string, error) { + genDoc, err := s.GenesisDoc() + if err != nil { + return "", err + } + return genDoc.ChainID, nil +} + // Save persists the State to the database. func (s *State) Save() { s.mtx.Lock() - defer s.mtx.Unlock() s.saveValidatorsInfo() s.db.SetSync(stateKey, s.Bytes()) + s.mtx.Unlock() +} + +// SetGenesisDoc sets the internal genesisDoc, but doesn't +// serialize it to the database, until Save is invoked. +func (s *State) SetGenesisDoc(genDoc *types.GenesisDoc) { + s.mtx.Lock() + s.genesisDoc = genDoc + s.mtx.Unlock() } // SaveABCIResponses persists the ABCIResponses to the database. @@ -264,12 +309,18 @@ func (s *State) GetValidators() (*types.ValidatorSet, *types.ValidatorSet) { return s.LastValidators, s.Validators } +var blankConsensusParams = types.ConsensusParams{} + // Params returns the consensus parameters used for // validating blocks func (s *State) Params() types.ConsensusParams { // TODO: this should move into the State proper // when we allow the app to change it - return *s.GenesisDoc.ConsensusParams + genDoc, err := s.GenesisDoc() + if err != nil || genDoc == nil { + return blankConsensusParams + } + return *genDoc.ConsensusParams } //------------------------------------------------------------------------ @@ -364,9 +415,9 @@ func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) (*State, error) { } return &State{ - db: db, - GenesisDoc: genDoc, - ChainID: genDoc.ChainID, + db: db, + + genesisDoc: genDoc, LastBlockHeight: 0, LastBlockID: types.BlockID{}, LastBlockTime: genDoc.GenesisTime, diff --git a/state/state_test.go b/state/state_test.go index 2ab2e9349..c36050542 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -2,16 +2,21 @@ package state import ( "bytes" + "encoding/json" "fmt" "testing" + "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/types" abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" + + "github.com/tendermint/go-wire/data" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -127,7 +132,9 @@ func TestValidatorChangesSaveLoad(t *testing.T) { // each valset is just one validator. // create list of them pubkeys := make([]crypto.PubKey, N+1) - pubkeys[0] = state.GenesisDoc.Validators[0].PubKey + genDoc, err := state.GenesisDoc() + assert.Nil(err, "want successful genDoc retrieval") + pubkeys[0] = genDoc.Validators[0].PubKey for i := 1; i < N+1; i++ { pubkeys[i] = crypto.GenPrivKeyEd25519().PubKey() } @@ -199,3 +206,104 @@ type valChangeTestCase struct { height int vals crypto.PubKey } + +var ( + aPrivKey = crypto.GenPrivKeyEd25519() + _1stGenesisDoc = &types.GenesisDoc{ + GenesisTime: time.Now().Add(-60 * time.Minute).Round(time.Second), + ChainID: "tendermint_state_test", + AppHash: data.Bytes{}, + ConsensusParams: &types.ConsensusParams{ + BlockSizeParams: types.BlockSizeParams{ + MaxBytes: 100, + MaxGas: 2000, + MaxTxs: 56, + }, + + BlockGossipParams: types.BlockGossipParams{ + BlockPartSizeBytes: 65336, + }, + }, + Validators: []types.GenesisValidator{ + {PubKey: aPrivKey.PubKey(), Power: 10000, Name: "TendermintFoo"}, + }, + } + _2ndGenesisDoc = func() *types.GenesisDoc { + copy := new(types.GenesisDoc) + *copy = *_1stGenesisDoc + copy.GenesisTime = time.Now().Round(time.Second) + return copy + }() +) + +// See Issue https://github.com/tendermint/tendermint/issues/671. +func TestGenesisDocAndChainIDAccessorsAndSetter(t *testing.T) { + tearDown, dbm, state := setupTestCase(t) + defer tearDown(t) + require := require.New(t) + + // Fire up the initial genesisDoc + _, err := state.GenesisDoc() + require.Nil(err, "expecting no error on first load of genesisDoc") + + // By contract, state doesn't expose the dbm, however we need to change + // it to test out that the respective chainID and genesisDoc will be changed + state.cachedGenesisDoc = nil + _1stBlob, err := json.Marshal(_1stGenesisDoc) + require.Nil(err, "expecting no error serializing _1stGenesisDoc") + dbm.Set(genesisDBKey, _1stBlob) + + retrGenDoc, err := state.GenesisDoc() + require.Nil(err, "unexpected error") + require.Equal(retrGenDoc, _1stGenesisDoc, "expecting the newly set-in-Db genesis doc") + chainID, err := state.ChainID() + require.Nil(err, "unexpected error") + require.Equal(chainID, _1stGenesisDoc.ChainID, "expecting the chainIDs to be equal") + + require.NotNil(state.cachedGenesisDoc, "after retrieval expecting a non-nil cachedGenesisDoc") + // Save should not discard the previous cachedGenesisDoc + // which was the point of filing https://github.com/tendermint/tendermint/issues/671. + state.Save() + require.NotNil(state.cachedGenesisDoc, "even after flush with .Save(), expecting a non-nil cachedGenesisDoc") + + // Now change up the data but ensure + // that a Save discards the old validator + _2ndBlob, err := json.Marshal(_2ndGenesisDoc) + require.Nil(err, "unexpected error") + dbm.Set(genesisDBKey, _2ndBlob) + + refreshGenDoc, err := state.GenesisDoc() + require.Nil(err, "unexpected error") + require.Equal(refreshGenDoc, _1stGenesisDoc, "despite setting the new genesisDoc in DB, it shouldn't affect the one in state") + state.SetGenesisDoc(_2ndGenesisDoc) + + refreshGenDoc, err = state.GenesisDoc() + require.Nil(err, "unexpected error") + require.Equal(refreshGenDoc, _2ndGenesisDoc, "expecting the newly set-in-Db genesis doc to have been reloaded after a .Save()") + + // Test that .Save() should never overwrite the currently set content in the DB + dbm.Set(genesisDBKey, _1stBlob) + state.Save() + require.Equal(dbm.Get(genesisDBKey), _1stBlob, ".Save() should NEVER serialize back the current genesisDoc") + + // ChainID on a nil cachedGenesisDoc should do a DB fetch + state.SetGenesisDoc(nil) + dbm.Set(genesisDBKey, _2ndBlob) + chainID, err = state.ChainID() + require.Nil(err, "unexpected error") + require.Equal(chainID, _2ndGenesisDoc.ChainID, "expecting the 2ndGenesisDoc.ChainID") + + // Now test what happens if we cannot find the genesis doc in the DB + // Checkpoint and discard + state.Save() + dbm.Set(genesisDBKey, nil) + state.SetGenesisDoc(nil) + gotGenDoc, err := state.GenesisDoc() + require.NotNil(err, "could not parse out a genesisDoc from the DB") + require.Nil(gotGenDoc, "since we couldn't parse the genesis doc, expecting a nil genesis doc") + + dbm.Set(genesisDBKey, []byte(`{}`)) + gotGenDoc, err = state.GenesisDoc() + require.NotNil(err, "despite {}, that's not a valid serialization for a genesisDoc") + require.Nil(gotGenDoc, "since we couldn't parse the genesis doc, expecting a nil genesis doc") +} From 1971e149fb42e48027e30ec5cf31356b707ac7cb Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 5 Oct 2017 16:50:05 +0400 Subject: [PATCH 2/6] ChainID() and Params() do not return errors - remove state#GenesisDoc() method --- blockchain/reactor.go | 10 +-- blockchain/store.go | 2 +- consensus/byzantine_test.go | 4 +- consensus/replay_test.go | 4 +- consensus/state.go | 37 ++--------- node/node.go | 29 ++++----- state/execution.go | 7 +- state/state.go | 100 +++++++++++----------------- state/state_test.go | 126 ++++++------------------------------ 9 files changed, 84 insertions(+), 235 deletions(-) diff --git a/blockchain/reactor.go b/blockchain/reactor.go index 3001206f7..6fba874b0 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -187,6 +187,8 @@ func (bcR *BlockchainReactor) poolRoutine() { statusUpdateTicker := time.NewTicker(statusUpdateIntervalSeconds * time.Second) switchToConsensusTicker := time.NewTicker(switchToConsensusIntervalSeconds * time.Second) + chainID := bcR.state.ChainID() + FOR_LOOP: for { select { @@ -242,13 +244,7 @@ FOR_LOOP: // NOTE: we can probably make this more efficient, but note that calling // first.Hash() doesn't verify the tx contents, so MakePartSet() is // currently necessary. - chainID, err := bcR.state.ChainID() - if err != nil { - bcR.Logger.Info("error in retrieving chainID", "err", err) - bcR.pool.RedoRequest(first.Height) - break SYNC_LOOP - } - err = bcR.state.Validators.VerifyCommit( + err := bcR.state.Validators.VerifyCommit( chainID, types.BlockID{first.Hash(), firstPartsHeader}, first.Height, second.LastCommit) if err != nil { bcR.Logger.Error("Error in validation", "err", err) diff --git a/blockchain/store.go b/blockchain/store.go index a37fda930..79edfeaf5 100644 --- a/blockchain/store.go +++ b/blockchain/store.go @@ -7,7 +7,7 @@ import ( "io" "sync" - "github.com/tendermint/go-wire" + wire "github.com/tendermint/go-wire" "github.com/tendermint/tendermint/types" . "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index 38dff4064..91a97c601 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -167,13 +167,13 @@ func byzantineDecideProposalFunc(t *testing.T, height, round int, cs *ConsensusS block1, blockParts1 := cs.createProposalBlock() polRound, polBlockID := cs.Votes.POLInfo() proposal1 := types.NewProposal(height, round, blockParts1.Header(), polRound, polBlockID) - cs.privValidator.SignProposal(cs.state.ChainID, proposal1) // byzantine doesnt err + cs.privValidator.SignProposal(cs.state.ChainID(), proposal1) // byzantine doesnt err // Create a new proposal block from state/txs from the mempool. block2, blockParts2 := cs.createProposalBlock() polRound, polBlockID = cs.Votes.POLInfo() proposal2 := types.NewProposal(height, round, blockParts2.Header(), polRound, polBlockID) - cs.privValidator.SignProposal(cs.state.ChainID, proposal2) // byzantine doesnt err + cs.privValidator.SignProposal(cs.state.ChainID(), proposal2) // byzantine doesnt err block1Hash := block1.Hash() block2Hash := block2.Hash() diff --git a/consensus/replay_test.go b/consensus/replay_test.go index c478a0958..6e63024aa 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -231,7 +231,7 @@ func TestWALCrashBeforeWritePropose(t *testing.T) { msg := readTimedWALMessage(t, proposalMsg) proposal := msg.Msg.(msgInfo).Msg.(*ProposalMessage) // Set LastSig - toPV(cs.privValidator).LastSignBytes = types.SignBytes(cs.state.ChainID, proposal.Proposal) + toPV(cs.privValidator).LastSignBytes = types.SignBytes(cs.state.ChainID(), proposal.Proposal) toPV(cs.privValidator).LastSignature = proposal.Proposal.Signature runReplayTest(t, cs, walFile, newBlockCh, thisCase, lineNum) } @@ -256,7 +256,7 @@ func testReplayCrashBeforeWriteVote(t *testing.T, thisCase *testCase, lineNum in msg := readTimedWALMessage(t, voteMsg) vote := msg.Msg.(msgInfo).Msg.(*VoteMessage) // Set LastSig - toPV(cs.privValidator).LastSignBytes = types.SignBytes(cs.state.ChainID, vote.Vote) + toPV(cs.privValidator).LastSignBytes = types.SignBytes(cs.state.ChainID(), vote.Vote) toPV(cs.privValidator).LastSignature = vote.Vote.Signature }) runReplayTest(t, cs, walFile, newBlockCh, thisCase, lineNum) diff --git a/consensus/state.go b/consensus/state.go index ac457f490..2d2a5e3d2 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -389,12 +389,8 @@ func (cs *ConsensusState) reconstructLastCommit(state *sm.State) { if state.LastBlockHeight == 0 { return } - chainID, err := cs.state.ChainID() - if err != nil { - cmn.PanicCrisis(cmn.Fmt("Failed to retrieve ChainID: %v", err)) - } seenCommit := cs.blockStore.LoadSeenCommit(state.LastBlockHeight) - lastPrecommits := types.NewVoteSet(chainID, state.LastBlockHeight, seenCommit.Round(), types.VoteTypePrecommit, state.LastValidators) + lastPrecommits := types.NewVoteSet(state.ChainID(), state.LastBlockHeight, seenCommit.Round(), types.VoteTypePrecommit, state.LastValidators) for _, precommit := range seenCommit.Precommits { if precommit == nil { continue @@ -711,6 +707,7 @@ func (cs *ConsensusState) proposalHeartbeat(height, round int) { // not a validator valIndex = -1 } + chainID := cs.state.ChainID() for { rs := cs.GetRoundState() // if we've already moved on, no need to send more heartbeats @@ -724,10 +721,6 @@ func (cs *ConsensusState) proposalHeartbeat(height, round int) { ValidatorAddress: addr, ValidatorIndex: valIndex, } - chainID, err := cs.state.ChainID() - if err != nil { - return - } cs.privValidator.SignHeartbeat(chainID, heartbeat) heartbeatEvent := types.EventDataProposalHeartbeat{heartbeat} types.FireEventProposalHeartbeat(cs.evsw, heartbeatEvent) @@ -805,11 +798,7 @@ func (cs *ConsensusState) defaultDecideProposal(height, round int) { // Make proposal polRound, polBlockID := cs.Votes.POLInfo() proposal := types.NewProposal(height, round, blockParts.Header(), polRound, polBlockID) - chainID, err := cs.state.ChainID() - if err != nil { - return - } - if err := cs.privValidator.SignProposal(chainID, proposal); err == nil { + if err := cs.privValidator.SignProposal(cs.state.ChainID(), proposal); err == nil { // Set fields /* fields set by setProposal and addBlockPart cs.Proposal = proposal @@ -868,12 +857,7 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts // Mempool validated transactions txs := cs.mempool.Reap(cs.config.MaxBlockSizeTxs) - chainID, err := cs.state.ChainID() - if err != nil { - cs.Logger.Error("chainID err", err) - return - } - return types.MakeBlock(cs.Height, chainID, txs, commit, + return types.MakeBlock(cs.Height, cs.state.ChainID(), txs, commit, cs.state.LastBlockID, cs.state.Validators.Hash(), cs.state.AppHash, cs.state.Params().BlockPartSizeBytes) } @@ -1278,13 +1262,8 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error { return ErrInvalidProposalPOLRound } - chainID, err := cs.state.ChainID() - if err != nil { - return err - } - // Verify signature - if !cs.Validators.GetProposer().PubKey.VerifyBytes(types.SignBytes(chainID, proposal), proposal.Signature) { + if !cs.Validators.GetProposer().PubKey.VerifyBytes(types.SignBytes(cs.state.ChainID(), proposal), proposal.Signature) { return ErrInvalidProposalSignature } @@ -1469,10 +1448,6 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool, } func (cs *ConsensusState) signVote(type_ byte, hash []byte, header types.PartSetHeader) (*types.Vote, error) { - chainID, err := cs.state.ChainID() - if err != nil { - return nil, err - } addr := cs.privValidator.GetAddress() valIndex, _ := cs.Validators.GetByAddress(addr) vote := &types.Vote{ @@ -1483,7 +1458,7 @@ func (cs *ConsensusState) signVote(type_ byte, hash []byte, header types.PartSet Type: type_, BlockID: types.BlockID{hash, header}, } - err = cs.privValidator.SignVote(chainID, vote) + err := cs.privValidator.SignVote(cs.state.ChainID(), vote) return vote, err } diff --git a/node/node.go b/node/node.go index 9a49cd596..a3267cdda 100644 --- a/node/node.go +++ b/node/node.go @@ -132,24 +132,24 @@ func NewNode(config *cfg.Config, if err != nil { return nil, err } + + genDoc, err := genesisDocProvider() + if err != nil { + return nil, err + } + state := sm.LoadState(stateDB) if state == nil { - genDoc, err := genesisDocProvider() - if err != nil { - return nil, err - } state, err = sm.MakeGenesisState(stateDB, genDoc) if err != nil { return nil, err } state.Save() + } else { + state.SetChainID(genDoc.ChainID) + state.SetParams(genDoc.ConsensusParams) } - state.SetLogger(stateLogger) - genesisDoc, err := state.GenesisDoc() - if err != nil { - return nil, err - } // Create the proxyApp, which manages connections (consensus, mempool, query) // and sync tendermint and the app by replaying any necessary blocks @@ -163,6 +163,8 @@ func NewNode(config *cfg.Config, // reload the state (it may have been updated by the handshake) state = sm.LoadState(stateDB) + state.SetChainID(genDoc.ChainID) + state.SetParams(genDoc.ConsensusParams) state.SetLogger(stateLogger) // Transaction indexing @@ -290,7 +292,7 @@ func NewNode(config *cfg.Config, node := &Node{ config: config, - genesisDoc: genesisDoc, + genesisDoc: genDoc, privValidator: privValidator, privKey: privKey, @@ -489,15 +491,10 @@ func (n *Node) makeNodeInfo() *p2p.NodeInfo { if _, ok := n.txIndexer.(*null.TxIndex); ok { txIndexerStatus = "off" } - chainID, err := n.consensusState.GetState().ChainID() - if err != nil { - cmn.PanicSanity(cmn.Fmt("failed ot get chainID: %v", err)) - } - nodeInfo := &p2p.NodeInfo{ PubKey: n.privKey.PubKey().Unwrap().(crypto.PubKeyEd25519), Moniker: n.config.Moniker, - Network: chainID, + Network: n.genesisDoc.ChainID, Version: version.Version, Other: []string{ cmn.Fmt("wire_version=%v", wire.Version), diff --git a/state/execution.go b/state/execution.go index e4992565d..603eab71b 100644 --- a/state/execution.go +++ b/state/execution.go @@ -179,12 +179,9 @@ func (s *State) ValidateBlock(block *types.Block) error { } func (s *State) validateBlock(block *types.Block) error { + chainID := s.ChainID() // Basic block validation. - chainID, err := s.ChainID() - if err != nil { - return err - } - err = block.ValidateBasic(chainID, s.LastBlockHeight, s.LastBlockID, s.LastBlockTime, s.AppHash) + err := block.ValidateBasic(chainID, s.LastBlockHeight, s.LastBlockID, s.LastBlockTime, s.AppHash) if err != nil { return err } diff --git a/state/state.go b/state/state.go index baf3b4616..6460f201d 100644 --- a/state/state.go +++ b/state/state.go @@ -2,7 +2,6 @@ package state import ( "bytes" - "errors" "fmt" "io/ioutil" "sync" @@ -39,11 +38,10 @@ type State struct { mtx sync.Mutex db dbm.DB - // genesisDoc is the memoized genesisDoc to cut down - // the number of unnecessary DB lookups since we no longer - // directly serialize the GenesisDoc in state. // See https://github.com/tendermint/tendermint/issues/671. - genesisDoc *types.GenesisDoc + chainID string + + params *types.ConsensusParams // These fields are updated by SetBlockAndValidators. // LastBlockHeight=0 at genesis (ie. block(H=0) does not exist) @@ -73,14 +71,22 @@ type State struct { // to the database. func GetState(stateDB dbm.DB, genesisFile string) (*State, error) { var err error + genDoc, err := MakeGenesisDocFromFile(genesisFile) + if err != nil { + return nil, err + } state := LoadState(stateDB) if state == nil { - state, err = MakeGenesisStateFromFile(stateDB, genesisFile) + state, err = MakeGenesisState(stateDB, genDoc) if err != nil { return nil, err } state.Save() + } else { + state.SetChainID(genDoc.ChainID) + state.SetParams(genDoc.ConsensusParams) } + return state, nil } @@ -113,6 +119,7 @@ func (s *State) SetLogger(l log.Logger) { // Copy makes a copy of the State for mutating. func (s *State) Copy() *State { + paramsCopy := *s.params return &State{ db: s.db, LastBlockHeight: s.LastBlockHeight, @@ -123,45 +130,10 @@ func (s *State) Copy() *State { AppHash: s.AppHash, TxIndexer: s.TxIndexer, // pointer here, not value LastHeightValidatorsChanged: s.LastHeightValidatorsChanged, - logger: s.logger, - } -} - -var ( - errNilGenesisDoc = errors.New("no genesisDoc was found") - - genesisDBKey = []byte("genesis-doc") -) - -// GenesisDoc is the accessor to retrieve the genesisDoc associated -// with a state. If the state has no set GenesisDoc, it fetches from -// its database the JSON marshaled bytes keyed by "genesis-doc", and -// parses the GenesisDoc from that memoizing it for later use. -// If you'd like to change the value of the GenesisDoc, invoke SetGenesisDoc. -func (s *State) GenesisDoc() (*types.GenesisDoc, error) { - s.mtx.Lock() - defer s.mtx.Unlock() - - if s.genesisDoc == nil { - retrGenesisDocBytes := s.db.Get(genesisDBKey) - if len(retrGenesisDocBytes) == 0 { - return nil, errNilGenesisDoc - } - genDoc, err := types.GenesisDocFromJSON(retrGenesisDocBytes) - if err != nil { - return nil, err - } - s.genesisDoc = genDoc + logger: s.logger, + chainID: s.chainID, + params: ¶msCopy, } - return s.genesisDoc, nil -} - -func (s *State) ChainID() (string, error) { - genDoc, err := s.GenesisDoc() - if err != nil { - return "", err - } - return genDoc.ChainID, nil } // Save persists the State to the database. @@ -172,14 +144,6 @@ func (s *State) Save() { s.mtx.Unlock() } -// SetGenesisDoc sets the internal genesisDoc, but doesn't -// serialize it to the database, until Save is invoked. -func (s *State) SetGenesisDoc(genDoc *types.GenesisDoc) { - s.mtx.Lock() - s.genesisDoc = genDoc - s.mtx.Unlock() -} - // SaveABCIResponses persists the ABCIResponses to the database. // This is useful in case we crash after app.Commit and before s.Save(). func (s *State) SaveABCIResponses(abciResponses *ABCIResponses) { @@ -213,7 +177,7 @@ func (s *State) LoadValidators(height int) (*types.ValidatorSet, error) { if v.ValidatorSet == nil { v = s.loadValidators(v.LastHeightChanged) if v == nil { - cmn.PanicSanity(fmt.Sprintf(`Couldn't find validators at + cmn.PanicSanity(fmt.Sprintf(`Couldn't find validators at height %d as last changed from height %d`, v.LastHeightChanged, height)) } } @@ -309,18 +273,26 @@ func (s *State) GetValidators() (*types.ValidatorSet, *types.ValidatorSet) { return s.LastValidators, s.Validators } -var blankConsensusParams = types.ConsensusParams{} +// ChainID returns the chain ID. +func (s *State) ChainID() string { + return s.chainID +} + +// SetChainID sets the chain ID. +func (s *State) SetChainID(chainID string) { + s.chainID = chainID +} // Params returns the consensus parameters used for -// validating blocks +// validating blocks. func (s *State) Params() types.ConsensusParams { - // TODO: this should move into the State proper - // when we allow the app to change it - genDoc, err := s.GenesisDoc() - if err != nil || genDoc == nil { - return blankConsensusParams - } - return *genDoc.ConsensusParams + return *s.params +} + +// SetParams sets the consensus parameters used for +// validating blocks. +func (s *State) SetParams(params *types.ConsensusParams) { + s.params = params } //------------------------------------------------------------------------ @@ -417,7 +389,9 @@ func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) (*State, error) { return &State{ db: db, - genesisDoc: genDoc, + chainID: genDoc.ChainID, + params: genDoc.ConsensusParams, + LastBlockHeight: 0, LastBlockID: types.BlockID{}, LastBlockTime: genDoc.GenesisTime, diff --git a/state/state_test.go b/state/state_test.go index c36050542..e0703d3b5 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -2,13 +2,10 @@ package state import ( "bytes" - "encoding/json" "fmt" "testing" - "time" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/types" @@ -16,7 +13,6 @@ import ( abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/go-wire/data" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -120,6 +116,22 @@ func TestValidatorSimpleSaveLoad(t *testing.T) { assert.IsType(ErrNoValSetForHeight{}, err, "expected err at unknown height") } +func TestChainID(t *testing.T) { + tearDown, _, state := setupTestCase(t) + defer tearDown(t) + + state.SetChainID("test") + assert.Equal(t, "test", state.ChainID()) +} + +func TestParams(t *testing.T) { + tearDown, _, state := setupTestCase(t) + defer tearDown(t) + + state.SetParams(&types.ConsensusParams{}) + assert.Equal(t, types.ConsensusParams{}, state.Params()) +} + func TestValidatorChangesSaveLoad(t *testing.T) { tearDown, _, state := setupTestCase(t) defer tearDown(t) @@ -132,9 +144,8 @@ func TestValidatorChangesSaveLoad(t *testing.T) { // each valset is just one validator. // create list of them pubkeys := make([]crypto.PubKey, N+1) - genDoc, err := state.GenesisDoc() - assert.Nil(err, "want successful genDoc retrieval") - pubkeys[0] = genDoc.Validators[0].PubKey + _, val := state.Validators.GetByIndex(0) + pubkeys[0] = val.PubKey for i := 1; i < N+1; i++ { pubkeys[i] = crypto.GenPrivKeyEd25519().PubKey() } @@ -206,104 +217,3 @@ type valChangeTestCase struct { height int vals crypto.PubKey } - -var ( - aPrivKey = crypto.GenPrivKeyEd25519() - _1stGenesisDoc = &types.GenesisDoc{ - GenesisTime: time.Now().Add(-60 * time.Minute).Round(time.Second), - ChainID: "tendermint_state_test", - AppHash: data.Bytes{}, - ConsensusParams: &types.ConsensusParams{ - BlockSizeParams: types.BlockSizeParams{ - MaxBytes: 100, - MaxGas: 2000, - MaxTxs: 56, - }, - - BlockGossipParams: types.BlockGossipParams{ - BlockPartSizeBytes: 65336, - }, - }, - Validators: []types.GenesisValidator{ - {PubKey: aPrivKey.PubKey(), Power: 10000, Name: "TendermintFoo"}, - }, - } - _2ndGenesisDoc = func() *types.GenesisDoc { - copy := new(types.GenesisDoc) - *copy = *_1stGenesisDoc - copy.GenesisTime = time.Now().Round(time.Second) - return copy - }() -) - -// See Issue https://github.com/tendermint/tendermint/issues/671. -func TestGenesisDocAndChainIDAccessorsAndSetter(t *testing.T) { - tearDown, dbm, state := setupTestCase(t) - defer tearDown(t) - require := require.New(t) - - // Fire up the initial genesisDoc - _, err := state.GenesisDoc() - require.Nil(err, "expecting no error on first load of genesisDoc") - - // By contract, state doesn't expose the dbm, however we need to change - // it to test out that the respective chainID and genesisDoc will be changed - state.cachedGenesisDoc = nil - _1stBlob, err := json.Marshal(_1stGenesisDoc) - require.Nil(err, "expecting no error serializing _1stGenesisDoc") - dbm.Set(genesisDBKey, _1stBlob) - - retrGenDoc, err := state.GenesisDoc() - require.Nil(err, "unexpected error") - require.Equal(retrGenDoc, _1stGenesisDoc, "expecting the newly set-in-Db genesis doc") - chainID, err := state.ChainID() - require.Nil(err, "unexpected error") - require.Equal(chainID, _1stGenesisDoc.ChainID, "expecting the chainIDs to be equal") - - require.NotNil(state.cachedGenesisDoc, "after retrieval expecting a non-nil cachedGenesisDoc") - // Save should not discard the previous cachedGenesisDoc - // which was the point of filing https://github.com/tendermint/tendermint/issues/671. - state.Save() - require.NotNil(state.cachedGenesisDoc, "even after flush with .Save(), expecting a non-nil cachedGenesisDoc") - - // Now change up the data but ensure - // that a Save discards the old validator - _2ndBlob, err := json.Marshal(_2ndGenesisDoc) - require.Nil(err, "unexpected error") - dbm.Set(genesisDBKey, _2ndBlob) - - refreshGenDoc, err := state.GenesisDoc() - require.Nil(err, "unexpected error") - require.Equal(refreshGenDoc, _1stGenesisDoc, "despite setting the new genesisDoc in DB, it shouldn't affect the one in state") - state.SetGenesisDoc(_2ndGenesisDoc) - - refreshGenDoc, err = state.GenesisDoc() - require.Nil(err, "unexpected error") - require.Equal(refreshGenDoc, _2ndGenesisDoc, "expecting the newly set-in-Db genesis doc to have been reloaded after a .Save()") - - // Test that .Save() should never overwrite the currently set content in the DB - dbm.Set(genesisDBKey, _1stBlob) - state.Save() - require.Equal(dbm.Get(genesisDBKey), _1stBlob, ".Save() should NEVER serialize back the current genesisDoc") - - // ChainID on a nil cachedGenesisDoc should do a DB fetch - state.SetGenesisDoc(nil) - dbm.Set(genesisDBKey, _2ndBlob) - chainID, err = state.ChainID() - require.Nil(err, "unexpected error") - require.Equal(chainID, _2ndGenesisDoc.ChainID, "expecting the 2ndGenesisDoc.ChainID") - - // Now test what happens if we cannot find the genesis doc in the DB - // Checkpoint and discard - state.Save() - dbm.Set(genesisDBKey, nil) - state.SetGenesisDoc(nil) - gotGenDoc, err := state.GenesisDoc() - require.NotNil(err, "could not parse out a genesisDoc from the DB") - require.Nil(gotGenDoc, "since we couldn't parse the genesis doc, expecting a nil genesis doc") - - dbm.Set(genesisDBKey, []byte(`{}`)) - gotGenDoc, err = state.GenesisDoc() - require.NotNil(err, "despite {}, that's not a valid serialization for a genesisDoc") - require.Nil(gotGenDoc, "since we couldn't parse the genesis doc, expecting a nil genesis doc") -} From 716364182d10519f815726c96550cf96f056c70b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 12 Oct 2017 14:50:09 +0400 Subject: [PATCH 3/6] [state] expose ChainID and Params ``` jaekwon Yeah we should definitely expose ChainID. ConsensusParams is small enough, we can just write it. ``` https://github.com/tendermint/tendermint/pull/676#discussion_r144123203 --- blockchain/reactor.go | 6 ++--- blockchain/reactor_test.go | 4 +-- consensus/byzantine_test.go | 4 +-- consensus/replay_test.go | 8 +++--- consensus/state.go | 16 +++++------ consensus/state_test.go | 14 +++++----- node/node.go | 5 ---- state/execution.go | 5 ++-- state/state.go | 54 +++++++++---------------------------- state/state_test.go | 16 ----------- 10 files changed, 40 insertions(+), 92 deletions(-) diff --git a/blockchain/reactor.go b/blockchain/reactor.go index 6fba874b0..9cc01fbac 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -175,7 +175,7 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) // maxMsgSize returns the maximum allowable size of a // message on the blockchain reactor. func (bcR *BlockchainReactor) maxMsgSize() int { - return bcR.state.Params().BlockSizeParams.MaxBytes + 2 + return bcR.state.Params.BlockSizeParams.MaxBytes + 2 } // Handle messages from the poolReactor telling the reactor what to do. @@ -187,7 +187,7 @@ func (bcR *BlockchainReactor) poolRoutine() { statusUpdateTicker := time.NewTicker(statusUpdateIntervalSeconds * time.Second) switchToConsensusTicker := time.NewTicker(switchToConsensusIntervalSeconds * time.Second) - chainID := bcR.state.ChainID() + chainID := bcR.state.ChainID FOR_LOOP: for { @@ -238,7 +238,7 @@ FOR_LOOP: // We need both to sync the first block. break SYNC_LOOP } - firstParts := first.MakePartSet(bcR.state.Params().BlockPartSizeBytes) + firstParts := first.MakePartSet(bcR.state.Params.BlockPartSizeBytes) firstPartsHeader := firstParts.Header() // Finally, verify the first block using the second's commit // NOTE: we can probably make this more efficient, but note that calling diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index 492ea7a80..633cae169 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -42,7 +42,7 @@ func newBlockchainReactor(logger log.Logger, maxBlockHeight int) *BlockchainReac for blockHeight := 1; blockHeight <= maxBlockHeight; blockHeight++ { firstBlock := makeBlock(blockHeight, state) secondBlock := makeBlock(blockHeight+1, state) - firstParts := firstBlock.MakePartSet(state.Params().BlockGossipParams.BlockPartSizeBytes) + firstParts := firstBlock.MakePartSet(state.Params.BlockGossipParams.BlockPartSizeBytes) blockStore.SaveBlock(firstBlock, firstParts, secondBlock.LastCommit) } @@ -113,7 +113,7 @@ func makeBlock(blockNumber int, state *sm.State) *types.Block { valHash := state.Validators.Hash() prevBlockID := types.BlockID{prevHash, prevParts} block, _ := types.MakeBlock(blockNumber, "test_chain", makeTxs(blockNumber), - new(types.Commit), prevBlockID, valHash, state.AppHash, state.Params().BlockGossipParams.BlockPartSizeBytes) + new(types.Commit), prevBlockID, valHash, state.AppHash, state.Params.BlockGossipParams.BlockPartSizeBytes) return block } diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index 91a97c601..38dff4064 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -167,13 +167,13 @@ func byzantineDecideProposalFunc(t *testing.T, height, round int, cs *ConsensusS block1, blockParts1 := cs.createProposalBlock() polRound, polBlockID := cs.Votes.POLInfo() proposal1 := types.NewProposal(height, round, blockParts1.Header(), polRound, polBlockID) - cs.privValidator.SignProposal(cs.state.ChainID(), proposal1) // byzantine doesnt err + cs.privValidator.SignProposal(cs.state.ChainID, proposal1) // byzantine doesnt err // Create a new proposal block from state/txs from the mempool. block2, blockParts2 := cs.createProposalBlock() polRound, polBlockID = cs.Votes.POLInfo() proposal2 := types.NewProposal(height, round, blockParts2.Header(), polRound, polBlockID) - cs.privValidator.SignProposal(cs.state.ChainID(), proposal2) // byzantine doesnt err + cs.privValidator.SignProposal(cs.state.ChainID, proposal2) // byzantine doesnt err block1Hash := block1.Hash() block2Hash := block2.Hash() diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 6e63024aa..6a6b1bcf3 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -231,7 +231,7 @@ func TestWALCrashBeforeWritePropose(t *testing.T) { msg := readTimedWALMessage(t, proposalMsg) proposal := msg.Msg.(msgInfo).Msg.(*ProposalMessage) // Set LastSig - toPV(cs.privValidator).LastSignBytes = types.SignBytes(cs.state.ChainID(), proposal.Proposal) + toPV(cs.privValidator).LastSignBytes = types.SignBytes(cs.state.ChainID, proposal.Proposal) toPV(cs.privValidator).LastSignature = proposal.Proposal.Signature runReplayTest(t, cs, walFile, newBlockCh, thisCase, lineNum) } @@ -256,7 +256,7 @@ func testReplayCrashBeforeWriteVote(t *testing.T, thisCase *testCase, lineNum in msg := readTimedWALMessage(t, voteMsg) vote := msg.Msg.(msgInfo).Msg.(*VoteMessage) // Set LastSig - toPV(cs.privValidator).LastSignBytes = types.SignBytes(cs.state.ChainID(), vote.Vote) + toPV(cs.privValidator).LastSignBytes = types.SignBytes(cs.state.ChainID, vote.Vote) toPV(cs.privValidator).LastSignature = vote.Vote.Signature }) runReplayTest(t, cs, walFile, newBlockCh, thisCase, lineNum) @@ -382,7 +382,7 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) { } func applyBlock(st *sm.State, blk *types.Block, proxyApp proxy.AppConns) { - testPartSize := st.Params().BlockPartSizeBytes + testPartSize := st.Params.BlockPartSizeBytes err := st.ApplyBlock(nil, proxyApp.Consensus(), blk, blk.MakePartSet(testPartSize).Header(), mempool) if err != nil { panic(err) @@ -562,7 +562,7 @@ func stateAndStore(config *cfg.Config, pubKey crypto.PubKey) (*sm.State, *mockBl state, _ := sm.MakeGenesisStateFromFile(stateDB, config.GenesisFile()) state.SetLogger(log.TestingLogger().With("module", "state")) - store := NewMockBlockStore(config, state.Params()) + store := NewMockBlockStore(config, *state.Params) return state, store } diff --git a/consensus/state.go b/consensus/state.go index 2d2a5e3d2..94ecd2a2c 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -390,7 +390,7 @@ func (cs *ConsensusState) reconstructLastCommit(state *sm.State) { return } seenCommit := cs.blockStore.LoadSeenCommit(state.LastBlockHeight) - lastPrecommits := types.NewVoteSet(state.ChainID(), state.LastBlockHeight, seenCommit.Round(), types.VoteTypePrecommit, state.LastValidators) + lastPrecommits := types.NewVoteSet(state.ChainID, state.LastBlockHeight, seenCommit.Round(), types.VoteTypePrecommit, state.LastValidators) for _, precommit := range seenCommit.Precommits { if precommit == nil { continue @@ -707,7 +707,7 @@ func (cs *ConsensusState) proposalHeartbeat(height, round int) { // not a validator valIndex = -1 } - chainID := cs.state.ChainID() + chainID := cs.state.ChainID for { rs := cs.GetRoundState() // if we've already moved on, no need to send more heartbeats @@ -798,7 +798,7 @@ func (cs *ConsensusState) defaultDecideProposal(height, round int) { // Make proposal polRound, polBlockID := cs.Votes.POLInfo() proposal := types.NewProposal(height, round, blockParts.Header(), polRound, polBlockID) - if err := cs.privValidator.SignProposal(cs.state.ChainID(), proposal); err == nil { + if err := cs.privValidator.SignProposal(cs.state.ChainID, proposal); err == nil { // Set fields /* fields set by setProposal and addBlockPart cs.Proposal = proposal @@ -857,9 +857,9 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts // Mempool validated transactions txs := cs.mempool.Reap(cs.config.MaxBlockSizeTxs) - return types.MakeBlock(cs.Height, cs.state.ChainID(), txs, commit, + return types.MakeBlock(cs.Height, cs.state.ChainID, txs, commit, cs.state.LastBlockID, cs.state.Validators.Hash(), - cs.state.AppHash, cs.state.Params().BlockPartSizeBytes) + cs.state.AppHash, cs.state.Params.BlockPartSizeBytes) } // Enter: `timeoutPropose` after entering Propose. @@ -1263,7 +1263,7 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error { } // Verify signature - if !cs.Validators.GetProposer().PubKey.VerifyBytes(types.SignBytes(cs.state.ChainID(), proposal), proposal.Signature) { + if !cs.Validators.GetProposer().PubKey.VerifyBytes(types.SignBytes(cs.state.ChainID, proposal), proposal.Signature) { return ErrInvalidProposalSignature } @@ -1294,7 +1294,7 @@ func (cs *ConsensusState) addProposalBlockPart(height int, part *types.Part, ver var n int var err error cs.ProposalBlock = wire.ReadBinary(&types.Block{}, cs.ProposalBlockParts.GetReader(), - cs.state.Params().BlockSizeParams.MaxBytes, &n, &err).(*types.Block) + cs.state.Params.BlockSizeParams.MaxBytes, &n, &err).(*types.Block) // NOTE: it's possible to receive complete proposal blocks for future rounds without having the proposal cs.Logger.Info("Received complete proposal block", "height", cs.ProposalBlock.Height, "hash", cs.ProposalBlock.Hash()) if cs.Step == cstypes.RoundStepPropose && cs.isProposalComplete() { @@ -1458,7 +1458,7 @@ func (cs *ConsensusState) signVote(type_ byte, hash []byte, header types.PartSet Type: type_, BlockID: types.BlockID{hash, header}, } - err := cs.privValidator.SignVote(cs.state.ChainID(), vote) + err := cs.privValidator.SignVote(cs.state.ChainID, vote) return vote, err } diff --git a/consensus/state_test.go b/consensus/state_test.go index 69b6d53ce..060e37d4e 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -181,7 +181,7 @@ func TestBadProposal(t *testing.T) { height, round := cs1.Height, cs1.Round vs2 := vss[1] - partSize := cs1.state.Params().BlockPartSizeBytes + partSize := cs1.state.Params.BlockPartSizeBytes proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1) voteCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringVote(), 1) @@ -328,7 +328,7 @@ func TestLockNoPOL(t *testing.T) { vs2 := vss[1] height := cs1.Height - partSize := cs1.state.Params().BlockPartSizeBytes + partSize := cs1.state.Params.BlockPartSizeBytes timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1) timeoutWaitCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutWait(), 1) @@ -494,7 +494,7 @@ func TestLockPOLRelock(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] - partSize := cs1.state.Params().BlockPartSizeBytes + partSize := cs1.state.Params.BlockPartSizeBytes timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1) timeoutWaitCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutWait(), 1) @@ -607,7 +607,7 @@ func TestLockPOLUnlock(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] - partSize := cs1.state.Params().BlockPartSizeBytes + partSize := cs1.state.Params.BlockPartSizeBytes proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1) timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1) @@ -702,7 +702,7 @@ func TestLockPOLSafety1(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] - partSize := cs1.state.Params().BlockPartSizeBytes + partSize := cs1.state.Params.BlockPartSizeBytes proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1) timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1) @@ -823,7 +823,7 @@ func TestLockPOLSafety2(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] - partSize := cs1.state.Params().BlockPartSizeBytes + partSize := cs1.state.Params.BlockPartSizeBytes proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1) timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1) @@ -998,7 +998,7 @@ func TestHalt1(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] - partSize := cs1.state.Params().BlockPartSizeBytes + partSize := cs1.state.Params.BlockPartSizeBytes proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1) timeoutWaitCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutWait(), 1) diff --git a/node/node.go b/node/node.go index a3267cdda..984965263 100644 --- a/node/node.go +++ b/node/node.go @@ -145,9 +145,6 @@ func NewNode(config *cfg.Config, return nil, err } state.Save() - } else { - state.SetChainID(genDoc.ChainID) - state.SetParams(genDoc.ConsensusParams) } state.SetLogger(stateLogger) @@ -163,8 +160,6 @@ func NewNode(config *cfg.Config, // reload the state (it may have been updated by the handshake) state = sm.LoadState(stateDB) - state.SetChainID(genDoc.ChainID) - state.SetParams(genDoc.ConsensusParams) state.SetLogger(stateLogger) // Transaction indexing diff --git a/state/execution.go b/state/execution.go index 603eab71b..b917bfbe8 100644 --- a/state/execution.go +++ b/state/execution.go @@ -179,9 +179,8 @@ func (s *State) ValidateBlock(block *types.Block) error { } func (s *State) validateBlock(block *types.Block) error { - chainID := s.ChainID() // Basic block validation. - err := block.ValidateBasic(chainID, s.LastBlockHeight, s.LastBlockID, s.LastBlockTime, s.AppHash) + err := block.ValidateBasic(s.ChainID, s.LastBlockHeight, s.LastBlockID, s.LastBlockTime, s.AppHash) if err != nil { return err } @@ -197,7 +196,7 @@ func (s *State) validateBlock(block *types.Block) error { s.LastValidators.Size(), len(block.LastCommit.Precommits))) } err := s.LastValidators.VerifyCommit( - chainID, s.LastBlockID, block.Height-1, block.LastCommit) + s.ChainID, s.LastBlockID, block.Height-1, block.LastCommit) if err != nil { return err } diff --git a/state/state.go b/state/state.go index 6460f201d..995773cb1 100644 --- a/state/state.go +++ b/state/state.go @@ -39,9 +39,10 @@ type State struct { db dbm.DB // See https://github.com/tendermint/tendermint/issues/671. - chainID string - - params *types.ConsensusParams + ChainID string + // consensus parameters used for validating blocks + // must not be nil + Params *types.ConsensusParams // These fields are updated by SetBlockAndValidators. // LastBlockHeight=0 at genesis (ie. block(H=0) does not exist) @@ -70,21 +71,14 @@ type State struct { // or creates a new one from the given genesisFile and persists the result // to the database. func GetState(stateDB dbm.DB, genesisFile string) (*State, error) { - var err error - genDoc, err := MakeGenesisDocFromFile(genesisFile) - if err != nil { - return nil, err - } state := LoadState(stateDB) if state == nil { - state, err = MakeGenesisState(stateDB, genDoc) + var err error + state, err = MakeGenesisStateFromFile(stateDB, genesisFile) if err != nil { return nil, err } state.Save() - } else { - state.SetChainID(genDoc.ChainID) - state.SetParams(genDoc.ConsensusParams) } return state, nil @@ -119,7 +113,7 @@ func (s *State) SetLogger(l log.Logger) { // Copy makes a copy of the State for mutating. func (s *State) Copy() *State { - paramsCopy := *s.params + paramsCopy := *s.Params return &State{ db: s.db, LastBlockHeight: s.LastBlockHeight, @@ -131,17 +125,17 @@ func (s *State) Copy() *State { TxIndexer: s.TxIndexer, // pointer here, not value LastHeightValidatorsChanged: s.LastHeightValidatorsChanged, logger: s.logger, - chainID: s.chainID, - params: ¶msCopy, + ChainID: s.ChainID, + Params: ¶msCopy, } } // Save persists the State to the database. func (s *State) Save() { s.mtx.Lock() + defer s.mtx.Unlock() s.saveValidatorsInfo() s.db.SetSync(stateKey, s.Bytes()) - s.mtx.Unlock() } // SaveABCIResponses persists the ABCIResponses to the database. @@ -273,28 +267,6 @@ func (s *State) GetValidators() (*types.ValidatorSet, *types.ValidatorSet) { return s.LastValidators, s.Validators } -// ChainID returns the chain ID. -func (s *State) ChainID() string { - return s.chainID -} - -// SetChainID sets the chain ID. -func (s *State) SetChainID(chainID string) { - s.chainID = chainID -} - -// Params returns the consensus parameters used for -// validating blocks. -func (s *State) Params() types.ConsensusParams { - return *s.params -} - -// SetParams sets the consensus parameters used for -// validating blocks. -func (s *State) SetParams(params *types.ConsensusParams) { - s.params = params -} - //------------------------------------------------------------------------ // ABCIResponses retains the responses of the various ABCI calls during block processing. @@ -364,8 +336,6 @@ func MakeGenesisDocFromFile(genDocFile string) (*types.GenesisDoc, error) { } // MakeGenesisState creates state from types.GenesisDoc. -// -// Used in tests. func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) (*State, error) { err := genDoc.ValidateAndComplete() if err != nil { @@ -389,8 +359,8 @@ func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) (*State, error) { return &State{ db: db, - chainID: genDoc.ChainID, - params: genDoc.ConsensusParams, + ChainID: genDoc.ChainID, + Params: genDoc.ConsensusParams, LastBlockHeight: 0, LastBlockID: types.BlockID{}, diff --git a/state/state_test.go b/state/state_test.go index e0703d3b5..8ac2eada0 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -116,22 +116,6 @@ func TestValidatorSimpleSaveLoad(t *testing.T) { assert.IsType(ErrNoValSetForHeight{}, err, "expected err at unknown height") } -func TestChainID(t *testing.T) { - tearDown, _, state := setupTestCase(t) - defer tearDown(t) - - state.SetChainID("test") - assert.Equal(t, "test", state.ChainID()) -} - -func TestParams(t *testing.T) { - tearDown, _, state := setupTestCase(t) - defer tearDown(t) - - state.SetParams(&types.ConsensusParams{}) - assert.Equal(t, types.ConsensusParams{}, state.Params()) -} - func TestValidatorChangesSaveLoad(t *testing.T) { tearDown, _, state := setupTestCase(t) defer tearDown(t) From c4646bf87fb0f44824287d49d2c167081f2614d1 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 13 Oct 2017 12:43:14 +0400 Subject: [PATCH 4/6] make state#Params not a pointer also remove the comment --- consensus/replay_test.go | 2 +- state/state.go | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 6a6b1bcf3..98295ec44 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -562,7 +562,7 @@ func stateAndStore(config *cfg.Config, pubKey crypto.PubKey) (*sm.State, *mockBl state, _ := sm.MakeGenesisStateFromFile(stateDB, config.GenesisFile()) state.SetLogger(log.TestingLogger().With("module", "state")) - store := NewMockBlockStore(config, *state.Params) + store := NewMockBlockStore(config, state.Params) return state, store } diff --git a/state/state.go b/state/state.go index 995773cb1..d3646cf18 100644 --- a/state/state.go +++ b/state/state.go @@ -38,11 +38,9 @@ type State struct { mtx sync.Mutex db dbm.DB - // See https://github.com/tendermint/tendermint/issues/671. ChainID string - // consensus parameters used for validating blocks - // must not be nil - Params *types.ConsensusParams + // Consensus parameters used for validating blocks + Params types.ConsensusParams // These fields are updated by SetBlockAndValidators. // LastBlockHeight=0 at genesis (ie. block(H=0) does not exist) @@ -113,7 +111,6 @@ func (s *State) SetLogger(l log.Logger) { // Copy makes a copy of the State for mutating. func (s *State) Copy() *State { - paramsCopy := *s.Params return &State{ db: s.db, LastBlockHeight: s.LastBlockHeight, @@ -126,7 +123,7 @@ func (s *State) Copy() *State { LastHeightValidatorsChanged: s.LastHeightValidatorsChanged, logger: s.logger, ChainID: s.ChainID, - Params: ¶msCopy, + Params: s.Params, } } @@ -360,7 +357,7 @@ func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) (*State, error) { db: db, ChainID: genDoc.ChainID, - Params: genDoc.ConsensusParams, + Params: *genDoc.ConsensusParams, LastBlockHeight: 0, LastBlockID: types.BlockID{}, From 6469e2ccca8816ea730cf114cd7c080593c65d94 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 13 Oct 2017 13:11:16 +0400 Subject: [PATCH 5/6] save genesis doc in DB to prevent user errors https://github.com/tendermint/tendermint/pull/676#discussion_r144411458 --- node/node.go | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/node/node.go b/node/node.go index 984965263..e99cedae9 100644 --- a/node/node.go +++ b/node/node.go @@ -2,6 +2,7 @@ package node import ( "bytes" + "encoding/json" "errors" "fmt" "net" @@ -133,9 +134,19 @@ func NewNode(config *cfg.Config, return nil, err } - genDoc, err := genesisDocProvider() + // Get genesis doc + genDoc, err := loadGenesisDoc(stateDB) if err != nil { - return nil, err + genDoc, err = genesisDocProvider() + if err != nil { + return nil, err + } + // save genesis doc to prevent a certain class of user errors (e.g. when it + // was changed, accidentally or not). Also good for audit trail. + err = saveGenesisDoc(stateDB, genDoc) + if err != nil { + return nil, fmt.Errorf("Failed to save genesis doc: %v", err) + } } state := sm.LoadState(stateDB) @@ -536,3 +547,27 @@ func (n *Node) DialSeeds(seeds []string) error { } //------------------------------------------------------------------------------ + +var ( + genesisDocKey = []byte("genesisDoc") +) + +func loadGenesisDoc(db dbm.DB) (*types.GenesisDoc, error) { + bytes := db.Get(genesisDocKey) + if len(bytes) == 0 { + return nil, errors.New("Genesis doc not found") + } else { + var genDoc *types.GenesisDoc + err := json.Unmarshal(bytes, &genDoc) + return genDoc, err + } +} + +func saveGenesisDoc(db dbm.DB, genDoc *types.GenesisDoc) error { + bytes, err := json.Marshal(genDoc) + if err != nil { + return err + } + db.SetSync(genesisDocKey, bytes) + return nil +} From 75b78bfb72a5e378c756f100678f01d7d13d8fba Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 17 Oct 2017 13:33:57 +0400 Subject: [PATCH 6/6] panic on marshal/unmarshal failures for genesisDoc --- node/node.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/node/node.go b/node/node.go index e99cedae9..9dfe617db 100644 --- a/node/node.go +++ b/node/node.go @@ -143,10 +143,7 @@ func NewNode(config *cfg.Config, } // save genesis doc to prevent a certain class of user errors (e.g. when it // was changed, accidentally or not). Also good for audit trail. - err = saveGenesisDoc(stateDB, genDoc) - if err != nil { - return nil, fmt.Errorf("Failed to save genesis doc: %v", err) - } + saveGenesisDoc(stateDB, genDoc) } state := sm.LoadState(stateDB) @@ -552,6 +549,7 @@ var ( genesisDocKey = []byte("genesisDoc") ) +// panics if failed to unmarshal bytes func loadGenesisDoc(db dbm.DB) (*types.GenesisDoc, error) { bytes := db.Get(genesisDocKey) if len(bytes) == 0 { @@ -559,15 +557,18 @@ func loadGenesisDoc(db dbm.DB) (*types.GenesisDoc, error) { } else { var genDoc *types.GenesisDoc err := json.Unmarshal(bytes, &genDoc) - return genDoc, err + if err != nil { + cmn.PanicCrisis(fmt.Sprintf("Failed to load genesis doc due to unmarshaling error: %v (bytes: %X)", err, bytes)) + } + return genDoc, nil } } -func saveGenesisDoc(db dbm.DB, genDoc *types.GenesisDoc) error { +// panics if failed to marshal the given genesis document +func saveGenesisDoc(db dbm.DB, genDoc *types.GenesisDoc) { bytes, err := json.Marshal(genDoc) if err != nil { - return err + cmn.PanicCrisis(fmt.Sprintf("Failed to save genesis doc due to marshaling error: %v", err)) } db.SetSync(genesisDocKey, bytes) - return nil }