Skip to content

Commit

Permalink
Merge branch 'feat/migrate-cosmos-to-new-api' into merge/migrate-cosm…
Browse files Browse the repository at this point in the history
…os-to-new-api
  • Loading branch information
loongy committed Sep 10, 2020
2 parents c2562d9 + 4c7ee2e commit 4f3e2dc
Show file tree
Hide file tree
Showing 16 changed files with 1,079 additions and 11 deletions.
2 changes: 0 additions & 2 deletions api/account/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ type Tx interface {
// information, and this should be accepted during the construction of the
// chain-specific transaction builder.
type TxBuilder interface {
// BuildTx consumes transaction fields to construct and return a transaction
// that implements the multichain.AccountTx interface
BuildTx(from, to address.Address, value, nonce, gasLimit, gasPrice pack.U256, payload pack.Bytes) (Tx, error)
}

Expand Down
72 changes: 72 additions & 0 deletions chain/cosmos/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package cosmos

import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/renproject/multichain/api/address"
)

// An Address is a public address that can be encoded/decoded to/from strings.
// Addresses are usually formatted different between different network
// configurations.
type Address sdk.AccAddress

// AccAddress convert Address to sdk.AccAddress
func (addr Address) AccAddress() sdk.AccAddress {
return sdk.AccAddress(addr)
}

// String implements the Stringer interface
func (addr Address) String() string {
return sdk.AccAddress(addr).String()
}

// AddressEncodeDecoder encapsulates fields that implement the
// address.EncodeDecoder interface
type AddressEncodeDecoder struct {
AddressEncoder
AddressDecoder
}

// NewAddressEncodeDecoder creates a new address encoder-decoder
func NewAddressEncodeDecoder(hrp string) AddressEncodeDecoder {
return AddressEncodeDecoder{
AddressEncoder: AddressEncoder{},
AddressDecoder: NewAddressDecoder(hrp),
}
}

// AddressEncoder implements the address.Encoder interface
type AddressEncoder struct{}

// AddressDecoder implements the address.Decoder interface
type AddressDecoder struct {
hrp string
}

// NewAddressDecoder creates a new address decoder
func NewAddressDecoder(hrp string) AddressDecoder {
return AddressDecoder{hrp: hrp}
}

// NewAddressEncoder creates a new address encoder
func NewAddressEncoder() AddressEncoder {
return AddressEncoder{}
}

// DecodeAddress consumes a human-readable representation of a cosmos
// compatible address and decodes it to its raw bytes representation.
func (decoder AddressDecoder) DecodeAddress(addr address.Address) (address.RawAddress, error) {
sdk.GetConfig().SetBech32PrefixForAccount(decoder.hrp, decoder.hrp+"pub")
rawAddr, err := sdk.AccAddressFromBech32(string(addr))
if err != nil {
return nil, err
}
return address.RawAddress(rawAddr), nil
}

// EncodeAddress consumes raw bytes and encodes them to a human-readable
// address format.
func (encoder AddressEncoder) EncodeAddress(rawAddr address.RawAddress) (address.Address, error) {
bech32Addr := sdk.AccAddress(rawAddr)
return address.Address(bech32Addr.String()), nil
}
1 change: 1 addition & 0 deletions chain/cosmos/address_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package cosmos_test
120 changes: 120 additions & 0 deletions chain/cosmos/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package cosmos

import (
"context"
"encoding/hex"
"fmt"
"time"

"github.com/renproject/multichain/api/account"
"github.com/renproject/pack"

cliContext "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
rpchttp "github.com/tendermint/tendermint/rpc/client/http"
)

const (
// DefaultClientTimeout used by the Client.
DefaultClientTimeout = time.Minute
// DefaultClientTimeoutRetry used by the Client.
DefaultClientTimeoutRetry = time.Second
// DefaultClientHost used by the Client. This should only be used for local
// deployments of the multichain.
DefaultClientHost = "http://0.0.0.0:26657"
// DefaultBroadcastMode configures the behaviour of a cosmos client while it
// interacts with the cosmos node. Allowed broadcast modes can be async, sync
// and block. "async" returns immediately after broadcasting, "sync" returns
// after the transaction has been checked and "block" waits until the
// transaction is committed to the chain.
DefaultBroadcastMode = "sync"
)

// ClientOptions are used to parameterise the behaviour of the Client.
type ClientOptions struct {
Timeout time.Duration
TimeoutRetry time.Duration
Host pack.String
BroadcastMode pack.String
}

// DefaultClientOptions returns ClientOptions with the default settings. These
// settings are valid for use with the default local deployment of the
// multichain. In production, the host, user, and password should be changed.
func DefaultClientOptions() ClientOptions {
return ClientOptions{
Timeout: DefaultClientTimeout,
TimeoutRetry: DefaultClientTimeoutRetry,
Host: pack.String(DefaultClientHost),
BroadcastMode: pack.String(DefaultBroadcastMode),
}
}

// WithHost sets the URL of the Bitcoin node.
func (opts ClientOptions) WithHost(host pack.String) ClientOptions {
opts.Host = host
return opts
}

// Client interacts with an instance of the Cosmos based network using the REST
// interface exposed by a lightclient node.
type Client struct {
opts ClientOptions
cliCtx cliContext.CLIContext
}

// NewClient returns a new Client.
func NewClient(opts ClientOptions, cdc *codec.Codec) account.Client {
httpClient, err := rpchttp.NewWithTimeout(opts.Host.String(), "websocket", uint(opts.Timeout/time.Second))
if err != nil {
panic(err)
}

cliCtx := cliContext.NewCLIContext().WithCodec(cdc).WithClient(httpClient).WithTrustNode(true)

return &Client{
opts: opts,
cliCtx: cliCtx,
}
}

// Tx query transaction with txHash
func (client *Client) Tx(ctx context.Context, txHash pack.Bytes) (account.Tx, pack.U64, error) {
res, err := utils.QueryTx(client.cliCtx, hex.EncodeToString(txHash[:]))
if err != nil {
return &StdTx{}, pack.NewU64(0), fmt.Errorf("query fail: %v", err)
}

authStdTx := res.Tx.(auth.StdTx)
if res.Code != 0 {
return &StdTx{}, pack.NewU64(0), fmt.Errorf("tx failed code: %v, log: %v", res.Code, res.RawLog)
}

stdTx, err := parseStdTx(authStdTx)
if err != nil {
return &StdTx{}, pack.NewU64(0), fmt.Errorf("parse tx failed: %v", err)
}

return &stdTx, pack.NewU64(1), nil
}

// SubmitTx to the Cosmos based network.
func (client *Client) SubmitTx(ctx context.Context, tx account.Tx) error {
txBytes, err := tx.Serialize()
if err != nil {
return fmt.Errorf("bad \"submittx\": %v", err)
}

res, err := client.cliCtx.WithBroadcastMode(client.opts.BroadcastMode.String()).BroadcastTx(txBytes)
if err != nil {
return err
}

if res.Code != 0 {
return fmt.Errorf("tx failed code: %v, log: %v", res.Code, res.RawLog)
}

return nil
}
1 change: 1 addition & 0 deletions chain/cosmos/cosmos.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package cosmos
13 changes: 13 additions & 0 deletions chain/cosmos/cosmos_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cosmos_test

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestCosmos(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Cosmos Suite")
}
140 changes: 140 additions & 0 deletions chain/cosmos/cosmos_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package cosmos_test

// import (
// "encoding/hex"
// "os"
// "strings"
// "time"

// "github.com/tendermint/tendermint/crypto/secp256k1"

// sdk "github.com/cosmos/cosmos-sdk/types"
// "github.com/terra-project/core/app"

// "github.com/renproject/multichain/chain/cosmos"
// "github.com/renproject/multichain/compat/cosmoscompat"
// "github.com/renproject/pack"

// . "github.com/onsi/ginkgo"
// . "github.com/onsi/gomega"
// )

// var _ = Describe("Cosmos", func() {
// Context("when submitting transactions", func() {
// Context("when sending LUNA to multiple addresses", func() {
// It("should work", func() {
// // Load private key, and assume that the associated address has
// // funds to spend. You can do this by setting TERRA_PK to the
// // value specified in the `./multichaindeploy/.env` file.
// pkEnv := os.Getenv("TERRA_PK")
// if pkEnv == "" {
// panic("TERRA_PK is undefined")
// }

// addrEnv := os.Getenv("TERRA_ADDRESS")
// if addrEnv == "" {
// panic("TERRA_ADDRESS is undefined")
// }

// // pkEnv := "a96e62ed3955e65be32703f12d87b6b5cf26039ecfa948dc5107a495418e5330"
// // addrEnv := "terra10s4mg25tu6termrk8egltfyme4q7sg3hl8s38u"

// pkBz, err := hex.DecodeString(pkEnv)
// Expect(err).ToNot(HaveOccurred())

// var pk secp256k1.PrivKeySecp256k1
// copy(pk[:], pkBz)

// addr := cosmoscompat.Address(pk.PubKey().Address())

// decoder := cosmos.NewAddressDecoder("terra")
// expectedAddr, err := decoder.DecodeAddress(pack.NewString(addrEnv))
// Expect(err).ToNot(HaveOccurred())
// Expect(addr).Should(Equal(expectedAddr))

// pk1 := secp256k1.GenPrivKey()
// pk2 := secp256k1.GenPrivKey()

// recipient1 := sdk.AccAddress(pk1.PubKey().Address())
// recipient2 := sdk.AccAddress(pk2.PubKey().Address())

// msgs := []cosmoscompat.MsgSend{
// {
// FromAddress: cosmoscompat.Address(addr),
// ToAddress: cosmoscompat.Address(recipient1),
// Amount: cosmoscompat.Coins{
// {
// Denom: "uluna",
// Amount: pack.U64(1000000),
// },
// },
// },
// {
// FromAddress: cosmoscompat.Address(addr),
// ToAddress: cosmoscompat.Address(recipient2),
// Amount: cosmoscompat.Coins{
// {
// Denom: "uluna",
// Amount: pack.U64(2000000),
// },
// },
// },
// }

// client := cosmoscompat.NewClient(cosmoscompat.DefaultClientOptions(), app.MakeCodec())
// account, err := client.Account(addr)
// Expect(err).NotTo(HaveOccurred())

// txBuilder := cosmos.NewTxBuilder(cosmoscompat.TxOptions{
// AccountNumber: account.AccountNumber,
// SequenceNumber: account.SequenceNumber,
// Gas: 200000,
// ChainID: "testnet",
// Memo: "multichain",
// Fees: cosmoscompat.Coins{
// {
// Denom: "uluna",
// Amount: pack.U64(3000),
// },
// },
// }).WithCodec(app.MakeCodec())

// tx, err := txBuilder.BuildTx(msgs)
// Expect(err).NotTo(HaveOccurred())

// sigBytes, err := pk.Sign(tx.SigBytes())
// Expect(err).NotTo(HaveOccurred())

// pubKey := pk.PubKey().(secp256k1.PubKeySecp256k1)
// err = tx.Sign([]cosmoscompat.StdSignature{
// {
// Signature: pack.NewBytes(sigBytes),
// PubKey: pack.NewBytes(pubKey[:]),
// },
// })
// Expect(err).NotTo(HaveOccurred())

// txHash, err := client.SubmitTx(tx, pack.NewString("sync"))
// Expect(err).NotTo(HaveOccurred())

// for {
// // Loop until the transaction has at least a few
// // confirmations. This implies that the transaction is
// // definitely valid, and the test has passed. We were
// // successfully able to use the multichain to construct and
// // submit a Bitcoin transaction!
// _, err := client.Tx(txHash)
// if err == nil {
// break
// }

// if !strings.Contains(err.Error(), "not found") {
// Expect(err).NotTo(HaveOccurred())
// }

// time.Sleep(10 * time.Second)
// }
// })
// })
// })
// })
30 changes: 30 additions & 0 deletions chain/cosmos/gas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cosmos

import (
"context"

"github.com/renproject/multichain/api/gas"
"github.com/renproject/pack"
)

// A GasEstimator returns the gas-per-byte that is needed in order to confirm
// transactions with an estimated maximum delay of one block. In distributed
// networks that collectively build, sign, and submit transactions, it is
// important that all nodes in the network have reached consensus on the
// gas-per-byte.
type GasEstimator struct {
gasPerByte pack.U256
}

// NewGasEstimator returns a simple gas estimator that always returns the same
// amount of gas-per-byte.
func NewGasEstimator(gasPerByte pack.U256) gas.Estimator {
return &GasEstimator{
gasPerByte: gasPerByte,
}
}

// EstimateGasPrice returns gas required per byte for Cosmos-compatible chains.
func (gasEstimator *GasEstimator) EstimateGasPrice(ctx context.Context) (pack.U256, error) {
return gasEstimator.gasPerByte, nil
}
Loading

0 comments on commit 4f3e2dc

Please sign in to comment.