Merge pull request #44 from tendermint/develop

v0.2.0
This commit is contained in:
Ethan Buchman 2017-03-06 05:54:17 -05:00 committed by GitHub
commit 6f83510d34
56 changed files with 1351 additions and 507 deletions

45
CHANGELOG.md Normal file
View File

@ -0,0 +1,45 @@
# Changelog
## 0.2.0 (March 6, 2017)
BREAKING CHANGES:
- Update to ABCI v0.4.0 and Tendermint v0.9.0
- Coins are specified on the CLI as `Xcoin`, eg. `5gold`
- `Cost` is now `Fee`
FEATURES:
- CLI for sending transactions and querying the state,
designed to be easily extensible as plugins are implemented
- Run Basecoin in-process with Tendermint
- Add `/account` path in Query
- IBC plugin for InterBlockchain Communication
- Demo script of IBC between two chains
IMPROVEMENTS:
- Use new Tendermint `/commit` endpoint for crafting IBC transactions
- More unit tests
- Use go-crypto S structs and go-data for more standard JSON
- Demo uses fewer sleeps
BUG FIXES:
- Various little fixes in coin arithmetic
- More commit validation in IBC
- Return results from transactions
## PreHistory
##### January 14-18, 2017
- Update to Tendermint v0.8.0
- Cleanup a bit and release blog post
##### September 22, 2016
- Basecoin compiles again

View File

@ -11,7 +11,7 @@ install:
go install github.com/tendermint/basecoin/cmd/...
test:
go test --race `${NOVENDOR}`
go test `${NOVENDOR}`
#go run tests/tendermint/*.go
get_deps:

View File

@ -13,6 +13,11 @@ and away you go with a full-stack blockchain and command line tool for transacti
WARNING: Currently uses plain-text private keys for transactions and is otherwise not production ready.
## Prerequisites
* Go to https://golang.org/doc/install to install Golang.
* You will also need to set the $GOPATH environment variable as per the instructions [here](https://golang.org/doc/code.html#GOPATH).
## Installation
On a good day, basecoin can be installed like a normal Go program:
@ -21,11 +26,9 @@ On a good day, basecoin can be installed like a normal Go program:
go get -u github.com/tendermint/basecoin/cmd/basecoin
```
In some cases, if that fails, or if another branch is required,
we use `glide` for dependency management.
The guaranteed correct way of compiling from source, assuming you've already
run `go get` or otherwise cloned the repo, is:
If that fails, or if another branch is required, you may have to compile from source.
You will first need to install the Golang package manager [`glide`](https://github.com/Masterminds/glide).
```
cd $GOPATH/src/github.com/tendermint/basecoin
@ -41,7 +44,7 @@ This will create the `basecoin` binary in `$GOPATH/bin`.
The basecoin CLI can be used to start a stand-alone basecoin instance (`basecoin start`),
or to start basecoin with Tendermint in the same process (`basecoin start --in-proc`).
It can also be used to send transactions, eg. `basecoin tx send --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 100`
It can also be used to send transactions, eg. `basecoin tx send --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 100btc,10gold`
See `basecoin --help` and `basecoin [cmd] --help` for more details`.
## Learn more

View File

@ -1,6 +1,7 @@
package app
import (
"encoding/json"
"strings"
abci "github.com/tendermint/abci/types"
@ -51,14 +52,15 @@ func (app *Basecoin) RegisterPlugin(plugin types.Plugin) {
}
// ABCI::SetOption
func (app *Basecoin) SetOption(key string, value string) (log string) {
PluginName, key := splitKey(key)
if PluginName != PluginNameBase {
func (app *Basecoin) SetOption(key string, value string) string {
pluginName, key := splitKey(key)
if pluginName != PluginNameBase {
// Set option on plugin
plugin := app.plugins.GetByName(PluginName)
plugin := app.plugins.GetByName(pluginName)
if plugin == nil {
return "Invalid plugin name: " + PluginName
return "Invalid plugin name: " + pluginName
}
log.Info("SetOption on plugin", "plugin", pluginName, "key", key, "value", value)
return plugin.SetOption(app.state, key, value)
} else {
// Set option on basecoin
@ -67,13 +69,13 @@ func (app *Basecoin) SetOption(key string, value string) (log string) {
app.state.SetChainID(value)
return "Success"
case "account":
var err error
var acc *types.Account
wire.ReadJSONPtr(&acc, []byte(value), &err)
var acc types.Account
err := json.Unmarshal([]byte(value), &acc)
if err != nil {
return "Error decoding acc message: " + err.Error()
}
app.state.SetAccount(acc.PubKey.Address(), acc)
app.state.SetAccount(acc.PubKey.Address(), &acc)
log.Info("SetAccount", "addr", acc.PubKey.Address(), "acc", acc)
return "Success"
}
return "Unrecognized option key " + key
@ -130,6 +132,12 @@ func (app *Basecoin) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQu
return
}
// handle special path for account info
if reqQuery.Path == "/account" {
reqQuery.Path = "/key"
reqQuery.Data = append([]byte("base/a/"), reqQuery.Data...)
}
resQuery, err := app.eyesCli.QuerySync(reqQuery)
if err != nil {
resQuery.Log = "Failed to query MerkleEyes: " + err.Error()
@ -188,9 +196,3 @@ func splitKey(key string) (prefix string, suffix string) {
}
return key, ""
}
// (not meant to be called)
// assert that Basecoin implements `abci.BlockchainAware` at compile-time
func _assertABCIBlockchainAware(basecoin *Basecoin) abci.BlockchainAware {
return basecoin
}

View File

@ -2,8 +2,6 @@ package app
import (
"encoding/json"
"fmt"
"reflect"
"github.com/pkg/errors"
cmn "github.com/tendermint/go-common"
@ -15,9 +13,7 @@ func (app *Basecoin) LoadGenesis(path string) error {
return err
}
for _, kv := range kvz {
log := app.SetOption(kv.Key, kv.Value)
// TODO: remove debug output
fmt.Printf("Set %v=%v. Log: %v", kv.Key, kv.Value, log)
app.SetOption(kv.Key, kv.Value)
}
return nil
}
@ -28,7 +24,7 @@ type keyValue struct {
}
func loadGenesis(filePath string) (kvz []keyValue, err error) {
kvz_ := []interface{}{}
kvz_ := []json.RawMessage{}
bytes, err := cmn.ReadFile(filePath)
if err != nil {
return nil, errors.Wrap(err, "loading genesis file")
@ -40,24 +36,21 @@ func loadGenesis(filePath string) (kvz []keyValue, err error) {
if len(kvz_)%2 != 0 {
return nil, errors.New("genesis cannot have an odd number of items. Format = [key1, value1, key2, value2, ...]")
}
for i := 0; i < len(kvz_); i += 2 {
keyIfc := kvz_[i]
valueIfc := kvz_[i+1]
var key, value string
key, ok := keyIfc.(string)
if !ok {
return nil, errors.Errorf("genesis had invalid key %v of type %v", keyIfc, reflect.TypeOf(keyIfc))
kv := keyValue{}
rawK := []byte(kvz_[i])
err := json.Unmarshal(rawK, &(kv.Key))
if err != nil {
return nil, errors.Errorf("Non-string key: %s", string(rawK))
}
if value_, ok := valueIfc.(string); ok {
value = value_
} else {
valueBytes, err := json.Marshal(valueIfc)
if err != nil {
return nil, errors.Errorf("genesis had invalid value %v: %v", value_, err.Error())
}
value = string(valueBytes)
// convert value to string if possible (otherwise raw json)
rawV := kvz_[i+1]
err = json.Unmarshal(rawV, &(kv.Value))
if err != nil {
kv.Value = string(rawV)
}
kvz = append(kvz, keyValue{key, value})
kvz = append(kvz, kv)
}
return kvz, nil
}

43
app/genesis_test.go Normal file
View File

@ -0,0 +1,43 @@
package app
import (
"encoding/hex"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/go-crypto"
eyescli "github.com/tendermint/merkleeyes/client"
)
func TestLoadGenesis(t *testing.T) {
assert, require := assert.New(t), require.New(t)
eyesCli := eyescli.NewLocalClient("", 0)
app := NewBasecoin(eyesCli)
err := app.LoadGenesis("./testdata/genesis.json")
require.Nil(err, "%+v", err)
// check the chain id
assert.Equal("foo_bar_chain", app.GetState().GetChainID())
// and check the account info - previously calculated values
addr, _ := hex.DecodeString("eb98e0688217cfdeb70eddf4b33cdcc37fc53197")
pkbyte, _ := hex.DecodeString("6880db93598e283a67c4d88fc67a8858aa2de70f713fe94a5109e29c137100c2")
acct := app.GetState().GetAccount(addr)
require.NotNil(acct)
// make sure balance is proper
assert.Equal(2, len(acct.Balance))
assert.EqualValues(12345, acct.Balance[0].Amount)
assert.EqualValues("blank", acct.Balance[0].Denom)
// and public key is parsed properly
apk := acct.PubKey.PubKey
require.NotNil(apk)
epk, ok := apk.(crypto.PubKeyEd25519)
if assert.True(ok) {
assert.EqualValues(pkbyte, epk[:])
}
}

7
app/log.go Normal file
View File

@ -0,0 +1,7 @@
package app
import (
"github.com/tendermint/go-logger"
)
var log = logger.New("module", "app")

19
app/testdata/genesis.json vendored Normal file
View File

@ -0,0 +1,19 @@
[
"base/chainID", "foo_bar_chain",
"base/account", {
"pub_key": {
"type": "ed25519",
"data": "6880db93598e283a67c4d88fc67a8858aa2de70f713fe94a5109e29c137100c2"
},
"coins": [
{
"denom": "blank",
"amount": 12345
},
{
"denom": "ETH",
"amount": 654321
}
]
}
]

View File

@ -17,10 +17,11 @@ dependencies:
- go get github.com/Masterminds/glide
- go version
- glide --version
- "cd $REPO && glide install"
- "cd $REPO && glide install && go install ./cmd/basecoin"
test:
override:
- "cd $REPO && make test"
- "cd $REPO/demo && bash start.sh"

View File

@ -4,6 +4,7 @@ import (
"os"
"github.com/tendermint/basecoin/cmd/commands"
"github.com/tendermint/basecoin/version"
"github.com/urfave/cli"
)
@ -11,14 +12,14 @@ func main() {
app := cli.NewApp()
app.Name = "basecoin"
app.Usage = "basecoin [command] [args...]"
app.Version = "0.1.0"
app.Version = version.Version
app.Commands = []cli.Command{
commands.StartCmd,
commands.TxCmd,
commands.QueryCmd,
commands.KeyCmd,
commands.VerifyCmd, // TODO: move to merkleeyes?
commands.BlockCmd, // TODO: move to adam?
commands.VerifyCmd,
commands.BlockCmd,
commands.AccountCmd,
}
app.Run(os.Args)

View File

@ -48,10 +48,10 @@ var (
Usage: "Destination address for the transaction",
}
AmountFlag = cli.IntFlag{
AmountFlag = cli.StringFlag{
Name: "amount",
Value: 0,
Usage: "Amount of coins to send in the transaction",
Value: "",
Usage: "Coins to send in transaction of the format <amt><coin>,<amt2><coin2>,... (eg: 1btc,2gold,5silver)",
}
FromFlag = cli.StringFlag{
@ -66,22 +66,16 @@ var (
Usage: "Sequence number for the account",
}
CoinFlag = cli.StringFlag{
Name: "coin",
Value: "mycoin",
Usage: "Specify a coin denomination",
}
GasFlag = cli.IntFlag{
Name: "gas",
Value: 0,
Usage: "The amount of gas for the transaction",
}
FeeFlag = cli.IntFlag{
FeeFlag = cli.StringFlag{
Name: "fee",
Value: 0,
Usage: "The transaction fee",
Value: "",
Usage: "Coins for the transaction fee of the format <amt><coin>",
}
DataFlag = cli.StringFlag{

View File

@ -9,7 +9,6 @@ import (
"github.com/urfave/cli"
"github.com/tendermint/basecoin/plugins/ibc"
"github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common"
"github.com/tendermint/go-merkle"
@ -17,10 +16,9 @@ import (
tmtypes "github.com/tendermint/tendermint/types"
)
// Register the IBC plugin at start and for transactions
func RegisterIBC() {
RegisterTxSubcommand(IbcCmd)
RegisterStartPlugin("ibc", func() types.Plugin { return ibc.New() })
// returns a new IBC plugin to be registered with Basecoin
func NewIBCPlugin() *ibc.IBCPlugin {
return ibc.New()
}
//---------------------------------------------------------------------
@ -104,9 +102,9 @@ var (
// ibc commands
var (
IbcCmd = cli.Command{
IbcTxCmd = cli.Command{
Name: "ibc",
Usage: "Send a transaction to the interblockchain (ibc) plugin",
Usage: "an IBC transaction, for InterBlockchain Communication",
Flags: TxFlags,
Subcommands: []cli.Command{
IbcRegisterTxCmd,

View File

@ -66,6 +66,11 @@ var (
}
)
// Register a subcommand of QueryCmd for plugin specific query functionality
func RegisterQuerySubcommand(cmd cli.Command) {
QueryCmd.Subcommands = append(QueryCmd.Subcommands, cmd)
}
func cmdQuery(c *cli.Context) error {
if len(c.Args()) != 1 {
return errors.New("query command requires an argument ([key])")
@ -133,11 +138,7 @@ func cmdBlock(c *cli.Context) error {
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)
header, commit, err := getHeaderAndCommit(c, height)
if err != nil {
return err
}
@ -147,12 +148,12 @@ func cmdBlock(c *cli.Context) error {
JSON BlockJSON `json:"json"`
}{
BlockHex{
Header: wire.BinaryBytes(nextBlock.Header),
Commit: wire.BinaryBytes(nextBlock.LastCommit),
Header: wire.BinaryBytes(header),
Commit: wire.BinaryBytes(commit),
},
BlockJSON{
Header: nextBlock.Header,
Commit: nextBlock.LastCommit,
Header: header,
Commit: commit,
},
})))

View File

@ -2,6 +2,7 @@ package commands
import (
"errors"
"fmt"
"os"
"path"
@ -71,7 +72,10 @@ func cmdStart(c *cli.Context) error {
// Create Basecoin app
basecoinApp := app.NewBasecoin(eyesCli)
// register all plugins
// register IBC plugn
basecoinApp.RegisterPlugin(NewIBCPlugin())
// register all other plugins
for _, p := range plugins {
basecoinApp.RegisterPlugin(p.newPlugin())
}
@ -83,12 +87,16 @@ func cmdStart(c *cli.Context) error {
if err != nil {
return errors.New(cmn.Fmt("%+v", err))
}
} else {
fmt.Printf("No genesis file at %s, skipping...\n", genesisFile)
}
if c.Bool("in-proc") {
startTendermint(c, basecoinApp)
} else {
startBasecoinABCI(c, basecoinApp)
if err := startBasecoinABCI(c, basecoinApp); err != nil {
return err
}
}
return nil

View File

@ -8,6 +8,7 @@ import (
"github.com/urfave/cli"
"github.com/tendermint/basecoin/types"
crypto "github.com/tendermint/go-crypto"
cmn "github.com/tendermint/go-common"
client "github.com/tendermint/go-rpc/client"
@ -23,7 +24,6 @@ var TxFlags = []cli.Flag{
FromFlag,
AmountFlag,
CoinFlag,
GasFlag,
FeeFlag,
SeqFlag,
@ -37,12 +37,13 @@ var (
Subcommands: []cli.Command{
SendTxCmd,
AppTxCmd,
IbcTxCmd,
},
}
SendTxCmd = cli.Command{
Name: "send",
Usage: "Create, sign, and broadcast a SendTx transaction",
Usage: "a SendTx transaction, for sending tokens around",
ArgsUsage: "",
Action: func(c *cli.Context) error {
return cmdSendTx(c)
@ -52,7 +53,7 @@ var (
AppTxCmd = cli.Command{
Name: "app",
Usage: "Create, sign, and broadcast a raw AppTx transaction",
Usage: "an AppTx transaction, for sending raw data to plugins",
ArgsUsage: "",
Action: func(c *cli.Context) error {
return cmdAppTx(c)
@ -71,9 +72,9 @@ func RegisterTxSubcommand(cmd cli.Command) {
func cmdSendTx(c *cli.Context) error {
toHex := c.String("to")
fromFile := c.String("from")
amount := int64(c.Int("amount"))
coin := c.String("coin")
gas, fee := c.Int("gas"), int64(c.Int("fee"))
amount := c.String("amount")
gas := int64(c.Int("gas"))
fee := c.String("fee")
chainID := c.String("chain_id")
// convert destination address to bytes
@ -91,25 +92,35 @@ func cmdSendTx(c *cli.Context) error {
return err
}
//parse the fee and amounts into coin types
feeCoin, err := ParseCoin(fee)
if err != nil {
return err
}
amountCoins, err := ParseCoins(amount)
if err != nil {
return err
}
// craft the tx
input := types.NewTxInput(privKey.PubKey, types.Coins{types.Coin{coin, amount}}, sequence)
output := newOutput(to, coin, amount)
input := types.NewTxInput(privKey.PubKey, amountCoins, sequence)
output := newOutput(to, amountCoins)
tx := &types.SendTx{
Gas: int64(gas),
Fee: types.Coin{coin, fee},
Gas: gas,
Fee: feeCoin,
Inputs: []types.TxInput{input},
Outputs: []types.TxOutput{output},
}
// sign that puppy
signBytes := tx.SignBytes(chainID)
tx.Inputs[0].Signature = privKey.Sign(signBytes)
tx.Inputs[0].Signature = crypto.SignatureS{privKey.Sign(signBytes)}
fmt.Println("Signed SendTx:")
fmt.Println(string(wire.JSONBytes(tx)))
// broadcast the transaction to tendermint
if _, err := broadcastTx(c, tx); err != nil {
if _, _, err := broadcastTx(c, tx); err != nil {
return err
}
return nil
@ -128,9 +139,9 @@ func cmdAppTx(c *cli.Context) error {
func AppTx(c *cli.Context, name string, data []byte) error {
fromFile := c.String("from")
amount := int64(c.Int("amount"))
coin := c.String("coin")
gas, fee := c.Int("gas"), int64(c.Int("fee"))
amount := c.String("amount")
fee := c.String("fee")
gas := int64(c.Int("gas"))
chainID := c.String("chain_id")
privKey := tmtypes.LoadPrivValidator(fromFile)
@ -140,31 +151,41 @@ func AppTx(c *cli.Context, name string, data []byte) error {
return err
}
input := types.NewTxInput(privKey.PubKey, types.Coins{types.Coin{coin, amount}}, sequence)
//parse the fee and amounts into coin types
feeCoin, err := ParseCoin(fee)
if err != nil {
return err
}
amountCoins, err := ParseCoins(amount)
if err != nil {
return err
}
input := types.NewTxInput(privKey.PubKey, amountCoins, sequence)
tx := &types.AppTx{
Gas: int64(gas),
Fee: types.Coin{coin, fee},
Gas: gas,
Fee: feeCoin,
Name: name,
Input: input,
Data: data,
}
tx.Input.Signature = privKey.Sign(tx.SignBytes(chainID))
tx.Input.Signature = crypto.SignatureS{privKey.Sign(tx.SignBytes(chainID))}
fmt.Println("Signed AppTx:")
fmt.Println(string(wire.JSONBytes(tx)))
res, err := broadcastTx(c, tx)
data, log, err := broadcastTx(c, tx)
if err != nil {
return err
}
fmt.Printf("Response: %X\n", res)
fmt.Printf("Response: %X ; %s\n", data, log)
return nil
}
// broadcast the transaction to tendermint
func broadcastTx(c *cli.Context, tx types.Tx) ([]byte, error) {
func broadcastTx(c *cli.Context, tx types.Tx) ([]byte, string, error) {
tmResult := new(ctypes.TMResult)
tmAddr := c.String("node")
clientURI := client.NewClientURI(tmAddr)
@ -176,19 +197,19 @@ func broadcastTx(c *cli.Context, tx types.Tx) ([]byte, error) {
}{tx}))
_, err := clientURI.Call("broadcast_tx_commit", map[string]interface{}{"tx": txBytes}, tmResult)
if err != nil {
return nil, errors.New(cmn.Fmt("Error on broadcast tx: %v", err))
return nil, "", errors.New(cmn.Fmt("Error on broadcast tx: %v", err))
}
res := (*tmResult).(*ctypes.ResultBroadcastTxCommit)
// if it fails check, we don't even get a delivertx back!
if !res.CheckTx.Code.IsOK() {
r := res.CheckTx
return nil, errors.New(cmn.Fmt("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log))
return nil, "", errors.New(cmn.Fmt("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log))
}
if !res.DeliverTx.Code.IsOK() {
r := res.DeliverTx
return nil, errors.New(cmn.Fmt("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log))
return nil, "", errors.New(cmn.Fmt("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log))
}
return res.DeliverTx.Data, nil
return res.DeliverTx.Data, res.DeliverTx.Log, nil
}
// if the sequence flag is set, return it;
@ -205,15 +226,10 @@ func getSeq(c *cli.Context, address []byte) (int, error) {
return acc.Sequence + 1, nil
}
func newOutput(to []byte, coin string, amount int64) types.TxOutput {
func newOutput(to []byte, amount types.Coins) types.TxOutput {
return types.TxOutput{
Address: to,
Coins: types.Coins{
types.Coin{
Denom: coin,
Amount: amount,
},
},
Coins: amount,
}
}

View File

@ -3,9 +3,13 @@ package commands
import (
"encoding/hex"
"errors"
"regexp"
"strconv"
"strings"
"github.com/urfave/cli"
"github.com/tendermint/basecoin/state"
"github.com/tendermint/basecoin/types"
abci "github.com/tendermint/abci/types"
@ -35,6 +39,44 @@ func StripHex(s string) string {
return s
}
//regex codes for extracting coins from CLI input
var reDenom = regexp.MustCompile("([^\\d\\W]+)")
var reAmt = regexp.MustCompile("(\\d+)")
func ParseCoin(str string) (types.Coin, error) {
var coin types.Coin
if len(str) > 0 {
amt, err := strconv.Atoi(reAmt.FindString(str))
if err != nil {
return coin, err
}
denom := reDenom.FindString(str)
coin = types.Coin{denom, int64(amt)}
}
return coin, nil
}
func ParseCoins(str string) (types.Coins, error) {
split := strings.Split(str, ",")
var coins []types.Coin
for _, el := range split {
if len(el) > 0 {
coin, err := ParseCoin(el)
if err != nil {
return coins, err
}
coins = append(coins, coin)
}
}
return coins, nil
}
func Query(tmAddr string, key []byte) (*abci.ResponseQuery, error) {
clientURI := client.NewClientURI(tmAddr)
tmResult := new(ctypes.TMResult)
@ -58,7 +100,7 @@ func Query(tmAddr string, key []byte) (*abci.ResponseQuery, error) {
// fetch the account by querying the app
func getAcc(tmAddr string, address []byte) (*types.Account, error) {
key := append([]byte("base/a/"), address...)
key := state.AccountKey(address)
response, err := Query(tmAddr, key)
if err != nil {
return nil, err
@ -80,15 +122,19 @@ func getAcc(tmAddr string, address []byte) (*types.Account, error) {
return acc, nil
}
func getBlock(c *cli.Context, height int) (*tmtypes.Block, error) {
func getHeaderAndCommit(c *cli.Context, height int) (*tmtypes.Header, *tmtypes.Commit, error) {
tmResult := new(ctypes.TMResult)
tmAddr := c.String("node")
clientURI := client.NewClientURI(tmAddr)
_, err := clientURI.Call("block", map[string]interface{}{"height": height}, tmResult)
method := "commit"
_, err := clientURI.Call(method, map[string]interface{}{"height": height}, tmResult)
if err != nil {
return nil, errors.New(cmn.Fmt("Error on broadcast tx: %v", err))
return nil, nil, errors.New(cmn.Fmt("Error on %s: %v", method, err))
}
res := (*tmResult).(*ctypes.ResultBlock)
return res.Block, nil
resCommit := (*tmResult).(*ctypes.ResultCommit)
header := resCommit.Header
commit := resCommit.Commit
return header, commit, nil
}

View File

@ -0,0 +1,80 @@
package commands
import (
"encoding/hex"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tendermint/basecoin/types"
)
func TestHex(t *testing.T) {
//test isHex
hexNoPrefix := hex.EncodeToString([]byte("foobar"))
hexWPrefix := "0x" + hexNoPrefix
str := "foobar"
strWPrefix := "0xfoobar"
//define the list of coin tests
var testList = []struct {
testPass bool
errMsg string
}{
{isHex(hexWPrefix), "isHex not identifying hex with 0x prefix"},
{!isHex(hexNoPrefix), "isHex shouldn't identify hex without 0x prefix"},
{!isHex(str), "isHex shouldn't identify non-hex string"},
{!isHex(strWPrefix), "isHex shouldn't identify non-hex string with 0x prefix"},
{StripHex(hexWPrefix) == hexNoPrefix, "StripHex doesn't remove first two characters"},
}
//execute the tests
for _, tl := range testList {
assert.True(t, tl.testPass, tl.errMsg)
}
}
//Test the parse coin and parse coins functionality
func TestParse(t *testing.T) {
makeCoin := func(str string) types.Coin {
coin, err := ParseCoin(str)
if err != nil {
panic(err.Error())
}
return coin
}
makeCoins := func(str string) types.Coins {
coin, err := ParseCoins(str)
if err != nil {
panic(err.Error())
}
return coin
}
//define the list of coin tests
var testList = []struct {
testPass bool
errMsg string
}{
//testing ParseCoin Function
{types.Coin{} == makeCoin(""), "parseCoin makes bad empty coin"},
{types.Coin{"fooCoin", 1} == makeCoin("1fooCoin"), "parseCoin makes bad coins"},
{types.Coin{"barCoin", 10} == makeCoin("10 barCoin"), "parseCoin makes bad coins"},
//testing ParseCoins Function
{types.Coins{{"fooCoin", 1}}.IsEqual(makeCoins("1fooCoin")),
"parseCoins doesn't parse a single coin"},
{types.Coins{{"barCoin", 99}, {"fooCoin", 1}}.IsEqual(makeCoins("99barCoin,1fooCoin")),
"parseCoins doesn't properly parse two coins"},
{types.Coins{{"barCoin", 99}, {"fooCoin", 1}}.IsEqual(makeCoins("99 barCoin, 1 fooCoin")),
"parseCoins doesn't properly parse two coins which use spaces"},
}
//execute the tests
for _, tl := range testList {
assert.True(t, tl.testPass, tl.errMsg)
}
}

View File

@ -1,12 +1,15 @@
[
"base/chainID", "test_chain_id",
"base/account", {
"pub_key": [1, "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"],
"pub_key": {
"type": "ed25519",
"data": "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"
},
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
{
"denom": "mycoin",
"amount": 9007199254740992
}
]
}
]

View File

@ -2,7 +2,7 @@
"address": "1B1BE55F969F54064628A63B9559E7C21C925165",
"priv_key": [
1,
"C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D10000000000000000000000000000000000000000000000000000000000000000"
"C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D1619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"
],
"pub_key": [
1,

View File

@ -2,7 +2,7 @@
"address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090",
"priv_key": [
1,
"34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A80000000000000000000000000000000000000000000000000000000000000000"
"34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A8352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8"
],
"pub_key": [
1,

View File

@ -1,12 +1,15 @@
[
"base/chainID", "test_chain_1",
"base/account", {
"pub_key": [1, "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"],
"pub_key": {
"type": "ed25519",
"data": "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"
},
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
{
"denom": "mycoin",
"amount": 9007199254740992
}
]
}
]

View File

@ -1,12 +1,15 @@
[
"base/chainID", "test_chain_2",
"base/account", {
"pub_key": [1, "0628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D"],
"pub_key": {
"type": "ed25519",
"data": "0628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D"
},
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
{
"denom": "mycoin",
"amount": 9007199254740992
}
]
}
]

View File

@ -1,14 +1,66 @@
#! /bin/bash
set -eu
set -e
cd $GOPATH/src/github.com/tendermint/basecoin/demo
LOG_DIR="."
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 develop
glide install
go install ./cmd/tendermint
popd
set -e
fi
set -u
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 to 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[1].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[1].latest_block_height`
done
}
# grab the chain ids
CHAIN_ID1=$(cat ./data/chain1/basecoin/genesis.json | jq .[1])
CHAIN_ID1=$(removeQuotes $CHAIN_ID1)
@ -25,29 +77,35 @@ echo ""
echo "... starting chains"
echo ""
# start the first node
TMROOT=./data/chain1/tendermint tendermint node &> chain1_tendermint.log &
basecoin start --dir ./data/chain1/basecoin &> chain1_basecoin.log &
TMROOT=./data/chain1/tendermint tendermint node --skip_upnp --log_level=info &> $LOG_DIR/chain1_tendermint.log &
basecoin start --dir ./data/chain1/basecoin &> $LOG_DIR/chain1_basecoin.log &
# start the second node
TMROOT=./data/chain2/tendermint tendermint node --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log &
basecoin start --address tcp://localhost:36658 --dir ./data/chain2/basecoin &> chain2_basecoin.log &
TMROOT=./data/chain2/tendermint tendermint node --skip_upnp --log_level=info --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> $LOG_DIR/chain2_tendermint.log &
basecoin start --address tcp://localhost:36658 --dir ./data/chain2/basecoin &> $LOG_DIR/chain2_basecoin.log &
echo ""
echo "... waiting for chains to start"
echo ""
sleep 10
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 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json
echo ""
echo "... creating egress packet on chain1"
echo ""
# create a packet on chain1 destined for chain2
PAYLOAD="DEADBEEF" #TODO
basecoin tx ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --sequence 1
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --sequence 1
echo ""
echo "... querying for packet data"
@ -66,10 +124,14 @@ 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 some blocks to be mined"
echo "... waiting for a block to be committed"
echo ""
sleep 5
waitForBlock localhost:46657
waitForBlock localhost:36657
echo ""
echo "... querying for block data"
@ -89,18 +151,18 @@ echo ""
echo "... updating state of chain1 on chain2"
echo ""
# update the state of chain1 on chain2
basecoin tx ibc --amount 10 $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT
echo ""
echo "... posting packet from chain1 on chain2"
echo ""
# post the packet from chain1 to chain2
basecoin tx ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height $((HEIGHT + 1)) --packet 0x$PACKET --proof 0x$PROOF
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height $HEIGHT --packet 0x$PACKET --proof 0x$PROOF
echo ""
echo "... checking if the packet is present on chain2"
echo ""
# query for the packet on chain2 !
# query for the packet on chain2
basecoin query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,1
echo ""

View File

@ -1,25 +1,71 @@
# Go Basics
This document is designed for developers new to the go language, especially experienced developers who are learning go for the purpose of using tendermint.
This document is designed for developers new to the go language, especially
experienced developers who are learning go for the purpose of using Tendermint.
Go is a rather simple language, which aims to produce fast, maintainable programs, while minimizing development effort. In order to speed up development, the go community has adopted quite a number of conventions, which are used in almost every open source project. The same way one rails dev can learn a new project quickly as they all have the same enforced layout, programming following these conventions allows for interoperability with much of the go tooling, and a much more fluid development experience.
Go is a rather simple language, which aims to produce fast, maintainable
programs, while minimizing development effort. In order to speed up
development, the go community has adopted quite a number of conventions, which
are used in almost every open source project. The same way one rails dev can
learn a new project quickly as they all have the same enforced layout,
programming following these conventions allows for interoperability with much
of the go tooling, and a much more fluid development experience.
First of all, you should read through [Effective Go](https://golang.org/doc/effective_go.html) to get a feel for the language and the constructs. And maybe pick up a book, read a tutorial, or do what you feel best to feel comfortable with the syntax.
First of all, you should read through [Effective
Go](https://golang.org/doc/effective_go.html) to get a feel for the language
and the constructs. And maybe pick up a book, read a tutorial, or do what you
feel best to feel comfortable with the syntax.
Second, you need to set up your go environment. In go, all code hangs out GOPATH. You don't have a separate root directory for each project. Pick a nice locations (like `$HOME/go`) and `export GOPATH` in your startup scripts (`.bashrc` or the like). Note that go compiles all programs to `$GOPATH/bin`, similarly PATH will need to be updated in the startup scripts. If your are editing `.bashrc` (typically found in HOME) you would add the following lines:
Second, you need to set up your go environment. In go, all code hangs out
GOPATH. You don't have a separate root directory for each project. Pick a nice
locations (like `$HOME/go`) and `export GOPATH` in your startup scripts
(`.bashrc` or the like). Note that go compiles all programs to `$GOPATH/bin`,
similarly PATH will need to be updated in the startup scripts. If your are
editing `.bashrc` (typically found in HOME) you would add the following lines:
```
export GOPATH=$HOME/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
```
Now, when you run `go get github.com/tendermint/basecoin`, this will create the directory `$GOPATH/src/github.com/tendermint/basecoin`, checkout the master branch with git, and try to compile if there are any scripts. All your repos will fit under GOPATH with a similar logic. Just pick good names for your github repos. If you put your code outside of GOPATH/src or have a path other than the url of the repo, you can expect errors. There are ways to do this, but quite complex and not worth the bother.
Now, when you run `go get github.com/tendermint/basecoin`, this will create the
directory `$GOPATH/src/github.com/tendermint/basecoin`, checkout the master
branch with git, and try to compile if there are any scripts. All your repos
will fit under GOPATH with a similar logic. Just pick good names for your
github repos. If you put your code outside of GOPATH/src or have a path other
than the url of the repo, you can expect errors. There are ways to do this,
but quite complex and not worth the bother.
Third, every repo in `$GOPATH/src` is checkout out of a version control system (commonly git), and you can go into those directories and manipulate them like any git repo (`git checkout develop`, `git pull`, `git remote set-url origin $MY_FORK`). `go get -u $REPO` is a nice convenience to do a `git pull` on the master branch and recompile if needed. If you work on develop, get used to using the git commands directly in these repos. [here](https://tendermint.com/docs/guides/contributing) are some more tips on using git with open source go projects with absolute dependencies such as tendermint.
Third, every repo in `$GOPATH/src` is checkout out of a version control system
(commonly git), and you can go into those directories and manipulate them like
any git repo (`git checkout develop`, `git pull`, `git remote set-url origin
$MY_FORK`). `go get -u $REPO` is a nice convenience to do a `git pull` on the
master branch and recompile if needed. If you work on develop, get used to
using the git commands directly in these repos.
[Here](https://tendermint.com/docs/guides/contributing) are some more tips on
using git with open source go projects with absolute dependencies such as
Tendermint.
Fourth, installing a go program is rather easy if you know what to do. First to note is all programs compiles with `go install` and end up in `$GOPATH/bin`. `go get` will checkout the repo, then try to `go install` it. Many repos are mainly a library that also export (one or more) commands, in these cases there is a subdir called `cmd`, with a different subdir for each command, using the command name as the directory name. To compile these commands, you can go something like `go install github.com/tendermint/basecoin/cmd/basecoin` or to compile all the commands `go install github.com/tendermint/basecoin/cmd/...` (... is a go tooling shortcut for all subdirs, like `*`).
Fourth, installing a go program is rather easy if you know what to do. First
to note is all programs compiles with `go install` and end up in `$GOPATH/bin`.
`go get` will checkout the repo, then try to `go install` it. Many repos are
mainly a library that also export (one or more) commands, in these cases there
is a subdir called `cmd`, with a different subdir for each command, using the
command name as the directory name. To compile these commands, you can go
something like `go install github.com/tendermint/basecoin/cmd/basecoin` or to
compile all the commands `go install github.com/tendermint/basecoin/cmd/...`
(... is a go tooling shortcut for all subdirs, like `*`).
Fifth, there isn't good dependency management built into go. By default, when compiling a go program which imports another repo, go will compile using the latest master branch, or whichever version you have checked out and located. This can cause serious issues, and there is tooling to do dependency management. As of go 1.6, the `vendor` directory is standard and a copy of a repo will be used rather than the repo under GOPATH. In order to create and maintain the code in the vendor directory, various tools have been created, with [glide](https://github.com/Masterminds/glide) being popular and in use in all the tendermint repos. In this case, `go install` is not enough. If you are working on code from the tendermint, you will usually want to do:
Fifth, there isn't good dependency management built into go. By default, when
compiling a go program which imports another repo, go will compile using the
latest master branch, or whichever version you have checked out and located.
This can cause serious issues, and there is tooling to do dependency
management. As of go 1.6, the `vendor` directory is standard and a copy of a
repo will be used rather than the repo under GOPATH. In order to create and
maintain the code in the vendor directory, various tools have been created,
with [glide](https://github.com/Masterminds/glide) being popular and in use in
all the Tendermint repos. In this case, `go install` is not enough. If you are
working on code from the Tendermint, you will usually want to do:
```
go get github.com/tendermint/$REPO
@ -29,6 +75,11 @@ make install
make test
```
`make get_vendor_deps` should update the vendor directory using glide, `make install` will compile all commands. `make test` is good to run the test suite and make sure things are working with your environment... failing tests are much easier to debug than a malfunctioning program.
`make get_vendor_deps` should update the vendor directory using glide, `make
install` will compile all commands. `make test` is good to run the test suite
and make sure things are working with your environment... failing tests are
much easier to debug than a malfunctioning program.
Okay, that's it, with this info you should be able to follow along and
trouble-shoot any issues you have with the rest of the guide.
Okay, that's it, with this info you should be able to follow along and trouble-shoot any issues you have with the rest of the guide.

View File

@ -1,13 +1,16 @@
# Basecoin Basics
Here we explain how to get started with a simple Basecoin blockchain, and how to send transactions between accounts using the `basecoin` tool.
Here we explain how to get started with a simple Basecoin blockchain,
and how to send transactions between accounts using the `basecoin` tool.
## Install
Make sure you have [basecoin installed](install.md).
You will also need to [install tendermint](https://tendermint.com/intro/getting-started/download).
You will also need to [install Tendermint](https://tendermint.com/intro/getting-started/download).
**Note** All code is on the 0.9 pre-release branch, you may have to [install tendermint from source](https://tendermint.com/docs/guides/install) until 0.9 is released. (Make sure to add `git checkout develop` to the linked install instructions)
**Note** All code is on the 0.9 pre-release branch, you may have to
[install Tendermint from source](https://tendermint.com/docs/guides/install)
until 0.9 is released. (Make sure to add `git checkout develop` to the linked install instructions)
## Initialization
@ -18,7 +21,7 @@ tendermint init
```
This will create the necessary files for a single Tendermint node in `~/.tendermint`.
If you had previously run tendermint, make sure you reset the chain
If you had previously run Tendermint, make sure you reset the chain
(note this will delete all chain data, so back it up if you need it):
```
@ -49,7 +52,8 @@ Now we can start basecoin:
basecoin start --in-proc
```
This will initialize the chain with the `genesis.json` file from the current directory. If you want to specify another location, you can run:
This will initialize the chain with the `genesis.json` file from the current directory.
If you want to specify another location, you can run:
```
basecoin start --in-proc --dir PATH/TO/CUSTOM/DATA
@ -71,7 +75,7 @@ tendermint node
In either case, you should see blocks start streaming in!
Note, however, that currently basecoin currently requires the
`develop` branch of tendermint for this to work.
`develop` branch of Tendermint for this to work.
## Send transactions
@ -91,14 +95,14 @@ The first account is flush with cash, while the second account doesn't exist.
Let's send funds from the first account to the second:
```
basecoin tx send --to 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 --amount 10
basecoin tx send --to 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 --amount 10mycoin
```
By default, the CLI looks for a `priv_validator.json` to sign the transaction with,
so this will only work if you are in the `$GOPATH/src/github.com/tendermint/basecoin/data`.
To specify a different key, we can use the `--from` flag.
Now if we check the second account, it should have `10` coins!
Now if we check the second account, it should have `10` 'mycoin' coins!
```
basecoin account 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090
@ -107,7 +111,7 @@ basecoin account 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090
We can send some of these coins back like so:
```
basecoin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --from key2.json --amount 5
basecoin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --from key2.json --amount 5mycoin
```
Note how we use the `--from` flag to select a different account to send from.
@ -115,7 +119,7 @@ Note how we use the `--from` flag to select a different account to send from.
If we try to send too much, we'll get an error:
```
basecoin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --from key2.json --amount 100
basecoin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --from key2.json --amount 100mycoin
```
See `basecoin tx send --help` for additional details.

View File

@ -14,7 +14,7 @@ This type of account was directly inspired by accounts in Ethereum,
and is unlike Bitcoin's use of Unspent Transaction Outputs (UTXOs).
Note Basecoin is a multi-asset cryptocurrency, so each account can have many different kinds of tokens.
```
```golang
type Account struct {
PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known.
Sequence int `json:"sequence"`
@ -42,7 +42,7 @@ The `SendTx` takes a list of inputs and a list of outputs,
and transfers all the tokens listed in the inputs from their corresponding accounts to the accounts listed in the output.
The `SendTx` is structured as follows:
```
```golang
type SendTx struct {
Gas int64 `json:"gas"`
Fee Coin `json:"fee"`
@ -71,15 +71,21 @@ This is slightly different from Ethereum's concept of `Gas` and `GasPrice`,
where `Fee = Gas x GasPrice`. In Basecoin, the `Gas` and `Fee` are independent,
and the `GasPrice` is implicit.
In tendermint, the `Fee` is meant to be used by the validators to inform the ordering of transactions, like in bitcoin. And the `Gas` is meant to be used by the application plugin to control its execution. There is currently no means to pass `Fee` information to the tendermint validators, but it will come soon...
In Tendermint, the `Fee` is meant to be used by the validators to inform the ordering
of transactions, like in bitcoin. And the `Gas` is meant to be used by the application
plugin to control its execution. There is currently no means to pass `Fee` information
to the Tendermint validators, but it will come soon...
Second, notice that the `PubKey` only needs to be sent for `Sequence == 0`.
After that, it is stored under the account in the Merkle tree and subsequent transactions can exclude it,
using only the `Address` to refer to the sender. Ethereum does not require public keys to be sent in transactions
as it uses a different elliptic curve scheme which enables the public key to be derrived from the signature itself.
as it uses a different elliptic curve scheme which enables the public key to be derived from the signature itself.
Finally, note that the use of multiple inputs and multiple outputs allows us to send many different types of tokens between many different accounts
at once in an atomic transaction. Thus, the `SendTx` can serve as a basic unit of decentralized exchange. When using multiple inputs and outputs, you must make sure that the sum of coins of the inputs equals the sum of coins of the outputs (no creating money), and that all accounts that provide inputs have signed the transaction.
Finally, note that the use of multiple inputs and multiple outputs allows us to send many
different types of tokens between many different accounts at once in an atomic transaction.
Thus, the `SendTx` can serve as a basic unit of decentralized exchange. When using multiple
inputs and outputs, you must make sure that the sum of coins of the inputs equals the sum of
coins of the outputs (no creating money), and that all accounts that provide inputs have signed the transaction.
## Plugins

View File

@ -1,7 +1,9 @@
## Deployment
Up until this point, we have only been testing the code as a stand-alone abci app, which is nice for developing, but it is no blockchain. Just a blockchain-ready application.
Up until this point, we have only been testing the code as a stand-alone abci app,
which is nice for developing, but it is no blockchain. Just a blockchain-ready application.
This section will demonstrate how to launch your basecoin-based application along with a tendermint testnet and initialize the genesis block for fun and profit.
This section will demonstrate how to launch your basecoin-based application along
with a tendermint testnet and initialize the genesis block for fun and profit.
**TODO** Maybe we link to a blog post for this???

View File

@ -21,7 +21,7 @@ plugin.go
The `main.go` is very simple and does not need to be changed:
```
```golang
func main() {
app := cli.NewApp()
app.Name = "example-plugin"
@ -50,7 +50,7 @@ This is where the `cmd.go` comes in.
First, we register the plugin:
```
```golang
func init() {
commands.RegisterTxSubcommand(ExamplePluginTxCmd)
commands.RegisterStartPlugin("example-plugin", func() types.Plugin { return NewExamplePlugin() })
@ -61,7 +61,7 @@ This creates a new subcommand under `tx` (defined below),
and ensures the plugin is activated when we start the app.
Now we actually define the new command:
```
```golang
var (
ExampleFlag = cli.BoolFlag{
Name: "valid",
@ -88,13 +88,13 @@ func cmdExamplePluginTx(c *cli.Context) error {
It's a simple command with one flag, which is just a boolean.
However, it actually inherits more flags from the Basecoin framework:
```
```golang
Flags: append(commands.TxFlags, ExampleFlag),
```
The `commands.TxFlags` is defined in `cmd/commands/tx.go`:
```
```golang
var TxFlags = []cli.Flag{
NodeFlag,
ChainIDFlag,
@ -132,10 +132,9 @@ OPTIONS:
--node value Tendermint RPC address (default: "tcp://localhost:46657")
--chain_id value ID of the chain for replay protection (default: "test_chain_id")
--from value Path to a private key to sign the transaction (default: "key.json")
--amount value Amount of coins to send in the transaction (default: 0)
--coin value Specify a coin denomination (default: "mycoin")
--amount value Coins to send in transaction of the format <amt><coin>,<amt2><coin2>,... (eg: 1btc,2gold,5silver)
--gas value The amount of gas for the transaction (default: 0)
--fee value The transaction fee (default: 0)
--fee value Coins for the transaction fee of the format <amt><coin>
--sequence value Sequence number for the account (default: 0)
--valid Set this to make the transaction valid
```
@ -144,7 +143,7 @@ Cool, eh?
Before we move on to `plugin.go`, let's look at the `cmdExamplePluginTx` function in `cmd.go`:
```
```golang
func cmdExamplePluginTx(c *cli.Context) error {
exampleFlag := c.Bool("valid")
exampleTx := ExamplePluginTx{exampleFlag}
@ -166,7 +165,7 @@ but are necessary boilerplate.
Your plugin may have additional requirements that utilize these other methods.
Here's what's relevant for us:
```
```golang
type ExamplePluginState struct {
Counter int
}
@ -236,7 +235,7 @@ and then using the `RunTx` method to define how the transaction updates the stat
Let's break down `RunTx` in parts. First, we deserialize the transaction:
```
```golang
// Decode tx
var tx ExamplePluginTx
err := wire.ReadBinaryBytes(txBytes, &tx)
@ -250,9 +249,9 @@ as defined in the `github.com/tendermint/go-wire` package.
If it's not encoded properly, we return an error.
If the transaction deserializes currectly, we can now check if it's valid:
If the transaction deserializes correctly, we can now check if it's valid:
```
```golang
// Validate tx
if !tx.Valid {
return abci.ErrInternalError.AppendLog("Valid must be true")
@ -264,7 +263,7 @@ Finally, we can update the state. In this example, the state simply counts how m
we've processed. But the state itself is serialized and kept in some `store`, which is typically a Merkle tree.
So first we have to load the state from the store and deserialize it:
```
```golang
// Load PluginState
var pluginState ExamplePluginState
stateBytes := store.Get(ep.StateKey())
@ -276,11 +275,14 @@ if len(stateBytes) > 0 {
}
```
Note the state is stored under `ep.StateKey()`, which is defined above as `ExamplePlugin.State`. Also note, that we do nothing if there is no existing state data. Is that a bug? No, we just make use of Go's variable initialization, that `pluginState` will contain a `Counter` value of 0. If your app needs more initialization than empty variables, then do this logic here in an `else` block.
Note the state is stored under `ep.StateKey()`, which is defined above as `ExamplePlugin.State`.
Also note, that we do nothing if there is no existing state data. Is that a bug? No, we just make
use of Go's variable initialization, that `pluginState` will contain a `Counter` value of 0.
If your app needs more initialization than empty variables, then do this logic here in an `else` block.
Finally, we can update the state's `Counter`, and save the state back to the store:
```
```golang
//App Logic
pluginState.Counter += 1
@ -313,7 +315,7 @@ example-plugin key new > key.json
Here's what my `key.json looks like:
```
```json
{
"address": "15F591CA434CFCCBDEC1D206F3ED3EBA207BFE7D",
"priv_key": [
@ -329,7 +331,7 @@ Here's what my `key.json looks like:
Now we can make a `genesis.json` file and add an account with out public key:
```
```json
[
"base/chainID", "example-chain",
"base/account", {
@ -346,7 +348,7 @@ Now we can make a `genesis.json` file and add an account with out public key:
Here we've granted ourselves `1000000000` units of the `gold` token.
Before we can start the blockchain, we must initialize and/or reset the tendermint state for a new blockchain:
Before we can start the blockchain, we must initialize and/or reset the Tendermint state for a new blockchain:
```
tendermint init
@ -363,25 +365,25 @@ example-plugin start --in-proc
In another window, we can try sending some transactions:
```
example-plugin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --amount 100 --coin gold --chain_id example-chain
example-plugin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --amount 100gold --chain_id example-chain
```
Note the `--coin` and `--chain_id` flags. In the [previous tutorial](basecoin-basics.md),
we didn't need them because we were using the default coin type ("mycoin") and chain ID ("test_chain_id").
Now that we're using custom values, we need to specify them explicitly on the command line.
Note the `--chain_id` flag. In the [previous tutorial](basecoin-basics.md),
we didn't include it because we were using the default chain ID ("test_chain_id").
Now that we're using a custom chain, we need to specify the chain explicitly on the command line.
Ok, so that's how we can send a `SendTx` transaction using our `example-plugin` CLI,
but we were already able to do that with the `basecoin` CLI.
With our new CLI, however, we can also send an `ExamplePluginTx`:
```
example-plugin tx example --amount 1 --coin gold --chain_id example-chain
example-plugin tx example --amount 1gold --chain_id example-chain
```
The transaction is invalid! That's because we didn't specify the `--valid` flag:
```
example-plugin tx example --valid --amount 1 --coin gold --chain_id example-chain
example-plugin tx example --valid --amount 1gold --chain_id example-chain
```
Tada! We successfuly created, signed, broadcast, and processed our custom transaction type.
@ -401,7 +403,7 @@ which contains only an integer.
If we send another transaction, and then query again, we'll see the value increment:
```
example-plugin tx example --valid --amount 1 --coin gold --chain_id example-chain
example-plugin tx example --valid --amount 1gold --chain_id example-chain
example-plugin query ExamplePlugin.State
```
@ -410,7 +412,7 @@ This is a Merkle proof that the state is what we say it is.
In a latter [tutorial on Interblockchain Communication](ibc.md),
we'll put this proof to work!
## Next Stpes
## Next Steps
In this tutorial we demonstrated how to create a new plugin and how to extend the
basecoin CLI to activate the plugin on the blockchain and to send transactions to it.

View File

@ -24,16 +24,16 @@ The purpose of IBC is to enable one blockchain to function as a light-client of
Since we are using a classical Byzantine Fault Tolerant consensus algorithm,
light-client verification is cheap and easy:
all we have to do is check validator signatures on the latest block,
and verify a merkle proof of the state.
and verify a Merkle proof of the state.
In Tendermint, validators agree on a block before processing it. This means
that the signatures and state root for that block aren't included until the
next block. Thus, each block contains a field called `LastCommit`, which
contains the votes responsible for committing the previous block, and a field
in the block header called `AppHash`, which refers to the merkle root hash of
in the block header called `AppHash`, which refers to the Merkle root hash of
the application after processing the transactions from the previous block. So,
if we want to verify the `AppHash` from height H, we need the signatures from `LastCommit` at height H+1. (And remember that this `AppHash` only contains the results from all transactions up to and including block H-1)
if we want to verify the `AppHash` from height H, we need the signatures from `LastCommit`
at height H+1. (And remember that this `AppHash` only contains the results from all transactions up to and including block H-1)
Unlike Proof-of-Work, the light-client protocol does not need to download and
check all the headers in the blockchain - the client can always jump straight
@ -46,15 +46,12 @@ postpone handling validator set changes for another time.
Now we can describe exactly how IBC works.
Suppose we have two blockchains, `chain1` and `chain2`, and we want to send some data from `chain1` to `chain2`.
We need to do the following:
```
1. Register the details (ie. chain ID and genesis configuration) of `chain1` on `chain2`
2. Within `chain1`, broadcast a transaction that creates an outgoing IBC packet destined for `chain2`
3. Broadcast a transaction to `chain2` informing it of the latest state (ie. header and commit signatures) of `chain1`
4. Post the outgoing packet from `chain1` to `chain2`, including the proof that
1. Register the details (ie. chain ID and genesis configuration) of `chain1` on `chain2`
2. Within `chain1`, broadcast a transaction that creates an outgoing IBC packet destined for `chain2`
3. Broadcast a transaction to `chain2` informing it of the latest state (ie. header and commit signatures) of `chain1`
4. Post the outgoing packet from `chain1` to `chain2`, including the proof that
it was indeed committed on `chain1`. Note `chain2` can only verify this proof
because it has a recent header and commit.
```
Each of these steps involves a separate IBC transaction type. Let's take them up in turn.
@ -63,7 +60,7 @@ Each of these steps involves a separate IBC transaction type. Let's take them up
The `IBCRegisterChainTx` is used to register one chain on another.
It contains the chain ID and genesis configuration of the chain to register:
```
```golang
type IBCRegisterChainTx struct {
BlockchainGenesis
}
@ -82,7 +79,7 @@ This transaction should only be sent once for a given chain ID, and successive s
The `IBCUpdateChainTx` is used to update the state of one chain on another.
It contains the header and commit signatures for some block in the chain:
```
```golang
type IBCUpdateChainTx struct {
Header tm.Header
Commit tm.Commit
@ -96,11 +93,11 @@ Anyone can relay an `IBCUpdateChainTx`, and they only need to do so as frequentl
The `IBCPacketCreateTx` is used to create an outgoing packet on one chain.
The packet itself contains the source and destination chain IDs,
a sequence number (ie. an integer that increments with every message sent between this pair of chains),
a packet type (eg. coin, data, etc.),
a sequence number (i.e. an integer that increments with every message sent between this pair of chains),
a packet type (e.g. coin, data, etc.),
and a payload.
```
```golang
type IBCPacketCreateTx struct {
Packet
}
@ -129,7 +126,7 @@ Those funds can only be unlocked with corresponding IBC messages back from
The `IBCPacketPostTx` is used to post an outgoing packet from one chain to another.
It contains the packet and a proof that the packet was committed into the state of the sending chain:
```
```golang
type IBCPacketPostTx struct {
FromChainID string // The immediate source of the packet, not always Packet.SrcChainID
FromChainHeight uint64 // The block height in which Packet was committed, to check Proof
@ -138,7 +135,7 @@ type IBCPacketPostTx struct {
}
```
The proof is a merkle proof in an IAVL tree, our implementation of a balanced, Merklized binary search tree.
The proof is a Merkle proof in an IAVL tree, our implementation of a balanced, Merklized binary search tree.
It contains a list of nodes in the tree, which can be hashed together to get the Merkle root hash.
This hash must match the `AppHash` contained in the header at `FromChainHeight + 1`
- note the `+ 1` is necessary since `FromChainHeight` is the height in which the packet was committed,
@ -147,14 +144,12 @@ and the resulting state root is not included until the next block.
### IBC State
Now that we've seen all the transaction types, let's talk about the state.
Each chain stores some IBC state in its merkle tree.
Each chain stores some IBC state in its Merkle tree.
For each chain being tracked by our chain, we store:
```
- Genesis configuration
- Latest state
- Headers for recent heights
```
We also store all incoming (ingress) and outgoing (egress) packets.
@ -180,7 +175,7 @@ The results of a query can thus be used as proof in an `IBCPacketPostTx`.
Now that we have all the background knowledge, let's actually walk through the tutorial.
Make sure you have installed
[tendermint](https://tendermint.com/intro/getting-started/download) and
[Tendermint](https://tendermint.com/intro/getting-started/download) and
[basecoin](/docs/guide/install.md).
`basecoin` is a framework for creating new cryptocurrency applications.
@ -236,13 +231,13 @@ export CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/key.jso
Let's start by registering `test_chain_1` on `test_chain_2`:
```
basecoin tx ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json
```
Now we can create the outgoing packet on `test_chain_1`:
```
basecoin tx ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --sequence 1
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --sequence 1
```
Note our payload is just `DEADBEEF`.
@ -252,8 +247,8 @@ Now that the packet is committed in the chain, let's get some proof by querying:
basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1
```
The result contains the latest height, a value (ie. the hex-encoded binary serialization of our packet),
and a proof (ie. hex-encoded binary serialization of a list of nodes from the Merkle tree) that the value is in the Merkle tree.
The result contains the latest height, a value (i.e. the hex-encoded binary serialization of our packet),
and a proof (i.e. hex-encoded binary serialization of a list of nodes from the Merkle tree) that the value is in the Merkle tree.
If we want to send this data to `test_chain_2`, we first have to update what it knows about `test_chain_1`.
We'll need a recent block header and a set of commit signatures.
@ -270,7 +265,7 @@ The former is used as input for later commands; the latter is human-readable, so
Let's send this updated information about `test_chain_1` to `test_chain_2`:
```
basecoin tx ibc --amount 10 $CHAIN_FLAGS2 update --header 0x<header>--commit 0x<commit>
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 update --header 0x<header>--commit 0x<commit>
```
where `<header>` and `<commit>` are the hex-encoded header and commit returned by the previous `block` command.
@ -280,7 +275,7 @@ along with proof the packet was committed on `test_chain_1`. Since `test_chain_2
of `test_chain_1`, it will be able to verify the proof!
```
basecoin tx ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height <height + 1> --packet 0x<packet> --proof 0x<proof>
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height <height + 1> --packet 0x<packet> --proof 0x<proof>
```
Here, `<height + 1>` is one greater than the height retuned by the previous `query` command, and `<packet>` and `<proof>` are the
@ -288,13 +283,13 @@ Here, `<height + 1>` is one greater than the height retuned by the previous `que
Tada!
## Conclusion
In this tutorial we explained how IBC works, and demonstrated how to use it to communicate between two chains.
We did the simplest communciation possible: a one way transfer of data from chain1 to chain2.
The most important part was that we updated chain2 with the latest state (ie. header and commit) of chain1,
The most important part was that we updated chain2 with the latest state (i.e. header and commit) of chain1,
and then were able to post a proof to chain2 that a packet was committed to the outgoing state of chain1.
In a future tutorial, we will demonstrate how to use IBC to actually transfer tokens between two blockchains,
but we'll do it with real testnets deployed across multiple nodes on the network. Stay tuned!

View File

@ -9,7 +9,7 @@ Here we describe how that functionality can be achieved through a plugin system.
In addition to the `SendTx`, Basecoin also defines another transaction type, the `AppTx`:
```
```golang
type AppTx struct {
Gas int64 `json:"gas"`
Fee Coin `json:"fee"`
@ -20,7 +20,7 @@ type AppTx struct {
```
The `AppTx` enables Basecoin to be extended with arbitrary additional functionality through the use of plugins.
The `Name` field in the `AppTx` refers to the particular plugin which should process the transasaction,
The `Name` field in the `AppTx` refers to the particular plugin which should process the transaction,
and the `Data` field of the `AppTx` is the data to be forwarded to the plugin for processing.
Note the `AppTx` also has a `Gas` and `Fee`, with the same meaning as for the `SendTx`.
@ -31,7 +31,7 @@ and some coins that can be forwarded to the plugin as well.
A plugin is simply a Go package that implements the `Plugin` interface:
```
```golang
type Plugin interface {
// Name of this plugin, should be short.
@ -61,9 +61,17 @@ while the `Input` from the `AppTx` is used to populate the `CallContext`.
Note that `RunTx` also takes a `KVStore` - this is an abstraction for the underlying Merkle tree which stores the account data.
By passing this to the plugin, we enable plugins to update accounts in the Basecoin state directly,
and also to store arbitrary other information in the state.
In this way, the functionality and state of a Basecoin-derrived cryptocurrency can be greatly extended.
In this way, the functionality and state of a Basecoin-derived cryptocurrency can be greatly extended.
One could imagine going so far as to implement the Ethereum Virtual Machine as a plugin!
Any required plugin initialization should be constructed within `SetOption`.
`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,
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.
## Examples
To get started with plugins, see [the example-plugin tutorial](example-plugin.md).

View File

@ -8,17 +8,24 @@ import (
"github.com/tendermint/basecoin/types"
)
//Called during CLI initialization
func init() {
//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() })
}
var (
//CLI Flags
ExampleFlag = cli.BoolFlag{
Name: "valid",
Usage: "Set this to make the transaction valid",
}
//CLI Plugin Commands
ExamplePluginTxCmd = cli.Command{
Name: "example",
Usage: "Create, sign, and broadcast a transaction to the example plugin",
@ -29,8 +36,31 @@ var (
}
)
//Send a transaction
func cmdExamplePluginTx(c *cli.Context) error {
//Retrieve any flag results
exampleFlag := c.Bool("valid")
// Create a transaction using the flag.
// The tx passes on custom information to the plugin
exampleTx := ExamplePluginTx{exampleFlag}
return commands.AppTx(c, "example-plugin", wire.BinaryBytes(exampleTx))
// 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(c, "example-plugin", exampleTxBytes)
}

View File

@ -8,6 +8,7 @@ import (
)
func main() {
//Initialize an instance of basecoin with default basecoin commands
app := cli.NewApp()
app.Name = "example-plugin"
app.Usage = "example-plugin [command] [args...]"

View File

@ -6,46 +6,98 @@ import (
"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
}
func (ep *ExamplePlugin) Name() string {
return ep.name
}
func (ep *ExamplePlugin) StateKey() []byte {
return []byte("ExamplePlugin.State")
}
//-----------------------------------------
// 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 tx
// Decode txBytes using go-wire. Attempt to write the txBytes to the variable
// tx, if the txBytes have not been properly encoded from a ExamplePluginTx
// struct wire will produce an error.
var tx ExamplePluginTx
err := wire.ReadBinaryBytes(txBytes, &tx)
if err != nil {
return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error())
}
// Validate tx
// Perform Transaction Validation
if !tx.Valid {
return abci.ErrInternalError.AppendLog("Valid must be true")
}
@ -53,8 +105,10 @@ func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txByt
// Load PluginState
var pluginState ExamplePluginState
stateBytes := store.Get(ep.StateKey())
// If the state does not exist, stateBytes will be initialized
// as an empty byte array with length of zero
if len(stateBytes) > 0 {
err = wire.ReadBinaryBytes(stateBytes, &pluginState)
err = wire.ReadBinaryBytes(stateBytes, &pluginState) //decode using go-wire
if err != nil {
return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error())
}

61
glide.lock generated
View File

@ -1,20 +1,18 @@
hash: 3869944d14a8df914ffcad02c2ef3548173daba51c5ea697767f8af77c07b348
updated: 2017-01-29T22:09:11.408245895-08:00
updated: 2017-03-06T05:37:03.828355251-05:00
imports:
- name: github.com/btcsuite/btcd
version: afec1bd1245a4a19e6dfe1306974b733e7cbb9b8
version: d06c0bb181529331be8f8d9350288c420d9e60e4
subpackages:
- btcec
- name: github.com/btcsuite/fastsha256
version: 637e656429416087660c84436a2a035d69d54e2e
- name: github.com/BurntSushi/toml
version: 99064174e013895bbd9b025c31100bd1d9b590ca
- name: github.com/ebuchman/fail-test
version: c1eddaa09da2b4017351245b0d43234955276798
version: 13f91f14c826314205cdbed1ec8ac8bf08e03381
- name: github.com/go-stack/stack
version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82
- name: github.com/golang/protobuf
version: 1f49d83d9aa00e6ce4fc8258c71cc7786aec968a
version: 8ee79997227bf9b34611aee7946ae64735e6fd93
subpackages:
- proto
- name: github.com/golang/snappy
@ -24,13 +22,13 @@ imports:
- name: github.com/jmhodges/levigo
version: c42d9e0ca023e2198120196f842701bb4c55d7b9
- name: github.com/mattn/go-colorable
version: ed8eb9e318d7a84ce5915b495b7d35e0cfe7b5a8
version: d228849504861217f796da67fae4f6e347643f15
- name: github.com/mattn/go-isatty
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
version: 30a891c33c7cde7b02a981314b4228ec99380cca
- name: github.com/pkg/errors
version: 248dadf4e9068a0b3e79f02ed0a610d935de5302
version: 645ef00459ed84a119197bfb8d8205042c6df63d
- name: github.com/syndtr/goleveldb
version: 6ae1797c0b42b9323fc27ff7dcf568df88f2f33d
version: 23851d93a2292dcc56e71a18ec9e0624d84a0f65
subpackages:
- leveldb
- leveldb/cache
@ -45,11 +43,10 @@ imports:
- leveldb/table
- leveldb/util
- name: github.com/tendermint/abci
version: 8df0bc3a40ccad0d2be10e33c62c404e65c92502
version: 1236e8fb6eee3a63909f4014a8e84385ead7933d
subpackages:
- client
- example/dummy
- example/nil
- server
- types
- name: github.com/tendermint/ed25519
@ -58,19 +55,21 @@ imports:
- edwards25519
- extra25519
- name: github.com/tendermint/go-autofile
version: 0416e0aa9c68205aa44844096f9f151ada9d0405
version: 48b17de82914e1ec2f134ce823ba426337d2c518
- name: github.com/tendermint/go-clist
version: 3baa390bbaf7634251c42ad69a8682e7e3990552
- name: github.com/tendermint/go-common
version: 339e135776142939d82bc8e699db0bf391fd938d
version: dcb015dff6c7af21e65c8e2f3b450df19d38c777
- name: github.com/tendermint/go-config
version: e64b424499acd0eb9856b88e10c0dff41628c0d6
version: 620dcbbd7d587cf3599dedbf329b64311b0c307a
- name: github.com/tendermint/go-crypto
version: 4b11d62bdb324027ea01554e5767b71174680ba0
version: 3f47cfac5fcd9e0f1727c7db980b3559913b3e3a
- name: github.com/tendermint/go-data
version: 32271140e8fd5abdbb22e268d7a02421fa382f0b
- name: github.com/tendermint/go-db
version: 2645626c33d8702739e52a61a55d705c2dfe4530
version: eac3f2bc147023957c8bf69432a4e6c4dc5c3f72
- name: github.com/tendermint/go-events
version: 2337086736a6adeb2de6f66739b66ecd77535997
version: f8ffbfb2be3483e9e7927495590a727f51c0c11f
- name: github.com/tendermint/go-flowrate
version: a20c98e61957faa93b4014fbd902f20ab9317a6a
subpackages:
@ -78,30 +77,30 @@ imports:
- name: github.com/tendermint/go-logger
version: cefb3a45c0bf3c493a04e9bcd9b1540528be59f2
- name: github.com/tendermint/go-merkle
version: 653cb1f631528351ddbc359b994eb0c96f0341cd
version: 714d4d04557fd068a7c2a1748241ce8428015a96
- name: github.com/tendermint/go-p2p
version: 67c9086b7458eb45b1970483decd01cd744c477a
version: 97a5ed2d1a17eaee8717b8a32cfaf7a9a82a273d
subpackages:
- upnp
- name: github.com/tendermint/go-rpc
version: 6177eb8398ebd4613fbecb71fd96d7c7d97303ec
version: fcea0cda21f64889be00a0f4b6d13266b1a76ee7
subpackages:
- client
- server
- types
- name: github.com/tendermint/go-wire
version: 3216ec9d47bbdf8d4fc27d22169ea86a6688bc15
version: f530b7af7a8b06e612c2063bff6ace49060a085e
- name: github.com/tendermint/log15
version: 9545b249b3aacafa97f79e0838b02b274adc6f5f
version: ae0f3d6450da9eac7074b439c8e1c3cabf0d5ce6
subpackages:
- term
- name: github.com/tendermint/merkleeyes
version: 7c1ec0ef86c42b7a461e3967efb6c35bd5652101
version: 9fb76efa5aebe773a598f97e68e75fe53d520e70
subpackages:
- app
- client
- name: github.com/tendermint/tendermint
version: 67ab574e9889c0641ae959296d391e3cadec55e3
version: d4f625455109d88e7f55a999fdb25e208f174802
subpackages:
- blockchain
- config/tendermint
@ -116,9 +115,9 @@ imports:
- types
- version
- name: github.com/urfave/cli
version: 8ef3805c9de2519805c3f060524b695bba2cd715
version: 0bdeddeeb0f650497d603c4ad7b20cfe685682f6
- name: golang.org/x/crypto
version: aa2481cbfe81d911eb62b642b7a6b5ec58bbea71
version: 7c6cc321c680f03b9ef0764448e780704f486b51
subpackages:
- curve25519
- nacl/box
@ -129,20 +128,21 @@ imports:
- ripemd160
- salsa20/salsa
- name: golang.org/x/net
version: cfe3c2a7525b50c3d707256e371c90938cfef98a
version: 61557ac0112b576429a0df080e1c2cef5dfbb642
subpackages:
- context
- http2
- http2/hpack
- idna
- internal/timeseries
- lex/httplex
- trace
- name: golang.org/x/sys
version: 30de6d19a3bd89a5f38ae4028e23aaa5582648af
version: d75a52659825e75fff6158388dddc6a5b04f9ba5
subpackages:
- unix
- name: google.golang.org/grpc
version: 50955793b0183f9de69bd78e2ec251cf20aab121
version: cbcceb2942a489498cf22b2f918536e819d33f0a
subpackages:
- codes
- credentials
@ -167,3 +167,4 @@ testImports:
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
subpackages:
- assert
- require

View File

@ -35,7 +35,7 @@ func New() *CounterPlugin {
return &CounterPlugin{}
}
func (cp *CounterPlugin) SetOption(store types.KVStore, key string, value string) (log string) {
func (cp *CounterPlugin) SetOption(store types.KVStore, key, value string) (log string) {
return ""
}

View File

@ -1,13 +1,15 @@
package counter
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/app"
"github.com/tendermint/basecoin/testutils"
"github.com/tendermint/basecoin/types"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire"
eyescli "github.com/tendermint/merkleeyes/client"
)
@ -19,19 +21,21 @@ func TestCounterPlugin(t *testing.T) {
chainID := "test_chain_id"
bcApp := app.NewBasecoin(eyesCli)
bcApp.SetOption("base/chainID", chainID)
t.Log(bcApp.Info())
// t.Log(bcApp.Info())
// Add Counter plugin
counterPlugin := New()
bcApp.RegisterPlugin(counterPlugin)
// Account initialization
test1PrivAcc := testutils.PrivAccountFromSecret("test1")
test1PrivAcc := types.PrivAccountFromSecret("test1")
// Seed Basecoin with account
test1Acc := test1PrivAcc.Account
test1Acc.Balance = types.Coins{{"", 1000}, {"gold", 1000}}
bcApp.SetOption("base/account", string(wire.JSONBytes(test1Acc)))
accOpt, err := json.Marshal(test1Acc)
require.Nil(t, err)
bcApp.SetOption("base/account", string(accOpt))
// Deliver a CounterTx
DeliverCounterTx := func(gas int64, fee types.Coin, inputCoins types.Coins, inputSequence int, appFee types.Coins) abci.Result {
@ -39,17 +43,17 @@ func TestCounterPlugin(t *testing.T) {
tx := &types.AppTx{
Gas: gas,
Fee: fee,
Name: "counter",
Name: counterPlugin.Name(),
Input: types.NewTxInput(test1Acc.PubKey, inputCoins, inputSequence),
Data: wire.BinaryBytes(CounterTx{Valid: true, Fee: appFee}),
}
// Sign request
signBytes := tx.SignBytes(chainID)
t.Logf("Sign bytes: %X\n", signBytes)
sig := test1PrivAcc.PrivKey.Sign(signBytes)
tx.Input.Signature = sig
t.Logf("Signed TX bytes: %X\n", wire.BinaryBytes(struct{ types.Tx }{tx}))
// t.Logf("Sign bytes: %X\n", signBytes)
sig := test1PrivAcc.Sign(signBytes)
tx.Input.Signature = crypto.SignatureS{sig}
// t.Logf("Signed TX bytes: %X\n", wire.BinaryBytes(struct{ types.Tx }{tx}))
// Write request
txBytes := wire.BinaryBytes(struct{ types.Tx }{tx})

View File

@ -1,6 +1,7 @@
package ibc
import (
"bytes"
"errors"
"net/url"
"strings"
@ -329,6 +330,7 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) {
save(sm.store, packetKeyIngress, packet)
// Load Header and make sure it exists
// If it exists, we already checked a valid commit for it in UpdateChainTx
var header tm.Header
exists, err := load(sm.store, headerKey, &header)
if err != nil {
@ -341,16 +343,6 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) {
return
}
/*
// Read Proof
var proof *merkle.IAVLProof
err = wire.ReadBinaryBytes(tx.Proof, &proof)
if err != nil {
sm.res.Code = IBCEncodingError
sm.res.Log = cmn.Fmt("Reading Proof: %v", err.Error())
return
}
*/
proof := tx.Proof
if proof == nil {
sm.res.Code = IBCCodeInvalidProof
@ -368,7 +360,6 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) {
}
return
}
func (ibc *IBCPlugin) InitChain(store types.KVStore, vals []*abci.Validator) {
@ -432,6 +423,7 @@ func verifyCommit(chainState BlockchainState, header *tm.Header, commit *tm.Comm
if chainState.ChainID != header.ChainID {
return errors.New(cmn.Fmt("Expected header.ChainID %v, got %v", chainState.ChainID, header.ChainID))
}
// Ensure things aren't empty
if len(chainState.Validators) == 0 {
return errors.New(cmn.Fmt("Blockchain has no validators")) // NOTE: Why would this happen?
}
@ -439,18 +431,23 @@ func verifyCommit(chainState BlockchainState, header *tm.Header, commit *tm.Comm
return errors.New(cmn.Fmt("Commit has no signatures"))
}
chainID := chainState.ChainID
vote0 := commit.Precommits[0]
vals := chainState.Validators
valSet := tm.NewValidatorSet(vals)
blockID := commit.Precommits[0].BlockID // XXX: incorrect
// NOTE: Currently this only works with the exact same validator set.
// Not this, but perhaps "ValidatorSet.VerifyCommitAny" should expose
// the functionality to verify commits even after validator changes.
err := valSet.VerifyCommit(chainID, vote0.BlockID, vote0.Height, commit)
err := valSet.VerifyCommit(chainID, blockID, header.Height, commit)
if err != nil {
return err
}
// Ensure the committed blockID matches the header
if !bytes.Equal(header.Hash(), blockID.Hash) {
return errors.New(cmn.Fmt("blockID.Hash (%X) does not match header.Hash (%X)", blockID.Hash, header.Hash()))
}
// All ok!
return nil
}

View File

@ -8,7 +8,6 @@ import (
"github.com/stretchr/testify/assert"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/testutils"
"github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common"
crypto "github.com/tendermint/go-crypto"
@ -29,9 +28,9 @@ func genGenesisDoc(chainID string, numVals int) (*tm.GenesisDoc, []types.PrivAcc
for i := 0; i < numVals; i++ {
name := cmn.Fmt("%v_val_%v", chainID, i)
privAcc := testutils.PrivAccountFromSecret(name)
privAcc := types.PrivAccountFromSecret(name)
genDoc.Validators = append(genDoc.Validators, tm.GenesisValidator{
PubKey: privAcc.Account.PubKey,
PubKey: privAcc.PubKey.PubKey,
Amount: 1,
Name: name,
})

View File

@ -3,7 +3,7 @@ package state
import (
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/types"
. "github.com/tendermint/go-common"
cmn "github.com/tendermint/go-common"
"github.com/tendermint/go-events"
)
@ -44,8 +44,13 @@ func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc e
return res.PrependLog("in validateInputsAdvanced()")
}
outTotal := sumOutputs(tx.Outputs)
if !inTotal.IsEqual(outTotal.Plus(types.Coins{tx.Fee})) {
return abci.ErrBaseInvalidOutput.AppendLog("Input total != output total + fees")
outPlusFees := outTotal
fees := types.Coins{tx.Fee}
if fees.IsValid() { // TODO: fix coins.Plus()
outPlusFees = outTotal.Plus(fees)
}
if !inTotal.IsEqual(outPlusFees) {
return abci.ErrBaseInvalidOutput.AppendLog(cmn.Fmt("Input total (%v) != output total + fees (%v)", inTotal, outPlusFees))
}
// TODO: Fee validation for SendTx
@ -68,7 +73,7 @@ func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc e
}
*/
return abci.OK
return abci.NewResultOK(types.TxID(chainID, tx), "")
case *types.AppTx:
// Validate input, basic
@ -82,7 +87,7 @@ func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc e
if inAcc == nil {
return abci.ErrBaseUnknownAddress
}
if tx.Input.PubKey != nil {
if !tx.Input.PubKey.Empty() {
inAcc.PubKey = tx.Input.PubKey
}
@ -90,19 +95,19 @@ func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc e
signBytes := tx.SignBytes(chainID)
res = validateInputAdvanced(inAcc, signBytes, tx.Input)
if res.IsErr() {
log.Info(Fmt("validateInputAdvanced failed on %X: %v", tx.Input.Address, res))
log.Info(cmn.Fmt("validateInputAdvanced failed on %X: %v", tx.Input.Address, res))
return res.PrependLog("in validateInputAdvanced()")
}
if !tx.Input.Coins.IsGTE(types.Coins{tx.Fee}) {
log.Info(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
return abci.ErrBaseInsufficientFunds
log.Info(cmn.Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
return abci.ErrBaseInsufficientFunds.AppendLog(cmn.Fmt("input coins is %v, but fee is %v", tx.Input.Coins, types.Coins{tx.Fee}))
}
// Validate call address
plugin := pgz.GetByName(tx.Name)
if plugin == nil {
return abci.ErrBaseUnknownAddress.AppendLog(
Fmt("Unrecognized plugin name%v", tx.Name))
cmn.Fmt("Unrecognized plugin name%v", tx.Name))
}
// Good!
@ -171,7 +176,7 @@ func getInputs(state types.AccountGetter, ins []types.TxInput) (map[string]*type
return nil, abci.ErrBaseUnknownAddress
}
if in.PubKey != nil {
if !in.PubKey.Empty() {
acc.PubKey = in.PubKey
}
accounts[string(in.Address)] = acc
@ -192,10 +197,8 @@ func getOrMakeOutputs(state types.AccountGetter, accounts map[string]*types.Acco
acc := state.GetAccount(out.Address)
// output account may be nil (new)
if acc == nil {
acc = &types.Account{
PubKey: nil,
Sequence: 0,
}
// zero value is valid, empty account
acc = &types.Account{}
}
accounts[string(out.Address)] = acc
}
@ -218,7 +221,7 @@ func validateInputsAdvanced(accounts map[string]*types.Account, signBytes []byte
for _, in := range ins {
acc := accounts[string(in.Address)]
if acc == nil {
PanicSanity("validateInputsAdvanced() expects account in accounts")
cmn.PanicSanity("validateInputsAdvanced() expects account in accounts")
}
res = validateInputAdvanced(acc, signBytes, in)
if res.IsErr() {
@ -234,15 +237,15 @@ func validateInputAdvanced(acc *types.Account, signBytes []byte, in types.TxInpu
// Check sequence/coins
seq, balance := acc.Sequence, acc.Balance
if seq+1 != in.Sequence {
return abci.ErrBaseInvalidSequence.AppendLog(Fmt("Got %v, expected %v. (acc.seq=%v)", in.Sequence, seq+1, acc.Sequence))
return abci.ErrBaseInvalidSequence.AppendLog(cmn.Fmt("Got %v, expected %v. (acc.seq=%v)", in.Sequence, seq+1, acc.Sequence))
}
// Check amount
if !balance.IsGTE(in.Coins) {
return abci.ErrBaseInsufficientFunds
return abci.ErrBaseInsufficientFunds.AppendLog(cmn.Fmt("balance is %v, tried to send %v", balance, in.Coins))
}
// Check signatures
if !acc.PubKey.VerifyBytes(signBytes, in.Signature) {
return abci.ErrBaseInvalidSignature.AppendLog(Fmt("SignBytes: %X", signBytes))
if !acc.PubKey.VerifyBytes(signBytes, in.Signature.Signature) {
return abci.ErrBaseInvalidSignature.AppendLog(cmn.Fmt("SignBytes: %X", signBytes))
}
return abci.OK
}
@ -268,10 +271,10 @@ func adjustByInputs(state types.AccountSetter, accounts map[string]*types.Accoun
for _, in := range ins {
acc := accounts[string(in.Address)]
if acc == nil {
PanicSanity("adjustByInputs() expects account in accounts")
cmn.PanicSanity("adjustByInputs() expects account in accounts")
}
if !acc.Balance.IsGTE(in.Coins) {
PanicSanity("adjustByInputs() expects sufficient funds")
cmn.PanicSanity("adjustByInputs() expects sufficient funds")
}
acc.Balance = acc.Balance.Minus(in.Coins)
acc.Sequence += 1
@ -283,7 +286,7 @@ func adjustByOutputs(state types.AccountSetter, accounts map[string]*types.Accou
for _, out := range outs {
acc := accounts[string(out.Address)]
if acc == nil {
PanicSanity("adjustByOutputs() expects account in accounts")
cmn.PanicSanity("adjustByOutputs() expects account in accounts")
}
acc.Balance = acc.Balance.Plus(out.Coins)
if !isCheckTx {

View File

@ -38,7 +38,7 @@ func (s *State) GetChainID() string {
}
func (s *State) Get(key []byte) (value []byte) {
if s.readCache != nil {
if s.readCache != nil { //if not a cachewrap
value, ok := s.readCache[string(key)]
if ok {
return value
@ -48,7 +48,7 @@ func (s *State) Get(key []byte) (value []byte) {
}
func (s *State) Set(key []byte, value []byte) {
if s.readCache != nil {
if s.readCache != nil { //if not a cachewrap
s.readCache[string(key)] = value
}
s.store.Set(key, value)
@ -78,8 +78,14 @@ func (s *State) CacheSync() {
}
func (s *State) Commit() abci.Result {
s.readCache = make(map[string][]byte)
return s.store.(*eyes.Client).CommitSync()
switch s.store.(type) {
case *eyes.Client:
s.readCache = make(map[string][]byte)
return s.store.(*eyes.Client).CommitSync()
default:
return abci.NewError(abci.CodeType_InternalError, "can only use Commit if store is merkleeyes")
}
}
//----------------------------------------

106
state/state_test.go Normal file
View File

@ -0,0 +1,106 @@
package state
import (
"bytes"
"testing"
"github.com/tendermint/basecoin/types"
eyes "github.com/tendermint/merkleeyes/client"
"github.com/stretchr/testify/assert"
)
func TestState(t *testing.T) {
//States and Stores for tests
store := types.NewMemKVStore()
state := NewState(store)
cache := state.CacheWrap()
eyesCli := eyes.NewLocalClient("", 0)
//Account and address for tests
dumAddr := []byte("dummyAddress")
acc := new(types.Account)
acc.Sequence = 1
//reset the store/state/cache
reset := func() {
store = types.NewMemKVStore()
state = NewState(store)
cache = state.CacheWrap()
}
//set the state to using the eyesCli instead of MemKVStore
useEyesCli := func() {
state = NewState(eyesCli)
cache = state.CacheWrap()
}
//key value pairs to be tested within the system
keyvalue := []struct {
key string
value string
}{
{"foo", "snake"},
{"bar", "mouse"},
}
//set the kvc to have all the key value pairs
setRecords := func(kv types.KVStore) {
for _, n := range keyvalue {
kv.Set([]byte(n.key), []byte(n.value))
}
}
//store has all the key value pairs
storeHasAll := func(kv types.KVStore) bool {
for _, n := range keyvalue {
if !bytes.Equal(kv.Get([]byte(n.key)), []byte(n.value)) {
return false
}
}
return true
}
//define the test list
testList := []struct {
testPass func() bool
errMsg string
}{
//test chainID
{func() bool { state.SetChainID("testchain"); return state.GetChainID() == "testchain" },
"ChainID is improperly stored"},
//test basic retrieve
{func() bool { setRecords(state); return storeHasAll(state) },
"state doesn't retrieve after Set"},
// Test account retrieve
{func() bool { state.SetAccount(dumAddr, acc); return state.GetAccount(dumAddr).Sequence == 1 },
"GetAccount not retrieving"},
//Test CacheWrap with local mem store
{func() bool { reset(); setRecords(cache); return !storeHasAll(store) },
"store retrieving before CacheSync"},
{func() bool { cache.CacheSync(); return storeHasAll(store) },
"store doesn't retrieve after CacheSync"},
//Test Commit on state with non-merkle store
{func() bool { return !state.Commit().IsOK() },
"Commit shouldn't work with non-merkle store"},
//Test CacheWrap with merkleeyes client store
{func() bool { useEyesCli(); setRecords(cache); return !storeHasAll(eyesCli) },
"eyesCli retrieving before Commit"},
{func() bool { cache.CacheSync(); return state.Commit().IsOK() },
"Bad Commit"},
{func() bool { return storeHasAll(eyesCli) },
"eyesCli doesn't retrieve after Commit"},
}
//execute the tests
for _, tl := range testList {
assert.True(t, tl.testPass(), tl.errMsg)
}
}

View File

@ -5,9 +5,9 @@ import (
"time"
"github.com/gorilla/websocket"
"github.com/tendermint/basecoin/testutils"
"github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-rpc/client"
"github.com/tendermint/go-rpc/types"
"github.com/tendermint/go-wire"
@ -37,10 +37,10 @@ func main() {
}()
// Get the root account
root := testutils.PrivAccountFromSecret("test")
root := types.PrivAccountFromSecret("test")
sequence := int(0)
// Make a bunch of PrivAccounts
privAccounts := testutils.RandAccounts(1000, 1000000, 0)
privAccounts := types.RandAccounts(1000, 1000000, 0)
privAccountSequences := make(map[string]int)
// Send coins to each account
@ -66,8 +66,8 @@ func main() {
// Sign request
signBytes := tx.SignBytes(chainID)
sig := root.PrivKey.Sign(signBytes)
tx.Inputs[0].Signature = sig
sig := root.Sign(signBytes)
tx.Inputs[0].Signature = crypto.SignatureS{sig}
//fmt.Println("tx:", tx)
// Write request
@ -116,8 +116,8 @@ func main() {
// Sign request
signBytes := tx.SignBytes(chainID)
sig := privAccountA.PrivKey.Sign(signBytes)
tx.Inputs[0].Signature = sig
sig := privAccountA.Sign(signBytes)
tx.Inputs[0].Signature = crypto.SignatureS{sig}
//fmt.Println("tx:", tx)
// Write request

View File

@ -1,12 +1,15 @@
package tmsp_test
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/basecoin/app"
"github.com/tendermint/basecoin/testutils"
"github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire"
eyescli "github.com/tendermint/merkleeyes/client"
)
@ -16,15 +19,17 @@ func TestSendTx(t *testing.T) {
chainID := "test_chain_id"
bcApp := app.NewBasecoin(eyesCli)
bcApp.SetOption("base/chainID", chainID)
t.Log(bcApp.Info())
// t.Log(bcApp.Info())
test1PrivAcc := testutils.PrivAccountFromSecret("test1")
test2PrivAcc := testutils.PrivAccountFromSecret("test2")
test1PrivAcc := types.PrivAccountFromSecret("test1")
test2PrivAcc := types.PrivAccountFromSecret("test2")
// Seed Basecoin with account
test1Acc := test1PrivAcc.Account
test1Acc.Balance = types.Coins{{"", 1000}}
t.Log(bcApp.SetOption("base/account", string(wire.JSONBytes(test1Acc))))
accOpt, err := json.Marshal(test1Acc)
require.Nil(t, err)
bcApp.SetOption("base/account", string(accOpt))
// Construct a SendTx signature
tx := &types.SendTx{
@ -43,18 +48,16 @@ func TestSendTx(t *testing.T) {
// Sign request
signBytes := tx.SignBytes(chainID)
t.Log("Sign bytes: %X\n", signBytes)
sig := test1PrivAcc.PrivKey.Sign(signBytes)
tx.Inputs[0].Signature = sig
t.Log("Signed TX bytes: %X\n", wire.BinaryBytes(struct{ types.Tx }{tx}))
// t.Log("Sign bytes: %X\n", signBytes)
sig := test1PrivAcc.Sign(signBytes)
tx.Inputs[0].Signature = crypto.SignatureS{sig}
// t.Log("Signed TX bytes: %X\n", wire.BinaryBytes(types.TxS{tx}))
// Write request
txBytes := wire.BinaryBytes(struct{ types.Tx }{tx})
txBytes := wire.BinaryBytes(types.TxS{tx})
res := bcApp.DeliverTx(txBytes)
t.Log(res)
if res.IsErr() {
t.Errorf("Failed: %v", res.Error())
}
// t.Log(res)
assert.False(t, res.IsErr(), "Failed: %v", res.Error())
}
func TestSequence(t *testing.T) {
@ -62,17 +65,19 @@ func TestSequence(t *testing.T) {
chainID := "test_chain_id"
bcApp := app.NewBasecoin(eyesCli)
bcApp.SetOption("base/chainID", chainID)
t.Log(bcApp.Info())
// t.Log(bcApp.Info())
// Get the test account
test1PrivAcc := testutils.PrivAccountFromSecret("test1")
test1PrivAcc := types.PrivAccountFromSecret("test1")
test1Acc := test1PrivAcc.Account
test1Acc.Balance = types.Coins{{"", 1 << 53}}
t.Log(bcApp.SetOption("base/account", string(wire.JSONBytes(test1Acc))))
accOpt, err := json.Marshal(test1Acc)
require.Nil(t, err)
bcApp.SetOption("base/account", string(accOpt))
sequence := int(1)
// Make a bunch of PrivAccounts
privAccounts := testutils.RandAccounts(1000, 1000000, 0)
privAccounts := types.RandAccounts(1000, 1000000, 0)
privAccountSequences := make(map[string]int)
// Send coins to each account
@ -96,23 +101,18 @@ func TestSequence(t *testing.T) {
// Sign request
signBytes := tx.SignBytes(chainID)
sig := test1PrivAcc.PrivKey.Sign(signBytes)
tx.Inputs[0].Signature = sig
sig := test1PrivAcc.Sign(signBytes)
tx.Inputs[0].Signature = crypto.SignatureS{sig}
// t.Log("ADDR: %X -> %X\n", tx.Inputs[0].Address, tx.Outputs[0].Address)
// Write request
txBytes := wire.BinaryBytes(struct{ types.Tx }{tx})
res := bcApp.DeliverTx(txBytes)
if res.IsErr() {
t.Errorf("DeliverTx error: " + res.Error())
}
assert.False(t, res.IsErr(), "DeliverTx error: %v", res.Error())
}
res := bcApp.Commit()
if res.IsErr() {
t.Errorf("Failed Commit: %v", res.Error())
}
assert.False(t, res.IsErr(), "Failed Commit: %v", res.Error())
t.Log("-------------------- RANDOM SENDS --------------------")
@ -133,11 +133,11 @@ func TestSequence(t *testing.T) {
Gas: 2,
Fee: types.Coin{"", 2},
Inputs: []types.TxInput{
types.NewTxInput(privAccountA.Account.PubKey, types.Coins{{"", 3}}, privAccountASequence+1),
types.NewTxInput(privAccountA.PubKey, types.Coins{{"", 3}}, privAccountASequence+1),
},
Outputs: []types.TxOutput{
types.TxOutput{
Address: privAccountB.Account.PubKey.Address(),
Address: privAccountB.PubKey.Address(),
Coins: types.Coins{{"", 1}},
},
},
@ -145,15 +145,13 @@ func TestSequence(t *testing.T) {
// Sign request
signBytes := tx.SignBytes(chainID)
sig := privAccountA.PrivKey.Sign(signBytes)
tx.Inputs[0].Signature = sig
sig := privAccountA.Sign(signBytes)
tx.Inputs[0].Signature = crypto.SignatureS{sig}
// t.Log("ADDR: %X -> %X\n", tx.Inputs[0].Address, tx.Outputs[0].Address)
// Write request
txBytes := wire.BinaryBytes(struct{ types.Tx }{tx})
res := bcApp.DeliverTx(txBytes)
if res.IsErr() {
t.Errorf("DeliverTx error: " + res.Error())
}
assert.False(t, res.IsErr(), "DeliverTx error: %v", res.Error())
}
}

View File

@ -1,47 +0,0 @@
// Functions used in testing throughout
package testutils
import (
"github.com/tendermint/basecoin/types"
. "github.com/tendermint/go-common"
"github.com/tendermint/go-crypto"
)
// Creates a PrivAccount from secret.
// The amount is not set.
func PrivAccountFromSecret(secret string) types.PrivAccount {
privKey := crypto.GenPrivKeyEd25519FromSecret([]byte(secret))
privAccount := types.PrivAccount{
PrivKey: privKey,
Account: types.Account{
PubKey: privKey.PubKey(),
Sequence: 0,
},
}
return privAccount
}
// Make `num` random accounts
func RandAccounts(num int, minAmount int64, maxAmount int64) []types.PrivAccount {
privAccs := make([]types.PrivAccount, num)
for i := 0; i < num; i++ {
balance := minAmount
if maxAmount > minAmount {
balance += RandInt64() % (maxAmount - minAmount)
}
privKey := crypto.GenPrivKeyEd25519()
pubKey := privKey.PubKey()
privAccs[i] = types.PrivAccount{
PrivKey: privKey,
Account: types.Account{
PubKey: pubKey,
Sequence: 0,
Balance: types.Coins{types.Coin{"", balance}},
},
}
}
return privAccs
}

View File

@ -7,12 +7,15 @@ import (
)
type Account struct {
PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known.
Sequence int `json:"sequence"`
Balance Coins `json:"coins"`
PubKey crypto.PubKeyS `json:"pub_key"` // May be nil, if not known.
Sequence int `json:"sequence"`
Balance Coins `json:"coins"`
}
func (acc *Account) Copy() *Account {
if acc == nil {
return nil
}
accCopy := *acc
return &accCopy
}
@ -28,7 +31,7 @@ func (acc *Account) String() string {
//----------------------------------------
type PrivAccount struct {
crypto.PrivKey
crypto.PrivKeyS
Account
}

22
types/account_test.go Normal file
View File

@ -0,0 +1,22 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNilAccount(t *testing.T) {
var acc Account
//test Copy
accCopy := acc.Copy()
assert.True(t, &acc != accCopy, "Account Copy Error")
assert.True(t, acc.Sequence == accCopy.Sequence)
//test sending nils for panic
var nilAcc *Account
nilAcc.String()
nilAcc.Copy()
}

View File

@ -42,6 +42,8 @@ func (coins Coins) IsValid() bool {
}
}
// TODO: handle empty coins!
// Currently appends an empty coin ...
func (coinsA Coins) Plus(coinsB Coins) Coins {
sum := []Coin{}
indexA, indexB := 0, 0
@ -100,7 +102,7 @@ func (coinsA Coins) IsGTE(coinsB Coins) bool {
if len(diff) == 0 {
return true
}
return diff.IsPositive()
return diff.IsNonnegative()
}
func (coins Coins) IsZero() bool {

View File

@ -2,79 +2,64 @@ package types
import (
"testing"
cmn "github.com/tendermint/go-common"
"github.com/stretchr/testify/assert"
)
func TestCoins(t *testing.T) {
coins := Coins{
//Define the coins to be used in tests
good := Coins{
Coin{"GAS", 1},
Coin{"MINERAL", 1},
Coin{"TREE", 1},
}
if !coins.IsValid() {
t.Fatal("Coins are valid")
neg := good.Negative()
sum := good.Plus(neg)
empty := Coins{
Coin{"GOLD", 0},
}
if !coins.IsPositive() {
t.Fatalf("Expected coins to be positive: %v", coins)
}
negCoins := coins.Negative()
if negCoins.IsPositive() {
t.Fatalf("Expected neg coins to not be positive: %v", negCoins)
}
sumCoins := coins.Plus(negCoins)
if len(sumCoins) != 0 {
t.Fatal("Expected 0 coins")
}
}
func TestCoinsBadSort(t *testing.T) {
coins := Coins{
badSort1 := Coins{
Coin{"TREE", 1},
Coin{"GAS", 1},
Coin{"MINERAL", 1},
}
if coins.IsValid() {
t.Fatal("Coins are not sorted")
}
}
func TestCoinsBadSort2(t *testing.T) {
// both are after the first one, but the second and third are in the wrong order
coins := Coins{
badSort2 := Coins{ // both are after the first one, but the second and third are in the wrong order
Coin{"GAS", 1},
Coin{"TREE", 1},
Coin{"MINERAL", 1},
}
if coins.IsValid() {
t.Fatal("Coins are not sorted")
}
}
func TestCoinsBadAmount(t *testing.T) {
coins := Coins{
badAmt := Coins{
Coin{"GAS", 1},
Coin{"TREE", 0},
Coin{"MINERAL", 1},
}
if coins.IsValid() {
t.Fatal("Coins cannot include 0 amounts")
}
}
func TestCoinsDuplicate(t *testing.T) {
coins := Coins{
dup := Coins{
Coin{"GAS", 1},
Coin{"GAS", 1},
Coin{"MINERAL", 1},
}
if coins.IsValid() {
t.Fatal("Duplicate coin")
//define the list of coin tests
var testList = []struct {
testPass bool
errMsg string
}{
{good.IsValid(), "Coins are valid"},
{good.IsPositive(), cmn.Fmt("Expected coins to be positive: %v", good)},
{good.IsGTE(empty), cmn.Fmt("Expected %v to be >= %v", good, empty)},
{!neg.IsPositive(), cmn.Fmt("Expected neg coins to not be positive: %v", neg)},
{len(sum) == 0, "Expected 0 coins"},
{!badSort1.IsValid(), "Coins are not sorted"},
{!badSort2.IsValid(), "Coins are not sorted"},
{!badAmt.IsValid(), "Coins cannot include 0 amounts"},
{!dup.IsValid(), "Duplicate coin"},
}
//execute the tests
for _, tl := range testList {
assert.True(t, tl.testPass, tl.errMsg)
}
}

View File

@ -113,6 +113,7 @@ func (kvc *KVCache) Get(key []byte) (value []byte) {
}
}
//Update the store with the values from the cache
func (kvc *KVCache) Sync() {
for e := kvc.keys.Front(); e != nil; e = e.Next() {
key := e.Value.([]byte)

79
types/kvstore_test.go Normal file
View File

@ -0,0 +1,79 @@
package types
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestKVStore(t *testing.T) {
//stores to be tested
ms := NewMemKVStore()
store := NewMemKVStore()
kvc := NewKVCache(store)
//key value pairs to be tested within the system
var keyvalue = []struct {
key string
value string
}{
{"foo", "snake"},
{"bar", "mouse"},
}
//set the kvc to have all the key value pairs
setRecords := func(kv KVStore) {
for _, n := range keyvalue {
kv.Set([]byte(n.key), []byte(n.value))
}
}
//store has all the key value pairs
storeHasAll := func(kv KVStore) bool {
for _, n := range keyvalue {
if !bytes.Equal(kv.Get([]byte(n.key)), []byte(n.value)) {
return false
}
}
return true
}
//define the test list
var testList = []struct {
testPass func() bool
errMsg string
}{
//test read/write for MemKVStore
{func() bool { setRecords(ms); return storeHasAll(ms) },
"MemKVStore doesn't retrieve after Set"},
//test read/write for KVCache
{func() bool { setRecords(kvc); return storeHasAll(kvc) },
"KVCache doesn't retrieve after Set"},
//test reset
{func() bool { kvc.Reset(); return !storeHasAll(kvc) },
"KVCache retrieving after reset"},
//test sync
{func() bool { setRecords(kvc); return !storeHasAll(store) },
"store retrieving before synced"},
{func() bool { kvc.Sync(); return storeHasAll(store) },
"store isn't retrieving after synced"},
//test logging
{func() bool { return len(kvc.GetLogLines()) == 0 },
"logging events existed before using SetLogging"},
{func() bool { kvc.SetLogging(); setRecords(kvc); return len(kvc.GetLogLines()) == 2 },
"incorrect number of logging events recorded"},
{func() bool { kvc.ClearLogLines(); return len(kvc.GetLogLines()) == 0 },
"logging events still exists after ClearLogLines"},
}
//execute the tests
for _, tl := range testList {
assert.True(t, tl.testPass(), tl.errMsg)
}
}

View File

@ -15,7 +15,7 @@ type Plugin interface {
RunTx(store KVStore, ctx CallContext, txBytes []byte) (res abci.Result)
// Other ABCI message handlers
SetOption(store KVStore, key string, value string) (log string)
SetOption(store KVStore, key, value string) (log string)
InitChain(store KVStore, vals []*abci.Validator)
BeginBlock(store KVStore, hash []byte, header *abci.Header)
EndBlock(store KVStore, height uint64) abci.ResponseEndBlock

56
types/plugin_test.go Normal file
View File

@ -0,0 +1,56 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
abci "github.com/tendermint/abci/types"
)
//----------------------------------
type Dummy struct{}
func (d *Dummy) Name() string {
return "dummy"
}
func (d *Dummy) RunTx(store KVStore, ctx CallContext, txBytes []byte) (res abci.Result) {
return
}
func (d *Dummy) SetOption(storei KVStore, key, value string) (log string) {
return ""
}
func (d *Dummy) InitChain(store KVStore, vals []*abci.Validator) {
}
func (d *Dummy) BeginBlock(store KVStore, hash []byte, header *abci.Header) {
}
func (d *Dummy) EndBlock(store KVStore, height uint64) (res abci.ResponseEndBlock) {
return
}
//----------------------------------
func TestPlugin(t *testing.T) {
plugins := NewPlugins()
//define the test list
var testList = []struct {
testPass func() bool
errMsg string
}{
{func() bool { return (len(plugins.GetList()) == 0) },
"plugins object init with a objects"},
{func() bool { plugins.RegisterPlugin(&Dummy{}); return (len(plugins.GetList()) == 1) },
"plugin wasn't added to plist after registered"},
{func() bool { return (plugins.GetByName("dummy").Name() == "dummy") },
"plugin wasn't retrieved properly with GetByName"},
}
//execute the tests
for _, tl := range testList {
assert.True(t, tl.testPass(), tl.errMsg)
}
}

45
types/test_helpers.go Normal file
View File

@ -0,0 +1,45 @@
package types
// Helper functions for testing
import (
cmn "github.com/tendermint/go-common"
"github.com/tendermint/go-crypto"
)
// Creates a PrivAccount from secret.
// The amount is not set.
func PrivAccountFromSecret(secret string) PrivAccount {
privKey := crypto.GenPrivKeyEd25519FromSecret([]byte(secret))
privAccount := PrivAccount{
PrivKeyS: crypto.PrivKeyS{privKey},
Account: Account{
PubKey: crypto.PubKeyS{privKey.PubKey()},
},
}
return privAccount
}
// Make `num` random accounts
func RandAccounts(num int, minAmount int64, maxAmount int64) []PrivAccount {
privAccs := make([]PrivAccount, num)
for i := 0; i < num; i++ {
balance := minAmount
if maxAmount > minAmount {
balance += cmn.RandInt64() % (maxAmount - minAmount)
}
privKey := crypto.GenPrivKeyEd25519()
pubKey := crypto.PubKeyS{privKey.PubKey()}
privAccs[i] = PrivAccount{
PrivKeyS: crypto.PrivKeyS{privKey},
Account: Account{
PubKey: pubKey,
Balance: Coins{Coin{"", balance}},
},
}
}
return privAccs
}

View File

@ -7,6 +7,7 @@ import (
abci "github.com/tendermint/abci/types"
. "github.com/tendermint/go-common"
"github.com/tendermint/go-crypto"
"github.com/tendermint/go-data"
"github.com/tendermint/go-wire"
)
@ -17,7 +18,6 @@ Account Types:
- SendTx Send coins to address
- AppTx Send a msg to a contract that runs in the vm
*/
type Tx interface {
AssertIsTx()
SignBytes(chainID string) []byte
@ -28,25 +28,47 @@ const (
// Account transactions
TxTypeSend = byte(0x01)
TxTypeApp = byte(0x02)
TxNameSend = "send"
TxNameApp = "app"
)
func (_ *SendTx) AssertIsTx() {}
func (_ *AppTx) AssertIsTx() {}
var _ = wire.RegisterInterface(
struct{ Tx }{},
wire.ConcreteType{&SendTx{}, TxTypeSend},
wire.ConcreteType{&AppTx{}, TxTypeApp},
)
var txMapper data.Mapper
// register both private key types with go-data (and thus go-wire)
func init() {
txMapper = data.NewMapper(TxS{}).
RegisterInterface(&SendTx{}, TxNameSend, TxTypeSend).
RegisterInterface(&AppTx{}, TxNameApp, TxTypeApp)
}
// TxS add json serialization to Tx
type TxS struct {
Tx
}
func (p TxS) MarshalJSON() ([]byte, error) {
return txMapper.ToJSON(p.Tx)
}
func (p *TxS) UnmarshalJSON(data []byte) (err error) {
parsed, err := txMapper.FromJSON(data)
if err == nil {
p.Tx = parsed.(Tx)
}
return
}
//-----------------------------------------------------------------------------
type TxInput struct {
Address []byte `json:"address"` // Hash of the PubKey
Coins Coins `json:"coins"` //
Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput
Signature crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx
PubKey crypto.PubKey `json:"pub_key"` // Is present iff Sequence == 0
Address data.Bytes `json:"address"` // Hash of the PubKey
Coins Coins `json:"coins"` //
Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput
Signature crypto.SignatureS `json:"signature"` // Depends on the PubKey type and the whole Tx
PubKey crypto.PubKeyS `json:"pub_key"` // Is present iff Sequence == 0
}
func (txIn TxInput) ValidateBasic() abci.Result {
@ -62,10 +84,10 @@ func (txIn TxInput) ValidateBasic() abci.Result {
if txIn.Sequence <= 0 {
return abci.ErrBaseInvalidInput.AppendLog("Sequence must be greater than 0")
}
if txIn.Sequence == 1 && txIn.PubKey == nil {
if txIn.Sequence == 1 && txIn.PubKey.Empty() {
return abci.ErrBaseInvalidInput.AppendLog("PubKey must be present when Sequence == 1")
}
if txIn.Sequence > 1 && txIn.PubKey != nil {
if txIn.Sequence > 1 && !txIn.PubKey.Empty() {
return abci.ErrBaseInvalidInput.AppendLog("PubKey must be nil when Sequence > 1")
}
return abci.OK
@ -78,12 +100,17 @@ func (txIn TxInput) String() string {
func NewTxInput(pubKey crypto.PubKey, coins Coins, sequence int) TxInput {
input := TxInput{
Address: pubKey.Address(),
PubKey: pubKey,
Coins: coins,
Sequence: sequence,
}
if sequence > 1 {
input.PubKey = nil
if sequence == 1 {
// safely wrap if needed
// TODO: extract this as utility function?
ps, ok := pubKey.(crypto.PubKeyS)
if !ok {
ps = crypto.PubKeyS{pubKey}
}
input.PubKey = ps
}
return input
}
@ -91,8 +118,8 @@ func NewTxInput(pubKey crypto.PubKey, coins Coins, sequence int) TxInput {
//-----------------------------------------------------------------------------
type TxOutput struct {
Address []byte `json:"address"` // Hash of the PubKey
Coins Coins `json:"coins"` //
Address data.Bytes `json:"address"` // Hash of the PubKey
Coins Coins `json:"coins"` //
}
func (txOut TxOutput) ValidateBasic() abci.Result {
@ -126,19 +153,23 @@ func (tx *SendTx) SignBytes(chainID string) []byte {
sigz := make([]crypto.Signature, len(tx.Inputs))
for i, input := range tx.Inputs {
sigz[i] = input.Signature
tx.Inputs[i].Signature = nil
tx.Inputs[i].Signature.Signature = nil
}
signBytes = append(signBytes, wire.BinaryBytes(tx)...)
for i := range tx.Inputs {
tx.Inputs[i].Signature = sigz[i]
tx.Inputs[i].Signature.Signature = sigz[i]
}
return signBytes
}
func (tx *SendTx) SetSignature(addr []byte, sig crypto.Signature) bool {
sigs, ok := sig.(crypto.SignatureS)
if !ok {
sigs = crypto.SignatureS{sig}
}
for i, input := range tx.Inputs {
if bytes.Equal(input.Address, addr) {
tx.Inputs[i].Signature = sig
tx.Inputs[i].Signature = sigs
return true
}
}
@ -152,24 +183,28 @@ func (tx *SendTx) String() string {
//-----------------------------------------------------------------------------
type AppTx struct {
Gas int64 `json:"gas"` // Gas
Fee Coin `json:"fee"` // Fee
Name string `json:"type"` // Which plugin
Input TxInput `json:"input"` // Hmmm do we want coins?
Data []byte `json:"data"`
Gas int64 `json:"gas"` // Gas
Fee Coin `json:"fee"` // Fee
Name string `json:"type"` // Which plugin
Input TxInput `json:"input"` // Hmmm do we want coins?
Data json.RawMessage `json:"data"`
}
func (tx *AppTx) SignBytes(chainID string) []byte {
signBytes := wire.BinaryBytes(chainID)
sig := tx.Input.Signature
tx.Input.Signature = nil
tx.Input.Signature.Signature = nil
signBytes = append(signBytes, wire.BinaryBytes(tx)...)
tx.Input.Signature = sig
return signBytes
}
func (tx *AppTx) SetSignature(sig crypto.Signature) bool {
tx.Input.Signature = sig
sigs, ok := sig.(crypto.SignatureS)
if !ok {
sigs = crypto.SignatureS{sig}
}
tx.Input.Signature = sigs
return true
}

View File

@ -3,7 +3,11 @@ package types
import (
"testing"
. "github.com/tendermint/go-common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
cmn "github.com/tendermint/go-common"
crypto "github.com/tendermint/go-crypto"
data "github.com/tendermint/go-data"
)
var chainID string = "test_chain"
@ -36,11 +40,11 @@ func TestSendTxSignable(t *testing.T) {
},
}
signBytes := sendTx.SignBytes(chainID)
signBytesHex := Fmt("%X", signBytes)
signBytesHex := cmn.Fmt("%X", signBytes)
expected := "010A746573745F636861696E0100000000000000DE00000000000000006F01020106696E7075743101010000000000000030390301093200000106696E70757432010100000000000000006F01DE0000010201076F757470757431010100000000000000014D01076F75747075743201010000000000000001BC"
if signBytesHex != expected {
t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signBytesHex)
}
assert.True(t, signBytesHex == expected,
cmn.Fmt("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signBytesHex))
}
func TestAppTxSignable(t *testing.T) {
@ -56,9 +60,66 @@ func TestAppTxSignable(t *testing.T) {
Data: []byte("data1"),
}
signBytes := callTx.SignBytes(chainID)
signBytesHex := Fmt("%X", signBytes)
signBytesHex := cmn.Fmt("%X", signBytes)
expected := "010A746573745F636861696E0100000000000000DE00000000000000006F0101580106696E70757431010100000000000000303903010932000001056461746131"
if signBytesHex != expected {
t.Errorf("Got unexpected sign string for AppTx. Expected:\n%v\nGot:\n%v", expected, signBytesHex)
}
assert.True(t, signBytesHex == expected,
cmn.Fmt("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signBytesHex))
}
func TestSendTxJSON(t *testing.T) {
chainID := "test_chain_id"
test1PrivAcc := PrivAccountFromSecret("sendtx1")
test2PrivAcc := PrivAccountFromSecret("sendtx2")
// Construct a SendTx signature
tx := &SendTx{
Gas: 1,
Fee: Coin{"foo", 2},
Inputs: []TxInput{
NewTxInput(test1PrivAcc.PubKey, Coins{{"foo", 10}}, 1),
},
Outputs: []TxOutput{
TxOutput{
Address: test2PrivAcc.PubKey.Address(),
Coins: Coins{{"foo", 8}},
},
},
}
// serialize this as json and back
js, err := data.ToJSON(TxS{tx})
require.Nil(t, err)
// fmt.Println(string(js))
txs := TxS{}
err = data.FromJSON(js, &txs)
require.Nil(t, err)
tx2, ok := txs.Tx.(*SendTx)
require.True(t, ok)
// make sure they are the same!
signBytes := tx.SignBytes(chainID)
signBytes2 := tx2.SignBytes(chainID)
assert.Equal(t, signBytes, signBytes2)
assert.Equal(t, tx, tx2)
// sign this thing
sig := test1PrivAcc.Sign(signBytes)
// we handle both raw sig and wrapped sig the same
tx.SetSignature(test1PrivAcc.PubKey.Address(), sig)
tx2.SetSignature(test1PrivAcc.PubKey.Address(), crypto.SignatureS{sig})
assert.Equal(t, tx, tx2)
// let's marshal / unmarshal this with signature
js, err = data.ToJSON(TxS{tx})
require.Nil(t, err)
// fmt.Println(string(js))
err = data.FromJSON(js, &txs)
require.Nil(t, err)
tx2, ok = txs.Tx.(*SendTx)
require.True(t, ok)
// and make sure the sig is preserved
assert.Equal(t, tx, tx2)
assert.False(t, tx2.Inputs[0].Signature.Empty())
}

7
version/version.go Normal file
View File

@ -0,0 +1,7 @@
package version
const Maj = "0"
const Min = "2"
const Fix = "0"
const Version = Maj + "." + Min + "." + Fix