Merge pull request #171 from tendermint/feature/38-ibc-with-light-client
Rewrite IBC to use light client verification
This commit is contained in:
commit
79435666ce
2
Makefile
2
Makefile
|
@ -35,7 +35,7 @@ test_cli: tests/cli/shunit2
|
||||||
./tests/cli/roles.sh
|
./tests/cli/roles.sh
|
||||||
./tests/cli/counter.sh
|
./tests/cli/counter.sh
|
||||||
./tests/cli/restart.sh
|
./tests/cli/restart.sh
|
||||||
# @./tests/cli/ibc.sh
|
./tests/cli/ibc.sh
|
||||||
|
|
||||||
test_tutorial: docs/guide/shunit2
|
test_tutorial: docs/guide/shunit2
|
||||||
@shelldown ${TUTORIALS}
|
@shelldown ${TUTORIALS}
|
||||||
|
|
28
app/app.go
28
app/app.go
|
@ -10,12 +10,6 @@ import (
|
||||||
|
|
||||||
"github.com/tendermint/basecoin"
|
"github.com/tendermint/basecoin"
|
||||||
"github.com/tendermint/basecoin/errors"
|
"github.com/tendermint/basecoin/errors"
|
||||||
"github.com/tendermint/basecoin/modules/auth"
|
|
||||||
"github.com/tendermint/basecoin/modules/base"
|
|
||||||
"github.com/tendermint/basecoin/modules/coin"
|
|
||||||
"github.com/tendermint/basecoin/modules/fee"
|
|
||||||
"github.com/tendermint/basecoin/modules/nonce"
|
|
||||||
"github.com/tendermint/basecoin/modules/roles"
|
|
||||||
"github.com/tendermint/basecoin/stack"
|
"github.com/tendermint/basecoin/stack"
|
||||||
sm "github.com/tendermint/basecoin/state"
|
sm "github.com/tendermint/basecoin/state"
|
||||||
"github.com/tendermint/basecoin/version"
|
"github.com/tendermint/basecoin/version"
|
||||||
|
@ -50,28 +44,6 @@ func NewBasecoin(handler basecoin.Handler, store *Store, logger log.Logger) *Bas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultHandler - placeholder to just handle sendtx
|
|
||||||
func DefaultHandler(feeDenom string) basecoin.Handler {
|
|
||||||
// use the default stack
|
|
||||||
c := coin.NewHandler()
|
|
||||||
r := roles.NewHandler()
|
|
||||||
d := stack.NewDispatcher(
|
|
||||||
stack.WrapHandler(c),
|
|
||||||
stack.WrapHandler(r),
|
|
||||||
)
|
|
||||||
return stack.New(
|
|
||||||
base.Logger{},
|
|
||||||
stack.Recovery{},
|
|
||||||
auth.Signatures{},
|
|
||||||
base.Chain{},
|
|
||||||
stack.Checkpoint{OnCheck: true},
|
|
||||||
nonce.ReplayCheck{},
|
|
||||||
roles.NewMiddleware(),
|
|
||||||
fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
|
|
||||||
stack.Checkpoint{OnDeliver: true},
|
|
||||||
).Use(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetChainID returns the currently stored chain
|
// GetChainID returns the currently stored chain
|
||||||
func (app *Basecoin) GetChainID() string {
|
func (app *Basecoin) GetChainID() string {
|
||||||
return app.info.GetChainID(app.state.Committed())
|
return app.info.GetChainID(app.state.Committed())
|
||||||
|
|
|
@ -13,13 +13,42 @@ import (
|
||||||
"github.com/tendermint/basecoin/modules/base"
|
"github.com/tendermint/basecoin/modules/base"
|
||||||
"github.com/tendermint/basecoin/modules/coin"
|
"github.com/tendermint/basecoin/modules/coin"
|
||||||
"github.com/tendermint/basecoin/modules/fee"
|
"github.com/tendermint/basecoin/modules/fee"
|
||||||
|
"github.com/tendermint/basecoin/modules/ibc"
|
||||||
"github.com/tendermint/basecoin/modules/nonce"
|
"github.com/tendermint/basecoin/modules/nonce"
|
||||||
|
"github.com/tendermint/basecoin/modules/roles"
|
||||||
"github.com/tendermint/basecoin/stack"
|
"github.com/tendermint/basecoin/stack"
|
||||||
"github.com/tendermint/basecoin/state"
|
"github.com/tendermint/basecoin/state"
|
||||||
wire "github.com/tendermint/go-wire"
|
wire "github.com/tendermint/go-wire"
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultHandler for the tests (coin, roles, ibc)
|
||||||
|
func DefaultHandler(feeDenom string) basecoin.Handler {
|
||||||
|
// use the default stack
|
||||||
|
r := roles.NewHandler()
|
||||||
|
i := ibc.NewHandler()
|
||||||
|
|
||||||
|
return stack.New(
|
||||||
|
base.Logger{},
|
||||||
|
stack.Recovery{},
|
||||||
|
auth.Signatures{},
|
||||||
|
base.Chain{},
|
||||||
|
stack.Checkpoint{OnCheck: true},
|
||||||
|
nonce.ReplayCheck{},
|
||||||
|
).
|
||||||
|
IBC(ibc.NewMiddleware()).
|
||||||
|
Apps(
|
||||||
|
roles.NewMiddleware(),
|
||||||
|
fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
|
||||||
|
stack.Checkpoint{OnDeliver: true},
|
||||||
|
).
|
||||||
|
Dispatch(
|
||||||
|
coin.NewHandler(),
|
||||||
|
stack.WrapHandler(r),
|
||||||
|
stack.WrapHandler(i),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------
|
//--------------------------------------------------------
|
||||||
// test environment is a list of input and output accounts
|
// test environment is a list of input and output accounts
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ func DefaultHandler(feeDenom string) basecoin.Handler {
|
||||||
c := coin.NewHandler()
|
c := coin.NewHandler()
|
||||||
r := roles.NewHandler()
|
r := roles.NewHandler()
|
||||||
d := stack.NewDispatcher(
|
d := stack.NewDispatcher(
|
||||||
stack.WrapHandler(c),
|
c,
|
||||||
stack.WrapHandler(r),
|
stack.WrapHandler(r),
|
||||||
)
|
)
|
||||||
return stack.New(
|
return stack.New(
|
||||||
|
|
|
@ -79,7 +79,7 @@ func GetCertifier() (*certifiers.InquiringCertifier, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cert := certifiers.NewInquiring(
|
cert := certifiers.NewInquiring(
|
||||||
viper.GetString(ChainFlag), seed.Validators, trust, source)
|
viper.GetString(ChainFlag), seed, trust, source)
|
||||||
return cert, nil
|
return cert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
package seeds
|
package seeds
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/tendermint/basecoin/client/commands"
|
"github.com/tendermint/basecoin/client/commands"
|
||||||
|
"github.com/tendermint/light-client/certifiers"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exportCmd = &cobra.Command{
|
var exportCmd = &cobra.Command{
|
||||||
|
@ -40,5 +44,16 @@ func exportSeed(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// now get the output file and write it
|
// now get the output file and write it
|
||||||
return seed.Write(path)
|
return writeSeed(seed, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeSeed(seed certifiers.Seed, path string) (err error) {
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
stream := json.NewEncoder(f)
|
||||||
|
err = stream.Encode(seed)
|
||||||
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/tendermint/light-client/certifiers"
|
"github.com/tendermint/light-client/certifiers"
|
||||||
|
|
||||||
|
@ -12,12 +13,13 @@ import (
|
||||||
|
|
||||||
var updateCmd = &cobra.Command{
|
var updateCmd = &cobra.Command{
|
||||||
Use: "update",
|
Use: "update",
|
||||||
Short: "Update seed to current chain state if possible",
|
Short: "Update seed to current height if possible",
|
||||||
RunE: commands.RequireInit(updateSeed),
|
RunE: commands.RequireInit(updateSeed),
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
updateCmd.Flags().Int(heightFlag, 0, "Update to this height, not latest")
|
||||||
RootCmd.AddCommand(updateCmd)
|
RootCmd.AddCommand(updateCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,14 +29,20 @@ func updateSeed(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h := viper.GetInt(heightFlag)
|
||||||
|
var seed certifiers.Seed
|
||||||
|
if h <= 0 {
|
||||||
// get the lastest from our source
|
// get the lastest from our source
|
||||||
seed, err := certifiers.LatestSeed(cert.SeedSource)
|
seed, err = certifiers.LatestSeed(cert.SeedSource)
|
||||||
|
} else {
|
||||||
|
seed, err = cert.SeedSource.GetByHeight(h)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("Trying to update to height: %d...\n", seed.Height())
|
|
||||||
|
|
||||||
// let the certifier do it's magic to update....
|
// let the certifier do it's magic to update....
|
||||||
|
fmt.Printf("Trying to update to height: %d...\n", seed.Height())
|
||||||
err = cert.Update(seed.Checkpoint, seed.Validators)
|
err = cert.Update(seed.Checkpoint, seed.Validators)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
basecmd "github.com/tendermint/basecoin/modules/base/commands"
|
basecmd "github.com/tendermint/basecoin/modules/base/commands"
|
||||||
coincmd "github.com/tendermint/basecoin/modules/coin/commands"
|
coincmd "github.com/tendermint/basecoin/modules/coin/commands"
|
||||||
feecmd "github.com/tendermint/basecoin/modules/fee/commands"
|
feecmd "github.com/tendermint/basecoin/modules/fee/commands"
|
||||||
|
ibccmd "github.com/tendermint/basecoin/modules/ibc/commands"
|
||||||
noncecmd "github.com/tendermint/basecoin/modules/nonce/commands"
|
noncecmd "github.com/tendermint/basecoin/modules/nonce/commands"
|
||||||
rolecmd "github.com/tendermint/basecoin/modules/roles/commands"
|
rolecmd "github.com/tendermint/basecoin/modules/roles/commands"
|
||||||
)
|
)
|
||||||
|
@ -46,6 +47,7 @@ func main() {
|
||||||
coincmd.AccountQueryCmd,
|
coincmd.AccountQueryCmd,
|
||||||
noncecmd.NonceQueryCmd,
|
noncecmd.NonceQueryCmd,
|
||||||
rolecmd.RoleQueryCmd,
|
rolecmd.RoleQueryCmd,
|
||||||
|
ibccmd.IBCQueryCmd,
|
||||||
)
|
)
|
||||||
proofs.TxPresenters.Register("base", txcmd.BaseTxPresenter{})
|
proofs.TxPresenters.Register("base", txcmd.BaseTxPresenter{})
|
||||||
|
|
||||||
|
@ -63,8 +65,13 @@ func main() {
|
||||||
txcmd.RootCmd.AddCommand(
|
txcmd.RootCmd.AddCommand(
|
||||||
// This is the default transaction, optional in your app
|
// This is the default transaction, optional in your app
|
||||||
coincmd.SendTxCmd,
|
coincmd.SendTxCmd,
|
||||||
|
coincmd.CreditTxCmd,
|
||||||
// this enables creating roles
|
// this enables creating roles
|
||||||
rolecmd.CreateRoleTxCmd,
|
rolecmd.CreateRoleTxCmd,
|
||||||
|
// these are for handling ibc
|
||||||
|
ibccmd.RegisterChainTxCmd,
|
||||||
|
ibccmd.UpdateChainTxCmd,
|
||||||
|
ibccmd.PostPacketTxCmd,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set up the various commands to use
|
// Set up the various commands to use
|
||||||
|
|
|
@ -120,7 +120,10 @@ func GetGenesisJSON(chainID, addr string) string {
|
||||||
"amount": 9007199254740992
|
"amount": 9007199254740992
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}]
|
}],
|
||||||
|
"plugin_options": [
|
||||||
|
"coin/issuer", {"app": "sigs", "addr": "%s"}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}`, chainID, addr)
|
}`, chainID, addr, addr)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,15 +5,46 @@ import (
|
||||||
|
|
||||||
"github.com/tendermint/tmlibs/cli"
|
"github.com/tendermint/tmlibs/cli"
|
||||||
|
|
||||||
"github.com/tendermint/basecoin/app"
|
"github.com/tendermint/basecoin"
|
||||||
"github.com/tendermint/basecoin/cmd/basecoin/commands"
|
"github.com/tendermint/basecoin/cmd/basecoin/commands"
|
||||||
|
"github.com/tendermint/basecoin/modules/auth"
|
||||||
|
"github.com/tendermint/basecoin/modules/base"
|
||||||
|
"github.com/tendermint/basecoin/modules/coin"
|
||||||
|
"github.com/tendermint/basecoin/modules/fee"
|
||||||
|
"github.com/tendermint/basecoin/modules/ibc"
|
||||||
|
"github.com/tendermint/basecoin/modules/nonce"
|
||||||
|
"github.com/tendermint/basecoin/modules/roles"
|
||||||
|
"github.com/tendermint/basecoin/stack"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// BuildApp constructs the stack we want to use for this app
|
||||||
|
func BuildApp(feeDenom string) basecoin.Handler {
|
||||||
|
return stack.New(
|
||||||
|
base.Logger{},
|
||||||
|
stack.Recovery{},
|
||||||
|
auth.Signatures{},
|
||||||
|
base.Chain{},
|
||||||
|
stack.Checkpoint{OnCheck: true},
|
||||||
|
nonce.ReplayCheck{},
|
||||||
|
).
|
||||||
|
IBC(ibc.NewMiddleware()).
|
||||||
|
Apps(
|
||||||
|
roles.NewMiddleware(),
|
||||||
|
fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
|
||||||
|
stack.Checkpoint{OnDeliver: true},
|
||||||
|
).
|
||||||
|
Dispatch(
|
||||||
|
coin.NewHandler(),
|
||||||
|
stack.WrapHandler(roles.NewHandler()),
|
||||||
|
stack.WrapHandler(ibc.NewHandler()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
rt := commands.RootCmd
|
rt := commands.RootCmd
|
||||||
|
|
||||||
// require all fees in mycoin - change this in your app!
|
// require all fees in mycoin - change this in your app!
|
||||||
commands.Handler = app.DefaultHandler("mycoin")
|
commands.Handler = BuildApp("mycoin")
|
||||||
|
|
||||||
rt.AddCommand(
|
rt.AddCommand(
|
||||||
commands.InitCmd,
|
commands.InitCmd,
|
||||||
|
|
18
context.go
18
context.go
|
@ -43,6 +43,24 @@ func (a Actor) Empty() bool {
|
||||||
return a.ChainID == "" && a.App == "" && len(a.Address) == 0
|
return a.ChainID == "" && a.App == "" && len(a.Address) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithChain creates a copy of the actor with a different chainID
|
||||||
|
func (a Actor) WithChain(chainID string) (b Actor) {
|
||||||
|
b = a
|
||||||
|
b.ChainID = chainID
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Actors []Actor
|
||||||
|
|
||||||
|
func (a Actors) AllHaveChain(chainID string) bool {
|
||||||
|
for _, b := range a {
|
||||||
|
if b.ChainID != chainID {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Context is an interface, so we can implement "secure" variants that
|
// Context is an interface, so we can implement "secure" variants that
|
||||||
// rely on private fields to control the actions
|
// rely on private fields to control the actions
|
||||||
type Context interface {
|
type Context interface {
|
||||||
|
|
|
@ -12,7 +12,9 @@ import (
|
||||||
"github.com/tendermint/basecoin/modules/base"
|
"github.com/tendermint/basecoin/modules/base"
|
||||||
"github.com/tendermint/basecoin/modules/coin"
|
"github.com/tendermint/basecoin/modules/coin"
|
||||||
"github.com/tendermint/basecoin/modules/fee"
|
"github.com/tendermint/basecoin/modules/fee"
|
||||||
|
"github.com/tendermint/basecoin/modules/ibc"
|
||||||
"github.com/tendermint/basecoin/modules/nonce"
|
"github.com/tendermint/basecoin/modules/nonce"
|
||||||
|
"github.com/tendermint/basecoin/modules/roles"
|
||||||
"github.com/tendermint/basecoin/stack"
|
"github.com/tendermint/basecoin/stack"
|
||||||
"github.com/tendermint/basecoin/state"
|
"github.com/tendermint/basecoin/state"
|
||||||
)
|
)
|
||||||
|
@ -90,13 +92,6 @@ func ErrDecoding() error {
|
||||||
|
|
||||||
// NewHandler returns a new counter transaction processing handler
|
// NewHandler returns a new counter transaction processing handler
|
||||||
func NewHandler(feeDenom string) basecoin.Handler {
|
func NewHandler(feeDenom string) basecoin.Handler {
|
||||||
// use the default stack
|
|
||||||
ch := coin.NewHandler()
|
|
||||||
counter := Handler{}
|
|
||||||
dispatcher := stack.NewDispatcher(
|
|
||||||
stack.WrapHandler(ch),
|
|
||||||
counter,
|
|
||||||
)
|
|
||||||
return stack.New(
|
return stack.New(
|
||||||
base.Logger{},
|
base.Logger{},
|
||||||
stack.Recovery{},
|
stack.Recovery{},
|
||||||
|
@ -104,9 +99,17 @@ func NewHandler(feeDenom string) basecoin.Handler {
|
||||||
base.Chain{},
|
base.Chain{},
|
||||||
stack.Checkpoint{OnCheck: true},
|
stack.Checkpoint{OnCheck: true},
|
||||||
nonce.ReplayCheck{},
|
nonce.ReplayCheck{},
|
||||||
|
).
|
||||||
|
IBC(ibc.NewMiddleware()).
|
||||||
|
Apps(
|
||||||
|
roles.NewMiddleware(),
|
||||||
fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
|
fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
|
||||||
stack.Checkpoint{OnDeliver: true},
|
stack.Checkpoint{OnDeliver: true},
|
||||||
).Use(dispatcher)
|
).
|
||||||
|
Dispatch(
|
||||||
|
coin.NewHandler(),
|
||||||
|
Handler{},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler the counter transaction processing handler
|
// Handler the counter transaction processing handler
|
||||||
|
|
|
@ -16,6 +16,7 @@ var (
|
||||||
errUnknownTxType = fmt.Errorf("Tx type unknown")
|
errUnknownTxType = fmt.Errorf("Tx type unknown")
|
||||||
errInvalidFormat = fmt.Errorf("Invalid format")
|
errInvalidFormat = fmt.Errorf("Invalid format")
|
||||||
errUnknownModule = fmt.Errorf("Unknown module")
|
errUnknownModule = fmt.Errorf("Unknown module")
|
||||||
|
errUnknownKey = fmt.Errorf("Unknown key")
|
||||||
|
|
||||||
internalErr = abci.CodeType_InternalError
|
internalErr = abci.CodeType_InternalError
|
||||||
encodingErr = abci.CodeType_EncodingError
|
encodingErr = abci.CodeType_EncodingError
|
||||||
|
@ -39,7 +40,7 @@ func unwrap(i interface{}) interface{} {
|
||||||
|
|
||||||
func ErrUnknownTxType(tx interface{}) TMError {
|
func ErrUnknownTxType(tx interface{}) TMError {
|
||||||
msg := fmt.Sprintf("%T", unwrap(tx))
|
msg := fmt.Sprintf("%T", unwrap(tx))
|
||||||
return WithMessage(msg, errUnknownTxType, abci.CodeType_UnknownRequest)
|
return WithMessage(msg, errUnknownTxType, unknownRequest)
|
||||||
}
|
}
|
||||||
func IsUnknownTxTypeErr(err error) bool {
|
func IsUnknownTxTypeErr(err error) bool {
|
||||||
return IsSameError(errUnknownTxType, err)
|
return IsSameError(errUnknownTxType, err)
|
||||||
|
@ -47,19 +48,26 @@ func IsUnknownTxTypeErr(err error) bool {
|
||||||
|
|
||||||
func ErrInvalidFormat(expected string, tx interface{}) TMError {
|
func ErrInvalidFormat(expected string, tx interface{}) TMError {
|
||||||
msg := fmt.Sprintf("%T not %s", unwrap(tx), expected)
|
msg := fmt.Sprintf("%T not %s", unwrap(tx), expected)
|
||||||
return WithMessage(msg, errInvalidFormat, abci.CodeType_UnknownRequest)
|
return WithMessage(msg, errInvalidFormat, unknownRequest)
|
||||||
}
|
}
|
||||||
func IsInvalidFormatErr(err error) bool {
|
func IsInvalidFormatErr(err error) bool {
|
||||||
return IsSameError(errInvalidFormat, err)
|
return IsSameError(errInvalidFormat, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ErrUnknownModule(mod string) TMError {
|
func ErrUnknownModule(mod string) TMError {
|
||||||
return WithMessage(mod, errUnknownModule, abci.CodeType_UnknownRequest)
|
return WithMessage(mod, errUnknownModule, unknownRequest)
|
||||||
}
|
}
|
||||||
func IsUnknownModuleErr(err error) bool {
|
func IsUnknownModuleErr(err error) bool {
|
||||||
return IsSameError(errUnknownModule, err)
|
return IsSameError(errUnknownModule, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ErrUnknownKey(mod string) TMError {
|
||||||
|
return WithMessage(mod, errUnknownKey, unknownRequest)
|
||||||
|
}
|
||||||
|
func IsUnknownKeyErr(err error) bool {
|
||||||
|
return IsSameError(errUnknownKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
func ErrInternal(msg string) TMError {
|
func ErrInternal(msg string) TMError {
|
||||||
return New(msg, internalErr)
|
return New(msg, internalErr)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
// CheckErr is the type of all the check functions here
|
||||||
|
type CheckErr func(error) bool
|
||||||
|
|
||||||
|
// NoErr is useful for test cases when you want to fulfil the CheckErr type
|
||||||
|
func NoErr(err error) bool {
|
||||||
|
return err == nil
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
hash: 45eed61138603d4d03518ea822068cf32b45d0a219bb7f3b836e52129f2a3a2b
|
hash: 2848c30b31fb205f846dd7dfca14ebed8a3249cbc5aaa759066b2bab3e4bbf42
|
||||||
updated: 2017-07-26T19:44:39.753066441-04:00
|
updated: 2017-07-27T16:46:31.962147949-04:00
|
||||||
imports:
|
imports:
|
||||||
- name: github.com/bgentry/speakeasy
|
- name: github.com/bgentry/speakeasy
|
||||||
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
|
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
|
||||||
|
@ -57,6 +57,8 @@ imports:
|
||||||
- json/parser
|
- json/parser
|
||||||
- json/scanner
|
- json/scanner
|
||||||
- json/token
|
- json/token
|
||||||
|
- name: github.com/howeyc/crc16
|
||||||
|
version: 96a97a1abb579c7ff1a8ffa77f2e72d1c314b57f
|
||||||
- name: github.com/inconshreveable/mousetrap
|
- name: github.com/inconshreveable/mousetrap
|
||||||
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||||
- name: github.com/jmhodges/levigo
|
- name: github.com/jmhodges/levigo
|
||||||
|
@ -117,7 +119,7 @@ imports:
|
||||||
- edwards25519
|
- edwards25519
|
||||||
- extra25519
|
- extra25519
|
||||||
- name: github.com/tendermint/go-crypto
|
- name: github.com/tendermint/go-crypto
|
||||||
version: d31cfbaeaa4d930798ec327b52917975f3203c11
|
version: bf355d1b58b27d4e98d8fb237eb14887b93a88f7
|
||||||
subpackages:
|
subpackages:
|
||||||
- cmd
|
- cmd
|
||||||
- keys
|
- keys
|
||||||
|
@ -133,7 +135,7 @@ imports:
|
||||||
- data
|
- data
|
||||||
- data/base58
|
- data/base58
|
||||||
- name: github.com/tendermint/light-client
|
- name: github.com/tendermint/light-client
|
||||||
version: 1c53d04dcc65c2fd15526152ed0651af10a09982
|
version: fcf4e411583135a1900157b8b0274c41e20ea3a1
|
||||||
subpackages:
|
subpackages:
|
||||||
- certifiers
|
- certifiers
|
||||||
- certifiers/client
|
- certifiers/client
|
||||||
|
|
|
@ -22,7 +22,7 @@ import:
|
||||||
subpackages:
|
subpackages:
|
||||||
- data
|
- data
|
||||||
- package: github.com/tendermint/light-client
|
- package: github.com/tendermint/light-client
|
||||||
version: 1c53d04dcc65c2fd15526152ed0651af10a09982
|
version: unstable
|
||||||
subpackages:
|
subpackages:
|
||||||
- proofs
|
- proofs
|
||||||
- certifiers
|
- certifiers
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"github.com/tendermint/basecoin/state"
|
"github.com/tendermint/basecoin/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeHandler() basecoin.Handler {
|
func makeHandler() stack.Dispatchable {
|
||||||
return NewHandler()
|
return NewHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ func BenchmarkSimpleTransfer(b *testing.B) {
|
||||||
|
|
||||||
// set the initial account
|
// set the initial account
|
||||||
acct := NewAccountWithKey(Coins{{"mycoin", 1234567890}})
|
acct := NewAccountWithKey(Coins{{"mycoin", 1234567890}})
|
||||||
h.SetOption(logger, store, NameCoin, "account", acct.MakeOption())
|
h.SetOption(logger, store, NameCoin, "account", acct.MakeOption(), nil)
|
||||||
sender := acct.Actor()
|
sender := acct.Actor()
|
||||||
receiver := basecoin.Actor{App: "foo", Address: cmn.RandBytes(20)}
|
receiver := basecoin.Actor{App: "foo", Address: cmn.RandBytes(20)}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ func BenchmarkSimpleTransfer(b *testing.B) {
|
||||||
for i := 1; i <= b.N; i++ {
|
for i := 1; i <= b.N; i++ {
|
||||||
ctx := stack.MockContext("foo", 100).WithPermissions(sender)
|
ctx := stack.MockContext("foo", 100).WithPermissions(sender)
|
||||||
tx := makeSimpleTx(sender, receiver, Coins{{"mycoin", 2}})
|
tx := makeSimpleTx(sender, receiver, Coins{{"mycoin", 2}})
|
||||||
_, err := h.DeliverTx(ctx, store, tx)
|
_, err := h.DeliverTx(ctx, store, tx, nil)
|
||||||
// never should error
|
// never should error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
|
@ -28,6 +28,7 @@ func accountQueryCmd(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
act = coin.ChainAddr(act)
|
||||||
key := stack.PrefixedKey(coin.NameCoin, act.Bytes())
|
key := stack.PrefixedKey(coin.NameCoin, act.Bytes())
|
||||||
|
|
||||||
acc := coin.Account{}
|
acc := coin.Account{}
|
||||||
|
|
|
@ -17,6 +17,13 @@ var SendTxCmd = &cobra.Command{
|
||||||
RunE: commands.RequireInit(sendTxCmd),
|
RunE: commands.RequireInit(sendTxCmd),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreditTxCmd is CLI command to issue credit to one account
|
||||||
|
var CreditTxCmd = &cobra.Command{
|
||||||
|
Use: "credit",
|
||||||
|
Short: "issue credit to one account",
|
||||||
|
RunE: commands.RequireInit(creditTxCmd),
|
||||||
|
}
|
||||||
|
|
||||||
//nolint
|
//nolint
|
||||||
const (
|
const (
|
||||||
FlagTo = "to"
|
FlagTo = "to"
|
||||||
|
@ -29,9 +36,12 @@ func init() {
|
||||||
flags.String(FlagTo, "", "Destination address for the bits")
|
flags.String(FlagTo, "", "Destination address for the bits")
|
||||||
flags.String(FlagAmount, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
|
flags.String(FlagAmount, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
|
||||||
flags.String(FlagFrom, "", "Address sending coins, if not first signer")
|
flags.String(FlagFrom, "", "Address sending coins, if not first signer")
|
||||||
|
|
||||||
|
fs2 := CreditTxCmd.Flags()
|
||||||
|
fs2.String(FlagTo, "", "Destination address for the bits")
|
||||||
|
fs2.String(FlagAmount, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendTxCmd is an example of how to make a tx
|
|
||||||
func sendTxCmd(cmd *cobra.Command, args []string) error {
|
func sendTxCmd(cmd *cobra.Command, args []string) error {
|
||||||
tx, err := readSendTxFlags()
|
tx, err := readSendTxFlags()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -62,6 +72,30 @@ func readSendTxFlags() (tx basecoin.Tx, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func creditTxCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
tx, err := readCreditTxFlags()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return txcmd.DoTx(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readCreditTxFlags() (tx basecoin.Tx, err error) {
|
||||||
|
// parse to address
|
||||||
|
toAddr, err := commands.ParseActor(viper.GetString(FlagTo))
|
||||||
|
if err != nil {
|
||||||
|
return tx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
amount, err := coin.ParseCoins(viper.GetString(FlagAmount))
|
||||||
|
if err != nil {
|
||||||
|
return tx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tx = coin.CreditTx{Debitor: toAddr, Credit: amount}.Wrap()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func readFromAddr() (basecoin.Actor, error) {
|
func readFromAddr() (basecoin.Actor, error) {
|
||||||
from := viper.GetString(FlagFrom)
|
from := viper.GetString(FlagFrom)
|
||||||
if from == "" {
|
if from == "" {
|
||||||
|
|
|
@ -12,11 +12,11 @@ import (
|
||||||
var (
|
var (
|
||||||
errNoAccount = fmt.Errorf("No such account")
|
errNoAccount = fmt.Errorf("No such account")
|
||||||
errInsufficientFunds = fmt.Errorf("Insufficient funds")
|
errInsufficientFunds = fmt.Errorf("Insufficient funds")
|
||||||
|
errInsufficientCredit = fmt.Errorf("Insufficient credit")
|
||||||
errNoInputs = fmt.Errorf("No input coins")
|
errNoInputs = fmt.Errorf("No input coins")
|
||||||
errNoOutputs = fmt.Errorf("No output coins")
|
errNoOutputs = fmt.Errorf("No output coins")
|
||||||
errInvalidAddress = fmt.Errorf("Invalid address")
|
errInvalidAddress = fmt.Errorf("Invalid address")
|
||||||
errInvalidCoins = fmt.Errorf("Invalid coins")
|
errInvalidCoins = fmt.Errorf("Invalid coins")
|
||||||
errUnknownKey = fmt.Errorf("Unknown key")
|
|
||||||
|
|
||||||
invalidInput = abci.CodeType_BaseInvalidInput
|
invalidInput = abci.CodeType_BaseInvalidInput
|
||||||
invalidOutput = abci.CodeType_BaseInvalidOutput
|
invalidOutput = abci.CodeType_BaseInvalidOutput
|
||||||
|
@ -67,6 +67,13 @@ func IsInsufficientFundsErr(err error) bool {
|
||||||
return errors.IsSameError(errInsufficientFunds, err)
|
return errors.IsSameError(errInsufficientFunds, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ErrInsufficientCredit() errors.TMError {
|
||||||
|
return errors.WithCode(errInsufficientCredit, invalidInput)
|
||||||
|
}
|
||||||
|
func IsInsufficientCreditErr(err error) bool {
|
||||||
|
return errors.IsSameError(errInsufficientCredit, err)
|
||||||
|
}
|
||||||
|
|
||||||
func ErrNoInputs() errors.TMError {
|
func ErrNoInputs() errors.TMError {
|
||||||
return errors.WithCode(errNoInputs, invalidInput)
|
return errors.WithCode(errNoInputs, invalidInput)
|
||||||
}
|
}
|
||||||
|
@ -80,10 +87,3 @@ func ErrNoOutputs() errors.TMError {
|
||||||
func IsNoOutputsErr(err error) bool {
|
func IsNoOutputsErr(err error) bool {
|
||||||
return errors.IsSameError(errNoOutputs, err)
|
return errors.IsSameError(errNoOutputs, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ErrUnknownKey(mod string) errors.TMError {
|
|
||||||
return errors.WithMessage(mod, errUnknownKey, unknownRequest)
|
|
||||||
}
|
|
||||||
func IsUnknownKeyErr(err error) bool {
|
|
||||||
return errors.IsSameError(errUnknownKey, err)
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"github.com/tendermint/basecoin"
|
"github.com/tendermint/basecoin"
|
||||||
"github.com/tendermint/basecoin/errors"
|
"github.com/tendermint/basecoin/errors"
|
||||||
"github.com/tendermint/basecoin/modules/auth"
|
"github.com/tendermint/basecoin/modules/auth"
|
||||||
|
"github.com/tendermint/basecoin/modules/ibc"
|
||||||
|
"github.com/tendermint/basecoin/stack"
|
||||||
"github.com/tendermint/basecoin/state"
|
"github.com/tendermint/basecoin/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,7 +18,7 @@ const NameCoin = "coin"
|
||||||
// Handler includes an accountant
|
// Handler includes an accountant
|
||||||
type Handler struct{}
|
type Handler struct{}
|
||||||
|
|
||||||
var _ basecoin.Handler = Handler{}
|
var _ stack.Dispatchable = Handler{}
|
||||||
|
|
||||||
// NewHandler - new accountant handler for the coin module
|
// NewHandler - new accountant handler for the coin module
|
||||||
func NewHandler() Handler {
|
func NewHandler() Handler {
|
||||||
|
@ -28,58 +30,177 @@ func (Handler) Name() string {
|
||||||
return NameCoin
|
return NameCoin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssertDispatcher - to fulfill Dispatchable interface
|
||||||
|
func (Handler) AssertDispatcher() {}
|
||||||
|
|
||||||
// CheckTx checks if there is enough money in the account
|
// CheckTx checks if there is enough money in the account
|
||||||
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
|
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB,
|
||||||
send, err := checkTx(ctx, tx)
|
tx basecoin.Tx, _ basecoin.Checker) (res basecoin.Result, err error) {
|
||||||
|
|
||||||
|
err = tx.ValidateBasic()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// now make sure there is money
|
switch t := tx.Unwrap().(type) {
|
||||||
for _, in := range send.Inputs {
|
case SendTx:
|
||||||
_, err = CheckCoins(store, in.Address, in.Coins.Negative())
|
return res, h.checkSendTx(ctx, store, t)
|
||||||
if err != nil {
|
case CreditTx:
|
||||||
return res, err
|
return h.creditTx(ctx, store, t)
|
||||||
}
|
}
|
||||||
}
|
return res, errors.ErrUnknownTxType(tx.Unwrap())
|
||||||
|
|
||||||
// otherwise, we are good
|
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeliverTx moves the money
|
// DeliverTx moves the money
|
||||||
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
|
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
|
||||||
send, err := checkTx(ctx, tx)
|
tx basecoin.Tx, cb basecoin.Deliver) (res basecoin.Result, err error) {
|
||||||
|
|
||||||
|
err = tx.ValidateBasic()
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t := tx.Unwrap().(type) {
|
||||||
|
case SendTx:
|
||||||
|
return h.sendTx(ctx, store, t, cb)
|
||||||
|
case CreditTx:
|
||||||
|
return h.creditTx(ctx, store, t)
|
||||||
|
}
|
||||||
|
return res, errors.ErrUnknownTxType(tx.Unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOption - sets the genesis account balance
|
||||||
|
func (h Handler) SetOption(l log.Logger, store state.SimpleDB,
|
||||||
|
module, key, value string, cb basecoin.SetOptioner) (log string, err error) {
|
||||||
|
if module != NameCoin {
|
||||||
|
return "", errors.ErrUnknownModule(module)
|
||||||
|
}
|
||||||
|
switch key {
|
||||||
|
case "account":
|
||||||
|
return setAccount(store, value)
|
||||||
|
case "issuer":
|
||||||
|
return setIssuer(store, value)
|
||||||
|
}
|
||||||
|
return "", errors.ErrUnknownKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) sendTx(ctx basecoin.Context, store state.SimpleDB,
|
||||||
|
send SendTx, cb basecoin.Deliver) (res basecoin.Result, err error) {
|
||||||
|
|
||||||
|
err = checkTx(ctx, send)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// deduct from all input accounts
|
// deduct from all input accounts
|
||||||
|
senders := basecoin.Actors{}
|
||||||
for _, in := range send.Inputs {
|
for _, in := range send.Inputs {
|
||||||
_, err = ChangeCoins(store, in.Address, in.Coins.Negative())
|
_, err = ChangeCoins(store, in.Address, in.Coins.Negative())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
senders = append(senders, in.Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// add to all output accounts
|
// add to all output accounts
|
||||||
for _, out := range send.Outputs {
|
for _, out := range send.Outputs {
|
||||||
|
// TODO: cleaner way, this makes sure we don't consider
|
||||||
|
// incoming ibc packets with our chain to be remote packets
|
||||||
|
if out.Address.ChainID == ctx.ChainID() {
|
||||||
|
out.Address.ChainID = ""
|
||||||
|
}
|
||||||
|
|
||||||
_, err = ChangeCoins(store, out.Address, out.Coins)
|
_, err = ChangeCoins(store, out.Address, out.Coins)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
// now send ibc packet if needed...
|
||||||
|
if out.Address.ChainID != "" {
|
||||||
|
// FIXME: if there are many outputs, we need to adjust inputs
|
||||||
|
// so the amounts in and out match. how?
|
||||||
|
inputs := make([]TxInput, len(send.Inputs))
|
||||||
|
for i := range send.Inputs {
|
||||||
|
inputs[i] = send.Inputs[i]
|
||||||
|
inputs[i].Address = inputs[i].Address.WithChain(ctx.ChainID())
|
||||||
|
}
|
||||||
|
|
||||||
|
outTx := NewSendTx(inputs, []TxOutput{out})
|
||||||
|
packet := ibc.CreatePacketTx{
|
||||||
|
DestChain: out.Address.ChainID,
|
||||||
|
Permissions: senders,
|
||||||
|
Tx: outTx,
|
||||||
|
}
|
||||||
|
ibcCtx := ctx.WithPermissions(ibc.AllowIBC(NameCoin))
|
||||||
|
_, err := cb.DeliverTx(ibcCtx, store, packet.Wrap())
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// a-ok!
|
// a-ok!
|
||||||
return basecoin.Result{}, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetOption - sets the genesis account balance
|
func (h Handler) creditTx(ctx basecoin.Context, store state.SimpleDB,
|
||||||
func (h Handler) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (log string, err error) {
|
credit CreditTx) (res basecoin.Result, err error) {
|
||||||
if module != NameCoin {
|
|
||||||
return "", errors.ErrUnknownModule(module)
|
// first check permissions!!
|
||||||
|
info, err := loadHandlerInfo(store)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
}
|
}
|
||||||
if key == "account" {
|
if info.Issuer.Empty() || !ctx.HasPermission(info.Issuer) {
|
||||||
|
return res, errors.ErrUnauthorized()
|
||||||
|
}
|
||||||
|
|
||||||
|
// load up the account
|
||||||
|
addr := ChainAddr(credit.Debitor)
|
||||||
|
acct, err := GetAccount(store, addr)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// make and check changes
|
||||||
|
acct.Coins = acct.Coins.Plus(credit.Credit)
|
||||||
|
if !acct.Coins.IsNonnegative() {
|
||||||
|
return res, ErrInsufficientFunds()
|
||||||
|
}
|
||||||
|
acct.Credit = acct.Credit.Plus(credit.Credit)
|
||||||
|
if !acct.Credit.IsNonnegative() {
|
||||||
|
return res, ErrInsufficientCredit()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = storeAccount(store, addr.Bytes(), acct)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkTx(ctx basecoin.Context, send SendTx) error {
|
||||||
|
// check if all inputs have permission
|
||||||
|
for _, in := range send.Inputs {
|
||||||
|
if !ctx.HasPermission(in.Address) {
|
||||||
|
return errors.ErrUnauthorized()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Handler) checkSendTx(ctx basecoin.Context, store state.SimpleDB, send SendTx) error {
|
||||||
|
err := checkTx(ctx, send)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// now make sure there is money
|
||||||
|
for _, in := range send.Inputs {
|
||||||
|
_, err := CheckCoins(store, in.Address, in.Coins.Negative())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAccount(store state.SimpleDB, value string) (log string, err error) {
|
||||||
var acc GenesisAccount
|
var acc GenesisAccount
|
||||||
err = data.FromJSON([]byte(value), &acc)
|
err = data.FromJSON([]byte(value), &acc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -97,27 +218,19 @@ func (h Handler) SetOption(l log.Logger, store state.SimpleDB, module, key, valu
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return "Success", nil
|
return "Success", nil
|
||||||
|
|
||||||
}
|
|
||||||
return "", ErrUnknownKey(key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkTx(ctx basecoin.Context, tx basecoin.Tx) (send SendTx, err error) {
|
// setIssuer sets a permission for some super-powerful account to
|
||||||
// check if the tx is proper type and valid
|
// mint money
|
||||||
send, ok := tx.Unwrap().(SendTx)
|
func setIssuer(store state.SimpleDB, value string) (log string, err error) {
|
||||||
if !ok {
|
var issuer basecoin.Actor
|
||||||
return send, errors.ErrInvalidFormat(TypeSend, tx)
|
err = data.FromJSON([]byte(value), &issuer)
|
||||||
}
|
|
||||||
err = send.ValidateBasic()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return send, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
err = storeIssuer(store, issuer)
|
||||||
// check if all inputs have permission
|
if err != nil {
|
||||||
for _, in := range send.Inputs {
|
return "", err
|
||||||
if !ctx.HasPermission(in.Address) {
|
|
||||||
return send, errors.ErrUnauthorized()
|
|
||||||
}
|
}
|
||||||
}
|
return "Success", nil
|
||||||
return send, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
|
|
||||||
"github.com/tendermint/basecoin"
|
"github.com/tendermint/basecoin"
|
||||||
|
"github.com/tendermint/basecoin/errors"
|
||||||
"github.com/tendermint/basecoin/modules/auth"
|
"github.com/tendermint/basecoin/modules/auth"
|
||||||
"github.com/tendermint/basecoin/stack"
|
"github.com/tendermint/basecoin/stack"
|
||||||
"github.com/tendermint/basecoin/state"
|
"github.com/tendermint/basecoin/state"
|
||||||
|
@ -75,7 +76,7 @@ func TestHandlerValidation(t *testing.T) {
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
ctx := stack.MockContext("base-chain", 100).WithPermissions(tc.perms...)
|
ctx := stack.MockContext("base-chain", 100).WithPermissions(tc.perms...)
|
||||||
_, err := checkTx(ctx, tc.tx)
|
err := checkTx(ctx, tc.tx.Unwrap().(SendTx))
|
||||||
if tc.valid {
|
if tc.valid {
|
||||||
assert.Nil(err, "%d: %+v", i, err)
|
assert.Nil(err, "%d: %+v", i, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -84,7 +85,7 @@ func TestHandlerValidation(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeliverTx(t *testing.T) {
|
func TestDeliverSendTx(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
|
@ -149,7 +150,7 @@ func TestDeliverTx(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := stack.MockContext("base-chain", 100).WithPermissions(tc.perms...)
|
ctx := stack.MockContext("base-chain", 100).WithPermissions(tc.perms...)
|
||||||
_, err := h.DeliverTx(ctx, store, tc.tx)
|
_, err := h.DeliverTx(ctx, store, tc.tx, nil)
|
||||||
if len(tc.final) > 0 { // valid
|
if len(tc.final) > 0 { // valid
|
||||||
assert.Nil(err, "%d: %+v", i, err)
|
assert.Nil(err, "%d: %+v", i, err)
|
||||||
// make sure the final balances are correct
|
// make sure the final balances are correct
|
||||||
|
@ -204,7 +205,7 @@ func TestSetOption(t *testing.T) {
|
||||||
for j, gen := range tc.init {
|
for j, gen := range tc.init {
|
||||||
value, err := json.Marshal(gen)
|
value, err := json.Marshal(gen)
|
||||||
require.Nil(err, "%d,%d: %+v", i, j, err)
|
require.Nil(err, "%d,%d: %+v", i, j, err)
|
||||||
_, err = h.SetOption(l, store, NameCoin, key, string(value))
|
_, err = h.SetOption(l, store, NameCoin, key, string(value), nil)
|
||||||
require.Nil(err)
|
require.Nil(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,5 +216,141 @@ func TestSetOption(t *testing.T) {
|
||||||
assert.Equal(f.coins, acct.Coins)
|
assert.Equal(f.coins, acct.Coins)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetIssuer(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
issuer basecoin.Actor
|
||||||
|
}{
|
||||||
|
{basecoin.Actor{App: "sig", Address: []byte("gwkfgk")}},
|
||||||
|
// and set back to empty (nil is valid, but assert.Equals doesn't match)
|
||||||
|
{basecoin.Actor{Address: []byte{}}},
|
||||||
|
{basecoin.Actor{ChainID: "other", App: "role", Address: []byte("vote")}},
|
||||||
|
}
|
||||||
|
|
||||||
|
h := NewHandler()
|
||||||
|
l := log.NewNopLogger()
|
||||||
|
for i, tc := range cases {
|
||||||
|
store := state.NewMemKVStore()
|
||||||
|
key := "issuer"
|
||||||
|
|
||||||
|
value, err := json.Marshal(tc.issuer)
|
||||||
|
require.Nil(err, "%d,%d: %+v", i, err)
|
||||||
|
_, err = h.SetOption(l, store, NameCoin, key, string(value), nil)
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
|
||||||
|
// check state is proper
|
||||||
|
info, err := loadHandlerInfo(store)
|
||||||
|
assert.Nil(err, "%d: %+v", i, err)
|
||||||
|
assert.Equal(tc.issuer, info.Issuer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeliverCreditTx(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
// sample coins
|
||||||
|
someCoins := Coins{{"atom", 6570}}
|
||||||
|
minusCoins := Coins{{"atom", -1234}}
|
||||||
|
lessCoins := someCoins.Plus(minusCoins)
|
||||||
|
otherCoins := Coins{{"eth", 11}}
|
||||||
|
mixedCoins := someCoins.Plus(otherCoins)
|
||||||
|
|
||||||
|
// some sample addresses
|
||||||
|
owner := basecoin.Actor{App: "foo", Address: []byte("rocks")}
|
||||||
|
addr1 := basecoin.Actor{App: "coin", Address: []byte{1, 2}}
|
||||||
|
key := NewAccountWithKey(someCoins)
|
||||||
|
addr2 := key.Actor()
|
||||||
|
addr3 := basecoin.Actor{ChainID: "other", App: "sigs", Address: []byte{3, 9}}
|
||||||
|
|
||||||
|
h := NewHandler()
|
||||||
|
store := state.NewMemKVStore()
|
||||||
|
ctx := stack.MockContext("secret", 77)
|
||||||
|
|
||||||
|
// set the owner who can issue credit
|
||||||
|
js, err := json.Marshal(owner)
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
_, err = h.SetOption(log.NewNopLogger(), store, "coin", "issuer", string(js), nil)
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
|
||||||
|
// give addr2 some coins to start
|
||||||
|
_, err = h.SetOption(log.NewNopLogger(), store, "coin", "account", key.MakeOption(), nil)
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
tx basecoin.Tx
|
||||||
|
perm basecoin.Actor
|
||||||
|
check errors.CheckErr
|
||||||
|
addr basecoin.Actor
|
||||||
|
expected Account
|
||||||
|
}{
|
||||||
|
// require permission
|
||||||
|
{
|
||||||
|
tx: NewCreditTx(addr1, someCoins),
|
||||||
|
check: errors.IsUnauthorizedErr,
|
||||||
|
},
|
||||||
|
// add credit
|
||||||
|
{
|
||||||
|
tx: NewCreditTx(addr1, someCoins),
|
||||||
|
perm: owner,
|
||||||
|
check: errors.NoErr,
|
||||||
|
addr: addr1,
|
||||||
|
expected: Account{Coins: someCoins, Credit: someCoins},
|
||||||
|
},
|
||||||
|
// remove some
|
||||||
|
{
|
||||||
|
tx: NewCreditTx(addr1, minusCoins),
|
||||||
|
perm: owner,
|
||||||
|
check: errors.NoErr,
|
||||||
|
addr: addr1,
|
||||||
|
expected: Account{Coins: lessCoins, Credit: lessCoins},
|
||||||
|
},
|
||||||
|
// can't remove more cash than there is
|
||||||
|
{
|
||||||
|
tx: NewCreditTx(addr1, otherCoins.Negative()),
|
||||||
|
perm: owner,
|
||||||
|
check: IsInsufficientFundsErr,
|
||||||
|
},
|
||||||
|
// cumulative with initial state
|
||||||
|
{
|
||||||
|
tx: NewCreditTx(addr2, otherCoins),
|
||||||
|
perm: owner,
|
||||||
|
check: errors.NoErr,
|
||||||
|
addr: addr2,
|
||||||
|
expected: Account{Coins: mixedCoins, Credit: otherCoins},
|
||||||
|
},
|
||||||
|
// Even if there is cash, credit can't go negative
|
||||||
|
{
|
||||||
|
tx: NewCreditTx(addr2, minusCoins),
|
||||||
|
perm: owner,
|
||||||
|
check: IsInsufficientCreditErr,
|
||||||
|
},
|
||||||
|
// make sure it works for other chains
|
||||||
|
{
|
||||||
|
tx: NewCreditTx(addr3, mixedCoins),
|
||||||
|
perm: owner,
|
||||||
|
check: errors.NoErr,
|
||||||
|
addr: ChainAddr(addr3),
|
||||||
|
expected: Account{Coins: mixedCoins, Credit: mixedCoins},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
myStore := store.Checkpoint()
|
||||||
|
|
||||||
|
myCtx := ctx.WithPermissions(tc.perm)
|
||||||
|
_, err = h.DeliverTx(myCtx, myStore, tc.tx, nil)
|
||||||
|
assert.True(tc.check(err), "%d: %+v", i, err)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
store.Commit(myStore)
|
||||||
|
acct, err := GetAccount(store, tc.addr)
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
assert.Equal(tc.expected, acct, "%d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
package coin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/tendermint/basecoin"
|
||||||
|
"github.com/tendermint/basecoin/errors"
|
||||||
|
"github.com/tendermint/basecoin/modules/auth"
|
||||||
|
"github.com/tendermint/basecoin/modules/ibc"
|
||||||
|
"github.com/tendermint/basecoin/stack"
|
||||||
|
"github.com/tendermint/basecoin/state"
|
||||||
|
wire "github.com/tendermint/go-wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: other test making sure tx is output on send, balance is updated
|
||||||
|
|
||||||
|
// This makes sure we respond properly to posttx
|
||||||
|
// TODO: set credit limit
|
||||||
|
func TestIBCPostPacket(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
otherID := "chain-2"
|
||||||
|
ourID := "dex"
|
||||||
|
start := 200
|
||||||
|
|
||||||
|
// create the app and our chain
|
||||||
|
app := stack.New().
|
||||||
|
IBC(ibc.NewMiddleware()).
|
||||||
|
Dispatch(
|
||||||
|
NewHandler(),
|
||||||
|
stack.WrapHandler(ibc.NewHandler()),
|
||||||
|
)
|
||||||
|
ourChain := ibc.NewAppChain(app, ourID)
|
||||||
|
|
||||||
|
// set up the other chain and register it with us
|
||||||
|
otherChain := ibc.NewMockChain(otherID, 7)
|
||||||
|
registerTx := otherChain.GetRegistrationTx(start).Wrap()
|
||||||
|
_, err := ourChain.DeliverTx(registerTx)
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
|
||||||
|
// set up a rich guy on this chain
|
||||||
|
wealth := Coins{{"btc", 300}, {"eth", 2000}, {"ltc", 5000}}
|
||||||
|
rich := NewAccountWithKey(wealth)
|
||||||
|
_, err = ourChain.SetOption("coin", "account", rich.MakeOption())
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
|
||||||
|
// sends money to another guy on a different chain, now other chain has credit
|
||||||
|
buddy := basecoin.Actor{ChainID: otherID, App: auth.NameSigs, Address: []byte("dude")}
|
||||||
|
outTx := NewSendOneTx(rich.Actor(), buddy, wealth)
|
||||||
|
_, err = ourChain.DeliverTx(outTx, rich.Actor())
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
|
||||||
|
// make sure the money moved to the other chain...
|
||||||
|
cstore := ourChain.GetStore(NameCoin)
|
||||||
|
acct, err := GetAccount(cstore, ChainAddr(buddy))
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
require.Equal(wealth, acct.Coins)
|
||||||
|
|
||||||
|
// make sure there is a proper packet for this....
|
||||||
|
istore := ourChain.GetStore(ibc.NameIBC)
|
||||||
|
assertPacket(t, istore, otherID, wealth)
|
||||||
|
|
||||||
|
// these are the people for testing incoming ibc from the other chain
|
||||||
|
recipient := basecoin.Actor{ChainID: ourID, App: auth.NameSigs, Address: []byte("bar")}
|
||||||
|
sender := basecoin.Actor{ChainID: otherID, App: auth.NameSigs, Address: []byte("foo")}
|
||||||
|
payment := Coins{{"eth", 100}, {"ltc", 300}}
|
||||||
|
coinTx := NewSendOneTx(sender, recipient, payment)
|
||||||
|
wrongCoin := NewSendOneTx(sender, recipient, Coins{{"missing", 20}})
|
||||||
|
|
||||||
|
p0 := ibc.NewPacket(coinTx, ourID, 0, sender)
|
||||||
|
packet0, update0 := otherChain.MakePostPacket(p0, start+5)
|
||||||
|
require.Nil(ourChain.Update(update0))
|
||||||
|
|
||||||
|
p1 := ibc.NewPacket(coinTx, ourID, 1, sender)
|
||||||
|
packet1, update1 := otherChain.MakePostPacket(p1, start+25)
|
||||||
|
require.Nil(ourChain.Update(update1))
|
||||||
|
|
||||||
|
p2 := ibc.NewPacket(wrongCoin, ourID, 2, sender)
|
||||||
|
packet2, update2 := otherChain.MakePostPacket(p2, start+50)
|
||||||
|
require.Nil(ourChain.Update(update2))
|
||||||
|
|
||||||
|
ibcPerm := basecoin.Actors{ibc.AllowIBC(NameCoin)}
|
||||||
|
cases := []struct {
|
||||||
|
packet ibc.PostPacketTx
|
||||||
|
permissions basecoin.Actors
|
||||||
|
checker errors.CheckErr
|
||||||
|
}{
|
||||||
|
// out of order -> error
|
||||||
|
{packet1, ibcPerm, ibc.IsPacketOutOfOrderErr},
|
||||||
|
|
||||||
|
// all good -> execute tx
|
||||||
|
{packet0, ibcPerm, errors.NoErr},
|
||||||
|
|
||||||
|
// all good -> execute tx (even if earlier attempt failed)
|
||||||
|
{packet1, ibcPerm, errors.NoErr},
|
||||||
|
|
||||||
|
// packet 2 attempts to spend money this chain doesn't have
|
||||||
|
{packet2, ibcPerm, IsInsufficientFundsErr},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
_, err := ourChain.DeliverTx(tc.packet.Wrap(), tc.permissions...)
|
||||||
|
assert.True(tc.checker(err), "%d: %+v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now, make sure the recipient got credited for the 2 successful sendtx
|
||||||
|
cstore = ourChain.GetStore(NameCoin)
|
||||||
|
// FIXME: we need to strip off this when it is local chain-id...
|
||||||
|
// think this throw and handle this better
|
||||||
|
local := recipient.WithChain("")
|
||||||
|
acct, err = GetAccount(cstore, local)
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
assert.Equal(payment.Plus(payment), acct.Coins)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertPacket(t *testing.T, istore state.SimpleDB, destID string, amount Coins) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
iq := ibc.InputQueue(istore, destID)
|
||||||
|
require.Equal(0, iq.Size())
|
||||||
|
|
||||||
|
q := ibc.OutputQueue(istore, destID)
|
||||||
|
require.Equal(1, q.Size())
|
||||||
|
d := q.Item(0)
|
||||||
|
var res ibc.Packet
|
||||||
|
err := wire.ReadBinaryBytes(d, &res)
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
assert.Equal(destID, res.DestChain)
|
||||||
|
assert.EqualValues(0, res.Sequence)
|
||||||
|
stx, ok := res.Tx.Unwrap().(SendTx)
|
||||||
|
if assert.True(ok) {
|
||||||
|
assert.Equal(1, len(stx.Outputs))
|
||||||
|
assert.Equal(amount, stx.Outputs[0].Coins)
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,8 @@ import (
|
||||||
|
|
||||||
// GetAccount - Get account from store and address
|
// GetAccount - Get account from store and address
|
||||||
func GetAccount(store state.SimpleDB, addr basecoin.Actor) (Account, error) {
|
func GetAccount(store state.SimpleDB, addr basecoin.Actor) (Account, error) {
|
||||||
|
// if the actor is another chain, we use one address for the chain....
|
||||||
|
addr = ChainAddr(addr)
|
||||||
acct, err := loadAccount(store, addr.Bytes())
|
acct, err := loadAccount(store, addr.Bytes())
|
||||||
|
|
||||||
// for empty accounts, don't return an error, but rather an empty account
|
// for empty accounts, don't return an error, but rather an empty account
|
||||||
|
@ -23,12 +25,18 @@ func GetAccount(store state.SimpleDB, addr basecoin.Actor) (Account, error) {
|
||||||
|
|
||||||
// CheckCoins makes sure there are funds, but doesn't change anything
|
// CheckCoins makes sure there are funds, but doesn't change anything
|
||||||
func CheckCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (Coins, error) {
|
func CheckCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (Coins, error) {
|
||||||
|
// if the actor is another chain, we use one address for the chain....
|
||||||
|
addr = ChainAddr(addr)
|
||||||
|
|
||||||
acct, err := updateCoins(store, addr, coins)
|
acct, err := updateCoins(store, addr, coins)
|
||||||
return acct.Coins, err
|
return acct.Coins, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeCoins changes the money, returns error if it would be negative
|
// ChangeCoins changes the money, returns error if it would be negative
|
||||||
func ChangeCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (Coins, error) {
|
func ChangeCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (Coins, error) {
|
||||||
|
// if the actor is another chain, we use one address for the chain....
|
||||||
|
addr = ChainAddr(addr)
|
||||||
|
|
||||||
acct, err := updateCoins(store, addr, coins)
|
acct, err := updateCoins(store, addr, coins)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return acct.Coins, err
|
return acct.Coins, err
|
||||||
|
@ -38,6 +46,19 @@ func ChangeCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (Coins,
|
||||||
return acct.Coins, err
|
return acct.Coins, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChainAddr collapses all addresses from another chain into one, so we can
|
||||||
|
// keep an over-all balance
|
||||||
|
//
|
||||||
|
// TODO: is there a better way to do this?
|
||||||
|
func ChainAddr(addr basecoin.Actor) basecoin.Actor {
|
||||||
|
if addr.ChainID == "" {
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
addr.App = ""
|
||||||
|
addr.Address = nil
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
// updateCoins will load the account, make all checks, and return the updated account.
|
// updateCoins will load the account, make all checks, and return the updated account.
|
||||||
//
|
//
|
||||||
// it doesn't save anything, that is up to you to decide (Check/Change Coins)
|
// it doesn't save anything, that is up to you to decide (Check/Change Coins)
|
||||||
|
@ -63,7 +84,11 @@ func updateCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (acct A
|
||||||
|
|
||||||
// Account - coin account structure
|
// Account - coin account structure
|
||||||
type Account struct {
|
type Account struct {
|
||||||
|
// Coins is how much is on the account
|
||||||
Coins Coins `json:"coins"`
|
Coins Coins `json:"coins"`
|
||||||
|
// Credit is how much has been "fronted" to the account
|
||||||
|
// (this is usually 0 except for trusted chains)
|
||||||
|
Credit Coins `json:"credit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadAccount(store state.SimpleDB, key []byte) (acct Account, err error) {
|
func loadAccount(store state.SimpleDB, key []byte) (acct Account, err error) {
|
||||||
|
@ -86,3 +111,35 @@ func storeAccount(store state.SimpleDB, key []byte, acct Account) error {
|
||||||
store.Set(key, bin)
|
store.Set(key, bin)
|
||||||
return nil // real stores can return error...
|
return nil // real stores can return error...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandlerInfo - this is global info on the coin handler
|
||||||
|
type HandlerInfo struct {
|
||||||
|
Issuer basecoin.Actor `json:"issuer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: where to store these special pieces??
|
||||||
|
var handlerKey = []byte{12, 34}
|
||||||
|
|
||||||
|
func loadHandlerInfo(store state.KVStore) (info HandlerInfo, err error) {
|
||||||
|
data := store.Get(handlerKey)
|
||||||
|
if len(data) == 0 {
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
err = wire.ReadBinaryBytes(data, &info)
|
||||||
|
if err != nil {
|
||||||
|
msg := "Error reading handler info"
|
||||||
|
return info, errors.ErrInternal(msg)
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeIssuer(store state.KVStore, issuer basecoin.Actor) error {
|
||||||
|
info, err := loadHandlerInfo(store)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
info.Issuer = issuer
|
||||||
|
d := wire.BinaryBytes(info)
|
||||||
|
store.Set(handlerKey, d)
|
||||||
|
return nil // real stores can return error...
|
||||||
|
}
|
||||||
|
|
|
@ -7,13 +7,17 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
basecoin.TxMapper.RegisterImplementation(SendTx{}, TypeSend, ByteSend)
|
basecoin.TxMapper.
|
||||||
|
RegisterImplementation(SendTx{}, TypeSend, ByteSend).
|
||||||
|
RegisterImplementation(CreditTx{}, TypeCredit, ByteCredit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we reserve the 0x20-0x3f range for standard modules
|
// we reserve the 0x20-0x3f range for standard modules
|
||||||
const (
|
const (
|
||||||
ByteSend = 0x20
|
ByteSend = 0x20
|
||||||
TypeSend = NameCoin + "/send"
|
TypeSend = NameCoin + "/send"
|
||||||
|
ByteCredit = 0x21
|
||||||
|
TypeCredit = NameCoin + "/credit"
|
||||||
)
|
)
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -157,3 +161,30 @@ func (tx SendTx) String() string {
|
||||||
func (tx SendTx) Wrap() basecoin.Tx {
|
func (tx SendTx) Wrap() basecoin.Tx {
|
||||||
return basecoin.Tx{tx}
|
return basecoin.Tx{tx}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// CreditTx - this allows a special issuer to give an account credit
|
||||||
|
// Satisfies: TxInner
|
||||||
|
type CreditTx struct {
|
||||||
|
Debitor basecoin.Actor `json:"debitor"`
|
||||||
|
// Credit is the amount to change the credit...
|
||||||
|
// This may be negative to remove some over-issued credit,
|
||||||
|
// but can never bring the credit or the balance to negative
|
||||||
|
Credit Coins `json:"credit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCreditTx - modify the credit granted to a given account
|
||||||
|
func NewCreditTx(debitor basecoin.Actor, credit Coins) basecoin.Tx {
|
||||||
|
return CreditTx{Debitor: debitor, Credit: credit}.Wrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap - used to satisfy TxInner
|
||||||
|
func (tx CreditTx) Wrap() basecoin.Tx {
|
||||||
|
return basecoin.Tx{tx}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateBasic - used to satisfy TxInner
|
||||||
|
func (tx CreditTx) ValidateBasic() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ func TestFeeChecks(t *testing.T) {
|
||||||
// OKHandler will just return success to a RawTx
|
// OKHandler will just return success to a RawTx
|
||||||
stack.WrapHandler(stack.OKHandler{}),
|
stack.WrapHandler(stack.OKHandler{}),
|
||||||
// coin is needed to handle the IPC call from Fee middleware
|
// coin is needed to handle the IPC call from Fee middleware
|
||||||
stack.WrapHandler(coin.NewHandler()),
|
coin.NewHandler(),
|
||||||
)
|
)
|
||||||
// app1 requires no fees
|
// app1 requires no fees
|
||||||
app1 := stack.New(fee.NewSimpleFeeMiddleware(atom(0), collector)).Use(disp)
|
app1 := stack.New(fee.NewSimpleFeeMiddleware(atom(0), collector)).Use(disp)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
|
|
||||||
// nolint
|
// nolint
|
||||||
const (
|
const (
|
||||||
ByteFees = 0x21
|
ByteFees = 0x28
|
||||||
TypeFees = NameFee + "/tx"
|
TypeFees = NameFee + "/tx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,226 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"github.com/tendermint/basecoin/client/commands"
|
||||||
|
proofcmd "github.com/tendermint/basecoin/client/commands/proofs"
|
||||||
|
"github.com/tendermint/basecoin/modules/ibc"
|
||||||
|
"github.com/tendermint/basecoin/stack"
|
||||||
|
"github.com/tendermint/go-wire/data"
|
||||||
|
"github.com/tendermint/light-client/proofs"
|
||||||
|
"github.com/tendermint/merkleeyes/iavl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: query seeds (register/update)
|
||||||
|
|
||||||
|
// IBCQueryCmd - parent command to query ibc info
|
||||||
|
var IBCQueryCmd = &cobra.Command{
|
||||||
|
Use: "ibc",
|
||||||
|
Short: "Get information about IBC",
|
||||||
|
RunE: commands.RequireInit(ibcQueryCmd),
|
||||||
|
// HandlerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainsQueryCmd - get a list of all registered chains
|
||||||
|
var ChainsQueryCmd = &cobra.Command{
|
||||||
|
Use: "chains",
|
||||||
|
Short: "Get a list of all registered chains",
|
||||||
|
RunE: commands.RequireInit(chainsQueryCmd),
|
||||||
|
// ChainSet ([]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainQueryCmd - get details on one registered chain
|
||||||
|
var ChainQueryCmd = &cobra.Command{
|
||||||
|
Use: "chain [id]",
|
||||||
|
Short: "Get details on one registered chain",
|
||||||
|
RunE: commands.RequireInit(chainQueryCmd),
|
||||||
|
// ChainInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// PacketsQueryCmd - get latest packet in a queue
|
||||||
|
var PacketsQueryCmd = &cobra.Command{
|
||||||
|
Use: "packets",
|
||||||
|
Short: "Get latest packet in a queue",
|
||||||
|
RunE: commands.RequireInit(packetsQueryCmd),
|
||||||
|
// uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// PacketQueryCmd - get the names packet (by queue and sequence)
|
||||||
|
var PacketQueryCmd = &cobra.Command{
|
||||||
|
Use: "packet",
|
||||||
|
Short: "Get packet with given sequence from the named queue",
|
||||||
|
RunE: commands.RequireInit(packetQueryCmd),
|
||||||
|
// Packet
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint
|
||||||
|
const (
|
||||||
|
FlagFromChain = "from"
|
||||||
|
FlagToChain = "to"
|
||||||
|
FlagSequence = "sequence"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
IBCQueryCmd.AddCommand(
|
||||||
|
ChainQueryCmd,
|
||||||
|
ChainsQueryCmd,
|
||||||
|
PacketQueryCmd,
|
||||||
|
PacketsQueryCmd,
|
||||||
|
)
|
||||||
|
|
||||||
|
fs1 := PacketsQueryCmd.Flags()
|
||||||
|
fs1.String(FlagFromChain, "", "Name of the input chain (where packets came from)")
|
||||||
|
fs1.String(FlagToChain, "", "Name of the output chain (where packets go to)")
|
||||||
|
|
||||||
|
fs2 := PacketQueryCmd.Flags()
|
||||||
|
fs2.String(FlagFromChain, "", "Name of the input chain (where packets came from)")
|
||||||
|
fs2.String(FlagToChain, "", "Name of the output chain (where packets go to)")
|
||||||
|
fs2.Int(FlagSequence, -1, "Index of the packet in the queue (starts with 0)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ibcQueryCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
var res ibc.HandlerInfo
|
||||||
|
key := stack.PrefixedKey(ibc.NameIBC, ibc.HandlerKey())
|
||||||
|
proof, err := proofcmd.GetAndParseAppProof(key, &res)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return proofcmd.OutputProof(res, proof.BlockHeight())
|
||||||
|
}
|
||||||
|
|
||||||
|
func chainsQueryCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
list := [][]byte{}
|
||||||
|
key := stack.PrefixedKey(ibc.NameIBC, ibc.ChainsKey())
|
||||||
|
proof, err := proofcmd.GetAndParseAppProof(key, &list)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert these names to strings for better output
|
||||||
|
res := make([]string, len(list))
|
||||||
|
for i := range list {
|
||||||
|
res[i] = string(list[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return proofcmd.OutputProof(res, proof.BlockHeight())
|
||||||
|
}
|
||||||
|
|
||||||
|
func chainQueryCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
arg, err := commands.GetOneArg(args, "id")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var res ibc.ChainInfo
|
||||||
|
key := stack.PrefixedKey(ibc.NameIBC, ibc.ChainKey(arg))
|
||||||
|
proof, err := proofcmd.GetAndParseAppProof(key, &res)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return proofcmd.OutputProof(res, proof.BlockHeight())
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertOne(from, to string) error {
|
||||||
|
if from == "" && to == "" {
|
||||||
|
return errors.Errorf("You must specify either --%s or --%s",
|
||||||
|
FlagFromChain, FlagToChain)
|
||||||
|
}
|
||||||
|
if from != "" && to != "" {
|
||||||
|
return errors.Errorf("You can only specify one of --%s or --%s",
|
||||||
|
FlagFromChain, FlagToChain)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func packetsQueryCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
from := viper.GetString(FlagFromChain)
|
||||||
|
to := viper.GetString(FlagToChain)
|
||||||
|
err := assertOne(from, to)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var key []byte
|
||||||
|
if from != "" {
|
||||||
|
key = stack.PrefixedKey(ibc.NameIBC, ibc.QueueInKey(from))
|
||||||
|
} else {
|
||||||
|
key = stack.PrefixedKey(ibc.NameIBC, ibc.QueueOutKey(to))
|
||||||
|
}
|
||||||
|
|
||||||
|
var res uint64
|
||||||
|
proof, err := proofcmd.GetAndParseAppProof(key, &res)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return proofcmd.OutputProof(res, proof.BlockHeight())
|
||||||
|
}
|
||||||
|
|
||||||
|
func packetQueryCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
from := viper.GetString(FlagFromChain)
|
||||||
|
to := viper.GetString(FlagToChain)
|
||||||
|
err := assertOne(from, to)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
seq := viper.GetInt(FlagSequence)
|
||||||
|
if seq < 0 {
|
||||||
|
return errors.Errorf("--%s must be a non-negative number", FlagSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
var key []byte
|
||||||
|
if from != "" {
|
||||||
|
key = stack.PrefixedKey(ibc.NameIBC, ibc.QueueInPacketKey(from, uint64(seq)))
|
||||||
|
} else {
|
||||||
|
key = stack.PrefixedKey(ibc.NameIBC, ibc.QueueOutPacketKey(to, uint64(seq)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input queue just display the results
|
||||||
|
if from != "" {
|
||||||
|
var packet ibc.Packet
|
||||||
|
proof, err := proofcmd.GetAndParseAppProof(key, &packet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return proofcmd.OutputProof(packet, proof.BlockHeight())
|
||||||
|
}
|
||||||
|
|
||||||
|
// output queue, create a post packet
|
||||||
|
var packet ibc.Packet
|
||||||
|
proof, err := proofcmd.GetAndParseAppProof(key, &packet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: oh so ugly. fix before merge!
|
||||||
|
// wait, i want to change go-merkle too....
|
||||||
|
appProof := proof.(proofs.AppProof)
|
||||||
|
extractedProof, err := iavl.ReadProof(appProof.Proof)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the post packet here.
|
||||||
|
post := ibc.PostPacketTx{
|
||||||
|
FromChainID: commands.GetChainID(),
|
||||||
|
FromChainHeight: proof.BlockHeight(),
|
||||||
|
Key: key,
|
||||||
|
Packet: packet,
|
||||||
|
Proof: extractedProof,
|
||||||
|
}
|
||||||
|
|
||||||
|
// print json direct, as we don't need to wrap with the height
|
||||||
|
res, err := data.ToJSON(post)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(string(res))
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"github.com/tendermint/basecoin/client/commands"
|
||||||
|
txcmd "github.com/tendermint/basecoin/client/commands/txs"
|
||||||
|
"github.com/tendermint/basecoin/modules/ibc"
|
||||||
|
"github.com/tendermint/light-client/certifiers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterChainTxCmd is CLI command to register a new chain for ibc
|
||||||
|
var RegisterChainTxCmd = &cobra.Command{
|
||||||
|
Use: "ibc-register",
|
||||||
|
Short: "Register a new chain",
|
||||||
|
RunE: commands.RequireInit(registerChainTxCmd),
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateChainTxCmd is CLI command to update the header for an ibc chain
|
||||||
|
var UpdateChainTxCmd = &cobra.Command{
|
||||||
|
Use: "ibc-update",
|
||||||
|
Short: "Add new header to an existing chain",
|
||||||
|
RunE: commands.RequireInit(updateChainTxCmd),
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostPacketTxCmd is CLI command to post ibc packet on the destination chain
|
||||||
|
var PostPacketTxCmd = &cobra.Command{
|
||||||
|
Use: "ibc-post",
|
||||||
|
Short: "Post an ibc packet on the destination chain",
|
||||||
|
RunE: commands.RequireInit(postPacketTxCmd),
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: relay!
|
||||||
|
|
||||||
|
//nolint
|
||||||
|
const (
|
||||||
|
FlagSeed = "seed"
|
||||||
|
FlagPacket = "packet"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
fs1 := RegisterChainTxCmd.Flags()
|
||||||
|
fs1.String(FlagSeed, "", "Filename with a seed file")
|
||||||
|
|
||||||
|
fs2 := UpdateChainTxCmd.Flags()
|
||||||
|
fs2.String(FlagSeed, "", "Filename with a seed file")
|
||||||
|
|
||||||
|
fs3 := PostPacketTxCmd.Flags()
|
||||||
|
fs3.String(FlagPacket, "", "Filename with a packet to post")
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerChainTxCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
seed, err := readSeed()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tx := ibc.RegisterChainTx{seed}.Wrap()
|
||||||
|
return txcmd.DoTx(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateChainTxCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
seed, err := readSeed()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tx := ibc.UpdateChainTx{seed}.Wrap()
|
||||||
|
return txcmd.DoTx(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func postPacketTxCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
post, err := readPostPacket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return txcmd.DoTx(post.Wrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
func readSeed() (seed certifiers.Seed, err error) {
|
||||||
|
name := viper.GetString(FlagSeed)
|
||||||
|
if name == "" {
|
||||||
|
return seed, errors.New("You must specify a seed file")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = readFile(name, &seed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPostPacket() (post ibc.PostPacketTx, err error) {
|
||||||
|
name := viper.GetString(FlagPacket)
|
||||||
|
if name == "" {
|
||||||
|
return post, errors.New("You must specify a packet file")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = readFile(name, &post)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFile(name string, input interface{}) (err error) {
|
||||||
|
var f *os.File
|
||||||
|
f, err = os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// read the file as json into a seed
|
||||||
|
j := json.NewDecoder(f)
|
||||||
|
err = j.Decode(input)
|
||||||
|
return errors.Wrap(err, "Invalid file")
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
package ibc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
abci "github.com/tendermint/abci/types"
|
||||||
|
"github.com/tendermint/basecoin/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nolint
|
||||||
|
var (
|
||||||
|
errChainNotRegistered = fmt.Errorf("Chain not registered")
|
||||||
|
errChainAlreadyExists = fmt.Errorf("Chain already exists")
|
||||||
|
errWrongDestChain = fmt.Errorf("This is not the destination")
|
||||||
|
errNeedsIBCPermission = fmt.Errorf("Needs app-permission to send IBC")
|
||||||
|
errCannotSetPermission = fmt.Errorf("Requesting invalid permission on IBC")
|
||||||
|
errHeaderNotFound = fmt.Errorf("Header not found")
|
||||||
|
errPacketAlreadyExists = fmt.Errorf("Packet already handled")
|
||||||
|
errPacketOutOfOrder = fmt.Errorf("Packet out of order")
|
||||||
|
errInvalidProof = fmt.Errorf("Invalid merkle proof")
|
||||||
|
msgInvalidCommit = "Invalid header and commit"
|
||||||
|
|
||||||
|
IBCCodeChainNotRegistered = abci.CodeType(1001)
|
||||||
|
IBCCodeChainAlreadyExists = abci.CodeType(1002)
|
||||||
|
IBCCodeUnknownChain = abci.CodeType(1003)
|
||||||
|
IBCCodeInvalidPacketSequence = abci.CodeType(1004)
|
||||||
|
IBCCodeUnknownHeight = abci.CodeType(1005)
|
||||||
|
IBCCodeInvalidCommit = abci.CodeType(1006)
|
||||||
|
IBCCodeInvalidProof = abci.CodeType(1007)
|
||||||
|
IBCCodeInvalidCall = abci.CodeType(1008)
|
||||||
|
)
|
||||||
|
|
||||||
|
func ErrNotRegistered(chainID string) error {
|
||||||
|
return errors.WithMessage(chainID, errChainNotRegistered, IBCCodeChainNotRegistered)
|
||||||
|
}
|
||||||
|
func IsNotRegisteredErr(err error) bool {
|
||||||
|
return errors.IsSameError(errChainNotRegistered, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrAlreadyRegistered(chainID string) error {
|
||||||
|
return errors.WithMessage(chainID, errChainAlreadyExists, IBCCodeChainAlreadyExists)
|
||||||
|
}
|
||||||
|
func IsAlreadyRegisteredErr(err error) bool {
|
||||||
|
return errors.IsSameError(errChainAlreadyExists, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrWrongDestChain(chainID string) error {
|
||||||
|
return errors.WithMessage(chainID, errWrongDestChain, IBCCodeUnknownChain)
|
||||||
|
}
|
||||||
|
func IsWrongDestChainErr(err error) bool {
|
||||||
|
return errors.IsSameError(errWrongDestChain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrNeedsIBCPermission() error {
|
||||||
|
return errors.WithCode(errNeedsIBCPermission, IBCCodeInvalidCall)
|
||||||
|
}
|
||||||
|
func IsNeedsIBCPermissionErr(err error) bool {
|
||||||
|
return errors.IsSameError(errNeedsIBCPermission, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrCannotSetPermission() error {
|
||||||
|
return errors.WithCode(errCannotSetPermission, IBCCodeInvalidCall)
|
||||||
|
}
|
||||||
|
func IsCannotSetPermissionErr(err error) bool {
|
||||||
|
return errors.IsSameError(errCannotSetPermission, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrHeaderNotFound(h int) error {
|
||||||
|
msg := fmt.Sprintf("height %d", h)
|
||||||
|
return errors.WithMessage(msg, errHeaderNotFound, IBCCodeUnknownHeight)
|
||||||
|
}
|
||||||
|
func IsHeaderNotFoundErr(err error) bool {
|
||||||
|
return errors.IsSameError(errHeaderNotFound, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrPacketAlreadyExists() error {
|
||||||
|
return errors.WithCode(errPacketAlreadyExists, IBCCodeInvalidPacketSequence)
|
||||||
|
}
|
||||||
|
func IsPacketAlreadyExistsErr(err error) bool {
|
||||||
|
return errors.IsSameError(errPacketAlreadyExists, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrPacketOutOfOrder(seq uint64) error {
|
||||||
|
msg := fmt.Sprintf("expected %d", seq)
|
||||||
|
return errors.WithMessage(msg, errPacketOutOfOrder, IBCCodeInvalidPacketSequence)
|
||||||
|
}
|
||||||
|
func IsPacketOutOfOrderErr(err error) bool {
|
||||||
|
return errors.IsSameError(errPacketOutOfOrder, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrInvalidProof() error {
|
||||||
|
return errors.WithCode(errInvalidProof, IBCCodeInvalidProof)
|
||||||
|
}
|
||||||
|
func IsInvalidProofErr(err error) bool {
|
||||||
|
return errors.IsSameError(errInvalidProof, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrInvalidCommit(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.WithMessage(msgInvalidCommit, err, IBCCodeInvalidCommit)
|
||||||
|
}
|
||||||
|
func IsInvalidCommitErr(err error) bool {
|
||||||
|
return errors.HasErrorCode(err, IBCCodeInvalidCommit)
|
||||||
|
}
|
|
@ -0,0 +1,208 @@
|
||||||
|
package ibc
|
||||||
|
|
||||||
|
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/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NameIBC is the name of this module
|
||||||
|
NameIBC = "ibc"
|
||||||
|
// OptionRegistrar is the option name to set the actor
|
||||||
|
// to handle ibc chain registration
|
||||||
|
OptionRegistrar = "registrar"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Semi-random bytes that shouldn't conflict with keys (20 bytes)
|
||||||
|
// or any strings (non-ascii).
|
||||||
|
// TODO: consider how to make this more collision-proof....
|
||||||
|
allowIBC = []byte{0x42, 0xbe, 0xef, 0x1}
|
||||||
|
)
|
||||||
|
|
||||||
|
// AllowIBC returns a specially crafted Actor that
|
||||||
|
// enables sending IBC packets for this app type
|
||||||
|
func AllowIBC(app string) basecoin.Actor {
|
||||||
|
return basecoin.Actor{App: app, Address: allowIBC}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler updates the chain state or creates an ibc packet
|
||||||
|
type Handler struct{}
|
||||||
|
|
||||||
|
var _ basecoin.Handler = Handler{}
|
||||||
|
|
||||||
|
// NewHandler returns a Handler that allows all chains to connect via IBC.
|
||||||
|
// Set a Registrar via SetOption to restrict it.
|
||||||
|
func NewHandler() Handler {
|
||||||
|
return Handler{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns name space
|
||||||
|
func (Handler) Name() string {
|
||||||
|
return NameIBC
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOption sets the registrar for IBC
|
||||||
|
func (h Handler) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (log string, err error) {
|
||||||
|
if module != NameIBC {
|
||||||
|
return "", errors.ErrUnknownModule(module)
|
||||||
|
}
|
||||||
|
if key == OptionRegistrar {
|
||||||
|
var act basecoin.Actor
|
||||||
|
err = data.FromJSON([]byte(value), &act)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Save the data
|
||||||
|
info := HandlerInfo{act}
|
||||||
|
info.Save(store)
|
||||||
|
return "Success", nil
|
||||||
|
}
|
||||||
|
return "", errors.ErrUnknownKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckTx verifies the packet is formated correctly, and has the proper sequence
|
||||||
|
// for a registered chain
|
||||||
|
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||||
|
err = tx.ValidateBasic()
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t := tx.Unwrap().(type) {
|
||||||
|
case RegisterChainTx:
|
||||||
|
return h.initSeed(ctx, store, t)
|
||||||
|
case UpdateChainTx:
|
||||||
|
return h.updateSeed(ctx, store, t)
|
||||||
|
case CreatePacketTx:
|
||||||
|
return h.createPacket(ctx, store, t)
|
||||||
|
}
|
||||||
|
return res, errors.ErrUnknownTxType(tx.Unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeliverTx verifies all signatures on the tx and updates the chain state
|
||||||
|
// apropriately
|
||||||
|
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||||
|
err = tx.ValidateBasic()
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t := tx.Unwrap().(type) {
|
||||||
|
case RegisterChainTx:
|
||||||
|
return h.initSeed(ctx, store, t)
|
||||||
|
case UpdateChainTx:
|
||||||
|
return h.updateSeed(ctx, store, t)
|
||||||
|
case CreatePacketTx:
|
||||||
|
return h.createPacket(ctx, store, t)
|
||||||
|
}
|
||||||
|
return res, errors.ErrUnknownTxType(tx.Unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
// initSeed imports the first seed for this chain and
|
||||||
|
// accepts it as the root of trust.
|
||||||
|
//
|
||||||
|
// only the registrar, if set, is allowed to do this
|
||||||
|
func (h Handler) initSeed(ctx basecoin.Context, store state.SimpleDB,
|
||||||
|
t RegisterChainTx) (res basecoin.Result, err error) {
|
||||||
|
|
||||||
|
info := LoadInfo(store)
|
||||||
|
if !info.Registrar.Empty() && !ctx.HasPermission(info.Registrar) {
|
||||||
|
return res, errors.ErrUnauthorized()
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify that the header looks reasonable
|
||||||
|
chainID := t.ChainID()
|
||||||
|
s := NewChainSet(store)
|
||||||
|
err = s.Register(chainID, ctx.BlockHeight(), t.Seed.Height())
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
space := stack.PrefixedStore(chainID, store)
|
||||||
|
provider := newDBProvider(space)
|
||||||
|
err = provider.StoreSeed(t.Seed)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateSeed checks the seed against the existing chain data and rejects it if it
|
||||||
|
// doesn't fit (or no chain data)
|
||||||
|
func (h Handler) updateSeed(ctx basecoin.Context, store state.SimpleDB,
|
||||||
|
t UpdateChainTx) (res basecoin.Result, err error) {
|
||||||
|
|
||||||
|
chainID := t.ChainID()
|
||||||
|
s := NewChainSet(store)
|
||||||
|
if !s.Exists([]byte(chainID)) {
|
||||||
|
return res, ErrNotRegistered(chainID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load the certifier for this chain
|
||||||
|
seed := t.Seed
|
||||||
|
space := stack.PrefixedStore(chainID, store)
|
||||||
|
cert, err := newCertifier(space, chainID, seed.Height())
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// this will import the seed if it is valid in the current context
|
||||||
|
err = cert.Update(seed.Checkpoint, seed.Validators)
|
||||||
|
if err != nil {
|
||||||
|
return res, ErrInvalidCommit(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the tracked height in chain info
|
||||||
|
err = s.Update(chainID, t.Seed.Height())
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// createPacket makes sure all permissions are good and the destination
|
||||||
|
// chain is registed. If so, it appends it to the outgoing queue
|
||||||
|
func (h Handler) createPacket(ctx basecoin.Context, store state.SimpleDB,
|
||||||
|
t CreatePacketTx) (res basecoin.Result, err error) {
|
||||||
|
|
||||||
|
// make sure the chain is registed
|
||||||
|
dest := t.DestChain
|
||||||
|
if !NewChainSet(store).Exists([]byte(dest)) {
|
||||||
|
return res, ErrNotRegistered(dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we have the special IBC permission
|
||||||
|
mod, err := t.Tx.GetMod()
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
if !ctx.HasPermission(AllowIBC(mod)) {
|
||||||
|
return res, ErrNeedsIBCPermission()
|
||||||
|
}
|
||||||
|
|
||||||
|
// start making the packet to send
|
||||||
|
packet := Packet{
|
||||||
|
DestChain: dest,
|
||||||
|
Tx: t.Tx,
|
||||||
|
Permissions: make([]basecoin.Actor, len(t.Permissions)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we have all the permissions we want to send
|
||||||
|
for i, p := range t.Permissions {
|
||||||
|
if !ctx.HasPermission(p) {
|
||||||
|
return res, ErrCannotSetPermission()
|
||||||
|
}
|
||||||
|
// add the permission with the current ChainID
|
||||||
|
packet.Permissions[i] = p.WithChain(ctx.ChainID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// now add it to the output queue....
|
||||||
|
q := OutputQueue(store, dest)
|
||||||
|
packet.Sequence = q.Tail()
|
||||||
|
q.Push(packet.Bytes())
|
||||||
|
|
||||||
|
res = basecoin.Result{Log: fmt.Sprintf("Packet %s %d", dest, packet.Sequence)}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,413 @@
|
||||||
|
package ibc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
wire "github.com/tendermint/go-wire"
|
||||||
|
"github.com/tendermint/light-client/certifiers"
|
||||||
|
"github.com/tendermint/tmlibs/log"
|
||||||
|
|
||||||
|
"github.com/tendermint/basecoin"
|
||||||
|
"github.com/tendermint/basecoin/errors"
|
||||||
|
"github.com/tendermint/basecoin/stack"
|
||||||
|
"github.com/tendermint/basecoin/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
// this tests registration without registrar permissions
|
||||||
|
func TestIBCRegister(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
// the validators we use to make seeds
|
||||||
|
keys := certifiers.GenValKeys(5)
|
||||||
|
keys2 := certifiers.GenValKeys(7)
|
||||||
|
appHash := []byte{0, 4, 7, 23}
|
||||||
|
appHash2 := []byte{12, 34, 56, 78}
|
||||||
|
|
||||||
|
// badSeed doesn't validate
|
||||||
|
badSeed := genEmptySeed(keys2, "chain-2", 123, appHash, len(keys2))
|
||||||
|
badSeed.Header.AppHash = appHash2
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
seed certifiers.Seed
|
||||||
|
checker errors.CheckErr
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
genEmptySeed(keys, "chain-1", 100, appHash, len(keys)),
|
||||||
|
errors.NoErr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
genEmptySeed(keys, "chain-1", 200, appHash, len(keys)),
|
||||||
|
IsAlreadyRegisteredErr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
badSeed,
|
||||||
|
IsInvalidCommitErr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
genEmptySeed(keys2, "chain-2", 123, appHash2, 5),
|
||||||
|
errors.NoErr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := stack.MockContext("hub", 50)
|
||||||
|
store := state.NewMemKVStore()
|
||||||
|
app := stack.New().Dispatch(stack.WrapHandler(NewHandler()))
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
tx := RegisterChainTx{tc.seed}.Wrap()
|
||||||
|
_, err := app.DeliverTx(ctx, store, tx)
|
||||||
|
assert.True(tc.checker(err), "%d: %+v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this tests permission controls on ibc registration
|
||||||
|
func TestIBCRegisterPermissions(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
// the validators we use to make seeds
|
||||||
|
keys := certifiers.GenValKeys(4)
|
||||||
|
appHash := []byte{0x17, 0x21, 0x5, 0x1e}
|
||||||
|
|
||||||
|
foobar := basecoin.Actor{App: "foo", Address: []byte("bar")}
|
||||||
|
baz := basecoin.Actor{App: "baz", Address: []byte("bar")}
|
||||||
|
foobaz := basecoin.Actor{App: "foo", Address: []byte("baz")}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
seed certifiers.Seed
|
||||||
|
registrar basecoin.Actor
|
||||||
|
signer basecoin.Actor
|
||||||
|
checker errors.CheckErr
|
||||||
|
}{
|
||||||
|
// no sig, no registrar
|
||||||
|
{
|
||||||
|
seed: genEmptySeed(keys, "chain-1", 100, appHash, len(keys)),
|
||||||
|
checker: errors.NoErr,
|
||||||
|
},
|
||||||
|
// sig, no registrar
|
||||||
|
{
|
||||||
|
seed: genEmptySeed(keys, "chain-2", 100, appHash, len(keys)),
|
||||||
|
signer: foobaz,
|
||||||
|
checker: errors.NoErr,
|
||||||
|
},
|
||||||
|
// registrar, no sig
|
||||||
|
{
|
||||||
|
seed: genEmptySeed(keys, "chain-3", 100, appHash, len(keys)),
|
||||||
|
registrar: foobar,
|
||||||
|
checker: errors.IsUnauthorizedErr,
|
||||||
|
},
|
||||||
|
// registrar, wrong sig
|
||||||
|
{
|
||||||
|
seed: genEmptySeed(keys, "chain-4", 100, appHash, len(keys)),
|
||||||
|
signer: foobaz,
|
||||||
|
registrar: foobar,
|
||||||
|
checker: errors.IsUnauthorizedErr,
|
||||||
|
},
|
||||||
|
// registrar, wrong sig
|
||||||
|
{
|
||||||
|
seed: genEmptySeed(keys, "chain-5", 100, appHash, len(keys)),
|
||||||
|
signer: baz,
|
||||||
|
registrar: foobar,
|
||||||
|
checker: errors.IsUnauthorizedErr,
|
||||||
|
},
|
||||||
|
// registrar, proper sig
|
||||||
|
{
|
||||||
|
seed: genEmptySeed(keys, "chain-6", 100, appHash, len(keys)),
|
||||||
|
signer: foobar,
|
||||||
|
registrar: foobar,
|
||||||
|
checker: errors.NoErr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
store := state.NewMemKVStore()
|
||||||
|
app := stack.New().Dispatch(stack.WrapHandler(NewHandler()))
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
// set option specifies the registrar
|
||||||
|
msg, err := json.Marshal(tc.registrar)
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
_, err = app.SetOption(log.NewNopLogger(), store,
|
||||||
|
NameIBC, OptionRegistrar, string(msg))
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
|
||||||
|
// add permissions to the context
|
||||||
|
ctx := stack.MockContext("hub", 50).WithPermissions(tc.signer)
|
||||||
|
tx := RegisterChainTx{tc.seed}.Wrap()
|
||||||
|
_, err = app.DeliverTx(ctx, store, tx)
|
||||||
|
assert.True(tc.checker(err), "%d: %+v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this verifies that we can properly update the headers on the chain
|
||||||
|
func TestIBCUpdate(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
// this is the root seed, that others are evaluated against
|
||||||
|
keys := certifiers.GenValKeys(7)
|
||||||
|
appHash := []byte{0, 4, 7, 23}
|
||||||
|
start := 100 // initial height
|
||||||
|
root := genEmptySeed(keys, "chain-1", 100, appHash, len(keys))
|
||||||
|
|
||||||
|
keys2 := keys.Extend(2)
|
||||||
|
keys3 := keys2.Extend(2)
|
||||||
|
|
||||||
|
// create the app and register the root of trust (for chain-1)
|
||||||
|
ctx := stack.MockContext("hub", 50)
|
||||||
|
store := state.NewMemKVStore()
|
||||||
|
app := stack.New().Dispatch(stack.WrapHandler(NewHandler()))
|
||||||
|
tx := RegisterChainTx{root}.Wrap()
|
||||||
|
_, err := app.DeliverTx(ctx, store, tx)
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
seed certifiers.Seed
|
||||||
|
checker errors.CheckErr
|
||||||
|
}{
|
||||||
|
// same validator, higher up
|
||||||
|
{
|
||||||
|
genEmptySeed(keys, "chain-1", start+50, []byte{22}, len(keys)),
|
||||||
|
errors.NoErr,
|
||||||
|
},
|
||||||
|
// same validator, between existing (not most recent)
|
||||||
|
{
|
||||||
|
genEmptySeed(keys, "chain-1", start+5, []byte{15, 43}, len(keys)),
|
||||||
|
errors.NoErr,
|
||||||
|
},
|
||||||
|
// same validators, before root of trust
|
||||||
|
{
|
||||||
|
genEmptySeed(keys, "chain-1", start-8, []byte{11, 77}, len(keys)),
|
||||||
|
IsHeaderNotFoundErr,
|
||||||
|
},
|
||||||
|
// insufficient signatures
|
||||||
|
{
|
||||||
|
genEmptySeed(keys, "chain-1", start+60, []byte{24}, len(keys)/2),
|
||||||
|
IsInvalidCommitErr,
|
||||||
|
},
|
||||||
|
// unregistered chain
|
||||||
|
{
|
||||||
|
genEmptySeed(keys, "chain-2", start+60, []byte{24}, len(keys)/2),
|
||||||
|
IsNotRegisteredErr,
|
||||||
|
},
|
||||||
|
// too much change (keys -> keys3)
|
||||||
|
{
|
||||||
|
genEmptySeed(keys3, "chain-1", start+100, []byte{22}, len(keys3)),
|
||||||
|
IsInvalidCommitErr,
|
||||||
|
},
|
||||||
|
// legit update to validator set (keys -> keys2)
|
||||||
|
{
|
||||||
|
genEmptySeed(keys2, "chain-1", start+90, []byte{33}, len(keys2)),
|
||||||
|
errors.NoErr,
|
||||||
|
},
|
||||||
|
// now impossible jump works (keys -> keys2 -> keys3)
|
||||||
|
{
|
||||||
|
genEmptySeed(keys3, "chain-1", start+100, []byte{44}, len(keys3)),
|
||||||
|
errors.NoErr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
tx := UpdateChainTx{tc.seed}.Wrap()
|
||||||
|
_, err := app.DeliverTx(ctx, store, tx)
|
||||||
|
assert.True(tc.checker(err), "%d: %+v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to create an ibc packet and verify the number we get back
|
||||||
|
func TestIBCCreatePacket(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
// this is the root seed, that others are evaluated against
|
||||||
|
keys := certifiers.GenValKeys(7)
|
||||||
|
appHash := []byte{1, 2, 3, 4}
|
||||||
|
start := 100 // initial height
|
||||||
|
chainID := "cosmos-hub"
|
||||||
|
root := genEmptySeed(keys, chainID, start, appHash, len(keys))
|
||||||
|
|
||||||
|
// create the app and register the root of trust (for chain-1)
|
||||||
|
ctx := stack.MockContext("hub", 50)
|
||||||
|
store := state.NewMemKVStore()
|
||||||
|
app := stack.New().Dispatch(stack.WrapHandler(NewHandler()))
|
||||||
|
tx := RegisterChainTx{root}.Wrap()
|
||||||
|
_, err := app.DeliverTx(ctx, store, tx)
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
|
||||||
|
// this is the tx we send, and the needed permission to send it
|
||||||
|
raw := stack.NewRawTx([]byte{0xbe, 0xef})
|
||||||
|
ibcPerm := AllowIBC(stack.NameOK)
|
||||||
|
somePerm := basecoin.Actor{App: "some", Address: []byte("perm")}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
dest string
|
||||||
|
ibcPerms basecoin.Actors
|
||||||
|
ctxPerms basecoin.Actors
|
||||||
|
checker errors.CheckErr
|
||||||
|
}{
|
||||||
|
// wrong chain -> error
|
||||||
|
{
|
||||||
|
dest: "some-other-chain",
|
||||||
|
ctxPerms: basecoin.Actors{ibcPerm},
|
||||||
|
checker: IsNotRegisteredErr,
|
||||||
|
},
|
||||||
|
|
||||||
|
// no ibc permission -> error
|
||||||
|
{
|
||||||
|
dest: chainID,
|
||||||
|
checker: IsNeedsIBCPermissionErr,
|
||||||
|
},
|
||||||
|
|
||||||
|
// correct -> nice sequence
|
||||||
|
{
|
||||||
|
dest: chainID,
|
||||||
|
ctxPerms: basecoin.Actors{ibcPerm},
|
||||||
|
checker: errors.NoErr,
|
||||||
|
},
|
||||||
|
|
||||||
|
// requesting invalid permissions -> error
|
||||||
|
{
|
||||||
|
dest: chainID,
|
||||||
|
ibcPerms: basecoin.Actors{somePerm},
|
||||||
|
ctxPerms: basecoin.Actors{ibcPerm},
|
||||||
|
checker: IsCannotSetPermissionErr,
|
||||||
|
},
|
||||||
|
|
||||||
|
// requesting extra permissions when present
|
||||||
|
{
|
||||||
|
dest: chainID,
|
||||||
|
ibcPerms: basecoin.Actors{somePerm},
|
||||||
|
ctxPerms: basecoin.Actors{ibcPerm, somePerm},
|
||||||
|
checker: errors.NoErr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
tx := CreatePacketTx{
|
||||||
|
DestChain: tc.dest,
|
||||||
|
Permissions: tc.ibcPerms,
|
||||||
|
Tx: raw,
|
||||||
|
}.Wrap()
|
||||||
|
|
||||||
|
myCtx := ctx.WithPermissions(tc.ctxPerms...)
|
||||||
|
_, err = app.DeliverTx(myCtx, store, tx)
|
||||||
|
assert.True(tc.checker(err), "%d: %+v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// query packet state - make sure both packets are properly writen
|
||||||
|
p := stack.PrefixedStore(NameIBC, store)
|
||||||
|
q := OutputQueue(p, chainID)
|
||||||
|
if assert.Equal(2, q.Size()) {
|
||||||
|
expected := []struct {
|
||||||
|
seq uint64
|
||||||
|
perm basecoin.Actors
|
||||||
|
}{
|
||||||
|
{0, nil},
|
||||||
|
{1, basecoin.Actors{somePerm}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range expected {
|
||||||
|
var packet Packet
|
||||||
|
err = wire.ReadBinaryBytes(q.Pop(), &packet)
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
assert.Equal(chainID, packet.DestChain)
|
||||||
|
assert.EqualValues(tc.seq, packet.Sequence)
|
||||||
|
assert.Equal(raw, packet.Tx)
|
||||||
|
assert.Equal(len(tc.perm), len(packet.Permissions))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIBCPostPacket(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
otherID := "chain-1"
|
||||||
|
ourID := "hub"
|
||||||
|
start := 200
|
||||||
|
msg := "it's okay"
|
||||||
|
|
||||||
|
// create the app and our chain
|
||||||
|
app := stack.New().
|
||||||
|
IBC(NewMiddleware()).
|
||||||
|
Dispatch(
|
||||||
|
stack.WrapHandler(NewHandler()),
|
||||||
|
stack.WrapHandler(stack.OKHandler{Log: msg}),
|
||||||
|
)
|
||||||
|
ourChain := NewAppChain(app, ourID)
|
||||||
|
|
||||||
|
// set up the other chain and register it with us
|
||||||
|
otherChain := NewMockChain(otherID, 7)
|
||||||
|
registerTx := otherChain.GetRegistrationTx(start).Wrap()
|
||||||
|
_, err := ourChain.DeliverTx(registerTx)
|
||||||
|
require.Nil(err, "%+v", err)
|
||||||
|
|
||||||
|
// make a random tx that is to be passed
|
||||||
|
rawTx := stack.NewRawTx([]byte{17, 24, 3, 8})
|
||||||
|
|
||||||
|
randomChain := NewMockChain("something-else", 4)
|
||||||
|
pbad := NewPacket(rawTx, "something-else", 0)
|
||||||
|
packetBad, _ := randomChain.MakePostPacket(pbad, 123)
|
||||||
|
|
||||||
|
p0 := NewPacket(rawTx, ourID, 0)
|
||||||
|
packet0, update0 := otherChain.MakePostPacket(p0, start+5)
|
||||||
|
require.Nil(ourChain.Update(update0))
|
||||||
|
|
||||||
|
packet0badHeight := packet0
|
||||||
|
packet0badHeight.FromChainHeight -= 2
|
||||||
|
|
||||||
|
theirActor := basecoin.Actor{ChainID: otherID, App: "foo", Address: []byte{1}}
|
||||||
|
p1 := NewPacket(rawTx, ourID, 1, theirActor)
|
||||||
|
packet1, update1 := otherChain.MakePostPacket(p1, start+25)
|
||||||
|
require.Nil(ourChain.Update(update1))
|
||||||
|
|
||||||
|
packet1badProof := packet1
|
||||||
|
packet1badProof.Key = []byte("random-data")
|
||||||
|
|
||||||
|
ourActor := basecoin.Actor{ChainID: ourID, App: "bar", Address: []byte{2}}
|
||||||
|
p2 := NewPacket(rawTx, ourID, 2, ourActor)
|
||||||
|
packet2, update2 := otherChain.MakePostPacket(p2, start+50)
|
||||||
|
require.Nil(ourChain.Update(update2))
|
||||||
|
|
||||||
|
ibcPerm := basecoin.Actors{AllowIBC(stack.NameOK)}
|
||||||
|
cases := []struct {
|
||||||
|
packet PostPacketTx
|
||||||
|
permissions basecoin.Actors
|
||||||
|
checker errors.CheckErr
|
||||||
|
}{
|
||||||
|
// bad chain -> error
|
||||||
|
{packetBad, ibcPerm, IsNotRegisteredErr},
|
||||||
|
|
||||||
|
// no matching header -> error
|
||||||
|
{packet0badHeight, nil, IsHeaderNotFoundErr},
|
||||||
|
|
||||||
|
// out of order -> error
|
||||||
|
{packet1, ibcPerm, IsPacketOutOfOrderErr},
|
||||||
|
|
||||||
|
// all good -> execute tx
|
||||||
|
{packet0, ibcPerm, errors.NoErr},
|
||||||
|
|
||||||
|
// bad proof -> error
|
||||||
|
{packet1badProof, ibcPerm, IsInvalidProofErr},
|
||||||
|
|
||||||
|
// all good -> execute tx (no special permission needed)
|
||||||
|
{packet1, nil, errors.NoErr},
|
||||||
|
|
||||||
|
// repeat -> error
|
||||||
|
{packet0, nil, IsPacketAlreadyExistsErr},
|
||||||
|
|
||||||
|
// packet2 contains invalid permissions
|
||||||
|
{packet2, nil, IsCannotSetPermissionErr},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
res, err := ourChain.DeliverTx(tc.packet.Wrap(), tc.permissions...)
|
||||||
|
assert.True(tc.checker(err), "%d: %+v", i, err)
|
||||||
|
if err == nil {
|
||||||
|
assert.Equal(msg, res.Log)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package ibc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tendermint/basecoin/stack"
|
||||||
|
"github.com/tendermint/basecoin/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// this is the prefix for the list of chains
|
||||||
|
// we otherwise use the chainid as prefix, so this must not be an
|
||||||
|
// alpha-numeric byte
|
||||||
|
prefixChains = "**"
|
||||||
|
|
||||||
|
prefixInput = "i"
|
||||||
|
prefixOutput = "o"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandlerKey is used for the global permission info
|
||||||
|
func HandlerKey() []byte {
|
||||||
|
return []byte{0x2}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainsKey is the key to get info on all chains
|
||||||
|
func ChainsKey() []byte {
|
||||||
|
return stack.PrefixedKey(prefixChains, state.SetKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainKey is the key to get info on one chain
|
||||||
|
func ChainKey(chainID string) []byte {
|
||||||
|
bkey := state.MakeBKey([]byte(chainID))
|
||||||
|
return stack.PrefixedKey(prefixChains, bkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueueInKey is the key to get newest of the input queue from this chain
|
||||||
|
func QueueInKey(chainID string) []byte {
|
||||||
|
return stack.PrefixedKey(chainID,
|
||||||
|
stack.PrefixedKey(prefixInput,
|
||||||
|
state.QueueTailKey()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueueOutKey is the key to get v of the output queue from this chain
|
||||||
|
func QueueOutKey(chainID string) []byte {
|
||||||
|
return stack.PrefixedKey(chainID,
|
||||||
|
stack.PrefixedKey(prefixOutput,
|
||||||
|
state.QueueTailKey()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueueInPacketKey is the key to get given packet from this chain's input queue
|
||||||
|
func QueueInPacketKey(chainID string, seq uint64) []byte {
|
||||||
|
return stack.PrefixedKey(chainID,
|
||||||
|
stack.PrefixedKey(prefixInput,
|
||||||
|
state.QueueItemKey(seq)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueueOutPacketKey is the key to get given packet from this chain's output queue
|
||||||
|
func QueueOutPacketKey(chainID string, seq uint64) []byte {
|
||||||
|
return stack.PrefixedKey(chainID,
|
||||||
|
stack.PrefixedKey(prefixOutput,
|
||||||
|
state.QueueItemKey(seq)))
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
package ibc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tendermint/basecoin"
|
||||||
|
"github.com/tendermint/basecoin/stack"
|
||||||
|
"github.com/tendermint/basecoin/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Middleware allows us to verify the IBC proof on a packet and
|
||||||
|
// and if valid, attach this permission to the wrapped packet
|
||||||
|
type Middleware struct {
|
||||||
|
stack.PassOption
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ stack.Middleware = Middleware{}
|
||||||
|
|
||||||
|
// NewMiddleware creates a role-checking middleware
|
||||||
|
func NewMiddleware() Middleware {
|
||||||
|
return Middleware{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name - return name space
|
||||||
|
func (Middleware) Name() string {
|
||||||
|
return NameIBC
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckTx verifies the named chain and height is present, and verifies
|
||||||
|
// the merkle proof in the packet
|
||||||
|
func (m Middleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
|
||||||
|
// if it is not a PostPacket, just let it go through
|
||||||
|
post, ok := tx.Unwrap().(PostPacketTx)
|
||||||
|
if !ok {
|
||||||
|
return next.CheckTx(ctx, store, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse this packet and get the ibc-enhanced tx and context
|
||||||
|
ictx, itx, err := m.verifyPost(ctx, store, post)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
return next.CheckTx(ictx, store, itx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeliverTx verifies the named chain and height is present, and verifies
|
||||||
|
// the merkle proof in the packet
|
||||||
|
func (m Middleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
|
||||||
|
// if it is not a PostPacket, just let it go through
|
||||||
|
post, ok := tx.Unwrap().(PostPacketTx)
|
||||||
|
if !ok {
|
||||||
|
return next.DeliverTx(ctx, store, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse this packet and get the ibc-enhanced tx and context
|
||||||
|
ictx, itx, err := m.verifyPost(ctx, store, post)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
return next.DeliverTx(ictx, store, itx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyPost accepts a message bound for this chain...
|
||||||
|
// TODO: think about relay
|
||||||
|
func (m Middleware) verifyPost(ctx basecoin.Context, store state.SimpleDB,
|
||||||
|
tx PostPacketTx) (ictx basecoin.Context, itx basecoin.Tx, err error) {
|
||||||
|
|
||||||
|
// make sure the chain is registered
|
||||||
|
from := tx.FromChainID
|
||||||
|
if !NewChainSet(store).Exists([]byte(from)) {
|
||||||
|
return ictx, itx, ErrNotRegistered(from)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: how to deal with routing/relaying???
|
||||||
|
packet := tx.Packet
|
||||||
|
if packet.DestChain != ctx.ChainID() {
|
||||||
|
return ictx, itx, ErrWrongDestChain(packet.DestChain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify packet.Permissions all come from the other chain
|
||||||
|
if !packet.Permissions.AllHaveChain(tx.FromChainID) {
|
||||||
|
return ictx, itx, ErrCannotSetPermission()
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure this sequence number is the next in the list
|
||||||
|
q := InputQueue(store, from)
|
||||||
|
tail := q.Tail()
|
||||||
|
if packet.Sequence < tail {
|
||||||
|
return ictx, itx, ErrPacketAlreadyExists()
|
||||||
|
}
|
||||||
|
if packet.Sequence > tail {
|
||||||
|
return ictx, itx, ErrPacketOutOfOrder(tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
// look up the referenced header
|
||||||
|
space := stack.PrefixedStore(from, store)
|
||||||
|
provider := newDBProvider(space)
|
||||||
|
seed, err := provider.GetExactHeight(int(tx.FromChainHeight))
|
||||||
|
if err != nil {
|
||||||
|
return ictx, itx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify the merkle hash....
|
||||||
|
root := seed.Header.AppHash
|
||||||
|
pBytes := packet.Bytes()
|
||||||
|
valid := tx.Proof.Verify(tx.Key, pBytes, root)
|
||||||
|
if !valid {
|
||||||
|
return ictx, itx, ErrInvalidProof()
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to input queue
|
||||||
|
q.Push(pBytes)
|
||||||
|
|
||||||
|
// return the wrapped tx along with the extra permissions
|
||||||
|
ictx = ctx.WithPermissions(packet.Permissions...)
|
||||||
|
itx = packet.Tx
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package ibc
|
||||||
|
|
||||||
|
import (
|
||||||
|
wire "github.com/tendermint/go-wire"
|
||||||
|
"github.com/tendermint/light-client/certifiers"
|
||||||
|
|
||||||
|
"github.com/tendermint/basecoin/stack"
|
||||||
|
"github.com/tendermint/basecoin/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
prefixHash = "v"
|
||||||
|
prefixHeight = "h"
|
||||||
|
prefixPacket = "p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newCertifier loads up the current state of this chain to make a proper certifier
|
||||||
|
// it will load the most recent height before block h if h is positive
|
||||||
|
// if h < 0, it will load the latest height
|
||||||
|
func newCertifier(store state.SimpleDB, chainID string, h int) (*certifiers.InquiringCertifier, error) {
|
||||||
|
// each chain has their own prefixed subspace
|
||||||
|
p := newDBProvider(store)
|
||||||
|
|
||||||
|
var seed certifiers.Seed
|
||||||
|
var err error
|
||||||
|
if h > 0 {
|
||||||
|
// this gets the most recent verified seed below the specified height
|
||||||
|
seed, err = p.GetByHeight(h)
|
||||||
|
} else {
|
||||||
|
// 0 or negative means start at latest seed
|
||||||
|
seed, err = certifiers.LatestSeed(p)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrHeaderNotFound(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have no source for untrusted keys, but use the db to load trusted history
|
||||||
|
cert := certifiers.NewInquiring(chainID, seed, p,
|
||||||
|
certifiers.MissingProvider{})
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dbProvider wraps our kv store so it integrates with light-client verification
|
||||||
|
type dbProvider struct {
|
||||||
|
byHash state.SimpleDB
|
||||||
|
byHeight *state.Span
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDBProvider(store state.SimpleDB) *dbProvider {
|
||||||
|
return &dbProvider{
|
||||||
|
byHash: stack.PrefixedStore(prefixHash, store),
|
||||||
|
byHeight: state.NewSpan(stack.PrefixedStore(prefixHeight, store)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ certifiers.Provider = &dbProvider{}
|
||||||
|
|
||||||
|
func (d *dbProvider) StoreSeed(seed certifiers.Seed) error {
|
||||||
|
// TODO: don't duplicate data....
|
||||||
|
b := wire.BinaryBytes(seed)
|
||||||
|
d.byHash.Set(seed.Hash(), b)
|
||||||
|
d.byHeight.Set(uint64(seed.Height()), b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbProvider) GetByHeight(h int) (seed certifiers.Seed, err error) {
|
||||||
|
b, _ := d.byHeight.LTE(uint64(h))
|
||||||
|
if b == nil {
|
||||||
|
return seed, certifiers.ErrSeedNotFound()
|
||||||
|
}
|
||||||
|
err = wire.ReadBinaryBytes(b, &seed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbProvider) GetByHash(hash []byte) (seed certifiers.Seed, err error) {
|
||||||
|
b := d.byHash.Get(hash)
|
||||||
|
if b == nil {
|
||||||
|
return seed, certifiers.ErrSeedNotFound()
|
||||||
|
}
|
||||||
|
err = wire.ReadBinaryBytes(b, &seed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExactHeight is like GetByHeight, but returns an error instead of
|
||||||
|
// closest match if there is no exact match
|
||||||
|
func (d *dbProvider) GetExactHeight(h int) (seed certifiers.Seed, err error) {
|
||||||
|
seed, err = d.GetByHeight(h)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if seed.Height() != h {
|
||||||
|
err = ErrHeaderNotFound(h)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
package ibc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/tendermint/basecoin/state"
|
||||||
|
"github.com/tendermint/light-client/certifiers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assertSeedEqual(t *testing.T, s, s2 certifiers.Seed) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
assert.Equal(s.Height(), s2.Height())
|
||||||
|
assert.Equal(s.Hash(), s2.Hash())
|
||||||
|
// TODO: more
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderStore(t *testing.T) {
|
||||||
|
assert, require := assert.New(t), require.New(t)
|
||||||
|
|
||||||
|
// make a few seeds
|
||||||
|
keys := certifiers.GenValKeys(2)
|
||||||
|
seeds := makeSeeds(keys, 4, "some-chain", "demo-store")
|
||||||
|
|
||||||
|
// make a provider
|
||||||
|
store := state.NewMemKVStore()
|
||||||
|
p := newDBProvider(store)
|
||||||
|
|
||||||
|
// check it...
|
||||||
|
_, err := p.GetByHeight(20)
|
||||||
|
require.NotNil(err)
|
||||||
|
assert.True(certifiers.IsSeedNotFoundErr(err))
|
||||||
|
|
||||||
|
// add a seed
|
||||||
|
for _, s := range seeds {
|
||||||
|
err = p.StoreSeed(s)
|
||||||
|
require.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we get it...
|
||||||
|
s := seeds[0]
|
||||||
|
val, err := p.GetByHeight(s.Height())
|
||||||
|
if assert.Nil(err) {
|
||||||
|
assertSeedEqual(t, s, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we get higher
|
||||||
|
val, err = p.GetByHeight(s.Height() + 2)
|
||||||
|
if assert.Nil(err) {
|
||||||
|
assertSeedEqual(t, s, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// below is nothing
|
||||||
|
_, err = p.GetByHeight(s.Height() - 2)
|
||||||
|
assert.True(certifiers.IsSeedNotFoundErr(err))
|
||||||
|
|
||||||
|
// make sure we get highest
|
||||||
|
val, err = certifiers.LatestSeed(p)
|
||||||
|
if assert.Nil(err) {
|
||||||
|
assertSeedEqual(t, seeds[3], val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure by hash also (note all have same hash, so overwritten)
|
||||||
|
val, err = p.GetByHash(seeds[1].Hash())
|
||||||
|
if assert.Nil(err) {
|
||||||
|
assertSeedEqual(t, seeds[3], val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDBProvider(t *testing.T) {
|
||||||
|
store := state.NewMemKVStore()
|
||||||
|
p := newDBProvider(store)
|
||||||
|
checkProvider(t, p, "test-db", "bling")
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSeeds(keys certifiers.ValKeys, count int, chainID, app string) []certifiers.Seed {
|
||||||
|
appHash := []byte(app)
|
||||||
|
seeds := make([]certifiers.Seed, count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
// two seeds for each validator, to check how we handle dups
|
||||||
|
// (10, 0), (10, 1), (10, 1), (10, 2), (10, 2), ...
|
||||||
|
vals := keys.ToValidators(10, int64(count/2))
|
||||||
|
h := 20 + 10*i
|
||||||
|
check := keys.GenCheckpoint(chainID, h, nil, vals, appHash, 0, len(keys))
|
||||||
|
seeds[i] = certifiers.Seed{check, vals}
|
||||||
|
}
|
||||||
|
return seeds
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkProvider(t *testing.T, p certifiers.Provider, chainID, app string) {
|
||||||
|
assert, require := assert.New(t), require.New(t)
|
||||||
|
keys := certifiers.GenValKeys(5)
|
||||||
|
count := 10
|
||||||
|
|
||||||
|
// make a bunch of seeds...
|
||||||
|
seeds := makeSeeds(keys, count, chainID, app)
|
||||||
|
|
||||||
|
// check provider is empty
|
||||||
|
seed, err := p.GetByHeight(20)
|
||||||
|
require.NotNil(err)
|
||||||
|
assert.True(certifiers.IsSeedNotFoundErr(err))
|
||||||
|
|
||||||
|
seed, err = p.GetByHash(seeds[3].Hash())
|
||||||
|
require.NotNil(err)
|
||||||
|
assert.True(certifiers.IsSeedNotFoundErr(err))
|
||||||
|
|
||||||
|
// now add them all to the provider
|
||||||
|
for _, s := range seeds {
|
||||||
|
err = p.StoreSeed(s)
|
||||||
|
require.Nil(err)
|
||||||
|
// and make sure we can get it back
|
||||||
|
s2, err := p.GetByHash(s.Hash())
|
||||||
|
assert.Nil(err)
|
||||||
|
assertSeedEqual(t, s, s2)
|
||||||
|
// by height as well
|
||||||
|
s2, err = p.GetByHeight(s.Height())
|
||||||
|
assert.Nil(err)
|
||||||
|
assertSeedEqual(t, s, s2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we get the last hash if we overstep
|
||||||
|
seed, err = p.GetByHeight(5000)
|
||||||
|
if assert.Nil(err) {
|
||||||
|
assertSeedEqual(t, seeds[count-1], seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// and middle ones as well
|
||||||
|
seed, err = p.GetByHeight(47)
|
||||||
|
if assert.Nil(err) {
|
||||||
|
// we only step by 10, so 40 must be the one below this
|
||||||
|
assert.Equal(40, seed.Height())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package ibc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tendermint/basecoin"
|
||||||
|
"github.com/tendermint/basecoin/stack"
|
||||||
|
"github.com/tendermint/basecoin/state"
|
||||||
|
wire "github.com/tendermint/go-wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandlerInfo is the global state of the ibc.Handler
|
||||||
|
type HandlerInfo struct {
|
||||||
|
Registrar basecoin.Actor `json:"registrar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the HandlerInfo to the store
|
||||||
|
func (h HandlerInfo) Save(store state.SimpleDB) {
|
||||||
|
b := wire.BinaryBytes(h)
|
||||||
|
store.Set(HandlerKey(), b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadInfo loads the HandlerInfo from the data store
|
||||||
|
func LoadInfo(store state.SimpleDB) (h HandlerInfo) {
|
||||||
|
b := store.Get(HandlerKey())
|
||||||
|
if len(b) > 0 {
|
||||||
|
wire.ReadBinaryBytes(b, &h)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainInfo is the global info we store for each registered chain,
|
||||||
|
// besides the headers, proofs, and packets
|
||||||
|
type ChainInfo struct {
|
||||||
|
RegisteredAt uint64 `json:"registered_at"`
|
||||||
|
RemoteBlock int `json:"remote_block"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainSet is the set of all registered chains
|
||||||
|
type ChainSet struct {
|
||||||
|
*state.Set
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChainSet loads or initialized the ChainSet
|
||||||
|
func NewChainSet(store state.SimpleDB) ChainSet {
|
||||||
|
space := stack.PrefixedStore(prefixChains, store)
|
||||||
|
return ChainSet{
|
||||||
|
Set: state.NewSet(space),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register adds the named chain with some info
|
||||||
|
// returns error if already present
|
||||||
|
func (c ChainSet) Register(chainID string, ourHeight uint64, theirHeight int) error {
|
||||||
|
if c.Exists([]byte(chainID)) {
|
||||||
|
return ErrAlreadyRegistered(chainID)
|
||||||
|
}
|
||||||
|
info := ChainInfo{
|
||||||
|
RegisteredAt: ourHeight,
|
||||||
|
RemoteBlock: theirHeight,
|
||||||
|
}
|
||||||
|
data := wire.BinaryBytes(info)
|
||||||
|
c.Set.Set([]byte(chainID), data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update sets the new tracked height on this chain
|
||||||
|
// returns error if not present
|
||||||
|
func (c ChainSet) Update(chainID string, theirHeight int) error {
|
||||||
|
d := c.Set.Get([]byte(chainID))
|
||||||
|
if len(d) == 0 {
|
||||||
|
return ErrNotRegistered(chainID)
|
||||||
|
}
|
||||||
|
// load the data
|
||||||
|
var info ChainInfo
|
||||||
|
err := wire.ReadBinaryBytes(d, &info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// change the remote block and save it
|
||||||
|
info.RemoteBlock = theirHeight
|
||||||
|
d = wire.BinaryBytes(info)
|
||||||
|
c.Set.Set([]byte(chainID), d)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packet is a wrapped transaction and permission that we want to
|
||||||
|
// send off to another chain.
|
||||||
|
type Packet struct {
|
||||||
|
DestChain string `json:"dest_chain"`
|
||||||
|
Sequence uint64 `json:"sequence"`
|
||||||
|
Permissions basecoin.Actors `json:"permissions"`
|
||||||
|
Tx basecoin.Tx `json:"tx"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPacket creates a new outgoing packet
|
||||||
|
func NewPacket(tx basecoin.Tx, dest string, seq uint64, perm ...basecoin.Actor) Packet {
|
||||||
|
return Packet{
|
||||||
|
DestChain: dest,
|
||||||
|
Sequence: seq,
|
||||||
|
Permissions: perm,
|
||||||
|
Tx: tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns a serialization of the Packet
|
||||||
|
func (p Packet) Bytes() []byte {
|
||||||
|
return wire.BinaryBytes(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputQueue returns the queue of input packets from this chain
|
||||||
|
func InputQueue(store state.SimpleDB, chainID string) *state.Queue {
|
||||||
|
ch := stack.PrefixedStore(chainID, store)
|
||||||
|
space := stack.PrefixedStore(prefixInput, ch)
|
||||||
|
return state.NewQueue(space)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutputQueue returns the queue of output packets destined for this chain
|
||||||
|
func OutputQueue(store state.SimpleDB, chainID string) *state.Queue {
|
||||||
|
ch := stack.PrefixedStore(chainID, store)
|
||||||
|
space := stack.PrefixedStore(prefixOutput, ch)
|
||||||
|
return state.NewQueue(space)
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
package ibc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tendermint/light-client/certifiers"
|
||||||
|
"github.com/tendermint/merkleeyes/iavl"
|
||||||
|
"github.com/tendermint/tmlibs/log"
|
||||||
|
|
||||||
|
"github.com/tendermint/basecoin"
|
||||||
|
"github.com/tendermint/basecoin/stack"
|
||||||
|
"github.com/tendermint/basecoin/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockChain is used to simulate a chain for ibc tests.
|
||||||
|
// It is able to produce ibc packets and all verification for
|
||||||
|
// them, but cannot respond to any responses.
|
||||||
|
type MockChain struct {
|
||||||
|
keys certifiers.ValKeys
|
||||||
|
chainID string
|
||||||
|
tree *iavl.IAVLTree
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockChain initializes a teststore and test validators
|
||||||
|
func NewMockChain(chainID string, numKeys int) MockChain {
|
||||||
|
return MockChain{
|
||||||
|
keys: certifiers.GenValKeys(numKeys),
|
||||||
|
chainID: chainID,
|
||||||
|
tree: iavl.NewIAVLTree(0, nil),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRegistrationTx returns a valid tx to register this chain
|
||||||
|
func (m MockChain) GetRegistrationTx(h int) RegisterChainTx {
|
||||||
|
seed := genEmptySeed(m.keys, m.chainID, h, m.tree.Hash(), len(m.keys))
|
||||||
|
return RegisterChainTx{seed}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakePostPacket commits the packet locally and returns the proof,
|
||||||
|
// in the form of two packets to update the header and prove this packet.
|
||||||
|
func (m MockChain) MakePostPacket(packet Packet, h int) (
|
||||||
|
PostPacketTx, UpdateChainTx) {
|
||||||
|
|
||||||
|
post := makePostPacket(m.tree, packet, m.chainID, h)
|
||||||
|
seed := genEmptySeed(m.keys, m.chainID, h, m.tree.Hash(), len(m.keys))
|
||||||
|
update := UpdateChainTx{seed}
|
||||||
|
|
||||||
|
return post, update
|
||||||
|
}
|
||||||
|
|
||||||
|
func genEmptySeed(keys certifiers.ValKeys, chain string, h int,
|
||||||
|
appHash []byte, count int) certifiers.Seed {
|
||||||
|
|
||||||
|
vals := keys.ToValidators(10, 0)
|
||||||
|
cp := keys.GenCheckpoint(chain, h, nil, vals, appHash, 0, count)
|
||||||
|
return certifiers.Seed{cp, vals}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePostPacket(tree *iavl.IAVLTree, packet Packet, fromID string, fromHeight int) PostPacketTx {
|
||||||
|
key := []byte(fmt.Sprintf("some-long-prefix-%06d", packet.Sequence))
|
||||||
|
tree.Set(key, packet.Bytes())
|
||||||
|
_, proof := tree.ConstructProof(key)
|
||||||
|
if proof == nil {
|
||||||
|
panic("wtf?")
|
||||||
|
}
|
||||||
|
|
||||||
|
return PostPacketTx{
|
||||||
|
FromChainID: fromID,
|
||||||
|
FromChainHeight: uint64(fromHeight),
|
||||||
|
Proof: proof,
|
||||||
|
Key: key,
|
||||||
|
Packet: packet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppChain is ready to handle tx
|
||||||
|
type AppChain struct {
|
||||||
|
chainID string
|
||||||
|
app basecoin.Handler
|
||||||
|
store state.SimpleDB
|
||||||
|
height int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAppChain returns a chain that is ready to respond to tx
|
||||||
|
func NewAppChain(app basecoin.Handler, chainID string) *AppChain {
|
||||||
|
return &AppChain{
|
||||||
|
chainID: chainID,
|
||||||
|
app: app,
|
||||||
|
store: state.NewMemKVStore(),
|
||||||
|
height: 123,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncrementHeight allows us to jump heights, more than the auto-step
|
||||||
|
// of 1. It returns the new height we are at.
|
||||||
|
func (a *AppChain) IncrementHeight(delta int) int {
|
||||||
|
a.height += delta
|
||||||
|
return a.height
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeliverTx runs the tx and commits the new tree, incrementing height
|
||||||
|
// by one.
|
||||||
|
func (a *AppChain) DeliverTx(tx basecoin.Tx, perms ...basecoin.Actor) (basecoin.Result, error) {
|
||||||
|
ctx := stack.MockContext(a.chainID, uint64(a.height)).WithPermissions(perms...)
|
||||||
|
store := a.store.Checkpoint()
|
||||||
|
res, err := a.app.DeliverTx(ctx, store, tx)
|
||||||
|
if err == nil {
|
||||||
|
// commit data on success
|
||||||
|
a.store.Commit(store)
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update is a shortcut to DeliverTx with this. Also one return value
|
||||||
|
// to test inline
|
||||||
|
func (a *AppChain) Update(tx UpdateChainTx) error {
|
||||||
|
_, err := a.DeliverTx(tx.Wrap())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOption sets the option on our app
|
||||||
|
func (a *AppChain) SetOption(mod, key, value string) (string, error) {
|
||||||
|
return a.app.SetOption(log.NewNopLogger(), a.store, mod, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStore is used to get the app-specific sub-store
|
||||||
|
func (a *AppChain) GetStore(app string) state.SimpleDB {
|
||||||
|
return stack.PrefixedStore(app, a.store)
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
package ibc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tendermint/go-wire/data"
|
||||||
|
"github.com/tendermint/light-client/certifiers"
|
||||||
|
merkle "github.com/tendermint/merkleeyes/iavl"
|
||||||
|
|
||||||
|
"github.com/tendermint/basecoin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nolint
|
||||||
|
const (
|
||||||
|
// 0x3? series for ibc
|
||||||
|
ByteRegisterChain = byte(0x30)
|
||||||
|
ByteUpdateChain = byte(0x31)
|
||||||
|
ByteCreatePacket = byte(0x32)
|
||||||
|
BytePostPacket = byte(0x33)
|
||||||
|
|
||||||
|
TypeRegisterChain = NameIBC + "/register"
|
||||||
|
TypeUpdateChain = NameIBC + "/update"
|
||||||
|
TypeCreatePacket = NameIBC + "/create"
|
||||||
|
TypePostPacket = NameIBC + "/post"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
basecoin.TxMapper.
|
||||||
|
RegisterImplementation(RegisterChainTx{}, TypeRegisterChain, ByteRegisterChain).
|
||||||
|
RegisterImplementation(UpdateChainTx{}, TypeUpdateChain, ByteUpdateChain).
|
||||||
|
RegisterImplementation(CreatePacketTx{}, TypeCreatePacket, ByteCreatePacket).
|
||||||
|
RegisterImplementation(PostPacketTx{}, TypePostPacket, BytePostPacket)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterChainTx allows you to register a new chain on this blockchain
|
||||||
|
type RegisterChainTx struct {
|
||||||
|
Seed certifiers.Seed `json:"seed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainID helps get the chain this tx refers to
|
||||||
|
func (r RegisterChainTx) ChainID() string {
|
||||||
|
return r.Seed.Header.ChainID
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateBasic makes sure this is consistent, without checking the sigs
|
||||||
|
func (r RegisterChainTx) ValidateBasic() error {
|
||||||
|
err := r.Seed.ValidateBasic(r.ChainID())
|
||||||
|
if err != nil {
|
||||||
|
err = ErrInvalidCommit(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap - used to satisfy TxInner
|
||||||
|
func (r RegisterChainTx) Wrap() basecoin.Tx {
|
||||||
|
return basecoin.Tx{r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateChainTx updates the state of this chain
|
||||||
|
type UpdateChainTx struct {
|
||||||
|
Seed certifiers.Seed `json:"seed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainID helps get the chain this tx refers to
|
||||||
|
func (u UpdateChainTx) ChainID() string {
|
||||||
|
return u.Seed.Header.ChainID
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateBasic makes sure this is consistent, without checking the sigs
|
||||||
|
func (u UpdateChainTx) ValidateBasic() error {
|
||||||
|
err := u.Seed.ValidateBasic(u.ChainID())
|
||||||
|
if err != nil {
|
||||||
|
err = ErrInvalidCommit(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap - used to satisfy TxInner
|
||||||
|
func (u UpdateChainTx) Wrap() basecoin.Tx {
|
||||||
|
return basecoin.Tx{u}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePacketTx is meant to be called by IPC, another module...
|
||||||
|
//
|
||||||
|
// this is the tx that will be sent to another app and the permissions it
|
||||||
|
// comes with (which must be a subset of the permissions on the current tx)
|
||||||
|
//
|
||||||
|
// If must have the special `AllowIBC` permission from the app
|
||||||
|
// that can send this packet (so only coins can request SendTx packet)
|
||||||
|
type CreatePacketTx struct {
|
||||||
|
DestChain string `json:"dest_chain"`
|
||||||
|
Permissions basecoin.Actors `json:"permissions"`
|
||||||
|
Tx basecoin.Tx `json:"tx"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateBasic makes sure this is consistent - used to satisfy TxInner
|
||||||
|
func (p CreatePacketTx) ValidateBasic() error {
|
||||||
|
if p.DestChain == "" {
|
||||||
|
return ErrWrongDestChain(p.DestChain)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap - used to satisfy TxInner
|
||||||
|
func (p CreatePacketTx) Wrap() basecoin.Tx {
|
||||||
|
return basecoin.Tx{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostPacketTx takes a wrapped packet from another chain and
|
||||||
|
// TODO!!!
|
||||||
|
// also think... which chains can relay packets???
|
||||||
|
// right now, enforce that these packets are only sent directly,
|
||||||
|
// not routed over the hub. add routing later.
|
||||||
|
type PostPacketTx struct {
|
||||||
|
// The immediate source of the packet, not always Packet.SrcChainID
|
||||||
|
FromChainID string `json:"src_chain"`
|
||||||
|
// The block height in which Packet was committed, to check Proof
|
||||||
|
FromChainHeight uint64 `json:"src_height"`
|
||||||
|
// this proof must match the header and the packet.Bytes()
|
||||||
|
Proof *merkle.IAVLProof `json:"proof"`
|
||||||
|
Key data.Bytes `json:"key"`
|
||||||
|
Packet Packet `json:"packet"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateBasic makes sure this is consistent - used to satisfy TxInner
|
||||||
|
func (p PostPacketTx) ValidateBasic() error {
|
||||||
|
// TODO
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap - used to satisfy TxInner
|
||||||
|
func (p PostPacketTx) Wrap() basecoin.Tx {
|
||||||
|
return basecoin.Tx{p}
|
||||||
|
}
|
|
@ -1,10 +1,9 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
lcmd "github.com/tendermint/basecoin/client/commands"
|
"github.com/tendermint/basecoin/client/commands"
|
||||||
proofcmd "github.com/tendermint/basecoin/client/commands/proofs"
|
proofcmd "github.com/tendermint/basecoin/client/commands/proofs"
|
||||||
"github.com/tendermint/basecoin/modules/roles"
|
"github.com/tendermint/basecoin/modules/roles"
|
||||||
"github.com/tendermint/basecoin/stack"
|
"github.com/tendermint/basecoin/stack"
|
||||||
|
@ -14,17 +13,15 @@ import (
|
||||||
var RoleQueryCmd = &cobra.Command{
|
var RoleQueryCmd = &cobra.Command{
|
||||||
Use: "role [name]",
|
Use: "role [name]",
|
||||||
Short: "Get details of a role, with proof",
|
Short: "Get details of a role, with proof",
|
||||||
RunE: lcmd.RequireInit(roleQueryCmd),
|
RunE: commands.RequireInit(roleQueryCmd),
|
||||||
}
|
}
|
||||||
|
|
||||||
func roleQueryCmd(cmd *cobra.Command, args []string) error {
|
func roleQueryCmd(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) == 0 {
|
arg, err := commands.GetOneArg(args, "name")
|
||||||
return errors.New("Missing required argument [name]")
|
if err != nil {
|
||||||
} else if len(args) > 1 {
|
return err
|
||||||
return errors.New("Command only supports one name")
|
|
||||||
}
|
}
|
||||||
|
role, err := parseRole(arg)
|
||||||
role, err := parseRole(args[0])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"github.com/tendermint/basecoin/modules/roles"
|
"github.com/tendermint/basecoin/modules/roles"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateRoleTxCmd is CLI command to send tokens between basecoin accounts
|
// CreateRoleTxCmd is CLI command to create a new role
|
||||||
var CreateRoleTxCmd = &cobra.Command{
|
var CreateRoleTxCmd = &cobra.Command{
|
||||||
Use: "create-role",
|
Use: "create-role",
|
||||||
Short: "Create a new role",
|
Short: "Create a new role",
|
||||||
|
@ -32,7 +32,7 @@ func init() {
|
||||||
flags.Int(FlagMinSigs, 0, "Minimum number of signatures needed to assume this role")
|
flags.Int(FlagMinSigs, 0, "Minimum number of signatures needed to assume this role")
|
||||||
}
|
}
|
||||||
|
|
||||||
// createRoleTxCmd is an example of how to make a tx
|
// createRoleTxCmd creates a basic role tx and then wraps, signs, and posts it
|
||||||
func createRoleTxCmd(cmd *cobra.Command, args []string) error {
|
func createRoleTxCmd(cmd *cobra.Command, args []string) error {
|
||||||
tx, err := readCreateRoleTxFlags()
|
tx, err := readCreateRoleTxFlags()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,600 +0,0 @@
|
||||||
package ibc
|
|
||||||
|
|
||||||
// import (
|
|
||||||
// "bytes"
|
|
||||||
// "encoding/json"
|
|
||||||
// "errors"
|
|
||||||
// "fmt"
|
|
||||||
// "net/url"
|
|
||||||
// "strconv"
|
|
||||||
// "strings"
|
|
||||||
|
|
||||||
// abci "github.com/tendermint/abci/types"
|
|
||||||
// "github.com/tendermint/go-wire"
|
|
||||||
// merkle "github.com/tendermint/merkleeyes/iavl"
|
|
||||||
// cmn "github.com/tendermint/tmlibs/common"
|
|
||||||
|
|
||||||
// "github.com/tendermint/basecoin/types"
|
|
||||||
// tm "github.com/tendermint/tendermint/types"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// const (
|
|
||||||
// // Key parts
|
|
||||||
// _IBC = "ibc"
|
|
||||||
// _BLOCKCHAIN = "blockchain"
|
|
||||||
// _GENESIS = "genesis"
|
|
||||||
// _STATE = "state"
|
|
||||||
// _HEADER = "header"
|
|
||||||
// _EGRESS = "egress"
|
|
||||||
// _INGRESS = "ingress"
|
|
||||||
// _CONNECTION = "connection"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// type IBCPluginState struct {
|
|
||||||
// // @[:ibc, :blockchain, :genesis, ChainID] <~ BlockchainGenesis
|
|
||||||
// // @[:ibc, :blockchain, :state, ChainID] <~ BlockchainState
|
|
||||||
// // @[:ibc, :blockchain, :header, ChainID, Height] <~ tm.Header
|
|
||||||
// // @[:ibc, :egress, Src, Dst, Sequence] <~ Packet
|
|
||||||
// // @[:ibc, :ingress, Dst, Src, Sequence] <~ Packet
|
|
||||||
// // @[:ibc, :connection, Src, Dst] <~ Connection # TODO - keep connection state
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type BlockchainGenesis struct {
|
|
||||||
// ChainID string
|
|
||||||
// Genesis string
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type BlockchainState struct {
|
|
||||||
// ChainID string
|
|
||||||
// Validators []*tm.Validator
|
|
||||||
// LastBlockHash []byte
|
|
||||||
// LastBlockHeight uint64
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type Packet struct {
|
|
||||||
// SrcChainID string
|
|
||||||
// DstChainID string
|
|
||||||
// Sequence uint64
|
|
||||||
// Type string // redundant now that Type() is a method on Payload ?
|
|
||||||
// Payload Payload
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func NewPacket(src, dst string, seq uint64, payload Payload) Packet {
|
|
||||||
// return Packet{
|
|
||||||
// SrcChainID: src,
|
|
||||||
// DstChainID: dst,
|
|
||||||
// Sequence: seq,
|
|
||||||
// Type: payload.Type(),
|
|
||||||
// Payload: payload,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // GetSequenceNumber gets the sequence number for packets being sent from the src chain to the dst chain.
|
|
||||||
// // The sequence number counts how many packets have been sent.
|
|
||||||
// // The next packet must include the latest sequence number.
|
|
||||||
// func GetSequenceNumber(store state.SimpleDB, src, dst string) uint64 {
|
|
||||||
// sequenceKey := toKey(_IBC, _EGRESS, src, dst)
|
|
||||||
// seqBytes := store.Get(sequenceKey)
|
|
||||||
// if seqBytes == nil {
|
|
||||||
// return 0
|
|
||||||
// }
|
|
||||||
// seq, err := strconv.ParseUint(string(seqBytes), 10, 64)
|
|
||||||
// if err != nil {
|
|
||||||
// cmn.PanicSanity(err.Error())
|
|
||||||
// }
|
|
||||||
// return seq
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // SetSequenceNumber sets the sequence number for packets being sent from the src chain to the dst chain
|
|
||||||
// func SetSequenceNumber(store state.SimpleDB, src, dst string, seq uint64) {
|
|
||||||
// sequenceKey := toKey(_IBC, _EGRESS, src, dst)
|
|
||||||
// store.Set(sequenceKey, []byte(strconv.FormatUint(seq, 10)))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // SaveNewIBCPacket creates an IBC packet with the given payload from the src chain to the dst chain
|
|
||||||
// // using the correct sequence number. It also increments the sequence number by 1
|
|
||||||
// func SaveNewIBCPacket(state state.SimpleDB, src, dst string, payload Payload) {
|
|
||||||
// // fetch sequence number and increment by 1
|
|
||||||
// seq := GetSequenceNumber(state, src, dst)
|
|
||||||
// SetSequenceNumber(state, src, dst, seq+1)
|
|
||||||
|
|
||||||
// // save ibc packet
|
|
||||||
// packetKey := toKey(_IBC, _EGRESS, src, dst, cmn.Fmt("%v", seq))
|
|
||||||
// packet := NewPacket(src, dst, uint64(seq), payload)
|
|
||||||
// save(state, packetKey, packet)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func GetIBCPacket(state state.SimpleDB, src, dst string, seq uint64) (Packet, error) {
|
|
||||||
// packetKey := toKey(_IBC, _EGRESS, src, dst, cmn.Fmt("%v", seq))
|
|
||||||
// packetBytes := state.Get(packetKey)
|
|
||||||
|
|
||||||
// var packet Packet
|
|
||||||
// err := wire.ReadBinaryBytes(packetBytes, &packet)
|
|
||||||
// return packet, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// const (
|
|
||||||
// PayloadTypeBytes = byte(0x01)
|
|
||||||
// PayloadTypeCoins = byte(0x02)
|
|
||||||
// )
|
|
||||||
|
|
||||||
// var _ = wire.RegisterInterface(
|
|
||||||
// struct{ Payload }{},
|
|
||||||
// wire.ConcreteType{DataPayload{}, PayloadTypeBytes},
|
|
||||||
// wire.ConcreteType{CoinsPayload{}, PayloadTypeCoins},
|
|
||||||
// )
|
|
||||||
|
|
||||||
// type Payload interface {
|
|
||||||
// AssertIsPayload()
|
|
||||||
// Type() string
|
|
||||||
// ValidateBasic() abci.Result
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (DataPayload) AssertIsPayload() {}
|
|
||||||
// func (CoinsPayload) AssertIsPayload() {}
|
|
||||||
|
|
||||||
// type DataPayload []byte
|
|
||||||
|
|
||||||
// func (p DataPayload) Type() string {
|
|
||||||
// return "data"
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (p DataPayload) ValidateBasic() abci.Result {
|
|
||||||
// return abci.OK
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type CoinsPayload struct {
|
|
||||||
// Address []byte
|
|
||||||
// Coins coin.Coins
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (p CoinsPayload) Type() string {
|
|
||||||
// return "coin"
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (p CoinsPayload) ValidateBasic() abci.Result {
|
|
||||||
// // TODO: validate
|
|
||||||
// return abci.OK
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// const (
|
|
||||||
// IBCTxTypeRegisterChain = byte(0x01)
|
|
||||||
// IBCTxTypeUpdateChain = byte(0x02)
|
|
||||||
// IBCTxTypePacketCreate = byte(0x03)
|
|
||||||
// IBCTxTypePacketPost = byte(0x04)
|
|
||||||
|
|
||||||
// IBCCodeEncodingError = abci.CodeType(1001)
|
|
||||||
// IBCCodeChainAlreadyExists = abci.CodeType(1002)
|
|
||||||
// IBCCodePacketAlreadyExists = abci.CodeType(1003)
|
|
||||||
// IBCCodeUnknownHeight = abci.CodeType(1004)
|
|
||||||
// IBCCodeInvalidCommit = abci.CodeType(1005)
|
|
||||||
// IBCCodeInvalidProof = abci.CodeType(1006)
|
|
||||||
// )
|
|
||||||
|
|
||||||
// var _ = wire.RegisterInterface(
|
|
||||||
// struct{ IBCTx }{},
|
|
||||||
// wire.ConcreteType{IBCRegisterChainTx{}, IBCTxTypeRegisterChain},
|
|
||||||
// wire.ConcreteType{IBCUpdateChainTx{}, IBCTxTypeUpdateChain},
|
|
||||||
// wire.ConcreteType{IBCPacketCreateTx{}, IBCTxTypePacketCreate},
|
|
||||||
// wire.ConcreteType{IBCPacketPostTx{}, IBCTxTypePacketPost},
|
|
||||||
// )
|
|
||||||
|
|
||||||
// type IBCTx interface {
|
|
||||||
// AssertIsIBCTx()
|
|
||||||
// ValidateBasic() abci.Result
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (IBCRegisterChainTx) AssertIsIBCTx() {}
|
|
||||||
// func (IBCUpdateChainTx) AssertIsIBCTx() {}
|
|
||||||
// func (IBCPacketCreateTx) AssertIsIBCTx() {}
|
|
||||||
// func (IBCPacketPostTx) AssertIsIBCTx() {}
|
|
||||||
|
|
||||||
// type IBCRegisterChainTx struct {
|
|
||||||
// BlockchainGenesis
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (IBCRegisterChainTx) ValidateBasic() (res abci.Result) {
|
|
||||||
// // TODO - validate
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type IBCUpdateChainTx struct {
|
|
||||||
// Header tm.Header
|
|
||||||
// Commit tm.Commit
|
|
||||||
// // TODO: NextValidators
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (IBCUpdateChainTx) ValidateBasic() (res abci.Result) {
|
|
||||||
// // TODO - validate
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type IBCPacketCreateTx struct {
|
|
||||||
// Packet
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (IBCPacketCreateTx) ValidateBasic() (res abci.Result) {
|
|
||||||
// // TODO - validate
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type IBCPacketPostTx struct {
|
|
||||||
// FromChainID string // The immediate source of the packet, not always Packet.SrcChainID
|
|
||||||
// FromChainHeight uint64 // The block height in which Packet was committed, to check Proof
|
|
||||||
// Packet
|
|
||||||
// Proof *merkle.IAVLProof
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (IBCPacketPostTx) ValidateBasic() (res abci.Result) {
|
|
||||||
// // TODO - validate
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// type IBCPlugin struct {
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (ibc *IBCPlugin) Name() string {
|
|
||||||
// return "IBC"
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (ibc *IBCPlugin) StateKey() []byte {
|
|
||||||
// return []byte("IBCPlugin.State")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func New() *IBCPlugin {
|
|
||||||
// return &IBCPlugin{}
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (ibc *IBCPlugin) SetOption(store state.SimpleDB, key string, value string) (log string) {
|
|
||||||
// return ""
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (ibc *IBCPlugin) RunTx(store state.SimpleDB, ctx types.CallContext, txBytes []byte) (res abci.Result) {
|
|
||||||
// // Decode tx
|
|
||||||
// var tx IBCTx
|
|
||||||
// err := wire.ReadBinaryBytes(txBytes, &tx)
|
|
||||||
// if err != nil {
|
|
||||||
// return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Validate tx
|
|
||||||
// res = tx.ValidateBasic()
|
|
||||||
// if res.IsErr() {
|
|
||||||
// return res.PrependLog("ValidateBasic Failed: ")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // TODO - Check whether sufficient funds
|
|
||||||
|
|
||||||
// defer func() {
|
|
||||||
// // TODO - Refund any remaining funds left over
|
|
||||||
// // e.g. !ctx.Coins.Minus(tx.Fee).IsZero()
|
|
||||||
// // ctx.CallerAccount is synced w/ store, so just modify that and store it.
|
|
||||||
// // NOTE: We should use the CallContext to store fund/refund information.
|
|
||||||
// }()
|
|
||||||
|
|
||||||
// sm := &IBCStateMachine{store, ctx, abci.OK}
|
|
||||||
|
|
||||||
// switch tx := tx.(type) {
|
|
||||||
// case IBCRegisterChainTx:
|
|
||||||
// sm.runRegisterChainTx(tx)
|
|
||||||
// case IBCUpdateChainTx:
|
|
||||||
// sm.runUpdateChainTx(tx)
|
|
||||||
// case IBCPacketCreateTx:
|
|
||||||
// sm.runPacketCreateTx(tx)
|
|
||||||
// case IBCPacketPostTx:
|
|
||||||
// sm.runPacketPostTx(tx)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return sm.res
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type IBCStateMachine struct {
|
|
||||||
// store state.SimpleDB
|
|
||||||
// ctx types.CallContext
|
|
||||||
// res abci.Result
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (sm *IBCStateMachine) runRegisterChainTx(tx IBCRegisterChainTx) {
|
|
||||||
// chainGenKey := toKey(_IBC, _BLOCKCHAIN, _GENESIS, tx.ChainID)
|
|
||||||
// chainStateKey := toKey(_IBC, _BLOCKCHAIN, _STATE, tx.ChainID)
|
|
||||||
// chainGen := tx.BlockchainGenesis
|
|
||||||
|
|
||||||
// // Parse genesis
|
|
||||||
// chainGenDoc := new(tm.GenesisDoc)
|
|
||||||
// err := json.Unmarshal([]byte(chainGen.Genesis), chainGenDoc)
|
|
||||||
// if err != nil {
|
|
||||||
// sm.res.Code = IBCCodeEncodingError
|
|
||||||
// sm.res.Log = "Genesis doc couldn't be parsed: " + err.Error()
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Make sure chainGen doesn't already exist
|
|
||||||
// if exists(sm.store, chainGenKey) {
|
|
||||||
// sm.res.Code = IBCCodeChainAlreadyExists
|
|
||||||
// sm.res.Log = "Already exists"
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Save new BlockchainGenesis
|
|
||||||
// save(sm.store, chainGenKey, chainGen)
|
|
||||||
|
|
||||||
// // Create new BlockchainState
|
|
||||||
// chainState := BlockchainState{
|
|
||||||
// ChainID: chainGenDoc.ChainID,
|
|
||||||
// Validators: make([]*tm.Validator, len(chainGenDoc.Validators)),
|
|
||||||
// LastBlockHash: nil,
|
|
||||||
// LastBlockHeight: 0,
|
|
||||||
// }
|
|
||||||
// // Make validators slice
|
|
||||||
// for i, val := range chainGenDoc.Validators {
|
|
||||||
// pubKey := val.PubKey
|
|
||||||
// address := pubKey.Address()
|
|
||||||
// chainState.Validators[i] = &tm.Validator{
|
|
||||||
// Address: address,
|
|
||||||
// PubKey: pubKey,
|
|
||||||
// VotingPower: val.Amount,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Save new BlockchainState
|
|
||||||
// save(sm.store, chainStateKey, chainState)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (sm *IBCStateMachine) runUpdateChainTx(tx IBCUpdateChainTx) {
|
|
||||||
// chainID := tx.Header.ChainID
|
|
||||||
// chainStateKey := toKey(_IBC, _BLOCKCHAIN, _STATE, chainID)
|
|
||||||
|
|
||||||
// // Make sure chainState exists
|
|
||||||
// if !exists(sm.store, chainStateKey) {
|
|
||||||
// return // Chain does not exist, do nothing
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Load latest chainState
|
|
||||||
// var chainState BlockchainState
|
|
||||||
// exists, err := load(sm.store, chainStateKey, &chainState)
|
|
||||||
// if err != nil {
|
|
||||||
// sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Loading ChainState: %v", err.Error()))
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// if !exists {
|
|
||||||
// sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Missing ChainState"))
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Check commit against last known state & validators
|
|
||||||
// err = verifyCommit(chainState, &tx.Header, &tx.Commit)
|
|
||||||
// if err != nil {
|
|
||||||
// sm.res.Code = IBCCodeInvalidCommit
|
|
||||||
// sm.res.Log = cmn.Fmt("Invalid Commit: %v", err.Error())
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Store header
|
|
||||||
// headerKey := toKey(_IBC, _BLOCKCHAIN, _HEADER, chainID, cmn.Fmt("%v", tx.Header.Height))
|
|
||||||
// save(sm.store, headerKey, tx.Header)
|
|
||||||
|
|
||||||
// // Update chainState
|
|
||||||
// chainState.LastBlockHash = tx.Header.Hash()
|
|
||||||
// chainState.LastBlockHeight = uint64(tx.Header.Height)
|
|
||||||
|
|
||||||
// // Store chainState
|
|
||||||
// save(sm.store, chainStateKey, chainState)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (sm *IBCStateMachine) runPacketCreateTx(tx IBCPacketCreateTx) {
|
|
||||||
// packet := tx.Packet
|
|
||||||
// packetKey := toKey(_IBC, _EGRESS,
|
|
||||||
// packet.SrcChainID,
|
|
||||||
// packet.DstChainID,
|
|
||||||
// cmn.Fmt("%v", packet.Sequence),
|
|
||||||
// )
|
|
||||||
// // Make sure packet doesn't already exist
|
|
||||||
// if exists(sm.store, packetKey) {
|
|
||||||
// sm.res.Code = IBCCodePacketAlreadyExists
|
|
||||||
// // TODO: .AppendLog() does not update sm.res
|
|
||||||
// sm.res.Log = "Already exists"
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Execute the payload
|
|
||||||
// switch payload := tx.Packet.Payload.(type) {
|
|
||||||
// case DataPayload:
|
|
||||||
// // do nothing
|
|
||||||
// case CoinsPayload:
|
|
||||||
// // ensure enough coins were sent in tx to cover the payload coins
|
|
||||||
// if !sm.ctx.Coins.IsGTE(payload.Coins) {
|
|
||||||
// sm.res.Code = abci.CodeType_InsufficientFunds
|
|
||||||
// sm.res.Log = fmt.Sprintf("Not enough funds sent in tx (%v) to send %v via IBC", sm.ctx.Coins, payload.Coins)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // deduct coins from context
|
|
||||||
// sm.ctx.Coins = sm.ctx.Coins.Minus(payload.Coins)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Save new Packet
|
|
||||||
// save(sm.store, packetKey, packet)
|
|
||||||
|
|
||||||
// // set the sequence number
|
|
||||||
// SetSequenceNumber(sm.store, packet.SrcChainID, packet.DstChainID, packet.Sequence)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) {
|
|
||||||
// packet := tx.Packet
|
|
||||||
// packetKeyEgress := toKey(_IBC, _EGRESS,
|
|
||||||
// packet.SrcChainID,
|
|
||||||
// packet.DstChainID,
|
|
||||||
// cmn.Fmt("%v", packet.Sequence),
|
|
||||||
// )
|
|
||||||
// packetKeyIngress := toKey(_IBC, _INGRESS,
|
|
||||||
// packet.DstChainID,
|
|
||||||
// packet.SrcChainID,
|
|
||||||
// cmn.Fmt("%v", packet.Sequence),
|
|
||||||
// )
|
|
||||||
// headerKey := toKey(_IBC, _BLOCKCHAIN, _HEADER,
|
|
||||||
// tx.FromChainID,
|
|
||||||
// cmn.Fmt("%v", tx.FromChainHeight),
|
|
||||||
// )
|
|
||||||
|
|
||||||
// // Make sure packet doesn't already exist
|
|
||||||
// if exists(sm.store, packetKeyIngress) {
|
|
||||||
// sm.res.Code = IBCCodePacketAlreadyExists
|
|
||||||
// sm.res.Log = "Already exists"
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Save new Packet (just for fun)
|
|
||||||
// save(sm.store, packetKeyIngress, packet)
|
|
||||||
|
|
||||||
// // Load Header and make sure it exists
|
|
||||||
// // If it exists, we already checked a valid commit for it in UpdateChainTx
|
|
||||||
// var header tm.Header
|
|
||||||
// exists, err := load(sm.store, headerKey, &header)
|
|
||||||
// if err != nil {
|
|
||||||
// sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Loading Header: %v", err.Error()))
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// if !exists {
|
|
||||||
// sm.res.Code = IBCCodeUnknownHeight
|
|
||||||
// sm.res.Log = cmn.Fmt("Loading Header: Unknown height")
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// proof := tx.Proof
|
|
||||||
// if proof == nil {
|
|
||||||
// sm.res.Code = IBCCodeInvalidProof
|
|
||||||
// sm.res.Log = "Proof is nil"
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// packetBytes := wire.BinaryBytes(packet)
|
|
||||||
|
|
||||||
// // Make sure packet's proof matches given (packet, key, blockhash)
|
|
||||||
// ok := proof.Verify(packetKeyEgress, packetBytes, header.AppHash)
|
|
||||||
// if !ok {
|
|
||||||
// sm.res.Code = IBCCodeInvalidProof
|
|
||||||
// sm.res.Log = fmt.Sprintf("Proof is invalid. key: %s; packetByes %X; header %v; proof %v", packetKeyEgress, packetBytes, header, proof)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Execute payload
|
|
||||||
// switch payload := packet.Payload.(type) {
|
|
||||||
// case DataPayload:
|
|
||||||
// // do nothing
|
|
||||||
// case CoinsPayload:
|
|
||||||
// // Add coins to destination account
|
|
||||||
// acc := types.GetAccount(sm.store, payload.Address)
|
|
||||||
// if acc == nil {
|
|
||||||
// acc = &types.Account{}
|
|
||||||
// }
|
|
||||||
// acc.Balance = acc.Balance.Plus(payload.Coins)
|
|
||||||
// types.SetAccount(sm.store, payload.Address, acc)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (ibc *IBCPlugin) InitChain(store state.SimpleDB, vals []*abci.Validator) {
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (cp *IBCPlugin) BeginBlock(store state.SimpleDB, hash []byte, header *abci.Header) {
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (cp *IBCPlugin) EndBlock(store state.SimpleDB, height uint64) (res abci.ResponseEndBlock) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //--------------------------------------------------------------------------------
|
|
||||||
// // TODO: move to utils
|
|
||||||
|
|
||||||
// // Returns true if exists, false if nil.
|
|
||||||
// func exists(store state.SimpleDB, key []byte) (exists bool) {
|
|
||||||
// value := store.Get(key)
|
|
||||||
// return len(value) > 0
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Load bytes from store by reading value for key and read into ptr.
|
|
||||||
// // Returns true if exists, false if nil.
|
|
||||||
// // Returns err if decoding error.
|
|
||||||
// func load(store state.SimpleDB, key []byte, ptr interface{}) (exists bool, err error) {
|
|
||||||
// value := store.Get(key)
|
|
||||||
// if len(value) > 0 {
|
|
||||||
// err = wire.ReadBinaryBytes(value, ptr)
|
|
||||||
// if err != nil {
|
|
||||||
// return true, errors.New(
|
|
||||||
// cmn.Fmt("Error decoding key 0x%X = 0x%X: %v", key, value, err.Error()),
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// return true, nil
|
|
||||||
// } else {
|
|
||||||
// return false, nil
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Save bytes to store by writing obj's go-wire binary bytes.
|
|
||||||
// func save(store state.SimpleDB, key []byte, obj interface{}) {
|
|
||||||
// store.Set(key, wire.BinaryBytes(obj))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Key parts are URL escaped and joined with ','
|
|
||||||
// func toKey(parts ...string) []byte {
|
|
||||||
// escParts := make([]string, len(parts))
|
|
||||||
// for i, part := range parts {
|
|
||||||
// escParts[i] = url.QueryEscape(part)
|
|
||||||
// }
|
|
||||||
// return []byte(strings.Join(escParts, ","))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // NOTE: Commit's votes include ValidatorAddress, so can be matched up
|
|
||||||
// // against chainState.Validators, even if the validator set had changed.
|
|
||||||
// // For the purpose of the demo, we assume that the validator set hadn't changed,
|
|
||||||
// // though we should check that explicitly.
|
|
||||||
// func verifyCommit(chainState BlockchainState, header *tm.Header, commit *tm.Commit) error {
|
|
||||||
|
|
||||||
// // Ensure that chainState and header ChainID match.
|
|
||||||
// if chainState.ChainID != header.ChainID {
|
|
||||||
// return errors.New(cmn.Fmt("Expected header.ChainID %v, got %v", chainState.ChainID, header.ChainID))
|
|
||||||
// }
|
|
||||||
// // Ensure things aren't empty
|
|
||||||
// if len(chainState.Validators) == 0 {
|
|
||||||
// return errors.New(cmn.Fmt("Blockchain has no validators")) // NOTE: Why would this happen?
|
|
||||||
// }
|
|
||||||
// if len(commit.Precommits) == 0 {
|
|
||||||
// return errors.New(cmn.Fmt("Commit has no signatures"))
|
|
||||||
// }
|
|
||||||
// chainID := chainState.ChainID
|
|
||||||
// vals := chainState.Validators
|
|
||||||
// valSet := tm.NewValidatorSet(vals)
|
|
||||||
|
|
||||||
// var blockID tm.BlockID
|
|
||||||
// for _, pc := range commit.Precommits {
|
|
||||||
// // XXX: incorrect. we want the one for +2/3, not just the first one
|
|
||||||
// if pc != nil {
|
|
||||||
// blockID = pc.BlockID
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if blockID.IsZero() {
|
|
||||||
// return errors.New("All precommits are nil!")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // NOTE: Currently this only works with the exact same validator set.
|
|
||||||
// // Not this, but perhaps "ValidatorSet.VerifyCommitAny" should expose
|
|
||||||
// // the functionality to verify commits even after validator changes.
|
|
||||||
// err := valSet.VerifyCommit(chainID, blockID, header.Height, commit)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Ensure the committed blockID matches the header
|
|
||||||
// if !bytes.Equal(header.Hash(), blockID.Hash) {
|
|
||||||
// return errors.New(cmn.Fmt("blockID.Hash (%X) does not match header.Hash (%X)", blockID.Hash, header.Hash()))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // All ok!
|
|
||||||
// return nil
|
|
||||||
// }
|
|
|
@ -1,512 +0,0 @@
|
||||||
package ibc
|
|
||||||
|
|
||||||
// import (
|
|
||||||
// "bytes"
|
|
||||||
// "encoding/json"
|
|
||||||
// "sort"
|
|
||||||
// "strings"
|
|
||||||
// "testing"
|
|
||||||
|
|
||||||
// "github.com/stretchr/testify/assert"
|
|
||||||
// "github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
// abci "github.com/tendermint/abci/types"
|
|
||||||
// crypto "github.com/tendermint/go-crypto"
|
|
||||||
// "github.com/tendermint/go-wire"
|
|
||||||
// eyes "github.com/tendermint/merkleeyes/client"
|
|
||||||
// "github.com/tendermint/merkleeyes/iavl"
|
|
||||||
// cmn "github.com/tendermint/tmlibs/common"
|
|
||||||
|
|
||||||
// "github.com/tendermint/basecoin/types"
|
|
||||||
// tm "github.com/tendermint/tendermint/types"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// // NOTE: PrivAccounts are sorted by Address,
|
|
||||||
// // GenesisDoc, not necessarily.
|
|
||||||
// func genGenesisDoc(chainID string, numVals int) (*tm.GenesisDoc, []types.PrivAccount) {
|
|
||||||
// var privAccs []types.PrivAccount
|
|
||||||
// genDoc := &tm.GenesisDoc{
|
|
||||||
// ChainID: chainID,
|
|
||||||
// Validators: nil,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// for i := 0; i < numVals; i++ {
|
|
||||||
// name := cmn.Fmt("%v_val_%v", chainID, i)
|
|
||||||
// privAcc := types.PrivAccountFromSecret(name)
|
|
||||||
// genDoc.Validators = append(genDoc.Validators, tm.GenesisValidator{
|
|
||||||
// PubKey: privAcc.PubKey,
|
|
||||||
// Amount: 1,
|
|
||||||
// Name: name,
|
|
||||||
// })
|
|
||||||
// privAccs = append(privAccs, privAcc)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Sort PrivAccounts
|
|
||||||
// sort.Sort(PrivAccountsByAddress(privAccs))
|
|
||||||
|
|
||||||
// return genDoc, privAccs
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //-------------------------------------
|
|
||||||
// // Implements sort for sorting PrivAccount by address.
|
|
||||||
|
|
||||||
// type PrivAccountsByAddress []types.PrivAccount
|
|
||||||
|
|
||||||
// func (pas PrivAccountsByAddress) Len() int {
|
|
||||||
// return len(pas)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (pas PrivAccountsByAddress) Less(i, j int) bool {
|
|
||||||
// return bytes.Compare(pas[i].Account.PubKey.Address(), pas[j].Account.PubKey.Address()) == -1
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (pas PrivAccountsByAddress) Swap(i, j int) {
|
|
||||||
// it := pas[i]
|
|
||||||
// pas[i] = pas[j]
|
|
||||||
// pas[j] = it
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// var testGenesisDoc = `{
|
|
||||||
// "app_hash": "",
|
|
||||||
// "chain_id": "test_chain_1",
|
|
||||||
// "genesis_time": "0001-01-01T00:00:00.000Z",
|
|
||||||
// "validators": [
|
|
||||||
// {
|
|
||||||
// "amount": 10,
|
|
||||||
// "name": "",
|
|
||||||
// "pub_key": {
|
|
||||||
// "type": "ed25519",
|
|
||||||
// "data":"D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// "app_options": {
|
|
||||||
// "accounts": [
|
|
||||||
// {
|
|
||||||
// "pub_key": {
|
|
||||||
// "type": "ed25519",
|
|
||||||
// "data": "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"
|
|
||||||
// },
|
|
||||||
// "coins": [
|
|
||||||
// {
|
|
||||||
// "denom": "mycoin",
|
|
||||||
// "amount": 9007199254740992
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// }`
|
|
||||||
|
|
||||||
// func TestIBCGenesisFromString(t *testing.T) {
|
|
||||||
// eyesClient := eyes.NewLocalClient("", 0)
|
|
||||||
// store := types.NewKVCache(eyesClient)
|
|
||||||
// store.SetLogging() // Log all activity
|
|
||||||
|
|
||||||
// ibcPlugin := New()
|
|
||||||
// ctx := types.NewCallContext(nil, nil, coin.Coins{})
|
|
||||||
|
|
||||||
// registerChain(t, ibcPlugin, store, ctx, "test_chain", testGenesisDoc)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// func TestIBCPluginRegister(t *testing.T) {
|
|
||||||
// require := require.New(t)
|
|
||||||
|
|
||||||
// eyesClient := eyes.NewLocalClient("", 0)
|
|
||||||
// store := types.NewKVCache(eyesClient)
|
|
||||||
// store.SetLogging() // Log all activity
|
|
||||||
|
|
||||||
// ibcPlugin := New()
|
|
||||||
// ctx := types.NewCallContext(nil, nil, coin.Coins{})
|
|
||||||
|
|
||||||
// chainID_1 := "test_chain"
|
|
||||||
// genDoc_1, _ := genGenesisDoc(chainID_1, 4)
|
|
||||||
// genDocJSON_1, err := json.Marshal(genDoc_1)
|
|
||||||
// require.Nil(err)
|
|
||||||
|
|
||||||
// // Register a malformed chain
|
|
||||||
// res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
|
|
||||||
// BlockchainGenesis{
|
|
||||||
// ChainID: "test_chain",
|
|
||||||
// Genesis: "<THIS IS NOT JSON>",
|
|
||||||
// },
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, IBCCodeEncodingError)
|
|
||||||
|
|
||||||
// // Successfully register a chain
|
|
||||||
// registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
|
|
||||||
|
|
||||||
// // Duplicate request fails
|
|
||||||
// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
|
|
||||||
// BlockchainGenesis{
|
|
||||||
// ChainID: "test_chain",
|
|
||||||
// Genesis: string(genDocJSON_1),
|
|
||||||
// },
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, IBCCodeChainAlreadyExists)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestIBCPluginPost(t *testing.T) {
|
|
||||||
// require := require.New(t)
|
|
||||||
|
|
||||||
// eyesClient := eyes.NewLocalClient("", 0)
|
|
||||||
// store := types.NewKVCache(eyesClient)
|
|
||||||
// store.SetLogging() // Log all activity
|
|
||||||
|
|
||||||
// ibcPlugin := New()
|
|
||||||
// ctx := types.NewCallContext(nil, nil, coin.Coins{})
|
|
||||||
|
|
||||||
// chainID_1 := "test_chain"
|
|
||||||
// genDoc_1, _ := genGenesisDoc(chainID_1, 4)
|
|
||||||
// genDocJSON_1, err := json.Marshal(genDoc_1)
|
|
||||||
// require.Nil(err)
|
|
||||||
|
|
||||||
// // Register a chain
|
|
||||||
// registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
|
|
||||||
|
|
||||||
// // Create a new packet (for testing)
|
|
||||||
// packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world")))
|
|
||||||
// res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
|
||||||
// Packet: packet,
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, abci.CodeType_OK)
|
|
||||||
|
|
||||||
// // Post a duplicate packet
|
|
||||||
// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
|
||||||
// Packet: packet,
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, IBCCodePacketAlreadyExists)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestIBCPluginPayloadBytes(t *testing.T) {
|
|
||||||
// assert := assert.New(t)
|
|
||||||
// require := require.New(t)
|
|
||||||
|
|
||||||
// eyesClient := eyes.NewLocalClient("", 0)
|
|
||||||
// store := types.NewKVCache(eyesClient)
|
|
||||||
// store.SetLogging() // Log all activity
|
|
||||||
|
|
||||||
// ibcPlugin := New()
|
|
||||||
// ctx := types.NewCallContext(nil, nil, coin.Coins{})
|
|
||||||
|
|
||||||
// chainID_1 := "test_chain"
|
|
||||||
// genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4)
|
|
||||||
// genDocJSON_1, err := json.Marshal(genDoc_1)
|
|
||||||
// require.Nil(err)
|
|
||||||
|
|
||||||
// // Register a chain
|
|
||||||
// registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
|
|
||||||
|
|
||||||
// // Create a new packet (for testing)
|
|
||||||
// packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world")))
|
|
||||||
// res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
|
||||||
// Packet: packet,
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, abci.CodeType_OK)
|
|
||||||
|
|
||||||
// // Construct a Header that includes the above packet.
|
|
||||||
// store.Sync()
|
|
||||||
// resCommit := eyesClient.CommitSync()
|
|
||||||
// appHash := resCommit.Data
|
|
||||||
// header := newHeader("test_chain", 999, appHash, []byte("must_exist"))
|
|
||||||
|
|
||||||
// // Construct a Commit that signs above header
|
|
||||||
// commit := constructCommit(privAccs_1, header)
|
|
||||||
|
|
||||||
// // Update a chain
|
|
||||||
// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
|
|
||||||
// Header: header,
|
|
||||||
// Commit: commit,
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, abci.CodeType_OK)
|
|
||||||
|
|
||||||
// // Get proof for the packet
|
|
||||||
// packetKey := toKey(_IBC, _EGRESS,
|
|
||||||
// packet.SrcChainID,
|
|
||||||
// packet.DstChainID,
|
|
||||||
// cmn.Fmt("%v", packet.Sequence),
|
|
||||||
// )
|
|
||||||
// resQuery, err := eyesClient.QuerySync(abci.RequestQuery{
|
|
||||||
// Path: "/store",
|
|
||||||
// Data: packetKey,
|
|
||||||
// Prove: true,
|
|
||||||
// })
|
|
||||||
// assert.Nil(err)
|
|
||||||
// var proof *iavl.IAVLProof
|
|
||||||
// err = wire.ReadBinaryBytes(resQuery.Proof, &proof)
|
|
||||||
// assert.Nil(err)
|
|
||||||
|
|
||||||
// // Post a packet
|
|
||||||
// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketPostTx{
|
|
||||||
// FromChainID: "test_chain",
|
|
||||||
// FromChainHeight: 999,
|
|
||||||
// Packet: packet,
|
|
||||||
// Proof: proof,
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, abci.CodeType_OK)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestIBCPluginPayloadCoins(t *testing.T) {
|
|
||||||
// assert := assert.New(t)
|
|
||||||
// require := require.New(t)
|
|
||||||
|
|
||||||
// eyesClient := eyes.NewLocalClient("", 0)
|
|
||||||
// store := types.NewKVCache(eyesClient)
|
|
||||||
// store.SetLogging() // Log all activity
|
|
||||||
|
|
||||||
// ibcPlugin := New()
|
|
||||||
// coins := coin.Coins{
|
|
||||||
// coin.Coin{
|
|
||||||
// Denom: "mycoin",
|
|
||||||
// Amount: 100,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// ctx := types.NewCallContext(nil, nil, coins)
|
|
||||||
|
|
||||||
// chainID_1 := "test_chain"
|
|
||||||
// genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4)
|
|
||||||
// genDocJSON_1, err := json.Marshal(genDoc_1)
|
|
||||||
// require.Nil(err)
|
|
||||||
|
|
||||||
// // Register a chain
|
|
||||||
// registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
|
|
||||||
|
|
||||||
// // send coins to this addr on the other chain
|
|
||||||
// destinationAddr := []byte("some address")
|
|
||||||
// coinsBad := coin.Coins{coin.Coin{"mycoin", 200}}
|
|
||||||
// coinsGood := coin.Coins{coin.Coin{"mycoin", 1}}
|
|
||||||
|
|
||||||
// // Try to send too many coins
|
|
||||||
// packet := NewPacket("test_chain", "dst_chain", 0, CoinsPayload{
|
|
||||||
// Address: destinationAddr,
|
|
||||||
// Coins: coinsBad,
|
|
||||||
// })
|
|
||||||
// res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
|
||||||
// Packet: packet,
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, abci.CodeType_InsufficientFunds)
|
|
||||||
|
|
||||||
// // Send a small enough number of coins
|
|
||||||
// packet = NewPacket("test_chain", "dst_chain", 0, CoinsPayload{
|
|
||||||
// Address: destinationAddr,
|
|
||||||
// Coins: coinsGood,
|
|
||||||
// })
|
|
||||||
// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
|
||||||
// Packet: packet,
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, abci.CodeType_OK)
|
|
||||||
|
|
||||||
// // Construct a Header that includes the above packet.
|
|
||||||
// store.Sync()
|
|
||||||
// resCommit := eyesClient.CommitSync()
|
|
||||||
// appHash := resCommit.Data
|
|
||||||
// header := newHeader("test_chain", 999, appHash, []byte("must_exist"))
|
|
||||||
|
|
||||||
// // Construct a Commit that signs above header
|
|
||||||
// commit := constructCommit(privAccs_1, header)
|
|
||||||
|
|
||||||
// // Update a chain
|
|
||||||
// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
|
|
||||||
// Header: header,
|
|
||||||
// Commit: commit,
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, abci.CodeType_OK)
|
|
||||||
|
|
||||||
// // Get proof for the packet
|
|
||||||
// packetKey := toKey(_IBC, _EGRESS,
|
|
||||||
// packet.SrcChainID,
|
|
||||||
// packet.DstChainID,
|
|
||||||
// cmn.Fmt("%v", packet.Sequence),
|
|
||||||
// )
|
|
||||||
// resQuery, err := eyesClient.QuerySync(abci.RequestQuery{
|
|
||||||
// Path: "/store",
|
|
||||||
// Data: packetKey,
|
|
||||||
// Prove: true,
|
|
||||||
// })
|
|
||||||
// assert.Nil(err)
|
|
||||||
// var proof *iavl.IAVLProof
|
|
||||||
// err = wire.ReadBinaryBytes(resQuery.Proof, &proof)
|
|
||||||
// assert.Nil(err)
|
|
||||||
|
|
||||||
// // Account should be empty before the tx
|
|
||||||
// acc := types.GetAccount(store, destinationAddr)
|
|
||||||
// assert.Nil(acc)
|
|
||||||
|
|
||||||
// // Post a packet
|
|
||||||
// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketPostTx{
|
|
||||||
// FromChainID: "test_chain",
|
|
||||||
// FromChainHeight: 999,
|
|
||||||
// Packet: packet,
|
|
||||||
// Proof: proof,
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, abci.CodeType_OK)
|
|
||||||
|
|
||||||
// // Account should now have some coins
|
|
||||||
// acc = types.GetAccount(store, destinationAddr)
|
|
||||||
// assert.Equal(acc.Balance, coinsGood)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestIBCPluginBadCommit(t *testing.T) {
|
|
||||||
// require := require.New(t)
|
|
||||||
|
|
||||||
// eyesClient := eyes.NewLocalClient("", 0)
|
|
||||||
// store := types.NewKVCache(eyesClient)
|
|
||||||
// store.SetLogging() // Log all activity
|
|
||||||
|
|
||||||
// ibcPlugin := New()
|
|
||||||
// ctx := types.NewCallContext(nil, nil, coin.Coins{})
|
|
||||||
|
|
||||||
// chainID_1 := "test_chain"
|
|
||||||
// genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4)
|
|
||||||
// genDocJSON_1, err := json.Marshal(genDoc_1)
|
|
||||||
// require.Nil(err)
|
|
||||||
|
|
||||||
// // Successfully register a chain
|
|
||||||
// registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
|
|
||||||
|
|
||||||
// // Construct a Header
|
|
||||||
// header := newHeader("test_chain", 999, nil, []byte("must_exist"))
|
|
||||||
|
|
||||||
// // Construct a Commit that signs above header
|
|
||||||
// commit := constructCommit(privAccs_1, header)
|
|
||||||
|
|
||||||
// // Update a chain with a broken commit
|
|
||||||
// // Modify the first byte of the first signature
|
|
||||||
// sig := commit.Precommits[0].Signature.Unwrap().(crypto.SignatureEd25519)
|
|
||||||
// sig[0] += 1
|
|
||||||
// commit.Precommits[0].Signature = sig.Wrap()
|
|
||||||
// res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
|
|
||||||
// Header: header,
|
|
||||||
// Commit: commit,
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, IBCCodeInvalidCommit)
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestIBCPluginBadProof(t *testing.T) {
|
|
||||||
// assert := assert.New(t)
|
|
||||||
// require := require.New(t)
|
|
||||||
|
|
||||||
// eyesClient := eyes.NewLocalClient("", 0)
|
|
||||||
// store := types.NewKVCache(eyesClient)
|
|
||||||
// store.SetLogging() // Log all activity
|
|
||||||
|
|
||||||
// ibcPlugin := New()
|
|
||||||
// ctx := types.NewCallContext(nil, nil, coin.Coins{})
|
|
||||||
|
|
||||||
// chainID_1 := "test_chain"
|
|
||||||
// genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4)
|
|
||||||
// genDocJSON_1, err := json.Marshal(genDoc_1)
|
|
||||||
// require.Nil(err)
|
|
||||||
|
|
||||||
// // Successfully register a chain
|
|
||||||
// registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
|
|
||||||
|
|
||||||
// // Create a new packet (for testing)
|
|
||||||
// packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world")))
|
|
||||||
// res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
|
||||||
// Packet: packet,
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, abci.CodeType_OK)
|
|
||||||
|
|
||||||
// // Construct a Header that includes the above packet.
|
|
||||||
// store.Sync()
|
|
||||||
// resCommit := eyesClient.CommitSync()
|
|
||||||
// appHash := resCommit.Data
|
|
||||||
// header := newHeader("test_chain", 999, appHash, []byte("must_exist"))
|
|
||||||
|
|
||||||
// // Construct a Commit that signs above header
|
|
||||||
// commit := constructCommit(privAccs_1, header)
|
|
||||||
|
|
||||||
// // Update a chain
|
|
||||||
// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
|
|
||||||
// Header: header,
|
|
||||||
// Commit: commit,
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, abci.CodeType_OK)
|
|
||||||
|
|
||||||
// // Get proof for the packet
|
|
||||||
// packetKey := toKey(_IBC, _EGRESS,
|
|
||||||
// packet.SrcChainID,
|
|
||||||
// packet.DstChainID,
|
|
||||||
// cmn.Fmt("%v", packet.Sequence),
|
|
||||||
// )
|
|
||||||
// resQuery, err := eyesClient.QuerySync(abci.RequestQuery{
|
|
||||||
// Path: "/store",
|
|
||||||
// Data: packetKey,
|
|
||||||
// Prove: true,
|
|
||||||
// })
|
|
||||||
// assert.Nil(err)
|
|
||||||
// var proof *iavl.IAVLProof
|
|
||||||
// err = wire.ReadBinaryBytes(resQuery.Proof, &proof)
|
|
||||||
// assert.Nil(err)
|
|
||||||
|
|
||||||
// // Mutate the proof
|
|
||||||
// proof.InnerNodes[0].Height += 1
|
|
||||||
|
|
||||||
// // Post a packet
|
|
||||||
// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketPostTx{
|
|
||||||
// FromChainID: "test_chain",
|
|
||||||
// FromChainHeight: 999,
|
|
||||||
// Packet: packet,
|
|
||||||
// Proof: proof,
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, IBCCodeInvalidProof)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //-------------------------------------
|
|
||||||
// // utils
|
|
||||||
|
|
||||||
// func assertAndLog(t *testing.T, store *types.KVCache, res abci.Result, codeExpected abci.CodeType) {
|
|
||||||
// assert := assert.New(t)
|
|
||||||
// assert.Equal(codeExpected, res.Code, res.Log)
|
|
||||||
// t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
|
|
||||||
// store.ClearLogLines()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func newHeader(chainID string, height int, appHash, valHash []byte) tm.Header {
|
|
||||||
// return tm.Header{
|
|
||||||
// ChainID: chainID,
|
|
||||||
// Height: height,
|
|
||||||
// AppHash: appHash,
|
|
||||||
// ValidatorsHash: valHash,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func registerChain(t *testing.T, ibcPlugin *IBCPlugin, store *types.KVCache, ctx types.CallContext, chainID, genDoc string) {
|
|
||||||
// res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
|
|
||||||
// BlockchainGenesis{
|
|
||||||
// ChainID: chainID,
|
|
||||||
// Genesis: genDoc,
|
|
||||||
// },
|
|
||||||
// }}))
|
|
||||||
// assertAndLog(t, store, res, abci.CodeType_OK)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func constructCommit(privAccs []types.PrivAccount, header tm.Header) tm.Commit {
|
|
||||||
// blockHash := header.Hash()
|
|
||||||
// blockID := tm.BlockID{Hash: blockHash}
|
|
||||||
// commit := tm.Commit{
|
|
||||||
// BlockID: blockID,
|
|
||||||
// Precommits: make([]*tm.Vote, len(privAccs)),
|
|
||||||
// }
|
|
||||||
// for i, privAcc := range privAccs {
|
|
||||||
// vote := &tm.Vote{
|
|
||||||
// ValidatorAddress: privAcc.Account.PubKey.Address(),
|
|
||||||
// ValidatorIndex: i,
|
|
||||||
// Height: 999,
|
|
||||||
// Round: 0,
|
|
||||||
// Type: tm.VoteTypePrecommit,
|
|
||||||
// BlockID: tm.BlockID{Hash: blockHash},
|
|
||||||
// }
|
|
||||||
// vote.Signature = privAcc.PrivKey.Sign(
|
|
||||||
// tm.SignBytes("test_chain", vote),
|
|
||||||
// )
|
|
||||||
// commit.Precommits[i] = vote
|
|
||||||
// }
|
|
||||||
// return commit
|
|
||||||
// }
|
|
|
@ -14,14 +14,17 @@ type nonce int64
|
||||||
|
|
||||||
type secureContext struct {
|
type secureContext struct {
|
||||||
app string
|
app string
|
||||||
|
ibc bool
|
||||||
// this exposes the log.Logger and all other methods we don't override
|
// this exposes the log.Logger and all other methods we don't override
|
||||||
naiveContext
|
naiveContext
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContext - create a new secureContext
|
// NewContext - create a new secureContext
|
||||||
func NewContext(chain string, height uint64, logger log.Logger) basecoin.Context {
|
func NewContext(chain string, height uint64, logger log.Logger) basecoin.Context {
|
||||||
|
mock := MockContext(chain, height).(naiveContext)
|
||||||
|
mock.Logger = logger
|
||||||
return secureContext{
|
return secureContext{
|
||||||
naiveContext: MockContext(chain, height).(naiveContext),
|
naiveContext: mock,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,24 +34,35 @@ var _ basecoin.Context = secureContext{}
|
||||||
func (c secureContext) WithPermissions(perms ...basecoin.Actor) basecoin.Context {
|
func (c secureContext) WithPermissions(perms ...basecoin.Actor) basecoin.Context {
|
||||||
// the guard makes sure you only set permissions for the app you are inside
|
// the guard makes sure you only set permissions for the app you are inside
|
||||||
for _, p := range perms {
|
for _, p := range perms {
|
||||||
// TODO: also check chainID, limit only certain middleware can set IBC?
|
if !c.validPermisison(p) {
|
||||||
if p.App != c.app {
|
err := errors.Errorf("Cannot set permission for %s/%s on (app=%s, ibc=%b)",
|
||||||
err := errors.Errorf("Cannot set permission for %s from %s", c.app, p.App)
|
p.ChainID, p.App, c.app, c.ibc)
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return secureContext{
|
return secureContext{
|
||||||
app: c.app,
|
app: c.app,
|
||||||
|
ibc: c.ibc,
|
||||||
naiveContext: c.naiveContext.WithPermissions(perms...).(naiveContext),
|
naiveContext: c.naiveContext.WithPermissions(perms...).(naiveContext),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c secureContext) validPermisison(p basecoin.Actor) bool {
|
||||||
|
// if app is set, then it must match
|
||||||
|
if c.app != "" && c.app != p.App {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// if ibc, chain must be set, otherwise it must not
|
||||||
|
return c.ibc == (p.ChainID != "")
|
||||||
|
}
|
||||||
|
|
||||||
// Reset should clear out all permissions,
|
// Reset should clear out all permissions,
|
||||||
// but carry on knowledge that this is a child
|
// but carry on knowledge that this is a child
|
||||||
func (c secureContext) Reset() basecoin.Context {
|
func (c secureContext) Reset() basecoin.Context {
|
||||||
return secureContext{
|
return secureContext{
|
||||||
app: c.app,
|
app: c.app,
|
||||||
|
ibc: c.ibc,
|
||||||
naiveContext: c.naiveContext.Reset().(naiveContext),
|
naiveContext: c.naiveContext.Reset().(naiveContext),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,6 +85,20 @@ func withApp(ctx basecoin.Context, app string) basecoin.Context {
|
||||||
}
|
}
|
||||||
return secureContext{
|
return secureContext{
|
||||||
app: app,
|
app: app,
|
||||||
|
ibc: false,
|
||||||
|
naiveContext: sc.naiveContext,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// withIBC is a private method so we can securely allow IBC permissioning
|
||||||
|
func withIBC(ctx basecoin.Context) basecoin.Context {
|
||||||
|
sc, ok := ctx.(secureContext)
|
||||||
|
if !ok {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
return secureContext{
|
||||||
|
app: "",
|
||||||
|
ibc: true,
|
||||||
naiveContext: sc.naiveContext,
|
naiveContext: sc.naiveContext,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@ import (
|
||||||
// heavily inspired by negroni's design
|
// heavily inspired by negroni's design
|
||||||
type middleware struct {
|
type middleware struct {
|
||||||
middleware Middleware
|
middleware Middleware
|
||||||
|
space string
|
||||||
|
allowIBC bool
|
||||||
next basecoin.Handler
|
next basecoin.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,13 +23,20 @@ func (m *middleware) Name() string {
|
||||||
return m.middleware.Name()
|
return m.middleware.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *middleware) wrapCtx(ctx basecoin.Context) basecoin.Context {
|
||||||
|
if m.allowIBC {
|
||||||
|
return withIBC(ctx)
|
||||||
|
}
|
||||||
|
return withApp(ctx, m.space)
|
||||||
|
}
|
||||||
|
|
||||||
// CheckTx always returns an empty success tx
|
// CheckTx always returns an empty success tx
|
||||||
func (m *middleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (basecoin.Result, error) {
|
func (m *middleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (basecoin.Result, error) {
|
||||||
// make sure we pass in proper context to child
|
// make sure we pass in proper context to child
|
||||||
next := secureCheck(m.next, ctx)
|
next := secureCheck(m.next, ctx)
|
||||||
// set the permissions for this app
|
// set the permissions for this app
|
||||||
ctx = withApp(ctx, m.Name())
|
ctx = m.wrapCtx(ctx)
|
||||||
store = stateSpace(store, m.Name())
|
store = stateSpace(store, m.space)
|
||||||
|
|
||||||
return m.middleware.CheckTx(ctx, store, tx, next)
|
return m.middleware.CheckTx(ctx, store, tx, next)
|
||||||
}
|
}
|
||||||
|
@ -37,22 +46,48 @@ func (m *middleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx ba
|
||||||
// make sure we pass in proper context to child
|
// make sure we pass in proper context to child
|
||||||
next := secureDeliver(m.next, ctx)
|
next := secureDeliver(m.next, ctx)
|
||||||
// set the permissions for this app
|
// set the permissions for this app
|
||||||
ctx = withApp(ctx, m.Name())
|
ctx = m.wrapCtx(ctx)
|
||||||
store = stateSpace(store, m.Name())
|
store = stateSpace(store, m.space)
|
||||||
|
|
||||||
return m.middleware.DeliverTx(ctx, store, tx, next)
|
return m.middleware.DeliverTx(ctx, store, tx, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *middleware) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) {
|
func (m *middleware) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) {
|
||||||
// set the namespace for the app
|
// set the namespace for the app
|
||||||
store = stateSpace(store, m.Name())
|
store = stateSpace(store, m.space)
|
||||||
|
|
||||||
return m.middleware.SetOption(l, store, module, key, value, m.next)
|
return m.middleware.SetOption(l, store, module, key, value, m.next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// builder is used to associate info with the middleware, so we can build
|
||||||
|
// it properly
|
||||||
|
type builder struct {
|
||||||
|
middleware Middleware
|
||||||
|
stateSpace string
|
||||||
|
allowIBC bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func prep(m Middleware, ibc bool) builder {
|
||||||
|
return builder{
|
||||||
|
middleware: m,
|
||||||
|
stateSpace: m.Name(),
|
||||||
|
allowIBC: ibc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap sets up the middleware with the proper options
|
||||||
|
func (b builder) wrap(next basecoin.Handler) basecoin.Handler {
|
||||||
|
return &middleware{
|
||||||
|
middleware: b.middleware,
|
||||||
|
space: b.stateSpace,
|
||||||
|
allowIBC: b.allowIBC,
|
||||||
|
next: next,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stack is the entire application stack
|
// Stack is the entire application stack
|
||||||
type Stack struct {
|
type Stack struct {
|
||||||
middles []Middleware
|
middles []builder
|
||||||
handler basecoin.Handler
|
handler basecoin.Handler
|
||||||
basecoin.Handler // the compiled version, which we expose
|
basecoin.Handler // the compiled version, which we expose
|
||||||
}
|
}
|
||||||
|
@ -62,9 +97,26 @@ var _ basecoin.Handler = &Stack{}
|
||||||
// New prepares a middleware stack, you must `.Use()` a Handler
|
// New prepares a middleware stack, you must `.Use()` a Handler
|
||||||
// before you can execute it.
|
// before you can execute it.
|
||||||
func New(middlewares ...Middleware) *Stack {
|
func New(middlewares ...Middleware) *Stack {
|
||||||
return &Stack{
|
stack := new(Stack)
|
||||||
middles: middlewares,
|
return stack.Apps(middlewares...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apps adds the following Middlewares as typical application
|
||||||
|
// middleware to the stack (limit permission to one app)
|
||||||
|
func (s *Stack) Apps(middlewares ...Middleware) *Stack {
|
||||||
|
// TODO: some wrapper...
|
||||||
|
for _, m := range middlewares {
|
||||||
|
s.middles = append(s.middles, prep(m, false))
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// IBC add the following middleware with permission to add cross-chain
|
||||||
|
// permissions
|
||||||
|
func (s *Stack) IBC(m Middleware) *Stack {
|
||||||
|
// TODO: some wrapper...
|
||||||
|
s.middles = append(s.middles, prep(m, true))
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use sets the final handler for the stack and prepares it for use
|
// Use sets the final handler for the stack and prepares it for use
|
||||||
|
@ -77,10 +129,17 @@ func (s *Stack) Use(handler basecoin.Handler) *Stack {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func build(mid []Middleware, end basecoin.Handler) basecoin.Handler {
|
// Dispatch is like Use, but a convenience method to construct a
|
||||||
|
// dispatcher with a set of modules to route.
|
||||||
|
func (s *Stack) Dispatch(routes ...Dispatchable) *Stack {
|
||||||
|
d := NewDispatcher(routes...)
|
||||||
|
return s.Use(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func build(mid []builder, end basecoin.Handler) basecoin.Handler {
|
||||||
if len(mid) == 0 {
|
if len(mid) == 0 {
|
||||||
return end
|
return end
|
||||||
}
|
}
|
||||||
next := build(mid[1:], end)
|
next := build(mid[1:], end)
|
||||||
return &middleware{mid[0], next}
|
return mid[0].wrap(next)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,25 +31,46 @@ func TestPermissionSandbox(t *testing.T) {
|
||||||
// test cases to make sure permissioning is solid
|
// test cases to make sure permissioning is solid
|
||||||
grantee := basecoin.Actor{App: NameGrant, Address: []byte{1}}
|
grantee := basecoin.Actor{App: NameGrant, Address: []byte{1}}
|
||||||
grantee2 := basecoin.Actor{App: NameGrant, Address: []byte{2}}
|
grantee2 := basecoin.Actor{App: NameGrant, Address: []byte{2}}
|
||||||
signer := basecoin.Actor{App: nameSigner, Address: []byte{1}}
|
// ibc and grantee are the same, just different chains
|
||||||
|
ibc := basecoin.Actor{ChainID: "other", App: NameGrant, Address: []byte{1}}
|
||||||
|
ibc2 := basecoin.Actor{ChainID: "other", App: nameSigner, Address: []byte{21}}
|
||||||
|
signer := basecoin.Actor{App: nameSigner, Address: []byte{21}}
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
|
asIBC bool
|
||||||
grant basecoin.Actor
|
grant basecoin.Actor
|
||||||
require basecoin.Actor
|
require basecoin.Actor
|
||||||
expectedRes data.Bytes
|
expectedRes data.Bytes
|
||||||
expected func(error) bool
|
expected func(error) bool
|
||||||
}{
|
}{
|
||||||
{grantee, grantee, rawBytes, nil},
|
// grant as normal app middleware
|
||||||
{grantee, grantee2, nil, errors.IsUnauthorizedErr},
|
{false, grantee, grantee, rawBytes, nil},
|
||||||
{grantee, signer, nil, errors.IsUnauthorizedErr},
|
{false, grantee, grantee2, nil, errors.IsUnauthorizedErr},
|
||||||
{signer, signer, nil, errors.IsInternalErr},
|
{false, grantee2, grantee2, rawBytes, nil},
|
||||||
|
{false, ibc, grantee, nil, errors.IsInternalErr},
|
||||||
|
{false, grantee, ibc, nil, errors.IsUnauthorizedErr},
|
||||||
|
{false, grantee, signer, nil, errors.IsUnauthorizedErr},
|
||||||
|
{false, signer, signer, nil, errors.IsInternalErr},
|
||||||
|
|
||||||
|
// grant as ibc middleware
|
||||||
|
{true, ibc, ibc, rawBytes, nil}, // ibc can set permissions
|
||||||
|
{true, ibc2, ibc2, rawBytes, nil}, // for any app
|
||||||
|
// the must match, both app and chain
|
||||||
|
{true, ibc, ibc2, nil, errors.IsUnauthorizedErr},
|
||||||
|
{true, ibc, grantee, nil, errors.IsUnauthorizedErr},
|
||||||
|
// cannot set local apps from ibc middleware
|
||||||
|
{true, grantee, grantee, nil, errors.IsInternalErr},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
app := New(
|
app := New(Recovery{})
|
||||||
Recovery{}, // we need this so panics turn to errors
|
if tc.asIBC {
|
||||||
GrantMiddleware{Auth: tc.grant},
|
app = app.IBC(GrantMiddleware{Auth: tc.grant})
|
||||||
CheckMiddleware{Required: tc.require},
|
} else {
|
||||||
).Use(EchoHandler{})
|
app = app.Apps(GrantMiddleware{Auth: tc.grant})
|
||||||
|
}
|
||||||
|
app = app.
|
||||||
|
Apps(CheckMiddleware{Required: tc.require}).
|
||||||
|
Use(EchoHandler{})
|
||||||
|
|
||||||
res, err := app.CheckTx(ctx, store, raw)
|
res, err := app.CheckTx(ctx, store, raw)
|
||||||
checkPerm(t, i, tc.expectedRes, tc.expected, res, err)
|
checkPerm(t, i, tc.expectedRes, tc.expected, res, err)
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
var (
|
||||||
|
headKey = []byte("h")
|
||||||
|
tailKey = []byte("t")
|
||||||
|
dataKey = []byte("d")
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueueHeadKey gives us the key for the height at head of the queue
|
||||||
|
func QueueHeadKey() []byte {
|
||||||
|
return headKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueueTailKey gives us the key for the height at tail of the queue
|
||||||
|
func QueueTailKey() []byte {
|
||||||
|
return tailKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueueItemKey gives us the key to look up one item by sequence
|
||||||
|
func QueueItemKey(i uint64) []byte {
|
||||||
|
return makeKey(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue allows us to fill up a range of the db, and grab from either end
|
||||||
|
type Queue struct {
|
||||||
|
store KVStore
|
||||||
|
head uint64 // if Size() > 0, the first element is here
|
||||||
|
tail uint64 // this is the first empty slot to Push() to
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQueue will load or initialize a queue in this state-space
|
||||||
|
//
|
||||||
|
// Generally, you will want to stack.PrefixStore() the space first
|
||||||
|
func NewQueue(store KVStore) *Queue {
|
||||||
|
q := &Queue{store: store}
|
||||||
|
q.head = q.getCount(headKey)
|
||||||
|
q.tail = q.getCount(tailKey)
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tail returns the next slot that Push() will use
|
||||||
|
func (q *Queue) Tail() uint64 {
|
||||||
|
return q.tail
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns how many elements are in the queue
|
||||||
|
func (q *Queue) Size() int {
|
||||||
|
return int(q.tail - q.head)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push adds an element to the tail of the queue and returns it's location
|
||||||
|
func (q *Queue) Push(value []byte) uint64 {
|
||||||
|
key := makeKey(q.tail)
|
||||||
|
q.store.Set(key, value)
|
||||||
|
q.tail++
|
||||||
|
q.setCount(tailKey, q.tail)
|
||||||
|
return q.tail - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop gets an element from the end of the queue
|
||||||
|
func (q *Queue) Pop() []byte {
|
||||||
|
if q.Size() <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
key := makeKey(q.head)
|
||||||
|
value := q.store.Get(key)
|
||||||
|
q.head++
|
||||||
|
q.setCount(headKey, q.head)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item looks at any element in the queue, without modifying anything
|
||||||
|
func (q *Queue) Item(seq uint64) []byte {
|
||||||
|
if seq >= q.tail || seq < q.head {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return q.store.Get(makeKey(seq))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) setCount(key []byte, val uint64) {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(b, val)
|
||||||
|
q.store.Set(key, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) getCount(key []byte) (val uint64) {
|
||||||
|
b := q.store.Get(key)
|
||||||
|
if b != nil {
|
||||||
|
val = binary.BigEndian.Uint64(b)
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeKey returns the key for a data point
|
||||||
|
func makeKey(val uint64) []byte {
|
||||||
|
b := make([]byte, 8+len(dataKey))
|
||||||
|
copy(b, dataKey)
|
||||||
|
binary.BigEndian.PutUint64(b[len(dataKey):], val)
|
||||||
|
return b
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQueue(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
lots := make([][]byte, 500)
|
||||||
|
for i := range lots {
|
||||||
|
lots[i] = []byte{1, 8, 7}
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
pushes [][]byte
|
||||||
|
pops [][]byte
|
||||||
|
}{
|
||||||
|
// fill it up and empty it all
|
||||||
|
{
|
||||||
|
[][]byte{{1, 2, 3}, {44}, {3, 0}},
|
||||||
|
[][]byte{{1, 2, 3}, {44}, {3, 0}},
|
||||||
|
},
|
||||||
|
// don't empty everything - size is 1 at the end
|
||||||
|
{
|
||||||
|
[][]byte{{77, 22}, {11, 9}, {121}},
|
||||||
|
[][]byte{{77, 22}, {11, 9}},
|
||||||
|
},
|
||||||
|
// empty too much, just get nil, no negative size
|
||||||
|
{
|
||||||
|
[][]byte{{1}, {2}, {4}},
|
||||||
|
[][]byte{{1}, {2}, {4}, nil, nil, nil},
|
||||||
|
},
|
||||||
|
// let's play with lots....
|
||||||
|
{lots, append(lots, nil)},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
store := NewMemKVStore()
|
||||||
|
|
||||||
|
// initialize a queue and add items
|
||||||
|
q := NewQueue(store)
|
||||||
|
for j, in := range tc.pushes {
|
||||||
|
cnt := q.Push(in)
|
||||||
|
assert.Equal(uint64(j), cnt, "%d", i)
|
||||||
|
}
|
||||||
|
assert.EqualValues(len(tc.pushes), q.Size())
|
||||||
|
|
||||||
|
// load from disk and pop them
|
||||||
|
r := NewQueue(store)
|
||||||
|
for _, out := range tc.pops {
|
||||||
|
val := r.Pop()
|
||||||
|
assert.Equal(out, val, "%d", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's empty in memory and on disk
|
||||||
|
expected := len(tc.pushes) - len(tc.pops)
|
||||||
|
if expected < 0 {
|
||||||
|
expected = 0
|
||||||
|
}
|
||||||
|
assert.EqualValues(expected, r.Size())
|
||||||
|
s := NewQueue(store)
|
||||||
|
assert.EqualValues(expected, s.Size())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
wire "github.com/tendermint/go-wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetKey returns the key to get all members of this set
|
||||||
|
func SetKey() []byte {
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set allows us to add arbitrary k-v pairs, check existence,
|
||||||
|
// as well as iterate through the set (always in key order)
|
||||||
|
//
|
||||||
|
// If we had full access to the IAVL tree, this would be completely
|
||||||
|
// trivial and redundant
|
||||||
|
type Set struct {
|
||||||
|
store KVStore
|
||||||
|
keys KeyList
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ KVStore = &Set{}
|
||||||
|
|
||||||
|
// NewSet loads or initializes a span of keys
|
||||||
|
func NewSet(store KVStore) *Set {
|
||||||
|
s := &Set{store: store}
|
||||||
|
s.loadKeys()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set puts a value at a given height.
|
||||||
|
// If the value is nil, or an empty slice, remove the key from the list
|
||||||
|
func (s *Set) Set(key []byte, value []byte) {
|
||||||
|
s.store.Set(MakeBKey(key), value)
|
||||||
|
if len(value) > 0 {
|
||||||
|
s.addKey(key)
|
||||||
|
} else {
|
||||||
|
s.removeKey(key)
|
||||||
|
}
|
||||||
|
s.storeKeys()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the element with a key if it exists
|
||||||
|
func (s *Set) Get(key []byte) []byte {
|
||||||
|
return s.store.Get(MakeBKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove deletes this key from the set (same as setting value = nil)
|
||||||
|
func (s *Set) Remove(key []byte) {
|
||||||
|
s.store.Set(key, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists checks for the existence of the key in the set
|
||||||
|
func (s *Set) Exists(key []byte) bool {
|
||||||
|
return len(s.Get(key)) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns how many elements are in the set
|
||||||
|
func (s *Set) Size() int {
|
||||||
|
return len(s.keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns all keys in the set
|
||||||
|
// It makes a copy, so we don't modify this in place
|
||||||
|
func (s *Set) List() (keys KeyList) {
|
||||||
|
out := make([][]byte, len(s.keys))
|
||||||
|
for i := range s.keys {
|
||||||
|
out[i] = append([]byte(nil), s.keys[i]...)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// addKey inserts this key, maintaining sorted order, no duplicates
|
||||||
|
func (s *Set) addKey(key []byte) {
|
||||||
|
for i, k := range s.keys {
|
||||||
|
cmp := bytes.Compare(k, key)
|
||||||
|
// don't add duplicates
|
||||||
|
if cmp == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// insert before the first key greater than input
|
||||||
|
if cmp > 0 {
|
||||||
|
// https://github.com/golang/go/wiki/SliceTricks
|
||||||
|
s.keys = append(s.keys, nil)
|
||||||
|
copy(s.keys[i+1:], s.keys[i:])
|
||||||
|
s.keys[i] = key
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if it is higher than all (or empty keys), append
|
||||||
|
s.keys = append(s.keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeKey removes this key if it is present, maintaining sorted order
|
||||||
|
func (s *Set) removeKey(key []byte) {
|
||||||
|
for i, k := range s.keys {
|
||||||
|
cmp := bytes.Compare(k, key)
|
||||||
|
// if there is a match, remove
|
||||||
|
if cmp == 0 {
|
||||||
|
s.keys = append(s.keys[:i], s.keys[i+1:]...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// if we has the proper location, without finding it, abort
|
||||||
|
if cmp > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Set) loadKeys() {
|
||||||
|
b := s.store.Get(keys)
|
||||||
|
if b == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := wire.ReadBinaryBytes(b, &s.keys)
|
||||||
|
// hahaha... just like i love to hate :)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Set) storeKeys() {
|
||||||
|
b := wire.BinaryBytes(s.keys)
|
||||||
|
s.store.Set(keys, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeBKey prefixes the byte slice for the storage key
|
||||||
|
func MakeBKey(key []byte) []byte {
|
||||||
|
return append(dataKey, key...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyList is a sortable list of byte slices
|
||||||
|
type KeyList [][]byte
|
||||||
|
|
||||||
|
//nolint
|
||||||
|
func (kl KeyList) Len() int { return len(kl) }
|
||||||
|
func (kl KeyList) Less(i, j int) bool { return bytes.Compare(kl[i], kl[j]) < 0 }
|
||||||
|
func (kl KeyList) Swap(i, j int) { kl[i], kl[j] = kl[j], kl[i] }
|
||||||
|
|
||||||
|
var _ sort.Interface = KeyList{}
|
||||||
|
|
||||||
|
// Equals checks for if the two lists have the same content...
|
||||||
|
// needed as == doesn't work for slices of slices
|
||||||
|
func (kl KeyList) Equals(kl2 KeyList) bool {
|
||||||
|
if len(kl) != len(kl2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range kl {
|
||||||
|
if !bytes.Equal(kl[i], kl2[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pair struct {
|
||||||
|
k []byte
|
||||||
|
v []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type setCase struct {
|
||||||
|
data []pair
|
||||||
|
// these are the tests to try out
|
||||||
|
gets []pair // for each item check the query matches
|
||||||
|
list KeyList // make sure the set returns the proper list
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSet(t *testing.T) {
|
||||||
|
|
||||||
|
a, b, c, d := []byte{0xaa}, []byte{0xbb}, []byte{0xcc}, []byte{0xdd}
|
||||||
|
|
||||||
|
cases := []setCase{
|
||||||
|
|
||||||
|
// simplest queries
|
||||||
|
{
|
||||||
|
[]pair{{a, a}, {b, b}, {c, c}},
|
||||||
|
[]pair{{c, c}, {d, nil}, {b, b}},
|
||||||
|
KeyList{a, b, c},
|
||||||
|
},
|
||||||
|
// out of order
|
||||||
|
{
|
||||||
|
[]pair{{c, a}, {a, b}, {d, c}, {b, d}},
|
||||||
|
[]pair{{a, b}, {b, d}},
|
||||||
|
KeyList{a, b, c, d},
|
||||||
|
},
|
||||||
|
// duplicate and removing
|
||||||
|
{
|
||||||
|
[]pair{{c, a}, {c, c}, {a, d}, {d, d}, {b, b}, {d, nil}, {a, nil}, {a, a}, {b, nil}},
|
||||||
|
[]pair{{a, a}, {c, c}, {b, nil}},
|
||||||
|
KeyList{a, c},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
store := NewMemKVStore()
|
||||||
|
|
||||||
|
// initialize a queue and add items
|
||||||
|
s := NewSet(store)
|
||||||
|
for _, x := range tc.data {
|
||||||
|
s.Set(x.k, x.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
testSet(t, i, s, tc)
|
||||||
|
// reload and try the queries again
|
||||||
|
s2 := NewSet(store)
|
||||||
|
testSet(t, i+10, s2, tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSet(t *testing.T, idx int, s *Set, tc setCase) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
i := strconv.Itoa(idx)
|
||||||
|
|
||||||
|
for _, g := range tc.gets {
|
||||||
|
v := s.Get(g.k)
|
||||||
|
assert.Equal(g.v, v, i)
|
||||||
|
e := s.Exists(g.k)
|
||||||
|
assert.Equal(e, (g.v != nil), i)
|
||||||
|
}
|
||||||
|
|
||||||
|
l := s.List()
|
||||||
|
assert.True(tc.list.Equals(l), "%s: %v / %v", i, tc.list, l)
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import wire "github.com/tendermint/go-wire"
|
||||||
|
|
||||||
|
var (
|
||||||
|
keys = []byte("keys")
|
||||||
|
// uses dataKey from queue.go to prefix data
|
||||||
|
)
|
||||||
|
|
||||||
|
// Span holds a number of different keys in a large range and allows
|
||||||
|
// use to make some basic range queries, like highest between, lowest between...
|
||||||
|
// All items are added with an index
|
||||||
|
//
|
||||||
|
// This becomes horribly inefficent as len(keys) => 1000+, but by then
|
||||||
|
// hopefully we have access to the iavl tree to do this well
|
||||||
|
//
|
||||||
|
// TODO: doesn't handle deleting....
|
||||||
|
type Span struct {
|
||||||
|
store KVStore
|
||||||
|
// keys is sorted ascending and cannot contain duplicates
|
||||||
|
keys []uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSpan loads or initializes a span of keys
|
||||||
|
func NewSpan(store KVStore) *Span {
|
||||||
|
s := &Span{store: store}
|
||||||
|
s.loadKeys()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set puts a value at a given height
|
||||||
|
func (s *Span) Set(h uint64, value []byte) {
|
||||||
|
key := makeKey(h)
|
||||||
|
s.store.Set(key, value)
|
||||||
|
s.addKey(h)
|
||||||
|
s.storeKeys()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the element at h if it exists
|
||||||
|
func (s *Span) Get(h uint64) []byte {
|
||||||
|
key := makeKey(h)
|
||||||
|
return s.store.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom returns the lowest element in the Span, along with its index
|
||||||
|
func (s *Span) Bottom() ([]byte, uint64) {
|
||||||
|
if len(s.keys) == 0 {
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
h := s.keys[0]
|
||||||
|
return s.Get(h), h
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top returns the highest element in the Span, along with its index
|
||||||
|
func (s *Span) Top() ([]byte, uint64) {
|
||||||
|
l := len(s.keys)
|
||||||
|
if l == 0 {
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
h := s.keys[l-1]
|
||||||
|
return s.Get(h), h
|
||||||
|
}
|
||||||
|
|
||||||
|
// GTE returns the lowest element in the Span that is >= h, along with its index
|
||||||
|
func (s *Span) GTE(h uint64) ([]byte, uint64) {
|
||||||
|
for _, k := range s.keys {
|
||||||
|
if k >= h {
|
||||||
|
return s.Get(k), k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// LTE returns the highest element in the Span that is <= h,
|
||||||
|
// along with its index
|
||||||
|
func (s *Span) LTE(h uint64) ([]byte, uint64) {
|
||||||
|
var k uint64
|
||||||
|
// start from the highest and go down for the first match
|
||||||
|
for i := len(s.keys) - 1; i >= 0; i-- {
|
||||||
|
k = s.keys[i]
|
||||||
|
if k <= h {
|
||||||
|
return s.Get(k), k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// addKey inserts this key, maintaining sorted order, no duplicates
|
||||||
|
func (s *Span) addKey(h uint64) {
|
||||||
|
for i, k := range s.keys {
|
||||||
|
// don't add duplicates
|
||||||
|
if h == k {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// insert before this key
|
||||||
|
if h < k {
|
||||||
|
// https://github.com/golang/go/wiki/SliceTricks
|
||||||
|
s.keys = append(s.keys, 0)
|
||||||
|
copy(s.keys[i+1:], s.keys[i:])
|
||||||
|
s.keys[i] = h
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if it is higher than all (or empty keys), append
|
||||||
|
s.keys = append(s.keys, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Span) loadKeys() {
|
||||||
|
b := s.store.Get(keys)
|
||||||
|
if b == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := wire.ReadBinaryBytes(b, &s.keys)
|
||||||
|
// hahaha... just like i love to hate :)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Span) storeKeys() {
|
||||||
|
b := wire.BinaryBytes(s.keys)
|
||||||
|
s.store.Set(keys, b)
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type kv struct {
|
||||||
|
k uint64
|
||||||
|
v []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type bscase struct {
|
||||||
|
data []kv
|
||||||
|
// these are the tests to try out
|
||||||
|
top kv
|
||||||
|
bottom kv
|
||||||
|
gets []kv // for each item check the query matches
|
||||||
|
lte []kv // value for lte queires...
|
||||||
|
gte []kv // value for gte
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicSpan(t *testing.T) {
|
||||||
|
|
||||||
|
a, b, c := []byte{0xaa}, []byte{0xbb}, []byte{0xcc}
|
||||||
|
|
||||||
|
lots := make([]kv, 1000)
|
||||||
|
for i := range lots {
|
||||||
|
lots[i] = kv{uint64(3 * i), []byte{byte(i / 100), byte(i % 100)}}
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []bscase{
|
||||||
|
// simplest queries
|
||||||
|
{
|
||||||
|
[]kv{{1, a}, {3, b}, {5, c}},
|
||||||
|
kv{5, c},
|
||||||
|
kv{1, a},
|
||||||
|
[]kv{{1, a}, {3, b}, {5, c}},
|
||||||
|
[]kv{{2, a}, {77, c}, {3, b}, {0, nil}}, // lte
|
||||||
|
[]kv{{6, nil}, {2, b}, {1, a}}, // gte
|
||||||
|
},
|
||||||
|
// add out of order
|
||||||
|
{
|
||||||
|
[]kv{{7, a}, {2, b}, {6, c}},
|
||||||
|
kv{7, a},
|
||||||
|
kv{2, b},
|
||||||
|
[]kv{{2, b}, {6, c}, {7, a}},
|
||||||
|
[]kv{{4, b}, {7, a}, {1, nil}}, // lte
|
||||||
|
[]kv{{4, c}, {7, a}, {1, b}}, // gte
|
||||||
|
},
|
||||||
|
// add out of order and with duplicates
|
||||||
|
{
|
||||||
|
[]kv{{7, a}, {2, b}, {6, c}, {7, c}, {6, b}, {2, a}},
|
||||||
|
kv{7, c},
|
||||||
|
kv{2, a},
|
||||||
|
[]kv{{2, a}, {6, b}, {7, c}},
|
||||||
|
[]kv{{5, a}, {6, b}, {123, c}}, // lte
|
||||||
|
[]kv{{0, a}, {3, b}, {7, c}, {8, nil}}, // gte
|
||||||
|
},
|
||||||
|
// try lots...
|
||||||
|
{
|
||||||
|
lots,
|
||||||
|
lots[len(lots)-1],
|
||||||
|
lots[0],
|
||||||
|
lots,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
store := NewMemKVStore()
|
||||||
|
|
||||||
|
// initialize a queue and add items
|
||||||
|
s := NewSpan(store)
|
||||||
|
for _, x := range tc.data {
|
||||||
|
s.Set(x.k, x.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
testSpan(t, i, s, tc)
|
||||||
|
// reload and try the queries again
|
||||||
|
s2 := NewSpan(store)
|
||||||
|
testSpan(t, i+10, s2, tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSpan(t *testing.T, idx int, s *Span, tc bscase) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
i := strconv.Itoa(idx)
|
||||||
|
|
||||||
|
v, k := s.Top()
|
||||||
|
assert.Equal(tc.top.k, k, i)
|
||||||
|
assert.Equal(tc.top.v, v, i)
|
||||||
|
|
||||||
|
v, k = s.Bottom()
|
||||||
|
assert.Equal(tc.bottom.k, k, i)
|
||||||
|
assert.Equal(tc.bottom.v, v, i)
|
||||||
|
|
||||||
|
for _, g := range tc.gets {
|
||||||
|
v = s.Get(g.k)
|
||||||
|
assert.Equal(g.v, v, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, l := range tc.lte {
|
||||||
|
v, k = s.LTE(l.k)
|
||||||
|
assert.Equal(l.v, v, i)
|
||||||
|
if l.v != nil {
|
||||||
|
assert.True(k <= l.k, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range tc.gte {
|
||||||
|
v, k = s.GTE(t.k)
|
||||||
|
assert.Equal(t.v, v, i)
|
||||||
|
if t.v != nil {
|
||||||
|
assert.True(k >= t.k, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -83,6 +83,23 @@ test02SendTxWithFee() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test03CreditTx() {
|
||||||
|
SENDER=$(getAddr $RICH)
|
||||||
|
RECV=$(getAddr $POOR)
|
||||||
|
|
||||||
|
# make sure we are controlled by permissions (only rich can issue credit)
|
||||||
|
assertFalse "line=${LINENO}, bad password" "echo qwertyuiop | ${CLIENT_EXE} tx credit --amount=1000mycoin --sequence=1 --to=$RECV --name=$POOR"
|
||||||
|
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx credit --amount=1000mycoin --sequence=3 --to=$RECV --name=$RICH)
|
||||||
|
txSucceeded $? "$TX" "$RECV"
|
||||||
|
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||||
|
TX_HEIGHT=$(echo $TX | jq .height)
|
||||||
|
|
||||||
|
# receiver got cash, sender didn't lose any (1000 more than last check)
|
||||||
|
checkAccount $RECV "2082"
|
||||||
|
checkAccount $SENDER "9007199254739900"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# Load common then run these tests with shunit2!
|
# Load common then run these tests with shunit2!
|
||||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||||
. $DIR/common.sh
|
. $DIR/common.sh
|
||||||
|
|
323
tests/cli/ibc.sh
323
tests/cli/ibc.sh
|
@ -9,8 +9,8 @@ ACCOUNTS=(jae ethan bucky rigel igor)
|
||||||
RICH=${ACCOUNTS[0]}
|
RICH=${ACCOUNTS[0]}
|
||||||
POOR=${ACCOUNTS[4]}
|
POOR=${ACCOUNTS[4]}
|
||||||
|
|
||||||
# Uncomment the following line for full stack traces in error output
|
# For full stack traces in error output, run
|
||||||
# CLIENT_EXE="basecli --trace"
|
# BC_TRACE=1 ./ibc.sh
|
||||||
|
|
||||||
oneTimeSetUp() {
|
oneTimeSetUp() {
|
||||||
# These are passed in as args
|
# These are passed in as args
|
||||||
|
@ -62,13 +62,13 @@ oneTimeTearDown() {
|
||||||
}
|
}
|
||||||
|
|
||||||
test00GetAccount() {
|
test00GetAccount() {
|
||||||
SENDER_1=$(BC_HOME=${CLIENT_1} getAddr $RICH)
|
|
||||||
RECV_1=$(BC_HOME=${CLIENT_1} getAddr $POOR)
|
|
||||||
export BC_HOME=${CLIENT_1}
|
export BC_HOME=${CLIENT_1}
|
||||||
|
SENDER_1=$(getAddr $RICH)
|
||||||
|
RECV_1=$(getAddr $POOR)
|
||||||
|
|
||||||
assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account 2>/dev/null"
|
assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account 2>/dev/null"
|
||||||
assertFalse "line=${LINENO}, has no genesis account" "${CLIENT_EXE} query account $RECV_1 2>/dev/null"
|
assertFalse "line=${LINENO}, has no genesis account" "${CLIENT_EXE} query account $RECV_1 2>/dev/null"
|
||||||
checkAccount $SENDER_1 "0" "9007199254740992"
|
checkAccount $SENDER_1 "9007199254740992"
|
||||||
|
|
||||||
export BC_HOME=${CLIENT_2}
|
export BC_HOME=${CLIENT_2}
|
||||||
SENDER_2=$(getAddr $RICH)
|
SENDER_2=$(getAddr $RICH)
|
||||||
|
@ -76,102 +76,281 @@ test00GetAccount() {
|
||||||
|
|
||||||
assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account 2>/dev/null"
|
assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account 2>/dev/null"
|
||||||
assertFalse "line=${LINENO}, has no genesis account" "${CLIENT_EXE} query account $RECV_2 2>/dev/null"
|
assertFalse "line=${LINENO}, has no genesis account" "${CLIENT_EXE} query account $RECV_2 2>/dev/null"
|
||||||
checkAccount $SENDER_2 "0" "9007199254740992"
|
checkAccount $SENDER_2 "9007199254740992"
|
||||||
|
|
||||||
# Make sure that they have different addresses on both chains (they are random keys)
|
# Make sure that they have different addresses on both chains (they are random keys)
|
||||||
assertNotEquals "line=${LINENO}, sender keys must be different" "$SENDER_1" "$SENDER_2"
|
assertNotEquals "line=${LINENO}, sender keys must be different" "$SENDER_1" "$SENDER_2"
|
||||||
assertNotEquals "line=${LINENO}, recipient keys must be different" "$RECV_1" "$RECV_2"
|
assertNotEquals "line=${LINENO}, recipient keys must be different" "$RECV_1" "$RECV_2"
|
||||||
}
|
}
|
||||||
|
|
||||||
test01SendIBCTx() {
|
test01RegisterChains() {
|
||||||
|
# let's get the root seeds to cross-register them
|
||||||
|
ROOT_1="$BASE_DIR_1/root_seed.json"
|
||||||
|
${CLIENT_EXE} seeds export $ROOT_1 --home=${CLIENT_1}
|
||||||
|
assertTrue "line=${LINENO}, export seed failed" $?
|
||||||
|
|
||||||
|
ROOT_2="$BASE_DIR_2/root_seed.json"
|
||||||
|
${CLIENT_EXE} seeds export $ROOT_2 --home=${CLIENT_2}
|
||||||
|
assertTrue "line=${LINENO}, export seed failed" $?
|
||||||
|
|
||||||
|
# register chain2 on chain1
|
||||||
|
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-register \
|
||||||
|
--sequence=1 --seed=${ROOT_2} --name=$POOR --home=${CLIENT_1})
|
||||||
|
txSucceeded $? "$TX" "register chain2 on chain 1"
|
||||||
|
# an example to quit early if there is no point in more tests
|
||||||
|
if [ $? != 0 ]; then echo "aborting!"; return 1; fi
|
||||||
|
# this is used later to check data
|
||||||
|
REG_HEIGHT=$(echo $TX | jq .height)
|
||||||
|
|
||||||
|
# register chain1 on chain2 (no money needed... yet)
|
||||||
|
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-register \
|
||||||
|
--sequence=1 --seed=${ROOT_1} --name=$POOR --home=${CLIENT_2})
|
||||||
|
txSucceeded $? "$TX" "register chain1 on chain 2"
|
||||||
|
# an example to quit early if there is no point in more tests
|
||||||
|
if [ $? != 0 ]; then echo "aborting!"; return 1; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test02UpdateChains() {
|
||||||
|
# let's get the root seeds to cross-register them
|
||||||
|
UPDATE_1="$BASE_DIR_1/seed_1.json"
|
||||||
|
${CLIENT_EXE} seeds update --home=${CLIENT_1} > /dev/null
|
||||||
|
${CLIENT_EXE} seeds export $UPDATE_1 --home=${CLIENT_1}
|
||||||
|
assertTrue "line=${LINENO}, export seed failed" $?
|
||||||
|
# make sure it is newer than the other....
|
||||||
|
assertNewHeight "line=${LINENO}" $ROOT_1 $UPDATE_1
|
||||||
|
|
||||||
|
UPDATE_2="$BASE_DIR_2/seed_2.json"
|
||||||
|
${CLIENT_EXE} seeds update --home=${CLIENT_2} > /dev/null
|
||||||
|
${CLIENT_EXE} seeds export $UPDATE_2 --home=${CLIENT_2}
|
||||||
|
assertTrue "line=${LINENO}, export seed failed" $?
|
||||||
|
assertNewHeight "line=${LINENO}" $ROOT_2 $UPDATE_2
|
||||||
|
# this is used later to check query data
|
||||||
|
REGISTER_2_HEIGHT=$(cat $ROOT_2 | jq .checkpoint.header.height)
|
||||||
|
UPDATE_2_HEIGHT=$(cat $UPDATE_2 | jq .checkpoint.header.height)
|
||||||
|
|
||||||
|
# update chain2 on chain1
|
||||||
|
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-update \
|
||||||
|
--sequence=2 --seed=${UPDATE_2} --name=$POOR --home=${CLIENT_1})
|
||||||
|
txSucceeded $? "$TX" "update chain2 on chain 1"
|
||||||
|
# an example to quit early if there is no point in more tests
|
||||||
|
if [ $? != 0 ]; then echo "aborting!"; return 1; fi
|
||||||
|
|
||||||
|
# update chain1 on chain2 (no money needed... yet)
|
||||||
|
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-update \
|
||||||
|
--sequence=2 --seed=${UPDATE_1} --name=$POOR --home=${CLIENT_2})
|
||||||
|
txSucceeded $? "$TX" "update chain1 on chain 2"
|
||||||
|
# an example to quit early if there is no point in more tests
|
||||||
|
if [ $? != 0 ]; then echo "aborting!"; return 1; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# make sure all query commands about ibc work...
|
||||||
|
test03QueryIBC() {
|
||||||
|
# just test on one chain, as they are all symetrical
|
||||||
|
export BC_HOME=${CLIENT_1}
|
||||||
|
|
||||||
|
# make sure we can list all chains
|
||||||
|
CHAINS=$(${CLIENT_EXE} query ibc chains)
|
||||||
|
assertTrue "line=${LINENO}, cannot query chains" $?
|
||||||
|
assertEquals "1" $(echo $CHAINS | jq '.data | length')
|
||||||
|
assertEquals "line=${LINENO}" "\"$CHAIN_ID_2\"" $(echo $CHAINS | jq '.data[0]')
|
||||||
|
|
||||||
|
# error on unknown chain, data on proper chain
|
||||||
|
assertFalse "line=${LINENO}, unknown chain" "${CLIENT_EXE} query ibc chain random 2>/dev/null"
|
||||||
|
CHAIN_INFO=$(${CLIENT_EXE} query ibc chain $CHAIN_ID_2)
|
||||||
|
assertTrue "line=${LINENO}, cannot query chain $CHAIN_ID_2" $?
|
||||||
|
assertEquals "line=${LINENO}, register height" $REG_HEIGHT $(echo $CHAIN_INFO | jq .data.registered_at)
|
||||||
|
assertEquals "line=${LINENO}, tracked height" $UPDATE_2_HEIGHT $(echo $CHAIN_INFO | jq .data.remote_block)
|
||||||
|
}
|
||||||
|
|
||||||
# Trigger a cross-chain sendTx... from RICH on chain1 to POOR on chain2
|
# Trigger a cross-chain sendTx... from RICH on chain1 to POOR on chain2
|
||||||
# we make sure the money was reduced, but nothing arrived
|
# we make sure the money was reduced, but nothing arrived
|
||||||
SENDER=$(BC_HOME=${CLIENT_1} getAddr $RICH)
|
test04SendIBCPacket() {
|
||||||
|
export BC_HOME=${CLIENT_1}
|
||||||
|
|
||||||
|
# make sure there are no packets yet
|
||||||
|
PACKETS=$(${CLIENT_EXE} query ibc packets --to=$CHAIN_ID_2 2>/dev/null)
|
||||||
|
assertFalse "line=${LINENO}, packet query" $?
|
||||||
|
|
||||||
|
SENDER=$(getAddr $RICH)
|
||||||
RECV=$(BC_HOME=${CLIENT_2} getAddr $POOR)
|
RECV=$(BC_HOME=${CLIENT_2} getAddr $POOR)
|
||||||
|
|
||||||
export BC_HOME=${CLIENT_1}
|
|
||||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=20002mycoin \
|
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=20002mycoin \
|
||||||
--sequence=1 --to=${CHAIN_ID_2}/${RECV} --name=$RICH)
|
--to=${CHAIN_ID_2}::${RECV} --name=$RICH)
|
||||||
txSucceeded $? "$TX" "${CHAIN_ID_2}/${RECV}"
|
txSucceeded $? "$TX" "${CHAIN_ID_2}::${RECV}"
|
||||||
# an example to quit early if there is no point in more tests
|
# quit early if there is no point in more tests
|
||||||
if [ $? != 0 ]; then echo "aborting!"; return 1; fi
|
if [ $? != 0 ]; then echo "aborting!"; return 1; fi
|
||||||
|
|
||||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||||
TX_HEIGHT=$(echo $TX | jq .height)
|
TX_HEIGHT=$(echo $TX | jq .height)
|
||||||
|
|
||||||
# Make sure balance went down and tx is indexed
|
# Make sure balance went down and tx is indexed
|
||||||
checkAccount $SENDER "1" "9007199254720990"
|
checkAccount $SENDER "9007199254720990"
|
||||||
checkSendTx $HASH $TX_HEIGHT $SENDER "20002"
|
checkSendTx $HASH $TX_HEIGHT $SENDER "20002"
|
||||||
|
|
||||||
# Make sure nothing arrived - yet
|
# look, we wrote a packet
|
||||||
waitForBlock ${PORT_1}
|
PACKETS=$(${CLIENT_EXE} query ibc packets --to=$CHAIN_ID_2)
|
||||||
|
assertTrue "line=${LINENO}, packets query" $?
|
||||||
|
assertEquals "line=${LINENO}, packet count" 1 $(echo $PACKETS | jq .data)
|
||||||
|
|
||||||
|
# and look at the packet itself
|
||||||
|
PACKET=$(${CLIENT_EXE} query ibc packet --to=$CHAIN_ID_2 --sequence=0)
|
||||||
|
assertTrue "line=${LINENO}, packet query" $?
|
||||||
|
assertEquals "line=${LINENO}, proper src" "\"$CHAIN_ID_1\"" $(echo $PACKET | jq .src_chain)
|
||||||
|
assertEquals "line=${LINENO}, proper dest" "\"$CHAIN_ID_2\"" $(echo $PACKET | jq .packet.dest_chain)
|
||||||
|
assertEquals "line=${LINENO}, proper sequence" "0" $(echo $PACKET | jq .packet.sequence)
|
||||||
|
|
||||||
|
# nothing arrived
|
||||||
|
ARRIVED=$(${CLIENT_EXE} query ibc packets --from=$CHAIN_ID_1 --home=$CLIENT_2 2>/dev/null)
|
||||||
|
assertFalse "line=${LINENO}, packet query" $?
|
||||||
assertFalse "line=${LINENO}, no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV"
|
assertFalse "line=${LINENO}, no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV"
|
||||||
|
|
||||||
# Start the relay and wait a few blocks...
|
|
||||||
# (already sent a tx on chain1, so use higher sequence)
|
|
||||||
startRelay 2 1
|
|
||||||
if [ $? != 0 ]; then echo "can't start relay"; cat ${BASE_DIR_1}/../relay.log; return 1; fi
|
|
||||||
|
|
||||||
# Give it a little time, then make sure the money arrived
|
|
||||||
echo "waiting for relay..."
|
|
||||||
sleep 1
|
|
||||||
waitForBlock ${PORT_1}
|
|
||||||
waitForBlock ${PORT_2}
|
|
||||||
|
|
||||||
# Check the new account
|
|
||||||
echo "checking ibc recipient..."
|
|
||||||
BC_HOME=${CLIENT_2} checkAccount $RECV "0" "20002"
|
|
||||||
|
|
||||||
# Stop relay
|
|
||||||
printf "stoping relay\n"
|
|
||||||
kill -9 $PID_RELAY
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# StartRelay $seq1 $seq2
|
test05ReceiveIBCPacket() {
|
||||||
# startRelay hooks up a relay between chain1 and chain2
|
|
||||||
# it needs the proper sequence number for $RICH on chain1 and chain2 as args
|
|
||||||
startRelay() {
|
|
||||||
# Send some cash to the default key, so it can send messages
|
|
||||||
RELAY_KEY=${BASE_DIR_1}/server/key.json
|
|
||||||
RELAY_ADDR=$(cat $RELAY_KEY | jq .address | tr -d \")
|
|
||||||
echo starting relay $PID_RELAY ...
|
|
||||||
|
|
||||||
# Get paid on chain1
|
|
||||||
export BC_HOME=${CLIENT_1}
|
|
||||||
SENDER=$(getAddr $RICH)
|
|
||||||
RES=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=100000mycoin \
|
|
||||||
--sequence=$1 --to=$RELAY_ADDR --name=$RICH)
|
|
||||||
txSucceeded $? "$RES" "$RELAY_ADDR"
|
|
||||||
if [ $? != 0 ]; then echo "can't pay chain1!"; return 1; fi
|
|
||||||
|
|
||||||
# Get paid on chain2
|
|
||||||
export BC_HOME=${CLIENT_2}
|
export BC_HOME=${CLIENT_2}
|
||||||
SENDER=$(getAddr $RICH)
|
|
||||||
RES=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=100000mycoin \
|
|
||||||
--sequence=$2 --to=$RELAY_ADDR --name=$RICH)
|
|
||||||
txSucceeded $? "$RES" "$RELAY_ADDR"
|
|
||||||
if [ $? != 0 ]; then echo "can't pay chain2!"; return 1; fi
|
|
||||||
|
|
||||||
# Initialize the relay (register both chains)
|
# make some credit, so we can accept the packet
|
||||||
${SERVER_EXE} relay init --chain1-id=$CHAIN_ID_1 --chain2-id=$CHAIN_ID_2 \
|
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx credit --amount=60006mycoin --to=$CHAIN_ID_1:: --name=$RICH)
|
||||||
--chain1-addr=tcp://localhost:${PORT_1} --chain2-addr=tcp://localhost:${PORT_2} \
|
txSucceeded $? "$TX" "${CHAIN_ID_1}::"
|
||||||
--genesis1=${BASE_DIR_1}/server/genesis.json --genesis2=${BASE_DIR_2}/server/genesis.json \
|
checkAccount $CHAIN_ID_1:: "60006"
|
||||||
--from=$RELAY_KEY > ${BASE_DIR_1}/../relay.log
|
|
||||||
if [ $? != 0 ]; then echo "can't initialize relays"; cat ${BASE_DIR_1}/../relay.log; return 1; fi
|
|
||||||
|
|
||||||
# Now start the relay (constantly send packets)
|
# now, we try to post it.... (this is PACKET from last test)
|
||||||
${SERVER_EXE} relay start --chain1-id=$CHAIN_ID_1 --chain2-id=$CHAIN_ID_2 \
|
|
||||||
--chain1-addr=tcp://localhost:${PORT_1} --chain2-addr=tcp://localhost:${PORT_2} \
|
|
||||||
--from=$RELAY_KEY >> ${BASE_DIR_1}/../relay.log &
|
|
||||||
sleep 2
|
|
||||||
PID_RELAY=$!
|
|
||||||
disown
|
|
||||||
|
|
||||||
# Return an error if it dies in the first two seconds to make sure it is running
|
# get the seed and post it
|
||||||
ps $PID_RELAY >/dev/null
|
SRC_HEIGHT=$(echo $PACKET | jq .src_height)
|
||||||
|
# FIXME: this should auto-update on proofs...
|
||||||
|
${CLIENT_EXE} seeds update --height=$SRC_HEIGHT --home=${CLIENT_1} > /dev/null
|
||||||
|
assertTrue "line=${LINENO}, update seed failed" $?
|
||||||
|
|
||||||
|
PACKET_SEED="$BASE_DIR_1/packet_seed.json"
|
||||||
|
${CLIENT_EXE} seeds export $PACKET_SEED --home=${CLIENT_1} #--height=$SRC_HEIGHT
|
||||||
|
assertTrue "line=${LINENO}, export seed failed" $?
|
||||||
|
# echo "**** SEED ****"
|
||||||
|
# cat $PACKET_SEED | jq .
|
||||||
|
|
||||||
|
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-update \
|
||||||
|
--seed=${PACKET_SEED} --name=$POOR)
|
||||||
|
txSucceeded $? "$TX" "prepare packet chain1 on chain 2"
|
||||||
|
# an example to quit early if there is no point in more tests
|
||||||
|
if [ $? != 0 ]; then echo "aborting!"; return 1; fi
|
||||||
|
|
||||||
|
# write the packet to the file
|
||||||
|
POST_PACKET="$BASE_DIR_1/post_packet.json"
|
||||||
|
echo $PACKET > $POST_PACKET
|
||||||
|
# echo "**** POST ****"
|
||||||
|
# cat $POST_PACKET | jq .
|
||||||
|
|
||||||
|
# post it as a tx (cross-fingers)
|
||||||
|
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-post \
|
||||||
|
--packet=${POST_PACKET} --name=$POOR)
|
||||||
|
txSucceeded $? "$TX" "post packet from chain1 on chain 2"
|
||||||
|
|
||||||
|
# TODO: more queries on stuff...
|
||||||
|
|
||||||
|
# look, we wrote a packet
|
||||||
|
PACKETS=$(${CLIENT_EXE} query ibc packets --from=$CHAIN_ID_1)
|
||||||
|
assertTrue "line=${LINENO}, packets query" $?
|
||||||
|
assertEquals "line=${LINENO}, packet count" 1 $(echo $PACKETS | jq .data)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# XXX Ex Usage: assertNewHeight $MSG $SEED_1 $SEED_2
|
||||||
|
# Desc: Asserts that seed2 has a higher block height than seed 1
|
||||||
|
assertNewHeight() {
|
||||||
|
H1=$(cat $2 | jq .checkpoint.header.height)
|
||||||
|
H2=$(cat $3 | jq .checkpoint.header.height)
|
||||||
|
assertTrue "$MSG" "test $H2 -gt $H1"
|
||||||
return $?
|
return $?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# test01SendIBCTx() {
|
||||||
|
# # Trigger a cross-chain sendTx... from RICH on chain1 to POOR on chain2
|
||||||
|
# # we make sure the money was reduced, but nothing arrived
|
||||||
|
# SENDER=$(BC_HOME=${CLIENT_1} getAddr $RICH)
|
||||||
|
# RECV=$(BC_HOME=${CLIENT_2} getAddr $POOR)
|
||||||
|
|
||||||
|
# export BC_HOME=${CLIENT_1}
|
||||||
|
# TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=20002mycoin \
|
||||||
|
# --sequence=1 --to=${CHAIN_ID_2}/${RECV} --name=$RICH)
|
||||||
|
# txSucceeded $? "$TX" "${CHAIN_ID_2}/${RECV}"
|
||||||
|
# # an example to quit early if there is no point in more tests
|
||||||
|
# if [ $? != 0 ]; then echo "aborting!"; return 1; fi
|
||||||
|
|
||||||
|
# HASH=$(echo $TX | jq .hash | tr -d \")
|
||||||
|
# TX_HEIGHT=$(echo $TX | jq .height)
|
||||||
|
|
||||||
|
# # Make sure balance went down and tx is indexed
|
||||||
|
# checkAccount $SENDER "1" "9007199254720990"
|
||||||
|
# checkSendTx $HASH $TX_HEIGHT $SENDER "20002"
|
||||||
|
|
||||||
|
# # Make sure nothing arrived - yet
|
||||||
|
# waitForBlock ${PORT_1}
|
||||||
|
# assertFalse "line=${LINENO}, no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV"
|
||||||
|
|
||||||
|
# # Start the relay and wait a few blocks...
|
||||||
|
# # (already sent a tx on chain1, so use higher sequence)
|
||||||
|
# startRelay 2 1
|
||||||
|
# if [ $? != 0 ]; then echo "can't start relay"; cat ${BASE_DIR_1}/../relay.log; return 1; fi
|
||||||
|
|
||||||
|
# # Give it a little time, then make sure the money arrived
|
||||||
|
# echo "waiting for relay..."
|
||||||
|
# sleep 1
|
||||||
|
# waitForBlock ${PORT_1}
|
||||||
|
# waitForBlock ${PORT_2}
|
||||||
|
|
||||||
|
# # Check the new account
|
||||||
|
# echo "checking ibc recipient..."
|
||||||
|
# BC_HOME=${CLIENT_2} checkAccount $RECV "0" "20002"
|
||||||
|
|
||||||
|
# # Stop relay
|
||||||
|
# printf "stoping relay\n"
|
||||||
|
# kill -9 $PID_RELAY
|
||||||
|
# }
|
||||||
|
|
||||||
|
# # StartRelay $seq1 $seq2
|
||||||
|
# # startRelay hooks up a relay between chain1 and chain2
|
||||||
|
# # it needs the proper sequence number for $RICH on chain1 and chain2 as args
|
||||||
|
# startRelay() {
|
||||||
|
# # Send some cash to the default key, so it can send messages
|
||||||
|
# RELAY_KEY=${BASE_DIR_1}/server/key.json
|
||||||
|
# RELAY_ADDR=$(cat $RELAY_KEY | jq .address | tr -d \")
|
||||||
|
# echo starting relay $PID_RELAY ...
|
||||||
|
|
||||||
|
# # Get paid on chain1
|
||||||
|
# export BC_HOME=${CLIENT_1}
|
||||||
|
# SENDER=$(getAddr $RICH)
|
||||||
|
# RES=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=100000mycoin \
|
||||||
|
# --sequence=$1 --to=$RELAY_ADDR --name=$RICH)
|
||||||
|
# txSucceeded $? "$RES" "$RELAY_ADDR"
|
||||||
|
# if [ $? != 0 ]; then echo "can't pay chain1!"; return 1; fi
|
||||||
|
|
||||||
|
# # Get paid on chain2
|
||||||
|
# export BC_HOME=${CLIENT_2}
|
||||||
|
# SENDER=$(getAddr $RICH)
|
||||||
|
# RES=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=100000mycoin \
|
||||||
|
# --sequence=$2 --to=$RELAY_ADDR --name=$RICH)
|
||||||
|
# txSucceeded $? "$RES" "$RELAY_ADDR"
|
||||||
|
# if [ $? != 0 ]; then echo "can't pay chain2!"; return 1; fi
|
||||||
|
|
||||||
|
# # Initialize the relay (register both chains)
|
||||||
|
# ${SERVER_EXE} relay init --chain1-id=$CHAIN_ID_1 --chain2-id=$CHAIN_ID_2 \
|
||||||
|
# --chain1-addr=tcp://localhost:${PORT_1} --chain2-addr=tcp://localhost:${PORT_2} \
|
||||||
|
# --genesis1=${BASE_DIR_1}/server/genesis.json --genesis2=${BASE_DIR_2}/server/genesis.json \
|
||||||
|
# --from=$RELAY_KEY > ${BASE_DIR_1}/../relay.log
|
||||||
|
# if [ $? != 0 ]; then echo "can't initialize relays"; cat ${BASE_DIR_1}/../relay.log; return 1; fi
|
||||||
|
|
||||||
|
# # Now start the relay (constantly send packets)
|
||||||
|
# ${SERVER_EXE} relay start --chain1-id=$CHAIN_ID_1 --chain2-id=$CHAIN_ID_2 \
|
||||||
|
# --chain1-addr=tcp://localhost:${PORT_1} --chain2-addr=tcp://localhost:${PORT_2} \
|
||||||
|
# --from=$RELAY_KEY >> ${BASE_DIR_1}/../relay.log &
|
||||||
|
# sleep 2
|
||||||
|
# PID_RELAY=$!
|
||||||
|
# disown
|
||||||
|
|
||||||
|
# # Return an error if it dies in the first two seconds to make sure it is running
|
||||||
|
# ps $PID_RELAY >/dev/null
|
||||||
|
# return $?
|
||||||
|
# }
|
||||||
|
|
||||||
# Load common then run these tests with shunit2!
|
# Load common then run these tests with shunit2!
|
||||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||||
. $DIR/common.sh
|
. $DIR/common.sh
|
||||||
|
|
|
@ -98,7 +98,7 @@ test02GetSecure() {
|
||||||
# assertFalse "missing height" "${CLIENT_EXE} rpc headers"
|
# assertFalse "missing height" "${CLIENT_EXE} rpc headers"
|
||||||
HEADERS=$(${CLIENT_EXE} rpc headers --min=$CHEIGHT --max=$HEIGHT)
|
HEADERS=$(${CLIENT_EXE} rpc headers --min=$CHEIGHT --max=$HEIGHT)
|
||||||
assertTrue "line=${LINENO}, get headers" "$?"
|
assertTrue "line=${LINENO}, get headers" "$?"
|
||||||
assertEquals "line=${LINENO}, proper height" "$HEIGHT" $(echo $HEADERS | jq '.last_height')
|
assertEquals "line=${LINENO}, proper height" "$HEIGHT" $(echo $HEADERS | jq '.block_metas[0].header.height')
|
||||||
assertEquals "line=${LINENO}, two headers" "2" $(echo $HEADERS | jq '.block_metas | length')
|
assertEquals "line=${LINENO}, two headers" "2" $(echo $HEADERS | jq '.block_metas | length')
|
||||||
# should we check these headers?
|
# should we check these headers?
|
||||||
CHEAD=$(echo $COMMIT | jq .header)
|
CHEAD=$(echo $COMMIT | jq .header)
|
||||||
|
|
15
tx.go
15
tx.go
|
@ -1,6 +1,8 @@
|
||||||
package basecoin
|
package basecoin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/tendermint/go-wire/data"
|
"github.com/tendermint/go-wire/data"
|
||||||
|
|
||||||
"github.com/tendermint/basecoin/errors"
|
"github.com/tendermint/basecoin/errors"
|
||||||
|
@ -75,3 +77,16 @@ func (t Tx) GetKind() (string, error) {
|
||||||
// grab the type we used in json
|
// grab the type we used in json
|
||||||
return text.Kind, nil
|
return text.Kind, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t Tx) GetMod() (string, error) {
|
||||||
|
kind, err := t.GetKind()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(kind, "/", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
// TODO: return "base"?
|
||||||
|
return "", errors.ErrUnknownTxType(t)
|
||||||
|
}
|
||||||
|
return parts[0], nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue