lotus/chain/vm/vm.go
Steven Allen 5733c71c50 Lint everything
We were ignoring quite a few error cases, and had one case where we weren't
actually updating state where we wanted to. Unfortunately, if the linter doesn't
pass, nobody has any reason to actually check lint failures in CI.

There are three remaining XXXs marked in the code for lint.
2020-08-20 20:46:36 -07:00

824 lines
22 KiB
Go

package vm
import (
"bytes"
"context"
"fmt"
"reflect"
"time"
bstore "github.com/filecoin-project/lotus/lib/blockstore"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/filecoin-project/specs-actors/actors/builtin"
block "github.com/ipfs/go-block-format"
cid "github.com/ipfs/go-cid"
cbor "github.com/ipfs/go-ipld-cbor"
logging "github.com/ipfs/go-log/v2"
mh "github.com/multiformats/go-multihash"
cbg "github.com/whyrusleeping/cbor-gen"
"go.opencensus.io/trace"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/builtin/account"
"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors/aerrors"
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/lib/blockstore"
"github.com/filecoin-project/lotus/lib/bufbstore"
)
var log = logging.Logger("vm")
var actorLog = logging.Logger("actors")
var gasOnActorExec = newGasCharge("OnActorExec", 0, 0)
// ResolveToKeyAddr returns the public key type of address (`BLS`/`SECP256K1`) of an account actor identified by `addr`.
func ResolveToKeyAddr(state types.StateTree, cst cbor.IpldStore, addr address.Address) (address.Address, aerrors.ActorError) {
if addr.Protocol() == address.BLS || addr.Protocol() == address.SECP256K1 {
return addr, nil
}
act, err := state.GetActor(addr)
if err != nil {
return address.Undef, aerrors.Newf(exitcode.SysErrInvalidParameters, "failed to find actor: %s", addr)
}
if act.Code != builtin.AccountActorCodeID {
return address.Undef, aerrors.Newf(exitcode.SysErrInvalidParameters, "address %s was not for an account actor", addr)
}
var aast account.State
if err := cst.Get(context.TODO(), act.Head, &aast); err != nil {
return address.Undef, aerrors.Absorb(err, exitcode.SysErrInvalidParameters, fmt.Sprintf("failed to get account actor state for %s", addr))
}
return aast.Address, nil
}
var _ cbor.IpldBlockstore = (*gasChargingBlocks)(nil)
type gasChargingBlocks struct {
chargeGas func(GasCharge)
pricelist Pricelist
under cbor.IpldBlockstore
}
func (bs *gasChargingBlocks) Get(c cid.Cid) (block.Block, error) {
bs.chargeGas(bs.pricelist.OnIpldGet())
blk, err := bs.under.Get(c)
if err != nil {
return nil, aerrors.Escalate(err, "failed to get block from blockstore")
}
bs.chargeGas(newGasCharge("OnIpldGetEnd", 0, 0).WithExtra(len(blk.RawData())))
bs.chargeGas(gasOnActorExec)
return blk, nil
}
func (bs *gasChargingBlocks) Put(blk block.Block) error {
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")
}
bs.chargeGas(gasOnActorExec)
return nil
}
func (vm *VM) makeRuntime(ctx context.Context, msg *types.Message, origin address.Address, originNonce uint64, usedGas int64, nac uint64) *Runtime {
rt := &Runtime{
ctx: ctx,
vm: vm,
state: vm.cstate,
msg: msg,
origin: origin,
originNonce: originNonce,
height: vm.blockHeight,
gasUsed: usedGas,
gasAvailable: msg.GasLimit,
numActorsCreated: nac,
pricelist: PricelistByEpoch(vm.blockHeight),
allowInternal: true,
callerValidated: false,
executionTrace: types.ExecutionTrace{Msg: msg},
}
rt.cst = &cbor.BasicIpldStore{
Blocks: &gasChargingBlocks{rt.chargeGasFunc(2), rt.pricelist, vm.cst.Blocks},
Atlas: vm.cst.Atlas,
}
rt.sys = pricedSyscalls{
under: vm.Syscalls(ctx, vm.cstate, rt.cst),
chargeGas: rt.chargeGasFunc(1),
pl: rt.pricelist,
}
vmm := *msg
resF, ok := rt.ResolveAddress(msg.From)
if !ok {
rt.Abortf(exitcode.SysErrInvalidReceiver, "resolve msg.From address failed")
}
vmm.From = resF
rt.vmsg = &vmm
return rt
}
type UnsafeVM struct {
VM *VM
}
func (vm *UnsafeVM) MakeRuntime(ctx context.Context, msg *types.Message, origin address.Address, originNonce uint64, usedGas int64, nac uint64) *Runtime {
return vm.VM.makeRuntime(ctx, msg, origin, originNonce, usedGas, nac)
}
type CircSupplyCalculator func(context.Context, abi.ChainEpoch, *state.StateTree) (abi.TokenAmount, error)
type VM struct {
cstate *state.StateTree
base cid.Cid
cst *cbor.BasicIpldStore
buf *bufbstore.BufferedBS
blockHeight abi.ChainEpoch
inv *Invoker
rand Rand
circSupplyCalc CircSupplyCalculator
baseFee abi.TokenAmount
Syscalls SyscallBuilder
}
type VMOpts struct {
StateBase cid.Cid
Epoch abi.ChainEpoch
Rand Rand
Bstore bstore.Blockstore
Syscalls SyscallBuilder
CircSupplyCalc CircSupplyCalculator
BaseFee abi.TokenAmount
}
func NewVM(opts *VMOpts) (*VM, error) {
buf := bufbstore.NewBufferedBstore(opts.Bstore)
cst := cbor.NewCborStore(buf)
state, err := state.LoadStateTree(cst, opts.StateBase)
if err != nil {
return nil, err
}
return &VM{
cstate: state,
base: opts.StateBase,
cst: cst,
buf: buf,
blockHeight: opts.Epoch,
inv: NewInvoker(),
rand: opts.Rand, // TODO: Probably should be a syscall
circSupplyCalc: opts.CircSupplyCalc,
Syscalls: opts.Syscalls,
baseFee: opts.BaseFee,
}, nil
}
type Rand interface {
GetChainRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error)
GetBeaconRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error)
}
type ApplyRet struct {
types.MessageReceipt
ActorErr aerrors.ActorError
Penalty types.BigInt
MinerTip types.BigInt
ExecutionTrace types.ExecutionTrace
Duration time.Duration
}
func (vm *VM) send(ctx context.Context, msg *types.Message, parent *Runtime,
gasCharge *GasCharge, start time.Time) ([]byte, aerrors.ActorError, *Runtime) {
st := vm.cstate
origin := msg.From
on := msg.Nonce
var nac uint64 = 0
var gasUsed int64
if parent != nil {
gasUsed = parent.gasUsed
origin = parent.origin
on = parent.originNonce
nac = parent.numActorsCreated
}
rt := vm.makeRuntime(ctx, msg, origin, on, gasUsed, nac)
rt.lastGasChargeTime = start
if parent != nil {
rt.lastGasChargeTime = parent.lastGasChargeTime
rt.lastGasCharge = parent.lastGasCharge
defer func() {
parent.gasUsed = rt.gasUsed
parent.lastGasChargeTime = rt.lastGasChargeTime
parent.lastGasCharge = rt.lastGasCharge
}()
}
if gasCharge != nil {
if err := rt.chargeGasSafe(*gasCharge); err != nil {
// this should never happen
return nil, aerrors.Wrap(err, "not enough gas for initial message charge, this should not happen"), rt
}
}
ret, err := func() ([]byte, aerrors.ActorError) {
_ = rt.chargeGasSafe(newGasCharge("OnGetActor", 0, 0))
toActor, err := st.GetActor(msg.To)
if err != nil {
if xerrors.Is(err, types.ErrActorNotFound) {
a, err := TryCreateAccountActor(rt, msg.To)
if err != nil {
return nil, aerrors.Wrapf(err, "could not create account")
}
toActor = a
} else {
return nil, aerrors.Escalate(err, "getting actor")
}
}
if aerr := rt.chargeGasSafe(rt.Pricelist().OnMethodInvocation(msg.Value, msg.Method)); aerr != nil {
return nil, aerrors.Wrap(aerr, "not enough gas for method invocation")
}
// not charging any gas, just logging
//nolint:errcheck
defer rt.chargeGasSafe(newGasCharge("OnMethodInvocationDone", 0, 0))
if types.BigCmp(msg.Value, types.NewInt(0)) != 0 {
if err := vm.transfer(msg.From, msg.To, msg.Value); err != nil {
return nil, aerrors.Wrap(err, "failed to transfer funds")
}
}
if msg.Method != 0 {
var ret []byte
_ = rt.chargeGasSafe(gasOnActorExec)
ret, err := vm.Invoke(toActor, rt, msg.Method, msg.Params)
return ret, err
}
return nil, nil
}()
mr := types.MessageReceipt{
ExitCode: aerrors.RetCode(err),
Return: ret,
GasUsed: rt.gasUsed,
}
rt.executionTrace.MsgRct = &mr
rt.executionTrace.Duration = time.Since(start)
if err != nil {
rt.executionTrace.Error = err.Error()
}
return ret, err, rt
}
func checkMessage(msg *types.Message) error {
if msg.GasLimit == 0 {
return xerrors.Errorf("message has no gas limit set")
}
if msg.GasLimit < 0 {
return xerrors.Errorf("message has negative gas limit")
}
if msg.GasFeeCap == types.EmptyInt {
return xerrors.Errorf("message fee cap not set")
}
if msg.GasPremium == types.EmptyInt {
return xerrors.Errorf("message gas premium not set")
}
if msg.Value == types.EmptyInt {
return xerrors.Errorf("message no value set")
}
return nil
}
func (vm *VM) ApplyImplicitMessage(ctx context.Context, msg *types.Message) (*ApplyRet, error) {
start := build.Clock.Now()
ret, actorErr, rt := vm.send(ctx, msg, nil, nil, start)
rt.finilizeGasTracing()
return &ApplyRet{
MessageReceipt: types.MessageReceipt{
ExitCode: aerrors.RetCode(actorErr),
Return: ret,
GasUsed: 0,
},
ActorErr: actorErr,
ExecutionTrace: rt.executionTrace,
Penalty: types.NewInt(0),
MinerTip: types.NewInt(0),
Duration: time.Since(start),
}, actorErr
}
func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, error) {
start := build.Clock.Now()
ctx, span := trace.StartSpan(ctx, "vm.ApplyMessage")
defer span.End()
msg := cmsg.VMMessage()
if span.IsRecordingEvents() {
span.AddAttributes(
trace.StringAttribute("to", msg.To.String()),
trace.Int64Attribute("method", int64(msg.Method)),
trace.StringAttribute("value", msg.Value.String()),
)
}
if err := checkMessage(msg); err != nil {
return nil, err
}
pl := PricelistByEpoch(vm.blockHeight)
msgGas := pl.OnChainMessage(cmsg.ChainLength())
msgGasCost := msgGas.Total()
// this should never happen, but is currently still exercised by some tests
if msgGasCost > msg.GasLimit {
return &ApplyRet{
MessageReceipt: types.MessageReceipt{
ExitCode: exitcode.SysErrOutOfGas,
GasUsed: 0,
},
Penalty: types.BigMul(vm.baseFee, abi.NewTokenAmount(msgGasCost)),
Duration: time.Since(start),
MinerTip: big.Zero(),
}, nil
}
st := vm.cstate
minerPenaltyAmount := types.BigMul(vm.baseFee, abi.NewTokenAmount(msg.GasLimit))
fromActor, err := st.GetActor(msg.From)
// this should never happen, but is currently still exercised by some tests
if err != nil {
if xerrors.Is(err, types.ErrActorNotFound) {
return &ApplyRet{
MessageReceipt: types.MessageReceipt{
ExitCode: exitcode.SysErrSenderInvalid,
GasUsed: 0,
},
ActorErr: aerrors.Newf(exitcode.SysErrSenderInvalid, "actor not found: %s", msg.From),
Penalty: minerPenaltyAmount,
Duration: time.Since(start),
MinerTip: big.Zero(),
}, nil
}
return nil, xerrors.Errorf("failed to look up from actor: %w", err)
}
// this should never happen, but is currently still exercised by some tests
if !fromActor.Code.Equals(builtin.AccountActorCodeID) {
return &ApplyRet{
MessageReceipt: types.MessageReceipt{
ExitCode: exitcode.SysErrSenderInvalid,
GasUsed: 0,
},
ActorErr: aerrors.Newf(exitcode.SysErrSenderInvalid, "send from not account actor: %s", fromActor.Code),
Penalty: minerPenaltyAmount,
Duration: time.Since(start),
MinerTip: big.Zero(),
}, nil
}
if msg.Nonce != fromActor.Nonce {
return &ApplyRet{
MessageReceipt: types.MessageReceipt{
ExitCode: exitcode.SysErrSenderStateInvalid,
GasUsed: 0,
},
ActorErr: aerrors.Newf(exitcode.SysErrSenderStateInvalid,
"actor nonce invalid: msg:%d != state:%d", msg.Nonce, fromActor.Nonce),
Penalty: minerPenaltyAmount,
Duration: time.Since(start),
MinerTip: big.Zero(),
}, nil
}
gascost := types.BigMul(types.NewInt(uint64(msg.GasLimit)), msg.GasFeeCap)
if fromActor.Balance.LessThan(gascost) {
return &ApplyRet{
MessageReceipt: types.MessageReceipt{
ExitCode: exitcode.SysErrSenderStateInvalid,
GasUsed: 0,
},
ActorErr: aerrors.Newf(exitcode.SysErrSenderStateInvalid,
"actor balance less than needed: %s < %s", types.FIL(fromActor.Balance), types.FIL(gascost)),
Penalty: minerPenaltyAmount,
Duration: time.Since(start),
MinerTip: big.Zero(),
}, nil
}
gasHolder := &types.Actor{Balance: types.NewInt(0)}
if err := vm.transferToGasHolder(msg.From, gasHolder, gascost); err != nil {
return nil, xerrors.Errorf("failed to withdraw gas funds: %w", err)
}
if err := vm.incrementNonce(msg.From); err != nil {
return nil, err
}
if err := st.Snapshot(ctx); err != nil {
return nil, xerrors.Errorf("snapshot failed: %w", err)
}
defer st.ClearSnapshot()
ret, actorErr, rt := vm.send(ctx, msg, nil, &msgGas, start)
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)
}
if actorErr != nil {
log.Warnw("Send actor error", "from", msg.From, "to", msg.To, "nonce", msg.Nonce, "method", msg.Method, "height", vm.blockHeight, "error", fmt.Sprintf("%+v", actorErr))
}
if actorErr != nil && len(ret) != 0 {
// This should not happen, something is wonky
return nil, xerrors.Errorf("message invocation errored, but had a return value anyway: %w", actorErr)
}
if rt == nil {
return nil, xerrors.Errorf("send returned nil runtime, send error was: %s", actorErr)
}
if len(ret) != 0 {
// safely override actorErr since it must be nil
actorErr = rt.chargeGasSafe(rt.Pricelist().OnChainReturnValue(len(ret)))
if actorErr != nil {
ret = nil
}
}
var errcode exitcode.ExitCode
var gasUsed int64
if errcode = aerrors.RetCode(actorErr); errcode != 0 {
// revert all state changes since snapshot
if err := st.Revert(); err != nil {
return nil, xerrors.Errorf("revert state failed: %w", err)
}
}
rt.finilizeGasTracing()
gasUsed = rt.gasUsed
if gasUsed < 0 {
gasUsed = 0
}
gasOutputs := ComputeGasOutputs(gasUsed, msg.GasLimit, vm.baseFee, msg.GasFeeCap, msg.GasPremium)
if err := vm.transferFromGasHolder(builtin.BurntFundsActorAddr, gasHolder,
gasOutputs.BaseFeeBurn); err != nil {
return nil, xerrors.Errorf("failed to burn base fee: %w", err)
}
if err := vm.transferFromGasHolder(builtin.RewardActorAddr, gasHolder, gasOutputs.MinerTip); err != nil {
return nil, xerrors.Errorf("failed to give miner gas reward: %w", err)
}
if err := vm.transferFromGasHolder(builtin.BurntFundsActorAddr, gasHolder,
gasOutputs.OverEstimationBurn); err != nil {
return nil, xerrors.Errorf("failed to burn overestimation fee: %w", err)
}
// refund unused gas
if err := vm.transferFromGasHolder(msg.From, gasHolder, gasOutputs.Refund); err != nil {
return nil, xerrors.Errorf("failed to refund gas: %w", err)
}
if types.BigCmp(types.NewInt(0), gasHolder.Balance) != 0 {
return nil, xerrors.Errorf("gas handling math is wrong")
}
return &ApplyRet{
MessageReceipt: types.MessageReceipt{
ExitCode: errcode,
Return: ret,
GasUsed: gasUsed,
},
ActorErr: actorErr,
ExecutionTrace: rt.executionTrace,
Penalty: gasOutputs.MinerPenalty,
MinerTip: gasOutputs.MinerTip,
Duration: time.Since(start),
}, nil
}
func (vm *VM) ActorBalance(addr address.Address) (types.BigInt, aerrors.ActorError) {
act, err := vm.cstate.GetActor(addr)
if err != nil {
return types.EmptyInt, aerrors.Absorb(err, 1, "failed to find actor")
}
return act.Balance, nil
}
func (vm *VM) Flush(ctx context.Context) (cid.Cid, error) {
_, span := trace.StartSpan(ctx, "vm.Flush")
defer span.End()
from := vm.buf
to := vm.buf.Read()
root, err := vm.cstate.Flush(ctx)
if err != nil {
return cid.Undef, xerrors.Errorf("flushing vm: %w", err)
}
if err := Copy(from, to, root); err != nil {
return cid.Undef, xerrors.Errorf("copying tree: %w", err)
}
return root, nil
}
// MutateState usage: MutateState(ctx, idAddr, func(cst cbor.IpldStore, st *ActorStateType) error {...})
func (vm *VM) MutateState(ctx context.Context, addr address.Address, fn interface{}) error {
act, err := vm.cstate.GetActor(addr)
if err != nil {
return xerrors.Errorf("actor not found: %w", err)
}
st := reflect.New(reflect.TypeOf(fn).In(1).Elem())
if err := vm.cst.Get(ctx, act.Head, st.Interface()); err != nil {
return xerrors.Errorf("read actor head: %w", err)
}
out := reflect.ValueOf(fn).Call([]reflect.Value{reflect.ValueOf(vm.cst), st})
if !out[0].IsNil() && out[0].Interface().(error) != nil {
return out[0].Interface().(error)
}
head, err := vm.cst.Put(ctx, st.Interface())
if err != nil {
return xerrors.Errorf("put new actor head: %w", err)
}
act.Head = head
if err := vm.cstate.SetActor(addr, act); err != nil {
return xerrors.Errorf("set actor: %w", err)
}
return nil
}
func linksForObj(blk block.Block, cb func(cid.Cid)) error {
switch blk.Cid().Prefix().Codec {
case cid.DagCBOR:
err := cbg.ScanForLinks(bytes.NewReader(blk.RawData()), cb)
if err != nil {
return xerrors.Errorf("cbg.ScanForLinks: %w", err)
}
return nil
case cid.Raw:
// We implicitly have all children of raw blocks.
return nil
default:
return xerrors.Errorf("vm flush copy method only supports dag cbor")
}
}
func Copy(from, to blockstore.Blockstore, root cid.Cid) error {
var batch []block.Block
batchCp := func(blk block.Block) error {
batch = append(batch, blk)
if len(batch) > 100 {
if err := to.PutMany(batch); err != nil {
return xerrors.Errorf("batch put in copy: %w", err)
}
batch = batch[:0]
}
return nil
}
if err := copyRec(from, to, root, batchCp); err != nil {
return xerrors.Errorf("copyRec: %w", err)
}
if len(batch) > 0 {
if err := to.PutMany(batch); err != nil {
return xerrors.Errorf("batch put in copy: %w", err)
}
}
return nil
}
func copyRec(from, to blockstore.Blockstore, root cid.Cid, cp func(block.Block) error) error {
if root.Prefix().MhType == 0 {
// identity cid, skip
return nil
}
blk, err := from.Get(root)
if err != nil {
return xerrors.Errorf("get %s failed: %w", root, err)
}
var lerr error
err = linksForObj(blk, func(link cid.Cid) {
if lerr != nil {
// Theres no erorr return on linksForObj callback :(
return
}
prefix := link.Prefix()
if prefix.Codec == cid.FilCommitmentSealed || prefix.Codec == cid.FilCommitmentUnsealed {
return
}
// We always have blocks inlined into CIDs, but we may not have their children.
if prefix.MhType == mh.IDENTITY {
// Unless the inlined block has no children.
if prefix.Codec == cid.Raw {
return
}
} else {
// If we have an object, we already have its children, skip the object.
has, err := to.Has(link)
if err != nil {
lerr = xerrors.Errorf("has: %w", err)
return
}
if has {
return
}
}
if err := copyRec(from, to, link, cp); err != nil {
lerr = err
return
}
})
if err != nil {
return xerrors.Errorf("linksForObj (%x): %w", blk.RawData(), err)
}
if lerr != nil {
return lerr
}
if err := cp(blk); err != nil {
return xerrors.Errorf("copy: %w", err)
}
return nil
}
func (vm *VM) StateTree() types.StateTree {
return vm.cstate
}
func (vm *VM) SetBlockHeight(h abi.ChainEpoch) {
vm.blockHeight = h
}
func (vm *VM) Invoke(act *types.Actor, rt *Runtime, method abi.MethodNum, params []byte) ([]byte, aerrors.ActorError) {
ctx, span := trace.StartSpan(rt.ctx, "vm.Invoke")
defer span.End()
if span.IsRecordingEvents() {
span.AddAttributes(
trace.StringAttribute("to", rt.Message().Receiver().String()),
trace.Int64Attribute("method", int64(method)),
trace.StringAttribute("value", rt.Message().ValueReceived().String()),
)
}
var oldCtx context.Context
oldCtx, rt.ctx = rt.ctx, ctx
defer func() {
rt.ctx = oldCtx
}()
ret, err := vm.inv.Invoke(act.Code, rt, method, params)
if err != nil {
return nil, err
}
return ret, nil
}
func (vm *VM) SetInvoker(i *Invoker) {
vm.inv = i
}
func (vm *VM) GetCircSupply(ctx context.Context) (abi.TokenAmount, error) {
return vm.circSupplyCalc(ctx, vm.blockHeight, vm.cstate)
}
func (vm *VM) incrementNonce(addr address.Address) error {
return vm.cstate.MutateActor(addr, func(a *types.Actor) error {
a.Nonce++
return nil
})
}
func (vm *VM) transfer(from, to address.Address, amt types.BigInt) aerrors.ActorError {
if from == to {
return nil
}
fromID, err := vm.cstate.LookupID(from)
if err != nil {
return aerrors.Fatalf("transfer failed when resolving sender address: %s", err)
}
toID, err := vm.cstate.LookupID(to)
if err != nil {
return aerrors.Fatalf("transfer failed when resolving receiver address: %s", err)
}
if fromID == toID {
return nil
}
if amt.LessThan(types.NewInt(0)) {
return aerrors.Newf(exitcode.SysErrForbidden, "attempted to transfer negative value: %s", amt)
}
f, err := vm.cstate.GetActor(fromID)
if err != nil {
return aerrors.Fatalf("transfer failed when retrieving sender actor: %s", err)
}
t, err := vm.cstate.GetActor(toID)
if err != nil {
return aerrors.Fatalf("transfer failed when retrieving receiver actor: %s", err)
}
if err := deductFunds(f, amt); err != nil {
return aerrors.Newf(exitcode.SysErrInsufficientFunds, "transfer failed when deducting funds (%s): %s", types.FIL(amt), err)
}
depositFunds(t, amt)
if err := vm.cstate.SetActor(fromID, f); err != nil {
return aerrors.Fatalf("transfer failed when setting receiver actor: %s", err)
}
if err := vm.cstate.SetActor(toID, t); err != nil {
return aerrors.Fatalf("transfer failed when setting sender actor: %s", err)
}
return nil
}
func (vm *VM) transferToGasHolder(addr address.Address, gasHolder *types.Actor, amt types.BigInt) error {
if amt.LessThan(types.NewInt(0)) {
return xerrors.Errorf("attempted to transfer negative value to gas holder")
}
return vm.cstate.MutateActor(addr, func(a *types.Actor) error {
if err := deductFunds(a, amt); err != nil {
return err
}
depositFunds(gasHolder, amt)
return nil
})
}
func (vm *VM) transferFromGasHolder(addr address.Address, gasHolder *types.Actor, amt types.BigInt) error {
if amt.LessThan(types.NewInt(0)) {
return xerrors.Errorf("attempted to transfer negative value from gas holder")
}
if amt.Equals(big.NewInt(0)) {
return nil
}
return vm.cstate.MutateActor(addr, func(a *types.Actor) error {
if err := deductFunds(gasHolder, amt); err != nil {
return err
}
depositFunds(a, amt)
return nil
})
}
func deductFunds(act *types.Actor, amt types.BigInt) error {
if act.Balance.LessThan(amt) {
return fmt.Errorf("not enough funds")
}
act.Balance = types.BigSub(act.Balance, amt)
return nil
}
func depositFunds(act *types.Actor, amt types.BigInt) {
act.Balance = types.BigAdd(act.Balance, amt)
}