178 lines
4.5 KiB
Go
178 lines
4.5 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"compress/gzip"
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"log"
|
||
|
|
||
|
"github.com/filecoin-project/test-vectors/schema"
|
||
|
"github.com/ipfs/go-cid"
|
||
|
|
||
|
"github.com/filecoin-project/lotus/chain/types"
|
||
|
lcli "github.com/filecoin-project/lotus/cli"
|
||
|
"github.com/filecoin-project/lotus/conformance"
|
||
|
)
|
||
|
|
||
|
func doExtractTipset(opts extractOpts) error {
|
||
|
ctx := context.Background()
|
||
|
|
||
|
if opts.tsk == "" {
|
||
|
return fmt.Errorf("tipset key cannot be empty")
|
||
|
}
|
||
|
|
||
|
if opts.retain != "accessed-cids" {
|
||
|
return fmt.Errorf("tipset extraction only supports 'accessed-cids' state retention")
|
||
|
}
|
||
|
|
||
|
ts, err := lcli.ParseTipSetRef(ctx, FullAPI, opts.tsk)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to fetch tipset: %w", err)
|
||
|
}
|
||
|
|
||
|
log.Printf("tipset block count: %d", len(ts.Blocks()))
|
||
|
|
||
|
var blocks []schema.Block
|
||
|
for _, b := range ts.Blocks() {
|
||
|
msgs, err := FullAPI.ChainGetBlockMessages(ctx, b.Cid())
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to get block messages (cid: %s): %w", b.Cid(), err)
|
||
|
}
|
||
|
|
||
|
log.Printf("block %s has %d messages", b.Cid(), len(msgs.Cids))
|
||
|
|
||
|
packed := make([]schema.Base64EncodedBytes, 0, len(msgs.Cids))
|
||
|
for _, m := range msgs.BlsMessages {
|
||
|
b, err := m.Serialize()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to serialize message: %w", err)
|
||
|
}
|
||
|
packed = append(packed, b)
|
||
|
}
|
||
|
for _, m := range msgs.SecpkMessages {
|
||
|
b, err := m.Message.Serialize()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to serialize message: %w", err)
|
||
|
}
|
||
|
packed = append(packed, b)
|
||
|
}
|
||
|
blocks = append(blocks, schema.Block{
|
||
|
MinerAddr: b.Miner,
|
||
|
WinCount: b.ElectionProof.WinCount,
|
||
|
Messages: packed,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
// create a read-through store that uses ChainGetObject to fetch unknown CIDs.
|
||
|
pst = NewProxyingStores(ctx, FullAPI)
|
||
|
g = NewSurgeon(ctx, FullAPI, pst)
|
||
|
)
|
||
|
|
||
|
driver := conformance.NewDriver(ctx, schema.Selector{}, conformance.DriverOpts{
|
||
|
DisableVMFlush: true,
|
||
|
})
|
||
|
|
||
|
// this is the root of the state tree we start with.
|
||
|
root := ts.ParentState()
|
||
|
log.Printf("base state tree root CID: %s", root)
|
||
|
|
||
|
basefee := ts.Blocks()[0].ParentBaseFee
|
||
|
log.Printf("basefee: %s", basefee)
|
||
|
|
||
|
tipset := schema.Tipset{
|
||
|
BaseFee: *basefee.Int,
|
||
|
Blocks: blocks,
|
||
|
}
|
||
|
|
||
|
// recordingRand will record randomness so we can embed it in the test vector.
|
||
|
recordingRand := conformance.NewRecordingRand(new(conformance.LogReporter), FullAPI)
|
||
|
|
||
|
log.Printf("using state retention strategy: %s", extractFlags.retain)
|
||
|
|
||
|
tbs, ok := pst.Blockstore.(TracingBlockstore)
|
||
|
if !ok {
|
||
|
return fmt.Errorf("requested 'accessed-cids' state retention, but no tracing blockstore was present")
|
||
|
}
|
||
|
|
||
|
tbs.StartTracing()
|
||
|
|
||
|
params := conformance.ExecuteTipsetParams{
|
||
|
Preroot: ts.ParentState(),
|
||
|
ParentEpoch: ts.Height() - 1,
|
||
|
Tipset: &tipset,
|
||
|
ExecEpoch: ts.Height(),
|
||
|
Rand: recordingRand,
|
||
|
}
|
||
|
result, err := driver.ExecuteTipset(pst.Blockstore, pst.Datastore, params)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to execute tipset: %w", err)
|
||
|
}
|
||
|
|
||
|
accessed := tbs.FinishTracing()
|
||
|
|
||
|
// write a CAR with the accessed state into a buffer.
|
||
|
var (
|
||
|
out = new(bytes.Buffer)
|
||
|
gw = gzip.NewWriter(out)
|
||
|
)
|
||
|
if err := g.WriteCARIncluding(gw, accessed, ts.ParentState(), result.PostStateRoot); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if err = gw.Flush(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if err = gw.Close(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
codename := GetProtocolCodename(ts.Height())
|
||
|
nv, err := FullAPI.StateNetworkVersion(ctx, ts.Key())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
vector := schema.TestVector{
|
||
|
Class: schema.ClassTipset,
|
||
|
Meta: &schema.Metadata{
|
||
|
ID: opts.id,
|
||
|
},
|
||
|
Selector: schema.Selector{
|
||
|
schema.SelectorMinProtocolVersion: codename,
|
||
|
},
|
||
|
Randomness: recordingRand.Recorded(),
|
||
|
CAR: out.Bytes(),
|
||
|
Pre: &schema.Preconditions{
|
||
|
Variants: []schema.Variant{
|
||
|
{ID: codename, Epoch: int64(ts.Height()), NetworkVersion: uint(nv)},
|
||
|
},
|
||
|
BaseFee: basefee.Int,
|
||
|
StateTree: &schema.StateTree{
|
||
|
RootCID: ts.ParentState(),
|
||
|
},
|
||
|
},
|
||
|
ApplyTipsets: []schema.Tipset{tipset},
|
||
|
Post: &schema.Postconditions{
|
||
|
StateTree: &schema.StateTree{
|
||
|
RootCID: result.PostStateRoot,
|
||
|
},
|
||
|
ReceiptsRoots: []cid.Cid{result.ReceiptsRoot},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
// do nothing with this for now.
|
||
|
var traces = make([]types.ExecutionTrace, 0, len(result.AppliedResults))
|
||
|
for _, res := range result.AppliedResults {
|
||
|
vector.Post.Receipts = append(vector.Post.Receipts, &schema.Receipt{
|
||
|
ExitCode: int64(res.ExitCode),
|
||
|
ReturnValue: res.Return,
|
||
|
GasUsed: res.GasUsed,
|
||
|
})
|
||
|
|
||
|
traces = append(traces, res.ExecutionTrace)
|
||
|
}
|
||
|
|
||
|
return writeVector(vector, opts.file)
|
||
|
}
|