Merge remote-tracking branch 'origin/next' into feat/faster-v2-upgrade

This commit is contained in:
Łukasz Magiera 2020-10-08 01:54:03 +02:00
commit 8dd8892b81
35 changed files with 997 additions and 219 deletions

View File

@ -3,6 +3,9 @@
package build package build
import ( import (
"math"
"os"
"github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/actors/policy"
@ -13,8 +16,10 @@ const BreezeGasTampingDuration = 0
const UpgradeSmokeHeight = -1 const UpgradeSmokeHeight = -1
const UpgradeIgnitionHeight = -2 const UpgradeIgnitionHeight = -2
const UpgradeLiftoffHeight = -3 const UpgradeRefuelHeight = -3
const UpgradeActorsV2Height = 10
var UpgradeActorsV2Height = abi.ChainEpoch(10)
var UpgradeLiftoffHeight = abi.ChainEpoch(-4)
var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ var DrandSchedule = map[abi.ChainEpoch]DrandEnum{
0: DrandMainnet, 0: DrandMainnet,
@ -25,6 +30,11 @@ func init() {
policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048))
policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) policy.SetMinVerifiedDealSize(abi.NewStoragePower(256))
if os.Getenv("LOTUS_DISABLE_V2_ACTOR_MIGRATION") == "1" {
UpgradeActorsV2Height = math.MaxInt64
UpgradeLiftoffHeight = 11
}
BuildType |= Build2k BuildType |= Build2k
} }

View File

@ -82,8 +82,9 @@ var (
UpgradeSmokeHeight abi.ChainEpoch = -1 UpgradeSmokeHeight abi.ChainEpoch = -1
UpgradeIgnitionHeight abi.ChainEpoch = -2 UpgradeIgnitionHeight abi.ChainEpoch = -2
UpgradeLiftoffHeight abi.ChainEpoch = -3 UpgradeRefuelHeight abi.ChainEpoch = -3
UpgradeActorsV2Height abi.ChainEpoch = 10 UpgradeActorsV2Height abi.ChainEpoch = 10
UpgradeLiftoffHeight abi.ChainEpoch = -4
DrandSchedule = map[abi.ChainEpoch]DrandEnum{ DrandSchedule = map[abi.ChainEpoch]DrandEnum{
0: DrandMainnet, 0: DrandMainnet,

View File

@ -5,6 +5,9 @@
package build package build
import ( import (
"math"
"os"
"github.com/filecoin-project/go-address" "github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/actors/policy"
@ -23,9 +26,9 @@ const BreezeGasTampingDuration = 120
const UpgradeSmokeHeight = 51000 const UpgradeSmokeHeight = 51000
const UpgradeIgnitionHeight = 94000 const UpgradeIgnitionHeight = 94000
const UpgradeRefuelHeight = 130800
// TODO: Actual epoch needs to be filled in var UpgradeActorsV2Height = abi.ChainEpoch(138720)
const UpgradeActorsV2Height = 128888
// This signals our tentative epoch for mainnet launch. Can make it later, but not earlier. // This signals our tentative epoch for mainnet launch. Can make it later, but not earlier.
// Miners, clients, developers, custodians all need time to prepare. // Miners, clients, developers, custodians all need time to prepare.
@ -39,7 +42,13 @@ func init() {
abi.RegisteredSealProof_StackedDrg64GiBV1, abi.RegisteredSealProof_StackedDrg64GiBV1,
) )
if os.Getenv("LOTUS_USE_TEST_ADDRESSES") != "1" {
SetAddressNetwork(address.Mainnet) SetAddressNetwork(address.Mainnet)
}
if os.Getenv("LOTUS_DISABLE_V2_ACTOR_MIGRATION") == "1" {
UpgradeActorsV2Height = math.MaxInt64
}
Devnet = false Devnet = false
} }

View File

@ -22,6 +22,7 @@ import (
var SystemActorAddr = builtin0.SystemActorAddr var SystemActorAddr = builtin0.SystemActorAddr
var BurntFundsActorAddr = builtin0.BurntFundsActorAddr var BurntFundsActorAddr = builtin0.BurntFundsActorAddr
var ReserveAddress = makeAddress("t090") var ReserveAddress = makeAddress("t090")
var RootVerifierAddress = makeAddress("t080")
// TODO: Why does actors have 2 different versions of this? // TODO: Why does actors have 2 different versions of this?
type SectorInfo = proof0.SectorInfo type SectorInfo = proof0.SectorInfo

View File

@ -0,0 +1,180 @@
package miner
import (
"errors"
"github.com/filecoin-project/go-bitfield"
"github.com/filecoin-project/go-state-types/exitcode"
)
type DeadlinesDiff map[uint64]*DeadlineDiff
func DiffDeadlines(pre, cur State) (*DeadlinesDiff, error) {
changed, err := pre.DeadlinesChanged(cur)
if err != nil {
return nil, err
}
if !changed {
return nil, nil
}
numDl, err := pre.NumDeadlines()
if err != nil {
return nil, err
}
dlDiff := make(DeadlinesDiff, numDl)
if err := pre.ForEachDeadline(func(idx uint64, preDl Deadline) error {
curDl, err := cur.LoadDeadline(idx)
if err != nil {
return err
}
diff, err := DiffDeadline(preDl, curDl)
if err != nil {
return err
}
dlDiff[idx] = diff
return nil
}); err != nil {
return nil, err
}
return &dlDiff, nil
}
type DeadlineDiff map[uint64]*PartitionDiff
func DiffDeadline(pre, cur Deadline) (*DeadlineDiff, error) {
changed, err := pre.PartitionsChanged(cur)
if err != nil {
return nil, err
}
if !changed {
return nil, nil
}
partDiff := make(DeadlineDiff)
if err := pre.ForEachPartition(func(idx uint64, prePart Partition) error {
// try loading current partition at this index
curPart, err := cur.LoadPartition(idx)
if err != nil {
if errors.Is(err, exitcode.ErrNotFound) {
// TODO correctness?
return nil // the partition was removed.
}
return err
}
// compare it with the previous partition
diff, err := DiffPartition(prePart, curPart)
if err != nil {
return err
}
partDiff[idx] = diff
return nil
}); err != nil {
return nil, err
}
// all previous partitions have been walked.
// all partitions in cur and not in prev are new... can they be faulty already?
// TODO is this correct?
if err := cur.ForEachPartition(func(idx uint64, curPart Partition) error {
if _, found := partDiff[idx]; found {
return nil
}
faults, err := curPart.FaultySectors()
if err != nil {
return err
}
recovering, err := curPart.RecoveringSectors()
if err != nil {
return err
}
partDiff[idx] = &PartitionDiff{
Removed: bitfield.New(),
Recovered: bitfield.New(),
Faulted: faults,
Recovering: recovering,
}
return nil
}); err != nil {
return nil, err
}
return &partDiff, nil
}
type PartitionDiff struct {
Removed bitfield.BitField
Recovered bitfield.BitField
Faulted bitfield.BitField
Recovering bitfield.BitField
}
func DiffPartition(pre, cur Partition) (*PartitionDiff, error) {
prevLiveSectors, err := pre.LiveSectors()
if err != nil {
return nil, err
}
curLiveSectors, err := cur.LiveSectors()
if err != nil {
return nil, err
}
removed, err := bitfield.SubtractBitField(prevLiveSectors, curLiveSectors)
if err != nil {
return nil, err
}
prevRecoveries, err := pre.RecoveringSectors()
if err != nil {
return nil, err
}
curRecoveries, err := cur.RecoveringSectors()
if err != nil {
return nil, err
}
recovering, err := bitfield.SubtractBitField(curRecoveries, prevRecoveries)
if err != nil {
return nil, err
}
prevFaults, err := pre.FaultySectors()
if err != nil {
return nil, err
}
curFaults, err := cur.FaultySectors()
if err != nil {
return nil, err
}
faulted, err := bitfield.SubtractBitField(curFaults, prevFaults)
if err != nil {
return nil, err
}
// all current good sectors
curActiveSectors, err := cur.ActiveSectors()
if err != nil {
return nil, err
}
// sectors that were previously fault and are now currently active are considered recovered.
recovered, err := bitfield.IntersectBitField(prevFaults, curActiveSectors)
if err != nil {
return nil, err
}
return &PartitionDiff{
Removed: removed,
Recovered: recovered,
Faulted: faulted,
Recovering: recovering,
}, nil
}

View File

@ -9,8 +9,8 @@ import (
type Version int type Version int
const ( const (
Version0 = 0 Version0 Version = 0
Version2 = 2 Version2 Version = 2
) )
// Converts a network version into an actors adt version. // Converts a network version into an actors adt version.

View File

@ -26,7 +26,6 @@ import (
adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" adt0 "github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
@ -117,7 +116,7 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge
return nil, nil, xerrors.Errorf("putting empty object: %w", err) return nil, nil, xerrors.Errorf("putting empty object: %w", err)
} }
state, err := state.NewStateTree(cst, actors.Version0) state, err := state.NewStateTree(cst, types.StateTreeVersion0)
if err != nil { if err != nil {
return nil, nil, xerrors.Errorf("making new state tree: %w", err) return nil, nil, xerrors.Errorf("making new state tree: %w", err)
} }

View File

@ -13,6 +13,7 @@ import (
"github.com/filecoin-project/go-address" "github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/network"
"github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors"
init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init"
cbg "github.com/whyrusleeping/cbor-gen" cbg "github.com/whyrusleeping/cbor-gen"
@ -26,7 +27,7 @@ var log = logging.Logger("statetree")
// StateTree stores actors state by their ID. // StateTree stores actors state by their ID.
type StateTree struct { type StateTree struct {
root adt.Map root adt.Map
version actors.Version // TODO version types.StateTreeVersion
info cid.Cid info cid.Cid
Store cbor.IpldStore Store cbor.IpldStore
@ -120,21 +121,41 @@ func (ss *stateSnaps) deleteActor(addr address.Address) {
ss.layers[len(ss.layers)-1].actors[addr] = streeOp{Delete: true} ss.layers[len(ss.layers)-1].actors[addr] = streeOp{Delete: true}
} }
func NewStateTree(cst cbor.IpldStore, version actors.Version) (*StateTree, error) { // VersionForNetwork returns the state tree version for the given network
// version.
func VersionForNetwork(ver network.Version) types.StateTreeVersion {
if actors.VersionForNetwork(ver) == actors.Version0 {
return types.StateTreeVersion0
}
return types.StateTreeVersion1
}
func adtForSTVersion(ver types.StateTreeVersion) actors.Version {
switch ver {
case types.StateTreeVersion0:
return actors.Version0
case types.StateTreeVersion1:
return actors.Version2
default:
panic("unhandled state tree version")
}
}
func NewStateTree(cst cbor.IpldStore, ver types.StateTreeVersion) (*StateTree, error) {
var info cid.Cid var info cid.Cid
switch version { switch ver {
case actors.Version0: case types.StateTreeVersion0:
// info is undefined // info is undefined
case actors.Version2: case types.StateTreeVersion1:
var err error var err error
info, err = cst.Put(context.TODO(), new(types.StateInfo)) info, err = cst.Put(context.TODO(), new(types.StateInfo0))
if err != nil { if err != nil {
return nil, err return nil, err
} }
default: default:
return nil, xerrors.Errorf("unsupported state tree version: %d", version) return nil, xerrors.Errorf("unsupported state tree version: %d", ver)
} }
root, err := adt.NewMap(adt.WrapStore(context.TODO(), cst), version) root, err := adt.NewMap(adt.WrapStore(context.TODO(), cst), adtForSTVersion(ver))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -142,7 +163,7 @@ func NewStateTree(cst cbor.IpldStore, version actors.Version) (*StateTree, error
return &StateTree{ return &StateTree{
root: root, root: root,
info: info, info: info,
version: version, version: ver,
Store: cst, Store: cst,
snaps: newStateSnaps(), snaps: newStateSnaps(),
}, nil }, nil
@ -154,13 +175,16 @@ func LoadStateTree(cst cbor.IpldStore, c cid.Cid) (*StateTree, error) {
if err := cst.Get(context.TODO(), c, &root); err != nil { if err := cst.Get(context.TODO(), c, &root); err != nil {
// We failed to decode as the new version, must be an old version. // We failed to decode as the new version, must be an old version.
root.Actors = c root.Actors = c
root.Version = actors.Version0 root.Version = types.StateTreeVersion0
} }
switch root.Version { switch root.Version {
case actors.Version0, actors.Version2: case types.StateTreeVersion0, types.StateTreeVersion1:
// Load the actual state-tree HAMT. // Load the actual state-tree HAMT.
nd, err := adt.AsMap(adt.WrapStore(context.TODO(), cst), root.Actors, actors.Version(root.Version)) nd, err := adt.AsMap(
adt.WrapStore(context.TODO(), cst), root.Actors,
adtForSTVersion(root.Version),
)
if err != nil { if err != nil {
log.Errorf("loading hamt node %s failed: %s", c, err) log.Errorf("loading hamt node %s failed: %s", c, err)
return nil, err return nil, err
@ -169,7 +193,7 @@ func LoadStateTree(cst cbor.IpldStore, c cid.Cid) (*StateTree, error) {
return &StateTree{ return &StateTree{
root: nd, root: nd,
info: root.Info, info: root.Info,
version: actors.Version(root.Version), version: root.Version,
Store: cst, Store: cst,
snaps: newStateSnaps(), snaps: newStateSnaps(),
}, nil }, nil
@ -309,11 +333,11 @@ func (st *StateTree) Flush(ctx context.Context) (cid.Cid, error) {
return cid.Undef, xerrors.Errorf("failed to flush state-tree hamt: %w", err) return cid.Undef, xerrors.Errorf("failed to flush state-tree hamt: %w", err)
} }
// If we're version 0, return a raw tree. // If we're version 0, return a raw tree.
if st.version == actors.Version0 { if st.version == types.StateTreeVersion0 {
return root, nil return root, nil
} }
// Otherwise, return a versioned tree. // Otherwise, return a versioned tree.
return st.Store.Put(ctx, &types.StateRoot{Version: uint64(st.version), Actors: root, Info: st.info}) return st.Store.Put(ctx, &types.StateRoot{Version: st.version, Actors: root, Info: st.info})
} }
func (st *StateTree) Snapshot(ctx context.Context) error { func (st *StateTree) Snapshot(ctx context.Context) error {
@ -400,7 +424,7 @@ func (st *StateTree) ForEach(f func(address.Address, *types.Actor) error) error
} }
// Version returns the version of the StateTree data structure in use. // Version returns the version of the StateTree data structure in use.
func (st *StateTree) Version() actors.Version { func (st *StateTree) Version() types.StateTreeVersion {
return st.version return st.version
} }

View File

@ -13,13 +13,12 @@ import (
"github.com/filecoin-project/specs-actors/actors/builtin" "github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
) )
func BenchmarkStateTreeSet(b *testing.B) { func BenchmarkStateTreeSet(b *testing.B) {
cst := cbor.NewMemCborStore() cst := cbor.NewMemCborStore()
st, err := NewStateTree(cst, actors.VersionForNetwork(build.NewestNetworkVersion)) st, err := NewStateTree(cst, VersionForNetwork(build.NewestNetworkVersion))
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -46,7 +45,7 @@ func BenchmarkStateTreeSet(b *testing.B) {
func BenchmarkStateTreeSetFlush(b *testing.B) { func BenchmarkStateTreeSetFlush(b *testing.B) {
cst := cbor.NewMemCborStore() cst := cbor.NewMemCborStore()
st, err := NewStateTree(cst, actors.VersionForNetwork(build.NewestNetworkVersion)) st, err := NewStateTree(cst, VersionForNetwork(build.NewestNetworkVersion))
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -76,7 +75,7 @@ func BenchmarkStateTreeSetFlush(b *testing.B) {
func BenchmarkStateTree10kGetActor(b *testing.B) { func BenchmarkStateTree10kGetActor(b *testing.B) {
cst := cbor.NewMemCborStore() cst := cbor.NewMemCborStore()
st, err := NewStateTree(cst, actors.VersionForNetwork(build.NewestNetworkVersion)) st, err := NewStateTree(cst, VersionForNetwork(build.NewestNetworkVersion))
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -118,7 +117,7 @@ func BenchmarkStateTree10kGetActor(b *testing.B) {
func TestSetCache(t *testing.T) { func TestSetCache(t *testing.T) {
cst := cbor.NewMemCborStore() cst := cbor.NewMemCborStore()
st, err := NewStateTree(cst, actors.VersionForNetwork(build.NewestNetworkVersion)) st, err := NewStateTree(cst, VersionForNetwork(build.NewestNetworkVersion))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -155,7 +154,7 @@ func TestSetCache(t *testing.T) {
func TestSnapshots(t *testing.T) { func TestSnapshots(t *testing.T) {
ctx := context.Background() ctx := context.Background()
cst := cbor.NewMemCborStore() cst := cbor.NewMemCborStore()
st, err := NewStateTree(cst, actors.VersionForNetwork(build.NewestNetworkVersion)) st, err := NewStateTree(cst, VersionForNetwork(build.NewestNetworkVersion))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -239,7 +238,7 @@ func assertNotHas(t *testing.T, st *StateTree, addr address.Address) {
func TestStateTreeConsistency(t *testing.T) { func TestStateTreeConsistency(t *testing.T) {
cst := cbor.NewMemCborStore() cst := cbor.NewMemCborStore()
// TODO: ActorUpgrade: this test tests pre actors v2 // TODO: ActorUpgrade: this test tests pre actors v2
st, err := NewStateTree(cst, actors.VersionForNetwork(network.Version3)) st, err := NewStateTree(cst, VersionForNetwork(network.Version3))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -2,6 +2,7 @@ package stmgr
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/filecoin-project/go-address" "github.com/filecoin-project/go-address"
@ -17,17 +18,38 @@ import (
"github.com/filecoin-project/lotus/chain/vm" "github.com/filecoin-project/lotus/chain/vm"
) )
var ErrExpensiveFork = errors.New("refusing explicit call due to state fork at epoch")
func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) { func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) {
ctx, span := trace.StartSpan(ctx, "statemanager.Call") ctx, span := trace.StartSpan(ctx, "statemanager.Call")
defer span.End() defer span.End()
// If no tipset is provided, try to find one without a fork.
if ts == nil { if ts == nil {
ts = sm.cs.GetHeaviestTipSet() ts = sm.cs.GetHeaviestTipSet()
// Search back till we find a height with no fork, or we reach the beginning.
for ts.Height() > 0 && sm.hasExpensiveFork(ctx, ts.Height()-1) {
var err error
ts, err = sm.cs.GetTipSetFromKey(ts.Parents())
if err != nil {
return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err)
}
}
} }
bstate := ts.ParentState() bstate := ts.ParentState()
bheight := ts.Height() bheight := ts.Height()
// If we have to run an expensive migration, and we're not at genesis,
// return an error because the migration will take too long.
//
// We allow this at height 0 for at-genesis migrations (for testing).
if bheight-1 > 0 && sm.hasExpensiveFork(ctx, bheight-1) {
return nil, ErrExpensiveFork
}
// Run the (not expensive) migration.
bstate, err := sm.handleStateForks(ctx, bstate, bheight-1, nil, ts) bstate, err := sm.handleStateForks(ctx, bstate, bheight-1, nil, ts)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to handle fork: %w", err) return nil, fmt.Errorf("failed to handle fork: %w", err)
@ -44,7 +66,7 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.
BaseFee: types.NewInt(0), BaseFee: types.NewInt(0),
} }
vmi, err := vm.NewVM(ctx, vmopt) vmi, err := sm.newVM(ctx, vmopt)
if err != nil { if err != nil {
return nil, xerrors.Errorf("failed to set up vm: %w", err) return nil, xerrors.Errorf("failed to set up vm: %w", err)
} }
@ -106,6 +128,24 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri
if ts == nil { if ts == nil {
ts = sm.cs.GetHeaviestTipSet() ts = sm.cs.GetHeaviestTipSet()
// Search back till we find a height with no fork, or we reach the beginning.
// We need the _previous_ height to have no fork, because we'll
// run the fork logic in `sm.TipSetState`. We need the _current_
// height to have no fork, because we'll run it inside this
// function before executing the given message.
for ts.Height() > 0 && (sm.hasExpensiveFork(ctx, ts.Height()) || sm.hasExpensiveFork(ctx, ts.Height()-1)) {
var err error
ts, err = sm.cs.GetTipSetFromKey(ts.Parents())
if err != nil {
return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err)
}
}
}
// When we're not at the genesis block, make sure we don't have an expensive migration.
if ts.Height() > 0 && (sm.hasExpensiveFork(ctx, ts.Height()) || sm.hasExpensiveFork(ctx, ts.Height()-1)) {
return nil, ErrExpensiveFork
} }
state, _, err := sm.TipSetState(ctx, ts) state, _, err := sm.TipSetState(ctx, ts)
@ -138,7 +178,7 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri
NtwkVersion: sm.GetNtwkVersion, NtwkVersion: sm.GetNtwkVersion,
BaseFee: ts.Blocks()[0].ParentBaseFee, BaseFee: ts.Blocks()[0].ParentBaseFee,
} }
vmi, err := vm.NewVM(ctx, vmopt) vmi, err := sm.newVM(ctx, vmopt)
if err != nil { if err != nil {
return nil, xerrors.Errorf("failed to set up vm: %w", err) return nil, xerrors.Errorf("failed to set up vm: %w", err)
} }

View File

@ -6,6 +6,8 @@ import (
"encoding/binary" "encoding/binary"
"math" "math"
"github.com/filecoin-project/lotus/chain/actors/builtin"
"github.com/filecoin-project/go-address" "github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/big"
@ -25,7 +27,6 @@ import (
states2 "github.com/filecoin-project/specs-actors/v2/actors/states" states2 "github.com/filecoin-project/specs-actors/v2/actors/states"
"github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/adt"
init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init"
"github.com/filecoin-project/lotus/chain/actors/builtin/multisig" "github.com/filecoin-project/lotus/chain/actors/builtin/multisig"
@ -37,11 +38,19 @@ import (
"github.com/filecoin-project/lotus/lib/bufbstore" "github.com/filecoin-project/lotus/lib/bufbstore"
) )
type UpgradeFunc func(context.Context, *StateManager, ExecCallback, cid.Cid, *types.TipSet) (cid.Cid, error) // UpgradeFunc is a migration function run at every upgrade.
//
// - The oldState is the state produced by the upgrade epoch.
// - The returned newState is the new state that will be used by the next epoch.
// - The height is the upgrade epoch height (already executed).
// - The tipset is the tipset for the last non-null block before the upgrade. Do
// not assume that ts.Height() is the upgrade height.
type UpgradeFunc func(ctx context.Context, sm *StateManager, cb ExecCallback, oldState cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (newState cid.Cid, err error)
type Upgrade struct { type Upgrade struct {
Height abi.ChainEpoch Height abi.ChainEpoch
Network network.Version Network network.Version
Expensive bool
Migration UpgradeFunc Migration UpgradeFunc
} }
@ -50,7 +59,7 @@ type UpgradeSchedule []Upgrade
func DefaultUpgradeSchedule() UpgradeSchedule { func DefaultUpgradeSchedule() UpgradeSchedule {
var us UpgradeSchedule var us UpgradeSchedule
for _, u := range []Upgrade{{ updates := []Upgrade{{
Height: build.UpgradeBreezeHeight, Height: build.UpgradeBreezeHeight,
Network: network.Version1, Network: network.Version1,
Migration: UpgradeFaucetBurnRecovery, Migration: UpgradeFaucetBurnRecovery,
@ -62,15 +71,46 @@ func DefaultUpgradeSchedule() UpgradeSchedule {
Height: build.UpgradeIgnitionHeight, Height: build.UpgradeIgnitionHeight,
Network: network.Version3, Network: network.Version3,
Migration: UpgradeIgnition, Migration: UpgradeIgnition,
}, {
Height: build.UpgradeRefuelHeight,
Network: network.Version3,
Migration: UpgradeRefuel,
}, { }, {
Height: build.UpgradeActorsV2Height, Height: build.UpgradeActorsV2Height,
Network: network.Version4, Network: network.Version4,
Expensive: true,
Migration: UpgradeActorsV2, Migration: UpgradeActorsV2,
}, { }, {
Height: build.UpgradeLiftoffHeight, Height: build.UpgradeLiftoffHeight,
Network: network.Version4, Network: network.Version4,
Migration: UpgradeLiftoff, Migration: UpgradeLiftoff,
}} { }}
if build.UpgradeActorsV2Height == math.MaxInt64 { // disable actors upgrade
updates = []Upgrade{{
Height: build.UpgradeBreezeHeight,
Network: network.Version1,
Migration: UpgradeFaucetBurnRecovery,
}, {
Height: build.UpgradeSmokeHeight,
Network: network.Version2,
Migration: nil,
}, {
Height: build.UpgradeIgnitionHeight,
Network: network.Version3,
Migration: UpgradeIgnition,
}, {
Height: build.UpgradeRefuelHeight,
Network: network.Version3,
Migration: UpgradeRefuel,
}, {
Height: build.UpgradeLiftoffHeight,
Network: network.Version3,
Migration: UpgradeLiftoff,
}}
}
for _, u := range updates {
if u.Height < 0 { if u.Height < 0 {
// upgrade disabled // upgrade disabled
continue continue
@ -112,7 +152,7 @@ func (sm *StateManager) handleStateForks(ctx context.Context, root cid.Cid, heig
var err error var err error
f, ok := sm.stateMigrations[height] f, ok := sm.stateMigrations[height]
if ok { if ok {
retCid, err = f(ctx, sm, cb, root, ts) retCid, err = f(ctx, sm, cb, root, height, ts)
if err != nil { if err != nil {
return cid.Undef, err return cid.Undef, err
} }
@ -121,6 +161,11 @@ func (sm *StateManager) handleStateForks(ctx context.Context, root cid.Cid, heig
return retCid, nil return retCid, nil
} }
func (sm *StateManager) hasExpensiveFork(ctx context.Context, height abi.ChainEpoch) bool {
_, ok := sm.expensiveUpgrades[height]
return ok
}
func doTransfer(cb ExecCallback, tree types.StateTree, from, to address.Address, amt abi.TokenAmount) error { func doTransfer(cb ExecCallback, tree types.StateTree, from, to address.Address, amt abi.TokenAmount) error {
fromAct, err := tree.GetActor(from) fromAct, err := tree.GetActor(from)
if err != nil { if err != nil {
@ -183,7 +228,7 @@ func doTransfer(cb ExecCallback, tree types.StateTree, from, to address.Address,
return nil return nil
} }
func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, ts *types.TipSet) (cid.Cid, error) { func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) {
// Some initial parameters // Some initial parameters
FundsForMiners := types.FromFil(1_000_000) FundsForMiners := types.FromFil(1_000_000)
LookbackEpoch := abi.ChainEpoch(32000) LookbackEpoch := abi.ChainEpoch(32000)
@ -435,11 +480,9 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, cb ExecCal
return tree.Flush(ctx) return tree.Flush(ctx)
} }
func UpgradeIgnition(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, ts *types.TipSet) (cid.Cid, error) { func UpgradeIgnition(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) {
store := sm.cs.Store(ctx) store := sm.cs.Store(ctx)
epoch := ts.Height() - 1
if build.UpgradeLiftoffHeight <= epoch { if build.UpgradeLiftoffHeight <= epoch {
return cid.Undef, xerrors.Errorf("liftoff height must be beyond ignition height") return cid.Undef, xerrors.Errorf("liftoff height must be beyond ignition height")
} }
@ -492,13 +535,42 @@ func UpgradeIgnition(ctx context.Context, sm *StateManager, cb ExecCallback, roo
return tree.Flush(ctx) return tree.Flush(ctx)
} }
func UpgradeActorsV2(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, ts *types.TipSet) (cid.Cid, error) { func UpgradeRefuel(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) {
store := sm.cs.Store(ctx)
tree, err := sm.StateTree(root)
if err != nil {
return cid.Undef, xerrors.Errorf("getting state tree: %w", err)
}
addr, err := address.NewFromString("t0122")
if err != nil {
return cid.Undef, xerrors.Errorf("getting address: %w", err)
}
err = resetMultisigVesting(ctx, store, tree, addr, 0, 0, big.Zero())
if err != nil {
return cid.Undef, xerrors.Errorf("tweaking msig vesting: %w", err)
}
err = resetMultisigVesting(ctx, store, tree, builtin.ReserveAddress, 0, 0, big.Zero())
if err != nil {
return cid.Undef, xerrors.Errorf("tweaking msig vesting: %w", err)
}
err = resetMultisigVesting(ctx, store, tree, builtin.RootVerifierAddress, 0, 0, big.Zero())
if err != nil {
return cid.Undef, xerrors.Errorf("tweaking msig vesting: %w", err)
}
return tree.Flush(ctx)
}
func UpgradeActorsV2(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) {
buf := bufbstore.NewTieredBstore(sm.cs.Blockstore(), bstore.NewTemporarySync()) buf := bufbstore.NewTieredBstore(sm.cs.Blockstore(), bstore.NewTemporarySync())
store := store.ActorStore(ctx, buf) store := store.ActorStore(ctx, buf)
epoch := ts.Height() - 1 info, err := store.Put(ctx, new(types.StateInfo0))
info, err := store.Put(ctx, new(types.StateInfo))
if err != nil { if err != nil {
return cid.Undef, xerrors.Errorf("failed to create new state info for actors v2: %w", err) return cid.Undef, xerrors.Errorf("failed to create new state info for actors v2: %w", err)
} }
@ -522,8 +594,7 @@ func UpgradeActorsV2(ctx context.Context, sm *StateManager, cb ExecCallback, roo
} }
newRoot, err := store.Put(ctx, &types.StateRoot{ newRoot, err := store.Put(ctx, &types.StateRoot{
// TODO: ActorUpgrade: should be state-tree specific, not just the actors version. Version: types.StateTreeVersion1,
Version: actors.Version2,
Actors: newHamtRoot, Actors: newHamtRoot,
Info: info, Info: info,
}) })
@ -554,7 +625,7 @@ func UpgradeActorsV2(ctx context.Context, sm *StateManager, cb ExecCallback, roo
return newRoot, nil return newRoot, nil
} }
func UpgradeLiftoff(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, ts *types.TipSet) (cid.Cid, error) { func UpgradeLiftoff(ctx context.Context, sm *StateManager, cb ExecCallback, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) {
tree, err := sm.StateTree(root) tree, err := sm.StateTree(root)
if err != nil { if err != nil {
return cid.Undef, xerrors.Errorf("getting state tree: %w", err) return cid.Undef, xerrors.Errorf("getting state tree: %w", err)
@ -712,6 +783,7 @@ func makeKeyAddr(splitAddr address.Address, count uint64) (address.Address, erro
return addr, nil return addr, nil
} }
// TODO: After the Liftoff epoch, refactor this to use resetMultisigVesting
func resetGenesisMsigs(ctx context.Context, sm *StateManager, store adt0.Store, tree *state.StateTree, startEpoch abi.ChainEpoch) error { func resetGenesisMsigs(ctx context.Context, sm *StateManager, store adt0.Store, tree *state.StateTree, startEpoch abi.ChainEpoch) error {
gb, err := sm.cs.GetGenesis() gb, err := sm.cs.GetGenesis()
if err != nil { if err != nil {
@ -761,3 +833,34 @@ func resetGenesisMsigs(ctx context.Context, sm *StateManager, store adt0.Store,
return nil return nil
} }
func resetMultisigVesting(ctx context.Context, store adt0.Store, tree *state.StateTree, addr address.Address, startEpoch abi.ChainEpoch, duration abi.ChainEpoch, balance abi.TokenAmount) error {
act, err := tree.GetActor(addr)
if err != nil {
return xerrors.Errorf("getting actor: %w", err)
}
if !builtin.IsMultisigActor(act.Code) {
return xerrors.Errorf("actor wasn't msig: %w", err)
}
var msigState multisig0.State
if err := store.Get(ctx, act.Head, &msigState); err != nil {
return xerrors.Errorf("reading multisig state: %w", err)
}
msigState.StartEpoch = startEpoch
msigState.UnlockDuration = duration
msigState.InitialBalance = balance
act.Head, err = store.Put(ctx, &msigState)
if err != nil {
return xerrors.Errorf("writing new multisig state: %w", err)
}
if err := tree.SetActor(addr, act); err != nil {
return xerrors.Errorf("setting multisig actor: %w", err)
}
return nil
}

View File

@ -10,8 +10,9 @@ import (
"github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/cbor" "github.com/filecoin-project/go-state-types/cbor"
"github.com/filecoin-project/specs-actors/actors/builtin" "github.com/filecoin-project/specs-actors/actors/builtin"
init_ "github.com/filecoin-project/specs-actors/actors/builtin/init" init0 "github.com/filecoin-project/specs-actors/actors/builtin/init"
"github.com/filecoin-project/specs-actors/actors/runtime" "github.com/filecoin-project/specs-actors/actors/runtime"
"github.com/stretchr/testify/require"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors"
@ -76,7 +77,7 @@ func (ta testActor) Exports() []interface{} {
func (ta *testActor) Constructor(rt runtime.Runtime, params *abi.EmptyValue) *abi.EmptyValue { func (ta *testActor) Constructor(rt runtime.Runtime, params *abi.EmptyValue) *abi.EmptyValue {
rt.ValidateImmediateCallerAcceptAny() rt.ValidateImmediateCallerAcceptAny()
rt.StateCreate(&testActorState{11}) rt.StateCreate(&testActorState{11})
fmt.Println("NEW ACTOR ADDRESS IS: ", rt.Receiver()) //fmt.Println("NEW ACTOR ADDRESS IS: ", rt.Receiver())
return abi.Empty return abi.Empty
} }
@ -120,7 +121,7 @@ func TestForkHeightTriggers(t *testing.T) {
Network: 1, Network: 1,
Height: testForkHeight, Height: testForkHeight,
Migration: func(ctx context.Context, sm *StateManager, cb ExecCallback, Migration: func(ctx context.Context, sm *StateManager, cb ExecCallback,
root cid.Cid, ts *types.TipSet) (cid.Cid, error) { root cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) {
cst := ipldcbor.NewCborStore(sm.ChainStore().Blockstore()) cst := ipldcbor.NewCborStore(sm.ChainStore().Blockstore())
st, err := sm.StateTree(root) st, err := sm.StateTree(root)
@ -173,7 +174,7 @@ func TestForkHeightTriggers(t *testing.T) {
var msgs []*types.SignedMessage var msgs []*types.SignedMessage
enc, err := actors.SerializeParams(&init_.ExecParams{CodeCID: (testActor{}).Code()}) enc, err := actors.SerializeParams(&init0.ExecParams{CodeCID: (testActor{}).Code()})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -233,3 +234,84 @@ func TestForkHeightTriggers(t *testing.T) {
} }
} }
} }
func TestForkRefuseCall(t *testing.T) {
logging.SetAllLoggers(logging.LevelInfo)
ctx := context.TODO()
cg, err := gen.NewGenerator()
if err != nil {
t.Fatal(err)
}
sm, err := NewStateManagerWithUpgradeSchedule(
cg.ChainStore(), UpgradeSchedule{{
Network: 1,
Expensive: true,
Height: testForkHeight,
Migration: func(ctx context.Context, sm *StateManager, cb ExecCallback,
root cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) {
return root, nil
}}})
if err != nil {
t.Fatal(err)
}
inv := vm.NewActorRegistry()
inv.Register(nil, testActor{})
sm.SetVMConstructor(func(ctx context.Context, vmopt *vm.VMOpts) (*vm.VM, error) {
nvm, err := vm.NewVM(ctx, vmopt)
if err != nil {
return nil, err
}
nvm.SetInvoker(inv)
return nvm, nil
})
cg.SetStateManager(sm)
enc, err := actors.SerializeParams(&init0.ExecParams{CodeCID: (testActor{}).Code()})
if err != nil {
t.Fatal(err)
}
m := &types.Message{
From: cg.Banker(),
To: lotusinit.Address,
Method: builtin.MethodsInit.Exec,
Params: enc,
GasLimit: types.TestGasLimit,
Value: types.NewInt(0),
GasPremium: types.NewInt(0),
GasFeeCap: types.NewInt(0),
}
for i := 0; i < 50; i++ {
ts, err := cg.NextTipSet()
if err != nil {
t.Fatal(err)
}
ret, err := sm.CallWithGas(ctx, m, nil, ts.TipSet.TipSet())
switch ts.TipSet.TipSet().Height() {
case testForkHeight, testForkHeight + 1:
// If I had a fork, or I _will_ have a fork, it should fail.
require.Equal(t, ErrExpensiveFork, err)
default:
require.NoError(t, err)
require.True(t, ret.MsgRct.ExitCode.IsSuccess())
}
// Call just runs on the parent state for a tipset, so we only
// expect an error at the fork height.
ret, err = sm.Call(ctx, m, ts.TipSet.TipSet())
switch ts.TipSet.TipSet().Height() {
case testForkHeight + 1:
require.Equal(t, ErrExpensiveFork, err)
default:
require.NoError(t, err)
require.True(t, ret.MsgRct.ExitCode.IsSuccess())
}
}
}

View File

@ -53,6 +53,10 @@ type StateManager struct {
// Maps chain epochs to upgrade functions. // Maps chain epochs to upgrade functions.
stateMigrations map[abi.ChainEpoch]UpgradeFunc stateMigrations map[abi.ChainEpoch]UpgradeFunc
// A set of potentially expensive/time consuming upgrades. Explicit
// calls for, e.g., gas estimation fail against this epoch with
// ErrExpensiveFork.
expensiveUpgrades map[abi.ChainEpoch]struct{}
stCache map[string][]cid.Cid stCache map[string][]cid.Cid
compWait map[string]chan struct{} compWait map[string]chan struct{}
@ -78,6 +82,7 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule
} }
stateMigrations := make(map[abi.ChainEpoch]UpgradeFunc, len(us)) stateMigrations := make(map[abi.ChainEpoch]UpgradeFunc, len(us))
expensiveUpgrades := make(map[abi.ChainEpoch]struct{}, len(us))
var networkVersions []versionSpec var networkVersions []versionSpec
lastVersion := network.Version0 lastVersion := network.Version0
if len(us) > 0 { if len(us) > 0 {
@ -87,6 +92,9 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule
if upgrade.Migration != nil { if upgrade.Migration != nil {
stateMigrations[upgrade.Height] = upgrade.Migration stateMigrations[upgrade.Height] = upgrade.Migration
} }
if upgrade.Expensive {
expensiveUpgrades[upgrade.Height] = struct{}{}
}
networkVersions = append(networkVersions, versionSpec{ networkVersions = append(networkVersions, versionSpec{
networkVersion: lastVersion, networkVersion: lastVersion,
atOrBelow: upgrade.Height, atOrBelow: upgrade.Height,
@ -102,6 +110,7 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule
networkVersions: networkVersions, networkVersions: networkVersions,
latestVersion: lastVersion, latestVersion: lastVersion,
stateMigrations: stateMigrations, stateMigrations: stateMigrations,
expensiveUpgrades: expensiveUpgrades,
newVM: vm.NewVM, newVM: vm.NewVM,
cs: cs, cs: cs,
stCache: make(map[string][]cid.Cid), stCache: make(map[string][]cid.Cid),

View File

@ -387,7 +387,7 @@ func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch,
NtwkVersion: sm.GetNtwkVersion, NtwkVersion: sm.GetNtwkVersion,
BaseFee: ts.Blocks()[0].ParentBaseFee, BaseFee: ts.Blocks()[0].ParentBaseFee,
} }
vmi, err := vm.NewVM(ctx, vmopt) vmi, err := sm.newVM(ctx, vmopt)
if err != nil { if err != nil {
return cid.Undef, nil, err return cid.Undef, nil, err
} }

View File

@ -1648,7 +1648,7 @@ func (t *StateRoot) MarshalCBOR(w io.Writer) error {
scratch := make([]byte, 9) scratch := make([]byte, 9)
// t.Version (uint64) (uint64) // t.Version (types.StateTreeVersion) (uint64)
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Version)); err != nil { if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Version)); err != nil {
return err return err
@ -1687,7 +1687,7 @@ func (t *StateRoot) UnmarshalCBOR(r io.Reader) error {
return fmt.Errorf("cbor input had wrong number of fields") return fmt.Errorf("cbor input had wrong number of fields")
} }
// t.Version (uint64) (uint64) // t.Version (types.StateTreeVersion) (uint64)
{ {
@ -1698,7 +1698,7 @@ func (t *StateRoot) UnmarshalCBOR(r io.Reader) error {
if maj != cbg.MajUnsignedInt { if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field") return fmt.Errorf("wrong type for uint64 field")
} }
t.Version = uint64(extra) t.Version = StateTreeVersion(extra)
} }
// t.Actors (cid.Cid) (struct) // t.Actors (cid.Cid) (struct)
@ -1728,22 +1728,22 @@ func (t *StateRoot) UnmarshalCBOR(r io.Reader) error {
return nil return nil
} }
var lengthBufStateInfo = []byte{128} var lengthBufStateInfo0 = []byte{128}
func (t *StateInfo) MarshalCBOR(w io.Writer) error { func (t *StateInfo0) MarshalCBOR(w io.Writer) error {
if t == nil { if t == nil {
_, err := w.Write(cbg.CborNull) _, err := w.Write(cbg.CborNull)
return err return err
} }
if _, err := w.Write(lengthBufStateInfo); err != nil { if _, err := w.Write(lengthBufStateInfo0); err != nil {
return err return err
} }
return nil return nil
} }
func (t *StateInfo) UnmarshalCBOR(r io.Reader) error { func (t *StateInfo0) UnmarshalCBOR(r io.Reader) error {
*t = StateInfo{} *t = StateInfo0{}
br := cbg.GetPeeker(r) br := cbg.GetPeeker(r)
scratch := make([]byte, 8) scratch := make([]byte, 8)

View File

@ -2,9 +2,20 @@ package types
import "github.com/ipfs/go-cid" import "github.com/ipfs/go-cid"
// StateTreeVersion is the version of the state tree itself, independent of the
// network version or the actors version.
type StateTreeVersion uint64
const (
// StateTreeVersion0 corresponds to actors < v2.
StateTreeVersion0 StateTreeVersion = iota
// StateTreeVersion1 corresponds to actors >= v2.
StateTreeVersion1
)
type StateRoot struct { type StateRoot struct {
// State root version. Versioned along with actors (for now). // State tree version.
Version uint64 Version StateTreeVersion
// Actors tree. The structure depends on the state root version. // Actors tree. The structure depends on the state root version.
Actors cid.Cid Actors cid.Cid
// Info. The structure depends on the state root version. // Info. The structure depends on the state root version.
@ -12,4 +23,4 @@ type StateRoot struct {
} }
// TODO: version this. // TODO: version this.
type StateInfo struct{} type StateInfo0 struct{}

View File

@ -70,12 +70,12 @@ func (ar *ActorRegistry) Invoke(codeCid cid.Cid, rt vmr.Runtime, method abi.Meth
log.Errorf("no code for actor %s (Addr: %s)", codeCid, rt.Receiver()) log.Errorf("no code for actor %s (Addr: %s)", codeCid, rt.Receiver())
return nil, aerrors.Newf(exitcode.SysErrorIllegalActor, "no code for actor %s(%d)(%s)", codeCid, method, hex.EncodeToString(params)) return nil, aerrors.Newf(exitcode.SysErrorIllegalActor, "no code for actor %s(%d)(%s)", codeCid, method, hex.EncodeToString(params))
} }
if err := act.predicate(rt, act.vmActor); err != nil {
return nil, aerrors.Newf(exitcode.SysErrorIllegalActor, "unsupported actor: %s", err)
}
if method >= abi.MethodNum(len(act.methods)) || act.methods[method] == nil { if method >= abi.MethodNum(len(act.methods)) || act.methods[method] == nil {
return nil, aerrors.Newf(exitcode.SysErrInvalidMethod, "no method %d on actor", method) return nil, aerrors.Newf(exitcode.SysErrInvalidMethod, "no method %d on actor", method)
} }
if err := act.predicate(rt, act.vmActor); err != nil {
return nil, aerrors.Newf(exitcode.SysErrInvalidMethod, "unsupported actor: %s", err)
}
return act.methods[method](rt, params) return act.methods[method](rt, params)
} }

View File

@ -49,6 +49,9 @@ func (m *Message) ValueReceived() abi.TokenAmount {
return m.msg.Value return m.msg.Value
} }
// EnableGasTracing, if true, outputs gas tracing in execution traces.
var EnableGasTracing = false
type Runtime struct { type Runtime struct {
rt0.Message rt0.Message
rt0.Syscalls rt0.Syscalls
@ -477,7 +480,7 @@ func (rt *Runtime) stateCommit(oldh, newh cid.Cid) aerrors.ActorError {
} }
func (rt *Runtime) finilizeGasTracing() { func (rt *Runtime) finilizeGasTracing() {
if enableTracing { if EnableGasTracing {
if rt.lastGasCharge != nil { if rt.lastGasCharge != nil {
rt.lastGasCharge.TimeTaken = time.Since(rt.lastGasChargeTime) rt.lastGasCharge.TimeTaken = time.Since(rt.lastGasChargeTime)
} }
@ -509,11 +512,9 @@ func (rt *Runtime) chargeGasFunc(skip int) func(GasCharge) {
} }
var enableTracing = false
func (rt *Runtime) chargeGasInternal(gas GasCharge, skip int) aerrors.ActorError { func (rt *Runtime) chargeGasInternal(gas GasCharge, skip int) aerrors.ActorError {
toUse := gas.Total() toUse := gas.Total()
if enableTracing { if EnableGasTracing {
var callers [10]uintptr var callers [10]uintptr
cout := 0 //gruntime.Callers(2+skip, callers[:]) cout := 0 //gruntime.Callers(2+skip, callers[:])

View File

@ -45,3 +45,23 @@ func TestRuntimePutErrors(t *testing.T) {
rt.StorePut(&NotAVeryGoodMarshaler{}) rt.StorePut(&NotAVeryGoodMarshaler{})
t.Error("expected panic") t.Error("expected panic")
} }
func BenchmarkRuntime_CreateRuntimeChargeGas_TracingDisabled(b *testing.B) {
var (
cst = cbor.NewCborStore(nil)
gch = newGasCharge("foo", 1000, 1000)
)
b.ResetTimer()
EnableGasTracing = false
noop := func() bool { return EnableGasTracing }
for n := 0; n < b.N; n++ {
// flip the value and access it to make sure
// the compiler doesn't optimize away
EnableGasTracing = true
_ = noop()
EnableGasTracing = false
_ = (&Runtime{cst: cst}).chargeGasInternal(gch, 0)
}
}

View File

@ -236,7 +236,7 @@ func (vm *VM) send(ctx context.Context, msg *types.Message, parent *Runtime,
} }
rt := vm.makeRuntime(ctx, msg, origin, on, gasUsed, nac) rt := vm.makeRuntime(ctx, msg, origin, on, gasUsed, nac)
if enableTracing { if EnableGasTracing {
rt.lastGasChargeTime = start rt.lastGasChargeTime = start
if parent != nil { if parent != nil {
rt.lastGasChargeTime = parent.lastGasChargeTime rt.lastGasChargeTime = parent.lastGasChargeTime

View File

@ -72,6 +72,7 @@ var stateCmd = &cli.Command{
stateMsgCostCmd, stateMsgCostCmd,
stateMinerInfo, stateMinerInfo,
stateMarketCmd, stateMarketCmd,
stateExecTraceCmd,
}, },
} }
@ -315,6 +316,74 @@ var stateActiveSectorsCmd = &cli.Command{
}, },
} }
var stateExecTraceCmd = &cli.Command{
Name: "exec-trace",
Usage: "Get the execution trace of a given message",
ArgsUsage: "<messageCid>",
Action: func(cctx *cli.Context) error {
if !cctx.Args().Present() {
return ShowHelp(cctx, fmt.Errorf("must pass message cid"))
}
mcid, err := cid.Decode(cctx.Args().First())
if err != nil {
return fmt.Errorf("message cid was invalid: %s", err)
}
capi, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
msg, err := capi.ChainGetMessage(ctx, mcid)
if err != nil {
return err
}
lookup, err := capi.StateSearchMsg(ctx, mcid)
if err != nil {
return err
}
ts, err := capi.ChainGetTipSet(ctx, lookup.TipSet)
if err != nil {
return err
}
pts, err := capi.ChainGetTipSet(ctx, ts.Parents())
if err != nil {
return err
}
cso, err := capi.StateCompute(ctx, pts.Height(), nil, pts.Key())
if err != nil {
return err
}
var trace *api.InvocResult
for _, t := range cso.Trace {
if t.Msg.From == msg.From && t.Msg.Nonce == msg.Nonce {
trace = t
break
}
}
if trace == nil {
return fmt.Errorf("failed to find message in tipset trace output")
}
out, err := json.MarshalIndent(trace, "", " ")
if err != nil {
return err
}
fmt.Println(string(out))
return nil
},
}
var stateReplaySetCmd = &cli.Command{ var stateReplaySetCmd = &cli.Command{
Name: "replay", Name: "replay",
Usage: "Replay a particular message within a tipset", Usage: "Replay a particular message within a tipset",

View File

@ -37,6 +37,7 @@ import (
"github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors/builtin/market"
"github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/builtin/miner"
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/tools/stats" "github.com/filecoin-project/lotus/tools/stats"
@ -342,6 +343,18 @@ var runCmd = &cli.Command{
Usage: "process ProveCommitSector messages", Usage: "process ProveCommitSector messages",
Value: true, Value: true,
}, },
&cli.BoolFlag{
Name: "windowed-post",
EnvVars: []string{"LOTUS_PCR_WINDOWED_POST"},
Usage: "process SubmitWindowedPoSt messages and refund gas fees",
Value: false,
},
&cli.BoolFlag{
Name: "storage-deals",
EnvVars: []string{"LOTUS_PCR_STORAGE_DEALS"},
Usage: "process PublishStorageDeals messages and refund gas fees",
Value: false,
},
&cli.IntFlag{ &cli.IntFlag{
Name: "head-delay", Name: "head-delay",
EnvVars: []string{"LOTUS_PCR_HEAD_DELAY"}, EnvVars: []string{"LOTUS_PCR_HEAD_DELAY"},
@ -378,6 +391,18 @@ var runCmd = &cli.Command{
Usage: "percent of refund to issue", Usage: "percent of refund to issue",
Value: 110, Value: 110,
}, },
&cli.StringFlag{
Name: "pre-fee-cap-max",
EnvVars: []string{"LOTUS_PCR_PRE_FEE_CAP_MAX"},
Usage: "messages with a fee cap larger than this will be skipped when processing pre commit messages",
Value: "0.0000000001",
},
&cli.StringFlag{
Name: "prove-fee-cap-max",
EnvVars: []string{"LOTUS_PCR_PROVE_FEE_CAP_MAX"},
Usage: "messages with a prove cap larger than this will be skipped when processing pre commit messages",
Value: "0.0000000001",
},
}, },
Action: func(cctx *cli.Context) error { Action: func(cctx *cli.Context) error {
go func() { go func() {
@ -421,6 +446,8 @@ var runCmd = &cli.Command{
dryRun := cctx.Bool("dry-run") dryRun := cctx.Bool("dry-run")
preCommitEnabled := cctx.Bool("pre-commit") preCommitEnabled := cctx.Bool("pre-commit")
proveCommitEnabled := cctx.Bool("prove-commit") proveCommitEnabled := cctx.Bool("prove-commit")
windowedPoStEnabled := cctx.Bool("windowed-post")
publishStorageDealsEnabled := cctx.Bool("storage-deals")
aggregateTipsets := cctx.Int("aggregate-tipsets") aggregateTipsets := cctx.Int("aggregate-tipsets")
minerRecoveryEnabled := cctx.Bool("miner-recovery") minerRecoveryEnabled := cctx.Bool("miner-recovery")
minerRecoveryPeriod := abi.ChainEpoch(int64(cctx.Int("miner-recovery-period"))) minerRecoveryPeriod := abi.ChainEpoch(int64(cctx.Int("miner-recovery-period")))
@ -428,6 +455,16 @@ var runCmd = &cli.Command{
minerRecoveryCutoff := uint64(cctx.Int("miner-recovery-cutoff")) minerRecoveryCutoff := uint64(cctx.Int("miner-recovery-cutoff"))
minerRecoveryBonus := uint64(cctx.Int("miner-recovery-bonus")) minerRecoveryBonus := uint64(cctx.Int("miner-recovery-bonus"))
preFeeCapMax, err := types.ParseFIL(cctx.String("pre-fee-cap-max"))
if err != nil {
return err
}
proveFeeCapMax, err := types.ParseFIL(cctx.String("prove-fee-cap-max"))
if err != nil {
return err
}
rf := &refunder{ rf := &refunder{
api: api, api: api,
wallet: from, wallet: from,
@ -438,6 +475,10 @@ var runCmd = &cli.Command{
dryRun: dryRun, dryRun: dryRun,
preCommitEnabled: preCommitEnabled, preCommitEnabled: preCommitEnabled,
proveCommitEnabled: proveCommitEnabled, proveCommitEnabled: proveCommitEnabled,
windowedPoStEnabled: windowedPoStEnabled,
publishStorageDealsEnabled: publishStorageDealsEnabled,
preFeeCapMax: types.BigInt(preFeeCapMax),
proveFeeCapMax: types.BigInt(proveFeeCapMax),
} }
var refunds *MinersRefund = NewMinersRefund() var refunds *MinersRefund = NewMinersRefund()
@ -589,7 +630,12 @@ type refunder struct {
dryRun bool dryRun bool
preCommitEnabled bool preCommitEnabled bool
proveCommitEnabled bool proveCommitEnabled bool
windowedPoStEnabled bool
publishStorageDealsEnabled bool
threshold big.Int threshold big.Int
preFeeCapMax big.Int
proveFeeCapMax big.Int
} }
func (r *refunder) FindMiners(ctx context.Context, tipset *types.TipSet, refunds *MinersRefund, owner, worker, control bool) (*MinersRefund, error) { func (r *refunder) FindMiners(ctx context.Context, tipset *types.TipSet, refunds *MinersRefund, owner, worker, control bool) (*MinersRefund, error) {
@ -817,6 +863,147 @@ func (r *refunder) EnsureMinerMinimums(ctx context.Context, tipset *types.TipSet
return refunds, nil return refunds, nil
} }
func (r *refunder) processTipsetStorageMarketActor(ctx context.Context, tipset *types.TipSet, msg api.Message, recp *types.MessageReceipt) (bool, string, types.BigInt, error) {
m := msg.Message
refundValue := types.NewInt(0)
var messageMethod string
switch m.Method {
case builtin0.MethodsMarket.PublishStorageDeals:
if !r.publishStorageDealsEnabled {
return false, messageMethod, types.NewInt(0), nil
}
messageMethod = "PublishStorageDeals"
if recp.ExitCode != exitcode.Ok {
log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recp.ExitCode)
return false, messageMethod, types.NewInt(0), nil
}
refundValue = types.BigMul(types.NewInt(uint64(recp.GasUsed)), tipset.Blocks()[0].ParentBaseFee)
}
return true, messageMethod, refundValue, nil
}
func (r *refunder) processTipsetStorageMinerActor(ctx context.Context, tipset *types.TipSet, msg api.Message, recp *types.MessageReceipt) (bool, string, types.BigInt, error) {
m := msg.Message
refundValue := types.NewInt(0)
var messageMethod string
switch m.Method {
case builtin0.MethodsMiner.SubmitWindowedPoSt:
if !r.windowedPoStEnabled {
return false, messageMethod, types.NewInt(0), nil
}
messageMethod = "SubmitWindowedPoSt"
if recp.ExitCode != exitcode.Ok {
log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recp.ExitCode)
return false, messageMethod, types.NewInt(0), nil
}
refundValue = types.BigMul(types.NewInt(uint64(recp.GasUsed)), tipset.Blocks()[0].ParentBaseFee)
case builtin0.MethodsMiner.ProveCommitSector:
if !r.proveCommitEnabled {
return false, messageMethod, types.NewInt(0), nil
}
messageMethod = "ProveCommitSector"
if recp.ExitCode != exitcode.Ok {
log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recp.ExitCode)
return false, messageMethod, types.NewInt(0), nil
}
if m.GasFeeCap.GreaterThan(r.proveFeeCapMax) {
log.Debugw("skipping high fee cap message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "gas_fee_cap", m.GasFeeCap, "fee_cap_max", r.proveFeeCapMax)
return false, messageMethod, types.NewInt(0), nil
}
var sn abi.SectorNumber
var proveCommitSector miner0.ProveCommitSectorParams
if err := proveCommitSector.UnmarshalCBOR(bytes.NewBuffer(m.Params)); err != nil {
log.Warnw("failed to decode provecommit params", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To)
return false, messageMethod, types.NewInt(0), nil
}
sn = proveCommitSector.SectorNumber
// We use the parent tipset key because precommit information is removed when ProveCommitSector is executed
precommitChainInfo, err := r.api.StateSectorPreCommitInfo(ctx, m.To, sn, tipset.Parents())
if err != nil {
log.Warnw("failed to get precommit info for sector", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn)
return false, messageMethod, types.NewInt(0), nil
}
precommitTipset, err := r.api.ChainGetTipSetByHeight(ctx, precommitChainInfo.PreCommitEpoch, tipset.Key())
if err != nil {
log.Warnf("failed to lookup precommit epoch", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn)
return false, messageMethod, types.NewInt(0), nil
}
collateral, err := r.api.StateMinerInitialPledgeCollateral(ctx, m.To, precommitChainInfo.Info, precommitTipset.Key())
if err != nil {
log.Warnw("failed to get initial pledge collateral", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn)
return false, messageMethod, types.NewInt(0), nil
}
collateral = big.Sub(collateral, precommitChainInfo.PreCommitDeposit)
if collateral.LessThan(big.Zero()) {
log.Debugw("skipping zero pledge collateral difference", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn)
return false, messageMethod, types.NewInt(0), nil
}
refundValue = collateral
if r.refundPercent > 0 {
refundValue = types.BigMul(types.BigDiv(refundValue, types.NewInt(100)), types.NewInt(uint64(r.refundPercent)))
}
case builtin0.MethodsMiner.PreCommitSector:
if !r.preCommitEnabled {
return false, messageMethod, types.NewInt(0), nil
}
messageMethod = "PreCommitSector"
if recp.ExitCode != exitcode.Ok {
log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recp.ExitCode)
return false, messageMethod, types.NewInt(0), nil
}
if m.GasFeeCap.GreaterThan(r.preFeeCapMax) {
log.Debugw("skipping high fee cap message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "gas_fee_cap", m.GasFeeCap, "fee_cap_max", r.preFeeCapMax)
return false, messageMethod, types.NewInt(0), nil
}
var precommitInfo miner.SectorPreCommitInfo
if err := precommitInfo.UnmarshalCBOR(bytes.NewBuffer(m.Params)); err != nil {
log.Warnw("failed to decode precommit params", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To)
return false, messageMethod, types.NewInt(0), nil
}
collateral, err := r.api.StateMinerInitialPledgeCollateral(ctx, m.To, precommitInfo, tipset.Key())
if err != nil {
log.Warnw("failed to calculate initial pledge collateral", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", precommitInfo.SectorNumber)
return false, messageMethod, types.NewInt(0), nil
}
refundValue = collateral
if r.refundPercent > 0 {
refundValue = types.BigMul(types.BigDiv(refundValue, types.NewInt(100)), types.NewInt(uint64(r.refundPercent)))
}
default:
return false, messageMethod, types.NewInt(0), nil
}
return true, messageMethod, refundValue, nil
}
func (r *refunder) ProcessTipset(ctx context.Context, tipset *types.TipSet, refunds *MinersRefund) (*MinersRefund, error) { func (r *refunder) ProcessTipset(ctx context.Context, tipset *types.TipSet, refunds *MinersRefund) (*MinersRefund, error) {
cids := tipset.Cids() cids := tipset.Cids()
if len(cids) == 0 { if len(cids) == 0 {
@ -852,91 +1039,28 @@ func (r *refunder) ProcessTipset(ctx context.Context, tipset *types.TipSet, refu
continue continue
} }
if !builtin.IsStorageMinerActor(a.Code) {
continue
}
var messageMethod string var messageMethod string
switch m.Method { if m.To == market.Address {
case builtin0.MethodsMiner.ProveCommitSector: var err error
if !r.proveCommitEnabled { var processed bool
continue processed, messageMethod, refundValue, err = r.processTipsetStorageMarketActor(ctx, tipset, msg, recps[i])
}
messageMethod = "ProveCommitSector"
if recps[i].ExitCode != exitcode.Ok {
log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recps[i].ExitCode)
continue
}
var sn abi.SectorNumber
var proveCommitSector miner0.ProveCommitSectorParams
if err := proveCommitSector.UnmarshalCBOR(bytes.NewBuffer(m.Params)); err != nil {
log.Warnw("failed to decode provecommit params", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To)
continue
}
sn = proveCommitSector.SectorNumber
// We use the parent tipset key because precommit information is removed when ProveCommitSector is executed
precommitChainInfo, err := r.api.StateSectorPreCommitInfo(ctx, m.To, sn, tipset.Parents())
if err != nil { if err != nil {
log.Warnw("failed to get precommit info for sector", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn)
continue continue
} }
if !processed {
precommitTipset, err := r.api.ChainGetTipSetByHeight(ctx, precommitChainInfo.PreCommitEpoch, tipset.Key()) continue
}
} else if builtin.IsStorageMinerActor(a.Code) {
var err error
var processed bool
processed, messageMethod, refundValue, err = r.processTipsetStorageMinerActor(ctx, tipset, msg, recps[i])
if err != nil { if err != nil {
log.Warnf("failed to lookup precommit epoch", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn)
continue continue
} }
if !processed {
collateral, err := r.api.StateMinerInitialPledgeCollateral(ctx, m.To, precommitChainInfo.Info, precommitTipset.Key())
if err != nil {
log.Warnw("failed to get initial pledge collateral", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn)
}
collateral = big.Sub(collateral, precommitChainInfo.PreCommitDeposit)
if collateral.LessThan(big.Zero()) {
log.Debugw("skipping zero pledge collateral difference", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn)
continue continue
} }
refundValue = collateral
case builtin0.MethodsMiner.PreCommitSector:
if !r.preCommitEnabled {
continue
}
messageMethod = "PreCommitSector"
if recps[i].ExitCode != exitcode.Ok {
log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recps[i].ExitCode)
continue
}
var precommitInfo miner.SectorPreCommitInfo
if err := precommitInfo.UnmarshalCBOR(bytes.NewBuffer(m.Params)); err != nil {
log.Warnw("failed to decode precommit params", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To)
continue
}
collateral, err := r.api.StateMinerInitialPledgeCollateral(ctx, m.To, precommitInfo, tipset.Key())
if err != nil {
log.Warnw("failed to calculate initial pledge collateral", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", precommitInfo.SectorNumber)
continue
}
refundValue = collateral
default:
continue
}
if r.refundPercent > 0 {
refundValue = types.BigMul(types.BigDiv(refundValue, types.NewInt(100)), types.NewInt(uint64(r.refundPercent)))
} }
log.Debugw( log.Debugw(

View File

@ -5,6 +5,8 @@ import (
"math" "math"
"github.com/filecoin-project/go-address" "github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
@ -32,6 +34,10 @@ var noncefix = &cli.Command{
&cli.BoolFlag{ &cli.BoolFlag{
Name: "auto", Name: "auto",
}, },
&cli.Int64Flag{
Name: "gas-fee-cap",
Usage: "specify gas fee cap for nonce filling messages",
},
}, },
Action: func(cctx *cli.Context) error { Action: func(cctx *cli.Context) error {
api, closer, err := lcli.GetFullNodeAPI(cctx) api, closer, err := lcli.GetFullNodeAPI(cctx)
@ -84,15 +90,32 @@ var noncefix = &cli.Command{
} }
fmt.Printf("Creating %d filler messages (%d ~ %d)\n", end-start, start, end) fmt.Printf("Creating %d filler messages (%d ~ %d)\n", end-start, start, end)
ts, err := api.ChainHead(ctx)
if err != nil {
return err
}
feeCap := big.Mul(ts.Blocks()[0].ParentBaseFee, big.NewInt(2)) // default fee cap to 2 * parent base fee
if fcf := cctx.Int64("gas-fee-cap"); fcf != 0 {
feeCap = abi.NewTokenAmount(fcf)
}
for i := start; i < end; i++ { for i := start; i < end; i++ {
msg := &types.Message{ msg := &types.Message{
From: addr, From: addr,
To: addr, To: addr,
Value: types.NewInt(1), Value: types.NewInt(0),
Nonce: i, Nonce: i,
GasLimit: 1000000,
GasFeeCap: feeCap,
GasPremium: abi.NewTokenAmount(5),
}
smsg, err := api.WalletSignMessage(ctx, addr, msg)
if err != nil {
return err
} }
_, err = api.MpoolPushMessage(ctx, msg, nil) _, err = api.MpoolPush(ctx, smsg)
if err != nil { if err != nil {
return err return err
} }

View File

@ -31,6 +31,10 @@ const metaFile = "sectorstore.json"
var storageCmd = &cli.Command{ var storageCmd = &cli.Command{
Name: "storage", Name: "storage",
Usage: "manage sector storage", Usage: "manage sector storage",
Description: `Sectors can be stored across many filesystem paths. These
commands provide ways to manage the storage the miner will used to store sectors
long term for proving (references as 'store') as well as how sectors will be
stored while moving through the sealing pipeline (references as 'seal').`,
Subcommands: []*cli.Command{ Subcommands: []*cli.Command{
storageAttachCmd, storageAttachCmd,
storageListCmd, storageListCmd,
@ -41,6 +45,25 @@ var storageCmd = &cli.Command{
var storageAttachCmd = &cli.Command{ var storageAttachCmd = &cli.Command{
Name: "attach", Name: "attach",
Usage: "attach local storage path", Usage: "attach local storage path",
Description: `Storage can be attached to the miner using this command. The storage volume
list is stored local to the miner in $LOTUS_MINER_PATH/storage.json. We do not
recommend manually modifying this value without further understanding of the
storage system.
Each storage volume contains a configuration file which describes the
capabilities of the volume. When the '--init' flag is provided, this file will
be created using the additional flags.
Weight
A high weight value means data will be more likely to be stored in this path
Seal
Data for the sealing process will be stored here
Store
Finalized sectors that will be moved here for long term storage and be proven
over time
`,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{
Name: "init", Name: "init",

View File

@ -198,8 +198,8 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error {
Preroot: root, Preroot: root,
Epoch: execTs.Height(), Epoch: execTs.Height(),
Message: m, Message: m,
CircSupply: &circSupplyDetail.FilCirculating, CircSupply: circSupplyDetail.FilCirculating,
BaseFee: &basefee, BaseFee: basefee,
}) })
if err != nil { if err != nil {
return fmt.Errorf("failed to execute precursor message: %w", err) return fmt.Errorf("failed to execute precursor message: %w", err)
@ -229,8 +229,8 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error {
Preroot: preroot, Preroot: preroot,
Epoch: execTs.Height(), Epoch: execTs.Height(),
Message: msg, Message: msg,
CircSupply: &circSupplyDetail.FilCirculating, CircSupply: circSupplyDetail.FilCirculating,
BaseFee: &basefee, BaseFee: basefee,
}) })
if err != nil { if err != nil {
return fmt.Errorf("failed to execute message: %w", err) return fmt.Errorf("failed to execute message: %w", err)
@ -260,8 +260,8 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error {
Preroot: preroot, Preroot: preroot,
Epoch: execTs.Height(), Epoch: execTs.Height(),
Message: msg, Message: msg,
CircSupply: &circSupplyDetail.FilCirculating, CircSupply: circSupplyDetail.FilCirculating,
BaseFee: &basefee, BaseFee: basefee,
}) })
if err != nil { if err != nil {
return fmt.Errorf("failed to execute message: %w", err) return fmt.Errorf("failed to execute message: %w", err)

View File

@ -2,6 +2,7 @@ package conformance
import ( import (
"context" "context"
gobig "math/big"
"os" "os"
"github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/state"
@ -14,6 +15,7 @@ import (
"github.com/filecoin-project/lotus/lib/blockstore" "github.com/filecoin-project/lotus/lib/blockstore"
"github.com/filecoin-project/go-state-types/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/go-state-types/crypto"
"github.com/filecoin-project/test-vectors/schema" "github.com/filecoin-project/test-vectors/schema"
@ -80,7 +82,7 @@ type ExecuteTipsetResult struct {
func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, preroot cid.Cid, parentEpoch abi.ChainEpoch, tipset *schema.Tipset) (*ExecuteTipsetResult, error) { func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, preroot cid.Cid, parentEpoch abi.ChainEpoch, tipset *schema.Tipset) (*ExecuteTipsetResult, error) {
var ( var (
syscalls = mkFakedSigSyscalls(vm.Syscalls(ffiwrapper.ProofVerifier)) syscalls = mkFakedSigSyscalls(vm.Syscalls(ffiwrapper.ProofVerifier))
vmRand = new(testRand) vmRand = NewFixedRand()
cs = store.NewChainStore(bs, ds, syscalls) cs = store.NewChainStore(bs, ds, syscalls)
sm = stmgr.NewStateManager(cs) sm = stmgr.NewStateManager(cs)
@ -143,8 +145,12 @@ type ExecuteMessageParams struct {
Preroot cid.Cid Preroot cid.Cid
Epoch abi.ChainEpoch Epoch abi.ChainEpoch
Message *types.Message Message *types.Message
CircSupply *abi.TokenAmount CircSupply abi.TokenAmount
BaseFee *abi.TokenAmount BaseFee abi.TokenAmount
// Rand is an optional vm.Rand implementation to use. If nil, the driver
// will use a vm.Rand that returns a fixed value for all calls.
Rand vm.Rand
} }
// ExecuteMessage executes a conformance test vector message in a temporary VM. // ExecuteMessage executes a conformance test vector message in a temporary VM.
@ -155,14 +161,8 @@ func (d *Driver) ExecuteMessage(bs blockstore.Blockstore, params ExecuteMessageP
_ = os.Setenv("LOTUS_DISABLE_VM_BUF", "iknowitsabadidea") _ = os.Setenv("LOTUS_DISABLE_VM_BUF", "iknowitsabadidea")
} }
basefee := DefaultBaseFee if params.Rand == nil {
if params.BaseFee != nil { params.Rand = NewFixedRand()
basefee = *params.BaseFee
}
circSupply := DefaultCirculatingSupply
if params.CircSupply != nil {
circSupply = *params.CircSupply
} }
// dummy state manager; only to reference the GetNetworkVersion method, // dummy state manager; only to reference the GetNetworkVersion method,
@ -172,13 +172,13 @@ func (d *Driver) ExecuteMessage(bs blockstore.Blockstore, params ExecuteMessageP
vmOpts := &vm.VMOpts{ vmOpts := &vm.VMOpts{
StateBase: params.Preroot, StateBase: params.Preroot,
Epoch: params.Epoch, Epoch: params.Epoch,
Rand: &testRand{}, // TODO always succeeds; need more flexibility.
Bstore: bs, Bstore: bs,
Syscalls: mkFakedSigSyscalls(vm.Syscalls(ffiwrapper.ProofVerifier)), // TODO always succeeds; need more flexibility. Syscalls: mkFakedSigSyscalls(vm.Syscalls(ffiwrapper.ProofVerifier)), // TODO always succeeds; need more flexibility.
CircSupplyCalc: func(_ context.Context, _ abi.ChainEpoch, _ *state.StateTree) (abi.TokenAmount, error) { CircSupplyCalc: func(_ context.Context, _ abi.ChainEpoch, _ *state.StateTree) (abi.TokenAmount, error) {
return circSupply, nil return params.CircSupply, nil
}, },
BaseFee: basefee, Rand: params.Rand,
BaseFee: params.BaseFee,
NtwkVersion: sm.GetNtwkVersion, NtwkVersion: sm.GetNtwkVersion,
} }
@ -231,3 +231,22 @@ func toChainMsg(msg *types.Message) (ret types.ChainMsg) {
} }
return ret return ret
} }
// BaseFeeOrDefault converts a basefee as passed in a test vector (go *big.Int
// type) to an abi.TokenAmount, or if nil it returns the DefaultBaseFee.
func BaseFeeOrDefault(basefee *gobig.Int) abi.TokenAmount {
if basefee == nil {
return DefaultBaseFee
}
return big.NewFromGo(basefee)
}
// CircSupplyOrDefault converts a circulating supply as passed in a test vector
// (go *big.Int type) to an abi.TokenAmount, or if nil it returns the
// DefaultCirculatingSupply.
func CircSupplyOrDefault(circSupply *gobig.Int) abi.TokenAmount {
if circSupply == nil {
return DefaultBaseFee
}
return big.NewFromGo(circSupply)
}

28
conformance/rand_fixed.go Normal file
View File

@ -0,0 +1,28 @@
package conformance
import (
"context"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/crypto"
"github.com/filecoin-project/lotus/chain/vm"
)
type fixedRand struct{}
var _ vm.Rand = (*fixedRand)(nil)
// NewFixedRand creates a test vm.Rand that always returns fixed bytes value
// of utf-8 string 'i_am_random_____i_am_random_____'.
func NewFixedRand() vm.Rand {
return &fixedRand{}
}
func (r *fixedRand) GetChainRandomness(_ context.Context, _ crypto.DomainSeparationTag, _ abi.ChainEpoch, _ []byte) ([]byte, error) {
return []byte("i_am_random_____i_am_random_____"), nil // 32 bytes.
}
func (r *fixedRand) GetBeaconRandomness(_ context.Context, _ crypto.DomainSeparationTag, _ abi.ChainEpoch, _ []byte) ([]byte, error) {
return []byte("i_am_random_____i_am_random_____"), nil // 32 bytes.
}

View File

@ -13,9 +13,7 @@ import (
"github.com/fatih/color" "github.com/fatih/color"
"github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/go-state-types/exitcode"
"github.com/filecoin-project/test-vectors/schema"
"github.com/ipfs/go-blockservice" "github.com/ipfs/go-blockservice"
"github.com/ipfs/go-cid" "github.com/ipfs/go-cid"
ds "github.com/ipfs/go-datastore" ds "github.com/ipfs/go-datastore"
@ -24,6 +22,8 @@ import (
"github.com/ipfs/go-merkledag" "github.com/ipfs/go-merkledag"
"github.com/ipld/go-car" "github.com/ipld/go-car"
"github.com/filecoin-project/test-vectors/schema"
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm" "github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/lotus/lib/blockstore" "github.com/filecoin-project/lotus/lib/blockstore"
@ -46,18 +46,6 @@ func ExecuteMessageVector(r Reporter, vector *schema.TestVector) {
// Create a new Driver. // Create a new Driver.
driver := NewDriver(ctx, vector.Selector, DriverOpts{DisableVMFlush: true}) driver := NewDriver(ctx, vector.Selector, DriverOpts{DisableVMFlush: true})
var circSupply *abi.TokenAmount
if cs := vector.Pre.CircSupply; cs != nil {
ta := big.NewFromGo(cs)
circSupply = &ta
}
var basefee *abi.TokenAmount
if bf := vector.Pre.BaseFee; bf != nil {
ta := big.NewFromGo(bf)
basefee = &ta
}
// Apply every message. // Apply every message.
for i, m := range vector.ApplyMessages { for i, m := range vector.ApplyMessages {
msg, err := types.DecodeMessage(m.Bytes) msg, err := types.DecodeMessage(m.Bytes)
@ -76,8 +64,8 @@ func ExecuteMessageVector(r Reporter, vector *schema.TestVector) {
Preroot: root, Preroot: root,
Epoch: abi.ChainEpoch(epoch), Epoch: abi.ChainEpoch(epoch),
Message: msg, Message: msg,
CircSupply: circSupply, BaseFee: BaseFeeOrDefault(vector.Pre.BaseFee),
BaseFee: basefee, CircSupply: CircSupplyOrDefault(vector.Pre.CircSupply),
}) })
if err != nil { if err != nil {
r.Fatalf("fatal failure when executing message: %s", err) r.Fatalf("fatal failure when executing message: %s", err)

View File

@ -6,28 +6,16 @@ import (
"github.com/filecoin-project/specs-actors/actors/runtime/proof" "github.com/filecoin-project/specs-actors/actors/runtime/proof"
"github.com/filecoin-project/go-address" "github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/vm" "github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/crypto"
"github.com/filecoin-project/specs-actors/actors/runtime" "github.com/filecoin-project/specs-actors/actors/runtime"
cbor "github.com/ipfs/go-ipld-cbor" cbor "github.com/ipfs/go-ipld-cbor"
) )
type testRand struct{}
var _ vm.Rand = (*testRand)(nil)
func (r *testRand) GetChainRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) {
return []byte("i_am_random_____i_am_random_____"), nil // 32 bytes.
}
func (r *testRand) GetBeaconRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) {
return []byte("i_am_random_____i_am_random_____"), nil // 32 bytes.
}
type testSyscalls struct { type testSyscalls struct {
runtime.Syscalls runtime.Syscalls
} }

View File

@ -31,9 +31,14 @@ type StoragePath struct {
// LocalStorageMeta [path]/sectorstore.json // LocalStorageMeta [path]/sectorstore.json
type LocalStorageMeta struct { type LocalStorageMeta struct {
ID ID ID ID
// A high weight means data is more likely to be stored in this path
Weight uint64 // 0 = readonly Weight uint64 // 0 = readonly
// Intermediate data for the sealing process will be stored here
CanSeal bool CanSeal bool
// Finalized sectors that will be proved over time will be stored here
CanStore bool CanStore bool
} }

2
extern/test-vectors vendored

@ -1 +1 @@
Subproject commit 3a6e0b5e069b1452ce1a032aa315354d645f3ec4 Subproject commit 7471e2805fc3e459e4ee325775633e8ec76cb7c6

View File

@ -27,7 +27,7 @@ func main() {
types.ExpTipSet{}, types.ExpTipSet{},
types.BeaconEntry{}, types.BeaconEntry{},
types.StateRoot{}, types.StateRoot{},
types.StateInfo{}, types.StateInfo0{},
) )
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)

View File

@ -176,7 +176,8 @@
"CompactPartitions", "CompactPartitions",
"CompactSectorNumbers", "CompactSectorNumbers",
"ConfirmUpdateWorkerKey", "ConfirmUpdateWorkerKey",
"RepayDebt" "RepayDebt",
"ChangeOwnerAddress"
], ],
"fil/2/storagepower": [ "fil/2/storagepower": [
"Send", "Send",

View File

@ -160,7 +160,18 @@ func (a *GasAPI) GasEstimateGasLimit(ctx context.Context, msgIn *types.Message,
priorMsgs = append(priorMsgs, m) priorMsgs = append(priorMsgs, m)
} }
res, err := a.Stmgr.CallWithGas(ctx, &msg, priorMsgs, ts) // Try calling until we find a height with no migration.
var res *api.InvocResult
for {
res, err = a.Stmgr.CallWithGas(ctx, &msg, priorMsgs, ts)
if err != stmgr.ErrExpensiveFork {
break
}
ts, err = a.Chain.GetTipSetFromKey(ts.Parents())
if err != nil {
return -1, xerrors.Errorf("getting parent tipset: %w", err)
}
}
if err != nil { if err != nil {
return -1, xerrors.Errorf("CallWithGas failed: %w", err) return -1, xerrors.Errorf("CallWithGas failed: %w", err)
} }

View File

@ -307,12 +307,22 @@ func (a *StateAPI) StateMinerPower(ctx context.Context, addr address.Address, ts
}, nil }, nil
} }
func (a *StateAPI) StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (*api.InvocResult, error) { func (a *StateAPI) StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (res *api.InvocResult, err error) {
ts, err := a.Chain.GetTipSetFromKey(tsk) ts, err := a.Chain.GetTipSetFromKey(tsk)
if err != nil { if err != nil {
return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err) return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err)
} }
return a.StateManager.Call(ctx, msg, ts) for {
res, err = a.StateManager.Call(ctx, msg, ts)
if err != stmgr.ErrExpensiveFork {
break
}
ts, err = a.Chain.GetTipSetFromKey(ts.Parents())
if err != nil {
return nil, xerrors.Errorf("getting parent tipset: %w", err)
}
}
return res, err
} }
func (a *StateAPI) StateReplay(ctx context.Context, tsk types.TipSetKey, mc cid.Cid) (*api.InvocResult, error) { func (a *StateAPI) StateReplay(ctx context.Context, tsk types.TipSetKey, mc cid.Cid) (*api.InvocResult, error) {