diff --git a/api/api.go b/api/api.go index 33b6626fd..8850a9d03 100644 --- a/api/api.go +++ b/api/api.go @@ -68,7 +68,7 @@ type FullNode interface { MinerRegister(context.Context, address.Address) error MinerUnregister(context.Context, address.Address) error MinerAddresses(context.Context) ([]address.Address, error) - MinerCreateBlock(context.Context, address.Address, *types.TipSet, []*types.Ticket, types.ElectionProof, []*types.SignedMessage) (*chain.BlockMsg, error) + MinerCreateBlock(context.Context, address.Address, *types.TipSet, []*types.Ticket, types.ElectionProof, []*types.SignedMessage, uint64) (*chain.BlockMsg, error) // // UX ? diff --git a/api/struct.go b/api/struct.go index 51607a2d7..84fdbf1fc 100644 --- a/api/struct.go +++ b/api/struct.go @@ -54,10 +54,10 @@ type FullNodeStruct struct { MpoolPending func(context.Context, *types.TipSet) ([]*types.SignedMessage, error) `perm:"read"` MpoolPush func(context.Context, *types.SignedMessage) error `perm:"write"` - MinerRegister func(context.Context, address.Address) error `perm:"admin"` - MinerUnregister func(context.Context, address.Address) error `perm:"admin"` - MinerAddresses func(context.Context) ([]address.Address, error) `perm:"write"` - MinerCreateBlock func(context.Context, address.Address, *types.TipSet, []*types.Ticket, types.ElectionProof, []*types.SignedMessage) (*chain.BlockMsg, error) `perm:"write"` + MinerRegister func(context.Context, address.Address) error `perm:"admin"` + MinerUnregister func(context.Context, address.Address) error `perm:"admin"` + MinerAddresses func(context.Context) ([]address.Address, error) `perm:"write"` + MinerCreateBlock func(context.Context, address.Address, *types.TipSet, []*types.Ticket, types.ElectionProof, []*types.SignedMessage, uint64) (*chain.BlockMsg, error) `perm:"write"` WalletNew func(context.Context, string) (address.Address, error) `perm:"write"` WalletHas func(context.Context, address.Address) (bool, error) `perm:"write"` @@ -174,8 +174,8 @@ func (c *FullNodeStruct) MinerAddresses(ctx context.Context) ([]address.Address, return c.Internal.MinerAddresses(ctx) } -func (c *FullNodeStruct) MinerCreateBlock(ctx context.Context, addr address.Address, base *types.TipSet, tickets []*types.Ticket, eproof types.ElectionProof, msgs []*types.SignedMessage) (*chain.BlockMsg, error) { - return c.Internal.MinerCreateBlock(ctx, addr, base, tickets, eproof, msgs) +func (c *FullNodeStruct) MinerCreateBlock(ctx context.Context, addr address.Address, base *types.TipSet, tickets []*types.Ticket, eproof types.ElectionProof, msgs []*types.SignedMessage, ts uint64) (*chain.BlockMsg, error) { + return c.Internal.MinerCreateBlock(ctx, addr, base, tickets, eproof, msgs, ts) } func (c *FullNodeStruct) ChainSubmitBlock(ctx context.Context, blk *chain.BlockMsg) error { diff --git a/chain/cbor_gen.go b/chain/cbor_gen.go index ae9ddc1e0..0a263e20a 100644 --- a/chain/cbor_gen.go +++ b/chain/cbor_gen.go @@ -522,6 +522,7 @@ func (t *BlockMsg) UnmarshalCBOR(br io.Reader) error { // t.t.Header (types.BlockHeader) t.Header = new(types.BlockHeader) + if err := t.Header.UnmarshalCBOR(br); err != nil { return err } diff --git a/chain/gen/gen.go b/chain/gen/gen.go index af3125ab8..6e10a451e 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -253,7 +253,7 @@ func (cg *ChainGen) NextBlock() (*types.FullBlock, []*types.SignedMessage, error return nil, nil, err } - fblk, err := MinerCreateBlock(context.TODO(), cg.cs, miner, parents, tickets, proof, msgs) + fblk, err := MinerCreateBlock(context.TODO(), cg.cs, cg.w, miner, parents, tickets, proof, msgs, 0) if err != nil { return nil, nil, err } diff --git a/chain/gen/mining.go b/chain/gen/mining.go index fc407be08..72185d03a 100644 --- a/chain/gen/mining.go +++ b/chain/gen/mining.go @@ -8,15 +8,17 @@ import ( hamt "github.com/ipfs/go-hamt-ipld" "github.com/pkg/errors" sharray "github.com/whyrusleeping/sharray" + "golang.org/x/xerrors" "github.com/filecoin-project/go-lotus/chain/actors" "github.com/filecoin-project/go-lotus/chain/address" "github.com/filecoin-project/go-lotus/chain/store" "github.com/filecoin-project/go-lotus/chain/types" "github.com/filecoin-project/go-lotus/chain/vm" + "github.com/filecoin-project/go-lotus/chain/wallet" ) -func MinerCreateBlock(ctx context.Context, cs *store.ChainStore, miner address.Address, parents *types.TipSet, tickets []*types.Ticket, proof types.ElectionProof, msgs []*types.SignedMessage) (*types.FullBlock, error) { +func MinerCreateBlock(ctx context.Context, cs *store.ChainStore, w *wallet.Wallet, miner address.Address, parents *types.TipSet, tickets []*types.Ticket, proof types.ElectionProof, msgs []*types.SignedMessage, timestamp uint64) (*types.FullBlock, error) { st, err := cs.TipSetState(parents.Cids()) if err != nil { return nil, errors.Wrap(err, "failed to load tipset state") @@ -29,8 +31,18 @@ func MinerCreateBlock(ctx context.Context, cs *store.ChainStore, miner address.A return nil, err } + owner, err := getMinerOwner(ctx, cs, st, miner) + if err != nil { + return nil, xerrors.Errorf("failed to get miner owner: %w", err) + } + + worker, err := getMinerWorker(ctx, cs, st, miner) + if err != nil { + return nil, xerrors.Errorf("failed to get miner worker: %w", err) + } + // apply miner reward - if err := vmi.TransferFunds(actors.NetworkAddress, miner, vm.MiningRewardForBlock(parents)); err != nil { + if err := vmi.TransferFunds(actors.NetworkAddress, owner, vm.MiningRewardForBlock(parents)); err != nil { return nil, err } @@ -121,6 +133,25 @@ func MinerCreateBlock(ctx context.Context, cs *store.ChainStore, miner address.A pweight := cs.Weight(parents) next.ParentWeight = types.NewInt(pweight) + // TODO: set timestamp + + nosigbytes, err := next.Serialize() + if err != nil { + return nil, xerrors.Errorf("failed to serialize block header with no signature: %w", err) + } + + waddr, err := vm.ResolveToKeyAddr(vmi.StateTree(), cst, worker) + if err != nil { + return nil, xerrors.Errorf("failed to resolve miner address to key address: %w", err) + } + + sig, err := w.Sign(ctx, waddr, nosigbytes) + if err != nil { + return nil, xerrors.Errorf("failed to sign new block: %w", err) + } + + next.BlockSig = *sig + fullBlock := &types.FullBlock{ Header: next, BlsMessages: blsMessages, @@ -130,6 +161,40 @@ func MinerCreateBlock(ctx context.Context, cs *store.ChainStore, miner address.A return fullBlock, nil } +func getMinerWorker(ctx context.Context, cs *store.ChainStore, state cid.Cid, maddr address.Address) (address.Address, error) { + rec, err := vm.CallRaw(ctx, cs, &types.Message{ + To: maddr, + From: maddr, + Method: actors.MAMethods.GetWorkerAddr, + }, state, 0) + if err != nil { + return address.Undef, err + } + + if rec.ExitCode != 0 { + return address.Undef, xerrors.Errorf("getWorker failed with exit code %d", rec.ExitCode) + } + + return address.NewFromBytes(rec.Return) +} + +func getMinerOwner(ctx context.Context, cs *store.ChainStore, state cid.Cid, maddr address.Address) (address.Address, error) { + rec, err := vm.CallRaw(ctx, cs, &types.Message{ + To: maddr, + From: maddr, + Method: actors.MAMethods.GetOwner, + }, state, 0) + if err != nil { + return address.Undef, err + } + + if rec.ExitCode != 0 { + return address.Undef, xerrors.Errorf("getOwner failed with exit code %d", rec.ExitCode) + } + + return address.NewFromBytes(rec.Return) +} + func aggregateSignatures(sigs []types.Signature) (types.Signature, error) { var blsSigs []bls.Signature for _, s := range sigs { diff --git a/chain/gen/utils.go b/chain/gen/utils.go index 4b84afac2..ce5e4607e 100644 --- a/chain/gen/utils.go +++ b/chain/gen/utils.go @@ -304,6 +304,7 @@ func MakeGenesisBlock(bs bstore.Blockstore, balances map[address.Address]types.B Messages: mmb.Cid(), MessageReceipts: emptyroot, BLSAggregate: types.Signature{Type: types.KTBLS, Data: []byte("signatureeee")}, + BlockSig: types.Signature{Type: types.KTBLS, Data: []byte("block signatureeee")}, } sb, err := b.ToStorageBlock() diff --git a/chain/sync.go b/chain/sync.go index f8d929a56..c0581f28b 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -395,6 +395,32 @@ func getMinerWorker(ctx context.Context, cs *store.ChainStore, st cid.Cid, maddr return worker, nil } +func getMinerOwner(ctx context.Context, cs *store.ChainStore, st cid.Cid, maddr address.Address) (address.Address, error) { + recp, err := vm.CallRaw(ctx, cs, &types.Message{ + To: maddr, + From: maddr, + Method: actors.MAMethods.GetOwner, + }, st, 0) + if err != nil { + return address.Undef, xerrors.Errorf("callRaw failed: %w", err) + } + + if recp.ExitCode != 0 { + return address.Undef, xerrors.Errorf("getting miner owner addr failed (exit code %d)", recp.ExitCode) + } + + owner, err := address.NewFromBytes(recp.Return) + if err != nil { + return address.Undef, err + } + + if owner.Protocol() == address.ID { + return address.Undef, xerrors.Errorf("need to resolve owner address to a pubkeyaddr") + } + + return owner, nil +} + // Should match up with 'Semantical Validation' in validation.md in the spec func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) error { h := b.Header @@ -426,7 +452,12 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err return xerrors.Errorf("failed to instantiate VM: %w", err) } - if err := vmi.TransferFunds(actors.NetworkAddress, b.Header.Miner, vm.MiningRewardForBlock(baseTs)); err != nil { + owner, err := getMinerOwner(ctx, syncer.store, stateroot, b.Header.Miner) + if err != nil { + return xerrors.Errorf("getting miner owner for block miner failed: %w", err) + } + + if err := vmi.TransferFunds(actors.NetworkAddress, owner, vm.MiningRewardForBlock(baseTs)); err != nil { return xerrors.Errorf("fund transfer failed: %w", err) } diff --git a/chain/types/blockheader.go b/chain/types/blockheader.go index 0b585a3d8..bea4602bc 100644 --- a/chain/types/blockheader.go +++ b/chain/types/blockheader.go @@ -39,6 +39,10 @@ type BlockHeader struct { BLSAggregate Signature MessageReceipts cid.Cid + + Timestamp uint64 + + BlockSig Signature } func (b *BlockHeader) ToStorageBlock() (block.Block, error) { diff --git a/chain/types/blockheader_test.go b/chain/types/blockheader_test.go index e82bd1616..8a996753b 100644 --- a/chain/types/blockheader_test.go +++ b/chain/types/blockheader_test.go @@ -39,6 +39,7 @@ func testBlockHeader(t testing.TB) *BlockHeader { Messages: c, Height: 85919298723, StateRoot: c, + BlockSig: Signature{Type: KTBLS, Data: []byte("boo! im a signature")}, } } diff --git a/chain/types/cbor_gen.go b/chain/types/cbor_gen.go index c77ddd923..084f2be85 100644 --- a/chain/types/cbor_gen.go +++ b/chain/types/cbor_gen.go @@ -14,7 +14,7 @@ import ( var _ = xerrors.Errorf func (t *BlockHeader) MarshalCBOR(w io.Writer) error { - if _, err := w.Write([]byte{138}); err != nil { + if _, err := w.Write([]byte{140}); err != nil { return err } @@ -80,6 +80,16 @@ func (t *BlockHeader) MarshalCBOR(w io.Writer) error { if err := cbg.WriteCid(w, t.MessageReceipts); err != nil { return xerrors.Errorf("failed to write cid field t.MessageReceipts: %w", err) } + + // t.t.Timestamp (uint64) + if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, t.Timestamp)); err != nil { + return err + } + + // t.t.BlockSig (types.Signature) + if err := t.BlockSig.MarshalCBOR(w); err != nil { + return err + } return nil } @@ -93,7 +103,7 @@ func (t *BlockHeader) UnmarshalCBOR(br io.Reader) error { return fmt.Errorf("cbor input should be of type array") } - if extra != 10 { + if extra != 12 { return fmt.Errorf("cbor input had wrong number of fields") } @@ -216,6 +226,21 @@ func (t *BlockHeader) UnmarshalCBOR(br io.Reader) error { } t.MessageReceipts = c } + // t.t.Timestamp (uint64) + + maj, extra, err = cbg.CborReadHeader(br) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Timestamp = extra + // t.t.BlockSig (types.Signature) + + if err := t.BlockSig.UnmarshalCBOR(br); err != nil { + return err + } return nil } diff --git a/chain/types/signature.go b/chain/types/signature.go index a2e08560a..83408a576 100644 --- a/chain/types/signature.go +++ b/chain/types/signature.go @@ -23,6 +23,8 @@ const ( ) const ( + IKTUnknown = -1 + IKTSecp256k1 = iota IKTBLS ) @@ -117,6 +119,8 @@ func (s *Signature) TypeCode() int { return IKTSecp256k1 case KTBLS: return IKTBLS + case "": + return IKTUnknown default: panic("unsupported signature type") } diff --git a/chain/vm/vm.go b/chain/vm/vm.go index c938b4eb4..7d774df9d 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -158,7 +158,7 @@ func (vmctx *VMContext) VerifySignature(sig *types.Signature, act address.Addres } if act.Protocol() == address.ID { - kaddr, err := vmctx.resolveToKeyAddr(act) + kaddr, err := ResolveToKeyAddr(vmctx.state, vmctx.cst, act) if err != nil { return aerrors.Wrap(err, "failed to resolve address to key address") } @@ -172,12 +172,12 @@ func (vmctx *VMContext) VerifySignature(sig *types.Signature, act address.Addres return nil } -func (vmctx *VMContext) resolveToKeyAddr(addr address.Address) (address.Address, aerrors.ActorError) { +func ResolveToKeyAddr(state types.StateTree, cst *hamt.CborIpldStore, addr address.Address) (address.Address, aerrors.ActorError) { if addr.Protocol() == address.BLS || addr.Protocol() == address.SECP256K1 { return addr, nil } - act, err := vmctx.state.GetActor(addr) + act, err := state.GetActor(addr) if err != nil { return address.Undef, aerrors.Newf(1, "failed to find actor: %s", addr) } @@ -187,7 +187,7 @@ func (vmctx *VMContext) resolveToKeyAddr(addr address.Address) (address.Address, } var aast actors.AccountActorState - if err := vmctx.cst.Get(context.TODO(), act.Head, &aast); err != nil { + if err := cst.Get(context.TODO(), act.Head, &aast); err != nil { return address.Undef, aerrors.Escalate(err, fmt.Sprintf("failed to get account actor state for %s", addr)) } @@ -195,7 +195,6 @@ func (vmctx *VMContext) resolveToKeyAddr(addr address.Address) (address.Address, } func (vm *VM) makeVMContext(ctx context.Context, sroot cid.Cid, msg *types.Message, origin address.Address, usedGas types.BigInt) *VMContext { - return &VMContext{ ctx: ctx, vm: vm, diff --git a/go.mod b/go.mod index 155c592cc..7e2508c41 100644 --- a/go.mod +++ b/go.mod @@ -64,6 +64,7 @@ require ( github.com/polydawn/refmt v0.0.0-20190809202753-05966cbd336a github.com/smartystreets/assertions v1.0.1 // indirect github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect + github.com/stretchr/objx v0.1.1 // indirect github.com/stretchr/testify v1.3.0 github.com/whyrusleeping/cbor-gen v0.0.0-20190822012446-bb2210dd2804 github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 diff --git a/miner/miner.go b/miner/miner.go index d8ff1455b..26e1d4650 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -336,7 +336,7 @@ func (m *Miner) createBlock(base *MiningBase, ticket *types.Ticket, proof types. msgs := m.selectMessages(pending) // why even return this? that api call could just submit it for us - return m.api.MinerCreateBlock(context.TODO(), m.addresses[0], base.ts, append(base.tickets, ticket), proof, msgs) + return m.api.MinerCreateBlock(context.TODO(), m.addresses[0], base.ts, append(base.tickets, ticket), proof, msgs, 0) } func (m *Miner) selectMessages(msgs []*types.SignedMessage) []*types.SignedMessage { diff --git a/node/impl/full/chain.go b/node/impl/full/chain.go index bb96c2da1..996502f6d 100644 --- a/node/impl/full/chain.go +++ b/node/impl/full/chain.go @@ -23,6 +23,8 @@ import ( type ChainAPI struct { fx.In + WalletAPI + Chain *store.ChainStore PubSub *pubsub.PubSub } @@ -163,8 +165,8 @@ func (a *ChainAPI) ChainReadState(ctx context.Context, act *types.Actor, ts *typ } // This is on ChainAPI because miner.Miner requires this, and MinerAPI requires miner.Miner -func (a *ChainAPI) MinerCreateBlock(ctx context.Context, addr address.Address, parents *types.TipSet, tickets []*types.Ticket, proof types.ElectionProof, msgs []*types.SignedMessage) (*chain.BlockMsg, error) { - fblk, err := gen.MinerCreateBlock(ctx, a.Chain, addr, parents, tickets, proof, msgs) +func (a *ChainAPI) MinerCreateBlock(ctx context.Context, addr address.Address, parents *types.TipSet, tickets []*types.Ticket, proof types.ElectionProof, msgs []*types.SignedMessage, ts uint64) (*chain.BlockMsg, error) { + fblk, err := gen.MinerCreateBlock(ctx, a.Chain, a.Wallet, addr, parents, tickets, proof, msgs, ts) if err != nil { return nil, err }