Browse Source

fix tests

pull/9/head
Jae Kwon 10 years ago
parent
commit
810aeb7bcb
9 changed files with 288 additions and 107 deletions
  1. +4
    -8
      blocks/block_test.go
  2. +12
    -16
      blocks/tx.go
  3. +1
    -1
      consensus/state.go
  4. +3
    -0
      merkle/iavl_tree.go
  5. +4
    -3
      state/account.go
  6. +192
    -61
      state/state.go
  7. +11
    -11
      state/state_test.go
  8. +16
    -7
      state/validator_set.go
  9. +45
    -0
      state/validator_set_test.go

+ 4
- 8
blocks/block_test.go View File

@ -12,7 +12,7 @@ func randSig() Signature {
} }
func randBaseTx() BaseTx { func randBaseTx() BaseTx {
return BaseTx{0, randSig()}
return BaseTx{0, RandUInt64Exp(), randSig()}
} }
func TestBlock(t *testing.T) { func TestBlock(t *testing.T) {
@ -21,14 +21,12 @@ func TestBlock(t *testing.T) {
sendTx := &SendTx{ sendTx := &SendTx{
BaseTx: randBaseTx(), BaseTx: randBaseTx(),
Fee: RandUInt64Exp(),
To: RandUInt64Exp(), To: RandUInt64Exp(),
Amount: RandUInt64Exp(), Amount: RandUInt64Exp(),
} }
nameTx := &NameTx{ nameTx := &NameTx{
BaseTx: randBaseTx(), BaseTx: randBaseTx(),
Fee: RandUInt64Exp(),
Name: string(RandBytes(12)), Name: string(RandBytes(12)),
PubKey: RandBytes(32), PubKey: RandBytes(32),
} }
@ -36,14 +34,12 @@ func TestBlock(t *testing.T) {
// Validation Txs // Validation Txs
bondTx := &BondTx{ bondTx := &BondTx{
BaseTx: randBaseTx(),
Fee: RandUInt64Exp(),
UnbondTo: RandUInt64Exp(),
BaseTx: randBaseTx(),
//UnbondTo: RandUInt64Exp(),
} }
unbondTx := &UnbondTx{ unbondTx := &UnbondTx{
BaseTx: randBaseTx(), BaseTx: randBaseTx(),
Fee: RandUInt64Exp(),
} }
dupeoutTx := &DupeoutTx{ dupeoutTx := &DupeoutTx{
@ -79,7 +75,7 @@ func TestBlock(t *testing.T) {
Signatures: []Signature{randSig(), randSig()}, Signatures: []Signature{randSig(), randSig()},
}, },
Data: Data{ Data: Data{
Txs: []Tx{sendTx, nameTx, bondTx, unbondTx, timeoutTx, dupeoutTx},
Txs: []Tx{sendTx, nameTx, bondTx, unbondTx, dupeoutTx},
}, },
} }


+ 12
- 16
blocks/tx.go View File

@ -20,6 +20,7 @@ Validation Txs:
type Tx interface { type Tx interface {
Signable Signable
GetSequence() uint GetSequence() uint
GetFee() uint64
} }
const ( const (
@ -38,27 +39,23 @@ func ReadTx(r io.Reader, n *int64, err *error) Tx {
case TxTypeSend: case TxTypeSend:
return &SendTx{ return &SendTx{
BaseTx: ReadBaseTx(r, n, err), BaseTx: ReadBaseTx(r, n, err),
Fee: ReadUInt64(r, n, err),
To: ReadUInt64(r, n, err), To: ReadUInt64(r, n, err),
Amount: ReadUInt64(r, n, err), Amount: ReadUInt64(r, n, err),
} }
case TxTypeName: case TxTypeName:
return &NameTx{ return &NameTx{
BaseTx: ReadBaseTx(r, n, err), BaseTx: ReadBaseTx(r, n, err),
Fee: ReadUInt64(r, n, err),
Name: ReadString(r, n, err), Name: ReadString(r, n, err),
PubKey: ReadByteSlice(r, n, err), PubKey: ReadByteSlice(r, n, err),
} }
case TxTypeBond: case TxTypeBond:
return &BondTx{ return &BondTx{
BaseTx: ReadBaseTx(r, n, err),
Fee: ReadUInt64(r, n, err),
UnbondTo: ReadUInt64(r, n, err),
BaseTx: ReadBaseTx(r, n, err),
//UnbondTo: ReadUInt64(r, n, err),
} }
case TxTypeUnbond: case TxTypeUnbond:
return &UnbondTx{ return &UnbondTx{
BaseTx: ReadBaseTx(r, n, err), BaseTx: ReadBaseTx(r, n, err),
Fee: ReadUInt64(r, n, err),
} }
case TxTypeDupeout: case TxTypeDupeout:
return &DupeoutTx{ return &DupeoutTx{
@ -76,18 +73,21 @@ func ReadTx(r io.Reader, n *int64, err *error) Tx {
type BaseTx struct { type BaseTx struct {
Sequence uint Sequence uint
Fee uint64
Signature Signature
} }
func ReadBaseTx(r io.Reader, n *int64, err *error) BaseTx { func ReadBaseTx(r io.Reader, n *int64, err *error) BaseTx {
return BaseTx{ return BaseTx{
Sequence: ReadUVarInt(r, n, err), Sequence: ReadUVarInt(r, n, err),
Fee: ReadUInt64(r, n, err),
Signature: ReadSignature(r, n, err), Signature: ReadSignature(r, n, err),
} }
} }
func (tx BaseTx) WriteTo(w io.Writer) (n int64, err error) { func (tx BaseTx) WriteTo(w io.Writer) (n int64, err error) {
WriteUVarInt(w, tx.Sequence, &n, &err) WriteUVarInt(w, tx.Sequence, &n, &err)
WriteUInt64(w, tx.Fee, &n, &err)
WriteBinary(w, tx.Signature, &n, &err) WriteBinary(w, tx.Signature, &n, &err)
return return
} }
@ -100,6 +100,10 @@ func (tx *BaseTx) GetSignature() Signature {
return tx.Signature return tx.Signature
} }
func (tx *BaseTx) GetFee() uint64 {
return tx.Fee
}
func (tx *BaseTx) SetSignature(sig Signature) { func (tx *BaseTx) SetSignature(sig Signature) {
tx.Signature = sig tx.Signature = sig
} }
@ -108,7 +112,6 @@ func (tx *BaseTx) SetSignature(sig Signature) {
type SendTx struct { type SendTx struct {
BaseTx BaseTx
Fee uint64
To uint64 To uint64
Amount uint64 Amount uint64
} }
@ -116,7 +119,6 @@ type SendTx struct {
func (tx *SendTx) WriteTo(w io.Writer) (n int64, err error) { func (tx *SendTx) WriteTo(w io.Writer) (n int64, err error) {
WriteByte(w, TxTypeSend, &n, &err) WriteByte(w, TxTypeSend, &n, &err)
WriteBinary(w, tx.BaseTx, &n, &err) WriteBinary(w, tx.BaseTx, &n, &err)
WriteUInt64(w, tx.Fee, &n, &err)
WriteUInt64(w, tx.To, &n, &err) WriteUInt64(w, tx.To, &n, &err)
WriteUInt64(w, tx.Amount, &n, &err) WriteUInt64(w, tx.Amount, &n, &err)
return return
@ -126,7 +128,6 @@ func (tx *SendTx) WriteTo(w io.Writer) (n int64, err error) {
type NameTx struct { type NameTx struct {
BaseTx BaseTx
Fee uint64
Name string Name string
PubKey []byte PubKey []byte
} }
@ -134,7 +135,6 @@ type NameTx struct {
func (tx *NameTx) WriteTo(w io.Writer) (n int64, err error) { func (tx *NameTx) WriteTo(w io.Writer) (n int64, err error) {
WriteByte(w, TxTypeName, &n, &err) WriteByte(w, TxTypeName, &n, &err)
WriteBinary(w, tx.BaseTx, &n, &err) WriteBinary(w, tx.BaseTx, &n, &err)
WriteUInt64(w, tx.Fee, &n, &err)
WriteString(w, tx.Name, &n, &err) WriteString(w, tx.Name, &n, &err)
WriteByteSlice(w, tx.PubKey, &n, &err) WriteByteSlice(w, tx.PubKey, &n, &err)
return return
@ -144,15 +144,13 @@ func (tx *NameTx) WriteTo(w io.Writer) (n int64, err error) {
type BondTx struct { type BondTx struct {
BaseTx BaseTx
Fee uint64
UnbondTo uint64
//UnbondTo uint64
} }
func (tx *BondTx) WriteTo(w io.Writer) (n int64, err error) { func (tx *BondTx) WriteTo(w io.Writer) (n int64, err error) {
WriteByte(w, TxTypeBond, &n, &err) WriteByte(w, TxTypeBond, &n, &err)
WriteBinary(w, tx.BaseTx, &n, &err) WriteBinary(w, tx.BaseTx, &n, &err)
WriteUInt64(w, tx.Fee, &n, &err)
WriteUInt64(w, tx.UnbondTo, &n, &err)
//WriteUInt64(w, tx.UnbondTo, &n, &err)
return return
} }
@ -160,13 +158,11 @@ func (tx *BondTx) WriteTo(w io.Writer) (n int64, err error) {
type UnbondTx struct { type UnbondTx struct {
BaseTx BaseTx
Fee uint64
} }
func (tx *UnbondTx) WriteTo(w io.Writer) (n int64, err error) { func (tx *UnbondTx) WriteTo(w io.Writer) (n int64, err error) {
WriteByte(w, TxTypeUnbond, &n, &err) WriteByte(w, TxTypeUnbond, &n, &err)
WriteBinary(w, tx.BaseTx, &n, &err) WriteBinary(w, tx.BaseTx, &n, &err)
WriteUInt64(w, tx.Fee, &n, &err)
return return
} }


+ 1
- 1
consensus/state.go View File

@ -86,7 +86,7 @@ func (cs *ConsensusState) updateToState(state *State) {
// Reset fields based on state. // Reset fields based on state.
height := state.Height height := state.Height
validators := state.Validators
validators := state.BondedValidators
cs.Height = height cs.Height = height
cs.Round = 0 cs.Round = 0
cs.Step = RoundStepStart cs.Step = RoundStepStart


+ 3
- 0
merkle/iavl_tree.go View File

@ -148,6 +148,9 @@ func (t *IAVLTree) Remove(key interface{}) (value interface{}, removed bool) {
} }
func (t *IAVLTree) Iterate(fn func(key interface{}, value interface{}) bool) (stopped bool) { func (t *IAVLTree) Iterate(fn func(key interface{}, value interface{}) bool) (stopped bool) {
if t.root == nil {
return false
}
return t.root.traverse(t, func(node *IAVLNode) bool { return t.root.traverse(t, func(node *IAVLNode) bool {
if node.height == 0 { if node.height == 0 {
return fn(node.key, node.value) return fn(node.key, node.value)


+ 4
- 3
state/account.go View File

@ -9,9 +9,10 @@ import (
) )
const ( const (
AccountDetailStatusNominal = byte(0x00)
AccountDetailStatusBonded = byte(0x01)
AccountDetailStatusUnbonding = byte(0x02)
AccountStatusNominal = byte(0x00)
AccountStatusBonded = byte(0x01)
AccountStatusUnbonding = byte(0x02)
AccountStatusDupedOut = byte(0x03)
) )
type Account struct { type Account struct {


+ 192
- 61
state/state.go View File

@ -3,39 +3,52 @@ package state
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"time" "time"
. "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/blocks" . "github.com/tendermint/tendermint/blocks"
. "github.com/tendermint/tendermint/common"
. "github.com/tendermint/tendermint/db" . "github.com/tendermint/tendermint/db"
"github.com/tendermint/tendermint/merkle" "github.com/tendermint/tendermint/merkle"
) )
var ( var (
ErrStateInvalidAccountId = errors.New("Error State invalid account id")
ErrStateInvalidSignature = errors.New("Error State invalid signature")
ErrStateInvalidSequenceNumber = errors.New("Error State invalid sequence number")
ErrStateInvalidAccountState = errors.New("Error State invalid account state")
ErrStateInvalidValidationStateHash = errors.New("Error State invalid ValidationStateHash")
ErrStateInvalidAccountStateHash = errors.New("Error State invalid AccountStateHash")
ErrStateInsufficientFunds = errors.New("Error State insufficient funds")
ErrStateInvalidAccountId = errors.New("Error State invalid account id")
ErrStateInvalidSignature = errors.New("Error State invalid signature")
ErrStateInvalidSequenceNumber = errors.New("Error State invalid sequence number")
ErrStateInvalidAccountState = errors.New("Error State invalid account state")
ErrStateInsufficientFunds = errors.New("Error State insufficient funds")
stateKey = []byte("stateKey") stateKey = []byte("stateKey")
minBondAmount = uint64(1) // TODO adjust
defaultAccountDetailsCacheCapacity = 1000 // TODO adjust
minBondAmount = uint64(1) // TODO adjust
defaultAccountDetailsCacheCapacity = 1000 // TODO adjust
unbondingPeriodBlocks = uint32(60 * 24 * 365) // TODO probably better to make it time based.
validatorTimeoutBlocks = uint32(10) // TODO adjust
) )
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
type InvalidTxError struct {
Tx Tx
Reason error
}
func (txErr InvalidTxError) Error() string {
return fmt.Sprintf("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason)
}
//-----------------------------------------------------------------------------
// NOTE: not goroutine-safe. // NOTE: not goroutine-safe.
type State struct { type State struct {
DB DB
Height uint32 // Last known block height
BlockHash []byte // Last known block hash
CommitTime time.Time
AccountDetails merkle.Tree
BondedValidators *ValidatorSet
UnbondedValidators *ValidatorSet
DB DB
Height uint32 // Last known block height
BlockHash []byte // Last known block hash
CommitTime time.Time
AccountDetails merkle.Tree
BondedValidators *ValidatorSet
UnbondingValidators *ValidatorSet
} }
func GenesisState(db DB, genesisTime time.Time, accDets []*AccountDetail) *State { func GenesisState(db DB, genesisTime time.Time, accDets []*AccountDetail) *State {
@ -46,7 +59,7 @@ func GenesisState(db DB, genesisTime time.Time, accDets []*AccountDetail) *State
for _, accDet := range accDets { for _, accDet := range accDets {
accountDetails.Set(accDet.Id, accDet) accountDetails.Set(accDet.Id, accDet)
if accDet.Status == AccountDetailStatusBonded {
if accDet.Status == AccountStatusBonded {
validators = append(validators, &Validator{ validators = append(validators, &Validator{
Account: accDet.Account, Account: accDet.Account,
BondHeight: 0, BondHeight: 0,
@ -61,13 +74,13 @@ func GenesisState(db DB, genesisTime time.Time, accDets []*AccountDetail) *State
} }
return &State{ return &State{
DB: db,
Height: 0,
BlockHash: nil,
CommitTime: genesisTime,
AccountDetails: accountDetails,
BondedValidators: NewValidatorSet(validators),
UnbondedValidators: NewValidatorSet(nil),
DB: db,
Height: 0,
BlockHash: nil,
CommitTime: genesisTime,
AccountDetails: accountDetails,
BondedValidators: NewValidatorSet(validators),
UnbondingValidators: NewValidatorSet(nil),
} }
} }
@ -87,7 +100,7 @@ func LoadState(db DB) *State {
s.AccountDetails = merkle.NewIAVLTree(BasicCodec, AccountDetailCodec, defaultAccountDetailsCacheCapacity, db) s.AccountDetails = merkle.NewIAVLTree(BasicCodec, AccountDetailCodec, defaultAccountDetailsCacheCapacity, db)
s.AccountDetails.Load(accountDetailsHash) s.AccountDetails.Load(accountDetailsHash)
s.BondedValidators = ReadValidatorSet(reader, &n, &err) s.BondedValidators = ReadValidatorSet(reader, &n, &err)
s.UnbondedValidators = ReadValidatorSet(reader, &n, &err)
s.UnbondingValidators = ReadValidatorSet(reader, &n, &err)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -110,7 +123,7 @@ func (s *State) Save(commitTime time.Time) {
WriteByteSlice(&buf, s.BlockHash, &n, &err) WriteByteSlice(&buf, s.BlockHash, &n, &err)
WriteByteSlice(&buf, s.AccountDetails.Hash(), &n, &err) WriteByteSlice(&buf, s.AccountDetails.Hash(), &n, &err)
WriteBinary(&buf, s.BondedValidators, &n, &err) WriteBinary(&buf, s.BondedValidators, &n, &err)
WriteBinary(&buf, s.UnbondedValidators, &n, &err)
WriteBinary(&buf, s.UnbondingValidators, &n, &err)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -119,13 +132,13 @@ func (s *State) Save(commitTime time.Time) {
func (s *State) Copy() *State { func (s *State) Copy() *State {
return &State{ return &State{
DB: s.DB,
Height: s.Height,
CommitTime: s.CommitTime,
BlockHash: s.BlockHash,
AccountDetails: s.AccountDetails.Copy(),
BondedValidators: s.BondedValidators.Copy(),
UnbondedValidators: s.UnbondedValidators.Copy(),
DB: s.DB,
Height: s.Height,
CommitTime: s.CommitTime,
BlockHash: s.BlockHash,
AccountDetails: s.AccountDetails.Copy(),
BondedValidators: s.BondedValidators.Copy(),
UnbondingValidators: s.UnbondingValidators.Copy(),
} }
} }
@ -144,20 +157,26 @@ func (s *State) ExecTx(tx Tx) error {
if tx.GetSequence() <= accDet.Sequence { if tx.GetSequence() <= accDet.Sequence {
return ErrStateInvalidSequenceNumber return ErrStateInvalidSequenceNumber
} }
// Subtract fee from balance.
if accDet.Balance < tx.GetFee() {
return ErrStateInsufficientFunds
} else {
accDet.Balance -= tx.GetFee()
}
// Exec tx // Exec tx
switch tx.(type) { switch tx.(type) {
case *SendTx: case *SendTx:
stx := tx.(*SendTx) stx := tx.(*SendTx)
toAccDet := s.GetAccountDetail(stx.To) toAccDet := s.GetAccountDetail(stx.To)
// Accounts must be nominal // Accounts must be nominal
if accDet.Status != AccountDetailStatusNominal {
if accDet.Status != AccountStatusNominal {
return ErrStateInvalidAccountState return ErrStateInvalidAccountState
} }
if toAccDet.Status != AccountDetailStatusNominal {
if toAccDet.Status != AccountStatusNominal {
return ErrStateInvalidAccountState return ErrStateInvalidAccountState
} }
// Check account balance // Check account balance
if accDet.Balance < stx.Fee+stx.Amount {
if accDet.Balance < stx.Amount {
return ErrStateInsufficientFunds return ErrStateInsufficientFunds
} }
// Check existence of destination account // Check existence of destination account
@ -165,27 +184,26 @@ func (s *State) ExecTx(tx Tx) error {
return ErrStateInvalidAccountId return ErrStateInvalidAccountId
} }
// Good! // Good!
accDet.Balance -= (stx.Fee + stx.Amount)
toAccDet.Balance += (stx.Amount)
accDet.Balance -= stx.Amount
toAccDet.Balance += stx.Amount
s.SetAccountDetail(accDet) s.SetAccountDetail(accDet)
s.SetAccountDetail(toAccDet) s.SetAccountDetail(toAccDet)
return nil
//case *NameTx //case *NameTx
case *BondTx: case *BondTx:
btx := tx.(*BondTx)
//btx := tx.(*BondTx)
// Account must be nominal // Account must be nominal
if accDet.Status != AccountDetailStatusNominal {
if accDet.Status != AccountStatusNominal {
return ErrStateInvalidAccountState return ErrStateInvalidAccountState
} }
// Check account balance // Check account balance
if accDet.Balance < minBondAmount { if accDet.Balance < minBondAmount {
return ErrStateInsufficientFunds return ErrStateInsufficientFunds
} }
// TODO: max number of validators?
// Good! // Good!
accDet.Balance -= btx.Fee // remaining balance are bonded coins.
accDet.Status = AccountDetailStatusBonded
accDet.Status = AccountStatusBonded
s.SetAccountDetail(accDet) s.SetAccountDetail(accDet)
added := s.BondednValidators.Add(&Validator{
added := s.BondedValidators.Add(&Validator{
Account: accDet.Account, Account: accDet.Account,
BondHeight: s.Height, BondHeight: s.Height,
VotingPower: accDet.Balance, VotingPower: accDet.Balance,
@ -194,29 +212,106 @@ func (s *State) ExecTx(tx Tx) error {
if !added { if !added {
panic("Failed to add validator") panic("Failed to add validator")
} }
return nil
case *UnbondTx: case *UnbondTx:
utx := tx.(*UnbondTx)
//utx := tx.(*UnbondTx)
// Account must be bonded. // Account must be bonded.
if accDet.Status != AccountDetailStatusBonded {
if accDet.Status != AccountStatusBonded {
return ErrStateInvalidAccountState return ErrStateInvalidAccountState
} }
// Good! // Good!
accDet.Status = AccountDetailStatusUnbonding
s.unbondValidator(accDet.Id, accDet)
s.SetAccountDetail(accDet) s.SetAccountDetail(accDet)
val, removed := s.BondedValidators.Remove(accDet.Id)
if !removed {
panic("Failed to remove validator")
return nil
case *DupeoutTx:
{
// NOTE: accDet is the one who created this transaction.
// Subtract any fees, save, and forget.
s.SetAccountDetail(accDet)
accDet = nil
} }
val.UnbondHeight = s.Height
added := s.UnbondedValidators.Add(val)
if !added {
panic("Failed to add validator")
dtx := tx.(*DupeoutTx)
// Verify the signatures
if dtx.VoteA.SignerId != dtx.VoteB.SignerId {
return ErrStateInvalidSignature
} }
case *DupeoutTx:
// XXX
accused := s.GetAccountDetail(dtx.VoteA.SignerId)
if !accused.Verify(&dtx.VoteA) || !accused.Verify(&dtx.VoteB) {
return ErrStateInvalidSignature
}
// Verify equivocation
if dtx.VoteA.Height != dtx.VoteB.Height {
return errors.New("DupeoutTx height must be the same.")
}
if dtx.VoteA.Type == VoteTypeCommit && dtx.VoteA.Round < dtx.VoteB.Round {
// Check special case.
// Validators should not sign another vote after committing.
} else {
if dtx.VoteA.Round != dtx.VoteB.Round {
return errors.New("DupeoutTx rounds don't match")
}
if dtx.VoteA.Type != dtx.VoteB.Type {
return errors.New("DupeoutTx types don't match")
}
if bytes.Equal(dtx.VoteA.BlockHash, dtx.VoteB.BlockHash) {
return errors.New("DupeoutTx blockhash shouldn't match")
}
}
// Good! (Bad validator!)
if accused.Status == AccountStatusBonded {
_, removed := s.BondedValidators.Remove(accused.Id)
if !removed {
panic("Failed to remove accused validator")
}
} else if accused.Status == AccountStatusUnbonding {
_, removed := s.UnbondingValidators.Remove(accused.Id)
if !removed {
panic("Failed to remove accused validator")
}
} else {
panic("Couldn't find accused validator")
}
accused.Status = AccountStatusDupedOut
updated := s.SetAccountDetail(accused)
if !updated {
panic("Failed to update accused validator account")
}
return nil
default:
panic("Unknown Tx type")
}
}
// accDet optional
func (s *State) unbondValidator(accountId uint64, accDet *AccountDetail) {
if accDet == nil {
accDet = s.GetAccountDetail(accountId)
}
accDet.Status = AccountStatusUnbonding
s.SetAccountDetail(accDet)
val, removed := s.BondedValidators.Remove(accDet.Id)
if !removed {
panic("Failed to remove validator")
}
val.UnbondHeight = s.Height
added := s.UnbondingValidators.Add(val)
if !added {
panic("Failed to add validator")
}
}
func (s *State) releaseValidator(accountId uint64) {
accDet := s.GetAccountDetail(accountId)
if accDet.Status != AccountStatusUnbonding {
panic("Cannot release validator")
}
accDet.Status = AccountStatusNominal
// TODO: move balance to designated address, UnbondTo.
s.SetAccountDetail(accDet)
_, removed := s.UnbondingValidators.Remove(accountId)
if !removed {
panic("Couldn't release validator")
} }
panic("Implement ExecTx()")
return nil
} }
// NOTE: If an error occurs during block execution, state will be left // NOTE: If an error occurs during block execution, state will be left
@ -232,25 +327,61 @@ func (s *State) AppendBlock(b *Block) error {
for _, tx := range b.Data.Txs { for _, tx := range b.Data.Txs {
err := s.ExecTx(tx) err := s.ExecTx(tx)
if err != nil { if err != nil {
return err
return InvalidTxError{tx, err}
}
}
// Update LastCommitHeight as necessary.
for _, sig := range b.Validation.Signatures {
_, val := s.BondedValidators.GetById(sig.SignerId)
if val == nil {
return ErrStateInvalidSignature
}
val.LastCommitHeight = b.Height
updated := s.BondedValidators.Update(val)
if !updated {
panic("Failed to update validator LastCommitHeight")
} }
} }
// If any unbonding periods are over, // If any unbonding periods are over,
// reward account with bonded coins. // reward account with bonded coins.
toRelease := []*Validator{}
s.UnbondingValidators.Iterate(func(val *Validator) bool {
if val.UnbondHeight+unbondingPeriodBlocks < b.Height {
toRelease = append(toRelease, val)
}
return false
})
for _, val := range toRelease {
s.releaseValidator(val.Id)
}
// If any validators haven't signed in a while, // If any validators haven't signed in a while,
// unbond them, they have timed out. // unbond them, they have timed out.
toTimeout := []*Validator{}
s.BondedValidators.Iterate(func(val *Validator) bool {
if val.LastCommitHeight+validatorTimeoutBlocks < b.Height {
toTimeout = append(toTimeout, val)
}
return false
})
for _, val := range toTimeout {
s.unbondValidator(val.Id, nil)
}
// Increment validator AccumPowers // Increment validator AccumPowers
s.BondedValidators.IncrementAccum() s.BondedValidators.IncrementAccum()
// State hashes should match // State hashes should match
// XXX include UnbondingValidators.Hash().
if !bytes.Equal(s.BondedValidators.Hash(), b.ValidationStateHash) { if !bytes.Equal(s.BondedValidators.Hash(), b.ValidationStateHash) {
return ErrStateInvalidValidationStateHash
return Errorf("Invalid ValidationStateHash. Got %X, block says %X",
s.BondedValidators.Hash(), b.ValidationStateHash)
} }
if !bytes.Equal(s.AccountDetails.Hash(), b.AccountStateHash) { if !bytes.Equal(s.AccountDetails.Hash(), b.AccountStateHash) {
return ErrStateInvalidAccountStateHash
return Errorf("Invalid AccountStateHash. Got %X, block says %X",
s.AccountDetails.Hash(), b.AccountStateHash)
} }
s.Height = b.Height s.Height = b.Height


+ 11
- 11
state/state_test.go View File

@ -29,9 +29,9 @@ func randGenesisState(numAccounts int, numValidators int) *State {
accountDetails := make([]*AccountDetail, numAccounts) accountDetails := make([]*AccountDetail, numAccounts)
for i := 0; i < numAccounts; i++ { for i := 0; i < numAccounts; i++ {
if i < numValidators { if i < numValidators {
accountDetails[i] = randAccountDetail(uint64(i), AccountDetailStatusNominal)
accountDetails[i] = randAccountDetail(uint64(i), AccountStatusNominal)
} else { } else {
accountDetails[i] = randAccountDetail(uint64(i), AccountDetailStatusBonded)
accountDetails[i] = randAccountDetail(uint64(i), AccountStatusBonded)
} }
} }
s0 := GenesisState(db, time.Now(), accountDetails) s0 := GenesisState(db, time.Now(), accountDetails)
@ -43,8 +43,8 @@ func TestGenesisSaveLoad(t *testing.T) {
// Generate a state, save & load it. // Generate a state, save & load it.
s0 := randGenesisState(10, 5) s0 := randGenesisState(10, 5)
// Figure out what the next state hashes should be. // Figure out what the next state hashes should be.
s0.Validators.Hash()
s0ValsCopy := s0.Validators.Copy()
s0.BondedValidators.Hash()
s0ValsCopy := s0.BondedValidators.Copy()
s0ValsCopy.IncrementAccum() s0ValsCopy.IncrementAccum()
nextValidationStateHash := s0ValsCopy.Hash() nextValidationStateHash := s0ValsCopy.Hash()
nextAccountStateHash := s0.AccountDetails.Hash() nextAccountStateHash := s0.AccountDetails.Hash()
@ -71,8 +71,8 @@ func TestGenesisSaveLoad(t *testing.T) {
// Sanity check s0 // Sanity check s0
//s0.DB.(*MemDB).Print() //s0.DB.(*MemDB).Print()
if s0.Validators.TotalVotingPower() == 0 {
t.Error("s0 Validators TotalVotingPower should not be 0")
if s0.BondedValidators.TotalVotingPower() == 0 {
t.Error("s0 BondedValidators TotalVotingPower should not be 0")
} }
if s0.Height != 1 { if s0.Height != 1 {
t.Error("s0 Height should be 1, got", s0.Height) t.Error("s0 Height should be 1, got", s0.Height)
@ -92,12 +92,12 @@ func TestGenesisSaveLoad(t *testing.T) {
if !bytes.Equal(s0.BlockHash, s1.BlockHash) { if !bytes.Equal(s0.BlockHash, s1.BlockHash) {
t.Error("BlockHash mismatch") t.Error("BlockHash mismatch")
} }
// Compare Validators
if s0.Validators.Size() != s1.Validators.Size() {
t.Error("Validators Size mismatch")
// Compare BondedValidators
if s0.BondedValidators.Size() != s1.BondedValidators.Size() {
t.Error("BondedValidators Size mismatch")
} }
if s0.Validators.TotalVotingPower() != s1.Validators.TotalVotingPower() {
t.Error("Validators TotalVotingPower mismatch")
if s0.BondedValidators.TotalVotingPower() != s1.BondedValidators.TotalVotingPower() {
t.Error("BondedValidators TotalVotingPower mismatch")
} }
if !bytes.Equal(s0.AccountDetails.Hash(), s1.AccountDetails.Hash()) { if !bytes.Equal(s0.AccountDetails.Hash(), s1.AccountDetails.Hash()) {
t.Error("AccountDetail mismatch") t.Error("AccountDetail mismatch")


+ 16
- 7
state/validator_set.go View File

@ -101,17 +101,26 @@ func (vset *ValidatorSet) Hash() []byte {
} }
func (vset *ValidatorSet) Add(val *Validator) (added bool) { func (vset *ValidatorSet) Add(val *Validator) (added bool) {
if val.Accum != 0 {
panic("AddValidator only accepts validators with zero accumpower")
}
if vset.validators.Has(val.Id) { if vset.validators.Has(val.Id) {
return false return false
} }
updated := vset.validators.Set(val.Id, val)
return !updated
return !vset.validators.Set(val.Id, val)
}
func (vset *ValidatorSet) Update(val *Validator) (updated bool) {
if !vset.validators.Has(val.Id) {
return false
}
return vset.validators.Set(val.Id, val)
} }
func (vset *ValidatorSet) Remove(validatorId uint64) (val *Validator, removed bool) { func (vset *ValidatorSet) Remove(validatorId uint64) (val *Validator, removed bool) {
val, removed = vset.validators.Remove(validatorId)
return val.(*Validator), removed
val_, removed := vset.validators.Remove(validatorId)
return val_.(*Validator), removed
}
func (vset *ValidatorSet) Iterate(fn func(val *Validator) bool) {
vset.validators.Iterate(func(key_ interface{}, val_ interface{}) bool {
return fn(val_.(*Validator))
})
} }

+ 45
- 0
state/validator_set_test.go View File

@ -0,0 +1,45 @@
package state
import (
. "github.com/tendermint/tendermint/common"
"bytes"
"testing"
)
func randValidator() *Validator {
return &Validator{
Account: Account{
Id: RandUInt64(),
PubKey: CRandBytes(32),
},
BondHeight: RandUInt32(),
UnbondHeight: RandUInt32(),
LastCommitHeight: RandUInt32(),
VotingPower: RandUInt64(),
Accum: int64(RandUInt64()),
}
}
func randValidatorSet(numValidators int) *ValidatorSet {
validators := make([]*Validator, numValidators)
for i := 0; i < numValidators; i++ {
validators[i] = randValidator()
}
return NewValidatorSet(validators)
}
func TestCopy(t *testing.T) {
vset := randValidatorSet(10)
vsetHash := vset.Hash()
if len(vsetHash) == 0 {
t.Fatalf("ValidatorSet had unexpected zero hash")
}
vsetCopy := vset.Copy()
vsetCopyHash := vsetCopy.Hash()
if !bytes.Equal(vsetHash, vsetCopyHash) {
t.Fatalf("ValidatorSet copy had wrong hash. Orig: %X, Copy: %X", vsetHash, vsetCopyHash)
}
}

Loading…
Cancel
Save