From c585ce6fafae978f476d43f3891dd213945c0fb8 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Mon, 14 Nov 2022 16:43:19 -0500 Subject: [PATCH] Add tooling for gas estimation --- blockstore/autobatch.go | 8 +- build/drand.go | 4 + chain/beacon/drand/drand.go | 2 +- chain/stmgr/call.go | 77 ++++++++++++++++ chain/types/tipset.go | 3 + cmd/lotus-shed/gas-estimation.go | 153 +++++++++++++++++++++++++++++++ cmd/lotus-shed/main.go | 1 + 7 files changed, 241 insertions(+), 7 deletions(-) create mode 100644 cmd/lotus-shed/gas-estimation.go diff --git a/blockstore/autobatch.go b/blockstore/autobatch.go index d41d521ef74..d810212d23b 100644 --- a/blockstore/autobatch.go +++ b/blockstore/autobatch.go @@ -181,22 +181,18 @@ func (bs *AutobatchBlockstore) Get(ctx context.Context, c cid.Cid) (block.Block, } bs.stateLock.Lock() + defer bs.stateLock.Unlock() v, ok := bs.flushingBatch.blockMap[c] if ok { - bs.stateLock.Unlock() return v, nil } v, ok = bs.bufferedBatch.blockMap[c] if ok { - bs.stateLock.Unlock() return v, nil } - bs.stateLock.Unlock() - // We have to check the backing store one more time because it may have been flushed by the - // time we were able to take the lock above. - return bs.backingBs.Get(ctx, c) + return nil, ipld.ErrNotFound{Cid: c} } func (bs *AutobatchBlockstore) DeleteBlock(context.Context, cid.Cid) error { diff --git a/build/drand.go b/build/drand.go index 3b976ac9254..3027d930b5a 100644 --- a/build/drand.go +++ b/build/drand.go @@ -69,6 +69,10 @@ var DrandConfigs = map[DrandEnum]dtypes.DrandConfig{ ChainInfoJSON: `{"public_key":"8cda589f88914aa728fd183f383980b35789ce81b274e5daee1f338b77d02566ef4d3fb0098af1f844f10f9c803c1827","period":25,"genesis_time":1595348225,"hash":"e73b7dc3c4f6a236378220c0dd6aa110eb16eed26c11259606e07ee122838d4f","groupHash":"567d4785122a5a3e75a9bc9911d7ea807dd85ff76b78dc4ff06b075712898607"}`, }, DrandIncentinet: { + Servers: []string{ + "https://dev1.drand.sh", + "https://dev2.drand.sh", + }, ChainInfoJSON: `{"public_key":"8cad0c72c606ab27d36ee06de1d5b2db1faf92e447025ca37575ab3a8aac2eaae83192f846fc9e158bc738423753d000","period":30,"genesis_time":1595873820,"hash":"80c8b872c714f4c00fdd3daa465d5514049f457f01f85a4caf68cdcd394ba039","groupHash":"d9406aaed487f7af71851b4399448e311f2328923d454e971536c05398ce2d9b"}`, }, } diff --git a/chain/beacon/drand/drand.go b/chain/beacon/drand/drand.go index 5b6cc45bd67..181fa304676 100644 --- a/chain/beacon/drand/drand.go +++ b/chain/beacon/drand/drand.go @@ -107,7 +107,7 @@ func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub, config dtypes client, err := dclient.Wrap(clients, opts...) if err != nil { - return nil, xerrors.Errorf("creating drand client") + return nil, xerrors.Errorf("creating drand client: %w", err) } lc, err := lru.New(1024) diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index 92066ca8101..eb13497bbbe 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -14,6 +14,7 @@ import ( "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/network" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/blockstore" @@ -153,6 +154,82 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types. }, err } +func (sm *StateManager) CallAtStateAndVersion(ctx context.Context, msg *types.Message, ts *types.TipSet, stateCid cid.Cid, v network.Version) (*api.InvocResult, error) { + r := rand.NewStateRand(sm.cs, ts.Cids(), sm.beacon, sm.GetNetworkVersion) + + buffStore := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) + vmopt := &vm.VMOpts{ + StateBase: stateCid, + Epoch: ts.Height() + 1, + Rand: r, + Bstore: buffStore, + Actors: sm.tsExec.NewActorRegistry(), + Syscalls: sm.Syscalls, + CircSupplyCalc: sm.GetVMCirculatingSupply, + NetworkVersion: v, + BaseFee: ts.Blocks()[0].ParentBaseFee, + LookbackState: LookbackStateGetterForTipset(sm, ts), + Tracing: true, + } + + vmi, err := sm.newVM(ctx, vmopt) + if err != nil { + return nil, xerrors.Errorf("failed to set up vm: %w", err) + } + + stTree, err := state.LoadStateTree(cbor.NewCborStore(buffStore), stateCid) + if err != nil { + return nil, xerrors.Errorf("loading state tree: %w", err) + } + + fromActor, err := stTree.GetActor(msg.From) + if err != nil { + return nil, xerrors.Errorf("call raw get actor: %s", err) + } + + msg.Nonce = fromActor.Nonce + + fromKey, err := sm.ResolveToKeyAddress(ctx, msg.From, ts) + if err != nil { + return nil, xerrors.Errorf("could not resolve key: %w", err) + } + + var msgApply types.ChainMsg + + switch fromKey.Protocol() { + case address.BLS: + msgApply = msg + case address.SECP256K1: + msgApply = &types.SignedMessage{ + Message: *msg, + Signature: crypto.Signature{ + Type: crypto.SigTypeSecp256k1, + Data: make([]byte, 65), + }, + } + } + + ret, err := vmi.ApplyMessage(ctx, msgApply) + if err != nil { + return nil, xerrors.Errorf("gas estimation failed: %w", err) + } + + var errs string + if ret.ActorErr != nil { + errs = ret.ActorErr.Error() + } + + return &api.InvocResult{ + MsgCid: msg.Cid(), + Msg: msg, + MsgRct: &ret.MessageReceipt, + GasCost: MakeMsgGasCost(msg, ret), + ExecutionTrace: ret.ExecutionTrace, + Error: errs, + Duration: ret.Duration, + }, nil +} + func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, priorMsgs []types.ChainMsg, ts *types.TipSet) (*api.InvocResult, error) { ctx, span := trace.StartSpan(ctx, "statemanager.CallWithGas") defer span.End() diff --git a/chain/types/tipset.go b/chain/types/tipset.go index cb981e0f01d..3cbfa916230 100644 --- a/chain/types/tipset.go +++ b/chain/types/tipset.go @@ -151,6 +151,9 @@ func (ts *TipSet) Key() TipSetKey { func (ts *TipSet) Height() abi.ChainEpoch { return ts.height } +func (ts *TipSet) SetHeight(h abi.ChainEpoch) { + ts.height = h +} func (ts *TipSet) Parents() TipSetKey { return NewTipSetKey(ts.blks[0].Parents...) diff --git a/cmd/lotus-shed/gas-estimation.go b/cmd/lotus-shed/gas-estimation.go new file mode 100644 index 00000000000..6517ab5b364 --- /dev/null +++ b/cmd/lotus-shed/gas-estimation.go @@ -0,0 +1,153 @@ +package main + +import ( + "context" + "fmt" + "io" + "os" + "strconv" + "text/tabwriter" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/go-state-types/network" + + "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/beacon" + "github.com/filecoin-project/lotus/chain/beacon/drand" + "github.com/filecoin-project/lotus/chain/consensus/filcns" + "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" + lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/node/repo" + "github.com/filecoin-project/lotus/storage/sealer/ffiwrapper" +) + +var gasEstimationCmd = &cli.Command{ + Name: "estimate-gas", + Description: "replay a message on the specified stateRoot and network version", + ArgsUsage: "[migratedStateRootCid migrationEpoch networkVersion messageHash]", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "repo", + Value: "~/.lotus", + }, + }, + Action: func(cctx *cli.Context) error { + ctx := context.TODO() + + if cctx.NArg() != 4 { + return lcli.IncorrectNumArgs(cctx) + } + + stateRootCid, err := cid.Decode(cctx.Args().Get(0)) + if err != nil { + return fmt.Errorf("failed to parse input: %w", err) + } + + epoch, err := strconv.ParseInt(cctx.Args().Get(1), 10, 64) + if err != nil { + return fmt.Errorf("failed to parse input: %w", err) + } + + nv, err := strconv.ParseInt(cctx.Args().Get(2), 10, 64) + if err != nil { + return fmt.Errorf("failed to parse input: %w", err) + } + + messageCid, err := cid.Decode(cctx.Args().Get(3)) + if err != nil { + return fmt.Errorf("failed to parse input: %w", err) + } + + fsrepo, err := repo.NewFS(cctx.String("repo")) + if err != nil { + return err + } + + lkrepo, err := fsrepo.Lock(repo.FullNode) + if err != nil { + return err + } + + defer lkrepo.Close() //nolint:errcheck + + bs, err := lkrepo.Blockstore(ctx, repo.UniversalBlockstore) + if err != nil { + return fmt.Errorf("failed to open blockstore: %w", err) + } + + defer func() { + if c, ok := bs.(io.Closer); ok { + if err := c.Close(); err != nil { + log.Warnf("failed to close blockstore: %s", err) + } + } + }() + + mds, err := lkrepo.Datastore(context.Background(), "/metadata") + if err != nil { + return err + } + + dcs := build.DrandConfigSchedule() + shd := beacon.Schedule{} + for _, dc := range dcs { + bc, err := drand.NewDrandBeacon(1598306400, build.BlockDelaySecs, nil, dc.Config) + if err != nil { + return xerrors.Errorf("creating drand beacon: %w", err) + } + shd = append(shd, beacon.BeaconPoint{Start: dc.Start, Beacon: bc}) + } + cs := store.NewChainStore(bs, bs, mds, filcns.Weight, nil) + defer cs.Close() //nolint:errcheck + + sm, err := stmgr.NewStateManager(cs, filcns.NewTipSetExecutor(), vm.Syscalls(ffiwrapper.ProofVerifier), filcns.DefaultUpgradeSchedule(), shd) + if err != nil { + return err + } + + msg, err := cs.GetMessage(ctx, messageCid) + if err != nil { + return err + } + + // Set to block limit so message will not run out of gas + msg.GasLimit = 10_000_000_000 + + err = cs.Load(ctx) + if err != nil { + return err + } + + executionTs, err := cs.GetTipsetByHeight(ctx, abi.ChainEpoch(epoch), nil, false) + if err != nil { + return err + } + + tw := tabwriter.NewWriter(os.Stdout, 2, 2, 2, ' ', 0) + res, err := sm.CallAtStateAndVersion(ctx, msg, executionTs, stateRootCid, network.Version(nv)) + if err != nil { + return err + } + printInternalExecutions(0, []types.ExecutionTrace{res.ExecutionTrace}, tw) + + return tw.Flush() + }, +} + +func printInternalExecutions(depth int, trace []types.ExecutionTrace, tw *tabwriter.Writer) { + if depth == 0 { + _, _ = fmt.Fprintf(tw, "depth\tFrom\tTo\tValue\tMethod\tGasUsed\tExitCode\tReturn\n") + } + for _, im := range trace { + _, _ = fmt.Fprintf(tw, "%d\t%s\t%s\t%s\t%d\t%d\t%d\t%x\n", depth, im.Msg.From, im.Msg.To, im.Msg.Value, im.Msg.Method, im.MsgRct.GasUsed, im.MsgRct.ExitCode, im.MsgRct.Return) + printInternalExecutions(depth+1, im.Subcalls, tw) + } +} diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index 3972a625b4d..1f1d57ebc87 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -76,6 +76,7 @@ func main() { msigCmd, fip36PollCmd, invariantsCmd, + gasEstimationCmd, } app := &cli.App{