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 BlockTime time.Time // LastKnown block time BondedValidators *ValidatorSet UnbondingValidators *ValidatorSet accountDetails merkle.Tree // Shouldn't be accessed directly. } 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.BlockHash = ReadByteSlice(reader, &n, &err) s.BlockTime = ReadTime(reader, &n, &err) s.BondedValidators = ReadValidatorSet(reader, &n, &err) s.UnbondingValidators = ReadValidatorSet(reader, &n, &err) accountDetailsHash := ReadByteSlice(reader, &n, &err) s.accountDetails = merkle.NewIAVLTree(BasicCodec, AccountDetailCodec, defaultAccountDetailsCacheCapacity, db) s.accountDetails.Load(accountDetailsHash) if err != nil { panic(err) } // TODO: ensure that buf is completely read. } return s } // Save this state into the db. func (s *State) Save() { s.accountDetails.Save() var buf bytes.Buffer var n int64 var err error WriteUInt32(&buf, s.Height, &n, &err) WriteByteSlice(&buf, s.BlockHash, &n, &err) WriteTime(&buf, s.BlockTime, &n, &err) WriteBinary(&buf, s.BondedValidators, &n, &err) WriteBinary(&buf, s.UnbondingValidators, &n, &err) WriteByteSlice(&buf, s.accountDetails.Hash(), &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, BlockHash: s.BlockHash, BlockTime: s.BlockTime, BondedValidators: s.BondedValidators.Copy(), UnbondingValidators: s.UnbondingValidators.Copy(), accountDetails: s.accountDetails.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() s.BlockTime = b.Time 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. func (s *State) Hash() []byte { hashables := []merkle.Hashable{ s.BondedValidators, s.UnbondingValidators, s.accountDetails, } return merkle.HashFromHashables(hashables) }