Merge pull request #115 from tendermint/feature/remove-client-from-basecoin
Feature/remove client from basecoin
This commit is contained in:
@@ -1,5 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.0 (???)
|
||||
|
||||
Make the basecli command the only way to use client-side, to enforce best
|
||||
security practices. Lots of enhancements to get it up to production quality.
|
||||
|
||||
BREAKING CHANGES:
|
||||
- basecli
|
||||
- `basecli proof state get` -> `basecli query key`
|
||||
- `basecli proof tx get` -> `basecli query tx`
|
||||
- `basecli proof state get --app=account` -> `basecli query account`
|
||||
- use `--chain-id` not `--chainid` for consistency
|
||||
- update to use `--trace` not `--debug` for stack traces on errors
|
||||
- complete overhaul on how tx and query subcommands are added. (see counter or trackomatron for examples)
|
||||
- no longer supports counter app (see new countercli)
|
||||
- basecoin
|
||||
- removed all client side functionality from it (use basecli now for proofs)
|
||||
|
||||
ENHANCEMENTS:
|
||||
- intergrates tendermint 0.10.0 (not the rc-2, but the real thing)
|
||||
- commands return error code (1) on failure for easier script testing
|
||||
- add `reset_all` to basecli, and never delete keys on `init`
|
||||
- new shutil based unit tests, with better coverage of the cli actions
|
||||
|
||||
BUG FIXES:
|
||||
- no longer panics on missing app_options in genesis (thanks, anton)
|
||||
|
||||
|
||||
## 0.5.2 (June 2, 2017)
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
GOTOOLS = \
|
||||
github.com/mitchellh/gox \
|
||||
github.com/Masterminds/glide
|
||||
GOTOOLS = github.com/mitchellh/gox \
|
||||
github.com/Masterminds/glide
|
||||
PACKAGES=$(shell go list ./... | grep -v '/vendor/')
|
||||
|
||||
all: get_vendor_deps test install
|
||||
all: get_vendor_deps install test
|
||||
|
||||
build:
|
||||
go build ./cmd/...
|
||||
@@ -15,36 +14,34 @@ dist:
|
||||
@bash scripts/dist.sh
|
||||
@bash scripts/publish.sh
|
||||
|
||||
clitest/shunit2:
|
||||
wget "https://raw.githubusercontent.com/kward/shunit2/master/source/2.1/src/shunit2" \
|
||||
-q -O clitest/shunit2
|
||||
test: test_unit test_cli
|
||||
|
||||
test_cli: clitest/shunit2
|
||||
# sudo apt-get install jq
|
||||
@./clitest/basictx.sh
|
||||
# @./clitest/ibc.sh
|
||||
|
||||
test:
|
||||
test_unit:
|
||||
go test $(PACKAGES)
|
||||
#go run tests/tendermint/*.go
|
||||
|
||||
# get_deps:
|
||||
# go get -d ./...
|
||||
|
||||
# update_deps:
|
||||
# go get -d -u ./...
|
||||
test_cli: tests/cli/shunit2
|
||||
# sudo apt-get install jq
|
||||
@./tests/cli/basictx.sh
|
||||
@./tests/cli/counter.sh
|
||||
@./tests/cli/ibc.sh
|
||||
|
||||
get_vendor_deps: tools
|
||||
glide install
|
||||
|
||||
build-docker:
|
||||
docker run -it --rm -v "$(PWD):/go/src/github.com/tendermint/basecoin" -w "/go/src/github.com/tendermint/basecoin" -e "CGO_ENABLED=0" golang:alpine go build ./cmd/basecoin
|
||||
docker run -it --rm -v "$(PWD):/go/src/github.com/tendermint/basecoin" -w \
|
||||
"/go/src/github.com/tendermint/basecoin" -e "CGO_ENABLED=0" golang:alpine go build ./cmd/basecoin
|
||||
docker build -t "tendermint/basecoin" .
|
||||
|
||||
tests/cli/shunit2:
|
||||
wget "https://raw.githubusercontent.com/kward/shunit2/master/source/2.1/src/shunit2" \
|
||||
-q -O tests/cli/shunit2
|
||||
|
||||
tools:
|
||||
go get -u -v $(GOTOOLS)
|
||||
|
||||
clean:
|
||||
@rm -f ./basecoin
|
||||
|
||||
.PHONY: all build install test get_deps update_deps get_vendor_deps build-docker clean
|
||||
.PHONY: all build install test test_cli test_unit get_vendor_deps build-docker clean
|
||||
|
||||
+1
-2
@@ -19,9 +19,8 @@ dependencies:
|
||||
|
||||
test:
|
||||
override:
|
||||
- "cd $REPO && glide install && go install ./cmd/basecoin"
|
||||
- "cd $REPO && glide install && go install ./cmd/..."
|
||||
- ls $GOPATH/bin
|
||||
- "cd $REPO && make test"
|
||||
- "cd $REPO/demo && bash start.sh"
|
||||
|
||||
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
oneTimeSetUp() {
|
||||
BASE_DIR=$HOME/.basecoin_test_basictx
|
||||
LOG=$BASE_DIR/test.log
|
||||
SERVER_LOG=$BASE_DIR/basecoin.log
|
||||
|
||||
rm -rf $BASE_DIR
|
||||
mkdir -p $BASE_DIR
|
||||
|
||||
ACCOUNTS=(jae ethan bucky rigel igor)
|
||||
RICH=${ACCOUNTS[0]}
|
||||
POOR=${ACCOUNTS[1]}
|
||||
|
||||
# set up client
|
||||
prepareClient
|
||||
|
||||
# start basecoin server (with counter)
|
||||
initServer
|
||||
echo pid $PID_SERVER
|
||||
|
||||
initClient
|
||||
|
||||
echo "...Testing may begin!"
|
||||
echo
|
||||
echo
|
||||
echo
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
echo
|
||||
echo
|
||||
echo "stopping basecoin test server..."
|
||||
kill -9 $PID_SERVER >/dev/null 2>&1
|
||||
sleep 1
|
||||
}
|
||||
|
||||
prepareClient() {
|
||||
echo "Preparing client keys..."
|
||||
export BC_HOME=$BASE_DIR/client
|
||||
basecli reset_all
|
||||
assertTrue $?
|
||||
|
||||
for i in "${!ACCOUNTS[@]}"; do
|
||||
newKey ${ACCOUNTS[$i]}
|
||||
done
|
||||
}
|
||||
|
||||
initServer() {
|
||||
echo "Setting up genesis..."
|
||||
SERVE_DIR=$BASE_DIR/server
|
||||
rm -rf $SERVE_DIR 2>/dev/null
|
||||
basecoin init --home=$SERVE_DIR >>$SERVER_LOG
|
||||
|
||||
#change the genesis to the first account
|
||||
GENKEY=$(basecli keys get ${RICH} -o json | jq .pubkey.data)
|
||||
GENJSON=$(cat $SERVE_DIR/genesis.json)
|
||||
echo $GENJSON | jq '.app_options.accounts[0].pub_key.data='$GENKEY > $SERVE_DIR/genesis.json
|
||||
|
||||
echo "Starting server..."
|
||||
basecoin start --home=$SERVE_DIR >>$SERVER_LOG 2>&1 &
|
||||
sleep 5
|
||||
PID_SERVER=$!
|
||||
}
|
||||
|
||||
initClient() {
|
||||
echo "Attaching client..."
|
||||
# hard-code the expected validator hash
|
||||
basecli init --chain-id=test_chain_id --node=tcp://localhost:46657 --valhash=EB168E17E45BAEB194D4C79067FFECF345C64DE6
|
||||
assertTrue "initialized light-client" $?
|
||||
}
|
||||
|
||||
# newKeys makes a key for a given username, second arg optional password
|
||||
newKey(){
|
||||
assertNotNull "keyname required" "$1"
|
||||
KEYPASS=${2:-qwertyuiop}
|
||||
(echo $KEYPASS; echo $KEYPASS) | basecli keys new $1 >>$LOG 2>/dev/null
|
||||
assertTrue "created $1" $?
|
||||
assertTrue "$1 doesn't exist" "basecli keys get $1"
|
||||
}
|
||||
|
||||
# getAddr gets the address for a key name
|
||||
getAddr() {
|
||||
assertNotNull "keyname required" "$1"
|
||||
RAW=$(basecli keys get $1)
|
||||
assertTrue "no key for $1" $?
|
||||
# print the addr
|
||||
echo $RAW | cut -d' ' -f2
|
||||
}
|
||||
|
||||
test00GetAccount() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=$(getAddr $POOR)
|
||||
|
||||
assertFalse "requires arg" "basecli query account"
|
||||
ACCT=$(basecli query account $SENDER)
|
||||
assertTrue "must have proper genesis account" $?
|
||||
assertEquals "no tx" "0" $(echo $ACCT | jq .data.sequence)
|
||||
assertEquals "has money" "9007199254740992" $(echo $ACCT | jq .data.coins[0].amount)
|
||||
|
||||
ACCT2=$(basecli query account $RECV)
|
||||
assertFalse "has no genesis account" $?
|
||||
}
|
||||
|
||||
test01SendTx() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=$(getAddr $POOR)
|
||||
|
||||
assertFalse "missing dest" "basecli tx send --amount=992mycoin --sequence=1 2>/dev/null"
|
||||
assertFalse "bad password" "echo foo | basecli tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH 2>/dev/null"
|
||||
# we have to remove the password request from stdout, to just get the json
|
||||
RES=$(echo qwertyuiop | basecli tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH 2>/dev/null | tail -n +2)
|
||||
assertTrue "sent tx" $?
|
||||
HASH=$(echo $RES | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $RES | jq .height)
|
||||
assertEquals "good check" "0" $(echo $RES | jq .check_tx.code)
|
||||
assertEquals "good deliver" "0" $(echo $RES | jq .deliver_tx.code)
|
||||
|
||||
# make sure sender goes down
|
||||
ACCT=$(basecli query account $SENDER)
|
||||
assertTrue "must have genesis account" $?
|
||||
assertEquals "one tx" "1" $(echo $ACCT | jq .data.sequence)
|
||||
assertEquals "has money" "9007199254740000" $(echo $ACCT | jq .data.coins[0].amount)
|
||||
|
||||
# make sure recipient goes up
|
||||
ACCT2=$(basecli query account $RECV)
|
||||
assertTrue "must have new account" $?
|
||||
assertEquals "no tx" "0" $(echo $ACCT2 | jq .data.sequence)
|
||||
assertEquals "has money" "992" $(echo $ACCT2 | jq .data.coins[0].amount)
|
||||
|
||||
# make sure tx is indexed
|
||||
TX=$(basecli query tx $HASH)
|
||||
assertTrue "found tx" $?
|
||||
assertEquals "proper height" $TX_HEIGHT $(echo $TX | jq .height)
|
||||
assertEquals "type=send" '"send"' $(echo $TX | jq .data.type)
|
||||
assertEquals "proper sender" "\"$SENDER\"" $(echo $TX | jq .data.data.inputs[0].address)
|
||||
assertEquals "proper out amount" "992" $(echo $TX | jq .data.data.outputs[0].coins[0].amount)
|
||||
}
|
||||
|
||||
|
||||
# load and run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
. $DIR/shunit2
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "ibc test not implemented"
|
||||
@@ -2,6 +2,7 @@ package commands
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -10,41 +11,48 @@ import (
|
||||
|
||||
"github.com/tendermint/light-client/commands"
|
||||
txcmd "github.com/tendermint/light-client/commands/txs"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
|
||||
btypes "github.com/tendermint/basecoin/types"
|
||||
)
|
||||
|
||||
/*** Here is the sendtx command ***/
|
||||
/******** SendTx *********/
|
||||
|
||||
// SendTxCmd is CLI command to send tokens between basecoin accounts
|
||||
var SendTxCmd = &cobra.Command{
|
||||
Use: "send",
|
||||
Short: "send tokens from one account to another",
|
||||
RunE: doSendTx,
|
||||
}
|
||||
|
||||
//nolint
|
||||
const (
|
||||
ToFlag = "to"
|
||||
AmountFlag = "amount"
|
||||
FeeFlag = "fee"
|
||||
GasFlag = "gas"
|
||||
SequenceFlag = "sequence"
|
||||
FlagTo = "to"
|
||||
FlagAmount = "amount"
|
||||
FlagFee = "fee"
|
||||
FlagGas = "gas"
|
||||
FlagSequence = "sequence"
|
||||
)
|
||||
|
||||
func init() {
|
||||
flags := SendTxCmd.Flags()
|
||||
flags.String(ToFlag, "", "Destination address for the bits")
|
||||
flags.String(AmountFlag, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
|
||||
flags.String(FeeFlag, "0mycoin", "Coins for the transaction fee of the format <amt><coin>")
|
||||
flags.Int64(GasFlag, 0, "Amount of gas for this transaction")
|
||||
flags.Int(SequenceFlag, -1, "Sequence number for this transaction")
|
||||
flags.String(FlagTo, "", "Destination address for the bits")
|
||||
flags.String(FlagAmount, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
|
||||
flags.String(FlagFee, "0mycoin", "Coins for the transaction fee of the format <amt><coin>")
|
||||
flags.Int64(FlagGas, 0, "Amount of gas for this transaction")
|
||||
flags.Int(FlagSequence, -1, "Sequence number for this transaction")
|
||||
}
|
||||
|
||||
// runDemo is an example of how to make a tx
|
||||
func doSendTx(cmd *cobra.Command, args []string) error {
|
||||
tx := new(btypes.SendTx)
|
||||
|
||||
// load data from json or flags
|
||||
tx := new(btypes.SendTx)
|
||||
found, err := txcmd.LoadJSON(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
err = readSendTxFlags(tx)
|
||||
}
|
||||
@@ -52,6 +60,7 @@ func doSendTx(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wrap and add signer
|
||||
send := &SendTx{
|
||||
chainID: commands.GetChainID(),
|
||||
Tx: tx,
|
||||
@@ -64,34 +73,34 @@ func doSendTx(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// output result
|
||||
// Output result
|
||||
return txcmd.OutputTx(bres)
|
||||
}
|
||||
|
||||
func readSendTxFlags(tx *btypes.SendTx) error {
|
||||
// parse to address
|
||||
to, err := ParseHexFlag(ToFlag)
|
||||
if err != nil {
|
||||
return errors.Errorf("To address is invalid hex: %v\n", err)
|
||||
}
|
||||
|
||||
//parse the fee and amounts into coin types
|
||||
tx.Fee, err = btypes.ParseCoin(viper.GetString(FeeFlag))
|
||||
to, err := parseChainAddress(viper.GetString(FlagTo))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amountCoins, err := btypes.ParseCoins(viper.GetString(AmountFlag))
|
||||
|
||||
//parse the fee and amounts into coin types
|
||||
tx.Fee, err = btypes.ParseCoin(viper.GetString(FlagFee))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amountCoins, err := btypes.ParseCoins(viper.GetString(FlagAmount))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set the gas
|
||||
tx.Gas = viper.GetInt64(GasFlag)
|
||||
tx.Gas = viper.GetInt64(FlagGas)
|
||||
|
||||
// craft the inputs and outputs
|
||||
tx.Inputs = []btypes.TxInput{{
|
||||
Coins: amountCoins,
|
||||
Sequence: viper.GetInt(SequenceFlag),
|
||||
Sequence: viper.GetInt(FlagSequence),
|
||||
}}
|
||||
tx.Outputs = []btypes.TxOutput{{
|
||||
Address: to,
|
||||
@@ -101,41 +110,81 @@ func readSendTxFlags(tx *btypes.SendTx) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseChainAddress(toFlag string) ([]byte, error) {
|
||||
var toHex string
|
||||
var chainPrefix string
|
||||
spl := strings.Split(toFlag, "/")
|
||||
switch len(spl) {
|
||||
case 1:
|
||||
toHex = spl[0]
|
||||
case 2:
|
||||
chainPrefix = spl[0]
|
||||
toHex = spl[1]
|
||||
default:
|
||||
return nil, errors.Errorf("To address has too many slashes")
|
||||
}
|
||||
|
||||
// convert destination address to bytes
|
||||
to, err := hex.DecodeString(cmn.StripHex(toHex))
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("To address is invalid hex: %v\n", err)
|
||||
}
|
||||
|
||||
if chainPrefix != "" {
|
||||
to = []byte(chainPrefix + "/" + string(to))
|
||||
}
|
||||
return to, nil
|
||||
}
|
||||
|
||||
/******** AppTx *********/
|
||||
|
||||
// BroadcastAppTx wraps, signs, and executes an app tx basecoin transaction
|
||||
func BroadcastAppTx(tx *btypes.AppTx) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
|
||||
// Generate app transaction to be broadcast
|
||||
appTx := WrapAppTx(tx)
|
||||
appTx.AddSigner(txcmd.GetSigner())
|
||||
|
||||
// Sign if needed and post to the node. This it the work-horse
|
||||
return txcmd.SignAndPostTx(appTx)
|
||||
}
|
||||
|
||||
// AddAppTxFlags adds flags required by apptx
|
||||
func AddAppTxFlags(fs *flag.FlagSet) {
|
||||
fs.String(AmountFlag, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
|
||||
fs.String(FeeFlag, "0mycoin", "Coins for the transaction fee of the format <amt><coin>")
|
||||
fs.Int64(GasFlag, 0, "Amount of gas for this transaction")
|
||||
fs.Int(SequenceFlag, -1, "Sequence number for this transaction")
|
||||
fs.String(FlagAmount, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
|
||||
fs.String(FlagFee, "0mycoin", "Coins for the transaction fee of the format <amt><coin>")
|
||||
fs.Int64(FlagGas, 0, "Amount of gas for this transaction")
|
||||
fs.Int(FlagSequence, -1, "Sequence number for this transaction")
|
||||
}
|
||||
|
||||
// ReadAppTxFlags reads in the standard flags
|
||||
// your command should parse info to set tx.Name and tx.Data
|
||||
func ReadAppTxFlags(tx *btypes.AppTx) error {
|
||||
//parse the fee and amounts into coin types
|
||||
var err error
|
||||
tx.Fee, err = btypes.ParseCoin(viper.GetString(FeeFlag))
|
||||
func ReadAppTxFlags() (gas int64, fee btypes.Coin, txInput btypes.TxInput, err error) {
|
||||
|
||||
// Set the gas
|
||||
gas = viper.GetInt64(FlagGas)
|
||||
|
||||
// Parse the fee and amounts into coin types
|
||||
fee, err = btypes.ParseCoin(viper.GetString(FlagFee))
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
amountCoins, err := btypes.ParseCoins(viper.GetString(AmountFlag))
|
||||
|
||||
// craft the inputs
|
||||
var amount btypes.Coins
|
||||
amount, err = btypes.ParseCoins(viper.GetString(FlagAmount))
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
txInput = btypes.TxInput{
|
||||
Coins: amount,
|
||||
Sequence: viper.GetInt(FlagSequence),
|
||||
}
|
||||
|
||||
// set the gas
|
||||
tx.Gas = viper.GetInt64(GasFlag)
|
||||
|
||||
// craft the inputs and outputs
|
||||
tx.Input = btypes.TxInput{
|
||||
Coins: amountCoins,
|
||||
Sequence: viper.GetInt(SequenceFlag),
|
||||
}
|
||||
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
// WrapAppTx wraps the transaction with chain id
|
||||
func WrapAppTx(tx *btypes.AppTx) *AppTx {
|
||||
return &AppTx{
|
||||
chainID: commands.GetChainID(),
|
||||
@@ -145,25 +194,7 @@ func WrapAppTx(tx *btypes.AppTx) *AppTx {
|
||||
|
||||
/** TODO copied from basecoin cli - put in common somewhere? **/
|
||||
|
||||
// ParseHexFlag parses a flag string to byte array
|
||||
func ParseHexFlag(flag string) ([]byte, error) {
|
||||
return hex.DecodeString(StripHex(viper.GetString(flag)))
|
||||
}
|
||||
|
||||
// Returns true for non-empty hex-string prefixed with "0x"
|
||||
func isHex(s string) bool {
|
||||
if len(s) > 2 && s[:2] == "0x" {
|
||||
_, err := hex.DecodeString(s[2:])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func StripHex(s string) string {
|
||||
if isHex(s) {
|
||||
return s[2:]
|
||||
}
|
||||
return s
|
||||
return hex.DecodeString(cmn.StripHex(viper.GetString(flag)))
|
||||
}
|
||||
|
||||
@@ -97,7 +97,8 @@ func (s *SendTx) ValidateBasic() error {
|
||||
}
|
||||
}
|
||||
for _, out := range s.Tx.Outputs {
|
||||
if len(out.Address) != 20 {
|
||||
// we now allow chain/addr, so it can be more than 20 bytes
|
||||
if len(out.Address) < 20 {
|
||||
return errors.Errorf("Invalid output address length: %d", len(out.Address))
|
||||
}
|
||||
if !out.Coins.IsValid() {
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package counter
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
txcmd "github.com/tendermint/light-client/commands/txs"
|
||||
|
||||
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
|
||||
"github.com/tendermint/basecoin/plugins/counter"
|
||||
btypes "github.com/tendermint/basecoin/types"
|
||||
)
|
||||
|
||||
var CounterTxCmd = &cobra.Command{
|
||||
Use: "counter",
|
||||
Short: "add a vote to the counter",
|
||||
Long: `Add a vote to the counter.
|
||||
|
||||
You must pass --valid for it to count and the countfee will be added to the counter.`,
|
||||
RunE: doCounterTx,
|
||||
}
|
||||
|
||||
const (
|
||||
CountFeeFlag = "countfee"
|
||||
ValidFlag = "valid"
|
||||
)
|
||||
|
||||
func init() {
|
||||
fs := CounterTxCmd.Flags()
|
||||
bcmd.AddAppTxFlags(fs)
|
||||
fs.String(CountFeeFlag, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
|
||||
fs.Bool(ValidFlag, false, "Is count valid?")
|
||||
}
|
||||
|
||||
func doCounterTx(cmd *cobra.Command, args []string) error {
|
||||
tx := new(btypes.AppTx)
|
||||
// Note: we don't support loading apptx from json currently, so skip that
|
||||
|
||||
// read the standard flags
|
||||
err := bcmd.ReadAppTxFlags(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now read the app-specific flags
|
||||
err = readCounterFlags(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
app := bcmd.WrapAppTx(tx)
|
||||
app.AddSigner(txcmd.GetSigner())
|
||||
|
||||
// Sign if needed and post. This it the work-horse
|
||||
bres, err := txcmd.SignAndPostTx(app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// output result
|
||||
return txcmd.OutputTx(bres)
|
||||
}
|
||||
|
||||
// readCounterFlags sets the app-specific data in the AppTx
|
||||
func readCounterFlags(tx *btypes.AppTx) error {
|
||||
countFee, err := btypes.ParseCoins(viper.GetString(CountFeeFlag))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := counter.CounterTx{
|
||||
Valid: viper.GetBool(ValidFlag),
|
||||
Fee: countFee,
|
||||
}
|
||||
|
||||
tx.Name = counter.New().Name()
|
||||
tx.Data = wire.BinaryBytes(ctx)
|
||||
return nil
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package counter
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
proofcmd "github.com/tendermint/light-client/commands/proofs"
|
||||
|
||||
"github.com/tendermint/basecoin/plugins/counter"
|
||||
)
|
||||
|
||||
var CounterQueryCmd = &cobra.Command{
|
||||
Use: "counter",
|
||||
Short: "Query counter state, with proof",
|
||||
RunE: doCounterQuery,
|
||||
}
|
||||
|
||||
func doCounterQuery(cmd *cobra.Command, args []string) error {
|
||||
key := counter.New().StateKey()
|
||||
|
||||
var cp counter.CounterPluginState
|
||||
proof, err := proofcmd.GetAndParseAppProof(key, &cp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return proofcmd.OutputProof(cp, proof.BlockHeight())
|
||||
}
|
||||
|
||||
/*** doesn't seem to be needed anymore??? ***/
|
||||
|
||||
// type CounterPresenter struct{}
|
||||
|
||||
// func (_ CounterPresenter) MakeKey(str string) ([]byte, error) {
|
||||
// key := counter.New().StateKey()
|
||||
// return key, nil
|
||||
// }
|
||||
|
||||
// func (_ CounterPresenter) ParseData(raw []byte) (interface{}, error) {
|
||||
// var cp counter.CounterPluginState
|
||||
// err := wire.ReadBinaryBytes(raw, &cp)
|
||||
// return cp, err
|
||||
// }
|
||||
+4
-12
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
|
||||
bcount "github.com/tendermint/basecoin/cmd/basecli/counter"
|
||||
)
|
||||
|
||||
// BaseCli represents the base command when called without any subcommands
|
||||
@@ -32,26 +31,19 @@ tmcli to work for any custom abci app.
|
||||
func main() {
|
||||
commands.AddBasicFlags(BaseCli)
|
||||
|
||||
// prepare queries
|
||||
// Prepare queries
|
||||
pr := proofs.RootCmd
|
||||
// these are default parsers, but you optional in your app
|
||||
// These are default parsers, but optional in your app (you can remove key)
|
||||
pr.AddCommand(proofs.TxCmd)
|
||||
pr.AddCommand(proofs.KeyCmd)
|
||||
pr.AddCommand(bcmd.AccountQueryCmd)
|
||||
pr.AddCommand(bcount.CounterQueryCmd)
|
||||
|
||||
// here is how you would add the custom txs... but don't really add demo in your app
|
||||
// you will always want this for the base send command
|
||||
proofs.TxPresenters.Register("base", bcmd.BaseTxPresenter{})
|
||||
tr := txs.RootCmd
|
||||
tr.AddCommand(bcmd.SendTxCmd)
|
||||
tr.AddCommand(bcount.CounterTxCmd)
|
||||
|
||||
// TODO
|
||||
|
||||
// txs.Register("send", bcmd.SendTxMaker{})
|
||||
// txs.Register("counter", bcount.CounterTxMaker{})
|
||||
|
||||
// set up the various commands to use
|
||||
// Set up the various commands to use
|
||||
BaseCli.AddCommand(
|
||||
commands.InitCmd,
|
||||
commands.ResetCmd,
|
||||
|
||||
@@ -20,11 +20,6 @@ func main() {
|
||||
commands.StartCmd,
|
||||
commands.RelayCmd,
|
||||
commands.TxCmd,
|
||||
commands.QueryCmd,
|
||||
commands.KeyCmd,
|
||||
commands.VerifyCmd,
|
||||
commands.BlockCmd,
|
||||
commands.AccountCmd,
|
||||
commands.UnsafeResetAllCmd,
|
||||
commands.VersionCmd,
|
||||
)
|
||||
|
||||
+3
-206
@@ -1,7 +1,6 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@@ -12,8 +11,6 @@ import (
|
||||
"github.com/tendermint/basecoin/plugins/ibc"
|
||||
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/merkleeyes/iavl"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// returns a new IBC plugin to be registered with Basecoin
|
||||
@@ -33,85 +30,25 @@ var (
|
||||
Short: "Register a blockchain via IBC",
|
||||
RunE: ibcRegisterTxCmd,
|
||||
}
|
||||
|
||||
IBCUpdateTxCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Update the latest state of a blockchain via IBC",
|
||||
RunE: ibcUpdateTxCmd,
|
||||
}
|
||||
|
||||
IBCPacketTxCmd = &cobra.Command{
|
||||
Use: "packet",
|
||||
Short: "Send a new packet via IBC",
|
||||
}
|
||||
|
||||
IBCPacketCreateTxCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create an egress IBC packet",
|
||||
RunE: ibcPacketCreateTxCmd,
|
||||
}
|
||||
|
||||
IBCPacketPostTxCmd = &cobra.Command{
|
||||
Use: "post",
|
||||
Short: "Deliver an IBC packet to another chain",
|
||||
RunE: ibcPacketPostTxCmd,
|
||||
}
|
||||
)
|
||||
|
||||
//flags
|
||||
var (
|
||||
ibcChainIDFlag string
|
||||
ibcGenesisFlag string
|
||||
ibcHeaderFlag string
|
||||
ibcCommitFlag string
|
||||
ibcFromFlag string
|
||||
ibcToFlag string
|
||||
ibcTypeFlag string
|
||||
ibcPayloadFlag string
|
||||
ibcPacketFlag string
|
||||
ibcProofFlag string
|
||||
ibcSequenceFlag int
|
||||
ibcHeightFlag int
|
||||
ibcChainIDFlag string
|
||||
ibcGenesisFlag string
|
||||
)
|
||||
|
||||
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)
|
||||
IBCTxCmd.AddCommand(IBCRegisterTxCmd)
|
||||
RegisterTxSubcommand(IBCTxCmd)
|
||||
}
|
||||
|
||||
@@ -148,143 +85,3 @@ func ibcRegisterTxCmd(cmd *cobra.Command, args []string) error {
|
||||
|
||||
return AppTx(name, data)
|
||||
}
|
||||
|
||||
func ibcUpdateTxCmd(cmd *cobra.Command, args []string) error {
|
||||
headerBytes, err := hex.DecodeString(StripHex(ibcHeaderFlag))
|
||||
if err != nil {
|
||||
return errors.Errorf("Header (%v) is invalid hex: %v\n", ibcHeaderFlag, err)
|
||||
}
|
||||
|
||||
commitBytes, err := hex.DecodeString(StripHex(ibcCommitFlag))
|
||||
if err != nil {
|
||||
return errors.Errorf("Commit (%v) is invalid hex: %v\n", ibcCommitFlag, err)
|
||||
}
|
||||
|
||||
header := new(tmtypes.Header)
|
||||
commit := new(tmtypes.Commit)
|
||||
|
||||
err = wire.ReadBinaryBytes(headerBytes, &header)
|
||||
if err != nil {
|
||||
return errors.Errorf("Error unmarshalling header: %v\n", err)
|
||||
}
|
||||
|
||||
err = wire.ReadBinaryBytes(commitBytes, &commit)
|
||||
if err != nil {
|
||||
return errors.Errorf("Error unmarshalling commit: %v\n", err)
|
||||
}
|
||||
|
||||
ibcTx := ibc.IBCUpdateChainTx{
|
||||
Header: *header,
|
||||
Commit: *commit,
|
||||
}
|
||||
|
||||
out, err := json.Marshal(ibcTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("IBCTx: %s\n", string(out))
|
||||
|
||||
data := []byte(wire.BinaryBytes(struct {
|
||||
ibc.IBCTx `json:"unwrap"`
|
||||
}{ibcTx}))
|
||||
name := "IBC"
|
||||
|
||||
return AppTx(name, data)
|
||||
}
|
||||
|
||||
func ibcPacketCreateTxCmd(cmd *cobra.Command, args []string) error {
|
||||
fromChain, toChain := ibcFromFlag, ibcToFlag
|
||||
packetType := ibcTypeFlag
|
||||
|
||||
payloadBytes, err := hex.DecodeString(StripHex(ibcPayloadFlag))
|
||||
if err != nil {
|
||||
return errors.Errorf("Payload (%v) is invalid hex: %v\n", ibcPayloadFlag, err)
|
||||
}
|
||||
|
||||
sequence, err := ibcSequenceCmd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var payload ibc.Payload
|
||||
if err := wire.ReadBinaryBytes(payloadBytes, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ibcTx := ibc.IBCPacketCreateTx{
|
||||
Packet: ibc.Packet{
|
||||
SrcChainID: fromChain,
|
||||
DstChainID: toChain,
|
||||
Sequence: sequence,
|
||||
Type: packetType,
|
||||
Payload: payload,
|
||||
},
|
||||
}
|
||||
|
||||
out, err := json.Marshal(ibcTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("IBCTx: %s\n", string(out))
|
||||
|
||||
data := []byte(wire.BinaryBytes(struct {
|
||||
ibc.IBCTx `json:"unwrap"`
|
||||
}{ibcTx}))
|
||||
|
||||
return AppTx("IBC", data)
|
||||
}
|
||||
|
||||
func ibcPacketPostTxCmd(cmd *cobra.Command, args []string) error {
|
||||
fromChain, fromHeight := ibcFromFlag, ibcHeightFlag
|
||||
|
||||
packetBytes, err := hex.DecodeString(StripHex(ibcPacketFlag))
|
||||
if err != nil {
|
||||
return errors.Errorf("Packet (%v) is invalid hex: %v\n", ibcPacketFlag, err)
|
||||
}
|
||||
|
||||
proofBytes, err := hex.DecodeString(StripHex(ibcProofFlag))
|
||||
if err != nil {
|
||||
return errors.Errorf("Proof (%v) is invalid hex: %v\n", ibcProofFlag, err)
|
||||
}
|
||||
|
||||
var packet ibc.Packet
|
||||
proof := new(iavl.IAVLProof)
|
||||
|
||||
err = wire.ReadBinaryBytes(packetBytes, &packet)
|
||||
if err != nil {
|
||||
return errors.Errorf("Error unmarshalling packet: %v\n", err)
|
||||
}
|
||||
|
||||
err = wire.ReadBinaryBytes(proofBytes, &proof)
|
||||
if err != nil {
|
||||
return errors.Errorf("Error unmarshalling proof: %v\n", err)
|
||||
}
|
||||
|
||||
ibcTx := ibc.IBCPacketPostTx{
|
||||
FromChainID: fromChain,
|
||||
FromChainHeight: uint64(fromHeight),
|
||||
Packet: packet,
|
||||
Proof: proof,
|
||||
}
|
||||
|
||||
out, err := json.Marshal(ibcTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("IBCTx: %s\n", string(out))
|
||||
|
||||
data := []byte(wire.BinaryBytes(struct {
|
||||
ibc.IBCTx `json:"unwrap"`
|
||||
}{ibcTx}))
|
||||
|
||||
return AppTx("IBC", data)
|
||||
}
|
||||
|
||||
func ibcSequenceCmd() (uint64, error) {
|
||||
if ibcSequenceFlag >= 0 {
|
||||
return uint64(ibcSequenceFlag), nil
|
||||
}
|
||||
|
||||
// TODO: get sequence
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
+1
-44
@@ -9,42 +9,13 @@ import (
|
||||
"strings"
|
||||
|
||||
//"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
)
|
||||
|
||||
//commands
|
||||
var (
|
||||
KeyCmd = &cobra.Command{
|
||||
Use: "key",
|
||||
Short: "Manage keys",
|
||||
}
|
||||
|
||||
NewKeyCmd = &cobra.Command{
|
||||
Use: "new",
|
||||
Short: "Create a new private key",
|
||||
RunE: newKeyCmd,
|
||||
}
|
||||
)
|
||||
|
||||
func newKeyCmd(cmd *cobra.Command, args []string) error {
|
||||
key := genKey()
|
||||
keyJSON, err := json.MarshalIndent(key, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(keyJSON))
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
//register commands
|
||||
KeyCmd.AddCommand(NewKeyCmd)
|
||||
}
|
||||
|
||||
//---------------------------------------------
|
||||
// simple implementation of a key
|
||||
|
||||
@@ -74,20 +45,6 @@ func (k *Key) Sign(msg []byte) crypto.Signature {
|
||||
return k.PrivKey.Sign(msg)
|
||||
}
|
||||
|
||||
// Generates a new validator with private key.
|
||||
func genKey() *Key {
|
||||
privKey := crypto.GenPrivKeyEd25519()
|
||||
pubKey := privKey.PubKey()
|
||||
addrBytes := pubKey.Address()
|
||||
var addr Address
|
||||
copy(addr[:], addrBytes)
|
||||
return &Key{
|
||||
Address: addr,
|
||||
PubKey: pubKey,
|
||||
PrivKey: privKey.Wrap(),
|
||||
}
|
||||
}
|
||||
|
||||
func LoadKey(keyFile string) (*Key, error) {
|
||||
filePath := keyFile
|
||||
|
||||
|
||||
@@ -19,11 +19,6 @@ 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)
|
||||
|
||||
@@ -1,234 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/go-wire/data"
|
||||
"github.com/tendermint/merkleeyes/iavl"
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
//commands
|
||||
var (
|
||||
QueryCmd = &cobra.Command{
|
||||
Use: "query [key]",
|
||||
Short: "Query the merkle tree",
|
||||
RunE: queryCmd,
|
||||
}
|
||||
|
||||
AccountCmd = &cobra.Command{
|
||||
Use: "account [address]",
|
||||
Short: "Get details of an account",
|
||||
RunE: accountCmd,
|
||||
}
|
||||
|
||||
BlockCmd = &cobra.Command{
|
||||
Use: "block [height]",
|
||||
Short: "Get the header and commit of a block",
|
||||
RunE: blockCmd,
|
||||
}
|
||||
|
||||
VerifyCmd = &cobra.Command{
|
||||
Use: "verify",
|
||||
Short: "Verify the IAVL proof",
|
||||
RunE: verifyCmd,
|
||||
}
|
||||
)
|
||||
|
||||
//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 queryCmd(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("query command requires an argument ([key])") //never stack trace
|
||||
}
|
||||
|
||||
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.Errorf("Query key (%v) is invalid hex: %v\n", keyString, err)
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := Query(nodeFlag, key)
|
||||
if err != nil {
|
||||
return errors.Errorf("Query returns error: %v\n", err)
|
||||
}
|
||||
|
||||
if !resp.Code.IsOK() {
|
||||
return errors.Errorf("Query for key (%v) returned non-zero code (%v): %v", keyString, resp.Code, resp.Log)
|
||||
}
|
||||
|
||||
val := resp.Value
|
||||
proof := resp.Proof
|
||||
height := resp.Height
|
||||
|
||||
out, err := json.Marshal(struct {
|
||||
Value data.Bytes `json:"value"`
|
||||
Proof data.Bytes `json:"proof"`
|
||||
Height uint64 `json:"height"`
|
||||
}{val, proof, height})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(string(out))
|
||||
return nil
|
||||
}
|
||||
|
||||
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(args[0])
|
||||
|
||||
// convert destination address to bytes
|
||||
addr, err := hex.DecodeString(addrHex)
|
||||
if err != nil {
|
||||
return errors.Errorf("Account address (%v) is invalid hex: %v\n", addrHex, err)
|
||||
}
|
||||
|
||||
httpClient := client.NewHTTP(nodeFlag, "/websocket")
|
||||
acc, err := getAccWithClient(httpClient, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := json.Marshal(acc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(out))
|
||||
return nil
|
||||
}
|
||||
|
||||
func blockCmd(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("block command requires an argument ([height])") //never stack trace
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
out, err := json.Marshal(struct {
|
||||
Hex BlockHex `json:"hex"`
|
||||
JSON BlockJSON `json:"json"`
|
||||
}{
|
||||
BlockHex{
|
||||
Header: wire.BinaryBytes(header),
|
||||
Commit: wire.BinaryBytes(commit),
|
||||
},
|
||||
BlockJSON{
|
||||
Header: header,
|
||||
Commit: commit,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(string(out))
|
||||
return nil
|
||||
}
|
||||
|
||||
type BlockHex struct {
|
||||
Header data.Bytes `json:"header"`
|
||||
Commit data.Bytes `json:"commit"`
|
||||
}
|
||||
|
||||
type BlockJSON struct {
|
||||
Header *tmtypes.Header `json:"header"`
|
||||
Commit *tmtypes.Commit `json:"commit"`
|
||||
}
|
||||
|
||||
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.Errorf("Key (%v) is invalid hex: %v\n", keyString, err)
|
||||
}
|
||||
}
|
||||
|
||||
value := []byte(valueString)
|
||||
if isHex(valueString) {
|
||||
value, err = hex.DecodeString(StripHex(valueString))
|
||||
if err != nil {
|
||||
return errors.Errorf("Value (%v) is invalid hex: %v\n", valueString, err)
|
||||
}
|
||||
}
|
||||
|
||||
root, err := hex.DecodeString(StripHex(rootFlag))
|
||||
if err != nil {
|
||||
return errors.Errorf("Root (%v) is invalid hex: %v\n", rootFlag, err)
|
||||
}
|
||||
|
||||
proofBytes, err := hex.DecodeString(StripHex(proofFlag))
|
||||
if err != nil {
|
||||
return errors.Errorf("Proof (%v) is invalid hex: %v\n", proofFlag, err)
|
||||
}
|
||||
|
||||
proof, err := iavl.ReadProof(proofBytes)
|
||||
if err != nil {
|
||||
return errors.Errorf("Error unmarshalling proof: %v\n", err)
|
||||
}
|
||||
|
||||
if proof.Verify(key, value, root) {
|
||||
fmt.Println("OK")
|
||||
} else {
|
||||
return errors.New("Proof does not verify")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -20,18 +18,6 @@ var (
|
||||
Use: "tx",
|
||||
Short: "Create, sign, and broadcast a transaction",
|
||||
}
|
||||
|
||||
SendTxCmd = &cobra.Command{
|
||||
Use: "send",
|
||||
Short: "A SendTx transaction, for sending tokens around",
|
||||
RunE: sendTxCmd,
|
||||
}
|
||||
|
||||
AppTxCmd = &cobra.Command{
|
||||
Use: "app",
|
||||
Short: "An AppTx transaction, for sending raw data to plugins",
|
||||
RunE: appTxCmd,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -43,11 +29,6 @@ var (
|
||||
gasFlag int
|
||||
feeFlag string
|
||||
chainIDFlag string
|
||||
|
||||
//non-persistent flags
|
||||
toFlag string
|
||||
dataFlag string
|
||||
nameFlag string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -62,106 +43,7 @@ func init() {
|
||||
{&feeFlag, "fee", "0coin", "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 sendTxCmd(cmd *cobra.Command, args []string) error {
|
||||
|
||||
var toHex string
|
||||
var chainPrefix string
|
||||
spl := strings.Split(toFlag, "/")
|
||||
switch len(spl) {
|
||||
case 1:
|
||||
toHex = spl[0]
|
||||
case 2:
|
||||
chainPrefix = spl[0]
|
||||
toHex = spl[1]
|
||||
default:
|
||||
return errors.Errorf("To address has too many slashes")
|
||||
}
|
||||
|
||||
// convert destination address to bytes
|
||||
to, err := hex.DecodeString(StripHex(toHex))
|
||||
if err != nil {
|
||||
return errors.Errorf("To address is invalid hex: %v\n", err)
|
||||
}
|
||||
|
||||
if chainPrefix != "" {
|
||||
to = []byte(chainPrefix + "/" + string(to))
|
||||
}
|
||||
|
||||
// load the priv key
|
||||
privKey, err := LoadKey(fromFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the sequence number for the tx
|
||||
sequence, err := getSeq(privKey.Address[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//parse the fee and amounts into coin types
|
||||
feeCoin, err := types.ParseCoin(feeFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amountCoins, err := types.ParseCoins(amountFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// craft the tx
|
||||
input := types.NewTxInput(privKey.PubKey, amountCoins, sequence)
|
||||
output := newOutput(to, amountCoins)
|
||||
tx := &types.SendTx{
|
||||
Gas: int64(gasFlag),
|
||||
Fee: feeCoin,
|
||||
Inputs: []types.TxInput{input},
|
||||
Outputs: []types.TxOutput{output},
|
||||
}
|
||||
|
||||
// sign that puppy
|
||||
signBytes := tx.SignBytes(chainIDFlag)
|
||||
tx.Inputs[0].Signature = privKey.Sign(signBytes)
|
||||
|
||||
out := wire.BinaryBytes(tx)
|
||||
fmt.Println("Signed SendTx:")
|
||||
fmt.Printf("%X\n", out)
|
||||
|
||||
// broadcast the transaction to tendermint
|
||||
data, log, err := broadcastTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Response: %X ; %s\n", data, log)
|
||||
return nil
|
||||
}
|
||||
|
||||
func appTxCmd(cmd *cobra.Command, args []string) error {
|
||||
// convert data to bytes
|
||||
data := []byte(dataFlag)
|
||||
if isHex(dataFlag) {
|
||||
data, _ = hex.DecodeString(dataFlag)
|
||||
}
|
||||
name := nameFlag
|
||||
return AppTx(name, data)
|
||||
}
|
||||
|
||||
func AppTx(name string, data []byte) error {
|
||||
|
||||
@@ -19,11 +19,6 @@ func main() {
|
||||
commands.InitCmd,
|
||||
commands.StartCmd,
|
||||
commands.TxCmd,
|
||||
commands.QueryCmd,
|
||||
commands.KeyCmd,
|
||||
commands.VerifyCmd,
|
||||
commands.BlockCmd,
|
||||
commands.AccountCmd,
|
||||
commands.UnsafeResetAllCmd,
|
||||
commands.VersionCmd,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
txcmd "github.com/tendermint/light-client/commands/txs"
|
||||
|
||||
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
|
||||
"github.com/tendermint/basecoin/plugins/counter"
|
||||
btypes "github.com/tendermint/basecoin/types"
|
||||
)
|
||||
|
||||
//CounterTxCmd is the CLI command to execute the counter
|
||||
// through the appTx Command
|
||||
var CounterTxCmd = &cobra.Command{
|
||||
Use: "counter",
|
||||
Short: "add a vote to the counter",
|
||||
Long: `Add a vote to the counter.
|
||||
|
||||
You must pass --valid for it to count and the countfee will be added to the counter.`,
|
||||
RunE: counterTxCmd,
|
||||
}
|
||||
|
||||
const (
|
||||
flagCountFee = "countfee"
|
||||
flagValid = "valid"
|
||||
)
|
||||
|
||||
func init() {
|
||||
fs := CounterTxCmd.Flags()
|
||||
bcmd.AddAppTxFlags(fs)
|
||||
fs.String(flagCountFee, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
|
||||
fs.Bool(flagValid, false, "Is count valid?")
|
||||
}
|
||||
|
||||
func counterTxCmd(cmd *cobra.Command, args []string) error {
|
||||
// Note: we don't support loading apptx from json currently, so skip that
|
||||
|
||||
// Read the app-specific flags
|
||||
name, data, err := getAppData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read the standard app-tx flags
|
||||
gas, fee, txInput, err := bcmd.ReadAppTxFlags()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create AppTx and broadcast
|
||||
tx := &btypes.AppTx{
|
||||
Gas: gas,
|
||||
Fee: fee,
|
||||
Name: name,
|
||||
Input: txInput,
|
||||
Data: data,
|
||||
}
|
||||
res, err := bcmd.BroadcastAppTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Output result
|
||||
return txcmd.OutputTx(res)
|
||||
}
|
||||
|
||||
func getAppData() (name string, data []byte, err error) {
|
||||
countFee, err := btypes.ParseCoins(viper.GetString(flagCountFee))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ctx := counter.CounterTx{
|
||||
Valid: viper.GetBool(flagValid),
|
||||
Fee: countFee,
|
||||
}
|
||||
|
||||
name = counter.New().Name()
|
||||
data = wire.BinaryBytes(ctx)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
proofcmd "github.com/tendermint/light-client/commands/proofs"
|
||||
|
||||
"github.com/tendermint/basecoin/plugins/counter"
|
||||
)
|
||||
|
||||
//CounterQueryCmd CLI command to query the counter state
|
||||
var CounterQueryCmd = &cobra.Command{
|
||||
Use: "counter",
|
||||
Short: "Query counter state, with proof",
|
||||
RunE: counterQueryCmd,
|
||||
}
|
||||
|
||||
func counterQueryCmd(cmd *cobra.Command, args []string) error {
|
||||
key := counter.New().StateKey()
|
||||
|
||||
var cp counter.CounterPluginState
|
||||
proof, err := proofcmd.GetAndParseAppProof(key, &cp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return proofcmd.OutputProof(cp, proof.BlockHeight())
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
keycmd "github.com/tendermint/go-crypto/cmd"
|
||||
"github.com/tendermint/light-client/commands"
|
||||
"github.com/tendermint/light-client/commands/proofs"
|
||||
"github.com/tendermint/light-client/commands/proxy"
|
||||
"github.com/tendermint/light-client/commands/seeds"
|
||||
"github.com/tendermint/light-client/commands/txs"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
|
||||
bcount "github.com/tendermint/basecoin/cmd/countercli/commands"
|
||||
)
|
||||
|
||||
// BaseCli represents the base command when called without any subcommands
|
||||
var BaseCli = &cobra.Command{
|
||||
Use: "basecli",
|
||||
Short: "Light client for tendermint",
|
||||
Long: `Basecli is an version of tmcli including custom logic to
|
||||
present a nice (not raw hex) interface to the basecoin blockchain structure.
|
||||
|
||||
This is a useful tool, but also serves to demonstrate how one can configure
|
||||
tmcli to work for any custom abci app.
|
||||
`,
|
||||
}
|
||||
|
||||
func main() {
|
||||
commands.AddBasicFlags(BaseCli)
|
||||
|
||||
// Prepare queries
|
||||
pr := proofs.RootCmd
|
||||
// These are default parsers, but you optional in your app
|
||||
pr.AddCommand(proofs.TxCmd)
|
||||
pr.AddCommand(proofs.KeyCmd)
|
||||
pr.AddCommand(bcmd.AccountQueryCmd)
|
||||
|
||||
// IMPORTANT: here is how you add custom query commands in your app
|
||||
pr.AddCommand(bcount.CounterQueryCmd)
|
||||
|
||||
proofs.TxPresenters.Register("base", bcmd.BaseTxPresenter{})
|
||||
tr := txs.RootCmd
|
||||
tr.AddCommand(bcmd.SendTxCmd)
|
||||
|
||||
// IMPORTANT: here is how you add custom tx construction for your app
|
||||
tr.AddCommand(bcount.CounterTxCmd)
|
||||
|
||||
// Set up the various commands to use
|
||||
BaseCli.AddCommand(
|
||||
commands.InitCmd,
|
||||
commands.ResetCmd,
|
||||
keycmd.RootCmd,
|
||||
seeds.RootCmd,
|
||||
pr,
|
||||
tr,
|
||||
proxy.RootCmd)
|
||||
|
||||
cmd := cli.PrepareMainCmd(BaseCli, "BC", os.ExpandEnv("$HOME/.basecli"))
|
||||
cmd.Execute()
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#! /bin/bash
|
||||
|
||||
killall -9 basecoin tendermint
|
||||
TMHOME=./data/chain1 tendermint unsafe_reset_all
|
||||
TMHOME=./data/chain2 tendermint unsafe_reset_all
|
||||
|
||||
rm ./*.log
|
||||
|
||||
rm ./data/chain1/*.bak
|
||||
rm ./data/chain2/*.bak
|
||||
@@ -1,11 +0,0 @@
|
||||
# This is a TOML config file.
|
||||
# For more information, see https://github.com/toml-lang/toml
|
||||
|
||||
proxy_app = "tcp://127.0.0.1:46658"
|
||||
moniker = "anonymous"
|
||||
node_laddr = "tcp://0.0.0.0:46656"
|
||||
seeds = ""
|
||||
fast_sync = true
|
||||
db_backend = "leveldb"
|
||||
log_level = "info"
|
||||
rpc_laddr = "tcp://0.0.0.0:46657"
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"app_hash": "",
|
||||
"chain_id": "test_chain_1",
|
||||
"genesis_time": "0001-01-01T00:00:00.000Z",
|
||||
"validators": [
|
||||
{
|
||||
"amount": 10,
|
||||
"name": "",
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data":"D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A"
|
||||
}
|
||||
}
|
||||
],
|
||||
"app_options": {
|
||||
"accounts": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 9007199254740992
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"address": "D397BC62B435F3CF50570FBAB4340FE52C60858F",
|
||||
"priv_key": {
|
||||
"type": "ed25519",
|
||||
"data": "39E75AA1CF7BC710585977EFC375CD1730519186BD231478C339F2819C3C26E7B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"
|
||||
},
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"address":"EBB0B4A899973C524A6BB18A161056A55F590F41","pub_key":{"type":"ed25519","data":"D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A"},"last_height":0,"last_round":0,"last_step":0,"last_signature":null,"priv_key":{"type":"ed25519","data":"5FFDC1EA5FA2CA4A0A5503C86D2D348C5B401AD80FAA1899508F1ED00D8982E8D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A"}}
|
||||
@@ -1,11 +0,0 @@
|
||||
# This is a TOML config file.
|
||||
# For more information, see https://github.com/toml-lang/toml
|
||||
|
||||
proxy_app = "tcp://127.0.0.1:46658"
|
||||
moniker = "anonymous"
|
||||
node_laddr = "tcp://0.0.0.0:46656"
|
||||
seeds = ""
|
||||
fast_sync = true
|
||||
db_backend = "leveldb"
|
||||
log_level = "info"
|
||||
rpc_laddr = "tcp://0.0.0.0:46657"
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"app_hash": "",
|
||||
"chain_id": "test_chain_2",
|
||||
"genesis_time": "0001-01-01T00:00:00.000Z",
|
||||
"validators": [
|
||||
{
|
||||
"amount": 10,
|
||||
"name": "",
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F"
|
||||
}
|
||||
}
|
||||
],
|
||||
"app_options": {
|
||||
"accounts": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "0628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 9007199254740992
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"address": "053BA0F19616AFF975C8756A2CBFF04F408B4D47",
|
||||
"priv_key": {
|
||||
"type": "ed25519",
|
||||
"data": "22920C428043D869987F253D7C9B2305E7010642C40CE88A52C9F6CE5ACC42080628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D"
|
||||
},
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "0628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{"address":"D42CFCB9C42DF9A73143EEA89255D1DF027B6240","pub_key":{"type":"ed25519","data":"9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F"},"last_height":0,"last_round":0,"last_step":0,"last_signature":null,"priv_key":{"type":"ed25519","data":"6353FAF4ADEB03EA496A9EAE5BE56C4C6A851CB705401788184FDC9198413C2C9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F"}}
|
||||
-198
@@ -1,198 +0,0 @@
|
||||
#! /bin/bash
|
||||
set -e
|
||||
|
||||
cd $GOPATH/src/github.com/tendermint/basecoin/demo
|
||||
|
||||
LOG_DIR="."
|
||||
TM_VERSION="master"
|
||||
#TM_VERSION="v0.10.0"
|
||||
|
||||
if [[ "$CIRCLECI" == "true" ]]; then
|
||||
# set log dir
|
||||
LOG_DIR="${CIRCLE_ARTIFACTS}"
|
||||
|
||||
# install tendermint
|
||||
set +e
|
||||
go get github.com/tendermint/tendermint
|
||||
pushd $GOPATH/src/github.com/tendermint/tendermint
|
||||
git checkout $TM_VERSION
|
||||
glide install
|
||||
go install ./cmd/tendermint
|
||||
popd
|
||||
set -e
|
||||
fi
|
||||
|
||||
set -u
|
||||
|
||||
function ifExit() {
|
||||
if [[ "$?" != 0 ]]; then
|
||||
echo "FAIL"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function removeQuotes() {
|
||||
temp="${1%\"}"
|
||||
temp="${temp#\"}"
|
||||
echo "$temp"
|
||||
}
|
||||
|
||||
function waitForNode() {
|
||||
addr=$1
|
||||
set +e
|
||||
curl -s $addr/status > /dev/null
|
||||
ERR=$?
|
||||
i=0
|
||||
while [ "$ERR" != 0 ]; do
|
||||
if [[ "$i" == 10 ]]; then
|
||||
echo "waited too long for chain to start"
|
||||
exit 1
|
||||
fi
|
||||
echo "...... still waiting on $addr"
|
||||
sleep 1
|
||||
curl -s $addr/status > /dev/null
|
||||
ERR=$?
|
||||
i=$((i+1))
|
||||
done
|
||||
set -e
|
||||
echo "... node $addr is up"
|
||||
}
|
||||
|
||||
function waitForBlock() {
|
||||
addr=$1
|
||||
b1=`curl -s $addr/status | jq .result.latest_block_height`
|
||||
b2=$b1
|
||||
while [ "$b2" == "$b1" ]; do
|
||||
echo "Waiting for node $addr to commit a block ..."
|
||||
sleep 1
|
||||
b2=`curl -s $addr/status | jq .result.latest_block_height`
|
||||
done
|
||||
}
|
||||
|
||||
# make basecoin root vars
|
||||
export BCHOME="."
|
||||
BCHOME1="./data/chain1"
|
||||
BCHOME2="./data/chain2"
|
||||
|
||||
# grab the chain ids
|
||||
CHAIN_ID1=$(cat $BCHOME1/genesis.json | jq .chain_id)
|
||||
CHAIN_ID1=$(removeQuotes $CHAIN_ID1)
|
||||
CHAIN_ID2=$(cat $BCHOME2/genesis.json | jq .chain_id)
|
||||
CHAIN_ID2=$(removeQuotes $CHAIN_ID2)
|
||||
echo "CHAIN_ID1: $CHAIN_ID1"
|
||||
echo "CHAIN_ID2: $CHAIN_ID2"
|
||||
|
||||
# make reusable chain flags
|
||||
CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from $BCHOME1/key.json"
|
||||
CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from $BCHOME2/key.json --node tcp://localhost:36657"
|
||||
|
||||
|
||||
echo ""
|
||||
echo "... starting chains"
|
||||
echo ""
|
||||
# start the first node
|
||||
TMHOME=$BCHOME1 tendermint node --p2p.skip_upnp --log_level=info &> $LOG_DIR/chain1_tendermint.log &
|
||||
ifExit
|
||||
BCHOME=$BCHOME1 basecoin start --without-tendermint &> $LOG_DIR/chain1_basecoin.log &
|
||||
ifExit
|
||||
|
||||
# start the second node
|
||||
TMHOME=$BCHOME2 tendermint node --p2p.skip_upnp --log_level=info --p2p.laddr tcp://localhost:36656 --rpc.laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> $LOG_DIR/chain2_tendermint.log &
|
||||
ifExit
|
||||
BCHOME=$BCHOME2 basecoin start --address tcp://localhost:36658 --without-tendermint &> $LOG_DIR/chain2_basecoin.log &
|
||||
ifExit
|
||||
|
||||
echo ""
|
||||
echo "... waiting for chains to start"
|
||||
echo ""
|
||||
|
||||
waitForNode localhost:46657
|
||||
waitForNode localhost:36657
|
||||
|
||||
# TODO: remove the sleep
|
||||
# Without it we sometimes get "Account bytes are empty for address: 053BA0F19616AFF975C8756A2CBFF04F408B4D47"
|
||||
sleep 3
|
||||
|
||||
echo "... registering chain1 on chain2"
|
||||
echo ""
|
||||
# register chain1 on chain2
|
||||
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --ibc_chain_id $CHAIN_ID1 --genesis $BCHOME1/genesis.json
|
||||
ifExit
|
||||
|
||||
echo ""
|
||||
echo "... creating egress packet on chain1"
|
||||
echo ""
|
||||
# send coins from chain1 to an address on chain2
|
||||
# TODO: dont hardcode the address
|
||||
basecoin tx send --amount 10mycoin $CHAIN_FLAGS1 --to $CHAIN_ID2/053BA0F19616AFF975C8756A2CBFF04F408B4D47
|
||||
ifExit
|
||||
|
||||
# alternative way to create packets (for testing)
|
||||
# basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --ibc_from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --ibc_sequence 0
|
||||
|
||||
echo ""
|
||||
echo "... querying for packet data"
|
||||
echo ""
|
||||
# query for the packet data and proof
|
||||
# since we only sent one packet, the sequence number is 0
|
||||
QUERY_RESULT=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,0)
|
||||
ifExit
|
||||
HEIGHT=$(echo $QUERY_RESULT | jq .height)
|
||||
PACKET=$(echo $QUERY_RESULT | jq .value)
|
||||
PROOF=$(echo $QUERY_RESULT | jq .proof)
|
||||
PACKET=$(removeQuotes $PACKET)
|
||||
PROOF=$(removeQuotes $PROOF)
|
||||
echo ""
|
||||
echo "QUERY_RESULT: $QUERY_RESULT"
|
||||
echo "HEIGHT: $HEIGHT"
|
||||
echo "PACKET: $PACKET"
|
||||
echo "PROOF: $PROOF"
|
||||
|
||||
|
||||
# the query returns the height of the next block, which contains the app hash
|
||||
# but which may not be committed yet, so we have to wait for it to query the commit
|
||||
echo ""
|
||||
echo "... waiting for a block to be committed"
|
||||
echo ""
|
||||
|
||||
waitForBlock localhost:46657
|
||||
waitForBlock localhost:36657
|
||||
|
||||
echo ""
|
||||
echo "... querying for block data"
|
||||
echo ""
|
||||
# get the header and commit for the height
|
||||
HEADER_AND_COMMIT=$(basecoin block $HEIGHT)
|
||||
ifExit
|
||||
HEADER=$(echo $HEADER_AND_COMMIT | jq .hex.header)
|
||||
HEADER=$(removeQuotes $HEADER)
|
||||
COMMIT=$(echo $HEADER_AND_COMMIT | jq .hex.commit)
|
||||
COMMIT=$(removeQuotes $COMMIT)
|
||||
echo ""
|
||||
echo "HEADER_AND_COMMIT: $HEADER_AND_COMMIT"
|
||||
echo "HEADER: $HEADER"
|
||||
echo "COMMIT: $COMMIT"
|
||||
|
||||
echo ""
|
||||
echo "... updating state of chain1 on chain2"
|
||||
echo ""
|
||||
# update the state of chain1 on chain2
|
||||
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT
|
||||
ifExit
|
||||
|
||||
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 --ibc_from $CHAIN_ID1 --height $HEIGHT --packet 0x$PACKET --proof 0x$PROOF
|
||||
ifExit
|
||||
|
||||
echo ""
|
||||
echo "... checking if the packet is present on chain2"
|
||||
echo ""
|
||||
# query for the packet on chain2
|
||||
basecoin query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,0
|
||||
ifExit
|
||||
|
||||
echo ""
|
||||
echo "DONE!"
|
||||
@@ -1,62 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
|
||||
"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)
|
||||
|
||||
//Register the example with basecoin at start
|
||||
commands.RegisterStartPlugin("example-plugin", func() types.Plugin { return NewExamplePlugin() })
|
||||
}
|
||||
|
||||
//Send a transaction
|
||||
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{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.
|
||||
// Once received in the plugin, these exampleTxBytes are decoded back
|
||||
// into the original ExamplePluginTx struct
|
||||
exampleTxBytes := wire.BinaryBytes(exampleTx)
|
||||
|
||||
// Send the transaction and return any errors.
|
||||
// Here exampleTxBytes is packaged in the `tx.Data` field of an AppTx,
|
||||
// and passed on to the plugin through the following sequence:
|
||||
// - passed as `data` to `commands.AppTx` (cmd/commands/tx.go)
|
||||
// - set as the `tx.Data` field of an AppTx, which is then passed to commands.broadcastTx (cmd/commands/tx.go)
|
||||
// - the tx is broadcast to Tendermint, which runs it through app.CheckTx (app/app.go)
|
||||
// - after passing CheckTx, it will eventually be included in a block and run through app.DeliverTx (app/app.go)
|
||||
// - DeliverTx receives txBytes, which is the serialization of the full AppTx (app/app.go)
|
||||
// - 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("example-plugin", exampleTxBytes)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/tendermint/basecoin/cmd/commands"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
//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.QueryCmd,
|
||||
commands.KeyCmd,
|
||||
commands.VerifyCmd,
|
||||
commands.BlockCmd,
|
||||
commands.AccountCmd,
|
||||
commands.UnsafeResetAllCmd,
|
||||
)
|
||||
|
||||
cmd := cli.PrepareMainCmd(RootCmd, "BC", os.ExpandEnv("$HOME/.basecoin-example-plugin"))
|
||||
cmd.Execute()
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/basecoin/types"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
//-----------------------------------------
|
||||
// Structs
|
||||
// * Note the fields in each struct may be expanded/modified
|
||||
|
||||
// Plugin State Struct
|
||||
// * Intended to store the current state of the plugin
|
||||
// * This example contains a field which holds the execution count
|
||||
// * Serialized (by go-wire) and stored within the KVStore using the key retrieved
|
||||
// from the ExamplePlugin.StateKey() function/
|
||||
// * All fields must be exposed for serialization by external libs (here go-wire)
|
||||
type ExamplePluginState struct {
|
||||
Counter int
|
||||
}
|
||||
|
||||
// Transaction Struct
|
||||
// * Stores transaction-specific plugin-customized information
|
||||
// * This example contains a dummy field 'Valid' intended to specify
|
||||
// if the transaction is a valid and should proceed
|
||||
// * Deserialized (by go-wire) from txBytes in ExamplePlugin.RunTx
|
||||
// * All fields must be exposed for serialization by external libs (here go-wire)
|
||||
type ExamplePluginTx struct {
|
||||
Valid bool
|
||||
}
|
||||
|
||||
// Plugin Struct
|
||||
// * Struct which satisfies the basecoin Plugin interface
|
||||
// * Stores global plugin settings, in this example just the plugin name
|
||||
type ExamplePlugin struct {
|
||||
name string
|
||||
}
|
||||
|
||||
//-----------------------------------------
|
||||
// Non-Mandatory Functions
|
||||
|
||||
// Return a new ExamplePlugin pointer with a hard-coded name
|
||||
func NewExamplePlugin() *ExamplePlugin {
|
||||
return &ExamplePlugin{
|
||||
name: "example-plugin",
|
||||
}
|
||||
}
|
||||
|
||||
// Return a byte array unique to this plugin which is used as the key
|
||||
// to store the plugin state (ExamplePluginState)
|
||||
func (ep *ExamplePlugin) StateKey() []byte {
|
||||
return []byte("ExamplePlugin.State")
|
||||
}
|
||||
|
||||
//-----------------------------------------
|
||||
// Basecoin Plugin Interface Functions
|
||||
|
||||
//Return the name of the plugin
|
||||
func (ep *ExamplePlugin) Name() string {
|
||||
return ep.name
|
||||
}
|
||||
|
||||
// SetOption may be called during genesis of basecoin and can be used to set
|
||||
// initial plugin parameters. Within genesis.json file entries are made in
|
||||
// the format: "<plugin>/<key>", "<value>" Where <plugin> is the plugin name,
|
||||
// in this file ExamplePlugin.name, and <key> and <value> are the strings passed
|
||||
// into the plugin SetOption function. This function is intended to be used to
|
||||
// set plugin specific information such as the plugin state. Within this example
|
||||
// SetOption is left unimplemented.
|
||||
func (ep *ExamplePlugin) SetOption(store types.KVStore, key string, value string) (log string) {
|
||||
return ""
|
||||
}
|
||||
|
||||
// The core tx logic of the app is containted within the RunTx function
|
||||
// Input fields:
|
||||
// - store types.KVStore
|
||||
// - This term provides read/write capabilities to the merkelized data store
|
||||
// which holds the basecoin state and is accessible to all plugins
|
||||
// - ctx types.CallContext
|
||||
// - The ctx contains the callers address, a pointer to the callers account,
|
||||
// and an amount of coins sent with the transaction
|
||||
// - txBytes []byte
|
||||
// - Used to send customized information to your plugin
|
||||
//
|
||||
// Other more complex plugins may have a variant on the process order within this
|
||||
// example including loading and saving multiple or variable states, or not
|
||||
// including a state stored in the KVStore whatsoever.
|
||||
func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) {
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
// Perform Transaction Validation
|
||||
if !tx.Valid {
|
||||
return abci.ErrInternalError.AppendLog("Valid must be true")
|
||||
}
|
||||
|
||||
// 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) //decode using go-wire
|
||||
if err != nil {
|
||||
return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
//App Logic
|
||||
pluginState.Counter += 1
|
||||
|
||||
// Save PluginState
|
||||
store.Set(ep.StateKey(), wire.BinaryBytes(pluginState))
|
||||
|
||||
return abci.OK
|
||||
}
|
||||
|
||||
func (ep *ExamplePlugin) InitChain(store types.KVStore, vals []*abci.Validator) {
|
||||
}
|
||||
|
||||
func (ep *ExamplePlugin) BeginBlock(store types.KVStore, hash []byte, header *abci.Header) {
|
||||
}
|
||||
|
||||
func (ep *ExamplePlugin) EndBlock(store types.KVStore, height uint64) abci.ResponseEndBlock {
|
||||
return abci.ResponseEndBlock{}
|
||||
}
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
|
||||
# these are two globals to control all scripts (can use eg. counter instead)
|
||||
SERVER_EXE=basecoin
|
||||
CLIENT_EXE=basecli
|
||||
|
||||
oneTimeSetUp() {
|
||||
# these are passed in as args
|
||||
BASE_DIR=$HOME/.basecoin_test_basictx
|
||||
CHAIN_ID=my-test-chain
|
||||
|
||||
rm -rf $BASE_DIR 2>/dev/null
|
||||
mkdir -p $BASE_DIR
|
||||
|
||||
# set up client - make sure you use the proper prefix if you set
|
||||
# a custom CLIENT_EXE
|
||||
export BC_HOME=${BASE_DIR}/client
|
||||
prepareClient
|
||||
|
||||
# start basecoin server (with counter)
|
||||
initServer $BASE_DIR $CHAIN_ID 3456
|
||||
PID_SERVER=$!
|
||||
|
||||
initClient $CHAIN_ID 34567
|
||||
|
||||
echo "...Testing may begin!"
|
||||
echo
|
||||
echo
|
||||
echo
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
echo
|
||||
echo
|
||||
echo "stopping $SERVER_EXE test server..."
|
||||
kill -9 $PID_SERVER >/dev/null 2>&1
|
||||
sleep 1
|
||||
}
|
||||
|
||||
test00GetAccount() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=$(getAddr $POOR)
|
||||
|
||||
assertFalse "requires arg" "${CLIENT_EXE} query account"
|
||||
|
||||
checkAccount $SENDER "0" "9007199254740992"
|
||||
|
||||
ACCT2=$(${CLIENT_EXE} query account $RECV)
|
||||
assertFalse "has no genesis account" $?
|
||||
}
|
||||
|
||||
test01SendTx() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=$(getAddr $POOR)
|
||||
|
||||
assertFalse "missing dest" "${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 2>/dev/null"
|
||||
assertFalse "bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH 2>/dev/null"
|
||||
RES=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH 2>/dev/null)
|
||||
txSucceeded $? "$RES"
|
||||
TX=`echo $RES | cut -d: -f2-`
|
||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
checkAccount $SENDER "1" "9007199254740000"
|
||||
checkAccount $RECV "0" "992"
|
||||
|
||||
# make sure tx is indexed
|
||||
checkSendTx $HASH $TX_HEIGHT $SENDER "992"
|
||||
}
|
||||
|
||||
|
||||
# load and run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
|
||||
# load common helpers
|
||||
. $DIR/common.sh
|
||||
|
||||
. $DIR/shunit2
|
||||
@@ -0,0 +1,122 @@
|
||||
# this is not executable, but helper functions for the other scripts
|
||||
|
||||
# these are general accounts to be prepared
|
||||
ACCOUNTS=(jae ethan bucky rigel igor)
|
||||
RICH=${ACCOUNTS[0]}
|
||||
POOR=${ACCOUNTS[4]}
|
||||
|
||||
prepareClient() {
|
||||
echo "Preparing client keys..."
|
||||
${CLIENT_EXE} reset_all
|
||||
assertTrue $?
|
||||
|
||||
for i in "${!ACCOUNTS[@]}"; do
|
||||
newKey ${ACCOUNTS[$i]}
|
||||
done
|
||||
}
|
||||
|
||||
# initServer takes two args - (root dir, chain_id)
|
||||
# and optionally port prefix as a third arg (default is 4665{6,7,8})
|
||||
# it grabs the first account to give it the money
|
||||
initServer() {
|
||||
echo "Setting up genesis..."
|
||||
SERVE_DIR=$1/server
|
||||
assertNotNull "no chain" $2
|
||||
CHAIN=$2
|
||||
SERVER_LOG=$1/${SERVER_EXE}.log
|
||||
${SERVER_EXE} init --home=$SERVE_DIR >>$SERVER_LOG
|
||||
|
||||
#change the genesis to the first account
|
||||
GENKEY=$(${CLIENT_EXE} keys get ${RICH} -o json | jq .pubkey.data)
|
||||
GENJSON=$(cat $SERVE_DIR/genesis.json)
|
||||
echo $GENJSON | jq '.app_options.accounts[0].pub_key.data='$GENKEY \
|
||||
| jq ".chain_id=\"$2\"" > $SERVE_DIR/genesis.json
|
||||
|
||||
# optionally set the port
|
||||
if [ -n "$3" ]; then
|
||||
echo "setting port $3"
|
||||
sed -ie "s/4665/$3/" $SERVE_DIR/config.toml
|
||||
fi
|
||||
|
||||
echo "Starting ${SERVER_EXE} server..."
|
||||
${SERVER_EXE} start --home=$SERVE_DIR >>$SERVER_LOG 2>&1 &
|
||||
sleep 5
|
||||
}
|
||||
|
||||
# initClient requires chain_id arg, port is optional (default 46657)
|
||||
initClient() {
|
||||
echo "Attaching ${CLIENT_EXE} client..."
|
||||
PORT=${2:-46657}
|
||||
# hard-code the expected validator hash
|
||||
${CLIENT_EXE} init --chain-id=$1 --node=tcp://localhost:${PORT} --valhash=EB168E17E45BAEB194D4C79067FFECF345C64DE6
|
||||
assertTrue "initialized light-client" $?
|
||||
}
|
||||
|
||||
# newKeys makes a key for a given username, second arg optional password
|
||||
newKey(){
|
||||
assertNotNull "keyname required" "$1"
|
||||
KEYPASS=${2:-qwertyuiop}
|
||||
(echo $KEYPASS; echo $KEYPASS) | ${CLIENT_EXE} keys new $1 >/dev/null 2>/dev/null
|
||||
assertTrue "created $1" $?
|
||||
assertTrue "$1 doesn't exist" "${CLIENT_EXE} keys get $1"
|
||||
}
|
||||
|
||||
# getAddr gets the address for a key name
|
||||
getAddr() {
|
||||
assertNotNull "keyname required" "$1"
|
||||
RAW=$(${CLIENT_EXE} keys get $1)
|
||||
assertTrue "no key for $1" $?
|
||||
# print the addr
|
||||
echo $RAW | cut -d' ' -f2
|
||||
}
|
||||
|
||||
# checkAccount $ADDR $SEQUENCE $BALANCE
|
||||
# assumes just one coin, checks the balance of first coin in any case
|
||||
checkAccount() {
|
||||
# make sure sender goes down
|
||||
ACCT=$(${CLIENT_EXE} query account $1)
|
||||
assertTrue "must have genesis account" $?
|
||||
assertEquals "proper sequence" "$2" $(echo $ACCT | jq .data.sequence)
|
||||
assertEquals "proper money" "$3" $(echo $ACCT | jq .data.coins[0].amount)
|
||||
return $?
|
||||
}
|
||||
|
||||
# txSucceeded $? "$RES"
|
||||
# must be called right after the `tx` command, makes sure it got a success response
|
||||
txSucceeded() {
|
||||
if (assertTrue "sent tx: $2" $1); then
|
||||
TX=`echo $2 | cut -d: -f2-` # strip off first line asking for password
|
||||
assertEquals "good check: $TX" "0" $(echo $TX | jq .check_tx.code)
|
||||
assertEquals "good deliver: $TX" "0" $(echo $TX | jq .deliver_tx.code)
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# checkSendTx $HASH $HEIGHT $SENDER $AMOUNT
|
||||
# this looks up the tx by hash, and makes sure the height and type match
|
||||
# and that the first input was from this sender for this amount
|
||||
checkSendTx() {
|
||||
TX=$(${CLIENT_EXE} query tx $1)
|
||||
assertTrue "found tx" $?
|
||||
assertEquals "proper height" $2 $(echo $TX | jq .height)
|
||||
assertEquals "type=send" '"send"' $(echo $TX | jq .data.type)
|
||||
assertEquals "proper sender" "\"$3\"" $(echo $TX | jq .data.data.inputs[0].address)
|
||||
assertEquals "proper out amount" "$4" $(echo $TX | jq .data.data.outputs[0].coins[0].amount)
|
||||
return $?
|
||||
}
|
||||
|
||||
# waitForBlock $port
|
||||
# waits until the block height on that node increases by one
|
||||
waitForBlock() {
|
||||
addr=http://localhost:$1
|
||||
b1=`curl -s $addr/status | jq .result.latest_block_height`
|
||||
b2=$b1
|
||||
while [ "$b2" == "$b1" ]; do
|
||||
echo "Waiting for node $addr to commit a block ..."
|
||||
sleep 1
|
||||
b2=`curl -s $addr/status | jq .result.latest_block_height`
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
Executable
+122
@@ -0,0 +1,122 @@
|
||||
#!/bin/bash
|
||||
|
||||
# these are two globals to control all scripts (can use eg. counter instead)
|
||||
SERVER_EXE=counter
|
||||
CLIENT_EXE=countercli
|
||||
|
||||
oneTimeSetUp() {
|
||||
# these are passed in as args
|
||||
BASE_DIR=$HOME/.basecoin_test_counter
|
||||
CHAIN_ID="counter-chain"
|
||||
|
||||
rm -rf $BASE_DIR 2>/dev/null
|
||||
mkdir -p $BASE_DIR
|
||||
|
||||
# set up client - make sure you use the proper prefix if you set
|
||||
# a custom CLIENT_EXE
|
||||
export BC_HOME=${BASE_DIR}/client
|
||||
prepareClient
|
||||
|
||||
# start basecoin server (with counter)
|
||||
initServer $BASE_DIR $CHAIN_ID 1234
|
||||
PID_SERVER=$!
|
||||
|
||||
initClient $CHAIN_ID 12347
|
||||
|
||||
echo "...Testing may begin!"
|
||||
echo
|
||||
echo
|
||||
echo
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
echo
|
||||
echo
|
||||
echo "stopping $SERVER_EXE test server..."
|
||||
kill -9 $PID_SERVER >/dev/null 2>&1
|
||||
sleep 1
|
||||
}
|
||||
|
||||
|
||||
test00GetAccount() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=$(getAddr $POOR)
|
||||
|
||||
assertFalse "requires arg" "${CLIENT_EXE} query account"
|
||||
|
||||
checkAccount $SENDER "0" "9007199254740992"
|
||||
|
||||
ACCT2=$(${CLIENT_EXE} query account $RECV)
|
||||
assertFalse "has no genesis account" $?
|
||||
}
|
||||
|
||||
test01SendTx() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=$(getAddr $POOR)
|
||||
|
||||
assertFalse "missing dest" "${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 2>/dev/null"
|
||||
assertFalse "bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH 2>/dev/null"
|
||||
RES=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH 2>/dev/null)
|
||||
txSucceeded $? "$RES"
|
||||
TX=`echo $RES | cut -d: -f2-`
|
||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
checkAccount $SENDER "1" "9007199254740000"
|
||||
checkAccount $RECV "0" "992"
|
||||
|
||||
# make sure tx is indexed
|
||||
checkSendTx $HASH $TX_HEIGHT $SENDER "992"
|
||||
}
|
||||
|
||||
test02GetCounter() {
|
||||
COUNT=$(${CLIENT_EXE} query counter)
|
||||
assertFalse "no default count" $?
|
||||
}
|
||||
|
||||
# checkAccount $COUNT $BALANCE
|
||||
# assumes just one coin, checks the balance of first coin in any case
|
||||
checkCounter() {
|
||||
# make sure sender goes down
|
||||
ACCT=$(${CLIENT_EXE} query counter)
|
||||
assertTrue "count is set" $?
|
||||
assertEquals "proper count" "$1" $(echo $ACCT | jq .data.Counter)
|
||||
assertEquals "proper money" "$2" $(echo $ACCT | jq .data.TotalFees[0].amount)
|
||||
}
|
||||
|
||||
test02AddCount() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
assertFalse "bad password" "echo hi | ${CLIENT_EXE} tx counter --amount=1000mycoin --sequence=2 --name=${RICH} 2>/dev/null"
|
||||
|
||||
RES=$(echo qwertyuiop | ${CLIENT_EXE} tx counter --amount=10mycoin --sequence=2 --name=${RICH} --valid --countfee=5mycoin 2>/dev/null)
|
||||
txSucceeded $? "$RES"
|
||||
TX=`echo $RES | cut -d: -f2-`
|
||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
checkCounter "1" "5"
|
||||
|
||||
# FIXME: cannot load apptx properly.
|
||||
# Look at the stack trace
|
||||
# This cannot be fixed with the current ugly apptx structure...
|
||||
# Leave for refactoring
|
||||
|
||||
# make sure tx is indexed
|
||||
# echo hash $HASH
|
||||
# TX=$(${CLIENT_EXE} query tx $HASH --trace)
|
||||
# echo tx $TX
|
||||
# if [-z assertTrue "found tx" $?]; then
|
||||
# assertEquals "proper height" $TX_HEIGHT $(echo $TX | jq .height)
|
||||
# assertEquals "type=app" '"app"' $(echo $TX | jq .data.type)
|
||||
# assertEquals "proper sender" "\"$SENDER\"" $(echo $TX | jq .data.data.input.address)
|
||||
# fi
|
||||
# echo $TX
|
||||
}
|
||||
|
||||
# load and run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
|
||||
# load common helpers
|
||||
. $DIR/common.sh
|
||||
|
||||
. $DIR/shunit2
|
||||
Executable
+184
@@ -0,0 +1,184 @@
|
||||
#!/bin/bash
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# these are two globals to control all scripts (can use eg. counter instead)
|
||||
SERVER_EXE=basecoin
|
||||
CLIENT_EXE=basecli
|
||||
# just uncomment this line for full stack traces in error output
|
||||
# CLIENT_EXE="basecli --trace"
|
||||
|
||||
oneTimeSetUp() {
|
||||
# these are passed in as args
|
||||
BASE_DIR_1=$HOME/.basecoin_test_ibc/chain1
|
||||
CHAIN_ID_1=test-chain-1
|
||||
CLIENT_1=${BASE_DIR_1}/client
|
||||
PREFIX_1=1234
|
||||
PORT_1=${PREFIX_1}7
|
||||
|
||||
BASE_DIR_2=$HOME/.basecoin_test_ibc/chain2
|
||||
CHAIN_ID_2=test-chain-2
|
||||
CLIENT_2=${BASE_DIR_2}/client
|
||||
PREFIX_2=2345
|
||||
PORT_2=${PREFIX_2}7
|
||||
|
||||
# clean up and create the test dirs
|
||||
rm -rf $BASE_DIR_1 $BASE_DIR_2 2>/dev/null
|
||||
mkdir -p $BASE_DIR_1 $BASE_DIR_2
|
||||
|
||||
# set up client for chain 1- make sure you use the proper prefix if you set
|
||||
# a custom CLIENT_EXE
|
||||
BC_HOME=${CLIENT_1} prepareClient
|
||||
BC_HOME=${CLIENT_2} prepareClient
|
||||
|
||||
# start basecoin server, giving money to the key in the first client
|
||||
BC_HOME=${CLIENT_1} initServer $BASE_DIR_1 $CHAIN_ID_1 $PREFIX_1
|
||||
PID_SERVER_1=$!
|
||||
|
||||
# start second basecoin server, giving money to the key in the second client
|
||||
BC_HOME=${CLIENT_2} initServer $BASE_DIR_2 $CHAIN_ID_2 $PREFIX_2
|
||||
PID_SERVER_2=$!
|
||||
|
||||
# connect both clients
|
||||
BC_HOME=${CLIENT_1} initClient $CHAIN_ID_1 $PORT_1
|
||||
BC_HOME=${CLIENT_2} initClient $CHAIN_ID_2 $PORT_2
|
||||
|
||||
echo "...Testing may begin!"
|
||||
echo
|
||||
echo
|
||||
echo
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
echo
|
||||
echo
|
||||
echo "stopping both $SERVER_EXE test servers... $PID_SERVER_1 $PID_SERVER_2"
|
||||
kill -9 $PID_SERVER_1
|
||||
kill -9 $PID_SERVER_2
|
||||
sleep 1
|
||||
}
|
||||
|
||||
test00GetAccount() {
|
||||
SENDER_1=$(BC_HOME=${CLIENT_1} getAddr $RICH)
|
||||
RECV_1=$(BC_HOME=${CLIENT_1} getAddr $POOR)
|
||||
export BC_HOME=${CLIENT_1}
|
||||
|
||||
assertFalse "requires arg" "${CLIENT_EXE} query account"
|
||||
assertFalse "has no genesis account" "${CLIENT_EXE} query account $RECV_1"
|
||||
checkAccount $SENDER_1 "0" "9007199254740992"
|
||||
|
||||
export BC_HOME=${CLIENT_2}
|
||||
SENDER_2=$(getAddr $RICH)
|
||||
RECV_2=$(getAddr $POOR)
|
||||
|
||||
assertFalse "requires arg" "${CLIENT_EXE} query account"
|
||||
assertFalse "has no genesis account" "${CLIENT_EXE} query account $RECV_2"
|
||||
checkAccount $SENDER_2 "0" "9007199254740992"
|
||||
|
||||
# make sure that they have different addresses on both chains (they are random keys)
|
||||
assertNotEquals "sender keys must be different" "$SENDER_1" "$SENDER_2"
|
||||
assertNotEquals "recipient keys must be different" "$RECV_1" "$RECV_2"
|
||||
}
|
||||
|
||||
test01SendIBCTx() {
|
||||
# trigger a cross-chain sendTx... from RICH on chain1 to POOR on chain2
|
||||
# we make sure the money was reduced, but nothing arrived
|
||||
SENDER=$(BC_HOME=${CLIENT_1} getAddr $RICH)
|
||||
RECV=$(BC_HOME=${CLIENT_2} getAddr $POOR)
|
||||
|
||||
# we have to remove the password request from stdout, to just get the json
|
||||
export BC_HOME=${CLIENT_1}
|
||||
RES=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=20002mycoin --sequence=1 --to=${CHAIN_ID_2}/${RECV} --name=$RICH 2>/dev/null)
|
||||
txSucceeded $? "$RES"
|
||||
# an example to quit early if there is no point in more tests
|
||||
if [ $? != 0 ]; then echo "aborting!"; return 1; fi
|
||||
|
||||
TX=`echo $RES | cut -d: -f2-`
|
||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
# make sure balance went down and tx is indexed
|
||||
checkAccount $SENDER "1" "9007199254720990"
|
||||
checkSendTx $HASH $TX_HEIGHT $SENDER "20002"
|
||||
|
||||
# # make sure nothing arrived - yet
|
||||
waitForBlock ${PORT_1}
|
||||
assertFalse "no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV"
|
||||
|
||||
# start the relay and wait a few blocks...
|
||||
# (already sent a tx on chain1, so use higher sequence)
|
||||
startRelay 2 1
|
||||
if [ $? != 0 ]; then echo "can't start relay!"; return 1; fi
|
||||
|
||||
# give it a little time, then make sure the money arrived
|
||||
echo "waiting for relay..."
|
||||
sleep 1
|
||||
waitForBlock ${PORT_1}
|
||||
waitForBlock ${PORT_2}
|
||||
|
||||
# check the new account
|
||||
echo "checking ibc recipient..."
|
||||
BC_HOME=${CLIENT_2} checkAccount $RECV "0" "20002"
|
||||
|
||||
# stop relay
|
||||
kill -9 $PID_RELAY
|
||||
}
|
||||
|
||||
# startRelay $seq1 $seq2
|
||||
# startRelay hooks up a relay between chain1 and chain2
|
||||
# it needs the proper sequence number for $RICH on chain1 and chain2 as args
|
||||
startRelay() {
|
||||
# send some cash to the default key, so it can send messages
|
||||
RELAY_KEY=${BASE_DIR_1}/server/key.json
|
||||
RELAY_ADDR=$(cat $RELAY_KEY | jq .address | tr -d \")
|
||||
|
||||
# get paid on chain1
|
||||
export BC_HOME=${CLIENT_1}
|
||||
SENDER=$(getAddr $RICH)
|
||||
RES=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=100000mycoin --sequence=$1 --to=$RELAY_ADDR --name=$RICH 2>/dev/null)
|
||||
txSucceeded $? "$RES"
|
||||
if [ $? != 0 ]; then echo "can't pay chain1!"; return 1; fi
|
||||
|
||||
# get paid on chain2
|
||||
export BC_HOME=${CLIENT_2}
|
||||
SENDER=$(getAddr $RICH)
|
||||
RES=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=100000mycoin --sequence=$2 --to=$RELAY_ADDR --name=$RICH 2>/dev/null)
|
||||
txSucceeded $? "$RES"
|
||||
if [ $? != 0 ]; then echo "can't pay chain2!"; return 1; fi
|
||||
|
||||
# now we need to register the chains
|
||||
# TODO: do this with basecli!!!!
|
||||
basecoin tx ibc --amount 10mycoin --from=$RELAY_KEY --chain_id=$CHAIN_ID_2 \
|
||||
--node=tcp://localhost:${PORT_2} \
|
||||
register --ibc_chain_id=$CHAIN_ID_1 --genesis=$BASE_DIR_1/server/genesis.json \
|
||||
>/dev/null
|
||||
if [ $? != 0 ]; then echo "can't register chain1 on chain 2"; return 1; fi
|
||||
|
||||
basecoin tx ibc --amount 10mycoin --from=$RELAY_KEY --chain_id=$CHAIN_ID_1 \
|
||||
--node=tcp://localhost:${PORT_1} \
|
||||
register --ibc_chain_id=$CHAIN_ID_2 --genesis=$BASE_DIR_2/server/genesis.json \
|
||||
>/dev/null
|
||||
if [ $? != 0 ]; then echo "can't register chain2 on chain 1"; return 1; fi
|
||||
|
||||
# now start the relay! (this remains a server command)
|
||||
# TODO: bucky, why does this die if I don't provide home???
|
||||
# It doesn't use the --from flag????
|
||||
${SERVER_EXE} relay --chain1-id=$CHAIN_ID_1 --chain2-id=$CHAIN_ID_2 \
|
||||
--chain1-addr=tcp://localhost:${PORT_1} --chain2-addr=tcp://localhost:${PORT_2} \
|
||||
--home=${BASE_DIR_1}/server --from=$RELAY_KEY > ${BASE_DIR_1}/../relay.log &
|
||||
PID_RELAY=$!
|
||||
echo starting relay $PID_RELAY ...
|
||||
|
||||
# return an error if it dies in the first two seconds to make sure it is running
|
||||
sleep 2
|
||||
ps $PID_RELAY >/dev/null
|
||||
return $?
|
||||
}
|
||||
|
||||
# load and run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
|
||||
# load common helpers
|
||||
. $DIR/common.sh
|
||||
|
||||
. $DIR/shunit2
|
||||
Reference in New Issue
Block a user