package itests import ( "context" "encoding/hex" "encoding/json" "os" "testing" "time" "github.com/stretchr/testify/require" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/itests/kit" ) // TestTransactionHashLookup tests to see if lotus correctly stores a mapping from ethereum transaction hash to // Filecoin Message Cid func TestTransactionHashLookup(t *testing.T) { kit.QuietMiningLogs() blocktime := 1 * time.Second client, _, ens := kit.EnsembleMinimal( t, kit.MockProofs(), kit.ThroughRPC(), ) ens.InterconnectAll().BeginMining(blocktime) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() // install contract contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") require.NoError(t, err) contract, err := hex.DecodeString(string(contractHex)) require.NoError(t, err) // create a new Ethereum account key, ethAddr, deployer := client.EVM().NewAccount() // send some funds to the f410 address kit.SendFunds(ctx, t, client, deployer, types.FromFil(10)) gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ From: ðAddr, Data: contract, }}) require.NoError(t, err) gaslimit, err := client.EthEstimateGas(ctx, gasParams) require.NoError(t, err) maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) require.NoError(t, err) // now deploy a contract from the embryo, and validate it went well tx := ethtypes.Eth1559TxArgs{ ChainID: build.Eip155ChainId, Value: big.Zero(), Nonce: 0, MaxFeePerGas: types.NanoFil, MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas), GasLimit: int(gaslimit), Input: contract, V: big.Zero(), R: big.Zero(), S: big.Zero(), } client.EVM().SignTransaction(&tx, key.PrivateKey) rawTxHash, err := tx.TxHash() require.NoError(t, err) hash := client.EVM().SubmitTransaction(ctx, &tx) require.Equal(t, rawTxHash, hash) mpoolTx, err := client.EthGetTransactionByHash(ctx, &hash) require.NoError(t, err) require.Equal(t, hash, mpoolTx.Hash) // Wait for message to land on chain var receipt *api.EthTxReceipt for i := 0; i < 20; i++ { receipt, err = client.EthGetTransactionReceipt(ctx, hash) if err != nil || receipt == nil { time.Sleep(blocktime) continue } break } require.NoError(t, err) require.NotNil(t, receipt) // Verify that the chain transaction now has new fields set. chainTx, err := client.EthGetTransactionByHash(ctx, &hash) require.NoError(t, err) require.Equal(t, hash, chainTx.Hash) // require that the hashes are identical require.Equal(t, hash, chainTx.Hash) require.NotNil(t, chainTx.BlockNumber) require.Greater(t, uint64(*chainTx.BlockNumber), uint64(0)) require.NotNil(t, chainTx.BlockHash) require.NotEmpty(t, *chainTx.BlockHash) require.NotNil(t, chainTx.TransactionIndex) require.Equal(t, uint64(*chainTx.TransactionIndex), uint64(0)) // only transaction } // TestTransactionHashLookupBlsFilecoinMessage tests to see if lotus can find a BLS Filecoin Message using the transaction hash func TestTransactionHashLookupBlsFilecoinMessage(t *testing.T) { kit.QuietMiningLogs() blocktime := 1 * time.Second client, _, ens := kit.EnsembleMinimal( t, kit.MockProofs(), kit.ThroughRPC(), ) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() // get the existing balance from the default wallet to then split it. bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) require.NoError(t, err) // create a new address where to send funds. addr, err := client.WalletNew(ctx, types.KTBLS) require.NoError(t, err) toSend := big.Div(bal, big.NewInt(2)) msg := &types.Message{ From: client.DefaultKey.Address, To: addr, Value: toSend, } sm, err := client.MpoolPushMessage(ctx, msg, nil) require.NoError(t, err) hash, err := ethtypes.EthHashFromCid(sm.Message.Cid()) require.NoError(t, err) // Assert that BLS messages cannot be retrieved from the message pool until it lands // on-chain via the eth API. _, err = client.EthGetTransactionByHash(ctx, &hash) require.Error(t, err) // Now start mining. ens.InterconnectAll().BeginMining(blocktime) // Wait for message to land on chain var receipt *api.EthTxReceipt for i := 0; i < 20; i++ { receipt, err = client.EthGetTransactionReceipt(ctx, hash) if err != nil || receipt == nil { time.Sleep(blocktime) continue } break } require.NoError(t, err) require.NotNil(t, receipt) require.Equal(t, hash, receipt.TransactionHash) // Verify that the chain transaction now has new fields set. chainTx, err := client.EthGetTransactionByHash(ctx, &hash) require.NoError(t, err) require.Equal(t, hash, chainTx.Hash) // require that the hashes are identical require.Equal(t, hash, chainTx.Hash) require.NotNil(t, chainTx.BlockNumber) require.Greater(t, uint64(*chainTx.BlockNumber), uint64(0)) require.NotNil(t, chainTx.BlockHash) require.NotEmpty(t, *chainTx.BlockHash) require.NotNil(t, chainTx.TransactionIndex) require.Equal(t, uint64(*chainTx.TransactionIndex), uint64(0)) // only transaction // verify that we correctly reported the to address. toId, err := client.StateLookupID(ctx, addr, types.EmptyTSK) require.NoError(t, err) toEth, err := client.FilecoinAddressToEthAddress(ctx, toId) require.NoError(t, err) require.Equal(t, &toEth, chainTx.To) const expectedHex = "868e10c4" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000060" + "0000000000000000000000000000000000000000000000000000000000000000" // verify that the params are correctly encoded. expected, err := hex.DecodeString(expectedHex) require.NoError(t, err) require.Equal(t, ethtypes.EthBytes(expected), chainTx.Input) } // TestTransactionHashLookupSecpFilecoinMessage tests to see if lotus can find a Secp Filecoin Message using the transaction hash func TestTransactionHashLookupSecpFilecoinMessage(t *testing.T) { kit.QuietMiningLogs() blocktime := 1 * time.Second client, _, ens := kit.EnsembleMinimal( t, kit.MockProofs(), kit.ThroughRPC(), ) ens.InterconnectAll().BeginMining(blocktime) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() // get the existing balance from the default wallet to then split it. bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) require.NoError(t, err) // create a new address where to send funds. addr, err := client.WalletNew(ctx, types.KTSecp256k1) require.NoError(t, err) toSend := big.Div(bal, big.NewInt(2)) setupMsg := &types.Message{ From: client.DefaultKey.Address, To: addr, Value: toSend, } setupSmsg, err := client.MpoolPushMessage(ctx, setupMsg, nil) require.NoError(t, err) _, err = client.StateWaitMsg(ctx, setupSmsg.Cid(), 3, api.LookbackNoLimit, true) require.NoError(t, err) // Send message for secp account secpMsg := &types.Message{ From: addr, To: client.DefaultKey.Address, Value: big.Div(toSend, big.NewInt(2)), } secpSmsg, err := client.MpoolPushMessage(ctx, secpMsg, nil) require.NoError(t, err) hash, err := ethtypes.EthHashFromCid(secpSmsg.Cid()) require.NoError(t, err) _, err = client.StateWaitMsg(ctx, secpSmsg.Cid(), 3, api.LookbackNoLimit, true) require.NoError(t, err) receipt, err := client.EthGetTransactionReceipt(ctx, hash) require.NoError(t, err) require.NotNil(t, receipt) require.Equal(t, hash, receipt.TransactionHash) // Verify that the chain transaction now has new fields set. chainTx, err := client.EthGetTransactionByHash(ctx, &hash) require.NoError(t, err) require.Equal(t, hash, chainTx.Hash) // require that the hashes are identical require.Equal(t, hash, chainTx.Hash) require.NotNil(t, chainTx.BlockNumber) require.Greater(t, uint64(*chainTx.BlockNumber), uint64(0)) require.NotNil(t, chainTx.BlockHash) require.NotEmpty(t, *chainTx.BlockHash) require.NotNil(t, chainTx.TransactionIndex) require.Equal(t, uint64(*chainTx.TransactionIndex), uint64(0)) // only transaction // verify that we correctly reported the to address. toId, err := client.StateLookupID(ctx, client.DefaultKey.Address, types.EmptyTSK) require.NoError(t, err) toEth, err := client.FilecoinAddressToEthAddress(ctx, toId) require.NoError(t, err) require.Equal(t, &toEth, chainTx.To) const expectedHex = "868e10c4" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000060" + "0000000000000000000000000000000000000000000000000000000000000000" // verify that the params are correctly encoded. expected, err := hex.DecodeString(expectedHex) require.NoError(t, err) require.Equal(t, ethtypes.EthBytes(expected), chainTx.Input) } // TestTransactionHashLookupNonexistentMessage tests to see if lotus can find a Secp Filecoin Message using the transaction hash func TestTransactionHashLookupNonexistentMessage(t *testing.T) { kit.QuietMiningLogs() blocktime := 1 * time.Second client, _, ens := kit.EnsembleMinimal( t, kit.MockProofs(), kit.ThroughRPC(), ) ens.InterconnectAll().BeginMining(blocktime) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() cid := build.MustParseCid("bafk2bzacecapjnxnyw4talwqv5ajbtbkzmzqiosztj5cb3sortyp73ndjl76e") // We shouldn't be able to return a hash for this fake cid chainHash, err := client.EthGetTransactionHashByCid(ctx, cid) require.NoError(t, err) require.Nil(t, chainHash) calculatedHash, err := ethtypes.EthHashFromCid(cid) require.NoError(t, err) // We shouldn't be able to return a cid for this fake hash chainCid, err := client.EthGetMessageCidByTransactionHash(ctx, &calculatedHash) require.NoError(t, err) require.Nil(t, chainCid) } func TestEthGetMessageCidByTransactionHashEthTx(t *testing.T) { kit.QuietMiningLogs() blocktime := 1 * time.Second client, _, ens := kit.EnsembleMinimal( t, kit.MockProofs(), kit.ThroughRPC(), ) ens.InterconnectAll().BeginMining(blocktime) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() // install contract contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") require.NoError(t, err) contract, err := hex.DecodeString(string(contractHex)) require.NoError(t, err) // create a new Ethereum account key, ethAddr, deployer := client.EVM().NewAccount() // send some funds to the f410 address kit.SendFunds(ctx, t, client, deployer, types.FromFil(10)) gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ From: ðAddr, Data: contract, }}) require.NoError(t, err) gaslimit, err := client.EthEstimateGas(ctx, gasParams) require.NoError(t, err) maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) require.NoError(t, err) // now deploy a contract from the embryo, and validate it went well tx := ethtypes.Eth1559TxArgs{ ChainID: build.Eip155ChainId, Value: big.Zero(), Nonce: 0, MaxFeePerGas: types.NanoFil, MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas), GasLimit: int(gaslimit), Input: contract, V: big.Zero(), R: big.Zero(), S: big.Zero(), } client.EVM().SignTransaction(&tx, key.PrivateKey) sender, err := tx.Sender() require.NoError(t, err) unsignedMessage, err := tx.ToUnsignedFilecoinMessage(sender) require.NoError(t, err) rawTxHash, err := tx.TxHash() require.NoError(t, err) hash := client.EVM().SubmitTransaction(ctx, &tx) require.Equal(t, rawTxHash, hash) mpoolCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) require.NoError(t, err) require.NotNil(t, mpoolCid) mpoolTx, err := client.ChainGetMessage(ctx, *mpoolCid) require.NoError(t, err) require.NotNil(t, mpoolTx) require.Equal(t, *unsignedMessage, *mpoolTx) // Wait for message to land on chain var receipt *api.EthTxReceipt for i := 0; i < 20; i++ { receipt, err = client.EthGetTransactionReceipt(ctx, hash) if err != nil || receipt == nil { time.Sleep(blocktime) continue } break } require.NoError(t, err) require.NotNil(t, receipt) chainCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) require.NoError(t, err) require.NotNil(t, chainCid) chainTx, err := client.ChainGetMessage(ctx, *mpoolCid) require.NoError(t, err) require.NotNil(t, chainTx) require.Equal(t, *unsignedMessage, *chainTx) } func TestEthGetMessageCidByTransactionHashSecp(t *testing.T) { kit.QuietMiningLogs() blocktime := 1 * time.Second client, _, ens := kit.EnsembleMinimal( t, kit.MockProofs(), kit.ThroughRPC(), ) ens.InterconnectAll().BeginMining(blocktime) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() // get the existing balance from the default wallet to then split it. bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) require.NoError(t, err) // create a new address where to send funds. addr, err := client.WalletNew(ctx, types.KTSecp256k1) require.NoError(t, err) toSend := big.Div(bal, big.NewInt(2)) setupMsg := &types.Message{ From: client.DefaultKey.Address, To: addr, Value: toSend, } setupSmsg, err := client.MpoolPushMessage(ctx, setupMsg, nil) require.NoError(t, err) _, err = client.StateWaitMsg(ctx, setupSmsg.Cid(), 3, api.LookbackNoLimit, true) require.NoError(t, err) // Send message for secp account secpMsg := &types.Message{ From: addr, To: client.DefaultKey.Address, Value: big.Div(toSend, big.NewInt(2)), } secpSmsg, err := client.MpoolPushMessage(ctx, secpMsg, nil) require.NoError(t, err) hash, err := ethtypes.EthHashFromCid(secpSmsg.Cid()) require.NoError(t, err) mpoolCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) require.NoError(t, err) require.NotNil(t, mpoolCid) mpoolTx, err := client.ChainGetMessage(ctx, *mpoolCid) require.NoError(t, err) require.NotNil(t, mpoolTx) require.Equal(t, secpSmsg.Message, *mpoolTx) _, err = client.StateWaitMsg(ctx, secpSmsg.Cid(), 3, api.LookbackNoLimit, true) require.NoError(t, err) chainCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) require.NoError(t, err) require.NotNil(t, chainCid) chainTx, err := client.ChainGetMessage(ctx, *mpoolCid) require.NoError(t, err) require.NotNil(t, chainTx) require.Equal(t, secpSmsg.Message, *chainTx) } func TestEthGetMessageCidByTransactionHashBLS(t *testing.T) { kit.QuietMiningLogs() blocktime := 1 * time.Second client, _, ens := kit.EnsembleMinimal( t, kit.MockProofs(), kit.ThroughRPC(), ) ens.InterconnectAll().BeginMining(blocktime) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() // get the existing balance from the default wallet to then split it. bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) require.NoError(t, err) // create a new address where to send funds. addr, err := client.WalletNew(ctx, types.KTBLS) require.NoError(t, err) toSend := big.Div(bal, big.NewInt(2)) msg := &types.Message{ From: client.DefaultKey.Address, To: addr, Value: toSend, } sm, err := client.MpoolPushMessage(ctx, msg, nil) require.NoError(t, err) hash, err := ethtypes.EthHashFromCid(sm.Cid()) require.NoError(t, err) mpoolCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) require.NoError(t, err) require.NotNil(t, mpoolCid) mpoolTx, err := client.ChainGetMessage(ctx, *mpoolCid) require.NoError(t, err) require.NotNil(t, mpoolTx) require.Equal(t, sm.Message, *mpoolTx) _, err = client.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true) require.NoError(t, err) chainCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) require.NoError(t, err) require.NotNil(t, chainCid) chainTx, err := client.ChainGetMessage(ctx, *mpoolCid) require.NoError(t, err) require.NotNil(t, chainTx) require.Equal(t, sm.Message, *chainTx) }