Merge pull request #154 from tendermint/feature/replace-binaries-0.7

Feature/replace binaries 0.7
This commit is contained in:
Ethan Frey 2017-07-06 12:41:30 +02:00 committed by GitHub
commit e95724d8b1
68 changed files with 1710 additions and 1392 deletions

View File

@ -6,20 +6,20 @@ TUTORIALS=$(shell find docs/guide -name "*md" -type f)
all: get_vendor_deps install test
build:
go build ./cmd/...
@go build ./cmd/...
install:
go install ./cmd/...
go install ./docs/guide/counter/cmd/...
@go install ./cmd/...
@go install ./docs/guide/counter/cmd/...
dist:
@bash scripts/dist.sh
@bash scripts/publish.sh
@bash publish/dist.sh
@bash publish/publish.sh
test: test_unit test_cli test_tutorial
test_unit:
go test `glide novendor`
@go test `glide novendor`
#go run tests/tendermint/*.go
test_cli: tests/cli/shunit2
@ -27,29 +27,29 @@ test_cli: tests/cli/shunit2
@./tests/cli/basictx.sh
@./tests/cli/counter.sh
@./tests/cli/restart.sh
@./tests/cli/ibc.sh
# @./tests/cli/ibc.sh
test_tutorial: docs/guide/shunit2
shelldown ${TUTORIALS}
for script in docs/guide/*.sh ; do \
@shelldown ${TUTORIALS}
@for script in docs/guide/*.sh ; do \
bash $$script ; \
done
tests/cli/shunit2:
wget "https://raw.githubusercontent.com/kward/shunit2/master/source/2.1/src/shunit2" \
@wget "https://raw.githubusercontent.com/kward/shunit2/master/source/2.1/src/shunit2" \
-q -O tests/cli/shunit2
docs/guide/shunit2:
wget "https://raw.githubusercontent.com/kward/shunit2/master/source/2.1/src/shunit2" \
@wget "https://raw.githubusercontent.com/kward/shunit2/master/source/2.1/src/shunit2" \
-q -O docs/guide/shunit2
get_vendor_deps: tools
glide install
@glide install
build-docker:
docker run -it --rm -v "$(PWD):/go/src/github.com/tendermint/basecoin" -w \
@docker run -it --rm -v "$(PWD):/go/src/github.com/tendermint/basecoin" -w \
"/go/src/github.com/tendermint/basecoin" -e "CGO_ENABLED=0" golang:alpine go build ./cmd/basecoin
docker build -t "tendermint/basecoin" .
@docker build -t "tendermint/basecoin" .
tools:
@go get $(GOTOOLS)

View File

@ -1,153 +1,143 @@
package app
import (
"encoding/hex"
"encoding/json"
"fmt"
"strings"
abci "github.com/tendermint/abci/types"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/basecoin"
eyes "github.com/tendermint/merkleeyes/client"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/stack"
sm "github.com/tendermint/basecoin/state"
"github.com/tendermint/basecoin/types"
"github.com/tendermint/basecoin/version"
)
//nolint
const (
maxTxSize = 10240
PluginNameBase = "base"
ModuleNameBase = "base"
ChainKey = "chain_id"
)
// Basecoin - The ABCI application
type Basecoin struct {
eyesCli *eyes.Client
state *sm.State
cacheState *sm.State
plugins *types.Plugins
handler basecoin.Handler
logger log.Logger
}
func NewBasecoin(eyesCli *eyes.Client) *Basecoin {
state := sm.NewState(eyesCli)
plugins := types.NewPlugins()
// NewBasecoin - create a new instance of the basecoin application
func NewBasecoin(handler basecoin.Handler, eyesCli *eyes.Client, logger log.Logger) *Basecoin {
state := sm.NewState(eyesCli, logger.With("module", "state"))
return &Basecoin{
handler: handler,
eyesCli: eyesCli,
state: state,
cacheState: nil,
plugins: plugins,
logger: log.NewNopLogger(),
logger: logger,
}
}
func (app *Basecoin) SetLogger(l log.Logger) {
app.logger = l
app.state.SetLogger(l.With("module", "state"))
// DefaultHandler - placeholder to just handle sendtx
func DefaultHandler() basecoin.Handler {
// use the default stack
h := coin.NewHandler()
d := stack.NewDispatcher(stack.WrapHandler(h))
return stack.NewDefault().Use(d)
}
// XXX For testing, not thread safe!
// GetState - XXX For testing, not thread safe!
func (app *Basecoin) GetState() *sm.State {
return app.state.CacheWrap()
}
// ABCI::Info
// Info - ABCI
func (app *Basecoin) Info() abci.ResponseInfo {
resp, err := app.eyesCli.InfoSync()
if err != nil {
cmn.PanicCrisis(err)
}
return abci.ResponseInfo{
Data: cmn.Fmt("Basecoin v%v", version.Version),
Data: fmt.Sprintf("Basecoin v%v", version.Version),
LastBlockHeight: resp.LastBlockHeight,
LastBlockAppHash: resp.LastBlockAppHash,
}
}
func (app *Basecoin) RegisterPlugin(plugin types.Plugin) {
app.plugins.RegisterPlugin(plugin)
}
// ABCI::SetOption
// SetOption - ABCI
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)
if plugin == nil {
return "Invalid plugin name: " + pluginName
}
app.logger.Info("SetOption on plugin", "plugin", pluginName, "key", key, "value", value)
return plugin.SetOption(app.state, key, value)
} else {
// Set option on basecoin
switch key {
case "chain_id":
module, key := splitKey(key)
if module == ModuleNameBase {
if key == ChainKey {
app.state.SetChainID(value)
return "Success"
case "account":
var acc GenesisAccount
err := json.Unmarshal([]byte(value), &acc)
if err != nil {
return "Error decoding acc message: " + err.Error()
}
acc.Balance.Sort()
addr, err := acc.GetAddr()
if err != nil {
return "Invalid address: " + err.Error()
}
app.state.SetAccount(addr, acc.ToAccount())
app.logger.Info("SetAccount", "addr", hex.EncodeToString(addr), "acc", acc)
return "Success"
}
return "Unrecognized option key " + key
return fmt.Sprintf("Error: unknown base option: %s", key)
}
log, err := app.handler.SetOption(app.logger, app.state, module, key, value)
if err == nil {
return log
}
return "Error: " + err.Error()
}
// ABCI::DeliverTx
func (app *Basecoin) DeliverTx(txBytes []byte) (res abci.Result) {
if len(txBytes) > maxTxSize {
return abci.ErrBaseEncodingError.AppendLog("Tx size exceeds maximum")
}
// Decode tx
var tx types.Tx
err := wire.ReadBinaryBytes(txBytes, &tx)
// DeliverTx - ABCI
func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result {
tx, err := basecoin.LoadTx(txBytes)
if err != nil {
return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error())
return errors.Result(err)
}
// Validate and exec tx
res = sm.ExecTx(app.state, app.plugins, tx, false, nil)
if res.IsErr() {
return res.PrependLog("Error in DeliverTx")
}
return res
}
// TODO: can we abstract this setup and commit logic??
cache := app.state.CacheWrap()
ctx := stack.NewContext(
app.state.GetChainID(),
app.logger.With("call", "delivertx"),
)
res, err := app.handler.DeliverTx(ctx, cache, tx)
// ABCI::CheckTx
func (app *Basecoin) CheckTx(txBytes []byte) (res abci.Result) {
if len(txBytes) > maxTxSize {
return abci.ErrBaseEncodingError.AppendLog("Tx size exceeds maximum")
}
// Decode tx
var tx types.Tx
err := wire.ReadBinaryBytes(txBytes, &tx)
if err != nil {
return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error())
// discard the cache...
return errors.Result(err)
}
// Validate tx
res = sm.ExecTx(app.cacheState, app.plugins, tx, true, nil)
if res.IsErr() {
return res.PrependLog("Error in CheckTx")
}
return abci.OK
// commit the cache and return result
cache.CacheSync()
return res.ToABCI()
}
// ABCI::Query
// CheckTx - ABCI
func (app *Basecoin) CheckTx(txBytes []byte) abci.Result {
tx, err := basecoin.LoadTx(txBytes)
if err != nil {
return errors.Result(err)
}
// TODO: can we abstract this setup and commit logic??
ctx := stack.NewContext(
app.state.GetChainID(),
app.logger.With("call", "checktx"),
)
// checktx generally shouldn't touch the state, but we don't care
// here on the framework level, since the cacheState is thrown away next block
res, err := app.handler.CheckTx(ctx, app.cacheState, tx)
if err != nil {
return errors.Result(err)
}
return res.ToABCI()
}
// Query - ABCI
func (app *Basecoin) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) {
if len(reqQuery.Data) == 0 {
resQuery.Log = "Query cannot be zero length"
@ -155,12 +145,6 @@ 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 = types.AccountKey(reqQuery.Data)
}
resQuery, err := app.eyesCli.QuerySync(reqQuery)
if err != nil {
resQuery.Log = "Failed to query MerkleEyes: " + err.Error()
@ -170,7 +154,7 @@ func (app *Basecoin) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQu
return
}
// ABCI::Commit
// Commit - ABCI
func (app *Basecoin) Commit() (res abci.Result) {
// Commit state
@ -185,37 +169,37 @@ func (app *Basecoin) Commit() (res abci.Result) {
return res
}
// ABCI::InitChain
// InitChain - ABCI
func (app *Basecoin) InitChain(validators []*abci.Validator) {
for _, plugin := range app.plugins.GetList() {
plugin.InitChain(app.state, validators)
}
// for _, plugin := range app.plugins.GetList() {
// plugin.InitChain(app.state, validators)
// }
}
// ABCI::BeginBlock
// BeginBlock - ABCI
func (app *Basecoin) BeginBlock(hash []byte, header *abci.Header) {
for _, plugin := range app.plugins.GetList() {
plugin.BeginBlock(app.state, hash, header)
}
// for _, plugin := range app.plugins.GetList() {
// plugin.BeginBlock(app.state, hash, header)
// }
}
// ABCI::EndBlock
// EndBlock - ABCI
func (app *Basecoin) EndBlock(height uint64) (res abci.ResponseEndBlock) {
for _, plugin := range app.plugins.GetList() {
pluginRes := plugin.EndBlock(app.state, height)
res.Diffs = append(res.Diffs, pluginRes.Diffs...)
}
// for _, plugin := range app.plugins.GetList() {
// pluginRes := plugin.EndBlock(app.state, height)
// res.Diffs = append(res.Diffs, pluginRes.Diffs...)
// }
return
}
//----------------------------------------
//TODO move split key to tmlibs?
// Splits the string at the first '/'.
// if there are none, the second string is nil.
func splitKey(key string) (prefix string, suffix string) {
// if there are none, assign default module ("base").
func splitKey(key string) (string, string) {
if strings.Contains(key, "/") {
keyParts := strings.SplitN(key, "/", 2)
return keyParts[0], keyParts[1]
}
return key, ""
return ModuleNameBase, key
}

View File

@ -3,13 +3,19 @@ package app
import (
"encoding/hex"
"encoding/json"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/txs"
"github.com/tendermint/basecoin/types"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/log"
@ -36,17 +42,24 @@ func newAppTest(t *testing.T) *appTest {
}
// make a tx sending 5mycoin from each accIn to accOut
func (at *appTest) getTx(seq int) *types.SendTx {
tx := types.MakeSendTx(seq, at.accOut, at.accIn)
types.SignTx(at.chainID, tx, at.accIn)
return tx
func (at *appTest) getTx(seq int, coins types.Coins) basecoin.Tx {
addrIn := at.accIn.Account.PubKey.Address()
addrOut := at.accOut.Account.PubKey.Address()
in := []coin.TxInput{{Address: stack.SigPerm(addrIn), Coins: coins, Sequence: seq}}
out := []coin.TxOutput{{Address: stack.SigPerm(addrOut), Coins: coins}}
tx := coin.NewSendTx(in, out)
tx = txs.NewChain(at.chainID, tx)
stx := txs.NewMulti(tx)
txs.Sign(stx, at.accIn.PrivKey)
return stx.Wrap()
}
// set the account on the app through SetOption
func (at *appTest) acc2app(acc types.Account) {
accBytes, err := json.Marshal(acc)
require.Nil(at.t, err)
res := at.app.SetOption("base/account", string(accBytes))
res := at.app.SetOption("coin/account", string(accBytes))
require.EqualValues(at.t, res, "Success")
}
@ -56,8 +69,14 @@ func (at *appTest) reset() {
at.accOut = types.MakeAcc("output0")
eyesCli := eyes.NewLocalClient("", 0)
at.app = NewBasecoin(eyesCli)
at.app.SetLogger(log.TestingLogger().With("module", "app"))
// logger := log.TestingLogger().With("module", "app"),
logger := log.NewTMLogger(os.Stdout).With("module", "app")
logger = log.NewTracingLogger(logger)
at.app = NewBasecoin(
DefaultHandler(),
eyesCli,
logger,
)
res := at.app.SetOption("base/chain_id", at.chainID)
require.EqualValues(at.t, res, "Success")
@ -69,45 +88,51 @@ func (at *appTest) reset() {
require.True(at.t, resabci.IsOK(), resabci)
}
func getBalance(pk crypto.PubKey, state types.KVStore) (types.Coins, error) {
return getAddr(pk.Address(), state)
}
func getAddr(addr []byte, state types.KVStore) (types.Coins, error) {
actor := stack.SigPerm(addr)
acct, err := coin.NewAccountant("").GetAccount(state, actor)
return acct.Coins, err
}
// returns the final balance and expected balance for input and output accounts
func (at *appTest) exec(tx *types.SendTx, checkTx bool) (res abci.Result, inputGot, inputExp, outputGot, outputExpected types.Coins) {
func (at *appTest) exec(t *testing.T, tx basecoin.Tx, checkTx bool) (res abci.Result, diffIn, diffOut types.Coins) {
require := require.New(t)
initBalIn := at.app.GetState().GetAccount(at.accIn.Account.PubKey.Address()).Balance
initBalOut := at.app.GetState().GetAccount(at.accOut.Account.PubKey.Address()).Balance
initBalIn, err := getBalance(at.accIn.Account.PubKey, at.app.GetState())
require.Nil(err, "%+v", err)
initBalOut, err := getBalance(at.accOut.Account.PubKey, at.app.GetState())
require.Nil(err, "%+v", err)
txBytes := []byte(wire.BinaryBytes(struct{ types.Tx }{tx}))
txBytes := wire.BinaryBytes(tx)
if checkTx {
res = at.app.CheckTx(txBytes)
} else {
res = at.app.DeliverTx(txBytes)
}
endBalIn := at.app.GetState().GetAccount(at.accIn.Account.PubKey.Address()).Balance
endBalOut := at.app.GetState().GetAccount(at.accOut.Account.PubKey.Address()).Balance
decrBalInExp := tx.Outputs[0].Coins.Plus(types.Coins{tx.Fee})
return res, endBalIn, initBalIn.Minus(decrBalInExp), endBalOut, initBalOut.Plus(tx.Outputs[0].Coins)
endBalIn, err := getBalance(at.accIn.Account.PubKey, at.app.GetState())
require.Nil(err, "%+v", err)
endBalOut, err := getBalance(at.accOut.Account.PubKey, at.app.GetState())
require.Nil(err, "%+v", err)
return res, endBalIn.Minus(initBalIn), endBalOut.Minus(initBalOut)
}
//--------------------------------------------------------
func TestSplitKey(t *testing.T) {
assert := assert.New(t)
prefix, suffix := splitKey("foo/bar")
assert.EqualValues("foo", prefix)
assert.EqualValues("bar", suffix)
prefix, suffix = splitKey("foobar")
assert.EqualValues("foobar", prefix)
assert.EqualValues("", suffix)
}
func TestSetOption(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
eyesCli := eyes.NewLocalClient("", 0)
app := NewBasecoin(eyesCli)
app.SetLogger(log.TestingLogger().With("module", "app"))
app := NewBasecoin(
DefaultHandler(),
eyesCli,
log.TestingLogger().With("module", "app"),
)
//testing ChainID
chainID := "testChain"
@ -116,15 +141,16 @@ func TestSetOption(t *testing.T) {
assert.EqualValues(res, "Success")
// make a nice account...
accIn := types.MakeAcc("input0")
accsInBytes, err := json.Marshal(accIn.Account)
accIn := types.MakeAcc("input0").Account
accsInBytes, err := json.Marshal(accIn)
assert.Nil(err)
res = app.SetOption("base/account", string(accsInBytes))
res = app.SetOption("coin/account", string(accsInBytes))
require.EqualValues(res, "Success")
// make sure it is set correctly, with some balance
acct := types.GetAccount(app.GetState(), accIn.PubKey.Address())
require.NotNil(acct)
assert.Equal(accIn.Balance, acct.Balance)
coins, err := getBalance(accIn.PubKey, app.state)
require.Nil(err)
assert.Equal(accIn.Balance, coins)
// let's parse an account with badly sorted coins...
unsortAddr, err := hex.DecodeString("C471FB670E44D219EE6DF2FC284BE38793ACBCE1")
@ -146,12 +172,13 @@ func TestSetOption(t *testing.T) {
}
]
}`
res = app.SetOption("base/account", unsortAcc)
res = app.SetOption("coin/account", unsortAcc)
require.EqualValues(res, "Success")
acct = types.GetAccount(app.GetState(), unsortAddr)
require.NotNil(acct)
assert.True(acct.Balance.IsValid())
assert.Equal(unsortCoins, acct.Balance)
coins, err = getAddr(unsortAddr, app.state)
require.Nil(err)
assert.True(coins.IsValid())
assert.Equal(unsortCoins, coins)
res = app.SetOption("base/dslfkgjdas", "")
assert.NotEqual(res, "Success")
@ -172,33 +199,32 @@ func TestTx(t *testing.T) {
//Bad Balance
at.accIn.Balance = types.Coins{{"mycoin", 2}}
at.acc2app(at.accIn.Account)
res, _, _, _, _ := at.exec(at.getTx(1), true)
res, _, _ := at.exec(t, at.getTx(1, types.Coins{{"mycoin", 5}}), true)
assert.True(res.IsErr(), "ExecTx/Bad CheckTx: Expected error return from ExecTx, returned: %v", res)
res, inGot, inExp, outGot, outExp := at.exec(at.getTx(1), false)
res, diffIn, diffOut := at.exec(t, at.getTx(1, types.Coins{{"mycoin", 5}}), false)
assert.True(res.IsErr(), "ExecTx/Bad DeliverTx: Expected error return from ExecTx, returned: %v", res)
assert.False(inGot.IsEqual(inExp), "ExecTx/Bad DeliverTx: shouldn't be equal, inGot: %v, inExp: %v", inGot, inExp)
assert.False(outGot.IsEqual(outExp), "ExecTx/Bad DeliverTx: shouldn't be equal, outGot: %v, outExp: %v", outGot, outExp)
assert.True(diffIn.IsZero())
assert.True(diffOut.IsZero())
//Regular CheckTx
at.reset()
res, _, _, _, _ = at.exec(at.getTx(1), true)
res, _, _ = at.exec(t, at.getTx(1, types.Coins{{"mycoin", 5}}), true)
assert.True(res.IsOK(), "ExecTx/Good CheckTx: Expected OK return from ExecTx, Error: %v", res)
//Regular DeliverTx
at.reset()
res, inGot, inExp, outGot, outExp = at.exec(at.getTx(1), false)
amt := types.Coins{{"mycoin", 3}}
res, diffIn, diffOut = at.exec(t, at.getTx(1, amt), false)
assert.True(res.IsOK(), "ExecTx/Good DeliverTx: Expected OK return from ExecTx, Error: %v", res)
assert.True(inGot.IsEqual(inExp),
"ExecTx/good DeliverTx: unexpected change in input coins, inGot: %v, inExp: %v", inGot, inExp)
assert.True(outGot.IsEqual(outExp),
"ExecTx/good DeliverTx: unexpected change in output coins, outGot: %v, outExp: %v", outGot, outExp)
assert.Equal(amt.Negative(), diffIn)
assert.Equal(amt, diffOut)
}
func TestQuery(t *testing.T) {
assert := assert.New(t)
at := newAppTest(t)
res, _, _, _, _ := at.exec(at.getTx(1), false)
res, _, _ := at.exec(t, at.getTx(1, types.Coins{{"mycoin", 5}}), false)
assert.True(res.IsOK(), "Commit, DeliverTx: Expected OK return from DeliverTx, Error: %v", res)
resQueryPreCommit := at.app.Query(abci.RequestQuery{
@ -215,3 +241,19 @@ func TestQuery(t *testing.T) {
})
assert.NotEqual(resQueryPreCommit, resQueryPostCommit, "Query should change before/after commit")
}
func TestSplitKey(t *testing.T) {
assert := assert.New(t)
prefix, suffix := splitKey("foo/bar")
assert.EqualValues("foo", prefix)
assert.EqualValues("bar", suffix)
prefix, suffix = splitKey("foobar")
assert.EqualValues("base", prefix)
assert.EqualValues("foobar", suffix)
prefix, suffix = splitKey("some/complex/issue")
assert.EqualValues("some", prefix)
assert.EqualValues("complex/issue", suffix)
}

View File

@ -1,17 +1,14 @@
package app
import (
"bytes"
"encoding/json"
"github.com/pkg/errors"
"github.com/tendermint/basecoin/types"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire/data"
cmn "github.com/tendermint/tmlibs/common"
)
// LoadGenesis - Load the genesis file into memory
func (app *Basecoin) LoadGenesis(path string) error {
genDoc, err := loadGenesis(path)
if err != nil {
@ -22,20 +19,13 @@ func (app *Basecoin) LoadGenesis(path string) error {
app.SetOption("base/chain_id", genDoc.ChainID)
// set accounts
for _, acc := range genDoc.AppOptions.Accounts {
accBytes, err := json.Marshal(acc)
if err != nil {
return err
}
r := app.SetOption("base/account", string(accBytes))
// TODO: SetOption returns an error
app.logger.Info("Done setting Account via SetOption", "result", r)
for _, acct := range genDoc.AppOptions.Accounts {
_ = app.SetOption("coin/account", string(acct))
}
// set plugin options
for _, kv := range genDoc.AppOptions.pluginOptions {
r := app.SetOption(kv.Key, kv.Value)
app.logger.Info("Done setting Plugin key-value pair via SetOption", "result", r, "k", kv.Key, "v", kv.Value)
_ = app.SetOption(kv.Key, kv.Value)
}
return nil
@ -46,14 +36,15 @@ type keyValue struct {
Value string `json:"value"`
}
// includes tendermint (in the json, we ignore here)
// FullGenesisDoc - includes tendermint (in the json, we ignore here)
type FullGenesisDoc struct {
ChainID string `json:"chain_id"`
AppOptions *GenesisDoc `json:"app_options"`
}
// GenesisDoc - All genesis values
type GenesisDoc struct {
Accounts []GenesisAccount `json:"accounts"`
Accounts []json.RawMessage `json:"accounts"`
PluginOptions []json.RawMessage `json:"plugin_options"`
pluginOptions []keyValue // unmarshaled rawmessages
@ -84,20 +75,20 @@ func loadGenesis(filePath string) (*FullGenesisDoc, error) {
return genDoc, nil
}
func parseGenesisList(kvz_ []json.RawMessage) (kvz []keyValue, err error) {
if len(kvz_)%2 != 0 {
func parseGenesisList(kvzIn []json.RawMessage) (kvz []keyValue, err error) {
if len(kvzIn)%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 {
for i := 0; i < len(kvzIn); i += 2 {
kv := keyValue{}
rawK := []byte(kvz_[i])
rawK := []byte(kvzIn[i])
err := json.Unmarshal(rawK, &(kv.Key))
if err != nil {
return nil, errors.Errorf("Non-string key: %s", string(rawK))
}
// convert value to string if possible (otherwise raw json)
rawV := kvz_[i+1]
rawV := kvzIn[i+1]
err = json.Unmarshal(rawV, &(kv.Value))
if err != nil {
kv.Value = string(rawV)
@ -106,40 +97,3 @@ func parseGenesisList(kvz_ []json.RawMessage) (kvz []keyValue, err error) {
}
return kvz, nil
}
/**** code to parse accounts from genesis docs ***/
type GenesisAccount struct {
Address data.Bytes `json:"address"`
// this from types.Account (don't know how to embed this properly)
PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known.
Sequence int `json:"sequence"`
Balance types.Coins `json:"coins"`
}
func (g GenesisAccount) ToAccount() *types.Account {
return &types.Account{
PubKey: g.PubKey,
Sequence: g.Sequence,
Balance: g.Balance,
}
}
func (g GenesisAccount) GetAddr() ([]byte, error) {
noAddr, noPk := len(g.Address) == 0, g.PubKey.Empty()
if noAddr {
if noPk {
return nil, errors.New("No address given")
}
return g.PubKey.Address(), nil
}
if noPk { // but is addr...
return g.Address, nil
}
// now, we have both, make sure they check out
if bytes.Equal(g.Address, g.PubKey.Address()) {
return g.Address, nil
}
return nil, errors.New("Address and pubkey don't match")
}

View File

@ -8,9 +8,9 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/basecoin/types"
crypto "github.com/tendermint/go-crypto"
eyescli "github.com/tendermint/merkleeyes/client"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
)
const genesisFilepath = "./testdata/genesis.json"
@ -18,7 +18,7 @@ const genesisAcctFilepath = "./testdata/genesis2.json"
func TestLoadGenesisDoNotFailIfAppOptionsAreMissing(t *testing.T) {
eyesCli := eyescli.NewLocalClient("", 0)
app := NewBasecoin(eyesCli)
app := NewBasecoin(DefaultHandler(), eyesCli, log.TestingLogger())
err := app.LoadGenesis("./testdata/genesis3.json")
require.Nil(t, err, "%+v", err)
}
@ -27,7 +27,7 @@ func TestLoadGenesis(t *testing.T) {
assert, require := assert.New(t), require.New(t)
eyesCli := eyescli.NewLocalClient("", 0)
app := NewBasecoin(eyesCli)
app := NewBasecoin(DefaultHandler(), eyesCli, log.TestingLogger())
err := app.LoadGenesis(genesisFilepath)
require.Nil(err, "%+v", err)
@ -36,27 +36,19 @@ func TestLoadGenesis(t *testing.T) {
// and check the account info - previously calculated values
addr, _ := hex.DecodeString("eb98e0688217cfdeb70eddf4b33cdcc37fc53197")
pkbyte, _ := hex.DecodeString("6880db93598e283a67c4d88fc67a8858aa2de70f713fe94a5109e29c137100c2")
acct := app.GetState().GetAccount(addr)
require.NotNil(acct)
coins, err := getAddr(addr, app.state)
require.Nil(err)
assert.True(coins.IsPositive())
// make sure balance is proper
assert.Equal(2, len(acct.Balance))
assert.True(acct.Balance.IsValid())
assert.Equal(2, len(coins))
assert.True(coins.IsValid())
// note, that we now sort them to be valid
assert.EqualValues(654321, acct.Balance[0].Amount)
assert.EqualValues("ETH", acct.Balance[0].Denom)
assert.EqualValues(12345, acct.Balance[1].Amount)
assert.EqualValues("blank", acct.Balance[1].Denom)
// and public key is parsed properly
apk := acct.PubKey.Unwrap()
require.NotNil(apk)
epk, ok := apk.(crypto.PubKeyEd25519)
if assert.True(ok) {
assert.EqualValues(pkbyte, epk[:])
}
assert.EqualValues(654321, coins[0].Amount)
assert.EqualValues("ETH", coins[0].Denom)
assert.EqualValues(12345, coins[1].Amount)
assert.EqualValues("blank", coins[1].Denom)
}
// Fix for issue #89, change the parse format for accounts in genesis.json
@ -64,7 +56,7 @@ func TestLoadGenesisAccountAddress(t *testing.T) {
assert, require := assert.New(t), require.New(t)
eyesCli := eyescli.NewLocalClient("", 0)
app := NewBasecoin(eyesCli)
app := NewBasecoin(DefaultHandler(), eyesCli, log.TestingLogger())
err := app.LoadGenesis(genesisAcctFilepath)
require.Nil(err, "%+v", err)
@ -89,17 +81,17 @@ func TestLoadGenesisAccountAddress(t *testing.T) {
{"979F080B1DD046C452C2A8A250D18646C6B669D4", true, true, types.Coins{{"four", 444}}},
}
for _, tc := range cases {
for i, tc := range cases {
addr, err := hex.DecodeString(tc.addr)
require.Nil(err, tc.addr)
acct := app.GetState().GetAccount(addr)
coins, err := getAddr(addr, app.state)
require.Nil(err, "%+v", err)
if !tc.exists {
assert.Nil(acct, tc.addr)
} else if assert.NotNil(acct, tc.addr) {
assert.True(coins.IsZero(), "%d", i)
} else if assert.False(coins.IsZero(), "%d", i) {
// it should and does exist...
assert.True(acct.Balance.IsValid())
assert.Equal(tc.coins, acct.Balance)
assert.Equal(!tc.hasPubkey, acct.PubKey.Empty(), tc.addr)
assert.True(coins.IsValid())
assert.Equal(tc.coins, coins)
}
}
}

View File

@ -1,82 +0,0 @@
package commands
import (
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
wire "github.com/tendermint/go-wire"
bc "github.com/tendermint/basecoin/types"
)
// AppTx Application transaction structure for client
type AppTx struct {
chainID string
signers []crypto.PubKey
Tx *bc.AppTx
}
var _ keys.Signable = &AppTx{}
// SignBytes returned the unsigned bytes, needing a signature
func (s *AppTx) SignBytes() []byte {
return s.Tx.SignBytes(s.chainID)
}
// Sign will add a signature and pubkey.
//
// Depending on the Signable, one may be able to call this multiple times for multisig
// Returns error if called with invalid data or too many times
func (s *AppTx) Sign(pubkey crypto.PubKey, sig crypto.Signature) error {
if len(s.signers) > 0 {
return errors.New("AppTx already signed")
}
s.Tx.SetSignature(sig)
s.signers = []crypto.PubKey{pubkey}
return nil
}
// Signers will return the public key(s) that signed if the signature
// is valid, or an error if there is any issue with the signature,
// including if there are no signatures
func (s *AppTx) Signers() ([]crypto.PubKey, error) {
if len(s.signers) == 0 {
return nil, errors.New("No signatures on AppTx")
}
return s.signers, nil
}
// TxBytes returns the transaction data as well as all signatures
// It should return an error if Sign was never called
func (s *AppTx) TxBytes() ([]byte, error) {
// TODO: verify it is signed
// Code and comment from: basecoin/cmd/basecoin/commands/tx.go
// Don't you hate having to do this?
// How many times have I lost an hour over this trick?!
txBytes := wire.BinaryBytes(bc.TxS{s.Tx})
return txBytes, nil
}
// TODO: this should really be in the basecoin.types SendTx,
// but that code is too ugly now, needs refactor..
func (a *AppTx) ValidateBasic() error {
if a.chainID == "" {
return errors.New("No chain-id specified")
}
in := a.Tx.Input
if len(in.Address) != 20 {
return errors.Errorf("Invalid input address length: %d", len(in.Address))
}
if !in.Coins.IsValid() {
return errors.Errorf("Invalid input coins %v", in.Coins)
}
if in.Coins.IsZero() {
return errors.New("Input coins cannot be zero")
}
if in.Sequence <= 0 {
return errors.New("Sequence must be greater than 0")
}
return nil
}

View File

@ -7,12 +7,14 @@ import (
"github.com/spf13/viper"
)
// AutoCompleteCmd - command to generate bash autocompletions
var AutoCompleteCmd = &cobra.Command{
Use: "complete",
Short: "generate bash autocompletions",
RunE: doAutoComplete,
}
// nolint - flags
const (
FlagOutput = "file"
)

View File

@ -6,14 +6,16 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/tendermint/basecoin"
"github.com/tendermint/light-client/commands"
txcmd "github.com/tendermint/light-client/commands/txs"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/txs"
btypes "github.com/tendermint/basecoin/types"
)
@ -48,27 +50,25 @@ func init() {
// runDemo is an example of how to make a tx
func doSendTx(cmd *cobra.Command, args []string) error {
// load data from json or flags
tx := new(btypes.SendTx)
found, err := txcmd.LoadJSON(tx)
var tx basecoin.Tx
found, err := txcmd.LoadJSON(&tx)
if err != nil {
return err
}
if !found {
err = readSendTxFlags(tx)
tx, err = readSendTxFlags()
}
if err != nil {
return err
}
// Wrap and add signer
send := &SendTx{
chainID: commands.GetChainID(),
Tx: tx,
}
send.AddSigner(txcmd.GetSigner())
// TODO: make this more flexible for middleware
// add the chain info
tx = txs.NewChain(commands.GetChainID(), tx)
stx := txs.NewSig(tx)
// Sign if needed and post. This it the work-horse
bres, err := txcmd.SignAndPostTx(send)
bres, err := txcmd.SignAndPostTx(stx)
if err != nil {
return err
}
@ -77,40 +77,50 @@ func doSendTx(cmd *cobra.Command, args []string) error {
return txcmd.OutputTx(bres)
}
func readSendTxFlags(tx *btypes.SendTx) error {
func readSendTxFlags() (tx basecoin.Tx, err error) {
// parse to address
to, err := parseChainAddress(viper.GetString(FlagTo))
chain, to, err := parseChainAddress(viper.GetString(FlagTo))
if err != nil {
return err
return tx, err
}
toAddr := stack.SigPerm(to)
toAddr.ChainID = chain
// //parse the fee and amounts into coin types
// tx.Fee, err = btypes.ParseCoin(viper.GetString(FlagFee))
// if err != nil {
// return err
// }
// // set the gas
// tx.Gas = viper.GetInt64(FlagGas)
//parse the fee and amounts into coin types
tx.Fee, err = btypes.ParseCoin(viper.GetString(FlagFee))
if err != nil {
return err
}
amountCoins, err := btypes.ParseCoins(viper.GetString(FlagAmount))
if err != nil {
return err
return tx, err
}
// set the gas
tx.Gas = viper.GetInt64(FlagGas)
// this could be much cooler with multisig...
var fromAddr basecoin.Actor
signer := txcmd.GetSigner()
if !signer.Empty() {
fromAddr = stack.SigPerm(signer.Address())
}
// craft the inputs and outputs
tx.Inputs = []btypes.TxInput{{
ins := []coin.TxInput{{
Address: fromAddr,
Coins: amountCoins,
Sequence: viper.GetInt(FlagSequence),
}}
tx.Outputs = []btypes.TxOutput{{
Address: to,
outs := []coin.TxOutput{{
Address: toAddr,
Coins: amountCoins,
}}
return nil
return coin.NewSendTx(ins, outs), nil
}
func parseChainAddress(toFlag string) ([]byte, error) {
func parseChainAddress(toFlag string) (string, []byte, error) {
var toHex string
var chainPrefix string
spl := strings.Split(toFlag, "/")
@ -121,87 +131,16 @@ func parseChainAddress(toFlag string) ([]byte, error) {
chainPrefix = spl[0]
toHex = spl[1]
default:
return nil, errors.Errorf("To address has too many slashes")
return "", nil, errors.Errorf("To address has too many slashes")
}
// convert destination address to bytes
to, err := hex.DecodeString(cmn.StripHex(toHex))
if err != nil {
return nil, errors.Errorf("To address is invalid hex: %v\n", err)
return "", nil, errors.Errorf("To address is invalid hex: %v\n", err)
}
if chainPrefix != "" {
to = []byte(chainPrefix + "/" + string(to))
}
return to, nil
}
//-------------------------
// AppTx
// BroadcastAppTx wraps, signs, and executes an app tx basecoin transaction
func BroadcastAppTx(tx *btypes.AppTx) (*ctypes.ResultBroadcastTxCommit, error) {
// Sign if needed and post to the node. This it the work-horse
return txcmd.SignAndPostTx(WrapAppTx(tx))
}
// AddAppTxFlags adds flags required by apptx
func AddAppTxFlags(fs *flag.FlagSet) {
fs.String(FlagAmount, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
fs.String(FlagFee, "0mycoin", "Coins for the transaction fee of the format <amt><coin>")
fs.Int64(FlagGas, 0, "Amount of gas for this transaction")
fs.Int(FlagSequence, -1, "Sequence number for this transaction")
}
// ReadAppTxFlags reads in the standard flags
// your command should parse info to set tx.Name and tx.Data
func ReadAppTxFlags() (gas int64, fee btypes.Coin, txInput btypes.TxInput, err error) {
// Set the gas
gas = viper.GetInt64(FlagGas)
// Parse the fee and amounts into coin types
fee, err = btypes.ParseCoin(viper.GetString(FlagFee))
if err != nil {
return
}
// retrieve the amount
var amount btypes.Coins
amount, err = btypes.ParseCoins(viper.GetString(FlagAmount))
if err != nil {
return
}
// get the PubKey of the signer
pk := txcmd.GetSigner()
// get addr if available
var addr []byte
if !pk.Empty() {
addr = pk.Address()
}
// set the output
txInput = btypes.TxInput{
Coins: amount,
Sequence: viper.GetInt(FlagSequence),
Address: addr,
}
// set the pubkey if needed
if txInput.Sequence == 1 {
txInput.PubKey = pk
}
return
}
// WrapAppTx wraps the transaction with chain id
func WrapAppTx(tx *btypes.AppTx) *AppTx {
return &AppTx{
chainID: commands.GetChainID(),
Tx: tx,
}
return chainPrefix, to, nil
}
/** TODO copied from basecoin cli - put in common somewhere? **/

View File

@ -4,15 +4,18 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/tendermint/basecoin"
wire "github.com/tendermint/go-wire"
lc "github.com/tendermint/light-client"
lcmd "github.com/tendermint/light-client/commands"
proofcmd "github.com/tendermint/light-client/commands/proofs"
"github.com/tendermint/light-client/proofs"
btypes "github.com/tendermint/basecoin/types"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/stack"
)
// AccountQueryCmd - command to query an account
var AccountQueryCmd = &cobra.Command{
Use: "account [address]",
Short: "Get details of an account, with proof",
@ -24,9 +27,9 @@ func doAccountQuery(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
key := btypes.AccountKey(addr)
key := coin.NewAccountant("").MakeKey(stack.SigPerm(addr))
acc := new(btypes.Account)
acc := coin.Account{}
proof, err := proofcmd.GetAndParseAppProof(key, &acc)
if lc.IsNoDataErr(err) {
return errors.Errorf("Account bytes are empty for address %X ", addr)
@ -42,8 +45,9 @@ type BaseTxPresenter struct {
proofs.RawPresenter // this handles MakeKey as hex bytes
}
func (_ BaseTxPresenter) ParseData(raw []byte) (interface{}, error) {
var tx btypes.TxS
// ParseData - unmarshal raw bytes to a basecoin tx
func (BaseTxPresenter) ParseData(raw []byte) (interface{}, error) {
var tx basecoin.Tx
err := wire.ReadBinaryBytes(raw, &tx)
return tx, err
}

View File

@ -1,113 +0,0 @@
package commands
import (
"github.com/pkg/errors"
bc "github.com/tendermint/basecoin/types"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
wire "github.com/tendermint/go-wire"
)
type SendTx struct {
chainID string
signers []crypto.PubKey
Tx *bc.SendTx
}
var _ keys.Signable = &SendTx{}
// SignBytes returned the unsigned bytes, needing a signature
func (s *SendTx) SignBytes() []byte {
return s.Tx.SignBytes(s.chainID)
}
// Sign will add a signature and pubkey.
//
// Depending on the Signable, one may be able to call this multiple times for multisig
// Returns error if called with invalid data or too many times
func (s *SendTx) Sign(pubkey crypto.PubKey, sig crypto.Signature) error {
addr := pubkey.Address()
set := s.Tx.SetSignature(addr, sig)
if !set {
return errors.Errorf("Cannot add signature for address %X", addr)
}
s.signers = append(s.signers, pubkey)
return nil
}
// Signers will return the public key(s) that signed if the signature
// is valid, or an error if there is any issue with the signature,
// including if there are no signatures
func (s *SendTx) Signers() ([]crypto.PubKey, error) {
if len(s.signers) == 0 {
return nil, errors.New("No signatures on SendTx")
}
return s.signers, nil
}
// TxBytes returns the transaction data as well as all signatures
// It should return an error if Sign was never called
func (s *SendTx) TxBytes() ([]byte, error) {
// TODO: verify it is signed
// Code and comment from: basecoin/cmd/basecoin/commands/tx.go
// Don't you hate having to do this?
// How many times have I lost an hour over this trick?!
txBytes := wire.BinaryBytes(struct {
bc.Tx `json:"unwrap"`
}{s.Tx})
return txBytes, nil
}
// AddSigner sets address and pubkey info on the tx based on the key that
// will be used for signing
func (s *SendTx) AddSigner(pk crypto.PubKey) {
// get addr if available
var addr []byte
if !pk.Empty() {
addr = pk.Address()
}
// set the send address, and pubkey if needed
in := s.Tx.Inputs
in[0].Address = addr
if in[0].Sequence == 1 {
in[0].PubKey = pk
}
}
// TODO: this should really be in the basecoin.types SendTx,
// but that code is too ugly now, needs refactor..
func (s *SendTx) ValidateBasic() error {
if s.chainID == "" {
return errors.New("No chain-id specified")
}
for _, in := range s.Tx.Inputs {
if len(in.Address) != 20 {
return errors.Errorf("Invalid input address length: %d", len(in.Address))
}
if !in.Coins.IsValid() {
return errors.Errorf("Invalid input coins %v", in.Coins)
}
if in.Coins.IsZero() {
return errors.New("Input coins cannot be zero")
}
if in.Sequence <= 0 {
return errors.New("Sequence must be greater than 0")
}
}
for _, out := range s.Tx.Outputs {
// we now allow chain/addr, so it can be more than 20 bytes
if len(out.Address) < 20 {
return errors.Errorf("Invalid output address length: %d", len(out.Address))
}
if !out.Coins.IsValid() {
return errors.Errorf("Invalid output coins %v", out.Coins)
}
if out.Coins.IsZero() {
return errors.New("Output coins cannot be zero")
}
}
return nil
}

View File

@ -18,7 +18,7 @@ import (
coincmd "github.com/tendermint/basecoin/cmd/basecoin/commands"
)
// BaseCli represents the base command when called without any subcommands
// BaseCli - main basecoin client command
var BaseCli = &cobra.Command{
Use: "basecli",
Short: "Light client for tendermint",
@ -34,16 +34,19 @@ func main() {
commands.AddBasicFlags(BaseCli)
// Prepare queries
pr := proofs.RootCmd
// These are default parsers, but optional in your app (you can remove key)
pr.AddCommand(proofs.TxCmd)
pr.AddCommand(proofs.KeyCmd)
pr.AddCommand(bcmd.AccountQueryCmd)
proofs.RootCmd.AddCommand(
// These are default parsers, but optional in your app (you can remove key)
proofs.TxCmd,
proofs.KeyCmd,
bcmd.AccountQueryCmd,
)
// you will always want this for the base send command
proofs.TxPresenters.Register("base", bcmd.BaseTxPresenter{})
tr := txs.RootCmd
tr.AddCommand(bcmd.SendTxCmd)
txs.RootCmd.AddCommand(
// This is the default transaction, optional in your app
bcmd.SendTxCmd,
)
// Set up the various commands to use
BaseCli.AddCommand(
@ -52,8 +55,8 @@ func main() {
keycmd.RootCmd,
seeds.RootCmd,
rpccmd.RootCmd,
pr,
tr,
proofs.RootCmd,
txs.RootCmd,
proxy.RootCmd,
coincmd.VersionCmd,
bcmd.AutoCompleteCmd,

View File

@ -1,8 +1,8 @@
package commands
import "github.com/tendermint/basecoin/plugins/ibc"
// import "github.com/tendermint/basecoin/plugins/ibc"
// returns a new IBC plugin to be registered with Basecoin
func NewIBCPlugin() *ibc.IBCPlugin {
return ibc.New()
}
// // returns a new IBC plugin to be registered with Basecoin
// func NewIBCPlugin() *ibc.IBCPlugin {
// return ibc.New()
// }

View File

@ -7,29 +7,25 @@ import (
"path"
"github.com/spf13/cobra"
"github.com/spf13/viper"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
)
//commands
var (
InitCmd = &cobra.Command{
Use: "init [address]",
Short: "Initialize a basecoin blockchain",
RunE: initCmd,
}
)
// InitCmd - node initialization command
var InitCmd = &cobra.Command{
Use: "init [address]",
Short: "Initialize a basecoin blockchain",
RunE: initCmd,
}
//flags
//nolint - flags
var (
chainIDFlag string
FlagChainID = "chain-id" //TODO group with other flags or remove? is this already a flag here?
)
func init() {
flags := []Flag2Register{
{&chainIDFlag, "chain-id", "test_chain_id", "Chain ID"},
}
RegisterFlags(InitCmd, flags)
InitCmd.Flags().String(FlagChainID, "test_chain_id", "Chain ID")
}
// returns 1 iff it set a file, otherwise 0 (so we can add them)
@ -62,7 +58,7 @@ func initCmd(cmd *cobra.Command, args []string) error {
privValFile := cfg.PrivValidatorFile()
keyFile := path.Join(cfg.RootDir, "key.json")
mod1, err := setupFile(genesisFile, GetGenesisJSON(chainIDFlag, userAddr), 0644)
mod1, err := setupFile(genesisFile, GetGenesisJSON(viper.GetString(FlagChainID), userAddr), 0644)
if err != nil {
return err
}
@ -85,6 +81,7 @@ func initCmd(cmd *cobra.Command, args []string) error {
return nil
}
// PrivValJSON - validator private key file contents in json
var PrivValJSON = `{
"address": "7A956FADD20D3A5B2375042B2959F8AB172A058F",
"last_height": 0,
@ -134,7 +131,7 @@ func GetGenesisJSON(chainID, addr string) string {
}`, chainID, addr)
}
// TODO: remove this once not needed for relay
// KeyJSON - TODO: remove this once not needed for relay
var KeyJSON = `{
"address": "1B1BE55F969F54064628A63B9559E7C21C925165",
"priv_key": {

View File

@ -19,12 +19,15 @@ import (
//---------------------------------------------
// simple implementation of a key
// Address - public address for a key
type Address [20]byte
// MarshalJSON - marshal the json bytes of the address
func (a Address) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%x"`, a[:])), nil
}
// UnmarshalJSON - unmarshal the json bytes of the address
func (a *Address) UnmarshalJSON(addrHex []byte) error {
addr, err := hex.DecodeString(strings.Trim(string(addrHex), `"`))
if err != nil {
@ -34,17 +37,19 @@ func (a *Address) UnmarshalJSON(addrHex []byte) error {
return nil
}
// Key - full private key
type Key struct {
Address Address `json:"address"`
PubKey crypto.PubKey `json:"pub_key"`
PrivKey crypto.PrivKey `json:"priv_key"`
}
// Implements Signer
// Sign - Implements Signer
func (k *Key) Sign(msg []byte) crypto.Signature {
return k.PrivKey.Sign(msg)
}
// LoadKey - load key from json file
func LoadKey(keyFile string) (*Key, error) {
filePath := keyFile
@ -61,7 +66,7 @@ func LoadKey(keyFile string) (*Key, error) {
key := new(Key)
err = json.Unmarshal(keyJSONBytes, key)
if err != nil {
return nil, fmt.Errorf("Error reading key from %v: %v\n", filePath, err) //never stack trace
return nil, fmt.Errorf("Error reading key from %v: %v", filePath, err) //never stack trace
}
return key, nil

View File

@ -14,12 +14,12 @@ type plugin struct {
var plugins = []plugin{}
// RegisterStartPlugin is used to enable a plugin
// RegisterStartPlugin - used to enable a plugin
func RegisterStartPlugin(name string, newPlugin func() types.Plugin) {
plugins = append(plugins, plugin{name: name, newPlugin: newPlugin})
}
//Returns a version command based on version input
// QuickVersionCmd - returns a version command based on version input
func QuickVersionCmd(version string) *cobra.Command {
return &cobra.Command{
Use: "version",

View File

@ -1,298 +1,298 @@
package commands
import (
"fmt"
"io/ioutil"
"strconv"
"time"
// import (
// "fmt"
// "io/ioutil"
// "strconv"
// "time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
// "github.com/pkg/errors"
// "github.com/spf13/cobra"
// "github.com/spf13/viper"
// "github.com/tendermint/tmlibs/cli"
// "github.com/tendermint/tmlibs/log"
// // "github.com/spf13/viper"
// // "github.com/tendermint/tmlibs/cli"
// // "github.com/tendermint/tmlibs/log"
"github.com/tendermint/go-wire"
"github.com/tendermint/merkleeyes/iavl"
cmn "github.com/tendermint/tmlibs/common"
// "github.com/tendermint/go-wire"
// "github.com/tendermint/merkleeyes/iavl"
// cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/basecoin/plugins/ibc"
"github.com/tendermint/basecoin/types"
"github.com/tendermint/tendermint/rpc/client"
tmtypes "github.com/tendermint/tendermint/types"
)
// "github.com/tendermint/basecoin/plugins/ibc"
// "github.com/tendermint/basecoin/types"
// "github.com/tendermint/tendermint/rpc/client"
// tmtypes "github.com/tendermint/tendermint/types"
// )
var RelayCmd = &cobra.Command{
Use: "relay",
Short: "Relay ibc packets between two chains",
}
// var RelayCmd = &cobra.Command{
// Use: "relay",
// Short: "Relay ibc packets between two chains",
// }
var RelayStartCmd = &cobra.Command{
Use: "start",
Short: "Start basecoin relayer to relay IBC packets between chains",
RunE: relayStartCmd,
}
// var RelayStartCmd = &cobra.Command{
// Use: "start",
// Short: "Start basecoin relayer to relay IBC packets between chains",
// RunE: relayStartCmd,
// }
var RelayInitCmd = &cobra.Command{
Use: "init",
Short: "Register both chains with each other, to prepare the relayer to run",
RunE: relayInitCmd,
}
// var RelayInitCmd = &cobra.Command{
// Use: "init",
// Short: "Register both chains with each other, to prepare the relayer to run",
// RunE: relayInitCmd,
// }
//flags
var (
chain1AddrFlag string
chain2AddrFlag string
// //flags
// var (
// chain1AddrFlag string
// chain2AddrFlag string
chain1IDFlag string
chain2IDFlag string
// chain1IDFlag string
// chain2IDFlag string
fromFileFlag string
// fromFileFlag string
genesisFile1Flag string
genesisFile2Flag string
)
// genesisFile1Flag string
// genesisFile2Flag string
// )
func init() {
flags := []Flag2Register{
{&chain1AddrFlag, "chain1-addr", "tcp://localhost:46657", "Node address for chain1"},
{&chain2AddrFlag, "chain2-addr", "tcp://localhost:36657", "Node address for chain2"},
{&chain1IDFlag, "chain1-id", "test_chain_1", "ChainID for chain1"},
{&chain2IDFlag, "chain2-id", "test_chain_2", "ChainID for chain2"},
{&fromFileFlag, "from", "key.json", "Path to a private key to sign the transaction"},
}
RegisterPersistentFlags(RelayCmd, flags)
// func init() {
// flags := []Flag2Register{
// {&chain1AddrFlag, "chain1-addr", "tcp://localhost:46657", "Node address for chain1"},
// {&chain2AddrFlag, "chain2-addr", "tcp://localhost:36657", "Node address for chain2"},
// {&chain1IDFlag, "chain1-id", "test_chain_1", "ChainID for chain1"},
// {&chain2IDFlag, "chain2-id", "test_chain_2", "ChainID for chain2"},
// {&fromFileFlag, "from", "key.json", "Path to a private key to sign the transaction"},
// }
// RegisterPersistentFlags(RelayCmd, flags)
initFlags := []Flag2Register{
{&genesisFile1Flag, "genesis1", "", "Path to genesis file for chain1"},
{&genesisFile2Flag, "genesis2", "", "Path to genesis file for chain2"},
}
RegisterFlags(RelayInitCmd, initFlags)
// initFlags := []Flag2Register{
// {&genesisFile1Flag, "genesis1", "", "Path to genesis file for chain1"},
// {&genesisFile2Flag, "genesis2", "", "Path to genesis file for chain2"},
// }
// RegisterFlags(RelayInitCmd, initFlags)
RelayCmd.AddCommand(RelayStartCmd)
RelayCmd.AddCommand(RelayInitCmd)
}
// RelayCmd.AddCommand(RelayStartCmd)
// RelayCmd.AddCommand(RelayInitCmd)
// }
func relayStartCmd(cmd *cobra.Command, args []string) error {
go loop(chain1AddrFlag, chain2AddrFlag, chain1IDFlag, chain2IDFlag)
go loop(chain2AddrFlag, chain1AddrFlag, chain2IDFlag, chain1IDFlag)
// func relayStartCmd(cmd *cobra.Command, args []string) error {
// go loop(chain1AddrFlag, chain2AddrFlag, chain1IDFlag, chain2IDFlag)
// go loop(chain2AddrFlag, chain1AddrFlag, chain2IDFlag, chain1IDFlag)
cmn.TrapSignal(func() {
// TODO: Cleanup
})
return nil
}
// cmn.TrapSignal(func() {
// // TODO: Cleanup
// })
// return nil
// }
func relayInitCmd(cmd *cobra.Command, args []string) error {
err := registerChain(chain1IDFlag, chain1AddrFlag, chain2IDFlag, genesisFile2Flag, fromFileFlag)
if err != nil {
return err
}
err = registerChain(chain2IDFlag, chain2AddrFlag, chain1IDFlag, genesisFile1Flag, fromFileFlag)
return err
}
// func relayInitCmd(cmd *cobra.Command, args []string) error {
// err := registerChain(chain1IDFlag, chain1AddrFlag, chain2IDFlag, genesisFile2Flag, fromFileFlag)
// if err != nil {
// return err
// }
// err = registerChain(chain2IDFlag, chain2AddrFlag, chain1IDFlag, genesisFile1Flag, fromFileFlag)
// return err
// }
func registerChain(chainID, node, registerChainID, registerGenesis, keyFile string) error {
genesisBytes, err := ioutil.ReadFile(registerGenesis)
if err != nil {
return errors.Errorf("Error reading genesis file %v: %v\n", registerGenesis, err)
}
// func registerChain(chainID, node, registerChainID, registerGenesis, keyFile string) error {
// genesisBytes, err := ioutil.ReadFile(registerGenesis)
// if err != nil {
// return errors.Errorf("Error reading genesis file %v: %v\n", registerGenesis, err)
// }
ibcTx := ibc.IBCRegisterChainTx{
ibc.BlockchainGenesis{
ChainID: registerChainID,
Genesis: string(genesisBytes),
},
}
// ibcTx := ibc.IBCRegisterChainTx{
// ibc.BlockchainGenesis{
// ChainID: registerChainID,
// Genesis: string(genesisBytes),
// },
// }
privKey, err := LoadKey(keyFile)
if err != nil {
return err
}
relay := newRelayer(privKey, chainID, node)
return relay.appTx(ibcTx)
}
// privKey, err := LoadKey(keyFile)
// if err != nil {
// return err
// }
// relay := newRelayer(privKey, chainID, node)
// return relay.appTx(ibcTx)
// }
func loop(addr1, addr2, id1, id2 string) {
nextSeq := 0
// func loop(addr1, addr2, id1, id2 string) {
// nextSeq := 0
// load the priv key
privKey, err := LoadKey(fromFileFlag)
if err != nil {
logger.Error(err.Error())
cmn.PanicCrisis(err.Error())
}
// // load the priv key
// privKey, err := LoadKey(fromFileFlag)
// if err != nil {
// logger.Error(err.Error())
// cmn.PanicCrisis(err.Error())
// }
// relay from chain1 to chain2
thisRelayer := newRelayer(privKey, id2, addr2)
// // relay from chain1 to chain2
// thisRelayer := newRelayer(privKey, id2, addr2)
logger.Info(fmt.Sprintf("Relaying from chain %v on %v to chain %v on %v", id1, addr1, id2, addr2))
// logger.Info(fmt.Sprintf("Relaying from chain %v on %v to chain %v on %v", id1, addr1, id2, addr2))
httpClient := client.NewHTTP(addr1, "/websocket")
// httpClient := client.NewHTTP(addr1, "/websocket")
OUTER:
for {
// OUTER:
// for {
time.Sleep(time.Second)
// time.Sleep(time.Second)
// get the latest ibc packet sequence number
key := fmt.Sprintf("ibc,egress,%v,%v", id1, id2)
query, err := queryWithClient(httpClient, []byte(key))
if err != nil {
logger.Error("Error querying for latest sequence", "key", key, "error", err.Error())
continue OUTER
}
if len(query.Value) == 0 {
// nothing yet
continue OUTER
}
// // get the latest ibc packet sequence number
// key := fmt.Sprintf("ibc,egress,%v,%v", id1, id2)
// query, err := queryWithClient(httpClient, []byte(key))
// if err != nil {
// logger.Error("Error querying for latest sequence", "key", key, "error", err.Error())
// continue OUTER
// }
// if len(query.Value) == 0 {
// // nothing yet
// continue OUTER
// }
seq, err := strconv.ParseUint(string(query.Value), 10, 64)
if err != nil {
logger.Error("Error parsing sequence number from query", "query.Value", query.Value, "error", err.Error())
continue OUTER
}
seq -= 1 // seq is the packet count. -1 because 0-indexed
// seq, err := strconv.ParseUint(string(query.Value), 10, 64)
// if err != nil {
// logger.Error("Error parsing sequence number from query", "query.Value", query.Value, "error", err.Error())
// continue OUTER
// }
// seq -= 1 // seq is the packet count. -1 because 0-indexed
if nextSeq <= int(seq) {
logger.Info("Got new packets", "last-sequence", nextSeq-1, "new-sequence", seq)
}
// if nextSeq <= int(seq) {
// logger.Info("Got new packets", "last-sequence", nextSeq-1, "new-sequence", seq)
// }
// get all packets since the last one we relayed
for ; nextSeq <= int(seq); nextSeq++ {
key := fmt.Sprintf("ibc,egress,%v,%v,%d", id1, id2, nextSeq)
query, err := queryWithClient(httpClient, []byte(key))
if err != nil {
logger.Error("Error querying for packet", "seqeuence", nextSeq, "key", key, "error", err.Error())
continue OUTER
}
// // get all packets since the last one we relayed
// for ; nextSeq <= int(seq); nextSeq++ {
// key := fmt.Sprintf("ibc,egress,%v,%v,%d", id1, id2, nextSeq)
// query, err := queryWithClient(httpClient, []byte(key))
// if err != nil {
// logger.Error("Error querying for packet", "seqeuence", nextSeq, "key", key, "error", err.Error())
// continue OUTER
// }
var packet ibc.Packet
err = wire.ReadBinaryBytes(query.Value, &packet)
if err != nil {
logger.Error("Error unmarshalling packet", "key", key, "query.Value", query.Value, "error", err.Error())
continue OUTER
}
// var packet ibc.Packet
// err = wire.ReadBinaryBytes(query.Value, &packet)
// if err != nil {
// logger.Error("Error unmarshalling packet", "key", key, "query.Value", query.Value, "error", err.Error())
// continue OUTER
// }
proof := new(iavl.IAVLProof)
err = wire.ReadBinaryBytes(query.Proof, &proof)
if err != nil {
logger.Error("Error unmarshalling proof", "query.Proof", query.Proof, "error", err.Error())
continue OUTER
}
// proof := new(iavl.IAVLProof)
// err = wire.ReadBinaryBytes(query.Proof, &proof)
// if err != nil {
// logger.Error("Error unmarshalling proof", "query.Proof", query.Proof, "error", err.Error())
// continue OUTER
// }
// query.Height is actually for the next block,
// so wait a block before we fetch the header & commit
if err := waitForBlock(httpClient); err != nil {
logger.Error("Error waiting for a block", "addr", addr1, "error", err.Error())
continue OUTER
}
// // query.Height is actually for the next block,
// // so wait a block before we fetch the header & commit
// if err := waitForBlock(httpClient); err != nil {
// logger.Error("Error waiting for a block", "addr", addr1, "error", err.Error())
// continue OUTER
// }
// get the header and commit from the height the query was done at
res, err := httpClient.Commit(int(query.Height))
if err != nil {
logger.Error("Error fetching header and commits", "height", query.Height, "error", err.Error())
continue OUTER
}
// // get the header and commit from the height the query was done at
// res, err := httpClient.Commit(int(query.Height))
// if err != nil {
// logger.Error("Error fetching header and commits", "height", query.Height, "error", err.Error())
// continue OUTER
// }
// update the chain state on the other chain
updateTx := ibc.IBCUpdateChainTx{
Header: *res.Header,
Commit: *res.Commit,
}
logger.Info("Updating chain", "src-chain", id1, "height", res.Header.Height, "appHash", res.Header.AppHash)
if err := thisRelayer.appTx(updateTx); err != nil {
logger.Error("Error creating/sending IBCUpdateChainTx", "error", err.Error())
continue OUTER
}
// // update the chain state on the other chain
// updateTx := ibc.IBCUpdateChainTx{
// Header: *res.Header,
// Commit: *res.Commit,
// }
// logger.Info("Updating chain", "src-chain", id1, "height", res.Header.Height, "appHash", res.Header.AppHash)
// if err := thisRelayer.appTx(updateTx); err != nil {
// logger.Error("Error creating/sending IBCUpdateChainTx", "error", err.Error())
// continue OUTER
// }
// relay the packet and proof
logger.Info("Relaying packet", "src-chain", id1, "height", query.Height, "sequence", nextSeq)
postTx := ibc.IBCPacketPostTx{
FromChainID: id1,
FromChainHeight: query.Height,
Packet: packet,
Proof: proof,
}
// // relay the packet and proof
// logger.Info("Relaying packet", "src-chain", id1, "height", query.Height, "sequence", nextSeq)
// postTx := ibc.IBCPacketPostTx{
// FromChainID: id1,
// FromChainHeight: query.Height,
// Packet: packet,
// Proof: proof,
// }
if err := thisRelayer.appTx(postTx); err != nil {
logger.Error("Error creating/sending IBCPacketPostTx", "error", err.Error())
// dont `continue OUTER` here. the error might be eg. Already exists
// TODO: catch this programmatically ?
}
}
}
}
// if err := thisRelayer.appTx(postTx); err != nil {
// logger.Error("Error creating/sending IBCPacketPostTx", "error", err.Error())
// // dont `continue OUTER` here. the error might be eg. Already exists
// // TODO: catch this programmatically ?
// }
// }
// }
// }
type relayer struct {
privKey *Key
chainID string
nodeAddr string
client *client.HTTP
}
// type relayer struct {
// privKey *Key
// chainID string
// nodeAddr string
// client *client.HTTP
// }
func newRelayer(privKey *Key, chainID, nodeAddr string) *relayer {
httpClient := client.NewHTTP(nodeAddr, "/websocket")
return &relayer{
privKey: privKey,
chainID: chainID,
nodeAddr: nodeAddr,
client: httpClient,
}
}
// func newRelayer(privKey *Key, chainID, nodeAddr string) *relayer {
// httpClient := client.NewHTTP(nodeAddr, "/websocket")
// return &relayer{
// privKey: privKey,
// chainID: chainID,
// nodeAddr: nodeAddr,
// client: httpClient,
// }
// }
func (r *relayer) appTx(ibcTx ibc.IBCTx) error {
acc, err := getAccWithClient(r.client, r.privKey.Address[:])
if err != nil {
return err
}
sequence := acc.Sequence + 1
// func (r *relayer) appTx(ibcTx ibc.IBCTx) error {
// acc, err := getAccWithClient(r.client, r.privKey.Address[:])
// if err != nil {
// return err
// }
// sequence := acc.Sequence + 1
data := []byte(wire.BinaryBytes(struct {
ibc.IBCTx `json:"unwrap"`
}{ibcTx}))
// data := []byte(wire.BinaryBytes(struct {
// ibc.IBCTx `json:"unwrap"`
// }{ibcTx}))
smallCoins := types.Coin{"mycoin", 1}
// smallCoins := types.Coin{"mycoin", 1}
input := types.NewTxInput(r.privKey.PubKey, types.Coins{smallCoins}, sequence)
tx := &types.AppTx{
Gas: 0,
Fee: smallCoins,
Name: "IBC",
Input: input,
Data: data,
}
// input := types.NewTxInput(r.privKey.PubKey, types.Coins{smallCoins}, sequence)
// tx := &types.AppTx{
// Gas: 0,
// Fee: smallCoins,
// Name: "IBC",
// Input: input,
// Data: data,
// }
tx.Input.Signature = r.privKey.Sign(tx.SignBytes(r.chainID))
txBytes := []byte(wire.BinaryBytes(struct {
types.Tx `json:"unwrap"`
}{tx}))
// tx.Input.Signature = r.privKey.Sign(tx.SignBytes(r.chainID))
// txBytes := []byte(wire.BinaryBytes(struct {
// types.Tx `json:"unwrap"`
// }{tx}))
data, log, err := broadcastTxWithClient(r.client, txBytes)
if err != nil {
return err
}
_, _ = data, log
return nil
}
// data, log, err := broadcastTxWithClient(r.client, txBytes)
// if err != nil {
// return err
// }
// _, _ = data, log
// return nil
// }
// broadcast the transaction to tendermint
func broadcastTxWithClient(httpClient *client.HTTP, tx tmtypes.Tx) ([]byte, string, error) {
res, err := httpClient.BroadcastTxCommit(tx)
if err != nil {
return nil, "", errors.Errorf("Error on broadcast tx: %v", err)
}
// // broadcast the transaction to tendermint
// func broadcastTxWithClient(httpClient *client.HTTP, tx tmtypes.Tx) ([]byte, string, error) {
// res, err := httpClient.BroadcastTxCommit(tx)
// if err != nil {
// return nil, "", errors.Errorf("Error on broadcast tx: %v", err)
// }
if !res.CheckTx.Code.IsOK() {
r := res.CheckTx
return nil, "", errors.Errorf("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log)
}
// if !res.CheckTx.Code.IsOK() {
// r := res.CheckTx
// return nil, "", errors.Errorf("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log)
// }
if !res.DeliverTx.Code.IsOK() {
r := res.DeliverTx
return nil, "", errors.Errorf("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log)
}
// if !res.DeliverTx.Code.IsOK() {
// r := res.DeliverTx
// return nil, "", errors.Errorf("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log)
// }
return res.DeliverTx.Data, res.DeliverTx.Log, nil
}
// return res.DeliverTx.Data, res.DeliverTx.Log, nil
// }

View File

@ -6,6 +6,7 @@ import (
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
)
// UnsafeResetAllCmd - extension of the tendermint command, resets initialization
var UnsafeResetAllCmd = &cobra.Command{
Use: "unsafe_reset_all",
Short: "Reset all blockchain data",

View File

@ -11,6 +11,7 @@ import (
"github.com/tendermint/tmlibs/log"
)
//nolint
const (
defaultLogLevel = "error"
FlagLogLevel = "log_level"
@ -20,6 +21,7 @@ var (
logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "main")
)
// RootCmd - main node command
var RootCmd = &cobra.Command{
Use: "basecoin",
Short: "A cryptocurrency framework in Golang based on Tendermint-Core",

View File

@ -10,6 +10,7 @@ import (
"github.com/spf13/viper"
"github.com/tendermint/abci/server"
"github.com/tendermint/basecoin"
eyesApp "github.com/tendermint/merkleeyes/app"
eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/cli"
@ -23,13 +24,14 @@ import (
"github.com/tendermint/basecoin/app"
)
// StartCmd - command to start running the basecoin node!
var StartCmd = &cobra.Command{
Use: "start",
Short: "Start basecoin",
RunE: startCmd,
}
// TODO: move to config file
// nolint TODO: move to config file
const EyesCacheSize = 10000
//nolint
@ -39,6 +41,12 @@ const (
FlagWithoutTendermint = "without-tendermint"
)
var (
// Handler - use a global to store the handler, so we can set it in main.
// TODO: figure out a cleaner way to register plugins
Handler basecoin.Handler
)
func init() {
flags := StartCmd.Flags()
flags.String(FlagAddress, "tcp://0.0.0.0:46658", "Listen address")
@ -66,16 +74,7 @@ func startCmd(cmd *cobra.Command, args []string) error {
}
// Create Basecoin app
basecoinApp := app.NewBasecoin(eyesCli)
basecoinApp.SetLogger(logger.With("module", "app"))
// register IBC plugn
basecoinApp.RegisterPlugin(NewIBCPlugin())
// register all other plugins
for _, p := range plugins {
basecoinApp.RegisterPlugin(p.newPlugin())
}
basecoinApp := app.NewBasecoin(Handler, eyesCli, logger.With("module", "app"))
// if chain_id has not been set yet, load the genesis.
// else, assume it's been loaded
@ -97,11 +96,10 @@ func startCmd(cmd *cobra.Command, args []string) error {
logger.Info("Starting Basecoin without Tendermint", "chain_id", chainID)
// run just the abci app/server
return startBasecoinABCI(basecoinApp)
} else {
logger.Info("Starting Basecoin with Tendermint", "chain_id", chainID)
// start the app with tendermint in-process
return startTendermint(rootDir, basecoinApp)
}
logger.Info("Starting Basecoin with Tendermint", "chain_id", chainID)
// start the app with tendermint in-process
return startTendermint(rootDir, basecoinApp)
}
func startBasecoinABCI(basecoinApp *app.Basecoin) error {

View File

@ -5,8 +5,6 @@ import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
abci "github.com/tendermint/abci/types"
wire "github.com/tendermint/go-wire"
@ -17,68 +15,6 @@ import (
tmtypes "github.com/tendermint/tendermint/types"
)
//Quickly registering flags can be quickly achieved through using the utility functions
//RegisterFlags, and RegisterPersistentFlags. Ex:
// flags := []Flag2Register{
// {&myStringFlag, "mystringflag", "foobar", "description of what this flag does"},
// {&myBoolFlag, "myboolflag", false, "description of what this flag does"},
// {&myInt64Flag, "myintflag", 333, "description of what this flag does"},
// }
// RegisterFlags(MyCobraCmd, flags)
type Flag2Register struct {
Pointer interface{}
Use string
Value interface{}
Desc string
}
//register flag utils
func RegisterFlags(c *cobra.Command, flags []Flag2Register) {
registerFlags(c, flags, false)
}
func RegisterPersistentFlags(c *cobra.Command, flags []Flag2Register) {
registerFlags(c, flags, true)
}
func registerFlags(c *cobra.Command, flags []Flag2Register, persistent bool) {
var flagset *pflag.FlagSet
if persistent {
flagset = c.PersistentFlags()
} else {
flagset = c.Flags()
}
for _, f := range flags {
ok := false
switch f.Value.(type) {
case string:
if _, ok = f.Pointer.(*string); ok {
flagset.StringVar(f.Pointer.(*string), f.Use, f.Value.(string), f.Desc)
}
case int:
if _, ok = f.Pointer.(*int); ok {
flagset.IntVar(f.Pointer.(*int), f.Use, f.Value.(int), f.Desc)
}
case uint64:
if _, ok = f.Pointer.(*uint64); ok {
flagset.Uint64Var(f.Pointer.(*uint64), f.Use, f.Value.(uint64), f.Desc)
}
case bool:
if _, ok = f.Pointer.(*bool); ok {
flagset.BoolVar(f.Pointer.(*bool), f.Use, f.Value.(bool), f.Desc)
}
}
if !ok {
panic("improper use of RegisterFlags")
}
}
}
// Returns true for non-empty hex-string prefixed with "0x"
func isHex(s string) bool {
if len(s) > 2 && s[:2] == "0x" {
@ -91,6 +27,7 @@ func isHex(s string) bool {
return false
}
// StripHex remove the first two hex bytes
func StripHex(s string) string {
if isHex(s) {
return s[2:]
@ -98,6 +35,7 @@ func StripHex(s string) string {
return s
}
// Query - send an abci query
func Query(tmAddr string, key []byte) (*abci.ResultQuery, error) {
httpClient := client.NewHTTP(tmAddr, "/websocket")
return queryWithClient(httpClient, key)

View File

@ -8,6 +8,7 @@ import (
"github.com/tendermint/basecoin/version"
)
// VersionCmd - command to show the application version
var VersionCmd = &cobra.Command{
Use: "version",
Short: "Show version info",

View File

@ -3,6 +3,7 @@ package main
import (
"os"
"github.com/tendermint/basecoin/app"
"github.com/tendermint/basecoin/cmd/basecoin/commands"
"github.com/tendermint/tmlibs/cli"
)
@ -10,16 +11,16 @@ import (
func main() {
rt := commands.RootCmd
commands.Handler = app.DefaultHandler()
rt.AddCommand(
commands.InitCmd,
commands.StartCmd,
commands.RelayCmd,
//commands.RelayCmd,
commands.UnsafeResetAllCmd,
commands.VersionCmd,
)
cmd := cli.PrepareMainCmd(rt, "BC", os.ExpandEnv("$HOME/.basecoin"))
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
cmd.Execute()
}

View File

@ -12,9 +12,9 @@ import (
// It doesn't just have to be a pubkey on this chain, it could stem from
// another app (like multi-sig account), or even another chain (via IBC)
type Actor struct {
ChainID string // this is empty unless it comes from a different chain
App string // the app that the actor belongs to
Address data.Bytes // arbitrary app-specific unique id
ChainID string `json:"chain"` // this is empty unless it comes from a different chain
App string `json:"app"` // the app that the actor belongs to
Address data.Bytes `json:"addr"` // arbitrary app-specific unique id
}
func NewActor(app string, addr []byte) Actor {
@ -32,6 +32,8 @@ type Context interface {
log.Logger
WithPermissions(perms ...Actor) Context
HasPermission(perm Actor) bool
GetPermissions(chain, app string) []Actor
IsParent(ctx Context) bool
Reset() Context
ChainID() string
}

View File

@ -2,47 +2,47 @@
#!/bin/bash
testTutorial_BasecoinBasics() {
#shelldown[1][3] >/dev/null
#shelldown[1][4] >/dev/null
#shelldown[1][3] >/dev/null
#shelldown[1][4] >/dev/null
KEYPASS=qwertyuiop
RES=$((echo $KEYPASS; echo $KEYPASS) | #shelldown[1][6])
assertTrue "Line $LINENO: Expected to contain safe, got $RES" '[[ $RES == *safe* ]]'
RES=$((echo $KEYPASS; echo $KEYPASS) | #shelldown[1][7])
assertTrue "Line $LINENO: Expected to contain safe, got $RES" '[[ $RES == *safe* ]]'
#shelldown[3][-1]
assertTrue "Expected true for line $LINENO" $?
#shelldown[4][-1] >>/dev/null 2>&1 &
sleep 5
PID_SERVER=$!
disown
RES=$((echo y) | #shelldown[5][-1] $1)
assertTrue "Line $LINENO: Expected to contain validator, got $RES" '[[ $RES == *validator* ]]'
#shelldown[6][0]
#shelldown[6][1]
RES=$(#shelldown[6][2] | jq '.data.coins[0].denom' | tr -d '"')
assertTrue "Line $LINENO: Expected to have mycoins, got $RES" '[[ $RES == mycoin ]]'
RES="$(#shelldown[6][3] 2>&1)"
assertTrue "Line $LINENO: Expected to contain ERROR, got $RES" '[[ $RES == *ERROR* ]]'
RES=$((echo $KEYPASS) | #shelldown[7][-1] | jq '.deliver_tx.code')
assertTrue "Line $LINENO: Expected 0 code deliver_tx, got $RES" '[[ $RES == 0 ]]'
RES=$(#shelldown[8][-1] | jq '.data.coins[0].amount')
assertTrue "Line $LINENO: Expected to contain 1000 mycoin, got $RES" '[[ $RES == 1000 ]]'
RES=$((echo $KEYPASS) | #shelldown[9][-1] | jq '.deliver_tx.code')
assertTrue "Line $LINENO: Expected 0 code deliver_tx, got $RES" '[[ $RES == 0 ]]'
RES=$((echo $KEYPASS) | #shelldown[10][-1])
assertTrue "Line $LINENO: Expected to contain insufficient funds error, got $RES" \
'[[ $RES == *"insufficient funds"* ]]'
'[[ $RES == *"Insufficient Funds"* ]]'
#perform a substitution within the final tests
HASH=$((echo $KEYPASS) | #shelldown[11][-1] | jq '.hash' | tr -d '"')
PRESUB="#shelldown[12][-1]"

View File

@ -2,49 +2,49 @@
#!/bin/bash
testTutorial_BasecoinPlugins() {
#Initialization
#shelldown[0][1]
#shelldown[0][2]
KEYPASS=qwertyuiop
#Making Keys
#Making Keys
RES=$((echo $KEYPASS; echo $KEYPASS) | #shelldown[0][4])
assertTrue "Line $LINENO: Expected to contain safe, got $RES" '[[ $RES == *safe* ]]'
RES=$((echo $KEYPASS; echo $KEYPASS) | #shelldown[0][5])
assertTrue "Line $LINENO: Expected to contain safe, got $RES" '[[ $RES == *safe* ]]'
#shelldown[0][7] >/dev/null
assertTrue "Expected true for line $LINENO" $?
#shelldown[0][9] >>/dev/null 2>&1 &
sleep 5
PID_SERVER=$!
disown
RES=$((echo y) | #shelldown[1][0] $1)
assertTrue "Line $LINENO: Expected to contain validator, got $RES" '[[ $RES == *validator* ]]'
#shelldown[1][2]
assertTrue "Expected true for line $LINENO" $?
RES=$((echo $KEYPASS) | #shelldown[1][3] | jq '.deliver_tx.code')
assertTrue "Line $LINENO: Expected 0 code deliver_tx, got $RES" '[[ $RES == 0 ]]'
RES=$((echo $KEYPASS) | #shelldown[2][0])
assertTrue "Line $LINENO: Expected to contain Valid error, got $RES" \
'[[ $RES == *"Valid must be true"* ]]'
'[[ $RES == *"Counter Tx marked invalid"* ]]'
RES=$((echo $KEYPASS) | #shelldown[2][1] | jq '.deliver_tx.code')
assertTrue "Line $LINENO: Expected 0 code deliver_tx, got $RES" '[[ $RES == 0 ]]'
RES=$(#shelldown[3][-1] | jq '.data.Counter')
RES=$(#shelldown[3][-1] | jq '.data.counter')
assertTrue "Line $LINENO: Expected Counter of 1, got $RES" '[[ $RES == 1 ]]'
RES=$((echo $KEYPASS) | #shelldown[4][0] | jq '.deliver_tx.code')
assertTrue "Line $LINENO: Expected 0 code deliver_tx, got $RES" '[[ $RES == 0 ]]'
RES=$(#shelldown[4][1])
RESCOUNT=$(printf "$RES" | jq '.data.Counter')
RESFEE=$(printf "$RES" | jq '.data.TotalFees[0].amount')
RESCOUNT=$(printf "$RES" | jq '.data.counter')
RESFEE=$(printf "$RES" | jq '.data.total_fees[0].amount')
assertTrue "Line $LINENO: Expected Counter of 2, got $RES" '[[ $RESCOUNT == 2 ]]'
assertTrue "Line $LINENO: Expected TotalFees of 2, got $RES" '[[ $RESFEE == 2 ]]'
}
@ -113,8 +113,8 @@ But the Counter has an additional command, `countercli tx counter`, which
crafts an `AppTx` specifically for this plugin:
```shelldown[2]
countercli tx counter --name cool --amount=1mycoin --sequence=2
countercli tx counter --name cool --amount=1mycoin --sequence=3 --valid
countercli tx counter --name cool
countercli tx counter --name cool --valid
```
The first transaction is rejected by the plugin because it was not marked as
@ -129,10 +129,11 @@ countercli query counter
Tada! We can now see that our custom counter plugin tx went through. You
should see a Counter value of 1 representing the number of valid transactions.
If we send another transaction, and then query again, we will see the value
increment:
increment. Note that we need the sequence number here to send the coins
(it didn't increment when we just pinged the counter)
```shelldown[4]
countercli tx counter --name cool --amount=2mycoin --sequence=4 --valid --countfee=2mycoin
countercli tx counter --name cool --countfee=2mycoin --sequence=2 --valid
countercli query counter
```

View File

@ -9,7 +9,6 @@ import (
"github.com/tendermint/basecoin/cmd/basecoin/commands"
"github.com/tendermint/basecoin/docs/guide/counter/plugins/counter"
"github.com/tendermint/basecoin/types"
)
func main() {
@ -18,6 +17,9 @@ func main() {
Short: "demo plugin for basecoin",
}
// TODO: register the counter here
commands.Handler = counter.NewHandler()
RootCmd.AddCommand(
commands.InitCmd,
commands.StartCmd,
@ -25,7 +27,6 @@ func main() {
commands.VersionCmd,
)
commands.RegisterStartPlugin("counter", func() types.Plugin { return counter.New() })
cmd := cli.PrepareMainCmd(RootCmd, "CT", os.ExpandEnv("$HOME/.counter"))
cmd.Execute()
}

View File

@ -4,11 +4,12 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/basecoin"
"github.com/tendermint/light-client/commands"
txcmd "github.com/tendermint/light-client/commands/txs"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
"github.com/tendermint/basecoin/docs/guide/counter/plugins/counter"
"github.com/tendermint/basecoin/txs"
btypes "github.com/tendermint/basecoin/types"
)
@ -20,64 +21,60 @@ var CounterTxCmd = &cobra.Command{
Long: `Add a vote to the counter.
You must pass --valid for it to count and the countfee will be added to the counter.`,
RunE: counterTxCmd,
RunE: counterTx,
}
// nolint - flags names
const (
flagCountFee = "countfee"
flagValid = "valid"
FlagCountFee = "countfee"
FlagValid = "valid"
FlagSequence = "sequence" // FIXME: currently not supported...
)
func init() {
fs := CounterTxCmd.Flags()
bcmd.AddAppTxFlags(fs)
fs.String(flagCountFee, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
fs.Bool(flagValid, false, "Is count valid?")
fs.String(FlagCountFee, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
fs.Bool(FlagValid, false, "Is count valid?")
fs.Int(FlagSequence, -1, "Sequence number for this transaction")
}
func counterTxCmd(cmd *cobra.Command, args []string) error {
// Note: we don't support loading apptx from json currently, so skip that
// Read the app-specific flags
name, data, err := getAppData()
// TODO: counterTx is very similar to the sendtx one,
// maybe we can pull out some common patterns?
func counterTx(cmd *cobra.Command, args []string) error {
// load data from json or flags
var tx basecoin.Tx
found, err := txcmd.LoadJSON(&tx)
if err != nil {
return err
}
if !found {
tx, err = readCounterTxFlags()
}
if err != nil {
return err
}
// Read the standard app-tx flags
gas, fee, txInput, err := bcmd.ReadAppTxFlags()
if err != nil {
return err
}
// TODO: make this more flexible for middleware
// add the chain info
tx = txs.NewChain(commands.GetChainID(), tx)
stx := txs.NewSig(tx)
// Create AppTx and broadcast
tx := &btypes.AppTx{
Gas: gas,
Fee: fee,
Name: name,
Input: txInput,
Data: data,
}
res, err := bcmd.BroadcastAppTx(tx)
// Sign if needed and post. This it the work-horse
bres, err := txcmd.SignAndPostTx(stx)
if err != nil {
return err
}
// Output result
return txcmd.OutputTx(res)
return txcmd.OutputTx(bres)
}
func getAppData() (name string, data []byte, err error) {
countFee, err := btypes.ParseCoins(viper.GetString(flagCountFee))
func readCounterTxFlags() (tx basecoin.Tx, err error) {
feeCoins, err := btypes.ParseCoins(viper.GetString(FlagCountFee))
if err != nil {
return
}
ctx := counter.CounterTx{
Valid: viper.GetBool(flagValid),
Fee: countFee,
return tx, err
}
name = counter.New().Name()
data = wire.BinaryBytes(ctx)
return
tx = counter.NewTx(viper.GetBool(FlagValid), feeCoins, viper.GetInt(FlagSequence))
return tx, nil
}

View File

@ -8,7 +8,7 @@ import (
"github.com/tendermint/basecoin/docs/guide/counter/plugins/counter"
)
//CounterQueryCmd CLI command to query the counter state
//CounterQueryCmd - CLI command to query the counter state
var CounterQueryCmd = &cobra.Command{
Use: "counter",
Short: "Query counter state, with proof",
@ -16,9 +16,9 @@ var CounterQueryCmd = &cobra.Command{
}
func counterQueryCmd(cmd *cobra.Command, args []string) error {
key := counter.New().StateKey()
key := counter.StateKey()
var cp counter.CounterPluginState
var cp counter.State
proof, err := proofcmd.GetAndParseAppProof(key, &cp)
if err != nil {
return err

View File

@ -4,95 +4,208 @@ import (
"fmt"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/types"
"github.com/tendermint/go-wire"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/types"
)
type CounterPluginState struct {
Counter int
TotalFees types.Coins
}
type CounterTx struct {
Valid bool
Fee types.Coins
}
// Tx
//--------------------------------------------------------------------------------
type CounterPlugin struct {
// register the tx type with it's validation logic
// make sure to use the name of the handler as the prefix in the tx type,
// so it gets routed properly
const (
NameCounter = "cntr"
ByteTx = 0x21 //TODO What does this byte represent should use typebytes probably
TypeTx = NameCounter + "/count"
)
func init() {
basecoin.TxMapper.RegisterImplementation(Tx{}, TypeTx, ByteTx)
}
func (cp *CounterPlugin) Name() string {
return "counter"
// Tx - struct for all counter transactions
type Tx struct {
Valid bool `json:"valid"`
Fee types.Coins `json:"fee"`
Sequence int `json:"sequence"`
}
func (cp *CounterPlugin) StateKey() []byte {
return []byte(fmt.Sprintf("CounterPlugin.State"))
// NewTx - return a new counter transaction struct wrapped as a basecoin transaction
func NewTx(valid bool, fee types.Coins, sequence int) basecoin.Tx {
return Tx{
Valid: valid,
Fee: fee,
Sequence: sequence,
}.Wrap()
}
func New() *CounterPlugin {
return &CounterPlugin{}
// Wrap - Wrap a Tx as a Basecoin Tx, used to satisfy the XXX interface
func (c Tx) Wrap() basecoin.Tx {
return basecoin.Tx{TxInner: c}
}
func (cp *CounterPlugin) SetOption(store types.KVStore, key, value string) (log string) {
return ""
// ValidateBasic just makes sure the Fee is a valid, non-negative value
func (c Tx) ValidateBasic() error {
if !c.Fee.IsValid() {
return coin.ErrInvalidCoins()
}
if !c.Fee.IsNonnegative() {
return coin.ErrInvalidCoins()
}
return nil
}
func (cp *CounterPlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) {
// Decode tx
var tx CounterTx
err := wire.ReadBinaryBytes(txBytes, &tx)
// Custom errors
//--------------------------------------------------------------------------------
var (
errInvalidCounter = fmt.Errorf("Counter Tx marked invalid")
)
// ErrInvalidCounter - custom error class
func ErrInvalidCounter() error {
return errors.WithCode(errInvalidCounter, abci.CodeType_BaseInvalidInput)
}
// IsInvalidCounterErr - custom error class check
func IsInvalidCounterErr(err error) bool {
return errors.IsSameError(errInvalidCounter, err)
}
// ErrDecoding - This is just a helper function to return a generic "internal error"
func ErrDecoding() error {
return errors.ErrInternal("Error decoding state")
}
// Counter Handler
//--------------------------------------------------------------------------------
// NewHandler returns a new counter transaction processing handler
func NewHandler() basecoin.Handler {
// use the default stack
coin := coin.NewHandler()
counter := Handler{}
dispatcher := stack.NewDispatcher(
stack.WrapHandler(coin),
counter,
)
return stack.NewDefault().Use(dispatcher)
}
// Handler the counter transaction processing handler
type Handler struct {
stack.NopOption
}
var _ stack.Dispatchable = Handler{}
// Name - return counter namespace
func (Handler) Name() string {
return NameCounter
}
// AssertDispatcher - placeholder to satisfy XXX
func (Handler) AssertDispatcher() {}
// CheckTx checks if the tx is properly structured
func (h Handler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, _ basecoin.Checker) (res basecoin.Result, err error) {
_, err = checkTx(ctx, tx)
return
}
// DeliverTx executes the tx if valid
func (h Handler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, dispatch basecoin.Deliver) (res basecoin.Result, err error) {
ctr, err := checkTx(ctx, tx)
if err != nil {
return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()).PrependLog("CounterTx Error: ")
return res, err
}
// note that we don't assert this on CheckTx (ValidateBasic),
// as we allow them to be writen to the chain
if !ctr.Valid {
return res, ErrInvalidCounter()
}
// Validate tx
if !tx.Valid {
return abci.ErrInternalError.AppendLog("CounterTx.Valid must be true")
}
if !tx.Fee.IsValid() {
return abci.ErrInternalError.AppendLog("CounterTx.Fee is not sorted or has zero amounts")
}
if !tx.Fee.IsNonnegative() {
return abci.ErrInternalError.AppendLog("CounterTx.Fee must be nonnegative")
}
// Did the caller provide enough coins?
if !ctx.Coins.IsGTE(tx.Fee) {
return abci.ErrInsufficientFunds.AppendLog("CounterTx.Fee was not provided")
}
// TODO If there are any funds left over, return funds.
// e.g. !ctx.Coins.Minus(tx.Fee).IsZero()
// ctx.CallerAccount is synced w/ store, so just modify that and store it.
// Load CounterPluginState
var cpState CounterPluginState
cpStateBytes := store.Get(cp.StateKey())
if len(cpStateBytes) > 0 {
err = wire.ReadBinaryBytes(cpStateBytes, &cpState)
// handle coin movement.... like, actually decrement the other account
if !ctr.Fee.IsZero() {
// take the coins and put them in out account!
senders := ctx.GetPermissions("", stack.NameSigs)
if len(senders) == 0 {
return res, errors.ErrMissingSignature()
}
in := []coin.TxInput{{Address: senders[0], Coins: ctr.Fee, Sequence: ctr.Sequence}}
out := []coin.TxOutput{{Address: StoreActor(), Coins: ctr.Fee}}
send := coin.NewSendTx(in, out)
// if the deduction fails (too high), abort the command
_, err = dispatch.DeliverTx(ctx, store, send)
if err != nil {
return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error())
return res, err
}
}
// Update CounterPluginState
cpState.Counter += 1
cpState.TotalFees = cpState.TotalFees.Plus(tx.Fee)
// update the counter
state, err := LoadState(store)
if err != nil {
return res, err
}
state.Counter++
state.TotalFees = state.TotalFees.Plus(ctr.Fee)
err = SaveState(store, state)
// Save CounterPluginState
store.Set(cp.StateKey(), wire.BinaryBytes(cpState))
return abci.OK
return res, err
}
func (cp *CounterPlugin) InitChain(store types.KVStore, vals []*abci.Validator) {
func checkTx(ctx basecoin.Context, tx basecoin.Tx) (ctr Tx, err error) {
ctr, ok := tx.Unwrap().(Tx)
if !ok {
return ctr, errors.ErrInvalidFormat(tx)
}
err = ctr.ValidateBasic()
if err != nil {
return ctr, err
}
return ctr, nil
}
func (cp *CounterPlugin) BeginBlock(store types.KVStore, hash []byte, header *abci.Header) {
// CounterStore
//--------------------------------------------------------------------------------
// StoreActor - return the basecoin actor for the account
func StoreActor() basecoin.Actor {
return basecoin.Actor{App: NameCounter, Address: []byte{0x04, 0x20}} //XXX what do these bytes represent? - should use typebyte variables
}
func (cp *CounterPlugin) EndBlock(store types.KVStore, height uint64) (res abci.ResponseEndBlock) {
return
// State - state of the counter applicaton
type State struct {
Counter int `json:"counter"`
TotalFees types.Coins `json:"total_fees"`
}
// StateKey - store key for the counter state
func StateKey() []byte {
return []byte(NameCounter + "/state")
}
// LoadState - retrieve the counter state from the store
func LoadState(store types.KVStore) (state State, err error) {
bytes := store.Get(StateKey())
if len(bytes) > 0 {
err = wire.ReadBinaryBytes(bytes, &state)
if err != nil {
return state, errors.ErrDecoding()
}
}
return state, nil
}
// SaveState - save the counter state to the provided store
func SaveState(store types.KVStore, state State) error {
bytes := wire.BinaryBytes(state)
store.Set(StateKey(), bytes)
return nil
}

View File

@ -2,15 +2,18 @@ package counter
import (
"encoding/json"
"os"
"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/txs"
"github.com/tendermint/basecoin/types"
"github.com/tendermint/go-wire"
eyescli "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/log"
)
func TestCounterPlugin(t *testing.T) {
@ -19,13 +22,16 @@ func TestCounterPlugin(t *testing.T) {
// Basecoin initialization
eyesCli := eyescli.NewLocalClient("", 0)
chainID := "test_chain_id"
bcApp := app.NewBasecoin(eyesCli)
bcApp.SetOption("base/chain_id", chainID)
// t.Log(bcApp.Info())
// Add Counter plugin
counterPlugin := New()
bcApp.RegisterPlugin(counterPlugin)
// logger := log.TestingLogger().With("module", "app"),
logger := log.NewTMLogger(os.Stdout).With("module", "app")
// logger = log.NewTracingLogger(logger)
bcApp := app.NewBasecoin(
NewHandler(),
eyesCli,
logger,
)
bcApp.SetOption("base/chain_id", chainID)
// Account initialization
test1PrivAcc := types.PrivAccountFromSecret("test1")
@ -35,68 +41,32 @@ func TestCounterPlugin(t *testing.T) {
test1Acc.Balance = types.Coins{{"", 1000}, {"gold", 1000}}
accOpt, err := json.Marshal(test1Acc)
require.Nil(t, err)
bcApp.SetOption("base/account", string(accOpt))
log := bcApp.SetOption("coin/account", string(accOpt))
require.Equal(t, "Success", log)
// Deliver a CounterTx
DeliverCounterTx := func(gas int64, fee types.Coin, inputCoins types.Coins, inputSequence int, appFee types.Coins) abci.Result {
// Construct an AppTx signature
tx := &types.AppTx{
Gas: gas,
Fee: fee,
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)
tx.Input.Signature = test1PrivAcc.Sign(signBytes)
// t.Logf("Signed TX bytes: %X\n", wire.BinaryBytes(struct{ types.Tx }{tx}))
// Write request
txBytes := wire.BinaryBytes(struct{ types.Tx }{tx})
DeliverCounterTx := func(valid bool, counterFee types.Coins, inputSequence int) abci.Result {
tx := NewTx(valid, counterFee, inputSequence)
tx = txs.NewChain(chainID, tx)
stx := txs.NewSig(tx)
txs.Sign(stx, test1PrivAcc.PrivKey)
txBytes := wire.BinaryBytes(stx.Wrap())
return bcApp.DeliverTx(txBytes)
}
// REF: DeliverCounterTx(gas, fee, inputCoins, inputSequence, appFee) {
// Test a basic send, no fee
res := DeliverCounterTx(0, types.Coin{}, types.Coins{{"", 1}}, 1, types.Coins{})
// Test a basic send, no fee (doesn't update sequence as no money spent)
res := DeliverCounterTx(true, nil, 1)
assert.True(res.IsOK(), res.String())
// Test fee prevented transaction
res = DeliverCounterTx(0, types.Coin{"", 2}, types.Coins{{"", 1}}, 2, types.Coins{})
// Test an invalid send, no fee
res = DeliverCounterTx(false, nil, 1)
assert.True(res.IsErr(), res.String())
// Test input equals fee
res = DeliverCounterTx(0, types.Coin{"", 2}, types.Coins{{"", 2}}, 2, types.Coins{})
// Test the fee (increments sequence)
res = DeliverCounterTx(true, types.Coins{{"gold", 100}}, 1)
assert.True(res.IsOK(), res.String())
// Test more input than fee
res = DeliverCounterTx(0, types.Coin{"", 2}, types.Coins{{"", 3}}, 3, types.Coins{})
assert.True(res.IsOK(), res.String())
// Test input equals fee+appFee
res = DeliverCounterTx(0, types.Coin{"", 1}, types.Coins{{"", 3}, {"gold", 1}}, 4, types.Coins{{"", 2}, {"gold", 1}})
assert.True(res.IsOK(), res.String())
// Test fee+appFee prevented transaction, not enough ""
res = DeliverCounterTx(0, types.Coin{"", 1}, types.Coins{{"", 2}, {"gold", 1}}, 5, types.Coins{{"", 2}, {"gold", 1}})
// Test unsupported fee
res = DeliverCounterTx(true, types.Coins{{"silver", 100}}, 2)
assert.True(res.IsErr(), res.String())
// Test fee+appFee prevented transaction, not enough "gold"
res = DeliverCounterTx(0, types.Coin{"", 1}, types.Coins{{"", 3}, {"gold", 1}}, 5, types.Coins{{"", 2}, {"gold", 2}})
assert.True(res.IsErr(), res.String())
// Test more input than fee, more ""
res = DeliverCounterTx(0, types.Coin{"", 1}, types.Coins{{"", 4}, {"gold", 1}}, 6, types.Coins{{"", 2}, {"gold", 1}})
assert.True(res.IsOK(), res.String())
// Test more input than fee, more "gold"
res = DeliverCounterTx(0, types.Coin{"", 1}, types.Coins{{"", 3}, {"gold", 2}}, 7, types.Coins{{"", 2}, {"gold", 1}})
assert.True(res.IsOK(), res.String())
// REF: DeliverCounterTx(gas, fee, inputCoins, inputSequence, appFee) {w
}

View File

@ -1,11 +1,7 @@
//nolint
package errors
/**
* Copyright (C) 2017 Ethan Frey
**/
import (
rawerr "errors"
"fmt"
"github.com/pkg/errors"
@ -14,16 +10,17 @@ import (
)
var (
errDecoding = rawerr.New("Error decoding input")
errUnauthorized = rawerr.New("Unauthorized")
errInvalidSignature = rawerr.New("Invalid Signature")
errTooLarge = rawerr.New("Input size too large")
errMissingSignature = rawerr.New("Signature missing")
errTooManySignatures = rawerr.New("Too many signatures")
errNoChain = rawerr.New("No chain id provided")
errWrongChain = rawerr.New("Wrong chain for tx")
errUnknownTxType = rawerr.New("Tx type unknown")
errInvalidFormat = rawerr.New("Invalid format")
errDecoding = fmt.Errorf("Error decoding input")
errUnauthorized = fmt.Errorf("Unauthorized")
errInvalidSignature = fmt.Errorf("Invalid Signature")
errTooLarge = fmt.Errorf("Input size too large")
errMissingSignature = fmt.Errorf("Signature missing")
errTooManySignatures = fmt.Errorf("Too many signatures")
errNoChain = fmt.Errorf("No chain id provided")
errWrongChain = fmt.Errorf("Wrong chain for tx")
errUnknownTxType = fmt.Errorf("Tx type unknown")
errInvalidFormat = fmt.Errorf("Invalid format")
errUnknownModule = fmt.Errorf("Unknown module")
)
func ErrUnknownTxType(tx basecoin.Tx) TMError {
@ -44,6 +41,14 @@ func IsInvalidFormatErr(err error) bool {
return IsSameError(errInvalidFormat, err)
}
func ErrUnknownModule(mod string) TMError {
w := errors.Wrap(errUnknownModule, mod)
return WithCode(w, abci.CodeType_UnknownRequest)
}
func IsUnknownModuleErr(err error) bool {
return IsSameError(errUnknownModule, err)
}
func ErrInternal(msg string) TMError {
return New(msg, abci.CodeType_InternalError)
}

View File

@ -1,9 +1,5 @@
package errors
/**
* Copyright (C) 2017 Ethan Frey
**/
import (
"fmt"
@ -23,6 +19,7 @@ type causer interface {
Cause() error
}
// TMError is the tendermint abci return type with stack trace
type TMError interface {
stackTracer
ErrorCode() abci.CodeType

View File

@ -3,10 +3,23 @@ package basecoin
import (
abci "github.com/tendermint/abci/types"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin/types"
)
// Handler is anything that processes a transaction
type Handler interface {
Checker
Deliver
SetOptioner
Named
// TODO: flesh these out as well
// InitChain(store types.KVStore, vals []*abci.Validator)
// BeginBlock(store types.KVStore, hash []byte, header *abci.Header)
// EndBlock(store types.KVStore, height uint64) abci.ResponseEndBlock
}
type Named interface {
Name() string
}
@ -33,16 +46,15 @@ func (c DeliverFunc) DeliverTx(ctx Context, store types.KVStore, tx Tx) (Result,
return c(ctx, store, tx)
}
// Handler is anything that processes a transaction
type Handler interface {
Checker
Deliver
Named
// TODO: flesh these out as well
// SetOption(store types.KVStore, key, value string) (log string)
// InitChain(store types.KVStore, vals []*abci.Validator)
// BeginBlock(store types.KVStore, hash []byte, header *abci.Header)
// EndBlock(store types.KVStore, height uint64) abci.ResponseEndBlock
type SetOptioner interface {
SetOption(l log.Logger, store types.KVStore, module, key, value string) (string, error)
}
// SetOptionFunc (like http.HandlerFunc) is a shortcut for making wrapers
type SetOptionFunc func(log.Logger, types.KVStore, string, string, string) (string, error)
func (c SetOptionFunc) SetOption(l log.Logger, store types.KVStore, module, key, value string) (string, error) {
return c(l, store, module, key, value)
}
// Result captures any non-error abci result
@ -58,3 +70,19 @@ func (r Result) ToABCI() abci.Result {
Log: r.Log,
}
}
// placeholders
// holders
type NopCheck struct{}
func (_ NopCheck) CheckTx(Context, types.KVStore, Tx) (r Result, e error) { return }
type NopDeliver struct{}
func (_ NopDeliver) DeliverTx(Context, types.KVStore, Tx) (r Result, e error) { return }
type NopOption struct{}
func (_ NopOption) SetOption(log.Logger, types.KVStore, string, string, string) (string, error) {
return "", nil
}

View File

@ -1,3 +1,4 @@
//nolint
package coin
import (

51
modules/coin/genesis.go Normal file
View File

@ -0,0 +1,51 @@
package coin
import (
"bytes"
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/basecoin/types"
)
/**** code to parse accounts from genesis docs ***/
// GenesisAccount - genesis account parameters
type GenesisAccount struct {
Address data.Bytes `json:"address"`
// this from types.Account (don't know how to embed this properly)
PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known.
Sequence int `json:"sequence"`
Balance types.Coins `json:"coins"`
}
// ToAccount - GenesisAccount struct to a basecoin Account
func (g GenesisAccount) ToAccount() Account {
return Account{
Sequence: g.Sequence,
Coins: g.Balance,
}
}
// GetAddr - Get the address of the genesis account
func (g GenesisAccount) GetAddr() ([]byte, error) {
noAddr, noPk := len(g.Address) == 0, g.PubKey.Empty()
if noAddr {
if noPk {
return nil, errors.New("No address given")
}
return g.PubKey.Address(), nil
}
if noPk { // but is addr...
return g.Address, nil
}
// now, we have both, make sure they check out
if bytes.Equal(g.Address, g.PubKey.Address()) {
return g.Address, nil
}
return nil, errors.New("Address and pubkey don't match")
}

View File

@ -1,29 +1,36 @@
package coin
import (
"fmt"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/types"
)
const (
NameCoin = "coin"
)
//NameCoin - name space of the coin module
const NameCoin = "coin"
// Handler writes
// Handler includes an accountant
type Handler struct {
Accountant
}
var _ basecoin.Handler = Handler{}
// NewHandler - new accountant handler for the coin module
func NewHandler() Handler {
return Handler{
Accountant: Accountant{Prefix: []byte(NameCoin + "/")},
Accountant: NewAccountant(""),
}
}
func (_ Handler) Name() string {
// Name - return name space
func (Handler) Name() string {
return NameCoin
}
@ -36,7 +43,7 @@ func (h Handler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.
// now make sure there is money
for _, in := range send.Inputs {
_, err = h.CheckCoins(store, in.Address, in.Coins, in.Sequence)
_, err = h.CheckCoins(store, in.Address, in.Coins.Negative(), in.Sequence)
if err != nil {
return res, err
}
@ -74,6 +81,35 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoi
return basecoin.Result{}, nil
}
// SetOption - sets the genesis account balance
func (h Handler) SetOption(l log.Logger, store types.KVStore, module, key, value string) (log string, err error) {
if module != NameCoin {
return "", errors.ErrUnknownModule(module)
}
if key == "account" {
var acc GenesisAccount
err = data.FromJSON([]byte(value), &acc)
if err != nil {
return "", err
}
acc.Balance.Sort()
addr, err := acc.GetAddr()
if err != nil {
return "", ErrInvalidAddress()
}
// this sets the permission for a public key signature, use that app
actor := stack.SigPerm(addr)
err = storeAccount(store, h.MakeKey(actor), acc.ToAccount())
if err != nil {
return "", err
}
return "Success", nil
}
msg := fmt.Sprintf("Unknown key: %s", key)
return "", errors.ErrInternal(msg)
}
func checkTx(ctx basecoin.Context, tx basecoin.Tx) (send SendTx, err error) {
// check if the tx is proper type and valid
send, ok := tx.Unwrap().(SendTx)

View File

@ -1,10 +1,15 @@
package coin
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/types"
@ -68,7 +73,7 @@ func TestHandlerValidation(t *testing.T) {
}
for i, tc := range cases {
ctx := stack.MockContext().WithPermissions(tc.perms...)
ctx := stack.MockContext("base-chain").WithPermissions(tc.perms...)
_, err := checkTx(ctx, tc.tx)
if tc.valid {
assert.Nil(err, "%d: %+v", i, err)
@ -138,17 +143,17 @@ func TestDeliverTx(t *testing.T) {
store := types.NewMemKVStore()
for _, m := range tc.init {
acct := Account{Coins: m.coins}
err := storeAccount(store, h.makeKey(m.addr), acct)
err := storeAccount(store, h.MakeKey(m.addr), acct)
require.Nil(err, "%d: %+v", i, err)
}
ctx := stack.MockContext().WithPermissions(tc.perms...)
ctx := stack.MockContext("base-chain").WithPermissions(tc.perms...)
_, err := h.DeliverTx(ctx, store, tc.tx)
if len(tc.final) > 0 { // valid
assert.Nil(err, "%d: %+v", i, err)
// make sure the final balances are correct
for _, f := range tc.final {
acct, err := loadAccount(store, h.makeKey(f.addr))
acct, err := loadAccount(store, h.MakeKey(f.addr))
assert.Nil(err, "%d: %+v", i, err)
assert.Equal(f.coins, acct.Coins)
}
@ -158,5 +163,56 @@ func TestDeliverTx(t *testing.T) {
}
}
}
func TestSetOption(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
// some sample settings
pk := crypto.GenPrivKeySecp256k1().Wrap()
addr := pk.PubKey().Address()
actor := basecoin.Actor{App: stack.NameSigs, Address: addr}
someCoins := types.Coins{{"atom", 123}}
otherCoins := types.Coins{{"eth", 11}}
mixedCoins := someCoins.Plus(otherCoins)
type money struct {
addr basecoin.Actor
coins types.Coins
}
cases := []struct {
init []GenesisAccount
expected []money
}{
{
[]GenesisAccount{{Address: addr, Balance: mixedCoins}},
[]money{{actor, mixedCoins}},
},
}
h := NewHandler()
l := log.NewNopLogger()
for i, tc := range cases {
store := types.NewMemKVStore()
key := "account"
// set the options
for j, gen := range tc.init {
value, err := json.Marshal(gen)
require.Nil(err, "%d,%d: %+v", i, j, err)
_, err = h.SetOption(l, store, NameCoin, key, string(value))
require.Nil(err)
}
// check state is proper
for _, f := range tc.expected {
acct, err := loadAccount(store, h.MakeKey(f.addr))
assert.Nil(err, "%d: %+v", i, err)
assert.Equal(f.coins, acct.Coins)
}
}
}

View File

@ -10,12 +10,26 @@ import (
"github.com/tendermint/basecoin/types"
)
// Accountant - custom object to manage coins for the coin module
// TODO prefix should be post-fix if maintaining the same key space
type Accountant struct {
Prefix []byte
}
// NewAccountant - create the new accountant with prefix information
func NewAccountant(prefix string) Accountant {
if prefix == "" {
prefix = NameCoin
}
return Accountant{
Prefix: []byte(prefix + "/"),
}
}
// GetAccount - Get account from store and address
func (a Accountant) GetAccount(store types.KVStore, addr basecoin.Actor) (Account, error) {
acct, err := loadAccount(store, a.makeKey(addr))
acct, err := loadAccount(store, a.MakeKey(addr))
// for empty accounts, don't return an error, but rather an empty account
if IsNoAccountErr(err) {
err = nil
@ -36,7 +50,7 @@ func (a Accountant) ChangeCoins(store types.KVStore, addr basecoin.Actor, coins
return acct.Coins, err
}
err = storeAccount(store, a.makeKey(addr), acct)
err = storeAccount(store, a.MakeKey(addr), acct)
return acct.Coins, err
}
@ -44,7 +58,7 @@ func (a Accountant) ChangeCoins(store types.KVStore, addr basecoin.Actor, coins
//
// it doesn't save anything, that is up to you to decide (Check/Change Coins)
func (a Accountant) updateCoins(store types.KVStore, addr basecoin.Actor, coins types.Coins, seq int) (acct Account, err error) {
acct, err = loadAccount(store, a.makeKey(addr))
acct, err = loadAccount(store, a.MakeKey(addr))
// we can increase an empty account...
if IsNoAccountErr(err) && coins.IsPositive() {
err = nil
@ -58,7 +72,7 @@ func (a Accountant) updateCoins(store types.KVStore, addr basecoin.Actor, coins
if seq != acct.Sequence+1 {
return acct, ErrInvalidSequence()
}
acct.Sequence += 1
acct.Sequence++
}
// check amount
@ -71,7 +85,9 @@ func (a Accountant) updateCoins(store types.KVStore, addr basecoin.Actor, coins
return acct, nil
}
func (a Accountant) makeKey(addr basecoin.Actor) []byte {
// MakeKey - generate key bytes from address using accountant prefix
// TODO Prefix -> PostFix for consistent namespace
func (a Accountant) MakeKey(addr basecoin.Actor) []byte {
key := addr.Bytes()
if len(a.Prefix) > 0 {
key = append(a.Prefix, key...)
@ -79,12 +95,14 @@ func (a Accountant) makeKey(addr basecoin.Actor) []byte {
return key
}
// Account - coin account structure
type Account struct {
Coins types.Coins `json:"coins"`
Sequence int `json:"seq"`
Sequence int `json:"sequence"`
}
func loadAccount(store types.KVStore, key []byte) (acct Account, err error) {
// fmt.Printf("load: %X\n", key)
data := store.Get(key)
if len(data) == 0 {
return acct, ErrNoAccount()
@ -98,6 +116,7 @@ func loadAccount(store types.KVStore, key []byte) (acct Account, err error) {
}
func storeAccount(store types.KVStore, key []byte, acct Account) error {
// fmt.Printf("store: %X\n", key)
bin := wire.BinaryBytes(acct)
store.Set(key, bin)
return nil // real stores can return error...

View File

@ -4,7 +4,6 @@ import (
"fmt"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/types"
)
@ -15,17 +14,19 @@ func init() {
// we reserve the 0x20-0x3f range for standard modules
const (
ByteSend = 0x20
TypeSend = "send"
TypeSend = NameCoin + "/send"
)
//-----------------------------------------------------------------------------
// TxInput - expected coin movement outputs, used with SendTx
type TxInput struct {
Address basecoin.Actor `json:"address"`
Coins types.Coins `json:"coins"`
Sequence int `json:"sequence"` // Nonce: Must be 1 greater than the last committed TxInput
}
// ValidateBasic - validate transaction input
func (txIn TxInput) ValidateBasic() error {
if txIn.Address.App == "" {
return ErrInvalidAddress()
@ -50,6 +51,7 @@ func (txIn TxInput) String() string {
return fmt.Sprintf("TxInput{%v,%v,%v}", txIn.Address, txIn.Coins, txIn.Sequence)
}
// NewTxInput - create a transaction input, used with SendTx
func NewTxInput(addr basecoin.Actor, coins types.Coins, sequence int) TxInput {
input := TxInput{
Address: addr,
@ -61,11 +63,13 @@ func NewTxInput(addr basecoin.Actor, coins types.Coins, sequence int) TxInput {
//-----------------------------------------------------------------------------
// TxOutput - expected coin movement output, used with SendTx
type TxOutput struct {
Address basecoin.Actor `json:"address"`
Coins types.Coins `json:"coins"`
}
// ValidateBasic - validate transaction output
func (txOut TxOutput) ValidateBasic() error {
if txOut.Address.App == "" {
return ErrInvalidAddress()
@ -87,6 +91,7 @@ func (txOut TxOutput) String() string {
return fmt.Sprintf("TxOutput{%X,%v}", txOut.Address, txOut.Coins)
}
// NewTxOutput - create a transaction output, used with SendTx
func NewTxOutput(addr basecoin.Actor, coins types.Coins) TxOutput {
output := TxOutput{
Address: addr,
@ -97,6 +102,8 @@ func NewTxOutput(addr basecoin.Actor, coins types.Coins) TxOutput {
//-----------------------------------------------------------------------------
// SendTx - high level transaction of the coin module
// Satisfies: TxInner
type SendTx struct {
Inputs []TxInput `json:"inputs"`
Outputs []TxOutput `json:"outputs"`
@ -104,10 +111,12 @@ type SendTx struct {
var _ basecoin.Tx = NewSendTx(nil, nil)
// NewSendTx - new SendTx
func NewSendTx(in []TxInput, out []TxOutput) basecoin.Tx {
return SendTx{Inputs: in, Outputs: out}.Wrap()
}
// ValidateBasic - validate the send transaction
func (tx SendTx) ValidateBasic() error {
// this just makes sure all the inputs and outputs are properly formatted,
// not that they actually have the money inside
@ -142,6 +151,7 @@ func (tx SendTx) String() string {
return fmt.Sprintf("SendTx{%v->%v}", tx.Inputs, tx.Outputs)
}
// Wrap - used to satisfy TxInner
func (tx SendTx) Wrap() basecoin.Tx {
return basecoin.Tx{tx}
}

View File

@ -1,3 +1,4 @@
//nolint
package fee
import (

View File

@ -7,10 +7,10 @@ import (
"github.com/tendermint/basecoin/types"
)
const (
NameFee = "fee"
)
// NameFee - namespace for the fee module
const NameFee = "fee"
// AccountChecker - interface used by SimpleFeeHandler
type AccountChecker interface {
// Get amount checks the current amount
GetAmount(store types.KVStore, addr basecoin.Actor) (types.Coins, error)
@ -20,12 +20,15 @@ type AccountChecker interface {
ChangeAmount(store types.KVStore, addr basecoin.Actor, coins types.Coins) (types.Coins, error)
}
// SimpleFeeHandler - checker object for fee checking
type SimpleFeeHandler struct {
AccountChecker
MinFee types.Coins
stack.PassOption
}
func (_ SimpleFeeHandler) Name() string {
// Name - return the namespace for the fee module
func (SimpleFeeHandler) Name() string {
return NameFee
}
@ -33,6 +36,7 @@ var _ stack.Middleware = SimpleFeeHandler{}
// Yes, I know refactor a bit... really too late already
// CheckTx - check the transaction
func (h SimpleFeeHandler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
feeTx, ok := tx.Unwrap().(*Fee)
if !ok {
@ -56,6 +60,7 @@ func (h SimpleFeeHandler) CheckTx(ctx basecoin.Context, store types.KVStore, tx
return basecoin.Result{Log: "Valid tx"}, nil
}
// DeliverTx - send the fee handler transaction
func (h SimpleFeeHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
feeTx, ok := tx.Unwrap().(*Fee)
if !ok {

View File

@ -13,7 +13,7 @@ const (
// Chain enforces that this tx was bound to the named chain
type Chain struct {
ChainID string
PassOption
}
func (_ Chain) Name() string {
@ -23,7 +23,7 @@ func (_ Chain) Name() string {
var _ Middleware = Chain{}
func (c Chain) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
stx, err := c.checkChain(tx)
stx, err := c.checkChain(ctx.ChainID(), tx)
if err != nil {
return res, err
}
@ -31,7 +31,7 @@ func (c Chain) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx
}
func (c Chain) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
stx, err := c.checkChain(tx)
stx, err := c.checkChain(ctx.ChainID(), tx)
if err != nil {
return res, err
}
@ -39,12 +39,12 @@ func (c Chain) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.
}
// checkChain makes sure the tx is a txs.Chain and
func (c Chain) checkChain(tx basecoin.Tx) (basecoin.Tx, error) {
func (c Chain) checkChain(chainID string, tx basecoin.Tx) (basecoin.Tx, error) {
ctx, ok := tx.Unwrap().(*txs.Chain)
if !ok {
return tx, errors.ErrNoChain()
}
if ctx.ChainID != c.ChainID {
if ctx.ChainID != chainID {
return tx, errors.ErrWrongChain(ctx.ChainID)
}
return ctx.Tx, nil

View File

@ -30,12 +30,12 @@ func TestChain(t *testing.T) {
}
// generic args here...
ctx := NewContext(log.NewNopLogger())
ctx := NewContext(chainID, log.NewNopLogger())
store := types.NewMemKVStore()
// build the stack
ok := OKHandler{msg}
app := New(Chain{chainID}).Use(ok)
ok := OKHandler{Log: msg}
app := New(Chain{}).Use(ok)
for idx, tc := range cases {
i := strconv.Itoa(idx)

View File

@ -17,20 +17,26 @@ type nonce int64
type secureContext struct {
id nonce
chain string
app string
perms []basecoin.Actor
log.Logger
}
func NewContext(logger log.Logger) basecoin.Context {
func NewContext(chain string, logger log.Logger) basecoin.Context {
return secureContext{
id: nonce(rand.Int63()),
chain: chain,
Logger: logger,
}
}
var _ basecoin.Context = secureContext{}
func (c secureContext) ChainID() string {
return c.chain
}
// WithPermissions will panic if they try to set permission without the proper app
func (c secureContext) WithPermissions(perms ...basecoin.Actor) basecoin.Context {
// the guard makes sure you only set permissions for the app you are inside
@ -44,6 +50,7 @@ func (c secureContext) WithPermissions(perms ...basecoin.Actor) basecoin.Context
return secureContext{
id: c.id,
chain: c.chain,
app: c.app,
perms: append(c.perms, perms...),
Logger: c.Logger,
@ -59,6 +66,17 @@ func (c secureContext) HasPermission(perm basecoin.Actor) bool {
return false
}
func (c secureContext) GetPermissions(chain, app string) (res []basecoin.Actor) {
for _, p := range c.perms {
if chain == p.ChainID {
if app == "" || app == p.App {
res = append(res, p)
}
}
}
return res
}
// IsParent ensures that this is derived from the given secureClient
func (c secureContext) IsParent(other basecoin.Context) bool {
so, ok := other.(secureContext)
@ -73,6 +91,7 @@ func (c secureContext) IsParent(other basecoin.Context) bool {
func (c secureContext) Reset() basecoin.Context {
return secureContext{
id: c.id,
chain: c.chain,
app: c.app,
perms: nil,
Logger: c.Logger,
@ -88,6 +107,7 @@ func withApp(ctx basecoin.Context, app string) basecoin.Context {
}
return secureContext{
id: sc.id,
chain: sc.chain,
app: app,
perms: sc.perms,
Logger: sc.Logger,

126
stack/dispatcher.go Normal file
View File

@ -0,0 +1,126 @@
package stack
import (
"fmt"
"strings"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/types"
)
// nolint
const (
NameDispatcher = "disp"
)
// Dispatcher grabs a bunch of Dispatchables and groups them into one Handler.
//
// It will route tx to the proper locations and also allows them to call each
// other synchronously through the same tx methods.
//
// Please note that iterating through a map is a non-deteministic operation
// and, as such, should never be done in the context of an ABCI app. Only
// use this map to look up an exact route by name.
type Dispatcher struct {
routes map[string]Dispatchable
}
// NewDispatcher creates a dispatcher and adds the given routes.
// You can also add routes later with .AddRoutes()
func NewDispatcher(routes ...Dispatchable) *Dispatcher {
d := &Dispatcher{
routes: map[string]Dispatchable{},
}
d.AddRoutes(routes...)
return d
}
var _ basecoin.Handler = new(Dispatcher)
// AddRoutes registers all these dispatchable choices under their subdomains
//
// Panics on attempt to double-register a route name, as this is a configuration error.
// Should I retrun an error instead?
func (d *Dispatcher) AddRoutes(routes ...Dispatchable) {
for _, r := range routes {
name := r.Name()
if _, ok := d.routes[name]; ok {
panic(fmt.Sprintf("%s already registered with dispatcher", name))
}
d.routes[name] = r
}
}
// Name - defines the name of this module
func (d *Dispatcher) Name() string {
return NameDispatcher
}
// CheckTx - implements Handler interface
//
// Tries to find a registered module (Dispatchable) based on the name of the tx.
// The tx name (as registered with go-data) should be in the form `<module name>/XXXX`,
// where `module name` must match the name of a dispatchable and XXX can be any string.
func (d *Dispatcher) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
r, err := d.lookupTx(tx)
if err != nil {
return res, err
}
// TODO: check on callback
cb := d
return r.CheckTx(ctx, store, tx, cb)
}
// DeliverTx - implements Handler interface
//
// Tries to find a registered module (Dispatchable) based on the name of the tx.
// The tx name (as registered with go-data) should be in the form `<module name>/XXXX`,
// where `module name` must match the name of a dispatchable and XXX can be any string.
func (d *Dispatcher) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
r, err := d.lookupTx(tx)
if err != nil {
return res, err
}
// TODO: check on callback
cb := d
return r.DeliverTx(ctx, store, tx, cb)
}
// SetOption - implements Handler interface
//
// Tries to find a registered module (Dispatchable) based on the
// module name from SetOption of the tx.
func (d *Dispatcher) SetOption(l log.Logger, store types.KVStore, module, key, value string) (string, error) {
r, err := d.lookupModule(module)
if err != nil {
return "", err
}
// TODO: check on callback
cb := d
return r.SetOption(l, store, module, key, value, cb)
}
func (d *Dispatcher) lookupTx(tx basecoin.Tx) (Dispatchable, error) {
kind, err := tx.GetKind()
if err != nil {
return nil, err
}
// grab everything before the /
name := strings.SplitN(kind, "/", 2)[0]
r, ok := d.routes[name]
if !ok {
return nil, errors.ErrUnknownTxType(tx)
}
return r, nil
}
func (d *Dispatcher) lookupModule(name string) (Dispatchable, error) {
r, ok := d.routes[name]
if !ok {
return nil, errors.ErrUnknownModule(name)
}
return r, nil
}

View File

@ -19,6 +19,7 @@ const (
// OKHandler just used to return okay to everything
type OKHandler struct {
Log string
basecoin.NopOption
}
var _ basecoin.Handler = OKHandler{}
@ -38,7 +39,9 @@ func (ok OKHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx base
}
// EchoHandler returns success, echoing res.Data = tx bytes
type EchoHandler struct{}
type EchoHandler struct {
basecoin.NopOption
}
var _ basecoin.Handler = EchoHandler{}
@ -61,6 +64,7 @@ func (_ EchoHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx bas
// FailHandler always returns an error
type FailHandler struct {
Err error
basecoin.NopOption
}
var _ basecoin.Handler = FailHandler{}
@ -83,6 +87,7 @@ func (f FailHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx bas
type PanicHandler struct {
Msg string
Err error
basecoin.NopOption
}
var _ basecoin.Handler = PanicHandler{}

View File

@ -15,12 +15,12 @@ import (
func TestOK(t *testing.T) {
assert := assert.New(t)
ctx := NewContext(log.NewNopLogger())
ctx := NewContext("test-chain", log.NewNopLogger())
store := types.NewMemKVStore()
data := "this looks okay"
tx := basecoin.Tx{}
ok := OKHandler{data}
ok := OKHandler{Log: data}
res, err := ok.CheckTx(ctx, store, tx)
assert.Nil(err, "%+v", err)
assert.Equal(data, res.Log)
@ -33,12 +33,12 @@ func TestOK(t *testing.T) {
func TestFail(t *testing.T) {
assert := assert.New(t)
ctx := NewContext(log.NewNopLogger())
ctx := NewContext("test-chain", log.NewNopLogger())
store := types.NewMemKVStore()
msg := "big problem"
tx := basecoin.Tx{}
fail := FailHandler{errors.New(msg)}
fail := FailHandler{Err: errors.New(msg)}
_, err := fail.CheckTx(ctx, store, tx)
if assert.NotNil(err) {
assert.Equal(msg, err.Error())
@ -53,7 +53,7 @@ func TestFail(t *testing.T) {
func TestPanic(t *testing.T) {
assert := assert.New(t)
ctx := NewContext(log.NewNopLogger())
ctx := NewContext("test-chain", log.NewNopLogger())
store := types.NewMemKVStore()
msg := "system crash!"
tx := basecoin.Tx{}

View File

@ -15,6 +15,7 @@ const (
// Required Actor, otherwise passes along the call untouched
type CheckMiddleware struct {
Required basecoin.Actor
PassOption
}
var _ Middleware = CheckMiddleware{}
@ -40,6 +41,7 @@ func (p CheckMiddleware) DeliverTx(ctx basecoin.Context, store types.KVStore, tx
// GrantMiddleware tries to set the permission to this Actor, which may be prohibited
type GrantMiddleware struct {
Auth basecoin.Actor
PassOption
}
var _ Middleware = GrantMiddleware{}

View File

@ -1,18 +1,12 @@
package stack
import (
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/types"
)
type CheckerMiddle interface {
CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Checker) (basecoin.Result, error)
}
type DeliverMiddle interface {
DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Deliver) (basecoin.Result, error)
}
// Middleware is anything that wraps another handler to enhance functionality.
//
// You can use utilities in handlers to construct them, the interfaces
@ -20,5 +14,98 @@ type DeliverMiddle interface {
type Middleware interface {
CheckerMiddle
DeliverMiddle
SetOptionMiddle
basecoin.Named
}
type CheckerMiddle interface {
CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Checker) (basecoin.Result, error)
}
type CheckerMiddleFunc func(basecoin.Context, types.KVStore, basecoin.Tx, basecoin.Checker) (basecoin.Result, error)
func (c CheckerMiddleFunc) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Checker) (basecoin.Result, error) {
return c(ctx, store, tx, next)
}
type DeliverMiddle interface {
DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Deliver) (basecoin.Result, error)
}
type DeliverMiddleFunc func(basecoin.Context, types.KVStore, basecoin.Tx, basecoin.Deliver) (basecoin.Result, error)
func (d DeliverMiddleFunc) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Deliver) (basecoin.Result, error) {
return d(ctx, store, tx, next)
}
type SetOptionMiddle interface {
SetOption(l log.Logger, store types.KVStore, module, key, value string, next basecoin.SetOptioner) (string, error)
}
type SetOptionMiddleFunc func(log.Logger, types.KVStore, string, string, string, basecoin.SetOptioner) (string, error)
func (c SetOptionMiddleFunc) SetOption(l log.Logger, store types.KVStore, module, key, value string, next basecoin.SetOptioner) (string, error) {
return c(l, store, module, key, value, next)
}
// holders
type PassCheck struct{}
func (_ PassCheck) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Checker) (basecoin.Result, error) {
return next.CheckTx(ctx, store, tx)
}
type PassDeliver struct{}
func (_ PassDeliver) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Deliver) (basecoin.Result, error) {
return next.DeliverTx(ctx, store, tx)
}
type PassOption struct{}
func (_ PassOption) SetOption(l log.Logger, store types.KVStore, module, key, value string, next basecoin.SetOptioner) (string, error) {
return next.SetOption(l, store, module, key, value)
}
type NopOption struct{}
func (_ NopOption) SetOption(l log.Logger, store types.KVStore, module, key, value string, next basecoin.SetOptioner) (string, error) {
return "", nil
}
// Dispatchable is like middleware, except the meaning of "next" is different.
// Whereas in the middleware, it is the next handler that we should pass the same tx into,
// for dispatchers, it is a dispatcher, which it can use to
type Dispatchable interface {
Middleware
AssertDispatcher()
}
// WrapHandler turns a basecoin.Handler into a Dispatchable interface
func WrapHandler(h basecoin.Handler) Dispatchable {
return wrapped{h}
}
type wrapped struct {
h basecoin.Handler
}
var _ Dispatchable = wrapped{}
func (w wrapped) AssertDispatcher() {}
func (w wrapped) Name() string {
return w.h.Name()
}
func (w wrapped) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, _ basecoin.Checker) (basecoin.Result, error) {
return w.h.CheckTx(ctx, store, tx)
}
func (w wrapped) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, _ basecoin.Deliver) (basecoin.Result, error) {
return w.h.DeliverTx(ctx, store, tx)
}
func (w wrapped) SetOption(l log.Logger, store types.KVStore, module, key, value string, _ basecoin.SetOptioner) (string, error) {
return w.h.SetOption(l, store, module, key, value)
}

View File

@ -3,6 +3,8 @@ package stack
import (
"time"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/types"
)
@ -48,6 +50,20 @@ func (_ Logger) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin
return
}
func (_ Logger) SetOption(l log.Logger, store types.KVStore, module, key, value string, next basecoin.SetOptioner) (string, error) {
start := time.Now()
res, err := next.SetOption(l, store, module, key, value)
delta := time.Now().Sub(start)
// TODO: log the value being set also?
l = l.With("duration", micros(delta)).With("mod", module).With("key", key)
if err == nil {
l.Info("SetOption", "log", res)
} else {
l.Error("SetOption", "err", err)
}
return res, err
}
// micros returns how many microseconds passed in a call
func micros(d time.Duration) int {
return int(d.Seconds() * 1000000)

View File

@ -1,6 +1,8 @@
package stack
import (
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/types"
)
@ -37,6 +39,10 @@ func (m *middleware) DeliverTx(ctx basecoin.Context, store types.KVStore, tx bas
return m.middleware.DeliverTx(ctx, store, tx, next)
}
func (m *middleware) SetOption(l log.Logger, store types.KVStore, module, key, value string) (string, error) {
return m.middleware.SetOption(l, store, module, key, value, m.next)
}
// Stack is the entire application stack
type Stack struct {
middles []Middleware
@ -57,12 +63,12 @@ func New(middlewares ...Middleware) *Stack {
// NewDefault sets up the common middlewares before your custom stack.
//
// This is logger, recovery, signature, and chain
func NewDefault(chainID string, middlewares ...Middleware) *Stack {
func NewDefault(middlewares ...Middleware) *Stack {
mids := []Middleware{
Logger{},
Recovery{},
Signatures{},
Chain{chainID},
Chain{},
}
mids = append(mids, middlewares...)
return New(mids...)

View File

@ -17,7 +17,7 @@ func TestPermissionSandbox(t *testing.T) {
require := require.New(t)
// generic args
ctx := NewContext(log.NewNopLogger())
ctx := NewContext("test-chain", log.NewNopLogger())
store := types.NewMemKVStore()
raw := txs.NewRaw([]byte{1, 2, 3, 4})
rawBytes, err := data.ToWire(raw)
@ -42,8 +42,8 @@ func TestPermissionSandbox(t *testing.T) {
for i, tc := range cases {
app := New(
Recovery{}, // we need this so panics turn to errors
GrantMiddleware{tc.grant},
CheckMiddleware{tc.require},
GrantMiddleware{Auth: tc.grant},
CheckMiddleware{Required: tc.require},
).Use(EchoHandler{})
res, err := app.CheckTx(ctx, store, raw)

View File

@ -10,17 +10,23 @@ import (
type mockContext struct {
perms []basecoin.Actor
chain string
log.Logger
}
func MockContext() basecoin.Context {
func MockContext(chain string) basecoin.Context {
return mockContext{
chain: chain,
Logger: log.NewNopLogger(),
}
}
var _ basecoin.Context = mockContext{}
func (c mockContext) ChainID() string {
return c.chain
}
// WithPermissions will panic if they try to set permission without the proper app
func (c mockContext) WithPermissions(perms ...basecoin.Actor) basecoin.Context {
return mockContext{
@ -38,6 +44,17 @@ func (c mockContext) HasPermission(perm basecoin.Actor) bool {
return false
}
func (c mockContext) GetPermissions(chain, app string) (res []basecoin.Actor) {
for _, p := range c.perms {
if chain == p.ChainID {
if app == "" || app == p.App {
res = append(res, p)
}
}
}
return res
}
// IsParent ensures that this is derived from the given secureClient
func (c mockContext) IsParent(other basecoin.Context) bool {
_, ok := other.(mockContext)

View File

@ -14,7 +14,9 @@ const (
NameMultiplexer = "mplx"
)
type Multiplexer struct{}
type Multiplexer struct {
PassOption
}
func (_ Multiplexer) Name() string {
return NameMultiplexer

View File

@ -3,6 +3,8 @@ package stack
import (
"fmt"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/types"
@ -39,6 +41,15 @@ func (_ Recovery) DeliverTx(ctx basecoin.Context, store types.KVStore, tx baseco
return next.DeliverTx(ctx, store, tx)
}
func (_ Recovery) SetOption(l log.Logger, store types.KVStore, module, key, value string, next basecoin.SetOptioner) (log string, err error) {
defer func() {
if r := recover(); r != nil {
err = normalizePanic(r)
}
}()
return next.SetOption(l, store, module, key, value)
}
// normalizePanic makes sure we can get a nice TMError (with stack) out of it
func normalizePanic(p interface{}) error {
if err, isErr := p.(error); isErr {

View File

@ -15,7 +15,7 @@ func TestRecovery(t *testing.T) {
assert := assert.New(t)
// generic args here...
ctx := NewContext(log.NewNopLogger())
ctx := NewContext("test-chain", log.NewNopLogger())
store := types.NewMemKVStore()
tx := basecoin.Tx{}

View File

@ -13,7 +13,9 @@ const (
NameSigs = "sigs"
)
type Signatures struct{}
type Signatures struct {
PassOption
}
func (_ Signatures) Name() string {
return NameSigs

View File

@ -17,7 +17,7 @@ func TestSignatureChecks(t *testing.T) {
assert := assert.New(t)
// generic args
ctx := NewContext(log.NewNopLogger())
ctx := NewContext("test-chain", log.NewNopLogger())
store := types.NewMemKVStore()
raw := txs.NewRaw([]byte{1, 2, 3, 4})
@ -58,7 +58,7 @@ func TestSignatureChecks(t *testing.T) {
app := New(
Recovery{}, // we need this so panics turn to errors
Signatures{},
CheckMiddleware{tc.check},
CheckMiddleware{Required: tc.check},
).Use(OKHandler{})
var tx basecoin.Tx

View File

@ -60,8 +60,7 @@ func (et *execTest) reset() {
et.accOut = types.MakeAcc("bar")
et.store = types.NewMemKVStore()
et.state = NewState(et.store)
et.state.SetLogger(log.TestingLogger())
et.state = NewState(et.store, log.TestingLogger())
et.state.SetChainID(et.chainID)
// NOTE we dont run acc2State here

View File

@ -17,20 +17,16 @@ type State struct {
logger log.Logger
}
func NewState(store types.KVStore) *State {
func NewState(store types.KVStore, l log.Logger) *State {
return &State{
chainID: "",
store: store,
readCache: make(map[string][]byte),
writeCache: nil,
logger: log.NewNopLogger(),
logger: l,
}
}
func (s *State) SetLogger(l log.Logger) {
s.logger = l
}
func (s *State) SetChainID(chainID string) {
s.chainID = chainID
s.store.Set([]byte("base/chain_id"), []byte(chainID))

View File

@ -16,8 +16,7 @@ func TestState(t *testing.T) {
//States and Stores for tests
store := types.NewMemKVStore()
state := NewState(store)
state.SetLogger(log.TestingLogger())
state := NewState(store, log.TestingLogger())
cache := state.CacheWrap()
eyesCli := eyes.NewLocalClient("", 0)
@ -30,15 +29,13 @@ func TestState(t *testing.T) {
//reset the store/state/cache
reset := func() {
store = types.NewMemKVStore()
state = NewState(store)
state.SetLogger(log.TestingLogger())
state = NewState(store, log.TestingLogger())
cache = state.CacheWrap()
}
//set the state to using the eyesCli instead of MemKVStore
useEyesCli := func() {
state = NewState(eyesCli)
state.SetLogger(log.TestingLogger())
state = NewState(eyesCli, log.TestingLogger())
cache = state.CacheWrap()
}

View File

@ -159,9 +159,13 @@ checkSendTx() {
if [ -n "$DEBUG" ]; then echo $TX; echo; fi
assertEquals "proper height" $2 $(echo $TX | jq .height)
assertEquals "type=send" '"send"' $(echo $TX | jq .data.type)
assertEquals "proper sender" "\"$3\"" $(echo $TX | jq .data.data.inputs[0].address)
assertEquals "proper out amount" "$4" $(echo $TX | jq .data.data.outputs[0].coins[0].amount)
assertEquals "type=sig" '"sig"' $(echo $TX | jq .data.type)
CTX=$(echo $TX | jq .data.data.tx)
assertEquals "type=chain" '"chain"' $(echo $CTX | jq .type)
STX=$(echo $CTX | jq .data.tx)
assertEquals "type=coin/send" '"coin/send"' $(echo $STX | jq .type)
assertEquals "proper sender" "\"$3\"" $(echo $STX | jq .data.inputs[0].address.addr)
assertEquals "proper out amount" "$4" $(echo $STX | jq .data.outputs[0].coins[0].amount)
return $?
}

View File

@ -55,37 +55,38 @@ test02GetCounter() {
checkCounter() {
# make sure sender goes down
ACCT=$(${CLIENT_EXE} query counter)
assertTrue "count is set" $?
assertEquals "proper count" "$1" $(echo $ACCT | jq .data.Counter)
assertEquals "proper money" "$2" $(echo $ACCT | jq .data.TotalFees[0].amount)
if assertTrue "count is set" $?; then
assertEquals "proper count" "$1" $(echo $ACCT | jq .data.counter)
assertEquals "proper money" "$2" $(echo $ACCT | jq .data.total_fees[0].amount)
fi
}
test03AddCount() {
SENDER=$(getAddr $RICH)
assertFalse "bad password" "echo hi | ${CLIENT_EXE} tx counter --amount=1000mycoin --sequence=2 --name=${RICH} 2>/dev/null"
assertFalse "bad password" "echo hi | ${CLIENT_EXE} tx counter --countfee=100mycoin --sequence=2 --name=${RICH} 2>/dev/null"
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx counter --amount=10mycoin --sequence=2 --name=${RICH} --valid --countfee=5mycoin)
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx counter --countfee=10mycoin --sequence=2 --name=${RICH} --valid)
txSucceeded $? "$TX" "counter"
HASH=$(echo $TX | jq .hash | tr -d \")
TX_HEIGHT=$(echo $TX | jq .height)
checkCounter "1" "5"
# make sure the counter was updated
checkCounter "1" "10"
# FIXME: cannot load apptx properly.
# Look at the stack trace
# This cannot be fixed with the current ugly apptx structure...
# Leave for refactoring
# make sure the account was debited
checkAccount $SENDER "2" "9007199254739990"
# make sure tx is indexed
# echo hash $HASH
# TX=$(${CLIENT_EXE} query tx $HASH --trace)
# echo tx $TX
# if [-z assertTrue "found tx" $?]; then
# assertEquals "proper height" $TX_HEIGHT $(echo $TX | jq .height)
# assertEquals "type=app" '"app"' $(echo $TX | jq .data.type)
# assertEquals "proper sender" "\"$SENDER\"" $(echo $TX | jq .data.data.input.address)
# fi
# echo $TX
TX=$(${CLIENT_EXE} query tx $HASH --trace)
if assertTrue "found tx" $?; then
assertEquals "proper height" $TX_HEIGHT $(echo $TX | jq .height)
assertEquals "type=sig" '"sig"' $(echo $TX | jq .data.type)
CTX=$(echo $TX | jq .data.data.tx)
assertEquals "type=chain" '"chain"' $(echo $CTX | jq .type)
CNTX=$(echo $CTX | jq .data.tx)
assertEquals "type=cntr/count" '"cntr/count"' $(echo $CNTX | jq .type)
assertEquals "proper fee" "10" $(echo $CNTX | jq .data.fee[0].amount)
fi
}
# Load common then run these tests with shunit2!

View File

@ -1,158 +1,159 @@
package tmsp_test
import (
"encoding/json"
"testing"
// TODO: replace with benchmarker
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/basecoin/app"
"github.com/tendermint/basecoin/types"
wire "github.com/tendermint/go-wire"
eyescli "github.com/tendermint/merkleeyes/client"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
)
// import (
// "encoding/json"
// "testing"
func TestSendTx(t *testing.T) {
eyesCli := eyescli.NewLocalClient("", 0)
chainID := "test_chain_id"
bcApp := app.NewBasecoin(eyesCli)
bcApp.SetLogger(log.TestingLogger().With("module", "app"))
bcApp.SetOption("base/chain_id", chainID)
// t.Log(bcApp.Info())
// "github.com/stretchr/testify/assert"
// "github.com/stretchr/testify/require"
// "github.com/tendermint/basecoin/app"
// "github.com/tendermint/basecoin/types"
// wire "github.com/tendermint/go-wire"
// eyescli "github.com/tendermint/merkleeyes/client"
// cmn "github.com/tendermint/tmlibs/common"
// "github.com/tendermint/tmlibs/log"
// )
test1PrivAcc := types.PrivAccountFromSecret("test1")
test2PrivAcc := types.PrivAccountFromSecret("test2")
// func TestSendTx(t *testing.T) {
// eyesCli := eyescli.NewLocalClient("", 0)
// chainID := "test_chain_id"
// bcApp := app.NewBasecoin(eyesCli, log.TestingLogger().With("module", "app"))
// bcApp.SetOption("base/chain_id", chainID)
// // t.Log(bcApp.Info())
// Seed Basecoin with account
test1Acc := test1PrivAcc.Account
test1Acc.Balance = types.Coins{{"", 1000}}
accOpt, err := json.Marshal(test1Acc)
require.Nil(t, err)
bcApp.SetOption("base/account", string(accOpt))
// test1PrivAcc := types.PrivAccountFromSecret("test1")
// test2PrivAcc := types.PrivAccountFromSecret("test2")
// Construct a SendTx signature
tx := &types.SendTx{
Gas: 0,
Fee: types.Coin{"", 0},
Inputs: []types.TxInput{
types.NewTxInput(test1PrivAcc.Account.PubKey, types.Coins{{"", 1}}, 1),
},
Outputs: []types.TxOutput{
types.TxOutput{
Address: test2PrivAcc.Account.PubKey.Address(),
Coins: types.Coins{{"", 1}},
},
},
}
// // Seed Basecoin with account
// test1Acc := test1PrivAcc.Account
// test1Acc.Balance = types.Coins{{"", 1000}}
// accOpt, err := json.Marshal(test1Acc)
// require.Nil(t, err)
// bcApp.SetOption("base/account", string(accOpt))
// Sign request
signBytes := tx.SignBytes(chainID)
// t.Log("Sign bytes: %X\n", signBytes)
sig := test1PrivAcc.Sign(signBytes)
tx.Inputs[0].Signature = sig
// t.Log("Signed TX bytes: %X\n", wire.BinaryBytes(types.TxS{tx}))
// // Construct a SendTx signature
// tx := &types.SendTx{
// Gas: 0,
// Fee: types.Coin{"", 0},
// Inputs: []types.TxInput{
// types.NewTxInput(test1PrivAcc.Account.PubKey, types.Coins{{"", 1}}, 1),
// },
// Outputs: []types.TxOutput{
// types.TxOutput{
// Address: test2PrivAcc.Account.PubKey.Address(),
// Coins: types.Coins{{"", 1}},
// },
// },
// }
// Write request
txBytes := wire.BinaryBytes(types.TxS{tx})
res := bcApp.DeliverTx(txBytes)
// t.Log(res)
assert.True(t, res.IsOK(), "Failed: %v", res.Error())
}
// // Sign request
// signBytes := tx.SignBytes(chainID)
// // t.Log("Sign bytes: %X\n", signBytes)
// sig := test1PrivAcc.Sign(signBytes)
// tx.Inputs[0].Signature = sig
// // t.Log("Signed TX bytes: %X\n", wire.BinaryBytes(types.TxS{tx}))
func TestSequence(t *testing.T) {
eyesCli := eyescli.NewLocalClient("", 0)
chainID := "test_chain_id"
bcApp := app.NewBasecoin(eyesCli)
bcApp.SetOption("base/chain_id", chainID)
// t.Log(bcApp.Info())
// // Write request
// txBytes := wire.BinaryBytes(types.TxS{tx})
// res := bcApp.DeliverTx(txBytes)
// // t.Log(res)
// assert.True(t, res.IsOK(), "Failed: %v", res.Error())
// }
// Get the test account
test1PrivAcc := types.PrivAccountFromSecret("test1")
test1Acc := test1PrivAcc.Account
test1Acc.Balance = types.Coins{{"", 1 << 53}}
accOpt, err := json.Marshal(test1Acc)
require.Nil(t, err)
bcApp.SetOption("base/account", string(accOpt))
// func TestSequence(t *testing.T) {
// eyesCli := eyescli.NewLocalClient("", 0)
// chainID := "test_chain_id"
// bcApp := app.NewBasecoin(eyesCli, log.TestingLogger().With("module", "app"))
// bcApp.SetOption("base/chain_id", chainID)
// // t.Log(bcApp.Info())
sequence := int(1)
// Make a bunch of PrivAccounts
privAccounts := types.RandAccounts(1000, 1000000, 0)
privAccountSequences := make(map[string]int)
// Send coins to each account
// // Get the test account
// test1PrivAcc := types.PrivAccountFromSecret("test1")
// test1Acc := test1PrivAcc.Account
// test1Acc.Balance = types.Coins{{"", 1 << 53}}
// accOpt, err := json.Marshal(test1Acc)
// require.Nil(t, err)
// bcApp.SetOption("base/account", string(accOpt))
for i := 0; i < len(privAccounts); i++ {
privAccount := privAccounts[i]
// sequence := int(1)
// // Make a bunch of PrivAccounts
// privAccounts := types.RandAccounts(1000, 1000000, 0)
// privAccountSequences := make(map[string]int)
// // Send coins to each account
tx := &types.SendTx{
Gas: 2,
Fee: types.Coin{"", 2},
Inputs: []types.TxInput{
types.NewTxInput(test1Acc.PubKey, types.Coins{{"", 1000002}}, sequence),
},
Outputs: []types.TxOutput{
types.TxOutput{
Address: privAccount.Account.PubKey.Address(),
Coins: types.Coins{{"", 1000000}},
},
},
}
sequence += 1
// for i := 0; i < len(privAccounts); i++ {
// privAccount := privAccounts[i]
// Sign request
signBytes := tx.SignBytes(chainID)
sig := test1PrivAcc.Sign(signBytes)
tx.Inputs[0].Signature = sig
// t.Log("ADDR: %X -> %X\n", tx.Inputs[0].Address, tx.Outputs[0].Address)
// tx := &types.SendTx{
// Gas: 2,
// Fee: types.Coin{"", 2},
// Inputs: []types.TxInput{
// types.NewTxInput(test1Acc.PubKey, types.Coins{{"", 1000002}}, sequence),
// },
// Outputs: []types.TxOutput{
// types.TxOutput{
// Address: privAccount.Account.PubKey.Address(),
// Coins: types.Coins{{"", 1000000}},
// },
// },
// }
// sequence += 1
// Write request
txBytes := wire.BinaryBytes(struct{ types.Tx }{tx})
res := bcApp.DeliverTx(txBytes)
assert.True(t, res.IsOK(), "DeliverTx error: %v", res.Error())
}
// // Sign request
// signBytes := tx.SignBytes(chainID)
// sig := test1PrivAcc.Sign(signBytes)
// tx.Inputs[0].Signature = sig
// // t.Log("ADDR: %X -> %X\n", tx.Inputs[0].Address, tx.Outputs[0].Address)
res := bcApp.Commit()
assert.True(t, res.IsOK(), "Failed Commit: %v", res.Error())
// // Write request
// txBytes := wire.BinaryBytes(struct{ types.Tx }{tx})
// res := bcApp.DeliverTx(txBytes)
// assert.True(t, res.IsOK(), "DeliverTx error: %v", res.Error())
// }
t.Log("-------------------- RANDOM SENDS --------------------")
// res := bcApp.Commit()
// assert.True(t, res.IsOK(), "Failed Commit: %v", res.Error())
// Now send coins between these accounts
for i := 0; i < 10000; i++ {
randA := cmn.RandInt() % len(privAccounts)
randB := cmn.RandInt() % len(privAccounts)
if randA == randB {
continue
}
// t.Log("-------------------- RANDOM SENDS --------------------")
privAccountA := privAccounts[randA]
privAccountASequence := privAccountSequences[privAccountA.Account.PubKey.KeyString()]
privAccountSequences[privAccountA.Account.PubKey.KeyString()] = privAccountASequence + 1
privAccountB := privAccounts[randB]
// // Now send coins between these accounts
// for i := 0; i < 10000; i++ {
// randA := cmn.RandInt() % len(privAccounts)
// randB := cmn.RandInt() % len(privAccounts)
// if randA == randB {
// continue
// }
tx := &types.SendTx{
Gas: 2,
Fee: types.Coin{"", 2},
Inputs: []types.TxInput{
types.NewTxInput(privAccountA.PubKey, types.Coins{{"", 3}}, privAccountASequence+1),
},
Outputs: []types.TxOutput{
types.TxOutput{
Address: privAccountB.PubKey.Address(),
Coins: types.Coins{{"", 1}},
},
},
}
// privAccountA := privAccounts[randA]
// privAccountASequence := privAccountSequences[privAccountA.Account.PubKey.KeyString()]
// privAccountSequences[privAccountA.Account.PubKey.KeyString()] = privAccountASequence + 1
// privAccountB := privAccounts[randB]
// Sign request
signBytes := tx.SignBytes(chainID)
sig := privAccountA.Sign(signBytes)
tx.Inputs[0].Signature = sig
// t.Log("ADDR: %X -> %X\n", tx.Inputs[0].Address, tx.Outputs[0].Address)
// tx := &types.SendTx{
// Gas: 2,
// Fee: types.Coin{"", 2},
// Inputs: []types.TxInput{
// types.NewTxInput(privAccountA.PubKey, types.Coins{{"", 3}}, privAccountASequence+1),
// },
// Outputs: []types.TxOutput{
// types.TxOutput{
// Address: privAccountB.PubKey.Address(),
// Coins: types.Coins{{"", 1}},
// },
// },
// }
// Write request
txBytes := wire.BinaryBytes(struct{ types.Tx }{tx})
res := bcApp.DeliverTx(txBytes)
assert.True(t, res.IsOK(), "DeliverTx error: %v", res.Error())
}
}
// // Sign request
// signBytes := tx.SignBytes(chainID)
// sig := privAccountA.Sign(signBytes)
// tx.Inputs[0].Signature = 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)
// assert.True(t, res.IsOK(), "DeliverTx error: %v", res.Error())
// }
// }

43
tx.go
View File

@ -1,5 +1,12 @@
package basecoin
import (
"github.com/pkg/errors"
"github.com/tendermint/go-wire/data"
)
const maxTxSize = 10240
// TxInner is the interface all concrete transactions should implement.
//
// It adds bindings for clean un/marhsaling of the various implementations
@ -15,6 +22,20 @@ type TxInner interface {
ValidateBasic() error
}
// LoadTx parses a tx from data
//
// TODO: label both errors with abci.CodeType_EncodingError
// need to move errors to avoid import cycle
func LoadTx(bin []byte) (tx Tx, err error) {
if len(bin) > maxTxSize {
return tx, errors.New("Tx size exceeds maximum")
}
// Decode tx
err = data.FromWire(bin, &tx)
return tx, err
}
// TODO: do we need this abstraction? TxLayer???
// please review again after implementing "middleware"
@ -34,3 +55,25 @@ func (t Tx) GetLayer() TxLayer {
l, _ := t.Unwrap().(TxLayer)
return l
}
// env lets us parse an envelope and just grab the type
type env struct {
Kind string `json:"type"`
}
// TODO: put this functionality into go-data in a cleaner and more efficient way
func (t Tx) GetKind() (string, error) {
// render as json
d, err := data.ToJSON(t)
if err != nil {
return "", err
}
// parse json
text := env{}
err = data.FromJSON(d, &text)
if err != nil {
return "", err
}
// grab the type we used in json
return text.Kind, nil
}

View File

@ -4,6 +4,20 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func init() {
TxMapper.
RegisterImplementation(Demo{}, TypeDemo, ByteDemo).
RegisterImplementation(Fake{}, TypeFake, ByteFake)
}
const (
ByteDemo = 0xF0
TypeDemo = "test/demo"
ByteFake = 0xF1
TypeFake = "test/fake"
)
// define a Demo struct that implements TxLayer
@ -35,3 +49,19 @@ func TestLayer(t *testing.T) {
assert.True(l.IsLayer())
assert.NotNil(l.GetLayer())
}
func TestKind(t *testing.T) {
cases := []struct {
tx Tx
kind string
}{
{Demo{}.Wrap(), TypeDemo},
{Fake{}.Wrap(), TypeFake},
}
for _, tc := range cases {
kind, err := tc.tx.GetKind()
require.Nil(t, err, "%+v", err)
assert.Equal(t, tc.kind, kind)
}
}

View File

@ -64,16 +64,12 @@ func (s *OneSig) Next() basecoin.Tx {
}
func (s *OneSig) ValidateBasic() error {
// TODO: VerifyBytes here, we do it in Signers?
if s.Empty() || !s.Pubkey.VerifyBytes(s.SignBytes(), s.Sig) {
return errors.ErrUnauthorized()
}
return s.Tx.ValidateBasic()
}
// TxBytes returns the full data with signatures
func (s *OneSig) TxBytes() ([]byte, error) {
return data.ToWire(s)
return data.ToWire(s.Wrap())
}
// SignBytes returns the original data passed into `NewSig`
@ -139,17 +135,12 @@ func (s *MultiSig) Next() basecoin.Tx {
}
func (s *MultiSig) ValidateBasic() error {
// TODO: more efficient
_, err := s.Signers()
if err != nil {
return err
}
return s.Tx.ValidateBasic()
}
// TxBytes returns the full data with signatures
func (s *MultiSig) TxBytes() ([]byte, error) {
return data.ToWire(s)
return data.ToWire(s.Wrap())
}
// SignBytes returns the original data passed into `NewSig`

View File

@ -1,3 +1,4 @@
//nolint
package version
const Maj = "0"