|
|
- package state
-
- import (
- "bytes"
- "errors"
- "fmt"
-
- acm "github.com/tendermint/tendermint/account"
- . "github.com/tendermint/tendermint/common"
- "github.com/tendermint/tendermint/events"
- ptypes "github.com/tendermint/tendermint/permission/types" // for GlobalPermissionAddress ...
- "github.com/tendermint/tendermint/types"
- "github.com/tendermint/tendermint/vm"
- )
-
- // NOTE: If an error occurs during block execution, state will be left
- // at an invalid state. Copy the state before calling ExecBlock!
- func ExecBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader) error {
- err := execBlock(s, block, blockPartsHeader)
- if err != nil {
- return err
- }
- // State.Hash should match block.StateHash
- stateHash := s.Hash()
- if !bytes.Equal(stateHash, block.StateHash) {
- return errors.New(Fmt("Invalid state hash. Expected %X, got %X",
- stateHash, block.StateHash))
- }
- return nil
- }
-
- // executes transactions of a block, does not check block.StateHash
- // NOTE: If an error occurs during block execution, state will be left
- // at an invalid state. Copy the state before calling execBlock!
- func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader) error {
- // Basic block validation.
- err := block.ValidateBasic(s.ChainID, s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime)
- if err != nil {
- return err
- }
-
- // Validate block LastValidation.
- if block.Height == 1 {
- if len(block.LastValidation.Precommits) != 0 {
- return errors.New("Block at height 1 (first block) should have no LastValidation precommits")
- }
- } else {
- if len(block.LastValidation.Precommits) != s.LastBondedValidators.Size() {
- return errors.New(Fmt("Invalid block validation size. Expected %v, got %v",
- s.LastBondedValidators.Size(), len(block.LastValidation.Precommits)))
- }
- err := s.LastBondedValidators.VerifyValidation(
- s.ChainID, s.LastBlockHash, s.LastBlockParts, block.Height-1, block.LastValidation)
- if err != nil {
- return err
- }
- }
-
- // Update Validator.LastCommitHeight as necessary.
- for i, precommit := range block.LastValidation.Precommits {
- if precommit == nil {
- continue
- }
- _, val := s.LastBondedValidators.GetByIndex(i)
- if val == nil {
- PanicCrisis(Fmt("Failed to fetch validator at index %v", i))
- }
- if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil {
- val_.LastCommitHeight = block.Height - 1
- updated := s.BondedValidators.Update(val_)
- if !updated {
- PanicCrisis("Failed to update bonded validator LastCommitHeight")
- }
- } else if _, val_ := s.UnbondingValidators.GetByAddress(val.Address); val_ != nil {
- val_.LastCommitHeight = block.Height - 1
- updated := s.UnbondingValidators.Update(val_)
- if !updated {
- PanicCrisis("Failed to update unbonding validator LastCommitHeight")
- }
- } else {
- PanicCrisis("Could not find validator")
- }
- }
-
- // Remember LastBondedValidators
- s.LastBondedValidators = s.BondedValidators.Copy()
-
- // Create BlockCache to cache changes to state.
- blockCache := NewBlockCache(s)
-
- // Execute each tx
- for _, tx := range block.Data.Txs {
- err := ExecTx(blockCache, tx, true, s.evc)
- if err != nil {
- return InvalidTxError{tx, err}
- }
- }
-
- // Now sync the BlockCache to the backend.
- blockCache.Sync()
-
- // If any unbonding periods are over,
- // reward account with bonded coins.
- toRelease := []*Validator{}
- s.UnbondingValidators.Iterate(func(index int, val *Validator) bool {
- if val.UnbondHeight+unbondingPeriodBlocks < block.Height {
- toRelease = append(toRelease, val)
- }
- return false
- })
- for _, val := range toRelease {
- s.releaseValidator(val)
- }
-
- // If any validators haven't signed in a while,
- // unbond them, they have timed out.
- toTimeout := []*Validator{}
- s.BondedValidators.Iterate(func(index int, val *Validator) bool {
- lastActivityHeight := MaxInt(val.BondHeight, val.LastCommitHeight)
- if lastActivityHeight+validatorTimeoutBlocks < block.Height {
- log.Notice("Validator timeout", "validator", val, "height", block.Height)
- toTimeout = append(toTimeout, val)
- }
- return false
- })
- for _, val := range toTimeout {
- s.unbondValidator(val)
- }
-
- // Increment validator AccumPowers
- s.BondedValidators.IncrementAccum(1)
- s.LastBlockHeight = block.Height
- s.LastBlockHash = block.Hash()
- s.LastBlockParts = blockPartsHeader
- s.LastBlockTime = block.Time
- return nil
- }
-
- // The accounts from the TxInputs must either already have
- // acm.PubKey.(type) != nil, (it must be known),
- // or it must be specified in the TxInput. If redeclared,
- // the TxInput is modified and input.PubKey set to nil.
- func getInputs(state AccountGetter, ins []*types.TxInput) (map[string]*acm.Account, error) {
- accounts := map[string]*acm.Account{}
- for _, in := range ins {
- // Account shouldn't be duplicated
- if _, ok := accounts[string(in.Address)]; ok {
- return nil, types.ErrTxDuplicateAddress
- }
- acc := state.GetAccount(in.Address)
- if acc == nil {
- return nil, types.ErrTxInvalidAddress
- }
- // PubKey should be present in either "account" or "in"
- if err := checkInputPubKey(acc, in); err != nil {
- return nil, err
- }
- accounts[string(in.Address)] = acc
- }
- return accounts, nil
- }
-
- func getOrMakeOutputs(state AccountGetter, accounts map[string]*acm.Account, outs []*types.TxOutput) (map[string]*acm.Account, error) {
- if accounts == nil {
- accounts = make(map[string]*acm.Account)
- }
-
- // we should err if an account is being created but the inputs don't have permission
- var checkedCreatePerms bool
- for _, out := range outs {
- // Account shouldn't be duplicated
- if _, ok := accounts[string(out.Address)]; ok {
- return nil, types.ErrTxDuplicateAddress
- }
- acc := state.GetAccount(out.Address)
- // output account may be nil (new)
- if acc == nil {
- if !checkedCreatePerms {
- if !hasCreateAccountPermission(state, accounts) {
- return nil, fmt.Errorf("At least one input does not have permission to create accounts")
- }
- checkedCreatePerms = true
- }
- acc = &acm.Account{
- Address: out.Address,
- PubKey: nil,
- Sequence: 0,
- Balance: 0,
- Permissions: ptypes.ZeroAccountPermissions,
- }
- }
- accounts[string(out.Address)] = acc
- }
- return accounts, nil
- }
-
- func checkInputPubKey(acc *acm.Account, in *types.TxInput) error {
- if acc.PubKey == nil {
- if in.PubKey == nil {
- return types.ErrTxUnknownPubKey
- }
- if !bytes.Equal(in.PubKey.Address(), acc.Address) {
- return types.ErrTxInvalidPubKey
- }
- acc.PubKey = in.PubKey
- } else {
- in.PubKey = nil
- }
- return nil
- }
-
- func validateInputs(accounts map[string]*acm.Account, signBytes []byte, ins []*types.TxInput) (total int64, err error) {
- for _, in := range ins {
- acc := accounts[string(in.Address)]
- if acc == nil {
- PanicSanity("validateInputs() expects account in accounts")
- }
- err = validateInput(acc, signBytes, in)
- if err != nil {
- return
- }
- // Good. Add amount to total
- total += in.Amount
- }
- return total, nil
- }
-
- func validateInput(acc *acm.Account, signBytes []byte, in *types.TxInput) (err error) {
- // Check TxInput basic
- if err := in.ValidateBasic(); err != nil {
- return err
- }
- // Check signatures
- if !acc.PubKey.VerifyBytes(signBytes, in.Signature) {
- return types.ErrTxInvalidSignature
- }
- // Check sequences
- if acc.Sequence+1 != in.Sequence {
- return types.ErrTxInvalidSequence{
- Got: in.Sequence,
- Expected: acc.Sequence + 1,
- }
- }
- // Check amount
- if acc.Balance < in.Amount {
- return types.ErrTxInsufficientFunds
- }
- return nil
- }
-
- func validateOutputs(outs []*types.TxOutput) (total int64, 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 adjustByInputs(accounts map[string]*acm.Account, ins []*types.TxInput) {
- for _, in := range ins {
- acc := accounts[string(in.Address)]
- if acc == nil {
- PanicSanity("adjustByInputs() expects account in accounts")
- }
- if acc.Balance < in.Amount {
- PanicSanity("adjustByInputs() expects sufficient funds")
- }
- acc.Balance -= in.Amount
- acc.Sequence += 1
- }
- }
-
- func adjustByOutputs(accounts map[string]*acm.Account, outs []*types.TxOutput) {
- for _, out := range outs {
- acc := accounts[string(out.Address)]
- if acc == nil {
- PanicSanity("adjustByOutputs() expects account in accounts")
- }
- acc.Balance += out.Amount
- }
- }
-
- // If the tx is invalid, an error will be returned.
- // Unlike ExecBlock(), state will not be altered.
- func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireable) (err error) {
-
- // TODO: do something with fees
- fees := int64(0)
- _s := blockCache.State() // hack to access validators and block height
-
- // Exec tx
- switch tx := tx.(type) {
- case *types.SendTx:
- accounts, err := getInputs(blockCache, tx.Inputs)
- if err != nil {
- return err
- }
-
- // ensure all inputs have send permissions
- if !hasSendPermission(blockCache, accounts) {
- return fmt.Errorf("At least one input lacks permission for SendTx")
- }
-
- // add outputs to accounts map
- // if any outputs don't exist, all inputs must have CreateAccount perm
- accounts, err = getOrMakeOutputs(blockCache, accounts, tx.Outputs)
- if err != nil {
- return err
- }
-
- signBytes := acm.SignBytes(_s.ChainID, tx)
- inTotal, err := validateInputs(accounts, signBytes, tx.Inputs)
- if err != nil {
- return err
- }
- outTotal, err := validateOutputs(tx.Outputs)
- if err != nil {
- return err
- }
- if outTotal > inTotal {
- return types.ErrTxInsufficientFunds
- }
- fee := inTotal - outTotal
- fees += fee
-
- // Good! Adjust accounts
- adjustByInputs(accounts, tx.Inputs)
- adjustByOutputs(accounts, tx.Outputs)
- for _, acc := range accounts {
- blockCache.UpdateAccount(acc)
- }
-
- // if the evc is nil, nothing will happen
- if evc != nil {
- for _, i := range tx.Inputs {
- evc.FireEvent(types.EventStringAccInput(i.Address), tx)
- }
-
- for _, o := range tx.Outputs {
- evc.FireEvent(types.EventStringAccOutput(o.Address), tx)
- }
- }
- return nil
-
- case *types.CallTx:
- var inAcc, outAcc *acm.Account
-
- // Validate input
- inAcc = blockCache.GetAccount(tx.Input.Address)
- if inAcc == nil {
- log.Info(Fmt("Can't find in account %X", tx.Input.Address))
- return types.ErrTxInvalidAddress
- }
-
- createAccount := len(tx.Address) == 0
- if createAccount {
- if !hasCreateContractPermission(blockCache, inAcc) {
- return fmt.Errorf("Account %X does not have Create permission", tx.Input.Address)
- }
- } else {
- if !hasCallPermission(blockCache, inAcc) {
- return fmt.Errorf("Account %X does not have Call permission", tx.Input.Address)
- }
- }
-
- // pubKey should be present in either "inAcc" or "tx.Input"
- if err := checkInputPubKey(inAcc, tx.Input); err != nil {
- log.Info(Fmt("Can't find pubkey for %X", tx.Input.Address))
- return err
- }
- signBytes := acm.SignBytes(_s.ChainID, tx)
- err := validateInput(inAcc, signBytes, tx.Input)
- if err != nil {
- log.Info(Fmt("validateInput failed on %X: %v", tx.Input.Address, err))
- return err
- }
- if tx.Input.Amount < tx.Fee {
- log.Info(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
- return types.ErrTxInsufficientFunds
- }
-
- if !createAccount {
- // Validate output
- if len(tx.Address) != 20 {
- log.Info(Fmt("Destination address is not 20 bytes %X", tx.Address))
- return types.ErrTxInvalidAddress
- }
- // this may be nil if we are still in mempool and contract was created in same block as this tx
- // but that's fine, because the account will be created properly when the create tx runs in the block
- // and then this won't return nil. otherwise, we take their fee
- outAcc = blockCache.GetAccount(tx.Address)
- }
-
- log.Info(Fmt("Out account: %v", outAcc))
-
- // Good!
- value := tx.Input.Amount - tx.Fee
- inAcc.Sequence += 1
-
- if runCall {
-
- var (
- gas int64 = tx.GasLimit
- err error = nil
- caller *vm.Account = toVMAccount(inAcc)
- callee *vm.Account = nil
- code []byte = nil
- txCache = NewTxCache(blockCache)
- params = vm.Params{
- BlockHeight: int64(_s.LastBlockHeight),
- BlockHash: LeftPadWord256(_s.LastBlockHash),
- BlockTime: _s.LastBlockTime.Unix(),
- GasLimit: 10000000,
- }
- )
-
- // get or create callee
- if !createAccount {
-
- if outAcc == nil || len(outAcc.Code) == 0 {
- // check if its a native contract
- if vm.RegisteredNativeContract(LeftPadWord256(tx.Address)) {
- return fmt.Errorf("NativeContracts can not be called using CallTx. Use a contract or the appropriate tx type (eg. PermissionsTx, NameTx)")
- }
-
- // if you call an account that doesn't exist
- // or an account with no code then we take fees (sorry pal)
- // NOTE: it's fine to create a contract and call it within one
- // block (nonce will prevent re-ordering of those txs)
- // but to create with one account and call with another
- // you have to wait a block to avoid a re-ordering attack
- // that will take your fees
- inAcc.Balance -= tx.Fee
- blockCache.UpdateAccount(inAcc)
- if outAcc == nil {
- log.Info(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address))
- } else {
- log.Info(Fmt("Attempting to call an account (%X) with no code. Deducting fee from caller", tx.Address))
- }
- return types.ErrTxInvalidAddress
- }
- callee = toVMAccount(outAcc)
- code = callee.Code
- log.Info(Fmt("Calling contract %X with code %X", callee.Address, callee.Code))
- } else {
- callee = txCache.CreateAccount(caller)
- log.Info(Fmt("Created new account %X", callee.Address))
- code = tx.Data
- }
- log.Info(Fmt("Code for this contract: %X", code))
-
- txCache.UpdateAccount(caller) // because we bumped nonce
- txCache.UpdateAccount(callee) // so the txCache knows about the callee and the create and/or transfer takes effect
-
- vmach := vm.NewVM(txCache, params, caller.Address, types.TxID(_s.ChainID, tx))
- vmach.SetFireable(evc)
-
- // NOTE: Call() transfers the value from caller to callee iff call succeeds.
- ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas)
- exception := ""
- if err != nil {
- exception = err.Error()
- // Failure. Charge the gas fee. The 'value' was otherwise not transferred.
- log.Info(Fmt("Error on execution: %v", err))
- inAcc.Balance -= tx.Fee
- blockCache.UpdateAccount(inAcc)
- // Throw away 'txCache' which holds incomplete updates (don't sync it).
- } else {
- log.Info("Successful execution")
- // Success
- if createAccount {
- callee.Code = ret
- }
-
- txCache.Sync()
- }
- // Create a receipt from the ret and whether errored.
- log.Notice("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err)
-
- // Fire Events for sender and receiver
- // a separate event will be fired from vm for each additional call
- if evc != nil {
- evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventMsgCallTx{tx, ret, exception})
- evc.FireEvent(types.EventStringAccOutput(tx.Address), types.EventMsgCallTx{tx, ret, exception})
- }
- } else {
- // The mempool does not call txs until
- // the proposer determines the order of txs.
- // So mempool will skip the actual .Call(),
- // and only deduct from the caller's balance.
- inAcc.Balance -= value
- if createAccount {
- inAcc.Sequence += 1
- }
- blockCache.UpdateAccount(inAcc)
- }
-
- return nil
-
- case *types.NameTx:
- var inAcc *acm.Account
-
- // Validate input
- inAcc = blockCache.GetAccount(tx.Input.Address)
- if inAcc == nil {
- log.Info(Fmt("Can't find in account %X", tx.Input.Address))
- return types.ErrTxInvalidAddress
- }
- // check permission
- if !hasNamePermission(blockCache, inAcc) {
- return fmt.Errorf("Account %X does not have Name permission", tx.Input.Address)
- }
- // pubKey should be present in either "inAcc" or "tx.Input"
- if err := checkInputPubKey(inAcc, tx.Input); err != nil {
- log.Info(Fmt("Can't find pubkey for %X", tx.Input.Address))
- return err
- }
- signBytes := acm.SignBytes(_s.ChainID, tx)
- err := validateInput(inAcc, signBytes, tx.Input)
- if err != nil {
- log.Info(Fmt("validateInput failed on %X: %v", tx.Input.Address, err))
- return err
- }
- // fee is in addition to the amount which is used to determine the TTL
- if tx.Input.Amount < tx.Fee {
- log.Info(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
- return types.ErrTxInsufficientFunds
- }
-
- // validate the input strings
- if err := tx.ValidateStrings(); err != nil {
- log.Info(err.Error())
- return types.ErrTxInvalidString
- }
-
- value := tx.Input.Amount - tx.Fee
-
- // let's say cost of a name for one block is len(data) + 32
- costPerBlock := types.NameCostPerBlock * types.NameCostPerByte * tx.BaseEntryCost()
- expiresIn := int(value / costPerBlock)
- lastBlockHeight := _s.LastBlockHeight
-
- log.Info("New NameTx", "value", value, "costPerBlock", costPerBlock, "expiresIn", expiresIn, "lastBlock", lastBlockHeight)
-
- // check if the name exists
- entry := blockCache.GetNameRegEntry(tx.Name)
-
- if entry != nil {
- var expired bool
- // if the entry already exists, and hasn't expired, we must be owner
- if entry.Expires > lastBlockHeight {
- // ensure we are owner
- if bytes.Compare(entry.Owner, tx.Input.Address) != 0 {
- log.Info(Fmt("Sender %X is trying to update a name (%s) for which he is not owner", tx.Input.Address, tx.Name))
- return types.ErrIncorrectOwner
- }
- } else {
- expired = true
- }
-
- // no value and empty data means delete the entry
- if value == 0 && len(tx.Data) == 0 {
- // maybe we reward you for telling us we can delete this crap
- // (owners if not expired, anyone if expired)
- log.Info("Removing namereg entry", "name", entry.Name)
- blockCache.RemoveNameRegEntry(entry.Name)
- } else {
- // update the entry by bumping the expiry
- // and changing the data
- if expired {
- if expiresIn < types.MinNameRegistrationPeriod {
- return errors.New(Fmt("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod))
- }
- entry.Expires = lastBlockHeight + expiresIn
- entry.Owner = tx.Input.Address
- log.Info("An old namereg entry has expired and been reclaimed", "name", entry.Name, "expiresIn", expiresIn, "owner", entry.Owner)
- } else {
- // since the size of the data may have changed
- // we use the total amount of "credit"
- oldCredit := int64(entry.Expires-lastBlockHeight) * types.BaseEntryCost(entry.Name, entry.Data)
- credit := oldCredit + value
- expiresIn = int(credit / costPerBlock)
- if expiresIn < types.MinNameRegistrationPeriod {
- return errors.New(Fmt("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod))
- }
- entry.Expires = lastBlockHeight + expiresIn
- log.Info("Updated namereg entry", "name", entry.Name, "expiresIn", expiresIn, "oldCredit", oldCredit, "value", value, "credit", credit)
- }
- entry.Data = tx.Data
- blockCache.UpdateNameRegEntry(entry)
- }
- } else {
- if expiresIn < types.MinNameRegistrationPeriod {
- return errors.New(Fmt("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod))
- }
- // entry does not exist, so create it
- entry = &types.NameRegEntry{
- Name: tx.Name,
- Owner: tx.Input.Address,
- Data: tx.Data,
- Expires: lastBlockHeight + expiresIn,
- }
- log.Info("Creating namereg entry", "name", entry.Name, "expiresIn", expiresIn)
- blockCache.UpdateNameRegEntry(entry)
- }
-
- // TODO: something with the value sent?
-
- // Good!
- inAcc.Sequence += 1
- inAcc.Balance -= value
- blockCache.UpdateAccount(inAcc)
-
- // TODO: maybe we want to take funds on error and allow txs in that don't do anythingi?
-
- return nil
-
- case *types.BondTx:
- valInfo := blockCache.State().GetValidatorInfo(tx.PubKey.Address())
- if valInfo != nil {
- // TODO: In the future, check that the validator wasn't destroyed,
- // add funds, merge UnbondTo outputs, and unbond validator.
- return errors.New("Adding coins to existing validators not yet supported")
- }
-
- accounts, err := getInputs(blockCache, tx.Inputs)
- if err != nil {
- return err
- }
-
- // add outputs to accounts map
- // if any outputs don't exist, all inputs must have CreateAccount perm
- // though outputs aren't created until unbonding/release time
- canCreate := hasCreateAccountPermission(blockCache, accounts)
- for _, out := range tx.UnbondTo {
- acc := blockCache.GetAccount(out.Address)
- if acc == nil && !canCreate {
- return fmt.Errorf("At least one input does not have permission to create accounts")
- }
- }
-
- bondAcc := blockCache.GetAccount(tx.PubKey.Address())
- if !hasBondPermission(blockCache, bondAcc) {
- return fmt.Errorf("The bonder does not have permission to bond")
- }
-
- if !hasBondOrSendPermission(blockCache, accounts) {
- return fmt.Errorf("At least one input lacks permission to bond")
- }
-
- signBytes := acm.SignBytes(_s.ChainID, tx)
- inTotal, err := validateInputs(accounts, signBytes, tx.Inputs)
- if err != nil {
- return err
- }
- if !tx.PubKey.VerifyBytes(signBytes, tx.Signature) {
- return types.ErrTxInvalidSignature
- }
- outTotal, err := validateOutputs(tx.UnbondTo)
- if err != nil {
- return err
- }
- if outTotal > inTotal {
- return types.ErrTxInsufficientFunds
- }
- fee := inTotal - outTotal
- fees += fee
-
- // Good! Adjust accounts
- adjustByInputs(accounts, tx.Inputs)
- for _, acc := range accounts {
- blockCache.UpdateAccount(acc)
- }
- // Add ValidatorInfo
- _s.SetValidatorInfo(&ValidatorInfo{
- Address: tx.PubKey.Address(),
- PubKey: tx.PubKey,
- UnbondTo: tx.UnbondTo,
- FirstBondHeight: _s.LastBlockHeight + 1,
- FirstBondAmount: outTotal,
- })
- // Add Validator
- added := _s.BondedValidators.Add(&Validator{
- Address: tx.PubKey.Address(),
- PubKey: tx.PubKey,
- BondHeight: _s.LastBlockHeight + 1,
- VotingPower: outTotal,
- Accum: 0,
- })
- if !added {
- PanicCrisis("Failed to add validator")
- }
- if evc != nil {
- // TODO: fire for all inputs
- evc.FireEvent(types.EventStringBond(), tx)
- }
- return nil
-
- case *types.UnbondTx:
- // The validator must be active
- _, val := _s.BondedValidators.GetByAddress(tx.Address)
- if val == nil {
- return types.ErrTxInvalidAddress
- }
-
- // Verify the signature
- signBytes := acm.SignBytes(_s.ChainID, tx)
- if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
- return types.ErrTxInvalidSignature
- }
-
- // tx.Height must be greater than val.LastCommitHeight
- if tx.Height <= val.LastCommitHeight {
- return errors.New("Invalid unbond height")
- }
-
- // Good!
- _s.unbondValidator(val)
- if evc != nil {
- evc.FireEvent(types.EventStringUnbond(), tx)
- }
- return nil
-
- case *types.RebondTx:
- // The validator must be inactive
- _, val := _s.UnbondingValidators.GetByAddress(tx.Address)
- if val == nil {
- return types.ErrTxInvalidAddress
- }
-
- // Verify the signature
- signBytes := acm.SignBytes(_s.ChainID, tx)
- if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
- return types.ErrTxInvalidSignature
- }
-
- // tx.Height must be in a suitable range
- minRebondHeight := _s.LastBlockHeight - (validatorTimeoutBlocks / 2)
- maxRebondHeight := _s.LastBlockHeight + 2
- if !((minRebondHeight <= tx.Height) && (tx.Height <= maxRebondHeight)) {
- return errors.New(Fmt("Rebond height not in range. Expected %v <= %v <= %v",
- minRebondHeight, tx.Height, maxRebondHeight))
- }
-
- // Good!
- _s.rebondValidator(val)
- if evc != nil {
- evc.FireEvent(types.EventStringRebond(), tx)
- }
- return nil
-
- case *types.DupeoutTx:
- // Verify the signatures
- _, accused := _s.BondedValidators.GetByAddress(tx.Address)
- if accused == nil {
- _, accused = _s.UnbondingValidators.GetByAddress(tx.Address)
- if accused == nil {
- return types.ErrTxInvalidAddress
- }
- }
- voteASignBytes := acm.SignBytes(_s.ChainID, &tx.VoteA)
- voteBSignBytes := acm.SignBytes(_s.ChainID, &tx.VoteB)
- if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) ||
- !accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) {
- return types.ErrTxInvalidSignature
- }
-
- // Verify equivocation
- // 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 tx.VoteA.Round != tx.VoteB.Round {
- return errors.New("DupeoutTx rounds don't match")
- }
- if tx.VoteA.Type != tx.VoteB.Type {
- return errors.New("DupeoutTx types don't match")
- }
- if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) {
- return errors.New("DupeoutTx blockhashes shouldn't match")
- }
-
- // Good! (Bad validator!)
- _s.destroyValidator(accused)
- if evc != nil {
- evc.FireEvent(types.EventStringDupeout(), tx)
- }
- return nil
-
- case *types.PermissionsTx:
- var inAcc *acm.Account
-
- // Validate input
- inAcc = blockCache.GetAccount(tx.Input.Address)
- if inAcc == nil {
- log.Debug(Fmt("Can't find in account %X", tx.Input.Address))
- return types.ErrTxInvalidAddress
- }
-
- permFlag := tx.PermArgs.PermFlag()
- // check permission
- if !HasPermission(blockCache, inAcc, permFlag) {
- return fmt.Errorf("Account %X does not have moderator permission %s (%b)", tx.Input.Address, ptypes.PermFlagToString(permFlag), permFlag)
- }
-
- // pubKey should be present in either "inAcc" or "tx.Input"
- if err := checkInputPubKey(inAcc, tx.Input); err != nil {
- log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address))
- return err
- }
- signBytes := acm.SignBytes(_s.ChainID, tx)
- err := validateInput(inAcc, signBytes, tx.Input)
- if err != nil {
- log.Debug(Fmt("validateInput failed on %X: %v", tx.Input.Address, err))
- return err
- }
-
- value := tx.Input.Amount
-
- log.Debug("New PermissionsTx", "function", ptypes.PermFlagToString(permFlag), "args", tx.PermArgs)
-
- var permAcc *acm.Account
- switch args := tx.PermArgs.(type) {
- case *ptypes.HasBaseArgs:
- // this one doesn't make sense from txs
- return fmt.Errorf("HasBase is for contracts, not humans. Just look at the blockchain")
- case *ptypes.SetBaseArgs:
- if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
- return fmt.Errorf("Trying to update permissions for unknown account %X", args.Address)
- }
- err = permAcc.Permissions.Base.Set(args.Permission, args.Value)
- case *ptypes.UnsetBaseArgs:
- if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
- return fmt.Errorf("Trying to update permissions for unknown account %X", args.Address)
- }
- err = permAcc.Permissions.Base.Unset(args.Permission)
- case *ptypes.SetGlobalArgs:
- if permAcc = blockCache.GetAccount(ptypes.GlobalPermissionsAddress); permAcc == nil {
- PanicSanity("can't find global permissions account")
- }
- err = permAcc.Permissions.Base.Set(args.Permission, args.Value)
- case *ptypes.HasRoleArgs:
- return fmt.Errorf("HasRole is for contracts, not humans. Just look at the blockchain")
- case *ptypes.AddRoleArgs:
- if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
- return fmt.Errorf("Trying to update roles for unknown account %X", args.Address)
- }
- if !permAcc.Permissions.AddRole(args.Role) {
- return fmt.Errorf("Role (%s) already exists for account %X", args.Role, args.Address)
- }
- case *ptypes.RmRoleArgs:
- if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
- return fmt.Errorf("Trying to update roles for unknown account %X", args.Address)
- }
- if !permAcc.Permissions.RmRole(args.Role) {
- return fmt.Errorf("Role (%s) does not exist for account %X", args.Role, args.Address)
- }
- default:
- PanicSanity(Fmt("invalid permission function: %s", ptypes.PermFlagToString(permFlag)))
- }
-
- // TODO: maybe we want to take funds on error and allow txs in that don't do anythingi?
- if err != nil {
- return err
- }
-
- // Good!
- inAcc.Sequence += 1
- inAcc.Balance -= value
- blockCache.UpdateAccount(inAcc)
- if permAcc != nil {
- blockCache.UpdateAccount(permAcc)
- }
-
- if evc != nil {
- evc.FireEvent(types.EventStringAccInput(tx.Input.Address), tx)
- evc.FireEvent(types.EventStringPermissions(ptypes.PermFlagToString(permFlag)), tx)
- }
-
- return nil
-
- default:
- // binary decoding should not let this happen
- PanicSanity("Unknown Tx type")
- return nil
- }
- }
-
- //---------------------------------------------------------------
-
- // Get permission on an account or fall back to global value
- func HasPermission(state AccountGetter, acc *acm.Account, perm ptypes.PermFlag) bool {
- if perm > ptypes.AllPermFlags {
- PanicSanity("Checking an unknown permission in state should never happen")
- }
-
- if acc == nil {
- // TODO
- // this needs to fall back to global or do some other specific things
- // eg. a bondAcc may be nil and so can only bond if global bonding is true
- }
- permString := ptypes.PermFlagToString(perm)
-
- v, err := acc.Permissions.Base.Get(perm)
- if _, ok := err.(ptypes.ErrValueNotSet); ok {
- if state == nil {
- PanicSanity("All known global permissions should be set!")
- }
- log.Info("Permission for account is not set. Querying GlobalPermissionsAddress", "perm", permString)
- return HasPermission(nil, state.GetAccount(ptypes.GlobalPermissionsAddress), perm)
- } else if v {
- log.Info("Account has permission", "address", Fmt("%X", acc.Address), "perm", permString)
- } else {
- log.Info("Account does not have permission", "address", Fmt("%X", acc.Address), "perm", permString)
- }
- return v
- }
-
- // TODO: for debug log the failed accounts
- func hasSendPermission(state AccountGetter, accs map[string]*acm.Account) bool {
- for _, acc := range accs {
- if !HasPermission(state, acc, ptypes.Send) {
- return false
- }
- }
- return true
- }
-
- func hasNamePermission(state AccountGetter, acc *acm.Account) bool {
- return HasPermission(state, acc, ptypes.Name)
- }
-
- func hasCallPermission(state AccountGetter, acc *acm.Account) bool {
- return HasPermission(state, acc, ptypes.Call)
- }
-
- func hasCreateContractPermission(state AccountGetter, acc *acm.Account) bool {
- return HasPermission(state, acc, ptypes.CreateContract)
- }
-
- func hasCreateAccountPermission(state AccountGetter, accs map[string]*acm.Account) bool {
- for _, acc := range accs {
- if !HasPermission(state, acc, ptypes.CreateAccount) {
- return false
- }
- }
- return true
- }
-
- func hasBondPermission(state AccountGetter, acc *acm.Account) bool {
- return HasPermission(state, acc, ptypes.Bond)
- }
-
- func hasBondOrSendPermission(state AccountGetter, accs map[string]*acm.Account) bool {
- for _, acc := range accs {
- if !HasPermission(state, acc, ptypes.Bond) {
- if !HasPermission(state, acc, ptypes.Send) {
- return false
- }
- }
- }
- return true
- }
|