diff --git a/chain/vm/gas.go b/chain/vm/gas.go new file mode 100644 index 000000000..109d51f12 --- /dev/null +++ b/chain/vm/gas.go @@ -0,0 +1,141 @@ +package vm + +import ( + "fmt" + + addr "github.com/filecoin-project/go-address" + "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/filecoin-project/specs-actors/actors/crypto" + "github.com/filecoin-project/specs-actors/actors/runtime" + vmr "github.com/filecoin-project/specs-actors/actors/runtime" + "github.com/ipfs/go-cid" +) + +// Pricelist provides prices for operations in the VM. +// +// Note: this interface should be APPEND ONLY since last chain checkpoint +type Pricelist interface { + // OnChainMessage returns the gas used for storing a message of a given size in the chain. + OnChainMessage(msgSize int) int64 + // OnChainReturnValue returns the gas used for storing the response of a message in the chain. + OnChainReturnValue(dataSize int) int64 + + // OnMethodInvocation returns the gas used when invoking a method. + OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) int64 + + // OnIpldGet returns the gas used for storing an object + OnIpldGet(dataSize int) int64 + // OnIpldPut returns the gas used for storing an object + OnIpldPut(dataSize int) int64 + + // OnCreateActor returns the gas used for creating an actor + OnCreateActor() int64 + // OnDeleteActor returns the gas used for deleting an actor + OnDeleteActor() int64 + + OnVerifySignature(sigType crypto.SigType, planTextSize int) int64 + OnHashing(dataSize int) int64 + OnComputeUnsealedSectorCid(proofType abi.RegisteredProof, pieces []abi.PieceInfo) int64 + OnVerifySeal(info abi.SealVerifyInfo) int64 + OnVerifyPost(info abi.PoStVerifyInfo) int64 + OnVerifyConsensusFault() int64 +} + +var prices = map[abi.ChainEpoch]Pricelist{ + abi.ChainEpoch(0): &pricelistV0{ + onChainMessageBase: 0, + onChainMessagePerByte: 2, + onChainReturnValuePerByte: 8, + sendBase: 5, + sendTransferFunds: 5, + sendInvokeMethod: 10, + ipldGetBase: 10, + ipldGetPerByte: 1, + ipldPutBase: 20, + ipldPutPerByte: 2, + createActorBase: 40, // IPLD put + 20 + createActorExtra: 500, + deleteActor: -500, // -createActorExtra + // Dragons: this cost is not persistable, create a LinearCost{a,b} struct that has a `.Cost(x) -> ax + b` + verifySignature: map[crypto.SigType]func(int64) int64{ + crypto.SigTypeBLS: func(x int64) int64 { return 3*x + 2 }, + crypto.SigTypeSecp256k1: func(x int64) int64 { return 3*x + 2 }, + }, + hashingBase: 5, + hashingPerByte: 2, + computeUnsealedSectorCidBase: 100, + verifySealBase: 2000, + verifyPostBase: 700, + verifyConsensusFault: 10, + }, +} + +// PricelistByEpoch finds the latest prices for the given epoch +func PricelistByEpoch(epoch abi.ChainEpoch) Pricelist { + // since we are storing the prices as map or epoch to price + // we need to get the price with the highest epoch that is lower or equal to the `epoch` arg + bestEpoch := abi.ChainEpoch(0) + bestPrice := prices[bestEpoch] + for e, pl := range prices { + // if `e` happened after `bestEpoch` and `e` is earlier or equal to the target `epoch` + if e > bestEpoch && e <= epoch { + bestEpoch = e + bestPrice = pl + } + } + if bestPrice == nil { + panic(fmt.Sprintf("bad setup: no gas prices available for epoch %d", epoch)) + } + return bestPrice +} + +type pricedSyscalls struct { + under vmr.Syscalls + pl Pricelist + chargeGas func(int64) +} + +// Verifies that a signature is valid for an address and plaintext. +func (ps pricedSyscalls) VerifySignature(signature crypto.Signature, signer addr.Address, plaintext []byte) error { + ps.chargeGas(ps.pl.OnVerifySignature(signature.Type, len(plaintext))) + return ps.under.VerifySignature(signature, signer, plaintext) +} + +// Hashes input data using blake2b with 256 bit output. +func (ps pricedSyscalls) HashBlake2b(data []byte) [32]byte { + ps.chargeGas(ps.pl.OnHashing(len(data))) + return ps.under.HashBlake2b(data) +} + +// Computes an unsealed sector CID (CommD) from its constituent piece CIDs (CommPs) and sizes. +func (ps pricedSyscalls) ComputeUnsealedSectorCID(reg abi.RegisteredProof, pieces []abi.PieceInfo) (cid.Cid, error) { + ps.chargeGas(ps.pl.OnComputeUnsealedSectorCid(reg, pieces)) + return ps.under.ComputeUnsealedSectorCID(reg, pieces) +} + +// Verifies a sector seal proof. +func (ps pricedSyscalls) VerifySeal(vi abi.SealVerifyInfo) error { + ps.chargeGas(ps.pl.OnVerifySeal(vi)) + return ps.under.VerifySeal(vi) +} + +// Verifies a proof of spacetime. +func (ps pricedSyscalls) VerifyPoSt(vi abi.PoStVerifyInfo) error { + ps.chargeGas(ps.pl.OnVerifyPost(vi)) + return ps.under.VerifyPoSt(vi) +} + +// Verifies that two block headers provide proof of a consensus fault: +// - both headers mined by the same actor +// - headers are different +// - first header is of the same or lower epoch as the second +// - at least one of the headers appears in the current chain at or after epoch `earliest` +// - the headers provide evidence of a fault (see the spec for the different fault types). +// The parameters are all serialized block headers. The third "extra" parameter is consulted only for +// the "parent grinding fault", in which case it must be the sibling of h1 (same parent tipset) and one of the +// blocks in the parent of h2 (i.e. h2's grandparent). +// Returns nil and an error if the headers don't prove a fault. +func (ps pricedSyscalls) VerifyConsensusFault(h1 []byte, h2 []byte, extra []byte, earliest abi.ChainEpoch) (*runtime.ConsensusFault, error) { + ps.chargeGas(ps.pl.OnVerifyConsensusFault()) + return ps.under.VerifyConsensusFault(h1, h2, extra, earliest) +} diff --git a/chain/vm/gas_v0.go b/chain/vm/gas_v0.go new file mode 100644 index 000000000..d0d6ab43d --- /dev/null +++ b/chain/vm/gas_v0.go @@ -0,0 +1,165 @@ +package vm + +import ( + "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/filecoin-project/specs-actors/actors/builtin" + "github.com/filecoin-project/specs-actors/actors/crypto" + "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" +) + +type pricelistV0 struct { + /////////////////////////////////////////////////////////////////////////// + // 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. + onChainMessageBase int64 + onChainMessagePerByte 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 + + // 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 (Base + len*PerByte) for any Get operation to the IPLD store + // in the runtime VM context. + ipldGetBase int64 + ipldGetPerByte 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. + createActorBase int64 + createActorExtra 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]func(len int64) int64 + + hashingBase int64 + hashingPerByte int64 + + computeUnsealedSectorCidBase int64 + verifySealBase int64 + verifyPostBase int64 + verifyConsensusFault int64 +} + +var _ Pricelist = (*pricelistV0)(nil) + +// OnChainMessage returns the gas used for storing a message of a given size in the chain. +func (pl *pricelistV0) OnChainMessage(msgSize int) int64 { + return pl.onChainMessageBase + pl.onChainMessagePerByte*int64(msgSize) +} + +// OnChainReturnValue returns the gas used for storing the response of a message in the chain. +func (pl *pricelistV0) OnChainReturnValue(dataSize int) int64 { + return int64(dataSize) * pl.onChainReturnValuePerByte +} + +// OnMethodInvocation returns the gas used when invoking a method. +func (pl *pricelistV0) OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) int64 { + ret := pl.sendBase + if value != abi.NewTokenAmount(0) { + ret += pl.sendTransferFunds + } + if methodNum != builtin.MethodSend { + ret += pl.sendInvokeMethod + } + return ret +} + +// OnIpldGet returns the gas used for storing an object +func (pl *pricelistV0) OnIpldGet(dataSize int) int64 { + return pl.ipldGetBase + int64(dataSize)*pl.ipldGetPerByte +} + +// OnIpldPut returns the gas used for storing an object +func (pl *pricelistV0) OnIpldPut(dataSize int) int64 { + return pl.ipldPutBase + int64(dataSize)*pl.ipldPutPerByte +} + +// OnCreateActor returns the gas used for creating an actor +func (pl *pricelistV0) OnCreateActor() int64 { + return pl.createActorBase + pl.createActorExtra +} + +// OnDeleteActor returns the gas used for deleting an actor +func (pl *pricelistV0) OnDeleteActor() int64 { + return pl.deleteActor +} + +// OnVerifySignature +func (pl *pricelistV0) OnVerifySignature(sigType crypto.SigType, planTextSize int) int64 { + costFn, ok := pl.verifySignature[sigType] + if !ok { + // TODO: fix retcode to be int64 + panic(aerrors.Newf(uint8(exitcode.SysErrInternal&0xff), "Cost function for signature type %d not supported", sigType)) + } + return costFn(int64(planTextSize)) +} + +// OnHashing +func (pl *pricelistV0) OnHashing(dataSize int) int64 { + return pl.hashingBase + int64(dataSize)*pl.hashingPerByte +} + +// OnComputeUnsealedSectorCid +func (pl *pricelistV0) OnComputeUnsealedSectorCid(proofType abi.RegisteredProof, pieces []abi.PieceInfo) int64 { + // TODO: this needs more cost tunning, check with @lotus + return pl.computeUnsealedSectorCidBase +} + +// OnVerifySeal +func (pl *pricelistV0) OnVerifySeal(info abi.SealVerifyInfo) int64 { + // TODO: this needs more cost tunning, check with @lotus + return pl.verifySealBase +} + +// OnVerifyPost +func (pl *pricelistV0) OnVerifyPost(info abi.PoStVerifyInfo) int64 { + // TODO: this needs more cost tunning, check with @lotus + return pl.verifyPostBase +} + +// OnVerifyConsensusFault +func (pl *pricelistV0) OnVerifyConsensusFault() int64 { + return pl.verifyConsensusFault +} diff --git a/chain/vm/runtime.go b/chain/vm/runtime.go index 4740c44d5..e6b450c8d 100644 --- a/chain/vm/runtime.go +++ b/chain/vm/runtime.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/binary" - "runtime/debug" "github.com/filecoin-project/go-address" "github.com/filecoin-project/specs-actors/actors/abi" @@ -28,11 +27,12 @@ import ( type Runtime struct { ctx context.Context - vm *VM - state *state.StateTree - msg *types.Message - height abi.ChainEpoch - cst cbor.IpldStore + vm *VM + state *state.StateTree + msg *types.Message + height abi.ChainEpoch + cst cbor.IpldStore + pricelist Pricelist gasAvailable int64 gasUsed int64 @@ -78,13 +78,11 @@ func (rs *Runtime) shimCall(f func() interface{}) (rval []byte, aerr aerrors.Act defer func() { if r := recover(); r != nil { if ar, ok := r.(aerrors.ActorError); ok { - log.Warn("VM.Call failure: ", ar) - debug.PrintStack() + log.Errorf("VM.Call failure: %+v", ar) aerr = ar return } - debug.PrintStack() - log.Errorf("ERROR") + log.Errorf("spec actors failure: %s", r) aerr = aerrors.Newf(1, "spec actors failure: %s", r) } }() @@ -180,6 +178,7 @@ func (rt *Runtime) NewActorAddress() address.Address { } func (rt *Runtime) CreateActor(codeId cid.Cid, address address.Address) { + rt.ChargeGas(rt.Pricelist().OnCreateActor()) var err error err = rt.state.SetActor(address, &types.Actor{ @@ -194,6 +193,7 @@ func (rt *Runtime) CreateActor(codeId cid.Cid, address address.Address) { } func (rt *Runtime) DeleteActor() { + rt.ChargeGas(rt.Pricelist().OnDeleteActor()) act, err := rt.state.GetActor(rt.Message().Receiver()) if err != nil { rt.Abortf(exitcode.SysErrInternal, "failed to load actor in delete actor: %s", err) @@ -314,6 +314,7 @@ func (rt *Runtime) internalSend(to address.Address, method abi.MethodNum, value return nil, aerrors.Fatalf("snapshot failed: %s", err) } defer st.ClearSnapshot() + rt.ChargeGas(rt.Pricelist().OnMethodInvocation(value, method)) ret, errSend, subrt := rt.vm.send(ctx, msg, rt, 0) if errSend != nil { @@ -400,8 +401,6 @@ func (rt *Runtime) GetBalance(a address.Address) (types.BigInt, aerrors.ActorErr } func (rt *Runtime) stateCommit(oldh, newh cid.Cid) aerrors.ActorError { - rt.ChargeGas(gasCommit) - // TODO: we can make this more efficient in the future... act, err := rt.state.GetActor(rt.Message().Receiver()) if err != nil { @@ -422,8 +421,20 @@ func (rt *Runtime) stateCommit(oldh, newh cid.Cid) aerrors.ActorError { } func (rt *Runtime) ChargeGas(toUse int64) { - rt.gasUsed = rt.gasUsed + toUse - if rt.gasUsed > rt.gasAvailable { - rt.Abortf(exitcode.SysErrOutOfGas, "not enough gas: used=%d, available=%d", rt.gasUsed, rt.gasAvailable) + err := rt.chargeGasSafe(toUse) + if err != nil { + panic(err) } } + +func (rt *Runtime) chargeGasSafe(toUse int64) aerrors.ActorError { + rt.gasUsed += toUse + if rt.gasUsed > rt.gasAvailable { + return aerrors.Newf(uint8(exitcode.SysErrOutOfGas), "not enough gas: used=%d, available=%d", rt.gasUsed, rt.gasAvailable) + } + return nil +} + +func (rt *Runtime) Pricelist() Pricelist { + return rt.pricelist +} diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 29d0afc00..cb337aaa8 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -92,22 +92,22 @@ var _ cbor.IpldBlockstore = (*gasChargingBlocks)(nil) type gasChargingBlocks struct { chargeGas func(int64) + pricelist Pricelist under cbor.IpldBlockstore } func (bs *gasChargingBlocks) Get(c cid.Cid) (block.Block, error) { - bs.chargeGas(gasGetObj) blk, err := bs.under.Get(c) if err != nil { return nil, aerrors.Escalate(err, "failed to get block from blockstore") } - bs.chargeGas(int64(len(blk.RawData())) * gasGetPerByte) + bs.chargeGas(bs.pricelist.OnIpldGet(len(blk.RawData()))) return blk, nil } func (bs *gasChargingBlocks) Put(blk block.Block) error { - bs.chargeGas(gasPutObj + int64(len(blk.RawData()))*gasPutPerByte) + bs.chargeGas(bs.pricelist.OnIpldPut(len(blk.RawData()))) if err := bs.under.Put(blk); err != nil { return aerrors.Escalate(err, "failed to write data to disk") @@ -124,16 +124,21 @@ func (vm *VM) makeRuntime(ctx context.Context, msg *types.Message, origin addres origin: origin, originNonce: originNonce, height: vm.blockHeight, - sys: vm.Syscalls, gasUsed: usedGas, gasAvailable: msg.GasLimit, internalCallCounter: icc, + pricelist: PricelistByEpoch(vm.blockHeight), } rt.cst = &cbor.BasicIpldStore{ - Blocks: &gasChargingBlocks{rt.ChargeGas, vm.cst.Blocks}, + Blocks: &gasChargingBlocks{rt.ChargeGas, rt.pricelist, vm.cst.Blocks}, Atlas: vm.cst.Atlas, } + rt.sys = pricedSyscalls{ + under: vm.Syscalls, + chargeGas: rt.ChargeGas, + pl: rt.pricelist, + } return rt } @@ -193,6 +198,8 @@ func (vm *VM) send(ctx context.Context, msg *types.Message, parent *Runtime, return nil, aerrors.Absorb(err, 1, "could not find source actor"), nil } + gasUsed := gasCharge + toActor, err := st.GetActor(msg.To) if err != nil { if xerrors.Is(err, init_.ErrAddressNotFound) { @@ -201,12 +208,12 @@ func (vm *VM) send(ctx context.Context, msg *types.Message, parent *Runtime, return nil, aerrors.Absorb(err, 1, "could not create account"), nil } toActor = a + gasUsed += PricelistByEpoch(vm.blockHeight).OnCreateActor() } else { return nil, aerrors.Escalate(err, "getting actor"), nil } } - gasUsed := gasCharge origin := msg.From on := msg.Nonce var icc int64 = 0 @@ -223,9 +230,12 @@ func (vm *VM) send(ctx context.Context, msg *types.Message, parent *Runtime, }() } - if types.BigCmp(msg.Value, types.NewInt(0)) != 0 { - rt.ChargeGas(gasFundTransfer) + aerr := rt.chargeGasSafe(rt.Pricelist().OnMethodInvocation(msg.Value, msg.Method)) + if aerr != nil { + return nil, aerr, rt + } + if types.BigCmp(msg.Value, types.NewInt(0)) != 0 { if err := Transfer(fromActor, toActor, msg.Value); err != nil { return nil, aerrors.Absorb(err, 1, "failed to transfer funds"), nil } @@ -273,11 +283,13 @@ func (vm *VM) ApplyMessage(ctx context.Context, msg *types.Message) (*ApplyRet, return nil, err } + pl := PricelistByEpoch(vm.blockHeight) serMsg, err := msg.Serialize() if err != nil { return nil, xerrors.Errorf("could not serialize message: %w", err) } - msgGasCost := int64(len(serMsg)) * gasPerMessageByte + msgGasCost := pl.OnChainMessage(len(serMsg)) + // TODO: charge miner?? if msgGasCost > msg.GasLimit { return &ApplyRet{ MessageReceipt: types.MessageReceipt{ @@ -335,6 +347,14 @@ func (vm *VM) ApplyMessage(ctx context.Context, msg *types.Message) (*ApplyRet, ret, actorErr, rt := vm.send(ctx, msg, nil, msgGasCost) + { + actorErr2 := rt.chargeGasSafe(rt.Pricelist().OnChainReturnValue(len(ret))) + if actorErr == nil { + //TODO: Ambigous what to do in this case + actorErr = actorErr2 + } + } + if aerrors.IsFatal(actorErr) { return nil, xerrors.Errorf("[from=%s,to=%s,n=%d,m=%d,h=%d] fatal error: %w", msg.From, msg.To, msg.Nonce, msg.Method, vm.blockHeight, actorErr) } @@ -353,6 +373,9 @@ func (vm *VM) ApplyMessage(ctx context.Context, msg *types.Message) (*ApplyRet, } } else { gasUsed = rt.gasUsed + if gasUsed < 0 { + gasUsed = 0 + } // refund unused gas refund := types.BigMul(types.NewInt(uint64(msg.GasLimit-gasUsed)), msg.GasPrice) if err := Transfer(gasHolder, fromActor, refund); err != nil { @@ -547,7 +570,6 @@ func (vm *VM) Invoke(act *types.Actor, rt *Runtime, method abi.MethodNum, params defer func() { rt.ctx = oldCtx }() - rt.ChargeGas(gasInvoke) ret, err := vm.inv.Invoke(act, rt, method, params) if err != nil { return nil, err diff --git a/go.mod b/go.mod index 3a9ae8276..f21f25313 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/coreos/go-systemd/v22 v22.0.0 github.com/docker/go-units v0.4.0 - github.com/filecoin-project/chain-validation v0.0.6-0.20200318065243-0ccb5ec3afc5 + github.com/filecoin-project/chain-validation v0.0.6-0.20200320065847-72b36edba0fd github.com/filecoin-project/filecoin-ffi v0.0.0-20200304181354-4446ff8a1bb9 github.com/filecoin-project/go-address v0.0.2-0.20200218010043-eb9bb40ed5be github.com/filecoin-project/go-amt-ipld/v2 v2.0.1-0.20200131012142-05d80eeccc5e diff --git a/go.sum b/go.sum index c17af0f03..70165b1b1 100644 --- a/go.sum +++ b/go.sum @@ -98,8 +98,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/fatih/color v1.8.0 h1:5bzFgL+oy7JITMTxUPJ00n7VxmYd/PdMp5mHFX40/RY= github.com/fatih/color v1.8.0/go.mod h1:3l45GVGkyrnYNl9HoIjnp2NnNWvh6hLAqD8yTfGjnw8= -github.com/filecoin-project/chain-validation v0.0.6-0.20200318065243-0ccb5ec3afc5 h1:cr9+8iX+u9fDV53MWqqZw820EyeWVX+h/HCz56JUWb0= -github.com/filecoin-project/chain-validation v0.0.6-0.20200318065243-0ccb5ec3afc5/go.mod h1:7HoEkq8OWN3vGcCZ4SRGxAPeL/mLckS+PNV3F0XmrCs= +github.com/filecoin-project/chain-validation v0.0.6-0.20200320065847-72b36edba0fd h1:4sf6dbvA/ZmCcU834shF9hEuHeC+CXoEnT+EIiZ95/0= +github.com/filecoin-project/chain-validation v0.0.6-0.20200320065847-72b36edba0fd/go.mod h1:YTLxUr6gOZpkUaXzLe7OZ4s1dpfJGp2FY/J2/K5DJqc= github.com/filecoin-project/go-address v0.0.0-20200107215422-da8eea2842b5 h1:/MmWluswvDIbuPvBct4q6HeQgVm62O2DzWYTB38kt4A= github.com/filecoin-project/go-address v0.0.0-20200107215422-da8eea2842b5/go.mod h1:SAOwJoakQ8EPjwNIsiakIQKsoKdkcbx8U3IapgCg9R0= github.com/filecoin-project/go-address v0.0.2-0.20200218010043-eb9bb40ed5be h1:TooKBwR/g8jG0hZ3lqe9S5sy2vTUcLOZLlz3M5wGn2E= @@ -133,8 +133,6 @@ github.com/filecoin-project/specs-actors v0.0.0-20200210130641-2d1fbd8672cf/go.m github.com/filecoin-project/specs-actors v0.0.0-20200226200336-94c9b92b2775/go.mod h1:0HAWYrvajFHDgRaKbF0rl+IybVLZL5z4gQ8koCMPhoU= github.com/filecoin-project/specs-actors v0.0.0-20200302223606-0eaf97b10aaf/go.mod h1:0HAWYrvajFHDgRaKbF0rl+IybVLZL5z4gQ8koCMPhoU= github.com/filecoin-project/specs-actors v0.0.0-20200306000749-99e98e61e2a0/go.mod h1:0HAWYrvajFHDgRaKbF0rl+IybVLZL5z4gQ8koCMPhoU= -github.com/filecoin-project/specs-actors v0.0.0-20200311215506-e95895452888 h1:VCrkpFmZuQRyHrUpFTS3K/09cCQDMi/ZJUQ6c4zr1g4= -github.com/filecoin-project/specs-actors v0.0.0-20200311215506-e95895452888/go.mod h1:5WngRgTN5Eo4+0SjCBqLzEr2l6Mj45DrP2606gBhqI0= github.com/filecoin-project/specs-actors v0.0.0-20200312030511-3f5510bf6130 h1:atiWEDtI/gzSm89fL+NyneLN3eHfBd1QPgOZyXPjA5M= github.com/filecoin-project/specs-actors v0.0.0-20200312030511-3f5510bf6130/go.mod h1:5WngRgTN5Eo4+0SjCBqLzEr2l6Mj45DrP2606gBhqI0= github.com/filecoin-project/specs-storage v0.0.0-20200303233430-1a5a408f7513 h1:okBx3lPomwDxlPmRvyP078BwivDfdxNUlpCDhDD0ia8=