Merge pull request #429 from cosmos/feature/remove_attec
Removes the _attic folder from x/
This commit is contained in:
commit
4412c49546
4
Makefile
4
Makefile
|
@ -1,7 +1,7 @@
|
|||
PACKAGES=$(shell go list ./... | grep -v '/vendor/' | grep -v '_attic')
|
||||
PACKAGES=$(shell go list ./... | grep -v '/vendor/')
|
||||
BUILD_FLAGS = -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=`git rev-parse --short HEAD`"
|
||||
|
||||
all: check_tools get_vendor_deps build test
|
||||
all: check_tools get_vendor_deps build test
|
||||
|
||||
########################################
|
||||
### CI
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
PACKAGES=$(shell go list ./... | grep -v '/vendor/' | grep -v '_attic')
|
||||
PACKAGES=$(shell go list ./... | grep -v '/vendor/')
|
||||
BUILD_FLAGS = -ldflags "-X github.com/cosmos/cosmos-sdk/examples/basecoin/version.GitCommit=`git rev-parse --short HEAD`"
|
||||
|
||||
all: check_tools get_vendor_deps build test
|
||||
all: check_tools get_vendor_deps build test
|
||||
|
||||
########################################
|
||||
### Build
|
||||
|
@ -38,4 +38,4 @@ benchmark:
|
|||
# To avoid unintended conflicts with file names, always add to .PHONY
|
||||
# unless there is a reason not to.
|
||||
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
|
||||
.PHONY: build check_tools get_tools get_vendor_deps test benchmark
|
||||
.PHONY: build check_tools get_tools get_vendor_deps test benchmark
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/tendermint/go-wire/gen"
|
||||
_ "github.com/clipperhouse/stringer"
|
||||
)
|
|
@ -1,44 +0,0 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/query"
|
||||
"github.com/cosmos/cosmos-sdk/modules/eyes"
|
||||
"github.com/cosmos/cosmos-sdk/util"
|
||||
)
|
||||
|
||||
// EyesQueryCmd - command to query raw data
|
||||
var EyesQueryCmd = &cobra.Command{
|
||||
Use: "eyes [key]",
|
||||
Short: "Get data stored under key in eyes",
|
||||
RunE: commands.RequireInit(eyesQueryCmd),
|
||||
}
|
||||
|
||||
func eyesQueryCmd(cmd *cobra.Command, args []string) error {
|
||||
var res eyes.Data
|
||||
|
||||
arg, err := commands.GetOneArg(args, "key")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, err := hex.DecodeString(cmn.StripHex(arg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key = util.PrefixedKey(eyes.Name, key)
|
||||
prove := !viper.GetBool(commands.FlagTrustNode)
|
||||
height, err := query.GetParsed(key, &res, query.GetHeight(), prove)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return query.OutputProof(res, height)
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/txs"
|
||||
"github.com/cosmos/cosmos-sdk/modules/eyes"
|
||||
)
|
||||
|
||||
// SetTxCmd is CLI command to set data
|
||||
var SetTxCmd = &cobra.Command{
|
||||
Use: "set",
|
||||
Short: "Sets a key value pair",
|
||||
RunE: commands.RequireInit(setTxCmd),
|
||||
}
|
||||
|
||||
// RemoveTxCmd is CLI command to remove data
|
||||
var RemoveTxCmd = &cobra.Command{
|
||||
Use: "remove",
|
||||
Short: "Removes a key value pair",
|
||||
RunE: commands.RequireInit(removeTxCmd),
|
||||
}
|
||||
|
||||
const (
|
||||
// FlagKey is the cli flag to set the key
|
||||
FlagKey = "key"
|
||||
// FlagValue is the cli flag to set the value
|
||||
FlagValue = "value"
|
||||
)
|
||||
|
||||
func init() {
|
||||
SetTxCmd.Flags().String(FlagKey, "", "Key to store data under (hex)")
|
||||
SetTxCmd.Flags().String(FlagValue, "", "Data to store (hex)")
|
||||
|
||||
RemoveTxCmd.Flags().String(FlagKey, "", "Key under which to remove data (hex)")
|
||||
}
|
||||
|
||||
// setTxCmd creates a SetTx, wraps, signs, and delivers it
|
||||
func setTxCmd(cmd *cobra.Command, args []string) error {
|
||||
key, err := commands.ParseHexFlag(FlagKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := commands.ParseHexFlag(FlagValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx := eyes.NewSetTx(key, value)
|
||||
return txs.DoTx(tx)
|
||||
}
|
||||
|
||||
// removeTxCmd creates a RemoveTx, wraps, signs, and delivers it
|
||||
func removeTxCmd(cmd *cobra.Command, args []string) error {
|
||||
key, err := commands.ParseHexFlag(FlagKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx := eyes.NewRemoveTx(key)
|
||||
return txs.DoTx(tx)
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package eyes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
errMissingData = fmt.Errorf("All tx fields must be filled")
|
||||
|
||||
malformed = abci.CodeType_EncodingError
|
||||
)
|
||||
|
||||
//nolint
|
||||
func ErrMissingData() errors.TMError {
|
||||
return errors.WithCode(errMissingData, malformed)
|
||||
}
|
||||
func IsMissingDataErr(err error) bool {
|
||||
return errors.IsSameError(errMissingData, err)
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
// Generated by: main
|
||||
// TypeWriter: wrapper
|
||||
// Directive: +gen on EyesTxInner
|
||||
|
||||
package eyes
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-wire/data"
|
||||
)
|
||||
|
||||
// Auto-generated adapters for happily unmarshaling interfaces
|
||||
// Apache License 2.0
|
||||
// Copyright (c) 2017 Ethan Frey (ethan.frey@tendermint.com)
|
||||
|
||||
type EyesTx struct {
|
||||
EyesTxInner "json:\"unwrap\""
|
||||
}
|
||||
|
||||
var EyesTxMapper = data.NewMapper(EyesTx{})
|
||||
|
||||
func (h EyesTx) MarshalJSON() ([]byte, error) {
|
||||
return EyesTxMapper.ToJSON(h.EyesTxInner)
|
||||
}
|
||||
|
||||
func (h *EyesTx) UnmarshalJSON(data []byte) (err error) {
|
||||
parsed, err := EyesTxMapper.FromJSON(data)
|
||||
if err == nil && parsed != nil {
|
||||
h.EyesTxInner = parsed.(EyesTxInner)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Unwrap recovers the concrete interface safely (regardless of levels of embeds)
|
||||
func (h EyesTx) Unwrap() EyesTxInner {
|
||||
hi := h.EyesTxInner
|
||||
for wrap, ok := hi.(EyesTx); ok; wrap, ok = hi.(EyesTx) {
|
||||
hi = wrap.EyesTxInner
|
||||
}
|
||||
return hi
|
||||
}
|
||||
|
||||
func (h EyesTx) Empty() bool {
|
||||
return h.EyesTxInner == nil
|
||||
}
|
||||
|
||||
/*** below are bindings for each implementation ***/
|
||||
|
||||
func init() {
|
||||
EyesTxMapper.RegisterImplementation(SetTx{}, "set", 0x1)
|
||||
}
|
||||
|
||||
func (hi SetTx) Wrap() EyesTx {
|
||||
return EyesTx{hi}
|
||||
}
|
||||
|
||||
func init() {
|
||||
EyesTxMapper.RegisterImplementation(RemoveTx{}, "remove", 0x2)
|
||||
}
|
||||
|
||||
func (hi RemoveTx) Wrap() EyesTx {
|
||||
return EyesTx{hi}
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
package eyes
|
||||
|
||||
import (
|
||||
wire "github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// Name is used to register this module
|
||||
Name = "eyes"
|
||||
// CostSet is the gas needed for the set operation
|
||||
CostSet uint64 = 10
|
||||
// CostRemove is the gas needed for the remove operation
|
||||
CostRemove = 10
|
||||
)
|
||||
|
||||
// Handler allows us to set and remove data
|
||||
type Handler struct{}
|
||||
|
||||
var _ sdk.Handler = Handler{}
|
||||
|
||||
// NewHandler makes a role handler to modify data
|
||||
func NewHandler() Handler {
|
||||
return Handler{}
|
||||
}
|
||||
|
||||
// InitState - sets the genesis state - implements InitStater
|
||||
func (h Handler) InitState(l log.Logger, store sdk.SimpleDB,
|
||||
module, key, value string) (log string, err error) {
|
||||
|
||||
if module != Name {
|
||||
return "", errors.ErrUnknownModule(module)
|
||||
}
|
||||
store.Set([]byte(key), []byte(value))
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// CheckTx verifies if the transaction is properly formated
|
||||
func (h Handler) CheckTx(ctx sdk.Context, store sdk.SimpleDB,
|
||||
msg interface{}) (res sdk.CheckResult, err error) {
|
||||
|
||||
tx := sdk.MustGetTx(msg).(EyesTx)
|
||||
if err := tx.ValidateBasic(); err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
switch tx.Unwrap().(type) {
|
||||
case SetTx:
|
||||
res = sdk.NewCheck(CostSet, "")
|
||||
case RemoveTx:
|
||||
res = sdk.NewCheck(CostRemove, "")
|
||||
default:
|
||||
err = errors.ErrUnknownTxType(tx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeliverTx tries to create a new role.
|
||||
//
|
||||
// Returns an error if the role already exists
|
||||
func (h Handler) DeliverTx(ctx sdk.Context, store sdk.SimpleDB,
|
||||
msg interface{}) (res sdk.DeliverResult, err error) {
|
||||
|
||||
tx := sdk.MustGetTx(msg).(EyesTx)
|
||||
if err := tx.ValidateBasic(); err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
switch t := tx.Unwrap().(type) {
|
||||
case SetTx:
|
||||
res, err = h.doSetTx(ctx, store, t)
|
||||
case RemoveTx:
|
||||
res, err = h.doRemoveTx(ctx, store, t)
|
||||
default:
|
||||
err = errors.ErrUnknownTxType(tx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// doSetTx writes to the store, overwriting any previous value
|
||||
// note that an empty response in DeliverTx is OK with no log or data returned
|
||||
func (h Handler) doSetTx(ctx sdk.Context, store sdk.SimpleDB,
|
||||
tx SetTx) (res sdk.DeliverResult, err error) {
|
||||
|
||||
data := NewData(tx.Value, ctx.BlockHeight())
|
||||
store.Set(tx.Key, wire.BinaryBytes(data))
|
||||
return
|
||||
}
|
||||
|
||||
// doRemoveTx deletes the value from the store and returns the last value
|
||||
// here we let res.Data to return the value over abci
|
||||
func (h Handler) doRemoveTx(ctx sdk.Context, store sdk.SimpleDB,
|
||||
tx RemoveTx) (res sdk.DeliverResult, err error) {
|
||||
|
||||
// we set res.Data so it gets returned to the client over the abci interface
|
||||
res.Data = store.Get(tx.Key)
|
||||
if len(res.Data) != 0 {
|
||||
store.Remove(tx.Key)
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
package eyes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/state"
|
||||
"github.com/cosmos/cosmos-sdk/util"
|
||||
)
|
||||
|
||||
func TestHandler(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
key := []byte("one")
|
||||
val := []byte("foo")
|
||||
var height uint64 = 123
|
||||
|
||||
h := NewHandler()
|
||||
ctx := util.MockContext("role-chain", height)
|
||||
store := state.NewMemKVStore()
|
||||
|
||||
set := sdk.WrapTx(NewSetTx(key, val))
|
||||
remove := sdk.WrapTx(NewRemoveTx(key))
|
||||
invalid := sdk.WrapTx(NewSetTx(nil, nil))
|
||||
|
||||
// make sure pricing makes sense
|
||||
cres, err := h.CheckTx(ctx, store, set)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.True(cres.GasAllocated > 5, "%#v", cres)
|
||||
|
||||
// set the value, no error
|
||||
dres, err := h.DeliverTx(ctx, store, set)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// get the data
|
||||
var data Data
|
||||
bs := store.Get(key)
|
||||
require.NotEmpty(bs)
|
||||
err = wire.ReadBinaryBytes(bs, &data)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(height, data.SetAt)
|
||||
assert.EqualValues(val, data.Value)
|
||||
|
||||
// make sure pricing makes sense
|
||||
cres, err = h.CheckTx(ctx, store, remove)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.True(cres.GasAllocated > 5, "%#v", cres)
|
||||
|
||||
// remove the data returns the same as the above query
|
||||
dres, err = h.DeliverTx(ctx, store, remove)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.EqualValues(bs, dres.Data)
|
||||
|
||||
// make sure invalid fails both ways
|
||||
_, err = h.CheckTx(ctx, store, invalid)
|
||||
require.NotNil(err)
|
||||
_, err = h.DeliverTx(ctx, store, invalid)
|
||||
require.NotNil(err)
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package eyes
|
||||
|
||||
import "github.com/tendermint/go-wire/data"
|
||||
|
||||
// Data is the struct we use to store in the merkle tree
|
||||
type Data struct {
|
||||
// SetAt is the block height this was set at
|
||||
SetAt uint64 `json:"set_at"`
|
||||
// Value is the data that was stored.
|
||||
// data.Bytes is like []byte but json encodes as hex not base64
|
||||
Value data.Bytes `json:"value"`
|
||||
}
|
||||
|
||||
// NewData creates a new Data item
|
||||
func NewData(value []byte, setAt uint64) Data {
|
||||
return Data{
|
||||
SetAt: setAt,
|
||||
Value: value,
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package eyes
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-wire/data"
|
||||
)
|
||||
|
||||
// DO NOT USE THIS INTERFACE.
|
||||
// You probably want to use EyesTx
|
||||
// +gen wrapper:"EyesTx,Impl[SetTx,RemoveTx],set,remove"
|
||||
type EyesTxInner interface {
|
||||
ValidateBasic() error
|
||||
}
|
||||
|
||||
// func init() {
|
||||
// sdk.TxMapper.
|
||||
// RegisterImplementation(SetTx{}, TypeSet, ByteSet).
|
||||
// RegisterImplementation(RemoveTx{}, TypeRemove, ByteRemove)
|
||||
// }
|
||||
|
||||
// SetTx sets a key-value pair
|
||||
type SetTx struct {
|
||||
Key data.Bytes `json:"key"`
|
||||
Value data.Bytes `json:"value"`
|
||||
}
|
||||
|
||||
func NewSetTx(key, value []byte) EyesTx {
|
||||
return SetTx{Key: key, Value: value}.Wrap()
|
||||
}
|
||||
|
||||
// ValidateBasic makes sure it is valid
|
||||
func (t SetTx) ValidateBasic() error {
|
||||
if len(t.Key) == 0 || len(t.Value) == 0 {
|
||||
return ErrMissingData()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveTx deletes the value at this key, returns old value
|
||||
type RemoveTx struct {
|
||||
Key data.Bytes `json:"key"`
|
||||
}
|
||||
|
||||
func NewRemoveTx(key []byte) EyesTx {
|
||||
return RemoveTx{Key: key}.Wrap()
|
||||
}
|
||||
|
||||
// ValidateBasic makes sure it is valid
|
||||
func (t RemoveTx) ValidateBasic() error {
|
||||
if len(t.Key) == 0 {
|
||||
return ErrMissingData()
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs"
|
||||
"github.com/cosmos/cosmos-sdk/modules/coin"
|
||||
"github.com/cosmos/cosmos-sdk/modules/fee"
|
||||
)
|
||||
|
||||
//nolint
|
||||
const (
|
||||
FlagFee = "fee"
|
||||
FlagPayer = "payer"
|
||||
)
|
||||
|
||||
// FeeWrapper wraps a tx with an optional fee payment
|
||||
type FeeWrapper struct{}
|
||||
|
||||
var _ txcmd.Wrapper = FeeWrapper{}
|
||||
|
||||
// Wrap checks for FlagFee and if present wraps the tx with a
|
||||
// FeeTx of the given amount, paid by the signer
|
||||
func (FeeWrapper) Wrap(tx sdk.Tx) (res sdk.Tx, err error) {
|
||||
//parse the fee and amounts into coin types
|
||||
toll, err := coin.ParseCoin(viper.GetString(FlagFee))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
// if no fee, do nothing, otherwise wrap it
|
||||
if toll.IsZero() {
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
payer, err := readPayer()
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
res = fee.NewFee(tx, toll, payer)
|
||||
return
|
||||
}
|
||||
|
||||
// Register adds the sequence flags to the cli
|
||||
func (FeeWrapper) Register(fs *pflag.FlagSet) {
|
||||
fs.String(FlagFee, "0mycoin", "Coins for the transaction fee of the format <amt><coin>")
|
||||
fs.String(FlagPayer, "", "Account to pay fee if not current signer (for multisig)")
|
||||
}
|
||||
|
||||
func readPayer() (sdk.Actor, error) {
|
||||
payer := viper.GetString(FlagPayer)
|
||||
if payer == "" {
|
||||
return txcmd.GetSignerAct(), nil
|
||||
}
|
||||
return commands.ParseActor(payer)
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package fee
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
e := ErrInsufficientFees()
|
||||
assert.True(IsInsufficientFeesErr(e))
|
||||
assert.False(IsWrongFeeDenomErr(e))
|
||||
|
||||
e2 := ErrWrongFeeDenom("atom")
|
||||
assert.False(IsInsufficientFeesErr(e2))
|
||||
assert.True(IsWrongFeeDenomErr(e2))
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
//nolint
|
||||
package fee
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
errInsufficientFees = fmt.Errorf("Insufficient fees")
|
||||
errWrongFeeDenom = fmt.Errorf("Required fee denomination")
|
||||
errSkipFees = fmt.Errorf("Skip fees")
|
||||
|
||||
invalidInput = abci.CodeType_BaseInvalidInput
|
||||
)
|
||||
|
||||
func ErrInsufficientFees() errors.TMError {
|
||||
return errors.WithCode(errInsufficientFees, invalidInput)
|
||||
}
|
||||
func IsInsufficientFeesErr(err error) bool {
|
||||
return errors.IsSameError(errInsufficientFees, err)
|
||||
}
|
||||
|
||||
func ErrWrongFeeDenom(denom string) errors.TMError {
|
||||
return errors.WithMessage(denom, errWrongFeeDenom, invalidInput)
|
||||
}
|
||||
func IsWrongFeeDenomErr(err error) bool {
|
||||
return errors.IsSameError(errWrongFeeDenom, err)
|
||||
}
|
||||
|
||||
func ErrSkipFees() errors.TMError {
|
||||
return errors.WithCode(errSkipFees, invalidInput)
|
||||
}
|
||||
func IsSkipFeesErr(err error) bool {
|
||||
return errors.IsSameError(errSkipFees, err)
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
package fee
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/errors"
|
||||
"github.com/cosmos/cosmos-sdk/modules/coin"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
"github.com/cosmos/cosmos-sdk/state"
|
||||
)
|
||||
|
||||
// NameFee - namespace for the fee module
|
||||
const NameFee = "fee"
|
||||
|
||||
// Bank is a default location for the fees, but pass anything into
|
||||
// the middleware constructor
|
||||
var Bank = sdk.Actor{App: NameFee, Address: []byte("bank")}
|
||||
|
||||
// SimpleFeeMiddleware - middleware for fee checking, constant amount
|
||||
// It used modules.coin to move the money
|
||||
type SimpleFeeMiddleware struct {
|
||||
// the fee must be the same denomination and >= this amount
|
||||
// if the amount is 0, then the fee tx wrapper is optional
|
||||
MinFee coin.Coin
|
||||
// all fees go here, which could be a dump (Bank) or something reachable
|
||||
// by other app logic
|
||||
Collector sdk.Actor
|
||||
stack.PassInitState
|
||||
stack.PassInitValidate
|
||||
}
|
||||
|
||||
var _ stack.Middleware = SimpleFeeMiddleware{}
|
||||
|
||||
// NewSimpleFeeMiddleware returns a fee handler with a fixed minimum fee.
|
||||
//
|
||||
// If minFee is 0, then the FeeTx is optional
|
||||
func NewSimpleFeeMiddleware(minFee coin.Coin, collector sdk.Actor) SimpleFeeMiddleware {
|
||||
return SimpleFeeMiddleware{
|
||||
MinFee: minFee,
|
||||
Collector: collector,
|
||||
}
|
||||
}
|
||||
|
||||
// Name - return the namespace for the fee module
|
||||
func (SimpleFeeMiddleware) Name() string {
|
||||
return NameFee
|
||||
}
|
||||
|
||||
// CheckTx - check the transaction
|
||||
func (h SimpleFeeMiddleware) CheckTx(ctx sdk.Context, store state.SimpleDB, tx sdk.Tx, next sdk.Checker) (res sdk.CheckResult, err error) {
|
||||
fee, err := h.verifyFee(ctx, tx)
|
||||
if err != nil {
|
||||
if IsSkipFeesErr(err) {
|
||||
return next.CheckTx(ctx, store, tx)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
var paid, used uint64
|
||||
if !fee.Fee.IsZero() { // now, try to make a IPC call to coins...
|
||||
send := coin.NewSendOneTx(fee.Payer, h.Collector, coin.Coins{fee.Fee})
|
||||
sendRes, err := next.CheckTx(ctx, store, send)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
paid = uint64(fee.Fee.Amount)
|
||||
used = sendRes.GasAllocated
|
||||
}
|
||||
|
||||
res, err = next.CheckTx(ctx, store, fee.Tx)
|
||||
// add the given fee to the price for gas, plus one query
|
||||
if err == nil {
|
||||
res.GasPayment += paid
|
||||
res.GasAllocated += used
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
// DeliverTx - send the fee handler transaction
|
||||
func (h SimpleFeeMiddleware) DeliverTx(ctx sdk.Context, store state.SimpleDB, tx sdk.Tx, next sdk.Deliver) (res sdk.DeliverResult, err error) {
|
||||
fee, err := h.verifyFee(ctx, tx)
|
||||
if IsSkipFeesErr(err) {
|
||||
return next.DeliverTx(ctx, store, tx)
|
||||
}
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
if !fee.Fee.IsZero() { // now, try to make a IPC call to coins...
|
||||
send := coin.NewSendOneTx(fee.Payer, h.Collector, coin.Coins{fee.Fee})
|
||||
_, err = next.DeliverTx(ctx, store, send)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
return next.DeliverTx(ctx, store, fee.Tx)
|
||||
}
|
||||
|
||||
func (h SimpleFeeMiddleware) verifyFee(ctx sdk.Context, tx sdk.Tx) (Fee, error) {
|
||||
feeTx, ok := tx.Unwrap().(Fee)
|
||||
if !ok {
|
||||
// the fee wrapper is not required if there is no minimum
|
||||
if h.MinFee.IsZero() {
|
||||
return feeTx, ErrSkipFees()
|
||||
}
|
||||
return feeTx, errors.ErrInvalidFormat(TypeFees, tx)
|
||||
}
|
||||
|
||||
// see if it is the proper denom and big enough
|
||||
fee := feeTx.Fee
|
||||
if fee.Denom != h.MinFee.Denom {
|
||||
return feeTx, ErrWrongFeeDenom(h.MinFee.Denom)
|
||||
}
|
||||
if !fee.IsGTE(h.MinFee) {
|
||||
return feeTx, ErrInsufficientFees()
|
||||
}
|
||||
return feeTx, nil
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
package fee_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/modules/coin"
|
||||
"github.com/cosmos/cosmos-sdk/modules/fee"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
"github.com/cosmos/cosmos-sdk/state"
|
||||
)
|
||||
|
||||
func TestFeeChecks(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
atom := func(i int64) coin.Coin { return coin.Coin{"atom", i} }
|
||||
eth := func(i int64) coin.Coin { return coin.Coin{"eth", i} }
|
||||
atoms := func(i int64) coin.Coins { return coin.Coins{{"atom", i}} }
|
||||
wallet := func(i, j int64) coin.Coins { return coin.Coins{atom(i), eth(j)} }
|
||||
|
||||
// some coin amounts...
|
||||
zero := coin.Coin{}
|
||||
mixed := wallet(1200, 55)
|
||||
pure := atoms(46657)
|
||||
|
||||
// these are some accounts
|
||||
collector := sdk.Actor{App: fee.NameFee, Address: []byte("mine")}
|
||||
key1 := coin.NewAccountWithKey(mixed)
|
||||
key2 := coin.NewAccountWithKey(pure)
|
||||
act1, act2 := key1.Actor(), key2.Actor()
|
||||
|
||||
// set up the apps....
|
||||
disp := stack.NewDispatcher(
|
||||
// OKHandler will just return success to a RawTx
|
||||
stack.WrapHandler(stack.OKHandler{}),
|
||||
// coin is needed to handle the IPC call from Fee middleware
|
||||
coin.NewHandler(),
|
||||
)
|
||||
// app1 requires no fees
|
||||
app1 := stack.New(fee.NewSimpleFeeMiddleware(atom(0), collector)).Use(disp)
|
||||
// app2 requires 2 atom
|
||||
app2 := stack.New(fee.NewSimpleFeeMiddleware(atom(2), collector)).Use(disp)
|
||||
|
||||
// set up the store and init the accounts
|
||||
store := state.NewMemKVStore()
|
||||
l := log.NewNopLogger()
|
||||
_, err := app1.InitState(l, store, "coin", "account", key1.MakeOption())
|
||||
require.Nil(err, "%+v", err)
|
||||
_, err = app2.InitState(l, store, "coin", "account", key2.MakeOption())
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// feeCost is what we expect if the fee is properly paid
|
||||
feeCost := coin.CostSend * 2
|
||||
cases := []struct {
|
||||
valid bool
|
||||
// this is the middleware stack to test
|
||||
app sdk.Handler
|
||||
// they sign the tx
|
||||
signer sdk.Actor
|
||||
// wrap with the given fee if hasFee is true
|
||||
hasFee bool
|
||||
payer sdk.Actor
|
||||
fee coin.Coin
|
||||
// expected balance after the tx
|
||||
left coin.Coins
|
||||
collected coin.Coins
|
||||
// expected gas allocated
|
||||
expectedCost uint64
|
||||
}{
|
||||
// make sure it works with no fee (control group)
|
||||
{true, app1, act1, false, act1, zero, mixed, nil, 0},
|
||||
{true, app1, act2, false, act2, zero, pure, nil, 0},
|
||||
|
||||
// no fee or too low is rejected
|
||||
{false, app2, act1, false, act1, zero, mixed, nil, 0},
|
||||
{false, app2, act2, false, act2, zero, pure, nil, 0},
|
||||
{false, app2, act1, true, act1, zero, mixed, nil, 0},
|
||||
{false, app2, act2, true, act2, atom(1), pure, nil, 0},
|
||||
|
||||
// proper fees are transfered in both cases
|
||||
{true, app1, act1, true, act1, atom(1), wallet(1199, 55), atoms(1), feeCost},
|
||||
{true, app2, act2, true, act2, atom(57), atoms(46600), atoms(58), feeCost},
|
||||
|
||||
// // fee must be the proper type...
|
||||
{false, app1, act1, true, act1, eth(5), wallet(1199, 55), atoms(58), 0},
|
||||
|
||||
// signature must match
|
||||
{false, app2, act1, true, act2, atom(5), atoms(46600), atoms(58), 0},
|
||||
|
||||
// send only works within limits
|
||||
{true, app2, act1, true, act1, atom(1100), wallet(99, 55), atoms(1158), feeCost},
|
||||
{false, app2, act1, true, act1, atom(500), wallet(99, 55), atoms(1158), 0},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
// build the tx
|
||||
tx := stack.NewRawTx([]byte{7, 8, 9})
|
||||
if tc.hasFee {
|
||||
tx = fee.NewFee(tx, tc.fee, tc.payer)
|
||||
}
|
||||
|
||||
// set up the permissions
|
||||
ctx := stack.MockContext("x", 1).WithPermissions(tc.signer)
|
||||
|
||||
// call checktx...
|
||||
cres, err := tc.app.CheckTx(ctx, store, tx)
|
||||
if tc.valid {
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
assert.EqualValues(tc.fee.Amount, cres.GasPayment)
|
||||
assert.EqualValues(tc.expectedCost, cres.GasAllocated)
|
||||
} else {
|
||||
assert.NotNil(err, "%d", i)
|
||||
}
|
||||
|
||||
// call delivertx...
|
||||
_, err = tc.app.DeliverTx(ctx, store, tx)
|
||||
if tc.valid {
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
} else {
|
||||
assert.NotNil(err, "%d", i)
|
||||
}
|
||||
|
||||
// check the account balance afterwards....
|
||||
cspace := stack.PrefixedStore(coin.NameCoin, store)
|
||||
acct, err := coin.GetAccount(cspace, tc.payer)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
assert.Equal(tc.left, acct.Coins, "%d", i)
|
||||
|
||||
// check the collected balance afterwards....
|
||||
acct, err = coin.GetAccount(cspace, collector)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
assert.Equal(tc.collected, acct.Coins, "%d", i)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package fee
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/modules/coin"
|
||||
)
|
||||
|
||||
// nolint
|
||||
const (
|
||||
ByteFees = 0x28
|
||||
TypeFees = NameFee + "/tx"
|
||||
)
|
||||
|
||||
func init() {
|
||||
sdk.TxMapper.
|
||||
RegisterImplementation(Fee{}, TypeFees, ByteFees)
|
||||
}
|
||||
|
||||
/**** Fee ****/
|
||||
|
||||
// Fee attaches a fee payment to the embedded tx
|
||||
type Fee struct {
|
||||
// Gas coin.Coin `json:"gas"` // ?????
|
||||
Fee coin.Coin `json:"fee"`
|
||||
Payer sdk.Actor `json:"payer"` // the address who pays the fee
|
||||
Tx sdk.Tx `json:"tx"`
|
||||
}
|
||||
|
||||
// NewFee wraps a tx with a promised fee from this actor
|
||||
func NewFee(tx sdk.Tx, fee coin.Coin, payer sdk.Actor) sdk.Tx {
|
||||
return Fee{Tx: tx, Fee: fee, Payer: payer}.Wrap()
|
||||
}
|
||||
|
||||
// nolint - TxInner Functions
|
||||
func (f Fee) ValidateBasic() error {
|
||||
// TODO: more checks
|
||||
return f.Tx.ValidateBasic()
|
||||
}
|
||||
func (f Fee) Wrap() sdk.Tx {
|
||||
return sdk.Tx{f}
|
||||
}
|
||||
func (f Fee) Next() sdk.Tx {
|
||||
return f.Tx
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
)
|
||||
|
||||
// Logger writes out log messages on every request
|
||||
type Logger struct{}
|
||||
|
||||
var _ sdk.Decorator = Logger{}
|
||||
|
||||
// CheckTx logs time and result - fulfills Middlware interface
|
||||
func (Logger) CheckTx(ctx sdk.Context, store sdk.SimpleDB, tx interface{}, next sdk.Checker) (res sdk.CheckResult, err error) {
|
||||
start := time.Now()
|
||||
res, err = next.CheckTx(ctx, store, tx)
|
||||
delta := time.Now().Sub(start)
|
||||
// TODO: log some info on the tx itself?
|
||||
l := ctx.With("duration", micros(delta))
|
||||
if err == nil {
|
||||
l.Debug("CheckTx", "log", res.Log)
|
||||
} else {
|
||||
l.Info("CheckTx", "err", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeliverTx logs time and result - fulfills Middlware interface
|
||||
func (Logger) DeliverTx(ctx sdk.Context, store sdk.SimpleDB, tx interface{}, next sdk.Deliverer) (res sdk.DeliverResult, err error) {
|
||||
start := time.Now()
|
||||
res, err = next.DeliverTx(ctx, store, tx)
|
||||
delta := time.Now().Sub(start)
|
||||
// TODO: log some info on the tx itself?
|
||||
l := ctx.With("duration", micros(delta))
|
||||
if err == nil {
|
||||
l.Info("DeliverTx", "log", res.Log)
|
||||
} else {
|
||||
l.Error("DeliverTx", "err", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// LogTicker wraps any ticker and records the time it takes.
|
||||
// Pass in a name to be logged with this to separate out various
|
||||
// tickers
|
||||
func LogTicker(clock sdk.Ticker, name string) sdk.Ticker {
|
||||
res := func(ctx sdk.Context, s sdk.SimpleDB) ([]*abci.Validator, error) {
|
||||
start := time.Now()
|
||||
vals, err := clock.Tick(ctx, s)
|
||||
delta := time.Now().Sub(start)
|
||||
l := ctx.With("duration", micros(delta))
|
||||
if name != "" {
|
||||
l = l.With("name", name)
|
||||
}
|
||||
if err == nil {
|
||||
l.Info("Tock")
|
||||
} else {
|
||||
l.Error("Tock", "err", err)
|
||||
}
|
||||
return vals, err
|
||||
}
|
||||
return sdk.TickerFunc(res)
|
||||
}
|
||||
|
||||
// micros returns how many microseconds passed in a call
|
||||
func micros(d time.Duration) int {
|
||||
return int(d.Seconds() * 1000000)
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/query"
|
||||
"github.com/cosmos/cosmos-sdk/modules/nonce"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
)
|
||||
|
||||
// NonceQueryCmd - command to query an nonce account
|
||||
var NonceQueryCmd = &cobra.Command{
|
||||
Use: "nonce [address]",
|
||||
Short: "Get details of a nonce sequence number, with proof",
|
||||
RunE: commands.RequireInit(nonceQueryCmd),
|
||||
}
|
||||
|
||||
func nonceQueryCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("Missing required argument [address]")
|
||||
}
|
||||
addr := strings.Join(args, ",")
|
||||
|
||||
signers, err := commands.ParseActors(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
seq, height, err := doNonceQuery(signers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return query.OutputProof(seq, height)
|
||||
}
|
||||
|
||||
func doNonceQuery(signers []sdk.Actor) (sequence uint32, height uint64, err error) {
|
||||
key := stack.PrefixedKey(nonce.NameNonce, nonce.GetSeqKey(signers))
|
||||
prove := !viper.GetBool(commands.FlagTrustNode)
|
||||
height, err = query.GetParsed(key, &sequence, query.GetHeight(), prove)
|
||||
if client.IsNoDataErr(err) {
|
||||
// no data, return sequence 0
|
||||
return 0, 0, nil
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs"
|
||||
"github.com/cosmos/cosmos-sdk/modules/nonce"
|
||||
)
|
||||
|
||||
// nolint
|
||||
const (
|
||||
FlagSequence = "sequence"
|
||||
FlagNonceKey = "nonce-key"
|
||||
)
|
||||
|
||||
// NonceWrapper wraps a tx with a nonce
|
||||
type NonceWrapper struct{}
|
||||
|
||||
var _ txcmd.Wrapper = NonceWrapper{}
|
||||
|
||||
// Wrap grabs the sequence number from the flag and wraps
|
||||
// the tx with this nonce. Grabs the permission from the signer,
|
||||
// as we still only support single sig on the cli
|
||||
func (NonceWrapper) Wrap(tx sdk.Tx) (res sdk.Tx, err error) {
|
||||
|
||||
signers, err := readNonceKey()
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
seq, err := readSequence(signers)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
res = nonce.NewTx(seq, signers, tx)
|
||||
return
|
||||
}
|
||||
|
||||
// Register adds the sequence flags to the cli
|
||||
func (NonceWrapper) Register(fs *pflag.FlagSet) {
|
||||
fs.Int(FlagSequence, -1, "Sequence number for this transaction")
|
||||
fs.String(FlagNonceKey, "", "Set of comma-separated addresses for the nonce (for multisig)")
|
||||
}
|
||||
|
||||
func readNonceKey() ([]sdk.Actor, error) {
|
||||
nonce := viper.GetString(FlagNonceKey)
|
||||
if nonce == "" {
|
||||
return []sdk.Actor{txcmd.GetSignerAct()}, nil
|
||||
}
|
||||
return commands.ParseActors(nonce)
|
||||
}
|
||||
|
||||
// read the sequence from the flag or query for it if flag is -1
|
||||
func readSequence(signers []sdk.Actor) (seq uint32, err error) {
|
||||
//add the nonce tx layer to the tx
|
||||
seqFlag := viper.GetInt(FlagSequence)
|
||||
|
||||
switch {
|
||||
case seqFlag > 0:
|
||||
seq = uint32(seqFlag)
|
||||
|
||||
case seqFlag == -1:
|
||||
//autocalculation for default sequence
|
||||
seq, _, err = doNonceQuery(signers)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//increase the sequence by 1!
|
||||
seq++
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("sequence must be either greater than 0, or -1 for autocalculation")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
//nolint
|
||||
package nonce
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoNonce = fmt.Errorf("Tx doesn't contain nonce")
|
||||
errNotMember = fmt.Errorf("Nonce contains non-permissioned member")
|
||||
errZeroSequence = fmt.Errorf("Sequence number cannot be zero")
|
||||
errNoSigners = fmt.Errorf("There are no signers")
|
||||
errTxEmpty = fmt.Errorf("The provided Tx is empty")
|
||||
|
||||
unauthorized = abci.CodeType_Unauthorized
|
||||
badNonce = abci.CodeType_BadNonce
|
||||
invalidInput = abci.CodeType_BaseInvalidInput
|
||||
)
|
||||
|
||||
func ErrBadNonce(got, expected uint32) errors.TMError {
|
||||
return errors.WithCode(fmt.Errorf("Bad nonce sequence, got %d, expected %d", got, expected), badNonce)
|
||||
}
|
||||
func ErrNoNonce() errors.TMError {
|
||||
return errors.WithCode(errNoNonce, badNonce)
|
||||
}
|
||||
func ErrNotMember() errors.TMError {
|
||||
return errors.WithCode(errNotMember, unauthorized)
|
||||
}
|
||||
func ErrZeroSequence() errors.TMError {
|
||||
return errors.WithCode(errZeroSequence, invalidInput)
|
||||
}
|
||||
func ErrNoSigners() errors.TMError {
|
||||
return errors.WithCode(errNoSigners, invalidInput)
|
||||
}
|
||||
func ErrTxEmpty() errors.TMError {
|
||||
return errors.WithCode(errTxEmpty, invalidInput)
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
package nonce
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
"github.com/cosmos/cosmos-sdk/state"
|
||||
)
|
||||
|
||||
//nolint
|
||||
const (
|
||||
NameNonce = "nonce"
|
||||
CostNonce = 10
|
||||
)
|
||||
|
||||
// ReplayCheck uses the sequence to check for replay attacks
|
||||
type ReplayCheck struct {
|
||||
stack.PassInitState
|
||||
stack.PassInitValidate
|
||||
}
|
||||
|
||||
// Name of the module - fulfills Middleware interface
|
||||
func (ReplayCheck) Name() string {
|
||||
return NameNonce
|
||||
}
|
||||
|
||||
var _ stack.Middleware = ReplayCheck{}
|
||||
|
||||
// CheckTx verifies tx is not being replayed - fulfills Middlware interface
|
||||
func (r ReplayCheck) CheckTx(ctx sdk.Context, store state.SimpleDB,
|
||||
tx sdk.Tx, next sdk.Checker) (res sdk.CheckResult, err error) {
|
||||
|
||||
stx, err := r.checkIncrementNonceTx(ctx, store, tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
res, err = next.CheckTx(ctx, store, stx)
|
||||
res.GasAllocated += CostNonce
|
||||
return res, err
|
||||
}
|
||||
|
||||
// DeliverTx verifies tx is not being replayed - fulfills Middlware interface
|
||||
// NOTE It is okay to modify the sequence before running the wrapped TX because if the
|
||||
// wrapped Tx fails, the state changes are not applied
|
||||
func (r ReplayCheck) DeliverTx(ctx sdk.Context, store state.SimpleDB,
|
||||
tx sdk.Tx, next sdk.Deliver) (res sdk.DeliverResult, err error) {
|
||||
|
||||
stx, err := r.checkIncrementNonceTx(ctx, store, tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
return next.DeliverTx(ctx, store, stx)
|
||||
}
|
||||
|
||||
// checkNonceTx varifies the nonce sequence, an increment sequence number
|
||||
func (r ReplayCheck) checkIncrementNonceTx(ctx sdk.Context, store state.SimpleDB,
|
||||
tx sdk.Tx) (sdk.Tx, error) {
|
||||
|
||||
// make sure it is a the nonce Tx (Tx from this package)
|
||||
nonceTx, ok := tx.Unwrap().(Tx)
|
||||
if !ok {
|
||||
return tx, ErrNoNonce()
|
||||
}
|
||||
|
||||
err := nonceTx.ValidateBasic()
|
||||
if err != nil {
|
||||
return tx, err
|
||||
}
|
||||
|
||||
// check the nonce sequence number
|
||||
err = nonceTx.CheckIncrementSeq(ctx, store)
|
||||
if err != nil {
|
||||
return tx, err
|
||||
}
|
||||
return nonceTx.Tx, nil
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/query"
|
||||
"github.com/cosmos/cosmos-sdk/errors"
|
||||
"github.com/cosmos/cosmos-sdk/modules/coin"
|
||||
"github.com/cosmos/cosmos-sdk/modules/nonce"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
// doQueryNonce is the HTTP handlerfunc to query a nonce
|
||||
func doQueryNonce(w http.ResponseWriter, r *http.Request) {
|
||||
args := mux.Vars(r)
|
||||
signature := args["signature"]
|
||||
actor, err := commands.ParseActor(signature)
|
||||
if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var h int
|
||||
qHeight := r.URL.Query().Get("height")
|
||||
if qHeight != "" {
|
||||
h, err = strconv.Atoi(qHeight)
|
||||
if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
actor = coin.ChainAddr(actor)
|
||||
key := nonce.GetSeqKey([]sdk.Actor{actor})
|
||||
key = stack.PrefixedKey(nonce.NameNonce, key)
|
||||
|
||||
prove := !viper.GetBool(commands.FlagTrustNode)
|
||||
|
||||
// query sequence number
|
||||
data, height, err := query.Get(key, h, prove)
|
||||
if client.IsNoDataErr(err) {
|
||||
err = fmt.Errorf("nonce empty for address: %q", signature)
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
} else if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// unmarshal sequence number
|
||||
var seq uint32
|
||||
err = wire.ReadBinaryBytes(data, &seq)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Error reading sequence for %X", key)
|
||||
common.WriteError(w, errors.ErrInternal(msg))
|
||||
return
|
||||
}
|
||||
|
||||
// write result to client
|
||||
if err := query.FoutputProof(w, seq, height); err != nil {
|
||||
common.WriteError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
// mux.Router registrars
|
||||
|
||||
// RegisterQueryNonce is a mux.Router handler that exposes GET
|
||||
// method access on route /query/nonce/{signature} to query nonces
|
||||
func RegisterQueryNonce(r *mux.Router) error {
|
||||
r.HandleFunc("/query/nonce/{signature}", doQueryNonce).Methods("GET")
|
||||
return nil
|
||||
}
|
||||
|
||||
// End of mux.Router registrars
|
|
@ -1,30 +0,0 @@
|
|||
package nonce
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/errors"
|
||||
"github.com/cosmos/cosmos-sdk/state"
|
||||
)
|
||||
|
||||
func getSeq(store state.SimpleDB, key []byte) (seq uint32, err error) {
|
||||
data := store.Get(key)
|
||||
if len(data) == 0 {
|
||||
//if the key is not stored, its a new key with a sequence of zero!
|
||||
return 0, nil
|
||||
}
|
||||
err = wire.ReadBinaryBytes(data, &seq)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Error reading sequence for %X", key)
|
||||
return seq, errors.ErrInternal(msg)
|
||||
}
|
||||
return seq, nil
|
||||
}
|
||||
|
||||
func setSeq(store state.SimpleDB, key []byte, seq uint32) error {
|
||||
bin := wire.BinaryBytes(seq)
|
||||
store.Set(key, bin)
|
||||
return nil // real stores can return error...
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
/*
|
||||
Package nonce - This module allows replay protection to be added to process stack.
|
||||
This is achieved through the use of a sequence number for each unique set of signers.
|
||||
Note that the sequence number for the single signing account "foo" will be unique
|
||||
from the sequence number for a multi-sig account {"foo", "bar"} which would also be
|
||||
unique from a different multi-sig account {"foo", "soup"}
|
||||
*/
|
||||
package nonce
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/state"
|
||||
)
|
||||
|
||||
// nolint
|
||||
const (
|
||||
ByteNonce = 0x69 //TODO overhaul byte assign system don't make no sense!
|
||||
TypeNonce = "nonce"
|
||||
)
|
||||
|
||||
func init() {
|
||||
sdk.TxMapper.RegisterImplementation(Tx{}, TypeNonce, ByteNonce)
|
||||
}
|
||||
|
||||
// Tx - Nonce transaction structure, contains list of signers and current sequence number
|
||||
type Tx struct {
|
||||
Sequence uint32 `json:"sequence"`
|
||||
Signers []sdk.Actor `json:"signers"`
|
||||
Tx sdk.Tx `json:"tx"`
|
||||
}
|
||||
|
||||
var _ sdk.TxInner = &Tx{}
|
||||
|
||||
// NewTx wraps the tx with a signable nonce
|
||||
func NewTx(sequence uint32, signers []sdk.Actor, tx sdk.Tx) sdk.Tx {
|
||||
return (Tx{
|
||||
Sequence: sequence,
|
||||
Signers: signers,
|
||||
Tx: tx,
|
||||
}).Wrap()
|
||||
}
|
||||
|
||||
//nolint
|
||||
func (n Tx) Wrap() sdk.Tx {
|
||||
return sdk.Tx{n}
|
||||
}
|
||||
func (n Tx) ValidateBasic() error {
|
||||
switch {
|
||||
case n.Tx.Empty():
|
||||
return ErrTxEmpty()
|
||||
case n.Sequence == 0:
|
||||
return ErrZeroSequence()
|
||||
case len(n.Signers) == 0:
|
||||
return ErrNoSigners()
|
||||
}
|
||||
return n.Tx.ValidateBasic()
|
||||
}
|
||||
|
||||
// CheckIncrementSeq - Check that the sequence number is one more than the state sequence number
|
||||
// and further increment the sequence number
|
||||
// NOTE It is okay to modify the sequence before running the wrapped TX because if the
|
||||
// wrapped Tx fails, the state changes are not applied
|
||||
func (n Tx) CheckIncrementSeq(ctx sdk.Context, store state.SimpleDB) error {
|
||||
|
||||
seqKey := n.getSeqKey()
|
||||
|
||||
// check the current state
|
||||
cur, err := getSeq(store, seqKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n.Sequence != cur+1 {
|
||||
return ErrBadNonce(n.Sequence, cur+1)
|
||||
}
|
||||
|
||||
// make sure they all signed
|
||||
for _, s := range n.Signers {
|
||||
if !ctx.HasPermission(s) {
|
||||
return ErrNotMember()
|
||||
}
|
||||
}
|
||||
|
||||
// increment the sequence by 1
|
||||
err = setSeq(store, seqKey, cur+1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n Tx) getSeqKey() (seqKey []byte) {
|
||||
return GetSeqKey(n.Signers)
|
||||
}
|
||||
|
||||
// GetSeqKey - Generate the sequence key as the concatenated list of signers, sorted by address.
|
||||
func GetSeqKey(signers []sdk.Actor) (seqKey []byte) {
|
||||
|
||||
// First copy the list of signers to sort as sort is done in place
|
||||
signers2sort := make([]sdk.Actor, len(signers))
|
||||
copy(signers2sort, signers)
|
||||
sort.Sort(sdk.ByAll(signers))
|
||||
|
||||
for _, signer := range signers {
|
||||
seqKey = append(seqKey, signer.Bytes()...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
package nonce
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
"github.com/cosmos/cosmos-sdk/state"
|
||||
)
|
||||
|
||||
func TestNonce(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
// generic args here...
|
||||
chainID := "my-chain"
|
||||
chain2ID := "woohoo"
|
||||
|
||||
height := uint64(100)
|
||||
ctx := stack.MockContext(chainID, height)
|
||||
store := state.NewMemKVStore()
|
||||
|
||||
appName1 := "fooz"
|
||||
appName2 := "foot"
|
||||
|
||||
//root actors for the tests
|
||||
act1 := sdk.Actor{ChainID: chainID, App: appName1, Address: []byte{1, 2, 3, 4}}
|
||||
act2 := sdk.Actor{ChainID: chainID, App: appName1, Address: []byte{1, 1, 1, 1}}
|
||||
act3 := sdk.Actor{ChainID: chainID, App: appName1, Address: []byte{3, 3, 3, 3}}
|
||||
act1DiffChain := sdk.Actor{ChainID: chain2ID, App: appName1, Address: []byte{1, 2, 3, 4}}
|
||||
act2DiffChain := sdk.Actor{ChainID: chain2ID, App: appName1, Address: []byte{1, 1, 1, 1}}
|
||||
act3DiffChain := sdk.Actor{ChainID: chain2ID, App: appName1, Address: []byte{3, 3, 3, 3}}
|
||||
act1DiffApp := sdk.Actor{ChainID: chainID, App: appName2, Address: []byte{1, 2, 3, 4}}
|
||||
act2DiffApp := sdk.Actor{ChainID: chainID, App: appName2, Address: []byte{1, 1, 1, 1}}
|
||||
act3DiffApp := sdk.Actor{ChainID: chainID, App: appName2, Address: []byte{3, 3, 3, 3}}
|
||||
|
||||
// let's construct some tests to make the table a bit less verbose
|
||||
set0 := []sdk.Actor{}
|
||||
set1 := []sdk.Actor{act1}
|
||||
set2 := []sdk.Actor{act2}
|
||||
set12 := []sdk.Actor{act1, act2}
|
||||
set21 := []sdk.Actor{act2, act1}
|
||||
set123 := []sdk.Actor{act1, act2, act3}
|
||||
set321 := []sdk.Actor{act3, act2, act1}
|
||||
|
||||
//some more test cases for different chains and apps for each actor
|
||||
set123Chain2 := []sdk.Actor{act1DiffChain, act2DiffChain, act3DiffChain}
|
||||
set123App2 := []sdk.Actor{act1DiffApp, act2DiffApp, act3DiffApp}
|
||||
set123MixedChains := []sdk.Actor{act1, act2DiffChain, act3}
|
||||
set123MixedApps := []sdk.Actor{act1, act2DiffApp, act3}
|
||||
|
||||
testList := []struct {
|
||||
valid bool
|
||||
seq uint32
|
||||
actors []sdk.Actor
|
||||
signers []sdk.Actor
|
||||
}{
|
||||
// one signer
|
||||
{false, 0, set1, set1}, // seq 0 is no good
|
||||
{false, 1, set1, set0}, // sig is required
|
||||
{true, 1, set1, set1}, // sig and seq are good
|
||||
{true, 2, set1, set1}, // increments each time
|
||||
{false, 777, set1, set1}, // seq is too high
|
||||
|
||||
// independent from second signer
|
||||
{false, 1, set2, set1}, // sig must match
|
||||
{true, 1, set2, set2}, // seq of set2 independent from set1
|
||||
{true, 2, set2, set321}, // extra sigs don't change the situation
|
||||
|
||||
// multisig has same requirements
|
||||
{false, 0, set12, set12}, // need valid sequence number
|
||||
{false, 1, set12, set2}, // they all must sign
|
||||
{true, 1, set12, set12}, // this is proper, independent of act1 and act2
|
||||
{true, 2, set21, set21}, // order of actors doesn't matter
|
||||
{false, 2, set12, set12}, // but can't repeat sequence
|
||||
{true, 3, set12, set321}, // no effect from extra sigs
|
||||
|
||||
// triple sigs also work
|
||||
{false, 2, set123, set123}, // must start with seq=1
|
||||
{false, 1, set123, set12}, // all must sign
|
||||
{true, 1, set123, set321}, // this works
|
||||
{true, 2, set321, set321}, // other order is the same
|
||||
{false, 2, set321, set321}, // no repetition
|
||||
|
||||
// signers with different chain-IDs and apps from actors
|
||||
{false, 3, set123, set123Chain2}, // sign with different chain actors
|
||||
{false, 3, set123, set123App2}, // sign with different app actors
|
||||
{false, 3, set123, set123MixedChains}, // sign with mixed chain actor
|
||||
{false, 3, set123, set123MixedApps}, // sign with mixed app actors
|
||||
|
||||
// signers from different chain-IDs and apps, working
|
||||
{true, 1, set123Chain2, set123Chain2},
|
||||
{true, 1, set123App2, set123App2},
|
||||
{true, 1, set123MixedChains, set123MixedChains},
|
||||
{true, 1, set123MixedApps, set123MixedApps},
|
||||
}
|
||||
|
||||
raw := stack.NewRawTx([]byte{42})
|
||||
for i, test := range testList {
|
||||
|
||||
// set the permissions
|
||||
myCtx := ctx.WithPermissions(test.signers...)
|
||||
|
||||
tx := NewTx(test.seq, test.actors, raw)
|
||||
nonceTx, ok := tx.Unwrap().(Tx)
|
||||
require.True(ok)
|
||||
|
||||
err := nonceTx.CheckIncrementSeq(myCtx, store)
|
||||
if test.valid {
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
} else {
|
||||
assert.NotNil(err, "%d", i)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/errors"
|
||||
)
|
||||
|
||||
// Recovery catches any panics and returns them as errors instead
|
||||
type Recovery struct{}
|
||||
|
||||
var _ sdk.Decorator = Recovery{}
|
||||
|
||||
// CheckTx catches any panic and converts to error - fulfills Middlware interface
|
||||
func (Recovery) CheckTx(ctx sdk.Context, store sdk.SimpleDB,
|
||||
tx interface{}, next sdk.Checker) (res sdk.CheckResult, err error) {
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = normalizePanic(r)
|
||||
}
|
||||
}()
|
||||
return next.CheckTx(ctx, store, tx)
|
||||
}
|
||||
|
||||
// DeliverTx catches any panic and converts to error - fulfills Middlware interface
|
||||
func (Recovery) DeliverTx(ctx sdk.Context, store sdk.SimpleDB,
|
||||
tx interface{}, next sdk.Deliverer) (res sdk.DeliverResult, err error) {
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = normalizePanic(r)
|
||||
}
|
||||
}()
|
||||
return next.DeliverTx(ctx, store, tx)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
msg := fmt.Sprintf("%v", p)
|
||||
return errors.ErrInternal(msg)
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/state"
|
||||
)
|
||||
|
||||
func TestRecovery(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// generic args here...
|
||||
ctx := MockContext("test-chain", 20)
|
||||
store := state.NewMemKVStore()
|
||||
tx := 0 // we ignore it, so it can be anything
|
||||
|
||||
cases := []struct {
|
||||
msg string // what to send to panic
|
||||
err error // what to send to panic
|
||||
expected string // expected text in panic
|
||||
}{
|
||||
{"buzz", nil, "buzz"},
|
||||
{"", errors.New("some text"), "some text"},
|
||||
{"text", errors.New("error"), "error"},
|
||||
}
|
||||
|
||||
for idx, tc := range cases {
|
||||
i := strconv.Itoa(idx)
|
||||
fail := PanicHandler{Msg: tc.msg, Err: tc.err}
|
||||
rec := Recovery{}
|
||||
app := sdk.ChainDecorators(rec).WithHandler(fail)
|
||||
|
||||
// make sure check returns error, not a panic crash
|
||||
_, err := app.CheckTx(ctx, store, tx)
|
||||
if assert.NotNil(err, i) {
|
||||
assert.Equal(tc.expected, err.Error(), i)
|
||||
}
|
||||
|
||||
// make sure deliver returns error, not a panic crash
|
||||
_, err = app.DeliverTx(ctx, store, tx)
|
||||
if assert.NotNil(err, i) {
|
||||
assert.Equal(tc.expected, err.Error(), i)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue