From ce2b8904d6b856fb1a0f14b3a29b2d860978acea Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Mon, 18 Apr 2016 08:09:19 -0700 Subject: [PATCH] Split validation into basic/advanced --- app/app.go | 57 +++++++++++++-------- state/.execution.go.swo | Bin 0 -> 28672 bytes state/execution.go | 108 +++++++++++++++++++--------------------- tests/tmsp/main.go | 107 +++++++++++++++++++++++++++------------ types/tx.go | 27 +++++++++- 5 files changed, 188 insertions(+), 111 deletions(-) create mode 100644 state/.execution.go.swo diff --git a/app/app.go b/app/app.go index a086cf9dc..af21daa62 100644 --- a/app/app.go +++ b/app/app.go @@ -16,13 +16,13 @@ const ( version = "0.1" maxTxSize = 10240 - typeByteBase = 0x01 - typeByteEyes = 0x02 - typeByteGov = 0x03 + PluginTypeByteBase = 0x01 + PluginTypeByteEyes = 0x02 + PluginTypeByteGov = 0x03 - pluginNameBase = "base" - pluginNameEyes = "eyes" - pluginNameGov = "gov" + PluginNameBase = "base" + PluginNameEyes = "eyes" + PluginNameGov = "gov" ) type Basecoin struct { @@ -36,7 +36,7 @@ func NewBasecoin(eyesCli *eyes.Client) *Basecoin { govMint := gov.NewGovernmint(eyesCli) state_ := state.NewState(eyesCli) plugins := types.NewPlugins() - plugins.RegisterPlugin(typeByteGov, pluginNameGov, govMint) // TODO: make constants + plugins.RegisterPlugin(PluginTypeByteGov, PluginNameGov, govMint) return &Basecoin{ eyesCli: eyesCli, govMint: govMint, @@ -52,12 +52,12 @@ func (app *Basecoin) Info() string { // TMSP::SetOption func (app *Basecoin) SetOption(key string, value string) (log string) { - pluginName, key := splitKey(key) - if pluginName != pluginNameBase { + PluginName, key := splitKey(key) + if PluginName != PluginNameBase { // Set option on plugin - plugin := app.plugins.GetByName(pluginName) + plugin := app.plugins.GetByName(PluginName) if plugin == nil { - return "Invalid plugin name: " + pluginName + return "Invalid plugin name: " + PluginName } return plugin.SetOption(key, value) } else { @@ -126,11 +126,11 @@ func (app *Basecoin) Query(query []byte) (res tmsp.Result) { typeByte := query[0] query = query[1:] switch typeByte { - case typeByteBase: + case PluginTypeByteBase: return tmsp.OK.SetLog("This type of query not yet supported") - case typeByteEyes: + case PluginTypeByteEyes: return app.eyesCli.QuerySync(query) - case typeByteGov: + case PluginTypeByteGov: return app.govMint.Query(query) } return tmsp.ErrBaseUnknownPlugin.SetLog( @@ -156,20 +156,35 @@ func (app *Basecoin) Commit() (res tmsp.Result) { // TMSP::InitChain 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 func (app *Basecoin) BeginBlock(height uint64) { - // app.govMint.BeginBlock(height) - // TODO other plugins? + app.state.ResetCacheState() + for _, plugin := range app.plugins.GetList() { + if _, ok := plugin.Plugin.(tmsp.BlockchainAware); ok { + plugin.Plugin.(tmsp.BlockchainAware).BeginBlock(height) + } + } } // TMSP::EndBlock -func (app *Basecoin) EndBlock(height uint64) []*tmsp.Validator { - app.state.ResetCacheState() - return app.govMint.EndBlock(height) - // TODO other plugins? +func (app *Basecoin) EndBlock(height uint64) (vals []*tmsp.Validator) { + for _, plugin := range app.plugins.GetList() { + if plugin.Plugin == app.govMint { + vals = plugin.Plugin.(tmsp.BlockchainAware).EndBlock(height) + } else { + if _, ok := plugin.Plugin.(tmsp.BlockchainAware); ok { + plugin.Plugin.(tmsp.BlockchainAware).EndBlock(height) + } + } + } + return } //---------------------------------------- diff --git a/state/.execution.go.swo b/state/.execution.go.swo new file mode 100644 index 0000000000000000000000000000000000000000..dc5b7ac8e6611215fdcbc2fb3a4cc3b4ec072280 GIT binary patch literal 28672 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 MPCKjN(oLNI1vP+(Jpcdz literal 0 HcmV?d00001 diff --git a/state/execution.go b/state/execution.go index f8f4227af..40e113f5d 100644 --- a/state/execution.go +++ b/state/execution.go @@ -1,8 +1,6 @@ package state import ( - "bytes" - "github.com/tendermint/basecoin/types" . "github.com/tendermint/go-common" "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 switch tx := tx.(type) { 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) if res.IsErr() { return res.PrependLog("in getInputs()") } - // Then, get or make outputs. + // Get or make outputs. accounts, res = getOrMakeOutputs(state, accounts, tx.Outputs) if res.IsErr() { return res.PrependLog("in getOrMakeOutputs()") } - // Validate inputs and outputs + // Validate inputs and outputs, advanced signBytes := tx.SignBytes(chainID) - inTotal, res := validateInputs(accounts, signBytes, tx.Inputs) + inTotal, res := validateInputsAdvanced(accounts, signBytes, tx.Inputs) if res.IsErr() { - return res.PrependLog("in validateInputs()") - } - outTotal, res := validateOutputs(tx.Outputs) - if res.IsErr() { - return res.PrependLog("in validateOutputs()") + return res.PrependLog("in validateInputsAdvanced()") } + outTotal := sumOutputs(tx.Outputs) if !inTotal.IsEqual(outTotal.Plus(types.Coins{{"", tx.Fee}})) { 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 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) if inAcc == nil { return tmsp.ErrBaseUnknownAddress } - - // Validate input - // 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 + if tx.Input.PubKey != nil { + inAcc.PubKey = tx.Input.PubKey } + + // Validate input, advanced signBytes := tx.SignBytes(chainID) - res := validateInput(inAcc, signBytes, tx.Input) + res = validateInputAdvanced(inAcc, signBytes, tx.Input) if res.IsErr() { - log.Info(Fmt("validateInput failed on %X: %v", tx.Input.Address, res)) - return res.PrependLog("in validateInput()") + log.Info(Fmt("validateInputAdvanced failed on %X: %v", tx.Input.Address, res)) + return res.PrependLog("in validateInputAdvanced()") } 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)) @@ -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 // 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. +// or it must be specified in the TxInput. func getInputs(state types.AccountGetter, ins []types.TxInput) (map[string]*types.Account, tmsp.Result) { accounts := map[string]*types.Account{} for _, in := range ins { @@ -173,9 +181,8 @@ func getInputs(state types.AccountGetter, ins []types.TxInput) (map[string]*type if acc == nil { return nil, tmsp.ErrBaseUnknownAddress } - // PubKey should be present in either "account" or "in" - if res := checkInputPubKey(in.Address, acc, in); res.IsErr() { - return nil, res + if in.PubKey != nil { + acc.PubKey = in.PubKey } accounts[string(in.Address)] = acc } @@ -205,37 +212,25 @@ func getOrMakeOutputs(state types.AccountGetter, accounts map[string]*types.Acco return accounts, tmsp.OK } -// 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) tmsp.Result { - if acc.PubKey == nil { - if in.PubKey == nil { - return tmsp.ErrBaseUnknownPubKey.AppendLog("PubKey not present in either acc or input") - } - 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") - } +// Validate inputs basic structure +func validateInputsBasic(ins []types.TxInput) (res tmsp.Result) { + for _, in := range ins { + // Check TxInput basic + if res := in.ValidateBasic(); res.IsErr() { + return res } } return tmsp.OK } // 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 { acc := accounts[string(in.Address)] 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() { return } @@ -245,11 +240,7 @@ func validateInputs(accounts map[string]*types.Account, signBytes []byte, ins [] return total, tmsp.OK } -func validateInput(acc *types.Account, signBytes []byte, in types.TxInput) (res tmsp.Result) { - // Check TxInput basic - if res := in.ValidateBasic(); res.IsErr() { - return res - } +func validateInputAdvanced(acc *types.Account, signBytes []byte, in types.TxInput) (res tmsp.Result) { // Check sequence/coins seq, balance := acc.Sequence, acc.Balance if seq+1 != in.Sequence { @@ -266,16 +257,21 @@ func validateInput(acc *types.Account, signBytes []byte, in types.TxInput) (res 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 { // Check TxOutput basic 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) } - return total, tmsp.OK + return total } func adjustByInputs(state types.AccountSetter, accounts map[string]*types.Account, ins []types.TxInput) { diff --git a/tests/tmsp/main.go b/tests/tmsp/main.go index 1bbfd07f6..6a6c0d67c 100644 --- a/tests/tmsp/main.go +++ b/tests/tmsp/main.go @@ -23,17 +23,18 @@ func main() { func testSendTx() { eyesCli := eyescli.NewLocalClient() + chainID := "test_chain_id" bcApp := app.NewBasecoin(eyesCli) + bcApp.SetOption("base/chainID", chainID) fmt.Println(bcApp.Info()) - tPriv := tests.PrivAccountFromSecret("test") - tPriv2 := tests.PrivAccountFromSecret("test2") + test1PrivAcc := tests.PrivAccountFromSecret("test1") + test2PrivAcc := tests.PrivAccountFromSecret("test2") // Seed Basecoin with account - tAcc := tPriv.Account - tAcc.Balance = types.Coins{{"", 1000}} - fmt.Println(bcApp.SetOption("base/chainID", "test_chain_id")) - fmt.Println(bcApp.SetOption("base/account", string(wire.JSONBytes(tAcc)))) + test1Acc := test1PrivAcc.Account + test1Acc.Balance = types.Coins{{"", 1000}} + fmt.Println(bcApp.SetOption("base/account", string(wire.JSONBytes(test1Acc)))) // Construct a SendTx signature tx := &types.SendTx{ @@ -41,24 +42,24 @@ func testSendTx() { Gas: 0, Inputs: []types.TxInput{ types.TxInput{ - Address: tPriv.Account.PubKey.Address(), - PubKey: tPriv.Account.PubKey, // TODO is this needed? + Address: test1PrivAcc.Account.PubKey.Address(), + PubKey: test1PrivAcc.Account.PubKey, // TODO is this needed? Coins: types.Coins{{"", 1}}, Sequence: 1, }, }, Outputs: []types.TxOutput{ types.TxOutput{ - Address: tPriv2.Account.PubKey.Address(), + Address: test2PrivAcc.Account.PubKey.Address(), Coins: types.Coins{{"", 1}}, }, }, } // Sign request - signBytes := tx.SignBytes("test_chain_id") + signBytes := tx.SignBytes(chainID) fmt.Printf("Sign bytes: %X\n", signBytes) - sig := tPriv.PrivKey.Sign(signBytes) + sig := test1PrivAcc.PrivKey.Sign(signBytes) tx.Inputs[0].Signature = sig //fmt.Println("tx:", tx) fmt.Printf("Signed TX bytes: %X\n", wire.BinaryBytes(struct{ types.Tx }{tx})) @@ -74,19 +75,21 @@ func testSendTx() { func testGov() { eyesCli := eyescli.NewLocalClient() + chainID := "test_chain_id" bcApp := app.NewBasecoin(eyesCli) + bcApp.SetOption("base/chainID", chainID) fmt.Println(bcApp.Info()) - tPriv := tests.PrivAccountFromSecret("test") - valPrivKey0 := crypto.GenPrivKeyEd25519FromSecret([]byte("val0")) - valPrivKey1 := crypto.GenPrivKeyEd25519FromSecret([]byte("val1")) - valPrivKey2 := crypto.GenPrivKeyEd25519FromSecret([]byte("val2")) + adminPrivAcc := tests.PrivAccountFromSecret("admin") + val0PrivKey := crypto.GenPrivKeyEd25519FromSecret([]byte("val0")) + val1PrivKey := crypto.GenPrivKeyEd25519FromSecret([]byte("val1")) + val2PrivKey := crypto.GenPrivKeyEd25519FromSecret([]byte("val2")) // Seed Basecoin with admin using PrivAccount - tAcc := tPriv.Account + adminAcc := adminPrivAcc.Account adminEntity := govtypes.Entity{ - Addr: tAcc.PubKey.Address(), - PubKey: tAcc.PubKey, + Addr: adminAcc.PubKey.Address(), + PubKey: adminAcc.PubKey, } log := bcApp.SetOption("gov/admin", string(wire.JSONBytes(adminEntity))) if log != "Success" { @@ -95,15 +98,15 @@ func testGov() { // Call InitChain to initialize the validator set bcApp.InitChain([]*tmsp.Validator{ - {PubKey: valPrivKey0.PubKey().Bytes(), Power: 1}, - {PubKey: valPrivKey1.PubKey().Bytes(), Power: 1}, - {PubKey: valPrivKey2.PubKey().Bytes(), Power: 1}, + {PubKey: val0PrivKey.PubKey().Bytes(), Power: 1}, + {PubKey: val1PrivKey.PubKey().Bytes(), Power: 1}, + {PubKey: val2PrivKey.PubKey().Bytes(), Power: 1}, }) // Query for validator set res := bcApp.Query(expr.MustCompile(`x02 x01 "gov/g/validators"`)) if res.IsErr() { - Exit(Fmt("Failed: %v", res.Error())) + Exit(Fmt("Failed to query validators: %v", res.Error())) } group := govtypes.Group{} err := wire.ReadBinaryBytes(res.Data, &group) @@ -111,22 +114,60 @@ func testGov() { Exit(Fmt("Unexpected query response bytes: %X error: %v", 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... } func testSequence() { eyesCli := eyescli.NewLocalClient() - bcApp := app.NewBasecoin(eyesCli) chainID := "test_chain_id" + bcApp := app.NewBasecoin(eyesCli) + bcApp.SetOption("base/chainID", chainID) + fmt.Println(bcApp.Info()) - // Get the root account - root := tests.PrivAccountFromSecret("test") - rootAcc := root.Account - rootAcc.Balance = types.Coins{{"", 1 << 53}} - fmt.Println(bcApp.SetOption("base/chainID", "test_chain_id")) - fmt.Println(bcApp.SetOption("base/account", string(wire.JSONBytes(rootAcc)))) + // Get the test account + test1PrivAcc := tests.PrivAccountFromSecret("test1") + test1Acc := test1PrivAcc.Account + test1Acc.Balance = types.Coins{{"", 1 << 53}} + fmt.Println(bcApp.SetOption("base/account", string(wire.JSONBytes(test1Acc)))) sequence := int(1) // Make a bunch of PrivAccounts @@ -141,8 +182,8 @@ func testSequence() { Gas: 2, Inputs: []types.TxInput{ types.TxInput{ - Address: root.Account.PubKey.Address(), - PubKey: root.Account.PubKey, // TODO is this needed? + Address: test1Acc.PubKey.Address(), + PubKey: test1Acc.PubKey, // TODO is this needed? Coins: types.Coins{{"", 1000002}}, Sequence: sequence, }, @@ -158,7 +199,7 @@ func testSequence() { // Sign request signBytes := tx.SignBytes(chainID) - sig := root.PrivKey.Sign(signBytes) + sig := test1PrivAcc.PrivKey.Sign(signBytes) tx.Inputs[0].Signature = sig // fmt.Printf("ADDR: %X -> %X\n", tx.Inputs[0].Address, tx.Outputs[0].Address) diff --git a/types/tx.go b/types/tx.go index 8e6577432..0e9521a12 100644 --- a/types/tx.go +++ b/types/tx.go @@ -45,7 +45,7 @@ type TxInput struct { Coins Coins `json:"coins"` // 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 + PubKey crypto.PubKey `json:"pub_key"` // Is present iff Sequence == 0 } func (txIn TxInput) ValidateBasic() tmsp.Result { @@ -58,6 +58,15 @@ func (txIn TxInput) ValidateBasic() tmsp.Result { if txIn.Coins.IsZero() { 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 } @@ -112,6 +121,16 @@ func (tx *SendTx) SignBytes(chainID string) []byte { 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 { 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 } +func (tx *AppTx) SetSignature(pubKey crypto.PubKey, sig crypto.Signature) bool { + // TODO + tx.Input.Signature = sig + return true +} + func (tx *AppTx) String() string { return Fmt("AppTx{%v/%v %v %v %X}", tx.Fee, tx.Gas, tx.Type, tx.Input, tx.Data) }