mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-20 06:54:41 +00:00
genesis: add support for arbitrary initial height (#5191)
Adds a genesis parameter `initial_height` which specifies the initial block height, as well as ABCI `RequestInitChain.InitialHeight` to pass it to the ABCI application, and `State.InitialHeight` to keep track of the initial height throughout the code. Fixes #2543, based on [RFC-002](https://github.com/tendermint/spec/pull/119). Spec changes in https://github.com/tendermint/spec/pull/135.
This commit is contained in:
+11
-6
@@ -134,7 +134,8 @@ func (blockExec *BlockExecutor) ApplyBlock(
|
||||
}
|
||||
|
||||
startTime := time.Now().UnixNano()
|
||||
abciResponses, err := execBlockOnProxyApp(blockExec.logger, blockExec.proxyApp, block, blockExec.db)
|
||||
abciResponses, err := execBlockOnProxyApp(blockExec.logger, blockExec.proxyApp, block,
|
||||
blockExec.db, state.InitialHeight)
|
||||
endTime := time.Now().UnixNano()
|
||||
blockExec.metrics.BlockProcessingTime.Observe(float64(endTime-startTime) / 1000000)
|
||||
if err != nil {
|
||||
@@ -254,6 +255,7 @@ func execBlockOnProxyApp(
|
||||
proxyAppConn proxy.AppConnConsensus,
|
||||
block *types.Block,
|
||||
stateDB dbm.DB,
|
||||
initialHeight int64,
|
||||
) (*tmstate.ABCIResponses, error) {
|
||||
var validTxs, invalidTxs = 0, 0
|
||||
|
||||
@@ -281,7 +283,7 @@ func execBlockOnProxyApp(
|
||||
}
|
||||
proxyAppConn.SetResponseCallback(proxyCb)
|
||||
|
||||
commitInfo, byzVals := getBeginBlockValidatorInfo(block, stateDB)
|
||||
commitInfo, byzVals := getBeginBlockValidatorInfo(block, stateDB, initialHeight)
|
||||
|
||||
// Begin block
|
||||
var err error
|
||||
@@ -320,12 +322,13 @@ func execBlockOnProxyApp(
|
||||
return abciResponses, nil
|
||||
}
|
||||
|
||||
func getBeginBlockValidatorInfo(block *types.Block, stateDB dbm.DB) (abci.LastCommitInfo, []abci.Evidence) {
|
||||
func getBeginBlockValidatorInfo(block *types.Block, stateDB dbm.DB,
|
||||
initialHeight int64) (abci.LastCommitInfo, []abci.Evidence) {
|
||||
voteInfos := make([]abci.VoteInfo, block.LastCommit.Size())
|
||||
// block.Height=1 -> LastCommitInfo.Votes are empty.
|
||||
// Initial block -> LastCommitInfo.Votes are empty.
|
||||
// Remember that the first LastCommit is intentionally empty, so it makes
|
||||
// sense for LastCommitInfo.Votes to also be empty.
|
||||
if block.Height > 1 {
|
||||
if block.Height > initialHeight {
|
||||
lastValSet, err := LoadValidators(stateDB, block.Height-1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -445,6 +448,7 @@ func updateState(
|
||||
return State{
|
||||
Version: nextVersion,
|
||||
ChainID: state.ChainID,
|
||||
InitialHeight: state.InitialHeight,
|
||||
LastBlockHeight: header.Height,
|
||||
LastBlockID: blockID,
|
||||
LastBlockTime: header.Time,
|
||||
@@ -515,8 +519,9 @@ func ExecCommitBlock(
|
||||
block *types.Block,
|
||||
logger log.Logger,
|
||||
stateDB dbm.DB,
|
||||
initialHeight int64,
|
||||
) ([]byte, error) {
|
||||
_, err := execBlockOnProxyApp(logger, appConnConsensus, block, stateDB)
|
||||
_, err := execBlockOnProxyApp(logger, appConnConsensus, block, stateDB, initialHeight)
|
||||
if err != nil {
|
||||
logger.Error("Error executing block on proxy app", "height", block.Height, "err", err)
|
||||
return nil, err
|
||||
|
||||
@@ -94,7 +94,7 @@ func TestBeginBlockValidators(t *testing.T) {
|
||||
// block for height 2
|
||||
block, _ := state.MakeBlock(2, makeTxs(2), lastCommit, nil, state.Validators.GetProposer().Address)
|
||||
|
||||
_, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB)
|
||||
_, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB, 1)
|
||||
require.Nil(t, err, tc.desc)
|
||||
|
||||
// -> app receives a list of validators with a bool indicating if they signed
|
||||
@@ -163,7 +163,7 @@ func TestBeginBlockByzantineValidators(t *testing.T) {
|
||||
block, _ := state.MakeBlock(10, makeTxs(2), lastCommit, nil, state.Validators.GetProposer().Address)
|
||||
block.Time = now
|
||||
block.Evidence.Evidence = tc.evidence
|
||||
_, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB)
|
||||
_, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB, 1)
|
||||
require.Nil(t, err, tc.desc)
|
||||
|
||||
// -> app must receive an index of the byzantine validator
|
||||
|
||||
+13
-8
@@ -49,7 +49,8 @@ type State struct {
|
||||
Version tmstate.Version
|
||||
|
||||
// immutable
|
||||
ChainID string
|
||||
ChainID string
|
||||
InitialHeight int64 // should be 1, not 0, when starting from height 1
|
||||
|
||||
// LastBlockHeight=0 at genesis (ie. block(H=0) does not exist)
|
||||
LastBlockHeight int64
|
||||
@@ -83,8 +84,9 @@ type State struct {
|
||||
func (state State) Copy() State {
|
||||
|
||||
return State{
|
||||
Version: state.Version,
|
||||
ChainID: state.ChainID,
|
||||
Version: state.Version,
|
||||
ChainID: state.ChainID,
|
||||
InitialHeight: state.InitialHeight,
|
||||
|
||||
LastBlockHeight: state.LastBlockHeight,
|
||||
LastBlockID: state.LastBlockID,
|
||||
@@ -139,6 +141,7 @@ func (state *State) ToProto() (*tmstate.State, error) {
|
||||
|
||||
sm.Version = state.Version
|
||||
sm.ChainID = state.ChainID
|
||||
sm.InitialHeight = state.InitialHeight
|
||||
sm.LastBlockHeight = state.LastBlockHeight
|
||||
|
||||
sm.LastBlockID = state.LastBlockID.ToProto()
|
||||
@@ -182,6 +185,7 @@ func StateFromProto(pb *tmstate.State) (*State, error) { //nolint:golint
|
||||
|
||||
state.Version = pb.Version
|
||||
state.ChainID = pb.ChainID
|
||||
state.InitialHeight = pb.InitialHeight
|
||||
|
||||
bi, err := types.BlockIDFromProto(&pb.LastBlockID)
|
||||
if err != nil {
|
||||
@@ -241,7 +245,7 @@ func (state State) MakeBlock(
|
||||
|
||||
// Set time.
|
||||
var timestamp time.Time
|
||||
if height == 1 {
|
||||
if height == state.InitialHeight {
|
||||
timestamp = state.LastBlockTime // genesis time
|
||||
} else {
|
||||
timestamp = MedianTime(commit, state.LastValidators)
|
||||
@@ -331,8 +335,9 @@ func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) {
|
||||
}
|
||||
|
||||
return State{
|
||||
Version: InitStateVersion,
|
||||
ChainID: genDoc.ChainID,
|
||||
Version: InitStateVersion,
|
||||
ChainID: genDoc.ChainID,
|
||||
InitialHeight: genDoc.InitialHeight,
|
||||
|
||||
LastBlockHeight: 0,
|
||||
LastBlockID: types.BlockID{},
|
||||
@@ -341,10 +346,10 @@ func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) {
|
||||
NextValidators: nextValidatorSet,
|
||||
Validators: validatorSet,
|
||||
LastValidators: types.NewValidatorSet(nil),
|
||||
LastHeightValidatorsChanged: 1,
|
||||
LastHeightValidatorsChanged: genDoc.InitialHeight,
|
||||
|
||||
ConsensusParams: *genDoc.ConsensusParams,
|
||||
LastHeightConsensusParamsChanged: 1,
|
||||
LastHeightConsensusParamsChanged: genDoc.InitialHeight,
|
||||
|
||||
AppHash: genDoc.AppHash,
|
||||
}, nil
|
||||
|
||||
+13
-8
@@ -110,12 +110,12 @@ func SaveState(db dbm.DB, state State) {
|
||||
|
||||
func saveState(db dbm.DB, state State, key []byte) {
|
||||
nextHeight := state.LastBlockHeight + 1
|
||||
// If first block, save validators for block 1.
|
||||
// If first block, save validators for the block.
|
||||
if nextHeight == 1 {
|
||||
nextHeight = state.InitialHeight
|
||||
// This extra logic due to Tendermint validator set changes being delayed 1 block.
|
||||
// It may get overwritten due to InitChain validator updates.
|
||||
lastHeightVoteChanged := int64(1)
|
||||
saveValidatorsInfo(db, nextHeight, lastHeightVoteChanged, state.Validators)
|
||||
saveValidatorsInfo(db, nextHeight, nextHeight, state.Validators)
|
||||
}
|
||||
// Save next validators.
|
||||
saveValidatorsInfo(db, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators)
|
||||
@@ -130,11 +130,16 @@ func saveState(db dbm.DB, state State, key []byte) {
|
||||
|
||||
// BootstrapState saves a new state, used e.g. by state sync when starting from non-zero height.
|
||||
func BootstrapState(db dbm.DB, state State) error {
|
||||
height := state.LastBlockHeight
|
||||
saveValidatorsInfo(db, height, height, state.LastValidators)
|
||||
saveValidatorsInfo(db, height+1, height+1, state.Validators)
|
||||
saveValidatorsInfo(db, height+2, height+2, state.NextValidators)
|
||||
saveConsensusParamsInfo(db, height+1, height+1, state.ConsensusParams)
|
||||
height := state.LastBlockHeight + 1
|
||||
if height == 1 {
|
||||
height = state.InitialHeight
|
||||
}
|
||||
if height > 1 && !state.LastValidators.IsNilOrEmpty() {
|
||||
saveValidatorsInfo(db, height-1, height-1, state.LastValidators)
|
||||
}
|
||||
saveValidatorsInfo(db, height, height, state.Validators)
|
||||
saveValidatorsInfo(db, height+1, height+1, state.NextValidators)
|
||||
saveConsensusParamsInfo(db, height, height, state.ConsensusParams)
|
||||
return db.SetSync(stateKey, state.Bytes())
|
||||
}
|
||||
|
||||
|
||||
@@ -119,6 +119,7 @@ func TestPruneStates(t *testing.T) {
|
||||
}
|
||||
|
||||
state := sm.State{
|
||||
InitialHeight: 1,
|
||||
LastBlockHeight: h - 1,
|
||||
Validators: validatorSet,
|
||||
NextValidators: validatorSet,
|
||||
|
||||
+15
-5
@@ -34,7 +34,11 @@ func validateBlock(evidencePool EvidencePool, stateDB dbm.DB, state State, block
|
||||
block.ChainID,
|
||||
)
|
||||
}
|
||||
if block.Height != state.LastBlockHeight+1 {
|
||||
if state.LastBlockHeight == 0 && block.Height != state.InitialHeight {
|
||||
return fmt.Errorf("wrong Block.Header.Height. Expected %v for initial block, got %v",
|
||||
block.Height, state.InitialHeight)
|
||||
}
|
||||
if state.LastBlockHeight > 0 && block.Height != state.LastBlockHeight+1 {
|
||||
return fmt.Errorf("wrong Block.Header.Height. Expected %v, got %v",
|
||||
state.LastBlockHeight+1,
|
||||
block.Height,
|
||||
@@ -82,9 +86,9 @@ func validateBlock(evidencePool EvidencePool, stateDB dbm.DB, state State, block
|
||||
}
|
||||
|
||||
// Validate block LastCommit.
|
||||
if block.Height == 1 {
|
||||
if block.Height == state.InitialHeight {
|
||||
if len(block.LastCommit.Signatures) != 0 {
|
||||
return errors.New("block at height 1 can't have LastCommit signatures")
|
||||
return errors.New("initial block can't have LastCommit signatures")
|
||||
}
|
||||
} else {
|
||||
// LastCommit.Signatures length is checked in VerifyCommit.
|
||||
@@ -110,7 +114,8 @@ func validateBlock(evidencePool EvidencePool, stateDB dbm.DB, state State, block
|
||||
}
|
||||
|
||||
// Validate block Time
|
||||
if block.Height > 1 {
|
||||
switch {
|
||||
case block.Height > state.InitialHeight:
|
||||
if !block.Time.After(state.LastBlockTime) {
|
||||
return fmt.Errorf("block time %v not greater than last block time %v",
|
||||
block.Time,
|
||||
@@ -124,7 +129,8 @@ func validateBlock(evidencePool EvidencePool, stateDB dbm.DB, state State, block
|
||||
block.Time,
|
||||
)
|
||||
}
|
||||
} else if block.Height == 1 {
|
||||
|
||||
case block.Height == state.InitialHeight:
|
||||
genesisTime := state.LastBlockTime
|
||||
if !block.Time.Equal(genesisTime) {
|
||||
return fmt.Errorf("block time %v is not equal to genesis time %v",
|
||||
@@ -132,6 +138,10 @@ func validateBlock(evidencePool EvidencePool, stateDB dbm.DB, state State, block
|
||||
genesisTime,
|
||||
)
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("block height %v lower than initial height %v",
|
||||
block.Height, state.InitialHeight)
|
||||
}
|
||||
|
||||
// Limit the amount of evidence
|
||||
|
||||
Reference in New Issue
Block a user