Move ChainID into context

This commit is contained in:
Ethan Frey 2017-07-03 17:32:01 +02:00
parent ef0ab758ed
commit 159574db89
16 changed files with 170 additions and 124 deletions

View File

@ -1,23 +1,22 @@
package app
import (
"encoding/hex"
"encoding/json"
"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"
)
const (
maxTxSize = 10240
PluginNameBase = "base"
)
@ -25,23 +24,29 @@ 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, l log.Logger) *Basecoin {
func NewBasecoin(h basecoin.Handler, eyesCli *eyes.Client, l log.Logger) *Basecoin {
state := sm.NewState(eyesCli, l.With("module", "state"))
plugins := types.NewPlugins()
return &Basecoin{
handler: h,
eyesCli: eyesCli,
state: state,
cacheState: nil,
plugins: plugins,
logger: l,
}
}
// placeholder to just handle sendtx
func DefaultHandler() basecoin.Handler {
// use the default stack
h := coin.NewHandler()
return stack.NewDefault().Use(h)
}
// XXX For testing, not thread safe!
func (app *Basecoin) GetState() *sm.State {
return app.state.CacheWrap()
@ -60,87 +65,85 @@ func (app *Basecoin) Info() abci.ResponseInfo {
}
}
func (app *Basecoin) RegisterPlugin(plugin types.Plugin) {
app.plugins.RegisterPlugin(plugin)
}
// ABCI::SetOption
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":
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)
// TODO
return "todo"
// 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":
// 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 "Success"
// }
// return "Unrecognized option key " + key
// }
}
// 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)
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")
// 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)
if err != nil {
// discard the cache...
return errors.Result(err)
}
return res
// commit the cache and return result
cache.CacheSync()
return res.ToABCI()
}
// 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)
func (app *Basecoin) CheckTx(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 tx
res = sm.ExecTx(app.cacheState, app.plugins, tx, true, nil)
if res.IsErr() {
return res.PrependLog("Error in CheckTx")
// 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 abci.OK
return res.ToABCI()
}
// ABCI::Query
@ -151,12 +154,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()
@ -183,24 +180,24 @@ func (app *Basecoin) Commit() (res abci.Result) {
// ABCI::InitChain
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
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
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
}

View File

@ -56,7 +56,8 @@ func (at *appTest) reset() {
at.accOut = types.MakeAcc("output0")
eyesCli := eyes.NewLocalClient("", 0)
at.app = NewBasecoin(eyesCli, log.TestingLogger().With("module", "app"))
at.app = NewBasecoin(DefaultHandler(), eyesCli,
log.TestingLogger().With("module", "app"))
res := at.app.SetOption("base/chain_id", at.chainID)
require.EqualValues(at.t, res, "Success")
@ -105,7 +106,8 @@ func TestSetOption(t *testing.T) {
require := require.New(t)
eyesCli := eyes.NewLocalClient("", 0)
app := NewBasecoin(eyesCli, log.TestingLogger().With("module", "app"))
app := NewBasecoin(DefaultHandler(), eyesCli,
log.TestingLogger().With("module", "app"))
//testing ChainID
chainID := "testChain"

View File

@ -19,7 +19,7 @@ const genesisAcctFilepath = "./testdata/genesis2.json"
func TestLoadGenesisDoNotFailIfAppOptionsAreMissing(t *testing.T) {
eyesCli := eyescli.NewLocalClient("", 0)
app := NewBasecoin(eyesCli, log.TestingLogger())
app := NewBasecoin(DefaultHandler(), eyesCli, log.TestingLogger())
err := app.LoadGenesis("./testdata/genesis3.json")
require.Nil(t, err, "%+v", err)
}
@ -28,7 +28,7 @@ func TestLoadGenesis(t *testing.T) {
assert, require := assert.New(t), require.New(t)
eyesCli := eyescli.NewLocalClient("", 0)
app := NewBasecoin(eyesCli, log.TestingLogger())
app := NewBasecoin(DefaultHandler(), eyesCli, log.TestingLogger())
err := app.LoadGenesis(genesisFilepath)
require.Nil(err, "%+v", err)
@ -65,7 +65,7 @@ func TestLoadGenesisAccountAddress(t *testing.T) {
assert, require := assert.New(t), require.New(t)
eyesCli := eyescli.NewLocalClient("", 0)
app := NewBasecoin(eyesCli, log.TestingLogger())
app := NewBasecoin(DefaultHandler(), eyesCli, log.TestingLogger())
err := app.LoadGenesis(genesisAcctFilepath)
require.Nil(err, "%+v", err)

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"
@ -21,6 +22,8 @@ import (
"github.com/tendermint/tendermint/types"
"github.com/tendermint/basecoin/app"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/stack"
)
var StartCmd = &cobra.Command{
@ -48,6 +51,22 @@ func init() {
tcmd.AddNodeFlags(StartCmd)
}
// TODO: setup handler instead of Plugins
func getHandler() basecoin.Handler {
// use the default stack
h := coin.NewHandler()
app := stack.NewDefault("change-this").Use(h)
return app
// register IBC plugn
// basecoinApp.RegisterPlugin(NewIBCPlugin())
// register all other plugins
// for _, p := range plugins {
// basecoinApp.RegisterPlugin(p.newPlugin())
// }
}
func startCmd(cmd *cobra.Command, args []string) error {
rootDir := viper.GetString(cli.HomeFlag)
meyes := viper.GetString(FlagEyes)
@ -66,15 +85,8 @@ func startCmd(cmd *cobra.Command, args []string) error {
}
// Create Basecoin app
basecoinApp := app.NewBasecoin(eyesCli, logger.With("module", "app"))
// register IBC plugn
// basecoinApp.RegisterPlugin(NewIBCPlugin())
// register all other plugins
for _, p := range plugins {
basecoinApp.RegisterPlugin(p.newPlugin())
}
h := app.DefaultHandler()
basecoinApp := app.NewBasecoin(h, eyesCli, logger.With("module", "app"))
// if chain_id has not been set yet, load the genesis.
// else, assume it's been loaded

View File

@ -34,4 +34,5 @@ type Context interface {
HasPermission(perm Actor) bool
IsParent(ctx Context) bool
Reset() Context
ChainID() string
}

View File

@ -68,7 +68,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)
@ -142,7 +142,7 @@ func TestDeliverTx(t *testing.T) {
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)

View File

@ -12,9 +12,7 @@ const (
)
// Chain enforces that this tx was bound to the named chain
type Chain struct {
ChainID string
}
type Chain struct{}
func (_ Chain) Name() string {
return NameRecovery
@ -23,7 +21,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 +29,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 +37,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)
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,
@ -73,6 +80,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 +96,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,

View File

@ -15,7 +15,7 @@ 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{}
@ -33,7 +33,7 @@ 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{}
@ -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

@ -57,12 +57,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)

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{

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

@ -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})

21
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"