508 lines
13 KiB
Go
508 lines
13 KiB
Go
package ibc
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"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/merkleeyes/iavl"
|
|
"github.com/tendermint/tmlibs/log"
|
|
|
|
"github.com/tendermint/basecoin"
|
|
"github.com/tendermint/basecoin/errors"
|
|
"github.com/tendermint/basecoin/modules/auth"
|
|
"github.com/tendermint/basecoin/modules/coin"
|
|
"github.com/tendermint/basecoin/stack"
|
|
"github.com/tendermint/basecoin/state"
|
|
)
|
|
|
|
type checkErr func(error) bool
|
|
|
|
func noErr(err error) bool {
|
|
return err == nil
|
|
}
|
|
|
|
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}
|
|
}
|
|
|
|
// 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 checkErr
|
|
}{
|
|
{
|
|
genEmptySeed(keys, "chain-1", 100, appHash, len(keys)),
|
|
noErr,
|
|
},
|
|
{
|
|
genEmptySeed(keys, "chain-1", 200, appHash, len(keys)),
|
|
IsAlreadyRegisteredErr,
|
|
},
|
|
{
|
|
badSeed,
|
|
IsInvalidCommitErr,
|
|
},
|
|
{
|
|
genEmptySeed(keys2, "chain-2", 123, appHash2, 5),
|
|
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 checkErr
|
|
}{
|
|
// no sig, no registrar
|
|
{
|
|
seed: genEmptySeed(keys, "chain-1", 100, appHash, len(keys)),
|
|
checker: noErr,
|
|
},
|
|
// sig, no registrar
|
|
{
|
|
seed: genEmptySeed(keys, "chain-2", 100, appHash, len(keys)),
|
|
signer: foobaz,
|
|
checker: 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: 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 checkErr
|
|
}{
|
|
// same validator, higher up
|
|
{
|
|
genEmptySeed(keys, "chain-1", start+50, []byte{22}, len(keys)),
|
|
noErr,
|
|
},
|
|
// same validator, between existing (not most recent)
|
|
{
|
|
genEmptySeed(keys, "chain-1", start+5, []byte{15, 43}, len(keys)),
|
|
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)),
|
|
noErr,
|
|
},
|
|
// now impossible jump works (keys -> keys2 -> keys3)
|
|
{
|
|
genEmptySeed(keys3, "chain-1", start+100, []byte{44}, len(keys3)),
|
|
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 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: 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: 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 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,
|
|
}
|
|
}
|
|
|
|
func updateChain(app basecoin.Handler, store state.KVStore, keys certifiers.ValKeys,
|
|
chain string, h int, appHash []byte) error {
|
|
seed := genEmptySeed(keys, chain, h, appHash, len(keys))
|
|
tx := UpdateChainTx{seed}.Wrap()
|
|
ctx := stack.MockContext("foo", 123)
|
|
_, err := app.DeliverTx(ctx, store, tx)
|
|
return err
|
|
}
|
|
|
|
func TestIBCPostPacket(t *testing.T) {
|
|
assert := assert.New(t)
|
|
require := require.New(t)
|
|
|
|
otherID := "chain-1"
|
|
ourID := "hub"
|
|
|
|
// this is the root seed, that others are evaluated against
|
|
keys := certifiers.GenValKeys(7)
|
|
appHash := []byte("this is just random garbage")
|
|
start := 100 // initial height
|
|
root := genEmptySeed(keys, otherID, start, appHash, len(keys))
|
|
|
|
// create the app and register the root of trust (for chain-1)
|
|
ctx := stack.MockContext(ourID, 50)
|
|
store := state.NewMemKVStore()
|
|
app := stack.New().
|
|
IBC(NewMiddleware()).
|
|
Dispatch(
|
|
stack.WrapHandler(NewHandler()),
|
|
stack.WrapHandler(coin.NewHandler()),
|
|
)
|
|
tx := RegisterChainTx{root}.Wrap()
|
|
_, err := app.DeliverTx(ctx, store, tx)
|
|
require.Nil(err, "%+v", err)
|
|
|
|
recipient := basecoin.Actor{ChainID: ourID, App: auth.NameSigs, Address: []byte("bar")}
|
|
sender := basecoin.Actor{ChainID: otherID, App: auth.NameSigs, Address: []byte("foo")}
|
|
coinTx := coin.NewSendOneTx(
|
|
sender,
|
|
recipient,
|
|
coin.Coins{{"eth", 100}, {"ltc", 300}},
|
|
)
|
|
|
|
// set some cash on this chain (TODO: via set options...)
|
|
otherAddr := coin.ChainAddr(sender)
|
|
acct := coin.Account{
|
|
Coins: coin.Coins{{"btc", 300}, {"eth", 2000}, {"ltc", 5000}},
|
|
}
|
|
cstore := stack.PrefixedStore(coin.NameCoin, store)
|
|
cstore.Set(otherAddr.Bytes(), wire.BinaryBytes(acct))
|
|
|
|
// make proofs for some packets....
|
|
tree := iavl.NewIAVLTree(0, nil)
|
|
pbad := Packet{
|
|
DestChain: "something-else",
|
|
Sequence: 0,
|
|
Tx: coinTx,
|
|
}
|
|
packetBad := makePostPacket(tree, pbad, "something-else", 123)
|
|
|
|
p0 := Packet{
|
|
DestChain: ourID,
|
|
Sequence: 0,
|
|
Permissions: basecoin.Actors{sender},
|
|
Tx: coinTx,
|
|
}
|
|
p1 := Packet{
|
|
DestChain: ourID,
|
|
Sequence: 1,
|
|
Permissions: basecoin.Actors{sender},
|
|
Tx: coinTx,
|
|
}
|
|
// this sends money we don't have registered
|
|
p2 := Packet{
|
|
DestChain: ourID,
|
|
Sequence: 2,
|
|
Permissions: basecoin.Actors{sender},
|
|
Tx: coin.NewSendOneTx(sender, recipient, coin.Coins{{"missing", 20}}),
|
|
}
|
|
|
|
packet0 := makePostPacket(tree, p0, otherID, start+5)
|
|
err = updateChain(app, store, keys, otherID, start+5, tree.Hash())
|
|
require.Nil(err, "%+v", err)
|
|
|
|
packet0badHeight := packet0
|
|
packet0badHeight.FromChainHeight -= 2
|
|
|
|
packet1 := makePostPacket(tree, p1, otherID, start+25)
|
|
err = updateChain(app, store, keys, otherID, start+25, tree.Hash())
|
|
require.Nil(err, "%+v", err)
|
|
|
|
packet1badProof := packet1
|
|
packet1badProof.Key = []byte("random-data")
|
|
|
|
packet2 := makePostPacket(tree, p2, otherID, start+50)
|
|
err = updateChain(app, store, keys, otherID, start+50, tree.Hash())
|
|
require.Nil(err, "%+v", err)
|
|
|
|
ibcPerm := basecoin.Actors{AllowIBC(coin.NameCoin)}
|
|
cases := []struct {
|
|
packet PostPacketTx
|
|
permissions basecoin.Actors
|
|
checker checkErr
|
|
}{
|
|
// bad chain -> error
|
|
{packetBad, ibcPerm, IsNotRegisteredErr},
|
|
|
|
// invalid permissions -> error
|
|
{packet0, nil, IsNeedsIBCPermissionErr},
|
|
|
|
// no matching header -> error
|
|
{packet0badHeight, ibcPerm, IsHeaderNotFoundErr},
|
|
|
|
// out of order -> error
|
|
{packet1, ibcPerm, IsPacketOutOfOrderErr},
|
|
|
|
// all good -> execute tx }
|
|
{packet0, ibcPerm, noErr},
|
|
|
|
// bad proof -> error
|
|
{packet1badProof, ibcPerm, IsInvalidProofErr},
|
|
|
|
// all good -> execute tx }
|
|
{packet1, ibcPerm, noErr},
|
|
|
|
// repeat -> error
|
|
{packet0, ibcPerm, IsPacketAlreadyExistsErr},
|
|
|
|
// packet 2 attempts to spend money this chain doesn't have
|
|
{packet2, ibcPerm, coin.IsInsufficientFundsErr},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
// cache wrap it like an app, so no state change on error...
|
|
myStore := state.NewKVCache(store)
|
|
|
|
myCtx := ctx
|
|
if len(tc.permissions) > 0 {
|
|
myCtx = myCtx.WithPermissions(tc.permissions...)
|
|
}
|
|
_, err := app.DeliverTx(myCtx, myStore, tc.packet.Wrap())
|
|
assert.True(tc.checker(err), "%d: %+v", i, err)
|
|
|
|
// only commit changes on success
|
|
if err == nil {
|
|
myStore.Sync()
|
|
}
|
|
}
|
|
}
|