Skip to content

Commit

Permalink
More ETH calls
Browse files Browse the repository at this point in the history
  • Loading branch information
firelizzard18 committed Jul 13, 2024
1 parent 302e9e7 commit 4e3fe37
Show file tree
Hide file tree
Showing 9 changed files with 440 additions and 19 deletions.
54 changes: 49 additions & 5 deletions internal/api/ethereum/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,78 @@ package ethimpl

import (
"context"
"math/big"

ethrpc "gitlab.com/accumulatenetwork/accumulate/pkg/api/ethereum"
"gitlab.com/accumulatenetwork/accumulate/pkg/api/v3"
"gitlab.com/accumulatenetwork/accumulate/pkg/errors"
"gitlab.com/accumulatenetwork/accumulate/pkg/types/encoding"
"gitlab.com/accumulatenetwork/accumulate/protocol"
)

type Service struct {
Network api.NetworkService
Query api.Querier
}

var _ ethrpc.Service = (*Service)(nil)

func (s *Service) EthChainId(ctx context.Context) (ethrpc.Bytes, error) {
func (s *Service) EthChainId(ctx context.Context) (*ethrpc.Number, error) {
ns, err := s.Network.NetworkStatus(ctx, api.NetworkStatusOptions{Partition: protocol.Directory})
if err != nil {
return nil, err
}
cid := protocol.EthChainID(ns.Network.NetworkName)
return cid.Bytes(), nil
return (*ethrpc.Number)(cid), nil
}

func (s *Service) EthBlockNumber(ctx context.Context) (uint64, error) {
func (s *Service) EthBlockNumber(ctx context.Context) (*ethrpc.Number, error) {
ns, err := s.Network.NetworkStatus(ctx, api.NetworkStatusOptions{Partition: protocol.Directory})
if err != nil {
return 0, err
return nil, err
}
return ns.DirectoryHeight, nil
return ethrpc.NewNumber(int64(ns.DirectoryHeight)), nil
}

func (s *Service) EthGasPrice(ctx context.Context) (*ethrpc.Number, error) {
ns, err := s.Network.NetworkStatus(ctx, api.NetworkStatusOptions{Partition: protocol.Directory})
if err != nil {
return nil, err
}
// Instead of the ACME precision, we have to use 18, because Ethereum
// wallets require native tokens to have a precision of 18
v := big.NewInt(1e18 / protocol.CreditPrecision)
v.Div(v, big.NewInt(int64(ns.Oracle.Price)))
return (*ethrpc.Number)(v), nil
}

func (s *Service) EthGetBalance(ctx context.Context, addr ethrpc.Address, block string) (*ethrpc.Number, error) {
u, err := protocol.LiteTokenAddressFromHash(addr[:], "ACME")
if err != nil {
return nil, err
}

var lite *protocol.LiteTokenAccount
_, err = api.Querier2{Querier: s.Query}.QueryAccountAs(ctx, u, nil, &lite)
if err != nil {
if errors.Is(err, errors.NotFound) {
return ethrpc.NewNumber(0), nil
}
return nil, err
}

value := new(big.Int)
value.Set(&lite.Balance)

// Adjust for precision
value.Mul(value, big.NewInt(1e18/protocol.AcmePrecision))

return (*ethrpc.Number)(value), nil
}

func (s *Service) EthGetBlockByNumber(ctx context.Context, block string, expand bool) (*ethrpc.BlockData, error) {
// TODO
return &ethrpc.BlockData{}, nil
}

func (s *Service) AccTypedData(_ context.Context, txn *protocol.Transaction, sig protocol.Signature) (*encoding.EIP712Call, error) {
Expand Down
1 change: 1 addition & 0 deletions internal/node/http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func NewHandler(opts Options) (*Handler, error) {
// Ethereum JSON-RPC
eth := ethrpc.NewHandler(&ethimpl.Service{
Network: selfClient,
Query: client,
})

// REST API
Expand Down
4 changes: 4 additions & 0 deletions pkg/api/ethereum/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ethrpc

//go:generate go run gitlab.com/accumulatenetwork/core/schema/cmd/generate schema schema.yml -w schema_gen.go
//go:generate go run gitlab.com/accumulatenetwork/core/schema/cmd/generate types schema.yml -w types_gen.go
83 changes: 73 additions & 10 deletions pkg/api/ethereum/jsonrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"math/big"
"net/http"

"gitlab.com/accumulatenetwork/accumulate/pkg/accumulate"
Expand All @@ -30,8 +29,8 @@ func NewHandler(service Service) http.Handler {
return jsonrpc.HTTPHandler{H: &JSONRPCHandler{Service: service}}
}

func (c *JSONRPCClient) EthChainId(ctx context.Context) (Bytes, error) {
return call[Bytes](c, ctx, "eth_chainId", json.RawMessage("{}"))
func (c *JSONRPCClient) EthChainId(ctx context.Context) (*Number, error) {
return clientCall[*Number](c, ctx, "eth_chainId", []any{})
}

func (h *JSONRPCHandler) eth_chainId(ctx context.Context, _ json.RawMessage) (any, error) {
Expand All @@ -43,21 +42,43 @@ func (h *JSONRPCHandler) net_version(ctx context.Context, _ json.RawMessage) (an
if err != nil {
return nil, err
}
ver := new(big.Int)
ver.SetBytes(id)
return ver, nil
return id.Int().Uint64(), nil
}

func (c *JSONRPCClient) EthBlockNumber(ctx context.Context) (uint64, error) {
return call[uint64](c, ctx, "eth_blockNumber", json.RawMessage("{}"))
func (c *JSONRPCClient) EthBlockNumber(ctx context.Context) (*Number, error) {
return clientCall[*Number](c, ctx, "eth_blockNumber", []any{})
}

func (h *JSONRPCHandler) eth_blockNumber(ctx context.Context, _ json.RawMessage) (any, error) {
return h.Service.EthBlockNumber(ctx)
}

func (c *JSONRPCClient) EthGasPrice(ctx context.Context) (*Number, error) {
return clientCall[*Number](c, ctx, "eth_gasPrice", []any{})
}

func (h *JSONRPCHandler) eth_gasPrice(ctx context.Context, _ json.RawMessage) (any, error) {
return h.Service.EthGasPrice(ctx)
}

func (c *JSONRPCClient) EthGetBalance(ctx context.Context, addr Address, block string) (*Number, error) {
return clientCall[*Number](c, ctx, "eth_getBalance", []any{})
}

func (h *JSONRPCHandler) eth_getBalance(ctx context.Context, raw json.RawMessage) (any, error) {
return handlerCall2(ctx, raw, h.Service.EthGetBalance)
}

func (c *JSONRPCClient) EthGetBlockByNumber(ctx context.Context, block string, expand bool) (*BlockData, error) {
return clientCall[*BlockData](c, ctx, "eth_getBlockByNumber", []any{block, expand})
}

func (h *JSONRPCHandler) eth_getBlockByNumber(ctx context.Context, raw json.RawMessage) (any, error) {
return handlerCall2(ctx, raw, h.Service.EthGetBlockByNumber)
}

func (c *JSONRPCClient) AccTypedData(ctx context.Context, txn *protocol.Transaction, sig protocol.Signature) (*encoding.EIP712Call, error) {
return call[*encoding.EIP712Call](c, ctx, "acc_typedData", map[string]any{
return clientCall[*encoding.EIP712Call](c, ctx, "acc_typedData", map[string]any{
"transaction": txn,
"signature": sig,
})
Expand Down Expand Up @@ -85,12 +106,43 @@ func (h *JSONRPCHandler) acc_typedData(ctx context.Context, raw json.RawMessage)
return h.Service.AccTypedData(ctx, params.Transaction, params.Signature.Value)
}

func call[V any](c *JSONRPCClient, ctx context.Context, method string, params any) (V, error) {
func clientCall[V any](c *JSONRPCClient, ctx context.Context, method string, params any) (V, error) {
var v V
err := c.Client.Call(ctx, c.Endpoint, method, params, &v)
return v, err
}

func handlerCall2[V1, V2, R any](ctx context.Context, raw json.RawMessage, fn func(context.Context, V1, V2) (R, error)) (any, error) {
v1, v2, err := decode2[V1, V2](raw)
if err != nil {
return nil, err
}
return fn(ctx, v1, v2)
}

func decode2[V1, V2 any](raw json.RawMessage) (v1 V1, v2 V2, err error) {
return v1, v2, decodeN(raw, &v1, &v2)
}

func decodeN(raw json.RawMessage, v ...any) error {
var params []json.RawMessage
err := json.Unmarshal(raw, &params)
if err != nil {
return jsonrpc.InvalidParams.With(err, err)
}
if len(params) != len(v) {
err := fmt.Errorf("expected %d parameters, got %d", len(v), len(params))
return jsonrpc.InvalidParams.With(err, err)
}
for i, param := range params {
err = json.Unmarshal(param, v[i])
if err != nil {
return jsonrpc.InvalidParams.With(err, err)
}
}
return nil
}

func (h *JSONRPCHandler) ServeJSONRPC(ctx context.Context, method string, params json.RawMessage) (result any, err error) {
switch method {
case "net_version":
Expand All @@ -100,11 +152,22 @@ func (h *JSONRPCHandler) ServeJSONRPC(ctx context.Context, method string, params
return h.eth_chainId(ctx, params)
case "eth_blockNumber":
return h.eth_blockNumber(ctx, params)
case "eth_gasPrice":
return h.eth_gasPrice(ctx, params)
case "eth_getBalance":
return h.eth_getBalance(ctx, params)
case "eth_getBlockByNumber":
return h.eth_getBlockByNumber(ctx, params)

case "acc_typedData":
return h.acc_typedData(ctx, params)
}

/*
Missing "eth_getCode"
Missing "eth_estimateGas"
Missing "eth_getTransactionCount"
*/
return nil, &jsonrpc.Error{
Code: jsonrpc.MethodNotFound,
Message: fmt.Sprintf("%q is not a supported method", method),
Expand Down
35 changes: 35 additions & 0 deletions pkg/api/ethereum/schema.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
$generate:
methods:
json: true

encode:
keepEmpty: true

import:
json: encoding/json

varPrefix:
schema: s

BlockData:
class: composite
fields:
- { name: Number, type: Number, encode: { keepEmpty: true } }
- { name: Hash, type: Bytes32, encode: { keepEmpty: true } }
- { name: ParentHash, type: Bytes32, encode: { keepEmpty: true } }
- { name: Nonce, type: Bytes, encode: { keepEmpty: true } }
- { name: SHA3Uncles, type: Bytes32, encode: { keepEmpty: true } }
- { name: LogsBloom, type: Bytes, encode: { keepEmpty: true } }
- { name: TransactionsRoot, type: Bytes32, encode: { keepEmpty: true } }
- { name: StateRoot, type: Bytes32, encode: { keepEmpty: true } }
- { name: ReceiptsRoot, type: Bytes32, encode: { keepEmpty: true } }
- { name: Miner, type: Address, encode: { keepEmpty: true } }
- { name: Difficulty, type: Number, encode: { keepEmpty: true } }
- { name: TotalDifficulty, type: Number, encode: { keepEmpty: true } }
- { name: ExtraData, type: Bytes, encode: { keepEmpty: true } }
- { name: Size, type: Number, encode: { keepEmpty: true } }
- { name: GasLimit, type: Number, encode: { keepEmpty: true } }
- { name: GasUsed, type: Number, encode: { keepEmpty: true } }
- { name: GasTimestamp, type: Number, encode: { keepEmpty: true } }
- { name: Transactions, type: '[]json.RawMessage', encode: { keepEmpty: true } }
- { name: Uncles, type: '[]Bytes32', encode: { keepEmpty: true } }
Loading

0 comments on commit 4e3fe37

Please sign in to comment.