From 665b39e3309b230030dff570c0df7c6d496cc2b6 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 28 Jan 2017 21:12:58 -0500 Subject: [PATCH 1/5] new cli --- cmd/basecoin/main.go | 195 ++++++++++++++++++++++++++++++++---------- cmd/basecoin/start.go | 54 ++++++++++++ cmd/basecoin/tx.go | 101 ++++++++++++++++++++++ 3 files changed, 305 insertions(+), 45 deletions(-) create mode 100644 cmd/basecoin/start.go create mode 100644 cmd/basecoin/tx.go diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 5dc536b917..dc940a370f 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -1,55 +1,160 @@ package main import ( - "flag" + "os" - "github.com/tendermint/abci/server" - "github.com/tendermint/basecoin/app" - cmn "github.com/tendermint/go-common" - eyes "github.com/tendermint/merkleeyes/client" + "github.com/urfave/cli" +) + +// start flags +var ( + addrFlag = cli.StringFlag{ + Name: "address", + Value: "tcp://0.0.0.0:46658", + Usage: "Listen address", + } + + eyesFlag = cli.StringFlag{ + Name: "eyes", + Value: "local", + Usage: "MerkleEyes address, or 'local' for embedded", + } + + eyesDBFlag = cli.StringFlag{ + Name: "eyes-db", + Value: "merkleeyes.db", + Usage: "MerkleEyes db name for embedded", + } + + // TODO: move to config file + // eyesCacheSizePtr := flag.Int("eyes-cache-size", 10000, "MerkleEyes db cache size, for embedded") + + genesisFlag = cli.StringFlag{ + Name: "genesis", + Value: "", + Usage: "Path to genesis file, if it exists", + } + + inProcTMFlag = cli.BoolFlag{ + Name: "in-proc", + Usage: "Run Tendermint in-process with the App", + } +) + +// tx flags + +var ( + toFlag = cli.StringFlag{ + Name: "to", + Value: "", + Usage: "Destination address for the transaction", + } + + amountFlag = cli.IntFlag{ + Name: "amount", + Value: 0, + Usage: "Amount of coins to send in the transaction", + } + + fromFlag = cli.StringFlag{ + Name: "from", + Value: "priv_validator.json", + Usage: "Path to a private key to sign the transaction", + } + + seqFlag = cli.IntFlag{ + Name: "sequence", + Value: 0, + Usage: "Sequence number for the account", + } + + coinFlag = cli.StringFlag{ + Name: "coin", + Value: "blank", + Usage: "Specify a coin denomination", + } + + gasFlag = cli.IntFlag{ + Name: "gas", + Value: 0, + Usage: "The amount of gas for the transaction", + } + + feeFlag = cli.IntFlag{ + Name: "fee", + Value: 0, + Usage: "The transaction fee", + } + + dataFlag = cli.StringFlag{ + Name: "data", + Value: "", + Usage: "Data to send with the transaction", + } + + nameFlag = cli.StringFlag{ + Name: "name", + Value: "", + Usage: "Plugin to send the transaction to", + } ) func main() { - addrPtr := flag.String("address", "tcp://0.0.0.0:46658", "Listen address") - eyesPtr := flag.String("eyes", "local", "MerkleEyes address, or 'local' for embedded") - eyesDBNamePtr := flag.String("eyes-db-name", "local.db", "MerkleEyes db name, for embedded") - eyesCacheSizePtr := flag.Int("eyes-cache-size", 10000, "MerkleEyes db cache size, for embedded") - genFilePath := flag.String("genesis", "", "Genesis file, if any") - flag.Parse() + app := cli.NewApp() + app.Name = "basecoin" + app.Usage = "basecoin [command] [args...]" + app.Version = "0.1.0" + app.Commands = []cli.Command{ + { + Name: "start", + Usage: "Start basecoin", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdStart(c) + }, + Flags: []cli.Flag{ + addrFlag, + eyesFlag, + eyesDBFlag, + genesisFlag, + inProcTMFlag, + }, + }, - // Connect to MerkleEyes - var eyesCli *eyes.Client - if *eyesPtr == "local" { - eyesCli = eyes.NewLocalClient(*eyesDBNamePtr, *eyesCacheSizePtr) - } else { - var err error - eyesCli, err = eyes.NewClient(*eyesPtr) - if err != nil { - cmn.Exit("connect to MerkleEyes: " + err.Error()) - } + { + Name: "sendtx", + Usage: "Broadcast a basecoin SendTx", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdSendTx(c) + }, + Flags: []cli.Flag{ + toFlag, + fromFlag, + amountFlag, + coinFlag, + gasFlag, + feeFlag, + }, + }, + + { + Name: "apptx", + Usage: "Broadcast a basecoin AppTx", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdAppTx(c) + }, + Flags: []cli.Flag{ + nameFlag, + fromFlag, + amountFlag, + coinFlag, + gasFlag, + feeFlag, + dataFlag, + }, + }, } - - // Create Basecoin app - app := app.NewBasecoin(eyesCli) - - // If genesis file was specified, set key-value options - if *genFilePath != "" { - err := app.LoadGenesis(*genFilePath) - if err != nil { - cmn.Exit(cmn.Fmt("%+v", err)) - } - } - - // Start the listener - svr, err := server.NewServer(*addrPtr, "socket", app) - if err != nil { - cmn.Exit("create listener: " + err.Error()) - } - - // Wait forever - cmn.TrapSignal(func() { - // Cleanup - svr.Stop() - }) - + app.Run(os.Args) } diff --git a/cmd/basecoin/start.go b/cmd/basecoin/start.go new file mode 100644 index 0000000000..fde62e3d62 --- /dev/null +++ b/cmd/basecoin/start.go @@ -0,0 +1,54 @@ +package main + +import ( + "errors" + + "github.com/urfave/cli" + + "github.com/tendermint/abci/server" + "github.com/tendermint/basecoin/app" + cmn "github.com/tendermint/go-common" + eyes "github.com/tendermint/merkleeyes/client" +) + +const EyesCacheSize = 10000 + +func cmdStart(c *cli.Context) error { + + // Connect to MerkleEyes + var eyesCli *eyes.Client + if c.String("eyes") == "local" { + eyesCli = eyes.NewLocalClient(c.String("eyes-db"), EyesCacheSize) + } else { + var err error + eyesCli, err = eyes.NewClient(c.String("eyes")) + if err != nil { + return errors.New("connect to MerkleEyes: " + err.Error()) + } + } + + // Create Basecoin app + app := app.NewBasecoin(eyesCli) + + // If genesis file was specified, set key-value options + if c.String("genesis") != "" { + err := app.LoadGenesis(c.String("genesis")) + if err != nil { + return errors.New(cmn.Fmt("%+v", err)) + } + } + + // Start the listener + svr, err := server.NewServer(c.String("address"), "socket", app) + if err != nil { + return errors.New("create listener: " + err.Error()) + } + + // Wait forever + cmn.TrapSignal(func() { + // Cleanup + svr.Stop() + }) + + return nil +} diff --git a/cmd/basecoin/tx.go b/cmd/basecoin/tx.go new file mode 100644 index 0000000000..3f902963a1 --- /dev/null +++ b/cmd/basecoin/tx.go @@ -0,0 +1,101 @@ +package main + +import ( + "encoding/hex" + "errors" + "fmt" + + "github.com/urfave/cli" + + "github.com/tendermint/basecoin/types" + cmn "github.com/tendermint/go-common" + "github.com/tendermint/go-wire" + tmtypes "github.com/tendermint/tendermint/types" +) + +func cmdSendTx(c *cli.Context) error { + toHex := c.String("to") + fromFile := c.String("from") + amount := int64(c.Int("amount")) + coin := c.String("coin") + gas, fee := c.Int("gas"), int64(c.Int("fee")) + chainID := c.String("chain_id") + + to, err := hex.DecodeString(toHex) + if err != nil { + return errors.New("To address is invalid hex: " + err.Error()) + } + + privVal := tmtypes.LoadPrivValidator(fromFile) + + sequence := getSeq(c) + + input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) + output := newOutput(to, coin, amount) + + tx := types.SendTx{ + Gas: int64(gas), + Fee: types.Coin{coin, fee}, + Inputs: []types.TxInput{input}, + Outputs: []types.TxOutput{output}, + } + + tx.Inputs[0].Signature = privVal.Sign(tx.SignBytes(chainID)) + fmt.Println(string(wire.JSONBytes(tx))) + + return nil +} + +func cmdAppTx(c *cli.Context) error { + name := c.String("name") + fromFile := c.String("from") + amount := int64(c.Int("amount")) + coin := c.String("coin") + gas, fee := c.Int("gas"), int64(c.Int("fee")) + chainID := c.String("chain_id") + dataString := c.String("data") + + data := []byte(dataString) + if cmn.IsHex(dataString) { + data, _ = hex.DecodeString(dataString) + } + + privVal := tmtypes.LoadPrivValidator(fromFile) + + sequence := getSeq(c) + + input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) + + tx := types.AppTx{ + Gas: int64(gas), + Fee: types.Coin{coin, fee}, + Name: name, + Input: input, + Data: data, + } + + tx.Input.Signature = privVal.Sign(tx.SignBytes(chainID)) + fmt.Println(string(wire.JSONBytes(tx))) + return nil +} + +func getSeq(c *cli.Context) int { + if c.IsSet("sequence") { + return c.Int("sequence") + } + // TODO: get from query + return 0 +} + +func newOutput(to []byte, coin string, amount int64) types.TxOutput { + return types.TxOutput{ + Address: to, + Coins: types.Coins{ + types.Coin{ + Denom: coin, + Amount: amount, + }, + }, + } + +} From 8262d0cc718ec0baaba8361bd784b5ee58dadb55 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 11:41:21 -0800 Subject: [PATCH 2/5] cli: working txs and account fetching --- cmd/basecoin/account.go | 69 +++++++++++++++++++++++++++ cmd/basecoin/main.go | 30 ++++++++++++ cmd/basecoin/start.go | 54 ++++++++++++++++++--- cmd/basecoin/tx.go | 103 +++++++++++++++++++++++++++++++++++----- 4 files changed, 237 insertions(+), 19 deletions(-) create mode 100644 cmd/basecoin/account.go diff --git a/cmd/basecoin/account.go b/cmd/basecoin/account.go new file mode 100644 index 0000000000..f3d3a4a8d7 --- /dev/null +++ b/cmd/basecoin/account.go @@ -0,0 +1,69 @@ +package main + +import ( + "encoding/hex" + "errors" + "fmt" + + "github.com/urfave/cli" + + "github.com/tendermint/basecoin/types" + cmn "github.com/tendermint/go-common" + client "github.com/tendermint/go-rpc/client" + "github.com/tendermint/go-wire" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +func cmdAccount(c *cli.Context) error { + if len(c.Args()) != 1 { + return errors.New("account command requires an argument ([address])") + } + addrHex := c.Args()[0] + + // convert destination address to bytes + addr, err := hex.DecodeString(addrHex) + if err != nil { + return errors.New("Account address is invalid hex: " + err.Error()) + } + + acc, err := getAcc(c, addr) + if err != nil { + return err + } + fmt.Println(string(wire.JSONBytes(acc))) + return nil +} + +// fetch the account by querying the app +func getAcc(c *cli.Context, address []byte) (*types.Account, error) { + tmAddr := c.String("tendermint") + clientURI := client.NewClientURI(tmAddr) + tmResult := new(ctypes.TMResult) + + params := map[string]interface{}{ + "path": "/key", + "data": append([]byte("base/a/"), address...), + "prove": false, + } + _, err := clientURI.Call("abci_query", params, tmResult) + if err != nil { + return nil, errors.New(cmn.Fmt("Error calling /abci_query: %v", err)) + } + res := (*tmResult).(*ctypes.ResultABCIQuery) + if !res.Response.Code.IsOK() { + return nil, errors.New(cmn.Fmt("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log)) + } + accountBytes := res.Response.Value + + if len(accountBytes) == 0 { + return nil, errors.New(cmn.Fmt("Account bytes are empty from query for address %X", address)) + } + var acc *types.Account + err = wire.ReadBinaryBytes(accountBytes, &acc) + if err != nil { + return nil, errors.New(cmn.Fmt("Error reading account %X error: %v", + accountBytes, err.Error())) + } + + return acc, nil +} diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index dc940a370f..6c4707f811 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -44,6 +44,12 @@ var ( // tx flags var ( + tmAddrFlag = cli.StringFlag{ + Name: "tendermint", + Value: "tcp://localhost:46657", + Usage: "Tendermint RPC address", + } + toFlag = cli.StringFlag{ Name: "to", Value: "", @@ -97,6 +103,12 @@ var ( Value: "", Usage: "Plugin to send the transaction to", } + + chainIDFlag = cli.StringFlag{ + Name: "chain_id", + Value: "test_chain_id", + Usage: "ID of the chain for replay protection", + } ) func main() { @@ -118,6 +130,7 @@ func main() { eyesDBFlag, genesisFlag, inProcTMFlag, + chainIDFlag, }, }, @@ -129,12 +142,15 @@ func main() { return cmdSendTx(c) }, Flags: []cli.Flag{ + tmAddrFlag, toFlag, fromFlag, amountFlag, coinFlag, gasFlag, feeFlag, + chainIDFlag, + seqFlag, }, }, @@ -146,6 +162,7 @@ func main() { return cmdAppTx(c) }, Flags: []cli.Flag{ + tmAddrFlag, nameFlag, fromFlag, amountFlag, @@ -153,6 +170,19 @@ func main() { gasFlag, feeFlag, dataFlag, + seqFlag, + }, + }, + + { + Name: "account", + Usage: "Get details of an account", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdAccount(c) + }, + Flags: []cli.Flag{ + tmAddrFlag, }, }, } diff --git a/cmd/basecoin/start.go b/cmd/basecoin/start.go index fde62e3d62..5d5c2de542 100644 --- a/cmd/basecoin/start.go +++ b/cmd/basecoin/start.go @@ -6,11 +6,21 @@ import ( "github.com/urfave/cli" "github.com/tendermint/abci/server" - "github.com/tendermint/basecoin/app" cmn "github.com/tendermint/go-common" + cfg "github.com/tendermint/go-config" + //logger "github.com/tendermint/go-logger" eyes "github.com/tendermint/merkleeyes/client" + + tmcfg "github.com/tendermint/tendermint/config/tendermint" + "github.com/tendermint/tendermint/node" + "github.com/tendermint/tendermint/proxy" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/tendermint/basecoin/app" ) +var config cfg.Config + const EyesCacheSize = 10000 func cmdStart(c *cli.Context) error { @@ -28,27 +38,57 @@ func cmdStart(c *cli.Context) error { } // Create Basecoin app - app := app.NewBasecoin(eyesCli) + basecoinApp := app.NewBasecoin(eyesCli) // If genesis file was specified, set key-value options if c.String("genesis") != "" { - err := app.LoadGenesis(c.String("genesis")) + err := basecoinApp.LoadGenesis(c.String("genesis")) if err != nil { return errors.New(cmn.Fmt("%+v", err)) } } - // Start the listener - svr, err := server.NewServer(c.String("address"), "socket", app) + if c.Bool("in-proc") { + startTendermint(c, basecoinApp) + } else { + startBasecoinABCI(c, basecoinApp) + } + + return nil +} + +func startBasecoinABCI(c *cli.Context, basecoinApp *app.Basecoin) error { + // Start the ABCI listener + svr, err := server.NewServer(c.String("address"), "socket", basecoinApp) if err != nil { return errors.New("create listener: " + err.Error()) } - // Wait forever cmn.TrapSignal(func() { // Cleanup svr.Stop() }) - return nil + +} + +func startTendermint(c *cli.Context, basecoinApp *app.Basecoin) { + // Get configuration + config = tmcfg.GetConfig("") + // logger.SetLogLevel("notice") //config.GetString("log_level")) + + // parseFlags(config, args[1:]) // Command line overrides + + // Create & start tendermint node + privValidatorFile := config.GetString("priv_validator_file") + privValidator := tmtypes.LoadOrGenPrivValidator(privValidatorFile) + n := node.NewNode(config, privValidator, proxy.NewLocalClientCreator(basecoinApp)) + + n.Start() + + // Wait forever + cmn.TrapSignal(func() { + // Cleanup + n.Stop() + }) } diff --git a/cmd/basecoin/tx.go b/cmd/basecoin/tx.go index 3f902963a1..e70d9a9ffa 100644 --- a/cmd/basecoin/tx.go +++ b/cmd/basecoin/tx.go @@ -9,7 +9,9 @@ import ( "github.com/tendermint/basecoin/types" cmn "github.com/tendermint/go-common" + client "github.com/tendermint/go-rpc/client" "github.com/tendermint/go-wire" + ctypes "github.com/tendermint/tendermint/rpc/core/types" tmtypes "github.com/tendermint/tendermint/types" ) @@ -21,28 +23,44 @@ func cmdSendTx(c *cli.Context) error { gas, fee := c.Int("gas"), int64(c.Int("fee")) chainID := c.String("chain_id") + // convert destination address to bytes to, err := hex.DecodeString(toHex) if err != nil { return errors.New("To address is invalid hex: " + err.Error()) } + // load the priv validator + // XXX: this is overkill for now, we need a keys solution privVal := tmtypes.LoadPrivValidator(fromFile) - sequence := getSeq(c) + // get the sequence number for the tx + sequence, err := getSeq(c, privVal.Address) + if err != nil { + return err + } + // craft the tx input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) output := newOutput(to, coin, amount) - - tx := types.SendTx{ + tx := &types.SendTx{ Gas: int64(gas), Fee: types.Coin{coin, fee}, Inputs: []types.TxInput{input}, Outputs: []types.TxOutput{output}, } - tx.Inputs[0].Signature = privVal.Sign(tx.SignBytes(chainID)) + // sign that puppy + signBytes := tx.SignBytes(chainID) + tx.Inputs[0].Signature = privVal.Sign(signBytes) + + fmt.Println("Signed SendTx:") fmt.Println(string(wire.JSONBytes(tx))) + // broadcast the transaction to tendermint + if err := broadcastTx(c, tx); err != nil { + return err + } + return nil } @@ -55,6 +73,7 @@ func cmdAppTx(c *cli.Context) error { chainID := c.String("chain_id") dataString := c.String("data") + // convert data to bytes data := []byte(dataString) if cmn.IsHex(dataString) { data, _ = hex.DecodeString(dataString) @@ -62,11 +81,13 @@ func cmdAppTx(c *cli.Context) error { privVal := tmtypes.LoadPrivValidator(fromFile) - sequence := getSeq(c) + sequence, err := getSeq(c, privVal.Address) + if err != nil { + return err + } input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) - - tx := types.AppTx{ + tx := &types.AppTx{ Gas: int64(gas), Fee: types.Coin{coin, fee}, Name: name, @@ -75,16 +96,74 @@ func cmdAppTx(c *cli.Context) error { } tx.Input.Signature = privVal.Sign(tx.SignBytes(chainID)) + + fmt.Println("Signed AppTx:") fmt.Println(string(wire.JSONBytes(tx))) + + if err := broadcastTx(c, tx); err != nil { + return err + } + return nil } -func getSeq(c *cli.Context) int { - if c.IsSet("sequence") { - return c.Int("sequence") +// broadcast the transaction to tendermint +func broadcastTx(c *cli.Context, tx types.Tx) error { + tmResult := new(ctypes.TMResult) + tmAddr := c.String("tendermint") + clientURI := client.NewClientURI(tmAddr) + + /*txBytes := []byte(wire.JSONBytes(struct { + types.Tx `json:"unwrap"` + }{tx}))*/ + txBytes := wire.BinaryBytes(tx) + _, err := clientURI.Call("broadcast_tx_sync", map[string]interface{}{"tx": txBytes}, tmResult) + if err != nil { + return errors.New(cmn.Fmt("Error on broadcast tx: %v", err)) } - // TODO: get from query - return 0 + res := (*tmResult).(*ctypes.ResultBroadcastTx) + if !res.Code.IsOK() { + return errors.New(cmn.Fmt("BroadcastTxSync got non-zero exit code: %v. %X; %s", res.Code, res.Data, res.Log)) + } + return nil +} + +// if the sequence flag is set, return it; +// else, fetch the account by querying the app and return the sequence number +func getSeq(c *cli.Context, address []byte) (int, error) { + if c.IsSet("sequence") { + return c.Int("sequence"), nil + } + tmAddr := c.String("tendermint") + clientURI := client.NewClientURI(tmAddr) + tmResult := new(ctypes.TMResult) + + params := map[string]interface{}{ + "path": "/key", + "data": append([]byte("base/a/"), address...), + "prove": false, + } + _, err := clientURI.Call("abci_query", params, tmResult) + if err != nil { + return 0, errors.New(cmn.Fmt("Error calling /abci_query: %v", err)) + } + res := (*tmResult).(*ctypes.ResultABCIQuery) + if !res.Response.Code.IsOK() { + return 0, errors.New(cmn.Fmt("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log)) + } + accountBytes := res.Response.Value + + if len(accountBytes) == 0 { + return 0, errors.New(cmn.Fmt("Account bytes are empty from query for address %X", address)) + } + var acc *types.Account + err = wire.ReadBinaryBytes(accountBytes, &acc) + if err != nil { + return 0, errors.New(cmn.Fmt("Error reading account %X error: %v", + accountBytes, err.Error())) + } + + return acc.Sequence + 1, nil } func newOutput(to []byte, coin string, amount int64) types.TxOutput { From 7bb21c4795ffda6770d24b2170f2613387e0de34 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 12:43:30 -0800 Subject: [PATCH 3/5] cleanup readme --- README.md | 43 +++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 980ff07575..6f93f0398a 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,7 @@ Basecoin is a sample [ABCI application](https://github.com/tendermint/abci) desi ## Contents 1. [Installation](#installation) - 1. [Learn Go](#learn-go) 1. [Using the plugin system](#using-the-plugin-system) - 1. [Forking the codebase](#forking-the-codebase) 1. [Tutorials and other reading](#tutorials-and-other-reading) ## Installation @@ -26,15 +24,15 @@ make get_vendor_deps make install ``` -This will create the `basecoin` binary. - -## Learn Go - -Check out our [guide to programming in Go](/docs/go_basics.md). +This will create the `basecoin` binary in `$GOPATH/bin`. ## Using the Plugin System -Basecoin handles public-key authentication of transaction, maintaining the balance of arbitrary types of currency (BTC, ATOM, ETH, MYCOIN, ...), sending currency (one-to-one or n-to-n multisig), and providing merkle-proofs of the state. These are common factors that many people wish to have in a crypto-currency system, so instead of trying to start from scratch, you can take advantage of the basecoin plugin system. +Basecoin is designed to serve as a common base layer for developers building cryptocurrency applications. +It handles public-key authentication of transactions, maintaining the balance of arbitrary types of currency (BTC, ATOM, ETH, MYCOIN, ...), +sending currency (one-to-one or n-to-m multisig), and providing merkle-proofs of the state. +These are common factors that many people wish to have in a crypto-currency system, +so instead of trying to start from scratch, developers can extend the functionality of Basecoin using the plugin system! The Plugin interface is defined in `types/plugin.go`: @@ -49,28 +47,17 @@ type Plugin interface { } ``` -`RunTx` is where you can handle any special transactions directed to your application. To see a very simple implementation, look at the demo [counter plugin](./plugins/counter/counter.go). If you want to create your own currency using a plugin, you don't have to fork basecoin at all. Just make your own repo, add the implementation of your custom plugin, and then build your own main script that instatiates BaseCoin and registers your plugin. +`RunTx` is where you can handle any special transactions directed to your application. +To see a very simple implementation, look at the demo [counter plugin](./plugins/counter/counter.go). +If you want to create your own currency using a plugin, you don't have to fork basecoin at all. +Just make your own repo, add the implementation of your custom plugin, and then build your own main script that instatiates Basecoin and registers your plugin. -An example is worth a 1000 words, so please take a look [at this example](https://github.com/tendermint/basecoin/blob/abci_proof/cmd/paytovote/main.go#L25-L31), in a dev branch for now. You can use the same technique in your own repo. - -There are a lot of changes on the dev branch, which should be merged in my early February, so experiment, but things will change soon.... - -## Forking the Codebase - -If you do want to fork basecoin, we would be happy if this was done in a public repo and any enhancements made as PRs on github. However, this is under the Apache license and you are free to keep the code private if you wish. - -If you don't have much experience forking in go, there are a few tricks you want to keep in mind to avoid headaches. Basically, all imports in go are absolute from GOPATH, so if you fork a repo with more than one directory, and you put it under github.com/MYNAME/repo, all the code will start caling github.com/ORIGINAL/repo, which is very confusing. My prefered solution to this is as follows: - - * Create your own fork on github, using the fork button. - * Go to the original repo checked out locally (from `go get`) - * `git remote rename origin upstream` - * `git remote add origin git@github.com:YOUR-NAME/basecoin.git` - * `git push -u origin master` - * You can now push all changes to your fork and all code compiles, all other code referencing the original repo, now references your fork. - * If you want to pull in updates from the original repo: - * `git fetch upstream` - * `git rebase upstream/master` (or whatever branch you want) +An example is worth a 1000 words, so please take a look [at this example](https://github.com/tendermint/basecoin/blob/develop/cmd/paytovote/main.go#L25-L31). +Note for now it is in a dev branch. +You can use the same technique in your own repo. ## Tutorials and Other Reading +See our [introductory blog post](https://cosmos.network/blog/cosmos-creating-interoperable-blockchains-part-1), which explains the motivation behind Basecoin. + We are working on some tutorials that will show you how to set up the genesis block, build a plugin to add custom logic, deploy to a tendermint testnet, and connect a UI to your blockchain. They should be published during the course of February 2017, so stay tuned.... From 241c1638762256ad1d0ce1f107486fc8d44830f9 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 12:48:28 -0800 Subject: [PATCH 4/5] update README, add data --- README.md | 8 ++++++++ cmd/basecoin/main.go | 2 +- data/genesis.json | 12 ++++++++++++ data/priv_validator.json | 17 +++++++++++++++++ data/priv_validator2.json | 16 ++++++++++++++++ genesis.json | 7 ------- 6 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 data/genesis.json create mode 100644 data/priv_validator.json create mode 100644 data/priv_validator2.json delete mode 100644 genesis.json diff --git a/README.md b/README.md index 6f93f0398a..5cbb2e526e 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Basecoin is a sample [ABCI application](https://github.com/tendermint/abci) desi 1. [Installation](#installation) 1. [Using the plugin system](#using-the-plugin-system) + 1. [Using the cli](#using-the-cli) 1. [Tutorials and other reading](#tutorials-and-other-reading) ## Installation @@ -56,6 +57,13 @@ An example is worth a 1000 words, so please take a look [at this example](https: Note for now it is in a dev branch. You can use the same technique in your own repo. +## Using the CLI + +The basecoin cli can be used to start a stand-alone basecoin instance (`basecoin start`), +or to start basecoin with tendermint in the same process (`basecoin start --in-proc`). +It can also be used to send transactions, eg. `basecoin sendtx --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 100` +See `basecoin --help` and `basecoin [cmd] --help` for more details`. + ## Tutorials and Other Reading See our [introductory blog post](https://cosmos.network/blog/cosmos-creating-interoperable-blockchains-part-1), which explains the motivation behind Basecoin. diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 6c4707f811..741c94f1f5 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -177,7 +177,7 @@ func main() { { Name: "account", Usage: "Get details of an account", - ArgsUsage: "", + ArgsUsage: "[address]", Action: func(c *cli.Context) error { return cmdAccount(c) }, diff --git a/data/genesis.json b/data/genesis.json new file mode 100644 index 0000000000..7aea6cb9bc --- /dev/null +++ b/data/genesis.json @@ -0,0 +1,12 @@ +[ + "base/chainID", "test_chain_id", + "base/account", { + "pub_key": [1, "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"], + "coins": [ + { + "denom": "blank", + "amount": 9007199254740992 + } + ] + } +] diff --git a/data/priv_validator.json b/data/priv_validator.json new file mode 100644 index 0000000000..15d7919240 --- /dev/null +++ b/data/priv_validator.json @@ -0,0 +1,17 @@ +{ + "address": "D397BC62B435F3CF50570FBAB4340FE52C60858F", + "last_height": 0, + "last_round": 0, + "last_signature": null, + "last_signbytes": "", + "last_step": 0, + "priv_key": [ + 1, + "39E75AA1CF7BC710585977EFC375CD1730519186BD231478C339F2819C3C26E7B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" + ], + "pub_key": [ + 1, + "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" + ] +} + diff --git a/data/priv_validator2.json b/data/priv_validator2.json new file mode 100644 index 0000000000..08256d1fd8 --- /dev/null +++ b/data/priv_validator2.json @@ -0,0 +1,16 @@ +{ + "address": "4793A333846E5104C46DD9AB9A00E31821B2F301", + "last_height": 0, + "last_round": 0, + "last_signature": null, + "last_signbytes": "", + "last_step": 0, + "priv_key": [ + 1, + "13A04A552ABAA2CCFA1F618CF9C97F1FD59FC3EE4968FE87DF3637C9B0F2FAAA93766F08BE7135E78DBFFA76B61BC7C52B96256EB4394A224B4EF8BCC954DE2E" + ], + "pub_key": [ + 1, + "93766F08BE7135E78DBFFA76B61BC7C52B96256EB4394A224B4EF8BCC954DE2E" + ] +} diff --git a/genesis.json b/genesis.json deleted file mode 100644 index 49b7b8605e..0000000000 --- a/genesis.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - "base/chainID", "test_chain_id", - "base/account", { - "pub_key": [1, "67D3B5EAF0C0BF6B5A602D359DAECC86A7A74053490EC37AE08E71360587C870"], - "balance": 9007199254740992 - } -] From 4ff02fd681fd322fca3d2d12bbb42526b67813fd Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 13:34:48 -0800 Subject: [PATCH 5/5] cmd: utils.go --- cmd/basecoin/account.go | 42 ++------------------------ cmd/basecoin/tx.go | 32 +++----------------- cmd/basecoin/utils.go | 65 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 68 deletions(-) create mode 100644 cmd/basecoin/utils.go diff --git a/cmd/basecoin/account.go b/cmd/basecoin/account.go index f3d3a4a8d7..22b4e076fc 100644 --- a/cmd/basecoin/account.go +++ b/cmd/basecoin/account.go @@ -7,18 +7,14 @@ import ( "github.com/urfave/cli" - "github.com/tendermint/basecoin/types" - cmn "github.com/tendermint/go-common" - client "github.com/tendermint/go-rpc/client" "github.com/tendermint/go-wire" - ctypes "github.com/tendermint/tendermint/rpc/core/types" ) func cmdAccount(c *cli.Context) error { if len(c.Args()) != 1 { return errors.New("account command requires an argument ([address])") } - addrHex := c.Args()[0] + addrHex := stripHex(c.Args()[0]) // convert destination address to bytes addr, err := hex.DecodeString(addrHex) @@ -26,44 +22,10 @@ func cmdAccount(c *cli.Context) error { return errors.New("Account address is invalid hex: " + err.Error()) } - acc, err := getAcc(c, addr) + acc, err := getAcc(c.String("tendermint"), addr) if err != nil { return err } fmt.Println(string(wire.JSONBytes(acc))) return nil } - -// fetch the account by querying the app -func getAcc(c *cli.Context, address []byte) (*types.Account, error) { - tmAddr := c.String("tendermint") - clientURI := client.NewClientURI(tmAddr) - tmResult := new(ctypes.TMResult) - - params := map[string]interface{}{ - "path": "/key", - "data": append([]byte("base/a/"), address...), - "prove": false, - } - _, err := clientURI.Call("abci_query", params, tmResult) - if err != nil { - return nil, errors.New(cmn.Fmt("Error calling /abci_query: %v", err)) - } - res := (*tmResult).(*ctypes.ResultABCIQuery) - if !res.Response.Code.IsOK() { - return nil, errors.New(cmn.Fmt("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log)) - } - accountBytes := res.Response.Value - - if len(accountBytes) == 0 { - return nil, errors.New(cmn.Fmt("Account bytes are empty from query for address %X", address)) - } - var acc *types.Account - err = wire.ReadBinaryBytes(accountBytes, &acc) - if err != nil { - return nil, errors.New(cmn.Fmt("Error reading account %X error: %v", - accountBytes, err.Error())) - } - - return acc, nil -} diff --git a/cmd/basecoin/tx.go b/cmd/basecoin/tx.go index e70d9a9ffa..4b0bd83845 100644 --- a/cmd/basecoin/tx.go +++ b/cmd/basecoin/tx.go @@ -24,7 +24,7 @@ func cmdSendTx(c *cli.Context) error { chainID := c.String("chain_id") // convert destination address to bytes - to, err := hex.DecodeString(toHex) + to, err := hex.DecodeString(stripHex(toHex)) if err != nil { return errors.New("To address is invalid hex: " + err.Error()) } @@ -75,7 +75,7 @@ func cmdAppTx(c *cli.Context) error { // convert data to bytes data := []byte(dataString) - if cmn.IsHex(dataString) { + if isHex(dataString) { data, _ = hex.DecodeString(dataString) } @@ -135,34 +135,10 @@ func getSeq(c *cli.Context, address []byte) (int, error) { return c.Int("sequence"), nil } tmAddr := c.String("tendermint") - clientURI := client.NewClientURI(tmAddr) - tmResult := new(ctypes.TMResult) - - params := map[string]interface{}{ - "path": "/key", - "data": append([]byte("base/a/"), address...), - "prove": false, - } - _, err := clientURI.Call("abci_query", params, tmResult) + acc, err := getAcc(tmAddr, address) if err != nil { - return 0, errors.New(cmn.Fmt("Error calling /abci_query: %v", err)) + return 0, err } - res := (*tmResult).(*ctypes.ResultABCIQuery) - if !res.Response.Code.IsOK() { - return 0, errors.New(cmn.Fmt("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log)) - } - accountBytes := res.Response.Value - - if len(accountBytes) == 0 { - return 0, errors.New(cmn.Fmt("Account bytes are empty from query for address %X", address)) - } - var acc *types.Account - err = wire.ReadBinaryBytes(accountBytes, &acc) - if err != nil { - return 0, errors.New(cmn.Fmt("Error reading account %X error: %v", - accountBytes, err.Error())) - } - return acc.Sequence + 1, nil } diff --git a/cmd/basecoin/utils.go b/cmd/basecoin/utils.go new file mode 100644 index 0000000000..a944c5bd10 --- /dev/null +++ b/cmd/basecoin/utils.go @@ -0,0 +1,65 @@ +package main + +import ( + "encoding/hex" + "errors" + + "github.com/tendermint/basecoin/types" + + cmn "github.com/tendermint/go-common" + client "github.com/tendermint/go-rpc/client" + "github.com/tendermint/go-wire" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +// Returns true for non-empty hex-string prefixed with "0x" +func isHex(s string) bool { + if len(s) > 2 && s[:2] == "0x" { + _, err := hex.DecodeString(s[2:]) + if err != nil { + return false + } + return true + } + return false +} + +func stripHex(s string) string { + if isHex(s) { + return s[2:] + } + return s +} + +// fetch the account by querying the app +func getAcc(tmAddr string, address []byte) (*types.Account, error) { + clientURI := client.NewClientURI(tmAddr) + tmResult := new(ctypes.TMResult) + + params := map[string]interface{}{ + "path": "/key", + "data": append([]byte("base/a/"), address...), + "prove": false, + } + _, err := clientURI.Call("abci_query", params, tmResult) + if err != nil { + return nil, errors.New(cmn.Fmt("Error calling /abci_query: %v", err)) + } + res := (*tmResult).(*ctypes.ResultABCIQuery) + if !res.Response.Code.IsOK() { + return nil, errors.New(cmn.Fmt("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log)) + } + accountBytes := res.Response.Value + + if len(accountBytes) == 0 { + return nil, errors.New(cmn.Fmt("Account bytes are empty from query for address %X", address)) + } + var acc *types.Account + err = wire.ReadBinaryBytes(accountBytes, &acc) + if err != nil { + return nil, errors.New(cmn.Fmt("Error reading account %X error: %v", + accountBytes, err.Error())) + } + + return acc, nil +}