diff --git a/CHANGELOG.md b/CHANGELOG.md index 927c2f688..b8014dd06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.4.0 (TBD) + +BREAKING CHANGES: + +- CLI now uses Cobra, which forced changes to some of the flag names and orderings + +IMPROVEMENTS: + +- `basecoin init` doesn't generate error if already initialized + ## 0.3.1 (March 23, 2017) IMPROVEMENTS: diff --git a/app/app.go b/app/app.go index f8cc5b739..fe1773669 100644 --- a/app/app.go +++ b/app/app.go @@ -5,17 +5,17 @@ import ( "strings" abci "github.com/tendermint/abci/types" - sm "github.com/tendermint/basecoin/state" - "github.com/tendermint/basecoin/types" . "github.com/tendermint/go-common" "github.com/tendermint/go-wire" eyes "github.com/tendermint/merkleeyes/client" + + sm "github.com/tendermint/basecoin/state" + "github.com/tendermint/basecoin/types" + "github.com/tendermint/basecoin/version" ) const ( - version = "0.1" - maxTxSize = 10240 - + maxTxSize = 10240 PluginNameBase = "base" ) @@ -44,7 +44,7 @@ func (app *Basecoin) GetState() *sm.State { // ABCI::Info func (app *Basecoin) Info() abci.ResponseInfo { - return abci.ResponseInfo{Data: Fmt("Basecoin v%v", version)} + return abci.ResponseInfo{Data: Fmt("Basecoin v%v", version.Version)} } func (app *Basecoin) RegisterPlugin(plugin types.Plugin) { diff --git a/app/genesis_test.go b/app/genesis_test.go index 90f61e152..74ca22e61 100644 --- a/app/genesis_test.go +++ b/app/genesis_test.go @@ -2,20 +2,24 @@ package app import ( "encoding/hex" + "encoding/json" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + cmn "github.com/tendermint/go-common" "github.com/tendermint/go-crypto" eyescli "github.com/tendermint/merkleeyes/client" ) +const genesisFilepath = "./testdata/genesis.json" + func TestLoadGenesis(t *testing.T) { assert, require := assert.New(t), require.New(t) eyesCli := eyescli.NewLocalClient("", 0) app := NewBasecoin(eyesCli) - err := app.LoadGenesis("./testdata/genesis.json") + err := app.LoadGenesis(genesisFilepath) require.Nil(err, "%+v", err) // check the chain id @@ -41,3 +45,24 @@ func TestLoadGenesis(t *testing.T) { assert.EqualValues(pkbyte, epk[:]) } } + +func TestParseGenesisList(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + bytes, err := cmn.ReadFile(genesisFilepath) + require.Nil(err, "loading genesis file %+v", err) + + // the basecoin genesis go-data :) + genDoc := new(FullGenesisDoc) + err = json.Unmarshal(bytes, genDoc) + require.Nil(err, "unmarshaling genesis file %+v", err) + + pluginOpts, err := parseGenesisList(genDoc.AppOptions.PluginOptions) + require.Nil(err, "%+v", err) + genDoc.AppOptions.pluginOptions = pluginOpts + + assert.Equal(genDoc.AppOptions.pluginOptions[0].Key, "plugin1/key1") + assert.Equal(genDoc.AppOptions.pluginOptions[1].Key, "plugin1/key2") + assert.Equal(genDoc.AppOptions.pluginOptions[0].Value, "value1") + assert.Equal(genDoc.AppOptions.pluginOptions[1].Value, "value2") +} diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 4eb4c944c..5b37ab7fe 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -1,20 +1,18 @@ package main import ( - "fmt" - "os" + "github.com/spf13/cobra" "github.com/tendermint/basecoin/cmd/commands" - "github.com/tendermint/basecoin/version" - "github.com/urfave/cli" ) func main() { - app := cli.NewApp() - app.Name = "basecoin" - app.Usage = "basecoin [command] [args...]" - app.Version = version.Version - app.Commands = []cli.Command{ + var RootCmd = &cobra.Command{ + Use: "basecoin", + Short: "A cryptocurrency framework in Golang based on Tendermint-Core", + } + + RootCmd.AddCommand( commands.InitCmd, commands.StartCmd, commands.TxCmd, @@ -24,10 +22,8 @@ func main() { commands.BlockCmd, commands.AccountCmd, commands.UnsafeResetAllCmd, - } - err := app.Run(os.Args) - if err != nil { - fmt.Println(err) - os.Exit(1) - } + commands.VersionCmd, + ) + + commands.ExecuteWithDebug(RootCmd) } diff --git a/cmd/commands/flags.go b/cmd/commands/flags.go deleted file mode 100644 index ccf95769d..000000000 --- a/cmd/commands/flags.go +++ /dev/null @@ -1,119 +0,0 @@ -package commands - -import ( - "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", - } - - // TODO: move to config file - // eyesCacheSizePtr := flag.Int("eyes-cache-size", 10000, "MerkleEyes db cache size, for embedded") - - WithoutTendermintFlag = cli.BoolFlag{ - Name: "without-tendermint", - Usage: "Run the Basecoin app without Tendermint", - } -) - -// tx flags - -var ( - NodeFlag = cli.StringFlag{ - Name: "node", - Value: "tcp://localhost:46657", - Usage: "Tendermint RPC address", - } - - ToFlag = cli.StringFlag{ - Name: "to", - Value: "", - Usage: "Destination address for the transaction", - } - - AmountFlag = cli.StringFlag{ - Name: "amount", - Value: "", - Usage: "Coins to send in transaction of the format ,,... (eg: 1btc,2gold,5silver)", - } - - FromFlag = cli.StringFlag{ - Name: "from", - Value: "key.json", - Usage: "Path to a private key to sign the transaction", - } - - SeqFlag = cli.IntFlag{ - Name: "sequence", - Value: 0, - Usage: "Sequence number for the account", - } - - GasFlag = cli.IntFlag{ - Name: "gas", - Value: 0, - Usage: "The amount of gas for the transaction", - } - - FeeFlag = cli.StringFlag{ - Name: "fee", - Value: "", - Usage: "Coins for the transaction fee of the format ", - } - - 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", - } - - ChainIDFlag = cli.StringFlag{ - Name: "chain_id", - Value: "test_chain_id", - Usage: "ID of the chain for replay protection", - } -) - -// proof flags -var ( - ProofFlag = cli.StringFlag{ - Name: "proof", - Usage: "hex-encoded IAVL proof", - Value: "", - } - - KeyFlag = cli.StringFlag{ - Name: "key", - Usage: "key to the IAVL tree", - Value: "", - } - - ValueFlag = cli.StringFlag{ - Name: "value", - Usage: "value in the IAVL tree", - Value: "", - } - - RootFlag = cli.StringFlag{ - Name: "root", - Usage: "root hash of the IAVL tree", - Value: "", - } -) diff --git a/cmd/commands/ibc.go b/cmd/commands/ibc.go index 34f4b7923..8b0938826 100644 --- a/cmd/commands/ibc.go +++ b/cmd/commands/ibc.go @@ -2,15 +2,14 @@ package commands import ( "encoding/hex" - "errors" "fmt" "io/ioutil" - "github.com/urfave/cli" + "github.com/pkg/errors" + "github.com/spf13/cobra" "github.com/tendermint/basecoin/plugins/ibc" - cmn "github.com/tendermint/go-common" "github.com/tendermint/go-merkle" "github.com/tendermint/go-wire" tmtypes "github.com/tendermint/tendermint/types" @@ -21,172 +20,110 @@ func NewIBCPlugin() *ibc.IBCPlugin { return ibc.New() } -//--------------------------------------------------------------------- -// ibc flags - +//commands var ( - IbcChainIDFlag = cli.StringFlag{ - Name: "chain_id", - Usage: "ChainID for the new blockchain", - Value: "", + IBCTxCmd = &cobra.Command{ + Use: "ibc", + Short: "An IBC transaction, for InterBlockchain Communication", } - IbcGenesisFlag = cli.StringFlag{ - Name: "genesis", - Usage: "Genesis file for the new blockchain", - Value: "", + IBCRegisterTxCmd = &cobra.Command{ + Use: "register", + Short: "Register a blockchain via IBC", + RunE: ibcRegisterTxCmd, } - IbcHeaderFlag = cli.StringFlag{ - Name: "header", - Usage: "Block header for an ibc update", - Value: "", + IBCUpdateTxCmd = &cobra.Command{ + Use: "update", + Short: "Update the latest state of a blockchain via IBC", + RunE: ibcUpdateTxCmd, } - IbcCommitFlag = cli.StringFlag{ - Name: "commit", - Usage: "Block commit for an ibc update", - Value: "", + IBCPacketTxCmd = &cobra.Command{ + Use: "packet", + Short: "Send a new packet via IBC", } - IbcFromFlag = cli.StringFlag{ - Name: "from", - Usage: "Source ChainID", - Value: "", + IBCPacketCreateTxCmd = &cobra.Command{ + Use: "create", + Short: "Create an egress IBC packet", + RunE: ibcPacketCreateTxCmd, } - IbcToFlag = cli.StringFlag{ - Name: "to", - Usage: "Destination ChainID", - Value: "", - } - - IbcTypeFlag = cli.StringFlag{ - Name: "type", - Usage: "IBC packet type (eg. coin)", - Value: "", - } - - IbcPayloadFlag = cli.StringFlag{ - Name: "payload", - Usage: "IBC packet payload", - Value: "", - } - - IbcPacketFlag = cli.StringFlag{ - Name: "packet", - Usage: "hex-encoded IBC packet", - Value: "", - } - - IbcProofFlag = cli.StringFlag{ - Name: "proof", - Usage: "hex-encoded proof of IBC packet from source chain", - Value: "", - } - - IbcSequenceFlag = cli.IntFlag{ - Name: "sequence", - Usage: "sequence number for IBC packet", - Value: 0, - } - - IbcHeightFlag = cli.IntFlag{ - Name: "height", - Usage: "Height the packet became egress in source chain", - Value: 0, + IBCPacketPostTxCmd = &cobra.Command{ + Use: "post", + Short: "Deliver an IBC packet to another chain", + RunE: ibcPacketPostTxCmd, } ) -//--------------------------------------------------------------------- -// ibc commands - +//flags var ( - IbcTxCmd = cli.Command{ - Name: "ibc", - Usage: "an IBC transaction, for InterBlockchain Communication", - Flags: TxFlags, - Subcommands: []cli.Command{ - IbcRegisterTxCmd, - IbcUpdateTxCmd, - IbcPacketTxCmd, - }, - } - - IbcRegisterTxCmd = cli.Command{ - Name: "register", - Usage: "Register a blockchain via IBC", - Action: func(c *cli.Context) error { - return cmdIBCRegisterTx(c) - }, - Flags: []cli.Flag{ - IbcChainIDFlag, - IbcGenesisFlag, - }, - } - - IbcUpdateTxCmd = cli.Command{ - Name: "update", - Usage: "Update the latest state of a blockchain via IBC", - Action: func(c *cli.Context) error { - return cmdIBCUpdateTx(c) - }, - Flags: []cli.Flag{ - IbcHeaderFlag, - IbcCommitFlag, - }, - } - - IbcPacketTxCmd = cli.Command{ - Name: "packet", - Usage: "Send a new packet via IBC", - Subcommands: []cli.Command{ - IbcPacketCreateTx, - IbcPacketPostTx, - }, - } - - IbcPacketCreateTx = cli.Command{ - Name: "create", - Usage: "Create an egress IBC packet", - Action: func(c *cli.Context) error { - return cmdIBCPacketCreateTx(c) - }, - Flags: []cli.Flag{ - IbcFromFlag, - IbcToFlag, - IbcTypeFlag, - IbcPayloadFlag, - IbcSequenceFlag, - }, - } - - IbcPacketPostTx = cli.Command{ - Name: "post", - Usage: "Deliver an IBC packet to another chain", - Action: func(c *cli.Context) error { - return cmdIBCPacketPostTx(c) - }, - Flags: []cli.Flag{ - IbcFromFlag, - IbcHeightFlag, - IbcPacketFlag, - IbcProofFlag, - }, - } + ibcChainIDFlag string + ibcGenesisFlag string + ibcHeaderFlag string + ibcCommitFlag string + ibcFromFlag string + ibcToFlag string + ibcTypeFlag string + ibcPayloadFlag string + ibcPacketFlag string + ibcProofFlag string + ibcSequenceFlag int + ibcHeightFlag int ) +func init() { + + // register flags + registerFlags := []Flag2Register{ + {&ibcChainIDFlag, "ibc_chain_id", "", "ChainID for the new blockchain"}, + {&ibcGenesisFlag, "genesis", "", "Genesis file for the new blockchain"}, + } + + updateFlags := []Flag2Register{ + {&ibcHeaderFlag, "header", "", "Block header for an ibc update"}, + {&ibcCommitFlag, "commit", "", "Block commit for an ibc update"}, + } + + fromFlagReg := Flag2Register{&ibcFromFlag, "ibc_from", "", "Source ChainID"} + + packetCreateFlags := []Flag2Register{ + fromFlagReg, + {&ibcToFlag, "to", "", "Destination ChainID"}, + {&ibcTypeFlag, "type", "", "IBC packet type (eg. coin)"}, + {&ibcPayloadFlag, "payload", "", "IBC packet payload"}, + {&ibcSequenceFlag, "ibc_sequence", -1, "sequence number for IBC packet"}, + } + + packetPostFlags := []Flag2Register{ + fromFlagReg, + {&ibcHeightFlag, "height", 0, "Height the packet became egress in source chain"}, + {&ibcPacketFlag, "packet", "", "hex-encoded IBC packet"}, + {&ibcProofFlag, "proof", "", "hex-encoded proof of IBC packet from source chain"}, + } + + RegisterFlags(IBCRegisterTxCmd, registerFlags) + RegisterFlags(IBCUpdateTxCmd, updateFlags) + RegisterFlags(IBCPacketCreateTxCmd, packetCreateFlags) + RegisterFlags(IBCPacketPostTxCmd, packetPostFlags) + + //register commands + IBCTxCmd.AddCommand(IBCRegisterTxCmd, IBCUpdateTxCmd, IBCPacketTxCmd) + IBCPacketTxCmd.AddCommand(IBCPacketCreateTxCmd, IBCPacketPostTxCmd) + RegisterTxSubcommand(IBCTxCmd) +} + //--------------------------------------------------------------------- // ibc command implementations -func cmdIBCRegisterTx(c *cli.Context) error { - chainID := c.String("chain_id") - genesisFile := c.String("genesis") - parent := c.Parent() +func ibcRegisterTxCmd(cmd *cobra.Command, args []string) error { + chainID := ibcChainIDFlag + genesisFile := ibcGenesisFlag genesisBytes, err := ioutil.ReadFile(genesisFile) if err != nil { - return errors.New(cmn.Fmt("Error reading genesis file %v: %v", genesisFile, err)) + return errors.Errorf("Error reading genesis file %v: %v\n", genesisFile, err) } ibcTx := ibc.IBCRegisterChainTx{ @@ -203,27 +140,31 @@ func cmdIBCRegisterTx(c *cli.Context) error { }{ibcTx})) name := "IBC" - return AppTx(parent, name, data) + return AppTx(name, data) } -func cmdIBCUpdateTx(c *cli.Context) error { - headerBytes, err := hex.DecodeString(StripHex(c.String("header"))) +func ibcUpdateTxCmd(cmd *cobra.Command, args []string) error { + headerBytes, err := hex.DecodeString(StripHex(ibcHeaderFlag)) if err != nil { - return errors.New(cmn.Fmt("Header (%v) is invalid hex: %v", c.String("header"), err)) + return errors.Errorf("Header (%v) is invalid hex: %v\n", ibcHeaderFlag, err) } - commitBytes, err := hex.DecodeString(StripHex(c.String("commit"))) + + commitBytes, err := hex.DecodeString(StripHex(ibcCommitFlag)) if err != nil { - return errors.New(cmn.Fmt("Commit (%v) is invalid hex: %v", c.String("commit"), err)) + return errors.Errorf("Commit (%v) is invalid hex: %v\n", ibcCommitFlag, err) } header := new(tmtypes.Header) commit := new(tmtypes.Commit) - if err := wire.ReadBinaryBytes(headerBytes, &header); err != nil { - return errors.New(cmn.Fmt("Error unmarshalling header: %v", err)) + err = wire.ReadBinaryBytes(headerBytes, &header) + if err != nil { + return errors.Errorf("Error unmarshalling header: %v\n", err) } - if err := wire.ReadBinaryBytes(commitBytes, &commit); err != nil { - return errors.New(cmn.Fmt("Error unmarshalling commit: %v", err)) + + err = wire.ReadBinaryBytes(commitBytes, &commit) + if err != nil { + return errors.Errorf("Error unmarshalling commit: %v\n", err) } ibcTx := ibc.IBCUpdateChainTx{ @@ -238,19 +179,19 @@ func cmdIBCUpdateTx(c *cli.Context) error { }{ibcTx})) name := "IBC" - return AppTx(c.Parent(), name, data) + return AppTx(name, data) } -func cmdIBCPacketCreateTx(c *cli.Context) error { - fromChain, toChain := c.String("from"), c.String("to") - packetType := c.String("type") +func ibcPacketCreateTxCmd(cmd *cobra.Command, args []string) error { + fromChain, toChain := ibcFromFlag, ibcToFlag + packetType := ibcTypeFlag - payloadBytes, err := hex.DecodeString(StripHex(c.String("payload"))) + payloadBytes, err := hex.DecodeString(StripHex(ibcPayloadFlag)) if err != nil { - return errors.New(cmn.Fmt("Payload (%v) is invalid hex: %v", c.String("payload"), err)) + return errors.Errorf("Payload (%v) is invalid hex: %v\n", ibcPayloadFlag, err) } - sequence, err := getIBCSequence(c) + sequence, err := ibcSequenceCmd() if err != nil { return err } @@ -271,29 +212,33 @@ func cmdIBCPacketCreateTx(c *cli.Context) error { ibc.IBCTx `json:"unwrap"` }{ibcTx})) - return AppTx(c.Parent().Parent(), "IBC", data) + return AppTx("IBC", data) } -func cmdIBCPacketPostTx(c *cli.Context) error { - fromChain, fromHeight := c.String("from"), c.Int("height") +func ibcPacketPostTxCmd(cmd *cobra.Command, args []string) error { + fromChain, fromHeight := ibcFromFlag, ibcHeightFlag - packetBytes, err := hex.DecodeString(StripHex(c.String("packet"))) + packetBytes, err := hex.DecodeString(StripHex(ibcPacketFlag)) if err != nil { - return errors.New(cmn.Fmt("Packet (%v) is invalid hex: %v", c.String("packet"), err)) + return errors.Errorf("Packet (%v) is invalid hex: %v\n", ibcPacketFlag, err) } - proofBytes, err := hex.DecodeString(StripHex(c.String("proof"))) + + proofBytes, err := hex.DecodeString(StripHex(ibcProofFlag)) if err != nil { - return errors.New(cmn.Fmt("Proof (%v) is invalid hex: %v", c.String("proof"), err)) + return errors.Errorf("Proof (%v) is invalid hex: %v\n", ibcProofFlag, err) } var packet ibc.Packet proof := new(merkle.IAVLProof) - if err := wire.ReadBinaryBytes(packetBytes, &packet); err != nil { - return errors.New(cmn.Fmt("Error unmarshalling packet: %v", err)) + err = wire.ReadBinaryBytes(packetBytes, &packet) + if err != nil { + return errors.Errorf("Error unmarshalling packet: %v\n", err) } - if err := wire.ReadBinaryBytes(proofBytes, &proof); err != nil { - return errors.New(cmn.Fmt("Error unmarshalling proof: %v", err)) + + err = wire.ReadBinaryBytes(proofBytes, &proof) + if err != nil { + return errors.Errorf("Error unmarshalling proof: %v\n", err) } ibcTx := ibc.IBCPacketPostTx{ @@ -309,12 +254,12 @@ func cmdIBCPacketPostTx(c *cli.Context) error { ibc.IBCTx `json:"unwrap"` }{ibcTx})) - return AppTx(c.Parent().Parent(), "IBC", data) + return AppTx("IBC", data) } -func getIBCSequence(c *cli.Context) (uint64, error) { - if c.IsSet("sequence") { - return uint64(c.Int("sequence")), nil +func ibcSequenceCmd() (uint64, error) { + if ibcSequenceFlag >= 0 { + return uint64(ibcSequenceFlag), nil } // TODO: get sequence diff --git a/cmd/commands/init.go b/cmd/commands/init.go index e60e7d130..f7fae72e6 100644 --- a/cmd/commands/init.go +++ b/cmd/commands/init.go @@ -2,26 +2,37 @@ package commands import ( "io/ioutil" + "os" "path" - "github.com/urfave/cli" + "github.com/spf13/cobra" cmn "github.com/tendermint/go-common" ) -var InitCmd = cli.Command{ - Name: "init", - Usage: "Initialize a basecoin blockchain", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdInit(c) - }, - Flags: []cli.Flag{ - ChainIDFlag, - }, +//commands +var ( + InitCmd = &cobra.Command{ + Use: "init", + Short: "Initialize a basecoin blockchain", + RunE: initCmd, + } +) + +// returns 1 iff it set a file, otherwise 0 (so we can add them) +func setupFile(path, data string, perm os.FileMode) (int, error) { + _, err := os.Stat(path) + if !os.IsNotExist(err) { //note, os.IsExist(err) != !os.IsNotExist(err) + return 0, nil + } + err = ioutil.WriteFile(path, []byte(data), perm) + if err != nil { + return 0, err + } + return 1, nil } -func cmdInit(c *cli.Context) error { +func initCmd(cmd *cobra.Command, args []string) error { rootDir := BasecoinRoot("") cmn.EnsureDir(rootDir, 0777) @@ -32,25 +43,33 @@ func cmdInit(c *cli.Context) error { key1File := path.Join(rootDir, "key.json") key2File := path.Join(rootDir, "key2.json") - if err := ioutil.WriteFile(genesisFile, []byte(genesisJSON), 0644); err != nil { + mod1, err := setupFile(genesisFile, GenesisJSON, 0644) + if err != nil { return err } - if err := ioutil.WriteFile(privValFile, []byte(privValJSON), 0400); err != nil { + mod2, err := setupFile(privValFile, PrivValJSON, 0400) + if err != nil { return err } - if err := ioutil.WriteFile(key1File, []byte(key1JSON), 0400); err != nil { + mod3, err := setupFile(key1File, Key1JSON, 0400) + if err != nil { return err } - if err := ioutil.WriteFile(key2File, []byte(key2JSON), 0400); err != nil { + mod4, err := setupFile(key2File, Key2JSON, 0400) + if err != nil { return err } - log.Notice("Initialized Basecoin", "genesis", genesisFile, "key", key1File) + if (mod1 + mod2 + mod3 + mod4) > 0 { + log.Notice("Initialized Basecoin", "genesis", genesisFile, "key", key1File) + } else { + log.Notice("Already initialized", "priv_validator", privValFile) + } return nil } -const privValJSON = `{ +var PrivValJSON = `{ "address": "7A956FADD20D3A5B2375042B2959F8AB172A058F", "last_height": 0, "last_round": 0, @@ -67,7 +86,7 @@ const privValJSON = `{ ] }` -const genesisJSON = `{ +var GenesisJSON = `{ "app_hash": "", "chain_id": "test_chain_id", "genesis_time": "0001-01-01T00:00:00.000Z", @@ -97,7 +116,7 @@ const genesisJSON = `{ } }` -const key1JSON = `{ +var Key1JSON = `{ "address": "1B1BE55F969F54064628A63B9559E7C21C925165", "priv_key": { "type": "ed25519", @@ -109,7 +128,7 @@ const key1JSON = `{ } }` -const key2JSON = `{ +var Key2JSON = `{ "address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090", "priv_key": { "type": "ed25519", diff --git a/cmd/commands/key.go b/cmd/commands/key.go index 124f390dd..91678f625 100644 --- a/cmd/commands/key.go +++ b/cmd/commands/key.go @@ -8,31 +8,27 @@ import ( "path" "strings" - "github.com/urfave/cli" + //"github.com/pkg/errors" + "github.com/spf13/cobra" - cmn "github.com/tendermint/go-common" "github.com/tendermint/go-crypto" ) +//commands var ( - KeyCmd = cli.Command{ - Name: "key", - Usage: "Manage keys", - ArgsUsage: "", - Subcommands: []cli.Command{NewKeyCmd}, + KeyCmd = &cobra.Command{ + Use: "key", + Short: "Manage keys", } - NewKeyCmd = cli.Command{ - Name: "new", - Usage: "Create a new private key", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdNewKey(c) - }, + NewKeyCmd = &cobra.Command{ + Use: "new", + Short: "Create a new private key", + RunE: newKeyCmd, } ) -func cmdNewKey(c *cli.Context) error { +func newKeyCmd(cmd *cobra.Command, args []string) error { key := genKey() keyJSON, err := json.MarshalIndent(key, "", "\t") if err != nil { @@ -42,6 +38,11 @@ func cmdNewKey(c *cli.Context) error { return nil } +func init() { + //register commands + KeyCmd.AddCommand(NewKeyCmd) +} + //--------------------------------------------- // simple implementation of a key @@ -84,16 +85,18 @@ func genKey() *Key { } } -func LoadKey(keyFile string) *Key { +func LoadKey(keyFile string) (*Key, error) { filePath := path.Join(BasecoinRoot(""), keyFile) keyJSONBytes, err := ioutil.ReadFile(filePath) if err != nil { - cmn.Exit(err.Error()) + return nil, err } + key := new(Key) err = json.Unmarshal(keyJSONBytes, key) if err != nil { - cmn.Exit(cmn.Fmt("Error reading key from %v: %v\n", filePath, err)) + return nil, fmt.Errorf("Error reading key from %v: %v\n", filePath, err) //never stack trace } - return key + + return key, nil } diff --git a/cmd/commands/plugin_util.go b/cmd/commands/plugin_util.go new file mode 100644 index 000000000..082ff2669 --- /dev/null +++ b/cmd/commands/plugin_util.go @@ -0,0 +1,41 @@ +package commands + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/tendermint/basecoin/types" +) + +type plugin struct { + name string + newPlugin func() types.Plugin +} + +var plugins = []plugin{} + +// RegisterStartPlugin is used to enable a plugin +func RegisterStartPlugin(name string, newPlugin func() types.Plugin) { + plugins = append(plugins, plugin{name: name, newPlugin: newPlugin}) +} + +// Register a subcommand of QueryCmd for plugin specific query functionality +func RegisterQuerySubcommand(cmd *cobra.Command) { + QueryCmd.AddCommand(cmd) +} + +// Register a subcommand of TxCmd to craft transactions for plugins +func RegisterTxSubcommand(cmd *cobra.Command) { + TxCmd.AddCommand(cmd) +} + +//Returns a version command based on version input +func QuickVersionCmd(version string) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Show version info", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(version) + }, + } +} diff --git a/cmd/commands/query.go b/cmd/commands/query.go index 231d32f9f..7d79c2f1f 100644 --- a/cmd/commands/query.go +++ b/cmd/commands/query.go @@ -2,97 +2,96 @@ package commands import ( "encoding/hex" - "errors" "fmt" "strconv" - "github.com/urfave/cli" + "github.com/pkg/errors" + "github.com/spf13/cobra" - cmn "github.com/tendermint/go-common" "github.com/tendermint/go-merkle" "github.com/tendermint/go-wire" tmtypes "github.com/tendermint/tendermint/types" ) +//commands var ( - QueryCmd = cli.Command{ - Name: "query", - Usage: "Query the merkle tree", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdQuery(c) - }, - Flags: []cli.Flag{ - NodeFlag, - }, + QueryCmd = &cobra.Command{ + Use: "query [key]", + Short: "Query the merkle tree", + RunE: queryCmd, } - AccountCmd = cli.Command{ - Name: "account", - Usage: "Get details of an account", - ArgsUsage: "
", - Action: func(c *cli.Context) error { - return cmdAccount(c) - }, - Flags: []cli.Flag{ - NodeFlag, - }, + AccountCmd = &cobra.Command{ + Use: "account [address]", + Short: "Get details of an account", + RunE: accountCmd, } - BlockCmd = cli.Command{ - Name: "block", - Usage: "Get the header and commit of a block", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdBlock(c) - }, - Flags: []cli.Flag{ - NodeFlag, - }, + BlockCmd = &cobra.Command{ + Use: "block [height]", + Short: "Get the header and commit of a block", + RunE: blockCmd, } - VerifyCmd = cli.Command{ - Name: "verify", - Usage: "Verify the IAVL proof", - Action: func(c *cli.Context) error { - return cmdVerify(c) - }, - Flags: []cli.Flag{ - ProofFlag, - KeyFlag, - ValueFlag, - RootFlag, - }, + VerifyCmd = &cobra.Command{ + Use: "verify", + Short: "Verify the IAVL proof", + RunE: verifyCmd, } ) -// Register a subcommand of QueryCmd for plugin specific query functionality -func RegisterQuerySubcommand(cmd cli.Command) { - QueryCmd.Subcommands = append(QueryCmd.Subcommands, cmd) +//flags +var ( + nodeFlag string + proofFlag string + keyFlag string + valueFlag string + rootFlag string +) + +func init() { + + commonFlags := []Flag2Register{ + {&nodeFlag, "node", "tcp://localhost:46657", "Tendermint RPC address"}, + } + + verifyFlags := []Flag2Register{ + {&proofFlag, "proof", "", "hex-encoded IAVL proof"}, + {&keyFlag, "key", "", "key to the IAVL tree"}, + {&valueFlag, "value", "", "value in the IAVL tree"}, + {&rootFlag, "root", "", "root hash of the IAVL tree"}, + } + + RegisterFlags(QueryCmd, commonFlags) + RegisterFlags(AccountCmd, commonFlags) + RegisterFlags(BlockCmd, commonFlags) + RegisterFlags(VerifyCmd, verifyFlags) } -func cmdQuery(c *cli.Context) error { - if len(c.Args()) != 1 { - return errors.New("query command requires an argument ([key])") +func queryCmd(cmd *cobra.Command, args []string) error { + + if len(args) != 1 { + return fmt.Errorf("query command requires an argument ([key])") //never stack trace } - keyString := c.Args()[0] + + keyString := args[0] key := []byte(keyString) if isHex(keyString) { // convert key to bytes var err error key, err = hex.DecodeString(StripHex(keyString)) if err != nil { - return errors.New(cmn.Fmt("Query key (%v) is invalid hex: %v", keyString, err)) + return errors.Errorf("Query key (%v) is invalid hex: %v\n", keyString, err) } } - resp, err := Query(c.String("node"), key) + resp, err := Query(nodeFlag, key) if err != nil { - return err + return errors.Errorf("Query returns error: %v\n", err) } if !resp.Code.IsOK() { - return errors.New(cmn.Fmt("Query for key (%v) returned non-zero code (%v): %v", keyString, resp.Code, resp.Log)) + return errors.Errorf("Query for key (%v) returned non-zero code (%v): %v", keyString, resp.Code, resp.Log) } val := resp.Value @@ -104,23 +103,24 @@ func cmdQuery(c *cli.Context) error { Proof []byte `json:"proof"` Height uint64 `json:"height"` }{val, proof, height}))) - return nil } -func cmdAccount(c *cli.Context) error { - if len(c.Args()) != 1 { - return errors.New("account command requires an argument ([address])") +func accountCmd(cmd *cobra.Command, args []string) error { + + if len(args) != 1 { + return fmt.Errorf("account command requires an argument ([address])") //never stack trace } - addrHex := StripHex(c.Args()[0]) + + addrHex := StripHex(args[0]) // convert destination address to bytes addr, err := hex.DecodeString(addrHex) if err != nil { - return errors.New(cmn.Fmt("Account address (%v) is invalid hex: %v", addrHex, err)) + return errors.Errorf("Account address (%v) is invalid hex: %v\n", addrHex, err) } - acc, err := getAcc(c.String("node"), addr) + acc, err := getAcc(nodeFlag, addr) if err != nil { return err } @@ -128,17 +128,19 @@ func cmdAccount(c *cli.Context) error { return nil } -func cmdBlock(c *cli.Context) error { - if len(c.Args()) != 1 { - return errors.New("block command requires an argument ([height])") - } - heightString := c.Args()[0] - height, err := strconv.Atoi(heightString) - if err != nil { - return errors.New(cmn.Fmt("Height must be an int, got %v: %v", heightString, err)) +func blockCmd(cmd *cobra.Command, args []string) error { + + if len(args) != 1 { + return fmt.Errorf("block command requires an argument ([height])") //never stack trace } - header, commit, err := getHeaderAndCommit(c, height) + heightString := args[0] + height, err := strconv.Atoi(heightString) + if err != nil { + return errors.Errorf("Height must be an int, got %v: %v\n", heightString, err) + } + + header, commit, err := getHeaderAndCommit(nodeFlag, height) if err != nil { return err } @@ -156,7 +158,6 @@ func cmdBlock(c *cli.Context) error { Commit: commit, }, }))) - return nil } @@ -170,15 +171,16 @@ type BlockJSON struct { Commit *tmtypes.Commit `json:"commit"` } -func cmdVerify(c *cli.Context) error { - keyString, valueString := c.String("key"), c.String("value") +func verifyCmd(cmd *cobra.Command, args []string) error { + + keyString, valueString := keyFlag, valueFlag var err error key := []byte(keyString) if isHex(keyString) { key, err = hex.DecodeString(StripHex(keyString)) if err != nil { - return errors.New(cmn.Fmt("Key (%v) is invalid hex: %v", keyString, err)) + return errors.Errorf("Key (%v) is invalid hex: %v\n", keyString, err) } } @@ -186,23 +188,23 @@ func cmdVerify(c *cli.Context) error { if isHex(valueString) { value, err = hex.DecodeString(StripHex(valueString)) if err != nil { - return errors.New(cmn.Fmt("Value (%v) is invalid hex: %v", valueString, err)) + return errors.Errorf("Value (%v) is invalid hex: %v\n", valueString, err) } } - root, err := hex.DecodeString(StripHex(c.String("root"))) + root, err := hex.DecodeString(StripHex(rootFlag)) if err != nil { - return errors.New(cmn.Fmt("Root (%v) is invalid hex: %v", c.String("root"), err)) + return errors.Errorf("Root (%v) is invalid hex: %v\n", rootFlag, err) } - proofBytes, err := hex.DecodeString(StripHex(c.String("proof"))) + proofBytes, err := hex.DecodeString(StripHex(proofFlag)) if err != nil { - return errors.New(cmn.Fmt("Proof (%v) is invalid hex: %v", c.String("proof"), err)) + return errors.Errorf("Proof (%v) is invalid hex: %v\n", proofFlag, err) } proof, err := merkle.ReadProof(proofBytes) if err != nil { - return errors.New(cmn.Fmt("Error unmarshalling proof: %v", err)) + return errors.Errorf("Error unmarshalling proof: %v\n", err) } if proof.Verify(key, value, root) { diff --git a/cmd/commands/reset.go b/cmd/commands/reset.go index d9688fd85..d1b4dfbbc 100644 --- a/cmd/commands/reset.go +++ b/cmd/commands/reset.go @@ -1,47 +1,25 @@ package commands import ( - "os" "path" - "github.com/urfave/cli" + "github.com/spf13/cobra" + tmcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" tmcfg "github.com/tendermint/tendermint/config/tendermint" - types "github.com/tendermint/tendermint/types" ) -var UnsafeResetAllCmd = cli.Command{ - Name: "unsafe_reset_all", - Usage: "Reset all blockchain data", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdUnsafeResetAll(c) - }, +var UnsafeResetAllCmd = &cobra.Command{ + Use: "unsafe_reset_all", + Short: "Reset all blockchain data", + RunE: unsafeResetAllCmd, } -func cmdUnsafeResetAll(c *cli.Context) error { +func unsafeResetAllCmd(cmd *cobra.Command, args []string) error { basecoinDir := BasecoinRoot("") tmDir := path.Join(basecoinDir) tmConfig := tmcfg.GetConfig(tmDir) - // Get and Reset PrivValidator - var privValidator *types.PrivValidator - privValidatorFile := tmConfig.GetString("priv_validator_file") - if _, err := os.Stat(privValidatorFile); err == nil { - privValidator = types.LoadPrivValidator(privValidatorFile) - privValidator.Reset() - log.Notice("Reset PrivValidator", "file", privValidatorFile) - } else { - privValidator = types.GenPrivValidator() - privValidator.SetFile(privValidatorFile) - privValidator.Save() - log.Notice("Generated PrivValidator", "file", privValidatorFile) - } - - // Remove all tendermint data - tmDataDir := tmConfig.GetString("db_dir") - os.RemoveAll(tmDataDir) - log.Notice("Removed all data", "dir", tmDataDir) - + tmcmd.ResetAll(tmConfig, log) return nil } diff --git a/cmd/commands/start.go b/cmd/commands/start.go index 1e6861ac0..4578738ce 100644 --- a/cmd/commands/start.go +++ b/cmd/commands/start.go @@ -1,12 +1,12 @@ package commands import ( - "errors" "fmt" "os" "path" - "github.com/urfave/cli" + "github.com/pkg/errors" + "github.com/spf13/cobra" "github.com/tendermint/abci/server" cmn "github.com/tendermint/go-common" @@ -18,50 +18,48 @@ import ( tmtypes "github.com/tendermint/tendermint/types" "github.com/tendermint/basecoin/app" - "github.com/tendermint/basecoin/types" ) +var StartCmd = &cobra.Command{ + Use: "start", + Short: "Start basecoin", + RunE: startCmd, +} + +//flags +var ( + addrFlag string + eyesFlag string + dirFlag string + withoutTendermintFlag bool +) + +// TODO: move to config file const EyesCacheSize = 10000 -var StartCmd = cli.Command{ - Name: "start", - Usage: "Start basecoin", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdStart(c) - }, - Flags: []cli.Flag{ - AddrFlag, - EyesFlag, - WithoutTendermintFlag, - ChainIDFlag, - }, +func init() { + + flags := []Flag2Register{ + {&addrFlag, "address", "tcp://0.0.0.0:46658", "Listen address"}, + {&eyesFlag, "eyes", "local", "MerkleEyes address, or 'local' for embedded"}, + {&dirFlag, "dir", ".", "Root directory"}, + {&withoutTendermintFlag, "without-tendermint", false, "Run Tendermint in-process with the App"}, + } + RegisterFlags(StartCmd, flags) } -type plugin struct { - name string - newPlugin func() types.Plugin -} - -var plugins = []plugin{} - -// RegisterStartPlugin is used to enable a plugin -func RegisterStartPlugin(name string, newPlugin func() types.Plugin) { - plugins = append(plugins, plugin{name: name, newPlugin: newPlugin}) -} - -func cmdStart(c *cli.Context) error { +func startCmd(cmd *cobra.Command, args []string) error { basecoinDir := BasecoinRoot("") // Connect to MerkleEyes var eyesCli *eyes.Client - if c.String("eyes") == "local" { + if eyesFlag == "local" { eyesCli = eyes.NewLocalClient(path.Join(basecoinDir, "data", "merkleeyes.db"), EyesCacheSize) } else { var err error - eyesCli, err = eyes.NewClient(c.String("eyes")) + eyesCli, err = eyes.NewClient(eyesFlag) if err != nil { - return errors.New("connect to MerkleEyes: " + err.Error()) + return errors.Errorf("Error connecting to MerkleEyes: %v\n", err) } } @@ -84,7 +82,7 @@ func cmdStart(c *cli.Context) error { if _, err := os.Stat(genesisFile); err == nil { err := basecoinApp.LoadGenesis(genesisFile) if err != nil { - return errors.New(cmn.Fmt("%+v", err)) + return errors.Errorf("Error in LoadGenesis: %v\n", err) } } else { fmt.Printf("No genesis file at %s, skipping...\n", genesisFile) @@ -92,40 +90,38 @@ func cmdStart(c *cli.Context) error { } chainID := basecoinApp.GetState().GetChainID() - if c.Bool("without-tendermint") { + if withoutTendermintFlag { log.Notice("Starting Basecoin without Tendermint", "chain_id", chainID) // run just the abci app/server - return startBasecoinABCI(c, basecoinApp) + return startBasecoinABCI(basecoinApp) } else { log.Notice("Starting Basecoin with Tendermint", "chain_id", chainID) // start the app with tendermint in-process return startTendermint(basecoinDir, basecoinApp) } - - return nil } -func startBasecoinABCI(c *cli.Context, basecoinApp *app.Basecoin) error { +func startBasecoinABCI(basecoinApp *app.Basecoin) error { + // Start the ABCI listener - svr, err := server.NewServer(c.String("address"), "socket", basecoinApp) + svr, err := server.NewServer(addrFlag, "socket", basecoinApp) if err != nil { - return errors.New("create listener: " + err.Error()) + return errors.Errorf("Error creating listener: %v\n", err) } + // Wait forever cmn.TrapSignal(func() { // Cleanup svr.Stop() }) return nil - } func startTendermint(dir string, basecoinApp *app.Basecoin) error { + // Get configuration tmConfig := tmcfg.GetConfig(dir) - // logger.SetLogLevel("notice") //config.GetString("log_level")) - // parseFlags(config, args[1:]) // Command line overrides // Create & start tendermint node @@ -143,6 +139,5 @@ func startTendermint(dir string, basecoinApp *app.Basecoin) error { // Cleanup n.Stop() }) - return nil } diff --git a/cmd/commands/tx.go b/cmd/commands/tx.go index 338bbcb22..2e12be06e 100644 --- a/cmd/commands/tx.go +++ b/cmd/commands/tx.go @@ -2,101 +2,111 @@ package commands import ( "encoding/hex" - "errors" "fmt" - "github.com/urfave/cli" + "github.com/pkg/errors" + "github.com/spf13/cobra" "github.com/tendermint/basecoin/types" crypto "github.com/tendermint/go-crypto" - cmn "github.com/tendermint/go-common" client "github.com/tendermint/go-rpc/client" wire "github.com/tendermint/go-wire" ctypes "github.com/tendermint/tendermint/rpc/core/types" ) -var TxFlags = []cli.Flag{ - NodeFlag, - ChainIDFlag, - - FromFlag, - - AmountFlag, - GasFlag, - FeeFlag, - SeqFlag, -} - +//commands var ( - TxCmd = cli.Command{ - Name: "tx", - Usage: "Create, sign, and broadcast a transaction", - ArgsUsage: "", - Subcommands: []cli.Command{ - SendTxCmd, - AppTxCmd, - IbcTxCmd, - }, + TxCmd = &cobra.Command{ + Use: "tx", + Short: "Create, sign, and broadcast a transaction", } - SendTxCmd = cli.Command{ - Name: "send", - Usage: "a SendTx transaction, for sending tokens around", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdSendTx(c) - }, - Flags: append(TxFlags, ToFlag), + SendTxCmd = &cobra.Command{ + Use: "send", + Short: "A SendTx transaction, for sending tokens around", + RunE: sendTxCmd, } - AppTxCmd = cli.Command{ - Name: "app", - Usage: "an AppTx transaction, for sending raw data to plugins", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdAppTx(c) - }, - Flags: append(TxFlags, NameFlag, DataFlag), - // Subcommands are dynamically registered with plugins as needed - Subcommands: []cli.Command{}, + AppTxCmd = &cobra.Command{ + Use: "app", + Short: "An AppTx transaction, for sending raw data to plugins", + RunE: appTxCmd, } ) -// Register a subcommand of TxCmd to craft transactions for plugins -func RegisterTxSubcommand(cmd cli.Command) { - TxCmd.Subcommands = append(TxCmd.Subcommands, cmd) +var ( + //persistent flags + txNodeFlag string + amountFlag string + fromFlag string + seqFlag int + gasFlag int + feeFlag string + chainIDFlag string + + //non-persistent flags + toFlag string + dataFlag string + nameFlag string +) + +func init() { + + // register flags + cmdTxFlags := []Flag2Register{ + {&txNodeFlag, "node", "tcp://localhost:46657", "Tendermint RPC address"}, + {&chainIDFlag, "chain_id", "test_chain_id", "ID of the chain for replay protection"}, + {&fromFlag, "from", "key.json", "Path to a private key to sign the transaction"}, + {&amountFlag, "amount", "", "Coins to send in transaction of the format ,,... (eg: 1btc,2gold,5silver)"}, + {&gasFlag, "gas", 0, "The amount of gas for the transaction"}, + {&feeFlag, "fee", "", "Coins for the transaction fee of the format "}, + {&seqFlag, "sequence", -1, "Sequence number for the account (-1 to autocalculate)"}, + } + + sendTxFlags := []Flag2Register{ + {&toFlag, "to", "", "Destination address for the transaction"}, + } + + appTxFlags := []Flag2Register{ + {&nameFlag, "name", "", "Plugin to send the transaction to"}, + {&dataFlag, "data", "", "Data to send with the transaction"}, + } + + RegisterPersistentFlags(TxCmd, cmdTxFlags) + RegisterFlags(SendTxCmd, sendTxFlags) + RegisterFlags(AppTxCmd, appTxFlags) + + //register commands + TxCmd.AddCommand(SendTxCmd, AppTxCmd) } -func cmdSendTx(c *cli.Context) error { - toHex := c.String("to") - fromFile := c.String("from") - amount := c.String("amount") - gas := int64(c.Int("gas")) - fee := c.String("fee") - chainID := c.String("chain_id") +func sendTxCmd(cmd *cobra.Command, args []string) error { // convert destination address to bytes - to, err := hex.DecodeString(StripHex(toHex)) + to, err := hex.DecodeString(StripHex(toFlag)) if err != nil { - return errors.New("To address is invalid hex: " + err.Error()) + return errors.Errorf("To address is invalid hex: %v\n", err) } // load the priv key - privKey := LoadKey(fromFile) + privKey, err := LoadKey(fromFlag) + if err != nil { + return err + } // get the sequence number for the tx - sequence, err := getSeq(c, privKey.Address[:]) + sequence, err := getSeq(privKey.Address[:]) if err != nil { return err } //parse the fee and amounts into coin types - feeCoin, err := types.ParseCoin(fee) + feeCoin, err := types.ParseCoin(feeFlag) if err != nil { return err } - amountCoins, err := types.ParseCoins(amount) + amountCoins, err := types.ParseCoins(amountFlag) if err != nil { return err } @@ -105,21 +115,21 @@ func cmdSendTx(c *cli.Context) error { input := types.NewTxInput(privKey.PubKey, amountCoins, sequence) output := newOutput(to, amountCoins) tx := &types.SendTx{ - Gas: gas, + Gas: int64(gasFlag), Fee: feeCoin, Inputs: []types.TxInput{input}, Outputs: []types.TxOutput{output}, } // sign that puppy - signBytes := tx.SignBytes(chainID) + signBytes := tx.SignBytes(chainIDFlag) tx.Inputs[0].Signature = crypto.SignatureS{privKey.Sign(signBytes)} fmt.Println("Signed SendTx:") fmt.Println(string(wire.JSONBytes(tx))) // broadcast the transaction to tendermint - data, log, err := broadcastTx(c, tx) + data, log, err := broadcastTx(tx) if err != nil { return err } @@ -127,100 +137,103 @@ func cmdSendTx(c *cli.Context) error { return nil } -func cmdAppTx(c *cli.Context) error { +func appTxCmd(cmd *cobra.Command, args []string) error { // convert data to bytes - dataString := c.String("data") - data := []byte(dataString) - if isHex(dataString) { - data, _ = hex.DecodeString(dataString) + data := []byte(dataFlag) + if isHex(dataFlag) { + data, _ = hex.DecodeString(dataFlag) } - name := c.String("name") - return AppTx(c, name, data) + name := nameFlag + return AppTx(name, data) } -func AppTx(c *cli.Context, name string, data []byte) error { - fromFile := c.String("from") - amount := c.String("amount") - fee := c.String("fee") - gas := int64(c.Int("gas")) - chainID := c.String("chain_id") +func AppTx(name string, data []byte) error { - privKey := LoadKey(fromFile) + privKey, err := LoadKey(fromFlag) + if err != nil { + return err + } - sequence, err := getSeq(c, privKey.Address[:]) + sequence, err := getSeq(privKey.Address[:]) if err != nil { return err } //parse the fee and amounts into coin types - feeCoin, err := types.ParseCoin(fee) + feeCoin, err := types.ParseCoin(feeFlag) if err != nil { return err } - amountCoins, err := types.ParseCoins(amount) + + amountCoins, err := types.ParseCoins(amountFlag) if err != nil { return err } input := types.NewTxInput(privKey.PubKey, amountCoins, sequence) tx := &types.AppTx{ - Gas: gas, + Gas: int64(gasFlag), Fee: feeCoin, Name: name, Input: input, Data: data, } - tx.Input.Signature = crypto.SignatureS{privKey.Sign(tx.SignBytes(chainID))} + tx.Input.Signature = crypto.SignatureS{privKey.Sign(tx.SignBytes(chainIDFlag))} fmt.Println("Signed AppTx:") fmt.Println(string(wire.JSONBytes(tx))) - data, log, err := broadcastTx(c, tx) + data, log, err := broadcastTx(tx) if err != nil { return err } fmt.Printf("Response: %X ; %s\n", data, log) - return nil } // broadcast the transaction to tendermint -func broadcastTx(c *cli.Context, tx types.Tx) ([]byte, string, error) { +func broadcastTx(tx types.Tx) ([]byte, string, error) { + tmResult := new(ctypes.TMResult) - tmAddr := c.String("node") - uriClient := client.NewURIClient(tmAddr) + uriClient := client.NewURIClient(txNodeFlag) // Don't you hate having to do this? // How many times have I lost an hour over this trick?! txBytes := []byte(wire.BinaryBytes(struct { types.Tx `json:"unwrap"` }{tx})) + _, err := uriClient.Call("broadcast_tx_commit", map[string]interface{}{"tx": txBytes}, tmResult) if err != nil { - return nil, "", errors.New(cmn.Fmt("Error on broadcast tx: %v", err)) + return nil, "", errors.Errorf("Error on broadcast tx: %v", err) } + res := (*tmResult).(*ctypes.ResultBroadcastTxCommit) + // if it fails check, we don't even get a delivertx back! if !res.CheckTx.Code.IsOK() { r := res.CheckTx - return nil, "", errors.New(cmn.Fmt("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log)) + return nil, "", errors.Errorf("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log) } + if !res.DeliverTx.Code.IsOK() { r := res.DeliverTx - return nil, "", errors.New(cmn.Fmt("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log)) + return nil, "", errors.Errorf("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log) } + return res.DeliverTx.Data, res.DeliverTx.Log, 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 +func getSeq(address []byte) (int, error) { + + if seqFlag >= 0 { + return seqFlag, nil } - tmAddr := c.String("node") - acc, err := getAcc(tmAddr, address) + + acc, err := getAcc(txNodeFlag, address) if err != nil { return 0, err } @@ -232,5 +245,4 @@ func newOutput(to []byte, amount types.Coins) types.TxOutput { Address: to, Coins: amount, } - } diff --git a/cmd/commands/utils.go b/cmd/commands/utils.go index efff67e0b..0f263d82a 100644 --- a/cmd/commands/utils.go +++ b/cmd/commands/utils.go @@ -2,10 +2,13 @@ package commands import ( "encoding/hex" - "errors" + "fmt" "os" + "path" - "github.com/urfave/cli" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/tendermint/basecoin/state" "github.com/tendermint/basecoin/types" @@ -18,16 +21,96 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) +//This variable can be overwritten by plugin applications +// if they require a different working directory +var DefaultHome = ".basecoin" + func BasecoinRoot(rootDir string) string { if rootDir == "" { rootDir = os.Getenv("BCHOME") } if rootDir == "" { - rootDir = os.Getenv("HOME") + "/.basecoin" + rootDir = path.Join(os.Getenv("HOME"), DefaultHome) } return rootDir } +//Add debugging flag and execute the root command +func ExecuteWithDebug(RootCmd *cobra.Command) { + + var debug bool + RootCmd.SilenceUsage = true + RootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "enables stack trace error messages") + + //note that Execute() prints the error if encountered, so no need to reprint the error, + // only if we want the full stack trace + if err := RootCmd.Execute(); err != nil && debug { + cmn.Exit(fmt.Sprintf("%+v\n", err)) + } +} + +//Quickly registering flags can be quickly achieved through using the utility functions +//RegisterFlags, and RegisterPersistentFlags. Ex: +// flags := []Flag2Register{ +// {&myStringFlag, "mystringflag", "foobar", "description of what this flag does"}, +// {&myBoolFlag, "myboolflag", false, "description of what this flag does"}, +// {&myInt64Flag, "myintflag", 333, "description of what this flag does"}, +// } +// RegisterFlags(MyCobraCmd, flags) +type Flag2Register struct { + Pointer interface{} + Use string + Value interface{} + Desc string +} + +//register flag utils +func RegisterFlags(c *cobra.Command, flags []Flag2Register) { + registerFlags(c, flags, false) +} + +func RegisterPersistentFlags(c *cobra.Command, flags []Flag2Register) { + registerFlags(c, flags, true) +} + +func registerFlags(c *cobra.Command, flags []Flag2Register, persistent bool) { + + var flagset *pflag.FlagSet + if persistent { + flagset = c.PersistentFlags() + } else { + flagset = c.Flags() + } + + for _, f := range flags { + + ok := false + + switch f.Value.(type) { + case string: + if _, ok = f.Pointer.(*string); ok { + flagset.StringVar(f.Pointer.(*string), f.Use, f.Value.(string), f.Desc) + } + case int: + if _, ok = f.Pointer.(*int); ok { + flagset.IntVar(f.Pointer.(*int), f.Use, f.Value.(int), f.Desc) + } + case uint64: + if _, ok = f.Pointer.(*uint64); ok { + flagset.Uint64Var(f.Pointer.(*uint64), f.Use, f.Value.(uint64), f.Desc) + } + case bool: + if _, ok = f.Pointer.(*bool); ok { + flagset.BoolVar(f.Pointer.(*bool), f.Use, f.Value.(bool), f.Desc) + } + } + + if !ok { + panic("improper use of RegisterFlags") + } + } +} + // Returns true for non-empty hex-string prefixed with "0x" func isHex(s string) bool { if len(s) > 2 && s[:2] == "0x" { @@ -58,11 +141,11 @@ func Query(tmAddr string, key []byte) (*abci.ResponseQuery, error) { } _, err := uriClient.Call("abci_query", params, tmResult) if err != nil { - return nil, errors.New(cmn.Fmt("Error calling /abci_query: %v", err)) + return nil, errors.Errorf("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)) + return nil, errors.Errorf("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log) } return &res.Response, nil } @@ -79,28 +162,27 @@ func getAcc(tmAddr string, address []byte) (*types.Account, error) { accountBytes := response.Value if len(accountBytes) == 0 { - return nil, errors.New(cmn.Fmt("Account bytes are empty for address: %X ", address)) + return nil, fmt.Errorf("Account bytes are empty for address: %X ", address) //never stack trace } 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 nil, errors.Errorf("Error reading account %X error: %v", + accountBytes, err.Error()) } return acc, nil } -func getHeaderAndCommit(c *cli.Context, height int) (*tmtypes.Header, *tmtypes.Commit, error) { +func getHeaderAndCommit(tmAddr string, height int) (*tmtypes.Header, *tmtypes.Commit, error) { tmResult := new(ctypes.TMResult) - tmAddr := c.String("node") uriClient := client.NewURIClient(tmAddr) method := "commit" _, err := uriClient.Call(method, map[string]interface{}{"height": height}, tmResult) if err != nil { - return nil, nil, errors.New(cmn.Fmt("Error on %s: %v", method, err)) + return nil, nil, errors.Errorf("Error on %s: %v", method, err) } resCommit := (*tmResult).(*ctypes.ResultCommit) header := resCommit.Header diff --git a/cmd/commands/version.go b/cmd/commands/version.go new file mode 100644 index 000000000..1cbf891e4 --- /dev/null +++ b/cmd/commands/version.go @@ -0,0 +1,17 @@ +package commands + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/tendermint/basecoin/version" +) + +var VersionCmd = &cobra.Command{ + Use: "version", + Short: "Show version info", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(version.Version) + }, +} diff --git a/cmd/counter/cmd.go b/cmd/counter/cmd.go index c125feb4f..451f42a48 100644 --- a/cmd/counter/cmd.go +++ b/cmd/counter/cmd.go @@ -3,46 +3,46 @@ package main import ( "fmt" + "github.com/spf13/cobra" wire "github.com/tendermint/go-wire" - "github.com/urfave/cli" "github.com/tendermint/basecoin/cmd/commands" "github.com/tendermint/basecoin/plugins/counter" "github.com/tendermint/basecoin/types" ) +//commands +var CounterTxCmd = &cobra.Command{ + Use: "counter", + Short: "Create, sign, and broadcast a transaction to the counter plugin", + RunE: counterTxCmd, +} + +//flags +var ( + validFlag bool + countFeeFlag string +) + func init() { + + CounterTxCmd.Flags().BoolVar(&validFlag, "valid", false, "Set valid field in CounterTx") + CounterTxCmd.Flags().StringVar(&countFeeFlag, "countfee", "", "Coins for the counter fee of the format ") + commands.RegisterTxSubcommand(CounterTxCmd) commands.RegisterStartPlugin("counter", func() types.Plugin { return counter.New() }) } -var ( - ValidFlag = cli.BoolFlag{ - Name: "valid", - Usage: "Set valid field in CounterTx", - } +func counterTxCmd(cmd *cobra.Command, args []string) error { - CounterTxCmd = cli.Command{ - Name: "counter", - Usage: "Create, sign, and broadcast a transaction to the counter plugin", - Action: func(c *cli.Context) error { - return cmdCounterTx(c) - }, - Flags: append(commands.TxFlags, ValidFlag), + countFee, err := types.ParseCoins(countFeeFlag) + if err != nil { + return err } -) - -func cmdCounterTx(c *cli.Context) error { - valid := c.Bool("valid") counterTx := counter.CounterTx{ - Valid: valid, - Fee: types.Coins{ - { - Denom: c.String("coin"), - Amount: int64(c.Int("fee")), - }, - }, + Valid: validFlag, + Fee: countFee, } fmt.Println("CounterTx:", string(wire.JSONBytes(counterTx))) @@ -50,5 +50,5 @@ func cmdCounterTx(c *cli.Context) error { data := wire.BinaryBytes(counterTx) name := "counter" - return commands.AppTx(c, name, data) + return commands.AppTx(name, data) } diff --git a/cmd/counter/main.go b/cmd/counter/main.go index 72395a9b0..8a96b50ed 100644 --- a/cmd/counter/main.go +++ b/cmd/counter/main.go @@ -1,23 +1,28 @@ package main import ( - "os" + "github.com/spf13/cobra" "github.com/tendermint/basecoin/cmd/commands" - "github.com/urfave/cli" ) func main() { - app := cli.NewApp() - app.Name = "counter" - app.Usage = "counter [command] [args...]" - app.Version = "0.1.0" - app.Commands = []cli.Command{ + + var RootCmd = &cobra.Command{ + Use: "counter", + Short: "demo plugin for basecoin", + } + + RootCmd.AddCommand( commands.StartCmd, commands.TxCmd, - commands.KeyCmd, commands.QueryCmd, + commands.KeyCmd, + commands.VerifyCmd, + commands.BlockCmd, commands.AccountCmd, - } - app.Run(os.Args) + commands.QuickVersionCmd("0.1.0"), + ) + + commands.ExecuteWithDebug(RootCmd) } diff --git a/demo/start.sh b/demo/start.sh index af91db2a0..d7d9659dc 100644 --- a/demo/start.sh +++ b/demo/start.sh @@ -103,14 +103,14 @@ sleep 3 echo "... registering chain1 on chain2" echo "" # register chain1 on chain2 -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/genesis.json +basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --ibc_chain_id $CHAIN_ID1 --genesis ./data/chain1/genesis.json echo "" echo "... creating egress packet on chain1" echo "" # create a packet on chain1 destined for chain2 PAYLOAD="DEADBEEF" #TODO -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --sequence 1 +basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --ibc_from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --ibc_sequence 1 echo "" echo "... querying for packet data" @@ -162,7 +162,7 @@ echo "" echo "... posting packet from chain1 on chain2" echo "" # post the packet from chain1 to chain2 -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height $HEIGHT --packet 0x$PACKET --proof 0x$PROOF +basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 packet post --ibc_from $CHAIN_ID1 --height $HEIGHT --packet 0x$PACKET --proof 0x$PROOF echo "" echo "... checking if the packet is present on chain2" diff --git a/docs/guide/example-plugin.md b/docs/guide/example-plugin.md index 31aa3479c..a4592b355 100644 --- a/docs/guide/example-plugin.md +++ b/docs/guide/example-plugin.md @@ -6,10 +6,11 @@ Here, we will demonstrate how to extend the blockchain and CLI to support a simp ## Overview -Creating a new plugin and CLI to support it requires a little bit of boilerplate, but not much. -For convenience, we've implemented an extremely simple example plugin that can be easily modified. -The example is under `docs/guide/src/example-plugin`. -To build your own plugin, copy this folder to a new location and start modifying it there. +Creating a new plugin and CLI to support it requires a little bit of +boilerplate, but not much. For convenience, we've implemented an extremely +simple example plugin that can be easily modified. The example is under +`docs/guide/src/example-plugin`. To build your own plugin, copy this folder to +a new location and start modifying it there. Let's take a look at the files in `docs/guide/src/example-plugin`: @@ -25,94 +26,104 @@ The `main.go` is very simple and does not need to be changed: ```golang func main() { - app := cli.NewApp() - app.Name = "example-plugin" - app.Usage = "example-plugin [command] [args...]" - app.Version = "0.1.0" - app.Commands = []cli.Command{ + //Initialize example-plugin root command + var RootCmd = &cobra.Command{ + Use: "example-plugin", + Short: "example-plugin usage description", + } + + //Add the default basecoin commands to the root command + RootCmd.AddCommand( + commands.InitCmd, commands.StartCmd, commands.TxCmd, - commands.KeyCmd, commands.QueryCmd, + commands.KeyCmd, + commands.VerifyCmd, + commands.BlockCmd, commands.AccountCmd, - } - app.Run(os.Args) + commands.UnsafeResetAllCmd, + ) + + //Run the root command + commands.ExecuteWithDebug(RootCmd) } ``` -It creates the CLI, exactly like the `basecoin` one. -However, if we want our plugin to be active, -we need to make sure it is registered with the application. -In addition, if we want to send transactions to our plugin, -we need to add a new command to the CLI. -This is where the `cmd.go` comes in. +It creates the CLI, exactly like the `basecoin` one. However, if we want our +plugin to be active, we need to make sure it is registered with the +application. In addition, if we want to send transactions to our plugin, we +need to add a new command to the CLI. This is where the `cmd.go` comes in. ### cmd.go -First, we register the plugin: +First we define the new CLI command and associated flag variables. +```golang +var ( + //CLI Flags + validFlag bool + + //CLI Plugin Commands + ExamplePluginTxCmd = &cobra.Command{ + Use: "example", + Short: "Create, sign, and broadcast a transaction to the example plugin", + RunE: examplePluginTxCmd, + } +) +``` + +Next within the `init` function we register our plugin's flags and register our +custom plugin command with the root command. This creates a new subcommand +under `tx` (defined below), and ensures the plugin is activated when we start +the app. ```golang func init() { + + //Set the Plugin Flags + ExamplePluginTxCmd.Flags().BoolVar(&validFlag, "valid", false, "Set this to make transaction valid") + + //Register a plugin specific CLI command as a subcommand of the tx command commands.RegisterTxSubcommand(ExamplePluginTxCmd) + + //Register the example with basecoin at start commands.RegisterStartPlugin("example-plugin", func() types.Plugin { return NewExamplePlugin() }) } ``` -This creates a new subcommand under `tx` (defined below), -and ensures the plugin is activated when we start the app. -Now we actually define the new command: +We now define the actual function which is called by our CLI command. + +```golang +func examplePluginTxCmd(cmd *cobra.Command, args []string) error { + exampleTx := ExamplePluginTx{validFlag} + exampleTxBytes := wire.BinaryBytes(exampleTx) + return commands.AppTx("example-plugin", exampleTxBytes) +} +``` + +Our function is a simple command with one boolean flag. However, it actually +inherits the persistent flags from the Basecoin framework. These persistent +flags use pointers to these variables stored in `cmd/commands/tx.go`: ```golang var ( - ExampleFlag = cli.BoolFlag{ - Name: "valid", - Usage: "Set this to make the transaction valid", - } - - ExamplePluginTxCmd = cli.Command{ - Name: "example", - Usage: "Create, sign, and broadcast a transaction to the example plugin", - Action: func(c *cli.Context) error { - return cmdExamplePluginTx(c) - }, - Flags: append(commands.TxFlags, ExampleFlag), - } + //persistent flags + txNodeFlag string + amountFlag string + fromFlag string + seqFlag int + gasFlag int + feeFlag string + chainIDFlag string + + //non-persistent flags + toFlag string + dataFlag string + nameFlag string ) - -func cmdExamplePluginTx(c *cli.Context) error { - exampleFlag := c.Bool("valid") - exampleTx := ExamplePluginTx{exampleFlag} - return commands.AppTx(c, "example-plugin", wire.BinaryBytes(exampleTx)) -} ``` -It's a simple command with one flag, which is just a boolean. -However, it actually inherits more flags from the Basecoin framework: - -```golang -Flags: append(commands.TxFlags, ExampleFlag), -``` - -The `commands.TxFlags` is defined in `cmd/commands/tx.go`: - -```golang -var TxFlags = []cli.Flag{ - NodeFlag, - ChainIDFlag, - - FromFlag, - - AmountFlag, - CoinFlag, - GasFlag, - FeeFlag, - SeqFlag, -} -``` - -It adds all the default flags for a Basecoin transaction. - If we now compile and run our program, we can see all the options: ``` @@ -124,48 +135,51 @@ example-plugin tx example --help The output: ``` -NAME: - example-plugin tx example - Create, sign, and broadcast a transaction to the example plugin +Create, sign, and broadcast a transaction to the example plugin -USAGE: - example-plugin tx example [command options] [arguments...] +Usage: + example-plugin tx example [flags] -OPTIONS: - --node value Tendermint RPC address (default: "tcp://localhost:46657") - --chain_id value ID of the chain for replay protection (default: "test_chain_id") - --from value Path to a private key to sign the transaction (default: "key.json") - --amount value Coins to send in transaction of the format ,,... (eg: 1btc,2gold,5silver) - --gas value The amount of gas for the transaction (default: 0) - --fee value Coins for the transaction fee of the format - --sequence value Sequence number for the account (default: 0) - --valid Set this to make the transaction valid +Flags: + --valid Set this to make transaction valid + +Global Flags: + --amount string Coins to send in transaction of the format ,,... (eg: 1btc,2gold,5silver}, + --chain_id string ID of the chain for replay protection (default "test_chain_id") + --fee string Coins for the transaction fee of the format + --from string Path to a private key to sign the transaction (default "key.json") + --gas int The amount of gas for the transaction + --node string Tendermint RPC address (default "tcp://localhost:46657") + --sequence int Sequence number for the account (-1 to autocalculate}, (default -1) ``` Cool, eh? -Before we move on to `plugin.go`, let's look at the `cmdExamplePluginTx` function in `cmd.go`: +Before we move on to `plugin.go`, let's look at the `examplePluginTxCmd` +function in `cmd.go`: ```golang -func cmdExamplePluginTx(c *cli.Context) error { - exampleFlag := c.Bool("valid") - exampleTx := ExamplePluginTx{exampleFlag} - return commands.AppTx(c, "example-plugin", wire.BinaryBytes(exampleTx)) +func examplePluginTxCmd(cmd *cobra.Command, args []string) { + exampleTx := ExamplePluginTx{validFlag} + exampleTxBytes := wire.BinaryBytes(exampleTx) + commands.AppTx("example-plugin", exampleTxBytes) } ``` We read the flag from the CLI library, and then create the example transaction. -Remember that Basecoin itself only knows about two transaction types, `SendTx` and `AppTx`. -All plugin data must be serialized (ie. encoded as a byte-array) -and sent as data in an `AppTx`. The `commands.AppTx` function does this for us - -it creates an `AppTx` with the corresponding data, signs it, and sends it on to the blockchain. +Remember that Basecoin itself only knows about two transaction types, `SendTx` +and `AppTx`. All plugin data must be serialized (ie. encoded as a byte-array) +and sent as data in an `AppTx`. The `commands.AppTx` function does this for us +- it creates an `AppTx` with the corresponding data, signs it, and sends it on +to the blockchain. ### plugin.go -Ok, now we're ready to actually look at the implementation of the plugin in `plugin.go`. -Note I'll leave out some of the methods as they don't serve any purpose for this example, -but are necessary boilerplate. -Your plugin may have additional requirements that utilize these other methods. -Here's what's relevant for us: +Ok, now we're ready to actually look at the implementation of the plugin in +`plugin.go`. Note I'll leave out some of the methods as they don't serve any +purpose for this example, but are necessary boilerplate. Your plugin may have +additional requirements that utilize these other methods. Here's what's +relevant for us: ```golang type ExamplePluginState struct { @@ -199,15 +213,17 @@ func (ep *ExamplePlugin) SetOption(store types.KVStore, key string, value string } func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) { - - // Decode tx + + // Decode txBytes using go-wire. Attempt to write the txBytes to the variable + // tx, if the txBytes have not been properly encoded from a ExamplePluginTx + // struct wire will produce an error. var tx ExamplePluginTx err := wire.ReadBinaryBytes(txBytes, &tx) if err != nil { return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) } - // Validate tx + // Perform Transaction Validation if !tx.Valid { return abci.ErrInternalError.AppendLog("Valid must be true") } @@ -215,8 +231,10 @@ func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txByt // Load PluginState var pluginState ExamplePluginState stateBytes := store.Get(ep.StateKey()) + // If the state does not exist, stateBytes will be initialized + // as an empty byte array with length of zero if len(stateBytes) > 0 { - err = wire.ReadBinaryBytes(stateBytes, &pluginState) + err = wire.ReadBinaryBytes(stateBytes, &pluginState) //decode using go-wire if err != nil { return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error()) } @@ -233,12 +251,11 @@ func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txByt ``` All we're doing here is defining a state and transaction type for our plugin, -and then using the `RunTx` method to define how the transaction updates the state. -Let's break down `RunTx` in parts. First, we deserialize the transaction: - +and then using the `RunTx` method to define how the transaction updates the +state. Let's break down `RunTx` in parts. First, we deserialize the +transaction: ```golang -// Decode tx var tx ExamplePluginTx err := wire.ReadBinaryBytes(txBytes, &tx) if err != nil { @@ -246,27 +263,26 @@ if err != nil { } ``` -The transaction is expected to be serialized according to Tendermint's "wire" format, -as defined in the `github.com/tendermint/go-wire` package. -If it's not encoded properly, we return an error. +The transaction is expected to be serialized according to Tendermint's "wire" +format, as defined in the `github.com/tendermint/go-wire` package. If it's not +encoded properly, we return an error. If the transaction deserializes correctly, we can now check if it's valid: ```golang -// Validate tx if !tx.Valid { return abci.ErrInternalError.AppendLog("Valid must be true") } ``` -The transaction is valid if the `Valid` field is set, otherwise it's not - simple as that. -Finally, we can update the state. In this example, the state simply counts how many valid transactions -we've processed. But the state itself is serialized and kept in some `store`, which is typically a Merkle tree. -So first we have to load the state from the store and deserialize it: +The transaction is valid if the `Valid` field is set, otherwise it's not - +simple as that. Finally, we can update the state. In this example, the state +simply counts how many valid transactions we've processed. But the state itself +is serialized and kept in some `store`, which is typically a Merkle tree. So +first we have to load the state from the store and deserialize it: ```golang -// Load PluginState var pluginState ExamplePluginState stateBytes := store.Get(ep.StateKey()) if len(stateBytes) > 0 { @@ -369,5 +385,5 @@ basecoin CLI to activate the plugin on the blockchain and to send transactions t Hopefully by now you have some ideas for your own plugin, and feel comfortable implementing them. In the [next tutorial](more-examples.md), we tour through some other plugin examples, -adding features for minting new coins, voting, and changing the Tendermint validator set. +addin mple-plugin query ExamplePlugin.Statefeatures for minting new coins, voting, and changing the Tendermint validator set. But first, you may want to learn a bit more about [the design of the plugin system](plugin-design.md) diff --git a/docs/guide/ibc.md b/docs/guide/ibc.md index 9b88c416b..abbf0908f 100644 --- a/docs/guide/ibc.md +++ b/docs/guide/ibc.md @@ -13,7 +13,8 @@ You may also want to see the tutorials on [a simple example plugin](example-plug and the list of [more advanced plugins](more-examples.md). The IBC plugin defines a new set of transactions as subtypes of the `AppTx`. -The plugin's functionality is accessed by setting the `AppTx.Name` field to `"IBC"`, and setting the `Data` field to the serialized IBC transaction type. +The plugin's functionality is accessed by setting the `AppTx.Name` field to `"IBC"`, +and setting the `Data` field to the serialized IBC transaction type. We'll demonstrate exactly how this works below. @@ -33,7 +34,8 @@ contains the votes responsible for committing the previous block, and a field in the block header called `AppHash`, which refers to the Merkle root hash of the application after processing the transactions from the previous block. So, if we want to verify the `AppHash` from height H, we need the signatures from `LastCommit` -at height H+1. (And remember that this `AppHash` only contains the results from all transactions up to and including block H-1) +at height H+1. (And remember that this `AppHash` only contains the results from all +transactions up to and including block H-1) Unlike Proof-of-Work, the light-client protocol does not need to download and check all the headers in the blockchain - the client can always jump straight diff --git a/docs/guide/plugin-design.md b/docs/guide/plugin-design.md index f59ce736c..e8e7dc92f 100644 --- a/docs/guide/plugin-design.md +++ b/docs/guide/plugin-design.md @@ -43,8 +43,8 @@ type Plugin interface { // Other ABCI message handlers SetOption(store KVStore, key string, value string) (log string) InitChain(store KVStore, vals []*abci.Validator) - BeginBlock(store KVStore, height uint64) - EndBlock(store KVStore, height uint64) []*abci.Validator + BeginBlock(store KVStore, hash []byte, header *abci.Header) + EndBlock(store KVStore, height uint64) (res abci.ResponseEndBlock) } type CallContext struct { diff --git a/docs/guide/src/example-plugin/cmd.go b/docs/guide/src/example-plugin/cmd.go index 6d7c29a79..b43917626 100644 --- a/docs/guide/src/example-plugin/cmd.go +++ b/docs/guide/src/example-plugin/cmd.go @@ -1,16 +1,32 @@ package main import ( + "github.com/spf13/cobra" + wire "github.com/tendermint/go-wire" - "github.com/urfave/cli" "github.com/tendermint/basecoin/cmd/commands" "github.com/tendermint/basecoin/types" ) +var ( + //CLI Flags + validFlag bool + + //CLI Plugin Commands + ExamplePluginTxCmd = &cobra.Command{ + Use: "example", + Short: "Create, sign, and broadcast a transaction to the example plugin", + RunE: examplePluginTxCmd, + } +) + //Called during CLI initialization func init() { + //Set the Plugin Flags + ExamplePluginTxCmd.Flags().BoolVar(&validFlag, "valid", false, "Set this to make transaction valid") + //Register a plugin specific CLI command as a subcommand of the tx command commands.RegisterTxSubcommand(ExamplePluginTxCmd) @@ -18,32 +34,12 @@ func init() { commands.RegisterStartPlugin("example-plugin", func() types.Plugin { return NewExamplePlugin() }) } -var ( - //CLI Flags - ExampleFlag = cli.BoolFlag{ - Name: "valid", - Usage: "Set this to make the transaction valid", - } - - //CLI Plugin Commands - ExamplePluginTxCmd = cli.Command{ - Name: "example", - Usage: "Create, sign, and broadcast a transaction to the example plugin", - Action: func(c *cli.Context) error { - return cmdExamplePluginTx(c) - }, - Flags: append(commands.TxFlags, ExampleFlag), - } -) - //Send a transaction -func cmdExamplePluginTx(c *cli.Context) error { - //Retrieve any flag results - exampleFlag := c.Bool("valid") +func examplePluginTxCmd(cmd *cobra.Command, args []string) error { // Create a transaction using the flag. // The tx passes on custom information to the plugin - exampleTx := ExamplePluginTx{exampleFlag} + exampleTx := ExamplePluginTx{validFlag} // The tx is passed to the plugin in the form of // a byte array. This is achieved by serializing the object using go-wire. @@ -62,5 +58,5 @@ func cmdExamplePluginTx(c *cli.Context) error { // - Once deserialized, the tx is passed to `state.ExecTx` (state/execution.go) // - If the tx passes various checks, the `tx.Data` is forwarded as `txBytes` to `plugin.RunTx` (docs/guide/src/example-plugin/plugin.go) // - Finally, it deserialized back to the ExamplePluginTx - return commands.AppTx(c, "example-plugin", exampleTxBytes) + return commands.AppTx("example-plugin", exampleTxBytes) } diff --git a/docs/guide/src/example-plugin/main.go b/docs/guide/src/example-plugin/main.go index d8cc38a95..e2892bd7d 100644 --- a/docs/guide/src/example-plugin/main.go +++ b/docs/guide/src/example-plugin/main.go @@ -1,24 +1,32 @@ package main import ( - "os" + "github.com/spf13/cobra" "github.com/tendermint/basecoin/cmd/commands" - "github.com/urfave/cli" ) func main() { - //Initialize an instance of basecoin with default basecoin commands - app := cli.NewApp() - app.Name = "example-plugin" - app.Usage = "example-plugin [command] [args...]" - app.Version = "0.1.0" - app.Commands = []cli.Command{ + + //Initialize example-plugin root command + var RootCmd = &cobra.Command{ + Use: "example-plugin", + Short: "example-plugin usage description", + } + + //Add the default basecoin commands to the root command + RootCmd.AddCommand( + commands.InitCmd, commands.StartCmd, commands.TxCmd, - commands.KeyCmd, commands.QueryCmd, + commands.KeyCmd, + commands.VerifyCmd, + commands.BlockCmd, commands.AccountCmd, - } - app.Run(os.Args) + commands.UnsafeResetAllCmd, + ) + + //Run the root command + commands.ExecuteWithDebug(RootCmd) } diff --git a/glide.lock b/glide.lock index 38755f1a6..bc150cd7d 100644 --- a/glide.lock +++ b/glide.lock @@ -1,34 +1,41 @@ -hash: c023dbd97e1ea0a525e33738f03afd6be61f997f1c2592a5d9928fdcecc71361 -updated: 2017-04-21T17:38:13.194966906+02:00 +hash: c6e5febc35b5fd1003066820defb8a089db048b407239dad9faf44553fdc15e8 +updated: 2017-04-21T12:55:42.7004558-04:00 imports: - name: github.com/btcsuite/btcd - version: 583684b21bfbde9b5fc4403916fd7c807feb0289 + version: 4b348c1d33373d672edd83fc576892d0e46686d2 subpackages: - btcec - name: github.com/BurntSushi/toml - version: e643e9ef00b049d75de26e61109c5ea01885cd21 + version: b26d9c308763d68093482582cea63d69be07a0f0 - name: github.com/ebuchman/fail-test version: 95f809107225be108efcf10a3509e4ea6ceef3c4 - name: github.com/go-stack/stack version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 - name: github.com/golang/protobuf - version: c9c7427a2a70d2eb3bafa0ab2dc163e45f143317 + version: 2bba0603135d7d7f5cb73b2125beeda19c09f4ef subpackages: - proto + - ptypes/any - name: github.com/golang/snappy version: 553a641470496b2327abcac10b36396bd98e45c9 - name: github.com/gorilla/websocket version: 3ab3a8b8831546bd18fd182c20687ca853b2bb13 +- name: github.com/inconshreveable/mousetrap + version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 - name: github.com/jmhodges/levigo version: c42d9e0ca023e2198120196f842701bb4c55d7b9 - name: github.com/mattn/go-colorable - version: a392f450ea64cee2b268dfaacdc2502b50a22b18 + version: ded68f7a9561c023e790de24279db7ebf473ea80 - name: github.com/mattn/go-isatty - version: 57fdcb988a5c543893cc61bce354a6e24ab70022 + version: fc9e8d8ef48496124e79ae0df75490096eccf6fe - name: github.com/pkg/errors - version: bfd5150e4e41705ded2129ec33379de1cb90b513 + version: ff09b135c25aae272398c51a07235b90a75aa4f0 +- name: github.com/spf13/cobra + version: 10f6b9d7e1631a54ad07c5c0fb71c28a1abfd3c2 +- name: github.com/spf13/pflag + version: 2300d0f8576fe575f71aaa5b9bbe4e1b0dc2eb51 - name: github.com/syndtr/goleveldb - version: 3c5717caf1475fd25964109a0fc640bd150fce43 + version: 8c81ea47d4c41a385645e133e15510fc6a2a74b4 subpackages: - leveldb - leveldb/cache @@ -83,7 +90,7 @@ imports: subpackages: - upnp - name: github.com/tendermint/go-rpc - version: 9d18cbe74e66f875afa36d2fa3be280e4a2dc9e6 + version: 559613689d56eaa423b19a3a4158546beb4857de subpackages: - client - server @@ -100,9 +107,10 @@ imports: - app - client - name: github.com/tendermint/tendermint - version: 7cf773e2d37b2b5a08bc94fb125cfd346b834824 + version: e8cad948e366cd1d0a9ebef642073f4ade9899e9 subpackages: - blockchain + - cmd/tendermint/commands - config/tendermint - consensus - mempool @@ -117,10 +125,8 @@ imports: - state/txindex/null - types - version -- name: github.com/urfave/cli - version: 0bdeddeeb0f650497d603c4ad7b20cfe685682f6 - name: golang.org/x/crypto - version: 728b753d0135da6801d45a38e6f43ff55779c5c2 + version: 96846453c37f0876340a66a47f3f75b1f3a6cd2d subpackages: - curve25519 - nacl/box @@ -131,7 +137,7 @@ imports: - ripemd160 - salsa20/salsa - name: golang.org/x/net - version: a6577fac2d73be281a500b310739095313165611 + version: c8c74377599bd978aee1cf3b9b63a8634051cec2 subpackages: - context - http2 @@ -141,11 +147,22 @@ imports: - lex/httplex - trace - name: golang.org/x/sys - version: 99f16d856c9836c42d24e7ab64ea72916925fa97 + version: ea9bcade75cb975a0b9738936568ab388b845617 subpackages: - unix +- name: golang.org/x/text + version: 19e3104b43db45fca0303f489a9536087b184802 + subpackages: + - secure/bidirule + - transform + - unicode/bidi + - unicode/norm +- name: google.golang.org/genproto + version: 411e09b969b1170a9f0c467558eb4c4c110d9c77 + subpackages: + - googleapis/rpc/status - name: google.golang.org/grpc - version: 0713829b980f4ddd276689a36235c5fcc82a21bf + version: 6914ab1e338c92da4218a23d27fcd03d0ad78d46 subpackages: - codes - credentials @@ -156,6 +173,7 @@ imports: - naming - peer - stats + - status - tap - transport testImports: diff --git a/glide.yaml b/glide.yaml index ecf4f151b..87735c19c 100644 --- a/glide.yaml +++ b/glide.yaml @@ -8,6 +8,8 @@ import: version: develop - package: github.com/tendermint/go-logger version: develop +- package: github.com/tendermint/go-data + version: develop - package: github.com/tendermint/go-rpc version: develop - package: github.com/tendermint/go-wire diff --git a/scripts/print_txs.go b/scripts/print_txs.go index d7570a0bd..689173a90 100644 --- a/scripts/print_txs.go +++ b/scripts/print_txs.go @@ -9,7 +9,7 @@ import ( "time" "github.com/gorilla/websocket" - . "github.com/tendermint/go-common" + cmn "github.com/tendermint/go-common" "github.com/tendermint/go-rpc/client" "github.com/tendermint/go-rpc/types" "github.com/tendermint/go-wire" @@ -21,7 +21,7 @@ func main() { _, err := ws.Start() if err != nil { - Exit(err.Error()) + cmn.Exit(err.Error()) } // Read a bunch of responses @@ -50,7 +50,7 @@ func main() { reqBytes := wire.JSONBytes(request) err = ws.WriteMessage(websocket.TextMessage, reqBytes) if err != nil { - Exit("writing websocket request: " + err.Error()) + cmn.Exit("writing websocket request: " + err.Error()) } } diff --git a/tests/tendermint/main.go b/tests/tendermint/main.go index 605de6823..73ace4ef8 100644 --- a/tests/tendermint/main.go +++ b/tests/tendermint/main.go @@ -73,6 +73,7 @@ func main() { // Write request txBytes := wire.BinaryBytes(struct{ types.Tx }{tx}) request := rpctypes.NewRPCRequest("fakeid", "broadcast_tx_sync", map[string]interface{}{"tx": txBytes}) + //request := rpctypes.NewRPCRequest("fakeid", "broadcast_tx_sync", map[string]interface{}{"tx": txBytes}) reqBytes := wire.JSONBytes(request) //fmt.Print(".") err := ws.WriteMessage(websocket.TextMessage, reqBytes) diff --git a/version/version.go b/version/version.go index 770886758..ae549d083 100644 --- a/version/version.go +++ b/version/version.go @@ -1,7 +1,7 @@ package version const Maj = "0" -const Min = "3" -const Fix = "1" +const Min = "4" +const Fix = "0" const Version = Maj + "." + Min + "." + Fix