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 }