From db8a900323af13ad01e7ecf4fb195417f76cfd7c Mon Sep 17 00:00:00 2001 From: jzimmerman Date: Thu, 28 Nov 2019 15:44:16 -0800 Subject: [PATCH] WIP: Gas accounting --- .../filecoin_vm/interpreter/vm_interpreter.go | 166 +++++++++++------- .../filecoin_vm/interpreter/vm_interpreter.id | 5 + src/systems/filecoin_vm/message/message.go | 46 ++++- src/systems/filecoin_vm/message/message.id | 11 +- .../runtime/gascost/vm_gascosts.go | 105 +++++++++-- src/systems/filecoin_vm/runtime/runtime.go | 82 +++++---- src/systems/filecoin_vm/runtime/runtime.id | 7 +- .../filecoin_vm/sysactors/init_actor.go | 2 + 8 files changed, 306 insertions(+), 118 deletions(-) diff --git a/src/systems/filecoin_vm/interpreter/vm_interpreter.go b/src/systems/filecoin_vm/interpreter/vm_interpreter.go index b00c2faf2..0f8edc116 100644 --- a/src/systems/filecoin_vm/interpreter/vm_interpreter.go +++ b/src/systems/filecoin_vm/interpreter/vm_interpreter.go @@ -33,14 +33,14 @@ func (vmi *VMInterpreter_I) ApplyTipSetMessages(inTree st.StateTree, msgs TipSet for _, blk := range msgs.Blocks() { // Pay block reward. reward := _makeBlockRewardMessage(outTree, blk.Miner()) - outTree, r = vmi.ApplyMessage(outTree, reward, blk.Miner()) + outTree, r = vmi.ApplyMessage(outTree, reward, 0, blk.Miner()) if r.ExitCode() != exitcode.OK() { panic("block reward failed") } // Process block miner's Election PoSt. epost := _makeElectionPoStMessage(outTree, blk.Miner(), msgs.Epoch(), blk.PoStProof()) - outTree, r = vmi.ApplyMessage(outTree, epost, blk.Miner()) + outTree, r = vmi.ApplyMessage(outTree, epost, 0, blk.Miner()) if r.ExitCode() != exitcode.OK() { panic("election post failed") } @@ -51,16 +51,25 @@ func (vmi *VMInterpreter_I) ApplyTipSetMessages(inTree st.StateTree, msgs TipSet if found { continue } - outTree, r = vmi.ApplyMessage(outTree, m, blk.Miner()) + + TODO() // TODO: include signature length? + onChainMessageLen := len(msg.Serialize_UnsignedMessage(m)) + + outTree, r = vmi.ApplyMessage(outTree, m, onChainMessageLen, blk.Miner()) receipts = append(receipts, r) seenMsgs[_msgCID(m)] = struct{}{} } } // Invoke cron tick, attributing it to the miner of the first block. + + TODO() + // TODO: miners shouldn't be able to trigger cron by sending messages. + // Maybe we need a separate ControlActor for this? + cronSender := msgs.Blocks()[0].Miner() cron := _makeCronTickMessage(outTree, cronSender) - outTree, r = vmi.ApplyMessage(outTree, cron, cronSender) + outTree, r = vmi.ApplyMessage(outTree, cron, 0, cronSender) if r.ExitCode() != exitcode.OK() { panic("cron tick failed") } @@ -68,54 +77,103 @@ func (vmi *VMInterpreter_I) ApplyTipSetMessages(inTree st.StateTree, msgs TipSet return } -func (vmi *VMInterpreter_I) ApplyMessage(inTree st.StateTree, message msg.UnsignedMessage, minerAddr addr.Address) ( +func StateTree_WithGasUsedFundsTransfer( + tree st.StateTree, gasUsed msg.GasAmount, message msg.UnsignedMessage, + fundsFrom addr.Address, fundsTo addr.Address) st.StateTree { + + TODO() // TODO: what if the miner has insufficient funds? + // Should we instead aggregate these deltas, and SubtractWhileNonnegative at the end of the tipset? + + return _withTransferFundsAssert( + tree, + fundsFrom, + fundsTo, + _gasToFIL(gasUsed, message.GasPrice()), + ) +} + +func (vmi *VMInterpreter_I) ApplyMessage( + inTree st.StateTree, message msg.UnsignedMessage, onChainMessageSize int, minerAddr addr.Address) ( st.StateTree, msg.MessageReceipt) { - compTree := inTree - var outTree st.StateTree - var toActor actor.ActorState - var err error + vmiGasRemaining := message.GasLimit() + + _applyReturn := func( + tree st.StateTree, invocOutput msg.InvocOutput, exitCode exitcode.ExitCode, + gasUsedFrom addr.Address, gasUsedTo addr.Address) (st.StateTree, msg.MessageReceipt) { + + vmiGasUsed := message.GasLimit().Subtract(vmiGasRemaining) + tree = StateTree_WithGasUsedFundsTransfer(tree, vmiGasUsed, message, gasUsedFrom, gasUsedTo) + return tree, msg.MessageReceipt_Make(invocOutput, exitCode, vmiGasUsed) + } - fromActor := compTree.GetActorState(message.From()) + _applyError := func( + tree st.StateTree, errExitCode exitcode.SystemErrorCode, + gasUsedFrom addr.Address, gasUsedTo addr.Address) (st.StateTree, msg.MessageReceipt) { + + return _applyReturn( + tree, msg.InvocOutput_Make(nil), exitcode.SystemError(errExitCode), gasUsedFrom, gasUsedTo) + } + + _vmiUseGas := func(amount msg.GasAmount) (vmiUseGasOK bool) { + vmiGasRemaining, vmiUseGasOK = vmiGasRemaining.SubtractWhileNonnegative(amount) + return + } + + ok := _vmiUseGas(gascost.OnChainMessage(onChainMessageSize)) + if !ok { + return _applyError(inTree, exitcode.OutOfGas, minerAddr, addr.BurntFundsActorAddr) + } + + fromActor := inTree.GetActorState(message.From()) if fromActor == nil { - // TODO: This was originally exitcode.InvalidMethod; which is correct? - return inTree, _applyError(exitcode.ActorNotFound) + return _applyError(inTree, exitcode.ActorNotFound, minerAddr, addr.BurntFundsActorAddr) } - // make sure fromActor has enough money to run the max invocation - maxGasCost := _gasToFIL(message.GasLimit(), message.GasPrice()) - totalCost := message.Value() + actor.TokenAmount(maxGasCost) + // make sure fromActor has enough money to run with the specified gas limit + gasLimitCost := _gasToFIL(message.GasLimit(), message.GasPrice()) + totalCost := message.Value() + actor.TokenAmount(gasLimitCost) if fromActor.Balance() < totalCost { - return inTree, _applyError(exitcode.InsufficientFunds_System) + return _applyError(inTree, exitcode.InsufficientFunds_System, minerAddr, addr.BurntFundsActorAddr) } // make sure this is the right message order for fromActor // (this is protection against replay attacks, and useful sequencing) if message.CallSeqNum() != fromActor.CallSeqNum()+1 { - return inTree, _applyError(exitcode.InvalidCallSeqNum) + return _applyError(inTree, exitcode.InvalidCallSeqNum, minerAddr, addr.BurntFundsActorAddr) } // WithActorForAddress may create new account actors - compTree, toActor = compTree.Impl().WithActorForAddress(message.To()) + compTreePreSend := inTree + compTreePreSend, toActor := compTreePreSend.Impl().WithActorForAddress(message.To()) if toActor == nil { - return inTree, _applyError(exitcode.ActorNotFound) + return _applyError(inTree, exitcode.ActorNotFound, message.From(), addr.BurntFundsActorAddr) } - // deduct maximum expenditure gas funds first - compTree = _withTransferFundsAssert(compTree, message.From(), addr.BurntFundsActorAddr, maxGasCost) + // deduct gas limit funds from sender first + compTreePreSend = _withTransferFundsAssert( + compTreePreSend, message.From(), addr.BurntFundsActorAddr, gasLimitCost) + + // Increment sender call sequence number. + var err error + compTreePreSend, err = compTreePreSend.Impl().WithIncrementedCallSeqNum(message.From()) + if err != nil { + // Note: if actor deletion is possible at some point, may need to allow this case + panic("Internal interpreter error: failed to increment call sequence number") + } rt := vmr.VMContext_Make( message.From(), - minerAddr, // TODO: may not exist? (see below) + minerAddr, fromActor.CallSeqNum(), actor.CallSeqNum(0), - compTree, + compTreePreSend, message.From(), actor.TokenAmount(0), - message.GasLimit(), + vmiGasRemaining, ) - sendRet, sendRetStateTree := rt.SendToplevelFromInterpreter( + sendRet, compTreePostSend := rt.SendToplevelFromInterpreter( msg.InvocInput_Make( message.To(), message.Method(), @@ -124,51 +182,33 @@ func (vmi *VMInterpreter_I) ApplyMessage(inTree st.StateTree, message msg.Unsign ), ) - if !sendRet.ExitCode().AllowsStateUpdate() { - // error -- revert all state changes -- ie drop updates. burn used gas. - outTree = inTree - outTree = _withTransferFundsAssert( - outTree, - message.From(), - addr.BurntFundsActorAddr, - _gasToFIL(sendRet.GasUsed(), message.GasPrice()), - ) - } else { - // success -- refund unused gas - outTree = sendRetStateTree - refundGas := message.GasLimit() - sendRet.GasUsed() - TODO() // TODO: assert refundGas is nonnegative - outTree = _withTransferFundsAssert( - outTree, - addr.BurntFundsActorAddr, - message.From(), - _gasToFIL(refundGas, message.GasPrice()), - ) + ok = _vmiUseGas(sendRet.GasUsed()) + if !ok { + panic("Interpreter error: runtime execution used more gas than provided") } - outTree, err = outTree.Impl().WithIncrementedCallSeqNum(message.To()) - if err != nil { - // TODO: if actor deletion is possible at some point, may need to allow this case - panic("Internal interpreter error: failed to increment call sequence number") + ok = _vmiUseGas(gascost.OnChainReturnValue(sendRet.ReturnValue())) + if !ok { + return _applyError(compTreePreSend, exitcode.OutOfGas, addr.BurntFundsActorAddr, minerAddr) + } + + compTreeRet := compTreePreSend + if sendRet.ExitCode().AllowsStateUpdate() { + compTreeRet = compTreePostSend } - // reward miner gas fees - outTree = _withTransferFundsAssert( - outTree, + // Refund unused gas to sender. + refundGas := vmiGasRemaining + compTreeRet = _withTransferFundsAssert( + compTreeRet, addr.BurntFundsActorAddr, - minerAddr, // TODO: may not exist - _gasToFIL(sendRet.GasUsed(), message.GasPrice()), + message.From(), + _gasToFIL(refundGas, message.GasPrice()), ) - return outTree, sendRet -} - -func _applyError(errCode exitcode.SystemErrorCode) msg.MessageReceipt { - // TODO: should this gasUsed value be zero? - // If nonzero, there is not guaranteed to be a nonzero gas balance from which to deduct it. - gasUsed := gascost.ApplyMessageFail - TODO() - return msg.MessageReceipt_MakeSystemError(errCode, gasUsed) + return _applyReturn( + compTreeRet, msg.InvocOutput_Make(sendRet.ReturnValue()), sendRet.ExitCode(), + addr.BurntFundsActorAddr, minerAddr) } func _withTransferFundsAssert(tree st.StateTree, from addr.Address, to addr.Address, amount actor.TokenAmount) st.StateTree { diff --git a/src/systems/filecoin_vm/interpreter/vm_interpreter.id b/src/systems/filecoin_vm/interpreter/vm_interpreter.id index 799400635..baa5b1b5e 100644 --- a/src/systems/filecoin_vm/interpreter/vm_interpreter.id +++ b/src/systems/filecoin_vm/interpreter/vm_interpreter.id @@ -17,6 +17,11 @@ type TipSetMessages struct { Epoch UInt64 // The chain epoch of the blocks } +type MessageRef struct { + Message msg.UnsignedMessage + MessageSizeOrig int +} + type VMInterpreter struct { ApplyTipSetMessages(inTree st.StateTree, msgs TipSetMessages) struct {outTree st.StateTree, ret [msg.MessageReceipt]} ApplyMessage(inTree st.StateTree, msg msg.Message, minerAddr addr.Address) struct {outTree st.StateTree, ret msg.MessageReceipt} diff --git a/src/systems/filecoin_vm/message/message.go b/src/systems/filecoin_vm/message/message.go index 242b9efaa..04e33861c 100644 --- a/src/systems/filecoin_vm/message/message.go +++ b/src/systems/filecoin_vm/message/message.go @@ -72,20 +72,52 @@ func Verify(message SignedMessage, publicKey filcrypto.PublicKey) (UnsignedMessa return message.Message(), nil } -func (x GasAmount) Add(y GasAmount) GasAmount { - panic("TODO") +func (x *GasAmount_I) Add(y GasAmount) GasAmount { + IMPL_FINISH() + panic("") +} + +func (x *GasAmount_I) Subtract(y GasAmount) GasAmount { + IMPL_FINISH() + panic("") } -func (x GasAmount) Subtract(y GasAmount) GasAmount { - panic("TODO") +func (x *GasAmount_I) SubtractWhileNonnegative(y GasAmount) (ret GasAmount, ok bool) { + ret = x.Subtract(y) + ok = true + if ret.LessThan(GasAmount_Zero()) { + ret = GasAmount_Zero() + ok = false + } + return } -func (x GasAmount) LessThan(y GasAmount) bool { - panic("TODO") +func (x *GasAmount_I) LessThan(y GasAmount) bool { + IMPL_FINISH() + panic("") +} + +func (x *GasAmount_I) Equals(y GasAmount) bool { + IMPL_FINISH() + panic("") +} + +func (x *GasAmount_I) Scale(count int) GasAmount { + IMPL_FINISH() + panic("") +} + +func GasAmount_Affine(b GasAmount, x int, m GasAmount) GasAmount { + return b.Add(m.Scale(x)) } func GasAmount_Zero() GasAmount { - panic("TODO") + return GasAmount_FromInt(0) +} + +func GasAmount_FromInt(x int) GasAmount { + IMPL_FINISH() + panic("") } func InvocInput_Make(to addr.Address, method actor.MethodNum, params actor.MethodParams, value actor.TokenAmount) InvocInput { diff --git a/src/systems/filecoin_vm/message/message.id b/src/systems/filecoin_vm/message/message.id index 66b539972..a91b7c38c 100644 --- a/src/systems/filecoin_vm/message/message.id +++ b/src/systems/filecoin_vm/message/message.id @@ -4,7 +4,16 @@ import actor "github.com/filecoin-project/specs/systems/filecoin_vm/actor" import exitcode "github.com/filecoin-project/specs/systems/filecoin_vm/runtime/exitcode" // GasAmount is a quantity of gas. -type GasAmount UVarint +type GasAmount struct { + value BigInt + + Add(GasAmount) GasAmount + Subtract(GasAmount) GasAmount + SubtractWhileNonnegative(GasAmount) (ret GasAmount, ok bool) + LessThan(GasAmount) bool + Equals(GasAmount) bool + Scale(int) GasAmount +} // GasPrice is a Gas-to-FIL cost type GasPrice actor.TokenAmount diff --git a/src/systems/filecoin_vm/runtime/gascost/vm_gascosts.go b/src/systems/filecoin_vm/runtime/gascost/vm_gascosts.go index b276d5d1a..6232d510f 100644 --- a/src/systems/filecoin_vm/runtime/gascost/vm_gascosts.go +++ b/src/systems/filecoin_vm/runtime/gascost/vm_gascosts.go @@ -1,25 +1,100 @@ package runtime +import actor "github.com/filecoin-project/specs/systems/filecoin_vm/actor" import msg "github.com/filecoin-project/specs/systems/filecoin_vm/message" +import util "github.com/filecoin-project/specs/util" + +type Bytes = util.Bytes + +var TODO = util.TODO + +var ( + // TODO: assign all of these. + GasAmountPlaceholder = msg.GasAmount_FromInt(1) + GasAmountPlaceholder_UpdateStateTree = GasAmountPlaceholder +) -// TODO: assign all of these. var ( - // SimpleValueSend is the amount of gas charged for sending value from one - // contract to another, without executing any other code. - SimpleValueSend = msg.GasAmount(1) + /////////////////////////////////////////////////////////////////////////// + // System operations + /////////////////////////////////////////////////////////////////////////// + + // Gas cost charged to the originator of an on-chain message (regardless of + // whether it succeeds or fails in application) is given by: + // OnChainMessageBase + len(serialized message)*OnChainMessagePerByte + OnChainMessageBase = GasAmountPlaceholder + OnChainMessagePerByte = GasAmountPlaceholder + + // Gas cost charged to the originator of a non-nil return value produced + // by an on-chain message is given by: + // OnChainReturnValueBase + len(return value)*OnChainReturnValuePerByte + OnChainReturnValueBase = GasAmountPlaceholder + OnChainReturnValuePerByte = GasAmountPlaceholder + + // Gas cost for any method invocation (including the original one initiated + // by an on-chain message). + InvokeMethodBase = GasAmountPlaceholder + + // Gas cost charged, in addition to InvokeMethodBase, if a method invocation + // is accompanied by any nonzero currency amount. + InvokeMethodTransferFunds = GasAmountPlaceholder_UpdateStateTree - // // ActorLookupFail is the amount of gas charged for a failure to lookup - // // an actor - // ActorLookupFail = msg.GasAmount(1) + // Gas cost (Base + len*PerByte) for any Get operation to the IPLD store + // in the runtime VM context. + IpldGetBase = GasAmountPlaceholder + IpldGetPerByte = GasAmountPlaceholder - // CodeLookupFail is the amount of gas charged for a failure to lookup - // code in the VM's code registry. - CodeLookupFail = msg.GasAmount(1) + // Gas cost (Base + len*PerByte) for any Put operation to the IPLD store + // in the runtime VM context. + // + // Note: these costs should be significantly higher than the costs for Get + // operations, since they reflect not only serialization/deserialization + // but also persistent storage of chain data. + IpldPutBase = GasAmountPlaceholder + IpldPutPerByte = GasAmountPlaceholder - // ApplyMessageFail represents the gas cost for failures to apply message. - // These failures are basic failures encountered at first application. - ApplyMessageFail = msg.GasAmount(1) + // Gas cost for updating an actor's substate (i.e., UpdateRelease). + UpdateActorSubstate = GasAmountPlaceholder_UpdateStateTree - // TODO: determine these costs - PublicKeyCryptoOp = msg.GasAmount(50) + // Gas cost for creating a new actor (via InitActor's Exec method). + ExecNewActor = GasAmountPlaceholder + + /////////////////////////////////////////////////////////////////////////// + // Pure functions (VM ABI) + /////////////////////////////////////////////////////////////////////////// + + // Gas cost charged per public-key cryptography operation (e.g., signature + // verification). + PublicKeyCryptoOp = GasAmountPlaceholder ) + +func OnChainMessage(onChainMessageLen int) msg.GasAmount { + return msg.GasAmount_Affine(OnChainMessageBase, onChainMessageLen, OnChainMessagePerByte) +} + +func OnChainReturnValue(returnValue Bytes) msg.GasAmount { + retLen := 0 + if returnValue != nil { + retLen = len(returnValue) + } + + return msg.GasAmount_Affine(OnChainReturnValueBase, retLen, OnChainReturnValuePerByte) +} + +func IpldGet(dataSize int) msg.GasAmount { + return msg.GasAmount_Affine(IpldGetBase, dataSize, IpldGetPerByte) +} + +func IpldPut(dataSize int) msg.GasAmount { + return msg.GasAmount_Affine(IpldPutBase, dataSize, IpldPutPerByte) +} + +func InvokeMethod(valueSent actor.TokenAmount) msg.GasAmount { + ret := InvokeMethodBase + + TODO() // should TokenAmount be an int or a BigInt? + if valueSent > 0 { + ret = ret.Add(InvokeMethodTransferFunds) + } + return ret +} diff --git a/src/systems/filecoin_vm/runtime/runtime.go b/src/systems/filecoin_vm/runtime/runtime.go index b0f55f870..d8022f712 100644 --- a/src/systems/filecoin_vm/runtime/runtime.go +++ b/src/systems/filecoin_vm/runtime/runtime.go @@ -8,6 +8,7 @@ import msg "github.com/filecoin-project/specs/systems/filecoin_vm/message" import addr "github.com/filecoin-project/specs/systems/filecoin_vm/actor/address" import actor "github.com/filecoin-project/specs/systems/filecoin_vm/actor" import exitcode "github.com/filecoin-project/specs/systems/filecoin_vm/runtime/exitcode" +import gascost "github.com/filecoin-project/specs/systems/filecoin_vm/runtime/gascost" import util "github.com/filecoin-project/specs/util" type ActorSubstateCID = actor.ActorSubstateCID @@ -138,6 +139,16 @@ func (rt *VMContext) AbortAPI(msg string) Runtime_AbortAPI_FunRet { return &Runtime_AbortAPI_FunRet_I{} } +func (rt *VMContext) CreateActor_DeductGas() Runtime_CreateActor_DeductGas_FunRet { + if !rt._actorAddress.Equals(addr.InitActorAddr) { + rt.AbortAPI("Only InitActor may call rt.CreateActor_DeductGas") + } + + rt._deductGasRemaining(gascost.ExecNewActor) + + return &Runtime_CreateActor_DeductGas_FunRet_I{} +} + func (rt *VMContext) CreateActor( stateCID actor.ActorSystemStateCID, address addr.Address, @@ -179,6 +190,8 @@ func (rt *VMContext) _updateActorSubstateInternal(actorAddress addr.Address, new func (rt *VMContext) _updateReleaseActorSubstate(newStateCID ActorSubstateCID) { rt._checkRunning() rt._checkActorStateAcquired() + rt._deductGasRemaining(gascost.UpdateActorSubstate) + rt._updateActorSubstateInternal(rt._actorAddress, newStateCID) rt._actorStateAcquired = false } @@ -314,24 +327,19 @@ func (rt *VMContext) _apiError(errMsg string) { rt._throwErrorFull(exitcode.SystemError(exitcode.RuntimeAPIError), errMsg) } -func (rt *VMContext) _checkGasRemaining() { - if rt._gasRemaining.LessThan(msg.GasAmount_Zero()) { - rt._throwError(exitcode.SystemError(exitcode.OutOfGas)) +func _gasAmountAssertValid(x msg.GasAmount) { + if x.LessThan(msg.GasAmount_Zero()) { + panic("Interpreter error: negative gas amount") } } func (rt *VMContext) _deductGasRemaining(x msg.GasAmount) { - // TODO: check x >= 0 - rt._checkGasRemaining() - rt._gasRemaining = rt._gasRemaining.Subtract(x) - rt._checkGasRemaining() -} - -func (rt *VMContext) _refundGasRemaining(x msg.GasAmount) { - // TODO: check x >= 0 - rt._checkGasRemaining() - rt._gasRemaining = rt._gasRemaining.Add(x) - rt._checkGasRemaining() + _gasAmountAssertValid(x) + var ok bool + rt._gasRemaining, ok = rt._gasRemaining.SubtractWhileNonnegative(x) + if !ok { + rt._throwError(exitcode.SystemError(exitcode.OutOfGas)) + } } func (rt *VMContext) _transferFunds(from addr.Address, to addr.Address, amount actor.TokenAmount) error { @@ -388,10 +396,9 @@ func _invokeMethodInternal( actorCode ActorCode, method actor.MethodNum, params actor.MethodParams) ( - ret InvocOutput, gasUsed msg.GasAmount, exitCode exitcode.ExitCode, internalCallSeqNumFinal actor.CallSeqNum) { + ret InvocOutput, exitCode exitcode.ExitCode, internalCallSeqNumFinal actor.CallSeqNum) { if method == actor.MethodSend { - gasUsed = msg.GasAmount_Zero() // TODO: verify ret = msg.InvocOutput_Make(nil) return } @@ -405,9 +412,6 @@ func _invokeMethodInternal( }) rt._running = false - // TODO: Update gasUsed - TODO() - internalCallSeqNumFinal = rt._internalCallSeqNum return @@ -417,6 +421,10 @@ func (rtOuter *VMContext) _sendInternal(input InvocInput, errSpec ErrorHandlingS rtOuter._checkRunning() rtOuter._checkActorStateNotAcquired() + initGasRemaining := rtOuter._gasRemaining + + rtOuter._deductGasRemaining(gascost.InvokeMethod(input.Value())) + toActor := rtOuter._globalStatePending.GetActorState(input.To()) toActorCode, err := loadActorCode(toActor.CodeID()) @@ -424,12 +432,6 @@ func (rtOuter *VMContext) _sendInternal(input InvocInput, errSpec ErrorHandlingS rtOuter._throwError(exitcode.SystemError(exitcode.ActorCodeNotFound)) } - var toActorMethodGasBound msg.GasAmount - TODO() // TODO: obtain from actor registry - rtOuter._deductGasRemaining(toActorMethodGasBound) - // TODO: gasUsed may be larger than toActorMethodGasBound if toActor itself makes sub-calls. - // To prevent this, we would need to calculate the gas bounds recursively. - err = rtOuter._transferFunds(rtOuter._actorAddress, input.To(), input.Value()) if err != nil { rtOuter._throwError(exitcode.SystemError(exitcode.InsufficientFunds_System)) @@ -446,17 +448,20 @@ func (rtOuter *VMContext) _sendInternal(input InvocInput, errSpec ErrorHandlingS rtOuter._gasRemaining, ) - invocOutput, gasUsed, exitCode, internalCallSeqNumFinal := _invokeMethodInternal( + invocOutput, exitCode, internalCallSeqNumFinal := _invokeMethodInternal( rtInner, toActorCode, input.Method(), input.Params(), ) + _gasAmountAssertValid(rtOuter._gasRemaining.Subtract(rtInner._gasRemaining)) + rtOuter._gasRemaining = rtInner._gasRemaining + gasUsed := initGasRemaining.Subtract(rtOuter._gasRemaining) + _gasAmountAssertValid(gasUsed) + rtOuter._internalCallSeqNum = internalCallSeqNumFinal - rtOuter._refundGasRemaining(toActorMethodGasBound) - rtOuter._deductGasRemaining(gasUsed) if exitCode.Equals(exitcode.SystemError(exitcode.OutOfGas)) { // OutOfGas error cannot be caught rtOuter._throwError(exitCode) @@ -504,11 +509,28 @@ func (rt *VMContext) Randomness(e block.ChainEpoch, offset uint64) util.Randomne } func (rt *VMContext) IpldPut(x ipld.Object) ipld.CID { - panic("TODO") + var serializedSize int + IMPL_FINISH() + panic("") // compute serializedSize + + rt._deductGasRemaining(gascost.IpldPut(serializedSize)) + + IMPL_FINISH() + panic("") // write to IPLD store } func (rt *VMContext) IpldGet(c ipld.CID) Runtime_IpldGet_FunRet { - panic("TODO") + IMPL_FINISH() + panic("") // get from IPLD store + + var serializedSize int + IMPL_FINISH() + panic("") // retrieve serializedSize + + rt._deductGasRemaining(gascost.IpldGet(serializedSize)) + + IMPL_FINISH() + panic("") // return item } func (rt *VMContext) CurrEpoch() block.ChainEpoch { diff --git a/src/systems/filecoin_vm/runtime/runtime.id b/src/systems/filecoin_vm/runtime/runtime.id index 9717d4826..5211387ba 100644 --- a/src/systems/filecoin_vm/runtime/runtime.id +++ b/src/systems/filecoin_vm/runtime/runtime.id @@ -93,6 +93,9 @@ type Runtime interface { constructorParams actor.MethodParams ) - IpldGet(c ipld.CID) union {Bytes, error} - IpldPut(x ipld.Object) ipld.CID + // Deduct gas cost for actor creation. May only be called by InitActor. + CreateActor_DeductGas() + + IpldGet(c ipld.CID) union {Bytes, error} + IpldPut(x ipld.Object) ipld.CID } diff --git a/src/systems/filecoin_vm/sysactors/init_actor.go b/src/systems/filecoin_vm/sysactors/init_actor.go index 01e1a3ebe..8e3d716ca 100644 --- a/src/systems/filecoin_vm/sysactors/init_actor.go +++ b/src/systems/filecoin_vm/sysactors/init_actor.go @@ -69,6 +69,8 @@ func (a *InitActorCode_I) Exec(rt Runtime, codeID actor.CodeID, constructorParam rt.AbortArgMsg("cannot exec an actor of this type") } + rt.CreateActor_DeductGas() + newAddr := _computeNewActorExecAddress(rt) actorState := &actor.ActorState_I{