Compute correct base burns, miner tip and so on

Signed-off-by: Jakub Sztandera <kubuxu@protocol.ai>
This commit is contained in:
Jakub Sztandera 2020-08-06 21:05:16 +02:00
parent 722d6e8ffb
commit b384ac6943
No known key found for this signature in database
GPG Key ID: 9A9AF56F8B3879BA
5 changed files with 99 additions and 31 deletions

View File

@ -234,7 +234,7 @@ func (sm *StateManager) ApplyBlocks(ctx context.Context, parentEpoch abi.ChainEp
}
receipts = append(receipts, &r.MessageReceipt)
gasReward = big.Add(gasReward, big.Mul(m.GasPrice, big.NewInt(r.GasUsed)))
gasReward = big.Add(gasReward, r.MinerTip)
penalty = big.Add(penalty, r.Penalty)
if cb != nil {

View File

@ -189,9 +189,11 @@ func toLotusMsg(msg *vtypes.Message) *types.Message {
Nonce: msg.CallSeqNum,
Method: msg.Method,
Value: msg.Value,
GasPrice: msg.GasPrice,
GasLimit: msg.GasLimit,
Value: msg.Value,
GasPrice: msg.GasPrice,
GasLimit: msg.GasLimit,
GasFeeCap: msg.GasPrice, // TODO: update chian val to use GasFeeCap
GasPremium: big.Zero(),
Params: msg.Params,
}

View File

@ -1,7 +1,8 @@
package vm
import (
"math/big"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
)
const (
@ -9,9 +10,21 @@ const (
gasOveruseDenom = 10
)
// ComputeGasOutputs computes amount of gas to be refunded and amount of gas to be burned
type GasOutputs struct {
BaseFeeBurn abi.TokenAmount
OverEstimationBurn abi.TokenAmount
MinerPenalty abi.TokenAmount
MinerTip abi.TokenAmount
Refund abi.TokenAmount
GasRefund int64
GasBurned int64
}
// ComputeGasOverestimationBurn computes amount of gas to be refunded and amount of gas to be burned
// Result is (refund, burn)
func ComputeGasOutputs(gasUsed, gasLimit int64) (int64, int64) {
func ComputeGasOverestimationBurn(gasUsed, gasLimit int64) (int64, int64) {
if gasUsed == 0 {
return 0, gasLimit
}
@ -37,8 +50,48 @@ func ComputeGasOutputs(gasUsed, gasLimit int64) (int64, int64) {
// needs bigint, as it overflows in pathological case gasLimit > 2^32 gasUsed = gasLimit / 2
gasToBurn := big.NewInt(gasLimit - gasUsed)
gasToBurn = gasToBurn.Mul(gasToBurn, big.NewInt(over))
gasToBurn = gasToBurn.Div(gasToBurn, big.NewInt(gasUsed))
gasToBurn = big.Mul(gasToBurn, big.NewInt(over))
gasToBurn = big.Div(gasToBurn, big.NewInt(gasUsed))
return gasLimit - gasUsed - gasToBurn.Int64(), gasToBurn.Int64()
}
func ComputeGasOutputs(gasUsed, gasLimit int64, baseFee, feeCap, gasPremium abi.TokenAmount) GasOutputs {
gasUsedBig := big.NewInt(gasUsed)
out := GasOutputs{
BaseFeeBurn: big.Zero(),
OverEstimationBurn: big.Zero(),
MinerPenalty: big.Zero(),
MinerTip: big.Zero(),
Refund: big.Zero(),
}
baseFeeToPay := baseFee
if baseFee.Cmp(feeCap.Int) > 0 {
baseFeeToPay = feeCap
out.MinerPenalty = big.Mul(big.Sub(baseFee, feeCap), gasUsedBig)
}
out.BaseFeeBurn = big.Mul(baseFeeToPay, big.NewInt(gasUsed))
minerTip := gasPremium
if big.Cmp(big.Add(baseFeeToPay, minerTip), feeCap) > 0 {
minerTip = big.Sub(feeCap, baseFeeToPay)
}
out.MinerTip = big.Mul(minerTip, big.NewInt(gasLimit))
out.GasRefund, out.GasBurned = ComputeGasOverestimationBurn(gasUsed, gasLimit)
if out.GasBurned != 0 {
gasBurnedBig := big.NewInt(out.GasBurned)
out.OverEstimationBurn = big.Mul(baseFeeToPay, gasBurnedBig)
minerPenalty := big.Mul(big.Sub(baseFee, baseFeeToPay), gasBurnedBig)
out.MinerPenalty = big.Add(out.MinerPenalty, minerPenalty)
}
requiredFunds := big.Mul(big.NewInt(gasLimit), feeCap)
refund := big.Sub(requiredFunds, out.BaseFeeBurn)
refund = big.Sub(refund, out.MinerTip)
refund = big.Sub(refund, out.OverEstimationBurn)
out.Refund = refund
return out
}

View File

@ -31,7 +31,7 @@ func TestGasBurn(t *testing.T) {
for _, test := range tests {
test := test
t.Run(fmt.Sprintf("%v", test), func(t *testing.T) {
refund, toBurn := ComputeGasOutputs(test.used, test.limit)
refund, toBurn := ComputeGasOverestimationBurn(test.used, test.limit)
assert.Equal(t, test.refund, refund, "refund")
assert.Equal(t, test.burn, toBurn, "burned")
})

View File

@ -9,6 +9,7 @@ import (
bstore "github.com/filecoin-project/lotus/lib/blockstore"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/filecoin-project/specs-actors/actors/builtin"
block "github.com/ipfs/go-block-format"
@ -196,6 +197,7 @@ type ApplyRet struct {
types.MessageReceipt
ActorErr aerrors.ActorError
Penalty types.BigInt
MinerTip types.BigInt
ExecutionTrace types.ExecutionTrace
Duration time.Duration
}
@ -315,6 +317,7 @@ func (vm *VM) ApplyImplicitMessage(ctx context.Context, msg *types.Message) (*Ap
ActorErr: actorErr,
ExecutionTrace: rt.executionTrace,
Penalty: types.NewInt(0),
MinerTip: types.NewInt(0),
Duration: time.Since(start),
}, actorErr
}
@ -347,14 +350,15 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet,
ExitCode: exitcode.SysErrOutOfGas,
GasUsed: 0,
},
Penalty: types.BigMul(msg.GasPrice, types.NewInt(uint64(msgGasCost))),
Penalty: types.BigMul(vm.baseFee, abi.NewTokenAmount(msgGasCost)),
Duration: time.Since(start),
MinerTip: big.Zero(),
}, nil
}
st := vm.cstate
minerPenaltyAmount := types.BigMul(msg.GasPrice, types.NewInt(uint64(msgGasCost)))
minerPenaltyAmount := types.BigMul(vm.baseFee, abi.NewTokenAmount(msg.GasLimit))
fromActor, err := st.GetActor(msg.From)
// this should never happen, but is currently still exercised by some tests
if err != nil {
@ -367,6 +371,7 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet,
ActorErr: aerrors.Newf(exitcode.SysErrSenderInvalid, "actor not found: %s", msg.From),
Penalty: minerPenaltyAmount,
Duration: time.Since(start),
MinerTip: big.Zero(),
}, nil
}
return nil, xerrors.Errorf("failed to look up from actor: %w", err)
@ -382,6 +387,7 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet,
ActorErr: aerrors.Newf(exitcode.SysErrSenderInvalid, "send from not account actor: %s", fromActor.Code),
Penalty: minerPenaltyAmount,
Duration: time.Since(start),
MinerTip: big.Zero(),
}, nil
}
@ -395,10 +401,11 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet,
"actor nonce invalid: msg:%d != state:%d", msg.Nonce, fromActor.Nonce),
Penalty: minerPenaltyAmount,
Duration: time.Since(start),
MinerTip: big.Zero(),
}, nil
}
gascost := types.BigMul(types.NewInt(uint64(msg.GasLimit)), msg.GasPrice)
gascost := types.BigMul(types.NewInt(uint64(msg.GasLimit)), msg.GasFeeCap)
if fromActor.Balance.LessThan(gascost) {
return &ApplyRet{
MessageReceipt: types.MessageReceipt{
@ -409,6 +416,7 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet,
"actor balance less than needed: %s < %s", types.FIL(fromActor.Balance), types.FIL(gascost)),
Penalty: minerPenaltyAmount,
Duration: time.Since(start),
MinerTip: big.Zero(),
}, nil
}
@ -468,25 +476,25 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet,
if gasUsed < 0 {
gasUsed = 0
}
gasRefund, gasToBurn := ComputeGasOutputs(gasUsed, msg.GasLimit)
gasOutputs := ComputeGasOutputs(gasUsed, msg.GasLimit, vm.baseFee, msg.GasFeeCap, msg.GasPremium)
if err := vm.transferFromGasHolder(builtin.BurntFundsActorAddr, gasHolder,
gasOutputs.BaseFeeBurn); err != nil {
return nil, xerrors.Errorf("failed to burn base fee: %w", err)
}
if err := vm.transferFromGasHolder(builtin.RewardActorAddr, gasHolder, gasOutputs.MinerTip); err != nil {
return nil, xerrors.Errorf("failed to give miner gas reward: %w", err)
}
if err := vm.transferFromGasHolder(builtin.BurntFundsActorAddr, gasHolder,
gasOutputs.OverEstimationBurn); err != nil {
return nil, xerrors.Errorf("failed to burn overestimation fee: %w", err)
}
// refund unused gas
refund := types.BigMul(types.NewInt(uint64(gasRefund)), msg.GasPrice)
if err := vm.transferFromGasHolder(msg.From, gasHolder, refund); err != nil {
return nil, xerrors.Errorf("failed to refund gas")
}
if gasToBurn > 0 {
// burn overallocated gas
burn := types.BigMul(types.NewInt(uint64(gasToBurn)), msg.GasPrice)
if err := vm.transferFromGasHolder(builtin.BurntFundsActorAddr, gasHolder, burn); err != nil {
return nil, xerrors.Errorf("failed to burn over estimated gas")
}
}
gasReward := types.BigMul(msg.GasPrice, types.NewInt(uint64(gasUsed)))
if err := vm.transferFromGasHolder(builtin.RewardActorAddr, gasHolder, gasReward); err != nil {
return nil, xerrors.Errorf("failed to give miner gas reward: %w", err)
if err := vm.transferFromGasHolder(msg.From, gasHolder, gasOutputs.Refund); err != nil {
return nil, xerrors.Errorf("failed to refund gas: %w", err)
}
if types.BigCmp(types.NewInt(0), gasHolder.Balance) != 0 {
@ -501,7 +509,8 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet,
},
ActorErr: actorErr,
ExecutionTrace: rt.executionTrace,
Penalty: types.NewInt(0),
Penalty: gasOutputs.MinerPenalty,
MinerTip: gasOutputs.MinerTip,
Duration: time.Since(start),
}, nil
}
@ -780,6 +789,10 @@ func (vm *VM) transferFromGasHolder(addr address.Address, gasHolder *types.Actor
return xerrors.Errorf("attempted to transfer negative value from gas holder")
}
if amt.Equals(big.NewInt(0)) {
return nil
}
return vm.cstate.MutateActor(addr, func(a *types.Actor) error {
if err := deductFunds(gasHolder, amt); err != nil {
return err