lotus/cmd/lotus-sim/simulation/node.go

242 lines
6.2 KiB
Go
Raw Normal View History

2021-05-19 00:01:30 +00:00
package simulation
import (
"context"
"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/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"
)
// 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
2021-05-19 00:01:30 +00:00
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) {
2021-05-19 00:01:30 +00:00
r, err := repo.NewFS(path)
if err != nil {
return nil, err
}
return NewNode(ctx, r)
}
// NewNode constructs a new node from the given repo.
func NewNode(ctx context.Context, r repo.Repo) (nd *Node, _err error) {
lr, err := r.Lock(repo.FullNode)
2021-05-19 00:01:30 +00:00
if err != nil {
return nil, err
}
defer func() {
if _err != nil {
2021-06-22 22:06:44 +00:00
_ = lr.Close()
}
}()
2021-05-19 00:01:30 +00:00
bs, err := lr.Blockstore(ctx, repo.UniversalBlockstore)
2021-05-19 00:01:30 +00:00
if err != nil {
return nil, err
}
ds, err := lr.Datastore(ctx, "/metadata")
2021-05-19 00:01:30 +00:00
if err != nil {
return nil, err
}
return &Node{
repo: lr,
2021-07-27 13:30:23 +00:00
Chainstore: store.NewChainStore(bs, bs, ds, nil),
MetadataDS: ds,
Blockstore: bs,
}, err
}
// Close cleanly close the repo. Please call this on shutdown to make sure everything is flushed.
func (nd *Node) Close() error {
if nd.repo != nil {
return nd.repo.Close()
2021-05-19 00:01:30 +00:00
}
return nil
2021-05-19 00:01:30 +00:00
}
// LoadSim loads
2021-05-19 00:01:30 +00:00
func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) {
stages, err := stages.DefaultPipeline()
if err != nil {
return nil, err
}
2021-05-19 00:01:30 +00:00
sim := &Simulation{
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-07-27 13:30:23 +00:00
sim.StateManager, err = stmgr.NewStateManagerWithUpgradeSchedule(nd.Chainstore, vm.Syscalls(mock.Verifier), 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
}
// 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)
}
stages, err := stages.DefaultPipeline()
if err != nil {
return nil, err
}
2021-05-19 00:01:30 +00:00
sim := &Simulation{
name: name,
Node: nd,
2021-07-27 13:30:23 +00:00
StateManager: stmgr.NewStateManager(nd.Chainstore, vm.Syscalls(mock.Verifier)),
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
}
// 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)
}
2021-06-18 18:17:35 +00:00
defer func() { _ = items.Close() }()
2021-05-19 00:01:30 +00:00
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()
}
}
}
var simFields = []string{"head", "start", "config"}
// 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
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
}
// CopySim copies a simulation.
func (nd *Node) CopySim(ctx context.Context, oldName, newName string) error {
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)
}
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)
}