diff --git a/app/app.go b/app/app.go index c96be39dd..d9b91fe20 100644 --- a/app/app.go +++ b/app/app.go @@ -12,23 +12,34 @@ import ( tmsp "github.com/tendermint/tmsp/types" ) -const version = "0.1" -const maxTxSize = 10240 +const ( + version = "0.1" + maxTxSize = 10240 + + typeByteBase = 0x01 + typeByteGov = 0x02 + + pluginNameBase = "base" + pluginNameGov = "gov" +) type Basecoin struct { eyesCli *eyes.Client govMint *gov.Governmint state *state.State + plugins *types.Plugins } func NewBasecoin(eyesCli *eyes.Client) *Basecoin { - state_ := state.NewState(eyesCli) govMint := gov.NewGovernmint(eyesCli) - state_.RegisterPlugin("GOV", govMint) + state_ := state.NewState(eyesCli) + plugins := types.NewPlugins() + plugins.RegisterPlugin(typeByteGov, pluginNameGov, govMint) // TODO: make constants return &Basecoin{ eyesCli: eyesCli, govMint: govMint, state: state_, + plugins: plugins, } } @@ -39,11 +50,10 @@ func (app *Basecoin) Info() string { // TMSP::SetOption func (app *Basecoin) SetOption(key string, value string) (log string) { - pluginName, key := splitKey(key) - if pluginName != "BASE" { + if pluginName != pluginNameBase { // Set option on plugin - plugin := app.state.GetPlugin(pluginName) + plugin := app.plugins.GetByName(pluginName) if plugin == nil { return "Invalid plugin name: " + pluginName } @@ -84,7 +94,7 @@ func (app *Basecoin) AppendTx(txBytes []byte) (res tmsp.Result) { return tmsp.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) } // Validate and exec tx - res = state.ExecTx(app.state, tx, false, nil) + res = state.ExecTx(app.state, app.plugins, tx, false, nil) if res.IsErr() { return res.PrependLog("Error in AppendTx") } @@ -103,7 +113,7 @@ func (app *Basecoin) CheckTx(txBytes []byte) (res tmsp.Result) { return tmsp.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) } // Validate tx - res = state.ExecTx(app.state, tx, true, nil) + res = state.ExecTx(app.state, app.plugins, tx, true, nil) if res.IsErr() { return res.PrependLog("Error in CheckTx") } @@ -113,8 +123,8 @@ func (app *Basecoin) CheckTx(txBytes []byte) (res tmsp.Result) { // TMSP::Query func (app *Basecoin) Query(query []byte) (res tmsp.Result) { pluginName, queryStr := splitKey(string(query)) - if pluginName != "BASE" { - plugin := app.state.GetPlugin(pluginName) + if pluginName != pluginNameBase { + plugin := app.plugins.GetByName(pluginName) if plugin == nil { return tmsp.ErrBaseUnknownPlugin.SetLog(Fmt("Unknown plugin %v", pluginName)) } @@ -132,7 +142,7 @@ func (app *Basecoin) Query(query []byte) (res tmsp.Result) { // TMSP::Commit func (app *Basecoin) Commit() (res tmsp.Result) { // First, commit all the plugins - for _, plugin := range app.state.GetPlugins() { + for _, plugin := range app.plugins.GetList() { res = plugin.Commit() if res.IsErr() { PanicSanity(Fmt("Error committing plugin %v", plugin.Name)) @@ -162,8 +172,8 @@ func (app *Basecoin) EndBlock(height uint64) []*tmsp.Validator { // Splits the string at the first :. // if there are none, the second string is nil. func splitKey(key string) (prefix string, sufix string) { - if strings.Contains(key, ":") { - keyParts := strings.SplitN(key, ":", 2) + if strings.Contains(key, "/") { + keyParts := strings.SplitN(key, "/", 2) return keyParts[0], keyParts[1] } return key, "" diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index ff7c3248e..fec7c7947 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -4,6 +4,7 @@ import ( "encoding/json" "flag" "fmt" + "reflect" "github.com/tendermint/basecoin/app" . "github.com/tendermint/go-common" @@ -58,14 +59,36 @@ type KeyValue struct { } func loadGenesis(filePath string) (kvz []KeyValue) { + kvz_ := []interface{}{} bytes, err := ReadFile(filePath) if err != nil { Exit("loading genesis file: " + err.Error()) } - fmt.Println(">>", string(bytes)) - err = json.Unmarshal(bytes, &kvz) + err = json.Unmarshal(bytes, &kvz_) if err != nil { Exit("parsing genesis file: " + err.Error()) } - return + if len(kvz_)%2 != 0 { + Exit("genesis cannot have an odd number of items. Format = [key1, value1, key2, value2, ...]") + } + for i := 0; i < len(kvz_)/2; i++ { + keyIfc := kvz_[i] + valueIfc := kvz_[i+1] + var key, value string + key, ok := keyIfc.(string) + if !ok { + Exit(Fmt("genesis had invalid key %v of type %v", keyIfc, reflect.TypeOf(keyIfc))) + } + if value_, ok := valueIfc.(string); ok { + value = value_ + } else { + valueBytes, err := json.Marshal(value_) + if err != nil { + Exit(Fmt("genesis had invalid value %v: %v", value_, err.Error())) + } + value = string(valueBytes) + } + kvz = append(kvz, KeyValue{key, value}) + } + return kvz } diff --git a/genesis.json b/genesis.json index 6c971f67a..92876231a 100644 --- a/genesis.json +++ b/genesis.json @@ -1,10 +1,7 @@ [ - { - "key": "chainID", - "value": "test_chain_id" - }, - { - "key": "account", - "value": "{\"pub_key\":[1,\"3E8A80E5412FD1711995D5888F5FA2EFB26EDD0970F1E27CE0B55CD237439E18\"],\"balance\":1000}" + "base/chainID", "test_chain_id", + "base/account", { + "pub_key", [1, "67D3B5EAF0C0BF6B5A602D359DAECC86A7A74053490EC37AE08E71360587C870"], + "balance": 1000 } ] diff --git a/state/execution.go b/state/execution.go index 2d35f0061..318921c15 100644 --- a/state/execution.go +++ b/state/execution.go @@ -10,7 +10,7 @@ import ( ) // If the tx is invalid, a TMSP error will be returned. -func ExecTx(state *State, tx types.Tx, isCheckTx bool, evc events.Fireable) tmsp.Result { +func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc events.Fireable) tmsp.Result { // TODO: do something with fees fees := int64(0) @@ -68,7 +68,7 @@ func ExecTx(state *State, tx types.Tx, isCheckTx bool, evc events.Fireable) tmsp return tmsp.OK - case *types.CallTx: + case *types.AppTx: // First, get input account inAcc := state.GetAccount(tx.Input.Address) if inAcc == nil { @@ -93,9 +93,10 @@ func ExecTx(state *State, tx types.Tx, isCheckTx bool, evc events.Fireable) tmsp } // Validate call address - plugin := state.GetPlugin(string(tx.Address)) + plugin := pgz.GetByByte(tx.Type) if plugin != nil { - return tmsp.ErrBaseUnknownAddress.AppendLog(Fmt("Unrecognized address %X (%v)", tx.Address, string(tx.Address))) + return tmsp.ErrBaseUnknownAddress.AppendLog( + Fmt("Unrecognized type byte %v", tx.Type)) } // Good! @@ -105,7 +106,7 @@ func ExecTx(state *State, tx types.Tx, isCheckTx bool, evc events.Fireable) tmsp state.SetCheckAccount(tx.Input.Address, inAcc.Sequence, inAcc.Balance) inAccCopy := inAcc.Copy() - // If this is AppendTx, actually save accounts + // If this is a CheckTx, stop now. if isCheckTx { return tmsp.OK } @@ -115,7 +116,7 @@ func ExecTx(state *State, tx types.Tx, isCheckTx bool, evc events.Fireable) tmsp cache.SetAccount(tx.Input.Address, inAcc) gas := int64(1) // TODO ctx := types.NewCallContext(cache, inAcc, value, &gas) - res = plugin.CallTx(ctx, tx.Data) + res = plugin.RunTx(ctx, tx.Data) if res.IsOK() { cache.Sync() log.Info("Successful execution") @@ -131,7 +132,7 @@ func ExecTx(state *State, tx types.Tx, isCheckTx bool, evc events.Fireable) tmsp } */ } else { - log.Info("CallTx failed", "error", res) + log.Info("AppTx failed", "error", res) // Just return the value and return. // TODO: return gas? inAccCopy.Balance += value diff --git a/state/state.go b/state/state.go index ac17ff0ae..3d2794c79 100644 --- a/state/state.go +++ b/state/state.go @@ -8,11 +8,9 @@ import ( ) type State struct { - chainID string - eyesCli *eyes.Client - checkCache map[string]checkAccount - plugins map[string]types.Plugin - pluginsList []types.NamedPlugin + chainID string + eyesCli *eyes.Client + checkCache map[string]checkAccount LastBlockHeight uint64 LastBlockHash []byte @@ -24,7 +22,6 @@ func NewState(eyesCli *eyes.Client) *State { chainID: "", eyesCli: eyesCli, checkCache: make(map[string]checkAccount), - plugins: make(map[string]types.Plugin), } return s } @@ -40,22 +37,6 @@ func (s *State) GetChainID() string { return s.chainID } -func (s *State) RegisterPlugin(name string, plugin types.Plugin) { - s.plugins[name] = plugin - s.pluginsList = append(s.pluginsList, types.NamedPlugin{ - Name: name, - Plugin: plugin, - }) -} - -func (s *State) GetPlugin(name string) types.Plugin { - return s.plugins[name] -} - -func (s *State) GetPlugins() []types.NamedPlugin { - return s.pluginsList -} - //---------------------------------------- // CheckTx state diff --git a/tests/tmsp/main.go b/tests/tmsp/main.go index 89a15315f..7f0eb583e 100644 --- a/tests/tmsp/main.go +++ b/tests/tmsp/main.go @@ -13,7 +13,7 @@ import ( ) func main() { - //testSendTx() + testSendTx() testGov() } @@ -28,8 +28,8 @@ func testSendTx() { // Seed Basecoin with account tAcc := tPriv.Account tAcc.Balance = 1000 - bcApp.SetOption("chainID", "test_chain_id") - bcApp.SetOption("account", string(wire.JSONBytes(tAcc))) + fmt.Println(bcApp.SetOption("base/chainID", "test_chain_id")) + fmt.Println(bcApp.SetOption("base/account", string(wire.JSONBytes(tAcc)))) // Construct a SendTx signature tx := &types.SendTx{ @@ -51,10 +51,11 @@ func testSendTx() { // Sign request signBytes := tx.SignBytes("test_chain_id") - fmt.Printf("SIGNBYTES %X", signBytes) + fmt.Printf("Sign bytes: %X\n", signBytes) sig := tPriv.PrivKey.Sign(signBytes) tx.Inputs[0].Signature = sig //fmt.Println("tx:", tx) + fmt.Printf("Signed TX bytes: %X\n", wire.BinaryBytes(tx)) // Write request txBytes := wire.BinaryBytes(tx) @@ -78,7 +79,7 @@ func testGov() { ID: "", PubKey: tAcc.PubKey, } - log := bcApp.SetOption("GOV:admin", string(wire.JSONBytes(adminEntity))) + log := bcApp.SetOption("gov/admin", string(wire.JSONBytes(adminEntity))) if log != "Success" { Exit(Fmt("Failed to set option: %v", log)) } diff --git a/types/plugin.go b/types/plugin.go index 0a4bd9370..3ab6f176b 100644 --- a/types/plugin.go +++ b/types/plugin.go @@ -9,16 +9,19 @@ import ( // if any gas is left the user is type Plugin interface { SetOption(key string, value string) (log string) - CallTx(ctx CallContext, txBytes []byte) (res tmsp.Result) + RunTx(ctx CallContext, txBytes []byte) (res tmsp.Result) Query(query []byte) (res tmsp.Result) Commit() (res tmsp.Result) } type NamedPlugin struct { + Byte byte Name string Plugin } +//---------------------------------------- + type CallContext struct { Cache AccountCacher Caller *Account @@ -34,3 +37,40 @@ func NewCallContext(cache AccountCacher, caller *Account, value int64, gas *int6 Gas: gas, } } + +//---------------------------------------- + +type Plugins struct { + byByte map[byte]Plugin + byName map[string]Plugin + plist []NamedPlugin +} + +func NewPlugins() *Plugins { + return &Plugins{ + byByte: make(map[byte]Plugin), + byName: make(map[string]Plugin), + } +} + +func (pgz *Plugins) RegisterPlugin(typeByte byte, name string, plugin Plugin) { + pgz.byByte[typeByte] = plugin + pgz.byName[name] = plugin + pgz.plist = append(pgz.plist, NamedPlugin{ + Byte: typeByte, + Name: name, + Plugin: plugin, + }) +} + +func (pgz *Plugins) GetByByte(typeByte byte) Plugin { + return pgz.byByte[typeByte] +} + +func (pgz *Plugins) GetByName(name string) Plugin { + return pgz.byName[name] +} + +func (pgz *Plugins) GetList() []NamedPlugin { + return pgz.plist +} diff --git a/types/tx.go b/types/tx.go index d16aebd11..feb509b46 100644 --- a/types/tx.go +++ b/types/tx.go @@ -1,14 +1,12 @@ package types import ( - "bytes" "encoding/json" . "github.com/tendermint/go-common" "github.com/tendermint/go-crypto" "github.com/tendermint/go-wire" tmsp "github.com/tendermint/tmsp/types" - "golang.org/x/crypto/ripemd160" ) /* @@ -16,7 +14,7 @@ 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 + - AppTx Send a msg to a contract that runs in the vm */ type Tx interface { @@ -27,13 +25,13 @@ type Tx interface { const ( // Account transactions TxTypeSend = byte(0x01) - TxTypeCall = byte(0x02) + TxTypeApp = byte(0x02) ) var _ = wire.RegisterInterface( struct{ Tx }{}, wire.ConcreteType{&SendTx{}, TxTypeSend}, - wire.ConcreteType{&CallTx{}, TxTypeCall}, + wire.ConcreteType{&AppTx{}, TxTypeApp}, ) //----------------------------------------------------------------------------- @@ -56,11 +54,6 @@ func (txIn TxInput) ValidateBasic() tmsp.Result { return tmsp.OK } -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) } @@ -82,11 +75,6 @@ func (txOut TxOutput) ValidateBasic() tmsp.Result { return tmsp.OK } -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) } @@ -99,24 +87,17 @@ type SendTx struct { } 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(",")) - } + signBytes := wire.BinaryBytes(chainID) + sigz := make([]crypto.Signature, len(tx.Inputs)) + for i, input := range tx.Inputs { + sigz[i] = input.Signature + tx.Inputs[i].Signature = nil } - buf.Write([]byte(`],"outputs":[`)) - for i, out := range tx.Outputs { - buf.Write(out.SignBytes()) - if i != len(tx.Outputs)-1 { - buf.Write([]byte(",")) - } + signBytes = append(signBytes, wire.BinaryBytes(tx)...) + for i := range tx.Inputs { + tx.Inputs[i].Signature = sigz[i] } - buf.Write([]byte(`]}]}`)) - return buf.Bytes() + return signBytes } func (tx *SendTx) String() string { @@ -125,35 +106,25 @@ func (tx *SendTx) String() string { //----------------------------------------------------------------------------- -type CallTx struct { - Input TxInput `json:"input"` - Address []byte `json:"address"` - GasLimit int64 `json:"gas_limit"` - Fee int64 `json:"fee"` - Data []byte `json:"data"` +type AppTx struct { + Type byte `json:"type"` // Which app + Gas int64 `json:"gas"` + Fee int64 `json:"fee"` + Input TxInput `json:"input"` + 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 *AppTx) SignBytes(chainID string) []byte { + signBytes := wire.BinaryBytes(chainID) + sig := tx.Input.Signature + tx.Input.Signature = nil + signBytes = append(signBytes, wire.BinaryBytes(tx)...) + tx.Input.Signature = sig + return signBytes } -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 (tx *AppTx) String() string { + return Fmt("AppTx{%v %v %v %v -> %X}", tx.Type, tx.Gas, tx.Fee, tx.Input, tx.Data) } //----------------------------------------------------------------------------- diff --git a/types/tx_test.go b/types/tx_test.go index 20ebf6ac2..49530424f 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -34,31 +34,29 @@ func TestSendTxSignable(t *testing.T) { }, } 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) + signBytesHex := Fmt("%X", signBytes) + expected := "010A746573745F636861696E0101020106696E7075743100000000000030390301093200000106696E70757432000000000000006F01DE0000010201076F757470757431000000000000014D01076F75747075743200000000000001BC" + if signBytesHex != expected { + t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signBytesHex) } } -func TestCallTxSignable(t *testing.T) { - callTx := &CallTx{ +func TestAppTxSignable(t *testing.T) { + callTx := &AppTx{ + Type: 0x01, + Gas: 111, + Fee: 222, Input: TxInput{ Address: []byte("input1"), Amount: 12345, Sequence: 67890, }, - Address: []byte("contract1"), - GasLimit: 111, - Fee: 222, - Data: []byte("data1"), + 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) + signBytesHex := Fmt("%X", signBytes) + expected := "010A746573745F636861696E0101000000000000006F00000000000000DE0106696E70757431000000000000303903010932000001056461746131" + if signBytesHex != expected { + t.Errorf("Got unexpected sign string for AppTx. Expected:\n%v\nGot:\n%v", expected, signBytesHex) } }