Merge basecoin with tendermint_classic
This commit is contained in:
parent
3d8cb897b8
commit
5049c35efc
281
app/app.go
281
app/app.go
|
@ -1,11 +1,8 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/tendermint/basecoin/types"
|
"github.com/tendermint/basecoin/types"
|
||||||
. "github.com/tendermint/go-common"
|
. "github.com/tendermint/go-common"
|
||||||
"github.com/tendermint/go-crypto"
|
|
||||||
"github.com/tendermint/go-wire"
|
"github.com/tendermint/go-wire"
|
||||||
gov "github.com/tendermint/governmint/gov"
|
gov "github.com/tendermint/governmint/gov"
|
||||||
eyes "github.com/tendermint/merkleeyes/client"
|
eyes "github.com/tendermint/merkleeyes/client"
|
||||||
|
@ -36,14 +33,13 @@ func (app *Basecoin) Info() string {
|
||||||
func (app *Basecoin) SetOption(key string, value string) (log string) {
|
func (app *Basecoin) SetOption(key string, value string) (log string) {
|
||||||
if key == "setAccount" {
|
if key == "setAccount" {
|
||||||
var err error
|
var err error
|
||||||
var setAccount types.PubAccount
|
var setAccount types.Account
|
||||||
wire.ReadJSONPtr(&setAccount, []byte(value), &err)
|
wire.ReadJSONPtr(&setAccount, []byte(value), &err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "Error decoding setAccount message: " + err.Error()
|
return "Error decoding setAccount message: " + err.Error()
|
||||||
}
|
}
|
||||||
pubKeyBytes := wire.BinaryBytes(setAccount.PubKey)
|
accBytes := wire.BinaryBytes(setAccount)
|
||||||
accBytes := wire.BinaryBytes(setAccount.Account)
|
err = app.eyesCli.SetSync(setAccount.PubKey.Address(), accBytes)
|
||||||
err = app.eyesCli.SetSync(pubKeyBytes, accBytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "Error saving account: " + err.Error()
|
return "Error saving account: " + err.Error()
|
||||||
}
|
}
|
||||||
|
@ -53,67 +49,61 @@ func (app *Basecoin) SetOption(key string, value string) (log string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TMSP::AppendTx
|
// TMSP::AppendTx
|
||||||
func (app *Basecoin) AppendTx(txBytes []byte) (code tmsp.CodeType, result []byte, log string) {
|
func (app *Basecoin) AppendTx(txBytes []byte) (res tmsp.Result) {
|
||||||
if len(txBytes) > maxTxSize {
|
if len(txBytes) > maxTxSize {
|
||||||
return tmsp.CodeType_BaseEncodingError, nil, "Tx size exceeds maximum"
|
return types.ErrEncodingError.AppendLog("Tx size exceeds maximum")
|
||||||
}
|
}
|
||||||
// Decode tx
|
// Decode tx
|
||||||
var tx types.Tx
|
var tx types.Tx
|
||||||
err := wire.ReadBinaryBytes(txBytes, &tx)
|
err := wire.ReadBinaryBytes(txBytes, &tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tmsp.CodeType_BaseEncodingError, nil, "Error decoding tx: " + err.Error()
|
return types.ErrEncodingError.AppendLog("Error decoding tx: " + err.Error())
|
||||||
}
|
}
|
||||||
// Validate tx
|
// Validate tx
|
||||||
code, errStr := validateTx(tx)
|
res = validateTx(tx)
|
||||||
if errStr != "" {
|
if !res.IsOK() {
|
||||||
return code, nil, "Error validating tx: " + errStr
|
return res.PrependLog("Error validating tx")
|
||||||
}
|
}
|
||||||
// Load accounts
|
|
||||||
accMap := loadAccounts(app.eyesCli, allPubKeys(tx))
|
|
||||||
// Execute tx
|
// Execute tx
|
||||||
accs, code, errStr := runTx(tx, accMap, false)
|
// TODO: get or make state with app.eeysCli, pass it to
|
||||||
if errStr != "" {
|
// state.execution.go ExecTx
|
||||||
return code, nil, "Error executing tx: " + errStr
|
// Synchronize the txCache.
|
||||||
}
|
//storeAccounts(app.eyesCli, accs)
|
||||||
// Store accounts
|
return types.ResultOK
|
||||||
storeAccounts(app.eyesCli, accs)
|
|
||||||
return tmsp.CodeType_OK, nil, "Success"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TMSP::CheckTx
|
// TMSP::CheckTx
|
||||||
func (app *Basecoin) CheckTx(txBytes []byte) (code tmsp.CodeType, result []byte, log string) {
|
func (app *Basecoin) CheckTx(txBytes []byte) (res tmsp.Result) {
|
||||||
if len(txBytes) > maxTxSize {
|
if len(txBytes) > maxTxSize {
|
||||||
return tmsp.CodeType_BaseEncodingError, nil, "Tx size exceeds maximum"
|
return types.ErrEncodingError.AppendLog("Tx size exceeds maximum")
|
||||||
}
|
}
|
||||||
// Decode tx
|
// Decode tx
|
||||||
var tx types.Tx
|
var tx types.Tx
|
||||||
err := wire.ReadBinaryBytes(txBytes, &tx)
|
err := wire.ReadBinaryBytes(txBytes, &tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tmsp.CodeType_BaseEncodingError, nil, "Error decoding tx: " + err.Error()
|
return types.ErrEncodingError.AppendLog("Error decoding tx: " + err.Error())
|
||||||
}
|
}
|
||||||
// Validate tx
|
// Validate tx
|
||||||
code, errStr := validateTx(tx)
|
res = validateTx(tx)
|
||||||
if errStr != "" {
|
if !res.IsOK() {
|
||||||
return code, nil, "Error validating tx: " + errStr
|
return res.PrependLog("Error validating tx")
|
||||||
}
|
}
|
||||||
// Load accounts
|
|
||||||
accMap := loadAccounts(app.eyesCli, allPubKeys(tx))
|
|
||||||
// Execute tx
|
// Execute tx
|
||||||
_, code, errStr = runTx(tx, accMap, false)
|
// TODO: get or make state with app.eeysCli, pass it to
|
||||||
if errStr != "" {
|
// state.execution.go ExecTx
|
||||||
return code, nil, "Error (mock) executing tx: " + errStr
|
// Synchronize the txCache.
|
||||||
}
|
//storeAccounts(app.eyesCli, accs)
|
||||||
return tmsp.CodeType_OK, nil, "Success"
|
return types.ResultOK.SetLog("Success")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TMSP::Query
|
// TMSP::Query
|
||||||
func (app *Basecoin) Query(query []byte) (code tmsp.CodeType, result []byte, log string) {
|
func (app *Basecoin) Query(query []byte) (res tmsp.Result) {
|
||||||
return tmsp.CodeType_OK, nil, ""
|
return types.ResultOK
|
||||||
value, err := app.eyesCli.GetSync(query)
|
value, err := app.eyesCli.GetSync(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Error making query: " + err.Error())
|
panic("Error making query: " + err.Error())
|
||||||
}
|
}
|
||||||
return tmsp.CodeType_OK, value, "Success"
|
return types.ResultOK.SetData(value).SetLog("Success")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TMSP::Commit
|
// TMSP::Commit
|
||||||
|
@ -134,218 +124,3 @@ func (app *Basecoin) InitChain(validators []*tmsp.Validator) {
|
||||||
func (app *Basecoin) EndBlock(height uint64) []*tmsp.Validator {
|
func (app *Basecoin) EndBlock(height uint64) []*tmsp.Validator {
|
||||||
return app.govMint.EndBlock(height)
|
return app.govMint.EndBlock(height)
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------
|
|
||||||
|
|
||||||
func validateTx(tx types.Tx) (code tmsp.CodeType, errStr string) {
|
|
||||||
inputs, outputs := tx.GetInputs(), tx.GetOutputs()
|
|
||||||
if len(inputs) == 0 {
|
|
||||||
return tmsp.CodeType_BaseEncodingError, "Tx.Inputs length cannot be 0"
|
|
||||||
}
|
|
||||||
seenPubKeys := map[string]bool{}
|
|
||||||
signBytes := tx.SignBytes()
|
|
||||||
for _, input := range inputs {
|
|
||||||
code, errStr = validateInput(input, signBytes)
|
|
||||||
if errStr != "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
keyString := input.PubKey.KeyString()
|
|
||||||
if seenPubKeys[keyString] {
|
|
||||||
return tmsp.CodeType_BaseEncodingError, "Duplicate input pubKey"
|
|
||||||
}
|
|
||||||
seenPubKeys[keyString] = true
|
|
||||||
}
|
|
||||||
for _, output := range outputs {
|
|
||||||
code, errStr = validateOutput(output)
|
|
||||||
if errStr != "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
keyString := output.PubKey.KeyString()
|
|
||||||
if seenPubKeys[keyString] {
|
|
||||||
return tmsp.CodeType_BaseEncodingError, "Duplicate output pubKey"
|
|
||||||
}
|
|
||||||
seenPubKeys[keyString] = true
|
|
||||||
}
|
|
||||||
sumInputs, overflow := sumAmounts(inputs, nil, 0)
|
|
||||||
if overflow {
|
|
||||||
return tmsp.CodeType_BaseEncodingError, "Input amount overflow"
|
|
||||||
}
|
|
||||||
sumOutputsPlus, overflow := sumAmounts(nil, outputs, len(inputs)+len(outputs))
|
|
||||||
if overflow {
|
|
||||||
return tmsp.CodeType_BaseEncodingError, "Output amount overflow"
|
|
||||||
}
|
|
||||||
if sumInputs < sumOutputsPlus {
|
|
||||||
return tmsp.CodeType_BaseInsufficientFees, "Insufficient fees"
|
|
||||||
}
|
|
||||||
return tmsp.CodeType_OK, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateInput(input types.Input, signBytes []byte) (code tmsp.CodeType, errStr string) {
|
|
||||||
if input.Amount == 0 {
|
|
||||||
return tmsp.CodeType_BaseEncodingError, "Input amount cannot be zero"
|
|
||||||
}
|
|
||||||
if input.PubKey == nil {
|
|
||||||
return tmsp.CodeType_BaseEncodingError, "Input pubKey cannot be nil"
|
|
||||||
}
|
|
||||||
if !input.PubKey.VerifyBytes(signBytes, input.Signature) {
|
|
||||||
return tmsp.CodeType_BaseUnauthorized, "Invalid signature"
|
|
||||||
}
|
|
||||||
return tmsp.CodeType_OK, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateOutput(output types.Output) (code tmsp.CodeType, errStr string) {
|
|
||||||
if output.Amount == 0 {
|
|
||||||
return tmsp.CodeType_BaseEncodingError, "Output amount cannot be zero"
|
|
||||||
}
|
|
||||||
if output.PubKey == nil {
|
|
||||||
return tmsp.CodeType_BaseEncodingError, "Output pubKey cannot be nil"
|
|
||||||
}
|
|
||||||
return tmsp.CodeType_OK, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func sumAmounts(inputs []types.Input, outputs []types.Output, more int) (total uint64, overflow bool) {
|
|
||||||
total = uint64(more)
|
|
||||||
for _, input := range inputs {
|
|
||||||
total2 := total + input.Amount
|
|
||||||
if total2 < total {
|
|
||||||
return 0, true
|
|
||||||
}
|
|
||||||
total = total2
|
|
||||||
}
|
|
||||||
for _, output := range outputs {
|
|
||||||
total2 := total + output.Amount
|
|
||||||
if total2 < total {
|
|
||||||
return 0, true
|
|
||||||
}
|
|
||||||
total = total2
|
|
||||||
}
|
|
||||||
return total, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns accounts in order of types.Tx inputs and outputs
|
|
||||||
// appendTx: true if this is for AppendTx.
|
|
||||||
// TODO: create more intelligent sequence-checking. Current impl is just for a throughput demo.
|
|
||||||
func runTx(tx types.Tx, accMap map[string]types.PubAccount, appendTx bool) (accs []types.PubAccount, code tmsp.CodeType, errStr string) {
|
|
||||||
switch tx := tx.(type) {
|
|
||||||
case *types.SendTx:
|
|
||||||
return runSendTx(tx, accMap, appendTx)
|
|
||||||
case *types.GovTx:
|
|
||||||
return runGovTx(tx, accMap, appendTx)
|
|
||||||
}
|
|
||||||
return nil, tmsp.CodeType_InternalError, "Unknown transaction type"
|
|
||||||
}
|
|
||||||
|
|
||||||
func processInputsOutputs(tx types.Tx, accMap map[string]types.PubAccount, appendTx bool) (accs []types.PubAccount, code tmsp.CodeType, errStr string) {
|
|
||||||
inputs, outputs := tx.GetInputs(), tx.GetOutputs()
|
|
||||||
accs = make([]types.PubAccount, 0, len(inputs)+len(outputs))
|
|
||||||
// Deduct from inputs
|
|
||||||
// TODO refactor, duplicated code.
|
|
||||||
for _, input := range inputs {
|
|
||||||
var acc, ok = accMap[input.PubKey.KeyString()]
|
|
||||||
if !ok {
|
|
||||||
return nil, tmsp.CodeType_BaseUnknownAccount, "Input account does not exist"
|
|
||||||
}
|
|
||||||
if appendTx {
|
|
||||||
if acc.Sequence != input.Sequence {
|
|
||||||
return nil, tmsp.CodeType_BaseBadNonce, "Invalid sequence"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if acc.Sequence > input.Sequence {
|
|
||||||
return nil, tmsp.CodeType_BaseBadNonce, "Invalid sequence (too low)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if acc.Balance < input.Amount {
|
|
||||||
return nil, tmsp.CodeType_BaseInsufficientFunds, "Insufficient funds"
|
|
||||||
}
|
|
||||||
// Good!
|
|
||||||
acc.Sequence++
|
|
||||||
acc.Balance -= input.Amount
|
|
||||||
accs = append(accs, acc)
|
|
||||||
}
|
|
||||||
// Add to outputs
|
|
||||||
for _, output := range outputs {
|
|
||||||
var acc, ok = accMap[output.PubKey.KeyString()]
|
|
||||||
if !ok {
|
|
||||||
// Create new account if it doesn't already exist.
|
|
||||||
acc = types.PubAccount{
|
|
||||||
PubKey: output.PubKey,
|
|
||||||
Account: types.Account{
|
|
||||||
Balance: output.Amount,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
accMap[output.PubKey.KeyString()] = acc
|
|
||||||
accs = append(accs, acc)
|
|
||||||
} else {
|
|
||||||
// Good!
|
|
||||||
if (acc.Balance + output.Amount) < acc.Balance {
|
|
||||||
return nil, tmsp.CodeType_InternalError, "Output balance overflow in runTx"
|
|
||||||
}
|
|
||||||
acc.Balance += output.Amount
|
|
||||||
accs = append(accs, acc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return accs, tmsp.CodeType_OK, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func runSendTx(tx types.Tx, accMap map[string]types.PubAccount, appendTx bool) (accs []types.PubAccount, code tmsp.CodeType, errStr string) {
|
|
||||||
return processInputsOutputs(tx, accMap, appendTx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runGovTx(tx *types.GovTx, accMap map[string]types.PubAccount, appendTx bool) (accs []types.PubAccount, code tmsp.CodeType, errStr string) {
|
|
||||||
accs, code, errStr = processInputsOutputs(tx, accMap, appendTx)
|
|
||||||
// XXX run GovTx
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------
|
|
||||||
|
|
||||||
func loadAccounts(eyesCli *eyes.Client, pubKeys []crypto.PubKey) (accMap map[string]types.PubAccount) {
|
|
||||||
accMap = make(map[string]types.PubAccount, len(pubKeys))
|
|
||||||
for _, pubKey := range pubKeys {
|
|
||||||
keyString := pubKey.KeyString()
|
|
||||||
accBytes, err := eyesCli.GetSync([]byte(keyString))
|
|
||||||
if err != nil {
|
|
||||||
panic("Error loading account: " + err.Error())
|
|
||||||
}
|
|
||||||
if len(accBytes) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var acc types.Account
|
|
||||||
err = wire.ReadBinaryBytes(accBytes, &acc)
|
|
||||||
if err != nil {
|
|
||||||
panic("Error reading account: " + err.Error())
|
|
||||||
}
|
|
||||||
accMap[keyString] = types.PubAccount{
|
|
||||||
Account: acc,
|
|
||||||
PubKey: pubKey,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: accs must be stored in deterministic order.
|
|
||||||
func storeAccounts(eyesCli *eyes.Client, accs []types.PubAccount) {
|
|
||||||
fmt.Println("STORE ACCOUNTS", accs)
|
|
||||||
for _, acc := range accs {
|
|
||||||
accBytes := wire.BinaryBytes(acc.Account)
|
|
||||||
err := eyesCli.SetSync([]byte(acc.PubKey.KeyString()), accBytes)
|
|
||||||
if err != nil {
|
|
||||||
panic("Error storing account: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------
|
|
||||||
|
|
||||||
func allPubKeys(tx types.Tx) (pubKeys []crypto.PubKey) {
|
|
||||||
inputs := tx.GetInputs()
|
|
||||||
outputs := tx.GetOutputs()
|
|
||||||
pubKeys = make([]crypto.PubKey, 0, len(inputs)+len(outputs))
|
|
||||||
for _, input := range inputs {
|
|
||||||
pubKeys = append(pubKeys, input.PubKey)
|
|
||||||
}
|
|
||||||
for _, output := range outputs {
|
|
||||||
pubKeys = append(pubKeys, output.PubKey)
|
|
||||||
}
|
|
||||||
return pubKeys
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tendermint/basecoin/types"
|
||||||
|
tmsp "github.com/tendermint/tmsp/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateTx(tx types.Tx) (res tmsp.Result) {
|
||||||
|
return
|
||||||
|
}
|
|
@ -4,9 +4,7 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
|
|
||||||
"github.com/tendermint/basecoin/app"
|
"github.com/tendermint/basecoin/app"
|
||||||
"github.com/tendermint/basecoin/types"
|
|
||||||
. "github.com/tendermint/go-common"
|
. "github.com/tendermint/go-common"
|
||||||
"github.com/tendermint/go-wire"
|
|
||||||
eyes "github.com/tendermint/merkleeyes/client"
|
eyes "github.com/tendermint/merkleeyes/client"
|
||||||
"github.com/tendermint/tmsp/server"
|
"github.com/tendermint/tmsp/server"
|
||||||
)
|
)
|
||||||
|
@ -15,7 +13,6 @@ func main() {
|
||||||
|
|
||||||
addrPtr := flag.String("address", "tcp://0.0.0.0:46658", "Listen address")
|
addrPtr := flag.String("address", "tcp://0.0.0.0:46658", "Listen address")
|
||||||
eyesPtr := flag.String("eyes", "tcp://0.0.0.0:46659", "MerkleEyes address")
|
eyesPtr := flag.String("eyes", "tcp://0.0.0.0:46659", "MerkleEyes address")
|
||||||
genPtr := flag.String("genesis", "genesis.json", "Genesis JSON file")
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// Connect to MerkleEyes
|
// Connect to MerkleEyes
|
||||||
|
@ -27,26 +24,6 @@ func main() {
|
||||||
// Create Basecoin app
|
// Create Basecoin app
|
||||||
app := app.NewBasecoin(eyesCli)
|
app := app.NewBasecoin(eyesCli)
|
||||||
|
|
||||||
// Load GenesisState
|
|
||||||
jsonBytes, err := ReadFile(*genPtr)
|
|
||||||
if err != nil {
|
|
||||||
Exit("read genesis: " + err.Error())
|
|
||||||
}
|
|
||||||
genesisState := types.GenesisState{}
|
|
||||||
wire.ReadJSONPtr(&genesisState, jsonBytes, &err)
|
|
||||||
if err != nil {
|
|
||||||
Exit("parsing genesis JSON: " + err.Error())
|
|
||||||
}
|
|
||||||
for _, account := range genesisState.Accounts {
|
|
||||||
// pubKeyBytes := wire.BinaryBytes(account.PubKey)
|
|
||||||
pubKeyString := account.PubKey.KeyString()
|
|
||||||
accBytes := wire.BinaryBytes(account.Account)
|
|
||||||
err = eyesCli.SetSync([]byte(pubKeyString), accBytes)
|
|
||||||
if err != nil {
|
|
||||||
Exit("loading genesis accounts: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the listener
|
// Start the listener
|
||||||
svr, err := server.NewServer(*addrPtr, app)
|
svr, err := server.NewServer(*addrPtr, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,363 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/tendermint/basecoin/types"
|
||||||
|
. "github.com/tendermint/go-common"
|
||||||
|
"github.com/tendermint/go-events"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The accounts from the TxInputs must either already have
|
||||||
|
// crypto.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 types.AccountGetter, ins []types.TxInput) (map[string]*types.Account, error) {
|
||||||
|
accounts := map[string]*types.Account{}
|
||||||
|
for _, in := range ins {
|
||||||
|
// Account shouldn't be duplicated
|
||||||
|
if _, ok := accounts[string(in.Address)]; ok {
|
||||||
|
return nil, types.ErrDuplicateAddress
|
||||||
|
}
|
||||||
|
acc := state.GetAccount(in.Address)
|
||||||
|
if acc == nil {
|
||||||
|
return nil, types.ErrInvalidAddress
|
||||||
|
}
|
||||||
|
// PubKey should be present in either "account" or "in"
|
||||||
|
if err := checkInputPubKey(in.Address, acc, in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
accounts[string(in.Address)] = acc
|
||||||
|
}
|
||||||
|
return accounts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOrMakeOutputs(state types.AccountGetter, accounts map[string]*types.Account, outs []types.TxOutput) (map[string]*types.Account, error) {
|
||||||
|
if accounts == nil {
|
||||||
|
accounts = make(map[string]*types.Account)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, out := range outs {
|
||||||
|
// Account shouldn't be duplicated
|
||||||
|
if _, ok := accounts[string(out.Address)]; ok {
|
||||||
|
return nil, types.ErrDuplicateAddress
|
||||||
|
}
|
||||||
|
acc := state.GetAccount(out.Address)
|
||||||
|
// output account may be nil (new)
|
||||||
|
if acc == nil {
|
||||||
|
acc = &types.Account{
|
||||||
|
PubKey: nil,
|
||||||
|
Sequence: 0,
|
||||||
|
Balance: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
accounts[string(out.Address)] = acc
|
||||||
|
}
|
||||||
|
return accounts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input must not have a redundant PubKey (i.e. Account already has PubKey).
|
||||||
|
// NOTE: Account has PubKey if Sequence > 0
|
||||||
|
func checkInputPubKey(address []byte, acc *types.Account, in types.TxInput) error {
|
||||||
|
if acc.PubKey == nil {
|
||||||
|
if in.PubKey == nil {
|
||||||
|
return types.ErrUnknownPubKey
|
||||||
|
}
|
||||||
|
if !bytes.Equal(in.PubKey.Address(), address) {
|
||||||
|
return types.ErrInvalidPubKey
|
||||||
|
}
|
||||||
|
acc.PubKey = in.PubKey
|
||||||
|
} else {
|
||||||
|
if in.PubKey != nil {
|
||||||
|
return types.ErrInvalidPubKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateInputs(accounts map[string]*types.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 *types.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.ErrInvalidSignature
|
||||||
|
}
|
||||||
|
// Check sequences
|
||||||
|
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) {
|
||||||
|
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]*types.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]*types.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 := tx.SignBytes(_s.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 {
|
||||||
|
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), 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 = blockCache.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(_s.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 = blockCache.GetAccount(tx.Address)
|
||||||
|
|
||||||
|
log.Info(Fmt("Out account: %v", outAcc))
|
||||||
|
|
||||||
|
// Good!
|
||||||
|
value := tx.Input.Amount - tx.Fee
|
||||||
|
inAcc.Sequence += 1
|
||||||
|
inAcc.Balance -= tx.Fee
|
||||||
|
blockCache.UpdateAccount(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(blockCache)
|
||||||
|
/*
|
||||||
|
params = vm.Params{
|
||||||
|
BlockHeight: int64(_s.LastBlockHeight),
|
||||||
|
BlockHash: LeftPadWord256(_s.LastBlockHash),
|
||||||
|
BlockTime: _s.LastBlockTime.Unix(),
|
||||||
|
GasLimit: _s.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 blockCache.
|
||||||
|
{ // Capture scope for goto.
|
||||||
|
// Write caller/callee to txCache.
|
||||||
|
// txCache.UpdateAccount(caller)
|
||||||
|
// txCache.UpdateAccount(callee)
|
||||||
|
// 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)
|
||||||
|
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
|
||||||
|
blockCache.UpdateAccount(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)
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tendermint/go-logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logger.New("module", "state")
|
|
@ -0,0 +1,48 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tendermint/basecoin/types"
|
||||||
|
"github.com/tendermint/go-wire"
|
||||||
|
eyes "github.com/tendermint/merkleeyes/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
chainID string
|
||||||
|
eyesCli *eyes.Client
|
||||||
|
|
||||||
|
LastBlockHeight uint64
|
||||||
|
LastBlockHash []byte
|
||||||
|
GasLimit int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewState(chainID string, eyesCli *eyes.Client) *State {
|
||||||
|
s := &State{
|
||||||
|
chainID: chainID,
|
||||||
|
eyesCli: eyesCli,
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) GetAccount(addr []byte) *types.Account {
|
||||||
|
accBytes, err := s.eyesCli.GetSync(addr)
|
||||||
|
if err != nil {
|
||||||
|
panic("Error loading account: " + err.Error())
|
||||||
|
}
|
||||||
|
if len(accBytes) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var acc types.Account
|
||||||
|
err = wire.ReadBinaryBytes(accBytes, &acc)
|
||||||
|
if err != nil {
|
||||||
|
panic("Error reading account: " + err.Error())
|
||||||
|
}
|
||||||
|
return &acc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) SetAccount(acc *types.Account) {
|
||||||
|
accBytes := wire.BinaryBytes(acc)
|
||||||
|
err := s.eyesCli.SetSync(acc.PubKey.Address(), accBytes)
|
||||||
|
if err != nil {
|
||||||
|
panic("Error storing account: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,8 +12,8 @@ func PrivAccountFromSecret(secret string) types.PrivAccount {
|
||||||
privKey := crypto.GenPrivKeyEd25519FromSecret([]byte(secret))
|
privKey := crypto.GenPrivKeyEd25519FromSecret([]byte(secret))
|
||||||
privAccount := types.PrivAccount{
|
privAccount := types.PrivAccount{
|
||||||
PrivKey: privKey,
|
PrivKey: privKey,
|
||||||
PubKey: privKey.PubKey(),
|
|
||||||
Account: types.Account{
|
Account: types.Account{
|
||||||
|
PubKey: privKey.PubKey(),
|
||||||
Sequence: 0,
|
Sequence: 0,
|
||||||
Balance: 0,
|
Balance: 0,
|
||||||
},
|
},
|
||||||
|
@ -22,21 +22,21 @@ func PrivAccountFromSecret(secret string) types.PrivAccount {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make `num` random accounts
|
// Make `num` random accounts
|
||||||
func RandAccounts(num int, minAmount uint64, maxAmount uint64) []types.PrivAccount {
|
func RandAccounts(num int, minAmount int64, maxAmount int64) []types.PrivAccount {
|
||||||
privAccs := make([]types.PrivAccount, num)
|
privAccs := make([]types.PrivAccount, num)
|
||||||
for i := 0; i < num; i++ {
|
for i := 0; i < num; i++ {
|
||||||
|
|
||||||
balance := minAmount
|
balance := minAmount
|
||||||
if maxAmount > minAmount {
|
if maxAmount > minAmount {
|
||||||
balance += RandUint64() % (maxAmount - minAmount)
|
balance += RandInt64() % (maxAmount - minAmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
privKey := crypto.GenPrivKeyEd25519()
|
privKey := crypto.GenPrivKeyEd25519()
|
||||||
pubKey := privKey.PubKey()
|
pubKey := privKey.PubKey()
|
||||||
privAccs[i] = types.PrivAccount{
|
privAccs[i] = types.PrivAccount{
|
||||||
PrivKey: privKey,
|
PrivKey: privKey,
|
||||||
PubKey: pubKey,
|
|
||||||
Account: types.Account{
|
Account: types.Account{
|
||||||
|
PubKey: pubKey,
|
||||||
Sequence: 0,
|
Sequence: 0,
|
||||||
Balance: balance,
|
Balance: balance,
|
||||||
},
|
},
|
||||||
|
|
|
@ -35,7 +35,7 @@ func main() {
|
||||||
|
|
||||||
// Get the root account
|
// Get the root account
|
||||||
root := tests.PrivAccountFromSecret("root")
|
root := tests.PrivAccountFromSecret("root")
|
||||||
sequence := uint(0)
|
sequence := int(0)
|
||||||
// Make a bunch of PrivAccounts
|
// Make a bunch of PrivAccounts
|
||||||
privAccounts := tests.RandAccounts(1000, 1000000, 0)
|
privAccounts := tests.RandAccounts(1000, 1000000, 0)
|
||||||
privAccountSequences := make(map[string]int)
|
privAccountSequences := make(map[string]int)
|
||||||
|
@ -44,16 +44,17 @@ func main() {
|
||||||
for i := 0; i < len(privAccounts); i++ {
|
for i := 0; i < len(privAccounts); i++ {
|
||||||
privAccount := privAccounts[i]
|
privAccount := privAccounts[i]
|
||||||
tx := &types.SendTx{
|
tx := &types.SendTx{
|
||||||
Inputs: []types.Input{
|
Inputs: []types.TxInput{
|
||||||
types.Input{
|
types.TxInput{
|
||||||
PubKey: root.PubKey,
|
Address: root.Account.PubKey.Address(),
|
||||||
|
PubKey: root.Account.PubKey, // TODO is this needed?
|
||||||
Amount: 1000002,
|
Amount: 1000002,
|
||||||
Sequence: sequence,
|
Sequence: sequence,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Outputs: []types.Output{
|
Outputs: []types.TxOutput{
|
||||||
types.Output{
|
types.TxOutput{
|
||||||
PubKey: privAccount.PubKey,
|
Address: privAccount.Account.PubKey.Address(),
|
||||||
Amount: 1000000,
|
Amount: 1000000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -89,21 +90,22 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
privAccountA := privAccounts[randA]
|
privAccountA := privAccounts[randA]
|
||||||
privAccountASequence := privAccountSequences[privAccountA.PubKey.KeyString()]
|
privAccountASequence := privAccountSequences[privAccountA.Account.PubKey.KeyString()]
|
||||||
privAccountSequences[privAccountA.PubKey.KeyString()] = privAccountASequence + 1
|
privAccountSequences[privAccountA.Account.PubKey.KeyString()] = privAccountASequence + 1
|
||||||
privAccountB := privAccounts[randB]
|
privAccountB := privAccounts[randB]
|
||||||
|
|
||||||
tx := &types.SendTx{
|
tx := &types.SendTx{
|
||||||
Inputs: []types.Input{
|
Inputs: []types.TxInput{
|
||||||
types.Input{
|
types.TxInput{
|
||||||
PubKey: privAccountA.PubKey,
|
Address: privAccountA.Account.PubKey.Address(),
|
||||||
|
PubKey: privAccountA.Account.PubKey,
|
||||||
Amount: 3,
|
Amount: 3,
|
||||||
Sequence: uint(privAccountASequence),
|
Sequence: privAccountASequence,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Outputs: []types.Output{
|
Outputs: []types.TxOutput{
|
||||||
types.Output{
|
types.TxOutput{
|
||||||
PubKey: privAccountB.PubKey,
|
Address: privAccountB.Account.PubKey.Address(),
|
||||||
Amount: 1,
|
Amount: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tendermint/go-crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Account struct {
|
||||||
|
PubKey crypto.PubKey // May be nil, if not known.
|
||||||
|
Sequence int
|
||||||
|
Balance int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (acc *Account) Copy() *Account {
|
||||||
|
accCopy := *acc
|
||||||
|
return &accCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (acc *Account) String() string {
|
||||||
|
if acc == nil {
|
||||||
|
return "nil-Account"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Account{%v %v %v}",
|
||||||
|
acc.PubKey, acc.Sequence, acc.Balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------
|
||||||
|
|
||||||
|
type PrivAccount struct {
|
||||||
|
crypto.PrivKey
|
||||||
|
Account
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------
|
||||||
|
|
||||||
|
type AccountGetter interface {
|
||||||
|
GetAccount(addr []byte) *Account
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountGetterSetter interface {
|
||||||
|
GetAccount(addr []byte) *Account
|
||||||
|
SetAccount(acc *Account)
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
tmsp "github.com/tendermint/tmsp/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrDuplicateAddress = tmsp.NewError(tmsp.CodeType_BaseDuplicateAddress, "Error duplicate address")
|
||||||
|
ErrEncodingError = tmsp.NewError(tmsp.CodeType_BaseEncodingError, "Error encoding error")
|
||||||
|
ErrInsufficientFees = tmsp.NewError(tmsp.CodeType_BaseInsufficientFees, "Error insufficient fees")
|
||||||
|
ErrInsufficientFunds = tmsp.NewError(tmsp.CodeType_BaseInsufficientFunds, "Error insufficient funds")
|
||||||
|
ErrInsufficientGasPrice = tmsp.NewError(tmsp.CodeType_BaseInsufficientGasPrice, "Error insufficient gas price")
|
||||||
|
ErrInvalidAddress = tmsp.NewError(tmsp.CodeType_BaseInvalidAddress, "Error invalid address")
|
||||||
|
ErrInvalidAmount = tmsp.NewError(tmsp.CodeType_BaseInvalidAmount, "Error invalid amount")
|
||||||
|
ErrInvalidPubKey = tmsp.NewError(tmsp.CodeType_BaseInvalidPubKey, "Error invalid pubkey")
|
||||||
|
ErrInvalidSequence = tmsp.NewError(tmsp.CodeType_BaseInvalidSequence, "Error invalid sequence")
|
||||||
|
ErrInvalidSignature = tmsp.NewError(tmsp.CodeType_BaseInvalidSignature, "Error invalid signature")
|
||||||
|
ErrUnknownPubKey = tmsp.NewError(tmsp.CodeType_BaseUnknownPubKey, "Error unknown pubkey")
|
||||||
|
|
||||||
|
ResultOK = tmsp.NewResultOK(nil, "")
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
type Plugin func(ags AccountGetterSetter,
|
||||||
|
caller *Account,
|
||||||
|
input []byte,
|
||||||
|
gas *int64) (result []byte, err error)
|
|
@ -0,0 +1,174 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
. "github.com/tendermint/go-common"
|
||||||
|
"github.com/tendermint/go-crypto"
|
||||||
|
"github.com/tendermint/go-wire"
|
||||||
|
"golang.org/x/crypto/ripemd160"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Tx (Transaction) is an atomic operation on the ledger state.
|
||||||
|
|
||||||
|
Account Types:
|
||||||
|
- SendTx Send coins to address
|
||||||
|
- CallTx Send a msg to a contract that runs in the vm
|
||||||
|
*/
|
||||||
|
|
||||||
|
type Tx interface {
|
||||||
|
SignBytes(chainID string) []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types of Tx implementations
|
||||||
|
const (
|
||||||
|
// Account transactions
|
||||||
|
TxTypeSend = byte(0x01)
|
||||||
|
TxTypeCall = byte(0x02)
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = wire.RegisterInterface(
|
||||||
|
struct{ Tx }{},
|
||||||
|
wire.ConcreteType{&SendTx{}, TxTypeSend},
|
||||||
|
wire.ConcreteType{&CallTx{}, TxTypeCall},
|
||||||
|
)
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type TxInput struct {
|
||||||
|
Address []byte `json:"address"` // Hash of the PubKey
|
||||||
|
Amount int64 `json:"amount"` // Must not exceed account balance
|
||||||
|
Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput
|
||||||
|
Signature crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx
|
||||||
|
PubKey crypto.PubKey `json:"pub_key"` // May be nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (txIn TxInput) ValidateBasic() error {
|
||||||
|
if len(txIn.Address) != 20 {
|
||||||
|
return ErrInvalidAddress
|
||||||
|
}
|
||||||
|
if txIn.Amount == 0 {
|
||||||
|
return ErrInvalidAmount
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (txIn TxInput) SignBytes() []byte {
|
||||||
|
return []byte(Fmt(`{"address":"%X","amount":%v,"sequence":%v}`,
|
||||||
|
txIn.Address, txIn.Amount, txIn.Sequence))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (txIn TxInput) String() string {
|
||||||
|
return Fmt("TxInput{%X,%v,%v,%v,%v}", txIn.Address, txIn.Amount, txIn.Sequence, txIn.Signature, txIn.PubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type TxOutput struct {
|
||||||
|
Address []byte `json:"address"` // Hash of the PubKey
|
||||||
|
Amount int64 `json:"amount"` // The sum of all outputs must not exceed the inputs.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (txOut TxOutput) ValidateBasic() error {
|
||||||
|
if len(txOut.Address) != 20 {
|
||||||
|
return ErrInvalidAddress
|
||||||
|
}
|
||||||
|
if txOut.Amount == 0 {
|
||||||
|
return ErrInvalidAmount
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (txOut TxOutput) SignBytes() []byte {
|
||||||
|
return []byte(Fmt(`{"address":"%X","amount":%v}`,
|
||||||
|
txOut.Address, txOut.Amount))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (txOut TxOutput) String() string {
|
||||||
|
return Fmt("TxOutput{%X,%v}", txOut.Address, txOut.Amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type SendTx struct {
|
||||||
|
Inputs []TxInput `json:"inputs"`
|
||||||
|
Outputs []TxOutput `json:"outputs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *SendTx) SignBytes(chainID string) []byte {
|
||||||
|
var buf = new(bytes.Buffer)
|
||||||
|
buf.Write([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))))
|
||||||
|
buf.Write([]byte(Fmt(`,"tx":[%v,{"inputs":[`, TxTypeSend)))
|
||||||
|
for i, in := range tx.Inputs {
|
||||||
|
buf.Write(in.SignBytes())
|
||||||
|
if i != len(tx.Inputs)-1 {
|
||||||
|
buf.Write([]byte(","))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.Write([]byte(`],"outputs":[`))
|
||||||
|
for i, out := range tx.Outputs {
|
||||||
|
buf.Write(out.SignBytes())
|
||||||
|
if i != len(tx.Outputs)-1 {
|
||||||
|
buf.Write([]byte(","))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.Write([]byte(`]}]}`))
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *SendTx) String() string {
|
||||||
|
return Fmt("SendTx{%v -> %v}", tx.Inputs, tx.Outputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type CallTx struct {
|
||||||
|
Input TxInput `json:"input"`
|
||||||
|
Address []byte `json:"address"`
|
||||||
|
GasLimit int64 `json:"gas_limit"`
|
||||||
|
Fee int64 `json:"fee"`
|
||||||
|
Data []byte `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *CallTx) SignBytes(chainID string) []byte {
|
||||||
|
var buf = new(bytes.Buffer)
|
||||||
|
buf.Write([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))))
|
||||||
|
buf.Write([]byte(Fmt(`,"tx":[%v,{"address":"%X","data":"%X"`, TxTypeCall, tx.Address, tx.Data)))
|
||||||
|
buf.Write([]byte(Fmt(`,"fee":%v,"gas_limit":%v,"input":`, tx.Fee, tx.GasLimit)))
|
||||||
|
buf.Write(tx.Input.SignBytes())
|
||||||
|
buf.Write([]byte(`}]}`))
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *CallTx) String() string {
|
||||||
|
return Fmt("CallTx{%v -> %x: %x}", tx.Input, tx.Address, tx.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContractAddress(caller []byte, nonce int) []byte {
|
||||||
|
temp := make([]byte, 32+8)
|
||||||
|
copy(temp, caller)
|
||||||
|
PutInt64BE(temp[32:], int64(nonce))
|
||||||
|
hasher := ripemd160.New()
|
||||||
|
hasher.Write(temp) // does not error
|
||||||
|
return hasher.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func TxID(chainID string, tx Tx) []byte {
|
||||||
|
signBytes := tx.SignBytes(chainID)
|
||||||
|
return wire.BinaryRipemd160(signBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Contract: This function is deterministic and completely reversible.
|
||||||
|
func jsonEscape(str string) string {
|
||||||
|
escapedBytes, err := json.Marshal(str)
|
||||||
|
if err != nil {
|
||||||
|
PanicSanity(Fmt("Error json-escaping a string", str))
|
||||||
|
}
|
||||||
|
return string(escapedBytes)
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/tendermint/go-common"
|
||||||
|
)
|
||||||
|
|
||||||
|
var chainID string = "test_chain"
|
||||||
|
|
||||||
|
func TestSendTxSignable(t *testing.T) {
|
||||||
|
sendTx := &SendTx{
|
||||||
|
Inputs: []TxInput{
|
||||||
|
TxInput{
|
||||||
|
Address: []byte("input1"),
|
||||||
|
Amount: 12345,
|
||||||
|
Sequence: 67890,
|
||||||
|
},
|
||||||
|
TxInput{
|
||||||
|
Address: []byte("input2"),
|
||||||
|
Amount: 111,
|
||||||
|
Sequence: 222,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Outputs: []TxOutput{
|
||||||
|
TxOutput{
|
||||||
|
Address: []byte("output1"),
|
||||||
|
Amount: 333,
|
||||||
|
},
|
||||||
|
TxOutput{
|
||||||
|
Address: []byte("output2"),
|
||||||
|
Amount: 444,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
signBytes := sendTx.SignBytes(chainID)
|
||||||
|
signStr := string(signBytes)
|
||||||
|
expected := Fmt(`{"chain_id":"%s","tx":[1,{"inputs":[{"address":"696E70757431","amount":12345,"sequence":67890},{"address":"696E70757432","amount":111,"sequence":222}],"outputs":[{"address":"6F757470757431","amount":333},{"address":"6F757470757432","amount":444}]}]}`,
|
||||||
|
chainID)
|
||||||
|
if signStr != expected {
|
||||||
|
t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallTxSignable(t *testing.T) {
|
||||||
|
callTx := &CallTx{
|
||||||
|
Input: TxInput{
|
||||||
|
Address: []byte("input1"),
|
||||||
|
Amount: 12345,
|
||||||
|
Sequence: 67890,
|
||||||
|
},
|
||||||
|
Address: []byte("contract1"),
|
||||||
|
GasLimit: 111,
|
||||||
|
Fee: 222,
|
||||||
|
Data: []byte("data1"),
|
||||||
|
}
|
||||||
|
signBytes := callTx.SignBytes(chainID)
|
||||||
|
signStr := string(signBytes)
|
||||||
|
expected := Fmt(`{"chain_id":"%s","tx":[2,{"address":"636F6E747261637431","data":"6461746131","fee":222,"gas_limit":111,"input":{"address":"696E70757431","amount":12345,"sequence":67890}}]}`,
|
||||||
|
chainID)
|
||||||
|
if signStr != expected {
|
||||||
|
t.Errorf("Got unexpected sign string for CallTx. Expected:\n%v\nGot:\n%v", expected, signStr)
|
||||||
|
}
|
||||||
|
}
|
100
types/types.go
100
types/types.go
|
@ -1,100 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/tendermint/go-crypto"
|
|
||||||
"github.com/tendermint/go-wire"
|
|
||||||
gov "github.com/tendermint/governmint/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Input struct {
|
|
||||||
PubKey crypto.PubKey
|
|
||||||
Amount uint64
|
|
||||||
Sequence uint
|
|
||||||
Signature crypto.Signature
|
|
||||||
}
|
|
||||||
|
|
||||||
type Output struct {
|
|
||||||
PubKey crypto.PubKey
|
|
||||||
Amount uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type SendTx struct {
|
|
||||||
Inputs []Input
|
|
||||||
Outputs []Output
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *SendTx) SignBytes() []byte {
|
|
||||||
sigs := make([]crypto.Signature, len(tx.Inputs))
|
|
||||||
for i, input := range tx.Inputs {
|
|
||||||
sigs[i] = input.Signature
|
|
||||||
input.Signature = nil
|
|
||||||
tx.Inputs[i] = input
|
|
||||||
}
|
|
||||||
signBytes := wire.BinaryBytes(tx)
|
|
||||||
for i := range tx.Inputs {
|
|
||||||
tx.Inputs[i].Signature = sigs[i]
|
|
||||||
}
|
|
||||||
return signBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *SendTx) GetInputs() []Input { return tx.Inputs }
|
|
||||||
func (tx *SendTx) GetOutputs() []Output { return tx.Outputs }
|
|
||||||
|
|
||||||
type GovTx struct {
|
|
||||||
Input Input
|
|
||||||
Tx gov.Tx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *GovTx) SignBytes() []byte {
|
|
||||||
sig := tx.Input.Signature
|
|
||||||
tx.Input.Signature = nil
|
|
||||||
signBytes := wire.BinaryBytes(tx)
|
|
||||||
tx.Input.Signature = sig
|
|
||||||
return signBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *GovTx) GetInputs() []Input { return []Input{tx.Input} }
|
|
||||||
func (tx *GovTx) GetOutputs() []Output { return nil }
|
|
||||||
|
|
||||||
type Tx interface {
|
|
||||||
AssertIsTx()
|
|
||||||
SignBytes() []byte
|
|
||||||
GetInputs() []Input
|
|
||||||
GetOutputs() []Output
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_ *SendTx) AssertIsTx() {}
|
|
||||||
func (_ *GovTx) AssertIsTx() {}
|
|
||||||
|
|
||||||
const (
|
|
||||||
TxTypeSend = byte(0x01)
|
|
||||||
TxTypeGov = byte(0x02)
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = wire.RegisterInterface(
|
|
||||||
struct{ Tx }{},
|
|
||||||
wire.ConcreteType{&SendTx{}, TxTypeSend},
|
|
||||||
wire.ConcreteType{&GovTx{}, TxTypeGov},
|
|
||||||
)
|
|
||||||
|
|
||||||
//----------------------------------------
|
|
||||||
|
|
||||||
type Account struct {
|
|
||||||
Sequence uint
|
|
||||||
Balance uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type PubAccount struct {
|
|
||||||
crypto.PubKey
|
|
||||||
Account
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrivAccount struct {
|
|
||||||
crypto.PubKey
|
|
||||||
crypto.PrivKey
|
|
||||||
Account
|
|
||||||
}
|
|
||||||
|
|
||||||
type GenesisState struct {
|
|
||||||
Accounts []PubAccount
|
|
||||||
}
|
|
Loading…
Reference in New Issue