Hook into tmsp app
This commit is contained in:
parent
0ccad3b5e1
commit
fd648a6782
37
app/app.go
37
app/app.go
|
@ -1,10 +1,11 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/tendermint/basecoin/state"
|
||||||
"github.com/tendermint/basecoin/types"
|
"github.com/tendermint/basecoin/types"
|
||||||
. "github.com/tendermint/go-common"
|
. "github.com/tendermint/go-common"
|
||||||
"github.com/tendermint/go-wire"
|
"github.com/tendermint/go-wire"
|
||||||
gov "github.com/tendermint/governmint/gov"
|
"github.com/tendermint/governmint/gov"
|
||||||
eyes "github.com/tendermint/merkleeyes/client"
|
eyes "github.com/tendermint/merkleeyes/client"
|
||||||
tmsp "github.com/tendermint/tmsp/types"
|
tmsp "github.com/tendermint/tmsp/types"
|
||||||
)
|
)
|
||||||
|
@ -15,23 +16,29 @@ const maxTxSize = 10240
|
||||||
type Basecoin struct {
|
type Basecoin struct {
|
||||||
eyesCli *eyes.Client
|
eyesCli *eyes.Client
|
||||||
govMint *gov.Governmint
|
govMint *gov.Governmint
|
||||||
|
state *state.State
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBasecoin(eyesCli *eyes.Client) *Basecoin {
|
func NewBasecoin(eyesCli *eyes.Client) *Basecoin {
|
||||||
|
govMint := gov.NewGovernmint(eyesCli)
|
||||||
return &Basecoin{
|
return &Basecoin{
|
||||||
eyesCli: eyesCli,
|
eyesCli: eyesCli,
|
||||||
govMint: gov.NewGovernmint(eyesCli),
|
govMint: govMint,
|
||||||
|
state: state.NewState(eyesCli),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TMSP::Info
|
// TMSP::Info
|
||||||
func (app *Basecoin) Info() string {
|
func (app *Basecoin) Info() string {
|
||||||
return Fmt("Basecoin v%v\n - %v", version, app.govMint.Info())
|
return Fmt("Basecoin v%v", version)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TMSP::SetOption
|
// TMSP::SetOption
|
||||||
func (app *Basecoin) SetOption(key string, value string) (log string) {
|
func (app *Basecoin) SetOption(key string, value string) (log string) {
|
||||||
if key == "setAccount" {
|
switch key {
|
||||||
|
case "chainID":
|
||||||
|
app.state.SetChainID(value)
|
||||||
|
case "account":
|
||||||
var err error
|
var err error
|
||||||
var setAccount types.Account
|
var setAccount types.Account
|
||||||
wire.ReadJSONPtr(&setAccount, []byte(value), &err)
|
wire.ReadJSONPtr(&setAccount, []byte(value), &err)
|
||||||
|
@ -59,16 +66,11 @@ func (app *Basecoin) AppendTx(txBytes []byte) (res tmsp.Result) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.ErrEncodingError.AppendLog("Error decoding tx: " + err.Error())
|
return types.ErrEncodingError.AppendLog("Error decoding tx: " + err.Error())
|
||||||
}
|
}
|
||||||
// Validate tx
|
// Validate and exec tx
|
||||||
res = validateTx(tx)
|
res = state.ExecTx(app.state, tx, false, nil)
|
||||||
if !res.IsOK() {
|
if !res.IsOK() {
|
||||||
return res.PrependLog("Error validating tx")
|
return res.PrependLog("Error in AppendTx")
|
||||||
}
|
}
|
||||||
// Execute tx
|
|
||||||
// TODO: get or make state with app.eeysCli, pass it to
|
|
||||||
// state.execution.go ExecTx
|
|
||||||
// Synchronize the txCache.
|
|
||||||
//storeAccounts(app.eyesCli, accs)
|
|
||||||
return types.ResultOK
|
return types.ResultOK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,16 +86,11 @@ func (app *Basecoin) CheckTx(txBytes []byte) (res tmsp.Result) {
|
||||||
return types.ErrEncodingError.AppendLog("Error decoding tx: " + err.Error())
|
return types.ErrEncodingError.AppendLog("Error decoding tx: " + err.Error())
|
||||||
}
|
}
|
||||||
// Validate tx
|
// Validate tx
|
||||||
res = validateTx(tx)
|
res = state.ExecTx(app.state, tx, true, nil)
|
||||||
if !res.IsOK() {
|
if !res.IsOK() {
|
||||||
return res.PrependLog("Error validating tx")
|
return res.PrependLog("Error in CheckTx")
|
||||||
}
|
}
|
||||||
// Execute tx
|
return types.ResultOK
|
||||||
// TODO: get or make state with app.eeysCli, pass it to
|
|
||||||
// state.execution.go ExecTx
|
|
||||||
// Synchronize the txCache.
|
|
||||||
//storeAccounts(app.eyesCli, accs)
|
|
||||||
return types.ResultOK.SetLog("Success")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TMSP::Query
|
// TMSP::Query
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/tendermint/basecoin/types"
|
|
||||||
tmsp "github.com/tendermint/tmsp/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func validateTx(tx types.Tx) (res tmsp.Result) {
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -2,17 +2,149 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/tendermint/basecoin/types"
|
"github.com/tendermint/basecoin/types"
|
||||||
. "github.com/tendermint/go-common"
|
. "github.com/tendermint/go-common"
|
||||||
"github.com/tendermint/go-events"
|
"github.com/tendermint/go-events"
|
||||||
|
tmsp "github.com/tendermint/tmsp/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// If the tx is invalid, a TMSP error will be returned.
|
||||||
|
func ExecTx(state *State, tx types.Tx, isCheckTx bool, evc events.Fireable) tmsp.Result {
|
||||||
|
|
||||||
|
// TODO: do something with fees
|
||||||
|
fees := int64(0)
|
||||||
|
|
||||||
|
// Exec tx
|
||||||
|
switch tx := tx.(type) {
|
||||||
|
case *types.SendTx:
|
||||||
|
// First, get inputs
|
||||||
|
accounts, res := getInputs(state, tx.Inputs)
|
||||||
|
if !res.IsOK() {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then, get or make outputs.
|
||||||
|
accounts, res = getOrMakeOutputs(state, accounts, tx.Outputs)
|
||||||
|
if !res.IsOK() {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate inputs and outputs
|
||||||
|
signBytes := tx.SignBytes(state.GetChainID())
|
||||||
|
inTotal, res := validateInputs(state, accounts, signBytes, tx.Inputs)
|
||||||
|
if !res.IsOK() {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
outTotal, res := validateOutputs(tx.Outputs)
|
||||||
|
if !res.IsOK() {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
if outTotal > inTotal {
|
||||||
|
return types.ErrInsufficientFunds
|
||||||
|
}
|
||||||
|
fee := inTotal - outTotal
|
||||||
|
fees += fee
|
||||||
|
|
||||||
|
// TODO: Fee validation for SendTx
|
||||||
|
|
||||||
|
// Good! Adjust accounts
|
||||||
|
adjustByInputs(state, accounts, tx.Inputs, isCheckTx)
|
||||||
|
adjustByOutputs(state, accounts, tx.Outputs, isCheckTx)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Fire events
|
||||||
|
if !isCheckTx {
|
||||||
|
if evc != nil {
|
||||||
|
for _, i := range tx.Inputs {
|
||||||
|
evc.FireEvent(types.EventStringAccInput(i.Address), types.EventDataTx{tx, nil, ""})
|
||||||
|
}
|
||||||
|
for _, o := range tx.Outputs {
|
||||||
|
evc.FireEvent(types.EventStringAccOutput(o.Address), types.EventDataTx{tx, nil, ""})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return types.ResultOK
|
||||||
|
|
||||||
|
case *types.CallTx:
|
||||||
|
// First, get input account
|
||||||
|
inAcc := state.GetAccount(tx.Input.Address)
|
||||||
|
if inAcc == nil {
|
||||||
|
log.Info(Fmt("Can't find in account %X", tx.Input.Address))
|
||||||
|
return types.ErrInvalidAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate input
|
||||||
|
// pubKey should be present in either "inAcc" or "tx.Input"
|
||||||
|
if res := checkInputPubKey(tx.Input.Address, inAcc, tx.Input); !res.IsOK() {
|
||||||
|
log.Info(Fmt("Can't find pubkey for %X", tx.Input.Address))
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
signBytes := tx.SignBytes(state.GetChainID())
|
||||||
|
res := validateInput(state, inAcc, signBytes, tx.Input)
|
||||||
|
if !res.IsOK() {
|
||||||
|
log.Info(Fmt("validateInput failed on %X: %v", tx.Input.Address, res))
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
if tx.Input.Amount < tx.Fee {
|
||||||
|
log.Info(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
|
||||||
|
return types.ErrInsufficientFunds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate call address
|
||||||
|
if strings.HasPrefix(string(tx.Address), "gov/") {
|
||||||
|
// This is a gov call.
|
||||||
|
} else {
|
||||||
|
return types.ErrInvalidAddress.AppendLog(Fmt("Unrecognized address %X", tx.Address))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Good!
|
||||||
|
value := tx.Input.Amount - tx.Fee
|
||||||
|
inAcc.Sequence += 1
|
||||||
|
inAcc.Balance -= tx.Input.Amount
|
||||||
|
state.SetCheckAccount(tx.Input.Address, inAcc.Sequence, inAcc.Balance)
|
||||||
|
|
||||||
|
// If this is AppendTx, actually save accounts
|
||||||
|
if !isCheckTx {
|
||||||
|
state.SetAccount(inAcc)
|
||||||
|
// NOTE: value is dangling.
|
||||||
|
// XXX: don't just give it back
|
||||||
|
inAcc.Balance += value
|
||||||
|
// TODO: logic.
|
||||||
|
// TODO: persist
|
||||||
|
// state.SetAccount(inAcc)
|
||||||
|
log.Info("Successful execution")
|
||||||
|
// Fire events
|
||||||
|
/*
|
||||||
|
if evc != nil {
|
||||||
|
exception := ""
|
||||||
|
if !res.IsOK() {
|
||||||
|
exception = res.Error()
|
||||||
|
}
|
||||||
|
evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventDataTx{tx, ret, exception})
|
||||||
|
evc.FireEvent(types.EventStringAccOutput(tx.Address), types.EventDataTx{tx, ret, exception})
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.ResultOK
|
||||||
|
|
||||||
|
default:
|
||||||
|
PanicSanity("Unknown Tx type")
|
||||||
|
return types.ErrInternalError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
|
||||||
// The accounts from the TxInputs must either already have
|
// The accounts from the TxInputs must either already have
|
||||||
// crypto.PubKey.(type) != nil, (it must be known),
|
// crypto.PubKey.(type) != nil, (it must be known),
|
||||||
// or it must be specified in the TxInput. If redeclared,
|
// or it must be specified in the TxInput. If redeclared,
|
||||||
// the TxInput is modified and input.PubKey set to nil.
|
// the TxInput is modified and input.PubKey set to nil.
|
||||||
func getInputs(state types.AccountGetter, ins []types.TxInput) (map[string]*types.Account, error) {
|
func getInputs(state types.AccountGetter, ins []types.TxInput) (map[string]*types.Account, tmsp.Result) {
|
||||||
accounts := map[string]*types.Account{}
|
accounts := map[string]*types.Account{}
|
||||||
for _, in := range ins {
|
for _, in := range ins {
|
||||||
// Account shouldn't be duplicated
|
// Account shouldn't be duplicated
|
||||||
|
@ -24,15 +156,15 @@ func getInputs(state types.AccountGetter, ins []types.TxInput) (map[string]*type
|
||||||
return nil, types.ErrInvalidAddress
|
return nil, types.ErrInvalidAddress
|
||||||
}
|
}
|
||||||
// PubKey should be present in either "account" or "in"
|
// PubKey should be present in either "account" or "in"
|
||||||
if err := checkInputPubKey(in.Address, acc, in); err != nil {
|
if res := checkInputPubKey(in.Address, acc, in); !res.IsOK() {
|
||||||
return nil, err
|
return nil, res
|
||||||
}
|
}
|
||||||
accounts[string(in.Address)] = acc
|
accounts[string(in.Address)] = acc
|
||||||
}
|
}
|
||||||
return accounts, nil
|
return accounts, types.ResultOK
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOrMakeOutputs(state types.AccountGetter, accounts map[string]*types.Account, outs []types.TxOutput) (map[string]*types.Account, error) {
|
func getOrMakeOutputs(state types.AccountGetter, accounts map[string]*types.Account, outs []types.TxOutput) (map[string]*types.Account, tmsp.Result) {
|
||||||
if accounts == nil {
|
if accounts == nil {
|
||||||
accounts = make(map[string]*types.Account)
|
accounts = make(map[string]*types.Account)
|
||||||
}
|
}
|
||||||
|
@ -53,12 +185,12 @@ func getOrMakeOutputs(state types.AccountGetter, accounts map[string]*types.Acco
|
||||||
}
|
}
|
||||||
accounts[string(out.Address)] = acc
|
accounts[string(out.Address)] = acc
|
||||||
}
|
}
|
||||||
return accounts, nil
|
return accounts, types.ResultOK
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input must not have a redundant PubKey (i.e. Account already has PubKey).
|
// Input must not have a redundant PubKey (i.e. Account already has PubKey).
|
||||||
// NOTE: Account has PubKey if Sequence > 0
|
// NOTE: Account has PubKey if Sequence > 0
|
||||||
func checkInputPubKey(address []byte, acc *types.Account, in types.TxInput) error {
|
func checkInputPubKey(address []byte, acc *types.Account, in types.TxInput) tmsp.Result {
|
||||||
if acc.PubKey == nil {
|
if acc.PubKey == nil {
|
||||||
if in.PubKey == nil {
|
if in.PubKey == nil {
|
||||||
return types.ErrUnknownPubKey
|
return types.ErrUnknownPubKey
|
||||||
|
@ -72,58 +204,61 @@ func checkInputPubKey(address []byte, acc *types.Account, in types.TxInput) erro
|
||||||
return types.ErrInvalidPubKey
|
return types.ErrInvalidPubKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return types.ResultOK
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateInputs(accounts map[string]*types.Account, signBytes []byte, ins []types.TxInput) (total int64, err error) {
|
// Validate inputs and compute total amount
|
||||||
|
func validateInputs(state *State, accounts map[string]*types.Account, signBytes []byte, ins []types.TxInput) (total int64, res tmsp.Result) {
|
||||||
|
|
||||||
for _, in := range ins {
|
for _, in := range ins {
|
||||||
acc := accounts[string(in.Address)]
|
acc := accounts[string(in.Address)]
|
||||||
if acc == nil {
|
if acc == nil {
|
||||||
PanicSanity("validateInputs() expects account in accounts")
|
PanicSanity("validateInputs() expects account in accounts")
|
||||||
}
|
}
|
||||||
err = validateInput(acc, signBytes, in)
|
res = validateInput(state, acc, signBytes, in)
|
||||||
if err != nil {
|
if !res.IsOK() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Good. Add amount to total
|
// Good. Add amount to total
|
||||||
total += in.Amount
|
total += in.Amount
|
||||||
}
|
}
|
||||||
return total, nil
|
return total, types.ResultOK
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateInput(acc *types.Account, signBytes []byte, in types.TxInput) (err error) {
|
func validateInput(state *State, acc *types.Account, signBytes []byte, in types.TxInput) (res tmsp.Result) {
|
||||||
// Check TxInput basic
|
// Check TxInput basic
|
||||||
if err := in.ValidateBasic(); err != nil {
|
if res := in.ValidateBasic(); !res.IsOK() {
|
||||||
return err
|
return res
|
||||||
|
}
|
||||||
|
// Check sequence/balance
|
||||||
|
seq, balance := state.GetCheckAccount(in.Address, acc.Sequence, acc.Balance)
|
||||||
|
if seq+1 != in.Sequence {
|
||||||
|
return types.ErrInvalidSequence.AppendLog(Fmt("Got %v, expected %v. (acc.seq=%v)", in.Sequence, seq+1, acc.Sequence))
|
||||||
|
}
|
||||||
|
// Check amount
|
||||||
|
if balance < in.Amount {
|
||||||
|
return types.ErrInsufficientFunds
|
||||||
}
|
}
|
||||||
// Check signatures
|
// Check signatures
|
||||||
if !acc.PubKey.VerifyBytes(signBytes, in.Signature) {
|
if !acc.PubKey.VerifyBytes(signBytes, in.Signature) {
|
||||||
return types.ErrInvalidSignature
|
return types.ErrInvalidSignature
|
||||||
}
|
}
|
||||||
// Check sequences
|
return types.ResultOK
|
||||||
if acc.Sequence+1 != in.Sequence {
|
|
||||||
return types.ErrInvalidSequence.AppendLog(Fmt("Got %v, expected %v", in.Sequence, acc.Sequence+1))
|
|
||||||
}
|
|
||||||
// Check amount
|
|
||||||
if acc.Balance < in.Amount {
|
|
||||||
return types.ErrInsufficientFunds
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOutputs(outs []types.TxOutput) (total int64, err error) {
|
func validateOutputs(outs []types.TxOutput) (total int64, res tmsp.Result) {
|
||||||
for _, out := range outs {
|
for _, out := range outs {
|
||||||
// Check TxOutput basic
|
// Check TxOutput basic
|
||||||
if err := out.ValidateBasic(); err != nil {
|
if res := out.ValidateBasic(); !res.IsOK() {
|
||||||
return 0, err
|
return 0, res
|
||||||
}
|
}
|
||||||
// Good. Add amount to total
|
// Good. Add amount to total
|
||||||
total += out.Amount
|
total += out.Amount
|
||||||
}
|
}
|
||||||
return total, nil
|
return total, types.ResultOK
|
||||||
}
|
}
|
||||||
|
|
||||||
func adjustByInputs(accounts map[string]*types.Account, ins []types.TxInput) {
|
func adjustByInputs(state *State, accounts map[string]*types.Account, ins []types.TxInput, isCheckTx bool) {
|
||||||
for _, in := range ins {
|
for _, in := range ins {
|
||||||
acc := accounts[string(in.Address)]
|
acc := accounts[string(in.Address)]
|
||||||
if acc == nil {
|
if acc == nil {
|
||||||
|
@ -134,229 +269,25 @@ func adjustByInputs(accounts map[string]*types.Account, ins []types.TxInput) {
|
||||||
}
|
}
|
||||||
acc.Balance -= in.Amount
|
acc.Balance -= in.Amount
|
||||||
acc.Sequence += 1
|
acc.Sequence += 1
|
||||||
|
state.SetCheckAccount(in.Address, acc.Sequence, acc.Balance)
|
||||||
|
if !isCheckTx {
|
||||||
|
// NOTE: Must be set in deterministic order
|
||||||
|
state.SetAccount(acc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func adjustByOutputs(accounts map[string]*types.Account, outs []types.TxOutput) {
|
func adjustByOutputs(state *State, accounts map[string]*types.Account, outs []types.TxOutput, isCheckTx bool) {
|
||||||
for _, out := range outs {
|
for _, out := range outs {
|
||||||
acc := accounts[string(out.Address)]
|
acc := accounts[string(out.Address)]
|
||||||
if acc == nil {
|
if acc == nil {
|
||||||
PanicSanity("adjustByOutputs() expects account in accounts")
|
PanicSanity("adjustByOutputs() expects account in accounts")
|
||||||
}
|
}
|
||||||
acc.Balance += out.Amount
|
acc.Balance += out.Amount
|
||||||
}
|
if !isCheckTx {
|
||||||
}
|
state.SetCheckAccount(out.Address, acc.Sequence, acc.Balance)
|
||||||
|
// NOTE: Must be set in deterministic order
|
||||||
// If the tx is invalid, an error will be returned.
|
|
||||||
// Unlike ExecBlock(), state will not be altered.
|
|
||||||
func ExecTx(state *State, tx types.Tx, runCall bool, evc events.Fireable) (err error) {
|
|
||||||
|
|
||||||
// TODO: do something with fees
|
|
||||||
fees := int64(0)
|
|
||||||
|
|
||||||
// Exec tx
|
|
||||||
switch tx := tx.(type) {
|
|
||||||
case *types.SendTx:
|
|
||||||
accounts, err := getInputs(state, tx.Inputs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// add outputs to accounts map
|
|
||||||
// if any outputs don't exist, all inputs must have CreateAccount perm
|
|
||||||
accounts, err = getOrMakeOutputs(state, accounts, tx.Outputs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
signBytes := tx.SignBytes(state.ChainID())
|
|
||||||
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.ErrInsufficientFunds
|
|
||||||
}
|
|
||||||
fee := inTotal - outTotal
|
|
||||||
fees += fee
|
|
||||||
|
|
||||||
// Good! Adjust accounts
|
|
||||||
adjustByInputs(accounts, tx.Inputs)
|
|
||||||
adjustByOutputs(accounts, tx.Outputs)
|
|
||||||
for _, acc := range accounts {
|
|
||||||
state.SetAccount(acc)
|
state.SetAccount(acc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the evc is nil, nothing will happen
|
|
||||||
/*
|
|
||||||
if evc != nil {
|
|
||||||
for _, i := range tx.Inputs {
|
|
||||||
evc.FireEvent(types.EventStringAccInput(i.Address), types.EventDataTx{tx, nil, ""})
|
|
||||||
}
|
|
||||||
for _, o := range tx.Outputs {
|
|
||||||
evc.FireEvent(types.EventStringAccOutput(o.Address), types.EventDataTx{tx, nil, ""})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case *types.CallTx:
|
|
||||||
var inAcc, outAcc *types.Account
|
|
||||||
|
|
||||||
// Validate input
|
|
||||||
inAcc = state.GetAccount(tx.Input.Address)
|
|
||||||
if inAcc == nil {
|
|
||||||
log.Info(Fmt("Can't find in account %X", tx.Input.Address))
|
|
||||||
return types.ErrInvalidAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
// pubKey should be present in either "inAcc" or "tx.Input"
|
|
||||||
if err := checkInputPubKey(tx.Input.Address, inAcc, tx.Input); err != nil {
|
|
||||||
log.Info(Fmt("Can't find pubkey for %X", tx.Input.Address))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
signBytes := tx.SignBytes(state.ChainID())
|
|
||||||
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.ErrInsufficientFunds
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(tx.Address) == 0 {
|
|
||||||
return types.ErrInvalidAddress.AppendLog("Address cannot be zero")
|
|
||||||
}
|
|
||||||
// Validate output
|
|
||||||
if len(tx.Address) != 20 {
|
|
||||||
log.Info(Fmt("Destination address is not 20 bytes %X", tx.Address))
|
|
||||||
return types.ErrInvalidAddress
|
|
||||||
}
|
|
||||||
// check if its a native contract
|
|
||||||
// XXX if IsNativeContract(tx.Address) {...}
|
|
||||||
|
|
||||||
// Output account 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 = state.GetAccount(tx.Address)
|
|
||||||
|
|
||||||
log.Info(Fmt("Out account: %v", outAcc))
|
|
||||||
|
|
||||||
// Good!
|
|
||||||
value := tx.Input.Amount - tx.Fee
|
|
||||||
inAcc.Sequence += 1
|
|
||||||
inAcc.Balance -= tx.Fee
|
|
||||||
state.SetAccount(inAcc)
|
|
||||||
|
|
||||||
// The logic in runCall MUST NOT return.
|
|
||||||
if runCall {
|
|
||||||
|
|
||||||
// VM call variables
|
|
||||||
var (
|
|
||||||
// gas int64 = tx.GasLimit
|
|
||||||
err error = nil
|
|
||||||
// caller *vm.Account = toVMAccount(inAcc)
|
|
||||||
// callee *vm.Account = nil // initialized below
|
|
||||||
// code []byte = nil
|
|
||||||
// ret []byte = nil
|
|
||||||
// txCache = NewTxCache(state)
|
|
||||||
/*
|
|
||||||
params = vm.Params{
|
|
||||||
BlockHeight: int64(state.LastBlockHeight),
|
|
||||||
BlockHash: LeftPadWord256(state.LastBlockHash),
|
|
||||||
BlockTime: state.LastBlockTime.Unix(),
|
|
||||||
GasLimit: state.GetGasLimit(),
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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 contract and call with another
|
|
||||||
// you have to wait a block to avoid a re-ordering attack
|
|
||||||
// that will take your fees
|
|
||||||
if outAcc == nil {
|
|
||||||
log.Info(Fmt("%X tries to call %X but it does not exist.", tx.Input.Address, tx.Address))
|
|
||||||
err = types.ErrInvalidAddress
|
|
||||||
goto CALL_COMPLETE
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
if len(outAcc.Code) == 0 {
|
|
||||||
log.Info(Fmt("%X tries to call %X but code is blank.", inAcc.Address, tx.Address))
|
|
||||||
err = types.ErrInvalidAddress
|
|
||||||
goto CALL_COMPLETE
|
|
||||||
}
|
|
||||||
log.Info(Fmt("Code for this contract: %X", code))
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Run VM call and sync txCache to state.
|
|
||||||
{ // Capture scope for goto.
|
|
||||||
// Write caller/callee to txCache.
|
|
||||||
// txCache.SetAccount(caller)
|
|
||||||
// txCache.SetAccount(callee)
|
|
||||||
// vmach := vm.NewVM(txCache, params, caller.Address, types.TxID(state.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)
|
|
||||||
if err != nil {
|
|
||||||
// Failure. Charge the gas fee. The 'value' was otherwise not transferred.
|
|
||||||
log.Info(Fmt("Error on execution: %v", err))
|
|
||||||
goto CALL_COMPLETE
|
|
||||||
}
|
|
||||||
log.Info("Successful execution")
|
|
||||||
// txCache.Sync()
|
|
||||||
}
|
|
||||||
|
|
||||||
CALL_COMPLETE: // err may or may not be nil.
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
exception := ""
|
|
||||||
if err != nil {
|
|
||||||
exception = err.Error()
|
|
||||||
}
|
|
||||||
evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventDataTx{tx, ret, exception})
|
|
||||||
evc.FireEvent(types.EventStringAccOutput(tx.Address), types.EventDataTx{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
|
|
||||||
state.SetAccount(inAcc)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
// binary decoding should not let this happen
|
|
||||||
PanicSanity("Unknown Tx type")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type InvalidTxError struct {
|
|
||||||
Tx types.Tx
|
|
||||||
Reason error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (txErr InvalidTxError) Error() string {
|
|
||||||
return Fmt("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason)
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,31 +2,67 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/tendermint/basecoin/types"
|
"github.com/tendermint/basecoin/types"
|
||||||
|
. "github.com/tendermint/go-common"
|
||||||
"github.com/tendermint/go-wire"
|
"github.com/tendermint/go-wire"
|
||||||
eyes "github.com/tendermint/merkleeyes/client"
|
eyes "github.com/tendermint/merkleeyes/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type State struct {
|
type State struct {
|
||||||
chainID string
|
chainID string
|
||||||
eyesCli *eyes.Client
|
eyesCli *eyes.Client
|
||||||
|
checkCache map[string]checkAccount
|
||||||
|
|
||||||
LastBlockHeight uint64
|
LastBlockHeight uint64
|
||||||
LastBlockHash []byte
|
LastBlockHash []byte
|
||||||
GasLimit int64
|
GasLimit int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewState(chainID string, eyesCli *eyes.Client) *State {
|
func NewState(eyesCli *eyes.Client) *State {
|
||||||
s := &State{
|
s := &State{
|
||||||
chainID: chainID,
|
chainID: "",
|
||||||
eyesCli: eyesCli,
|
eyesCli: eyesCli,
|
||||||
|
checkCache: make(map[string]checkAccount),
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) ChainID() string {
|
func (s *State) SetChainID(chainID string) {
|
||||||
|
s.chainID = chainID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) GetChainID() string {
|
||||||
|
if s.chainID == "" {
|
||||||
|
PanicSanity("Expected to have set SetChainID")
|
||||||
|
}
|
||||||
return s.chainID
|
return s.chainID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//----------------------------------------
|
||||||
|
// CheckTx state
|
||||||
|
|
||||||
|
type checkAccount struct {
|
||||||
|
sequence int
|
||||||
|
balance int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) GetCheckAccount(addr []byte, defaultSequence int, defaultBalance int64) (sequence int, balance int64) {
|
||||||
|
cAcc, ok := s.checkCache[string(addr)]
|
||||||
|
if !ok {
|
||||||
|
return defaultSequence, defaultBalance
|
||||||
|
}
|
||||||
|
return cAcc.sequence, cAcc.balance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) SetCheckAccount(addr []byte, sequence int, balance int64) {
|
||||||
|
s.checkCache[string(addr)] = checkAccount{sequence, balance}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) ResetCacheState() {
|
||||||
|
s.checkCache = make(map[string]checkAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------
|
||||||
|
|
||||||
func (s *State) GetAccount(addr []byte) *types.Account {
|
func (s *State) GetAccount(addr []byte) *types.Account {
|
||||||
accBytes, err := s.eyesCli.GetSync(addr)
|
accBytes, err := s.eyesCli.GetSync(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
ErrInternalError = tmsp.NewError(tmsp.CodeType_InternalError, "Internal error")
|
||||||
ErrDuplicateAddress = tmsp.NewError(tmsp.CodeType_BaseDuplicateAddress, "Error duplicate address")
|
ErrDuplicateAddress = tmsp.NewError(tmsp.CodeType_BaseDuplicateAddress, "Error duplicate address")
|
||||||
ErrEncodingError = tmsp.NewError(tmsp.CodeType_BaseEncodingError, "Error encoding error")
|
ErrEncodingError = tmsp.NewError(tmsp.CodeType_BaseEncodingError, "Error encoding error")
|
||||||
ErrInsufficientFees = tmsp.NewError(tmsp.CodeType_BaseInsufficientFees, "Error insufficient fees")
|
ErrInsufficientFees = tmsp.NewError(tmsp.CodeType_BaseInsufficientFees, "Error insufficient fees")
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
. "github.com/tendermint/go-common"
|
. "github.com/tendermint/go-common"
|
||||||
"github.com/tendermint/go-crypto"
|
"github.com/tendermint/go-crypto"
|
||||||
"github.com/tendermint/go-wire"
|
"github.com/tendermint/go-wire"
|
||||||
|
tmsp "github.com/tendermint/tmsp/types"
|
||||||
"golang.org/x/crypto/ripemd160"
|
"golang.org/x/crypto/ripemd160"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,14 +46,14 @@ type TxInput struct {
|
||||||
PubKey crypto.PubKey `json:"pub_key"` // May be nil
|
PubKey crypto.PubKey `json:"pub_key"` // May be nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (txIn TxInput) ValidateBasic() error {
|
func (txIn TxInput) ValidateBasic() tmsp.Result {
|
||||||
if len(txIn.Address) != 20 {
|
if len(txIn.Address) != 20 {
|
||||||
return ErrInvalidAddress
|
return ErrInvalidAddress
|
||||||
}
|
}
|
||||||
if txIn.Amount == 0 {
|
if txIn.Amount == 0 {
|
||||||
return ErrInvalidAmount
|
return ErrInvalidAmount
|
||||||
}
|
}
|
||||||
return nil
|
return ResultOK
|
||||||
}
|
}
|
||||||
|
|
||||||
func (txIn TxInput) SignBytes() []byte {
|
func (txIn TxInput) SignBytes() []byte {
|
||||||
|
@ -71,14 +72,14 @@ type TxOutput struct {
|
||||||
Amount int64 `json:"amount"` // The sum of all outputs must not exceed the inputs.
|
Amount int64 `json:"amount"` // The sum of all outputs must not exceed the inputs.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (txOut TxOutput) ValidateBasic() error {
|
func (txOut TxOutput) ValidateBasic() tmsp.Result {
|
||||||
if len(txOut.Address) != 20 {
|
if len(txOut.Address) != 20 {
|
||||||
return ErrInvalidAddress
|
return ErrInvalidAddress
|
||||||
}
|
}
|
||||||
if txOut.Amount == 0 {
|
if txOut.Amount == 0 {
|
||||||
return ErrInvalidAmount
|
return ErrInvalidAmount
|
||||||
}
|
}
|
||||||
return nil
|
return ResultOK
|
||||||
}
|
}
|
||||||
|
|
||||||
func (txOut TxOutput) SignBytes() []byte {
|
func (txOut TxOutput) SignBytes() []byte {
|
||||||
|
|
Loading…
Reference in New Issue