diff --git a/blocks/block_test.go b/blocks/block_test.go index 972ae854..a50bde6f 100644 --- a/blocks/block_test.go +++ b/blocks/block_test.go @@ -46,11 +46,6 @@ func TestBlock(t *testing.T) { Fee: RandUInt64Exp(), } - timeoutTx := &TimeoutTx{ - AccountId: RandUInt64Exp(), - Penalty: RandUInt64Exp(), - } - dupeoutTx := &DupeoutTx{ VoteA: Vote{ Height: RandUInt32Exp(), diff --git a/blocks/tx.go b/blocks/tx.go index e7d248e9..b0655de8 100644 --- a/blocks/tx.go +++ b/blocks/tx.go @@ -14,8 +14,7 @@ Account Txs: Validation Txs: 3. Bond New validator posts a bond 4. Unbond Validator leaves -5. Timeout Validator times out -6. Dupeout Validator dupes out (signs twice) +5. Dupeout Validator dupes out (signs twice) */ type Tx interface { @@ -31,8 +30,7 @@ const ( // Validation transactions TxTypeBond = byte(0x11) TxTypeUnbond = byte(0x12) - TxTypeTimeout = byte(0x13) - TxTypeDupeout = byte(0x14) + TxTypeDupeout = byte(0x13) ) func ReadTx(r io.Reader, n *int64, err *error) Tx { @@ -62,12 +60,6 @@ func ReadTx(r io.Reader, n *int64, err *error) Tx { BaseTx: ReadBaseTx(r, n, err), Fee: ReadUInt64(r, n, err), } - case TxTypeTimeout: - return &TimeoutTx{ - BaseTx: ReadBaseTx(r, n, err), - AccountId: ReadUInt64(r, n, err), - Penalty: ReadUInt64(r, n, err), - } case TxTypeDupeout: return &DupeoutTx{ BaseTx: ReadBaseTx(r, n, err), @@ -180,22 +172,6 @@ func (tx *UnbondTx) WriteTo(w io.Writer) (n int64, err error) { //----------------------------------------------------------------------------- -type TimeoutTx struct { - BaseTx - AccountId uint64 - Penalty uint64 -} - -func (tx *TimeoutTx) WriteTo(w io.Writer) (n int64, err error) { - WriteByte(w, TxTypeTimeout, &n, &err) - WriteBinary(w, tx.BaseTx, &n, &err) - WriteUInt64(w, tx.AccountId, &n, &err) - WriteUInt64(w, tx.Penalty, &n, &err) - return -} - -//----------------------------------------------------------------------------- - type DupeoutTx struct { BaseTx VoteA Vote diff --git a/state/state.go b/state/state.go index d7568797..04fb7444 100644 --- a/state/state.go +++ b/state/state.go @@ -29,12 +29,13 @@ var ( // NOTE: not goroutine-safe. type State struct { - DB DB - Height uint32 // Last known block height - BlockHash []byte // Last known block hash - CommitTime time.Time - AccountDetails merkle.Tree - Validators *ValidatorSet + DB DB + Height uint32 // Last known block height + BlockHash []byte // Last known block hash + CommitTime time.Time + AccountDetails merkle.Tree + BondedValidators *ValidatorSet + UnbondedValidators *ValidatorSet } func GenesisState(db DB, genesisTime time.Time, accDets []*AccountDetail) *State { @@ -58,15 +59,15 @@ func GenesisState(db DB, genesisTime time.Time, accDets []*AccountDetail) *State if len(validators) == 0 { panic("Must have some validators") } - validatorSet := NewValidatorSet(validators) return &State{ - DB: db, - Height: 0, - BlockHash: nil, - CommitTime: genesisTime, - AccountDetails: accountDetails, - Validators: validatorSet, + DB: db, + Height: 0, + BlockHash: nil, + CommitTime: genesisTime, + AccountDetails: accountDetails, + BondedValidators: NewValidatorSet(validators), + UnbondedValidators: NewValidatorSet(nil), } } @@ -85,7 +86,8 @@ func LoadState(db DB) *State { accountDetailsHash := ReadByteSlice(reader, &n, &err) s.AccountDetails = merkle.NewIAVLTree(BasicCodec, AccountDetailCodec, defaultAccountDetailsCacheCapacity, db) s.AccountDetails.Load(accountDetailsHash) - s.Validators = ReadValidatorSet(reader, &n, &err) + s.BondedValidators = ReadValidatorSet(reader, &n, &err) + s.UnbondedValidators = ReadValidatorSet(reader, &n, &err) if err != nil { panic(err) } @@ -107,7 +109,8 @@ func (s *State) Save(commitTime time.Time) { WriteTime(&buf, commitTime, &n, &err) WriteByteSlice(&buf, s.BlockHash, &n, &err) WriteByteSlice(&buf, s.AccountDetails.Hash(), &n, &err) - WriteBinary(&buf, s.Validators, &n, &err) + WriteBinary(&buf, s.BondedValidators, &n, &err) + WriteBinary(&buf, s.UnbondedValidators, &n, &err) if err != nil { panic(err) } @@ -116,12 +119,13 @@ func (s *State) Save(commitTime time.Time) { func (s *State) Copy() *State { return &State{ - DB: s.DB, - Height: s.Height, - CommitTime: s.CommitTime, - BlockHash: s.BlockHash, - AccountDetails: s.AccountDetails.Copy(), - Validators: s.Validators.Copy(), + DB: s.DB, + Height: s.Height, + CommitTime: s.CommitTime, + BlockHash: s.BlockHash, + AccountDetails: s.AccountDetails.Copy(), + BondedValidators: s.BondedValidators.Copy(), + UnbondedValidators: s.UnbondedValidators.Copy(), } } @@ -181,10 +185,35 @@ func (s *State) ExecTx(tx Tx) error { accDet.Balance -= btx.Fee // remaining balance are bonded coins. accDet.Status = AccountDetailStatusBonded s.SetAccountDetail(accDet) - // XXX add validator + added := s.BondednValidators.Add(&Validator{ + Account: accDet.Account, + BondHeight: s.Height, + VotingPower: accDet.Balance, + Accum: 0, + }) + if !added { + panic("Failed to add validator") + } case *UnbondTx: - case *TimeoutTx: + utx := tx.(*UnbondTx) + // Account must be bonded. + if accDet.Status != AccountDetailStatusBonded { + return ErrStateInvalidAccountState + } + // Good! + accDet.Status = AccountDetailStatusUnbonding + s.SetAccountDetail(accDet) + val, removed := s.BondedValidators.Remove(accDet.Id) + if !removed { + panic("Failed to remove validator") + } + val.UnbondHeight = s.Height + added := s.UnbondedValidators.Add(val) + if !added { + panic("Failed to add validator") + } case *DupeoutTx: + // XXX } panic("Implement ExecTx()") return nil @@ -207,11 +236,17 @@ func (s *State) AppendBlock(b *Block) error { } } + // If any unbonding periods are over, + // reward account with bonded coins. + + // If any validators haven't signed in a while, + // unbond them, they have timed out. + // Increment validator AccumPowers - s.Validators.IncrementAccum() + s.BondedValidators.IncrementAccum() // State hashes should match - if !bytes.Equal(s.Validators.Hash(), b.ValidationStateHash) { + if !bytes.Equal(s.BondedValidators.Hash(), b.ValidationStateHash) { return ErrStateInvalidValidationStateHash } if !bytes.Equal(s.AccountDetails.Hash(), b.AccountStateHash) { diff --git a/state/validator.go b/state/validator.go index f57b3aaf..8505d46f 100644 --- a/state/validator.go +++ b/state/validator.go @@ -11,28 +11,34 @@ import ( // TODO consider moving this to another common types package. type Validator struct { Account - BondHeight uint32 // TODO: is this needed? - VotingPower uint64 - Accum int64 + BondHeight uint32 + UnbondHeight uint32 + LastCommitHeight uint32 + VotingPower uint64 + Accum int64 } // Used to persist the state of ConsensusStateControl. func ReadValidator(r io.Reader, n *int64, err *error) *Validator { return &Validator{ - Account: ReadAccount(r, n, err), - BondHeight: ReadUInt32(r, n, err), - VotingPower: ReadUInt64(r, n, err), - Accum: ReadInt64(r, n, err), + Account: ReadAccount(r, n, err), + BondHeight: ReadUInt32(r, n, err), + UnbondHeight: ReadUInt32(r, n, err), + LastCommitHeight: ReadUInt32(r, n, err), + VotingPower: ReadUInt64(r, n, err), + Accum: ReadInt64(r, n, err), } } // Creates a new copy of the validator so we can mutate accum. func (v *Validator) Copy() *Validator { return &Validator{ - Account: v.Account, - BondHeight: v.BondHeight, - VotingPower: v.VotingPower, - Accum: v.Accum, + Account: v.Account, + BondHeight: v.BondHeight, + UnbondHeight: v.UnbondHeight, + LastCommitHeight: v.LastCommitHeight, + VotingPower: v.VotingPower, + Accum: v.Accum, } } @@ -40,6 +46,8 @@ func (v *Validator) Copy() *Validator { func (v *Validator) WriteTo(w io.Writer) (n int64, err error) { WriteBinary(w, v.Account, &n, &err) WriteUInt32(w, v.BondHeight, &n, &err) + WriteUInt32(w, v.UnbondHeight, &n, &err) + WriteUInt32(w, v.LastCommitHeight, &n, &err) WriteUInt64(w, v.VotingPower, &n, &err) WriteInt64(w, v.Accum, &n, &err) return diff --git a/state/validator_set.go b/state/validator_set.go index 38b9a4e0..1cacbb1d 100644 --- a/state/validator_set.go +++ b/state/validator_set.go @@ -100,7 +100,7 @@ func (vset *ValidatorSet) Hash() []byte { return vset.validators.Hash() } -func (vset *ValidatorSet) AddValidator(val *Validator) (added bool) { +func (vset *ValidatorSet) Add(val *Validator) (added bool) { if val.Accum != 0 { panic("AddValidator only accepts validators with zero accumpower") } @@ -111,7 +111,7 @@ func (vset *ValidatorSet) AddValidator(val *Validator) (added bool) { return !updated } -func (vset *ValidatorSet) RemoveValidator(validatorId uint64) (removed bool) { - _, removed = vset.validators.Remove(validatorId) - return removed +func (vset *ValidatorSet) Remove(validatorId uint64) (val *Validator, removed bool) { + val, removed = vset.validators.Remove(validatorId) + return val.(*Validator), removed }