457 lines
15 KiB
Go
457 lines
15 KiB
Go
package full
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
|
|
"go.uber.org/fx"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/filecoin-project/go-state-types/abi"
|
|
"github.com/filecoin-project/go-state-types/big"
|
|
"github.com/filecoin-project/go-state-types/exitcode"
|
|
"github.com/filecoin-project/specs-actors/actors/builtin"
|
|
|
|
"github.com/filecoin-project/lotus/api"
|
|
"github.com/filecoin-project/lotus/build"
|
|
"github.com/filecoin-project/lotus/chain/messagepool"
|
|
"github.com/filecoin-project/lotus/chain/stmgr"
|
|
"github.com/filecoin-project/lotus/chain/store"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
|
)
|
|
|
|
type EthModuleAPI interface {
|
|
EthBlockNumber(ctx context.Context) (api.EthInt, error)
|
|
EthAccounts(ctx context.Context) ([]api.EthAddress, error)
|
|
EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum api.EthInt) (api.EthInt, error)
|
|
EthGetBlockTransactionCountByHash(ctx context.Context, blkHash api.EthHash) (api.EthInt, error)
|
|
EthGetBlockByHash(ctx context.Context, blkHash api.EthHash, fullTxInfo bool) (api.EthBlock, error)
|
|
EthGetBlockByNumber(ctx context.Context, blkNum api.EthInt, fullTxInfo bool) (api.EthBlock, error)
|
|
EthGetTransactionByHash(ctx context.Context, txHash api.EthHash) (api.EthTx, error)
|
|
EthGetTransactionCount(ctx context.Context, sender api.EthAddress, blkOpt string) (api.EthInt, error)
|
|
EthGetTransactionReceipt(ctx context.Context, txHash api.EthHash) (api.EthTxReceipt, error)
|
|
EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash api.EthHash, txIndex api.EthInt) (api.EthTx, error)
|
|
EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum api.EthInt, txIndex api.EthInt) (api.EthTx, error)
|
|
EthGetCode(ctx context.Context, address api.EthAddress) (string, error)
|
|
EthGetStorageAt(ctx context.Context, address api.EthAddress, position api.EthInt, blkParam string) (string, error)
|
|
EthGetBalance(ctx context.Context, address api.EthAddress, blkParam string) (api.EthBigInt, error)
|
|
EthChainId(ctx context.Context) (api.EthInt, error)
|
|
NetVersion(ctx context.Context) (string, error)
|
|
NetListening(ctx context.Context) (bool, error)
|
|
EthProtocolVersion(ctx context.Context) (api.EthInt, error)
|
|
EthGasPrice(ctx context.Context) (api.EthBigInt, error)
|
|
EthEstimateGas(ctx context.Context, tx api.EthCall, blkParam string) (api.EthInt, error)
|
|
EthCall(ctx context.Context, tx api.EthCall, blkParam string) (api.EthBytes, error)
|
|
EthMaxPriorityFeePerGas(ctx context.Context) (api.EthBigInt, error)
|
|
EthSendRawTransaction(ctx context.Context, rawTx api.EthBytes) (api.EthHash, error)
|
|
}
|
|
|
|
var _ EthModuleAPI = *new(api.FullNode)
|
|
|
|
// EthModule provides a default implementation of EthModuleAPI.
|
|
// It can be swapped out with another implementation through Dependency
|
|
// Injection (for example with a thin RPC client).
|
|
type EthModule struct {
|
|
fx.In
|
|
|
|
Chain *store.ChainStore
|
|
Mpool *messagepool.MessagePool
|
|
StateManager *stmgr.StateManager
|
|
|
|
ChainAPI
|
|
MpoolAPI
|
|
StateAPI
|
|
}
|
|
|
|
var _ EthModuleAPI = (*EthModule)(nil)
|
|
|
|
type EthAPI struct {
|
|
fx.In
|
|
|
|
Chain *store.ChainStore
|
|
|
|
EthModuleAPI
|
|
}
|
|
|
|
func (a *EthModule) StateNetworkName(ctx context.Context) (dtypes.NetworkName, error) {
|
|
return stmgr.GetNetworkName(ctx, a.StateManager, a.Chain.GetHeaviestTipSet().ParentState())
|
|
}
|
|
|
|
func (a *EthModule) EthBlockNumber(context.Context) (api.EthInt, error) {
|
|
height := a.Chain.GetHeaviestTipSet().Height()
|
|
return api.EthInt(height), nil
|
|
}
|
|
|
|
func (a *EthModule) EthAccounts(context.Context) ([]api.EthAddress, error) {
|
|
// The lotus node is not expected to hold manage accounts, so we'll always return an empty array
|
|
return []api.EthAddress{}, nil
|
|
}
|
|
|
|
func (a *EthModule) countTipsetMsgs(ctx context.Context, ts *types.TipSet) (int, error) {
|
|
blkMsgs, err := a.Chain.BlockMsgsForTipset(ctx, ts)
|
|
if err != nil {
|
|
return 0, xerrors.Errorf("error loading messages for tipset: %v: %w", ts, err)
|
|
}
|
|
|
|
count := 0
|
|
for _, blkMsg := range blkMsgs {
|
|
// TODO: may need to run canonical ordering and deduplication here
|
|
count += len(blkMsg.BlsMessages) + len(blkMsg.SecpkMessages)
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
func (a *EthModule) EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum api.EthInt) (api.EthInt, error) {
|
|
ts, err := a.Chain.GetTipsetByHeight(ctx, abi.ChainEpoch(blkNum), nil, false)
|
|
if err != nil {
|
|
return api.EthInt(0), xerrors.Errorf("error loading tipset %s: %w", ts, err)
|
|
}
|
|
|
|
count, err := a.countTipsetMsgs(ctx, ts)
|
|
return api.EthInt(count), err
|
|
}
|
|
|
|
func (a *EthModule) EthGetBlockTransactionCountByHash(ctx context.Context, blkHash api.EthHash) (api.EthInt, error) {
|
|
ts, err := a.Chain.GetTipSetByCid(ctx, blkHash.ToCid())
|
|
if err != nil {
|
|
return api.EthInt(0), xerrors.Errorf("error loading tipset %s: %w", ts, err)
|
|
}
|
|
count, err := a.countTipsetMsgs(ctx, ts)
|
|
return api.EthInt(count), err
|
|
}
|
|
|
|
func (a *EthModule) EthGetBlockByHash(ctx context.Context, blkHash api.EthHash, fullTxInfo bool) (api.EthBlock, error) {
|
|
ts, err := a.Chain.GetTipSetByCid(ctx, blkHash.ToCid())
|
|
if err != nil {
|
|
return api.EthBlock{}, xerrors.Errorf("error loading tipset %s: %w", ts, err)
|
|
}
|
|
return a.ethBlockFromFilecoinTipSet(ctx, ts, fullTxInfo)
|
|
}
|
|
|
|
func (a *EthModule) EthGetBlockByNumber(ctx context.Context, blkNum api.EthInt, fullTxInfo bool) (api.EthBlock, error) {
|
|
ts, err := a.Chain.GetTipsetByHeight(ctx, abi.ChainEpoch(blkNum), nil, false)
|
|
if err != nil {
|
|
return api.EthBlock{}, xerrors.Errorf("error loading tipset %s: %w", ts, err)
|
|
}
|
|
return a.ethBlockFromFilecoinTipSet(ctx, ts, fullTxInfo)
|
|
}
|
|
|
|
func (a *EthModule) EthGetTransactionByHash(ctx context.Context, txHash api.EthHash) (api.EthTx, error) {
|
|
cid := txHash.ToCid()
|
|
|
|
msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, cid, api.LookbackNoLimit, true)
|
|
if err != nil {
|
|
return api.EthTx{}, nil
|
|
}
|
|
|
|
tx, err := a.ethTxFromFilecoinMessageLookup(ctx, msgLookup)
|
|
if err != nil {
|
|
return api.EthTx{}, err
|
|
}
|
|
return tx, nil
|
|
}
|
|
|
|
func (a *EthModule) EthGetTransactionCount(ctx context.Context, sender api.EthAddress, blkParam string) (api.EthInt, error) {
|
|
addr, err := sender.ToFilecoinAddress()
|
|
if err != nil {
|
|
return api.EthInt(0), err
|
|
}
|
|
nonce, err := a.Mpool.GetNonce(ctx, addr, types.EmptyTSK)
|
|
if err != nil {
|
|
return api.EthInt(0), err
|
|
}
|
|
return api.EthInt(nonce), nil
|
|
}
|
|
|
|
func (a *EthModule) EthGetTransactionReceipt(ctx context.Context, txHash api.EthHash) (api.EthTxReceipt, error) {
|
|
cid := txHash.ToCid()
|
|
|
|
msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, cid, api.LookbackNoLimit, true)
|
|
if err != nil {
|
|
return api.EthTxReceipt{}, err
|
|
}
|
|
|
|
tx, err := a.ethTxFromFilecoinMessageLookup(ctx, msgLookup)
|
|
if err != nil {
|
|
return api.EthTxReceipt{}, err
|
|
}
|
|
|
|
replay, err := a.StateAPI.StateReplay(ctx, types.EmptyTSK, cid)
|
|
if err != nil {
|
|
return api.EthTxReceipt{}, err
|
|
}
|
|
|
|
receipt, err := api.NewEthTxReceipt(tx, msgLookup, replay)
|
|
if err != nil {
|
|
return api.EthTxReceipt{}, err
|
|
}
|
|
return receipt, nil
|
|
}
|
|
|
|
func (a *EthModule) EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash api.EthHash, txIndex api.EthInt) (api.EthTx, error) {
|
|
return api.EthTx{}, nil
|
|
}
|
|
|
|
func (a *EthModule) EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum api.EthInt, txIndex api.EthInt) (api.EthTx, error) {
|
|
return api.EthTx{}, nil
|
|
}
|
|
|
|
// EthGetCode returns string value of the compiled bytecode
|
|
func (a *EthModule) EthGetCode(ctx context.Context, address api.EthAddress) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func (a *EthModule) EthGetStorageAt(ctx context.Context, address api.EthAddress, position api.EthInt, blkParam string) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func (a *EthModule) EthGetBalance(ctx context.Context, address api.EthAddress, blkParam string) (api.EthBigInt, error) {
|
|
filAddr, err := address.ToFilecoinAddress()
|
|
if err != nil {
|
|
return api.EthBigInt{}, err
|
|
}
|
|
|
|
actor, err := a.StateGetActor(ctx, filAddr, types.EmptyTSK)
|
|
if err != nil {
|
|
return api.EthBigInt{}, err
|
|
}
|
|
|
|
return api.EthBigInt{Int: actor.Balance.Int}, nil
|
|
}
|
|
|
|
func (a *EthModule) EthChainId(ctx context.Context) (api.EthInt, error) {
|
|
return api.EthInt(build.Eip155ChainId), nil
|
|
}
|
|
|
|
func (a *EthModule) NetVersion(ctx context.Context) (string, error) {
|
|
// Note that networkId is not encoded in hex
|
|
nv, err := a.StateNetworkVersion(ctx, types.EmptyTSK)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strconv.FormatUint(uint64(nv), 10), nil
|
|
}
|
|
|
|
func (a *EthModule) NetListening(ctx context.Context) (bool, error) {
|
|
return true, nil
|
|
}
|
|
|
|
func (a *EthModule) EthProtocolVersion(ctx context.Context) (api.EthInt, error) {
|
|
height := a.Chain.GetHeaviestTipSet().Height()
|
|
return api.EthInt(a.StateManager.GetNetworkVersion(ctx, height)), nil
|
|
}
|
|
|
|
func (a *EthModule) EthMaxPriorityFeePerGas(ctx context.Context) (api.EthBigInt, error) {
|
|
gasPremium, err := a.GasAPI.GasEstimateGasPremium(ctx, 0, builtin.SystemActorAddr, 10000, types.EmptyTSK)
|
|
if err != nil {
|
|
return api.EthBigInt(big.Zero()), err
|
|
}
|
|
return api.EthBigInt(gasPremium), nil
|
|
}
|
|
|
|
func (a *EthModule) EthGasPrice(ctx context.Context) (api.EthBigInt, error) {
|
|
// According to Geth's implementation, eth_gasPrice should return base + tip
|
|
// Ref: https://github.com/ethereum/pm/issues/328#issuecomment-853234014
|
|
|
|
ts := a.Chain.GetHeaviestTipSet()
|
|
baseFee := ts.Blocks()[0].ParentBaseFee
|
|
|
|
premium, err := a.EthMaxPriorityFeePerGas(ctx)
|
|
if err != nil {
|
|
return api.EthBigInt(big.Zero()), nil
|
|
}
|
|
|
|
gasPrice := big.Add(baseFee, big.Int(premium))
|
|
return api.EthBigInt(gasPrice), nil
|
|
}
|
|
|
|
func (a *EthModule) EthSendRawTransaction(ctx context.Context, rawTx api.EthBytes) (api.EthHash, error) {
|
|
return api.EthHash{}, nil
|
|
}
|
|
|
|
func (a *EthModule) applyEvmMsg(ctx context.Context, tx api.EthCall) (*api.InvocResult, error) {
|
|
from, err := tx.From.ToFilecoinAddress()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
to, err := tx.To.ToFilecoinAddress()
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("cannot get Filecoin address: %w", err)
|
|
}
|
|
msg := &types.Message{
|
|
From: from,
|
|
To: to,
|
|
Value: big.Int(tx.Value),
|
|
Method: abi.MethodNum(2),
|
|
Params: tx.Data,
|
|
GasLimit: build.BlockGasLimit,
|
|
GasFeeCap: big.Zero(),
|
|
GasPremium: big.Zero(),
|
|
}
|
|
ts := a.Chain.GetHeaviestTipSet()
|
|
|
|
// Try calling until we find a height with no migration.
|
|
var res *api.InvocResult
|
|
for {
|
|
res, err = a.StateManager.CallWithGas(ctx, msg, []types.ChainMsg{}, ts)
|
|
if err != stmgr.ErrExpensiveFork {
|
|
break
|
|
}
|
|
ts, err = a.Chain.GetTipSetFromKey(ctx, ts.Parents())
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("getting parent tipset: %w", err)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("CallWithGas failed: %w", err)
|
|
}
|
|
if res.MsgRct.ExitCode != exitcode.Ok {
|
|
return nil, xerrors.Errorf("message execution failed: exit %s, reason: %s", res.MsgRct.ExitCode, res.Error)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (a *EthModule) EthEstimateGas(ctx context.Context, tx api.EthCall, blkParam string) (api.EthInt, error) {
|
|
invokeResult, err := a.applyEvmMsg(ctx, tx)
|
|
if err != nil {
|
|
return api.EthInt(0), err
|
|
}
|
|
ret := invokeResult.MsgRct.GasUsed
|
|
return api.EthInt(ret), nil
|
|
}
|
|
|
|
func (a *EthModule) EthCall(ctx context.Context, tx api.EthCall, blkParam string) (api.EthBytes, error) {
|
|
invokeResult, err := a.applyEvmMsg(ctx, tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(invokeResult.MsgRct.Return) > 0 {
|
|
return api.EthBytes(invokeResult.MsgRct.Return), nil
|
|
}
|
|
return api.EthBytes{}, nil
|
|
}
|
|
|
|
func (a *EthModule) ethBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTxInfo bool) (api.EthBlock, error) {
|
|
parent, err := a.Chain.LoadTipSet(ctx, ts.Parents())
|
|
if err != nil {
|
|
return api.EthBlock{}, err
|
|
}
|
|
parentKeyCid, err := parent.Key().Cid()
|
|
if err != nil {
|
|
return api.EthBlock{}, err
|
|
}
|
|
parentBlkHash, err := api.EthHashFromCid(parentKeyCid)
|
|
if err != nil {
|
|
return api.EthBlock{}, err
|
|
}
|
|
|
|
blkMsgs, err := a.Chain.BlockMsgsForTipset(ctx, ts)
|
|
if err != nil {
|
|
return api.EthBlock{}, xerrors.Errorf("error loading messages for tipset: %v: %w", ts, err)
|
|
}
|
|
|
|
block := api.NewEthBlock()
|
|
|
|
// this seems to be a very expensive way to get gasUsed of the block. may need to find an efficient way to do it
|
|
gasUsed := int64(0)
|
|
for _, blkMsg := range blkMsgs {
|
|
for _, msg := range append(blkMsg.BlsMessages, blkMsg.SecpkMessages...) {
|
|
msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, msg.Cid(), api.LookbackNoLimit, true)
|
|
if err != nil {
|
|
return api.EthBlock{}, nil
|
|
}
|
|
gasUsed += msgLookup.Receipt.GasUsed
|
|
|
|
if fullTxInfo {
|
|
tx, err := a.ethTxFromFilecoinMessageLookup(ctx, msgLookup)
|
|
if err != nil {
|
|
return api.EthBlock{}, nil
|
|
}
|
|
block.Transactions = append(block.Transactions, tx)
|
|
} else {
|
|
hash, err := api.EthHashFromCid(msg.Cid())
|
|
if err != nil {
|
|
return api.EthBlock{}, err
|
|
}
|
|
block.Transactions = append(block.Transactions, hash.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
block.Number = api.EthInt(ts.Height())
|
|
block.ParentHash = parentBlkHash
|
|
block.Timestamp = api.EthInt(ts.Blocks()[0].Timestamp)
|
|
block.BaseFeePerGas = api.EthBigInt{Int: ts.Blocks()[0].ParentBaseFee.Int}
|
|
block.GasUsed = api.EthInt(gasUsed)
|
|
return block, nil
|
|
}
|
|
|
|
func (a *EthModule) ethTxFromFilecoinMessageLookup(ctx context.Context, msgLookup *api.MsgLookup) (api.EthTx, error) {
|
|
cid := msgLookup.Message
|
|
txHash, err := api.EthHashFromCid(cid)
|
|
if err != nil {
|
|
return api.EthTx{}, err
|
|
}
|
|
|
|
tsCid, err := msgLookup.TipSet.Cid()
|
|
if err != nil {
|
|
return api.EthTx{}, err
|
|
}
|
|
|
|
blkHash, err := api.EthHashFromCid(tsCid)
|
|
if err != nil {
|
|
return api.EthTx{}, err
|
|
}
|
|
|
|
msg, err := a.ChainAPI.ChainGetMessage(ctx, msgLookup.Message)
|
|
if err != nil {
|
|
return api.EthTx{}, err
|
|
}
|
|
|
|
fromFilIdAddr, err := a.StateAPI.StateLookupID(ctx, msg.From, types.EmptyTSK)
|
|
if err != nil {
|
|
return api.EthTx{}, err
|
|
}
|
|
|
|
fromEthAddr, err := api.EthAddressFromFilecoinIDAddress(fromFilIdAddr)
|
|
if err != nil {
|
|
return api.EthTx{}, err
|
|
}
|
|
|
|
toFilAddr, err := a.StateAPI.StateLookupID(ctx, msg.To, types.EmptyTSK)
|
|
if err != nil {
|
|
return api.EthTx{}, err
|
|
}
|
|
|
|
toEthAddr, err := api.EthAddressFromFilecoinIDAddress(toFilAddr)
|
|
if err != nil {
|
|
return api.EthTx{}, err
|
|
}
|
|
|
|
toAddr := &toEthAddr
|
|
_, err = api.CheckContractCreation(msgLookup)
|
|
if err == nil {
|
|
toAddr = nil
|
|
}
|
|
|
|
tx := api.EthTx{
|
|
ChainID: api.EthInt(build.Eip155ChainId),
|
|
Hash: txHash,
|
|
BlockHash: blkHash,
|
|
BlockNumber: api.EthInt(msgLookup.Height),
|
|
From: fromEthAddr,
|
|
To: toAddr,
|
|
Value: api.EthBigInt(msg.Value),
|
|
Type: api.EthInt(2),
|
|
Gas: api.EthInt(msg.GasLimit),
|
|
MaxFeePerGas: api.EthBigInt(msg.GasFeeCap),
|
|
MaxPriorityFeePerGas: api.EthBigInt(msg.GasPremium),
|
|
V: api.EthBigIntZero,
|
|
R: api.EthBigIntZero,
|
|
S: api.EthBigIntZero,
|
|
Input: msg.Params,
|
|
}
|
|
return tx, nil
|
|
}
|