fix: gas: update ffi & correct the message inclusion cost in nv18 (#10228)

Co-authored-by: Raúl Kripalani <raul@protocol.ai>
This commit is contained in:
Steven Allen 2023-02-10 04:48:20 -08:00 committed by GitHub
parent 54ed1f12ff
commit 80fccba93f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 129 additions and 4 deletions

View File

@ -213,6 +213,16 @@ var Prices = map[abi.ChainEpoch]Pricelist{
verifyReplicaUpdate: 36316136, verifyReplicaUpdate: 36316136,
}, },
build.UpgradeHyggeHeight: &pricelistV0{
computeGasMulti: 1,
storageGasMulti: 1300, // only applies to messages/return values.
onChainMessageComputeBase: 38863 + 475000, // includes the actor update cost
onChainMessageStorageBase: 36,
onChainMessageStoragePerByte: 1,
onChainReturnValuePerByte: 1,
},
} }
// PricelistByEpoch finds the latest prices for the given epoch // PricelistByEpoch finds the latest prices for the given epoch

2
extern/filecoin-ffi vendored

@ -1 +1 @@
Subproject commit 32df7d1f15e46406c5efafb5de5c2c70ec34d685 Subproject commit 9e4fe75471f635a4ba91f17ee76f75200eb91de8

View File

@ -58,7 +58,7 @@ func buildInputFromuint64(number uint64) []byte {
// recursive delegate calls that fail due to gas limits are currently getting to 229 iterations // recursive delegate calls that fail due to gas limits are currently getting to 229 iterations
// before running out of gas // before running out of gas
func recursiveDelegatecallFail(ctx context.Context, t *testing.T, client *kit.TestFullNode, filename string, count uint64) { func recursiveDelegatecallFail(ctx context.Context, t *testing.T, client *kit.TestFullNode, filename string, count uint64) {
expectedIterationsBeforeFailing := int(229) expectedIterationsBeforeFailing := int(220)
fromAddr, idAddr := client.EVM().DeployContractFromFilename(ctx, filename) fromAddr, idAddr := client.EVM().DeployContractFromFilename(ctx, filename)
t.Log("recursion count - ", count) t.Log("recursion count - ", count)
inputData := buildInputFromuint64(count) inputData := buildInputFromuint64(count)
@ -158,7 +158,7 @@ func TestFEVMRecursiveDelegatecallCount(t *testing.T) {
ctx, cancel, client := kit.SetupFEVMTest(t) ctx, cancel, client := kit.SetupFEVMTest(t)
defer cancel() defer cancel()
highestSuccessCount := uint64(235) highestSuccessCount := uint64(225)
filename := "contracts/RecursiveDelegeatecall.hex" filename := "contracts/RecursiveDelegeatecall.hex"
recursiveDelegatecallSuccess(ctx, t, client, filename, uint64(1)) recursiveDelegatecallSuccess(ctx, t, client, filename, uint64(1))

View File

@ -2,16 +2,23 @@ package itests
import ( import (
"context" "context"
"math"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/exitcode"
"github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors/builtin"
"github.com/filecoin-project/lotus/chain/actors/builtin/account" "github.com/filecoin-project/lotus/chain/actors/builtin/account"
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/itests/kit"
) )
@ -37,8 +44,9 @@ func TestEstimateGasNoFunds(t *testing.T) {
sm, err := client.MpoolPushMessage(ctx, msg, nil) sm, err := client.MpoolPushMessage(ctx, msg, nil)
require.NoError(t, err) require.NoError(t, err)
_, err = client.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true) ret, err := client.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true)
require.NoError(t, err) require.NoError(t, err)
require.True(t, ret.Receipt.ExitCode.IsSuccess())
// Make sure we can estimate gas even if we have no funds. // Make sure we can estimate gas even if we have no funds.
msg2 := &types.Message{ msg2 := &types.Message{
@ -52,3 +60,110 @@ func TestEstimateGasNoFunds(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotZero(t, limit) require.NotZero(t, limit)
} }
// 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")
}