From 82031c241e1514ca8985690a30861b99fae0c808 Mon Sep 17 00:00:00 2001 From: ZenGround0 Date: Mon, 10 May 2021 11:13:24 -0400 Subject: [PATCH 1/2] Add gas to test vm - introduce runtime.Pricelist - the price list interface - copy current filecoin pricelist to testing package - charge for gas - return (and ignore) gas used from ApplyMessage - setup large default gas limit - handle out of gas aborts --- actors/runtime/runtime.go | 27 +++ actors/runtime/types.go | 27 +++ actors/test/commit_post_test.go | 2 +- actors/test/common_test.go | 8 +- actors/test/multisig_delete_self_test.go | 6 +- gen/gen.go | 7 + support/agent/deal_client_agent.go | 5 +- support/agent/sim.go | 9 +- support/vm/cbor_gen.go | 250 ++++++++++++++++++++ support/vm/invocation_context.go | 97 ++++++-- support/vm/price_list.go | 282 +++++++++++++++++++++++ support/vm/testing.go | 4 +- support/vm/vm.go | 102 ++++++-- 13 files changed, 774 insertions(+), 52 deletions(-) create mode 100644 support/vm/cbor_gen.go create mode 100644 support/vm/price_list.go diff --git a/actors/runtime/runtime.go b/actors/runtime/runtime.go index ba0b4f3de..1907b7b5c 100644 --- a/actors/runtime/runtime.go +++ b/actors/runtime/runtime.go @@ -231,3 +231,30 @@ type StateHandle interface { // ``` StateTransaction(obj cbor.Er, f func()) } + +type Pricelist interface { + // OnChainMessage returns the gas used for storing a message of a given size in the chain. + OnChainMessage(msgSize int) GasCharge + // OnChainReturnValue returns the gas used for storing the response of a message in the chain. + OnChainReturnValue(dataSize int) GasCharge + + // OnMethodInvocation returns the gas used when invoking a method. + OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) GasCharge + + // OnIpldGet returns the gas used for storing an object + OnIpldGet() GasCharge + // OnIpldPut returns the gas used for storing an object + OnIpldPut(dataSize int) GasCharge + + // OnCreateActor returns the gas used for creating an actor + OnCreateActor() GasCharge + // OnDeleteActor returns the gas used for deleting an actor + OnDeleteActor() GasCharge + + OnVerifySignature(sigType crypto.SigType, planTextSize int) (GasCharge, error) + OnHashing(dataSize int) GasCharge + OnComputeUnsealedSectorCid(proofType abi.RegisteredSealProof, pieces []abi.PieceInfo) GasCharge + OnVerifySeal(info proof.SealVerifyInfo) GasCharge + OnVerifyPost(info proof.WindowPoStVerifyInfo) GasCharge + OnVerifyConsensusFault() GasCharge +} diff --git a/actors/runtime/types.go b/actors/runtime/types.go index 37682d646..1f84d8dd1 100644 --- a/actors/runtime/types.go +++ b/actors/runtime/types.go @@ -28,3 +28,30 @@ const ( ) type VMActor = rt.VMActor + +type GasCharge struct { + Name string + Extra interface{} + + ComputeGas int64 + StorageGas int64 + + VirtualCompute int64 + VirtualStorage int64 +} + +func (g GasCharge) Total() int64 { + return g.ComputeGas + g.StorageGas +} +func (g GasCharge) WithVirtual(compute, storage int64) GasCharge { + out := g + out.VirtualCompute = compute + out.VirtualStorage = storage + return out +} + +func (g GasCharge) WithExtra(extra interface{}) GasCharge { + out := g + out.Extra = extra + return out +} diff --git a/actors/test/commit_post_test.go b/actors/test/commit_post_test.go index 095cc580a..f7d395605 100644 --- a/actors/test/commit_post_test.go +++ b/actors/test/commit_post_test.go @@ -287,7 +287,7 @@ func TestCommitPoStFlow(t *testing.T) { ChainCommitRand: []byte("not really random"), } // PoSt is rejected for skipping all sectors. - _, code := tv.ApplyMessage(addrs[0], minerAddrs.RobustAddress, big.Zero(), builtin.MethodsMiner.SubmitWindowedPoSt, &submitParams) + _, code, _ := tv.ApplyMessage(addrs[0], minerAddrs.RobustAddress, big.Zero(), builtin.MethodsMiner.SubmitWindowedPoSt, &submitParams) assert.Equal(t, exitcode.ErrIllegalArgument, code) vm.ExpectInvocation{ diff --git a/actors/test/common_test.go b/actors/test/common_test.go index bde9e5e56..8de6f5a52 100644 --- a/actors/test/common_test.go +++ b/actors/test/common_test.go @@ -35,11 +35,13 @@ func publishDeal(t *testing.T, v *vm.VM, provider, dealClient, minerID addr.Addr publishDealParams := market.PublishStorageDealsParams{ Deals: []market.ClientDealProposal{{ - Proposal: deal, - ClientSignature: crypto.Signature{}, + Proposal: deal, + ClientSignature: crypto.Signature{ + Type: crypto.SigTypeBLS, + }, }}, } - ret, code := v.ApplyMessage(provider, builtin.StorageMarketActorAddr, big.Zero(), builtin.MethodsMarket.PublishStorageDeals, &publishDealParams) + ret, code, _ := v.ApplyMessage(provider, builtin.StorageMarketActorAddr, big.Zero(), builtin.MethodsMarket.PublishStorageDeals, &publishDealParams) require.Equal(t, exitcode.Ok, code) expectedPublishSubinvocations := []vm.ExpectInvocation{ diff --git a/actors/test/multisig_delete_self_test.go b/actors/test/multisig_delete_self_test.go index be1035d91..892685bd8 100644 --- a/actors/test/multisig_delete_self_test.go +++ b/actors/test/multisig_delete_self_test.go @@ -64,7 +64,7 @@ func TestMultisigDeleteSelf2Of3RemovedIsProposer(t *testing.T) { vm.ApplyOk(t, v, addrs[1], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) // txnid not found when third approval gets processed indicating that the transaction has gone through successfully - _, code := v.ApplyMessage(addrs[2], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) + _, code, _ := v.ApplyMessage(addrs[2], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) assert.Equal(t, exitcode.ErrNotFound, code) } @@ -115,7 +115,7 @@ func TestMultisigDeleteSelf2Of3RemovedIsApprover(t *testing.T) { vm.ApplyOk(t, v, addrs[0], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) // txnid not found when third approval gets processed indicating that the transaction has gone through successfully - _, code := v.ApplyMessage(addrs[2], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) + _, code, _ := v.ApplyMessage(addrs[2], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) assert.Equal(t, exitcode.ErrNotFound, code) } @@ -165,7 +165,7 @@ func TestMultisigDeleteSelf2Of2(t *testing.T) { vm.ApplyOk(t, v, addrs[1], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) // txnid not found when another approval gets processed indicating that the transaction has gone through successfully - _, code := v.ApplyMessage(addrs[1], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) + _, code, _ := v.ApplyMessage(addrs[1], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) assert.Equal(t, exitcode.ErrNotFound, code) } diff --git a/gen/gen.go b/gen/gen.go index fce3bb777..1bedb5c0d 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -16,6 +16,7 @@ import ( "github.com/filecoin-project/specs-actors/v5/actors/builtin/system" "github.com/filecoin-project/specs-actors/v5/actors/builtin/verifreg" "github.com/filecoin-project/specs-actors/v5/actors/util/smoothing" + vm_test "github.com/filecoin-project/specs-actors/v5/support/vm" ) func main() { @@ -233,4 +234,10 @@ func main() { panic(err) } + if err := gen.WriteTupleEncodersToFile("./support/vm/cbor_gen.go", "vm_test", + vm_test.ChainMessage{}, + ); err != nil { + panic(err) + } + } diff --git a/support/agent/deal_client_agent.go b/support/agent/deal_client_agent.go index 700311a08..a4722be9f 100644 --- a/support/agent/deal_client_agent.go +++ b/support/agent/deal_client_agent.go @@ -2,11 +2,12 @@ package agent import ( "crypto/sha256" - mh "github.com/multiformats/go-multihash" "math/bits" "math/rand" "strconv" + mh "github.com/multiformats/go-multihash" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" @@ -170,7 +171,7 @@ func (dca *DealClientAgent) createDeal(s SimState, provider DealProvider) error provider.CreateDeal(market.ClientDealProposal{ Proposal: proposal, - ClientSignature: crypto.Signature{}, + ClientSignature: crypto.Signature{Type: crypto.SigTypeBLS}, }) dca.DealCount++ return nil diff --git a/support/agent/sim.go b/support/agent/sim.go index ecf84852b..89fd85c18 100644 --- a/support/agent/sim.go +++ b/support/agent/sim.go @@ -155,7 +155,7 @@ func (s *Sim) Tick() error { // run messages for _, msg := range blockMessages { - ret, code := s.v.ApplyMessage(msg.From, msg.To, msg.Value, msg.Method, msg.Params) + ret, code, _ := s.v.ApplyMessage(msg.From, msg.To, msg.Value, msg.Method, msg.Params) // for now, assume everything should work if code != exitcode.Ok { @@ -185,7 +185,7 @@ func (s *Sim) Tick() error { } // run cron - _, code := s.v.ApplyMessage(builtin.SystemActorAddr, builtin.CronActorAddr, big.Zero(), builtin.MethodsCron.EpochTick, nil) + _, code, _ := s.v.ApplyMessage(builtin.SystemActorAddr, builtin.CronActorAddr, big.Zero(), builtin.MethodsCron.EpochTick, nil) if code != exitcode.Ok { return errors.Errorf("exitcode %d: cron message failed:\n%s\n", code, strings.Join(s.v.GetLogs(), "\n")) } @@ -315,7 +315,7 @@ func (s *Sim) rewardMiner(addr address.Address, wins uint64) error { GasReward: big.Zero(), WinCount: int64(wins), } - _, code := s.v.ApplyMessage(builtin.SystemActorAddr, builtin.RewardActorAddr, big.Zero(), builtin.MethodsReward.AwardBlockReward, &rewardParams) + _, code, _ := s.v.ApplyMessage(builtin.SystemActorAddr, builtin.RewardActorAddr, big.Zero(), builtin.MethodsReward.AwardBlockReward, &rewardParams) if code != exitcode.Ok { return errors.Errorf("exitcode %d: reward message failed:\n%s\n", code, strings.Join(s.v.GetLogs(), "\n")) } @@ -441,7 +441,7 @@ type PowerTable struct { // VM interface allowing a simulation to operate over multiple VM versions type SimVM interface { - ApplyMessage(from, to address.Address, value abi.TokenAmount, method abi.MethodNum, params interface{}) (cbor.Marshaler, exitcode.ExitCode) + ApplyMessage(from, to address.Address, value abi.TokenAmount, method abi.MethodNum, params interface{}) (cbor.Marshaler, exitcode.ExitCode, int64) GetCirculatingSupply() abi.TokenAmount GetLogs() []string GetState(addr address.Address, out cbor.Unmarshaler) error @@ -458,7 +458,6 @@ type SimVM interface { } var _ SimVM = (*vm.VM)(nil) -var _ SimVM = (*vm2.VM)(nil) type SimMinerState interface { HasSectorNo(adt.Store, abi.SectorNumber) (bool, error) diff --git a/support/vm/cbor_gen.go b/support/vm/cbor_gen.go new file mode 100644 index 000000000..aa0e3a72c --- /dev/null +++ b/support/vm/cbor_gen.go @@ -0,0 +1,250 @@ +// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. + +package vm_test + +import ( + "fmt" + "io" + + abi "github.com/filecoin-project/go-state-types/abi" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" +) + +var _ = xerrors.Errorf + +var lengthBufChainMessage = []byte{138} + +func (t *ChainMessage) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write(lengthBufChainMessage); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.Version (uint64) (uint64) + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Version)); err != nil { + return err + } + + // t.From (address.Address) (struct) + if err := t.From.MarshalCBOR(w); err != nil { + return err + } + + // t.To (address.Address) (struct) + if err := t.To.MarshalCBOR(w); err != nil { + return err + } + + // t.Nonce (uint64) (uint64) + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Nonce)); err != nil { + return err + } + + // t.Value (big.Int) (struct) + if err := t.Value.MarshalCBOR(w); err != nil { + return err + } + + // t.GasLimit (int64) (int64) + if t.GasLimit >= 0 { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.GasLimit)); err != nil { + return err + } + } else { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-t.GasLimit-1)); err != nil { + return err + } + } + + // t.GasFeeCap (big.Int) (struct) + if err := t.GasFeeCap.MarshalCBOR(w); err != nil { + return err + } + + // t.GasPremium (big.Int) (struct) + if err := t.GasPremium.MarshalCBOR(w); err != nil { + return err + } + + // t.Method (abi.MethodNum) (uint64) + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Method)); err != nil { + return err + } + + // t.Params ([]uint8) (slice) + if len(t.Params) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Params was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.Params))); err != nil { + return err + } + + if _, err := w.Write(t.Params[:]); err != nil { + return err + } + return nil +} + +func (t *ChainMessage) UnmarshalCBOR(r io.Reader) error { + *t = ChainMessage{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 10 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Version (uint64) (uint64) + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Version = uint64(extra) + + } + // t.From (address.Address) (struct) + + { + + if err := t.From.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.From: %w", err) + } + + } + // t.To (address.Address) (struct) + + { + + if err := t.To.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.To: %w", err) + } + + } + // t.Nonce (uint64) (uint64) + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Nonce = uint64(extra) + + } + // t.Value (big.Int) (struct) + + { + + if err := t.Value.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.Value: %w", err) + } + + } + // t.GasLimit (int64) (int64) + { + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative oveflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.GasLimit = int64(extraI) + } + // t.GasFeeCap (big.Int) (struct) + + { + + if err := t.GasFeeCap.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.GasFeeCap: %w", err) + } + + } + // t.GasPremium (big.Int) (struct) + + { + + if err := t.GasPremium.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.GasPremium: %w", err) + } + + } + // t.Method (abi.MethodNum) (uint64) + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Method = abi.MethodNum(extra) + + } + // t.Params ([]uint8) (slice) + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Params: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Params = make([]uint8, extra) + } + + if _, err := io.ReadFull(br, t.Params[:]); err != nil { + return err + } + return nil +} diff --git a/support/vm/invocation_context.go b/support/vm/invocation_context.go index 279068375..bc135e076 100644 --- a/support/vm/invocation_context.go +++ b/support/vm/invocation_context.go @@ -33,6 +33,8 @@ import ( var EmptyObjectCid cid.Cid +const testGasLimit = 5_000_000_000 + // Context for an individual message invocation, including inter-actor sends. type invocationContext struct { rt *VM @@ -47,6 +49,10 @@ type invocationContext struct { // Used for detecting modifications to state outside of transactions. stateUsedObjs map[cbor.Marshaler]cid.Cid stats *CallStats + // Gas trackign fields + gasPrices runtime.Pricelist + gasUsed int64 + gasAvailable int64 } // Context for a top-level invocation sequence @@ -58,7 +64,7 @@ type topLevelContext struct { circSupply abi.TokenAmount // default or externally specified circulating FIL supply } -func newInvocationContext(rt *VM, topLevel *topLevelContext, msg InternalMessage, fromActor *states.Actor, emptyObject cid.Cid) invocationContext { +func newInvocationContext(rt *VM, topLevel *topLevelContext, msg InternalMessage, fromActor *states.Actor, emptyObject cid.Cid, gasUsed int64, gasPrices runtime.Pricelist) invocationContext { // Note: the toActor and stateHandle are loaded during the `invoke()` return invocationContext{ rt: rt, @@ -71,6 +77,9 @@ func newInvocationContext(rt *VM, topLevel *topLevelContext, msg InternalMessage callerValidated: false, stateUsedObjs: map[cbor.Marshaler]cid.Cid{}, stats: vm2.NewCallStats(topLevel.statsSource), + gasUsed: gasUsed, + gasAvailable: testGasLimit, + gasPrices: gasPrices, } } @@ -84,9 +93,9 @@ func (ic *invocationContext) loadState(obj cbor.Unmarshaler) cid.Cid { if !c.Defined() { ic.Abortf(exitcode.SysErrorIllegalActor, "failed to load undefined state, must construct first") } - err := ic.rt.store.Get(ic.rt.ctx, c, obj) - if err != nil { - panic(errors.Wrapf(err, "failed to load state for actor %s, CID %s", ic.msg.to, c)) + found := ic.StoreGet(c, obj) + if !found { + panic(fmt.Errorf("state not found for actor %s, CID %s", ic.msg.to, c)) } return c } @@ -109,6 +118,21 @@ func (ic *invocationContext) storeActor(actr *states.Actor) { } } +func (ic *invocationContext) chargeGas(gas runtime.GasCharge) { + toUse := gas.Total() + if ic.gasUsed > ic.gasAvailable-toUse { + gasUsed := ic.gasUsed + ic.gasUsed = ic.gasAvailable + panic( + abort{ + exitcode.SysErrOutOfGas, + fmt.Sprintf("not enough gas: used=%d, available=%d, attempt to use=%d", gasUsed, ic.gasAvailable, toUse), + }, + ) + } + ic.gasUsed += toUse +} + ///////////////////////////////////////////// // Runtime methods ///////////////////////////////////////////// @@ -117,11 +141,21 @@ var _ runtime.Runtime = (*invocationContext)(nil) // Store implements runtime.Runtime. func (ic *invocationContext) StoreGet(c cid.Cid, o cbor.Unmarshaler) bool { + ic.chargeGas(ic.gasPrices.OnIpldGet()) sw := &storeWrapper{s: ic.rt.store, rt: ic.rt} return sw.StoreGet(c, o) } func (ic *invocationContext) StorePut(x cbor.Marshaler) cid.Cid { + // Serialize before putting data to charge gas + // This could be made more efficient by avoiding double serialization + // with a gas charging block store but this is easier for testing + var buf bytes.Buffer + err := x.MarshalCBOR(&buf) + if err != nil { + ic.rt.Abortf(exitcode.ErrIllegalState, "could not put object in store") + } + ic.chargeGas(ic.gasPrices.OnIpldPut(len(buf.Bytes()))) sw := &storeWrapper{s: ic.rt.store, rt: ic.rt} return sw.StorePut(x) } @@ -147,10 +181,7 @@ func (ic *invocationContext) StateCreate(obj cbor.Marshaler) { if actr.Head.Defined() && !ic.emptyObject.Equals(actr.Head) { ic.Abortf(exitcode.SysErrorIllegalActor, "failed to construct actor state: already initialized") } - c, err := ic.rt.store.Put(ic.rt.ctx, obj) - if err != nil { - ic.Abortf(exitcode.ErrIllegalState, "failed to create actor state") - } + c := ic.StorePut(obj) actr.Head = c ic.storeActor(actr) ic.stateUsedObjs[obj] = c // Track the expected CID of the object. @@ -183,22 +214,31 @@ func (ic *invocationContext) StateTransaction(obj cbor.Er, f func()) { } func (ic *invocationContext) VerifySignature(signature crypto.Signature, signer address.Address, plaintext []byte) error { + charge, err := ic.gasPrices.OnVerifySignature(signature.Type, len(plaintext)) + if err != nil { + return err + } + ic.chargeGas(charge) return ic.Syscalls().VerifySignature(signature, signer, plaintext) } func (ic *invocationContext) HashBlake2b(data []byte) [32]byte { + ic.chargeGas(ic.gasPrices.OnHashing(len(data))) return ic.Syscalls().HashBlake2b(data) } func (ic *invocationContext) ComputeUnsealedSectorCID(reg abi.RegisteredSealProof, pieces []abi.PieceInfo) (cid.Cid, error) { + ic.chargeGas(ic.gasPrices.OnComputeUnsealedSectorCid(reg, pieces)) return ic.Syscalls().ComputeUnsealedSectorCID(reg, pieces) } func (ic *invocationContext) VerifySeal(vi proof.SealVerifyInfo) error { + ic.chargeGas(ic.gasPrices.OnVerifySeal(vi)) return ic.Syscalls().VerifySeal(vi) } func (ic *invocationContext) BatchVerifySeals(vis map[address.Address][]proof.SealVerifyInfo) (map[address.Address][]bool, error) { + // no explicit gas charged return ic.Syscalls().BatchVerifySeals(vis) } @@ -207,10 +247,12 @@ func (ic *invocationContext) VerifyAggregateSeals(agg proof.AggregateSealVerifyP } func (ic *invocationContext) VerifyPoSt(vi proof.WindowPoStVerifyInfo) error { + ic.chargeGas(ic.gasPrices.OnVerifyPost(vi)) return ic.Syscalls().VerifyPoSt(vi) } func (ic *invocationContext) VerifyConsensusFault(h1, h2, extra []byte) (*runtime.ConsensusFault, error) { + ic.chargeGas(ic.gasPrices.OnVerifyConsensusFault()) return ic.Syscalls().VerifyConsensusFault(h1, h2, extra) } @@ -344,9 +386,10 @@ func (ic *invocationContext) Send(toAddr address.Address, methodNum abi.MethodNu params: params, } - newCtx := newInvocationContext(ic.rt, ic.topLevel, newMsg, fromActor, ic.emptyObject) + newCtx := newInvocationContext(ic.rt, ic.topLevel, newMsg, fromActor, ic.emptyObject, ic.gasUsed, ic.gasPrices) ret, code := newCtx.invoke() + ic.gasUsed = newCtx.gasUsed ic.stats.MergeSubStat(newCtx.toActor.Code, newMsg.method, newCtx.stats) err = ret.Into(out) @@ -358,6 +401,7 @@ func (ic *invocationContext) Send(toAddr address.Address, methodNum abi.MethodNu // CreateActor implements runtime.ExtendedInvocationContext. func (ic *invocationContext) CreateActor(codeID cid.Cid, addr address.Address) { + ic.chargeGas(ic.gasPrices.OnCreateActor()) act, ok := ic.rt.ActorImpls[codeID] if !ok { ic.Abortf(exitcode.SysErrorIllegalArgument, "Can only create built-in actors.") @@ -392,6 +436,7 @@ func (ic *invocationContext) CreateActor(codeID cid.Cid, addr address.Address) { // deleteActor implements runtime.ExtendedInvocationContext. func (ic *invocationContext) DeleteActor(beneficiary address.Address) { + ic.chargeGas(ic.gasPrices.OnDeleteActor()) receiver := ic.msg.to receiverActor, found, err := ic.rt.GetActor(receiver) if err != nil { @@ -421,8 +466,12 @@ func (ic *invocationContext) Context() context.Context { return ic.rt.ctx } -func (ic *invocationContext) ChargeGas(_ string, _ int64, _ int64) { - // no-op +func (ic *invocationContext) ChargeGas(name string, compute int64, virtual int64) { + ic.chargeGas(runtime.GasCharge{ + Name: name, + ComputeGas: compute, + VirtualCompute: virtual, + }) } // Starts a new tracing span. The span must be End()ed explicitly, typically with a deferred invocation. @@ -581,10 +630,11 @@ func (ic *invocationContext) invoke() (ret returnWrapper, errcode exitcode.ExitC // pre-dispatch // 1. load target actor - // 2. transfer optional funds - // 3. short-circuit _Send_ method - // 4. load target actor code - // 5. create target state handle + // 2. charge gas for method invoc + // 3. transfer optional funds + // 4. short-circuit _Send_ method + // 5. load target actor code + // 6. create target state handle // assert from address is an ID address. if ic.msg.from.Protocol() != address.ID { panic("bad Exitcode: sender address MUST be an ID address at invocation time") @@ -594,7 +644,10 @@ func (ic *invocationContext) invoke() (ret returnWrapper, errcode exitcode.ExitC // Note: we replace the "to" address with the normalized version ic.toActor, ic.msg.to = ic.resolveTarget(ic.msg.to) - // 3. transfer funds carried by the msg + // 3. charge gas for method invocation + ic.chargeGas(ic.gasPrices.OnMethodInvocation(ic.msg.value, ic.msg.method)) + + // 4. transfer funds carried by the msg if !ic.msg.value.NilOrZero() { if ic.msg.value.LessThan(big.Zero()) { ic.Abortf(exitcode.SysErrForbidden, "attempt to transfer negative value %s from %s to %s", @@ -607,13 +660,13 @@ func (ic *invocationContext) invoke() (ret returnWrapper, errcode exitcode.ExitC ic.toActor, ic.fromActor = ic.rt.transfer(ic.msg.from, ic.msg.to, ic.msg.value) } - // 4. if we are just sending funds, there is nothing else to do. + // 5. if we are just sending funds, there is nothing else to do. if ic.msg.method == builtin.MethodSend { ic.rt.endInvocation(exitcode.Ok, abi.Empty) return returnWrapper{abi.Empty}, exitcode.Ok } - // 5. load target actor code + // 6. load target actor code actorImpl := ic.rt.getActorImpl(ic.toActor.Code) // dispatch @@ -763,9 +816,10 @@ func (ic *invocationContext) resolveTarget(target address.Address) (*states.Acto params: &target, } - newCtx := newInvocationContext(ic.rt, ic.topLevel, newMsg, nil, ic.emptyObject) + newCtx := newInvocationContext(ic.rt, ic.topLevel, newMsg, nil, ic.emptyObject, ic.gasUsed, ic.gasPrices) _, code := newCtx.invoke() + ic.gasUsed = newCtx.gasUsed ic.stats.MergeSubStat(builtin.InitActorCodeID, builtin.MethodsAccount.Constructor, newCtx.stats) if code.IsError() { @@ -799,10 +853,7 @@ func (ic *invocationContext) replace(obj cbor.Marshaler) cid.Cid { if !found { ic.rt.Abortf(exitcode.ErrIllegalState, "failed to find actor %s for state", ic.msg.to) } - c, err := ic.rt.store.Put(ic.rt.ctx, obj) - if err != nil { - ic.rt.Abortf(exitcode.ErrIllegalState, "could not save new state") - } + c := ic.StorePut(obj) actr.Head = c err = ic.rt.setActor(ic.rt.ctx, ic.msg.to, actr) if err != nil { diff --git a/support/vm/price_list.go b/support/vm/price_list.go new file mode 100644 index 000000000..cf8faf2dd --- /dev/null +++ b/support/vm/price_list.go @@ -0,0 +1,282 @@ +package vm_test + +import ( + "fmt" + + abi "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/specs-actors/v5/actors/builtin" + "github.com/filecoin-project/specs-actors/v5/actors/runtime" + "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" +) + +func newGasCharge(name string, computeGas int64, storageGas int64) runtime.GasCharge { + return runtime.GasCharge{ + Name: name, + ComputeGas: computeGas, + StorageGas: storageGas, + } +} + +type scalingCost struct { + flat int64 + scale int64 +} + +type pricelist struct { + computeGasMulti int64 + storageGasMulti int64 + /////////////////////////////////////////////////////////////////////////// + // 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 + // Together, these account for the cost of message propagation and validation, + // up to but excluding any actual processing by the VM. + // This is the cost a block producer burns when including an invalid message. + onChainMessageComputeBase int64 + onChainMessageStorageBase int64 + onChainMessageStoragePerByte int64 + + // Gas cost charged to the originator of a non-nil return value produced + // by an on-chain message is given by: + // len(return value)*OnChainReturnValuePerByte + onChainReturnValuePerByte int64 + + // Gas cost for any message send execution(including the top-level one + // initiated by an on-chain message). + // This accounts for the cost of loading sender and receiver actors and + // (for top-level messages) incrementing the sender's sequence number. + // Load and store of actor sub-state is charged separately. + sendBase int64 + + // Gas cost charged, in addition to SendBase, if a message send + // is accompanied by any nonzero currency amount. + // Accounts for writing receiver's new balance (the sender's state is + // already accounted for). + sendTransferFunds int64 + + // Gsa cost charged, in addition to SendBase, if message only transfers funds. + sendTransferOnlyPremium int64 + + // Gas cost charged, in addition to SendBase, if a message invokes + // a method on the receiver. + // Accounts for the cost of loading receiver code and method dispatch. + sendInvokeMethod int64 + + // Gas cost for any Get operation to the IPLD store + // in the runtime VM context. + ipldGetBase int64 + + // 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 int64 + ipldPutPerByte int64 + + // Gas cost for creating a new actor (via InitActor's Exec method). + // + // Note: this costs assume that the extra will be partially or totally refunded while + // the base is covering for the put. + createActorCompute int64 + createActorStorage int64 + + // Gas cost for deleting an actor. + // + // Note: this partially refunds the create cost to incentivise the deletion of the actors. + deleteActor int64 + + verifySignature map[crypto.SigType]int64 + + hashingBase int64 + + computeUnsealedSectorCidBase int64 + verifySealBase int64 + verifyPostLookup map[abi.RegisteredPoStProof]scalingCost + verifyPostDiscount bool + verifyConsensusFault int64 +} + +var _ runtime.Pricelist = (*pricelist)(nil) + +// OnChainMessage returns the gas used for storing a message of a given size in the chain. +func (pl *pricelist) OnChainMessage(msgSize int) runtime.GasCharge { + return newGasCharge("OnChainMessage", pl.onChainMessageComputeBase, + (pl.onChainMessageStorageBase+pl.onChainMessageStoragePerByte*int64(msgSize))*pl.storageGasMulti) +} + +// OnChainReturnValue returns the gas used for storing the response of a message in the chain. +func (pl *pricelist) OnChainReturnValue(dataSize int) runtime.GasCharge { + return newGasCharge("OnChainReturnValue", 0, int64(dataSize)*pl.onChainReturnValuePerByte*pl.storageGasMulti) +} + +// OnMethodInvocation returns the gas used when invoking a method. +func (pl *pricelist) OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) runtime.GasCharge { + ret := pl.sendBase + extra := "" + + if big.Cmp(value, abi.NewTokenAmount(0)) != 0 { + ret += pl.sendTransferFunds + if methodNum == builtin.MethodSend { + // 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) +} + +// OnIpldGet returns the gas used for storing an object +func (pl *pricelist) OnIpldGet() runtime.GasCharge { + return newGasCharge("OnIpldGet", pl.ipldGetBase, 0).WithVirtual(114617, 0) +} + +// OnIpldPut returns the gas used for storing an object +func (pl *pricelist) OnIpldPut(dataSize int) runtime.GasCharge { + return newGasCharge("OnIpldPut", pl.ipldPutBase, int64(dataSize)*pl.ipldPutPerByte*pl.storageGasMulti). + WithExtra(dataSize).WithVirtual(400000, int64(dataSize)*1300) +} + +// OnCreateActor returns the gas used for creating an actor +func (pl *pricelist) OnCreateActor() runtime.GasCharge { + return newGasCharge("OnCreateActor", pl.createActorCompute, pl.createActorStorage*pl.storageGasMulti) +} + +// OnDeleteActor returns the gas used for deleting an actor +func (pl *pricelist) OnDeleteActor() runtime.GasCharge { + return newGasCharge("OnDeleteActor", 0, pl.deleteActor*pl.storageGasMulti) +} + +// OnVerifySignature + +func (pl *pricelist) OnVerifySignature(sigType crypto.SigType, planTextSize int) (runtime.GasCharge, error) { + cost, ok := pl.verifySignature[sigType] + if !ok { + return runtime.GasCharge{}, fmt.Errorf("cost function for signature type %d not supported", sigType) + } + + sigName, _ := sigType.Name() + return newGasCharge("OnVerifySignature", cost, 0). + WithExtra(map[string]interface{}{ + "type": sigName, + "size": planTextSize, + }), nil +} + +// OnHashing +func (pl *pricelist) OnHashing(dataSize int) runtime.GasCharge { + return newGasCharge("OnHashing", pl.hashingBase, 0).WithExtra(dataSize) +} + +// OnComputeUnsealedSectorCid +func (pl *pricelist) OnComputeUnsealedSectorCid(proofType abi.RegisteredSealProof, pieces []abi.PieceInfo) runtime.GasCharge { + return newGasCharge("OnComputeUnsealedSectorCid", pl.computeUnsealedSectorCidBase, 0) +} + +// OnVerifySeal +func (pl *pricelist) OnVerifySeal(info proof.SealVerifyInfo) runtime.GasCharge { + // TODO: this needs more cost tunning, check with @lotus + // this is not used + return newGasCharge("OnVerifySeal", pl.verifySealBase, 0) +} + +// OnVerifyPost +func (pl *pricelist) OnVerifyPost(info proof.WindowPoStVerifyInfo) runtime.GasCharge { + sectorSize := "unknown" + var proofType abi.RegisteredPoStProof + + if len(info.Proofs) != 0 { + proofType = info.Proofs[0].PoStProof + ss, err := info.Proofs[0].PoStProof.SectorSize() + if err == nil { + sectorSize = ss.ShortString() + } + } + + cost, ok := pl.verifyPostLookup[proofType] + if !ok { + cost = pl.verifyPostLookup[abi.RegisteredPoStProof_StackedDrgWindow512MiBV1] + } + + gasUsed := cost.flat + int64(len(info.ChallengedSectors))*cost.scale + if pl.verifyPostDiscount { + gasUsed /= 2 // XXX: this is an artificial discount + } + + return newGasCharge("OnVerifyPost", gasUsed, 0). + WithVirtual(117680921+43780*int64(len(info.ChallengedSectors)), 0). + WithExtra(map[string]interface{}{ + "type": sectorSize, + "size": len(info.ChallengedSectors), + }) +} + +// OnVerifyConsensusFault +func (pl *pricelist) OnVerifyConsensusFault() runtime.GasCharge { + return newGasCharge("OnVerifyConsensusFault", pl.verifyConsensusFault, 0) +} + +// gas prices as of filecoin v13 +// Note this should be updated to latest next upgrade pricelist before conformance +// test vector generation to ensure it is up to date with latest protocol. +// Source of truth here: https://github.com/filecoin-project/lotus/blob/master/chain/vm/gas.go#L82 +var v13PriceList = pricelist{ + computeGasMulti: 1, + storageGasMulti: 1300, + + onChainMessageComputeBase: 38863, + onChainMessageStorageBase: 36, + onChainMessageStoragePerByte: 1, + + onChainReturnValuePerByte: 1, + + sendBase: 29233, + sendTransferFunds: 27500, + sendTransferOnlyPremium: 159672, + sendInvokeMethod: -5377, + + ipldGetBase: 114617, + ipldPutBase: 353640, + 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: 117680921, + scale: 43780, + }, + abi.RegisteredPoStProof_StackedDrgWindow32GiBV1: { + flat: 117680921, + scale: 43780, + }, + abi.RegisteredPoStProof_StackedDrgWindow64GiBV1: { + flat: 117680921, + scale: 43780, + }, + }, + verifyPostDiscount: false, + verifyConsensusFault: 495422, +} diff --git a/support/vm/testing.go b/support/vm/testing.go index 3c76c0530..29b42f30f 100644 --- a/support/vm/testing.go +++ b/support/vm/testing.go @@ -304,7 +304,7 @@ func AdvanceByDeadline(t *testing.T, v *VM, minerIDAddr address.Address, predica v, err = v.WithEpoch(dlInfo.Last()) require.NoError(t, err) - _, code := v.ApplyMessage(builtin.SystemActorAddr, builtin.CronActorAddr, big.Zero(), builtin.MethodsCron.EpochTick, nil) + _, code, _ := v.ApplyMessage(builtin.SystemActorAddr, builtin.CronActorAddr, big.Zero(), builtin.MethodsCron.EpochTick, nil) require.Equal(t, exitcode.Ok, code) dlInfo = NextMinerDLInfo(t, v, minerIDAddr) @@ -480,7 +480,7 @@ func GetDealState(t *testing.T, vm *VM, dealID abi.DealID) (*market.DealState, b // func ApplyOk(t *testing.T, v *VM, from, to address.Address, value abi.TokenAmount, method abi.MethodNum, params interface{}) cbor.Marshaler { - ret, code := v.ApplyMessage(from, to, value, method, params) + ret, code, _ := v.ApplyMessage(from, to, value, method, params) require.Equal(t, exitcode.Ok, code) return ret } diff --git a/support/vm/vm.go b/support/vm/vm.go index e4762b8f2..088fe8b14 100644 --- a/support/vm/vm.go +++ b/support/vm/vm.go @@ -1,6 +1,7 @@ package vm import ( + "bytes" "context" "fmt" @@ -38,8 +39,7 @@ type VM struct { actors *adt.Map // The current (not necessarily committed) root node. actorsDirty bool - emptyObject cid.Cid - callSequence uint64 + emptyObject cid.Cid logs []string invocationStack []*Invocation @@ -49,6 +49,8 @@ type VM struct { statsByMethod StatsByCall circSupply abi.TokenAmount + + gasPrices runtime.Pricelist } // VM types @@ -64,6 +66,50 @@ type InternalMessage struct { params interface{} } +type ChainMessage struct { + Version uint64 + + From address.Address + To address.Address + + Nonce uint64 + + Value abi.TokenAmount + + GasLimit int64 + GasFeeCap abi.TokenAmount + GasPremium abi.TokenAmount + + Method abi.MethodNum + Params []byte +} + +func makeChainMessage(from, to address.Address, nonce uint64, value abi.TokenAmount, method abi.MethodNum, params interface{}) (*ChainMessage, error) { + var buf bytes.Buffer + if params == nil { + if err := abi.Empty.MarshalCBOR(&buf); err != nil { + return nil, err + } + } else { + if err := params.(cbor.Er).MarshalCBOR(&buf); err != nil { + return nil, err + } + } + + return &ChainMessage{ + Version: 0, + From: from, + To: to, + Nonce: nonce, + Value: value, + GasLimit: 5_000_000_000, + GasFeeCap: big.Zero(), + GasPremium: big.Zero(), + Method: method, + Params: buf.Bytes(), + }, nil +} + type Invocation struct { Msg *InternalMessage Exitcode exitcode.ExitCode @@ -98,6 +144,7 @@ func NewVM(ctx context.Context, actorImpls ActorImplLookup, store adt.Store) *VM networkVersion: network.VersionMax, statsByMethod: make(StatsByCall), circSupply: big.Mul(big.NewInt(1e9), big.NewInt(1e18)), + gasPrices: &v13PriceList, } } @@ -125,6 +172,7 @@ func NewVMAtEpoch(ctx context.Context, actorImpls ActorImplLookup, store adt.Sto networkVersion: network.VersionMax, statsByMethod: make(StatsByCall), circSupply: big.Mul(big.NewInt(1e9), big.NewInt(1e18)), + gasPrices: &v13PriceList, }, nil } @@ -152,6 +200,7 @@ func (vm *VM) WithEpoch(epoch abi.ChainEpoch) (*VM, error) { statsSource: vm.statsSource, statsByMethod: make(StatsByCall), circSupply: vm.circSupply, + gasPrices: &v13PriceList, }, nil } @@ -179,6 +228,7 @@ func (vm *VM) WithNetworkVersion(nv network.Version) (*VM, error) { statsSource: vm.statsSource, statsByMethod: make(StatsByCall), circSupply: vm.circSupply, + gasPrices: &v13PriceList, }, nil } @@ -285,15 +335,16 @@ func (vm *VM) NormalizeAddress(addr address.Address) (address.Address, bool) { } // ApplyMessage applies the message to the current state. -func (vm *VM) ApplyMessage(from, to address.Address, value abi.TokenAmount, method abi.MethodNum, params interface{}) (cbor.Marshaler, exitcode.ExitCode) { +func (vm *VM) ApplyMessage(from, to address.Address, value abi.TokenAmount, method abi.MethodNum, params interface{}) (cbor.Marshaler, exitcode.ExitCode, int64) { // This method does not actually execute the message itself, // but rather deals with the pre/post processing of a message. // (see: `invocationContext.invoke()` for the dispatch and execution) + gasCharged := int64(0) // load actor from global state fromID, ok := vm.NormalizeAddress(from) if !ok { - return nil, exitcode.SysErrSenderInvalid + return nil, exitcode.SysErrSenderInvalid, gasCharged } fromActor, found, err := vm.GetActor(fromID) @@ -302,7 +353,7 @@ func (vm *VM) ApplyMessage(from, to address.Address, value abi.TokenAmount, meth } if !found { // Execution error; sender does not exist at time of message execution. - return nil, exitcode.SysErrSenderInvalid + return nil, exitcode.SysErrSenderInvalid, gasCharged } // checkpoint state @@ -315,19 +366,37 @@ func (vm *VM) ApplyMessage(from, to address.Address, value abi.TokenAmount, meth } // send - // 1. build internal message - // 2. build invocation context - // 3. process the msg + // 1. update state tree nonce + // 2. build chain message and charge gas + // 3. build internal message + // 4. build invocation context + // 5. process the msg + callSeq := fromActor.CallSeqNum + fromActor.CallSeqNum = callSeq + 1 + if err := vm.setActor(context.Background(), fromID, fromActor); err != nil { + panic(err) + } + + msg, err := makeChainMessage(from, to, callSeq, value, method, params) + if err != nil { + panic(err) + } + var msgBuf bytes.Buffer + if err := msg.MarshalCBOR(&msgBuf); err != nil { + panic(err) + } + bs := msgBuf.Bytes() + charge := vm.gasPrices.OnChainMessage(len(bs)) + msgGasCharge := charge.Total() topLevel := topLevelContext{ originatorStableAddress: from, // this should be nonce, but we only care that it creates a unique stable address - originatorCallSeq: vm.callSequence, + originatorCallSeq: callSeq, newActorAddressCount: 0, statsSource: vm.statsSource, circSupply: vm.circSupply, } - vm.callSequence++ // build internal msg imsg := InternalMessage{ @@ -339,7 +408,7 @@ func (vm *VM) ApplyMessage(from, to address.Address, value abi.TokenAmount, meth } // build invocation context - ctx := newInvocationContext(vm, &topLevel, imsg, fromActor, vm.emptyObject) + ctx := newInvocationContext(vm, &topLevel, imsg, fromActor, vm.emptyObject, msgGasCharge, vm.gasPrices) // 3. invoke ret, exitCode := ctx.invoke() @@ -360,10 +429,17 @@ func (vm *VM) ApplyMessage(from, to address.Address, value abi.TokenAmount, meth if _, err := vm.checkpoint(); err != nil { panic(err) } + } + // serialize return and charge gas + var retBuf bytes.Buffer + if err := ret.inner.MarshalCBOR(&retBuf); err != nil { + panic(err) } + retGasCharge := vm.gasPrices.OnChainReturnValue(len(retBuf.Bytes())) + gasCharged = retGasCharge.Total() + ctx.gasUsed - return ret.inner, exitCode + return ret.inner, exitCode, gasCharged } func (vm *VM) StateRoot() cid.Cid { @@ -437,7 +513,7 @@ func (vm *VM) GetActorImpls() map[cid.Cid]rt.VMActor { // transfer debits money from one account and credits it to another. // avoid calling this method with a zero amount else it will perform unnecessary actor loading. // -// WARNING: this method will panic if the the amount is negative, accounts dont exist, or have inssuficient funds. +// WARNING: this method will panic if the the amount is negative, accounts dont exist, or have insufficient funds. // // Note: this is not idiomatic, it follows the Spec expectations for this method. func (vm *VM) transfer(debitFrom address.Address, creditTo address.Address, amount abi.TokenAmount) (*states.Actor, *states.Actor) { From d02c10378400cbd55dc6f64fbed2f0e0c95119be Mon Sep 17 00:00:00 2001 From: ZenGround0 Date: Wed, 19 May 2021 11:33:30 -0400 Subject: [PATCH 2/2] Review --- actors/runtime/runtime.go | 27 ------- actors/runtime/types.go | 27 ------- actors/test/commit_post_test.go | 4 +- actors/test/common_test.go | 6 +- actors/test/multisig_delete_self_test.go | 12 ++-- gen/gen.go | 6 +- support/agent/sim.go | 22 +++--- support/vm/cbor_gen.go | 2 +- support/vm/invocation_context.go | 79 ++++++++++---------- support/vm/price_list.go | 91 +++++++++++++++++++----- support/vm/testing.go | 10 +-- support/vm/vm.go | 25 ++++--- 12 files changed, 158 insertions(+), 153 deletions(-) diff --git a/actors/runtime/runtime.go b/actors/runtime/runtime.go index 1907b7b5c..ba0b4f3de 100644 --- a/actors/runtime/runtime.go +++ b/actors/runtime/runtime.go @@ -231,30 +231,3 @@ type StateHandle interface { // ``` StateTransaction(obj cbor.Er, f func()) } - -type Pricelist interface { - // OnChainMessage returns the gas used for storing a message of a given size in the chain. - OnChainMessage(msgSize int) GasCharge - // OnChainReturnValue returns the gas used for storing the response of a message in the chain. - OnChainReturnValue(dataSize int) GasCharge - - // OnMethodInvocation returns the gas used when invoking a method. - OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) GasCharge - - // OnIpldGet returns the gas used for storing an object - OnIpldGet() GasCharge - // OnIpldPut returns the gas used for storing an object - OnIpldPut(dataSize int) GasCharge - - // OnCreateActor returns the gas used for creating an actor - OnCreateActor() GasCharge - // OnDeleteActor returns the gas used for deleting an actor - OnDeleteActor() GasCharge - - OnVerifySignature(sigType crypto.SigType, planTextSize int) (GasCharge, error) - OnHashing(dataSize int) GasCharge - OnComputeUnsealedSectorCid(proofType abi.RegisteredSealProof, pieces []abi.PieceInfo) GasCharge - OnVerifySeal(info proof.SealVerifyInfo) GasCharge - OnVerifyPost(info proof.WindowPoStVerifyInfo) GasCharge - OnVerifyConsensusFault() GasCharge -} diff --git a/actors/runtime/types.go b/actors/runtime/types.go index 1f84d8dd1..37682d646 100644 --- a/actors/runtime/types.go +++ b/actors/runtime/types.go @@ -28,30 +28,3 @@ const ( ) type VMActor = rt.VMActor - -type GasCharge struct { - Name string - Extra interface{} - - ComputeGas int64 - StorageGas int64 - - VirtualCompute int64 - VirtualStorage int64 -} - -func (g GasCharge) Total() int64 { - return g.ComputeGas + g.StorageGas -} -func (g GasCharge) WithVirtual(compute, storage int64) GasCharge { - out := g - out.VirtualCompute = compute - out.VirtualStorage = storage - return out -} - -func (g GasCharge) WithExtra(extra interface{}) GasCharge { - out := g - out.Extra = extra - return out -} diff --git a/actors/test/commit_post_test.go b/actors/test/commit_post_test.go index f7d395605..1d5524d43 100644 --- a/actors/test/commit_post_test.go +++ b/actors/test/commit_post_test.go @@ -287,8 +287,8 @@ func TestCommitPoStFlow(t *testing.T) { ChainCommitRand: []byte("not really random"), } // PoSt is rejected for skipping all sectors. - _, code, _ := tv.ApplyMessage(addrs[0], minerAddrs.RobustAddress, big.Zero(), builtin.MethodsMiner.SubmitWindowedPoSt, &submitParams) - assert.Equal(t, exitcode.ErrIllegalArgument, code) + result := tv.ApplyMessage(addrs[0], minerAddrs.RobustAddress, big.Zero(), builtin.MethodsMiner.SubmitWindowedPoSt, &submitParams) + assert.Equal(t, exitcode.ErrIllegalArgument, result.Code) vm.ExpectInvocation{ To: minerAddrs.IDAddress, diff --git a/actors/test/common_test.go b/actors/test/common_test.go index 8de6f5a52..6aeb73b36 100644 --- a/actors/test/common_test.go +++ b/actors/test/common_test.go @@ -41,8 +41,8 @@ func publishDeal(t *testing.T, v *vm.VM, provider, dealClient, minerID addr.Addr }, }}, } - ret, code, _ := v.ApplyMessage(provider, builtin.StorageMarketActorAddr, big.Zero(), builtin.MethodsMarket.PublishStorageDeals, &publishDealParams) - require.Equal(t, exitcode.Ok, code) + result := v.ApplyMessage(provider, builtin.StorageMarketActorAddr, big.Zero(), builtin.MethodsMarket.PublishStorageDeals, &publishDealParams) + require.Equal(t, exitcode.Ok, result.Code) expectedPublishSubinvocations := []vm.ExpectInvocation{ {To: minerID, Method: builtin.MethodsMiner.ControlAddresses, SubInvocations: []vm.ExpectInvocation{}}, @@ -64,5 +64,5 @@ func publishDeal(t *testing.T, v *vm.VM, provider, dealClient, minerID addr.Addr SubInvocations: expectedPublishSubinvocations, }.Matches(t, v.LastInvocation()) - return ret.(*market.PublishStorageDealsReturn) + return result.Ret.(*market.PublishStorageDealsReturn) } diff --git a/actors/test/multisig_delete_self_test.go b/actors/test/multisig_delete_self_test.go index 892685bd8..f49ceaa36 100644 --- a/actors/test/multisig_delete_self_test.go +++ b/actors/test/multisig_delete_self_test.go @@ -64,8 +64,8 @@ func TestMultisigDeleteSelf2Of3RemovedIsProposer(t *testing.T) { vm.ApplyOk(t, v, addrs[1], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) // txnid not found when third approval gets processed indicating that the transaction has gone through successfully - _, code, _ := v.ApplyMessage(addrs[2], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) - assert.Equal(t, exitcode.ErrNotFound, code) + result := v.ApplyMessage(addrs[2], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) + assert.Equal(t, exitcode.ErrNotFound, result.Code) } @@ -115,8 +115,8 @@ func TestMultisigDeleteSelf2Of3RemovedIsApprover(t *testing.T) { vm.ApplyOk(t, v, addrs[0], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) // txnid not found when third approval gets processed indicating that the transaction has gone through successfully - _, code, _ := v.ApplyMessage(addrs[2], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) - assert.Equal(t, exitcode.ErrNotFound, code) + result := v.ApplyMessage(addrs[2], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) + assert.Equal(t, exitcode.ErrNotFound, result.Code) } @@ -165,8 +165,8 @@ func TestMultisigDeleteSelf2Of2(t *testing.T) { vm.ApplyOk(t, v, addrs[1], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) // txnid not found when another approval gets processed indicating that the transaction has gone through successfully - _, code, _ := v.ApplyMessage(addrs[1], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) - assert.Equal(t, exitcode.ErrNotFound, code) + result := v.ApplyMessage(addrs[1], multisigAddr, big.Zero(), builtin.MethodsMultisig.Approve, &approveRemoveSignerParams) + assert.Equal(t, exitcode.ErrNotFound, result.Code) } func TestMultisigSwapsSelf2Of3(t *testing.T) { diff --git a/gen/gen.go b/gen/gen.go index 1bedb5c0d..ee7feab70 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -16,7 +16,7 @@ import ( "github.com/filecoin-project/specs-actors/v5/actors/builtin/system" "github.com/filecoin-project/specs-actors/v5/actors/builtin/verifreg" "github.com/filecoin-project/specs-actors/v5/actors/util/smoothing" - vm_test "github.com/filecoin-project/specs-actors/v5/support/vm" + "github.com/filecoin-project/specs-actors/v5/support/vm" ) func main() { @@ -234,8 +234,8 @@ func main() { panic(err) } - if err := gen.WriteTupleEncodersToFile("./support/vm/cbor_gen.go", "vm_test", - vm_test.ChainMessage{}, + if err := gen.WriteTupleEncodersToFile("./support/vm/cbor_gen.go", "vm", + vm.ChainMessage{}, ); err != nil { panic(err) } diff --git a/support/agent/sim.go b/support/agent/sim.go index 89fd85c18..d2fc653ac 100644 --- a/support/agent/sim.go +++ b/support/agent/sim.go @@ -155,15 +155,15 @@ func (s *Sim) Tick() error { // run messages for _, msg := range blockMessages { - ret, code, _ := s.v.ApplyMessage(msg.From, msg.To, msg.Value, msg.Method, msg.Params) + result := s.v.ApplyMessage(msg.From, msg.To, msg.Value, msg.Method, msg.Params) // for now, assume everything should work - if code != exitcode.Ok { - return errors.Errorf("exitcode %d: message failed: %v\n%s\n", code, msg, strings.Join(s.v.GetLogs(), "\n")) + if result.Code != exitcode.Ok { + return errors.Errorf("exitcode %d: message failed: %v\n%s\n", result.Code, msg, strings.Join(s.v.GetLogs(), "\n")) } if msg.ReturnHandler != nil { - if err := msg.ReturnHandler(s, msg, ret); err != nil { + if err := msg.ReturnHandler(s, msg, result.Ret); err != nil { return err } } @@ -185,9 +185,9 @@ func (s *Sim) Tick() error { } // run cron - _, code, _ := s.v.ApplyMessage(builtin.SystemActorAddr, builtin.CronActorAddr, big.Zero(), builtin.MethodsCron.EpochTick, nil) - if code != exitcode.Ok { - return errors.Errorf("exitcode %d: cron message failed:\n%s\n", code, strings.Join(s.v.GetLogs(), "\n")) + result := s.v.ApplyMessage(builtin.SystemActorAddr, builtin.CronActorAddr, big.Zero(), builtin.MethodsCron.EpochTick, nil) + if result.Code != exitcode.Ok { + return errors.Errorf("exitcode %d: cron message failed:\n%s\n", result.Code, strings.Join(s.v.GetLogs(), "\n")) } // store last stats @@ -315,9 +315,9 @@ func (s *Sim) rewardMiner(addr address.Address, wins uint64) error { GasReward: big.Zero(), WinCount: int64(wins), } - _, code, _ := s.v.ApplyMessage(builtin.SystemActorAddr, builtin.RewardActorAddr, big.Zero(), builtin.MethodsReward.AwardBlockReward, &rewardParams) - if code != exitcode.Ok { - return errors.Errorf("exitcode %d: reward message failed:\n%s\n", code, strings.Join(s.v.GetLogs(), "\n")) + result := s.v.ApplyMessage(builtin.SystemActorAddr, builtin.RewardActorAddr, big.Zero(), builtin.MethodsReward.AwardBlockReward, &rewardParams) + if result.Code != exitcode.Ok { + return errors.Errorf("exitcode %d: reward message failed:\n%s\n", result.Code, strings.Join(s.v.GetLogs(), "\n")) } return nil } @@ -441,7 +441,7 @@ type PowerTable struct { // VM interface allowing a simulation to operate over multiple VM versions type SimVM interface { - ApplyMessage(from, to address.Address, value abi.TokenAmount, method abi.MethodNum, params interface{}) (cbor.Marshaler, exitcode.ExitCode, int64) + ApplyMessage(from, to address.Address, value abi.TokenAmount, method abi.MethodNum, params interface{}) vm.MessageResult GetCirculatingSupply() abi.TokenAmount GetLogs() []string GetState(addr address.Address, out cbor.Unmarshaler) error diff --git a/support/vm/cbor_gen.go b/support/vm/cbor_gen.go index aa0e3a72c..8f4e877d0 100644 --- a/support/vm/cbor_gen.go +++ b/support/vm/cbor_gen.go @@ -1,6 +1,6 @@ // Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. -package vm_test +package vm import ( "fmt" diff --git a/support/vm/invocation_context.go b/support/vm/invocation_context.go index bc135e076..90c2e534a 100644 --- a/support/vm/invocation_context.go +++ b/support/vm/invocation_context.go @@ -33,7 +33,7 @@ import ( var EmptyObjectCid cid.Cid -const testGasLimit = 5_000_000_000 +const defaultGasLimit = 5_000_000_000 // Context for an individual message invocation, including inter-actor sends. type invocationContext struct { @@ -49,10 +49,6 @@ type invocationContext struct { // Used for detecting modifications to state outside of transactions. stateUsedObjs map[cbor.Marshaler]cid.Cid stats *CallStats - // Gas trackign fields - gasPrices runtime.Pricelist - gasUsed int64 - gasAvailable int64 } // Context for a top-level invocation sequence @@ -62,9 +58,28 @@ type topLevelContext struct { newActorAddressCount uint64 // Count of calls to NewActorAddress (mutable). statsSource StatsSource // optional source of external statistics that can be used to profile calls circSupply abi.TokenAmount // default or externally specified circulating FIL supply + // Gas tracking fields + gasPrices Pricelist + gasUsed int64 + gasAvailable int64 +} + +func (tc *topLevelContext) chargeGas(gas GasCharge) { + toUse := gas.Total() + if tc.gasUsed > tc.gasAvailable-toUse { + gasUsed := tc.gasUsed + tc.gasUsed = tc.gasAvailable + panic( + abort{ + exitcode.SysErrOutOfGas, + fmt.Sprintf("not enough gas: used=%d, available=%d, attempt to use=%d", gasUsed, tc.gasAvailable, toUse), + }, + ) + } + tc.gasUsed += toUse } -func newInvocationContext(rt *VM, topLevel *topLevelContext, msg InternalMessage, fromActor *states.Actor, emptyObject cid.Cid, gasUsed int64, gasPrices runtime.Pricelist) invocationContext { +func newInvocationContext(rt *VM, topLevel *topLevelContext, msg InternalMessage, fromActor *states.Actor, emptyObject cid.Cid) invocationContext { // Note: the toActor and stateHandle are loaded during the `invoke()` return invocationContext{ rt: rt, @@ -77,9 +92,6 @@ func newInvocationContext(rt *VM, topLevel *topLevelContext, msg InternalMessage callerValidated: false, stateUsedObjs: map[cbor.Marshaler]cid.Cid{}, stats: vm2.NewCallStats(topLevel.statsSource), - gasUsed: gasUsed, - gasAvailable: testGasLimit, - gasPrices: gasPrices, } } @@ -118,21 +130,6 @@ func (ic *invocationContext) storeActor(actr *states.Actor) { } } -func (ic *invocationContext) chargeGas(gas runtime.GasCharge) { - toUse := gas.Total() - if ic.gasUsed > ic.gasAvailable-toUse { - gasUsed := ic.gasUsed - ic.gasUsed = ic.gasAvailable - panic( - abort{ - exitcode.SysErrOutOfGas, - fmt.Sprintf("not enough gas: used=%d, available=%d, attempt to use=%d", gasUsed, ic.gasAvailable, toUse), - }, - ) - } - ic.gasUsed += toUse -} - ///////////////////////////////////////////// // Runtime methods ///////////////////////////////////////////// @@ -141,7 +138,7 @@ var _ runtime.Runtime = (*invocationContext)(nil) // Store implements runtime.Runtime. func (ic *invocationContext) StoreGet(c cid.Cid, o cbor.Unmarshaler) bool { - ic.chargeGas(ic.gasPrices.OnIpldGet()) + ic.topLevel.chargeGas(ic.topLevel.gasPrices.OnIpldGet()) sw := &storeWrapper{s: ic.rt.store, rt: ic.rt} return sw.StoreGet(c, o) } @@ -155,7 +152,7 @@ func (ic *invocationContext) StorePut(x cbor.Marshaler) cid.Cid { if err != nil { ic.rt.Abortf(exitcode.ErrIllegalState, "could not put object in store") } - ic.chargeGas(ic.gasPrices.OnIpldPut(len(buf.Bytes()))) + ic.topLevel.chargeGas(ic.topLevel.gasPrices.OnIpldPut(len(buf.Bytes()))) sw := &storeWrapper{s: ic.rt.store, rt: ic.rt} return sw.StorePut(x) } @@ -214,26 +211,26 @@ func (ic *invocationContext) StateTransaction(obj cbor.Er, f func()) { } func (ic *invocationContext) VerifySignature(signature crypto.Signature, signer address.Address, plaintext []byte) error { - charge, err := ic.gasPrices.OnVerifySignature(signature.Type, len(plaintext)) + charge, err := ic.topLevel.gasPrices.OnVerifySignature(signature.Type, len(plaintext)) if err != nil { return err } - ic.chargeGas(charge) + ic.topLevel.chargeGas(charge) return ic.Syscalls().VerifySignature(signature, signer, plaintext) } func (ic *invocationContext) HashBlake2b(data []byte) [32]byte { - ic.chargeGas(ic.gasPrices.OnHashing(len(data))) + ic.topLevel.chargeGas(ic.topLevel.gasPrices.OnHashing(len(data))) return ic.Syscalls().HashBlake2b(data) } func (ic *invocationContext) ComputeUnsealedSectorCID(reg abi.RegisteredSealProof, pieces []abi.PieceInfo) (cid.Cid, error) { - ic.chargeGas(ic.gasPrices.OnComputeUnsealedSectorCid(reg, pieces)) + ic.topLevel.chargeGas(ic.topLevel.gasPrices.OnComputeUnsealedSectorCid(reg, pieces)) return ic.Syscalls().ComputeUnsealedSectorCID(reg, pieces) } func (ic *invocationContext) VerifySeal(vi proof.SealVerifyInfo) error { - ic.chargeGas(ic.gasPrices.OnVerifySeal(vi)) + ic.topLevel.chargeGas(ic.topLevel.gasPrices.OnVerifySeal(vi)) return ic.Syscalls().VerifySeal(vi) } @@ -247,12 +244,12 @@ func (ic *invocationContext) VerifyAggregateSeals(agg proof.AggregateSealVerifyP } func (ic *invocationContext) VerifyPoSt(vi proof.WindowPoStVerifyInfo) error { - ic.chargeGas(ic.gasPrices.OnVerifyPost(vi)) + ic.topLevel.chargeGas(ic.topLevel.gasPrices.OnVerifyPost(vi)) return ic.Syscalls().VerifyPoSt(vi) } func (ic *invocationContext) VerifyConsensusFault(h1, h2, extra []byte) (*runtime.ConsensusFault, error) { - ic.chargeGas(ic.gasPrices.OnVerifyConsensusFault()) + ic.topLevel.chargeGas(ic.topLevel.gasPrices.OnVerifyConsensusFault()) return ic.Syscalls().VerifyConsensusFault(h1, h2, extra) } @@ -386,10 +383,10 @@ func (ic *invocationContext) Send(toAddr address.Address, methodNum abi.MethodNu params: params, } - newCtx := newInvocationContext(ic.rt, ic.topLevel, newMsg, fromActor, ic.emptyObject, ic.gasUsed, ic.gasPrices) + newCtx := newInvocationContext(ic.rt, ic.topLevel, newMsg, fromActor, ic.emptyObject) ret, code := newCtx.invoke() - ic.gasUsed = newCtx.gasUsed + ic.topLevel.gasUsed = newCtx.topLevel.gasUsed ic.stats.MergeSubStat(newCtx.toActor.Code, newMsg.method, newCtx.stats) err = ret.Into(out) @@ -401,7 +398,7 @@ func (ic *invocationContext) Send(toAddr address.Address, methodNum abi.MethodNu // CreateActor implements runtime.ExtendedInvocationContext. func (ic *invocationContext) CreateActor(codeID cid.Cid, addr address.Address) { - ic.chargeGas(ic.gasPrices.OnCreateActor()) + ic.topLevel.chargeGas(ic.topLevel.gasPrices.OnCreateActor()) act, ok := ic.rt.ActorImpls[codeID] if !ok { ic.Abortf(exitcode.SysErrorIllegalArgument, "Can only create built-in actors.") @@ -436,7 +433,7 @@ func (ic *invocationContext) CreateActor(codeID cid.Cid, addr address.Address) { // deleteActor implements runtime.ExtendedInvocationContext. func (ic *invocationContext) DeleteActor(beneficiary address.Address) { - ic.chargeGas(ic.gasPrices.OnDeleteActor()) + ic.topLevel.chargeGas(ic.topLevel.gasPrices.OnDeleteActor()) receiver := ic.msg.to receiverActor, found, err := ic.rt.GetActor(receiver) if err != nil { @@ -467,7 +464,7 @@ func (ic *invocationContext) Context() context.Context { } func (ic *invocationContext) ChargeGas(name string, compute int64, virtual int64) { - ic.chargeGas(runtime.GasCharge{ + ic.topLevel.chargeGas(GasCharge{ Name: name, ComputeGas: compute, VirtualCompute: virtual, @@ -645,7 +642,7 @@ func (ic *invocationContext) invoke() (ret returnWrapper, errcode exitcode.ExitC ic.toActor, ic.msg.to = ic.resolveTarget(ic.msg.to) // 3. charge gas for method invocation - ic.chargeGas(ic.gasPrices.OnMethodInvocation(ic.msg.value, ic.msg.method)) + ic.topLevel.chargeGas(ic.topLevel.gasPrices.OnMethodInvocation(ic.msg.value, ic.msg.method)) // 4. transfer funds carried by the msg if !ic.msg.value.NilOrZero() { @@ -816,10 +813,10 @@ func (ic *invocationContext) resolveTarget(target address.Address) (*states.Acto params: &target, } - newCtx := newInvocationContext(ic.rt, ic.topLevel, newMsg, nil, ic.emptyObject, ic.gasUsed, ic.gasPrices) + newCtx := newInvocationContext(ic.rt, ic.topLevel, newMsg, nil, ic.emptyObject) _, code := newCtx.invoke() - ic.gasUsed = newCtx.gasUsed + ic.topLevel.gasUsed = newCtx.topLevel.gasUsed ic.stats.MergeSubStat(builtin.InitActorCodeID, builtin.MethodsAccount.Constructor, newCtx.stats) if code.IsError() { diff --git a/support/vm/price_list.go b/support/vm/price_list.go index cf8faf2dd..98e4bb424 100644 --- a/support/vm/price_list.go +++ b/support/vm/price_list.go @@ -1,4 +1,4 @@ -package vm_test +package vm import ( "fmt" @@ -7,12 +7,65 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/specs-actors/v5/actors/builtin" - "github.com/filecoin-project/specs-actors/v5/actors/runtime" "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" ) -func newGasCharge(name string, computeGas int64, storageGas int64) runtime.GasCharge { - return runtime.GasCharge{ +type Pricelist interface { + // OnChainMessage returns the gas used for storing a message of a given size in the chain. + OnChainMessage(msgSize int) GasCharge + // OnChainReturnValue returns the gas used for storing the response of a message in the chain. + OnChainReturnValue(dataSize int) GasCharge + + // OnMethodInvocation returns the gas used when invoking a method. + OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) GasCharge + + // OnIpldGet returns the gas used for storing an object + OnIpldGet() GasCharge + // OnIpldPut returns the gas used for storing an object + OnIpldPut(dataSize int) GasCharge + + // OnCreateActor returns the gas used for creating an actor + OnCreateActor() GasCharge + // OnDeleteActor returns the gas used for deleting an actor + OnDeleteActor() GasCharge + + OnVerifySignature(sigType crypto.SigType, planTextSize int) (GasCharge, error) + OnHashing(dataSize int) GasCharge + OnComputeUnsealedSectorCid(proofType abi.RegisteredSealProof, pieces []abi.PieceInfo) GasCharge + OnVerifySeal(info proof.SealVerifyInfo) GasCharge + OnVerifyPost(info proof.WindowPoStVerifyInfo) GasCharge + OnVerifyConsensusFault() GasCharge +} + +type GasCharge struct { + Name string + Extra interface{} + + ComputeGas int64 + StorageGas int64 + + VirtualCompute int64 + VirtualStorage int64 +} + +func (g GasCharge) Total() int64 { + return g.ComputeGas + g.StorageGas +} +func (g GasCharge) WithVirtual(compute, storage int64) GasCharge { + out := g + out.VirtualCompute = compute + out.VirtualStorage = storage + return out +} + +func (g GasCharge) WithExtra(extra interface{}) GasCharge { + out := g + out.Extra = extra + return out +} + +func newGasCharge(name string, computeGas int64, storageGas int64) GasCharge { + return GasCharge{ Name: name, ComputeGas: computeGas, StorageGas: storageGas, @@ -103,21 +156,21 @@ type pricelist struct { verifyConsensusFault int64 } -var _ runtime.Pricelist = (*pricelist)(nil) +var _ Pricelist = (*pricelist)(nil) // OnChainMessage returns the gas used for storing a message of a given size in the chain. -func (pl *pricelist) OnChainMessage(msgSize int) runtime.GasCharge { +func (pl *pricelist) OnChainMessage(msgSize int) GasCharge { return newGasCharge("OnChainMessage", pl.onChainMessageComputeBase, (pl.onChainMessageStorageBase+pl.onChainMessageStoragePerByte*int64(msgSize))*pl.storageGasMulti) } // OnChainReturnValue returns the gas used for storing the response of a message in the chain. -func (pl *pricelist) OnChainReturnValue(dataSize int) runtime.GasCharge { +func (pl *pricelist) OnChainReturnValue(dataSize int) GasCharge { return newGasCharge("OnChainReturnValue", 0, int64(dataSize)*pl.onChainReturnValuePerByte*pl.storageGasMulti) } // OnMethodInvocation returns the gas used when invoking a method. -func (pl *pricelist) OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) runtime.GasCharge { +func (pl *pricelist) OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) GasCharge { ret := pl.sendBase extra := "" @@ -139,32 +192,32 @@ func (pl *pricelist) OnMethodInvocation(value abi.TokenAmount, methodNum abi.Met } // OnIpldGet returns the gas used for storing an object -func (pl *pricelist) OnIpldGet() runtime.GasCharge { +func (pl *pricelist) OnIpldGet() GasCharge { return newGasCharge("OnIpldGet", pl.ipldGetBase, 0).WithVirtual(114617, 0) } // OnIpldPut returns the gas used for storing an object -func (pl *pricelist) OnIpldPut(dataSize int) runtime.GasCharge { +func (pl *pricelist) OnIpldPut(dataSize int) GasCharge { return newGasCharge("OnIpldPut", pl.ipldPutBase, int64(dataSize)*pl.ipldPutPerByte*pl.storageGasMulti). WithExtra(dataSize).WithVirtual(400000, int64(dataSize)*1300) } // OnCreateActor returns the gas used for creating an actor -func (pl *pricelist) OnCreateActor() runtime.GasCharge { +func (pl *pricelist) OnCreateActor() GasCharge { return newGasCharge("OnCreateActor", pl.createActorCompute, pl.createActorStorage*pl.storageGasMulti) } // OnDeleteActor returns the gas used for deleting an actor -func (pl *pricelist) OnDeleteActor() runtime.GasCharge { +func (pl *pricelist) OnDeleteActor() GasCharge { return newGasCharge("OnDeleteActor", 0, pl.deleteActor*pl.storageGasMulti) } // OnVerifySignature -func (pl *pricelist) OnVerifySignature(sigType crypto.SigType, planTextSize int) (runtime.GasCharge, error) { +func (pl *pricelist) OnVerifySignature(sigType crypto.SigType, planTextSize int) (GasCharge, error) { cost, ok := pl.verifySignature[sigType] if !ok { - return runtime.GasCharge{}, fmt.Errorf("cost function for signature type %d not supported", sigType) + return GasCharge{}, fmt.Errorf("cost function for signature type %d not supported", sigType) } sigName, _ := sigType.Name() @@ -176,24 +229,24 @@ func (pl *pricelist) OnVerifySignature(sigType crypto.SigType, planTextSize int) } // OnHashing -func (pl *pricelist) OnHashing(dataSize int) runtime.GasCharge { +func (pl *pricelist) OnHashing(dataSize int) GasCharge { return newGasCharge("OnHashing", pl.hashingBase, 0).WithExtra(dataSize) } // OnComputeUnsealedSectorCid -func (pl *pricelist) OnComputeUnsealedSectorCid(proofType abi.RegisteredSealProof, pieces []abi.PieceInfo) runtime.GasCharge { +func (pl *pricelist) OnComputeUnsealedSectorCid(proofType abi.RegisteredSealProof, pieces []abi.PieceInfo) GasCharge { return newGasCharge("OnComputeUnsealedSectorCid", pl.computeUnsealedSectorCidBase, 0) } // OnVerifySeal -func (pl *pricelist) OnVerifySeal(info proof.SealVerifyInfo) runtime.GasCharge { +func (pl *pricelist) OnVerifySeal(info proof.SealVerifyInfo) GasCharge { // TODO: this needs more cost tunning, check with @lotus // this is not used return newGasCharge("OnVerifySeal", pl.verifySealBase, 0) } // OnVerifyPost -func (pl *pricelist) OnVerifyPost(info proof.WindowPoStVerifyInfo) runtime.GasCharge { +func (pl *pricelist) OnVerifyPost(info proof.WindowPoStVerifyInfo) GasCharge { sectorSize := "unknown" var proofType abi.RegisteredPoStProof @@ -224,7 +277,7 @@ func (pl *pricelist) OnVerifyPost(info proof.WindowPoStVerifyInfo) runtime.GasCh } // OnVerifyConsensusFault -func (pl *pricelist) OnVerifyConsensusFault() runtime.GasCharge { +func (pl *pricelist) OnVerifyConsensusFault() GasCharge { return newGasCharge("OnVerifyConsensusFault", pl.verifyConsensusFault, 0) } diff --git a/support/vm/testing.go b/support/vm/testing.go index 29b42f30f..63a34be3c 100644 --- a/support/vm/testing.go +++ b/support/vm/testing.go @@ -304,8 +304,8 @@ func AdvanceByDeadline(t *testing.T, v *VM, minerIDAddr address.Address, predica v, err = v.WithEpoch(dlInfo.Last()) require.NoError(t, err) - _, code, _ := v.ApplyMessage(builtin.SystemActorAddr, builtin.CronActorAddr, big.Zero(), builtin.MethodsCron.EpochTick, nil) - require.Equal(t, exitcode.Ok, code) + result := v.ApplyMessage(builtin.SystemActorAddr, builtin.CronActorAddr, big.Zero(), builtin.MethodsCron.EpochTick, nil) + require.Equal(t, exitcode.Ok, result.Code) dlInfo = NextMinerDLInfo(t, v, minerIDAddr) } @@ -480,9 +480,9 @@ func GetDealState(t *testing.T, vm *VM, dealID abi.DealID) (*market.DealState, b // func ApplyOk(t *testing.T, v *VM, from, to address.Address, value abi.TokenAmount, method abi.MethodNum, params interface{}) cbor.Marshaler { - ret, code, _ := v.ApplyMessage(from, to, value, method, params) - require.Equal(t, exitcode.Ok, code) - return ret + result := v.ApplyMessage(from, to, value, method, params) + require.Equal(t, exitcode.Ok, result.Code) + return result.Ret } // diff --git a/support/vm/vm.go b/support/vm/vm.go index 088fe8b14..456e202fd 100644 --- a/support/vm/vm.go +++ b/support/vm/vm.go @@ -50,7 +50,7 @@ type VM struct { circSupply abi.TokenAmount - gasPrices runtime.Pricelist + gasPrices Pricelist } // VM types @@ -102,7 +102,7 @@ func makeChainMessage(from, to address.Address, nonce uint64, value abi.TokenAmo To: to, Nonce: nonce, Value: value, - GasLimit: 5_000_000_000, + GasLimit: defaultGasLimit, GasFeeCap: big.Zero(), GasPremium: big.Zero(), Method: method, @@ -334,8 +334,14 @@ func (vm *VM) NormalizeAddress(addr address.Address) (address.Address, bool) { return idAddr, found } +type MessageResult struct { + Ret cbor.Marshaler + Code exitcode.ExitCode + GasCharged int64 +} + // ApplyMessage applies the message to the current state. -func (vm *VM) ApplyMessage(from, to address.Address, value abi.TokenAmount, method abi.MethodNum, params interface{}) (cbor.Marshaler, exitcode.ExitCode, int64) { +func (vm *VM) ApplyMessage(from, to address.Address, value abi.TokenAmount, method abi.MethodNum, params interface{}) MessageResult { // This method does not actually execute the message itself, // but rather deals with the pre/post processing of a message. // (see: `invocationContext.invoke()` for the dispatch and execution) @@ -344,7 +350,7 @@ func (vm *VM) ApplyMessage(from, to address.Address, value abi.TokenAmount, meth // load actor from global state fromID, ok := vm.NormalizeAddress(from) if !ok { - return nil, exitcode.SysErrSenderInvalid, gasCharged + return MessageResult{nil, exitcode.SysErrSenderInvalid, gasCharged} } fromActor, found, err := vm.GetActor(fromID) @@ -353,7 +359,7 @@ func (vm *VM) ApplyMessage(from, to address.Address, value abi.TokenAmount, meth } if !found { // Execution error; sender does not exist at time of message execution. - return nil, exitcode.SysErrSenderInvalid, gasCharged + return MessageResult{nil, exitcode.SysErrSenderInvalid, gasCharged} } // checkpoint state @@ -396,6 +402,9 @@ func (vm *VM) ApplyMessage(from, to address.Address, value abi.TokenAmount, meth newActorAddressCount: 0, statsSource: vm.statsSource, circSupply: vm.circSupply, + gasUsed: msgGasCharge, + gasPrices: vm.gasPrices, + gasAvailable: defaultGasLimit, } // build internal msg @@ -408,7 +417,7 @@ func (vm *VM) ApplyMessage(from, to address.Address, value abi.TokenAmount, meth } // build invocation context - ctx := newInvocationContext(vm, &topLevel, imsg, fromActor, vm.emptyObject, msgGasCharge, vm.gasPrices) + ctx := newInvocationContext(vm, &topLevel, imsg, fromActor, vm.emptyObject) // 3. invoke ret, exitCode := ctx.invoke() @@ -437,9 +446,9 @@ func (vm *VM) ApplyMessage(from, to address.Address, value abi.TokenAmount, meth panic(err) } retGasCharge := vm.gasPrices.OnChainReturnValue(len(retBuf.Bytes())) - gasCharged = retGasCharge.Total() + ctx.gasUsed + gasCharged = retGasCharge.Total() + ctx.topLevel.gasUsed - return ret.inner, exitCode, gasCharged + return MessageResult{ret.inner, exitCode, gasCharged} } func (vm *VM) StateRoot() cid.Cid {