Skip to content
This repository was archived by the owner on May 13, 2022. It is now read-only.

Commit 468279f

Browse files
author
Silas Davis
authored
Merge pull request #1148 from hyperledger/develop
Release 0.26.2
2 parents d3282a0 + 7b19d21 commit 468279f

File tree

17 files changed

+362
-181
lines changed

17 files changed

+362
-181
lines changed

CHANGELOG.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
## [Unreleased]
33

44

5+
## [0.26.2] - 2019-06-19
6+
### Fixed
7+
- [Blockchain] Persist LastBlockTime in Blockchain - before this patch LastBlockTime would only be set correctly after the first block had been received after a node is restarted - this can lead to non-determinism in the EVM via the TIMESTAMP opcode that use the LastBlockTime which is itself sourced from Tendermint's block header (from their implementation of BFT time). Implementing no empty blocks made observing this bug more likely by increasing the amount of time spent in a bad state (LastBlockTime is initially set to GenesisTime).
8+
9+
510
## [0.26.1] - 2019-06-16
611
### Changed
712
- [CLI] 'burrow dump' renamed 'burrow dump remote'
@@ -502,7 +507,8 @@ This release marks the start of Eris-DB as the full permissioned blockchain node
502507
- [Blockchain] Fix getBlocks to respect block height cap.
503508

504509

505-
[Unreleased]: https://github.com/hyperledger/burrow/compare/v0.26.1...HEAD
510+
[Unreleased]: https://github.com/hyperledger/burrow/compare/v0.26.2...HEAD
511+
[0.26.2]: https://github.com/hyperledger/burrow/compare/v0.26.1...v0.26.2
506512
[0.26.1]: https://github.com/hyperledger/burrow/compare/v0.26.0...v0.26.1
507513
[0.26.0]: https://github.com/hyperledger/burrow/compare/v0.25.1...v0.26.0
508514
[0.25.1]: https://github.com/hyperledger/burrow/compare/v0.25.0...v0.25.1

NOTES.md

+1-13
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
### Changed
2-
- [CLI] 'burrow dump' renamed 'burrow dump remote'
3-
- [Consensus] By default Burrow no longer creates empty blocks at the end of a round - though does make on every 5 minutes by default. Set CreateEmptyBlocks to "never" or omit to create no blocks unless there are transactions, or "always" to generate blocks even when there are no transactions.
4-
- [State] Burrow state does not store empty blocks in the execution event store even when Tendermint creates them.
5-
- [Build] 'make install_burrow' is now just 'make install'
6-
71
### Fixed
8-
- [Deploy] Always read TxExecution exception in Burrow deploy to avoid panics later on
9-
- [Restore] Set restore transaction hash to non-zero (sha256 of original ChainID + Height)
10-
- [Vent] --txs and --blocks now actually enable their respective tables in the Vent database
11-
- [Consensus] Tendermint config CreateEmptyBlocks, CreateEmptyBlocksInterval now work as intended and prevent empty blocks being produced (except when needed for proof purposes) or when the interval expires (when set)
12-
13-
### Added
14-
- [Dump] burrow dump now has local variant that produces a dump directly from a compatible burrow directory rather than over GRPC. If dumping/restoring between state-incompatible versions use burrow dump remote.
2+
- [Blockchain] Persist LastBlockTime in Blockchain - before this patch LastBlockTime would only be set correctly after the first block had been received after a node is restarted - this can lead to non-determinism in the EVM via the TIMESTAMP opcode that use the LastBlockTime which is itself sourced from Tendermint's block header (from their implementation of BFT time). Implementing no empty blocks made observing this bug more likely by increasing the amount of time spent in a bad state (LastBlockTime is initially set to GenesisTime).
153

bcm/block.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ func NewBlock(txDecoder txs.Decoder, block *types.Block) *Block {
1717
}
1818
}
1919

20-
func (b *Block) Transactions(iter func(*txs.Envelope) (stop bool)) (stopped bool, err error) {
20+
func (b *Block) Transactions(iter func(*txs.Envelope) error) error {
2121
for i := 0; i < len(b.Txs); i++ {
2222
tx, err := b.txDecoder.DecodeTx(b.Txs[i])
2323
if err != nil {
24-
return false, err
24+
return err
2525
}
26-
if iter(tx) {
27-
return true, nil
26+
err = iter(tx)
27+
if err != nil {
28+
return err
2829
}
2930
}
30-
return false, nil
31+
return nil
3132
}

bcm/block_store.go

+7-6
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ func (bs *BlockStore) BlockMeta(height int64) (_ *types.BlockMeta, err error) {
5252
}
5353

5454
// Iterate over blocks between start (inclusive) and end (exclusive)
55-
func (bs *BlockStore) Blocks(start, end int64, iter func(*Block) (stop bool)) (stopped bool, err error) {
55+
func (bs *BlockStore) Blocks(start, end int64, iter func(*Block) error) error {
5656
if end > 0 && start >= end {
57-
return false, fmt.Errorf("end height must be strictly greater than start height")
57+
return fmt.Errorf("end height must be strictly greater than start height")
5858
}
5959
if start <= 0 {
6060
// From first block
@@ -68,12 +68,13 @@ func (bs *BlockStore) Blocks(start, end int64, iter func(*Block) (stop bool)) (s
6868
for height := start; height <= end; height++ {
6969
block, err := bs.Block(height)
7070
if err != nil {
71-
return false, err
71+
return err
7272
}
73-
if iter(block) {
74-
return true, nil
73+
err = iter(block)
74+
if err != nil {
75+
return err
7576
}
7677
}
7778

78-
return false, nil
79+
return nil
7980
}

bcm/blockchain.go

+40-48
Original file line numberDiff line numberDiff line change
@@ -48,60 +48,63 @@ type BlockchainInfo interface {
4848

4949
type Blockchain struct {
5050
sync.RWMutex
51-
db dbm.DB
52-
genesisHash []byte
53-
genesisDoc genesis.GenesisDoc
54-
chainID string
55-
lastBlockHeight uint64
56-
lastBlockTime time.Time
57-
lastBlockHash []byte
58-
lastCommitTime time.Time
59-
lastCommitDuration time.Duration
60-
appHashAfterLastBlock []byte
61-
blockStore *BlockStore
51+
persistedState PersistedState
52+
// Non-persisted state
53+
db dbm.DB
54+
blockStore *BlockStore
55+
genesisDoc genesis.GenesisDoc
56+
lastBlockHash []byte
57+
lastCommitTime time.Time
58+
lastCommitDuration time.Duration
6259
}
6360

6461
var _ BlockchainInfo = &Blockchain{}
6562

6663
type PersistedState struct {
6764
AppHashAfterLastBlock []byte
65+
LastBlockTime time.Time
6866
LastBlockHeight uint64
6967
GenesisHash []byte
7068
}
7169

7270
// LoadOrNewBlockchain returns true if state already exists
73-
func LoadOrNewBlockchain(db dbm.DB, genesisDoc *genesis.GenesisDoc, logger *logging.Logger) (bool, *Blockchain, error) {
71+
func LoadOrNewBlockchain(db dbm.DB, genesisDoc *genesis.GenesisDoc, logger *logging.Logger) (_ *Blockchain, exists bool, _ error) {
7472
logger = logger.WithScope("LoadOrNewBlockchain")
7573
logger.InfoMsg("Trying to load blockchain state from database",
7674
"database_key", stateKey)
7775
bc, err := loadBlockchain(db, genesisDoc)
7876
if err != nil {
79-
return false, nil, fmt.Errorf("error loading blockchain state from database: %v", err)
77+
return nil, false, fmt.Errorf("error loading blockchain state from database: %v", err)
8078
}
8179
if bc != nil {
82-
dbHash := bc.genesisHash
80+
dbHash := bc.GenesisHash()
8381
argHash := genesisDoc.Hash()
8482
if !bytes.Equal(dbHash, argHash) {
85-
return false, nil, fmt.Errorf("GenesisDoc passed to LoadOrNewBlockchain has hash: 0x%X, which does not "+
83+
return nil, false, fmt.Errorf("GenesisDoc passed to LoadOrNewBlockchain has hash: 0x%X, which does not "+
8684
"match the one found in database: 0x%X, database genesis:\n%v\npassed genesis:\n%v\n",
8785
argHash, dbHash, bc.genesisDoc.JSONString(), genesisDoc.JSONString())
8886
}
89-
return true, bc, nil
87+
if bc.LastBlockTime().Before(genesisDoc.GenesisTime) {
88+
return nil, false, fmt.Errorf("LastBlockTime %v from loaded Blockchain is before GenesisTime %v",
89+
bc.LastBlockTime(), genesisDoc.GenesisTime)
90+
}
91+
return bc, true, nil
9092
}
9193

9294
logger.InfoMsg("No existing blockchain state found in database, making new blockchain")
93-
return false, NewBlockchain(db, genesisDoc), nil
95+
return NewBlockchain(db, genesisDoc), false, nil
9496
}
9597

9698
// NewBlockchain returns a pointer to blockchain state initialised from genesis
9799
func NewBlockchain(db dbm.DB, genesisDoc *genesis.GenesisDoc) *Blockchain {
98100
bc := &Blockchain{
99-
db: db,
100-
genesisHash: genesisDoc.Hash(),
101-
genesisDoc: *genesisDoc,
102-
chainID: genesisDoc.ChainID(),
103-
lastBlockTime: genesisDoc.GenesisTime,
104-
appHashAfterLastBlock: genesisDoc.Hash(),
101+
db: db,
102+
persistedState: PersistedState{
103+
AppHashAfterLastBlock: genesisDoc.Hash(),
104+
GenesisHash: genesisDoc.Hash(),
105+
LastBlockTime: genesisDoc.GenesisTime,
106+
},
107+
genesisDoc: *genesisDoc,
105108
}
106109
return bc
107110
}
@@ -131,7 +134,7 @@ func loadBlockchain(db dbm.DB, genesisDoc *genesis.GenesisDoc) (*Blockchain, err
131134
}
132135

133136
func (bc *Blockchain) CommitBlock(blockTime time.Time, blockHash, appHash []byte) error {
134-
return bc.CommitBlockAtHeight(blockTime, blockHash, appHash, bc.lastBlockHeight+1)
137+
return bc.CommitBlockAtHeight(blockTime, blockHash, appHash, bc.persistedState.LastBlockHeight+1)
135138
}
136139

137140
func (bc *Blockchain) CommitBlockAtHeight(blockTime time.Time, blockHash, appHash []byte, height uint64) error {
@@ -144,17 +147,17 @@ func (bc *Blockchain) CommitBlockAtHeight(blockTime time.Time, blockHash, appHas
144147
if err != nil {
145148
return err
146149
}
147-
bc.lastCommitDuration = blockTime.Sub(bc.lastBlockTime)
148-
bc.lastBlockHeight = height
149-
bc.lastBlockTime = blockTime
150+
bc.lastCommitDuration = blockTime.Sub(bc.persistedState.LastBlockTime)
150151
bc.lastBlockHash = blockHash
151-
bc.appHashAfterLastBlock = appHash
152+
bc.persistedState.LastBlockHeight = height
153+
bc.persistedState.LastBlockTime = blockTime
154+
bc.persistedState.AppHashAfterLastBlock = appHash
152155
bc.lastCommitTime = time.Now().UTC()
153156
return nil
154157
}
155158

156159
func (bc *Blockchain) CommitWithAppHash(appHash []byte) error {
157-
bc.appHashAfterLastBlock = appHash
160+
bc.persistedState.AppHashAfterLastBlock = appHash
158161
bc.Lock()
159162
defer bc.Unlock()
160163

@@ -175,43 +178,32 @@ func (bc *Blockchain) save() error {
175178
var cdc = amino.NewCodec()
176179

177180
func (bc *Blockchain) Encode() ([]byte, error) {
178-
persistedState := &PersistedState{
179-
GenesisHash: bc.genesisDoc.Hash(),
180-
AppHashAfterLastBlock: bc.appHashAfterLastBlock,
181-
LastBlockHeight: bc.lastBlockHeight,
182-
}
183-
encodedState, err := cdc.MarshalBinaryBare(persistedState)
181+
encodedState, err := cdc.MarshalBinaryBare(bc.persistedState)
184182
if err != nil {
185183
return nil, err
186184
}
187185
return encodedState, nil
188186
}
189187

190188
func decodeBlockchain(encodedState []byte, genesisDoc *genesis.GenesisDoc) (*Blockchain, error) {
191-
persistedState := new(PersistedState)
192-
err := cdc.UnmarshalBinaryBare(encodedState, persistedState)
189+
bc := NewBlockchain(nil, genesisDoc)
190+
err := cdc.UnmarshalBinaryBare(encodedState, &bc.persistedState)
193191
if err != nil {
194192
return nil, err
195193
}
196-
197-
bc := NewBlockchain(nil, genesisDoc)
198-
bc.genesisHash = persistedState.GenesisHash
199-
//bc.lastBlockHeight = persistedState.LastBlockHeight
200-
bc.lastBlockHeight = persistedState.LastBlockHeight
201-
bc.appHashAfterLastBlock = persistedState.AppHashAfterLastBlock
202194
return bc, nil
203195
}
204196

205197
func (bc *Blockchain) GenesisHash() []byte {
206-
return bc.genesisHash
198+
return bc.persistedState.GenesisHash
207199
}
208200

209201
func (bc *Blockchain) GenesisDoc() genesis.GenesisDoc {
210202
return bc.genesisDoc
211203
}
212204

213205
func (bc *Blockchain) ChainID() string {
214-
return bc.chainID
206+
return bc.genesisDoc.ChainID()
215207
}
216208

217209
func (bc *Blockchain) LastBlockHeight() uint64 {
@@ -220,13 +212,13 @@ func (bc *Blockchain) LastBlockHeight() uint64 {
220212
}
221213
bc.RLock()
222214
defer bc.RUnlock()
223-
return bc.lastBlockHeight
215+
return bc.persistedState.LastBlockHeight
224216
}
225217

226218
func (bc *Blockchain) LastBlockTime() time.Time {
227219
bc.RLock()
228220
defer bc.RUnlock()
229-
return bc.lastBlockTime
221+
return bc.persistedState.LastBlockTime
230222
}
231223

232224
func (bc *Blockchain) LastCommitTime() time.Time {
@@ -250,7 +242,7 @@ func (bc *Blockchain) LastBlockHash() []byte {
250242
func (bc *Blockchain) AppHashAfterLastBlock() []byte {
251243
bc.RLock()
252244
defer bc.RUnlock()
253-
return bc.appHashAfterLastBlock
245+
return bc.persistedState.AppHashAfterLastBlock
254246
}
255247

256248
// Tendermint block access

bcm/blockchain_test.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package bcm
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/hyperledger/burrow/crypto/sha3"
8+
"github.com/hyperledger/burrow/genesis"
9+
"github.com/hyperledger/burrow/logging"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
dbm "github.com/tendermint/tendermint/libs/db"
13+
)
14+
15+
func TestLoadOrNewBlockchain(t *testing.T) {
16+
// Initial load - fresh chain
17+
genesisDoc := newGenesisDoc()
18+
db := dbm.NewMemDB()
19+
blockchain, exists, err := LoadOrNewBlockchain(db, genesisDoc, logging.NewNoopLogger())
20+
require.NoError(t, err)
21+
assert.False(t, exists)
22+
assert.Equal(t, genesisDoc.GenesisTime, blockchain.LastBlockTime())
23+
assert.Equal(t, uint64(0), blockchain.LastBlockHeight())
24+
assert.Equal(t, genesisDoc.Hash(), blockchain.AppHashAfterLastBlock())
25+
26+
// First block
27+
blockTime1 := genesisDoc.GenesisTime.Add(time.Second * 10)
28+
blockHash1 := sha3.Sha3([]byte("blockHash"))
29+
appHash1 := sha3.Sha3([]byte("appHash"))
30+
err = blockchain.CommitBlock(blockTime1, blockHash1, appHash1)
31+
require.NoError(t, err)
32+
assertState(t, blockchain, 1, blockTime1, appHash1)
33+
34+
// Second block
35+
blockTime2a := blockTime1.Add(time.Second * 30)
36+
blockHash2a := sha3.Sha3(append(blockHash1, 2))
37+
appHash2a := sha3.Sha3(append(appHash1, 2))
38+
err = blockchain.CommitBlock(blockTime2a, blockHash2a, appHash2a)
39+
require.NoError(t, err)
40+
assertState(t, blockchain, 2, blockTime2a, appHash2a)
41+
42+
// Load at checkpoint (i.e. first block)
43+
blockchain, exists, err = LoadOrNewBlockchain(db, genesisDoc, logging.NewNoopLogger())
44+
require.NoError(t, err)
45+
assert.True(t, exists)
46+
// Assert first block values
47+
assertState(t, blockchain, 1, blockTime1, appHash1)
48+
49+
// Commit (overwriting previous block 2 pointer
50+
blockTime2b := blockTime1.Add(time.Second * 30)
51+
blockHash2b := sha3.Sha3(append(blockHash1, 2))
52+
appHash2b := sha3.Sha3(append(appHash1, 2))
53+
err = blockchain.CommitBlock(blockTime2b, blockHash2b, appHash2b)
54+
require.NoError(t, err)
55+
assertState(t, blockchain, 2, blockTime2b, appHash2b)
56+
57+
// Commit again to check things are okay
58+
blockTime3 := blockTime2b.Add(time.Second * 30)
59+
blockHash3 := sha3.Sha3(append(blockHash2b, 2))
60+
appHash3 := sha3.Sha3(append(appHash2b, 2))
61+
err = blockchain.CommitBlock(blockTime3, blockHash3, appHash3)
62+
require.NoError(t, err)
63+
assertState(t, blockchain, 3, blockTime3, appHash3)
64+
65+
// Load at checkpoint (i.e. block 2b)
66+
blockchain, exists, err = LoadOrNewBlockchain(db, genesisDoc, logging.NewNoopLogger())
67+
require.NoError(t, err)
68+
assert.True(t, exists)
69+
// Assert first block values
70+
assertState(t, blockchain, 2, blockTime2b, appHash2b)
71+
}
72+
73+
func assertState(t *testing.T, blockchain *Blockchain, height uint64, blockTime time.Time, appHash []byte) {
74+
assert.Equal(t, height, blockchain.LastBlockHeight())
75+
assert.Equal(t, blockTime, blockchain.LastBlockTime())
76+
assert.Equal(t, appHash, blockchain.AppHashAfterLastBlock())
77+
}
78+
79+
func newGenesisDoc() *genesis.GenesisDoc {
80+
genesisDoc, _, _ := genesis.NewDeterministicGenesis(3450976).GenesisDoc(23, true, 4, 10, true, 4)
81+
return genesisDoc
82+
}

0 commit comments

Comments
 (0)