Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

genesis: add support for arbitrary initial height #5191

Merged
merged 28 commits into from
Aug 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
35c8a7c
genesis: add initial_height field
erikgrinaker Jul 30, 2020
31622cb
abci: add RequestInitChain.initial_height
erikgrinaker Jul 30, 2020
110576f
pass initial height to ABCI application
erikgrinaker Jul 31, 2020
9a0156f
testnet: add --initial-height switch
erikgrinaker Jul 31, 2020
d9d91b5
Makefile: run local testnets with --initial-height 1000
erikgrinaker Jul 31, 2020
77bfbee
initial prototype implementation
erikgrinaker Jul 31, 2020
3e4713c
state: add State.InitialHeight
erikgrinaker Aug 3, 2020
19ebc3c
use State.InitialHeight in BlockExecutor
erikgrinaker Aug 3, 2020
31b9154
handle block replay with non-zero genesis
erikgrinaker Aug 3, 2020
7a83f4f
make SaveState() handle state.InitialHeight
erikgrinaker Aug 5, 2020
9024238
Makefile: don't use --initial-height 1000
erikgrinaker Aug 5, 2020
37ea68c
wal: remove initialHeight parameter
erikgrinaker Aug 6, 2020
5492bda
fix tests
erikgrinaker Aug 6, 2020
5b07bcf
Merge branch 'master' into erik/nonzero-genesis
erikgrinaker Aug 6, 2020
3117ee3
lint fix
erikgrinaker Aug 6, 2020
6ce918e
fix data race
erikgrinaker Aug 6, 2020
0ba502a
minor tweaks
erikgrinaker Aug 7, 2020
06941fa
remove incorrect comment
erikgrinaker Aug 7, 2020
7a6c109
update changelog and upgrading
erikgrinaker Aug 7, 2020
f2b46f3
Merge branch 'master' into erik/nonzero-genesis
erikgrinaker Aug 7, 2020
e2f3767
fix some spurious diffs
erikgrinaker Aug 7, 2020
5f0516d
wal: write marker for height 0 after all
erikgrinaker Aug 10, 2020
9f119f2
rpc: reword error message
erikgrinaker Aug 11, 2020
2d1ac9c
upgrading: add note on RequestInitChain.InitialHeight
erikgrinaker Aug 11, 2020
01163ce
Merge branch 'master' into erik/nonzero-genesis
erikgrinaker Aug 11, 2020
4d8f8f1
update evidence vote checks to use State.InitialHeight
erikgrinaker Aug 11, 2020
12bb951
changelog: remove ExecCommitBlock note, not considered public API
erikgrinaker Aug 11, 2020
f0dac40
Merge branch 'master' into erik/nonzero-genesis
erikgrinaker Aug 11, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi
- [evidence] [\#5181](https://github.com/tendermint/tendermint/pull/5181) Phantom validator evidence was removed (also from abci) (@cmwaters)
- [merkle] [\#5193](https://github.com/tendermint/tendermint/pull/5193) `HashFromByteSlices` and `ProofsFromByteSlices` now return a hash for empty inputs, following RFC6962 (@erikgrinaker)
- [crypto] [\#5214] Change `GenPrivKeySecp256k1` to `GenPrivKeyFromSecret` to be consistent with other keys
- [state] [\#5191](https://github.com/tendermint/tendermint/pull/5191/files) Add `State.InitialHeight` field to record initial block height, must be `1` (not `0`) to start from 1 (@erikgrinaker)

### FEATURES:

- [abci] [\#5174](https://github.com/tendermint/tendermint/pull/5174) Add amnesia evidence and remove mock and potential amnesia evidence from abci (@cmwaters)
- [abci] [\#5191](https://github.com/tendermint/tendermint/pull/5191/files) Add `InitChain.InitialHeight` field giving the initial block height (@erikgrinaker)
- [genesis] [\#5191](https://github.com/tendermint/tendermint/pull/5191/files) Add `initial_height` field to specify the initial chain height (defaults to `1`) (@erikgrinaker)

### IMPROVEMENTS:

Expand Down
9 changes: 9 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ if you want to learn more & support it (with cosmos-sdk you get it
`KV.Pair` has been replaced with `abci.EventAttribute`. `EventAttribute.Index`
field allows ABCI applications to dictate which events should be indexed.

The blockchain can now start from an arbitrary initial height, provided to the
application via `RequestInitChain.InitialHeight`.

### P2P Protocol

The default codec is now proto3, not amino. Check out the [TODO]() for
Expand Down Expand Up @@ -137,6 +140,12 @@ functions) and `Client` object, which represents the complete light client.
RPC client can be found in `/rpc` directory. HTTP(S) proxy is located in
`/proxy` directory.

### State

A field `State.InitialHeight` has been added to record the initial chain height, which must be `1`
(not `0`) if starting from height `1`. This can be configured via the genesis field
`initial_height`.

## v0.33.4

### Go API
Expand Down
372 changes: 204 additions & 168 deletions abci/types/types.pb.go

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions cmd/tendermint/commands/testnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
var (
nValidators int
nNonValidators int
initialHeight int64
configFile string
outputDir string
nodeDirPrefix string
Expand Down Expand Up @@ -50,6 +51,8 @@ func init() {
"Directory to store initialization data for the testnet")
TestnetFilesCmd.Flags().StringVar(&nodeDirPrefix, "node-dir-prefix", "node",
"Prefix the directory name for each node with (node results in node0, node1, ...)")
TestnetFilesCmd.Flags().Int64Var(&initialHeight, "initial-height", 0,
erikgrinaker marked this conversation as resolved.
Show resolved Hide resolved
"Initial height of the first block")

TestnetFilesCmd.Flags().BoolVar(&populatePersistentPeers, "populate-persistent-peers", true,
"Update config of each node with the list of persistent peers build using either"+
Expand Down Expand Up @@ -175,6 +178,7 @@ func testnetFiles(cmd *cobra.Command, args []string) error {
ChainID: "chain-" + tmrand.Str(6),
ConsensusParams: types.DefaultConsensusParams(),
GenesisTime: tmtime.Now(),
InitialHeight: initialHeight,
erikgrinaker marked this conversation as resolved.
Show resolved Hide resolved
Validators: genVals,
}

Expand Down
1 change: 1 addition & 0 deletions config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ func ResetTestRootWithChainID(testName string, chainID string) *Config {
var testGenesisFmt = `{
"genesis_time": "2018-10-10T08:20:13.695936996Z",
"chain_id": "%s",
"initial_height": "1",
"validators": [
{
"pub_key": {
Expand Down
13 changes: 8 additions & 5 deletions consensus/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ func randState(nValidators int) (*State, []*validatorStub) {
return cs, vss
}

func randStateWithEvpool(nValidators int) (*State, []*validatorStub, *evidence.Pool) {
func randStateWithEvpool(t *testing.T, nValidators int) (*State, []*validatorStub, *evidence.Pool) {
state, privVals := randGenesisState(nValidators, false, 10)

vss := make([]*validatorStub, nValidators)
Expand All @@ -451,7 +451,9 @@ func randStateWithEvpool(nValidators int) (*State, []*validatorStub, *evidence.P
mempool.EnableTxsAvailable()
}
stateDB := dbm.NewMemDB()
evpool, _ := evidence.NewPool(stateDB, evidenceDB, blockStore)
sm.SaveState(stateDB, state)
evpool, err := evidence.NewPool(stateDB, evidenceDB, blockStore)
require.NoError(t, err)
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool)
cs := NewState(config.Consensus, state, blockExec, blockStore, mempool, evpool)
cs.SetLogger(log.TestingLogger().With("module", "consensus"))
Expand Down Expand Up @@ -821,9 +823,10 @@ func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.G
sort.Sort(types.PrivValidatorsByAddress(privValidators))

return &types.GenesisDoc{
GenesisTime: tmtime.Now(),
ChainID: config.ChainID(),
Validators: validators,
GenesisTime: tmtime.Now(),
InitialHeight: 1,
ChainID: config.ChainID(),
Validators: validators,
}, privValidators
}

Expand Down
34 changes: 31 additions & 3 deletions consensus/reactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,14 @@ func (conR *Reactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) {
case StateChannel:
switch msg := msg.(type) {
case *NewRoundStepMessage:
conR.conS.mtx.Lock()
initialHeight := conR.conS.state.InitialHeight
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this immutable? Maybe we can load it once upfront and then not need to worry about the mutex?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but I figured that'd be premature optimization unless we could demonstrate that there was actual mutex contention -- complexity (i.e. caching) is usually far more likely to cause problems. Although I suppose storing this during consensus.NewState() would pretty much always do the right thing.

Opened #6012, we can have a look after the refactor.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, this was changed to a read-lock in the consensus reactor p2p refactor.

conR.conS.mtx.Unlock()
if err = msg.ValidateHeight(initialHeight); err != nil {
conR.Logger.Error("Peer sent us invalid msg", "peer", src, "msg", msg, "err", err)
conR.Switch.StopPeerForError(src, err)
return
}
ps.ApplyNewRoundStepMessage(msg)
case *NewValidBlockMessage:
ps.ApplyNewValidBlockMessage(msg)
Expand Down Expand Up @@ -1435,9 +1443,29 @@ func (m *NewRoundStepMessage) ValidateBasic() error {

// NOTE: SecondsSinceStartTime may be negative

if (m.Height == 1 && m.LastCommitRound != -1) ||
(m.Height > 1 && m.LastCommitRound < 0) {
return errors.New("invalid LastCommitRound (for 1st block: -1, for others: >= 0)")
// LastCommitRound will be -1 for the initial height, but we don't know what height this is
// since it can be specified in genesis. The reactor will have to validate this via
// ValidateHeight().
if m.LastCommitRound < -1 {
return errors.New("invalid LastCommitRound (cannot be < -1)")
}

return nil
}

// ValidateHeight validates the height given the chain's initial height.
func (m *NewRoundStepMessage) ValidateHeight(initialHeight int64) error {
if m.Height < initialHeight {
return fmt.Errorf("invalid Height %v (lower than initial height %v)",
m.Height, initialHeight)
}
if m.Height == initialHeight && m.LastCommitRound != -1 {
return fmt.Errorf("invalid LastCommitRound %v (must be -1 for initial height %v)",
m.LastCommitRound, initialHeight)
}
if m.Height > initialHeight && m.LastCommitRound < 0 {
return fmt.Errorf("LastCommitRound can only be negative for initial height %v", // nolint
initialHeight)
}
return nil
}
Expand Down
47 changes: 44 additions & 3 deletions consensus/reactor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -710,8 +710,9 @@ func TestNewRoundStepMessageValidateBasic(t *testing.T) {
{true, -1, 0, 0, "Negative round", cstypes.RoundStepNewHeight},
{true, 0, 0, -1, "Negative height", cstypes.RoundStepNewHeight},
{true, 0, 0, 0, "Invalid Step", cstypes.RoundStepCommit + 1},
{true, 0, 0, 1, "H == 1 but LCR != -1 ", cstypes.RoundStepNewHeight},
{true, 0, -1, 2, "H > 1 but LCR < 0", cstypes.RoundStepNewHeight},
// The following cases will be handled by ValidateHeight
{false, 0, 0, 1, "H == 1 but LCR != -1 ", cstypes.RoundStepNewHeight},
{false, 0, -1, 2, "H > 1 but LCR < 0", cstypes.RoundStepNewHeight},
}

for _, tc := range testCases {
Expand All @@ -724,7 +725,47 @@ func TestNewRoundStepMessageValidateBasic(t *testing.T) {
LastCommitRound: tc.messageLastCommitRound,
}

assert.Equal(t, tc.expectErr, message.ValidateBasic() != nil, "Validate Basic had an unexpected result")
err := message.ValidateBasic()
if tc.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}

func TestNewRoundStepMessageValidateHeight(t *testing.T) {
initialHeight := int64(10)
testCases := []struct { // nolint: maligned
expectErr bool
messageLastCommitRound int32
messageHeight int64
testName string
}{
{false, 0, 11, "Valid Message"},
{true, 0, -1, "Negative height"},
{true, 0, 0, "Zero height"},
{true, 0, 10, "Initial height but LCR != -1 "},
{true, -1, 11, "Normal height but LCR < 0"},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.testName, func(t *testing.T) {
message := NewRoundStepMessage{
Height: tc.messageHeight,
Round: 0,
Step: cstypes.RoundStepNewHeight,
LastCommitRound: tc.messageLastCommitRound,
}

err := message.ValidateHeight(initialHeight)
if tc.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
Expand Down
30 changes: 23 additions & 7 deletions consensus/replay.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,21 @@ func (cs *State) catchupReplay(csHeight int64) error {
// Search for last height marker.
//
// Ignore data corruption errors in previous heights because we only care about last height
gr, found, err = cs.wal.SearchForEndHeight(csHeight-1, &WALSearchOptions{IgnoreDataCorruptionErrors: true})
if csHeight < cs.state.InitialHeight {
return fmt.Errorf("cannot replay height %v, below initial height %v", csHeight, cs.state.InitialHeight)
}
endHeight := csHeight - 1
if csHeight == cs.state.InitialHeight {
endHeight = 0
}
gr, found, err = cs.wal.SearchForEndHeight(endHeight, &WALSearchOptions{IgnoreDataCorruptionErrors: true})
if err == io.EOF {
cs.Logger.Error("Replay: wal.group.Search returned EOF", "#ENDHEIGHT", csHeight-1)
cs.Logger.Error("Replay: wal.group.Search returned EOF", "#ENDHEIGHT", endHeight)
} else if err != nil {
return err
}
if !found {
return fmt.Errorf("cannot replay height %d. WAL does not contain #ENDHEIGHT for %d", csHeight, csHeight-1)
return fmt.Errorf("cannot replay height %d. WAL does not contain #ENDHEIGHT for %d", csHeight, endHeight)
}
defer gr.Close()

Expand Down Expand Up @@ -254,7 +261,7 @@ func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error {
"protocol-version", res.AppVersion,
)

// Only set the version if we're starting from zero.
// Only set the version if there is no existing state.
if h.initialState.LastBlockHeight == 0 {
h.initialState.Version.Consensus.App = res.AppVersion
}
Expand Down Expand Up @@ -306,6 +313,7 @@ func (h *Handshaker) ReplayBlocks(
req := abci.RequestInitChain{
Time: h.genDoc.GenesisTime,
ChainId: h.genDoc.ChainID,
InitialHeight: h.genDoc.InitialHeight,
ConsensusParams: csParams,
Validators: nextVals,
AppStateBytes: h.genDoc.AppState,
Expand Down Expand Up @@ -353,7 +361,11 @@ func (h *Handshaker) ReplayBlocks(
assertAppHashEqualsOneFromState(appHash, state)
return appHash, nil

case appBlockHeight < storeBlockBase-1:
case appBlockHeight == 0 && state.InitialHeight < storeBlockBase:
// the app has no state, and the block store is truncated above the initial height
return appHash, sm.ErrAppBlockHeightTooLow{AppHeight: appBlockHeight, StoreBase: storeBlockBase}

case appBlockHeight > 0 && appBlockHeight < storeBlockBase-1:
// the app is too far behind truncated store (can be 1 behind since we replay the next)
return appHash, sm.ErrAppBlockHeightTooLow{AppHeight: appBlockHeight, StoreBase: storeBlockBase}

Expand Down Expand Up @@ -444,15 +456,19 @@ func (h *Handshaker) replayBlocks(
if mutateState {
finalBlock--
}
for i := appBlockHeight + 1; i <= finalBlock; i++ {
firstBlock := appBlockHeight + 1
if firstBlock == 1 {
firstBlock = state.InitialHeight
}
for i := firstBlock; i <= finalBlock; i++ {
h.logger.Info("Applying block", "height", i)
block := h.store.LoadBlock(i)
// Extra check to ensure the app was not changed in a way it shouldn't have.
if len(appHash) > 0 {
assertAppHashEqualsOneFromBlock(appHash, block)
}

appHash, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, h.logger, h.stateDB)
appHash, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, h.logger, h.stateDB, h.genDoc.InitialHeight)
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion consensus/replay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ LOOP:
logger := log.NewNopLogger()
blockDB := dbm.NewMemDB()
stateDB := blockDB
state, _ := sm.MakeGenesisStateFromFile(consensusReplayConfig.GenesisFile())
state, err := sm.MakeGenesisStateFromFile(consensusReplayConfig.GenesisFile())
require.NoError(t, err)
privValidator := loadPrivValidator(consensusReplayConfig)
cs := newStateWithConfigAndBlockStore(
consensusReplayConfig,
Expand Down