commit
6f83510d34
|
@ -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
|
||||
|
||||
|
||||
|
2
Makefile
2
Makefile
|
@ -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:
|
||||
|
|
13
README.md
13
README.md
|
@ -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
|
||||
|
|
32
app/app.go
32
app/app.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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[:])
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-logger"
|
||||
)
|
||||
|
||||
var log = logger.New("module", "app")
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -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"
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"address": "1B1BE55F969F54064628A63B9559E7C21C925165",
|
||||
"priv_key": [
|
||||
1,
|
||||
"C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D10000000000000000000000000000000000000000000000000000000000000000"
|
||||
"C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D1619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"
|
||||
],
|
||||
"pub_key": [
|
||||
1,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090",
|
||||
"priv_key": [
|
||||
1,
|
||||
"34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A80000000000000000000000000000000000000000000000000000000000000000"
|
||||
"34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A8352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8"
|
||||
],
|
||||
"pub_key": [
|
||||
1,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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???
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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!
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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...]"
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ""
|
||||
}
|
||||
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
91
types/tx.go
91
types/tx.go
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package version
|
||||
|
||||
const Maj = "0"
|
||||
const Min = "2"
|
||||
const Fix = "0"
|
||||
|
||||
const Version = Maj + "." + Min + "." + Fix
|
Loading…
Reference in New Issue