diff --git a/.codecov.yml b/.codecov.yml index 1967f6eca..a70061aaa 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,19 +1,57 @@ -comment: off -ignore: - - "**/cbor_gen.go" - - "api/test/**/*" - - "api/test/*" - - "gen/**/*" - - "gen/*" - - "cmd/lotus-shed/*" - - "cmd/tvx/*" - - "cmd/lotus-pcr/*" - - "cmd/tvx/*" - - "cmd/lotus-chainwatch/*" - - "cmd/lotus-health/*" - - "cmd/lotus-fountain/*" - - "cmd/lotus-townhall/*" - - "cmd/lotus-stats/*" - - "cmd/lotus-pcr/*" -github_checks: - annotations: false +coverage: + status: + project: + tools-and-tests: + target: auto + threshold: 0.5% + informational: true + paths: + - "testplans" + - "tools" + - "system" + - "snap" + - "lotuspond" + - "conformance" + - "scripts" + - "gen" + - "build" + markets: + target: auto + threshold: 0.5% + informational: false + paths: + - "markets" + - "paychmgr" + miner: + target: auto + threshold: 0.5% + informational: false + paths: + - "miner" + - "storage" + chain: + target: auto + threshold: 0.5% + informational: false + paths: + - "chain" + node: + target: auto + threshold: 0.5% + informational: false + paths: + - "node" + - "blockstore" + - "metrics" + - "lib" + - "genesis" + - "gateway" + - "api" + - "journal" + cli: + target: auto + threshold: 0.5% + informational: true + paths: + - "cli" + - "cmd" \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7876715e2..9c76b54dd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -21,35 +21,45 @@ body: required: true - label: I did not make any code changes to lotus. required: false -- type: dropdown - id: component-and-area - validations: - required: true +- type: checkboxes attributes: label: Lotus component description: Please select the lotus component you are filing a bug for options: - - lotus daemon - chain sync - - lotus miner - mining and block production - - lotus miner/worker - sealing - - lotus miner - proving(WindowPoSt) - - lotus miner/market - storage deal - - lotus miner/market - retrieval deal - - lotus client - - lotus JSON-RPC API - - lotus message management (mpool) - - Other + - label: lotus daemon - chain sync + required: false + - label: lotus miner - mining and block production + required: false + - label: lotus miner/worker - sealing + required: false + - label: lotus miner - proving(WindowPoSt) + required: false + - label: lotus miner/market - storage deal + required: false + - label: lotus miner/market - retrieval deal + required: false + - label: lotus miner/market - data transfer + required: false + - label: lotus client + required: false + - label: lotus JSON-RPC API + required: false + - label: lotus message management (mpool) + required: false + - label: Other + required: false - type: textarea id: version attributes: label: Lotus Version + render: text description: Enter the output of `lotus version` and `lotus-miner version` if applicable. placeholder: | e.g. Daemon:1.11.0-rc2+debug+git.0519cd371.dirty+api1.3.0 Local: lotus version 1.11.0-rc2+debug+git.0519cd371.dirty validations: - reuiqred: true + required: true - type: textarea id: Description attributes: @@ -62,19 +72,18 @@ body: * For sealing issues, include the output of `lotus-miner sectors status --log ` for the failed sector(s). * For proving issues, include the output of `lotus-miner proving` info. * For deal making issues, include the output of `lotus client list-deals -v` and/or `lotus-miner storage-deals|retrieval-deals|data-transfers list [-v]` commands for the deal(s) in question. - render: bash validations: required: true - type: textarea id: extraInfo attributes: label: Logging Information + render: text description: | Please provide debug logs of the problem, remember you can get set log level control for: * lotus: use `lotus log list` to get all log systems available and set level by `lotus log set-level`. An example can be found [here](https://docs.filecoin.io/get-started/lotus/configuration-and-advanced-usage/#log-level-control). * lotus-miner:`lotus-miner log list` to get all log systems available and set level by `lotus-miner log set-level If you don't provide detailed logs when you raise the issue it will almost certainly be the first request I make before furthur diagnosing the problem. - render: bash validations: required: true - type: textarea @@ -87,6 +96,5 @@ body: 2. Do '...' 3. See error '...' ... - render: bash validations: - required: false \ No newline at end of file + required: false diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml index 7320fa5c5..da662688d 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yml +++ b/.github/ISSUE_TEMPLATE/enhancement.yml @@ -15,24 +15,33 @@ body: required: true - label: I **have** a specific, actionable, and well motivated improvement to propose. required: true -- type: dropdown - id: component - validations: - required: true +- type: checkboxes attributes: label: Lotus component - description: Please select the lotus component you are propoing improvement for + description: Please select the lotus component you are filing an improvement request for options: - - lotus daemon - chain sync - - lotus miner - mining and block production - - lotus miner/worker - sealing - - lotus miner - proving(WindowPoSt) - - lotus miner/market - storage deal - - lotus miner/market - retrieval deal - - lotus client - - lotus JSON-RPC API - - lotus message management (mpool) - - Other + - label: lotus daemon - chain sync + required: false + - label: lotus miner - mining and block production + required: false + - label: lotus miner/worker - sealing + required: false + - label: lotus miner - proving(WindowPoSt) + required: false + - label: lotus miner/market - storage deal + required: false + - label: lotus miner/market - retrieval deal + required: false + - label: lotus miner/market - data transfer + required: false + - label: lotus client + required: false + - label: lotus JSON-RPC API + required: false + - label: lotus message management (mpool) + required: false + - label: Other + required: false - type: textarea id: request attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 5cb39b0d5..210549095 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -13,24 +13,33 @@ body: required: true - label: I **have** a specific, actionable, and well motivated feature request to propose. required: true -- type: dropdown - id: component - validations: - required: true +- type: checkboxes attributes: label: Lotus component - description: Please select the lotus component you are requesting a new feature for + description: Please select the lotus component you are filing a new feature request for options: - - lotus daemon - chain sync - - lotus miner - mining and block production - - lotus miner/worker - sealing - - lotus miner - proving(WindowPoSt) - - lotus miner/market - storage deal - - lotus miner/market - retrieval deal - - lotus client - - lotus JSON-RPC API - - lotus message management (mpool) - - Other + - label: lotus daemon - chain sync + required: false + - label: lotus miner - mining and block production + required: false + - label: lotus miner/worker - sealing + required: false + - label: lotus miner - proving(WindowPoSt) + required: false + - label: lotus miner/market - storage deal + required: false + - label: lotus miner/market - retrieval deal + required: false + - label: lotus miner/market - data transfer + required: false + - label: lotus client + required: false + - label: lotus JSON-RPC API + required: false + - label: lotus message management (mpool) + required: false + - label: Other + required: false - type: textarea id: request attributes: diff --git a/.github/ISSUE_TEMPLATE/m1_bug_report_deal.yml b/.github/ISSUE_TEMPLATE/m1_bug_report_deal.yml deleted file mode 100644 index 4402e97da..000000000 --- a/.github/ISSUE_TEMPLATE/m1_bug_report_deal.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: "M1 Bug Report For Deal Making" -description: "File a bug report around deal making for the M1 releases" -labels: [need/triage, kind/bug, M1-release] -body: -- type: checkboxes - id: checklist - attributes: - label: Checklist - description: Please check off the following boxes before continuing to file a bug report! - options: - - label: This is **not** a question or a support request. If you have any lotus related questions, please ask in the [lotus forum](https://github.com/filecoin-project/lotus/discussions). - required: true - - label: I **am** reporting a bug w.r.t one of the [M1 tags](https://github.com/filecoin-project/lotus/discussions/6852#discussioncomment-1043951). If not, choose another issue option [here](https://github.com/filecoin-project/lotus/issues/new/choose). - required: true - - label: I **am** reporting a bug around deal making. If not, create a [M1 Bug Report For Non Deal Making Issue](https://github.com/filecoin-project/lotus/issues/new?assignees=&labels=need%2Ftriage%2Ckind%2Fbug%2CM1-release&template=m1_bug_report_non_deal.yml). - required: true - - label: I have my log level set as instructed [here](https://github.com/filecoin-project/lotus/discussions/6852#discussioncomment-1043678) and have logs available for troubleshooting. - required: true - - label: The deal is coming from one of the M1 clients(communitcated in the coordination slack channel). - required: true - - label: I **have** searched on the [issue tracker](https://github.com/filecoin-project/lotus/issues) and the [lotus forum](https://github.com/filecoin-project/lotus/discussions), and there is no existing related issue or discussion. - required: true -- type: dropdown - id: lotus-componets - validations: - required: true - attributes: - label: Lotus Component - description: Please select the lotus component you are filing a bug for - options: - - lotus miner market subsystem - storage deal - - lotus miner market subsystem - retrieval deal - - lotus miner - storage deal - - lotus miner - retrieval deal -- type: textarea - id: version - attributes: - label: Lotus Tag and Version - description: Enter the lotus tag, output of `lotus version` and `lotus-miner version`. - validations: - reuiqred: true -- type: textarea - id: Description - attributes: - label: Describe the Bug - description: | - This is where you get to tell us what went wrong, when doing so, please try to provide a clear and concise description of the bug with all related information: - * What you were doding when you experienced the bug? - * Any *error* messages you saw, *where* you saw them, and what you believe may have caused them (if you have any ideas). - * What is the expected behaviour? - render: bash - validations: - required: true -- type: textarea - id: deal-status - attributes: - label: Deal Status - description: What's the status of the deal? - placeholder: | - Please share the output of `lotus-miner storage-deals|retrieval-deals list [-v]` commands for the deal(s) in question. - validations: - required: true -- type: textarea - id: data-transfer-status - attributes: - label: Data Transfer Status - description: What's the status of the data transfer? - placeholder: | - Please share the output of `lotus-miner data-transfers list -v` commands for the deal(s) in question. - validations: - required: true -- type: textarea - id: logging - attributes: - label: Logging Information - description: Please link to the whole of the miner logs on your side of the transaction. You can upload the logs to a [gist](https://gist.github.com). - validations: - required: true -- type: textarea - id: RepoSteps - attributes: - label: Repo Steps (optional) - description: "Steps to reproduce the behavior" - value: | - 1. Run '...' - 2. Do '...' - 3. See error '...' - ... - render: bash - validations: - required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/m1_bug_report_non_deal.yml b/.github/ISSUE_TEMPLATE/m1_bug_report_non_deal.yml deleted file mode 100644 index ede3593e5..000000000 --- a/.github/ISSUE_TEMPLATE/m1_bug_report_non_deal.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: "M1 Bug Report For Non Deal Making Issue" -description: "File a bug report around non deal making issue for the M1 releases" -labels: [need/triage, kind/bug, M1-release] -body: -- type: checkboxes - id: checklist - attributes: - label: Checklist - description: Please check off the following boxes before continuing to file a bug report! - options: - - label: This is **not** a question or a support request. If you have any lotus related questions, please ask in the [lotus forum](https://github.com/filecoin-project/lotus/discussions). - required: true - - label: I **am** reporting a bug w.r.t one of the [M1 tags](https://github.com/filecoin-project/lotus/discussions/6852#discussioncomment-1043951). If not, choose another issue option [here](https://github.com/filecoin-project/lotus/issues/new/choose). - required: true - - label: I am **not** reporting a bug around deal making. If yes, create a [M1 Bug Report For Deal Making](https://github.com/filecoin-project/lotus/issues/new?assignees=&labels=need%2Ftriage%2Ckind%2Fbug%2CM1-release&template=m1_bug_report_deal.yml). - required: true - - label: I **have** searched on the [issue tracker](https://github.com/filecoin-project/lotus/issues) and the [lotus forum](https://github.com/filecoin-project/lotus/discussions), and there is no existing related issue or discussion. - required: true -- type: dropdown - id: component-and-area - validations: - required: true - attributes: - label: Lotus component - description: Please select the lotus component you are filing a bug for - options: - - lotus daemon - chain sync **with** splitstore enabled - - lotus daemon - chain sync **without** splitstore enabled - - lotus miner - mining and block production - - lotus miner/worker - sealing - - lotus miner - proving(WindowPoSt) - - lotus client - - lotus JSON-RPC API - - lotus message management (mpool) - - Other -- type: textarea - id: version - attributes: - label: Lotus Tag and Version - description: Enter the lotus tag, output of `lotus version` and `lotus-miner version`. - validations: - reuiqred: true -- type: textarea - id: Description - attributes: - label: Describe the Bug - description: | - This is where you get to tell us what went wrong, when doing so, please try to provide a clear and concise description of the bug with all related information: - * What you were doding when you experienced the bug? - * Any *error* messages you saw, *where* you saw them, and what you believe may have caused them (if you have any ideas). - * What is the expected behaviour? - * For sealing issues, include the output of `lotus-miner sectors status --log ` for the failed sector(s). - * For proving issues, include the output of `lotus-miner proving` info. - render: bash - validations: - required: true -- type: textarea - id: extraInfo - attributes: - label: Logging Information - description: | - Please provide debug logs of the problem, remember you can get set log level control for: - * lotus: use `lotus log list` to get all log systems available and set level by `lotus log set-level`. An example can be found [here](https://docs.filecoin.io/get-started/lotus/configuration-and-advanced-usage/#log-level-control). - * lotus-miner:`lotus-miner log list` to get all log systems available and set level by `lotus-miner log set-level - If you don't provide detailed logs when you raise the issue it will almost certainly be the first request I make before furthur diagnosing the problem. - render: bash - validations: - required: true -- type: textarea - id: RepoSteps - attributes: - label: Repo Steps - description: "Steps to reproduce the behavior" - value: | - 1. Run '...' - 2. Do '...' - 3. See error '...' - ... - render: bash - validations: - required: false \ No newline at end of file diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 16a9feebe..35b97e369 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -29,5 +29,5 @@ jobs: days-before-pr-close: 2 remove-stale-when-updated: true enable-statistics: true - + diff --git a/CHANGELOG.md b/CHANGELOG.md index 457b0e1c0..72584e87a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,154 @@ # Lotus changelog +# v1.11.2 / 2021-09-06 + +lotus v1.11.2 is a feature release that's **highly recommended ALL lotus users to upgrade**, including node operators, +storage providers and clients. + +## Highlights +- 🌟🌟🌟 Introduce Dagstore and CARv2 for deal-making (#6671) ([filecoin-project/lotus#6671](https://github.com/filecoin-project/lotus/pull/6671)) + - **[lotus miner markets' Dagstore](https://docs.filecoin.io/mine/lotus/dagstore/#conceptual-overview)** is a + component of the `markets` subsystem in lotus-miner. It is a sharded store to hold large IPLD graphs efficiently, + packaged as location-transparent attachable CAR files and it replaces the former Badger staging blockstore. It + is designed to provide high efficiency and throughput, and minimize resource utilization during deal-making operations. + The dagstore also leverages the indexing features of [CARv2](https://github.com/ipld/ipld/blob/master/specs/transport/car/carv2/index.md) to enable plan CAR files to act as read and write + blockstores, which are served as the direct medium for data exchanges in markets for both storage and retrieval + deal making without requiring intermediate buffers. + - In the future, lotus will leverage and interact with Dagstore a lot for new features and improvements for deal + making, therefore, it's highly recommended to lotus users to go through [Lotus Miner: About the markets dagstore](https://docs.filecoin.io/mine/lotus/dagstore/#conceptual-overview) thoroughly to learn more about Dagstore's + conceptual overview, terminology, directory structure, configuration and so on. + - **Note**: + - When you first start your lotus-miner or market subsystem with this release, a one-time/first-time **dagstore migration** will be triggered which replaces the former Badger staging blockstore with dagstore. We highly + recommend storage providers to read this [section](https://docs.filecoin.io/mine/lotus/dagstore/#first-time-migration) to learn more about + what the process does, what to expect and how monitor it. + - It is highly recommended to **wait all ongoing data transfer to finish or cancel inbound storage deals that + are still transferring**, using the `lotus-miner data-transfers cancel` command before upgrade your market nodes. Reason being that the new dagstore changes attributes in the internal deal state objects, and the paths to the staging CARs where the deal data was being placed will be lost. + - ‼️Having your dags initialized will become important in the near feature for you to provide a better storage + and retrieval service. We'd suggest you to start [forced bulk initialization] soon if possible as this process + places relatively high IP workload on your storage system and is better to be carried out gradually and over a + longer timeframe. Read how to do properly perform a force bulk initialization [here](https://docs.filecoin.io/mine/lotus/dagstore/#forcing-bulk-initialization). + - ⏮ Rollback Alert(from v1.11.2-rcX to any version lower): If a storages deal is initiated with M1/v1.11.2(-rcX) + release, it needs to get to the `StorageDealAwaitingPrecommit` state before you can do a version rollback or the markets process may panic. + - 💙 **Special thanks to [MinerX fellows for testing and providing valuable feedbacks](https://github.com/filecoin-project/lotus/discussions/6852) for Dagstore in the past month!** +- 🌟🌟 rpcenc: Support reader redirect ([filecoin-project/lotus#6952](https://github.com/filecoin-project/lotus/pull/6952)) + - This allows market processes to send piece bytes directly to workers involved on `AddPiece`. +- Extending sectors: more practical and flexible tools ([filecoin-project/lotus#6097](https://github.com/filecoin-project/lotus/pull/6097)) + - `lotus-miner sectors check-expire` to inspect expiring sectors. + - `lotus-miner sectors renew` for renewing expiring sectors, see the command help menu for customizable option + like `extension`, `new-expiration` and so on. +- ‼️ MpoolReplaceCmd ( lotus mpool replace`) now takes FIL for fee-limit ([filecoin-project/lotus#6927](https://github.com/filecoin-project/lotus/pull/6927)) +- Drop townhall/chainwatch ([filecoin-project/lotus#6912](https://github.com/filecoin-project/lotus/pull/6912)) + - ChainWatch is no longer supported by lotus. +- Configurable CC Sector Expiration ([filecoin-project/lotus#6803](https://github.com/filecoin-project/lotus/pull/6803)) + - Set `CommittedCapacitySectorLifetime` in lotus-miner/config.toml to specify the default expiration for a new CC + sector, value must be between 180-540 days inclusive. + +## New Features +- api/command for encoding actor params ([filecoin-project/lotus#7150](https://github.com/filecoin-project/lotus/pull/7150)) +- shed: Support raw encoding in cid id ([filecoin-project/lotus#7149](https://github.com/filecoin-project/lotus/pull/7149)) +- feat(miner deals): create subdir to miner repo for staged deals ([filecoin-project/lotus#6853](https://github.com/filecoin-project/lotus/pull/6853)) +- Support --actor in miner actor control list ([filecoin-project/lotus#7027](https://github.com/filecoin-project/lotus/pull/7027)) +- Shed: Include network name in genesis-verify ([filecoin-project/lotus#7019](https://github.com/filecoin-project/lotus/pull/7019)) +- feat: add ChainGetTipSetAfterHeight ([filecoin-project/lotus#6990](https://github.com/filecoin-project/lotus/pull/6990)) +- lotus-shed splitstore clear command ([filecoin-project/lotus#6967](https://github.com/filecoin-project/lotus/pull/6967)) + +## Improvements +- improve get api error messages ([filecoin-project/lotus#7088](https://github.com/filecoin-project/lotus/pull/7088)) +- Strict major minor version checking on v0 and v1 apis ([filecoin-project/lotus#7038](https://github.com/filecoin-project/lotus/pull/7038)) +- make lotus-miner net commands hit markets subsystem. ([filecoin-project/lotus#7042](https://github.com/filecoin-project/lotus/pull/7042)) +- Test with latest actors version ([filecoin-project/lotus#6998](https://github.com/filecoin-project/lotus/pull/6998)) +- Reduce splitstore memory usage during chain walks ([filecoin-project/lotus#6949](https://github.com/filecoin-project/lotus/pull/6949)) +- Remove forgotten non-functioning config from the pre-mainnet days ([filecoin-project/lotus#6970](https://github.com/filecoin-project/lotus/pull/6970)) +- add explicit error msg if repo dir does not exist ([filecoin-project/lotus#6909](https://github.com/filecoin-project/lotus/pull/6909)) +- Test/pledge batching msg prop ([filecoin-project/lotus#6537](https://github.com/filecoin-project/lotus/pull/6537)) +- reasonable max value for initial sector expiration ([filecoin-project/lotus#6099](https://github.com/filecoin-project/lotus/pull/6099)) +- support MARKETS_API_INFO env var, and markets-repo, markets-api-url CLI flags. ([filecoin-project/lotus#6936](https://github.com/filecoin-project/lotus/pull/6936)) +- Improve formatting of workers CLI ([filecoin-project/lotus#6942](https://github.com/filecoin-project/lotus/pull/6942)) +- make: set default GOCC earlier ([filecoin-project/lotus#6932](https://github.com/filecoin-project/lotus/pull/6932)) +- Moving GC Followup ([filecoin-project/lotus#6905](https://github.com/filecoin-project/lotus/pull/6905)) +- Log more call context during errors ([filecoin-project/lotus#6918](https://github.com/filecoin-project/lotus/pull/6918)) +- polish(errors): better state tree errors ([filecoin-project/lotus#6923](https://github.com/filecoin-project/lotus/pull/6923)) +- adding an RuntimeSubsystems API to storage miner; fix `lotus-miner info` ([filecoin-project/lotus#6906](https://github.com/filecoin-project/lotus/pull/6906)) +- Reduce entropy in the chain package ([filecoin-project/lotus#6889](https://github.com/filecoin-project/lotus/pull/6889)) +- make: Allow setting Go compiler with GOCC ([filecoin-project/lotus#6911](https://github.com/filecoin-project/lotus/pull/6911)) + +## Bug Fixes +- sealing: Fix RecoverDealIDs loop with changed PieceCID ([filecoin-project/lotus#7117](https://github.com/filecoin-project/lotus/pull/7117)) +- Fix error handling in SectorAddPieceToAny api impl ([filecoin-project/lotus#7135](https://github.com/filecoin-project/lotus/pull/7135)) +- add rice box to required binaries ([filecoin-project/lotus#7125](https://github.com/filecoin-project/lotus/pull/7125)) +- fix(miner): always create miner deal staging directory (#7098) ([filecoin-project/lotus#7098](https://github.com/filecoin-project/lotus/pull/7098)) +- fix build after merging #6097. (#7096) ([filecoin-project/lotus#7096](https://github.com/filecoin-project/lotus/pull/7096)) +- fix: don't check for t_aux when proving ([filecoin-project/lotus#7011](https://github.com/filecoin-project/lotus/pull/7011)) +- fix: vet actors shims ([filecoin-project/lotus#6999](https://github.com/filecoin-project/lotus/pull/6999)) +- fix: more logging in maybeStartBatch error ([filecoin-project/lotus#6996](https://github.com/filecoin-project/lotus/pull/6996)) +- fix flaky TestDealPublisher and re-enable ([filecoin-project/lotus#6991](https://github.com/filecoin-project/lotus/pull/6991)) +- fix skipCount ([filecoin-project/lotus#6940](https://github.com/filecoin-project/lotus/pull/6940)) +- fix bug in MpoolPending message exclusion ([filecoin-project/lotus#6945](https://github.com/filecoin-project/lotus/pull/6945)) +- PreCommitPolicy: Don't try to align expirations on proving period boundaries ([filecoin-project/lotus#7018](https://github.com/filecoin-project/lotus/pull/7018)) +- make: fix version check when using gotip ([filecoin-project/lotus#6916](https://github.com/filecoin-project/lotus/pull/6916)) +- fix ticket check ([filecoin-project/lotus#6882](https://github.com/filecoin-project/lotus/pull/6882)) + +## Dependency Updates +- github.com/filecoin-project/go-data-transfer (v1.7.2 -> v1.7.8): +- github.com/filecoin-project/go-fil-markets (v1.6.2 -> v1.8.1): +- update go-libp2p-pubsub to v0.5.4 ([filecoin-project/lotus#6958](https://github.com/filecoin-project/lotus/pull/6958)) +- integrate the proof patch: tag proofs-v9-revert-deps-hotfix +- Update markets, dt and graphsync ([filecoin-project/lotus#7160](https://github.com/filecoin-project/lotus/pull/7160)) +- Remove replace directive for multihash dep (#7113) ([filecoin-project/lotus#7113](https://github.com/filecoin-project/lotus/pull/7113)) +- upgrade upstream dependencies. ([filecoin-project/lotus#7115](https://github.com/filecoin-project/lotus/pull/7115)) +- Update to latest FFI ([filecoin-project/lotus#7110](https://github.com/filecoin-project/lotus/pull/7110)) +- Update state machine deps for logging ([filecoin-project/lotus#6941](https://github.com/filecoin-project/lotus/pull/6941)) +- Update deps for more logging in data transfer and markets ([filecoin-project/lotus#6937](https://github.com/filecoin-project/lotus/pull/6937)) +- Update to branches with improved logging ([filecoin-project/lotus#6919](https://github.com/filecoin-project/lotus/pull/6919)) +- update go-libp2p-pubsub to v0.5.3 ([filecoin-project/lotus#6907](https://github.com/filecoin-project/lotus/pull/6907)) + +## Others +- Fix nits and see if codecov works now with auto ([filecoin-project/lotus#7151](https://github.com/filecoin-project/lotus/pull/7151)) +- Codecov Projects ([filecoin-project/lotus#7147](https://github.com/filecoin-project/lotus/pull/7147)) +- remove m1 templates and make area selection multi-optionable ([filecoin-project/lotus#7121](https://github.com/filecoin-project/lotus/pull/7121)) +- release -> master ([filecoin-project/lotus#7105](https://github.com/filecoin-project/lotus/pull/7105)) +- Lotus release process - how we make releases ([filecoin-project/lotus#6944](https://github.com/filecoin-project/lotus/pull/6944)) +- codecov: fix mock name ([filecoin-project/lotus#7039](https://github.com/filecoin-project/lotus/pull/7039)) +- codecov: fix regexes ([filecoin-project/lotus#7037](https://github.com/filecoin-project/lotus/pull/7037)) +- chore: disable flaky test ([filecoin-project/lotus#6957](https://github.com/filecoin-project/lotus/pull/6957)) +- set buildtype in nerpa and butterfly ([filecoin-project/lotus#6085](https://github.com/filecoin-project/lotus/pull/6085)) +- release v1.11.1 backport -> master ([filecoin-project/lotus#6929](https://github.com/filecoin-project/lotus/pull/6929)) +- chore: fixup issue templates ([filecoin-project/lotus#6899](https://github.com/filecoin-project/lotus/pull/6899)) +- bump master version to v1.11.2-dev ([filecoin-project/lotus#6903](https://github.com/filecoin-project/lotus/pull/6903)) +- releases -> master for v1.11.0 ([filecoin-project/lotus#6894](https://github.com/filecoin-project/lotus/pull/6894)) + + +Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @magik6k | 23 | +5040/-8389 | 114 | +| @aarshkshah1992 | 11 | +4859/-1078 | 101 | +| @raulk | 5 | +4170/-1662 | 104 | +| @vyzo | 30 | +1092/-702 | 49 | +| @nonsense | 6 | +630/-472 | 19 | +| @ZenGround0 | 31 | +556/-274 | 74 | +| @He Weidong | 16 | +680/-128 | 16 | +| @raulk | 16 | +444/-277 | 49 | +| @Stebalien | 11 | +403/-259 | 48 | +| @jennijuju| 17 | +276/-281 | 42 | +| @dirkmc | 5 | +204/-138 | 20 | +| @placer14 | 7 | +178/-77 | 17 | +| @BlocksOnAChain | 1 | +138/-0 | 1 | +| @Frrist | 1 | +63/-56 | 2 | +| @arajasek | 7 | +74/-42 | 13 | +| @frrist | 2 | +67/-6 | 6 | +| @hannahhoward | 2 | +13/-11 | 3 | +| @coryschwartz | 1 | +16/-6 | 3 | +| @whyrusleeping | 1 | +7/-7 | 1 | +| @hunjixin | 1 | +8/-6 | 1 | +| @aarshkshah1992 | 1 | +6/-6 | 2 | +| @dirkmc | 2 | +8/-0 | 2 | +| @mx | 2 | +6/-1 | 2 | +| @travisperson | 1 | +3/-2 | 1 | +| @jennijuju | 2 | +2/-2 | 2 | +| @ribasushi | 1 | +1/-2 | 2 | + # 1.11.1 / 2021-08-16 > Note: for discussion about this release, please comment [here](https://github.com/filecoin-project/lotus/discussions/6904) @@ -175,7 +324,6 @@ This is a **highly recommended** but optional Lotus v1.11.1 release that introd | turuslan | 1 | +1/-1 | 1 | | Kirk Baird | 1 | +0/-0 | 1 | - # 1.11.0 / 2021-07-22 This is a **highly recommended** release of Lotus that have many bug fixes, improvements and new features. @@ -429,13 +577,6 @@ Contributors | @zhoutian527 | 1 | +2/-2 | 1 | | @ribasushi| 1 | +1/-1 | 1 | - -<<<<<<< HEAD -||||||| merged common ancestors ->>>>>>>>> Temporary merge branch 2 -======= ->>>>>>> releases ->>>>>>> releases # 1.10.0 / 2021-06-23 This is a mandatory release of Lotus that introduces Filecoin network v13, codenamed the HyperDrive upgrade. The diff --git a/LOTUS_RELEASE_FLOW.md b/LOTUS_RELEASE_FLOW.md new file mode 100644 index 000000000..4a327125a --- /dev/null +++ b/LOTUS_RELEASE_FLOW.md @@ -0,0 +1,142 @@ + + + +- [`lotus` Release Flow](#lotus-release-flow) + - [Purpose](#purpose) + - [High-level Summary](#high-level-summary) + - [Motivation and Requirements](#motivation-and-requirements) + - [Adopted Conventions](#adopted-conventions) + - [Major Releases](#major-releases) + - [Mandatory Releases](#mandatory-releases) + - [Feature Releases](#feature-releases) + - [Examples Scenarios](#examples-scenarios) + - [Release Cycle](#release-cycle) + - [Patch Releases](#patch-releases) + - [Performing a Release](#performing-a-release) + - [Security Fix Policy](#security-fix-policy) + - [FAQ](#faq) + - [Why aren't Go major versions used more?](#why-arent-go-major-versions-used-more) + - [Related Items](#related-items) + + + +# `lotus` Release Flow + +## Purpose + +This document aims to describe how the Lotus team plans to ship releases of the Lotus implementation of Filecoin network. Interested parties can expect new releases to come out as described in this document. + +## High-level Summary + +- **Major releases** (1.0.0, 2.0.0, etc.) are reserved for significant changes to the Filecoin protocol that transform the network and its usecases. Such changes could include the addition of new Virtual Machines to the protocol, major upgrades to the Proofs of Replication used by Filecoin, etc. +- Even minor releases (1.2.0, 1.4.0, etc.) of the Lotus software correspond to **mandatory releases** as they ship Filecoin network upgrades. Users **must** upgrade to these releases before a certain time in order to keep in sync with the Filecoin network. We aim to ensure there is at least one week before the upgrade deadline. +- Patch versions of even minor releases (1.2.1, 1.4.2, etc.) correspond to **hotfix releases**. Such releases will only be shipped when a critical fix is needed to be applied on top of a mandatory release. +- Odd minor releases (1.3.0, 1.5.0, etc.), as well as patch releases in these series (1.3.1, 1.5.2, etc.) correspond to **feature releases** with new development and bugfixes. These releases are not mandatory, but still highly recommended, **as they may contain critical security fixes**. +- We aim to ship a new feature release of the Lotus software every 3 weeks, so users can expect a regular cadence of Lotus feature releases. Note that mandatory releases for network upgrades may disrupt this schedule. + +## Motivation and Requirements + +Our primary motivation is for users of the Lotus software (storage providers, storage clients, developers, etc.) to have a clear idea about when they can expect Lotus releases, and what they can expect in a release. + +In order to achieve this, we need the following from our release process and conventions: + +- Lotus version conventions make it immediately obvious whether a new Lotus release is mandatory or not. A release is mandatory if it ships a network upgrade to the Filecoin protocol. +- The ability to ship critical fixes on top of mandatory releases, so as to avoid forcing users to consume larger unrelated changes. +- A regular cadence of feature releases, so that users can know when a new Lotus release will be available for consumption. +- The ability to ship any number of feature releases between two mandatory releases. +- A clear description of the various stages of testing that a Lotus Release Candidate (RC) goes through. +- Lotus Release issues will present a single source of truth for what may be contained in Lotus releases, including security fixes, and how they will be disclosed. + +## Adopted Conventions + +This section describes the conventions we have adopted. Users of Lotus are encouraged to review this in detail so as to be informed when new Lotus releases are shipped. + +### Major Releases + +Bumps to the Lotus major version number (1.0.0, 2.0.0, etc.) are reserved for significant changes to the Filecoin protocol that dramatically transform the network itself. Such changes could include the addition of new Virtual Machines to the protocol, major upgrades to the Proofs of Replication used by Filecoin, etc. These releases are expected to take lots of time to develop and will be rare. See also "Why aren't Go major versions used more?" below. + +### Mandatory Releases + +Even bumps to the Lotus minor version number (1.2.0, 1.4.0, etc.) are reserved for **mandatory releases** that ship Filecoin network upgrades. Users **must** upgrade to these releases before a certain time in order to keep in sync with the Filecoin network. + +Depending on the scope of the upgrade, these releases may take up to several weeks to fully develop and test. We aim to ensure there are at least 2 weeks between the publication of the final Lotus release and the Filecoin network upgrade deadline. + +These releases do not follow a regular cadence, as they are developed in lockstep with the other implementations of the Filecoin protocol. As of August 2021, the developers aim to ship 3-4 Filecoin network upgrades a year, though smaller security-critical upgrades may occur unpredictably. + +Mandatory releases are somewhat sensitive since all Lotus users are forced to upgrade to them at the same time. As a result, they will be shipped on top of the most recent stable release of Lotus, which will generally be the latest Lotus release that has been in production for more than 2 weeks. (Note: given this rule, the basis of a mandatory release could be a mandatory release or a feature release depending on timing). Mandatory releases will not include any new feature development or bugfixes that haven't already baked in production for 2+ weeks, except for the changes needed for the network upgrade itself. Further, any critical fixes that are needed after the network upgrade will be shipped as patch version bumps to the mandatory release (1.2.1, 1.2.2, etc.) This prevents users from being forced to quickly digest unnecessary changes. + +Users should generally aim to always upgrade to a new even minor version release since they either introduce a mandatory network upgrade or a critical fix. + +### Feature Releases + +All releases under an odd minor version number indicate **feature releases**. These could include releases such as 1.3.0, 1.3.1, 1.5.2, etc. + +Feature releases include new development and bug fixes. They are not mandatory, but still highly recommended, **as they may contain critical security fixes**. Note that some of these releases may be very small patch releases that include critical hotfixes. There is no way to distinguish between a bug fix release and a feature release on the "feature" version. Both cases will use the "patch" version number. + +We aim to ship a new feature release of the Lotus software from our development (master) branch every 3 weeks, so users can expect a regular cadence of Lotus feature releases. Note that mandatory releases for network upgrades may disrupt this schedule. For more, see the Release Cycle section (TODO: Link). + +### Examples Scenarios + +- **Scenario 1**: **Lotus 1.12.0 shipped a network upgrade, and no network upgrades are needed for a long while.** + + In this case, the next feature release will be Lotus 1.13.0. In three-week intervals, we will ship Lotus 1.13.1, 1.13.2, and 1.13.3, all containing new features and bug fixes. + + Let us assume that after the release of 1.13.3, a critical issue is discovered and a hotfix quickly developed. This hotfix will then be shipped in **both** 1.12.1 and 1.13.4. Users who have already upgrade to the 1.13 series can simply upgrade to 1.13.4. Users who have chosen to still be on 1.12.0, however, can use 1.12.1 to patch the critical issue without being forced to consume all the changes in the 1.13 series. + +- **Scenario 2**: **Lotus 1.12.0 shipped a network upgrade, but the need for an unexpected network upgrade soon arises** + + In this case, the Lotus 1.13 series will be dropped entirely, including any RCs that may have been undergoing testing. Instead, the network upgrade will be shipped as Lotus 1.14.0, built on top of Lotus 1.12.0. It will thus include no unnecessary changes, only the work needed to support the new network upgrade. + + Any changes that were being worked on in the 1.13.0 series will then get applied on top of Lotus 1.14.0 and get shipped as Lotus 1.15.0. + +## Release Cycle + +A mandatory release process should take about 3-6 weeks, depending on the amount and the overall complexity of new features being introduced to the network protocol. It may also be shorter if there is a network incident that requires an emergency upgrade. A feature release process should take about 2-3 weeks. + +The start time of the mandatory release process is subject to the network upgrade timeline. We will start a new feature release process every 3 weeks on Tuesdays, regardless of when the previous release landed unless it's still ongoing. + +### Patch Releases + +**Mandatory Release** + +If we encounter a serious bug in a mandatory release post a network upgrade, we will create a patch release based on this release. Strictly only the fix to the bug will be included in the patch, and the bug fix will be backported to the master (dev) branch, and any ongoing feature release branch if applicable. + +Patch release process for the mandatory releases will follow a compressed release cycle from hours to days depending on the severity and the impact to the network of the bug. In a patch release: + +1. Automated and internal testing (stage 0 and 1) will be compressed into a few hours. +2. Stage 2-3 will be skipped or shortened case by case. + +Some patch releases, especially ones fixing one or more complex bugs that doesn't require a follow-up mandatory upgrade, may undergo the full release process. + +**Feature Release** + +Patch releases in odd minor releases (1.3.0, 1.5.0, etc.) like 1.3.1, 1.5.2 and etc are corresponding to another **feature releases** with new development and bugfixes. These releases are not mandatory, but still **highly** recommended, **as they may contain critical security fixes**. + +--- + +### Performing a Release + +At the beginning of each release cycle, we will generate our "Release tracking issue", which is populated with the content at [https://github.com/filecoin-project/lotus/blob/master/documentation/misc/RELEASE_ISSUE_TEMPLATE.md](https://github.com/filecoin-project/lotus/blob/master/documentation/misc/RELEASE_ISSUE_TEMPLATE.md) + +This template will be used to track major goals we have, a planned shipping date, and a complete release checklist tied to a specific release. + +### Security Fix Policy + +Any release may contain security fixes. Unless the fix addresses a bug being exploited in the wild, the fix will *not* be called out in the release notes. Please make sure to update ASAP. + +By policy, the team will usually wait until about 3 weeks after the final release to announce any fixed security issues. However, depending on the impact and ease of discovery of the issue, the team may wait more or less time. + + It is important to always update to the latest version ASAP and file issues if you're unable to update for some reason. + +Finally, unless a security issue is actively being exploited or a significant number of users are unable to update to the latest version (e.g., due to a difficult migration, breaking changes, etc.), security fixes will *not* be backported to previous releases. + +## FAQ + +### Why aren't Go major versions used more? + +Golang tightly couples source code with versioning (major versions beyond v1 leak into import paths). This poses logistical difficulties to using major versions here. Concretely, if we were to pick a policy that bumped the major version on every network upgrade, we would disrupt every single downstream library/application that consumed the native Lotus API (e.g., libraries depending on the JSON-RPC client, testground tests). They would need to update their code every single time that we released a network breaking change, even if it brought on zero expectation of breakage for the Golang APIs that they depend on. In this scenario, we are signaling breakage on the wrong API surface! We're signaling breakage on the Go level, when what breaks is the network protocol. + +## Related Items + +1. [Release Issue template](https://github.com/filecoin-project/lotus/blob/master/documentation/misc/RELEASE_ISSUE_TEMPLATE.md) +2. [Lotus Release Flow Discussion](https://github.com/filecoin-project/lotus/discussions/7053): Leave a comment if you have any questions or feedbacks with regard to the lotus release flow. diff --git a/Makefile b/Makefile index 77fd38b9e..5d170c320 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,9 @@ all: build unexport GOFLAGS -GOVERSION:=$(shell go version | cut -d' ' -f 3 | sed 's/^go//' | awk -F. '{printf "%d%03d%03d", $$1, $$2, $$3}') +GOCC?=go + +GOVERSION:=$(shell $(GOCC) version | tr ' ' '\n' | grep go1 | sed 's/^go//' | awk -F. '{printf "%d%03d%03d", $$1, $$2, $$3}') ifeq ($(shell expr $(GOVERSION) \< 1016000), 1) $(warning Your Golang version is go$(shell expr $(GOVERSION) / 1000000).$(shell expr $(GOVERSION) % 1000000 / 1000).$(shell expr $(GOVERSION) % 1000)) $(error Update Golang to version to at least 1.16.0) @@ -85,32 +87,32 @@ interopnet: build-devnets lotus: $(BUILD_DEPS) rm -f lotus - go build $(GOFLAGS) -o lotus ./cmd/lotus + $(GOCC) build $(GOFLAGS) -o lotus ./cmd/lotus .PHONY: lotus BINS+=lotus lotus-miner: $(BUILD_DEPS) rm -f lotus-miner - go build $(GOFLAGS) -o lotus-miner ./cmd/lotus-miner + $(GOCC) build $(GOFLAGS) -o lotus-miner ./cmd/lotus-miner .PHONY: lotus-miner BINS+=lotus-miner lotus-worker: $(BUILD_DEPS) rm -f lotus-worker - go build $(GOFLAGS) -o lotus-worker ./cmd/lotus-seal-worker + $(GOCC) build $(GOFLAGS) -o lotus-worker ./cmd/lotus-seal-worker .PHONY: lotus-worker BINS+=lotus-worker lotus-shed: $(BUILD_DEPS) rm -f lotus-shed - go build $(GOFLAGS) -o lotus-shed ./cmd/lotus-shed + $(GOCC) build $(GOFLAGS) -o lotus-shed ./cmd/lotus-shed .PHONY: lotus-shed BINS+=lotus-shed lotus-gateway: $(BUILD_DEPS) rm -f lotus-gateway - go build $(GOFLAGS) -o lotus-gateway ./cmd/lotus-gateway + $(GOCC) build $(GOFLAGS) -o lotus-gateway ./cmd/lotus-gateway .PHONY: lotus-gateway BINS+=lotus-gateway @@ -138,19 +140,19 @@ install-app: lotus-seed: $(BUILD_DEPS) rm -f lotus-seed - go build $(GOFLAGS) -o lotus-seed ./cmd/lotus-seed + $(GOCC) build $(GOFLAGS) -o lotus-seed ./cmd/lotus-seed .PHONY: lotus-seed BINS+=lotus-seed benchmarks: - go run github.com/whyrusleeping/bencher ./... > bench.json + $(GOCC) run github.com/whyrusleeping/bencher ./... > bench.json @echo Submitting results @curl -X POST 'http://benchmark.kittyhawk.wtf/benchmark' -d '@bench.json' -u "${benchmark_http_cred}" .PHONY: benchmarks lotus-pond: 2k - go build -o lotus-pond ./lotuspond + $(GOCC) build -o lotus-pond ./lotuspond .PHONY: lotus-pond BINS+=lotus-pond @@ -161,85 +163,64 @@ lotus-pond-front: lotus-pond-app: lotus-pond-front lotus-pond .PHONY: lotus-pond-app -lotus-townhall: - rm -f lotus-townhall - go build -o lotus-townhall ./cmd/lotus-townhall -.PHONY: lotus-townhall -BINS+=lotus-townhall - -lotus-townhall-front: - (cd ./cmd/lotus-townhall/townhall && npm i && npm run build) -.PHONY: lotus-townhall-front - -lotus-townhall-app: lotus-touch lotus-townhall-front -.PHONY: lotus-townhall-app - lotus-fountain: rm -f lotus-fountain - go build -o lotus-fountain ./cmd/lotus-fountain + go build $(GOFLAGS) -o lotus-fountain ./cmd/lotus-fountain + go run github.com/GeertJohan/go.rice/rice append --exec lotus-fountain -i ./cmd/lotus-fountain -i ./build .PHONY: lotus-fountain BINS+=lotus-fountain -lotus-chainwatch: - rm -f lotus-chainwatch - go build $(GOFLAGS) -o lotus-chainwatch ./cmd/lotus-chainwatch -.PHONY: lotus-chainwatch -BINS+=lotus-chainwatch - lotus-bench: rm -f lotus-bench - go build -o lotus-bench ./cmd/lotus-bench + $(GOCC) build -o lotus-bench ./cmd/lotus-bench .PHONY: lotus-bench BINS+=lotus-bench lotus-stats: rm -f lotus-stats - go build $(GOFLAGS) -o lotus-stats ./cmd/lotus-stats + $(GOCC) build $(GOFLAGS) -o lotus-stats ./cmd/lotus-stats .PHONY: lotus-stats BINS+=lotus-stats lotus-pcr: rm -f lotus-pcr - go build $(GOFLAGS) -o lotus-pcr ./cmd/lotus-pcr + $(GOCC) build $(GOFLAGS) -o lotus-pcr ./cmd/lotus-pcr .PHONY: lotus-pcr BINS+=lotus-pcr lotus-health: rm -f lotus-health - go build -o lotus-health ./cmd/lotus-health + $(GOCC) build -o lotus-health ./cmd/lotus-health .PHONY: lotus-health BINS+=lotus-health lotus-wallet: rm -f lotus-wallet - go build -o lotus-wallet ./cmd/lotus-wallet + $(GOCC) build -o lotus-wallet ./cmd/lotus-wallet .PHONY: lotus-wallet BINS+=lotus-wallet lotus-keygen: rm -f lotus-keygen - go build -o lotus-keygen ./cmd/lotus-keygen + $(GOCC) build -o lotus-keygen ./cmd/lotus-keygen .PHONY: lotus-keygen BINS+=lotus-keygen testground: - go build -tags testground -o /dev/null ./cmd/lotus + $(GOCC) build -tags testground -o /dev/null ./cmd/lotus .PHONY: testground BINS+=testground tvx: rm -f tvx - go build -o tvx ./cmd/tvx + $(GOCC) build -o tvx ./cmd/tvx .PHONY: tvx BINS+=tvx -install-chainwatch: lotus-chainwatch - install -C ./lotus-chainwatch /usr/local/bin/lotus-chainwatch - lotus-sim: $(BUILD_DEPS) rm -f lotus-sim - go build $(GOFLAGS) -o lotus-sim ./cmd/lotus-sim + $(GOCC) build $(GOFLAGS) -o lotus-sim ./cmd/lotus-sim .PHONY: lotus-sim BINS+=lotus-sim @@ -261,21 +242,13 @@ install-miner-service: install-miner install-daemon-service @echo @echo "lotus-miner service installed. Don't forget to run 'sudo systemctl start lotus-miner' to start it and 'sudo systemctl enable lotus-miner' for it to be enabled on startup." -install-chainwatch-service: install-chainwatch install-daemon-service - mkdir -p /etc/systemd/system - mkdir -p /var/log/lotus - install -C -m 0644 ./scripts/lotus-chainwatch.service /etc/systemd/system/lotus-chainwatch.service - systemctl daemon-reload - @echo - @echo "chainwatch service installed. Don't forget to run 'sudo systemctl start lotus-chainwatch' to start it and 'sudo systemctl enable lotus-chainwatch' for it to be enabled on startup." - install-main-services: install-miner-service -install-all-services: install-main-services install-chainwatch-service +install-all-services: install-main-services install-services: install-main-services -clean-daemon-service: clean-miner-service clean-chainwatch-service +clean-daemon-service: clean-miner-service -systemctl stop lotus-daemon -systemctl disable lotus-daemon rm -f /etc/systemd/system/lotus-daemon.service @@ -287,12 +260,6 @@ clean-miner-service: rm -f /etc/systemd/system/lotus-miner.service systemctl daemon-reload -clean-chainwatch-service: - -systemctl stop lotus-chainwatch - -systemctl disable lotus-chainwatch - rm -f /etc/systemd/system/lotus-chainwatch.service - systemctl daemon-reload - clean-main-services: clean-daemon-service clean-all-services: clean-main-services @@ -319,25 +286,25 @@ dist-clean: .PHONY: dist-clean type-gen: api-gen - go run ./gen/main.go - go generate -x ./... + $(GOCC) run ./gen/main.go + $(GOCC) generate -x ./... goimports -w api/ method-gen: api-gen - (cd ./lotuspond/front/src/chain && go run ./methodgen.go) + (cd ./lotuspond/front/src/chain && $(GOCC) run ./methodgen.go) actors-gen: - go run ./chain/actors/agen - go fmt ./... + $(GOCC) run ./chain/actors/agen + $(GOCC) fmt ./... api-gen: - go run ./gen/api + $(GOCC) run ./gen/api goimports -w api goimports -w api .PHONY: api-gen cfgdoc-gen: - go run ./node/config/cfgdocgen > ./node/config/doc_gen.go + $(GOCC) run ./node/config/cfgdocgen > ./node/config/doc_gen.go appimage: lotus rm -rf appimage-builder-cache || true @@ -351,9 +318,9 @@ appimage: lotus docsgen: docsgen-md docsgen-openrpc docsgen-md-bin: api-gen actors-gen - go build $(GOFLAGS) -o docgen-md ./api/docgen/cmd + $(GOCC) build $(GOFLAGS) -o docgen-md ./api/docgen/cmd docsgen-openrpc-bin: api-gen actors-gen - go build $(GOFLAGS) -o docgen-openrpc ./api/docgen-openrpc/cmd + $(GOCC) build $(GOFLAGS) -o docgen-openrpc ./api/docgen-openrpc/cmd docsgen-md: docsgen-md-full docsgen-md-storage docsgen-md-worker diff --git a/api/api_full.go b/api/api_full.go index 412e223cd..0649ececf 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -12,14 +12,14 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" datatransfer "github.com/filecoin-project/go-data-transfer" - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-multistore" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" + "github.com/filecoin-project/go-fil-markets/storagemarket" + apitypes "github.com/filecoin-project/lotus/api/types" "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/market" @@ -29,6 +29,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" marketevents "github.com/filecoin-project/lotus/markets/loggers" "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/repo/imports" ) //go:generate go run github.com/golang/mock/mockgen -destination=mocks/mock_full.go -package=mocks . FullNode @@ -113,6 +114,11 @@ type FullNode interface { // will be returned. ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) //perm:read + // ChainGetTipSetAfterHeight looks back for a tipset at the specified epoch. + // If there are no blocks at the specified epoch, the first non-nil tipset at a later epoch + // will be returned. + ChainGetTipSetAfterHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) //perm:read + // ChainReadObj reads ipld nodes referenced by the specified CID from chain // blockstore and returns raw bytes. ChainReadObj(context.Context, cid.Cid) ([]byte, error) //perm:read @@ -331,7 +337,7 @@ type FullNode interface { // ClientImport imports file under the specified path into filestore. ClientImport(ctx context.Context, ref FileRef) (*ImportRes, error) //perm:admin // ClientRemoveImport removes file import - ClientRemoveImport(ctx context.Context, importID multistore.StoreID) error //perm:admin + ClientRemoveImport(ctx context.Context, importID imports.ID) error //perm:admin // ClientStartDeal proposes a deal with a miner. ClientStartDeal(ctx context.Context, params *StartDealParams) (*cid.Cid, error) //perm:admin // ClientStatelessDeal fire-and-forget-proposes an offline deal to a miner without subsequent tracking. @@ -428,6 +434,8 @@ type FullNode interface { StateListMessages(ctx context.Context, match *MessageMatch, tsk types.TipSetKey, toht abi.ChainEpoch) ([]cid.Cid, error) //perm:read // StateDecodeParams attempts to decode the provided params, based on the recipient actor address and method number. StateDecodeParams(ctx context.Context, toAddr address.Address, method abi.MethodNum, params []byte, tsk types.TipSetKey) (interface{}, error) //perm:read + // StateEncodeParams attempts to encode the provided json params to the binary from + StateEncodeParams(ctx context.Context, toActCode cid.Cid, method abi.MethodNum, params json.RawMessage) ([]byte, error) //perm:read // StateNetworkName returns the name of the network the node is synced to StateNetworkName(context.Context) (dtypes.NetworkName, error) //perm:read @@ -723,16 +731,28 @@ type MinerSectors struct { type ImportRes struct { Root cid.Cid - ImportID multistore.StoreID + ImportID imports.ID } type Import struct { - Key multistore.StoreID + Key imports.ID Err string - Root *cid.Cid - Source string + Root *cid.Cid + + // Source is the provenance of the import, e.g. "import", "unknown", else. + // Currently useless but may be used in the future. + Source string + + // FilePath is the path of the original file. It is important that the file + // is retained at this path, because it will be referenced during + // the transfer (when we do the UnixFS chunking, we don't duplicate the + // leaves, but rather point to chunks of the original data through + // positional references). FilePath string + + // CARPath is the path of the CAR file containing the DAG for this import. + CARPath string } type DealInfo struct { @@ -915,7 +935,7 @@ type RetrievalOrder struct { Piece *cid.Cid Size uint64 - LocalStore *multistore.StoreID // if specified, get data from local store + FromLocalCAR string // if specified, get data from a local CARv2 file. // TODO: support offset Total types.BigInt UnsealPrice types.BigInt diff --git a/api/api_gateway.go b/api/api_gateway.go index 6db1c8e45..29cd8ce24 100644 --- a/api/api_gateway.go +++ b/api/api_gateway.go @@ -35,6 +35,7 @@ type Gateway interface { ChainGetMessage(ctx context.Context, mc cid.Cid) (*types.Message, error) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) + ChainGetTipSetAfterHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) ChainNotify(context.Context) (<-chan []*HeadChange, error) ChainReadObj(context.Context, cid.Cid) ([]byte, error) GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *MessageSendSpec, tsk types.TipSetKey) (*types.Message, error) diff --git a/api/api_storage.go b/api/api_storage.go index c39114929..a26080617 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -13,13 +13,14 @@ import ( "github.com/filecoin-project/go-address" datatransfer "github.com/filecoin-project/go-data-transfer" - "github.com/filecoin-project/go-fil-markets/piecestore" - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" "github.com/filecoin-project/specs-storage/storage" + "github.com/filecoin-project/go-fil-markets/piecestore" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/extern/sector-storage/fsutil" "github.com/filecoin-project/lotus/extern/sector-storage/stores" @@ -166,6 +167,48 @@ type StorageMiner interface { MarketPendingDeals(ctx context.Context) (PendingDealInfo, error) //perm:write MarketPublishPendingDeals(ctx context.Context) error //perm:admin + // DagstoreListShards returns information about all shards known to the + // DAG store. Only available on nodes running the markets subsystem. + DagstoreListShards(ctx context.Context) ([]DagstoreShardInfo, error) //perm:read + + // DagstoreInitializeShard initializes an uninitialized shard. + // + // Initialization consists of fetching the shard's data (deal payload) from + // the storage subsystem, generating an index, and persisting the index + // to facilitate later retrievals, and/or to publish to external sources. + // + // This operation is intended to complement the initial migration. The + // migration registers a shard for every unique piece CID, with lazy + // initialization. Thus, shards are not initialized immediately to avoid + // IO activity competing with proving. Instead, shard are initialized + // when first accessed. This method forces the initialization of a shard by + // accessing it and immediately releasing it. This is useful to warm up the + // cache to facilitate subsequent retrievals, and to generate the indexes + // to publish them externally. + // + // This operation fails if the shard is not in ShardStateNew state. + // It blocks until initialization finishes. + DagstoreInitializeShard(ctx context.Context, key string) error //perm:write + + // DagstoreRecoverShard attempts to recover a failed shard. + // + // This operation fails if the shard is not in ShardStateErrored state. + // It blocks until recovery finishes. If recovery failed, it returns the + // error. + DagstoreRecoverShard(ctx context.Context, key string) error //perm:write + + // DagstoreInitializeAll initializes all uninitialized shards in bulk, + // according to the policy passed in the parameters. + // + // It is recommended to set a maximum concurrency to avoid extreme + // IO pressure if the storage subsystem has a large amount of deals. + // + // It returns a stream of events to report progress. + DagstoreInitializeAll(ctx context.Context, params DagstoreInitializeAllParams) (<-chan DagstoreInitializeAllEvent, error) //perm:write + + // DagstoreGC runs garbage collection on the DAG store. + DagstoreGC(ctx context.Context) ([]DagstoreShardResult, error) //perm:admin + // RuntimeSubsystems returns the subsystems that are enabled // in this instance. RuntimeSubsystems(ctx context.Context) (MinerSubsystems, error) //perm:read @@ -336,3 +379,34 @@ type DealSchedule struct { StartEpoch abi.ChainEpoch EndEpoch abi.ChainEpoch } + +// DagstoreShardInfo is the serialized form of dagstore.DagstoreShardInfo that +// we expose through JSON-RPC to avoid clients having to depend on the +// dagstore lib. +type DagstoreShardInfo struct { + Key string + State string + Error string +} + +// DagstoreShardResult enumerates results per shard. +type DagstoreShardResult struct { + Key string + Success bool + Error string +} + +type DagstoreInitializeAllParams struct { + MaxConcurrency int + IncludeSealed bool +} + +// DagstoreInitializeAllEvent represents an initialization event. +type DagstoreInitializeAllEvent struct { + Key string + Event string // "start", "end" + Success bool + Error string + Total int + Current int +} diff --git a/api/api_test.go b/api/api_test.go index 738e1b067..e65d50ca3 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -76,7 +76,7 @@ func TestReturnTypes(t *testing.T) { seen[typ] = struct{}{} if typ.Kind() == reflect.Interface && typ != bareIface && !typ.Implements(jmarsh) { - t.Error("methods can't return interfaces", m.Name) + t.Error("methods can't return interfaces or struct types not implementing json.Marshaller", m.Name) } switch typ.Kind() { diff --git a/api/cbor_gen.go b/api/cbor_gen.go index 4434b45ed..eb4c34f8a 100644 --- a/api/cbor_gen.go +++ b/api/cbor_gen.go @@ -5,6 +5,7 @@ package api import ( "fmt" "io" + "math" "sort" abi "github.com/filecoin-project/go-state-types/abi" @@ -17,6 +18,7 @@ import ( var _ = xerrors.Errorf var _ = cid.Undef +var _ = math.E var _ = sort.Sort func (t *PaymentInfo) MarshalCBOR(w io.Writer) error { diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index f9addc940..4fd246289 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -13,6 +13,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-multistore" "github.com/google/uuid" "github.com/ipfs/go-cid" "github.com/ipfs/go-filestore" @@ -27,7 +28,6 @@ import ( filestore2 "github.com/filecoin-project/go-fil-markets/filestore" "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-jsonrpc/auth" - "github.com/filecoin-project/go-multistore" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" @@ -43,6 +43,7 @@ import ( "github.com/filecoin-project/lotus/extern/sector-storage/storiface" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/repo/imports" ) var ExampleValues = map[reflect.Type]interface{}{ @@ -90,6 +91,7 @@ func init() { addExample(&pid) multistoreIDExample := multistore.StoreID(50) + storeIDExample := imports.ID(50) addExample(bitfield.NewFromSet([]uint64{5})) addExample(abi.RegisteredSealProof_StackedDrg32GiBV1_1) @@ -120,6 +122,8 @@ func init() { addExample(time.Minute) addExample(datatransfer.TransferID(3)) addExample(datatransfer.Ongoing) + addExample(storeIDExample) + addExample(&storeIDExample) addExample(multistoreIDExample) addExample(&multistoreIDExample) addExample(retrievalmarket.ClientEventDealAccepted) @@ -176,7 +180,7 @@ func init() { // miner specific addExample(filestore2.Path(".lotusminer/fstmp123")) - si := multistore.StoreID(12) + si := uint64(12) addExample(&si) addExample(retrievalmarket.DealID(5)) addExample(abi.ActorID(1000)) @@ -271,6 +275,15 @@ func init() { api.SubsystemSectorStorage, api.SubsystemMarkets, }) + addExample(api.DagstoreShardResult{ + Key: "baga6ea4seaqecmtz7iak33dsfshi627abz4i4665dfuzr3qfs4bmad6dx3iigdq", + Error: "", + }) + addExample(api.DagstoreShardInfo{ + Key: "baga6ea4seaqecmtz7iak33dsfshi627abz4i4665dfuzr3qfs4bmad6dx3iigdq", + State: "ShardStateAvailable", + Error: "", + }) } func GetAPIType(name, pkg string) (i interface{}, t reflect.Type, permStruct []reflect.Type) { diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 124532c14..a6e0e9e91 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -6,6 +6,7 @@ package mocks import ( context "context" + json "encoding/json" reflect "reflect" address "github.com/filecoin-project/go-address" @@ -14,7 +15,6 @@ import ( retrievalmarket "github.com/filecoin-project/go-fil-markets/retrievalmarket" storagemarket "github.com/filecoin-project/go-fil-markets/storagemarket" auth "github.com/filecoin-project/go-jsonrpc/auth" - multistore "github.com/filecoin-project/go-multistore" abi "github.com/filecoin-project/go-state-types/abi" big "github.com/filecoin-project/go-state-types/big" crypto "github.com/filecoin-project/go-state-types/crypto" @@ -26,6 +26,7 @@ import ( types "github.com/filecoin-project/lotus/chain/types" marketevents "github.com/filecoin-project/lotus/markets/loggers" dtypes "github.com/filecoin-project/lotus/node/modules/dtypes" + imports "github.com/filecoin-project/lotus/node/repo/imports" miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" paych "github.com/filecoin-project/specs-actors/actors/builtin/paych" gomock "github.com/golang/mock/gomock" @@ -343,6 +344,21 @@ func (mr *MockFullNodeMockRecorder) ChainGetTipSet(arg0, arg1 interface{}) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetTipSet", reflect.TypeOf((*MockFullNode)(nil).ChainGetTipSet), arg0, arg1) } +// ChainGetTipSetAfterHeight mocks base method. +func (m *MockFullNode) ChainGetTipSetAfterHeight(arg0 context.Context, arg1 abi.ChainEpoch, arg2 types.TipSetKey) (*types.TipSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetTipSetAfterHeight", arg0, arg1, arg2) + ret0, _ := ret[0].(*types.TipSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetTipSetAfterHeight indicates an expected call of ChainGetTipSetAfterHeight. +func (mr *MockFullNodeMockRecorder) ChainGetTipSetAfterHeight(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetTipSetAfterHeight", reflect.TypeOf((*MockFullNode)(nil).ChainGetTipSetAfterHeight), arg0, arg1, arg2) +} + // ChainGetTipSetByHeight mocks base method. func (m *MockFullNode) ChainGetTipSetByHeight(arg0 context.Context, arg1 abi.ChainEpoch, arg2 types.TipSetKey) (*types.TipSet, error) { m.ctrl.T.Helper() @@ -760,7 +776,7 @@ func (mr *MockFullNodeMockRecorder) ClientQueryAsk(arg0, arg1, arg2 interface{}) } // ClientRemoveImport mocks base method. -func (m *MockFullNode) ClientRemoveImport(arg0 context.Context, arg1 multistore.StoreID) error { +func (m *MockFullNode) ClientRemoveImport(arg0 context.Context, arg1 imports.ID) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ClientRemoveImport", arg0, arg1) ret0, _ := ret[0].(error) @@ -2229,6 +2245,21 @@ func (mr *MockFullNodeMockRecorder) StateDecodeParams(arg0, arg1, arg2, arg3, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateDecodeParams", reflect.TypeOf((*MockFullNode)(nil).StateDecodeParams), arg0, arg1, arg2, arg3, arg4) } +// StateEncodeParams mocks base method. +func (m *MockFullNode) StateEncodeParams(arg0 context.Context, arg1 cid.Cid, arg2 abi.MethodNum, arg3 json.RawMessage) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateEncodeParams", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateEncodeParams indicates an expected call of StateEncodeParams. +func (mr *MockFullNodeMockRecorder) StateEncodeParams(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateEncodeParams", reflect.TypeOf((*MockFullNode)(nil).StateEncodeParams), arg0, arg1, arg2, arg3) +} + // StateGetActor mocks base method. func (m *MockFullNode) StateGetActor(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (*types.Actor, error) { m.ctrl.T.Helper() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index a4feb7be1..fd521a2b5 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -4,6 +4,7 @@ package api import ( "context" + "encoding/json" "time" "github.com/filecoin-project/go-address" @@ -13,7 +14,6 @@ import ( "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-jsonrpc/auth" - "github.com/filecoin-project/go-multistore" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/dline" @@ -29,6 +29,7 @@ import ( "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" marketevents "github.com/filecoin-project/lotus/markets/loggers" "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/repo/imports" "github.com/filecoin-project/specs-storage/storage" "github.com/google/uuid" "github.com/ipfs/go-cid" @@ -132,6 +133,8 @@ type FullNodeStruct struct { ChainGetTipSet func(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) `perm:"read"` + ChainGetTipSetAfterHeight func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) `perm:"read"` + ChainGetTipSetByHeight func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) `perm:"read"` ChainHasObj func(p0 context.Context, p1 cid.Cid) (bool, error) `perm:"read"` @@ -188,7 +191,7 @@ type FullNodeStruct struct { ClientQueryAsk func(p0 context.Context, p1 peer.ID, p2 address.Address) (*storagemarket.StorageAsk, error) `perm:"read"` - ClientRemoveImport func(p0 context.Context, p1 multistore.StoreID) error `perm:"admin"` + ClientRemoveImport func(p0 context.Context, p1 imports.ID) error `perm:"admin"` ClientRestartDataTransfer func(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error `perm:"write"` @@ -340,6 +343,8 @@ type FullNodeStruct struct { StateDecodeParams func(p0 context.Context, p1 address.Address, p2 abi.MethodNum, p3 []byte, p4 types.TipSetKey) (interface{}, error) `perm:"read"` + StateEncodeParams func(p0 context.Context, p1 cid.Cid, p2 abi.MethodNum, p3 json.RawMessage) ([]byte, error) `perm:"read"` + StateGetActor func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) `perm:"read"` StateListActors func(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) `perm:"read"` @@ -474,6 +479,8 @@ type GatewayStruct struct { ChainGetTipSet func(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) `` + ChainGetTipSetAfterHeight func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) `` + ChainGetTipSetByHeight func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) `` ChainHasObj func(p0 context.Context, p1 cid.Cid) (bool, error) `` @@ -603,6 +610,16 @@ type StorageMinerStruct struct { CreateBackup func(p0 context.Context, p1 string) error `perm:"admin"` + DagstoreGC func(p0 context.Context) ([]DagstoreShardResult, error) `perm:"admin"` + + DagstoreInitializeAll func(p0 context.Context, p1 DagstoreInitializeAllParams) (<-chan DagstoreInitializeAllEvent, error) `perm:"write"` + + DagstoreInitializeShard func(p0 context.Context, p1 string) error `perm:"write"` + + DagstoreListShards func(p0 context.Context) ([]DagstoreShardInfo, error) `perm:"read"` + + DagstoreRecoverShard func(p0 context.Context, p1 string) error `perm:"write"` + DealsConsiderOfflineRetrievalDeals func(p0 context.Context) (bool, error) `perm:"admin"` DealsConsiderOfflineStorageDeals func(p0 context.Context) (bool, error) `perm:"admin"` @@ -1171,6 +1188,17 @@ func (s *FullNodeStub) ChainGetTipSet(p0 context.Context, p1 types.TipSetKey) (* return nil, ErrNotSupported } +func (s *FullNodeStruct) ChainGetTipSetAfterHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + if s.Internal.ChainGetTipSetAfterHeight == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetTipSetAfterHeight(p0, p1, p2) +} + +func (s *FullNodeStub) ChainGetTipSetAfterHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + return nil, ErrNotSupported +} + func (s *FullNodeStruct) ChainGetTipSetByHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { if s.Internal.ChainGetTipSetByHeight == nil { return nil, ErrNotSupported @@ -1479,14 +1507,14 @@ func (s *FullNodeStub) ClientQueryAsk(p0 context.Context, p1 peer.ID, p2 address return nil, ErrNotSupported } -func (s *FullNodeStruct) ClientRemoveImport(p0 context.Context, p1 multistore.StoreID) error { +func (s *FullNodeStruct) ClientRemoveImport(p0 context.Context, p1 imports.ID) error { if s.Internal.ClientRemoveImport == nil { return ErrNotSupported } return s.Internal.ClientRemoveImport(p0, p1) } -func (s *FullNodeStub) ClientRemoveImport(p0 context.Context, p1 multistore.StoreID) error { +func (s *FullNodeStub) ClientRemoveImport(p0 context.Context, p1 imports.ID) error { return ErrNotSupported } @@ -2315,6 +2343,17 @@ func (s *FullNodeStub) StateDecodeParams(p0 context.Context, p1 address.Address, return nil, ErrNotSupported } +func (s *FullNodeStruct) StateEncodeParams(p0 context.Context, p1 cid.Cid, p2 abi.MethodNum, p3 json.RawMessage) ([]byte, error) { + if s.Internal.StateEncodeParams == nil { + return *new([]byte), ErrNotSupported + } + return s.Internal.StateEncodeParams(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateEncodeParams(p0 context.Context, p1 cid.Cid, p2 abi.MethodNum, p3 json.RawMessage) ([]byte, error) { + return *new([]byte), ErrNotSupported +} + func (s *FullNodeStruct) StateGetActor(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) { if s.Internal.StateGetActor == nil { return nil, ErrNotSupported @@ -2997,6 +3036,17 @@ func (s *GatewayStub) ChainGetTipSet(p0 context.Context, p1 types.TipSetKey) (*t return nil, ErrNotSupported } +func (s *GatewayStruct) ChainGetTipSetAfterHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + if s.Internal.ChainGetTipSetAfterHeight == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetTipSetAfterHeight(p0, p1, p2) +} + +func (s *GatewayStub) ChainGetTipSetAfterHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + return nil, ErrNotSupported +} + func (s *GatewayStruct) ChainGetTipSetByHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { if s.Internal.ChainGetTipSetByHeight == nil { return nil, ErrNotSupported @@ -3569,6 +3619,61 @@ func (s *StorageMinerStub) CreateBackup(p0 context.Context, p1 string) error { return ErrNotSupported } +func (s *StorageMinerStruct) DagstoreGC(p0 context.Context) ([]DagstoreShardResult, error) { + if s.Internal.DagstoreGC == nil { + return *new([]DagstoreShardResult), ErrNotSupported + } + return s.Internal.DagstoreGC(p0) +} + +func (s *StorageMinerStub) DagstoreGC(p0 context.Context) ([]DagstoreShardResult, error) { + return *new([]DagstoreShardResult), ErrNotSupported +} + +func (s *StorageMinerStruct) DagstoreInitializeAll(p0 context.Context, p1 DagstoreInitializeAllParams) (<-chan DagstoreInitializeAllEvent, error) { + if s.Internal.DagstoreInitializeAll == nil { + return nil, ErrNotSupported + } + return s.Internal.DagstoreInitializeAll(p0, p1) +} + +func (s *StorageMinerStub) DagstoreInitializeAll(p0 context.Context, p1 DagstoreInitializeAllParams) (<-chan DagstoreInitializeAllEvent, error) { + return nil, ErrNotSupported +} + +func (s *StorageMinerStruct) DagstoreInitializeShard(p0 context.Context, p1 string) error { + if s.Internal.DagstoreInitializeShard == nil { + return ErrNotSupported + } + return s.Internal.DagstoreInitializeShard(p0, p1) +} + +func (s *StorageMinerStub) DagstoreInitializeShard(p0 context.Context, p1 string) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) DagstoreListShards(p0 context.Context) ([]DagstoreShardInfo, error) { + if s.Internal.DagstoreListShards == nil { + return *new([]DagstoreShardInfo), ErrNotSupported + } + return s.Internal.DagstoreListShards(p0) +} + +func (s *StorageMinerStub) DagstoreListShards(p0 context.Context) ([]DagstoreShardInfo, error) { + return *new([]DagstoreShardInfo), ErrNotSupported +} + +func (s *StorageMinerStruct) DagstoreRecoverShard(p0 context.Context, p1 string) error { + if s.Internal.DagstoreRecoverShard == nil { + return ErrNotSupported + } + return s.Internal.DagstoreRecoverShard(p0, p1) +} + +func (s *StorageMinerStub) DagstoreRecoverShard(p0 context.Context, p1 string) error { + return ErrNotSupported +} + func (s *StorageMinerStruct) DealsConsiderOfflineRetrievalDeals(p0 context.Context) (bool, error) { if s.Internal.DealsConsiderOfflineRetrievalDeals == nil { return false, ErrNotSupported diff --git a/api/v0api/full.go b/api/v0api/full.go index b152c6cbb..a22406fe1 100644 --- a/api/v0api/full.go +++ b/api/v0api/full.go @@ -8,7 +8,6 @@ import ( datatransfer "github.com/filecoin-project/go-data-transfer" "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-multistore" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/dline" @@ -22,6 +21,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" marketevents "github.com/filecoin-project/lotus/markets/loggers" "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/repo/imports" ) //go:generate go run github.com/golang/mock/mockgen -destination=v0mocks/mock_full.go -package=v0mocks . FullNode @@ -305,7 +305,7 @@ type FullNode interface { // ClientImport imports file under the specified path into filestore. ClientImport(ctx context.Context, ref api.FileRef) (*api.ImportRes, error) //perm:admin // ClientRemoveImport removes file import - ClientRemoveImport(ctx context.Context, importID multistore.StoreID) error //perm:admin + ClientRemoveImport(ctx context.Context, importID imports.ID) error //perm:admin // ClientStartDeal proposes a deal with a miner. ClientStartDeal(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) //perm:admin // ClientStatelessDeal fire-and-forget-proposes an offline deal to a miner without subsequent tracking. diff --git a/api/v0api/proxy_gen.go b/api/v0api/proxy_gen.go index 21b751ca2..eec8577fc 100644 --- a/api/v0api/proxy_gen.go +++ b/api/v0api/proxy_gen.go @@ -10,7 +10,6 @@ import ( datatransfer "github.com/filecoin-project/go-data-transfer" "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-multistore" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/dline" @@ -22,6 +21,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" marketevents "github.com/filecoin-project/lotus/markets/loggers" "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/repo/imports" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p-core/peer" "golang.org/x/xerrors" @@ -121,7 +121,7 @@ type FullNodeStruct struct { ClientQueryAsk func(p0 context.Context, p1 peer.ID, p2 address.Address) (*storagemarket.StorageAsk, error) `perm:"read"` - ClientRemoveImport func(p0 context.Context, p1 multistore.StoreID) error `perm:"admin"` + ClientRemoveImport func(p0 context.Context, p1 imports.ID) error `perm:"admin"` ClientRestartDataTransfer func(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error `perm:"write"` @@ -939,14 +939,14 @@ func (s *FullNodeStub) ClientQueryAsk(p0 context.Context, p1 peer.ID, p2 address return nil, ErrNotSupported } -func (s *FullNodeStruct) ClientRemoveImport(p0 context.Context, p1 multistore.StoreID) error { +func (s *FullNodeStruct) ClientRemoveImport(p0 context.Context, p1 imports.ID) error { if s.Internal.ClientRemoveImport == nil { return ErrNotSupported } return s.Internal.ClientRemoveImport(p0, p1) } -func (s *FullNodeStub) ClientRemoveImport(p0 context.Context, p1 multistore.StoreID) error { +func (s *FullNodeStub) ClientRemoveImport(p0 context.Context, p1 imports.ID) error { return ErrNotSupported } diff --git a/api/v0api/v0mocks/mock_full.go b/api/v0api/v0mocks/mock_full.go index 6a4ef690e..ae717e1ec 100644 --- a/api/v0api/v0mocks/mock_full.go +++ b/api/v0api/v0mocks/mock_full.go @@ -14,7 +14,6 @@ import ( retrievalmarket "github.com/filecoin-project/go-fil-markets/retrievalmarket" storagemarket "github.com/filecoin-project/go-fil-markets/storagemarket" auth "github.com/filecoin-project/go-jsonrpc/auth" - multistore "github.com/filecoin-project/go-multistore" abi "github.com/filecoin-project/go-state-types/abi" big "github.com/filecoin-project/go-state-types/big" crypto "github.com/filecoin-project/go-state-types/crypto" @@ -26,6 +25,7 @@ import ( types "github.com/filecoin-project/lotus/chain/types" marketevents "github.com/filecoin-project/lotus/markets/loggers" dtypes "github.com/filecoin-project/lotus/node/modules/dtypes" + imports "github.com/filecoin-project/lotus/node/repo/imports" miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" paych "github.com/filecoin-project/specs-actors/actors/builtin/paych" gomock "github.com/golang/mock/gomock" @@ -731,7 +731,7 @@ func (mr *MockFullNodeMockRecorder) ClientQueryAsk(arg0, arg1, arg2 interface{}) } // ClientRemoveImport mocks base method. -func (m *MockFullNode) ClientRemoveImport(arg0 context.Context, arg1 multistore.StoreID) error { +func (m *MockFullNode) ClientRemoveImport(arg0 context.Context, arg1 imports.ID) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ClientRemoveImport", arg0, arg1) ret0, _ := ret[0].(error) diff --git a/blockstore/splitstore/markset.go b/blockstore/splitstore/markset.go index 458ea8beb..218681e13 100644 --- a/blockstore/splitstore/markset.go +++ b/blockstore/splitstore/markset.go @@ -21,15 +21,26 @@ type MarkSet interface { SetConcurrent() } +type MarkSetVisitor interface { + MarkSet + ObjectVisitor +} + type MarkSetEnv interface { + // Create creates a new markset within the environment. + // name is a unique name for this markset, mapped to the filesystem in disk-backed environments + // sizeHint is a hint about the expected size of the markset Create(name string, sizeHint int64) (MarkSet, error) + // CreateVisitor is like Create, but returns a wider interface that supports atomic visits. + // It may not be supported by some markset types (e.g. bloom). + CreateVisitor(name string, sizeHint int64) (MarkSetVisitor, error) + // SupportsVisitor returns true if the marksets created by this environment support the visitor interface. + SupportsVisitor() bool Close() error } func OpenMarkSetEnv(path string, mtype string) (MarkSetEnv, error) { switch mtype { - case "bloom": - return NewBloomMarkSetEnv() case "map": return NewMapMarkSetEnv() case "badger": diff --git a/blockstore/splitstore/markset_badger.go b/blockstore/splitstore/markset_badger.go index ef67db213..ae06a69f8 100644 --- a/blockstore/splitstore/markset_badger.go +++ b/blockstore/splitstore/markset_badger.go @@ -27,12 +27,14 @@ type BadgerMarkSet struct { writing map[int]map[string]struct{} writers int seqno int + version int db *badger.DB path string } var _ MarkSet = (*BadgerMarkSet)(nil) +var _ MarkSetVisitor = (*BadgerMarkSet)(nil) var badgerMarkSetBatchSize = 16384 @@ -46,9 +48,241 @@ func NewBadgerMarkSetEnv(path string) (MarkSetEnv, error) { return &BadgerMarkSetEnv{path: msPath}, nil } -func (e *BadgerMarkSetEnv) Create(name string, sizeHint int64) (MarkSet, error) { +func (e *BadgerMarkSetEnv) create(name string, sizeHint int64) (*BadgerMarkSet, error) { + name += ".tmp" path := filepath.Join(e.path, name) + db, err := openTransientBadgerDB(path) + if err != nil { + return nil, xerrors.Errorf("error creating badger db: %w", err) + } + + ms := &BadgerMarkSet{ + pend: make(map[string]struct{}), + writing: make(map[int]map[string]struct{}), + db: db, + path: path, + } + ms.cond.L = &ms.mx + + return ms, nil +} + +func (e *BadgerMarkSetEnv) Create(name string, sizeHint int64) (MarkSet, error) { + return e.create(name, sizeHint) +} + +func (e *BadgerMarkSetEnv) CreateVisitor(name string, sizeHint int64) (MarkSetVisitor, error) { + return e.create(name, sizeHint) +} + +func (e *BadgerMarkSetEnv) SupportsVisitor() bool { return true } + +func (e *BadgerMarkSetEnv) Close() error { + return os.RemoveAll(e.path) +} + +func (s *BadgerMarkSet) Mark(c cid.Cid) error { + s.mx.Lock() + if s.pend == nil { + s.mx.Unlock() + return errMarkSetClosed + } + + write, seqno := s.put(string(c.Hash())) + s.mx.Unlock() + + if write { + return s.write(seqno) + } + + return nil +} + +func (s *BadgerMarkSet) Has(c cid.Cid) (bool, error) { + s.mx.RLock() + defer s.mx.RUnlock() + + key := c.Hash() + pendKey := string(key) + + has, err := s.tryPending(pendKey) + if has || err != nil { + return has, err + } + + return s.tryDB(key) +} + +func (s *BadgerMarkSet) Visit(c cid.Cid) (bool, error) { + key := c.Hash() + pendKey := string(key) + + s.mx.RLock() + + has, err := s.tryPending(pendKey) + if has || err != nil { + s.mx.RUnlock() + return false, err + } + + has, err = s.tryDB(key) + if has || err != nil { + s.mx.RUnlock() + return false, err + } + + // we need to upgrade the lock to exclusive in order to write; take the version count to see + // if there was another write while we were upgrading + version := s.version + s.mx.RUnlock() + + s.mx.Lock() + // we have to do the check dance again + has, err = s.tryPending(pendKey) + if has || err != nil { + s.mx.Unlock() + return false, err + } + + if version != s.version { + // something was written to the db, we need to check it + has, err = s.tryDB(key) + if has || err != nil { + s.mx.Unlock() + return false, err + } + } + + write, seqno := s.put(pendKey) + s.mx.Unlock() + + if write { + err = s.write(seqno) + } + + return true, err +} + +// reader holds the (r)lock +func (s *BadgerMarkSet) tryPending(key string) (has bool, err error) { + if s.pend == nil { + return false, errMarkSetClosed + } + + if _, ok := s.pend[key]; ok { + return true, nil + } + + for _, wr := range s.writing { + if _, ok := wr[key]; ok { + return true, nil + } + } + + return false, nil +} + +func (s *BadgerMarkSet) tryDB(key []byte) (has bool, err error) { + err = s.db.View(func(txn *badger.Txn) error { + _, err := txn.Get(key) + return err + }) + + switch err { + case nil: + return true, nil + + case badger.ErrKeyNotFound: + return false, nil + + default: + return false, err + } +} + +// writer holds the exclusive lock +func (s *BadgerMarkSet) put(key string) (write bool, seqno int) { + s.pend[key] = struct{}{} + if len(s.pend) < badgerMarkSetBatchSize { + return false, 0 + } + + seqno = s.seqno + s.seqno++ + s.writing[seqno] = s.pend + s.pend = make(map[string]struct{}) + + return true, seqno +} + +func (s *BadgerMarkSet) write(seqno int) (err error) { + s.mx.Lock() + if s.pend == nil { + s.mx.Unlock() + return errMarkSetClosed + } + + pend := s.writing[seqno] + s.writers++ + s.mx.Unlock() + + defer func() { + s.mx.Lock() + defer s.mx.Unlock() + + if err == nil { + delete(s.writing, seqno) + s.version++ + } + + s.writers-- + if s.writers == 0 { + s.cond.Broadcast() + } + }() + + empty := []byte{} // not nil + + batch := s.db.NewWriteBatch() + defer batch.Cancel() + + for k := range pend { + if err = batch.Set([]byte(k), empty); err != nil { + return xerrors.Errorf("error setting batch: %w", err) + } + } + + err = batch.Flush() + if err != nil { + return xerrors.Errorf("error flushing batch to badger markset: %w", err) + } + + return nil +} + +func (s *BadgerMarkSet) Close() error { + s.mx.Lock() + defer s.mx.Unlock() + + if s.pend == nil { + return nil + } + + for s.writers > 0 { + s.cond.Wait() + } + + s.pend = nil + db := s.db + s.db = nil + + return closeTransientBadgerDB(db, s.path) +} + +func (s *BadgerMarkSet) SetConcurrent() {} + +func openTransientBadgerDB(path string) (*badger.DB, error) { // clean up first err := os.RemoveAll(path) if err != nil { @@ -76,140 +310,16 @@ func (e *BadgerMarkSetEnv) Create(name string, sizeHint int64) (MarkSet, error) skip2: log.Desugar().WithOptions(zap.AddCallerSkip(2)).Sugar(), } - db, err := badger.Open(opts) - if err != nil { - return nil, xerrors.Errorf("error creating badger markset: %w", err) - } - - ms := &BadgerMarkSet{ - pend: make(map[string]struct{}), - writing: make(map[int]map[string]struct{}), - db: db, - path: path, - } - ms.cond.L = &ms.mx - - return ms, nil + return badger.Open(opts) } -func (e *BadgerMarkSetEnv) Close() error { - return os.RemoveAll(e.path) -} - -func (s *BadgerMarkSet) Mark(c cid.Cid) error { - s.mx.Lock() - - if s.pend == nil { - s.mx.Unlock() - return errMarkSetClosed - } - - s.pend[string(c.Hash())] = struct{}{} - - if len(s.pend) < badgerMarkSetBatchSize { - s.mx.Unlock() - return nil - } - - pend := s.pend - seqno := s.seqno - s.seqno++ - s.writing[seqno] = pend - s.pend = make(map[string]struct{}) - s.writers++ - s.mx.Unlock() - - defer func() { - s.mx.Lock() - defer s.mx.Unlock() - - delete(s.writing, seqno) - s.writers-- - if s.writers == 0 { - s.cond.Broadcast() - } - }() - - empty := []byte{} // not nil - - batch := s.db.NewWriteBatch() - defer batch.Cancel() - - for k := range pend { - if err := batch.Set([]byte(k), empty); err != nil { - return err - } - } - - err := batch.Flush() - if err != nil { - return xerrors.Errorf("error flushing batch to badger markset: %w", err) - } - - return nil -} - -func (s *BadgerMarkSet) Has(c cid.Cid) (bool, error) { - s.mx.RLock() - defer s.mx.RUnlock() - - if s.pend == nil { - return false, errMarkSetClosed - } - - key := c.Hash() - pendKey := string(key) - _, ok := s.pend[pendKey] - if ok { - return true, nil - } - - for _, wr := range s.writing { - _, ok := wr[pendKey] - if ok { - return true, nil - } - } - - err := s.db.View(func(txn *badger.Txn) error { - _, err := txn.Get(key) - return err - }) - - switch err { - case nil: - return true, nil - - case badger.ErrKeyNotFound: - return false, nil - - default: - return false, xerrors.Errorf("error checking badger markset: %w", err) - } -} - -func (s *BadgerMarkSet) Close() error { - s.mx.Lock() - defer s.mx.Unlock() - - if s.pend == nil { - return nil - } - - for s.writers > 0 { - s.cond.Wait() - } - - s.pend = nil - db := s.db - s.db = nil - +func closeTransientBadgerDB(db *badger.DB, path string) error { err := db.Close() if err != nil { return xerrors.Errorf("error closing badger markset: %w", err) } - err = os.RemoveAll(s.path) + err = os.RemoveAll(path) if err != nil { return xerrors.Errorf("error deleting badger markset: %w", err) } @@ -217,8 +327,6 @@ func (s *BadgerMarkSet) Close() error { return nil } -func (s *BadgerMarkSet) SetConcurrent() {} - // badger logging through go-log type badgerLogger struct { *zap.SugaredLogger diff --git a/blockstore/splitstore/markset_bloom.go b/blockstore/splitstore/markset_bloom.go deleted file mode 100644 index 9261de7c7..000000000 --- a/blockstore/splitstore/markset_bloom.go +++ /dev/null @@ -1,107 +0,0 @@ -package splitstore - -import ( - "crypto/rand" - "crypto/sha256" - "sync" - - "golang.org/x/xerrors" - - bbloom "github.com/ipfs/bbloom" - cid "github.com/ipfs/go-cid" -) - -const ( - BloomFilterMinSize = 10_000_000 - BloomFilterProbability = 0.01 -) - -type BloomMarkSetEnv struct{} - -var _ MarkSetEnv = (*BloomMarkSetEnv)(nil) - -type BloomMarkSet struct { - salt []byte - mx sync.RWMutex - bf *bbloom.Bloom - ts bool -} - -var _ MarkSet = (*BloomMarkSet)(nil) - -func NewBloomMarkSetEnv() (*BloomMarkSetEnv, error) { - return &BloomMarkSetEnv{}, nil -} - -func (e *BloomMarkSetEnv) Create(name string, sizeHint int64) (MarkSet, error) { - size := int64(BloomFilterMinSize) - for size < sizeHint { - size += BloomFilterMinSize - } - - salt := make([]byte, 4) - _, err := rand.Read(salt) - if err != nil { - return nil, xerrors.Errorf("error reading salt: %w", err) - } - - bf, err := bbloom.New(float64(size), BloomFilterProbability) - if err != nil { - return nil, xerrors.Errorf("error creating bloom filter: %w", err) - } - - return &BloomMarkSet{salt: salt, bf: bf}, nil -} - -func (e *BloomMarkSetEnv) Close() error { - return nil -} - -func (s *BloomMarkSet) saltedKey(cid cid.Cid) []byte { - hash := cid.Hash() - key := make([]byte, len(s.salt)+len(hash)) - n := copy(key, s.salt) - copy(key[n:], hash) - rehash := sha256.Sum256(key) - return rehash[:] -} - -func (s *BloomMarkSet) Mark(cid cid.Cid) error { - if s.ts { - s.mx.Lock() - defer s.mx.Unlock() - } - - if s.bf == nil { - return errMarkSetClosed - } - - s.bf.Add(s.saltedKey(cid)) - return nil -} - -func (s *BloomMarkSet) Has(cid cid.Cid) (bool, error) { - if s.ts { - s.mx.RLock() - defer s.mx.RUnlock() - } - - if s.bf == nil { - return false, errMarkSetClosed - } - - return s.bf.Has(s.saltedKey(cid)), nil -} - -func (s *BloomMarkSet) Close() error { - if s.ts { - s.mx.Lock() - defer s.mx.Unlock() - } - s.bf = nil - return nil -} - -func (s *BloomMarkSet) SetConcurrent() { - s.ts = true -} diff --git a/blockstore/splitstore/markset_map.go b/blockstore/splitstore/markset_map.go index 197c82424..07a7ae70d 100644 --- a/blockstore/splitstore/markset_map.go +++ b/blockstore/splitstore/markset_map.go @@ -18,17 +18,28 @@ type MapMarkSet struct { } var _ MarkSet = (*MapMarkSet)(nil) +var _ MarkSetVisitor = (*MapMarkSet)(nil) func NewMapMarkSetEnv() (*MapMarkSetEnv, error) { return &MapMarkSetEnv{}, nil } -func (e *MapMarkSetEnv) Create(name string, sizeHint int64) (MarkSet, error) { +func (e *MapMarkSetEnv) create(name string, sizeHint int64) (*MapMarkSet, error) { return &MapMarkSet{ set: make(map[string]struct{}, sizeHint), }, nil } +func (e *MapMarkSetEnv) Create(name string, sizeHint int64) (MarkSet, error) { + return e.create(name, sizeHint) +} + +func (e *MapMarkSetEnv) CreateVisitor(name string, sizeHint int64) (MarkSetVisitor, error) { + return e.create(name, sizeHint) +} + +func (e *MapMarkSetEnv) SupportsVisitor() bool { return true } + func (e *MapMarkSetEnv) Close() error { return nil } @@ -61,6 +72,25 @@ func (s *MapMarkSet) Has(cid cid.Cid) (bool, error) { return ok, nil } +func (s *MapMarkSet) Visit(c cid.Cid) (bool, error) { + if s.ts { + s.mx.Lock() + defer s.mx.Unlock() + } + + if s.set == nil { + return false, errMarkSetClosed + } + + key := string(c.Hash()) + if _, ok := s.set[key]; ok { + return false, nil + } + + s.set[key] = struct{}{} + return true, nil +} + func (s *MapMarkSet) Close() error { if s.ts { s.mx.Lock() diff --git a/blockstore/splitstore/markset_test.go b/blockstore/splitstore/markset_test.go index 38519949a..a4a42e860 100644 --- a/blockstore/splitstore/markset_test.go +++ b/blockstore/splitstore/markset_test.go @@ -2,6 +2,7 @@ package splitstore import ( "io/ioutil" + "os" "testing" cid "github.com/ipfs/go-cid" @@ -10,10 +11,7 @@ import ( func TestMapMarkSet(t *testing.T) { testMarkSet(t, "map") -} - -func TestBloomMarkSet(t *testing.T) { - testMarkSet(t, "bloom") + testMarkSetVisitor(t, "map") } func TestBadgerMarkSet(t *testing.T) { @@ -23,16 +21,21 @@ func TestBadgerMarkSet(t *testing.T) { badgerMarkSetBatchSize = bs }) testMarkSet(t, "badger") + testMarkSetVisitor(t, "badger") } func testMarkSet(t *testing.T, lsType string) { t.Helper() - path, err := ioutil.TempDir("", "sweep-test.*") + path, err := ioutil.TempDir("", "markset.*") if err != nil { t.Fatal(err) } + t.Cleanup(func() { + _ = os.RemoveAll(path) + }) + env, err := OpenMarkSetEnv(path, lsType) if err != nil { t.Fatal(err) @@ -145,3 +148,74 @@ func testMarkSet(t *testing.T, lsType string) { t.Fatal(err) } } + +func testMarkSetVisitor(t *testing.T, lsType string) { + t.Helper() + + path, err := ioutil.TempDir("", "markset.*") + if err != nil { + t.Fatal(err) + } + + t.Cleanup(func() { + _ = os.RemoveAll(path) + }) + + env, err := OpenMarkSetEnv(path, lsType) + if err != nil { + t.Fatal(err) + } + defer env.Close() //nolint:errcheck + + visitor, err := env.CreateVisitor("test", 0) + if err != nil { + t.Fatal(err) + } + defer visitor.Close() //nolint:errcheck + + makeCid := func(key string) cid.Cid { + h, err := multihash.Sum([]byte(key), multihash.SHA2_256, -1) + if err != nil { + t.Fatal(err) + } + + return cid.NewCidV1(cid.Raw, h) + } + + mustVisit := func(v ObjectVisitor, cid cid.Cid) { + visit, err := v.Visit(cid) + if err != nil { + t.Fatal(err) + } + + if !visit { + t.Fatal("object should be visited") + } + } + + mustNotVisit := func(v ObjectVisitor, cid cid.Cid) { + visit, err := v.Visit(cid) + if err != nil { + t.Fatal(err) + } + + if visit { + t.Fatal("unexpected visit") + } + } + + k1 := makeCid("a") + k2 := makeCid("b") + k3 := makeCid("c") + k4 := makeCid("d") + + mustVisit(visitor, k1) + mustVisit(visitor, k2) + mustVisit(visitor, k3) + mustVisit(visitor, k4) + + mustNotVisit(visitor, k1) + mustNotVisit(visitor, k2) + mustNotVisit(visitor, k3) + mustNotVisit(visitor, k4) +} diff --git a/blockstore/splitstore/splitstore.go b/blockstore/splitstore/splitstore.go index 171b5a6e4..0e34fe952 100644 --- a/blockstore/splitstore/splitstore.go +++ b/blockstore/splitstore/splitstore.go @@ -142,7 +142,6 @@ type SplitStore struct { txnViews int txnViewsWaiting bool txnActive bool - txnProtect MarkSet txnRefsMx sync.Mutex txnRefs map[cid.Cid]struct{} txnMissing map[cid.Cid]struct{} @@ -174,6 +173,10 @@ func Open(path string, ds dstore.Datastore, hot, cold bstore.Blockstore, cfg *Co return nil, err } + if !markSetEnv.SupportsVisitor() { + return nil, xerrors.Errorf("markset type does not support atomic visitors") + } + // and now we can make a SplitStore ss := &SplitStore{ cfg: cfg, diff --git a/blockstore/splitstore/splitstore_check.go b/blockstore/splitstore/splitstore_check.go index 8c38b07e9..8907abf9e 100644 --- a/blockstore/splitstore/splitstore_check.go +++ b/blockstore/splitstore/splitstore_check.go @@ -83,7 +83,14 @@ func (s *SplitStore) doCheck(curTs *types.TipSet) error { write("--") var coldCnt, missingCnt int64 - err = s.walkChain(curTs, boundaryEpoch, boundaryEpoch, + + visitor, err := s.markSetEnv.CreateVisitor("check", 0) + if err != nil { + return xerrors.Errorf("error creating visitor: %w", err) + } + defer visitor.Close() //nolint + + err = s.walkChain(curTs, boundaryEpoch, boundaryEpoch, visitor, func(c cid.Cid) error { if isUnitaryObject(c) { return errStopWalk diff --git a/blockstore/splitstore/splitstore_compact.go b/blockstore/splitstore/splitstore_compact.go index b95459ea5..4ff38a5fb 100644 --- a/blockstore/splitstore/splitstore_compact.go +++ b/blockstore/splitstore/splitstore_compact.go @@ -211,7 +211,7 @@ func (s *SplitStore) trackTxnRefMany(cids []cid.Cid) { } // protect all pending transactional references -func (s *SplitStore) protectTxnRefs(markSet MarkSet) error { +func (s *SplitStore) protectTxnRefs(markSet MarkSetVisitor) error { for { var txnRefs map[cid.Cid]struct{} @@ -283,30 +283,29 @@ func (s *SplitStore) protectTxnRefs(markSet MarkSet) error { // transactionally protect a reference by walking the object and marking. // concurrent markings are short circuited by checking the markset. -func (s *SplitStore) doTxnProtect(root cid.Cid, markSet MarkSet) error { +func (s *SplitStore) doTxnProtect(root cid.Cid, markSet MarkSetVisitor) error { if err := s.checkClosing(); err != nil { return err } // Note: cold objects are deleted heaviest first, so the consituents of an object // cannot be deleted before the object itself. - return s.walkObjectIncomplete(root, cid.NewSet(), + return s.walkObjectIncomplete(root, tmpVisitor(), func(c cid.Cid) error { if isUnitaryObject(c) { return errStopWalk } - mark, err := markSet.Has(c) + visit, err := markSet.Visit(c) if err != nil { - return xerrors.Errorf("error checking markset: %w", err) + return xerrors.Errorf("error visiting object: %w", err) } - // it's marked, nothing to do - if mark { + if !visit { return errStopWalk } - return markSet.Mark(c) + return nil }, func(c cid.Cid) error { if s.txnMissing != nil { @@ -382,7 +381,7 @@ func (s *SplitStore) doCompact(curTs *types.TipSet) error { log.Infow("running compaction", "currentEpoch", currentEpoch, "baseEpoch", s.baseEpoch, "boundaryEpoch", boundaryEpoch, "inclMsgsEpoch", inclMsgsEpoch, "compactionIndex", s.compactionIndex) - markSet, err := s.markSetEnv.Create("live", s.markSetSize) + markSet, err := s.markSetEnv.CreateVisitor("live", s.markSetSize) if err != nil { return xerrors.Errorf("error creating mark set: %w", err) } @@ -410,14 +409,23 @@ func (s *SplitStore) doCompact(curTs *types.TipSet) error { startMark := time.Now() var count int64 - err = s.walkChain(curTs, boundaryEpoch, inclMsgsEpoch, + err = s.walkChain(curTs, boundaryEpoch, inclMsgsEpoch, &noopVisitor{}, func(c cid.Cid) error { if isUnitaryObject(c) { return errStopWalk } + visit, err := markSet.Visit(c) + if err != nil { + return xerrors.Errorf("error visiting object: %w", err) + } + + if !visit { + return errStopWalk + } + count++ - return markSet.Mark(c) + return nil }) if err != nil { @@ -578,12 +586,8 @@ func (s *SplitStore) beginTxnProtect() { s.txnMissing = make(map[cid.Cid]struct{}) } -func (s *SplitStore) beginTxnMarking(markSet MarkSet) { +func (s *SplitStore) beginTxnMarking(markSet MarkSetVisitor) { markSet.SetConcurrent() - - s.txnLk.Lock() - s.txnProtect = markSet - s.txnLk.Unlock() } func (s *SplitStore) endTxnProtect() { @@ -594,21 +598,14 @@ func (s *SplitStore) endTxnProtect() { return } - // release markset memory - if s.txnProtect != nil { - _ = s.txnProtect.Close() - } - s.txnActive = false - s.txnProtect = nil s.txnRefs = nil s.txnMissing = nil } func (s *SplitStore) walkChain(ts *types.TipSet, inclState, inclMsgs abi.ChainEpoch, - f func(cid.Cid) error) error { - visited := cid.NewSet() - walked := cid.NewSet() + visitor ObjectVisitor, f func(cid.Cid) error) error { + var walked *cid.Set toWalk := ts.Cids() walkCnt := 0 scanCnt := 0 @@ -616,7 +613,7 @@ func (s *SplitStore) walkChain(ts *types.TipSet, inclState, inclMsgs abi.ChainEp stopWalk := func(_ cid.Cid) error { return errStopWalk } walkBlock := func(c cid.Cid) error { - if !visited.Visit(c) { + if !walked.Visit(c) { return nil } @@ -640,19 +637,19 @@ func (s *SplitStore) walkChain(ts *types.TipSet, inclState, inclMsgs abi.ChainEp if inclMsgs < inclState { // we need to use walkObjectIncomplete here, as messages/receipts may be missing early on if we // synced from snapshot and have a long HotStoreMessageRetentionPolicy. - if err := s.walkObjectIncomplete(hdr.Messages, walked, f, stopWalk); err != nil { + if err := s.walkObjectIncomplete(hdr.Messages, visitor, f, stopWalk); err != nil { return xerrors.Errorf("error walking messages (cid: %s): %w", hdr.Messages, err) } - if err := s.walkObjectIncomplete(hdr.ParentMessageReceipts, walked, f, stopWalk); err != nil { + if err := s.walkObjectIncomplete(hdr.ParentMessageReceipts, visitor, f, stopWalk); err != nil { return xerrors.Errorf("error walking messages receipts (cid: %s): %w", hdr.ParentMessageReceipts, err) } } else { - if err := s.walkObject(hdr.Messages, walked, f); err != nil { + if err := s.walkObject(hdr.Messages, visitor, f); err != nil { return xerrors.Errorf("error walking messages (cid: %s): %w", hdr.Messages, err) } - if err := s.walkObject(hdr.ParentMessageReceipts, walked, f); err != nil { + if err := s.walkObject(hdr.ParentMessageReceipts, visitor, f); err != nil { return xerrors.Errorf("error walking message receipts (cid: %s): %w", hdr.ParentMessageReceipts, err) } } @@ -660,7 +657,7 @@ func (s *SplitStore) walkChain(ts *types.TipSet, inclState, inclMsgs abi.ChainEp // state is only retained if within the inclState boundary, with the exception of genesis if hdr.Height >= inclState || hdr.Height == 0 { - if err := s.walkObject(hdr.ParentStateRoot, walked, f); err != nil { + if err := s.walkObject(hdr.ParentStateRoot, visitor, f); err != nil { return xerrors.Errorf("error walking state root (cid: %s): %w", hdr.ParentStateRoot, err) } scanCnt++ @@ -679,6 +676,10 @@ func (s *SplitStore) walkChain(ts *types.TipSet, inclState, inclMsgs abi.ChainEp return err } + // the walk is BFS, so we can reset the walked set in every iteration and avoid building up + // a set that contains all blocks (1M epochs -> 5M blocks -> 200MB worth of memory and growing + // over time) + walked = cid.NewSet() walking := toWalk toWalk = nil for _, c := range walking { @@ -693,8 +694,13 @@ func (s *SplitStore) walkChain(ts *types.TipSet, inclState, inclMsgs abi.ChainEp return nil } -func (s *SplitStore) walkObject(c cid.Cid, walked *cid.Set, f func(cid.Cid) error) error { - if !walked.Visit(c) { +func (s *SplitStore) walkObject(c cid.Cid, visitor ObjectVisitor, f func(cid.Cid) error) error { + visit, err := visitor.Visit(c) + if err != nil { + return xerrors.Errorf("error visiting object: %w", err) + } + + if !visit { return nil } @@ -716,7 +722,7 @@ func (s *SplitStore) walkObject(c cid.Cid, walked *cid.Set, f func(cid.Cid) erro } var links []cid.Cid - err := s.view(c, func(data []byte) error { + err = s.view(c, func(data []byte) error { return cbg.ScanForLinks(bytes.NewReader(data), func(c cid.Cid) { links = append(links, c) }) @@ -727,7 +733,7 @@ func (s *SplitStore) walkObject(c cid.Cid, walked *cid.Set, f func(cid.Cid) erro } for _, c := range links { - err := s.walkObject(c, walked, f) + err := s.walkObject(c, visitor, f) if err != nil { return xerrors.Errorf("error walking link (cid: %s): %w", c, err) } @@ -737,8 +743,13 @@ func (s *SplitStore) walkObject(c cid.Cid, walked *cid.Set, f func(cid.Cid) erro } // like walkObject, but the object may be potentially incomplete (references missing) -func (s *SplitStore) walkObjectIncomplete(c cid.Cid, walked *cid.Set, f, missing func(cid.Cid) error) error { - if !walked.Visit(c) { +func (s *SplitStore) walkObjectIncomplete(c cid.Cid, visitor ObjectVisitor, f, missing func(cid.Cid) error) error { + visit, err := visitor.Visit(c) + if err != nil { + return xerrors.Errorf("error visiting object: %w", err) + } + + if !visit { return nil } @@ -777,7 +788,7 @@ func (s *SplitStore) walkObjectIncomplete(c cid.Cid, walked *cid.Set, f, missing } var links []cid.Cid - err := s.view(c, func(data []byte) error { + err = s.view(c, func(data []byte) error { return cbg.ScanForLinks(bytes.NewReader(data), func(c cid.Cid) { links = append(links, c) }) @@ -788,7 +799,7 @@ func (s *SplitStore) walkObjectIncomplete(c cid.Cid, walked *cid.Set, f, missing } for _, c := range links { - err := s.walkObjectIncomplete(c, walked, f, missing) + err := s.walkObjectIncomplete(c, visitor, f, missing) if err != nil { return xerrors.Errorf("error walking link (cid: %s): %w", c, err) } @@ -984,7 +995,7 @@ func (s *SplitStore) purgeBatch(cids []cid.Cid, deleteBatch func([]cid.Cid) erro return nil } -func (s *SplitStore) purge(cids []cid.Cid, markSet MarkSet) error { +func (s *SplitStore) purge(cids []cid.Cid, markSet MarkSetVisitor) error { deadCids := make([]cid.Cid, 0, batchSize) var purgeCnt, liveCnt int defer func() { @@ -1050,7 +1061,7 @@ func (s *SplitStore) purge(cids []cid.Cid, markSet MarkSet) error { // have this gem[TM]. // My best guess is that they are parent message receipts or yet to be computed state roots; magik // thinks the cause may be block validation. -func (s *SplitStore) waitForMissingRefs(markSet MarkSet) { +func (s *SplitStore) waitForMissingRefs(markSet MarkSetVisitor) { s.txnLk.Lock() missing := s.txnMissing s.txnMissing = nil @@ -1079,27 +1090,27 @@ func (s *SplitStore) waitForMissingRefs(markSet MarkSet) { } towalk := missing - walked := cid.NewSet() + visitor := tmpVisitor() missing = make(map[cid.Cid]struct{}) for c := range towalk { - err := s.walkObjectIncomplete(c, walked, + err := s.walkObjectIncomplete(c, visitor, func(c cid.Cid) error { if isUnitaryObject(c) { return errStopWalk } - mark, err := markSet.Has(c) + visit, err := markSet.Visit(c) if err != nil { - return xerrors.Errorf("error checking markset for %s: %w", c, err) + return xerrors.Errorf("error visiting object: %w", err) } - if mark { + if !visit { return errStopWalk } count++ - return markSet.Mark(c) + return nil }, func(c cid.Cid) error { missing[c] = struct{}{} diff --git a/blockstore/splitstore/splitstore_warmup.go b/blockstore/splitstore/splitstore_warmup.go index 2079a5474..216de571a 100644 --- a/blockstore/splitstore/splitstore_warmup.go +++ b/blockstore/splitstore/splitstore_warmup.go @@ -59,7 +59,15 @@ func (s *SplitStore) doWarmup(curTs *types.TipSet) error { count := int64(0) xcount := int64(0) missing := int64(0) - err := s.walkChain(curTs, boundaryEpoch, epoch+1, // we don't load messages/receipts in warmup + + visitor, err := s.markSetEnv.CreateVisitor("warmup", 0) + if err != nil { + return xerrors.Errorf("error creating visitor: %w", err) + } + defer visitor.Close() //nolint + + err = s.walkChain(curTs, boundaryEpoch, epoch+1, // we don't load messages/receipts in warmup + visitor, func(c cid.Cid) error { if isUnitaryObject(c) { return errStopWalk diff --git a/blockstore/splitstore/visitor.go b/blockstore/splitstore/visitor.go new file mode 100644 index 000000000..f89c8f389 --- /dev/null +++ b/blockstore/splitstore/visitor.go @@ -0,0 +1,32 @@ +package splitstore + +import ( + cid "github.com/ipfs/go-cid" +) + +// ObjectVisitor is an interface for deduplicating objects during walks +type ObjectVisitor interface { + Visit(cid.Cid) (bool, error) +} + +type noopVisitor struct{} + +var _ ObjectVisitor = (*noopVisitor)(nil) + +func (v *noopVisitor) Visit(_ cid.Cid) (bool, error) { + return true, nil +} + +type cidSetVisitor struct { + set *cid.Set +} + +var _ ObjectVisitor = (*cidSetVisitor)(nil) + +func (v *cidSetVisitor) Visit(c cid.Cid) (bool, error) { + return v.set.Visit(c), nil +} + +func tmpVisitor() ObjectVisitor { + return &cidSetVisitor{set: cid.NewSet()} +} diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 2f85885ef..66813c3f1 100644 Binary files a/build/openrpc/full.json.gz and b/build/openrpc/full.json.gz differ diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 9d2f5c2e0..1baa2163d 100644 Binary files a/build/openrpc/miner.json.gz and b/build/openrpc/miner.json.gz differ diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 53bb24e58..a71f29c26 100644 Binary files a/build/openrpc/worker.json.gz and b/build/openrpc/worker.json.gz differ diff --git a/build/params_butterfly.go b/build/params_butterfly.go index 258f6ab0f..4f4cc756d 100644 --- a/build/params_butterfly.go +++ b/build/params_butterfly.go @@ -46,6 +46,8 @@ func init() { SetAddressNetwork(address.Testnet) Devnet = true + + BuildType = BuildButterflynet } const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) diff --git a/build/params_nerpanet.go b/build/params_nerpanet.go index 3cba64ae2..5eef25cb2 100644 --- a/build/params_nerpanet.go +++ b/build/params_nerpanet.go @@ -66,6 +66,8 @@ func init() { //miner.WPoStChallengeLookback = abi.ChainEpoch(2) Devnet = false + + BuildType = BuildNerpanet } const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) diff --git a/build/params_shared_vals.go b/build/params_shared_vals.go index e4240ccce..db198e4aa 100644 --- a/build/params_shared_vals.go +++ b/build/params_shared_vals.go @@ -26,7 +26,6 @@ const UnixfsLinksPerLevel = 1024 const AllowableClockDriftSecs = uint64(1) const NewestNetworkVersion = network.Version13 -const ActorUpgradeNetworkVersion = network.Version4 // Epochs const ForkLengthThreshold = Finality diff --git a/build/version.go b/build/version.go index f1c12b16b..2e03befb4 100644 --- a/build/version.go +++ b/build/version.go @@ -6,12 +6,14 @@ var CurrentCommit string var BuildType int const ( - BuildDefault = 0 - BuildMainnet = 0x1 - Build2k = 0x2 - BuildDebug = 0x3 - BuildCalibnet = 0x4 - BuildInteropnet = 0x5 + BuildDefault = 0 + BuildMainnet = 0x1 + Build2k = 0x2 + BuildDebug = 0x3 + BuildCalibnet = 0x4 + BuildInteropnet = 0x5 + BuildNerpanet = 0x6 + BuildButterflynet = 0x7 ) func buildType() string { @@ -28,13 +30,17 @@ func buildType() string { return "+calibnet" case BuildInteropnet: return "+interopnet" + case BuildNerpanet: + return "+nerpanet" + case BuildButterflynet: + return "+butterflynet" default: return "+huh?" } } // BuildVersion is the local build version -const BuildVersion = "1.11.1" +const BuildVersion = "1.11.2" func UserVersion() string { if os.Getenv("LOTUS_VERSION_IGNORE_COMMIT") == "1" { diff --git a/chain/actors/builtin/miner/actor.go.template b/chain/actors/builtin/miner/actor.go.template index 12f418b37..edc298ca9 100644 --- a/chain/actors/builtin/miner/actor.go.template +++ b/chain/actors/builtin/miner/actor.go.template @@ -38,7 +38,7 @@ func init() { var Methods = builtin{{.latestVersion}}.MethodsMiner -// Unchanged between v0, v2, v3, and v4 actors +// Unchanged between v0, v2, v3, v4, and v5 actors var WPoStProvingPeriod = miner0.WPoStProvingPeriod var WPoStPeriodDeadlines = miner0.WPoStPeriodDeadlines var WPoStChallengeWindow = miner0.WPoStChallengeWindow diff --git a/chain/actors/builtin/miner/miner.go b/chain/actors/builtin/miner/miner.go index fc1d60e71..43e3a0828 100644 --- a/chain/actors/builtin/miner/miner.go +++ b/chain/actors/builtin/miner/miner.go @@ -61,7 +61,7 @@ func init() { var Methods = builtin5.MethodsMiner -// Unchanged between v0, v2, v3, and v4 actors +// Unchanged between v0, v2, v3, v4, and v5 actors var WPoStProvingPeriod = miner0.WPoStProvingPeriod var WPoStPeriodDeadlines = miner0.WPoStPeriodDeadlines var WPoStChallengeWindow = miner0.WPoStChallengeWindow diff --git a/chain/actors/builtin/miner/state.go.template b/chain/actors/builtin/miner/state.go.template index 09c1202d9..b63a73a2c 100644 --- a/chain/actors/builtin/miner/state.go.template +++ b/chain/actors/builtin/miner/state.go.template @@ -479,12 +479,15 @@ func (s *state{{.v}}) EraseAllUnproven() error { return dls.UpdateDeadline(s.store, dindx, dl) }) + if err != nil { + return err + } return s.State.SaveDeadlines(s.store, dls) {{else}} // field doesn't exist until v2 + return nil {{end}} - return nil } func (d *deadline{{.v}}) LoadPartition(idx uint64) (Partition, error) { diff --git a/chain/actors/builtin/miner/v0.go b/chain/actors/builtin/miner/v0.go index cd922645e..422afec8a 100644 --- a/chain/actors/builtin/miner/v0.go +++ b/chain/actors/builtin/miner/v0.go @@ -444,8 +444,8 @@ func (s *state0) decodeSectorPreCommitOnChainInfo(val *cbg.Deferred) (SectorPreC func (s *state0) EraseAllUnproven() error { // field doesn't exist until v2 - return nil + } func (d *deadline0) LoadPartition(idx uint64) (Partition, error) { diff --git a/chain/actors/builtin/miner/v2.go b/chain/actors/builtin/miner/v2.go index 5de653fe4..81b32abb7 100644 --- a/chain/actors/builtin/miner/v2.go +++ b/chain/actors/builtin/miner/v2.go @@ -470,10 +470,12 @@ func (s *state2) EraseAllUnproven() error { return dls.UpdateDeadline(s.store, dindx, dl) }) + if err != nil { + return err + } return s.State.SaveDeadlines(s.store, dls) - return nil } func (d *deadline2) LoadPartition(idx uint64) (Partition, error) { diff --git a/chain/actors/builtin/miner/v3.go b/chain/actors/builtin/miner/v3.go index 1819428a6..8ac77915a 100644 --- a/chain/actors/builtin/miner/v3.go +++ b/chain/actors/builtin/miner/v3.go @@ -467,10 +467,12 @@ func (s *state3) EraseAllUnproven() error { return dls.UpdateDeadline(s.store, dindx, dl) }) + if err != nil { + return err + } return s.State.SaveDeadlines(s.store, dls) - return nil } func (d *deadline3) LoadPartition(idx uint64) (Partition, error) { diff --git a/chain/actors/builtin/miner/v4.go b/chain/actors/builtin/miner/v4.go index 5a3a75053..5f442962f 100644 --- a/chain/actors/builtin/miner/v4.go +++ b/chain/actors/builtin/miner/v4.go @@ -467,10 +467,12 @@ func (s *state4) EraseAllUnproven() error { return dls.UpdateDeadline(s.store, dindx, dl) }) + if err != nil { + return err + } return s.State.SaveDeadlines(s.store, dls) - return nil } func (d *deadline4) LoadPartition(idx uint64) (Partition, error) { diff --git a/chain/actors/builtin/miner/v5.go b/chain/actors/builtin/miner/v5.go index 82e98c2ef..a3e03a7d4 100644 --- a/chain/actors/builtin/miner/v5.go +++ b/chain/actors/builtin/miner/v5.go @@ -467,10 +467,12 @@ func (s *state5) EraseAllUnproven() error { return dls.UpdateDeadline(s.store, dindx, dl) }) + if err != nil { + return err + } return s.State.SaveDeadlines(s.store, dls) - return nil } func (d *deadline5) LoadPartition(idx uint64) (Partition, error) { diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go index 492f76183..a67415726 100644 --- a/chain/actors/policy/policy.go +++ b/chain/actors/policy/policy.go @@ -315,6 +315,10 @@ func GetMaxSectorExpirationExtension() abi.ChainEpoch { return miner5.MaxSectorExpirationExtension } +func GetMinSectorExpiration() abi.ChainEpoch { + return miner5.MinSectorExpiration +} + func GetMaxPoStPartitions(nv network.Version, p abi.RegisteredPoStProof) (int, error) { sectorsPerPart, err := builtin5.PoStProofWindowPoStPartitionSectors(p) if err != nil { diff --git a/chain/actors/policy/policy.go.template b/chain/actors/policy/policy.go.template index 264d42992..9da91f73f 100644 --- a/chain/actors/policy/policy.go.template +++ b/chain/actors/policy/policy.go.template @@ -203,6 +203,10 @@ func GetMaxSectorExpirationExtension() abi.ChainEpoch { return miner{{.latestVersion}}.MaxSectorExpirationExtension } +func GetMinSectorExpiration() abi.ChainEpoch { + return miner{{.latestVersion}}.MinSectorExpiration +} + func GetMaxPoStPartitions(nv network.Version, p abi.RegisteredPoStProof) (int, error) { sectorsPerPart, err := builtin{{.latestVersion}}.PoStProofWindowPoStPartitionSectors(p) if err != nil { diff --git a/chain/exchange/cbor_gen.go b/chain/exchange/cbor_gen.go index da5f7cbe2..7a8597fd0 100644 --- a/chain/exchange/cbor_gen.go +++ b/chain/exchange/cbor_gen.go @@ -5,6 +5,7 @@ package exchange import ( "fmt" "io" + "math" "sort" types "github.com/filecoin-project/lotus/chain/types" @@ -15,6 +16,7 @@ import ( var _ = xerrors.Errorf var _ = cid.Undef +var _ = math.E var _ = sort.Sort var lengthBufRequest = []byte{131} diff --git a/chain/gen/gen.go b/chain/gen/gen.go index 424ee6edc..6b30f99ee 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -233,7 +233,7 @@ func NewGeneratorWithSectorsAndUpgradeSchedule(numSectors int, us stmgr.UpgradeS return nil, xerrors.Errorf("make genesis block failed: %w", err) } - cs := store.NewChainStore(bs, bs, ds, sys, j) + cs := store.NewChainStore(bs, bs, ds, j) genfb := &types.FullBlock{Header: genb.Genesis} gents := store.NewFullTipSet([]*types.FullBlock{genfb}) @@ -247,7 +247,7 @@ func NewGeneratorWithSectorsAndUpgradeSchedule(numSectors int, us stmgr.UpgradeS mgen[genesis2.MinerAddress(uint64(i))] = &wppProvider{} } - sm, err := stmgr.NewStateManagerWithUpgradeSchedule(cs, us) + sm, err := stmgr.NewStateManagerWithUpgradeSchedule(cs, sys, us) if err != nil { return nil, xerrors.Errorf("initing stmgr: %w", err) } diff --git a/chain/gen/genesis/genesis.go b/chain/gen/genesis/genesis.go index a94442d65..b737d319d 100644 --- a/chain/gen/genesis/genesis.go +++ b/chain/gen/genesis/genesis.go @@ -474,7 +474,7 @@ func createMultisigAccount(ctx context.Context, cst cbor.IpldStore, state *state return nil } -func VerifyPreSealedData(ctx context.Context, cs *store.ChainStore, stateroot cid.Cid, template genesis.Template, keyIDs map[address.Address]address.Address, nv network.Version) (cid.Cid, error) { +func VerifyPreSealedData(ctx context.Context, cs *store.ChainStore, sys vm.SyscallBuilder, stateroot cid.Cid, template genesis.Template, keyIDs map[address.Address]address.Address, nv network.Version) (cid.Cid, error) { verifNeeds := make(map[address.Address]abi.PaddedPieceSize) var sum abi.PaddedPieceSize @@ -483,7 +483,7 @@ func VerifyPreSealedData(ctx context.Context, cs *store.ChainStore, stateroot ci Epoch: 0, Rand: &fakeRand{}, Bstore: cs.StateBlockstore(), - Syscalls: mkFakedSigSyscalls(cs.VMSys()), + Syscalls: mkFakedSigSyscalls(sys), CircSupplyCalc: nil, NtwkVersion: func(_ context.Context, _ abi.ChainEpoch) network.Version { return nv @@ -562,15 +562,15 @@ func MakeGenesisBlock(ctx context.Context, j journal.Journal, bs bstore.Blocksto } // temp chainstore - cs := store.NewChainStore(bs, bs, datastore.NewMapDatastore(), sys, j) + cs := store.NewChainStore(bs, bs, datastore.NewMapDatastore(), j) // Verify PreSealed Data - stateroot, err = VerifyPreSealedData(ctx, cs, stateroot, template, keyIDs, template.NetworkVersion) + stateroot, err = VerifyPreSealedData(ctx, cs, sys, stateroot, template, keyIDs, template.NetworkVersion) if err != nil { return nil, xerrors.Errorf("failed to verify presealed data: %w", err) } - stateroot, err = SetupStorageMiners(ctx, cs, stateroot, template.Miners, template.NetworkVersion) + stateroot, err = SetupStorageMiners(ctx, cs, sys, stateroot, template.Miners, template.NetworkVersion) if err != nil { return nil, xerrors.Errorf("setup miners failed: %w", err) } diff --git a/chain/gen/genesis/miners.go b/chain/gen/genesis/miners.go index 38d3db518..1d95275df 100644 --- a/chain/gen/genesis/miners.go +++ b/chain/gen/genesis/miners.go @@ -6,30 +6,6 @@ import ( "fmt" "math/rand" - power4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/power" - - reward4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/reward" - - market4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/market" - - "github.com/filecoin-project/lotus/chain/actors" - - "github.com/filecoin-project/lotus/chain/actors/builtin" - - "github.com/filecoin-project/lotus/chain/actors/policy" - - "github.com/filecoin-project/lotus/chain/actors/adt" - - "github.com/filecoin-project/go-state-types/network" - - market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" - - "github.com/filecoin-project/lotus/chain/actors/builtin/power" - "github.com/filecoin-project/lotus/chain/actors/builtin/reward" - - "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" cbg "github.com/whyrusleeping/cbor-gen" @@ -39,12 +15,28 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/network" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" power0 "github.com/filecoin-project/specs-actors/actors/builtin/power" reward0 "github.com/filecoin-project/specs-actors/actors/builtin/reward" + market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" + reward2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/reward" + market4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/market" + power4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/power" + reward4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/reward" runtime5 "github.com/filecoin-project/specs-actors/v5/actors/runtime" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/actors/builtin/reward" + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" @@ -78,7 +70,7 @@ func mkFakedSigSyscalls(base vm.SyscallBuilder) vm.SyscallBuilder { } // Note: Much of this is brittle, if the methodNum / param / return changes, it will break things -func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid, miners []genesis.Miner, nv network.Version) (cid.Cid, error) { +func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sys vm.SyscallBuilder, sroot cid.Cid, miners []genesis.Miner, nv network.Version) (cid.Cid, error) { cst := cbor.NewCborStore(cs.StateBlockstore()) av, err := actors.VersionForNetwork(nv) @@ -95,7 +87,7 @@ func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid Epoch: 0, Rand: &fakeRand{}, Bstore: cs.StateBlockstore(), - Syscalls: mkFakedSigSyscalls(cs.VMSys()), + Syscalls: mkFakedSigSyscalls(sys), CircSupplyCalc: csc, NtwkVersion: func(_ context.Context, _ abi.ChainEpoch) network.Version { return nv @@ -404,8 +396,8 @@ func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid return cid.Undef, xerrors.Errorf("failed to confirm presealed sectors: %w", err) } - if av > actors.Version2 { - // post v2, we need to explicitly Claim this power since ConfirmSectorProofsValid doesn't do it anymore + if av >= actors.Version2 { + // post v0, we need to explicitly Claim this power since ConfirmSectorProofsValid doesn't do it anymore claimParams := &power4.UpdateClaimedPowerParams{ RawByteDelta: types.NewInt(uint64(m.SectorSize)), QualityAdjustedDelta: sectorWeight, @@ -537,7 +529,6 @@ func dealWeight(ctx context.Context, vm *vm.VM, maddr address.Address, dealIDs [ SectorExpiry: sectorExpiry, } - var dealWeights market0.VerifyDealsForActivationReturn ret, err := doExecValue(ctx, vm, market.Address, maddr, @@ -548,11 +539,23 @@ func dealWeight(ctx context.Context, vm *vm.VM, maddr address.Address, dealIDs [ if err != nil { return big.Zero(), big.Zero(), err } - if err := dealWeights.UnmarshalCBOR(bytes.NewReader(ret)); err != nil { + var weight, verifiedWeight abi.DealWeight + if av < actors.Version2 { + var dealWeights market0.VerifyDealsForActivationReturn + err = dealWeights.UnmarshalCBOR(bytes.NewReader(ret)) + weight = dealWeights.DealWeight + verifiedWeight = dealWeights.VerifiedDealWeight + } else { + var dealWeights market2.VerifyDealsForActivationReturn + err = dealWeights.UnmarshalCBOR(bytes.NewReader(ret)) + weight = dealWeights.DealWeight + verifiedWeight = dealWeights.VerifiedDealWeight + } + if err != nil { return big.Zero(), big.Zero(), err } - return dealWeights.DealWeight, dealWeights.VerifiedDealWeight, nil + return weight, verifiedWeight, nil } params := &market4.VerifyDealsForActivationParams{Sectors: []market4.SectorDeals{{ SectorExpiry: sectorExpiry, @@ -584,7 +587,8 @@ func currentEpochBlockReward(ctx context.Context, vm *vm.VM, maddr address.Addre } // TODO: This hack should move to reward actor wrapper - if av <= actors.Version2 { + switch av { + case actors.Version0: var epochReward reward0.ThisEpochRewardReturn if err := epochReward.UnmarshalCBOR(bytes.NewReader(rwret)); err != nil { @@ -592,6 +596,14 @@ func currentEpochBlockReward(ctx context.Context, vm *vm.VM, maddr address.Addre } return epochReward.ThisEpochBaselinePower, *epochReward.ThisEpochRewardSmoothed, nil + case actors.Version2: + var epochReward reward2.ThisEpochRewardReturn + + if err := epochReward.UnmarshalCBOR(bytes.NewReader(rwret)); err != nil { + return big.Zero(), builtin.FilterEstimate{}, err + } + + return epochReward.ThisEpochBaselinePower, builtin.FilterEstimate(epochReward.ThisEpochRewardSmoothed), nil } var epochReward reward4.ThisEpochRewardReturn diff --git a/chain/market/cbor_gen.go b/chain/market/cbor_gen.go index 7d9e55b36..1c13e9ddc 100644 --- a/chain/market/cbor_gen.go +++ b/chain/market/cbor_gen.go @@ -5,6 +5,7 @@ package market import ( "fmt" "io" + "math" "sort" cid "github.com/ipfs/go-cid" @@ -14,6 +15,7 @@ import ( var _ = xerrors.Errorf var _ = cid.Undef +var _ = math.E var _ = sort.Sort var lengthBufFundedAddressState = []byte{131} diff --git a/chain/metrics/consensus.go b/chain/metrics/consensus.go deleted file mode 100644 index c3c4a10d1..000000000 --- a/chain/metrics/consensus.go +++ /dev/null @@ -1,129 +0,0 @@ -package metrics - -import ( - "context" - "encoding/json" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - pubsub "github.com/libp2p/go-libp2p-pubsub" - "go.uber.org/fx" - - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/node/impl/full" - "github.com/filecoin-project/lotus/node/modules/helpers" -) - -var log = logging.Logger("metrics") - -const baseTopic = "/fil/headnotifs/" - -type Update struct { - Type string -} - -func SendHeadNotifs(nickname string) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, ps *pubsub.PubSub, chain full.ChainAPI) error { - return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, ps *pubsub.PubSub, chain full.ChainAPI) error { - ctx := helpers.LifecycleCtx(mctx, lc) - - lc.Append(fx.Hook{ - OnStart: func(_ context.Context) error { - gen, err := chain.Chain.GetGenesis() - if err != nil { - return err - } - - topic := baseTopic + gen.Cid().String() - - go func() { - if err := sendHeadNotifs(ctx, ps, topic, chain, nickname); err != nil { - log.Error("consensus metrics error", err) - return - } - }() - go func() { - sub, err := ps.Subscribe(topic) //nolint - if err != nil { - return - } - defer sub.Cancel() - - for { - if _, err := sub.Next(ctx); err != nil { - return - } - } - - }() - return nil - }, - }) - - return nil - } -} - -type message struct { - // TipSet - Cids []cid.Cid - Blocks []*types.BlockHeader - Height abi.ChainEpoch - Weight types.BigInt - Time uint64 - Nonce uint64 - - // Meta - - NodeName string -} - -func sendHeadNotifs(ctx context.Context, ps *pubsub.PubSub, topic string, chain full.ChainAPI, nickname string) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - notifs, err := chain.ChainNotify(ctx) - if err != nil { - return err - } - - // using unix nano time makes very sure we pick a nonce higher than previous restart - nonce := uint64(build.Clock.Now().UnixNano()) - - for { - select { - case notif := <-notifs: - n := notif[len(notif)-1] - - w, err := chain.ChainTipSetWeight(ctx, n.Val.Key()) - if err != nil { - return err - } - - m := message{ - Cids: n.Val.Cids(), - Blocks: n.Val.Blocks(), - Height: n.Val.Height(), - Weight: w, - NodeName: nickname, - Time: uint64(build.Clock.Now().UnixNano() / 1000_000), - Nonce: nonce, - } - - b, err := json.Marshal(m) - if err != nil { - return err - } - - //nolint - if err := ps.Publish(topic, b); err != nil { - return err - } - case <-ctx.Done(): - return nil - } - - nonce++ - } -} diff --git a/chain/state/statetree.go b/chain/state/statetree.go index 8705aeff8..8140cd4db 100644 --- a/chain/state/statetree.go +++ b/chain/state/statetree.go @@ -273,7 +273,7 @@ func LoadStateTree(cst cbor.IpldStore, c cid.Cid) (*StateTree, error) { } if err != nil { log.Errorf("failed to load state tree: %s", err) - return nil, xerrors.Errorf("failed to load state tree: %w", err) + return nil, xerrors.Errorf("failed to load state tree %s: %w", c, err) } s := &StateTree{ diff --git a/chain/stmgr/actors.go b/chain/stmgr/actors.go new file mode 100644 index 000000000..0c1e219c8 --- /dev/null +++ b/chain/stmgr/actors.go @@ -0,0 +1,551 @@ +package stmgr + +import ( + "bytes" + "context" + "os" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/network" + cid "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/beacon" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" +) + +func GetMinerWorkerRaw(ctx context.Context, sm *StateManager, st cid.Cid, maddr address.Address) (address.Address, error) { + state, err := sm.StateTree(st) + if err != nil { + return address.Undef, xerrors.Errorf("(get sset) failed to load state tree: %w", err) + } + act, err := state.GetActor(maddr) + if err != nil { + return address.Undef, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) + } + mas, err := miner.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return address.Undef, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) + } + + info, err := mas.Info() + if err != nil { + return address.Undef, xerrors.Errorf("failed to load actor info: %w", err) + } + + return vm.ResolveToKeyAddr(state, sm.cs.ActorStore(ctx), info.Worker) +} + +func GetPower(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (power.Claim, power.Claim, bool, error) { + return GetPowerRaw(ctx, sm, ts.ParentState(), maddr) +} + +func GetPowerRaw(ctx context.Context, sm *StateManager, st cid.Cid, maddr address.Address) (power.Claim, power.Claim, bool, error) { + act, err := sm.LoadActorRaw(ctx, power.Address, st) + if err != nil { + return power.Claim{}, power.Claim{}, false, xerrors.Errorf("(get sset) failed to load power actor state: %w", err) + } + + pas, err := power.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return power.Claim{}, power.Claim{}, false, err + } + + tpow, err := pas.TotalPower() + if err != nil { + return power.Claim{}, power.Claim{}, false, err + } + + var mpow power.Claim + var minpow bool + if maddr != address.Undef { + var found bool + mpow, found, err = pas.MinerPower(maddr) + if err != nil || !found { + return power.Claim{}, tpow, false, err + } + + minpow, err = pas.MinerNominalPowerMeetsConsensusMinimum(maddr) + if err != nil { + return power.Claim{}, power.Claim{}, false, err + } + } + + return mpow, tpow, minpow, nil +} + +func PreCommitInfo(ctx context.Context, sm *StateManager, maddr address.Address, sid abi.SectorNumber, ts *types.TipSet) (*miner.SectorPreCommitOnChainInfo, error) { + act, err := sm.LoadActor(ctx, maddr, ts) + if err != nil { + return nil, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) + } + + mas, err := miner.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) + } + + return mas.GetPrecommittedSector(sid) +} + +func MinerSectorInfo(ctx context.Context, sm *StateManager, maddr address.Address, sid abi.SectorNumber, ts *types.TipSet) (*miner.SectorOnChainInfo, error) { + act, err := sm.LoadActor(ctx, maddr, ts) + if err != nil { + return nil, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) + } + + mas, err := miner.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) + } + + return mas.GetSector(sid) +} + +func GetSectorsForWinningPoSt(ctx context.Context, nv network.Version, pv ffiwrapper.Verifier, sm *StateManager, st cid.Cid, maddr address.Address, rand abi.PoStRandomness) ([]builtin.SectorInfo, error) { + act, err := sm.LoadActorRaw(ctx, maddr, st) + if err != nil { + return nil, xerrors.Errorf("failed to load miner actor: %w", err) + } + + mas, err := miner.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("failed to load miner actor state: %w", err) + } + + var provingSectors bitfield.BitField + if nv < network.Version7 { + allSectors, err := miner.AllPartSectors(mas, miner.Partition.AllSectors) + if err != nil { + return nil, xerrors.Errorf("get all sectors: %w", err) + } + + faultySectors, err := miner.AllPartSectors(mas, miner.Partition.FaultySectors) + if err != nil { + return nil, xerrors.Errorf("get faulty sectors: %w", err) + } + + provingSectors, err = bitfield.SubtractBitField(allSectors, faultySectors) + if err != nil { + return nil, xerrors.Errorf("calc proving sectors: %w", err) + } + } else { + provingSectors, err = miner.AllPartSectors(mas, miner.Partition.ActiveSectors) + if err != nil { + return nil, xerrors.Errorf("get active sectors sectors: %w", err) + } + } + + numProvSect, err := provingSectors.Count() + if err != nil { + return nil, xerrors.Errorf("failed to count bits: %w", err) + } + + // TODO(review): is this right? feels fishy to me + if numProvSect == 0 { + return nil, nil + } + + info, err := mas.Info() + if err != nil { + return nil, xerrors.Errorf("getting miner info: %w", err) + } + + mid, err := address.IDFromAddress(maddr) + if err != nil { + return nil, xerrors.Errorf("getting miner ID: %w", err) + } + + proofType, err := miner.WinningPoStProofTypeFromWindowPoStProofType(nv, info.WindowPoStProofType) + if err != nil { + return nil, xerrors.Errorf("determining winning post proof type: %w", err) + } + + ids, err := pv.GenerateWinningPoStSectorChallenge(ctx, proofType, abi.ActorID(mid), rand, numProvSect) + if err != nil { + return nil, xerrors.Errorf("generating winning post challenges: %w", err) + } + + iter, err := provingSectors.BitIterator() + if err != nil { + return nil, xerrors.Errorf("iterating over proving sectors: %w", err) + } + + // Select winning sectors by _index_ in the all-sectors bitfield. + selectedSectors := bitfield.New() + prev := uint64(0) + for _, n := range ids { + sno, err := iter.Nth(n - prev) + if err != nil { + return nil, xerrors.Errorf("iterating over proving sectors: %w", err) + } + selectedSectors.Set(sno) + prev = n + } + + sectors, err := mas.LoadSectors(&selectedSectors) + if err != nil { + return nil, xerrors.Errorf("loading proving sectors: %w", err) + } + + out := make([]builtin.SectorInfo, len(sectors)) + for i, sinfo := range sectors { + out[i] = builtin.SectorInfo{ + SealProof: sinfo.SealProof, + SectorNumber: sinfo.SectorNumber, + SealedCID: sinfo.SealedCID, + } + } + + return out, nil +} + +func GetMinerSlashed(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (bool, error) { + act, err := sm.LoadActor(ctx, power.Address, ts) + if err != nil { + return false, xerrors.Errorf("failed to load power actor: %w", err) + } + + spas, err := power.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return false, xerrors.Errorf("failed to load power actor state: %w", err) + } + + _, ok, err := spas.MinerPower(maddr) + if err != nil { + return false, xerrors.Errorf("getting miner power: %w", err) + } + + if !ok { + return true, nil + } + + return false, nil +} + +func GetStorageDeal(ctx context.Context, sm *StateManager, dealID abi.DealID, ts *types.TipSet) (*api.MarketDeal, error) { + act, err := sm.LoadActor(ctx, market.Address, ts) + if err != nil { + return nil, xerrors.Errorf("failed to load market actor: %w", err) + } + + state, err := market.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("failed to load market actor state: %w", err) + } + + proposals, err := state.Proposals() + if err != nil { + return nil, err + } + + proposal, found, err := proposals.Get(dealID) + + if err != nil { + return nil, err + } else if !found { + return nil, xerrors.Errorf( + "deal %d not found "+ + "- deal may not have completed sealing before deal proposal "+ + "start epoch, or deal may have been slashed", + dealID) + } + + states, err := state.States() + if err != nil { + return nil, err + } + + st, found, err := states.Get(dealID) + if err != nil { + return nil, err + } + + if !found { + st = market.EmptyDealState() + } + + return &api.MarketDeal{ + Proposal: *proposal, + State: *st, + }, nil +} + +func ListMinerActors(ctx context.Context, sm *StateManager, ts *types.TipSet) ([]address.Address, error) { + act, err := sm.LoadActor(ctx, power.Address, ts) + if err != nil { + return nil, xerrors.Errorf("failed to load power actor: %w", err) + } + + powState, err := power.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("failed to load power actor state: %w", err) + } + + return powState.ListAllMiners() +} + +func MinerGetBaseInfo(ctx context.Context, sm *StateManager, bcs beacon.Schedule, tsk types.TipSetKey, round abi.ChainEpoch, maddr address.Address, pv ffiwrapper.Verifier) (*api.MiningBaseInfo, error) { + ts, err := sm.ChainStore().LoadTipSet(tsk) + if err != nil { + return nil, xerrors.Errorf("failed to load tipset for mining base: %w", err) + } + + prev, err := sm.ChainStore().GetLatestBeaconEntry(ts) + if err != nil { + if os.Getenv("LOTUS_IGNORE_DRAND") != "_yes_" { + return nil, xerrors.Errorf("failed to get latest beacon entry: %w", err) + } + + prev = &types.BeaconEntry{} + } + + entries, err := beacon.BeaconEntriesForBlock(ctx, bcs, round, ts.Height(), *prev) + if err != nil { + return nil, err + } + + rbase := *prev + if len(entries) > 0 { + rbase = entries[len(entries)-1] + } + + lbts, lbst, err := GetLookbackTipSetForRound(ctx, sm, ts, round) + if err != nil { + return nil, xerrors.Errorf("getting lookback miner actor state: %w", err) + } + + act, err := sm.LoadActorRaw(ctx, maddr, lbst) + if xerrors.Is(err, types.ErrActorNotFound) { + _, err := sm.LoadActor(ctx, maddr, ts) + if err != nil { + return nil, xerrors.Errorf("loading miner in current state: %w", err) + } + + return nil, nil + } + if err != nil { + return nil, xerrors.Errorf("failed to load miner actor: %w", err) + } + + mas, err := miner.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("failed to load miner actor state: %w", err) + } + + buf := new(bytes.Buffer) + if err := maddr.MarshalCBOR(buf); err != nil { + return nil, xerrors.Errorf("failed to marshal miner address: %w", err) + } + + prand, err := store.DrawRandomness(rbase.Data, crypto.DomainSeparationTag_WinningPoStChallengeSeed, round, buf.Bytes()) + if err != nil { + return nil, xerrors.Errorf("failed to get randomness for winning post: %w", err) + } + + nv := sm.GetNtwkVersion(ctx, ts.Height()) + + sectors, err := GetSectorsForWinningPoSt(ctx, nv, pv, sm, lbst, maddr, prand) + if err != nil { + return nil, xerrors.Errorf("getting winning post proving set: %w", err) + } + + if len(sectors) == 0 { + return nil, nil + } + + mpow, tpow, _, err := GetPowerRaw(ctx, sm, lbst, maddr) + if err != nil { + return nil, xerrors.Errorf("failed to get power: %w", err) + } + + info, err := mas.Info() + if err != nil { + return nil, err + } + + worker, err := sm.ResolveToKeyAddress(ctx, info.Worker, ts) + if err != nil { + return nil, xerrors.Errorf("resolving worker address: %w", err) + } + + // TODO: Not ideal performance...This method reloads miner and power state (already looked up here and in GetPowerRaw) + eligible, err := MinerEligibleToMine(ctx, sm, maddr, ts, lbts) + if err != nil { + return nil, xerrors.Errorf("determining miner eligibility: %w", err) + } + + return &api.MiningBaseInfo{ + MinerPower: mpow.QualityAdjPower, + NetworkPower: tpow.QualityAdjPower, + Sectors: sectors, + WorkerKey: worker, + SectorSize: info.SectorSize, + PrevBeaconEntry: *prev, + BeaconEntries: entries, + EligibleForMining: eligible, + }, nil +} + +func minerHasMinPower(ctx context.Context, sm *StateManager, addr address.Address, ts *types.TipSet) (bool, error) { + pact, err := sm.LoadActor(ctx, power.Address, ts) + if err != nil { + return false, xerrors.Errorf("loading power actor state: %w", err) + } + + ps, err := power.Load(sm.cs.ActorStore(ctx), pact) + if err != nil { + return false, err + } + + return ps.MinerNominalPowerMeetsConsensusMinimum(addr) +} + +func MinerEligibleToMine(ctx context.Context, sm *StateManager, addr address.Address, baseTs *types.TipSet, lookbackTs *types.TipSet) (bool, error) { + hmp, err := minerHasMinPower(ctx, sm, addr, lookbackTs) + + // TODO: We're blurring the lines between a "runtime network version" and a "Lotus upgrade epoch", is that unavoidable? + if sm.GetNtwkVersion(ctx, baseTs.Height()) <= network.Version3 { + return hmp, err + } + + if err != nil { + return false, err + } + + if !hmp { + return false, nil + } + + // Post actors v2, also check MinerEligibleForElection with base ts + + pact, err := sm.LoadActor(ctx, power.Address, baseTs) + if err != nil { + return false, xerrors.Errorf("loading power actor state: %w", err) + } + + pstate, err := power.Load(sm.cs.ActorStore(ctx), pact) + if err != nil { + return false, err + } + + mact, err := sm.LoadActor(ctx, addr, baseTs) + if err != nil { + return false, xerrors.Errorf("loading miner actor state: %w", err) + } + + mstate, err := miner.Load(sm.cs.ActorStore(ctx), mact) + if err != nil { + return false, err + } + + // Non-empty power claim. + if claim, found, err := pstate.MinerPower(addr); err != nil { + return false, err + } else if !found { + return false, err + } else if claim.QualityAdjPower.LessThanEqual(big.Zero()) { + return false, err + } + + // No fee debt. + if debt, err := mstate.FeeDebt(); err != nil { + return false, err + } else if !debt.IsZero() { + return false, err + } + + // No active consensus faults. + if mInfo, err := mstate.Info(); err != nil { + return false, err + } else if baseTs.Height() <= mInfo.ConsensusFaultElapsed { + return false, nil + } + + return true, nil +} + +func (sm *StateManager) GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error) { + st, err := sm.ParentState(ts) + if err != nil { + return nil, nil, err + } + + act, err := st.GetActor(addr) + if err != nil { + return nil, nil, err + } + + actState, err := paych.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, nil, err + } + return act, actState, nil +} + +func (sm *StateManager) GetMarketState(ctx context.Context, ts *types.TipSet) (market.State, error) { + st, err := sm.ParentState(ts) + if err != nil { + return nil, err + } + + act, err := st.GetActor(market.Address) + if err != nil { + return nil, err + } + + actState, err := market.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, err + } + return actState, nil +} + +func (sm *StateManager) MarketBalance(ctx context.Context, addr address.Address, ts *types.TipSet) (api.MarketBalance, error) { + mstate, err := sm.GetMarketState(ctx, ts) + if err != nil { + return api.MarketBalance{}, err + } + + addr, err = sm.LookupID(ctx, addr, ts) + if err != nil { + return api.MarketBalance{}, err + } + + var out api.MarketBalance + + et, err := mstate.EscrowTable() + if err != nil { + return api.MarketBalance{}, err + } + out.Escrow, err = et.Get(addr) + if err != nil { + return api.MarketBalance{}, xerrors.Errorf("getting escrow balance: %w", err) + } + + lt, err := mstate.LockedTable() + if err != nil { + return api.MarketBalance{}, err + } + out.Locked, err = lt.Get(addr) + if err != nil { + return api.MarketBalance{}, xerrors.Errorf("getting locked balance: %w", err) + } + + return out, nil +} + +var _ StateManagerAPI = (*StateManager)(nil) diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index dc6da0f9c..caa815132 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -64,7 +64,7 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types. Epoch: pheight + 1, Rand: store.NewChainRand(sm.cs, ts.Cids()), Bstore: sm.cs.StateBlockstore(), - Syscalls: sm.cs.VMSys(), + Syscalls: sm.syscalls, CircSupplyCalc: sm.GetVMCirculatingSupply, NtwkVersion: sm.GetNtwkVersion, BaseFee: types.NewInt(0), @@ -179,7 +179,7 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri Epoch: ts.Height() + 1, Rand: r, Bstore: sm.cs.StateBlockstore(), - Syscalls: sm.cs.VMSys(), + Syscalls: sm.syscalls, CircSupplyCalc: sm.GetVMCirculatingSupply, NtwkVersion: sm.GetNtwkVersion, BaseFee: ts.Blocks()[0].ParentBaseFee, diff --git a/chain/stmgr/execute.go b/chain/stmgr/execute.go new file mode 100644 index 000000000..3191a45db --- /dev/null +++ b/chain/stmgr/execute.go @@ -0,0 +1,326 @@ +package stmgr + +import ( + "context" + "fmt" + "sync/atomic" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "go.opencensus.io/stats" + "go.opencensus.io/trace" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/cron" + "github.com/filecoin-project/lotus/chain/actors/builtin/reward" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/metrics" +) + +func (sm *StateManager) ApplyBlocks(ctx context.Context, parentEpoch abi.ChainEpoch, pstate cid.Cid, bms []store.BlockMessages, epoch abi.ChainEpoch, r vm.Rand, em ExecMonitor, baseFee abi.TokenAmount, ts *types.TipSet) (cid.Cid, cid.Cid, error) { + done := metrics.Timer(ctx, metrics.VMApplyBlocksTotal) + defer done() + + partDone := metrics.Timer(ctx, metrics.VMApplyEarly) + defer func() { + partDone() + }() + + makeVmWithBaseState := func(base cid.Cid) (*vm.VM, error) { + vmopt := &vm.VMOpts{ + StateBase: base, + Epoch: epoch, + Rand: r, + Bstore: sm.cs.StateBlockstore(), + Syscalls: sm.syscalls, + CircSupplyCalc: sm.GetVMCirculatingSupply, + NtwkVersion: sm.GetNtwkVersion, + BaseFee: baseFee, + LookbackState: LookbackStateGetterForTipset(sm, ts), + } + + return sm.newVM(ctx, vmopt) + } + + vmi, err := makeVmWithBaseState(pstate) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("making vm: %w", err) + } + + runCron := func(epoch abi.ChainEpoch) error { + cronMsg := &types.Message{ + To: cron.Address, + From: builtin.SystemActorAddr, + Nonce: uint64(epoch), + Value: types.NewInt(0), + GasFeeCap: types.NewInt(0), + GasPremium: types.NewInt(0), + GasLimit: build.BlockGasLimit * 10000, // Make super sure this is never too little + Method: cron.Methods.EpochTick, + Params: nil, + } + ret, err := vmi.ApplyImplicitMessage(ctx, cronMsg) + if err != nil { + return err + } + if em != nil { + if err := em.MessageApplied(ctx, ts, cronMsg.Cid(), cronMsg, ret, true); err != nil { + return xerrors.Errorf("callback failed on cron message: %w", err) + } + } + if ret.ExitCode != 0 { + return xerrors.Errorf("CheckProofSubmissions exit was non-zero: %d", ret.ExitCode) + } + + return nil + } + + for i := parentEpoch; i < epoch; i++ { + if i > parentEpoch { + // run cron for null rounds if any + if err := runCron(i); err != nil { + return cid.Undef, cid.Undef, err + } + + pstate, err = vmi.Flush(ctx) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("flushing vm: %w", err) + } + } + + // handle state forks + // XXX: The state tree + newState, err := sm.handleStateForks(ctx, pstate, i, em, ts) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("error handling state forks: %w", err) + } + + if pstate != newState { + vmi, err = makeVmWithBaseState(newState) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("making vm: %w", err) + } + } + + vmi.SetBlockHeight(i + 1) + pstate = newState + } + + partDone() + partDone = metrics.Timer(ctx, metrics.VMApplyMessages) + + var receipts []cbg.CBORMarshaler + processedMsgs := make(map[cid.Cid]struct{}) + for _, b := range bms { + penalty := types.NewInt(0) + gasReward := big.Zero() + + for _, cm := range append(b.BlsMessages, b.SecpkMessages...) { + m := cm.VMMessage() + if _, found := processedMsgs[m.Cid()]; found { + continue + } + r, err := vmi.ApplyMessage(ctx, cm) + if err != nil { + return cid.Undef, cid.Undef, err + } + + receipts = append(receipts, &r.MessageReceipt) + gasReward = big.Add(gasReward, r.GasCosts.MinerTip) + penalty = big.Add(penalty, r.GasCosts.MinerPenalty) + + if em != nil { + if err := em.MessageApplied(ctx, ts, cm.Cid(), m, r, false); err != nil { + return cid.Undef, cid.Undef, err + } + } + processedMsgs[m.Cid()] = struct{}{} + } + + params, err := actors.SerializeParams(&reward.AwardBlockRewardParams{ + Miner: b.Miner, + Penalty: penalty, + GasReward: gasReward, + WinCount: b.WinCount, + }) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to serialize award params: %w", err) + } + + rwMsg := &types.Message{ + From: builtin.SystemActorAddr, + To: reward.Address, + Nonce: uint64(epoch), + Value: types.NewInt(0), + GasFeeCap: types.NewInt(0), + GasPremium: types.NewInt(0), + GasLimit: 1 << 30, + Method: reward.Methods.AwardBlockReward, + Params: params, + } + ret, actErr := vmi.ApplyImplicitMessage(ctx, rwMsg) + if actErr != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to apply reward message for miner %s: %w", b.Miner, actErr) + } + if em != nil { + if err := em.MessageApplied(ctx, ts, rwMsg.Cid(), rwMsg, ret, true); err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("callback failed on reward message: %w", err) + } + } + + if ret.ExitCode != 0 { + return cid.Undef, cid.Undef, xerrors.Errorf("reward application message failed (exit %d): %s", ret.ExitCode, ret.ActorErr) + } + } + + partDone() + partDone = metrics.Timer(ctx, metrics.VMApplyCron) + + if err := runCron(epoch); err != nil { + return cid.Cid{}, cid.Cid{}, err + } + + partDone() + partDone = metrics.Timer(ctx, metrics.VMApplyFlush) + + rectarr := blockadt.MakeEmptyArray(sm.cs.ActorStore(ctx)) + for i, receipt := range receipts { + if err := rectarr.Set(uint64(i), receipt); err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to build receipts amt: %w", err) + } + } + rectroot, err := rectarr.Root() + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to build receipts amt: %w", err) + } + + st, err := vmi.Flush(ctx) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("vm flush failed: %w", err) + } + + stats.Record(ctx, metrics.VMSends.M(int64(atomic.LoadUint64(&vm.StatSends))), + metrics.VMApplied.M(int64(atomic.LoadUint64(&vm.StatApplied)))) + + return st, rectroot, nil +} + +func (sm *StateManager) TipSetState(ctx context.Context, ts *types.TipSet) (st cid.Cid, rec cid.Cid, err error) { + ctx, span := trace.StartSpan(ctx, "tipSetState") + defer span.End() + if span.IsRecordingEvents() { + span.AddAttributes(trace.StringAttribute("tipset", fmt.Sprint(ts.Cids()))) + } + + ck := cidsToKey(ts.Cids()) + sm.stlk.Lock() + cw, cwok := sm.compWait[ck] + if cwok { + sm.stlk.Unlock() + span.AddAttributes(trace.BoolAttribute("waited", true)) + select { + case <-cw: + sm.stlk.Lock() + case <-ctx.Done(): + return cid.Undef, cid.Undef, ctx.Err() + } + } + cached, ok := sm.stCache[ck] + if ok { + sm.stlk.Unlock() + span.AddAttributes(trace.BoolAttribute("cache", true)) + return cached[0], cached[1], nil + } + ch := make(chan struct{}) + sm.compWait[ck] = ch + + defer func() { + sm.stlk.Lock() + delete(sm.compWait, ck) + if st != cid.Undef { + sm.stCache[ck] = []cid.Cid{st, rec} + } + sm.stlk.Unlock() + close(ch) + }() + + sm.stlk.Unlock() + + if ts.Height() == 0 { + // NB: This is here because the process that executes blocks requires that the + // block miner reference a valid miner in the state tree. Unless we create some + // magical genesis miner, this won't work properly, so we short circuit here + // This avoids the question of 'who gets paid the genesis block reward' + return ts.Blocks()[0].ParentStateRoot, ts.Blocks()[0].ParentMessageReceipts, nil + } + + st, rec, err = sm.computeTipSetState(ctx, ts, sm.tsExecMonitor) + if err != nil { + return cid.Undef, cid.Undef, err + } + + return st, rec, nil +} + +func (sm *StateManager) ExecutionTraceWithMonitor(ctx context.Context, ts *types.TipSet, em ExecMonitor) (cid.Cid, error) { + st, _, err := sm.computeTipSetState(ctx, ts, em) + return st, err +} + +func (sm *StateManager) ExecutionTrace(ctx context.Context, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error) { + var invocTrace []*api.InvocResult + st, err := sm.ExecutionTraceWithMonitor(ctx, ts, &InvocationTracer{trace: &invocTrace}) + if err != nil { + return cid.Undef, nil, err + } + return st, invocTrace, nil +} + +func (sm *StateManager) computeTipSetState(ctx context.Context, ts *types.TipSet, em ExecMonitor) (cid.Cid, cid.Cid, error) { + ctx, span := trace.StartSpan(ctx, "computeTipSetState") + defer span.End() + + blks := ts.Blocks() + + for i := 0; i < len(blks); i++ { + for j := i + 1; j < len(blks); j++ { + if blks[i].Miner == blks[j].Miner { + return cid.Undef, cid.Undef, + xerrors.Errorf("duplicate miner in a tipset (%s %s)", + blks[i].Miner, blks[j].Miner) + } + } + } + + var parentEpoch abi.ChainEpoch + pstate := blks[0].ParentStateRoot + if blks[0].Height > 0 { + parent, err := sm.cs.GetBlock(blks[0].Parents[0]) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("getting parent block: %w", err) + } + + parentEpoch = parent.Height + } + + r := store.NewChainRand(sm.cs, ts.Cids()) + + blkmsgs, err := sm.cs.BlockMsgsForTipset(ts) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("getting block messages for tipset: %w", err) + } + + baseFee := blks[0].ParentBaseFee + + return sm.ApplyBlocks(ctx, parentEpoch, pstate, blkmsgs, blks[0].Height, r, em, baseFee, ts) +} diff --git a/chain/stmgr/forks_test.go b/chain/stmgr/forks_test.go index 9caeee51f..0df6ce397 100644 --- a/chain/stmgr/forks_test.go +++ b/chain/stmgr/forks_test.go @@ -121,7 +121,7 @@ func TestForkHeightTriggers(t *testing.T) { } sm, err := NewStateManagerWithUpgradeSchedule( - cg.ChainStore(), UpgradeSchedule{{ + cg.ChainStore(), cg.StateManager().VMSys(), UpgradeSchedule{{ Network: network.Version1, Height: testForkHeight, Migration: func(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, @@ -250,7 +250,7 @@ func TestForkRefuseCall(t *testing.T) { } sm, err := NewStateManagerWithUpgradeSchedule( - cg.ChainStore(), UpgradeSchedule{{ + cg.ChainStore(), cg.StateManager().VMSys(), UpgradeSchedule{{ Network: network.Version1, Expensive: true, Height: testForkHeight, @@ -365,7 +365,7 @@ func TestForkPreMigration(t *testing.T) { counter := make(chan struct{}, 10) sm, err := NewStateManagerWithUpgradeSchedule( - cg.ChainStore(), UpgradeSchedule{{ + cg.ChainStore(), cg.StateManager().VMSys(), UpgradeSchedule{{ Network: network.Version1, Height: testForkHeight, Migration: func(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, diff --git a/chain/stmgr/read.go b/chain/stmgr/read.go index 3c7fb5d91..bc259f227 100644 --- a/chain/stmgr/read.go +++ b/chain/stmgr/read.go @@ -31,6 +31,14 @@ func (sm *StateManager) ParentState(ts *types.TipSet) (*state.StateTree, error) return state, nil } +func (sm *StateManager) parentState(ts *types.TipSet) cid.Cid { + if ts == nil { + ts = sm.cs.GetHeaviestTipSet() + } + + return ts.ParentState() +} + func (sm *StateManager) StateTree(st cid.Cid) (*state.StateTree, error) { cst := cbor.NewCborStore(sm.cs.StateBlockstore()) state, err := state.LoadStateTree(cst, st) diff --git a/chain/stmgr/searchwait.go b/chain/stmgr/searchwait.go new file mode 100644 index 000000000..518180824 --- /dev/null +++ b/chain/stmgr/searchwait.go @@ -0,0 +1,279 @@ +package stmgr + +import ( + "context" + "errors" + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" +) + +// WaitForMessage blocks until a message appears on chain. It looks backwards in the chain to see if this has already +// happened, with an optional limit to how many epochs it will search. It guarantees that the message has been on +// chain for at least confidence epochs without being reverted before returning. +func (sm *StateManager) WaitForMessage(ctx context.Context, mcid cid.Cid, confidence uint64, lookbackLimit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + msg, err := sm.cs.GetCMessage(mcid) + if err != nil { + return nil, nil, cid.Undef, fmt.Errorf("failed to load message: %w", err) + } + + tsub := sm.cs.SubHeadChanges(ctx) + + head, ok := <-tsub + if !ok { + return nil, nil, cid.Undef, fmt.Errorf("SubHeadChanges stream was invalid") + } + + if len(head) != 1 { + return nil, nil, cid.Undef, fmt.Errorf("SubHeadChanges first entry should have been one item") + } + + if head[0].Type != store.HCCurrent { + return nil, nil, cid.Undef, fmt.Errorf("expected current head on SHC stream (got %s)", head[0].Type) + } + + r, foundMsg, err := sm.tipsetExecutedMessage(head[0].Val, mcid, msg.VMMessage(), allowReplaced) + if err != nil { + return nil, nil, cid.Undef, err + } + + if r != nil { + return head[0].Val, r, foundMsg, nil + } + + var backTs *types.TipSet + var backRcp *types.MessageReceipt + var backFm cid.Cid + backSearchWait := make(chan struct{}) + go func() { + fts, r, foundMsg, err := sm.searchBackForMsg(ctx, head[0].Val, msg, lookbackLimit, allowReplaced) + if err != nil { + log.Warnf("failed to look back through chain for message: %v", err) + return + } + + backTs = fts + backRcp = r + backFm = foundMsg + close(backSearchWait) + }() + + var candidateTs *types.TipSet + var candidateRcp *types.MessageReceipt + var candidateFm cid.Cid + heightOfHead := head[0].Val.Height() + reverts := map[types.TipSetKey]bool{} + + for { + select { + case notif, ok := <-tsub: + if !ok { + return nil, nil, cid.Undef, ctx.Err() + } + for _, val := range notif { + switch val.Type { + case store.HCRevert: + if val.Val.Equals(candidateTs) { + candidateTs = nil + candidateRcp = nil + candidateFm = cid.Undef + } + if backSearchWait != nil { + reverts[val.Val.Key()] = true + } + case store.HCApply: + if candidateTs != nil && val.Val.Height() >= candidateTs.Height()+abi.ChainEpoch(confidence) { + return candidateTs, candidateRcp, candidateFm, nil + } + r, foundMsg, err := sm.tipsetExecutedMessage(val.Val, mcid, msg.VMMessage(), allowReplaced) + if err != nil { + return nil, nil, cid.Undef, err + } + if r != nil { + if confidence == 0 { + return val.Val, r, foundMsg, err + } + candidateTs = val.Val + candidateRcp = r + candidateFm = foundMsg + } + heightOfHead = val.Val.Height() + } + } + case <-backSearchWait: + // check if we found the message in the chain and that is hasn't been reverted since we started searching + if backTs != nil && !reverts[backTs.Key()] { + // if head is at or past confidence interval, return immediately + if heightOfHead >= backTs.Height()+abi.ChainEpoch(confidence) { + return backTs, backRcp, backFm, nil + } + + // wait for confidence interval + candidateTs = backTs + candidateRcp = backRcp + candidateFm = backFm + } + reverts = nil + backSearchWait = nil + case <-ctx.Done(): + return nil, nil, cid.Undef, ctx.Err() + } + } +} + +func (sm *StateManager) SearchForMessage(ctx context.Context, head *types.TipSet, mcid cid.Cid, lookbackLimit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { + msg, err := sm.cs.GetCMessage(mcid) + if err != nil { + return nil, nil, cid.Undef, fmt.Errorf("failed to load message: %w", err) + } + + r, foundMsg, err := sm.tipsetExecutedMessage(head, mcid, msg.VMMessage(), allowReplaced) + if err != nil { + return nil, nil, cid.Undef, err + } + + if r != nil { + return head, r, foundMsg, nil + } + + fts, r, foundMsg, err := sm.searchBackForMsg(ctx, head, msg, lookbackLimit, allowReplaced) + + if err != nil { + log.Warnf("failed to look back through chain for message %s", mcid) + return nil, nil, cid.Undef, err + } + + if fts == nil { + return nil, nil, cid.Undef, nil + } + + return fts, r, foundMsg, nil +} + +// searchBackForMsg searches up to limit tipsets backwards from the given +// tipset for a message receipt. +// If limit is +// - 0 then no tipsets are searched +// - 5 then five tipset are searched +// - LookbackNoLimit then there is no limit +func (sm *StateManager) searchBackForMsg(ctx context.Context, from *types.TipSet, m types.ChainMsg, limit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { + limitHeight := from.Height() - limit + noLimit := limit == LookbackNoLimit + + cur := from + curActor, err := sm.LoadActor(ctx, m.VMMessage().From, cur) + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("failed to load initital tipset") + } + + mFromId, err := sm.LookupID(ctx, m.VMMessage().From, from) + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("looking up From id address: %w", err) + } + + mNonce := m.VMMessage().Nonce + + for { + // If we've reached the genesis block, or we've reached the limit of + // how far back to look + if cur.Height() == 0 || !noLimit && cur.Height() <= limitHeight { + // it ain't here! + return nil, nil, cid.Undef, nil + } + + select { + case <-ctx.Done(): + return nil, nil, cid.Undef, nil + default: + } + + // we either have no messages from the sender, or the latest message we found has a lower nonce than the one being searched for, + // either way, no reason to lookback, it ain't there + if curActor == nil || curActor.Nonce == 0 || curActor.Nonce < mNonce { + return nil, nil, cid.Undef, nil + } + + pts, err := sm.cs.LoadTipSet(cur.Parents()) + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("failed to load tipset during msg wait searchback: %w", err) + } + + act, err := sm.LoadActor(ctx, mFromId, pts) + actorNoExist := errors.Is(err, types.ErrActorNotFound) + if err != nil && !actorNoExist { + return nil, nil, cid.Cid{}, xerrors.Errorf("failed to load the actor: %w", err) + } + + // check that between cur and parent tipset the nonce fell into range of our message + if actorNoExist || (curActor.Nonce > mNonce && act.Nonce <= mNonce) { + r, foundMsg, err := sm.tipsetExecutedMessage(cur, m.Cid(), m.VMMessage(), allowReplaced) + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("checking for message execution during lookback: %w", err) + } + + if r != nil { + return cur, r, foundMsg, nil + } + } + + cur = pts + curActor = act + } +} + +func (sm *StateManager) tipsetExecutedMessage(ts *types.TipSet, msg cid.Cid, vmm *types.Message, allowReplaced bool) (*types.MessageReceipt, cid.Cid, error) { + // The genesis block did not execute any messages + if ts.Height() == 0 { + return nil, cid.Undef, nil + } + + pts, err := sm.cs.LoadTipSet(ts.Parents()) + if err != nil { + return nil, cid.Undef, err + } + + cm, err := sm.cs.MessagesForTipset(pts) + if err != nil { + return nil, cid.Undef, err + } + + for ii := range cm { + // iterate in reverse because we going backwards through the chain + i := len(cm) - ii - 1 + m := cm[i] + + if m.VMMessage().From == vmm.From { // cheaper to just check origin first + if m.VMMessage().Nonce == vmm.Nonce { + if allowReplaced && m.VMMessage().EqualCall(vmm) { + if m.Cid() != msg { + log.Warnw("found message with equal nonce and call params but different CID", + "wanted", msg, "found", m.Cid(), "nonce", vmm.Nonce, "from", vmm.From) + } + + pr, err := sm.cs.GetParentReceipt(ts.Blocks()[0], i) + if err != nil { + return nil, cid.Undef, err + } + return pr, m.Cid(), nil + } + + // this should be that message + return nil, cid.Undef, xerrors.Errorf("found message with equal nonce as the one we are looking for (F:%s n %d, TS: %s n%d)", + msg, vmm.Nonce, m.Cid(), m.VMMessage().Nonce) + } + if m.VMMessage().Nonce < vmm.Nonce { + return nil, cid.Undef, nil // don't bother looking further + } + } + } + + return nil, cid.Undef, nil +} diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index 4f1351d2c..1748c341e 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -2,52 +2,30 @@ package stmgr import ( "context" - "errors" "fmt" "sync" - "sync/atomic" - - "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" logging "github.com/ipfs/go-log/v2" - cbg "github.com/whyrusleeping/cbor-gen" - "go.opencensus.io/stats" - "go.opencensus.io/trace" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/network" // Used for genesis. msig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" "github.com/filecoin-project/specs-actors/v3/actors/migration/nv10" - // we use the same adt for all receipts - blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" - "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/actors/adt" - "github.com/filecoin-project/lotus/chain/actors/builtin" - "github.com/filecoin-project/lotus/chain/actors/builtin/cron" - _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" - "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" "github.com/filecoin-project/lotus/chain/actors/builtin/paych" - "github.com/filecoin-project/lotus/chain/actors/builtin/power" - "github.com/filecoin-project/lotus/chain/actors/builtin/reward" - "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" - "github.com/filecoin-project/lotus/metrics" ) const LookbackNoLimit = api.LookbackNoLimit @@ -97,6 +75,7 @@ type StateManager struct { stlk sync.Mutex genesisMsigLk sync.Mutex newVM func(context.Context, *vm.VMOpts) (*vm.VM, error) + syscalls vm.SyscallBuilder preIgnitionVesting []msig0.State postIgnitionVesting []msig0.State postCalicoVesting []msig0.State @@ -113,15 +92,15 @@ type treeCache struct { tree *state.StateTree } -func NewStateManager(cs *store.ChainStore) *StateManager { - sm, err := NewStateManagerWithUpgradeSchedule(cs, DefaultUpgradeSchedule()) +func NewStateManager(cs *store.ChainStore, sys vm.SyscallBuilder) *StateManager { + sm, err := NewStateManagerWithUpgradeSchedule(cs, sys, DefaultUpgradeSchedule()) if err != nil { panic(fmt.Sprintf("default upgrade schedule is invalid: %s", err)) } return sm } -func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule) (*StateManager, error) { +func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, sys vm.SyscallBuilder, us UpgradeSchedule) (*StateManager, error) { // If we have upgrades, make sure they're in-order and make sense. if err := us.Validate(); err != nil { return nil, err @@ -163,6 +142,7 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule stateMigrations: stateMigrations, expensiveUpgrades: expensiveUpgrades, newVM: vm.NewVM, + syscalls: sys, cs: cs, stCache: make(map[string][]cid.Cid), tCache: treeCache{ @@ -173,8 +153,8 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule }, nil } -func NewStateManagerWithUpgradeScheduleAndMonitor(cs *store.ChainStore, us UpgradeSchedule, em ExecMonitor) (*StateManager, error) { - sm, err := NewStateManagerWithUpgradeSchedule(cs, us) +func NewStateManagerWithUpgradeScheduleAndMonitor(cs *store.ChainStore, sys vm.SyscallBuilder, us UpgradeSchedule, em ExecMonitor) (*StateManager, error) { + sm, err := NewStateManagerWithUpgradeSchedule(cs, sys, us) if err != nil { return nil, err } @@ -217,312 +197,6 @@ func (sm *StateManager) Stop(ctx context.Context) error { return nil } -func (sm *StateManager) TipSetState(ctx context.Context, ts *types.TipSet) (st cid.Cid, rec cid.Cid, err error) { - ctx, span := trace.StartSpan(ctx, "tipSetState") - defer span.End() - if span.IsRecordingEvents() { - span.AddAttributes(trace.StringAttribute("tipset", fmt.Sprint(ts.Cids()))) - } - - ck := cidsToKey(ts.Cids()) - sm.stlk.Lock() - cw, cwok := sm.compWait[ck] - if cwok { - sm.stlk.Unlock() - span.AddAttributes(trace.BoolAttribute("waited", true)) - select { - case <-cw: - sm.stlk.Lock() - case <-ctx.Done(): - return cid.Undef, cid.Undef, ctx.Err() - } - } - cached, ok := sm.stCache[ck] - if ok { - sm.stlk.Unlock() - span.AddAttributes(trace.BoolAttribute("cache", true)) - return cached[0], cached[1], nil - } - ch := make(chan struct{}) - sm.compWait[ck] = ch - - defer func() { - sm.stlk.Lock() - delete(sm.compWait, ck) - if st != cid.Undef { - sm.stCache[ck] = []cid.Cid{st, rec} - } - sm.stlk.Unlock() - close(ch) - }() - - sm.stlk.Unlock() - - if ts.Height() == 0 { - // NB: This is here because the process that executes blocks requires that the - // block miner reference a valid miner in the state tree. Unless we create some - // magical genesis miner, this won't work properly, so we short circuit here - // This avoids the question of 'who gets paid the genesis block reward' - return ts.Blocks()[0].ParentStateRoot, ts.Blocks()[0].ParentMessageReceipts, nil - } - - st, rec, err = sm.computeTipSetState(ctx, ts, sm.tsExecMonitor) - if err != nil { - return cid.Undef, cid.Undef, err - } - - return st, rec, nil -} - -func (sm *StateManager) ExecutionTraceWithMonitor(ctx context.Context, ts *types.TipSet, em ExecMonitor) (cid.Cid, error) { - st, _, err := sm.computeTipSetState(ctx, ts, em) - return st, err -} - -func (sm *StateManager) ExecutionTrace(ctx context.Context, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error) { - var invocTrace []*api.InvocResult - st, err := sm.ExecutionTraceWithMonitor(ctx, ts, &InvocationTracer{trace: &invocTrace}) - if err != nil { - return cid.Undef, nil, err - } - return st, invocTrace, nil -} - -func (sm *StateManager) ApplyBlocks(ctx context.Context, parentEpoch abi.ChainEpoch, pstate cid.Cid, bms []store.BlockMessages, epoch abi.ChainEpoch, r vm.Rand, em ExecMonitor, baseFee abi.TokenAmount, ts *types.TipSet) (cid.Cid, cid.Cid, error) { - done := metrics.Timer(ctx, metrics.VMApplyBlocksTotal) - defer done() - - partDone := metrics.Timer(ctx, metrics.VMApplyEarly) - defer func() { - partDone() - }() - - makeVmWithBaseState := func(base cid.Cid) (*vm.VM, error) { - vmopt := &vm.VMOpts{ - StateBase: base, - Epoch: epoch, - Rand: r, - Bstore: sm.cs.StateBlockstore(), - Syscalls: sm.cs.VMSys(), - CircSupplyCalc: sm.GetVMCirculatingSupply, - NtwkVersion: sm.GetNtwkVersion, - BaseFee: baseFee, - LookbackState: LookbackStateGetterForTipset(sm, ts), - } - - return sm.newVM(ctx, vmopt) - } - - vmi, err := makeVmWithBaseState(pstate) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("making vm: %w", err) - } - - runCron := func(epoch abi.ChainEpoch) error { - cronMsg := &types.Message{ - To: cron.Address, - From: builtin.SystemActorAddr, - Nonce: uint64(epoch), - Value: types.NewInt(0), - GasFeeCap: types.NewInt(0), - GasPremium: types.NewInt(0), - GasLimit: build.BlockGasLimit * 10000, // Make super sure this is never too little - Method: cron.Methods.EpochTick, - Params: nil, - } - ret, err := vmi.ApplyImplicitMessage(ctx, cronMsg) - if err != nil { - return err - } - if em != nil { - if err := em.MessageApplied(ctx, ts, cronMsg.Cid(), cronMsg, ret, true); err != nil { - return xerrors.Errorf("callback failed on cron message: %w", err) - } - } - if ret.ExitCode != 0 { - return xerrors.Errorf("CheckProofSubmissions exit was non-zero: %d", ret.ExitCode) - } - - return nil - } - - for i := parentEpoch; i < epoch; i++ { - if i > parentEpoch { - // run cron for null rounds if any - if err := runCron(i); err != nil { - return cid.Undef, cid.Undef, err - } - - pstate, err = vmi.Flush(ctx) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("flushing vm: %w", err) - } - } - - // handle state forks - // XXX: The state tree - newState, err := sm.handleStateForks(ctx, pstate, i, em, ts) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("error handling state forks: %w", err) - } - - if pstate != newState { - vmi, err = makeVmWithBaseState(newState) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("making vm: %w", err) - } - } - - vmi.SetBlockHeight(i + 1) - pstate = newState - } - - partDone() - partDone = metrics.Timer(ctx, metrics.VMApplyMessages) - - var receipts []cbg.CBORMarshaler - processedMsgs := make(map[cid.Cid]struct{}) - for _, b := range bms { - penalty := types.NewInt(0) - gasReward := big.Zero() - - for _, cm := range append(b.BlsMessages, b.SecpkMessages...) { - m := cm.VMMessage() - if _, found := processedMsgs[m.Cid()]; found { - continue - } - r, err := vmi.ApplyMessage(ctx, cm) - if err != nil { - return cid.Undef, cid.Undef, err - } - - receipts = append(receipts, &r.MessageReceipt) - gasReward = big.Add(gasReward, r.GasCosts.MinerTip) - penalty = big.Add(penalty, r.GasCosts.MinerPenalty) - - if em != nil { - if err := em.MessageApplied(ctx, ts, cm.Cid(), m, r, false); err != nil { - return cid.Undef, cid.Undef, err - } - } - processedMsgs[m.Cid()] = struct{}{} - } - - params, err := actors.SerializeParams(&reward.AwardBlockRewardParams{ - Miner: b.Miner, - Penalty: penalty, - GasReward: gasReward, - WinCount: b.WinCount, - }) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to serialize award params: %w", err) - } - - rwMsg := &types.Message{ - From: builtin.SystemActorAddr, - To: reward.Address, - Nonce: uint64(epoch), - Value: types.NewInt(0), - GasFeeCap: types.NewInt(0), - GasPremium: types.NewInt(0), - GasLimit: 1 << 30, - Method: reward.Methods.AwardBlockReward, - Params: params, - } - ret, actErr := vmi.ApplyImplicitMessage(ctx, rwMsg) - if actErr != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to apply reward message for miner %s: %w", b.Miner, actErr) - } - if em != nil { - if err := em.MessageApplied(ctx, ts, rwMsg.Cid(), rwMsg, ret, true); err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("callback failed on reward message: %w", err) - } - } - - if ret.ExitCode != 0 { - return cid.Undef, cid.Undef, xerrors.Errorf("reward application message failed (exit %d): %s", ret.ExitCode, ret.ActorErr) - } - } - - partDone() - partDone = metrics.Timer(ctx, metrics.VMApplyCron) - - if err := runCron(epoch); err != nil { - return cid.Cid{}, cid.Cid{}, err - } - - partDone() - partDone = metrics.Timer(ctx, metrics.VMApplyFlush) - - rectarr := blockadt.MakeEmptyArray(sm.cs.ActorStore(ctx)) - for i, receipt := range receipts { - if err := rectarr.Set(uint64(i), receipt); err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to build receipts amt: %w", err) - } - } - rectroot, err := rectarr.Root() - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to build receipts amt: %w", err) - } - - st, err := vmi.Flush(ctx) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("vm flush failed: %w", err) - } - - stats.Record(ctx, metrics.VMSends.M(int64(atomic.LoadUint64(&vm.StatSends))), - metrics.VMApplied.M(int64(atomic.LoadUint64(&vm.StatApplied)))) - - return st, rectroot, nil -} - -func (sm *StateManager) computeTipSetState(ctx context.Context, ts *types.TipSet, em ExecMonitor) (cid.Cid, cid.Cid, error) { - ctx, span := trace.StartSpan(ctx, "computeTipSetState") - defer span.End() - - blks := ts.Blocks() - - for i := 0; i < len(blks); i++ { - for j := i + 1; j < len(blks); j++ { - if blks[i].Miner == blks[j].Miner { - return cid.Undef, cid.Undef, - xerrors.Errorf("duplicate miner in a tipset (%s %s)", - blks[i].Miner, blks[j].Miner) - } - } - } - - var parentEpoch abi.ChainEpoch - pstate := blks[0].ParentStateRoot - if blks[0].Height > 0 { - parent, err := sm.cs.GetBlock(blks[0].Parents[0]) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("getting parent block: %w", err) - } - - parentEpoch = parent.Height - } - - r := store.NewChainRand(sm.cs, ts.Cids()) - - blkmsgs, err := sm.cs.BlockMsgsForTipset(ts) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("getting block messages for tipset: %w", err) - } - - baseFee := blks[0].ParentBaseFee - - return sm.ApplyBlocks(ctx, parentEpoch, pstate, blkmsgs, blks[0].Height, r, em, baseFee, ts) -} - -func (sm *StateManager) parentState(ts *types.TipSet) cid.Cid { - if ts == nil { - ts = sm.cs.GetHeaviestTipSet() - } - - return ts.ParentState() -} - func (sm *StateManager) ChainStore() *store.ChainStore { return sm.cs } @@ -547,7 +221,7 @@ func (sm *StateManager) ResolveToKeyAddress(ctx context.Context, addr address.Ad // First try to resolve the actor in the parent state, so we don't have to compute anything. tree, err := state.LoadStateTree(cst, ts.ParentState()) if err != nil { - return address.Undef, xerrors.Errorf("failed to load parent state tree: %w", err) + return address.Undef, xerrors.Errorf("failed to load parent state tree at tipset %s: %w", ts.Parents(), err) } resolved, err := vm.ResolveToKeyAddr(tree, cst, addr) @@ -558,12 +232,12 @@ func (sm *StateManager) ResolveToKeyAddress(ctx context.Context, addr address.Ad // If that fails, compute the tip-set and try again. st, _, err := sm.TipSetState(ctx, ts) if err != nil { - return address.Undef, xerrors.Errorf("resolve address failed to get tipset state: %w", err) + return address.Undef, xerrors.Errorf("resolve address failed to get tipset %s state: %w", ts, err) } tree, err = state.LoadStateTree(cst, st) if err != nil { - return address.Undef, xerrors.Errorf("failed to load state tree") + return address.Undef, xerrors.Errorf("failed to load state tree at tipset %s: %w", ts, err) } return vm.ResolveToKeyAddr(tree, cst, addr) @@ -637,338 +311,6 @@ func (sm *StateManager) LookupID(ctx context.Context, addr address.Address, ts * return state.LookupID(addr) } -// WaitForMessage blocks until a message appears on chain. It looks backwards in the chain to see if this has already -// happened, with an optional limit to how many epochs it will search. It guarantees that the message has been on -// chain for at least confidence epochs without being reverted before returning. -func (sm *StateManager) WaitForMessage(ctx context.Context, mcid cid.Cid, confidence uint64, lookbackLimit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - msg, err := sm.cs.GetCMessage(mcid) - if err != nil { - return nil, nil, cid.Undef, fmt.Errorf("failed to load message: %w", err) - } - - tsub := sm.cs.SubHeadChanges(ctx) - - head, ok := <-tsub - if !ok { - return nil, nil, cid.Undef, fmt.Errorf("SubHeadChanges stream was invalid") - } - - if len(head) != 1 { - return nil, nil, cid.Undef, fmt.Errorf("SubHeadChanges first entry should have been one item") - } - - if head[0].Type != store.HCCurrent { - return nil, nil, cid.Undef, fmt.Errorf("expected current head on SHC stream (got %s)", head[0].Type) - } - - r, foundMsg, err := sm.tipsetExecutedMessage(head[0].Val, mcid, msg.VMMessage(), allowReplaced) - if err != nil { - return nil, nil, cid.Undef, err - } - - if r != nil { - return head[0].Val, r, foundMsg, nil - } - - var backTs *types.TipSet - var backRcp *types.MessageReceipt - var backFm cid.Cid - backSearchWait := make(chan struct{}) - go func() { - fts, r, foundMsg, err := sm.searchBackForMsg(ctx, head[0].Val, msg, lookbackLimit, allowReplaced) - if err != nil { - log.Warnf("failed to look back through chain for message: %v", err) - return - } - - backTs = fts - backRcp = r - backFm = foundMsg - close(backSearchWait) - }() - - var candidateTs *types.TipSet - var candidateRcp *types.MessageReceipt - var candidateFm cid.Cid - heightOfHead := head[0].Val.Height() - reverts := map[types.TipSetKey]bool{} - - for { - select { - case notif, ok := <-tsub: - if !ok { - return nil, nil, cid.Undef, ctx.Err() - } - for _, val := range notif { - switch val.Type { - case store.HCRevert: - if val.Val.Equals(candidateTs) { - candidateTs = nil - candidateRcp = nil - candidateFm = cid.Undef - } - if backSearchWait != nil { - reverts[val.Val.Key()] = true - } - case store.HCApply: - if candidateTs != nil && val.Val.Height() >= candidateTs.Height()+abi.ChainEpoch(confidence) { - return candidateTs, candidateRcp, candidateFm, nil - } - r, foundMsg, err := sm.tipsetExecutedMessage(val.Val, mcid, msg.VMMessage(), allowReplaced) - if err != nil { - return nil, nil, cid.Undef, err - } - if r != nil { - if confidence == 0 { - return val.Val, r, foundMsg, err - } - candidateTs = val.Val - candidateRcp = r - candidateFm = foundMsg - } - heightOfHead = val.Val.Height() - } - } - case <-backSearchWait: - // check if we found the message in the chain and that is hasn't been reverted since we started searching - if backTs != nil && !reverts[backTs.Key()] { - // if head is at or past confidence interval, return immediately - if heightOfHead >= backTs.Height()+abi.ChainEpoch(confidence) { - return backTs, backRcp, backFm, nil - } - - // wait for confidence interval - candidateTs = backTs - candidateRcp = backRcp - candidateFm = backFm - } - reverts = nil - backSearchWait = nil - case <-ctx.Done(): - return nil, nil, cid.Undef, ctx.Err() - } - } -} - -func (sm *StateManager) SearchForMessage(ctx context.Context, head *types.TipSet, mcid cid.Cid, lookbackLimit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { - msg, err := sm.cs.GetCMessage(mcid) - if err != nil { - return nil, nil, cid.Undef, fmt.Errorf("failed to load message: %w", err) - } - - r, foundMsg, err := sm.tipsetExecutedMessage(head, mcid, msg.VMMessage(), allowReplaced) - if err != nil { - return nil, nil, cid.Undef, err - } - - if r != nil { - return head, r, foundMsg, nil - } - - fts, r, foundMsg, err := sm.searchBackForMsg(ctx, head, msg, lookbackLimit, allowReplaced) - - if err != nil { - log.Warnf("failed to look back through chain for message %s", mcid) - return nil, nil, cid.Undef, err - } - - if fts == nil { - return nil, nil, cid.Undef, nil - } - - return fts, r, foundMsg, nil -} - -// searchBackForMsg searches up to limit tipsets backwards from the given -// tipset for a message receipt. -// If limit is -// - 0 then no tipsets are searched -// - 5 then five tipset are searched -// - LookbackNoLimit then there is no limit -func (sm *StateManager) searchBackForMsg(ctx context.Context, from *types.TipSet, m types.ChainMsg, limit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { - limitHeight := from.Height() - limit - noLimit := limit == LookbackNoLimit - - cur := from - curActor, err := sm.LoadActor(ctx, m.VMMessage().From, cur) - if err != nil { - return nil, nil, cid.Undef, xerrors.Errorf("failed to load initital tipset") - } - - mFromId, err := sm.LookupID(ctx, m.VMMessage().From, from) - if err != nil { - return nil, nil, cid.Undef, xerrors.Errorf("looking up From id address: %w", err) - } - - mNonce := m.VMMessage().Nonce - - for { - // If we've reached the genesis block, or we've reached the limit of - // how far back to look - if cur.Height() == 0 || !noLimit && cur.Height() <= limitHeight { - // it ain't here! - return nil, nil, cid.Undef, nil - } - - select { - case <-ctx.Done(): - return nil, nil, cid.Undef, nil - default: - } - - // we either have no messages from the sender, or the latest message we found has a lower nonce than the one being searched for, - // either way, no reason to lookback, it ain't there - if curActor == nil || curActor.Nonce == 0 || curActor.Nonce < mNonce { - return nil, nil, cid.Undef, nil - } - - pts, err := sm.cs.LoadTipSet(cur.Parents()) - if err != nil { - return nil, nil, cid.Undef, xerrors.Errorf("failed to load tipset during msg wait searchback: %w", err) - } - - act, err := sm.LoadActor(ctx, mFromId, pts) - actorNoExist := errors.Is(err, types.ErrActorNotFound) - if err != nil && !actorNoExist { - return nil, nil, cid.Cid{}, xerrors.Errorf("failed to load the actor: %w", err) - } - - // check that between cur and parent tipset the nonce fell into range of our message - if actorNoExist || (curActor.Nonce > mNonce && act.Nonce <= mNonce) { - r, foundMsg, err := sm.tipsetExecutedMessage(cur, m.Cid(), m.VMMessage(), allowReplaced) - if err != nil { - return nil, nil, cid.Undef, xerrors.Errorf("checking for message execution during lookback: %w", err) - } - - if r != nil { - return cur, r, foundMsg, nil - } - } - - cur = pts - curActor = act - } -} - -func (sm *StateManager) tipsetExecutedMessage(ts *types.TipSet, msg cid.Cid, vmm *types.Message, allowReplaced bool) (*types.MessageReceipt, cid.Cid, error) { - // The genesis block did not execute any messages - if ts.Height() == 0 { - return nil, cid.Undef, nil - } - - pts, err := sm.cs.LoadTipSet(ts.Parents()) - if err != nil { - return nil, cid.Undef, err - } - - cm, err := sm.cs.MessagesForTipset(pts) - if err != nil { - return nil, cid.Undef, err - } - - for ii := range cm { - // iterate in reverse because we going backwards through the chain - i := len(cm) - ii - 1 - m := cm[i] - - if m.VMMessage().From == vmm.From { // cheaper to just check origin first - if m.VMMessage().Nonce == vmm.Nonce { - if allowReplaced && m.VMMessage().EqualCall(vmm) { - if m.Cid() != msg { - log.Warnw("found message with equal nonce and call params but different CID", - "wanted", msg, "found", m.Cid(), "nonce", vmm.Nonce, "from", vmm.From) - } - - pr, err := sm.cs.GetParentReceipt(ts.Blocks()[0], i) - if err != nil { - return nil, cid.Undef, err - } - return pr, m.Cid(), nil - } - - // this should be that message - return nil, cid.Undef, xerrors.Errorf("found message with equal nonce as the one we are looking for (F:%s n %d, TS: %s n%d)", - msg, vmm.Nonce, m.Cid(), m.VMMessage().Nonce) - } - if m.VMMessage().Nonce < vmm.Nonce { - return nil, cid.Undef, nil // don't bother looking further - } - } - } - - return nil, cid.Undef, nil -} - -func (sm *StateManager) ListAllActors(ctx context.Context, ts *types.TipSet) ([]address.Address, error) { - if ts == nil { - ts = sm.cs.GetHeaviestTipSet() - } - st := ts.ParentState() - - stateTree, err := sm.StateTree(st) - if err != nil { - return nil, err - } - - var out []address.Address - err = stateTree.ForEach(func(addr address.Address, act *types.Actor) error { - out = append(out, addr) - return nil - }) - if err != nil { - return nil, err - } - - return out, nil -} - -func (sm *StateManager) MarketBalance(ctx context.Context, addr address.Address, ts *types.TipSet) (api.MarketBalance, error) { - st, err := sm.ParentState(ts) - if err != nil { - return api.MarketBalance{}, err - } - - act, err := st.GetActor(market.Address) - if err != nil { - return api.MarketBalance{}, err - } - - mstate, err := market.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return api.MarketBalance{}, err - } - - addr, err = sm.LookupID(ctx, addr, ts) - if err != nil { - return api.MarketBalance{}, err - } - - var out api.MarketBalance - - et, err := mstate.EscrowTable() - if err != nil { - return api.MarketBalance{}, err - } - out.Escrow, err = et.Get(addr) - if err != nil { - return api.MarketBalance{}, xerrors.Errorf("getting escrow balance: %w", err) - } - - lt, err := mstate.LockedTable() - if err != nil { - return api.MarketBalance{}, err - } - out.Locked, err = lt.Get(addr) - if err != nil { - return api.MarketBalance{}, xerrors.Errorf("getting locked balance: %w", err) - } - - return out, nil -} - func (sm *StateManager) ValidateChain(ctx context.Context, ts *types.TipSet) error { tschain := []*types.TipSet{ts} for ts.Height() != 0 { @@ -1002,450 +344,6 @@ func (sm *StateManager) SetVMConstructor(nvm func(context.Context, *vm.VMOpts) ( sm.newVM = nvm } -// sets up information about the vesting schedule -func (sm *StateManager) setupGenesisVestingSchedule(ctx context.Context) error { - - gb, err := sm.cs.GetGenesis() - if err != nil { - return xerrors.Errorf("getting genesis block: %w", err) - } - - gts, err := types.NewTipSet([]*types.BlockHeader{gb}) - if err != nil { - return xerrors.Errorf("getting genesis tipset: %w", err) - } - - st, _, err := sm.TipSetState(ctx, gts) - if err != nil { - return xerrors.Errorf("getting genesis tipset state: %w", err) - } - - cst := cbor.NewCborStore(sm.cs.StateBlockstore()) - sTree, err := state.LoadStateTree(cst, st) - if err != nil { - return xerrors.Errorf("loading state tree: %w", err) - } - - gmf, err := getFilMarketLocked(ctx, sTree) - if err != nil { - return xerrors.Errorf("setting up genesis market funds: %w", err) - } - - gp, err := getFilPowerLocked(ctx, sTree) - if err != nil { - return xerrors.Errorf("setting up genesis pledge: %w", err) - } - - sm.genesisMarketFunds = gmf - sm.genesisPledge = gp - - totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount) - - // 6 months - sixMonths := abi.ChainEpoch(183 * builtin.EpochsInDay) - totalsByEpoch[sixMonths] = big.NewInt(49_929_341) - totalsByEpoch[sixMonths] = big.Add(totalsByEpoch[sixMonths], big.NewInt(32_787_700)) - - // 1 year - oneYear := abi.ChainEpoch(365 * builtin.EpochsInDay) - totalsByEpoch[oneYear] = big.NewInt(22_421_712) - - // 2 years - twoYears := abi.ChainEpoch(2 * 365 * builtin.EpochsInDay) - totalsByEpoch[twoYears] = big.NewInt(7_223_364) - - // 3 years - threeYears := abi.ChainEpoch(3 * 365 * builtin.EpochsInDay) - totalsByEpoch[threeYears] = big.NewInt(87_637_883) - - // 6 years - sixYears := abi.ChainEpoch(6 * 365 * builtin.EpochsInDay) - totalsByEpoch[sixYears] = big.NewInt(100_000_000) - totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(300_000_000)) - - sm.preIgnitionVesting = make([]msig0.State, 0, len(totalsByEpoch)) - for k, v := range totalsByEpoch { - ns := msig0.State{ - InitialBalance: v, - UnlockDuration: k, - PendingTxns: cid.Undef, - } - sm.preIgnitionVesting = append(sm.preIgnitionVesting, ns) - } - - return nil -} - -// sets up information about the vesting schedule post the ignition upgrade -func (sm *StateManager) setupPostIgnitionVesting(ctx context.Context) error { - - totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount) - - // 6 months - sixMonths := abi.ChainEpoch(183 * builtin.EpochsInDay) - totalsByEpoch[sixMonths] = big.NewInt(49_929_341) - totalsByEpoch[sixMonths] = big.Add(totalsByEpoch[sixMonths], big.NewInt(32_787_700)) - - // 1 year - oneYear := abi.ChainEpoch(365 * builtin.EpochsInDay) - totalsByEpoch[oneYear] = big.NewInt(22_421_712) - - // 2 years - twoYears := abi.ChainEpoch(2 * 365 * builtin.EpochsInDay) - totalsByEpoch[twoYears] = big.NewInt(7_223_364) - - // 3 years - threeYears := abi.ChainEpoch(3 * 365 * builtin.EpochsInDay) - totalsByEpoch[threeYears] = big.NewInt(87_637_883) - - // 6 years - sixYears := abi.ChainEpoch(6 * 365 * builtin.EpochsInDay) - totalsByEpoch[sixYears] = big.NewInt(100_000_000) - totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(300_000_000)) - - sm.postIgnitionVesting = make([]msig0.State, 0, len(totalsByEpoch)) - for k, v := range totalsByEpoch { - ns := msig0.State{ - // In the pre-ignition logic, we incorrectly set this value in Fil, not attoFil, an off-by-10^18 error - InitialBalance: big.Mul(v, big.NewInt(int64(build.FilecoinPrecision))), - UnlockDuration: k, - PendingTxns: cid.Undef, - // In the pre-ignition logic, the start epoch was 0. This changes in the fork logic of the Ignition upgrade itself. - StartEpoch: build.UpgradeLiftoffHeight, - } - sm.postIgnitionVesting = append(sm.postIgnitionVesting, ns) - } - - return nil -} - -// sets up information about the vesting schedule post the calico upgrade -func (sm *StateManager) setupPostCalicoVesting(ctx context.Context) error { - - totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount) - - // 0 days - zeroDays := abi.ChainEpoch(0) - totalsByEpoch[zeroDays] = big.NewInt(10_632_000) - - // 6 months - sixMonths := abi.ChainEpoch(183 * builtin.EpochsInDay) - totalsByEpoch[sixMonths] = big.NewInt(19_015_887) - totalsByEpoch[sixMonths] = big.Add(totalsByEpoch[sixMonths], big.NewInt(32_787_700)) - - // 1 year - oneYear := abi.ChainEpoch(365 * builtin.EpochsInDay) - totalsByEpoch[oneYear] = big.NewInt(22_421_712) - totalsByEpoch[oneYear] = big.Add(totalsByEpoch[oneYear], big.NewInt(9_400_000)) - - // 2 years - twoYears := abi.ChainEpoch(2 * 365 * builtin.EpochsInDay) - totalsByEpoch[twoYears] = big.NewInt(7_223_364) - - // 3 years - threeYears := abi.ChainEpoch(3 * 365 * builtin.EpochsInDay) - totalsByEpoch[threeYears] = big.NewInt(87_637_883) - totalsByEpoch[threeYears] = big.Add(totalsByEpoch[threeYears], big.NewInt(898_958)) - - // 6 years - sixYears := abi.ChainEpoch(6 * 365 * builtin.EpochsInDay) - totalsByEpoch[sixYears] = big.NewInt(100_000_000) - totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(300_000_000)) - totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(9_805_053)) - - sm.postCalicoVesting = make([]msig0.State, 0, len(totalsByEpoch)) - for k, v := range totalsByEpoch { - ns := msig0.State{ - InitialBalance: big.Mul(v, big.NewInt(int64(build.FilecoinPrecision))), - UnlockDuration: k, - PendingTxns: cid.Undef, - StartEpoch: build.UpgradeLiftoffHeight, - } - sm.postCalicoVesting = append(sm.postCalicoVesting, ns) - } - - return nil -} - -// GetVestedFunds returns all funds that have "left" actors that are in the genesis state: -// - For Multisigs, it counts the actual amounts that have vested at the given epoch -// - For Accounts, it counts max(currentBalance - genesisBalance, 0). -func (sm *StateManager) GetFilVested(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { - vf := big.Zero() - if height <= build.UpgradeIgnitionHeight { - for _, v := range sm.preIgnitionVesting { - au := big.Sub(v.InitialBalance, v.AmountLocked(height)) - vf = big.Add(vf, au) - } - } else if height <= build.UpgradeCalicoHeight { - for _, v := range sm.postIgnitionVesting { - // In the pre-ignition logic, we simply called AmountLocked(height), assuming startEpoch was 0. - // The start epoch changed in the Ignition upgrade. - au := big.Sub(v.InitialBalance, v.AmountLocked(height-v.StartEpoch)) - vf = big.Add(vf, au) - } - } else { - for _, v := range sm.postCalicoVesting { - // In the pre-ignition logic, we simply called AmountLocked(height), assuming startEpoch was 0. - // The start epoch changed in the Ignition upgrade. - au := big.Sub(v.InitialBalance, v.AmountLocked(height-v.StartEpoch)) - vf = big.Add(vf, au) - } - } - - // After UpgradeAssemblyHeight these funds are accounted for in GetFilReserveDisbursed - if height <= build.UpgradeAssemblyHeight { - // continue to use preIgnitionGenInfos, nothing changed at the Ignition epoch - vf = big.Add(vf, sm.genesisPledge) - // continue to use preIgnitionGenInfos, nothing changed at the Ignition epoch - vf = big.Add(vf, sm.genesisMarketFunds) - } - - return vf, nil -} - -func GetFilReserveDisbursed(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { - ract, err := st.GetActor(builtin.ReserveAddress) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to get reserve actor: %w", err) - } - - // If money enters the reserve actor, this could lead to a negative term - return big.Sub(big.NewFromGo(build.InitialFilReserved), ract.Balance), nil -} - -func GetFilMined(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { - ractor, err := st.GetActor(reward.Address) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to load reward actor state: %w", err) - } - - rst, err := reward.Load(adt.WrapStore(ctx, st.Store), ractor) - if err != nil { - return big.Zero(), err - } - - return rst.TotalStoragePowerReward() -} - -func getFilMarketLocked(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { - act, err := st.GetActor(market.Address) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to load market actor: %w", err) - } - - mst, err := market.Load(adt.WrapStore(ctx, st.Store), act) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to load market state: %w", err) - } - - return mst.TotalLocked() -} - -func getFilPowerLocked(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { - pactor, err := st.GetActor(power.Address) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to load power actor: %w", err) - } - - pst, err := power.Load(adt.WrapStore(ctx, st.Store), pactor) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to load power state: %w", err) - } - - return pst.TotalLocked() -} - -func (sm *StateManager) GetFilLocked(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { - - filMarketLocked, err := getFilMarketLocked(ctx, st) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to get filMarketLocked: %w", err) - } - - filPowerLocked, err := getFilPowerLocked(ctx, st) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to get filPowerLocked: %w", err) - } - - return types.BigAdd(filMarketLocked, filPowerLocked), nil -} - -func GetFilBurnt(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { - burnt, err := st.GetActor(builtin.BurntFundsActorAddr) - if err != nil { - return big.Zero(), xerrors.Errorf("failed to load burnt actor: %w", err) - } - - return burnt.Balance, nil -} - -func (sm *StateManager) GetVMCirculatingSupply(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { - cs, err := sm.GetVMCirculatingSupplyDetailed(ctx, height, st) - if err != nil { - return types.EmptyInt, err - } - - return cs.FilCirculating, err -} - -func (sm *StateManager) GetVMCirculatingSupplyDetailed(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (api.CirculatingSupply, error) { - sm.genesisMsigLk.Lock() - defer sm.genesisMsigLk.Unlock() - if sm.preIgnitionVesting == nil || sm.genesisPledge.IsZero() || sm.genesisMarketFunds.IsZero() { - err := sm.setupGenesisVestingSchedule(ctx) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to setup pre-ignition vesting schedule: %w", err) - } - } - if sm.postIgnitionVesting == nil { - err := sm.setupPostIgnitionVesting(ctx) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to setup post-ignition vesting schedule: %w", err) - } - } - if sm.postCalicoVesting == nil { - err := sm.setupPostCalicoVesting(ctx) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to setup post-calico vesting schedule: %w", err) - } - } - - filVested, err := sm.GetFilVested(ctx, height, st) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filVested: %w", err) - } - - filReserveDisbursed := big.Zero() - if height > build.UpgradeAssemblyHeight { - filReserveDisbursed, err = GetFilReserveDisbursed(ctx, st) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filReserveDisbursed: %w", err) - } - } - - filMined, err := GetFilMined(ctx, st) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filMined: %w", err) - } - - filBurnt, err := GetFilBurnt(ctx, st) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filBurnt: %w", err) - } - - filLocked, err := sm.GetFilLocked(ctx, st) - if err != nil { - return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filLocked: %w", err) - } - - ret := types.BigAdd(filVested, filMined) - ret = types.BigAdd(ret, filReserveDisbursed) - ret = types.BigSub(ret, filBurnt) - ret = types.BigSub(ret, filLocked) - - if ret.LessThan(big.Zero()) { - ret = big.Zero() - } - - return api.CirculatingSupply{ - FilVested: filVested, - FilMined: filMined, - FilBurnt: filBurnt, - FilLocked: filLocked, - FilCirculating: ret, - FilReserveDisbursed: filReserveDisbursed, - }, nil -} - -func (sm *StateManager) GetCirculatingSupply(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { - circ := big.Zero() - unCirc := big.Zero() - err := st.ForEach(func(a address.Address, actor *types.Actor) error { - switch { - case actor.Balance.IsZero(): - // Do nothing for zero-balance actors - break - case a == _init.Address || - a == reward.Address || - a == verifreg.Address || - // The power actor itself should never receive funds - a == power.Address || - a == builtin.SystemActorAddr || - a == builtin.CronActorAddr || - a == builtin.BurntFundsActorAddr || - a == builtin.SaftAddress || - a == builtin.ReserveAddress: - - unCirc = big.Add(unCirc, actor.Balance) - - case a == market.Address: - mst, err := market.Load(sm.cs.ActorStore(ctx), actor) - if err != nil { - return err - } - - lb, err := mst.TotalLocked() - if err != nil { - return err - } - - circ = big.Add(circ, big.Sub(actor.Balance, lb)) - unCirc = big.Add(unCirc, lb) - - case builtin.IsAccountActor(actor.Code) || builtin.IsPaymentChannelActor(actor.Code): - circ = big.Add(circ, actor.Balance) - - case builtin.IsStorageMinerActor(actor.Code): - mst, err := miner.Load(sm.cs.ActorStore(ctx), actor) - if err != nil { - return err - } - - ab, err := mst.AvailableBalance(actor.Balance) - - if err == nil { - circ = big.Add(circ, ab) - unCirc = big.Add(unCirc, big.Sub(actor.Balance, ab)) - } else { - // Assume any error is because the miner state is "broken" (lower actor balance than locked funds) - // In this case, the actor's entire balance is considered "uncirculating" - unCirc = big.Add(unCirc, actor.Balance) - } - - case builtin.IsMultisigActor(actor.Code): - mst, err := multisig.Load(sm.cs.ActorStore(ctx), actor) - if err != nil { - return err - } - - lb, err := mst.LockedBalance(height) - if err != nil { - return err - } - - ab := big.Sub(actor.Balance, lb) - circ = big.Add(circ, big.Max(ab, big.Zero())) - unCirc = big.Add(unCirc, big.Min(actor.Balance, lb)) - default: - return xerrors.Errorf("unexpected actor: %s", a) - } - - return nil - }) - - if err != nil { - return types.EmptyInt, err - } - - total := big.Add(circ, unCirc) - if !total.Equals(types.TotalFilecoinInt) { - return types.EmptyInt, xerrors.Errorf("total filecoin didn't add to expected amount: %s != %s", total, types.TotalFilecoinInt) - } - - return circ, nil -} - func (sm *StateManager) GetNtwkVersion(ctx context.Context, height abi.ChainEpoch) network.Version { // The epochs here are the _last_ epoch for every version, or -1 if the // version is disabled. @@ -1457,40 +355,6 @@ func (sm *StateManager) GetNtwkVersion(ctx context.Context, height abi.ChainEpoc return sm.latestVersion } -func (sm *StateManager) GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error) { - st, err := sm.ParentState(ts) - if err != nil { - return nil, nil, err - } - - act, err := st.GetActor(addr) - if err != nil { - return nil, nil, err - } - - actState, err := paych.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, nil, err - } - return act, actState, nil +func (sm *StateManager) VMSys() vm.SyscallBuilder { + return sm.syscalls } - -func (sm *StateManager) GetMarketState(ctx context.Context, ts *types.TipSet) (market.State, error) { - st, err := sm.ParentState(ts) - if err != nil { - return nil, err - } - - act, err := st.GetActor(market.Address) - if err != nil { - return nil, err - } - - actState, err := market.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, err - } - return actState, nil -} - -var _ StateManagerAPI = (*StateManager)(nil) diff --git a/chain/stmgr/supply.go b/chain/stmgr/supply.go new file mode 100644 index 000000000..c9475a51e --- /dev/null +++ b/chain/stmgr/supply.go @@ -0,0 +1,473 @@ +package stmgr + +import ( + "context" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + + msig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/actors/builtin/reward" + "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/types" +) + +// sets up information about the vesting schedule +func (sm *StateManager) setupGenesisVestingSchedule(ctx context.Context) error { + + gb, err := sm.cs.GetGenesis() + if err != nil { + return xerrors.Errorf("getting genesis block: %w", err) + } + + gts, err := types.NewTipSet([]*types.BlockHeader{gb}) + if err != nil { + return xerrors.Errorf("getting genesis tipset: %w", err) + } + + st, _, err := sm.TipSetState(ctx, gts) + if err != nil { + return xerrors.Errorf("getting genesis tipset state: %w", err) + } + + cst := cbor.NewCborStore(sm.cs.StateBlockstore()) + sTree, err := state.LoadStateTree(cst, st) + if err != nil { + return xerrors.Errorf("loading state tree: %w", err) + } + + gmf, err := getFilMarketLocked(ctx, sTree) + if err != nil { + return xerrors.Errorf("setting up genesis market funds: %w", err) + } + + gp, err := getFilPowerLocked(ctx, sTree) + if err != nil { + return xerrors.Errorf("setting up genesis pledge: %w", err) + } + + sm.genesisMarketFunds = gmf + sm.genesisPledge = gp + + totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount) + + // 6 months + sixMonths := abi.ChainEpoch(183 * builtin.EpochsInDay) + totalsByEpoch[sixMonths] = big.NewInt(49_929_341) + totalsByEpoch[sixMonths] = big.Add(totalsByEpoch[sixMonths], big.NewInt(32_787_700)) + + // 1 year + oneYear := abi.ChainEpoch(365 * builtin.EpochsInDay) + totalsByEpoch[oneYear] = big.NewInt(22_421_712) + + // 2 years + twoYears := abi.ChainEpoch(2 * 365 * builtin.EpochsInDay) + totalsByEpoch[twoYears] = big.NewInt(7_223_364) + + // 3 years + threeYears := abi.ChainEpoch(3 * 365 * builtin.EpochsInDay) + totalsByEpoch[threeYears] = big.NewInt(87_637_883) + + // 6 years + sixYears := abi.ChainEpoch(6 * 365 * builtin.EpochsInDay) + totalsByEpoch[sixYears] = big.NewInt(100_000_000) + totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(300_000_000)) + + sm.preIgnitionVesting = make([]msig0.State, 0, len(totalsByEpoch)) + for k, v := range totalsByEpoch { + ns := msig0.State{ + InitialBalance: v, + UnlockDuration: k, + PendingTxns: cid.Undef, + } + sm.preIgnitionVesting = append(sm.preIgnitionVesting, ns) + } + + return nil +} + +// sets up information about the vesting schedule post the ignition upgrade +func (sm *StateManager) setupPostIgnitionVesting(ctx context.Context) error { + + totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount) + + // 6 months + sixMonths := abi.ChainEpoch(183 * builtin.EpochsInDay) + totalsByEpoch[sixMonths] = big.NewInt(49_929_341) + totalsByEpoch[sixMonths] = big.Add(totalsByEpoch[sixMonths], big.NewInt(32_787_700)) + + // 1 year + oneYear := abi.ChainEpoch(365 * builtin.EpochsInDay) + totalsByEpoch[oneYear] = big.NewInt(22_421_712) + + // 2 years + twoYears := abi.ChainEpoch(2 * 365 * builtin.EpochsInDay) + totalsByEpoch[twoYears] = big.NewInt(7_223_364) + + // 3 years + threeYears := abi.ChainEpoch(3 * 365 * builtin.EpochsInDay) + totalsByEpoch[threeYears] = big.NewInt(87_637_883) + + // 6 years + sixYears := abi.ChainEpoch(6 * 365 * builtin.EpochsInDay) + totalsByEpoch[sixYears] = big.NewInt(100_000_000) + totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(300_000_000)) + + sm.postIgnitionVesting = make([]msig0.State, 0, len(totalsByEpoch)) + for k, v := range totalsByEpoch { + ns := msig0.State{ + // In the pre-ignition logic, we incorrectly set this value in Fil, not attoFil, an off-by-10^18 error + InitialBalance: big.Mul(v, big.NewInt(int64(build.FilecoinPrecision))), + UnlockDuration: k, + PendingTxns: cid.Undef, + // In the pre-ignition logic, the start epoch was 0. This changes in the fork logic of the Ignition upgrade itself. + StartEpoch: build.UpgradeLiftoffHeight, + } + sm.postIgnitionVesting = append(sm.postIgnitionVesting, ns) + } + + return nil +} + +// sets up information about the vesting schedule post the calico upgrade +func (sm *StateManager) setupPostCalicoVesting(ctx context.Context) error { + + totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount) + + // 0 days + zeroDays := abi.ChainEpoch(0) + totalsByEpoch[zeroDays] = big.NewInt(10_632_000) + + // 6 months + sixMonths := abi.ChainEpoch(183 * builtin.EpochsInDay) + totalsByEpoch[sixMonths] = big.NewInt(19_015_887) + totalsByEpoch[sixMonths] = big.Add(totalsByEpoch[sixMonths], big.NewInt(32_787_700)) + + // 1 year + oneYear := abi.ChainEpoch(365 * builtin.EpochsInDay) + totalsByEpoch[oneYear] = big.NewInt(22_421_712) + totalsByEpoch[oneYear] = big.Add(totalsByEpoch[oneYear], big.NewInt(9_400_000)) + + // 2 years + twoYears := abi.ChainEpoch(2 * 365 * builtin.EpochsInDay) + totalsByEpoch[twoYears] = big.NewInt(7_223_364) + + // 3 years + threeYears := abi.ChainEpoch(3 * 365 * builtin.EpochsInDay) + totalsByEpoch[threeYears] = big.NewInt(87_637_883) + totalsByEpoch[threeYears] = big.Add(totalsByEpoch[threeYears], big.NewInt(898_958)) + + // 6 years + sixYears := abi.ChainEpoch(6 * 365 * builtin.EpochsInDay) + totalsByEpoch[sixYears] = big.NewInt(100_000_000) + totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(300_000_000)) + totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(9_805_053)) + + sm.postCalicoVesting = make([]msig0.State, 0, len(totalsByEpoch)) + for k, v := range totalsByEpoch { + ns := msig0.State{ + InitialBalance: big.Mul(v, big.NewInt(int64(build.FilecoinPrecision))), + UnlockDuration: k, + PendingTxns: cid.Undef, + StartEpoch: build.UpgradeLiftoffHeight, + } + sm.postCalicoVesting = append(sm.postCalicoVesting, ns) + } + + return nil +} + +// GetVestedFunds returns all funds that have "left" actors that are in the genesis state: +// - For Multisigs, it counts the actual amounts that have vested at the given epoch +// - For Accounts, it counts max(currentBalance - genesisBalance, 0). +func (sm *StateManager) GetFilVested(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { + vf := big.Zero() + if height <= build.UpgradeIgnitionHeight { + for _, v := range sm.preIgnitionVesting { + au := big.Sub(v.InitialBalance, v.AmountLocked(height)) + vf = big.Add(vf, au) + } + } else if height <= build.UpgradeCalicoHeight { + for _, v := range sm.postIgnitionVesting { + // In the pre-ignition logic, we simply called AmountLocked(height), assuming startEpoch was 0. + // The start epoch changed in the Ignition upgrade. + au := big.Sub(v.InitialBalance, v.AmountLocked(height-v.StartEpoch)) + vf = big.Add(vf, au) + } + } else { + for _, v := range sm.postCalicoVesting { + // In the pre-ignition logic, we simply called AmountLocked(height), assuming startEpoch was 0. + // The start epoch changed in the Ignition upgrade. + au := big.Sub(v.InitialBalance, v.AmountLocked(height-v.StartEpoch)) + vf = big.Add(vf, au) + } + } + + // After UpgradeAssemblyHeight these funds are accounted for in GetFilReserveDisbursed + if height <= build.UpgradeAssemblyHeight { + // continue to use preIgnitionGenInfos, nothing changed at the Ignition epoch + vf = big.Add(vf, sm.genesisPledge) + // continue to use preIgnitionGenInfos, nothing changed at the Ignition epoch + vf = big.Add(vf, sm.genesisMarketFunds) + } + + return vf, nil +} + +func GetFilReserveDisbursed(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + ract, err := st.GetActor(builtin.ReserveAddress) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to get reserve actor: %w", err) + } + + // If money enters the reserve actor, this could lead to a negative term + return big.Sub(big.NewFromGo(build.InitialFilReserved), ract.Balance), nil +} + +func GetFilMined(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + ractor, err := st.GetActor(reward.Address) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load reward actor state: %w", err) + } + + rst, err := reward.Load(adt.WrapStore(ctx, st.Store), ractor) + if err != nil { + return big.Zero(), err + } + + return rst.TotalStoragePowerReward() +} + +func getFilMarketLocked(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + act, err := st.GetActor(market.Address) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load market actor: %w", err) + } + + mst, err := market.Load(adt.WrapStore(ctx, st.Store), act) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load market state: %w", err) + } + + return mst.TotalLocked() +} + +func getFilPowerLocked(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + pactor, err := st.GetActor(power.Address) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load power actor: %w", err) + } + + pst, err := power.Load(adt.WrapStore(ctx, st.Store), pactor) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load power state: %w", err) + } + + return pst.TotalLocked() +} + +func (sm *StateManager) GetFilLocked(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + + filMarketLocked, err := getFilMarketLocked(ctx, st) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to get filMarketLocked: %w", err) + } + + filPowerLocked, err := getFilPowerLocked(ctx, st) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to get filPowerLocked: %w", err) + } + + return types.BigAdd(filMarketLocked, filPowerLocked), nil +} + +func GetFilBurnt(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + burnt, err := st.GetActor(builtin.BurntFundsActorAddr) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load burnt actor: %w", err) + } + + return burnt.Balance, nil +} + +func (sm *StateManager) GetVMCirculatingSupply(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { + cs, err := sm.GetVMCirculatingSupplyDetailed(ctx, height, st) + if err != nil { + return types.EmptyInt, err + } + + return cs.FilCirculating, err +} + +func (sm *StateManager) GetVMCirculatingSupplyDetailed(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (api.CirculatingSupply, error) { + sm.genesisMsigLk.Lock() + defer sm.genesisMsigLk.Unlock() + if sm.preIgnitionVesting == nil || sm.genesisPledge.IsZero() || sm.genesisMarketFunds.IsZero() { + err := sm.setupGenesisVestingSchedule(ctx) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to setup pre-ignition vesting schedule: %w", err) + } + } + if sm.postIgnitionVesting == nil { + err := sm.setupPostIgnitionVesting(ctx) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to setup post-ignition vesting schedule: %w", err) + } + } + if sm.postCalicoVesting == nil { + err := sm.setupPostCalicoVesting(ctx) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to setup post-calico vesting schedule: %w", err) + } + } + + filVested, err := sm.GetFilVested(ctx, height, st) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filVested: %w", err) + } + + filReserveDisbursed := big.Zero() + if height > build.UpgradeAssemblyHeight { + filReserveDisbursed, err = GetFilReserveDisbursed(ctx, st) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filReserveDisbursed: %w", err) + } + } + + filMined, err := GetFilMined(ctx, st) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filMined: %w", err) + } + + filBurnt, err := GetFilBurnt(ctx, st) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filBurnt: %w", err) + } + + filLocked, err := sm.GetFilLocked(ctx, st) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filLocked: %w", err) + } + + ret := types.BigAdd(filVested, filMined) + ret = types.BigAdd(ret, filReserveDisbursed) + ret = types.BigSub(ret, filBurnt) + ret = types.BigSub(ret, filLocked) + + if ret.LessThan(big.Zero()) { + ret = big.Zero() + } + + return api.CirculatingSupply{ + FilVested: filVested, + FilMined: filMined, + FilBurnt: filBurnt, + FilLocked: filLocked, + FilCirculating: ret, + FilReserveDisbursed: filReserveDisbursed, + }, nil +} + +func (sm *StateManager) GetCirculatingSupply(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { + circ := big.Zero() + unCirc := big.Zero() + err := st.ForEach(func(a address.Address, actor *types.Actor) error { + switch { + case actor.Balance.IsZero(): + // Do nothing for zero-balance actors + break + case a == _init.Address || + a == reward.Address || + a == verifreg.Address || + // The power actor itself should never receive funds + a == power.Address || + a == builtin.SystemActorAddr || + a == builtin.CronActorAddr || + a == builtin.BurntFundsActorAddr || + a == builtin.SaftAddress || + a == builtin.ReserveAddress: + + unCirc = big.Add(unCirc, actor.Balance) + + case a == market.Address: + mst, err := market.Load(sm.cs.ActorStore(ctx), actor) + if err != nil { + return err + } + + lb, err := mst.TotalLocked() + if err != nil { + return err + } + + circ = big.Add(circ, big.Sub(actor.Balance, lb)) + unCirc = big.Add(unCirc, lb) + + case builtin.IsAccountActor(actor.Code) || builtin.IsPaymentChannelActor(actor.Code): + circ = big.Add(circ, actor.Balance) + + case builtin.IsStorageMinerActor(actor.Code): + mst, err := miner.Load(sm.cs.ActorStore(ctx), actor) + if err != nil { + return err + } + + ab, err := mst.AvailableBalance(actor.Balance) + + if err == nil { + circ = big.Add(circ, ab) + unCirc = big.Add(unCirc, big.Sub(actor.Balance, ab)) + } else { + // Assume any error is because the miner state is "broken" (lower actor balance than locked funds) + // In this case, the actor's entire balance is considered "uncirculating" + unCirc = big.Add(unCirc, actor.Balance) + } + + case builtin.IsMultisigActor(actor.Code): + mst, err := multisig.Load(sm.cs.ActorStore(ctx), actor) + if err != nil { + return err + } + + lb, err := mst.LockedBalance(height) + if err != nil { + return err + } + + ab := big.Sub(actor.Balance, lb) + circ = big.Add(circ, big.Max(ab, big.Zero())) + unCirc = big.Add(unCirc, big.Min(actor.Balance, lb)) + default: + return xerrors.Errorf("unexpected actor: %s", a) + } + + return nil + }) + + if err != nil { + return types.EmptyInt, err + } + + total := big.Add(circ, unCirc) + if !total.Equals(types.TotalFilecoinInt) { + return types.EmptyInt, xerrors.Errorf("total filecoin didn't add to expected amount: %s != %s", total, types.TotalFilecoinInt) + } + + return circ, nil +} diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index d2a2c6e60..a4d78f997 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -1,540 +1,38 @@ package stmgr import ( - "bytes" "context" "fmt" - "os" "reflect" "runtime" "strings" - exported5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/exported" - - "github.com/filecoin-project/go-state-types/big" - - "github.com/filecoin-project/go-state-types/network" - - cid "github.com/ipfs/go-cid" + "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/rt" exported0 "github.com/filecoin-project/specs-actors/actors/builtin/exported" exported2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/exported" exported3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/exported" exported4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/exported" + exported5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/exported" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/builtin" init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" - "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/actors/policy" - "github.com/filecoin-project/lotus/chain/beacon" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" - "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" "github.com/filecoin-project/lotus/node/modules/dtypes" ) -func GetNetworkName(ctx context.Context, sm *StateManager, st cid.Cid) (dtypes.NetworkName, error) { - act, err := sm.LoadActorRaw(ctx, init_.Address, st) - if err != nil { - return "", err - } - ias, err := init_.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return "", err - } - - return ias.NetworkName() -} - -func GetMinerWorkerRaw(ctx context.Context, sm *StateManager, st cid.Cid, maddr address.Address) (address.Address, error) { - state, err := sm.StateTree(st) - if err != nil { - return address.Undef, xerrors.Errorf("(get sset) failed to load state tree: %w", err) - } - act, err := state.GetActor(maddr) - if err != nil { - return address.Undef, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) - } - mas, err := miner.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return address.Undef, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) - } - - info, err := mas.Info() - if err != nil { - return address.Undef, xerrors.Errorf("failed to load actor info: %w", err) - } - - return vm.ResolveToKeyAddr(state, sm.cs.ActorStore(ctx), info.Worker) -} - -func GetPower(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (power.Claim, power.Claim, bool, error) { - return GetPowerRaw(ctx, sm, ts.ParentState(), maddr) -} - -func GetPowerRaw(ctx context.Context, sm *StateManager, st cid.Cid, maddr address.Address) (power.Claim, power.Claim, bool, error) { - act, err := sm.LoadActorRaw(ctx, power.Address, st) - if err != nil { - return power.Claim{}, power.Claim{}, false, xerrors.Errorf("(get sset) failed to load power actor state: %w", err) - } - - pas, err := power.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return power.Claim{}, power.Claim{}, false, err - } - - tpow, err := pas.TotalPower() - if err != nil { - return power.Claim{}, power.Claim{}, false, err - } - - var mpow power.Claim - var minpow bool - if maddr != address.Undef { - var found bool - mpow, found, err = pas.MinerPower(maddr) - if err != nil || !found { - return power.Claim{}, tpow, false, err - } - - minpow, err = pas.MinerNominalPowerMeetsConsensusMinimum(maddr) - if err != nil { - return power.Claim{}, power.Claim{}, false, err - } - } - - return mpow, tpow, minpow, nil -} - -func PreCommitInfo(ctx context.Context, sm *StateManager, maddr address.Address, sid abi.SectorNumber, ts *types.TipSet) (*miner.SectorPreCommitOnChainInfo, error) { - act, err := sm.LoadActor(ctx, maddr, ts) - if err != nil { - return nil, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) - } - - mas, err := miner.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) - } - - return mas.GetPrecommittedSector(sid) -} - -func MinerSectorInfo(ctx context.Context, sm *StateManager, maddr address.Address, sid abi.SectorNumber, ts *types.TipSet) (*miner.SectorOnChainInfo, error) { - act, err := sm.LoadActor(ctx, maddr, ts) - if err != nil { - return nil, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) - } - - mas, err := miner.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) - } - - return mas.GetSector(sid) -} - -func GetSectorsForWinningPoSt(ctx context.Context, nv network.Version, pv ffiwrapper.Verifier, sm *StateManager, st cid.Cid, maddr address.Address, rand abi.PoStRandomness) ([]builtin.SectorInfo, error) { - act, err := sm.LoadActorRaw(ctx, maddr, st) - if err != nil { - return nil, xerrors.Errorf("failed to load miner actor: %w", err) - } - - mas, err := miner.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, xerrors.Errorf("failed to load miner actor state: %w", err) - } - - var provingSectors bitfield.BitField - if nv < network.Version7 { - allSectors, err := miner.AllPartSectors(mas, miner.Partition.AllSectors) - if err != nil { - return nil, xerrors.Errorf("get all sectors: %w", err) - } - - faultySectors, err := miner.AllPartSectors(mas, miner.Partition.FaultySectors) - if err != nil { - return nil, xerrors.Errorf("get faulty sectors: %w", err) - } - - provingSectors, err = bitfield.SubtractBitField(allSectors, faultySectors) - if err != nil { - return nil, xerrors.Errorf("calc proving sectors: %w", err) - } - } else { - provingSectors, err = miner.AllPartSectors(mas, miner.Partition.ActiveSectors) - if err != nil { - return nil, xerrors.Errorf("get active sectors sectors: %w", err) - } - } - - numProvSect, err := provingSectors.Count() - if err != nil { - return nil, xerrors.Errorf("failed to count bits: %w", err) - } - - // TODO(review): is this right? feels fishy to me - if numProvSect == 0 { - return nil, nil - } - - info, err := mas.Info() - if err != nil { - return nil, xerrors.Errorf("getting miner info: %w", err) - } - - mid, err := address.IDFromAddress(maddr) - if err != nil { - return nil, xerrors.Errorf("getting miner ID: %w", err) - } - - proofType, err := miner.WinningPoStProofTypeFromWindowPoStProofType(nv, info.WindowPoStProofType) - if err != nil { - return nil, xerrors.Errorf("determining winning post proof type: %w", err) - } - - ids, err := pv.GenerateWinningPoStSectorChallenge(ctx, proofType, abi.ActorID(mid), rand, numProvSect) - if err != nil { - return nil, xerrors.Errorf("generating winning post challenges: %w", err) - } - - iter, err := provingSectors.BitIterator() - if err != nil { - return nil, xerrors.Errorf("iterating over proving sectors: %w", err) - } - - // Select winning sectors by _index_ in the all-sectors bitfield. - selectedSectors := bitfield.New() - prev := uint64(0) - for _, n := range ids { - sno, err := iter.Nth(n - prev) - if err != nil { - return nil, xerrors.Errorf("iterating over proving sectors: %w", err) - } - selectedSectors.Set(sno) - prev = n - } - - sectors, err := mas.LoadSectors(&selectedSectors) - if err != nil { - return nil, xerrors.Errorf("loading proving sectors: %w", err) - } - - out := make([]builtin.SectorInfo, len(sectors)) - for i, sinfo := range sectors { - out[i] = builtin.SectorInfo{ - SealProof: sinfo.SealProof, - SectorNumber: sinfo.SectorNumber, - SealedCID: sinfo.SealedCID, - } - } - - return out, nil -} - -func GetMinerSlashed(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (bool, error) { - act, err := sm.LoadActor(ctx, power.Address, ts) - if err != nil { - return false, xerrors.Errorf("failed to load power actor: %w", err) - } - - spas, err := power.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return false, xerrors.Errorf("failed to load power actor state: %w", err) - } - - _, ok, err := spas.MinerPower(maddr) - if err != nil { - return false, xerrors.Errorf("getting miner power: %w", err) - } - - if !ok { - return true, nil - } - - return false, nil -} - -func GetStorageDeal(ctx context.Context, sm *StateManager, dealID abi.DealID, ts *types.TipSet) (*api.MarketDeal, error) { - act, err := sm.LoadActor(ctx, market.Address, ts) - if err != nil { - return nil, xerrors.Errorf("failed to load market actor: %w", err) - } - - state, err := market.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, xerrors.Errorf("failed to load market actor state: %w", err) - } - - proposals, err := state.Proposals() - if err != nil { - return nil, err - } - - proposal, found, err := proposals.Get(dealID) - - if err != nil { - return nil, err - } else if !found { - return nil, xerrors.Errorf( - "deal %d not found "+ - "- deal may not have completed sealing before deal proposal "+ - "start epoch, or deal may have been slashed", - dealID) - } - - states, err := state.States() - if err != nil { - return nil, err - } - - st, found, err := states.Get(dealID) - if err != nil { - return nil, err - } - - if !found { - st = market.EmptyDealState() - } - - return &api.MarketDeal{ - Proposal: *proposal, - State: *st, - }, nil -} - -func ListMinerActors(ctx context.Context, sm *StateManager, ts *types.TipSet) ([]address.Address, error) { - act, err := sm.LoadActor(ctx, power.Address, ts) - if err != nil { - return nil, xerrors.Errorf("failed to load power actor: %w", err) - } - - powState, err := power.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, xerrors.Errorf("failed to load power actor state: %w", err) - } - - return powState.ListAllMiners() -} - -func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch, msgs []*types.Message, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error) { - if ts == nil { - ts = sm.cs.GetHeaviestTipSet() - } - - base, trace, err := sm.ExecutionTrace(ctx, ts) - if err != nil { - return cid.Undef, nil, err - } - - for i := ts.Height(); i < height; i++ { - // handle state forks - base, err = sm.handleStateForks(ctx, base, i, &InvocationTracer{trace: &trace}, ts) - if err != nil { - return cid.Undef, nil, xerrors.Errorf("error handling state forks: %w", err) - } - - // TODO: should we also run cron here? - } - - r := store.NewChainRand(sm.cs, ts.Cids()) - vmopt := &vm.VMOpts{ - StateBase: base, - Epoch: height, - Rand: r, - Bstore: sm.cs.StateBlockstore(), - Syscalls: sm.cs.VMSys(), - CircSupplyCalc: sm.GetVMCirculatingSupply, - NtwkVersion: sm.GetNtwkVersion, - BaseFee: ts.Blocks()[0].ParentBaseFee, - LookbackState: LookbackStateGetterForTipset(sm, ts), - } - vmi, err := sm.newVM(ctx, vmopt) - if err != nil { - return cid.Undef, nil, err - } - - for i, msg := range msgs { - // TODO: Use the signed message length for secp messages - ret, err := vmi.ApplyMessage(ctx, msg) - if err != nil { - return cid.Undef, nil, xerrors.Errorf("applying message %s: %w", msg.Cid(), err) - } - if ret.ExitCode != 0 { - log.Infof("compute state apply message %d failed (exit: %d): %s", i, ret.ExitCode, ret.ActorErr) - } - } - - root, err := vmi.Flush(ctx) - if err != nil { - return cid.Undef, nil, err - } - - return root, trace, nil -} - -func LookbackStateGetterForTipset(sm *StateManager, ts *types.TipSet) vm.LookbackStateGetter { - return func(ctx context.Context, round abi.ChainEpoch) (*state.StateTree, error) { - _, st, err := GetLookbackTipSetForRound(ctx, sm, ts, round) - if err != nil { - return nil, err - } - return sm.StateTree(st) - } -} - -func GetLookbackTipSetForRound(ctx context.Context, sm *StateManager, ts *types.TipSet, round abi.ChainEpoch) (*types.TipSet, cid.Cid, error) { - var lbr abi.ChainEpoch - lb := policy.GetWinningPoStSectorSetLookback(sm.GetNtwkVersion(ctx, round)) - if round > lb { - lbr = round - lb - } - - // more null blocks than our lookback - if lbr >= ts.Height() { - // This should never happen at this point, but may happen before - // network version 3 (where the lookback was only 10 blocks). - st, _, err := sm.TipSetState(ctx, ts) - if err != nil { - return nil, cid.Undef, err - } - return ts, st, nil - } - - // Get the tipset after the lookback tipset, or the next non-null one. - nextTs, err := sm.ChainStore().GetTipsetByHeight(ctx, lbr+1, ts, false) - if err != nil { - return nil, cid.Undef, xerrors.Errorf("failed to get lookback tipset+1: %w", err) - } - - if lbr > nextTs.Height() { - return nil, cid.Undef, xerrors.Errorf("failed to find non-null tipset %s (%d) which is known to exist, found %s (%d)", ts.Key(), ts.Height(), nextTs.Key(), nextTs.Height()) - - } - - lbts, err := sm.ChainStore().GetTipSetFromKey(nextTs.Parents()) - if err != nil { - return nil, cid.Undef, xerrors.Errorf("failed to resolve lookback tipset: %w", err) - } - - return lbts, nextTs.ParentState(), nil -} - -func MinerGetBaseInfo(ctx context.Context, sm *StateManager, bcs beacon.Schedule, tsk types.TipSetKey, round abi.ChainEpoch, maddr address.Address, pv ffiwrapper.Verifier) (*api.MiningBaseInfo, error) { - ts, err := sm.ChainStore().LoadTipSet(tsk) - if err != nil { - return nil, xerrors.Errorf("failed to load tipset for mining base: %w", err) - } - - prev, err := sm.ChainStore().GetLatestBeaconEntry(ts) - if err != nil { - if os.Getenv("LOTUS_IGNORE_DRAND") != "_yes_" { - return nil, xerrors.Errorf("failed to get latest beacon entry: %w", err) - } - - prev = &types.BeaconEntry{} - } - - entries, err := beacon.BeaconEntriesForBlock(ctx, bcs, round, ts.Height(), *prev) - if err != nil { - return nil, err - } - - rbase := *prev - if len(entries) > 0 { - rbase = entries[len(entries)-1] - } - - lbts, lbst, err := GetLookbackTipSetForRound(ctx, sm, ts, round) - if err != nil { - return nil, xerrors.Errorf("getting lookback miner actor state: %w", err) - } - - act, err := sm.LoadActorRaw(ctx, maddr, lbst) - if xerrors.Is(err, types.ErrActorNotFound) { - _, err := sm.LoadActor(ctx, maddr, ts) - if err != nil { - return nil, xerrors.Errorf("loading miner in current state: %w", err) - } - - return nil, nil - } - if err != nil { - return nil, xerrors.Errorf("failed to load miner actor: %w", err) - } - - mas, err := miner.Load(sm.cs.ActorStore(ctx), act) - if err != nil { - return nil, xerrors.Errorf("failed to load miner actor state: %w", err) - } - - buf := new(bytes.Buffer) - if err := maddr.MarshalCBOR(buf); err != nil { - return nil, xerrors.Errorf("failed to marshal miner address: %w", err) - } - - prand, err := store.DrawRandomness(rbase.Data, crypto.DomainSeparationTag_WinningPoStChallengeSeed, round, buf.Bytes()) - if err != nil { - return nil, xerrors.Errorf("failed to get randomness for winning post: %w", err) - } - - nv := sm.GetNtwkVersion(ctx, ts.Height()) - - sectors, err := GetSectorsForWinningPoSt(ctx, nv, pv, sm, lbst, maddr, prand) - if err != nil { - return nil, xerrors.Errorf("getting winning post proving set: %w", err) - } - - if len(sectors) == 0 { - return nil, nil - } - - mpow, tpow, _, err := GetPowerRaw(ctx, sm, lbst, maddr) - if err != nil { - return nil, xerrors.Errorf("failed to get power: %w", err) - } - - info, err := mas.Info() - if err != nil { - return nil, err - } - - worker, err := sm.ResolveToKeyAddress(ctx, info.Worker, ts) - if err != nil { - return nil, xerrors.Errorf("resolving worker address: %w", err) - } - - // TODO: Not ideal performance...This method reloads miner and power state (already looked up here and in GetPowerRaw) - eligible, err := MinerEligibleToMine(ctx, sm, maddr, ts, lbts) - if err != nil { - return nil, xerrors.Errorf("determining miner eligibility: %w", err) - } - - return &api.MiningBaseInfo{ - MinerPower: mpow.QualityAdjPower, - NetworkPower: tpow.QualityAdjPower, - Sectors: sectors, - WorkerKey: worker, - SectorSize: info.SectorSize, - PrevBeaconEntry: *prev, - BeaconEntries: entries, - EligibleForMining: eligible, - }, nil -} - type MethodMeta struct { Name string @@ -621,86 +119,124 @@ func GetParamType(actCode cid.Cid, method abi.MethodNum) (cbg.CBORUnmarshaler, e return reflect.New(m.Params.Elem()).Interface().(cbg.CBORUnmarshaler), nil } -func minerHasMinPower(ctx context.Context, sm *StateManager, addr address.Address, ts *types.TipSet) (bool, error) { - pact, err := sm.LoadActor(ctx, power.Address, ts) +func GetNetworkName(ctx context.Context, sm *StateManager, st cid.Cid) (dtypes.NetworkName, error) { + act, err := sm.LoadActorRaw(ctx, init_.Address, st) if err != nil { - return false, xerrors.Errorf("loading power actor state: %w", err) + return "", err + } + ias, err := init_.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return "", err } - ps, err := power.Load(sm.cs.ActorStore(ctx), pact) - if err != nil { - return false, err - } - - return ps.MinerNominalPowerMeetsConsensusMinimum(addr) + return ias.NetworkName() } -func MinerEligibleToMine(ctx context.Context, sm *StateManager, addr address.Address, baseTs *types.TipSet, lookbackTs *types.TipSet) (bool, error) { - hmp, err := minerHasMinPower(ctx, sm, addr, lookbackTs) - - // TODO: We're blurring the lines between a "runtime network version" and a "Lotus upgrade epoch", is that unavoidable? - if sm.GetNtwkVersion(ctx, baseTs.Height()) <= network.Version3 { - return hmp, err +func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch, msgs []*types.Message, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error) { + if ts == nil { + ts = sm.cs.GetHeaviestTipSet() } + base, trace, err := sm.ExecutionTrace(ctx, ts) if err != nil { - return false, err + return cid.Undef, nil, err } - if !hmp { - return false, nil + for i := ts.Height(); i < height; i++ { + // handle state forks + base, err = sm.handleStateForks(ctx, base, i, &InvocationTracer{trace: &trace}, ts) + if err != nil { + return cid.Undef, nil, xerrors.Errorf("error handling state forks: %w", err) + } + + // TODO: should we also run cron here? } - // Post actors v2, also check MinerEligibleForElection with base ts - - pact, err := sm.LoadActor(ctx, power.Address, baseTs) + r := store.NewChainRand(sm.cs, ts.Cids()) + vmopt := &vm.VMOpts{ + StateBase: base, + Epoch: height, + Rand: r, + Bstore: sm.cs.StateBlockstore(), + Syscalls: sm.syscalls, + CircSupplyCalc: sm.GetVMCirculatingSupply, + NtwkVersion: sm.GetNtwkVersion, + BaseFee: ts.Blocks()[0].ParentBaseFee, + LookbackState: LookbackStateGetterForTipset(sm, ts), + } + vmi, err := sm.newVM(ctx, vmopt) if err != nil { - return false, xerrors.Errorf("loading power actor state: %w", err) + return cid.Undef, nil, err } - pstate, err := power.Load(sm.cs.ActorStore(ctx), pact) + for i, msg := range msgs { + // TODO: Use the signed message length for secp messages + ret, err := vmi.ApplyMessage(ctx, msg) + if err != nil { + return cid.Undef, nil, xerrors.Errorf("applying message %s: %w", msg.Cid(), err) + } + if ret.ExitCode != 0 { + log.Infof("compute state apply message %d failed (exit: %d): %s", i, ret.ExitCode, ret.ActorErr) + } + } + + root, err := vmi.Flush(ctx) if err != nil { - return false, err + return cid.Undef, nil, err } - mact, err := sm.LoadActor(ctx, addr, baseTs) - if err != nil { - return false, xerrors.Errorf("loading miner actor state: %w", err) - } - - mstate, err := miner.Load(sm.cs.ActorStore(ctx), mact) - if err != nil { - return false, err - } - - // Non-empty power claim. - if claim, found, err := pstate.MinerPower(addr); err != nil { - return false, err - } else if !found { - return false, err - } else if claim.QualityAdjPower.LessThanEqual(big.Zero()) { - return false, err - } - - // No fee debt. - if debt, err := mstate.FeeDebt(); err != nil { - return false, err - } else if !debt.IsZero() { - return false, err - } - - // No active consensus faults. - if mInfo, err := mstate.Info(); err != nil { - return false, err - } else if baseTs.Height() <= mInfo.ConsensusFaultElapsed { - return false, nil - } - - return true, nil + return root, trace, nil } -func CheckTotalFIL(ctx context.Context, sm *StateManager, ts *types.TipSet) (abi.TokenAmount, error) { - str, err := state.LoadStateTree(sm.ChainStore().ActorStore(ctx), ts.ParentState()) +func LookbackStateGetterForTipset(sm *StateManager, ts *types.TipSet) vm.LookbackStateGetter { + return func(ctx context.Context, round abi.ChainEpoch) (*state.StateTree, error) { + _, st, err := GetLookbackTipSetForRound(ctx, sm, ts, round) + if err != nil { + return nil, err + } + return sm.StateTree(st) + } +} + +func GetLookbackTipSetForRound(ctx context.Context, sm *StateManager, ts *types.TipSet, round abi.ChainEpoch) (*types.TipSet, cid.Cid, error) { + var lbr abi.ChainEpoch + lb := policy.GetWinningPoStSectorSetLookback(sm.GetNtwkVersion(ctx, round)) + if round > lb { + lbr = round - lb + } + + // more null blocks than our lookback + if lbr >= ts.Height() { + // This should never happen at this point, but may happen before + // network version 3 (where the lookback was only 10 blocks). + st, _, err := sm.TipSetState(ctx, ts) + if err != nil { + return nil, cid.Undef, err + } + return ts, st, nil + } + + // Get the tipset after the lookback tipset, or the next non-null one. + nextTs, err := sm.ChainStore().GetTipsetByHeight(ctx, lbr+1, ts, false) + if err != nil { + return nil, cid.Undef, xerrors.Errorf("failed to get lookback tipset+1: %w", err) + } + + if lbr > nextTs.Height() { + return nil, cid.Undef, xerrors.Errorf("failed to find non-null tipset %s (%d) which is known to exist, found %s (%d)", ts.Key(), ts.Height(), nextTs.Key(), nextTs.Height()) + + } + + lbts, err := sm.ChainStore().GetTipSetFromKey(nextTs.Parents()) + if err != nil { + return nil, cid.Undef, xerrors.Errorf("failed to resolve lookback tipset: %w", err) + } + + return lbts, nextTs.ParentState(), nil +} + +func CheckTotalFIL(ctx context.Context, cs *store.ChainStore, ts *types.TipSet) (abi.TokenAmount, error) { + str, err := state.LoadStateTree(cs.ActorStore(ctx), ts.ParentState()) if err != nil { return abi.TokenAmount{}, err } @@ -729,3 +265,21 @@ func MakeMsgGasCost(msg *types.Message, ret *vm.ApplyRet) api.MsgGasCost { TotalCost: big.Sub(msg.RequiredFunds(), ret.GasCosts.Refund), } } + +func (sm *StateManager) ListAllActors(ctx context.Context, ts *types.TipSet) ([]address.Address, error) { + stateTree, err := sm.StateTree(sm.parentState(ts)) + if err != nil { + return nil, err + } + + var out []address.Address + err = stateTree.ForEach(func(addr address.Address, act *types.Actor) error { + out = append(out, addr) + return nil + }) + if err != nil { + return nil, err + } + + return out, nil +} diff --git a/chain/store/index_test.go b/chain/store/index_test.go index 447071901..b74bc835b 100644 --- a/chain/store/index_test.go +++ b/chain/store/index_test.go @@ -31,7 +31,7 @@ func TestIndexSeeks(t *testing.T) { ctx := context.TODO() nbs := blockstore.NewMemorySync() - cs := store.NewChainStore(nbs, nbs, syncds.MutexWrap(datastore.NewMapDatastore()), nil, nil) + cs := store.NewChainStore(nbs, nbs, syncds.MutexWrap(datastore.NewMapDatastore()), nil) defer cs.Close() //nolint:errcheck _, err = cs.Import(bytes.NewReader(gencar)) diff --git a/chain/store/messages.go b/chain/store/messages.go new file mode 100644 index 000000000..9f5160559 --- /dev/null +++ b/chain/store/messages.go @@ -0,0 +1,303 @@ +package store + +import ( + "context" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + block "github.com/ipfs/go-block-format" + cbor "github.com/ipfs/go-ipld-cbor" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/types" +) + +type storable interface { + ToStorageBlock() (block.Block, error) +} + +func PutMessage(bs bstore.Blockstore, m storable) (cid.Cid, error) { + b, err := m.ToStorageBlock() + if err != nil { + return cid.Undef, err + } + + if err := bs.Put(b); err != nil { + return cid.Undef, err + } + + return b.Cid(), nil +} + +func (cs *ChainStore) PutMessage(m storable) (cid.Cid, error) { + return PutMessage(cs.chainBlockstore, m) +} + +func (cs *ChainStore) GetCMessage(c cid.Cid) (types.ChainMsg, error) { + m, err := cs.GetMessage(c) + if err == nil { + return m, nil + } + if err != bstore.ErrNotFound { + log.Warnf("GetCMessage: unexpected error getting unsigned message: %s", err) + } + + return cs.GetSignedMessage(c) +} + +func (cs *ChainStore) GetMessage(c cid.Cid) (*types.Message, error) { + var msg *types.Message + err := cs.chainLocalBlockstore.View(c, func(b []byte) (err error) { + msg, err = types.DecodeMessage(b) + return err + }) + return msg, err +} + +func (cs *ChainStore) GetSignedMessage(c cid.Cid) (*types.SignedMessage, error) { + var msg *types.SignedMessage + err := cs.chainLocalBlockstore.View(c, func(b []byte) (err error) { + msg, err = types.DecodeSignedMessage(b) + return err + }) + return msg, err +} + +func (cs *ChainStore) readAMTCids(root cid.Cid) ([]cid.Cid, error) { + ctx := context.TODO() + // block headers use adt0, for now. + a, err := blockadt.AsArray(cs.ActorStore(ctx), root) + if err != nil { + return nil, xerrors.Errorf("amt load: %w", err) + } + + var ( + cids []cid.Cid + cborCid cbg.CborCid + ) + if err := a.ForEach(&cborCid, func(i int64) error { + c := cid.Cid(cborCid) + cids = append(cids, c) + return nil + }); err != nil { + return nil, xerrors.Errorf("failed to traverse amt: %w", err) + } + + if uint64(len(cids)) != a.Length() { + return nil, xerrors.Errorf("found %d cids, expected %d", len(cids), a.Length()) + } + + return cids, nil +} + +type BlockMessages struct { + Miner address.Address + BlsMessages []types.ChainMsg + SecpkMessages []types.ChainMsg + WinCount int64 +} + +func (cs *ChainStore) BlockMsgsForTipset(ts *types.TipSet) ([]BlockMessages, error) { + applied := make(map[address.Address]uint64) + + cst := cbor.NewCborStore(cs.stateBlockstore) + st, err := state.LoadStateTree(cst, ts.Blocks()[0].ParentStateRoot) + if err != nil { + return nil, xerrors.Errorf("failed to load state tree at tipset %s: %w", ts, err) + } + + selectMsg := func(m *types.Message) (bool, error) { + var sender address.Address + if ts.Height() >= build.UpgradeHyperdriveHeight { + sender, err = st.LookupID(m.From) + if err != nil { + return false, err + } + } else { + sender = m.From + } + + // The first match for a sender is guaranteed to have correct nonce -- the block isn't valid otherwise + if _, ok := applied[sender]; !ok { + applied[sender] = m.Nonce + } + + if applied[sender] != m.Nonce { + return false, nil + } + + applied[sender]++ + + return true, nil + } + + var out []BlockMessages + for _, b := range ts.Blocks() { + + bms, sms, err := cs.MessagesForBlock(b) + if err != nil { + return nil, xerrors.Errorf("failed to get messages for block: %w", err) + } + + bm := BlockMessages{ + Miner: b.Miner, + BlsMessages: make([]types.ChainMsg, 0, len(bms)), + SecpkMessages: make([]types.ChainMsg, 0, len(sms)), + WinCount: b.ElectionProof.WinCount, + } + + for _, bmsg := range bms { + b, err := selectMsg(bmsg.VMMessage()) + if err != nil { + return nil, xerrors.Errorf("failed to decide whether to select message for block: %w", err) + } + + if b { + bm.BlsMessages = append(bm.BlsMessages, bmsg) + } + } + + for _, smsg := range sms { + b, err := selectMsg(smsg.VMMessage()) + if err != nil { + return nil, xerrors.Errorf("failed to decide whether to select message for block: %w", err) + } + + if b { + bm.SecpkMessages = append(bm.SecpkMessages, smsg) + } + } + + out = append(out, bm) + } + + return out, nil +} + +func (cs *ChainStore) MessagesForTipset(ts *types.TipSet) ([]types.ChainMsg, error) { + bmsgs, err := cs.BlockMsgsForTipset(ts) + if err != nil { + return nil, err + } + + var out []types.ChainMsg + for _, bm := range bmsgs { + for _, blsm := range bm.BlsMessages { + out = append(out, blsm) + } + + for _, secm := range bm.SecpkMessages { + out = append(out, secm) + } + } + + return out, nil +} + +type mmCids struct { + bls []cid.Cid + secpk []cid.Cid +} + +func (cs *ChainStore) ReadMsgMetaCids(mmc cid.Cid) ([]cid.Cid, []cid.Cid, error) { + o, ok := cs.mmCache.Get(mmc) + if ok { + mmcids := o.(*mmCids) + return mmcids.bls, mmcids.secpk, nil + } + + cst := cbor.NewCborStore(cs.chainLocalBlockstore) + var msgmeta types.MsgMeta + if err := cst.Get(context.TODO(), mmc, &msgmeta); err != nil { + return nil, nil, xerrors.Errorf("failed to load msgmeta (%s): %w", mmc, err) + } + + blscids, err := cs.readAMTCids(msgmeta.BlsMessages) + if err != nil { + return nil, nil, xerrors.Errorf("loading bls message cids for block: %w", err) + } + + secpkcids, err := cs.readAMTCids(msgmeta.SecpkMessages) + if err != nil { + return nil, nil, xerrors.Errorf("loading secpk message cids for block: %w", err) + } + + cs.mmCache.Add(mmc, &mmCids{ + bls: blscids, + secpk: secpkcids, + }) + + return blscids, secpkcids, nil +} + +func (cs *ChainStore) MessagesForBlock(b *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) { + blscids, secpkcids, err := cs.ReadMsgMetaCids(b.Messages) + if err != nil { + return nil, nil, err + } + + blsmsgs, err := cs.LoadMessagesFromCids(blscids) + if err != nil { + return nil, nil, xerrors.Errorf("loading bls messages for block: %w", err) + } + + secpkmsgs, err := cs.LoadSignedMessagesFromCids(secpkcids) + if err != nil { + return nil, nil, xerrors.Errorf("loading secpk messages for block: %w", err) + } + + return blsmsgs, secpkmsgs, nil +} + +func (cs *ChainStore) GetParentReceipt(b *types.BlockHeader, i int) (*types.MessageReceipt, error) { + ctx := context.TODO() + // block headers use adt0, for now. + a, err := blockadt.AsArray(cs.ActorStore(ctx), b.ParentMessageReceipts) + if err != nil { + return nil, xerrors.Errorf("amt load: %w", err) + } + + var r types.MessageReceipt + if found, err := a.Get(uint64(i), &r); err != nil { + return nil, err + } else if !found { + return nil, xerrors.Errorf("failed to find receipt %d", i) + } + + return &r, nil +} + +func (cs *ChainStore) LoadMessagesFromCids(cids []cid.Cid) ([]*types.Message, error) { + msgs := make([]*types.Message, 0, len(cids)) + for i, c := range cids { + m, err := cs.GetMessage(c) + if err != nil { + return nil, xerrors.Errorf("failed to get message: (%s):%d: %w", c, i, err) + } + + msgs = append(msgs, m) + } + + return msgs, nil +} + +func (cs *ChainStore) LoadSignedMessagesFromCids(cids []cid.Cid) ([]*types.SignedMessage, error) { + msgs := make([]*types.SignedMessage, 0, len(cids)) + for i, c := range cids { + m, err := cs.GetSignedMessage(c) + if err != nil { + return nil, xerrors.Errorf("failed to get message: (%s):%d: %w", c, i, err) + } + + msgs = append(msgs, m) + } + + return msgs, nil +} diff --git a/chain/store/rand.go b/chain/store/rand.go new file mode 100644 index 000000000..1fa9e678f --- /dev/null +++ b/chain/store/rand.go @@ -0,0 +1,182 @@ +package store + +import ( + "context" + "encoding/binary" + "os" + + "github.com/ipfs/go-cid" + "github.com/minio/blake2b-simd" + "go.opencensus.io/trace" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" +) + +func DrawRandomness(rbase []byte, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + h := blake2b.New256() + if err := binary.Write(h, binary.BigEndian, int64(pers)); err != nil { + return nil, xerrors.Errorf("deriving randomness: %w", err) + } + VRFDigest := blake2b.Sum256(rbase) + _, err := h.Write(VRFDigest[:]) + if err != nil { + return nil, xerrors.Errorf("hashing VRFDigest: %w", err) + } + if err := binary.Write(h, binary.BigEndian, round); err != nil { + return nil, xerrors.Errorf("deriving randomness: %w", err) + } + _, err = h.Write(entropy) + if err != nil { + return nil, xerrors.Errorf("hashing entropy: %w", err) + } + + return h.Sum(nil), nil +} + +func (cs *ChainStore) GetBeaconRandomnessLookingBack(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cs.GetBeaconRandomness(ctx, blks, pers, round, entropy, true) +} + +func (cs *ChainStore) GetBeaconRandomnessLookingForward(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cs.GetBeaconRandomness(ctx, blks, pers, round, entropy, false) +} + +func (cs *ChainStore) GetBeaconRandomness(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte, lookback bool) ([]byte, error) { + _, span := trace.StartSpan(ctx, "store.GetBeaconRandomness") + defer span.End() + span.AddAttributes(trace.Int64Attribute("round", int64(round))) + + ts, err := cs.LoadTipSet(types.NewTipSetKey(blks...)) + if err != nil { + return nil, err + } + + if round > ts.Height() { + return nil, xerrors.Errorf("cannot draw randomness from the future") + } + + searchHeight := round + if searchHeight < 0 { + searchHeight = 0 + } + + randTs, err := cs.GetTipsetByHeight(ctx, searchHeight, ts, lookback) + if err != nil { + return nil, err + } + + be, err := cs.GetLatestBeaconEntry(randTs) + if err != nil { + return nil, err + } + + // if at (or just past -- for null epochs) appropriate epoch + // or at genesis (works for negative epochs) + return DrawRandomness(be.Data, pers, round, entropy) +} + +func (cs *ChainStore) GetChainRandomnessLookingBack(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cs.GetChainRandomness(ctx, blks, pers, round, entropy, true) +} + +func (cs *ChainStore) GetChainRandomnessLookingForward(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cs.GetChainRandomness(ctx, blks, pers, round, entropy, false) +} + +func (cs *ChainStore) GetChainRandomness(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte, lookback bool) ([]byte, error) { + _, span := trace.StartSpan(ctx, "store.GetChainRandomness") + defer span.End() + span.AddAttributes(trace.Int64Attribute("round", int64(round))) + + ts, err := cs.LoadTipSet(types.NewTipSetKey(blks...)) + if err != nil { + return nil, err + } + + if round > ts.Height() { + return nil, xerrors.Errorf("cannot draw randomness from the future") + } + + searchHeight := round + if searchHeight < 0 { + searchHeight = 0 + } + + randTs, err := cs.GetTipsetByHeight(ctx, searchHeight, ts, lookback) + if err != nil { + return nil, err + } + + mtb := randTs.MinTicketBlock() + + // if at (or just past -- for null epochs) appropriate epoch + // or at genesis (works for negative epochs) + return DrawRandomness(mtb.Ticket.VRFProof, pers, round, entropy) +} + +func (cs *ChainStore) GetLatestBeaconEntry(ts *types.TipSet) (*types.BeaconEntry, error) { + cur := ts + for i := 0; i < 20; i++ { + cbe := cur.Blocks()[0].BeaconEntries + if len(cbe) > 0 { + return &cbe[len(cbe)-1], nil + } + + if cur.Height() == 0 { + return nil, xerrors.Errorf("made it back to genesis block without finding beacon entry") + } + + next, err := cs.LoadTipSet(cur.Parents()) + if err != nil { + return nil, xerrors.Errorf("failed to load parents when searching back for latest beacon entry: %w", err) + } + cur = next + } + + if os.Getenv("LOTUS_IGNORE_DRAND") == "_yes_" { + return &types.BeaconEntry{ + Data: []byte{9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}, + }, nil + } + + return nil, xerrors.Errorf("found NO beacon entries in the 20 latest tipsets") +} + +type chainRand struct { + cs *ChainStore + blks []cid.Cid +} + +func NewChainRand(cs *ChainStore, blks []cid.Cid) vm.Rand { + return &chainRand{ + cs: cs, + blks: blks, + } +} + +func (cr *chainRand) GetChainRandomnessLookingBack(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cr.cs.GetChainRandomnessLookingBack(ctx, cr.blks, pers, round, entropy) +} + +func (cr *chainRand) GetChainRandomnessLookingForward(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cr.cs.GetChainRandomnessLookingForward(ctx, cr.blks, pers, round, entropy) +} + +func (cr *chainRand) GetBeaconRandomnessLookingBack(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cr.cs.GetBeaconRandomnessLookingBack(ctx, cr.blks, pers, round, entropy) +} + +func (cr *chainRand) GetBeaconRandomnessLookingForward(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cr.cs.GetBeaconRandomnessLookingForward(ctx, cr.blks, pers, round, entropy) +} + +func (cs *ChainStore) GetTipSetFromKey(tsk types.TipSetKey) (*types.TipSet, error) { + if tsk.IsEmpty() { + return cs.GetHeaviestTipSet(), nil + } + return cs.LoadTipSet(tsk) +} diff --git a/chain/store/snapshot.go b/chain/store/snapshot.go new file mode 100644 index 000000000..1d4ce3758 --- /dev/null +++ b/chain/store/snapshot.go @@ -0,0 +1,205 @@ +package store + +import ( + "bytes" + "context" + "io" + + "github.com/ipfs/go-cid" + "github.com/ipld/go-car" + carutil "github.com/ipld/go-car/util" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" +) + +func (cs *ChainStore) Export(ctx context.Context, ts *types.TipSet, inclRecentRoots abi.ChainEpoch, skipOldMsgs bool, w io.Writer) error { + h := &car.CarHeader{ + Roots: ts.Cids(), + Version: 1, + } + + if err := car.WriteHeader(h, w); err != nil { + return xerrors.Errorf("failed to write car header: %s", err) + } + + unionBs := bstore.Union(cs.stateBlockstore, cs.chainBlockstore) + return cs.WalkSnapshot(ctx, ts, inclRecentRoots, skipOldMsgs, true, func(c cid.Cid) error { + blk, err := unionBs.Get(c) + if err != nil { + return xerrors.Errorf("writing object to car, bs.Get: %w", err) + } + + if err := carutil.LdWrite(w, c.Bytes(), blk.RawData()); err != nil { + return xerrors.Errorf("failed to write block to car output: %w", err) + } + + return nil + }) +} + +func (cs *ChainStore) Import(r io.Reader) (*types.TipSet, error) { + // TODO: writing only to the state blockstore is incorrect. + // At this time, both the state and chain blockstores are backed by the + // universal store. When we physically segregate the stores, we will need + // to route state objects to the state blockstore, and chain objects to + // the chain blockstore. + header, err := car.LoadCar(cs.StateBlockstore(), r) + if err != nil { + return nil, xerrors.Errorf("loadcar failed: %w", err) + } + + root, err := cs.LoadTipSet(types.NewTipSetKey(header.Roots...)) + if err != nil { + return nil, xerrors.Errorf("failed to load root tipset from chainfile: %w", err) + } + + return root, nil +} + +func (cs *ChainStore) WalkSnapshot(ctx context.Context, ts *types.TipSet, inclRecentRoots abi.ChainEpoch, skipOldMsgs, skipMsgReceipts bool, cb func(cid.Cid) error) error { + if ts == nil { + ts = cs.GetHeaviestTipSet() + } + + seen := cid.NewSet() + walked := cid.NewSet() + + blocksToWalk := ts.Cids() + currentMinHeight := ts.Height() + + walkChain := func(blk cid.Cid) error { + if !seen.Visit(blk) { + return nil + } + + if err := cb(blk); err != nil { + return err + } + + data, err := cs.chainBlockstore.Get(blk) + if err != nil { + return xerrors.Errorf("getting block: %w", err) + } + + var b types.BlockHeader + if err := b.UnmarshalCBOR(bytes.NewBuffer(data.RawData())); err != nil { + return xerrors.Errorf("unmarshaling block header (cid=%s): %w", blk, err) + } + + if currentMinHeight > b.Height { + currentMinHeight = b.Height + if currentMinHeight%builtin.EpochsInDay == 0 { + log.Infow("export", "height", currentMinHeight) + } + } + + var cids []cid.Cid + if !skipOldMsgs || b.Height > ts.Height()-inclRecentRoots { + if walked.Visit(b.Messages) { + mcids, err := recurseLinks(cs.chainBlockstore, walked, b.Messages, []cid.Cid{b.Messages}) + if err != nil { + return xerrors.Errorf("recursing messages failed: %w", err) + } + cids = mcids + } + } + + if b.Height > 0 { + for _, p := range b.Parents { + blocksToWalk = append(blocksToWalk, p) + } + } else { + // include the genesis block + cids = append(cids, b.Parents...) + } + + out := cids + + if b.Height == 0 || b.Height > ts.Height()-inclRecentRoots { + if walked.Visit(b.ParentStateRoot) { + cids, err := recurseLinks(cs.stateBlockstore, walked, b.ParentStateRoot, []cid.Cid{b.ParentStateRoot}) + if err != nil { + return xerrors.Errorf("recursing genesis state failed: %w", err) + } + + out = append(out, cids...) + } + + if !skipMsgReceipts && walked.Visit(b.ParentMessageReceipts) { + out = append(out, b.ParentMessageReceipts) + } + } + + for _, c := range out { + if seen.Visit(c) { + if c.Prefix().Codec != cid.DagCBOR { + continue + } + + if err := cb(c); err != nil { + return err + } + + } + } + + return nil + } + + log.Infow("export started") + exportStart := build.Clock.Now() + + for len(blocksToWalk) > 0 { + next := blocksToWalk[0] + blocksToWalk = blocksToWalk[1:] + if err := walkChain(next); err != nil { + return xerrors.Errorf("walk chain failed: %w", err) + } + } + + log.Infow("export finished", "duration", build.Clock.Now().Sub(exportStart).Seconds()) + + return nil +} + +func recurseLinks(bs bstore.Blockstore, walked *cid.Set, root cid.Cid, in []cid.Cid) ([]cid.Cid, error) { + if root.Prefix().Codec != cid.DagCBOR { + return in, nil + } + + data, err := bs.Get(root) + if err != nil { + return nil, xerrors.Errorf("recurse links get (%s) failed: %w", root, err) + } + + var rerr error + err = cbg.ScanForLinks(bytes.NewReader(data.RawData()), func(c cid.Cid) { + if rerr != nil { + // No error return on ScanForLinks :( + return + } + + // traversed this already... + if !walked.Visit(c) { + return + } + + in = append(in, c) + var err error + in, err = recurseLinks(bs, walked, c, in) + if err != nil { + rerr = err + } + }) + if err != nil { + return nil, xerrors.Errorf("scanning for links failed: %w", err) + } + + return in, rerr +} diff --git a/chain/store/store.go b/chain/store/store.go index 523726863..df5936c37 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -1,36 +1,24 @@ package store import ( - "bytes" "context" - "encoding/binary" "encoding/json" "errors" - "io" "os" "strconv" "strings" "sync" "time" - "github.com/filecoin-project/lotus/chain/state" - "golang.org/x/sync/errgroup" - "github.com/filecoin-project/go-state-types/crypto" - "github.com/minio/blake2b-simd" - "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" - "github.com/filecoin-project/lotus/api" bstore "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/adt" - "github.com/filecoin-project/lotus/chain/actors/builtin" - "github.com/filecoin-project/lotus/chain/vm" "github.com/filecoin-project/lotus/journal" "github.com/filecoin-project/lotus/metrics" @@ -48,9 +36,6 @@ import ( "github.com/ipfs/go-datastore/query" cbor "github.com/ipfs/go-ipld-cbor" logging "github.com/ipfs/go-log/v2" - "github.com/ipld/go-car" - carutil "github.com/ipld/go-car/util" - cbg "github.com/whyrusleeping/cbor-gen" "github.com/whyrusleeping/pubsub" "golang.org/x/xerrors" ) @@ -134,11 +119,9 @@ type ChainStore struct { reorgCh chan<- reorg reorgNotifeeCh chan ReorgNotifee - mmCache *lru.ARCCache + mmCache *lru.ARCCache // msg meta cache (mh.Messages -> secp, bls []cid) tsCache *lru.ARCCache - vmcalls vm.SyscallBuilder - evtTypes [1]journal.EventType journal journal.Journal @@ -146,7 +129,7 @@ type ChainStore struct { wg sync.WaitGroup } -func NewChainStore(chainBs bstore.Blockstore, stateBs bstore.Blockstore, ds dstore.Batching, vmcalls vm.SyscallBuilder, j journal.Journal) *ChainStore { +func NewChainStore(chainBs bstore.Blockstore, stateBs bstore.Blockstore, ds dstore.Batching, j journal.Journal) *ChainStore { c, _ := lru.NewARC(DefaultMsgMetaCacheSize) tsc, _ := lru.NewARC(DefaultTipSetCacheSize) if j == nil { @@ -166,7 +149,6 @@ func NewChainStore(chainBs bstore.Blockstore, stateBs bstore.Blockstore, ds dsto tipsets: make(map[abi.ChainEpoch][]cid.Cid), mmCache: c, tsCache: tsc, - vmcalls: vmcalls, cancelFn: cancel, journal: j, } @@ -988,27 +970,6 @@ func (cs *ChainStore) PersistBlockHeaders(b ...*types.BlockHeader) error { return err } -type storable interface { - ToStorageBlock() (block.Block, error) -} - -func PutMessage(bs bstore.Blockstore, m storable) (cid.Cid, error) { - b, err := m.ToStorageBlock() - if err != nil { - return cid.Undef, err - } - - if err := bs.Put(b); err != nil { - return cid.Undef, err - } - - return b.Cid(), nil -} - -func (cs *ChainStore) PutMessage(m storable) (cid.Cid, error) { - return PutMessage(cs.chainBlockstore, m) -} - func (cs *ChainStore) expandTipset(b *types.BlockHeader) (*types.TipSet, error) { // Hold lock for the whole function for now, if it becomes a problem we can // fix pretty easily @@ -1080,203 +1041,6 @@ func (cs *ChainStore) GetGenesis() (*types.BlockHeader, error) { return cs.GetBlock(c) } -func (cs *ChainStore) GetCMessage(c cid.Cid) (types.ChainMsg, error) { - m, err := cs.GetMessage(c) - if err == nil { - return m, nil - } - if err != bstore.ErrNotFound { - log.Warnf("GetCMessage: unexpected error getting unsigned message: %s", err) - } - - return cs.GetSignedMessage(c) -} - -func (cs *ChainStore) GetMessage(c cid.Cid) (*types.Message, error) { - var msg *types.Message - err := cs.chainLocalBlockstore.View(c, func(b []byte) (err error) { - msg, err = types.DecodeMessage(b) - return err - }) - return msg, err -} - -func (cs *ChainStore) GetSignedMessage(c cid.Cid) (*types.SignedMessage, error) { - var msg *types.SignedMessage - err := cs.chainLocalBlockstore.View(c, func(b []byte) (err error) { - msg, err = types.DecodeSignedMessage(b) - return err - }) - return msg, err -} - -func (cs *ChainStore) readAMTCids(root cid.Cid) ([]cid.Cid, error) { - ctx := context.TODO() - // block headers use adt0, for now. - a, err := blockadt.AsArray(cs.ActorStore(ctx), root) - if err != nil { - return nil, xerrors.Errorf("amt load: %w", err) - } - - var ( - cids []cid.Cid - cborCid cbg.CborCid - ) - if err := a.ForEach(&cborCid, func(i int64) error { - c := cid.Cid(cborCid) - cids = append(cids, c) - return nil - }); err != nil { - return nil, xerrors.Errorf("failed to traverse amt: %w", err) - } - - if uint64(len(cids)) != a.Length() { - return nil, xerrors.Errorf("found %d cids, expected %d", len(cids), a.Length()) - } - - return cids, nil -} - -type BlockMessages struct { - Miner address.Address - BlsMessages []types.ChainMsg - SecpkMessages []types.ChainMsg - WinCount int64 -} - -func (cs *ChainStore) BlockMsgsForTipset(ts *types.TipSet) ([]BlockMessages, error) { - applied := make(map[address.Address]uint64) - - cst := cbor.NewCborStore(cs.stateBlockstore) - st, err := state.LoadStateTree(cst, ts.Blocks()[0].ParentStateRoot) - if err != nil { - return nil, xerrors.Errorf("failed to load state tree") - } - - selectMsg := func(m *types.Message) (bool, error) { - var sender address.Address - if ts.Height() >= build.UpgradeHyperdriveHeight { - sender, err = st.LookupID(m.From) - if err != nil { - return false, err - } - } else { - sender = m.From - } - - // The first match for a sender is guaranteed to have correct nonce -- the block isn't valid otherwise - if _, ok := applied[sender]; !ok { - applied[sender] = m.Nonce - } - - if applied[sender] != m.Nonce { - return false, nil - } - - applied[sender]++ - - return true, nil - } - - var out []BlockMessages - for _, b := range ts.Blocks() { - - bms, sms, err := cs.MessagesForBlock(b) - if err != nil { - return nil, xerrors.Errorf("failed to get messages for block: %w", err) - } - - bm := BlockMessages{ - Miner: b.Miner, - BlsMessages: make([]types.ChainMsg, 0, len(bms)), - SecpkMessages: make([]types.ChainMsg, 0, len(sms)), - WinCount: b.ElectionProof.WinCount, - } - - for _, bmsg := range bms { - b, err := selectMsg(bmsg.VMMessage()) - if err != nil { - return nil, xerrors.Errorf("failed to decide whether to select message for block: %w", err) - } - - if b { - bm.BlsMessages = append(bm.BlsMessages, bmsg) - } - } - - for _, smsg := range sms { - b, err := selectMsg(smsg.VMMessage()) - if err != nil { - return nil, xerrors.Errorf("failed to decide whether to select message for block: %w", err) - } - - if b { - bm.SecpkMessages = append(bm.SecpkMessages, smsg) - } - } - - out = append(out, bm) - } - - return out, nil -} - -func (cs *ChainStore) MessagesForTipset(ts *types.TipSet) ([]types.ChainMsg, error) { - bmsgs, err := cs.BlockMsgsForTipset(ts) - if err != nil { - return nil, err - } - - var out []types.ChainMsg - for _, bm := range bmsgs { - for _, blsm := range bm.BlsMessages { - out = append(out, blsm) - } - - for _, secm := range bm.SecpkMessages { - out = append(out, secm) - } - } - - return out, nil -} - -type mmCids struct { - bls []cid.Cid - secpk []cid.Cid -} - -func (cs *ChainStore) ReadMsgMetaCids(mmc cid.Cid) ([]cid.Cid, []cid.Cid, error) { - o, ok := cs.mmCache.Get(mmc) - if ok { - mmcids := o.(*mmCids) - return mmcids.bls, mmcids.secpk, nil - } - - cst := cbor.NewCborStore(cs.chainLocalBlockstore) - var msgmeta types.MsgMeta - if err := cst.Get(context.TODO(), mmc, &msgmeta); err != nil { - return nil, nil, xerrors.Errorf("failed to load msgmeta (%s): %w", mmc, err) - } - - blscids, err := cs.readAMTCids(msgmeta.BlsMessages) - if err != nil { - return nil, nil, xerrors.Errorf("loading bls message cids for block: %w", err) - } - - secpkcids, err := cs.readAMTCids(msgmeta.SecpkMessages) - if err != nil { - return nil, nil, xerrors.Errorf("loading secpk message cids for block: %w", err) - } - - cs.mmCache.Add(mmc, &mmCids{ - bls: blscids, - secpk: secpkcids, - }) - - return blscids, secpkcids, nil -} - // GetPath returns the sequence of atomic head change operations that // need to be applied in order to switch the head of the chain from the `from` // tipset to the `to` tipset. @@ -1304,71 +1068,6 @@ func (cs *ChainStore) GetPath(ctx context.Context, from types.TipSetKey, to type return path, nil } -func (cs *ChainStore) MessagesForBlock(b *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) { - blscids, secpkcids, err := cs.ReadMsgMetaCids(b.Messages) - if err != nil { - return nil, nil, err - } - - blsmsgs, err := cs.LoadMessagesFromCids(blscids) - if err != nil { - return nil, nil, xerrors.Errorf("loading bls messages for block: %w", err) - } - - secpkmsgs, err := cs.LoadSignedMessagesFromCids(secpkcids) - if err != nil { - return nil, nil, xerrors.Errorf("loading secpk messages for block: %w", err) - } - - return blsmsgs, secpkmsgs, nil -} - -func (cs *ChainStore) GetParentReceipt(b *types.BlockHeader, i int) (*types.MessageReceipt, error) { - ctx := context.TODO() - // block headers use adt0, for now. - a, err := blockadt.AsArray(cs.ActorStore(ctx), b.ParentMessageReceipts) - if err != nil { - return nil, xerrors.Errorf("amt load: %w", err) - } - - var r types.MessageReceipt - if found, err := a.Get(uint64(i), &r); err != nil { - return nil, err - } else if !found { - return nil, xerrors.Errorf("failed to find receipt %d", i) - } - - return &r, nil -} - -func (cs *ChainStore) LoadMessagesFromCids(cids []cid.Cid) ([]*types.Message, error) { - msgs := make([]*types.Message, 0, len(cids)) - for i, c := range cids { - m, err := cs.GetMessage(c) - if err != nil { - return nil, xerrors.Errorf("failed to get message: (%s):%d: %w", c, i, err) - } - - msgs = append(msgs, m) - } - - return msgs, nil -} - -func (cs *ChainStore) LoadSignedMessagesFromCids(cids []cid.Cid) ([]*types.SignedMessage, error) { - msgs := make([]*types.SignedMessage, 0, len(cids)) - for i, c := range cids { - m, err := cs.GetSignedMessage(c) - if err != nil { - return nil, xerrors.Errorf("failed to get message: (%s):%d: %w", c, i, err) - } - - msgs = append(msgs, m) - } - - return msgs, nil -} - // ChainBlockstore returns the chain blockstore. Currently the chain and state // // stores are both backed by the same physical store, albeit with different // // caching policies, but in the future they will segregate. @@ -1391,10 +1090,6 @@ func (cs *ChainStore) ActorStore(ctx context.Context) adt.Store { return ActorStore(ctx, cs.stateBlockstore) } -func (cs *ChainStore) VMSys() vm.SyscallBuilder { - return cs.vmcalls -} - func (cs *ChainStore) TryFillTipSet(ts *types.TipSet) (*FullTipSet, error) { var out []*types.FullBlock @@ -1417,108 +1112,6 @@ func (cs *ChainStore) TryFillTipSet(ts *types.TipSet) (*FullTipSet, error) { return NewFullTipSet(out), nil } -func DrawRandomness(rbase []byte, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - h := blake2b.New256() - if err := binary.Write(h, binary.BigEndian, int64(pers)); err != nil { - return nil, xerrors.Errorf("deriving randomness: %w", err) - } - VRFDigest := blake2b.Sum256(rbase) - _, err := h.Write(VRFDigest[:]) - if err != nil { - return nil, xerrors.Errorf("hashing VRFDigest: %w", err) - } - if err := binary.Write(h, binary.BigEndian, round); err != nil { - return nil, xerrors.Errorf("deriving randomness: %w", err) - } - _, err = h.Write(entropy) - if err != nil { - return nil, xerrors.Errorf("hashing entropy: %w", err) - } - - return h.Sum(nil), nil -} - -func (cs *ChainStore) GetBeaconRandomnessLookingBack(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - return cs.GetBeaconRandomness(ctx, blks, pers, round, entropy, true) -} - -func (cs *ChainStore) GetBeaconRandomnessLookingForward(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - return cs.GetBeaconRandomness(ctx, blks, pers, round, entropy, false) -} - -func (cs *ChainStore) GetBeaconRandomness(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte, lookback bool) ([]byte, error) { - _, span := trace.StartSpan(ctx, "store.GetBeaconRandomness") - defer span.End() - span.AddAttributes(trace.Int64Attribute("round", int64(round))) - - ts, err := cs.LoadTipSet(types.NewTipSetKey(blks...)) - if err != nil { - return nil, err - } - - if round > ts.Height() { - return nil, xerrors.Errorf("cannot draw randomness from the future") - } - - searchHeight := round - if searchHeight < 0 { - searchHeight = 0 - } - - randTs, err := cs.GetTipsetByHeight(ctx, searchHeight, ts, lookback) - if err != nil { - return nil, err - } - - be, err := cs.GetLatestBeaconEntry(randTs) - if err != nil { - return nil, err - } - - // if at (or just past -- for null epochs) appropriate epoch - // or at genesis (works for negative epochs) - return DrawRandomness(be.Data, pers, round, entropy) -} - -func (cs *ChainStore) GetChainRandomnessLookingBack(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - return cs.GetChainRandomness(ctx, blks, pers, round, entropy, true) -} - -func (cs *ChainStore) GetChainRandomnessLookingForward(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - return cs.GetChainRandomness(ctx, blks, pers, round, entropy, false) -} - -func (cs *ChainStore) GetChainRandomness(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte, lookback bool) ([]byte, error) { - _, span := trace.StartSpan(ctx, "store.GetChainRandomness") - defer span.End() - span.AddAttributes(trace.Int64Attribute("round", int64(round))) - - ts, err := cs.LoadTipSet(types.NewTipSetKey(blks...)) - if err != nil { - return nil, err - } - - if round > ts.Height() { - return nil, xerrors.Errorf("cannot draw randomness from the future") - } - - searchHeight := round - if searchHeight < 0 { - searchHeight = 0 - } - - randTs, err := cs.GetTipsetByHeight(ctx, searchHeight, ts, lookback) - if err != nil { - return nil, err - } - - mtb := randTs.MinTicketBlock() - - // if at (or just past -- for null epochs) appropriate epoch - // or at genesis (works for negative epochs) - return DrawRandomness(mtb.Ticket.VRFProof, pers, round, entropy) -} - // GetTipsetByHeight returns the tipset on the chain behind 'ts' at the given // height. In the case that the given height is a null round, the 'prev' flag // selects the tipset before the null round if true, and the tipset following @@ -1555,252 +1148,3 @@ func (cs *ChainStore) GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, t return cs.LoadTipSet(lbts.Parents()) } - -func recurseLinks(bs bstore.Blockstore, walked *cid.Set, root cid.Cid, in []cid.Cid) ([]cid.Cid, error) { - if root.Prefix().Codec != cid.DagCBOR { - return in, nil - } - - data, err := bs.Get(root) - if err != nil { - return nil, xerrors.Errorf("recurse links get (%s) failed: %w", root, err) - } - - var rerr error - err = cbg.ScanForLinks(bytes.NewReader(data.RawData()), func(c cid.Cid) { - if rerr != nil { - // No error return on ScanForLinks :( - return - } - - // traversed this already... - if !walked.Visit(c) { - return - } - - in = append(in, c) - var err error - in, err = recurseLinks(bs, walked, c, in) - if err != nil { - rerr = err - } - }) - if err != nil { - return nil, xerrors.Errorf("scanning for links failed: %w", err) - } - - return in, rerr -} - -func (cs *ChainStore) Export(ctx context.Context, ts *types.TipSet, inclRecentRoots abi.ChainEpoch, skipOldMsgs bool, w io.Writer) error { - h := &car.CarHeader{ - Roots: ts.Cids(), - Version: 1, - } - - if err := car.WriteHeader(h, w); err != nil { - return xerrors.Errorf("failed to write car header: %s", err) - } - - unionBs := bstore.Union(cs.stateBlockstore, cs.chainBlockstore) - return cs.WalkSnapshot(ctx, ts, inclRecentRoots, skipOldMsgs, true, func(c cid.Cid) error { - blk, err := unionBs.Get(c) - if err != nil { - return xerrors.Errorf("writing object to car, bs.Get: %w", err) - } - - if err := carutil.LdWrite(w, c.Bytes(), blk.RawData()); err != nil { - return xerrors.Errorf("failed to write block to car output: %w", err) - } - - return nil - }) -} - -func (cs *ChainStore) WalkSnapshot(ctx context.Context, ts *types.TipSet, inclRecentRoots abi.ChainEpoch, skipOldMsgs, skipMsgReceipts bool, cb func(cid.Cid) error) error { - if ts == nil { - ts = cs.GetHeaviestTipSet() - } - - seen := cid.NewSet() - walked := cid.NewSet() - - blocksToWalk := ts.Cids() - currentMinHeight := ts.Height() - - walkChain := func(blk cid.Cid) error { - if !seen.Visit(blk) { - return nil - } - - if err := cb(blk); err != nil { - return err - } - - data, err := cs.chainBlockstore.Get(blk) - if err != nil { - return xerrors.Errorf("getting block: %w", err) - } - - var b types.BlockHeader - if err := b.UnmarshalCBOR(bytes.NewBuffer(data.RawData())); err != nil { - return xerrors.Errorf("unmarshaling block header (cid=%s): %w", blk, err) - } - - if currentMinHeight > b.Height { - currentMinHeight = b.Height - if currentMinHeight%builtin.EpochsInDay == 0 { - log.Infow("export", "height", currentMinHeight) - } - } - - var cids []cid.Cid - if !skipOldMsgs || b.Height > ts.Height()-inclRecentRoots { - if walked.Visit(b.Messages) { - mcids, err := recurseLinks(cs.chainBlockstore, walked, b.Messages, []cid.Cid{b.Messages}) - if err != nil { - return xerrors.Errorf("recursing messages failed: %w", err) - } - cids = mcids - } - } - - if b.Height > 0 { - for _, p := range b.Parents { - blocksToWalk = append(blocksToWalk, p) - } - } else { - // include the genesis block - cids = append(cids, b.Parents...) - } - - out := cids - - if b.Height == 0 || b.Height > ts.Height()-inclRecentRoots { - if walked.Visit(b.ParentStateRoot) { - cids, err := recurseLinks(cs.stateBlockstore, walked, b.ParentStateRoot, []cid.Cid{b.ParentStateRoot}) - if err != nil { - return xerrors.Errorf("recursing genesis state failed: %w", err) - } - - out = append(out, cids...) - } - - if !skipMsgReceipts && walked.Visit(b.ParentMessageReceipts) { - out = append(out, b.ParentMessageReceipts) - } - } - - for _, c := range out { - if seen.Visit(c) { - if c.Prefix().Codec != cid.DagCBOR { - continue - } - - if err := cb(c); err != nil { - return err - } - - } - } - - return nil - } - - log.Infow("export started") - exportStart := build.Clock.Now() - - for len(blocksToWalk) > 0 { - next := blocksToWalk[0] - blocksToWalk = blocksToWalk[1:] - if err := walkChain(next); err != nil { - return xerrors.Errorf("walk chain failed: %w", err) - } - } - - log.Infow("export finished", "duration", build.Clock.Now().Sub(exportStart).Seconds()) - - return nil -} - -func (cs *ChainStore) Import(r io.Reader) (*types.TipSet, error) { - // TODO: writing only to the state blockstore is incorrect. - // At this time, both the state and chain blockstores are backed by the - // universal store. When we physically segregate the stores, we will need - // to route state objects to the state blockstore, and chain objects to - // the chain blockstore. - header, err := car.LoadCar(cs.StateBlockstore(), r) - if err != nil { - return nil, xerrors.Errorf("loadcar failed: %w", err) - } - - root, err := cs.LoadTipSet(types.NewTipSetKey(header.Roots...)) - if err != nil { - return nil, xerrors.Errorf("failed to load root tipset from chainfile: %w", err) - } - - return root, nil -} - -func (cs *ChainStore) GetLatestBeaconEntry(ts *types.TipSet) (*types.BeaconEntry, error) { - cur := ts - for i := 0; i < 20; i++ { - cbe := cur.Blocks()[0].BeaconEntries - if len(cbe) > 0 { - return &cbe[len(cbe)-1], nil - } - - if cur.Height() == 0 { - return nil, xerrors.Errorf("made it back to genesis block without finding beacon entry") - } - - next, err := cs.LoadTipSet(cur.Parents()) - if err != nil { - return nil, xerrors.Errorf("failed to load parents when searching back for latest beacon entry: %w", err) - } - cur = next - } - - if os.Getenv("LOTUS_IGNORE_DRAND") == "_yes_" { - return &types.BeaconEntry{ - Data: []byte{9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}, - }, nil - } - - return nil, xerrors.Errorf("found NO beacon entries in the 20 latest tipsets") -} - -type chainRand struct { - cs *ChainStore - blks []cid.Cid -} - -func NewChainRand(cs *ChainStore, blks []cid.Cid) vm.Rand { - return &chainRand{ - cs: cs, - blks: blks, - } -} - -func (cr *chainRand) GetChainRandomnessLookingBack(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - return cr.cs.GetChainRandomnessLookingBack(ctx, cr.blks, pers, round, entropy) -} - -func (cr *chainRand) GetChainRandomnessLookingForward(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - return cr.cs.GetChainRandomnessLookingForward(ctx, cr.blks, pers, round, entropy) -} - -func (cr *chainRand) GetBeaconRandomnessLookingBack(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - return cr.cs.GetBeaconRandomnessLookingBack(ctx, cr.blks, pers, round, entropy) -} - -func (cr *chainRand) GetBeaconRandomnessLookingForward(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - return cr.cs.GetBeaconRandomnessLookingForward(ctx, cr.blks, pers, round, entropy) -} - -func (cs *ChainStore) GetTipSetFromKey(tsk types.TipSetKey) (*types.TipSet, error) { - if tsk.IsEmpty() { - return cs.GetHeaviestTipSet(), nil - } - return cs.LoadTipSet(tsk) -} diff --git a/chain/store/store_test.go b/chain/store/store_test.go index 62a0430e3..2db2f061b 100644 --- a/chain/store/store_test.go +++ b/chain/store/store_test.go @@ -70,7 +70,7 @@ func BenchmarkGetRandomness(b *testing.B) { b.Fatal(err) } - cs := store.NewChainStore(bs, bs, mds, nil, nil) + cs := store.NewChainStore(bs, bs, mds, nil) defer cs.Close() //nolint:errcheck b.ResetTimer() @@ -105,7 +105,7 @@ func TestChainExportImport(t *testing.T) { } nbs := blockstore.NewMemory() - cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), nil, nil) + cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), nil) defer cs.Close() //nolint:errcheck root, err := cs.Import(buf) @@ -140,7 +140,7 @@ func TestChainExportImportFull(t *testing.T) { } nbs := blockstore.NewMemory() - cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), nil, nil) + cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), nil) defer cs.Close() //nolint:errcheck root, err := cs.Import(buf) @@ -157,7 +157,7 @@ func TestChainExportImportFull(t *testing.T) { t.Fatal("imported chain differed from exported chain") } - sm := stmgr.NewStateManager(cs) + sm := stmgr.NewStateManager(cs, nil) for i := 0; i < 100; i++ { ts, err := cs.GetTipsetByHeight(context.TODO(), abi.ChainEpoch(i), nil, false) if err != nil { diff --git a/chain/types/cbor_gen.go b/chain/types/cbor_gen.go index db1f3cdb2..b54c18c07 100644 --- a/chain/types/cbor_gen.go +++ b/chain/types/cbor_gen.go @@ -5,6 +5,7 @@ package types import ( "fmt" "io" + "math" "sort" abi "github.com/filecoin-project/go-state-types/abi" @@ -18,6 +19,7 @@ import ( var _ = xerrors.Errorf var _ = cid.Undef +var _ = math.E var _ = sort.Sort var lengthBufBlockHeader = []byte{144} diff --git a/chain/vm/invoker.go b/chain/vm/invoker.go index be35d93c8..d31a9010f 100644 --- a/chain/vm/invoker.go +++ b/chain/vm/invoker.go @@ -231,7 +231,7 @@ func DumpActorState(act *types.Actor, b []byte) (interface{}, error) { return nil, nil } - i := NewActorRegistry() // TODO: register builtins in init block + i := NewActorRegistry() actInfo, ok := i.actors[act.Code] if !ok { diff --git a/chain/vm/invoker_test.go b/chain/vm/invoker_test.go index 6822e2371..8499f001a 100644 --- a/chain/vm/invoker_test.go +++ b/chain/vm/invoker_test.go @@ -6,6 +6,8 @@ import ( "io" "testing" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/network" cbor "github.com/ipfs/go-ipld-cbor" @@ -80,6 +82,30 @@ func (basicContract) InvokeSomething10(rt runtime2.Runtime, params *basicParams) return nil } +type basicRtMessage struct{} + +var _ runtime2.Message = (*basicRtMessage)(nil) + +func (*basicRtMessage) Caller() address.Address { + a, err := address.NewIDAddress(0) + if err != nil { + panic(err) + } + return a +} + +func (*basicRtMessage) Receiver() address.Address { + a, err := address.NewIDAddress(1) + if err != nil { + panic(err) + } + return a +} + +func (*basicRtMessage) ValueReceived() abi.TokenAmount { + return big.NewInt(0) +} + func TestInvokerBasic(t *testing.T) { inv := ActorRegistry{} code, err := inv.transform(basicContract{}) @@ -89,7 +115,7 @@ func TestInvokerBasic(t *testing.T) { bParam, err := actors.SerializeParams(&basicParams{B: 1}) assert.NoError(t, err) - _, aerr := code[0](&Runtime{}, bParam) + _, aerr := code[0](&Runtime{Message: &basicRtMessage{}}, bParam) assert.Equal(t, exitcode.ExitCode(1), aerrors.RetCode(aerr), "return code should be 1") if aerrors.IsFatal(aerr) { @@ -101,7 +127,7 @@ func TestInvokerBasic(t *testing.T) { bParam, err := actors.SerializeParams(&basicParams{B: 2}) assert.NoError(t, err) - _, aerr := code[10](&Runtime{}, bParam) + _, aerr := code[10](&Runtime{Message: &basicRtMessage{}}, bParam) assert.Equal(t, exitcode.ExitCode(12), aerrors.RetCode(aerr), "return code should be 12") if aerrors.IsFatal(aerr) { t.Fatal("err should not be fatal") @@ -113,6 +139,7 @@ func TestInvokerBasic(t *testing.T) { vm: &VM{ntwkVersion: func(ctx context.Context, epoch abi.ChainEpoch) network.Version { return network.Version0 }}, + Message: &basicRtMessage{}, }, []byte{99}) if aerrors.IsFatal(aerr) { t.Fatal("err should not be fatal") @@ -125,6 +152,7 @@ func TestInvokerBasic(t *testing.T) { vm: &VM{ntwkVersion: func(ctx context.Context, epoch abi.ChainEpoch) network.Version { return network.Version7 }}, + Message: &basicRtMessage{}, }, []byte{99}) if aerrors.IsFatal(aerr) { t.Fatal("err should not be fatal") diff --git a/chain/vm/runtime.go b/chain/vm/runtime.go index 2845c7696..7117f3443 100644 --- a/chain/vm/runtime.go +++ b/chain/vm/runtime.go @@ -146,7 +146,7 @@ func (rt *Runtime) shimCall(f func() interface{}) (rval []byte, aerr aerrors.Act defer func() { if r := recover(); r != nil { if ar, ok := r.(aerrors.ActorError); ok { - log.Warnf("VM.Call failure: %+v", ar) + log.Warnf("VM.Call failure in call from: %s to %s: %+v", rt.Caller(), rt.Receiver(), ar) aerr = ar return } @@ -391,7 +391,7 @@ func (rt *Runtime) Send(to address.Address, method abi.MethodNum, m cbor.Marshal if err.IsFatal() { panic(err) } - log.Warnf("vmctx send failed: to: %s, method: %d: ret: %d, err: %s", to, method, ret, err) + log.Warnf("vmctx send failed: from: %s to: %s, method: %d: err: %s", rt.Receiver(), to, method, err) return err.RetCode() } _ = rt.chargeGasSafe(gasOnActorExec) diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 5a31187b7..199896671 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -641,15 +641,6 @@ func (vm *VM) ShouldBurn(ctx context.Context, st *state.StateTree, msg *types.Me return true, nil } -func (vm *VM) ActorBalance(addr address.Address) (types.BigInt, aerrors.ActorError) { - act, err := vm.cstate.GetActor(addr) - if err != nil { - return types.EmptyInt, aerrors.Absorb(err, 1, "failed to find actor") - } - - return act.Balance, nil -} - type vmFlushKey struct{} func (vm *VM) Flush(ctx context.Context) (cid.Cid, error) { diff --git a/cli/auth.go b/cli/auth.go index 286eb978b..e6eeace89 100644 --- a/cli/auth.go +++ b/cli/auth.go @@ -123,7 +123,7 @@ var AuthApiInfoToken = &cli.Command{ ainfo, err := GetAPIInfo(cctx, t) if err != nil { - return xerrors.Errorf("could not get API info: %w", err) + return xerrors.Errorf("could not get API info for %s: %w", t, err) } // TODO: Log in audit log when it is implemented diff --git a/cli/chain.go b/cli/chain.go index e30a685dd..875dcb21a 100644 --- a/cli/chain.go +++ b/cli/chain.go @@ -10,7 +10,6 @@ import ( "os" "os/exec" "path" - "reflect" "sort" "strconv" "strings" @@ -1347,7 +1346,7 @@ var ChainEncodeCmd = &cli.Command{ var chainEncodeParamsCmd = &cli.Command{ Name: "params", Usage: "Encodes the given JSON params", - ArgsUsage: "[toAddr method params]", + ArgsUsage: "[dest method params]", Flags: []cli.Flag{ &cli.StringFlag{ Name: "tipset", @@ -1357,62 +1356,65 @@ var chainEncodeParamsCmd = &cli.Command{ Value: "base64", Usage: "specify input encoding to parse", }, + &cli.BoolFlag{ + Name: "to-code", + Usage: "interpret dest as code CID instead of as address", + }, }, Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - if cctx.Args().Len() != 3 { return ShowHelp(cctx, fmt.Errorf("incorrect number of arguments")) } - to, err := address.NewFromString(cctx.Args().First()) - if err != nil { - return xerrors.Errorf("parsing toAddr: %w", err) - } - method, err := strconv.ParseInt(cctx.Args().Get(1), 10, 64) if err != nil { return xerrors.Errorf("parsing method id: %w", err) } - ts, err := LoadTipSet(ctx, cctx, api) - if err != nil { - return err - } + ctx := ReqContext(cctx) - act, err := api.StateGetActor(ctx, to, ts.Key()) - if err != nil { - return xerrors.Errorf("getting actor: %w", err) - } + var p []byte + if !cctx.Bool("to-code") { + svc, err := GetFullNodeServices(cctx) + if err != nil { + return err + } + defer svc.Close() // nolint - methodMeta, found := stmgr.MethodsMap[act.Code][abi.MethodNum(method)] - if !found { - return fmt.Errorf("method %d not found on actor %s", method, act.Code) - } + to, err := address.NewFromString(cctx.Args().First()) + if err != nil { + return xerrors.Errorf("parsing to addr: %w", err) + } - p := reflect.New(methodMeta.Params.Elem()).Interface().(cbg.CBORMarshaler) + p, err = svc.DecodeTypedParamsFromJSON(ctx, to, abi.MethodNum(method), cctx.Args().Get(2)) + if err != nil { + return xerrors.Errorf("decoding json params: %w", err) + } + } else { + api, done, err := GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer done() - if err := json.Unmarshal([]byte(cctx.Args().Get(2)), p); err != nil { - return fmt.Errorf("unmarshaling input into params type: %w", err) - } + to, err := cid.Parse(cctx.Args().First()) + if err != nil { + return xerrors.Errorf("parsing to addr: %w", err) + } - buf := new(bytes.Buffer) - if err := p.MarshalCBOR(buf); err != nil { - return err + p, err = api.StateEncodeParams(ctx, to, abi.MethodNum(method), json.RawMessage(cctx.Args().Get(2))) + if err != nil { + return xerrors.Errorf("decoding json params: %w", err) + } } switch cctx.String("encoding") { - case "base64": - fmt.Println(base64.StdEncoding.EncodeToString(buf.Bytes())) + case "base64", "b64": + fmt.Println(base64.StdEncoding.EncodeToString(p)) case "hex": - fmt.Println(hex.EncodeToString(buf.Bytes())) + fmt.Println(hex.EncodeToString(p)) default: - return xerrors.Errorf("unrecognized encoding: %s", cctx.String("encoding")) + return xerrors.Errorf("unknown encoding") } return nil diff --git a/cli/client.go b/cli/client.go index 774d9aa5f..549589d64 100644 --- a/cli/client.go +++ b/cli/client.go @@ -24,7 +24,6 @@ import ( "github.com/docker/go-units" "github.com/fatih/color" datatransfer "github.com/filecoin-project/go-data-transfer" - "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/ipfs/go-cid" "github.com/ipfs/go-cidutil/cidenc" "github.com/libp2p/go-libp2p-core/peer" @@ -32,12 +31,14 @@ import ( "github.com/urfave/cli/v2" "golang.org/x/xerrors" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" + "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-multistore" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/lotus/api" lapi "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/v0api" @@ -46,6 +47,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/market" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/lib/tablewriter" + "github.com/filecoin-project/lotus/node/repo/imports" ) var CidBaseFlag = cli.StringFlag{ @@ -174,18 +176,18 @@ var clientDropCmd = &cli.Command{ defer closer() ctx := ReqContext(cctx) - var ids []multistore.StoreID + var ids []uint64 for i, s := range cctx.Args().Slice() { - id, err := strconv.ParseInt(s, 10, 0) + id, err := strconv.ParseUint(s, 10, 64) if err != nil { return xerrors.Errorf("parsing %d-th import ID: %w", i, err) } - ids = append(ids, multistore.StoreID(id)) + ids = append(ids, id) } for _, id := range ids { - if err := api.ClientRemoveImport(ctx, id); err != nil { + if err := api.ClientRemoveImport(ctx, imports.ID(id)); err != nil { return xerrors.Errorf("removing import %d: %w", id, err) } } @@ -1104,8 +1106,8 @@ var clientRetrieveCmd = &cli.Command{ for _, i := range imports { if i.Root != nil && i.Root.Equals(file) { order = &lapi.RetrievalOrder{ - Root: file, - LocalStore: &i.Key, + Root: file, + FromLocalCAR: i.CARPath, Total: big.Zero(), UnsealPrice: big.Zero(), diff --git a/cli/mpool.go b/cli/mpool.go index b128ccc15..adefd25a8 100644 --- a/cli/mpool.go +++ b/cli/mpool.go @@ -381,8 +381,8 @@ var MpoolReplaceCmd = &cli.Command{ Usage: "automatically reprice the specified message", }, &cli.StringFlag{ - Name: "max-fee", - Usage: "Spend up to X attoFIL for this message (applicable for auto mode)", + Name: "fee-limit", + Usage: "Spend up to X FIL for this message in units of FIL. Previously when flag was `max-fee` units were in attoFIL. Applicable for auto mode", }, }, ArgsUsage: " | ", @@ -457,13 +457,13 @@ var MpoolReplaceCmd = &cli.Command{ minRBF := messagepool.ComputeMinRBF(msg.GasPremium) var mss *lapi.MessageSendSpec - if cctx.IsSet("max-fee") { - maxFee, err := types.BigFromString(cctx.String("max-fee")) + if cctx.IsSet("fee-limit") { + maxFee, err := types.ParseFIL(cctx.String("fee-limit")) if err != nil { return fmt.Errorf("parsing max-spend: %w", err) } mss = &lapi.MessageSendSpec{ - MaxFee: maxFee, + MaxFee: abi.TokenAmount(maxFee), } } diff --git a/cli/pprof.go b/cli/pprof.go index 0da245910..ae4016e11 100644 --- a/cli/pprof.go +++ b/cli/pprof.go @@ -34,7 +34,7 @@ var PprofGoroutines = &cli.Command{ } ainfo, err := GetAPIInfo(cctx, t) if err != nil { - return xerrors.Errorf("could not get API info: %w", err) + return xerrors.Errorf("could not get API info for %s: %w", t, err) } addr, err := ainfo.Host() if err != nil { diff --git a/cli/util/api.go b/cli/util/api.go index 37df41a87..4a7247b32 100644 --- a/cli/util/api.go +++ b/cli/util/api.go @@ -2,6 +2,7 @@ package cliutil import ( "context" + "errors" "fmt" "net/http" "net/url" @@ -142,6 +143,15 @@ func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) { return APIInfo{}, xerrors.Errorf("could not open repo at path: %s; %w", p, err) } + exists, err := r.Exists() + if err != nil { + return APIInfo{}, xerrors.Errorf("repo.Exists returned an error: %w", err) + } + + if !exists { + return APIInfo{}, errors.New("repo directory does not exist. Make sure your configuration is correct") + } + ma, err := r.APIEndpoint() if err != nil { return APIInfo{}, xerrors.Errorf("could not get api endpoint: %w", err) @@ -171,7 +181,7 @@ func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) { func GetRawAPI(ctx *cli.Context, t repo.RepoType, version string) (string, http.Header, error) { ainfo, err := GetAPIInfo(ctx, t) if err != nil { - return "", nil, xerrors.Errorf("could not get API info: %w", err) + return "", nil, xerrors.Errorf("could not get API info for %s: %w", t, err) } addr, err := ainfo.DialArgs(version) @@ -243,7 +253,19 @@ func GetFullNodeAPIV1(ctx *cli.Context) (v1api.FullNode, jsonrpc.ClientCloser, e _, _ = fmt.Fprintln(ctx.App.Writer, "using full node API v1 endpoint:", addr) } - return client.NewFullNodeRPCV1(ctx.Context, addr, headers) + v1API, closer, err := client.NewFullNodeRPCV1(ctx.Context, addr, headers) + if err != nil { + return nil, nil, err + } + + v, err := v1API.Version(ctx.Context) + if err != nil { + return nil, nil, err + } + if !v.APIVersion.EqMajorMinor(api.FullAPIVersion1) { + return nil, nil, xerrors.Errorf("Remote API version didn't match (expected %s, remote %s)", api.FullAPIVersion1, v.APIVersion) + } + return v1API, closer, nil } type GetStorageMinerOptions struct { diff --git a/cli/wait.go b/cli/wait.go index ea897d5ad..5fc5fa469 100644 --- a/cli/wait.go +++ b/cli/wait.go @@ -22,7 +22,7 @@ var WaitApiCmd = &cli.Command{ ctx := ReqContext(cctx) - _, err = api.ID(ctx) + _, err = api.Version(ctx) if err != nil { return err } diff --git a/cmd/lotus-bench/import.go b/cmd/lotus-bench/import.go index 4b464bebe..d8ef57138 100644 --- a/cmd/lotus-bench/import.go +++ b/cmd/lotus-bench/import.go @@ -253,10 +253,10 @@ var importBenchCmd = &cli.Command{ } metadataDs := datastore.NewMapDatastore() - cs := store.NewChainStore(bs, bs, metadataDs, vm.Syscalls(verifier), nil) + cs := store.NewChainStore(bs, bs, metadataDs, nil) defer cs.Close() //nolint:errcheck - stm := stmgr.NewStateManager(cs) + stm := stmgr.NewStateManager(cs, vm.Syscalls(verifier)) var carFile *os.File // open the CAR file if one is provided. diff --git a/cmd/lotus-chainwatch/dot.go b/cmd/lotus-chainwatch/dot.go deleted file mode 100644 index 3149d65f5..000000000 --- a/cmd/lotus-chainwatch/dot.go +++ /dev/null @@ -1,131 +0,0 @@ -package main - -import ( - "database/sql" - "fmt" - "hash/crc32" - "strconv" - - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" -) - -var dotCmd = &cli.Command{ - Name: "dot", - Usage: "generate dot graphs", - ArgsUsage: " ", - Action: func(cctx *cli.Context) error { - ll := cctx.String("log-level") - if err := logging.SetLogLevel("*", ll); err != nil { - return err - } - - db, err := sql.Open("postgres", cctx.String("db")) - if err != nil { - return err - } - defer func() { - if err := db.Close(); err != nil { - log.Errorw("Failed to close database", "error", err) - } - }() - - if err := db.Ping(); err != nil { - return xerrors.Errorf("Database failed to respond to ping (is it online?): %w", err) - } - - minH, err := strconv.ParseInt(cctx.Args().Get(0), 10, 32) - if err != nil { - return err - } - tosee, err := strconv.ParseInt(cctx.Args().Get(1), 10, 32) - if err != nil { - return err - } - maxH := minH + tosee - - res, err := db.Query(`select block, parent, b.miner, b.height, p.height from block_parents - inner join blocks b on block_parents.block = b.cid - inner join blocks p on block_parents.parent = p.cid -where b.height > $1 and b.height < $2`, minH, maxH) - - if err != nil { - return err - } - - fmt.Println("digraph D {") - - hl, err := syncedBlocks(db) - if err != nil { - log.Fatal(err) - } - - for res.Next() { - var block, parent, miner string - var height, ph uint64 - if err := res.Scan(&block, &parent, &miner, &height, &ph); err != nil { - return err - } - - bc, err := cid.Parse(block) - if err != nil { - return err - } - - _, has := hl[bc] - - col := crc32.Checksum([]byte(miner), crc32.MakeTable(crc32.Castagnoli))&0xc0c0c0c0 + 0x30303030 - - hasstr := "" - if !has { - //col = 0xffffffff - hasstr = " UNSYNCED" - } - - nulls := height - ph - 1 - for i := uint64(0); i < nulls; i++ { - name := block + "NP" + fmt.Sprint(i) - - fmt.Printf("%s [label = \"NULL:%d\", fillcolor = \"#ffddff\", style=filled, forcelabels=true]\n%s -> %s\n", - name, height-nulls+i, name, parent) - - parent = name - } - - fmt.Printf("%s [label = \"%s:%d%s\", fillcolor = \"#%06x\", style=filled, forcelabels=true]\n%s -> %s\n", block, miner, height, hasstr, col, block, parent) - } - if res.Err() != nil { - return res.Err() - } - - fmt.Println("}") - - return nil - }, -} - -func syncedBlocks(db *sql.DB) (map[cid.Cid]struct{}, error) { - // timestamp is used to return a configurable amount of rows based on when they were last added. - rws, err := db.Query(`select cid FROM blocks_synced`) - if err != nil { - return nil, xerrors.Errorf("Failed to query blocks_synced: %w", err) - } - out := map[cid.Cid]struct{}{} - - for rws.Next() { - var c string - if err := rws.Scan(&c); err != nil { - return nil, xerrors.Errorf("Failed to scan blocks_synced: %w", err) - } - - ci, err := cid.Parse(c) - if err != nil { - return nil, xerrors.Errorf("Failed to parse blocks_synced: %w", err) - } - - out[ci] = struct{}{} - } - return out, nil -} diff --git a/cmd/lotus-chainwatch/main.go b/cmd/lotus-chainwatch/main.go deleted file mode 100644 index 5cb0f3507..000000000 --- a/cmd/lotus-chainwatch/main.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "os" - - "github.com/filecoin-project/lotus/build" - logging "github.com/ipfs/go-log/v2" - "github.com/urfave/cli/v2" -) - -var log = logging.Logger("chainwatch") - -func main() { - if err := logging.SetLogLevel("*", "info"); err != nil { - log.Fatal(err) - } - log.Info("Starting chainwatch", " v", build.UserVersion()) - - app := &cli.App{ - Name: "lotus-chainwatch", - Usage: "Devnet token distribution utility", - Version: build.UserVersion(), - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "repo", - EnvVars: []string{"LOTUS_PATH"}, - Value: "~/.lotus", // TODO: Consider XDG_DATA_HOME - }, - &cli.StringFlag{ - Name: "api", - EnvVars: []string{"FULLNODE_API_INFO"}, - Value: "", - }, - &cli.StringFlag{ - Name: "db", - EnvVars: []string{"LOTUS_DB"}, - Value: "", - }, - &cli.StringFlag{ - Name: "log-level", - EnvVars: []string{"GOLOG_LOG_LEVEL"}, - Value: "info", - }, - }, - Commands: []*cli.Command{ - dotCmd, - runCmd, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} diff --git a/cmd/lotus-chainwatch/processor/common_actors.go b/cmd/lotus-chainwatch/processor/common_actors.go deleted file mode 100644 index 0f2c0d2ea..000000000 --- a/cmd/lotus-chainwatch/processor/common_actors.go +++ /dev/null @@ -1,299 +0,0 @@ -package processor - -import ( - "context" - "time" - - "golang.org/x/sync/errgroup" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - "github.com/ipfs/go-cid" - - builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" - - "github.com/filecoin-project/lotus/chain/actors/builtin" - _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" - "github.com/filecoin-project/lotus/chain/events/state" - "github.com/filecoin-project/lotus/chain/types" - cw_util "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/util" -) - -func (p *Processor) setupCommonActors() error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create table if not exists id_address_map -( - id text not null, - address text not null, - constraint id_address_map_pk - primary key (id, address) -); - -create unique index if not exists id_address_map_id_uindex - on id_address_map (id); - -create unique index if not exists id_address_map_address_uindex - on id_address_map (address); - -create table if not exists actors - ( - id text not null - constraint id_address_map_actors_id_fk - references id_address_map (id), - code text not null, - head text not null, - nonce int not null, - balance text not null, - stateroot text - ); - -create index if not exists actors_id_index - on actors (id); - -create index if not exists id_address_map_address_index - on id_address_map (address); - -create index if not exists id_address_map_id_index - on id_address_map (id); - -create or replace function actor_tips(epoch bigint) - returns table (id text, - code text, - head text, - nonce int, - balance text, - stateroot text, - height bigint, - parentstateroot text) as -$body$ - select distinct on (id) * from actors - inner join state_heights sh on sh.parentstateroot = stateroot - where height < $1 - order by id, height desc; -$body$ language sql; - -create table if not exists actor_states -( - head text not null, - code text not null, - state json not null -); - -create unique index if not exists actor_states_head_code_uindex - on actor_states (head, code); - -create index if not exists actor_states_head_index - on actor_states (head); - -create index if not exists actor_states_code_head_index - on actor_states (head, code); - -`); err != nil { - return err - } - - return tx.Commit() -} - -func (p *Processor) HandleCommonActorsChanges(ctx context.Context, actors map[cid.Cid]ActorTips) error { - if err := p.storeActorAddresses(ctx, actors); err != nil { - return err - } - - grp, _ := errgroup.WithContext(ctx) - - grp.Go(func() error { - if err := p.storeActorHeads(actors); err != nil { - return err - } - return nil - }) - - grp.Go(func() error { - if err := p.storeActorStates(actors); err != nil { - return err - } - return nil - }) - - return grp.Wait() -} - -type UpdateAddresses struct { - Old state.AddressPair - New state.AddressPair -} - -func (p Processor) storeActorAddresses(ctx context.Context, actors map[cid.Cid]ActorTips) error { - start := time.Now() - defer func() { - log.Debugw("Stored Actor Addresses", "duration", time.Since(start).String()) - }() - - addressToID := map[address.Address]address.Address{} - // HACK until genesis storage is figured out: - addressToID[builtin2.SystemActorAddr] = builtin2.SystemActorAddr - addressToID[builtin2.InitActorAddr] = builtin2.InitActorAddr - addressToID[builtin2.RewardActorAddr] = builtin2.RewardActorAddr - addressToID[builtin2.CronActorAddr] = builtin2.CronActorAddr - addressToID[builtin2.StoragePowerActorAddr] = builtin2.StoragePowerActorAddr - addressToID[builtin2.StorageMarketActorAddr] = builtin2.StorageMarketActorAddr - addressToID[builtin2.VerifiedRegistryActorAddr] = builtin2.VerifiedRegistryActorAddr - addressToID[builtin2.BurntFundsActorAddr] = builtin2.BurntFundsActorAddr - initActor, err := p.node.StateGetActor(ctx, builtin2.InitActorAddr, types.EmptyTSK) - if err != nil { - return err - } - - initActorState, err := _init.Load(cw_util.NewAPIIpldStore(ctx, p.node), initActor) - if err != nil { - return err - } - // gross.. - if err := initActorState.ForEachActor(func(id abi.ActorID, addr address.Address) error { - idAddr, err := address.NewIDAddress(uint64(id)) - if err != nil { - return err - } - addressToID[addr] = idAddr - return nil - }); err != nil { - return err - } - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create temp table iam (like id_address_map excluding constraints) on commit drop; -`); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy iam (id, address) from STDIN `) - if err != nil { - return err - } - - for a, i := range addressToID { - if i == address.Undef { - continue - } - if _, err := stmt.Exec( - i.String(), - a.String(), - ); err != nil { - return err - } - } - if err := stmt.Close(); err != nil { - return err - } - - // HACK until chain watch can handle reorgs we need to update this table when ID -> PubKey mappings change - if _, err := tx.Exec(`insert into id_address_map select * from iam on conflict (id) do update set address = EXCLUDED.address`); err != nil { - log.Warnw("Failed to update id_address_map table, this is a known issue") - return nil - } - - return tx.Commit() -} - -func (p *Processor) storeActorHeads(actors map[cid.Cid]ActorTips) error { - start := time.Now() - defer func() { - log.Debugw("Stored Actor Heads", "duration", time.Since(start).String()) - }() - // Basic - tx, err := p.db.Begin() - if err != nil { - return err - } - if _, err := tx.Exec(` - create temp table a_tmp (like actors excluding constraints) on commit drop; - `); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy a_tmp (id, code, head, nonce, balance, stateroot) from stdin `) - if err != nil { - return err - } - - for code, actTips := range actors { - actorName := code.String() - if builtin.IsBuiltinActor(code) { - actorName = builtin.ActorNameByCode(code) - } - for _, actorInfo := range actTips { - for _, a := range actorInfo { - if _, err := stmt.Exec(a.addr.String(), actorName, a.act.Head.String(), a.act.Nonce, a.act.Balance.String(), a.stateroot.String()); err != nil { - return err - } - } - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into actors select * from a_tmp on conflict do nothing `); err != nil { - return xerrors.Errorf("actor put: %w", err) - } - - return tx.Commit() -} - -func (p *Processor) storeActorStates(actors map[cid.Cid]ActorTips) error { - start := time.Now() - defer func() { - log.Debugw("Stored Actor States", "duration", time.Since(start).String()) - }() - // States - tx, err := p.db.Begin() - if err != nil { - return err - } - if _, err := tx.Exec(` - create temp table as_tmp (like actor_states excluding constraints) on commit drop; - `); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy as_tmp (head, code, state) from stdin `) - if err != nil { - return err - } - - for code, actTips := range actors { - actorName := code.String() - if builtin.IsBuiltinActor(code) { - actorName = builtin.ActorNameByCode(code) - } - for _, actorInfo := range actTips { - for _, a := range actorInfo { - if _, err := stmt.Exec(a.act.Head.String(), actorName, a.state); err != nil { - return err - } - } - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into actor_states select * from as_tmp on conflict do nothing `); err != nil { - return xerrors.Errorf("actor put: %w", err) - } - - return tx.Commit() -} diff --git a/cmd/lotus-chainwatch/processor/market.go b/cmd/lotus-chainwatch/processor/market.go deleted file mode 100644 index 17aa1c37b..000000000 --- a/cmd/lotus-chainwatch/processor/market.go +++ /dev/null @@ -1,316 +0,0 @@ -package processor - -import ( - "context" - "strconv" - "time" - - "golang.org/x/sync/errgroup" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/events/state" -) - -func (p *Processor) setupMarket() error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create table if not exists market_deal_proposals -( - deal_id bigint not null, - - state_root text not null, - - piece_cid text not null, - padded_piece_size bigint not null, - unpadded_piece_size bigint not null, - is_verified bool not null, - - client_id text not null, - provider_id text not null, - - start_epoch bigint not null, - end_epoch bigint not null, - slashed_epoch bigint, - storage_price_per_epoch text not null, - - provider_collateral text not null, - client_collateral text not null, - - constraint market_deal_proposal_pk - primary key (deal_id) -); - -create table if not exists market_deal_states -( - deal_id bigint not null, - - sector_start_epoch bigint not null, - last_update_epoch bigint not null, - slash_epoch bigint not null, - - state_root text not null, - - unique (deal_id, sector_start_epoch, last_update_epoch, slash_epoch), - - constraint market_deal_states_pk - primary key (deal_id, state_root) - -); - -create table if not exists minerid_dealid_sectorid -( - deal_id bigint not null - constraint sectors_sector_ids_id_fk - references market_deal_proposals(deal_id), - - sector_id bigint not null, - miner_id text not null, - foreign key (sector_id, miner_id) references sector_precommit_info(sector_id, miner_id), - - constraint miner_sector_deal_ids_pk - primary key (miner_id, sector_id, deal_id) -); - -`); err != nil { - return err - } - - return tx.Commit() -} - -type marketActorInfo struct { - common actorInfo -} - -func (p *Processor) HandleMarketChanges(ctx context.Context, marketTips ActorTips) error { - marketChanges, err := p.processMarket(ctx, marketTips) - if err != nil { - log.Fatalw("Failed to process market actors", "error", err) - } - - if err := p.persistMarket(ctx, marketChanges); err != nil { - log.Fatalw("Failed to persist market actors", "error", err) - } - - if err := p.updateMarket(ctx, marketChanges); err != nil { - log.Fatalw("Failed to update market actors", "error", err) - } - return nil -} - -func (p *Processor) processMarket(ctx context.Context, marketTips ActorTips) ([]marketActorInfo, error) { - start := time.Now() - defer func() { - log.Debugw("Processed Market", "duration", time.Since(start).String()) - }() - - var out []marketActorInfo - for _, markets := range marketTips { - for _, mt := range markets { - // NB: here is where we can extract the market state when we need it. - out = append(out, marketActorInfo{common: mt}) - } - } - return out, nil -} - -func (p *Processor) persistMarket(ctx context.Context, info []marketActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Persisted Market", "duration", time.Since(start).String()) - }() - - grp, ctx := errgroup.WithContext(ctx) - - grp.Go(func() error { - if err := p.storeMarketActorDealProposals(ctx, info); err != nil { - return xerrors.Errorf("Failed to store marker deal proposals: %w", err) - } - return nil - }) - - grp.Go(func() error { - if err := p.storeMarketActorDealStates(info); err != nil { - return xerrors.Errorf("Failed to store marker deal states: %w", err) - } - return nil - }) - - return grp.Wait() - -} - -func (p *Processor) updateMarket(ctx context.Context, info []marketActorInfo) error { - if err := p.updateMarketActorDealProposals(ctx, info); err != nil { - return xerrors.Errorf("Failed to update market info: %w", err) - } - return nil -} - -func (p *Processor) storeMarketActorDealStates(marketTips []marketActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Stored Market Deal States", "duration", time.Since(start).String()) - }() - tx, err := p.db.Begin() - if err != nil { - return err - } - if _, err := tx.Exec(`create temp table mds (like market_deal_states excluding constraints) on commit drop;`); err != nil { - return err - } - stmt, err := tx.Prepare(`copy mds (deal_id, sector_start_epoch, last_update_epoch, slash_epoch, state_root) from STDIN`) - if err != nil { - return err - } - for _, mt := range marketTips { - dealStates, err := p.node.StateMarketDeals(context.TODO(), mt.common.tsKey) - if err != nil { - return err - } - - for dealID, ds := range dealStates { - id, err := strconv.ParseUint(dealID, 10, 64) - if err != nil { - return err - } - - if _, err := stmt.Exec( - id, - ds.State.SectorStartEpoch, - ds.State.LastUpdatedEpoch, - ds.State.SlashEpoch, - mt.common.stateroot.String(), - ); err != nil { - return err - } - - } - } - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into market_deal_states select * from mds on conflict do nothing`); err != nil { - return err - } - - return tx.Commit() -} - -func (p *Processor) storeMarketActorDealProposals(ctx context.Context, marketTips []marketActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Stored Market Deal Proposals", "duration", time.Since(start).String()) - }() - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(`create temp table mdp (like market_deal_proposals excluding constraints) on commit drop;`); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy mdp (deal_id, state_root, piece_cid, padded_piece_size, unpadded_piece_size, is_verified, client_id, provider_id, start_epoch, end_epoch, slashed_epoch, storage_price_per_epoch, provider_collateral, client_collateral) from STDIN`) - if err != nil { - return err - } - - // insert in sorted order (lowest height -> highest height) since dealid is pk of table. - for _, mt := range marketTips { - dealStates, err := p.node.StateMarketDeals(ctx, mt.common.tsKey) - if err != nil { - return err - } - - for dealID, ds := range dealStates { - id, err := strconv.ParseUint(dealID, 10, 64) - if err != nil { - return err - } - - if _, err := stmt.Exec( - id, - mt.common.stateroot.String(), - ds.Proposal.PieceCID.String(), - ds.Proposal.PieceSize, - ds.Proposal.PieceSize.Unpadded(), - ds.Proposal.VerifiedDeal, - ds.Proposal.Client.String(), - ds.Proposal.Provider.String(), - ds.Proposal.StartEpoch, - ds.Proposal.EndEpoch, - nil, // slashed_epoch - ds.Proposal.StoragePricePerEpoch.String(), - ds.Proposal.ProviderCollateral.String(), - ds.Proposal.ClientCollateral.String(), - ); err != nil { - return err - } - - } - } - if err := stmt.Close(); err != nil { - return err - } - if _, err := tx.Exec(`insert into market_deal_proposals select * from mdp on conflict do nothing`); err != nil { - return err - } - - return tx.Commit() - -} - -func (p *Processor) updateMarketActorDealProposals(ctx context.Context, marketTip []marketActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Updated Market Deal Proposals", "duration", time.Since(start).String()) - }() - pred := state.NewStatePredicates(p.node) - - tx, err := p.db.Begin() - if err != nil { - return err - } - - stmt, err := tx.Prepare(`update market_deal_proposals set slashed_epoch=$1 where deal_id=$2`) - if err != nil { - return err - } - - for _, mt := range marketTip { - stateDiff := pred.OnStorageMarketActorChanged(pred.OnDealStateChanged(pred.OnDealStateAmtChanged())) - - changed, val, err := stateDiff(ctx, mt.common.parentTsKey, mt.common.tsKey) - if err != nil { - log.Warnw("error getting market deal state diff", "error", err) - } - if !changed { - continue - } - changes, ok := val.(*market.DealStateChanges) - if !ok { - return xerrors.Errorf("Unknown type returned by Deal State AMT predicate: %T", val) - } - - for _, modified := range changes.Modified { - if modified.From.SlashEpoch != modified.To.SlashEpoch { - if _, err := stmt.Exec(modified.To.SlashEpoch, modified.ID); err != nil { - return err - } - } - } - } - - if err := stmt.Close(); err != nil { - return err - } - - return tx.Commit() -} diff --git a/cmd/lotus-chainwatch/processor/messages.go b/cmd/lotus-chainwatch/processor/messages.go deleted file mode 100644 index 333477c6a..000000000 --- a/cmd/lotus-chainwatch/processor/messages.go +++ /dev/null @@ -1,318 +0,0 @@ -package processor - -import ( - "context" - "sync" - - "golang.org/x/sync/errgroup" - "golang.org/x/xerrors" - - "github.com/ipfs/go-cid" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/lib/parmap" -) - -func (p *Processor) setupMessages() error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create table if not exists messages -( - cid text not null - constraint messages_pk - primary key, - "from" text not null, - "to" text not null, - size_bytes bigint not null, - nonce bigint not null, - value text not null, - gas_fee_cap text not null, - gas_premium text not null, - gas_limit bigint not null, - method bigint, - params bytea -); - -create unique index if not exists messages_cid_uindex - on messages (cid); - -create index if not exists messages_from_index - on messages ("from"); - -create index if not exists messages_to_index - on messages ("to"); - -create table if not exists block_messages -( - block text not null - constraint blocks_block_cids_cid_fk - references block_cids (cid), - message text not null, - constraint block_messages_pk - primary key (block, message) -); - -create table if not exists mpool_messages -( - msg text not null - constraint mpool_messages_pk - primary key - constraint mpool_messages_messages_cid_fk - references messages, - add_ts int not null -); - -create unique index if not exists mpool_messages_msg_uindex - on mpool_messages (msg); - -create table if not exists receipts -( - msg text not null, - state text not null, - idx int not null, - exit int not null, - gas_used bigint not null, - return bytea, - constraint receipts_pk - primary key (msg, state) -); - -create index if not exists receipts_msg_state_index - on receipts (msg, state); -`); err != nil { - return err - } - - return tx.Commit() -} - -func (p *Processor) HandleMessageChanges(ctx context.Context, blocks map[cid.Cid]*types.BlockHeader) error { - if err := p.persistMessagesAndReceipts(ctx, blocks); err != nil { - return err - } - return nil -} - -func (p *Processor) persistMessagesAndReceipts(ctx context.Context, blocks map[cid.Cid]*types.BlockHeader) error { - messages, inclusions := p.fetchMessages(ctx, blocks) - receipts := p.fetchParentReceipts(ctx, blocks) - - grp, _ := errgroup.WithContext(ctx) - - grp.Go(func() error { - return p.storeMessages(messages) - }) - - grp.Go(func() error { - return p.storeMsgInclusions(inclusions) - }) - - grp.Go(func() error { - return p.storeReceipts(receipts) - }) - - return grp.Wait() -} - -func (p *Processor) storeReceipts(recs map[mrec]*types.MessageReceipt) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create temp table recs (like receipts excluding constraints) on commit drop; -`); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy recs (msg, state, idx, exit, gas_used, return) from stdin `) - if err != nil { - return err - } - - for c, m := range recs { - if _, err := stmt.Exec( - c.msg.String(), - c.state.String(), - c.idx, - m.ExitCode, - m.GasUsed, - m.Return, - ); err != nil { - return err - } - } - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into receipts select * from recs on conflict do nothing `); err != nil { - return xerrors.Errorf("actor put: %w", err) - } - - return tx.Commit() -} - -func (p *Processor) storeMsgInclusions(incls map[cid.Cid][]cid.Cid) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create temp table mi (like block_messages excluding constraints) on commit drop; -`); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy mi (block, message) from STDIN `) - if err != nil { - return err - } - - for b, msgs := range incls { - for _, msg := range msgs { - if _, err := stmt.Exec( - b.String(), - msg.String(), - ); err != nil { - return err - } - } - } - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into block_messages select * from mi on conflict do nothing `); err != nil { - return xerrors.Errorf("actor put: %w", err) - } - - return tx.Commit() -} - -func (p *Processor) storeMessages(msgs map[cid.Cid]*types.Message) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create temp table msgs (like messages excluding constraints) on commit drop; -`); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy msgs (cid, "from", "to", size_bytes, nonce, "value", gas_premium, gas_fee_cap, gas_limit, method, params) from stdin `) - if err != nil { - return err - } - - for c, m := range msgs { - var msgBytes int - if b, err := m.Serialize(); err == nil { - msgBytes = len(b) - } - - if _, err := stmt.Exec( - c.String(), - m.From.String(), - m.To.String(), - msgBytes, - m.Nonce, - m.Value.String(), - m.GasPremium.String(), - m.GasFeeCap.String(), - m.GasLimit, - m.Method, - m.Params, - ); err != nil { - return err - } - } - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into messages select * from msgs on conflict do nothing `); err != nil { - return xerrors.Errorf("actor put: %w", err) - } - - return tx.Commit() -} - -func (p *Processor) fetchMessages(ctx context.Context, blocks map[cid.Cid]*types.BlockHeader) (map[cid.Cid]*types.Message, map[cid.Cid][]cid.Cid) { - var lk sync.Mutex - messages := map[cid.Cid]*types.Message{} - inclusions := map[cid.Cid][]cid.Cid{} // block -> msgs - - parmap.Par(50, parmap.MapArr(blocks), func(header *types.BlockHeader) { - msgs, err := p.node.ChainGetBlockMessages(ctx, header.Cid()) - if err != nil { - log.Error(err) - log.Debugw("ChainGetBlockMessages", "header_cid", header.Cid()) - return - } - - vmm := make([]*types.Message, 0, len(msgs.Cids)) - for _, m := range msgs.BlsMessages { - vmm = append(vmm, m) - } - - for _, m := range msgs.SecpkMessages { - vmm = append(vmm, &m.Message) - } - - lk.Lock() - for _, message := range vmm { - messages[message.Cid()] = message - inclusions[header.Cid()] = append(inclusions[header.Cid()], message.Cid()) - } - lk.Unlock() - }) - - return messages, inclusions -} - -type mrec struct { - msg cid.Cid - state cid.Cid - idx int -} - -func (p *Processor) fetchParentReceipts(ctx context.Context, toSync map[cid.Cid]*types.BlockHeader) map[mrec]*types.MessageReceipt { - var lk sync.Mutex - out := map[mrec]*types.MessageReceipt{} - - parmap.Par(50, parmap.MapArr(toSync), func(header *types.BlockHeader) { - recs, err := p.node.ChainGetParentReceipts(ctx, header.Cid()) - if err != nil { - log.Error(err) - log.Debugw("ChainGetParentReceipts", "header_cid", header.Cid()) - return - } - msgs, err := p.node.ChainGetParentMessages(ctx, header.Cid()) - if err != nil { - log.Error(err) - log.Debugw("ChainGetParentMessages", "header_cid", header.Cid()) - return - } - - lk.Lock() - for i, r := range recs { - out[mrec{ - msg: msgs[i].Cid, - state: header.ParentStateRoot, - idx: i, - }] = r - } - lk.Unlock() - }) - - return out -} diff --git a/cmd/lotus-chainwatch/processor/miner.go b/cmd/lotus-chainwatch/processor/miner.go deleted file mode 100644 index f3514df88..000000000 --- a/cmd/lotus-chainwatch/processor/miner.go +++ /dev/null @@ -1,1035 +0,0 @@ -package processor - -import ( - "context" - "strings" - "time" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-bitfield" - "github.com/ipfs/go-cid" - "golang.org/x/sync/errgroup" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" - - "github.com/filecoin-project/lotus/api/v0api" - "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/actors/builtin/power" - "github.com/filecoin-project/lotus/chain/events/state" - "github.com/filecoin-project/lotus/chain/store" - "github.com/filecoin-project/lotus/chain/types" - cw_util "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/util" -) - -func (p *Processor) setupMiners() error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` - -create table if not exists miner_info -( - miner_id text not null, - owner_addr text not null, - worker_addr text not null, - peer_id text, - sector_size text not null, - - constraint miner_info_pk - primary key (miner_id) -); - -create table if not exists sector_precommit_info -( - miner_id text not null, - sector_id bigint not null, - sealed_cid text not null, - state_root text not null, - - seal_rand_epoch bigint not null, - expiration_epoch bigint not null, - - precommit_deposit text not null, - precommit_epoch bigint not null, - deal_weight text not null, - verified_deal_weight text not null, - - - is_replace_capacity bool not null, - replace_sector_deadline bigint, - replace_sector_partition bigint, - replace_sector_number bigint, - - unique (miner_id, sector_id), - - constraint sector_precommit_info_pk - primary key (miner_id, sector_id, sealed_cid) - -); - -create table if not exists sector_info -( - miner_id text not null, - sector_id bigint not null, - sealed_cid text not null, - state_root text not null, - - activation_epoch bigint not null, - expiration_epoch bigint not null, - - deal_weight text not null, - verified_deal_weight text not null, - - initial_pledge text not null, - expected_day_reward text not null, - expected_storage_pledge text not null, - - constraint sector_info_pk - primary key (miner_id, sector_id, sealed_cid) -); - -/* -* captures miner-specific power state for any given stateroot -*/ -create table if not exists miner_power -( - miner_id text not null, - state_root text not null, - raw_bytes_power text not null, - quality_adjusted_power text not null, - constraint miner_power_pk - primary key (miner_id, state_root) -); - -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'miner_sector_event_type') THEN - CREATE TYPE miner_sector_event_type AS ENUM - ( - 'PRECOMMIT_ADDED', 'PRECOMMIT_EXPIRED', 'COMMIT_CAPACITY_ADDED', 'SECTOR_ADDED', - 'SECTOR_EXTENDED', 'SECTOR_EXPIRED', 'SECTOR_FAULTED', 'SECTOR_RECOVERING', 'SECTOR_RECOVERED', 'SECTOR_TERMINATED' - ); - END IF; -END$$; - -create table if not exists miner_sector_events -( - miner_id text not null, - sector_id bigint not null, - state_root text not null, - event miner_sector_event_type not null, - - constraint miner_sector_events_pk - primary key (sector_id, event, miner_id, state_root) -); - -`); err != nil { - return err - } - - return tx.Commit() -} - -type SectorLifecycleEvent string - -const ( - PreCommitAdded = "PRECOMMIT_ADDED" - PreCommitExpired = "PRECOMMIT_EXPIRED" - - CommitCapacityAdded = "COMMIT_CAPACITY_ADDED" - - SectorAdded = "SECTOR_ADDED" - SectorExpired = "SECTOR_EXPIRED" - SectorExtended = "SECTOR_EXTENDED" - SectorFaulted = "SECTOR_FAULTED" - SectorRecovering = "SECTOR_RECOVERING" - SectorRecovered = "SECTOR_RECOVERED" - SectorTerminated = "SECTOR_TERMINATED" -) - -type MinerSectorsEvent struct { - MinerID address.Address - SectorIDs []uint64 - StateRoot cid.Cid - Event SectorLifecycleEvent -} - -type SectorDealEvent struct { - MinerID address.Address - SectorID uint64 - DealIDs []abi.DealID -} - -type PartitionStatus struct { - Terminated bitfield.BitField - Expired bitfield.BitField - Faulted bitfield.BitField - InRecovery bitfield.BitField - Recovered bitfield.BitField -} - -type minerActorInfo struct { - common actorInfo - - state miner.State - - // tracked by power actor - rawPower big.Int - qalPower big.Int -} - -func (p *Processor) HandleMinerChanges(ctx context.Context, minerTips ActorTips) error { - minerChanges, err := p.processMiners(ctx, minerTips) - if err != nil { - log.Fatalw("Failed to process miner actors", "error", err) - } - - if err := p.persistMiners(ctx, minerChanges); err != nil { - log.Fatalw("Failed to persist miner actors", "error", err) - } - - return nil -} - -func (p *Processor) processMiners(ctx context.Context, minerTips map[types.TipSetKey][]actorInfo) ([]minerActorInfo, error) { - start := time.Now() - defer func() { - log.Debugw("Processed Miners", "duration", time.Since(start).String()) - }() - - stor := store.ActorStore(ctx, blockstore.NewAPIBlockstore(p.node)) - - var out []minerActorInfo - // TODO add parallel calls if this becomes slow - for tipset, miners := range minerTips { - // get the power actors claims map - powerState, err := getPowerActorState(ctx, p.node, tipset) - if err != nil { - return nil, err - } - - // Get miner raw and quality power - for _, act := range miners { - var mi minerActorInfo - mi.common = act - - // get miner claim from power actors claim map and store if found, else the miner had no claim at - // this tipset - claim, found, err := powerState.MinerPower(act.addr) - if err != nil { - return nil, err - } - if found { - mi.qalPower = claim.QualityAdjPower - mi.rawPower = claim.RawBytePower - } - - // Get the miner state - mas, err := miner.Load(stor, &act.act) - if err != nil { - log.Warnw("failed to find miner actor state", "address", act.addr, "error", err) - continue - } - mi.state = mas - out = append(out, mi) - } - } - return out, nil -} - -func (p *Processor) persistMiners(ctx context.Context, miners []minerActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Persisted Miners", "duration", time.Since(start).String()) - }() - - grp, _ := errgroup.WithContext(ctx) - - grp.Go(func() error { - if err := p.storeMinersPower(miners); err != nil { - return err - } - return nil - }) - - grp.Go(func() error { - if err := p.storeMinersActorInfoState(ctx, miners); err != nil { - return err - } - return nil - }) - - // 8 is arbitrary, idk what a good value here is. - preCommitEvents := make(chan *MinerSectorsEvent, 8) - sectorEvents := make(chan *MinerSectorsEvent, 8) - partitionEvents := make(chan *MinerSectorsEvent, 8) - dealEvents := make(chan *SectorDealEvent, 8) - - grp.Go(func() error { - return p.storePreCommitDealInfo(dealEvents) - }) - - grp.Go(func() error { - return p.storeMinerSectorEvents(ctx, sectorEvents, preCommitEvents, partitionEvents) - }) - - grp.Go(func() error { - defer func() { - close(preCommitEvents) - close(dealEvents) - }() - return p.storeMinerPreCommitInfo(ctx, miners, preCommitEvents, dealEvents) - }) - - grp.Go(func() error { - defer close(sectorEvents) - return p.storeMinerSectorInfo(ctx, miners, sectorEvents) - }) - - grp.Go(func() error { - defer close(partitionEvents) - return p.getMinerPartitionsDifferences(ctx, miners, partitionEvents) - }) - - return grp.Wait() -} - -func (p *Processor) storeMinerPreCommitInfo(ctx context.Context, miners []minerActorInfo, sectorEvents chan<- *MinerSectorsEvent, sectorDeals chan<- *SectorDealEvent) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(`create temp table spi (like sector_precommit_info excluding constraints) on commit drop;`); err != nil { - return xerrors.Errorf("Failed to create temp table for sector_precommit_info: %w", err) - } - - stmt, err := tx.Prepare(`copy spi (miner_id, sector_id, sealed_cid, state_root, seal_rand_epoch, expiration_epoch, precommit_deposit, precommit_epoch, deal_weight, verified_deal_weight, is_replace_capacity, replace_sector_deadline, replace_sector_partition, replace_sector_number) from STDIN`) - - if err != nil { - return xerrors.Errorf("Failed to prepare miner precommit info statement: %w", err) - } - - grp, _ := errgroup.WithContext(ctx) - for _, m := range miners { - m := m - grp.Go(func() error { - changes, err := p.getMinerPreCommitChanges(ctx, m) - if err != nil { - if strings.Contains(err.Error(), types.ErrActorNotFound.Error()) { - return nil - } - return err - } - if changes == nil { - return nil - } - - preCommitAdded := make([]uint64, len(changes.Added)) - for i, added := range changes.Added { - if len(added.Info.DealIDs) > 0 { - sectorDeals <- &SectorDealEvent{ - MinerID: m.common.addr, - SectorID: uint64(added.Info.SectorNumber), - DealIDs: added.Info.DealIDs, - } - } - if added.Info.ReplaceCapacity { - if _, err := stmt.Exec( - m.common.addr.String(), - added.Info.SectorNumber, - added.Info.SealedCID.String(), - m.common.stateroot.String(), - added.Info.SealRandEpoch, - added.Info.Expiration, - added.PreCommitDeposit.String(), - added.PreCommitEpoch, - added.DealWeight.String(), - added.VerifiedDealWeight.String(), - added.Info.ReplaceCapacity, - added.Info.ReplaceSectorDeadline, - added.Info.ReplaceSectorPartition, - added.Info.ReplaceSectorNumber, - ); err != nil { - return err - } - } else { - if _, err := stmt.Exec( - m.common.addr.String(), - added.Info.SectorNumber, - added.Info.SealedCID.String(), - m.common.stateroot.String(), - added.Info.SealRandEpoch, - added.Info.Expiration, - added.PreCommitDeposit.String(), - added.PreCommitEpoch, - added.DealWeight.String(), - added.VerifiedDealWeight.String(), - added.Info.ReplaceCapacity, - nil, // replace deadline - nil, // replace partition - nil, // replace sector - ); err != nil { - return err - } - - } - preCommitAdded[i] = uint64(added.Info.SectorNumber) - } - if len(preCommitAdded) > 0 { - sectorEvents <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: preCommitAdded, - Event: PreCommitAdded, - } - } - var preCommitExpired []uint64 - for _, removed := range changes.Removed { - // TODO: we can optimize this to not load the AMT every time, if necessary. - si, err := m.state.GetSector(removed.Info.SectorNumber) - if err != nil { - return err - } - if si == nil { - preCommitExpired = append(preCommitExpired, uint64(removed.Info.SectorNumber)) - } - } - if len(preCommitExpired) > 0 { - sectorEvents <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: preCommitExpired, - Event: PreCommitExpired, - } - } - return nil - }) - } - if err := grp.Wait(); err != nil { - return err - } - - if err := stmt.Close(); err != nil { - return xerrors.Errorf("Failed to close sector precommit info statement: %w", err) - } - - if _, err := tx.Exec(`insert into sector_precommit_info select * from spi on conflict do nothing`); err != nil { - return xerrors.Errorf("Failed to insert into sector precommit info table: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("Failed to commit sector precommit info: %w", err) - } - return nil -} - -func (p *Processor) storeMinerSectorInfo(ctx context.Context, miners []minerActorInfo, events chan<- *MinerSectorsEvent) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(`create temp table si (like sector_info excluding constraints) on commit drop;`); err != nil { - return xerrors.Errorf("Failed to create temp table for sector_: %w", err) - } - - stmt, err := tx.Prepare(`copy si (miner_id, sector_id, sealed_cid, state_root, activation_epoch, expiration_epoch, deal_weight, verified_deal_weight, initial_pledge, expected_day_reward, expected_storage_pledge) from STDIN`) - if err != nil { - return xerrors.Errorf("Failed to prepare miner sector info statement: %w", err) - } - - grp, _ := errgroup.WithContext(ctx) - for _, m := range miners { - m := m - grp.Go(func() error { - changes, err := p.getMinerSectorChanges(ctx, m) - if err != nil { - if strings.Contains(err.Error(), types.ErrActorNotFound.Error()) { - return nil - } - return err - } - if changes == nil { - return nil - } - var sectorsAdded []uint64 - var ccAdded []uint64 - var extended []uint64 - for _, added := range changes.Added { - // add the sector to the table - if _, err := stmt.Exec( - m.common.addr.String(), - added.SectorNumber, - added.SealedCID.String(), - m.common.stateroot.String(), - added.Activation.String(), - added.Expiration.String(), - added.DealWeight.String(), - added.VerifiedDealWeight.String(), - added.InitialPledge.String(), - added.ExpectedDayReward.String(), - added.ExpectedStoragePledge.String(), - ); err != nil { - log.Errorw("writing miner sector changes statement", "error", err.Error()) - } - if len(added.DealIDs) == 0 { - ccAdded = append(ccAdded, uint64(added.SectorNumber)) - } else { - sectorsAdded = append(sectorsAdded, uint64(added.SectorNumber)) - } - } - - for _, mod := range changes.Extended { - extended = append(extended, uint64(mod.To.SectorNumber)) - } - - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: ccAdded, - Event: CommitCapacityAdded, - } - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: sectorsAdded, - Event: SectorAdded, - } - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: extended, - Event: SectorExtended, - } - return nil - }) - } - - if err := grp.Wait(); err != nil { - return err - } - - if err := stmt.Close(); err != nil { - return xerrors.Errorf("Failed to close sector info statement: %w", err) - } - - if _, err := tx.Exec(`insert into sector_info select * from si on conflict do nothing`); err != nil { - return xerrors.Errorf("Failed to insert into sector info table: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("Failed to commit sector info: %w", err) - } - return nil - -} - -func (p *Processor) getMinerPartitionsDifferences(ctx context.Context, miners []minerActorInfo, events chan<- *MinerSectorsEvent) error { - grp, ctx := errgroup.WithContext(ctx) - for _, m := range miners { - m := m - grp.Go(func() error { - if err := p.diffMinerPartitions(ctx, m, events); err != nil { - if strings.Contains(err.Error(), types.ErrActorNotFound.Error()) { - return nil - } - return err - } - return nil - }) - } - return grp.Wait() -} - -func (p *Processor) storeMinerSectorEvents(ctx context.Context, sectorEvents, preCommitEvents, partitionEvents <-chan *MinerSectorsEvent) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(`create temp table mse (like miner_sector_events excluding constraints) on commit drop;`); err != nil { - return xerrors.Errorf("Failed to create temp table for sector_: %w", err) - } - - stmt, err := tx.Prepare(`copy mse (miner_id, sector_id, event, state_root) from STDIN`) - if err != nil { - return xerrors.Errorf("Failed to prepare miner sector info statement: %w", err) - } - - grp, ctx := errgroup.WithContext(ctx) - grp.Go(func() error { - innerGrp, _ := errgroup.WithContext(ctx) - for mse := range sectorEvents { - mse := mse - innerGrp.Go(func() error { - for _, sid := range mse.SectorIDs { - if _, err := stmt.Exec( - mse.MinerID.String(), - sid, - mse.Event, - mse.StateRoot.String(), - ); err != nil { - return err - } - } - return nil - }) - } - return innerGrp.Wait() - }) - - grp.Go(func() error { - innerGrp, _ := errgroup.WithContext(ctx) - for mse := range preCommitEvents { - mse := mse - innerGrp.Go(func() error { - for _, sid := range mse.SectorIDs { - if _, err := stmt.Exec( - mse.MinerID.String(), - sid, - mse.Event, - mse.StateRoot.String(), - ); err != nil { - return err - } - } - return nil - }) - } - return innerGrp.Wait() - }) - - grp.Go(func() error { - innerGrp, _ := errgroup.WithContext(ctx) - for mse := range partitionEvents { - mse := mse - grp.Go(func() error { - for _, sid := range mse.SectorIDs { - if _, err := stmt.Exec( - mse.MinerID.String(), - sid, - mse.Event, - mse.StateRoot.String(), - ); err != nil { - return err - } - } - return nil - }) - } - return innerGrp.Wait() - }) - - if err := grp.Wait(); err != nil { - return err - } - - if err := stmt.Close(); err != nil { - return xerrors.Errorf("Failed to close sector event statement: %w", err) - } - - if _, err := tx.Exec(`insert into miner_sector_events select * from mse on conflict do nothing`); err != nil { - return xerrors.Errorf("Failed to insert into sector event table: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("Failed to commit sector events: %w", err) - } - return nil -} - -func (p *Processor) getMinerStateAt(ctx context.Context, maddr address.Address, tskey types.TipSetKey) (miner.State, error) { - prevActor, err := p.node.StateGetActor(ctx, maddr, tskey) - if err != nil { - return nil, err - } - return miner.Load(store.ActorStore(ctx, blockstore.NewAPIBlockstore(p.node)), prevActor) -} - -func (p *Processor) getMinerPreCommitChanges(ctx context.Context, m minerActorInfo) (*miner.PreCommitChanges, error) { - pred := state.NewStatePredicates(p.node) - changed, val, err := pred.OnMinerActorChange(m.common.addr, pred.OnMinerPreCommitChange())(ctx, m.common.parentTsKey, m.common.tsKey) - if err != nil { - return nil, xerrors.Errorf("Failed to diff miner precommit amt: %w", err) - } - if !changed { - return nil, nil - } - out := val.(*miner.PreCommitChanges) - return out, nil -} - -func (p *Processor) getMinerSectorChanges(ctx context.Context, m minerActorInfo) (*miner.SectorChanges, error) { - pred := state.NewStatePredicates(p.node) - changed, val, err := pred.OnMinerActorChange(m.common.addr, pred.OnMinerSectorChange())(ctx, m.common.parentTsKey, m.common.tsKey) - if err != nil { - return nil, xerrors.Errorf("Failed to diff miner sectors amt: %w", err) - } - if !changed { - return nil, nil - } - out := val.(*miner.SectorChanges) - return out, nil -} - -func (p *Processor) diffMinerPartitions(ctx context.Context, m minerActorInfo, events chan<- *MinerSectorsEvent) error { - prevMiner, err := p.getMinerStateAt(ctx, m.common.addr, m.common.parentTsKey) - if err != nil { - return err - } - curMiner := m.state - dc, err := prevMiner.DeadlinesChanged(curMiner) - if err != nil { - return err - } - if !dc { - return nil - } - panic("TODO") - - // FIXME: This code doesn't work. - // 1. We need to diff all deadlines, not just the "current" deadline. - // 2. We need to handle the case where we _add_ a partition. (i.e., - // where len(newPartitions) != len(oldPartitions). - /* - - // NOTE: If we change the number of deadlines in an upgrade, this will - // break. - - // load the old deadline - prevDls, err := prevMiner.LoadDeadlines(p.ctxStore) - if err != nil { - return err - } - var prevDl miner.Deadline - if err := p.ctxStore.Get(ctx, prevDls.Due[dlIdx], &prevDl); err != nil { - return err - } - - prevPartitions, err := prevDl.PartitionsArray(p.ctxStore) - if err != nil { - return err - } - - // load the new deadline - curDls, err := curMiner.LoadDeadlines(p.ctxStore) - if err != nil { - return err - } - - var curDl miner.Deadline - if err := p.ctxStore.Get(ctx, curDls.Due[dlIdx], &curDl); err != nil { - return err - } - - curPartitions, err := curDl.PartitionsArray(p.ctxStore) - if err != nil { - return err - } - - // TODO this can be optimized by inspecting the miner state for partitions that have changed and only inspecting those. - var prevPart miner.Partition - if err := prevPartitions.ForEach(&prevPart, func(i int64) error { - var curPart miner.Partition - if found, err := curPartitions.Get(uint64(i), &curPart); err != nil { - return err - } else if !found { - log.Fatal("I don't know what this means, are partitions ever removed?") - } - partitionDiff, err := p.diffPartition(prevPart, curPart) - if err != nil { - return err - } - - recovered, err := partitionDiff.Recovered.All(miner.SectorsMax) - if err != nil { - return err - } - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: recovered, - Event: SectorRecovered, - } - inRecovery, err := partitionDiff.InRecovery.All(miner.SectorsMax) - if err != nil { - return err - } - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: inRecovery, - Event: SectorRecovering, - } - faulted, err := partitionDiff.Faulted.All(miner.SectorsMax) - if err != nil { - return err - } - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: faulted, - Event: SectorFaulted, - } - terminated, err := partitionDiff.Terminated.All(miner.SectorsMax) - if err != nil { - return err - } - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: terminated, - Event: SectorTerminated, - } - expired, err := partitionDiff.Expired.All(miner.SectorsMax) - if err != nil { - return err - } - events <- &MinerSectorsEvent{ - MinerID: m.common.addr, - StateRoot: m.common.stateroot, - SectorIDs: expired, - Event: SectorExpired, - } - - return nil - }); err != nil { - return err - } - - return nil - */ -} - -func (p *Processor) diffPartition(prevPart, curPart miner.Partition) (*PartitionStatus, error) { - prevLiveSectors, err := prevPart.LiveSectors() - if err != nil { - return nil, err - } - curLiveSectors, err := curPart.LiveSectors() - if err != nil { - return nil, err - } - - removedSectors, err := bitfield.SubtractBitField(prevLiveSectors, curLiveSectors) - if err != nil { - return nil, err - } - - prevRecoveries, err := prevPart.RecoveringSectors() - if err != nil { - return nil, err - } - - curRecoveries, err := curPart.RecoveringSectors() - if err != nil { - return nil, err - } - - newRecoveries, err := bitfield.SubtractBitField(curRecoveries, prevRecoveries) - if err != nil { - return nil, err - } - - prevFaults, err := prevPart.FaultySectors() - if err != nil { - return nil, err - } - - curFaults, err := curPart.FaultySectors() - if err != nil { - return nil, err - } - - newFaults, err := bitfield.SubtractBitField(curFaults, prevFaults) - if err != nil { - return nil, err - } - - // all current good sectors - curActiveSectors, err := curPart.ActiveSectors() - if err != nil { - return nil, err - } - - // sectors that were previously fault and are now currently active are considered recovered. - recovered, err := bitfield.IntersectBitField(prevFaults, curActiveSectors) - if err != nil { - return nil, err - } - - // TODO: distinguish between "terminated" and "expired" sectors. The - // previous code here never had a chance of working in the first place, - // so I'm not going to try to replicate it right now. - // - // How? If the sector expires before it should (according to sector - // info) and it wasn't replaced by a pre-commit deleted in this change - // set, it was "early terminated". - - return &PartitionStatus{ - Terminated: bitfield.New(), - Expired: removedSectors, - Faulted: newFaults, - InRecovery: newRecoveries, - Recovered: recovered, - }, nil -} - -func (p *Processor) storeMinersActorInfoState(ctx context.Context, miners []minerActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Stored Miners Actor State", "duration", time.Since(start).String()) - }() - - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(`create temp table mi (like miner_info excluding constraints) on commit drop;`); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy mi (miner_id, owner_addr, worker_addr, peer_id, sector_size) from STDIN`) - if err != nil { - return err - } - for _, m := range miners { - mi, err := p.node.StateMinerInfo(ctx, m.common.addr, m.common.tsKey) - if err != nil { - if strings.Contains(err.Error(), types.ErrActorNotFound.Error()) { - continue - } else { - return err - } - } - var pid string - if mi.PeerId != nil { - pid = mi.PeerId.String() - } - if _, err := stmt.Exec( - m.common.addr.String(), - mi.Owner.String(), - mi.Worker.String(), - pid, - mi.SectorSize.ShortString(), - ); err != nil { - log.Errorw("failed to store miner state", "state", m.state, "info", m.state.Info, "error", err) - return xerrors.Errorf("failed to store miner state: %w", err) - } - - } - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into miner_info select * from mi on conflict do nothing `); err != nil { - return xerrors.Errorf("actor put: %w", err) - } - - return tx.Commit() -} - -func (p *Processor) storePreCommitDealInfo(dealEvents <-chan *SectorDealEvent) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(`create temp table mds (like minerid_dealid_sectorid excluding constraints) on commit drop;`); err != nil { - return xerrors.Errorf("Failed to create temp table for minerid_dealid_sectorid: %w", err) - } - - stmt, err := tx.Prepare(`copy mds (deal_id, miner_id, sector_id) from STDIN`) - if err != nil { - return xerrors.Errorf("Failed to prepare minerid_dealid_sectorid statement: %w", err) - } - - for sde := range dealEvents { - for _, did := range sde.DealIDs { - if _, err := stmt.Exec( - uint64(did), - sde.MinerID.String(), - sde.SectorID, - ); err != nil { - return err - } - } - } - - if err := stmt.Close(); err != nil { - return xerrors.Errorf("Failed to close miner sector deals statement: %w", err) - } - - if _, err := tx.Exec(`insert into minerid_dealid_sectorid select * from mds on conflict do nothing`); err != nil { - return xerrors.Errorf("Failed to insert into miner deal sector table: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("Failed to commit miner deal sector table: %w", err) - } - return nil - -} - -func (p *Processor) storeMinersPower(miners []minerActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Stored Miners Power", "duration", time.Since(start).String()) - }() - - tx, err := p.db.Begin() - if err != nil { - return xerrors.Errorf("begin miner_power tx: %w", err) - } - - if _, err := tx.Exec(`create temp table mp (like miner_power excluding constraints) on commit drop`); err != nil { - return xerrors.Errorf("prep miner_power temp: %w", err) - } - - stmt, err := tx.Prepare(`copy mp (miner_id, state_root, raw_bytes_power, quality_adjusted_power) from STDIN`) - if err != nil { - return xerrors.Errorf("prepare tmp miner_power: %w", err) - } - - for _, m := range miners { - if _, err := stmt.Exec( - m.common.addr.String(), - m.common.stateroot.String(), - m.rawPower.String(), - m.qalPower.String(), - ); err != nil { - log.Errorw("failed to store miner power", "miner", m.common.addr, "stateroot", m.common.stateroot, "error", err) - } - } - - if err := stmt.Close(); err != nil { - return xerrors.Errorf("close prepared miner_power: %w", err) - } - - if _, err := tx.Exec(`insert into miner_power select * from mp on conflict do nothing`); err != nil { - return xerrors.Errorf("insert miner_power from tmp: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("commit miner_power tx: %w", err) - } - - return nil - -} - -// load the power actor state clam as an adt.Map at the tipset `ts`. -func getPowerActorState(ctx context.Context, api v0api.FullNode, ts types.TipSetKey) (power.State, error) { - powerActor, err := api.StateGetActor(ctx, power.Address, ts) - if err != nil { - return nil, err - } - return power.Load(cw_util.NewAPIIpldStore(ctx, api), powerActor) -} diff --git a/cmd/lotus-chainwatch/processor/mpool.go b/cmd/lotus-chainwatch/processor/mpool.go deleted file mode 100644 index 0a6445d78..000000000 --- a/cmd/lotus-chainwatch/processor/mpool.go +++ /dev/null @@ -1,100 +0,0 @@ -package processor - -import ( - "context" - "time" - - "golang.org/x/xerrors" - - "github.com/ipfs/go-cid" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/types" -) - -func (p *Processor) subMpool(ctx context.Context) { - sub, err := p.node.MpoolSub(ctx) - if err != nil { - return - } - - for { - var updates []api.MpoolUpdate - - select { - case update := <-sub: - updates = append(updates, update) - case <-ctx.Done(): - return - } - - loop: - for { - select { - case update := <-sub: - updates = append(updates, update) - case <-time.After(10 * time.Millisecond): - break loop - } - } - - msgs := map[cid.Cid]*types.Message{} - for _, v := range updates { - if v.Type != api.MpoolAdd { - continue - } - - msgs[v.Message.Message.Cid()] = &v.Message.Message - } - - err := p.storeMessages(msgs) - if err != nil { - log.Error(err) - } - - if err := p.storeMpoolInclusions(updates); err != nil { - log.Error(err) - } - } -} - -func (p *Processor) storeMpoolInclusions(msgs []api.MpoolUpdate) error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` - create temp table mi (like mpool_messages excluding constraints) on commit drop; - `); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - stmt, err := tx.Prepare(`copy mi (msg, add_ts) from stdin `) - if err != nil { - return err - } - - for _, msg := range msgs { - if msg.Type != api.MpoolAdd { - continue - } - - if _, err := stmt.Exec( - msg.Message.Message.Cid().String(), - time.Now().Unix(), - ); err != nil { - return err - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into mpool_messages select * from mi on conflict do nothing `); err != nil { - return xerrors.Errorf("actor put: %w", err) - } - - return tx.Commit() -} diff --git a/cmd/lotus-chainwatch/processor/power.go b/cmd/lotus-chainwatch/processor/power.go deleted file mode 100644 index 726a46706..000000000 --- a/cmd/lotus-chainwatch/processor/power.go +++ /dev/null @@ -1,190 +0,0 @@ -package processor - -import ( - "context" - "time" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-state-types/big" - - "github.com/filecoin-project/lotus/chain/actors/builtin" -) - -type powerActorInfo struct { - common actorInfo - - totalRawBytes big.Int - totalRawBytesCommitted big.Int - totalQualityAdjustedBytes big.Int - totalQualityAdjustedBytesCommitted big.Int - totalPledgeCollateral big.Int - - qaPowerSmoothed builtin.FilterEstimate - - minerCount int64 - minerCountAboveMinimumPower int64 -} - -func (p *Processor) setupPower() error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -create table if not exists chain_power -( - state_root text not null - constraint power_smoothing_estimates_pk - primary key, - - total_raw_bytes_power text not null, - total_raw_bytes_committed text not null, - total_qa_bytes_power text not null, - total_qa_bytes_committed text not null, - total_pledge_collateral text not null, - - qa_smoothed_position_estimate text not null, - qa_smoothed_velocity_estimate text not null, - - miner_count int not null, - minimum_consensus_miner_count int not null -); -`); err != nil { - return err - } - - return tx.Commit() -} - -func (p *Processor) HandlePowerChanges(ctx context.Context, powerTips ActorTips) error { - powerChanges, err := p.processPowerActors(ctx, powerTips) - if err != nil { - return xerrors.Errorf("Failed to process power actors: %w", err) - } - - if err := p.persistPowerActors(ctx, powerChanges); err != nil { - return err - } - - return nil -} - -func (p *Processor) processPowerActors(ctx context.Context, powerTips ActorTips) ([]powerActorInfo, error) { - start := time.Now() - defer func() { - log.Debugw("Processed Power Actors", "duration", time.Since(start).String()) - }() - - var out []powerActorInfo - for tipset, powerStates := range powerTips { - for _, act := range powerStates { - var pw powerActorInfo - pw.common = act - - powerActorState, err := getPowerActorState(ctx, p.node, tipset) - if err != nil { - return nil, xerrors.Errorf("get power state (@ %s): %w", pw.common.stateroot.String(), err) - } - - totalPower, err := powerActorState.TotalPower() - if err != nil { - return nil, xerrors.Errorf("failed to compute total power: %w", err) - } - - totalCommitted, err := powerActorState.TotalCommitted() - if err != nil { - return nil, xerrors.Errorf("failed to compute total committed: %w", err) - } - - totalLocked, err := powerActorState.TotalLocked() - if err != nil { - return nil, xerrors.Errorf("failed to compute total locked: %w", err) - } - - powerSmoothed, err := powerActorState.TotalPowerSmoothed() - if err != nil { - return nil, xerrors.Errorf("failed to determine smoothed power: %w", err) - } - - // NOTE: this doesn't set new* fields. Previously, we - // filled these using ThisEpoch* fields from the actor - // state, but these fields are effectively internal - // state and don't represent "new" power, as was - // assumed. - - participatingMiners, totalMiners, err := powerActorState.MinerCounts() - if err != nil { - return nil, xerrors.Errorf("failed to count miners: %w", err) - } - - pw.totalRawBytes = totalPower.RawBytePower - pw.totalQualityAdjustedBytes = totalPower.QualityAdjPower - pw.totalRawBytesCommitted = totalCommitted.RawBytePower - pw.totalQualityAdjustedBytesCommitted = totalCommitted.QualityAdjPower - pw.totalPledgeCollateral = totalLocked - pw.qaPowerSmoothed = powerSmoothed - pw.minerCountAboveMinimumPower = int64(participatingMiners) - pw.minerCount = int64(totalMiners) - } - } - - return out, nil -} - -func (p *Processor) persistPowerActors(ctx context.Context, powerStates []powerActorInfo) error { - // NB: use errgroup when there is more than a single store operation - return p.storePowerSmoothingEstimates(powerStates) -} - -func (p *Processor) storePowerSmoothingEstimates(powerStates []powerActorInfo) error { - tx, err := p.db.Begin() - if err != nil { - return xerrors.Errorf("begin chain_power tx: %w", err) - } - - if _, err := tx.Exec(`create temp table cp (like chain_power) on commit drop`); err != nil { - return xerrors.Errorf("prep chain_power: %w", err) - } - - stmt, err := tx.Prepare(`copy cp (state_root, total_raw_bytes_power, total_raw_bytes_committed, total_qa_bytes_power, total_qa_bytes_committed, total_pledge_collateral, qa_smoothed_position_estimate, qa_smoothed_velocity_estimate, miner_count, minimum_consensus_miner_count) from stdin;`) - if err != nil { - return xerrors.Errorf("prepare tmp chain_power: %w", err) - } - - for _, ps := range powerStates { - if _, err := stmt.Exec( - ps.common.stateroot.String(), - - ps.totalRawBytes.String(), - ps.totalRawBytesCommitted.String(), - ps.totalQualityAdjustedBytes.String(), - ps.totalQualityAdjustedBytesCommitted.String(), - ps.totalPledgeCollateral.String(), - - ps.qaPowerSmoothed.PositionEstimate.String(), - ps.qaPowerSmoothed.VelocityEstimate.String(), - - ps.minerCount, - ps.minerCountAboveMinimumPower, - ); err != nil { - return xerrors.Errorf("failed to store smoothing estimate: %w", err) - } - } - - if err := stmt.Close(); err != nil { - return xerrors.Errorf("close prepared chain_power: %w", err) - } - - if _, err := tx.Exec(`insert into chain_power select * from cp on conflict do nothing`); err != nil { - return xerrors.Errorf("insert chain_power from tmp: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("commit chain_power tx: %w", err) - } - - return nil - -} diff --git a/cmd/lotus-chainwatch/processor/processor.go b/cmd/lotus-chainwatch/processor/processor.go deleted file mode 100644 index af5935d47..000000000 --- a/cmd/lotus-chainwatch/processor/processor.go +++ /dev/null @@ -1,420 +0,0 @@ -package processor - -import ( - "context" - "database/sql" - "encoding/json" - "math" - "sync" - "time" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - - "github.com/filecoin-project/go-state-types/abi" - builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" - - "github.com/filecoin-project/lotus/api/v0api" - "github.com/filecoin-project/lotus/chain/types" - cw_util "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/util" - "github.com/filecoin-project/lotus/lib/parmap" -) - -var log = logging.Logger("processor") - -type Processor struct { - db *sql.DB - - node v0api.FullNode - ctxStore *cw_util.APIIpldStore - - genesisTs *types.TipSet - - // number of blocks processed at a time - batch int -} - -type ActorTips map[types.TipSetKey][]actorInfo - -type actorInfo struct { - act types.Actor - - stateroot cid.Cid - height abi.ChainEpoch // so that we can walk the actor changes in chronological order. - - tsKey types.TipSetKey - parentTsKey types.TipSetKey - - addr address.Address - state string -} - -func NewProcessor(ctx context.Context, db *sql.DB, node v0api.FullNode, batch int) *Processor { - ctxStore := cw_util.NewAPIIpldStore(ctx, node) - return &Processor{ - db: db, - ctxStore: ctxStore, - node: node, - batch: batch, - } -} - -func (p *Processor) setupSchemas() error { - // maintain order, subsequent calls create tables with foreign keys. - if err := p.setupMiners(); err != nil { - return err - } - - if err := p.setupMarket(); err != nil { - return err - } - - if err := p.setupRewards(); err != nil { - return err - } - - if err := p.setupMessages(); err != nil { - return err - } - - if err := p.setupCommonActors(); err != nil { - return err - } - - if err := p.setupPower(); err != nil { - return err - } - - return nil -} - -func (p *Processor) Start(ctx context.Context) { - log.Debug("Starting Processor") - - if err := p.setupSchemas(); err != nil { - log.Fatalw("Failed to setup processor", "error", err) - } - - var err error - p.genesisTs, err = p.node.ChainGetGenesis(ctx) - if err != nil { - log.Fatalw("Failed to get genesis state from lotus", "error", err.Error()) - } - - go p.subMpool(ctx) - - // main processor loop - go func() { - for { - select { - case <-ctx.Done(): - log.Info("Stopping Processor...") - return - default: - loopStart := time.Now() - toProcess, err := p.unprocessedBlocks(ctx, p.batch) - if err != nil { - log.Fatalw("Failed to get unprocessed blocks", "error", err) - } - - if len(toProcess) == 0 { - log.Info("No unprocessed blocks. Wait then try again...") - time.Sleep(time.Second * 30) - continue - } - - // TODO special case genesis state handling here to avoid all the special cases that will be needed for it else where - // before doing "normal" processing. - - actorChanges, nullRounds, err := p.collectActorChanges(ctx, toProcess) - if err != nil { - log.Fatalw("Failed to collect actor changes", "error", err) - } - log.Infow("Collected Actor Changes", - "MarketChanges", len(actorChanges[builtin2.StorageMarketActorCodeID]), - "MinerChanges", len(actorChanges[builtin2.StorageMinerActorCodeID]), - "RewardChanges", len(actorChanges[builtin2.RewardActorCodeID]), - "AccountChanges", len(actorChanges[builtin2.AccountActorCodeID]), - "nullRounds", len(nullRounds)) - - grp := sync.WaitGroup{} - - grp.Add(1) - go func() { - defer grp.Done() - if err := p.HandleMarketChanges(ctx, actorChanges[builtin2.StorageMarketActorCodeID]); err != nil { - log.Errorf("Failed to handle market changes: %v", err) - return - } - }() - - grp.Add(1) - go func() { - defer grp.Done() - if err := p.HandleMinerChanges(ctx, actorChanges[builtin2.StorageMinerActorCodeID]); err != nil { - log.Errorf("Failed to handle miner changes: %v", err) - return - } - }() - - grp.Add(1) - go func() { - defer grp.Done() - if err := p.HandleRewardChanges(ctx, actorChanges[builtin2.RewardActorCodeID], nullRounds); err != nil { - log.Errorf("Failed to handle reward changes: %v", err) - return - } - }() - - grp.Add(1) - go func() { - defer grp.Done() - if err := p.HandlePowerChanges(ctx, actorChanges[builtin2.StoragePowerActorCodeID]); err != nil { - log.Errorf("Failed to handle power actor changes: %v", err) - return - } - }() - - grp.Add(1) - go func() { - defer grp.Done() - if err := p.HandleMessageChanges(ctx, toProcess); err != nil { - log.Errorf("Failed to handle message changes: %v", err) - return - } - }() - - grp.Add(1) - go func() { - defer grp.Done() - if err := p.HandleCommonActorsChanges(ctx, actorChanges); err != nil { - log.Errorf("Failed to handle common actor changes: %v", err) - return - } - }() - - grp.Wait() - - if err := p.markBlocksProcessed(ctx, toProcess); err != nil { - log.Fatalw("Failed to mark blocks as processed", "error", err) - } - - if err := p.refreshViews(); err != nil { - log.Errorw("Failed to refresh views", "error", err) - } - log.Infow("Processed Batch Complete", "duration", time.Since(loopStart).String()) - } - } - }() - -} - -func (p *Processor) refreshViews() error { - if _, err := p.db.Exec(`refresh materialized view state_heights`); err != nil { - return err - } - - return nil -} - -func (p *Processor) collectActorChanges(ctx context.Context, toProcess map[cid.Cid]*types.BlockHeader) (map[cid.Cid]ActorTips, []types.TipSetKey, error) { - start := time.Now() - defer func() { - log.Debugw("Collected Actor Changes", "duration", time.Since(start).String()) - }() - // ActorCode - > tipset->[]actorInfo - out := map[cid.Cid]ActorTips{} - var outMu sync.Mutex - - // map of addresses to changed actors - var changes map[string]types.Actor - actorsSeen := map[cid.Cid]struct{}{} - - var nullRounds []types.TipSetKey - var nullBlkMu sync.Mutex - - // collect all actor state that has changes between block headers - paDone := 0 - parmap.Par(50, parmap.MapArr(toProcess), func(bh *types.BlockHeader) { - paDone++ - if paDone%100 == 0 { - log.Debugw("Collecting actor changes", "done", paDone, "percent", (paDone*100)/len(toProcess)) - } - - pts, err := p.node.ChainGetTipSet(ctx, types.NewTipSetKey(bh.Parents...)) - if err != nil { - log.Error(err) - return - } - - if pts.ParentState().Equals(bh.ParentStateRoot) { - nullBlkMu.Lock() - nullRounds = append(nullRounds, pts.Key()) - nullBlkMu.Unlock() - } - - // collect all actors that had state changes between the blockheader parent-state and its grandparent-state. - // TODO: changes will contain deleted actors, this causes needless processing further down the pipeline, consider - // a separate strategy for deleted actors - changes, err = p.node.StateChangedActors(ctx, pts.ParentState(), bh.ParentStateRoot) - if err != nil { - log.Error(err) - log.Debugw("StateChangedActors", "grandparent_state", pts.ParentState(), "parent_state", bh.ParentStateRoot) - return - } - - // record the state of all actors that have changed - for a, act := range changes { - act := act - a := a - - // ignore actors that were deleted. - has, err := p.node.ChainHasObj(ctx, act.Head) - if err != nil { - log.Error(err) - log.Debugw("ChanHasObj", "actor_head", act.Head) - return - } - if !has { - continue - } - - addr, err := address.NewFromString(a) - if err != nil { - log.Error(err) - log.Debugw("NewFromString", "address_string", a) - return - } - - ast, err := p.node.StateReadState(ctx, addr, pts.Key()) - if err != nil { - log.Error(err) - log.Debugw("StateReadState", "address_string", a, "parent_tipset_key", pts.Key()) - return - } - - // TODO look here for an empty state, maybe thats a sign the actor was deleted? - - state, err := json.Marshal(ast.State) - if err != nil { - log.Error(err) - return - } - - outMu.Lock() - if _, ok := actorsSeen[act.Head]; !ok { - _, ok := out[act.Code] - if !ok { - out[act.Code] = map[types.TipSetKey][]actorInfo{} - } - out[act.Code][pts.Key()] = append(out[act.Code][pts.Key()], actorInfo{ - act: act, - stateroot: bh.ParentStateRoot, - height: bh.Height, - tsKey: pts.Key(), - parentTsKey: pts.Parents(), - addr: addr, - state: string(state), - }) - } - actorsSeen[act.Head] = struct{}{} - outMu.Unlock() - } - }) - return out, nullRounds, nil -} - -func (p *Processor) unprocessedBlocks(ctx context.Context, batch int) (map[cid.Cid]*types.BlockHeader, error) { - start := time.Now() - defer func() { - log.Debugw("Gathered Blocks to process", "duration", time.Since(start).String()) - }() - rows, err := p.db.Query(` -with toProcess as ( - select b.cid, b.height, rank() over (order by height) as rnk - from blocks_synced bs - left join blocks b on bs.cid = b.cid - where bs.processed_at is null and b.height > 0 -) -select cid -from toProcess -where rnk <= $1 -`, batch) - if err != nil { - return nil, xerrors.Errorf("Failed to query for unprocessed blocks: %w", err) - } - out := map[cid.Cid]*types.BlockHeader{} - - minBlock := abi.ChainEpoch(math.MaxInt64) - maxBlock := abi.ChainEpoch(0) - // TODO consider parallel execution here for getting the blocks from the api as is done in fetchMessages() - for rows.Next() { - if rows.Err() != nil { - return nil, err - } - var c string - if err := rows.Scan(&c); err != nil { - log.Errorf("Failed to scan unprocessed blocks: %s", err.Error()) - continue - } - ci, err := cid.Parse(c) - if err != nil { - log.Errorf("Failed to parse unprocessed blocks: %s", err.Error()) - continue - } - bh, err := p.node.ChainGetBlock(ctx, ci) - if err != nil { - // this is a pretty serious issue. - log.Errorf("Failed to get block header %s: %s", ci.String(), err.Error()) - continue - } - out[ci] = bh - if bh.Height < minBlock { - minBlock = bh.Height - } - if bh.Height > maxBlock { - maxBlock = bh.Height - } - } - if minBlock <= maxBlock { - log.Infow("Gathered Blocks to Process", "start", minBlock, "end", maxBlock) - } - return out, rows.Close() -} - -func (p *Processor) markBlocksProcessed(ctx context.Context, processed map[cid.Cid]*types.BlockHeader) error { - start := time.Now() - processedHeight := abi.ChainEpoch(0) - defer func() { - log.Debugw("Marked blocks as Processed", "duration", time.Since(start).String()) - log.Infow("Processed Blocks", "height", processedHeight) - }() - tx, err := p.db.Begin() - if err != nil { - return err - } - - processedAt := time.Now().Unix() - stmt, err := tx.Prepare(`update blocks_synced set processed_at=$1 where cid=$2`) - if err != nil { - return err - } - - for c, bh := range processed { - if bh.Height > processedHeight { - processedHeight = bh.Height - } - if _, err := stmt.Exec(processedAt, c.String()); err != nil { - return err - } - } - - if err := stmt.Close(); err != nil { - return err - } - - return tx.Commit() -} diff --git a/cmd/lotus-chainwatch/processor/reward.go b/cmd/lotus-chainwatch/processor/reward.go deleted file mode 100644 index 72a329c87..000000000 --- a/cmd/lotus-chainwatch/processor/reward.go +++ /dev/null @@ -1,234 +0,0 @@ -package processor - -import ( - "context" - "time" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" - - "github.com/filecoin-project/lotus/chain/actors/builtin" - "github.com/filecoin-project/lotus/chain/actors/builtin/reward" - "github.com/filecoin-project/lotus/chain/types" - - cw_util "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/util" -) - -type rewardActorInfo struct { - common actorInfo - - cumSumBaselinePower big.Int - cumSumRealizedPower big.Int - - effectiveNetworkTime abi.ChainEpoch - effectiveBaselinePower big.Int - - // NOTE: These variables are wrong. Talk to @ZX about fixing. These _do - // not_ represent "new" anything. - newBaselinePower big.Int - newBaseReward big.Int - newSmoothingEstimate builtin.FilterEstimate - - totalMinedReward big.Int -} - -func (rw *rewardActorInfo) set(s reward.State) (err error) { - rw.cumSumBaselinePower, err = s.CumsumBaseline() - if err != nil { - return xerrors.Errorf("getting cumsum baseline power (@ %s): %w", rw.common.stateroot.String(), err) - } - - rw.cumSumRealizedPower, err = s.CumsumRealized() - if err != nil { - return xerrors.Errorf("getting cumsum realized power (@ %s): %w", rw.common.stateroot.String(), err) - } - - rw.effectiveNetworkTime, err = s.EffectiveNetworkTime() - if err != nil { - return xerrors.Errorf("getting effective network time (@ %s): %w", rw.common.stateroot.String(), err) - } - - rw.effectiveBaselinePower, err = s.EffectiveBaselinePower() - if err != nil { - return xerrors.Errorf("getting effective baseline power (@ %s): %w", rw.common.stateroot.String(), err) - } - - rw.totalMinedReward, err = s.TotalStoragePowerReward() - if err != nil { - return xerrors.Errorf("getting total mined (@ %s): %w", rw.common.stateroot.String(), err) - } - - rw.newBaselinePower, err = s.ThisEpochBaselinePower() - if err != nil { - return xerrors.Errorf("getting this epoch baseline power (@ %s): %w", rw.common.stateroot.String(), err) - } - - rw.newBaseReward, err = s.ThisEpochReward() - if err != nil { - return xerrors.Errorf("getting this epoch baseline power (@ %s): %w", rw.common.stateroot.String(), err) - } - - rw.newSmoothingEstimate, err = s.ThisEpochRewardSmoothed() - if err != nil { - return xerrors.Errorf("getting this epoch baseline power (@ %s): %w", rw.common.stateroot.String(), err) - } - return nil -} - -func (p *Processor) setupRewards() error { - tx, err := p.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -/* captures chain-specific power state for any given stateroot */ -create table if not exists chain_reward -( - state_root text not null - constraint chain_reward_pk - primary key, - cum_sum_baseline text not null, - cum_sum_realized text not null, - effective_network_time int not null, - effective_baseline_power text not null, - - new_baseline_power text not null, - new_reward numeric not null, - new_reward_smoothed_position_estimate text not null, - new_reward_smoothed_velocity_estimate text not null, - - total_mined_reward text not null -); -`); err != nil { - return err - } - - return tx.Commit() -} - -func (p *Processor) HandleRewardChanges(ctx context.Context, rewardTips ActorTips, nullRounds []types.TipSetKey) error { - rewardChanges, err := p.processRewardActors(ctx, rewardTips, nullRounds) - if err != nil { - return xerrors.Errorf("Failed to process reward actors: %w", err) - } - - if err := p.persistRewardActors(ctx, rewardChanges); err != nil { - return err - } - - return nil -} - -func (p *Processor) processRewardActors(ctx context.Context, rewardTips ActorTips, nullRounds []types.TipSetKey) ([]rewardActorInfo, error) { - start := time.Now() - defer func() { - log.Debugw("Processed Reward Actors", "duration", time.Since(start).String()) - }() - - var out []rewardActorInfo - for tipset, rewards := range rewardTips { - for _, act := range rewards { - var rw rewardActorInfo - rw.common = act - - // get reward actor states at each tipset once for all updates - rewardActor, err := p.node.StateGetActor(ctx, reward.Address, tipset) - if err != nil { - return nil, xerrors.Errorf("get reward state (@ %s): %w", rw.common.stateroot.String(), err) - } - - rewardActorState, err := reward.Load(cw_util.NewAPIIpldStore(ctx, p.node), rewardActor) - if err != nil { - return nil, xerrors.Errorf("read state obj (@ %s): %w", rw.common.stateroot.String(), err) - } - if err := rw.set(rewardActorState); err != nil { - return nil, err - } - - out = append(out, rw) - } - } - for _, tsKey := range nullRounds { - var rw rewardActorInfo - tipset, err := p.node.ChainGetTipSet(ctx, tsKey) - if err != nil { - return nil, err - } - rw.common.tsKey = tipset.Key() - rw.common.height = tipset.Height() - rw.common.stateroot = tipset.ParentState() - rw.common.parentTsKey = tipset.Parents() - // get reward actor states at each tipset once for all updates - rewardActor, err := p.node.StateGetActor(ctx, reward.Address, tsKey) - if err != nil { - return nil, err - } - - rewardActorState, err := reward.Load(cw_util.NewAPIIpldStore(ctx, p.node), rewardActor) - if err != nil { - return nil, xerrors.Errorf("read state obj (@ %s): %w", rw.common.stateroot.String(), err) - } - - if err := rw.set(rewardActorState); err != nil { - return nil, err - } - out = append(out, rw) - } - - return out, nil -} - -func (p *Processor) persistRewardActors(ctx context.Context, rewards []rewardActorInfo) error { - start := time.Now() - defer func() { - log.Debugw("Persisted Reward Actors", "duration", time.Since(start).String()) - }() - - tx, err := p.db.Begin() - if err != nil { - return xerrors.Errorf("begin chain_reward tx: %w", err) - } - - if _, err := tx.Exec(`create temp table cr (like chain_reward excluding constraints) on commit drop`); err != nil { - return xerrors.Errorf("prep chain_reward temp: %w", err) - } - - stmt, err := tx.Prepare(`copy cr ( state_root, cum_sum_baseline, cum_sum_realized, effective_network_time, effective_baseline_power, new_baseline_power, new_reward, new_reward_smoothed_position_estimate, new_reward_smoothed_velocity_estimate, total_mined_reward) from STDIN`) - if err != nil { - return xerrors.Errorf("prepare tmp chain_reward: %w", err) - } - - for _, rewardState := range rewards { - if _, err := stmt.Exec( - rewardState.common.stateroot.String(), - rewardState.cumSumBaselinePower.String(), - rewardState.cumSumRealizedPower.String(), - uint64(rewardState.effectiveNetworkTime), - rewardState.effectiveBaselinePower.String(), - rewardState.newBaselinePower.String(), - rewardState.newBaseReward.String(), - rewardState.newSmoothingEstimate.PositionEstimate.String(), - rewardState.newSmoothingEstimate.VelocityEstimate.String(), - rewardState.totalMinedReward.String(), - ); err != nil { - log.Errorw("failed to store chain power", "state_root", rewardState.common.stateroot, "error", err) - } - } - - if err := stmt.Close(); err != nil { - return xerrors.Errorf("close prepared chain_reward: %w", err) - } - - if _, err := tx.Exec(`insert into chain_reward select * from cr on conflict do nothing`); err != nil { - return xerrors.Errorf("insert chain_reward from tmp: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("commit chain_reward tx: %w", err) - } - - return nil -} diff --git a/cmd/lotus-chainwatch/run.go b/cmd/lotus-chainwatch/run.go deleted file mode 100644 index 6e47a100d..000000000 --- a/cmd/lotus-chainwatch/run.go +++ /dev/null @@ -1,107 +0,0 @@ -package main - -import ( - "database/sql" - "fmt" - "net/http" - _ "net/http/pprof" - "os" - "strings" - - "github.com/filecoin-project/lotus/api/v0api" - - _ "github.com/lib/pq" - - "github.com/filecoin-project/go-jsonrpc" - logging "github.com/ipfs/go-log/v2" - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - lcli "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/processor" - "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/scheduler" - "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/syncer" - "github.com/filecoin-project/lotus/cmd/lotus-chainwatch/util" -) - -var runCmd = &cli.Command{ - Name: "run", - Usage: "Start lotus chainwatch", - Flags: []cli.Flag{ - &cli.IntFlag{ - Name: "max-batch", - Value: 50, - }, - }, - Action: func(cctx *cli.Context) error { - go func() { - http.ListenAndServe(":6060", nil) //nolint:errcheck - }() - ll := cctx.String("log-level") - if err := logging.SetLogLevel("*", ll); err != nil { - return err - } - if err := logging.SetLogLevel("rpc", "error"); err != nil { - return err - } - - var api v0api.FullNode - var closer jsonrpc.ClientCloser - var err error - if tokenMaddr := cctx.String("api"); tokenMaddr != "" { - toks := strings.Split(tokenMaddr, ":") - if len(toks) != 2 { - return fmt.Errorf("invalid api tokens, expected :, got: %s", tokenMaddr) - } - - api, closer, err = util.GetFullNodeAPIUsingCredentials(cctx.Context, toks[1], toks[0]) - if err != nil { - return err - } - } else { - api, closer, err = lcli.GetFullNodeAPI(cctx) - if err != nil { - return err - } - } - defer closer() - ctx := lcli.ReqContext(cctx) - - v, err := api.Version(ctx) - if err != nil { - return err - } - - log.Infof("Remote version: %s", v.Version) - - maxBatch := cctx.Int("max-batch") - - db, err := sql.Open("postgres", cctx.String("db")) - if err != nil { - return err - } - defer func() { - if err := db.Close(); err != nil { - log.Errorw("Failed to close database", "error", err) - } - }() - - if err := db.Ping(); err != nil { - return xerrors.Errorf("Database failed to respond to ping (is it online?): %w", err) - } - db.SetMaxOpenConns(1350) - - sync := syncer.NewSyncer(db, api, 1400) - sync.Start(ctx) - - proc := processor.NewProcessor(ctx, db, api, maxBatch) - proc.Start(ctx) - - sched := scheduler.PrepareScheduler(db) - sched.Start(ctx) - - <-ctx.Done() - os.Exit(0) - return nil - }, -} diff --git a/cmd/lotus-chainwatch/scheduler/refresh_top_miners_by_base_reward.go b/cmd/lotus-chainwatch/scheduler/refresh_top_miners_by_base_reward.go deleted file mode 100644 index 145e84229..000000000 --- a/cmd/lotus-chainwatch/scheduler/refresh_top_miners_by_base_reward.go +++ /dev/null @@ -1,78 +0,0 @@ -package scheduler - -import ( - "context" - "database/sql" - - "golang.org/x/xerrors" -) - -func setupTopMinerByBaseRewardSchema(ctx context.Context, db *sql.DB) error { - select { - case <-ctx.Done(): - return nil - default: - } - - tx, err := db.Begin() - if err != nil { - return err - } - if _, err := tx.Exec(` - create materialized view if not exists top_miners_by_base_reward as - with total_rewards_by_miner as ( - select - b.miner, - sum(cr.new_reward * b.win_count) as total_reward - from blocks b - inner join chain_reward cr on b.parentstateroot = cr.state_root - group by 1 - ) select - rank() over (order by total_reward desc), - miner, - total_reward - from total_rewards_by_miner - group by 2, 3; - - create index if not exists top_miners_by_base_reward_miner_index - on top_miners_by_base_reward (miner); - - create materialized view if not exists top_miners_by_base_reward_max_height as - select - b."timestamp"as current_timestamp, - max(b.height) as current_height - from blocks b - join chain_reward cr on b.parentstateroot = cr.state_root - where cr.new_reward is not null - group by 1 - order by 1 desc - limit 1; - `); err != nil { - return xerrors.Errorf("create top_miners_by_base_reward views: %w", err) - } - - if err := tx.Commit(); err != nil { - return xerrors.Errorf("committing top_miners_by_base_reward views; %w", err) - } - return nil -} - -func refreshTopMinerByBaseReward(ctx context.Context, db *sql.DB) error { - select { - case <-ctx.Done(): - return nil - default: - } - - _, err := db.Exec("refresh materialized view top_miners_by_base_reward;") - if err != nil { - return xerrors.Errorf("refresh top_miners_by_base_reward: %w", err) - } - - _, err = db.Exec("refresh materialized view top_miners_by_base_reward_max_height;") - if err != nil { - return xerrors.Errorf("refresh top_miners_by_base_reward_max_height: %w", err) - } - - return nil -} diff --git a/cmd/lotus-chainwatch/scheduler/scheduler.go b/cmd/lotus-chainwatch/scheduler/scheduler.go deleted file mode 100644 index 6782bc16d..000000000 --- a/cmd/lotus-chainwatch/scheduler/scheduler.go +++ /dev/null @@ -1,60 +0,0 @@ -package scheduler - -import ( - "context" - "database/sql" - "time" - - logging "github.com/ipfs/go-log/v2" - - "golang.org/x/xerrors" -) - -var log = logging.Logger("scheduler") - -// Scheduler manages the execution of jobs triggered -// by tickers. Not externally configurable at runtime. -type Scheduler struct { - db *sql.DB -} - -// PrepareScheduler returns a ready-to-run Scheduler -func PrepareScheduler(db *sql.DB) *Scheduler { - return &Scheduler{db} -} - -func (s *Scheduler) setupSchema(ctx context.Context) error { - if err := setupTopMinerByBaseRewardSchema(ctx, s.db); err != nil { - return xerrors.Errorf("setup top miners by reward schema: %w", err) - } - return nil -} - -// Start the scheduler jobs at the defined intervals -func (s *Scheduler) Start(ctx context.Context) { - log.Debug("Starting Scheduler") - - if err := s.setupSchema(ctx); err != nil { - log.Fatalw("applying scheduling schema", "error", err) - } - - go func() { - // run once on start after schema has initialized - time.Sleep(1 * time.Minute) - if err := refreshTopMinerByBaseReward(ctx, s.db); err != nil { - log.Errorw("failed to refresh top miner", "error", err) - } - refreshTopMinerCh := time.NewTicker(30 * time.Second) - defer refreshTopMinerCh.Stop() - for { - select { - case <-refreshTopMinerCh.C: - if err := refreshTopMinerByBaseReward(ctx, s.db); err != nil { - log.Errorw("failed to refresh top miner", "error", err) - } - case <-ctx.Done(): - return - } - } - }() -} diff --git a/cmd/lotus-chainwatch/syncer/blockssub.go b/cmd/lotus-chainwatch/syncer/blockssub.go deleted file mode 100644 index ea9c079e8..000000000 --- a/cmd/lotus-chainwatch/syncer/blockssub.go +++ /dev/null @@ -1,27 +0,0 @@ -package syncer - -import ( - "context" - "time" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/ipfs/go-cid" -) - -func (s *Syncer) subBlocks(ctx context.Context) { - sub, err := s.node.SyncIncomingBlocks(ctx) - if err != nil { - log.Errorf("opening incoming block channel: %+v", err) - return - } - - log.Infow("Capturing incoming blocks") - for bh := range sub { - err := s.storeHeaders(map[cid.Cid]*types.BlockHeader{ - bh.Cid(): bh, - }, false, time.Now()) - if err != nil { - log.Errorf("storing incoming block header: %+v", err) - } - } -} diff --git a/cmd/lotus-chainwatch/syncer/sync.go b/cmd/lotus-chainwatch/syncer/sync.go deleted file mode 100644 index b5e9c73d6..000000000 --- a/cmd/lotus-chainwatch/syncer/sync.go +++ /dev/null @@ -1,527 +0,0 @@ -package syncer - -import ( - "container/list" - "context" - "database/sql" - "fmt" - "sync" - "time" - - "golang.org/x/xerrors" - - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - - "github.com/filecoin-project/lotus/api/v0api" - "github.com/filecoin-project/lotus/chain/store" - "github.com/filecoin-project/lotus/chain/types" -) - -var log = logging.Logger("syncer") - -type Syncer struct { - db *sql.DB - - lookbackLimit uint64 - - headerLk sync.Mutex - node v0api.FullNode -} - -func NewSyncer(db *sql.DB, node v0api.FullNode, lookbackLimit uint64) *Syncer { - return &Syncer{ - db: db, - node: node, - lookbackLimit: lookbackLimit, - } -} - -func (s *Syncer) setupSchemas() error { - tx, err := s.db.Begin() - if err != nil { - return err - } - - if _, err := tx.Exec(` -/* tracks circulating fil available on the network at each tipset */ -create table if not exists chain_economics -( - parent_state_root text not null - constraint chain_economics_pk primary key, - circulating_fil text not null, - vested_fil text not null, - mined_fil text not null, - burnt_fil text not null, - locked_fil text not null -); - -create table if not exists block_cids -( - cid text not null - constraint block_cids_pk - primary key -); - -create unique index if not exists block_cids_cid_uindex - on block_cids (cid); - -create table if not exists blocks_synced -( - cid text not null - constraint blocks_synced_pk - primary key - constraint blocks_block_cids_cid_fk - references block_cids (cid), - synced_at int not null, - processed_at int -); - -create unique index if not exists blocks_synced_cid_uindex - on blocks_synced (cid,processed_at); - -create table if not exists block_parents -( - block text not null - constraint blocks_block_cids_cid_fk - references block_cids (cid), - parent text not null -); - -create unique index if not exists block_parents_block_parent_uindex - on block_parents (block, parent); - -create table if not exists drand_entries -( - round bigint not null - constraint drand_entries_pk - primary key, - data bytea not null -); -create unique index if not exists drand_entries_round_uindex - on drand_entries (round); - -create table if not exists block_drand_entries -( - round bigint not null - constraint block_drand_entries_drand_entries_round_fk - references drand_entries (round), - block text not null - constraint blocks_block_cids_cid_fk - references block_cids (cid) -); -create unique index if not exists block_drand_entries_round_uindex - on block_drand_entries (round, block); - -create table if not exists blocks -( - cid text not null - constraint blocks_pk - primary key - constraint blocks_block_cids_cid_fk - references block_cids (cid), - parentWeight numeric not null, - parentStateRoot text not null, - height bigint not null, - miner text not null, - timestamp bigint not null, - ticket bytea not null, - election_proof bytea, - win_count bigint, - parent_base_fee text not null, - forksig bigint not null -); - -create unique index if not exists block_cid_uindex - on blocks (cid,height); - -create materialized view if not exists state_heights - as select min(b.height) height, b.parentstateroot - from blocks b group by b.parentstateroot; - -create index if not exists state_heights_height_index - on state_heights (height); - -create index if not exists state_heights_parentstateroot_index - on state_heights (parentstateroot); -`); err != nil { - return err - } - - return tx.Commit() -} - -func (s *Syncer) Start(ctx context.Context) { - if err := logging.SetLogLevel("syncer", "info"); err != nil { - log.Fatal(err) - } - log.Debug("Starting Syncer") - - if err := s.setupSchemas(); err != nil { - log.Fatal(err) - } - - // capture all reported blocks - go s.subBlocks(ctx) - - // we need to ensure that on a restart we don't reprocess the whole flarping chain - var sinceEpoch uint64 - blkCID, height, err := s.mostRecentlySyncedBlockHeight() - if err != nil { - log.Fatalw("failed to find most recently synced block", "error", err) - } else { - if height > 0 { - log.Infow("Found starting point for syncing", "blockCID", blkCID.String(), "height", height) - sinceEpoch = uint64(height) - } - } - - // continue to keep the block headers table up to date. - notifs, err := s.node.ChainNotify(ctx) - if err != nil { - log.Fatal(err) - } - - go func() { - for notif := range notifs { - for _, change := range notif { - switch change.Type { - case store.HCCurrent: - // This case is important for capturing the initial state of a node - // which might be on a dead network with no new blocks being produced. - // It also allows a fresh Chainwatch instance to start walking the - // chain without waiting for a new block to come along. - fallthrough - case store.HCApply: - unsynced, err := s.unsyncedBlocks(ctx, change.Val, sinceEpoch) - if err != nil { - log.Errorw("failed to gather unsynced blocks", "error", err) - } - - if err := s.storeCirculatingSupply(ctx, change.Val); err != nil { - log.Errorw("failed to store circulating supply", "error", err) - } - - if len(unsynced) == 0 { - continue - } - - if err := s.storeHeaders(unsynced, true, time.Now()); err != nil { - // so this is pretty bad, need some kind of retry.. - // for now just log an error and the blocks will be attempted again on next notifi - log.Errorw("failed to store unsynced blocks", "error", err) - } - - sinceEpoch = uint64(change.Val.Height()) - case store.HCRevert: - log.Debug("revert todo") - } - } - } - }() -} - -func (s *Syncer) unsyncedBlocks(ctx context.Context, head *types.TipSet, since uint64) (map[cid.Cid]*types.BlockHeader, error) { - hasList, err := s.syncedBlocks(since, s.lookbackLimit) - if err != nil { - return nil, err - } - - // build a list of blocks that we have not synced. - toVisit := list.New() - for _, header := range head.Blocks() { - toVisit.PushBack(header) - } - - toSync := map[cid.Cid]*types.BlockHeader{} - - for toVisit.Len() > 0 { - bh := toVisit.Remove(toVisit.Back()).(*types.BlockHeader) - _, has := hasList[bh.Cid()] - if _, seen := toSync[bh.Cid()]; seen || has { - continue - } - - toSync[bh.Cid()] = bh - if len(toSync)%500 == 10 { - log.Debugw("To visit", "toVisit", toVisit.Len(), "toSync", len(toSync), "current_height", bh.Height) - } - - if bh.Height == 0 { - continue - } - - pts, err := s.node.ChainGetTipSet(ctx, types.NewTipSetKey(bh.Parents...)) - if err != nil { - log.Error(err) - continue - } - - for _, header := range pts.Blocks() { - toVisit.PushBack(header) - } - } - log.Debugw("Gathered unsynced blocks", "count", len(toSync)) - return toSync, nil -} - -func (s *Syncer) syncedBlocks(since, limit uint64) (map[cid.Cid]struct{}, error) { - rws, err := s.db.Query(`select bs.cid FROM blocks_synced bs left join blocks b on b.cid = bs.cid where b.height <= $1 and bs.processed_at is not null limit $2`, since, limit) - if err != nil { - return nil, xerrors.Errorf("Failed to query blocks_synced: %w", err) - } - out := map[cid.Cid]struct{}{} - - for rws.Next() { - var c string - if err := rws.Scan(&c); err != nil { - return nil, xerrors.Errorf("Failed to scan blocks_synced: %w", err) - } - - ci, err := cid.Parse(c) - if err != nil { - return nil, xerrors.Errorf("Failed to parse blocks_synced: %w", err) - } - - out[ci] = struct{}{} - } - return out, nil -} - -func (s *Syncer) mostRecentlySyncedBlockHeight() (cid.Cid, int64, error) { - rw := s.db.QueryRow(` -select blocks_synced.cid, b.height -from blocks_synced -left join blocks b on blocks_synced.cid = b.cid -where processed_at is not null -order by height desc -limit 1 -`) - - var c string - var h int64 - if err := rw.Scan(&c, &h); err != nil { - if err == sql.ErrNoRows { - return cid.Undef, 0, nil - } - return cid.Undef, -1, err - } - - ci, err := cid.Parse(c) - if err != nil { - return cid.Undef, -1, err - } - - return ci, h, nil -} - -func (s *Syncer) storeCirculatingSupply(ctx context.Context, tipset *types.TipSet) error { - supply, err := s.node.StateVMCirculatingSupplyInternal(ctx, tipset.Key()) - if err != nil { - return err - } - - ceInsert := `insert into chain_economics (parent_state_root, circulating_fil, vested_fil, mined_fil, burnt_fil, locked_fil) ` + - `values ('%s', '%s', '%s', '%s', '%s', '%s') on conflict on constraint chain_economics_pk do ` + - `update set (circulating_fil, vested_fil, mined_fil, burnt_fil, locked_fil) = ('%[2]s', '%[3]s', '%[4]s', '%[5]s', '%[6]s') ` + - `where chain_economics.parent_state_root = '%[1]s';` - - if _, err := s.db.Exec(fmt.Sprintf(ceInsert, - tipset.ParentState().String(), - supply.FilCirculating.String(), - supply.FilVested.String(), - supply.FilMined.String(), - supply.FilBurnt.String(), - supply.FilLocked.String(), - )); err != nil { - return xerrors.Errorf("insert circulating supply for tipset (%s): %w", tipset.Key().String(), err) - } - - return nil -} - -func (s *Syncer) storeHeaders(bhs map[cid.Cid]*types.BlockHeader, sync bool, timestamp time.Time) error { - s.headerLk.Lock() - defer s.headerLk.Unlock() - if len(bhs) == 0 { - return nil - } - log.Debugw("Storing Headers", "count", len(bhs)) - - tx, err := s.db.Begin() - if err != nil { - return xerrors.Errorf("begin: %w", err) - } - - if _, err := tx.Exec(` - -create temp table bc (like block_cids excluding constraints) on commit drop; -create temp table de (like drand_entries excluding constraints) on commit drop; -create temp table bde (like block_drand_entries excluding constraints) on commit drop; -create temp table tbp (like block_parents excluding constraints) on commit drop; -create temp table bs (like blocks_synced excluding constraints) on commit drop; -create temp table b (like blocks excluding constraints) on commit drop; - - -`); err != nil { - return xerrors.Errorf("prep temp: %w", err) - } - - { - stmt, err := tx.Prepare(`copy bc (cid) from STDIN`) - if err != nil { - return err - } - - for _, bh := range bhs { - if _, err := stmt.Exec(bh.Cid().String()); err != nil { - log.Error(err) - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into block_cids select * from bc on conflict do nothing `); err != nil { - return xerrors.Errorf("drand entries put: %w", err) - } - } - - { - stmt, err := tx.Prepare(`copy de (round, data) from STDIN`) - if err != nil { - return err - } - - for _, bh := range bhs { - for _, ent := range bh.BeaconEntries { - if _, err := stmt.Exec(ent.Round, ent.Data); err != nil { - log.Error(err) - } - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into drand_entries select * from de on conflict do nothing `); err != nil { - return xerrors.Errorf("drand entries put: %w", err) - } - } - - { - stmt, err := tx.Prepare(`copy bde (round, block) from STDIN`) - if err != nil { - return err - } - - for _, bh := range bhs { - for _, ent := range bh.BeaconEntries { - if _, err := stmt.Exec(ent.Round, bh.Cid().String()); err != nil { - log.Error(err) - } - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into block_drand_entries select * from bde on conflict do nothing `); err != nil { - return xerrors.Errorf("block drand entries put: %w", err) - } - } - - { - stmt, err := tx.Prepare(`copy tbp (block, parent) from STDIN`) - if err != nil { - return err - } - - for _, bh := range bhs { - for _, parent := range bh.Parents { - if _, err := stmt.Exec(bh.Cid().String(), parent.String()); err != nil { - log.Error(err) - } - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into block_parents select * from tbp on conflict do nothing `); err != nil { - return xerrors.Errorf("parent put: %w", err) - } - } - - if sync { - - stmt, err := tx.Prepare(`copy bs (cid, synced_at) from stdin `) - if err != nil { - return err - } - - for _, bh := range bhs { - if _, err := stmt.Exec(bh.Cid().String(), timestamp.Unix()); err != nil { - log.Error(err) - } - } - - if err := stmt.Close(); err != nil { - return err - } - - if _, err := tx.Exec(`insert into blocks_synced select * from bs on conflict do nothing `); err != nil { - return xerrors.Errorf("syncd put: %w", err) - } - } - - stmt2, err := tx.Prepare(`copy b (cid, parentWeight, parentStateRoot, height, miner, "timestamp", ticket, election_proof, win_count, parent_base_fee, forksig) from stdin`) - if err != nil { - return err - } - - for _, bh := range bhs { - var eproof, winCount interface{} - if bh.ElectionProof != nil { - eproof = bh.ElectionProof.VRFProof - winCount = bh.ElectionProof.WinCount - } - - if bh.Ticket == nil { - log.Warnf("got a block with nil ticket") - - bh.Ticket = &types.Ticket{ - VRFProof: []byte{}, - } - } - - if _, err := stmt2.Exec( - bh.Cid().String(), - bh.ParentWeight.String(), - bh.ParentStateRoot.String(), - bh.Height, - bh.Miner.String(), - bh.Timestamp, - bh.Ticket.VRFProof, - eproof, - winCount, - bh.ParentBaseFee.String(), - bh.ForkSignaling); err != nil { - log.Error(err) - } - } - - if err := stmt2.Close(); err != nil { - return xerrors.Errorf("s2 close: %w", err) - } - - if _, err := tx.Exec(`insert into blocks select * from b on conflict do nothing `); err != nil { - return xerrors.Errorf("blk put: %w", err) - } - - return tx.Commit() -} diff --git a/cmd/lotus-chainwatch/util/api.go b/cmd/lotus-chainwatch/util/api.go deleted file mode 100644 index f8f22cbbf..000000000 --- a/cmd/lotus-chainwatch/util/api.go +++ /dev/null @@ -1,34 +0,0 @@ -package util - -import ( - "context" - "net/http" - - "github.com/filecoin-project/go-jsonrpc" - "github.com/filecoin-project/lotus/api/client" - "github.com/filecoin-project/lotus/api/v0api" - ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr/net" -) - -func GetFullNodeAPIUsingCredentials(ctx context.Context, listenAddr, token string) (v0api.FullNode, jsonrpc.ClientCloser, error) { - parsedAddr, err := ma.NewMultiaddr(listenAddr) - if err != nil { - return nil, nil, err - } - - _, addr, err := manet.DialArgs(parsedAddr) - if err != nil { - return nil, nil, err - } - - return client.NewFullNodeRPCV0(ctx, apiURI(addr), apiHeaders(token)) -} -func apiURI(addr string) string { - return "ws://" + addr + "/rpc/v0" -} -func apiHeaders(token string) http.Header { - headers := http.Header{} - headers.Add("Authorization", "Bearer "+token) - return headers -} diff --git a/cmd/lotus-chainwatch/util/contextStore.go b/cmd/lotus-chainwatch/util/contextStore.go deleted file mode 100644 index c93f87f9b..000000000 --- a/cmd/lotus-chainwatch/util/contextStore.go +++ /dev/null @@ -1,51 +0,0 @@ -package util - -import ( - "bytes" - "context" - "fmt" - - "github.com/ipfs/go-cid" - cbg "github.com/whyrusleeping/cbor-gen" - - "github.com/filecoin-project/lotus/api/v0api" -) - -// TODO extract this to a common location in lotus and reuse the code - -// APIIpldStore is required for AMT and HAMT access. -type APIIpldStore struct { - ctx context.Context - api v0api.FullNode -} - -func NewAPIIpldStore(ctx context.Context, api v0api.FullNode) *APIIpldStore { - return &APIIpldStore{ - ctx: ctx, - api: api, - } -} - -func (ht *APIIpldStore) Context() context.Context { - return ht.ctx -} - -func (ht *APIIpldStore) Get(ctx context.Context, c cid.Cid, out interface{}) error { - raw, err := ht.api.ChainReadObj(ctx, c) - if err != nil { - return err - } - - cu, ok := out.(cbg.CBORUnmarshaler) - if ok { - if err := cu.UnmarshalCBOR(bytes.NewReader(raw)); err != nil { - return err - } - return nil - } - return fmt.Errorf("Object does not implement CBORUnmarshaler: %T", out) -} - -func (ht *APIIpldStore) Put(ctx context.Context, v interface{}) (cid.Cid, error) { - return cid.Undef, fmt.Errorf("Put is not implemented on APIIpldStore") -} diff --git a/cmd/lotus-miner/actor.go b/cmd/lotus-miner/actor.go index 8b03f2360..0595c447a 100644 --- a/cmd/lotus-miner/actor.go +++ b/cmd/lotus-miner/actor.go @@ -415,7 +415,7 @@ var actorControlList = &cli.Command{ ctx := lcli.ReqContext(cctx) - maddr, err := nodeApi.ActorAddress(ctx) + maddr, err := getActorAddress(ctx, cctx) if err != nil { return err } diff --git a/cmd/lotus-miner/actor_test.go b/cmd/lotus-miner/actor_test.go index 073a83059..5650a9ac5 100644 --- a/cmd/lotus-miner/actor_test.go +++ b/cmd/lotus-miner/actor_test.go @@ -10,7 +10,6 @@ import ( "testing" "time" - "github.com/filecoin-project/go-state-types/network" "github.com/stretchr/testify/require" "github.com/urfave/cli/v2" @@ -34,9 +33,7 @@ func TestWorkerKeyChange(t *testing.T) { kit.QuietMiningLogs() blocktime := 1 * time.Millisecond - client1, client2, miner, ens := kit.EnsembleTwoOne(t, kit.MockProofs(), - kit.ConstructorOpts(kit.InstantaneousNetworkVersion(network.Version13)), - ) + client1, client2, miner, ens := kit.EnsembleTwoOne(t, kit.MockProofs()) ens.InterconnectAll().BeginMining(blocktime) output := bytes.NewBuffer(nil) diff --git a/cmd/lotus-miner/dagstore.go b/cmd/lotus-miner/dagstore.go new file mode 100644 index 000000000..6522b02dc --- /dev/null +++ b/cmd/lotus-miner/dagstore.go @@ -0,0 +1,267 @@ +package main + +import ( + "fmt" + "os" + + "github.com/fatih/color" + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/lotus/api" + lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/lib/tablewriter" +) + +var dagstoreCmd = &cli.Command{ + Name: "dagstore", + Usage: "Manage the dagstore on the markets subsystem", + Subcommands: []*cli.Command{ + dagstoreListShardsCmd, + dagstoreInitializeShardCmd, + dagstoreRecoverShardCmd, + dagstoreInitializeAllCmd, + dagstoreGcCmd, + }, +} + +var dagstoreListShardsCmd = &cli.Command{ + Name: "list-shards", + Usage: "List all shards known to the dagstore, with their current status", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "color", + Usage: "use color in display output", + DefaultText: "depends on output being a TTY", + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.IsSet("color") { + color.NoColor = !cctx.Bool("color") + } + + marketsApi, closer, err := lcli.GetMarketsAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := lcli.ReqContext(cctx) + + shards, err := marketsApi.DagstoreListShards(ctx) + if err != nil { + return err + } + + if len(shards) == 0 { + return nil + } + + tw := tablewriter.New( + tablewriter.Col("Key"), + tablewriter.Col("State"), + tablewriter.Col("Error"), + ) + + colors := map[string]color.Attribute{ + "ShardStateAvailable": color.FgGreen, + "ShardStateServing": color.FgBlue, + "ShardStateErrored": color.FgRed, + "ShardStateNew": color.FgYellow, + } + + for _, s := range shards { + m := map[string]interface{}{ + "Key": s.Key, + "State": func() string { + if c, ok := colors[s.State]; ok { + return color.New(c).Sprint(s.State) + } + return s.State + }(), + "Error": s.Error, + } + tw.Write(m) + } + + return tw.Flush(os.Stdout) + }, +} + +var dagstoreInitializeShardCmd = &cli.Command{ + Name: "initialize-shard", + ArgsUsage: "[key]", + Usage: "Initialize the specified shard", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "color", + Usage: "use color in display output", + DefaultText: "depends on output being a TTY", + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.IsSet("color") { + color.NoColor = !cctx.Bool("color") + } + + if cctx.NArg() != 1 { + return fmt.Errorf("must provide a single shard key") + } + + marketsApi, closer, err := lcli.GetMarketsAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := lcli.ReqContext(cctx) + + return marketsApi.DagstoreInitializeShard(ctx, cctx.Args().First()) + }, +} + +var dagstoreRecoverShardCmd = &cli.Command{ + Name: "recover-shard", + ArgsUsage: "[key]", + Usage: "Attempt to recover a shard in errored state", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "color", + Usage: "use color in display output", + DefaultText: "depends on output being a TTY", + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.IsSet("color") { + color.NoColor = !cctx.Bool("color") + } + + if cctx.NArg() != 1 { + return fmt.Errorf("must provide a single shard key") + } + + marketsApi, closer, err := lcli.GetMarketsAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := lcli.ReqContext(cctx) + + return marketsApi.DagstoreRecoverShard(ctx, cctx.Args().First()) + }, +} + +var dagstoreInitializeAllCmd = &cli.Command{ + Name: "initialize-all", + Usage: "Initialize all uninitialized shards, streaming results as they're produced; only shards for unsealed pieces are initialized by default", + Flags: []cli.Flag{ + &cli.UintFlag{ + Name: "concurrency", + Usage: "maximum shards to initialize concurrently at a time; use 0 for unlimited", + Required: true, + }, + &cli.BoolFlag{ + Name: "include-sealed", + Usage: "initialize sealed pieces as well", + }, + &cli.BoolFlag{ + Name: "color", + Usage: "use color in display output", + DefaultText: "depends on output being a TTY", + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.IsSet("color") { + color.NoColor = !cctx.Bool("color") + } + + concurrency := cctx.Uint("concurrency") + sealed := cctx.Bool("sealed") + + marketsApi, closer, err := lcli.GetMarketsAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := lcli.ReqContext(cctx) + + params := api.DagstoreInitializeAllParams{ + MaxConcurrency: int(concurrency), + IncludeSealed: sealed, + } + + ch, err := marketsApi.DagstoreInitializeAll(ctx, params) + if err != nil { + return err + } + + for { + select { + case evt, ok := <-ch: + if !ok { + return nil + } + _, _ = fmt.Fprint(os.Stdout, color.New(color.BgHiBlack).Sprintf("(%d/%d)", evt.Current, evt.Total)) + _, _ = fmt.Fprint(os.Stdout, " ") + if evt.Event == "start" { + _, _ = fmt.Fprintln(os.Stdout, evt.Key, color.New(color.Reset).Sprint("STARTING")) + } else { + if evt.Success { + _, _ = fmt.Fprintln(os.Stdout, evt.Key, color.New(color.FgGreen).Sprint("SUCCESS")) + } else { + _, _ = fmt.Fprintln(os.Stdout, evt.Key, color.New(color.FgRed).Sprint("ERROR"), evt.Error) + } + } + + case <-ctx.Done(): + return fmt.Errorf("aborted") + } + } + }, +} + +var dagstoreGcCmd = &cli.Command{ + Name: "gc", + Usage: "Garbage collect the dagstore", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "color", + Usage: "use color in display output", + DefaultText: "depends on output being a TTY", + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.IsSet("color") { + color.NoColor = !cctx.Bool("color") + } + + marketsApi, closer, err := lcli.GetMarketsAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := lcli.ReqContext(cctx) + + collected, err := marketsApi.DagstoreGC(ctx) + if err != nil { + return err + } + + if len(collected) == 0 { + _, _ = fmt.Fprintln(os.Stdout, "no shards collected") + return nil + } + + for _, e := range collected { + if e.Error == "" { + _, _ = fmt.Fprintln(os.Stdout, e.Key, color.New(color.FgGreen).Sprint("SUCCESS")) + } else { + _, _ = fmt.Fprintln(os.Stdout, e.Key, color.New(color.FgRed).Sprint("ERROR"), e.Error) + } + } + + return nil + }, +} diff --git a/cmd/lotus-miner/main.go b/cmd/lotus-miner/main.go index 9cee61b03..47eb3e66f 100644 --- a/cmd/lotus-miner/main.go +++ b/cmd/lotus-miner/main.go @@ -10,12 +10,13 @@ import ( "go.opencensus.io/trace" "golang.org/x/xerrors" + cliutil "github.com/filecoin-project/lotus/cli/util" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" lcli "github.com/filecoin-project/lotus/cli" - cliutil "github.com/filecoin-project/lotus/cli/util" "github.com/filecoin-project/lotus/lib/lotuslog" "github.com/filecoin-project/lotus/lib/tracing" "github.com/filecoin-project/lotus/node/repo" @@ -47,6 +48,7 @@ func main() { lcli.WithCategory("market", storageDealsCmd), lcli.WithCategory("market", retrievalDealsCmd), lcli.WithCategory("market", dataTransfersCmd), + lcli.WithCategory("market", dagstoreCmd), lcli.WithCategory("storage", sectorsCmd), lcli.WithCategory("storage", provingCmd), lcli.WithCategory("storage", storageCmd), diff --git a/cmd/lotus-miner/market.go b/cmd/lotus-miner/market.go index a9d1f2f46..be1c5fcd2 100644 --- a/cmd/lotus-miner/market.go +++ b/cmd/lotus-miner/market.go @@ -591,7 +591,7 @@ var setSealDurationCmd = &cli.Command{ Usage: "Set the expected time, in minutes, that you expect sealing sectors to take. Deals that start before this duration will be rejected.", ArgsUsage: "", Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + nodeApi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } diff --git a/cmd/lotus-miner/pieces.go b/cmd/lotus-miner/pieces.go index d5f189943..75605c1ed 100644 --- a/cmd/lotus-miner/pieces.go +++ b/cmd/lotus-miner/pieces.go @@ -26,7 +26,7 @@ var piecesListPiecesCmd = &cli.Command{ Name: "list-pieces", Usage: "list registered pieces", Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + nodeApi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -49,7 +49,7 @@ var piecesListCidInfosCmd = &cli.Command{ Name: "list-cids", Usage: "list registered payload CIDs", Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + nodeApi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -76,7 +76,7 @@ var piecesInfoCmd = &cli.Command{ return lcli.ShowHelp(cctx, fmt.Errorf("must specify piece cid")) } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + nodeApi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -111,7 +111,7 @@ var piecesCidInfoCmd = &cli.Command{ return lcli.ShowHelp(cctx, fmt.Errorf("must specify payload cid")) } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + nodeApi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } diff --git a/cmd/lotus-miner/sealing.go b/cmd/lotus-miner/sealing.go index 3bf4c675f..75f02845c 100644 --- a/cmd/lotus-miner/sealing.go +++ b/cmd/lotus-miner/sealing.go @@ -91,7 +91,10 @@ var sealingWorkersCmd = &cli.Command{ var barCols = uint64(64) cpuBars := int(stat.CpuUse * barCols / stat.Info.Resources.CPUs) - cpuBar := strings.Repeat("|", cpuBars) + strings.Repeat(" ", int(barCols)-cpuBars) + cpuBar := strings.Repeat("|", cpuBars) + if int(barCols)-cpuBars >= 0 { + cpuBar += strings.Repeat(" ", int(barCols)-cpuBars) + } fmt.Printf("\tCPU: [%s] %d/%d core(s) in use\n", color.GreenString(cpuBar), stat.CpuUse, stat.Info.Resources.CPUs) diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index fbf84ecff..39f9f53b9 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -1,6 +1,8 @@ package main import ( + "bufio" + "encoding/json" "fmt" "os" "sort" @@ -10,16 +12,19 @@ import ( "github.com/docker/go-units" "github.com/fatih/color" + cbor "github.com/ipfs/go-ipld-cbor" "github.com/urfave/cli/v2" "golang.org/x/xerrors" "github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" - miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" @@ -38,6 +43,8 @@ var sectorsCmd = &cli.Command{ sectorsRefsCmd, sectorsUpdateCmd, sectorsPledgeCmd, + sectorsCheckExpireCmd, + sectorsRenewCmd, sectorsExtendCmd, sectorsTerminateCmd, sectorsRemoveCmd, @@ -396,7 +403,7 @@ var sectorsRefsCmd = &cli.Command{ Name: "refs", Usage: "List References to sectors", Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + nodeApi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -418,6 +425,519 @@ var sectorsRefsCmd = &cli.Command{ }, } +var sectorsCheckExpireCmd = &cli.Command{ + Name: "check-expire", + Usage: "Inspect expiring sectors", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "cutoff", + Usage: "skip sectors whose current expiration is more than epochs from now, defaults to 60 days", + Value: 172800, + }, + }, + Action: func(cctx *cli.Context) error { + + fullApi, nCloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer nCloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := getActorAddress(ctx, cctx) + if err != nil { + return err + } + + head, err := fullApi.ChainHead(ctx) + if err != nil { + return err + } + currEpoch := head.Height() + + nv, err := fullApi.StateNetworkVersion(ctx, types.EmptyTSK) + if err != nil { + return err + } + + sectors, err := fullApi.StateMinerActiveSectors(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + n := 0 + for _, s := range sectors { + if s.Expiration-currEpoch <= abi.ChainEpoch(cctx.Int64("cutoff")) { + sectors[n] = s + n++ + } + } + sectors = sectors[:n] + + sort.Slice(sectors, func(i, j int) bool { + if sectors[i].Expiration == sectors[j].Expiration { + return sectors[i].SectorNumber < sectors[j].SectorNumber + } + return sectors[i].Expiration < sectors[j].Expiration + }) + + tw := tablewriter.New( + tablewriter.Col("ID"), + tablewriter.Col("SealProof"), + tablewriter.Col("InitialPledge"), + tablewriter.Col("Activation"), + tablewriter.Col("Expiration"), + tablewriter.Col("MaxExpiration"), + tablewriter.Col("MaxExtendNow")) + + for _, sector := range sectors { + MaxExpiration := sector.Activation + policy.GetSectorMaxLifetime(sector.SealProof, nv) + MaxExtendNow := currEpoch + policy.GetMaxSectorExpirationExtension() + + if MaxExtendNow > MaxExpiration { + MaxExtendNow = MaxExpiration + } + + tw.Write(map[string]interface{}{ + "ID": sector.SectorNumber, + "SealProof": sector.SealProof, + "InitialPledge": types.FIL(sector.InitialPledge).Short(), + "Activation": lcli.EpochTime(currEpoch, sector.Activation), + "Expiration": lcli.EpochTime(currEpoch, sector.Expiration), + "MaxExpiration": lcli.EpochTime(currEpoch, MaxExpiration), + "MaxExtendNow": lcli.EpochTime(currEpoch, MaxExtendNow), + }) + } + + return tw.Flush(os.Stdout) + }, +} + +type PseudoExpirationExtension struct { + Deadline uint64 + Partition uint64 + Sectors string + NewExpiration abi.ChainEpoch +} + +type PseudoExtendSectorExpirationParams struct { + Extensions []PseudoExpirationExtension +} + +func NewPseudoExtendParams(p *miner5.ExtendSectorExpirationParams) (*PseudoExtendSectorExpirationParams, error) { + res := PseudoExtendSectorExpirationParams{} + for _, ext := range p.Extensions { + scount, err := ext.Sectors.Count() + if err != nil { + return nil, err + } + + sectors, err := ext.Sectors.All(scount) + if err != nil { + return nil, err + } + + res.Extensions = append(res.Extensions, PseudoExpirationExtension{ + Deadline: ext.Deadline, + Partition: ext.Partition, + Sectors: ArrayToString(sectors), + NewExpiration: ext.NewExpiration, + }) + } + return &res, nil +} + +// ArrayToString Example: {1,3,4,5,8,9} -> "1,3-5,8-9" +func ArrayToString(array []uint64) string { + sort.Slice(array, func(i, j int) bool { + return array[i] < array[j] + }) + + var sarray []string + s := "" + + for i, elm := range array { + if i == 0 { + s = strconv.FormatUint(elm, 10) + continue + } + if elm == array[i-1] { + continue // filter out duplicates + } else if elm == array[i-1]+1 { + s = strings.Split(s, "-")[0] + "-" + strconv.FormatUint(elm, 10) + } else { + sarray = append(sarray, s) + s = strconv.FormatUint(elm, 10) + } + } + + if s != "" { + sarray = append(sarray, s) + } + + return strings.Join(sarray, ",") +} + +func getSectorsFromFile(filePath string) ([]uint64, error) { + file, err := os.Open(filePath) + if err != nil { + return nil, err + } + + scanner := bufio.NewScanner(file) + sectors := make([]uint64, 0) + + for scanner.Scan() { + line := scanner.Text() + + id, err := strconv.ParseUint(line, 10, 64) + if err != nil { + return nil, xerrors.Errorf("could not parse %s as sector id: %s", line, err) + } + + sectors = append(sectors, id) + } + + if err = file.Close(); err != nil { + return nil, err + } + + return sectors, nil +} + +var sectorsRenewCmd = &cli.Command{ + Name: "renew", + Usage: "Renew expiring sectors while not exceeding each sector's max life", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "from", + Usage: "only consider sectors whose current expiration epoch is in the range of [from, to], defaults to: now + 120 (1 hour)", + }, + &cli.Int64Flag{ + Name: "to", + Usage: "only consider sectors whose current expiration epoch is in the range of [from, to], defaults to: now + 92160 (32 days)", + }, + &cli.StringFlag{ + Name: "sector-file", + Usage: "provide a file containing one sector number in each line, ignoring above selecting criteria", + }, + &cli.StringFlag{ + Name: "exclude", + Usage: "optionally provide a file containing excluding sectors", + }, + &cli.Int64Flag{ + Name: "extension", + Usage: "try to extend selected sectors by this number of epochs, defaults to 540 days", + Value: 1555200, + }, + &cli.Int64Flag{ + Name: "new-expiration", + Usage: "try to extend selected sectors to this epoch, ignoring extension", + }, + &cli.Int64Flag{ + Name: "tolerance", + Usage: "don't try to extend sectors by fewer than this number of epochs, defaults to 7 days", + Value: 20160, + }, + &cli.StringFlag{ + Name: "max-fee", + Usage: "use up to this amount of FIL for one message. pass this flag to avoid message congestion.", + Value: "0", + }, + &cli.BoolFlag{ + Name: "really-do-it", + Usage: "pass this flag to really renew sectors, otherwise will only print out json representation of parameters", + }, + }, + Action: func(cctx *cli.Context) error { + mf, err := types.ParseFIL(cctx.String("max-fee")) + if err != nil { + return err + } + + spec := &api.MessageSendSpec{MaxFee: abi.TokenAmount(mf)} + + fullApi, nCloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer nCloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := getActorAddress(ctx, cctx) + if err != nil { + return err + } + + head, err := fullApi.ChainHead(ctx) + if err != nil { + return err + } + currEpoch := head.Height() + + nv, err := fullApi.StateNetworkVersion(ctx, types.EmptyTSK) + if err != nil { + return err + } + + activeSet, err := fullApi.StateMinerActiveSectors(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + activeSectorsInfo := make(map[abi.SectorNumber]*miner.SectorOnChainInfo, len(activeSet)) + for _, info := range activeSet { + activeSectorsInfo[info.SectorNumber] = info + } + + mact, err := fullApi.StateGetActor(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(fullApi), blockstore.NewMemory()) + mas, err := miner.Load(adt.WrapStore(ctx, cbor.NewCborStore(tbs)), mact) + if err != nil { + return err + } + + activeSectorsLocation := make(map[abi.SectorNumber]*miner.SectorLocation, len(activeSet)) + + if err := mas.ForEachDeadline(func(dlIdx uint64, dl miner.Deadline) error { + return dl.ForEachPartition(func(partIdx uint64, part miner.Partition) error { + pas, err := part.ActiveSectors() + if err != nil { + return err + } + + return pas.ForEach(func(i uint64) error { + activeSectorsLocation[abi.SectorNumber(i)] = &miner.SectorLocation{ + Deadline: dlIdx, + Partition: partIdx, + } + return nil + }) + }) + }); err != nil { + return err + } + + excludeSet := make(map[uint64]struct{}) + + if cctx.IsSet("exclude") { + excludeSectors, err := getSectorsFromFile(cctx.String("exclude")) + if err != nil { + return err + } + + for _, id := range excludeSectors { + excludeSet[id] = struct{}{} + } + } + + var sis []*miner.SectorOnChainInfo + + if cctx.IsSet("sector-file") { + sectors, err := getSectorsFromFile(cctx.String("sector-file")) + if err != nil { + return err + } + + for _, id := range sectors { + if _, exclude := excludeSet[id]; exclude { + continue + } + + si, found := activeSectorsInfo[abi.SectorNumber(id)] + if !found { + return xerrors.Errorf("sector %d is not active", id) + } + + sis = append(sis, si) + } + } else { + from := currEpoch + 120 + to := currEpoch + 92160 + + if cctx.IsSet("from") { + from = abi.ChainEpoch(cctx.Int64("from")) + } + + if cctx.IsSet("to") { + to = abi.ChainEpoch(cctx.Int64("to")) + } + + for _, si := range activeSet { + if si.Expiration >= from && si.Expiration <= to { + if _, exclude := excludeSet[uint64(si.SectorNumber)]; !exclude { + sis = append(sis, si) + } + } + } + } + + extensions := map[miner.SectorLocation]map[abi.ChainEpoch][]uint64{} + + withinTolerance := func(a, b abi.ChainEpoch) bool { + diff := a - b + if diff < 0 { + diff = -diff + } + + return diff <= abi.ChainEpoch(cctx.Int64("tolerance")) + } + + for _, si := range sis { + extension := abi.ChainEpoch(cctx.Int64("extension")) + newExp := si.Expiration + extension + + if cctx.IsSet("new-expiration") { + newExp = abi.ChainEpoch(cctx.Int64("new-expiration")) + } + + maxExtendNow := currEpoch + policy.GetMaxSectorExpirationExtension() + if newExp > maxExtendNow { + newExp = maxExtendNow + } + + maxExp := si.Activation + policy.GetSectorMaxLifetime(si.SealProof, nv) + if newExp > maxExp { + newExp = maxExp + } + + if newExp <= si.Expiration || withinTolerance(newExp, si.Expiration) { + continue + } + + l, found := activeSectorsLocation[si.SectorNumber] + if !found { + return xerrors.Errorf("location for sector %d not found", si.SectorNumber) + } + + es, found := extensions[*l] + if !found { + ne := make(map[abi.ChainEpoch][]uint64) + ne[newExp] = []uint64{uint64(si.SectorNumber)} + extensions[*l] = ne + } else { + added := false + for exp := range es { + if withinTolerance(newExp, exp) { + es[exp] = append(es[exp], uint64(si.SectorNumber)) + added = true + break + } + } + + if !added { + es[newExp] = []uint64{uint64(si.SectorNumber)} + } + } + } + + var params []miner5.ExtendSectorExpirationParams + + p := miner5.ExtendSectorExpirationParams{} + scount := 0 + + for l, exts := range extensions { + for newExp, numbers := range exts { + scount += len(numbers) + addrSectors, err := policy.GetAddressedSectorsMax(nv) + if err != nil { + return err + } + declMax, err := policy.GetDeclarationsMax(nv) + if err != nil { + return err + } + if scount > addrSectors || len(p.Extensions) == declMax { + params = append(params, p) + p = miner5.ExtendSectorExpirationParams{} + scount = len(numbers) + } + + p.Extensions = append(p.Extensions, miner5.ExpirationExtension{ + Deadline: l.Deadline, + Partition: l.Partition, + Sectors: bitfield.NewFromSet(numbers), + NewExpiration: newExp, + }) + } + } + + // if we have any sectors, then one last append is needed here + if scount != 0 { + params = append(params, p) + } + + if len(params) == 0 { + fmt.Println("nothing to extend") + return nil + } + + mi, err := fullApi.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting miner info: %w", err) + } + + stotal := 0 + + for i := range params { + scount := 0 + for _, ext := range params[i].Extensions { + count, err := ext.Sectors.Count() + if err != nil { + return err + } + scount += int(count) + } + fmt.Printf("Renewing %d sectors: ", scount) + stotal += scount + + if !cctx.Bool("really-do-it") { + pp, err := NewPseudoExtendParams(¶ms[i]) + if err != nil { + return err + } + + data, err := json.MarshalIndent(pp, "", " ") + if err != nil { + return err + } + + fmt.Println() + fmt.Println(string(data)) + continue + } + + sp, aerr := actors.SerializeParams(¶ms[i]) + if aerr != nil { + return xerrors.Errorf("serializing params: %w", err) + } + + smsg, err := fullApi.MpoolPushMessage(ctx, &types.Message{ + From: mi.Worker, + To: maddr, + Method: miner.Methods.ExtendSectorExpiration, + Value: big.Zero(), + Params: sp, + }, spec) + if err != nil { + return xerrors.Errorf("mpool push message: %w", err) + } + + fmt.Println(smsg.Cid()) + } + + fmt.Printf("%d sectors renewed\n", stotal) + + return nil + }, +} + var sectorsExtendCmd = &cli.Command{ Name: "extend", Usage: "Extend sector expiration", @@ -467,7 +987,7 @@ var sectorsExtendCmd = &cli.Command{ return err } - var params []miner3.ExtendSectorExpirationParams + var params []miner5.ExtendSectorExpirationParams if cctx.Bool("v1-sectors") { @@ -520,7 +1040,7 @@ var sectorsExtendCmd = &cli.Command{ } // Set the new expiration to 48 hours less than the theoretical maximum lifetime - newExp := ml - (miner3.WPoStProvingPeriod * 2) + si.Activation + newExp := ml - (miner5.WPoStProvingPeriod * 2) + si.Activation if withinTolerance(si.Expiration, newExp) || si.Expiration >= newExp { continue } @@ -555,7 +1075,7 @@ var sectorsExtendCmd = &cli.Command{ } } - p := miner3.ExtendSectorExpirationParams{} + p := miner5.ExtendSectorExpirationParams{} scount := 0 for l, exts := range extensions { @@ -571,11 +1091,11 @@ var sectorsExtendCmd = &cli.Command{ } if scount > addressedMax || len(p.Extensions) == declMax { params = append(params, p) - p = miner3.ExtendSectorExpirationParams{} + p = miner5.ExtendSectorExpirationParams{} scount = len(numbers) } - p.Extensions = append(p.Extensions, miner3.ExpirationExtension{ + p.Extensions = append(p.Extensions, miner5.ExpirationExtension{ Deadline: l.Deadline, Partition: l.Partition, Sectors: bitfield.NewFromSet(numbers), @@ -613,11 +1133,11 @@ var sectorsExtendCmd = &cli.Command{ sectors[*p] = append(sectors[*p], id) } - p := miner3.ExtendSectorExpirationParams{} + p := miner5.ExtendSectorExpirationParams{} for l, numbers := range sectors { // TODO: Dedup with above loop - p.Extensions = append(p.Extensions, miner3.ExpirationExtension{ + p.Extensions = append(p.Extensions, miner5.ExpirationExtension{ Deadline: l.Deadline, Partition: l.Partition, Sectors: bitfield.NewFromSet(numbers), diff --git a/cmd/lotus-shed/balances.go b/cmd/lotus-shed/balances.go index 87530c666..3a158483f 100644 --- a/cmd/lotus-shed/balances.go +++ b/cmd/lotus-shed/balances.go @@ -510,13 +510,13 @@ var chainBalanceStateCmd = &cli.Command{ return err } - cs := store.NewChainStore(bs, bs, mds, vm.Syscalls(ffiwrapper.ProofVerifier), nil) + cs := store.NewChainStore(bs, bs, mds, nil) defer cs.Close() //nolint:errcheck cst := cbor.NewCborStore(bs) store := adt.WrapStore(ctx, cst) - sm := stmgr.NewStateManager(cs) + sm := stmgr.NewStateManager(cs, vm.Syscalls(ffiwrapper.ProofVerifier)) tree, err := state.LoadStateTree(cst, sroot) if err != nil { @@ -731,13 +731,13 @@ var chainPledgeCmd = &cli.Command{ return err } - cs := store.NewChainStore(bs, bs, mds, vm.Syscalls(ffiwrapper.ProofVerifier), nil) + cs := store.NewChainStore(bs, bs, mds, nil) defer cs.Close() //nolint:errcheck cst := cbor.NewCborStore(bs) store := adt.WrapStore(ctx, cst) - sm := stmgr.NewStateManager(cs) + sm := stmgr.NewStateManager(cs, vm.Syscalls(ffiwrapper.ProofVerifier)) state, err := state.LoadStateTree(cst, sroot) if err != nil { diff --git a/cmd/lotus-shed/cid.go b/cmd/lotus-shed/cid.go index d3bd2c3c9..09f3d6e6f 100644 --- a/cmd/lotus-shed/cid.go +++ b/cmd/lotus-shed/cid.go @@ -26,9 +26,10 @@ var cidIdCmd = &cli.Command{ ArgsUsage: "[data]", Flags: []cli.Flag{ &cli.StringFlag{ - Name: "encoding", - Value: "base64", - Usage: "specify input encoding to parse", + Name: "encoding", + Aliases: []string{"e"}, + Value: "base64", + Usage: "specify input encoding to parse", }, &cli.StringFlag{ Name: "codec", @@ -49,12 +50,14 @@ var cidIdCmd = &cli.Command{ return xerrors.Errorf("decoding base64 value: %w", err) } dec = data - case "hex": + case "hex", "x": data, err := hex.DecodeString(cctx.Args().First()) if err != nil { return xerrors.Errorf("decoding hex value: %w", err) } dec = data + case "raw", "r": + dec = []byte(cctx.Args().First()) default: return xerrors.Errorf("unrecognized encoding: %s", cctx.String("encoding")) } diff --git a/cmd/lotus-shed/export.go b/cmd/lotus-shed/export.go index e711ba2bb..dc5cc3bd2 100644 --- a/cmd/lotus-shed/export.go +++ b/cmd/lotus-shed/export.go @@ -90,7 +90,7 @@ var exportChainCmd = &cli.Command{ return err } - cs := store.NewChainStore(bs, bs, mds, nil, nil) + cs := store.NewChainStore(bs, bs, mds, nil) defer cs.Close() //nolint:errcheck if err := cs.Load(); err != nil { diff --git a/cmd/lotus-shed/genesis-verify.go b/cmd/lotus-shed/genesis-verify.go index 32e4e14ad..0b61b680b 100644 --- a/cmd/lotus-shed/genesis-verify.go +++ b/cmd/lotus-shed/genesis-verify.go @@ -6,6 +6,8 @@ import ( "os" "sort" + _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/fatih/color" @@ -52,7 +54,7 @@ var genesisVerifyCmd = &cli.Command{ } bs := blockstore.FromDatastore(datastore.NewMapDatastore()) - cs := store.NewChainStore(bs, bs, datastore.NewMapDatastore(), nil, nil) + cs := store.NewChainStore(bs, bs, datastore.NewMapDatastore(), nil) defer cs.Close() //nolint:errcheck cf := cctx.Args().Get(0) @@ -66,9 +68,7 @@ var genesisVerifyCmd = &cli.Command{ return err } - sm := stmgr.NewStateManager(cs) - - total, err := stmgr.CheckTotalFIL(context.TODO(), sm, ts) + total, err := stmgr.CheckTotalFIL(context.TODO(), cs, ts) if err != nil { return err } @@ -173,6 +173,24 @@ var genesisVerifyCmd = &cli.Command{ } fmt.Printf("]\n") } + + act, err := stree.GetActor(_init.Address) + if err != nil { + return err + } + + ias, err := _init.Load(store, act) + if err != nil { + return err + } + + nn, err := ias.NetworkName() + if err != nil { + return err + } + + fmt.Println("Network name: ", nn) + return nil }, } diff --git a/cmd/lotus-shed/miner-types.go b/cmd/lotus-shed/miner-types.go index 19a30c4b9..491a77aa0 100644 --- a/cmd/lotus-shed/miner-types.go +++ b/cmd/lotus-shed/miner-types.go @@ -15,8 +15,6 @@ import ( "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/chain/vm" - "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" "github.com/filecoin-project/lotus/node/repo" builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" "github.com/filecoin-project/specs-actors/v4/actors/util/adt" @@ -76,7 +74,7 @@ var minerTypesCmd = &cli.Command{ return err } - cs := store.NewChainStore(bs, bs, mds, vm.Syscalls(ffiwrapper.ProofVerifier), nil) + cs := store.NewChainStore(bs, bs, mds, nil) defer cs.Close() //nolint:errcheck cst := cbor.NewCborStore(bs) diff --git a/cmd/lotus-shed/pruning.go b/cmd/lotus-shed/pruning.go index 188f5b28f..68488862a 100644 --- a/cmd/lotus-shed/pruning.go +++ b/cmd/lotus-shed/pruning.go @@ -13,8 +13,6 @@ import ( badgerbs "github.com/filecoin-project/lotus/blockstore/badger" "github.com/filecoin-project/lotus/chain/store" - "github.com/filecoin-project/lotus/chain/vm" - "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" "github.com/filecoin-project/lotus/node/repo" ) @@ -169,7 +167,7 @@ var stateTreePruneCmd = &cli.Command{ return nil } - cs := store.NewChainStore(bs, bs, mds, vm.Syscalls(ffiwrapper.ProofVerifier), nil) + cs := store.NewChainStore(bs, bs, mds, nil) defer cs.Close() //nolint:errcheck if err := cs.Load(); err != nil { diff --git a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go index bd757614b..ebcaae2b6 100644 --- a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go +++ b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go @@ -83,7 +83,7 @@ func NewBlockBuilder(ctx context.Context, logger *zap.SugaredLogger, sm *stmgr.S Epoch: parentTs.Height() + 1, Rand: r, Bstore: sm.ChainStore().StateBlockstore(), - Syscalls: sm.ChainStore().VMSys(), + Syscalls: sm.VMSys(), CircSupplyCalc: sm.GetVMCirculatingSupply, NtwkVersion: sm.GetNtwkVersion, BaseFee: abi.NewTokenAmount(0), diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index 5b8bf2bf9..c2a497bcb 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -61,7 +61,7 @@ func NewNode(ctx context.Context, r repo.Repo) (nd *Node, _err error) { } return &Node{ repo: lr, - Chainstore: store.NewChainStore(bs, bs, ds, vm.Syscalls(mock.Verifier), nil), + Chainstore: store.NewChainStore(bs, bs, ds, nil), MetadataDS: ds, Blockstore: bs, }, err @@ -105,7 +105,7 @@ func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) { if err != nil { return nil, xerrors.Errorf("failed to create upgrade schedule for simulation %s: %w", name, err) } - sim.StateManager, err = stmgr.NewStateManagerWithUpgradeSchedule(nd.Chainstore, us) + sim.StateManager, err = stmgr.NewStateManagerWithUpgradeSchedule(nd.Chainstore, vm.Syscalls(mock.Verifier), us) if err != nil { return nil, xerrors.Errorf("failed to create state manager for simulation %s: %w", name, err) } @@ -127,7 +127,7 @@ func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) sim := &Simulation{ name: name, Node: nd, - StateManager: stmgr.NewStateManager(nd.Chainstore), + StateManager: stmgr.NewStateManager(nd.Chainstore, vm.Syscalls(mock.Verifier)), stages: stages, } if has, err := nd.MetadataDS.Has(sim.key("head")); err != nil { diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index d91d30eda..83b45f942 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -18,6 +18,8 @@ import ( "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock" "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/stages" ) @@ -198,7 +200,7 @@ func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch if err != nil { return err } - sm, err := stmgr.NewStateManagerWithUpgradeSchedule(sim.Node.Chainstore, newUpgradeSchedule) + sm, err := stmgr.NewStateManagerWithUpgradeSchedule(sim.Node.Chainstore, vm.Syscalls(mock.Verifier), newUpgradeSchedule) if err != nil { return err } diff --git a/cmd/lotus-townhall/main.go b/cmd/lotus-townhall/main.go deleted file mode 100644 index 1e0460dee..000000000 --- a/cmd/lotus-townhall/main.go +++ /dev/null @@ -1,134 +0,0 @@ -package main - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - rice "github.com/GeertJohan/go.rice" - "github.com/gorilla/websocket" - "github.com/ipld/go-car" - "github.com/libp2p/go-libp2p" - "github.com/libp2p/go-libp2p-core/peer" - pubsub "github.com/libp2p/go-libp2p-pubsub" - - "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/build" -) - -var topic = "/fil/headnotifs/" - -func init() { - genBytes := build.MaybeGenesis() - if len(genBytes) == 0 { - topic = "" - return - } - - bs := blockstore.NewMemory() - - c, err := car.LoadCar(bs, bytes.NewReader(genBytes)) - if err != nil { - panic(err) - } - if len(c.Roots) != 1 { - panic("expected genesis file to have one root") - } - - fmt.Printf("Genesis CID: %s\n", c.Roots[0]) - topic = topic + c.Roots[0].String() -} - -var upgrader = websocket.Upgrader{ - WriteBufferSize: 1024, - CheckOrigin: func(r *http.Request) bool { - return true - }, -} - -func main() { - if topic == "" { - fmt.Println("FATAL: No genesis found") - return - } - - ctx := context.Background() - - host, err := libp2p.New( - ctx, - libp2p.Defaults, - ) - if err != nil { - panic(err) - } - ps, err := pubsub.NewGossipSub(ctx, host) - if err != nil { - panic(err) - } - - pi, err := build.BuiltinBootstrap() - if err != nil { - panic(err) - } - - if err := host.Connect(ctx, pi[0]); err != nil { - panic(err) - } - - http.HandleFunc("/sub", handler(ps)) - http.Handle("/", http.FileServer(rice.MustFindBox("townhall/build").HTTPBox())) - - fmt.Println("listening on http://localhost:2975") - - if err := http.ListenAndServe("0.0.0.0:2975", nil); err != nil { - panic(err) - } -} - -type update struct { - From peer.ID - Update json.RawMessage - Time uint64 -} - -func handler(ps *pubsub.PubSub) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", "*") - if r.Header.Get("Sec-WebSocket-Protocol") != "" { - w.Header().Set("Sec-WebSocket-Protocol", r.Header.Get("Sec-WebSocket-Protocol")) - } - - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - return - } - - sub, err := ps.Subscribe(topic) //nolint - if err != nil { - return - } - defer sub.Cancel() //nolint:errcheck - - fmt.Println("new conn") - - for { - msg, err := sub.Next(r.Context()) - if err != nil { - return - } - - //fmt.Println(msg) - - if err := conn.WriteJSON(update{ - From: peer.ID(msg.From), - Update: msg.Data, - Time: uint64(time.Now().UnixNano() / 1000_000), - }); err != nil { - return - } - } - } -} diff --git a/cmd/lotus-townhall/townhall/.gitignore b/cmd/lotus-townhall/townhall/.gitignore deleted file mode 100644 index 4d29575de..000000000 --- a/cmd/lotus-townhall/townhall/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/cmd/lotus-townhall/townhall/package.json b/cmd/lotus-townhall/townhall/package.json deleted file mode 100644 index 5a8167622..000000000 --- a/cmd/lotus-townhall/townhall/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "townhall", - "version": "0.1.0", - "private": true, - "dependencies": { - "react": "^16.10.2", - "react-dom": "^16.10.2", - "react-scripts": "3.2.0" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": "react-app" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - } -} diff --git a/cmd/lotus-townhall/townhall/public/index.html b/cmd/lotus-townhall/townhall/public/index.html deleted file mode 100644 index 38af10597..000000000 --- a/cmd/lotus-townhall/townhall/public/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Lotus TownHall - - - -
- - diff --git a/cmd/lotus-townhall/townhall/public/robots.txt b/cmd/lotus-townhall/townhall/public/robots.txt deleted file mode 100644 index 01b0f9a10..000000000 --- a/cmd/lotus-townhall/townhall/public/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * diff --git a/cmd/lotus-townhall/townhall/src/App.css b/cmd/lotus-townhall/townhall/src/App.css deleted file mode 100644 index 8b1378917..000000000 --- a/cmd/lotus-townhall/townhall/src/App.css +++ /dev/null @@ -1 +0,0 @@ - diff --git a/cmd/lotus-townhall/townhall/src/App.js b/cmd/lotus-townhall/townhall/src/App.js deleted file mode 100644 index 2f216f5da..000000000 --- a/cmd/lotus-townhall/townhall/src/App.js +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react'; -import './App.css'; - -function colForH(besth, height) { - const diff = besth - height - if(diff === 0) return '#6f6' - if(diff === 1) return '#df4' - if(diff < 4) return '#ff0' - if(diff < 10) return '#f60' - return '#f00' -} - -function colLag(lag) { - if(lag < 100) return '#6f6' - if(lag < 400) return '#df4' - if(lag < 1000) return '#ff0' - if(lag < 4000) return '#f60' - return '#f00' -} - -function lagCol(lag, good) { - return - {lag} - ms - -} - -class App extends React.Component { - constructor(props) { - super(props); - - let ws = new WebSocket("ws://" + window.location.host + "/sub") - //let ws = new WebSocket("ws://127.0.0.1:2975/sub") - - ws.onmessage = (ev) => { - console.log(ev) - let update = JSON.parse(ev.data) - - update.Update.Weight = Number(update.Update.Weight) - - let wdiff = update.Update.Weight - (this.state[update.From] || {Weight: update.Update.Weight}).Weight - wdiff = {wdiff} - - let utDiff = update.Time - (this.state[update.From] || {utime: update.Time}).utime - utDiff = {utDiff}ms - - this.setState( prev => ({ - ...prev, [update.From]: {...update.Update, utime: update.Time, wdiff: wdiff, utDiff: utDiff}, - })) - } - - ws.onclose = () => { - this.setState({disconnected: true}) - } - - this.state = {} - } - - render() { - if(this.state.disconnected) { - return Error: disconnected - } - - let besth = Object.keys(this.state).map(k => this.state[k]).reduce((p, n) => p > n.Height ? p : n.Height, -1) - let bestw = Object.keys(this.state).map(k => this.state[k]).reduce((p, n) => p > n.Weight ? p : n.Weight, -1) - - return - - {Object.keys(this.state).map(k => [k, this.state[k]]).map(([k, v]) => { - let mnrs = v.Blocks.map(b => ) - let l = [ - , - , - , - , - , - ...mnrs, - ] - - l = {l} - return l - }) - } -
PeerIDNicknameLagWeight(best, prev)HeightBlocks
 m:{b.Miner}({lagCol(v.Time ? v.Time - (b.Timestamp*1000) : v.utime - (b.Timestamp*1000), v.Time)}){k}{v.NodeName}{v.Time ? lagCol(v.utime - v.Time, true) : ""}(Δ{v.utDiff}){v.Weight}({bestw - v.Weight}, {v.wdiff}){v.Height}({besth - v.Height})
- } -} -export default App; diff --git a/cmd/lotus-townhall/townhall/src/App.test.js b/cmd/lotus-townhall/townhall/src/App.test.js deleted file mode 100644 index a754b201b..000000000 --- a/cmd/lotus-townhall/townhall/src/App.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './App'; - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/cmd/lotus-townhall/townhall/src/index.css b/cmd/lotus-townhall/townhall/src/index.css deleted file mode 100644 index fb0d9d10e..000000000 --- a/cmd/lotus-townhall/townhall/src/index.css +++ /dev/null @@ -1,6 +0,0 @@ -body { - margin: 0; - font-family: monospace; - background: #1f1f1f; - color: #f0f0f0; -} diff --git a/cmd/lotus-townhall/townhall/src/index.js b/cmd/lotus-townhall/townhall/src/index.js deleted file mode 100644 index 395b74997..000000000 --- a/cmd/lotus-townhall/townhall/src/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; -import App from './App'; - -ReactDOM.render(, document.getElementById('root')); diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 0d5961aae..486ac8ed7 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -481,7 +481,7 @@ func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool) return xerrors.Errorf("failed to open journal: %w", err) } - cst := store.NewChainStore(bs, bs, mds, vm.Syscalls(ffiwrapper.ProofVerifier), j) + cst := store.NewChainStore(bs, bs, mds, j) defer cst.Close() //nolint:errcheck log.Infof("importing chain from %s...", fname) @@ -517,7 +517,7 @@ func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool) return err } - stm := stmgr.NewStateManager(cst) + stm := stmgr.NewStateManager(cst, vm.Syscalls(ffiwrapper.ProofVerifier)) if !snapshot { log.Infof("validating imported chain...") diff --git a/conformance/chaos/cbor_gen.go b/conformance/chaos/cbor_gen.go index 5bf85606f..d0d1ad627 100644 --- a/conformance/chaos/cbor_gen.go +++ b/conformance/chaos/cbor_gen.go @@ -5,6 +5,7 @@ package chaos import ( "fmt" "io" + "math" "sort" address "github.com/filecoin-project/go-address" @@ -17,6 +18,7 @@ import ( var _ = xerrors.Errorf var _ = cid.Undef +var _ = math.E var _ = sort.Sort var lengthBufState = []byte{130} diff --git a/conformance/driver.go b/conformance/driver.go index c7fc0d6c4..0b3d42644 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -101,8 +101,8 @@ func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, params tipset = params.Tipset syscalls = vm.Syscalls(ffiwrapper.ProofVerifier) - cs = store.NewChainStore(bs, bs, ds, syscalls, nil) - sm = stmgr.NewStateManager(cs) + cs = store.NewChainStore(bs, bs, ds, nil) + sm = stmgr.NewStateManager(cs, syscalls) ) if params.Rand == nil { @@ -196,7 +196,7 @@ func (d *Driver) ExecuteMessage(bs blockstore.Blockstore, params ExecuteMessageP // dummy state manager; only to reference the GetNetworkVersion method, // which does not depend on state. - sm := stmgr.NewStateManager(nil) + sm := stmgr.NewStateManager(nil, nil) vmOpts := &vm.VMOpts{ StateBase: params.Preroot, diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index 86cf62bbc..b02ac6c0d 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -18,6 +18,12 @@ * [ComputeProof](#ComputeProof) * [Create](#Create) * [CreateBackup](#CreateBackup) +* [Dagstore](#Dagstore) + * [DagstoreGC](#DagstoreGC) + * [DagstoreInitializeAll](#DagstoreInitializeAll) + * [DagstoreInitializeShard](#DagstoreInitializeShard) + * [DagstoreListShards](#DagstoreListShards) + * [DagstoreRecoverShard](#DagstoreRecoverShard) * [Deals](#Deals) * [DealsConsiderOfflineRetrievalDeals](#DealsConsiderOfflineRetrievalDeals) * [DealsConsiderOfflineStorageDeals](#DealsConsiderOfflineStorageDeals) @@ -345,6 +351,114 @@ Inputs: Response: `{}` +## Dagstore + + +### DagstoreGC +DagstoreGC runs garbage collection on the DAG store. + + +Perms: admin + +Inputs: `null` + +Response: `null` + +### DagstoreInitializeAll +DagstoreInitializeAll initializes all uninitialized shards in bulk, +according to the policy passed in the parameters. + +It is recommended to set a maximum concurrency to avoid extreme +IO pressure if the storage subsystem has a large amount of deals. + +It returns a stream of events to report progress. + + +Perms: write + +Inputs: +```json +[ + { + "MaxConcurrency": 123, + "IncludeSealed": true + } +] +``` + +Response: +```json +{ + "Key": "string value", + "Event": "string value", + "Success": true, + "Error": "string value", + "Total": 123, + "Current": 123 +} +``` + +### DagstoreInitializeShard +DagstoreInitializeShard initializes an uninitialized shard. + +Initialization consists of fetching the shard's data (deal payload) from +the storage subsystem, generating an index, and persisting the index +to facilitate later retrievals, and/or to publish to external sources. + +This operation is intended to complement the initial migration. The +migration registers a shard for every unique piece CID, with lazy +initialization. Thus, shards are not initialized immediately to avoid +IO activity competing with proving. Instead, shard are initialized +when first accessed. This method forces the initialization of a shard by +accessing it and immediately releasing it. This is useful to warm up the +cache to facilitate subsequent retrievals, and to generate the indexes +to publish them externally. + +This operation fails if the shard is not in ShardStateNew state. +It blocks until initialization finishes. + + +Perms: write + +Inputs: +```json +[ + "string value" +] +``` + +Response: `{}` + +### DagstoreListShards +DagstoreListShards returns information about all shards known to the +DAG store. Only available on nodes running the markets subsystem. + + +Perms: read + +Inputs: `null` + +Response: `null` + +### DagstoreRecoverShard +DagstoreRecoverShard attempts to recover a failed shard. + +This operation fails if the shard is not in ShardStateErrored state. +It blocks until recovery finishes. If recovery failed, it returns the +error. + + +Perms: write + +Inputs: +```json +[ + "string value" +] +``` + +Response: `{}` + ## Deals @@ -690,7 +804,6 @@ Response: "SlashEpoch": 10101, "FastRetrieval": true, "Message": "string value", - "StoreID": 12, "FundsReserved": "0", "Ref": { "TransferType": "string value", @@ -709,7 +822,8 @@ Response: "Responder": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", "ID": 3 }, - "SectorNumber": 9 + "SectorNumber": 9, + "InboundCAR": "string value" } ``` diff --git a/documentation/en/api-v0-methods.md b/documentation/en/api-v0-methods.md index 4466cde8c..6f030e979 100644 --- a/documentation/en/api-v0-methods.md +++ b/documentation/en/api-v0-methods.md @@ -1467,7 +1467,7 @@ Inputs: }, "Piece": null, "Size": 42, - "LocalStore": 12, + "FromLocalCAR": "string value", "Total": "0", "UnsealPrice": "0", "PaymentInterval": 42, @@ -1521,7 +1521,7 @@ Inputs: }, "Piece": null, "Size": 42, - "LocalStore": 12, + "FromLocalCAR": "string value", "Total": "0", "UnsealPrice": "0", "PaymentInterval": 42, diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index 4d7c4dc3c..72a9becba 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -27,6 +27,7 @@ * [ChainGetRandomnessFromBeacon](#ChainGetRandomnessFromBeacon) * [ChainGetRandomnessFromTickets](#ChainGetRandomnessFromTickets) * [ChainGetTipSet](#ChainGetTipSet) + * [ChainGetTipSetAfterHeight](#ChainGetTipSetAfterHeight) * [ChainGetTipSetByHeight](#ChainGetTipSetByHeight) * [ChainHasObj](#ChainHasObj) * [ChainHead](#ChainHead) @@ -163,6 +164,7 @@ * [StateCompute](#StateCompute) * [StateDealProviderCollateralBounds](#StateDealProviderCollateralBounds) * [StateDecodeParams](#StateDecodeParams) + * [StateEncodeParams](#StateEncodeParams) * [StateGetActor](#StateGetActor) * [StateListActors](#StateListActors) * [StateListMessages](#StateListMessages) @@ -766,6 +768,38 @@ Response: } ``` +### ChainGetTipSetAfterHeight +ChainGetTipSetAfterHeight looks back for a tipset at the specified epoch. +If there are no blocks at the specified epoch, the first non-nil tipset at a later epoch +will be returned. + + +Perms: read + +Inputs: +```json +[ + 10101, + [ + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + { + "/": "bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve" + } + ] +] +``` + +Response: +```json +{ + "Cids": null, + "Blocks": null, + "Height": 0 +} +``` + ### ChainGetTipSetByHeight ChainGetTipSetByHeight looks back for a tipset at the specified epoch. If there are no blocks at the specified epoch, a tipset at an earlier epoch @@ -1497,7 +1531,7 @@ Inputs: }, "Piece": null, "Size": 42, - "LocalStore": 12, + "FromLocalCAR": "string value", "Total": "0", "UnsealPrice": "0", "PaymentInterval": 42, @@ -1551,7 +1585,7 @@ Inputs: }, "Piece": null, "Size": 42, - "LocalStore": 12, + "FromLocalCAR": "string value", "Total": "0", "UnsealPrice": "0", "PaymentInterval": 42, @@ -4151,6 +4185,25 @@ Inputs: Response: `{}` +### StateEncodeParams +StateEncodeParams attempts to encode the provided json params to the binary from + + +Perms: read + +Inputs: +```json +[ + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + 1, + null +] +``` + +Response: `"Ynl0ZSBhcnJheQ=="` + ### StateGetActor StateGetActor returns the indicated actor's nonce and balance. diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index a292f6926..94ee85e95 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -7,7 +7,7 @@ USAGE: lotus-miner [global options] command [command options] [arguments...] VERSION: - 1.11.1 + 1.11.2 COMMANDS: init Initialize a lotus miner repo @@ -29,6 +29,7 @@ COMMANDS: storage-deals Manage storage deals and related configuration retrieval-deals Manage retrieval deals and related configuration data-transfers Manage data transfers + dagstore Manage the dagstore on the markets subsystem NETWORK: net Manage P2P Network RETRIEVAL: @@ -1000,6 +1001,100 @@ OPTIONS: ``` +## lotus-miner dagstore +``` +NAME: + lotus-miner dagstore - Manage the dagstore on the markets subsystem + +USAGE: + lotus-miner dagstore command [command options] [arguments...] + +COMMANDS: + list-shards List all shards known to the dagstore, with their current status + initialize-shard Initialize the specified shard + recover-shard Attempt to recover a shard in errored state + initialize-all Initialize all uninitialized shards, streaming results as they're produced; only shards for unsealed pieces are initialized by default + gc Garbage collect the dagstore + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus-miner dagstore list-shards +``` +NAME: + lotus-miner dagstore list-shards - List all shards known to the dagstore, with their current status + +USAGE: + lotus-miner dagstore list-shards [command options] [arguments...] + +OPTIONS: + --color use color in display output (default: depends on output being a TTY) + --help, -h show help (default: false) + +``` + +### lotus-miner dagstore initialize-shard +``` +NAME: + lotus-miner dagstore initialize-shard - Initialize the specified shard + +USAGE: + lotus-miner dagstore initialize-shard [command options] [key] + +OPTIONS: + --color use color in display output (default: depends on output being a TTY) + --help, -h show help (default: false) + +``` + +### lotus-miner dagstore recover-shard +``` +NAME: + lotus-miner dagstore recover-shard - Attempt to recover a shard in errored state + +USAGE: + lotus-miner dagstore recover-shard [command options] [key] + +OPTIONS: + --color use color in display output (default: depends on output being a TTY) + --help, -h show help (default: false) + +``` + +### lotus-miner dagstore initialize-all +``` +NAME: + lotus-miner dagstore initialize-all - Initialize all uninitialized shards, streaming results as they're produced; only shards for unsealed pieces are initialized by default + +USAGE: + lotus-miner dagstore initialize-all [command options] [arguments...] + +OPTIONS: + --concurrency value maximum shards to initialize concurrently at a time; use 0 for unlimited (default: 0) + --include-sealed initialize sealed pieces as well (default: false) + --color use color in display output (default: depends on output being a TTY) + --help, -h show help (default: false) + +``` + +### lotus-miner dagstore gc +``` +NAME: + lotus-miner dagstore gc - Garbage collect the dagstore + +USAGE: + lotus-miner dagstore gc [command options] [arguments...] + +OPTIONS: + --color use color in display output (default: depends on output being a TTY) + --help, -h show help (default: false) + +``` + ## lotus-miner net ``` NAME: @@ -1376,6 +1471,8 @@ COMMANDS: refs List References to sectors update-state ADVANCED: manually update the state of a sector, this may aid in error recovery pledge store random data in a sector + check-expire Inspect expiring sectors + renew Renew expiring sectors while not exceeding each sector's max life extend Extend sector expiration terminate Terminate sector on-chain then remove (WARNING: This means losing power and collateral for the removed sector) remove Forcefully remove a sector (WARNING: This means losing power and collateral for the removed sector (use 'terminate' for lower penalty)) @@ -1466,6 +1563,42 @@ OPTIONS: ``` +### lotus-miner sectors check-expire +``` +NAME: + lotus-miner sectors check-expire - Inspect expiring sectors + +USAGE: + lotus-miner sectors check-expire [command options] [arguments...] + +OPTIONS: + --cutoff value skip sectors whose current expiration is more than epochs from now, defaults to 60 days (default: 172800) + --help, -h show help (default: false) + +``` + +### lotus-miner sectors renew +``` +NAME: + lotus-miner sectors renew - Renew expiring sectors while not exceeding each sector's max life + +USAGE: + lotus-miner sectors renew [command options] [arguments...] + +OPTIONS: + --from value only consider sectors whose current expiration epoch is in the range of [from, to], defaults to: now + 120 (1 hour) (default: 0) + --to value only consider sectors whose current expiration epoch is in the range of [from, to], defaults to: now + 92160 (32 days) (default: 0) + --sector-file value provide a file containing one sector number in each line, ignoring above selecting criteria + --exclude value optionally provide a file containing excluding sectors + --extension value try to extend selected sectors by this number of epochs, defaults to 540 days (default: 1555200) + --new-expiration value try to extend selected sectors to this epoch, ignoring extension (default: 0) + --tolerance value don't try to extend sectors by fewer than this number of epochs, defaults to 7 days (default: 20160) + --max-fee value use up to this amount of FIL for one message. pass this flag to avoid message congestion. (default: "0") + --really-do-it pass this flag to really renew sectors, otherwise will only print out json representation of parameters (default: false) + --help, -h show help (default: false) + +``` + ### lotus-miner sectors extend ``` NAME: diff --git a/documentation/en/cli-lotus-worker.md b/documentation/en/cli-lotus-worker.md index 177c45b92..1837ae68a 100644 --- a/documentation/en/cli-lotus-worker.md +++ b/documentation/en/cli-lotus-worker.md @@ -7,7 +7,7 @@ USAGE: lotus-worker [global options] command [command options] [arguments...] VERSION: - 1.11.1 + 1.11.2 COMMANDS: run Start lotus worker diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index a960299ff..a4c2cc59d 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -7,7 +7,7 @@ USAGE: lotus [global options] command [command options] [arguments...] VERSION: - 1.11.1 + 1.11.2 COMMANDS: daemon Start a lotus daemon process @@ -1531,7 +1531,7 @@ OPTIONS: --gas-premium value gas price for new message (pay to miner, attoFIL/GasUnit) --gas-limit value gas limit for new message (GasUnit) (default: 0) --auto automatically reprice the specified message (default: false) - --max-fee value Spend up to X attoFIL for this message (applicable for auto mode) + --fee-limit max-fee Spend up to X FIL for this message in units of FIL. Previously when flag was max-fee units were in attoFIL. Applicable for auto mode --help, -h show help (default: false) ``` @@ -2297,11 +2297,12 @@ NAME: lotus chain encode params - Encodes the given JSON params USAGE: - lotus chain encode params [command options] [toAddr method params] + lotus chain encode params [command options] [dest method params] OPTIONS: --tipset value --encoding value specify input encoding to parse (default: "base64") + --to-code interpret dest as code CID instead of as address (default: false) --help, -h show help (default: false) ``` diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index a7b3c2e69..78366aeb8 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit a7b3c2e695393fd716e9265ff8cba932a3e38dd4 +Subproject commit 78366aeb85796c0687a53107a6cd52da7bd8abd5 diff --git a/extern/sector-storage/cbor_gen.go b/extern/sector-storage/cbor_gen.go index ed06b920e..623d75b33 100644 --- a/extern/sector-storage/cbor_gen.go +++ b/extern/sector-storage/cbor_gen.go @@ -5,6 +5,7 @@ package sectorstorage import ( "fmt" "io" + "math" "sort" sealtasks "github.com/filecoin-project/lotus/extern/sector-storage/sealtasks" @@ -15,6 +16,7 @@ import ( var _ = xerrors.Errorf var _ = cid.Undef +var _ = math.E var _ = sort.Sort func (t *Call) MarshalCBOR(w io.Writer) error { diff --git a/extern/sector-storage/faults.go b/extern/sector-storage/faults.go index fdd5f6b7d..7fdf5c337 100644 --- a/extern/sector-storage/faults.go +++ b/extern/sector-storage/faults.go @@ -63,7 +63,6 @@ func (m *Manager) CheckProvable(ctx context.Context, pp abi.RegisteredPoStProof, toCheck := map[string]int64{ lp.Sealed: 1, - filepath.Join(lp.Cache, "t_aux"): 0, filepath.Join(lp.Cache, "p_aux"): 0, } diff --git a/extern/sector-storage/storiface/cbor_gen.go b/extern/sector-storage/storiface/cbor_gen.go index a8ade7d10..44b5ae9ee 100644 --- a/extern/sector-storage/storiface/cbor_gen.go +++ b/extern/sector-storage/storiface/cbor_gen.go @@ -5,6 +5,7 @@ package storiface import ( "fmt" "io" + "math" "sort" cid "github.com/ipfs/go-cid" @@ -14,6 +15,7 @@ import ( var _ = xerrors.Errorf var _ = cid.Undef +var _ = math.E var _ = sort.Sort func (t *CallID) MarshalCBOR(w io.Writer) error { diff --git a/extern/storage-sealing/cbor_gen.go b/extern/storage-sealing/cbor_gen.go index b71c2863c..1dfaf54a5 100644 --- a/extern/storage-sealing/cbor_gen.go +++ b/extern/storage-sealing/cbor_gen.go @@ -5,6 +5,7 @@ package sealing import ( "fmt" "io" + "math" "sort" abi "github.com/filecoin-project/go-state-types/abi" @@ -17,6 +18,7 @@ import ( var _ = xerrors.Errorf var _ = cid.Undef +var _ = math.E var _ = sort.Sort func (t *Piece) MarshalCBOR(w io.Writer) error { diff --git a/extern/storage-sealing/checks.go b/extern/storage-sealing/checks.go index 5ba23026d..b01f746ba 100644 --- a/extern/storage-sealing/checks.go +++ b/extern/storage-sealing/checks.go @@ -93,27 +93,29 @@ func checkPrecommit(ctx context.Context, maddr address.Address, si SectorInfo, t return &ErrBadCommD{xerrors.Errorf("on chain CommD differs from sector: %s != %s", commD, si.CommD)} } - ticketEarliest := height - policy.MaxPreCommitRandomnessLookback - - if si.TicketEpoch < ticketEarliest { - return &ErrExpiredTicket{xerrors.Errorf("ticket expired: seal height: %d, head: %d", si.TicketEpoch+policy.SealRandomnessLookback, height)} - } - pci, err := api.StateSectorPreCommitInfo(ctx, maddr, si.SectorNumber, tok) if err != nil { if err == ErrSectorAllocated { + //committed P2 message but commit C2 message too late, pci should be null in this case return &ErrSectorNumberAllocated{err} } return &ErrApi{xerrors.Errorf("getting precommit info: %w", err)} } if pci != nil { + // committed P2 message if pci.Info.SealRandEpoch != si.TicketEpoch { return &ErrBadTicket{xerrors.Errorf("bad ticket epoch: %d != %d", pci.Info.SealRandEpoch, si.TicketEpoch)} } return &ErrPrecommitOnChain{xerrors.Errorf("precommit already on chain")} } + //never commit P2 message before, check ticket expiration + ticketEarliest := height - policy.MaxPreCommitRandomnessLookback + + if si.TicketEpoch < ticketEarliest { + return &ErrExpiredTicket{xerrors.Errorf("ticket expired: seal height: %d, head: %d", si.TicketEpoch+policy.SealRandomnessLookback, height)} + } return nil } @@ -122,7 +124,7 @@ func (m *Sealing) checkCommit(ctx context.Context, si SectorInfo, proof []byte, return &ErrBadSeed{xerrors.Errorf("seed epoch was not set")} } - pci, err := m.api.StateSectorPreCommitInfo(ctx, m.maddr, si.SectorNumber, tok) + pci, err := m.Api.StateSectorPreCommitInfo(ctx, m.maddr, si.SectorNumber, tok) if err == ErrSectorAllocated { // not much more we can check here, basically try to wait for commit, // and hope that this will work @@ -150,7 +152,7 @@ func (m *Sealing) checkCommit(ctx context.Context, si SectorInfo, proof []byte, return err } - seed, err := m.api.ChainGetRandomnessFromBeacon(ctx, tok, crypto.DomainSeparationTag_InteractiveSealChallengeSeed, si.SeedEpoch, buf.Bytes()) + seed, err := m.Api.ChainGetRandomnessFromBeacon(ctx, tok, crypto.DomainSeparationTag_InteractiveSealChallengeSeed, si.SeedEpoch, buf.Bytes()) if err != nil { return &ErrApi{xerrors.Errorf("failed to get randomness for computing seal proof: %w", err)} } @@ -179,7 +181,7 @@ func (m *Sealing) checkCommit(ctx context.Context, si SectorInfo, proof []byte, return &ErrInvalidProof{xerrors.New("invalid proof (compute error?)")} } - if err := checkPieces(ctx, m.maddr, si, m.api); err != nil { + if err := checkPieces(ctx, m.maddr, si, m.Api); err != nil { return err } diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index c12a9f933..383562583 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -229,6 +229,11 @@ func (b *CommitBatcher) maybeStartBatch(notif bool) ([]sealiface.CommitBatchRes, } else { res, err = b.processBatch(cfg) } + + if err != nil { + log.Warnf("CommitBatcher maybeStartBatch individual:%v processBatch %v", individual, err) + } + if err != nil && len(res) == 0 { return nil, err } diff --git a/extern/storage-sealing/fsm.go b/extern/storage-sealing/fsm.go index d04aef790..0a4dedbf2 100644 --- a/extern/storage-sealing/fsm.go +++ b/extern/storage-sealing/fsm.go @@ -351,6 +351,13 @@ func (m *Sealing) plan(events []statemachine.Event, state *SectorInfo) (func(sta log.Errorw("update sector stats", "error", err) } + // todo: drop this, use Context iface everywhere + wrapCtx := func(f func(Context, SectorInfo) error) func(statemachine.Context, SectorInfo) error { + return func(ctx statemachine.Context, info SectorInfo) error { + return f(&ctx, info) + } + } + switch state.State { // Happy path case Empty: @@ -413,7 +420,7 @@ func (m *Sealing) plan(events []statemachine.Event, state *SectorInfo) (func(sta case DealsExpired: return m.handleDealsExpired, processed, nil case RecoverDealIDs: - return m.handleRecoverDealIDs, processed, nil + return wrapCtx(m.HandleRecoverDealIDs), processed, nil // Post-seal case Proving: diff --git a/extern/storage-sealing/mocks/api.go b/extern/storage-sealing/mocks/api.go new file mode 100644 index 000000000..d2962c56d --- /dev/null +++ b/extern/storage-sealing/mocks/api.go @@ -0,0 +1,439 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/filecoin-project/lotus/extern/storage-sealing (interfaces: SealingAPI) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + address "github.com/filecoin-project/go-address" + abi "github.com/filecoin-project/go-state-types/abi" + big "github.com/filecoin-project/go-state-types/big" + crypto "github.com/filecoin-project/go-state-types/crypto" + dline "github.com/filecoin-project/go-state-types/dline" + network "github.com/filecoin-project/go-state-types/network" + api "github.com/filecoin-project/lotus/api" + market "github.com/filecoin-project/lotus/chain/actors/builtin/market" + miner "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + types "github.com/filecoin-project/lotus/chain/types" + sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" + gomock "github.com/golang/mock/gomock" + cid "github.com/ipfs/go-cid" +) + +// MockSealingAPI is a mock of SealingAPI interface. +type MockSealingAPI struct { + ctrl *gomock.Controller + recorder *MockSealingAPIMockRecorder +} + +// MockSealingAPIMockRecorder is the mock recorder for MockSealingAPI. +type MockSealingAPIMockRecorder struct { + mock *MockSealingAPI +} + +// NewMockSealingAPI creates a new mock instance. +func NewMockSealingAPI(ctrl *gomock.Controller) *MockSealingAPI { + mock := &MockSealingAPI{ctrl: ctrl} + mock.recorder = &MockSealingAPIMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSealingAPI) EXPECT() *MockSealingAPIMockRecorder { + return m.recorder +} + +// ChainBaseFee mocks base method. +func (m *MockSealingAPI) ChainBaseFee(arg0 context.Context, arg1 sealing.TipSetToken) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainBaseFee", arg0, arg1) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainBaseFee indicates an expected call of ChainBaseFee. +func (mr *MockSealingAPIMockRecorder) ChainBaseFee(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainBaseFee", reflect.TypeOf((*MockSealingAPI)(nil).ChainBaseFee), arg0, arg1) +} + +// ChainGetMessage mocks base method. +func (m *MockSealingAPI) ChainGetMessage(arg0 context.Context, arg1 cid.Cid) (*types.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetMessage", arg0, arg1) + ret0, _ := ret[0].(*types.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetMessage indicates an expected call of ChainGetMessage. +func (mr *MockSealingAPIMockRecorder) ChainGetMessage(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetMessage", reflect.TypeOf((*MockSealingAPI)(nil).ChainGetMessage), arg0, arg1) +} + +// ChainGetRandomnessFromBeacon mocks base method. +func (m *MockSealingAPI) ChainGetRandomnessFromBeacon(arg0 context.Context, arg1 sealing.TipSetToken, arg2 crypto.DomainSeparationTag, arg3 abi.ChainEpoch, arg4 []byte) (abi.Randomness, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetRandomnessFromBeacon", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(abi.Randomness) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetRandomnessFromBeacon indicates an expected call of ChainGetRandomnessFromBeacon. +func (mr *MockSealingAPIMockRecorder) ChainGetRandomnessFromBeacon(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetRandomnessFromBeacon", reflect.TypeOf((*MockSealingAPI)(nil).ChainGetRandomnessFromBeacon), arg0, arg1, arg2, arg3, arg4) +} + +// ChainGetRandomnessFromTickets mocks base method. +func (m *MockSealingAPI) ChainGetRandomnessFromTickets(arg0 context.Context, arg1 sealing.TipSetToken, arg2 crypto.DomainSeparationTag, arg3 abi.ChainEpoch, arg4 []byte) (abi.Randomness, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetRandomnessFromTickets", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(abi.Randomness) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetRandomnessFromTickets indicates an expected call of ChainGetRandomnessFromTickets. +func (mr *MockSealingAPIMockRecorder) ChainGetRandomnessFromTickets(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetRandomnessFromTickets", reflect.TypeOf((*MockSealingAPI)(nil).ChainGetRandomnessFromTickets), arg0, arg1, arg2, arg3, arg4) +} + +// ChainHead mocks base method. +func (m *MockSealingAPI) ChainHead(arg0 context.Context) (sealing.TipSetToken, abi.ChainEpoch, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainHead", arg0) + ret0, _ := ret[0].(sealing.TipSetToken) + ret1, _ := ret[1].(abi.ChainEpoch) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// ChainHead indicates an expected call of ChainHead. +func (mr *MockSealingAPIMockRecorder) ChainHead(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainHead", reflect.TypeOf((*MockSealingAPI)(nil).ChainHead), arg0) +} + +// ChainReadObj mocks base method. +func (m *MockSealingAPI) ChainReadObj(arg0 context.Context, arg1 cid.Cid) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainReadObj", arg0, arg1) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainReadObj indicates an expected call of ChainReadObj. +func (mr *MockSealingAPIMockRecorder) ChainReadObj(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainReadObj", reflect.TypeOf((*MockSealingAPI)(nil).ChainReadObj), arg0, arg1) +} + +// SendMsg mocks base method. +func (m *MockSealingAPI) SendMsg(arg0 context.Context, arg1, arg2 address.Address, arg3 abi.MethodNum, arg4, arg5 big.Int, arg6 []byte) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendMsg", arg0, arg1, arg2, arg3, arg4, arg5, arg6) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SendMsg indicates an expected call of SendMsg. +func (mr *MockSealingAPIMockRecorder) SendMsg(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMsg", reflect.TypeOf((*MockSealingAPI)(nil).SendMsg), arg0, arg1, arg2, arg3, arg4, arg5, arg6) +} + +// StateComputeDataCommitment mocks base method. +func (m *MockSealingAPI) StateComputeDataCommitment(arg0 context.Context, arg1 address.Address, arg2 abi.RegisteredSealProof, arg3 []abi.DealID, arg4 sealing.TipSetToken) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateComputeDataCommitment", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateComputeDataCommitment indicates an expected call of StateComputeDataCommitment. +func (mr *MockSealingAPIMockRecorder) StateComputeDataCommitment(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateComputeDataCommitment", reflect.TypeOf((*MockSealingAPI)(nil).StateComputeDataCommitment), arg0, arg1, arg2, arg3, arg4) +} + +// StateLookupID mocks base method. +func (m *MockSealingAPI) StateLookupID(arg0 context.Context, arg1 address.Address, arg2 sealing.TipSetToken) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateLookupID", arg0, arg1, arg2) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateLookupID indicates an expected call of StateLookupID. +func (mr *MockSealingAPIMockRecorder) StateLookupID(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateLookupID", reflect.TypeOf((*MockSealingAPI)(nil).StateLookupID), arg0, arg1, arg2) +} + +// StateMarketStorageDeal mocks base method. +func (m *MockSealingAPI) StateMarketStorageDeal(arg0 context.Context, arg1 abi.DealID, arg2 sealing.TipSetToken) (*api.MarketDeal, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMarketStorageDeal", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.MarketDeal) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMarketStorageDeal indicates an expected call of StateMarketStorageDeal. +func (mr *MockSealingAPIMockRecorder) StateMarketStorageDeal(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMarketStorageDeal", reflect.TypeOf((*MockSealingAPI)(nil).StateMarketStorageDeal), arg0, arg1, arg2) +} + +// StateMarketStorageDealProposal mocks base method. +func (m *MockSealingAPI) StateMarketStorageDealProposal(arg0 context.Context, arg1 abi.DealID, arg2 sealing.TipSetToken) (market.DealProposal, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMarketStorageDealProposal", arg0, arg1, arg2) + ret0, _ := ret[0].(market.DealProposal) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMarketStorageDealProposal indicates an expected call of StateMarketStorageDealProposal. +func (mr *MockSealingAPIMockRecorder) StateMarketStorageDealProposal(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMarketStorageDealProposal", reflect.TypeOf((*MockSealingAPI)(nil).StateMarketStorageDealProposal), arg0, arg1, arg2) +} + +// StateMinerAvailableBalance mocks base method. +func (m *MockSealingAPI) StateMinerAvailableBalance(arg0 context.Context, arg1 address.Address, arg2 sealing.TipSetToken) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerAvailableBalance", arg0, arg1, arg2) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerAvailableBalance indicates an expected call of StateMinerAvailableBalance. +func (mr *MockSealingAPIMockRecorder) StateMinerAvailableBalance(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerAvailableBalance", reflect.TypeOf((*MockSealingAPI)(nil).StateMinerAvailableBalance), arg0, arg1, arg2) +} + +// StateMinerInfo mocks base method. +func (m *MockSealingAPI) StateMinerInfo(arg0 context.Context, arg1 address.Address, arg2 sealing.TipSetToken) (miner.MinerInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerInfo", arg0, arg1, arg2) + ret0, _ := ret[0].(miner.MinerInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerInfo indicates an expected call of StateMinerInfo. +func (mr *MockSealingAPIMockRecorder) StateMinerInfo(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerInfo", reflect.TypeOf((*MockSealingAPI)(nil).StateMinerInfo), arg0, arg1, arg2) +} + +// StateMinerInitialPledgeCollateral mocks base method. +func (m *MockSealingAPI) StateMinerInitialPledgeCollateral(arg0 context.Context, arg1 address.Address, arg2 miner0.SectorPreCommitInfo, arg3 sealing.TipSetToken) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerInitialPledgeCollateral", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerInitialPledgeCollateral indicates an expected call of StateMinerInitialPledgeCollateral. +func (mr *MockSealingAPIMockRecorder) StateMinerInitialPledgeCollateral(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerInitialPledgeCollateral", reflect.TypeOf((*MockSealingAPI)(nil).StateMinerInitialPledgeCollateral), arg0, arg1, arg2, arg3) +} + +// StateMinerPartitions mocks base method. +func (m *MockSealingAPI) StateMinerPartitions(arg0 context.Context, arg1 address.Address, arg2 uint64, arg3 sealing.TipSetToken) ([]api.Partition, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerPartitions", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]api.Partition) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerPartitions indicates an expected call of StateMinerPartitions. +func (mr *MockSealingAPIMockRecorder) StateMinerPartitions(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerPartitions", reflect.TypeOf((*MockSealingAPI)(nil).StateMinerPartitions), arg0, arg1, arg2, arg3) +} + +// StateMinerPreCommitDepositForPower mocks base method. +func (m *MockSealingAPI) StateMinerPreCommitDepositForPower(arg0 context.Context, arg1 address.Address, arg2 miner0.SectorPreCommitInfo, arg3 sealing.TipSetToken) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerPreCommitDepositForPower", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerPreCommitDepositForPower indicates an expected call of StateMinerPreCommitDepositForPower. +func (mr *MockSealingAPIMockRecorder) StateMinerPreCommitDepositForPower(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerPreCommitDepositForPower", reflect.TypeOf((*MockSealingAPI)(nil).StateMinerPreCommitDepositForPower), arg0, arg1, arg2, arg3) +} + +// StateMinerProvingDeadline mocks base method. +func (m *MockSealingAPI) StateMinerProvingDeadline(arg0 context.Context, arg1 address.Address, arg2 sealing.TipSetToken) (*dline.Info, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerProvingDeadline", arg0, arg1, arg2) + ret0, _ := ret[0].(*dline.Info) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerProvingDeadline indicates an expected call of StateMinerProvingDeadline. +func (mr *MockSealingAPIMockRecorder) StateMinerProvingDeadline(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerProvingDeadline", reflect.TypeOf((*MockSealingAPI)(nil).StateMinerProvingDeadline), arg0, arg1, arg2) +} + +// StateMinerSectorAllocated mocks base method. +func (m *MockSealingAPI) StateMinerSectorAllocated(arg0 context.Context, arg1 address.Address, arg2 abi.SectorNumber, arg3 sealing.TipSetToken) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerSectorAllocated", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerSectorAllocated indicates an expected call of StateMinerSectorAllocated. +func (mr *MockSealingAPIMockRecorder) StateMinerSectorAllocated(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerSectorAllocated", reflect.TypeOf((*MockSealingAPI)(nil).StateMinerSectorAllocated), arg0, arg1, arg2, arg3) +} + +// StateMinerSectorSize mocks base method. +func (m *MockSealingAPI) StateMinerSectorSize(arg0 context.Context, arg1 address.Address, arg2 sealing.TipSetToken) (abi.SectorSize, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerSectorSize", arg0, arg1, arg2) + ret0, _ := ret[0].(abi.SectorSize) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerSectorSize indicates an expected call of StateMinerSectorSize. +func (mr *MockSealingAPIMockRecorder) StateMinerSectorSize(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerSectorSize", reflect.TypeOf((*MockSealingAPI)(nil).StateMinerSectorSize), arg0, arg1, arg2) +} + +// StateMinerWorkerAddress mocks base method. +func (m *MockSealingAPI) StateMinerWorkerAddress(arg0 context.Context, arg1 address.Address, arg2 sealing.TipSetToken) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerWorkerAddress", arg0, arg1, arg2) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerWorkerAddress indicates an expected call of StateMinerWorkerAddress. +func (mr *MockSealingAPIMockRecorder) StateMinerWorkerAddress(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerWorkerAddress", reflect.TypeOf((*MockSealingAPI)(nil).StateMinerWorkerAddress), arg0, arg1, arg2) +} + +// StateNetworkVersion mocks base method. +func (m *MockSealingAPI) StateNetworkVersion(arg0 context.Context, arg1 sealing.TipSetToken) (network.Version, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateNetworkVersion", arg0, arg1) + ret0, _ := ret[0].(network.Version) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateNetworkVersion indicates an expected call of StateNetworkVersion. +func (mr *MockSealingAPIMockRecorder) StateNetworkVersion(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateNetworkVersion", reflect.TypeOf((*MockSealingAPI)(nil).StateNetworkVersion), arg0, arg1) +} + +// StateSearchMsg mocks base method. +func (m *MockSealingAPI) StateSearchMsg(arg0 context.Context, arg1 cid.Cid) (*sealing.MsgLookup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSearchMsg", arg0, arg1) + ret0, _ := ret[0].(*sealing.MsgLookup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSearchMsg indicates an expected call of StateSearchMsg. +func (mr *MockSealingAPIMockRecorder) StateSearchMsg(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSearchMsg", reflect.TypeOf((*MockSealingAPI)(nil).StateSearchMsg), arg0, arg1) +} + +// StateSectorGetInfo mocks base method. +func (m *MockSealingAPI) StateSectorGetInfo(arg0 context.Context, arg1 address.Address, arg2 abi.SectorNumber, arg3 sealing.TipSetToken) (*miner.SectorOnChainInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSectorGetInfo", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*miner.SectorOnChainInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSectorGetInfo indicates an expected call of StateSectorGetInfo. +func (mr *MockSealingAPIMockRecorder) StateSectorGetInfo(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSectorGetInfo", reflect.TypeOf((*MockSealingAPI)(nil).StateSectorGetInfo), arg0, arg1, arg2, arg3) +} + +// StateSectorPartition mocks base method. +func (m *MockSealingAPI) StateSectorPartition(arg0 context.Context, arg1 address.Address, arg2 abi.SectorNumber, arg3 sealing.TipSetToken) (*sealing.SectorLocation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSectorPartition", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*sealing.SectorLocation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSectorPartition indicates an expected call of StateSectorPartition. +func (mr *MockSealingAPIMockRecorder) StateSectorPartition(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSectorPartition", reflect.TypeOf((*MockSealingAPI)(nil).StateSectorPartition), arg0, arg1, arg2, arg3) +} + +// StateSectorPreCommitInfo mocks base method. +func (m *MockSealingAPI) StateSectorPreCommitInfo(arg0 context.Context, arg1 address.Address, arg2 abi.SectorNumber, arg3 sealing.TipSetToken) (*miner.SectorPreCommitOnChainInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSectorPreCommitInfo", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*miner.SectorPreCommitOnChainInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSectorPreCommitInfo indicates an expected call of StateSectorPreCommitInfo. +func (mr *MockSealingAPIMockRecorder) StateSectorPreCommitInfo(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSectorPreCommitInfo", reflect.TypeOf((*MockSealingAPI)(nil).StateSectorPreCommitInfo), arg0, arg1, arg2, arg3) +} + +// StateWaitMsg mocks base method. +func (m *MockSealingAPI) StateWaitMsg(arg0 context.Context, arg1 cid.Cid) (sealing.MsgLookup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateWaitMsg", arg0, arg1) + ret0, _ := ret[0].(sealing.MsgLookup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateWaitMsg indicates an expected call of StateWaitMsg. +func (mr *MockSealingAPIMockRecorder) StateWaitMsg(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateWaitMsg", reflect.TypeOf((*MockSealingAPI)(nil).StateWaitMsg), arg0, arg1) +} diff --git a/extern/storage-sealing/mocks/statemachine.go b/extern/storage-sealing/mocks/statemachine.go new file mode 100644 index 000000000..9fdabdc87 --- /dev/null +++ b/extern/storage-sealing/mocks/statemachine.go @@ -0,0 +1,63 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/filecoin-project/lotus/extern/storage-sealing (interfaces: Context) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockContext is a mock of Context interface. +type MockContext struct { + ctrl *gomock.Controller + recorder *MockContextMockRecorder +} + +// MockContextMockRecorder is the mock recorder for MockContext. +type MockContextMockRecorder struct { + mock *MockContext +} + +// NewMockContext creates a new mock instance. +func NewMockContext(ctrl *gomock.Controller) *MockContext { + mock := &MockContext{ctrl: ctrl} + mock.recorder = &MockContextMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockContext) EXPECT() *MockContextMockRecorder { + return m.recorder +} + +// Context mocks base method. +func (m *MockContext) Context() context.Context { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Context") + ret0, _ := ret[0].(context.Context) + return ret0 +} + +// Context indicates an expected call of Context. +func (mr *MockContextMockRecorder) Context() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockContext)(nil).Context)) +} + +// Send mocks base method. +func (m *MockContext) Send(arg0 interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Send", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Send indicates an expected call of Send. +func (mr *MockContextMockRecorder) Send(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockContext)(nil).Send), arg0) +} diff --git a/extern/storage-sealing/precommit_policy.go b/extern/storage-sealing/precommit_policy.go index a6add5693..398956f01 100644 --- a/extern/storage-sealing/precommit_policy.go +++ b/extern/storage-sealing/precommit_policy.go @@ -3,11 +3,13 @@ package sealing import ( "context" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - - "github.com/filecoin-project/go-state-types/network" + "golang.org/x/xerrors" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/policy" ) type PreCommitPolicy interface { @@ -34,21 +36,21 @@ type Chain interface { // If we're in Mode 2: The pre-commit expiration epoch will be set to the // current epoch + the provided default duration. type BasicPreCommitPolicy struct { - api Chain + api Chain + getSealingConfig GetSealingConfigFunc - provingBoundary abi.ChainEpoch - duration abi.ChainEpoch + provingBuffer abi.ChainEpoch } // NewBasicPreCommitPolicy produces a BasicPreCommitPolicy. // // The provided duration is used as the default sector expiry when the sector // contains no deals. The proving boundary is used to adjust/align the sector's expiration. -func NewBasicPreCommitPolicy(api Chain, duration abi.ChainEpoch, provingBoundary abi.ChainEpoch) BasicPreCommitPolicy { +func NewBasicPreCommitPolicy(api Chain, cfgGetter GetSealingConfigFunc, provingBuffer abi.ChainEpoch) BasicPreCommitPolicy { return BasicPreCommitPolicy{ - api: api, - provingBoundary: provingBoundary, - duration: duration, + api: api, + getSealingConfig: cfgGetter, + provingBuffer: provingBuffer, } } @@ -79,11 +81,46 @@ func (p *BasicPreCommitPolicy) Expiration(ctx context.Context, ps ...Piece) (abi } if end == nil { - tmp := epoch + p.duration + // no deal pieces, get expiration for committed capacity sector + expirationDuration, err := p.getCCSectorLifetime() + if err != nil { + return 0, err + } + + tmp := epoch + expirationDuration end = &tmp } - *end += miner.WPoStProvingPeriod - (*end % miner.WPoStProvingPeriod) + p.provingBoundary - 1 + // Ensure there is at least one day for the PC message to land without falling below min sector lifetime + // TODO: The "one day" should probably be a config, though it doesn't matter too much + minExp := epoch + policy.GetMinSectorExpiration() + miner.WPoStProvingPeriod + if *end < minExp { + end = &minExp + } return *end, nil } + +func (p *BasicPreCommitPolicy) getCCSectorLifetime() (abi.ChainEpoch, error) { + c, err := p.getSealingConfig() + if err != nil { + return 0, xerrors.Errorf("sealing config load error: %w", err) + } + + var ccLifetimeEpochs = abi.ChainEpoch(uint64(c.CommittedCapacitySectorLifetime.Seconds()) / builtin.EpochDurationSeconds) + // if zero value in config, assume maximum sector extension + if ccLifetimeEpochs == 0 { + ccLifetimeEpochs = policy.GetMaxSectorExpirationExtension() + } + + if minExpiration := abi.ChainEpoch(miner.MinSectorExpiration); ccLifetimeEpochs < minExpiration { + log.Warnf("value for CommittedCapacitySectorLiftime is too short, using default minimum (%d epochs)", minExpiration) + return minExpiration, nil + } + if maxExpiration := policy.GetMaxSectorExpirationExtension(); ccLifetimeEpochs > maxExpiration { + log.Warnf("value for CommittedCapacitySectorLiftime is too long, using default maximum (%d epochs)", maxExpiration) + return maxExpiration, nil + } + + return ccLifetimeEpochs - p.provingBuffer, nil +} diff --git a/extern/storage-sealing/precommit_policy_test.go b/extern/storage-sealing/precommit_policy_test.go index a6c17d3fd..d4fa3eae4 100644 --- a/extern/storage-sealing/precommit_policy_test.go +++ b/extern/storage-sealing/precommit_policy_test.go @@ -3,10 +3,15 @@ package sealing_test import ( "context" "testing" + "time" "github.com/filecoin-project/go-state-types/network" api "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/policy" + sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" "github.com/ipfs/go-cid" "github.com/stretchr/testify/assert" @@ -14,14 +19,28 @@ import ( commcid "github.com/filecoin-project/go-fil-commcid" "github.com/filecoin-project/go-state-types/abi" - - sealing "github.com/filecoin-project/lotus/extern/storage-sealing" ) type fakeChain struct { h abi.ChainEpoch } +type fakeConfigStub struct { + CCSectorLifetime time.Duration +} + +func fakeConfigGetter(stub *fakeConfigStub) sealing.GetSealingConfigFunc { + return func() (sealiface.Config, error) { + if stub == nil { + return sealiface.Config{}, nil + } + + return sealiface.Config{ + CommittedCapacitySectorLifetime: stub.CCSectorLifetime, + }, nil + } +} + func (f *fakeChain) StateNetworkVersion(ctx context.Context, tok sealing.TipSetToken) (network.Version, error) { return build.NewestNetworkVersion, nil } @@ -38,21 +57,42 @@ func fakePieceCid(t *testing.T) cid.Cid { } func TestBasicPolicyEmptySector(t *testing.T) { - policy := sealing.NewBasicPreCommitPolicy(&fakeChain{ - h: abi.ChainEpoch(55), - }, 10, 0) + cfg := fakeConfigGetter(nil) + h := abi.ChainEpoch(55) + pBuffer := abi.ChainEpoch(2) + pcp := sealing.NewBasicPreCommitPolicy(&fakeChain{h: h}, cfg, pBuffer) + exp, err := pcp.Expiration(context.Background()) - exp, err := policy.Expiration(context.Background()) require.NoError(t, err) - assert.Equal(t, 2879, int(exp)) + // as set when there are no deal pieces + expected := h + policy.GetMaxSectorExpirationExtension() - pBuffer + assert.Equal(t, int(expected), int(exp)) +} + +func TestCustomCCSectorConfig(t *testing.T) { + customLifetime := 200 * 24 * time.Hour + customLifetimeEpochs := abi.ChainEpoch(int64(customLifetime.Seconds()) / builtin.EpochDurationSeconds) + cfgStub := fakeConfigStub{CCSectorLifetime: customLifetime} + cfg := fakeConfigGetter(&cfgStub) + h := abi.ChainEpoch(55) + pBuffer := abi.ChainEpoch(2) + pcp := sealing.NewBasicPreCommitPolicy(&fakeChain{h: h}, cfg, pBuffer) + exp, err := pcp.Expiration(context.Background()) + + require.NoError(t, err) + + // as set when there are no deal pieces + expected := h + customLifetimeEpochs - pBuffer + assert.Equal(t, int(expected), int(exp)) } func TestBasicPolicyMostConstrictiveSchedule(t *testing.T) { + cfg := fakeConfigGetter(nil) policy := sealing.NewBasicPreCommitPolicy(&fakeChain{ h: abi.ChainEpoch(55), - }, 100, 11) - + }, cfg, 2) + longestDealEpochEnd := abi.ChainEpoch(547300) pieces := []sealing.Piece{ { Piece: abi.PieceInfo{ @@ -63,7 +103,7 @@ func TestBasicPolicyMostConstrictiveSchedule(t *testing.T) { DealID: abi.DealID(42), DealSchedule: api.DealSchedule{ StartEpoch: abi.ChainEpoch(70), - EndEpoch: abi.ChainEpoch(75), + EndEpoch: abi.ChainEpoch(547275), }, }, }, @@ -76,7 +116,7 @@ func TestBasicPolicyMostConstrictiveSchedule(t *testing.T) { DealID: abi.DealID(43), DealSchedule: api.DealSchedule{ StartEpoch: abi.ChainEpoch(80), - EndEpoch: abi.ChainEpoch(100), + EndEpoch: longestDealEpochEnd, }, }, }, @@ -85,13 +125,14 @@ func TestBasicPolicyMostConstrictiveSchedule(t *testing.T) { exp, err := policy.Expiration(context.Background(), pieces...) require.NoError(t, err) - assert.Equal(t, 2890, int(exp)) + assert.Equal(t, int(longestDealEpochEnd), int(exp)) } func TestBasicPolicyIgnoresExistingScheduleIfExpired(t *testing.T) { + cfg := fakeConfigGetter(nil) policy := sealing.NewBasicPreCommitPolicy(&fakeChain{ h: abi.ChainEpoch(55), - }, 100, 0) + }, cfg, 0) pieces := []sealing.Piece{ { @@ -112,13 +153,15 @@ func TestBasicPolicyIgnoresExistingScheduleIfExpired(t *testing.T) { exp, err := policy.Expiration(context.Background(), pieces...) require.NoError(t, err) - assert.Equal(t, 2879, int(exp)) + // Treated as a CC sector, so expiration becomes currEpoch + maxLifetime = 55 + 1555200 + assert.Equal(t, 1555255, int(exp)) } func TestMissingDealIsIgnored(t *testing.T) { + cfg := fakeConfigGetter(nil) policy := sealing.NewBasicPreCommitPolicy(&fakeChain{ h: abi.ChainEpoch(55), - }, 100, 11) + }, cfg, 0) pieces := []sealing.Piece{ { @@ -130,7 +173,7 @@ func TestMissingDealIsIgnored(t *testing.T) { DealID: abi.DealID(44), DealSchedule: api.DealSchedule{ StartEpoch: abi.ChainEpoch(1), - EndEpoch: abi.ChainEpoch(10), + EndEpoch: abi.ChainEpoch(547300), }, }, }, @@ -146,5 +189,5 @@ func TestMissingDealIsIgnored(t *testing.T) { exp, err := policy.Expiration(context.Background(), pieces...) require.NoError(t, err) - assert.Equal(t, 2890, int(exp)) + assert.Equal(t, 547300, int(exp)) } diff --git a/extern/storage-sealing/sealiface/config.go b/extern/storage-sealing/sealiface/config.go index e33b36263..95b851609 100644 --- a/extern/storage-sealing/sealiface/config.go +++ b/extern/storage-sealing/sealiface/config.go @@ -20,6 +20,8 @@ type Config struct { WaitDealsDelay time.Duration + CommittedCapacitySectorLifetime time.Duration + AlwaysKeepUnsealedCopy bool FinalizeEarly bool diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index 3e40d10f3..3defdd830 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -44,6 +44,8 @@ type SectorLocation struct { var ErrSectorAllocated = errors.New("sectorNumber is allocated, but PreCommit info wasn't found on chain") +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . SealingAPI + type SealingAPI interface { StateWaitMsg(context.Context, cid.Cid) (MsgLookup, error) StateSearchMsg(context.Context, cid.Cid) (*MsgLookup, error) @@ -80,7 +82,9 @@ type SectorStateNotifee func(before, after SectorInfo) type AddrSel func(ctx context.Context, mi miner.MinerInfo, use api.AddrUse, goodFunds, minFunds abi.TokenAmount) (address.Address, abi.TokenAmount, error) type Sealing struct { - api SealingAPI + Api SealingAPI + DealInfo *CurrentDealInfoManager + feeCfg config.MinerFeeConfig events Events @@ -114,7 +118,6 @@ type Sealing struct { commiter *CommitBatcher getConfig GetSealingConfigFunc - dealInfo *CurrentDealInfoManager } type openSector struct { @@ -135,7 +138,9 @@ type pendingPiece struct { func New(mctx context.Context, api SealingAPI, fc config.MinerFeeConfig, events Events, maddr address.Address, ds datastore.Batching, sealer sectorstorage.SectorManager, sc SectorIDCounter, verif ffiwrapper.Verifier, prov ffiwrapper.Prover, pcp PreCommitPolicy, gc GetSealingConfigFunc, notifee SectorStateNotifee, as AddrSel) *Sealing { s := &Sealing{ - api: api, + Api: api, + DealInfo: &CurrentDealInfoManager{api}, + feeCfg: fc, events: events, @@ -159,7 +164,6 @@ func New(mctx context.Context, api SealingAPI, fc config.MinerFeeConfig, events commiter: NewCommitBatcher(mctx, maddr, api, as, fc, gc, prov), getConfig: gc, - dealInfo: &CurrentDealInfoManager{api}, stats: SectorStats{ bySector: map[abi.SectorID]statSectorState{}, @@ -229,12 +233,12 @@ func (m *Sealing) CommitPending(ctx context.Context) ([]abi.SectorID, error) { } func (m *Sealing) currentSealProof(ctx context.Context) (abi.RegisteredSealProof, error) { - mi, err := m.api.StateMinerInfo(ctx, m.maddr, nil) + mi, err := m.Api.StateMinerInfo(ctx, m.maddr, nil) if err != nil { return 0, err } - ver, err := m.api.StateNetworkVersion(ctx, nil) + ver, err := m.Api.StateNetworkVersion(ctx, nil) if err != nil { return 0, err } diff --git a/extern/storage-sealing/states_failed.go b/extern/storage-sealing/states_failed.go index bd5f489b4..f1fd092b6 100644 --- a/extern/storage-sealing/states_failed.go +++ b/extern/storage-sealing/states_failed.go @@ -35,13 +35,13 @@ func failedCooldown(ctx statemachine.Context, sector SectorInfo) error { } func (m *Sealing) checkPreCommitted(ctx statemachine.Context, sector SectorInfo) (*miner.SectorPreCommitOnChainInfo, bool) { - tok, _, err := m.api.ChainHead(ctx.Context()) + tok, _, err := m.Api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handleSealPrecommit1Failed(%d): temp error: %+v", sector.SectorNumber, err) return nil, false } - info, err := m.api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, tok) + info, err := m.Api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, tok) if err != nil { log.Errorf("handleSealPrecommit1Failed(%d): temp error: %+v", sector.SectorNumber, err) return nil, false @@ -71,14 +71,14 @@ func (m *Sealing) handleSealPrecommit2Failed(ctx statemachine.Context, sector Se } func (m *Sealing) handlePreCommitFailed(ctx statemachine.Context, sector SectorInfo) error { - tok, height, err := m.api.ChainHead(ctx.Context()) + tok, height, err := m.Api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handlePreCommitFailed: api error, not proceeding: %+v", err) return nil } if sector.PreCommitMessage != nil { - mw, err := m.api.StateSearchMsg(ctx.Context(), *sector.PreCommitMessage) + mw, err := m.Api.StateSearchMsg(ctx.Context(), *sector.PreCommitMessage) if err != nil { // API error if err := failedCooldown(ctx, sector); err != nil { @@ -105,7 +105,7 @@ func (m *Sealing) handlePreCommitFailed(ctx statemachine.Context, sector SectorI } } - if err := checkPrecommit(ctx.Context(), m.Address(), sector, tok, height, m.api); err != nil { + if err := checkPrecommit(ctx.Context(), m.Address(), sector, tok, height, m.Api); err != nil { switch err.(type) { case *ErrApi: log.Errorf("handlePreCommitFailed: api error, not proceeding: %+v", err) @@ -182,14 +182,14 @@ func (m *Sealing) handleComputeProofFailed(ctx statemachine.Context, sector Sect } func (m *Sealing) handleCommitFailed(ctx statemachine.Context, sector SectorInfo) error { - tok, _, err := m.api.ChainHead(ctx.Context()) + tok, _, err := m.Api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handleCommitting: api error, not proceeding: %+v", err) return nil } if sector.CommitMessage != nil { - mw, err := m.api.StateSearchMsg(ctx.Context(), *sector.CommitMessage) + mw, err := m.Api.StateSearchMsg(ctx.Context(), *sector.CommitMessage) if err != nil { // API error if err := failedCooldown(ctx, sector); err != nil { @@ -286,7 +286,7 @@ func (m *Sealing) handleTerminateFailed(ctx statemachine.Context, sector SectorI // ignoring error as it's most likely an API error - `pci` will be nil, and we'll go back to // the Terminating state after cooldown. If the API is still failing, well get back to here // with the error in SectorInfo log. - pci, _ := m.api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, nil) + pci, _ := m.Api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, nil) if pci != nil { return nil // pause the fsm, needs manual user action } @@ -300,7 +300,7 @@ func (m *Sealing) handleTerminateFailed(ctx statemachine.Context, sector SectorI func (m *Sealing) handleDealsExpired(ctx statemachine.Context, sector SectorInfo) error { // First make vary sure the sector isn't committed - si, err := m.api.StateSectorGetInfo(ctx.Context(), m.maddr, sector.SectorNumber, nil) + si, err := m.Api.StateSectorGetInfo(ctx.Context(), m.maddr, sector.SectorNumber, nil) if err != nil { return xerrors.Errorf("getting sector info: %w", err) } @@ -319,8 +319,8 @@ func (m *Sealing) handleDealsExpired(ctx statemachine.Context, sector SectorInfo return ctx.Send(SectorRemove{}) } -func (m *Sealing) handleRecoverDealIDs(ctx statemachine.Context, sector SectorInfo) error { - tok, height, err := m.api.ChainHead(ctx.Context()) +func (m *Sealing) HandleRecoverDealIDs(ctx Context, sector SectorInfo) error { + tok, height, err := m.Api.ChainHead(ctx.Context()) if err != nil { return xerrors.Errorf("getting chain head: %w", err) } @@ -340,7 +340,7 @@ func (m *Sealing) handleRecoverDealIDs(ctx statemachine.Context, sector SectorIn continue } - proposal, err := m.api.StateMarketStorageDealProposal(ctx.Context(), p.DealInfo.DealID, tok) + proposal, err := m.Api.StateMarketStorageDealProposal(ctx.Context(), p.DealInfo.DealID, tok) if err != nil { log.Warnf("getting deal %d for piece %d: %+v", p.DealInfo.DealID, i, err) toFix = append(toFix, i) @@ -389,11 +389,16 @@ func (m *Sealing) handleRecoverDealIDs(ctx statemachine.Context, sector SectorIn mdp := market.DealProposal(*p.DealInfo.DealProposal) dp = &mdp } - res, err := m.dealInfo.GetCurrentDealInfo(ctx.Context(), tok, dp, *p.DealInfo.PublishCid) + res, err := m.DealInfo.GetCurrentDealInfo(ctx.Context(), tok, dp, *p.DealInfo.PublishCid) if err != nil { failed[i] = xerrors.Errorf("getting current deal info for piece %d: %w", i, err) } + if res.MarketDeal.Proposal.PieceCID != p.Piece.PieceCID { + failed[i] = xerrors.Errorf("recovered piece (%d) deal in sector %d (dealid %d) has different PieceCID %s != %s", i, sector.SectorNumber, p.DealInfo.DealID, p.Piece.PieceCID, res.MarketDeal.Proposal.PieceCID) + continue + } + updates[i] = res.DealID } @@ -409,7 +414,11 @@ func (m *Sealing) handleRecoverDealIDs(ctx statemachine.Context, sector SectorIn } // todo: try to remove bad pieces (hard; see the todo above) - return xerrors.Errorf("failed to recover some deals: %w", merr) + + // for now removing sectors is probably better than having them stuck in RecoverDealIDs + // and expire anyways + log.Errorf("removing sector %d: deals expired or unrecoverable: %+v", sector.SectorNumber, merr) + return ctx.Send(SectorRemove{}) } // Not much to do here, we can't go back in time to commit this sector diff --git a/extern/storage-sealing/states_failed_test.go b/extern/storage-sealing/states_failed_test.go new file mode 100644 index 000000000..d73c597dc --- /dev/null +++ b/extern/storage-sealing/states_failed_test.go @@ -0,0 +1,99 @@ +package sealing_test + +import ( + "bytes" + "context" + "testing" + + "github.com/golang/mock/gomock" + "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/exitcode" + + api2 "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + "github.com/filecoin-project/lotus/extern/storage-sealing/mocks" +) + +func TestStateRecoverDealIDs(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + ctx := context.Background() + + api := mocks.NewMockSealingAPI(mockCtrl) + + fakeSealing := &sealing.Sealing{ + Api: api, + DealInfo: &sealing.CurrentDealInfoManager{CDAPI: api}, + } + + sctx := mocks.NewMockContext(mockCtrl) + sctx.EXPECT().Context().AnyTimes().Return(ctx) + + api.EXPECT().ChainHead(ctx).Times(1).Return(nil, abi.ChainEpoch(10), nil) + + var dealId abi.DealID = 12 + dealProposal := market.DealProposal{ + PieceCID: idCid("newPieceCID"), + } + + api.EXPECT().StateMarketStorageDealProposal(ctx, dealId, nil).Return(dealProposal, nil) + + pc := idCid("publishCID") + + // expect GetCurrentDealInfo + { + api.EXPECT().StateSearchMsg(ctx, pc).Return(&sealing.MsgLookup{ + Receipt: sealing.MessageReceipt{ + ExitCode: exitcode.Ok, + Return: cborRet(&market.PublishStorageDealsReturn{ + IDs: []abi.DealID{dealId}, + }), + }, + }, nil) + api.EXPECT().StateMarketStorageDeal(ctx, dealId, nil).Return(&api2.MarketDeal{ + Proposal: dealProposal, + }, nil) + + } + + sctx.EXPECT().Send(sealing.SectorRemove{}).Return(nil) + + err := fakeSealing.HandleRecoverDealIDs(sctx, sealing.SectorInfo{ + Pieces: []sealing.Piece{ + { + DealInfo: &api2.PieceDealInfo{ + DealID: dealId, + PublishCid: &pc, + }, + Piece: abi.PieceInfo{ + PieceCID: idCid("oldPieceCID"), + }, + }, + }, + }) + require.NoError(t, err) +} + +func idCid(str string) cid.Cid { + builder := cid.V1Builder{Codec: cid.Raw, MhType: mh.IDENTITY} + c, err := builder.Sum([]byte(str)) + if err != nil { + panic(err) + } + return c +} + +func cborRet(v cbor.Marshaler) []byte { + var buf bytes.Buffer + if err := v.MarshalCBOR(&buf); err != nil { + panic(err) + } + return buf.Bytes() +} diff --git a/extern/storage-sealing/states_proving.go b/extern/storage-sealing/states_proving.go index 5e613b20b..2deefa80f 100644 --- a/extern/storage-sealing/states_proving.go +++ b/extern/storage-sealing/states_proving.go @@ -23,7 +23,7 @@ func (m *Sealing) handleFaultReported(ctx statemachine.Context, sector SectorInf return xerrors.Errorf("entered fault reported state without a FaultReportMsg cid") } - mw, err := m.api.StateWaitMsg(ctx.Context(), *sector.FaultReportMsg) + mw, err := m.Api.StateWaitMsg(ctx.Context(), *sector.FaultReportMsg) if err != nil { return xerrors.Errorf("failed to wait for fault declaration: %w", err) } @@ -45,7 +45,7 @@ func (m *Sealing) handleTerminating(ctx statemachine.Context, sector SectorInfo) // * Check for correct termination // * wait for expiration (+winning lookback?) - si, err := m.api.StateSectorGetInfo(ctx.Context(), m.maddr, sector.SectorNumber, nil) + si, err := m.Api.StateSectorGetInfo(ctx.Context(), m.maddr, sector.SectorNumber, nil) if err != nil { return ctx.Send(SectorTerminateFailed{xerrors.Errorf("getting sector info: %w", err)}) } @@ -53,7 +53,7 @@ func (m *Sealing) handleTerminating(ctx statemachine.Context, sector SectorInfo) if si == nil { // either already terminated or not committed yet - pci, err := m.api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, nil) + pci, err := m.Api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, nil) if err != nil { return ctx.Send(SectorTerminateFailed{xerrors.Errorf("checking precommit presence: %w", err)}) } @@ -81,7 +81,7 @@ func (m *Sealing) handleTerminateWait(ctx statemachine.Context, sector SectorInf return xerrors.New("entered TerminateWait with nil TerminateMessage") } - mw, err := m.api.StateWaitMsg(ctx.Context(), *sector.TerminateMessage) + mw, err := m.Api.StateWaitMsg(ctx.Context(), *sector.TerminateMessage) if err != nil { return ctx.Send(SectorTerminateFailed{xerrors.Errorf("waiting for terminate message to land on chain: %w", err)}) } @@ -95,12 +95,12 @@ func (m *Sealing) handleTerminateWait(ctx statemachine.Context, sector SectorInf func (m *Sealing) handleTerminateFinality(ctx statemachine.Context, sector SectorInfo) error { for { - tok, epoch, err := m.api.ChainHead(ctx.Context()) + tok, epoch, err := m.Api.ChainHead(ctx.Context()) if err != nil { return ctx.Send(SectorTerminateFailed{xerrors.Errorf("getting chain head: %w", err)}) } - nv, err := m.api.StateNetworkVersion(ctx.Context(), tok) + nv, err := m.Api.StateNetworkVersion(ctx.Context(), tok) if err != nil { return ctx.Send(SectorTerminateFailed{xerrors.Errorf("getting network version: %w", err)}) } diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 7bedc1ed3..ed704d9dc 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -110,7 +110,7 @@ func checkProveCommitExpired(preCommitEpoch, msd abi.ChainEpoch, currEpoch abi.C } func (m *Sealing) getTicket(ctx statemachine.Context, sector SectorInfo) (abi.SealRandomness, abi.ChainEpoch, bool, error) { - tok, epoch, err := m.api.ChainHead(ctx.Context()) + tok, epoch, err := m.Api.ChainHead(ctx.Context()) if err != nil { log.Errorf("getTicket: api error, not proceeding: %+v", err) return nil, 0, false, nil @@ -118,7 +118,7 @@ func (m *Sealing) getTicket(ctx statemachine.Context, sector SectorInfo) (abi.Se // the reason why the StateMinerSectorAllocated function is placed here, if it is outside, // if the MarshalCBOR function and StateSectorPreCommitInfo function return err, it will be executed - allocated, aerr := m.api.StateMinerSectorAllocated(ctx.Context(), m.maddr, sector.SectorNumber, nil) + allocated, aerr := m.Api.StateMinerSectorAllocated(ctx.Context(), m.maddr, sector.SectorNumber, nil) if aerr != nil { log.Errorf("getTicket: api error, checking if sector is allocated: %+v", aerr) return nil, 0, false, nil @@ -130,7 +130,7 @@ func (m *Sealing) getTicket(ctx statemachine.Context, sector SectorInfo) (abi.Se return nil, 0, allocated, err } - pci, err := m.api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, tok) + pci, err := m.Api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, tok) if err != nil { return nil, 0, allocated, xerrors.Errorf("getting precommit info: %w", err) } @@ -138,7 +138,7 @@ func (m *Sealing) getTicket(ctx statemachine.Context, sector SectorInfo) (abi.Se if pci != nil { ticketEpoch = pci.Info.SealRandEpoch - nv, err := m.api.StateNetworkVersion(ctx.Context(), tok) + nv, err := m.Api.StateNetworkVersion(ctx.Context(), tok) if err != nil { return nil, 0, allocated, xerrors.Errorf("getTicket: StateNetworkVersion: api error, not proceeding: %+v", err) } @@ -161,7 +161,7 @@ func (m *Sealing) getTicket(ctx statemachine.Context, sector SectorInfo) (abi.Se return nil, 0, allocated, xerrors.Errorf("sector %s precommitted but expired", sector.SectorNumber) } - rand, err := m.api.ChainGetRandomnessFromTickets(ctx.Context(), tok, crypto.DomainSeparationTag_SealRandomness, ticketEpoch, buf.Bytes()) + rand, err := m.Api.ChainGetRandomnessFromTickets(ctx.Context(), tok, crypto.DomainSeparationTag_SealRandomness, ticketEpoch, buf.Bytes()) if err != nil { return nil, 0, allocated, err } @@ -192,7 +192,7 @@ func (m *Sealing) handleGetTicket(ctx statemachine.Context, sector SectorInfo) e } func (m *Sealing) handlePreCommit1(ctx statemachine.Context, sector SectorInfo) error { - if err := checkPieces(ctx.Context(), m.maddr, sector, m.api); err != nil { // Sanity check state + if err := checkPieces(ctx.Context(), m.maddr, sector, m.Api); err != nil { // Sanity check state switch err.(type) { case *ErrApi: log.Errorf("handlePreCommit1: api error, not proceeding: %+v", err) @@ -207,14 +207,14 @@ func (m *Sealing) handlePreCommit1(ctx statemachine.Context, sector SectorInfo) } } - tok, height, err := m.api.ChainHead(ctx.Context()) + tok, height, err := m.Api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handlePreCommit1: api error, not proceeding: %+v", err) return nil } if checkTicketExpired(sector.TicketEpoch, height) { - pci, err := m.api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, tok) + pci, err := m.Api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, tok) if err != nil { log.Errorf("handlePreCommit1: StateSectorPreCommitInfo: api error, not proceeding: %+v", err) return nil @@ -224,7 +224,7 @@ func (m *Sealing) handlePreCommit1(ctx statemachine.Context, sector SectorInfo) return ctx.Send(SectorOldTicket{}) // go get new ticket } - nv, err := m.api.StateNetworkVersion(ctx.Context(), tok) + nv, err := m.Api.StateNetworkVersion(ctx.Context(), tok) if err != nil { log.Errorf("handlePreCommit1: StateNetworkVersion: api error, not proceeding: %+v", err) return nil @@ -282,13 +282,13 @@ func (m *Sealing) remarkForUpgrade(sid abi.SectorNumber) { } func (m *Sealing) preCommitParams(ctx statemachine.Context, sector SectorInfo) (*miner.SectorPreCommitInfo, big.Int, TipSetToken, error) { - tok, height, err := m.api.ChainHead(ctx.Context()) + tok, height, err := m.Api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handlePreCommitting: api error, not proceeding: %+v", err) return nil, big.Zero(), nil, nil } - if err := checkPrecommit(ctx.Context(), m.Address(), sector, tok, height, m.api); err != nil { + if err := checkPrecommit(ctx.Context(), m.Address(), sector, tok, height, m.Api); err != nil { switch err := err.(type) { case *ErrApi: log.Errorf("handlePreCommitting: api error, not proceeding: %+v", err) @@ -320,9 +320,7 @@ func (m *Sealing) preCommitParams(ctx statemachine.Context, sector SectorInfo) ( return nil, big.Zero(), nil, ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("handlePreCommitting: failed to compute pre-commit expiry: %w", err)}) } - // Sectors must last _at least_ MinSectorExpiration + MaxSealDuration. - // TODO: The "+10" allows the pre-commit to take 10 blocks to be accepted. - nv, err := m.api.StateNetworkVersion(ctx.Context(), tok) + nv, err := m.Api.StateNetworkVersion(ctx.Context(), tok) if err != nil { return nil, big.Zero(), nil, ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("failed to get network version: %w", err)}) } @@ -339,7 +337,12 @@ func (m *Sealing) preCommitParams(ctx statemachine.Context, sector SectorInfo) ( if minExpiration := sector.TicketEpoch + policy.MaxPreCommitRandomnessLookback + msd + miner.MinSectorExpiration; expiration < minExpiration { expiration = minExpiration } - // TODO: enforce a reasonable _maximum_ sector lifetime? + + // Assume: both precommit msg & commit msg land on chain as early as possible + maxExpiration := height + policy.GetPreCommitChallengeDelay() + policy.GetMaxSectorExpirationExtension() + if expiration > maxExpiration { + expiration = maxExpiration + } params := &miner.SectorPreCommitInfo{ Expiration: expiration, @@ -353,7 +356,7 @@ func (m *Sealing) preCommitParams(ctx statemachine.Context, sector SectorInfo) ( depositMinimum := m.tryUpgradeSector(ctx.Context(), params) - collateral, err := m.api.StateMinerPreCommitDepositForPower(ctx.Context(), m.maddr, *params, tok) + collateral, err := m.Api.StateMinerPreCommitDepositForPower(ctx.Context(), m.maddr, *params, tok) if err != nil { return nil, big.Zero(), nil, xerrors.Errorf("getting initial pledge collateral: %w", err) } @@ -370,7 +373,7 @@ func (m *Sealing) handlePreCommitting(ctx statemachine.Context, sector SectorInf } if cfg.BatchPreCommits { - nv, err := m.api.StateNetworkVersion(ctx.Context(), nil) + nv, err := m.Api.StateNetworkVersion(ctx.Context(), nil) if err != nil { return xerrors.Errorf("getting network version: %w", err) } @@ -388,7 +391,7 @@ func (m *Sealing) handlePreCommitting(ctx statemachine.Context, sector SectorInf return nil // event was sent in preCommitParams } - deposit, err := collateralSendAmount(ctx.Context(), m.api, m.maddr, cfg, pcd) + deposit, err := collateralSendAmount(ctx.Context(), m.Api, m.maddr, cfg, pcd) if err != nil { return err } @@ -398,7 +401,7 @@ func (m *Sealing) handlePreCommitting(ctx statemachine.Context, sector SectorInf return ctx.Send(SectorChainPreCommitFailed{xerrors.Errorf("could not serialize pre-commit sector parameters: %w", err)}) } - mi, err := m.api.StateMinerInfo(ctx.Context(), m.maddr, tok) + mi, err := m.Api.StateMinerInfo(ctx.Context(), m.maddr, tok) if err != nil { log.Errorf("handlePreCommitting: api error, not proceeding: %+v", err) return nil @@ -412,7 +415,7 @@ func (m *Sealing) handlePreCommitting(ctx statemachine.Context, sector SectorInf } log.Infof("submitting precommit for sector %d (deposit: %s): ", sector.SectorNumber, deposit) - mcid, err := m.api.SendMsg(ctx.Context(), from, m.maddr, miner.Methods.PreCommitSector, deposit, big.Int(m.feeCfg.MaxPreCommitGasFee), enc.Bytes()) + mcid, err := m.Api.SendMsg(ctx.Context(), from, m.maddr, miner.Methods.PreCommitSector, deposit, big.Int(m.feeCfg.MaxPreCommitGasFee), enc.Bytes()) if err != nil { if params.ReplaceCapacity { m.remarkForUpgrade(params.ReplaceSectorNumber) @@ -459,7 +462,7 @@ func (m *Sealing) handlePreCommitWait(ctx statemachine.Context, sector SectorInf // would be ideal to just use the events.Called handler, but it wouldn't be able to handle individual message timeouts log.Info("Sector precommitted: ", sector.SectorNumber) - mw, err := m.api.StateWaitMsg(ctx.Context(), *sector.PreCommitMessage) + mw, err := m.Api.StateWaitMsg(ctx.Context(), *sector.PreCommitMessage) if err != nil { return ctx.Send(SectorChainPreCommitFailed{err}) } @@ -484,13 +487,13 @@ func (m *Sealing) handlePreCommitWait(ctx statemachine.Context, sector SectorInf } func (m *Sealing) handleWaitSeed(ctx statemachine.Context, sector SectorInfo) error { - tok, _, err := m.api.ChainHead(ctx.Context()) + tok, _, err := m.Api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handleWaitSeed: api error, not proceeding: %+v", err) return nil } - pci, err := m.api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, tok) + pci, err := m.Api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, tok) if err != nil { return xerrors.Errorf("getting precommit info: %w", err) } @@ -503,7 +506,7 @@ func (m *Sealing) handleWaitSeed(ctx statemachine.Context, sector SectorInfo) er err = m.events.ChainAt(func(ectx context.Context, _ TipSetToken, curH abi.ChainEpoch) error { // in case of null blocks the randomness can land after the tipset we // get from the events API - tok, _, err := m.api.ChainHead(ctx.Context()) + tok, _, err := m.Api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handleCommitting: api error, not proceeding: %+v", err) return nil @@ -513,7 +516,7 @@ func (m *Sealing) handleWaitSeed(ctx statemachine.Context, sector SectorInfo) er if err := m.maddr.MarshalCBOR(buf); err != nil { return err } - rand, err := m.api.ChainGetRandomnessFromBeacon(ectx, tok, crypto.DomainSeparationTag_InteractiveSealChallengeSeed, randHeight, buf.Bytes()) + rand, err := m.Api.ChainGetRandomnessFromBeacon(ectx, tok, crypto.DomainSeparationTag_InteractiveSealChallengeSeed, randHeight, buf.Bytes()) if err != nil { err = xerrors.Errorf("failed to get randomness for computing seal proof (ch %d; rh %d; tsk %x): %w", curH, randHeight, tok, err) @@ -540,7 +543,7 @@ func (m *Sealing) handleCommitting(ctx statemachine.Context, sector SectorInfo) if sector.CommitMessage != nil { log.Warnf("sector %d entered committing state with a commit message cid", sector.SectorNumber) - ml, err := m.api.StateSearchMsg(ctx.Context(), *sector.CommitMessage) + ml, err := m.Api.StateSearchMsg(ctx.Context(), *sector.CommitMessage) if err != nil { log.Warnf("sector %d searching existing commit message %s: %+v", sector.SectorNumber, *sector.CommitMessage, err) } @@ -579,7 +582,7 @@ func (m *Sealing) handleCommitting(ctx statemachine.Context, sector SectorInfo) } { - tok, _, err := m.api.ChainHead(ctx.Context()) + tok, _, err := m.Api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handleCommitting: api error, not proceeding: %+v", err) return nil @@ -608,7 +611,7 @@ func (m *Sealing) handleSubmitCommit(ctx statemachine.Context, sector SectorInfo } if cfg.AggregateCommits { - nv, err := m.api.StateNetworkVersion(ctx.Context(), nil) + nv, err := m.Api.StateNetworkVersion(ctx.Context(), nil) if err != nil { return xerrors.Errorf("getting network version: %w", err) } @@ -618,7 +621,7 @@ func (m *Sealing) handleSubmitCommit(ctx statemachine.Context, sector SectorInfo } } - tok, _, err := m.api.ChainHead(ctx.Context()) + tok, _, err := m.Api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handleSubmitCommit: api error, not proceeding: %+v", err) return nil @@ -638,13 +641,13 @@ func (m *Sealing) handleSubmitCommit(ctx statemachine.Context, sector SectorInfo return ctx.Send(SectorCommitFailed{xerrors.Errorf("could not serialize commit sector parameters: %w", err)}) } - mi, err := m.api.StateMinerInfo(ctx.Context(), m.maddr, tok) + mi, err := m.Api.StateMinerInfo(ctx.Context(), m.maddr, tok) if err != nil { log.Errorf("handleCommitting: api error, not proceeding: %+v", err) return nil } - pci, err := m.api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, tok) + pci, err := m.Api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, tok) if err != nil { return xerrors.Errorf("getting precommit info: %w", err) } @@ -652,7 +655,7 @@ func (m *Sealing) handleSubmitCommit(ctx statemachine.Context, sector SectorInfo return ctx.Send(SectorCommitFailed{error: xerrors.Errorf("precommit info not found on chain")}) } - collateral, err := m.api.StateMinerInitialPledgeCollateral(ctx.Context(), m.maddr, pci.Info, tok) + collateral, err := m.Api.StateMinerInitialPledgeCollateral(ctx.Context(), m.maddr, pci.Info, tok) if err != nil { return xerrors.Errorf("getting initial pledge collateral: %w", err) } @@ -662,7 +665,7 @@ func (m *Sealing) handleSubmitCommit(ctx statemachine.Context, sector SectorInfo collateral = big.Zero() } - collateral, err = collateralSendAmount(ctx.Context(), m.api, m.maddr, cfg, collateral) + collateral, err = collateralSendAmount(ctx.Context(), m.Api, m.maddr, cfg, collateral) if err != nil { return err } @@ -675,7 +678,7 @@ func (m *Sealing) handleSubmitCommit(ctx statemachine.Context, sector SectorInfo } // TODO: check seed / ticket / deals are up to date - mcid, err := m.api.SendMsg(ctx.Context(), from, m.maddr, miner.Methods.ProveCommitSector, collateral, big.Int(m.feeCfg.MaxCommitGasFee), enc.Bytes()) + mcid, err := m.Api.SendMsg(ctx.Context(), from, m.maddr, miner.Methods.ProveCommitSector, collateral, big.Int(m.feeCfg.MaxCommitGasFee), enc.Bytes()) if err != nil { return ctx.Send(SectorCommitFailed{xerrors.Errorf("pushing message to mpool: %w", err)}) } @@ -706,7 +709,7 @@ func (m *Sealing) handleSubmitCommitAggregate(ctx statemachine.Context, sector S } if res.Error != "" { - tok, _, err := m.api.ChainHead(ctx.Context()) + tok, _, err := m.Api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handleSubmitCommit: api error, not proceeding: %+v", err) return nil @@ -736,7 +739,7 @@ func (m *Sealing) handleCommitWait(ctx statemachine.Context, sector SectorInfo) return ctx.Send(SectorCommitFailed{xerrors.Errorf("entered commit wait with no commit cid")}) } - mw, err := m.api.StateWaitMsg(ctx.Context(), *sector.CommitMessage) + mw, err := m.Api.StateWaitMsg(ctx.Context(), *sector.CommitMessage) if err != nil { return ctx.Send(SectorCommitFailed{xerrors.Errorf("failed to wait for porep inclusion: %w", err)}) } @@ -753,7 +756,7 @@ func (m *Sealing) handleCommitWait(ctx statemachine.Context, sector SectorInfo) return ctx.Send(SectorCommitFailed{xerrors.Errorf("submitting sector proof failed (exit=%d, msg=%s) (t:%x; s:%x(%d); p:%x)", mw.Receipt.ExitCode, sector.CommitMessage, sector.TicketValue, sector.SeedValue, sector.SeedEpoch, sector.Proof)}) } - si, err := m.api.StateSectorGetInfo(ctx.Context(), m.maddr, sector.SectorNumber, mw.TipSetTok) + si, err := m.Api.StateSectorGetInfo(ctx.Context(), m.maddr, sector.SectorNumber, mw.TipSetTok) if err != nil { return ctx.Send(SectorCommitFailed{xerrors.Errorf("proof validation failed, calling StateSectorGetInfo: %w", err)}) } diff --git a/extern/storage-sealing/types.go b/extern/storage-sealing/types.go index c5aed505a..aeb378f29 100644 --- a/extern/storage-sealing/types.go +++ b/extern/storage-sealing/types.go @@ -17,6 +17,14 @@ import ( "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" ) +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/statemachine.go -package=mocks . Context + +// Context is a go-statemachine context +type Context interface { + Context() context.Context + Send(evt interface{}) error +} + // Piece is a tuple of piece and deal info type PieceWithDealInfo struct { Piece abi.PieceInfo diff --git a/extern/storage-sealing/upgrade_queue.go b/extern/storage-sealing/upgrade_queue.go index 78a78fc45..02db41fde 100644 --- a/extern/storage-sealing/upgrade_queue.go +++ b/extern/storage-sealing/upgrade_queue.go @@ -57,7 +57,7 @@ func (m *Sealing) tryUpgradeSector(ctx context.Context, params *miner.SectorPreC } replace := m.maybeUpgradableSector() if replace != nil { - loc, err := m.api.StateSectorPartition(ctx, m.maddr, *replace, nil) + loc, err := m.Api.StateSectorPartition(ctx, m.maddr, *replace, nil) if err != nil { log.Errorf("error calling StateSectorPartition for replaced sector: %+v", err) return big.Zero() @@ -70,7 +70,7 @@ func (m *Sealing) tryUpgradeSector(ctx context.Context, params *miner.SectorPreC log.Infof("replacing sector %d with %d", *replace, params.SectorNumber) - ri, err := m.api.StateSectorGetInfo(ctx, m.maddr, *replace, nil) + ri, err := m.Api.StateSectorGetInfo(ctx, m.maddr, *replace, nil) if err != nil { log.Errorf("error calling StateSectorGetInfo for replaced sector: %+v", err) return big.Zero() diff --git a/gateway/node.go b/gateway/node.go index 3c7a67196..84b616f26 100644 --- a/gateway/node.go +++ b/gateway/node.go @@ -36,6 +36,7 @@ type TargetAPI interface { ChainGetNode(ctx context.Context, p string) (*api.IpldObject, error) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) + ChainGetTipSetAfterHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) ChainHasObj(context.Context, cid.Cid) (bool, error) ChainHead(ctx context.Context) (*types.TipSet, error) ChainNotify(context.Context) (<-chan []*api.HeadChange, error) @@ -163,32 +164,48 @@ func (gw *Node) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types } func (gw *Node) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) { + if err := gw.checkTipSetHeight(ctx, h, tsk); err != nil { + return nil, err + } + + return gw.target.ChainGetTipSetByHeight(ctx, h, tsk) +} + +func (gw *Node) ChainGetTipSetAfterHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) { + if err := gw.checkTipSetHeight(ctx, h, tsk); err != nil { + return nil, err + } + + return gw.target.ChainGetTipSetAfterHeight(ctx, h, tsk) +} + +func (gw *Node) checkTipSetHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) error { var ts *types.TipSet if tsk.IsEmpty() { head, err := gw.target.ChainHead(ctx) if err != nil { - return nil, err + return err } ts = head } else { gts, err := gw.target.ChainGetTipSet(ctx, tsk) if err != nil { - return nil, err + return err } ts = gts } // Check if the tipset key refers to gw tipset that's too far in the past if err := gw.checkTipset(ts); err != nil { - return nil, err + return err } // Check if the height is too far in the past if err := gw.checkTipsetHeight(ts, h); err != nil { - return nil, err + return err } - return gw.target.ChainGetTipSetByHeight(ctx, h, tsk) + return nil } func (gw *Node) ChainGetNode(ctx context.Context, p string) (*api.IpldObject, error) { diff --git a/go.mod b/go.mod index bea227884..8413508b0 100644 --- a/go.mod +++ b/go.mod @@ -26,17 +26,17 @@ require ( github.com/elastic/gosigar v0.12.0 github.com/etclabscore/go-openrpc-reflect v0.0.36 github.com/fatih/color v1.9.0 + github.com/filecoin-project/dagstore v0.4.3 github.com/filecoin-project/filecoin-ffi v0.30.4-0.20200910194244-f640612a1a1f github.com/filecoin-project/go-address v0.0.5 - github.com/filecoin-project/go-amt-ipld/v2 v2.1.1-0.20201006184820-924ee87a1349 // indirect github.com/filecoin-project/go-bitfield v0.2.4 github.com/filecoin-project/go-cbor-util v0.0.0-20191219014500-08c40a1e63a2 github.com/filecoin-project/go-commp-utils v0.1.1-0.20210427191551-70bf140d31c7 github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03 - github.com/filecoin-project/go-data-transfer v1.7.2 + github.com/filecoin-project/go-data-transfer v1.7.8 github.com/filecoin-project/go-fil-commcid v0.1.0 github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 - github.com/filecoin-project/go-fil-markets v1.6.2 + github.com/filecoin-project/go-fil-markets v1.8.1 github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec github.com/filecoin-project/go-multistore v0.0.3 github.com/filecoin-project/go-padreader v0.0.0-20210723183308-812a16dc01b1 @@ -57,7 +57,7 @@ require ( github.com/go-kit/kit v0.10.0 github.com/go-ole/go-ole v1.2.4 // indirect github.com/golang/mock v1.6.0 - github.com/google/uuid v1.1.2 + github.com/google/uuid v1.2.0 github.com/gorilla/mux v1.7.4 github.com/gorilla/websocket v1.4.2 github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 @@ -66,10 +66,10 @@ require ( github.com/hashicorp/golang-lru v0.5.4 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d github.com/ipfs/bbloom v0.0.4 - github.com/ipfs/go-bitswap v0.3.2 + github.com/ipfs/go-bitswap v0.3.4 github.com/ipfs/go-block-format v0.0.3 - github.com/ipfs/go-blockservice v0.1.4 - github.com/ipfs/go-cid v0.0.7 + github.com/ipfs/go-blockservice v0.1.5 + github.com/ipfs/go-cid v0.1.0 github.com/ipfs/go-cidutil v0.0.2 github.com/ipfs/go-datastore v0.4.5 github.com/ipfs/go-ds-badger2 v0.1.1-0.20200708190120-187fc06f714e @@ -78,8 +78,9 @@ require ( github.com/ipfs/go-ds-pebble v0.0.2-0.20200921225637-ce220f8ac459 github.com/ipfs/go-filestore v1.0.0 github.com/ipfs/go-fs-lock v0.0.6 - github.com/ipfs/go-graphsync v0.6.6 - github.com/ipfs/go-ipfs-blockstore v1.0.3 + github.com/ipfs/go-graphsync v0.6.9 + github.com/ipfs/go-ipfs-blockstore v1.0.4 + github.com/ipfs/go-ipfs-blocksutil v0.0.1 github.com/ipfs/go-ipfs-chunker v0.0.5 github.com/ipfs/go-ipfs-ds-help v1.0.0 github.com/ipfs/go-ipfs-exchange-interface v0.0.1 @@ -95,12 +96,12 @@ require ( github.com/ipfs/go-metrics-interface v0.0.1 github.com/ipfs/go-metrics-prometheus v0.0.2 github.com/ipfs/go-path v0.0.7 - github.com/ipfs/go-unixfs v0.2.4 + github.com/ipfs/go-unixfs v0.2.6 github.com/ipfs/interface-go-ipfs-core v0.2.3 github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d + github.com/ipld/go-car/v2 v2.0.3-0.20210811121346-c514a30114d7 github.com/ipld/go-ipld-prime v0.5.1-0.20201021195245-109253e8a018 github.com/kelseyhightower/envconfig v1.4.0 - github.com/lib/pq v1.10.2 github.com/libp2p/go-buffer-pool v0.0.2 github.com/libp2p/go-eventbus v0.2.1 github.com/libp2p/go-libp2p v0.14.2 @@ -111,7 +112,7 @@ require ( github.com/libp2p/go-libp2p-mplex v0.4.1 github.com/libp2p/go-libp2p-noise v0.2.0 github.com/libp2p/go-libp2p-peerstore v0.2.8 - github.com/libp2p/go-libp2p-pubsub v0.5.3 + github.com/libp2p/go-libp2p-pubsub v0.5.4 github.com/libp2p/go-libp2p-quic-transport v0.11.2 github.com/libp2p/go-libp2p-record v0.1.3 github.com/libp2p/go-libp2p-routing-helpers v0.2.3 @@ -119,7 +120,6 @@ require ( github.com/libp2p/go-libp2p-tls v0.1.3 github.com/libp2p/go-libp2p-yamux v0.5.4 github.com/libp2p/go-maddr-filter v0.1.0 - github.com/mattn/go-colorable v0.1.6 // indirect github.com/mattn/go-isatty v0.0.13 github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 github.com/mitchellh/go-homedir v1.1.0 @@ -128,6 +128,7 @@ require ( github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multibase v0.0.3 github.com/multiformats/go-multihash v0.0.15 + github.com/multiformats/go-varint v0.0.6 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 github.com/opentracing/opentracing-go v1.2.0 github.com/polydawn/refmt v0.0.0-20190809202753-05966cbd336a @@ -135,12 +136,11 @@ require ( github.com/raulk/clock v1.1.0 github.com/raulk/go-watchdog v1.0.1 github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25 - github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.0 github.com/urfave/cli/v2 v2.2.0 github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba - github.com/whyrusleeping/cbor-gen v0.0.0-20210219115102-f37d292932f2 + github.com/whyrusleeping/cbor-gen v0.0.0-20210713220151-be142a5ae1a8 github.com/whyrusleeping/ledger-filecoin-go v0.9.1-0.20201010031517-c3dcc1bddce4 github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 github.com/whyrusleeping/pubsub v0.0.0-20190708150250-92bcb0691325 @@ -148,17 +148,16 @@ require ( go.opencensus.io v0.23.0 go.uber.org/dig v1.10.0 // indirect go.uber.org/fx v1.9.0 - go.uber.org/multierr v1.6.0 + go.uber.org/multierr v1.7.0 go.uber.org/zap v1.16.0 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c golang.org/x/time v0.0.0-20191024005414-555d28b269f0 golang.org/x/tools v0.1.5 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 gopkg.in/cheggaaa/pb.v1 v1.0.28 gotest.tools v2.2.0+incompatible - honnef.co/go/tools v0.0.1-2020.1.3 // indirect ) replace github.com/libp2p/go-libp2p-yamux => github.com/libp2p/go-libp2p-yamux v0.5.1 diff --git a/go.sum b/go.sum index 89a1e8d3a..1e93c4cd2 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,7 @@ contrib.go.opencensus.io/exporter/prometheus v0.1.0 h1:SByaIoWwNgMdPSgl5sMqM2KDE contrib.go.opencensus.io/exporter/prometheus v0.1.0/go.mod h1:cGFniUXGZlKRjzOyuZJ6mgB+PgBcCIa79kEKR8YCW+A= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= @@ -168,6 +169,7 @@ github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmf github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0 h1:kq/SbG2BCKLkDKkjQf5OWwKWUKj1lgs3lFI4PxnR5lg= @@ -256,12 +258,14 @@ github.com/fatih/color v1.8.0/go.mod h1:3l45GVGkyrnYNl9HoIjnp2NnNWvh6hLAqD8yTfGj github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fd/go-nat v1.0.0/go.mod h1:BTBu/CKvMmOMUPkKVef1pngt2WFH/lg7E6yQnulfp6E= +github.com/filecoin-project/dagstore v0.4.2/go.mod h1:WY5OoLfnwISCk6eASSF927KKPqLPIlTwmG1qHpA08KY= +github.com/filecoin-project/dagstore v0.4.3 h1:yeFl6+2BRY1gOVp/hrZuFa24s7LY0Qqkqx/Gh8lidZs= +github.com/filecoin-project/dagstore v0.4.3/go.mod h1:dm/91AO5UaDd3bABFjg/5fmRH99vvpS7g1mykqvz6KQ= github.com/filecoin-project/go-address v0.0.3/go.mod h1:jr8JxKsYx+lQlQZmF5i2U0Z+cGQ59wMIps/8YW/lDj8= github.com/filecoin-project/go-address v0.0.5 h1:SSaFT/5aLfPXycUlFyemoHYhRgdyXClXCyDdNJKPlDM= github.com/filecoin-project/go-address v0.0.5/go.mod h1:jr8JxKsYx+lQlQZmF5i2U0Z+cGQ59wMIps/8YW/lDj8= +github.com/filecoin-project/go-amt-ipld/v2 v2.1.0 h1:t6qDiuGYYngDqaLc2ZUvdtAg4UNxPeOYaXhBWSNsVaM= github.com/filecoin-project/go-amt-ipld/v2 v2.1.0/go.mod h1:nfFPoGyX0CU9SkXX8EoCcSuHN1XcbN0c6KBh7yvP5fs= -github.com/filecoin-project/go-amt-ipld/v2 v2.1.1-0.20201006184820-924ee87a1349 h1:pIuR0dnMD0i+as8wNnjjHyQrnhP5O5bmba/lmgQeRgU= -github.com/filecoin-project/go-amt-ipld/v2 v2.1.1-0.20201006184820-924ee87a1349/go.mod h1:vgmwKBkx+ca5OIeEvstiQgzAZnb7R6QaqE1oEDSqa6g= github.com/filecoin-project/go-amt-ipld/v3 v3.0.0/go.mod h1:Qa95YNAbtoVCTSVtX38aAC1ptBnJfPma1R/zZsKmx4o= github.com/filecoin-project/go-amt-ipld/v3 v3.1.0 h1:ZNJ9tEG5bE72vBWYiuh5bkxJVM3ViHNOmQ7qew9n6RE= github.com/filecoin-project/go-amt-ipld/v3 v3.1.0/go.mod h1:UjM2QhDFrrjD5s1CdnkJkat4ga+LqZBZgTMniypABRo= @@ -276,9 +280,9 @@ github.com/filecoin-project/go-commp-utils v0.1.1-0.20210427191551-70bf140d31c7/ github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03 h1:2pMXdBnCiXjfCYx/hLqFxccPoqsSveQFxVLvNxy9bus= github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03/go.mod h1:+viYnvGtUTgJRdy6oaeF4MTFKAfatX071MPDPBL11EQ= github.com/filecoin-project/go-data-transfer v1.0.1/go.mod h1:UxvfUAY9v3ub0a21BSK9u3pB2aq30Y0KMsG+w9/ysyo= -github.com/filecoin-project/go-data-transfer v1.7.0/go.mod h1:GLRr5BmLEqsLwXfiRDG7uJvph22KGL2M4iOuF8EINaU= -github.com/filecoin-project/go-data-transfer v1.7.2 h1:iL3q5pxSloA7V2QucFofoVN3lquULz+Ml0KrNqMT5ZU= -github.com/filecoin-project/go-data-transfer v1.7.2/go.mod h1:GLRr5BmLEqsLwXfiRDG7uJvph22KGL2M4iOuF8EINaU= +github.com/filecoin-project/go-data-transfer v1.7.6/go.mod h1:Cbl9lzKOuAyyIxp1tE+VbV5Aix4bxzA7uJGA9wGM4fM= +github.com/filecoin-project/go-data-transfer v1.7.8 h1:s4cF9nX9sEy7RgZd3NW92YN/hKyIy2fQl+7dVOAS8r8= +github.com/filecoin-project/go-data-transfer v1.7.8/go.mod h1:Cbl9lzKOuAyyIxp1tE+VbV5Aix4bxzA7uJGA9wGM4fM= github.com/filecoin-project/go-ds-versioning v0.1.0 h1:y/X6UksYTsK8TLCI7rttCKEvl8btmWxyFMEeeWGUxIQ= github.com/filecoin-project/go-ds-versioning v0.1.0/go.mod h1:mp16rb4i2QPmxBnmanUx8i/XANp+PFCCJWiAb+VW4/s= github.com/filecoin-project/go-fil-commcid v0.0.0-20200716160307-8f644712406f/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= @@ -288,8 +292,8 @@ github.com/filecoin-project/go-fil-commcid v0.1.0/go.mod h1:Eaox7Hvus1JgPrL5+M3+ github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 h1:imrrpZWEHRnNqqv0tN7LXep5bFEVOVmQWHJvl2mgsGo= github.com/filecoin-project/go-fil-commp-hashhash v0.1.0/go.mod h1:73S8WSEWh9vr0fDJVnKADhfIv/d6dCbAGaAGWbdJEI8= github.com/filecoin-project/go-fil-markets v1.0.5-0.20201113164554-c5eba40d5335/go.mod h1:AJySOJC00JRWEZzRG2KsfUnqEf5ITXxeX09BE9N4f9c= -github.com/filecoin-project/go-fil-markets v1.6.2 h1:ib1sGUOF+hf50YwP7+p9yoK+9g84YcXzvuenxd6MYoE= -github.com/filecoin-project/go-fil-markets v1.6.2/go.mod h1:ZuFDagROUV6GfvBU//KReTQDw+EZci4rH7jMYTD10vs= +github.com/filecoin-project/go-fil-markets v1.8.1 h1:nNJB5EIp5c6yo/z51DloVaL7T24SslCoxSDOXwNQr9k= +github.com/filecoin-project/go-fil-markets v1.8.1/go.mod h1:PIPyOhoDLWT5NcciJQeK6Hes7MIeczGLNWVO/2Vy0a4= github.com/filecoin-project/go-hamt-ipld v0.1.5 h1:uoXrKbCQZ49OHpsTCkrThPNelC4W3LPEk0OrS/ytIBM= github.com/filecoin-project/go-hamt-ipld v0.1.5/go.mod h1:6Is+ONR5Cd5R6XZoCse1CWaXZc0Hdb/JeX+EQCQzX24= github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0 h1:b3UDemBYN2HNfk3KOXNuxgTTxlWi3xVvbQP0IT38fvM= @@ -328,7 +332,6 @@ github.com/filecoin-project/specs-actors v0.9.13/go.mod h1:TS1AW/7LbG+615j4NsjMK github.com/filecoin-project/specs-actors v0.9.14 h1:68PVstg2UB3ZsMLF+DKFTAs/YKsqhKWynkr0IqmVRQY= github.com/filecoin-project/specs-actors v0.9.14/go.mod h1:TS1AW/7LbG+615j4NsjMK1qlpAwaFsG9w0V2tg2gSao= github.com/filecoin-project/specs-actors/v2 v2.0.1/go.mod h1:v2NZVYinNIKA9acEMBm5wWXxqv5+frFEbekBFemYghY= -github.com/filecoin-project/specs-actors/v2 v2.3.2/go.mod h1:UuJQLoTx/HPvvWeqlIFmC/ywlOLHNe8SNQ3OunFbu2Y= github.com/filecoin-project/specs-actors/v2 v2.3.5-0.20210114162132-5b58b773f4fb/go.mod h1:LljnY2Mn2homxZsmokJZCpRuhOPxfXhvcek5gWkmqAc= github.com/filecoin-project/specs-actors/v2 v2.3.5 h1:PbT4tPlSXZ8sRgajhb4D8AOEmiaaZ+jg6tc6BBv8VQc= github.com/filecoin-project/specs-actors/v2 v2.3.5/go.mod h1:LljnY2Mn2homxZsmokJZCpRuhOPxfXhvcek5gWkmqAc= @@ -380,6 +383,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= @@ -483,8 +487,9 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -576,8 +581,8 @@ github.com/ipfs/go-bitswap v0.0.9/go.mod h1:kAPf5qgn2W2DrgAcscZ3HrM9qh4pH+X8Fkk3 github.com/ipfs/go-bitswap v0.1.0/go.mod h1:FFJEf18E9izuCqUtHxbWEvq+reg7o4CW5wSAE1wsxj0= github.com/ipfs/go-bitswap v0.1.2/go.mod h1:qxSWS4NXGs7jQ6zQvoPY3+NmOfHHG47mhkiLzBpJQIs= github.com/ipfs/go-bitswap v0.1.8/go.mod h1:TOWoxllhccevbWFUR2N7B1MTSVVge1s6XSMiCSA4MzM= -github.com/ipfs/go-bitswap v0.3.2 h1:TdKx7lpidYe2dMAKfdeNS26y6Pc/AZX/i8doI1GV210= -github.com/ipfs/go-bitswap v0.3.2/go.mod h1:AyWWfN3moBzQX0banEtfKOfbXb3ZeoOeXnZGNPV9S6w= +github.com/ipfs/go-bitswap v0.3.4 h1:AhJhRrG8xkxh6x87b4wWs+4U4y3DVB3doI8yFNqgQME= +github.com/ipfs/go-bitswap v0.3.4/go.mod h1:4T7fvNv/LmOys+21tnLzGKncMeeXUYUd1nUiJ2teMvI= github.com/ipfs/go-block-format v0.0.1/go.mod h1:DK/YYcsSUIVAFNwo/KZCdIIbpN0ROH/baNLgayt4pFc= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3 h1:r8t66QstRp/pd/or4dpnbVfXT5Gt7lOqRvC+/dDTpMc= @@ -587,8 +592,8 @@ github.com/ipfs/go-blockservice v0.0.7/go.mod h1:EOfb9k/Y878ZTRY/CH0x5+ATtaipfbR github.com/ipfs/go-blockservice v0.1.0/go.mod h1:hzmMScl1kXHg3M2BjTymbVPjv627N7sYcvYaKbop39M= github.com/ipfs/go-blockservice v0.1.3/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU= github.com/ipfs/go-blockservice v0.1.4-0.20200624145336-a978cec6e834/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU= -github.com/ipfs/go-blockservice v0.1.4 h1:Vq+MlsH8000KbbUciRyYMEw/NNP8UAGmcqKi4uWmFGA= -github.com/ipfs/go-blockservice v0.1.4/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU= +github.com/ipfs/go-blockservice v0.1.5 h1:euqZu96CCbToPyYVwVshu8ENURi8BhFd7FUFfTLi+fQ= +github.com/ipfs/go-blockservice v0.1.5/go.mod h1:yLk8lBJCBRWRqerqCSVi3cE/Dncdt3vGC/PJMVKhLTY= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= @@ -597,8 +602,10 @@ github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-cid v0.0.6-0.20200501230655-7c82f3b81c00/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= -github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/ipfs/go-cid v0.0.8-0.20210716091050-de6c03deae1c/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= +github.com/ipfs/go-cid v0.1.0 h1:YN33LQulcRHjfom/i25yoOZR4Telp1Hr/2RU3d0PnC0= +github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= github.com/ipfs/go-cidutil v0.0.2 h1:CNOboQf1t7Qp0nuNh8QMmhJs0+Q//bRL1axtCnIB1Yo= github.com/ipfs/go-cidutil v0.0.2/go.mod h1:ewllrvrxG6AMYStla3GD7Cqn+XYSLqjK0vc+086tB6s= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= @@ -641,17 +648,18 @@ github.com/ipfs/go-fs-lock v0.0.6/go.mod h1:OTR+Rj9sHiRubJh3dRhD15Juhd/+w6VPOY28 github.com/ipfs/go-graphsync v0.1.0/go.mod h1:jMXfqIEDFukLPZHqDPp8tJMbHO9Rmeb9CEGevngQbmE= github.com/ipfs/go-graphsync v0.4.2/go.mod h1:/VmbZTUdUMTbNkgzAiCEucIIAU3BkLE2cZrDCVUhyi0= github.com/ipfs/go-graphsync v0.4.3/go.mod h1:mPOwDYv128gf8gxPFgXnz4fNrSYPsWyqisJ7ych+XDY= -github.com/ipfs/go-graphsync v0.6.4/go.mod h1:5WyaeigpNdpiYQuW2vwpuecOoEfB4h747ZGEOKmAGTg= -github.com/ipfs/go-graphsync v0.6.6 h1:In7jjzvSXlrAUz4OjN41lxYf/dzkf1bVeVxLpwKMRo8= -github.com/ipfs/go-graphsync v0.6.6/go.mod h1:GdHT8JeuIZ0R4lSjFR16Oe4zPi5dXwKi9zR9ADVlcdk= +github.com/ipfs/go-graphsync v0.6.8/go.mod h1:GdHT8JeuIZ0R4lSjFR16Oe4zPi5dXwKi9zR9ADVlcdk= +github.com/ipfs/go-graphsync v0.6.9 h1:I15gVcZuqsaeaj64/SjlwiIAc9MkOgfSv0M1CgcoFRE= +github.com/ipfs/go-graphsync v0.6.9/go.mod h1:GdHT8JeuIZ0R4lSjFR16Oe4zPi5dXwKi9zR9ADVlcdk= github.com/ipfs/go-hamt-ipld v0.1.1/go.mod h1:1EZCr2v0jlCnhpa+aZ0JZYp8Tt2w16+JJOAVz17YcDk= github.com/ipfs/go-ipfs-blockstore v0.0.1/go.mod h1:d3WClOmRQKFnJ0Jz/jj/zmksX0ma1gROTlovZKBmN08= github.com/ipfs/go-ipfs-blockstore v0.1.0/go.mod h1:5aD0AvHPi7mZc6Ci1WCAhiBQu2IsfTduLl+422H6Rqw= github.com/ipfs/go-ipfs-blockstore v0.1.4/go.mod h1:Jxm3XMVjh6R17WvxFEiyKBLUGr86HgIYJW/D/MwqeYQ= github.com/ipfs/go-ipfs-blockstore v1.0.0/go.mod h1:knLVdhVU9L7CC4T+T4nvGdeUIPAXlnd9zmXfp+9MIjU= github.com/ipfs/go-ipfs-blockstore v1.0.1/go.mod h1:MGNZlHNEnR4KGgPHM3/k8lBySIOK2Ve+0KjZubKlaOE= -github.com/ipfs/go-ipfs-blockstore v1.0.3 h1:RDhK6fdg5YsonkpMuMpdvk/pRtOQlrIRIybuQfkvB2M= github.com/ipfs/go-ipfs-blockstore v1.0.3/go.mod h1:MGNZlHNEnR4KGgPHM3/k8lBySIOK2Ve+0KjZubKlaOE= +github.com/ipfs/go-ipfs-blockstore v1.0.4 h1:DZdeya9Vu4ttvlGheQPGrj6kWehXnYZRFCp9EsZQ1hI= +github.com/ipfs/go-ipfs-blockstore v1.0.4/go.mod h1:uL7/gTJ8QIZ3MtA3dWf+s1a0U3fJy2fcEZAsovpRp+w= github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk= github.com/ipfs/go-ipfs-chunker v0.0.1/go.mod h1:tWewYK0we3+rMbOh7pPFGDyypCtvGcBFymgY4rSDLAw= @@ -746,8 +754,9 @@ github.com/ipfs/go-todocounter v0.0.1/go.mod h1:l5aErvQc8qKE2r7NDMjmq5UNAvuZy0rC github.com/ipfs/go-unixfs v0.0.4/go.mod h1:eIo/p9ADu/MFOuyxzwU+Th8D6xoxU//r590vUpWyfz8= github.com/ipfs/go-unixfs v0.2.1/go.mod h1:IwAAgul1UQIcNZzKPYZWOCijryFBeCV79cNubPzol+k= github.com/ipfs/go-unixfs v0.2.2-0.20190827150610-868af2e9e5cb/go.mod h1:IwAAgul1UQIcNZzKPYZWOCijryFBeCV79cNubPzol+k= -github.com/ipfs/go-unixfs v0.2.4 h1:6NwppOXefWIyysZ4LR/qUBPvXd5//8J3jiMdvpbw6Lo= github.com/ipfs/go-unixfs v0.2.4/go.mod h1:SUdisfUjNoSDzzhGVxvCL9QO/nKdwXdr+gbMUdqcbYw= +github.com/ipfs/go-unixfs v0.2.6 h1:gq3U3T2vh8x6tXhfo3uSO3n+2z4yW0tYtNgVP/3sIyA= +github.com/ipfs/go-unixfs v0.2.6/go.mod h1:GTTzQvaZsTZARdNkkdjDKFFnBhmO3e5mIM1PkH/x4p0= github.com/ipfs/go-verifcid v0.0.1 h1:m2HI7zIuR5TFyQ1b79Da5N9dnnCP1vcu2QqawmWlK2E= github.com/ipfs/go-verifcid v0.0.1/go.mod h1:5Hrva5KBeIog4A+UpqlaIU+DEstipcJYQQZc0g37pY0= github.com/ipfs/interface-go-ipfs-core v0.2.3 h1:E6uQ+1fJjkxJWlL9lAE72a5FWeyeeNL3GitLy8+jq3Y= @@ -760,6 +769,10 @@ github.com/ipld/go-car v0.1.0/go.mod h1:RCWzaUh2i4mOEkB3W45Vc+9jnS/M6Qay5ooytiBH github.com/ipld/go-car v0.1.1-0.20200923150018-8cdef32e2da4/go.mod h1:xrMEcuSq+D1vEwl+YAXsg/JfA98XGpXDwnkIL4Aimqw= github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d h1:iphSzTuPqyDgH7WUVZsdqUnQNzYgIblsVr1zhVNA33U= github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d/go.mod h1:2Gys8L8MJ6zkh1gktTSXreY63t4UbyvNp5JaudTyxHQ= +github.com/ipld/go-car/v2 v2.0.0-beta1.0.20210721090610-5a9d1b217d25/go.mod h1:I2ACeeg6XNBe5pdh5TaR7Ambhfa7If9KXxmXgZsYENU= +github.com/ipld/go-car/v2 v2.0.2/go.mod h1:I2ACeeg6XNBe5pdh5TaR7Ambhfa7If9KXxmXgZsYENU= +github.com/ipld/go-car/v2 v2.0.3-0.20210811121346-c514a30114d7 h1:6Z0beJSZNsRY+7udoqUl4gQ/tqtrPuRvDySrlsvbqZA= +github.com/ipld/go-car/v2 v2.0.3-0.20210811121346-c514a30114d7/go.mod h1:I2ACeeg6XNBe5pdh5TaR7Ambhfa7If9KXxmXgZsYENU= github.com/ipld/go-ipld-prime v0.0.2-0.20191108012745-28a82f04c785/go.mod h1:bDDSvVz7vaK12FNvMeRYnpRFkSUPNQOiCYQezMD/P3w= github.com/ipld/go-ipld-prime v0.0.2-0.20200428162820-8b59dc292b8e/go.mod h1:uVIwe/u0H4VdKv3kaN1ck7uCb6yD9cFLS9/ELyXbsw8= github.com/ipld/go-ipld-prime v0.5.1-0.20200828233916-988837377a7f/go.mod h1:0xEgdD6MKbZ1vF0GC+YcR/C4SQCAlRuOjIJ2i0HxqzM= @@ -835,8 +848,10 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/cpuid/v2 v2.0.4 h1:g0I61F2K2DjRHz1cnxlkNSBIaePVoJIjjnHui8QHbiw= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.8 h1:bhR2mgIlno/Sfk4oUbH4sPlc83z1yGrN9bvqiq3C33I= +github.com/klauspost/cpuid/v2 v2.0.8/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20180514024734-4a0ed625a78b/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= @@ -851,8 +866,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= github.com/libp2p/go-addr-util v0.1.0 h1:acKsntI33w2bTU7tC9a0SaPimJGfSI0bFKC18ChxeVI= @@ -892,6 +905,8 @@ github.com/libp2p/go-libp2p v0.8.3/go.mod h1:EsH1A+8yoWK+L4iKcbPYu6MPluZ+CHWI9El github.com/libp2p/go-libp2p v0.9.2/go.mod h1:cunHNLDVus66Ct9iXXcjKRLdmHdFdHVe1TAnbubJQqQ= github.com/libp2p/go-libp2p v0.10.0/go.mod h1:yBJNpb+mGJdgrwbKAKrhPU0u3ogyNFTfjJ6bdM+Q/G8= github.com/libp2p/go-libp2p v0.12.0/go.mod h1:FpHZrfC1q7nA8jitvdjKBDF31hguaC676g/nT9PgQM0= +github.com/libp2p/go-libp2p v0.13.0/go.mod h1:pM0beYdACRfHO1WcJlp65WXyG2A6NqYM+t2DTVAJxMo= +github.com/libp2p/go-libp2p v0.14.0/go.mod h1:dsQrWLAoIn+GkHPN/U+yypizkHiB9tnv79Os+kSgQ4Q= github.com/libp2p/go-libp2p v0.14.2 h1:qs0ABtjjNjS+RIXT1uM7sMJEvIc0pq2nKR0VQxFXhHI= github.com/libp2p/go-libp2p v0.14.2/go.mod h1:0PQMADQEjCM2l8cSMYDpTgsb8gr6Zq7i4LUgq1mlW2E= github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052 h1:BM7aaOF7RpmNn9+9g6uTjGJ0cTzWr5j9i9IKeun2M8U= @@ -1036,8 +1051,8 @@ github.com/libp2p/go-libp2p-protocol v0.0.1/go.mod h1:Af9n4PiruirSDjHycM1QuiMi/1 github.com/libp2p/go-libp2p-protocol v0.1.0/go.mod h1:KQPHpAabB57XQxGrXCNvbL6UEXfQqUgC/1adR2Xtflk= github.com/libp2p/go-libp2p-pubsub v0.1.1/go.mod h1:ZwlKzRSe1eGvSIdU5bD7+8RZN/Uzw0t1Bp9R1znpR/Q= github.com/libp2p/go-libp2p-pubsub v0.3.2-0.20200527132641-c0712c6e92cf/go.mod h1:TxPOBuo1FPdsTjFnv+FGZbNbWYsp74Culx+4ViQpato= -github.com/libp2p/go-libp2p-pubsub v0.5.3 h1:XCn5xvgA/AKpbbaeqbomfKtQCbT9QsU39tYsVj0IndQ= -github.com/libp2p/go-libp2p-pubsub v0.5.3/go.mod h1:gVOzwebXVdSMDQBTfH8ACO5EJ4SQrvsHqCmYsCZpD0E= +github.com/libp2p/go-libp2p-pubsub v0.5.4 h1:rHl9/Xok4zX3zgi0pg0XnUj9Xj2OeXO8oTu85q2+YA8= +github.com/libp2p/go-libp2p-pubsub v0.5.4/go.mod h1:gVOzwebXVdSMDQBTfH8ACO5EJ4SQrvsHqCmYsCZpD0E= github.com/libp2p/go-libp2p-quic-transport v0.1.1/go.mod h1:wqG/jzhF3Pu2NrhJEvE+IE0NTHNXslOPn9JQzyCAxzU= github.com/libp2p/go-libp2p-quic-transport v0.5.0/go.mod h1:IEcuC5MLxvZ5KuHKjRu+dr3LjCT1Be3rcD/4d8JrX8M= github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= @@ -1070,6 +1085,7 @@ github.com/libp2p/go-libp2p-swarm v0.2.7/go.mod h1:ZSJ0Q+oq/B1JgfPHJAT2HTall+xYR github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= github.com/libp2p/go-libp2p-swarm v0.3.1/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= +github.com/libp2p/go-libp2p-swarm v0.4.0/go.mod h1:XVFcO52VoLoo0eitSxNQWYq4D6sydGOweTOAjJNraCw= github.com/libp2p/go-libp2p-swarm v0.5.0/go.mod h1:sU9i6BoHE0Ve5SKz3y9WfKrh8dUat6JknzUehFx8xW4= github.com/libp2p/go-libp2p-swarm v0.5.3 h1:hsYaD/y6+kZff1o1Mc56NcuwSg80lIphTS/zDk3mO4M= github.com/libp2p/go-libp2p-swarm v0.5.3/go.mod h1:NBn7eNW2lu568L7Ns9wdFrOhgRlkRnIDg0FLKbuu3i8= @@ -1094,6 +1110,7 @@ github.com/libp2p/go-libp2p-transport-upgrader v0.0.4/go.mod h1:RGq+tupk+oj7PzL2 github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.0/go.mod h1:J4ko0ObtZSmgn5BX5AmegP+dK3CSnU2lMCKsSq/EY0s= github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= github.com/libp2p/go-libp2p-transport-upgrader v0.4.6 h1:SHt3g0FslnqIkEWF25YOB8UCOCTpGAVvHRWQYJ+veiI= github.com/libp2p/go-libp2p-transport-upgrader v0.4.6/go.mod h1:JE0WQuQdy+uLZ5zOaI3Nw9dWGYJIA7mywEtP2lMvnyk= @@ -1216,8 +1233,8 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -1321,6 +1338,10 @@ github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/g github.com/multiformats/go-multibase v0.0.2/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multicodec v0.2.1-0.20210713081508-b421db6850ae/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= +github.com/multiformats/go-multicodec v0.2.1-0.20210714093213-b2b5bd6fe68b/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= +github.com/multiformats/go-multicodec v0.3.0 h1:tstDwfIjiHbnIjeM5Lp+pMrSeN+LCMsEwOrkPmWm03A= +github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.7/go.mod h1:XuKXPp8VHcTygube3OWZC+aZrA+H1IhmjoCDtJc7PXM= @@ -1412,6 +1433,8 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk= +github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9/go.mod h1:x3N5drFsm2uilKKuuYo6LdyD8vZAW55sH/9w+pbo1sw= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1487,6 +1510,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= @@ -1529,8 +1554,9 @@ github.com/siebenmann/go-kstat v0.0.0-20160321171754-d34789b79745/go.mod h1:G81a github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= @@ -1568,8 +1594,6 @@ github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25 h1:7z3LSn867ex6 github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -1623,6 +1647,8 @@ github.com/weaveworks/promrus v1.2.0/go.mod h1:SaE82+OJ91yqjrE1rsvBWVzNZKcHYFtMU github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc/go.mod h1:r45hJU7yEoA81k6MWNhpMj/kms0n14dkzkxYHoB96UM= github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba h1:X4n8JG2e2biEZZXdBKt9HX7DN3bYGFUqljqqy0DqgnY= github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba/go.mod h1:CHQnYnQUEPydYCwuy8lmTHfGmdw9TKrhWV0xLx8l0oM= +github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 h1:5HZfQkwe0mIfyDmc1Em5GqlNRzcdtlv4HTNmdpt7XH0= +github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11/go.mod h1:Wlo/SzPmxVp6vXpGt/zaXhHH0fn4IxgqZc82aKg6bpQ= github.com/whyrusleeping/cbor-gen v0.0.0-20191216205031-b047b6acb3c0/go.mod h1:xdlJQaiqipF0HW+Mzpg7XRM3fWbGvfgFlcppuvlkIvY= github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= github.com/whyrusleeping/cbor-gen v0.0.0-20200402171437-3d27c146c105/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= @@ -1636,8 +1662,8 @@ github.com/whyrusleeping/cbor-gen v0.0.0-20200810223238-211df3b9e24c/go.mod h1:f github.com/whyrusleeping/cbor-gen v0.0.0-20200812213548-958ddffe352c/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20200826160007-0b9f6c5fb163/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20210118024343-169e9d70c0c2/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= -github.com/whyrusleeping/cbor-gen v0.0.0-20210219115102-f37d292932f2 h1:bsUlNhdmbtlfdLVXAVfuvKQ01RnWAM09TVrJkI7NZs4= -github.com/whyrusleeping/cbor-gen v0.0.0-20210219115102-f37d292932f2/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= +github.com/whyrusleeping/cbor-gen v0.0.0-20210713220151-be142a5ae1a8 h1:TEv7MId88TyIqIUL4hbf9otOookIolMxlEbN0ro671Y= +github.com/whyrusleeping/cbor-gen v0.0.0-20210713220151-be142a5ae1a8/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= github.com/whyrusleeping/go-ctrlnet v0.0.0-20180313164037-f564fbbdaa95/go.mod h1:SJqKCCPXRfBFCwXjfNT/skfsceF7+MBFLI2OrvuRA7g= @@ -1703,6 +1729,11 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -1721,8 +1752,9 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= @@ -1769,19 +1801,25 @@ golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200513190911-00229845015e h1:rMqLP+9XLy+LdbCXHjJHAmTfXCr93W7oruWA6Hq1Alc= golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20210615023648-acb5c1269671/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= +golang.org/x/exp v0.0.0-20210714144626-1041f73d31d8/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= +golang.org/x/exp v0.0.0-20210715201039-d37aa40e8013 h1:Jp57DBw4K7mimZNA3F9f7CndVcUt4kJjmyJf2rzJHoI= +golang.org/x/exp v0.0.0-20210715201039-d37aa40e8013/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1798,10 +1836,12 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= @@ -1951,6 +1991,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1960,8 +2001,11 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q= golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= @@ -2007,11 +2051,11 @@ golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -2022,6 +2066,7 @@ golang.org/x/tools v0.0.0-20200827010519-17fd2f27a9e3/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20201112185108-eeaa07dd7696/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -2137,8 +2182,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= @@ -2148,8 +2194,8 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= modernc.org/cc v1.0.0 h1:nPibNuDEx6tvYrUAtvDTTw98rx5juGsa5zuDnKwEEQQ= diff --git a/itests/api_test.go b/itests/api_test.go index ba77701a2..6c25ef181 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -186,7 +186,7 @@ func (ts *apiSuite) testNonGenesisMiner(t *testing.T) { var newMiner kit.TestMiner ens.Miner(&newMiner, full, kit.OwnerAddr(full.DefaultKey), - kit.ProofType(abi.RegisteredSealProof_StackedDrg2KiBV1), // we're using v0 actors with old proofs. + kit.ProofType(abi.RegisteredSealProof_StackedDrg2KiBV1_1), kit.WithAllSubsystems(), ).Start().InterconnectAll() diff --git a/itests/ccupgrade_test.go b/itests/ccupgrade_test.go index dfd0144f2..c5b380835 100644 --- a/itests/ccupgrade_test.go +++ b/itests/ccupgrade_test.go @@ -17,10 +17,9 @@ func TestCCUpgrade(t *testing.T) { kit.QuietMiningLogs() for _, height := range []abi.ChainEpoch{ - -1, // before - 162, // while sealing - 530, // after upgrade deal - 5000, // after + -1, // before + 162, // while sealing + 560, // after upgrade deal } { height := height // make linters happy by copying t.Run(fmt.Sprintf("upgrade-%d", height), func(t *testing.T) { @@ -33,8 +32,7 @@ func runTestCCUpgrade(t *testing.T, upgradeHeight abi.ChainEpoch) { ctx := context.Background() blockTime := 5 * time.Millisecond - opts := kit.ConstructorOpts(kit.LatestActorsAt(upgradeHeight)) - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.LatestActorsAt(upgradeHeight)) ens.InterconnectAll().BeginMining(blockTime) maddr, err := miner.ActorAddress(ctx) diff --git a/itests/deadlines_test.go b/itests/deadlines_test.go index 19b0a10dc..c698f1154 100644 --- a/itests/deadlines_test.go +++ b/itests/deadlines_test.go @@ -75,9 +75,8 @@ func TestDeadlineToggling(t *testing.T) { minerD kit.TestMiner minerE kit.TestMiner ) - opts := []kit.NodeOpt{kit.ConstructorOpts(kit.NetworkUpgradeAt(network.Version12, upgradeH))} - opts = append(opts, kit.WithAllSubsystems()) - ens := kit.NewEnsemble(t, kit.MockProofs()). + opts := []kit.NodeOpt{kit.WithAllSubsystems()} + ens := kit.NewEnsemble(t, kit.MockProofs(), kit.TurboUpgradeAt(upgradeH)). FullNode(&client, opts...). Miner(&minerA, &client, opts...). Start(). diff --git a/itests/deals_concurrent_test.go b/itests/deals_concurrent_test.go index 69e1b4e7f..3fd554c62 100644 --- a/itests/deals_concurrent_test.go +++ b/itests/deals_concurrent_test.go @@ -28,6 +28,8 @@ func TestDealWithMarketAndMinerNode(t *testing.T) { t.Skip("skipping test in short mode") } + t.Skip("skipping due to flakiness: see #6956") + kit.QuietMiningLogs() oldDelay := policy.GetPreCommitChallengeDelay() @@ -106,7 +108,7 @@ func TestDealCyclesConcurrent(t *testing.T) { ns := fmt.Sprintf("%d", n) t.Run(ns+"-fastretrieval-CAR", func(t *testing.T) { runTest(t, n, true, true) }) t.Run(ns+"-fastretrieval-NoCAR", func(t *testing.T) { runTest(t, n, true, false) }) - t.Run(ns+"-stdretrieval-CAR", func(t *testing.T) { runTest(t, n, true, false) }) + t.Run(ns+"-stdretrieval-CAR", func(t *testing.T) { runTest(t, n, false, true) }) t.Run(ns+"-stdretrieval-NoCAR", func(t *testing.T) { runTest(t, n, false, false) }) } } diff --git a/itests/deals_publish_test.go b/itests/deals_publish_test.go index 6cefde6b9..85a358f06 100644 --- a/itests/deals_publish_test.go +++ b/itests/deals_publish_test.go @@ -49,7 +49,6 @@ func TestPublishDealsBatching(t *testing.T) { DisableOwnerFallback: true, DisableWorkerFallback: true, })), - kit.LatestActorsAt(-1), ) client, miner, ens := kit.EnsembleMinimal(t, kit.Account(publisherKey, types.FromFil(10)), kit.MockProofs(), kit.ConstructorOpts(opts)) diff --git a/itests/deals_test.go b/itests/deals_test.go index a461586a1..4ad97e969 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -37,4 +37,9 @@ func TestDealsWithSealingAndRPC(t *testing.T) { dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1, FastRetrieval: true}) dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1, FastRetrieval: true}) }) + + t.Run("stdretrieval-carv1", func(t *testing.T) { + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1, UseCARFileForStorageDeal: true}) + }) + } diff --git a/itests/kit/client.go b/itests/kit/client.go index bd81e0c04..c9f8946ec 100644 --- a/itests/kit/client.go +++ b/itests/kit/client.go @@ -26,7 +26,7 @@ func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode *TestFullNode) defer cancel() // Create mock CLI - mockCLI := NewMockCLI(ctx, t, cmds) + mockCLI := NewMockCLI(ctx, t, cmds, api.NodeFull) clientCLI := mockCLI.Client(clientNode.ListenAddr) // Get the Miner address diff --git a/itests/kit/deals.go b/itests/kit/deals.go index 0832447f2..7d78d8c02 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -33,9 +33,10 @@ type DealHarness struct { } type MakeFullDealParams struct { - Rseed int - FastRet bool - StartEpoch abi.ChainEpoch + Rseed int + FastRet bool + StartEpoch abi.ChainEpoch + UseCARFileForStorageDeal bool // SuspendUntilCryptoeconStable suspends deal-making, until cryptoecon // parameters are stabilised. This affects projected collateral, and tests @@ -78,7 +79,11 @@ func NewDealHarness(t *testing.T, client *TestFullNode, main *TestMiner, market // // TODO: convert input parameters to struct, and add size as an input param. func (dh *DealHarness) MakeOnlineDeal(ctx context.Context, params MakeFullDealParams) (deal *cid.Cid, res *api.ImportRes, path string) { - res, path = dh.client.CreateImportFile(ctx, params.Rseed, 0) + if params.UseCARFileForStorageDeal { + res, _, path = dh.client.ClientImportCARFile(ctx, params.Rseed, 200) + } else { + res, path = dh.client.CreateImportFile(ctx, params.Rseed, 0) + } dh.t.Logf("FILE CID: %s", res.Root) @@ -284,10 +289,11 @@ func (dh *DealHarness) ExtractFileFromCAR(ctx context.Context, file *os.File) (o } type RunConcurrentDealsOpts struct { - N int - FastRetrieval bool - CarExport bool - StartEpoch abi.ChainEpoch + N int + FastRetrieval bool + CarExport bool + StartEpoch abi.ChainEpoch + UseCARFileForStorageDeal bool } func (dh *DealHarness) RunConcurrentDeals(opts RunConcurrentDealsOpts) { @@ -307,9 +313,10 @@ func (dh *DealHarness) RunConcurrentDeals(opts RunConcurrentDealsOpts) { dh.t.Logf("making storage deal %d/%d", i, opts.N) deal, res, inPath := dh.MakeOnlineDeal(context.Background(), MakeFullDealParams{ - Rseed: 5 + i, - FastRet: opts.FastRetrieval, - StartEpoch: opts.StartEpoch, + Rseed: 5 + i, + FastRet: opts.FastRetrieval, + StartEpoch: opts.StartEpoch, + UseCARFileForStorageDeal: opts.UseCARFileForStorageDeal, }) dh.t.Logf("retrieving deal %d/%d", i, opts.N) diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index 77a743d0c..672efd6be 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -27,6 +27,7 @@ import ( "github.com/filecoin-project/lotus/chain/gen" genesis2 "github.com/filecoin-project/lotus/chain/gen/genesis" "github.com/filecoin-project/lotus/chain/messagepool" + "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet" "github.com/filecoin-project/lotus/cmd/lotus-seed/seed" @@ -114,6 +115,7 @@ type Ensemble struct { bms map[*TestMiner]*BlockMiner } genesis struct { + version network.Version miners []genesis.Miner accounts []genesis.Actor } @@ -130,6 +132,12 @@ func NewEnsemble(t *testing.T, opts ...EnsembleOpt) *Ensemble { n := &Ensemble{t: t, options: &options} n.active.bms = make(map[*TestMiner]*BlockMiner) + for _, up := range options.upgradeSchedule { + if up.Height < 0 { + n.genesis.version = up.Network + } + } + // add accounts from ensemble options to genesis. for _, acc := range options.accounts { n.genesis.accounts = append(n.genesis.accounts, genesis.Actor{ @@ -173,7 +181,7 @@ func (n *Ensemble) FullNode(full *TestFullNode, opts ...NodeOpt) *Ensemble { // Miner enrolls a new miner, using the provided full node for chain // interactions. -func (n *Ensemble) Miner(miner *TestMiner, full *TestFullNode, opts ...NodeOpt) *Ensemble { +func (n *Ensemble) Miner(minerNode *TestMiner, full *TestFullNode, opts ...NodeOpt) *Ensemble { require.NotNil(n.t, full, "full node required when instantiating miner") options := DefaultNodeOpts @@ -208,11 +216,15 @@ func (n *Ensemble) Miner(miner *TestMiner, full *TestFullNode, opts ...NodeOpt) genm *genesis.Miner ) + // Default 2KiB sector for the network version + proofType, err := miner.SealProofTypeFromSectorSize(2<<10, n.genesis.version) + require.NoError(n.t, err) + // create the preseal commitment. if n.options.mockProofs { - genm, k, err = mockstorage.PreSeal(abi.RegisteredSealProof_StackedDrg2KiBV1, actorAddr, sectors) + genm, k, err = mockstorage.PreSeal(proofType, actorAddr, sectors) } else { - genm, k, err = seed.PreSeal(actorAddr, abi.RegisteredSealProof_StackedDrg2KiBV1, 0, sectors, tdir, []byte("make genesis mem random"), nil, true) + genm, k, err = seed.PreSeal(actorAddr, proofType, 0, sectors, tdir, []byte("make genesis mem random"), nil, true) } require.NoError(n.t, err) @@ -237,7 +249,7 @@ func (n *Ensemble) Miner(miner *TestMiner, full *TestFullNode, opts ...NodeOpt) rl, err := net.Listen("tcp", "127.0.0.1:") require.NoError(n.t, err) - *miner = TestMiner{ + *minerNode = TestMiner{ t: n.t, ActorAddr: actorAddr, OwnerKey: ownerKey, @@ -247,10 +259,10 @@ func (n *Ensemble) Miner(miner *TestMiner, full *TestFullNode, opts ...NodeOpt) RemoteListener: rl, } - miner.Libp2p.PeerID = peerId - miner.Libp2p.PrivKey = privkey + minerNode.Libp2p.PeerID = peerId + minerNode.Libp2p.PrivKey = privkey - n.inactive.miners = append(n.inactive.miners, miner) + n.inactive.miners = append(n.inactive.miners, minerNode) return n } @@ -283,6 +295,9 @@ func (n *Ensemble) Start() *Ensemble { // so that we subscribe to pubsub topics immediately node.Override(new(dtypes.Bootstrapper), dtypes.Bootstrapper(true)), + + // upgrades + node.Override(new(stmgr.UpgradeSchedule), n.options.upgradeSchedule), } // append any node builder options. @@ -510,6 +525,9 @@ func (n *Ensemble) Start() *Ensemble { scfg.Storage.ResourceFiltering = sectorstorage.ResourceFilteringDisabled return scfg.Storage }), + + // upgrades + node.Override(new(stmgr.UpgradeSchedule), n.options.upgradeSchedule), } // append any node builder options. @@ -693,7 +711,7 @@ func (n *Ensemble) generateGenesis() *genesis.Template { } templ := &genesis.Template{ - NetworkVersion: network.Version0, + NetworkVersion: n.genesis.version, Accounts: n.genesis.accounts, Miners: n.genesis.miners, NetworkName: "test", diff --git a/itests/kit/ensemble_opts.go b/itests/kit/ensemble_opts.go index 440362ed1..9db64d3bc 100644 --- a/itests/kit/ensemble_opts.go +++ b/itests/kit/ensemble_opts.go @@ -4,6 +4,8 @@ import ( "time" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/wallet" ) @@ -19,10 +21,16 @@ type ensembleOpts struct { verifiedRoot genesisAccount accounts []genesisAccount mockProofs bool + + upgradeSchedule stmgr.UpgradeSchedule } var DefaultEnsembleOpts = ensembleOpts{ pastOffset: 10000000 * time.Second, // time sufficiently in the past to trigger catch-up mining. + upgradeSchedule: stmgr.UpgradeSchedule{{ + Height: -1, + Network: build.NewestNetworkVersion, + }}, } // MockProofs activates mock proofs for the entire ensemble. diff --git a/itests/kit/ensemble_opts_nv.go b/itests/kit/ensemble_opts_nv.go new file mode 100644 index 000000000..651c3f324 --- /dev/null +++ b/itests/kit/ensemble_opts_nv.go @@ -0,0 +1,58 @@ +package kit + +import ( + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/chain/stmgr" +) + +func UpgradeSchedule(upgrades ...stmgr.Upgrade) EnsembleOpt { + return func(opts *ensembleOpts) error { + opts.upgradeSchedule = upgrades + return opts.upgradeSchedule.Validate() + } +} + +// GenesisNetworkVersion sets the network version of genesis. +func GenesisNetworkVersion(nv network.Version) EnsembleOpt { + return UpgradeSchedule(stmgr.Upgrade{ + Network: nv, + Height: -1, + }) +} + +func SDRUpgradeAt(calico, persian abi.ChainEpoch) EnsembleOpt { + return UpgradeSchedule(stmgr.Upgrade{ + Network: network.Version6, + Height: -1, + }, stmgr.Upgrade{ + Network: network.Version7, + Height: calico, + Migration: stmgr.UpgradeCalico, + }, stmgr.Upgrade{ + Network: network.Version8, + Height: persian, + }) +} + +func LatestActorsAt(upgradeHeight abi.ChainEpoch) EnsembleOpt { + return UpgradeSchedule(stmgr.Upgrade{ + Network: network.Version12, + Height: -1, + }, stmgr.Upgrade{ + Network: network.Version13, + Height: upgradeHeight, + Migration: stmgr.UpgradeActorsV5, + }) +} + +func TurboUpgradeAt(upgradeHeight abi.ChainEpoch) EnsembleOpt { + return UpgradeSchedule(stmgr.Upgrade{ + Network: network.Version11, + Height: -1, + }, stmgr.Upgrade{ + Network: network.Version12, + Height: upgradeHeight, + Migration: stmgr.UpgradeActorsV4, + }) +} diff --git a/itests/kit/files.go b/itests/kit/files.go index 48592b518..9babac941 100644 --- a/itests/kit/files.go +++ b/itests/kit/files.go @@ -1,17 +1,38 @@ package kit import ( + "bufio" "bytes" + "context" "io" "math/rand" "os" "testing" + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" + cidutil "github.com/ipfs/go-cidutil" + ds "github.com/ipfs/go-datastore" + dssync "github.com/ipfs/go-datastore/sync" + bstore "github.com/ipfs/go-ipfs-blockstore" + chunk "github.com/ipfs/go-ipfs-chunker" + offline "github.com/ipfs/go-ipfs-exchange-offline" + files "github.com/ipfs/go-ipfs-files" + ipldformat "github.com/ipfs/go-ipld-format" + "github.com/ipfs/go-merkledag" + "github.com/ipfs/go-unixfs/importer/balanced" + ihelper "github.com/ipfs/go-unixfs/importer/helpers" + "github.com/ipld/go-car" "github.com/minio/blake2b-simd" + mh "github.com/multiformats/go-multihash" "github.com/stretchr/testify/require" ) +const unixfsChunkSize uint64 = 1 << 10 + +var defaultHashFunction = uint64(mh.BLAKE2B_MIN + 31) + // CreateRandomFile creates a random file with the provided seed and the // provided size. func CreateRandomFile(t *testing.T, rseed, size int) (path string) { @@ -31,6 +52,79 @@ func CreateRandomFile(t *testing.T, rseed, size int) (path string) { return file.Name() } +// CreateRandomFile creates a normal file with the provided seed and the +// provided size and then transforms it to a CARv1 file and returns it. +func CreateRandomCARv1(t *testing.T, rseed, size int) (carV1FilePath string, origFilePath string) { + ctx := context.Background() + if size == 0 { + size = 1600 + } + + source := io.LimitReader(rand.New(rand.NewSource(int64(rseed))), int64(size)) + + file, err := os.CreateTemp(t.TempDir(), "sourcefile.dat") + require.NoError(t, err) + + n, err := io.Copy(file, source) + require.NoError(t, err) + require.EqualValues(t, n, size) + + // + _, err = file.Seek(0, io.SeekStart) + require.NoError(t, err) + bs := bstore.NewBlockstore(dssync.MutexWrap(ds.NewMapDatastore())) + dagSvc := merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs))) + + root := writeUnixfsDAG(ctx, t, file, dagSvc) + + // create a CARv1 file from the DAG + tmp, err := os.CreateTemp(t.TempDir(), "randcarv1") + require.NoError(t, err) + require.NoError(t, car.WriteCar(ctx, dagSvc, []cid.Cid{root}, tmp)) + _, err = tmp.Seek(0, io.SeekStart) + require.NoError(t, err) + hd, _, err := car.ReadHeader(bufio.NewReader(tmp)) + require.NoError(t, err) + require.EqualValues(t, 1, hd.Version) + require.Len(t, hd.Roots, 1) + require.NoError(t, tmp.Close()) + + return tmp.Name(), file.Name() +} + +func writeUnixfsDAG(ctx context.Context, t *testing.T, rd io.Reader, dag ipldformat.DAGService) cid.Cid { + rpf := files.NewReaderFile(rd) + + // generate the dag and get the root + // import to UnixFS + prefix, err := merkledag.PrefixForCidVersion(1) + require.NoError(t, err) + prefix.MhType = defaultHashFunction + + bufferedDS := ipldformat.NewBufferedDAG(ctx, dag) + params := ihelper.DagBuilderParams{ + Maxlinks: 1024, + RawLeaves: true, + CidBuilder: cidutil.InlineBuilder{ + Builder: prefix, + Limit: 126, + }, + Dagserv: bufferedDS, + } + + db, err := params.New(chunk.NewSizeSplitter(rpf, int64(unixfsChunkSize))) + require.NoError(t, err) + + nd, err := balanced.Layout(db) + require.NoError(t, err) + require.NotEqualValues(t, cid.Undef, nd.Cid()) + + err = bufferedDS.Commit() + require.NoError(t, err) + require.NoError(t, rpf.Close()) + return nd.Cid() +} + // AssertFilesEqual compares two files by blake2b hash equality and // fails the test if unequal. func AssertFilesEqual(t *testing.T, left, right string) { diff --git a/itests/kit/mockcli.go b/itests/kit/mockcli.go index c0f218920..5a09a9af8 100644 --- a/itests/kit/mockcli.go +++ b/itests/kit/mockcli.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/filecoin-project/lotus/api" "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" lcli "github.com/urfave/cli/v2" @@ -19,7 +20,7 @@ type MockCLI struct { out *bytes.Buffer } -func NewMockCLI(ctx context.Context, t *testing.T, cmds []*lcli.Command) *MockCLI { +func NewMockCLI(ctx context.Context, t *testing.T, cmds []*lcli.Command, nodeType api.NodeType) *MockCLI { // Create a CLI App with an --api-url flag so that we can specify which node // the command should be executed against app := &lcli.App{ @@ -31,6 +32,8 @@ func NewMockCLI(ctx context.Context, t *testing.T, cmds []*lcli.Command) *MockCL }, Commands: cmds, } + // Set node type + api.RunningNodeType = nodeType var out bytes.Buffer app.Writer = &out diff --git a/itests/kit/node_full.go b/itests/kit/node_full.go index 83586e188..b606db8f4 100644 --- a/itests/kit/node_full.go +++ b/itests/kit/node_full.go @@ -28,6 +28,13 @@ type TestFullNode struct { options nodeOpts } +func (f *TestFullNode) ClientImportCARFile(ctx context.Context, rseed int, size int) (res *api.ImportRes, carv1FilePath string, origFilePath string) { + carv1FilePath, origFilePath = CreateRandomCARv1(f.t, rseed, size) + res, err := f.ClientImport(ctx, api.FileRef{Path: carv1FilePath, IsCAR: true}) + require.NoError(f.t, err) + return res, carv1FilePath, origFilePath +} + // CreateImportFile creates a random file with the specified seed and size, and // imports it into the full node. func (f *TestFullNode) CreateImportFile(ctx context.Context, rseed int, size int) (res *api.ImportRes, path string) { diff --git a/itests/kit/node_opts_nv.go b/itests/kit/node_opts_nv.go deleted file mode 100644 index d4c84b4f1..000000000 --- a/itests/kit/node_opts_nv.go +++ /dev/null @@ -1,90 +0,0 @@ -package kit - -import ( - "context" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/network" - "github.com/filecoin-project/lotus/chain/stmgr" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/node" - "github.com/ipfs/go-cid" -) - -// DefaultTestUpgradeSchedule -var DefaultTestUpgradeSchedule = stmgr.UpgradeSchedule{{ - Network: network.Version9, - Height: 1, - Migration: stmgr.UpgradeActorsV2, -}, { - Network: network.Version10, - Height: 2, - Migration: stmgr.UpgradeActorsV3, -}, { - Network: network.Version12, - Height: 3, - Migration: stmgr.UpgradeActorsV4, -}, { - Network: network.Version13, - Height: 4, - Migration: stmgr.UpgradeActorsV5, -}} - -func LatestActorsAt(upgradeHeight abi.ChainEpoch) node.Option { - // Attention: Update this when introducing new actor versions or your tests will be sad - return NetworkUpgradeAt(network.Version13, upgradeHeight) -} - -// InstantaneousNetworkVersion starts the network instantaneously at the -// specified version in height 1. -func InstantaneousNetworkVersion(version network.Version) node.Option { - // composes all migration functions - var mf stmgr.MigrationFunc = func(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, oldState cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (newState cid.Cid, err error) { - var state = oldState - for _, u := range DefaultTestUpgradeSchedule { - if u.Network > version { - break - } - state, err = u.Migration(ctx, sm, cache, cb, state, height, ts) - if err != nil { - return cid.Undef, err - } - } - return state, nil - } - return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{ - {Network: version, Height: 1, Migration: mf}, - }) -} - -func NetworkUpgradeAt(version network.Version, upgradeHeight abi.ChainEpoch) node.Option { - schedule := stmgr.UpgradeSchedule{} - for _, upgrade := range DefaultTestUpgradeSchedule { - if upgrade.Network > version { - break - } - - schedule = append(schedule, upgrade) - } - - if upgradeHeight > 0 { - schedule[len(schedule)-1].Height = upgradeHeight - } - - return node.Override(new(stmgr.UpgradeSchedule), schedule) -} - -func SDRUpgradeAt(calico, persian abi.ChainEpoch) node.Option { - return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ - Network: network.Version6, - Height: 1, - Migration: stmgr.UpgradeActorsV2, - }, { - Network: network.Version7, - Height: calico, - Migration: stmgr.UpgradeCalico, - }, { - Network: network.Version8, - Height: persian, - }}) -} diff --git a/itests/multisig/suite.go b/itests/multisig/suite.go index 86a8ab738..55400a99c 100644 --- a/itests/multisig/suite.go +++ b/itests/multisig/suite.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/itests/kit" @@ -17,7 +18,7 @@ import ( func RunMultisigTests(t *testing.T, client *kit.TestFullNode) { // Create mock CLI ctx := context.Background() - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) + mockCLI := kit.NewMockCLI(ctx, t, cli.Commands, api.NodeFull) clientCLI := mockCLI.Client(client.ListenAddr) // Create some wallets on the node to use for testing multisig diff --git a/itests/paych_cli_test.go b/itests/paych_cli_test.go index 82955e6c1..17e7bcbf6 100644 --- a/itests/paych_cli_test.go +++ b/itests/paych_cli_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/itests/kit" @@ -42,7 +43,7 @@ func TestPaymentChannelsBasic(t *testing.T) { creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) + mockCLI := kit.NewMockCLI(ctx, t, cli.Commands, api.NodeFull) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) receiverCLI := mockCLI.Client(paymentReceiver.ListenAddr) @@ -98,7 +99,7 @@ func TestPaymentChannelStatus(t *testing.T) { creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) + mockCLI := kit.NewMockCLI(ctx, t, cli.Commands, api.NodeFull) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) // creator: paych status-by-from-to @@ -178,7 +179,7 @@ func TestPaymentChannelVouchers(t *testing.T) { creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) + mockCLI := kit.NewMockCLI(ctx, t, cli.Commands, api.NodeFull) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) receiverCLI := mockCLI.Client(paymentReceiver.ListenAddr) @@ -310,7 +311,7 @@ func TestPaymentChannelVoucherCreateShortfall(t *testing.T) { creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) + mockCLI := kit.NewMockCLI(ctx, t, cli.Commands, api.NodeFull) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) // creator: paych add-funds diff --git a/itests/sdr_upgrade_test.go b/itests/sdr_upgrade_test.go index 3aa685b09..f4cefd67c 100644 --- a/itests/sdr_upgrade_test.go +++ b/itests/sdr_upgrade_test.go @@ -30,8 +30,10 @@ func TestSDRUpgrade(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - opts := kit.ConstructorOpts(kit.SDRUpgradeAt(500, 1000)) - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + client, miner, ens := kit.EnsembleMinimal(t, + kit.MockProofs(), + kit.SDRUpgradeAt(500, 1000), + ) ens.InterconnectAll() build.Clock.Sleep(time.Second) diff --git a/itests/sector_miner_collateral_test.go b/itests/sector_miner_collateral_test.go index 8e7525dba..891356ef1 100644 --- a/itests/sector_miner_collateral_test.go +++ b/itests/sector_miner_collateral_test.go @@ -30,7 +30,6 @@ func TestMinerBalanceCollateral(t *testing.T) { defer cancel() opts := kit.ConstructorOpts( - kit.LatestActorsAt(-1), node.ApplyIf(node.IsType(repo.StorageMiner), node.Override(new(dtypes.GetSealingConfigFunc), func() (dtypes.GetSealingConfigFunc, error) { return func() (sealiface.Config, error) { return sealiface.Config{ @@ -59,6 +58,7 @@ func TestMinerBalanceCollateral(t *testing.T) { })), ) full, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blockTime) full.WaitTillChain(ctx, kit.HeightAtLeast(10)) diff --git a/itests/sector_pledge_test.go b/itests/sector_pledge_test.go index d911dcb68..a32eb958f 100644 --- a/itests/sector_pledge_test.go +++ b/itests/sector_pledge_test.go @@ -7,12 +7,18 @@ import ( "testing" "time" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/lotus/itests/kit" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + "github.com/filecoin-project/lotus/node/impl" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" ) func TestPledgeSectors(t *testing.T) { @@ -54,8 +60,7 @@ func TestPledgeBatching(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - opts := kit.ConstructorOpts(kit.LatestActorsAt(-1)) - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) ens.InterconnectAll().BeginMining(blockTime) client.WaitTillChain(ctx, kit.HeightAtLeast(10)) @@ -104,6 +109,78 @@ func TestPledgeBatching(t *testing.T) { }) } +func TestPledgeMaxBatching(t *testing.T) { + blockTime := 50 * time.Millisecond + + runTest := func(t *testing.T) { + nSectors := miner5.MaxAggregatedSectors + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _, full, miner, ens := kit.EnsembleTwoOne(t, kit.MockProofs()) + ens.InterconnectAll().BeginMining(blockTime) + m, ok := miner.StorageMiner.(*impl.StorageMinerAPI) + require.True(t, ok) + cfg, err := m.GetSealingConfigFunc() + require.NoError(t, err) + cfg.MinCommitBatch = miner5.MaxAggregatedSectors + require.NoError(t, m.SetSealingConfigFunc(cfg)) + + toCheck := miner.StartPledge(ctx, nSectors, 0, nil) + var lastSectorNo abi.SectorNumber + + for len(toCheck) > 0 { + states := map[api.SectorState]int{} + + for n := range toCheck { + lastSectorNo = n + st, err := miner.SectorsStatus(ctx, n, false) + require.NoError(t, err) + states[st.State]++ + if st.State == api.SectorState(sealing.Proving) { + delete(toCheck, n) + } + if strings.Contains(string(st.State), "Fail") { + t.Fatal("sector in a failed state", st.State) + } + } + if states[api.SectorState(sealing.SubmitPreCommitBatch)] == nSectors || + (states[api.SectorState(sealing.SubmitPreCommitBatch)] > 0 && states[api.SectorState(sealing.PreCommit1)] == 0 && states[api.SectorState(sealing.PreCommit2)] == 0) { + pcb, err := miner.SectorPreCommitFlush(ctx) + require.NoError(t, err) + if pcb != nil { + fmt.Printf("PRECOMMIT BATCH: %+v\n", pcb) + } + } + + if states[api.SectorState(sealing.SubmitCommitAggregate)] == nSectors { + cb, err := miner.SectorCommitFlush(ctx) + require.NoError(t, err) + if cb != nil { + fmt.Printf("COMMIT BATCH: %+v\n", cb) + } + } + + build.Clock.Sleep(100 * time.Millisecond) + fmt.Printf("WaitSeal: %d %+v\n", len(toCheck), states) + } + + // Wait for flushed ProveCommitAggregate to land on chain + st, err := miner.SectorsStatus(ctx, lastSectorNo, false) + require.NoError(t, err) + for st.State == api.SectorState(sealing.CommitAggregateWait) { + build.Clock.Sleep(100 * time.Millisecond) + } + + // Ensure that max aggregate message has propagated to the other node by checking current state + sectorInfosAfter, err := full.StateMinerSectors(ctx, miner.ActorAddr, nil, types.EmptyTSK) + require.NoError(t, err) + assert.Equal(t, miner5.MaxAggregatedSectors+kit.DefaultPresealsPerBootstrapMiner, len(sectorInfosAfter)) + } + + t.Run("Force max prove commit aggregate size", runTest) +} + func TestPledgeBeforeNv13(t *testing.T) { blocktime := 50 * time.Millisecond @@ -111,8 +188,8 @@ func TestPledgeBeforeNv13(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - opts := kit.ConstructorOpts(kit.LatestActorsAt(1000000000)) - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), + kit.GenesisNetworkVersion(network.Version12)) ens.InterconnectAll().BeginMining(blocktime) client.WaitTillChain(ctx, kit.HeightAtLeast(10)) diff --git a/itests/sector_terminate_test.go b/itests/sector_terminate_test.go index 2fb4ef0f5..2a3143a0a 100644 --- a/itests/sector_terminate_test.go +++ b/itests/sector_terminate_test.go @@ -24,8 +24,7 @@ func TestTerminate(t *testing.T) { ctx = context.Background() ) - opts := kit.ConstructorOpts(kit.LatestActorsAt(-1)) - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.PresealSectors(nSectors), opts) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.PresealSectors(nSectors)) ens.InterconnectAll().BeginMining(blocktime) maddr, err := miner.ActorAddress(ctx) @@ -50,7 +49,7 @@ func TestTerminate(t *testing.T) { di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) require.NoError(t, err) - waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 20 // 20 is some slack for the proof to be submitted + applied + waitUntil := di.Open + di.WPoStProvingPeriod t.Logf("End for head.Height > %d", waitUntil) ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) @@ -62,7 +61,7 @@ func TestTerminate(t *testing.T) { p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) require.Equal(t, p.MinerPower, p.TotalPower) - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(nSectors))) + require.Equal(t, types.NewInt(uint64(ssz)*uint64(nSectors)), p.MinerPower.RawBytePower) t.Log("Terminate a sector") diff --git a/itests/tape_test.go b/itests/tape_test.go index 08970152f..c6728b834 100644 --- a/itests/tape_test.go +++ b/itests/tape_test.go @@ -8,10 +8,8 @@ import ( "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/stmgr" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/node" "github.com/stretchr/testify/require" ) @@ -30,20 +28,12 @@ func testTapeFix(t *testing.T, blocktime time.Duration, after bool) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - upgradeSchedule := stmgr.UpgradeSchedule{{ - Network: build.ActorUpgradeNetworkVersion, - Height: 1, - Migration: stmgr.UpgradeActorsV2, - }} + networkVersion := network.Version4 if after { - upgradeSchedule = append(upgradeSchedule, stmgr.Upgrade{ - Network: network.Version5, - Height: 2, - }) + networkVersion = network.Version5 } - nopts := kit.ConstructorOpts(node.Override(new(stmgr.UpgradeSchedule), upgradeSchedule)) - _, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), nopts) + _, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.GenesisNetworkVersion(networkVersion)) ens.InterconnectAll().BeginMining(blocktime) sid, err := miner.PledgeSector(ctx) diff --git a/itests/verifreg_test.go b/itests/verifreg_test.go index 28a72263e..80a21b0a0 100644 --- a/itests/verifreg_test.go +++ b/itests/verifreg_test.go @@ -42,7 +42,7 @@ func TestVerifiedClientTopUp(t *testing.T) { node, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.RootVerifier(rootKey, abi.NewTokenAmount(bal.Int64())), kit.Account(verifierKey, abi.NewTokenAmount(bal.Int64())), // assign some balance to the verifier so they can send an AddClient message. - kit.ConstructorOpts(kit.InstantaneousNetworkVersion(nv))) + kit.GenesisNetworkVersion(nv)) ens.InterconnectAll().BeginMining(blockTime) diff --git a/itests/wdpost_dispute_test.go b/itests/wdpost_dispute_test.go index f73882032..39bf38bf7 100644 --- a/itests/wdpost_dispute_test.go +++ b/itests/wdpost_dispute_test.go @@ -39,8 +39,7 @@ func TestWindowPostDispute(t *testing.T) { // it doesn't submit proofs. // // Then we're going to manually submit bad proofs. - opts := []kit.NodeOpt{kit.ConstructorOpts(kit.LatestActorsAt(-1))} - opts = append(opts, kit.WithAllSubsystems()) + opts := []kit.NodeOpt{kit.WithAllSubsystems()} ens := kit.NewEnsemble(t, kit.MockProofs()). FullNode(&client, opts...). Miner(&chainMiner, &client, opts...). @@ -220,8 +219,7 @@ func TestWindowPostDisputeFails(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - opts := kit.ConstructorOpts(kit.LatestActorsAt(-1)) - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) ens.InterconnectAll().BeginMining(blocktime) defaultFrom, err := client.WalletDefaultAddress(ctx) diff --git a/itests/wdpost_test.go b/itests/wdpost_test.go index 6764350cc..d87059bb4 100644 --- a/itests/wdpost_test.go +++ b/itests/wdpost_test.go @@ -11,6 +11,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/specs-storage/storage" "github.com/filecoin-project/lotus/api" @@ -47,8 +48,9 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, ctx, cancel := context.WithCancel(context.Background()) defer cancel() - opts := kit.ConstructorOpts(kit.LatestActorsAt(upgradeHeight)) - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + client, miner, ens := kit.EnsembleMinimal(t, + kit.MockProofs(), + kit.LatestActorsAt(upgradeHeight)) ens.InterconnectAll().BeginMining(blocktime) miner.PledgeSectors(ctx, nSectors, 0, nil) @@ -63,7 +65,7 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, require.NoError(t, err) t.Log("Running one proving period") - waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 + waitUntil := di.Open + di.WPoStProvingPeriod t.Logf("End for head.Height > %d", waitUntil) ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) @@ -139,7 +141,7 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, require.NoError(t, err) t.Log("Go through another PP, wait for sectors to become faulty") - waitUntil = di.PeriodStart + di.WPoStProvingPeriod + 2 + waitUntil = di.Open + di.WPoStProvingPeriod t.Logf("End for head.Height > %d", waitUntil) ts = client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) @@ -161,7 +163,7 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, di, err = client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) require.NoError(t, err) - waitUntil = di.PeriodStart + di.WPoStProvingPeriod + 2 + waitUntil = di.Open + di.WPoStProvingPeriod t.Logf("End for head.Height > %d", waitUntil) ts = client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) @@ -184,7 +186,7 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, di, err = client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) require.NoError(t, err) - waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 + waitUntil := di.Open + di.WPoStProvingPeriod t.Logf("End for head.Height > %d\n", waitUntil) ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) @@ -213,18 +215,13 @@ func TestWindowPostBaseFeeNoBurn(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - sched := kit.DefaultTestUpgradeSchedule - lastUpgradeHeight := sched[len(sched)-1].Height - och := build.UpgradeClausHeight - build.UpgradeClausHeight = lastUpgradeHeight + 1 + build.UpgradeClausHeight = 0 + t.Cleanup(func() { build.UpgradeClausHeight = och }) - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.GenesisNetworkVersion(network.Version9)) ens.InterconnectAll().BeginMining(blocktime) - // Wait till all upgrades are done and we've passed the clause epoch. - client.WaitTillChain(ctx, kit.HeightAtLeast(build.UpgradeClausHeight+1)) - maddr, err := miner.ActorAddress(ctx) require.NoError(t, err) @@ -256,8 +253,6 @@ waitForProof: require.NoError(t, err) require.Equal(t, pmr.GasCost.BaseFeeBurn, big.Zero()) - - build.UpgradeClausHeight = och } func TestWindowPostBaseFeeBurn(t *testing.T) { @@ -270,16 +265,9 @@ func TestWindowPostBaseFeeBurn(t *testing.T) { blocktime := 2 * time.Millisecond - opts := kit.ConstructorOpts(kit.LatestActorsAt(-1)) - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) ens.InterconnectAll().BeginMining(blocktime) - // Ideally we'd be a bit more precise here, but getting the information we need from the - // test framework is more work than it's worth. - // - // We just need to wait till all upgrades are done. - client.WaitTillChain(ctx, kit.HeightAtLeast(20)) - maddr, err := miner.ActorAddress(ctx) require.NoError(t, err) diff --git a/lib/rpcenc/reader.go b/lib/rpcenc/reader.go index 23944af6c..6693dc83d 100644 --- a/lib/rpcenc/reader.go +++ b/lib/rpcenc/reader.go @@ -3,6 +3,7 @@ package rpcenc import ( "context" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -40,7 +41,63 @@ type ReaderStream struct { Info string } +var client = func() *http.Client { + c := *http.DefaultClient + c.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } + return &c +}() + +/* + + Example rpc function: + Push(context.Context, io.Reader) error + + Request flow: + 1. Client invokes a method with an io.Reader param + 2. go-jsonrpc invokes `ReaderParamEncoder` for the client-provided io.Reader + 3. `ReaderParamEncoder` transforms the reader into a `ReaderStream` which can + be serialized as JSON, and sent as jsonrpc request parameter + 3.1. If the reader is of type `*sealing.NullReader`, the resulting object + is `ReaderStream{ Type: "null", Info: "[base 10 number of bytes]" }` + 3.2. If the reader is of type `*RpcReader`, and it wasn't read from, we + notify that RpcReader to go a different push endpoint, and return + a `ReaderStream` object like in 3.4. + 3.3. In remaining cases we start a goroutine which: + 3.3.1. Makes a HEAD request to the server push endpoint + 3.3.2. If the HEAD request is redirected, it follows the redirect + 3.3.3. If the request succeeds, it starts a POST request to the + endpoint to which the last HEAD request was sent with the + reader set as request body. + 3.4. We return a `ReaderStream` indicating the uuid of push request, ex: + `ReaderStream{ Type: "push", Info: "[UUID string]" }` + 4. If the reader wasn't a NullReader, the server will receive a HEAD (or + POST in case of older clients) request to the push endpoint. + 4.1. The server gets or registers an `*RpcReader` in the `readers` map. + 4.2. It waits for a request to a matching push endpoint to be opened + 4.3. After the request is opened, it returns the `*RpcReader` to + go-jsonrpc, which will pass it as the io.Reader parameter to the + rpc method implementation + 4.4. If the first request made to the push endpoint was a POST, the + returned `*RpcReader` acts as a simple reader reading the POST + request body + 4.5. If the first request made to the push endpoint was a HEAD + 4.5.1. On the first call to Read or Close the server responds with + a 200 OK header, the client starts a POST request to the same + push URL, and the reader starts passing through the POST request + body + 4.5.2. If the reader is passed to another (now client) RPC method as a + reader parameter, the server for the first request responds to the + HEAD request with http 302 Found, instructing the first client to + go to the push endpoint of the second RPC server + 5. If the reader was a NullReader (ReaderStream.Type=="null"), we instantiate + it, and provide to the method implementation + +*/ + func ReaderParamEncoder(addr string) jsonrpc.Option { + // Client side parameter encoder. Runs on the rpc client side. io.Reader -> ReaderStream{} return jsonrpc.WithParamEncoder(new(io.Reader), func(value reflect.Value) (reflect.Value, error) { r := value.Interface().(io.Reader) @@ -55,62 +112,215 @@ func ReaderParamEncoder(addr string) jsonrpc.Option { } u.Path = path.Join(u.Path, reqID.String()) - go func() { - // TODO: figure out errors here + rpcReader, redir := r.(*RpcReader) + if redir { + // if we have an rpc stream, redirect instead of proxying all the data + redir = rpcReader.redirect(u.String()) + } - resp, err := http.Post(u.String(), "application/octet-stream", r) - if err != nil { - log.Errorf("sending reader param: %+v", err) - return - } + if !redir { + go func() { + // TODO: figure out errors here + for { + req, err := http.NewRequest("HEAD", u.String(), nil) + if err != nil { + log.Errorf("sending HEAD request for the reder param: %+v", err) + return + } + req.Header.Set("Content-Type", "application/octet-stream") + resp, err := client.Do(req) + if err != nil { + log.Errorf("sending reader param: %+v", err) + return + } + // todo do we need to close the body for a head request? - defer resp.Body.Close() //nolint:errcheck + if resp.StatusCode == http.StatusFound { + nextStr := resp.Header.Get("Location") + u, err = url.Parse(nextStr) + if err != nil { + log.Errorf("sending HEAD request for the reder param, parsing next url (%s): %+v", nextStr, err) + return + } - if resp.StatusCode != 200 { - b, _ := ioutil.ReadAll(resp.Body) - log.Errorf("sending reader param (%s): non-200 status: %s, msg: '%s'", u.String(), resp.Status, string(b)) - return - } + continue + } - }() + if resp.StatusCode == http.StatusNoContent { // reader closed before reading anything + // todo just return?? + return + } + + if resp.StatusCode != http.StatusOK { + b, _ := ioutil.ReadAll(resp.Body) + log.Errorf("sending reader param (%s): non-200 status: %s, msg: '%s'", u.String(), resp.Status, string(b)) + return + } + + break + } + + // now actually send the data + req, err := http.NewRequest("POST", u.String(), r) + if err != nil { + log.Errorf("sending reader param: %+v", err) + return + } + req.Header.Set("Content-Type", "application/octet-stream") + resp, err := client.Do(req) + if err != nil { + log.Errorf("sending reader param: %+v", err) + return + } + + defer resp.Body.Close() //nolint + + if resp.StatusCode != http.StatusOK { + b, _ := ioutil.ReadAll(resp.Body) + log.Errorf("sending reader param (%s): non-200 status: %s, msg: '%s'", u.String(), resp.Status, string(b)) + return + } + }() + } return reflect.ValueOf(ReaderStream{Type: PushStream, Info: reqID.String()}), nil }) } -// watchReadCloser watches the ReadCloser and closes the watch channel when +type resType int + +const ( + resStart resType = iota // send on first read after HEAD + resRedirect // send on redirect before first read after HEAD + resError + // done/closed = close res channel +) + +type readRes struct { + rt resType + meta string +} + +// RpcReader watches the ReadCloser and closes the res channel when // either: (1) the ReaderCloser fails on Read (including with a benign error // like EOF), or (2) when Close is called. // // Use it be notified of terminal states, in situations where a Read failure (or // EOF) is considered a terminal state too (besides Close). -type watchReadCloser struct { - io.ReadCloser - watch chan struct{} +type RpcReader struct { + postBody io.ReadCloser // nil on initial head request + next chan *RpcReader // on head will get us the postBody after sending resStart + mustRedirect bool + + res chan readRes + beginOnce *sync.Once closeOnce sync.Once } -func (w *watchReadCloser) Read(p []byte) (int, error) { - n, err := w.ReadCloser.Read(p) +var ErrHasBody = errors.New("RPCReader has body, either already read from or from a client with no redirect support") +var ErrMustRedirect = errors.New("reader can't be read directly; marked as MustRedirect") + +// MustRedirect marks the reader as required to be redirected. Will make local +// calls Read fail. MUST be called before this reader is used in any goroutine. +// If the reader can't be redirected will return ErrHasBody +func (w *RpcReader) MustRedirect() error { + if w.postBody != nil { + w.closeOnce.Do(func() { + w.res <- readRes{ + rt: resError, + } + close(w.res) + }) + + return ErrHasBody + } + + w.mustRedirect = true + return nil +} + +func (w *RpcReader) beginPost() { + if w.mustRedirect { + w.res <- readRes{ + rt: resError, + } + close(w.res) + return + } + + if w.postBody == nil { + w.res <- readRes{ + rt: resStart, + } + + nr := <-w.next + + w.postBody = nr.postBody + w.res = nr.res + w.beginOnce = nr.beginOnce + } +} + +func (w *RpcReader) Read(p []byte) (int, error) { + w.beginOnce.Do(func() { + w.beginPost() + }) + + if w.mustRedirect { + return 0, ErrMustRedirect + } + + if w.postBody == nil { + return 0, xerrors.Errorf("reader already closed or redirected") + } + + n, err := w.postBody.Read(p) if err != nil { w.closeOnce.Do(func() { - close(w.watch) + close(w.res) }) } return n, err } -func (w *watchReadCloser) Close() error { +func (w *RpcReader) Close() error { + w.beginOnce.Do(func() {}) w.closeOnce.Do(func() { - close(w.watch) + close(w.res) }) - return w.ReadCloser.Close() + if w.postBody == nil { + return nil + } + return w.postBody.Close() +} + +func (w *RpcReader) redirect(to string) bool { + if w.postBody != nil { + return false + } + + done := false + + w.beginOnce.Do(func() { + w.closeOnce.Do(func() { + w.res <- readRes{ + rt: resRedirect, + meta: to, + } + + done = true + close(w.res) + }) + }) + + return done } func ReaderParamDecoder() (http.HandlerFunc, jsonrpc.ServerOption) { var readersLk sync.Mutex - readers := map[uuid.UUID]chan *watchReadCloser{} + readers := map[uuid.UUID]chan *RpcReader{} + // runs on the rpc server side, called by the client before making the jsonrpc request hnd := func(resp http.ResponseWriter, req *http.Request) { strId := path.Base(req.URL.Path) u, err := uuid.Parse(strId) @@ -122,14 +332,24 @@ func ReaderParamDecoder() (http.HandlerFunc, jsonrpc.ServerOption) { readersLk.Lock() ch, found := readers[u] if !found { - ch = make(chan *watchReadCloser) + ch = make(chan *RpcReader) readers[u] = ch } readersLk.Unlock() - wr := &watchReadCloser{ - ReadCloser: req.Body, - watch: make(chan struct{}), + wr := &RpcReader{ + res: make(chan readRes), + next: ch, + beginOnce: &sync.Once{}, + } + + switch req.Method { + case http.MethodHead: + // leave body nil + case http.MethodPost: + wr.postBody = req.Body + default: + http.Error(resp, "unsupported method", http.StatusMethodNotAllowed) } tctx, cancel := context.WithTimeout(req.Context(), Timeout) @@ -145,18 +365,39 @@ func ReaderParamDecoder() (http.HandlerFunc, jsonrpc.ServerOption) { } select { - case <-wr.watch: + case res, ok := <-wr.res: + if !ok { + if req.Method == http.MethodHead { + resp.WriteHeader(http.StatusNoContent) + } else { + resp.WriteHeader(http.StatusOK) + } + return + } // TODO should we check if we failed the Read, and if so - // return an HTTP 500? i.e. turn watch into a chan error? + // return an HTTP 500? i.e. turn res into a chan error? + + switch res.rt { + case resRedirect: + http.Redirect(resp, req, res.meta, http.StatusFound) + case resStart: // responding to HEAD, request POST with reader data + resp.WriteHeader(http.StatusOK) + case resError: + resp.WriteHeader(500) + default: + log.Errorf("unknown res.rt") + resp.WriteHeader(500) + } + + return case <-req.Context().Done(): log.Errorf("context error in reader stream handler (2): %v", req.Context().Err()) resp.WriteHeader(500) return } - - resp.WriteHeader(200) } + // Server side reader decoder. runs on the rpc server side, invoked when decoding client request parameters. json(ReaderStream{}) -> io.Reader dec := jsonrpc.WithParamDecoder(new(io.Reader), func(ctx context.Context, b []byte) (reflect.Value, error) { var rs ReaderStream if err := json.Unmarshal(b, &rs); err != nil { @@ -180,7 +421,7 @@ func ReaderParamDecoder() (http.HandlerFunc, jsonrpc.ServerOption) { readersLk.Lock() ch, found := readers[u] if !found { - ch = make(chan *watchReadCloser) + ch = make(chan *RpcReader) readers[u] = ch } readersLk.Unlock() diff --git a/lib/rpcenc/reader_test.go b/lib/rpcenc/reader_test.go index 3aef2bb54..87296e1e5 100644 --- a/lib/rpcenc/reader_test.go +++ b/lib/rpcenc/reader_test.go @@ -10,12 +10,45 @@ import ( "github.com/gorilla/mux" "github.com/stretchr/testify/require" + "golang.org/x/xerrors" "github.com/filecoin-project/go-jsonrpc" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" ) type ReaderHandler struct { + readApi func(ctx context.Context, r io.Reader) ([]byte, error) +} + +func (h *ReaderHandler) ReadAllApi(ctx context.Context, r io.Reader, mustRedir bool) ([]byte, error) { + if mustRedir { + if err := r.(*RpcReader).MustRedirect(); err != nil { + return nil, err + } + } + return h.readApi(ctx, r) +} + +func (h *ReaderHandler) ReadStartAndApi(ctx context.Context, r io.Reader, mustRedir bool) ([]byte, error) { + if mustRedir { + if err := r.(*RpcReader).MustRedirect(); err != nil { + return nil, err + } + } + + n, err := r.Read([]byte{0}) + if err != nil { + return nil, err + } + if n != 1 { + return nil, xerrors.Errorf("not one") + } + + return h.readApi(ctx, r) +} + +func (h *ReaderHandler) CloseReader(ctx context.Context, r io.Reader) error { + return r.(io.Closer).Close() } func (h *ReaderHandler) ReadAll(ctx context.Context, r io.Reader) ([]byte, error) { @@ -88,3 +121,74 @@ func TestNullReaderProxy(t *testing.T) { require.NoError(t, err) require.Equal(t, int64(1016), n) } + +func TestReaderRedirect(t *testing.T) { + var allClient struct { + ReadAll func(ctx context.Context, r io.Reader) ([]byte, error) + } + + { + allServerHandler := &ReaderHandler{} + readerHandler, readerServerOpt := ReaderParamDecoder() + rpcServer := jsonrpc.NewServer(readerServerOpt) + rpcServer.Register("ReaderHandler", allServerHandler) + + mux := mux.NewRouter() + mux.Handle("/rpc/v0", rpcServer) + mux.Handle("/rpc/streams/v0/push/{uuid}", readerHandler) + + testServ := httptest.NewServer(mux) + defer testServ.Close() + + re := ReaderParamEncoder("http://" + testServ.Listener.Addr().String() + "/rpc/streams/v0/push") + closer, err := jsonrpc.NewMergeClient(context.Background(), "ws://"+testServ.Listener.Addr().String()+"/rpc/v0", "ReaderHandler", []interface{}{&allClient}, nil, re) + require.NoError(t, err) + + defer closer() + } + + var redirClient struct { + ReadAllApi func(ctx context.Context, r io.Reader, mustRedir bool) ([]byte, error) + ReadStartAndApi func(ctx context.Context, r io.Reader, mustRedir bool) ([]byte, error) + CloseReader func(ctx context.Context, r io.Reader) error + } + + { + allServerHandler := &ReaderHandler{readApi: allClient.ReadAll} + readerHandler, readerServerOpt := ReaderParamDecoder() + rpcServer := jsonrpc.NewServer(readerServerOpt) + rpcServer.Register("ReaderHandler", allServerHandler) + + mux := mux.NewRouter() + mux.Handle("/rpc/v0", rpcServer) + mux.Handle("/rpc/streams/v0/push/{uuid}", readerHandler) + + testServ := httptest.NewServer(mux) + defer testServ.Close() + + re := ReaderParamEncoder("http://" + testServ.Listener.Addr().String() + "/rpc/streams/v0/push") + closer, err := jsonrpc.NewMergeClient(context.Background(), "ws://"+testServ.Listener.Addr().String()+"/rpc/v0", "ReaderHandler", []interface{}{&redirClient}, nil, re) + require.NoError(t, err) + + defer closer() + } + + // redirect + read, err := redirClient.ReadAllApi(context.TODO(), strings.NewReader("rediracted pooooootato"), true) + require.NoError(t, err) + require.Equal(t, "rediracted pooooootato", string(read), "potatoes weren't equal") + + // proxy (because we started reading locally) + read, err = redirClient.ReadStartAndApi(context.TODO(), strings.NewReader("rediracted pooooootato"), false) + require.NoError(t, err) + require.Equal(t, "ediracted pooooootato", string(read), "otatoes weren't equal") + + // check mustredir check; proxy (because we started reading locally) + read, err = redirClient.ReadStartAndApi(context.TODO(), strings.NewReader("rediracted pooooootato"), true) + require.Error(t, err) + require.Contains(t, err.Error(), ErrMustRedirect.Error()) + require.Empty(t, read) + + err = redirClient.CloseReader(context.TODO(), strings.NewReader("rediracted pooooootato")) + require.NoError(t, err) +} diff --git a/markets/dagstore/blockstore.go b/markets/dagstore/blockstore.go new file mode 100644 index 000000000..8980d40cf --- /dev/null +++ b/markets/dagstore/blockstore.go @@ -0,0 +1,33 @@ +package dagstore + +import ( + "io" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + bstore "github.com/ipfs/go-ipfs-blockstore" + "golang.org/x/xerrors" + + "github.com/filecoin-project/dagstore" +) + +// Blockstore promotes a dagstore.ReadBlockstore to a full closeable Blockstore, +// stubbing out the write methods with erroring implementations. +type Blockstore struct { + dagstore.ReadBlockstore + io.Closer +} + +var _ bstore.Blockstore = (*Blockstore)(nil) + +func (b *Blockstore) DeleteBlock(c cid.Cid) error { + return xerrors.Errorf("DeleteBlock called but not implemented") +} + +func (b *Blockstore) Put(block blocks.Block) error { + return xerrors.Errorf("Put called but not implemented") +} + +func (b *Blockstore) PutMany(blocks []blocks.Block) error { + return xerrors.Errorf("PutMany called but not implemented") +} diff --git a/markets/dagstore/fixtures/sample-rw-bs-v2.car b/markets/dagstore/fixtures/sample-rw-bs-v2.car new file mode 100644 index 000000000..9f7b56df3 Binary files /dev/null and b/markets/dagstore/fixtures/sample-rw-bs-v2.car differ diff --git a/markets/dagstore/miner_api.go b/markets/dagstore/miner_api.go new file mode 100644 index 000000000..afe623eb2 --- /dev/null +++ b/markets/dagstore/miner_api.go @@ -0,0 +1,184 @@ +package dagstore + +import ( + "context" + "fmt" + "io" + + "github.com/filecoin-project/dagstore/throttle" + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-fil-markets/piecestore" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" + "github.com/filecoin-project/go-fil-markets/shared" +) + +type MinerAPI interface { + FetchUnsealedPiece(ctx context.Context, pieceCid cid.Cid) (io.ReadCloser, error) + GetUnpaddedCARSize(ctx context.Context, pieceCid cid.Cid) (uint64, error) + IsUnsealed(ctx context.Context, pieceCid cid.Cid) (bool, error) + Start(ctx context.Context) error +} + +type minerAPI struct { + pieceStore piecestore.PieceStore + sa retrievalmarket.SectorAccessor + throttle throttle.Throttler + readyMgr *shared.ReadyManager +} + +var _ MinerAPI = (*minerAPI)(nil) + +func NewMinerAPI(store piecestore.PieceStore, sa retrievalmarket.SectorAccessor, concurrency int) MinerAPI { + return &minerAPI{ + pieceStore: store, + sa: sa, + throttle: throttle.Fixed(concurrency), + readyMgr: shared.NewReadyManager(), + } +} + +func (m *minerAPI) Start(_ context.Context) error { + return m.readyMgr.FireReady(nil) +} + +func (m *minerAPI) IsUnsealed(ctx context.Context, pieceCid cid.Cid) (bool, error) { + err := m.readyMgr.AwaitReady() + if err != nil { + return false, xerrors.Errorf("failed while waiting for accessor to start: %w", err) + } + + var pieceInfo piecestore.PieceInfo + err = m.throttle.Do(ctx, func(ctx context.Context) (err error) { + pieceInfo, err = m.pieceStore.GetPieceInfo(pieceCid) + return err + }) + + if err != nil { + return false, xerrors.Errorf("failed to fetch pieceInfo for piece %s: %w", pieceCid, err) + } + + if len(pieceInfo.Deals) == 0 { + return false, xerrors.Errorf("no storage deals found for piece %s", pieceCid) + } + + // check if we have an unsealed deal for the given piece in any of the unsealed sectors. + for _, deal := range pieceInfo.Deals { + deal := deal + + var isUnsealed bool + // Throttle this path to avoid flooding the storage subsystem. + err := m.throttle.Do(ctx, func(ctx context.Context) (err error) { + isUnsealed, err = m.sa.IsUnsealed(ctx, deal.SectorID, deal.Offset.Unpadded(), deal.Length.Unpadded()) + if err != nil { + return fmt.Errorf("failed to check if sector %d for deal %d was unsealed: %w", deal.SectorID, deal.DealID, err) + } + return nil + }) + + if err != nil { + log.Warnf("failed to check/retrieve unsealed sector: %s", err) + continue // move on to the next match. + } + + if isUnsealed { + return true, nil + } + } + + // we don't have an unsealed sector containing the piece + return false, nil +} + +func (m *minerAPI) FetchUnsealedPiece(ctx context.Context, pieceCid cid.Cid) (io.ReadCloser, error) { + err := m.readyMgr.AwaitReady() + if err != nil { + return nil, err + } + + // Throttle this path to avoid flooding the storage subsystem. + var pieceInfo piecestore.PieceInfo + err = m.throttle.Do(ctx, func(ctx context.Context) (err error) { + pieceInfo, err = m.pieceStore.GetPieceInfo(pieceCid) + return err + }) + + if err != nil { + return nil, xerrors.Errorf("failed to fetch pieceInfo for piece %s: %w", pieceCid, err) + } + + if len(pieceInfo.Deals) == 0 { + return nil, xerrors.Errorf("no storage deals found for piece %s", pieceCid) + } + + // prefer an unsealed sector containing the piece if one exists + for _, deal := range pieceInfo.Deals { + deal := deal + + // Throttle this path to avoid flooding the storage subsystem. + var reader io.ReadCloser + err := m.throttle.Do(ctx, func(ctx context.Context) (err error) { + isUnsealed, err := m.sa.IsUnsealed(ctx, deal.SectorID, deal.Offset.Unpadded(), deal.Length.Unpadded()) + if err != nil { + return fmt.Errorf("failed to check if sector %d for deal %d was unsealed: %w", deal.SectorID, deal.DealID, err) + } + if !isUnsealed { + return nil + } + // Because we know we have an unsealed copy, this UnsealSector call will actually not perform any unsealing. + reader, err = m.sa.UnsealSector(ctx, deal.SectorID, deal.Offset.Unpadded(), deal.Length.Unpadded()) + return err + }) + + if err != nil { + log.Warnf("failed to check/retrieve unsealed sector: %s", err) + continue // move on to the next match. + } + + if reader != nil { + // we were able to obtain a reader for an already unsealed piece + return reader, nil + } + } + + lastErr := xerrors.New("no sectors found to unseal from") + // if there is no unsealed sector containing the piece, just read the piece from the first sector we are able to unseal. + for _, deal := range pieceInfo.Deals { + // Note that if the deal data is not already unsealed, unsealing may + // block for a long time with the current PoRep + // + // This path is unthrottled. + reader, err := m.sa.UnsealSector(ctx, deal.SectorID, deal.Offset.Unpadded(), deal.Length.Unpadded()) + if err != nil { + lastErr = xerrors.Errorf("failed to unseal deal %d: %w", deal.DealID, err) + log.Warn(lastErr.Error()) + continue + } + + // Successfully fetched the deal data so return a reader over the data + return reader, nil + } + + return nil, lastErr +} + +func (m *minerAPI) GetUnpaddedCARSize(ctx context.Context, pieceCid cid.Cid) (uint64, error) { + err := m.readyMgr.AwaitReady() + if err != nil { + return 0, err + } + + pieceInfo, err := m.pieceStore.GetPieceInfo(pieceCid) + if err != nil { + return 0, xerrors.Errorf("failed to fetch pieceInfo for piece %s: %w", pieceCid, err) + } + + if len(pieceInfo.Deals) == 0 { + return 0, xerrors.Errorf("no storage deals found for piece %s", pieceCid) + } + + len := pieceInfo.Deals[0].Length + + return uint64(len), nil +} diff --git a/markets/dagstore/miner_api_test.go b/markets/dagstore/miner_api_test.go new file mode 100644 index 000000000..4a61c62a8 --- /dev/null +++ b/markets/dagstore/miner_api_test.go @@ -0,0 +1,237 @@ +package dagstore + +import ( + "bytes" + "context" + "io" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/ipfs/go-cid" + ds "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/specs-actors/actors/builtin/paych" + + "github.com/filecoin-project/go-fil-markets/piecestore" + piecestoreimpl "github.com/filecoin-project/go-fil-markets/piecestore/impl" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" + "github.com/filecoin-project/go-fil-markets/shared" +) + +const unsealedSectorID = abi.SectorNumber(1) +const sealedSectorID = abi.SectorNumber(2) + +func TestLotusAccessorFetchUnsealedPiece(t *testing.T) { + ctx := context.Background() + + cid1, err := cid.Parse("bafkqaaa") + require.NoError(t, err) + + unsealedSectorData := "unsealed" + sealedSectorData := "sealed" + mockData := map[abi.SectorNumber]string{ + unsealedSectorID: unsealedSectorData, + sealedSectorID: sealedSectorData, + } + + testCases := []struct { + name string + deals []abi.SectorNumber + fetchedData string + isUnsealed bool + + expectErr bool + }{{ + // Expect error if there is no deal info for piece CID + name: "no deals", + expectErr: true, + }, { + // Expect the API to always fetch the unsealed deal (because it's + // cheaper than fetching the sealed deal) + name: "prefer unsealed deal", + deals: []abi.SectorNumber{unsealedSectorID, sealedSectorID}, + fetchedData: unsealedSectorData, + isUnsealed: true, + }, { + // Expect the API to unseal the data if there are no unsealed deals + name: "unseal if necessary", + deals: []abi.SectorNumber{sealedSectorID}, + fetchedData: sealedSectorData, + isUnsealed: false, + }} + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + ps := getPieceStore(t) + rpn := &mockRPN{ + sectors: mockData, + } + api := NewMinerAPI(ps, rpn, 100) + require.NoError(t, api.Start(ctx)) + + // Add deals to piece store + for _, sectorID := range tc.deals { + dealInfo := piecestore.DealInfo{ + SectorID: sectorID, + } + err = ps.AddDealForPiece(cid1, dealInfo) + require.NoError(t, err) + } + + // Fetch the piece + r, err := api.FetchUnsealedPiece(ctx, cid1) + if tc.expectErr { + require.Error(t, err) + return + } + + // Check that the returned reader is for the correct piece + require.NoError(t, err) + bz, err := io.ReadAll(r) + require.NoError(t, err) + + require.Equal(t, tc.fetchedData, string(bz)) + + uns, err := api.IsUnsealed(ctx, cid1) + require.NoError(t, err) + require.Equal(t, tc.isUnsealed, uns) + }) + } +} + +func TestLotusAccessorGetUnpaddedCARSize(t *testing.T) { + ctx := context.Background() + cid1, err := cid.Parse("bafkqaaa") + require.NoError(t, err) + + ps := getPieceStore(t) + rpn := &mockRPN{} + api := NewMinerAPI(ps, rpn, 100) + require.NoError(t, api.Start(ctx)) + + // Add a deal with data Length 10 + dealInfo := piecestore.DealInfo{ + Length: 10, + } + err = ps.AddDealForPiece(cid1, dealInfo) + require.NoError(t, err) + + // Check that the data length is correct + len, err := api.GetUnpaddedCARSize(ctx, cid1) + require.NoError(t, err) + require.EqualValues(t, 10, len) +} + +func TestThrottle(t *testing.T) { + ctx := context.Background() + cid1, err := cid.Parse("bafkqaaa") + require.NoError(t, err) + + ps := getPieceStore(t) + rpn := &mockRPN{ + sectors: map[abi.SectorNumber]string{ + unsealedSectorID: "foo", + }, + } + api := NewMinerAPI(ps, rpn, 3) + require.NoError(t, api.Start(ctx)) + + // Add a deal with data Length 10 + dealInfo := piecestore.DealInfo{ + SectorID: unsealedSectorID, + Length: 10, + } + err = ps.AddDealForPiece(cid1, dealInfo) + require.NoError(t, err) + + // hold the lock to block. + rpn.lk.Lock() + + // fetch the piece concurrently. + errgrp, ctx := errgroup.WithContext(context.Background()) + for i := 0; i < 10; i++ { + errgrp.Go(func() error { + r, err := api.FetchUnsealedPiece(ctx, cid1) + if err == nil { + _ = r.Close() + } + return err + }) + } + + time.Sleep(500 * time.Millisecond) + require.EqualValues(t, 3, atomic.LoadInt32(&rpn.calls)) // throttled + + // allow to proceed. + rpn.lk.Unlock() + + // allow all to finish. + err = errgrp.Wait() + require.NoError(t, err) + + require.EqualValues(t, 10, atomic.LoadInt32(&rpn.calls)) // throttled + +} + +func getPieceStore(t *testing.T) piecestore.PieceStore { + ps, err := piecestoreimpl.NewPieceStore(ds_sync.MutexWrap(ds.NewMapDatastore())) + require.NoError(t, err) + + ch := make(chan struct{}, 1) + ps.OnReady(func(_ error) { + ch <- struct{}{} + }) + + err = ps.Start(context.Background()) + require.NoError(t, err) + <-ch + return ps +} + +type mockRPN struct { + calls int32 // guarded by atomic + lk sync.RWMutex // lock to simulate blocks. + sectors map[abi.SectorNumber]string +} + +func (m *mockRPN) UnsealSector(ctx context.Context, sectorID abi.SectorNumber, offset abi.UnpaddedPieceSize, length abi.UnpaddedPieceSize) (io.ReadCloser, error) { + atomic.AddInt32(&m.calls, 1) + m.lk.RLock() + defer m.lk.RUnlock() + + data, ok := m.sectors[sectorID] + if !ok { + panic("sector not found") + } + return io.NopCloser(bytes.NewBuffer([]byte(data))), nil +} + +func (m *mockRPN) IsUnsealed(ctx context.Context, sectorID abi.SectorNumber, offset abi.UnpaddedPieceSize, length abi.UnpaddedPieceSize) (bool, error) { + return sectorID == unsealedSectorID, nil +} + +func (m *mockRPN) GetChainHead(ctx context.Context) (shared.TipSetToken, abi.ChainEpoch, error) { + panic("implement me") +} + +func (m *mockRPN) GetMinerWorkerAddress(ctx context.Context, miner address.Address, tok shared.TipSetToken) (address.Address, error) { + panic("implement me") +} + +func (m *mockRPN) SavePaymentVoucher(ctx context.Context, paymentChannel address.Address, voucher *paych.SignedVoucher, proof []byte, expectedAmount abi.TokenAmount, tok shared.TipSetToken) (abi.TokenAmount, error) { + panic("implement me") +} + +func (m *mockRPN) GetRetrievalPricingInput(ctx context.Context, pieceCID cid.Cid, storageDeals []abi.DealID) (retrievalmarket.PricingInput, error) { + panic("implement me") +} + +var _ retrievalmarket.RetrievalProviderNode = (*mockRPN)(nil) diff --git a/markets/dagstore/mocks/mock_lotus_accessor.go b/markets/dagstore/mocks/mock_lotus_accessor.go new file mode 100644 index 000000000..2e19b4482 --- /dev/null +++ b/markets/dagstore/mocks/mock_lotus_accessor.go @@ -0,0 +1,96 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: lotusaccessor.go + +// Package mock_dagstore is a generated GoMock package. +package mock_dagstore + +import ( + context "context" + io "io" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + cid "github.com/ipfs/go-cid" +) + +// MockLotusAccessor is a mock of LotusAccessor interface. +type MockLotusAccessor struct { + ctrl *gomock.Controller + recorder *MockLotusAccessorMockRecorder +} + +// MockLotusAccessorMockRecorder is the mock recorder for MockLotusAccessor. +type MockLotusAccessorMockRecorder struct { + mock *MockLotusAccessor +} + +// NewMockLotusAccessor creates a new mock instance. +func NewMockLotusAccessor(ctrl *gomock.Controller) *MockLotusAccessor { + mock := &MockLotusAccessor{ctrl: ctrl} + mock.recorder = &MockLotusAccessorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLotusAccessor) EXPECT() *MockLotusAccessorMockRecorder { + return m.recorder +} + +// FetchUnsealedPiece mocks base method. +func (m *MockLotusAccessor) FetchUnsealedPiece(ctx context.Context, pieceCid cid.Cid) (io.ReadCloser, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchUnsealedPiece", ctx, pieceCid) + ret0, _ := ret[0].(io.ReadCloser) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchUnsealedPiece indicates an expected call of FetchUnsealedPiece. +func (mr *MockLotusAccessorMockRecorder) FetchUnsealedPiece(ctx, pieceCid interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchUnsealedPiece", reflect.TypeOf((*MockLotusAccessor)(nil).FetchUnsealedPiece), ctx, pieceCid) +} + +// GetUnpaddedCARSize mocks base method. +func (m *MockLotusAccessor) GetUnpaddedCARSize(ctx context.Context, pieceCid cid.Cid) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUnpaddedCARSize", ctx, pieceCid) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUnpaddedCARSize indicates an expected call of GetUnpaddedCARSize. +func (mr *MockLotusAccessorMockRecorder) GetUnpaddedCARSize(ctx, pieceCid interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnpaddedCARSize", reflect.TypeOf((*MockLotusAccessor)(nil).GetUnpaddedCARSize), ctx, pieceCid) +} + +// IsUnsealed mocks base method. +func (m *MockLotusAccessor) IsUnsealed(ctx context.Context, pieceCid cid.Cid) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsUnsealed", ctx, pieceCid) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IsUnsealed indicates an expected call of IsUnsealed. +func (mr *MockLotusAccessorMockRecorder) IsUnsealed(ctx, pieceCid interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsUnsealed", reflect.TypeOf((*MockLotusAccessor)(nil).IsUnsealed), ctx, pieceCid) +} + +// Start mocks base method. +func (m *MockLotusAccessor) Start(ctx context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Start", ctx) + ret0, _ := ret[0].(error) + return ret0 +} + +// Start indicates an expected call of Start. +func (mr *MockLotusAccessorMockRecorder) Start(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockLotusAccessor)(nil).Start), ctx) +} diff --git a/markets/dagstore/mount.go b/markets/dagstore/mount.go new file mode 100644 index 000000000..c97dcbf86 --- /dev/null +++ b/markets/dagstore/mount.go @@ -0,0 +1,110 @@ +package dagstore + +import ( + "context" + "io" + "net/url" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/dagstore/mount" +) + +const lotusScheme = "lotus" + +var _ mount.Mount = (*LotusMount)(nil) + +// mountTemplate returns a templated LotusMount containing the supplied API. +// +// It is called when registering a mount type with the mount registry +// of the DAG store. It is used to reinstantiate mounts after a restart. +// +// When the registry needs to deserialize a mount it clones the template then +// calls Deserialize on the cloned instance, which will have a reference to the +// lotus mount API supplied here. +func mountTemplate(api MinerAPI) *LotusMount { + return &LotusMount{API: api} +} + +// LotusMount is a DAGStore mount implementation that fetches deal data +// from a PieceCID. +type LotusMount struct { + API MinerAPI + PieceCid cid.Cid +} + +func NewLotusMount(pieceCid cid.Cid, api MinerAPI) (*LotusMount, error) { + return &LotusMount{ + PieceCid: pieceCid, + API: api, + }, nil +} + +func (l *LotusMount) Serialize() *url.URL { + return &url.URL{ + Host: l.PieceCid.String(), + } +} + +func (l *LotusMount) Deserialize(u *url.URL) error { + pieceCid, err := cid.Decode(u.Host) + if err != nil { + return xerrors.Errorf("failed to parse PieceCid from host '%s': %w", u.Host, err) + } + l.PieceCid = pieceCid + return nil +} + +func (l *LotusMount) Fetch(ctx context.Context) (mount.Reader, error) { + r, err := l.API.FetchUnsealedPiece(ctx, l.PieceCid) + if err != nil { + return nil, xerrors.Errorf("failed to fetch unsealed piece %s: %w", l.PieceCid, err) + } + return &readCloser{r}, nil +} + +func (l *LotusMount) Info() mount.Info { + return mount.Info{ + Kind: mount.KindRemote, + AccessSequential: true, + AccessSeek: false, + AccessRandom: false, + } +} + +func (l *LotusMount) Close() error { + return nil +} + +func (l *LotusMount) Stat(ctx context.Context) (mount.Stat, error) { + size, err := l.API.GetUnpaddedCARSize(ctx, l.PieceCid) + if err != nil { + return mount.Stat{}, xerrors.Errorf("failed to fetch piece size for piece %s: %w", l.PieceCid, err) + } + isUnsealed, err := l.API.IsUnsealed(ctx, l.PieceCid) + if err != nil { + return mount.Stat{}, xerrors.Errorf("failed to verify if we have the unsealed piece %s: %w", l.PieceCid, err) + } + + // TODO Mark false when storage deal expires. + return mount.Stat{ + Exists: true, + Size: int64(size), + Ready: isUnsealed, + }, nil +} + +type readCloser struct { + io.ReadCloser +} + +var _ mount.Reader = (*readCloser)(nil) + +func (r *readCloser) ReadAt(p []byte, off int64) (n int, err error) { + return 0, xerrors.Errorf("ReadAt called but not implemented") +} + +func (r *readCloser) Seek(offset int64, whence int) (int64, error) { + return 0, xerrors.Errorf("Seek called but not implemented") +} diff --git a/markets/dagstore/mount_test.go b/markets/dagstore/mount_test.go new file mode 100644 index 000000000..09b255d6a --- /dev/null +++ b/markets/dagstore/mount_test.go @@ -0,0 +1,126 @@ +package dagstore + +import ( + "context" + "io/ioutil" + "net/url" + "strings" + "testing" + + "github.com/golang/mock/gomock" + blocksutil "github.com/ipfs/go-ipfs-blocksutil" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/dagstore/mount" + + mock_dagstore "github.com/filecoin-project/lotus/markets/dagstore/mocks" +) + +func TestLotusMount(t *testing.T) { + ctx := context.Background() + bgen := blocksutil.NewBlockGenerator() + cid := bgen.Next().Cid() + + mockCtrl := gomock.NewController(t) + // when test is done, assert expectations on all mock objects. + defer mockCtrl.Finish() + + // create a mock lotus api that returns the reader we want + mockLotusMountAPI := mock_dagstore.NewMockLotusAccessor(mockCtrl) + + mockLotusMountAPI.EXPECT().IsUnsealed(gomock.Any(), cid).Return(true, nil).Times(1) + + mockLotusMountAPI.EXPECT().FetchUnsealedPiece(gomock.Any(), cid).Return(&readCloser{ioutil.NopCloser(strings.NewReader("testing"))}, nil).Times(1) + mockLotusMountAPI.EXPECT().FetchUnsealedPiece(gomock.Any(), cid).Return(&readCloser{ioutil.NopCloser(strings.NewReader("testing"))}, nil).Times(1) + mockLotusMountAPI.EXPECT().GetUnpaddedCARSize(ctx, cid).Return(uint64(100), nil).Times(1) + + mnt, err := NewLotusMount(cid, mockLotusMountAPI) + require.NoError(t, err) + info := mnt.Info() + require.Equal(t, info.Kind, mount.KindRemote) + + // fetch and assert success + rd, err := mnt.Fetch(context.Background()) + require.NoError(t, err) + + bz, err := ioutil.ReadAll(rd) + require.NoError(t, err) + require.NoError(t, rd.Close()) + require.Equal(t, []byte("testing"), bz) + + stat, err := mnt.Stat(ctx) + require.NoError(t, err) + require.EqualValues(t, 100, stat.Size) + + // serialize url then deserialize from mount template -> should get back + // the same mount + url := mnt.Serialize() + mnt2 := mountTemplate(mockLotusMountAPI) + err = mnt2.Deserialize(url) + require.NoError(t, err) + + // fetching on this mount should get us back the same data. + rd, err = mnt2.Fetch(context.Background()) + require.NoError(t, err) + bz, err = ioutil.ReadAll(rd) + require.NoError(t, err) + require.NoError(t, rd.Close()) + require.Equal(t, []byte("testing"), bz) +} + +func TestLotusMountDeserialize(t *testing.T) { + api := &minerAPI{} + + bgen := blocksutil.NewBlockGenerator() + cid := bgen.Next().Cid() + + // success + us := lotusScheme + "://" + cid.String() + u, err := url.Parse(us) + require.NoError(t, err) + + mnt := mountTemplate(api) + err = mnt.Deserialize(u) + require.NoError(t, err) + + require.Equal(t, cid, mnt.PieceCid) + require.Equal(t, api, mnt.API) + + // fails if cid is not valid + us = lotusScheme + "://" + "rand" + u, err = url.Parse(us) + require.NoError(t, err) + err = mnt.Deserialize(u) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to parse PieceCid") +} + +func TestLotusMountRegistration(t *testing.T) { + ctx := context.Background() + bgen := blocksutil.NewBlockGenerator() + cid := bgen.Next().Cid() + + // success + us := lotusScheme + "://" + cid.String() + u, err := url.Parse(us) + require.NoError(t, err) + + mockCtrl := gomock.NewController(t) + // when test is done, assert expectations on all mock objects. + defer mockCtrl.Finish() + + mockLotusMountAPI := mock_dagstore.NewMockLotusAccessor(mockCtrl) + registry := mount.NewRegistry() + err = registry.Register(lotusScheme, mountTemplate(mockLotusMountAPI)) + require.NoError(t, err) + + mnt, err := registry.Instantiate(u) + require.NoError(t, err) + + mockLotusMountAPI.EXPECT().IsUnsealed(ctx, cid).Return(true, nil) + mockLotusMountAPI.EXPECT().GetUnpaddedCARSize(ctx, cid).Return(uint64(100), nil).Times(1) + stat, err := mnt.Stat(context.Background()) + require.NoError(t, err) + require.EqualValues(t, 100, stat.Size) + require.True(t, stat.Ready) +} diff --git a/markets/dagstore/wrapper.go b/markets/dagstore/wrapper.go new file mode 100644 index 000000000..3d9288659 --- /dev/null +++ b/markets/dagstore/wrapper.go @@ -0,0 +1,418 @@ +package dagstore + +import ( + "context" + "errors" + "math" + "os" + "path/filepath" + "sync" + "time" + + "github.com/ipfs/go-cid" + ds "github.com/ipfs/go-datastore" + levelds "github.com/ipfs/go-ds-leveldb" + measure "github.com/ipfs/go-ds-measure" + logging "github.com/ipfs/go-log/v2" + ldbopts "github.com/syndtr/goleveldb/leveldb/opt" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/node/config" + + "github.com/filecoin-project/go-statemachine/fsm" + + "github.com/filecoin-project/dagstore" + "github.com/filecoin-project/dagstore/index" + "github.com/filecoin-project/dagstore/mount" + "github.com/filecoin-project/dagstore/shard" + + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/go-fil-markets/storagemarket/impl/providerstates" + "github.com/filecoin-project/go-fil-markets/stores" +) + +const ( + maxRecoverAttempts = 1 + shardRegMarker = ".shard-registration-complete" +) + +var log = logging.Logger("dagstore") + +type Wrapper struct { + ctx context.Context + cancel context.CancelFunc + backgroundWg sync.WaitGroup + + cfg config.DAGStoreConfig + dagst dagstore.Interface + minerAPI MinerAPI + failureCh chan dagstore.ShardResult + traceCh chan dagstore.Trace + gcInterval time.Duration +} + +var _ stores.DAGStoreWrapper = (*Wrapper)(nil) + +func NewDAGStore(cfg config.DAGStoreConfig, minerApi MinerAPI) (*dagstore.DAGStore, *Wrapper, error) { + // construct the DAG Store. + registry := mount.NewRegistry() + if err := registry.Register(lotusScheme, mountTemplate(minerApi)); err != nil { + return nil, nil, xerrors.Errorf("failed to create registry: %w", err) + } + + // The dagstore will write Shard failures to the `failureCh` here. + failureCh := make(chan dagstore.ShardResult, 1) + + // The dagstore will write Trace events to the `traceCh` here. + traceCh := make(chan dagstore.Trace, 32) + + var ( + transientsDir = filepath.Join(cfg.RootDir, "transients") + datastoreDir = filepath.Join(cfg.RootDir, "datastore") + indexDir = filepath.Join(cfg.RootDir, "index") + ) + + dstore, err := newDatastore(datastoreDir) + if err != nil { + return nil, nil, xerrors.Errorf("failed to create dagstore datastore in %s: %w", datastoreDir, err) + } + + irepo, err := index.NewFSRepo(indexDir) + if err != nil { + return nil, nil, xerrors.Errorf("failed to initialise dagstore index repo") + } + + dcfg := dagstore.Config{ + TransientsDir: transientsDir, + IndexRepo: irepo, + Datastore: dstore, + MountRegistry: registry, + FailureCh: failureCh, + TraceCh: traceCh, + // not limiting fetches globally, as the Lotus mount does + // conditional throttling. + MaxConcurrentIndex: cfg.MaxConcurrentIndex, + MaxConcurrentReadyFetches: cfg.MaxConcurrentReadyFetches, + RecoverOnStart: dagstore.RecoverOnAcquire, + } + + dagst, err := dagstore.NewDAGStore(dcfg) + if err != nil { + return nil, nil, xerrors.Errorf("failed to create DAG store: %w", err) + } + + w := &Wrapper{ + cfg: cfg, + dagst: dagst, + minerAPI: minerApi, + failureCh: failureCh, + traceCh: traceCh, + gcInterval: time.Duration(cfg.GCInterval), + } + + return dagst, w, nil +} + +// newDatastore creates a datastore under the given base directory +// for dagstore metadata. +func newDatastore(dir string) (ds.Batching, error) { + // Create the datastore directory if it doesn't exist yet. + if err := os.MkdirAll(dir, 0755); err != nil { + return nil, xerrors.Errorf("failed to create directory %s for DAG store datastore: %w", dir, err) + } + + // Create a new LevelDB datastore + dstore, err := levelds.NewDatastore(dir, &levelds.Options{ + Compression: ldbopts.NoCompression, + NoSync: false, + Strict: ldbopts.StrictAll, + ReadOnly: false, + }) + if err != nil { + return nil, xerrors.Errorf("failed to open datastore for DAG store: %w", err) + } + // Keep statistics about the datastore + mds := measure.New("measure.", dstore) + return mds, nil +} + +func (w *Wrapper) Start(ctx context.Context) error { + w.ctx, w.cancel = context.WithCancel(ctx) + + // Run a go-routine to do DagStore GC. + w.backgroundWg.Add(1) + go w.gcLoop() + + // run a go-routine to read the trace for debugging. + w.backgroundWg.Add(1) + go w.traceLoop() + + // Run a go-routine for shard recovery + if dss, ok := w.dagst.(*dagstore.DAGStore); ok { + w.backgroundWg.Add(1) + go dagstore.RecoverImmediately(w.ctx, dss, w.failureCh, maxRecoverAttempts, w.backgroundWg.Done) + } + + return w.dagst.Start(ctx) +} + +func (w *Wrapper) traceLoop() { + defer w.backgroundWg.Done() + + for w.ctx.Err() == nil { + select { + // Log trace events from the DAG store + case tr := <-w.traceCh: + log.Debugw("trace", + "shard-key", tr.Key.String(), + "op-type", tr.Op.String(), + "after", tr.After.String()) + + case <-w.ctx.Done(): + return + } + } +} + +func (w *Wrapper) gcLoop() { + defer w.backgroundWg.Done() + + ticker := time.NewTicker(w.gcInterval) + defer ticker.Stop() + + for w.ctx.Err() == nil { + select { + // GC the DAG store on every tick + case <-ticker.C: + _, _ = w.dagst.GC(w.ctx) + + // Exit when the DAG store wrapper is shutdown + case <-w.ctx.Done(): + return + } + } +} + +func (w *Wrapper) LoadShard(ctx context.Context, pieceCid cid.Cid) (stores.ClosableBlockstore, error) { + log.Debugf("acquiring shard for piece CID %s", pieceCid) + + key := shard.KeyFromCID(pieceCid) + resch := make(chan dagstore.ShardResult, 1) + err := w.dagst.AcquireShard(ctx, key, resch, dagstore.AcquireOpts{}) + log.Debugf("sent message to acquire shard for piece CID %s", pieceCid) + + if err != nil { + if !errors.Is(err, dagstore.ErrShardUnknown) { + return nil, xerrors.Errorf("failed to schedule acquire shard for piece CID %s: %w", pieceCid, err) + } + + // if the DAGStore does not know about the Shard -> register it and then try to acquire it again. + log.Warnw("failed to load shard as shard is not registered, will re-register", "pieceCID", pieceCid) + // The path of a transient file that we can ask the DAG Store to use + // to perform the Indexing rather than fetching it via the Mount if + // we already have a transient file. However, we don't have it here + // and therefore we pass an empty file path. + carPath := "" + if err := stores.RegisterShardSync(ctx, w, pieceCid, carPath, false); err != nil { + return nil, xerrors.Errorf("failed to re-register shard during loading piece CID %s: %w", pieceCid, err) + } + log.Warnw("successfully re-registered shard", "pieceCID", pieceCid) + + resch = make(chan dagstore.ShardResult, 1) + if err := w.dagst.AcquireShard(ctx, key, resch, dagstore.AcquireOpts{}); err != nil { + return nil, xerrors.Errorf("failed to acquire Shard for piece CID %s after re-registering: %w", pieceCid, err) + } + } + + // TODO: The context is not yet being actively monitored by the DAG store, + // so we need to select against ctx.Done() until the following issue is + // implemented: + // https://github.com/filecoin-project/dagstore/issues/39 + var res dagstore.ShardResult + select { + case <-ctx.Done(): + return nil, ctx.Err() + case res = <-resch: + if res.Error != nil { + return nil, xerrors.Errorf("failed to acquire shard for piece CID %s: %w", pieceCid, res.Error) + } + } + + bs, err := res.Accessor.Blockstore() + if err != nil { + return nil, err + } + + log.Debugf("successfully loaded blockstore for piece CID %s", pieceCid) + return &Blockstore{ReadBlockstore: bs, Closer: res.Accessor}, nil +} + +func (w *Wrapper) RegisterShard(ctx context.Context, pieceCid cid.Cid, carPath string, eagerInit bool, resch chan dagstore.ShardResult) error { + // Create a lotus mount with the piece CID + key := shard.KeyFromCID(pieceCid) + mt, err := NewLotusMount(pieceCid, w.minerAPI) + if err != nil { + return xerrors.Errorf("failed to create lotus mount for piece CID %s: %w", pieceCid, err) + } + + // Register the shard + opts := dagstore.RegisterOpts{ + ExistingTransient: carPath, + LazyInitialization: !eagerInit, + } + err = w.dagst.RegisterShard(ctx, key, mt, resch, opts) + if err != nil { + return xerrors.Errorf("failed to schedule register shard for piece CID %s: %w", pieceCid, err) + } + log.Debugf("successfully submitted Register Shard request for piece CID %s with eagerInit=%t", pieceCid, eagerInit) + + return nil +} + +func (w *Wrapper) MigrateDeals(ctx context.Context, deals []storagemarket.MinerDeal) (bool, error) { + log := log.Named("migrator") + + // Check if all deals have already been registered as shards + isComplete, err := w.registrationComplete() + if err != nil { + return false, xerrors.Errorf("failed to get dagstore migration status: %w", err) + } + if isComplete { + // All deals have been registered as shards, bail out + log.Info("no shard migration necessary; already marked complete") + return false, nil + } + + log.Infow("registering shards for all active deals in sealing subsystem", "count", len(deals)) + + inSealingSubsystem := make(map[fsm.StateKey]struct{}, len(providerstates.StatesKnownBySealingSubsystem)) + for _, s := range providerstates.StatesKnownBySealingSubsystem { + inSealingSubsystem[s] = struct{}{} + } + + // channel where results will be received, and channel where the total + // number of registered shards will be sent. + resch := make(chan dagstore.ShardResult, 32) + totalCh := make(chan int) + doneCh := make(chan struct{}) + + // Start making progress consuming results. We won't know how many to + // actually consume until we register all shards. + // + // If there are any problems registering shards, just log an error + go func() { + defer close(doneCh) + + var total = math.MaxInt64 + var res dagstore.ShardResult + for rcvd := 0; rcvd < total; { + select { + case total = <-totalCh: + // we now know the total number of registered shards + // nullify so that we no longer consume from it after closed. + close(totalCh) + totalCh = nil + case res = <-resch: + rcvd++ + if res.Error == nil { + log.Infow("async shard registration completed successfully", "shard_key", res.Key) + } else { + log.Warnw("async shard registration failed", "shard_key", res.Key, "error", res.Error) + } + } + } + }() + + // Filter for deals that are handed off. + // + // If the deal has not yet been handed off to the sealing subsystem, we + // don't need to call RegisterShard in this migration; RegisterShard will + // be called in the new code once the deal reaches the state where it's + // handed off to the sealing subsystem. + var registered int + for _, deal := range deals { + pieceCid := deal.Proposal.PieceCID + + // enrich log statements in this iteration with deal ID and piece CID. + log := log.With("deal_id", deal.DealID, "piece_cid", pieceCid) + + // Filter for deals that have been handed off to the sealing subsystem + if _, ok := inSealingSubsystem[deal.State]; !ok { + log.Infow("deal not ready; skipping") + continue + } + + log.Infow("registering deal in dagstore with lazy init") + + // Register the deal as a shard with the DAG store with lazy initialization. + // The index will be populated the first time the deal is retrieved, or + // through the bulk initialization script. + err = w.RegisterShard(ctx, pieceCid, "", false, resch) + if err != nil { + log.Warnw("failed to register shard", "error", err) + continue + } + registered++ + } + + log.Infow("finished registering all shards", "total", registered) + totalCh <- registered + <-doneCh + + log.Infow("confirmed registration of all shards") + + // Completed registering all shards, so mark the migration as complete + err = w.markRegistrationComplete() + if err != nil { + log.Errorf("failed to mark shards as registered: %s", err) + } else { + log.Info("successfully marked migration as complete") + } + + log.Infow("dagstore migration complete") + + return true, nil +} + +// Check for the existence of a "marker" file indicating that the migration +// has completed +func (w *Wrapper) registrationComplete() (bool, error) { + path := filepath.Join(w.cfg.RootDir, shardRegMarker) + _, err := os.Stat(path) + if os.IsNotExist(err) { + return false, nil + } + if err != nil { + return false, err + } + return true, nil +} + +// Create a "marker" file indicating that the migration has completed +func (w *Wrapper) markRegistrationComplete() error { + path := filepath.Join(w.cfg.RootDir, shardRegMarker) + file, err := os.Create(path) + if err != nil { + return err + } + return file.Close() +} + +func (w *Wrapper) Close() error { + // Cancel the context + w.cancel() + + // Close the DAG store + log.Info("will close the dagstore") + if err := w.dagst.Close(); err != nil { + return xerrors.Errorf("failed to close dagstore: %w", err) + } + log.Info("dagstore closed") + + // Wait for the background go routine to exit + log.Info("waiting for dagstore background wrapper goroutines to exit") + w.backgroundWg.Wait() + log.Info("exited dagstore background wrapper goroutines") + + return nil +} diff --git a/markets/dagstore/wrapper_migration_test.go b/markets/dagstore/wrapper_migration_test.go new file mode 100644 index 000000000..13d8db876 --- /dev/null +++ b/markets/dagstore/wrapper_migration_test.go @@ -0,0 +1,121 @@ +package dagstore + +import ( + "context" + "testing" + + "github.com/filecoin-project/dagstore" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/go-fil-markets/retrievalmarket/impl/testnodes" + tut "github.com/filecoin-project/go-fil-markets/shared_testutil" + "github.com/filecoin-project/go-fil-markets/storagemarket" + + "github.com/filecoin-project/lotus/node/config" + + "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" +) + +func TestShardRegistration(t *testing.T) { + ps := tut.NewTestPieceStore() + sa := testnodes.NewTestSectorAccessor() + + ctx := context.Background() + cids := tut.GenerateCids(4) + pieceCidUnsealed := cids[0] + pieceCidSealed := cids[1] + pieceCidUnsealed2 := cids[2] + pieceCidUnsealed3 := cids[3] + + sealedSector := abi.SectorNumber(1) + unsealedSector1 := abi.SectorNumber(2) + unsealedSector2 := abi.SectorNumber(3) + unsealedSector3 := abi.SectorNumber(4) + + // ps.ExpectPiece(pieceCidUnsealed, piecestore.PieceInfo{ + // PieceCID: pieceCidUnsealed, + // Deals: []piecestore.DealInfo{ + // { + // SectorID: unsealedSector1, + // }, + // }, + // }) + // + // ps.ExpectPiece(pieceCidSealed, piecestore.PieceInfo{ + // PieceCID: pieceCidSealed, + // Deals: []piecestore.DealInfo{ + // { + // SectorID: sealedSector, + // }, + // }, + // }) + + deals := []storagemarket.MinerDeal{{ + // Should be registered + State: storagemarket.StorageDealSealing, + SectorNumber: unsealedSector1, + ClientDealProposal: market.ClientDealProposal{ + Proposal: market.DealProposal{ + PieceCID: pieceCidUnsealed, + }, + }, + }, { + // Should be registered with lazy registration (because sector is sealed) + State: storagemarket.StorageDealSealing, + SectorNumber: sealedSector, + ClientDealProposal: market.ClientDealProposal{ + Proposal: market.DealProposal{ + PieceCID: pieceCidSealed, + }, + }, + }, { + // Should be ignored because deal is no longer active + State: storagemarket.StorageDealError, + SectorNumber: unsealedSector2, + ClientDealProposal: market.ClientDealProposal{ + Proposal: market.DealProposal{ + PieceCID: pieceCidUnsealed2, + }, + }, + }, { + // Should be ignored because deal is not yet sealing + State: storagemarket.StorageDealFundsReserved, + SectorNumber: unsealedSector3, + ClientDealProposal: market.ClientDealProposal{ + Proposal: market.DealProposal{ + PieceCID: pieceCidUnsealed3, + }, + }, + }} + + cfg := config.DefaultStorageMiner().DAGStore + cfg.RootDir = t.TempDir() + + mapi := NewMinerAPI(ps, sa, 10) + dagst, w, err := NewDAGStore(cfg, mapi) + require.NoError(t, err) + require.NotNil(t, dagst) + require.NotNil(t, w) + + err = dagst.Start(context.Background()) + require.NoError(t, err) + + migrated, err := w.MigrateDeals(ctx, deals) + require.True(t, migrated) + require.NoError(t, err) + + info := dagst.AllShardsInfo() + require.Len(t, info, 2) + for _, i := range info { + require.Equal(t, dagstore.ShardStateNew, i.ShardState) + } + + // Run register shard migration again + migrated, err = w.MigrateDeals(ctx, deals) + require.False(t, migrated) + require.NoError(t, err) + + // ps.VerifyExpectations(t) +} diff --git a/markets/dagstore/wrapper_test.go b/markets/dagstore/wrapper_test.go new file mode 100644 index 000000000..9d3e6939e --- /dev/null +++ b/markets/dagstore/wrapper_test.go @@ -0,0 +1,214 @@ +package dagstore + +import ( + "bytes" + "context" + "io" + "os" + "testing" + "time" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/node/config" + + "github.com/filecoin-project/dagstore" + "github.com/filecoin-project/dagstore/mount" + "github.com/filecoin-project/dagstore/shard" + + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" +) + +// TestWrapperAcquireRecovery verifies that if acquire shard returns a "not found" +// error, the wrapper will attempt to register the shard then reacquire +func TestWrapperAcquireRecovery(t *testing.T) { + ctx := context.Background() + pieceCid, err := cid.Parse("bafkqaaa") + require.NoError(t, err) + + // Create a DAG store wrapper + dagst, w, err := NewDAGStore(config.DAGStoreConfig{ + RootDir: t.TempDir(), + GCInterval: config.Duration(1 * time.Millisecond), + }, mockLotusMount{}) + require.NoError(t, err) + + defer dagst.Close() //nolint:errcheck + + // Return an error from acquire shard the first time + acquireShardErr := make(chan error, 1) + acquireShardErr <- xerrors.Errorf("unknown shard: %w", dagstore.ErrShardUnknown) + + // Create a mock DAG store in place of the real DAG store + mock := &mockDagStore{ + acquireShardErr: acquireShardErr, + acquireShardRes: dagstore.ShardResult{ + Accessor: getShardAccessor(t), + }, + register: make(chan shard.Key, 1), + } + w.dagst = mock + + mybs, err := w.LoadShard(ctx, pieceCid) + require.NoError(t, err) + + // Expect the wrapper to try to recover from the error returned from + // acquire shard by calling register shard with the same key + tctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + select { + case <-tctx.Done(): + require.Fail(t, "failed to call register") + case k := <-mock.register: + require.Equal(t, k.String(), pieceCid.String()) + } + + // Verify that we can get things from the acquired blockstore + var count int + ch, err := mybs.AllKeysChan(ctx) + require.NoError(t, err) + for range ch { + count++ + } + require.Greater(t, count, 0) +} + +// TestWrapperBackground verifies the behaviour of the background go routine +func TestWrapperBackground(t *testing.T) { + ctx := context.Background() + + // Create a DAG store wrapper + dagst, w, err := NewDAGStore(config.DAGStoreConfig{ + RootDir: t.TempDir(), + GCInterval: config.Duration(1 * time.Millisecond), + }, mockLotusMount{}) + require.NoError(t, err) + + defer dagst.Close() //nolint:errcheck + + // Create a mock DAG store in place of the real DAG store + mock := &mockDagStore{ + gc: make(chan struct{}, 1), + recover: make(chan shard.Key, 1), + close: make(chan struct{}, 1), + } + w.dagst = mock + + // Start up the wrapper + err = w.Start(ctx) + require.NoError(t, err) + + // Expect GC to be called automatically + tctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + select { + case <-tctx.Done(): + require.Fail(t, "failed to call GC") + case <-mock.gc: + } + + // Expect that when the wrapper is closed it will call close on the + // DAG store + err = w.Close() + require.NoError(t, err) + + tctx, cancel3 := context.WithTimeout(ctx, time.Second) + defer cancel3() + select { + case <-tctx.Done(): + require.Fail(t, "failed to call close") + case <-mock.close: + } +} + +type mockDagStore struct { + acquireShardErr chan error + acquireShardRes dagstore.ShardResult + register chan shard.Key + + gc chan struct{} + recover chan shard.Key + close chan struct{} +} + +func (m *mockDagStore) DestroyShard(ctx context.Context, key shard.Key, out chan dagstore.ShardResult, _ dagstore.DestroyOpts) error { + panic("implement me") +} + +func (m *mockDagStore) GetShardInfo(k shard.Key) (dagstore.ShardInfo, error) { + panic("implement me") +} + +func (m *mockDagStore) AllShardsInfo() dagstore.AllShardsInfo { + panic("implement me") +} + +func (m *mockDagStore) Start(_ context.Context) error { + return nil +} + +func (m *mockDagStore) RegisterShard(ctx context.Context, key shard.Key, mnt mount.Mount, out chan dagstore.ShardResult, opts dagstore.RegisterOpts) error { + m.register <- key + out <- dagstore.ShardResult{Key: key} + return nil +} + +func (m *mockDagStore) AcquireShard(ctx context.Context, key shard.Key, out chan dagstore.ShardResult, _ dagstore.AcquireOpts) error { + select { + case err := <-m.acquireShardErr: + return err + default: + } + + out <- m.acquireShardRes + return nil +} + +func (m *mockDagStore) RecoverShard(ctx context.Context, key shard.Key, out chan dagstore.ShardResult, _ dagstore.RecoverOpts) error { + m.recover <- key + return nil +} + +func (m *mockDagStore) GC(ctx context.Context) (*dagstore.GCResult, error) { + select { + case m.gc <- struct{}{}: + default: + } + + return nil, nil +} + +func (m *mockDagStore) Close() error { + m.close <- struct{}{} + return nil +} + +type mockLotusMount struct { +} + +func (m mockLotusMount) Start(ctx context.Context) error { + return nil +} + +func (m mockLotusMount) FetchUnsealedPiece(ctx context.Context, pieceCid cid.Cid) (io.ReadCloser, error) { + panic("implement me") +} + +func (m mockLotusMount) GetUnpaddedCARSize(ctx context.Context, pieceCid cid.Cid) (uint64, error) { + panic("implement me") +} + +func (m mockLotusMount) IsUnsealed(ctx context.Context, pieceCid cid.Cid) (bool, error) { + panic("implement me") +} + +func getShardAccessor(t *testing.T) *dagstore.ShardAccessor { + data, err := os.ReadFile("./fixtures/sample-rw-bs-v2.car") + require.NoError(t, err) + buff := bytes.NewReader(data) + reader := &mount.NopCloser{Reader: buff, ReaderAt: buff, Seeker: buff} + shardAccessor, err := dagstore.NewShardAccessor(reader, nil, nil) + require.NoError(t, err) + return shardAccessor +} diff --git a/markets/retrievaladapter/client_blockstore.go b/markets/retrievaladapter/client_blockstore.go new file mode 100644 index 000000000..84c75fdbd --- /dev/null +++ b/markets/retrievaladapter/client_blockstore.go @@ -0,0 +1,83 @@ +package retrievaladapter + +import ( + "fmt" + "path/filepath" + "sync" + + "github.com/ipfs/go-cid" + bstore "github.com/ipfs/go-ipfs-blockstore" + "github.com/ipld/go-car/v2/blockstore" + + "github.com/filecoin-project/go-fil-markets/retrievalmarket" +) + +// ProxyBlockstoreAccessor is an accessor that returns a fixed blockstore. +// To be used in combination with IPFS integration. +type ProxyBlockstoreAccessor struct { + Blockstore bstore.Blockstore +} + +var _ retrievalmarket.BlockstoreAccessor = (*ProxyBlockstoreAccessor)(nil) + +func NewFixedBlockstoreAccessor(bs bstore.Blockstore) retrievalmarket.BlockstoreAccessor { + return &ProxyBlockstoreAccessor{Blockstore: bs} +} + +func (p *ProxyBlockstoreAccessor) Get(_ retrievalmarket.DealID, _ retrievalmarket.PayloadCID) (bstore.Blockstore, error) { + return p.Blockstore, nil +} + +func (p *ProxyBlockstoreAccessor) Done(_ retrievalmarket.DealID) error { + return nil +} + +type CARBlockstoreAccessor struct { + rootdir string + lk sync.Mutex + open map[retrievalmarket.DealID]*blockstore.ReadWrite +} + +var _ retrievalmarket.BlockstoreAccessor = (*CARBlockstoreAccessor)(nil) + +func NewCARBlockstoreAccessor(rootdir string) *CARBlockstoreAccessor { + return &CARBlockstoreAccessor{ + rootdir: rootdir, + open: make(map[retrievalmarket.DealID]*blockstore.ReadWrite), + } +} + +func (c *CARBlockstoreAccessor) Get(id retrievalmarket.DealID, payloadCid retrievalmarket.PayloadCID) (bstore.Blockstore, error) { + c.lk.Lock() + defer c.lk.Unlock() + + bs, ok := c.open[id] + if ok { + return bs, nil + } + + path := c.PathFor(id) + bs, err := blockstore.OpenReadWrite(path, []cid.Cid{payloadCid}, blockstore.UseWholeCIDs(true)) + if err != nil { + return nil, err + } + c.open[id] = bs + return bs, nil +} + +func (c *CARBlockstoreAccessor) Done(id retrievalmarket.DealID) error { + c.lk.Lock() + defer c.lk.Unlock() + + bs, ok := c.open[id] + if !ok { + return nil + } + + delete(c.open, id) + return bs.Finalize() +} + +func (c *CARBlockstoreAccessor) PathFor(id retrievalmarket.DealID) string { + return filepath.Join(c.rootdir, fmt.Sprintf("%d.car", id)) +} diff --git a/markets/retrievaladapter/provider.go b/markets/retrievaladapter/provider.go index 2f6305805..470c1cfc7 100644 --- a/markets/retrievaladapter/provider.go +++ b/markets/retrievaladapter/provider.go @@ -2,44 +2,34 @@ package retrievaladapter import ( "context" - "io" - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/v1api" - "github.com/filecoin-project/lotus/node/modules/dtypes" - "github.com/filecoin-project/lotus/storage/sectorblocks" "github.com/hashicorp/go-multierror" "golang.org/x/xerrors" "github.com/ipfs/go-cid" - "github.com/filecoin-project/lotus/chain/actors/builtin/paych" - "github.com/filecoin-project/lotus/chain/types" - sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" - "github.com/filecoin-project/lotus/extern/sector-storage/storiface" - "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-fil-markets/shared" "github.com/filecoin-project/go-state-types/abi" - specstorage "github.com/filecoin-project/specs-storage/storage" - + "github.com/filecoin-project/lotus/api/v1api" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" + "github.com/filecoin-project/lotus/chain/types" logging "github.com/ipfs/go-log/v2" ) var log = logging.Logger("retrievaladapter") type retrievalProviderNode struct { - maddr address.Address - secb sectorblocks.SectorBuilder - pp sectorstorage.PieceProvider - full v1api.FullNode + full v1api.FullNode } +var _ retrievalmarket.RetrievalProviderNode = (*retrievalProviderNode)(nil) + // NewRetrievalProviderNode returns a new node adapter for a retrieval provider that talks to the // Lotus Node -func NewRetrievalProviderNode(maddr dtypes.MinerAddress, secb sectorblocks.SectorBuilder, pp sectorstorage.PieceProvider, full v1api.FullNode) retrievalmarket.RetrievalProviderNode { - return &retrievalProviderNode{address.Address(maddr), secb, pp, full} +func NewRetrievalProviderNode(full v1api.FullNode) retrievalmarket.RetrievalProviderNode { + return &retrievalProviderNode{full: full} } func (rpn *retrievalProviderNode) GetMinerWorkerAddress(ctx context.Context, miner address.Address, tok shared.TipSetToken) (address.Address, error) { @@ -52,42 +42,6 @@ func (rpn *retrievalProviderNode) GetMinerWorkerAddress(ctx context.Context, min return mi.Worker, err } -func (rpn *retrievalProviderNode) UnsealSector(ctx context.Context, sectorID abi.SectorNumber, offset abi.UnpaddedPieceSize, length abi.UnpaddedPieceSize) (io.ReadCloser, error) { - log.Debugf("get sector %d, offset %d, length %d", sectorID, offset, length) - si, err := rpn.sectorsStatus(ctx, sectorID, false) - if err != nil { - return nil, err - } - - mid, err := address.IDFromAddress(rpn.maddr) - if err != nil { - return nil, err - } - - ref := specstorage.SectorRef{ - ID: abi.SectorID{ - Miner: abi.ActorID(mid), - Number: sectorID, - }, - ProofType: si.SealProof, - } - - var commD cid.Cid - if si.CommD != nil { - commD = *si.CommD - } - - // Get a reader for the piece, unsealing the piece if necessary - log.Debugf("read piece in sector %d, offset %d, length %d from miner %d", sectorID, offset, length, mid) - r, unsealed, err := rpn.pp.ReadPiece(ctx, ref, storiface.UnpaddedByteIndex(offset), length, si.Ticket.Value, commD) - if err != nil { - return nil, xerrors.Errorf("failed to unseal piece from sector %d: %w", sectorID, err) - } - _ = unsealed // todo: use - - return r, nil -} - func (rpn *retrievalProviderNode) SavePaymentVoucher(ctx context.Context, paymentChannel address.Address, voucher *paych.SignedVoucher, proof []byte, expectedAmount abi.TokenAmount, tok shared.TipSetToken) (abi.TokenAmount, error) { // TODO: respect the provided TipSetToken (a serialized TipSetKey) when // querying the chain @@ -104,29 +58,6 @@ func (rpn *retrievalProviderNode) GetChainHead(ctx context.Context) (shared.TipS return head.Key().Bytes(), head.Height(), nil } -func (rpn *retrievalProviderNode) IsUnsealed(ctx context.Context, sectorID abi.SectorNumber, offset abi.UnpaddedPieceSize, length abi.UnpaddedPieceSize) (bool, error) { - si, err := rpn.sectorsStatus(ctx, sectorID, true) - if err != nil { - return false, xerrors.Errorf("failed to get sector info: %w", err) - } - - mid, err := address.IDFromAddress(rpn.maddr) - if err != nil { - return false, err - } - - ref := specstorage.SectorRef{ - ID: abi.SectorID{ - Miner: abi.ActorID(mid), - Number: sectorID, - }, - ProofType: si.SealProof, - } - - log.Debugf("will call IsUnsealed now sector=%+v, offset=%d, size=%d", sectorID, offset, length) - return rpn.pp.IsUnsealed(ctx, ref, storiface.UnpaddedByteIndex(offset), length) -} - // GetRetrievalPricingInput takes a set of candidate storage deals that can serve a retrieval request, // and returns an minimally populated PricingInput. This PricingInput should be enhanced // with more data, and passed to the pricing function to determine the final quoted price. @@ -175,37 +106,3 @@ func (rpn *retrievalProviderNode) GetRetrievalPricingInput(ctx context.Context, return resp, nil } - -func (rpn *retrievalProviderNode) sectorsStatus(ctx context.Context, sid abi.SectorNumber, showOnChainInfo bool) (api.SectorInfo, error) { - sInfo, err := rpn.secb.SectorsStatus(ctx, sid, false) - if err != nil { - return api.SectorInfo{}, err - } - - if !showOnChainInfo { - return sInfo, nil - } - - onChainInfo, err := rpn.full.StateSectorGetInfo(ctx, rpn.maddr, sid, types.EmptyTSK) - if err != nil { - return sInfo, err - } - if onChainInfo == nil { - return sInfo, nil - } - sInfo.SealProof = onChainInfo.SealProof - sInfo.Activation = onChainInfo.Activation - sInfo.Expiration = onChainInfo.Expiration - sInfo.DealWeight = onChainInfo.DealWeight - sInfo.VerifiedDealWeight = onChainInfo.VerifiedDealWeight - sInfo.InitialPledge = onChainInfo.InitialPledge - - ex, err := rpn.full.StateSectorExpiration(ctx, rpn.maddr, sid, types.EmptyTSK) - if err != nil { - return sInfo, nil - } - sInfo.OnTime = ex.OnTime - sInfo.Early = ex.Early - - return sInfo, nil -} diff --git a/markets/sectoraccessor/sectoraccessor.go b/markets/sectoraccessor/sectoraccessor.go new file mode 100644 index 000000000..1304a3a00 --- /dev/null +++ b/markets/sectoraccessor/sectoraccessor.go @@ -0,0 +1,132 @@ +package sectoraccessor + +import ( + "context" + "io" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/api/v1api" + "github.com/filecoin-project/lotus/chain/types" + sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" + "github.com/filecoin-project/lotus/extern/sector-storage/storiface" + "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/storage/sectorblocks" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" + "github.com/filecoin-project/go-state-types/abi" + specstorage "github.com/filecoin-project/specs-storage/storage" + + "github.com/ipfs/go-cid" + logging "github.com/ipfs/go-log/v2" +) + +var log = logging.Logger("sectoraccessor") + +type sectorAccessor struct { + maddr address.Address + secb sectorblocks.SectorBuilder + pp sectorstorage.PieceProvider + full v1api.FullNode +} + +var _ retrievalmarket.SectorAccessor = (*sectorAccessor)(nil) + +func NewSectorAccessor(maddr dtypes.MinerAddress, secb sectorblocks.SectorBuilder, pp sectorstorage.PieceProvider, full v1api.FullNode) retrievalmarket.SectorAccessor { + return §orAccessor{address.Address(maddr), secb, pp, full} +} + +func (sa *sectorAccessor) UnsealSector(ctx context.Context, sectorID abi.SectorNumber, offset abi.UnpaddedPieceSize, length abi.UnpaddedPieceSize) (io.ReadCloser, error) { + log.Debugf("get sector %d, offset %d, length %d", sectorID, offset, length) + si, err := sa.sectorsStatus(ctx, sectorID, false) + if err != nil { + return nil, err + } + + mid, err := address.IDFromAddress(sa.maddr) + if err != nil { + return nil, err + } + + ref := specstorage.SectorRef{ + ID: abi.SectorID{ + Miner: abi.ActorID(mid), + Number: sectorID, + }, + ProofType: si.SealProof, + } + + var commD cid.Cid + if si.CommD != nil { + commD = *si.CommD + } + + // Get a reader for the piece, unsealing the piece if necessary + log.Debugf("read piece in sector %d, offset %d, length %d from miner %d", sectorID, offset, length, mid) + r, unsealed, err := sa.pp.ReadPiece(ctx, ref, storiface.UnpaddedByteIndex(offset), length, si.Ticket.Value, commD) + if err != nil { + return nil, xerrors.Errorf("failed to unseal piece from sector %d: %w", sectorID, err) + } + _ = unsealed // todo: use + + return r, nil +} + +func (sa *sectorAccessor) IsUnsealed(ctx context.Context, sectorID abi.SectorNumber, offset abi.UnpaddedPieceSize, length abi.UnpaddedPieceSize) (bool, error) { + si, err := sa.sectorsStatus(ctx, sectorID, true) + if err != nil { + return false, xerrors.Errorf("failed to get sector info: %w", err) + } + + mid, err := address.IDFromAddress(sa.maddr) + if err != nil { + return false, err + } + + ref := specstorage.SectorRef{ + ID: abi.SectorID{ + Miner: abi.ActorID(mid), + Number: sectorID, + }, + ProofType: si.SealProof, + } + + log.Debugf("will call IsUnsealed now sector=%+v, offset=%d, size=%d", sectorID, offset, length) + return sa.pp.IsUnsealed(ctx, ref, storiface.UnpaddedByteIndex(offset), length) +} + +func (sa *sectorAccessor) sectorsStatus(ctx context.Context, sid abi.SectorNumber, showOnChainInfo bool) (api.SectorInfo, error) { + sInfo, err := sa.secb.SectorsStatus(ctx, sid, false) + if err != nil { + return api.SectorInfo{}, err + } + + if !showOnChainInfo { + return sInfo, nil + } + + onChainInfo, err := sa.full.StateSectorGetInfo(ctx, sa.maddr, sid, types.EmptyTSK) + if err != nil { + return sInfo, err + } + if onChainInfo == nil { + return sInfo, nil + } + sInfo.SealProof = onChainInfo.SealProof + sInfo.Activation = onChainInfo.Activation + sInfo.Expiration = onChainInfo.Expiration + sInfo.DealWeight = onChainInfo.DealWeight + sInfo.VerifiedDealWeight = onChainInfo.VerifiedDealWeight + sInfo.InitialPledge = onChainInfo.InitialPledge + + ex, err := sa.full.StateSectorExpiration(ctx, sa.maddr, sid, types.EmptyTSK) + if err != nil { + return sInfo, nil + } + sInfo.OnTime = ex.OnTime + sInfo.Early = ex.Early + + return sInfo, nil +} diff --git a/markets/storageadapter/client_blockstore.go b/markets/storageadapter/client_blockstore.go new file mode 100644 index 000000000..4239251d3 --- /dev/null +++ b/markets/storageadapter/client_blockstore.go @@ -0,0 +1,101 @@ +package storageadapter + +import ( + "sync" + + "github.com/ipfs/go-cid" + blockstore "github.com/ipfs/go-ipfs-blockstore" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/go-fil-markets/stores" + "github.com/filecoin-project/lotus/node/repo/imports" +) + +// ProxyBlockstoreAccessor is an accessor that returns a fixed blockstore. +// To be used in combination with IPFS integration. +type ProxyBlockstoreAccessor struct { + Blockstore blockstore.Blockstore +} + +var _ storagemarket.BlockstoreAccessor = (*ProxyBlockstoreAccessor)(nil) + +func NewFixedBlockstoreAccessor(bs blockstore.Blockstore) storagemarket.BlockstoreAccessor { + return &ProxyBlockstoreAccessor{Blockstore: bs} +} + +func (p *ProxyBlockstoreAccessor) Get(cid storagemarket.PayloadCID) (blockstore.Blockstore, error) { + return p.Blockstore, nil +} + +func (p *ProxyBlockstoreAccessor) Done(cid storagemarket.PayloadCID) error { + return nil +} + +// ImportsBlockstoreAccessor is a blockstore accessor backed by the +// imports.Manager. +type ImportsBlockstoreAccessor struct { + m *imports.Manager + lk sync.Mutex + open map[cid.Cid]struct { + st stores.ClosableBlockstore + refs int + } +} + +var _ storagemarket.BlockstoreAccessor = (*ImportsBlockstoreAccessor)(nil) + +func NewImportsBlockstoreAccessor(importmgr *imports.Manager) *ImportsBlockstoreAccessor { + return &ImportsBlockstoreAccessor{ + m: importmgr, + open: make(map[cid.Cid]struct { + st stores.ClosableBlockstore + refs int + }), + } +} + +func (s *ImportsBlockstoreAccessor) Get(payloadCID storagemarket.PayloadCID) (blockstore.Blockstore, error) { + s.lk.Lock() + defer s.lk.Unlock() + + e, ok := s.open[payloadCID] + if ok { + e.refs++ + return e.st, nil + } + + path, err := s.m.CARPathFor(payloadCID) + if err != nil { + return nil, xerrors.Errorf("failed to get client blockstore for root %s: %w", payloadCID, err) + } + if path == "" { + return nil, xerrors.Errorf("no client blockstore for root %s", payloadCID) + } + ret, err := stores.ReadOnlyFilestore(path) + if err != nil { + return nil, err + } + e.st = ret + s.open[payloadCID] = e + return ret, nil +} + +func (s *ImportsBlockstoreAccessor) Done(payloadCID storagemarket.PayloadCID) error { + s.lk.Lock() + defer s.lk.Unlock() + + e, ok := s.open[payloadCID] + if !ok { + return nil + } + + e.refs-- + if e.refs == 0 { + if err := e.st.Close(); err != nil { + log.Warnf("failed to close blockstore: %s", err) + } + delete(s.open, payloadCID) + } + return nil +} diff --git a/markets/storageadapter/dealpublisher.go b/markets/storageadapter/dealpublisher.go index 9f7ba1629..7b1ca1793 100644 --- a/markets/storageadapter/dealpublisher.go +++ b/markets/storageadapter/dealpublisher.go @@ -17,6 +17,7 @@ import ( market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/builtin/market" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" @@ -203,9 +204,9 @@ func (p *DealPublisher) processNewDeal(pdeal *pendingDeal) { log.Infof("add deal with piece CID %s to publish deals queue - %d deals in queue (max queue size %d)", pdeal.deal.Proposal.PieceCID, len(p.pending), p.maxDealsPerPublishMsg) - // If the maximum number of deals per message has been reached, - // send a publish message - if uint64(len(p.pending)) >= p.maxDealsPerPublishMsg { + // If the maximum number of deals per message has been reached or we're not batching, send a + // publish message + if uint64(len(p.pending)) >= p.maxDealsPerPublishMsg || p.publishPeriod == 0 { log.Infof("publish deals queue has reached max size of %d, publishing deals", p.maxDealsPerPublishMsg) p.publishAllDeals() return @@ -218,7 +219,7 @@ func (p *DealPublisher) processNewDeal(pdeal *pendingDeal) { func (p *DealPublisher) waitForMoreDeals() { // Check if we're already waiting for deals if !p.publishPeriodStart.IsZero() { - elapsed := time.Since(p.publishPeriodStart) + elapsed := build.Clock.Since(p.publishPeriodStart) log.Infof("%s elapsed of / %s until publish deals queue is published", elapsed, p.publishPeriod) return @@ -227,11 +228,11 @@ func (p *DealPublisher) waitForMoreDeals() { // Set a timeout to wait for more deals to arrive log.Infof("waiting publish deals queue period of %s before publishing", p.publishPeriod) ctx, cancel := context.WithCancel(p.ctx) - p.publishPeriodStart = time.Now() + p.publishPeriodStart = build.Clock.Now() p.cancelWaitForMoreDeals = cancel go func() { - timer := time.NewTimer(p.publishPeriod) + timer := build.Clock.Timer(p.publishPeriod) select { case <-ctx.Done(): timer.Stop() diff --git a/markets/storageadapter/dealpublisher_test.go b/markets/storageadapter/dealpublisher_test.go index b2f107bf4..a4991396a 100644 --- a/markets/storageadapter/dealpublisher_test.go +++ b/markets/storageadapter/dealpublisher_test.go @@ -9,12 +9,14 @@ import ( "github.com/filecoin-project/go-state-types/crypto" market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" "github.com/ipfs/go-cid" + "github.com/raulk/clock" "github.com/stretchr/testify/require" tutils "github.com/filecoin-project/specs-actors/v2/support/testing" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin/market" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" @@ -25,7 +27,11 @@ import ( ) func TestDealPublisher(t *testing.T) { - t.Skip("this test randomly fails in various subtests; see issue #6799") + oldClock := build.Clock + t.Cleanup(func() { build.Clock = oldClock }) + mc := clock.NewMock() + build.Clock = mc + testCases := []struct { name string publishPeriod time.Duration @@ -92,6 +98,7 @@ func TestDealPublisher(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { + mc.Set(time.Now()) dpapi := newDPAPI(t) // Create a deal publisher @@ -116,7 +123,31 @@ func TestDealPublisher(t *testing.T) { } // Wait until publish period has elapsed - time.Sleep(2 * tc.publishPeriod) + if tc.publishPeriod > 0 { + // If we expect deals to get stuck in the queue, wait until that happens + if tc.maxDealsPerMsg != 0 && tc.dealCountWithinPublishPeriod%int(tc.maxDealsPerMsg) != 0 { + require.Eventually(t, func() bool { + dp.lk.Lock() + defer dp.lk.Unlock() + return !dp.publishPeriodStart.IsZero() + }, time.Second, time.Millisecond, "failed to queue deals") + } + + // Then wait to send + require.Eventually(t, func() bool { + dp.lk.Lock() + defer dp.lk.Unlock() + + // Advance if necessary. + if mc.Since(dp.publishPeriodStart) <= tc.publishPeriod { + dp.lk.Unlock() + mc.Set(dp.publishPeriodStart.Add(tc.publishPeriod + 1)) + dp.lk.Lock() + } + + return len(dp.pending) == 0 + }, time.Second, time.Millisecond, "failed to send pending messages") + } // Publish deals after publish period for i := 0; i < tc.dealCountAfterPublishPeriod; i++ { @@ -124,6 +155,19 @@ func TestDealPublisher(t *testing.T) { dealsToPublish = append(dealsToPublish, deal) } + if tc.publishPeriod > 0 && tc.dealCountAfterPublishPeriod > 0 { + require.Eventually(t, func() bool { + dp.lk.Lock() + defer dp.lk.Unlock() + if mc.Since(dp.publishPeriodStart) <= tc.publishPeriod { + dp.lk.Unlock() + mc.Set(dp.publishPeriodStart.Add(tc.publishPeriod + 1)) + dp.lk.Lock() + } + return len(dp.pending) == 0 + }, time.Second, time.Millisecond, "failed to send pending messages") + } + checkPublishedDeals(t, dpapi, dealsToPublish, tc.expectedDealsPerMsg) }) } @@ -133,7 +177,7 @@ func TestForcePublish(t *testing.T) { dpapi := newDPAPI(t) // Create a deal publisher - start := time.Now() + start := build.Clock.Now() publishPeriod := time.Hour dp := newDealPublisher(dpapi, nil, PublishMsgConfig{ Period: publishPeriod, @@ -152,7 +196,7 @@ func TestForcePublish(t *testing.T) { dealsToPublish = append(dealsToPublish, deal) // Allow a moment for them to be queued - time.Sleep(10 * time.Millisecond) + build.Clock.Sleep(10 * time.Millisecond) // Should be two deals in the pending deals list // (deal with cancelled context is ignored) @@ -160,7 +204,7 @@ func TestForcePublish(t *testing.T) { require.Len(t, pendingInfo.Deals, 2) require.Equal(t, publishPeriod, pendingInfo.PublishPeriod) require.True(t, pendingInfo.PublishPeriodStart.After(start)) - require.True(t, pendingInfo.PublishPeriodStart.Before(time.Now())) + require.True(t, pendingInfo.PublishPeriodStart.Before(build.Clock.Now())) // Force publish all pending deals dp.ForcePublishPendingDeals() diff --git a/markets/storageadapter/provider.go b/markets/storageadapter/provider.go index b899c0810..23a3c32a8 100644 --- a/markets/storageadapter/provider.go +++ b/markets/storageadapter/provider.go @@ -32,7 +32,6 @@ import ( "github.com/filecoin-project/lotus/lib/sigs" "github.com/filecoin-project/lotus/markets/utils" "github.com/filecoin-project/lotus/node/config" - "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/node/modules/helpers" "github.com/filecoin-project/lotus/storage/sectorblocks" ) @@ -45,9 +44,6 @@ var log = logging.Logger("storageadapter") type ProviderNodeAdapter struct { v1api.FullNode - // this goes away with the data transfer module - dag dtypes.StagingDAG - secb *sectorblocks.SectorBlocks ev *events.Events @@ -59,15 +55,14 @@ type ProviderNodeAdapter struct { scMgr *SectorCommittedManager } -func NewProviderNodeAdapter(fc *config.MinerFeeConfig, dc *config.DealmakingConfig) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, dag dtypes.StagingDAG, secb *sectorblocks.SectorBlocks, full v1api.FullNode, dealPublisher *DealPublisher) storagemarket.StorageProviderNode { - return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, dag dtypes.StagingDAG, secb *sectorblocks.SectorBlocks, full v1api.FullNode, dealPublisher *DealPublisher) storagemarket.StorageProviderNode { +func NewProviderNodeAdapter(fc *config.MinerFeeConfig, dc *config.DealmakingConfig) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, secb *sectorblocks.SectorBlocks, full v1api.FullNode, dealPublisher *DealPublisher) storagemarket.StorageProviderNode { + return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, secb *sectorblocks.SectorBlocks, full v1api.FullNode, dealPublisher *DealPublisher) storagemarket.StorageProviderNode { ctx := helpers.LifecycleCtx(mctx, lc) ev := events.NewEvents(ctx, full) na := &ProviderNodeAdapter{ FullNode: full, - dag: dag, secb: secb, ev: ev, dealPublisher: dealPublisher, @@ -107,8 +102,8 @@ func (n *ProviderNodeAdapter) OnDealComplete(ctx context.Context, deal storagema } p, offset, err := n.secb.AddPiece(ctx, pieceSize, pieceData, sdInfo) - curTime := time.Now() - for time.Since(curTime) < addPieceRetryTimeout { + curTime := build.Clock.Now() + for build.Clock.Since(curTime) < addPieceRetryTimeout { if !xerrors.Is(err, sealing.ErrTooManySectorsSealing) { if err != nil { log.Errorf("failed to addPiece for deal %d, err: %v", deal.DealID, err) @@ -116,7 +111,7 @@ func (n *ProviderNodeAdapter) OnDealComplete(ctx context.Context, deal storagema break } select { - case <-time.After(addPieceRetryWait): + case <-build.Clock.After(addPieceRetryWait): p, offset, err = n.secb.AddPiece(ctx, pieceSize, pieceData, sdInfo) case <-ctx.Done(): return nil, xerrors.New("context expired while waiting to retry AddPiece") diff --git a/node/builder.go b/node/builder.go index 6963cf4a4..f04678bc8 100644 --- a/node/builder.go +++ b/node/builder.go @@ -6,9 +6,10 @@ import ( "os" "time" - "github.com/filecoin-project/lotus/node/impl/net" metricsi "github.com/ipfs/go-metrics-interface" + "github.com/filecoin-project/lotus/node/impl/net" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/system" @@ -66,6 +67,7 @@ var ( AutoNATSvcKey = special{10} // Libp2p option BandwidthReporterKey = special{11} // Libp2p option ConnGaterKey = special{12} // libp2p option + DAGStoreKey = special{13} // constructor returns multiple values ) type invoke int @@ -332,10 +334,9 @@ func Repo(r repo.Repo) Option { ), Override(new(dtypes.ClientImportMgr), modules.ClientImportMgr), - Override(new(dtypes.ClientMultiDstore), modules.ClientMultiDatastore), Override(new(dtypes.ClientBlockstore), modules.ClientBlockstore), - Override(new(dtypes.ClientRetrievalStoreManager), modules.ClientRetrievalStoreManager), + Override(new(ci.PrivKey), lp2p.PrivKey), Override(new(ci.PubKey), ci.PrivKey.GetPublic), Override(new(peer.ID), peer.IDFromPublicKey), diff --git a/node/builder_chain.go b/node/builder_chain.go index 1447a4df7..f8eeaecb3 100644 --- a/node/builder_chain.go +++ b/node/builder_chain.go @@ -17,7 +17,6 @@ import ( "github.com/filecoin-project/lotus/chain/market" "github.com/filecoin-project/lotus/chain/messagepool" "github.com/filecoin-project/lotus/chain/messagesigner" - "github.com/filecoin-project/lotus/chain/metrics" "github.com/filecoin-project/lotus/chain/stmgr" rpcstmgr "github.com/filecoin-project/lotus/chain/stmgr/rpc" "github.com/filecoin-project/lotus/chain/store" @@ -113,12 +112,14 @@ var ChainNode = Options( // Markets (retrieval) Override(new(discovery.PeerResolver), modules.RetrievalResolver), + Override(new(retrievalmarket.BlockstoreAccessor), modules.RetrievalBlockstoreAccessor), Override(new(retrievalmarket.RetrievalClient), modules.RetrievalClient), Override(new(dtypes.ClientDataTransfer), modules.NewClientGraphsyncDataTransfer), // Markets (storage) Override(new(*market.FundManager), market.NewFundManager), Override(new(dtypes.ClientDatastore), modules.NewClientDatastore), + Override(new(storagemarket.BlockstoreAccessor), modules.StorageBlockstoreAccessor), Override(new(storagemarket.StorageClient), modules.StorageClient), Override(new(storagemarket.StorageClientNode), storageadapter.NewClientNodeAdapter), Override(HandleMigrateClientFundsKey, modules.HandleMigrateClientFunds), @@ -168,16 +169,13 @@ func ConfigFullNode(c interface{}) Option { If(cfg.Client.UseIpfs, Override(new(dtypes.ClientBlockstore), modules.IpfsClientBlockstore(ipfsMaddr, cfg.Client.IpfsOnlineMode)), + Override(new(storagemarket.BlockstoreAccessor), modules.IpfsStorageBlockstoreAccessor), If(cfg.Client.IpfsUseForRetrieval, - Override(new(dtypes.ClientRetrievalStoreManager), modules.ClientBlockstoreRetrievalStoreManager), + Override(new(retrievalmarket.BlockstoreAccessor), modules.IpfsRetrievalBlockstoreAccessor), ), ), Override(new(dtypes.Graphsync), modules.Graphsync(cfg.Client.SimultaneousTransfers)), - If(cfg.Metrics.HeadNotifs, - Override(HeadMetricsKey, metrics.SendHeadNotifs(cfg.Metrics.Nickname)), - ), - If(cfg.Wallet.RemoteBackend != "", Override(new(*remotewallet.RemoteWallet), remotewallet.SetupRemoteWallet(cfg.Wallet.RemoteBackend)), ), diff --git a/node/builder_miner.go b/node/builder_miner.go index 3be055de7..fd69de678 100644 --- a/node/builder_miner.go +++ b/node/builder_miner.go @@ -12,7 +12,6 @@ import ( "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-fil-markets/storagemarket/impl/storedask" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/markets/retrievaladapter" storage2 "github.com/filecoin-project/specs-storage/storage" "github.com/filecoin-project/lotus/api" @@ -23,7 +22,10 @@ import ( "github.com/filecoin-project/lotus/extern/sector-storage/stores" "github.com/filecoin-project/lotus/extern/sector-storage/storiface" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + "github.com/filecoin-project/lotus/markets/dagstore" "github.com/filecoin-project/lotus/markets/dealfilter" + "github.com/filecoin-project/lotus/markets/retrievaladapter" + "github.com/filecoin-project/lotus/markets/sectoraccessor" "github.com/filecoin-project/lotus/markets/storageadapter" "github.com/filecoin-project/lotus/miner" "github.com/filecoin-project/lotus/node/config" @@ -130,9 +132,7 @@ func ConfigStorageMiner(c interface{}) Option { If(cfg.Subsystems.EnableMarkets, // Markets - Override(new(dtypes.StagingMultiDstore), modules.StagingMultiDatastore), Override(new(dtypes.StagingBlockstore), modules.StagingBlockstore), - Override(new(dtypes.StagingDAG), modules.StagingDAG), Override(new(dtypes.StagingGraphsync), modules.StagingGraphsync(cfg.Dealmaking.SimultaneousTransfers)), Override(new(dtypes.ProviderPieceStore), modules.NewProviderPieceStore), Override(new(*sectorblocks.SectorBlocks), sectorblocks.NewSectorBlocks), @@ -147,7 +147,12 @@ func ConfigStorageMiner(c interface{}) Option { })), Override(new(dtypes.RetrievalPricingFunc), modules.RetrievalPricingFunc(cfg.Dealmaking)), + // DAG Store + Override(new(dagstore.MinerAPI), modules.NewMinerAPI), + Override(DAGStoreKey, modules.DAGStore), + // Markets (retrieval) + Override(new(retrievalmarket.SectorAccessor), sectoraccessor.NewSectorAccessor), Override(new(retrievalmarket.RetrievalProviderNode), retrievaladapter.NewRetrievalProviderNode), Override(new(rmnet.RetrievalMarketNetwork), modules.RetrievalNetwork), Override(new(retrievalmarket.RetrievalProvider), modules.RetrievalProvider), diff --git a/node/config/def.go b/node/config/def.go index c5c455c68..b75831eef 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -10,6 +10,8 @@ import ( "github.com/filecoin-project/go-state-types/big" miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" ) @@ -47,7 +49,6 @@ func defCommon() Common { Pubsub: Pubsub{ Bootstrapper: false, DirectPeers: nil, - RemoteTracer: "/dns4/pubsub-tracer.filecoin.io/tcp/4001/p2p/QmTd6UvR47vUidRNZ1ZKXHrAFhqTJAD27rKL9XYghEKgKX", }, } @@ -100,6 +101,8 @@ func DefaultStorageMiner() *StorageMiner { PreCommitBatchWait: Duration(24 * time.Hour), // this should be less than 31.5 hours, which is the expiration of a precommit ticket PreCommitBatchSlack: Duration(3 * time.Hour), // time buffer for forceful batch submission before sectors/deals in batch would start expiring, higher value will lower the chances for message fail due to expiration + CommittedCapacitySectorLifetime: Duration(builtin.EpochDurationSeconds * policy.GetMaxSectorExpirationExtension()), + AggregateCommits: true, MinCommitBatch: miner5.MinAggregatedSectors, // per FIP13, we must have at least four proofs to aggregate, where 4 is the cross over point where aggregation wins out on single provecommit gas costs MaxCommitBatch: miner5.MaxAggregatedSectors, // maximum 819 sectors, this is the maximum aggregation per FIP13 @@ -188,6 +191,12 @@ func DefaultStorageMiner() *StorageMiner { TerminateControl: []string{}, DealPublishControl: []string{}, }, + + DAGStore: DAGStoreConfig{ + MaxConcurrentIndex: 5, + MaxConcurrencyStorageCalls: 100, + GCInterval: Duration(1 * time.Minute), + }, } cfg.Common.API.ListenAddress = "/ip4/127.0.0.1/tcp/2345/http" cfg.Common.API.RemoteListenAddress = "127.0.0.1:2345" diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 5d4a91d5f..9efe4e03c 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -125,6 +125,55 @@ and storage providers`, Comment: ``, }, }, + "DAGStoreConfig": []DocField{ + { + Name: "RootDir", + Type: "string", + + Comment: `Path to the dagstore root directory. This directory contains three +subdirectories, which can be symlinked to alternative locations if +need be: +- ./transients: caches unsealed deals that have been fetched from the +storage subsystem for serving retrievals. +- ./indices: stores shard indices. +- ./datastore: holds the KV store tracking the state of every shard +known to the DAG store. +Default value: /dagstore (split deployment) or +/dagstore (monolith deployment)`, + }, + { + Name: "MaxConcurrentIndex", + Type: "int", + + Comment: `The maximum amount of indexing jobs that can run simultaneously. +0 means unlimited. +Default value: 5.`, + }, + { + Name: "MaxConcurrentReadyFetches", + Type: "int", + + Comment: `The maximum amount of unsealed deals that can be fetched simultaneously +from the storage subsystem. 0 means unlimited. +Default value: 0 (unlimited).`, + }, + { + Name: "MaxConcurrencyStorageCalls", + Type: "int", + + Comment: `The maximum number of simultaneous inflight API calls to the storage +subsystem. +Default value: 100.`, + }, + { + Name: "GCInterval", + Type: "Duration", + + Comment: `The time between calls to periodic dagstore GC, in time.Duration string +representation, e.g. 1m, 5m, 1h. +Default value: 1 minute.`, + }, + }, "DealmakingConfig": []DocField{ { Name: "ConsiderOnlineStorageDeals", @@ -245,12 +294,6 @@ see https://docs.filecoin.io/mine/lotus/miner-configuration/#using-filters-for-f Comment: ``, }, - { - Name: "Metrics", - Type: "Metrics", - - Comment: ``, - }, { Name: "Wallet", Type: "Wallet", @@ -324,20 +367,6 @@ Format: multiaddress`, Comment: ``, }, }, - "Metrics": []DocField{ - { - Name: "Nickname", - Type: "string", - - Comment: ``, - }, - { - Name: "HeadNotifs", - Type: "bool", - - Comment: ``, - }, - }, "MinerAddressConfig": []DocField{ { Name: "PreCommitControl", @@ -562,6 +591,14 @@ Note that setting this number too high in relation to deal ingestion rate may re Comment: `Upper bound on how many sectors can be sealing at the same time when creating new sectors with deals (0 = unlimited)`, }, + { + Name: "CommittedCapacitySectorLifetime", + Type: "Duration", + + Comment: `CommittedCapacitySectorLifetime is the duration a Committed Capacity (CC) sector will +live before it must be extended or converted into sector containing deals before it is +terminated. Value must be between 180-540 days inclusive`, + }, { Name: "WaitDealsDelay", Type: "Duration", @@ -753,6 +790,12 @@ Default is 20 (about once a week).`, Name: "Addresses", Type: "MinerAddressConfig", + Comment: ``, + }, + { + Name: "DAGStore", + Type: "DAGStoreConfig", + Comment: ``, }, }, diff --git a/node/config/types.go b/node/config/types.go index fe42aa27e..1576169e6 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -23,7 +23,6 @@ type Common struct { type FullNode struct { Common Client Client - Metrics Metrics Wallet Wallet Fees FeeConfig Chainstore Chainstore @@ -50,6 +49,41 @@ type StorageMiner struct { Storage sectorstorage.SealerConfig Fees MinerFeeConfig Addresses MinerAddressConfig + DAGStore DAGStoreConfig +} + +type DAGStoreConfig struct { + // Path to the dagstore root directory. This directory contains three + // subdirectories, which can be symlinked to alternative locations if + // need be: + // - ./transients: caches unsealed deals that have been fetched from the + // storage subsystem for serving retrievals. + // - ./indices: stores shard indices. + // - ./datastore: holds the KV store tracking the state of every shard + // known to the DAG store. + // Default value: /dagstore (split deployment) or + // /dagstore (monolith deployment) + RootDir string + + // The maximum amount of indexing jobs that can run simultaneously. + // 0 means unlimited. + // Default value: 5. + MaxConcurrentIndex int + + // The maximum amount of unsealed deals that can be fetched simultaneously + // from the storage subsystem. 0 means unlimited. + // Default value: 0 (unlimited). + MaxConcurrentReadyFetches int + + // The maximum number of simultaneous inflight API calls to the storage + // subsystem. + // Default value: 100. + MaxConcurrencyStorageCalls int + + // The time between calls to periodic dagstore GC, in time.Duration string + // representation, e.g. 1m, 5m, 1h. + // Default value: 1 minute. + GCInterval Duration } type MinerSubsystemConfig struct { @@ -141,6 +175,11 @@ type SealingConfig struct { // Upper bound on how many sectors can be sealing at the same time when creating new sectors with deals (0 = unlimited) MaxSealingSectorsForDeals uint64 + // CommittedCapacitySectorLifetime is the duration a Committed Capacity (CC) sector will + // live before it must be extended or converted into sector containing deals before it is + // terminated. Value must be between 180-540 days inclusive + CommittedCapacitySectorLifetime Duration + // Period of time that a newly created sector will wait for more deals to be packed in to before it starts to seal. // Sectors which are fully filled will start sealing immediately WaitDealsDelay Duration @@ -298,12 +337,6 @@ type Splitstore struct { } // // Full Node - -type Metrics struct { - Nickname string - HeadNotifs bool -} - type Client struct { UseIpfs bool IpfsOnlineMode bool diff --git a/node/hello/cbor_gen.go b/node/hello/cbor_gen.go index 7669f60ed..145a19728 100644 --- a/node/hello/cbor_gen.go +++ b/node/hello/cbor_gen.go @@ -5,6 +5,7 @@ package hello import ( "fmt" "io" + "math" "sort" abi "github.com/filecoin-project/go-state-types/abi" @@ -15,6 +16,7 @@ import ( var _ = xerrors.Errorf var _ = cid.Undef +var _ = math.E var _ = sort.Sort var lengthBufHelloMessage = []byte{132} diff --git a/node/impl/client/car_helpers.go b/node/impl/client/car_helpers.go new file mode 100644 index 000000000..c638b4bef --- /dev/null +++ b/node/impl/client/car_helpers.go @@ -0,0 +1,91 @@ +package client + +import ( + "fmt" + "io" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + "github.com/ipld/go-car/util" + "github.com/multiformats/go-varint" +) + +// ————————————————————————————————————————————————————————— +// +// This code is temporary, and should be deleted when +// https://github.com/ipld/go-car/issues/196 is resolved. +// +// ————————————————————————————————————————————————————————— + +func init() { + cbor.RegisterCborType(CarHeader{}) +} + +type CarHeader struct { + Roots []cid.Cid + Version uint64 +} + +func readHeader(r io.Reader) (*CarHeader, error) { + hb, err := ldRead(r, false) + if err != nil { + return nil, err + } + + var ch CarHeader + if err := cbor.DecodeInto(hb, &ch); err != nil { + return nil, fmt.Errorf("invalid header: %v", err) + } + + return &ch, nil +} + +func writeHeader(h *CarHeader, w io.Writer) error { + hb, err := cbor.DumpObject(h) + if err != nil { + return err + } + + return util.LdWrite(w, hb) +} + +func ldRead(r io.Reader, zeroLenAsEOF bool) ([]byte, error) { + l, err := varint.ReadUvarint(toByteReader(r)) + if err != nil { + // If the length of bytes read is non-zero when the error is EOF then signal an unclean EOF. + if l > 0 && err == io.EOF { + return nil, io.ErrUnexpectedEOF + } + return nil, err + } else if l == 0 && zeroLenAsEOF { + return nil, io.EOF + } + + buf := make([]byte, l) + if _, err := io.ReadFull(r, buf); err != nil { + return nil, err + } + + return buf, nil +} + +type readerPlusByte struct { + io.Reader +} + +func (rb readerPlusByte) ReadByte() (byte, error) { + return readByte(rb) +} + +func readByte(r io.Reader) (byte, error) { + var p [1]byte + _, err := io.ReadFull(r, p[:]) + return p[0], err +} + +func toByteReader(r io.Reader) io.ByteReader { + if br, ok := r.(io.ByteReader); ok { + return br + } + return &readerPlusByte{r} +} diff --git a/node/impl/client/client.go b/node/impl/client/client.go index 7ba6463e6..f06a62f90 100644 --- a/node/impl/client/client.go +++ b/node/impl/client/client.go @@ -2,6 +2,7 @@ package client import ( "bufio" + "bytes" "context" "fmt" "io" @@ -9,8 +10,11 @@ import ( "sort" "time" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - + bstore "github.com/ipfs/go-ipfs-blockstore" + unixfile "github.com/ipfs/go-unixfs/file" + "github.com/ipld/go-car" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/blockstore" "golang.org/x/xerrors" "github.com/filecoin-project/go-padreader" @@ -18,16 +22,9 @@ import ( "github.com/filecoin-project/go-state-types/dline" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" - "github.com/ipfs/go-cidutil" - chunker "github.com/ipfs/go-ipfs-chunker" offline "github.com/ipfs/go-ipfs-exchange-offline" files "github.com/ipfs/go-ipfs-files" - ipld "github.com/ipfs/go-ipld-format" "github.com/ipfs/go-merkledag" - unixfile "github.com/ipfs/go-unixfs/file" - "github.com/ipfs/go-unixfs/importer/balanced" - ihelper "github.com/ipfs/go-unixfs/importer/helpers" - "github.com/ipld/go-car" basicnode "github.com/ipld/go-ipld-prime/node/basic" "github.com/ipld/go-ipld-prime/traversal/selector" "github.com/ipld/go-ipld-prime/traversal/selector/builder" @@ -42,34 +39,41 @@ import ( "github.com/filecoin-project/go-commp-utils/ffiwrapper" "github.com/filecoin-project/go-commp-utils/writer" datatransfer "github.com/filecoin-project/go-data-transfer" + "github.com/filecoin-project/go-fil-markets/discovery" "github.com/filecoin-project/go-fil-markets/retrievalmarket" rm "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-fil-markets/shared" "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-fil-markets/storagemarket/network" - "github.com/filecoin-project/go-multistore" + "github.com/filecoin-project/go-fil-markets/stores" + + "github.com/filecoin-project/lotus/markets/retrievaladapter" + "github.com/filecoin-project/lotus/markets/storageadapter" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/specs-actors/v3/actors/builtin/market" marketevents "github.com/filecoin-project/lotus/markets/loggers" + "github.com/filecoin-project/lotus/node/repo/imports" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/markets/utils" "github.com/filecoin-project/lotus/node/impl/full" "github.com/filecoin-project/lotus/node/impl/paych" "github.com/filecoin-project/lotus/node/modules/dtypes" - "github.com/filecoin-project/lotus/node/repo/importmgr" - "github.com/filecoin-project/lotus/node/repo/retrievalstoremgr" + "github.com/filecoin-project/lotus/node/repo" ) var DefaultHashFunction = uint64(mh.BLAKE2B_MIN + 31) // 8 days ~= SealDuration + PreCommit + MaxProveCommitDuration + 8 hour buffer const dealStartBufferHours uint64 = 8 * 24 +const DefaultDAGStoreDir = "dagstore" type API struct { fx.In @@ -84,13 +88,15 @@ type API struct { Retrieval rm.RetrievalClient Chain *store.ChainStore - Imports dtypes.ClientImportMgr - Mds dtypes.ClientMultiDstore + // accessors for imports and retrievals. + Imports dtypes.ClientImportMgr + StorageBlockstoreAccessor storagemarket.BlockstoreAccessor + RtvlBlockstoreAccessor retrievalmarket.BlockstoreAccessor - CombinedBstore dtypes.ClientBlockstore // TODO: try to remove - RetrievalStoreMgr dtypes.ClientRetrievalStoreManager - DataTransfer dtypes.ClientDataTransfer - Host host.Host + DataTransfer dtypes.ClientDataTransfer + Host host.Host + + Repo repo.LockedRepo } func calcDealExpiration(minDuration uint64, md *dline.Info, startEpoch abi.ChainEpoch) abi.ChainEpoch { @@ -107,7 +113,8 @@ func calcDealExpiration(minDuration uint64, md *dline.Info, startEpoch abi.Chain return exp } -func (a *API) imgr() *importmgr.Mgr { +// importManager converts the injected type to the required type. +func (a *API) importManager() *imports.Manager { return a.Imports } @@ -120,7 +127,6 @@ func (a *API) ClientStatelessDeal(ctx context.Context, params *api.StartDealPara } func (a *API) dealStarter(ctx context.Context, params *api.StartDealParams, isStateless bool) (*cid.Cid, error) { - var storeID *multistore.StoreID if isStateless { if params.Data.TransferType != storagemarket.TTManual { return nil, xerrors.Errorf("invalid transfer type %s for stateless storage deal", params.Data.TransferType) @@ -129,24 +135,16 @@ func (a *API) dealStarter(ctx context.Context, params *api.StartDealParams, isSt return nil, xerrors.New("stateless storage deals can only be initiated with storage price of 0") } } else if params.Data.TransferType == storagemarket.TTGraphsync { - importIDs := a.imgr().List() - for _, importID := range importIDs { - info, err := a.imgr().Info(importID) - if err != nil { - continue - } - if info.Labels[importmgr.LRootCid] == "" { - continue - } - c, err := cid.Parse(info.Labels[importmgr.LRootCid]) - if err != nil { - continue - } - if c.Equals(params.Data.Root) { - storeID = &importID //nolint - break - } + bs, onDone, err := a.dealBlockstore(params.Data.Root) + if err != nil { + return nil, xerrors.Errorf("failed to find blockstore for root CID: %w", err) } + if has, err := bs.Has(params.Data.Root); err != nil { + return nil, xerrors.Errorf("failed to query blockstore for root CID: %w", err) + } else if !has { + return nil, xerrors.Errorf("failed to find root CID in blockstore: %w", err) + } + onDone() } walletKey, err := a.StateAccountKey(ctx, params.Wallet, types.EmptyTSK) @@ -212,7 +210,6 @@ func (a *API) dealStarter(ctx context.Context, params *api.StartDealParams, isSt Rt: st, FastRetrieval: params.FastRetrieval, VerifiedDeal: params.VerifiedDeal, - StoreID: storeID, }) if err != nil { @@ -411,17 +408,12 @@ func (a *API) newDealInfoWithTransfer(transferCh *api.DataTransferChannel, v sto } } -func (a *API) ClientHasLocal(ctx context.Context, root cid.Cid) (bool, error) { - // TODO: check if we have the ENTIRE dag - - offExch := merkledag.NewDAGService(blockservice.New(a.Imports.Blockstore, offline.Exchange(a.Imports.Blockstore))) - _, err := offExch.Get(ctx, root) - if err == ipld.ErrNotFound { - return false, nil - } +func (a *API) ClientHasLocal(_ context.Context, root cid.Cid) (bool, error) { + _, onDone, err := a.dealBlockstore(root) if err != nil { return false, err } + onDone() return true, nil } @@ -495,88 +487,219 @@ func (a *API) makeRetrievalQuery(ctx context.Context, rp rm.RetrievalPeer, paylo } } -func (a *API) ClientImport(ctx context.Context, ref api.FileRef) (*api.ImportRes, error) { - id, st, err := a.imgr().NewStore() +func (a *API) ClientImport(ctx context.Context, ref api.FileRef) (res *api.ImportRes, err error) { + var ( + imgr = a.importManager() + id imports.ID + root cid.Cid + carPath string + ) + + id, err = imgr.CreateImport() if err != nil { - return nil, err - } - if err := a.imgr().AddLabel(id, importmgr.LSource, "import"); err != nil { - return nil, err + return nil, xerrors.Errorf("failed to create import: %w", err) } - if err := a.imgr().AddLabel(id, importmgr.LFileName, ref.Path); err != nil { - return nil, err + if ref.IsCAR { + // user gave us a CAR fil, use it as-is + // validate that it's either a carv1 or carv2, and has one root. + f, err := os.Open(ref.Path) + if err != nil { + return nil, xerrors.Errorf("failed to open CAR file: %w", err) + } + defer f.Close() //nolint:errcheck + + hd, _, err := car.ReadHeader(bufio.NewReader(f)) + if err != nil { + return nil, xerrors.Errorf("failed to read CAR header: %w", err) + } + if len(hd.Roots) != 1 { + return nil, xerrors.New("car file can have one and only one header") + } + if hd.Version != 1 && hd.Version != 2 { + return nil, xerrors.Errorf("car version must be 1 or 2, is %d", hd.Version) + } + + carPath = ref.Path + root = hd.Roots[0] + } else { + carPath, err = imgr.AllocateCAR(id) + if err != nil { + return nil, xerrors.Errorf("failed to create car path for import: %w", err) + } + + // remove the import if something went wrong. + defer func() { + if err != nil { + _ = os.Remove(carPath) + _ = imgr.Remove(id) + } + }() + + // perform the unixfs chunking. + root, err = a.createUnixFSFilestore(ctx, ref.Path, carPath) + if err != nil { + return nil, xerrors.Errorf("failed to import file using unixfs: %w", err) + } } - nd, err := a.clientImport(ctx, ref, st) - if err != nil { + if err = imgr.AddLabel(id, imports.LSource, "import"); err != nil { return nil, err } - - if err := a.imgr().AddLabel(id, importmgr.LRootCid, nd.String()); err != nil { + if err = imgr.AddLabel(id, imports.LFileName, ref.Path); err != nil { + return nil, err + } + if err = imgr.AddLabel(id, imports.LCARPath, carPath); err != nil { + return nil, err + } + if err = imgr.AddLabel(id, imports.LRootCid, root.String()); err != nil { return nil, err } - return &api.ImportRes{ - Root: nd, + Root: root, ImportID: id, }, nil } -func (a *API) ClientRemoveImport(ctx context.Context, importID multistore.StoreID) error { - return a.imgr().Remove(importID) +func (a *API) ClientRemoveImport(ctx context.Context, id imports.ID) error { + info, err := a.importManager().Info(id) + if err != nil { + return xerrors.Errorf("failed to get import metadata: %w", err) + } + + owner := info.Labels[imports.LCAROwner] + path := info.Labels[imports.LCARPath] + + // CARv2 file was not provided by the user, delete it. + if path != "" && owner == imports.CAROwnerImportMgr { + _ = os.Remove(path) + } + + return a.importManager().Remove(id) } -func (a *API) ClientImportLocal(ctx context.Context, f io.Reader) (cid.Cid, error) { - file := files.NewReaderFile(f) +// ClientImportLocal imports a standard file into this node as a UnixFS payload, +// storing it in a CARv2 file. Note that this method is NOT integrated with the +// IPFS blockstore. That is, if client-side IPFS integration is enabled, this +// method won't import the file into that +func (a *API) ClientImportLocal(ctx context.Context, r io.Reader) (cid.Cid, error) { + file := files.NewReaderFile(r) - id, st, err := a.imgr().NewStore() + // write payload to temp file + id, err := a.importManager().CreateImport() if err != nil { return cid.Undef, err } - if err := a.imgr().AddLabel(id, "source", "import-local"); err != nil { - return cid.Cid{}, err + if err := a.importManager().AddLabel(id, imports.LSource, "import-local"); err != nil { + return cid.Undef, err } - bufferedDS := ipld.NewBufferedDAG(ctx, st.DAG) - - prefix, err := merkledag.PrefixForCidVersion(1) + path, err := a.importManager().AllocateCAR(id) if err != nil { return cid.Undef, err } - prefix.MhType = DefaultHashFunction - params := ihelper.DagBuilderParams{ - Maxlinks: build.UnixfsLinksPerLevel, - RawLeaves: true, - CidBuilder: cidutil.InlineBuilder{ - Builder: prefix, - Limit: 126, - }, - Dagserv: bufferedDS, - } + // writing a carv2 requires knowing the root ahead of time, which makes + // streaming cases impossible. + // https://github.com/ipld/go-car/issues/196 + // we work around this limitation by informing a placeholder root CID of the + // same length as our unixfs chunking strategy will generate. + // once the DAG is formed and the root is calculated, we overwrite the + // inner carv1 header with the final root. - db, err := params.New(chunker.NewSizeSplitter(file, int64(build.UnixfsChunkSize))) + b, err := unixFSCidBuilder() if err != nil { return cid.Undef, err } - nd, err := balanced.Layout(db) + + // placeholder payload needs to be larger than inline CID threshold; 256 + // bytes is a safe value. + placeholderRoot, err := b.Sum(make([]byte, 256)) if err != nil { - return cid.Undef, err - } - if err := a.imgr().AddLabel(id, "root", nd.Cid().String()); err != nil { - return cid.Cid{}, err + return cid.Undef, xerrors.Errorf("failed to calculate placeholder root: %w", err) } - return nd.Cid(), bufferedDS.Commit() + bs, err := blockstore.OpenReadWrite(path, []cid.Cid{placeholderRoot}, blockstore.UseWholeCIDs(true)) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to create carv2 read/write blockstore: %w", err) + } + + root, err := buildUnixFS(ctx, file, bs, false) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to build unixfs dag: %w", err) + } + + err = bs.Finalize() + if err != nil { + return cid.Undef, xerrors.Errorf("failed to finalize carv2 read/write blockstore: %w", err) + } + + // record the root in the import manager. + if err := a.importManager().AddLabel(id, imports.LRootCid, root.String()); err != nil { + return cid.Undef, xerrors.Errorf("failed to record root CID in import manager: %w", err) + } + + // now go ahead and overwrite the root in the carv1 header. + reader, err := carv2.OpenReader(path) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to create car reader: %w", err) + } + + // save the header offset. + headerOff := reader.Header.DataOffset + + // read the old header. + dr := reader.DataReader() + header, err := readHeader(dr) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to read car reader: %w", err) + } + _ = reader.Close() // close the CAR reader. + + // write the old header into a buffer. + var oldBuf bytes.Buffer + if err = writeHeader(header, &oldBuf); err != nil { + return cid.Undef, xerrors.Errorf("failed to write header into buffer: %w", err) + } + + // replace the root. + header.Roots = []cid.Cid{root} + + // write the new header into a buffer. + var newBuf bytes.Buffer + err = writeHeader(header, &newBuf) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to write header into buffer: %w", err) + } + + // verify the length matches. + if newBuf.Len() != oldBuf.Len() { + return cid.Undef, xerrors.Errorf("failed to replace carv1 header; length mismatch (old: %d, new: %d)", oldBuf.Len(), newBuf.Len()) + } + + // open the file again, seek to the header position, and write. + f, err := os.OpenFile(path, os.O_WRONLY, 0755) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to open car: %w", err) + } + defer f.Close() //nolint:errcheck + + n, err := f.WriteAt(newBuf.Bytes(), int64(headerOff)) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to write new header to car (bytes written: %d): %w", n, err) + } + return root, nil } -func (a *API) ClientListImports(ctx context.Context) ([]api.Import, error) { - importIDs := a.imgr().List() +func (a *API) ClientListImports(_ context.Context) ([]api.Import, error) { + ids, err := a.importManager().List() + if err != nil { + return nil, xerrors.Errorf("failed to fetch imports: %w", err) + } - out := make([]api.Import, len(importIDs)) - for i, id := range importIDs { - info, err := a.imgr().Info(id) + out := make([]api.Import, len(ids)) + for i, id := range ids { + info, err := a.importManager().Info(id) if err != nil { out[i] = api.Import{ Key: id, @@ -587,12 +710,13 @@ func (a *API) ClientListImports(ctx context.Context) ([]api.Import, error) { ai := api.Import{ Key: id, - Source: info.Labels[importmgr.LSource], - FilePath: info.Labels[importmgr.LFileName], + Source: info.Labels[imports.LSource], + FilePath: info.Labels[imports.LFileName], + CARPath: info.Labels[imports.LCARPath], } - if info.Labels[importmgr.LRootCid] != "" { - c, err := cid.Parse(info.Labels[importmgr.LRootCid]) + if info.Labels[imports.LRootCid] != "" { + c, err := cid.Parse(info.Labels[imports.LRootCid]) if err != nil { ai.Err = err.Error() } else { @@ -660,7 +784,7 @@ type retrievalSubscribeEvent struct { state rm.ClientDealState } -func readSubscribeEvents(ctx context.Context, dealID retrievalmarket.DealID, subscribeEvents chan retrievalSubscribeEvent, events chan marketevents.RetrievalEvent) error { +func consumeAllEvents(ctx context.Context, dealID retrievalmarket.DealID, subscribeEvents chan retrievalSubscribeEvent, events chan marketevents.RetrievalEvent) error { for { var subscribeEvent retrievalSubscribeEvent select { @@ -711,9 +835,28 @@ func (a *API) clientRetrieve(ctx context.Context, order api.RetrievalOrder, ref } } - var store retrievalstoremgr.RetrievalStore + // summary: + // 1. if we're retrieving from an import, FromLocalCAR will be informed. + // Open as a Filestore and populate the target CAR or UnixFS export from it. + // (cannot use ExtractV1File because user wants a dense CAR, not a ref CAR/filestore) + // 2. if we're using an IPFS blockstore for retrieval, retrieve into it, + // then extract the CAR or UnixFS export from it. + // 3. if we have to retrieve, perform a CARv2 retrieval, then extract + // the CARv1 (with ExtractV1File) or UnixFS export from it. + + // this indicates we're proxying to IPFS. + proxyBss, retrieveIntoIPFS := a.RtvlBlockstoreAccessor.(*retrievaladapter.ProxyBlockstoreAccessor) + carBss, retrieveIntoCAR := a.RtvlBlockstoreAccessor.(*retrievaladapter.CARBlockstoreAccessor) + + carPath := order.FromLocalCAR + if carPath == "" { + if !retrieveIntoIPFS && !retrieveIntoCAR { + // we actually need to retrieve from the network, but we don't + // recognize the blockstore accessor. + finish(xerrors.Errorf("unsupported retrieval blockstore accessor")) + return + } - if order.LocalStore == nil { if order.MinerPeer == nil || order.MinerPeer.ID == "" { mi, err := a.StateMinerInfo(ctx, order.Miner, types.EmptyTSK) if err != nil { @@ -737,14 +880,6 @@ func (a *API) clientRetrieve(ctx context.Context, order api.RetrievalOrder, ref return } - /*id, st, err := a.imgr().NewStore() - if err != nil { - return err - } - if err := a.imgr().AddLabel(id, "source", "retrieval"); err != nil { - return err - }*/ - ppb := types.BigDiv(order.Total, types.NewInt(order.Size)) params, err := rm.NewParamsV1(ppb, order.PaymentInterval, order.PaymentIntervalIncrease, shared.AllSelector(), order.Piece, order.UnsealPrice) @@ -753,22 +888,12 @@ func (a *API) clientRetrieve(ctx context.Context, order api.RetrievalOrder, ref return } - store, err = a.RetrievalStoreMgr.NewStore() - if err != nil { - finish(xerrors.Errorf("Error setting up new store: %w", err)) - return - } - - defer func() { - _ = a.RetrievalStoreMgr.ReleaseStore(store) - }() - // Subscribe to events before retrieving to avoid losing events. subscribeEvents := make(chan retrievalSubscribeEvent, 1) subscribeCtx, cancel := context.WithCancel(ctx) defer cancel() unsubscribe := a.Retrieval.SubscribeToEvents(func(event rm.ClientEvent, state rm.ClientDealState) { - // We'll check the deal IDs inside readSubscribeEvents. + // We'll check the deal IDs inside consumeAllEvents. if state.PayloadCID.Equals(order.Root) { select { case <-subscribeCtx.Done(): @@ -777,15 +902,17 @@ func (a *API) clientRetrieve(ctx context.Context, order api.RetrievalOrder, ref } }) - dealID, err := a.Retrieval.Retrieve( + id := a.Retrieval.NextID() + id, err = a.Retrieval.Retrieve( ctx, + id, order.Root, params, order.Total, *order.MinerPeer, order.Client, order.Miner, - store.StoreID()) + ) if err != nil { unsubscribe() @@ -793,62 +920,82 @@ func (a *API) clientRetrieve(ctx context.Context, order api.RetrievalOrder, ref return } - err = readSubscribeEvents(ctx, dealID, subscribeEvents, events) + err = consumeAllEvents(ctx, id, subscribeEvents, events) unsubscribe() if err != nil { finish(xerrors.Errorf("Retrieve: %w", err)) return } - } else { - // local retrieval - st, err := ((*multistore.MultiStore)(a.Mds)).Get(*order.LocalStore) - if err != nil { - finish(xerrors.Errorf("Retrieve: %w", err)) - return - } - store = &multiStoreRetrievalStore{ - storeID: *order.LocalStore, - store: st, + if retrieveIntoCAR { + carPath = carBss.PathFor(id) } } - // If ref is nil, it only fetches the data into the configured blockstore. if ref == nil { + // If ref is nil, it only fetches the data into the configured blockstore + // (if fetching from network). finish(nil) return } - rdag := store.DAGService() - + // Are we outputting a CAR? if ref.IsCAR { - f, err := os.OpenFile(ref.Path, os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - finish(err) + if retrieveIntoIPFS { + // generating a CARv1 from IPFS. + f, err := os.OpenFile(ref.Path, os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + finish(err) + return + } + + bs := proxyBss.Blockstore + dags := merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs))) + err = car.WriteCar(ctx, dags, []cid.Cid{order.Root}, f) + if err != nil { + finish(err) + return + } + finish(f.Close()) return } - err = car.WriteCar(ctx, rdag, []cid.Cid{order.Root}, f) - if err != nil { - finish(err) - return - } - finish(f.Close()) + + // generating a CARv1 from the CARv2 where we stored the retrieval. + err := carv2.ExtractV1File(carPath, ref.Path) + finish(err) return } - nd, err := rdag.Get(ctx, order.Root) + // we are extracting a UnixFS file. + var bs bstore.Blockstore + if retrieveIntoIPFS { + bs = proxyBss.Blockstore + } else { + cbs, err := stores.ReadOnlyFilestore(carPath) + if err != nil { + finish(err) + return + } + defer cbs.Close() //nolint:errcheck + bs = cbs + } + + bsvc := blockservice.New(bs, offline.Exchange(bs)) + dag := merkledag.NewDAGService(bsvc) + + nd, err := dag.Get(ctx, order.Root) if err != nil { finish(xerrors.Errorf("ClientRetrieve: %w", err)) return } - file, err := unixfile.NewUnixfsFile(ctx, rdag, nd) + file, err := unixfile.NewUnixfsFile(ctx, dag, nd) if err != nil { finish(xerrors.Errorf("ClientRetrieve: %w", err)) return } + finish(files.WriteTo(file, ref.Path)) - return } func (a *API) ClientListRetrievals(ctx context.Context) ([]api.RetrievalInfo, error) { @@ -928,19 +1075,6 @@ func (a *API) newRetrievalInfo(ctx context.Context, v rm.ClientDealState) api.Re return a.newRetrievalInfoWithTransfer(transferCh, v) } -type multiStoreRetrievalStore struct { - storeID multistore.StoreID - store *multistore.Store -} - -func (mrs *multiStoreRetrievalStore) StoreID() *multistore.StoreID { - return &mrs.storeID -} - -func (mrs *multiStoreRetrievalStore) DAGService() ipld.DAGService { - return mrs.store.DAG -} - func (a *API) ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.StorageAsk, error) { mi, err := a.StateMinerInfo(ctx, miner, types.EmptyTSK) if err != nil { @@ -1009,11 +1143,16 @@ func (w *lenWriter) Write(p []byte) (n int, err error) { } func (a *API) ClientDealSize(ctx context.Context, root cid.Cid) (api.DataSize, error) { - dag := merkledag.NewDAGService(blockservice.New(a.CombinedBstore, offline.Exchange(a.CombinedBstore))) + bs, onDone, err := a.dealBlockstore(root) + if err != nil { + return api.DataSize{}, err + } + defer onDone() - w := lenWriter(0) + dag := merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs))) - err := car.WriteCar(ctx, dag, []cid.Cid{root}, &w) + var w lenWriter + err = car.WriteCar(ctx, dag, []cid.Cid{root}, &w) if err != nil { return api.DataSize{}, err } @@ -1027,12 +1166,17 @@ func (a *API) ClientDealSize(ctx context.Context, root cid.Cid) (api.DataSize, e } func (a *API) ClientDealPieceCID(ctx context.Context, root cid.Cid) (api.DataCIDSize, error) { - dag := merkledag.NewDAGService(blockservice.New(a.CombinedBstore, offline.Exchange(a.CombinedBstore))) + bs, onDone, err := a.dealBlockstore(root) + if err != nil { + return api.DataCIDSize{}, err + } + defer onDone() + dag := merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs))) w := &writer.Writer{} bw := bufio.NewWriterSize(w, int(writer.CommPBuf)) - err := car.WriteCar(ctx, dag, []cid.Cid{root}, w) + err = car.WriteCar(ctx, dag, []cid.Cid{root}, w) if err != nil { return api.DataCIDSize{}, err } @@ -1046,113 +1190,49 @@ func (a *API) ClientDealPieceCID(ctx context.Context, root cid.Cid) (api.DataCID } func (a *API) ClientGenCar(ctx context.Context, ref api.FileRef, outputPath string) error { - id, st, err := a.imgr().NewStore() + // create a temporary import to represent this job and obtain a staging CAR. + id, err := a.importManager().CreateImport() if err != nil { - return err - } - if err := a.imgr().AddLabel(id, "source", "gen-car"); err != nil { - return err + return xerrors.Errorf("failed to create temporary import: %w", err) } + defer a.importManager().Remove(id) //nolint:errcheck - bufferedDS := ipld.NewBufferedDAG(ctx, st.DAG) - c, err := a.clientImport(ctx, ref, st) - + tmp, err := a.importManager().AllocateCAR(id) if err != nil { - return err + return xerrors.Errorf("failed to allocate temporary CAR: %w", err) + } + defer os.Remove(tmp) //nolint:errcheck + + // generate and import the UnixFS DAG into a filestore (positional reference) CAR. + root, err := a.createUnixFSFilestore(ctx, ref.Path, tmp) + if err != nil { + return xerrors.Errorf("failed to import file using unixfs: %w", err) } - // TODO: does that defer mean to remove the whole blockstore? - defer bufferedDS.Remove(ctx, c) //nolint:errcheck + // open the positional reference CAR as a filestore. + fs, err := stores.ReadOnlyFilestore(tmp) + if err != nil { + return xerrors.Errorf("failed to open filestore from carv2 in path %s: %w", tmp, err) + } + defer fs.Close() //nolint:errcheck + + // build a dense deterministic CAR (dense = containing filled leaves) ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any) - - // entire DAG selector - allSelector := ssb.ExploreRecursive(selector.RecursionLimitNone(), + allSelector := ssb.ExploreRecursive( + selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge())).Node() - + sc := car.NewSelectiveCar(ctx, fs, []car.Dag{{Root: root, Selector: allSelector}}) f, err := os.Create(outputPath) if err != nil { return err } - - sc := car.NewSelectiveCar(ctx, st.Bstore, []car.Dag{{Root: c, Selector: allSelector}}) if err = sc.Write(f); err != nil { - return err + return xerrors.Errorf("failed to write CAR to output file: %w", err) } return f.Close() } -func (a *API) clientImport(ctx context.Context, ref api.FileRef, store *multistore.Store) (cid.Cid, error) { - f, err := os.Open(ref.Path) - if err != nil { - return cid.Undef, err - } - defer f.Close() //nolint:errcheck - - stat, err := f.Stat() - if err != nil { - return cid.Undef, err - } - - file, err := files.NewReaderPathFile(ref.Path, f, stat) - if err != nil { - return cid.Undef, err - } - - if ref.IsCAR { - var st car.Store - if store.Fstore == nil { - st = store.Bstore - } else { - st = store.Fstore - } - result, err := car.LoadCar(st, file) - if err != nil { - return cid.Undef, err - } - - if len(result.Roots) != 1 { - return cid.Undef, xerrors.New("cannot import car with more than one root") - } - - return result.Roots[0], nil - } - - bufDs := ipld.NewBufferedDAG(ctx, store.DAG) - - prefix, err := merkledag.PrefixForCidVersion(1) - if err != nil { - return cid.Undef, err - } - prefix.MhType = DefaultHashFunction - - params := ihelper.DagBuilderParams{ - Maxlinks: build.UnixfsLinksPerLevel, - RawLeaves: true, - CidBuilder: cidutil.InlineBuilder{ - Builder: prefix, - Limit: 126, - }, - Dagserv: bufDs, - NoCopy: true, - } - - db, err := params.New(chunker.NewSizeSplitter(file, int64(build.UnixfsChunkSize))) - if err != nil { - return cid.Undef, err - } - nd, err := balanced.Layout(db) - if err != nil { - return cid.Undef, err - } - - if err := bufDs.Commit(); err != nil { - return cid.Undef, err - } - - return nd.Cid(), nil -} - func (a *API) ClientListDataTransfers(ctx context.Context) ([]api.DataTransferChannel, error) { inProgressChannels, err := a.DataTransfer.InProgressChannels(ctx) if err != nil { @@ -1214,3 +1294,27 @@ func (a *API) ClientGetDealStatus(ctx context.Context, statusCode uint64) (strin return ststr, nil } + +// dealBlockstore picks the source blockstore for a storage deal; either the +// IPFS blockstore, or an import CARv2 file. It also returns a function that +// must be called when done. +func (a *API) dealBlockstore(root cid.Cid) (bstore.Blockstore, func(), error) { + switch acc := a.StorageBlockstoreAccessor.(type) { + case *storageadapter.ImportsBlockstoreAccessor: + bs, err := acc.Get(root) + if err != nil { + return nil, nil, xerrors.Errorf("no import found for root %s: %w", root, err) + } + + doneFn := func() { + _ = acc.Done(root) //nolint:errcheck + } + return bs, doneFn, nil + + case *storageadapter.ProxyBlockstoreAccessor: + return acc.Blockstore, func() {}, nil + + default: + return nil, nil, xerrors.Errorf("unsupported blockstore accessor type: %T", acc) + } +} diff --git a/node/impl/client/client_test.go b/node/impl/client/client_test.go index da13c8ef3..834c980ab 100644 --- a/node/impl/client/client_test.go +++ b/node/impl/client/client_test.go @@ -1 +1,130 @@ package client + +import ( + "bytes" + "context" + "embed" + "io/ioutil" + "path/filepath" + "strings" + "testing" + + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-datastore" + dssync "github.com/ipfs/go-datastore/sync" + blockstore "github.com/ipfs/go-ipfs-blockstore" + offline "github.com/ipfs/go-ipfs-exchange-offline" + files "github.com/ipfs/go-ipfs-files" + "github.com/ipfs/go-merkledag" + unixfile "github.com/ipfs/go-unixfs/file" + "github.com/ipld/go-car" + carv2 "github.com/ipld/go-car/v2" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/markets/storageadapter" + "github.com/filecoin-project/lotus/node/repo/imports" +) + +//go:embed testdata/* +var testdata embed.FS + +func TestImportLocal(t *testing.T) { + ds := dssync.MutexWrap(datastore.NewMapDatastore()) + dir := t.TempDir() + im := imports.NewManager(ds, dir) + ctx := context.Background() + + a := &API{ + Imports: im, + StorageBlockstoreAccessor: storageadapter.NewImportsBlockstoreAccessor(im), + } + + b, err := testdata.ReadFile("testdata/payload.txt") + require.NoError(t, err) + + root, err := a.ClientImportLocal(ctx, bytes.NewReader(b)) + require.NoError(t, err) + require.NotEqual(t, cid.Undef, root) + + list, err := a.ClientListImports(ctx) + require.NoError(t, err) + require.Len(t, list, 1) + + it := list[0] + require.Equal(t, root, *it.Root) + require.True(t, strings.HasPrefix(it.CARPath, dir)) + + local, err := a.ClientHasLocal(ctx, root) + require.NoError(t, err) + require.True(t, local) + + order := api.RetrievalOrder{ + Root: root, + FromLocalCAR: it.CARPath, + } + + // retrieve as UnixFS. + out1 := filepath.Join(dir, "retrieval1.data") // as unixfs + out2 := filepath.Join(dir, "retrieval2.data") // as car + err = a.ClientRetrieve(ctx, order, &api.FileRef{ + Path: out1, + }) + require.NoError(t, err) + + outBytes, err := ioutil.ReadFile(out1) + require.NoError(t, err) + require.Equal(t, b, outBytes) + + err = a.ClientRetrieve(ctx, order, &api.FileRef{ + Path: out2, + IsCAR: true, + }) + require.NoError(t, err) + + // open the CARv2 being custodied by the import manager + orig, err := carv2.OpenReader(it.CARPath) + require.NoError(t, err) + + // open the CARv1 we just exported + exported, err := carv2.OpenReader(out2) + require.NoError(t, err) + + require.EqualValues(t, 1, exported.Version) + require.EqualValues(t, 2, orig.Version) + + origRoots, err := orig.Roots() + require.NoError(t, err) + require.Len(t, origRoots, 1) + + exportedRoots, err := exported.Roots() + require.NoError(t, err) + require.Len(t, exportedRoots, 1) + + require.EqualValues(t, origRoots, exportedRoots) + + // recreate the unixfs dag, and see if it matches the original file byte by byte + // import the car into a memory blockstore, then export the unixfs file. + bs := blockstore.NewBlockstore(datastore.NewMapDatastore()) + _, err = car.LoadCar(bs, exported.DataReader()) + require.NoError(t, err) + + dag := merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs))) + + nd, err := dag.Get(ctx, exportedRoots[0]) + require.NoError(t, err) + + file, err := unixfile.NewUnixfsFile(ctx, dag, nd) + require.NoError(t, err) + + exportedPath := filepath.Join(dir, "exported.data") + err = files.WriteTo(file, exportedPath) + require.NoError(t, err) + + exportedBytes, err := ioutil.ReadFile(exportedPath) + require.NoError(t, err) + + // compare original file to recreated unixfs file. + require.Equal(t, b, exportedBytes) +} diff --git a/node/impl/client/import.go b/node/impl/client/import.go new file mode 100644 index 000000000..367cc73c7 --- /dev/null +++ b/node/impl/client/import.go @@ -0,0 +1,156 @@ +package client + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "os" + + "github.com/filecoin-project/go-fil-markets/stores" + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-cidutil" + bstore "github.com/ipfs/go-ipfs-blockstore" + chunker "github.com/ipfs/go-ipfs-chunker" + offline "github.com/ipfs/go-ipfs-exchange-offline" + files "github.com/ipfs/go-ipfs-files" + ipld "github.com/ipfs/go-ipld-format" + "github.com/ipfs/go-merkledag" + "github.com/ipfs/go-unixfs/importer/balanced" + ihelper "github.com/ipfs/go-unixfs/importer/helpers" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/build" +) + +func unixFSCidBuilder() (cid.Builder, error) { + prefix, err := merkledag.PrefixForCidVersion(1) + if err != nil { + return nil, fmt.Errorf("failed to initialize UnixFS CID Builder: %w", err) + } + prefix.MhType = DefaultHashFunction + b := cidutil.InlineBuilder{ + Builder: prefix, + Limit: 126, + } + return b, nil +} + +// createUnixFSFilestore takes a standard file whose path is src, forms a UnixFS DAG, and +// writes a CARv2 file with positional mapping (backed by the go-filestore library). +func (a *API) createUnixFSFilestore(ctx context.Context, srcPath string, dstPath string) (cid.Cid, error) { + // This method uses a two-phase approach with a staging CAR blockstore and + // a final CAR blockstore. + // + // This is necessary because of https://github.com/ipld/go-car/issues/196 + // + // TODO: do we need to chunk twice? Isn't the first output already in the + // right order? Can't we just copy the CAR file and replace the header? + + src, err := os.Open(srcPath) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to open input file: %w", err) + } + defer src.Close() //nolint:errcheck + + stat, err := src.Stat() + if err != nil { + return cid.Undef, xerrors.Errorf("failed to stat file :%w", err) + } + + file, err := files.NewReaderPathFile(srcPath, src, stat) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to create reader path file: %w", err) + } + + f, err := ioutil.TempFile("", "") + if err != nil { + return cid.Undef, xerrors.Errorf("failed to create temp file: %w", err) + } + _ = f.Close() // close; we only want the path. + + tmp := f.Name() + defer os.Remove(tmp) //nolint:errcheck + + // Step 1. Compute the UnixFS DAG and write it to a CARv2 file to get + // the root CID of the DAG. + fstore, err := stores.ReadWriteFilestore(tmp) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to create temporary filestore: %w", err) + } + + finalRoot1, err := buildUnixFS(ctx, file, fstore, true) + if err != nil { + _ = fstore.Close() + return cid.Undef, xerrors.Errorf("failed to import file to store to compute root: %w", err) + } + + if err := fstore.Close(); err != nil { + return cid.Undef, xerrors.Errorf("failed to finalize car filestore: %w", err) + } + + // Step 2. We now have the root of the UnixFS DAG, and we can write the + // final CAR for real under `dst`. + bs, err := stores.ReadWriteFilestore(dstPath, finalRoot1) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to create a carv2 read/write filestore: %w", err) + } + + // rewind file to the beginning. + if _, err := src.Seek(0, 0); err != nil { + return cid.Undef, xerrors.Errorf("failed to rewind file: %w", err) + } + + finalRoot2, err := buildUnixFS(ctx, file, bs, true) + if err != nil { + _ = bs.Close() + return cid.Undef, xerrors.Errorf("failed to create UnixFS DAG with carv2 blockstore: %w", err) + } + + if err := bs.Close(); err != nil { + return cid.Undef, xerrors.Errorf("failed to finalize car blockstore: %w", err) + } + + if finalRoot1 != finalRoot2 { + return cid.Undef, xerrors.New("roots do not match") + } + + return finalRoot1, nil +} + +// buildUnixFS builds a UnixFS DAG out of the supplied reader, +// and imports the DAG into the supplied service. +func buildUnixFS(ctx context.Context, reader io.Reader, into bstore.Blockstore, filestore bool) (cid.Cid, error) { + b, err := unixFSCidBuilder() + if err != nil { + return cid.Undef, err + } + + bsvc := blockservice.New(into, offline.Exchange(into)) + dags := merkledag.NewDAGService(bsvc) + bufdag := ipld.NewBufferedDAG(ctx, dags) + + params := ihelper.DagBuilderParams{ + Maxlinks: build.UnixfsLinksPerLevel, + RawLeaves: true, + CidBuilder: b, + Dagserv: bufdag, + NoCopy: filestore, + } + + db, err := params.New(chunker.NewSizeSplitter(reader, int64(build.UnixfsChunkSize))) + if err != nil { + return cid.Undef, err + } + nd, err := balanced.Layout(db) + if err != nil { + return cid.Undef, err + } + + if err := bufdag.Commit(); err != nil { + return cid.Undef, err + } + + return nd.Cid(), nil +} diff --git a/node/impl/client/import_test.go b/node/impl/client/import_test.go new file mode 100644 index 000000000..adf6531d0 --- /dev/null +++ b/node/impl/client/import_test.go @@ -0,0 +1,133 @@ +package client + +import ( + "bytes" + "context" + "io" + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" + offline "github.com/ipfs/go-ipfs-exchange-offline" + files "github.com/ipfs/go-ipfs-files" + "github.com/ipfs/go-merkledag" + unixfile "github.com/ipfs/go-unixfs/file" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/blockstore" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-fil-markets/stores" + + "github.com/filecoin-project/lotus/node/repo/imports" +) + +// This test uses a full "dense" CARv2, and not a filestore (positional mapping). +func TestRoundtripUnixFS_Dense(t *testing.T) { + ctx := context.Background() + + inputPath, inputContents := genInputFile(t) + defer os.Remove(inputPath) //nolint:errcheck + + carv2File := newTmpFile(t) + defer os.Remove(carv2File) //nolint:errcheck + + // import a file to a Unixfs DAG using a CARv2 read/write blockstore. + bs, err := blockstore.OpenReadWrite(carv2File, nil, + carv2.ZeroLengthSectionAsEOF(true), + blockstore.UseWholeCIDs(true)) + require.NoError(t, err) + + root, err := buildUnixFS(ctx, bytes.NewBuffer(inputContents), bs, false) + require.NoError(t, err) + require.NotEqual(t, cid.Undef, root) + require.NoError(t, bs.Finalize()) + + // reconstruct the file. + readOnly, err := blockstore.OpenReadOnly(carv2File, + carv2.ZeroLengthSectionAsEOF(true), + blockstore.UseWholeCIDs(true)) + require.NoError(t, err) + defer readOnly.Close() //nolint:errcheck + + dags := merkledag.NewDAGService(blockservice.New(readOnly, offline.Exchange(readOnly))) + + nd, err := dags.Get(ctx, root) + require.NoError(t, err) + + file, err := unixfile.NewUnixfsFile(ctx, dags, nd) + require.NoError(t, err) + + tmpOutput := newTmpFile(t) + defer os.Remove(tmpOutput) //nolint:errcheck + require.NoError(t, files.WriteTo(file, tmpOutput)) + + // ensure contents of the initial input file and the output file are identical. + fo, err := os.Open(tmpOutput) + require.NoError(t, err) + bz2, err := ioutil.ReadAll(fo) + require.NoError(t, err) + require.NoError(t, fo.Close()) + require.Equal(t, inputContents, bz2) +} + +func TestRoundtripUnixFS_Filestore(t *testing.T) { + ctx := context.Background() + a := &API{ + Imports: &imports.Manager{}, + } + + inputPath, inputContents := genInputFile(t) + defer os.Remove(inputPath) //nolint:errcheck + + dst := newTmpFile(t) + defer os.Remove(dst) //nolint:errcheck + + root, err := a.createUnixFSFilestore(ctx, inputPath, dst) + require.NoError(t, err) + require.NotEqual(t, cid.Undef, root) + + // convert the CARv2 to a normal file again and ensure the contents match + fs, err := stores.ReadOnlyFilestore(dst) + require.NoError(t, err) + defer fs.Close() //nolint:errcheck + + dags := merkledag.NewDAGService(blockservice.New(fs, offline.Exchange(fs))) + + nd, err := dags.Get(ctx, root) + require.NoError(t, err) + + file, err := unixfile.NewUnixfsFile(ctx, dags, nd) + require.NoError(t, err) + + tmpOutput := newTmpFile(t) + defer os.Remove(tmpOutput) //nolint:errcheck + require.NoError(t, files.WriteTo(file, tmpOutput)) + + // ensure contents of the initial input file and the output file are identical. + fo, err := os.Open(tmpOutput) + require.NoError(t, err) + bz2, err := ioutil.ReadAll(fo) + require.NoError(t, err) + require.NoError(t, fo.Close()) + require.Equal(t, inputContents, bz2) +} + +func newTmpFile(t *testing.T) string { + f, err := os.CreateTemp("", "") + require.NoError(t, err) + require.NoError(t, f.Close()) + return f.Name() +} + +func genInputFile(t *testing.T) (filepath string, contents []byte) { + s := strings.Repeat("abcde", 100) + tmp, err := os.CreateTemp("", "") + require.NoError(t, err) + _, err = io.Copy(tmp, strings.NewReader(s)) + require.NoError(t, err) + require.NoError(t, tmp.Close()) + return tmp.Name(), []byte(s) +} diff --git a/node/impl/client/testdata/duplicate_blocks.txt b/node/impl/client/testdata/duplicate_blocks.txt new file mode 100644 index 000000000..53695d7b9 --- /dev/null +++ b/node/impl/client/testdata/duplicate_blocks.txt @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd \ No newline at end of file diff --git a/node/impl/client/testdata/payload.txt b/node/impl/client/testdata/payload.txt new file mode 100644 index 000000000..fd4a2f3c1 --- /dev/null +++ b/node/impl/client/testdata/payload.txt @@ -0,0 +1,49 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae semper quis lectus nulla at volutpat diam ut venenatis. Ac tortor dignissim convallis aenean et tortor at. Faucibus ornare suspendisse sed nisi lacus sed. Commodo ullamcorper a lacus vestibulum sed arcu non. Est pellentesque elit ullamcorper dignissim. Quam quisque id diam vel quam. Pretium aenean pharetra magna ac. In nulla posuere sollicitudin aliquam ultrices. Sed arcu non odio euismod lacinia at. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla pellentesque. Feugiat vivamus at augue eget arcu. + +Pellentesque nec nam aliquam sem et tortor. Vitae tortor condimentum lacinia quis vel. Cras pulvinar mattis nunc sed. In massa tempor nec feugiat. Ornare arcu odio ut sem nulla. Diam maecenas sed enim ut sem. Pretium vulputate sapien nec sagittis. Bibendum arcu vitae elementum curabitur vitae nunc sed velit dignissim. Duis ut diam quam nulla porttitor massa. Viverra mauris in aliquam sem fringilla ut morbi. Ullamcorper eget nulla facilisi etiam dignissim. Vulputate mi sit amet mauris commodo quis imperdiet massa tincidunt. Nunc consequat interdum varius sit. Nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Nunc sed augue lacus viverra. Lobortis scelerisque fermentum dui faucibus in ornare quam. Urna neque viverra justo nec ultrices. Varius vel pharetra vel turpis nunc eget lorem dolor sed. + +Feugiat nisl pretium fusce id velit ut tortor pretium. Lorem dolor sed viverra ipsum nunc aliquet bibendum. Ultrices vitae auctor eu augue ut lectus. Pharetra massa massa ultricies mi quis. Nibh cras pulvinar mattis nunc sed blandit libero. Ac felis donec et odio pellentesque diam volutpat. Lectus proin nibh nisl condimentum id venenatis. Quis vel eros donec ac odio. Commodo sed egestas egestas fringilla phasellus faucibus scelerisque eleifend donec. Adipiscing diam donec adipiscing tristique. + +Tempus imperdiet nulla malesuada pellentesque elit eget gravida cum sociis. Libero nunc consequat interdum varius sit. Et pharetra pharetra massa massa. Feugiat pretium nibh ipsum consequat. Amet commodo nulla facilisi nullam vehicula. Ornare arcu dui vivamus arcu felis bibendum ut tristique. At erat pellentesque adipiscing commodo elit at imperdiet dui. Auctor neque vitae tempus quam pellentesque nec nam aliquam sem. Eget velit aliquet sagittis id consectetur. Enim diam vulputate ut pharetra sit amet aliquam id diam. Eget velit aliquet sagittis id consectetur purus ut faucibus pulvinar. Amet porttitor eget dolor morbi. Felis eget velit aliquet sagittis id. Facilisis magna etiam tempor orci eu. Lacus suspendisse faucibus interdum posuere lorem. Pharetra et ultrices neque ornare aenean euismod. Platea dictumst quisque sagittis purus. + +Quis varius quam quisque id diam vel quam elementum. Augue mauris augue neque gravida in fermentum et sollicitudin. Sapien nec sagittis aliquam malesuada bibendum arcu. Urna duis convallis convallis tellus id interdum velit. Tellus in hac habitasse platea dictumst vestibulum. Fames ac turpis egestas maecenas pharetra convallis. Diam volutpat commodo sed egestas egestas fringilla phasellus faucibus. Placerat orci nulla pellentesque dignissim enim sit amet venenatis. Sed adipiscing diam donec adipiscing. Praesent elementum facilisis leo vel fringilla est. Sed enim ut sem viverra aliquet eget sit amet tellus. Proin sagittis nisl rhoncus mattis rhoncus urna neque viverra. Turpis egestas pretium aenean pharetra magna ac placerat vestibulum. Massa id neque aliquam vestibulum morbi blandit cursus risus. Vitae congue eu consequat ac. Egestas erat imperdiet sed euismod nisi porta lorem mollis aliquam. Dolor purus non enim praesent elementum facilisis. Ultrices mi tempus imperdiet nulla malesuada pellentesque elit. In est ante in nibh. + +Facilisis gravida neque convallis a. Urna nunc id cursus metus aliquam eleifend mi. Lacus luctus accumsan tortor posuere ac. Molestie nunc non blandit massa. Iaculis urna id volutpat lacus laoreet non. Cursus vitae congue mauris rhoncus aenean. Nunc vel risus commodo viverra maecenas. A pellentesque sit amet porttitor eget dolor morbi. Leo vel orci porta non pulvinar neque laoreet suspendisse. Sit amet facilisis magna etiam tempor. Consectetur a erat nam at lectus urna duis convallis convallis. Vestibulum morbi blandit cursus risus at ultrices. Dolor purus non enim praesent elementum. Adipiscing elit pellentesque habitant morbi tristique senectus et netus et. Et odio pellentesque diam volutpat commodo sed egestas egestas fringilla. Leo vel fringilla est ullamcorper eget nulla. Dui ut ornare lectus sit amet. Erat pellentesque adipiscing commodo elit at imperdiet dui accumsan sit. + +Tristique senectus et netus et. Pellentesque diam volutpat commodo sed egestas egestas fringilla. Mauris pharetra et ultrices neque ornare aenean. Amet tellus cras adipiscing enim. Convallis aenean et tortor at risus viverra adipiscing at. Proin sagittis nisl rhoncus mattis rhoncus urna neque viverra justo. Dictumst vestibulum rhoncus est pellentesque elit. Fringilla ut morbi tincidunt augue interdum velit euismod in pellentesque. Dictum at tempor commodo ullamcorper a lacus vestibulum. Sed viverra tellus in hac habitasse platea. Sed id semper risus in hendrerit. In hendrerit gravida rutrum quisque non tellus orci ac. Sit amet risus nullam eget. Sit amet est placerat in egestas erat imperdiet sed. In nisl nisi scelerisque eu ultrices. Sit amet mattis vulputate enim nulla aliquet. + +Dignissim suspendisse in est ante in nibh mauris cursus. Vitae proin sagittis nisl rhoncus. Id leo in vitae turpis massa sed elementum. Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Arcu dictum varius duis at consectetur. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet consectetur. Imperdiet nulla malesuada pellentesque elit eget gravida cum sociis. Sed adipiscing diam donec adipiscing. Purus sit amet volutpat consequat mauris nunc congue nisi vitae. Elementum nisi quis eleifend quam adipiscing vitae proin sagittis nisl. Mattis ullamcorper velit sed ullamcorper morbi tincidunt ornare massa. Sit amet nisl purus in mollis nunc sed. Turpis tincidunt id aliquet risus feugiat in ante. Id diam maecenas ultricies mi eget mauris pharetra et ultrices. + +Aliquam purus sit amet luctus venenatis lectus magna fringilla urna. Id diam vel quam elementum pulvinar. Elementum sagittis vitae et leo duis. Viverra aliquet eget sit amet tellus cras adipiscing enim eu. Et tortor at risus viverra adipiscing at in tellus integer. Purus in massa tempor nec feugiat. Augue neque gravida in fermentum et sollicitudin ac orci. Sodales ut eu sem integer vitae justo eget magna fermentum. Netus et malesuada fames ac. Augue interdum velit euismod in. Sed elementum tempus egestas sed sed risus pretium. Mattis vulputate enim nulla aliquet porttitor lacus luctus. Dui vivamus arcu felis bibendum ut tristique et egestas quis. + +Viverra justo nec ultrices dui sapien. Quisque egestas diam in arcu cursus euismod quis viverra nibh. Nam libero justo laoreet sit amet cursus sit amet. Lacus sed viverra tellus in hac habitasse. Blandit aliquam etiam erat velit scelerisque in. Ut sem nulla pharetra diam sit amet nisl suscipit adipiscing. Diam sollicitudin tempor id eu nisl nunc. Eget duis at tellus at urna condimentum mattis. Urna porttitor rhoncus dolor purus non enim praesent elementum facilisis. Sed turpis tincidunt id aliquet risus feugiat. Est velit egestas dui id ornare arcu odio ut sem. Nibh sit amet commodo nulla facilisi nullam vehicula. Sit amet consectetur adipiscing elit duis tristique sollicitudin. Eu facilisis sed odio morbi. Massa id neque aliquam vestibulum morbi. In eu mi bibendum neque egestas congue quisque egestas. Massa sed elementum tempus egestas sed sed risus. Quam elementum pulvinar etiam non. At augue eget arcu dictum varius duis at consectetur lorem. + +Penatibus et magnis dis parturient montes nascetur ridiculus. Dictumst quisque sagittis purus sit amet volutpat consequat. Bibendum at varius vel pharetra. Sed adipiscing diam donec adipiscing tristique risus nec feugiat in. Phasellus faucibus scelerisque eleifend donec pretium. Vitae tortor condimentum lacinia quis vel eros. Ac tincidunt vitae semper quis lectus nulla at volutpat diam. Eget sit amet tellus cras adipiscing. Morbi tristique senectus et netus. Nullam vehicula ipsum a arcu cursus vitae congue mauris rhoncus. Auctor urna nunc id cursus metus aliquam eleifend. Ultrices vitae auctor eu augue. Eu non diam phasellus vestibulum lorem sed risus ultricies. Fames ac turpis egestas sed tempus. Volutpat blandit aliquam etiam erat. Dictum varius duis at consectetur lorem. Sit amet volutpat consequat mauris nunc congue. Volutpat sed cras ornare arcu dui vivamus arcu felis. + +Scelerisque fermentum dui faucibus in ornare quam viverra. Interdum velit laoreet id donec ultrices tincidunt arcu. Netus et malesuada fames ac. Netus et malesuada fames ac turpis. Suscipit tellus mauris a diam maecenas sed enim ut sem. Id velit ut tortor pretium. Neque aliquam vestibulum morbi blandit cursus risus at. Cum sociis natoque penatibus et magnis dis parturient. Lobortis elementum nibh tellus molestie nunc non blandit. Ipsum dolor sit amet consectetur adipiscing elit duis tristique. Amet nisl purus in mollis. Amet massa vitae tortor condimentum lacinia quis vel eros donec. Proin sagittis nisl rhoncus mattis rhoncus urna neque viverra justo. + +Nullam ac tortor vitae purus faucibus. Dis parturient montes nascetur ridiculus mus mauris. Molestie at elementum eu facilisis sed odio morbi. Scelerisque felis imperdiet proin fermentum leo vel orci porta. Lectus proin nibh nisl condimentum id venenatis a. Eget nullam non nisi est sit amet facilisis. Hendrerit gravida rutrum quisque non tellus orci ac auctor. Ut faucibus pulvinar elementum integer enim. Rhoncus dolor purus non enim praesent elementum facilisis. Enim sed faucibus turpis in eu mi bibendum. Faucibus nisl tincidunt eget nullam. + +Cursus risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Pretium nibh ipsum consequat nisl vel pretium lectus quam. Semper viverra nam libero justo laoreet sit amet cursus sit. Augue eget arcu dictum varius duis at consectetur lorem donec. Et malesuada fames ac turpis. Erat nam at lectus urna duis convallis convallis. Dictum sit amet justo donec enim. Urna condimentum mattis pellentesque id nibh tortor id. Morbi tempus iaculis urna id. Lectus proin nibh nisl condimentum id venenatis a condimentum. Nibh sit amet commodo nulla facilisi nullam vehicula. Dui faucibus in ornare quam. Gravida arcu ac tortor dignissim convallis aenean. Consectetur adipiscing elit pellentesque habitant morbi tristique. Pulvinar elementum integer enim neque volutpat ac tincidunt vitae. Pharetra pharetra massa massa ultricies mi quis hendrerit. Dictum at tempor commodo ullamcorper a lacus vestibulum sed. Mattis pellentesque id nibh tortor id. Ultricies integer quis auctor elit sed vulputate. Pretium vulputate sapien nec sagittis aliquam malesuada. + +Auctor augue mauris augue neque gravida. Porttitor lacus luctus accumsan tortor posuere ac ut. Urna neque viverra justo nec ultrices dui. Sit amet est placerat in egestas. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Tincidunt eget nullam non nisi est sit amet facilisis magna. Elementum tempus egestas sed sed risus pretium quam vulputate dignissim. Fermentum posuere urna nec tincidunt praesent semper feugiat nibh sed. Porttitor eget dolor morbi non arcu risus quis. Non quam lacus suspendisse faucibus interdum. Venenatis cras sed felis eget velit aliquet sagittis id. Arcu ac tortor dignissim convallis aenean et. Morbi tincidunt ornare massa eget egestas purus. Ac feugiat sed lectus vestibulum mattis ullamcorper velit sed ullamcorper. Vestibulum morbi blandit cursus risus at ultrices. Volutpat blandit aliquam etiam erat velit scelerisque. + +Et egestas quis ipsum suspendisse. Amet consectetur adipiscing elit duis. Purus ut faucibus pulvinar elementum integer enim neque. Cursus vitae congue mauris rhoncus aenean vel elit scelerisque mauris. Tincidunt eget nullam non nisi est. Aliquam purus sit amet luctus. Dui ut ornare lectus sit amet est placerat in. Fringilla ut morbi tincidunt augue interdum velit euismod in. Felis eget nunc lobortis mattis aliquam faucibus purus in. Suspendisse interdum consectetur libero id faucibus nisl. + +Scelerisque fermentum dui faucibus in ornare quam. Lectus proin nibh nisl condimentum id venenatis a condimentum vitae. Fames ac turpis egestas integer eget aliquet nibh praesent tristique. Arcu non sodales neque sodales ut etiam sit. Pharetra convallis posuere morbi leo urna. Nec dui nunc mattis enim ut tellus. Nunc sed augue lacus viverra vitae. Consequat id porta nibh venenatis cras sed felis. Dolor sit amet consectetur adipiscing. Tellus rutrum tellus pellentesque eu tincidunt tortor aliquam nulla. + +Metus aliquam eleifend mi in nulla posuere. Blandit massa enim nec dui nunc mattis enim. Aliquet nibh praesent tristique magna. In aliquam sem fringilla ut. Magna fermentum iaculis eu non. Eget aliquet nibh praesent tristique magna sit amet purus. Ultrices gravida dictum fusce ut placerat orci. Fermentum posuere urna nec tincidunt praesent. Enim tortor at auctor urna nunc. Ridiculus mus mauris vitae ultricies leo integer malesuada nunc vel. Sed id semper risus in hendrerit gravida rutrum. Vestibulum lectus mauris ultrices eros in cursus turpis. Et sollicitudin ac orci phasellus egestas tellus rutrum. Pellentesque elit ullamcorper dignissim cras tincidunt lobortis feugiat vivamus at. Metus vulputate eu scelerisque felis imperdiet proin fermentum leo. Porta non pulvinar neque laoreet suspendisse. Suscipit adipiscing bibendum est ultricies integer quis auctor elit sed. Euismod in pellentesque massa placerat duis ultricies lacus sed. Pellentesque adipiscing commodo elit at imperdiet dui accumsan sit amet. + +Pellentesque eu tincidunt tortor aliquam nulla facilisi. Commodo nulla facilisi nullam vehicula ipsum a arcu. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et. Faucibus purus in massa tempor. Purus semper eget duis at tellus at urna condimentum. Vivamus at augue eget arcu dictum. Lacus vel facilisis volutpat est velit egestas dui id. Malesuada fames ac turpis egestas maecenas pharetra. Nunc faucibus a pellentesque sit amet porttitor eget dolor. Ultricies tristique nulla aliquet enim. Vel risus commodo viverra maecenas accumsan lacus vel facilisis volutpat. Dignissim diam quis enim lobortis scelerisque. Donec ultrices tincidunt arcu non sodales neque sodales ut etiam. + +Vitae proin sagittis nisl rhoncus mattis rhoncus urna neque. Fermentum leo vel orci porta non. At elementum eu facilisis sed. Quis enim lobortis scelerisque fermentum. Fermentum odio eu feugiat pretium nibh ipsum consequat. Habitant morbi tristique senectus et netus et. Enim praesent elementum facilisis leo vel fringilla est ullamcorper. Egestas quis ipsum suspendisse ultrices gravida dictum. Nam libero justo laoreet sit amet cursus sit amet. Viverra tellus in hac habitasse platea dictumst vestibulum. Varius vel pharetra vel turpis nunc eget. Nullam non nisi est sit amet facilisis magna. Ullamcorper eget nulla facilisi etiam dignissim diam. Ante metus dictum at tempor commodo ullamcorper a lacus. + +Etiam non quam lacus suspendisse. Ut venenatis tellus in metus vulputate eu scelerisque felis. Pulvinar sapien et ligula ullamcorper malesuada proin libero. Consequat interdum varius sit amet mattis. Nunc eget lorem dolor sed viverra ipsum nunc aliquet. Potenti nullam ac tortor vitae purus faucibus ornare. Urna et pharetra pharetra massa massa ultricies mi quis hendrerit. Purus in mollis nunc sed id. Pharetra vel turpis nunc eget lorem dolor sed viverra. Et netus et malesuada fames ac turpis. Libero id faucibus nisl tincidunt eget nullam non nisi. Cursus sit amet dictum sit amet. Porttitor lacus luctus accumsan tortor. + +Volutpat diam ut venenatis tellus in metus vulputate eu scelerisque. Sed viverra tellus in hac habitasse. Aliquam sem et tortor consequat id. Pellentesque habitant morbi tristique senectus et netus et. Consectetur purus ut faucibus pulvinar elementum. Aliquam malesuada bibendum arcu vitae elementum curabitur vitae nunc sed. Malesuada bibendum arcu vitae elementum curabitur vitae nunc sed. Sollicitudin tempor id eu nisl nunc mi ipsum. Fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate sapien nec. Quis eleifend quam adipiscing vitae proin sagittis nisl rhoncus. Bibendum neque egestas congue quisque egestas. A iaculis at erat pellentesque adipiscing commodo elit at imperdiet. Pulvinar etiam non quam lacus. Adipiscing commodo elit at imperdiet. Scelerisque eu ultrices vitae auctor. Sed cras ornare arcu dui vivamus arcu felis bibendum ut. Ornare lectus sit amet est. + +Consequat semper viverra nam libero justo laoreet sit. Imperdiet sed euismod nisi porta lorem mollis aliquam ut porttitor. Cras sed felis eget velit aliquet sagittis id consectetur. Dolor morbi non arcu risus quis. Adipiscing tristique risus nec feugiat in fermentum posuere urna. Dolor magna eget est lorem ipsum dolor. Mauris pharetra et ultrices neque ornare aenean euismod. Nulla facilisi etiam dignissim diam quis. Ultrices tincidunt arcu non sodales. Fames ac turpis egestas maecenas pharetra convallis posuere morbi leo. Interdum varius sit amet mattis vulputate. Tincidunt praesent semper feugiat nibh sed pulvinar. Quisque sagittis purus sit amet volutpat. + +Sed vulputate odio ut enim blandit. Vitae auctor eu augue ut lectus arcu bibendum. Consectetur adipiscing elit pellentesque habitant morbi tristique senectus et. Scelerisque eu ultrices vitae auctor eu augue. Etiam dignissim diam quis enim lobortis scelerisque fermentum dui faucibus. Tellus integer feugiat scelerisque varius. Vulputate enim nulla aliquet porttitor lacus luctus accumsan tortor. Amet nisl purus in mollis. Scelerisque viverra mauris in aliquam sem fringilla ut morbi tincidunt. Semper eget duis at tellus at. Erat velit scelerisque in dictum non consectetur a erat nam. Gravida rutrum quisque non tellus orci. Morbi blandit cursus risus at. Mauris sit amet massa vitae. Non odio euismod lacinia at quis risus sed vulputate. Fermentum posuere urna nec tincidunt praesent. Ut eu sem integer vitae justo eget magna fermentum iaculis. Ullamcorper velit sed ullamcorper morbi tincidunt ornare massa. Arcu cursus euismod quis viverra nibh. Arcu dui vivamus arcu felis bibendum. + +Eros in cursus turpis massa tincidunt dui ut. Urna condimentum mattis pellentesque id nibh tortor id aliquet lectus. Nibh venenatis cras sed felis. Ac felis donec et odio pellentesque diam. Ultricies lacus sed turpis tincidunt id aliquet risus. Diam volutpat commodo sed egestas. Dignissim sodales ut eu sem integer vitae. Pellentesque eu tincidunt tortor aliquam nulla facilisi. Et tortor consequat id porta nibh venenatis cras sed. \ No newline at end of file diff --git a/node/impl/client/testdata/payload2.txt b/node/impl/client/testdata/payload2.txt new file mode 100644 index 000000000..16fb150f5 --- /dev/null +++ b/node/impl/client/testdata/payload2.txt @@ -0,0 +1,49 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae semper quis lectus nulla at volutpat diam ut venenatis. Ac tortor dignissim convallis aenean et tortor at. Faucibus ornare suspendisse sed nisi lacus sed. Commodo ullamcorper a lacus vestibulum sed arcu non. Est pellentesque elit ullamcorper dignissim. Quam quisque id diam vel quam. Pretium aenean pharetra magna ac. In nulla posuere sollicitudin aliquam ultrices. Sed arcu non odio euismod lacinia at. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla pellentesque. Feugiat vivamus at augue eget arcu. + +Pellentesque nec nam aliquam sem et tortor. Vitae tortor condimentum lacinia quis vel. Cras pulvinar mattis nunc sed. In massa tempor nec feugiat. Ornare arcu odio ut sem nulla. Diam maecenas sed enim ut sem. Pretium vulputate sapien nec sagittis. Bibendum arcu vitae elementum curabitur vitae nunc sed velit dignissim. Duis ut diam quam nulla porttitor massa. Viverra mauris in aliquam sem fringilla ut morbi. Ullamcorper eget nulla facilisi etiam dignissim. Vulputate mi sit amet mauris commodo quis imperdiet massa tincidunt. Nunc consequat interdum varius sit. Nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Nunc sed augue lacus viverra. Lobortis scelerisque fermentum dui faucibus in ornare quam. Urna neque viverra justo nec ultrices. Varius vel pharetra vel turpis nunc eget lorem dolor sed. + +Feugiat nisl pretium fusce id velit ut tortor pretium. Lorem dolor sed viverra ipsum nunc aliquet bibendum. Ultrices vitae auctor eu augue ut lectus. Pharetra massa massa ultricies mi quis. Nibh cras pulvinar mattis nunc sed blandit libero. Ac felis donec et odio pellentesque diam volutpat. Lectus proin nibh nisl condimentum id venenatis. Quis vel eros donec ac odio. Commodo sed egestas egestas fringilla phasellus faucibus scelerisque eleifend donec. Adipiscing diam donec adipiscing tristique. + +Tempus imperdiet nulla malesuada pellentesque elit eget gravida cum sociis. Libero nunc consequat interdum varius sit. Et pharetra pharetra massa massa. Feugiat pretium nibh ipsum consequat. Amet commodo nulla facilisi nullam vehicula. Ornare arcu dui vivamus arcu felis bibendum ut tristique. At erat pellentesque adipiscing commodo elit at imperdiet dui. Auctor neque vitae tempus quam pellentesque nec nam aliquam sem. Eget velit aliquet sagittis id consectetur. Enim diam vulputate ut pharetra sit amet aliquam id diam. Eget velit aliquet sagittis id consectetur purus ut faucibus pulvinar. Amet porttitor eget dolor morbi. Felis eget velit aliquet sagittis id. Facilisis magna etiam tempor orci eu. Lacus suspendisse faucibus interdum posuere lorem. Pharetra et ultrices neque ornare aenean euismod. Platea dictumst quisque sagittis purus. + +Quis varius quam quisque id diam vel quam elementum. Augue mauris augue neque gravida in fermentum et sollicitudin. Sapien nec sagittis aliquam malesuada bibendum arcu. Urna duis convallis convallis tellus id interdum velit. Tellus in hac habitasse platea dictumst vestibulum. Fames ac turpis egestas maecenas pharetra convallis. Diam volutpat commodo sed egestas egestas fringilla phasellus faucibus. Placerat orci nulla pellentesque dignissim enim sit amet venenatis. Sed adipiscing diam donec adipiscing. Praesent elementum facilisis leo vel fringilla est. Sed enim ut sem viverra aliquet eget sit amet tellus. Proin sagittis nisl rhoncus mattis rhoncus urna neque viverra. Turpis egestas pretium aenean pharetra magna ac placerat vestibulum. Massa id neque aliquam vestibulum morbi blandit cursus risus. Vitae congue eu consequat ac. Egestas erat imperdiet sed euismod nisi porta lorem mollis aliquam. Dolor purus non enim praesent elementum facilisis. Ultrices mi tempus imperdiet nulla malesuada pellentesque elit. In est ante in nibh. + +Facilisis gravida neque convallis a. Urna nunc id cursus metus aliquam eleifend mi. Lacus luctus accumsan tortor posuere ac. Molestie nunc non blandit massa. Iaculis urna id volutpat lacus laoreet non. Cursus vitae congue mauris rhoncus aenean. Nunc vel risus commodo viverra maecenas. A pellentesque sit amet porttitor eget dolor morbi. Leo vel orci porta non pulvinar neque laoreet suspendisse. Sit amet facilisis magna etiam tempor. Consectetur a erat nam at lectus urna duis convallis convallis. Vestibulum morbi blandit cursus risus at ultrices. Dolor purus non enim praesent elementum. Adipiscing elit pellentesque habitant morbi tristique senectus et netus et. Et odio pellentesque diam volutpat commodo sed egestas egestas fringilla. Leo vel fringilla est ullamcorper eget nulla. Dui ut ornare lectus sit amet. Erat pellentesque adipiscing commodo elit at imperdiet dui accumsan sit. + +Tristique senectus et netus et. Pellentesque diam volutpat commodo sed egestas egestas fringilla. Mauris pharetra et ultrices neque ornare aenean. Amet tellus cras adipiscing enim. Convallis aenean et tortor at risus viverra adipiscing at. Proin sagittis nisl rhoncus mattis rhoncus urna neque viverra justo. Dictumst vestibulum rhoncus est pellentesque elit. Fringilla ut morbi tincidunt augue interdum velit euismod in pellentesque. Dictum at tempor commodo ullamcorper a lacus vestibulum. Sed viverra tellus in hac habitasse platea. Sed id semper risus in hendrerit. In hendrerit gravida rutrum quisque non tellus orci ac. Sit amet risus nullam eget. Sit amet est placerat in egestas erat imperdiet sed. In nisl nisi scelerisque eu ultrices. Sit amet mattis vulputate enim nulla aliquet. + +Dignissim suspendisse in est ante in nibh mauris cursus. Vitae proin sagittis nisl rhoncus. Id leo in vitae turpis massa sed elementum. Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Arcu dictum varius duis at consectetur. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet consectetur. Imperdiet nulla malesuada pellentesque elit eget gravida cum sociis. Sed adipiscing diam donec adipiscing. Purus sit amet volutpat consequat mauris nunc congue nisi vitae. Elementum nisi quis eleifend quam adipiscing vitae proin sagittis nisl. Mattis ullamcorper velit sed ullamcorper morbi tincidunt ornare massa. Sit amet nisl purus in mollis nunc sed. Turpis tincidunt id aliquet risus feugiat in ante. Id diam maecenas ultricies mi eget mauris pharetra et ultrices. + +Aliquam purus sit amet luctus venenatis lectus magna fringilla urna. Id diam vel quam elementum pulvinar. Elementum sagittis vitae et leo duis. Viverra aliquet eget sit amet tellus cras adipiscing enim eu. Et tortor at risus viverra adipiscing at in tellus integer. Purus in massa tempor nec feugiat. Augue neque gravida in fermentum et sollicitudin ac orci. Sodales ut eu sem integer vitae justo eget magna fermentum. Netus et malesuada fames ac. Augue interdum velit euismod in. Sed elementum tempus egestas sed sed risus pretium. Mattis vulputate enim nulla aliquet porttitor lacus luctus. Dui vivamus arcu felis bibendum ut tristique et egestas quis. + +Viverra justo nec ultrices dui sapien. Quisque egestas diam in arcu cursus euismod quis viverra nibh. Nam libero justo laoreet sit amet cursus sit amet. Lacus sed viverra tellus in hac habitasse. Blandit aliquam etiam erat velit scelerisque in. Ut sem nulla pharetra diam sit amet nisl suscipit adipiscing. Diam sollicitudin tempor id eu nisl nunc. Eget duis at tellus at urna condimentum mattis. Urna porttitor rhoncus dolor purus non enim praesent elementum facilisis. Sed turpis tincidunt id aliquet risus feugiat. Est velit egestas dui id ornare arcu odio ut sem. Nibh sit amet commodo nulla facilisi nullam vehicula. Sit amet consectetur adipiscing elit duis tristique sollicitudin. Eu facilisis sed odio morbi. Massa id neque aliquam vestibulum morbi. In eu mi bibendum neque egestas congue quisque egestas. Massa sed elementum tempus egestas sed sed risus. Quam elementum pulvinar etiam non. At augue eget arcu dictum varius duis at consectetur lorem. + +Penatibus et magnis dis parturient montes nascetur ridiculus. Dictumst quisque sagittis purus sit amet volutpat consequat. Bibendum at varius vel pharetra. Sed adipiscing diam donec adipiscing tristique risus nec feugiat in. Phasellus faucibus scelerisque eleifend donec pretium. Vitae tortor condimentum lacinia quis vel eros. Ac tincidunt vitae semper quis lectus nulla at volutpat diam. Eget sit amet tellus cras adipiscing. Morbi tristique senectus et netus. Nullam vehicula ipsum a arcu cursus vitae congue mauris rhoncus. Auctor urna nunc id cursus metus aliquam eleifend. Ultrices vitae auctor eu augue. Eu non diam phasellus vestibulum lorem sed risus ultricies. Fames ac turpis egestas sed tempus. Volutpat blandit aliquam etiam erat. Dictum varius duis at consectetur lorem. Sit amet volutpat consequat mauris nunc congue. Volutpat sed cras ornare arcu dui vivamus arcu felis. + +Scelerisque fermentum dui faucibus in ornare quam viverra. Interdum velit laoreet id donec ultrices tincidunt arcu. Netus et malesuada fames ac. Netus et malesuada fames ac turpis. Suscipit tellus mauris a diam maecenas sed enim ut sem. Id velit ut tortor pretium. Neque aliquam vestibulum morbi blandit cursus risus at. Cum sociis natoque penatibus et magnis dis parturient. Lobortis elementum nibh tellus molestie nunc non blandit. Ipsum dolor sit amet consectetur adipiscing elit duis tristique. Amet nisl purus in mollis. Amet massa vitae tortor condimentum lacinia quis vel eros donec. Proin sagittis nisl rhoncus mattis rhoncus urna neque viverra justo. + +Nullam ac tortor vitae purus faucibus. Dis parturient montes nascetur ridiculus mus mauris. Molestie at elementum eu facilisis sed odio morbi. Scelerisque felis imperdiet proin fermentum leo vel orci porta. Lectus proin nibh nisl condimentum id venenatis a. Eget nullam non nisi est sit amet facilisis. Hendrerit gravida rutrum quisque non tellus orci ac auctor. Ut faucibus pulvinar elementum integer enim. Rhoncus dolor purus non enim praesent elementum facilisis. Enim sed faucibus turpis in eu mi bibendum. Faucibus nisl tincidunt eget nullam. + +Cursus risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Pretium nibh ipsum consequat nisl vel pretium lectus quam. Semper viverra nam libero justo laoreet sit amet cursus sit. Augue eget arcu dictum varius duis at consectetur lorem donec. Et malesuada fames ac turpis. Erat nam at lectus urna duis convallis convallis. Dictum sit amet justo donec enim. Urna condimentum mattis pellentesque id nibh tortor id. Morbi tempus iaculis urna id. Lectus proin nibh nisl condimentum id venenatis a condimentum. Nibh sit amet commodo nulla facilisi nullam vehicula. Dui faucibus in ornare quam. Gravida arcu ac tortor dignissim convallis aenean. Consectetur adipiscing elit pellentesque habitant morbi tristique. Pulvinar elementum integer enim neque volutpat ac tincidunt vitae. Pharetra pharetra massa massa ultricies mi quis hendrerit. Dictum at tempor commodo ullamcorper a lacus vestibulum sed. Mattis pellentesque id nibh tortor id. Ultricies integer quis auctor elit sed vulputate. Pretium vulputate sapien nec sagittis aliquam malesuada. + +Auctor augue mauris augue neque gravida. Porttitor lacus luctus accumsan tortor posuere ac ut. Urna neque viverra justo nec ultrices dui. Sit amet est placerat in egestas. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Tincidunt eget nullam non nisi est sit amet facilisis magna. Elementum tempus egestas sed sed risus pretium quam vulputate dignissim. Fermentum posuere urna nec tincidunt praesent semper feugiat nibh sed. Porttitor eget dolor morbi non arcu risus quis. Non quam lacus suspendisse faucibus interdum. Venenatis cras sed felis eget velit aliquet sagittis id. Arcu ac tortor dignissim convallis aenean et. Morbi tincidunt ornare massa eget egestas purus. Ac feugiat sed lectus vestibulum mattis ullamcorper velit sed ullamcorper. Vestibulum morbi blandit cursus risus at ultrices. Volutpat blandit aliquam etiam erat velit scelerisque. + +Et egestas quis ipsum suspendisse. Amet consectetur adipiscing elit duis. Purus ut faucibus pulvinar elementum integer enim neque. Cursus vitae congue mauris rhoncus aenean vel elit scelerisque mauris. Tincidunt eget nullam non nisi est. Aliquam purus sit amet luctus. Dui ut ornare lectus sit amet est placerat in. Fringilla ut morbi tincidunt augue interdum velit euismod in. Felis eget nunc lobortis mattis aliquam faucibus purus in. Suspendisse interdum consectetur libero id faucibus nisl. + +Scelerisque fermentum dui faucibus in ornare quam. Lectus proin nibh nisl condimentum id venenatis a condimentum vitae. Fames ac turpis egestas integer eget aliquet nibh praesent tristique. Arcu non sodales neque sodales ut etiam sit. Pharetra convallis posuere morbi leo urna. Nec dui nunc mattis enim ut tellus. Nunc sed augue lacus viverra vitae. Consequat id porta nibh venenatis cras sed felis. Dolor sit amet consectetur adipiscing. Tellus rutrum tellus pellentesque eu tincidunt tortor aliquam nulla. + +Metus aliquam eleifend mi in nulla posuere. Blandit massa enim nec dui nunc mattis enim. Aliquet nibh praesent tristique magna. In aliquam sem fringilla ut. Magna fermentum iaculis eu non. Eget aliquet nibh praesent tristique magna sit amet purus. Ultrices gravida dictum fusce ut placerat orci. Fermentum posuere urna nec tincidunt praesent. Enim tortor at auctor urna nunc. Ridiculus mus mauris vitae ultricies leo integer malesuada nunc vel. Sed id semper risus in hendrerit gravida rutrum. Vestibulum lectus mauris ultrices eros in cursus turpis. Et sollicitudin ac orci phasellus egestas tellus rutrum. Pellentesque elit ullamcorper dignissim cras tincidunt lobortis feugiat vivamus at. Metus vulputate eu scelerisque felis imperdiet proin fermentum leo. Porta non pulvinar neque laoreet suspendisse. Suscipit adipiscing bibendum est ultricies integer quis auctor elit sed. Euismod in pellentesque massa placerat duis ultricies lacus sed. Pellentesque adipiscing commodo elit at imperdiet dui accumsan sit amet. + +Pellentesque eu tincidunt tortor aliquam nulla facilisi. Commodo nulla facilisi nullam vehicula ipsum a arcu. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et. Faucibus purus in massa tempor. Purus semper eget duis at tellus at urna condimentum. Vivamus at augue eget arcu dictum. Lacus vel facilisis volutpat est velit egestas dui id. Malesuada fames ac turpis egestas maecenas pharetra. Nunc faucibus a pellentesque sit amet porttitor eget dolor. Ultricies tristique nulla aliquet enim. Vel risus commodo viverra maecenas accumsan lacus vel facilisis volutpat. Dignissim diam quis enim lobortis scelerisque. Donec ultrices tincidunt arcu non sodales neque sodales ut etiam. + +Vitae proin sagittis nisl rhoncus mattis rhoncus urna neque. Fermentum leo vel orci porta non. At elementum eu facilisis sed. Quis enim lobortis scelerisque fermentum. Fermentum odio eu feugiat pretium nibh ipsum consequat. Habitant morbi tristique senectus et netus et. Enim praesent elementum facilisis leo vel fringilla est ullamcorper. Egestas quis ipsum suspendisse ultrices gravida dictum. Nam libero justo laoreet sit amet cursus sit amet. Viverra tellus in hac habitasse platea dictumst vestibulum. Varius vel pharetra vel turpis nunc eget. Nullam non nisi est sit amet facilisis magna. Ullamcorper eget nulla facilisi etiam dignissim diam. Ante metus dictum at tempor commodo ullamcorper a lacus. + +Etiam non quam lacus suspendisse. Ut venenatis tellus in metus vulputate eu scelerisque felis. Pulvinar sapien et ligula ullamcorper malesuada proin libero. Consequat interdum varius sit amet mattis. Nunc eget lorem dolor sed viverra ipsum nunc aliquet. Potenti nullam ac tortor vitae purus faucibus ornare. Urna et pharetra pharetra massa massa ultricies mi quis hendrerit. Purus in mollis nunc sed id. Pharetra vel turpis nunc eget lorem dolor sed viverra. Et netus et malesuada fames ac turpis. Libero id faucibus nisl tincidunt eget nullam non nisi. Cursus sit amet dictum sit amet. Porttitor lacus luctus accumsan tortor. + +Volutpat diam ut venenatis tellus in metus vulputate eu scelerisque. Sed viverra tellus in hac habitasse. Aliquam sem et tortor consequat id. Pellentesque habitant morbi tristique senectus et netus et. Consectetur purus ut faucibus pulvinar elementum. Aliquam malesuada bibendum arcu vitae elementum curabitur vitae nunc sed. Malesuada bibendum arcu vitae elementum curabitur vitae nunc sed. Sollicitudin tempor id eu nisl nunc mi ipsum. Fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate sapien nec. Quis eleifend quam adipiscing vitae proin sagittis nisl rhoncus. Bibendum neque egestas congue quisque egestas. A iaculis at erat pellentesque adipiscing commodo elit at imperdiet. Pulvinar etiam non quam lacus. Adipiscing commodo elit at imperdiet. Scelerisque eu ultrices vitae auctor. Sed cras ornare arcu dui vivamus arcu felis bibendum ut. Ornare lectus sit amet est. + +Consequat semper viverra nam libero justo laoreet sit. Imperdiet sed euismod nisi porta lorem mollis aliquam ut porttitor. Cras sed felis eget velit aliquet sagittis id consectetur. Dolor morbi non arcu risus quis. Adipiscing tristique risus nec feugiat in fermentum posuere urna. Dolor magna eget est lorem ipsum dolor. Mauris pharetra et ultrices neque ornare aenean euismod. Nulla facilisi etiam dignissim diam quis. Ultrices tincidunt arcu non sodales. Fames ac turpis egestas maecenas pharetra convallis posuere morbi leo. Interdum varius sit amet mattis vulputate. Tincidunt praesent semper feugiat nibh sed pulvinar. Quisque sagittis purus sit amet volutpat. + +Sed vulputate odio ut enim blandit. Vitae auctor eu augue ut lectus arcu bibendum. Consectetur adipiscing elit pellentesque habitant morbi tristique senectus et. Scelerisque eu ultrices vitae auctor eu augue. Etiam dignissim diam quis enim lobortis scelerisque fermentum dui faucibus. Tellus integer feugiat scelerisque varius. Vulputate enim nulla aliquet porttitor lacus luctus accumsan tortor. Amet nisl purus in mollis. Scelerisque viverra mauris in aliquam sem fringilla ut morbi tincidunt. Semper eget duis at tellus at. Erat velit scelerisque in dictum non consectetur a erat nam. Gravida rutrum quisque non tellus orci. Morbi blandit cursus risus at. Mauris sit amet massa vitae. Non odio euismod lacinia at quis risus sed vulputate. Fermentum posuere urna nec tincidunt praesent. Ut eu sem integer vitae justo eget magna fermentum iaculis. Ullamcorper velit sed ullamcorper morbi tincidunt ornare massa. Arcu cursus euismod quis viverra nibh. Arcu dui vivamus arcu felis bibendum. + +Eros in cursus turpis massa tincidunt dui ut. Aarsh shah is simply an amazing person. Urna condimentum mattis pellentesque id nibh tortor id aliquet lectus. Nibh venenatis cras sed felis. Ac felis donec et odio pellentesque diam. Ultricies lacus sed turpis tincidunt id aliquet risus. Diam volutpat commodo sed egestas. Dignissim sodales ut eu sem integer vitae. Pellentesque eu tincidunt tortor aliquam nulla facilisi. Et tortor consequat id porta nibh venenatis cras sed. \ No newline at end of file diff --git a/node/impl/full/chain.go b/node/impl/full/chain.go index c5c2334ad..433573010 100644 --- a/node/impl/full/chain.go +++ b/node/impl/full/chain.go @@ -50,6 +50,7 @@ type ChainModuleAPI interface { ChainGetMessage(ctx context.Context, mc cid.Cid) (*types.Message, error) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) + ChainGetTipSetAfterHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) ChainReadObj(context.Context, cid.Cid) ([]byte, error) } @@ -266,6 +267,14 @@ func (m *ChainModule) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpo return m.Chain.GetTipsetByHeight(ctx, h, ts, true) } +func (m *ChainModule) ChainGetTipSetAfterHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) { + ts, err := m.Chain.GetTipSetFromKey(tsk) + if err != nil { + return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err) + } + return m.Chain.GetTipsetByHeight(ctx, h, ts, false) +} + func (m *ChainModule) ChainReadObj(ctx context.Context, obj cid.Cid) ([]byte, error) { blk, err := m.ExposedBlockstore.Get(obj) if err != nil { diff --git a/node/impl/full/mpool.go b/node/impl/full/mpool.go index bd91387a2..f792cdf99 100644 --- a/node/impl/full/mpool.go +++ b/node/impl/full/mpool.go @@ -84,9 +84,10 @@ func (a *MpoolAPI) MpoolPending(ctx context.Context, tsk types.TipSetKey) ([]*ty if mpts.Equals(ts) { return pending, nil } - // different blocks in tipsets - have, err := a.Mpool.MessagesForBlocks(ts.Blocks()) + // different blocks in tipsets of the same height + // we exclude messages that have been included in blocks in the mpool tipset + have, err := a.Mpool.MessagesForBlocks(mpts.Blocks()) if err != nil { return nil, xerrors.Errorf("getting messages for base ts: %w", err) } diff --git a/node/impl/full/state.go b/node/impl/full/state.go index df0888787..ba441c6cd 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -3,8 +3,10 @@ package full import ( "bytes" "context" + "encoding/json" "strconv" + "github.com/filecoin-project/go-state-types/cbor" cid "github.com/ipfs/go-cid" "go.uber.org/fx" "golang.org/x/xerrors" @@ -497,6 +499,24 @@ func (a *StateAPI) StateDecodeParams(ctx context.Context, toAddr address.Address return paramType, nil } +func (a *StateAPI) StateEncodeParams(ctx context.Context, toActCode cid.Cid, method abi.MethodNum, params json.RawMessage) ([]byte, error) { + paramType, err := stmgr.GetParamType(toActCode, method) + if err != nil { + return nil, xerrors.Errorf("getting params type: %w", err) + } + + if err := json.Unmarshal(params, ¶mType); err != nil { + return nil, xerrors.Errorf("json unmarshal: %w", err) + } + + var cbb bytes.Buffer + if err := paramType.(cbor.Marshaler).MarshalCBOR(&cbb); err != nil { + return nil, xerrors.Errorf("cbor marshal: %w", err) + } + + return cbb.Bytes(), nil +} + // This is on StateAPI because miner.Miner requires this, and MinerAPI requires miner.Miner func (a *StateAPI) MinerGetBaseInfo(ctx context.Context, maddr address.Address, epoch abi.ChainEpoch, tsk types.TipSetKey) (*api.MiningBaseInfo, error) { // XXX: Gets the state by computing the tipset state, instead of looking at the parent. diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 0fbd12111..657e8cfdf 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -3,16 +3,20 @@ package impl import ( "context" "encoding/json" + "fmt" "net/http" "os" + "sort" "strconv" "time" + "github.com/filecoin-project/dagstore" + "github.com/filecoin-project/dagstore/shard" "github.com/filecoin-project/go-jsonrpc/auth" + "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/gen" - "github.com/filecoin-project/lotus/build" "github.com/google/uuid" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p-core/host" @@ -20,6 +24,8 @@ import ( "go.uber.org/fx" "golang.org/x/xerrors" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/go-address" datatransfer "github.com/filecoin-project/go-data-transfer" "github.com/filecoin-project/go-fil-markets/piecestore" @@ -34,6 +40,8 @@ import ( sealing "github.com/filecoin-project/lotus/extern/storage-sealing" "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" + sto "github.com/filecoin-project/specs-storage/storage" + "github.com/filecoin-project/lotus/api" apitypes "github.com/filecoin-project/lotus/api/types" "github.com/filecoin-project/lotus/chain/types" @@ -42,7 +50,6 @@ import ( "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/storage" "github.com/filecoin-project/lotus/storage/sectorblocks" - sto "github.com/filecoin-project/specs-storage/storage" ) type StorageMinerAPI struct { @@ -61,10 +68,12 @@ type StorageMinerAPI struct { PieceStore dtypes.ProviderPieceStore `optional:"true"` StorageProvider storagemarket.StorageProvider `optional:"true"` RetrievalProvider retrievalmarket.RetrievalProvider `optional:"true"` + SectorAccessor retrievalmarket.SectorAccessor `optional:"true"` DataTransfer dtypes.ProviderDataTransfer `optional:"true"` DealPublisher *storageadapter.DealPublisher `optional:"true"` SectorBlocks *sectorblocks.SectorBlocks `optional:"true"` Host host.Host `optional:"true"` + DAGStore *dagstore.DAGStore `optional:"true"` // Miner / storage Miner *storage.Miner `optional:"true"` @@ -98,6 +107,8 @@ type StorageMinerAPI struct { SetExpectedSealDurationFunc dtypes.SetExpectedSealDurationFunc `optional:"true"` } +var _ api.StorageMiner = &StorageMinerAPI{} + func (sm *StorageMinerAPI) ServeRemote(perm bool) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { if perm == true { @@ -201,7 +212,13 @@ func (sm *StorageMinerAPI) SectorsStatus(ctx context.Context, sid abi.SectorNumb } func (sm *StorageMinerAPI) SectorAddPieceToAny(ctx context.Context, size abi.UnpaddedPieceSize, r sto.Data, d api.PieceDealInfo) (api.SectorOffset, error) { - return sm.Miner.SectorAddPieceToAny(ctx, size, r, d) + so, err := sm.Miner.SectorAddPieceToAny(ctx, size, r, d) + if err != nil { + // jsonrpc doesn't support returning values with errors, make sure we never do that + return api.SectorOffset{}, err + } + + return so, nil } func (sm *StorageMinerAPI) SectorsUnsealPiece(ctx context.Context, sector sto.SectorRef, offset storiface.UnpaddedByteIndex, size abi.UnpaddedPieceSize, randomness abi.SealRandomness, commd *cid.Cid) error { @@ -545,6 +562,267 @@ func (sm *StorageMinerAPI) MarketPublishPendingDeals(ctx context.Context) error return nil } +func (sm *StorageMinerAPI) DagstoreListShards(ctx context.Context) ([]api.DagstoreShardInfo, error) { + if sm.DAGStore == nil { + return nil, fmt.Errorf("dagstore not available on this node") + } + + info := sm.DAGStore.AllShardsInfo() + ret := make([]api.DagstoreShardInfo, 0, len(info)) + for k, i := range info { + ret = append(ret, api.DagstoreShardInfo{ + Key: k.String(), + State: i.ShardState.String(), + Error: func() string { + if i.Error == nil { + return "" + } + return i.Error.Error() + }(), + }) + } + + // order by key. + sort.SliceStable(ret, func(i, j int) bool { + return ret[i].Key < ret[j].Key + }) + + return ret, nil +} + +func (sm *StorageMinerAPI) DagstoreInitializeShard(ctx context.Context, key string) error { + if sm.DAGStore == nil { + return fmt.Errorf("dagstore not available on this node") + } + + k := shard.KeyFromString(key) + + info, err := sm.DAGStore.GetShardInfo(k) + if err != nil { + return fmt.Errorf("failed to get shard info: %w", err) + } + if st := info.ShardState; st != dagstore.ShardStateNew { + return fmt.Errorf("cannot initialize shard; expected state ShardStateNew, was: %s", st.String()) + } + + ch := make(chan dagstore.ShardResult, 1) + if err = sm.DAGStore.AcquireShard(ctx, k, ch, dagstore.AcquireOpts{}); err != nil { + return fmt.Errorf("failed to acquire shard: %w", err) + } + + var res dagstore.ShardResult + select { + case res = <-ch: + case <-ctx.Done(): + return ctx.Err() + } + + if err := res.Error; err != nil { + return fmt.Errorf("failed to acquire shard: %w", err) + } + + if res.Accessor != nil { + err = res.Accessor.Close() + if err != nil { + log.Warnw("failed to close shard accessor; continuing", "shard_key", k, "error", err) + } + } + + return nil +} + +func (sm *StorageMinerAPI) DagstoreInitializeAll(ctx context.Context, params api.DagstoreInitializeAllParams) (<-chan api.DagstoreInitializeAllEvent, error) { + if sm.DAGStore == nil { + return nil, fmt.Errorf("dagstore not available on this node") + } + + if sm.SectorAccessor == nil { + return nil, fmt.Errorf("sector accessor not available on this node") + } + + // prepare the thottler tokens. + var throttle chan struct{} + if c := params.MaxConcurrency; c > 0 { + throttle = make(chan struct{}, c) + for i := 0; i < c; i++ { + throttle <- struct{}{} + } + } + + // are we initializing only unsealed pieces? + onlyUnsealed := !params.IncludeSealed + + info := sm.DAGStore.AllShardsInfo() + var toInitialize []string + for k, i := range info { + if i.ShardState != dagstore.ShardStateNew { + continue + } + + // if we're initializing only unsealed pieces, check if there's an + // unsealed deal for this piece available. + if onlyUnsealed { + pieceCid, err := cid.Decode(k.String()) + if err != nil { + log.Warnw("DagstoreInitializeAll: failed to decode shard key as piece CID; skipping", "shard_key", k.String(), "error", err) + continue + } + + pi, err := sm.PieceStore.GetPieceInfo(pieceCid) + if err != nil { + log.Warnw("DagstoreInitializeAll: failed to get piece info; skipping", "piece_cid", pieceCid, "error", err) + continue + } + + var isUnsealed bool + for _, d := range pi.Deals { + isUnsealed, err = sm.SectorAccessor.IsUnsealed(ctx, d.SectorID, d.Offset.Unpadded(), d.Length.Unpadded()) + if err != nil { + log.Warnw("DagstoreInitializeAll: failed to get unsealed status; skipping deal", "deal_id", d.DealID, "error", err) + continue + } + if isUnsealed { + break + } + } + + if !isUnsealed { + log.Infow("DagstoreInitializeAll: skipping piece because it's sealed", "piece_cid", pieceCid, "error", err) + continue + } + } + + // yes, we're initializing this shard. + toInitialize = append(toInitialize, k.String()) + } + + total := len(toInitialize) + if total == 0 { + out := make(chan api.DagstoreInitializeAllEvent) + close(out) + return out, nil + } + + // response channel must be closed when we're done, or the context is cancelled. + // this buffering is necessary to prevent inflight children goroutines from + // publishing to a closed channel (res) when the context is cancelled. + out := make(chan api.DagstoreInitializeAllEvent, 32) // internal buffer. + res := make(chan api.DagstoreInitializeAllEvent, 32) // returned to caller. + + // pump events back to caller. + // two events per shard. + go func() { + defer close(res) + + for i := 0; i < total*2; i++ { + select { + case res <- <-out: + case <-ctx.Done(): + return + } + } + }() + + go func() { + for i, k := range toInitialize { + select { + case <-throttle: + // acquired a throttle token, proceed. + case <-ctx.Done(): + return + } + + go func(k string, i int) { + r := api.DagstoreInitializeAllEvent{ + Key: k, + Event: "start", + Total: total, + Current: i + 1, // start with 1 + } + select { + case out <- r: + case <-ctx.Done(): + return + } + + err := sm.DagstoreInitializeShard(ctx, k) + throttle <- struct{}{} + + r.Event = "end" + if err == nil { + r.Success = true + } else { + r.Success = false + r.Error = err.Error() + } + + select { + case out <- r: + case <-ctx.Done(): + } + }(k, i) + } + }() + + return res, nil + +} + +func (sm *StorageMinerAPI) DagstoreRecoverShard(ctx context.Context, key string) error { + if sm.DAGStore == nil { + return fmt.Errorf("dagstore not available on this node") + } + + k := shard.KeyFromString(key) + + info, err := sm.DAGStore.GetShardInfo(k) + if err != nil { + return fmt.Errorf("failed to get shard info: %w", err) + } + if st := info.ShardState; st != dagstore.ShardStateErrored { + return fmt.Errorf("cannot recover shard; expected state ShardStateErrored, was: %s", st.String()) + } + + ch := make(chan dagstore.ShardResult, 1) + if err = sm.DAGStore.RecoverShard(ctx, k, ch, dagstore.RecoverOpts{}); err != nil { + return fmt.Errorf("failed to recover shard: %w", err) + } + + var res dagstore.ShardResult + select { + case res = <-ch: + case <-ctx.Done(): + return ctx.Err() + } + + return res.Error +} + +func (sm *StorageMinerAPI) DagstoreGC(ctx context.Context) ([]api.DagstoreShardResult, error) { + if sm.DAGStore == nil { + return nil, fmt.Errorf("dagstore not available on this node") + } + + res, err := sm.DAGStore.GC(ctx) + if err != nil { + return nil, fmt.Errorf("failed to gc: %w", err) + } + + ret := make([]api.DagstoreShardResult, 0, len(res.Shards)) + for k, err := range res.Shards { + r := api.DagstoreShardResult{Key: k.String()} + if err == nil { + r.Success = true + } else { + r.Success = false + r.Error = err.Error() + } + ret = append(ret, r) + } + + return ret, nil +} + func (sm *StorageMinerAPI) DealsList(ctx context.Context) ([]api.MarketDeal, error) { return sm.listDeals(ctx) } @@ -708,5 +986,3 @@ func (sm *StorageMinerAPI) ComputeProof(ctx context.Context, ssi []builtin.Secto func (sm *StorageMinerAPI) RuntimeSubsystems(context.Context) (res api.MinerSubsystems, err error) { return sm.EnabledSubsystems, nil } - -var _ api.StorageMiner = &StorageMinerAPI{} diff --git a/node/modules/chain.go b/node/modules/chain.go index c4017b8c0..a0e7f2f51 100644 --- a/node/modules/chain.go +++ b/node/modules/chain.go @@ -72,8 +72,8 @@ func MessagePool(lc fx.Lifecycle, mpp messagepool.Provider, ds dtypes.MetadataDS return mp, nil } -func ChainStore(lc fx.Lifecycle, cbs dtypes.ChainBlockstore, sbs dtypes.StateBlockstore, ds dtypes.MetadataDS, basebs dtypes.BaseBlockstore, syscalls vm.SyscallBuilder, j journal.Journal) *store.ChainStore { - chain := store.NewChainStore(cbs, sbs, ds, syscalls, j) +func ChainStore(lc fx.Lifecycle, cbs dtypes.ChainBlockstore, sbs dtypes.StateBlockstore, ds dtypes.MetadataDS, basebs dtypes.BaseBlockstore, j journal.Journal) *store.ChainStore { + chain := store.NewChainStore(cbs, sbs, ds, j) if err := chain.Load(); err != nil { log.Warnf("loading chain state from disk: %s", err) @@ -100,14 +100,14 @@ func ChainStore(lc fx.Lifecycle, cbs dtypes.ChainBlockstore, sbs dtypes.StateBlo return chain } -func NetworkName(mctx helpers.MetricsCtx, lc fx.Lifecycle, cs *store.ChainStore, us stmgr.UpgradeSchedule, _ dtypes.AfterGenesisSet) (dtypes.NetworkName, error) { +func NetworkName(mctx helpers.MetricsCtx, lc fx.Lifecycle, cs *store.ChainStore, syscalls vm.SyscallBuilder, us stmgr.UpgradeSchedule, _ dtypes.AfterGenesisSet) (dtypes.NetworkName, error) { if !build.Devnet { return "testnetnet", nil } ctx := helpers.LifecycleCtx(mctx, lc) - sm, err := stmgr.NewStateManagerWithUpgradeSchedule(cs, us) + sm, err := stmgr.NewStateManagerWithUpgradeSchedule(cs, syscalls, us) if err != nil { return "", err } diff --git a/node/modules/client.go b/node/modules/client.go index e0bcc13c7..7ca97f0e0 100644 --- a/node/modules/client.go +++ b/node/modules/client.go @@ -14,6 +14,11 @@ import ( dtimpl "github.com/filecoin-project/go-data-transfer/impl" dtnet "github.com/filecoin-project/go-data-transfer/network" dtgstransport "github.com/filecoin-project/go-data-transfer/transport/graphsync" + "github.com/filecoin-project/go-state-types/abi" + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" + "github.com/libp2p/go-libp2p-core/host" + "github.com/filecoin-project/go-fil-markets/discovery" discoveryimpl "github.com/filecoin-project/go-fil-markets/discovery/impl" "github.com/filecoin-project/go-fil-markets/retrievalmarket" @@ -23,13 +28,9 @@ import ( storageimpl "github.com/filecoin-project/go-fil-markets/storagemarket/impl" "github.com/filecoin-project/go-fil-markets/storagemarket/impl/requestvalidation" smnet "github.com/filecoin-project/go-fil-markets/storagemarket/network" - "github.com/filecoin-project/go-multistore" - "github.com/filecoin-project/go-state-types/abi" - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" - "github.com/libp2p/go-libp2p-core/host" - "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/markets/storageadapter" + "github.com/filecoin-project/lotus/chain/market" "github.com/filecoin-project/lotus/journal" "github.com/filecoin-project/lotus/markets" @@ -38,10 +39,8 @@ import ( "github.com/filecoin-project/lotus/node/impl/full" payapi "github.com/filecoin-project/lotus/node/impl/paych" "github.com/filecoin-project/lotus/node/modules/dtypes" - "github.com/filecoin-project/lotus/node/modules/helpers" "github.com/filecoin-project/lotus/node/repo" - "github.com/filecoin-project/lotus/node/repo/importmgr" - "github.com/filecoin-project/lotus/node/repo/retrievalstoremgr" + "github.com/filecoin-project/lotus/node/repo/imports" ) func HandleMigrateClientFunds(lc fx.Lifecycle, ds dtypes.MetadataDS, wallet full.WalletAPI, fundMgr *market.FundManager) { @@ -78,34 +77,21 @@ func HandleMigrateClientFunds(lc fx.Lifecycle, ds dtypes.MetadataDS, wallet full }) } -func ClientMultiDatastore(lc fx.Lifecycle, mctx helpers.MetricsCtx, r repo.LockedRepo) (dtypes.ClientMultiDstore, error) { - ctx := helpers.LifecycleCtx(mctx, lc) - ds, err := r.Datastore(ctx, "/client") - if err != nil { - return nil, xerrors.Errorf("getting datastore out of repo: %w", err) +func ClientImportMgr(ds dtypes.MetadataDS, r repo.LockedRepo) (dtypes.ClientImportMgr, error) { + // store the imports under the repo's `imports` subdirectory. + dir := filepath.Join(r.Path(), "imports") + if err := os.MkdirAll(dir, 0755); err != nil { + return nil, xerrors.Errorf("failed to create directory %s: %w", dir, err) } - mds, err := multistore.NewMultiDstore(ds) - if err != nil { - return nil, err - } - - lc.Append(fx.Hook{ - OnStop: func(ctx context.Context) error { - return mds.Close() - }, - }) - - return mds, nil + ns := namespace.Wrap(ds, datastore.NewKey("/client")) + return imports.NewManager(ns, dir), nil } -func ClientImportMgr(mds dtypes.ClientMultiDstore, ds dtypes.MetadataDS) dtypes.ClientImportMgr { - return importmgr.New(mds, namespace.Wrap(ds, datastore.NewKey("/client"))) -} - -func ClientBlockstore(imgr dtypes.ClientImportMgr) dtypes.ClientBlockstore { +// TODO this should be removed. +func ClientBlockstore() dtypes.ClientBlockstore { // in most cases this is now unused in normal operations -- however, it's important to preserve for the IPFS use case - return blockstore.WrapIDStore(imgr.Blockstore) + return blockstore.WrapIDStore(blockstore.FromDatastore(datastore.NewMapDatastore())) } // RegisterClientValidator is an initialization hook that registers the client @@ -173,13 +159,30 @@ func NewClientDatastore(ds dtypes.MetadataDS) dtypes.ClientDatastore { return namespace.Wrap(ds, datastore.NewKey("/deals/client")) } -func StorageClient(lc fx.Lifecycle, h host.Host, ibs dtypes.ClientBlockstore, mds dtypes.ClientMultiDstore, r repo.LockedRepo, dataTransfer dtypes.ClientDataTransfer, discovery *discoveryimpl.Local, deals dtypes.ClientDatastore, scn storagemarket.StorageClientNode, j journal.Journal) (storagemarket.StorageClient, error) { +// StorageBlockstoreAccessor returns the default storage blockstore accessor +// from the import manager. +func StorageBlockstoreAccessor(importmgr dtypes.ClientImportMgr) storagemarket.BlockstoreAccessor { + return storageadapter.NewImportsBlockstoreAccessor(importmgr) +} + +// RetrievalBlockstoreAccessor returns the default retrieval blockstore accessor +// using the subdirectory `retrievals` under the repo. +func RetrievalBlockstoreAccessor(r repo.LockedRepo) (retrievalmarket.BlockstoreAccessor, error) { + dir := filepath.Join(r.Path(), "retrievals") + if err := os.MkdirAll(dir, 0755); err != nil { + return nil, xerrors.Errorf("failed to create directory %s: %w", dir, err) + } + return retrievaladapter.NewCARBlockstoreAccessor(dir), nil +} + +func StorageClient(lc fx.Lifecycle, h host.Host, dataTransfer dtypes.ClientDataTransfer, discovery *discoveryimpl.Local, + deals dtypes.ClientDatastore, scn storagemarket.StorageClientNode, accessor storagemarket.BlockstoreAccessor, j journal.Journal) (storagemarket.StorageClient, error) { // go-fil-markets protocol retries: // 1s, 5s, 25s, 2m5s, 5m x 11 ~= 1 hour marketsRetryParams := smnet.RetryParameters(time.Second, 5*time.Minute, 15, 5) net := smnet.NewFromLibp2pHost(h, marketsRetryParams) - c, err := storageimpl.NewClient(net, ibs, mds, dataTransfer, discovery, deals, scn, storageimpl.DealPollingInterval(time.Second)) + c, err := storageimpl.NewClient(net, dataTransfer, discovery, deals, scn, accessor, storageimpl.DealPollingInterval(time.Second)) if err != nil { return nil, err } @@ -201,10 +204,13 @@ func StorageClient(lc fx.Lifecycle, h host.Host, ibs dtypes.ClientBlockstore, md } // RetrievalClient creates a new retrieval client attached to the client blockstore -func RetrievalClient(lc fx.Lifecycle, h host.Host, mds dtypes.ClientMultiDstore, dt dtypes.ClientDataTransfer, payAPI payapi.PaychAPI, resolver discovery.PeerResolver, ds dtypes.MetadataDS, chainAPI full.ChainAPI, stateAPI full.StateAPI, j journal.Journal) (retrievalmarket.RetrievalClient, error) { +func RetrievalClient(lc fx.Lifecycle, h host.Host, r repo.LockedRepo, dt dtypes.ClientDataTransfer, payAPI payapi.PaychAPI, resolver discovery.PeerResolver, + ds dtypes.MetadataDS, chainAPI full.ChainAPI, stateAPI full.StateAPI, accessor retrievalmarket.BlockstoreAccessor, j journal.Journal) (retrievalmarket.RetrievalClient, error) { + adapter := retrievaladapter.NewRetrievalClientNode(payAPI, chainAPI, stateAPI) network := rmnet.NewFromLibp2pHost(h) - client, err := retrievalimpl.NewClient(network, mds, dt, adapter, resolver, namespace.Wrap(ds, datastore.NewKey("/retrievals/client"))) + ds = namespace.Wrap(ds, datastore.NewKey("/retrievals/client")) + client, err := retrievalimpl.NewClient(network, dt, adapter, resolver, ds, accessor) if err != nil { return nil, err } @@ -221,13 +227,3 @@ func RetrievalClient(lc fx.Lifecycle, h host.Host, mds dtypes.ClientMultiDstore, }) return client, nil } - -// ClientRetrievalStoreManager is the default version of the RetrievalStoreManager that runs on multistore -func ClientRetrievalStoreManager(imgr dtypes.ClientImportMgr) dtypes.ClientRetrievalStoreManager { - return retrievalstoremgr.NewMultiStoreRetrievalStoreManager(imgr) -} - -// ClientBlockstoreRetrievalStoreManager is the default version of the RetrievalStoreManager that runs on multistore -func ClientBlockstoreRetrievalStoreManager(bs dtypes.ClientBlockstore) dtypes.ClientRetrievalStoreManager { - return retrievalstoremgr.NewBlockstoreRetrievalStoreManager(bs) -} diff --git a/node/modules/dtypes/storage.go b/node/modules/dtypes/storage.go index b4420f701..6893908f7 100644 --- a/node/modules/dtypes/storage.go +++ b/node/modules/dtypes/storage.go @@ -1,22 +1,18 @@ package dtypes import ( + datatransfer "github.com/filecoin-project/go-data-transfer" + "github.com/filecoin-project/go-statestore" bserv "github.com/ipfs/go-blockservice" "github.com/ipfs/go-datastore" "github.com/ipfs/go-graphsync" exchange "github.com/ipfs/go-ipfs-exchange-interface" - format "github.com/ipfs/go-ipld-format" - "github.com/filecoin-project/go-fil-markets/storagemarket/impl/requestvalidation" - "github.com/filecoin-project/go-multistore" - - datatransfer "github.com/filecoin-project/go-data-transfer" "github.com/filecoin-project/go-fil-markets/piecestore" - "github.com/filecoin-project/go-statestore" + "github.com/filecoin-project/go-fil-markets/storagemarket/impl/requestvalidation" "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/node/repo/importmgr" - "github.com/filecoin-project/lotus/node/repo/retrievalstoremgr" + "github.com/filecoin-project/lotus/node/repo/imports" ) // MetadataDS stores metadata. By default it's namespaced under /metadata in @@ -71,13 +67,11 @@ type ( type ChainBitswap exchange.Interface type ChainBlockService bserv.BlockService -type ClientMultiDstore *multistore.MultiStore -type ClientImportMgr *importmgr.Mgr +type ClientImportMgr *imports.Manager type ClientBlockstore blockstore.BasicBlockstore type ClientDealStore *statestore.StateStore type ClientRequestValidator *requestvalidation.UnifiedRequestValidator type ClientDatastore datastore.Batching -type ClientRetrievalStoreManager retrievalstoremgr.RetrievalStoreManager type Graphsync graphsync.GraphExchange @@ -92,7 +86,5 @@ type ProviderRequestValidator *requestvalidation.UnifiedRequestValidator // ProviderDataTransfer is a data transfer manager for the provider type ProviderDataTransfer datatransfer.Manager -type StagingDAG format.DAGService type StagingBlockstore blockstore.BasicBlockstore type StagingGraphsync graphsync.GraphExchange -type StagingMultiDstore *multistore.MultiStore diff --git a/node/modules/graphsync.go b/node/modules/graphsync.go index a7f62db76..cbd67ad1f 100644 --- a/node/modules/graphsync.go +++ b/node/modules/graphsync.go @@ -1,9 +1,6 @@ package modules import ( - "github.com/filecoin-project/lotus/node/modules/dtypes" - "github.com/filecoin-project/lotus/node/modules/helpers" - "github.com/filecoin-project/lotus/node/repo" "github.com/ipfs/go-graphsync" graphsyncimpl "github.com/ipfs/go-graphsync/impl" gsnet "github.com/ipfs/go-graphsync/network" @@ -11,6 +8,10 @@ import ( "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" "go.uber.org/fx" + + "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/modules/helpers" + "github.com/filecoin-project/lotus/node/repo" ) // Graphsync creates a graphsync instance from the given loader and storer diff --git a/node/modules/ipfsclient.go b/node/modules/ipfs.go similarity index 69% rename from node/modules/ipfsclient.go rename to node/modules/ipfs.go index 24c5c9678..149608309 100644 --- a/node/modules/ipfsclient.go +++ b/node/modules/ipfs.go @@ -1,16 +1,29 @@ package modules import ( + bstore "github.com/ipfs/go-ipfs-blockstore" "go.uber.org/fx" "golang.org/x/xerrors" "github.com/multiformats/go-multiaddr" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" + "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/markets/retrievaladapter" + "github.com/filecoin-project/lotus/markets/storageadapter" "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/node/modules/helpers" ) +func IpfsStorageBlockstoreAccessor(ipfsBlockstore dtypes.ClientBlockstore) storagemarket.BlockstoreAccessor { + return storageadapter.NewFixedBlockstoreAccessor(bstore.Blockstore(ipfsBlockstore)) +} + +func IpfsRetrievalBlockstoreAccessor(ipfsBlockstore dtypes.ClientBlockstore) retrievalmarket.BlockstoreAccessor { + return retrievaladapter.NewFixedBlockstoreAccessor(bstore.Blockstore(ipfsBlockstore)) +} + // IpfsClientBlockstore returns a ClientBlockstore implementation backed by an IPFS node. // If ipfsMaddr is empty, a local IPFS node is assumed considering IPFS_PATH configuration. // If ipfsMaddr is not empty, it will connect to the remote IPFS node with the provided multiaddress. diff --git a/node/modules/stmgr.go b/node/modules/stmgr.go index 9d3917b85..af53457f9 100644 --- a/node/modules/stmgr.go +++ b/node/modules/stmgr.go @@ -1,14 +1,15 @@ package modules import ( + "github.com/filecoin-project/lotus/chain/vm" "go.uber.org/fx" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" ) -func StateManager(lc fx.Lifecycle, cs *store.ChainStore, us stmgr.UpgradeSchedule) (*stmgr.StateManager, error) { - sm, err := stmgr.NewStateManagerWithUpgradeSchedule(cs, us) +func StateManager(lc fx.Lifecycle, cs *store.ChainStore, sys vm.SyscallBuilder, us stmgr.UpgradeSchedule) (*stmgr.StateManager, error) { + sm, err := stmgr.NewStateManagerWithUpgradeSchedule(cs, sys, us) if err != nil { return nil, err } diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 5497eab58..075eed99d 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -11,24 +11,10 @@ import ( "strings" "time" - "github.com/filecoin-project/lotus/markets/pricing" "go.uber.org/fx" "go.uber.org/multierr" "golang.org/x/xerrors" - "github.com/ipfs/go-bitswap" - "github.com/ipfs/go-bitswap/network" - "github.com/ipfs/go-blockservice" - "github.com/ipfs/go-cid" - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" - graphsync "github.com/ipfs/go-graphsync/impl" - gsnet "github.com/ipfs/go-graphsync/network" - "github.com/ipfs/go-graphsync/storeutil" - "github.com/ipfs/go-merkledag" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/routing" - "github.com/filecoin-project/go-address" dtimpl "github.com/filecoin-project/go-data-transfer/impl" dtnet "github.com/filecoin-project/go-data-transfer/network" @@ -44,19 +30,25 @@ import ( "github.com/filecoin-project/go-fil-markets/storagemarket/impl/storedask" smnet "github.com/filecoin-project/go-fil-markets/storagemarket/network" "github.com/filecoin-project/go-jsonrpc/auth" - "github.com/filecoin-project/go-multistore" "github.com/filecoin-project/go-paramfetch" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-statestore" "github.com/filecoin-project/go-storedcounter" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" + graphsync "github.com/ipfs/go-graphsync/impl" + gsnet "github.com/ipfs/go-graphsync/network" + "github.com/ipfs/go-graphsync/storeutil" + "github.com/libp2p/go-libp2p-core/host" - "github.com/filecoin-project/lotus/api" sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" "github.com/filecoin-project/lotus/extern/sector-storage/stores" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/v0api" "github.com/filecoin-project/lotus/api/v1api" "github.com/filecoin-project/lotus/blockstore" @@ -67,7 +59,9 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/journal" "github.com/filecoin-project/lotus/markets" + "github.com/filecoin-project/lotus/markets/dagstore" marketevents "github.com/filecoin-project/lotus/markets/loggers" + "github.com/filecoin-project/lotus/markets/pricing" lotusminer "github.com/filecoin-project/lotus/miner" "github.com/filecoin-project/lotus/node/config" "github.com/filecoin-project/lotus/node/modules/dtypes" @@ -383,27 +377,6 @@ func NewProviderPieceStore(lc fx.Lifecycle, ds dtypes.MetadataDS) (dtypes.Provid return ps, nil } -func StagingMultiDatastore(lc fx.Lifecycle, mctx helpers.MetricsCtx, r repo.LockedRepo) (dtypes.StagingMultiDstore, error) { - ctx := helpers.LifecycleCtx(mctx, lc) - ds, err := r.Datastore(ctx, "/staging") - if err != nil { - return nil, xerrors.Errorf("getting datastore out of reop: %w", err) - } - - mds, err := multistore.NewMultiDstore(ds) - if err != nil { - return nil, err - } - - lc.Append(fx.Hook{ - OnStop: func(ctx context.Context) error { - return mds.Close() - }, - }) - - return mds, nil -} - // StagingBlockstore creates a blockstore for staging blocks for a miner // in a storage deal, prior to sealing func StagingBlockstore(lc fx.Lifecycle, mctx helpers.MetricsCtx, r repo.LockedRepo) (dtypes.StagingBlockstore, error) { @@ -416,26 +389,6 @@ func StagingBlockstore(lc fx.Lifecycle, mctx helpers.MetricsCtx, r repo.LockedRe return blockstore.FromDatastore(stagingds), nil } -// StagingDAG is a DAGService for the StagingBlockstore -func StagingDAG(mctx helpers.MetricsCtx, lc fx.Lifecycle, ibs dtypes.StagingBlockstore, rt routing.Routing, h host.Host) (dtypes.StagingDAG, error) { - - bitswapNetwork := network.NewFromIpfsHost(h, rt) - bitswapOptions := []bitswap.Option{bitswap.ProvideEnabled(false)} - exch := bitswap.New(mctx, bitswapNetwork, ibs, bitswapOptions...) - - bsvc := blockservice.New(ibs, exch) - dag := merkledag.NewDAGService(bsvc) - - lc.Append(fx.Hook{ - OnStop: func(_ context.Context) error { - // blockservice closes the exchange - return bsvc.Close() - }, - }) - - return dag, nil -} - // StagingGraphsync creates a graphsync instance which reads and writes blocks // to the StagingBlockstore func StagingGraphsync(parallelTransfers uint64) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, ibs dtypes.StagingBlockstore, h host.Host) dtypes.StagingGraphsync { @@ -599,22 +552,43 @@ func BasicDealFilter(user dtypes.StorageDealFilter) func(onlineOk dtypes.Conside func StorageProvider(minerAddress dtypes.MinerAddress, storedAsk *storedask.StoredAsk, h host.Host, ds dtypes.MetadataDS, - mds dtypes.StagingMultiDstore, r repo.LockedRepo, pieceStore dtypes.ProviderPieceStore, dataTransfer dtypes.ProviderDataTransfer, spn storagemarket.StorageProviderNode, df dtypes.StorageDealFilter, + dsw *dagstore.Wrapper, ) (storagemarket.StorageProvider, error) { net := smnet.NewFromLibp2pHost(h) - store, err := piecefilestore.NewLocalFileStore(piecefilestore.OsPath(r.Path())) + + dir := filepath.Join(r.Path(), "deal-staging") + + // migrate temporary files that were created directly under the repo, by + // moving them to the new directory and symlinking them. + oldDir := r.Path() + if err := migrateDealStaging(oldDir, dir); err != nil { + return nil, xerrors.Errorf("failed to make deal staging directory %w", err) + } + + store, err := piecefilestore.NewLocalFileStore(piecefilestore.OsPath(dir)) if err != nil { return nil, err } opt := storageimpl.CustomDealDecisionLogic(storageimpl.DealDeciderFunc(df)) - return storageimpl.NewProvider(net, namespace.Wrap(ds, datastore.NewKey("/deals/provider")), store, mds, pieceStore, dataTransfer, spn, address.Address(minerAddress), storedAsk, opt) + return storageimpl.NewProvider( + net, + namespace.Wrap(ds, datastore.NewKey("/deals/provider")), + store, + dsw, + pieceStore, + dataTransfer, + spn, + address.Address(minerAddress), + storedAsk, + opt, + ) } func RetrievalDealFilter(userFilter dtypes.RetrievalDealFilter) func(onlineOk dtypes.ConsiderOnlineRetrievalDealsConfigFunc, @@ -672,17 +646,28 @@ func RetrievalPricingFunc(cfg config.DealmakingConfig) func(_ dtypes.ConsiderOnl func RetrievalProvider( maddr dtypes.MinerAddress, adapter retrievalmarket.RetrievalProviderNode, + sa retrievalmarket.SectorAccessor, netwk rmnet.RetrievalMarketNetwork, ds dtypes.MetadataDS, pieceStore dtypes.ProviderPieceStore, - mds dtypes.StagingMultiDstore, dt dtypes.ProviderDataTransfer, pricingFnc dtypes.RetrievalPricingFunc, userFilter dtypes.RetrievalDealFilter, + dagStore *dagstore.Wrapper, ) (retrievalmarket.RetrievalProvider, error) { opt := retrievalimpl.DealDeciderOpt(retrievalimpl.DealDecider(userFilter)) - return retrievalimpl.NewProvider(address.Address(maddr), adapter, netwk, pieceStore, mds, dt, namespace.Wrap(ds, datastore.NewKey("/retrievals/provider")), - retrievalimpl.RetrievalPricingFunc(pricingFnc), opt) + return retrievalimpl.NewProvider( + address.Address(maddr), + adapter, + sa, + netwk, + pieceStore, + dagStore, + dt, + namespace.Wrap(ds, datastore.NewKey("/retrievals/provider")), + retrievalimpl.RetrievalPricingFunc(pricingFnc), + opt, + ) } var WorkerCallsPrefix = datastore.NewKey("/worker/calls") @@ -1008,6 +993,59 @@ func mutateCfg(r repo.LockedRepo, mutator func(*config.StorageMiner)) error { return multierr.Combine(typeErr, setConfigErr) } +func migrateDealStaging(oldPath, newPath string) error { + dirInfo, err := os.Stat(newPath) + if err == nil { + if !dirInfo.IsDir() { + return xerrors.Errorf("%s is not a directory", newPath) + } + // The newPath exists already, below migration has already occurred. + return nil + } + + // if the directory doesn't exist, create it + if os.IsNotExist(err) { + if err := os.MkdirAll(newPath, 0755); err != nil { + return xerrors.Errorf("failed to mk directory %s for deal staging: %w", newPath, err) + } + } else { // if we failed for other reasons, abort. + return err + } + + // if this is the first time we created the directory, symlink all staged deals into it. "Migration" + // get a list of files in the miner repo + dirEntries, err := os.ReadDir(oldPath) + if err != nil { + return xerrors.Errorf("failed to list directory %s for deal staging: %w", oldPath, err) + } + + for _, entry := range dirEntries { + // ignore directories, they are not the deals. + if entry.IsDir() { + continue + } + // the FileStore from fil-storage-market creates temporary staged deal files with the pattern "fstmp" + // https://github.com/filecoin-project/go-fil-markets/blob/00ff81e477d846ac0cb58a0c7d1c2e9afb5ee1db/filestore/filestore.go#L69 + name := entry.Name() + if strings.Contains(name, "fstmp") { + // from the miner repo + oldPath := filepath.Join(oldPath, name) + // to its subdir "deal-staging" + newPath := filepath.Join(newPath, name) + // create a symbolic link in the new deal staging directory to preserve existing staged deals. + // all future staged deals will be created here. + if err := os.Rename(oldPath, newPath); err != nil { + return xerrors.Errorf("failed to move %s to %s: %w", oldPath, newPath, err) + } + if err := os.Symlink(newPath, oldPath); err != nil { + return xerrors.Errorf("failed to symlink %s to %s: %w", oldPath, newPath, err) + } + log.Infow("symlinked staged deal", "from", oldPath, "to", newPath) + } + } + return nil +} + func ExtractEnabledMinerSubsystems(cfg config.MinerSubsystemConfig) (res api.MinerSubsystems) { if cfg.EnableMining { res = append(res, api.SubsystemMining) diff --git a/node/modules/storageminer_dagstore.go b/node/modules/storageminer_dagstore.go new file mode 100644 index 000000000..1f72a49b9 --- /dev/null +++ b/node/modules/storageminer_dagstore.go @@ -0,0 +1,112 @@ +package modules + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strconv" + + "go.uber.org/fx" + "golang.org/x/xerrors" + + "github.com/filecoin-project/dagstore" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" + + mdagstore "github.com/filecoin-project/lotus/markets/dagstore" + "github.com/filecoin-project/lotus/node/config" + "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/repo" +) + +const ( + EnvDAGStoreCopyConcurrency = "LOTUS_DAGSTORE_COPY_CONCURRENCY" + DefaultDAGStoreDir = "dagstore" +) + +// NewMinerAPI creates a new MinerAPI adaptor for the dagstore mounts. +func NewMinerAPI(lc fx.Lifecycle, r repo.LockedRepo, pieceStore dtypes.ProviderPieceStore, sa retrievalmarket.SectorAccessor) (mdagstore.MinerAPI, error) { + cfg, err := extractDAGStoreConfig(r) + if err != nil { + return nil, err + } + + // caps the amount of concurrent calls to the storage, so that we don't + // spam it during heavy processes like bulk migration. + if v, ok := os.LookupEnv("LOTUS_DAGSTORE_MOUNT_CONCURRENCY"); ok { + concurrency, err := strconv.Atoi(v) + if err == nil { + cfg.MaxConcurrencyStorageCalls = concurrency + } + } + + mountApi := mdagstore.NewMinerAPI(pieceStore, sa, cfg.MaxConcurrencyStorageCalls) + ready := make(chan error, 1) + pieceStore.OnReady(func(err error) { + ready <- err + }) + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + if err := <-ready; err != nil { + return fmt.Errorf("aborting dagstore start; piecestore failed to start: %s", err) + } + return mountApi.Start(ctx) + }, + OnStop: func(context.Context) error { + return nil + }, + }) + + return mountApi, nil +} + +// DAGStore constructs a DAG store using the supplied minerAPI, and the +// user configuration. It returns both the DAGStore and the Wrapper suitable for +// passing to markets. +func DAGStore(lc fx.Lifecycle, r repo.LockedRepo, minerAPI mdagstore.MinerAPI) (*dagstore.DAGStore, *mdagstore.Wrapper, error) { + cfg, err := extractDAGStoreConfig(r) + if err != nil { + return nil, nil, err + } + + // fall back to default root directory if not explicitly set in the config. + if cfg.RootDir == "" { + cfg.RootDir = filepath.Join(r.Path(), DefaultDAGStoreDir) + } + + v, ok := os.LookupEnv(EnvDAGStoreCopyConcurrency) + if ok { + concurrency, err := strconv.Atoi(v) + if err == nil { + cfg.MaxConcurrentReadyFetches = concurrency + } + } + + dagst, w, err := mdagstore.NewDAGStore(cfg, minerAPI) + if err != nil { + return nil, nil, xerrors.Errorf("failed to create DAG store: %w", err) + } + + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + return w.Start(ctx) + }, + OnStop: func(context.Context) error { + return w.Close() + }, + }) + + return dagst, w, nil +} + +func extractDAGStoreConfig(r repo.LockedRepo) (config.DAGStoreConfig, error) { + cfg, err := r.Config() + if err != nil { + return config.DAGStoreConfig{}, xerrors.Errorf("could not load config: %w", err) + } + mcfg, ok := cfg.(*config.StorageMiner) + if !ok { + return config.DAGStoreConfig{}, xerrors.Errorf("config not expected type; expected config.StorageMiner, got: %T", cfg) + } + return mcfg.DAGStore, nil +} diff --git a/node/repo/importmgr/mgr.go b/node/repo/importmgr/mgr.go deleted file mode 100644 index 936d9b606..000000000 --- a/node/repo/importmgr/mgr.go +++ /dev/null @@ -1,111 +0,0 @@ -package importmgr - -import ( - "encoding/json" - "fmt" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-multistore" - "github.com/filecoin-project/lotus/blockstore" - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" -) - -type Mgr struct { - mds *multistore.MultiStore - ds datastore.Batching - - Blockstore blockstore.BasicBlockstore -} - -type Label string - -const ( - LSource = "source" // Function which created the import - LRootCid = "root" // Root CID - LFileName = "filename" // Local file path - LMTime = "mtime" // File modification timestamp -) - -func New(mds *multistore.MultiStore, ds datastore.Batching) *Mgr { - return &Mgr{ - mds: mds, - Blockstore: blockstore.Adapt(mds.MultiReadBlockstore()), - - ds: datastore.NewLogDatastore(namespace.Wrap(ds, datastore.NewKey("/stores")), "storess"), - } -} - -type StoreMeta struct { - Labels map[string]string -} - -func (m *Mgr) NewStore() (multistore.StoreID, *multistore.Store, error) { - id := m.mds.Next() - st, err := m.mds.Get(id) - if err != nil { - return 0, nil, err - } - - meta, err := json.Marshal(&StoreMeta{Labels: map[string]string{ - "source": "unknown", - }}) - if err != nil { - return 0, nil, xerrors.Errorf("marshaling empty store metadata: %w", err) - } - - err = m.ds.Put(datastore.NewKey(fmt.Sprintf("%d", id)), meta) - return id, st, err -} - -func (m *Mgr) AddLabel(id multistore.StoreID, key, value string) error { // source, file path, data CID.. - meta, err := m.ds.Get(datastore.NewKey(fmt.Sprintf("%d", id))) - if err != nil { - return xerrors.Errorf("getting metadata form datastore: %w", err) - } - - var sm StoreMeta - if err := json.Unmarshal(meta, &sm); err != nil { - return xerrors.Errorf("unmarshaling store meta: %w", err) - } - - sm.Labels[key] = value - - meta, err = json.Marshal(&sm) - if err != nil { - return xerrors.Errorf("marshaling store meta: %w", err) - } - - return m.ds.Put(datastore.NewKey(fmt.Sprintf("%d", id)), meta) -} - -func (m *Mgr) List() []multistore.StoreID { - return m.mds.List() -} - -func (m *Mgr) Info(id multistore.StoreID) (*StoreMeta, error) { - meta, err := m.ds.Get(datastore.NewKey(fmt.Sprintf("%d", id))) - if err != nil { - return nil, xerrors.Errorf("getting metadata form datastore: %w", err) - } - - var sm StoreMeta - if err := json.Unmarshal(meta, &sm); err != nil { - return nil, xerrors.Errorf("unmarshaling store meta: %w", err) - } - - return &sm, nil -} - -func (m *Mgr) Remove(id multistore.StoreID) error { - if err := m.mds.Delete(id); err != nil { - return xerrors.Errorf("removing import: %w", err) - } - - if err := m.ds.Delete(datastore.NewKey(fmt.Sprintf("%d", id))); err != nil { - return xerrors.Errorf("removing import metadata: %w", err) - } - - return nil -} diff --git a/node/repo/imports/manager.go b/node/repo/imports/manager.go new file mode 100644 index 000000000..d972ffb77 --- /dev/null +++ b/node/repo/imports/manager.go @@ -0,0 +1,268 @@ +package imports + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strconv" + + "github.com/ipfs/go-cid" + "github.com/ipfs/go-datastore/query" + logging "github.com/ipfs/go-log/v2" + "golang.org/x/xerrors" + + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" + + "github.com/filecoin-project/go-fil-markets/shared" +) + +var log = logging.Logger("importmgr") + +type ID uint64 + +func (id ID) dsKey() datastore.Key { + return datastore.NewKey(fmt.Sprintf("%d", id)) +} + +type Manager struct { + ds datastore.Batching + rootDir string + counter *shared.TimeCounter +} + +type LabelKey = string +type LabelValue = string + +const ( + CAROwnerImportMgr = "importmgr" + CAROwnerUser = "user" +) + +const ( + LSource = LabelKey("source") // Function which created the import + LRootCid = LabelKey("root") // Root CID + LFileName = LabelKey("filename") // Local file path of the source file. + LCARPath = LabelKey("car_path") // Path of the CARv2 file containing the imported data. + LCAROwner = LabelKey("car_owner") // Owner of the CAR; "importmgr" is us; "user" or empty is them. +) + +func NewManager(ds datastore.Batching, rootDir string) *Manager { + ds = namespace.Wrap(ds, datastore.NewKey("/stores")) + ds = datastore.NewLogDatastore(ds, "storess") + + m := &Manager{ + ds: ds, + rootDir: rootDir, + counter: shared.NewTimeCounter(), + } + + log.Info("sanity checking imports") + + ids, err := m.List() + if err != nil { + log.Warnw("failed to enumerate imports on initialization", "error", err) + return m + } + + var broken int + for _, id := range ids { + log := log.With("id", id) + + info, err := m.Info(id) + if err != nil { + log.Warnw("failed to query metadata for import; skipping", "error", err) + continue + } + + log = log.With("source", info.Labels[LSource], "root", info.Labels[LRootCid], "original", info.Labels[LFileName]) + + path, ok := info.Labels[LCARPath] + if !ok { + broken++ + log.Warnw("import lacks carv2 path; import will not work; please reimport") + continue + } + + stat, err := os.Stat(path) + if err != nil { + broken++ + log.Warnw("import has missing/broken carv2; please reimport", "error", err) + continue + } + + log.Infow("import ok", "size", stat.Size()) + } + + log.Infow("sanity check completed", "broken", broken, "total", len(ids)) + + return m +} + +type Meta struct { + Labels map[LabelKey]LabelValue +} + +// CreateImport initializes a new import, returning its ID and optionally a +// CAR path where to place the data, if requested. +func (m *Manager) CreateImport() (id ID, err error) { + id = ID(m.counter.Next()) + + meta := &Meta{Labels: map[LabelKey]LabelValue{ + LSource: "unknown", + }} + + metajson, err := json.Marshal(meta) + if err != nil { + return 0, xerrors.Errorf("marshaling store metadata: %w", err) + } + + err = m.ds.Put(id.dsKey(), metajson) + if err != nil { + return 0, xerrors.Errorf("failed to insert import metadata: %w", err) + } + + return id, err +} + +// AllocateCAR creates a new CAR allocated to the supplied import under the +// root directory. +func (m *Manager) AllocateCAR(id ID) (path string, err error) { + meta, err := m.ds.Get(id.dsKey()) + if err != nil { + return "", xerrors.Errorf("getting metadata form datastore: %w", err) + } + + var sm Meta + if err := json.Unmarshal(meta, &sm); err != nil { + return "", xerrors.Errorf("unmarshaling store meta: %w", err) + } + + // refuse if a CAR path already exists. + if curr := sm.Labels[LCARPath]; curr != "" { + return "", xerrors.Errorf("import CAR already exists at %s: %w", curr, err) + } + + path = filepath.Join(m.rootDir, fmt.Sprintf("%d.car", id)) + file, err := os.Create(path) + if err != nil { + return "", xerrors.Errorf("failed to create car file for import: %w", err) + } + + // close the file before returning the path. + if err := file.Close(); err != nil { + return "", xerrors.Errorf("failed to close temp file: %w", err) + } + + // record the path and ownership. + sm.Labels[LCARPath] = path + sm.Labels[LCAROwner] = CAROwnerImportMgr + + if meta, err = json.Marshal(sm); err != nil { + return "", xerrors.Errorf("marshaling store metadata: %w", err) + } + + err = m.ds.Put(id.dsKey(), meta) + return path, err +} + +// AddLabel adds a label associated with an import, such as the source, +// car path, CID, etc. +func (m *Manager) AddLabel(id ID, key LabelKey, value LabelValue) error { + meta, err := m.ds.Get(id.dsKey()) + if err != nil { + return xerrors.Errorf("getting metadata form datastore: %w", err) + } + + var sm Meta + if err := json.Unmarshal(meta, &sm); err != nil { + return xerrors.Errorf("unmarshaling store meta: %w", err) + } + + sm.Labels[key] = value + + meta, err = json.Marshal(&sm) + if err != nil { + return xerrors.Errorf("marshaling store meta: %w", err) + } + + return m.ds.Put(id.dsKey(), meta) +} + +// List returns all import IDs known by this Manager. +func (m *Manager) List() ([]ID, error) { + var keys []ID + + qres, err := m.ds.Query(query.Query{KeysOnly: true}) + if err != nil { + return nil, xerrors.Errorf("query error: %w", err) + } + defer qres.Close() //nolint:errcheck + + for r := range qres.Next() { + k := r.Key + if string(k[0]) == "/" { + k = k[1:] + } + + id, err := strconv.ParseUint(k, 10, 64) + if err != nil { + return nil, xerrors.Errorf("failed to parse key %s to uint64, err=%w", r.Key, err) + } + keys = append(keys, ID(id)) + } + + return keys, nil +} + +// Info returns the metadata known to this store for the specified import ID. +func (m *Manager) Info(id ID) (*Meta, error) { + meta, err := m.ds.Get(id.dsKey()) + if err != nil { + return nil, xerrors.Errorf("getting metadata form datastore: %w", err) + } + + var sm Meta + if err := json.Unmarshal(meta, &sm); err != nil { + return nil, xerrors.Errorf("unmarshaling store meta: %w", err) + } + + return &sm, nil +} + +// Remove drops all data associated with the supplied import ID. +func (m *Manager) Remove(id ID) error { + if err := m.ds.Delete(id.dsKey()); err != nil { + return xerrors.Errorf("removing import metadata: %w", err) + } + return nil +} + +func (m *Manager) CARPathFor(dagRoot cid.Cid) (string, error) { + ids, err := m.List() + if err != nil { + return "", xerrors.Errorf("failed to fetch import IDs: %w", err) + } + + for _, id := range ids { + info, err := m.Info(id) + if err != nil { + log.Errorf("failed to fetch info, importID=%d: %s", id, err) + continue + } + if info.Labels[LRootCid] == "" { + continue + } + c, err := cid.Parse(info.Labels[LRootCid]) + if err != nil { + log.Errorf("failed to parse root cid %s: %s", info.Labels[LRootCid], err) + continue + } + if c.Equals(dagRoot) { + return info.Labels[LCARPath], nil + } + } + + return "", nil +} diff --git a/node/repo/memrepo.go b/node/repo/memrepo.go index 00ea32b88..3be4ab567 100644 --- a/node/repo/memrepo.go +++ b/node/repo/memrepo.go @@ -107,6 +107,12 @@ func (lmem *lockedMemRepo) Path() string { } if lmem.t == StorageMiner { + // this is required due to the method makeDealStaging from cmd/lotus-storage-miner/init.go + // deal-staging is the directory deal files are staged in before being sealed into sectors + // for offline deal flow. + if err := os.MkdirAll(filepath.Join(t, "deal-staging"), 0755); err != nil { + panic(err) + } if err := config.WriteStorageFile(filepath.Join(t, fsStorageConfig), stores.StorageConfig{ StoragePaths: []stores.LocalPath{ {Path: t}, diff --git a/node/repo/retrievalstoremgr/retrievalstoremgr.go b/node/repo/retrievalstoremgr/retrievalstoremgr.go deleted file mode 100644 index ba86ccee5..000000000 --- a/node/repo/retrievalstoremgr/retrievalstoremgr.go +++ /dev/null @@ -1,110 +0,0 @@ -package retrievalstoremgr - -import ( - "errors" - - "github.com/filecoin-project/go-multistore" - "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/node/repo/importmgr" - "github.com/ipfs/go-blockservice" - offline "github.com/ipfs/go-ipfs-exchange-offline" - ipldformat "github.com/ipfs/go-ipld-format" - "github.com/ipfs/go-merkledag" -) - -// RetrievalStore references a store for a retrieval deal -// which may or may not have a multistore ID associated with it -type RetrievalStore interface { - StoreID() *multistore.StoreID - DAGService() ipldformat.DAGService -} - -// RetrievalStoreManager manages stores for retrieval deals, abstracting -// the underlying storage mechanism -type RetrievalStoreManager interface { - NewStore() (RetrievalStore, error) - ReleaseStore(RetrievalStore) error -} - -// MultiStoreRetrievalStoreManager manages stores on top of the import manager -type MultiStoreRetrievalStoreManager struct { - imgr *importmgr.Mgr -} - -var _ RetrievalStoreManager = &MultiStoreRetrievalStoreManager{} - -// NewMultiStoreRetrievalStoreManager returns a new multstore based RetrievalStoreManager -func NewMultiStoreRetrievalStoreManager(imgr *importmgr.Mgr) RetrievalStoreManager { - return &MultiStoreRetrievalStoreManager{ - imgr: imgr, - } -} - -// NewStore creates a new store (uses multistore) -func (mrsm *MultiStoreRetrievalStoreManager) NewStore() (RetrievalStore, error) { - storeID, store, err := mrsm.imgr.NewStore() - if err != nil { - return nil, err - } - return &multiStoreRetrievalStore{storeID, store}, nil -} - -// ReleaseStore releases a store (uses multistore remove) -func (mrsm *MultiStoreRetrievalStoreManager) ReleaseStore(retrievalStore RetrievalStore) error { - mrs, ok := retrievalStore.(*multiStoreRetrievalStore) - if !ok { - return errors.New("Cannot release this store type") - } - return mrsm.imgr.Remove(mrs.storeID) -} - -type multiStoreRetrievalStore struct { - storeID multistore.StoreID - store *multistore.Store -} - -func (mrs *multiStoreRetrievalStore) StoreID() *multistore.StoreID { - return &mrs.storeID -} - -func (mrs *multiStoreRetrievalStore) DAGService() ipldformat.DAGService { - return mrs.store.DAG -} - -// BlockstoreRetrievalStoreManager manages a single blockstore as if it were multiple stores -type BlockstoreRetrievalStoreManager struct { - bs blockstore.BasicBlockstore -} - -var _ RetrievalStoreManager = &BlockstoreRetrievalStoreManager{} - -// NewBlockstoreRetrievalStoreManager returns a new blockstore based RetrievalStoreManager -func NewBlockstoreRetrievalStoreManager(bs blockstore.BasicBlockstore) RetrievalStoreManager { - return &BlockstoreRetrievalStoreManager{ - bs: bs, - } -} - -// NewStore creates a new store (just uses underlying blockstore) -func (brsm *BlockstoreRetrievalStoreManager) NewStore() (RetrievalStore, error) { - return &blockstoreRetrievalStore{ - dagService: merkledag.NewDAGService(blockservice.New(brsm.bs, offline.Exchange(brsm.bs))), - }, nil -} - -// ReleaseStore for this implementation does nothing -func (brsm *BlockstoreRetrievalStoreManager) ReleaseStore(RetrievalStore) error { - return nil -} - -type blockstoreRetrievalStore struct { - dagService ipldformat.DAGService -} - -func (brs *blockstoreRetrievalStore) StoreID() *multistore.StoreID { - return nil -} - -func (brs *blockstoreRetrievalStore) DAGService() ipldformat.DAGService { - return brs.dagService -} diff --git a/node/repo/retrievalstoremgr/retrievalstoremgr_test.go b/node/repo/retrievalstoremgr/retrievalstoremgr_test.go deleted file mode 100644 index 0a44fa072..000000000 --- a/node/repo/retrievalstoremgr/retrievalstoremgr_test.go +++ /dev/null @@ -1,137 +0,0 @@ -package retrievalstoremgr_test - -import ( - "context" - "math/rand" - "testing" - - "github.com/ipfs/go-cid" - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/query" - dss "github.com/ipfs/go-datastore/sync" - format "github.com/ipfs/go-ipld-format" - dag "github.com/ipfs/go-merkledag" - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-multistore" - - "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/node/repo/importmgr" - "github.com/filecoin-project/lotus/node/repo/retrievalstoremgr" -) - -func TestMultistoreRetrievalStoreManager(t *testing.T) { - ctx := context.Background() - ds := dss.MutexWrap(datastore.NewMapDatastore()) - multiDS, err := multistore.NewMultiDstore(ds) - require.NoError(t, err) - imgr := importmgr.New(multiDS, ds) - retrievalStoreMgr := retrievalstoremgr.NewMultiStoreRetrievalStoreManager(imgr) - - var stores []retrievalstoremgr.RetrievalStore - for i := 0; i < 5; i++ { - store, err := retrievalStoreMgr.NewStore() - require.NoError(t, err) - stores = append(stores, store) - nds := generateNodesOfSize(5, 100) - err = store.DAGService().AddMany(ctx, nds) - require.NoError(t, err) - } - - t.Run("creates all keys", func(t *testing.T) { - qres, err := ds.Query(query.Query{KeysOnly: true}) - require.NoError(t, err) - all, err := qres.Rest() - require.NoError(t, err) - require.Len(t, all, 31) - }) - - t.Run("loads DAG services", func(t *testing.T) { - for _, store := range stores { - mstore, err := multiDS.Get(*store.StoreID()) - require.NoError(t, err) - require.Equal(t, mstore.DAG, store.DAGService()) - } - }) - - t.Run("delete stores", func(t *testing.T) { - err := retrievalStoreMgr.ReleaseStore(stores[4]) - require.NoError(t, err) - storeIndexes := multiDS.List() - require.Len(t, storeIndexes, 4) - - qres, err := ds.Query(query.Query{KeysOnly: true}) - require.NoError(t, err) - all, err := qres.Rest() - require.NoError(t, err) - require.Len(t, all, 25) - }) -} - -func TestBlockstoreRetrievalStoreManager(t *testing.T) { - ctx := context.Background() - ds := dss.MutexWrap(datastore.NewMapDatastore()) - bs := blockstore.FromDatastore(ds) - retrievalStoreMgr := retrievalstoremgr.NewBlockstoreRetrievalStoreManager(bs) - var stores []retrievalstoremgr.RetrievalStore - var cids []cid.Cid - for i := 0; i < 5; i++ { - store, err := retrievalStoreMgr.NewStore() - require.NoError(t, err) - stores = append(stores, store) - nds := generateNodesOfSize(5, 100) - err = store.DAGService().AddMany(ctx, nds) - require.NoError(t, err) - for _, nd := range nds { - cids = append(cids, nd.Cid()) - } - } - - t.Run("creates all keys", func(t *testing.T) { - qres, err := ds.Query(query.Query{KeysOnly: true}) - require.NoError(t, err) - all, err := qres.Rest() - require.NoError(t, err) - require.Len(t, all, 25) - }) - - t.Run("loads DAG services, all DAG has all nodes", func(t *testing.T) { - for _, store := range stores { - dagService := store.DAGService() - for _, cid := range cids { - _, err := dagService.Get(ctx, cid) - require.NoError(t, err) - } - } - }) - - t.Run("release store has no effect", func(t *testing.T) { - err := retrievalStoreMgr.ReleaseStore(stores[4]) - require.NoError(t, err) - qres, err := ds.Query(query.Query{KeysOnly: true}) - require.NoError(t, err) - all, err := qres.Rest() - require.NoError(t, err) - require.Len(t, all, 25) - }) -} - -var seedSeq int64 = 0 - -func randomBytes(n int64) []byte { - randBytes := make([]byte, n) - r := rand.New(rand.NewSource(seedSeq)) - _, _ = r.Read(randBytes) - seedSeq++ - return randBytes -} - -func generateNodesOfSize(n int, size int64) []format.Node { - generatedNodes := make([]format.Node, 0, n) - for i := 0; i < n; i++ { - b := dag.NewRawNode(randomBytes(size)) - generatedNodes = append(generatedNodes, b) - - } - return generatedNodes -} diff --git a/paychmgr/cbor_gen.go b/paychmgr/cbor_gen.go index f25183db8..caa4143a2 100644 --- a/paychmgr/cbor_gen.go +++ b/paychmgr/cbor_gen.go @@ -5,6 +5,7 @@ package paychmgr import ( "fmt" "io" + "math" "sort" address "github.com/filecoin-project/go-address" @@ -16,6 +17,7 @@ import ( var _ = xerrors.Errorf var _ = cid.Undef +var _ = math.E var _ = sort.Sort func (t *VoucherInfo) MarshalCBOR(w io.Writer) error { diff --git a/scripts/lotus-chainwatch.service b/scripts/lotus-chainwatch.service deleted file mode 100644 index e121cb1d1..000000000 --- a/scripts/lotus-chainwatch.service +++ /dev/null @@ -1,15 +0,0 @@ -[Unit] -Description=Chainwatch -After=lotus-daemon.service -Requires=lotus-daemon.service - -[Service] -Environment=GOLOG_FILE="/var/log/lotus/chainwatch.log" -Environment=GOLOG_LOG_FMT="json" -Environment=LOTUS_DB="" -Environment=LOTUS_PATH="%h/.lotus" -EnvironmentFile=-/etc/lotus/chainwatch.env -ExecStart=/usr/local/bin/lotus-chainwatch run - -[Install] -WantedBy=multi-user.target diff --git a/storage/miner.go b/storage/miner.go index cdacc2734..c4bf41c12 100644 --- a/storage/miner.go +++ b/storage/miner.go @@ -27,7 +27,6 @@ import ( "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/events" "github.com/filecoin-project/lotus/chain/gen" "github.com/filecoin-project/lotus/chain/types" @@ -114,6 +113,7 @@ type fullNodeFilteredAPI interface { ChainGetRandomnessFromTickets(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) ChainGetRandomnessFromBeacon(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) + ChainGetTipSetAfterHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) ChainGetBlockMessages(context.Context, cid.Cid) (*api.BlockMessages, error) ChainGetMessage(ctx context.Context, mc cid.Cid) (*types.Message, error) ChainReadObj(context.Context, cid.Cid) ([]byte, error) @@ -179,19 +179,15 @@ func (m *Miner) Run(ctx context.Context) error { adaptedAPI = NewSealingAPIAdapter(m.api) // Instantiate a precommit policy. - defaultDuration = policy.GetMaxSectorExpirationExtension() - (md.WPoStProvingPeriod * 2) - provingBoundary = md.PeriodStart % md.WPoStProvingPeriod + cfg = sealing.GetSealingConfigFunc(m.getSealConfig) + provingBuffer = md.WPoStProvingPeriod * 2 - // TODO: Maybe we update this policy after actor upgrades? - pcp = sealing.NewBasicPreCommitPolicy(adaptedAPI, defaultDuration, provingBoundary) + pcp = sealing.NewBasicPreCommitPolicy(adaptedAPI, cfg, provingBuffer) // address selector. as = func(ctx context.Context, mi miner.MinerInfo, use api.AddrUse, goodFunds, minFunds abi.TokenAmount) (address.Address, abi.TokenAmount, error) { return m.addrSel.AddressFor(ctx, m.api, mi, use, goodFunds, minFunds) } - - // sealing configuration. - cfg = sealing.GetSealingConfigFunc(m.getSealConfig) ) // Instantiate the sealing FSM. diff --git a/storage/wdpost_run.go b/storage/wdpost_run.go index 20d1eeadc..82c7dede4 100644 --- a/storage/wdpost_run.go +++ b/storage/wdpost_run.go @@ -560,12 +560,12 @@ func (s *WindowPoStScheduler) runPoStCycle(ctx context.Context, di dline.Info, t Proofs: nil, } - skipCount := uint64(0) postSkipped := bitfield.New() somethingToProve := false // Retry until we run out of sectors to prove. for retries := 0; ; retries++ { + skipCount := uint64(0) var partitions []miner.PoStPartition var sinfos []proof2.SectorInfo for partIdx, partition := range batch { @@ -705,7 +705,6 @@ func (s *WindowPoStScheduler) runPoStCycle(ctx context.Context, di dline.Info, t return nil, ctx.Err() } - skipCount += uint64(len(ps)) for _, sector := range ps { postSkipped.Set(uint64(sector.Number)) } diff --git a/testplans/lotus-soup/go.mod b/testplans/lotus-soup/go.mod index 55da298db..713426aa0 100644 --- a/testplans/lotus-soup/go.mod +++ b/testplans/lotus-soup/go.mod @@ -8,25 +8,24 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/drand/drand v1.2.1 github.com/filecoin-project/go-address v0.0.5 - github.com/filecoin-project/go-data-transfer v1.6.0 - github.com/filecoin-project/go-fil-markets v1.5.0 + github.com/filecoin-project/go-data-transfer v1.7.0 + github.com/filecoin-project/go-fil-markets v1.6.0-rc1.0.20210715124641-2412ccd89114 github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48 github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b - github.com/filecoin-project/lotus v1.10.1-0.20210707122128-1fe08f5973f4 + github.com/filecoin-project/lotus v1.10.1-0.20210715125135-ed058ae1936d github.com/filecoin-project/specs-actors v0.9.14 github.com/google/uuid v1.1.2 github.com/gorilla/mux v1.7.4 github.com/hashicorp/go-multierror v1.1.0 github.com/influxdata/influxdb v1.8.3 // indirect - github.com/ipfs/go-cid v0.0.7 + github.com/ipfs/go-cid v0.0.8-0.20210702173502-41f2377d9672 github.com/ipfs/go-datastore v0.4.5 - github.com/ipfs/go-graphsync v0.6.2-0.20210428121800-88edb5462e17 // indirect github.com/ipfs/go-ipfs-files v0.0.8 github.com/ipfs/go-ipld-format v0.2.0 - github.com/ipfs/go-log/v2 v2.1.3 + github.com/ipfs/go-log/v2 v2.3.0 github.com/ipfs/go-merkledag v0.3.2 - github.com/ipfs/go-unixfs v0.2.4 + github.com/ipfs/go-unixfs v0.2.6 github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d github.com/kpacha/opencensus-influxdb v0.0.0-20181102202715-663e2683a27c github.com/libp2p/go-libp2p v0.14.2 diff --git a/testplans/lotus-soup/go.sum b/testplans/lotus-soup/go.sum index 9969c5182..8123b533a 100644 --- a/testplans/lotus-soup/go.sum +++ b/testplans/lotus-soup/go.sum @@ -25,6 +25,7 @@ contrib.go.opencensus.io/exporter/prometheus v0.1.0 h1:SByaIoWwNgMdPSgl5sMqM2KDE contrib.go.opencensus.io/exporter/prometheus v0.1.0/go.mod h1:cGFniUXGZlKRjzOyuZJ6mgB+PgBcCIa79kEKR8YCW+A= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= @@ -175,6 +176,7 @@ github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmf github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0 h1:kq/SbG2BCKLkDKkjQf5OWwKWUKj1lgs3lFI4PxnR5lg= @@ -263,6 +265,8 @@ github.com/fatih/color v1.8.0/go.mod h1:3l45GVGkyrnYNl9HoIjnp2NnNWvh6hLAqD8yTfGj github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fd/go-nat v1.0.0/go.mod h1:BTBu/CKvMmOMUPkKVef1pngt2WFH/lg7E6yQnulfp6E= +github.com/filecoin-project/dagstore v0.1.0 h1:lENA+8LlO2TtGBTP2MzZGF3kmjmzE9hB7hZ+bDGsnPY= +github.com/filecoin-project/dagstore v0.1.0/go.mod h1:cqqORk5fbkKVwwZkFk3D7XfeLpsTbWkX5Uj1GrsBOmM= github.com/filecoin-project/go-address v0.0.3/go.mod h1:jr8JxKsYx+lQlQZmF5i2U0Z+cGQ59wMIps/8YW/lDj8= github.com/filecoin-project/go-address v0.0.5 h1:SSaFT/5aLfPXycUlFyemoHYhRgdyXClXCyDdNJKPlDM= github.com/filecoin-project/go-address v0.0.5/go.mod h1:jr8JxKsYx+lQlQZmF5i2U0Z+cGQ59wMIps/8YW/lDj8= @@ -278,22 +282,22 @@ github.com/filecoin-project/go-bitfield v0.2.4 h1:uZ7MeE+XfM5lqrHJZ93OnhQKc/rveW github.com/filecoin-project/go-bitfield v0.2.4/go.mod h1:CNl9WG8hgR5mttCnUErjcQjGvuiZjRqK9rHVBsQF4oM= github.com/filecoin-project/go-cbor-util v0.0.0-20191219014500-08c40a1e63a2 h1:av5fw6wmm58FYMgJeoB/lK9XXrgdugYiTqkdxjTy9k8= github.com/filecoin-project/go-cbor-util v0.0.0-20191219014500-08c40a1e63a2/go.mod h1:pqTiPHobNkOVM5thSRsHYjyQfq7O5QSCMhvuu9JoDlg= -github.com/filecoin-project/go-commp-utils v0.0.0-20201119054358-b88f7a96a434/go.mod h1:6s95K91mCyHY51RPWECZieD3SGWTqIFLf1mPOes9l5U= +github.com/filecoin-project/go-commp-utils v0.1.0/go.mod h1:6s95K91mCyHY51RPWECZieD3SGWTqIFLf1mPOes9l5U= github.com/filecoin-project/go-commp-utils v0.1.1-0.20210427191551-70bf140d31c7 h1:U9Z+76pHCKBmtdxFV7JFZJj7OVm12I6dEKwtMVbq5p0= github.com/filecoin-project/go-commp-utils v0.1.1-0.20210427191551-70bf140d31c7/go.mod h1:6s95K91mCyHY51RPWECZieD3SGWTqIFLf1mPOes9l5U= github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03 h1:2pMXdBnCiXjfCYx/hLqFxccPoqsSveQFxVLvNxy9bus= github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03/go.mod h1:+viYnvGtUTgJRdy6oaeF4MTFKAfatX071MPDPBL11EQ= github.com/filecoin-project/go-data-transfer v1.0.1/go.mod h1:UxvfUAY9v3ub0a21BSK9u3pB2aq30Y0KMsG+w9/ysyo= -github.com/filecoin-project/go-data-transfer v1.6.0 h1:DHIzEc23ydRCCBwtFet3MfgO8gMpZEnw60Y+s71oX6o= -github.com/filecoin-project/go-data-transfer v1.6.0/go.mod h1:E3WW4mCEYwU2y65swPEajSZoFWFmfXt7uwGduoACZQc= +github.com/filecoin-project/go-data-transfer v1.7.0 h1:mFRn+UuTdPROmhplLSekzd4rAs9ug8ubtSY4nw9wYkU= +github.com/filecoin-project/go-data-transfer v1.7.0/go.mod h1:GLRr5BmLEqsLwXfiRDG7uJvph22KGL2M4iOuF8EINaU= github.com/filecoin-project/go-ds-versioning v0.1.0 h1:y/X6UksYTsK8TLCI7rttCKEvl8btmWxyFMEeeWGUxIQ= github.com/filecoin-project/go-ds-versioning v0.1.0/go.mod h1:mp16rb4i2QPmxBnmanUx8i/XANp+PFCCJWiAb+VW4/s= github.com/filecoin-project/go-fil-commcid v0.0.0-20200716160307-8f644712406f/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= github.com/filecoin-project/go-fil-commcid v0.0.0-20201016201715-d41df56b4f6a h1:hyJ+pUm/4U4RdEZBlg6k8Ma4rDiuvqyGpoICXAxwsTg= github.com/filecoin-project/go-fil-commcid v0.0.0-20201016201715-d41df56b4f6a/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= github.com/filecoin-project/go-fil-markets v1.0.5-0.20201113164554-c5eba40d5335/go.mod h1:AJySOJC00JRWEZzRG2KsfUnqEf5ITXxeX09BE9N4f9c= -github.com/filecoin-project/go-fil-markets v1.5.0 h1:3KEs01L8XFCEgujZ6ggFjr1XWjpjTQcmSSeo3I99I0k= -github.com/filecoin-project/go-fil-markets v1.5.0/go.mod h1:7be6zzFwaN8kxVeYZf/UUj/JilHC0ogPvWqE1TW8Ptk= +github.com/filecoin-project/go-fil-markets v1.6.0-rc1.0.20210715124641-2412ccd89114 h1:uEJghQAwCTCPpR/aQLGvnqahWPDOLYL4jnYsdeItsKc= +github.com/filecoin-project/go-fil-markets v1.6.0-rc1.0.20210715124641-2412ccd89114/go.mod h1:iegdHk34YkHHpgGVB/dKij1emfhoTb2lat80WMWw3Ag= github.com/filecoin-project/go-hamt-ipld v0.1.5 h1:uoXrKbCQZ49OHpsTCkrThPNelC4W3LPEk0OrS/ytIBM= github.com/filecoin-project/go-hamt-ipld v0.1.5/go.mod h1:6Is+ONR5Cd5R6XZoCse1CWaXZc0Hdb/JeX+EQCQzX24= github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0 h1:b3UDemBYN2HNfk3KOXNuxgTTxlWi3xVvbQP0IT38fvM= @@ -323,15 +327,14 @@ github.com/filecoin-project/go-statestore v0.1.1 h1:ufMFq00VqnT2CAuDpcGnwLnCX1I/ github.com/filecoin-project/go-statestore v0.1.1/go.mod h1:LFc9hD+fRxPqiHiaqUEZOinUJB4WARkRfNl10O7kTnI= github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b h1:fkRZSPrYpk42PV3/lIXiL0LHetxde7vyYYvSsttQtfg= github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/go.mod h1:Q0GQOBtKf1oE10eSXSlhN45kDBdGvEcVOqMiffqX+N8= -github.com/filecoin-project/lotus v1.10.1-0.20210707122128-1fe08f5973f4 h1:u5/uky+PdeaGuEGsExtVP8UUB8No/e873xjqcb7h3CM= -github.com/filecoin-project/lotus v1.10.1-0.20210707122128-1fe08f5973f4/go.mod h1:8ooe5Rzw80rJL0br81A8NNiwZ4BUVzPRwAnDxUG4E7g= +github.com/filecoin-project/lotus v1.10.1-0.20210715125135-ed058ae1936d h1:hGVeAKlfdyk6cjiU/vK8pl9+Oj8OKM4PLt3j6cAGMvg= +github.com/filecoin-project/lotus v1.10.1-0.20210715125135-ed058ae1936d/go.mod h1:ZU8NxaMnfUp5uPL+8sYwBsL2qQX6ZxeIEzVz76soAPE= github.com/filecoin-project/specs-actors v0.9.4/go.mod h1:BStZQzx5x7TmCkLv0Bpa07U6cPKol6fd3w9KjMPZ6Z4= github.com/filecoin-project/specs-actors v0.9.12/go.mod h1:TS1AW/7LbG+615j4NsjMK1qlpAwaFsG9w0V2tg2gSao= github.com/filecoin-project/specs-actors v0.9.13/go.mod h1:TS1AW/7LbG+615j4NsjMK1qlpAwaFsG9w0V2tg2gSao= github.com/filecoin-project/specs-actors v0.9.14 h1:68PVstg2UB3ZsMLF+DKFTAs/YKsqhKWynkr0IqmVRQY= github.com/filecoin-project/specs-actors v0.9.14/go.mod h1:TS1AW/7LbG+615j4NsjMK1qlpAwaFsG9w0V2tg2gSao= github.com/filecoin-project/specs-actors/v2 v2.0.1/go.mod h1:v2NZVYinNIKA9acEMBm5wWXxqv5+frFEbekBFemYghY= -github.com/filecoin-project/specs-actors/v2 v2.3.2/go.mod h1:UuJQLoTx/HPvvWeqlIFmC/ywlOLHNe8SNQ3OunFbu2Y= github.com/filecoin-project/specs-actors/v2 v2.3.5-0.20210114162132-5b58b773f4fb/go.mod h1:LljnY2Mn2homxZsmokJZCpRuhOPxfXhvcek5gWkmqAc= github.com/filecoin-project/specs-actors/v2 v2.3.5 h1:PbT4tPlSXZ8sRgajhb4D8AOEmiaaZ+jg6tc6BBv8VQc= github.com/filecoin-project/specs-actors/v2 v2.3.5/go.mod h1:LljnY2Mn2homxZsmokJZCpRuhOPxfXhvcek5gWkmqAc= @@ -384,6 +387,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= @@ -468,8 +472,9 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -608,8 +613,9 @@ github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-cid v0.0.6-0.20200501230655-7c82f3b81c00/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= -github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/ipfs/go-cid v0.0.8-0.20210702173502-41f2377d9672 h1:PabVicIEIt7qUwx5gu80wZsALHUZ4Zux37M+x0n/Erk= +github.com/ipfs/go-cid v0.0.8-0.20210702173502-41f2377d9672/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= github.com/ipfs/go-cidutil v0.0.2 h1:CNOboQf1t7Qp0nuNh8QMmhJs0+Q//bRL1axtCnIB1Yo= github.com/ipfs/go-cidutil v0.0.2/go.mod h1:ewllrvrxG6AMYStla3GD7Cqn+XYSLqjK0vc+086tB6s= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= @@ -650,9 +656,8 @@ github.com/ipfs/go-fs-lock v0.0.6/go.mod h1:OTR+Rj9sHiRubJh3dRhD15Juhd/+w6VPOY28 github.com/ipfs/go-graphsync v0.1.0/go.mod h1:jMXfqIEDFukLPZHqDPp8tJMbHO9Rmeb9CEGevngQbmE= github.com/ipfs/go-graphsync v0.4.2/go.mod h1:/VmbZTUdUMTbNkgzAiCEucIIAU3BkLE2cZrDCVUhyi0= github.com/ipfs/go-graphsync v0.4.3/go.mod h1:mPOwDYv128gf8gxPFgXnz4fNrSYPsWyqisJ7ych+XDY= -github.com/ipfs/go-graphsync v0.6.1/go.mod h1:e2ZxnClqBBYAtd901g9vXMJzS47labjAtOzsWtOzKNk= -github.com/ipfs/go-graphsync v0.6.2-0.20210428121800-88edb5462e17 h1:rOoF88dVuDGbIx7idSdimN7JvXriyOIT96WD3eX9sHA= -github.com/ipfs/go-graphsync v0.6.2-0.20210428121800-88edb5462e17/go.mod h1:5WyaeigpNdpiYQuW2vwpuecOoEfB4h747ZGEOKmAGTg= +github.com/ipfs/go-graphsync v0.6.4 h1:g6wFRK2BkLPnx8nfoSdnokp5gtpuGyWZjbqI6q3NGb8= +github.com/ipfs/go-graphsync v0.6.4/go.mod h1:5WyaeigpNdpiYQuW2vwpuecOoEfB4h747ZGEOKmAGTg= github.com/ipfs/go-hamt-ipld v0.1.1/go.mod h1:1EZCr2v0jlCnhpa+aZ0JZYp8Tt2w16+JJOAVz17YcDk= github.com/ipfs/go-ipfs-blockstore v0.0.1/go.mod h1:d3WClOmRQKFnJ0Jz/jj/zmksX0ma1gROTlovZKBmN08= github.com/ipfs/go-ipfs-blockstore v0.1.0/go.mod h1:5aD0AvHPi7mZc6Ci1WCAhiBQu2IsfTduLl+422H6Rqw= @@ -718,8 +723,9 @@ github.com/ipfs/go-log v1.0.0/go.mod h1:JO7RzlMK6rA+CIxFMLOuB6Wf5b81GDiKElL7UPSI github.com/ipfs/go-log v1.0.1/go.mod h1:HuWlQttfN6FWNHRhlY5yMk/lW7evQC0HHGOxEwMRR8I= github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= -github.com/ipfs/go-log v1.0.4 h1:6nLQdX4W8P9yZZFH7mO+X/PzjN8Laozm/lMJ6esdgzY= github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= +github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= +github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= github.com/ipfs/go-log/v2 v2.0.1/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= @@ -728,8 +734,9 @@ github.com/ipfs/go-log/v2 v2.0.8/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscw github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-log/v2 v2.1.2-0.20200626104915-0016c0b4b3e4/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-log/v2 v2.1.2/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= -github.com/ipfs/go-log/v2 v2.1.3 h1:1iS3IU7aXRlbgUpN8yTTpJ53NXYjAe37vcI5+5nYrzk= github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= +github.com/ipfs/go-log/v2 v2.3.0 h1:31Re/cPqFHpsRHgyVwjWADPoF0otB1WrjTy8ZFYwEZU= +github.com/ipfs/go-log/v2 v2.3.0/go.mod h1:QqGoj30OTpnKaG/LKTGTxoP2mmQtjVMEnK72gynbe/g= github.com/ipfs/go-merkledag v0.0.3/go.mod h1:Oc5kIXLHokkE1hWGMBHw+oxehkAaTOqtEb7Zbh6BhLA= github.com/ipfs/go-merkledag v0.0.6/go.mod h1:QYPdnlvkOg7GnQRofu9XZimC5ZW5Wi3bKys/4GQQfto= github.com/ipfs/go-merkledag v0.2.3/go.mod h1:SQiXrtSts3KGNmgOzMICy5c0POOpUNQLvB3ClKnBAlk= @@ -752,8 +759,9 @@ github.com/ipfs/go-todocounter v0.0.1/go.mod h1:l5aErvQc8qKE2r7NDMjmq5UNAvuZy0rC github.com/ipfs/go-unixfs v0.0.4/go.mod h1:eIo/p9ADu/MFOuyxzwU+Th8D6xoxU//r590vUpWyfz8= github.com/ipfs/go-unixfs v0.2.1/go.mod h1:IwAAgul1UQIcNZzKPYZWOCijryFBeCV79cNubPzol+k= github.com/ipfs/go-unixfs v0.2.2-0.20190827150610-868af2e9e5cb/go.mod h1:IwAAgul1UQIcNZzKPYZWOCijryFBeCV79cNubPzol+k= -github.com/ipfs/go-unixfs v0.2.4 h1:6NwppOXefWIyysZ4LR/qUBPvXd5//8J3jiMdvpbw6Lo= github.com/ipfs/go-unixfs v0.2.4/go.mod h1:SUdisfUjNoSDzzhGVxvCL9QO/nKdwXdr+gbMUdqcbYw= +github.com/ipfs/go-unixfs v0.2.6 h1:gq3U3T2vh8x6tXhfo3uSO3n+2z4yW0tYtNgVP/3sIyA= +github.com/ipfs/go-unixfs v0.2.6/go.mod h1:GTTzQvaZsTZARdNkkdjDKFFnBhmO3e5mIM1PkH/x4p0= github.com/ipfs/go-verifcid v0.0.1 h1:m2HI7zIuR5TFyQ1b79Da5N9dnnCP1vcu2QqawmWlK2E= github.com/ipfs/go-verifcid v0.0.1/go.mod h1:5Hrva5KBeIog4A+UpqlaIU+DEstipcJYQQZc0g37pY0= github.com/ipfs/interface-go-ipfs-core v0.2.3 h1:E6uQ+1fJjkxJWlL9lAE72a5FWeyeeNL3GitLy8+jq3Y= @@ -766,6 +774,9 @@ github.com/ipld/go-car v0.1.0/go.mod h1:RCWzaUh2i4mOEkB3W45Vc+9jnS/M6Qay5ooytiBH github.com/ipld/go-car v0.1.1-0.20200923150018-8cdef32e2da4/go.mod h1:xrMEcuSq+D1vEwl+YAXsg/JfA98XGpXDwnkIL4Aimqw= github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d h1:iphSzTuPqyDgH7WUVZsdqUnQNzYgIblsVr1zhVNA33U= github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d/go.mod h1:2Gys8L8MJ6zkh1gktTSXreY63t4UbyvNp5JaudTyxHQ= +github.com/ipld/go-car/v2 v2.0.0-20210708104948-d79de78d9567/go.mod h1:Ueq4zx/SNx7yHwmfr9xKlKpXxRCMM6wyqC8B0rv9oig= +github.com/ipld/go-car/v2 v2.0.0-20210715123707-a315bb047f6b h1:jr7cFCEeu+rDhkivLTI5BX1JrPNLvzYtsOpIHAcfdR8= +github.com/ipld/go-car/v2 v2.0.0-20210715123707-a315bb047f6b/go.mod h1:0nAH3QhJOua+Dz6SkD6zOYtoZMNCJSDHY4IrbYe3AQs= github.com/ipld/go-ipld-prime v0.0.2-0.20191108012745-28a82f04c785/go.mod h1:bDDSvVz7vaK12FNvMeRYnpRFkSUPNQOiCYQezMD/P3w= github.com/ipld/go-ipld-prime v0.0.2-0.20200428162820-8b59dc292b8e/go.mod h1:uVIwe/u0H4VdKv3kaN1ck7uCb6yD9cFLS9/ELyXbsw8= github.com/ipld/go-ipld-prime v0.5.1-0.20200828233916-988837377a7f/go.mod h1:0xEgdD6MKbZ1vF0GC+YcR/C4SQCAlRuOjIJ2i0HxqzM= @@ -842,7 +853,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5 h1:2U0HzY8BJ8hVwDKIzp7y4voR9CX/nvcfymLmg2UiOio= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.0.4 h1:g0I61F2K2DjRHz1cnxlkNSBIaePVoJIjjnHui8QHbiw= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -1017,6 +1031,7 @@ github.com/libp2p/go-libp2p-netutil v0.0.1/go.mod h1:GdusFvujWZI9Vt0X5BKqwWWmZFx github.com/libp2p/go-libp2p-netutil v0.1.0 h1:zscYDNVEcGxyUpMd0JReUZTrpMfia8PmLKcKF72EAMQ= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= github.com/libp2p/go-libp2p-noise v0.1.1/go.mod h1:QDFLdKX7nluB7DEnlVPbz7xlLHdwHFA9HiohJRr3vwM= +github.com/libp2p/go-libp2p-noise v0.1.2/go.mod h1:9B10b7ueo7TIxZHHcjcDCo5Hd6kfKT2m77by82SFRfE= github.com/libp2p/go-libp2p-noise v0.2.0 h1:wmk5nhB9a2w2RxMOyvsoKjizgJOEaJdfAakr0jN8gds= github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= github.com/libp2p/go-libp2p-peer v0.0.1/go.mod h1:nXQvOBbwVqoP+T5Y5nCjeH4sP9IX/J0AMzcDUVruVoo= @@ -1111,6 +1126,7 @@ github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2EzI2h7HbFm9eAKI4= github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= +github.com/libp2p/go-libp2p-yamux v0.4.1/go.mod h1:FA/NjRYRVNjqOzpGuGqcruH7jAU2mYIjtKBicVOL3dc= github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= github.com/libp2p/go-libp2p-yamux v0.5.4 h1:/UOPtT/6DHPtr3TtKXBHa6g0Le0szYuI33Xc/Xpd7fQ= github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= @@ -1227,15 +1243,16 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -1267,8 +1284,9 @@ github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+ github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= -github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -1327,6 +1345,8 @@ github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/g github.com/multiformats/go-multibase v0.0.2/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multicodec v0.2.1-0.20210713081508-b421db6850ae h1:wfljHPpiR0UDOjeqld9ds0Zxl3Nt/j+0wnvyBc01JgY= +github.com/multiformats/go-multicodec v0.2.1-0.20210713081508-b421db6850ae/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.7/go.mod h1:XuKXPp8VHcTygube3OWZC+aZrA+H1IhmjoCDtJc7PXM= @@ -1334,8 +1354,9 @@ github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa github.com/multiformats/go-multihash v0.0.9/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= -github.com/multiformats/go-multihash v0.0.14 h1:QoBceQYQQtNUuf6s7wHxnE2c8bhbMqhfGzNI032se/I= github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-multihash v0.0.15 h1:hWOPdrNqDjwHDx82vsYGSDZNyktOJJ2dzZJzFkOV1jM= +github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= github.com/multiformats/go-multistream v0.0.1/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= github.com/multiformats/go-multistream v0.0.4/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= @@ -1360,6 +1381,7 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nikkolasg/hexjson v0.0.0-20181101101858-78e39397e00c h1:5bFTChQxSKNwy8ALwOebjekYExl9HTT9urdawqC95tA= github.com/nikkolasg/hexjson v0.0.0-20181101101858-78e39397e00c/go.mod h1:7qN3Y0BvzRUf4LofcoJplQL10lsFDb4PYlePTVwrP28= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229 h1:E2B8qYyeSgv5MXpmzZXRNp8IAQ4vjxIjhpAf5hv/tAg= @@ -1415,6 +1437,8 @@ github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChl github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk= +github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9/go.mod h1:x3N5drFsm2uilKKuuYo6LdyD8vZAW55sH/9w+pbo1sw= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= @@ -1490,6 +1514,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= @@ -1534,8 +1560,9 @@ github.com/siebenmann/go-kstat v0.0.0-20160321171754-d34789b79745/go.mod h1:G81a github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= @@ -1572,7 +1599,6 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -1628,6 +1654,8 @@ github.com/weaveworks/promrus v1.2.0/go.mod h1:SaE82+OJ91yqjrE1rsvBWVzNZKcHYFtMU github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc/go.mod h1:r45hJU7yEoA81k6MWNhpMj/kms0n14dkzkxYHoB96UM= github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba h1:X4n8JG2e2biEZZXdBKt9HX7DN3bYGFUqljqqy0DqgnY= github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba/go.mod h1:CHQnYnQUEPydYCwuy8lmTHfGmdw9TKrhWV0xLx8l0oM= +github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 h1:5HZfQkwe0mIfyDmc1Em5GqlNRzcdtlv4HTNmdpt7XH0= +github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11/go.mod h1:Wlo/SzPmxVp6vXpGt/zaXhHH0fn4IxgqZc82aKg6bpQ= github.com/whyrusleeping/cbor-gen v0.0.0-20191216205031-b047b6acb3c0/go.mod h1:xdlJQaiqipF0HW+Mzpg7XRM3fWbGvfgFlcppuvlkIvY= github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= github.com/whyrusleeping/cbor-gen v0.0.0-20200402171437-3d27c146c105/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= @@ -1708,8 +1736,14 @@ go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -1774,8 +1808,10 @@ golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1783,13 +1819,15 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200513190911-00229845015e h1:rMqLP+9XLy+LdbCXHjJHAmTfXCr93W7oruWA6Hq1Alc= golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20210615023648-acb5c1269671 h1:ddvpKwqE7dm58PoWjRCmaCiA3DANEW0zWGfNYQD212Y= +golang.org/x/exp v0.0.0-20210615023648-acb5c1269671/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1807,13 +1845,16 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1881,6 +1922,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180202135801-37707fdb30a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1953,10 +1995,14 @@ golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200926100807-9d91bd62050c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426080607-c94f62235c83 h1:kHSDPqCtsHZOg0nVylfTo20DDhE9gG4Y0jn7hKQ0QAM= golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2002,12 +2048,12 @@ golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -2016,8 +2062,9 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200711155855-7342f9734a7d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200827010519-17fd2f27a9e3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201112185108-eeaa07dd7696/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a h1:CB3a9Nez8M13wwlr/E2YtwoU+qYHKfC+JrDa45RXXoQ= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2110,6 +2157,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= @@ -2145,8 +2193,8 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= modernc.org/cc v1.0.0 h1:nPibNuDEx6tvYrUAtvDTTw98rx5juGsa5zuDnKwEEQQ= diff --git a/tools/packer/repo/config.toml b/tools/packer/repo/config.toml index b8cbf0ec3..4a4fb510e 100644 --- a/tools/packer/repo/config.toml +++ b/tools/packer/repo/config.toml @@ -13,7 +13,7 @@ ListenAddresses = ["/ip4/0.0.0.0/tcp/5678", "/ip6/::/tcp/5678"] # [Pubsub] # Bootstrapper = false -# RemoteTracer = "/dns4/pubsub-tracer.filecoin.io/tcp/4001/p2p/QmTd6UvR47vUidRNZ1ZKXHrAFhqTJAD27rKL9XYghEKgKX" +# RemoteTracer = "" # [Client] # UseIpfs = false