diff --git a/cmd/tvx/extract.go b/cmd/tvx/extract.go index a3d538abd02..281e469bb60 100644 --- a/cmd/tvx/extract.go +++ b/cmd/tvx/extract.go @@ -13,8 +13,8 @@ import ( ) const ( - PrecursorSelectAll = "all" - PrecursorSelectSender = "sender" + PrecursorSelectAll = "all" + PrecursorSelectParticipants = "participants" ) type extractOpts struct { @@ -86,12 +86,12 @@ var extractCmd = &cli.Command{ }, &cli.StringFlag{ Name: "precursor-select", - Usage: "precursors to apply; values: 'all', 'sender'; 'all' selects all preceding " + - "messages in the canonicalised tipset, 'sender' selects only preceding messages from the same " + - "sender. Usually, 'sender' is a good tradeoff and gives you sufficient accuracy. If the receipt sanity " + + Usage: "precursors to apply; values: 'all', 'participants'; 'all' selects all preceding " + + "messages in the canonicalised tipset, 'participants' selects only preceding messages from the same " + + "participants. Usually, 'participants' is a good tradeoff and gives you sufficient accuracy. If the receipt sanity " + "check fails due to gas reasons, switch to 'all', as previous messages in the tipset may have " + "affected state in a disruptive way", - Value: "sender", + Value: "participants", Destination: &extractFlags.precursor, }, &cli.BoolFlag{ diff --git a/cmd/tvx/extract_many.go b/cmd/tvx/extract_many.go index ae196542e87..3520cd2adef 100644 --- a/cmd/tvx/extract_many.go +++ b/cmd/tvx/extract_many.go @@ -13,11 +13,12 @@ import ( "github.com/fatih/color" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/exitcode" - "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/hashicorp/go-multierror" "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" "github.com/urfave/cli/v2" + + "github.com/filecoin-project/lotus/chain/consensus/filcns" ) var extractManyFlags struct { @@ -176,7 +177,7 @@ func runExtractMany(c *cli.Context) error { // Vector filename, using a base of outdir. file := filepath.Join(outdir, actorcodename, methodname, exitcodename, id) + ".json" - log.Println(color.YellowString("processing message cid with 'sender' precursor mode: %s", id)) + log.Println(color.YellowString("processing message cid with 'participants' precursor mode: %s", id)) opts := extractOpts{ id: id, @@ -185,7 +186,7 @@ func runExtractMany(c *cli.Context) error { cid: mcid, file: file, retain: "accessed-cids", - precursor: PrecursorSelectSender, + precursor: PrecursorSelectParticipants, } if err := doExtractMessage(opts); err != nil { @@ -199,7 +200,7 @@ func runExtractMany(c *cli.Context) error { generated = append(generated, file) } - log.Printf("extractions to try with canonical precursor selection mode: %d", len(retry)) + log.Printf("extractions to try with 'all' precursor selection mode: %d", len(retry)) for _, r := range retry { log.Printf("retrying %s: %s", r.cid, r.id) diff --git a/cmd/tvx/extract_message.go b/cmd/tvx/extract_message.go index 68376654af8..33ae5ab8f5b 100644 --- a/cmd/tvx/extract_message.go +++ b/cmd/tvx/extract_message.go @@ -71,7 +71,7 @@ func doExtractMessage(opts extractOpts) error { return fmt.Errorf("failed to fetch messages in canonical order from inclusion tipset: %w", err) } - related, found, err := findMsgAndPrecursors(opts.precursor, mcid, msg.From, msgs) + related, found, err := findMsgAndPrecursors(ctx, opts.precursor, mcid, msg.From, msg.To, msgs) if err != nil { return fmt.Errorf("failed while finding message and precursors: %w", err) } @@ -114,7 +114,7 @@ func doExtractMessage(opts extractOpts) error { log.Printf("applying precursor %d, cid: %s", i, m.Cid()) _, root, err = driver.ExecuteMessage(pst.Blockstore, conformance.ExecuteMessageParams{ Preroot: root, - Epoch: execTs.Height(), + Epoch: incTs.Height(), Message: m, CircSupply: circSupplyDetail.FilCirculating, BaseFee: basefee, @@ -139,6 +139,7 @@ func doExtractMessage(opts extractOpts) error { ) log.Printf("using state retention strategy: %s", retention) + log.Printf("now applying requested message: %s", msg.Cid()) switch retention { case "accessed-cids": tbs, ok := pst.Blockstore.(TracingBlockstore) @@ -151,7 +152,7 @@ func doExtractMessage(opts extractOpts) error { preroot = root applyret, postroot, err = driver.ExecuteMessage(pst.Blockstore, conformance.ExecuteMessageParams{ Preroot: preroot, - Epoch: execTs.Height(), + Epoch: incTs.Height(), Message: msg, CircSupply: circSupplyDetail.FilCirculating, BaseFee: basefee, @@ -184,7 +185,7 @@ func doExtractMessage(opts extractOpts) error { } applyret, postroot, err = driver.ExecuteMessage(pst.Blockstore, conformance.ExecuteMessageParams{ Preroot: preroot, - Epoch: execTs.Height(), + Epoch: incTs.Height(), Message: msg, CircSupply: circSupplyDetail.FilCirculating, BaseFee: basefee, @@ -299,7 +300,7 @@ func doExtractMessage(opts extractOpts) error { CAR: out.Bytes(), Pre: &schema.Preconditions{ Variants: []schema.Variant{ - {ID: codename, Epoch: int64(execTs.Height()), NetworkVersion: uint(nv)}, + {ID: codename, Epoch: int64(incTs.Height()), NetworkVersion: uint(nv)}, }, CircSupply: circSupply.Int, BaseFee: basefee.Int, @@ -368,13 +369,13 @@ func resolveFromChain(ctx context.Context, api v0api.FullNode, mcid cid.Cid, blo // types.EmptyTSK hints to use the HEAD. execTs, err = api.ChainGetTipSetByHeight(ctx, blk.Height+1, types.EmptyTSK) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get message execution tipset: %w", err) + return nil, nil, nil, fmt.Errorf("failed to get message execution tipset (%d) : %w", blk.Height+1, err) } // walk back from the execTs instead of HEAD, to save time. incTs, err = api.ChainGetTipSetByHeight(ctx, blk.Height, execTs.Key()) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get message inclusion tipset: %w", err) + return nil, nil, nil, fmt.Errorf("failed to get message inclusion tipset (%d): %w", blk.Height, err) } return msg, execTs, incTs, nil @@ -403,19 +404,29 @@ func fetchThisAndPrevTipset(ctx context.Context, api v0api.FullNode, target type // findMsgAndPrecursors ranges through the canonical messages slice, locating // the target message and returning precursors in accordance to the supplied // mode. -func findMsgAndPrecursors(mode string, msgCid cid.Cid, sender address.Address, msgs []api.Message) (related []*types.Message, found bool, err error) { - // Range through canonicalised messages, selecting only the precursors based - // on selection mode. - for _, other := range msgs { +func findMsgAndPrecursors(ctx context.Context, mode string, msgCid cid.Cid, sender address.Address, recipient address.Address, msgs []api.Message) (related []*types.Message, found bool, err error) { + // Resolve addresses to IDs for canonicality. + senderID := mustResolveAddr(ctx, sender) + recipientID := mustResolveAddr(ctx, recipient) + + // Range through messages, selecting only the precursors based on selection mode. + for _, m := range msgs { + msgSenderID := mustResolveAddr(ctx, m.Message.From) + msgRecipientID := mustResolveAddr(ctx, m.Message.To) + switch { case mode == PrecursorSelectAll: fallthrough - case mode == PrecursorSelectSender && other.Message.From == sender: - related = append(related, other.Message) + case mode == PrecursorSelectParticipants && + msgSenderID == senderID || + msgRecipientID == recipientID || + msgSenderID == recipientID || + msgRecipientID == senderID: + related = append(related, m.Message) } // this message is the target; we're done. - if other.Cid == msgCid { + if m.Cid == msgCid { return related, true, nil } } @@ -425,3 +436,17 @@ func findMsgAndPrecursors(mode string, msgCid cid.Cid, sender address.Address, m // target). return related, false, nil } + +var addressCache = make(map[address.Address]address.Address) + +func mustResolveAddr(ctx context.Context, addr address.Address) address.Address { + if resolved, ok := addressCache[addr]; ok { + return resolved + } + id, err := FullAPI.StateLookupID(ctx, addr, types.EmptyTSK) + if err != nil { + panic(fmt.Errorf("failed to resolve addr: %w", err)) + } + addressCache[addr] = id + return id +} diff --git a/conformance/driver.go b/conformance/driver.go index a065d15305d..4d87dfcf326 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -6,6 +6,7 @@ import ( "os" "github.com/filecoin-project/go-state-types/network" + cbor "github.com/ipfs/go-ipld-cbor" "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/chain/consensus/filcns" @@ -199,6 +200,9 @@ type ExecuteMessageParams struct { // Rand is an optional vm.Rand implementation to use. If nil, the driver // will use a vm.Rand that returns a fixed value for all calls. Rand vm.Rand + + // Lookback is the LookbackStateGetter; returns the state tree at a given epoch. + Lookback vm.LookbackStateGetter } // ExecuteMessage executes a conformance test vector message in a temporary VM. @@ -213,6 +217,17 @@ func (d *Driver) ExecuteMessage(bs blockstore.Blockstore, params ExecuteMessageP params.Rand = NewFixedRand() } + // TODO: This lookback state returns the supplied precondition state tree, unconditionally. + // This is obviously not correct, but the lookback state tree is only used to validate the + // worker key when verifying a consensus fault. If the worker key hasn't changed in the + // current finality window, this workaround is enough. + // The correct solutions are documented in https://github.com/filecoin-project/ref-fvm/issues/381, + // but they're much harder to implement, and the tradeoffs aren't clear. + var lookback vm.LookbackStateGetter = func(ctx context.Context, epoch abi.ChainEpoch) (*state.StateTree, error) { + cst := cbor.NewCborStore(bs) + return state.LoadStateTree(cst, params.Preroot) + } + vmOpts := &vm.VMOpts{ StateBase: params.Preroot, Epoch: params.Epoch, @@ -224,6 +239,7 @@ func (d *Driver) ExecuteMessage(bs blockstore.Blockstore, params ExecuteMessageP Rand: params.Rand, BaseFee: params.BaseFee, NetworkVersion: params.NetworkVersion, + LookbackState: lookback, } lvm, err := vm.NewVM(context.TODO(), vmOpts)