Re-implement counter plugin
This commit is contained in:
parent
473451f020
commit
6d56891a0f
|
@ -22,8 +22,6 @@ 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{
|
||||
|
@ -42,6 +40,12 @@ const (
|
|||
FlagWithoutTendermint = "without-tendermint"
|
||||
)
|
||||
|
||||
var (
|
||||
// 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")
|
||||
|
@ -51,22 +55,6 @@ 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().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)
|
||||
|
@ -85,8 +73,7 @@ func startCmd(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
// Create Basecoin app
|
||||
h := app.DefaultHandler()
|
||||
basecoinApp := app.NewBasecoin(h, eyesCli, logger.With("module", "app"))
|
||||
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
|
||||
|
|
|
@ -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,6 +11,8 @@ import (
|
|||
func main() {
|
||||
rt := commands.RootCmd
|
||||
|
||||
commands.Handler = app.DefaultHandler()
|
||||
|
||||
rt.AddCommand(
|
||||
commands.InitCmd,
|
||||
commands.StartCmd,
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
"github.com/tendermint/basecoin/app"
|
||||
"github.com/tendermint/basecoin/cmd/basecoin/commands"
|
||||
"github.com/tendermint/basecoin/docs/guide/counter/plugins/counter"
|
||||
"github.com/tendermint/basecoin/types"
|
||||
|
@ -18,6 +19,9 @@ func main() {
|
|||
Short: "demo plugin for basecoin",
|
||||
}
|
||||
|
||||
// TODO: register the counter here
|
||||
commands.Handler = app.DefaultHandler()
|
||||
|
||||
RootCmd.AddCommand(
|
||||
commands.InitCmd,
|
||||
commands.StartCmd,
|
||||
|
|
|
@ -1,16 +1,31 @@
|
|||
package counter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
rawerr "errors"
|
||||
|
||||
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/types"
|
||||
)
|
||||
|
||||
type CounterPluginState struct {
|
||||
Counter int
|
||||
TotalFees types.Coins
|
||||
// CounterTx
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
// 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
|
||||
TypeTx = NameCounter + "/count"
|
||||
)
|
||||
|
||||
func init() {
|
||||
basecoin.TxMapper.RegisterImplementation(CounterTx{}, TypeTx, ByteTx)
|
||||
}
|
||||
|
||||
type CounterTx struct {
|
||||
|
@ -18,81 +33,130 @@ type CounterTx struct {
|
|||
Fee types.Coins
|
||||
}
|
||||
|
||||
func NewCounterTx(valid bool, fee types.Coins) basecoin.Tx {
|
||||
return CounterTx{
|
||||
Valid: valid,
|
||||
Fee: fee,
|
||||
}.Wrap()
|
||||
}
|
||||
|
||||
func (c CounterTx) Wrap() basecoin.Tx {
|
||||
return basecoin.Tx{c}
|
||||
}
|
||||
|
||||
// ValidateBasic just makes sure the Fee is a valid, non-negative value
|
||||
func (c CounterTx) ValidateBasic() error {
|
||||
if !c.Fee.IsValid() {
|
||||
return coin.ErrInvalidCoins()
|
||||
}
|
||||
if !c.Fee.IsNonnegative() {
|
||||
return coin.ErrInvalidCoins()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Custom errors
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
type CounterPlugin struct {
|
||||
var (
|
||||
errInvalidCounter = rawerr.New("Counter Tx marked invalid")
|
||||
)
|
||||
|
||||
// This is a custom error class
|
||||
func ErrInvalidCounter() error {
|
||||
return errors.WithCode(errInvalidCounter, abci.CodeType_BaseInvalidInput)
|
||||
}
|
||||
func IsInvalidCounterErr(err error) bool {
|
||||
return errors.IsSameError(errInvalidCounter, err)
|
||||
}
|
||||
|
||||
func (cp *CounterPlugin) Name() string {
|
||||
return "counter"
|
||||
// This is just a helper function to return a generic "internal error"
|
||||
func ErrDecoding() error {
|
||||
return errors.ErrInternal("Error decoding state")
|
||||
}
|
||||
|
||||
func (cp *CounterPlugin) StateKey() []byte {
|
||||
return []byte(fmt.Sprintf("CounterPlugin.State"))
|
||||
// CounterHandler
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
type CounterHandler struct {
|
||||
basecoin.NopOption
|
||||
}
|
||||
|
||||
func New() *CounterPlugin {
|
||||
return &CounterPlugin{}
|
||||
var _ basecoin.Handler = CounterHandler{}
|
||||
|
||||
func (_ CounterHandler) Name() string {
|
||||
return NameCounter
|
||||
}
|
||||
|
||||
func (cp *CounterPlugin) SetOption(store types.KVStore, key, value string) (log string) {
|
||||
return ""
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()).PrependLog("CounterTx Error: ")
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Update CounterPluginState
|
||||
cpState.Counter += 1
|
||||
cpState.TotalFees = cpState.TotalFees.Plus(tx.Fee)
|
||||
|
||||
// Save CounterPluginState
|
||||
store.Set(cp.StateKey(), wire.BinaryBytes(cpState))
|
||||
|
||||
return abci.OK
|
||||
}
|
||||
|
||||
func (cp *CounterPlugin) InitChain(store types.KVStore, vals []*abci.Validator) {
|
||||
}
|
||||
|
||||
func (cp *CounterPlugin) BeginBlock(store types.KVStore, hash []byte, header *abci.Header) {
|
||||
}
|
||||
|
||||
func (cp *CounterPlugin) EndBlock(store types.KVStore, height uint64) (res abci.ResponseEndBlock) {
|
||||
// CheckTx checks if the tx is properly structured
|
||||
func (h CounterHandler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
_, err = checkTx(ctx, tx)
|
||||
return
|
||||
}
|
||||
|
||||
// DeliverTx executes the tx if valid
|
||||
func (h CounterHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
ctr, err := checkTx(ctx, tx)
|
||||
if err != nil {
|
||||
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()
|
||||
}
|
||||
|
||||
// TODO: handle coin movement.... ugh, need sequence to do this, right?
|
||||
|
||||
// update the counter
|
||||
state, err := LoadState(store)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
state.Counter += 1
|
||||
state.TotalFees = state.TotalFees.Plus(ctr.Fee)
|
||||
err = StoreState(store, state)
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func checkTx(ctx basecoin.Context, tx basecoin.Tx) (ctr CounterTx, err error) {
|
||||
ctr, ok := tx.Unwrap().(CounterTx)
|
||||
if !ok {
|
||||
return ctr, errors.ErrInvalidFormat(tx)
|
||||
}
|
||||
err = ctr.ValidateBasic()
|
||||
if err != nil {
|
||||
return ctr, err
|
||||
}
|
||||
return ctr, nil
|
||||
}
|
||||
|
||||
// CounterStore
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
type CounterPluginState struct {
|
||||
Counter int
|
||||
TotalFees types.Coins
|
||||
}
|
||||
|
||||
func StateKey() []byte {
|
||||
return []byte(NameCounter + "/state")
|
||||
}
|
||||
|
||||
func LoadState(store types.KVStore) (state CounterPluginState, 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
|
||||
}
|
||||
|
||||
func StoreState(store types.KVStore, state CounterPluginState) error {
|
||||
bytes := wire.BinaryBytes(state)
|
||||
store.Set(StateKey(), bytes)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/tendermint/basecoin/app"
|
||||
"github.com/tendermint/basecoin/modules/coin"
|
||||
"github.com/tendermint/basecoin/stack"
|
||||
"github.com/tendermint/basecoin/txs"
|
||||
"github.com/tendermint/basecoin/types"
|
||||
"github.com/tendermint/go-wire"
|
||||
eyescli "github.com/tendermint/merkleeyes/client"
|
||||
|
@ -18,10 +19,15 @@ import (
|
|||
)
|
||||
|
||||
// TODO: actually handle the counter here...
|
||||
func CounterHandler() basecoin.Handler {
|
||||
func NewCounterHandler() basecoin.Handler {
|
||||
// use the default stack
|
||||
h := coin.NewHandler()
|
||||
return stack.NewDefault().Use(h)
|
||||
coin := coin.NewHandler()
|
||||
counter := CounterHandler{}
|
||||
dispatcher := stack.NewDispatcher(
|
||||
stack.WrapHandler(coin),
|
||||
stack.WrapHandler(counter),
|
||||
)
|
||||
return stack.NewDefault().Use(dispatcher)
|
||||
}
|
||||
|
||||
func TestCounterPlugin(t *testing.T) {
|
||||
|
@ -30,8 +36,11 @@ func TestCounterPlugin(t *testing.T) {
|
|||
// Basecoin initialization
|
||||
eyesCli := eyescli.NewLocalClient("", 0)
|
||||
chainID := "test_chain_id"
|
||||
bcApp := app.NewBasecoin(CounterHandler(), eyesCli,
|
||||
log.TestingLogger().With("module", "app"))
|
||||
bcApp := app.NewBasecoin(
|
||||
NewCounterHandler(),
|
||||
eyesCli,
|
||||
log.TestingLogger().With("module", "app"),
|
||||
)
|
||||
bcApp.SetOption("base/chain_id", chainID)
|
||||
// t.Log(bcApp.Info())
|
||||
|
||||
|
@ -43,68 +52,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: "counter",
|
||||
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 := NewCounterTx(valid, counterFee)
|
||||
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{})
|
||||
res := DeliverCounterTx(true, types.Coins{}, 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, types.Coins{}, 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
|
||||
res = DeliverCounterTx(true, types.Coins{{"gold", 100}}, 2)
|
||||
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}})
|
||||
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
|
||||
|
||||
// TODO: Test unsupported fee
|
||||
// res = DeliverCounterTx(true, types.Coins{{"silver", 100}}, 3)
|
||||
// assert.True(res.IsErr(), res.String())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue