diff --git a/process/process.go b/process/process.go index fa207365..72feee5d 100644 --- a/process/process.go +++ b/process/process.go @@ -49,6 +49,7 @@ type Blockchain interface { InsertBlockAtHeight(block.Height, block.Block) BlockAtHeight(block.Height) (block.Block, bool) BlockExistsAtHeight(block.Height) bool + LatestBaseBlock() block.Block } // A SaveRestorer defines a storage interface for the State. @@ -756,10 +757,7 @@ func (p *Process) syncLatestCommit(latestCommit LatestCommit) { // Validate the commits signatories := map[id.Signatory]struct{}{} - baseBlock, ok := p.blockchain.BlockAtHeight(0) - if !ok { - panic("no genesis block") - } + baseBlock := p.blockchain.LatestBaseBlock() for _, sig := range baseBlock.Header().Signatories() { signatories[sig] = struct{}{} } diff --git a/replica/rebase.go b/replica/rebase.go index 50b0af3b..7ab84c39 100644 --- a/replica/rebase.go +++ b/replica/rebase.go @@ -23,6 +23,11 @@ type BlockIterator interface { // NextBlock returns the `block.Txs`, `block.Plan` and the parent // `block.State` for the given `block.Height`. NextBlock(block.Kind, block.Height, Shard) (block.Txs, block.Plan, block.State) + + // BaseBlocksInRange must return an upper bound estimate for the number of + // base blocks between two blocks (identified by their block hash). This is + // used to prevent forking by old signatories. + BaseBlocksInRange(begin, end id.Hash) int } type Validator interface { diff --git a/replica/replica.go b/replica/replica.go index 7601c280..b27ee8d2 100644 --- a/replica/replica.go +++ b/replica/replica.go @@ -195,6 +195,23 @@ func (replica *Replica) HandleMessage(m Message) { return } } + if m.Message.Type() == process.ProposeMessageType { + if m.Message.Height() > replica.p.CurrentHeight()+1 { + // If the Propose is not at the next height, then we need to make + // sure that no base blocks have been missed. Otherwise, reject the + // Propose, and wait until the appropriate one has been seen. + baseBlockHash := replica.blockStorage.LatestBaseBlock(m.Shard).Hash() + blockHash := m.Message.BlockHash() + numBlocks := replica.rebaser.blockIterator.BaseBlocksInRange(baseBlockHash, blockHash) + if numBlocks > 0 { + // We have missed a base block, so we reject the Propose. This + // assumes that the first Propose after the base block will + // eventually be seen by the Process, as per the underlying + // network assumptions. + return + } + } + } // Check that the Message sender is from our Shard (this can be a moderately // expensive operation, so we cache the result until a new `block.Base` is diff --git a/replica/replica_suite_test.go b/replica/replica_suite_test.go index 19ed69f0..5744dcc3 100644 --- a/replica/replica_suite_test.go +++ b/replica/replica_suite_test.go @@ -75,6 +75,10 @@ func (m mockBlockIterator) NextBlock(kind block.Kind, height block.Height, shard return RandomBytesSlice(), RandomBytesSlice(), RandomBytesSlice() } +func (m mockBlockIterator) BaseBlocksInRange(begin, end id.Hash) int { + return 0 // mockBlockIterator does not support rebasing. +} + type mockValidator struct { valid error } diff --git a/testutil/process.go b/testutil/process.go index fcdf7644..ebf39979 100644 --- a/testutil/process.go +++ b/testutil/process.go @@ -281,6 +281,17 @@ func (bc *MockBlockchain) BlockAtHeight(height block.Height) (block.Block, bool) return block, ok } +func (bc *MockBlockchain) LatestBaseBlock() block.Block { + bc.mu.RLock() + defer bc.mu.RUnlock() + + block, ok := bc.blocks[0] + if !ok { + panic("no genesis block") + } + return block +} + func (bc *MockBlockchain) StateAtHeight(height block.Height) (block.State, bool) { bc.mu.RLock() defer bc.mu.RUnlock() diff --git a/testutil/replica/replica.go b/testutil/replica/replica.go index 70a8c67a..e973be02 100644 --- a/testutil/replica/replica.go +++ b/testutil/replica/replica.go @@ -78,6 +78,10 @@ func (m *MockBlockIterator) NextBlock(kind block.Kind, height block.Height, shar } } +func (m *MockBlockIterator) BaseBlocksInRange(begin, end id.Hash) int { + return 0 // MockBlockIterator does not support rebasing. +} + type MockValidator struct { store *MockPersistentStorage }