diff --git a/blocks/block.go b/blocks/block.go index 77d636d89..15518d22f 100644 --- a/blocks/block.go +++ b/blocks/block.go @@ -4,7 +4,9 @@ import ( "bytes" "crypto/sha256" "errors" + "fmt" "io" + "strings" "time" . "github.com/tendermint/tendermint/binary" @@ -58,17 +60,16 @@ func (b *Block) ValidateBasic(lastBlockHeight uint32, lastBlockHash []byte) erro } func (b *Block) Hash() []byte { - if b.hash != nil { - return b.hash - } else { + if b.hash == nil { hashes := [][]byte{ b.Header.Hash(), b.Validation.Hash(), b.Data.Hash(), } // Merkle hash from sub-hashes. - return merkle.HashFromHashes(hashes) + b.hash = merkle.HashFromHashes(hashes) } + return b.hash } // Convenience. @@ -88,27 +89,40 @@ func (b *Block) HashesTo(hash []byte) bool { func (b *Block) MakeNextBlock() *Block { return &Block{ Header: Header{ - Network: b.Header.Network, - Height: b.Header.Height + 1, - //Fees: uint64(0), + Network: b.Header.Network, + Height: b.Header.Height + 1, Time: time.Now(), LastBlockHash: b.Hash(), - //ValidationStateHash: nil, - //AccountStateHash: nil, + StateHash: nil, }, } } +func (b *Block) String() string { + return b.StringWithIndent("") +} + +func (b *Block) StringWithIndent(indent string) string { + return fmt.Sprintf(`Block{ +%s %v +%s %v +%s %v +%s}#%X`, + indent, b.Header.StringWithIndent(indent+" "), + indent, b.Validation.StringWithIndent(indent+" "), + indent, b.Data.StringWithIndent(indent+" "), + indent, b.hash) +} + //----------------------------------------------------------------------------- type Header struct { - Network string - Height uint32 - Fees uint64 - Time time.Time - LastBlockHash []byte - ValidationStateHash []byte - AccountStateHash []byte + Network string + Height uint32 + Time time.Time + Fees uint64 + LastBlockHash []byte + StateHash []byte // Volatile hash []byte @@ -119,39 +133,53 @@ func ReadHeader(r io.Reader, n *int64, err *error) (h Header) { return Header{} } return Header{ - Network: ReadString(r, n, err), - Height: ReadUInt32(r, n, err), - Fees: ReadUInt64(r, n, err), - Time: ReadTime(r, n, err), - LastBlockHash: ReadByteSlice(r, n, err), - ValidationStateHash: ReadByteSlice(r, n, err), - AccountStateHash: ReadByteSlice(r, n, err), + Network: ReadString(r, n, err), + Height: ReadUInt32(r, n, err), + Time: ReadTime(r, n, err), + Fees: ReadUInt64(r, n, err), + LastBlockHash: ReadByteSlice(r, n, err), + StateHash: ReadByteSlice(r, n, err), } } func (h *Header) WriteTo(w io.Writer) (n int64, err error) { WriteString(w, h.Network, &n, &err) WriteUInt32(w, h.Height, &n, &err) - WriteUInt64(w, h.Fees, &n, &err) WriteTime(w, h.Time, &n, &err) + WriteUInt64(w, h.Fees, &n, &err) WriteByteSlice(w, h.LastBlockHash, &n, &err) - WriteByteSlice(w, h.ValidationStateHash, &n, &err) - WriteByteSlice(w, h.AccountStateHash, &n, &err) + WriteByteSlice(w, h.StateHash, &n, &err) return } func (h *Header) Hash() []byte { - if h.hash != nil { - return h.hash - } else { + if h.hash == nil { hasher := sha256.New() _, err := h.WriteTo(hasher) if err != nil { panic(err) } h.hash = hasher.Sum(nil) - return h.hash } + return h.hash +} + +func (h *Header) StringWithIndent(indent string) string { + return fmt.Sprintf(`Header{ +%s Network: %v +%s Height: %v +%s Time: %v +%s Fees: %v +%s LastBlockHash: %X +%s StateHash: %X +%s}#%X`, + indent, h.Network, + indent, h.Height, + indent, h.Time, + indent, h.Fees, + indent, h.LastBlockHash, + indent, h.StateHash, + indent, h.hash) } //----------------------------------------------------------------------------- @@ -183,17 +211,26 @@ func (v *Validation) WriteTo(w io.Writer) (n int64, err error) { } func (v *Validation) Hash() []byte { - if v.hash != nil { - return v.hash - } else { - hasher := sha256.New() - _, err := v.WriteTo(hasher) - if err != nil { - panic(err) + if v.hash == nil { + bs := make([]Binary, len(v.Signatures)) + for i, sig := range v.Signatures { + bs[i] = Binary(sig) } - v.hash = hasher.Sum(nil) - return v.hash + v.hash = merkle.HashFromBinaries(bs) + } + return v.hash +} + +func (v *Validation) StringWithIndent(indent string) string { + sigStrings := make([]string, len(v.Signatures)) + for i, sig := range v.Signatures { + sigStrings[i] = sig.String() } + return fmt.Sprintf(`Validation{ +%s %v +%s}#%X`, + indent, strings.Join(sigStrings, "\n"+indent+" "), + indent, v.hash) } //----------------------------------------------------------------------------- @@ -223,14 +260,24 @@ func (data *Data) WriteTo(w io.Writer) (n int64, err error) { } func (data *Data) Hash() []byte { - if data.hash != nil { - return data.hash - } else { - bs := make([]Binary, 0, len(data.Txs)) + if data.hash == nil { + bs := make([]Binary, len(data.Txs)) for i, tx := range data.Txs { bs[i] = Binary(tx) } data.hash = merkle.HashFromBinaries(bs) - return data.hash } + return data.hash +} + +func (data *Data) StringWithIndent(indent string) string { + txStrings := make([]string, len(data.Txs)) + for i, tx := range data.Txs { + txStrings[i] = tx.String() + } + return fmt.Sprintf(`Data{ +%s %v +%s}#%X`, + indent, strings.Join(txStrings, "\n"+indent+" "), + indent, data.hash) } diff --git a/blocks/block_test.go b/blocks/block_test.go index 5c93897c8..474fa9f8a 100644 --- a/blocks/block_test.go +++ b/blocks/block_test.go @@ -15,16 +15,13 @@ func randBaseTx() BaseTx { return BaseTx{0, RandUInt64Exp(), randSig()} } -func TestBlock(t *testing.T) { - +func randBlock() *Block { // Account Txs - sendTx := &SendTx{ BaseTx: randBaseTx(), To: RandUInt64Exp(), Amount: RandUInt64Exp(), } - nameTx := &NameTx{ BaseTx: randBaseTx(), Name: string(RandBytes(12)), @@ -32,16 +29,13 @@ func TestBlock(t *testing.T) { } // Validation Txs - bondTx := &BondTx{ BaseTx: randBaseTx(), //UnbondTo: RandUInt64Exp(), } - unbondTx := &UnbondTx{ BaseTx: randBaseTx(), } - dupeoutTx := &DupeoutTx{ VoteA: Vote{ Height: RandUInt32Exp(), @@ -60,16 +54,14 @@ func TestBlock(t *testing.T) { } // Block - block := &Block{ Header: Header{ - Network: "Tendermint", - Height: RandUInt32Exp(), - Fees: RandUInt64Exp(), - Time: RandTime(), - LastBlockHash: RandBytes(32), - ValidationStateHash: RandBytes(32), - AccountStateHash: RandBytes(32), + Network: "Tendermint", + Height: RandUInt32Exp(), + Fees: RandUInt64Exp(), + Time: RandTime(), + LastBlockHash: RandBytes(32), + StateHash: RandBytes(32), }, Validation: Validation{ Signatures: []Signature{randSig(), randSig()}, @@ -78,22 +70,52 @@ func TestBlock(t *testing.T) { Txs: []Tx{sendTx, nameTx, bondTx, unbondTx, dupeoutTx}, }, } + return block +} - // Write the block, read it in again, write it again. - // Then, compare. - // TODO We should compute the hash instead, so Block -> Bytes -> Block and compare hashes. +func TestBlock(t *testing.T) { - blockBytes := BinaryBytes(block) + block := randBlock() + // Mutate the block and ensure that the hash changed. + lastHash := block.Hash() + expectChange := func(mutateFn func(b *Block), message string) { + // mutate block + mutateFn(block) + // nuke hashes + block.hash = nil + block.Header.hash = nil + block.Validation.hash = nil + block.Data.hash = nil + // compare + if bytes.Equal(lastHash, block.Hash()) { + t.Error(message) + } else { + lastHash = block.Hash() + } + } + expectChange(func(b *Block) { b.Header.Network = "blah" }, "Expected hash to depend on Network") + expectChange(func(b *Block) { b.Header.Height += 1 }, "Expected hash to depend on Height") + expectChange(func(b *Block) { b.Header.Fees += 1 }, "Expected hash to depend on Fees") + expectChange(func(b *Block) { b.Header.Time = RandTime() }, "Expected hash to depend on Time") + expectChange(func(b *Block) { b.Header.LastBlockHash = RandBytes(32) }, "Expected hash to depend on LastBlockHash") + expectChange(func(b *Block) { b.Header.StateHash = RandBytes(32) }, "Expected hash to depend on StateHash") + expectChange(func(b *Block) { b.Validation.Signatures[0].SignerId += 1 }, "Expected hash to depend on Validation Signature") + expectChange(func(b *Block) { b.Validation.Signatures[0].Bytes = RandBytes(32) }, "Expected hash to depend on Validation Signature") + expectChange(func(b *Block) { b.Data.Txs[0].(*SendTx).SignerId += 1 }, "Expected hash to depend on tx Signature") + expectChange(func(b *Block) { b.Data.Txs[0].(*SendTx).Amount += 1 }, "Expected hash to depend on send tx Amount") + + // Write the block, read it in again, check hash. + block1 := randBlock() + block1Bytes := BinaryBytes(block1) var n int64 var err error - block2 := ReadBlock(bytes.NewReader(blockBytes), &n, &err) + block2 := ReadBlock(bytes.NewReader(block1Bytes), &n, &err) if err != nil { t.Errorf("Reading block failed: %v", err) } - - blockBytes2 := BinaryBytes(block2) - - if !bytes.Equal(blockBytes, blockBytes2) { - t.Fatal("Write->Read of block failed.") + if !bytes.Equal(block1.Hash(), block2.Hash()) { + t.Errorf("Expected write/read to preserve original hash") + t.Logf("\nBlock1:\n%v", block1) + t.Logf("\nBlock2:\n%v", block2) } } diff --git a/blocks/codec_test.go b/blocks/codec_test.go index 1339f1b19..32c10769d 100644 --- a/blocks/codec_test.go +++ b/blocks/codec_test.go @@ -15,13 +15,12 @@ func BenchmarkTestCustom(b *testing.B) { b.StopTimer() h := &Header{ - Network: "Header", - Height: 123, - Fees: 123, - Time: time.Unix(123, 0), - LastBlockHash: []byte("prevhash"), - ValidationStateHash: []byte("validationhash"), - AccountStateHash: []byte("accounthash"), + Network: "Header", + Height: 123, + Fees: 123, + Time: time.Unix(123, 0), + LastBlockHash: []byte("prevhash"), + StateHash: []byte("statehash"), } buf := bytes.NewBuffer(nil) @@ -40,26 +39,24 @@ func BenchmarkTestCustom(b *testing.B) { } type HHeader struct { - Network string `json:"N"` - Height uint64 `json:"H"` - Fees uint64 `json:"F"` - Time uint64 `json:"T"` - LastBlockHash []byte `json:"PH"` - ValidationStateHash []byte `json:"VH"` - AccountStateHash []byte `json:"DH"` + Network string `json:"N"` + Height uint64 `json:"H"` + Fees uint64 `json:"F"` + Time uint64 `json:"T"` + LastBlockHash []byte `json:"PH"` + StateHash []byte `json:"SH"` } func BenchmarkTestJSON(b *testing.B) { b.StopTimer() h := &HHeader{ - Network: "Header", - Height: 123, - Fees: 123, - Time: 123, - LastBlockHash: []byte("prevhash"), - ValidationStateHash: []byte("validationhash"), - AccountStateHash: []byte("accounthash"), + Network: "Header", + Height: 123, + Fees: 123, + Time: 123, + LastBlockHash: []byte("prevhash"), + StateHash: []byte("statehash"), } h2 := &HHeader{} @@ -82,13 +79,12 @@ func BenchmarkTestGob(b *testing.B) { b.StopTimer() h := &Header{ - Network: "Header", - Height: 123, - Fees: 123, - Time: time.Unix(123, 0), - LastBlockHash: []byte("prevhash"), - ValidationStateHash: []byte("validationhash"), - AccountStateHash: []byte("datahash"), + Network: "Header", + Height: 123, + Fees: 123, + Time: time.Unix(123, 0), + LastBlockHash: []byte("prevhash"), + StateHash: []byte("statehash"), } h2 := &Header{} @@ -111,13 +107,12 @@ func BenchmarkTestMsgPack(b *testing.B) { b.StopTimer() h := &Header{ - Network: "Header", - Height: 123, - Fees: 123, - Time: time.Unix(123, 0), - LastBlockHash: []byte("prevhash"), - ValidationStateHash: []byte("validationhash"), - AccountStateHash: []byte("datahash"), + Network: "Header", + Height: 123, + Fees: 123, + Time: time.Unix(123, 0), + LastBlockHash: []byte("prevhash"), + StateHash: []byte("statehash"), } h2 := &Header{} @@ -140,13 +135,12 @@ func BenchmarkTestMsgPack2(b *testing.B) { b.StopTimer() h := &Header{ - Network: "Header", - Height: 123, - Fees: 123, - Time: time.Unix(123, 0), - LastBlockHash: []byte("prevhash"), - ValidationStateHash: []byte("validationhash"), - AccountStateHash: []byte("accounthash"), + Network: "Header", + Height: 123, + Fees: 123, + Time: time.Unix(123, 0), + LastBlockHash: []byte("prevhash"), + StateHash: []byte("statehash"), } h2 := &Header{} var mh codec.MsgpackHandle diff --git a/blocks/signature.go b/blocks/signature.go index 33f6f9a47..7c73328b9 100644 --- a/blocks/signature.go +++ b/blocks/signature.go @@ -1,8 +1,10 @@ package blocks import ( - . "github.com/tendermint/tendermint/binary" + "fmt" "io" + + . "github.com/tendermint/tendermint/binary" ) type Signable interface { @@ -35,6 +37,12 @@ func (sig Signature) WriteTo(w io.Writer) (n int64, err error) { return } +func (sig Signature) String() string { + return fmt.Sprintf("Signature{%v:%X}", sig.SignerId, sig.Bytes) +} + +//------------------------------------- + func ReadSignatures(r io.Reader, n *int64, err *error) (sigs []Signature) { length := ReadUInt32(r, n, err) for i := uint32(0); i < length; i++ { diff --git a/blocks/tx.go b/blocks/tx.go index a262d6854..421a599e7 100644 --- a/blocks/tx.go +++ b/blocks/tx.go @@ -1,9 +1,11 @@ package blocks import ( + "fmt" + "io" + . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/common" - "io" ) /* @@ -21,6 +23,7 @@ type Tx interface { Signable GetSequence() uint GetFee() uint64 + String() string } const ( @@ -108,6 +111,10 @@ func (tx *BaseTx) SetSignature(sig Signature) { tx.Signature = sig } +func (tx *BaseTx) String() string { + return fmt.Sprintf("{S:%v F:%v Sig:%X}", tx.Sequence, tx.Fee, tx.Signature) +} + //----------------------------------------------------------------------------- type SendTx struct { @@ -124,6 +131,10 @@ func (tx *SendTx) WriteTo(w io.Writer) (n int64, err error) { return } +func (tx *SendTx) String() string { + return fmt.Sprintf("SendTx{%v To:%v Amount:%v}", tx.BaseTx, tx.To, tx.Amount) +} + //----------------------------------------------------------------------------- type NameTx struct { @@ -140,6 +151,10 @@ func (tx *NameTx) WriteTo(w io.Writer) (n int64, err error) { return } +func (tx *NameTx) String() string { + return fmt.Sprintf("NameTx{%v Name:%v PubKey:%X}", tx.BaseTx, tx.Name, tx.PubKey) +} + //----------------------------------------------------------------------------- type BondTx struct { @@ -154,6 +169,10 @@ func (tx *BondTx) WriteTo(w io.Writer) (n int64, err error) { return } +func (tx *BondTx) String() string { + return fmt.Sprintf("BondTx{%v}", tx.BaseTx) +} + //----------------------------------------------------------------------------- type UnbondTx struct { @@ -166,6 +185,10 @@ func (tx *UnbondTx) WriteTo(w io.Writer) (n int64, err error) { return } +func (tx *UnbondTx) String() string { + return fmt.Sprintf("UnbondTx{%v}", tx.BaseTx) +} + //----------------------------------------------------------------------------- type DupeoutTx struct { @@ -181,3 +204,7 @@ func (tx *DupeoutTx) WriteTo(w io.Writer) (n int64, err error) { WriteBinary(w, &tx.VoteB, &n, &err) return } + +func (tx *DupeoutTx) String() string { + return fmt.Sprintf("DupeoutTx{%v VoteA:%v VoteB:%v}", tx.BaseTx, tx.VoteA, tx.VoteB) +} diff --git a/mempool/mempool.go b/mempool/mempool.go index b721cde21..c2b2b2b68 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -53,8 +53,10 @@ func (mem *Mempool) MakeProposalBlock() (*Block, *State) { return nextBlock, mem.state } -// Txs that are present in block are discarded from mempool. -// Txs that have become invalid in the new state are also discarded. +// "block" is the new block being committed. +// "state" is the result of state.AppendBlock("block"). +// Txs that are present in "block" are discarded from mempool. +// Txs that have become invalid in the new "state" are also discarded. func (mem *Mempool) ResetForBlockAndState(block *Block, state *State) { mem.mtx.Lock() defer mem.mtx.Unlock() diff --git a/merkle/iavl_tree.go b/merkle/iavl_tree.go index 76cb31443..3a5cad5bd 100644 --- a/merkle/iavl_tree.go +++ b/merkle/iavl_tree.go @@ -40,18 +40,24 @@ func NewIAVLTree(keyCodec, valueCodec Codec, cacheSize int, db DB) *IAVLTree { // The returned tree and the original tree are goroutine independent. // That is, they can each run in their own goroutine. func (t *IAVLTree) Copy() Tree { + if t.root == nil { + return &IAVLTree{ + keyCodec: t.keyCodec, + valueCodec: t.valueCodec, + root: nil, + ndb: t.ndb, + } + } if t.ndb != nil && !t.root.persisted { - panic("It is unsafe to Copy() an unpersisted tree.") // Saving a tree finalizes all the nodes. // It sets all the hashes recursively, // clears all the leftNode/rightNode values recursively, // and all the .persisted flags get set. - // On the other hand, in-memory trees (ndb == nil) - // don't mutate + panic("It is unsafe to Copy() an unpersisted tree.") } else if t.ndb == nil && t.root.hash == nil { - panic("An in-memory IAVLTree must be hashed first") // An in-memory IAVLTree is finalized when the hashes are // calculated. + t.root.hashWithCount(t) } return &IAVLTree{ keyCodec: t.keyCodec, diff --git a/state/state.go b/state/state.go index f7b8d8719..bfc9b9b55 100644 --- a/state/state.go +++ b/state/state.go @@ -314,9 +314,12 @@ func (s *State) releaseValidator(accountId uint64) { } } +// "checkStateHash": If false, instead of checking the resulting +// state.Hash() against block.StateHash, it *sets* the block.StateHash. +// (used for constructing a new proposal) // NOTE: If an error occurs during block execution, state will be left // at an invalid state. Copy the state before calling AppendBlock! -func (s *State) AppendBlock(b *Block) error { +func (s *State) AppendBlock(b *Block, checkStateHash bool) error { // Basic block validation. err := b.ValidateBasic(s.Height, s.BlockHash) if err != nil { @@ -373,15 +376,20 @@ func (s *State) AppendBlock(b *Block) error { // Increment validator AccumPowers s.BondedValidators.IncrementAccum() - // State hashes should match - // XXX include UnbondingValidators.Hash(). - if !bytes.Equal(s.BondedValidators.Hash(), b.ValidationStateHash) { - return Errorf("Invalid ValidationStateHash. Got %X, block says %X", - s.BondedValidators.Hash(), b.ValidationStateHash) - } - if !bytes.Equal(s.AccountDetails.Hash(), b.AccountStateHash) { - return Errorf("Invalid AccountStateHash. Got %X, block says %X", - s.AccountDetails.Hash(), b.AccountStateHash) + // Check or set block.StateHash + stateHash := s.Hash() + if checkStateHash { + // State hash should match + if !bytes.Equal(stateHash, b.StateHash) { + return Errorf("Invalid state hash. Got %X, block says %X", + stateHash, b.StateHash) + } + } else { + // Set the state hash. + if b.StateHash != nil { + panic("Cannot overwrite block.StateHash") + } + b.StateHash = stateHash } s.Height = b.Height @@ -401,3 +409,14 @@ func (s *State) GetAccountDetail(accountId uint64) *AccountDetail { func (s *State) SetAccountDetail(accDet *AccountDetail) (updated bool) { return s.AccountDetails.Set(accDet.Id, accDet) } + +// Returns a hash that represents the state data, +// excluding Height, BlockHash, and CommitTime. +func (s *State) Hash() []byte { + hashables := []merkle.Hashable{ + s.AccountDetails, + s.BondedValidators, + s.UnbondingValidators, + } + return merkle.HashFromHashables(hashables) +} diff --git a/state/state_test.go b/state/state_test.go index 66c43fc5d..0d8b686b1 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -35,32 +35,67 @@ func randGenesisState(numAccounts int, numValidators int) *State { } } s0 := GenesisState(db, time.Now(), accountDetails) + s0.Save(time.Now()) return s0 } +func TestCopyState(t *testing.T) { + // Generate a state + s0 := randGenesisState(10, 5) + s0Hash := s0.Hash() + if len(s0Hash) == 0 { + t.Error("Expected state hash") + } + + // Check hash of copy + s0Copy := s0.Copy() + if !bytes.Equal(s0Hash, s0Copy.Hash()) { + t.Error("Expected state copy hash to be the same") + } + + // Mutate the original. + _, accDet_ := s0.AccountDetails.GetByIndex(0) + accDet := accDet_.(*AccountDetail) + if accDet == nil { + t.Error("Expected state to have an account") + } + accDet.Balance += 1 + s0.AccountDetails.Set(accDet.Id, accDet) + if bytes.Equal(s0Hash, s0.Hash()) { + t.Error("Expected state hash to have changed") + } + if !bytes.Equal(s0Hash, s0Copy.Hash()) { + t.Error("Expected state copy hash to have not changed") + } +} + func TestGenesisSaveLoad(t *testing.T) { // Generate a state, save & load it. s0 := randGenesisState(10, 5) - // Figure out what the next state hashes should be. - s0.BondedValidators.Hash() - s0ValsCopy := s0.BondedValidators.Copy() - s0ValsCopy.IncrementAccum() - nextValidationStateHash := s0ValsCopy.Hash() - nextAccountStateHash := s0.AccountDetails.Hash() // Mutate the state to append one empty block. block := &Block{ Header: Header{ - Network: Config.Network, - Height: 1, - ValidationStateHash: nextValidationStateHash, - AccountStateHash: nextAccountStateHash, + Network: Config.Network, + Height: 1, + StateHash: nil, }, Data: Data{ Txs: []Tx{}, }, } - err := s0.AppendBlock(block) + // The second argument to AppendBlock() is false, + // which sets Block.Header.StateHash. + err := s0.Copy().AppendBlock(block, false) + if err != nil { + t.Error("Error appending initial block:", err) + } + if len(block.Header.StateHash) == 0 { + t.Error("Expected StateHash but got nothing.") + } + // Now append the block to s0. + // This time we also check the StateHash (as computed above). + err = s0.AppendBlock(block, true) if err != nil { t.Error("Error appending initial block:", err) } @@ -92,13 +127,28 @@ func TestGenesisSaveLoad(t *testing.T) { if !bytes.Equal(s0.BlockHash, s1.BlockHash) { t.Error("BlockHash mismatch") } - // Compare BondedValidators + // Compare state merkle trees if s0.BondedValidators.Size() != s1.BondedValidators.Size() { t.Error("BondedValidators Size mismatch") } if s0.BondedValidators.TotalVotingPower() != s1.BondedValidators.TotalVotingPower() { t.Error("BondedValidators TotalVotingPower mismatch") } + if bytes.Equal(s0.BondedValidators.Hash(), s1.BondedValidators.Hash()) { + // The BondedValidators hash should have changed because + // each AppendBlock() calls IncrementAccum(), + // changing each validator's Accum. + t.Error("BondedValidators hash should have changed") + } + if s0.UnbondingValidators.Size() != s1.UnbondingValidators.Size() { + t.Error("UnbondingValidators Size mismatch") + } + if s0.UnbondingValidators.TotalVotingPower() != s1.UnbondingValidators.TotalVotingPower() { + t.Error("UnbondingValidators TotalVotingPower mismatch") + } + if !bytes.Equal(s0.UnbondingValidators.Hash(), s1.UnbondingValidators.Hash()) { + t.Error("UnbondingValidators hash mismatch") + } if !bytes.Equal(s0.AccountDetails.Hash(), s1.AccountDetails.Hash()) { t.Error("AccountDetail mismatch") }