Merge pull request #93 from tendermint/ibc-coins
Prototype of Sending coins between chains via ibc
This commit is contained in:
commit
670d4b5633
@ -150,7 +150,7 @@ func (app *Basecoin) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQu
|
||||
// handle special path for account info
|
||||
if reqQuery.Path == "/account" {
|
||||
reqQuery.Path = "/key"
|
||||
reqQuery.Data = sm.AccountKey(reqQuery.Data)
|
||||
reqQuery.Data = types.AccountKey(reqQuery.Data)
|
||||
}
|
||||
|
||||
resQuery, err := app.eyesCli.QuerySync(reqQuery)
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/basecoin/state"
|
||||
"github.com/tendermint/basecoin/types"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
eyes "github.com/tendermint/merkleeyes/client"
|
||||
@ -38,7 +37,7 @@ func newAppTest(t *testing.T) *appTest {
|
||||
|
||||
// make a tx sending 5mycoin from each accIn to accOut
|
||||
func (at *appTest) getTx(seq int) *types.SendTx {
|
||||
tx := types.GetTx(seq, at.accOut, at.accIn)
|
||||
tx := types.MakeSendTx(seq, at.accOut, at.accIn)
|
||||
types.SignTx(at.chainID, tx, at.accIn)
|
||||
return tx
|
||||
}
|
||||
@ -123,7 +122,7 @@ func TestSetOption(t *testing.T) {
|
||||
res = app.SetOption("base/account", string(accsInBytes))
|
||||
require.EqualValues(res, "Success")
|
||||
// make sure it is set correctly, with some balance
|
||||
acct := state.GetAccount(app.GetState(), accIn.PubKey.Address())
|
||||
acct := types.GetAccount(app.GetState(), accIn.PubKey.Address())
|
||||
require.NotNil(acct)
|
||||
assert.Equal(accIn.Balance, acct.Balance)
|
||||
|
||||
@ -149,7 +148,7 @@ func TestSetOption(t *testing.T) {
|
||||
}`
|
||||
res = app.SetOption("base/account", unsortAcc)
|
||||
require.EqualValues(res, "Success")
|
||||
acct = state.GetAccount(app.GetState(), unsortAddr)
|
||||
acct = types.GetAccount(app.GetState(), unsortAddr)
|
||||
require.NotNil(acct)
|
||||
assert.True(acct.Balance.IsValid())
|
||||
assert.Equal(unsortCoins, acct.Balance)
|
||||
|
||||
@ -14,7 +14,6 @@ import (
|
||||
"github.com/tendermint/light-client/commands"
|
||||
"github.com/tendermint/light-client/proofs"
|
||||
|
||||
"github.com/tendermint/basecoin/state"
|
||||
btypes "github.com/tendermint/basecoin/types"
|
||||
)
|
||||
|
||||
@ -23,7 +22,7 @@ type AccountPresenter struct{}
|
||||
func (_ AccountPresenter) MakeKey(str string) ([]byte, error) {
|
||||
res, err := hex.DecodeString(str)
|
||||
if err == nil {
|
||||
res = state.AccountKey(res)
|
||||
res = btypes.AccountKey(res)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ func main() {
|
||||
RootCmd.AddCommand(
|
||||
commands.InitCmd,
|
||||
commands.StartCmd,
|
||||
commands.RelayCmd,
|
||||
commands.TxCmd,
|
||||
commands.QueryCmd,
|
||||
commands.KeyCmd,
|
||||
|
||||
@ -196,13 +196,18 @@ func ibcPacketCreateTxCmd(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var payload ibc.Payload
|
||||
if err := wire.ReadBinaryBytes(payloadBytes, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ibcTx := ibc.IBCPacketCreateTx{
|
||||
Packet: ibc.Packet{
|
||||
SrcChainID: fromChain,
|
||||
DstChainID: toChain,
|
||||
Sequence: sequence,
|
||||
Type: packetType,
|
||||
Payload: payloadBytes,
|
||||
Payload: payload,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/merkleeyes/iavl"
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
@ -120,7 +121,8 @@ func accountCmd(cmd *cobra.Command, args []string) error {
|
||||
return errors.Errorf("Account address (%v) is invalid hex: %v\n", addrHex, err)
|
||||
}
|
||||
|
||||
acc, err := getAcc(nodeFlag, addr)
|
||||
httpClient := client.NewHTTP(nodeFlag, "/websocket")
|
||||
acc, err := getAccWithClient(httpClient, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
247
cmd/commands/relay.go
Normal file
247
cmd/commands/relay.go
Normal file
@ -0,0 +1,247 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
// "github.com/spf13/viper"
|
||||
// "github.com/tendermint/tmlibs/cli"
|
||||
// "github.com/tendermint/tmlibs/log"
|
||||
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/merkleeyes/iavl"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
|
||||
"github.com/tendermint/basecoin/plugins/ibc"
|
||||
"github.com/tendermint/basecoin/types"
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
var RelayCmd = &cobra.Command{
|
||||
Use: "relay",
|
||||
Short: "Start basecoin relayer to relay IBC packets between chains",
|
||||
RunE: relayCmd,
|
||||
}
|
||||
|
||||
//flags
|
||||
var (
|
||||
chain1AddrFlag string
|
||||
chain2AddrFlag string
|
||||
|
||||
chain1IDFlag string
|
||||
chain2IDFlag string
|
||||
|
||||
fromFileFlag string
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
flags := []Flag2Register{
|
||||
{&chain1AddrFlag, "chain1-addr", "tcp://localhost:46657", "Node address for chain1"},
|
||||
{&chain2AddrFlag, "chain2-addr", "tcp://localhost:36657", "Node address for chain2"},
|
||||
{&chain1IDFlag, "chain1-id", "test_chain_1", "ChainID for chain1"},
|
||||
{&chain2IDFlag, "chain2-id", "test_chain_2", "ChainID for chain2"},
|
||||
{&fromFileFlag, "from", "key.json", "Path to a private key to sign the transaction"},
|
||||
}
|
||||
RegisterFlags(RelayCmd, flags)
|
||||
}
|
||||
|
||||
func loop(addr1, addr2, id1, id2 string) {
|
||||
nextSeq := 0
|
||||
|
||||
// load the priv key
|
||||
privKey, err := LoadKey(fromFlag)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
cmn.PanicCrisis(err.Error())
|
||||
}
|
||||
|
||||
// relay from chain1 to chain2
|
||||
thisRelayer := newRelayer(privKey, id2, addr2)
|
||||
|
||||
logger.Info(fmt.Sprintf("Relaying from chain %v on %v to chain %v on %v", id1, addr1, id2, addr2))
|
||||
|
||||
httpClient := client.NewHTTP(addr1, "/websocket")
|
||||
|
||||
OUTER:
|
||||
for {
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
// get the latest ibc packet sequence number
|
||||
key := fmt.Sprintf("ibc,egress,%v,%v", id1, id2)
|
||||
query, err := queryWithClient(httpClient, []byte(key))
|
||||
if err != nil {
|
||||
logger.Error("Error querying for latest sequence", "key", key, "error", err.Error())
|
||||
continue OUTER
|
||||
}
|
||||
if len(query.Value) == 0 {
|
||||
// nothing yet
|
||||
continue OUTER
|
||||
}
|
||||
|
||||
seq, err := strconv.ParseUint(string(query.Value), 10, 64)
|
||||
if err != nil {
|
||||
logger.Error("Error parsing sequence number from query", "query.Value", query.Value, "error", err.Error())
|
||||
continue OUTER
|
||||
}
|
||||
seq -= 1 // seq is the packet count. -1 because 0-indexed
|
||||
|
||||
if nextSeq <= int(seq) {
|
||||
logger.Info("Got new packets", "last-sequence", nextSeq-1, "new-sequence", seq)
|
||||
}
|
||||
|
||||
// get all packets since the last one we relayed
|
||||
for ; nextSeq <= int(seq); nextSeq++ {
|
||||
key := fmt.Sprintf("ibc,egress,%v,%v,%d", id1, id2, nextSeq)
|
||||
query, err := queryWithClient(httpClient, []byte(key))
|
||||
if err != nil {
|
||||
logger.Error("Error querying for packet", "seqeuence", nextSeq, "key", key, "error", err.Error())
|
||||
continue OUTER
|
||||
}
|
||||
|
||||
var packet ibc.Packet
|
||||
err = wire.ReadBinaryBytes(query.Value, &packet)
|
||||
if err != nil {
|
||||
logger.Error("Error unmarshalling packet", "key", key, "query.Value", query.Value, "error", err.Error())
|
||||
continue OUTER
|
||||
}
|
||||
|
||||
proof := new(iavl.IAVLProof)
|
||||
err = wire.ReadBinaryBytes(query.Proof, &proof)
|
||||
if err != nil {
|
||||
logger.Error("Error unmarshalling proof", "query.Proof", query.Proof, "error", err.Error())
|
||||
continue OUTER
|
||||
}
|
||||
|
||||
// query.Height is actually for the next block,
|
||||
// so wait a block before we fetch the header & commit
|
||||
if err := waitForBlock(httpClient); err != nil {
|
||||
logger.Error("Error waiting for a block", "addr", addr1, "error", err.Error())
|
||||
continue OUTER
|
||||
}
|
||||
|
||||
// get the header and commit from the height the query was done at
|
||||
res, err := httpClient.Commit(int(query.Height))
|
||||
if err != nil {
|
||||
logger.Error("Error fetching header and commits", "height", query.Height, "error", err.Error())
|
||||
continue OUTER
|
||||
}
|
||||
|
||||
// update the chain state on the other chain
|
||||
updateTx := ibc.IBCUpdateChainTx{
|
||||
Header: *res.Header,
|
||||
Commit: *res.Commit,
|
||||
}
|
||||
logger.Info("Updating chain", "src-chain", id1, "height", res.Header.Height, "appHash", res.Header.AppHash)
|
||||
if err := thisRelayer.appTx(updateTx); err != nil {
|
||||
logger.Error("Error creating/sending IBCUpdateChainTx", "error", err.Error())
|
||||
continue OUTER
|
||||
}
|
||||
|
||||
// relay the packet and proof
|
||||
logger.Info("Relaying packet", "src-chain", id1, "height", query.Height, "sequence", nextSeq)
|
||||
postTx := ibc.IBCPacketPostTx{
|
||||
FromChainID: id1,
|
||||
FromChainHeight: query.Height,
|
||||
Packet: packet,
|
||||
Proof: proof,
|
||||
}
|
||||
|
||||
if err := thisRelayer.appTx(postTx); err != nil {
|
||||
logger.Error("Error creating/sending IBCPacketPostTx", "error", err.Error())
|
||||
// dont `continue OUTER` here. the error might be eg. Already exists
|
||||
// TODO: catch this programmatically ?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type relayer struct {
|
||||
privKey *Key
|
||||
chainID string
|
||||
nodeAddr string
|
||||
client *client.HTTP
|
||||
}
|
||||
|
||||
func newRelayer(privKey *Key, chainID, nodeAddr string) *relayer {
|
||||
httpClient := client.NewHTTP(nodeAddr, "/websocket")
|
||||
return &relayer{
|
||||
privKey: privKey,
|
||||
chainID: chainID,
|
||||
nodeAddr: nodeAddr,
|
||||
client: httpClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *relayer) appTx(ibcTx ibc.IBCTx) error {
|
||||
acc, err := getAccWithClient(r.client, r.privKey.Address[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sequence := acc.Sequence + 1
|
||||
|
||||
data := []byte(wire.BinaryBytes(struct {
|
||||
ibc.IBCTx `json:"unwrap"`
|
||||
}{ibcTx}))
|
||||
|
||||
smallCoins := types.Coin{"mycoin", 1}
|
||||
|
||||
input := types.NewTxInput(r.privKey.PubKey, types.Coins{smallCoins}, sequence)
|
||||
tx := &types.AppTx{
|
||||
Gas: 0,
|
||||
Fee: smallCoins,
|
||||
Name: "IBC",
|
||||
Input: input,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
tx.Input.Signature = r.privKey.Sign(tx.SignBytes(r.chainID))
|
||||
txBytes := []byte(wire.BinaryBytes(struct {
|
||||
types.Tx `json:"unwrap"`
|
||||
}{tx}))
|
||||
|
||||
data, log, err := broadcastTxWithClient(r.client, txBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = data, log
|
||||
return nil
|
||||
}
|
||||
|
||||
// broadcast the transaction to tendermint
|
||||
func broadcastTxWithClient(httpClient *client.HTTP, tx tmtypes.Tx) ([]byte, string, error) {
|
||||
res, err := httpClient.BroadcastTxCommit(tx)
|
||||
if err != nil {
|
||||
return nil, "", errors.Errorf("Error on broadcast tx: %v", err)
|
||||
}
|
||||
|
||||
if !res.CheckTx.Code.IsOK() {
|
||||
r := res.CheckTx
|
||||
return nil, "", errors.Errorf("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log)
|
||||
}
|
||||
|
||||
if !res.DeliverTx.Code.IsOK() {
|
||||
r := res.DeliverTx
|
||||
return nil, "", errors.Errorf("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log)
|
||||
}
|
||||
|
||||
return res.DeliverTx.Data, res.DeliverTx.Log, nil
|
||||
}
|
||||
|
||||
func relayCmd(cmd *cobra.Command, args []string) error {
|
||||
|
||||
go loop(chain1AddrFlag, chain2AddrFlag, chain1IDFlag, chain2IDFlag)
|
||||
go loop(chain2AddrFlag, chain1AddrFlag, chain2IDFlag, chain1IDFlag)
|
||||
|
||||
cmn.TrapSignal(func() {
|
||||
// TODO: Cleanup
|
||||
})
|
||||
return nil
|
||||
|
||||
}
|
||||
@ -3,6 +3,7 @@ package commands
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@ -81,12 +82,29 @@ func init() {
|
||||
|
||||
func sendTxCmd(cmd *cobra.Command, args []string) error {
|
||||
|
||||
var toHex string
|
||||
var chainPrefix string
|
||||
spl := strings.Split(toFlag, "/")
|
||||
switch len(spl) {
|
||||
case 1:
|
||||
toHex = spl[0]
|
||||
case 2:
|
||||
chainPrefix = spl[0]
|
||||
toHex = spl[1]
|
||||
default:
|
||||
return errors.Errorf("To address has too many slashes")
|
||||
}
|
||||
|
||||
// convert destination address to bytes
|
||||
to, err := hex.DecodeString(StripHex(toFlag))
|
||||
to, err := hex.DecodeString(StripHex(toHex))
|
||||
if err != nil {
|
||||
return errors.Errorf("To address is invalid hex: %v\n", err)
|
||||
}
|
||||
|
||||
if chainPrefix != "" {
|
||||
to = []byte(chainPrefix + "/" + string(to))
|
||||
}
|
||||
|
||||
// load the priv key
|
||||
privKey, err := LoadKey(fromFlag)
|
||||
if err != nil {
|
||||
@ -220,12 +238,12 @@ func broadcastTx(tx types.Tx) ([]byte, string, error) {
|
||||
// if the sequence flag is set, return it;
|
||||
// else, fetch the account by querying the app and return the sequence number
|
||||
func getSeq(address []byte) (int, error) {
|
||||
|
||||
if seqFlag >= 0 {
|
||||
return seqFlag, nil
|
||||
}
|
||||
|
||||
acc, err := getAcc(txNodeFlag, address)
|
||||
httpClient := client.NewHTTP(txNodeFlag, "/websocket")
|
||||
acc, err := getAccWithClient(httpClient, address)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import (
|
||||
abci "github.com/tendermint/abci/types"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
|
||||
"github.com/tendermint/basecoin/state"
|
||||
"github.com/tendermint/basecoin/types"
|
||||
|
||||
client "github.com/tendermint/tendermint/rpc/client"
|
||||
@ -101,6 +100,10 @@ func StripHex(s string) string {
|
||||
|
||||
func Query(tmAddr string, key []byte) (*abci.ResultQuery, error) {
|
||||
httpClient := client.NewHTTP(tmAddr, "/websocket")
|
||||
return queryWithClient(httpClient, key)
|
||||
}
|
||||
|
||||
func queryWithClient(httpClient *client.HTTP, key []byte) (*abci.ResultQuery, error) {
|
||||
res, err := httpClient.ABCIQuery("/key", key, true)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Error calling /abci_query: %v", err)
|
||||
@ -112,10 +115,10 @@ func Query(tmAddr string, key []byte) (*abci.ResultQuery, error) {
|
||||
}
|
||||
|
||||
// fetch the account by querying the app
|
||||
func getAcc(tmAddr string, address []byte) (*types.Account, error) {
|
||||
func getAccWithClient(httpClient *client.HTTP, address []byte) (*types.Account, error) {
|
||||
|
||||
key := state.AccountKey(address)
|
||||
response, err := Query(tmAddr, key)
|
||||
key := types.AccountKey(address)
|
||||
response, err := queryWithClient(httpClient, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -147,3 +150,23 @@ func getHeaderAndCommit(tmAddr string, height int) (*tmtypes.Header, *tmtypes.Co
|
||||
|
||||
return header, commit, nil
|
||||
}
|
||||
|
||||
func waitForBlock(httpClient *client.HTTP) error {
|
||||
res, err := httpClient.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastHeight := res.LatestBlockHeight
|
||||
for {
|
||||
res, err := httpClient.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.LatestBlockHeight > lastHeight {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -122,16 +122,20 @@ ifExit
|
||||
echo ""
|
||||
echo "... creating egress packet on chain1"
|
||||
echo ""
|
||||
# create a packet on chain1 destined for chain2
|
||||
PAYLOAD="DEADBEEF" #TODO
|
||||
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --ibc_from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --ibc_sequence 1
|
||||
# send coins from chain1 to an address on chain2
|
||||
# TODO: dont hardcode the address
|
||||
basecoin tx send --amount 10mycoin $CHAIN_FLAGS1 --to $CHAIN_ID2/053BA0F19616AFF975C8756A2CBFF04F408B4D47
|
||||
ifExit
|
||||
|
||||
# alternative way to create packets (for testing)
|
||||
# basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --ibc_from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --ibc_sequence 0
|
||||
|
||||
echo ""
|
||||
echo "... querying for packet data"
|
||||
echo ""
|
||||
# query for the packet data and proof
|
||||
QUERY_RESULT=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1)
|
||||
# since we only sent one packet, the sequence number is 0
|
||||
QUERY_RESULT=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,0)
|
||||
ifExit
|
||||
HEIGHT=$(echo $QUERY_RESULT | jq .height)
|
||||
PACKET=$(echo $QUERY_RESULT | jq .value)
|
||||
@ -187,7 +191,7 @@ echo ""
|
||||
echo "... checking if the packet is present on chain2"
|
||||
echo ""
|
||||
# query for the packet on chain2
|
||||
basecoin query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,1
|
||||
basecoin query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,0
|
||||
ifExit
|
||||
|
||||
echo ""
|
||||
|
||||
16
glide.lock
generated
16
glide.lock
generated
@ -1,5 +1,5 @@
|
||||
hash: 997e4cc3339141ee01aa2adf656425a49ebf117e6ca9e81ba72b8f94fee3e86e
|
||||
updated: 2017-05-17T12:25:00.580569867+02:00
|
||||
hash: 9d06ae13959cbb2835f5ae400a4b65e4bc329a567c949aec4aeab318c271da39
|
||||
updated: 2017-05-21T21:31:06.796806933-04:00
|
||||
imports:
|
||||
- name: github.com/bgentry/speakeasy
|
||||
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
|
||||
@ -101,7 +101,7 @@ imports:
|
||||
- leveldb/table
|
||||
- leveldb/util
|
||||
- name: github.com/tendermint/abci
|
||||
version: 5dabeffb35c027d7087a12149685daa68989168b
|
||||
version: 864d1f80b36b440bde030a5c18d8ac3aa8c2949d
|
||||
subpackages:
|
||||
- client
|
||||
- example/dummy
|
||||
@ -113,7 +113,7 @@ imports:
|
||||
- edwards25519
|
||||
- extra25519
|
||||
- name: github.com/tendermint/go-crypto
|
||||
version: 438b16f1f84ef002d7408ecd6fc3a3974cbc9559
|
||||
version: 7dff40942a64cdeefefa9446b2d104750b349f8a
|
||||
subpackages:
|
||||
- cmd
|
||||
- keys
|
||||
@ -122,7 +122,7 @@ imports:
|
||||
- keys/server/types
|
||||
- keys/storage/filestorage
|
||||
- name: github.com/tendermint/go-wire
|
||||
version: 97beaedf0f4dbc035309157c92be3b30cc6e5d74
|
||||
version: 5f88da3dbc1a72844e6dfaf274ce87f851d488eb
|
||||
subpackages:
|
||||
- data
|
||||
- data/base58
|
||||
@ -139,13 +139,13 @@ imports:
|
||||
- commands/txs
|
||||
- proofs
|
||||
- name: github.com/tendermint/merkleeyes
|
||||
version: c722818b460381bc5b82e38c73ff6e22a9df624d
|
||||
version: a0e73e1ac3e18e12a007520a4ea2c9822256e307
|
||||
subpackages:
|
||||
- app
|
||||
- client
|
||||
- iavl
|
||||
- name: github.com/tendermint/tendermint
|
||||
version: 11b5d11e9eec170e1d3dce165f0270d5c0759d69
|
||||
version: 267f134d44e76efb2adef5f0c993da8a5d5bd1b8
|
||||
subpackages:
|
||||
- blockchain
|
||||
- config
|
||||
@ -170,7 +170,7 @@ imports:
|
||||
- types
|
||||
- version
|
||||
- name: github.com/tendermint/tmlibs
|
||||
version: 8af1c70a8be17543eb33e9bfbbcdd8371e3201cc
|
||||
version: 306795ae1d8e4f4a10dcc8bdb32a00455843c9d5
|
||||
subpackages:
|
||||
- autofile
|
||||
- cli
|
||||
|
||||
@ -6,17 +6,14 @@ import:
|
||||
- package: github.com/spf13/pflag
|
||||
- package: github.com/spf13/viper
|
||||
- package: github.com/tendermint/abci
|
||||
version: develop
|
||||
subpackages:
|
||||
- server
|
||||
- types
|
||||
- package: github.com/tendermint/go-crypto
|
||||
version: develop
|
||||
subpackages:
|
||||
- cmd
|
||||
- keys
|
||||
- package: github.com/tendermint/go-wire
|
||||
version: develop
|
||||
subpackages:
|
||||
- data
|
||||
- package: github.com/tendermint/light-client
|
||||
@ -28,7 +25,6 @@ import:
|
||||
- commands/txs
|
||||
- proofs
|
||||
- package: github.com/tendermint/merkleeyes
|
||||
version: develop
|
||||
subpackages:
|
||||
- client
|
||||
- iavl
|
||||
@ -44,7 +40,6 @@ import:
|
||||
- rpc/lib/types
|
||||
- types
|
||||
- package: github.com/tendermint/tmlibs
|
||||
version: develop
|
||||
subpackages:
|
||||
- cli
|
||||
- common
|
||||
|
||||
@ -4,7 +4,9 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
@ -53,8 +55,108 @@ type Packet struct {
|
||||
SrcChainID string
|
||||
DstChainID string
|
||||
Sequence uint64
|
||||
Type string
|
||||
Payload []byte
|
||||
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 types.KVStore, 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 types.KVStore, 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 types.KVStore, 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 types.KVStore, 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 types.Coins
|
||||
}
|
||||
|
||||
func (p CoinsPayload) Type() string {
|
||||
return "coin"
|
||||
}
|
||||
|
||||
func (p CoinsPayload) ValidateBasic() abci.Result {
|
||||
// TODO: validate
|
||||
return abci.OK
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
@ -299,8 +401,28 @@ func (sm *IBCStateMachine) runPacketCreateTx(tx IBCPacketCreateTx) {
|
||||
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) {
|
||||
@ -327,7 +449,7 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) {
|
||||
return
|
||||
}
|
||||
|
||||
// Save new Packet
|
||||
// Save new Packet (just for fun)
|
||||
save(sm.store, packetKeyIngress, packet)
|
||||
|
||||
// Load Header and make sure it exists
|
||||
@ -356,10 +478,24 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) {
|
||||
ok := proof.Verify(packetKeyEgress, packetBytes, header.AppHash)
|
||||
if !ok {
|
||||
sm.res.Code = IBCCodeInvalidProof
|
||||
sm.res.Log = "Proof is invalid"
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@ -101,34 +101,19 @@ var testGenesisDoc = `{
|
||||
}`
|
||||
|
||||
func TestIBCGenesisFromString(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
eyesClient := eyes.NewLocalClient("", 0)
|
||||
store := types.NewKVCache(eyesClient)
|
||||
store.SetLogging() // Log all activity
|
||||
|
||||
ibcPlugin := New()
|
||||
ctx := types.CallContext{
|
||||
CallerAddress: nil,
|
||||
CallerAccount: nil,
|
||||
Coins: types.Coins{},
|
||||
}
|
||||
ctx := types.NewCallContext(nil, nil, types.Coins{})
|
||||
|
||||
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
|
||||
BlockchainGenesis{
|
||||
ChainID: "test_chain",
|
||||
Genesis: testGenesisDoc,
|
||||
},
|
||||
}}))
|
||||
assert.True(res.IsOK(), res.Log)
|
||||
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
|
||||
store.ClearLogLines()
|
||||
registerChain(t, ibcPlugin, store, ctx, "test_chain", testGenesisDoc)
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
func TestIBCPlugin(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
func TestIBCPluginRegister(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
eyesClient := eyes.NewLocalClient("", 0)
|
||||
@ -136,14 +121,10 @@ func TestIBCPlugin(t *testing.T) {
|
||||
store.SetLogging() // Log all activity
|
||||
|
||||
ibcPlugin := New()
|
||||
ctx := types.CallContext{
|
||||
CallerAddress: nil,
|
||||
CallerAccount: nil,
|
||||
Coins: types.Coins{},
|
||||
}
|
||||
ctx := types.NewCallContext(nil, nil, types.Coins{})
|
||||
|
||||
chainID_1 := "test_chain"
|
||||
genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4)
|
||||
genDoc_1, _ := genGenesisDoc(chainID_1, 4)
|
||||
genDocJSON_1, err := json.Marshal(genDoc_1)
|
||||
require.Nil(err)
|
||||
|
||||
@ -154,20 +135,10 @@ func TestIBCPlugin(t *testing.T) {
|
||||
Genesis: "<THIS IS NOT JSON>",
|
||||
},
|
||||
}}))
|
||||
assert.Equal(IBCCodeEncodingError, res.Code)
|
||||
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
|
||||
store.ClearLogLines()
|
||||
assertAndLog(t, store, res, IBCCodeEncodingError)
|
||||
|
||||
// Successfully register a chain
|
||||
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
|
||||
BlockchainGenesis{
|
||||
ChainID: "test_chain",
|
||||
Genesis: string(genDocJSON_1),
|
||||
},
|
||||
}}))
|
||||
assert.True(res.IsOK(), res.Log)
|
||||
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
|
||||
store.ClearLogLines()
|
||||
registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
|
||||
|
||||
// Duplicate request fails
|
||||
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
|
||||
@ -176,74 +147,82 @@ func TestIBCPlugin(t *testing.T) {
|
||||
Genesis: string(genDocJSON_1),
|
||||
},
|
||||
}}))
|
||||
assert.Equal(IBCCodeChainAlreadyExists, res.Code, res.Log)
|
||||
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
|
||||
store.ClearLogLines()
|
||||
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, types.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 := Packet{
|
||||
SrcChainID: "test_chain",
|
||||
DstChainID: "dst_chain",
|
||||
Sequence: 0,
|
||||
Type: "data",
|
||||
Payload: []byte("hello world"),
|
||||
}
|
||||
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
||||
packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world")))
|
||||
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
||||
Packet: packet,
|
||||
}}))
|
||||
assert.Equal(abci.CodeType_OK, res.Code, res.Log)
|
||||
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
|
||||
store.ClearLogLines()
|
||||
assertAndLog(t, store, res, abci.CodeType_OK)
|
||||
|
||||
// Post a duplicate packet
|
||||
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
||||
Packet: packet,
|
||||
}}))
|
||||
assert.Equal(IBCCodePacketAlreadyExists, res.Code, res.Log)
|
||||
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
|
||||
store.ClearLogLines()
|
||||
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, types.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 := tm.Header{
|
||||
ChainID: "test_chain",
|
||||
Height: 999,
|
||||
AppHash: appHash,
|
||||
ValidatorsHash: []byte("must_exist"), // TODO make optional
|
||||
}
|
||||
header := newHeader("test_chain", 999, appHash, []byte("must_exist"))
|
||||
|
||||
// Construct a Commit that signs above header
|
||||
blockHash := header.Hash()
|
||||
blockID := tm.BlockID{Hash: blockHash}
|
||||
commit := tm.Commit{
|
||||
BlockID: blockID,
|
||||
Precommits: make([]*tm.Vote, len(privAccs_1)),
|
||||
}
|
||||
for i, privAcc := range privAccs_1 {
|
||||
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
|
||||
}
|
||||
commit := constructCommit(privAccs_1, header)
|
||||
|
||||
// Update a chain
|
||||
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
|
||||
Header: header,
|
||||
Commit: commit,
|
||||
}}))
|
||||
assert.Equal(abci.CodeType_OK, res.Code, res.Log)
|
||||
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
|
||||
store.ClearLogLines()
|
||||
assertAndLog(t, store, res, abci.CodeType_OK)
|
||||
|
||||
// Get proof for the packet
|
||||
packetKey := toKey(_IBC, _EGRESS,
|
||||
@ -268,12 +247,10 @@ func TestIBCPlugin(t *testing.T) {
|
||||
Packet: packet,
|
||||
Proof: proof,
|
||||
}}))
|
||||
assert.Equal(abci.CodeType_OK, res.Code, res.Log)
|
||||
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
|
||||
store.ClearLogLines()
|
||||
assertAndLog(t, store, res, abci.CodeType_OK)
|
||||
}
|
||||
|
||||
func TestIBCPluginBadCommit(t *testing.T) {
|
||||
func TestIBCPluginPayloadCoins(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
@ -282,11 +259,106 @@ func TestIBCPluginBadCommit(t *testing.T) {
|
||||
store.SetLogging() // Log all activity
|
||||
|
||||
ibcPlugin := New()
|
||||
ctx := types.CallContext{
|
||||
CallerAddress: nil,
|
||||
CallerAccount: nil,
|
||||
Coins: types.Coins{},
|
||||
coins := types.Coins{
|
||||
types.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 := types.Coins{types.Coin{"mycoin", 200}}
|
||||
coinsGood := types.Coins{types.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, types.Coins{})
|
||||
|
||||
chainID_1 := "test_chain"
|
||||
genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4)
|
||||
@ -294,57 +366,24 @@ func TestIBCPluginBadCommit(t *testing.T) {
|
||||
require.Nil(err)
|
||||
|
||||
// Successfully register a chain
|
||||
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
|
||||
BlockchainGenesis{
|
||||
ChainID: "test_chain",
|
||||
Genesis: string(genDocJSON_1),
|
||||
},
|
||||
}}))
|
||||
assert.True(res.IsOK(), res.Log)
|
||||
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
|
||||
store.ClearLogLines()
|
||||
registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
|
||||
|
||||
// Construct a Header
|
||||
header := tm.Header{
|
||||
ChainID: "test_chain",
|
||||
Height: 999,
|
||||
ValidatorsHash: []byte("must_exist"), // TODO make optional
|
||||
}
|
||||
header := newHeader("test_chain", 999, nil, []byte("must_exist"))
|
||||
|
||||
// Construct a Commit that signs above header
|
||||
blockHash := header.Hash()
|
||||
blockID := tm.BlockID{Hash: blockHash}
|
||||
commit := tm.Commit{
|
||||
BlockID: blockID,
|
||||
Precommits: make([]*tm.Vote, len(privAccs_1)),
|
||||
}
|
||||
for i, privAcc := range privAccs_1 {
|
||||
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
|
||||
}
|
||||
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{
|
||||
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
|
||||
Header: header,
|
||||
Commit: commit,
|
||||
}}))
|
||||
assert.Equal(IBCCodeInvalidCommit, res.Code, res.Log)
|
||||
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
|
||||
store.ClearLogLines()
|
||||
assertAndLog(t, store, res, IBCCodeInvalidCommit)
|
||||
|
||||
}
|
||||
|
||||
@ -357,11 +396,7 @@ func TestIBCPluginBadProof(t *testing.T) {
|
||||
store.SetLogging() // Log all activity
|
||||
|
||||
ibcPlugin := New()
|
||||
ctx := types.CallContext{
|
||||
CallerAddress: nil,
|
||||
CallerAccount: nil,
|
||||
Coins: types.Coins{},
|
||||
}
|
||||
ctx := types.NewCallContext(nil, nil, types.Coins{})
|
||||
|
||||
chainID_1 := "test_chain"
|
||||
genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4)
|
||||
@ -369,72 +404,30 @@ func TestIBCPluginBadProof(t *testing.T) {
|
||||
require.Nil(err)
|
||||
|
||||
// Successfully register a chain
|
||||
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
|
||||
BlockchainGenesis{
|
||||
ChainID: "test_chain",
|
||||
Genesis: string(genDocJSON_1),
|
||||
},
|
||||
}}))
|
||||
assert.True(res.IsOK(), res.Log)
|
||||
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
|
||||
store.ClearLogLines()
|
||||
registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
|
||||
|
||||
// Create a new packet (for testing)
|
||||
packet := Packet{
|
||||
SrcChainID: "test_chain",
|
||||
DstChainID: "dst_chain",
|
||||
Sequence: 0,
|
||||
Type: "data",
|
||||
Payload: []byte("hello world"),
|
||||
}
|
||||
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
||||
packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world")))
|
||||
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
||||
Packet: packet,
|
||||
}}))
|
||||
assert.Equal(abci.CodeType_OK, res.Code, res.Log)
|
||||
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
|
||||
store.ClearLogLines()
|
||||
assertAndLog(t, store, res, abci.CodeType_OK)
|
||||
|
||||
// Construct a Header that includes the above packet.
|
||||
store.Sync()
|
||||
resCommit := eyesClient.CommitSync()
|
||||
appHash := resCommit.Data
|
||||
header := tm.Header{
|
||||
ChainID: "test_chain",
|
||||
Height: 999,
|
||||
AppHash: appHash,
|
||||
ValidatorsHash: []byte("must_exist"), // TODO make optional
|
||||
}
|
||||
header := newHeader("test_chain", 999, appHash, []byte("must_exist"))
|
||||
|
||||
// Construct a Commit that signs above header
|
||||
blockHash := header.Hash()
|
||||
blockID := tm.BlockID{Hash: blockHash}
|
||||
commit := tm.Commit{
|
||||
BlockID: blockID,
|
||||
Precommits: make([]*tm.Vote, len(privAccs_1)),
|
||||
}
|
||||
for i, privAcc := range privAccs_1 {
|
||||
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
|
||||
}
|
||||
commit := constructCommit(privAccs_1, header)
|
||||
|
||||
// Update a chain
|
||||
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
|
||||
Header: header,
|
||||
Commit: commit,
|
||||
}}))
|
||||
assert.Equal(abci.CodeType_OK, res.Code, res.Log)
|
||||
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
|
||||
store.ClearLogLines()
|
||||
assertAndLog(t, store, res, abci.CodeType_OK)
|
||||
|
||||
// Get proof for the packet
|
||||
packetKey := toKey(_IBC, _EGRESS,
|
||||
@ -462,7 +455,58 @@ func TestIBCPluginBadProof(t *testing.T) {
|
||||
Packet: packet,
|
||||
Proof: proof,
|
||||
}}))
|
||||
assert.Equal(IBCCodeInvalidProof, res.Code, res.Log)
|
||||
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
|
||||
}
|
||||
|
||||
@ -2,9 +2,11 @@ package state
|
||||
|
||||
import (
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/basecoin/types"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/events"
|
||||
|
||||
"github.com/tendermint/basecoin/plugins/ibc"
|
||||
"github.com/tendermint/basecoin/types"
|
||||
)
|
||||
|
||||
// If the tx is invalid, a TMSP error will be returned.
|
||||
@ -189,17 +191,23 @@ func getOrMakeOutputs(state types.AccountGetter, accounts map[string]*types.Acco
|
||||
}
|
||||
|
||||
for _, out := range outs {
|
||||
chain, outAddress, _ := out.ChainAndAddress() // already validated
|
||||
if chain != nil {
|
||||
// we dont need an account for the other chain.
|
||||
// we'll just create an outgoing ibc packet
|
||||
continue
|
||||
}
|
||||
// Account shouldn't be duplicated
|
||||
if _, ok := accounts[string(out.Address)]; ok {
|
||||
if _, ok := accounts[string(outAddress)]; ok {
|
||||
return nil, abci.ErrBaseDuplicateAddress
|
||||
}
|
||||
acc := state.GetAccount(out.Address)
|
||||
acc := state.GetAccount(outAddress)
|
||||
// output account may be nil (new)
|
||||
if acc == nil {
|
||||
// zero value is valid, empty account
|
||||
acc = &types.Account{}
|
||||
}
|
||||
accounts[string(out.Address)] = acc
|
||||
accounts[string(outAddress)] = acc
|
||||
}
|
||||
return accounts, abci.OK
|
||||
}
|
||||
@ -281,15 +289,22 @@ func adjustByInputs(state types.AccountSetter, accounts map[string]*types.Accoun
|
||||
}
|
||||
}
|
||||
|
||||
func adjustByOutputs(state types.AccountSetter, accounts map[string]*types.Account, outs []types.TxOutput, isCheckTx bool) {
|
||||
func adjustByOutputs(state *State, accounts map[string]*types.Account, outs []types.TxOutput, isCheckTx bool) {
|
||||
for _, out := range outs {
|
||||
acc := accounts[string(out.Address)]
|
||||
destChain, outAddress, _ := out.ChainAndAddress() // already validated
|
||||
if destChain != nil {
|
||||
payload := ibc.CoinsPayload{outAddress, out.Coins}
|
||||
ibc.SaveNewIBCPacket(state, state.GetChainID(), string(destChain), payload)
|
||||
continue
|
||||
}
|
||||
|
||||
acc := accounts[string(outAddress)]
|
||||
if acc == nil {
|
||||
cmn.PanicSanity("adjustByOutputs() expects account in accounts")
|
||||
}
|
||||
acc.Balance = acc.Balance.Plus(out.Coins)
|
||||
if !isCheckTx {
|
||||
state.SetAccount(out.Address, acc)
|
||||
state.SetAccount(outAddress, acc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,10 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/basecoin/types"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
"github.com/tendermint/basecoin/plugins/ibc"
|
||||
"github.com/tendermint/basecoin/types"
|
||||
)
|
||||
|
||||
//--------------------------------------------------------
|
||||
@ -33,11 +35,6 @@ func (et *execTest) signTx(tx *types.SendTx, accsIn ...types.PrivAccount) {
|
||||
types.SignTx(et.chainID, tx, accsIn...)
|
||||
}
|
||||
|
||||
// make tx from accsIn to et.accOut
|
||||
func (et *execTest) getTx(seq int, accOut types.PrivAccount, accsIn ...types.PrivAccount) *types.SendTx {
|
||||
return types.GetTx(seq, accOut, accsIn...)
|
||||
}
|
||||
|
||||
// returns the final balance and expected balance for input and output accounts
|
||||
func (et *execTest) exec(tx *types.SendTx, checkTx bool) (res abci.Result, inGot, inExp, outGot, outExp types.Coins) {
|
||||
initBalIn := et.state.GetAccount(et.accIn.Account.PubKey.Address()).Balance
|
||||
@ -85,19 +82,19 @@ func TestGetInputs(t *testing.T) {
|
||||
|
||||
//test getInputs for registered, non-registered account
|
||||
et.reset()
|
||||
txs := types.Accs2TxInputs(1, et.accIn)
|
||||
acc, res = getInputs(et.state, txs)
|
||||
inputs := types.Accs2TxInputs(1, et.accIn)
|
||||
acc, res = getInputs(et.state, inputs)
|
||||
assert.True(res.IsErr(), "getInputs: expected error when using getInput with non-registered Input")
|
||||
|
||||
et.acc2State(et.accIn)
|
||||
acc, res = getInputs(et.state, txs)
|
||||
acc, res = getInputs(et.state, inputs)
|
||||
assert.True(res.IsOK(), "getInputs: expected to getInput from registered Input")
|
||||
|
||||
//test sending duplicate accounts
|
||||
et.reset()
|
||||
et.acc2State(et.accIn, et.accIn, et.accIn)
|
||||
txs = types.Accs2TxInputs(1, et.accIn, et.accIn, et.accIn)
|
||||
acc, res = getInputs(et.state, txs)
|
||||
inputs = types.Accs2TxInputs(1, et.accIn, et.accIn, et.accIn)
|
||||
acc, res = getInputs(et.state, inputs)
|
||||
assert.True(res.IsErr(), "getInputs: expected error when sending duplicate accounts")
|
||||
}
|
||||
|
||||
@ -112,24 +109,24 @@ func TestGetOrMakeOutputs(t *testing.T) {
|
||||
|
||||
//test sending duplicate accounts
|
||||
et.reset()
|
||||
txs := types.Accs2TxOutputs(et.accIn, et.accIn, et.accIn)
|
||||
_, res = getOrMakeOutputs(et.state, nil, txs)
|
||||
outputs := types.Accs2TxOutputs(et.accIn, et.accIn, et.accIn)
|
||||
_, res = getOrMakeOutputs(et.state, nil, outputs)
|
||||
assert.True(res.IsErr(), "getOrMakeOutputs: expected error when sending duplicate accounts")
|
||||
|
||||
//test sending to existing/new account
|
||||
et.reset()
|
||||
txs1 := types.Accs2TxOutputs(et.accIn)
|
||||
txs2 := types.Accs2TxOutputs(et.accOut)
|
||||
outputs1 := types.Accs2TxOutputs(et.accIn)
|
||||
outputs2 := types.Accs2TxOutputs(et.accOut)
|
||||
|
||||
et.acc2State(et.accIn)
|
||||
_, res = getOrMakeOutputs(et.state, nil, txs1)
|
||||
_, res = getOrMakeOutputs(et.state, nil, outputs1)
|
||||
assert.True(res.IsOK(), "getOrMakeOutputs: error when sending to existing account")
|
||||
|
||||
mapRes2, res := getOrMakeOutputs(et.state, nil, txs2)
|
||||
mapRes2, res := getOrMakeOutputs(et.state, nil, outputs2)
|
||||
assert.True(res.IsOK(), "getOrMakeOutputs: error when sending to new account")
|
||||
|
||||
//test the map results
|
||||
_, map2ok := mapRes2[string(txs2[0].Address)]
|
||||
_, map2ok := mapRes2[string(outputs2[0].Address)]
|
||||
assert.True(map2ok, "getOrMakeOutputs: account output does not contain new account map item")
|
||||
|
||||
}
|
||||
@ -139,12 +136,12 @@ func TestValidateInputsBasic(t *testing.T) {
|
||||
et := newExecTest()
|
||||
|
||||
//validate input basic
|
||||
txs := types.Accs2TxInputs(1, et.accIn)
|
||||
res := validateInputsBasic(txs)
|
||||
inputs := types.Accs2TxInputs(1, et.accIn)
|
||||
res := validateInputsBasic(inputs)
|
||||
assert.True(res.IsOK(), "validateInputsBasic: expected no error on good tx input. Error: %v", res.Error())
|
||||
|
||||
txs[0].Coins[0].Amount = 0
|
||||
res = validateInputsBasic(txs)
|
||||
inputs[0].Coins[0].Amount = 0
|
||||
res = validateInputsBasic(inputs)
|
||||
assert.True(res.IsErr(), "validateInputsBasic: expected error on bad tx input")
|
||||
|
||||
}
|
||||
@ -159,28 +156,28 @@ func TestValidateInputsAdvanced(t *testing.T) {
|
||||
accIn3 := types.MakeAcc("fooz")
|
||||
|
||||
//validate inputs advanced
|
||||
txs := et.getTx(1, et.accOut, accIn1, accIn2, accIn3)
|
||||
tx := types.MakeSendTx(1, et.accOut, accIn1, accIn2, accIn3)
|
||||
|
||||
et.acc2State(accIn1, accIn2, accIn3, et.accOut)
|
||||
accMap, res := getInputs(et.state, txs.Inputs)
|
||||
accMap, res := getInputs(et.state, tx.Inputs)
|
||||
assert.True(res.IsOK(), "validateInputsAdvanced: error retrieving accMap. Error: %v", res.Error())
|
||||
signBytes := txs.SignBytes(et.chainID)
|
||||
signBytes := tx.SignBytes(et.chainID)
|
||||
|
||||
//test bad case, unsigned
|
||||
totalCoins, res := validateInputsAdvanced(accMap, signBytes, txs.Inputs)
|
||||
totalCoins, res := validateInputsAdvanced(accMap, signBytes, tx.Inputs)
|
||||
assert.True(res.IsErr(), "validateInputsAdvanced: expected an error on an unsigned tx input")
|
||||
|
||||
//test good case sgined
|
||||
et.signTx(txs, accIn1, accIn2, accIn3, et.accOut)
|
||||
totalCoins, res = validateInputsAdvanced(accMap, signBytes, txs.Inputs)
|
||||
et.signTx(tx, accIn1, accIn2, accIn3, et.accOut)
|
||||
totalCoins, res = validateInputsAdvanced(accMap, signBytes, tx.Inputs)
|
||||
assert.True(res.IsOK(), "validateInputsAdvanced: expected no error on good tx input. Error: %v", res.Error())
|
||||
|
||||
txsTotalCoins := txs.Inputs[0].Coins.
|
||||
Plus(txs.Inputs[1].Coins).
|
||||
Plus(txs.Inputs[2].Coins)
|
||||
txTotalCoins := tx.Inputs[0].Coins.
|
||||
Plus(tx.Inputs[1].Coins).
|
||||
Plus(tx.Inputs[2].Coins)
|
||||
|
||||
assert.True(totalCoins.IsEqual(txsTotalCoins),
|
||||
"ValidateInputsAdvanced: transaction total coins are not equal: got %v, expected %v", txsTotalCoins, totalCoins)
|
||||
assert.True(totalCoins.IsEqual(txTotalCoins),
|
||||
"ValidateInputsAdvanced: transaction total coins are not equal: got %v, expected %v", txTotalCoins, totalCoins)
|
||||
}
|
||||
|
||||
func TestValidateInputAdvanced(t *testing.T) {
|
||||
@ -188,31 +185,31 @@ func TestValidateInputAdvanced(t *testing.T) {
|
||||
et := newExecTest()
|
||||
|
||||
//validate input advanced
|
||||
txs := et.getTx(1, et.accOut, et.accIn)
|
||||
tx := types.MakeSendTx(1, et.accOut, et.accIn)
|
||||
|
||||
et.acc2State(et.accIn, et.accOut)
|
||||
signBytes := txs.SignBytes(et.chainID)
|
||||
signBytes := tx.SignBytes(et.chainID)
|
||||
|
||||
//unsigned case
|
||||
res := validateInputAdvanced(&et.accIn.Account, signBytes, txs.Inputs[0])
|
||||
res := validateInputAdvanced(&et.accIn.Account, signBytes, tx.Inputs[0])
|
||||
assert.True(res.IsErr(), "validateInputAdvanced: expected error on tx input without signature")
|
||||
|
||||
//good signed case
|
||||
et.signTx(txs, et.accIn, et.accOut)
|
||||
res = validateInputAdvanced(&et.accIn.Account, signBytes, txs.Inputs[0])
|
||||
et.signTx(tx, et.accIn, et.accOut)
|
||||
res = validateInputAdvanced(&et.accIn.Account, signBytes, tx.Inputs[0])
|
||||
assert.True(res.IsOK(), "validateInputAdvanced: expected no error on good tx input. Error: %v", res.Error())
|
||||
|
||||
//bad sequence case
|
||||
et.accIn.Sequence = 1
|
||||
et.signTx(txs, et.accIn, et.accOut)
|
||||
res = validateInputAdvanced(&et.accIn.Account, signBytes, txs.Inputs[0])
|
||||
et.signTx(tx, et.accIn, et.accOut)
|
||||
res = validateInputAdvanced(&et.accIn.Account, signBytes, tx.Inputs[0])
|
||||
assert.Equal(abci.CodeType_BaseInvalidSequence, res.Code, "validateInputAdvanced: expected error on tx input with bad sequence")
|
||||
et.accIn.Sequence = 0 //restore sequence
|
||||
|
||||
//bad balance case
|
||||
et.accIn.Balance = types.Coins{{"mycoin", 2}}
|
||||
et.signTx(txs, et.accIn, et.accOut)
|
||||
res = validateInputAdvanced(&et.accIn.Account, signBytes, txs.Inputs[0])
|
||||
et.signTx(tx, et.accIn, et.accOut)
|
||||
res = validateInputAdvanced(&et.accIn.Account, signBytes, tx.Inputs[0])
|
||||
assert.Equal(abci.CodeType_BaseInsufficientFunds, res.Code,
|
||||
"validateInputAdvanced: expected error on tx input with insufficient funds %v", et.accIn.Sequence)
|
||||
}
|
||||
@ -222,12 +219,12 @@ func TestValidateOutputsAdvanced(t *testing.T) {
|
||||
et := newExecTest()
|
||||
|
||||
//validateOutputsBasic
|
||||
txs := types.Accs2TxOutputs(et.accIn)
|
||||
res := validateOutputsBasic(txs)
|
||||
tx := types.Accs2TxOutputs(et.accIn)
|
||||
res := validateOutputsBasic(tx)
|
||||
assert.True(res.IsOK(), "validateOutputsBasic: expected no error on good tx output. Error: %v", res.Error())
|
||||
|
||||
txs[0].Coins[0].Amount = 0
|
||||
res = validateOutputsBasic(txs)
|
||||
tx[0].Coins[0].Amount = 0
|
||||
res = validateOutputsBasic(tx)
|
||||
assert.True(res.IsErr(), "validateInputBasic: expected error on bad tx output. Error: %v", res.Error())
|
||||
}
|
||||
|
||||
@ -236,9 +233,9 @@ func TestSumOutput(t *testing.T) {
|
||||
et := newExecTest()
|
||||
|
||||
//SumOutput
|
||||
txs := types.Accs2TxOutputs(et.accIn, et.accOut)
|
||||
total := sumOutputs(txs)
|
||||
assert.True(total.IsEqual(txs[0].Coins.Plus(txs[1].Coins)), "sumOutputs: total coins are not equal")
|
||||
tx := types.Accs2TxOutputs(et.accIn, et.accOut)
|
||||
total := sumOutputs(tx)
|
||||
assert.True(total.IsEqual(tx[0].Coins.Plus(tx[1].Coins)), "sumOutputs: total coins are not equal")
|
||||
}
|
||||
|
||||
func TestAdjustBy(t *testing.T) {
|
||||
@ -271,23 +268,23 @@ func TestAdjustBy(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestExecTx(t *testing.T) {
|
||||
func TestSendTx(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
et := newExecTest()
|
||||
|
||||
//ExecTx
|
||||
txs := et.getTx(1, et.accOut, et.accIn)
|
||||
tx := types.MakeSendTx(1, et.accOut, et.accIn)
|
||||
et.acc2State(et.accIn)
|
||||
et.acc2State(et.accOut)
|
||||
et.signTx(txs, et.accIn)
|
||||
et.signTx(tx, et.accIn)
|
||||
|
||||
//Bad Balance
|
||||
et.accIn.Balance = types.Coins{{"mycoin", 2}}
|
||||
et.acc2State(et.accIn)
|
||||
res, _, _, _, _ := et.exec(txs, true)
|
||||
res, _, _, _, _ := et.exec(tx, true)
|
||||
assert.True(res.IsErr(), "ExecTx/Bad CheckTx: Expected error return from ExecTx, returned: %v", res)
|
||||
|
||||
res, balIn, balInExp, balOut, balOutExp := et.exec(txs, false)
|
||||
res, balIn, balInExp, balOut, balOutExp := et.exec(tx, false)
|
||||
assert.True(res.IsErr(), "ExecTx/Bad DeliverTx: Expected error return from ExecTx, returned: %v", res)
|
||||
assert.False(balIn.IsEqual(balInExp),
|
||||
"ExecTx/Bad DeliverTx: balance shouldn't be equal for accIn: got %v, expected: %v", balIn, balInExp)
|
||||
@ -298,17 +295,59 @@ func TestExecTx(t *testing.T) {
|
||||
et.reset()
|
||||
et.acc2State(et.accIn)
|
||||
et.acc2State(et.accOut)
|
||||
res, _, _, _, _ = et.exec(txs, true)
|
||||
res, _, _, _, _ = et.exec(tx, true)
|
||||
assert.True(res.IsOK(), "ExecTx/Good CheckTx: Expected OK return from ExecTx, Error: %v", res)
|
||||
|
||||
//Regular DeliverTx
|
||||
et.reset()
|
||||
et.acc2State(et.accIn)
|
||||
et.acc2State(et.accOut)
|
||||
res, balIn, balInExp, balOut, balOutExp = et.exec(txs, false)
|
||||
res, balIn, balInExp, balOut, balOutExp = et.exec(tx, false)
|
||||
assert.True(res.IsOK(), "ExecTx/Good DeliverTx: Expected OK return from ExecTx, Error: %v", res)
|
||||
assert.True(balIn.IsEqual(balInExp),
|
||||
"ExecTx/good DeliverTx: unexpected change in input balance, got: %v, expected: %v", balIn, balInExp)
|
||||
assert.True(balOut.IsEqual(balOutExp),
|
||||
"ExecTx/good DeliverTx: unexpected change in output balance, got: %v, expected: %v", balOut, balOutExp)
|
||||
}
|
||||
|
||||
func TestSendTxIBC(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
et := newExecTest()
|
||||
|
||||
//ExecTx
|
||||
chainID2 := "otherchain"
|
||||
tx := types.MakeSendTx(1, et.accOut, et.accIn)
|
||||
dstAddress := tx.Outputs[0].Address
|
||||
tx.Outputs[0].Address = []byte(chainID2 + "/" + string(tx.Outputs[0].Address))
|
||||
et.acc2State(et.accIn)
|
||||
et.signTx(tx, et.accIn)
|
||||
|
||||
//Regular DeliverTx
|
||||
et.reset()
|
||||
et.acc2State(et.accIn)
|
||||
|
||||
initBalIn := et.state.GetAccount(et.accIn.Account.PubKey.Address()).Balance
|
||||
|
||||
res := ExecTx(et.state, nil, tx, false, nil)
|
||||
|
||||
balIn := et.state.GetAccount(et.accIn.Account.PubKey.Address()).Balance
|
||||
decrBalInExp := tx.Outputs[0].Coins.Plus(types.Coins{tx.Fee}) //expected decrease in balance In
|
||||
balInExp := initBalIn.Minus(decrBalInExp)
|
||||
|
||||
assert.True(res.IsOK(), "ExecTx/Good DeliverTx: Expected OK return from ExecTx, Error: %v", res)
|
||||
assert.True(balIn.IsEqual(balInExp),
|
||||
"ExecTx/good DeliverTx: unexpected change in input balance, got: %v, expected: %v", balIn, balInExp)
|
||||
|
||||
packet, err := ibc.GetIBCPacket(et.state, et.chainID, chainID2, 0)
|
||||
assert.Nil(err)
|
||||
|
||||
assert.Equal(packet.SrcChainID, et.chainID)
|
||||
assert.Equal(packet.DstChainID, chainID2)
|
||||
assert.Equal(packet.Sequence, uint64(0))
|
||||
assert.Equal(packet.Type, "coin")
|
||||
|
||||
coins, ok := packet.Payload.(ibc.CoinsPayload)
|
||||
assert.True(ok)
|
||||
assert.Equal(coins.Coins, tx.Outputs[0].Coins)
|
||||
assert.EqualValues(coins.Address, dstAddress)
|
||||
}
|
||||
|
||||
@ -3,9 +3,7 @@ package state
|
||||
import (
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/basecoin/types"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
eyes "github.com/tendermint/merkleeyes/client"
|
||||
. "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
)
|
||||
|
||||
@ -64,11 +62,11 @@ func (s *State) Set(key []byte, value []byte) {
|
||||
}
|
||||
|
||||
func (s *State) GetAccount(addr []byte) *types.Account {
|
||||
return GetAccount(s, addr)
|
||||
return types.GetAccount(s, addr)
|
||||
}
|
||||
|
||||
func (s *State) SetAccount(addr []byte, acc *types.Account) {
|
||||
SetAccount(s, addr, acc)
|
||||
types.SetAccount(s, addr, acc)
|
||||
}
|
||||
|
||||
func (s *State) CacheWrap() *State {
|
||||
@ -97,28 +95,3 @@ func (s *State) Commit() abci.Result {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
|
||||
func AccountKey(addr []byte) []byte {
|
||||
return append([]byte("base/a/"), addr...)
|
||||
}
|
||||
|
||||
func GetAccount(store types.KVStore, addr []byte) *types.Account {
|
||||
data := store.Get(AccountKey(addr))
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
var acc *types.Account
|
||||
err := wire.ReadBinaryBytes(data, &acc)
|
||||
if err != nil {
|
||||
panic(Fmt("Error reading account %X error: %v",
|
||||
data, err.Error()))
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
func SetAccount(store types.KVStore, addr []byte, acc *types.Account) {
|
||||
accBytes := wire.BinaryBytes(acc)
|
||||
store.Set(AccountKey(addr), accBytes)
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
@ -49,3 +50,26 @@ type AccountGetterSetter interface {
|
||||
GetAccount(addr []byte) *Account
|
||||
SetAccount(addr []byte, acc *Account)
|
||||
}
|
||||
|
||||
func AccountKey(addr []byte) []byte {
|
||||
return append([]byte("base/a/"), addr...)
|
||||
}
|
||||
|
||||
func GetAccount(store KVStore, addr []byte) *Account {
|
||||
data := store.Get(AccountKey(addr))
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
var acc *Account
|
||||
err := wire.ReadBinaryBytes(data, &acc)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error reading account %X error: %v",
|
||||
data, err.Error()))
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
func SetAccount(store KVStore, addr []byte, acc *Account) {
|
||||
accBytes := wire.BinaryBytes(acc)
|
||||
store.Set(AccountKey(addr), accBytes)
|
||||
}
|
||||
|
||||
@ -86,15 +86,15 @@ func Accs2TxOutputs(accs ...PrivAccount) []TxOutput {
|
||||
return txs
|
||||
}
|
||||
|
||||
func GetTx(seq int, accOut PrivAccount, accsIn ...PrivAccount) *SendTx {
|
||||
txs := &SendTx{
|
||||
func MakeSendTx(seq int, accOut PrivAccount, accsIn ...PrivAccount) *SendTx {
|
||||
tx := &SendTx{
|
||||
Gas: 0,
|
||||
Fee: Coin{"mycoin", 1},
|
||||
Inputs: Accs2TxInputs(seq, accsIn...),
|
||||
Outputs: Accs2TxOutputs(accOut),
|
||||
}
|
||||
|
||||
return txs
|
||||
return tx
|
||||
}
|
||||
|
||||
func SignTx(chainID string, tx *SendTx, accs ...PrivAccount) {
|
||||
|
||||
29
types/tx.go
29
types/tx.go
@ -116,10 +116,33 @@ type TxOutput struct {
|
||||
Coins Coins `json:"coins"` //
|
||||
}
|
||||
|
||||
func (txOut TxOutput) ValidateBasic() abci.Result {
|
||||
if len(txOut.Address) != 20 {
|
||||
return abci.ErrBaseInvalidOutput.AppendLog("Invalid address length")
|
||||
// An output destined for another chain may be formatted as `chainID/address`.
|
||||
// ChainAndAddress returns the chainID prefix and the address.
|
||||
// If there is no chainID prefix, the first returned value is nil.
|
||||
func (txOut TxOutput) ChainAndAddress() ([]byte, []byte, abci.Result) {
|
||||
var chainPrefix []byte
|
||||
address := txOut.Address
|
||||
if len(address) > 20 {
|
||||
spl := bytes.Split(address, []byte("/"))
|
||||
if len(spl) < 2 {
|
||||
return nil, nil, abci.ErrBaseInvalidOutput.AppendLog("Invalid address format")
|
||||
}
|
||||
chainPrefix = spl[0]
|
||||
address = bytes.Join(spl[1:], nil)
|
||||
}
|
||||
|
||||
if len(address) != 20 {
|
||||
return nil, nil, abci.ErrBaseInvalidOutput.AppendLog("Invalid address length")
|
||||
}
|
||||
return chainPrefix, address, abci.OK
|
||||
}
|
||||
|
||||
func (txOut TxOutput) ValidateBasic() abci.Result {
|
||||
_, _, r := txOut.ChainAndAddress()
|
||||
if r.IsErr() {
|
||||
return r
|
||||
}
|
||||
|
||||
if !txOut.Coins.IsValid() {
|
||||
return abci.ErrBaseInvalidOutput.AppendLog(Fmt("Invalid coins %v", txOut.Coins))
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user