github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/pkg/publisher/filecoin_lotus/api/api.go (about)

     1  //nolint:gochecknoinits,stylecheck // Most of the code in this package is copied with hope to use the upstream version
     2  package api
     3  
     4  import (
     5  	"context"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"os"
    10  	"path/filepath"
    11  	"time"
    12  
    13  	"github.com/filecoin-project/bacalhau/pkg/model"
    14  	"github.com/filecoin-project/bacalhau/pkg/publisher/filecoin_lotus/api/retrievalmarket"
    15  	"github.com/filecoin-project/bacalhau/pkg/publisher/filecoin_lotus/api/storagemarket"
    16  	"github.com/filecoin-project/bacalhau/pkg/system"
    17  	"github.com/filecoin-project/bacalhau/pkg/telemetry"
    18  	"github.com/filecoin-project/go-address"
    19  	"github.com/filecoin-project/go-jsonrpc"
    20  	abi2 "github.com/filecoin-project/go-state-types/abi"
    21  	big2 "github.com/filecoin-project/go-state-types/big"
    22  	"github.com/filecoin-project/go-state-types/builtin/v9/miner"
    23  	"github.com/ipfs/go-cid"
    24  	"github.com/libp2p/go-libp2p/core/peer"
    25  	"github.com/multiformats/go-multiaddr"
    26  	"github.com/pelletier/go-toml/v2"
    27  	"github.com/rs/zerolog/log"
    28  	semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
    29  	"go.opentelemetry.io/otel/trace"
    30  )
    31  
    32  func NewClient(ctx context.Context, host string, token string) (Client, error) {
    33  	log.Ctx(ctx).Debug().Str("hostname", host).Msg("Building Lotus client")
    34  
    35  	headers := http.Header{"Authorization": []string{fmt.Sprintf("Bearer %s", token)}}
    36  
    37  	u := url.URL{
    38  		Scheme: "ws",
    39  		Host:   host,
    40  		Path:   "/rpc/v1",
    41  	}
    42  
    43  	var client api
    44  	closer, err := jsonrpc.NewMergeClient(ctx, u.String(), "Filecoin", []interface{}{&client.internal}, headers)
    45  	if err != nil {
    46  		return nil, fmt.Errorf("unable to create client to %s: %w", host, err)
    47  	}
    48  	client.close = closer
    49  	client.hostname = host
    50  
    51  	return &client, nil
    52  }
    53  
    54  func NewClientFromConfigDir(ctx context.Context, dir string) (Client, error) {
    55  	tokenFile := filepath.Join(dir, "token")
    56  	token, err := os.ReadFile(tokenFile)
    57  	if err != nil {
    58  		return nil, fmt.Errorf("unable to open token file %s: %w", tokenFile, err)
    59  	}
    60  
    61  	configFile := filepath.Join(dir, "config.toml")
    62  	hostname, err := fetchHostnameFromConfig(configFile)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	client, err := NewClient(ctx, hostname, string(token))
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	return client, nil
    73  }
    74  
    75  func fetchHostnameFromConfig(file string) (string, error) {
    76  	unparsedConfig, err := os.ReadFile(file)
    77  	if err != nil {
    78  		return "", fmt.Errorf("unable to open config file %s: %w", file, err)
    79  	}
    80  	var config struct {
    81  		API struct {
    82  			ListenAddress string
    83  		}
    84  	}
    85  	if err := toml.Unmarshal(unparsedConfig, &config); err != nil { //nolint:govet
    86  		return "", fmt.Errorf("unable to parse config file %s: %w", file, err)
    87  	}
    88  
    89  	addr, err := multiaddr.NewMultiaddr(config.API.ListenAddress)
    90  	if err != nil {
    91  		return "", fmt.Errorf("unable to parse ListenAddress in config file %s: %w", file, err)
    92  	}
    93  
    94  	var host, port string
    95  	multiaddr.SplitFunc(addr, func(component multiaddr.Component) bool {
    96  		switch component.Protocol().Code {
    97  		case multiaddr.P_IP4:
    98  			h := component.Value()
    99  			if h == "0.0.0.0" {
   100  				h = "localhost"
   101  			}
   102  			host = h
   103  		case multiaddr.P_TCP:
   104  			port = component.Value()
   105  		}
   106  
   107  		return host != "" && port != ""
   108  	})
   109  
   110  	if host == "" {
   111  		return "", fmt.Errorf("unable to parse host from ListenAddress in config file %s", file)
   112  	}
   113  	if port == "" {
   114  		return "", fmt.Errorf("unable to parse port from ListenAddress in config file %s", file)
   115  	}
   116  
   117  	return fmt.Sprintf("%s:%s", host, port), nil
   118  }
   119  
   120  // Importing the Lotus API causes dependency conflicts between this project and Lotus
   121  // So 're-implement' the API here to avoid the conflicts
   122  // https://github.com/filecoin-project/lotus/blob/master/api/api_full.go
   123  
   124  type Client interface {
   125  	ClientDealPieceCID(context.Context, cid.Cid) (DataCIDSize, error)
   126  	ClientExport(context.Context, ExportRef, FileRef) error
   127  	ClientGetDealUpdates(ctx context.Context) (<-chan DealInfo, error)
   128  	ClientListImports(context.Context) ([]Import, error)
   129  	ClientImport(context.Context, FileRef) (*ImportRes, error)
   130  	ClientQueryAsk(context.Context, peer.ID, address.Address) (*StorageAsk, error)
   131  	ClientStartDeal(context.Context, *StartDealParams) (*cid.Cid, error)
   132  	StateGetNetworkParams(context.Context) (*NetworkParams, error)
   133  	StateListMiners(context.Context, TipSetKey) ([]address.Address, error)
   134  	StateMinerInfo(context.Context, address.Address, TipSetKey) (MinerInfo, error)
   135  	StateMinerPower(context.Context, address.Address, TipSetKey) (*MinerPower, error)
   136  	Version(context.Context) (APIVersion, error)
   137  	WalletDefaultAddress(context.Context) (address.Address, error)
   138  
   139  	Close() error
   140  }
   141  
   142  var _ Client = &api{}
   143  
   144  type api struct {
   145  	internal struct {
   146  		ClientDealPieceCID    func(context.Context, cid.Cid) (DataCIDSize, error)
   147  		ClientExport          func(context.Context, ExportRef, FileRef) error
   148  		ClientGetDealUpdates  func(ctx context.Context) (<-chan DealInfo, error)
   149  		ClientListImports     func(context.Context) ([]Import, error)
   150  		ClientImport          func(context.Context, FileRef) (*ImportRes, error)
   151  		ClientQueryAsk        func(context.Context, peer.ID, address.Address) (*StorageAsk, error)
   152  		ClientStartDeal       func(context.Context, *StartDealParams) (*cid.Cid, error)
   153  		StateGetNetworkParams func(context.Context) (*NetworkParams, error)
   154  		StateListMiners       func(context.Context, TipSetKey) ([]address.Address, error)
   155  		StateMinerInfo        func(context.Context, address.Address, TipSetKey) (MinerInfo, error)
   156  		StateMinerPower       func(context.Context, address.Address, TipSetKey) (*MinerPower, error)
   157  		Version               func(context.Context) (APIVersion, error)
   158  		WalletDefaultAddress  func(context.Context) (address.Address, error)
   159  	}
   160  	close func()
   161  
   162  	hostname string
   163  }
   164  
   165  func (a *api) ClientDealPieceCID(ctx context.Context, root cid.Cid) (DataCIDSize, error) {
   166  	ctx, span := a.span(ctx, "ClientDealPieceCID")
   167  	defer span.End()
   168  	return telemetry.RecordErrorOnSpanTwo[DataCIDSize](span)(a.internal.ClientDealPieceCID(ctx, root))
   169  }
   170  
   171  func (a *api) ClientExport(ctx context.Context, exportRef ExportRef, fileRef FileRef) error {
   172  	ctx, span := a.span(ctx, "ClientExport")
   173  	defer span.End()
   174  	return telemetry.RecordErrorOnSpan(span)(a.internal.ClientExport(ctx, exportRef, fileRef))
   175  }
   176  
   177  func (a *api) ClientGetDealUpdates(ctx context.Context) (<-chan DealInfo, error) {
   178  	ctx, span := a.span(ctx, "ClientGetDealUpdates")
   179  	defer span.End()
   180  	return telemetry.RecordErrorOnSpanOneChannel[DealInfo](span)(a.internal.ClientGetDealUpdates(ctx))
   181  }
   182  
   183  func (a *api) ClientListImports(ctx context.Context) ([]Import, error) {
   184  	ctx, span := a.span(ctx, "ClientListImports")
   185  	defer span.End()
   186  	return telemetry.RecordErrorOnSpanTwo[[]Import](span)(a.internal.ClientListImports(ctx))
   187  }
   188  
   189  func (a *api) ClientImport(ctx context.Context, ref FileRef) (*ImportRes, error) {
   190  	ctx, span := a.span(ctx, "ClientImport")
   191  	defer span.End()
   192  	return telemetry.RecordErrorOnSpanTwo[*ImportRes](span)(a.internal.ClientImport(ctx, ref))
   193  }
   194  
   195  func (a *api) ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*StorageAsk, error) {
   196  	ctx, span := a.span(ctx, "ClientQueryAsk")
   197  	defer span.End()
   198  	return telemetry.RecordErrorOnSpanTwo[*StorageAsk](span)(a.internal.ClientQueryAsk(ctx, p, miner))
   199  }
   200  
   201  func (a *api) ClientStartDeal(ctx context.Context, params *StartDealParams) (*cid.Cid, error) {
   202  	ctx, span := a.span(ctx, "ClientStartDeal")
   203  	defer span.End()
   204  	return telemetry.RecordErrorOnSpanTwo[*cid.Cid](span)(a.internal.ClientStartDeal(ctx, params))
   205  }
   206  
   207  func (a *api) StateGetNetworkParams(ctx context.Context) (*NetworkParams, error) {
   208  	ctx, span := a.span(ctx, "StateGetNetworkParams")
   209  	defer span.End()
   210  	return telemetry.RecordErrorOnSpanTwo[*NetworkParams](span)(a.internal.StateGetNetworkParams(ctx))
   211  }
   212  
   213  func (a *api) StateListMiners(ctx context.Context, key TipSetKey) ([]address.Address, error) {
   214  	ctx, span := a.span(ctx, "StateListMiners")
   215  	defer span.End()
   216  	return telemetry.RecordErrorOnSpanTwo[[]address.Address](span)(a.internal.StateListMiners(ctx, key))
   217  }
   218  
   219  func (a *api) StateMinerInfo(ctx context.Context, a2 address.Address, key TipSetKey) (MinerInfo, error) {
   220  	ctx, span := a.span(ctx, "StateMinerInfo")
   221  	defer span.End()
   222  	return telemetry.RecordErrorOnSpanTwo[MinerInfo](span)(a.internal.StateMinerInfo(ctx, a2, key))
   223  }
   224  
   225  func (a *api) StateMinerPower(ctx context.Context, a2 address.Address, key TipSetKey) (*MinerPower, error) {
   226  	ctx, span := a.span(ctx, "StateMinerPower")
   227  	defer span.End()
   228  	return telemetry.RecordErrorOnSpanTwo[*MinerPower](span)(a.internal.StateMinerPower(ctx, a2, key))
   229  }
   230  
   231  func (a *api) Version(ctx context.Context) (APIVersion, error) {
   232  	ctx, span := a.span(ctx, "Version")
   233  	defer span.End()
   234  	return telemetry.RecordErrorOnSpanTwo[APIVersion](span)(a.internal.Version(ctx))
   235  }
   236  
   237  func (a *api) WalletDefaultAddress(ctx context.Context) (address.Address, error) {
   238  	ctx, span := a.span(ctx, "WalletDefaultAddress")
   239  	defer span.End()
   240  
   241  	return telemetry.RecordErrorOnSpanTwo[address.Address](span)(a.internal.WalletDefaultAddress(ctx))
   242  }
   243  
   244  func (a *api) Close() error {
   245  	a.close()
   246  	return nil
   247  }
   248  
   249  func (a *api) span(ctx context.Context, method string) (context.Context, trace.Span) {
   250  	return system.NewSpan(
   251  		ctx,
   252  		system.GetTracer(),
   253  		fmt.Sprintf("pkg/publisher/filecoin_lotus/api.api.%s", method),
   254  		trace.WithAttributes(semconv.HostName(a.hostname), semconv.PeerService("lotus")),
   255  		trace.WithSpanKind(trace.SpanKindClient),
   256  	)
   257  }
   258  
   259  type TipSetKey struct {
   260  	value string
   261  }
   262  
   263  func (k TipSetKey) MarshalJSON() ([]byte, error) {
   264  	return model.JSONMarshalWithMax(k.CIDs())
   265  }
   266  
   267  func (k TipSetKey) CIDs() []cid.Cid {
   268  	cids, err := decodeKey([]byte(k.value))
   269  	if err != nil {
   270  		panic("invalid tipset key: " + err.Error())
   271  	}
   272  	return cids
   273  }
   274  
   275  // The length of a block header CID in bytes.
   276  var blockHeaderCIDLen int
   277  
   278  func init() {
   279  	// hash a large string of zeros so we don't estimate based on inlined CIDs.
   280  	var buf [256]byte
   281  	c, err := abi2.CidBuilder.Sum(buf[:])
   282  	if err != nil {
   283  		panic(err)
   284  	}
   285  	blockHeaderCIDLen = len(c.Bytes())
   286  }
   287  
   288  func decodeKey(encoded []byte) ([]cid.Cid, error) {
   289  	// To avoid reallocation of the underlying array, estimate the number of CIDs to be extracted
   290  	// by dividing the encoded length by the expected CID length.
   291  	estimatedCount := len(encoded) / blockHeaderCIDLen
   292  	cids := make([]cid.Cid, 0, estimatedCount)
   293  	nextIdx := 0
   294  	for nextIdx < len(encoded) {
   295  		nr, c, err := cid.CidFromBytes(encoded[nextIdx:])
   296  		if err != nil {
   297  			return nil, err
   298  		}
   299  		cids = append(cids, c)
   300  		nextIdx += nr
   301  	}
   302  	return cids, nil
   303  }
   304  
   305  type MinerPower struct {
   306  	MinerPower  Claim
   307  	TotalPower  Claim
   308  	HasMinPower bool
   309  }
   310  
   311  type Claim struct {
   312  	// Sum of raw byte power for a miner's sectors.
   313  	RawBytePower abi2.StoragePower
   314  
   315  	// Sum of quality adjusted power for a miner's sectors.
   316  	QualityAdjPower abi2.StoragePower
   317  }
   318  
   319  type FileRef struct {
   320  	Path  string
   321  	IsCAR bool
   322  }
   323  
   324  type ImportRes struct {
   325  	Root     cid.Cid
   326  	ImportID ID
   327  }
   328  
   329  type ID uint64
   330  
   331  type StartDealParams struct {
   332  	Data               *DataRef
   333  	Wallet             address.Address
   334  	Miner              address.Address
   335  	EpochPrice         big2.Int
   336  	MinBlocksDuration  uint64
   337  	ProviderCollateral big2.Int
   338  	DealStartEpoch     ChainEpoch
   339  	FastRetrieval      bool
   340  	VerifiedDeal       bool
   341  }
   342  
   343  type ChainEpoch int64
   344  
   345  type DataRef struct {
   346  	TransferType string
   347  	Root         cid.Cid
   348  
   349  	PieceCid     *cid.Cid               // Optional for non-manual transfer, will be recomputed from the data if not given
   350  	PieceSize    abi2.UnpaddedPieceSize // Optional for non-manual transfer, will be recomputed from the data if not given
   351  	RawBlockSize uint64                 // Optional: used as the denominator when calculating transfer %
   352  }
   353  
   354  type DealInfo struct {
   355  	ProposalCid cid.Cid
   356  	State       storagemarket.StorageDealStatus
   357  	Message     string // more information about deal state, particularly errors
   358  	Provider    address.Address
   359  
   360  	DataRef  *storagemarket.DataRef
   361  	PieceCID cid.Cid
   362  	Size     uint64
   363  
   364  	PricePerEpoch big2.Int
   365  	Duration      uint64
   366  
   367  	DealID abi2.DealID
   368  
   369  	CreationTime time.Time
   370  	Verified     bool
   371  }
   372  
   373  type FIL big2.Int
   374  
   375  type APIVersion struct {
   376  	Version    string
   377  	APIVersion Version
   378  	BlockDelay uint64
   379  }
   380  
   381  type Version uint32
   382  
   383  type DataCIDSize struct {
   384  	PayloadSize int64
   385  	PieceSize   abi2.PaddedPieceSize
   386  	PieceCID    cid.Cid
   387  }
   388  
   389  type StorageAsk struct {
   390  	Response *storagemarket.StorageAsk
   391  
   392  	DealProtocols []string
   393  }
   394  
   395  type MinerInfo struct {
   396  	Owner                      address.Address   // Must be an ID-address.
   397  	Worker                     address.Address   // Must be an ID-address.
   398  	NewWorker                  address.Address   // Must be an ID-address.
   399  	ControlAddresses           []address.Address // Must be an ID-addresses.
   400  	WorkerChangeEpoch          int64
   401  	PeerId                     *peer.ID
   402  	Multiaddrs                 [][]byte
   403  	WindowPoStProofType        int64
   404  	SectorSize                 uint64
   405  	WindowPoStPartitionSectors uint64
   406  	ConsensusFaultElapsed      int64
   407  	Beneficiary                address.Address
   408  	BeneficiaryTerm            *miner.BeneficiaryTerm
   409  	PendingBeneficiaryTerm     *miner.PendingBeneficiaryChange
   410  }
   411  
   412  type NetworkParams struct {
   413  	NetworkName    string
   414  	BlockDelaySecs uint64
   415  }
   416  
   417  type Import struct {
   418  	Key      ID
   419  	Err      string
   420  	Root     *cid.Cid
   421  	Source   string
   422  	FilePath string
   423  	CARPath  string
   424  }
   425  
   426  type Selector string
   427  
   428  type DagSpec struct {
   429  	DataSelector      *Selector
   430  	ExportMerkleProof bool
   431  }
   432  
   433  type ExportRef struct {
   434  	Root         cid.Cid
   435  	DAGs         []DagSpec
   436  	FromLocalCAR string
   437  	DealID       retrievalmarket.DealID
   438  }