2f7d7aed31
Hopefully, this'll make this code a bit easier to approach.
180 lines
4.8 KiB
Go
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
|
|
}
|