Browse Source

s/Validation/Commit/g

pull/206/merge
Jae Kwon 9 years ago
parent
commit
f17c4c1d57
13 changed files with 206 additions and 234 deletions
  1. +3
    -2
      blockchain/pool.go
  2. +4
    -4
      blockchain/reactor.go
  3. +30
    -30
      blockchain/store.go
  4. +7
    -7
      consensus/reactor.go
  5. +34
    -2
      consensus/replay_test.go
  6. +14
    -14
      consensus/state.go
  7. +10
    -10
      state/execution.go
  8. +0
    -61
      types/README.md
  9. +74
    -74
      types/block.go
  10. +13
    -13
      types/validator_set.go
  11. +2
    -2
      types/vote.go
  12. +5
    -5
      types/vote_set.go
  13. +10
    -10
      types/vote_set_test.go

+ 3
- 2
blockchain/pool.go View File

@ -149,8 +149,9 @@ func (pool *BlockPool) IsCaughtUp() bool {
return isCaughtUp return isCaughtUp
} }
// We need to see the second block's Validation to validate the first block.
// We need to see the second block's Commit to validate the first block.
// So we peek two blocks at a time. // So we peek two blocks at a time.
// The caller will verify the commit.
func (pool *BlockPool) PeekTwoBlocks() (first *types.Block, second *types.Block) { func (pool *BlockPool) PeekTwoBlocks() (first *types.Block, second *types.Block) {
pool.mtx.Lock() // Lock pool.mtx.Lock() // Lock
defer pool.mtx.Unlock() defer pool.mtx.Unlock()
@ -165,7 +166,7 @@ func (pool *BlockPool) PeekTwoBlocks() (first *types.Block, second *types.Block)
} }
// Pop the first block at pool.height // Pop the first block at pool.height
// It must have been validated by 'second'.Validation from PeekTwoBlocks().
// It must have been validated by 'second'.Commit from PeekTwoBlocks().
func (pool *BlockPool) PopRequest() { func (pool *BlockPool) PopRequest() {
pool.mtx.Lock() // Lock pool.mtx.Lock() // Lock
defer pool.mtx.Unlock() defer pool.mtx.Unlock()


+ 4
- 4
blockchain/reactor.go View File

@ -222,9 +222,9 @@ FOR_LOOP:
} }
firstParts := first.MakePartSet() firstParts := first.MakePartSet()
firstPartsHeader := firstParts.Header() firstPartsHeader := firstParts.Header()
// Finally, verify the first block using the second's validation.
err := bcR.state.Validators.VerifyValidation(
bcR.state.ChainID, first.Hash(), firstPartsHeader, first.Height, second.LastValidation)
// Finally, verify the first block using the second's commit
err := bcR.state.Validators.VerifyCommit(
bcR.state.ChainID, first.Hash(), firstPartsHeader, first.Height, second.LastCommit)
if err != nil { if err != nil {
log.Info("error in validation", "error", err) log.Info("error in validation", "error", err)
bcR.pool.RedoRequest(first.Height) bcR.pool.RedoRequest(first.Height)
@ -243,7 +243,7 @@ FOR_LOOP:
PanicQ(Fmt("Failed to commit block at application: %v", err)) PanicQ(Fmt("Failed to commit block at application: %v", err))
} }
*/ */
bcR.store.SaveBlock(first, firstParts, second.LastValidation)
bcR.store.SaveBlock(first, firstParts, second.LastCommit)
bcR.state.Save() bcR.state.Save()
} }
} }


+ 30
- 30
blockchain/store.go View File

@ -18,11 +18,11 @@ Simple low level store for blocks.
There are three types of information stored: There are three types of information stored:
- BlockMeta: Meta information about each block - BlockMeta: Meta information about each block
- Block part: Parts of each block, aggregated w/ PartSet - Block part: Parts of each block, aggregated w/ PartSet
- Validation: The Validation part of each block, for gossiping precommit votes
- Commit: The commit part of each block, for gossiping precommit votes
Currently the precommit signatures are duplicated in the Block parts as Currently the precommit signatures are duplicated in the Block parts as
well as the Validation. In the future this may change, perhaps by moving
the Validation data outside the Block.
well as the Commit. In the future this may change, perhaps by moving
the Commit data outside the Block.
Panics indicate probable corruption in the data Panics indicate probable corruption in the data
*/ */
@ -104,42 +104,42 @@ func (bs *BlockStore) LoadBlockMeta(height int) *types.BlockMeta {
} }
// The +2/3 and other Precommit-votes for block at `height`. // The +2/3 and other Precommit-votes for block at `height`.
// This Validation comes from block.LastValidation for `height+1`.
func (bs *BlockStore) LoadBlockValidation(height int) *types.Validation {
// This Commit comes from block.LastCommit for `height+1`.
func (bs *BlockStore) LoadBlockCommit(height int) *types.Commit {
var n int var n int
var err error var err error
r := bs.GetReader(calcBlockValidationKey(height))
r := bs.GetReader(calcBlockCommitKey(height))
if r == nil { if r == nil {
return nil return nil
} }
validation := wire.ReadBinary(&types.Validation{}, r, 0, &n, &err).(*types.Validation)
commit := wire.ReadBinary(&types.Commit{}, r, 0, &n, &err).(*types.Commit)
if err != nil { if err != nil {
PanicCrisis(Fmt("Error reading validation: %v", err))
PanicCrisis(Fmt("Error reading commit: %v", err))
} }
return validation
return commit
} }
// NOTE: the Precommit-vote heights are for the block at `height` // NOTE: the Precommit-vote heights are for the block at `height`
func (bs *BlockStore) LoadSeenValidation(height int) *types.Validation {
func (bs *BlockStore) LoadSeenCommit(height int) *types.Commit {
var n int var n int
var err error var err error
r := bs.GetReader(calcSeenValidationKey(height))
r := bs.GetReader(calcSeenCommitKey(height))
if r == nil { if r == nil {
return nil return nil
} }
validation := wire.ReadBinary(&types.Validation{}, r, 0, &n, &err).(*types.Validation)
commit := wire.ReadBinary(&types.Commit{}, r, 0, &n, &err).(*types.Commit)
if err != nil { if err != nil {
PanicCrisis(Fmt("Error reading validation: %v", err))
PanicCrisis(Fmt("Error reading commit: %v", err))
} }
return validation
return commit
} }
// blockParts: Must be parts of the block
// seenValidation: The +2/3 precommits that were seen which committed at height.
// If all the nodes restart after committing a block,
// we need this to reload the precommits to catch-up nodes to the
// most recent height. Otherwise they'd stall at H-1.
func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, seenValidation *types.Validation) {
// blockParts: Must be parts of the block
// seenCommit: The +2/3 precommits that were seen which committed at height.
// If all the nodes restart after committing a block,
// we need this to reload the precommits to catch-up nodes to the
// most recent height. Otherwise they'd stall at H-1.
func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) {
height := block.Height height := block.Height
if height != bs.height+1 { if height != bs.height+1 {
PanicSanity(Fmt("BlockStore can only save contiguous blocks. Wanted %v, got %v", bs.height+1, height)) PanicSanity(Fmt("BlockStore can only save contiguous blocks. Wanted %v, got %v", bs.height+1, height))
@ -158,13 +158,13 @@ func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, s
bs.saveBlockPart(height, i, blockParts.GetPart(i)) bs.saveBlockPart(height, i, blockParts.GetPart(i))
} }
// Save block validation (duplicate and separate from the Block)
blockValidationBytes := wire.BinaryBytes(block.LastValidation)
bs.db.Set(calcBlockValidationKey(height-1), blockValidationBytes)
// Save block commit (duplicate and separate from the Block)
blockCommitBytes := wire.BinaryBytes(block.LastCommit)
bs.db.Set(calcBlockCommitKey(height-1), blockCommitBytes)
// Save seen validation (seen +2/3 precommits for block)
seenValidationBytes := wire.BinaryBytes(seenValidation)
bs.db.Set(calcSeenValidationKey(height), seenValidationBytes)
// Save seen commit (seen +2/3 precommits for block)
seenCommitBytes := wire.BinaryBytes(seenCommit)
bs.db.Set(calcSeenCommitKey(height), seenCommitBytes)
// Save new BlockStoreStateJSON descriptor // Save new BlockStoreStateJSON descriptor
BlockStoreStateJSON{Height: height}.Save(bs.db) BlockStoreStateJSON{Height: height}.Save(bs.db)
@ -191,12 +191,12 @@ func calcBlockPartKey(height int, partIndex int) []byte {
return []byte(fmt.Sprintf("P:%v:%v", height, partIndex)) return []byte(fmt.Sprintf("P:%v:%v", height, partIndex))
} }
func calcBlockValidationKey(height int) []byte {
return []byte(fmt.Sprintf("V:%v", height))
func calcBlockCommitKey(height int) []byte {
return []byte(fmt.Sprintf("C:%v", height))
} }
func calcSeenValidationKey(height int) []byte {
return []byte(fmt.Sprintf("SV:%v", height))
func calcSeenCommitKey(height int) []byte {
return []byte(fmt.Sprintf("SC:%v", height))
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------


+ 7
- 7
consensus/reactor.go View File

@ -488,14 +488,14 @@ OUTER_LOOP:
} }
// Catchup logic // Catchup logic
// If peer is lagging by more than 1, send Validation.
// If peer is lagging by more than 1, send Commit.
if prs.Height != 0 && rs.Height >= prs.Height+2 { if prs.Height != 0 && rs.Height >= prs.Height+2 {
// Load the block validation for prs.Height,
// Load the block commit for prs.Height,
// which contains precommit signatures for prs.Height. // which contains precommit signatures for prs.Height.
validation := conR.blockStore.LoadBlockValidation(prs.Height)
log.Info("Loaded BlockValidation for catch-up", "height", prs.Height, "validation", validation)
if ps.PickSendVote(validation) {
log.Info("Picked Catchup validation to send")
commit := conR.blockStore.LoadBlockCommit(prs.Height)
log.Info("Loaded BlockCommit for catch-up", "height", prs.Height, "commit", commit)
if ps.PickSendVote(commit) {
log.Info("Picked Catchup commit to send")
continue OUTER_LOOP continue OUTER_LOOP
} }
} }
@ -696,7 +696,7 @@ func (ps *PeerState) ensureCatchupCommitRound(height, round int, numValidators i
} }
/* /*
NOTE: This is wrong, 'round' could change. NOTE: This is wrong, 'round' could change.
e.g. if orig round is not the same as block LastValidation round.
e.g. if orig round is not the same as block LastCommit round.
if ps.CatchupCommitRound != -1 && ps.CatchupCommitRound != round { if ps.CatchupCommitRound != -1 && ps.CatchupCommitRound != round {
PanicSanity(Fmt("Conflicting CatchupCommitRound. Height: %v, Orig: %v, New: %v", height, ps.CatchupCommitRound, round)) PanicSanity(Fmt("Conflicting CatchupCommitRound. Height: %v, Orig: %v, New: %v", height, ps.CatchupCommitRound, round))
} }


+ 34
- 2
consensus/replay_test.go View File

@ -9,14 +9,46 @@ import (
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
) )
/*
If you need to change the signatures, you can use a script as follows:
The privBytes comes from config/tendermint_test/...
```
package main
import (
"encoding/hex"
"fmt"
"github.com/tendermint/go-crypto"
)
func main() {
signBytes, err := hex.DecodeString("7B22636861696E5F6964223A2274656E6465726D696E745F74657374222C22766F7465223A7B22626C6F636B5F68617368223A2242453544373939433846353044354645383533364334333932464443384537423342313830373638222C22626C6F636B5F70617274735F686561646572223A506172745365747B543A31204236323237323535464632307D2C22686569676874223A312C22726F756E64223A302C2274797065223A327D7D")
if err != nil {
panic(err)
}
privBytes, err := hex.DecodeString("27F82582AEFAE7AB151CFB01C48BB6C1A0DA78F9BDDA979A9F70A84D074EB07D3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8")
if err != nil {
panic(err)
}
privKey := crypto.PrivKeyEd25519{}
copy(privKey[:], privBytes)
signature := privKey.Sign(signBytes)
signatureEd25519 := signature.(crypto.SignatureEd25519)
fmt.Printf("Signature Bytes: %X\n", signatureEd25519[:])
}
```
*/
var testLog = `{"time":"2016-01-18T20:46:00.774Z","msg":[3,{"duration":982632969,"height":1,"round":0,"step":1}]} var testLog = `{"time":"2016-01-18T20:46:00.774Z","msg":[3,{"duration":982632969,"height":1,"round":0,"step":1}]}
{"time":"2016-01-18T20:46:00.776Z","msg":[1,{"height":1,"round":0,"step":"RoundStepPropose"}]} {"time":"2016-01-18T20:46:00.776Z","msg":[1,{"height":1,"round":0,"step":"RoundStepPropose"}]}
{"time":"2016-01-18T20:46:00.776Z","msg":[2,{"msg":[17,{"Proposal":{"height":1,"round":0,"block_parts_header":{"total":1,"hash":"B6227255FF20758326B0B7DFF529F20E33E58F45"},"pol_round":-1,"signature":"A1803A1364F6398C154FE45D5649A89129039F18A0FE42B211BADFDF6E81EA53F48F83D3610DDD848C3A5284D3F09BDEB26FA1D856BDF70D48C507BF2453A70E"}}],"peer_key":""}]} {"time":"2016-01-18T20:46:00.776Z","msg":[2,{"msg":[17,{"Proposal":{"height":1,"round":0,"block_parts_header":{"total":1,"hash":"B6227255FF20758326B0B7DFF529F20E33E58F45"},"pol_round":-1,"signature":"A1803A1364F6398C154FE45D5649A89129039F18A0FE42B211BADFDF6E81EA53F48F83D3610DDD848C3A5284D3F09BDEB26FA1D856BDF70D48C507BF2453A70E"}}],"peer_key":""}]}
{"time":"2016-01-18T20:46:00.777Z","msg":[2,{"msg":[19,{"Height":1,"Round":0,"Part":{"index":0,"bytes":"0101010F74656E6465726D696E745F746573740101142AA030B15DDFC000000000000000000000000000000114C4B01D3810579550997AC5641E759E20D99B51C10001000100","proof":{"aunts":[]}}}],"peer_key":""}]} {"time":"2016-01-18T20:46:00.777Z","msg":[2,{"msg":[19,{"Height":1,"Round":0,"Part":{"index":0,"bytes":"0101010F74656E6465726D696E745F746573740101142AA030B15DDFC000000000000000000000000000000114C4B01D3810579550997AC5641E759E20D99B51C10001000100","proof":{"aunts":[]}}}],"peer_key":""}]}
{"time":"2016-01-18T20:46:00.781Z","msg":[1,{"height":1,"round":0,"step":"RoundStepPrevote"}]} {"time":"2016-01-18T20:46:00.781Z","msg":[1,{"height":1,"round":0,"step":"RoundStepPrevote"}]}
{"time":"2016-01-18T20:46:00.781Z","msg":[2,{"msg":[20,{"ValidatorIndex":0,"Vote":{"height":1,"round":0,"type":1,"block_hash":"E05D1DB8DEC7CDA507A42C8FF208EE4317C663F6","block_parts_header":{"total":1,"hash":"B6227255FF20758326B0B7DFF529F20E33E58F45"},"signature":"88F5708C802BEE54EFBF438967FBC6C6EAAFC41258A85D92B9B055481175BE9FA71007B1AAF2BFBC3BF3CC0542DB48A9812324B7BBA7307446CCDBF029077F07"}}],"peer_key":""}]}
{"time":"2016-01-18T20:46:00.781Z","msg":[2,{"msg":[20,{"ValidatorIndex":0,"Vote":{"height":1,"round":0,"type":1,"block_hash":"BE5D799C8F50D5FE8536C4392FDC8E7B3B180768","block_parts_header":{"total":1,"hash":"B6227255FF20758326B0B7DFF529F20E33E58F45"},"signature":"A63A15D9155C6C97762E65559E8CD34D853ED5FE22DA8540519453F9B7BE23C9287110929EDAA430DA30183768C60D4CF2811128C9D2E4B6A173EFE28FB54F05"}}],"peer_key":""}]}
{"time":"2016-01-18T20:46:00.786Z","msg":[1,{"height":1,"round":0,"step":"RoundStepPrecommit"}]} {"time":"2016-01-18T20:46:00.786Z","msg":[1,{"height":1,"round":0,"step":"RoundStepPrecommit"}]}
{"time":"2016-01-18T20:46:00.786Z","msg":[2,{"msg":[20,{"ValidatorIndex":0,"Vote":{"height":1,"round":0,"type":2,"block_hash":"E05D1DB8DEC7CDA507A42C8FF208EE4317C663F6","block_parts_header":{"total":1,"hash":"B6227255FF20758326B0B7DFF529F20E33E58F45"},"signature":"65B0C9D2A8C9919FC9B036F82C3F1818E706E8BC066A78D99D3316E4814AB06594841E387B323AA7773F926D253C1E4D4A0930F7A8C8AE1E838CA15C673B2B02"}}],"peer_key":""}]}
{"time":"2016-01-18T20:46:00.786Z","msg":[2,{"msg":[20,{"ValidatorIndex":0,"Vote":{"height":1,"round":0,"type":2,"block_hash":"BE5D799C8F50D5FE8536C4392FDC8E7B3B180768","block_parts_header":{"total":1,"hash":"B6227255FF20758326B0B7DFF529F20E33E58F45"},"signature":"7190035C558F7173946581E6121E1EEB6EE91F03F04F2858FD5CBDAC006521FDC50366ED920B69C7357D9EF7621F9578F00E5350757CBC0AE6D441C39170CC07"}}],"peer_key":""}]}
` `
func TestReplayCatchup(t *testing.T) { func TestReplayCatchup(t *testing.T) {


+ 14
- 14
consensus/state.go View File

@ -432,15 +432,15 @@ func (cs *ConsensusState) sendInternalMessage(mi msgInfo) {
} }
} }
// Reconstruct LastCommit from SeenValidation, which we saved along with the block,
// Reconstruct LastCommit from SeenCommit, which we saved along with the block,
// (which happens even before saving the state) // (which happens even before saving the state)
func (cs *ConsensusState) reconstructLastCommit(state *sm.State) { func (cs *ConsensusState) reconstructLastCommit(state *sm.State) {
if state.LastBlockHeight == 0 { if state.LastBlockHeight == 0 {
return return
} }
seenValidation := cs.blockStore.LoadSeenValidation(state.LastBlockHeight)
lastPrecommits := types.NewVoteSet(state.LastBlockHeight, seenValidation.Round(), types.VoteTypePrecommit, state.LastValidators)
for idx, precommit := range seenValidation.Precommits {
seenCommit := cs.blockStore.LoadSeenCommit(state.LastBlockHeight)
lastPrecommits := types.NewVoteSet(state.LastBlockHeight, seenCommit.Round(), types.VoteTypePrecommit, state.LastValidators)
for idx, precommit := range seenCommit.Precommits {
if precommit == nil { if precommit == nil {
continue continue
} }
@ -863,17 +863,17 @@ func (cs *ConsensusState) isProposalComplete() bool {
// Returns nil block upon error. // Returns nil block upon error.
// NOTE: keep it side-effect free for clarity. // NOTE: keep it side-effect free for clarity.
func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts *types.PartSet) { func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts *types.PartSet) {
var validation *types.Validation
var commit *types.Commit
if cs.Height == 1 { if cs.Height == 1 {
// We're creating a proposal for the first block. // We're creating a proposal for the first block.
// The validation is empty, but not nil.
validation = &types.Validation{}
// The commit is empty, but not nil.
commit = &types.Commit{}
} else if cs.LastCommit.HasTwoThirdsMajority() { } else if cs.LastCommit.HasTwoThirdsMajority() {
// Make the validation from LastCommit
validation = cs.LastCommit.MakeValidation()
// Make the commit from LastCommit
commit = cs.LastCommit.MakeCommit()
} else { } else {
// This shouldn't happen. // This shouldn't happen.
log.Error("enterPropose: Cannot propose anything: No validation for the previous block.")
log.Error("enterPropose: Cannot propose anything: No commit for the previous block.")
return return
} }
@ -892,7 +892,7 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts
ValidatorsHash: cs.state.Validators.Hash(), ValidatorsHash: cs.state.Validators.Hash(),
AppHash: cs.state.AppHash, // state merkle root of txs from the previous block. AppHash: cs.state.AppHash, // state merkle root of txs from the previous block.
}, },
LastValidation: validation,
LastCommit: commit,
Data: &types.Data{ Data: &types.Data{
Txs: txs, Txs: txs,
}, },
@ -1222,9 +1222,9 @@ func (cs *ConsensusState) finalizeCommit(height int) {
// Save to blockStore. // Save to blockStore.
if cs.blockStore.Height() < block.Height { if cs.blockStore.Height() < block.Height {
commits := cs.Votes.Precommits(cs.CommitRound)
seenValidation := commits.MakeValidation()
cs.blockStore.SaveBlock(block, blockParts, seenValidation)
precommits := cs.Votes.Precommits(cs.CommitRound)
seenCommit := precommits.MakeCommit()
cs.blockStore.SaveBlock(block, blockParts, seenCommit)
} }
/* /*


+ 10
- 10
state/execution.go View File

@ -118,18 +118,18 @@ func (s *State) validateBlock(block *types.Block) error {
return err return err
} }
// Validate block LastValidation.
// Validate block LastCommit.
if block.Height == 1 { if block.Height == 1 {
if len(block.LastValidation.Precommits) != 0 {
return errors.New("Block at height 1 (first block) should have no LastValidation precommits")
if len(block.LastCommit.Precommits) != 0 {
return errors.New("Block at height 1 (first block) should have no LastCommit precommits")
} }
} else { } else {
if len(block.LastValidation.Precommits) != s.LastValidators.Size() {
return fmt.Errorf("Invalid block validation size. Expected %v, got %v",
s.LastValidators.Size(), len(block.LastValidation.Precommits))
if len(block.LastCommit.Precommits) != s.LastValidators.Size() {
return fmt.Errorf("Invalid block commit size. Expected %v, got %v",
s.LastValidators.Size(), len(block.LastCommit.Precommits))
} }
err := s.LastValidators.VerifyValidation(
s.ChainID, s.LastBlockHash, s.LastBlockParts, block.Height-1, block.LastValidation)
err := s.LastValidators.VerifyCommit(
s.ChainID, s.LastBlockHash, s.LastBlockParts, block.Height-1, block.LastCommit)
if err != nil { if err != nil {
return err return err
} }
@ -139,11 +139,11 @@ func (s *State) validateBlock(block *types.Block) error {
} }
// Updates the LastCommitHeight of the validators in valSet, in place. // Updates the LastCommitHeight of the validators in valSet, in place.
// Assumes that lastValSet matches the valset of block.LastValidation
// Assumes that lastValSet matches the valset of block.LastCommit
// CONTRACT: lastValSet is not mutated. // CONTRACT: lastValSet is not mutated.
func updateValidatorsWithBlock(lastValSet *types.ValidatorSet, valSet *types.ValidatorSet, block *types.Block) { func updateValidatorsWithBlock(lastValSet *types.ValidatorSet, valSet *types.ValidatorSet, block *types.Block) {
for i, precommit := range block.LastValidation.Precommits {
for i, precommit := range block.LastCommit.Precommits {
if precommit == nil { if precommit == nil {
continue continue
} }


+ 0
- 61
types/README.md View File

@ -1,61 +0,0 @@
# `tendermint/block`
## Block
TODO: document
### Header
### Validation
### Data
## PartSet
PartSet is used to split a byteslice of data into parts (pieces) for transmission.
By splitting data into smaller parts and computing a Merkle root hash on the list,
you can verify that a part is legitimately part of the complete data, and the
part can be forwarded to other peers before all the parts are known. In short,
it's a fast way to propagate a large file over a gossip network.
PartSet was inspired by the LibSwift project.
Usage:
```Go
data := RandBytes(2 << 20) // Something large
partSet := NewPartSetFromData(data)
partSet.Total() // Total number of 4KB parts
partSet.Count() // Equal to the Total, since we already have all the parts
partSet.Hash() // The Merkle root hash
partSet.BitArray() // A BitArray of partSet.Total() 1's
header := partSet.Header() // Send this to the peer
header.Total // Total number of parts
header.Hash // The merkle root hash
// Now we'll reconstruct the data from the parts
partSet2 := NewPartSetFromHeader(header)
partSet2.Total() // Same total as partSet.Total()
partSet2.Count() // Zero, since this PartSet doesn't have any parts yet.
partSet2.Hash() // Same hash as in partSet.Hash()
partSet2.BitArray() // A BitArray of partSet.Total() 0's
// In a gossip network the parts would arrive in arbitrary order, perhaps
// in response to explicit requests for parts, or optimistically in response
// to the receiving peer's partSet.BitArray().
for !partSet2.IsComplete() {
part := receivePartFromGossipNetwork()
added, err := partSet2.AddPart(part)
if err != nil {
// A wrong part,
// the merkle trail does not hash to partSet2.Hash()
} else if !added {
// A duplicate part already received
}
}
data2, _ := ioutil.ReadAll(partSet2.GetReader())
bytes.Equal(data, data2) // true
```

+ 74
- 74
types/block.go View File

@ -15,9 +15,9 @@ import (
const MaxBlockSize = 22020096 // 21MB TODO make it configurable const MaxBlockSize = 22020096 // 21MB TODO make it configurable
type Block struct { type Block struct {
*Header `json:"header"`
*Data `json:"data"`
LastValidation *Validation `json:"last_validation"`
*Header `json:"header"`
*Data `json:"data"`
LastCommit *Commit `json:"last_commit"`
} }
// Basic validation that doesn't involve state data. // Basic validation that doesn't involve state data.
@ -46,11 +46,11 @@ func (b *Block) ValidateBasic(chainID string, lastBlockHeight int, lastBlockHash
if !b.LastBlockParts.Equals(lastBlockParts) { if !b.LastBlockParts.Equals(lastBlockParts) {
return errors.New(Fmt("Wrong Block.Header.LastBlockParts. Expected %v, got %v", lastBlockParts, b.LastBlockParts)) return errors.New(Fmt("Wrong Block.Header.LastBlockParts. Expected %v, got %v", lastBlockParts, b.LastBlockParts))
} }
if !bytes.Equal(b.LastValidationHash, b.LastValidation.Hash()) {
return errors.New(Fmt("Wrong Block.Header.LastValidationHash. Expected %X, got %X", b.LastValidationHash, b.LastValidation.Hash()))
if !bytes.Equal(b.LastCommitHash, b.LastCommit.Hash()) {
return errors.New(Fmt("Wrong Block.Header.LastCommitHash. Expected %X, got %X", b.LastCommitHash, b.LastCommit.Hash()))
} }
if b.Header.Height != 1 { if b.Header.Height != 1 {
if err := b.LastValidation.ValidateBasic(); err != nil {
if err := b.LastCommit.ValidateBasic(); err != nil {
return err return err
} }
} }
@ -65,8 +65,8 @@ func (b *Block) ValidateBasic(chainID string, lastBlockHeight int, lastBlockHash
} }
func (b *Block) FillHeader() { func (b *Block) FillHeader() {
if b.LastValidationHash == nil {
b.LastValidationHash = b.LastValidation.Hash()
if b.LastCommitHash == nil {
b.LastCommitHash = b.LastCommit.Hash()
} }
if b.DataHash == nil { if b.DataHash == nil {
b.DataHash = b.Data.Hash() b.DataHash = b.Data.Hash()
@ -76,7 +76,7 @@ func (b *Block) FillHeader() {
// Computes and returns the block hash. // Computes and returns the block hash.
// If the block is incomplete, block hash is nil for safety. // If the block is incomplete, block hash is nil for safety.
func (b *Block) Hash() []byte { func (b *Block) Hash() []byte {
if b.Header == nil || b.Data == nil || b.LastValidation == nil {
if b.Header == nil || b.Data == nil || b.LastCommit == nil {
return nil return nil
} }
b.FillHeader() b.FillHeader()
@ -115,7 +115,7 @@ func (b *Block) StringIndented(indent string) string {
%s}#%X`, %s}#%X`,
indent, b.Header.StringIndented(indent+" "), indent, b.Header.StringIndented(indent+" "),
indent, b.Data.StringIndented(indent+" "), indent, b.Data.StringIndented(indent+" "),
indent, b.LastValidation.StringIndented(indent+" "),
indent, b.LastCommit.StringIndented(indent+" "),
indent, b.Hash()) indent, b.Hash())
} }
@ -130,17 +130,17 @@ func (b *Block) StringShort() string {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
type Header struct { type Header struct {
ChainID string `json:"chain_id"`
Height int `json:"height"`
Time time.Time `json:"time"`
Fees int64 `json:"fees"`
NumTxs int `json:"num_txs"`
LastBlockHash []byte `json:"last_block_hash"`
LastBlockParts PartSetHeader `json:"last_block_parts"`
LastValidationHash []byte `json:"last_validation_hash"`
DataHash []byte `json:"data_hash"`
ValidatorsHash []byte `json:"validators_hash"`
AppHash []byte `json:"app_hash"` // state merkle root of txs from the previous block
ChainID string `json:"chain_id"`
Height int `json:"height"`
Time time.Time `json:"time"`
Fees int64 `json:"fees"`
NumTxs int `json:"num_txs"`
LastBlockHash []byte `json:"last_block_hash"`
LastBlockParts PartSetHeader `json:"last_block_parts"`
LastCommitHash []byte `json:"last_commit_hash"`
DataHash []byte `json:"data_hash"`
ValidatorsHash []byte `json:"validators_hash"`
AppHash []byte `json:"app_hash"` // state merkle root of txs from the previous block
} }
// NOTE: hash is nil if required fields are missing. // NOTE: hash is nil if required fields are missing.
@ -156,7 +156,7 @@ func (h *Header) Hash() []byte {
"NumTxs": h.NumTxs, "NumTxs": h.NumTxs,
"LastBlock": h.LastBlockHash, "LastBlock": h.LastBlockHash,
"LastBlockParts": h.LastBlockParts, "LastBlockParts": h.LastBlockParts,
"LastValidation": h.LastValidationHash,
"LastCommit": h.LastCommitHash,
"Data": h.DataHash, "Data": h.DataHash,
"Validators": h.ValidatorsHash, "Validators": h.ValidatorsHash,
"App": h.AppHash, "App": h.AppHash,
@ -172,10 +172,10 @@ func (h *Header) StringIndented(indent string) string {
%s Height: %v %s Height: %v
%s Time: %v %s Time: %v
%s Fees: %v %s Fees: %v
%s NumTxs: %v
%s NumTxs: %v
%s LastBlock: %X %s LastBlock: %X
%s LastBlockParts: %v %s LastBlockParts: %v
%s LastValidation: %X
%s LastCommit: %X
%s Data: %X %s Data: %X
%s Validators: %X %s Validators: %X
%s App: %X %s App: %X
@ -187,7 +187,7 @@ func (h *Header) StringIndented(indent string) string {
indent, h.NumTxs, indent, h.NumTxs,
indent, h.LastBlockHash, indent, h.LastBlockHash,
indent, h.LastBlockParts, indent, h.LastBlockParts,
indent, h.LastValidationHash,
indent, h.LastCommitHash,
indent, h.DataHash, indent, h.DataHash,
indent, h.ValidatorsHash, indent, h.ValidatorsHash,
indent, h.AppHash, indent, h.AppHash,
@ -196,8 +196,8 @@ func (h *Header) StringIndented(indent string) string {
//------------------------------------- //-------------------------------------
// NOTE: Validation is empty for height 1, but never nil.
type Validation struct {
// NOTE: Commit is empty for height 1, but never nil.
type Commit struct {
// NOTE: The Precommits are in order of address to preserve the bonded ValidatorSet order. // NOTE: The Precommits are in order of address to preserve the bonded ValidatorSet order.
// Any peer with a block can gossip precommits by index with a peer without recalculating the // Any peer with a block can gossip precommits by index with a peer without recalculating the
// active ValidatorSet. // active ValidatorSet.
@ -209,121 +209,121 @@ type Validation struct {
bitArray *BitArray bitArray *BitArray
} }
func (v *Validation) FirstPrecommit() *Vote {
if len(v.Precommits) == 0 {
func (commit *Commit) FirstPrecommit() *Vote {
if len(commit.Precommits) == 0 {
return nil return nil
} }
if v.firstPrecommit != nil {
return v.firstPrecommit
if commit.firstPrecommit != nil {
return commit.firstPrecommit
} }
for _, precommit := range v.Precommits {
for _, precommit := range commit.Precommits {
if precommit != nil { if precommit != nil {
v.firstPrecommit = precommit
commit.firstPrecommit = precommit
return precommit return precommit
} }
} }
return nil return nil
} }
func (v *Validation) Height() int {
if len(v.Precommits) == 0 {
func (commit *Commit) Height() int {
if len(commit.Precommits) == 0 {
return 0 return 0
} }
return v.FirstPrecommit().Height
return commit.FirstPrecommit().Height
} }
func (v *Validation) Round() int {
if len(v.Precommits) == 0 {
func (commit *Commit) Round() int {
if len(commit.Precommits) == 0 {
return 0 return 0
} }
return v.FirstPrecommit().Round
return commit.FirstPrecommit().Round
} }
func (v *Validation) Type() byte {
func (commit *Commit) Type() byte {
return VoteTypePrecommit return VoteTypePrecommit
} }
func (v *Validation) Size() int {
if v == nil {
func (commit *Commit) Size() int {
if commit == nil {
return 0 return 0
} }
return len(v.Precommits)
return len(commit.Precommits)
} }
func (v *Validation) BitArray() *BitArray {
if v.bitArray == nil {
v.bitArray = NewBitArray(len(v.Precommits))
for i, precommit := range v.Precommits {
v.bitArray.SetIndex(i, precommit != nil)
func (commit *Commit) BitArray() *BitArray {
if commit.bitArray == nil {
commit.bitArray = NewBitArray(len(commit.Precommits))
for i, precommit := range commit.Precommits {
commit.bitArray.SetIndex(i, precommit != nil)
} }
} }
return v.bitArray
return commit.bitArray
} }
func (v *Validation) GetByIndex(index int) *Vote {
return v.Precommits[index]
func (commit *Commit) GetByIndex(index int) *Vote {
return commit.Precommits[index]
} }
func (v *Validation) IsCommit() bool {
if len(v.Precommits) == 0 {
func (commit *Commit) IsCommit() bool {
if len(commit.Precommits) == 0 {
return false return false
} }
return true return true
} }
func (v *Validation) ValidateBasic() error {
if len(v.Precommits) == 0 {
return errors.New("No precommits in validation")
func (commit *Commit) ValidateBasic() error {
if len(commit.Precommits) == 0 {
return errors.New("No precommits in commit")
} }
height, round := v.Height(), v.Round()
for _, precommit := range v.Precommits {
height, round := commit.Height(), commit.Round()
for _, precommit := range commit.Precommits {
// It's OK for precommits to be missing. // It's OK for precommits to be missing.
if precommit == nil { if precommit == nil {
continue continue
} }
// Ensure that all votes are precommits // Ensure that all votes are precommits
if precommit.Type != VoteTypePrecommit { if precommit.Type != VoteTypePrecommit {
return fmt.Errorf("Invalid validation vote. Expected precommit, got %v",
return fmt.Errorf("Invalid commit vote. Expected precommit, got %v",
precommit.Type) precommit.Type)
} }
// Ensure that all heights are the same // Ensure that all heights are the same
if precommit.Height != height { if precommit.Height != height {
return fmt.Errorf("Invalid validation precommit height. Expected %v, got %v",
return fmt.Errorf("Invalid commit precommit height. Expected %v, got %v",
height, precommit.Height) height, precommit.Height)
} }
// Ensure that all rounds are the same // Ensure that all rounds are the same
if precommit.Round != round { if precommit.Round != round {
return fmt.Errorf("Invalid validation precommit round. Expected %v, got %v",
return fmt.Errorf("Invalid commit precommit round. Expected %v, got %v",
round, precommit.Round) round, precommit.Round)
} }
} }
return nil return nil
} }
func (v *Validation) Hash() []byte {
if v.hash == nil {
bs := make([]interface{}, len(v.Precommits))
for i, precommit := range v.Precommits {
func (commit *Commit) Hash() []byte {
if commit.hash == nil {
bs := make([]interface{}, len(commit.Precommits))
for i, precommit := range commit.Precommits {
bs[i] = precommit bs[i] = precommit
} }
v.hash = merkle.SimpleHashFromBinaries(bs)
commit.hash = merkle.SimpleHashFromBinaries(bs)
} }
return v.hash
return commit.hash
} }
func (v *Validation) StringIndented(indent string) string {
if v == nil {
return "nil-Validation"
func (commit *Commit) StringIndented(indent string) string {
if commit == nil {
return "nil-Commit"
} }
precommitStrings := make([]string, len(v.Precommits))
for i, precommit := range v.Precommits {
precommitStrings := make([]string, len(commit.Precommits))
for i, precommit := range commit.Precommits {
precommitStrings[i] = precommit.String() precommitStrings[i] = precommit.String()
} }
return fmt.Sprintf(`Validation{
return fmt.Sprintf(`Commit{
%s Precommits: %v %s Precommits: %v
%s}#%X`, %s}#%X`,
indent, strings.Join(precommitStrings, "\n"+indent+" "), indent, strings.Join(precommitStrings, "\n"+indent+" "),
indent, v.hash)
indent, commit.hash)
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------


+ 13
- 13
types/validator_set.go View File

@ -206,37 +206,37 @@ func (valSet *ValidatorSet) Iterate(fn func(index int, val *Validator) bool) {
} }
// Verify that +2/3 of the set had signed the given signBytes // Verify that +2/3 of the set had signed the given signBytes
func (valSet *ValidatorSet) VerifyValidation(chainID string,
hash []byte, parts PartSetHeader, height int, v *Validation) error {
if valSet.Size() != len(v.Precommits) {
return fmt.Errorf("Invalid validation -- wrong set size: %v vs %v", valSet.Size(), len(v.Precommits))
func (valSet *ValidatorSet) VerifyCommit(chainID string,
hash []byte, parts PartSetHeader, height int, commit *Commit) error {
if valSet.Size() != len(commit.Precommits) {
return fmt.Errorf("Invalid commit -- wrong set size: %v vs %v", valSet.Size(), len(commit.Precommits))
} }
if height != v.Height() {
return fmt.Errorf("Invalid validation -- wrong height: %v vs %v", height, v.Height())
if height != commit.Height() {
return fmt.Errorf("Invalid commit -- wrong height: %v vs %v", height, commit.Height())
} }
talliedVotingPower := int64(0) talliedVotingPower := int64(0)
round := v.Round()
round := commit.Round()
for idx, precommit := range v.Precommits {
for idx, precommit := range commit.Precommits {
// may be nil if validator skipped. // may be nil if validator skipped.
if precommit == nil { if precommit == nil {
continue continue
} }
if precommit.Height != height { if precommit.Height != height {
return fmt.Errorf("Invalid validation -- wrong height: %v vs %v", height, precommit.Height)
return fmt.Errorf("Invalid commit -- wrong height: %v vs %v", height, precommit.Height)
} }
if precommit.Round != round { if precommit.Round != round {
return fmt.Errorf("Invalid validation -- wrong round: %v vs %v", round, precommit.Round)
return fmt.Errorf("Invalid commit -- wrong round: %v vs %v", round, precommit.Round)
} }
if precommit.Type != VoteTypePrecommit { if precommit.Type != VoteTypePrecommit {
return fmt.Errorf("Invalid validation -- not precommit @ index %v", idx)
return fmt.Errorf("Invalid commit -- not precommit @ index %v", idx)
} }
_, val := valSet.GetByIndex(idx) _, val := valSet.GetByIndex(idx)
// Validate signature // Validate signature
precommitSignBytes := SignBytes(chainID, precommit) precommitSignBytes := SignBytes(chainID, precommit)
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) { if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
return fmt.Errorf("Invalid validation -- invalid signature: %v", precommit)
return fmt.Errorf("Invalid commit -- invalid signature: %v", precommit)
} }
if !bytes.Equal(precommit.BlockHash, hash) { if !bytes.Equal(precommit.BlockHash, hash) {
continue // Not an error, but doesn't count continue // Not an error, but doesn't count
@ -251,7 +251,7 @@ func (valSet *ValidatorSet) VerifyValidation(chainID string,
if talliedVotingPower > valSet.TotalVotingPower()*2/3 { if talliedVotingPower > valSet.TotalVotingPower()*2/3 {
return nil return nil
} else { } else {
return fmt.Errorf("Invalid validation -- insufficient voting power: got %v, needed %v",
return fmt.Errorf("Invalid commit -- insufficient voting power: got %v, needed %v",
talliedVotingPower, (valSet.TotalVotingPower()*2/3 + 1)) talliedVotingPower, (valSet.TotalVotingPower()*2/3 + 1))
} }
} }


+ 2
- 2
types/vote.go View File

@ -71,9 +71,9 @@ func (vote *Vote) String() string {
} }
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// TODO: Move blocks/Validation to here?
// TODO: Move blocks/Commit to here?
// Common interface between *consensus.VoteSet and types.Validation
// Common interface between *consensus.VoteSet and types.Commit
type VoteSetReader interface { type VoteSetReader interface {
Height() int Height() int
Round() int Round() int


+ 5
- 5
types/vote_set.go View File

@ -270,16 +270,16 @@ func (voteSet *VoteSet) StringShort() string {
} }
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Validation
// Commit
func (voteSet *VoteSet) MakeValidation() *Validation {
func (voteSet *VoteSet) MakeCommit() *Commit {
if voteSet.type_ != VoteTypePrecommit { if voteSet.type_ != VoteTypePrecommit {
PanicSanity("Cannot MakeValidation() unless VoteSet.Type is VoteTypePrecommit")
PanicSanity("Cannot MakeCommit() unless VoteSet.Type is VoteTypePrecommit")
} }
voteSet.mtx.Lock() voteSet.mtx.Lock()
defer voteSet.mtx.Unlock() defer voteSet.mtx.Unlock()
if len(voteSet.maj23Hash) == 0 { if len(voteSet.maj23Hash) == 0 {
PanicSanity("Cannot MakeValidation() unless a blockhash has +2/3")
PanicSanity("Cannot MakeCommit() unless a blockhash has +2/3")
} }
precommits := make([]*Vote, voteSet.valSet.Size()) precommits := make([]*Vote, voteSet.valSet.Size())
voteSet.valSet.Iterate(func(valIndex int, val *Validator) bool { voteSet.valSet.Iterate(func(valIndex int, val *Validator) bool {
@ -296,7 +296,7 @@ func (voteSet *VoteSet) MakeValidation() *Validation {
precommits[valIndex] = vote precommits[valIndex] = vote
return false return false
}) })
return &Validation{
return &Commit{
Precommits: precommits, Precommits: precommits,
} }
} }

+ 10
- 10
types/vote_set_test.go View File

@ -232,7 +232,7 @@ func TestBadVotes(t *testing.T) {
} }
} }
func TestMakeValidation(t *testing.T) {
func TestMakeCommit(t *testing.T) {
height, round := 1, 0 height, round := 1, 0
voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrecommit, 10, 1) voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrecommit, 10, 1)
blockHash, blockPartsHeader := crypto.CRandBytes(32), PartSetHeader{123, crypto.CRandBytes(32)} blockHash, blockPartsHeader := crypto.CRandBytes(32), PartSetHeader{123, crypto.CRandBytes(32)}
@ -245,8 +245,8 @@ func TestMakeValidation(t *testing.T) {
signAddVote(privValidators[i], vote, voteSet) signAddVote(privValidators[i], vote, voteSet)
} }
// MakeValidation should fail.
AssertPanics(t, "Doesn't have +2/3 majority", func() { voteSet.MakeValidation() })
// MakeCommit should fail.
AssertPanics(t, "Doesn't have +2/3 majority", func() { voteSet.MakeCommit() })
// 7th voted for some other block. // 7th voted for some other block.
{ {
@ -260,16 +260,16 @@ func TestMakeValidation(t *testing.T) {
signAddVote(privValidators[7], vote, voteSet) signAddVote(privValidators[7], vote, voteSet)
} }
validation := voteSet.MakeValidation()
commit := voteSet.MakeCommit()
// Validation should have 10 elements
if len(validation.Precommits) != 10 {
t.Errorf("Validation Precommits should have the same number of precommits as validators")
// Commit should have 10 elements
if len(commit.Precommits) != 10 {
t.Errorf("Commit Precommits should have the same number of precommits as validators")
} }
// Ensure that Validation precommits are ordered.
if err := validation.ValidateBasic(); err != nil {
t.Errorf("Error in Validation.ValidateBasic(): %v", err)
// Ensure that Commit precommits are ordered.
if err := commit.ValidateBasic(); err != nil {
t.Errorf("Error in Commit.ValidateBasic(): %v", err)
} }
} }

Loading…
Cancel
Save