Split validation into basic/advanced

This commit is contained in:
Jae Kwon 2016-04-18 08:09:19 -07:00
parent bc78a2d272
commit ce2b8904d6
5 changed files with 188 additions and 111 deletions

View File

@ -16,13 +16,13 @@ const (
version = "0.1" version = "0.1"
maxTxSize = 10240 maxTxSize = 10240
typeByteBase = 0x01 PluginTypeByteBase = 0x01
typeByteEyes = 0x02 PluginTypeByteEyes = 0x02
typeByteGov = 0x03 PluginTypeByteGov = 0x03
pluginNameBase = "base" PluginNameBase = "base"
pluginNameEyes = "eyes" PluginNameEyes = "eyes"
pluginNameGov = "gov" PluginNameGov = "gov"
) )
type Basecoin struct { type Basecoin struct {
@ -36,7 +36,7 @@ func NewBasecoin(eyesCli *eyes.Client) *Basecoin {
govMint := gov.NewGovernmint(eyesCli) govMint := gov.NewGovernmint(eyesCli)
state_ := state.NewState(eyesCli) state_ := state.NewState(eyesCli)
plugins := types.NewPlugins() plugins := types.NewPlugins()
plugins.RegisterPlugin(typeByteGov, pluginNameGov, govMint) // TODO: make constants plugins.RegisterPlugin(PluginTypeByteGov, PluginNameGov, govMint)
return &Basecoin{ return &Basecoin{
eyesCli: eyesCli, eyesCli: eyesCli,
govMint: govMint, govMint: govMint,
@ -52,12 +52,12 @@ func (app *Basecoin) Info() string {
// TMSP::SetOption // TMSP::SetOption
func (app *Basecoin) SetOption(key string, value string) (log string) { func (app *Basecoin) SetOption(key string, value string) (log string) {
pluginName, key := splitKey(key) PluginName, key := splitKey(key)
if pluginName != pluginNameBase { if PluginName != PluginNameBase {
// Set option on plugin // Set option on plugin
plugin := app.plugins.GetByName(pluginName) plugin := app.plugins.GetByName(PluginName)
if plugin == nil { if plugin == nil {
return "Invalid plugin name: " + pluginName return "Invalid plugin name: " + PluginName
} }
return plugin.SetOption(key, value) return plugin.SetOption(key, value)
} else { } else {
@ -126,11 +126,11 @@ func (app *Basecoin) Query(query []byte) (res tmsp.Result) {
typeByte := query[0] typeByte := query[0]
query = query[1:] query = query[1:]
switch typeByte { switch typeByte {
case typeByteBase: case PluginTypeByteBase:
return tmsp.OK.SetLog("This type of query not yet supported") return tmsp.OK.SetLog("This type of query not yet supported")
case typeByteEyes: case PluginTypeByteEyes:
return app.eyesCli.QuerySync(query) return app.eyesCli.QuerySync(query)
case typeByteGov: case PluginTypeByteGov:
return app.govMint.Query(query) return app.govMint.Query(query)
} }
return tmsp.ErrBaseUnknownPlugin.SetLog( return tmsp.ErrBaseUnknownPlugin.SetLog(
@ -156,20 +156,35 @@ func (app *Basecoin) Commit() (res tmsp.Result) {
// TMSP::InitChain // TMSP::InitChain
func (app *Basecoin) InitChain(validators []*tmsp.Validator) { func (app *Basecoin) InitChain(validators []*tmsp.Validator) {
app.govMint.InitChain(validators) for _, plugin := range app.plugins.GetList() {
if _, ok := plugin.Plugin.(tmsp.BlockchainAware); ok {
plugin.Plugin.(tmsp.BlockchainAware).InitChain(validators)
}
}
} }
// TMSP::BeginBlock // TMSP::BeginBlock
func (app *Basecoin) BeginBlock(height uint64) { func (app *Basecoin) BeginBlock(height uint64) {
// app.govMint.BeginBlock(height) app.state.ResetCacheState()
// TODO other plugins? for _, plugin := range app.plugins.GetList() {
if _, ok := plugin.Plugin.(tmsp.BlockchainAware); ok {
plugin.Plugin.(tmsp.BlockchainAware).BeginBlock(height)
}
}
} }
// TMSP::EndBlock // TMSP::EndBlock
func (app *Basecoin) EndBlock(height uint64) []*tmsp.Validator { func (app *Basecoin) EndBlock(height uint64) (vals []*tmsp.Validator) {
app.state.ResetCacheState() for _, plugin := range app.plugins.GetList() {
return app.govMint.EndBlock(height) if plugin.Plugin == app.govMint {
// TODO other plugins? vals = plugin.Plugin.(tmsp.BlockchainAware).EndBlock(height)
} else {
if _, ok := plugin.Plugin.(tmsp.BlockchainAware); ok {
plugin.Plugin.(tmsp.BlockchainAware).EndBlock(height)
}
}
}
return
} }
//---------------------------------------- //----------------------------------------

BIN
state/.execution.go.swo Normal file

Binary file not shown.

View File

@ -1,8 +1,6 @@
package state package state
import ( import (
"bytes"
"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"
@ -28,28 +26,35 @@ func ExecTx(s *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc event
// Exec tx // Exec tx
switch tx := tx.(type) { switch tx := tx.(type) {
case *types.SendTx: case *types.SendTx:
// First, get inputs // Validate inputs and outputs, basic
res := validateInputsBasic(tx.Inputs)
if res.IsErr() {
return res.PrependLog("in validateInputsBasic()")
}
res = validateOutputsBasic(tx.Outputs)
if res.IsErr() {
return res.PrependLog("in validateOutputsBasic()")
}
// Get inputs
accounts, res := getInputs(state, tx.Inputs) accounts, res := getInputs(state, tx.Inputs)
if res.IsErr() { if res.IsErr() {
return res.PrependLog("in getInputs()") return res.PrependLog("in getInputs()")
} }
// Then, get or make outputs. // Get or make outputs.
accounts, res = getOrMakeOutputs(state, accounts, tx.Outputs) accounts, res = getOrMakeOutputs(state, accounts, tx.Outputs)
if res.IsErr() { if res.IsErr() {
return res.PrependLog("in getOrMakeOutputs()") return res.PrependLog("in getOrMakeOutputs()")
} }
// Validate inputs and outputs // Validate inputs and outputs, advanced
signBytes := tx.SignBytes(chainID) signBytes := tx.SignBytes(chainID)
inTotal, res := validateInputs(accounts, signBytes, tx.Inputs) inTotal, res := validateInputsAdvanced(accounts, signBytes, tx.Inputs)
if res.IsErr() { if res.IsErr() {
return res.PrependLog("in validateInputs()") return res.PrependLog("in validateInputsAdvanced()")
}
outTotal, res := validateOutputs(tx.Outputs)
if res.IsErr() {
return res.PrependLog("in validateOutputs()")
} }
outTotal := sumOutputs(tx.Outputs)
if !inTotal.IsEqual(outTotal.Plus(types.Coins{{"", tx.Fee}})) { if !inTotal.IsEqual(outTotal.Plus(types.Coins{{"", tx.Fee}})) {
return tmsp.ErrBaseInvalidOutput.AppendLog("Input total != output total + fees") return tmsp.ErrBaseInvalidOutput.AppendLog("Input total != output total + fees")
} }
@ -78,23 +83,27 @@ func ExecTx(s *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc event
return tmsp.OK return tmsp.OK
case *types.AppTx: case *types.AppTx:
// First, get input account // Validate input, basic
res := tx.Input.ValidateBasic()
if res.IsErr() {
return res
}
// Get input account
inAcc := state.GetAccount(tx.Input.Address) inAcc := state.GetAccount(tx.Input.Address)
if inAcc == nil { if inAcc == nil {
return tmsp.ErrBaseUnknownAddress return tmsp.ErrBaseUnknownAddress
} }
if tx.Input.PubKey != nil {
// Validate input inAcc.PubKey = tx.Input.PubKey
// pubKey should be present in either "inAcc" or "tx.Input"
if res := checkInputPubKey(tx.Input.Address, inAcc, tx.Input); res.IsErr() {
log.Info(Fmt("Can't find pubkey for %X", tx.Input.Address))
return res
} }
// Validate input, advanced
signBytes := tx.SignBytes(chainID) signBytes := tx.SignBytes(chainID)
res := validateInput(inAcc, signBytes, tx.Input) res = validateInputAdvanced(inAcc, signBytes, tx.Input)
if res.IsErr() { if res.IsErr() {
log.Info(Fmt("validateInput failed on %X: %v", tx.Input.Address, res)) log.Info(Fmt("validateInputAdvanced failed on %X: %v", tx.Input.Address, res))
return res.PrependLog("in validateInput()") return res.PrependLog("in validateInputAdvanced()")
} }
if !tx.Input.Coins.IsGTE(types.Coins{{"", 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))
@ -160,8 +169,7 @@ func ExecTx(s *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc event
// 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.
// the TxInput is modified and input.PubKey set to nil.
func getInputs(state types.AccountGetter, ins []types.TxInput) (map[string]*types.Account, tmsp.Result) { 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 {
@ -173,9 +181,8 @@ func getInputs(state types.AccountGetter, ins []types.TxInput) (map[string]*type
if acc == nil { if acc == nil {
return nil, tmsp.ErrBaseUnknownAddress return nil, tmsp.ErrBaseUnknownAddress
} }
// PubKey should be present in either "account" or "in" if in.PubKey != nil {
if res := checkInputPubKey(in.Address, acc, in); res.IsErr() { acc.PubKey = in.PubKey
return nil, res
} }
accounts[string(in.Address)] = acc accounts[string(in.Address)] = acc
} }
@ -205,37 +212,25 @@ func getOrMakeOutputs(state types.AccountGetter, accounts map[string]*types.Acco
return accounts, tmsp.OK return accounts, tmsp.OK
} }
// Input must not have a redundant PubKey (i.e. Account already has PubKey). // Validate inputs basic structure
// NOTE: Account has PubKey if Sequence > 0 func validateInputsBasic(ins []types.TxInput) (res tmsp.Result) {
func checkInputPubKey(address []byte, acc *types.Account, in types.TxInput) tmsp.Result { for _, in := range ins {
if acc.PubKey == nil { // Check TxInput basic
if in.PubKey == nil { if res := in.ValidateBasic(); res.IsErr() {
return tmsp.ErrBaseUnknownPubKey.AppendLog("PubKey not present in either acc or input") return res
}
if !bytes.Equal(in.PubKey.Address(), address) {
return tmsp.ErrBaseInvalidPubKey.AppendLog("Input PubKey address does not match address")
}
acc.PubKey = in.PubKey
} else {
if in.PubKey != nil {
// NOTE: allow redundant pubkey.
if !bytes.Equal(in.PubKey.Address(), address) {
return tmsp.ErrBaseInvalidPubKey.AppendLog("Input PubKey address does not match address")
}
} }
} }
return tmsp.OK return tmsp.OK
} }
// Validate inputs and compute total amount of coins // Validate inputs and compute total amount of coins
func validateInputs(accounts map[string]*types.Account, signBytes []byte, ins []types.TxInput) (total types.Coins, res tmsp.Result) { func validateInputsAdvanced(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)]
if acc == nil { if acc == nil {
PanicSanity("validateInputs() expects account in accounts") PanicSanity("validateInputsAdvanced() expects account in accounts")
} }
res = validateInput(acc, signBytes, in) res = validateInputAdvanced(acc, signBytes, in)
if res.IsErr() { if res.IsErr() {
return return
} }
@ -245,11 +240,7 @@ func validateInputs(accounts map[string]*types.Account, signBytes []byte, ins []
return total, tmsp.OK return total, tmsp.OK
} }
func validateInput(acc *types.Account, signBytes []byte, in types.TxInput) (res tmsp.Result) { func validateInputAdvanced(acc *types.Account, signBytes []byte, in types.TxInput) (res tmsp.Result) {
// Check TxInput basic
if res := in.ValidateBasic(); res.IsErr() {
return res
}
// Check sequence/coins // 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 {
@ -266,16 +257,21 @@ func validateInput(acc *types.Account, signBytes []byte, in types.TxInput) (res
return tmsp.OK return tmsp.OK
} }
func validateOutputs(outs []types.TxOutput) (total types.Coins, res tmsp.Result) { func validateOutputsBasic(outs []types.TxOutput) (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 nil, res return res
} }
// Good. Add amount to total }
return tmsp.OK
}
func sumOutputs(outs []types.TxOutput) (total types.Coins) {
for _, out := range outs {
total = total.Plus(out.Coins) total = total.Plus(out.Coins)
} }
return total, tmsp.OK return total
} }
func adjustByInputs(state types.AccountSetter, accounts map[string]*types.Account, ins []types.TxInput) { func adjustByInputs(state types.AccountSetter, accounts map[string]*types.Account, ins []types.TxInput) {

View File

@ -23,17 +23,18 @@ func main() {
func testSendTx() { func testSendTx() {
eyesCli := eyescli.NewLocalClient() eyesCli := eyescli.NewLocalClient()
chainID := "test_chain_id"
bcApp := app.NewBasecoin(eyesCli) bcApp := app.NewBasecoin(eyesCli)
bcApp.SetOption("base/chainID", chainID)
fmt.Println(bcApp.Info()) fmt.Println(bcApp.Info())
tPriv := tests.PrivAccountFromSecret("test") test1PrivAcc := tests.PrivAccountFromSecret("test1")
tPriv2 := tests.PrivAccountFromSecret("test2") test2PrivAcc := tests.PrivAccountFromSecret("test2")
// Seed Basecoin with account // Seed Basecoin with account
tAcc := tPriv.Account test1Acc := test1PrivAcc.Account
tAcc.Balance = types.Coins{{"", 1000}} test1Acc.Balance = types.Coins{{"", 1000}}
fmt.Println(bcApp.SetOption("base/chainID", "test_chain_id")) fmt.Println(bcApp.SetOption("base/account", string(wire.JSONBytes(test1Acc))))
fmt.Println(bcApp.SetOption("base/account", string(wire.JSONBytes(tAcc))))
// Construct a SendTx signature // Construct a SendTx signature
tx := &types.SendTx{ tx := &types.SendTx{
@ -41,24 +42,24 @@ func testSendTx() {
Gas: 0, Gas: 0,
Inputs: []types.TxInput{ Inputs: []types.TxInput{
types.TxInput{ types.TxInput{
Address: tPriv.Account.PubKey.Address(), Address: test1PrivAcc.Account.PubKey.Address(),
PubKey: tPriv.Account.PubKey, // TODO is this needed? PubKey: test1PrivAcc.Account.PubKey, // TODO is this needed?
Coins: types.Coins{{"", 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: test2PrivAcc.Account.PubKey.Address(),
Coins: types.Coins{{"", 1}}, Coins: types.Coins{{"", 1}},
}, },
}, },
} }
// Sign request // Sign request
signBytes := tx.SignBytes("test_chain_id") signBytes := tx.SignBytes(chainID)
fmt.Printf("Sign bytes: %X\n", signBytes) fmt.Printf("Sign bytes: %X\n", signBytes)
sig := tPriv.PrivKey.Sign(signBytes) sig := test1PrivAcc.PrivKey.Sign(signBytes)
tx.Inputs[0].Signature = sig tx.Inputs[0].Signature = sig
//fmt.Println("tx:", tx) //fmt.Println("tx:", tx)
fmt.Printf("Signed TX bytes: %X\n", wire.BinaryBytes(struct{ types.Tx }{tx})) fmt.Printf("Signed TX bytes: %X\n", wire.BinaryBytes(struct{ types.Tx }{tx}))
@ -74,19 +75,21 @@ func testSendTx() {
func testGov() { func testGov() {
eyesCli := eyescli.NewLocalClient() eyesCli := eyescli.NewLocalClient()
chainID := "test_chain_id"
bcApp := app.NewBasecoin(eyesCli) bcApp := app.NewBasecoin(eyesCli)
bcApp.SetOption("base/chainID", chainID)
fmt.Println(bcApp.Info()) fmt.Println(bcApp.Info())
tPriv := tests.PrivAccountFromSecret("test") adminPrivAcc := tests.PrivAccountFromSecret("admin")
valPrivKey0 := crypto.GenPrivKeyEd25519FromSecret([]byte("val0")) val0PrivKey := crypto.GenPrivKeyEd25519FromSecret([]byte("val0"))
valPrivKey1 := crypto.GenPrivKeyEd25519FromSecret([]byte("val1")) val1PrivKey := crypto.GenPrivKeyEd25519FromSecret([]byte("val1"))
valPrivKey2 := crypto.GenPrivKeyEd25519FromSecret([]byte("val2")) val2PrivKey := crypto.GenPrivKeyEd25519FromSecret([]byte("val2"))
// Seed Basecoin with admin using PrivAccount // Seed Basecoin with admin using PrivAccount
tAcc := tPriv.Account adminAcc := adminPrivAcc.Account
adminEntity := govtypes.Entity{ adminEntity := govtypes.Entity{
Addr: tAcc.PubKey.Address(), Addr: adminAcc.PubKey.Address(),
PubKey: tAcc.PubKey, PubKey: adminAcc.PubKey,
} }
log := bcApp.SetOption("gov/admin", string(wire.JSONBytes(adminEntity))) log := bcApp.SetOption("gov/admin", string(wire.JSONBytes(adminEntity)))
if log != "Success" { if log != "Success" {
@ -95,15 +98,15 @@ func testGov() {
// Call InitChain to initialize the validator set // Call InitChain to initialize the validator set
bcApp.InitChain([]*tmsp.Validator{ bcApp.InitChain([]*tmsp.Validator{
{PubKey: valPrivKey0.PubKey().Bytes(), Power: 1}, {PubKey: val0PrivKey.PubKey().Bytes(), Power: 1},
{PubKey: valPrivKey1.PubKey().Bytes(), Power: 1}, {PubKey: val1PrivKey.PubKey().Bytes(), Power: 1},
{PubKey: valPrivKey2.PubKey().Bytes(), Power: 1}, {PubKey: val2PrivKey.PubKey().Bytes(), Power: 1},
}) })
// Query for validator set // Query for validator set
res := bcApp.Query(expr.MustCompile(`x02 x01 "gov/g/validators"`)) res := bcApp.Query(expr.MustCompile(`x02 x01 "gov/g/validators"`))
if res.IsErr() { if res.IsErr() {
Exit(Fmt("Failed: %v", res.Error())) Exit(Fmt("Failed to query validators: %v", res.Error()))
} }
group := govtypes.Group{} group := govtypes.Group{}
err := wire.ReadBinaryBytes(res.Data, &group) err := wire.ReadBinaryBytes(res.Data, &group)
@ -111,22 +114,60 @@ func testGov() {
Exit(Fmt("Unexpected query response bytes: %X error: %v", Exit(Fmt("Unexpected query response bytes: %X error: %v",
res.Data, err)) res.Data, err))
} }
fmt.Println(">>", group) // fmt.Println("Initialized gov/g/validators", group)
// Mutate the validator set.
proposal := govtypes.Proposal{
ID: "my_proposal_id",
VoteGroupID: "gov/admin",
StartHeight: 0,
EndHeight: 0,
Info: &govtypes.GroupUpdateProposalInfo{
UpdateGroupID: "gov/g/validators",
NextVersion: 0,
ChangedMembers: []govtypes.Member{
{nil, 1}, // TODO Fill this out.
},
},
}
proposalTx := &govtypes.ProposalTx{
EntityAddr: adminEntity.Addr,
Proposal: proposal,
}
proposalTx.SetSignature(nil, adminPrivAcc.Sign(proposalTx.SignBytes()))
tx := &types.AppTx{
Fee: 1,
Gas: 1,
Type: app.PluginTypeByteGov, // XXX Remove typebytes?
Input: types.TxInput{
Address: adminEntity.Addr,
Coins: types.Coins{{"", 1}},
Sequence: 1,
},
Data: nil,
}
tx.SetSignature(nil, adminPrivAcc.Sign(tx.SignBytes(chainID)))
res = bcApp.AppendTx(wire.BinaryBytes(struct{ types.Tx }{tx}))
if res.IsErr() {
Exit(Fmt("Failed to mutate validators: %v", res.Error()))
}
fmt.Println(res)
// TODO more tests... // TODO more tests...
} }
func testSequence() { func testSequence() {
eyesCli := eyescli.NewLocalClient() eyesCli := eyescli.NewLocalClient()
bcApp := app.NewBasecoin(eyesCli)
chainID := "test_chain_id" chainID := "test_chain_id"
bcApp := app.NewBasecoin(eyesCli)
bcApp.SetOption("base/chainID", chainID)
fmt.Println(bcApp.Info())
// Get the root account // Get the test account
root := tests.PrivAccountFromSecret("test") test1PrivAcc := tests.PrivAccountFromSecret("test1")
rootAcc := root.Account test1Acc := test1PrivAcc.Account
rootAcc.Balance = types.Coins{{"", 1 << 53}} test1Acc.Balance = types.Coins{{"", 1 << 53}}
fmt.Println(bcApp.SetOption("base/chainID", "test_chain_id")) fmt.Println(bcApp.SetOption("base/account", string(wire.JSONBytes(test1Acc))))
fmt.Println(bcApp.SetOption("base/account", string(wire.JSONBytes(rootAcc))))
sequence := int(1) sequence := int(1)
// Make a bunch of PrivAccounts // Make a bunch of PrivAccounts
@ -141,8 +182,8 @@ func testSequence() {
Gas: 2, Gas: 2,
Inputs: []types.TxInput{ Inputs: []types.TxInput{
types.TxInput{ types.TxInput{
Address: root.Account.PubKey.Address(), Address: test1Acc.PubKey.Address(),
PubKey: root.Account.PubKey, // TODO is this needed? PubKey: test1Acc.PubKey, // TODO is this needed?
Coins: types.Coins{{"", 1000002}}, Coins: types.Coins{{"", 1000002}},
Sequence: sequence, Sequence: sequence,
}, },
@ -158,7 +199,7 @@ func testSequence() {
// Sign request // Sign request
signBytes := tx.SignBytes(chainID) signBytes := tx.SignBytes(chainID)
sig := root.PrivKey.Sign(signBytes) sig := test1PrivAcc.PrivKey.Sign(signBytes)
tx.Inputs[0].Signature = sig tx.Inputs[0].Signature = sig
// fmt.Printf("ADDR: %X -> %X\n", tx.Inputs[0].Address, tx.Outputs[0].Address) // fmt.Printf("ADDR: %X -> %X\n", tx.Inputs[0].Address, tx.Outputs[0].Address)

View File

@ -45,7 +45,7 @@ type TxInput struct {
Coins Coins `json:"coins"` // 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"` // Is present iff Sequence == 0
} }
func (txIn TxInput) ValidateBasic() tmsp.Result { func (txIn TxInput) ValidateBasic() tmsp.Result {
@ -58,6 +58,15 @@ func (txIn TxInput) ValidateBasic() tmsp.Result {
if txIn.Coins.IsZero() { if txIn.Coins.IsZero() {
return tmsp.ErrBaseInvalidInput.AppendLog("Coins cannot be zero") return tmsp.ErrBaseInvalidInput.AppendLog("Coins cannot be zero")
} }
if txIn.Sequence <= 0 {
return tmsp.ErrBaseInvalidInput.AppendLog("Sequence must be greater than 0")
}
if txIn.Sequence == 1 && txIn.PubKey == nil {
return tmsp.ErrBaseInvalidInput.AppendLog("PubKey must be present when Sequence == 1")
}
if txIn.Sequence > 1 && txIn.PubKey != nil {
return tmsp.ErrBaseInvalidInput.AppendLog("PubKey must be nil when Sequence > 1")
}
return tmsp.OK return tmsp.OK
} }
@ -112,6 +121,16 @@ func (tx *SendTx) SignBytes(chainID string) []byte {
return signBytes return signBytes
} }
func (tx *SendTx) SetSignature(pubKey crypto.PubKey, sig crypto.Signature) bool {
for i, input := range tx.Inputs {
if input.PubKey.Equals(pubKey) {
tx.Inputs[i].Signature = sig
return true
}
}
return false
}
func (tx *SendTx) String() string { func (tx *SendTx) String() string {
return Fmt("SendTx{%v/%v %v->%v}", tx.Fee, tx.Gas, tx.Inputs, tx.Outputs) return Fmt("SendTx{%v/%v %v->%v}", tx.Fee, tx.Gas, tx.Inputs, tx.Outputs)
} }
@ -135,6 +154,12 @@ func (tx *AppTx) SignBytes(chainID string) []byte {
return signBytes return signBytes
} }
func (tx *AppTx) SetSignature(pubKey crypto.PubKey, sig crypto.Signature) bool {
// TODO
tx.Input.Signature = sig
return true
}
func (tx *AppTx) String() string { func (tx *AppTx) String() string {
return Fmt("AppTx{%v/%v %v %v %X}", tx.Fee, tx.Gas, tx.Type, tx.Input, tx.Data) return Fmt("AppTx{%v/%v %v %v %X}", tx.Fee, tx.Gas, tx.Type, tx.Input, tx.Data)
} }