diff --git a/.circleci/config.yml b/.circleci/config.yml index 5f0e29313..b35d2ceb4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -137,7 +137,6 @@ workflows: jobs: - lint-changes: args: "--new-from-rev origin/master" - - lint-all - test: codecov-upload: true - mod-tidy-check diff --git a/chain/actor_init.go b/chain/actor_init.go new file mode 100644 index 000000000..ee0bde4bc --- /dev/null +++ b/chain/actor_init.go @@ -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 +} diff --git a/chain/actors.go b/chain/actors.go index 87e9094ca..2883b8820 100644 --- a/chain/actors.go +++ b/chain/actors.go @@ -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 -} diff --git a/chain/chain.go b/chain/chain.go index 2a48f0332..55633c569 100644 --- a/chain/chain.go +++ b/chain/chain.go @@ -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 } diff --git a/chain/invoker.go b/chain/invoker.go index 1478a59e5..3128d0c0c 100644 --- a/chain/invoker.go +++ b/chain/invoker.go @@ -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 } diff --git a/chain/types/vmcontext.go b/chain/types/vmcontext.go index 984525c7c..1353ba627 100644 --- a/chain/types/vmcontext.go +++ b/chain/types/vmcontext.go @@ -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) } diff --git a/chain/vm.go b/chain/vm.go index 77fb7da52..f4fd06466 100644 --- a/chain/vm.go +++ b/chain/vm.go @@ -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()) +} diff --git a/chain/vm_test.go b/chain/vm_test.go new file mode 100644 index 000000000..882d5a6a8 --- /dev/null +++ b/chain/vm_test.go @@ -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") + } +}