synapsecns/sanguine

View on GitHub
ethergo/chain/client/near/block.go

Summary

Maintainability
A
1 hr
Test Coverage
package near

import (
    "context"
    "encoding/json"
    "fmt"
    "math/big"

    "github.com/ethereum/go-ethereum"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/common/hexutil"
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/rpc"
)

// AuroraClient contains the methods necessary to use the aurora client adapter.
type AuroraClient interface {
    // CallContext performs a JSON-RPC call with the given arguments. If the context is
    // canceled before the call has successfully returned, CallContext returns immediately.
    //
    // The result must be a pointer so that package json can unmarshal into it. You
    // can also pass nil, in which case the result is ignored.
    CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error
    // BatchCallContext sends all given requests as a single batch and waits for the server
    // to return a response for all of them. The wait duration is bounded by the
    // context's deadline.
    //
    // In contrast to CallContext, BatchCallContext only returns errors that have occurred
    // while sending the request. Any error specific to a request is reported through the
    // Error field of the corresponding BatchElem.
    //
    // Note that batch calls may not be executed atomically on the server side.
    BatchCallContext(ctx context.Context, b []rpc.BatchElem) error
    // BlockNumber gets the latest block number
    BlockNumber(ctx context.Context) (uint64, error)
    // TransactionByHash gets a tx by hash
    TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error)
}

// BlockByNumber overrides the get block unmarshalling to be compatible with near.
func BlockByNumber(ctx context.Context, c AuroraClient, number *big.Int) (*types.Block, error) {
    if number == nil {
        latestBlock, err := c.BlockNumber(ctx)
        if err != nil {
            //nolint:wrapcheck
            return nil, err
        }

        return getBlock(ctx, c, "eth_getBlockByNumber", toBlockNumArg(new(big.Int).SetUint64(latestBlock)))
    }
    return getBlock(ctx, c, "eth_getBlockByNumber", toBlockNumArg(number))
}

// BlockByHash overrides the get block unmarshalling to be compatible with near.
func BlockByHash(ctx context.Context, c AuroraClient, hash common.Hash) (*types.Block, error) {
    return getBlock(ctx, c, "eth_getBlockByHash", hash, true)
}

func toBlockNumArg(number *big.Int) string {
    if number == nil {
        return "latest"
    }
    pending := big.NewInt(-1)
    if number.Cmp(pending) == 0 {
        return "pending"
    }
    return hexutil.EncodeBig(number)
}

// see https://git.io/Jy1Oc
type rpcBlock struct {
    Hash         common.Hash   `json:"hash"`
    Transactions []common.Hash `json:"transactions"`
    UncleHashes  []common.Hash `json:"uncles"`
}

// getBlock is a temporary workaround for https://github.com/aurora-is-near/aurora-relayer/pull/141
// amd will be removed as soon as possible.
//
//nolint:gocognit,cyclop
func getBlock(ctx context.Context, c AuroraClient, method string, args ...interface{}) (*types.Block, error) {
    var raw json.RawMessage
    err := c.CallContext(ctx, &raw, method, args...)
    if err != nil {
        //nolint:wrapcheck
        return nil, err
    } else if len(raw) == 0 {
        //nolint:wrapcheck
        return nil, ethereum.NotFound
    }
    // Decode header and transactions.
    var head *types.Header
    var body rpcBlock
    if err := json.Unmarshal(raw, &head); err != nil {
        //nolint: wrapcheck
        return nil, err
    }
    if err := json.Unmarshal(raw, &body); err != nil {
        //nolint: wrapcheck
        return nil, err
    }

    // near doesn't have uncles: manually overwrite the uncle hash with the empty uncle hash
    head.UncleHash = types.EmptyUncleHash

    if len(body.Transactions) == 0 {
        head.TxHash = types.EmptyRootHash
    }

    // Quick-verify transaction and uncle lists. This mostly helps with debugging the server.
    if head.UncleHash == types.EmptyUncleHash && len(body.UncleHashes) > 0 {
        return nil, fmt.Errorf("server returned non-empty uncle list but block header indicates no uncles")
    }
    if head.UncleHash != types.EmptyUncleHash && len(body.UncleHashes) == 0 {
        return nil, fmt.Errorf("server returned empty uncle list but block header indicates uncles")
    }
    if head.TxHash == types.EmptyRootHash && len(body.Transactions) > 0 {
        return nil, fmt.Errorf("server returned non-empty transaction list but block header indicates no transactions")
    }
    if head.TxHash != types.EmptyRootHash && len(body.Transactions) == 0 {
        return nil, fmt.Errorf("server returned empty transaction list but block header indicates transactions")
    }

    // there are no uncles in aurora
    var uncles []*types.Header

    txes, err := getTransactions(ctx, c, body.Transactions, body.Hash)
    if err != nil {
        return nil, err
    }

    return types.NewBlockWithHeader(head).WithBody(txes, uncles), nil
}