2022-07-07 20:06:51 +00:00
|
|
|
package itests
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-02-10 12:48:20 +00:00
|
|
|
"math"
|
2022-07-07 20:06:51 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
2023-02-10 12:48:20 +00:00
|
|
|
"github.com/filecoin-project/go-address"
|
|
|
|
"github.com/filecoin-project/go-state-types/abi"
|
2022-07-07 20:06:51 +00:00
|
|
|
"github.com/filecoin-project/go-state-types/big"
|
2023-02-10 12:48:20 +00:00
|
|
|
"github.com/filecoin-project/go-state-types/exitcode"
|
2022-07-07 20:06:51 +00:00
|
|
|
|
|
|
|
"github.com/filecoin-project/lotus/api"
|
2023-02-10 12:48:20 +00:00
|
|
|
"github.com/filecoin-project/lotus/build"
|
|
|
|
"github.com/filecoin-project/lotus/chain/actors/builtin"
|
2022-07-07 20:06:51 +00:00
|
|
|
"github.com/filecoin-project/lotus/chain/actors/builtin/account"
|
|
|
|
"github.com/filecoin-project/lotus/chain/types"
|
2023-02-10 12:48:20 +00:00
|
|
|
"github.com/filecoin-project/lotus/chain/vm"
|
2022-07-07 20:06:51 +00:00
|
|
|
"github.com/filecoin-project/lotus/itests/kit"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestEstimateGasNoFunds(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
kit.QuietMiningLogs()
|
|
|
|
|
|
|
|
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs())
|
|
|
|
ens.InterconnectAll().BeginMining(10 * time.Millisecond)
|
|
|
|
|
|
|
|
// create a new address
|
|
|
|
addr, err := client.WalletNew(ctx, types.KTBLS)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Create that address.
|
|
|
|
msg := &types.Message{
|
|
|
|
From: client.DefaultKey.Address,
|
|
|
|
To: addr,
|
|
|
|
Value: big.Zero(),
|
|
|
|
}
|
|
|
|
|
|
|
|
sm, err := client.MpoolPushMessage(ctx, msg, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-02-10 12:48:20 +00:00
|
|
|
ret, err := client.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true)
|
2022-07-07 20:06:51 +00:00
|
|
|
require.NoError(t, err)
|
2023-02-10 12:48:20 +00:00
|
|
|
require.True(t, ret.Receipt.ExitCode.IsSuccess())
|
2022-07-07 20:06:51 +00:00
|
|
|
|
2022-07-11 15:57:27 +00:00
|
|
|
// Make sure we can estimate gas even if we have no funds.
|
2022-07-07 20:06:51 +00:00
|
|
|
msg2 := &types.Message{
|
|
|
|
From: addr,
|
|
|
|
To: client.DefaultKey.Address,
|
|
|
|
Method: account.Methods.PubkeyAddress,
|
|
|
|
Value: big.Zero(),
|
|
|
|
}
|
|
|
|
|
|
|
|
limit, err := client.GasEstimateGasLimit(ctx, msg2, types.EmptyTSK)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotZero(t, limit)
|
|
|
|
}
|
2023-02-10 12:48:20 +00:00
|
|
|
|
|
|
|
// Make sure that we correctly calculate the inclusion cost. Especially, make sure the FVM and Lotus
|
|
|
|
// agree and that:
|
|
|
|
// 1. The FVM will never charge _less_ than the inclusion cost.
|
|
|
|
// 2. The FVM will never fine a storage provider for including a message that costs exactly the
|
|
|
|
// inclusion cost.
|
|
|
|
func TestEstimateInclusion(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
kit.QuietMiningLogs()
|
|
|
|
|
|
|
|
// We need this to be "correct" in this test so that lotus can get the correct gas value
|
|
|
|
// (which, unfortunately, looks at the height and not the current network version).
|
|
|
|
oldPrices := vm.Prices
|
|
|
|
vm.Prices = map[abi.ChainEpoch]vm.Pricelist{
|
|
|
|
0: oldPrices[build.UpgradeHyggeHeight],
|
|
|
|
}
|
|
|
|
t.Cleanup(func() { vm.Prices = oldPrices })
|
|
|
|
|
|
|
|
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs())
|
|
|
|
ens.InterconnectAll().BeginMining(10 * time.Millisecond)
|
|
|
|
|
|
|
|
// First, try sending a message that should have no fees beyond the inclusion cost. I.e., it
|
|
|
|
// does absolutely nothing:
|
|
|
|
msg := &types.Message{
|
|
|
|
From: client.DefaultKey.Address,
|
|
|
|
To: client.DefaultKey.Address,
|
|
|
|
Value: big.Zero(),
|
|
|
|
GasLimit: 0,
|
|
|
|
GasFeeCap: abi.NewTokenAmount(10000),
|
|
|
|
GasPremium: big.Zero(),
|
|
|
|
}
|
|
|
|
|
|
|
|
burntBefore, err := client.WalletBalance(ctx, builtin.BurntFundsActorAddr)
|
|
|
|
require.NoError(t, err)
|
|
|
|
balanceBefore, err := client.WalletBalance(ctx, client.DefaultKey.Address)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Sign the message and compute the correct inclusion cost.
|
|
|
|
var smsg *types.SignedMessage
|
|
|
|
for i := 0; ; i++ {
|
|
|
|
var err error
|
|
|
|
smsg, err = client.WalletSignMessage(ctx, client.DefaultKey.Address, msg)
|
|
|
|
require.NoError(t, err)
|
|
|
|
estimatedGas := vm.PricelistByEpoch(math.MaxInt).OnChainMessage(smsg.ChainLength()).Total()
|
|
|
|
if estimatedGas == msg.GasLimit {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
// Try 10 times to get the right gas value.
|
|
|
|
require.Less(t, i, 10, "unable to estimate gas: %s != %s", estimatedGas, msg.GasLimit)
|
|
|
|
msg.GasLimit = estimatedGas
|
|
|
|
}
|
|
|
|
|
|
|
|
cid, err := client.MpoolPush(ctx, smsg)
|
|
|
|
require.NoError(t, err)
|
|
|
|
ret, err := client.StateWaitMsg(ctx, cid, 3, api.LookbackNoLimit, true)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, ret.Receipt.ExitCode.IsSuccess())
|
|
|
|
require.Equal(t, msg.GasLimit, ret.Receipt.GasUsed)
|
|
|
|
|
|
|
|
// Then try sending a message of the same size that tries to create an actor. This should
|
|
|
|
// get successfully included, but fail with out of gas:
|
|
|
|
|
|
|
|
// Mutate the last byte to get a new address of the same length.
|
|
|
|
toBytes := msg.To.Bytes()
|
|
|
|
toBytes[len(toBytes)-1] += 1 //nolint:golint
|
|
|
|
newAddr, err := address.NewFromBytes(toBytes)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
msg.Nonce = 1
|
|
|
|
msg.To = newAddr
|
|
|
|
smsg, err = client.WalletSignMessage(ctx, client.DefaultKey.Address, msg)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
cid, err = client.MpoolPush(ctx, smsg)
|
|
|
|
require.NoError(t, err)
|
|
|
|
ret, err = client.StateWaitMsg(ctx, cid, 3, api.LookbackNoLimit, true)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, ret.Receipt.ExitCode, exitcode.SysErrOutOfGas)
|
|
|
|
require.Equal(t, msg.GasLimit, ret.Receipt.GasUsed)
|
|
|
|
|
|
|
|
// Now make sure that the client is the only contributor to the burnt funds actor (the
|
|
|
|
// miners should not have been fined for either message).
|
|
|
|
|
|
|
|
burntAfter, err := client.WalletBalance(ctx, builtin.BurntFundsActorAddr)
|
|
|
|
require.NoError(t, err)
|
|
|
|
balanceAfter, err := client.WalletBalance(ctx, client.DefaultKey.Address)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
burnt := big.Sub(burntAfter, burntBefore)
|
|
|
|
spent := big.Sub(balanceBefore, balanceAfter)
|
|
|
|
|
|
|
|
require.Equal(t, burnt, spent)
|
|
|
|
|
|
|
|
// Finally, try to submit a message with too little gas. This should fail.
|
|
|
|
|
|
|
|
msg.Nonce = 2
|
|
|
|
msg.To = msg.From
|
|
|
|
msg.GasLimit -= 1 //nolint:golint
|
|
|
|
|
|
|
|
smsg, err = client.WalletSignMessage(ctx, client.DefaultKey.Address, msg)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
_, err = client.MpoolPush(ctx, smsg)
|
|
|
|
require.ErrorContains(t, err, "will not be included in a block")
|
|
|
|
require.ErrorContains(t, err, "cannot be less than the cost of storing a message")
|
|
|
|
}
|