lotus/chain/vm/fvm.go

388 lines
11 KiB
Go
Raw Normal View History

2021-12-17 03:38:13 +00:00
package vm
import (
2022-02-15 23:00:39 +00:00
"bytes"
2021-12-17 03:38:13 +00:00
"context"
2022-02-23 19:23:20 +00:00
"time"
2021-12-17 03:38:13 +00:00
2022-04-12 00:45:13 +00:00
"github.com/ipfs/go-cid"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/chain/actors/aerrors"
"github.com/filecoin-project/lotus/chain/actors/policy"
2022-03-04 18:52:41 +00:00
"github.com/filecoin-project/go-state-types/network"
2022-02-17 04:21:06 +00:00
"github.com/filecoin-project/go-state-types/big"
2022-02-23 19:23:20 +00:00
"github.com/filecoin-project/lotus/build"
2022-02-15 23:00:39 +00:00
"github.com/filecoin-project/lotus/chain/state"
cbor "github.com/ipfs/go-ipld-cbor"
2021-12-17 03:38:13 +00:00
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/exitcode"
2022-02-15 23:00:39 +00:00
"github.com/filecoin-project/lotus/lib/sigs"
2021-12-17 03:38:13 +00:00
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/blockstore"
ffi "github.com/filecoin-project/filecoin-ffi"
2022-01-31 10:31:58 +00:00
ffi_cgo "github.com/filecoin-project/filecoin-ffi/cgo"
2021-12-17 03:38:13 +00:00
2022-02-15 23:00:39 +00:00
"github.com/filecoin-project/lotus/chain/actors/adt"
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
2021-12-17 03:38:13 +00:00
"github.com/filecoin-project/lotus/chain/types"
)
2022-03-15 22:46:56 +00:00
var _ Interface = (*FVM)(nil)
2022-02-15 23:00:39 +00:00
var _ ffi_cgo.Externs = (*FvmExtern)(nil)
2021-12-17 03:38:13 +00:00
type FvmExtern struct {
2022-01-31 10:31:58 +00:00
Rand
blockstore.Blockstore
2022-02-15 23:00:39 +00:00
epoch abi.ChainEpoch
nv network.Version
2022-02-15 23:00:39 +00:00
lbState LookbackStateGetter
base cid.Cid
}
2022-03-13 21:24:13 +00:00
// This may eventually become identical to ExecutionTrace, but we can make incremental progress towards that
type FvmExecutionTrace struct {
Msg *types.Message
MsgRct *types.MessageReceipt
Error string
Subcalls []FvmExecutionTrace
}
func (t *FvmExecutionTrace) ToExecutionTrace() types.ExecutionTrace {
if t == nil {
return types.ExecutionTrace{}
}
ret := types.ExecutionTrace{
Msg: t.Msg,
MsgRct: t.MsgRct,
Error: t.Error,
Duration: 0,
GasCharges: nil,
Subcalls: make([]types.ExecutionTrace, len(t.Subcalls)),
}
for i, v := range t.Subcalls {
ret.Subcalls[i] = v.ToExecutionTrace()
}
return ret
}
// VerifyConsensusFault is similar to the one in syscalls.go used by the Lotus VM, except it never errors
2022-02-15 23:00:39 +00:00
// Errors are logged and "no fault" is returned, which is functionally what go-actors does anyway
2022-02-17 04:21:06 +00:00
func (x *FvmExtern) VerifyConsensusFault(ctx context.Context, a, b, extra []byte) (*ffi_cgo.ConsensusFault, int64) {
totalGas := int64(0)
ret := &ffi_cgo.ConsensusFault{
Type: ffi_cgo.ConsensusFaultNone,
2022-02-15 23:00:39 +00:00
}
// Note that block syntax is not validated. Any validly signed block will be accepted pursuant to the below conditions.
// Whether or not it could ever have been accepted in a chain is not checked/does not matter here.
// for that reason when checking block parent relationships, rather than instantiating a Tipset to do so
// (which runs a syntactic check), we do it directly on the CIDs.
// (0) cheap preliminary checks
// can blocks be decoded properly?
var blockA, blockB types.BlockHeader
if decodeErr := blockA.UnmarshalCBOR(bytes.NewReader(a)); decodeErr != nil {
log.Info("invalid consensus fault: cannot decode first block header: %w", decodeErr)
2022-02-17 04:21:06 +00:00
return ret, totalGas
2022-02-15 23:00:39 +00:00
}
if decodeErr := blockB.UnmarshalCBOR(bytes.NewReader(b)); decodeErr != nil {
log.Info("invalid consensus fault: cannot decode second block header: %w", decodeErr)
2022-02-17 04:21:06 +00:00
return ret, totalGas
2022-02-15 23:00:39 +00:00
}
// are blocks the same?
if blockA.Cid().Equals(blockB.Cid()) {
log.Info("invalid consensus fault: submitted blocks are the same")
2022-02-17 04:21:06 +00:00
return ret, totalGas
2022-02-15 23:00:39 +00:00
}
// (1) check conditions necessary to any consensus fault
// were blocks mined by same miner?
if blockA.Miner != blockB.Miner {
log.Info("invalid consensus fault: blocks not mined by the same miner")
2022-02-17 04:21:06 +00:00
return ret, totalGas
2022-02-15 23:00:39 +00:00
}
// block a must be earlier or equal to block b, epoch wise (ie at least as early in the chain).
if blockB.Height < blockA.Height {
log.Info("invalid consensus fault: first block must not be of higher height than second")
2022-02-17 04:21:06 +00:00
return ret, totalGas
2022-02-15 23:00:39 +00:00
}
ret.Epoch = blockB.Height
faultType := ffi_cgo.ConsensusFaultNone
// (2) check for the consensus faults themselves
// (a) double-fork mining fault
if blockA.Height == blockB.Height {
faultType = ffi_cgo.ConsensusFaultDoubleForkMining
}
// (b) time-offset mining fault
// strictly speaking no need to compare heights based on double fork mining check above,
// but at same height this would be a different fault.
if types.CidArrsEqual(blockA.Parents, blockB.Parents) && blockA.Height != blockB.Height {
faultType = ffi_cgo.ConsensusFaultTimeOffsetMining
}
// (c) parent-grinding fault
// Here extra is the "witness", a third block that shows the connection between A and B as
// A's sibling and B's parent.
// Specifically, since A is of lower height, it must be that B was mined omitting A from its tipset
//
// B
// |
// [A, C]
var blockC types.BlockHeader
if len(extra) > 0 {
if decodeErr := blockC.UnmarshalCBOR(bytes.NewReader(extra)); decodeErr != nil {
log.Info("invalid consensus fault: cannot decode extra: %w", decodeErr)
2022-02-17 04:21:06 +00:00
return ret, totalGas
2022-02-15 23:00:39 +00:00
}
if types.CidArrsEqual(blockA.Parents, blockC.Parents) && blockA.Height == blockC.Height &&
types.CidArrsContains(blockB.Parents, blockC.Cid()) && !types.CidArrsContains(blockB.Parents, blockA.Cid()) {
faultType = ffi_cgo.ConsensusFaultParentGrinding
}
}
// (3) return if no consensus fault by now
if faultType == ffi_cgo.ConsensusFaultNone {
log.Info("invalid consensus fault: no fault detected")
2022-02-17 04:21:06 +00:00
return ret, totalGas
2022-02-15 23:00:39 +00:00
}
// else
// (4) expensive final checks
// check blocks are properly signed by their respective miner
// note we do not need to check extra's: it is a parent to block b
// which itself is signed, so it was willingly included by the miner
2022-02-17 04:21:06 +00:00
gasA, sigErr := x.VerifyBlockSig(ctx, &blockA)
totalGas += gasA
2022-02-15 23:00:39 +00:00
if sigErr != nil {
log.Info("invalid consensus fault: cannot verify first block sig: %w", sigErr)
2022-02-17 04:21:06 +00:00
return ret, totalGas
2022-02-15 23:00:39 +00:00
}
2022-02-17 04:21:06 +00:00
gas2, sigErr := x.VerifyBlockSig(ctx, &blockB)
totalGas += gas2
2022-02-15 23:00:39 +00:00
if sigErr != nil {
log.Info("invalid consensus fault: cannot verify second block sig: %w", sigErr)
2022-02-17 04:21:06 +00:00
return ret, totalGas
2022-02-15 23:00:39 +00:00
}
ret.Type = faultType
2022-02-17 04:21:06 +00:00
ret.Target = blockA.Miner
2022-02-23 19:23:20 +00:00
2022-02-17 04:21:06 +00:00
return ret, totalGas
2022-02-15 23:00:39 +00:00
}
func (x *FvmExtern) VerifyBlockSig(ctx context.Context, blk *types.BlockHeader) (int64, error) {
waddr, gasUsed, err := x.workerKeyAtLookback(ctx, blk.Miner, blk.Height)
if err != nil {
return gasUsed, err
}
return gasUsed, sigs.CheckBlockSignature(ctx, blk, waddr)
2021-12-17 03:38:13 +00:00
}
2022-02-15 23:00:39 +00:00
func (x *FvmExtern) workerKeyAtLookback(ctx context.Context, minerId address.Address, height abi.ChainEpoch) (address.Address, int64, error) {
if height < x.epoch-policy.ChainFinality {
return address.Undef, 0, xerrors.Errorf("cannot get worker key (currEpoch %d, height %d)", x.epoch, height)
}
2022-02-15 23:00:39 +00:00
gasUsed := int64(0)
gasAdder := func(gc GasCharge) {
// technically not overflow safe, but that's fine
gasUsed += gc.Total()
}
cstWithoutGas := cbor.NewCborStore(x.Blockstore)
cbb := &gasChargingBlocks{gasAdder, PricelistByEpochAndNetworkVersion(x.epoch, x.nv), x.Blockstore}
2022-02-15 23:00:39 +00:00
cstWithGas := cbor.NewCborStore(cbb)
lbState, err := x.lbState(ctx, height)
if err != nil {
return address.Undef, gasUsed, err
}
// get appropriate miner actor
act, err := lbState.GetActor(minerId)
if err != nil {
return address.Undef, gasUsed, err
}
// use that to get the miner state
mas, err := miner.Load(adt.WrapStore(ctx, cstWithGas), act)
if err != nil {
return address.Undef, gasUsed, err
}
info, err := mas.Info()
if err != nil {
return address.Undef, gasUsed, err
}
stateTree, err := state.LoadStateTree(cstWithoutGas, x.base)
if err != nil {
return address.Undef, gasUsed, err
}
raddr, err := ResolveToKeyAddr(stateTree, cstWithGas, info.Worker)
if err != nil {
return address.Undef, gasUsed, err
}
return raddr, gasUsed, nil
2021-12-17 03:38:13 +00:00
}
type FVM struct {
2022-01-31 10:31:58 +00:00
fvm *ffi.FVM
2021-12-17 03:38:13 +00:00
}
func NewFVM(ctx context.Context, opts *VMOpts) (*FVM, error) {
2022-03-23 17:17:32 +00:00
log.Info("using the FVM, this is experimental!")
2022-03-04 18:52:41 +00:00
circToReport := opts.FilVested
// For v14 (and earlier), we perform the FilVested portion of the calculation, and let the FVM dynamically do the rest
// v15 and after, the circ supply is always constant per epoch, so we calculate the base and report it at creation
if opts.NetworkVersion >= network.Version15 {
state, err := state.LoadStateTree(cbor.NewCborStore(opts.Bstore), opts.StateBase)
if err != nil {
return nil, err
}
circToReport, err = opts.CircSupplyCalc(ctx, opts.Epoch, state)
if err != nil {
return nil, err
}
}
2022-03-27 02:36:32 +00:00
fvmOpts := ffi.FVMOpts{
FVMVersion: 0,
Externs: &FvmExtern{Rand: opts.Rand, Blockstore: opts.Bstore, lbState: opts.LookbackState, base: opts.StateBase, epoch: opts.Epoch, nv: opts.NetworkVersion},
Epoch: opts.Epoch,
BaseFee: opts.BaseFee,
BaseCircSupply: circToReport,
NetworkVersion: opts.NetworkVersion,
StateBase: opts.StateBase,
2022-03-13 21:24:13 +00:00
Tracing: EnableDetailedTracing,
2022-03-27 02:36:32 +00:00
}
fvm, err := ffi.CreateFVM(&fvmOpts)
2021-12-17 03:38:13 +00:00
if err != nil {
return nil, err
}
return &FVM{
2022-01-31 10:31:58 +00:00
fvm: fvm,
2021-12-17 03:38:13 +00:00
}, nil
}
func (vm *FVM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, error) {
2022-02-23 19:23:20 +00:00
start := build.Clock.Now()
2021-12-17 03:38:13 +00:00
msgBytes, err := cmsg.VMMessage().Serialize()
if err != nil {
return nil, xerrors.Errorf("serializing msg: %w", err)
}
ret, err := vm.fvm.ApplyMessage(msgBytes, uint(cmsg.ChainLength()))
2021-12-17 03:38:13 +00:00
if err != nil {
return nil, xerrors.Errorf("applying msg: %w", err)
}
2022-03-13 21:24:13 +00:00
var et FvmExecutionTrace
if len(ret.ExecTraceBytes) != 0 {
if err = et.UnmarshalCBOR(bytes.NewReader(ret.ExecTraceBytes)); err != nil {
return nil, xerrors.Errorf("failed to unmarshal exectrace: %w", err)
}
}
var aerr aerrors.ActorError
if ret.ExitCode != 0 {
amsg := ret.FailureInfo
if amsg == "" {
amsg = "unknown error"
}
aerr = aerrors.New(exitcode.ExitCode(ret.ExitCode), amsg)
}
2021-12-17 03:38:13 +00:00
return &ApplyRet{
MessageReceipt: types.MessageReceipt{
Return: ret.Return,
ExitCode: exitcode.ExitCode(ret.ExitCode),
GasUsed: ret.GasUsed,
},
GasCosts: &GasOutputs{
// TODO: do the other optional fields eventually
2022-02-17 04:21:06 +00:00
BaseFeeBurn: big.Zero(),
OverEstimationBurn: big.Zero(),
2021-12-17 03:38:13 +00:00
MinerPenalty: ret.MinerPenalty,
MinerTip: ret.MinerTip,
2022-02-17 04:21:06 +00:00
Refund: big.Zero(),
2021-12-17 03:38:13 +00:00
GasRefund: 0,
GasBurned: 0,
},
ActorErr: aerr,
2022-03-13 21:24:13 +00:00
ExecutionTrace: et.ToExecutionTrace(),
2022-02-23 19:23:20 +00:00
Duration: time.Since(start),
2021-12-17 03:38:13 +00:00
}, nil
}
func (vm *FVM) ApplyImplicitMessage(ctx context.Context, cmsg *types.Message) (*ApplyRet, error) {
2022-02-23 19:23:20 +00:00
start := build.Clock.Now()
2021-12-17 03:38:13 +00:00
msgBytes, err := cmsg.VMMessage().Serialize()
if err != nil {
return nil, xerrors.Errorf("serializing msg: %w", err)
}
ret, err := vm.fvm.ApplyImplicitMessage(msgBytes)
2021-12-17 03:38:13 +00:00
if err != nil {
return nil, xerrors.Errorf("applying msg: %w", err)
}
2022-03-13 21:24:13 +00:00
var et FvmExecutionTrace
if len(ret.ExecTraceBytes) != 0 {
if err = et.UnmarshalCBOR(bytes.NewReader(ret.ExecTraceBytes)); err != nil {
return nil, xerrors.Errorf("failed to unmarshal exectrace: %w", err)
}
}
var aerr aerrors.ActorError
if ret.ExitCode != 0 {
amsg := ret.FailureInfo
if amsg == "" {
amsg = "unknown error"
}
aerr = aerrors.New(exitcode.ExitCode(ret.ExitCode), amsg)
}
2021-12-17 03:38:13 +00:00
return &ApplyRet{
MessageReceipt: types.MessageReceipt{
Return: ret.Return,
ExitCode: exitcode.ExitCode(ret.ExitCode),
GasUsed: ret.GasUsed,
},
ActorErr: aerr,
2022-03-13 21:24:13 +00:00
ExecutionTrace: et.ToExecutionTrace(),
2022-02-23 19:23:20 +00:00
Duration: time.Since(start),
2021-12-17 03:38:13 +00:00
}, nil
}
func (vm *FVM) Flush(ctx context.Context) (cid.Cid, error) {
2022-01-31 10:31:58 +00:00
return vm.fvm.Flush()
2021-12-17 03:38:13 +00:00
}