Merge pull request #140 from filecoin-project/feat/deal-payments

deals: Wire up client side of payments
This commit is contained in:
Łukasz Magiera 2019-08-16 00:03:05 +02:00 committed by GitHub
commit 6bee253e33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 299 additions and 70 deletions

View File

@ -106,7 +106,7 @@ func (ia InitActor) Exec(act *types.Actor, vmctx types.VMContext, p *ExecParams)
// Set up the actor itself
actor := types.Actor{
Code: p.Code,
Balance: vmctx.Message().Value,
Balance: types.NewInt(0),
Head: EmptyCBOR,
Nonce: 0,
}

View File

@ -2,17 +2,15 @@ package actors
import (
"context"
"github.com/filecoin-project/go-lotus/chain/actors/aerrors"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/types"
"github.com/filecoin-project/go-lotus/lib/sectorbuilder"
"golang.org/x/xerrors"
cid "github.com/ipfs/go-cid"
hamt "github.com/ipfs/go-hamt-ipld"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-hamt-ipld"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/libp2p/go-libp2p-core/peer"
"golang.org/x/xerrors"
)
func init() {
@ -21,6 +19,9 @@ func init() {
cbor.RegisterCborType(CommitSectorParams{})
cbor.RegisterCborType(MinerInfo{})
cbor.RegisterCborType(SubmitPoStParams{})
cbor.RegisterCborType(PieceInclVoucherData{})
cbor.RegisterCborType(InclusionProof{})
cbor.RegisterCborType(PaymentVerifyParams{})
}
var ProvingPeriodDuration = uint64(2 * 60) // an hour, for now
@ -98,23 +99,27 @@ type StorageMinerConstructorParams struct {
}
type maMethods struct {
Constructor uint64
CommitSector uint64
SubmitPoSt uint64
SlashStorageFault uint64
GetCurrentProvingSet uint64
ArbitrateDeal uint64
DePledge uint64
GetOwner uint64
GetWorkerAddr uint64
GetPower uint64
GetPeerID uint64
GetSectorSize uint64
UpdatePeerID uint64
ChangeWorker uint64
Constructor uint64
CommitSector uint64
SubmitPoSt uint64
SlashStorageFault uint64
GetCurrentProvingSet uint64
ArbitrateDeal uint64
DePledge uint64
GetOwner uint64
GetWorkerAddr uint64
GetPower uint64
GetPeerID uint64
GetSectorSize uint64
UpdatePeerID uint64
ChangeWorker uint64
IsSlashed uint64
IsLate uint64
PaymentVerifyInclusion uint64
PaymentVerifySector uint64
}
var MAMethods = maMethods{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}
var MAMethods = maMethods{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}
func (sma StorageMinerActor) Exports() []interface{} {
return []interface{}{
@ -132,6 +137,10 @@ func (sma StorageMinerActor) Exports() []interface{} {
12: sma.GetSectorSize,
13: sma.UpdatePeerID,
//14: sma.ChangeWorker,
//15: sma.IsSlashed,
//16: sma.IsLate,
17: sma.PaymentVerifyInclusion,
18: sma.PaymentVerifySector,
}
}
@ -376,6 +385,38 @@ func AddToSectorSet(ctx context.Context, cst *hamt.CborIpldStore, ss cid.Cid, se
return ssroot, nil
}
func GetFromSectorSet(ctx context.Context, cst *hamt.CborIpldStore, ss cid.Cid, sectorID types.BigInt) (bool, []byte, []byte, ActorError) {
nd, err := hamt.LoadNode(ctx, cst, ss)
if err != nil {
return false, nil, nil, aerrors.Escalate(err, "could not load HAMT node")
}
infoIf, err := nd.Find(ctx, sectorID.String())
if err == hamt.ErrNotFound {
return false, nil, nil, nil
}
if err != nil {
return false, nil, nil, aerrors.Escalate(err, "failed to find sector in sector set")
}
infoB, ok := infoIf.([]byte)
if !ok {
return false, nil, nil, aerrors.Escalate(xerrors.New("casting infoIf to []byte failed"), "") // TODO: Review: how to create aerrror without retcode?
}
var comms [][]byte // [ [commR], [commD] ]
err = cbor.DecodeInto(infoB, &comms)
if err != nil {
return false, nil, nil, aerrors.Escalate(err, "failed to decode sector set entry")
}
if len(comms) != 2 {
return false, nil, nil, aerrors.Escalate(xerrors.New("sector set entry should only have 2 elements"), "")
}
return true, comms[0], comms[1], nil
}
func ValidatePoRep(maddr address.Address, ssize types.BigInt, params *CommitSectorParams) (bool, ActorError) {
ok, err := sectorbuilder.VerifySeal(ssize.Uint64(), params.CommR, params.CommD, params.CommRStar, maddr, params.SectorID.Uint64(), params.Proof)
if err != nil {
@ -493,3 +534,86 @@ func (sma StorageMinerActor) GetSectorSize(act *types.Actor, vmctx types.VMConte
return mi.SectorSize.Bytes(), nil
}
type PaymentVerifyParams struct {
Extra []byte
Proof []byte
}
type PieceInclVoucherData struct { // TODO: Update spec at https://github.com/filecoin-project/specs/blob/master/actors.md#paymentverify
CommP []byte
PieceSize types.BigInt
}
type InclusionProof struct {
Sector types.BigInt // for CommD, also verifies the sector is in sector set
Proof []byte
}
func (sma StorageMinerActor) PaymentVerifyInclusion(act *types.Actor, vmctx types.VMContext, params *PaymentVerifyParams) ([]byte, ActorError) {
// params.Extra - PieceInclVoucherData
// params.Proof - InclusionProof
_, self, aerr := loadState(vmctx)
if aerr != nil {
return nil, aerr
}
mi, aerr := loadMinerInfo(vmctx, self)
if aerr != nil {
return nil, aerr
}
var voucherData PieceInclVoucherData
if err := cbor.DecodeInto(params.Extra, &voucherData); err != nil {
return nil, aerrors.Escalate(err, "failed to decode storage voucher data for verification")
}
var proof InclusionProof
if err := cbor.DecodeInto(params.Proof, &proof); err != nil {
return nil, aerrors.Escalate(err, "failed to decode storage payment proof")
}
ok, _, commD, aerr := GetFromSectorSet(context.TODO(), vmctx.Ipld(), self.Sectors, proof.Sector)
if aerr != nil {
return nil, aerr
}
if !ok {
return nil, aerrors.New(1, "miner does not have required sector")
}
ok, err := sectorbuilder.VerifyPieceInclusionProof(mi.SectorSize.Uint64(), voucherData.PieceSize.Uint64(), voucherData.CommP, commD, params.Proof)
if err != nil {
return nil, aerrors.Escalate(err, "verify piece inclusion proof failed")
}
if !ok {
return nil, aerrors.New(2, "piece inclusion proof was invalid")
}
return nil, nil
}
func (sma StorageMinerActor) PaymentVerifySector(act *types.Actor, vmctx types.VMContext, params *PaymentVerifyParams) ([]byte, ActorError) {
// params.Extra - BigInt - sector id
// params.Proof - nil
_, self, aerr := loadState(vmctx)
if aerr != nil {
return nil, aerr
}
// TODO: ensure no sector ID reusability within related deal lifetime
sector := types.BigFromBytes(params.Extra)
if len(params.Proof) > 0 {
return nil, aerrors.New(1, "unexpected proof bytes")
}
ok, _, _, aerr := GetFromSectorSet(context.TODO(), vmctx.Ipld(), self.Sectors, sector)
if aerr != nil {
return nil, aerr
}
if !ok {
return nil, aerrors.New(2, "miner does not have required sector")
}
return nil, nil
}

View File

@ -183,43 +183,56 @@ func (c *Client) waitAccept(s inet.Stream, proposal StorageDealProposal, minerID
}, nil
}
func (c *Client) Start(ctx context.Context, data cid.Cid, totalPrice types.BigInt, from address.Address, miner address.Address, minerID peer.ID, blocksDuration uint64) (cid.Cid, error) {
type ClientDealProposal struct {
Data cid.Cid
TotalPrice types.BigInt
Duration uint64
Payment actors.PaymentInfo
MinerAddress address.Address
ClientAddress address.Address
MinerID peer.ID
}
func (c *Client) VerifyParams(ctx context.Context, data cid.Cid) (*actors.PieceInclVoucherData, error) {
commP, size, err := c.commP(ctx, data)
if err != nil {
return cid.Undef, err
return nil, err
}
dummyCid, _ := cid.Parse("bafkqaaa")
return &actors.PieceInclVoucherData{
CommP: commP,
PieceSize: types.NewInt(uint64(size)),
}, nil
}
func (c *Client) Start(ctx context.Context, p ClientDealProposal, vd *actors.PieceInclVoucherData) (cid.Cid, error) {
// TODO: use data
proposal := StorageDealProposal{
PieceRef: data.String(),
PieceRef: p.Data.String(),
SerializationMode: SerializationUnixFs,
CommP: commP[:],
Size: uint64(size),
TotalPrice: totalPrice,
Duration: blocksDuration,
Payment: actors.PaymentInfo{
PayChActor: address.Address{},
Payer: address.Address{},
ChannelMessage: dummyCid,
Vouchers: nil,
},
MinerAddress: miner,
ClientAddress: from,
CommP: vd.CommP[:],
Size: vd.PieceSize.Uint64(),
TotalPrice: p.TotalPrice,
Duration: p.Duration,
Payment: p.Payment,
MinerAddress: p.MinerAddress,
ClientAddress: p.ClientAddress,
}
s, err := c.h.NewStream(ctx, minerID, ProtocolID)
s, err := c.h.NewStream(ctx, p.MinerID, ProtocolID)
if err != nil {
return cid.Undef, err
}
defer s.Reset() // TODO: handle other updates
if err := c.sendProposal(s, proposal, from); err != nil {
if err := c.sendProposal(s, proposal, p.ClientAddress); err != nil {
return cid.Undef, err
}
deal, err := c.waitAccept(s, proposal, minerID)
deal, err := c.waitAccept(s, proposal, p.MinerID)
if err != nil {
return cid.Undef, err
}

View File

@ -100,6 +100,14 @@ func VerifySeal(sectorSize uint64, commR, commD, commRStar []byte, proverID addr
return sectorbuilder.VerifySeal(sectorSize, commRa, commDa, commRStara, proverIDa, sectorIDa, proof)
}
func VerifyPieceInclusionProof(sectorSize uint64, pieceSize uint64, commP []byte, commD []byte, proof []byte) (bool, error) {
var commPa, commDa [32]byte
copy(commPa[:], commP)
copy(commDa[:], commD)
return sectorbuilder.VerifyPieceInclusionProof(sectorSize, pieceSize, commPa, commDa, proof)
}
func VerifyPost(sectorSize uint64, sortedCommRs [][CommLen]byte, challengeSeed [CommLen]byte, proofs [][]byte, faults []uint64) (bool, error) {
// sectorbuilder.VerifyPost()
panic("no")

View File

@ -59,7 +59,12 @@ class Address extends React.Component {
addr = <a href="#" onClick={this.openState}>{addr}</a>
}
return <span>{addr}:&nbsp;{this.state.balance}&nbsp;{actInfo}&nbsp;{add1k}</span>
let balance = <span>:&nbsp;{this.state.balance}</span>
if(this.props.nobalance) {
balance = <span></span>
}
return <span>{addr}{balance}&nbsp;{actInfo}&nbsp;{add1k}</span>
}
}

View File

@ -16,10 +16,14 @@
background: #f9be77;
user-select: text;
font-family: monospace;
min-width: 40em;
min-width: 50em;
display: inline-block;
}
.FullNode-voucher {
padding-left: 1em;
}
.StorageNode {
background: #f9be77;
user-select: text;

View File

@ -36,21 +36,17 @@ class FullNode extends React.Component {
const tipset = await this.props.client.call("Filecoin.ChainHead", [])
const addrs = await this.props.client.call('Filecoin.WalletList', [])
let addrs = await this.props.client.call('Filecoin.WalletList', [])
let defaultAddr = ""
if (addrs.length > 0) {
defaultAddr = await this.props.client.call('Filecoin.WalletDefaultAddress', [])
}
/* const balances = await addrss.map(async addr => {
let balance = 0
try {
balance = await this.props.client.call('Filecoin.WalletBalance', [addr])
} catch {
balance = -1
}
return [addr, balance]
}).reduce(awaitListReducer, Promise.resolve([]))*/
let paychs = await this.props.client.call('Filecoin.PaychList', [])
if(!paychs)
paychs = []
const vouchers = await Promise.all(paychs.map(paych => {
return this.props.client.call('Filecoin.PaychVoucherList', [paych])
}))
this.setState(() => ({
id: id,
@ -59,6 +55,9 @@ class FullNode extends React.Component {
tipset: tipset,
addrs: addrs,
paychs: paychs,
vouchers: vouchers,
defaultAddr: defaultAddr}))
}
@ -118,6 +117,23 @@ class FullNode extends React.Component {
}
return <div key={addr}>{line}</div>
})
let paychannels = this.state.paychs.map((addr, ak) => {
const line = <Address client={this.props.client} add1k={this.add1k} addr={addr} mountWindow={this.props.mountWindow}/>
const vouchers = this.state.vouchers[ak].map(voucher => {
let extra = <span></span>
if(voucher.Extra) {
extra = <span>Verif: &lt;<b><Address nobalance={true} client={this.props.client} addr={voucher.Extra.Actor} mountWindow={this.props.mountWindow}/>M{voucher.Extra.Method}</b>&gt;</span>
}
return <div key={voucher.Nonce} className="FullNode-voucher">
Voucher Nonce:<b>{voucher.Nonce}</b> Lane:<b>{voucher.Lane}</b> Amt:<b>{voucher.Amount}</b> TL:<b>{voucher.TimeLock}</b> MinCl:<b>{voucher.MinCloseHeight}</b> {extra}
</div>
})
return <div key={addr}>
{line}
{vouchers}
</div>
})
runtime = (
<div>
@ -130,6 +146,7 @@ class FullNode extends React.Component {
<div>
<div>Balances: [New <a href="#" onClick={this.newScepAddr}>[Secp256k1]</a>]</div>
<div>{addresses}</div>
<div>{paychannels}</div>
</div>
</div>

View File

@ -47,11 +47,13 @@ type FullNodeAPI struct {
}
func (a *FullNodeAPI) ClientStartDeal(ctx context.Context, data cid.Cid, miner address.Address, price types.BigInt, blocksDuration uint64) (*cid.Cid, error) {
// TODO: make this a param
self, err := a.WalletDefaultAddress(ctx)
if err != nil {
return nil, err
}
// get miner peerID
msg := &types.Message{
To: miner,
From: miner,
@ -67,8 +69,59 @@ func (a *FullNodeAPI) ClientStartDeal(ctx context.Context, data cid.Cid, miner a
return nil, err
}
vd, err := a.DealClient.VerifyParams(ctx, data)
if err != nil {
return nil, err
}
voucherData, err := cbor.DumpObject(vd)
if err != nil {
return nil, err
}
// setup payments
total := types.BigMul(price, types.NewInt(blocksDuration))
c, err := a.DealClient.Start(ctx, data, total, self, miner, pid, blocksDuration)
// TODO: at least ping the miner before creating paych / locking the money
paych, paychMsg, err := a.paychCreate(ctx, self, miner, total)
if err != nil {
return nil, err
}
voucher := types.SignedVoucher{
// TimeLock: 0, // TODO: do we want to use this somehow?
Extra: &types.ModVerifyParams{
Actor: miner,
Method: actors.MAMethods.PaymentVerifyInclusion,
Data: voucherData,
},
Lane: 0,
Amount: total,
MinCloseHeight: blocksDuration, // TODO: some way to start this after initial piece inclusion by actor? (also, at least add current height)
}
sv, err := a.paychVoucherCreate(ctx, paych, voucher)
if err != nil {
return nil, err
}
proposal := deals.ClientDealProposal{
Data: data,
TotalPrice: total,
Duration: blocksDuration,
Payment: actors.PaymentInfo{
PayChActor: paych,
Payer: self,
ChannelMessage: paychMsg,
Vouchers: []types.SignedVoucher{*sv},
},
MinerAddress: miner,
ClientAddress: self,
MinerID: pid,
}
c, err := a.DealClient.Start(ctx, proposal, vd)
// TODO: send updated voucher with PaymentVerifySector for cheaper validation (validate the sector the miner sent us first!)
return &c, err
}
@ -420,15 +473,19 @@ func (a *FullNodeAPI) StateMinerProvingSet(ctx context.Context, addr address.Add
}
func (a *FullNodeAPI) PaychCreate(ctx context.Context, from, to address.Address, amt types.BigInt) (address.Address, error) {
act, _, err := a.paychCreate(ctx, from, to, amt)
return act, err
}
func (a *FullNodeAPI) paychCreate(ctx context.Context, from, to address.Address, amt types.BigInt) (address.Address, cid.Cid, error) {
params, aerr := actors.SerializeParams(&actors.PCAConstructorParams{To: to})
if aerr != nil {
return address.Undef, aerr
return address.Undef, cid.Undef, aerr
}
nonce, err := a.MpoolGetNonce(ctx, from)
if err != nil {
return address.Undef, err
return address.Undef, cid.Undef, err
}
enc, err := actors.SerializeParams(&actors.ExecParams{
@ -449,12 +506,12 @@ func (a *FullNodeAPI) PaychCreate(ctx context.Context, from, to address.Address,
ser, err := msg.Serialize()
if err != nil {
return address.Undef, err
return address.Undef, cid.Undef, err
}
sig, err := a.WalletSign(ctx, from, ser)
if err != nil {
return address.Undef, err
return address.Undef, cid.Undef, err
}
smsg := &types.SignedMessage{
@ -463,28 +520,28 @@ func (a *FullNodeAPI) PaychCreate(ctx context.Context, from, to address.Address,
}
if err := a.MpoolPush(ctx, smsg); err != nil {
return address.Undef, err
return address.Undef, cid.Undef, err
}
mwait, err := a.ChainWaitMsg(ctx, smsg.Cid())
if err != nil {
return address.Undef, err
return address.Undef, cid.Undef, err
}
if mwait.Receipt.ExitCode != 0 {
return address.Undef, fmt.Errorf("payment channel creation failed (exit code %d)", mwait.Receipt.ExitCode)
return address.Undef, cid.Undef, fmt.Errorf("payment channel creation failed (exit code %d)", mwait.Receipt.ExitCode)
}
paychaddr, err := address.NewFromBytes(mwait.Receipt.Return)
if err != nil {
return address.Undef, err
return address.Undef, cid.Undef, err
}
if err := a.PaychMgr.TrackOutboundChannel(ctx, paychaddr); err != nil {
return address.Undef, err
return address.Undef, cid.Undef, err
}
return paychaddr, nil
return paychaddr, msg.Cid(), nil
}
func (a *FullNodeAPI) PaychList(ctx context.Context) ([]address.Address, error) {
@ -551,21 +608,22 @@ func (a *FullNodeAPI) PaychVoucherAdd(ctx context.Context, ch address.Address, s
// actual additional value of this voucher will only be the difference between
// the two.
func (a *FullNodeAPI) PaychVoucherCreate(ctx context.Context, pch address.Address, amt types.BigInt, lane uint64) (*types.SignedVoucher, error) {
return a.paychVoucherCreate(ctx, pch, types.SignedVoucher{Amount: amt, Lane: lane})
}
func (a *FullNodeAPI) paychVoucherCreate(ctx context.Context, pch address.Address, voucher types.SignedVoucher) (*types.SignedVoucher, error) {
ci, err := a.PaychMgr.GetChannelInfo(pch)
if err != nil {
return nil, err
}
nonce, err := a.PaychMgr.NextNonceForLane(ctx, pch, lane)
nonce, err := a.PaychMgr.NextNonceForLane(ctx, pch, voucher.Lane)
if err != nil {
return nil, err
}
sv := &types.SignedVoucher{
Lane: lane,
Nonce: nonce,
Amount: amt,
}
sv := &voucher
sv.Nonce = nonce
vb, err := sv.SigningBytes()
if err != nil {