test vector builder API + partial migration of suites (#229)

Co-authored-by: Anton Evangelatov <anton.evangelatov@gmail.com>
This commit is contained in:
Raúl Kripalani 2020-08-14 13:51:43 +01:00 committed by GitHub
parent ffe3019561
commit 99f466e35e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 2072 additions and 487 deletions

213
tvx/builders/actors.go Normal file
View File

@ -0,0 +1,213 @@
package builders
import (
"context"
"log"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/specs-actors/actors/abi"
abi_spec "github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/builtin/account"
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
"github.com/filecoin-project/specs-actors/actors/builtin/power"
"github.com/filecoin-project/specs-actors/actors/runtime"
"github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/ipfs/go-cid"
cbg "github.com/whyrusleeping/cbor-gen"
)
// Actors is an object that manages actors in the test vector.
type Actors struct {
accounts []AddressHandle
miners []AddressHandle
b *Builder
}
// AccountN creates many account actors of the specified kind, with the
// specified balance, and places their addresses in the supplied AddressHandles.
func (a *Actors) AccountN(typ address.Protocol, balance abi.TokenAmount, handles ...*AddressHandle) {
for _, handle := range handles {
h := a.Account(typ, balance)
*handle = h
}
}
// Account creates a single account actor of the specified kind, with the
// specified balance, and returns its AddressHandle.
func (a *Actors) Account(typ address.Protocol, balance abi.TokenAmount) AddressHandle {
a.b.Assert.In(typ, address.SECP256K1, address.BLS)
var addr address.Address
switch typ {
case address.SECP256K1:
addr = a.b.Wallet.NewSECP256k1Account()
case address.BLS:
addr = a.b.Wallet.NewBLSAccount()
}
actorState := &account.State{Address: addr}
handle := a.CreateActor(builtin.AccountActorCodeID, addr, balance, actorState)
a.accounts = append(a.accounts, handle)
return handle
}
type MinerActorCfg struct {
SealProofType abi.RegisteredSealProof
PeriodBoundary abi.ChainEpoch
OwnerBalance abi.TokenAmount
}
// Miner creates an owner account, a worker account, and a miner actor managed
// by those accounts.
func (a *Actors) Miner(cfg MinerActorCfg) (minerActor, owner, worker AddressHandle) {
owner = a.Account(address.SECP256K1, cfg.OwnerBalance)
worker = a.Account(address.BLS, big.Zero())
// expectedMinerActorIDAddress := chain.MustNewIDAddr(chain.MustIDFromAddress(minerWorkerID) + 1)
// minerActorAddrs := computeInitActorExecReturn(minerWorkerPk, 0, 1, expectedMinerActorIDAddress)
ss, err := cfg.SealProofType.SectorSize()
a.b.Assert.NoError(err, "seal proof sector size")
ps, err := cfg.SealProofType.WindowPoStPartitionSectors()
a.b.Assert.NoError(err, "seal proof window PoSt partition sectors")
mi := &miner.MinerInfo{
Owner: owner.ID,
Worker: worker.ID,
PendingWorkerKey: nil,
PeerId: abi.PeerID("chain-validation"),
Multiaddrs: nil,
SealProofType: cfg.SealProofType,
SectorSize: ss,
WindowPoStPartitionSectors: ps,
}
infoCid, err := a.b.Stores.CBORStore.Put(context.Background(), mi)
if err != nil {
panic(err)
}
// create the miner actor s.t. it exists in the init actors map
minerState, err := miner.ConstructState(infoCid,
cfg.PeriodBoundary,
EmptyBitfieldCid,
EmptyArrayCid,
EmptyMapCid,
EmptyDeadlinesCid,
)
if err != nil {
panic(err)
}
minerActorAddr := worker.NextActorAddress(0, 0)
handle := a.CreateActor(builtin.StorageMinerActorCodeID, minerActorAddr, big.Zero(), minerState)
// assert miner actor has been created, exists in the state tree, and has an entry in the init actor.
// next update the storage power actor to track the miner
var spa power.State
a.ActorState(builtin.StoragePowerActorAddr, &spa)
// set the miners claim
hm, err := adt.AsMap(adt.WrapStore(context.Background(), a.b.Stores.CBORStore), spa.Claims)
if err != nil {
panic(err)
}
// add claim for the miner
err = hm.Put(adt.AddrKey(handle.ID), &power.Claim{
RawBytePower: abi.NewStoragePower(0),
QualityAdjPower: abi.NewTokenAmount(0),
})
if err != nil {
panic(err)
}
// save the claim
spa.Claims, err = hm.Root()
if err != nil {
panic(err)
}
// update miner count
spa.MinerCount += 1
// update storage power actor's state in the tree
_, err = a.b.Stores.CBORStore.Put(context.Background(), &spa)
if err != nil {
panic(err)
}
a.miners = append(a.miners, handle)
return handle, owner, worker
}
// CreateActor creates an actor in the state tree, of the specified kind, with
// the specified address and balance, and sets its state to the supplied state.
func (a *Actors) CreateActor(code cid.Cid, addr address.Address, balance abi.TokenAmount, state runtime.CBORMarshaler) AddressHandle {
var id address.Address
if addr.Protocol() != address.ID {
var err error
id, err = a.b.StateTree.RegisterNewAddress(addr)
if err != nil {
log.Panicf("register new address for actor: %v", err)
}
}
// Store the new state.
head, err := a.b.StateTree.Store.Put(context.Background(), state)
if err != nil {
panic(err)
}
// Set the actor's head to point to that state.
actr := &types.Actor{
Code: code,
Head: head,
Balance: balance,
}
if err := a.b.StateTree.SetActor(addr, actr); err != nil {
log.Panicf("setting new actor for actor: %v", err)
}
return AddressHandle{id, addr}
}
// ActorState retrieves the state of the supplied actor, and sets it in the
// provided object. It also returns the actor's header from the state tree.
func (a *Actors) ActorState(addr address.Address, out cbg.CBORUnmarshaler) *types.Actor {
actor := a.Header(addr)
err := a.b.StateTree.Store.Get(context.Background(), actor.Head, out)
a.b.Assert.NoError(err, "failed to load state for actorr %s; head=%s", addr, actor.Head)
return actor
}
// Header returns the actor's header from the state tree.
func (a *Actors) Header(addr address.Address) *types.Actor {
actor, err := a.b.StateTree.GetActor(addr)
a.b.Assert.NoError(err, "failed to fetch actor %s from state", addr)
return actor
}
// Balance is a shortcut for Header(addr).Balance.
func (a *Actors) Balance(addr address.Address) abi_spec.TokenAmount {
return a.Header(addr).Balance
}
// Head is a shortcut for Header(addr).Head.
func (a *Actors) Head(addr address.Address) cid.Cid {
return a.Header(addr).Head
}
// Nonce is a shortcut for Header(addr).Nonce.
func (a *Actors) Nonce(addr address.Address) uint64 {
return a.Header(addr).Nonce
}
// Code is a shortcut for Header(addr).Code.
func (a *Actors) Code(addr address.Address) cid.Cid {
return a.Header(addr).Code
}

89
tvx/builders/address.go Normal file
View File

@ -0,0 +1,89 @@
package builders
import (
"bytes"
"encoding/binary"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/chain/actors/aerrors"
"github.com/multiformats/go-varint"
)
// AddressHandle encapsulates both the ID and Robust addresses of an actor.
type AddressHandle struct {
ID, Robust address.Address
}
// NextActorAddress predicts the address of the next actor created by this address.
//
// Code is adapted from vm.Runtime#NewActorAddress()
func (ah *AddressHandle) NextActorAddress(nonce, numActorsCreated uint64) address.Address {
var b bytes.Buffer
if err := ah.Robust.MarshalCBOR(&b); err != nil {
panic(aerrors.Fatalf("writing caller address into assert buffer: %v", err))
}
if err := binary.Write(&b, binary.BigEndian, nonce); err != nil {
panic(aerrors.Fatalf("writing nonce address into assert buffer: %v", err))
}
if err := binary.Write(&b, binary.BigEndian, numActorsCreated); err != nil {
panic(aerrors.Fatalf("writing callSeqNum address into assert buffer: %v", err))
}
addr, err := address.NewActorAddress(b.Bytes())
if err != nil {
panic(aerrors.Fatalf("create actor address: %v", err))
}
return addr
}
// MustNewIDAddr returns an address.Address of kind ID.
func MustNewIDAddr(id uint64) address.Address {
addr, err := address.NewIDAddress(id)
if err != nil {
panic(err)
}
return addr
}
// MustNewSECP256K1Addr returns an address.Address of kind secp256k1.
func MustNewSECP256K1Addr(pubkey string) address.Address {
// the pubkey of assert secp256k1 address is hashed for consistent length.
addr, err := address.NewSecp256k1Address([]byte(pubkey))
if err != nil {
panic(err)
}
return addr
}
// MustNewBLSAddr returns an address.Address of kind bls.
func MustNewBLSAddr(seed int64) address.Address {
buf := make([]byte, address.BlsPublicKeyBytes)
binary.PutVarint(buf, seed)
addr, err := address.NewBLSAddress(buf)
if err != nil {
panic(err)
}
return addr
}
// MustNewActorAddr returns an address.Address of kind actor.
func MustNewActorAddr(data string) address.Address {
addr, err := address.NewActorAddress([]byte(data))
if err != nil {
panic(err)
}
return addr
}
// MustIDFromAddress returns the integer ID from an ID address.
func MustIDFromAddress(a address.Address) uint64 {
if a.Protocol() != address.ID {
panic("must be ID protocol address")
}
id, _, err := varint.FromUvarint(a.Payload())
if err != nil {
panic(err)
}
return id
}

82
tvx/builders/asserter.go Normal file
View File

@ -0,0 +1,82 @@
package builders
import (
"fmt"
"os"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/stretchr/testify/require"
)
// Asserter offers useful assertions to verify outcomes at various stages of
// the test vector creation.
type Asserter struct {
*require.Assertions
b *Builder
stage Stage
}
var _ require.TestingT = &Asserter{}
func newAsserter(b *Builder, stage Stage) *Asserter {
a := &Asserter{stage: stage, b: b}
a.Assertions = require.New(a)
return a
}
// In is assert fluid version of require.Contains. It inverts the argument order,
// such that the admissible set can be supplied through assert variadic argument.
func (a *Asserter) In(v interface{}, set ...interface{}) {
a.Contains(set, v, "set %v does not contain element %v", set, v)
}
// BalanceEq verifies that the balance of the address equals the expected one.
func (a *Asserter) BalanceEq(addr address.Address, expected abi.TokenAmount) {
actor, err := a.b.StateTree.GetActor(addr)
a.NoError(err, "failed to fetch actor %s from state", addr)
a.Equal(expected, actor.Balance, "balances mismatch for address %s", addr)
}
// NonceEq verifies that the nonce of the actor equals the expected one.
func (a *Asserter) NonceEq(addr address.Address, expected uint64) {
actor, err := a.b.StateTree.GetActor(addr)
a.NoError(err, "failed to fetch actor %s from state", addr)
a.Equal(expected, actor.Nonce, "expected actor %s nonce: %d, got: %d", addr, expected, actor.Nonce)
}
// ActorExists verifies that the actor exists in the state tree.
func (a *Asserter) ActorExists(addr address.Address) {
_, err := a.b.StateTree.GetActor(addr)
a.NoError(err, "expected no error while looking up actor %s", addr)
}
// ActorExists verifies that the actor is absent from the state tree.
func (a *Asserter) ActorMissing(addr address.Address) {
_, err := a.b.StateTree.GetActor(addr)
a.Error(err, "expected error while looking up actor %s", addr)
}
// EveryMessageResultSatisfies verifies that every message result satisfies the
// provided predicate.
func (a *Asserter) EveryMessageResultSatisfies(predicate ApplyRetPredicate, except ...*ApplicableMessage) {
exceptm := make(map[*ApplicableMessage]struct{}, len(except))
for _, am := range except {
exceptm[am] = struct{}{}
}
for i, m := range a.b.Messages.messages {
if _, ok := exceptm[m]; ok {
continue
}
a.NoError(predicate(m.Result), "message result predicate failed on message %d", i)
}
}
func (a *Asserter) FailNow() {
os.Exit(1)
}
func (a *Asserter) Errorf(format string, args ...interface{}) {
fmt.Printf("%s: "+format, append([]interface{}{a.stage}, args...))
}

194
tvx/builders/builder.go Normal file
View File

@ -0,0 +1,194 @@
package builders
import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"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(ioutil.Discard)
log.SetFlags(0)
}
// 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,
})
fmt.Println(am.Result.ExitCode)
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
}

153
tvx/builders/messages.go Normal file
View File

@ -0,0 +1,153 @@
package builders
import (
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
)
// TypedCall represents a call to a known built-in actor kind.
type TypedCall func() (method abi.MethodNum, params []byte)
// Messages accumulates the messages to be executed within the test vector.
type Messages struct {
b *Builder
defaults msgOpts
messages []*ApplicableMessage
}
// SetDefaults sets default options for all messages.
func (m *Messages) SetDefaults(opts ...MsgOpt) *Messages {
for _, opt := range opts {
opt(&m.defaults)
}
return m
}
// ApplicableMessage represents a message to be applied on the test vector.
type ApplicableMessage struct {
Epoch abi.ChainEpoch
Message *types.Message
Result *vm.ApplyRet
}
func (m *Messages) Sugar() *sugarMsg {
return &sugarMsg{m}
}
// All returns all ApplicableMessages that have been accumulated, in the same
// order they were added.
func (m *Messages) All() []*ApplicableMessage {
return m.messages
}
// Typed adds a typed call to this message accumulator.
func (m *Messages) Typed(from, to address.Address, typedm TypedCall, opts ...MsgOpt) *ApplicableMessage {
method, params := typedm()
return m.Raw(from, to, method, params, opts...)
}
// Raw adds a raw message to this message accumulator.
func (m *Messages) Raw(from, to address.Address, method abi.MethodNum, params []byte, opts ...MsgOpt) *ApplicableMessage {
options := m.defaults
for _, opt := range opts {
opt(&options)
}
msg := &types.Message{
To: to,
From: from,
Nonce: options.nonce,
Value: options.value,
Method: method,
Params: params,
GasPrice: options.gasPrice,
GasLimit: options.gasLimit,
}
am := &ApplicableMessage{
Epoch: options.epoch,
Message: msg,
}
m.messages = append(m.messages, am)
return am
}
// ApplyOne applies the provided message. The following constraints are checked:
// - all previous messages have been applied.
// - we know about this message (i.e. it has been added through Typed, Raw or Sugar).
func (m *Messages) ApplyOne(am *ApplicableMessage) {
var found bool
for _, other := range m.messages {
if am == other {
// we have scanned all preceding messages, and verified they had been applied.
// we are ready to perform the application.
found = true
break
}
// verify that preceding messages have been applied.
// this will abort if unsatisfied.
m.b.Assert.Nil(other.Result, "preceding messages must have been applied when calling Apply*; first unapplied: %v", other)
}
m.b.Assert.True(found, "ApplicableMessage not found")
m.b.applyMessage(am)
}
// ApplyN calls ApplyOne for the supplied messages, in the order they are passed.
// The constraints described in ApplyOne apply.
func (m *Messages) ApplyN(ams ...*ApplicableMessage) {
for _, am := range ams {
m.ApplyOne(am)
}
}
type msgOpts struct {
nonce uint64
value big.Int
gasPrice big.Int
gasLimit int64
epoch abi.ChainEpoch
}
// MsgOpt is an option configuring message value, gas parameters, execution
// epoch, and other elements.
type MsgOpt func(*msgOpts)
// Value sets a value on a message.
func Value(value big.Int) MsgOpt {
return func(opts *msgOpts) {
opts.value = value
}
}
// Nonce sets the nonce of a message.
func Nonce(n uint64) MsgOpt {
return func(opts *msgOpts) {
opts.nonce = n
}
}
// GasLimit sets the gas limit of a message.
func GasLimit(limit int64) MsgOpt {
return func(opts *msgOpts) {
opts.gasLimit = limit
}
}
// GasPrice sets the gas price of a message.
func GasPrice(price int64) MsgOpt {
return func(opts *msgOpts) {
opts.gasPrice = big.NewInt(price)
}
}
// Epoch sets the epoch in which a message is to be executed.
func Epoch(epoch abi.ChainEpoch) MsgOpt {
return func(opts *msgOpts) {
opts.epoch = epoch
}
}

View File

@ -0,0 +1,56 @@
package builders
import (
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/builtin"
init_ "github.com/filecoin-project/specs-actors/actors/builtin/init"
"github.com/filecoin-project/specs-actors/actors/builtin/multisig"
"github.com/filecoin-project/specs-actors/actors/builtin/paych"
"github.com/filecoin-project/specs-actors/actors/builtin/power"
"github.com/libp2p/go-libp2p-core/peer"
)
type sugarMsg struct{ m *Messages }
// Transfer enlists a value transfer message.
func (s *sugarMsg) Transfer(from, to address.Address, opts ...MsgOpt) *ApplicableMessage {
return s.m.Typed(from, to, Transfer(), opts...)
}
func (s *sugarMsg) CreatePaychActor(from, to address.Address, opts ...MsgOpt) *ApplicableMessage {
ctorparams := &paych.ConstructorParams{
From: from,
To: to,
}
return s.m.Typed(from, builtin.InitActorAddr, InitExec(&init_.ExecParams{
CodeCID: builtin.PaymentChannelActorCodeID,
ConstructorParams: MustSerialize(ctorparams),
}), opts...)
}
func (s *sugarMsg) CreateMultisigActor(from address.Address, signers []address.Address, unlockDuration abi.ChainEpoch, numApprovals uint64, opts ...MsgOpt) *ApplicableMessage {
ctorparams := &multisig.ConstructorParams{
Signers: signers,
NumApprovalsThreshold: numApprovals,
UnlockDuration: unlockDuration,
}
return s.m.Typed(from, builtin.InitActorAddr, InitExec(&init_.ExecParams{
CodeCID: builtin.MultisigActorCodeID,
ConstructorParams: MustSerialize(ctorparams),
}), opts...)
}
func (s *sugarMsg) CreateMinerActor(owner, worker address.Address, sealProofType abi.RegisteredSealProof, pid peer.ID, maddrs []abi.Multiaddrs, opts ...MsgOpt) *ApplicableMessage {
params := &power.CreateMinerParams{
Worker: worker,
Owner: owner,
SealProofType: sealProofType,
Peer: abi.PeerID(pid),
Multiaddrs: maddrs,
}
return s.m.Typed(owner, builtin.StoragePowerActorAddr, PowerCreateMiner(params), opts...)
}

View File

@ -0,0 +1,362 @@
package builders
import (
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/builtin/cron"
init_ "github.com/filecoin-project/specs-actors/actors/builtin/init"
"github.com/filecoin-project/specs-actors/actors/builtin/market"
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
"github.com/filecoin-project/specs-actors/actors/builtin/multisig"
"github.com/filecoin-project/specs-actors/actors/builtin/paych"
"github.com/filecoin-project/specs-actors/actors/builtin/power"
"github.com/filecoin-project/specs-actors/actors/builtin/reward"
)
func Transfer() TypedCall {
return func() (method abi.MethodNum, params []byte) {
return builtin.MethodSend, []byte{}
}
}
// ----------------------------------------------------------------------------
// | ACCOUNT
// ----------------------------------------------------------------------------
func AccountConstructor(params *address.Address) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsAccount.Constructor, MustSerialize(params)
}
}
func AccountPubkeyAddress(params *adt.EmptyValue) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsAccount.PubkeyAddress, MustSerialize(params)
}
}
// ----------------------------------------------------------------------------
// | MARKET
// ----------------------------------------------------------------------------
func MarketConstructor(params *adt.EmptyValue) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMarket.Constructor, MustSerialize(params)
}
}
func MarketAddBalance(params *address.Address) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMarket.AddBalance, MustSerialize(params)
}
}
func MarketWithdrawBalance(params *market.WithdrawBalanceParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMarket.WithdrawBalance, MustSerialize(params)
}
}
func MarketPublishStorageDeals(params *market.PublishStorageDealsParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMarket.PublishStorageDeals, MustSerialize(params)
}
}
func MarketVerifyDealsForActivation(params *market.VerifyDealsForActivationParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMarket.VerifyDealsForActivation, MustSerialize(params)
}
}
func MarketActivateDeals(params *market.ActivateDealsParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMarket.ActivateDeals, MustSerialize(params)
}
}
func MarketOnMinerSectorsTerminate(params *market.OnMinerSectorsTerminateParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMarket.OnMinerSectorsTerminate, MustSerialize(params)
}
}
func MarketComputeDataCommitment(params *market.ComputeDataCommitmentParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMarket.ComputeDataCommitment, MustSerialize(params)
}
}
func MarketCronTick(params *adt.EmptyValue) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMarket.CronTick, MustSerialize(params)
}
}
// ----------------------------------------------------------------------------
// | MINER
// ----------------------------------------------------------------------------
func MinerConstructor(params *power.MinerConstructorParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.Constructor, MustSerialize(params)
}
}
func MinerControlAddresses(params *adt.EmptyValue) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.ControlAddresses, MustSerialize(params)
}
}
func MinerChangeWorkerAddress(params *miner.ChangeWorkerAddressParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.ChangeWorkerAddress, MustSerialize(params)
}
}
func MinerChangePeerID(params *miner.ChangePeerIDParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.ChangePeerID, MustSerialize(params)
}
}
func MinerSubmitWindowedPoSt(params *miner.SubmitWindowedPoStParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.SubmitWindowedPoSt, MustSerialize(params)
}
}
func MinerPreCommitSector(params *miner.SectorPreCommitInfo) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.PreCommitSector, MustSerialize(params)
}
}
func MinerProveCommitSector(params *miner.ProveCommitSectorParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.ProveCommitSector, MustSerialize(params)
}
}
func MinerExtendSectorExpiration(params *miner.ExtendSectorExpirationParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.ExtendSectorExpiration, MustSerialize(params)
}
}
func MinerTerminateSectors(params *miner.TerminateSectorsParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.TerminateSectors, MustSerialize(params)
}
}
func MinerDeclareFaults(params *miner.DeclareFaultsParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.DeclareFaults, MustSerialize(params)
}
}
func MinerDeclareFaultsRecovered(params *miner.DeclareFaultsRecoveredParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.DeclareFaultsRecovered, MustSerialize(params)
}
}
func MinerOnDeferredCronEvent(params *miner.CronEventPayload) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.OnDeferredCronEvent, MustSerialize(params)
}
}
func MinerCheckSectorProven(params *miner.CheckSectorProvenParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.CheckSectorProven, MustSerialize(params)
}
}
func MinerAddLockedFund(params *big.Int) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.AddLockedFund, MustSerialize(params)
}
}
func MinerReportConsensusFault(params *miner.ReportConsensusFaultParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.ReportConsensusFault, MustSerialize(params)
}
}
func MinerWithdrawBalance(params *miner.WithdrawBalanceParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.WithdrawBalance, MustSerialize(params)
}
}
func MinerConfirmSectorProofsValid(params *builtin.ConfirmSectorProofsParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.ConfirmSectorProofsValid, MustSerialize(params)
}
}
func MinerChangeMultiaddrs(params *miner.ChangeMultiaddrsParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMiner.ChangeMultiaddrs, MustSerialize(params)
}
}
// ----------------------------------------------------------------------------
// | MULTISIG
// ----------------------------------------------------------------------------
func MultisigConstructor(params *multisig.ConstructorParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMultisig.Constructor, MustSerialize(params)
}
}
func MultisigPropose(params *multisig.ProposeParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMultisig.Propose, MustSerialize(params)
}
}
func MultisigApprove(params *multisig.TxnIDParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMultisig.Approve, MustSerialize(params)
}
}
func MultisigCancel(params *multisig.TxnIDParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMultisig.Cancel, MustSerialize(params)
}
}
func MultisigAddSigner(params *multisig.AddSignerParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMultisig.AddSigner, MustSerialize(params)
}
}
func MultisigRemoveSigner(params *multisig.RemoveSignerParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMultisig.RemoveSigner, MustSerialize(params)
}
}
func MultisigSwapSigner(params *multisig.SwapSignerParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMultisig.SwapSigner, MustSerialize(params)
}
}
func MultisigChangeNumApprovalsThreshold(params *multisig.ChangeNumApprovalsThresholdParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsMultisig.ChangeNumApprovalsThreshold, MustSerialize(params)
}
}
// ----------------------------------------------------------------------------
// | POWER
// ----------------------------------------------------------------------------
func PowerConstructor(params *adt.EmptyValue) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsPower.Constructor, MustSerialize(params)
}
}
func PowerCreateMiner(params *power.CreateMinerParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsPower.CreateMiner, MustSerialize(params)
}
}
func PowerUpdateClaimedPower(params *power.UpdateClaimedPowerParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsPower.UpdateClaimedPower, MustSerialize(params)
}
}
func PowerEnrollCronEvent(params *power.EnrollCronEventParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsPower.EnrollCronEvent, MustSerialize(params)
}
}
func PowerOnEpochTickEnd(params *adt.EmptyValue) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsPower.OnEpochTickEnd, MustSerialize(params)
}
}
func PowerUpdatePledgeTotal(params *big.Int) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsPower.UpdatePledgeTotal, MustSerialize(params)
}
}
func PowerOnConsensusFault(params *big.Int) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsPower.OnConsensusFault, MustSerialize(params)
}
}
func PowerSubmitPoRepForBulkVerify(params *abi.SealVerifyInfo) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsPower.SubmitPoRepForBulkVerify, MustSerialize(params)
}
}
func PowerCurrentTotalPower(params *adt.EmptyValue) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsPower.CurrentTotalPower, MustSerialize(params)
}
}
// ----------------------------------------------------------------------------
// | REWARD
// ----------------------------------------------------------------------------
func RewardConstructor(params *adt.EmptyValue) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsReward.Constructor, MustSerialize(params)
}
}
func RewardAwardBlockReward(params *reward.AwardBlockRewardParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsReward.AwardBlockReward, MustSerialize(params)
}
}
func RewardThisEpochReward(params *adt.EmptyValue) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsReward.ThisEpochReward, MustSerialize(params)
}
}
func RewardUpdateNetworkKPI(params *big.Int) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsReward.UpdateNetworkKPI, MustSerialize(params)
}
}
// ----------------------------------------------------------------------------
// | PAYCH
// ----------------------------------------------------------------------------
func PaychConstructor(params *paych.ConstructorParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsPaych.Constructor, MustSerialize(params)
}
}
func PaychUpdateChannelState(params *paych.UpdateChannelStateParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsPaych.UpdateChannelState, MustSerialize(params)
}
}
func PaychSettle(params *adt.EmptyValue) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsPaych.Settle, MustSerialize(params)
}
}
func PaychCollect(params *adt.EmptyValue) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsPaych.Collect, MustSerialize(params)
}
}
// ----------------------------------------------------------------------------
// | CRON
// ----------------------------------------------------------------------------
func CronConstructor(params *cron.ConstructorParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsCron.Constructor, MustSerialize(params)
}
}
func CronEpochTick(params *adt.EmptyValue) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsCron.EpochTick, MustSerialize(params)
}
}
// ----------------------------------------------------------------------------
// | INIT
// ----------------------------------------------------------------------------
func InitConstructor(params *init_.ConstructorParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsInit.Constructor, MustSerialize(params)
}
}
func InitExec(params *init_.ExecParams) TypedCall {
return func() (abi.MethodNum, []byte) {
return builtin.MethodsInit.Exec, MustSerialize(params)
}
}

View File

@ -0,0 +1,19 @@
package builders
import (
"fmt"
"github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
)
type ApplyRetPredicate func(ret *vm.ApplyRet) error
func ExitCode(expect exitcode.ExitCode) ApplyRetPredicate {
return func(ret *vm.ApplyRet) error {
if ret.ExitCode == expect {
return nil
}
return fmt.Errorf("message exit code was %d; expected %d", ret.ExitCode, expect)
}
}

View File

@ -0,0 +1,38 @@
package builders
import (
"bytes"
"fmt"
cbg "github.com/whyrusleeping/cbor-gen"
)
func MustSerialize(i cbg.CBORMarshaler) []byte {
out, err := Serialize(i)
if err != nil {
panic(err)
}
return out
}
func Serialize(i cbg.CBORMarshaler) ([]byte, error) {
buf := new(bytes.Buffer)
if err := i.MarshalCBOR(buf); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func MustDeserialize(b []byte, out interface{}) {
if err := Deserialize(b, out); err != nil {
panic(err)
}
}
func Deserialize(b []byte, out interface{}) error {
um, ok := out.(cbg.CBORUnmarshaler)
if !ok {
return fmt.Errorf("type %T does not implement UnmarshalCBOR", out)
}
return um.UnmarshalCBOR(bytes.NewReader(b))
}

172
tvx/builders/state_zero.go Normal file
View File

@ -0,0 +1,172 @@
package builders
import (
"context"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-bitfield"
abi_spec "github.com/filecoin-project/specs-actors/actors/abi"
big_spec "github.com/filecoin-project/specs-actors/actors/abi/big"
builtin_spec "github.com/filecoin-project/specs-actors/actors/builtin"
account_spec "github.com/filecoin-project/specs-actors/actors/builtin/account"
cron_spec "github.com/filecoin-project/specs-actors/actors/builtin/cron"
init_spec "github.com/filecoin-project/specs-actors/actors/builtin/init"
market_spec "github.com/filecoin-project/specs-actors/actors/builtin/market"
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
power_spec "github.com/filecoin-project/specs-actors/actors/builtin/power"
reward_spec "github.com/filecoin-project/specs-actors/actors/builtin/reward"
"github.com/filecoin-project/specs-actors/actors/builtin/system"
runtime_spec "github.com/filecoin-project/specs-actors/actors/runtime"
adt_spec "github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/ipfs/go-cid"
)
const (
totalFilecoin = 2_000_000_000
filecoinPrecision = 1_000_000_000_000_000_000
)
var (
TotalNetworkBalance = big_spec.Mul(big_spec.NewInt(totalFilecoin), big_spec.NewInt(filecoinPrecision))
EmptyReturnValue = []byte{}
)
var (
// initialized by calling initializeStoreWithAdtRoots
EmptyArrayCid cid.Cid
EmptyDeadlinesCid cid.Cid
EmptyMapCid cid.Cid
EmptyMultiMapCid cid.Cid
EmptyBitfieldCid cid.Cid
)
const (
TestSealProofType = abi_spec.RegisteredSealProof_StackedDrg2KiBV1
)
func (b *Builder) initializeZeroState() {
if err := insertEmptyStructures(b.Stores.ADTStore); err != nil {
panic(err)
}
type ActorState struct {
Addr address.Address
Balance abi_spec.TokenAmount
Code cid.Cid
State runtime_spec.CBORMarshaler
}
var actors []ActorState
actors = append(actors, ActorState{
Addr: builtin_spec.InitActorAddr,
Balance: big_spec.Zero(),
Code: builtin_spec.InitActorCodeID,
State: init_spec.ConstructState(EmptyMapCid, "chain-validation"),
})
zeroRewardState := reward_spec.ConstructState(big_spec.Zero())
zeroRewardState.ThisEpochReward = big_spec.NewInt(1e17)
actors = append(actors, ActorState{
Addr: builtin_spec.RewardActorAddr,
Balance: TotalNetworkBalance,
Code: builtin_spec.RewardActorCodeID,
State: zeroRewardState,
})
actors = append(actors, ActorState{
Addr: builtin_spec.BurntFundsActorAddr,
Balance: big_spec.Zero(),
Code: builtin_spec.AccountActorCodeID,
State: &account_spec.State{Address: builtin_spec.BurntFundsActorAddr},
})
actors = append(actors, ActorState{
Addr: builtin_spec.StoragePowerActorAddr,
Balance: big_spec.Zero(),
Code: builtin_spec.StoragePowerActorCodeID,
State: power_spec.ConstructState(EmptyMapCid, EmptyMultiMapCid),
})
actors = append(actors, ActorState{
Addr: builtin_spec.StorageMarketActorAddr,
Balance: big_spec.Zero(),
Code: builtin_spec.StorageMarketActorCodeID,
State: &market_spec.State{
Proposals: EmptyArrayCid,
States: EmptyArrayCid,
PendingProposals: EmptyMapCid,
EscrowTable: EmptyMapCid,
LockedTable: EmptyMapCid,
NextID: abi_spec.DealID(0),
DealOpsByEpoch: EmptyMultiMapCid,
LastCron: 0,
},
})
actors = append(actors, ActorState{
Addr: builtin_spec.SystemActorAddr,
Balance: big_spec.Zero(),
Code: builtin_spec.SystemActorCodeID,
State: &system.State{},
})
actors = append(actors, ActorState{
Addr: builtin_spec.CronActorAddr,
Balance: big_spec.Zero(),
Code: builtin_spec.CronActorCodeID,
State: &cron_spec.State{Entries: []cron_spec.Entry{
{
Receiver: builtin_spec.StoragePowerActorAddr,
MethodNum: builtin_spec.MethodsPower.OnEpochTickEnd,
},
}},
})
for _, act := range actors {
_ = b.Actors.CreateActor(act.Code, act.Addr, act.Balance, act.State)
}
}
func insertEmptyStructures(store adt_spec.Store) error {
var err error
_, err = store.Put(context.TODO(), []struct{}{})
if err != nil {
return err
}
EmptyArrayCid, err = adt_spec.MakeEmptyArray(store).Root()
if err != nil {
return err
}
EmptyMapCid, err = adt_spec.MakeEmptyMap(store).Root()
if err != nil {
return err
}
EmptyMultiMapCid, err = adt_spec.MakeEmptyMultimap(store).Root()
if err != nil {
return err
}
EmptyDeadlinesCid, err = store.Put(context.TODO(), &miner.Deadline{
Partitions: EmptyArrayCid,
ExpirationsEpochs: EmptyArrayCid,
PostSubmissions: abi_spec.NewBitField(),
EarlyTerminations: abi_spec.NewBitField(),
LiveSectors: 0,
})
if err != nil {
return err
}
emptyBitfield := bitfield.NewFromSet(nil)
EmptyBitfieldCid, err = store.Put(context.TODO(), emptyBitfield)
if err != nil {
return err
}
return nil
}

105
tvx/builders/wallet.go Normal file
View File

@ -0,0 +1,105 @@
package builders
import (
"fmt"
"math/rand"
"github.com/minio/blake2b-simd"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-crypto"
acrypto "github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/filecoin-project/lotus/chain/wallet"
"github.com/filecoin-project/lotus/chain/types"
)
type Wallet struct {
// Private keys by address
keys map[address.Address]*wallet.Key
// Seed for deterministic secp key generation.
secpSeed int64
// Seed for deterministic bls key generation.
blsSeed int64 // nolint: structcheck
}
func newWallet() *Wallet {
return &Wallet{
keys: make(map[address.Address]*wallet.Key),
secpSeed: 0,
}
}
func (w *Wallet) NewSECP256k1Account() address.Address {
secpKey := w.newSecp256k1Key()
w.keys[secpKey.Address] = secpKey
return secpKey.Address
}
func (w *Wallet) NewBLSAccount() address.Address {
blsKey := w.newBLSKey()
w.keys[blsKey.Address] = blsKey
return blsKey.Address
}
func (w *Wallet) Sign(addr address.Address, data []byte) (acrypto.Signature, error) {
ki, ok := w.keys[addr]
if !ok {
return acrypto.Signature{}, fmt.Errorf("unknown address %v", addr)
}
var sigType acrypto.SigType
if ki.Type == wallet.KTSecp256k1 {
sigType = acrypto.SigTypeBLS
hashed := blake2b.Sum256(data)
sig, err := crypto.Sign(ki.PrivateKey, hashed[:])
if err != nil {
return acrypto.Signature{}, err
}
return acrypto.Signature{
Type: sigType,
Data: sig,
}, nil
} else if ki.Type == wallet.KTBLS {
panic("lotus validator cannot sign BLS messages")
} else {
panic("unknown signature type")
}
}
func (w *Wallet) newSecp256k1Key() *wallet.Key {
randSrc := rand.New(rand.NewSource(w.secpSeed))
prv, err := crypto.GenerateKeyFromSeed(randSrc)
if err != nil {
panic(err)
}
w.secpSeed++
key, err := wallet.NewKey(types.KeyInfo{
Type: wallet.KTSecp256k1,
PrivateKey: prv,
})
if err != nil {
panic(err)
}
return key
}
func (w *Wallet) newBLSKey() *wallet.Key {
// FIXME: bls needs deterministic key generation
//sk := ffi.PrivateKeyGenerate(s.blsSeed)
// s.blsSeed++
sk := [32]byte{}
sk[0] = uint8(w.blsSeed) // hack to keep gas values determinist
w.blsSeed++
key, err := wallet.NewKey(types.KeyInfo{
Type: wallet.KTBLS,
PrivateKey: sk[:],
})
if err != nil {
panic(err)
}
return key
}

View File

@ -2,7 +2,7 @@ package lotus
import ( import (
"context" "context"
"fmt" "log"
"github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
@ -24,7 +24,7 @@ func NewDriver(ctx context.Context) *Driver {
} }
func (d *Driver) ExecuteMessage(msg *types.Message, preroot cid.Cid, bs blockstore.Blockstore, epoch abi.ChainEpoch) (*vm.ApplyRet, cid.Cid, error) { func (d *Driver) ExecuteMessage(msg *types.Message, preroot cid.Cid, bs blockstore.Blockstore, epoch abi.ChainEpoch) (*vm.ApplyRet, cid.Cid, error) {
fmt.Println("execution sanity check") log.Println("execution sanity check")
cst := cbor.NewCborStore(bs) cst := cbor.NewCborStore(bs)
st, err := state.LoadStateTree(cst, preroot) st, err := state.LoadStateTree(cst, preroot)
if err != nil { if err != nil {
@ -33,12 +33,12 @@ func (d *Driver) ExecuteMessage(msg *types.Message, preroot cid.Cid, bs blocksto
actor, err := st.GetActor(msg.From) actor, err := st.GetActor(msg.From)
if err != nil { if err != nil {
fmt.Println("from actor not found: ", msg.From) log.Println("from actor not found: ", msg.From)
} else { } else {
fmt.Println("from actor found: ", actor) log.Println("from actor found: ", actor)
} }
fmt.Println("creating vm") log.Println("creating vm")
lvm, err := vm.NewVM(preroot, epoch, &vmRand{}, bs, mkFakedSigSyscalls(vm.Syscalls(ffiwrapper.ProofVerifier)), nil) lvm, err := vm.NewVM(preroot, epoch, &vmRand{}, bs, mkFakedSigSyscalls(vm.Syscalls(ffiwrapper.ProofVerifier)), nil)
if err != nil { if err != nil {
return nil, cid.Undef, err return nil, cid.Undef, err
@ -51,15 +51,15 @@ func (d *Driver) ExecuteMessage(msg *types.Message, preroot cid.Cid, bs blocksto
return nil, cid.Undef, err return nil, cid.Undef, err
} }
fmt.Println("applying message") log.Println("applying message")
ret, err := lvm.ApplyMessage(d.ctx, msg) ret, err := lvm.ApplyMessage(d.ctx, msg)
if err != nil { if err != nil {
return nil, cid.Undef, err return nil, cid.Undef, err
} }
fmt.Printf("applied message: %+v\n", ret) log.Printf("applied message: %+v\n", ret)
fmt.Println("flushing") log.Println("flushing")
root, err := lvm.Flush(d.ctx) root, err := lvm.Flush(d.ctx)
return ret, root, err return ret, root, err
} }

View File

@ -0,0 +1,247 @@
package main
import (
"os"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/filecoin-project/specs-actors/actors/builtin/paych"
"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
. "github.com/filecoin-project/oni/tvx/builders"
"github.com/filecoin-project/oni/tvx/schema"
)
var (
unknown = MustNewIDAddr(10000000)
balance1T = abi.NewTokenAmount(1_000_000_000_000)
transferAmnt = abi.NewTokenAmount(10)
)
func main() {
failCoverReceiptGasCost()
failCoverOnChainSizeGasCost()
failUnknownSender()
failInvalidActorNonce()
failInvalidReceiverMethod()
failInexistentReceiver()
failCoverTransferAccountCreationGasStepwise()
failActorExecutionAborted()
}
func failCoverReceiptGasCost() {
metadata := &schema.Metadata{
ID: "msg-apply-fail-receipt-gas",
Version: "v1",
Desc: "fail to cover gas cost for message receipt on chain",
}
v := MessageVector(metadata)
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
alice := v.Actors.Account(address.SECP256K1, balance1T)
v.CommitPreconditions()
v.Messages.Sugar().Transfer(alice.ID, alice.ID, Value(transferAmnt), Nonce(0), GasLimit(8))
v.CommitApplies()
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.SysErrOutOfGas))
v.Finish(os.Stdout)
}
func failCoverOnChainSizeGasCost() {
metadata := &schema.Metadata{
ID: "msg-apply-fail-onchainsize-gas",
Version: "v1",
Desc: "not enough gas to pay message on-chain-size cost",
}
v := MessageVector(metadata)
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(10))
alice := v.Actors.Account(address.SECP256K1, balance1T)
v.CommitPreconditions()
v.Messages.Sugar().Transfer(alice.ID, alice.ID, Value(transferAmnt), Nonce(0), GasLimit(1))
v.CommitApplies()
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.SysErrOutOfGas))
v.Finish(os.Stdout)
}
func failUnknownSender() {
metadata := &schema.Metadata{
ID: "msg-apply-fail-unknown-sender",
Version: "v1",
Desc: "fail due to lack of gas when sender is unknown",
}
v := MessageVector(metadata)
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
alice := v.Actors.Account(address.SECP256K1, balance1T)
v.CommitPreconditions()
v.Messages.Sugar().Transfer(unknown, alice.ID, Value(transferAmnt), Nonce(0))
v.CommitApplies()
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.SysErrSenderInvalid))
v.Finish(os.Stdout)
}
func failInvalidActorNonce() {
metadata := &schema.Metadata{
ID: "msg-apply-fail-invalid-nonce",
Version: "v1",
Desc: "invalid actor nonce",
}
v := MessageVector(metadata)
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
alice := v.Actors.Account(address.SECP256K1, balance1T)
v.CommitPreconditions()
// invalid nonce from known account.
msg1 := v.Messages.Sugar().Transfer(alice.ID, alice.ID, Value(transferAmnt), Nonce(1))
// invalid nonce from an unknown account.
msg2 := v.Messages.Sugar().Transfer(unknown, alice.ID, Value(transferAmnt), Nonce(1))
v.CommitApplies()
v.Assert.Equal(msg1.Result.ExitCode, exitcode.SysErrSenderStateInvalid)
v.Assert.Equal(msg2.Result.ExitCode, exitcode.SysErrSenderInvalid)
v.Finish(os.Stdout)
}
func failInvalidReceiverMethod() {
metadata := &schema.Metadata{
ID: "msg-apply-fail-invalid-receiver-method",
Version: "v1",
Desc: "invalid receiver method",
}
v := MessageVector(metadata)
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
alice := v.Actors.Account(address.SECP256K1, balance1T)
v.CommitPreconditions()
v.Messages.Typed(alice.ID, alice.ID, MarketComputeDataCommitment(nil), Nonce(0), Value(big.Zero()))
v.CommitApplies()
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.SysErrInvalidMethod))
v.Finish(os.Stdout)
}
func failInexistentReceiver() {
metadata := &schema.Metadata{
ID: "msg-apply-fail-inexistent-receiver",
Version: "v1",
Desc: "inexistent receiver",
Comment: `Note that this test is not a valid message, since it is using
an unknown actor. However in the event that an invalid message isn't filtered by
block validation we need to ensure behaviour is consistent across VM implementations.`,
}
v := MessageVector(metadata)
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
alice := v.Actors.Account(address.SECP256K1, balance1T)
v.CommitPreconditions()
// Sending a message to non-existent ID address must produce an error.
unknownID := MustNewIDAddr(10000000)
v.Messages.Sugar().Transfer(alice.ID, unknownID, Value(transferAmnt), Nonce(0))
unknownActor := MustNewActorAddr("1234")
v.Messages.Sugar().Transfer(alice.ID, unknownActor, Value(transferAmnt), Nonce(1))
v.CommitApplies()
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.SysErrInvalidReceiver))
v.Finish(os.Stdout)
}
func failCoverTransferAccountCreationGasStepwise() {
metadata := &schema.Metadata{
ID: "msg-apply-fail-transfer-accountcreation-gas",
Version: "v1",
Desc: "fail not enough gas to cover account actor creation on transfer",
}
v := MessageVector(metadata)
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
var alice, bob, charlie AddressHandle
alice = v.Actors.Account(address.SECP256K1, balance1T)
bob.Robust, charlie.Robust = MustNewSECP256K1Addr("1"), MustNewSECP256K1Addr("2")
v.CommitPreconditions()
var nonce uint64
ref := v.Messages.Sugar().Transfer(alice.Robust, bob.Robust, Value(transferAmnt), Nonce(nonce))
nonce++
v.Messages.ApplyOne(ref)
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.Ok))
// decrease the gas cost by `gasStep` for each apply and ensure `SysErrOutOfGas` is always returned.
trueGas := ref.Result.GasUsed
gasStep := trueGas / 100
for tryGas := trueGas - gasStep; tryGas > 0; tryGas -= gasStep {
v.Messages.Sugar().Transfer(alice.Robust, charlie.Robust, Value(transferAmnt), Nonce(nonce), GasPrice(1), GasLimit(tryGas))
nonce++
}
v.CommitApplies()
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.SysErrOutOfGas), ref)
v.Finish(os.Stdout)
}
func failActorExecutionAborted() {
metadata := &schema.Metadata{
ID: "msg-apply-fail-actor-execution-illegal-arg",
Version: "v1",
Desc: "abort during actor execution due to illegal argument",
}
// Set up sender and receiver accounts.
var sender, receiver AddressHandle
var paychAddr AddressHandle
v := MessageVector(metadata)
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
v.Actors.AccountN(address.SECP256K1, balance1T, &sender, &receiver)
paychAddr = AddressHandle{
ID: MustNewIDAddr(MustIDFromAddress(receiver.ID) + 1),
Robust: sender.NextActorAddress(0, 0),
}
v.CommitPreconditions()
// Construct the payment channel.
createMsg := v.Messages.Sugar().CreatePaychActor(sender.Robust, receiver.Robust, Value(abi.NewTokenAmount(10_000)))
// Update the payment channel.
updateMsg := v.Messages.Typed(sender.Robust, paychAddr.Robust, PaychUpdateChannelState(&paych.UpdateChannelStateParams{
Sv: paych.SignedVoucher{
ChannelAddr: paychAddr.Robust,
TimeLockMin: abi.ChainEpoch(10),
Lane: 123,
Nonce: 1,
Amount: big.NewInt(10),
Signature: &crypto.Signature{
Type: crypto.SigTypeBLS,
Data: []byte("Grrr im an invalid signature, I cause panics in the payment channel actor"),
},
}}), Nonce(1), Value(big.Zero()))
v.CommitApplies()
v.Assert.Equal(exitcode.Ok, createMsg.Result.ExitCode)
v.Assert.Equal(exitcode.ErrIllegalArgument, updateMsg.Result.ExitCode)
v.Finish(os.Stdout)
}

113
tvx/scripts/nested.go Normal file
View File

@ -0,0 +1,113 @@
package main
import (
"os"
"bytes"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
init_ "github.com/filecoin-project/specs-actors/actors/builtin/init"
builtin "github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/runtime"
//"github.com/filecoin-project/specs-actors/actors/builtin/paych"
"github.com/filecoin-project/specs-actors/actors/builtin/multisig"
//"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
. "github.com/filecoin-project/oni/tvx/builders"
"github.com/filecoin-project/oni/tvx/schema"
//"github.com/davecgh/go-spew/spew"
)
func main() {
nestedSends_OkBasic()
}
func nestedSends_OkBasic() {
var acctDefaultBalance = abi.NewTokenAmount(1_000_000_000_000)
var multisigBalance = abi.NewTokenAmount(1_000_000_000)
nonce := uint64(1)
metadata := &schema.Metadata{ID: "nested-sends-ok-basic", Version: "v1", Desc: ""}
v := MessageVector(metadata)
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
stage := prepareStage(v, acctDefaultBalance, multisigBalance)
balanceBefore := v.Actors.Balance(stage.creator)
// Multisig sends back to the creator.
amtSent := abi.NewTokenAmount(1)
result := stage.sendOk(stage.creator, amtSent, builtin.MethodSend, nil, nonce)
//td.AssertActor(stage.creator, big.Sub(big.Add(balanceBefore, amtSent), result.Receipt.GasUsed.Big()), nonce+1)
v.Assert.NonceEq(stage.creator, nonce+1)
v.Assert.BalanceEq(stage.creator, big.Sub(big.Add(balanceBefore, amtSent), big.NewInt(result.MessageReceipt.GasUsed)))
v.Finish(os.Stdout)
}
type msStage struct {
v *Builder
creator address.Address // Address of the creator and sole signer of the multisig.
msAddr address.Address // Address of the multisig actor from which nested messages are sent.
}
// Creates a multisig actor with its creator as sole approver.
func prepareStage(v *Builder, creatorBalance, msBalance abi.TokenAmount) *msStage {
// Set up sender and receiver accounts.
creator := v.Actors.Account(address.SECP256K1, creatorBalance)
v.CommitPreconditions()
msg := v.Messages.Sugar().CreateMultisigActor(creator.ID, []address.Address{creator.ID}, 0, 1, Value(msBalance), Nonce(0))
v.Messages.ApplyOne(msg)
v.Assert.Equal(msg.Result.ExitCode, exitcode.Ok)
// Verify init actor return.
var ret init_.ExecReturn
MustDeserialize(msg.Result.Return, &ret)
return &msStage{
v: v,
creator: creator.ID,
msAddr: ret.IDAddress,
}
}
//func (s *msStage) sendOk(to address.Address, value abi.TokenAmount, method abi.MethodNum, params runtime.CBORMarshaler, approverNonce uint64) vtypes.ApplyMessageResult {
func (s *msStage) sendOk(to address.Address, value abi.TokenAmount, method abi.MethodNum, params runtime.CBORMarshaler, approverNonce uint64) *vm.ApplyRet {
buf := bytes.Buffer{}
if params != nil {
err := params.MarshalCBOR(&buf)
if err != nil {
panic(err)
}
//require.NoError(drivers.T, err)
}
pparams := multisig.ProposeParams{
To: to,
Value: value,
Method: method,
Params: buf.Bytes(),
}
msg := s.v.Messages.Typed(s.creator, s.msAddr, MultisigPropose(&pparams), Nonce(approverNonce), Value(big.NewInt(0)))
//result := s.driver.ApplyMessage(msg)
s.v.CommitApplies()
//s.v.Assert.Equal(exitcode_spec.Ok, result.Receipt.ExitCode)
// all messages succeeded.
s.v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.Ok))
return msg.Result
}
//func (s *msStage) state() *multisig.State {
//var msState multisig.State
//s.driver.GetActorState(s.msAddr, &msState)
//return &msState
//}

185
tvx/scripts/paych.go Normal file
View File

@ -0,0 +1,185 @@
package main
import (
"os"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
init_ "github.com/filecoin-project/specs-actors/actors/builtin/init"
"github.com/filecoin-project/specs-actors/actors/builtin/paych"
"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
. "github.com/filecoin-project/oni/tvx/builders"
"github.com/filecoin-project/oni/tvx/schema"
)
var (
balance200B = abi.NewTokenAmount(200_000_000_000)
toSend = abi.NewTokenAmount(10_000)
)
func main() {
happyPathCreate()
happyPathUpdate()
happyPathCollect()
}
func happyPathCreate() {
metadata := &schema.Metadata{ID: "paych-create-ok", Version: "v1", Desc: "payment channel create"}
v := MessageVector(metadata)
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
// Set up sender and receiver accounts.
var sender, receiver AddressHandle
v.Actors.AccountN(address.SECP256K1, balance200B, &sender, &receiver)
v.CommitPreconditions()
// Add the constructor message.
createMsg := v.Messages.Sugar().CreatePaychActor(sender.Robust, receiver.Robust, Value(toSend))
v.CommitApplies()
expectedActorAddr := AddressHandle{
ID: MustNewIDAddr(MustIDFromAddress(receiver.ID) + 1),
Robust: sender.NextActorAddress(0, 0),
}
// Verify init actor return.
var ret init_.ExecReturn
MustDeserialize(createMsg.Result.Return, &ret)
v.Assert.Equal(expectedActorAddr.Robust, ret.RobustAddress)
v.Assert.Equal(expectedActorAddr.ID, ret.IDAddress)
// Verify the paych state.
var state paych.State
actor := v.Actors.ActorState(ret.IDAddress, &state)
v.Assert.Equal(sender.ID, state.From)
v.Assert.Equal(receiver.ID, state.To)
v.Assert.Equal(toSend, actor.Balance)
v.Finish(os.Stdout)
}
func happyPathUpdate() {
metadata := &schema.Metadata{ID: "paych-update-ok", Version: "v1", Desc: "payment channel update"}
var (
timelock = abi.ChainEpoch(0)
lane = uint64(123)
nonce = uint64(1)
amount = big.NewInt(10)
)
// Set up sender and receiver accounts.
var sender, receiver AddressHandle
var paychAddr AddressHandle
v := MessageVector(metadata)
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
v.Actors.AccountN(address.SECP256K1, balance200B, &sender, &receiver)
paychAddr = AddressHandle{
ID: MustNewIDAddr(MustIDFromAddress(receiver.ID) + 1),
Robust: sender.NextActorAddress(0, 0),
}
v.CommitPreconditions()
// Construct the payment channel.
createMsg := v.Messages.Sugar().CreatePaychActor(sender.Robust, receiver.Robust, Value(toSend))
// Update the payment channel.
v.Messages.Typed(sender.Robust, paychAddr.Robust, PaychUpdateChannelState(&paych.UpdateChannelStateParams{
Sv: paych.SignedVoucher{
ChannelAddr: paychAddr.Robust,
TimeLockMin: timelock,
TimeLockMax: 0, // TimeLockMax set to 0 means no timeout
Lane: lane,
Nonce: nonce,
Amount: amount,
MinSettleHeight: 0,
Signature: &crypto.Signature{
Type: crypto.SigTypeBLS,
Data: []byte("signature goes here"), // TODO may need to generate an actual signature
},
}}), Nonce(1), Value(big.Zero()))
v.CommitApplies()
// all messages succeeded.
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.Ok))
// Verify init actor return.
var ret init_.ExecReturn
MustDeserialize(createMsg.Result.Return, &ret)
// Verify the paych state.
var state paych.State
v.Actors.ActorState(ret.RobustAddress, &state)
v.Assert.Len(state.LaneStates, 1)
ls := state.LaneStates[0]
v.Assert.Equal(amount, ls.Redeemed)
v.Assert.Equal(nonce, ls.Nonce)
v.Assert.Equal(lane, ls.ID)
v.Finish(os.Stdout)
}
func happyPathCollect() {
metadata := &schema.Metadata{ID: "paych-collect-ok", Version: "v1", Desc: "payment channel collect"}
v := MessageVector(metadata)
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
// Set up sender and receiver accounts.
var sender, receiver AddressHandle
var paychAddr AddressHandle
v.Actors.AccountN(address.SECP256K1, balance200B, &sender, &receiver)
paychAddr = AddressHandle{
ID: MustNewIDAddr(MustIDFromAddress(receiver.ID) + 1),
Robust: sender.NextActorAddress(0, 0),
}
v.CommitPreconditions()
// Construct the payment channel.
v.Messages.Sugar().CreatePaychActor(sender.Robust, receiver.Robust, Value(toSend))
// Update the payment channel.
v.Messages.Typed(sender.Robust, paychAddr.Robust, PaychUpdateChannelState(&paych.UpdateChannelStateParams{
Sv: paych.SignedVoucher{
ChannelAddr: paychAddr.Robust,
TimeLockMin: 0,
TimeLockMax: 0, // TimeLockMax set to 0 means no timeout
Lane: 1,
Nonce: 1,
Amount: toSend,
MinSettleHeight: 0,
Signature: &crypto.Signature{
Type: crypto.SigTypeBLS,
Data: []byte("signature goes here"), // TODO may need to generate an actual signature
},
}}), Nonce(1), Value(big.Zero()))
settleMsg := v.Messages.Typed(receiver.Robust, paychAddr.Robust, PaychSettle(nil), Value(big.Zero()), Nonce(0))
// advance the epoch so the funds may be redeemed.
collectMsg := v.Messages.Typed(receiver.Robust, paychAddr.Robust, PaychCollect(nil), Value(big.Zero()), Nonce(1), Epoch(paych.SettleDelay))
v.CommitApplies()
// all messages succeeded.
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.Ok))
// receiver_balance = initial_balance + paych_send - settle_paych_msg_gas - collect_paych_msg_gas
gasUsed := big.Add(big.NewInt(settleMsg.Result.MessageReceipt.GasUsed), big.NewInt(collectMsg.Result.MessageReceipt.GasUsed))
v.Assert.BalanceEq(receiver.Robust, big.Sub(big.Add(toSend, balance200B), gasUsed))
// the paych actor should have been deleted after the collect
v.Assert.ActorMissing(paychAddr.Robust)
v.Assert.ActorMissing(paychAddr.ID)
v.Finish(os.Stdout)
}

View File

@ -19,8 +19,10 @@ import (
"github.com/ipfs/go-merkledag" "github.com/ipfs/go-merkledag"
) )
// ProxyingStores implements the ipld store where unknown items are fetched over the node API. // Stores is a collection of the different stores and services that are needed
type ProxyingStores struct { // to deal with the data layer of Filecoin, conveniently interlinked with one
// another.
type Stores struct {
CBORStore cbor.IpldStore CBORStore cbor.IpldStore
ADTStore adt.Store ADTStore adt.Store
Datastore ds.Batching Datastore ds.Batching
@ -30,6 +32,33 @@ type ProxyingStores struct {
DAGService format.DAGService DAGService format.DAGService
} }
func newStores(ctx context.Context, ds ds.Batching, bs blockstore.Blockstore) *Stores {
var (
cborstore = cbor.NewCborStore(bs)
offl = offline.Exchange(bs)
blkserv = blockservice.New(bs, offl)
dserv = merkledag.NewDAGService(blkserv)
)
return &Stores{
CBORStore: cborstore,
ADTStore: adt.WrapStore(ctx, cborstore),
Datastore: ds,
Blockstore: bs,
Exchange: offl,
BlockService: blkserv,
DAGService: dserv,
}
}
// NewLocalStores creates a Stores object that operates entirely in-memory with
// no read-through remote fetch fallback.
func NewLocalStores(ctx context.Context) *Stores {
ds := ds.NewMapDatastore()
bs := blockstore.NewBlockstore(ds)
return newStores(ctx, ds, bs)
}
type proxyingBlockstore struct { type proxyingBlockstore struct {
ctx context.Context ctx context.Context
api api.FullNode api api.FullNode
@ -60,12 +89,9 @@ func (pb *proxyingBlockstore) Get(cid cid.Cid) (blocks.Block, error) {
return block, nil return block, nil
} }
// NewProxyingStore is a blockstore that proxies get requests for unknown CIDs // NewProxyingStore is a Stores that proxies get requests for unknown CIDs
// to a Filecoin node, via the ChainReadObj RPC. // to a Filecoin node, via the ChainReadObj RPC.
// func NewProxyingStore(ctx context.Context, api api.FullNode) *Stores {
// It also contains all possible stores, services and gadget that IPLD
// requires (quite a handful).
func NewProxyingStore(ctx context.Context, api api.FullNode) *ProxyingStores {
ds := ds.NewMapDatastore() ds := ds.NewMapDatastore()
bs := &proxyingBlockstore{ bs := &proxyingBlockstore{
@ -74,20 +100,5 @@ func NewProxyingStore(ctx context.Context, api api.FullNode) *ProxyingStores {
Blockstore: blockstore.NewBlockstore(ds), Blockstore: blockstore.NewBlockstore(ds),
} }
var ( return newStores(ctx, ds, bs)
cborstore = cbor.NewCborStore(bs)
offl = offline.Exchange(bs)
blkserv = blockservice.New(bs, offl)
dserv = merkledag.NewDAGService(blkserv)
)
return &ProxyingStores{
CBORStore: cborstore,
ADTStore: adt.WrapStore(ctx, cborstore),
Datastore: ds,
Blockstore: bs,
Exchange: offl,
BlockService: blkserv,
DAGService: dserv,
}
} }

View File

@ -26,12 +26,12 @@ import (
type Surgeon struct { type Surgeon struct {
ctx context.Context ctx context.Context
api api.FullNode api api.FullNode
stores *ProxyingStores stores *Stores
} }
// NewSurgeon returns a state surgeon, an object used to fetch and manipulate // NewSurgeon returns a state surgeon, an object used to fetch and manipulate
// state. // state.
func NewSurgeon(ctx context.Context, api api.FullNode, stores *ProxyingStores) *Surgeon { func NewSurgeon(ctx context.Context, api api.FullNode, stores *Stores) *Surgeon {
return &Surgeon{ return &Surgeon{
ctx: ctx, ctx: ctx,
api: api, api: api,

View File

@ -26,9 +26,7 @@ func suiteMessages(c *cli.Context) error {
var err *multierror.Error var err *multierror.Error
err = multierror.Append(MessageTest_AccountActorCreation()) err = multierror.Append(MessageTest_AccountActorCreation())
err = multierror.Append(MessageTest_InitActorSequentialIDAddressCreate()) err = multierror.Append(MessageTest_InitActorSequentialIDAddressCreate())
err = multierror.Append(MessageTest_MessageApplicationEdgecases())
err = multierror.Append(MessageTest_MultiSigActor()) err = multierror.Append(MessageTest_MultiSigActor())
err = multierror.Append(MessageTest_Paych())
err = multierror.Append(MessageTest_ValueTransferSimple()) err = multierror.Append(MessageTest_ValueTransferSimple())
err = multierror.Append(MessageTest_ValueTransferAdvance()) err = multierror.Append(MessageTest_ValueTransferAdvance())
err = multierror.Append(MessageTest_NestedSends()) err = multierror.Append(MessageTest_NestedSends())

View File

@ -1,267 +0,0 @@
package main
import (
"os"
abi_spec "github.com/filecoin-project/specs-actors/actors/abi"
big_spec "github.com/filecoin-project/specs-actors/actors/abi/big"
paych_spec "github.com/filecoin-project/specs-actors/actors/builtin/paych"
crypto_spec "github.com/filecoin-project/specs-actors/actors/crypto"
exitcode_spec "github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
"github.com/filecoin-project/oni/tvx/chain"
"github.com/filecoin-project/oni/tvx/drivers"
)
func MessageTest_MessageApplicationEdgecases() error {
var aliceBal = abi_spec.NewTokenAmount(1_000_000_000_000)
var transferAmnt = abi_spec.NewTokenAmount(10)
err := func(testname string) error {
td := drivers.NewTestDriver()
td.Vector.Meta.Desc = testname
alice, _ := td.NewAccountActor(drivers.SECP, aliceBal)
td.UpdatePreStateRoot()
msg := td.MessageProducer.Transfer(alice, alice, chain.Value(transferAmnt), chain.Nonce(0), chain.GasPrice(1), chain.GasLimit(8))
td.ApplyFailure(
msg,
exitcode_spec.SysErrOutOfGas)
td.MustSerialize(os.Stdout)
return nil
}("fail to cover gas cost for message receipt on chain")
if err != nil {
return err
}
err = func(testname string) error {
td := drivers.NewTestDriver()
td.Vector.Meta.Desc = testname
alice, _ := td.NewAccountActor(drivers.SECP, aliceBal)
td.UpdatePreStateRoot()
msg := td.MessageProducer.Transfer(alice, alice, chain.Value(transferAmnt), chain.Nonce(0), chain.GasPrice(10), chain.GasLimit(1))
// Expect Message application to fail due to lack of gas
td.ApplyFailure(
msg,
exitcode_spec.SysErrOutOfGas)
unknown := chain.MustNewIDAddr(10000000)
msg = td.MessageProducer.Transfer(unknown, alice, chain.Value(transferAmnt), chain.Nonce(0), chain.GasPrice(10), chain.GasLimit(1))
// Expect Message application to fail due to lack of gas when sender is unknown
td.ApplyFailure(
msg,
exitcode_spec.SysErrOutOfGas)
td.MustSerialize(os.Stdout)
return nil
}("not enough gas to pay message on-chain-size cost")
if err != nil {
return err
}
err = func(testname string) error {
td := drivers.NewTestDriver()
td.Vector.Meta.Desc = testname
alice, _ := td.NewAccountActor(drivers.SECP, aliceBal)
td.UpdatePreStateRoot()
aliceNonce := uint64(0)
aliceNonceF := func() uint64 {
defer func() { aliceNonce++ }()
return aliceNonce
}
newAccountA := chain.MustNewSECP256K1Addr("1")
msg := td.MessageProducer.Transfer(alice, newAccountA, chain.Value(transferAmnt), chain.Nonce(aliceNonceF()))
// get the "true" gas cost of applying the message
result := td.ApplyOk(msg)
// decrease the gas cost by `gasStep` for each apply and ensure `SysErrOutOfGas` is always returned.
trueGas := int64(result.GasUsed())
gasStep := int64(trueGas / 100)
newAccountB := chain.MustNewSECP256K1Addr("2")
for tryGas := trueGas - gasStep; tryGas > 0; tryGas -= gasStep {
msg := td.MessageProducer.Transfer(alice, newAccountB, chain.Value(transferAmnt), chain.Nonce(aliceNonceF()), chain.GasPrice(1), chain.GasLimit(tryGas))
td.ApplyFailure(
msg,
exitcode_spec.SysErrOutOfGas,
)
}
td.MustSerialize(os.Stdout)
return nil
}("fail not enough gas to cover account actor creation")
if err != nil {
return err
}
err = func(testname string) error {
td := drivers.NewTestDriver()
td.Vector.Meta.Desc = testname
alice, _ := td.NewAccountActor(drivers.SECP, aliceBal)
td.UpdatePreStateRoot()
msg := td.MessageProducer.Transfer(alice, alice, chain.Value(transferAmnt), chain.Nonce(1))
// Expect Message application to fail due to callseqnum being invalid: 1 instead of 0
td.ApplyFailure(
msg,
exitcode_spec.SysErrSenderStateInvalid)
unknown := chain.MustNewIDAddr(10000000)
msg = td.MessageProducer.Transfer(unknown, alice, chain.Value(transferAmnt), chain.Nonce(1))
// Expect message application to fail due to unknow actor when call seq num is also incorrect
td.ApplyFailure(
msg,
exitcode_spec.SysErrSenderInvalid)
td.MustSerialize(os.Stdout)
return nil
}("invalid actor nonce")
if err != nil {
return err
}
err = func(testname string) error {
td := drivers.NewTestDriver()
td.Vector.Meta.Desc = testname
const pcTimeLock = abi_spec.ChainEpoch(10)
const pcLane = uint64(123)
const pcNonce = uint64(1)
var pcAmount = big_spec.NewInt(10)
var initialBal = abi_spec.NewTokenAmount(200_000_000_000)
var toSend = abi_spec.NewTokenAmount(10_000)
var pcSig = &crypto_spec.Signature{
Type: crypto_spec.SigTypeBLS,
Data: []byte("Grrr im an invalid signature, I cause panics in the payment channel actor"),
}
// will create and send on payment channel
sender, _ := td.NewAccountActor(drivers.SECP, initialBal)
// will be receiver on paych
receiver, receiverID := td.NewAccountActor(drivers.SECP, initialBal)
// the _expected_ address of the payment channel
paychAddr := chain.MustNewIDAddr(chain.MustIDFromAddress(receiverID) + 1)
createRet := td.ComputeInitActorExecReturn(sender, 0, 0, paychAddr)
td.UpdatePreStateRoot()
msg := td.MessageProducer.CreatePaymentChannelActor(sender, receiver, chain.Value(toSend), chain.Nonce(0))
td.ApplyExpect(
msg,
chain.MustSerialize(&createRet))
msg = td.MessageProducer.PaychUpdateChannelState(sender, paychAddr, &paych_spec.UpdateChannelStateParams{
Sv: paych_spec.SignedVoucher{
ChannelAddr: paychAddr,
TimeLockMin: pcTimeLock,
TimeLockMax: pcTimeLock,
SecretPreimage: nil,
Extra: nil,
Lane: pcLane,
Nonce: pcNonce,
Amount: pcAmount,
MinSettleHeight: 0,
Merges: nil,
Signature: pcSig, // construct with invalid signature
},
}, chain.Nonce(1), chain.Value(big_spec.Zero()))
// message application fails due to invalid argument (signature).
td.ApplyFailure(
msg,
exitcode_spec.ErrIllegalArgument)
td.MustSerialize(os.Stdout)
return nil
}("abort during actor execution")
if err != nil {
return err
}
err = func(testname string) error {
td := drivers.NewTestDriver()
td.Vector.Meta.Desc = testname
alice, _ := td.NewAccountActor(drivers.SECP, aliceBal)
td.UpdatePreStateRoot()
msg := td.MessageProducer.MarketComputeDataCommitment(alice, alice, nil, chain.Nonce(0))
// message application fails because ComputeDataCommitment isn't defined
// on the recipient actor
td.ApplyFailure(
msg,
exitcode_spec.SysErrInvalidMethod)
td.MustSerialize(os.Stdout)
return nil
}("invalid method for receiver")
if err != nil {
return err
}
err = func(testname string) error {
td := drivers.NewTestDriver()
td.Vector.Meta.Desc = testname
td.Vector.Meta.Comment = "Note that this test is not a valid message, since it is using an unknown actor. However in the event that an invalid message isn't filtered by block validation we need to ensure behaviour is consistent across VM implementations."
alice, _ := td.NewAccountActor(drivers.SECP, aliceBal)
td.UpdatePreStateRoot()
// Sending a message to non-existent ID address must produce an error.
unknownA := chain.MustNewIDAddr(10000000)
msg := td.MessageProducer.Transfer(alice, unknownA, chain.Value(transferAmnt), chain.Nonce(0))
td.ApplyFailure(
msg,
exitcode_spec.SysErrInvalidReceiver)
// Sending a message to non-existing actor address must produce an error.
unknownB := chain.MustNewActorAddr("1234")
msg = td.MessageProducer.Transfer(alice, unknownB, chain.Value(transferAmnt), chain.Nonce(1))
td.ApplyFailure(
msg,
exitcode_spec.SysErrInvalidReceiver)
td.MustSerialize(os.Stdout)
return nil
}("receiver ID/Actor address does not exist")
if err != nil {
return err
}
return nil
// TODO more tests:
// - missing/mismatched params for receiver
// - various out-of-gas cases
}

View File

@ -1,185 +0,0 @@
package main
import (
"os"
abi_spec "github.com/filecoin-project/specs-actors/actors/abi"
big_spec "github.com/filecoin-project/specs-actors/actors/abi/big"
paych_spec "github.com/filecoin-project/specs-actors/actors/builtin/paych"
crypto_spec "github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/stretchr/testify/assert"
"github.com/filecoin-project/oni/tvx/chain"
"github.com/filecoin-project/oni/tvx/drivers"
)
func MessageTest_Paych() error {
var initialBal = abi_spec.NewTokenAmount(200_000_000_000)
var toSend = abi_spec.NewTokenAmount(10_000)
err := func(testname string) error {
td := drivers.NewTestDriver()
td.Vector.Meta.Desc = testname
// will create and send on payment channel
sender, senderID := td.NewAccountActor(drivers.SECP, initialBal)
// will be receiver on paych
receiver, receiverID := td.NewAccountActor(drivers.SECP, initialBal)
td.UpdatePreStateRoot()
// the _expected_ address of the payment channel
paychAddr := chain.MustNewIDAddr(chain.MustIDFromAddress(receiverID) + 1)
createRet := td.ComputeInitActorExecReturn(sender, 0, 0, paychAddr)
msg := td.MessageProducer.CreatePaymentChannelActor(sender, receiver, chain.Value(toSend), chain.Nonce(0))
// init actor creates the payment channel
td.ApplyExpect(
msg,
chain.MustSerialize(&createRet))
var pcState paych_spec.State
td.GetActorState(paychAddr, &pcState)
assert.Equal(drivers.T, senderID, pcState.From)
assert.Equal(drivers.T, receiverID, pcState.To)
td.AssertBalance(paychAddr, toSend)
td.MustSerialize(os.Stdout)
return nil
}("happy path constructor")
if err != nil {
return err
}
err = func(testname string) error {
td := drivers.NewTestDriver()
td.Vector.Meta.Desc = testname
//const pcTimeLock = abi_spec.ChainEpoch(1)
const pcTimeLock = abi_spec.ChainEpoch(0)
const pcLane = uint64(123)
const pcNonce = uint64(1)
var pcAmount = big_spec.NewInt(10)
var pcSig = &crypto_spec.Signature{
Type: crypto_spec.SigTypeBLS,
Data: []byte("signature goes here"), // TODO may need to generate an actual signature
}
// will create and send on payment channel
sender, _ := td.NewAccountActor(drivers.SECP, initialBal)
// will be receiver on paych
receiver, receiverID := td.NewAccountActor(drivers.SECP, initialBal)
td.UpdatePreStateRoot()
// the _expected_ address of the payment channel
paychAddr := chain.MustNewIDAddr(chain.MustIDFromAddress(receiverID) + 1)
createRet := td.ComputeInitActorExecReturn(sender, 0, 0, paychAddr)
msg := td.MessageProducer.CreatePaymentChannelActor(sender, receiver, chain.Value(toSend), chain.Nonce(0))
td.ApplyExpect(
msg,
chain.MustSerialize(&createRet))
msg = td.MessageProducer.PaychUpdateChannelState(sender, paychAddr, &paych_spec.UpdateChannelStateParams{
Sv: paych_spec.SignedVoucher{
ChannelAddr: paychAddr,
TimeLockMin: pcTimeLock,
TimeLockMax: 0, // TimeLockMax set to 0 means no timeout
SecretPreimage: nil,
Extra: nil,
Lane: pcLane,
Nonce: pcNonce,
Amount: pcAmount,
MinSettleHeight: 0,
Merges: nil,
Signature: pcSig,
},
}, chain.Nonce(1), chain.Value(big_spec.Zero()))
td.ApplyOk(msg)
var pcState paych_spec.State
td.GetActorState(paychAddr, &pcState)
assert.Equal(drivers.T, 1, len(pcState.LaneStates))
ls := pcState.LaneStates[0]
assert.Equal(drivers.T, pcAmount, ls.Redeemed)
assert.Equal(drivers.T, pcNonce, ls.Nonce)
assert.Equal(drivers.T, pcLane, ls.ID)
td.MustSerialize(os.Stdout)
return nil
}("happy path update")
if err != nil {
return err
}
err = func(testname string) error {
td := drivers.NewTestDriver()
td.Vector.Meta.Desc = testname
// create the payment channel
sender, _ := td.NewAccountActor(drivers.SECP, initialBal)
receiver, receiverID := td.NewAccountActor(drivers.SECP, initialBal)
paychAddr := chain.MustNewIDAddr(chain.MustIDFromAddress(receiverID) + 1)
initRet := td.ComputeInitActorExecReturn(sender, 0, 0, paychAddr)
td.UpdatePreStateRoot()
msg := td.MessageProducer.CreatePaymentChannelActor(sender, receiver, chain.Value(toSend), chain.Nonce(0))
td.ApplyExpect(
msg,
chain.MustSerialize(&initRet))
td.AssertBalance(paychAddr, toSend)
msg = td.MessageProducer.PaychUpdateChannelState(sender, paychAddr, &paych_spec.UpdateChannelStateParams{
Sv: paych_spec.SignedVoucher{
ChannelAddr: paychAddr,
TimeLockMin: abi_spec.ChainEpoch(0),
TimeLockMax: 0, // TimeLockMax set to 0 means no timeout
SecretPreimage: nil,
Extra: nil,
Lane: 1,
Nonce: 1,
Amount: toSend, // the amount that can be redeemed by receiver,
MinSettleHeight: 0,
Merges: nil,
Signature: &crypto_spec.Signature{
Type: crypto_spec.SigTypeBLS,
Data: []byte("signature goes here"),
},
},
}, chain.Nonce(1), chain.Value(big_spec.Zero()))
td.ApplyOk(msg)
// settle the payment channel so it may be collected
msg = td.MessageProducer.PaychSettle(receiver, paychAddr, nil, chain.Value(big_spec.Zero()), chain.Nonce(0))
settleResult := td.ApplyOk(msg)
// advance the epoch so the funds may be redeemed.
td.ExeCtx.Epoch += paych_spec.SettleDelay
msg = td.MessageProducer.PaychCollect(receiver, paychAddr, nil, chain.Nonce(1), chain.Value(big_spec.Zero()))
collectResult := td.ApplyOk(msg)
// receiver_balance = initial_balance + paych_send - settle_paych_msg_gas - collect_paych_msg_gas
td.AssertBalance(receiver, big_spec.Sub(big_spec.Sub(big_spec.Add(toSend, initialBal), settleResult.Receipt.GasUsed.Big()), collectResult.Receipt.GasUsed.Big()))
// the paych actor should have been deleted after the collect
td.AssertNoActor(paychAddr)
td.MustSerialize(os.Stdout)
return nil
}("happy path collect")
if err != nil {
return err
}
return nil
}