Skip to content

Commit 90f479a

Browse files
authored
Merge pull request #29 from renproject/test/multichain-tests
Multichain test suite
2 parents 05be1ad + 2fc2930 commit 90f479a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2094
-197
lines changed

.github/CODEOWNERS

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @loongy @jazg

.github/workflows/test.yml

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
name: go
2+
on: [push]
3+
jobs:
4+
test:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- name: Set up Go 1.13
8+
uses: actions/setup-go@v1
9+
with:
10+
go-version: 1.13
11+
id: go
12+
13+
- name: Check out code into the Go module directory
14+
uses: actions/checkout@v1
15+
with:
16+
submodules: recursive
17+
18+
- name: Caching modules
19+
uses: actions/cache@v1
20+
with:
21+
path: ~/go/pkg/mod
22+
key: ${{ runner.os }}-go-aw-${{ hashFiles('**/go.sum') }}
23+
24+
- name: Install dependency packages
25+
run: |
26+
sudo apt-get update
27+
sudo apt-get install -y build-essential
28+
sudo apt-get install -y jq mesa-opencl-icd ocl-icd-opencl-dev pkg-config
29+
curl https://sh.rustup.rs -sSf | sh -s -- -y
30+
source $HOME/.cargo/env
31+
32+
- name: Get dependencies
33+
run: |
34+
export PATH=$PATH:$(go env GOPATH)/bin
35+
source $HOME/.cargo/env
36+
go get -u github.com/onsi/ginkgo/ginkgo
37+
go get -u github.com/onsi/gomega/...
38+
go get -u golang.org/x/lint/golint
39+
go get -u github.com/loongy/covermerge
40+
go get -u github.com/mattn/goveralls
41+
42+
- name: Run vetting
43+
run: |
44+
source $HOME/.cargo/env
45+
cd $GITHUB_WORKSPACE/chain/filecoin/filecoin-ffi
46+
make
47+
cd $GITHUB_WORKSPACE
48+
export PATH=$PATH:$(go env GOPATH)/bin
49+
go vet ./...
50+
51+
- name: Run linting
52+
run: |
53+
cd $GITHUB_WORKSPACE
54+
export PATH=$PATH:$(go env GOPATH)/bin
55+
go get -u golang.org/x/lint/golint
56+
golint $(go list ./... | grep -v filecoin-ffi)
57+
58+
- name: Run multichain infrastructure
59+
run: |
60+
cd $GITHUB_WORKSPACE/infra
61+
source .env
62+
docker-compose up -d --build \
63+
bitcoin \
64+
bitcoincash \
65+
dogecoin \
66+
terra \
67+
zcash
68+
docker run -d -p 1234:1234 -h 0.0.0.0 \
69+
--name infra_filecoin_1 rohitnarurkar/multichain_filecoin:latest
70+
71+
- name: Sleep until the nodes are up
72+
uses: jakejarvis/wait-action@master
73+
with:
74+
time: '5m'
75+
76+
- name: Check on docker containers
77+
run: docker ps -a
78+
79+
- name: Run tests and report test coverage
80+
env:
81+
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
82+
run: |
83+
export PATH=$PATH:$(go env GOPATH)/bin
84+
source ./infra/.env
85+
cd $GITHUB_WORKSPACE
86+
CI=true go test -timeout 1500s

api/account/account.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,16 @@ type Tx interface {
5656
// information, and this should be accepted during the construction of the
5757
// chain-specific transaction builder.
5858
type TxBuilder interface {
59-
BuildTx(from, to address.Address, value, nonce, gasLimit, gasPrice pack.U256, payload pack.Bytes) (Tx, error)
59+
BuildTx(ctx context.Context, from, to address.Address, value, nonce, gasLimit, gasPrice pack.U256, payload pack.Bytes) (Tx, error)
6060
}
6161

6262
// The Client interface defines the functionality required to interact with a
6363
// chain over RPC.
6464
type Client interface {
65+
// AccountNonce is the current nonce of this account, which must be used to
66+
// build a new transaction.
67+
AccountNonce(context.Context, address.Address) (pack.U256, error)
68+
6569
// Tx returns the transaction uniquely identified by the given transaction
6670
// hash. It also returns the number of confirmations for the transaction. If
6771
// the transaction cannot be found before the context is done, or the

api/gas/gas.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ type Estimator interface {
2121
// next block. In Ethereum, it would be the recommended GWEI-per-gas
2222
// required to get a transaction into one of the next few blocks (because
2323
// blocks happen a lot faster).
24-
EstimateGasPrice(context.Context) (pack.U256, error)
24+
EstimateGasPrice(context.Context) (pack.U256, pack.U256, error)
2525
}

chain/bitcoin/address.go

+20-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package bitcoin
22

33
import (
4+
"fmt"
5+
46
"github.com/btcsuite/btcd/chaincfg"
57
"github.com/btcsuite/btcutil"
68
"github.com/btcsuite/btcutil/base58"
79
"github.com/renproject/multichain/api/address"
8-
"github.com/renproject/pack"
910
)
1011

1112
// AddressEncodeDecoder implements the address.EncodeDecoder interface
@@ -37,11 +38,12 @@ func NewAddressEncoder(params *chaincfg.Params) AddressEncoder {
3738

3839
// EncodeAddress implements the address.Encoder interface
3940
func (encoder AddressEncoder) EncodeAddress(rawAddr address.RawAddress) (address.Address, error) {
41+
// Validate that the base58 address is in fact in correct format.
4042
encodedAddr := base58.Encode([]byte(rawAddr))
4143
if _, err := btcutil.DecodeAddress(encodedAddr, encoder.params); err != nil {
42-
// Check that the address is valid.
4344
return address.Address(""), err
4445
}
46+
4547
return address.Address(encodedAddr), nil
4648
}
4749

@@ -58,10 +60,21 @@ func NewAddressDecoder(params *chaincfg.Params) AddressDecoder {
5860
}
5961

6062
// DecodeAddress implements the address.Decoder interface
61-
func (decoder AddressDecoder) DecodeAddress(addr address.Address) (pack.Bytes, error) {
62-
if _, err := btcutil.DecodeAddress(string(addr), decoder.params); err != nil {
63-
// Check that the address is valid.
64-
return nil, err
63+
func (decoder AddressDecoder) DecodeAddress(addr address.Address) (address.RawAddress, error) {
64+
// Decode the checksummed base58 format address.
65+
decoded, ver, err := base58.CheckDecode(string(addr))
66+
if err != nil {
67+
return nil, fmt.Errorf("checking: %v", err)
68+
}
69+
if len(decoded) != 20 {
70+
return nil, fmt.Errorf("expected len 20, got len %v", len(decoded))
71+
}
72+
73+
// Validate the address format.
74+
switch ver {
75+
case decoder.params.PubKeyHashAddrID, decoder.params.ScriptHashAddrID:
76+
return address.RawAddress(base58.Decode(string(addr))), nil
77+
default:
78+
return nil, fmt.Errorf("unexpected address prefix")
6579
}
66-
return pack.NewBytes(base58.Decode(string(addr))), nil
6780
}

chain/bitcoin/bitcoin.go

+20
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ type Client interface {
8888
UnspentOutputs(ctx context.Context, minConf, maxConf int64, address address.Address) ([]utxo.Output, error)
8989
// Confirmations of a transaction in the Bitcoin network.
9090
Confirmations(ctx context.Context, txHash pack.Bytes) (int64, error)
91+
// EstimateSmartFee
92+
EstimateSmartFee(ctx context.Context, numBlocks int64) (float64, error)
9193
}
9294

9395
type client struct {
@@ -239,6 +241,24 @@ func (client *client) Confirmations(ctx context.Context, txHash pack.Bytes) (int
239241
return confirmations, nil
240242
}
241243

244+
// EstimateSmartFee fetches the estimated bitcoin network fees to be paid (in
245+
// BTC per kilobyte) needed for a transaction to be confirmed within `numBlocks`
246+
// blocks. An error will be returned if the bitcoin node hasn't observed enough
247+
// blocks to make an estimate for the provided target `numBlocks`.
248+
func (client *client) EstimateSmartFee(ctx context.Context, numBlocks int64) (float64, error) {
249+
resp := btcjson.EstimateSmartFeeResult{}
250+
251+
if err := client.send(ctx, &resp, "estimatesmartfee", numBlocks); err != nil {
252+
return 0.0, fmt.Errorf("estimating smart fee: %v", err)
253+
}
254+
255+
if resp.Errors != nil && len(resp.Errors) > 0 {
256+
return 0.0, fmt.Errorf("estimating smart fee: %v", resp.Errors[0])
257+
}
258+
259+
return *resp.FeeRate, nil
260+
}
261+
242262
func (client *client) send(ctx context.Context, resp interface{}, method string, params ...interface{}) error {
243263
// Encode the request.
244264
data, err := encodeRequest(method, params)

chain/bitcoin/gas.go

+25-8
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,44 @@ import (
66
"github.com/renproject/pack"
77
)
88

9+
const (
10+
btcToSatoshis = 1e8
11+
kilobyteToByte = 1024
12+
)
13+
914
// A GasEstimator returns the SATs-per-byte that is needed in order to confirm
1015
// transactions with an estimated maximum delay of one block. In distributed
1116
// networks that collectively build, sign, and submit transactions, it is
1217
// important that all nodes in the network have reached consensus on the
1318
// SATs-per-byte.
1419
type GasEstimator struct {
15-
satsPerByte pack.U256
20+
client Client
21+
numBlocks int64
1622
}
1723

1824
// NewGasEstimator returns a simple gas estimator that always returns the given
1925
// number of SATs-per-byte.
20-
func NewGasEstimator(satsPerByte pack.U256) GasEstimator {
26+
func NewGasEstimator(client Client, numBlocks int64) GasEstimator {
2127
return GasEstimator{
22-
satsPerByte: satsPerByte,
28+
client: client,
29+
numBlocks: numBlocks,
2330
}
2431
}
2532

2633
// EstimateGasPrice returns the number of SATs-per-byte that is needed in order
27-
// to confirm transactions with an estimated maximum delay of one block. It is
28-
// the responsibility of the caller to know the number of bytes in their
29-
// transaction.
30-
func (gasEstimator GasEstimator) EstimateGasPrice(_ context.Context) (pack.U256, error) {
31-
return gasEstimator.satsPerByte, nil
34+
// to confirm transactions with an estimated maximum delay of `numBlocks` block.
35+
// It is the responsibility of the caller to know the number of bytes in their
36+
// transaction. This method calls the `estimatesmartfee` RPC call to the node,
37+
// which based on a conservative (considering longer history) strategy returns
38+
// the estimated BTC per kilobyte of data in the transaction. An error will be
39+
// returned if the bitcoin node hasn't observed enough blocks to make an
40+
// estimate for the provided target `numBlocks`.
41+
func (gasEstimator GasEstimator) EstimateGasPrice(ctx context.Context) (pack.U256, pack.U256, error) {
42+
feeRate, err := gasEstimator.client.EstimateSmartFee(ctx, gasEstimator.numBlocks)
43+
if err != nil {
44+
return pack.NewU256([32]byte{}), pack.NewU256([32]byte{}), err
45+
}
46+
47+
satsPerByte := uint64(feeRate * btcToSatoshis / kilobyteToByte)
48+
return pack.NewU256FromU64(pack.NewU64(satsPerByte)), pack.NewU256([32]byte{}), nil
3249
}

chain/bitcoin/gas_test.go

+39
Original file line numberDiff line numberDiff line change
@@ -1 +1,40 @@
11
package bitcoin_test
2+
3+
import (
4+
"context"
5+
6+
"github.com/renproject/multichain/chain/bitcoin"
7+
8+
. "github.com/onsi/ginkgo"
9+
. "github.com/onsi/gomega"
10+
)
11+
12+
var _ = Describe("Gas", func() {
13+
Context("when estimating bitcoin network fee", func() {
14+
It("should work", func() {
15+
ctx, cancel := context.WithCancel(context.Background())
16+
defer cancel()
17+
18+
client := bitcoin.NewClient(bitcoin.DefaultClientOptions())
19+
20+
// estimate fee to include tx within 1 block.
21+
gasEstimator1 := bitcoin.NewGasEstimator(client, 1)
22+
gasPrice1, _, err := gasEstimator1.EstimateGasPrice(ctx)
23+
Expect(err).NotTo(HaveOccurred())
24+
25+
// estimate fee to include tx within 10 blocks.
26+
gasEstimator2 := bitcoin.NewGasEstimator(client, 10)
27+
gasPrice2, _, err := gasEstimator2.EstimateGasPrice(ctx)
28+
Expect(err).NotTo(HaveOccurred())
29+
30+
// estimate fee to include tx within 100 blocks.
31+
gasEstimator3 := bitcoin.NewGasEstimator(client, 100)
32+
gasPrice3, _, err := gasEstimator3.EstimateGasPrice(ctx)
33+
Expect(err).NotTo(HaveOccurred())
34+
35+
// expect fees in this order at the very least.
36+
Expect(gasPrice1.GreaterThanEqual(gasPrice2)).To(BeTrue())
37+
Expect(gasPrice2.GreaterThanEqual(gasPrice3)).To(BeTrue())
38+
})
39+
})
40+
})

0 commit comments

Comments
 (0)