2021-05-19 00:01:30 +00:00
|
|
|
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"
|
2021-06-12 01:39:15 +00:00
|
|
|
"github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock"
|
|
|
|
"github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/stages"
|
2021-05-19 00:01:30 +00:00
|
|
|
"github.com/filecoin-project/lotus/node/repo"
|
|
|
|
)
|
|
|
|
|
2021-06-08 00:45:53 +00:00
|
|
|
// Node represents the local lotus node, or at least the part of it we care about.
|
2021-05-19 00:01:30 +00:00
|
|
|
type Node struct {
|
|
|
|
Repo repo.LockedRepo
|
|
|
|
Blockstore blockstore.Blockstore
|
|
|
|
MetadataDS datastore.Batching
|
|
|
|
Chainstore *store.ChainStore
|
|
|
|
}
|
|
|
|
|
2021-06-08 00:45:53 +00:00
|
|
|
// OpenNode opens the local lotus node for writing. This will fail if the node is online.
|
2021-05-19 00:01:30 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-06-12 01:39:15 +00:00
|
|
|
node.Chainstore = store.NewChainStore(node.Blockstore, node.Blockstore, node.MetadataDS, vm.Syscalls(mock.Verifier), nil)
|
2021-05-19 00:01:30 +00:00
|
|
|
return &node, nil
|
|
|
|
}
|
|
|
|
|
2021-06-08 00:45:53 +00:00
|
|
|
// Close cleanly close the node. Please call this on shutdown to make sure everything is flushed.
|
2021-05-19 00:01:30 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-06-08 00:45:53 +00:00
|
|
|
// LoadSim loads
|
2021-05-19 00:01:30 +00:00
|
|
|
func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) {
|
2021-06-12 01:39:15 +00:00
|
|
|
stages, err := stages.DefaultPipeline()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-05-19 00:01:30 +00:00
|
|
|
sim := &Simulation{
|
2021-06-12 01:39:15 +00:00
|
|
|
Node: nd,
|
|
|
|
name: name,
|
|
|
|
stages: stages,
|
2021-05-19 00:01:30 +00:00
|
|
|
}
|
2021-06-08 17:42:59 +00:00
|
|
|
|
|
|
|
sim.head, err = sim.loadNamedTipSet("head")
|
2021-05-19 00:01:30 +00:00
|
|
|
if err != nil {
|
2021-06-08 17:42:59 +00:00
|
|
|
return nil, err
|
2021-05-19 00:01:30 +00:00
|
|
|
}
|
2021-06-08 17:42:59 +00:00
|
|
|
sim.start, err = sim.loadNamedTipSet("start")
|
2021-05-19 00:01:30 +00:00
|
|
|
if err != nil {
|
2021-06-08 17:42:59 +00:00
|
|
|
return nil, err
|
2021-05-19 00:01:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
2021-06-08 18:22:11 +00:00
|
|
|
sim.StateManager, err = stmgr.NewStateManagerWithUpgradeSchedule(nd.Chainstore, us)
|
2021-05-19 00:01:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("failed to create state manager for simulation %s: %w", name, err)
|
|
|
|
}
|
|
|
|
return sim, nil
|
|
|
|
}
|
|
|
|
|
2021-06-08 00:45:53 +00:00
|
|
|
// Create creates a new simulation.
|
|
|
|
//
|
|
|
|
// - This will fail if a simulation already exists with the given name.
|
|
|
|
// - Name must not contain a '/'.
|
2021-05-19 00:01:30 +00:00
|
|
|
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)
|
|
|
|
}
|
2021-06-12 01:39:15 +00:00
|
|
|
stages, err := stages.DefaultPipeline()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-05-19 00:01:30 +00:00
|
|
|
sim := &Simulation{
|
2021-06-08 18:22:11 +00:00
|
|
|
name: name,
|
|
|
|
Node: nd,
|
|
|
|
StateManager: stmgr.NewStateManager(nd.Chainstore),
|
2021-06-12 01:39:15 +00:00
|
|
|
stages: stages,
|
2021-05-19 00:01:30 +00:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2021-06-08 17:42:59 +00:00
|
|
|
if err := sim.storeNamedTipSet("start", head); err != nil {
|
|
|
|
return nil, xerrors.Errorf("failed to set simulation start: %w", err)
|
|
|
|
}
|
|
|
|
|
2021-05-19 00:01:30 +00:00
|
|
|
if err := sim.SetHead(head); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return sim, nil
|
|
|
|
}
|
|
|
|
|
2021-06-08 00:45:53 +00:00
|
|
|
// ListSims lists all simulations.
|
2021-05-19 00:01:30 +00:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-08 20:32:32 +00:00
|
|
|
var simFields = []string{"head", "start", "config"}
|
|
|
|
|
2021-06-08 00:45:53 +00:00
|
|
|
// DeleteSim deletes a simulation and all related metadata.
|
|
|
|
//
|
|
|
|
// NOTE: This function does not delete associated messages, blocks, or chain state.
|
2021-05-19 00:01:30 +00:00
|
|
|
func (nd *Node) DeleteSim(ctx context.Context, name string) error {
|
|
|
|
var err error
|
2021-06-08 20:32:32 +00:00
|
|
|
for _, field := range simFields {
|
|
|
|
key := simulationPrefix.ChildString(field).ChildString(name)
|
2021-05-19 00:01:30 +00:00
|
|
|
err = multierr.Append(err, nd.MetadataDS.Delete(key))
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
2021-06-08 20:32:32 +00:00
|
|
|
|
|
|
|
// CopySim copies a simulation.
|
|
|
|
func (nd *Node) CopySim(ctx context.Context, oldName, newName string) error {
|
2021-06-08 20:42:01 +00:00
|
|
|
if strings.Contains(newName, "/") {
|
|
|
|
return xerrors.Errorf("simulation name %q cannot contain a '/'", newName)
|
|
|
|
}
|
|
|
|
if strings.Contains(oldName, "/") {
|
|
|
|
return xerrors.Errorf("simulation name %q cannot contain a '/'", oldName)
|
|
|
|
}
|
|
|
|
|
2021-06-08 20:32:32 +00:00
|
|
|
values := make(map[string][]byte)
|
|
|
|
for _, field := range simFields {
|
|
|
|
key := simulationPrefix.ChildString(field).ChildString(oldName)
|
|
|
|
value, err := nd.MetadataDS.Get(key)
|
|
|
|
if err == datastore.ErrNotFound {
|
|
|
|
continue
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
values[field] = value
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := values["head"]; !ok {
|
|
|
|
return xerrors.Errorf("simulation named %s not found", oldName)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, field := range simFields {
|
|
|
|
key := simulationPrefix.ChildString(field).ChildString(newName)
|
|
|
|
var err error
|
|
|
|
if value, ok := values[field]; ok {
|
|
|
|
err = nd.MetadataDS.Put(key, value)
|
|
|
|
} else {
|
|
|
|
err = nd.MetadataDS.Delete(key)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RenameSim renames a simulation.
|
|
|
|
func (nd *Node) RenameSim(ctx context.Context, oldName, newName string) error {
|
|
|
|
if err := nd.CopySim(ctx, oldName, newName); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nd.DeleteSim(ctx, oldName)
|
|
|
|
}
|