Merge pull request #93 from tendermint/ibc-coins

Prototype of Sending coins between chains via ibc
This commit is contained in:
Ethan Frey 2017-05-24 15:09:15 +02:00 committed by GitHub
commit 670d4b5633
20 changed files with 866 additions and 319 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -18,6 +18,7 @@ func main() {
RootCmd.AddCommand(
commands.InitCmd,
commands.StartCmd,
commands.RelayCmd,
commands.TxCmd,
commands.QueryCmd,
commands.KeyCmd,

View File

@ -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,
},
}

View File

@ -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
View 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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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) {

View File

@ -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))
}