integrate init actor to vm invoker

more wiring

Add a test for the basic init.exec running and make it pass

fix bad block comment

License: MIT
Signed-off-by: Jakub Sztandera <kubuxu@protonmail.ch>
This commit is contained in:
whyrusleeping 2019-07-11 20:59:55 -07:00 committed by Jakub Sztandera
parent e720f5d3a6
commit c4022505c7
7 changed files with 416 additions and 83 deletions

183
chain/actor_init.go Normal file
View File

@ -0,0 +1,183 @@
package chain
import (
"context"
"fmt"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/types"
"github.com/ipfs/go-cid"
hamt "github.com/ipfs/go-hamt-ipld"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/pkg/errors"
)
func init() {
cbor.RegisterCborType(ExecParams{})
}
type InitActor struct{}
type InitActorState struct {
// TODO: this needs to be a HAMT, its a dumb map for now
AddressMap cid.Cid
NextID uint64
}
func (ia InitActor) Exports() []interface{} {
return []interface{}{
nil,
ia.Exec,
}
}
type ExecParams struct {
Code cid.Cid
Params []byte
}
func (ep *ExecParams) UnmarshalCBOR(b []byte) (int, error) {
if err := cbor.DecodeInto(b, ep); err != nil {
return 0, err
}
return len(b), nil
}
func (ia InitActor) Exec(act *types.Actor, vmctx types.VMContext, p *ExecParams) (InvokeRet, error) {
beginState := vmctx.Storage().GetHead()
var self InitActorState
if err := vmctx.Storage().Get(beginState, &self); err != nil {
return InvokeRet{}, err
}
// Make sure that only the actors defined in the spec can be launched.
if !IsBuiltinActor(p.Code) {
log.Error("cannot launch actor instance that is not a builtin actor")
return InvokeRet{
returnCode: 1,
}, nil
}
// Ensure that singeltons can be only launched once.
// TODO: do we want to enforce this? If so how should actors be marked as such?
if IsSingletonActor(p.Code) {
log.Error("cannot launch another actor of this type")
return InvokeRet{
returnCode: 1,
}, nil
}
// This generates a unique address for this actor that is stable across message
// reordering
creator := vmctx.Message().From
nonce := vmctx.Message().Nonce
addr, err := ComputeActorAddress(creator, nonce)
if err != nil {
return InvokeRet{}, err
}
// Set up the actor itself
actor := types.Actor{
Code: p.Code,
Balance: vmctx.Message().Value,
Head: cid.Undef,
Nonce: 0,
}
// The call to the actors constructor will set up the initial state
// from the given parameters, setting `actor.Head` to a new value when successfull.
// TODO: can constructors fail?
//actor.Constructor(p.Params)
// Store the mapping of address to actor ID.
idAddr, err := self.AddActor(vmctx, addr)
if err != nil {
return InvokeRet{}, errors.Wrap(err, "adding new actor mapping")
}
// NOTE: This is a privileged call that only the init actor is allowed to make
// FIXME: Had to comment this because state is not in interface
_ = actor
//if err := vmctx.state.SetActor(idAddr, &actor); err != nil {
//return InvokeRet{}, errors.Wrap(err, "inserting new actor into state tree")
//}
c, err := vmctx.Storage().Put(self)
if err != nil {
return InvokeRet{}, err
}
if err := vmctx.Storage().Commit(beginState, c); err != nil {
return InvokeRet{}, err
}
return InvokeRet{
result: idAddr.Bytes(),
}, nil
}
func IsBuiltinActor(code cid.Cid) bool {
switch code {
case StorageMinerCodeCid, StorageMinerCodeCid, AccountActorCodeCid, InitActorCodeCid, MultisigActorCodeCid:
return true
default:
return false
}
}
func IsSingletonActor(code cid.Cid) bool {
return code == StorageMarketActorCodeCid || code == InitActorCodeCid
}
func (ias *InitActorState) AddActor(vmctx types.VMContext, addr address.Address) (address.Address, error) {
nid := ias.NextID
ias.NextID++
amap, err := hamt.LoadNode(context.TODO(), vmctx.Ipld(), ias.AddressMap)
if err != nil {
return address.Undef, err
}
if err := amap.Set(context.TODO(), string(addr.Bytes()), nid); err != nil {
return address.Undef, err
}
if err := amap.Flush(context.TODO()); err != nil {
return address.Undef, err
}
ncid, err := vmctx.Ipld().Put(context.TODO(), amap)
if err != nil {
return address.Undef, err
}
ias.AddressMap = ncid
return address.NewIDAddress(nid)
}
func (ias *InitActorState) Lookup(cst *hamt.CborIpldStore, addr address.Address) (address.Address, error) {
amap, err := hamt.LoadNode(context.TODO(), cst, ias.AddressMap)
if err != nil {
return address.Undef, err
}
val, err := amap.Find(context.TODO(), string(addr.Bytes()))
if err != nil {
return address.Undef, err
}
ival, ok := val.(uint64)
if !ok {
return address.Undef, fmt.Errorf("invalid value in init actor state, expected uint64, got %T", val)
}
return address.NewIDAddress(ival)
}
type AccountActorState struct {
Address address.Address
}

View File

@ -1,13 +1,9 @@
package chain package chain
import ( import (
"context"
"fmt"
"github.com/filecoin-project/go-lotus/chain/address" "github.com/filecoin-project/go-lotus/chain/address"
"github.com/ipfs/go-cid" "github.com/ipfs/go-cid"
hamt "github.com/ipfs/go-hamt-ipld"
cbor "github.com/ipfs/go-ipld-cbor" cbor "github.com/ipfs/go-ipld-cbor"
mh "github.com/multiformats/go-multihash" mh "github.com/multiformats/go-multihash"
) )
@ -54,58 +50,3 @@ func init() {
type VMActor struct { type VMActor struct {
} }
type InitActorState struct {
AddressMap cid.Cid
NextID uint64
}
func (ias *InitActorState) AddActor(vmctx *VMContext, addr address.Address) (address.Address, error) {
nid := ias.NextID
ias.NextID++
amap, err := hamt.LoadNode(context.TODO(), vmctx.Ipld(), ias.AddressMap)
if err != nil {
return address.Undef, err
}
if err := amap.Set(context.TODO(), string(addr.Bytes()), nid); err != nil {
return address.Undef, err
}
if err := amap.Flush(context.TODO()); err != nil {
return address.Undef, err
}
ncid, err := vmctx.Ipld().Put(context.TODO(), amap)
if err != nil {
return address.Undef, err
}
ias.AddressMap = ncid
return address.NewIDAddress(nid)
}
func (ias *InitActorState) Lookup(cst *hamt.CborIpldStore, addr address.Address) (address.Address, error) {
amap, err := hamt.LoadNode(context.TODO(), cst, ias.AddressMap)
if err != nil {
return address.Undef, err
}
val, err := amap.Find(context.TODO(), string(addr.Bytes()))
if err != nil {
return address.Undef, err
}
ival, ok := val.(uint64)
if !ok {
return address.Undef, fmt.Errorf("invalid value in init actor state, expected uint64, got %T", val)
}
return address.NewIDAddress(ival)
}
type AccountActorState struct {
Address address.Address
}

View File

@ -9,7 +9,6 @@ import (
"github.com/filecoin-project/go-lotus/chain/types" "github.com/filecoin-project/go-lotus/chain/types"
"github.com/ipfs/go-cid" "github.com/ipfs/go-cid"
datastore "github.com/ipfs/go-datastore"
dstore "github.com/ipfs/go-datastore" dstore "github.com/ipfs/go-datastore"
hamt "github.com/ipfs/go-hamt-ipld" hamt "github.com/ipfs/go-hamt-ipld"
bstore "github.com/ipfs/go-ipfs-blockstore" bstore "github.com/ipfs/go-ipfs-blockstore"
@ -78,7 +77,7 @@ func init() {
var EmptyObjectCid cid.Cid var EmptyObjectCid cid.Cid
func MakeGenesisBlock(bs bstore.Blockstore, w *Wallet) (*GenesisBootstrap, error) { func MakeInitialStateTree(bs bstore.Blockstore, actors map[address.Address]types.BigInt) (*StateTree, error) {
cst := hamt.CSTFromBstore(bs) cst := hamt.CSTFromBstore(bs)
state, err := NewStateTree(cst) state, err := NewStateTree(cst)
if err != nil { if err != nil {
@ -90,12 +89,12 @@ func MakeGenesisBlock(bs bstore.Blockstore, w *Wallet) (*GenesisBootstrap, error
return nil, err return nil, err
} }
minerAddr, err := w.GenerateKey(KTSecp256k1) var addrs []address.Address
if err != nil { for a := range actors {
return nil, err addrs = append(addrs, a)
} }
initact, err := SetupInitActor(bs, []address.Address{minerAddr}) initact, err := SetupInitActor(bs, addrs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -104,6 +103,17 @@ func MakeGenesisBlock(bs bstore.Blockstore, w *Wallet) (*GenesisBootstrap, error
return nil, err return nil, err
} }
/*
smact, err := SetupStorageMarketActor(bs)
if err != nil {
return nil, err
}
if err := state.SetActor(StorageMarketAddress, smact); err != nil {
return nil, err
}
*/
err = state.SetActor(NetworkAddress, &types.Actor{ err = state.SetActor(NetworkAddress, &types.Actor{
Code: AccountActorCodeCid, Code: AccountActorCodeCid,
Balance: types.NewInt(100000000000), Balance: types.NewInt(100000000000),
@ -113,22 +123,64 @@ func MakeGenesisBlock(bs bstore.Blockstore, w *Wallet) (*GenesisBootstrap, error
return nil, err return nil, err
} }
err = state.SetActor(minerAddr, &types.Actor{ for a, v := range actors {
err = state.SetActor(a, &types.Actor{
Code: AccountActorCodeCid, Code: AccountActorCodeCid,
Balance: types.NewInt(5000000), Balance: v,
Head: emptyobject, Head: emptyobject,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
fmt.Println("about to flush state...") }
return state, nil
}
/*
func SetupStorageMarketActor(bs bstore.Blockstore) (*Actor, error) {
sms := &StorageMarketState{
Miners: make(map[address.Address]struct{}),
TotalStorage: NewInt(0),
}
stcid, err := hamt.CSTFromBstore(bs).Put(context.TODO(), sms)
if err != nil {
return nil, err
}
return &Actor{
Code: StorageMarketActorCodeCid,
Head: stcid,
Nonce: 0,
Balance: NewInt(0),
}, nil
}
*/
func MakeGenesisBlock(bs bstore.Blockstore, w *Wallet) (*GenesisBootstrap, error) {
fmt.Println("at end of make Genesis block")
minerAddr, err := w.GenerateKey(KTSecp256k1)
if err != nil {
return nil, err
}
addrs := map[address.Address]types.BigInt{
minerAddr: types.NewInt(50000000),
}
state, err := MakeInitialStateTree(bs, addrs)
if err != nil {
return nil, err
}
stateroot, err := state.Flush() stateroot, err := state.Flush()
if err != nil { if err != nil {
return nil, err return nil, err
} }
fmt.Println("at end of make Genesis block")
cst := hamt.CSTFromBstore(bs)
emptyroot, err := sharray.Build(context.TODO(), 4, []interface{}{}, cst) emptyroot, err := sharray.Build(context.TODO(), 4, []interface{}{}, cst)
if err != nil { if err != nil {
return nil, err return nil, err
@ -164,7 +216,7 @@ func MakeGenesisBlock(bs bstore.Blockstore, w *Wallet) (*GenesisBootstrap, error
type ChainStore struct { type ChainStore struct {
bs bstore.Blockstore bs bstore.Blockstore
ds datastore.Datastore ds dstore.Datastore
heaviestLk sync.Mutex heaviestLk sync.Mutex
heaviest *TipSet heaviest *TipSet
@ -174,7 +226,7 @@ type ChainStore struct {
headChange func(rev, app []*TipSet) error headChange func(rev, app []*TipSet) error
} }
func NewChainStore(bs bstore.Blockstore, ds datastore.Batching) *ChainStore { func NewChainStore(bs bstore.Blockstore, ds dstore.Batching) *ChainStore {
return &ChainStore{ return &ChainStore{
bs: bs, bs: bs,
ds: ds, ds: ds,
@ -204,7 +256,7 @@ func (cs *ChainStore) SetGenesis(b *BlockHeader) error {
return err return err
} }
return cs.ds.Put(datastore.NewKey("0"), b.Cid().Bytes()) return cs.ds.Put(dstore.NewKey("0"), b.Cid().Bytes())
} }
func (cs *ChainStore) PutTipSet(ts *FullTipSet) error { func (cs *ChainStore) PutTipSet(ts *FullTipSet) error {
@ -379,7 +431,7 @@ func (cs *ChainStore) AddBlock(b *BlockHeader) error {
} }
func (cs *ChainStore) GetGenesis() (*BlockHeader, error) { func (cs *ChainStore) GetGenesis() (*BlockHeader, error) {
data, err := cs.ds.Get(datastore.NewKey("0")) data, err := cs.ds.Get(dstore.NewKey("0"))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -24,7 +24,10 @@ func newInvoker() *invoker {
inv := &invoker{ inv := &invoker{
builtInCode: make(map[cid.Cid]nativeCode), builtInCode: make(map[cid.Cid]nativeCode),
} }
// add builtInCode using: register(cid, singleton) // add builtInCode using: register(cid, singleton)
inv.register(InitActorCodeCid, InitActor{})
return inv return inv
} }

View File

@ -4,13 +4,26 @@ import (
"math/big" "math/big"
"github.com/filecoin-project/go-lotus/chain/address" "github.com/filecoin-project/go-lotus/chain/address"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-hamt-ipld" "github.com/ipfs/go-hamt-ipld"
) )
type Storage interface {
Put(interface{}) (cid.Cid, error)
Get(cid.Cid, interface{}) error
GetHead() cid.Cid
// Commit sets the new head of the actors state as long as the current
// state matches 'oldh'
Commit(oldh cid.Cid, newh cid.Cid) error
}
type VMContext interface { type VMContext interface {
Message() *Message Message() *Message
Ipld() *hamt.CborIpldStore Ipld() *hamt.CborIpldStore
Send(to address.Address, method string, value *big.Int, params []interface{}) ([][]byte, uint8, error) Send(to address.Address, method string, value *big.Int, params []interface{}) ([][]byte, uint8, error)
BlockHeight() uint64 BlockHeight() uint64
GasUsed() BigInt GasUsed() BigInt
Storage() Storage
} }

View File

@ -1,7 +1,9 @@
package chain package chain
import ( import (
"bytes"
"context" "context"
"encoding/binary"
"fmt" "fmt"
"math/big" "math/big"
@ -12,6 +14,7 @@ import (
bserv "github.com/ipfs/go-blockservice" bserv "github.com/ipfs/go-blockservice"
cid "github.com/ipfs/go-cid" cid "github.com/ipfs/go-cid"
hamt "github.com/ipfs/go-hamt-ipld" hamt "github.com/ipfs/go-hamt-ipld"
bstore "github.com/ipfs/go-ipfs-blockstore"
ipld "github.com/ipfs/go-ipld-format" ipld "github.com/ipfs/go-ipld-format"
dag "github.com/ipfs/go-merkledag" dag "github.com/ipfs/go-merkledag"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -22,6 +25,11 @@ type VMContext struct {
msg *types.Message msg *types.Message
height uint64 height uint64
cst *hamt.CborIpldStore cst *hamt.CborIpldStore
// root cid of the state of the actor this invocation will be on
sroot cid.Cid
storage types.Storage
} }
// Message is the message that kicked off the current invocation // Message is the message that kicked off the current invocation
@ -29,12 +37,38 @@ func (vmc *VMContext) Message() *types.Message {
return vmc.msg return vmc.msg
} }
/* type storage struct {
// Storage provides access to the VM storage layer // would be great to stop depending on this crap everywhere
func (vmc *VMContext) Storage() Storage { // I am my own worst enemy
panic("nyi") cst *hamt.CborIpldStore
head cid.Cid
}
func (s *storage) Put(i interface{}) (cid.Cid, error) {
return s.cst.Put(context.TODO(), i)
}
func (s *storage) Get(c cid.Cid, out interface{}) error {
return s.cst.Get(context.TODO(), c, out)
}
func (s *storage) GetHead() cid.Cid {
return s.head
}
func (s *storage) Commit(oldh, newh cid.Cid) error {
if s.head != oldh {
return fmt.Errorf("failed to update, inconsistent base reference")
}
s.head = newh
return nil
}
// Storage provides access to the VM storage layer
func (vmc *VMContext) Storage() types.Storage {
return vmc.storage
} }
*/
func (vmc *VMContext) Ipld() *hamt.CborIpldStore { func (vmc *VMContext) Ipld() *hamt.CborIpldStore {
return vmc.cst return vmc.cst
@ -54,11 +88,18 @@ func (vmc *VMContext) GasUsed() types.BigInt {
return types.NewInt(0) return types.NewInt(0)
} }
func makeVMContext(state *StateTree, msg *types.Message, height uint64) *VMContext { func makeVMContext(state *StateTree, bs bstore.Blockstore, sroot cid.Cid, msg *types.Message, height uint64) *VMContext {
cst := hamt.CSTFromBstore(bs)
return &VMContext{ return &VMContext{
state: state, state: state,
sroot: sroot,
msg: msg, msg: msg,
height: height, height: height,
cst: cst,
storage: &storage{
cst: cst,
head: sroot,
},
} }
} }
@ -128,7 +169,7 @@ func (vm *VM) ApplyMessage(msg *types.Message) (*types.MessageReceipt, error) {
} }
DepositFunds(toActor, msg.Value) DepositFunds(toActor, msg.Value)
vmctx := makeVMContext(st, msg, vm.blockHeight) vmctx := makeVMContext(st, vm.cs.bs, toActor.Head, msg, vm.blockHeight)
var errcode byte var errcode byte
var ret []byte var ret []byte
@ -214,3 +255,12 @@ func (vm *VM) Invoke(act *types.Actor, vmctx *VMContext, method uint64, params [
} }
return ret.result, ret.returnCode, nil return ret.result, ret.returnCode, nil
} }
func ComputeActorAddress(creator address.Address, nonce uint64) (address.Address, error) {
buf := new(bytes.Buffer)
buf.Write(creator.Bytes())
binary.Write(buf, binary.BigEndian, nonce)
return address.NewActorAddress(buf.Bytes())
}

91
chain/vm_test.go Normal file
View File

@ -0,0 +1,91 @@
package chain
import (
"encoding/binary"
"testing"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/types"
dstore "github.com/ipfs/go-datastore"
bstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
)
func blsaddr(n uint64) address.Address {
buf := make([]byte, 48)
binary.PutUvarint(buf, n)
addr, err := address.NewBLSAddress(buf)
if err != nil {
panic(err)
}
return addr
}
func TestVMInvokeMethod(t *testing.T) {
bs := bstore.NewBlockstore(dstore.NewMapDatastore())
from := blsaddr(0)
maddr := blsaddr(1)
actors := map[address.Address]types.BigInt{
from: types.NewInt(1000000),
maddr: types.NewInt(0),
}
st, err := MakeInitialStateTree(bs, actors)
if err != nil {
t.Fatal(err)
}
stateroot, err := st.Flush()
if err != nil {
t.Fatal(err)
}
cs := &ChainStore{
bs: bs,
}
vm, err := NewVM(stateroot, 1, maddr, cs)
if err != nil {
t.Fatal(err)
}
execparams := &ExecParams{
Code: StorageMinerCodeCid,
Params: []byte("cats"),
}
enc, err := cbor.DumpObject(execparams)
if err != nil {
t.Fatal(err)
}
msg := &types.Message{
To: InitActorAddress,
From: from,
Method: 1,
Params: enc,
GasPrice: types.NewInt(1),
GasLimit: types.NewInt(1),
Value: types.NewInt(0),
}
ret, err := vm.ApplyMessage(msg)
if err != nil {
t.Fatal(err)
}
if ret.ExitCode != 0 {
t.Fatal("invocation failed")
}
outaddr, err := address.NewFromBytes(ret.Return)
if err != nil {
t.Fatal(err)
}
if outaddr.String() != "t0102" {
t.Fatal("hold up")
}
}