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/counter.sh
|
||||
./tests/cli/restart.sh
|
||||
# @./tests/cli/ibc.sh
|
||||
./tests/cli/ibc.sh
|
||||
|
||||
test_tutorial: docs/guide/shunit2
|
||||
@shelldown ${TUTORIALS}
|
||||
|
|
28
app/app.go
28
app/app.go
|
@ -10,12 +10,6 @@ import (
|
|||
|
||||
"github.com/tendermint/basecoin"
|
||||
"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"
|
||||
sm "github.com/tendermint/basecoin/state"
|
||||
"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
|
||||
func (app *Basecoin) GetChainID() string {
|
||||
return app.info.GetChainID(app.state.Committed())
|
||||
|
|
|
@ -13,13 +13,42 @@ import (
|
|||
"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"
|
||||
"github.com/tendermint/basecoin/state"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
"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
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ func DefaultHandler(feeDenom string) basecoin.Handler {
|
|||
c := coin.NewHandler()
|
||||
r := roles.NewHandler()
|
||||
d := stack.NewDispatcher(
|
||||
stack.WrapHandler(c),
|
||||
c,
|
||||
stack.WrapHandler(r),
|
||||
)
|
||||
return stack.New(
|
||||
|
|
|
@ -79,7 +79,7 @@ func GetCertifier() (*certifiers.InquiringCertifier, error) {
|
|||
return nil, err
|
||||
}
|
||||
cert := certifiers.NewInquiring(
|
||||
viper.GetString(ChainFlag), seed.Validators, trust, source)
|
||||
viper.GetString(ChainFlag), seed, trust, source)
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
package seeds
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/basecoin/client/commands"
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
)
|
||||
|
||||
var exportCmd = &cobra.Command{
|
||||
|
@ -40,5 +44,16 @@ func exportSeed(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
// 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"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
|
||||
|
@ -12,12 +13,13 @@ import (
|
|||
|
||||
var updateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Update seed to current chain state if possible",
|
||||
Short: "Update seed to current height if possible",
|
||||
RunE: commands.RequireInit(updateSeed),
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
updateCmd.Flags().Int(heightFlag, 0, "Update to this height, not latest")
|
||||
RootCmd.AddCommand(updateCmd)
|
||||
}
|
||||
|
||||
|
@ -27,14 +29,20 @@ func updateSeed(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// get the lastest from our source
|
||||
seed, err := certifiers.LatestSeed(cert.SeedSource)
|
||||
h := viper.GetInt(heightFlag)
|
||||
var seed certifiers.Seed
|
||||
if h <= 0 {
|
||||
// get the lastest from our source
|
||||
seed, err = certifiers.LatestSeed(cert.SeedSource)
|
||||
} else {
|
||||
seed, err = cert.SeedSource.GetByHeight(h)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Trying to update to height: %d...\n", seed.Height())
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
basecmd "github.com/tendermint/basecoin/modules/base/commands"
|
||||
coincmd "github.com/tendermint/basecoin/modules/coin/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"
|
||||
rolecmd "github.com/tendermint/basecoin/modules/roles/commands"
|
||||
)
|
||||
|
@ -46,6 +47,7 @@ func main() {
|
|||
coincmd.AccountQueryCmd,
|
||||
noncecmd.NonceQueryCmd,
|
||||
rolecmd.RoleQueryCmd,
|
||||
ibccmd.IBCQueryCmd,
|
||||
)
|
||||
proofs.TxPresenters.Register("base", txcmd.BaseTxPresenter{})
|
||||
|
||||
|
@ -63,8 +65,13 @@ func main() {
|
|||
txcmd.RootCmd.AddCommand(
|
||||
// This is the default transaction, optional in your app
|
||||
coincmd.SendTxCmd,
|
||||
coincmd.CreditTxCmd,
|
||||
// this enables creating roles
|
||||
rolecmd.CreateRoleTxCmd,
|
||||
// these are for handling ibc
|
||||
ibccmd.RegisterChainTxCmd,
|
||||
ibccmd.UpdateChainTxCmd,
|
||||
ibccmd.PostPacketTxCmd,
|
||||
)
|
||||
|
||||
// Set up the various commands to use
|
||||
|
|
|
@ -120,7 +120,10 @@ func GetGenesisJSON(chainID, addr string) string {
|
|||
"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/basecoin/app"
|
||||
"github.com/tendermint/basecoin"
|
||||
"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() {
|
||||
rt := commands.RootCmd
|
||||
|
||||
// require all fees in mycoin - change this in your app!
|
||||
commands.Handler = app.DefaultHandler("mycoin")
|
||||
commands.Handler = BuildApp("mycoin")
|
||||
|
||||
rt.AddCommand(
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// rely on private fields to control the actions
|
||||
type Context interface {
|
||||
|
|
|
@ -12,7 +12,9 @@ import (
|
|||
"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"
|
||||
"github.com/tendermint/basecoin/state"
|
||||
)
|
||||
|
@ -90,13 +92,6 @@ func ErrDecoding() error {
|
|||
|
||||
// NewHandler returns a new counter transaction processing 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(
|
||||
base.Logger{},
|
||||
stack.Recovery{},
|
||||
|
@ -104,9 +99,17 @@ func NewHandler(feeDenom string) basecoin.Handler {
|
|||
base.Chain{},
|
||||
stack.Checkpoint{OnCheck: true},
|
||||
nonce.ReplayCheck{},
|
||||
fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
|
||||
stack.Checkpoint{OnDeliver: true},
|
||||
).Use(dispatcher)
|
||||
).
|
||||
IBC(ibc.NewMiddleware()).
|
||||
Apps(
|
||||
roles.NewMiddleware(),
|
||||
fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
|
||||
stack.Checkpoint{OnDeliver: true},
|
||||
).
|
||||
Dispatch(
|
||||
coin.NewHandler(),
|
||||
Handler{},
|
||||
)
|
||||
}
|
||||
|
||||
// Handler the counter transaction processing handler
|
||||
|
|
|
@ -16,6 +16,7 @@ var (
|
|||
errUnknownTxType = fmt.Errorf("Tx type unknown")
|
||||
errInvalidFormat = fmt.Errorf("Invalid format")
|
||||
errUnknownModule = fmt.Errorf("Unknown module")
|
||||
errUnknownKey = fmt.Errorf("Unknown key")
|
||||
|
||||
internalErr = abci.CodeType_InternalError
|
||||
encodingErr = abci.CodeType_EncodingError
|
||||
|
@ -39,7 +40,7 @@ func unwrap(i interface{}) interface{} {
|
|||
|
||||
func ErrUnknownTxType(tx interface{}) TMError {
|
||||
msg := fmt.Sprintf("%T", unwrap(tx))
|
||||
return WithMessage(msg, errUnknownTxType, abci.CodeType_UnknownRequest)
|
||||
return WithMessage(msg, errUnknownTxType, unknownRequest)
|
||||
}
|
||||
func IsUnknownTxTypeErr(err error) bool {
|
||||
return IsSameError(errUnknownTxType, err)
|
||||
|
@ -47,19 +48,26 @@ func IsUnknownTxTypeErr(err error) bool {
|
|||
|
||||
func ErrInvalidFormat(expected string, tx interface{}) TMError {
|
||||
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 {
|
||||
return IsSameError(errInvalidFormat, err)
|
||||
}
|
||||
|
||||
func ErrUnknownModule(mod string) TMError {
|
||||
return WithMessage(mod, errUnknownModule, abci.CodeType_UnknownRequest)
|
||||
return WithMessage(mod, errUnknownModule, unknownRequest)
|
||||
}
|
||||
func IsUnknownModuleErr(err error) bool {
|
||||
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 {
|
||||
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
|
||||
updated: 2017-07-26T19:44:39.753066441-04:00
|
||||
hash: 2848c30b31fb205f846dd7dfca14ebed8a3249cbc5aaa759066b2bab3e4bbf42
|
||||
updated: 2017-07-27T16:46:31.962147949-04:00
|
||||
imports:
|
||||
- name: github.com/bgentry/speakeasy
|
||||
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
|
||||
|
@ -57,6 +57,8 @@ imports:
|
|||
- json/parser
|
||||
- json/scanner
|
||||
- json/token
|
||||
- name: github.com/howeyc/crc16
|
||||
version: 96a97a1abb579c7ff1a8ffa77f2e72d1c314b57f
|
||||
- name: github.com/inconshreveable/mousetrap
|
||||
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||
- name: github.com/jmhodges/levigo
|
||||
|
@ -117,7 +119,7 @@ imports:
|
|||
- edwards25519
|
||||
- extra25519
|
||||
- name: github.com/tendermint/go-crypto
|
||||
version: d31cfbaeaa4d930798ec327b52917975f3203c11
|
||||
version: bf355d1b58b27d4e98d8fb237eb14887b93a88f7
|
||||
subpackages:
|
||||
- cmd
|
||||
- keys
|
||||
|
@ -133,7 +135,7 @@ imports:
|
|||
- data
|
||||
- data/base58
|
||||
- name: github.com/tendermint/light-client
|
||||
version: 1c53d04dcc65c2fd15526152ed0651af10a09982
|
||||
version: fcf4e411583135a1900157b8b0274c41e20ea3a1
|
||||
subpackages:
|
||||
- certifiers
|
||||
- certifiers/client
|
||||
|
|
|
@ -22,7 +22,7 @@ import:
|
|||
subpackages:
|
||||
- data
|
||||
- package: github.com/tendermint/light-client
|
||||
version: 1c53d04dcc65c2fd15526152ed0651af10a09982
|
||||
version: unstable
|
||||
subpackages:
|
||||
- proofs
|
||||
- certifiers
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/tendermint/basecoin/state"
|
||||
)
|
||||
|
||||
func makeHandler() basecoin.Handler {
|
||||
func makeHandler() stack.Dispatchable {
|
||||
return NewHandler()
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ func BenchmarkSimpleTransfer(b *testing.B) {
|
|||
|
||||
// set the initial account
|
||||
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()
|
||||
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++ {
|
||||
ctx := stack.MockContext("foo", 100).WithPermissions(sender)
|
||||
tx := makeSimpleTx(sender, receiver, Coins{{"mycoin", 2}})
|
||||
_, err := h.DeliverTx(ctx, store, tx)
|
||||
_, err := h.DeliverTx(ctx, store, tx, nil)
|
||||
// never should error
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -28,6 +28,7 @@ func accountQueryCmd(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
act = coin.ChainAddr(act)
|
||||
key := stack.PrefixedKey(coin.NameCoin, act.Bytes())
|
||||
|
||||
acc := coin.Account{}
|
||||
|
|
|
@ -17,6 +17,13 @@ var SendTxCmd = &cobra.Command{
|
|||
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
|
||||
const (
|
||||
FlagTo = "to"
|
||||
|
@ -29,9 +36,12 @@ func init() {
|
|||
flags.String(FlagTo, "", "Destination address for the bits")
|
||||
flags.String(FlagAmount, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
|
||||
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 {
|
||||
tx, err := readSendTxFlags()
|
||||
if err != nil {
|
||||
|
@ -62,6 +72,30 @@ func readSendTxFlags() (tx basecoin.Tx, err error) {
|
|||
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) {
|
||||
from := viper.GetString(FlagFrom)
|
||||
if from == "" {
|
||||
|
|
|
@ -10,13 +10,13 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
errNoAccount = fmt.Errorf("No such account")
|
||||
errInsufficientFunds = fmt.Errorf("Insufficient funds")
|
||||
errNoInputs = fmt.Errorf("No input coins")
|
||||
errNoOutputs = fmt.Errorf("No output coins")
|
||||
errInvalidAddress = fmt.Errorf("Invalid address")
|
||||
errInvalidCoins = fmt.Errorf("Invalid coins")
|
||||
errUnknownKey = fmt.Errorf("Unknown key")
|
||||
errNoAccount = fmt.Errorf("No such account")
|
||||
errInsufficientFunds = fmt.Errorf("Insufficient funds")
|
||||
errInsufficientCredit = fmt.Errorf("Insufficient credit")
|
||||
errNoInputs = fmt.Errorf("No input coins")
|
||||
errNoOutputs = fmt.Errorf("No output coins")
|
||||
errInvalidAddress = fmt.Errorf("Invalid address")
|
||||
errInvalidCoins = fmt.Errorf("Invalid coins")
|
||||
|
||||
invalidInput = abci.CodeType_BaseInvalidInput
|
||||
invalidOutput = abci.CodeType_BaseInvalidOutput
|
||||
|
@ -67,6 +67,13 @@ func IsInsufficientFundsErr(err error) bool {
|
|||
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 {
|
||||
return errors.WithCode(errNoInputs, invalidInput)
|
||||
}
|
||||
|
@ -80,10 +87,3 @@ func ErrNoOutputs() errors.TMError {
|
|||
func IsNoOutputsErr(err error) bool {
|
||||
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/errors"
|
||||
"github.com/tendermint/basecoin/modules/auth"
|
||||
"github.com/tendermint/basecoin/modules/ibc"
|
||||
"github.com/tendermint/basecoin/stack"
|
||||
"github.com/tendermint/basecoin/state"
|
||||
)
|
||||
|
||||
|
@ -16,7 +18,7 @@ const NameCoin = "coin"
|
|||
// Handler includes an accountant
|
||||
type Handler struct{}
|
||||
|
||||
var _ basecoin.Handler = Handler{}
|
||||
var _ stack.Dispatchable = Handler{}
|
||||
|
||||
// NewHandler - new accountant handler for the coin module
|
||||
func NewHandler() Handler {
|
||||
|
@ -28,96 +30,207 @@ func (Handler) Name() string {
|
|||
return NameCoin
|
||||
}
|
||||
|
||||
// AssertDispatcher - to fulfill Dispatchable interface
|
||||
func (Handler) AssertDispatcher() {}
|
||||
|
||||
// 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) {
|
||||
send, err := checkTx(ctx, tx)
|
||||
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB,
|
||||
tx basecoin.Tx, _ basecoin.Checker) (res basecoin.Result, err error) {
|
||||
|
||||
err = tx.ValidateBasic()
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// now make sure there is money
|
||||
for _, in := range send.Inputs {
|
||||
_, err = CheckCoins(store, in.Address, in.Coins.Negative())
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
switch t := tx.Unwrap().(type) {
|
||||
case SendTx:
|
||||
return res, h.checkSendTx(ctx, store, t)
|
||||
case CreditTx:
|
||||
return h.creditTx(ctx, store, t)
|
||||
}
|
||||
|
||||
// otherwise, we are good
|
||||
return res, nil
|
||||
return res, errors.ErrUnknownTxType(tx.Unwrap())
|
||||
}
|
||||
|
||||
// DeliverTx moves the money
|
||||
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
send, err := checkTx(ctx, tx)
|
||||
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
|
||||
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 {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// deduct from all input accounts
|
||||
senders := basecoin.Actors{}
|
||||
for _, in := range send.Inputs {
|
||||
_, err = ChangeCoins(store, in.Address, in.Coins.Negative())
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
senders = append(senders, in.Address)
|
||||
}
|
||||
|
||||
// add to all output accounts
|
||||
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)
|
||||
if err != nil {
|
||||
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!
|
||||
return basecoin.Result{}, nil
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SetOption - sets the genesis account balance
|
||||
func (h Handler) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (log string, err error) {
|
||||
if module != NameCoin {
|
||||
return "", errors.ErrUnknownModule(module)
|
||||
}
|
||||
if key == "account" {
|
||||
var acc GenesisAccount
|
||||
err = data.FromJSON([]byte(value), &acc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
acc.Balance.Sort()
|
||||
addr, err := acc.GetAddr()
|
||||
if err != nil {
|
||||
return "", ErrInvalidAddress()
|
||||
}
|
||||
// this sets the permission for a public key signature, use that app
|
||||
actor := auth.SigPerm(addr)
|
||||
err = storeAccount(store, actor.Bytes(), acc.ToAccount())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "Success", nil
|
||||
func (h Handler) creditTx(ctx basecoin.Context, store state.SimpleDB,
|
||||
credit CreditTx) (res basecoin.Result, err error) {
|
||||
|
||||
}
|
||||
return "", ErrUnknownKey(key)
|
||||
}
|
||||
|
||||
func checkTx(ctx basecoin.Context, tx basecoin.Tx) (send SendTx, err error) {
|
||||
// check if the tx is proper type and valid
|
||||
send, ok := tx.Unwrap().(SendTx)
|
||||
if !ok {
|
||||
return send, errors.ErrInvalidFormat(TypeSend, tx)
|
||||
}
|
||||
err = send.ValidateBasic()
|
||||
// first check permissions!!
|
||||
info, err := loadHandlerInfo(store)
|
||||
if err != nil {
|
||||
return send, err
|
||||
return res, err
|
||||
}
|
||||
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 send, errors.ErrUnauthorized()
|
||||
return errors.ErrUnauthorized()
|
||||
}
|
||||
}
|
||||
return send, nil
|
||||
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
|
||||
err = data.FromJSON([]byte(value), &acc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
acc.Balance.Sort()
|
||||
addr, err := acc.GetAddr()
|
||||
if err != nil {
|
||||
return "", ErrInvalidAddress()
|
||||
}
|
||||
// this sets the permission for a public key signature, use that app
|
||||
actor := auth.SigPerm(addr)
|
||||
err = storeAccount(store, actor.Bytes(), acc.ToAccount())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "Success", nil
|
||||
}
|
||||
|
||||
// setIssuer sets a permission for some super-powerful account to
|
||||
// mint money
|
||||
func setIssuer(store state.SimpleDB, value string) (log string, err error) {
|
||||
var issuer basecoin.Actor
|
||||
err = data.FromJSON([]byte(value), &issuer)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = storeIssuer(store, issuer)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "Success", nil
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
"github.com/tendermint/basecoin"
|
||||
"github.com/tendermint/basecoin/errors"
|
||||
"github.com/tendermint/basecoin/modules/auth"
|
||||
"github.com/tendermint/basecoin/stack"
|
||||
"github.com/tendermint/basecoin/state"
|
||||
|
@ -75,7 +76,7 @@ func TestHandlerValidation(t *testing.T) {
|
|||
|
||||
for i, tc := range cases {
|
||||
ctx := stack.MockContext("base-chain", 100).WithPermissions(tc.perms...)
|
||||
_, err := checkTx(ctx, tc.tx)
|
||||
err := checkTx(ctx, tc.tx.Unwrap().(SendTx))
|
||||
if tc.valid {
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
} else {
|
||||
|
@ -84,7 +85,7 @@ func TestHandlerValidation(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDeliverTx(t *testing.T) {
|
||||
func TestDeliverSendTx(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
|
@ -149,7 +150,7 @@ func TestDeliverTx(t *testing.T) {
|
|||
}
|
||||
|
||||
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
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
// make sure the final balances are correct
|
||||
|
@ -204,7 +205,7 @@ func TestSetOption(t *testing.T) {
|
|||
for j, gen := range tc.init {
|
||||
value, err := json.Marshal(gen)
|
||||
require.Nil(err, "%d,%d: %+v", i, j, err)
|
||||
_, err = h.SetOption(l, store, NameCoin, key, string(value))
|
||||
_, err = h.SetOption(l, store, NameCoin, key, string(value), nil)
|
||||
require.Nil(err)
|
||||
}
|
||||
|
||||
|
@ -215,5 +216,141 @@ func TestSetOption(t *testing.T) {
|
|||
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
|
||||
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())
|
||||
|
||||
// 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
|
||||
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)
|
||||
return acct.Coins, err
|
||||
}
|
||||
|
||||
// ChangeCoins changes the money, returns error if it would be negative
|
||||
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)
|
||||
if err != nil {
|
||||
return acct.Coins, err
|
||||
|
@ -38,6 +46,19 @@ func ChangeCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (Coins,
|
|||
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.
|
||||
//
|
||||
// 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
|
||||
type Account struct {
|
||||
// Coins is how much is on the account
|
||||
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) {
|
||||
|
@ -86,3 +111,35 @@ func storeAccount(store state.SimpleDB, key []byte, acct Account) error {
|
|||
store.Set(key, bin)
|
||||
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() {
|
||||
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
|
||||
const (
|
||||
ByteSend = 0x20
|
||||
TypeSend = NameCoin + "/send"
|
||||
ByteSend = 0x20
|
||||
TypeSend = NameCoin + "/send"
|
||||
ByteCredit = 0x21
|
||||
TypeCredit = NameCoin + "/credit"
|
||||
)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -157,3 +161,30 @@ func (tx SendTx) String() string {
|
|||
func (tx SendTx) Wrap() basecoin.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
|
||||
stack.WrapHandler(stack.OKHandler{}),
|
||||
// coin is needed to handle the IPC call from Fee middleware
|
||||
stack.WrapHandler(coin.NewHandler()),
|
||||
coin.NewHandler(),
|
||||
)
|
||||
// app1 requires no fees
|
||||
app1 := stack.New(fee.NewSimpleFeeMiddleware(atom(0), collector)).Use(disp)
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
// nolint
|
||||
const (
|
||||
ByteFees = 0x21
|
||||
ByteFees = 0x28
|
||||
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
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"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"
|
||||
"github.com/tendermint/basecoin/modules/roles"
|
||||
"github.com/tendermint/basecoin/stack"
|
||||
|
@ -14,17 +13,15 @@ import (
|
|||
var RoleQueryCmd = &cobra.Command{
|
||||
Use: "role [name]",
|
||||
Short: "Get details of a role, with proof",
|
||||
RunE: lcmd.RequireInit(roleQueryCmd),
|
||||
RunE: commands.RequireInit(roleQueryCmd),
|
||||
}
|
||||
|
||||
func roleQueryCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("Missing required argument [name]")
|
||||
} else if len(args) > 1 {
|
||||
return errors.New("Command only supports one name")
|
||||
arg, err := commands.GetOneArg(args, "name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
role, err := parseRole(args[0])
|
||||
role, err := parseRole(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"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{
|
||||
Use: "create-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")
|
||||
}
|
||||
|
||||
// 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 {
|
||||
tx, err := readCreateRoleTxFlags()
|
||||
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 {
|
||||
app string
|
||||
ibc bool
|
||||
// this exposes the log.Logger and all other methods we don't override
|
||||
naiveContext
|
||||
}
|
||||
|
||||
// NewContext - create a new secureContext
|
||||
func NewContext(chain string, height uint64, logger log.Logger) basecoin.Context {
|
||||
mock := MockContext(chain, height).(naiveContext)
|
||||
mock.Logger = logger
|
||||
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 {
|
||||
// the guard makes sure you only set permissions for the app you are inside
|
||||
for _, p := range perms {
|
||||
// TODO: also check chainID, limit only certain middleware can set IBC?
|
||||
if p.App != c.app {
|
||||
err := errors.Errorf("Cannot set permission for %s from %s", c.app, p.App)
|
||||
if !c.validPermisison(p) {
|
||||
err := errors.Errorf("Cannot set permission for %s/%s on (app=%s, ibc=%b)",
|
||||
p.ChainID, p.App, c.app, c.ibc)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return secureContext{
|
||||
app: c.app,
|
||||
ibc: c.ibc,
|
||||
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,
|
||||
// but carry on knowledge that this is a child
|
||||
func (c secureContext) Reset() basecoin.Context {
|
||||
return secureContext{
|
||||
app: c.app,
|
||||
ibc: c.ibc,
|
||||
naiveContext: c.naiveContext.Reset().(naiveContext),
|
||||
}
|
||||
}
|
||||
|
@ -71,6 +85,20 @@ func withApp(ctx basecoin.Context, app string) basecoin.Context {
|
|||
}
|
||||
return secureContext{
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ import (
|
|||
// heavily inspired by negroni's design
|
||||
type middleware struct {
|
||||
middleware Middleware
|
||||
space string
|
||||
allowIBC bool
|
||||
next basecoin.Handler
|
||||
}
|
||||
|
||||
|
@ -21,13 +23,20 @@ func (m *middleware) Name() string {
|
|||
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
|
||||
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
|
||||
next := secureCheck(m.next, ctx)
|
||||
// set the permissions for this app
|
||||
ctx = withApp(ctx, m.Name())
|
||||
store = stateSpace(store, m.Name())
|
||||
ctx = m.wrapCtx(ctx)
|
||||
store = stateSpace(store, m.space)
|
||||
|
||||
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
|
||||
next := secureDeliver(m.next, ctx)
|
||||
// set the permissions for this app
|
||||
ctx = withApp(ctx, m.Name())
|
||||
store = stateSpace(store, m.Name())
|
||||
ctx = m.wrapCtx(ctx)
|
||||
store = stateSpace(store, m.space)
|
||||
|
||||
return m.middleware.DeliverTx(ctx, store, tx, next)
|
||||
}
|
||||
|
||||
func (m *middleware) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) {
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
type Stack struct {
|
||||
middles []Middleware
|
||||
middles []builder
|
||||
handler basecoin.Handler
|
||||
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
|
||||
// before you can execute it.
|
||||
func New(middlewares ...Middleware) *Stack {
|
||||
return &Stack{
|
||||
middles: middlewares,
|
||||
stack := new(Stack)
|
||||
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
|
||||
|
@ -77,10 +129,17 @@ func (s *Stack) Use(handler basecoin.Handler) *Stack {
|
|||
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 {
|
||||
return 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
|
||||
grantee := basecoin.Actor{App: NameGrant, Address: []byte{1}}
|
||||
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 {
|
||||
asIBC bool
|
||||
grant basecoin.Actor
|
||||
require basecoin.Actor
|
||||
expectedRes data.Bytes
|
||||
expected func(error) bool
|
||||
}{
|
||||
{grantee, grantee, rawBytes, nil},
|
||||
{grantee, grantee2, nil, errors.IsUnauthorizedErr},
|
||||
{grantee, signer, nil, errors.IsUnauthorizedErr},
|
||||
{signer, signer, nil, errors.IsInternalErr},
|
||||
// grant as normal app middleware
|
||||
{false, grantee, grantee, rawBytes, nil},
|
||||
{false, grantee, grantee2, nil, errors.IsUnauthorizedErr},
|
||||
{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 {
|
||||
app := New(
|
||||
Recovery{}, // we need this so panics turn to errors
|
||||
GrantMiddleware{Auth: tc.grant},
|
||||
CheckMiddleware{Required: tc.require},
|
||||
).Use(EchoHandler{})
|
||||
app := New(Recovery{})
|
||||
if tc.asIBC {
|
||||
app = app.IBC(GrantMiddleware{Auth: tc.grant})
|
||||
} else {
|
||||
app = app.Apps(GrantMiddleware{Auth: tc.grant})
|
||||
}
|
||||
app = app.
|
||||
Apps(CheckMiddleware{Required: tc.require}).
|
||||
Use(EchoHandler{})
|
||||
|
||||
res, err := app.CheckTx(ctx, store, raw)
|
||||
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!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
. $DIR/common.sh
|
||||
|
|
327
tests/cli/ibc.sh
327
tests/cli/ibc.sh
|
@ -9,8 +9,8 @@ ACCOUNTS=(jae ethan bucky rigel igor)
|
|||
RICH=${ACCOUNTS[0]}
|
||||
POOR=${ACCOUNTS[4]}
|
||||
|
||||
# Uncomment the following line for full stack traces in error output
|
||||
# CLIENT_EXE="basecli --trace"
|
||||
# For full stack traces in error output, run
|
||||
# BC_TRACE=1 ./ibc.sh
|
||||
|
||||
oneTimeSetUp() {
|
||||
# These are passed in as args
|
||||
|
@ -62,13 +62,13 @@ oneTimeTearDown() {
|
|||
}
|
||||
|
||||
test00GetAccount() {
|
||||
SENDER_1=$(BC_HOME=${CLIENT_1} getAddr $RICH)
|
||||
RECV_1=$(BC_HOME=${CLIENT_1} getAddr $POOR)
|
||||
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}, 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}
|
||||
SENDER_2=$(getAddr $RICH)
|
||||
|
@ -76,102 +76,281 @@ test00GetAccount() {
|
|||
|
||||
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"
|
||||
checkAccount $SENDER_2 "0" "9007199254740992"
|
||||
checkAccount $SENDER_2 "9007199254740992"
|
||||
|
||||
# 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}, recipient keys must be different" "$RECV_1" "$RECV_2"
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
# we make sure the money was reduced, but nothing arrived
|
||||
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)
|
||||
|
||||
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
|
||||
--to=${CHAIN_ID_2}::${RECV} --name=$RICH)
|
||||
txSucceeded $? "$TX" "${CHAIN_ID_2}::${RECV}"
|
||||
# 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"
|
||||
checkAccount $SENDER "9007199254720990"
|
||||
checkSendTx $HASH $TX_HEIGHT $SENDER "20002"
|
||||
|
||||
# Make sure nothing arrived - yet
|
||||
waitForBlock ${PORT_1}
|
||||
# look, we wrote a packet
|
||||
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"
|
||||
|
||||
# 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
|
||||
test05ReceiveIBCPacket() {
|
||||
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
|
||||
# make some credit, so we can accept the packet
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx credit --amount=60006mycoin --to=$CHAIN_ID_1:: --name=$RICH)
|
||||
txSucceeded $? "$TX" "${CHAIN_ID_1}::"
|
||||
checkAccount $CHAIN_ID_1:: "60006"
|
||||
|
||||
# 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
|
||||
# now, we try to post it.... (this is PACKET from last test)
|
||||
|
||||
# Return an error if it dies in the first two seconds to make sure it is running
|
||||
ps $PID_RELAY >/dev/null
|
||||
# get the seed and post it
|
||||
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 $?
|
||||
}
|
||||
|
||||
# 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!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
. $DIR/common.sh
|
||||
|
|
|
@ -98,7 +98,7 @@ test02GetSecure() {
|
|||
# assertFalse "missing height" "${CLIENT_EXE} rpc headers"
|
||||
HEADERS=$(${CLIENT_EXE} rpc headers --min=$CHEIGHT --max=$HEIGHT)
|
||||
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')
|
||||
# should we check these headers?
|
||||
CHEAD=$(echo $COMMIT | jq .header)
|
||||
|
|
15
tx.go
15
tx.go
|
@ -1,6 +1,8 @@
|
|||
package basecoin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/tendermint/go-wire/data"
|
||||
|
||||
"github.com/tendermint/basecoin/errors"
|
||||
|
@ -75,3 +77,16 @@ func (t Tx) GetKind() (string, error) {
|
|||
// grab the type we used in json
|
||||
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