lotus/chain/deals/client.go

252 lines
5.7 KiB
Go
Raw Normal View History

2019-08-01 17:12:41 +00:00
package deals
import (
"context"
2019-08-06 22:04:21 +00:00
"math"
2019-08-01 17:12:41 +00:00
2019-08-16 04:40:59 +00:00
"github.com/filecoin-project/go-lotus/chain/actors"
2019-08-02 16:25:10 +00:00
sectorbuilder "github.com/filecoin-project/go-sectorbuilder"
2019-08-01 17:12:41 +00:00
"github.com/ipfs/go-cid"
2019-08-06 22:04:21 +00:00
"github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/namespace"
2019-08-06 23:08:34 +00:00
files "github.com/ipfs/go-ipfs-files"
2019-08-02 16:25:10 +00:00
cbor "github.com/ipfs/go-ipld-cbor"
2019-08-01 17:12:41 +00:00
logging "github.com/ipfs/go-log"
2019-08-06 23:08:34 +00:00
unixfile "github.com/ipfs/go-unixfs/file"
2019-08-02 14:09:54 +00:00
"github.com/libp2p/go-libp2p-core/host"
2019-08-07 19:48:53 +00:00
inet "github.com/libp2p/go-libp2p-core/network"
2019-08-01 17:12:41 +00:00
"github.com/libp2p/go-libp2p-core/peer"
2019-08-06 22:04:21 +00:00
"golang.org/x/xerrors"
2019-08-01 17:12:41 +00:00
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/store"
"github.com/filecoin-project/go-lotus/chain/types"
2019-08-02 16:25:10 +00:00
"github.com/filecoin-project/go-lotus/chain/wallet"
2019-08-02 14:09:54 +00:00
"github.com/filecoin-project/go-lotus/lib/cborrpc"
2019-08-06 22:04:21 +00:00
"github.com/filecoin-project/go-lotus/node/modules/dtypes"
2019-08-01 17:12:41 +00:00
)
2019-08-06 23:08:34 +00:00
func init() {
cbor.RegisterCborType(ClientDeal{})
}
2019-08-01 17:12:41 +00:00
var log = logging.Logger("deals")
2019-08-02 14:09:54 +00:00
const ProtocolID = "/fil/storage/mk/1.0.0"
2019-08-01 17:12:41 +00:00
type DealStatus int
const (
DealResolvingMiner = DealStatus(iota)
)
2019-08-06 22:04:21 +00:00
type ClientDeal struct {
ProposalCid cid.Cid
Status DealStatus
Miner peer.ID
2019-08-01 17:12:41 +00:00
}
type Client struct {
2019-08-06 23:20:34 +00:00
cs *store.ChainStore
h host.Host
w *wallet.Wallet
2019-08-06 23:08:34 +00:00
dag dtypes.ClientDAG
2019-08-01 17:12:41 +00:00
2019-08-06 22:04:21 +00:00
deals StateStore
2019-08-01 17:12:41 +00:00
2019-08-06 22:04:21 +00:00
incoming chan ClientDeal
2019-08-01 17:12:41 +00:00
stop chan struct{}
stopped chan struct{}
}
2019-08-06 23:08:34 +00:00
func NewClient(cs *store.ChainStore, h host.Host, w *wallet.Wallet, ds dtypes.MetadataDS, dag dtypes.ClientDAG) *Client {
2019-08-01 17:12:41 +00:00
c := &Client{
2019-08-06 23:20:34 +00:00
cs: cs,
h: h,
w: w,
2019-08-06 23:08:34 +00:00
dag: dag,
2019-08-01 17:12:41 +00:00
2019-08-06 22:04:21 +00:00
deals: StateStore{ds: namespace.Wrap(ds, datastore.NewKey("/deals/client"))},
2019-08-01 17:12:41 +00:00
2019-08-06 22:04:21 +00:00
incoming: make(chan ClientDeal, 16),
2019-08-01 17:12:41 +00:00
stop: make(chan struct{}),
stopped: make(chan struct{}),
}
return c
}
func (c *Client) Run() {
go func() {
defer close(c.stopped)
for {
select {
case deal := <-c.incoming:
log.Info("incoming deal")
2019-08-02 14:09:54 +00:00
// TODO: track in datastore
2019-08-06 22:04:21 +00:00
if err := c.deals.Begin(deal.ProposalCid, deal); err != nil {
log.Errorf("deal state begin failed: %s", err)
continue
}
2019-08-01 17:12:41 +00:00
case <-c.stop:
return
}
}
}()
}
2019-08-07 19:48:53 +00:00
func (c *Client) commP(ctx context.Context, data cid.Cid) ([]byte, int64, error) {
2019-08-06 23:08:34 +00:00
root, err := c.dag.Get(ctx, data)
2019-08-01 17:12:41 +00:00
if err != nil {
2019-08-06 23:08:34 +00:00
log.Errorf("failed to get file root for deal: %s", err)
2019-08-07 19:48:53 +00:00
return nil, 0, err
2019-08-01 17:12:41 +00:00
}
2019-08-06 23:08:34 +00:00
n, err := unixfile.NewUnixfsFile(ctx, c.dag, root)
2019-08-01 17:12:41 +00:00
if err != nil {
2019-08-06 23:08:34 +00:00
log.Errorf("cannot open unixfs file: %s", err)
2019-08-07 19:48:53 +00:00
return nil, 0, err
2019-08-06 23:08:34 +00:00
}
uf, ok := n.(files.File)
if !ok {
// TODO: we probably got directory, how should we handle this in unixfs mode?
2019-08-07 19:48:53 +00:00
return nil, 0, xerrors.New("unsupported unixfs type")
2019-08-06 23:08:34 +00:00
}
2019-08-07 11:24:03 +00:00
size, err := uf.Size()
if err != nil {
2019-08-07 19:48:53 +00:00
return nil, 0, err
2019-08-07 11:24:03 +00:00
}
2019-08-07 19:48:53 +00:00
var commP [sectorbuilder.CommitmentBytesLen]byte
err = withTemp(uf, func(f string) error {
commP, err = sectorbuilder.GeneratePieceCommitment(f, uint64(size))
return err
})
return commP[:], size, err
}
func (c *Client) sendProposal(s inet.Stream, proposal StorageDealProposal, from address.Address) error {
log.Info("Sending deal proposal")
msg, err := cbor.DumpObject(proposal)
2019-08-06 23:08:34 +00:00
if err != nil {
2019-08-07 19:48:53 +00:00
return err
2019-08-06 23:08:34 +00:00
}
2019-08-16 04:40:59 +00:00
sig, err := c.w.Sign(context.TODO(), from, msg)
2019-08-07 19:48:53 +00:00
if err != nil {
return err
2019-08-02 14:09:54 +00:00
}
2019-08-07 19:48:53 +00:00
signedProposal := &SignedStorageDealProposal{
Proposal: proposal,
Signature: sig,
2019-08-02 14:09:54 +00:00
}
2019-08-07 19:48:53 +00:00
return cborrpc.WriteCborRPC(s, signedProposal)
}
func (c *Client) waitAccept(s inet.Stream, proposal StorageDealProposal, minerID peer.ID) (ClientDeal, error) {
log.Info("Waiting for response")
var resp SignedStorageDealResponse
if err := cborrpc.ReadCborRPC(s, &resp); err != nil {
log.Errorw("failed to read StorageDealResponse message", "error", err)
return ClientDeal{}, err
}
// TODO: verify signature
if resp.Response.State != Accepted {
return ClientDeal{}, xerrors.Errorf("Deal wasn't accepted (State=%d)", resp.Response.State)
}
proposalNd, err := cbor.WrapObject(proposal, math.MaxUint64, -1)
2019-08-02 14:09:54 +00:00
if err != nil {
2019-08-07 19:48:53 +00:00
return ClientDeal{}, err
}
if resp.Response.Proposal != proposalNd.Cid() {
return ClientDeal{}, xerrors.New("miner responded to a wrong proposal")
2019-08-02 14:09:54 +00:00
}
2019-08-07 19:48:53 +00:00
return ClientDeal{
ProposalCid: proposalNd.Cid(),
Status: DealResolvingMiner,
Miner: minerID,
}, nil
}
2019-08-15 00:28:52 +00:00
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) {
2019-08-15 14:28:11 +00:00
commP, size, err := c.commP(ctx, data)
2019-08-07 19:48:53 +00:00
if err != nil {
2019-08-15 14:28:11 +00:00
return nil, err
2019-08-01 17:12:41 +00:00
}
return &actors.PieceInclVoucherData{
2019-08-15 14:28:11 +00:00
CommP: commP,
PieceSize: types.NewInt(uint64(size)),
}, nil
}
func (c *Client) Start(ctx context.Context, p ClientDealProposal, vd *actors.PieceInclVoucherData) (cid.Cid, error) {
2019-08-02 14:09:54 +00:00
// TODO: use data
proposal := StorageDealProposal{
2019-08-26 08:02:26 +00:00
PieceRef: p.Data,
2019-08-06 23:08:34 +00:00
SerializationMode: SerializationUnixFs,
2019-08-15 14:28:11 +00:00
CommP: vd.CommP[:],
Size: vd.PieceSize.Uint64(),
2019-08-15 00:28:52 +00:00
TotalPrice: p.TotalPrice,
Duration: p.Duration,
Payment: p.Payment,
MinerAddress: p.MinerAddress,
ClientAddress: p.ClientAddress,
}
s, err := c.h.NewStream(ctx, p.MinerID, ProtocolID)
2019-08-01 17:12:41 +00:00
if err != nil {
2019-08-06 22:04:21 +00:00
return cid.Undef, err
2019-08-01 17:12:41 +00:00
}
2019-08-07 19:48:53 +00:00
defer s.Reset() // TODO: handle other updates
2019-08-02 16:25:10 +00:00
2019-08-15 00:28:52 +00:00
if err := c.sendProposal(s, proposal, p.ClientAddress); err != nil {
2019-08-06 22:04:21 +00:00
return cid.Undef, err
}
2019-08-15 00:28:52 +00:00
deal, err := c.waitAccept(s, proposal, p.MinerID)
2019-08-06 22:04:21 +00:00
if err != nil {
return cid.Undef, err
}
2019-08-07 19:48:53 +00:00
log.Info("DEAL ACCEPTED!")
2019-08-01 17:12:41 +00:00
2019-08-07 19:48:53 +00:00
// TODO: actually care about what happens with the deal after it was accepted
//c.incoming <- deal
return deal.ProposalCid, nil
2019-08-01 17:12:41 +00:00
}
func (c *Client) Stop() {
close(c.stop)
<-c.stopped
}