mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-13 11:34:17 +00:00
Refactor Tx, Validator, and Account structure
This commit is contained in:
+21
-151
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user