Merge pull request #64 from tendermint/cli_cobra

Update to Cobra CLI
This commit is contained in:
Ethan Buchman 2017-04-21 12:59:53 -04:00 committed by GitHub
commit 488f3e1291
28 changed files with 885 additions and 831 deletions

View File

@ -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:

View File

@ -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) {

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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 <amt><coin>,<amt2><coin2>,... (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 <amt><coin>",
}
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: "",
}
)

View File

@ -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

View File

@ -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",

View File

@ -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
}

View File

@ -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)
},
}
}

View File

@ -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: "<key>",
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: "<address>",
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: "<height>",
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) {

View File

@ -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
}

View File

@ -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
}

View File

@ -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 <amt><coin>,<amt2><coin2>,... (eg: 1btc,2gold,5silver)"},
{&gasFlag, "gas", 0, "The amount of gas for the transaction"},
{&feeFlag, "fee", "", "Coins for the transaction fee of the format <amt><coin>"},
{&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,
}
}

View File

@ -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

17
cmd/commands/version.go Normal file
View File

@ -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)
},
}

View File

@ -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 <amt><coin>")
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)
}

View File

@ -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)
}

View File

@ -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"

View File

@ -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 <amt><coin>,<amt2><coin2>,... (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 <amt><coin>
--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 <amt><coin>,<amt2><coin2>,... (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 <amt><coin>
--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)

View File

@ -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

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)
}

52
glide.lock generated
View File

@ -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:

View File

@ -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

View File

@ -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())
}
}

View File

@ -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)

View File

@ -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