diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 98e48b14a237d2c37a3a2035e42d369521ef237f..af8e43e413f415f74a319641e667d22c10c9d555 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -148,11 +148,18 @@ func NewTip(chainID string, genesisTime time.Time, genesisHash []byte) *Tip { func (bc *Blockchain) CommitBlock(blockTime time.Time, blockHash, appHash []byte) error { bc.Lock() defer bc.Unlock() + // Checkpoint on the _previous_ block. If we die, this is where we will resume since we know it must have been + // committed since we are committing the next block. If we fall over we can resume a safe committed state and + // Tendermint will catch us up + err := bc.save() + if err != nil { + return err + } bc.lastBlockHeight += 1 bc.lastBlockTime = blockTime bc.lastBlockHash = blockHash bc.appHashAfterLastBlock = appHash - return bc.save() + return nil } func (bc *Blockchain) save() error { diff --git a/consensus/tendermint/abci/app.go b/consensus/tendermint/abci/app.go index 6cca22aef6580d1feeeb653adb3516be3a3827b9..ba7fec9f55324e640ec2f24b0ccf6a440f5c97b9 100644 --- a/consensus/tendermint/abci/app.go +++ b/consensus/tendermint/abci/app.go @@ -227,13 +227,15 @@ func (app *App) Commit() abciTypes.ResponseCommit { } }() + // First commit the app start, this app hash will not get checkpointed until the next block when we are sure + // that nothing in the downstream commit process could have failed. At worst we go back one block. appHash, err := app.committer.Commit() if err != nil { panic(errors.Wrap(err, "Could not commit transactions in block to execution state")) - } - // Commit to our blockchain state + // Commit to our blockchain state which will checkpoint the previous app hash by saving it to the database + // (we know the previous app hash is safely committed because we are about to commit the next) err = app.blockchain.CommitBlock(time.Unix(int64(app.block.Header.Time), 0), app.block.Hash, appHash) if err != nil { panic(errors.Wrap(err, "could not commit block to blockchain state")) diff --git a/consensus/tendermint/tendermint.go b/consensus/tendermint/tendermint.go index 175cbab85273ca972dbc4434d0e8ef6b51f63f75..8e9813f48a7a22fd35338708efc211c0138ea55a 100644 --- a/consensus/tendermint/tendermint.go +++ b/consensus/tendermint/tendermint.go @@ -90,10 +90,11 @@ func DeriveGenesisDoc(burrowGenesisDoc *genesis.GenesisDoc) *tm_types.GenesisDoc } } return &tm_types.GenesisDoc{ - ChainID: burrowGenesisDoc.ChainID(), - GenesisTime: burrowGenesisDoc.GenesisTime, - Validators: validators, - AppHash: burrowGenesisDoc.Hash(), + ChainID: burrowGenesisDoc.ChainID(), + GenesisTime: burrowGenesisDoc.GenesisTime, + Validators: validators, + AppHash: burrowGenesisDoc.Hash(), + ConsensusParams: tm_types.DefaultConsensusParams(), } } diff --git a/core/kernel.go b/core/kernel.go index f5a8549f56ffbc666be97488b08938b34ed69738..a92a5b3ace13480cef033abe13c59fbe571a39a2 100644 --- a/core/kernel.go +++ b/core/kernel.go @@ -84,7 +84,7 @@ func NewKernel(ctx context.Context, keyClient keys.KeyClient, privValidator tm_t var state *execution.State // These should be in sync unless we are at the genesis block if blockchain.LastBlockHeight() > 0 { - state, err = execution.LoadState(stateDB) + state, err = execution.LoadState(stateDB, blockchain.AppHashAfterLastBlock()) if err != nil { return nil, fmt.Errorf("could not load persisted execution state at hash 0x%X: %v", blockchain.AppHashAfterLastBlock(), err) diff --git a/core/kernel_test.go b/core/kernel_test.go index d242de8e9946a882c68cd54e894a156168248754..fae573639b9ce3c39eb379e187447bac2fd9e47b 100644 --- a/core/kernel_test.go +++ b/core/kernel_test.go @@ -14,6 +14,7 @@ import ( "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/rpc" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" tmConfig "github.com/tendermint/tendermint/config" tmTypes "github.com/tendermint/tendermint/types" ) @@ -42,18 +43,18 @@ func TestBootShutdownResume(t *testing.T) { genesisDoc, _, privateValidators := genesis.NewDeterministicGenesis(123).GenesisDoc(1, true, 1000, 1, true, 1000) privValidator := validator.NewPrivValidatorMemory(privateValidators[0], privateValidators[0]) - i := int64(1) + i := int64(0) // asserts we get a consecutive run of blocks blockChecker := func(block *tmTypes.EventDataNewBlock) bool { - assert.Equal(t, i, block.Block.Height) + assert.Equal(t, i+1, block.Block.Height) i++ // stop every third block return i%3 != 0 } // First run - assert.NoError(t, bootWaitBlocksShutdown(privValidator, genesisDoc, tmConf, logger, blockChecker)) + require.NoError(t, bootWaitBlocksShutdown(privValidator, genesisDoc, tmConf, logger, blockChecker)) // Resume and check we pick up where we left off - assert.NoError(t, bootWaitBlocksShutdown(privValidator, genesisDoc, tmConf, logger, blockChecker)) + require.NoError(t, bootWaitBlocksShutdown(privValidator, genesisDoc, tmConf, logger, blockChecker)) // Resuming with mismatched genesis should fail genesisDoc.Salt = []byte("foo") assert.Error(t, bootWaitBlocksShutdown(privValidator, genesisDoc, tmConf, logger, blockChecker)) diff --git a/execution/state.go b/execution/state.go index b4b03ce91b2405399ac69ebb97e24ec2be48bd9a..39942cda25ffea3cf263a964f521b18b3910a086 100644 --- a/execution/state.go +++ b/execution/state.go @@ -57,8 +57,7 @@ var _ state.Writer = &State{} type State struct { sync.RWMutex - db dbm.DB - // TODO: + db dbm.DB tree *iavl.VersionedTree logger *logging.Logger } @@ -129,25 +128,53 @@ func MakeGenesisState(db dbm.DB, genesisDoc *genesis.GenesisDoc) (*State, error) } // Tries to load the execution state from DB, returns nil with no error if no state found -func LoadState(db dbm.DB) (*State, error) { +func LoadState(db dbm.DB, hash []byte) (*State, error) { s := NewState(db) - _, err := s.tree.Load() + // Get the version associated with this state hash + version, err := s.GetVersion(hash) + if err != nil { + return nil, err + } + treeVersion, err := s.tree.LoadVersion(version) if err != nil { return nil, fmt.Errorf("could not load versioned state tree") } + if treeVersion != version { + return nil, fmt.Errorf("tried to load state version %v for state hash %X but loaded version %v", + version, hash, treeVersion) + } return s, nil } func (s *State) Save() error { s.Lock() defer s.Unlock() - _, _, err := s.tree.SaveVersion() + // Save state at a new version may still be orphaned before we save the version against the hash + hash, treeVersion, err := s.tree.SaveVersion() if err != nil { return err } + // Provide a reference to load this version in the future from the state hash + s.SetVersion(hash, treeVersion) return nil } +// Get a previously saved tree version stored by state hash +func (s *State) GetVersion(hash []byte) (int64, error) { + versionBytes := s.db.Get(prefixedKey(versionPrefix, hash)) + if versionBytes == nil { + return -1, fmt.Errorf("could not retrieve version corresponding to state hash '%X' in database", hash) + } + return binary.GetInt64BE(versionBytes), nil +} + +// Set the tree version associated with a particular hash +func (s *State) SetVersion(hash []byte, version int64) { + versionBytes := make([]byte, 8) + binary.PutInt64BE(versionBytes, version) + s.db.SetSync(prefixedKey(versionPrefix, hash), versionBytes) +} + // Computes the state hash, also computed on save where it is returned func (s *State) Hash() []byte { s.RLock()