Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eth): fix EthGetTransactionCount for pending block parameter #12520

Merged
merged 9 commits into from
Oct 1, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ From https://github.com/filecoin-project/lotus/compare/v1.28.2...release/v1.29.0

### Chores

- feat(eth): fix EthGetTransactionCount for pending block parameter ([filecoin-project/lotus#12520](https://github.com/filecoin-project/lotus/pull/11581))
- chore: ffi: copy verifier iface, mock & ffi out of storage (#11581) ([filecoin-project/lotus#11581](https://github.com/filecoin-project/lotus/pull/11581))
- docs: update LOTUS_RELEASE_FLOW.MD document (#12322) ([filecoin-project/lotus#12322](https://github.com/filecoin-project/lotus/pull/12322))
- docs: update references to releases branch (#12396) ([filecoin-project/lotus#12396](https://github.com/filecoin-project/lotus/pull/12396))
Expand Down
79 changes: 79 additions & 0 deletions itests/fevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1199,3 +1199,82 @@ func deployContractWithEth(ctx context.Context, t *testing.T, client *kit.TestFu
S: big.Zero(),
}
}

func TestEthGetTransactionCount(t *testing.T) {
ctx, cancel, client := kit.SetupFEVMTest(t)
defer cancel()

// Create a new Ethereum account
key, ethAddr, filAddr := client.EVM().NewAccount()

// Test initial state (should be zero)
initialCount, err := client.EVM().EthGetTransactionCount(ctx, ethAddr, ethtypes.NewEthBlockNumberOrHashFromPredefined("latest"))
require.NoError(t, err)
require.Zero(t, initialCount)

// Send some funds to the new account (this shouldn't change the nonce)
kit.SendFunds(ctx, t, client, filAddr, types.FromFil(10))

// Check nonce again (should still be zero)
count, err := client.EVM().EthGetTransactionCount(ctx, ethAddr, ethtypes.NewEthBlockNumberOrHashFromPredefined("latest"))
require.NoError(t, err)
require.Zero(t, count)

// Prepare and send multiple transactions
numTx := 5
var lastHash ethtypes.EthHash

contractHex, err := os.ReadFile("./contracts/SelfDestruct.hex")
require.NoError(t, err)
contract, err := hex.DecodeString(string(contractHex))
require.NoError(t, err)

gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{
From: &ethAddr,
Data: contract,
}})
require.NoError(t, err)
gaslimit, err := client.EthEstimateGas(ctx, gasParams)
require.NoError(t, err)
for i := 0; i < numTx; i++ {
tx := &ethtypes.Eth1559TxArgs{
ChainID: buildconstants.Eip155ChainId,
To: &ethAddr, // sending to self
Value: big.NewInt(1000),
Nonce: i,
MaxFeePerGas: types.NanoFil,
MaxPriorityFeePerGas: types.NanoFil,
GasLimit: int(gaslimit),
}
client.EVM().SignTransaction(tx, key.PrivateKey)
lastHash = client.EVM().SubmitTransaction(ctx, tx)

// Wait for the transaction to be mined
_, err := client.EVM().WaitTransaction(ctx, lastHash)
require.NoError(t, err)
}

// Get the latest transaction count
latestCount, err := client.EVM().EthGetTransactionCount(ctx, ethAddr, ethtypes.NewEthBlockNumberOrHashFromPredefined("latest"))
virajbhartiya marked this conversation as resolved.
Show resolved Hide resolved
require.NoError(t, err)
require.Equal(t, ethtypes.EthUint64(numTx), latestCount)

// Test with a contract
createReturn := client.EVM().DeployContract(ctx, client.DefaultKey.Address, contract)
contractAddr := createReturn.EthAddress
contractFilAddr := *createReturn.RobustAddress

// Check contract nonce (should be 1 after deployment)
contractNonce, err := client.EVM().EthGetTransactionCount(ctx, contractAddr, ethtypes.NewEthBlockNumberOrHashFromPredefined("latest"))
require.NoError(t, err)
require.Equal(t, ethtypes.EthUint64(1), contractNonce)

// Destroy the contract
_, _, err = client.EVM().InvokeContractByFuncName(ctx, client.DefaultKey.Address, contractFilAddr, "destroy()", nil)
require.NoError(t, err)

// Check contract nonce after destruction (should be 0)
contractNonceAfterDestroy, err := client.EVM().EthGetTransactionCount(ctx, contractAddr, ethtypes.NewEthBlockNumberOrHashFromPredefined("latest"))
require.NoError(t, err)
require.Zero(t, contractNonceAfterDestroy)
}
32 changes: 22 additions & 10 deletions node/impl/full/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,22 +461,37 @@ func (a *EthModule) EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid)

func (a *EthModule) EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthUint64, error) {
addr, err := sender.ToFilecoinAddress()

virajbhartiya marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return ethtypes.EthUint64(0), nil
return ethtypes.EthUint64(0), xerrors.Errorf("invalid address: %w", err)
rvagg marked this conversation as resolved.
Show resolved Hide resolved
}

// Handle "pending" block parameter separately
if blkParam.PredefinedBlock != nil && *blkParam.PredefinedBlock == "pending" {
nonce, err := a.MpoolAPI.MpoolGetNonce(ctx, addr)
if err != nil {
return ethtypes.EthUint64(0), xerrors.Errorf("failed to get nonce from mpool: %w", err)
}
return ethtypes.EthUint64(nonce), nil
}

// For all other cases, get the tipset based on the block parameter
ts, err := getTipsetByEthBlockNumberOrHash(ctx, a.Chain, blkParam)
if err != nil {
return ethtypes.EthUint64(0), xerrors.Errorf("failed to process block param: %v; %w", blkParam, err)
}

// First, handle the case where the "sender" is an EVM actor.
if actor, err := a.StateManager.LoadActor(ctx, addr, ts); err != nil {
// Get the actor state at the specified tipset
actor, err := a.StateManager.LoadActor(ctx, addr, ts)
if err != nil {
if errors.Is(err, types.ErrActorNotFound) {
return 0, nil
}
return 0, xerrors.Errorf("failed to lookup contract %s: %w", sender, err)
} else if builtinactors.IsEvmActor(actor.Code) {
return 0, xerrors.Errorf("failed to lookup actor %s: %w", sender, err)
}

// Handle EVM actor case
if builtinactors.IsEvmActor(actor.Code) {
evmState, err := builtinevm.Load(a.Chain.ActorStore(ctx), actor)
if err != nil {
return 0, xerrors.Errorf("failed to load evm state: %w", err)
Expand All @@ -490,11 +505,8 @@ func (a *EthModule) EthGetTransactionCount(ctx context.Context, sender ethtypes.
return ethtypes.EthUint64(nonce), err
}

nonce, err := a.Mpool.GetNonce(ctx, addr, ts.Key())
if err != nil {
return ethtypes.EthUint64(0), nil
}
return ethtypes.EthUint64(nonce), nil
// For non-EVM actors, get the nonce from the actor state
return ethtypes.EthUint64(actor.Nonce), nil
}

func (a *EthModule) EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*api.EthTxReceipt, error) {
Expand Down
Loading