Refactor Tx, Validator, and Account structure

This commit is contained in:
Jae Kwon
2014-12-16 05:45:40 -08:00
parent 4424a85fbd
commit 83d313cbe5
56 changed files with 1917 additions and 2022 deletions
+21 -151
View File
@@ -1,177 +1,47 @@
package state
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"github.com/tendermint/go-ed25519"
. "github.com/tendermint/tendermint/account"
. "github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/blocks"
. "github.com/tendermint/tendermint/common"
)
const (
AccountStatusNominal = byte(0x00)
AccountStatusBonded = byte(0x01)
AccountStatusUnbonding = byte(0x02)
AccountStatusDupedOut = byte(0x03)
)
type Account struct {
Id uint64 // Numeric id of account, incrementing.
PubKey []byte
}
func ReadAccount(r io.Reader, n *int64, err *error) Account {
return Account{
Id: ReadUInt64(r, n, err),
PubKey: ReadByteSlice(r, n, err),
}
}
func (account Account) WriteTo(w io.Writer) (n int64, err error) {
WriteUInt64(w, account.Id, &n, &err)
WriteByteSlice(w, account.PubKey, &n, &err)
return
}
func (account Account) VerifyBytes(msg []byte, sig Signature) bool {
if sig.SignerId != account.Id {
panic("account.id doesn't match sig.signerid")
}
if len(sig.Bytes) == 0 {
panic("signature is empty")
}
v1 := &ed25519.Verify{
Message: msg,
PubKey: account.PubKey,
Signature: sig.Bytes,
}
ok := ed25519.VerifyBatch([]*ed25519.Verify{v1})
return ok
}
func (account Account) Verify(o Signable) bool {
sig := o.GetSignature()
o.SetSignature(Signature{}) // clear
msg := BinaryBytes(o)
o.SetSignature(sig) // restore
return account.VerifyBytes(msg, sig)
}
func (account Account) String() string {
return fmt.Sprintf("Account{%v:%X}", account.Id, account.PubKey[:6])
}
//-----------------------------------------------------------------------------
type AccountDetail struct {
Account
Address []byte
PubKey PubKey
Sequence uint
Balance uint64
Status byte
}
func ReadAccountDetail(r io.Reader, n *int64, err *error) *AccountDetail {
return &AccountDetail{
Account: ReadAccount(r, n, err),
Sequence: ReadUVarInt(r, n, err),
Balance: ReadUInt64(r, n, err),
Status: ReadByte(r, n, err),
func NewAccount(address []byte, pubKey PubKey) *Account {
return &Account{
Address: address,
PubKey: pubKey,
Sequence: uint(0),
Balance: uint64(0),
}
}
func (accDet *AccountDetail) WriteTo(w io.Writer) (n int64, err error) {
WriteBinary(w, accDet.Account, &n, &err)
WriteUVarInt(w, accDet.Sequence, &n, &err)
WriteUInt64(w, accDet.Balance, &n, &err)
WriteByte(w, accDet.Status, &n, &err)
return
func (account *Account) Copy() *Account {
accountCopy := *account
return &accountCopy
}
func (accDet *AccountDetail) String() string {
return fmt.Sprintf("AccountDetail{%v:%X Sequence:%v Balance:%v Status:%X}",
accDet.Id, accDet.PubKey, accDet.Sequence, accDet.Balance, accDet.Status)
func (account *Account) String() string {
return fmt.Sprintf("Account{%X:%v}", account.Address, account.PubKey)
}
func (accDet *AccountDetail) Copy() *AccountDetail {
accDetCopy := *accDet
return &accDetCopy
func AccountEncoder(o interface{}, w io.Writer, n *int64, err *error) {
WriteBinary(o.(*Account), w, n, err)
}
//-------------------------------------
var AccountDetailCodec = accountDetailCodec{}
type accountDetailCodec struct{}
func (abc accountDetailCodec) Encode(accDet interface{}, w io.Writer, n *int64, err *error) {
WriteBinary(w, accDet.(*AccountDetail), n, err)
func AccountDecoder(r io.Reader, n *int64, err *error) interface{} {
return ReadBinary(&Account{}, r, n, err)
}
func (abc accountDetailCodec) Decode(r io.Reader, n *int64, err *error) interface{} {
return ReadAccountDetail(r, n, err)
}
func (abc accountDetailCodec) Compare(o1 interface{}, o2 interface{}) int {
panic("AccountDetailCodec.Compare not implemented")
}
//-----------------------------------------------------------------------------
type PrivAccount struct {
Account
PrivKey []byte
}
// Generates a new account with private key.
// The Account.Id is empty since it isn't in the blockchain.
func GenPrivAccount() *PrivAccount {
privKey := CRandBytes(32)
pubKey := ed25519.MakePubKey(privKey)
return &PrivAccount{
Account: Account{
Id: uint64(0),
PubKey: pubKey,
},
PrivKey: privKey,
}
}
// The Account.Id is empty since it isn't in the blockchain.
func PrivAccountFromJSON(jsonBlob []byte) (privAccount *PrivAccount) {
err := json.Unmarshal(jsonBlob, &privAccount)
if err != nil {
Panicf("Couldn't read PrivAccount: %v", err)
}
return
}
// The Account.Id is empty since it isn't in the blockchain.
func PrivAccountFromFile(file string) *PrivAccount {
jsonBlob, err := ioutil.ReadFile(file)
if err != nil {
Panicf("Couldn't read PrivAccount from file: %v", err)
}
return PrivAccountFromJSON(jsonBlob)
}
func (pa *PrivAccount) SignBytes(msg []byte) Signature {
signature := ed25519.SignMessage(msg, pa.PrivKey, pa.PubKey)
sig := Signature{
SignerId: pa.Id,
Bytes: signature,
}
return sig
}
func (pa *PrivAccount) Sign(o Signable) {
if !o.GetSignature().IsZero() {
panic("Cannot sign: already signed")
}
msg := BinaryBytes(o)
sig := pa.SignBytes(msg)
o.SetSignature(sig)
var AccountCodec = Codec{
Encode: AccountEncoder,
Decode: AccountDecoder,
}
-28
View File
@@ -1,28 +0,0 @@
package state
import (
. "github.com/tendermint/tendermint/common"
"testing"
)
func TestSignAndValidate(t *testing.T) {
privAccount := GenPrivAccount()
account := &privAccount.Account
msg := CRandBytes(128)
sig := privAccount.SignBytes(msg)
t.Logf("msg: %X, sig: %X", msg, sig)
// Test the signature
if !account.VerifyBytes(msg, sig) {
t.Errorf("Account message signature verification failed")
}
// Mutate the signature, just one bit.
sig.Bytes[0] ^= byte(0x01)
if account.VerifyBytes(msg, sig) {
t.Errorf("Account message signature verification should have failed but passed instead")
}
}
+19 -33
View File
@@ -13,8 +13,9 @@ import (
)
type GenesisDoc struct {
GenesisTime time.Time
AccountDetails []*AccountDetail
GenesisTime time.Time
Accounts []*Account
Validators []*Validator
}
func GenesisDocFromJSON(jsonBlob []byte) (genState *GenesisDoc) {
@@ -31,47 +32,32 @@ func GenesisStateFromFile(db db_.DB, genDocFile string) *State {
Panicf("Couldn't read GenesisDoc file: %v", err)
}
genDoc := GenesisDocFromJSON(jsonBlob)
return GenesisStateFromDoc(db, genDoc)
return GenesisState(db, genDoc)
}
func GenesisStateFromDoc(db db_.DB, genDoc *GenesisDoc) *State {
return GenesisState(db, genDoc.GenesisTime, genDoc.AccountDetails)
}
func GenesisState(db db_.DB, genesisTime time.Time, accDets []*AccountDetail) *State {
if genesisTime.IsZero() {
genesisTime = time.Now()
}
// 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 {
func GenesisState(db db_.DB, genDoc *GenesisDoc) *State {
if len(genDoc.Validators) == 0 {
panic("Must have some validators")
}
if genDoc.GenesisTime.IsZero() {
genDoc.GenesisTime = time.Now()
}
// Make accounts state tree
accounts := merkle.NewIAVLTree(BasicCodec, AccountCodec, defaultAccountsCacheCapacity, db)
for _, acc := range genDoc.Accounts {
accounts.Set(acc.Address, acc)
}
return &State{
DB: db,
LastBlockHeight: 0,
LastBlockHash: nil,
LastBlockParts: PartSetHeader{},
LastBlockTime: genesisTime,
BondedValidators: NewValidatorSet(validators),
LastBlockTime: genDoc.GenesisTime,
BondedValidators: NewValidatorSet(genDoc.Validators),
UnbondingValidators: NewValidatorSet(nil),
accountDetails: accountDetails,
accounts: accounts,
}
}
+321 -182
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"time"
. "github.com/tendermint/tendermint/account"
. "github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/blocks"
. "github.com/tendermint/tendermint/common"
@@ -14,17 +15,11 @@ import (
)
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
stateKey = []byte("stateKey")
minBondAmount = uint64(1) // TODO adjust
defaultAccountsCacheCapacity = 1000 // TODO adjust
unbondingPeriodBlocks = uint(60 * 24 * 365) // TODO probably better to make it time based.
validatorTimeoutBlocks = uint(10) // TODO adjust
)
//-----------------------------------------------------------------------------
@@ -43,13 +38,14 @@ func (txErr InvalidTxError) Error() string {
// NOTE: not goroutine-safe.
type State struct {
DB db_.DB
LastBlockHeight uint32
LastBlockHeight uint
LastBlockHash []byte
LastBlockParts PartSetHeader
LastBlockTime time.Time
BondedValidators *ValidatorSet
UnbondingValidators *ValidatorSet
accountDetails merkle.Tree // Shouldn't be accessed directly.
accounts merkle.Tree // Shouldn't be accessed directly.
validatorInfos merkle.Tree // Shouldn't be accessed directly.
}
func LoadState(db db_.DB) *State {
@@ -58,20 +54,21 @@ func LoadState(db db_.DB) *State {
if len(buf) == 0 {
return nil
} else {
reader := bytes.NewReader(buf)
var n int64
var err error
s.LastBlockHeight = ReadUInt32(reader, &n, &err)
s.LastBlockHash = ReadByteSlice(reader, &n, &err)
s.LastBlockParts = ReadPartSetHeader(reader, &n, &err)
s.LastBlockTime = 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)
r, n, err := bytes.NewReader(buf), new(int64), new(error)
s.LastBlockHeight = ReadUVarInt(r, n, err)
s.LastBlockHash = ReadByteSlice(r, n, err)
s.LastBlockParts = ReadBinary(PartSetHeader{}, r, n, err).(PartSetHeader)
s.LastBlockTime = ReadTime(r, n, err)
s.BondedValidators = ReadBinary(&ValidatorSet{}, r, n, err).(*ValidatorSet)
s.UnbondingValidators = ReadBinary(&ValidatorSet{}, r, n, err).(*ValidatorSet)
accountsHash := ReadByteSlice(r, n, err)
s.accounts = merkle.NewIAVLTree(BasicCodec, AccountCodec, defaultAccountsCacheCapacity, db)
s.accounts.Load(accountsHash)
validatorInfosHash := ReadByteSlice(r, n, err)
s.validatorInfos = merkle.NewIAVLTree(BasicCodec, ValidatorInfoCodec, 0, db)
s.validatorInfos.Load(validatorInfosHash)
if *err != nil {
panic(*err)
}
// TODO: ensure that buf is completely read.
}
@@ -80,19 +77,18 @@ func LoadState(db db_.DB) *State {
// 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.LastBlockHeight, &n, &err)
WriteByteSlice(&buf, s.LastBlockHash, &n, &err)
WriteBinary(&buf, s.LastBlockParts, &n, &err)
WriteTime(&buf, s.LastBlockTime, &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.accounts.Save()
buf, n, err := new(bytes.Buffer), new(int64), new(error)
WriteUVarInt(s.LastBlockHeight, buf, n, err)
WriteByteSlice(s.LastBlockHash, buf, n, err)
WriteBinary(s.LastBlockParts, buf, n, err)
WriteTime(s.LastBlockTime, buf, n, err)
WriteBinary(s.BondedValidators, buf, n, err)
WriteBinary(s.UnbondingValidators, buf, n, err)
WriteByteSlice(s.accounts.Hash(), buf, n, err)
WriteByteSlice(s.validatorInfos.Hash(), buf, n, err)
if *err != nil {
panic(*err)
}
s.DB.Set(stateKey, buf.Bytes())
}
@@ -106,185 +102,312 @@ func (s *State) Copy() *State {
LastBlockTime: s.LastBlockTime,
BondedValidators: s.BondedValidators.Copy(),
UnbondingValidators: s.UnbondingValidators.Copy(),
accountDetails: s.accountDetails.Copy(),
accounts: s.accounts.Copy(),
validatorInfos: s.validatorInfos.Copy(),
}
}
func (s *State) GetOrMakeAccounts(ins []*TxInput, outs []*TxOutput) (map[string]*Account, error) {
accounts := map[string]*Account{}
for _, in := range ins {
// Account shouldn't be duplicated
if _, ok := accounts[string(in.Address)]; ok {
return nil, ErrTxDuplicateAddress
}
account := s.GetAccount(in.Address)
if account == nil {
return nil, ErrTxInvalidAddress
}
accounts[string(in.Address)] = account
}
for _, out := range outs {
// Account shouldn't be duplicated
if _, ok := accounts[string(out.Address)]; ok {
return nil, ErrTxDuplicateAddress
}
account := s.GetAccount(out.Address)
// output account may be nil (new)
if account == nil {
account = NewAccount(out.Address, PubKeyUnknown{})
}
accounts[string(out.Address)] = account
}
return accounts, nil
}
func (s *State) ValidateInputs(accounts map[string]*Account, signBytes []byte, ins []*TxInput) (total uint64, err error) {
for _, in := range ins {
account := accounts[string(in.Address)]
if account == nil {
panic("ValidateInputs() expects account in accounts")
}
// Check TxInput basic
if err := in.ValidateBasic(); err != nil {
return 0, err
}
// Check amount
if account.Balance < in.Amount {
return 0, ErrTxInsufficientFunds
}
// Check signatures
if !account.PubKey.VerifyBytes(signBytes, in.Signature) {
return 0, ErrTxInvalidSignature
}
// Check sequences
if account.Sequence+1 != in.Sequence {
return 0, ErrTxInvalidSequence
}
// Good. Add amount to total
total += in.Amount
}
return total, nil
}
func (s *State) ValidateOutputs(outs []*TxOutput) (total uint64, err error) {
for _, out := range outs {
// Check TxOutput basic
if err := out.ValidateBasic(); err != nil {
return 0, err
}
// Good. Add amount to total
total += out.Amount
}
return total, nil
}
func (s *State) AdjustByInputs(accounts map[string]*Account, ins []*TxInput) {
for _, in := range ins {
account := accounts[string(in.Address)]
if account == nil {
panic("AdjustByInputs() expects account in accounts")
}
if account.Balance < in.Amount {
panic("AdjustByInputs() expects sufficient funds")
}
account.Balance -= in.Amount
}
}
func (s *State) AdjustByOutputs(accounts map[string]*Account, outs []*TxOutput) {
for _, out := range outs {
account := accounts[string(out.Address)]
if account == nil {
panic("AdjustByInputs() expects account in accounts")
}
account.Balance += out.Amount
}
}
// 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()
}
func (s *State) ExecTx(tx_ Tx) error {
// TODO: do something with fees
fees := uint64(0)
// Exec tx
switch tx.(type) {
switch tx_.(type) {
case *SendTx:
stx := tx.(*SendTx)
toAccDet := s.GetAccountDetail(stx.To)
// Accounts must be nominal
if accDet.Status != AccountStatusNominal {
return ErrStateInvalidAccountState
tx := tx_.(*SendTx)
accounts, err := s.GetOrMakeAccounts(tx.Inputs, tx.Outputs)
if err != nil {
return err
}
if toAccDet.Status != AccountStatusNominal {
return ErrStateInvalidAccountState
signBytes := SignBytes(tx)
inTotal, err := s.ValidateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
// Check account balance
if accDet.Balance < stx.Amount {
return ErrStateInsufficientFunds
outTotal, err := s.ValidateOutputs(tx.Outputs)
if err != nil {
return err
}
// Check existence of destination account
if toAccDet == nil {
return ErrStateInvalidAccountId
if outTotal > inTotal {
return ErrTxInsufficientFunds
}
// Good!
accDet.Balance -= stx.Amount
toAccDet.Balance += stx.Amount
s.SetAccountDetail(accDet)
s.SetAccountDetail(toAccDet)
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
s.AdjustByInputs(accounts, tx.Inputs)
s.AdjustByOutputs(accounts, tx.Outputs)
s.SetAccounts(accounts)
return nil
//case *NameTx
case *BondTx:
//btx := tx.(*BondTx)
// Account must be nominal
if accDet.Status != AccountStatusNominal {
return ErrStateInvalidAccountState
tx := tx_.(*BondTx)
accounts, err := s.GetOrMakeAccounts(tx.Inputs, tx.UnbondTo)
if err != nil {
return err
}
// Check account balance
if accDet.Balance < minBondAmount {
return ErrStateInsufficientFunds
signBytes := SignBytes(tx)
inTotal, err := s.ValidateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
// Good!
accDet.Status = AccountStatusBonded
s.SetAccountDetail(accDet)
if err := tx.PubKey.ValidateBasic(); err != nil {
return err
}
outTotal, err := s.ValidateOutputs(tx.UnbondTo)
if err != nil {
return err
}
if outTotal > inTotal {
return ErrTxInsufficientFunds
}
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
s.AdjustByInputs(accounts, tx.Inputs)
s.SetAccounts(accounts)
// Add ValidatorInfo
updated := s.SetValidatorInfo(&ValidatorInfo{
Address: tx.PubKey.Address(),
PubKey: tx.PubKey,
UnbondTo: tx.UnbondTo,
FirstBondHeight: s.LastBlockHeight + 1,
})
if !updated {
panic("Failed to add validator info")
}
// Add Validator
added := s.BondedValidators.Add(&Validator{
Account: accDet.Account,
BondHeight: s.LastBlockHeight,
VotingPower: accDet.Balance,
Address: tx.PubKey.Address(),
PubKey: tx.PubKey,
BondHeight: s.LastBlockHeight + 1,
VotingPower: inTotal,
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
tx := tx_.(*UnbondTx)
// The validator must be active
_, val := s.BondedValidators.GetByAddress(tx.Address)
if val == nil {
return ErrTxInvalidAddress
}
// Verify the signature
signBytes := SignBytes(tx)
if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
return ErrTxInvalidSignature
}
// tx.Height must be greater than val.LastCommitHeight
if tx.Height < val.LastCommitHeight {
return errors.New("Invalid bond height")
}
// Good!
s.unbondValidator(accDet.Id, accDet)
s.SetAccountDetail(accDet)
s.unbondValidator(val)
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)
tx := 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
_, accused := s.BondedValidators.GetByAddress(tx.Address)
voteASignBytes := SignBytes(&tx.VoteA)
voteBSignBytes := SignBytes(&tx.VoteB)
if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) ||
!accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) {
return ErrTxInvalidSignature
}
// Verify equivocation
if dtx.VoteA.Height != dtx.VoteB.Height {
return errors.New("DupeoutTx height must be the same.")
// TODO: in the future, just require one vote from a previous height that
// doesn't exist on this chain.
if tx.VoteA.Height != tx.VoteB.Height {
return errors.New("DupeoutTx heights don't match")
}
if dtx.VoteA.Type == VoteTypeCommit && dtx.VoteA.Round < dtx.VoteB.Round {
if tx.VoteA.Type == VoteTypeCommit && tx.VoteA.Round < tx.VoteB.Round {
// Check special case.
// Validators should not sign another vote after committing.
} else {
if dtx.VoteA.Round != dtx.VoteB.Round {
if tx.VoteA.Round != tx.VoteB.Round {
return errors.New("DupeoutTx rounds don't match")
}
if dtx.VoteA.Type != dtx.VoteB.Type {
if tx.VoteA.Type != tx.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")
if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) {
return errors.New("DupeoutTx blockhashes 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")
}
s.destroyValidator(accused)
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)
func (s *State) unbondValidator(val *Validator) {
// Move validator to UnbondingValidators
val, removed := s.BondedValidators.Remove(val.Address)
if !removed {
panic("Failed to remove validator")
panic("Couldn't remove validator for unbonding")
}
val.UnbondHeight = s.LastBlockHeight
added := s.UnbondingValidators.Add(val)
if !added {
panic("Failed to add validator")
panic("Couldn't add validator for unbonding")
}
}
func (s *State) releaseValidator(accountId uint64) {
accDet := s.GetAccountDetail(accountId)
if accDet.Status != AccountStatusUnbonding {
panic("Cannot release validator")
func (s *State) releaseValidator(val *Validator) {
// Update validatorInfo
valInfo := s.GetValidatorInfo(val.Address)
if valInfo == nil {
panic("Couldn't find validatorInfo for release")
}
accDet.Status = AccountStatusNominal
// TODO: move balance to designated address, UnbondTo.
s.SetAccountDetail(accDet)
_, removed := s.UnbondingValidators.Remove(accountId)
valInfo.ReleasedHeight = s.LastBlockHeight + 1
s.SetValidatorInfo(valInfo)
// Send coins back to UnbondTo outputs
accounts, err := s.GetOrMakeAccounts(nil, valInfo.UnbondTo)
if err != nil {
panic("Couldn't get or make unbondTo accounts")
}
s.AdjustByOutputs(accounts, valInfo.UnbondTo)
s.SetAccounts(accounts)
// Remove validator from UnbondingValidators
_, removed := s.UnbondingValidators.Remove(val.Address)
if !removed {
panic("Couldn't release validator")
panic("Couldn't remove validator for release")
}
}
func (s *State) destroyValidator(val *Validator) {
// Update validatorInfo
valInfo := s.GetValidatorInfo(val.Address)
if valInfo == nil {
panic("Couldn't find validatorInfo for release")
}
valInfo.DestroyedHeight = s.LastBlockHeight + 1
valInfo.DestroyedAmount = val.VotingPower
s.SetValidatorInfo(valInfo)
// Remove validator
_, removed := s.BondedValidators.Remove(val.Address)
if !removed {
_, removed := s.UnbondingValidators.Remove(val.Address)
if !removed {
panic("Couldn't remove validator for destruction")
}
}
}
// "checkStateHash": If false, instead of checking the resulting
// state.Hash() against block.StateHash, it *sets* the block.StateHash.
// (used for constructing a new proposal)
@@ -308,23 +431,18 @@ func (s *State) AppendBlock(block *Block, blockPartsHeader PartSetHeader, checkS
}
var sumVotingPower uint64
s.BondedValidators.Iterate(func(index uint, val *Validator) bool {
rsig := block.Validation.Commits[index]
if rsig.IsZero() {
commit := block.Validation.Commits[index]
if commit.IsZero() {
return false
} else {
if rsig.SignerId != val.Id {
err = errors.New("Invalid validation order")
return true
}
vote := &Vote{
Height: block.Height - 1,
Round: rsig.Round,
Round: commit.Round,
Type: VoteTypeCommit,
BlockHash: block.LastBlockHash,
BlockParts: block.LastBlockParts,
Signature: rsig.Signature,
}
if val.Verify(vote) {
if val.PubKey.VerifyBytes(SignBytes(vote), commit.Signature) {
sumVotingPower += val.VotingPower
return false
} else {
@@ -351,10 +469,13 @@ func (s *State) AppendBlock(block *Block, blockPartsHeader PartSetHeader, checkS
}
// Update Validator.LastCommitHeight as necessary.
for _, rsig := range block.Validation.Commits {
_, val := s.BondedValidators.GetById(rsig.SignerId)
for i, commit := range block.Validation.Commits {
if commit.IsZero() {
continue
}
_, val := s.BondedValidators.GetByIndex(uint(i))
if val == nil {
return ErrStateInvalidSignature
return ErrTxInvalidSignature
}
val.LastCommitHeight = block.Height - 1
updated := s.BondedValidators.Update(val)
@@ -373,7 +494,7 @@ func (s *State) AppendBlock(block *Block, blockPartsHeader PartSetHeader, checkS
return false
})
for _, val := range toRelease {
s.releaseValidator(val.Id)
s.releaseValidator(val)
}
// If any validators haven't signed in a while,
@@ -386,7 +507,7 @@ func (s *State) AppendBlock(block *Block, blockPartsHeader PartSetHeader, checkS
return false
})
for _, val := range toTimeout {
s.unbondValidator(val.Id, nil)
s.unbondValidator(val)
}
// Increment validator AccumPowers
@@ -415,21 +536,39 @@ func (s *State) AppendBlock(block *Block, blockPartsHeader PartSetHeader, checkS
return nil
}
// The returned AccountDetail is a copy, so mutating it
// The returned Account 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 {
func (s *State) GetAccount(address []byte) *Account {
_, account := s.accounts.Get(address)
if account == nil {
return nil
}
return accDet.(*AccountDetail).Copy()
return account.(*Account).Copy()
}
// The accounts are copied before setting, so mutating it
// afterwards has no side effects.
func (s *State) SetAccounts(accounts map[string]*Account) {
for _, account := range accounts {
s.accounts.Set(account.Address, account.Copy())
}
}
// The returned ValidatorInfo is a copy, so mutating it
// has no side effects.
func (s *State) GetValidatorInfo(address []byte) *ValidatorInfo {
_, valInfo := s.validatorInfos.Get(address)
if valInfo == nil {
return nil
}
return valInfo.(*ValidatorInfo).Copy()
}
// Returns false if new, true if updated.
// The accDet is copied before setting, so mutating it
// The valInfo 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())
func (s *State) SetValidatorInfo(valInfo *ValidatorInfo) (updated bool) {
return s.validatorInfos.Set(valInfo.Address, valInfo.Copy())
}
// Returns a hash that represents the state data,
@@ -438,7 +577,7 @@ func (s *State) Hash() []byte {
hashables := []merkle.Hashable{
s.BondedValidators,
s.UnbondingValidators,
s.accountDetails,
s.accounts,
}
return merkle.HashFromHashables(hashables)
}
+55 -38
View File
@@ -1,34 +1,61 @@
package state
import (
"bytes"
"fmt"
"io"
. "github.com/tendermint/tendermint/account"
. "github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/blocks"
)
// Holds state for a Validator at a given height+round.
// Meant to be discarded every round of the consensus protocol.
// TODO consider moving this to another common types package.
type Validator struct {
Account
BondHeight uint32
UnbondHeight uint32
LastCommitHeight uint32
VotingPower uint64
Accum int64
// Persistent static data for each Validator
type ValidatorInfo struct {
Address []byte
PubKey PubKeyEd25519
UnbondTo []*TxOutput
FirstBondHeight uint
// If destroyed:
DestroyedHeight uint
DestroyedAmount uint64
// If released:
ReleasedHeight uint
}
// 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),
UnbondHeight: ReadUInt32(r, n, err),
LastCommitHeight: ReadUInt32(r, n, err),
VotingPower: ReadUInt64(r, n, err),
Accum: ReadInt64(r, n, err),
}
func (valInfo *ValidatorInfo) Copy() *ValidatorInfo {
valInfoCopy := *valInfo
return &valInfoCopy
}
func ValidatorInfoEncoder(o interface{}, w io.Writer, n *int64, err *error) {
WriteBinary(o.(*ValidatorInfo), w, n, err)
}
func ValidatorInfoDecoder(r io.Reader, n *int64, err *error) interface{} {
return ReadBinary(&ValidatorInfo{}, r, n, err)
}
var ValidatorInfoCodec = Codec{
Encode: ValidatorInfoEncoder,
Decode: ValidatorInfoDecoder,
}
//-----------------------------------------------------------------------------
// Volatile state for each Validator
// Also persisted with the state, but fields change
// every height|round so they don't go in merkle.Tree
type Validator struct {
Address []byte
PubKey PubKeyEd25519
BondHeight uint
UnbondHeight uint
LastCommitHeight uint
VotingPower uint64
Accum int64
}
// Creates a new copy of the validator so we can mutate accum.
@@ -37,17 +64,6 @@ func (v *Validator) Copy() *Validator {
return &vCopy
}
// Used to persist the state of ConsensusStateControl.
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
}
// Returns the one with higher Accum.
func (v *Validator) CompareAccum(other *Validator) *Validator {
if v == nil {
@@ -58,9 +74,9 @@ func (v *Validator) CompareAccum(other *Validator) *Validator {
} else if v.Accum < other.Accum {
return other
} else {
if v.Id < other.Id {
if bytes.Compare(v.Address, other.Address) < 0 {
return v
} else if v.Id > other.Id {
} else if bytes.Compare(v.Address, other.Address) > 0 {
return other
} else {
panic("Cannot compare identical validators")
@@ -69,8 +85,9 @@ func (v *Validator) CompareAccum(other *Validator) *Validator {
}
func (v *Validator) String() string {
return fmt.Sprintf("Validator{%v %v-%v-%v VP:%v A:%v}",
v.Account,
return fmt.Sprintf("Validator{%X %v %v-%v-%v VP:%v A:%v}",
v.Address,
v.PubKey,
v.BondHeight,
v.LastCommitHeight,
v.UnbondHeight,
@@ -79,7 +96,7 @@ func (v *Validator) String() string {
}
func (v *Validator) Hash() []byte {
return BinaryHash(v)
return BinarySha256(v)
}
//-------------------------------------
@@ -89,11 +106,11 @@ var ValidatorCodec = validatorCodec{}
type validatorCodec struct{}
func (vc validatorCodec) Encode(o interface{}, w io.Writer, n *int64, err *error) {
WriteBinary(w, o.(*Validator), n, err)
WriteBinary(o.(*Validator), w, n, err)
}
func (vc validatorCodec) Decode(r io.Reader, n *int64, err *error) interface{} {
return ReadValidator(r, n, err)
return ReadBinary(&Validator{}, r, n, err)
}
func (vc validatorCodec) Compare(o1 interface{}, o2 interface{}) int {
+87 -101
View File
@@ -1,12 +1,11 @@
package state
import (
"bytes"
"fmt"
"io"
"sort"
"strings"
. "github.com/tendermint/tendermint/binary"
"github.com/tendermint/tendermint/merkle"
)
@@ -20,7 +19,7 @@ func (vs ValidatorSlice) Len() int {
}
func (vs ValidatorSlice) Less(i, j int) bool {
return vs[i].Id < vs[j].Id
return bytes.Compare(vs[i].Address, vs[j].Address) == -1
}
func (vs ValidatorSlice) Swap(i, j int) {
@@ -31,10 +30,17 @@ func (vs ValidatorSlice) Swap(i, j int) {
//-------------------------------------
// Not goroutine-safe.
// TODO: consider validator Accum overflow?
// ValidatorSet represent a set of *Validator at a given height.
// The validators can be fetched by address or index.
// The index is in order of .Address, so the index are the same
// for all rounds of a given blockchain height.
// On the other hand, the .AccumPower of each validator and
// the designated .Proposer() of a set changes every round,
// upon calling .IncrementAccum().
// NOTE: Not goroutine-safe.
// NOTE: All get/set to validators should copy the value for safety.
// TODO: consider validator Accum overflow
// TODO: replace validators []*Validator with github.com/jaekwon/go-ibbs?
// NOTE: all get/set to validators should copy the value for safety.
type ValidatorSet struct {
validators []*Validator
@@ -54,169 +60,149 @@ func NewValidatorSet(vals []*Validator) *ValidatorSet {
}
}
func ReadValidatorSet(r io.Reader, n *int64, err *error) *ValidatorSet {
size := ReadUVarInt(r, n, err)
validators := []*Validator{}
for i := uint(0); i < size; i++ {
validator := ReadValidator(r, n, err)
validators = append(validators, validator)
}
sort.Sort(ValidatorSlice(validators))
return NewValidatorSet(validators)
}
func (vset *ValidatorSet) WriteTo(w io.Writer) (n int64, err error) {
WriteUVarInt(w, uint(len(vset.validators)), &n, &err)
vset.Iterate(func(index uint, val *Validator) bool {
WriteBinary(w, val, &n, &err)
return false
})
return
}
func (vset *ValidatorSet) IncrementAccum() {
func (valSet *ValidatorSet) IncrementAccum() {
// Decrement from previous proposer
oldProposer := vset.Proposer()
oldProposer.Accum -= int64(vset.TotalVotingPower())
vset.Update(oldProposer)
oldProposer := valSet.Proposer()
oldProposer.Accum -= int64(valSet.TotalVotingPower())
valSet.Update(oldProposer)
var newProposer *Validator
// Increment accum and find new proposer
// NOTE: updates validators in place.
for _, val := range vset.validators {
for _, val := range valSet.validators {
val.Accum += int64(val.VotingPower)
newProposer = newProposer.CompareAccum(val)
}
vset.proposer = newProposer
valSet.proposer = newProposer
}
func (vset *ValidatorSet) Copy() *ValidatorSet {
validators := make([]*Validator, len(vset.validators))
for i, val := range vset.validators {
func (valSet *ValidatorSet) Copy() *ValidatorSet {
validators := make([]*Validator, len(valSet.validators))
for i, val := range valSet.validators {
// NOTE: must copy, since IncrementAccum updates in place.
validators[i] = val.Copy()
}
return &ValidatorSet{
validators: validators,
proposer: vset.proposer,
totalVotingPower: vset.totalVotingPower,
proposer: valSet.proposer,
totalVotingPower: valSet.totalVotingPower,
}
}
func (vset *ValidatorSet) HasId(id uint64) bool {
idx := sort.Search(len(vset.validators), func(i int) bool {
return id <= vset.validators[i].Id
func (valSet *ValidatorSet) HasAddress(address []byte) bool {
idx := sort.Search(len(valSet.validators), func(i int) bool {
return bytes.Compare(address, valSet.validators[i].Address) <= 0
})
return idx != len(vset.validators) && vset.validators[idx].Id == id
return idx != len(valSet.validators) && bytes.Compare(valSet.validators[idx].Address, address) == 0
}
func (vset *ValidatorSet) GetById(id uint64) (index uint, val *Validator) {
idx := sort.Search(len(vset.validators), func(i int) bool {
return id <= vset.validators[i].Id
func (valSet *ValidatorSet) GetByAddress(address []byte) (index uint, val *Validator) {
idx := sort.Search(len(valSet.validators), func(i int) bool {
return bytes.Compare(address, valSet.validators[i].Address) <= 0
})
if idx != len(vset.validators) && vset.validators[idx].Id == id {
return uint(idx), vset.validators[idx].Copy()
if idx != len(valSet.validators) && bytes.Compare(valSet.validators[idx].Address, address) == 0 {
return uint(idx), valSet.validators[idx].Copy()
} else {
return 0, nil
}
}
func (vset *ValidatorSet) GetByIndex(index uint) (id uint64, val *Validator) {
val = vset.validators[index]
return val.Id, val.Copy()
func (valSet *ValidatorSet) GetByIndex(index uint) (address []byte, val *Validator) {
val = valSet.validators[index]
return val.Address, val.Copy()
}
func (vset *ValidatorSet) Size() uint {
return uint(len(vset.validators))
func (valSet *ValidatorSet) Size() uint {
return uint(len(valSet.validators))
}
func (vset *ValidatorSet) TotalVotingPower() uint64 {
if vset.totalVotingPower == 0 {
for _, val := range vset.validators {
vset.totalVotingPower += val.VotingPower
func (valSet *ValidatorSet) TotalVotingPower() uint64 {
if valSet.totalVotingPower == 0 {
for _, val := range valSet.validators {
valSet.totalVotingPower += val.VotingPower
}
}
return vset.totalVotingPower
return valSet.totalVotingPower
}
func (vset *ValidatorSet) Proposer() (proposer *Validator) {
if vset.proposer == nil {
for _, val := range vset.validators {
vset.proposer = vset.proposer.CompareAccum(val)
func (valSet *ValidatorSet) Proposer() (proposer *Validator) {
if valSet.proposer == nil {
for _, val := range valSet.validators {
valSet.proposer = valSet.proposer.CompareAccum(val)
}
}
return vset.proposer.Copy()
return valSet.proposer.Copy()
}
func (vset *ValidatorSet) Hash() []byte {
if len(vset.validators) == 0 {
func (valSet *ValidatorSet) Hash() []byte {
if len(valSet.validators) == 0 {
return nil
}
hashables := make([]merkle.Hashable, len(vset.validators))
for i, val := range vset.validators {
hashables := make([]merkle.Hashable, len(valSet.validators))
for i, val := range valSet.validators {
hashables[i] = val
}
return merkle.HashFromHashables(hashables)
}
func (vset *ValidatorSet) Add(val *Validator) (added bool) {
func (valSet *ValidatorSet) Add(val *Validator) (added bool) {
val = val.Copy()
idx := sort.Search(len(vset.validators), func(i int) bool {
return val.Id <= vset.validators[i].Id
idx := sort.Search(len(valSet.validators), func(i int) bool {
return bytes.Compare(val.Address, valSet.validators[i].Address) <= 0
})
if idx == len(vset.validators) {
vset.validators = append(vset.validators, val)
if idx == len(valSet.validators) {
valSet.validators = append(valSet.validators, val)
// Invalidate cache
vset.proposer = nil
vset.totalVotingPower = 0
valSet.proposer = nil
valSet.totalVotingPower = 0
return true
} else if vset.validators[idx].Id == val.Id {
} else if bytes.Compare(valSet.validators[idx].Address, val.Address) == 0 {
return false
} else {
newValidators := append(vset.validators[:idx], val)
newValidators = append(newValidators, vset.validators[idx:]...)
vset.validators = newValidators
newValidators := append(valSet.validators[:idx], val)
newValidators = append(newValidators, valSet.validators[idx:]...)
valSet.validators = newValidators
// Invalidate cache
vset.proposer = nil
vset.totalVotingPower = 0
valSet.proposer = nil
valSet.totalVotingPower = 0
return true
}
}
func (vset *ValidatorSet) Update(val *Validator) (updated bool) {
index, sameVal := vset.GetById(val.Id)
func (valSet *ValidatorSet) Update(val *Validator) (updated bool) {
index, sameVal := valSet.GetByAddress(val.Address)
if sameVal == nil {
return false
} else {
vset.validators[index] = val.Copy()
valSet.validators[index] = val.Copy()
// Invalidate cache
vset.proposer = nil
vset.totalVotingPower = 0
valSet.proposer = nil
valSet.totalVotingPower = 0
return true
}
}
func (vset *ValidatorSet) Remove(id uint64) (val *Validator, removed bool) {
idx := sort.Search(len(vset.validators), func(i int) bool {
return id <= vset.validators[i].Id
func (valSet *ValidatorSet) Remove(address []byte) (val *Validator, removed bool) {
idx := sort.Search(len(valSet.validators), func(i int) bool {
return bytes.Compare(address, valSet.validators[i].Address) <= 0
})
if idx == len(vset.validators) || vset.validators[idx].Id != id {
if idx == len(valSet.validators) || bytes.Compare(valSet.validators[idx].Address, address) != 0 {
return nil, false
} else {
removedVal := vset.validators[idx]
newValidators := vset.validators[:idx]
if idx+1 < len(vset.validators) {
newValidators = append(newValidators, vset.validators[idx+1:]...)
removedVal := valSet.validators[idx]
newValidators := valSet.validators[:idx]
if idx+1 < len(valSet.validators) {
newValidators = append(newValidators, valSet.validators[idx+1:]...)
}
vset.validators = newValidators
valSet.validators = newValidators
// Invalidate cache
vset.proposer = nil
vset.totalVotingPower = 0
valSet.proposer = nil
valSet.totalVotingPower = 0
return removedVal, true
}
}
func (vset *ValidatorSet) Iterate(fn func(index uint, val *Validator) bool) {
for i, val := range vset.validators {
func (valSet *ValidatorSet) Iterate(fn func(index uint, val *Validator) bool) {
for i, val := range valSet.validators {
stop := fn(uint(i), val.Copy())
if stop {
break
@@ -224,13 +210,13 @@ func (vset *ValidatorSet) Iterate(fn func(index uint, val *Validator) bool) {
}
}
func (vset *ValidatorSet) String() string {
return vset.StringWithIndent("")
func (valSet *ValidatorSet) String() string {
return valSet.StringWithIndent("")
}
func (vset *ValidatorSet) StringWithIndent(indent string) string {
func (valSet *ValidatorSet) StringWithIndent(indent string) string {
valStrings := []string{}
vset.Iterate(func(index uint, val *Validator) bool {
valSet.Iterate(func(index uint, val *Validator) bool {
valStrings = append(valStrings, val.String())
return false
})
@@ -239,7 +225,7 @@ func (vset *ValidatorSet) StringWithIndent(indent string) string {
%s Validators:
%s %v
%s}`,
indent, vset.Proposer().String(),
indent, valSet.Proposer().String(),
indent,
indent, strings.Join(valStrings, "\n"+indent+" "),
indent)