Coins and fees and gas...
This commit is contained in:
parent
f81718eea4
commit
a16b96062b
|
@ -13,7 +13,7 @@ import (
|
||||||
func ExecTx(s *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc events.Fireable) tmsp.Result {
|
func ExecTx(s *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc events.Fireable) tmsp.Result {
|
||||||
|
|
||||||
// TODO: do something with fees
|
// TODO: do something with fees
|
||||||
fees := int64(0)
|
fees := types.Coins{}
|
||||||
chainID := s.GetChainID()
|
chainID := s.GetChainID()
|
||||||
|
|
||||||
// Get the state. If isCheckTx, then we use a cache.
|
// Get the state. If isCheckTx, then we use a cache.
|
||||||
|
@ -50,11 +50,10 @@ func ExecTx(s *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc event
|
||||||
if res.IsErr() {
|
if res.IsErr() {
|
||||||
return res.PrependLog("in validateOutputs()")
|
return res.PrependLog("in validateOutputs()")
|
||||||
}
|
}
|
||||||
if outTotal > inTotal {
|
if !inTotal.IsEqual(outTotal.Plus(types.Coins{{"", tx.Fee}})) {
|
||||||
return tmsp.ErrBaseInsufficientFunds
|
return tmsp.ErrBaseInvalidOutput.AppendLog("Input total != output total + fees")
|
||||||
}
|
}
|
||||||
fee := inTotal - outTotal
|
fees = fees.Plus(types.Coins{{"", tx.Fee}})
|
||||||
fees += fee
|
|
||||||
|
|
||||||
// TODO: Fee validation for SendTx
|
// TODO: Fee validation for SendTx
|
||||||
|
|
||||||
|
@ -97,7 +96,7 @@ func ExecTx(s *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc event
|
||||||
log.Info(Fmt("validateInput failed on %X: %v", tx.Input.Address, res))
|
log.Info(Fmt("validateInput failed on %X: %v", tx.Input.Address, res))
|
||||||
return res.PrependLog("in validateInput()")
|
return res.PrependLog("in validateInput()")
|
||||||
}
|
}
|
||||||
if tx.Input.Amount < tx.Fee {
|
if !tx.Input.Coins.IsGTE(types.Coins{{"", tx.Fee}}) {
|
||||||
log.Info(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
|
log.Info(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
|
||||||
return tmsp.ErrBaseInsufficientFunds
|
return tmsp.ErrBaseInsufficientFunds
|
||||||
}
|
}
|
||||||
|
@ -110,9 +109,9 @@ func ExecTx(s *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc event
|
||||||
}
|
}
|
||||||
|
|
||||||
// Good!
|
// Good!
|
||||||
value := tx.Input.Amount - tx.Fee
|
coins := tx.Input.Coins.Minus(types.Coins{{"", tx.Fee}})
|
||||||
inAcc.Sequence += 1
|
inAcc.Sequence += 1
|
||||||
inAcc.Balance -= tx.Input.Amount
|
inAcc.Balance = inAcc.Balance.Minus(tx.Input.Coins)
|
||||||
|
|
||||||
// If this is a CheckTx, stop now.
|
// If this is a CheckTx, stop now.
|
||||||
if isCheckTx {
|
if isCheckTx {
|
||||||
|
@ -126,8 +125,7 @@ func ExecTx(s *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc event
|
||||||
// Run the tx.
|
// Run the tx.
|
||||||
cache := types.NewAccountCache(state)
|
cache := types.NewAccountCache(state)
|
||||||
cache.SetAccount(tx.Input.Address, inAcc)
|
cache.SetAccount(tx.Input.Address, inAcc)
|
||||||
gas := int64(1) // TODO
|
ctx := types.NewCallContext(cache, inAcc, coins)
|
||||||
ctx := types.NewCallContext(cache, inAcc, value, &gas)
|
|
||||||
res = plugin.RunTx(ctx, tx.Data)
|
res = plugin.RunTx(ctx, tx.Data)
|
||||||
if res.IsOK() {
|
if res.IsOK() {
|
||||||
cache.Sync()
|
cache.Sync()
|
||||||
|
@ -145,9 +143,10 @@ func ExecTx(s *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc event
|
||||||
*/
|
*/
|
||||||
} else {
|
} else {
|
||||||
log.Info("AppTx failed", "error", res)
|
log.Info("AppTx failed", "error", res)
|
||||||
// Just return the value and return.
|
// Just return the coins and return.
|
||||||
// TODO: return gas?
|
inAccCopy.Balance = inAccCopy.Balance.Plus(coins)
|
||||||
inAccCopy.Balance += value
|
// But take the gas
|
||||||
|
// TODO
|
||||||
state.SetAccount(tx.Input.Address, inAccCopy)
|
state.SetAccount(tx.Input.Address, inAccCopy)
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
|
@ -199,7 +198,6 @@ func getOrMakeOutputs(state types.AccountGetter, accounts map[string]*types.Acco
|
||||||
acc = &types.Account{
|
acc = &types.Account{
|
||||||
PubKey: nil,
|
PubKey: nil,
|
||||||
Sequence: 0,
|
Sequence: 0,
|
||||||
Balance: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
accounts[string(out.Address)] = acc
|
accounts[string(out.Address)] = acc
|
||||||
|
@ -229,8 +227,8 @@ func checkInputPubKey(address []byte, acc *types.Account, in types.TxInput) tmsp
|
||||||
return tmsp.OK
|
return tmsp.OK
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate inputs and compute total amount
|
// Validate inputs and compute total amount of coins
|
||||||
func validateInputs(accounts map[string]*types.Account, signBytes []byte, ins []types.TxInput) (total int64, res tmsp.Result) {
|
func validateInputs(accounts map[string]*types.Account, signBytes []byte, ins []types.TxInput) (total types.Coins, res tmsp.Result) {
|
||||||
|
|
||||||
for _, in := range ins {
|
for _, in := range ins {
|
||||||
acc := accounts[string(in.Address)]
|
acc := accounts[string(in.Address)]
|
||||||
|
@ -242,7 +240,7 @@ func validateInputs(accounts map[string]*types.Account, signBytes []byte, ins []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Good. Add amount to total
|
// Good. Add amount to total
|
||||||
total += in.Amount
|
total = total.Plus(in.Coins)
|
||||||
}
|
}
|
||||||
return total, tmsp.OK
|
return total, tmsp.OK
|
||||||
}
|
}
|
||||||
|
@ -252,13 +250,13 @@ func validateInput(acc *types.Account, signBytes []byte, in types.TxInput) (res
|
||||||
if res := in.ValidateBasic(); res.IsErr() {
|
if res := in.ValidateBasic(); res.IsErr() {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
// Check sequence/balance
|
// Check sequence/coins
|
||||||
seq, balance := acc.Sequence, acc.Balance
|
seq, balance := acc.Sequence, acc.Balance
|
||||||
if seq+1 != in.Sequence {
|
if seq+1 != in.Sequence {
|
||||||
return tmsp.ErrBaseInvalidSequence.AppendLog(Fmt("Got %v, expected %v. (acc.seq=%v)", in.Sequence, seq+1, acc.Sequence))
|
return tmsp.ErrBaseInvalidSequence.AppendLog(Fmt("Got %v, expected %v. (acc.seq=%v)", in.Sequence, seq+1, acc.Sequence))
|
||||||
}
|
}
|
||||||
// Check amount
|
// Check amount
|
||||||
if balance < in.Amount {
|
if !balance.IsGTE(in.Coins) {
|
||||||
return tmsp.ErrBaseInsufficientFunds
|
return tmsp.ErrBaseInsufficientFunds
|
||||||
}
|
}
|
||||||
// Check signatures
|
// Check signatures
|
||||||
|
@ -268,14 +266,14 @@ func validateInput(acc *types.Account, signBytes []byte, in types.TxInput) (res
|
||||||
return tmsp.OK
|
return tmsp.OK
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOutputs(outs []types.TxOutput) (total int64, res tmsp.Result) {
|
func validateOutputs(outs []types.TxOutput) (total types.Coins, res tmsp.Result) {
|
||||||
for _, out := range outs {
|
for _, out := range outs {
|
||||||
// Check TxOutput basic
|
// Check TxOutput basic
|
||||||
if res := out.ValidateBasic(); res.IsErr() {
|
if res := out.ValidateBasic(); res.IsErr() {
|
||||||
return 0, res
|
return nil, res
|
||||||
}
|
}
|
||||||
// Good. Add amount to total
|
// Good. Add amount to total
|
||||||
total += out.Amount
|
total = total.Plus(out.Coins)
|
||||||
}
|
}
|
||||||
return total, tmsp.OK
|
return total, tmsp.OK
|
||||||
}
|
}
|
||||||
|
@ -286,10 +284,10 @@ func adjustByInputs(state types.AccountSetter, accounts map[string]*types.Accoun
|
||||||
if acc == nil {
|
if acc == nil {
|
||||||
PanicSanity("adjustByInputs() expects account in accounts")
|
PanicSanity("adjustByInputs() expects account in accounts")
|
||||||
}
|
}
|
||||||
if acc.Balance < in.Amount {
|
if !acc.Balance.IsGTE(in.Coins) {
|
||||||
PanicSanity("adjustByInputs() expects sufficient funds")
|
PanicSanity("adjustByInputs() expects sufficient funds")
|
||||||
}
|
}
|
||||||
acc.Balance -= in.Amount
|
acc.Balance = acc.Balance.Minus(in.Coins)
|
||||||
acc.Sequence += 1
|
acc.Sequence += 1
|
||||||
state.SetAccount(in.Address, acc)
|
state.SetAccount(in.Address, acc)
|
||||||
}
|
}
|
||||||
|
@ -301,7 +299,7 @@ func adjustByOutputs(state types.AccountSetter, accounts map[string]*types.Accou
|
||||||
if acc == nil {
|
if acc == nil {
|
||||||
PanicSanity("adjustByOutputs() expects account in accounts")
|
PanicSanity("adjustByOutputs() expects account in accounts")
|
||||||
}
|
}
|
||||||
acc.Balance += out.Amount
|
acc.Balance = acc.Balance.Plus(out.Coins)
|
||||||
if !isCheckTx {
|
if !isCheckTx {
|
||||||
state.SetAccount(out.Address, acc)
|
state.SetAccount(out.Address, acc)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ func PrivAccountFromSecret(secret string) types.PrivAccount {
|
||||||
Account: types.Account{
|
Account: types.Account{
|
||||||
PubKey: privKey.PubKey(),
|
PubKey: privKey.PubKey(),
|
||||||
Sequence: 0,
|
Sequence: 0,
|
||||||
Balance: 0,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return privAccount
|
return privAccount
|
||||||
|
@ -38,7 +37,7 @@ func RandAccounts(num int, minAmount int64, maxAmount int64) []types.PrivAccount
|
||||||
Account: types.Account{
|
Account: types.Account{
|
||||||
PubKey: pubKey,
|
PubKey: pubKey,
|
||||||
Sequence: 0,
|
Sequence: 0,
|
||||||
Balance: balance,
|
Balance: types.Coins{types.Coin{"", balance}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,14 +51,14 @@ func main() {
|
||||||
types.TxInput{
|
types.TxInput{
|
||||||
Address: root.Account.PubKey.Address(),
|
Address: root.Account.PubKey.Address(),
|
||||||
PubKey: root.Account.PubKey, // TODO is this needed?
|
PubKey: root.Account.PubKey, // TODO is this needed?
|
||||||
Amount: 1000002,
|
Coins: types.Coins{{"", 1000002}},
|
||||||
Sequence: sequence,
|
Sequence: sequence,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Outputs: []types.TxOutput{
|
Outputs: []types.TxOutput{
|
||||||
types.TxOutput{
|
types.TxOutput{
|
||||||
Address: privAccount.Account.PubKey.Address(),
|
Address: privAccount.Account.PubKey.Address(),
|
||||||
Amount: 1000000,
|
Coins: types.Coins{{"", 1000000}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -102,14 +102,14 @@ func main() {
|
||||||
types.TxInput{
|
types.TxInput{
|
||||||
Address: privAccountA.Account.PubKey.Address(),
|
Address: privAccountA.Account.PubKey.Address(),
|
||||||
PubKey: privAccountA.Account.PubKey,
|
PubKey: privAccountA.Account.PubKey,
|
||||||
Amount: 3,
|
Coins: types.Coins{{"", 3}},
|
||||||
Sequence: privAccountASequence + 1,
|
Sequence: privAccountASequence + 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Outputs: []types.TxOutput{
|
Outputs: []types.TxOutput{
|
||||||
types.TxOutput{
|
types.TxOutput{
|
||||||
Address: privAccountB.Account.PubKey.Address(),
|
Address: privAccountB.Account.PubKey.Address(),
|
||||||
Amount: 1,
|
Coins: types.Coins{{"", 1}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,24 +28,26 @@ func testSendTx() {
|
||||||
|
|
||||||
// Seed Basecoin with account
|
// Seed Basecoin with account
|
||||||
tAcc := tPriv.Account
|
tAcc := tPriv.Account
|
||||||
tAcc.Balance = 1000
|
tAcc.Balance = types.Coins{{"", 1000}}
|
||||||
fmt.Println(bcApp.SetOption("base/chainID", "test_chain_id"))
|
fmt.Println(bcApp.SetOption("base/chainID", "test_chain_id"))
|
||||||
fmt.Println(bcApp.SetOption("base/account", string(wire.JSONBytes(tAcc))))
|
fmt.Println(bcApp.SetOption("base/account", string(wire.JSONBytes(tAcc))))
|
||||||
|
|
||||||
// Construct a SendTx signature
|
// Construct a SendTx signature
|
||||||
tx := &types.SendTx{
|
tx := &types.SendTx{
|
||||||
|
Fee: 0,
|
||||||
|
Gas: 0,
|
||||||
Inputs: []types.TxInput{
|
Inputs: []types.TxInput{
|
||||||
types.TxInput{
|
types.TxInput{
|
||||||
Address: tPriv.Account.PubKey.Address(),
|
Address: tPriv.Account.PubKey.Address(),
|
||||||
PubKey: tPriv.Account.PubKey, // TODO is this needed?
|
PubKey: tPriv.Account.PubKey, // TODO is this needed?
|
||||||
Amount: 1,
|
Coins: types.Coins{{"", 1}},
|
||||||
Sequence: 1,
|
Sequence: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Outputs: []types.TxOutput{
|
Outputs: []types.TxOutput{
|
||||||
types.TxOutput{
|
types.TxOutput{
|
||||||
Address: tPriv2.Account.PubKey.Address(),
|
Address: tPriv2.Account.PubKey.Address(),
|
||||||
Amount: 1,
|
Coins: types.Coins{{"", 1}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -95,7 +97,7 @@ func testSequence() {
|
||||||
// Get the root account
|
// Get the root account
|
||||||
root := tests.PrivAccountFromSecret("test")
|
root := tests.PrivAccountFromSecret("test")
|
||||||
rootAcc := root.Account
|
rootAcc := root.Account
|
||||||
rootAcc.Balance = 1 << 53
|
rootAcc.Balance = types.Coins{{"", 1 << 53}}
|
||||||
fmt.Println(bcApp.SetOption("base/chainID", "test_chain_id"))
|
fmt.Println(bcApp.SetOption("base/chainID", "test_chain_id"))
|
||||||
fmt.Println(bcApp.SetOption("base/account", string(wire.JSONBytes(rootAcc))))
|
fmt.Println(bcApp.SetOption("base/account", string(wire.JSONBytes(rootAcc))))
|
||||||
|
|
||||||
|
@ -108,18 +110,20 @@ func testSequence() {
|
||||||
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{
|
||||||
|
Fee: 2,
|
||||||
|
Gas: 2,
|
||||||
Inputs: []types.TxInput{
|
Inputs: []types.TxInput{
|
||||||
types.TxInput{
|
types.TxInput{
|
||||||
Address: root.Account.PubKey.Address(),
|
Address: root.Account.PubKey.Address(),
|
||||||
PubKey: root.Account.PubKey, // TODO is this needed?
|
PubKey: root.Account.PubKey, // TODO is this needed?
|
||||||
Amount: 1000002,
|
Coins: types.Coins{{"", 1000002}},
|
||||||
Sequence: sequence,
|
Sequence: sequence,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Outputs: []types.TxOutput{
|
Outputs: []types.TxOutput{
|
||||||
types.TxOutput{
|
types.TxOutput{
|
||||||
Address: privAccount.Account.PubKey.Address(),
|
Address: privAccount.Account.PubKey.Address(),
|
||||||
Amount: 1000000,
|
Coins: types.Coins{{"", 1000000}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -155,18 +159,20 @@ func testSequence() {
|
||||||
privAccountB := privAccounts[randB]
|
privAccountB := privAccounts[randB]
|
||||||
|
|
||||||
tx := &types.SendTx{
|
tx := &types.SendTx{
|
||||||
|
Fee: 2,
|
||||||
|
Gas: 2,
|
||||||
Inputs: []types.TxInput{
|
Inputs: []types.TxInput{
|
||||||
types.TxInput{
|
types.TxInput{
|
||||||
Address: privAccountA.Account.PubKey.Address(),
|
Address: privAccountA.Account.PubKey.Address(),
|
||||||
PubKey: privAccountA.Account.PubKey,
|
PubKey: privAccountA.Account.PubKey,
|
||||||
Amount: 3,
|
Coins: types.Coins{{"", 3}},
|
||||||
Sequence: privAccountASequence + 1,
|
Sequence: privAccountASequence + 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Outputs: []types.TxOutput{
|
Outputs: []types.TxOutput{
|
||||||
types.TxOutput{
|
types.TxOutput{
|
||||||
Address: privAccountB.Account.PubKey.Address(),
|
Address: privAccountB.Account.PubKey.Address(),
|
||||||
Amount: 1,
|
Coins: types.Coins{{"", 1}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
type Account struct {
|
type Account struct {
|
||||||
PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known.
|
PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known.
|
||||||
Sequence int `json:"sequence"`
|
Sequence int `json:"sequence"`
|
||||||
Balance int64 `json:"balance"`
|
Balance Coins `json:"coins"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (acc *Account) Copy() *Account {
|
func (acc *Account) Copy() *Account {
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Coin struct {
|
||||||
|
Denom string `json:"denom"`
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (coin Coin) String() string {
|
||||||
|
return fmt.Sprintf("(%v %v)",
|
||||||
|
coin.Denom, coin.Amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------
|
||||||
|
|
||||||
|
type Coins []Coin
|
||||||
|
|
||||||
|
// Must be sorted, and not have 0 amounts
|
||||||
|
func (coins Coins) IsValid() bool {
|
||||||
|
switch len(coins) {
|
||||||
|
case 0:
|
||||||
|
return true
|
||||||
|
case 1:
|
||||||
|
return coins[0].Amount != 0
|
||||||
|
default:
|
||||||
|
lowDenom := coins[0].Denom
|
||||||
|
for _, coin := range coins[1:] {
|
||||||
|
if coin.Denom <= lowDenom {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if coin.Amount == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (coinsA Coins) Plus(coinsB Coins) Coins {
|
||||||
|
sum := []Coin{}
|
||||||
|
indexA, indexB := 0, 0
|
||||||
|
lenA, lenB := len(coinsA), len(coinsB)
|
||||||
|
for {
|
||||||
|
if indexA == lenA {
|
||||||
|
if indexB == lenB {
|
||||||
|
return sum
|
||||||
|
} else {
|
||||||
|
return append(sum, coinsB[indexB:]...)
|
||||||
|
}
|
||||||
|
} else if indexB == lenB {
|
||||||
|
return append(sum, coinsA[indexA:]...)
|
||||||
|
}
|
||||||
|
coinA, coinB := coinsA[indexA], coinsB[indexB]
|
||||||
|
switch strings.Compare(coinA.Denom, coinB.Denom) {
|
||||||
|
case -1:
|
||||||
|
sum = append(sum, coinA)
|
||||||
|
indexA += 1
|
||||||
|
case 0:
|
||||||
|
if coinA.Amount+coinB.Amount == 0 {
|
||||||
|
// ignore 0 sum coin type
|
||||||
|
} else {
|
||||||
|
sum = append(sum, Coin{
|
||||||
|
Denom: coinA.Denom,
|
||||||
|
Amount: coinA.Amount + coinB.Amount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
indexA += 1
|
||||||
|
indexB += 1
|
||||||
|
case 1:
|
||||||
|
sum = append(sum, coinB)
|
||||||
|
indexB += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
func (coins Coins) Negative() Coins {
|
||||||
|
res := make([]Coin, 0, len(coins))
|
||||||
|
for _, coin := range coins {
|
||||||
|
res = append(res, Coin{
|
||||||
|
Denom: coin.Denom,
|
||||||
|
Amount: -coin.Amount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (coinsA Coins) Minus(coinsB Coins) Coins {
|
||||||
|
return coinsA.Plus(coinsB.Negative())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (coinsA Coins) IsGTE(coinsB Coins) bool {
|
||||||
|
diff := coinsA.Minus(coinsB)
|
||||||
|
if len(diff) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return diff.IsPositive()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (coins Coins) IsZero() bool {
|
||||||
|
return len(coins) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (coinsA Coins) IsEqual(coinsB Coins) bool {
|
||||||
|
if len(coinsA) != len(coinsB) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < len(coinsA); i++ {
|
||||||
|
if coinsA[i] != coinsB[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (coins Coins) IsPositive() bool {
|
||||||
|
if len(coins) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, coinAmount := range coins {
|
||||||
|
if coinAmount.Amount <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCoins(t *testing.T) {
|
||||||
|
coins := Coins{
|
||||||
|
Coin{"GAS", 1},
|
||||||
|
Coin{"MINERAL", 1},
|
||||||
|
Coin{"TREE", 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !coins.IsValid() {
|
||||||
|
t.Fatal("Coins are valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !coins.IsPositive() {
|
||||||
|
t.Fatalf("Expected coins to be positive: %v", coins)
|
||||||
|
}
|
||||||
|
|
||||||
|
negCoins := coins.Negative()
|
||||||
|
if negCoins.IsPositive() {
|
||||||
|
t.Fatalf("Expected neg coins to not be positive: %v", negCoins)
|
||||||
|
}
|
||||||
|
|
||||||
|
sumCoins := coins.Plus(negCoins)
|
||||||
|
if len(sumCoins) != 0 {
|
||||||
|
t.Fatal("Expected 0 coins")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCoinsBadSort(t *testing.T) {
|
||||||
|
coins := Coins{
|
||||||
|
Coin{"TREE", 1},
|
||||||
|
Coin{"GAS", 1},
|
||||||
|
Coin{"MINERAL", 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
if coins.IsValid() {
|
||||||
|
t.Fatal("Coins are not sorted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCoinsBadAmount(t *testing.T) {
|
||||||
|
coins := Coins{
|
||||||
|
Coin{"GAS", 1},
|
||||||
|
Coin{"TREE", 0},
|
||||||
|
Coin{"MINERAL", 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
if coins.IsValid() {
|
||||||
|
t.Fatal("Coins cannot include 0 amounts")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCoinsDuplicate(t *testing.T) {
|
||||||
|
coins := Coins{
|
||||||
|
Coin{"GAS", 1},
|
||||||
|
Coin{"GAS", 1},
|
||||||
|
Coin{"MINERAL", 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
if coins.IsValid() {
|
||||||
|
t.Fatal("Duplicate coin")
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,8 +5,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Value is any floating value. It must be given to someone.
|
// Value is any floating value. It must be given to someone.
|
||||||
// Gas is a pointer to remainig gas. Decrement as you go,
|
|
||||||
// if any gas is left the user is
|
|
||||||
type Plugin interface {
|
type Plugin interface {
|
||||||
SetOption(key string, value string) (log string)
|
SetOption(key string, value string) (log string)
|
||||||
RunTx(ctx CallContext, txBytes []byte) (res tmsp.Result)
|
RunTx(ctx CallContext, txBytes []byte) (res tmsp.Result)
|
||||||
|
@ -25,16 +23,14 @@ type NamedPlugin struct {
|
||||||
type CallContext struct {
|
type CallContext struct {
|
||||||
Cache AccountCacher
|
Cache AccountCacher
|
||||||
Caller *Account
|
Caller *Account
|
||||||
Value int64
|
Coins Coins
|
||||||
Gas *int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCallContext(cache AccountCacher, caller *Account, value int64, gas *int64) CallContext {
|
func NewCallContext(cache AccountCacher, caller *Account, coins Coins) CallContext {
|
||||||
return CallContext{
|
return CallContext{
|
||||||
Cache: cache,
|
Cache: cache,
|
||||||
Caller: caller,
|
Caller: caller,
|
||||||
Value: value,
|
Coins: coins,
|
||||||
Gas: gas,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
40
types/tx.go
40
types/tx.go
|
@ -42,7 +42,7 @@ var _ = wire.RegisterInterface(
|
||||||
|
|
||||||
type TxInput struct {
|
type TxInput struct {
|
||||||
Address []byte `json:"address"` // Hash of the PubKey
|
Address []byte `json:"address"` // Hash of the PubKey
|
||||||
Amount int64 `json:"amount"` // Must not exceed account balance
|
Coins Coins `json:"coins"` //
|
||||||
Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput
|
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
|
Signature crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx
|
||||||
PubKey crypto.PubKey `json:"pub_key"` // May be nil
|
PubKey crypto.PubKey `json:"pub_key"` // May be nil
|
||||||
|
@ -50,42 +50,50 @@ type TxInput struct {
|
||||||
|
|
||||||
func (txIn TxInput) ValidateBasic() tmsp.Result {
|
func (txIn TxInput) ValidateBasic() tmsp.Result {
|
||||||
if len(txIn.Address) != 20 {
|
if len(txIn.Address) != 20 {
|
||||||
return tmsp.ErrBaseInvalidAddress.AppendLog("(in TxInput)")
|
return tmsp.ErrBaseInvalidInput.AppendLog("Invalid address length")
|
||||||
}
|
}
|
||||||
if txIn.Amount == 0 {
|
if !txIn.Coins.IsValid() {
|
||||||
return tmsp.ErrBaseInvalidAmount.AppendLog("(in TxInput)")
|
return tmsp.ErrBaseInvalidInput.AppendLog(Fmt("Invalid coins %v", txIn.Coins))
|
||||||
|
}
|
||||||
|
if txIn.Coins.IsZero() {
|
||||||
|
return tmsp.ErrBaseInvalidInput.AppendLog("Coins cannot be zero")
|
||||||
}
|
}
|
||||||
return tmsp.OK
|
return tmsp.OK
|
||||||
}
|
}
|
||||||
|
|
||||||
func (txIn TxInput) String() string {
|
func (txIn TxInput) String() string {
|
||||||
return Fmt("TxInput{%X,%v,%v,%v,%v}", txIn.Address, txIn.Amount, txIn.Sequence, txIn.Signature, txIn.PubKey)
|
return Fmt("TxInput{%X,%v,%v,%v,%v}", txIn.Address, txIn.Coins, txIn.Sequence, txIn.Signature, txIn.PubKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
type TxOutput struct {
|
type TxOutput struct {
|
||||||
Address []byte `json:"address"` // Hash of the PubKey
|
Address []byte `json:"address"` // Hash of the PubKey
|
||||||
Amount int64 `json:"amount"` // The sum of all outputs must not exceed the inputs.
|
Coins Coins `json:"coins"` //
|
||||||
}
|
}
|
||||||
|
|
||||||
func (txOut TxOutput) ValidateBasic() tmsp.Result {
|
func (txOut TxOutput) ValidateBasic() tmsp.Result {
|
||||||
if len(txOut.Address) != 20 {
|
if len(txOut.Address) != 20 {
|
||||||
return tmsp.ErrBaseInvalidAddress.AppendLog("(in TxOutput)")
|
return tmsp.ErrBaseInvalidOutput.AppendLog("Invalid address length")
|
||||||
}
|
}
|
||||||
if txOut.Amount == 0 {
|
if !txOut.Coins.IsValid() {
|
||||||
return tmsp.ErrBaseInvalidAmount.AppendLog("(in TxOutput)")
|
return tmsp.ErrBaseInvalidOutput.AppendLog(Fmt("Invalid coins %v", txOut.Coins))
|
||||||
|
}
|
||||||
|
if txOut.Coins.IsZero() {
|
||||||
|
return tmsp.ErrBaseInvalidOutput.AppendLog("Coins cannot be zero")
|
||||||
}
|
}
|
||||||
return tmsp.OK
|
return tmsp.OK
|
||||||
}
|
}
|
||||||
|
|
||||||
func (txOut TxOutput) String() string {
|
func (txOut TxOutput) String() string {
|
||||||
return Fmt("TxOutput{%X,%v}", txOut.Address, txOut.Amount)
|
return Fmt("TxOutput{%X,%v}", txOut.Address, txOut.Coins)
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
type SendTx struct {
|
type SendTx struct {
|
||||||
|
Fee int64 `json:"fee"` // Fee
|
||||||
|
Gas int64 `json:"gas"` // Gas
|
||||||
Inputs []TxInput `json:"inputs"`
|
Inputs []TxInput `json:"inputs"`
|
||||||
Outputs []TxOutput `json:"outputs"`
|
Outputs []TxOutput `json:"outputs"`
|
||||||
}
|
}
|
||||||
|
@ -105,16 +113,16 @@ func (tx *SendTx) SignBytes(chainID string) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *SendTx) String() string {
|
func (tx *SendTx) String() string {
|
||||||
return Fmt("SendTx{%v -> %v}", tx.Inputs, tx.Outputs)
|
return Fmt("SendTx{%v/%v %v->%v}", tx.Fee, tx.Gas, tx.Inputs, tx.Outputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
type AppTx struct {
|
type AppTx struct {
|
||||||
Type byte `json:"type"` // Which app
|
Fee int64 `json:"fee"` // Fee
|
||||||
Gas int64 `json:"gas"`
|
Gas int64 `json:"gas"` // Gas
|
||||||
Fee int64 `json:"fee"`
|
Type byte `json:"type"` // Which app
|
||||||
Input TxInput `json:"input"`
|
Input TxInput `json:"input"` // Hmmm do we want coins?
|
||||||
Data []byte `json:"data"`
|
Data []byte `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +136,7 @@ func (tx *AppTx) SignBytes(chainID string) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *AppTx) String() string {
|
func (tx *AppTx) String() string {
|
||||||
return Fmt("AppTx{%v %v %v %v -> %X}", tx.Type, tx.Gas, tx.Fee, tx.Input, tx.Data)
|
return Fmt("AppTx{%v/%v %v %v %X}", tx.Fee, tx.Gas, tx.Type, tx.Input, tx.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
|
@ -10,32 +10,34 @@ var chainID string = "test_chain"
|
||||||
|
|
||||||
func TestSendTxSignable(t *testing.T) {
|
func TestSendTxSignable(t *testing.T) {
|
||||||
sendTx := &SendTx{
|
sendTx := &SendTx{
|
||||||
|
Fee: 111,
|
||||||
|
Gas: 222,
|
||||||
Inputs: []TxInput{
|
Inputs: []TxInput{
|
||||||
TxInput{
|
TxInput{
|
||||||
Address: []byte("input1"),
|
Address: []byte("input1"),
|
||||||
Amount: 12345,
|
Coins: Coins{{"", 12345}},
|
||||||
Sequence: 67890,
|
Sequence: 67890,
|
||||||
},
|
},
|
||||||
TxInput{
|
TxInput{
|
||||||
Address: []byte("input2"),
|
Address: []byte("input2"),
|
||||||
Amount: 111,
|
Coins: Coins{{"", 111}},
|
||||||
Sequence: 222,
|
Sequence: 222,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Outputs: []TxOutput{
|
Outputs: []TxOutput{
|
||||||
TxOutput{
|
TxOutput{
|
||||||
Address: []byte("output1"),
|
Address: []byte("output1"),
|
||||||
Amount: 333,
|
Coins: Coins{{"", 333}},
|
||||||
},
|
},
|
||||||
TxOutput{
|
TxOutput{
|
||||||
Address: []byte("output2"),
|
Address: []byte("output2"),
|
||||||
Amount: 444,
|
Coins: Coins{{"", 444}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
signBytes := sendTx.SignBytes(chainID)
|
signBytes := sendTx.SignBytes(chainID)
|
||||||
signBytesHex := Fmt("%X", signBytes)
|
signBytesHex := Fmt("%X", signBytes)
|
||||||
expected := "010A746573745F636861696E0101020106696E7075743100000000000030390301093200000106696E70757432000000000000006F01DE0000010201076F757470757431000000000000014D01076F75747075743200000000000001BC"
|
expected := "010A746573745F636861696E01000000000000006F00000000000000DE01020106696E7075743101010000000000000030390301093200000106696E70757432010100000000000000006F01DE0000010201076F757470757431010100000000000000014D01076F75747075743201010000000000000001BC"
|
||||||
if signBytesHex != expected {
|
if signBytesHex != expected {
|
||||||
t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signBytesHex)
|
t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signBytesHex)
|
||||||
}
|
}
|
||||||
|
@ -43,19 +45,19 @@ func TestSendTxSignable(t *testing.T) {
|
||||||
|
|
||||||
func TestAppTxSignable(t *testing.T) {
|
func TestAppTxSignable(t *testing.T) {
|
||||||
callTx := &AppTx{
|
callTx := &AppTx{
|
||||||
|
Fee: 111,
|
||||||
|
Gas: 222,
|
||||||
Type: 0x01,
|
Type: 0x01,
|
||||||
Gas: 111,
|
|
||||||
Fee: 222,
|
|
||||||
Input: TxInput{
|
Input: TxInput{
|
||||||
Address: []byte("input1"),
|
Address: []byte("input1"),
|
||||||
Amount: 12345,
|
Coins: Coins{{"", 12345}},
|
||||||
Sequence: 67890,
|
Sequence: 67890,
|
||||||
},
|
},
|
||||||
Data: []byte("data1"),
|
Data: []byte("data1"),
|
||||||
}
|
}
|
||||||
signBytes := callTx.SignBytes(chainID)
|
signBytes := callTx.SignBytes(chainID)
|
||||||
signBytesHex := Fmt("%X", signBytes)
|
signBytesHex := Fmt("%X", signBytes)
|
||||||
expected := "010A746573745F636861696E0101000000000000006F00000000000000DE0106696E70757431000000000000303903010932000001056461746131"
|
expected := "010A746573745F636861696E01000000000000006F00000000000000DE010106696E70757431010100000000000000303903010932000001056461746131"
|
||||||
if signBytesHex != expected {
|
if signBytesHex != expected {
|
||||||
t.Errorf("Got unexpected sign string for AppTx. Expected:\n%v\nGot:\n%v", expected, signBytesHex)
|
t.Errorf("Got unexpected sign string for AppTx. Expected:\n%v\nGot:\n%v", expected, signBytesHex)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue