|
|
- package state
-
- import (
- "bytes"
- "errors"
- "fmt"
- "time"
-
- . "github.com/tendermint/tendermint/binary"
- . "github.com/tendermint/tendermint/blocks"
- . "github.com/tendermint/tendermint/common"
- db_ "github.com/tendermint/tendermint/db"
- "github.com/tendermint/tendermint/merkle"
- )
-
- 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")
- ErrStateInsufficientFunds = errors.New("Error State insufficient funds")
-
- stateKey = []byte("stateKey")
- 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.
- type State struct {
- DB db_.DB
- Height uint32 // Last known block height
- BlockHash []byte // Last known block hash
- CommitTime time.Time
- accountDetails merkle.Tree // Shouldn't be accessed directly.
- BondedValidators *ValidatorSet
- UnbondingValidators *ValidatorSet
- }
-
- func GenesisState(db db_.DB, genesisTime time.Time, accDets []*AccountDetail) *State {
-
- // TODO: Use "uint64Codec" instead of BasicCodec
- accountDetails := merkle.NewIAVLTree(BasicCodec, AccountDetailCodec, defaultAccountDetailsCacheCapacity, db)
- validators := []*Validator{}
-
- for _, accDet := range accDets {
- accountDetails.Set(accDet.Id, accDet)
- if accDet.Status == AccountStatusBonded {
- validators = append(validators, &Validator{
- Account: accDet.Account,
- BondHeight: 0,
- VotingPower: accDet.Balance,
- Accum: 0,
- })
- }
- }
-
- if len(validators) == 0 {
- panic("Must have some validators")
- }
-
- return &State{
- DB: db,
- Height: 0,
- BlockHash: nil,
- CommitTime: genesisTime,
- accountDetails: accountDetails,
- BondedValidators: NewValidatorSet(validators),
- UnbondingValidators: NewValidatorSet(nil),
- }
- }
-
- func LoadState(db db_.DB) *State {
- s := &State{DB: db}
- buf := db.Get(stateKey)
- if len(buf) == 0 {
- return nil
- } else {
- reader := bytes.NewReader(buf)
- var n int64
- var err error
- s.Height = ReadUInt32(reader, &n, &err)
- s.CommitTime = ReadTime(reader, &n, &err)
- s.BlockHash = ReadByteSlice(reader, &n, &err)
- accountDetailsHash := ReadByteSlice(reader, &n, &err)
- s.accountDetails = merkle.NewIAVLTree(BasicCodec, AccountDetailCodec, defaultAccountDetailsCacheCapacity, db)
- s.accountDetails.Load(accountDetailsHash)
- s.BondedValidators = ReadValidatorSet(reader, &n, &err)
- s.UnbondingValidators = ReadValidatorSet(reader, &n, &err)
- if err != nil {
- panic(err)
- }
- // TODO: ensure that buf is completely read.
- }
- return s
- }
-
- // Save this state into the db.
- // For convenience, the commitTime (required by ConsensusAgent)
- // is saved here.
- func (s *State) Save(commitTime time.Time) {
- s.CommitTime = commitTime
- s.accountDetails.Save()
- var buf bytes.Buffer
- var n int64
- var err error
- WriteUInt32(&buf, s.Height, &n, &err)
- WriteTime(&buf, commitTime, &n, &err)
- WriteByteSlice(&buf, s.BlockHash, &n, &err)
- WriteByteSlice(&buf, s.accountDetails.Hash(), &n, &err)
- WriteBinary(&buf, s.BondedValidators, &n, &err)
- WriteBinary(&buf, s.UnbondingValidators, &n, &err)
- if err != nil {
- panic(err)
- }
- s.DB.Set(stateKey, buf.Bytes())
- }
-
- func (s *State) Copy() *State {
- return &State{
- DB: s.DB,
- Height: s.Height,
- CommitTime: s.CommitTime,
- BlockHash: s.BlockHash,
- accountDetails: s.accountDetails.Copy(),
- BondedValidators: s.BondedValidators.Copy(),
- UnbondingValidators: s.UnbondingValidators.Copy(),
- }
- }
-
- // If the tx is invalid, an error will be returned.
- // Unlike AppendBlock(), state will not be altered.
- func (s *State) ExecTx(tx Tx) error {
- accDet := s.GetAccountDetail(tx.GetSignature().SignerId)
- if accDet == nil {
- return ErrStateInvalidAccountId
- }
- // Check signature
- if !accDet.Verify(tx) {
- return ErrStateInvalidSignature
- }
- // Check and update sequence
- if tx.GetSequence() <= accDet.Sequence {
- return ErrStateInvalidSequenceNumber
- } else {
- // TODO consider prevSequence for tx chaining.
- accDet.Sequence = tx.GetSequence()
- }
- // Subtract fee from balance.
- if accDet.Balance < tx.GetFee() {
- return ErrStateInsufficientFunds
- } else {
- accDet.Balance -= tx.GetFee()
- }
- // Exec tx
- switch tx.(type) {
- case *SendTx:
- stx := tx.(*SendTx)
- toAccDet := s.GetAccountDetail(stx.To)
- // Accounts must be nominal
- if accDet.Status != AccountStatusNominal {
- return ErrStateInvalidAccountState
- }
- if toAccDet.Status != AccountStatusNominal {
- return ErrStateInvalidAccountState
- }
- // Check account balance
- if accDet.Balance < stx.Amount {
- return ErrStateInsufficientFunds
- }
- // Check existence of destination account
- if toAccDet == nil {
- return ErrStateInvalidAccountId
- }
- // Good!
- accDet.Balance -= stx.Amount
- toAccDet.Balance += stx.Amount
- s.SetAccountDetail(accDet)
- s.SetAccountDetail(toAccDet)
- return nil
- //case *NameTx
- case *BondTx:
- //btx := tx.(*BondTx)
- // Account must be nominal
- if accDet.Status != AccountStatusNominal {
- return ErrStateInvalidAccountState
- }
- // Check account balance
- if accDet.Balance < minBondAmount {
- return ErrStateInsufficientFunds
- }
- // Good!
- accDet.Status = AccountStatusBonded
- s.SetAccountDetail(accDet)
- added := s.BondedValidators.Add(&Validator{
- Account: accDet.Account,
- BondHeight: s.Height,
- VotingPower: accDet.Balance,
- Accum: 0,
- })
- if !added {
- panic("Failed to add validator")
- }
- return nil
- case *UnbondTx:
- //utx := tx.(*UnbondTx)
- // Account must be bonded.
- if accDet.Status != AccountStatusBonded {
- return ErrStateInvalidAccountState
- }
- // Good!
- s.unbondValidator(accDet.Id, accDet)
- s.SetAccountDetail(accDet)
- return nil
- case *DupeoutTx:
- {
- // NOTE: accDet is the one who created this transaction.
- // Subtract any fees, save, and forget.
- s.SetAccountDetail(accDet)
- accDet = nil
- }
- dtx := tx.(*DupeoutTx)
- // Verify the signatures
- if dtx.VoteA.SignerId != dtx.VoteB.SignerId {
- return ErrStateInvalidSignature
- }
- 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")
- }
- }
-
- // "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, checkStateHash bool) error {
- // Basic block validation.
- err := b.ValidateBasic(s.Height, s.BlockHash)
- if err != nil {
- return err
- }
-
- // Commit each tx
- for _, tx := range b.Data.Txs {
- err := s.ExecTx(tx)
- if err != nil {
- 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,
- // 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,
- // 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
- s.BondedValidators.IncrementAccum()
-
- // 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
- s.BlockHash = b.Hash()
- return nil
- }
-
- // The returned AccountDetail is a copy, so mutating it
- // has no side effects.
- func (s *State) GetAccountDetail(accountId uint64) *AccountDetail {
- _, accDet := s.accountDetails.Get(accountId)
- if accDet == nil {
- return nil
- }
- return accDet.(*AccountDetail).Copy()
- }
-
- // Returns false if new, true if updated.
- // The accDet is copied before setting, so mutating it
- // afterwards has no side effects.
- func (s *State) SetAccountDetail(accDet *AccountDetail) (updated bool) {
- return s.accountDetails.Set(accDet.Id, accDet.Copy())
- }
-
- // 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)
- }
|