From 90d0b53a2fd64b976f859e8dcc1990ca4501e56f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 18:41:37 -0800 Subject: [PATCH] cmd: ibc --- cmd/basecoin/account.go | 31 ----------- cmd/basecoin/cmd.go | 107 ++++++++++++++++++++++++++++++++++-- cmd/basecoin/flags.go | 67 ++++++++++++++++++++++- cmd/basecoin/ibc.go | 114 +++++++++++++++++++++++++++++++++++++++ cmd/basecoin/main.go | 3 ++ cmd/basecoin/query.go | 117 ++++++++++++++++++++++++++++++++++++++++ cmd/basecoin/tx.go | 4 +- cmd/basecoin/utils.go | 41 +++++++++++--- 8 files changed, 439 insertions(+), 45 deletions(-) delete mode 100644 cmd/basecoin/account.go create mode 100644 cmd/basecoin/ibc.go create mode 100644 cmd/basecoin/query.go diff --git a/cmd/basecoin/account.go b/cmd/basecoin/account.go deleted file mode 100644 index 22b4e076fc..0000000000 --- a/cmd/basecoin/account.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import ( - "encoding/hex" - "errors" - "fmt" - - "github.com/urfave/cli" - - "github.com/tendermint/go-wire" -) - -func cmdAccount(c *cli.Context) error { - if len(c.Args()) != 1 { - return errors.New("account command requires an argument ([address])") - } - addrHex := stripHex(c.Args()[0]) - - // convert destination address to bytes - addr, err := hex.DecodeString(addrHex) - if err != nil { - return errors.New("Account address is invalid hex: " + err.Error()) - } - - acc, err := getAcc(c.String("tendermint"), addr) - if err != nil { - return err - } - fmt.Println(string(wire.JSONBytes(acc))) - return nil -} diff --git a/cmd/basecoin/cmd.go b/cmd/basecoin/cmd.go index 7fe5fb3dbb..890f390d00 100644 --- a/cmd/basecoin/cmd.go +++ b/cmd/basecoin/cmd.go @@ -31,7 +31,7 @@ var ( return cmdSendTx(c) }, Flags: []cli.Flag{ - tmAddrFlag, + nodeFlag, chainIDFlag, fromFlag, @@ -54,7 +54,7 @@ var ( return cmdAppTx(c) }, Flags: []cli.Flag{ - tmAddrFlag, + nodeFlag, chainIDFlag, fromFlag, @@ -84,15 +84,114 @@ var ( }, } + ibcCmd = cli.Command{ + Name: "ibc", + Usage: "Send a transaction to the interblockchain (ibc) plugin", + Flags: []cli.Flag{ + nodeFlag, + }, + 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", + Flags: []cli.Flag{ + // + }, + 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, + }, + } + + 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{ + ibcPacketFlag, + ibcProofFlag, + }, + } + + queryCmd = cli.Command{ + Name: "query", + Usage: "Query the merkle tree", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdQuery(c) + }, + Flags: []cli.Flag{ + nodeFlag, + }, + } + accountCmd = cli.Command{ Name: "account", Usage: "Get details of an account", - ArgsUsage: "[address]", + ArgsUsage: "
", Action: func(c *cli.Context) error { return cmdAccount(c) }, Flags: []cli.Flag{ - tmAddrFlag, + nodeFlag, + }, + } + + 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, }, } ) diff --git a/cmd/basecoin/flags.go b/cmd/basecoin/flags.go index c4b4c9f62c..34b3f7c2e7 100644 --- a/cmd/basecoin/flags.go +++ b/cmd/basecoin/flags.go @@ -48,8 +48,8 @@ var ( // tx flags var ( - tmAddrFlag = cli.StringFlag{ - Name: "tendermint", + nodeFlag = cli.StringFlag{ + Name: "node", Value: "tcp://localhost:46657", Usage: "Tendermint RPC address", } @@ -119,3 +119,66 @@ var ( Usage: "Set valid field in CounterTx", } ) + +// ibc flags +var ( + ibcChainIDFlag = cli.StringFlag{ + Name: "chain_id", + Usage: "ChainID for the new blockchain", + Value: "", + } + + ibcGenesisFlag = cli.StringFlag{ + Name: "genesis", + Usage: "Genesis file for the new blockchain", + Value: "", + } + + ibcHeaderFlag = cli.StringFlag{ + Name: "header", + Usage: "Block header for an ibc update", + Value: "", + } + + ibcCommitFlag = cli.StringFlag{ + Name: "commit", + Usage: "Block commit for an ibc update", + Value: "", + } + + ibcFromFlag = cli.StringFlag{ + Name: "from", + Usage: "Source ChainID", + Value: "", + } + + 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: "", + } +) diff --git a/cmd/basecoin/ibc.go b/cmd/basecoin/ibc.go new file mode 100644 index 0000000000..9a4446a3c8 --- /dev/null +++ b/cmd/basecoin/ibc.go @@ -0,0 +1,114 @@ +package main + +import ( + "encoding/hex" + "errors" + "fmt" + "io/ioutil" + + "github.com/urfave/cli" + + "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" +) + +func cmdIBCRegisterTx(c *cli.Context) error { + chainID := c.String("chain_id") + genesisFile := c.String("genesis") + parent := c.Parent() + + genesisBytes, err := ioutil.ReadFile(genesisFile) + if err != nil { + return errors.New(cmn.Fmt("Error reading genesis file %v: %v", genesisFile, err)) + } + + ibcTx := ibc.IBCRegisterChainTx{ + ibc.BlockchainGenesis{ + ChainID: chainID, + Genesis: string(genesisBytes), + }, + } + + fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx))) + + data := wire.BinaryBytes(ibcTx) + name := "ibc" + + return appTx(parent, name, data) +} + +func cmdIBCUpdateTx(c *cli.Context) error { + parent := c.Parent() + + headerBytes, err := hex.DecodeString(stripHex(c.String("header"))) + if err != nil { + return errors.New(cmn.Fmt("Header (%v) is invalid hex: %v", c.String("header"), err)) + } + commitBytes, err := hex.DecodeString(stripHex(c.String("commit"))) + if err != nil { + return errors.New(cmn.Fmt("Commit (%v) is invalid hex: %v", c.String("commit"), err)) + } + + var header tmtypes.Header + var commit tmtypes.Commit + + if err := wire.ReadBinaryBytes(headerBytes, &header); err != nil { + return errors.New(cmn.Fmt("Error unmarshalling header: %v", err)) + } + if err := wire.ReadBinaryBytes(commitBytes, &commit); err != nil { + return errors.New(cmn.Fmt("Error unmarshalling commit: %v", err)) + } + + ibcTx := ibc.IBCUpdateChainTx{ + Header: header, + Commit: commit, + } + + fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx))) + + data := wire.BinaryBytes(ibcTx) + name := "ibc" + + return appTx(parent, name, data) +} + +func cmdIBCPacketCreateTx(c *cli.Context) error { + return nil +} + +func cmdIBCPacketPostTx(c *cli.Context) error { + parent := c.Parent() + + var fromChain string + var fromHeight uint64 + var proof merkle.IAVLProof + + var srcChain, dstChain string + var sequence uint64 + var packetType string + var payload []byte + + ibcTx := ibc.IBCPacketTx{ + FromChainID: fromChain, + FromChainHeight: fromHeight, + Packet: ibc.Packet{ + SrcChainID: srcChain, + DstChainID: dstChain, + Sequence: sequence, + Type: packetType, + Payload: payload, + }, + Proof: proof, + } + + fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx))) + + data := wire.BinaryBytes(ibcTx) + name := "ibc" + + return appTx(parent, name, data) +} diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 1fd2c79eb4..7bc44b99ac 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -15,6 +15,9 @@ func main() { startCmd, sendTxCmd, appTxCmd, + ibcCmd, + queryCmd, + blockCmd, accountCmd, } app.Run(os.Args) diff --git a/cmd/basecoin/query.go b/cmd/basecoin/query.go new file mode 100644 index 0000000000..c42507e44d --- /dev/null +++ b/cmd/basecoin/query.go @@ -0,0 +1,117 @@ +package main + +import ( + "encoding/hex" + "errors" + "fmt" + "strconv" + + "github.com/urfave/cli" + + cmn "github.com/tendermint/go-common" + "github.com/tendermint/go-wire" + tmtypes "github.com/tendermint/tendermint/types" +) + +func cmdQuery(c *cli.Context) error { + if len(c.Args()) != 1 { + return errors.New("query command requires an argument ([key])") + } + keyString := c.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)) + } + } + + resp, err := query(c.String("node"), key) + if err != nil { + return 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)) + } + + val := resp.Value + proof := resp.Proof + height := resp.Height + + fmt.Println(string(wire.JSONBytes(struct { + Value []byte `json:"value"` + 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])") + } + addrHex := stripHex(c.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)) + } + + acc, err := getAcc(c.String("node"), addr) + if err != nil { + return err + } + fmt.Println(string(wire.JSONBytes(acc))) + 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)) + } + + block, err := getBlock(c, height) + if err != nil { + return err + } + nextBlock, err := getBlock(c, height+1) + if err != nil { + return err + } + + fmt.Println(string(wire.JSONBytes(struct { + Hex BlockHex `json:"hex"` + JSON BlockJSON `json:"json"` + }{ + BlockHex{ + Header: wire.BinaryBytes(block.Header), + Commit: wire.BinaryBytes(nextBlock.LastCommit), + }, + BlockJSON{ + Header: block.Header, + Commit: nextBlock.LastCommit, + }, + }))) + + return nil +} + +type BlockHex struct { + Header []byte `json:"header"` + Commit []byte `json:"commit"` +} + +type BlockJSON struct { + Header *tmtypes.Header `json:"header"` + Commit *tmtypes.Commit `json:"commit"` +} diff --git a/cmd/basecoin/tx.go b/cmd/basecoin/tx.go index e80d1a77a8..43e5a8e2e2 100644 --- a/cmd/basecoin/tx.go +++ b/cmd/basecoin/tx.go @@ -136,7 +136,7 @@ func cmdCounterTx(c *cli.Context) error { // broadcast the transaction to tendermint func broadcastTx(c *cli.Context, tx types.Tx) error { tmResult := new(ctypes.TMResult) - tmAddr := c.String("tendermint") + tmAddr := c.String("node") clientURI := client.NewClientURI(tmAddr) // Don't you hate having to do this? @@ -161,7 +161,7 @@ func getSeq(c *cli.Context, address []byte) (int, error) { if c.IsSet("sequence") { return c.Int("sequence"), nil } - tmAddr := c.String("tendermint") + tmAddr := c.String("node") acc, err := getAcc(tmAddr, address) if err != nil { return 0, err diff --git a/cmd/basecoin/utils.go b/cmd/basecoin/utils.go index a944c5bd10..b005f3306c 100644 --- a/cmd/basecoin/utils.go +++ b/cmd/basecoin/utils.go @@ -4,12 +4,16 @@ import ( "encoding/hex" "errors" + "github.com/urfave/cli" + "github.com/tendermint/basecoin/types" + abci "github.com/tendermint/abci/types" cmn "github.com/tendermint/go-common" client "github.com/tendermint/go-rpc/client" "github.com/tendermint/go-wire" ctypes "github.com/tendermint/tendermint/rpc/core/types" + tmtypes "github.com/tendermint/tendermint/types" ) // Returns true for non-empty hex-string prefixed with "0x" @@ -31,15 +35,14 @@ func stripHex(s string) string { return s } -// fetch the account by querying the app -func getAcc(tmAddr string, address []byte) (*types.Account, error) { +func query(tmAddr string, key []byte) (*abci.ResponseQuery, error) { clientURI := client.NewClientURI(tmAddr) tmResult := new(ctypes.TMResult) params := map[string]interface{}{ "path": "/key", - "data": append([]byte("base/a/"), address...), - "prove": false, + "data": key, + "prove": true, } _, err := clientURI.Call("abci_query", params, tmResult) if err != nil { @@ -49,11 +52,24 @@ func getAcc(tmAddr string, address []byte) (*types.Account, error) { if !res.Response.Code.IsOK() { return nil, errors.New(cmn.Fmt("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log)) } - accountBytes := res.Response.Value + return &res.Response, nil +} + +// fetch the account by querying the app +func getAcc(tmAddr string, address []byte) (*types.Account, error) { + + key := append([]byte("base/a/"), address...) + response, err := query(tmAddr, key) + if err != nil { + return nil, err + } + + accountBytes := response.Value if len(accountBytes) == 0 { - return nil, errors.New(cmn.Fmt("Account bytes are empty from query for address %X", address)) + return nil, errors.New(cmn.Fmt("Account bytes are empty for address: %X ", address)) } + var acc *types.Account err = wire.ReadBinaryBytes(accountBytes, &acc) if err != nil { @@ -63,3 +79,16 @@ func getAcc(tmAddr string, address []byte) (*types.Account, error) { return acc, nil } + +func getBlock(c *cli.Context, height int) (*tmtypes.Block, error) { + tmResult := new(ctypes.TMResult) + tmAddr := c.String("node") + clientURI := client.NewClientURI(tmAddr) + + _, err := clientURI.Call("block", map[string]interface{}{"height": height}, tmResult) + if err != nil { + return nil, errors.New(cmn.Fmt("Error on broadcast tx: %v", err)) + } + res := (*tmResult).(*ctypes.ResultBlock) + return res.Block, nil +}