feat(storagemarket): extract storage market

remove all shared components, point at go-fil-components repo
This commit is contained in:
hannahhoward
2020-01-10 09:49:07 -08:00
parent 0a4eeb7cb1
commit 65ecb33630
30 changed files with 325 additions and 3654 deletions
-784
View File
@@ -1,784 +0,0 @@
package deals
import (
"fmt"
"io"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
"github.com/libp2p/go-libp2p-core/peer"
cbg "github.com/whyrusleeping/cbor-gen"
xerrors "golang.org/x/xerrors"
)
// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT.
var _ = xerrors.Errorf
func (t *AskRequest) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{129}); err != nil {
return err
}
// t.Miner (address.Address) (struct)
if err := t.Miner.MarshalCBOR(w); err != nil {
return err
}
return nil
}
func (t *AskRequest) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 1 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.Miner (address.Address) (struct)
{
if err := t.Miner.UnmarshalCBOR(br); err != nil {
return err
}
}
return nil
}
func (t *AskResponse) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{129}); err != nil {
return err
}
// t.Ask (types.SignedStorageAsk) (struct)
if err := t.Ask.MarshalCBOR(w); err != nil {
return err
}
return nil
}
func (t *AskResponse) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 1 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.Ask (types.SignedStorageAsk) (struct)
{
pb, err := br.PeekByte()
if err != nil {
return err
}
if pb == cbg.CborNull[0] {
var nbuf [1]byte
if _, err := br.Read(nbuf[:]); err != nil {
return err
}
} else {
t.Ask = new(types.SignedStorageAsk)
if err := t.Ask.UnmarshalCBOR(br); err != nil {
return err
}
}
}
return nil
}
func (t *Proposal) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{130}); err != nil {
return err
}
// t.DealProposal (actors.StorageDealProposal) (struct)
if err := t.DealProposal.MarshalCBOR(w); err != nil {
return err
}
// t.Piece (cid.Cid) (struct)
if err := cbg.WriteCid(w, t.Piece); err != nil {
return xerrors.Errorf("failed to write cid field t.Piece: %w", err)
}
return nil
}
func (t *Proposal) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 2 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.DealProposal (actors.StorageDealProposal) (struct)
{
pb, err := br.PeekByte()
if err != nil {
return err
}
if pb == cbg.CborNull[0] {
var nbuf [1]byte
if _, err := br.Read(nbuf[:]); err != nil {
return err
}
} else {
t.DealProposal = new(actors.StorageDealProposal)
if err := t.DealProposal.UnmarshalCBOR(br); err != nil {
return err
}
}
}
// t.Piece (cid.Cid) (struct)
{
c, err := cbg.ReadCid(br)
if err != nil {
return xerrors.Errorf("failed to read cid field t.Piece: %w", err)
}
t.Piece = c
}
return nil
}
func (t *Response) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{132}); err != nil {
return err
}
// t.State (uint64) (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.State))); err != nil {
return err
}
// t.Message (string) (string)
if len(t.Message) > cbg.MaxLength {
return xerrors.Errorf("Value in field t.Message was too long")
}
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len(t.Message)))); err != nil {
return err
}
if _, err := w.Write([]byte(t.Message)); err != nil {
return err
}
// t.Proposal (cid.Cid) (struct)
if err := cbg.WriteCid(w, t.Proposal); err != nil {
return xerrors.Errorf("failed to write cid field t.Proposal: %w", err)
}
// t.PublishMessage (cid.Cid) (struct)
if t.PublishMessage == nil {
if _, err := w.Write(cbg.CborNull); err != nil {
return err
}
} else {
if err := cbg.WriteCid(w, *t.PublishMessage); err != nil {
return xerrors.Errorf("failed to write cid field t.PublishMessage: %w", err)
}
}
return nil
}
func (t *Response) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 4 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.State (uint64) (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.State = uint64(extra)
// t.Message (string) (string)
{
sval, err := cbg.ReadString(br)
if err != nil {
return err
}
t.Message = string(sval)
}
// t.Proposal (cid.Cid) (struct)
{
c, err := cbg.ReadCid(br)
if err != nil {
return xerrors.Errorf("failed to read cid field t.Proposal: %w", err)
}
t.Proposal = c
}
// t.PublishMessage (cid.Cid) (struct)
{
pb, err := br.PeekByte()
if err != nil {
return err
}
if pb == cbg.CborNull[0] {
var nbuf [1]byte
if _, err := br.Read(nbuf[:]); err != nil {
return err
}
} else {
c, err := cbg.ReadCid(br)
if err != nil {
return xerrors.Errorf("failed to read cid field t.PublishMessage: %w", err)
}
t.PublishMessage = &c
}
}
return nil
}
func (t *SignedResponse) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{130}); err != nil {
return err
}
// t.Response (deals.Response) (struct)
if err := t.Response.MarshalCBOR(w); err != nil {
return err
}
// t.Signature (types.Signature) (struct)
if err := t.Signature.MarshalCBOR(w); err != nil {
return err
}
return nil
}
func (t *SignedResponse) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 2 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.Response (deals.Response) (struct)
{
if err := t.Response.UnmarshalCBOR(br); err != nil {
return err
}
}
// t.Signature (types.Signature) (struct)
{
pb, err := br.PeekByte()
if err != nil {
return err
}
if pb == cbg.CborNull[0] {
var nbuf [1]byte
if _, err := br.Read(nbuf[:]); err != nil {
return err
}
} else {
t.Signature = new(types.Signature)
if err := t.Signature.UnmarshalCBOR(br); err != nil {
return err
}
}
}
return nil
}
func (t *ClientDealProposal) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{136}); err != nil {
return err
}
// t.Data (cid.Cid) (struct)
if err := cbg.WriteCid(w, t.Data); err != nil {
return xerrors.Errorf("failed to write cid field t.Data: %w", err)
}
// t.PricePerEpoch (types.BigInt) (struct)
if err := t.PricePerEpoch.MarshalCBOR(w); err != nil {
return err
}
// t.ProposalExpiration (uint64) (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.ProposalExpiration))); err != nil {
return err
}
// t.Duration (uint64) (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.Duration))); err != nil {
return err
}
// t.ProviderAddress (address.Address) (struct)
if err := t.ProviderAddress.MarshalCBOR(w); err != nil {
return err
}
// t.Client (address.Address) (struct)
if err := t.Client.MarshalCBOR(w); err != nil {
return err
}
// t.MinerWorker (address.Address) (struct)
if err := t.MinerWorker.MarshalCBOR(w); err != nil {
return err
}
// t.MinerID (peer.ID) (string)
if len(t.MinerID) > cbg.MaxLength {
return xerrors.Errorf("Value in field t.MinerID was too long")
}
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len(t.MinerID)))); err != nil {
return err
}
if _, err := w.Write([]byte(t.MinerID)); err != nil {
return err
}
return nil
}
func (t *ClientDealProposal) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 8 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.Data (cid.Cid) (struct)
{
c, err := cbg.ReadCid(br)
if err != nil {
return xerrors.Errorf("failed to read cid field t.Data: %w", err)
}
t.Data = c
}
// t.PricePerEpoch (types.BigInt) (struct)
{
if err := t.PricePerEpoch.UnmarshalCBOR(br); err != nil {
return err
}
}
// t.ProposalExpiration (uint64) (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.ProposalExpiration = uint64(extra)
// t.Duration (uint64) (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.Duration = uint64(extra)
// t.ProviderAddress (address.Address) (struct)
{
if err := t.ProviderAddress.UnmarshalCBOR(br); err != nil {
return err
}
}
// t.Client (address.Address) (struct)
{
if err := t.Client.UnmarshalCBOR(br); err != nil {
return err
}
}
// t.MinerWorker (address.Address) (struct)
{
if err := t.MinerWorker.UnmarshalCBOR(br); err != nil {
return err
}
}
// t.MinerID (peer.ID) (string)
{
sval, err := cbg.ReadString(br)
if err != nil {
return err
}
t.MinerID = peer.ID(sval)
}
return nil
}
func (t *ClientDeal) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{129}); err != nil {
return err
}
// t.ClientDeal (storagemarket.ClientDeal) (struct)
if err := t.ClientDeal.MarshalCBOR(w); err != nil {
return err
}
return nil
}
func (t *ClientDeal) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 1 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.ClientDeal (storagemarket.ClientDeal) (struct)
{
if err := t.ClientDeal.UnmarshalCBOR(br); err != nil {
return err
}
}
return nil
}
func (t *MinerDeal) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{135}); err != nil {
return err
}
// t.Client (peer.ID) (string)
if len(t.Client) > cbg.MaxLength {
return xerrors.Errorf("Value in field t.Client was too long")
}
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len(t.Client)))); err != nil {
return err
}
if _, err := w.Write([]byte(t.Client)); err != nil {
return err
}
// t.Proposal (actors.StorageDealProposal) (struct)
if err := t.Proposal.MarshalCBOR(w); err != nil {
return err
}
// t.ProposalCid (cid.Cid) (struct)
if err := cbg.WriteCid(w, t.ProposalCid); err != nil {
return xerrors.Errorf("failed to write cid field t.ProposalCid: %w", err)
}
// t.State (uint64) (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.State))); err != nil {
return err
}
// t.Ref (cid.Cid) (struct)
if err := cbg.WriteCid(w, t.Ref); err != nil {
return xerrors.Errorf("failed to write cid field t.Ref: %w", err)
}
// t.DealID (uint64) (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.DealID))); err != nil {
return err
}
// t.SectorID (uint64) (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.SectorID))); err != nil {
return err
}
return nil
}
func (t *MinerDeal) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 7 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.Client (peer.ID) (string)
{
sval, err := cbg.ReadString(br)
if err != nil {
return err
}
t.Client = peer.ID(sval)
}
// t.Proposal (actors.StorageDealProposal) (struct)
{
if err := t.Proposal.UnmarshalCBOR(br); err != nil {
return err
}
}
// t.ProposalCid (cid.Cid) (struct)
{
c, err := cbg.ReadCid(br)
if err != nil {
return xerrors.Errorf("failed to read cid field t.ProposalCid: %w", err)
}
t.ProposalCid = c
}
// t.State (uint64) (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.State = uint64(extra)
// t.Ref (cid.Cid) (struct)
{
c, err := cbg.ReadCid(br)
if err != nil {
return xerrors.Errorf("failed to read cid field t.Ref: %w", err)
}
t.Ref = c
}
// t.DealID (uint64) (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.DealID = uint64(extra)
// t.SectorID (uint64) (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.SectorID = uint64(extra)
return nil
}
func (t *StorageDataTransferVoucher) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write([]byte{130}); err != nil {
return err
}
// t.Proposal (cid.Cid) (struct)
if err := cbg.WriteCid(w, t.Proposal); err != nil {
return xerrors.Errorf("failed to write cid field t.Proposal: %w", err)
}
// t.DealID (uint64) (uint64)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.DealID))); err != nil {
return err
}
return nil
}
func (t *StorageDataTransferVoucher) UnmarshalCBOR(r io.Reader) error {
br := cbg.GetPeeker(r)
maj, extra, err := cbg.CborReadHeader(br)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 2 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.Proposal (cid.Cid) (struct)
{
c, err := cbg.ReadCid(br)
if err != nil {
return xerrors.Errorf("failed to read cid field t.Proposal: %w", err)
}
t.Proposal = c
}
// t.DealID (uint64) (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.DealID = uint64(extra)
return nil
}
-293
View File
@@ -1,293 +0,0 @@
package deals
import (
"context"
"github.com/ipfs/go-cid"
logging "github.com/ipfs/go-log/v2"
"github.com/libp2p/go-libp2p-core/host"
inet "github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
cborutil "github.com/filecoin-project/go-cbor-util"
"github.com/filecoin-project/go-fil-markets/retrievalmarket"
"github.com/filecoin-project/go-fil-markets/retrievalmarket/discovery"
"github.com/filecoin-project/go-statestore"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/node/modules/dtypes"
"github.com/filecoin-project/lotus/storagemarket"
)
var log = logging.Logger("deals")
type ClientDeal struct {
storagemarket.ClientDeal
s inet.Stream
}
type Client struct {
h host.Host
// dataTransfer
// TODO: once the data transfer module is complete, the
// client will listen to events on the data transfer module
// Because we are using only a fake DAGService
// implementation, there's no validation or events on the client side
dataTransfer dtypes.ClientDataTransfer
dag dtypes.ClientDAG
discovery *discovery.Local
node storagemarket.StorageClientNode
deals *statestore.StateStore
conns map[cid.Cid]inet.Stream
incoming chan *ClientDeal
updated chan clientDealUpdate
stop chan struct{}
stopped chan struct{}
}
type clientDealUpdate struct {
newState api.DealState
id cid.Cid
err error
mut func(*ClientDeal)
}
func NewClient(h host.Host, dag dtypes.ClientDAG, dataTransfer dtypes.ClientDataTransfer, discovery *discovery.Local, deals dtypes.ClientDealStore, scn storagemarket.StorageClientNode) *Client {
c := &Client{
h: h,
dataTransfer: dataTransfer,
dag: dag,
discovery: discovery,
node: scn,
deals: deals,
conns: map[cid.Cid]inet.Stream{},
incoming: make(chan *ClientDeal, 16),
updated: make(chan clientDealUpdate, 16),
stop: make(chan struct{}),
stopped: make(chan struct{}),
}
return c
}
func (c *Client) Run(ctx context.Context) {
go func() {
defer close(c.stopped)
for {
select {
case deal := <-c.incoming:
c.onIncoming(deal)
case update := <-c.updated:
c.onUpdated(ctx, update)
case <-c.stop:
return
}
}
}()
}
func (c *Client) onIncoming(deal *ClientDeal) {
log.Info("incoming deal")
if _, ok := c.conns[deal.ProposalCid]; ok {
log.Errorf("tracking deal connection: already tracking connection for deal %s", deal.ProposalCid)
return
}
c.conns[deal.ProposalCid] = deal.s
if err := c.deals.Begin(deal.ProposalCid, deal); err != nil {
// We may have re-sent the proposal
log.Errorf("deal tracking failed: %s", err)
c.failDeal(deal.ProposalCid, err)
return
}
go func() {
c.updated <- clientDealUpdate{
newState: api.DealUnknown,
id: deal.ProposalCid,
err: nil,
}
}()
}
func (c *Client) onUpdated(ctx context.Context, update clientDealUpdate) {
log.Infof("Client deal %s updated state to %s", update.id, api.DealStates[update.newState])
var deal ClientDeal
err := c.deals.Mutate(update.id, func(d *ClientDeal) error {
d.State = update.newState
if update.mut != nil {
update.mut(d)
}
deal = *d
return nil
})
if update.err != nil {
log.Errorf("deal %s failed: %s", update.id, update.err)
c.failDeal(update.id, update.err)
return
}
if err != nil {
c.failDeal(update.id, err)
return
}
switch update.newState {
case api.DealUnknown: // new
c.handle(ctx, deal, c.new, api.DealAccepted)
case api.DealAccepted:
c.handle(ctx, deal, c.accepted, api.DealStaged)
case api.DealStaged:
c.handle(ctx, deal, c.staged, api.DealSealing)
case api.DealSealing:
c.handle(ctx, deal, c.sealing, api.DealNoUpdate)
// TODO: DealComplete -> watch for faults, expiration, etc.
}
}
type ClientDealProposal struct {
Data cid.Cid
PricePerEpoch types.BigInt
ProposalExpiration uint64
Duration uint64
ProviderAddress address.Address
Client address.Address
MinerWorker address.Address
MinerID peer.ID
}
func (c *Client) Start(ctx context.Context, p ClientDealProposal) (cid.Cid, error) {
amount := types.BigMul(p.PricePerEpoch, types.NewInt(p.Duration))
if err := c.node.EnsureFunds(ctx, p.Client, storagemarket.TokenAmount(amount)); err != nil {
return cid.Undef, xerrors.Errorf("adding market funds failed: %w", err)
}
commP, pieceSize, err := c.commP(ctx, p.Data)
if err != nil {
return cid.Undef, xerrors.Errorf("computing commP failed: %w", err)
}
dealProposal := &actors.StorageDealProposal{
PieceRef: commP,
PieceSize: uint64(pieceSize),
Client: p.Client,
Provider: p.ProviderAddress,
ProposalExpiration: p.ProposalExpiration,
Duration: p.Duration,
StoragePricePerEpoch: p.PricePerEpoch,
StorageCollateral: types.NewInt(uint64(pieceSize)), // TODO: real calc
}
if err := c.node.SignProposal(ctx, p.Client, dealProposal); err != nil {
return cid.Undef, xerrors.Errorf("signing deal proposal failed: %w", err)
}
proposalNd, err := cborutil.AsIpld(dealProposal)
if err != nil {
return cid.Undef, xerrors.Errorf("getting proposal node failed: %w", err)
}
s, err := c.h.NewStream(ctx, p.MinerID, storagemarket.DealProtocolID)
if err != nil {
return cid.Undef, xerrors.Errorf("connecting to storage provider failed: %w", err)
}
proposal := &Proposal{
DealProposal: dealProposal,
Piece: p.Data,
}
if err := cborutil.WriteCborRPC(s, proposal); err != nil {
s.Reset()
return cid.Undef, xerrors.Errorf("sending proposal to storage provider failed: %w", err)
}
deal := &ClientDeal{
ClientDeal: storagemarket.ClientDeal{
ProposalCid: proposalNd.Cid(),
Proposal: *dealProposal,
State: api.DealUnknown,
Miner: p.MinerID,
MinerWorker: p.MinerWorker,
PayloadCid: p.Data,
},
s: s,
}
c.incoming <- deal
return deal.ProposalCid, c.discovery.AddPeer(p.Data, retrievalmarket.RetrievalPeer{
Address: dealProposal.Provider,
ID: deal.Miner,
})
}
func (c *Client) QueryAsk(ctx context.Context, p peer.ID, a address.Address) (*types.SignedStorageAsk, error) {
s, err := c.h.NewStream(ctx, p, storagemarket.AskProtocolID)
if err != nil {
return nil, xerrors.Errorf("failed to open stream to miner: %w", err)
}
req := &AskRequest{
Miner: a,
}
if err := cborutil.WriteCborRPC(s, req); err != nil {
return nil, xerrors.Errorf("failed to send ask request: %w", err)
}
var out AskResponse
if err := cborutil.ReadCborRPC(s, &out); err != nil {
return nil, xerrors.Errorf("failed to read ask response: %w", err)
}
if out.Ask == nil {
return nil, xerrors.Errorf("got no ask back")
}
if out.Ask.Ask.Miner != a {
return nil, xerrors.Errorf("got back ask for wrong miner")
}
if err := c.checkAskSignature(out.Ask); err != nil {
return nil, xerrors.Errorf("ask was not properly signed")
}
return out.Ask, nil
}
func (c *Client) List() ([]ClientDeal, error) {
var out []ClientDeal
if err := c.deals.List(&out); err != nil {
return nil, err
}
return out, nil
}
func (c *Client) GetDeal(d cid.Cid) (*ClientDeal, error) {
var out ClientDeal
if err := c.deals.Get(d, &out); err != nil {
return nil, err
}
return &out, nil
}
func (c *Client) Stop() {
close(c.stop)
<-c.stopped
}
-97
View File
@@ -1,97 +0,0 @@
package deals
import (
"context"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/types"
)
type clientHandlerFunc func(ctx context.Context, deal ClientDeal) (func(*ClientDeal), error)
func (c *Client) handle(ctx context.Context, deal ClientDeal, cb clientHandlerFunc, next api.DealState) {
go func() {
mut, err := cb(ctx, deal)
if err != nil {
next = api.DealError
}
if err == nil && next == api.DealNoUpdate {
return
}
select {
case c.updated <- clientDealUpdate{
newState: next,
id: deal.ProposalCid,
err: err,
mut: mut,
}:
case <-c.stop:
}
}()
}
func (c *Client) new(ctx context.Context, deal ClientDeal) (func(*ClientDeal), error) {
resp, err := c.readStorageDealResp(deal)
if err != nil {
return nil, err
}
// TODO: verify StorageDealSubmission
if err := c.disconnect(deal); err != nil {
return nil, err
}
/* data transfer happens */
if resp.State != api.DealAccepted {
return nil, xerrors.Errorf("deal wasn't accepted (State=%d)", resp.State)
}
return func(info *ClientDeal) {
info.PublishMessage = resp.PublishMessage
}, nil
}
func (c *Client) accepted(ctx context.Context, deal ClientDeal) (func(*ClientDeal), error) {
log.Infow("DEAL ACCEPTED!")
dealId, err := c.node.ValidatePublishedDeal(ctx, deal.ClientDeal)
if err != nil {
return nil, err
}
return func(info *ClientDeal) {
info.DealID = dealId
}, nil
}
func (c *Client) staged(ctx context.Context, deal ClientDeal) (func(*ClientDeal), error) {
// TODO: Maybe wait for pre-commit
return nil, nil
}
func (c *Client) sealing(ctx context.Context, deal ClientDeal) (func(*ClientDeal), error) {
cb := func(err error) {
select {
case c.updated <- clientDealUpdate{
newState: api.DealComplete,
id: deal.ProposalCid,
err: err,
}:
case <-c.stop:
}
}
err := c.node.OnDealSectorCommitted(ctx, deal.Proposal.Provider, deal.DealID, cb)
return nil, err
}
func (c *Client) checkAskSignature(ask *types.SignedStorageAsk) error {
return c.node.ValidateAskSignature(ask)
}
-118
View File
@@ -1,118 +0,0 @@
package deals
// this file implements storagemarket.StorageClient
import (
"context"
"github.com/ipfs/go-cid"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/storagemarket"
)
func (c *Client) ListProviders(ctx context.Context) (<-chan storagemarket.StorageProviderInfo, error) {
providers, err := c.node.ListStorageProviders(ctx)
if err != nil {
return nil, err
}
out := make(chan storagemarket.StorageProviderInfo)
go func() {
for _, p := range providers {
select {
case out <- *p:
case <-ctx.Done():
return
}
}
}()
return out, nil
}
func (c *Client) ListDeals(ctx context.Context, addr address.Address) ([]actors.OnChainDeal, error) {
return c.node.ListClientDeals(ctx, addr)
}
func (c *Client) ListInProgressDeals(ctx context.Context) ([]storagemarket.ClientDeal, error) {
deals, err := c.List()
if err != nil {
return nil, err
}
out := make([]storagemarket.ClientDeal, len(deals))
for k, v := range deals {
out[k] = storagemarket.ClientDeal{
ProposalCid: v.ProposalCid,
Proposal: v.Proposal,
State: v.State,
Miner: v.Miner,
MinerWorker: v.MinerWorker,
DealID: v.DealID,
PublishMessage: v.PublishMessage,
}
}
return out, nil
}
func (c *Client) GetInProgressDeal(ctx context.Context, cid cid.Cid) (storagemarket.ClientDeal, error) {
deals, err := c.ListInProgressDeals(ctx)
if err != nil {
return storagemarket.ClientDeal{}, err
}
for _, deal := range deals {
if deal.ProposalCid == cid {
return deal, nil
}
}
return storagemarket.ClientDeal{}, xerrors.Errorf("couldn't find client deal")
}
func (c *Client) GetAsk(ctx context.Context, info storagemarket.StorageProviderInfo) (*storagemarket.StorageAsk, error) {
return c.QueryAsk(ctx, info.PeerID, info.Address)
}
func (c *Client) ProposeStorageDeal(ctx context.Context, addr address.Address, info *storagemarket.StorageProviderInfo, payloadCid cid.Cid, proposalExpiration storagemarket.Epoch, duration storagemarket.Epoch, price storagemarket.TokenAmount, collateral storagemarket.TokenAmount) (*storagemarket.ProposeStorageDealResult, error) {
proposal := ClientDealProposal{
Data: payloadCid,
PricePerEpoch: types.BigInt(price),
ProposalExpiration: uint64(proposalExpiration),
Duration: uint64(duration),
Client: addr,
ProviderAddress: info.Address,
MinerWorker: info.Worker,
MinerID: info.PeerID,
}
proposalCid, err := c.Start(ctx, proposal)
result := &storagemarket.ProposeStorageDealResult{
ProposalCid: proposalCid,
}
return result, err
}
func (c *Client) GetPaymentEscrow(ctx context.Context, addr address.Address) (storagemarket.Balance, error) {
balance, err := c.node.GetBalance(ctx, addr)
return balance, err
}
func (c *Client) AddPaymentEscrow(ctx context.Context, addr address.Address, amount storagemarket.TokenAmount) error {
return c.node.AddFunds(ctx, addr, amount)
}
var _ storagemarket.StorageClient = &Client{}
-170
View File
@@ -1,170 +0,0 @@
package deals
import (
"context"
"runtime"
sectorbuilder "github.com/filecoin-project/go-sectorbuilder"
"github.com/ipfs/go-cid"
files "github.com/ipfs/go-ipfs-files"
unixfile "github.com/ipfs/go-unixfs/file"
"github.com/ipld/go-ipld-prime"
"github.com/libp2p/go-libp2p-core/peer"
"golang.org/x/xerrors"
cborutil "github.com/filecoin-project/go-cbor-util"
"github.com/filecoin-project/go-data-transfer"
"github.com/filecoin-project/go-statestore"
"github.com/filecoin-project/lotus/lib/padreader"
"github.com/filecoin-project/lotus/node/modules/dtypes"
)
func (c *Client) failDeal(id cid.Cid, cerr error) {
if cerr == nil {
_, f, l, _ := runtime.Caller(1)
cerr = xerrors.Errorf("unknown error (fail called at %s:%d)", f, l)
}
s, ok := c.conns[id]
if ok {
_ = s.Reset()
delete(c.conns, id)
}
// TODO: store in some sort of audit log
log.Errorf("deal %s failed: %+v", id, cerr)
}
func (c *Client) commP(ctx context.Context, data cid.Cid) ([]byte, uint64, error) {
root, err := c.dag.Get(ctx, data)
if err != nil {
log.Errorf("failed to get file root for deal: %s", err)
return nil, 0, err
}
n, err := unixfile.NewUnixfsFile(ctx, c.dag, root)
if err != nil {
log.Errorf("cannot open unixfs file: %s", err)
return nil, 0, err
}
uf, ok := n.(files.File)
if !ok {
// TODO: we probably got directory, how should we handle this in unixfs mode?
return nil, 0, xerrors.New("unsupported unixfs type")
}
s, err := uf.Size()
if err != nil {
return nil, 0, err
}
pr, psize := padreader.New(uf, uint64(s))
commp, err := sectorbuilder.GeneratePieceCommitment(pr, psize)
if err != nil {
return nil, 0, xerrors.Errorf("generating CommP: %w", err)
}
return commp[:], psize, nil
}
func (c *Client) readStorageDealResp(deal ClientDeal) (*Response, error) {
s, ok := c.conns[deal.ProposalCid]
if !ok {
// TODO: Try to re-establish the connection using query protocol
return nil, xerrors.Errorf("no connection to miner")
}
var resp SignedResponse
if err := cborutil.ReadCborRPC(s, &resp); err != nil {
log.Errorw("failed to read Response message", "error", err)
return nil, err
}
if err := resp.Verify(deal.MinerWorker); err != nil {
return nil, xerrors.Errorf("verifying response signature failed", err)
}
if resp.Response.Proposal != deal.ProposalCid {
return nil, xerrors.Errorf("miner responded to a wrong proposal: %s != %s", resp.Response.Proposal, deal.ProposalCid)
}
return &resp.Response, nil
}
func (c *Client) disconnect(deal ClientDeal) error {
s, ok := c.conns[deal.ProposalCid]
if !ok {
return nil
}
err := s.Close()
delete(c.conns, deal.ProposalCid)
return err
}
var _ datatransfer.RequestValidator = &ClientRequestValidator{}
// ClientRequestValidator validates data transfer requests for the client
// in a storage market
type ClientRequestValidator struct {
deals *statestore.StateStore
}
// NewClientRequestValidator returns a new client request validator for the
// given datastore
func NewClientRequestValidator(deals dtypes.ClientDealStore) *ClientRequestValidator {
crv := &ClientRequestValidator{
deals: deals,
}
return crv
}
// ValidatePush validates a push request received from the peer that will send data
// Will always error because clients should not accept push requests from a provider
// in a storage deal (i.e. send data to client).
func (c *ClientRequestValidator) ValidatePush(
sender peer.ID,
voucher datatransfer.Voucher,
baseCid cid.Cid,
Selector ipld.Node) error {
return ErrNoPushAccepted
}
// ValidatePull validates a pull request received from the peer that will receive data
// Will succeed only if:
// - voucher has correct type
// - voucher references an active deal
// - referenced deal matches the receiver (miner)
// - referenced deal matches the given base CID
// - referenced deal is in an acceptable state
func (c *ClientRequestValidator) ValidatePull(
receiver peer.ID,
voucher datatransfer.Voucher,
baseCid cid.Cid,
Selector ipld.Node) error {
dealVoucher, ok := voucher.(*StorageDataTransferVoucher)
if !ok {
return xerrors.Errorf("voucher type %s: %w", voucher.Type(), ErrWrongVoucherType)
}
var deal ClientDeal
err := c.deals.Get(dealVoucher.Proposal, &deal)
if err != nil {
return xerrors.Errorf("Proposal CID %s: %w", dealVoucher.Proposal.String(), ErrNoDeal)
}
if deal.Miner != receiver {
return xerrors.Errorf("Deal Peer %s, Data Transfer Peer %s: %w", deal.Miner.String(), receiver.String(), ErrWrongPeer)
}
if !deal.PayloadCid.Equals(baseCid) {
return xerrors.Errorf("Deal Payload CID %s, Data Transfer CID %s: %w", string(deal.Proposal.PieceRef), baseCid.String(), ErrWrongPiece)
}
for _, state := range DataTransferStates {
if deal.State == state {
return nil
}
}
return xerrors.Errorf("Deal State %s: %w", deal.State, ErrInacceptableDealState)
}
-293
View File
@@ -1,293 +0,0 @@
package deals
import (
"context"
"errors"
"sync"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/namespace"
"github.com/libp2p/go-libp2p-core/host"
inet "github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-cbor-util"
"github.com/filecoin-project/go-data-transfer"
"github.com/filecoin-project/go-statestore"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/node/modules/dtypes"
"github.com/filecoin-project/lotus/storagemarket"
)
var ProviderDsPrefix = "/deals/provider"
type MinerDeal struct {
Client peer.ID
Proposal actors.StorageDealProposal
ProposalCid cid.Cid
State api.DealState
Ref cid.Cid
DealID uint64
SectorID uint64 // Set when State >= DealStaged
s inet.Stream
}
type Provider struct {
pricePerByteBlock types.BigInt // how much we want for storing one byte for one block
minPieceSize uint64
ask *types.SignedStorageAsk
askLk sync.Mutex
spn storagemarket.StorageProviderNode
// TODO: This will go away once storage market module + CAR
// is implemented
dag dtypes.StagingDAG
// dataTransfer is the manager of data transfers used by this storage provider
dataTransfer dtypes.ProviderDataTransfer
deals *statestore.StateStore
ds dtypes.MetadataDS
conns map[cid.Cid]inet.Stream
actor address.Address
incoming chan MinerDeal
updated chan minerDealUpdate
stop chan struct{}
stopped chan struct{}
}
type minerDealUpdate struct {
newState api.DealState
id cid.Cid
err error
mut func(*MinerDeal)
}
var (
// ErrDataTransferFailed means a data transfer for a deal failed
ErrDataTransferFailed = errors.New("deal data transfer failed")
)
func NewProvider(ds dtypes.MetadataDS, dag dtypes.StagingDAG, dataTransfer dtypes.ProviderDataTransfer, spn storagemarket.StorageProviderNode) (storagemarket.StorageProvider, error) {
addr, err := ds.Get(datastore.NewKey("miner-address"))
if err != nil {
return nil, err
}
minerAddress, err := address.NewFromBytes(addr)
if err != nil {
return nil, err
}
h := &Provider{
dag: dag,
dataTransfer: dataTransfer,
spn: spn,
pricePerByteBlock: types.NewInt(3), // TODO: allow setting
minPieceSize: 256, // TODO: allow setting (BUT KEEP MIN 256! (because of how we fill sectors up))
conns: map[cid.Cid]inet.Stream{},
incoming: make(chan MinerDeal),
updated: make(chan minerDealUpdate),
stop: make(chan struct{}),
stopped: make(chan struct{}),
actor: minerAddress,
deals: statestore.New(namespace.Wrap(ds, datastore.NewKey(ProviderDsPrefix))),
ds: ds,
}
if err := h.tryLoadAsk(); err != nil {
return nil, err
}
if h.ask == nil {
// TODO: we should be fine with this state, and just say it means 'not actively accepting deals'
// for now... lets just set a price
if err := h.SetPrice(types.NewInt(500_000_000), 1000000); err != nil {
return nil, xerrors.Errorf("failed setting a default price: %w", err)
}
}
// register a data transfer event handler -- this will move deals from
// accepted to staged
h.dataTransfer.SubscribeToEvents(h.onDataTransferEvent)
return h, nil
}
func (p *Provider) Run(ctx context.Context, host host.Host) {
// TODO: restore state
host.SetStreamHandler(storagemarket.DealProtocolID, p.HandleStream)
host.SetStreamHandler(storagemarket.AskProtocolID, p.HandleAskStream)
go func() {
defer log.Warn("quitting deal provider loop")
defer close(p.stopped)
for {
select {
case deal := <-p.incoming: // DealAccepted
p.onIncoming(deal)
case update := <-p.updated: // DealStaged
p.onUpdated(ctx, update)
case <-p.stop:
return
}
}
}()
}
func (p *Provider) onIncoming(deal MinerDeal) {
log.Info("incoming deal")
p.conns[deal.ProposalCid] = deal.s
if err := p.deals.Begin(deal.ProposalCid, &deal); err != nil {
// This can happen when client re-sends proposal
p.failDeal(context.TODO(), deal.ProposalCid, err)
log.Errorf("deal tracking failed: %s", err)
return
}
go func() {
p.updated <- minerDealUpdate{
newState: api.DealAccepted,
id: deal.ProposalCid,
err: nil,
}
}()
}
func (p *Provider) onUpdated(ctx context.Context, update minerDealUpdate) {
log.Infof("Deal %s updated state to %s", update.id, api.DealStates[update.newState])
if update.err != nil {
log.Errorf("deal %s (newSt: %d) failed: %+v", update.id, update.newState, update.err)
p.failDeal(ctx, update.id, update.err)
return
}
var deal MinerDeal
err := p.deals.Mutate(update.id, func(d *MinerDeal) error {
d.State = update.newState
if update.mut != nil {
update.mut(d)
}
deal = *d
return nil
})
if err != nil {
p.failDeal(ctx, update.id, err)
return
}
switch update.newState {
case api.DealAccepted:
p.handle(ctx, deal, p.accept, api.DealNoUpdate)
case api.DealStaged:
p.handle(ctx, deal, p.staged, api.DealSealing)
case api.DealSealing:
p.handle(ctx, deal, p.sealing, api.DealComplete)
case api.DealComplete:
p.handle(ctx, deal, p.complete, api.DealNoUpdate)
}
}
// onDataTransferEvent is the function called when an event occurs in a data
// transfer -- it reads the voucher to verify this even occurred in a storage
// market deal, then, based on the data transfer event that occurred, it generates
// and update message for the deal -- either moving to staged for a completion
// event or moving to error if a data transfer error occurs
func (p *Provider) onDataTransferEvent(event datatransfer.Event, channelState datatransfer.ChannelState) {
voucher, ok := channelState.Voucher().(*StorageDataTransferVoucher)
// if this event is for a transfer not related to storage, ignore
if !ok {
return
}
// data transfer events for opening and progress do not affect deal state
var next api.DealState
var err error
var mut func(*MinerDeal)
switch event.Code {
case datatransfer.Complete:
next = api.DealStaged
mut = func(deal *MinerDeal) {
deal.DealID = voucher.DealID
}
case datatransfer.Error:
next = api.DealFailed
err = ErrDataTransferFailed
default:
// the only events we care about are complete and error
return
}
select {
case p.updated <- minerDealUpdate{
newState: next,
id: voucher.Proposal,
err: err,
mut: mut,
}:
case <-p.stop:
}
}
func (p *Provider) newDeal(s inet.Stream, proposal Proposal) (MinerDeal, error) {
proposalNd, err := cborutil.AsIpld(proposal.DealProposal)
if err != nil {
return MinerDeal{}, err
}
return MinerDeal{
Client: s.Conn().RemotePeer(),
Proposal: *proposal.DealProposal,
ProposalCid: proposalNd.Cid(),
State: api.DealUnknown,
Ref: proposal.Piece,
s: s,
}, nil
}
func (p *Provider) HandleStream(s inet.Stream) {
log.Info("Handling storage deal proposal!")
proposal, err := p.readProposal(s)
if err != nil {
log.Error(err)
s.Close()
return
}
deal, err := p.newDeal(s, proposal)
if err != nil {
log.Errorf("%+v", err)
s.Close()
return
}
p.incoming <- deal
}
func (p *Provider) Stop() {
close(p.stop)
<-p.stopped
}
-143
View File
@@ -1,143 +0,0 @@
package deals
import (
"bytes"
"context"
"time"
"github.com/ipfs/go-datastore"
inet "github.com/libp2p/go-libp2p-core/network"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-cbor-util"
"github.com/filecoin-project/lotus/chain/types"
)
func (p *Provider) SetPrice(price types.BigInt, ttlsecs int64) error {
p.askLk.Lock()
defer p.askLk.Unlock()
var seqno uint64
if p.ask != nil {
seqno = p.ask.Ask.SeqNo + 1
}
now := time.Now().Unix()
ask := &types.StorageAsk{
Price: price,
Timestamp: uint64(now),
Expiry: uint64(now + ttlsecs),
Miner: p.actor,
SeqNo: seqno,
MinPieceSize: p.minPieceSize,
}
ssa, err := p.signAsk(ask)
if err != nil {
return err
}
return p.saveAsk(ssa)
}
func (p *Provider) GetAsk(m address.Address) *types.SignedStorageAsk {
p.askLk.Lock()
defer p.askLk.Unlock()
if m != p.actor {
return nil
}
return p.ask
}
func (p *Provider) HandleAskStream(s inet.Stream) {
defer s.Close()
var ar AskRequest
if err := cborutil.ReadCborRPC(s, &ar); err != nil {
log.Errorf("failed to read AskRequest from incoming stream: %s", err)
return
}
resp := p.processAskRequest(&ar)
if err := cborutil.WriteCborRPC(s, resp); err != nil {
log.Errorf("failed to write ask response: %s", err)
return
}
}
func (p *Provider) processAskRequest(ar *AskRequest) *AskResponse {
return &AskResponse{
Ask: p.GetAsk(ar.Miner),
}
}
var bestAskKey = datastore.NewKey("latest-ask")
func (p *Provider) tryLoadAsk() error {
p.askLk.Lock()
defer p.askLk.Unlock()
err := p.loadAsk()
if err != nil {
if xerrors.Is(err, datastore.ErrNotFound) {
log.Warn("no previous ask found, miner will not accept deals until a price is set")
return nil
}
return err
}
return nil
}
func (p *Provider) loadAsk() error {
askb, err := p.ds.Get(datastore.NewKey("latest-ask"))
if err != nil {
return xerrors.Errorf("failed to load most recent ask from disk: %w", err)
}
var ssa types.SignedStorageAsk
if err := cborutil.ReadCborRPC(bytes.NewReader(askb), &ssa); err != nil {
return err
}
p.ask = &ssa
return nil
}
func (p *Provider) signAsk(a *types.StorageAsk) (*types.SignedStorageAsk, error) {
b, err := cborutil.Dump(a)
if err != nil {
return nil, err
}
worker, err := p.spn.GetMinerWorker(context.TODO(), p.actor)
if err != nil {
return nil, xerrors.Errorf("failed to get worker to sign ask: %w", err)
}
sig, err := p.spn.SignBytes(context.TODO(), worker, b)
if err != nil {
return nil, err
}
return &types.SignedStorageAsk{
Ask: a,
Signature: sig,
}, nil
}
func (p *Provider) saveAsk(a *types.SignedStorageAsk) error {
b, err := cborutil.Dump(a)
if err != nil {
return err
}
if err := p.ds.Put(bestAskKey, b); err != nil {
return err
}
p.ask = a
return nil
}
-172
View File
@@ -1,172 +0,0 @@
package deals
import (
"context"
ipldfree "github.com/ipld/go-ipld-prime/impl/free"
"github.com/ipld/go-ipld-prime/traversal/selector"
"github.com/ipld/go-ipld-prime/traversal/selector/builder"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/storagemarket"
)
type providerHandlerFunc func(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error)
func (p *Provider) handle(ctx context.Context, deal MinerDeal, cb providerHandlerFunc, next api.DealState) {
go func() {
mut, err := cb(ctx, deal)
if err == nil && next == api.DealNoUpdate {
return
}
select {
case p.updated <- minerDealUpdate{
newState: next,
id: deal.ProposalCid,
err: err,
mut: mut,
}:
case <-p.stop:
}
}()
}
// ACCEPTED
func (p *Provider) accept(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
head, err := p.spn.MostRecentStateId(ctx)
if err != nil {
return nil, err
}
if head.Height() >= deal.Proposal.ProposalExpiration {
return nil, xerrors.Errorf("deal proposal already expired")
}
// TODO: check StorageCollateral
minPrice := types.BigDiv(types.BigMul(p.ask.Ask.Price, types.NewInt(deal.Proposal.PieceSize)), types.NewInt(1<<30))
if deal.Proposal.StoragePricePerEpoch.LessThan(minPrice) {
return nil, xerrors.Errorf("storage price per epoch less than asking price: %s < %s", deal.Proposal.StoragePricePerEpoch, minPrice)
}
if deal.Proposal.PieceSize < p.ask.Ask.MinPieceSize {
return nil, xerrors.Errorf("piece size less than minimum required size: %d < %d", deal.Proposal.PieceSize, p.ask.Ask.MinPieceSize)
}
// check market funds
clientMarketBalance, err := p.spn.GetBalance(ctx, deal.Proposal.Client)
if err != nil {
return nil, xerrors.Errorf("getting client market balance failed: %w", err)
}
// This doesn't guarantee that the client won't withdraw / lock those funds
// but it's a decent first filter
if clientMarketBalance.Available.LessThan(deal.Proposal.TotalStoragePrice()) {
return nil, xerrors.New("clientMarketBalance.Available too small")
}
waddr, err := p.spn.GetMinerWorker(ctx, deal.Proposal.Provider)
if err != nil {
return nil, err
}
// TODO: check StorageCollateral (may be too large (or too small))
if err := p.spn.EnsureFunds(ctx, waddr, storagemarket.TokenAmount(deal.Proposal.StorageCollateral)); err != nil {
return nil, err
}
smDeal := storagemarket.MinerDeal{
Client: deal.Client,
Proposal: deal.Proposal,
ProposalCid: deal.ProposalCid,
State: deal.State,
Ref: deal.Ref,
SectorID: deal.SectorID,
}
dealId, mcid, err := p.spn.PublishDeals(ctx, smDeal)
if err != nil {
return nil, err
}
log.Infof("fetching data for a deal %d", dealId)
err = p.sendSignedResponse(ctx, &Response{
State: api.DealAccepted,
Proposal: deal.ProposalCid,
PublishMessage: &mcid,
})
if err != nil {
return nil, err
}
if err := p.disconnect(deal); err != nil {
log.Warnf("closing client connection: %+v", err)
}
ssb := builder.NewSelectorSpecBuilder(ipldfree.NodeBuilder())
// this is the selector for "get the whole DAG"
// TODO: support storage deals with custom payload selectors
allSelector := ssb.ExploreRecursive(selector.RecursionLimitNone(),
ssb.ExploreAll(ssb.ExploreRecursiveEdge())).Node()
// initiate a pull data transfer. This will complete asynchronously and the
// completion of the data transfer will trigger a change in deal state
// (see onDataTransferEvent)
_, err = p.dataTransfer.OpenPullDataChannel(ctx,
deal.Client,
&StorageDataTransferVoucher{Proposal: deal.ProposalCid, DealID: uint64(dealId)},
deal.Ref,
allSelector,
)
if err != nil {
return nil, xerrors.Errorf("failed to open pull data channel: %w", err)
}
return nil, nil
}
// STAGED
func (p *Provider) staged(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
sectorID, err := p.spn.OnDealComplete(
ctx,
storagemarket.MinerDeal{
Client: deal.Client,
Proposal: deal.Proposal,
ProposalCid: deal.ProposalCid,
State: deal.State,
Ref: deal.Ref,
DealID: deal.DealID,
},
"",
)
if err != nil {
return nil, err
}
return func(deal *MinerDeal) {
deal.SectorID = sectorID
}, nil
}
// SEALING
func (p *Provider) sealing(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
// TODO: consider waiting for seal to happen
return nil, nil
}
func (p *Provider) complete(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
// TODO: observe sector lifecycle, status, expiration..
return nil, nil
}
-65
View File
@@ -1,65 +0,0 @@
package deals
// this file implements storagemarket.StorageClient
import (
"context"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/storagemarket"
)
func (p *Provider) AddAsk(price storagemarket.TokenAmount, ttlsecs int64) error {
return p.SetPrice(types.BigInt(price), ttlsecs)
}
func (p *Provider) ListAsks(addr address.Address) []*types.SignedStorageAsk {
ask := p.GetAsk(addr)
if ask != nil {
return []*types.SignedStorageAsk{ask}
}
return nil
}
func (p *Provider) ListDeals(ctx context.Context) ([]actors.OnChainDeal, error) {
return p.spn.ListProviderDeals(ctx, p.actor)
}
func (p *Provider) AddStorageCollateral(ctx context.Context, amount storagemarket.TokenAmount) error {
return p.spn.AddFunds(ctx, p.actor, amount)
}
func (p *Provider) GetStorageCollateral(ctx context.Context) (storagemarket.Balance, error) {
balance, err := p.spn.GetBalance(ctx, p.actor)
return balance, err
}
func (p *Provider) ListIncompleteDeals() ([]storagemarket.MinerDeal, error) {
var out []storagemarket.MinerDeal
var deals []MinerDeal
if err := p.deals.List(&deals); err != nil {
return nil, err
}
for _, deal := range deals {
out = append(out, storagemarket.MinerDeal{
Client: deal.Client,
Proposal: deal.Proposal,
ProposalCid: deal.ProposalCid,
State: deal.State,
Ref: deal.Ref,
DealID: deal.DealID,
SectorID: deal.SectorID,
})
}
return out, nil
}
var _ storagemarket.StorageProvider = &Provider{}
-180
View File
@@ -1,180 +0,0 @@
package deals
import (
"context"
"runtime"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-cbor-util"
"github.com/filecoin-project/go-data-transfer"
"github.com/filecoin-project/go-statestore"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
inet "github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/node/modules/dtypes"
)
func (p *Provider) failDeal(ctx context.Context, id cid.Cid, cerr error) {
if err := p.deals.End(id); err != nil {
log.Warnf("deals.End: %s", err)
}
if cerr == nil {
_, f, l, _ := runtime.Caller(1)
cerr = xerrors.Errorf("unknown error (fail called at %s:%d)", f, l)
}
log.Warnf("deal %s failed: %s", id, cerr)
err := p.sendSignedResponse(ctx, &Response{
State: api.DealFailed,
Message: cerr.Error(),
Proposal: id,
})
s, ok := p.conns[id]
if ok {
_ = s.Reset()
delete(p.conns, id)
}
if err != nil {
log.Warnf("notifying client about deal failure: %s", err)
}
}
func (p *Provider) readProposal(s inet.Stream) (proposal Proposal, err error) {
if err := cborutil.ReadCborRPC(s, &proposal); err != nil {
log.Errorw("failed to read proposal message", "error", err)
return proposal, err
}
if proposal.DealProposal.ProposerSignature == nil {
return proposal, xerrors.Errorf("incoming deal proposal has no signature")
}
if err := proposal.DealProposal.Verify(address.Undef); err != nil {
return proposal, xerrors.Errorf("verifying StorageDealProposal: %w", err)
}
if proposal.DealProposal.Provider != p.actor {
log.Errorf("proposal with wrong ProviderAddress: %s", proposal.DealProposal.Provider)
return proposal, err
}
return
}
func (p *Provider) sendSignedResponse(ctx context.Context, resp *Response) error {
s, ok := p.conns[resp.Proposal]
if !ok {
return xerrors.New("couldn't send response: not connected")
}
msg, err := cborutil.Dump(resp)
if err != nil {
return xerrors.Errorf("serializing response: %w", err)
}
worker, err := p.spn.GetMinerWorker(ctx, p.actor)
if err != nil {
return err
}
sig, err := p.spn.SignBytes(ctx, worker, msg)
if err != nil {
return xerrors.Errorf("failed to sign response message: %w", err)
}
signedResponse := &SignedResponse{
Response: *resp,
Signature: sig,
}
err = cborutil.WriteCborRPC(s, signedResponse)
if err != nil {
// Assume client disconnected
s.Close()
delete(p.conns, resp.Proposal)
}
return err
}
func (p *Provider) disconnect(deal MinerDeal) error {
s, ok := p.conns[deal.ProposalCid]
if !ok {
return nil
}
err := s.Close()
delete(p.conns, deal.ProposalCid)
return err
}
var _ datatransfer.RequestValidator = &ProviderRequestValidator{}
// ProviderRequestValidator validates data transfer requests for the provider
// in a storage market
type ProviderRequestValidator struct {
deals *statestore.StateStore
}
// NewProviderRequestValidator returns a new client request validator for the
// given datastore
func NewProviderRequestValidator(deals dtypes.ProviderDealStore) *ProviderRequestValidator {
return &ProviderRequestValidator{
deals: deals,
}
}
// ValidatePush validates a push request received from the peer that will send data
// Will succeed only if:
// - voucher has correct type
// - voucher references an active deal
// - referenced deal matches the client
// - referenced deal matches the given base CID
// - referenced deal is in an acceptable state
func (m *ProviderRequestValidator) ValidatePush(
sender peer.ID,
voucher datatransfer.Voucher,
baseCid cid.Cid,
Selector ipld.Node) error {
dealVoucher, ok := voucher.(*StorageDataTransferVoucher)
if !ok {
return xerrors.Errorf("voucher type %s: %w", voucher.Type(), ErrWrongVoucherType)
}
var deal MinerDeal
err := m.deals.Get(dealVoucher.Proposal, &deal)
if err != nil {
return xerrors.Errorf("Proposal CID %s: %w", dealVoucher.Proposal.String(), ErrNoDeal)
}
if deal.Client != sender {
return xerrors.Errorf("Deal Peer %s, Data Transfer Peer %s: %w", deal.Client.String(), sender.String(), ErrWrongPeer)
}
if !deal.Ref.Equals(baseCid) {
return xerrors.Errorf("Deal Payload CID %s, Data Transfer CID %s: %w", string(deal.Proposal.PieceRef), baseCid.String(), ErrWrongPiece)
}
for _, state := range DataTransferStates {
if deal.State == state {
return nil
}
}
return xerrors.Errorf("Deal State %s: %w", deal.State, ErrInacceptableDealState)
}
// ValidatePull validates a pull request received from the peer that will receive data.
// Will always error because providers should not accept pull requests from a client
// in a storage deal (i.e. send data to client).
func (m *ProviderRequestValidator) ValidatePull(
receiver peer.ID,
voucher datatransfer.Voucher,
baseCid cid.Cid,
Selector ipld.Node) error {
return ErrNoPullAccepted
}
-274
View File
@@ -1,274 +0,0 @@
package deals_test
import (
"fmt"
"math/rand"
"testing"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/namespace"
dss "github.com/ipfs/go-datastore/sync"
blocksutil "github.com/ipfs/go-ipfs-blocksutil"
"github.com/libp2p/go-libp2p-core/peer"
xerrors "golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-cbor-util"
"github.com/filecoin-project/go-statestore"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/deals"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/storagemarket"
)
var blockGenerator = blocksutil.NewBlockGenerator()
type wrongDTType struct {
}
func (wrongDTType) ToBytes() ([]byte, error) {
return []byte{}, nil
}
func (wrongDTType) FromBytes([]byte) error {
return fmt.Errorf("not implemented")
}
func (wrongDTType) Type() string {
return "WrongDTTYPE"
}
func uniqueStorageDealProposal() (actors.StorageDealProposal, error) {
clientAddr, err := address.NewIDAddress(uint64(rand.Int()))
if err != nil {
return actors.StorageDealProposal{}, err
}
providerAddr, err := address.NewIDAddress(uint64(rand.Int()))
if err != nil {
return actors.StorageDealProposal{}, err
}
return actors.StorageDealProposal{
PieceRef: blockGenerator.Next().Cid().Bytes(),
Client: clientAddr,
Provider: providerAddr,
ProposerSignature: &types.Signature{
Data: []byte("foo bar cat dog"),
Type: types.KTBLS,
},
}, nil
}
func newClientDeal(minerID peer.ID, state api.DealState) (deals.ClientDeal, error) {
newProposal, err := uniqueStorageDealProposal()
if err != nil {
return deals.ClientDeal{}, err
}
proposalNd, err := cborutil.AsIpld(&newProposal)
if err != nil {
return deals.ClientDeal{}, err
}
minerAddr, err := address.NewIDAddress(uint64(rand.Int()))
if err != nil {
return deals.ClientDeal{}, err
}
return deals.ClientDeal{
ClientDeal: storagemarket.ClientDeal{
Proposal: newProposal,
ProposalCid: proposalNd.Cid(),
PayloadCid: blockGenerator.Next().Cid(),
Miner: minerID,
MinerWorker: minerAddr,
State: state,
},
}, nil
}
func newMinerDeal(clientID peer.ID, state api.DealState) (deals.MinerDeal, error) {
newProposal, err := uniqueStorageDealProposal()
if err != nil {
return deals.MinerDeal{}, err
}
proposalNd, err := cborutil.AsIpld(&newProposal)
if err != nil {
return deals.MinerDeal{}, err
}
ref := blockGenerator.Next().Cid()
return deals.MinerDeal{
Proposal: newProposal,
ProposalCid: proposalNd.Cid(),
Client: clientID,
State: state,
Ref: ref,
}, nil
}
func TestClientRequestValidation(t *testing.T) {
ds := dss.MutexWrap(datastore.NewMapDatastore())
state := statestore.New(namespace.Wrap(ds, datastore.NewKey("/deals/client")))
crv := deals.NewClientRequestValidator(state)
minerID := peer.ID("fakepeerid")
block := blockGenerator.Next()
t.Run("ValidatePush fails", func(t *testing.T) {
if !xerrors.Is(crv.ValidatePush(minerID, wrongDTType{}, block.Cid(), nil), deals.ErrNoPushAccepted) {
t.Fatal("Push should fail for the client request validator for storage deals")
}
})
t.Run("ValidatePull fails deal not found", func(t *testing.T) {
proposal, err := uniqueStorageDealProposal()
if err != nil {
t.Fatal("error creating proposal")
}
proposalNd, err := cborutil.AsIpld(&proposal)
if err != nil {
t.Fatal("error serializing proposal")
}
pieceRef, err := cid.Cast(proposal.PieceRef)
if err != nil {
t.Fatal("unable to construct piece cid")
}
if !xerrors.Is(crv.ValidatePull(minerID, &deals.StorageDataTransferVoucher{proposalNd.Cid(), 1}, pieceRef, nil), deals.ErrNoDeal) {
t.Fatal("Pull should fail if there is no deal stored")
}
})
t.Run("ValidatePull fails wrong client", func(t *testing.T) {
otherMiner := peer.ID("otherminer")
clientDeal, err := newClientDeal(otherMiner, api.DealAccepted)
if err != nil {
t.Fatal("error creating client deal")
}
if err := state.Begin(clientDeal.ProposalCid, &clientDeal); err != nil {
t.Fatal("deal tracking failed")
}
payloadCid := clientDeal.PayloadCid
if !xerrors.Is(crv.ValidatePull(minerID, &deals.StorageDataTransferVoucher{clientDeal.ProposalCid, 1}, payloadCid, nil), deals.ErrWrongPeer) {
t.Fatal("Pull should fail if miner address is incorrect")
}
})
t.Run("ValidatePull fails wrong piece ref", func(t *testing.T) {
clientDeal, err := newClientDeal(minerID, api.DealAccepted)
if err != nil {
t.Fatal("error creating client deal")
}
if err := state.Begin(clientDeal.ProposalCid, &clientDeal); err != nil {
t.Fatal("deal tracking failed")
}
if !xerrors.Is(crv.ValidatePull(minerID, &deals.StorageDataTransferVoucher{clientDeal.ProposalCid, 1}, blockGenerator.Next().Cid(), nil), deals.ErrWrongPiece) {
t.Fatal("Pull should fail if piece ref is incorrect")
}
})
t.Run("ValidatePull fails wrong deal state", func(t *testing.T) {
clientDeal, err := newClientDeal(minerID, api.DealComplete)
if err != nil {
t.Fatal("error creating client deal")
}
if err := state.Begin(clientDeal.ProposalCid, &clientDeal); err != nil {
t.Fatal("deal tracking failed")
}
payloadCid := clientDeal.PayloadCid
if !xerrors.Is(crv.ValidatePull(minerID, &deals.StorageDataTransferVoucher{clientDeal.ProposalCid, 1}, payloadCid, nil), deals.ErrInacceptableDealState) {
t.Fatal("Pull should fail if deal is in a state that cannot be data transferred")
}
})
t.Run("ValidatePull succeeds", func(t *testing.T) {
clientDeal, err := newClientDeal(minerID, api.DealAccepted)
if err != nil {
t.Fatal("error creating client deal")
}
if err := state.Begin(clientDeal.ProposalCid, &clientDeal); err != nil {
t.Fatal("deal tracking failed")
}
payloadCid := clientDeal.PayloadCid
if crv.ValidatePull(minerID, &deals.StorageDataTransferVoucher{clientDeal.ProposalCid, 1}, payloadCid, nil) != nil {
t.Fatal("Pull should should succeed when all parameters are correct")
}
})
}
func TestProviderRequestValidation(t *testing.T) {
ds := dss.MutexWrap(datastore.NewMapDatastore())
state := statestore.New(namespace.Wrap(ds, datastore.NewKey("/deals/client")))
mrv := deals.NewProviderRequestValidator(state)
clientID := peer.ID("fakepeerid")
block := blockGenerator.Next()
t.Run("ValidatePull fails", func(t *testing.T) {
if !xerrors.Is(mrv.ValidatePull(clientID, wrongDTType{}, block.Cid(), nil), deals.ErrNoPullAccepted) {
t.Fatal("Pull should fail for the provider request validator for storage deals")
}
})
t.Run("ValidatePush fails deal not found", func(t *testing.T) {
proposal, err := uniqueStorageDealProposal()
if err != nil {
t.Fatal("error creating proposal")
}
proposalNd, err := cborutil.AsIpld(&proposal)
if err != nil {
t.Fatal("error serializing proposal")
}
pieceRef, err := cid.Cast(proposal.PieceRef)
if err != nil {
t.Fatal("unable to construct piece cid")
}
if !xerrors.Is(mrv.ValidatePush(clientID, &deals.StorageDataTransferVoucher{proposalNd.Cid(), 1}, pieceRef, nil), deals.ErrNoDeal) {
t.Fatal("Push should fail if there is no deal stored")
}
})
t.Run("ValidatePush fails wrong miner", func(t *testing.T) {
otherClient := peer.ID("otherclient")
minerDeal, err := newMinerDeal(otherClient, api.DealAccepted)
if err != nil {
t.Fatal("error creating client deal")
}
if err := state.Begin(minerDeal.ProposalCid, &minerDeal); err != nil {
t.Fatal("deal tracking failed")
}
ref := minerDeal.Ref
if !xerrors.Is(mrv.ValidatePush(clientID, &deals.StorageDataTransferVoucher{minerDeal.ProposalCid, 1}, ref, nil), deals.ErrWrongPeer) {
t.Fatal("Push should fail if miner address is incorrect")
}
})
t.Run("ValidatePush fails wrong piece ref", func(t *testing.T) {
minerDeal, err := newMinerDeal(clientID, api.DealAccepted)
if err != nil {
t.Fatal("error creating client deal")
}
if err := state.Begin(minerDeal.ProposalCid, &minerDeal); err != nil {
t.Fatal("deal tracking failed")
}
if !xerrors.Is(mrv.ValidatePush(clientID, &deals.StorageDataTransferVoucher{minerDeal.ProposalCid, 1}, blockGenerator.Next().Cid(), nil), deals.ErrWrongPiece) {
t.Fatal("Push should fail if piece ref is incorrect")
}
})
t.Run("ValidatePush fails wrong deal state", func(t *testing.T) {
minerDeal, err := newMinerDeal(clientID, api.DealComplete)
if err != nil {
t.Fatal("error creating client deal")
}
if err := state.Begin(minerDeal.ProposalCid, &minerDeal); err != nil {
t.Fatal("deal tracking failed")
}
ref := minerDeal.Ref
if !xerrors.Is(mrv.ValidatePush(clientID, &deals.StorageDataTransferVoucher{minerDeal.ProposalCid, 1}, ref, nil), deals.ErrInacceptableDealState) {
t.Fatal("Push should fail if deal is in a state that cannot be data transferred")
}
})
t.Run("ValidatePush succeeds", func(t *testing.T) {
minerDeal, err := newMinerDeal(clientID, api.DealAccepted)
if err != nil {
t.Fatal("error creating client deal")
}
if err := state.Begin(minerDeal.ProposalCid, &minerDeal); err != nil {
t.Fatal("deal tracking failed")
}
ref := minerDeal.Ref
if mrv.ValidatePush(clientID, &deals.StorageDataTransferVoucher{minerDeal.ProposalCid, 1}, ref, nil) != nil {
t.Fatal("Push should should succeed when all parameters are correct")
}
})
}
-112
View File
@@ -1,112 +0,0 @@
package deals
import (
"bytes"
"errors"
"github.com/ipfs/go-cid"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-cbor-util"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
)
var (
// ErrWrongVoucherType means the voucher was not the correct type can validate against
ErrWrongVoucherType = errors.New("cannot validate voucher type.")
// ErrNoPushAccepted just means clients do not accept pushes for storage deals
ErrNoPushAccepted = errors.New("client should not receive data for a storage deal.")
// ErrNoPullAccepted just means providers do not accept pulls for storage deals
ErrNoPullAccepted = errors.New("provider should not send data for a storage deal.")
// ErrNoDeal means no active deal was found for this vouchers proposal cid
ErrNoDeal = errors.New("no deal found for this proposal.")
// ErrWrongPeer means that the other peer for this data transfer request does not match
// the other peer for the deal
ErrWrongPeer = errors.New("data Transfer peer id and Deal peer id do not match.")
// ErrWrongPiece means that the pieceref for this data transfer request does not match
// the one specified in the deal
ErrWrongPiece = errors.New("base CID for deal does not match CID for piece.")
// ErrInacceptableDealState means the deal for this transfer is not in a deal state
// where transfer can be performed
ErrInacceptableDealState = errors.New("deal is not a in a state where deals are accepted.")
// DataTransferStates are the states in which it would make sense to actually start a data transfer
DataTransferStates = []api.DealState{api.DealAccepted, api.DealUnknown}
)
type Proposal struct {
DealProposal *actors.StorageDealProposal
Piece cid.Cid // Used for retrieving from the client
}
type Response struct {
State api.DealState
// DealProposalRejected
Message string
Proposal cid.Cid
// DealAccepted
PublishMessage *cid.Cid
}
// TODO: Do we actually need this to be signed?
type SignedResponse struct {
Response Response
Signature *types.Signature
}
func (r *SignedResponse) Verify(addr address.Address) error {
b, err := cborutil.Dump(&r.Response)
if err != nil {
return err
}
return r.Signature.Verify(addr, b)
}
type AskRequest struct {
Miner address.Address
}
type AskResponse struct {
Ask *types.SignedStorageAsk
}
// StorageDataTransferVoucher is the voucher type for data transfers
// used by the storage market
type StorageDataTransferVoucher struct {
Proposal cid.Cid
DealID uint64
}
// ToBytes converts the StorageDataTransferVoucher to raw bytes
func (dv *StorageDataTransferVoucher) ToBytes() ([]byte, error) {
var buf bytes.Buffer
err := dv.MarshalCBOR(&buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// FromBytes converts the StorageDataTransferVoucher to raw bytes
func (dv *StorageDataTransferVoucher) FromBytes(raw []byte) error {
r := bytes.NewReader(raw)
return dv.UnmarshalCBOR(r)
}
// Type is the unique string identifier for a StorageDataTransferVoucher
func (dv *StorageDataTransferVoucher) Type() string {
return "StorageDataTransferVoucher"
}