filecoin lotus转账fil流程和gas计算

tech2025-11-20  1

文章目录

转账流程 和 gas计算完整信息转账签名签名过程 消息广播广播过程 自动计算gas发送交易GAS计算gasLimit,gasPreium,gasFeeCapGasEstimateGasLimit调用过程计算相关常量 GasEstimateGasPremiumGasEstimateFeeCap 区块浏览器中展现的值Base Fee Burn 和 Over Estimation Burn 计算方式ComputeGasOverestimationBurn(gasUsed, gasLimit)

转账流程 和 gas计算

从cli命令send中, 有两种转账方式

指定所有参数,如gas,nonce仅指定接收者和金额

完整信息转账

路径cli/send.go

message内容

msg := &types.Message{ From: fromAddr, To: toAddr, Value: types.BigInt(val), GasPremium: gp, GasFeeCap: gfc, GasLimit: cctx.Int64("gas-limit"), Method: method,//0 Params: params,//可为nil }

签名

sm, err := api.WalletSignMessage(ctx, fromAddr, msg) if err != nil { return err }

签名过程

路径node/impl/full/wallet.go

//K -> fromAddr func (a *WalletAPI) WalletSignMessage(ctx context.Context, k address.Address, msg *types.Message) (*types.SignedMessage, error) { mcid := msg.Cid()//msg序列化,最后得到一个block //内部对msg序列化,得到[]byte,再针对这个数据进行sum,得到一个cid //返回一个block,内部数据就是[]byte,和cid sig, err := a.WalletSign(ctx, k, mcid.Bytes())//注意传输的实际就是msg序列化后的数据 if err != nil { return nil, xerrors.Errorf("failed to sign message: %w", err) } return &types.SignedMessage{ Message: *msg,//msg原始数据 Signature: *sig,//通过私钥对数据进行签名后的数据(签名类型,签名后的[]byte (长度65) ) }, nil }

a.WalletSign

func (a *WalletAPI) WalletSign(ctx context.Context, k address.Address, msg []byte) (*crypto.Signature, error) { keyAddr, err := a.StateManager.ResolveToKeyAddress(ctx, k, nil)//内部应该是验证地址合法性,BLS/SECP256K1类型地址,禁止Actor类型 if err != nil { return nil, xerrors.Errorf("failed to resolve ID address: %w", keyAddr) } return a.Wallet.Sign(ctx, keyAddr, msg) }

a.Wallet.Sign 路径 chain/wallet/wallet.go

func (w *Wallet) Sign(ctx context.Context, addr address.Address, msg []byte) (*crypto.Signature, error) { ki, err := w.findKey(addr)//获取一个地址的公私钥 if err != nil { return nil, err } if ki == nil { return nil, xerrors.Errorf("signing using key '%s': %w", addr.String(), types.ErrKeyInfoNotFound) } //通过私钥对msg进行签名 return sigs.Sign(ActSigType(ki.Type), ki.PrivateKey, msg) }

sigs.Sign 路径lib/sigs/sigs.go

// Sign takes in signature type, private key and message. Returns a signature for that message. // Valid sigTypes are: "secp256k1" and "bls" func Sign(sigType crypto.SigType, privkey []byte, msg []byte) (*crypto.Signature, error) { sv, ok := sigs[sigType] if !ok { return nil, fmt.Errorf("cannot sign message with signature of unsupported type: %v", sigType) } sb, err := sv.Sign(privkey, msg) if err != nil { return nil, err } return &crypto.Signature{ Type: sigType, Data: sb, }, nil }

sign 对应加密方式签名 路径

lib/sigs/secp/init.golib/sigs/secp/init.go func (secpSigner) Sign(pk []byte, msg []byte) ([]byte, error) { b2sum := blake2b.Sum256(msg) sig, err := crypto.Sign(pk, b2sum[:]) if err != nil { return nil, err } return sig, nil }

过程完毕

消息广播

_, err = api.MpoolPush(ctx, sm)// //sm的结构 type SignedMessage struct { Message Message//原始信息 Signature crypto.Signature//签名类型,使用私钥签名后的数据 }

广播过程

api.MpoolPush 路径 node/impl/full/mpool.go

func (a *MpoolAPI) MpoolPush(ctx context.Context, smsg *types.SignedMessage) (cid.Cid, error) { return a.Mpool.Push(smsg) }

a.Mpool.Push 路径chain/messagepool/messagepool.go

func (mp *MessagePool) Push(m *types.SignedMessage) (cid.Cid, error) { err := mp.checkMessage(m) if err != nil { return cid.Undef, err } // serialize push access to reduce lock contention mp.addSema <- struct{}{} defer func() { <-mp.addSema }() mp.curTsLk.Lock() curTs := mp.curTs epoch := curTs.Height() mp.curTsLk.Unlock() if err := mp.verifyMsgBeforePush(m, epoch); err != nil { return cid.Undef, err } msgb, err := m.Serialize() if err != nil { return cid.Undef, err } mp.curTsLk.Lock() if mp.curTs != curTs { mp.curTsLk.Unlock() return cid.Undef, ErrTryAgain } if err := mp.addTs(m, mp.curTs); err != nil { mp.curTsLk.Unlock() return cid.Undef, err } mp.curTsLk.Unlock() mp.lk.Lock() if err := mp.addLocal(m, msgb); err != nil { mp.lk.Unlock() return cid.Undef, err } mp.lk.Unlock() return m.Cid(), mp.api.PubSubPublish(build.MessagesTopic(mp.netName), msgb) }

mp.verifyMsgBeforePush

func (mp *MessagePool) verifyMsgBeforePush(m *types.SignedMessage, epoch abi.ChainEpoch) error { minGas := vm.PricelistByEpoch(epoch).OnChainMessage(m.ChainLength())//获取最小gas //gas计算,另外说明 //验证消息参数是否正确,以及验证gas是否足够 if err := m.VMMessage().ValidForBlockInclusion(minGas.Total()); err != nil { return xerrors.Errorf("message will not be included in a block: %w", err) } return nil }

mp.Add(m)

func (mp *MessagePool) Add(m *types.SignedMessage) error { // big messages are bad, anti DOS if m.Size() > 32*1024 { return xerrors.Errorf("mpool message too large (%dB): %w", m.Size(), ErrMessageTooBig) } if m.Message.To == address.Undef { return ErrInvalidToAddr } //2_000_000_000 最大转账金额 if !m.Message.Value.LessThan(types.TotalFilecoinInt) { return ErrMessageValueTooHigh } //验证签名, 内部是通过签名后的信息,和msg的原始序列化信息,得出公钥, //通过公钥导出地址,对比from是否一样 if err := mp.VerifyMsgSig(m); err != nil { log.Warnf("mpooladd signature verification failed: %s", err) return err } // serialize push access to reduce lock contention mp.addSema <- struct{}{} defer func() { <-mp.addSema }() mp.curTsLk.Lock() defer mp.curTsLk.Unlock() return mp.addTs(m, mp.curTs) }

mp.addTs

func (mp *MessagePool) addTs(m *types.SignedMessage, curTs *types.TipSet) error { //内部实现暂忽略,目的是获取到最小nonce snonce, err := mp.getStateNonce(m.Message.From, curTs) if err != nil { return xerrors.Errorf("failed to look up actor state nonce: %s: %w", err, ErrBroadcastAnyway) } if snonce > m.Message.Nonce { return xerrors.Errorf("minimum expected nonce is %d: %w", snonce, ErrNonceTooLow) } //获取余额 balance, err := mp.getStateBalance(m.Message.From, curTs) if err != nil { return xerrors.Errorf("failed to check sender balance: %s: %w", err, ErrBroadcastAnyway) } //内部是capCap*limit,少了value判断 if balance.LessThan(m.Message.RequiredFunds()) { return xerrors.Errorf("not enough funds (required: %s, balance: %s): %w", types.FIL(m.Message.RequiredFunds()), types.FIL(balance), ErrNotEnoughFunds) } mp.lk.Lock() defer mp.lk.Unlock() return mp.addLocked(m) }

mp.addLocked(m)

func (mp *MessagePool) addLocked(m *types.SignedMessage) error { log.Debugf("mpooladd: %s %d", m.Message.From, m.Message.Nonce) if m.Signature.Type == crypto.SigTypeBLS { mp.blsSigCache.Add(m.Cid(), m.Signature) } if m.Message.GasLimit > build.BlockGasLimit { return xerrors.Errorf("given message has too high of a gas limit") } //一次put signMsg,一次Msg,都实现了ToStorageBlock,本地缓存 if _, err := mp.api.PutMessage(m); err != nil { log.Warnf("mpooladd cs.PutMessage failed: %s", err) return err } if _, err := mp.api.PutMessage(&m.Message); err != nil { log.Warnf("mpooladd cs.PutMessage failed: %s", err) return err } mset, ok := mp.pending[m.Message.From] if !ok { mset = newMsgSet() mp.pending[m.Message.From] = mset } //添加消息池子map, incr, err := mset.add(m, mp) if err != nil { log.Info(err) return err } if incr { //如果是新增,池子+1 mp.currentSize++ if mp.currentSize > mp.cfg.SizeLimitHigh {//如果交易池msg量大于配置数量,发送减少信号 //注意内部也有一个冷却时间,冷却时间外会实际出发减少操作 //查看runLoop() // send signal to prune messages if it hasnt already been sent select { case mp.pruneTrigger <- struct{}{}: default: } } } mp.changes.Pub(api.MpoolUpdate{ Type: api.MpoolAdd, Message: m, }, localUpdates) return nil }

mset.add()

func (ms *msgSet) add(m *types.SignedMessage, mp *MessagePool) (bool, error) { //如果本地是0,或者大于本地记录,则将下一次的默认nonce自增1 if len(ms.msgs) == 0 || m.Message.Nonce >= ms.nextNonce { ms.nextNonce = m.Message.Nonce + 1 } exms, has := ms.msgs[m.Message.Nonce] if has { //如果存在同nonce的交易在交易池 //且两次的交易信息不一样,需要判断能否覆盖,需要手续费大于一定值 if m.Cid() != exms.Cid() { // check if RBF passes //=>ReplaceByFeeRatioDefault 检查覆盖手续费是否足够 minPrice := exms.Message.GasPremium //rbfNumBig、rbfNumBig都是常量 //min + min/(256*(1.25-1))/256 //256*0.25=64 //min = min + min/64/256 minPrice = types.BigAdd(minPrice, types.BigDiv(types.BigMul(minPrice, rbfNumBig), rbfNumBig)) //再基础上+1, 必须大于等于计算得来的gasPrice minPrice = types.BigAdd(minPrice, types.NewInt(1)) if types.BigCmp(m.Message.GasPremium, minPrice) >= 0 { log.Infow("add with RBF", "oldpremium", exms.Message.GasPremium, "newpremium", m.Message.GasPremium, "addr", m.Message.From, "nonce", m.Message.Nonce) } else { log.Info("add with duplicate nonce") return false, xerrors.Errorf("message from %s with nonce %d already in mpool,"+ " increase GasPremium to %s from %s to trigger replace by fee: %w", m.Message.From, m.Message.Nonce, minPrice, m.Message.GasPremium, ErrRBFTooLowPremium) } } } ms.msgs[m.Message.Nonce] = m return !has, nil }

mp.addLocal(m, msgb)

//msgb是m序列化得来的[]byte func (mp *MessagePool) addLocal(m *types.SignedMessage, msgb []byte) error { mp.localAddrs[m.Message.From] = struct{}{}//只是标记是否有交易要重新push //本地缓存消息 if err := mp.localMsgs.Put(datastore.NewKey(string(m.Cid().Bytes())), msgb); err != nil { return xerrors.Errorf("persisting local message: %w", err) } return nil }

自动计算gas发送交易

命令 send --from=xxx xxxto 0.1

node/impl/full/mpool.go

func (a *MpoolAPI) MpoolPushMessage(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec) (*types.SignedMessage, error) { //获取地址 { fromA, err := a.Stmgr.ResolveToKeyAddress(ctx, msg.From, nil) if err != nil { return nil, xerrors.Errorf("getting key address: %w", err) } done, err := a.PushLocks.TakeLock(ctx, fromA) if err != nil { return nil, xerrors.Errorf("taking lock: %w", err) } defer done() } if msg.Nonce != 0 { return nil, xerrors.Errorf("MpoolPushMessage expects message nonce to be 0, was %d", msg.Nonce) } //计算gasLimit,gasPreium,gasFeeCap msg, err := a.GasAPI.GasEstimateMessageGas(ctx, msg, spec, types.EmptyTSK) if err != nil { return nil, xerrors.Errorf("GasEstimateMessageGas error: %w", err) } sign := func(from address.Address, nonce uint64) (*types.SignedMessage, error) { msg.Nonce = nonce if msg.From.Protocol() == address.ID { log.Warnf("Push from ID address (%s), adjusting to %s", msg.From, from) msg.From = from } //获取余额 b, err := a.WalletBalance(ctx, msg.From) if err != nil { return nil, xerrors.Errorf("mpool push: getting origin balance: %w", err) } //判断价格是否小于转账值 if b.LessThan(msg.Value) { return nil, xerrors.Errorf("mpool push: not enough funds: %s < %s", b, msg.Value) } //调用签名,这个签名/推送中有说明 return a.WalletSignMessage(ctx, from, msg) } var m *types.SignedMessage again: m, err = a.Mpool.PushWithNonce(ctx, msg.From, sign) if err == messagepool.ErrTryAgain { log.Debugf("temporary failure while pushing message: %s; retrying", err) goto again //如果发送过程中有相同nonce先push到交易池,需重新推送 } return m, err }

a.Mpool.PushWithNonce(ctx, msg.From, sign) 路径 chain/messagepool/messagepool.go

func (mp *MessagePool) PushWithNonce(ctx context.Context, addr address.Address, cb func(address.Address, uint64) (*types.SignedMessage, error)) (*types.SignedMessage, error) { // serialize push access to reduce lock contention mp.addSema <- struct{}{} defer func() { <-mp.addSema }() mp.curTsLk.Lock() mp.lk.Lock() curTs := mp.curTs fromKey := addr if fromKey.Protocol() == address.ID { var err error fromKey, err = mp.api.StateAccountKey(ctx, fromKey, mp.curTs) if err != nil { mp.lk.Unlock() mp.curTsLk.Unlock() return nil, xerrors.Errorf("resolving sender key: %w", err) } } nonce, err := mp.getNonceLocked(fromKey, mp.curTs) if err != nil { mp.lk.Unlock() mp.curTsLk.Unlock() return nil, xerrors.Errorf("get nonce locked failed: %w", err) } // release the locks for signing mp.lk.Unlock() mp.curTsLk.Unlock() msg, err := cb(fromKey, nonce) if err != nil { return nil, err } // reacquire the locks and check state for consistency mp.curTsLk.Lock() defer mp.curTsLk.Unlock() if mp.curTs != curTs { return nil, ErrTryAgain } mp.lk.Lock() defer mp.lk.Unlock() //再次获取是防止有同nonce交易发出去了 nonce2, err := mp.getNonceLocked(fromKey, mp.curTs) if err != nil { return nil, xerrors.Errorf("get nonce locked failed: %w", err) } if nonce2 != nonce { return nil, ErrTryAgain } //后面的流程和push中的一样 if err := mp.verifyMsgBeforePush(msg, mp.curTs.Height()); err != nil { return nil, err } msgb, err := msg.Serialize() if err != nil { return nil, err } if err := mp.addLocked(msg); err != nil { return nil, xerrors.Errorf("add locked failed: %w", err) } if err := mp.addLocal(msg, msgb); err != nil { log.Errorf("addLocal failed: %+v", err) } return msg, mp.api.PubSubPublish(build.MessagesTopic(mp.netName), msgb) }

GAS

需先了解EIP1559的概念

计算gasLimit,gasPreium,gasFeeCap

路径 node/impl/full/gas.go

func (a *GasAPI) GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, _ types.TipSetKey) (*types.Message, error) { if msg.GasLimit == 0 { // gasLimit, err := a.GasEstimateGasLimit(ctx, msg, types.TipSetKey{}) if err != nil { return nil, xerrors.Errorf("estimating gas used: %w", err) } msg.GasLimit = int64(float64(gasLimit) * a.Mpool.GetConfig().GasLimitOverestimation) } if msg.GasPremium == types.EmptyInt || types.BigCmp(msg.GasPremium, types.NewInt(0)) == 0 { //内部实现没太看明白, 取最近的N*2个块所有的GasPremium均值(此处n是 2,往前推) gasPremium, err := a.GasEstimateGasPremium(ctx, 2, msg.From, msg.GasLimit, types.TipSetKey{}) if err != nil { return nil, xerrors.Errorf("estimating gas price: %w", err) } msg.GasPremium = gasPremium } if msg.GasFeeCap == types.EmptyInt || types.BigCmp(msg.GasFeeCap, types.NewInt(0)) == 0 { feeCap, err := a.GasEstimateFeeCap(ctx, msg, 10, types.EmptyTSK) if err != nil { return nil, xerrors.Errorf("estimating fee cap: %w", err) } msg.GasFeeCap = big.Add(feeCap, msg.GasPremium) } capGasFee(msg, spec.Get().MaxFee) return msg, nil }

GasEstimateGasLimit

调用过程
// node/impl/full/gas.go func GasEstimateGasLimit(){ msg.GasLimit = build.BlockGasLimit//10_000_000_000 msg.GasFeeCap = types.NewInt(uint64(build.MinimumBaseFee) + 1)//101 msg.GasPremium = types.NewInt(1)//1 a.Stmgr.CallWithGas() } // chain/stmgr/call.go func CallWithGas(){ ret, err := vmi.ApplyMessage(ctx, msgApply) } // chain/vm/vm.go func ApplyMessage(){ msgGas := pl.OnChainMessage(cmsg.ChainLength()) ret, actorErr, rt := vm.send(ctx, msg, nil, &msgGas, start) } func send(){ //计算1 if gasCharge != nil {//即上面的 msgGas if err := rt.chargeGasSafe(*gasCharge); err != nil { // this should never happen return nil, aerrors.Wrap(err, "not enough gas for initial message charge, this should not happen"), rt } } //计算2 if aerr := rt.chargeGasSafe(rt.Pricelist().OnMethodInvocation(msg.Value, msg.Method)); aerr != nil { return nil, aerrors.Wrap(aerr, "not enough gas for method invocation") } } // chain/vm/runtime.go func (rt *Runtime) chargeGasInternal(gas GasCharge,){ toUse := gas.ComputeGas*GasComputeMulti + gas.StorageGas*GasStorageMulti rt.gasUsed += toUse } //计算1的 GasCharge // chain/vm/gas_v0.go func (pl *pricelistV0) OnChainMessage(msgSize int) GasCharge { return newGasCharge("OnChainMessage", pl.onChainMessageComputeBase, pl.onChainMessageStorageBase+pl.onChainMessageStoragePerByte*int64(msgSize)) } //计算2的 GasCharge const ( MethodSend = abi.MethodNum(0) MethodConstructor = abi.MethodNum(1) ) func (pl *pricelistV0) OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) GasCharge { ret := pl.sendBase extra := "" if big.Cmp(value, abi.NewTokenAmount(0)) != 0 { ret += pl.sendTransferFunds if methodNum == builtin.MethodSend {//转账method是 0 // transfer only ret += pl.sendTransferOnlyPremium } extra += "t" } if methodNum != builtin.MethodSend { extra += "i" // running actors is cheaper becase we hand over to actors ret += pl.sendInvokeMethod } return newGasCharge("OnMethodInvocation", ret, 0).WithExtra(extra) }
计算相关常量
const ( GasStorageMulti = 1000 GasComputeMulti = 1 ) var prices = map[abi.ChainEpoch]Pricelist{ abi.ChainEpoch(0): &pricelistV0{ onChainMessageComputeBase: 38863, onChainMessageStorageBase: 36, onChainMessageStoragePerByte: 1, onChainReturnValuePerByte: 1, sendBase: 29233, sendTransferFunds: 27500, sendTransferOnlyPremium: 159672, sendInvokeMethod: -5377, ipldGetBase: 75242, ipldPutBase: 84070, ipldPutPerByte: 1, createActorCompute: 1108454, createActorStorage: 36 + 40, deleteActor: -(36 + 40), // -createActorStorage verifySignature: map[crypto.SigType]int64{ crypto.SigTypeBLS: 16598605, crypto.SigTypeSecp256k1: 1637292, }, hashingBase: 31355, computeUnsealedSectorCidBase: 98647, verifySealBase: 2000, // TODO gas , it VerifySeal syscall is not used verifyPostLookup: map[abi.RegisteredPoStProof]scalingCost{ abi.RegisteredPoStProof_StackedDrgWindow512MiBV1: { flat: 123861062, scale: 9226981, }, abi.RegisteredPoStProof_StackedDrgWindow32GiBV1: { flat: 748593537, scale: 85639, }, abi.RegisteredPoStProof_StackedDrgWindow64GiBV1: { flat: 748593537, scale: 85639, }, }, verifyConsensusFault: 495422, }, }

计算结果与日志比对

当前操作是转0.001 FIL,对应的消息size是142 实际获取到的结果 433268 (打印日志获取) GasStorageMulti = 1000 GasComputeMulti = 1 计算方式: (上面代码注释中的 计算2) ret := pl.sendBase ret += pl.sendTransferFunds ret += pl.sendTransferOnlyPremium//如果仅仅只是转账,需要该笔 gas = newGasCharge("OnMethodInvocation", ret, 0) 计算ret = 29233 + 27500 + 159672 = 216405 toUse := g.ComputeGas*GasComputeMulti + g.StorageGas*GasStorageMulti = 216405 * 1 + 0 = 216405 还有一次计算 (上面代码注释中的 计算1) newGasCharge("OnChainMessage", pl.onChainMessageComputeBase, pl.onChainMessageStorageBase+pl.onChainMessageStoragePerByte*int64(msgSize)) 转换对应数值,msgSize是sm *SignedMessage 序列化的结果, 打印的size是 142 newGasCharge("OnChainMessage", 38863, 36+142) gas = newGasCharge("OnChainMessage", ret, 0) 38863 * 1 + (36+142)*1000 = 216,863 216405 + 216,863 = 433268 计算成功 注意: 在得出这个值后, 还需要 * 1.25 433268 * 1.25 = 541585

GasEstimateGasPremium

大概是最近的块的消息取均值,使用方式参考发送交易 nblocksincl 传2,取最近n*2个块的所有消息小费均值 具体算法没太理解,可自行查看源码…

func (a *GasAPI) GasEstimateGasPremium(ctx context.Context, nblocksincl uint64, sender address.Address, gaslimit int64, _ types.TipSetKey) (types.BigInt, error) { //略 } //内部计算大概是取当前往前推N(=nblocksincl*2)个块的所有消息的GasPremium,GasLimit,算法没看懂 //应该是取均值之类的 <!--关键参数--> //nblocksincl 大概是块数量,包括当前块的意思,内部使用时 *2 //gaslimit 内部未使用到该参数,忽略 <!--该方法两个地方用到了 --> //1、当前gas计算代码, 参数:nblocksincl写死2 a.GasEstimateGasPremium(ctx, 2, msg.From, msg.GasLimit, types.TipSetKey{}) //2、 rpc获取GasPremium nb := []int{1, 2, 3, 5, 10, 20, 50, 100, 300} for _, nblocks := range nb { //这个TODO提示使用真实账号.... addr := builtin.SystemActorAddr // TODO: make real when used in GasEstimateGasPremium //另外这个nb是列表 也没提示为啥这么用,也是取均值? est, err := api.GasEstimateGasPremium(ctx, uint64(nblocks), addr, 10000, types.EmptyTSK) if err != nil { return err } fmt.Printf("%d blocks: %s (%s)\n", nblocks, est, types.FIL(est)) } <!--rpc返回值,经常发交易的话,前两个返回值可能是几万--> <!--1 blocks: 1 (0.000000000000000001 FIL)--> <!--2 blocks: 1 (0.000000000000000001 FIL)--> <!--3 blocks: 1 (0.000000000000000001 FIL)--> <!--5 blocks: 1 (0.000000000000000001 FIL)--> <!--10 blocks: 1 (0.000000000000000001 FIL)--> <!--20 blocks: 1 (0.000000000000000001 FIL)--> <!--50 blocks: 1 (0.000000000000000001 FIL)--> <!--100 blocks: 1 (0.000000000000000001 FIL)--> <!--300 blocks: 1 (0.000000000000000001 FIL)-->

GasEstimateFeeCap

代码

func (a *GasAPI) GasEstimateFeeCap(ctx context.Context, msg *types.Message, maxqueueblks int64, tsk types.TipSetKey) (types.BigInt, error) { ts := a.Chain.GetHeaviestTipSet()//获取最新块 var act types.Actor//获取账号状态 err := a.Stmgr.WithParentState(ts, a.Stmgr.WithActor(msg.From, stmgr.GetActor(&act))) if err != nil { return types.NewInt(0), xerrors.Errorf("getting actor: %w", err) } //获取父区块的最低手续费 parentBaseFee := ts.Blocks()[0].ParentBaseFee //增加因子 3.247321025468409 increaseFactor := math.Pow(1.+1./float64(build.BaseFeeMaxChangeDenom), float64(maxqueueblks)) //fee在原来的基础上*parentBaseFee feeInFuture := types.BigMul(parentBaseFee, types.NewInt(uint64(increaseFactor*(1<<8)))) feeInFuture = types.BigDiv(feeInFuture, types.NewInt(1<<8)) gasLimitBig := types.NewInt(uint64(msg.GasLimit)) maxAccepted := types.BigDiv(act.Balance, types.NewInt(MaxSpendOnFeeDenom))// expectedFee := types.BigMul(feeInFuture, gasLimitBig)//预期手续费base * gasLimit out := feeInFuture if types.BigCmp(expectedFee, maxAccepted) > 0 { //如果预期手续费大于余额的1%,提示手续费太高 log.Warnf("Expected fee for message higher than tolerance: %s > %s, setting to tolerance", types.FIL(expectedFee), types.FIL(maxAccepted)) //改成 out = types.BigDiv(maxAccepted, gasLimitBig) } return out, nil }

简化版

//简化版,具体查看后面的代码 curBase = parentBaseFee * 3.247321025468409 expectedFee = curBase * gasLimit maxAccepted = balance / 100 if expectedFee > maxAccepted { out = maxAccepted / gasLimit } 然后再加上小费 msg.GasFeeCap = big.Add(feeCap, msg.GasPremium)

区块浏览器中展现的值

飞狐浏览器 https://filfox.info/zh/ 官方浏览器没展示这么清楚

0.772703973308509788 FIL 销毁手续费 = Base Fee Burn + Over Estimation Burn 版本 (API) 0 Nonce 26 Gas Fee Cap 1,712.955555402 nanoFIL //下面有根据日志计算得来的 Gas Premium 1 attoFIL // 小费 Gas 限额 542,835 //通过gasLimit * 1.25 Gas 使用量 435,268 //计算得来的gasLimit Base Fee Burn 0.745594738688717736 FIL // FeeCap * 使用量 (两种情况,具体看下面计算方式) Over Estimation Burn 0.027109234619792052 FIL // 通过 feeCap/gas限额/使用量计算的来,具体看下面 Miner Tip 0 FIL Gas Fee Cap 计算时日志,当前余额92.985222891635558 FIL 日志 打印上个块base 10089621013943253 Expected fee for message higher than tolerance: 17778.85359609112888176 FIL > 0.92985222891635558 FIL, setting to tolerance 计算得来的GasFeeCap 1712955555401 再加上小费的GasFeeCap 1712955555402 按照这日志, 估计得余额170w+ fil的才能走基于parentBaseFee计算(另外一次日志是19274.3FIL,更高... 190W+) 否则都是balance / 100 / gasLimit 92985222891635558000 / 100 / 542835 + 1(这个1是premium) 得出1712955555402 转成nanoFIL 除于9个0

Base Fee Burn 和 Over Estimation Burn 计算方式

路径 chain/vm/burn.go

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 {//如果baseFee 大于feeCap baseFeeToPay = feeCap // out.MinerPenalty = big.Mul(big.Sub(baseFee, feeCap), gasUsedBig) } //计算结果,当前日志baseFee是大于feeCap得,所以结果是 feeCap * gasUsed out.BaseFeeBurn = big.Mul(baseFeeToPay, gasUsedBig) minerTip := gasPremium if big.Cmp(big.Add(baseFeeToPay, minerTip), feeCap) > 0 { minerTip = big.Sub(feeCap, baseFeeToPay) } out.MinerTip = big.Mul(minerTip, big.NewInt(gasLimit)) //计算OverestimationBurn //当前日志传参是 (435268,542835) out.GasRefund, out.GasBurned = ComputeGasOverestimationBurn(gasUsed, gasLimit) if out.GasBurned != 0 { gasBurnedBig := big.NewInt(out.GasBurned) //这里计算结果feeCapp * out.GasBurned //27109234619792052 //转换得 0.027109234619792052 ; 和浏览器中一致 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 }

ComputeGasOverestimationBurn(gasUsed, gasLimit)

const ( gasOveruseNum = 11 gasOveruseDenom = 10 ) func ComputeGasOverestimationBurn(gasUsed, gasLimit int64) (int64, int64) { if gasUsed == 0 { return 0, gasLimit } // over = gasLimit/gasUsed - 1 - 0.1 // over = min(over, 1) // gasToBurn = (gasLimit - gasUsed) * over // so to factor out division from `over` // over*gasUsed = min(gasLimit - (11*gasUsed)/10, gasUsed) // gasToBurn = ((gasLimit - gasUsed)*over*gasUsed) / gasUsed over := gasLimit - (gasOveruseNum*gasUsed)/gasOveruseDenom if over < 0 { return gasLimit - gasUsed, 0 } // if we want sharper scaling it goes here: // over *= 2 if over > gasUsed { over = gasUsed } // needs bigint, as it overflows in pathological case gasLimit > 2^32 gasUsed = gasLimit / 2 gasToBurn := big.NewInt(gasLimit - gasUsed) gasToBurn = big.Mul(gasToBurn, big.NewInt(over)) gasToBurn = big.Div(gasToBurn, big.NewInt(gasUsed)) return gasLimit - gasUsed - gasToBurn.Int64(), gasToBurn.Int64() }
最新回复(0)