lotus/tvx/builders/builder.go

193 lines
4.6 KiB
Go
Raw Normal View History

package builders
import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"io"
"log"
"os"
"github.com/filecoin-project/lotus/chain/state"
"github.com/ipfs/go-cid"
format "github.com/ipfs/go-ipld-format"
"github.com/ipld/go-car"
"github.com/filecoin-project/oni/tvx/lotus"
"github.com/filecoin-project/oni/tvx/schema"
ostate "github.com/filecoin-project/oni/tvx/state"
)
type Stage string
const (
StagePreconditions = Stage("preconditions")
StageApplies = Stage("applies")
StageChecks = Stage("checks")
StageFinished = Stage("finished")
)
func init() {
// disable logs, as we need a clean stdout output.
log.SetOutput(os.Stderr)
log.SetPrefix(">>> ")
}
// TODO use stage.Surgeon with non-proxying blockstore.
type Builder struct {
Actors *Actors
Assert *Asserter
Messages *Messages
Driver *lotus.Driver
Root cid.Cid
Wallet *Wallet
StateTree *state.StateTree
Stores *ostate.Stores
vector schema.TestVector
stage Stage
}
// MessageVector creates a builder for a message-class vector.
func MessageVector(metadata *schema.Metadata) *Builder {
stores := ostate.NewLocalStores(context.Background())
// Create a brand new state tree.
st, err := state.NewStateTree(stores.CBORStore)
if err != nil {
panic(err)
}
b := &Builder{
stage: StagePreconditions,
Stores: stores,
StateTree: st,
Driver: lotus.NewDriver(context.Background()),
}
b.Wallet = newWallet()
b.Assert = newAsserter(b, StagePreconditions)
b.Actors = &Actors{b: b}
b.Messages = &Messages{b: b}
b.vector.Class = schema.ClassMessage
b.vector.Meta = metadata
b.vector.Pre = &schema.Preconditions{}
b.vector.Post = &schema.Postconditions{}
b.initializeZeroState()
return b
}
func (b *Builder) CommitPreconditions() {
if b.stage != StagePreconditions {
panic("called CommitPreconditions at the wrong time")
}
// capture the preroot after applying all preconditions.
preroot := b.FlushState()
b.vector.Pre.Epoch = 0
b.vector.Pre.StateTree = &schema.StateTree{RootCID: preroot}
b.Root = preroot
b.stage = StageApplies
b.Assert = newAsserter(b, StageApplies)
}
func (b *Builder) CommitApplies() {
if b.stage != StageApplies {
panic("called CommitApplies at the wrong time")
}
for _, am := range b.Messages.All() {
// apply all messages that are pending application.
if am.Result == nil {
b.applyMessage(am)
}
}
b.vector.Post.StateTree = &schema.StateTree{RootCID: b.Root}
b.stage = StageChecks
b.Assert = newAsserter(b, StageChecks)
}
// applyMessage executes the provided message via the driver, records the new
// root, refreshes the state tree, and updates the underlying vector with the
// message and its receipt.
func (b *Builder) applyMessage(am *ApplicableMessage) {
var err error
am.Result, b.Root, err = b.Driver.ExecuteMessage(am.Message, b.Root, b.Stores.Blockstore, am.Epoch)
b.Assert.NoError(err)
// replace the state tree.
b.StateTree, err = state.LoadStateTree(b.Stores.CBORStore, b.Root)
b.Assert.NoError(err)
b.vector.ApplyMessages = append(b.vector.ApplyMessages, schema.Message{
Bytes: MustSerialize(am.Message),
Epoch: &am.Epoch,
})
b.vector.Post.Receipts = append(b.vector.Post.Receipts, &schema.Receipt{
ExitCode: am.Result.ExitCode,
ReturnValue: am.Result.Return,
GasUsed: am.Result.GasUsed,
})
}
func (b *Builder) Finish(w io.Writer) {
if b.stage != StageChecks {
panic("called Finish at the wrong time")
}
out := new(bytes.Buffer)
gw := gzip.NewWriter(out)
if err := b.WriteCAR(gw, b.vector.Pre.StateTree.RootCID, b.vector.Post.StateTree.RootCID); err != nil {
panic(err)
}
if err := gw.Flush(); err != nil {
panic(err)
}
if err := gw.Close(); err != nil {
panic(err)
}
b.vector.CAR = out.Bytes()
b.stage = StageFinished
b.Assert = nil
encoder := json.NewEncoder(w)
if err := encoder.Encode(b.vector); err != nil {
panic(err)
}
}
// WriteCAR recursively writes the tree referenced by the root as assert CAR into the
// supplied io.Writer.
//
// TODO use state.Surgeon instead. (This is assert copy of Surgeon#WriteCAR).
func (b *Builder) WriteCAR(w io.Writer, roots ...cid.Cid) error {
carWalkFn := func(nd format.Node) (out []*format.Link, err error) {
for _, link := range nd.Links() {
if link.Cid.Prefix().Codec == cid.FilCommitmentSealed || link.Cid.Prefix().Codec == cid.FilCommitmentUnsealed {
continue
}
out = append(out, link)
}
return out, nil
}
return car.WriteCarWithWalker(context.Background(), b.Stores.DAGService, roots, w, carWalkFn)
}
func (b *Builder) FlushState() cid.Cid {
preroot, err := b.StateTree.Flush(context.Background())
if err != nil {
panic(err)
}
return preroot
}