lotus/cmd/lotus-sim/simulation/node.go
Steven Allen 2f7d7aed31 feat(lotus-sim): refactor and document
Hopefully, this'll make this code a bit easier to approach.
2021-06-18 15:44:34 -07:00

180 lines
4.8 KiB
Go

package simulation
import (
"context"
"io"
"strings"
"go.uber.org/multierr"
"golang.org/x/xerrors"
"github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/query"
"github.com/filecoin-project/lotus/blockstore"
"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/lotus/node/repo"
)
// Node represents the local lotus node, or at least the part of it we care about.
type Node struct {
Repo repo.LockedRepo
Blockstore blockstore.Blockstore
MetadataDS datastore.Batching
Chainstore *store.ChainStore
}
// OpenNode opens the local lotus node for writing. This will fail if the node is online.
func OpenNode(ctx context.Context, path string) (*Node, error) {
var node Node
r, err := repo.NewFS(path)
if err != nil {
return nil, err
}
node.Repo, err = r.Lock(repo.FullNode)
if err != nil {
node.Close()
return nil, err
}
node.Blockstore, err = node.Repo.Blockstore(ctx, repo.UniversalBlockstore)
if err != nil {
node.Close()
return nil, err
}
node.MetadataDS, err = node.Repo.Datastore(ctx, "/metadata")
if err != nil {
node.Close()
return nil, err
}
node.Chainstore = store.NewChainStore(node.Blockstore, node.Blockstore, node.MetadataDS, vm.Syscalls(mockVerifier{}), nil)
return &node, nil
}
// Close cleanly close the node. Please call this on shutdown to make sure everything is flushed.
func (nd *Node) Close() error {
var err error
if closer, ok := nd.Blockstore.(io.Closer); ok && closer != nil {
err = multierr.Append(err, closer.Close())
}
if nd.MetadataDS != nil {
err = multierr.Append(err, nd.MetadataDS.Close())
}
if nd.Repo != nil {
err = multierr.Append(err, nd.Repo.Close())
}
return err
}
// LoadSim loads
func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) {
sim := &Simulation{
Node: nd,
name: name,
}
tskBytes, err := nd.MetadataDS.Get(sim.key("head"))
if err != nil {
return nil, xerrors.Errorf("failed to load simulation %s: %w", name, err)
}
tsk, err := types.TipSetKeyFromBytes(tskBytes)
if err != nil {
return nil, xerrors.Errorf("failed to parse simulation %s's tipset %v: %w", name, tskBytes, err)
}
sim.head, err = nd.Chainstore.LoadTipSet(tsk)
if err != nil {
return nil, xerrors.Errorf("failed to load simulation tipset %s: %w", tsk, err)
}
err = sim.loadConfig()
if err != nil {
return nil, xerrors.Errorf("failed to load config for simulation %s: %w", name, err)
}
us, err := sim.config.upgradeSchedule()
if err != nil {
return nil, xerrors.Errorf("failed to create upgrade schedule for simulation %s: %w", name, err)
}
sim.sm, err = stmgr.NewStateManagerWithUpgradeSchedule(nd.Chainstore, us)
if err != nil {
return nil, xerrors.Errorf("failed to create state manager for simulation %s: %w", name, err)
}
return sim, nil
}
// Create creates a new simulation.
//
// - This will fail if a simulation already exists with the given name.
// - Name must not contain a '/'.
func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) (*Simulation, error) {
if strings.Contains(name, "/") {
return nil, xerrors.Errorf("simulation name %q cannot contain a '/'", name)
}
sim := &Simulation{
name: name,
Node: nd,
sm: stmgr.NewStateManager(nd.Chainstore),
}
if has, err := nd.MetadataDS.Has(sim.key("head")); err != nil {
return nil, err
} else if has {
return nil, xerrors.Errorf("simulation named %s already exists", name)
}
if err := sim.SetHead(head); err != nil {
return nil, err
}
return sim, nil
}
// ListSims lists all simulations.
func (nd *Node) ListSims(ctx context.Context) ([]string, error) {
prefix := simulationPrefix.ChildString("head").String()
items, err := nd.MetadataDS.Query(query.Query{
Prefix: prefix,
KeysOnly: true,
Orders: []query.Order{query.OrderByKey{}},
})
if err != nil {
return nil, xerrors.Errorf("failed to list simulations: %w", err)
}
defer items.Close()
var names []string
for {
select {
case result, ok := <-items.Next():
if !ok {
return names, nil
}
if result.Error != nil {
return nil, xerrors.Errorf("failed to retrieve next simulation: %w", result.Error)
}
names = append(names, strings.TrimPrefix(result.Key, prefix+"/"))
case <-ctx.Done():
return nil, ctx.Err()
}
}
}
// DeleteSim deletes a simulation and all related metadata.
//
// NOTE: This function does not delete associated messages, blocks, or chain state.
func (nd *Node) DeleteSim(ctx context.Context, name string) error {
// TODO: make this a bit more generic?
keys := []datastore.Key{
simulationPrefix.ChildString("head").ChildString(name),
simulationPrefix.ChildString("config").ChildString(name),
}
var err error
for _, key := range keys {
err = multierr.Append(err, nd.MetadataDS.Delete(key))
}
return err
}