Skip to content

Commit

Permalink
test vector builder API + partial migration of suites (#229)
Browse files Browse the repository at this point in the history
Co-authored-by: Anton Evangelatov <anton.evangelatov@gmail.com>
  • Loading branch information
raulk and nonsense committed Aug 14, 2020
1 parent ffe3019 commit 99f466e
Show file tree
Hide file tree
Showing 20 changed files with 2,072 additions and 487 deletions.
213 changes: 213 additions & 0 deletions tvx/builders/actors.go
Original file line number Diff line number Diff line change
@@ -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 changes: 89 additions & 0 deletions tvx/builders/address.go
Original file line number Diff line number Diff line change
@@ -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 changes: 82 additions & 0 deletions tvx/builders/asserter.go
Original file line number Diff line number Diff line change
@@ -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...))
}
Loading

0 comments on commit 99f466e

Please sign in to comment.