Merge pull request #23 from filecoin-project/feat/init-actor-rebase

init actor
This commit is contained in:
Whyrusleeping 2019-07-12 09:58:06 -07:00 committed by GitHub
commit 87a3eba8db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 440 additions and 84 deletions

View File

@ -137,7 +137,6 @@ workflows:
jobs:
- lint-changes:
args: "--new-from-rev origin/master"
- lint-all
- test:
codecov-upload: true
- mod-tidy-check

187
chain/actor_init.go Normal file
View File

@ -0,0 +1,187 @@
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 successful.
// 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
state, err := vmctx.StateTree()
if err != nil {
return InvokeRet{}, err
}
if err := 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
import (
"context"
"fmt"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/ipfs/go-cid"
hamt "github.com/ipfs/go-hamt-ipld"
cbor "github.com/ipfs/go-ipld-cbor"
mh "github.com/multiformats/go-multihash"
)
@ -54,58 +50,3 @@ func init() {
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/ipfs/go-cid"
datastore "github.com/ipfs/go-datastore"
dstore "github.com/ipfs/go-datastore"
hamt "github.com/ipfs/go-hamt-ipld"
bstore "github.com/ipfs/go-ipfs-blockstore"
@ -78,7 +77,7 @@ func init() {
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)
state, err := NewStateTree(cst)
if err != nil {
@ -90,12 +89,12 @@ func MakeGenesisBlock(bs bstore.Blockstore, w *Wallet) (*GenesisBootstrap, error
return nil, err
}
minerAddr, err := w.GenerateKey(KTSecp256k1)
if err != nil {
return nil, err
var addrs []address.Address
for a := range actors {
addrs = append(addrs, a)
}
initact, err := SetupInitActor(bs, []address.Address{minerAddr})
initact, err := SetupInitActor(bs, addrs)
if err != nil {
return nil, err
}
@ -104,6 +103,17 @@ func MakeGenesisBlock(bs bstore.Blockstore, w *Wallet) (*GenesisBootstrap, error
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{
Code: AccountActorCodeCid,
Balance: types.NewInt(100000000000),
@ -113,22 +123,64 @@ func MakeGenesisBlock(bs bstore.Blockstore, w *Wallet) (*GenesisBootstrap, error
return nil, err
}
err = state.SetActor(minerAddr, &types.Actor{
Code: AccountActorCodeCid,
Balance: types.NewInt(5000000),
Head: emptyobject,
})
for a, v := range actors {
err = state.SetActor(a, &types.Actor{
Code: AccountActorCodeCid,
Balance: v,
Head: emptyobject,
})
if err != nil {
return nil, err
}
}
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
}
fmt.Println("about to flush state...")
stateroot, err := state.Flush()
if err != nil {
return nil, err
}
fmt.Println("at end of make Genesis block")
cst := hamt.CSTFromBstore(bs)
emptyroot, err := sharray.Build(context.TODO(), 4, []interface{}{}, cst)
if err != nil {
return nil, err
@ -164,7 +216,7 @@ func MakeGenesisBlock(bs bstore.Blockstore, w *Wallet) (*GenesisBootstrap, error
type ChainStore struct {
bs bstore.Blockstore
ds datastore.Datastore
ds dstore.Datastore
heaviestLk sync.Mutex
heaviest *TipSet
@ -174,7 +226,7 @@ type ChainStore struct {
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{
bs: bs,
ds: ds,
@ -204,7 +256,7 @@ func (cs *ChainStore) SetGenesis(b *BlockHeader) error {
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 {
@ -379,7 +431,7 @@ func (cs *ChainStore) AddBlock(b *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 {
return nil, err
}

View File

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

View File

@ -4,13 +4,32 @@ import (
"math/big"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/ipfs/go-cid"
"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 StateTree interface {
SetActor(addr address.Address, act *Actor) error
GetActor(addr address.Address) (*Actor, error)
}
type VMContext interface {
Message() *Message
Ipld() *hamt.CborIpldStore
Send(to address.Address, method string, value *big.Int, params []interface{}) ([][]byte, uint8, error)
BlockHeight() uint64
GasUsed() BigInt
Storage() Storage
StateTree() (StateTree, error)
}

View File

@ -1,7 +1,9 @@
package chain
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"math/big"
@ -12,6 +14,7 @@ import (
bserv "github.com/ipfs/go-blockservice"
cid "github.com/ipfs/go-cid"
hamt "github.com/ipfs/go-hamt-ipld"
bstore "github.com/ipfs/go-ipfs-blockstore"
ipld "github.com/ipfs/go-ipld-format"
dag "github.com/ipfs/go-merkledag"
"github.com/pkg/errors"
@ -22,6 +25,11 @@ type VMContext struct {
msg *types.Message
height uint64
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
@ -29,12 +37,38 @@ func (vmc *VMContext) Message() *types.Message {
return vmc.msg
}
/*
// Storage provides access to the VM storage layer
func (vmc *VMContext) Storage() Storage {
panic("nyi")
type storage struct {
// would be great to stop depending on this crap everywhere
// I am my own worst enemy
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 {
return vmc.cst
@ -54,11 +88,26 @@ func (vmc *VMContext) GasUsed() types.BigInt {
return types.NewInt(0)
}
func makeVMContext(state *StateTree, msg *types.Message, height uint64) *VMContext {
func (vmc *VMContext) StateTree() (types.StateTree, error) {
if vmc.msg.To != InitActorAddress {
return nil, fmt.Errorf("only init actor can access state tree directly")
}
return vmc.state, nil
}
func makeVMContext(state *StateTree, bs bstore.Blockstore, sroot cid.Cid, msg *types.Message, height uint64) *VMContext {
cst := hamt.CSTFromBstore(bs)
return &VMContext{
state: state,
sroot: sroot,
msg: msg,
height: height,
cst: cst,
storage: &storage{
cst: cst,
head: sroot,
},
}
}
@ -128,7 +177,7 @@ func (vm *VM) ApplyMessage(msg *types.Message) (*types.MessageReceipt, error) {
}
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 ret []byte
@ -214,3 +263,18 @@ func (vm *VM) Invoke(act *types.Actor, vmctx *VMContext, method uint64, params [
}
return ret.result, ret.returnCode, nil
}
func ComputeActorAddress(creator address.Address, nonce uint64) (address.Address, error) {
buf := new(bytes.Buffer)
_, err := buf.Write(creator.Bytes())
if err != nil {
return address.Address{}, err
}
err = binary.Write(buf, binary.BigEndian, nonce)
if err != nil {
return address.Address{}, err
}
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")
}
}