Skip to content

Commit

Permalink
Merge pull request #102 from renproject/feat/eth_api
Browse files Browse the repository at this point in the history
Implemented account, contract and gas api for ethereum
  • Loading branch information
jazg authored Jun 15, 2021
2 parents a0b3a57 + a591e45 commit d9d85a9
Show file tree
Hide file tree
Showing 10 changed files with 804 additions and 84 deletions.
124 changes: 124 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -736,3 +736,127 @@ jobs:
-btc=true \
-bch=true \
-timeout 1500s
test-eth:
runs-on: ubuntu-latest
env:
FILECOIN_FFI_COMMIT: 8b97bd8230b77bd32f4f27e4766a6d8a03b4e801
SOLANA_FFI_COMMIT: d4c670dd402894b7e98a41bccf35d2d5066c573f
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.13
id: go

- name: Configure git for Private Modules
env:
TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
run: git config --global url."https://roynalnaruto:${TOKEN}@github.com".insteadOf "https://github.com"

- name: Check out code into the Go module directory
uses: actions/checkout@v1
with:
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
submodules: recursive

- name: Caching modules
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-aw-${{ hashFiles('**/go.sum') }}

- name: Cache extern dependencies (FFI)
id: cache-extern
uses: actions/cache@v2
env:
cache-name: cache-externs
with:
path: .extern
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.FILECOIN_FFI_COMMIT }}-${{ env.SOLANA_FFI_COMMIT }}

# Remove apt repos that are known to break from time to time
# See https://github.com/actions/virtual-environments/issues/323
- name: Install dependency packages
run: |
for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done
sudo apt-get update
sudo apt-get install -y build-essential
sudo apt-get install -y jq mesa-opencl-icd ocl-icd-opencl-dev pkg-config libudev-dev hwloc libhwloc-dev
curl https://sh.rustup.rs -sSf | sh -s -- -y
source $HOME/.cargo/env
- name: Get dependencies
run: |
export PATH=$PATH:$(go env GOPATH)/bin
source $HOME/.cargo/env
go get -u github.com/onsi/ginkgo/ginkgo
go get -u github.com/onsi/gomega/...
go get -u golang.org/x/lint/golint
go get -u github.com/loongy/covermerge
go get -u github.com/mattn/goveralls
go get -u github.com/xlab/c-for-go
- name: Install dependencies (Filecoin FFI)
if: steps.cache-extern.outputs.cache-hit != 'true'
run: |
export PATH=$PATH:$(go env GOPATH)/bin
source $HOME/.cargo/env
cd $GITHUB_WORKSPACE
mkdir .extern && cd .extern
git clone https://github.com/filecoin-project/filecoin-ffi.git
cd filecoin-ffi
git checkout ${{ env.FILECOIN_FFI_COMMIT }}
make
- name: Install dependencies (Solana FFI)
if: steps.cache-extern.outputs.cache-hit != 'true'
run: |
export PATH=$PATH:$(go env GOPATH)/bin
source $HOME/.cargo/env
cd $GITHUB_WORKSPACE/.extern
git clone https://github.com/renproject/solana-ffi.git
cd solana-ffi
git checkout ${{ env.SOLANA_FFI_COMMIT }}
make clean
make
- name: Run vetting
run: |
export PATH=$PATH:$(go env GOPATH)/bin
source $HOME/.cargo/env
go mod edit -replace=github.com/filecoin-project/filecoin-ffi=./.extern/filecoin-ffi
go mod edit -replace=github.com/renproject/solana-ffi=./.extern/solana-ffi
go vet ./...
- name: Run linting
run: |
cd $GITHUB_WORKSPACE
export PATH=$PATH:$(go env GOPATH)/bin
go get -u golang.org/x/lint/golint
golint $(go list ./... | grep -v filecoin-ffi)
- name: Run multichain infrastructure
run: |
cd $GITHUB_WORKSPACE/infra
source .env
docker-compose up -d --build ethereum
- name: Sleep until the nodes are up
uses: jakejarvis/wait-action@master
with:
time: '1m'

- name: Check on docker containers
run: docker ps -a

- name: Run tests and report test coverage
env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
export PATH=$PATH:$(go env GOPATH)/bin
source ./infra/.env
cd $GITHUB_WORKSPACE
go test \
-eth=true \
-timeout 1500s
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@
*.test

# Outputs from tools
*.out
*.out

# IDE specific files
.idea/
153 changes: 153 additions & 0 deletions chain/ethereum/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package ethereum

import (
"context"
"fmt"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/renproject/multichain/api/account"
"github.com/renproject/multichain/api/address"
"github.com/renproject/multichain/api/contract"
"github.com/renproject/pack"
)

const (
// DefaultClientRPCURL is the RPC URL used by default, to interact with the
// ethereum node.
DefaultClientRPCURL = "http://127.0.0.1:8545/"
)

// Client holds the underlying RPC client instance.
type Client struct {
ethClient *ethclient.Client
}

// NewClient creates and returns a new JSON-RPC client to the Ethereum node
func NewClient(rpcURL string) (*Client, error) {
client, err := ethclient.Dial(rpcURL)
if err != nil {
return nil, fmt.Errorf(fmt.Sprintf("dialing url: %v", rpcURL))
}
return &Client{
client,
}, nil
}

// LatestBlock returns the block number at the current chain head.
func (client *Client) LatestBlock(ctx context.Context) (pack.U64, error) {
header, err := client.ethClient.HeaderByNumber(ctx, nil)
if err != nil {
return pack.NewU64(0), fmt.Errorf("fetching header: %v", err)
}
return pack.NewU64(header.Number.Uint64()), nil
}

// Tx returns the transaction uniquely identified by the given transaction
// hash. It also returns the number of confirmations for the transaction.
func (client *Client) Tx(ctx context.Context, txID pack.Bytes) (account.Tx, pack.U64, error) {
tx, pending, err := client.ethClient.TransactionByHash(ctx, common.BytesToHash(txID))
if err != nil {
return nil, pack.NewU64(0), fmt.Errorf(fmt.Sprintf("fetching tx by hash '%v': %v", txID, err))
}
chainID, err := client.ethClient.ChainID(ctx)
if err != nil {
return nil, pack.NewU64(0), fmt.Errorf("fetching chain ID: %v", err)
}

// If the transaction is still pending, use default EIP-155 signer.
pendingTx := Tx{
ethTx: tx,
signer: types.NewEIP155Signer(chainID),
}
if pending {
return &pendingTx, 0, nil
}

receipt, err := client.ethClient.TransactionReceipt(ctx, common.BytesToHash(txID))
if err != nil {
return nil, pack.NewU64(0), fmt.Errorf("fetching recipt for tx %v : %v", txID, err)
}

// if no receipt, tx has 0 confirmations
if receipt == nil {
return &pendingTx, 0, nil
}

// reverted tx
if receipt.Status == 0 {
return nil, pack.NewU64(0), fmt.Errorf("tx %v reverted, reciept status 0", txID)
}

// tx confirmed
confirmedTx := Tx{
tx,
types.LatestSignerForChainID(chainID),
}

header, err := client.ethClient.HeaderByNumber(ctx, nil)
if err != nil {
return nil, pack.NewU64(0), fmt.Errorf("fetching header : %v", err)
}

return &confirmedTx, pack.NewU64(header.Number.Uint64() - receipt.BlockNumber.Uint64()), nil
}

// SubmitTx to the underlying blockchain network.
func (client *Client) SubmitTx(ctx context.Context, tx account.Tx) error {
switch tx := tx.(type) {
case *Tx:
err := client.ethClient.SendTransaction(ctx, tx.ethTx)
if err != nil {
return fmt.Errorf(fmt.Sprintf("sending transaction '%v': %v", tx.Hash(), err))
}
return nil
default:
return fmt.Errorf("expected type %T, got type %T", new(Tx), tx)
}
}

// AccountNonce returns the current nonce of the account. This is the nonce to
// be used while building a new transaction.
func (client *Client) AccountNonce(ctx context.Context, addr address.Address) (pack.U256, error) {
targetAddr, err := NewAddressFromHex(string(pack.String(addr)))
if err != nil {
return pack.U256{}, fmt.Errorf("bad to address '%v': %v", addr, err)
}
nonce, err := client.ethClient.NonceAt(ctx, common.Address(targetAddr), nil)
if err != nil {
return pack.U256{}, fmt.Errorf("failed to get nonce for '%v': %v", addr, err)
}

return pack.NewU256FromU64(pack.NewU64(nonce)), nil
}

// AccountBalance returns the account balancee for a given address.
func (client *Client) AccountBalance(ctx context.Context, addr address.Address) (pack.U256, error) {
targetAddr, err := NewAddressFromHex(string(pack.String(addr)))
if err != nil {
return pack.U256{}, fmt.Errorf("bad to address '%v': %v", addr, err)
}
balance, err := client.ethClient.BalanceAt(ctx, common.Address(targetAddr), nil)
if err != nil {
return pack.U256{}, fmt.Errorf("failed to get balance for '%v': %v", addr, err)
}

return pack.NewU256FromInt(balance), nil
}

// CallContract implements the multichain Contract API.
func (client *Client) CallContract(ctx context.Context, program address.Address, calldata contract.CallData) (pack.Bytes, error) {
targetAddr, err := NewAddressFromHex(string(pack.String(program)))
if err != nil {
return nil, fmt.Errorf("bad to address '%v': %v", program, err)
}
addr := common.Address(targetAddr)

callMsg := ethereum.CallMsg{
To: &addr,
Data: calldata,
}
return client.ethClient.CallContract(ctx, callMsg, nil)
}
33 changes: 33 additions & 0 deletions chain/ethereum/gas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package ethereum

import (
"context"
"fmt"
"github.com/renproject/pack"
)

// A GasEstimator returns the gas price and the provide gas limit that is needed in
// order to confirm transactions with an estimated maximum delay of one block.
type GasEstimator struct {
client *Client
}

// NewGasEstimator returns a simple gas estimator that fetches the ideal gas
// price for a ethereum transaction to be included in a block
// with minimal delay.
func NewGasEstimator(client *Client) *GasEstimator {
return &GasEstimator{
client: client,
}
}

// EstimateGas returns an estimate of the current gas price
// and returns the gas limit provided. These numbers change with congestion. These estimates
// are often a little bit off, and this should be considered when using them.
func (gasEstimator *GasEstimator) EstimateGas(ctx context.Context) (pack.U256, pack.U256, error) {
gasPrice, err := gasEstimator.client.ethClient.SuggestGasPrice(ctx)
if err != nil {
return pack.NewU256([32]byte{}), pack.NewU256([32]byte{}), fmt.Errorf("failed to get eth suggested gas price: %v", err)
}
return pack.NewU256FromInt(gasPrice), pack.NewU256FromInt(gasPrice), nil
}
Loading

0 comments on commit d9d85a9

Please sign in to comment.