REST folder restructure & cleanup
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
# IBC Doubble Hubble
|
||||
|
||||
## Remove remaining data
|
||||
|
||||
```console
|
||||
> rm -r ~/.chain1
|
||||
> rm -r ~/.chain2
|
||||
> rm -r ~/.basecli
|
||||
```
|
||||
|
||||
## Initialize both chains
|
||||
|
||||
```console
|
||||
> basecoind init --home ~/.chain1
|
||||
I[04-02|14:03:33.704] Generated private validator module=main path=/home/mossid/.chain1/config/priv_validator.json
|
||||
I[04-02|14:03:33.705] Generated genesis file module=main path=/home/mossid/.chain1/config/genesis.json
|
||||
{
|
||||
"secret": "crunch ignore trigger neither differ dance cheap brick situate floor luxury citizen husband decline arrow abandon",
|
||||
"account": "C69FEB398A29AAB1B3C4F07DE22208F35E711BCC",
|
||||
"validator": {
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "8C9917D5E982E221F5A1450103102B44BBFC1E8768126C606246CB37B5794F4D"
|
||||
},
|
||||
"power": 10,
|
||||
"name": ""
|
||||
},
|
||||
"node_id": "3ac8e6242315fd62143dc3e52c161edaaa6b1a64",
|
||||
"chain_id": "test-chain-ZajMfr"
|
||||
}
|
||||
> ADDR1=C69FEB398A29AAB1B3C4F07DE22208F35E711BCC
|
||||
> ID1=test-chain-ZajMfr
|
||||
> NODE1=tcp://0.0.0.0:36657
|
||||
> basecli keys add key1 --recover
|
||||
Enter a passphrase for your key:
|
||||
Repeat the passphrase:
|
||||
Enter your recovery seed phrase:
|
||||
crunch ignore trigger neither differ dance cheap brick situate floor luxury citizen husband decline arrow abandon
|
||||
key1 C69FEB398A29AAB1B3C4F07DE22208F35E711BCC
|
||||
|
||||
|
||||
> basecoind init --home ~/.chain2
|
||||
I[04-02|14:09:14.453] Generated private validator module=main path=/home/mossid/.chain2/config/priv_validator.json
|
||||
I[04-02|14:09:14.453] Generated genesis file module=main path=/home/mossid/.chain2/config/genesis.json
|
||||
{
|
||||
"secret": "age guide awesome month female left oxygen soccer define high grocery work desert dinner arena abandon",
|
||||
"account": "DC26002735D3AA9573707CFA6D77C12349E49868",
|
||||
"validator": {
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "A94FE4B9AD763D301F4DD5A2766009812495FB7A79F1275FB8A5AF09B44FD5F3"
|
||||
},
|
||||
"power": 10,
|
||||
"name": ""
|
||||
},
|
||||
"node_id": "ad26831330e1c72b85276d53c20f0680e6fd4cf5"
|
||||
"chain_id": "test-chain-4XHTPn"
|
||||
}
|
||||
> ADDR2=DC26002735D3AA9573707CFA6D77C12349E49868
|
||||
> ID2=test-chain-4XHTPn
|
||||
> NODE2=tcp://0.0.0.0:46657
|
||||
> basecli keys add key2 --recover
|
||||
Enter a passphrase for your key:
|
||||
Repeat the passphrase:
|
||||
Enter your recovery seed phrase:
|
||||
age guide awesome month female left oxygen soccer define high grocery work desert dinner arena abandon
|
||||
key2 DC26002735D3AA9573707CFA6D77C12349E49868
|
||||
|
||||
|
||||
> basecoind start --home ~/.chain1 --address tcp://0.0.0.0:36658 --rpc.laddr tcp://0.0.0.0:36657 --p2p.laddr tcp://0.0.0.0:36656
|
||||
...
|
||||
|
||||
> basecoind start --home ~/.chain2 # --address tcp://0.0.0.0:46658 --rpc.laddr tcp://0.0.0.0:46657 --p2p.laddr tcp://0.0.0.0:46656
|
||||
...
|
||||
```
|
||||
## Check balance
|
||||
|
||||
```console
|
||||
> basecli account $ADDR1 --node $NODE1
|
||||
{
|
||||
"address": "C69FEB398A29AAB1B3C4F07DE22208F35E711BCC",
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 9007199254740992
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": 0,
|
||||
"name": ""
|
||||
}
|
||||
|
||||
> basecli account $ADDR2 --node $NODE2
|
||||
{
|
||||
"address": "DC26002735D3AA9573707CFA6D77C12349E49868",
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 9007199254740992
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": 0,
|
||||
"name": ""
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Transfer coins (addr1:chain1 -> addr2:chain2)
|
||||
|
||||
```console
|
||||
> basecli transfer --name key1 --to $ADDR2 --amount 10mycoin --chain $ID2 --chain-id $ID1 --node $NODE1
|
||||
Password to sign with 'key1':
|
||||
Committed at block 1022. Hash: E16019DCC4AA08CA70AFCFBC96028ABCC51B6AD0
|
||||
> basecli account $ADDR1 --node $NODE1
|
||||
{
|
||||
"address": "C69FEB398A29AAB1B3C4F07DE22208F35E711BCC",
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 9007199254740982
|
||||
}
|
||||
],
|
||||
"public_key": {
|
||||
"type": "ed25519",
|
||||
"data": "9828FF1780A066A0D93D840737566B697035448D6C880807322BED8919348B2B"
|
||||
},
|
||||
"sequence": 1,
|
||||
"name": ""
|
||||
}
|
||||
```
|
||||
|
||||
## Relay IBC packets
|
||||
|
||||
```console
|
||||
> basecli relay --name key2 --from-chain-id $ID1 --from-chain-node $NODE1 --to-chain-id $ID2 --to-chain-node $NODE2 --chain-id $ID2
|
||||
Password to sign with 'key2':
|
||||
I[04-03|16:18:59.984] Detected IBC packet number=0
|
||||
I[04-03|16:19:00.869] Relayed IBC packet number=0
|
||||
> basecli account $ADDR2 --node $NODE2
|
||||
{
|
||||
"address": "DC26002735D3AA9573707CFA6D77C12349E49868",
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 9007199254741002
|
||||
}
|
||||
],
|
||||
"public_key": {
|
||||
"type": "ed25519",
|
||||
"data": "F52B4FA545F4E9BFE5D7AF1DD2236899FDEF905F9B3057C38D7C01BF1B8EB52E"
|
||||
},
|
||||
"sequence": 1,
|
||||
"name": ""
|
||||
}
|
||||
|
||||
```
|
||||
@@ -0,0 +1,84 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
wire "github.com/cosmos/cosmos-sdk/wire"
|
||||
|
||||
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
||||
"github.com/cosmos/cosmos-sdk/x/ibc"
|
||||
)
|
||||
|
||||
const (
|
||||
flagTo = "to"
|
||||
flagAmount = "amount"
|
||||
flagChain = "chain"
|
||||
)
|
||||
|
||||
// IBC transfer command
|
||||
func IBCTransferCmd(cdc *wire.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "transfer",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc))
|
||||
|
||||
// get the from address
|
||||
from, err := ctx.GetFromAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// build the message
|
||||
msg, err := buildMsg(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get password
|
||||
res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String(flagTo, "", "Address to send coins")
|
||||
cmd.Flags().String(flagAmount, "", "Amount of coins to send")
|
||||
cmd.Flags().String(flagChain, "", "Destination chain to send coins")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func buildMsg(from sdk.Address) (sdk.Msg, error) {
|
||||
amount := viper.GetString(flagAmount)
|
||||
coins, err := sdk.ParseCoins(amount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dest := viper.GetString(flagTo)
|
||||
bz, err := hex.DecodeString(dest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
to := sdk.Address(bz)
|
||||
|
||||
packet := ibc.NewIBCPacket(from, to, coins, viper.GetString(client.FlagChainID),
|
||||
viper.GetString(flagChain))
|
||||
|
||||
msg := ibc.IBCTransferMsg{
|
||||
IBCPacket: packet,
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
wire "github.com/cosmos/cosmos-sdk/wire"
|
||||
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
||||
"github.com/cosmos/cosmos-sdk/x/ibc"
|
||||
)
|
||||
|
||||
// flags
|
||||
const (
|
||||
FlagFromChainID = "from-chain-id"
|
||||
FlagFromChainNode = "from-chain-node"
|
||||
FlagToChainID = "to-chain-id"
|
||||
FlagToChainNode = "to-chain-node"
|
||||
)
|
||||
|
||||
type relayCommander struct {
|
||||
cdc *wire.Codec
|
||||
address sdk.Address
|
||||
decoder sdk.AccountDecoder
|
||||
mainStore string
|
||||
ibcStore string
|
||||
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// IBC relay command
|
||||
func IBCRelayCmd(cdc *wire.Codec) *cobra.Command {
|
||||
cmdr := relayCommander{
|
||||
cdc: cdc,
|
||||
decoder: authcmd.GetAccountDecoder(cdc),
|
||||
ibcStore: "ibc",
|
||||
mainStore: "main",
|
||||
|
||||
logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "relay",
|
||||
Run: cmdr.runIBCRelay,
|
||||
}
|
||||
|
||||
cmd.Flags().String(FlagFromChainID, "", "Chain ID for ibc node to check outgoing packets")
|
||||
cmd.Flags().String(FlagFromChainNode, "tcp://localhost:46657", "<host>:<port> to tendermint rpc interface for this chain")
|
||||
cmd.Flags().String(FlagToChainID, "", "Chain ID for ibc node to broadcast incoming packets")
|
||||
cmd.Flags().String(FlagToChainNode, "tcp://localhost:36657", "<host>:<port> to tendermint rpc interface for this chain")
|
||||
|
||||
cmd.MarkFlagRequired(FlagFromChainID)
|
||||
cmd.MarkFlagRequired(FlagFromChainNode)
|
||||
cmd.MarkFlagRequired(FlagToChainID)
|
||||
cmd.MarkFlagRequired(FlagToChainNode)
|
||||
|
||||
viper.BindPFlag(FlagFromChainID, cmd.Flags().Lookup(FlagFromChainID))
|
||||
viper.BindPFlag(FlagFromChainNode, cmd.Flags().Lookup(FlagFromChainNode))
|
||||
viper.BindPFlag(FlagToChainID, cmd.Flags().Lookup(FlagToChainID))
|
||||
viper.BindPFlag(FlagToChainNode, cmd.Flags().Lookup(FlagToChainNode))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c relayCommander) runIBCRelay(cmd *cobra.Command, args []string) {
|
||||
fromChainID := viper.GetString(FlagFromChainID)
|
||||
fromChainNode := viper.GetString(FlagFromChainNode)
|
||||
toChainID := viper.GetString(FlagToChainID)
|
||||
toChainNode := viper.GetString(FlagToChainNode)
|
||||
address, err := context.NewCoreContextFromViper().GetFromAddress()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.address = address
|
||||
|
||||
c.loop(fromChainID, fromChainNode, toChainID, toChainNode)
|
||||
}
|
||||
|
||||
func (c relayCommander) loop(fromChainID, fromChainNode, toChainID,
|
||||
toChainNode string) {
|
||||
|
||||
ctx := context.NewCoreContextFromViper()
|
||||
// get password
|
||||
passphrase, err := ctx.GetPassphraseFromStdin(ctx.FromAddressName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ingressKey := ibc.IngressSequenceKey(fromChainID)
|
||||
|
||||
OUTER:
|
||||
for {
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
processedbz, err := query(toChainNode, ingressKey, c.ibcStore)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var processed int64
|
||||
if processedbz == nil {
|
||||
processed = 0
|
||||
} else if err = c.cdc.UnmarshalBinary(processedbz, &processed); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
lengthKey := ibc.EgressLengthKey(toChainID)
|
||||
egressLengthbz, err := query(fromChainNode, lengthKey, c.ibcStore)
|
||||
if err != nil {
|
||||
c.logger.Error("Error querying outgoing packet list length", "err", err)
|
||||
continue OUTER //TODO replace with continue (I think it should just to the correct place where OUTER is now)
|
||||
}
|
||||
var egressLength int64
|
||||
if egressLengthbz == nil {
|
||||
egressLength = 0
|
||||
} else if err = c.cdc.UnmarshalBinary(egressLengthbz, &egressLength); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if egressLength > processed {
|
||||
c.logger.Info("Detected IBC packet", "number", egressLength-1)
|
||||
}
|
||||
|
||||
seq := c.getSequence(toChainNode)
|
||||
|
||||
for i := processed; i < egressLength; i++ {
|
||||
egressbz, err := query(fromChainNode, ibc.EgressKey(toChainID, i), c.ibcStore)
|
||||
if err != nil {
|
||||
c.logger.Error("Error querying egress packet", "err", err)
|
||||
continue OUTER // TODO replace to break, will break first loop then send back to the beginning (aka OUTER)
|
||||
}
|
||||
|
||||
err = c.broadcastTx(seq, toChainNode, c.refine(egressbz, i, passphrase))
|
||||
seq++
|
||||
if err != nil {
|
||||
c.logger.Error("Error broadcasting ingress packet", "err", err)
|
||||
continue OUTER // TODO replace to break, will break first loop then send back to the beginning (aka OUTER)
|
||||
}
|
||||
|
||||
c.logger.Info("Relayed IBC packet", "number", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func query(node string, key []byte, storeName string) (res []byte, err error) {
|
||||
return context.NewCoreContextFromViper().WithNodeURI(node).Query(key, storeName)
|
||||
}
|
||||
|
||||
func (c relayCommander) broadcastTx(seq int64, node string, tx []byte) error {
|
||||
_, err := context.NewCoreContextFromViper().WithNodeURI(node).WithSequence(seq + 1).BroadcastTx(tx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c relayCommander) getSequence(node string) int64 {
|
||||
res, err := query(node, c.address, c.mainStore)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
account, err := c.decoder(res)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return account.GetSequence()
|
||||
}
|
||||
|
||||
func (c relayCommander) refine(bz []byte, sequence int64, passphrase string) []byte {
|
||||
var packet ibc.IBCPacket
|
||||
if err := c.cdc.UnmarshalBinary(bz, &packet); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
msg := ibc.IBCReceiveMsg{
|
||||
IBCPacket: packet,
|
||||
Relayer: c.address,
|
||||
Sequence: sequence,
|
||||
}
|
||||
|
||||
ctx := context.NewCoreContextFromViper()
|
||||
res, err := ctx.SignAndBuild(ctx.FromAddressName, passphrase, msg, c.cdc)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/ibc"
|
||||
)
|
||||
|
||||
// RegisterRoutes - Central function to define routes that get registered by the main application
|
||||
func RegisterRoutes(r *mux.Router, cdc *wire.Codec, kb keys.Keybase) {
|
||||
ctx := context.NewCoreContextFromViper()
|
||||
|
||||
r.HandleFunc("/ibc/{destchain}/{address}/send", TransferRequestHandler(cdc, kb, ctx)).Methods("POST")
|
||||
}
|
||||
|
||||
type transferBody struct {
|
||||
// Fees sdk.Coin `json="fees"`
|
||||
Amount sdk.Coins `json:"amount"`
|
||||
LocalAccountName string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
SrcChainID string `json:"src_chain_id"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
}
|
||||
|
||||
// TransferRequestHandler - http request handler to transfer coins to a address
|
||||
// on a different chain via IBC
|
||||
func TransferRequestHandler(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// collect data
|
||||
vars := mux.Vars(r)
|
||||
destChainID := vars["destchain"]
|
||||
address := vars["address"]
|
||||
|
||||
var m transferBody
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
err = cdc.UnmarshalJSON(body, &m)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
info, err := kb.Get(m.LocalAccountName)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
bz, err := hex.DecodeString(address)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
to := sdk.Address(bz)
|
||||
|
||||
// build message
|
||||
packet := ibc.NewIBCPacket(info.PubKey.Address(), to, m.Amount, m.SrcChainID, destChainID)
|
||||
msg := ibc.IBCTransferMsg{packet}
|
||||
|
||||
// sign
|
||||
ctx = ctx.WithSequence(m.Sequence)
|
||||
txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, msg, cdc)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// send
|
||||
res, err := ctx.BroadcastTx(txBytes)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
output, err := cdc.MarshalJSON(res)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(output)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user