193 lines
4.6 KiB
Go
193 lines
4.6 KiB
Go
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
|
|
}
|