From 3b7f71f533ca2b7824f09b1e99b846a6baa18182 Mon Sep 17 00:00:00 2001 From: "jonathan@vulcanize.io" Date: Fri, 21 Jun 2024 18:48:22 +0000 Subject: [PATCH] upgrading to upstream v1.27.1-rc1 --- .github/actions/install-go/action.yml | 7 + .github/actions/start-yugabytedb/action.yml | 2 +- .github/workflows/build.yml | 3 +- .github/workflows/builtin-actor-tests.yml | 3 +- .github/workflows/check.yml | 6 +- .github/workflows/docker.yml | 36 +- .github/workflows/release.yml | 62 +- .github/workflows/sorted-pr-checks.yml | 6 +- .github/workflows/stale.yml | 3 +- .github/workflows/sync-master-main.yaml | 3 +- .github/workflows/test.yml | 12 +- .gitignore | 1 + CHANGELOG.md | 142 +- Dockerfile | 4 - Makefile | 85 +- api/api_curio.go | 34 - api/api_full.go | 227 +- api/api_gateway.go | 1 + api/api_storage.go | 136 +- api/client/client.go | 10 - api/docgen-openrpc/openrpc.go | 2 +- api/docgen/docgen.go | 35 +- api/eth_aliases.go | 1 + api/miner_subsystems.go | 5 - api/mocks/mock_full.go | 430 +-- api/proxy_gen.go | 1161 +----- api/types.go | 126 +- api/v0api/full.go | 109 - api/v0api/proxy_gen.go | 357 -- api/v0api/v0mocks/mock_full.go | 404 -- api/v0api/v1_wrapper.go | 153 - api/v1api/latest.go | 2 - api/version.go | 2 - blockstore/splitstore/splitstore_prune.go | 2 +- build/actors/v14.tar.zst | Bin 0 -> 1646079 bytes build/builtin_actors.go | 12 +- build/builtin_actors_gen.go | 175 +- build/builtin_actors_gen_test.go | 6 +- build/openrpc/full.json | 3344 ++--------------- build/openrpc/gateway.json | 337 +- build/openrpc/miner.json | 3028 +-------------- build/openrpc/worker.json | 76 +- build/panic_reporter.go | 28 +- build/params_2k.go | 9 +- build/params_butterfly.go | 6 +- build/params_calibnet.go | 3 + build/params_interop.go | 3 +- build/params_mainnet.go | 13 +- build/params_shared_vals.go | 2 +- build/params_testground.go | 1 + build/version.go | 23 +- chain/actors/builtin/account/account.go | 11 +- chain/actors/builtin/account/v14.go | 62 + chain/actors/builtin/cron/cron.go | 13 +- chain/actors/builtin/cron/v14.go | 57 + chain/actors/builtin/datacap/datacap.go | 13 +- chain/actors/builtin/datacap/v14.go | 82 + chain/actors/builtin/evm/evm.go | 10 +- chain/actors/builtin/evm/v14.go | 72 + chain/actors/builtin/init/init.go | 13 +- chain/actors/builtin/init/v14.go | 147 + chain/actors/builtin/market/actor.go.template | 5 + chain/actors/builtin/market/market.go | 15 + chain/actors/builtin/market/state.go.template | 8 + chain/actors/builtin/market/v0.go | 6 + chain/actors/builtin/market/v10.go | 6 + chain/actors/builtin/market/v11.go | 6 + chain/actors/builtin/market/v12.go | 6 + chain/actors/builtin/market/v13.go | 6 + chain/actors/builtin/market/v14.go | 410 ++ chain/actors/builtin/market/v2.go | 6 + chain/actors/builtin/market/v3.go | 6 + chain/actors/builtin/market/v4.go | 6 + chain/actors/builtin/market/v5.go | 6 + chain/actors/builtin/market/v6.go | 6 + chain/actors/builtin/market/v7.go | 6 + chain/actors/builtin/market/v8.go | 6 + chain/actors/builtin/market/v9.go | 6 + chain/actors/builtin/miner/miner.go | 7 + chain/actors/builtin/miner/v14.go | 595 +++ chain/actors/builtin/multisig/message10.go | 4 +- chain/actors/builtin/multisig/message11.go | 4 +- chain/actors/builtin/multisig/message12.go | 4 +- chain/actors/builtin/multisig/message13.go | 4 +- chain/actors/builtin/multisig/message14.go | 77 + chain/actors/builtin/multisig/message8.go | 4 +- chain/actors/builtin/multisig/message9.go | 4 +- chain/actors/builtin/multisig/multisig.go | 24 +- chain/actors/builtin/multisig/v14.go | 138 + chain/actors/builtin/paych/message14.go | 109 + chain/actors/builtin/paych/paych.go | 7 + chain/actors/builtin/paych/v14.go | 135 + chain/actors/builtin/power/power.go | 13 +- chain/actors/builtin/power/v14.go | 207 + chain/actors/builtin/registry.go | 120 + chain/actors/builtin/reward/reward.go | 13 +- chain/actors/builtin/reward/v14.go | 120 + chain/actors/builtin/system/system.go | 11 +- chain/actors/builtin/system/v14.go | 72 + chain/actors/builtin/verifreg/v14.go | 196 + chain/actors/builtin/verifreg/verifreg.go | 13 +- chain/actors/policy/policy.go | 73 +- chain/actors/version.go | 5 +- chain/beacon/drand/drand.go | 2 +- chain/consensus/common.go | 17 + chain/consensus/compute_state.go | 1 + chain/consensus/filcns/upgrades.go | 114 + chain/consensus/signatures.go | 36 +- chain/events/filter/event.go | 10 +- chain/events/filter/index.go | 60 +- chain/market/fundmanager_test.go | 2 +- chain/messagepool/messagepool.go | 5 + chain/messagesigner/messagesigner.go | 2 +- chain/state/statetree.go | 2 +- chain/stmgr/supply.go | 35 +- chain/sub/incoming.go | 5 +- chain/types/ethtypes/eth_1559_transactions.go | 342 ++ .../ethtypes/eth_1559_transactions_test.go | 241 ++ .../ethtypes/eth_legacy_155_transactions.go | 303 ++ .../eth_legacy_155_transactions_test.go | 188 + .../eth_legacy_homestead_transactions.go | 228 ++ .../eth_legacy_homestead_transactions_test.go | 299 ++ chain/types/ethtypes/eth_transactions.go | 891 ++--- chain/types/ethtypes/eth_transactions_test.go | 358 +- chain/types/ethtypes/eth_types.go | 8 + chain/types/ethtypes/rlp_test.go | 2 +- chain/types/fil.go | 2 +- cli/clicommands/cmd.go | 1 - cli/client.go | 2468 ------------ cli/client_retr.go | 550 --- cli/cmd.go | 1 - cli/helper.go | 6 +- cli/info.go | 68 +- cli/multisig.go | 69 +- cli/net.go | 31 +- cli/paych.go | 61 +- cli/send.go | 2 +- cli/sending_ui.go | 12 +- cli/spcli/actor.go | 15 +- cli/spcli/sectors.go | 17 +- cli/util/api.go | 21 - cli/util/retrieval.go | 77 - cmd/curio/cli.go | 249 -- cmd/curio/config.go | 440 --- cmd/curio/config_new.go | 57 - cmd/curio/config_test.go | 438 --- cmd/curio/deps/apiinfo.go | 94 - cmd/curio/deps/deps.go | 537 --- cmd/curio/guidedsetup/guidedsetup.go | 881 ----- cmd/curio/guidedsetup/shared.go | 258 -- cmd/curio/internal/translations/catalog.go | 476 --- .../internal/translations/knowns/main.go | 82 - .../translations/locales/en/out.gotext.json | 1636 -------- .../locales/ko/messages.gotext.json | 1106 ------ .../translations/locales/ko/out.gotext.json | 4 - .../locales/zh/messages.gotext.json | 1076 ------ .../translations/locales/zh/out.gotext.json | 4 - .../internal/translations/translations.go | 27 - cmd/curio/internal/translations/updateLang.sh | 8 - cmd/curio/log.go | 105 - cmd/curio/main.go | 189 - cmd/curio/market.go | 70 - cmd/curio/migrate.go | 1 - cmd/curio/pipeline.go | 135 - cmd/curio/proving.go | 204 - cmd/curio/rpc/rpc.go | 325 -- cmd/curio/run.go | 196 - cmd/curio/stop.go | 30 - cmd/curio/storage.go | 499 --- cmd/curio/tasks/tasks.go | 240 -- cmd/lotus-bench/cli.go | 8 +- cmd/lotus-bench/main.go | 2 +- cmd/lotus-bench/reporter.go | 26 +- cmd/lotus-bench/rpc.go | 10 +- cmd/lotus-fountain/main.go | 2 +- cmd/lotus-gateway/main.go | 2 +- cmd/lotus-health/main.go | 2 +- cmd/lotus-miner/allinfo_test.go | 7 - cmd/lotus-miner/dagstore.go | 282 -- cmd/lotus-miner/index_provider.go | 62 - cmd/lotus-miner/info_all.go | 71 - cmd/lotus-miner/init.go | 1 - cmd/lotus-miner/init_service.go | 159 - cmd/lotus-miner/main.go | 15 +- cmd/lotus-miner/market.go | 1039 ----- cmd/lotus-miner/pieces.go | 193 - cmd/lotus-miner/retrieval-deals.go | 231 -- cmd/lotus-miner/run.go | 29 +- cmd/lotus-miner/sectors.go | 2 +- cmd/lotus-pcr/main.go | 2 +- cmd/lotus-seed/main.go | 2 +- cmd/lotus-shed/actor.go | 20 +- cmd/lotus-shed/deal.go | 284 -- cmd/lotus-shed/eth.go | 6 +- cmd/lotus-shed/indexes.go | 104 +- cmd/lotus-shed/main.go | 4 +- cmd/lotus-shed/miner-multisig.go | 36 +- cmd/lotus-shed/miner.go | 9 +- cmd/lotus-shed/misc.go | 40 - cmd/lotus-sim/info.go | 22 +- cmd/lotus-sim/info_capacity.go | 2 +- cmd/lotus-sim/info_state.go | 2 +- cmd/lotus-sim/info_wdpost.go | 2 +- cmd/lotus-sim/list.go | 2 +- cmd/lotus-sim/profile.go | 4 +- cmd/lotus-sim/run.go | 10 +- cmd/lotus-sim/upgrade.go | 4 +- cmd/lotus-sim/util.go | 5 +- cmd/lotus-stats/main.go | 2 +- cmd/lotus-wallet/main.go | 2 +- cmd/lotus-worker/main.go | 4 +- cmd/lotus/daemon.go | 2 +- cmd/lotus/main.go | 4 +- cmd/sptool/actor.go | 141 - cmd/sptool/main.go | 84 - cmd/sptool/proving.go | 18 - cmd/sptool/sector.go | 355 -- curiosrc/address.go | 64 - curiosrc/builder.go | 46 - curiosrc/chainsched/chain_sched.go | 136 - curiosrc/ffi/piece_funcs.go | 76 - curiosrc/ffi/sdr_funcs.go | 627 ---- curiosrc/ffi/task_storage.go | 231 -- curiosrc/gc/storage_endpoint_gc.go | 287 -- curiosrc/market/deal_ingest.go | 137 - curiosrc/market/fakelm/iface.go | 33 - curiosrc/market/fakelm/lmimpl.go | 348 -- curiosrc/market/lmrpc/lmrpc.go | 499 --- curiosrc/message/sender.go | 396 -- curiosrc/message/watch.go | 214 -- curiosrc/multictladdr/multiaddresses.go | 81 - curiosrc/piece/task_cleanup_piece.go | 130 - curiosrc/piece/task_park_piece.go | 221 -- curiosrc/proof/treed_build.go | 292 -- curiosrc/proof/treed_build_test.go | 516 --- curiosrc/seal/README.md | 28 - curiosrc/seal/finalize_pieces.go | 51 - curiosrc/seal/poller.go | 285 -- curiosrc/seal/poller_commit_msg.go | 108 - curiosrc/seal/poller_precommit_msg.go | 119 - curiosrc/seal/sector_num_alloc.go | 127 - curiosrc/seal/task_finalize.go | 156 - curiosrc/seal/task_movestorage.go | 176 - curiosrc/seal/task_porep.go | 216 -- curiosrc/seal/task_sdr.go | 239 -- curiosrc/seal/task_submit_commit.go | 178 - curiosrc/seal/task_submit_precommit.go | 300 -- curiosrc/seal/task_trees.go | 326 -- curiosrc/seal/task_trees_test.go | 74 - curiosrc/web/api/apihelper/apihelper.go | 19 - curiosrc/web/api/config/config.go | 179 - curiosrc/web/api/debug/debug.go | 229 -- curiosrc/web/api/routes.go | 17 - curiosrc/web/api/sector/sector.go | 378 -- curiosrc/web/hapi/robust_rpc.go | 102 - curiosrc/web/hapi/routes.go | 58 - curiosrc/web/hapi/simpleinfo.go | 857 ----- .../web/hapi/simpleinfo_pipeline_porep.go | 195 - curiosrc/web/hapi/watch_actor.go | 286 -- curiosrc/web/hapi/web/actor_summary.gohtml | 30 - curiosrc/web/hapi/web/chain_rpcs.gohtml | 15 - curiosrc/web/hapi/web/cluster_machines.gohtml | 12 - .../web/hapi/web/cluster_task_history.gohtml | 19 - curiosrc/web/hapi/web/cluster_tasks.gohtml | 10 - curiosrc/web/hapi/web/node_info.gohtml | 100 - .../hapi/web/pipeline_porep_sectors.gohtml | 200 - curiosrc/web/hapi/web/pipline_porep.gohtml | 15 - curiosrc/web/hapi/web/root.gohtml | 26 - curiosrc/web/hapi/web/sector_info.gohtml | 57 - curiosrc/web/srv.go | 82 - curiosrc/web/static/chain-connectivity.mjs | 59 - curiosrc/web/static/config/edit.html | 160 - curiosrc/web/static/config/index.html | 97 - curiosrc/web/static/favicon.svg | 1 - curiosrc/web/static/index.html | 201 - curiosrc/web/static/pipeline_porep.html | 98 - curiosrc/web/static/sector/index.html | 129 - curiosrc/web/static/ux/curio-ux.mjs | 100 - curiosrc/web/static/ux/main.css | 53 - curiosrc/window/compute_do.go | 442 --- curiosrc/window/compute_task.go | 443 --- curiosrc/window/faults_simple.go | 152 - curiosrc/window/recover_task.go | 324 -- curiosrc/window/submit_task.go | 307 -- curiosrc/winning/winning_task.go | 688 ---- documentation/en/api-v0-methods-miner.md | 1121 +----- documentation/en/api-v0-methods.md | 1090 +----- documentation/en/api-v1-unstable-methods.md | 1126 +----- documentation/en/cli-curio.md | 563 --- documentation/en/cli-lotus-miner.md | 20 +- documentation/en/cli-lotus-provider.md | 410 -- documentation/en/cli-lotus-worker.md | 2 +- documentation/en/cli-lotus.md | 517 +-- documentation/en/cli-sptool.md | 474 --- documentation/en/default-curio-config.toml | 366 -- documentation/en/default-lotus-config.toml | 24 - .../en/default-lotus-miner-config.toml | 239 -- .../misc/Building_a_network_skeleton.md | 226 +- extern/filecoin-ffi | 2 +- gateway/node.go | 1 + gateway/proxy_eth.go | 8 + gen/inlinegen-data.json | 8 +- go.mod | 48 +- go.sum | 497 +-- itests/api_test.go | 6 +- itests/batch_deal_test.go | 147 - itests/cli_test.go | 27 - itests/curio_test.go | 67 - itests/deals_512mb_test.go | 51 - itests/deals_anycid_test.go | 162 - itests/deals_concurrent_test.go | 212 -- itests/deals_max_staging_deals_test.go | 70 - itests/deals_offline_test.go | 107 - itests/deals_padding_test.go | 84 - .../deals_partial_retrieval_dm-level_test.go | 267 -- itests/deals_partial_retrieval_test.go | 256 -- itests/deals_power_test.go | 70 - itests/deals_pricing_test.go | 150 - itests/deals_publish_test.go | 143 - itests/deals_remote_retrieval_test.go | 104 - itests/deals_retry_deal_no_funds_test.go | 188 - itests/deals_test.go | 45 - itests/eth_account_abstraction_test.go | 12 +- itests/eth_conformance_test.go | 2 +- itests/eth_deploy_test.go | 2 +- itests/eth_hash_lookup_test.go | 6 +- itests/eth_legacy_transactions_test.go | 400 ++ itests/eth_transactions_test.go | 83 +- itests/fevm_test.go | 10 +- itests/gateway_test.go | 41 - itests/harmonytask_test.go | 303 -- itests/kit/blockminer.go | 133 +- itests/kit/client.go | 161 - itests/kit/deals.go | 483 --- itests/kit/ensemble.go | 174 +- itests/kit/ensemble_opts_nv.go | 6 +- itests/kit/ensemble_presets.go | 39 - itests/kit/evm.go | 53 +- itests/kit/node_full.go | 53 +- itests/kit/node_miner.go | 3 +- itests/kit/node_opts.go | 1 - itests/kit/node_unmanaged.go | 1072 ++++++ itests/manual_onboarding_test.go | 174 + itests/path_type_filters_test.go | 200 - itests/sector_finalize_early_test.go | 72 - itests/supply_test.go | 205 + itests/worker_test.go | 79 - itests/worker_upgrade_test.go | 170 - lib/filler/filler.go | 43 + .../filler/filler_test.go | 4 +- lib/harmony/harmonydb/harmonydb.go | 44 +- .../harmonydb/sql/20230719-harmony.sql | 51 - lib/harmony/harmonydb/sql/20230823-wdpost.sql | 48 - lib/harmony/harmonydb/sql/20230919-config.sql | 5 - .../harmonydb/sql/20231103-chain_sends.sql | 55 - .../harmonydb/sql/20231110-mining_tasks.sql | 39 - .../sql/20231113-harmony_taskhistory_oops.sql | 1 - .../harmonydb/sql/20231120-testing1.sql | 8 - .../harmonydb/sql/20231217-sdr-pipeline.sql | 135 - .../harmonydb/sql/20231225-message-waits.sql | 13 - .../harmonydb/sql/20240212-common-layers.sql | 42 - .../harmonydb/sql/20240228-piece-park.sql | 35 - .../sql/20240317-web-summary-index.sql | 8 - .../20240402-sdr-pipeline-ddo-deal-info.sql | 8 - .../harmonydb/sql/20240404-machine_detail.sql | 12 - .../sql/20240416-harmony_singleton_task.sql | 8 - .../sql/20240417-sector_index_gc.sql | 13 - .../sql/20240420-web-task-indexes.sql | 9 - lib/harmony/harmonydb/userfuncs.go | 4 + lib/harmony/harmonytask/doc.go | 71 - lib/harmony/harmonytask/harmonytask.go | 387 -- lib/harmony/harmonytask/singleton_task.go | 52 - lib/harmony/harmonytask/task_type_handler.go | 295 -- lib/harmony/resources/getGPU.go | 34 - lib/harmony/resources/getGPU_darwin.go | 8 - lib/harmony/resources/memsys.go | 22 - lib/harmony/resources/miniopencl/cl.h | 17 - .../resources/miniopencl/mini_opencl.go | 93 - lib/harmony/resources/resources.go | 165 - lib/harmony/taskhelp/common.go | 19 - lib/passcall/every.go | 28 - lib/unixfs/filestore.go | 159 - lib/unixfs/filestore_test.go | 128 - markets/dagstore/blockstore.go | 34 - markets/dagstore/fixtures/sample-rw-bs-v2.car | Bin 1875 -> 0 bytes markets/dagstore/miner_api.go | 205 - markets/dagstore/miner_api_test.go | 252 -- markets/dagstore/mocks/mock_lotus_accessor.go | 97 - markets/dagstore/mount.go | 91 - markets/dagstore/mount_test.go | 151 - markets/dagstore/wrapper.go | 436 --- markets/dagstore/wrapper_migration_test.go | 153 - markets/dagstore/wrapper_test.go | 262 -- markets/dealfilter/cli.go | 62 - markets/idxprov/idxprov_test/noop.go | 16 - markets/idxprov/mesh.go | 59 - markets/journal.go | 76 - markets/loggers/loggers.go | 76 - markets/pricing/cli.go | 50 - markets/retrievaladapter/client.go | 127 - markets/retrievaladapter/client_blockstore.go | 166 - markets/retrievaladapter/provider.go | 108 - markets/retrievaladapter/provider_test.go | 206 - markets/sectoraccessor/sectoraccessor.go | 136 - markets/storageadapter/api.go | 55 - markets/storageadapter/client.go | 446 --- markets/storageadapter/client_blockstore.go | 102 - markets/storageadapter/dealpublisher.go | 466 --- markets/storageadapter/dealpublisher_test.go | 423 --- markets/storageadapter/dealstatematcher.go | 85 - .../storageadapter/dealstatematcher_test.go | 155 - .../storageadapter/ondealsectorcommitted.go | 418 --- .../ondealsectorcommitted_test.go | 583 --- markets/storageadapter/provider.go | 441 --- markets/utils/converters.go | 39 - markets/utils/selectors.go | 98 - node/builder.go | 9 +- node/builder_chain.go | 36 +- node/builder_miner.go | 115 +- node/config/def.go | 117 +- node/config/def_test.go | 6 - node/config/doc_gen.go | 351 +- node/config/load.go | 2 +- node/config/types.go | 199 +- node/hello/hello.go | 6 +- node/impl/client/car_helpers.go | 91 - node/impl/client/client.go | 1536 -------- node/impl/client/client_test.go | 136 - .../impl/client/testdata/duplicate_blocks.txt | 1 - node/impl/client/testdata/payload.txt | 49 - node/impl/client/testdata/payload2.txt | 49 - node/impl/common/common.go | 4 +- node/impl/full.go | 2 - node/impl/full/dummy.go | 4 + node/impl/full/eth.go | 60 +- node/impl/full/eth_events.go | 2 +- node/impl/full/eth_utils.go | 33 +- node/impl/full/state.go | 3 +- node/impl/full/txhashmanager.go | 3 +- node/impl/storminer.go | 772 +--- node/modules/alerts.go | 10 - node/modules/client.go | 218 -- node/modules/dtypes/miner.go | 11 - node/modules/dtypes/storage.go | 31 - node/modules/graphsync.go | 101 - node/modules/lp2p/host.go | 4 +- node/modules/services.go | 24 - node/modules/storageminer.go | 732 +--- node/modules/storageminer_dagstore.go | 94 - node/modules/storageminer_idxprov.go | 117 - node/modules/storageminer_idxprov_test.go | 99 - node/repo/fsrepo.go | 24 - node/repo/imports/manager.go | 275 -- node/rpc.go | 120 - scripts/curio.service | 12 - scripts/generate-lotus-cli.py | 2 - scripts/publish-checksums.sh | 2 +- scripts/version-check.sh | 2 +- storage/paths/db_index.go | 6 +- storage/paths/interface.go | 2 +- storage/paths/local.go | 24 +- storage/paths/mocks/store.go | 8 +- storage/paths/remote.go | 4 +- storage/pipeline/numassign.go | 2 +- storage/pipeline/piece/piece_info.go | 9 + storage/pipeline/states_sealing.go | 3 +- storage/pipeline/utils.go | 37 - storage/sealer/ffiwrapper/sealer_cgo.go | 7 + storage/sealer/mock/mock.go | 3 +- storage/sealer/piece_provider.go | 9 +- storage/sealer/piece_reader.go | 3 - storage/sealer/proofpaths/cachefiles.go | 8 + storage/sealer/storiface/storage.go | 10 + storage/sealer/worker_local.go | 10 +- 474 files changed, 9796 insertions(+), 62469 deletions(-) delete mode 100644 api/api_curio.go create mode 100644 build/actors/v14.tar.zst create mode 100644 chain/actors/builtin/account/v14.go create mode 100644 chain/actors/builtin/cron/v14.go create mode 100644 chain/actors/builtin/datacap/v14.go create mode 100644 chain/actors/builtin/evm/v14.go create mode 100644 chain/actors/builtin/init/v14.go create mode 100644 chain/actors/builtin/market/v14.go create mode 100644 chain/actors/builtin/miner/v14.go create mode 100644 chain/actors/builtin/multisig/message14.go create mode 100644 chain/actors/builtin/multisig/v14.go create mode 100644 chain/actors/builtin/paych/message14.go create mode 100644 chain/actors/builtin/paych/v14.go create mode 100644 chain/actors/builtin/power/v14.go create mode 100644 chain/actors/builtin/reward/v14.go create mode 100644 chain/actors/builtin/system/v14.go create mode 100644 chain/actors/builtin/verifreg/v14.go create mode 100644 chain/types/ethtypes/eth_1559_transactions.go create mode 100644 chain/types/ethtypes/eth_1559_transactions_test.go create mode 100644 chain/types/ethtypes/eth_legacy_155_transactions.go create mode 100644 chain/types/ethtypes/eth_legacy_155_transactions_test.go create mode 100644 chain/types/ethtypes/eth_legacy_homestead_transactions.go create mode 100644 chain/types/ethtypes/eth_legacy_homestead_transactions_test.go delete mode 100644 cli/client.go delete mode 100644 cli/client_retr.go delete mode 100644 cli/util/retrieval.go delete mode 100644 cmd/curio/cli.go delete mode 100644 cmd/curio/config.go delete mode 100644 cmd/curio/config_new.go delete mode 100644 cmd/curio/config_test.go delete mode 100644 cmd/curio/deps/apiinfo.go delete mode 100644 cmd/curio/deps/deps.go delete mode 100644 cmd/curio/guidedsetup/guidedsetup.go delete mode 100644 cmd/curio/guidedsetup/shared.go delete mode 100644 cmd/curio/internal/translations/catalog.go delete mode 100644 cmd/curio/internal/translations/knowns/main.go delete mode 100644 cmd/curio/internal/translations/locales/en/out.gotext.json delete mode 100644 cmd/curio/internal/translations/locales/ko/messages.gotext.json delete mode 100644 cmd/curio/internal/translations/locales/ko/out.gotext.json delete mode 100644 cmd/curio/internal/translations/locales/zh/messages.gotext.json delete mode 100644 cmd/curio/internal/translations/locales/zh/out.gotext.json delete mode 100644 cmd/curio/internal/translations/translations.go delete mode 100755 cmd/curio/internal/translations/updateLang.sh delete mode 100644 cmd/curio/log.go delete mode 100644 cmd/curio/main.go delete mode 100644 cmd/curio/market.go delete mode 100644 cmd/curio/migrate.go delete mode 100644 cmd/curio/pipeline.go delete mode 100644 cmd/curio/proving.go delete mode 100644 cmd/curio/rpc/rpc.go delete mode 100644 cmd/curio/run.go delete mode 100644 cmd/curio/stop.go delete mode 100644 cmd/curio/storage.go delete mode 100644 cmd/curio/tasks/tasks.go delete mode 100644 cmd/lotus-miner/dagstore.go delete mode 100644 cmd/lotus-miner/index_provider.go delete mode 100644 cmd/lotus-miner/init_service.go delete mode 100644 cmd/lotus-miner/market.go delete mode 100644 cmd/lotus-miner/pieces.go delete mode 100644 cmd/lotus-miner/retrieval-deals.go delete mode 100644 cmd/lotus-shed/deal.go delete mode 100644 cmd/lotus-shed/misc.go delete mode 100644 cmd/sptool/actor.go delete mode 100644 cmd/sptool/main.go delete mode 100644 cmd/sptool/proving.go delete mode 100644 cmd/sptool/sector.go delete mode 100644 curiosrc/address.go delete mode 100644 curiosrc/builder.go delete mode 100644 curiosrc/chainsched/chain_sched.go delete mode 100644 curiosrc/ffi/piece_funcs.go delete mode 100644 curiosrc/ffi/sdr_funcs.go delete mode 100644 curiosrc/ffi/task_storage.go delete mode 100644 curiosrc/gc/storage_endpoint_gc.go delete mode 100644 curiosrc/market/deal_ingest.go delete mode 100644 curiosrc/market/fakelm/iface.go delete mode 100644 curiosrc/market/fakelm/lmimpl.go delete mode 100644 curiosrc/market/lmrpc/lmrpc.go delete mode 100644 curiosrc/message/sender.go delete mode 100644 curiosrc/message/watch.go delete mode 100644 curiosrc/multictladdr/multiaddresses.go delete mode 100644 curiosrc/piece/task_cleanup_piece.go delete mode 100644 curiosrc/piece/task_park_piece.go delete mode 100644 curiosrc/proof/treed_build.go delete mode 100644 curiosrc/proof/treed_build_test.go delete mode 100644 curiosrc/seal/README.md delete mode 100644 curiosrc/seal/finalize_pieces.go delete mode 100644 curiosrc/seal/poller.go delete mode 100644 curiosrc/seal/poller_commit_msg.go delete mode 100644 curiosrc/seal/poller_precommit_msg.go delete mode 100644 curiosrc/seal/sector_num_alloc.go delete mode 100644 curiosrc/seal/task_finalize.go delete mode 100644 curiosrc/seal/task_movestorage.go delete mode 100644 curiosrc/seal/task_porep.go delete mode 100644 curiosrc/seal/task_sdr.go delete mode 100644 curiosrc/seal/task_submit_commit.go delete mode 100644 curiosrc/seal/task_submit_precommit.go delete mode 100644 curiosrc/seal/task_trees.go delete mode 100644 curiosrc/seal/task_trees_test.go delete mode 100644 curiosrc/web/api/apihelper/apihelper.go delete mode 100644 curiosrc/web/api/config/config.go delete mode 100644 curiosrc/web/api/debug/debug.go delete mode 100644 curiosrc/web/api/routes.go delete mode 100644 curiosrc/web/api/sector/sector.go delete mode 100644 curiosrc/web/hapi/robust_rpc.go delete mode 100644 curiosrc/web/hapi/routes.go delete mode 100644 curiosrc/web/hapi/simpleinfo.go delete mode 100644 curiosrc/web/hapi/simpleinfo_pipeline_porep.go delete mode 100644 curiosrc/web/hapi/watch_actor.go delete mode 100644 curiosrc/web/hapi/web/actor_summary.gohtml delete mode 100644 curiosrc/web/hapi/web/chain_rpcs.gohtml delete mode 100644 curiosrc/web/hapi/web/cluster_machines.gohtml delete mode 100644 curiosrc/web/hapi/web/cluster_task_history.gohtml delete mode 100644 curiosrc/web/hapi/web/cluster_tasks.gohtml delete mode 100644 curiosrc/web/hapi/web/node_info.gohtml delete mode 100644 curiosrc/web/hapi/web/pipeline_porep_sectors.gohtml delete mode 100644 curiosrc/web/hapi/web/pipline_porep.gohtml delete mode 100644 curiosrc/web/hapi/web/root.gohtml delete mode 100644 curiosrc/web/hapi/web/sector_info.gohtml delete mode 100644 curiosrc/web/srv.go delete mode 100644 curiosrc/web/static/chain-connectivity.mjs delete mode 100644 curiosrc/web/static/config/edit.html delete mode 100644 curiosrc/web/static/config/index.html delete mode 100644 curiosrc/web/static/favicon.svg delete mode 100644 curiosrc/web/static/index.html delete mode 100644 curiosrc/web/static/pipeline_porep.html delete mode 100644 curiosrc/web/static/sector/index.html delete mode 100644 curiosrc/web/static/ux/curio-ux.mjs delete mode 100644 curiosrc/web/static/ux/main.css delete mode 100644 curiosrc/window/compute_do.go delete mode 100644 curiosrc/window/compute_task.go delete mode 100644 curiosrc/window/faults_simple.go delete mode 100644 curiosrc/window/recover_task.go delete mode 100644 curiosrc/window/submit_task.go delete mode 100644 curiosrc/winning/winning_task.go delete mode 100644 documentation/en/cli-curio.md delete mode 100644 documentation/en/cli-lotus-provider.md delete mode 100644 documentation/en/cli-sptool.md delete mode 100644 documentation/en/default-curio-config.toml delete mode 100644 itests/batch_deal_test.go delete mode 100644 itests/cli_test.go delete mode 100644 itests/curio_test.go delete mode 100644 itests/deals_512mb_test.go delete mode 100644 itests/deals_anycid_test.go delete mode 100644 itests/deals_concurrent_test.go delete mode 100644 itests/deals_max_staging_deals_test.go delete mode 100644 itests/deals_offline_test.go delete mode 100644 itests/deals_padding_test.go delete mode 100644 itests/deals_partial_retrieval_dm-level_test.go delete mode 100644 itests/deals_partial_retrieval_test.go delete mode 100644 itests/deals_power_test.go delete mode 100644 itests/deals_pricing_test.go delete mode 100644 itests/deals_publish_test.go delete mode 100644 itests/deals_remote_retrieval_test.go delete mode 100644 itests/deals_retry_deal_no_funds_test.go delete mode 100644 itests/deals_test.go create mode 100644 itests/eth_legacy_transactions_test.go delete mode 100644 itests/harmonytask_test.go delete mode 100644 itests/kit/client.go delete mode 100644 itests/kit/deals.go create mode 100644 itests/kit/node_unmanaged.go create mode 100644 itests/manual_onboarding_test.go delete mode 100644 itests/path_type_filters_test.go delete mode 100644 itests/sector_finalize_early_test.go create mode 100644 itests/supply_test.go delete mode 100644 itests/worker_upgrade_test.go create mode 100644 lib/filler/filler.go rename storage/pipeline/utils_test.go => lib/filler/filler_test.go (96%) delete mode 100644 lib/harmony/harmonydb/sql/20230719-harmony.sql delete mode 100644 lib/harmony/harmonydb/sql/20230823-wdpost.sql delete mode 100644 lib/harmony/harmonydb/sql/20230919-config.sql delete mode 100644 lib/harmony/harmonydb/sql/20231103-chain_sends.sql delete mode 100644 lib/harmony/harmonydb/sql/20231110-mining_tasks.sql delete mode 100644 lib/harmony/harmonydb/sql/20231113-harmony_taskhistory_oops.sql delete mode 100644 lib/harmony/harmonydb/sql/20231120-testing1.sql delete mode 100644 lib/harmony/harmonydb/sql/20231217-sdr-pipeline.sql delete mode 100644 lib/harmony/harmonydb/sql/20231225-message-waits.sql delete mode 100644 lib/harmony/harmonydb/sql/20240212-common-layers.sql delete mode 100644 lib/harmony/harmonydb/sql/20240228-piece-park.sql delete mode 100644 lib/harmony/harmonydb/sql/20240317-web-summary-index.sql delete mode 100644 lib/harmony/harmonydb/sql/20240402-sdr-pipeline-ddo-deal-info.sql delete mode 100644 lib/harmony/harmonydb/sql/20240404-machine_detail.sql delete mode 100644 lib/harmony/harmonydb/sql/20240416-harmony_singleton_task.sql delete mode 100644 lib/harmony/harmonydb/sql/20240417-sector_index_gc.sql delete mode 100644 lib/harmony/harmonydb/sql/20240420-web-task-indexes.sql delete mode 100644 lib/harmony/harmonytask/doc.go delete mode 100644 lib/harmony/harmonytask/harmonytask.go delete mode 100644 lib/harmony/harmonytask/singleton_task.go delete mode 100644 lib/harmony/harmonytask/task_type_handler.go delete mode 100644 lib/harmony/resources/getGPU.go delete mode 100644 lib/harmony/resources/getGPU_darwin.go delete mode 100644 lib/harmony/resources/memsys.go delete mode 100644 lib/harmony/resources/miniopencl/cl.h delete mode 100644 lib/harmony/resources/miniopencl/mini_opencl.go delete mode 100644 lib/harmony/resources/resources.go delete mode 100644 lib/harmony/taskhelp/common.go delete mode 100644 lib/passcall/every.go delete mode 100644 lib/unixfs/filestore.go delete mode 100644 lib/unixfs/filestore_test.go delete mode 100644 markets/dagstore/blockstore.go delete mode 100644 markets/dagstore/fixtures/sample-rw-bs-v2.car delete mode 100644 markets/dagstore/miner_api.go delete mode 100644 markets/dagstore/miner_api_test.go delete mode 100644 markets/dagstore/mocks/mock_lotus_accessor.go delete mode 100644 markets/dagstore/mount.go delete mode 100644 markets/dagstore/mount_test.go delete mode 100644 markets/dagstore/wrapper.go delete mode 100644 markets/dagstore/wrapper_migration_test.go delete mode 100644 markets/dagstore/wrapper_test.go delete mode 100644 markets/dealfilter/cli.go delete mode 100644 markets/idxprov/idxprov_test/noop.go delete mode 100644 markets/idxprov/mesh.go delete mode 100644 markets/journal.go delete mode 100644 markets/loggers/loggers.go delete mode 100644 markets/pricing/cli.go delete mode 100644 markets/retrievaladapter/client.go delete mode 100644 markets/retrievaladapter/client_blockstore.go delete mode 100644 markets/retrievaladapter/provider.go delete mode 100644 markets/retrievaladapter/provider_test.go delete mode 100644 markets/sectoraccessor/sectoraccessor.go delete mode 100644 markets/storageadapter/api.go delete mode 100644 markets/storageadapter/client.go delete mode 100644 markets/storageadapter/client_blockstore.go delete mode 100644 markets/storageadapter/dealpublisher.go delete mode 100644 markets/storageadapter/dealpublisher_test.go delete mode 100644 markets/storageadapter/dealstatematcher.go delete mode 100644 markets/storageadapter/dealstatematcher_test.go delete mode 100644 markets/storageadapter/ondealsectorcommitted.go delete mode 100644 markets/storageadapter/ondealsectorcommitted_test.go delete mode 100644 markets/storageadapter/provider.go delete mode 100644 markets/utils/converters.go delete mode 100644 markets/utils/selectors.go delete mode 100644 node/impl/client/car_helpers.go delete mode 100644 node/impl/client/client.go delete mode 100644 node/impl/client/client_test.go delete mode 100644 node/impl/client/testdata/duplicate_blocks.txt delete mode 100644 node/impl/client/testdata/payload.txt delete mode 100644 node/impl/client/testdata/payload2.txt delete mode 100644 node/modules/client.go delete mode 100644 node/modules/graphsync.go delete mode 100644 node/modules/storageminer_dagstore.go delete mode 100644 node/modules/storageminer_idxprov.go delete mode 100644 node/modules/storageminer_idxprov_test.go delete mode 100644 node/repo/imports/manager.go delete mode 100644 scripts/curio.service diff --git a/.github/actions/install-go/action.yml b/.github/actions/install-go/action.yml index 173559284..30be7d7f1 100644 --- a/.github/actions/install-go/action.yml +++ b/.github/actions/install-go/action.yml @@ -1,6 +1,11 @@ name: Install Go description: Install Go for Filecoin Lotus +inputs: + working-directory: + description: Specifies the working directory where the command is run. + required: false + runs: using: composite steps: @@ -10,6 +15,8 @@ runs: cache: false - id: go-mod uses: ipdxco/unified-github-workflows/.github/actions/read-go-mod@main + with: + working-directory: ${{ inputs.working-directory || github.workspace }} - uses: actions/setup-go@v5 with: go-version: ${{ fromJSON(steps.go-mod.outputs.json).Go }}.x diff --git a/.github/actions/start-yugabytedb/action.yml b/.github/actions/start-yugabytedb/action.yml index 13c480c66..1019d5575 100644 --- a/.github/actions/start-yugabytedb/action.yml +++ b/.github/actions/start-yugabytedb/action.yml @@ -4,7 +4,7 @@ description: Install Yugabyte Database for Filecoin Lotus runs: using: composite steps: - - run: docker run --rm --name yugabyte -d -p 5433:5433 yugabytedb/yugabyte:2.18.0.0-b65 bin/yugabyted start --daemon=false + - run: docker run --rm --name yugabyte -d -p 5433:5433 yugabytedb/yugabyte:2.21.0.1-b1 bin/yugabyted start --daemon=false shell: bash - run: | while true; do diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d7dd59e14..543b17dd8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,8 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} -permissions: {} +permissions: + contents: read jobs: build: diff --git a/.github/workflows/builtin-actor-tests.yml b/.github/workflows/builtin-actor-tests.yml index 93d4c669e..c24d8db1f 100644 --- a/.github/workflows/builtin-actor-tests.yml +++ b/.github/workflows/builtin-actor-tests.yml @@ -8,7 +8,8 @@ on: branches: - release/* -permissions: {} +permissions: + contents: read jobs: release: diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 2f60cdde7..6c8482219 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -16,7 +16,8 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} -permissions: {} +permissions: + contents: read jobs: check-docsgen: @@ -43,7 +44,6 @@ jobs: - uses: ./.github/actions/install-go - run: make deps lotus - run: go install golang.org/x/tools/cmd/goimports - - run: go install github.com/hannahhoward/cbor-gen-for - run: make gen - run: git diff --exit-code - run: make docsgen-cli @@ -57,7 +57,7 @@ jobs: submodules: 'recursive' - uses: ./.github/actions/install-system-dependencies - uses: ./.github/actions/install-go - - run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.58.2 + - run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.59.0 - run: make deps - run: golangci-lint run -v --timeout 10m --concurrency 4 check-fmt: diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f6afe68bf..cfde8bb42 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -10,16 +10,21 @@ on: schedule: - cron: '0 0 * * *' workflow_dispatch: + inputs: + ref: + description: The GitHub ref (e.g. refs/tags/v1.0.0) to release + required: false defaults: run: shell: bash -permissions: {} +permissions: + contents: read jobs: docker: - name: Docker (${{ matrix.image }} / ${{ matrix.network }}) [publish=${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') }}] + name: Docker (${{ matrix.image }} / ${{ matrix.network }}) [publish=${{ (inputs.ref || github.ref) == 'refs/heads/master' || startsWith(inputs.ref || github.ref, 'refs/tags/') }}] runs-on: ubuntu-latest strategy: fail-fast: false @@ -35,13 +40,13 @@ jobs: - image: lotus network: mainnet env: - PUBLISH: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') }} + PUBLISH: ${{ github.ref == 'refs/heads/master' || startsWith(inputs.ref || github.ref, 'refs/tags/') }} steps: - id: channel env: - IS_MASTER: ${{ github.ref == 'refs/heads/master' }} - IS_TAG: ${{ startsWith(github.ref, 'refs/tags/') }} - IS_RC: ${{ endsWith(github.ref, '-rc') }} + IS_MASTER: ${{ (inputs.ref || github.ref) == 'refs/heads/master' }} + IS_TAG: ${{ startsWith(inputs.ref || github.ref, 'refs/tags/') }} + IS_RC: ${{ contains(inputs.ref || github.ref, '-rc') }} IS_SCHEDULED: ${{ github.event_name == 'schedule' }} run: | channel='' @@ -58,10 +63,20 @@ jobs: channel=stable fi fi - echo "channel=$channel" | tee -a $GITHUB_ENV + echo "channel=$channel" | tee -a $GITHUB_OUTPUT - uses: actions/checkout@v4 with: submodules: 'recursive' + ref: ${{ inputs.ref || github.ref }} + - id: git + env: + REF: ${{ inputs.ref || github.ref }} + run: | + ref="${REF#refs/heads/}" + ref="${ref#refs/tags/}" + sha="$(git rev-parse --short HEAD)" + echo "ref=$ref" | tee -a "$GITHUB_OUTPUT" + echo "sha=$sha" | tee -a "$GITHUB_OUTPUT" - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Docker meta @@ -70,10 +85,9 @@ jobs: with: images: filecoin/${{ matrix.image }} tags: | - type=schedule - type=raw,enable=${{ github.event_name != 'schedule' && steps.channel.outputs.channel != '' }},value=${{ steps.channel.outputs.channel }} - type=ref,event=tag - type=sha,prefix= + type=raw,enable=${{ steps.channel.outputs.channel != '' }},value=${{ steps.channel.outputs.channel }} + type=raw,enable=${{ startsWith(inputs.ref || github.ref, 'refs/tags/') }},value=${{ steps.git.outputs.ref }} + type=raw,value=${{ steps.git.outputs.sha }} flavor: | latest=false suffix=${{ matrix.network != 'mainnet' && format('-{0}', matrix.network) || '' }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 797473bb3..ce3ba6e98 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,12 +8,17 @@ on: tags: - v* workflow_dispatch: + inputs: + ref: + description: The GitHub ref (e.g. refs/tags/v1.0.0) to release + required: false defaults: run: shell: bash -permissions: {} +permissions: + contents: read jobs: build: @@ -41,38 +46,57 @@ jobs: echo "::error title=Unexpected Runner::Expected $OS/$ARCH, got $RUNNER_OS/$RUNNER_ARCH" exit 1 fi + - uses: actions/checkout@v4 + with: + path: actions - uses: actions/checkout@v4 with: submodules: 'recursive' - - uses: ./.github/actions/install-system-dependencies - - uses: ./.github/actions/install-go + ref: ${{ inputs.ref || github.ref }} + path: lotus + - uses: ./actions/.github/actions/install-system-dependencies + - uses: ./actions/.github/actions/install-go + with: + working-directory: lotus - env: GITHUB_TOKEN: ${{ github.token }} run: make deps lotus lotus-miner lotus-worker + working-directory: lotus - if: runner.os == 'macOS' run: otool -hv lotus - - run: ./scripts/version-check.sh ./lotus + working-directory: lotus + - env: + INPUTS_REF: ${{ inputs.ref }} + run: | + export GITHUB_REF=${INPUTS_REF:-$GITHUB_REF} + ../actions/scripts/version-check.sh ./lotus + working-directory: lotus - uses: actions/upload-artifact@v4 with: name: lotus-${{ matrix.os }}-${{ matrix.arch }} path: | - lotus - lotus-miner - lotus-worker + lotus/lotus + lotus/lotus-miner + lotus/lotus-worker release: - name: Release [publish=${{ startsWith(github.ref, 'refs/tags/') }}] + name: Release [publish=${{ startsWith(inputs.ref || github.ref, 'refs/tags/') }}] permissions: # This enables the job to create and/or update GitHub releases contents: write runs-on: ubuntu-latest needs: [build] env: - PUBLISH: ${{ startsWith(github.ref, 'refs/tags/') }} + PUBLISH: ${{ startsWith(inputs.ref || github.ref, 'refs/tags/') }} steps: + - uses: actions/checkout@v4 + with: + path: actions - uses: actions/checkout@v4 with: submodules: 'recursive' fetch-depth: 0 + path: lotus + ref: ${{ inputs.ref || github.ref }} - uses: actions/download-artifact@v4 with: name: lotus-Linux-X64 @@ -85,7 +109,9 @@ jobs: with: name: lotus-macOS-ARM64 path: darwin_arm64 - - uses: ./.github/actions/install-go + - uses: ./actions/.github/actions/install-go + with: + working-directory: lotus - uses: ipfs/download-ipfs-distribution-action@v1 with: name: kubo @@ -95,11 +121,21 @@ jobs: distribution: goreleaser-pro version: latest args: release --clean --debug ${{ env.PUBLISH == 'false' && '--snapshot' || '' }} + workdir: lotus env: - GITHUB_TOKEN: ${{ env.PUBLISH == 'true' && github.token || '' }} + GITHUB_TOKEN: ${{ env.PUBLISH == 'true' && secrets.GORELEASER_GITUB_TOKEN || github.token || '' }} GORELEASER_KEY: ${{ env.PUBLISH == 'true' && secrets.GORELEASER_KEY || '' }} - - run: ./scripts/generate-checksums.sh + - env: + INPUTS_REF: ${{ inputs.ref }} + run: | + export GITHUB_REF=${INPUTS_REF:-$GITHUB_REF} + ../actions/scripts/generate-checksums.sh + working-directory: lotus - if: env.PUBLISH == 'true' env: GITHUB_TOKEN: ${{ github.token }} - run: ./scripts/publish-checksums.sh + INPUTS_REF: ${{ inputs.ref }} + run: | + export GITHUB_REF=${INPUTS_REF:-$GITHUB_REF} + ../actions/scripts/publish-checksums.sh + working-directory: lotus diff --git a/.github/workflows/sorted-pr-checks.yml b/.github/workflows/sorted-pr-checks.yml index 0c2b0ee8a..2ce76cb17 100644 --- a/.github/workflows/sorted-pr-checks.yml +++ b/.github/workflows/sorted-pr-checks.yml @@ -17,15 +17,17 @@ on: - completed permissions: + actions: read + checks: read pull-requests: write concurrency: - group: ${{ github.workflow }}-${{ github.event.inputs.pull_number || github.event.workflow_run.pull_requests[0].number || 'unknown' }} + group: ${{ github.workflow }}-${{ github.event.inputs.pull_number || github.event.workflow_run.pull_requests[0].number }} cancel-in-progress: true jobs: comment: - if: github.event.inputs.pull_number || github.event.workflow_run.pull_requests[0] + if: github.event.inputs.pull_number || github.event.workflow_run.event == 'pull_request' uses: ipdxco/sorted-pr-checks/.github/workflows/comment.yml@v1 with: pull_number: ${{ github.event.inputs.pull_number || github.event.workflow_run.pull_requests[0].number }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 3116da07c..9def5f678 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -4,7 +4,8 @@ on: schedule: - cron: '0 12 * * *' -permissions: {} +permissions: + contents: read jobs: stale: diff --git a/.github/workflows/sync-master-main.yaml b/.github/workflows/sync-master-main.yaml index b629b5604..3ffb6932a 100644 --- a/.github/workflows/sync-master-main.yaml +++ b/.github/workflows/sync-master-main.yaml @@ -5,7 +5,8 @@ on: branches: - master -permissions: {} +permissions: + contents: read jobs: sync: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a64a613a8..2a5648a54 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,8 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} -permissions: {} +permissions: + contents: read jobs: discover: @@ -96,6 +97,7 @@ jobs: "itest-get_messages_in_ts": ["self-hosted", "linux", "x64", "xlarge"], "itest-lite_migration": ["self-hosted", "linux", "x64", "xlarge"], "itest-lookup_robust_address": ["self-hosted", "linux", "x64", "xlarge"], + "itest-manual_onboarding": ["self-hosted", "linux", "x64", "xlarge"], "itest-mempool": ["self-hosted", "linux", "x64", "xlarge"], "itest-mpool_msg_uuid": ["self-hosted", "linux", "x64", "xlarge"], "itest-mpool_push_with_uuid": ["self-hosted", "linux", "x64", "xlarge"], @@ -115,7 +117,7 @@ jobs: } # A list of test groups that require YugabyteDB to be running yugabytedb: | - ["itest-harmonydb", "itest-harmonytask", "itest-curio"] + ["itest-harmonydb"] # A list of test groups that require Proof Parameters to be fetched parameters: | [ @@ -128,6 +130,7 @@ jobs: "itest-deals", "itest-direct_data_onboard_verified", "itest-direct_data_onboard", + "itest-manual_onboarding", "itest-net", "itest-path_detach_redeclare", "itest-path_type_filters", @@ -144,7 +147,8 @@ jobs: "itest-worker", "multicore-sdr", "unit-cli", - "unit-storage" + "unit-storage", + "itest-curio" ] run: | # Create a list of integration test groups @@ -203,7 +207,7 @@ jobs: echo -e "path< v0.3.2) +- github.com/filecoin-project/go-state-types (v0.13.3 -> v0.14.0-dev) + +## Lotus-Miner / Curio related changes + +- fix logs (#12036) ([filecoin-project/lotus#12036](https://github.com/filecoin-project/lotus/pull/12036)) +- feat: curioweb: Improve task_history indexes (#11911) ([filecoin-project/lotus#11911](https://github.com/filecoin-project/lotus/pull/11911)) +- fix: curio taskstorage: Don't try to free reservations by nulled TaskID (#12018) ([filecoin-project/lotus#12018](https://github.com/filecoin-project/lotus/pull/12018)) +- fix actor string (#12019) ([filecoin-project/lotus#12019](https://github.com/filecoin-project/lotus/pull/12019)) +- fix: curio: Update pgx imports, fix db_storage alloc +- feat: curioweb: Show piece info on the sector page (#11955) ([filecoin-project/lotus#11955](https://github.com/filecoin-project/lotus/pull/11955)) +- curio: feat: break trees task into TreeD(prefetch) and TreeRC (#11895) ([filecoin-project/lotus#11895](https://github.com/filecoin-project/lotus/pull/11895)) +- fix: curio: node UI & darwin gpu count (#11950) ([filecoin-project/lotus#11950](https://github.com/filecoin-project/lotus/pull/11950)) +- feat: curio: Keep more sector metadata in the DB long-term (#11933) ([filecoin-project/lotus#11933](https://github.com/filecoin-project/lotus/pull/11933)) +- fix: curio/lmrpc: Check ParkPiece success before creating sectors (#11975) ([filecoin-project/lotus#11975](https://github.com/filecoin-project/lotus/pull/11975)) +- feat: curio: docker devnet (#11954) ([filecoin-project/lotus#11954](https://github.com/filecoin-project/lotus/pull/11954)) +- feat: curio: alertManager (#11926) ([filecoin-project/lotus#11926](https://github.com/filecoin-project/lotus/pull/11926)) +- curio cfg edit: ux cleanups (#11985) ([filecoin-project/lotus#11985](https://github.com/filecoin-project/lotus/pull/11985)) +- fix: curio: Drop FKs from pipeline to fix retry loops (#11973) ([filecoin-project/lotus#11973](https://github.com/filecoin-project/lotus/pull/11973)) +- Produce DEB files for amd64 for openCL and cuda (#11885) ([filecoin-project/lotus#11885](https://github.com/filecoin-project/lotus/pull/11885)) +- gui-listen fix (#12013) ([filecoin-project/lotus#12013](https://github.com/filecoin-project/lotus/pull/12013)) +- feat: curio: allow multiple pieces per sector (#11935) ([filecoin-project/lotus#11935](https://github.com/filecoin-project/lotus/pull/11935)) +- chore: update yugabyte deps (#12022) ([filecoin-project/lotus#12022](https://github.com/filecoin-project/lotus/pull/12022)) +- fix: harmonydb: Use timestampz instead of timestamp across the schema (#12030) ([filecoin-project/lotus#12030](https://github.com/filecoin-project/lotus/pull/12030)) +- cleanup: miner: remove markets and deal-making from Lotus Miner (#12005) ([filecoin-project/lotus#12005](https://github.com/filecoin-project/lotus/pull/12005)) +- fix non existing sector (#12012) ([filecoin-project/lotus#12012](https://github.com/filecoin-project/lotus/pull/12012)) +- feat: curio ffiselect: Isolate gpu calls in a subprocess (#11994) ([filecoin-project/lotus#11994](https://github.com/filecoin-project/lotus/pull/11994)) +- feat: curio: jsonrpc in webui (#11904) ([filecoin-project/lotus#11904](https://github.com/filecoin-project/lotus/pull/11904)) +- fix: itests: Fix flaky curio itest (#12037) ([filecoin-project/lotus#12037](https://github.com/filecoin-project/lotus/pull/12037)) +- feat: curio: wdPost and wnPost alerts (#12029) ([filecoin-project/lotus#12029](https://github.com/filecoin-project/lotus/pull/12029)) +- fix: storage: Fix a race in GenerateWindowPoStAdv (#12064) ([filecoin-project/lotus#12064](https://github.com/filecoin-project/lotus/pull/12064)) +- Remove "provider" relics (#11992) ([filecoin-project/lotus#11992](https://github.com/filecoin-project/lotus/pull/11992)) +- fix sector UI (#12016) ([filecoin-project/lotus#12016](https://github.com/filecoin-project/lotus/pull/12016)) + +## Others +- ci: deprecate circle ci in favour of github actions (#11786) ([filecoin-project/lotus#11786](https://github.com/filecoin-project/lotus/pull/11786)) +- src: chain: remove C dependency from builtin types (#12015) ([filecoin-project/lotus#12015](https://github.com/filecoin-project/lotus/pull/12015)) +- chore: fix function names (#12043) ([filecoin-project/lotus#12043](https://github.com/filecoin-project/lotus/pull/12043)) +- chore: bump build version in master (#11946) ([filecoin-project/lotus#11946](https://github.com/filecoin-project/lotus/pull/11946)) +- fix: test: no snap deals in immutable deadlines (#12071) ([filecoin-project/lotus#12071](https://github.com/filecoin-project/lotus/pull/12071)) +- test: actors: manual CC onboarding and proving integration test (#12017) ([filecoin-project/lotus#12017](https://github.com/filecoin-project/lotus/pull/12017)) +- fix: ci: keep lotus checkout clean in the release workflow (#12028) ([filecoin-project/lotus#12028](https://github.com/filecoin-project/lotus/pull/12028)) +- feat!: build: separate miner and node version strings +- chore: lint: address feedback from reviews +- chore: lint: fix lint errors with new linting config +- chore: lint: update golangci lint config +- ci: fix when sorted pr checks workflow is executed +- doc: eth: restore comment lost in linter cleanup +- fix: ci: publish correct docker tags on workflow dispatch (#12060) ([filecoin-project/lotus#12060](https://github.com/filecoin-project/lotus/pull/12060)) +- feat: libp2p: Lotus stream cleanup (#11993) ([filecoin-project/lotus#11993](https://github.com/filecoin-project/lotus/pull/11993)) +- Update SupportedProofTypes (#11988) ([filecoin-project/lotus#11988](https://github.com/filecoin-project/lotus/pull/11988)) +- Revert "Update SupportedProofTypes (#11988)" (#11990) ([filecoin-project/lotus#11990](https://github.com/filecoin-project/lotus/pull/11990)) +- chore: docs: Update skeleton guide (#11960) ([filecoin-project/lotus#11960](https://github.com/filecoin-project/lotus/pull/11960)) +- chore: ci: request contents read permissions explicitly in gha (#12055) ([filecoin-project/lotus#12055](https://github.com/filecoin-project/lotus/pull/12055)) +- fix: ci: use custom GITHUB_TOKEN for GoReleaser (#12059) ([filecoin-project/lotus#12059](https://github.com/filecoin-project/lotus/pull/12059)) +- chore: pin golanglint-ci to v1.58.2 (#12054) ([filecoin-project/lotus#12054](https://github.com/filecoin-project/lotus/pull/12054)) +- chore: fix some function names (#12031) ([filecoin-project/lotus#12031](https://github.com/filecoin-project/lotus/pull/12031)) +- src: lint: bump golangci-lint to 1.59, address unchecked fmt.Fprint* + +## Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| Aarsh Shah | 9 | +5710/-35899 | 201 | +| Łukasz Magiera | 21 | +1891/-33776 | 335 | +| LexLuthr | 9 | +4916/-1637 | 107 | +| Phi-rjan | 9 | +3544/-187 | 92 | +| Rod Vagg | 15 | +2183/-479 | 164 | +| Piotr Galar | 6 | +130/-2386 | 30 | +| Andrew Jackson (Ajax) | 6 | +1072/-533 | 63 | +| ZenGround0 | 1 | +235/-13 | 3 | +| Hubert Bugaj | 3 | +57/-37 | 5 | +| Steven Allen | 3 | +25/-15 | 6 | +| Peter Rabbitson | 1 | +16/-8 | 4 | +| tomfees | 1 | +6/-6 | 5 | +| imxyb | 1 | +6/-0 | 1 | +| yumeiyin | 1 | +2/-2 | 2 | +| galargh | 1 | +2/-2 | 1 | + # v1.27.0 / 2024-05-27 This is an optional feature release of Lotus. Lotus v1.27.0 includes numerous improvements, bugfixes and enhancements for node operators, RPC- and ETH RPC-providers. This feature release also introduces Curio in a Beta release. Check out the Curio Beta release section for how you can get started with Curio. @@ -25,7 +164,6 @@ This release includes a lot of improvements and fixes for indexers, RPC- and ETH - [Length check the array sent to eth_feeHistory RPC](https://github.com/filecoin-project/lotus/pull/11696) - [ETH subscribe tipsets API should only return tipsets that have been executed](https://github.com/filecoin-project/lotus/pull/11858) - [Adjust indexes in event index db to match query patterns](https://github.com/filecoin-project/lotus/pull/111934) -- ## ⭐️ Curio Beta Release ⭐️ @@ -147,7 +285,6 @@ Visit the Curio Official Website insert link - github.com/libp2p/go-libp2p-pubsub (v0.10.0 -> v0.10.1) - github.com/libp2p/go-libp2p (v0.33.2 -> v0.34.1) - ## Others - ci: ci: create gh workflow that runs go checks (#11761) ([filecoin-project/lotus#11761](https://github.com/filecoin-project/lotus/pull/11761)) @@ -172,6 +309,7 @@ Visit the Curio Official Website insert link - chore: Add v13 support to invariants-checker (#11931) ([filecoin-project/lotus#11931](https://github.com/filecoin-project/lotus/pull/11931)) - chore: remove unmaintained bootstrappers (#11983) ([filecoin-project/lotus#11983](https://github.com/filecoin-project/lotus/pull/11983)) - chore: go mod: revert go version change as it breaks Docker build (#12050) ([filecoin-project/lotus#12050](https://github.com/filecoin-project/lotus/pull/12050)) +- chore: pin golanglint-ci to v1.58.2 ([filecoin-project/lotus#12054](https://github.com/filecoin-project/lotus/pull/12054)) ## Contributors diff --git a/Dockerfile b/Dockerfile index 5b77b134a..51a39ed03 100644 --- a/Dockerfile +++ b/Dockerfile @@ -109,7 +109,6 @@ COPY --from=lotus-builder /opt/filecoin/lotus-wallet /usr/local/bin/ COPY --from=lotus-builder /opt/filecoin/lotus-gateway /usr/local/bin/ COPY --from=lotus-builder /opt/filecoin/lotus-miner /usr/local/bin/ COPY --from=lotus-builder /opt/filecoin/lotus-worker /usr/local/bin/ -COPY --from=lotus-builder /opt/filecoin/curio /usr/local/bin/ COPY --from=lotus-builder /opt/filecoin/lotus-stats /usr/local/bin/ COPY --from=lotus-builder /opt/filecoin/lotus-fountain /usr/local/bin/ @@ -118,13 +117,11 @@ RUN mkdir /var/lib/lotus RUN mkdir /var/lib/lotus-miner RUN mkdir /var/lib/lotus-worker RUN mkdir /var/lib/lotus-wallet -RUN mkdir /var/lib/curio RUN chown fc: /var/tmp/filecoin-proof-parameters RUN chown fc: /var/lib/lotus RUN chown fc: /var/lib/lotus-miner RUN chown fc: /var/lib/lotus-worker RUN chown fc: /var/lib/lotus-wallet -RUN chown fc: /var/lib/curio VOLUME /var/tmp/filecoin-proof-parameters @@ -132,7 +129,6 @@ VOLUME /var/lib/lotus VOLUME /var/lib/lotus-miner VOLUME /var/lib/lotus-worker VOLUME /var/lib/lotus-wallet -VOLUME /var/lib/curio EXPOSE 1234 EXPOSE 2345 diff --git a/Makefile b/Makefile index 901c8dc00..1a81ed4f0 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ CLEAN+=build/.update-modules deps: $(BUILD_DEPS) .PHONY: deps -build-devnets: build lotus-seed lotus-shed curio sptool +build-devnets: build lotus-seed lotus-shed .PHONY: build-devnets debug: GOFLAGS+=-tags=debug @@ -97,21 +97,6 @@ lotus-miner: $(BUILD_DEPS) .PHONY: lotus-miner BINS+=lotus-miner -curio: $(BUILD_DEPS) - rm -f curio - $(GOCC) build $(GOFLAGS) -o curio ./cmd/curio -.PHONY: curio -BINS+=curio - -cu2k: GOFLAGS+=-tags=2k -cu2k: curio - -sptool: $(BUILD_DEPS) - rm -f sptool - $(GOCC) build $(GOFLAGS) -o sptool ./cmd/sptool -.PHONY: sptool -BINS+=sptool - lotus-worker: $(BUILD_DEPS) rm -f lotus-worker $(GOCC) build $(GOFLAGS) -o lotus-worker ./cmd/lotus-worker @@ -130,13 +115,13 @@ lotus-gateway: $(BUILD_DEPS) .PHONY: lotus-gateway BINS+=lotus-gateway -build: lotus lotus-miner lotus-worker curio sptool +build: lotus lotus-miner lotus-worker @[[ $$(type -P "lotus") ]] && echo "Caution: you have \ an existing lotus binary in your PATH. This may cause problems if you don't run 'sudo make install'" || true .PHONY: build -install: install-daemon install-miner install-worker install-curio install-sptool +install: install-daemon install-miner install-worker install-daemon: install -C ./lotus /usr/local/bin/lotus @@ -144,12 +129,6 @@ install-daemon: install-miner: install -C ./lotus-miner /usr/local/bin/lotus-miner -install-curio: - install -C ./curio /usr/local/bin/curio - -install-sptool: - install -C ./sptool /usr/local/bin/sptool - install-worker: install -C ./lotus-worker /usr/local/bin/lotus-worker @@ -165,12 +144,6 @@ uninstall-daemon: uninstall-miner: rm -f /usr/local/bin/lotus-miner -uninstall-curio: - rm -f /usr/local/bin/curio - -uninstall-sptool: - rm -f /usr/local/bin/sptool - uninstall-worker: rm -f /usr/local/bin/lotus-worker @@ -272,14 +245,6 @@ install-miner-service: install-miner install-daemon-service @echo "To start the service, run: 'sudo systemctl start lotus-miner'" @echo "To enable the service on startup, run: 'sudo systemctl enable lotus-miner'" -install-curio-service: install-curio install-sptool install-daemon-service - mkdir -p /etc/systemd/system - mkdir -p /var/log/lotus - install -C -m 0644 ./scripts/curio.service /etc/systemd/system/curio.service - systemctl daemon-reload - @echo - @echo "Curio service installed. Don't forget to run 'sudo systemctl start curio' to start it and 'sudo systemctl enable curio' for it to be enabled on startup." - install-main-services: install-miner-service install-all-services: install-main-services @@ -298,12 +263,6 @@ clean-miner-service: rm -f /etc/systemd/system/lotus-miner.service systemctl daemon-reload -clean-curio-service: - -systemctl stop curio - -systemctl disable curio - rm -f /etc/systemd/system/curio.service - systemctl daemon-reload - clean-main-services: clean-daemon-service clean-all-services: clean-main-services @@ -378,7 +337,7 @@ docsgen-md-bin: api-gen actors-gen docsgen-openrpc-bin: api-gen actors-gen $(GOCC) build $(GOFLAGS) -o docgen-openrpc ./api/docgen-openrpc/cmd -docsgen-md: docsgen-md-full docsgen-md-storage docsgen-md-worker docsgen-md-curio +docsgen-md: docsgen-md-full docsgen-md-storage docsgen-md-worker docsgen-md-full: docsgen-md-bin ./docgen-md "api/api_full.go" "FullNode" "api" "./api" > documentation/en/api-v1-unstable-methods.md @@ -387,8 +346,6 @@ docsgen-md-storage: docsgen-md-bin ./docgen-md "api/api_storage.go" "StorageMiner" "api" "./api" > documentation/en/api-v0-methods-miner.md docsgen-md-worker: docsgen-md-bin ./docgen-md "api/api_worker.go" "Worker" "api" "./api" > documentation/en/api-v0-methods-worker.md -docsgen-md-curio: docsgen-md-bin - ./docgen-md "api/api_curio.go" "Curio" "api" "./api" > documentation/en/api-v0-methods-curio.md docsgen-openrpc: docsgen-openrpc-full docsgen-openrpc-storage docsgen-openrpc-worker docsgen-openrpc-gateway @@ -413,47 +370,17 @@ gen: actors-code-gen type-gen cfgdoc-gen docsgen api-gen jen: gen -snap: lotus lotus-miner lotus-worker curio sptool +snap: lotus lotus-miner lotus-worker snapcraft # snapcraft upload ./lotus_*.snap # separate from gen because it needs binaries -docsgen-cli: lotus lotus-miner lotus-worker curio sptool +docsgen-cli: lotus lotus-miner lotus-worker python3 ./scripts/generate-lotus-cli.py ./lotus config default > documentation/en/default-lotus-config.toml ./lotus-miner config default > documentation/en/default-lotus-miner-config.toml - ./curio config default > documentation/en/default-curio-config.toml .PHONY: docsgen-cli print-%: @echo $*=$($*) -### Curio devnet images -curio_docker_user?=curio -curio_base_image=$(curio_docker_user)/curio-all-in-one:latest-debug -ffi_from_source?=0 - -curio-devnet: lotus lotus-miner lotus-shed lotus-seed curio sptool -.PHONY: curio-devnet - -curio_docker_build_cmd=docker build --build-arg CURIO_TEST_IMAGE=$(curio_base_image) \ - --build-arg FFI_BUILD_FROM_SOURCE=$(ffi_from_source) $(docker_args) - -docker/curio-all-in-one: - $(curio_docker_build_cmd) -f Dockerfile.curio --target curio-all-in-one \ - -t $(curio_base_image) --build-arg GOFLAGS=-tags=debug . -.PHONY: docker/curio-all-in-one - -docker/%: - cd curiosrc/docker/$* && DOCKER_BUILDKIT=1 $(curio_docker_build_cmd) -t $(curio_docker_user)/$*-dev:dev \ - --build-arg BUILD_VERSION=dev . - -docker/curio-devnet: $(lotus_build_cmd) \ - docker/curio-all-in-one docker/lotus docker/lotus-miner docker/curio docker/yugabyte -.PHONY: docker/curio-devnet - -curio-devnet/up: - rm -rf ./curiosrc/docker/data && docker compose -f ./curiosrc/docker/docker-compose.yaml up -d - -curio-devnet/down: - docker compose -f ./curiosrc/docker/docker-compose.yaml down --rmi=local && sleep 2 && rm -rf ./curiosrc/docker/data diff --git a/api/api_curio.go b/api/api_curio.go deleted file mode 100644 index 0eceb484f..000000000 --- a/api/api_curio.go +++ /dev/null @@ -1,34 +0,0 @@ -package api - -import ( - "context" - "net/http" - "net/url" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/storage/sealer/fsutil" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -type Curio interface { - Version(context.Context) (Version, error) //perm:admin - - AllocatePieceToSector(ctx context.Context, maddr address.Address, piece PieceDealInfo, rawSize int64, source url.URL, header http.Header) (SectorOffset, error) //perm:write - - StorageInit(ctx context.Context, path string, opts storiface.LocalStorageMeta) error //perm:admin - StorageAddLocal(ctx context.Context, path string) error //perm:admin - StorageDetachLocal(ctx context.Context, path string) error //perm:admin - StorageList(ctx context.Context) (map[storiface.ID][]storiface.Decl, error) //perm:admin - StorageLocal(ctx context.Context) (map[storiface.ID]string, error) //perm:admin - StorageStat(ctx context.Context, id storiface.ID) (fsutil.FsStat, error) //perm:admin - StorageInfo(context.Context, storiface.ID) (storiface.StorageInfo, error) //perm:admin - StorageFindSector(ctx context.Context, sector abi.SectorID, ft storiface.SectorFileType, ssize abi.SectorSize, allowFetch bool) ([]storiface.SectorStorageInfo, error) //perm:admin - - LogList(ctx context.Context) ([]string, error) //perm:read - LogSetLevel(ctx context.Context, subsystem, level string) error //perm:admin - - // Trigger shutdown - Shutdown(context.Context) error //perm:admin -} diff --git a/api/api_full.go b/api/api_full.go index bbfcae0a2..b82b172eb 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -9,13 +9,9 @@ import ( "github.com/google/uuid" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - "github.com/libp2p/go-libp2p/core/peer" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" @@ -34,7 +30,6 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" "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 @@ -348,74 +343,6 @@ type FullNode interface { // Other - // MethodGroup: Client - // The Client methods all have to do with interacting with the storage and - // retrieval markets as a client - - // 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 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. - ClientStatelessDeal(ctx context.Context, params *StartDealParams) (*cid.Cid, error) //perm:write - // ClientGetDealInfo returns the latest information about a given deal. - ClientGetDealInfo(context.Context, cid.Cid) (*DealInfo, error) //perm:read - // ClientListDeals returns information about the deals made by the local client. - ClientListDeals(ctx context.Context) ([]DealInfo, error) //perm:write - // ClientGetDealUpdates returns the status of updated deals - ClientGetDealUpdates(ctx context.Context) (<-chan DealInfo, error) //perm:write - // ClientGetDealStatus returns status given a code - ClientGetDealStatus(ctx context.Context, statusCode uint64) (string, error) //perm:read - // ClientHasLocal indicates whether a certain CID is locally stored. - ClientHasLocal(ctx context.Context, root cid.Cid) (bool, error) //perm:write - // ClientFindData identifies peers that have a certain file, and returns QueryOffers (one per peer). - ClientFindData(ctx context.Context, root cid.Cid, piece *cid.Cid) ([]QueryOffer, error) //perm:read - // ClientMinerQueryOffer returns a QueryOffer for the specific miner and file. - ClientMinerQueryOffer(ctx context.Context, miner address.Address, root cid.Cid, piece *cid.Cid) (QueryOffer, error) //perm:read - // ClientRetrieve initiates the retrieval of a file, as specified in the order. - ClientRetrieve(ctx context.Context, params RetrievalOrder) (*RestrievalRes, error) //perm:admin - // ClientRetrieveWait waits for retrieval to be complete - ClientRetrieveWait(ctx context.Context, deal retrievalmarket.DealID) error //perm:admin - // ClientExport exports a file stored in the local filestore to a system file - ClientExport(ctx context.Context, exportRef ExportRef, fileRef FileRef) error //perm:admin - // ClientListRetrievals returns information about retrievals made by the local client - ClientListRetrievals(ctx context.Context) ([]RetrievalInfo, error) //perm:write - // ClientGetRetrievalUpdates returns status of updated retrieval deals - ClientGetRetrievalUpdates(ctx context.Context) (<-chan RetrievalInfo, error) //perm:write - // ClientQueryAsk returns a signed StorageAsk from the specified miner. - ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*StorageAsk, error) //perm:read - // ClientCalcCommP calculates the CommP and data size of the specified CID - ClientDealPieceCID(ctx context.Context, root cid.Cid) (DataCIDSize, error) //perm:read - // ClientCalcCommP calculates the CommP for a specified file - ClientCalcCommP(ctx context.Context, inpath string) (*CommPRet, error) //perm:write - // ClientGenCar generates a CAR file for the specified file. - ClientGenCar(ctx context.Context, ref FileRef, outpath string) error //perm:write - // ClientDealSize calculates real deal data size - ClientDealSize(ctx context.Context, root cid.Cid) (DataSize, error) //perm:read - // ClientListTransfers returns the status of all ongoing transfers of data - ClientListDataTransfers(ctx context.Context) ([]DataTransferChannel, error) //perm:write - ClientDataTransferUpdates(ctx context.Context) (<-chan DataTransferChannel, error) //perm:write - // ClientRestartDataTransfer attempts to restart a data transfer with the given transfer ID and other peer - ClientRestartDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error //perm:write - // ClientCancelDataTransfer cancels a data transfer with the given transfer ID and other peer - ClientCancelDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error //perm:write - // ClientRetrieveTryRestartInsufficientFunds attempts to restart stalled retrievals on a given payment channel - // which are stuck due to insufficient funds - ClientRetrieveTryRestartInsufficientFunds(ctx context.Context, paymentChannel address.Address) error //perm:write - - // ClientCancelRetrievalDeal cancels an ongoing retrieval deal based on DealID - ClientCancelRetrievalDeal(ctx context.Context, dealid retrievalmarket.DealID) error //perm:write - - // ClientUnimport removes references to the specified file from filestore - // ClientUnimport(path string) - - // ClientListImports lists imported files and their root CIDs - ClientListImports(ctx context.Context) ([]Import, error) //perm:write - - // ClientListAsks() []Ask - // MethodGroup: State // The State methods are used to query, inspect, and interact with chain state. // Most methods take a TipSetKey as a parameter. The state looked up is the parent state of the tipset. @@ -898,6 +825,9 @@ type FullNode interface { // Replays all transactions in a block returning the requested traces for each transaction EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) //perm:read + // Implmements OpenEthereum-compatible API method trace_transaction + EthTraceTransaction(ctx context.Context, txHash string) ([]*ethtypes.EthTraceTransaction, error) //perm:read + // CreateBackup creates node backup onder the specified file name. The // method requires that the lotus daemon is running with the // LOTUS_BACKUP_BASE_PATH environment variable set to some path, and that @@ -938,17 +868,6 @@ type EthSubscriber interface { EthSubscription(ctx context.Context, r jsonrpc.RawParams) error // rpc_method:eth_subscription notify:true } -type StorageAsk struct { - Response *storagemarket.StorageAsk - - DealProtocols []string -} - -type FileRef struct { - Path string - IsCAR bool -} - type MinerSectors struct { // Live sectors that should be proven. Live uint64 @@ -958,55 +877,6 @@ type MinerSectors struct { Faulty uint64 } -type ImportRes struct { - Root cid.Cid - ImportID imports.ID -} - -type Import struct { - Key imports.ID - Err 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 { - ProposalCid cid.Cid - State storagemarket.StorageDealStatus - Message string // more information about deal state, particularly errors - DealStages *storagemarket.DealStages - Provider address.Address - - DataRef *storagemarket.DataRef - PieceCID cid.Cid - Size uint64 - - PricePerEpoch types.BigInt - Duration uint64 - - DealID abi.DealID - - CreationTime time.Time - Verified bool - - TransferChannelID *datatransfer.ChannelID - DataTransfer *DataTransferChannel -} - type MsgLookup struct { Message cid.Cid // Can be different than requested, in case it was replaced, but only gas values changed Receipt types.MessageReceipt @@ -1128,51 +998,21 @@ type MinerPower struct { HasMinPower bool } -type QueryOffer struct { - Err string - - Root cid.Cid - Piece *cid.Cid - - Size uint64 - MinPrice types.BigInt - UnsealPrice types.BigInt - PricePerByte abi.TokenAmount - PaymentInterval uint64 - PaymentIntervalIncrease uint64 - Miner address.Address - MinerPeer retrievalmarket.RetrievalPeer -} - -func (o *QueryOffer) Order(client address.Address) RetrievalOrder { - return RetrievalOrder{ - Root: o.Root, - Piece: o.Piece, - Size: o.Size, - Total: o.MinPrice, - UnsealPrice: o.UnsealPrice, - PaymentInterval: o.PaymentInterval, - PaymentIntervalIncrease: o.PaymentIntervalIncrease, - Client: client, - - Miner: o.Miner, - MinerPeer: &o.MinerPeer, - } -} - type MarketBalance struct { Escrow big.Int Locked big.Int } type MarketDealState struct { - SectorStartEpoch abi.ChainEpoch // -1 if not yet included in proven sector - LastUpdatedEpoch abi.ChainEpoch // -1 if deal state never updated - SlashEpoch abi.ChainEpoch // -1 if deal never slashed + SectorNumber abi.SectorNumber // 0 if not yet included in proven sector (0 is also a valid sector number). + SectorStartEpoch abi.ChainEpoch // -1 if not yet included in proven sector + LastUpdatedEpoch abi.ChainEpoch // -1 if deal state never updated + SlashEpoch abi.ChainEpoch // -1 if deal never slashed } func MakeDealState(mds market.DealState) MarketDealState { return MarketDealState{ + SectorNumber: mds.SectorNumber(), SectorStartEpoch: mds.SectorStartEpoch(), LastUpdatedEpoch: mds.LastUpdatedEpoch(), SlashEpoch: mds.SlashEpoch(), @@ -1183,6 +1023,10 @@ type mstate struct { s MarketDealState } +func (m mstate) SectorNumber() abi.SectorNumber { + return m.s.SectorNumber +} + func (m mstate) SectorStartEpoch() abi.ChainEpoch { return m.s.SectorStartEpoch } @@ -1208,25 +1052,6 @@ type MarketDeal struct { State MarketDealState } -type RetrievalOrder struct { - Root cid.Cid - Piece *cid.Cid - DataSelector *Selector - - // todo: Size/Total are only used for calculating price per byte; we should let users just pass that - Size uint64 - Total types.BigInt - - UnsealPrice types.BigInt - PaymentInterval uint64 - PaymentIntervalIncrease uint64 - Client address.Address - Miner address.Address - MinerPeer *retrievalmarket.RetrievalPeer - - RemoteStore *RemoteStoreID `json:"RemoteStore,omitempty"` -} - type RemoteStoreID = uuid.UUID type InvocResult struct { @@ -1244,34 +1069,6 @@ type MethodCall struct { Error string } -type StartDealParams struct { - Data *storagemarket.DataRef - Wallet address.Address - Miner address.Address - EpochPrice types.BigInt - MinBlocksDuration uint64 - ProviderCollateral big.Int - DealStartEpoch abi.ChainEpoch - FastRetrieval bool - VerifiedDeal bool -} - -func (s *StartDealParams) UnmarshalJSON(raw []byte) (err error) { - type sdpAlias StartDealParams - - sdp := sdpAlias{ - FastRetrieval: true, - } - - if err := json.Unmarshal(raw, &sdp); err != nil { - return err - } - - *s = StartDealParams(sdp) - - return nil -} - type IpldObject struct { Cid cid.Cid Obj interface{} diff --git a/api/api_gateway.go b/api/api_gateway.go index 62bff64cf..a9f0e3962 100644 --- a/api/api_gateway.go +++ b/api/api_gateway.go @@ -132,6 +132,7 @@ type Gateway interface { Web3ClientVersion(ctx context.Context) (string, error) EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error) EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) + EthTraceTransaction(ctx context.Context, txHash string) ([]*ethtypes.EthTraceTransaction, error) GetActorEventsRaw(ctx context.Context, filter *types.ActorEventFilter) ([]*types.ActorEvent, error) SubscribeActorEventsRaw(ctx context.Context, filter *types.ActorEventFilter) (<-chan *types.ActorEvent, error) diff --git a/api/api_storage.go b/api/api_storage.go index 410fa2af1..ae1c7ae36 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -7,14 +7,9 @@ import ( "github.com/google/uuid" "github.com/ipfs/go-cid" - "github.com/libp2p/go-libp2p/core/peer" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - "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-jsonrpc/auth" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" @@ -215,110 +210,12 @@ type StorageMiner interface { StorageDetachLocal(ctx context.Context, path string) error //perm:admin StorageRedeclareLocal(ctx context.Context, id *storiface.ID, dropMissing bool) error //perm:admin - MarketImportDealData(ctx context.Context, propcid cid.Cid, path string) error //perm:write - MarketListDeals(ctx context.Context) ([]*MarketDeal, error) //perm:read - - // MarketListRetrievalDeals is deprecated, returns empty list - MarketListRetrievalDeals(ctx context.Context) ([]struct{}, error) //perm:read - MarketGetDealUpdates(ctx context.Context) (<-chan storagemarket.MinerDeal, error) //perm:read - MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error) //perm:read - MarketSetAsk(ctx context.Context, price types.BigInt, verifiedPrice types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error //perm:admin - MarketGetAsk(ctx context.Context) (*storagemarket.SignedStorageAsk, error) //perm:read - MarketSetRetrievalAsk(ctx context.Context, rask *retrievalmarket.Ask) error //perm:admin - MarketGetRetrievalAsk(ctx context.Context) (*retrievalmarket.Ask, error) //perm:read - MarketListDataTransfers(ctx context.Context) ([]DataTransferChannel, error) //perm:write - MarketDataTransferUpdates(ctx context.Context) (<-chan DataTransferChannel, error) //perm:write - // MarketDataTransferDiagnostics generates debugging information about current data transfers over graphsync - MarketDataTransferDiagnostics(ctx context.Context, p peer.ID) (*TransferDiagnostics, error) //perm:write - // MarketRestartDataTransfer attempts to restart a data transfer with the given transfer ID and other peer - MarketRestartDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error //perm:write - // MarketCancelDataTransfer cancels a data transfer with the given transfer ID and other peer - MarketCancelDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error //perm:write - MarketPendingDeals(ctx context.Context) (PendingDealInfo, error) //perm:write - MarketPublishPendingDeals(ctx context.Context) error //perm:admin - MarketRetryPublishDeal(ctx context.Context, propcid cid.Cid) 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 - - // DagstoreRegisterShard registers a shard manually with dagstore with given pieceCID - DagstoreRegisterShard(ctx context.Context, key string) error //perm:admin - - // IndexerAnnounceDeal informs indexer nodes that a new deal was received, - // so they can download its index - IndexerAnnounceDeal(ctx context.Context, proposalCid cid.Cid) error //perm:admin - - // IndexerAnnounceAllDeals informs the indexer nodes aboutall active deals. - IndexerAnnounceAllDeals(ctx context.Context) error //perm:admin - - // DagstoreLookupPieces returns information about shards that contain the given CID. - DagstoreLookupPieces(ctx context.Context, cid cid.Cid) ([]DagstoreShardInfo, error) //perm:admin + MarketListDeals(ctx context.Context) ([]*MarketDeal, error) //perm:read // RuntimeSubsystems returns the subsystems that are enabled // in this instance. RuntimeSubsystems(ctx context.Context) (MinerSubsystems, error) //perm:read - DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error //perm:admin - DealsList(ctx context.Context) ([]*MarketDeal, error) //perm:admin - DealsConsiderOnlineStorageDeals(context.Context) (bool, error) //perm:admin - DealsSetConsiderOnlineStorageDeals(context.Context, bool) error //perm:admin - DealsConsiderOnlineRetrievalDeals(context.Context) (bool, error) //perm:admin - DealsSetConsiderOnlineRetrievalDeals(context.Context, bool) error //perm:admin - DealsPieceCidBlocklist(context.Context) ([]cid.Cid, error) //perm:admin - DealsSetPieceCidBlocklist(context.Context, []cid.Cid) error //perm:admin - DealsConsiderOfflineStorageDeals(context.Context) (bool, error) //perm:admin - DealsSetConsiderOfflineStorageDeals(context.Context, bool) error //perm:admin - DealsConsiderOfflineRetrievalDeals(context.Context) (bool, error) //perm:admin - DealsSetConsiderOfflineRetrievalDeals(context.Context, bool) error //perm:admin - DealsConsiderVerifiedStorageDeals(context.Context) (bool, error) //perm:admin - DealsSetConsiderVerifiedStorageDeals(context.Context, bool) error //perm:admin - DealsConsiderUnverifiedStorageDeals(context.Context) (bool, error) //perm:admin - DealsSetConsiderUnverifiedStorageDeals(context.Context, bool) error //perm:admin - - PiecesListPieces(ctx context.Context) ([]cid.Cid, error) //perm:read - PiecesListCidInfos(ctx context.Context) ([]cid.Cid, error) //perm:read - PiecesGetPieceInfo(ctx context.Context, pieceCid cid.Cid) (*piecestore.PieceInfo, error) //perm:read - PiecesGetCIDInfo(ctx context.Context, payloadCid cid.Cid) (*piecestore.CIDInfo, error) //perm:read - // CreateBackup creates node backup onder the specified file name. The // method requires that the lotus-miner is running with the // LOTUS_BACKUP_BASE_PATH environment variable set to some path, and that @@ -471,37 +368,6 @@ type SectorOffset struct { Offset abi.PaddedPieceSize } -// 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 -} - type NumAssignerMeta struct { Reserved bitfield.BitField Allocated bitfield.BitField diff --git a/api/client/client.go b/api/client/client.go index 4a8ff9272..8b159c5b1 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -15,16 +15,6 @@ import ( "github.com/filecoin-project/lotus/lib/rpcenc" ) -// NewCurioRpc creates a new http jsonrpc client. -func NewCurioRpc(ctx context.Context, addr string, requestHeader http.Header) (api.Curio, jsonrpc.ClientCloser, error) { - var res v1api.CurioStruct - - closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", - api.GetInternalStructs(&res), requestHeader, jsonrpc.WithErrors(api.RPCErrors)) - - return &res, closer, err -} - // NewCommonRPCV0 creates a new http jsonrpc client. func NewCommonRPCV0(ctx context.Context, addr string, requestHeader http.Header) (api.CommonNet, jsonrpc.ClientCloser, error) { var res v0api.CommonNetStruct diff --git a/api/docgen-openrpc/openrpc.go b/api/docgen-openrpc/openrpc.go index c9504ba89..52e37aec6 100644 --- a/api/docgen-openrpc/openrpc.go +++ b/api/docgen-openrpc/openrpc.go @@ -106,7 +106,7 @@ func NewLotusOpenRPCDocument(Comments, GroupDocs map[string]string) *go_openrpc_ title := "Lotus RPC API" info.Title = (*meta_schema.InfoObjectProperties)(&title) - version := build.BuildVersion + version := build.NodeBuildVersion info.Version = (*meta_schema.InfoObjectVersion)(&version) return info }, diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 2d34a0903..eace7a9a1 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -16,7 +16,6 @@ import ( "github.com/google/uuid" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - "github.com/ipfs/go-graphsync" textselector "github.com/ipld/go-ipld-selector-text-lite" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/metrics" @@ -27,9 +26,6 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - "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-state-types/abi" "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" @@ -44,7 +40,6 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/node/modules/dtypes" - "github.com/filecoin-project/lotus/node/repo/imports" sealing "github.com/filecoin-project/lotus/storage/pipeline" "github.com/filecoin-project/lotus/storage/sealer/sealtasks" "github.com/filecoin-project/lotus/storage/sealer/storiface" @@ -96,10 +91,8 @@ func init() { addExample(pid) addExample(&pid) - storeIDExample := imports.ID(50) textSelExample := textselector.Expression("Links/21/Hash/Links/42/Hash") apiSelExample := api.Selector("Links/21/Hash/Links/42/Hash") - clientEvent := retrievalmarket.ClientEventDealAccepted block := blocks.Block(&blocks.BasicBlock{}) ExampleValues[reflect.TypeOf(&block).Elem()] = block @@ -130,15 +123,7 @@ func init() { addExample(api.FullAPIVersion1) addExample(api.PCHInbound) addExample(time.Minute) - addExample(graphsync.NewRequestID()) - addExample(datatransfer.TransferID(3)) - addExample(datatransfer.Ongoing) - addExample(storeIDExample) - addExample(&storeIDExample) - addExample(clientEvent) - addExample(&clientEvent) - addExample(retrievalmarket.ClientEventDealAccepted) - addExample(retrievalmarket.DealStatusNew) + addExample(&textSelExample) addExample(&apiSelExample) addExample(network.ReachabilityPublic) @@ -206,10 +191,9 @@ func init() { ExampleValues[reflect.TypeOf(struct{ A multiaddr.Multiaddr }{}).Field(0).Type] = maddr // miner specific - addExample(filestore.Path(".lotusminer/fstmp123")) + si := uint64(12) addExample(&si) - addExample(retrievalmarket.DealID(5)) addExample(map[string]cid.Cid{}) addExample(map[string][]api.SealedRef{ "98000": { @@ -313,17 +297,8 @@ func init() { api.SubsystemMining, api.SubsystemSealing, api.SubsystemSectorStorage, - api.SubsystemMarkets, - }) - addExample(api.DagstoreShardResult{ - Key: "baga6ea4seaqecmtz7iak33dsfshi627abz4i4665dfuzr3qfs4bmad6dx3iigdq", - Error: "", - }) - addExample(api.DagstoreShardInfo{ - Key: "baga6ea4seaqecmtz7iak33dsfshi627abz4i4665dfuzr3qfs4bmad6dx3iigdq", - State: "ShardStateAvailable", - Error: "", }) + addExample(storiface.ResourceTable) addExample(network.ScopeStat{ Memory: 123, @@ -456,10 +431,6 @@ func GetAPIType(name, pkg string) (i interface{}, t reflect.Type, permStruct []r i = &api.GatewayStruct{} t = reflect.TypeOf(new(struct{ api.Gateway })).Elem() permStruct = append(permStruct, reflect.TypeOf(api.GatewayStruct{}.Internal)) - case "Curio": - i = &api.CurioStruct{} - t = reflect.TypeOf(new(struct{ api.Curio })).Elem() - permStruct = append(permStruct, reflect.TypeOf(api.CurioStruct{}.Internal)) default: panic("unknown type") } diff --git a/api/eth_aliases.go b/api/eth_aliases.go index eb0c51005..6a7901cf9 100644 --- a/api/eth_aliases.go +++ b/api/eth_aliases.go @@ -42,6 +42,7 @@ func CreateEthRPCAliases(as apitypes.Aliaser) { as.AliasMethod("trace_block", "Filecoin.EthTraceBlock") as.AliasMethod("trace_replayBlockTransactions", "Filecoin.EthTraceReplayBlockTransactions") + as.AliasMethod("trace_transaction", "Filecoin.EthTraceTransaction") as.AliasMethod("net_version", "Filecoin.NetVersion") as.AliasMethod("net_listening", "Filecoin.NetListening") diff --git a/api/miner_subsystems.go b/api/miner_subsystems.go index a77de7e3c..2f17ad02f 100644 --- a/api/miner_subsystems.go +++ b/api/miner_subsystems.go @@ -13,9 +13,6 @@ const ( // SubsystemUnknown is a placeholder for the zero value. It should never // be used. SubsystemUnknown MinerSubsystem = iota - // SubsystemMarkets signifies the storage and retrieval - // deal-making subsystem. - SubsystemMarkets // SubsystemMining signifies the mining subsystem. SubsystemMining // SubsystemSealing signifies the sealing subsystem. @@ -26,7 +23,6 @@ const ( var MinerSubsystemToString = map[MinerSubsystem]string{ SubsystemUnknown: "Unknown", - SubsystemMarkets: "Markets", SubsystemMining: "Mining", SubsystemSealing: "Sealing", SubsystemSectorStorage: "SectorStorage", @@ -34,7 +30,6 @@ var MinerSubsystemToString = map[MinerSubsystem]string{ var MinerSubsystemToID = map[string]MinerSubsystem{ "Unknown": SubsystemUnknown, - "Markets": SubsystemMarkets, "Mining": SubsystemMining, "Sealing": SubsystemSealing, "SectorStorage": SubsystemSectorStorage, diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 9960faeff..b15eea341 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -21,8 +21,6 @@ import ( address "github.com/filecoin-project/go-address" bitfield "github.com/filecoin-project/go-bitfield" - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - retrievalmarket "github.com/filecoin-project/go-fil-markets/retrievalmarket" jsonrpc "github.com/filecoin-project/go-jsonrpc" auth "github.com/filecoin-project/go-jsonrpc/auth" abi "github.com/filecoin-project/go-state-types/abi" @@ -42,7 +40,6 @@ import ( ethtypes "github.com/filecoin-project/lotus/chain/types/ethtypes" alerting "github.com/filecoin-project/lotus/journal/alerting" dtypes "github.com/filecoin-project/lotus/node/modules/dtypes" - imports "github.com/filecoin-project/lotus/node/repo/imports" ) // MockFullNode is a mock of FullNode interface. @@ -511,418 +508,6 @@ func (mr *MockFullNodeMockRecorder) ChainTipSetWeight(arg0, arg1 interface{}) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainTipSetWeight", reflect.TypeOf((*MockFullNode)(nil).ChainTipSetWeight), arg0, arg1) } -// ClientCalcCommP mocks base method. -func (m *MockFullNode) ClientCalcCommP(arg0 context.Context, arg1 string) (*api.CommPRet, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientCalcCommP", arg0, arg1) - ret0, _ := ret[0].(*api.CommPRet) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientCalcCommP indicates an expected call of ClientCalcCommP. -func (mr *MockFullNodeMockRecorder) ClientCalcCommP(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientCalcCommP", reflect.TypeOf((*MockFullNode)(nil).ClientCalcCommP), arg0, arg1) -} - -// ClientCancelDataTransfer mocks base method. -func (m *MockFullNode) ClientCancelDataTransfer(arg0 context.Context, arg1 datatransfer.TransferID, arg2 peer.ID, arg3 bool) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientCancelDataTransfer", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(error) - return ret0 -} - -// ClientCancelDataTransfer indicates an expected call of ClientCancelDataTransfer. -func (mr *MockFullNodeMockRecorder) ClientCancelDataTransfer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientCancelDataTransfer", reflect.TypeOf((*MockFullNode)(nil).ClientCancelDataTransfer), arg0, arg1, arg2, arg3) -} - -// ClientCancelRetrievalDeal mocks base method. -func (m *MockFullNode) ClientCancelRetrievalDeal(arg0 context.Context, arg1 retrievalmarket.DealID) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientCancelRetrievalDeal", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// ClientCancelRetrievalDeal indicates an expected call of ClientCancelRetrievalDeal. -func (mr *MockFullNodeMockRecorder) ClientCancelRetrievalDeal(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientCancelRetrievalDeal", reflect.TypeOf((*MockFullNode)(nil).ClientCancelRetrievalDeal), arg0, arg1) -} - -// ClientDataTransferUpdates mocks base method. -func (m *MockFullNode) ClientDataTransferUpdates(arg0 context.Context) (<-chan api.DataTransferChannel, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientDataTransferUpdates", arg0) - ret0, _ := ret[0].(<-chan api.DataTransferChannel) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientDataTransferUpdates indicates an expected call of ClientDataTransferUpdates. -func (mr *MockFullNodeMockRecorder) ClientDataTransferUpdates(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientDataTransferUpdates", reflect.TypeOf((*MockFullNode)(nil).ClientDataTransferUpdates), arg0) -} - -// ClientDealPieceCID mocks base method. -func (m *MockFullNode) ClientDealPieceCID(arg0 context.Context, arg1 cid.Cid) (api.DataCIDSize, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientDealPieceCID", arg0, arg1) - ret0, _ := ret[0].(api.DataCIDSize) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientDealPieceCID indicates an expected call of ClientDealPieceCID. -func (mr *MockFullNodeMockRecorder) ClientDealPieceCID(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientDealPieceCID", reflect.TypeOf((*MockFullNode)(nil).ClientDealPieceCID), arg0, arg1) -} - -// ClientDealSize mocks base method. -func (m *MockFullNode) ClientDealSize(arg0 context.Context, arg1 cid.Cid) (api.DataSize, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientDealSize", arg0, arg1) - ret0, _ := ret[0].(api.DataSize) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientDealSize indicates an expected call of ClientDealSize. -func (mr *MockFullNodeMockRecorder) ClientDealSize(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientDealSize", reflect.TypeOf((*MockFullNode)(nil).ClientDealSize), arg0, arg1) -} - -// ClientExport mocks base method. -func (m *MockFullNode) ClientExport(arg0 context.Context, arg1 api.ExportRef, arg2 api.FileRef) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientExport", arg0, arg1, arg2) - ret0, _ := ret[0].(error) - return ret0 -} - -// ClientExport indicates an expected call of ClientExport. -func (mr *MockFullNodeMockRecorder) ClientExport(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientExport", reflect.TypeOf((*MockFullNode)(nil).ClientExport), arg0, arg1, arg2) -} - -// ClientFindData mocks base method. -func (m *MockFullNode) ClientFindData(arg0 context.Context, arg1 cid.Cid, arg2 *cid.Cid) ([]api.QueryOffer, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientFindData", arg0, arg1, arg2) - ret0, _ := ret[0].([]api.QueryOffer) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientFindData indicates an expected call of ClientFindData. -func (mr *MockFullNodeMockRecorder) ClientFindData(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientFindData", reflect.TypeOf((*MockFullNode)(nil).ClientFindData), arg0, arg1, arg2) -} - -// ClientGenCar mocks base method. -func (m *MockFullNode) ClientGenCar(arg0 context.Context, arg1 api.FileRef, arg2 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientGenCar", arg0, arg1, arg2) - ret0, _ := ret[0].(error) - return ret0 -} - -// ClientGenCar indicates an expected call of ClientGenCar. -func (mr *MockFullNodeMockRecorder) ClientGenCar(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGenCar", reflect.TypeOf((*MockFullNode)(nil).ClientGenCar), arg0, arg1, arg2) -} - -// ClientGetDealInfo mocks base method. -func (m *MockFullNode) ClientGetDealInfo(arg0 context.Context, arg1 cid.Cid) (*api.DealInfo, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientGetDealInfo", arg0, arg1) - ret0, _ := ret[0].(*api.DealInfo) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientGetDealInfo indicates an expected call of ClientGetDealInfo. -func (mr *MockFullNodeMockRecorder) ClientGetDealInfo(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetDealInfo", reflect.TypeOf((*MockFullNode)(nil).ClientGetDealInfo), arg0, arg1) -} - -// ClientGetDealStatus mocks base method. -func (m *MockFullNode) ClientGetDealStatus(arg0 context.Context, arg1 uint64) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientGetDealStatus", arg0, arg1) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientGetDealStatus indicates an expected call of ClientGetDealStatus. -func (mr *MockFullNodeMockRecorder) ClientGetDealStatus(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetDealStatus", reflect.TypeOf((*MockFullNode)(nil).ClientGetDealStatus), arg0, arg1) -} - -// ClientGetDealUpdates mocks base method. -func (m *MockFullNode) ClientGetDealUpdates(arg0 context.Context) (<-chan api.DealInfo, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientGetDealUpdates", arg0) - ret0, _ := ret[0].(<-chan api.DealInfo) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientGetDealUpdates indicates an expected call of ClientGetDealUpdates. -func (mr *MockFullNodeMockRecorder) ClientGetDealUpdates(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetDealUpdates", reflect.TypeOf((*MockFullNode)(nil).ClientGetDealUpdates), arg0) -} - -// ClientGetRetrievalUpdates mocks base method. -func (m *MockFullNode) ClientGetRetrievalUpdates(arg0 context.Context) (<-chan api.RetrievalInfo, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientGetRetrievalUpdates", arg0) - ret0, _ := ret[0].(<-chan api.RetrievalInfo) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientGetRetrievalUpdates indicates an expected call of ClientGetRetrievalUpdates. -func (mr *MockFullNodeMockRecorder) ClientGetRetrievalUpdates(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetRetrievalUpdates", reflect.TypeOf((*MockFullNode)(nil).ClientGetRetrievalUpdates), arg0) -} - -// ClientHasLocal mocks base method. -func (m *MockFullNode) ClientHasLocal(arg0 context.Context, arg1 cid.Cid) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientHasLocal", arg0, arg1) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientHasLocal indicates an expected call of ClientHasLocal. -func (mr *MockFullNodeMockRecorder) ClientHasLocal(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientHasLocal", reflect.TypeOf((*MockFullNode)(nil).ClientHasLocal), arg0, arg1) -} - -// ClientImport mocks base method. -func (m *MockFullNode) ClientImport(arg0 context.Context, arg1 api.FileRef) (*api.ImportRes, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientImport", arg0, arg1) - ret0, _ := ret[0].(*api.ImportRes) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientImport indicates an expected call of ClientImport. -func (mr *MockFullNodeMockRecorder) ClientImport(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientImport", reflect.TypeOf((*MockFullNode)(nil).ClientImport), arg0, arg1) -} - -// ClientListDataTransfers mocks base method. -func (m *MockFullNode) ClientListDataTransfers(arg0 context.Context) ([]api.DataTransferChannel, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientListDataTransfers", arg0) - ret0, _ := ret[0].([]api.DataTransferChannel) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientListDataTransfers indicates an expected call of ClientListDataTransfers. -func (mr *MockFullNodeMockRecorder) ClientListDataTransfers(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListDataTransfers", reflect.TypeOf((*MockFullNode)(nil).ClientListDataTransfers), arg0) -} - -// ClientListDeals mocks base method. -func (m *MockFullNode) ClientListDeals(arg0 context.Context) ([]api.DealInfo, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientListDeals", arg0) - ret0, _ := ret[0].([]api.DealInfo) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientListDeals indicates an expected call of ClientListDeals. -func (mr *MockFullNodeMockRecorder) ClientListDeals(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListDeals", reflect.TypeOf((*MockFullNode)(nil).ClientListDeals), arg0) -} - -// ClientListImports mocks base method. -func (m *MockFullNode) ClientListImports(arg0 context.Context) ([]api.Import, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientListImports", arg0) - ret0, _ := ret[0].([]api.Import) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientListImports indicates an expected call of ClientListImports. -func (mr *MockFullNodeMockRecorder) ClientListImports(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListImports", reflect.TypeOf((*MockFullNode)(nil).ClientListImports), arg0) -} - -// ClientListRetrievals mocks base method. -func (m *MockFullNode) ClientListRetrievals(arg0 context.Context) ([]api.RetrievalInfo, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientListRetrievals", arg0) - ret0, _ := ret[0].([]api.RetrievalInfo) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientListRetrievals indicates an expected call of ClientListRetrievals. -func (mr *MockFullNodeMockRecorder) ClientListRetrievals(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListRetrievals", reflect.TypeOf((*MockFullNode)(nil).ClientListRetrievals), arg0) -} - -// ClientMinerQueryOffer mocks base method. -func (m *MockFullNode) ClientMinerQueryOffer(arg0 context.Context, arg1 address.Address, arg2 cid.Cid, arg3 *cid.Cid) (api.QueryOffer, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientMinerQueryOffer", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(api.QueryOffer) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientMinerQueryOffer indicates an expected call of ClientMinerQueryOffer. -func (mr *MockFullNodeMockRecorder) ClientMinerQueryOffer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientMinerQueryOffer", reflect.TypeOf((*MockFullNode)(nil).ClientMinerQueryOffer), arg0, arg1, arg2, arg3) -} - -// ClientQueryAsk mocks base method. -func (m *MockFullNode) ClientQueryAsk(arg0 context.Context, arg1 peer.ID, arg2 address.Address) (*api.StorageAsk, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientQueryAsk", arg0, arg1, arg2) - ret0, _ := ret[0].(*api.StorageAsk) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientQueryAsk indicates an expected call of ClientQueryAsk. -func (mr *MockFullNodeMockRecorder) ClientQueryAsk(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientQueryAsk", reflect.TypeOf((*MockFullNode)(nil).ClientQueryAsk), arg0, arg1, arg2) -} - -// ClientRemoveImport mocks base method. -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) - return ret0 -} - -// ClientRemoveImport indicates an expected call of ClientRemoveImport. -func (mr *MockFullNodeMockRecorder) ClientRemoveImport(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRemoveImport", reflect.TypeOf((*MockFullNode)(nil).ClientRemoveImport), arg0, arg1) -} - -// ClientRestartDataTransfer mocks base method. -func (m *MockFullNode) ClientRestartDataTransfer(arg0 context.Context, arg1 datatransfer.TransferID, arg2 peer.ID, arg3 bool) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientRestartDataTransfer", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(error) - return ret0 -} - -// ClientRestartDataTransfer indicates an expected call of ClientRestartDataTransfer. -func (mr *MockFullNodeMockRecorder) ClientRestartDataTransfer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRestartDataTransfer", reflect.TypeOf((*MockFullNode)(nil).ClientRestartDataTransfer), arg0, arg1, arg2, arg3) -} - -// ClientRetrieve mocks base method. -func (m *MockFullNode) ClientRetrieve(arg0 context.Context, arg1 api.RetrievalOrder) (*api.RestrievalRes, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientRetrieve", arg0, arg1) - ret0, _ := ret[0].(*api.RestrievalRes) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientRetrieve indicates an expected call of ClientRetrieve. -func (mr *MockFullNodeMockRecorder) ClientRetrieve(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRetrieve", reflect.TypeOf((*MockFullNode)(nil).ClientRetrieve), arg0, arg1) -} - -// ClientRetrieveTryRestartInsufficientFunds mocks base method. -func (m *MockFullNode) ClientRetrieveTryRestartInsufficientFunds(arg0 context.Context, arg1 address.Address) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientRetrieveTryRestartInsufficientFunds", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// ClientRetrieveTryRestartInsufficientFunds indicates an expected call of ClientRetrieveTryRestartInsufficientFunds. -func (mr *MockFullNodeMockRecorder) ClientRetrieveTryRestartInsufficientFunds(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRetrieveTryRestartInsufficientFunds", reflect.TypeOf((*MockFullNode)(nil).ClientRetrieveTryRestartInsufficientFunds), arg0, arg1) -} - -// ClientRetrieveWait mocks base method. -func (m *MockFullNode) ClientRetrieveWait(arg0 context.Context, arg1 retrievalmarket.DealID) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientRetrieveWait", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// ClientRetrieveWait indicates an expected call of ClientRetrieveWait. -func (mr *MockFullNodeMockRecorder) ClientRetrieveWait(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRetrieveWait", reflect.TypeOf((*MockFullNode)(nil).ClientRetrieveWait), arg0, arg1) -} - -// ClientStartDeal mocks base method. -func (m *MockFullNode) ClientStartDeal(arg0 context.Context, arg1 *api.StartDealParams) (*cid.Cid, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientStartDeal", arg0, arg1) - ret0, _ := ret[0].(*cid.Cid) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientStartDeal indicates an expected call of ClientStartDeal. -func (mr *MockFullNodeMockRecorder) ClientStartDeal(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientStartDeal", reflect.TypeOf((*MockFullNode)(nil).ClientStartDeal), arg0, arg1) -} - -// ClientStatelessDeal mocks base method. -func (m *MockFullNode) ClientStatelessDeal(arg0 context.Context, arg1 *api.StartDealParams) (*cid.Cid, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientStatelessDeal", arg0, arg1) - ret0, _ := ret[0].(*cid.Cid) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientStatelessDeal indicates an expected call of ClientStatelessDeal. -func (mr *MockFullNodeMockRecorder) ClientStatelessDeal(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientStatelessDeal", reflect.TypeOf((*MockFullNode)(nil).ClientStatelessDeal), arg0, arg1) -} - // Closing mocks base method. func (m *MockFullNode) Closing(arg0 context.Context) (<-chan struct{}, error) { m.ctrl.T.Helper() @@ -1522,6 +1107,21 @@ func (mr *MockFullNodeMockRecorder) EthTraceReplayBlockTransactions(arg0, arg1, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthTraceReplayBlockTransactions", reflect.TypeOf((*MockFullNode)(nil).EthTraceReplayBlockTransactions), arg0, arg1, arg2) } +// EthTraceTransaction mocks base method. +func (m *MockFullNode) EthTraceTransaction(arg0 context.Context, arg1 string) ([]*ethtypes.EthTraceTransaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthTraceTransaction", arg0, arg1) + ret0, _ := ret[0].([]*ethtypes.EthTraceTransaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthTraceTransaction indicates an expected call of EthTraceTransaction. +func (mr *MockFullNodeMockRecorder) EthTraceTransaction(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthTraceTransaction", reflect.TypeOf((*MockFullNode)(nil).EthTraceTransaction), arg0, arg1) +} + // EthUninstallFilter mocks base method. func (m *MockFullNode) EthUninstallFilter(arg0 context.Context, arg1 ethtypes.EthFilterID) (bool, error) { m.ctrl.T.Helper() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index cde8230c4..e41b4fcba 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -5,8 +5,6 @@ package api import ( "context" "encoding/json" - "net/http" - "net/url" "time" "github.com/google/uuid" @@ -20,10 +18,6 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - "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-jsonrpc" "github.com/filecoin-project/go-jsonrpc/auth" "github.com/filecoin-project/go-state-types/abi" @@ -42,7 +36,6 @@ import ( "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/journal/alerting" "github.com/filecoin-project/lotus/node/modules/dtypes" - "github.com/filecoin-project/lotus/node/repo/imports" "github.com/filecoin-project/lotus/storage/pipeline/piece" "github.com/filecoin-project/lotus/storage/pipeline/sealiface" "github.com/filecoin-project/lotus/storage/sealer/fsutil" @@ -115,41 +108,6 @@ type CommonNetStub struct { NetStub } -type CurioStruct struct { - Internal CurioMethods -} - -type CurioMethods struct { - AllocatePieceToSector func(p0 context.Context, p1 address.Address, p2 PieceDealInfo, p3 int64, p4 url.URL, p5 http.Header) (SectorOffset, error) `perm:"write"` - - LogList func(p0 context.Context) ([]string, error) `perm:"read"` - - LogSetLevel func(p0 context.Context, p1 string, p2 string) error `perm:"admin"` - - Shutdown func(p0 context.Context) error `perm:"admin"` - - StorageAddLocal func(p0 context.Context, p1 string) error `perm:"admin"` - - StorageDetachLocal func(p0 context.Context, p1 string) error `perm:"admin"` - - StorageFindSector func(p0 context.Context, p1 abi.SectorID, p2 storiface.SectorFileType, p3 abi.SectorSize, p4 bool) ([]storiface.SectorStorageInfo, error) `perm:"admin"` - - StorageInfo func(p0 context.Context, p1 storiface.ID) (storiface.StorageInfo, error) `perm:"admin"` - - StorageInit func(p0 context.Context, p1 string, p2 storiface.LocalStorageMeta) error `perm:"admin"` - - StorageList func(p0 context.Context) (map[storiface.ID][]storiface.Decl, error) `perm:"admin"` - - StorageLocal func(p0 context.Context) (map[storiface.ID]string, error) `perm:"admin"` - - StorageStat func(p0 context.Context, p1 storiface.ID) (fsutil.FsStat, error) `perm:"admin"` - - Version func(p0 context.Context) (Version, error) `perm:"admin"` -} - -type CurioStub struct { -} - type EthSubscriberStruct struct { Internal EthSubscriberMethods } @@ -226,62 +184,6 @@ type FullNodeMethods struct { ChainTipSetWeight func(p0 context.Context, p1 types.TipSetKey) (types.BigInt, error) `perm:"read"` - ClientCalcCommP func(p0 context.Context, p1 string) (*CommPRet, error) `perm:"write"` - - ClientCancelDataTransfer func(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error `perm:"write"` - - ClientCancelRetrievalDeal func(p0 context.Context, p1 retrievalmarket.DealID) error `perm:"write"` - - ClientDataTransferUpdates func(p0 context.Context) (<-chan DataTransferChannel, error) `perm:"write"` - - ClientDealPieceCID func(p0 context.Context, p1 cid.Cid) (DataCIDSize, error) `perm:"read"` - - ClientDealSize func(p0 context.Context, p1 cid.Cid) (DataSize, error) `perm:"read"` - - ClientExport func(p0 context.Context, p1 ExportRef, p2 FileRef) error `perm:"admin"` - - ClientFindData func(p0 context.Context, p1 cid.Cid, p2 *cid.Cid) ([]QueryOffer, error) `perm:"read"` - - ClientGenCar func(p0 context.Context, p1 FileRef, p2 string) error `perm:"write"` - - ClientGetDealInfo func(p0 context.Context, p1 cid.Cid) (*DealInfo, error) `perm:"read"` - - ClientGetDealStatus func(p0 context.Context, p1 uint64) (string, error) `perm:"read"` - - ClientGetDealUpdates func(p0 context.Context) (<-chan DealInfo, error) `perm:"write"` - - ClientGetRetrievalUpdates func(p0 context.Context) (<-chan RetrievalInfo, error) `perm:"write"` - - ClientHasLocal func(p0 context.Context, p1 cid.Cid) (bool, error) `perm:"write"` - - ClientImport func(p0 context.Context, p1 FileRef) (*ImportRes, error) `perm:"admin"` - - ClientListDataTransfers func(p0 context.Context) ([]DataTransferChannel, error) `perm:"write"` - - ClientListDeals func(p0 context.Context) ([]DealInfo, error) `perm:"write"` - - ClientListImports func(p0 context.Context) ([]Import, error) `perm:"write"` - - ClientListRetrievals func(p0 context.Context) ([]RetrievalInfo, error) `perm:"write"` - - ClientMinerQueryOffer func(p0 context.Context, p1 address.Address, p2 cid.Cid, p3 *cid.Cid) (QueryOffer, error) `perm:"read"` - - ClientQueryAsk func(p0 context.Context, p1 peer.ID, p2 address.Address) (*StorageAsk, error) `perm:"read"` - - 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"` - - ClientRetrieve func(p0 context.Context, p1 RetrievalOrder) (*RestrievalRes, error) `perm:"admin"` - - ClientRetrieveTryRestartInsufficientFunds func(p0 context.Context, p1 address.Address) error `perm:"write"` - - ClientRetrieveWait func(p0 context.Context, p1 retrievalmarket.DealID) error `perm:"admin"` - - ClientStartDeal func(p0 context.Context, p1 *StartDealParams) (*cid.Cid, error) `perm:"admin"` - - ClientStatelessDeal func(p0 context.Context, p1 *StartDealParams) (*cid.Cid, error) `perm:"write"` - CreateBackup func(p0 context.Context, p1 string) error `perm:"admin"` EthAccounts func(p0 context.Context) ([]ethtypes.EthAddress, error) `perm:"read"` @@ -358,6 +260,8 @@ type FullNodeMethods struct { EthTraceReplayBlockTransactions func(p0 context.Context, p1 string, p2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) `perm:"read"` + EthTraceTransaction func(p0 context.Context, p1 string) ([]*ethtypes.EthTraceTransaction, error) `perm:"read"` + EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) `perm:"read"` EthUnsubscribe func(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) `perm:"read"` @@ -788,6 +692,8 @@ type GatewayMethods struct { EthTraceReplayBlockTransactions func(p0 context.Context, p1 string, p2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) `` + EthTraceTransaction func(p0 context.Context, p1 string) ([]*ethtypes.EthTraceTransaction, error) `` + EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) `` EthUnsubscribe func(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) `` @@ -985,100 +891,10 @@ type StorageMinerMethods 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"` - - DagstoreLookupPieces func(p0 context.Context, p1 cid.Cid) ([]DagstoreShardInfo, error) `perm:"admin"` - - DagstoreRecoverShard func(p0 context.Context, p1 string) error `perm:"write"` - - DagstoreRegisterShard func(p0 context.Context, p1 string) error `perm:"admin"` - - DealsConsiderOfflineRetrievalDeals func(p0 context.Context) (bool, error) `perm:"admin"` - - DealsConsiderOfflineStorageDeals func(p0 context.Context) (bool, error) `perm:"admin"` - - DealsConsiderOnlineRetrievalDeals func(p0 context.Context) (bool, error) `perm:"admin"` - - DealsConsiderOnlineStorageDeals func(p0 context.Context) (bool, error) `perm:"admin"` - - DealsConsiderUnverifiedStorageDeals func(p0 context.Context) (bool, error) `perm:"admin"` - - DealsConsiderVerifiedStorageDeals func(p0 context.Context) (bool, error) `perm:"admin"` - - DealsImportData func(p0 context.Context, p1 cid.Cid, p2 string) error `perm:"admin"` - - DealsList func(p0 context.Context) ([]*MarketDeal, error) `perm:"admin"` - - DealsPieceCidBlocklist func(p0 context.Context) ([]cid.Cid, error) `perm:"admin"` - - DealsSetConsiderOfflineRetrievalDeals func(p0 context.Context, p1 bool) error `perm:"admin"` - - DealsSetConsiderOfflineStorageDeals func(p0 context.Context, p1 bool) error `perm:"admin"` - - DealsSetConsiderOnlineRetrievalDeals func(p0 context.Context, p1 bool) error `perm:"admin"` - - DealsSetConsiderOnlineStorageDeals func(p0 context.Context, p1 bool) error `perm:"admin"` - - DealsSetConsiderUnverifiedStorageDeals func(p0 context.Context, p1 bool) error `perm:"admin"` - - DealsSetConsiderVerifiedStorageDeals func(p0 context.Context, p1 bool) error `perm:"admin"` - - DealsSetPieceCidBlocklist func(p0 context.Context, p1 []cid.Cid) error `perm:"admin"` - - IndexerAnnounceAllDeals func(p0 context.Context) error `perm:"admin"` - - IndexerAnnounceDeal func(p0 context.Context, p1 cid.Cid) error `perm:"admin"` - - MarketCancelDataTransfer func(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error `perm:"write"` - - MarketDataTransferDiagnostics func(p0 context.Context, p1 peer.ID) (*TransferDiagnostics, error) `perm:"write"` - - MarketDataTransferUpdates func(p0 context.Context) (<-chan DataTransferChannel, error) `perm:"write"` - - MarketGetAsk func(p0 context.Context) (*storagemarket.SignedStorageAsk, error) `perm:"read"` - - MarketGetDealUpdates func(p0 context.Context) (<-chan storagemarket.MinerDeal, error) `perm:"read"` - - MarketGetRetrievalAsk func(p0 context.Context) (*retrievalmarket.Ask, error) `perm:"read"` - - MarketImportDealData func(p0 context.Context, p1 cid.Cid, p2 string) error `perm:"write"` - - MarketListDataTransfers func(p0 context.Context) ([]DataTransferChannel, error) `perm:"write"` - MarketListDeals func(p0 context.Context) ([]*MarketDeal, error) `perm:"read"` - MarketListIncompleteDeals func(p0 context.Context) ([]storagemarket.MinerDeal, error) `perm:"read"` - - MarketListRetrievalDeals func(p0 context.Context) ([]struct{}, error) `perm:"read"` - - MarketPendingDeals func(p0 context.Context) (PendingDealInfo, error) `perm:"write"` - - MarketPublishPendingDeals func(p0 context.Context) error `perm:"admin"` - - MarketRestartDataTransfer func(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error `perm:"write"` - - MarketRetryPublishDeal func(p0 context.Context, p1 cid.Cid) error `perm:"admin"` - - MarketSetAsk func(p0 context.Context, p1 types.BigInt, p2 types.BigInt, p3 abi.ChainEpoch, p4 abi.PaddedPieceSize, p5 abi.PaddedPieceSize) error `perm:"admin"` - - MarketSetRetrievalAsk func(p0 context.Context, p1 *retrievalmarket.Ask) error `perm:"admin"` - MiningBase func(p0 context.Context) (*types.TipSet, error) `perm:"read"` - PiecesGetCIDInfo func(p0 context.Context, p1 cid.Cid) (*piecestore.CIDInfo, error) `perm:"read"` - - PiecesGetPieceInfo func(p0 context.Context, p1 cid.Cid) (*piecestore.PieceInfo, error) `perm:"read"` - - PiecesListCidInfos func(p0 context.Context) ([]cid.Cid, error) `perm:"read"` - - PiecesListPieces func(p0 context.Context) ([]cid.Cid, error) `perm:"read"` - PledgeSector func(p0 context.Context) (abi.SectorID, error) `perm:"write"` RecoverFault func(p0 context.Context, p1 []abi.SectorNumber) ([]cid.Cid, error) `perm:"admin"` @@ -1498,149 +1314,6 @@ func (s *CommonStub) Version(p0 context.Context) (APIVersion, error) { return *new(APIVersion), ErrNotSupported } -func (s *CurioStruct) AllocatePieceToSector(p0 context.Context, p1 address.Address, p2 PieceDealInfo, p3 int64, p4 url.URL, p5 http.Header) (SectorOffset, error) { - if s.Internal.AllocatePieceToSector == nil { - return *new(SectorOffset), ErrNotSupported - } - return s.Internal.AllocatePieceToSector(p0, p1, p2, p3, p4, p5) -} - -func (s *CurioStub) AllocatePieceToSector(p0 context.Context, p1 address.Address, p2 PieceDealInfo, p3 int64, p4 url.URL, p5 http.Header) (SectorOffset, error) { - return *new(SectorOffset), ErrNotSupported -} - -func (s *CurioStruct) LogList(p0 context.Context) ([]string, error) { - if s.Internal.LogList == nil { - return *new([]string), ErrNotSupported - } - return s.Internal.LogList(p0) -} - -func (s *CurioStub) LogList(p0 context.Context) ([]string, error) { - return *new([]string), ErrNotSupported -} - -func (s *CurioStruct) LogSetLevel(p0 context.Context, p1 string, p2 string) error { - if s.Internal.LogSetLevel == nil { - return ErrNotSupported - } - return s.Internal.LogSetLevel(p0, p1, p2) -} - -func (s *CurioStub) LogSetLevel(p0 context.Context, p1 string, p2 string) error { - return ErrNotSupported -} - -func (s *CurioStruct) Shutdown(p0 context.Context) error { - if s.Internal.Shutdown == nil { - return ErrNotSupported - } - return s.Internal.Shutdown(p0) -} - -func (s *CurioStub) Shutdown(p0 context.Context) error { - return ErrNotSupported -} - -func (s *CurioStruct) StorageAddLocal(p0 context.Context, p1 string) error { - if s.Internal.StorageAddLocal == nil { - return ErrNotSupported - } - return s.Internal.StorageAddLocal(p0, p1) -} - -func (s *CurioStub) StorageAddLocal(p0 context.Context, p1 string) error { - return ErrNotSupported -} - -func (s *CurioStruct) StorageDetachLocal(p0 context.Context, p1 string) error { - if s.Internal.StorageDetachLocal == nil { - return ErrNotSupported - } - return s.Internal.StorageDetachLocal(p0, p1) -} - -func (s *CurioStub) StorageDetachLocal(p0 context.Context, p1 string) error { - return ErrNotSupported -} - -func (s *CurioStruct) StorageFindSector(p0 context.Context, p1 abi.SectorID, p2 storiface.SectorFileType, p3 abi.SectorSize, p4 bool) ([]storiface.SectorStorageInfo, error) { - if s.Internal.StorageFindSector == nil { - return *new([]storiface.SectorStorageInfo), ErrNotSupported - } - return s.Internal.StorageFindSector(p0, p1, p2, p3, p4) -} - -func (s *CurioStub) StorageFindSector(p0 context.Context, p1 abi.SectorID, p2 storiface.SectorFileType, p3 abi.SectorSize, p4 bool) ([]storiface.SectorStorageInfo, error) { - return *new([]storiface.SectorStorageInfo), ErrNotSupported -} - -func (s *CurioStruct) StorageInfo(p0 context.Context, p1 storiface.ID) (storiface.StorageInfo, error) { - if s.Internal.StorageInfo == nil { - return *new(storiface.StorageInfo), ErrNotSupported - } - return s.Internal.StorageInfo(p0, p1) -} - -func (s *CurioStub) StorageInfo(p0 context.Context, p1 storiface.ID) (storiface.StorageInfo, error) { - return *new(storiface.StorageInfo), ErrNotSupported -} - -func (s *CurioStruct) StorageInit(p0 context.Context, p1 string, p2 storiface.LocalStorageMeta) error { - if s.Internal.StorageInit == nil { - return ErrNotSupported - } - return s.Internal.StorageInit(p0, p1, p2) -} - -func (s *CurioStub) StorageInit(p0 context.Context, p1 string, p2 storiface.LocalStorageMeta) error { - return ErrNotSupported -} - -func (s *CurioStruct) StorageList(p0 context.Context) (map[storiface.ID][]storiface.Decl, error) { - if s.Internal.StorageList == nil { - return *new(map[storiface.ID][]storiface.Decl), ErrNotSupported - } - return s.Internal.StorageList(p0) -} - -func (s *CurioStub) StorageList(p0 context.Context) (map[storiface.ID][]storiface.Decl, error) { - return *new(map[storiface.ID][]storiface.Decl), ErrNotSupported -} - -func (s *CurioStruct) StorageLocal(p0 context.Context) (map[storiface.ID]string, error) { - if s.Internal.StorageLocal == nil { - return *new(map[storiface.ID]string), ErrNotSupported - } - return s.Internal.StorageLocal(p0) -} - -func (s *CurioStub) StorageLocal(p0 context.Context) (map[storiface.ID]string, error) { - return *new(map[storiface.ID]string), ErrNotSupported -} - -func (s *CurioStruct) StorageStat(p0 context.Context, p1 storiface.ID) (fsutil.FsStat, error) { - if s.Internal.StorageStat == nil { - return *new(fsutil.FsStat), ErrNotSupported - } - return s.Internal.StorageStat(p0, p1) -} - -func (s *CurioStub) StorageStat(p0 context.Context, p1 storiface.ID) (fsutil.FsStat, error) { - return *new(fsutil.FsStat), ErrNotSupported -} - -func (s *CurioStruct) Version(p0 context.Context) (Version, error) { - if s.Internal.Version == nil { - return *new(Version), ErrNotSupported - } - return s.Internal.Version(p0) -} - -func (s *CurioStub) Version(p0 context.Context) (Version, error) { - return *new(Version), ErrNotSupported -} - func (s *EthSubscriberStruct) EthSubscription(p0 context.Context, p1 jsonrpc.RawParams) error { if s.Internal.EthSubscription == nil { return ErrNotSupported @@ -1960,314 +1633,6 @@ func (s *FullNodeStub) ChainTipSetWeight(p0 context.Context, p1 types.TipSetKey) return *new(types.BigInt), ErrNotSupported } -func (s *FullNodeStruct) ClientCalcCommP(p0 context.Context, p1 string) (*CommPRet, error) { - if s.Internal.ClientCalcCommP == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientCalcCommP(p0, p1) -} - -func (s *FullNodeStub) ClientCalcCommP(p0 context.Context, p1 string) (*CommPRet, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientCancelDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { - if s.Internal.ClientCancelDataTransfer == nil { - return ErrNotSupported - } - return s.Internal.ClientCancelDataTransfer(p0, p1, p2, p3) -} - -func (s *FullNodeStub) ClientCancelDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { - return ErrNotSupported -} - -func (s *FullNodeStruct) ClientCancelRetrievalDeal(p0 context.Context, p1 retrievalmarket.DealID) error { - if s.Internal.ClientCancelRetrievalDeal == nil { - return ErrNotSupported - } - return s.Internal.ClientCancelRetrievalDeal(p0, p1) -} - -func (s *FullNodeStub) ClientCancelRetrievalDeal(p0 context.Context, p1 retrievalmarket.DealID) error { - return ErrNotSupported -} - -func (s *FullNodeStruct) ClientDataTransferUpdates(p0 context.Context) (<-chan DataTransferChannel, error) { - if s.Internal.ClientDataTransferUpdates == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientDataTransferUpdates(p0) -} - -func (s *FullNodeStub) ClientDataTransferUpdates(p0 context.Context) (<-chan DataTransferChannel, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientDealPieceCID(p0 context.Context, p1 cid.Cid) (DataCIDSize, error) { - if s.Internal.ClientDealPieceCID == nil { - return *new(DataCIDSize), ErrNotSupported - } - return s.Internal.ClientDealPieceCID(p0, p1) -} - -func (s *FullNodeStub) ClientDealPieceCID(p0 context.Context, p1 cid.Cid) (DataCIDSize, error) { - return *new(DataCIDSize), ErrNotSupported -} - -func (s *FullNodeStruct) ClientDealSize(p0 context.Context, p1 cid.Cid) (DataSize, error) { - if s.Internal.ClientDealSize == nil { - return *new(DataSize), ErrNotSupported - } - return s.Internal.ClientDealSize(p0, p1) -} - -func (s *FullNodeStub) ClientDealSize(p0 context.Context, p1 cid.Cid) (DataSize, error) { - return *new(DataSize), ErrNotSupported -} - -func (s *FullNodeStruct) ClientExport(p0 context.Context, p1 ExportRef, p2 FileRef) error { - if s.Internal.ClientExport == nil { - return ErrNotSupported - } - return s.Internal.ClientExport(p0, p1, p2) -} - -func (s *FullNodeStub) ClientExport(p0 context.Context, p1 ExportRef, p2 FileRef) error { - return ErrNotSupported -} - -func (s *FullNodeStruct) ClientFindData(p0 context.Context, p1 cid.Cid, p2 *cid.Cid) ([]QueryOffer, error) { - if s.Internal.ClientFindData == nil { - return *new([]QueryOffer), ErrNotSupported - } - return s.Internal.ClientFindData(p0, p1, p2) -} - -func (s *FullNodeStub) ClientFindData(p0 context.Context, p1 cid.Cid, p2 *cid.Cid) ([]QueryOffer, error) { - return *new([]QueryOffer), ErrNotSupported -} - -func (s *FullNodeStruct) ClientGenCar(p0 context.Context, p1 FileRef, p2 string) error { - if s.Internal.ClientGenCar == nil { - return ErrNotSupported - } - return s.Internal.ClientGenCar(p0, p1, p2) -} - -func (s *FullNodeStub) ClientGenCar(p0 context.Context, p1 FileRef, p2 string) error { - return ErrNotSupported -} - -func (s *FullNodeStruct) ClientGetDealInfo(p0 context.Context, p1 cid.Cid) (*DealInfo, error) { - if s.Internal.ClientGetDealInfo == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientGetDealInfo(p0, p1) -} - -func (s *FullNodeStub) ClientGetDealInfo(p0 context.Context, p1 cid.Cid) (*DealInfo, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientGetDealStatus(p0 context.Context, p1 uint64) (string, error) { - if s.Internal.ClientGetDealStatus == nil { - return "", ErrNotSupported - } - return s.Internal.ClientGetDealStatus(p0, p1) -} - -func (s *FullNodeStub) ClientGetDealStatus(p0 context.Context, p1 uint64) (string, error) { - return "", ErrNotSupported -} - -func (s *FullNodeStruct) ClientGetDealUpdates(p0 context.Context) (<-chan DealInfo, error) { - if s.Internal.ClientGetDealUpdates == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientGetDealUpdates(p0) -} - -func (s *FullNodeStub) ClientGetDealUpdates(p0 context.Context) (<-chan DealInfo, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientGetRetrievalUpdates(p0 context.Context) (<-chan RetrievalInfo, error) { - if s.Internal.ClientGetRetrievalUpdates == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientGetRetrievalUpdates(p0) -} - -func (s *FullNodeStub) ClientGetRetrievalUpdates(p0 context.Context) (<-chan RetrievalInfo, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientHasLocal(p0 context.Context, p1 cid.Cid) (bool, error) { - if s.Internal.ClientHasLocal == nil { - return false, ErrNotSupported - } - return s.Internal.ClientHasLocal(p0, p1) -} - -func (s *FullNodeStub) ClientHasLocal(p0 context.Context, p1 cid.Cid) (bool, error) { - return false, ErrNotSupported -} - -func (s *FullNodeStruct) ClientImport(p0 context.Context, p1 FileRef) (*ImportRes, error) { - if s.Internal.ClientImport == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientImport(p0, p1) -} - -func (s *FullNodeStub) ClientImport(p0 context.Context, p1 FileRef) (*ImportRes, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientListDataTransfers(p0 context.Context) ([]DataTransferChannel, error) { - if s.Internal.ClientListDataTransfers == nil { - return *new([]DataTransferChannel), ErrNotSupported - } - return s.Internal.ClientListDataTransfers(p0) -} - -func (s *FullNodeStub) ClientListDataTransfers(p0 context.Context) ([]DataTransferChannel, error) { - return *new([]DataTransferChannel), ErrNotSupported -} - -func (s *FullNodeStruct) ClientListDeals(p0 context.Context) ([]DealInfo, error) { - if s.Internal.ClientListDeals == nil { - return *new([]DealInfo), ErrNotSupported - } - return s.Internal.ClientListDeals(p0) -} - -func (s *FullNodeStub) ClientListDeals(p0 context.Context) ([]DealInfo, error) { - return *new([]DealInfo), ErrNotSupported -} - -func (s *FullNodeStruct) ClientListImports(p0 context.Context) ([]Import, error) { - if s.Internal.ClientListImports == nil { - return *new([]Import), ErrNotSupported - } - return s.Internal.ClientListImports(p0) -} - -func (s *FullNodeStub) ClientListImports(p0 context.Context) ([]Import, error) { - return *new([]Import), ErrNotSupported -} - -func (s *FullNodeStruct) ClientListRetrievals(p0 context.Context) ([]RetrievalInfo, error) { - if s.Internal.ClientListRetrievals == nil { - return *new([]RetrievalInfo), ErrNotSupported - } - return s.Internal.ClientListRetrievals(p0) -} - -func (s *FullNodeStub) ClientListRetrievals(p0 context.Context) ([]RetrievalInfo, error) { - return *new([]RetrievalInfo), ErrNotSupported -} - -func (s *FullNodeStruct) ClientMinerQueryOffer(p0 context.Context, p1 address.Address, p2 cid.Cid, p3 *cid.Cid) (QueryOffer, error) { - if s.Internal.ClientMinerQueryOffer == nil { - return *new(QueryOffer), ErrNotSupported - } - return s.Internal.ClientMinerQueryOffer(p0, p1, p2, p3) -} - -func (s *FullNodeStub) ClientMinerQueryOffer(p0 context.Context, p1 address.Address, p2 cid.Cid, p3 *cid.Cid) (QueryOffer, error) { - return *new(QueryOffer), ErrNotSupported -} - -func (s *FullNodeStruct) ClientQueryAsk(p0 context.Context, p1 peer.ID, p2 address.Address) (*StorageAsk, error) { - if s.Internal.ClientQueryAsk == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientQueryAsk(p0, p1, p2) -} - -func (s *FullNodeStub) ClientQueryAsk(p0 context.Context, p1 peer.ID, p2 address.Address) (*StorageAsk, error) { - return nil, ErrNotSupported -} - -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 imports.ID) error { - return ErrNotSupported -} - -func (s *FullNodeStruct) ClientRestartDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { - if s.Internal.ClientRestartDataTransfer == nil { - return ErrNotSupported - } - return s.Internal.ClientRestartDataTransfer(p0, p1, p2, p3) -} - -func (s *FullNodeStub) ClientRestartDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { - return ErrNotSupported -} - -func (s *FullNodeStruct) ClientRetrieve(p0 context.Context, p1 RetrievalOrder) (*RestrievalRes, error) { - if s.Internal.ClientRetrieve == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientRetrieve(p0, p1) -} - -func (s *FullNodeStub) ClientRetrieve(p0 context.Context, p1 RetrievalOrder) (*RestrievalRes, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientRetrieveTryRestartInsufficientFunds(p0 context.Context, p1 address.Address) error { - if s.Internal.ClientRetrieveTryRestartInsufficientFunds == nil { - return ErrNotSupported - } - return s.Internal.ClientRetrieveTryRestartInsufficientFunds(p0, p1) -} - -func (s *FullNodeStub) ClientRetrieveTryRestartInsufficientFunds(p0 context.Context, p1 address.Address) error { - return ErrNotSupported -} - -func (s *FullNodeStruct) ClientRetrieveWait(p0 context.Context, p1 retrievalmarket.DealID) error { - if s.Internal.ClientRetrieveWait == nil { - return ErrNotSupported - } - return s.Internal.ClientRetrieveWait(p0, p1) -} - -func (s *FullNodeStub) ClientRetrieveWait(p0 context.Context, p1 retrievalmarket.DealID) error { - return ErrNotSupported -} - -func (s *FullNodeStruct) ClientStartDeal(p0 context.Context, p1 *StartDealParams) (*cid.Cid, error) { - if s.Internal.ClientStartDeal == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientStartDeal(p0, p1) -} - -func (s *FullNodeStub) ClientStartDeal(p0 context.Context, p1 *StartDealParams) (*cid.Cid, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientStatelessDeal(p0 context.Context, p1 *StartDealParams) (*cid.Cid, error) { - if s.Internal.ClientStatelessDeal == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientStatelessDeal(p0, p1) -} - -func (s *FullNodeStub) ClientStatelessDeal(p0 context.Context, p1 *StartDealParams) (*cid.Cid, error) { - return nil, ErrNotSupported -} - func (s *FullNodeStruct) CreateBackup(p0 context.Context, p1 string) error { if s.Internal.CreateBackup == nil { return ErrNotSupported @@ -2686,6 +2051,17 @@ func (s *FullNodeStub) EthTraceReplayBlockTransactions(p0 context.Context, p1 st return *new([]*ethtypes.EthTraceReplayBlockTransaction), ErrNotSupported } +func (s *FullNodeStruct) EthTraceTransaction(p0 context.Context, p1 string) ([]*ethtypes.EthTraceTransaction, error) { + if s.Internal.EthTraceTransaction == nil { + return *new([]*ethtypes.EthTraceTransaction), ErrNotSupported + } + return s.Internal.EthTraceTransaction(p0, p1) +} + +func (s *FullNodeStub) EthTraceTransaction(p0 context.Context, p1 string) ([]*ethtypes.EthTraceTransaction, error) { + return *new([]*ethtypes.EthTraceTransaction), ErrNotSupported +} + func (s *FullNodeStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) { if s.Internal.EthUninstallFilter == nil { return false, ErrNotSupported @@ -4985,6 +4361,17 @@ func (s *GatewayStub) EthTraceReplayBlockTransactions(p0 context.Context, p1 str return *new([]*ethtypes.EthTraceReplayBlockTransaction), ErrNotSupported } +func (s *GatewayStruct) EthTraceTransaction(p0 context.Context, p1 string) ([]*ethtypes.EthTraceTransaction, error) { + if s.Internal.EthTraceTransaction == nil { + return *new([]*ethtypes.EthTraceTransaction), ErrNotSupported + } + return s.Internal.EthTraceTransaction(p0, p1) +} + +func (s *GatewayStub) EthTraceTransaction(p0 context.Context, p1 string) ([]*ethtypes.EthTraceTransaction, error) { + return *new([]*ethtypes.EthTraceTransaction), ErrNotSupported +} + func (s *GatewayStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) { if s.Internal.EthUninstallFilter == nil { return false, ErrNotSupported @@ -5898,369 +5285,6 @@ 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) DagstoreLookupPieces(p0 context.Context, p1 cid.Cid) ([]DagstoreShardInfo, error) { - if s.Internal.DagstoreLookupPieces == nil { - return *new([]DagstoreShardInfo), ErrNotSupported - } - return s.Internal.DagstoreLookupPieces(p0, p1) -} - -func (s *StorageMinerStub) DagstoreLookupPieces(p0 context.Context, p1 cid.Cid) ([]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) DagstoreRegisterShard(p0 context.Context, p1 string) error { - if s.Internal.DagstoreRegisterShard == nil { - return ErrNotSupported - } - return s.Internal.DagstoreRegisterShard(p0, p1) -} - -func (s *StorageMinerStub) DagstoreRegisterShard(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 - } - return s.Internal.DealsConsiderOfflineRetrievalDeals(p0) -} - -func (s *StorageMinerStub) DealsConsiderOfflineRetrievalDeals(p0 context.Context) (bool, error) { - return false, ErrNotSupported -} - -func (s *StorageMinerStruct) DealsConsiderOfflineStorageDeals(p0 context.Context) (bool, error) { - if s.Internal.DealsConsiderOfflineStorageDeals == nil { - return false, ErrNotSupported - } - return s.Internal.DealsConsiderOfflineStorageDeals(p0) -} - -func (s *StorageMinerStub) DealsConsiderOfflineStorageDeals(p0 context.Context) (bool, error) { - return false, ErrNotSupported -} - -func (s *StorageMinerStruct) DealsConsiderOnlineRetrievalDeals(p0 context.Context) (bool, error) { - if s.Internal.DealsConsiderOnlineRetrievalDeals == nil { - return false, ErrNotSupported - } - return s.Internal.DealsConsiderOnlineRetrievalDeals(p0) -} - -func (s *StorageMinerStub) DealsConsiderOnlineRetrievalDeals(p0 context.Context) (bool, error) { - return false, ErrNotSupported -} - -func (s *StorageMinerStruct) DealsConsiderOnlineStorageDeals(p0 context.Context) (bool, error) { - if s.Internal.DealsConsiderOnlineStorageDeals == nil { - return false, ErrNotSupported - } - return s.Internal.DealsConsiderOnlineStorageDeals(p0) -} - -func (s *StorageMinerStub) DealsConsiderOnlineStorageDeals(p0 context.Context) (bool, error) { - return false, ErrNotSupported -} - -func (s *StorageMinerStruct) DealsConsiderUnverifiedStorageDeals(p0 context.Context) (bool, error) { - if s.Internal.DealsConsiderUnverifiedStorageDeals == nil { - return false, ErrNotSupported - } - return s.Internal.DealsConsiderUnverifiedStorageDeals(p0) -} - -func (s *StorageMinerStub) DealsConsiderUnverifiedStorageDeals(p0 context.Context) (bool, error) { - return false, ErrNotSupported -} - -func (s *StorageMinerStruct) DealsConsiderVerifiedStorageDeals(p0 context.Context) (bool, error) { - if s.Internal.DealsConsiderVerifiedStorageDeals == nil { - return false, ErrNotSupported - } - return s.Internal.DealsConsiderVerifiedStorageDeals(p0) -} - -func (s *StorageMinerStub) DealsConsiderVerifiedStorageDeals(p0 context.Context) (bool, error) { - return false, ErrNotSupported -} - -func (s *StorageMinerStruct) DealsImportData(p0 context.Context, p1 cid.Cid, p2 string) error { - if s.Internal.DealsImportData == nil { - return ErrNotSupported - } - return s.Internal.DealsImportData(p0, p1, p2) -} - -func (s *StorageMinerStub) DealsImportData(p0 context.Context, p1 cid.Cid, p2 string) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) DealsList(p0 context.Context) ([]*MarketDeal, error) { - if s.Internal.DealsList == nil { - return *new([]*MarketDeal), ErrNotSupported - } - return s.Internal.DealsList(p0) -} - -func (s *StorageMinerStub) DealsList(p0 context.Context) ([]*MarketDeal, error) { - return *new([]*MarketDeal), ErrNotSupported -} - -func (s *StorageMinerStruct) DealsPieceCidBlocklist(p0 context.Context) ([]cid.Cid, error) { - if s.Internal.DealsPieceCidBlocklist == nil { - return *new([]cid.Cid), ErrNotSupported - } - return s.Internal.DealsPieceCidBlocklist(p0) -} - -func (s *StorageMinerStub) DealsPieceCidBlocklist(p0 context.Context) ([]cid.Cid, error) { - return *new([]cid.Cid), ErrNotSupported -} - -func (s *StorageMinerStruct) DealsSetConsiderOfflineRetrievalDeals(p0 context.Context, p1 bool) error { - if s.Internal.DealsSetConsiderOfflineRetrievalDeals == nil { - return ErrNotSupported - } - return s.Internal.DealsSetConsiderOfflineRetrievalDeals(p0, p1) -} - -func (s *StorageMinerStub) DealsSetConsiderOfflineRetrievalDeals(p0 context.Context, p1 bool) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) DealsSetConsiderOfflineStorageDeals(p0 context.Context, p1 bool) error { - if s.Internal.DealsSetConsiderOfflineStorageDeals == nil { - return ErrNotSupported - } - return s.Internal.DealsSetConsiderOfflineStorageDeals(p0, p1) -} - -func (s *StorageMinerStub) DealsSetConsiderOfflineStorageDeals(p0 context.Context, p1 bool) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) DealsSetConsiderOnlineRetrievalDeals(p0 context.Context, p1 bool) error { - if s.Internal.DealsSetConsiderOnlineRetrievalDeals == nil { - return ErrNotSupported - } - return s.Internal.DealsSetConsiderOnlineRetrievalDeals(p0, p1) -} - -func (s *StorageMinerStub) DealsSetConsiderOnlineRetrievalDeals(p0 context.Context, p1 bool) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) DealsSetConsiderOnlineStorageDeals(p0 context.Context, p1 bool) error { - if s.Internal.DealsSetConsiderOnlineStorageDeals == nil { - return ErrNotSupported - } - return s.Internal.DealsSetConsiderOnlineStorageDeals(p0, p1) -} - -func (s *StorageMinerStub) DealsSetConsiderOnlineStorageDeals(p0 context.Context, p1 bool) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) DealsSetConsiderUnverifiedStorageDeals(p0 context.Context, p1 bool) error { - if s.Internal.DealsSetConsiderUnverifiedStorageDeals == nil { - return ErrNotSupported - } - return s.Internal.DealsSetConsiderUnverifiedStorageDeals(p0, p1) -} - -func (s *StorageMinerStub) DealsSetConsiderUnverifiedStorageDeals(p0 context.Context, p1 bool) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) DealsSetConsiderVerifiedStorageDeals(p0 context.Context, p1 bool) error { - if s.Internal.DealsSetConsiderVerifiedStorageDeals == nil { - return ErrNotSupported - } - return s.Internal.DealsSetConsiderVerifiedStorageDeals(p0, p1) -} - -func (s *StorageMinerStub) DealsSetConsiderVerifiedStorageDeals(p0 context.Context, p1 bool) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) DealsSetPieceCidBlocklist(p0 context.Context, p1 []cid.Cid) error { - if s.Internal.DealsSetPieceCidBlocklist == nil { - return ErrNotSupported - } - return s.Internal.DealsSetPieceCidBlocklist(p0, p1) -} - -func (s *StorageMinerStub) DealsSetPieceCidBlocklist(p0 context.Context, p1 []cid.Cid) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) IndexerAnnounceAllDeals(p0 context.Context) error { - if s.Internal.IndexerAnnounceAllDeals == nil { - return ErrNotSupported - } - return s.Internal.IndexerAnnounceAllDeals(p0) -} - -func (s *StorageMinerStub) IndexerAnnounceAllDeals(p0 context.Context) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) IndexerAnnounceDeal(p0 context.Context, p1 cid.Cid) error { - if s.Internal.IndexerAnnounceDeal == nil { - return ErrNotSupported - } - return s.Internal.IndexerAnnounceDeal(p0, p1) -} - -func (s *StorageMinerStub) IndexerAnnounceDeal(p0 context.Context, p1 cid.Cid) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) MarketCancelDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { - if s.Internal.MarketCancelDataTransfer == nil { - return ErrNotSupported - } - return s.Internal.MarketCancelDataTransfer(p0, p1, p2, p3) -} - -func (s *StorageMinerStub) MarketCancelDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) MarketDataTransferDiagnostics(p0 context.Context, p1 peer.ID) (*TransferDiagnostics, error) { - if s.Internal.MarketDataTransferDiagnostics == nil { - return nil, ErrNotSupported - } - return s.Internal.MarketDataTransferDiagnostics(p0, p1) -} - -func (s *StorageMinerStub) MarketDataTransferDiagnostics(p0 context.Context, p1 peer.ID) (*TransferDiagnostics, error) { - return nil, ErrNotSupported -} - -func (s *StorageMinerStruct) MarketDataTransferUpdates(p0 context.Context) (<-chan DataTransferChannel, error) { - if s.Internal.MarketDataTransferUpdates == nil { - return nil, ErrNotSupported - } - return s.Internal.MarketDataTransferUpdates(p0) -} - -func (s *StorageMinerStub) MarketDataTransferUpdates(p0 context.Context) (<-chan DataTransferChannel, error) { - return nil, ErrNotSupported -} - -func (s *StorageMinerStruct) MarketGetAsk(p0 context.Context) (*storagemarket.SignedStorageAsk, error) { - if s.Internal.MarketGetAsk == nil { - return nil, ErrNotSupported - } - return s.Internal.MarketGetAsk(p0) -} - -func (s *StorageMinerStub) MarketGetAsk(p0 context.Context) (*storagemarket.SignedStorageAsk, error) { - return nil, ErrNotSupported -} - -func (s *StorageMinerStruct) MarketGetDealUpdates(p0 context.Context) (<-chan storagemarket.MinerDeal, error) { - if s.Internal.MarketGetDealUpdates == nil { - return nil, ErrNotSupported - } - return s.Internal.MarketGetDealUpdates(p0) -} - -func (s *StorageMinerStub) MarketGetDealUpdates(p0 context.Context) (<-chan storagemarket.MinerDeal, error) { - return nil, ErrNotSupported -} - -func (s *StorageMinerStruct) MarketGetRetrievalAsk(p0 context.Context) (*retrievalmarket.Ask, error) { - if s.Internal.MarketGetRetrievalAsk == nil { - return nil, ErrNotSupported - } - return s.Internal.MarketGetRetrievalAsk(p0) -} - -func (s *StorageMinerStub) MarketGetRetrievalAsk(p0 context.Context) (*retrievalmarket.Ask, error) { - return nil, ErrNotSupported -} - -func (s *StorageMinerStruct) MarketImportDealData(p0 context.Context, p1 cid.Cid, p2 string) error { - if s.Internal.MarketImportDealData == nil { - return ErrNotSupported - } - return s.Internal.MarketImportDealData(p0, p1, p2) -} - -func (s *StorageMinerStub) MarketImportDealData(p0 context.Context, p1 cid.Cid, p2 string) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) MarketListDataTransfers(p0 context.Context) ([]DataTransferChannel, error) { - if s.Internal.MarketListDataTransfers == nil { - return *new([]DataTransferChannel), ErrNotSupported - } - return s.Internal.MarketListDataTransfers(p0) -} - -func (s *StorageMinerStub) MarketListDataTransfers(p0 context.Context) ([]DataTransferChannel, error) { - return *new([]DataTransferChannel), ErrNotSupported -} - func (s *StorageMinerStruct) MarketListDeals(p0 context.Context) ([]*MarketDeal, error) { if s.Internal.MarketListDeals == nil { return *new([]*MarketDeal), ErrNotSupported @@ -6272,94 +5296,6 @@ func (s *StorageMinerStub) MarketListDeals(p0 context.Context) ([]*MarketDeal, e return *new([]*MarketDeal), ErrNotSupported } -func (s *StorageMinerStruct) MarketListIncompleteDeals(p0 context.Context) ([]storagemarket.MinerDeal, error) { - if s.Internal.MarketListIncompleteDeals == nil { - return *new([]storagemarket.MinerDeal), ErrNotSupported - } - return s.Internal.MarketListIncompleteDeals(p0) -} - -func (s *StorageMinerStub) MarketListIncompleteDeals(p0 context.Context) ([]storagemarket.MinerDeal, error) { - return *new([]storagemarket.MinerDeal), ErrNotSupported -} - -func (s *StorageMinerStruct) MarketListRetrievalDeals(p0 context.Context) ([]struct{}, error) { - if s.Internal.MarketListRetrievalDeals == nil { - return *new([]struct{}), ErrNotSupported - } - return s.Internal.MarketListRetrievalDeals(p0) -} - -func (s *StorageMinerStub) MarketListRetrievalDeals(p0 context.Context) ([]struct{}, error) { - return *new([]struct{}), ErrNotSupported -} - -func (s *StorageMinerStruct) MarketPendingDeals(p0 context.Context) (PendingDealInfo, error) { - if s.Internal.MarketPendingDeals == nil { - return *new(PendingDealInfo), ErrNotSupported - } - return s.Internal.MarketPendingDeals(p0) -} - -func (s *StorageMinerStub) MarketPendingDeals(p0 context.Context) (PendingDealInfo, error) { - return *new(PendingDealInfo), ErrNotSupported -} - -func (s *StorageMinerStruct) MarketPublishPendingDeals(p0 context.Context) error { - if s.Internal.MarketPublishPendingDeals == nil { - return ErrNotSupported - } - return s.Internal.MarketPublishPendingDeals(p0) -} - -func (s *StorageMinerStub) MarketPublishPendingDeals(p0 context.Context) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) MarketRestartDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { - if s.Internal.MarketRestartDataTransfer == nil { - return ErrNotSupported - } - return s.Internal.MarketRestartDataTransfer(p0, p1, p2, p3) -} - -func (s *StorageMinerStub) MarketRestartDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) MarketRetryPublishDeal(p0 context.Context, p1 cid.Cid) error { - if s.Internal.MarketRetryPublishDeal == nil { - return ErrNotSupported - } - return s.Internal.MarketRetryPublishDeal(p0, p1) -} - -func (s *StorageMinerStub) MarketRetryPublishDeal(p0 context.Context, p1 cid.Cid) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) MarketSetAsk(p0 context.Context, p1 types.BigInt, p2 types.BigInt, p3 abi.ChainEpoch, p4 abi.PaddedPieceSize, p5 abi.PaddedPieceSize) error { - if s.Internal.MarketSetAsk == nil { - return ErrNotSupported - } - return s.Internal.MarketSetAsk(p0, p1, p2, p3, p4, p5) -} - -func (s *StorageMinerStub) MarketSetAsk(p0 context.Context, p1 types.BigInt, p2 types.BigInt, p3 abi.ChainEpoch, p4 abi.PaddedPieceSize, p5 abi.PaddedPieceSize) error { - return ErrNotSupported -} - -func (s *StorageMinerStruct) MarketSetRetrievalAsk(p0 context.Context, p1 *retrievalmarket.Ask) error { - if s.Internal.MarketSetRetrievalAsk == nil { - return ErrNotSupported - } - return s.Internal.MarketSetRetrievalAsk(p0, p1) -} - -func (s *StorageMinerStub) MarketSetRetrievalAsk(p0 context.Context, p1 *retrievalmarket.Ask) error { - return ErrNotSupported -} - func (s *StorageMinerStruct) MiningBase(p0 context.Context) (*types.TipSet, error) { if s.Internal.MiningBase == nil { return nil, ErrNotSupported @@ -6371,50 +5307,6 @@ func (s *StorageMinerStub) MiningBase(p0 context.Context) (*types.TipSet, error) return nil, ErrNotSupported } -func (s *StorageMinerStruct) PiecesGetCIDInfo(p0 context.Context, p1 cid.Cid) (*piecestore.CIDInfo, error) { - if s.Internal.PiecesGetCIDInfo == nil { - return nil, ErrNotSupported - } - return s.Internal.PiecesGetCIDInfo(p0, p1) -} - -func (s *StorageMinerStub) PiecesGetCIDInfo(p0 context.Context, p1 cid.Cid) (*piecestore.CIDInfo, error) { - return nil, ErrNotSupported -} - -func (s *StorageMinerStruct) PiecesGetPieceInfo(p0 context.Context, p1 cid.Cid) (*piecestore.PieceInfo, error) { - if s.Internal.PiecesGetPieceInfo == nil { - return nil, ErrNotSupported - } - return s.Internal.PiecesGetPieceInfo(p0, p1) -} - -func (s *StorageMinerStub) PiecesGetPieceInfo(p0 context.Context, p1 cid.Cid) (*piecestore.PieceInfo, error) { - return nil, ErrNotSupported -} - -func (s *StorageMinerStruct) PiecesListCidInfos(p0 context.Context) ([]cid.Cid, error) { - if s.Internal.PiecesListCidInfos == nil { - return *new([]cid.Cid), ErrNotSupported - } - return s.Internal.PiecesListCidInfos(p0) -} - -func (s *StorageMinerStub) PiecesListCidInfos(p0 context.Context) ([]cid.Cid, error) { - return *new([]cid.Cid), ErrNotSupported -} - -func (s *StorageMinerStruct) PiecesListPieces(p0 context.Context) ([]cid.Cid, error) { - if s.Internal.PiecesListPieces == nil { - return *new([]cid.Cid), ErrNotSupported - } - return s.Internal.PiecesListPieces(p0) -} - -func (s *StorageMinerStub) PiecesListPieces(p0 context.Context) ([]cid.Cid, error) { - return *new([]cid.Cid), ErrNotSupported -} - func (s *StorageMinerStruct) PledgeSector(p0 context.Context) (abi.SectorID, error) { if s.Internal.PledgeSector == nil { return *new(abi.SectorID), ErrNotSupported @@ -7738,7 +6630,6 @@ func (s *WorkerStub) WaitQuiet(p0 context.Context) error { var _ ChainIO = new(ChainIOStruct) var _ Common = new(CommonStruct) var _ CommonNet = new(CommonNetStruct) -var _ Curio = new(CurioStruct) var _ EthSubscriber = new(EthSubscriberStruct) var _ FullNode = new(FullNodeStruct) var _ Gateway = new(GatewayStruct) diff --git a/api/types.go b/api/types.go index b7dbe7b36..a79615f12 100644 --- a/api/types.go +++ b/api/types.go @@ -2,22 +2,16 @@ package api import ( "encoding/json" - "fmt" "time" "github.com/google/uuid" "github.com/ipfs/go-cid" - "github.com/ipfs/go-graphsync" - "github.com/ipld/go-ipld-prime" - "github.com/ipld/go-ipld-prime/codec/dagjson" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" ma "github.com/multiformats/go-multiaddr" "github.com/filecoin-project/go-address" - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" @@ -69,71 +63,6 @@ type MessageSendSpec struct { MaximizeFeeCap bool } -// GraphSyncDataTransfer provides diagnostics on a data transfer happening over graphsync -type GraphSyncDataTransfer struct { - // GraphSync request id for this transfer - RequestID *graphsync.RequestID - // Graphsync state for this transfer - RequestState string - // If a channel ID is present, indicates whether this is the current graphsync request for this channel - // (could have changed in a restart) - IsCurrentChannelRequest bool - // Data transfer channel ID for this transfer - ChannelID *datatransfer.ChannelID - // Data transfer state for this transfer - ChannelState *DataTransferChannel - // Diagnostic information about this request -- and unexpected inconsistencies in - // request state - Diagnostics []string -} - -// TransferDiagnostics give current information about transfers going over graphsync that may be helpful for debugging -type TransferDiagnostics struct { - ReceivingTransfers []*GraphSyncDataTransfer - SendingTransfers []*GraphSyncDataTransfer -} - -type DataTransferChannel struct { - TransferID datatransfer.TransferID - Status datatransfer.Status - BaseCID cid.Cid - IsInitiator bool - IsSender bool - Voucher string - Message string - OtherPeer peer.ID - Transferred uint64 - Stages *datatransfer.ChannelStages -} - -// NewDataTransferChannel constructs an API DataTransferChannel type from full channel state snapshot and a host id -func NewDataTransferChannel(hostID peer.ID, channelState datatransfer.ChannelState) DataTransferChannel { - channel := DataTransferChannel{ - TransferID: channelState.TransferID(), - Status: channelState.Status(), - BaseCID: channelState.BaseCID(), - IsSender: channelState.Sender() == hostID, - Message: channelState.Message(), - } - voucher := channelState.Voucher() - voucherJSON, err := ipld.Encode(voucher.Voucher, dagjson.Encode) - if err != nil { - channel.Voucher = fmt.Errorf("Voucher Serialization: %w", err).Error() - } else { - channel.Voucher = string(voucherJSON) - } - if channel.IsSender { - channel.IsInitiator = !channelState.IsPull() - channel.Transferred = channelState.Sent() - channel.OtherPeer = channelState.Recipient() - } else { - channel.IsInitiator = channelState.IsPull() - channel.Transferred = channelState.Received() - channel.OtherPeer = channelState.Sender() - } - return channel -} - type NetStat struct { System *network.ScopeStat `json:",omitempty"` Transient *network.ScopeStat `json:",omitempty"` @@ -229,31 +158,6 @@ type MessagePrototype struct { ValidNonce bool } -type RetrievalInfo struct { - PayloadCID cid.Cid - ID retrievalmarket.DealID - PieceCID *cid.Cid - PricePerByte abi.TokenAmount - UnsealPrice abi.TokenAmount - - Status retrievalmarket.DealStatus - Message string // more information about deal state, particularly errors - Provider peer.ID - BytesReceived uint64 - BytesPaidFor uint64 - TotalPaid abi.TokenAmount - - TransferChannelID *datatransfer.ChannelID - DataTransfer *DataTransferChannel - - // optional event if part of ClientGetRetrievalUpdates - Event *retrievalmarket.ClientEvent -} - -type RestrievalRes struct { - DealID retrievalmarket.DealID -} - // Selector specifies ipld selector string // - if the string starts with '{', it's interpreted as json selector string // see https://ipld.io/specs/selectors/ and https://ipld.io/specs/selectors/fixtures/selector-fixtures-1/ @@ -261,35 +165,6 @@ type RestrievalRes struct { // see https://github.com/ipld/go-ipld-selector-text-lite type Selector string -type DagSpec struct { - // DataSelector matches data to be retrieved - // - when using textselector, the path specifies subtree - // - the matched graph must have a single root - DataSelector *Selector - - // ExportMerkleProof is applicable only when exporting to a CAR file via a path textselector - // When true, in addition to the selection target, the resulting CAR will contain every block along the - // path back to, and including the original root - // When false the resulting CAR contains only the blocks of the target subdag - ExportMerkleProof bool -} - -type ExportRef struct { - Root cid.Cid - - // DAGs array specifies a list of DAGs to export - // - If exporting into unixfs files, only one DAG is supported, DataSelector is only used to find the targeted root node - // - If exporting into a car file - // - When exactly one text-path DataSelector is specified exports the subgraph and its full merkle-path from the original root - // - Otherwise ( multiple paths and/or JSON selector specs) determines each individual subroot and exports the subtrees as a multi-root car - // - When not specified defaults to a single DAG: - // - Data - the entire DAG: `{"R":{"l":{"none":{}},":>":{"a":{">":{"@":{}}}}}}` - DAGs []DagSpec - - FromLocalCAR string // if specified, get data from a local CARv2 file. - DealID retrievalmarket.DealID -} - type MinerInfo struct { Owner address.Address // Must be an ID-address. Worker address.Address // Must be an ID-address. @@ -346,6 +221,7 @@ type ForkUpgradeParams struct { UpgradeWatermelonHeight abi.ChainEpoch UpgradeDragonHeight abi.ChainEpoch UpgradePhoenixHeight abi.ChainEpoch + UpgradeAussieHeight abi.ChainEpoch } // ChainExportConfig holds configuration for chain ranged exports. diff --git a/api/v0api/full.go b/api/v0api/full.go index b61fc1570..334c5c56d 100644 --- a/api/v0api/full.go +++ b/api/v0api/full.go @@ -5,14 +5,9 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - textselector "github.com/ipld/go-ipld-selector-text-lite" - "github.com/libp2p/go-libp2p/core/peer" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - "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/go-state-types/builtin/v8/paych" verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" @@ -24,9 +19,7 @@ import ( apitypes "github.com/filecoin-project/lotus/api/types" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "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,74 +298,6 @@ type FullNode interface { WalletValidateAddress(context.Context, string) (address.Address, error) //perm:read // Other - - // MethodGroup: Client - // The Client methods all have to do with interacting with the storage and - // retrieval markets as a client - - // 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 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. - ClientStatelessDeal(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) //perm:write - // ClientGetDealInfo returns the latest information about a given deal. - ClientGetDealInfo(context.Context, cid.Cid) (*api.DealInfo, error) //perm:read - // ClientListDeals returns information about the deals made by the local client. - ClientListDeals(ctx context.Context) ([]api.DealInfo, error) //perm:write - // ClientGetDealUpdates returns the status of updated deals - ClientGetDealUpdates(ctx context.Context) (<-chan api.DealInfo, error) //perm:write - // ClientGetDealStatus returns status given a code - ClientGetDealStatus(ctx context.Context, statusCode uint64) (string, error) //perm:read - // ClientHasLocal indicates whether a certain CID is locally stored. - ClientHasLocal(ctx context.Context, root cid.Cid) (bool, error) //perm:write - // ClientFindData identifies peers that have a certain file, and returns QueryOffers (one per peer). - ClientFindData(ctx context.Context, root cid.Cid, piece *cid.Cid) ([]api.QueryOffer, error) //perm:read - // ClientMinerQueryOffer returns a QueryOffer for the specific miner and file. - ClientMinerQueryOffer(ctx context.Context, miner address.Address, root cid.Cid, piece *cid.Cid) (api.QueryOffer, error) //perm:read - // ClientRetrieve initiates the retrieval of a file, as specified in the order. - ClientRetrieve(ctx context.Context, order RetrievalOrder, ref *api.FileRef) error //perm:admin - // ClientRetrieveWithEvents initiates the retrieval of a file, as specified in the order, and provides a channel - // of status updates. - ClientRetrieveWithEvents(ctx context.Context, order RetrievalOrder, ref *api.FileRef) (<-chan marketevents.RetrievalEvent, error) //perm:admin - // ClientQueryAsk returns a signed StorageAsk from the specified miner. - // ClientListRetrievals returns information about retrievals made by the local client - ClientListRetrievals(ctx context.Context) ([]api.RetrievalInfo, error) //perm:write - // ClientGetRetrievalUpdates returns status of updated retrieval deals - ClientGetRetrievalUpdates(ctx context.Context) (<-chan api.RetrievalInfo, error) //perm:write - ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.StorageAsk, error) //perm:read - // ClientCalcCommP calculates the CommP and data size of the specified CID - ClientDealPieceCID(ctx context.Context, root cid.Cid) (api.DataCIDSize, error) //perm:read - // ClientCalcCommP calculates the CommP for a specified file - ClientCalcCommP(ctx context.Context, inpath string) (*api.CommPRet, error) //perm:write - // ClientGenCar generates a CAR file for the specified file. - ClientGenCar(ctx context.Context, ref api.FileRef, outpath string) error //perm:write - // ClientDealSize calculates real deal data size - ClientDealSize(ctx context.Context, root cid.Cid) (api.DataSize, error) //perm:read - // ClientListTransfers returns the status of all ongoing transfers of data - ClientListDataTransfers(ctx context.Context) ([]api.DataTransferChannel, error) //perm:write - ClientDataTransferUpdates(ctx context.Context) (<-chan api.DataTransferChannel, error) //perm:write - // ClientRestartDataTransfer attempts to restart a data transfer with the given transfer ID and other peer - ClientRestartDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error //perm:write - // ClientCancelDataTransfer cancels a data transfer with the given transfer ID and other peer - ClientCancelDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error //perm:write - // ClientRetrieveTryRestartInsufficientFunds attempts to restart stalled retrievals on a given payment channel - // which are stuck due to insufficient funds - ClientRetrieveTryRestartInsufficientFunds(ctx context.Context, paymentChannel address.Address) error //perm:write - - // ClientCancelRetrievalDeal cancels an ongoing retrieval deal based on DealID - ClientCancelRetrievalDeal(ctx context.Context, dealid retrievalmarket.DealID) error //perm:write - - // ClientUnimport removes references to the specified file from filestore - // ClientUnimport(path string) - - // ClientListImports lists imported files and their root CIDs - ClientListImports(ctx context.Context) ([]api.Import, error) //perm:write - - // ClientListAsks() []Ask - // MethodGroup: State // The State methods are used to query, inspect, and interact with chain state. // Most methods take a TipSetKey as a parameter. The state looked up is the parent state of the tipset. @@ -743,37 +668,3 @@ type FullNode interface { // the path specified when calling CreateBackup is within the base path CreateBackup(ctx context.Context, fpath string) error //perm:admin } - -func OfferOrder(o api.QueryOffer, client address.Address) RetrievalOrder { - return RetrievalOrder{ - Root: o.Root, - Piece: o.Piece, - Size: o.Size, - Total: o.MinPrice, - UnsealPrice: o.UnsealPrice, - PaymentInterval: o.PaymentInterval, - PaymentIntervalIncrease: o.PaymentIntervalIncrease, - Client: client, - - Miner: o.Miner, - MinerPeer: &o.MinerPeer, - } -} - -type RetrievalOrder struct { - // TODO: make this less unixfs specific - Root cid.Cid - Piece *cid.Cid - DatamodelPathSelector *textselector.Expression - Size uint64 - - FromLocalCAR string // if specified, get data from a local CARv2 file. - // TODO: support offset - Total types.BigInt - UnsealPrice types.BigInt - PaymentInterval uint64 - PaymentIntervalIncrease uint64 - Client address.Address - Miner address.Address - MinerPeer *retrievalmarket.RetrievalPeer -} diff --git a/api/v0api/proxy_gen.go b/api/v0api/proxy_gen.go index 90c25d4a7..a87568949 100644 --- a/api/v0api/proxy_gen.go +++ b/api/v0api/proxy_gen.go @@ -7,14 +7,10 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - "github.com/libp2p/go-libp2p/core/peer" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - "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/go-state-types/builtin/v8/paych" verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" @@ -26,9 +22,7 @@ import ( apitypes "github.com/filecoin-project/lotus/api/types" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "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" ) var ErrNotSupported = xerrors.New("method not supported") @@ -90,60 +84,6 @@ type FullNodeMethods struct { ChainTipSetWeight func(p0 context.Context, p1 types.TipSetKey) (types.BigInt, error) `perm:"read"` - ClientCalcCommP func(p0 context.Context, p1 string) (*api.CommPRet, error) `perm:"write"` - - ClientCancelDataTransfer func(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error `perm:"write"` - - ClientCancelRetrievalDeal func(p0 context.Context, p1 retrievalmarket.DealID) error `perm:"write"` - - ClientDataTransferUpdates func(p0 context.Context) (<-chan api.DataTransferChannel, error) `perm:"write"` - - ClientDealPieceCID func(p0 context.Context, p1 cid.Cid) (api.DataCIDSize, error) `perm:"read"` - - ClientDealSize func(p0 context.Context, p1 cid.Cid) (api.DataSize, error) `perm:"read"` - - ClientFindData func(p0 context.Context, p1 cid.Cid, p2 *cid.Cid) ([]api.QueryOffer, error) `perm:"read"` - - ClientGenCar func(p0 context.Context, p1 api.FileRef, p2 string) error `perm:"write"` - - ClientGetDealInfo func(p0 context.Context, p1 cid.Cid) (*api.DealInfo, error) `perm:"read"` - - ClientGetDealStatus func(p0 context.Context, p1 uint64) (string, error) `perm:"read"` - - ClientGetDealUpdates func(p0 context.Context) (<-chan api.DealInfo, error) `perm:"write"` - - ClientGetRetrievalUpdates func(p0 context.Context) (<-chan api.RetrievalInfo, error) `perm:"write"` - - ClientHasLocal func(p0 context.Context, p1 cid.Cid) (bool, error) `perm:"write"` - - ClientImport func(p0 context.Context, p1 api.FileRef) (*api.ImportRes, error) `perm:"admin"` - - ClientListDataTransfers func(p0 context.Context) ([]api.DataTransferChannel, error) `perm:"write"` - - ClientListDeals func(p0 context.Context) ([]api.DealInfo, error) `perm:"write"` - - ClientListImports func(p0 context.Context) ([]api.Import, error) `perm:"write"` - - ClientListRetrievals func(p0 context.Context) ([]api.RetrievalInfo, error) `perm:"write"` - - ClientMinerQueryOffer func(p0 context.Context, p1 address.Address, p2 cid.Cid, p3 *cid.Cid) (api.QueryOffer, error) `perm:"read"` - - ClientQueryAsk func(p0 context.Context, p1 peer.ID, p2 address.Address) (*storagemarket.StorageAsk, error) `perm:"read"` - - 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"` - - ClientRetrieve func(p0 context.Context, p1 RetrievalOrder, p2 *api.FileRef) error `perm:"admin"` - - ClientRetrieveTryRestartInsufficientFunds func(p0 context.Context, p1 address.Address) error `perm:"write"` - - ClientRetrieveWithEvents func(p0 context.Context, p1 RetrievalOrder, p2 *api.FileRef) (<-chan marketevents.RetrievalEvent, error) `perm:"admin"` - - ClientStartDeal func(p0 context.Context, p1 *api.StartDealParams) (*cid.Cid, error) `perm:"admin"` - - ClientStatelessDeal func(p0 context.Context, p1 *api.StartDealParams) (*cid.Cid, error) `perm:"write"` - CreateBackup func(p0 context.Context, p1 string) error `perm:"admin"` GasEstimateFeeCap func(p0 context.Context, p1 *types.Message, p2 int64, p3 types.TipSetKey) (types.BigInt, error) `perm:"read"` @@ -796,303 +736,6 @@ func (s *FullNodeStub) ChainTipSetWeight(p0 context.Context, p1 types.TipSetKey) return *new(types.BigInt), ErrNotSupported } -func (s *FullNodeStruct) ClientCalcCommP(p0 context.Context, p1 string) (*api.CommPRet, error) { - if s.Internal.ClientCalcCommP == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientCalcCommP(p0, p1) -} - -func (s *FullNodeStub) ClientCalcCommP(p0 context.Context, p1 string) (*api.CommPRet, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientCancelDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { - if s.Internal.ClientCancelDataTransfer == nil { - return ErrNotSupported - } - return s.Internal.ClientCancelDataTransfer(p0, p1, p2, p3) -} - -func (s *FullNodeStub) ClientCancelDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { - return ErrNotSupported -} - -func (s *FullNodeStruct) ClientCancelRetrievalDeal(p0 context.Context, p1 retrievalmarket.DealID) error { - if s.Internal.ClientCancelRetrievalDeal == nil { - return ErrNotSupported - } - return s.Internal.ClientCancelRetrievalDeal(p0, p1) -} - -func (s *FullNodeStub) ClientCancelRetrievalDeal(p0 context.Context, p1 retrievalmarket.DealID) error { - return ErrNotSupported -} - -func (s *FullNodeStruct) ClientDataTransferUpdates(p0 context.Context) (<-chan api.DataTransferChannel, error) { - if s.Internal.ClientDataTransferUpdates == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientDataTransferUpdates(p0) -} - -func (s *FullNodeStub) ClientDataTransferUpdates(p0 context.Context) (<-chan api.DataTransferChannel, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientDealPieceCID(p0 context.Context, p1 cid.Cid) (api.DataCIDSize, error) { - if s.Internal.ClientDealPieceCID == nil { - return *new(api.DataCIDSize), ErrNotSupported - } - return s.Internal.ClientDealPieceCID(p0, p1) -} - -func (s *FullNodeStub) ClientDealPieceCID(p0 context.Context, p1 cid.Cid) (api.DataCIDSize, error) { - return *new(api.DataCIDSize), ErrNotSupported -} - -func (s *FullNodeStruct) ClientDealSize(p0 context.Context, p1 cid.Cid) (api.DataSize, error) { - if s.Internal.ClientDealSize == nil { - return *new(api.DataSize), ErrNotSupported - } - return s.Internal.ClientDealSize(p0, p1) -} - -func (s *FullNodeStub) ClientDealSize(p0 context.Context, p1 cid.Cid) (api.DataSize, error) { - return *new(api.DataSize), ErrNotSupported -} - -func (s *FullNodeStruct) ClientFindData(p0 context.Context, p1 cid.Cid, p2 *cid.Cid) ([]api.QueryOffer, error) { - if s.Internal.ClientFindData == nil { - return *new([]api.QueryOffer), ErrNotSupported - } - return s.Internal.ClientFindData(p0, p1, p2) -} - -func (s *FullNodeStub) ClientFindData(p0 context.Context, p1 cid.Cid, p2 *cid.Cid) ([]api.QueryOffer, error) { - return *new([]api.QueryOffer), ErrNotSupported -} - -func (s *FullNodeStruct) ClientGenCar(p0 context.Context, p1 api.FileRef, p2 string) error { - if s.Internal.ClientGenCar == nil { - return ErrNotSupported - } - return s.Internal.ClientGenCar(p0, p1, p2) -} - -func (s *FullNodeStub) ClientGenCar(p0 context.Context, p1 api.FileRef, p2 string) error { - return ErrNotSupported -} - -func (s *FullNodeStruct) ClientGetDealInfo(p0 context.Context, p1 cid.Cid) (*api.DealInfo, error) { - if s.Internal.ClientGetDealInfo == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientGetDealInfo(p0, p1) -} - -func (s *FullNodeStub) ClientGetDealInfo(p0 context.Context, p1 cid.Cid) (*api.DealInfo, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientGetDealStatus(p0 context.Context, p1 uint64) (string, error) { - if s.Internal.ClientGetDealStatus == nil { - return "", ErrNotSupported - } - return s.Internal.ClientGetDealStatus(p0, p1) -} - -func (s *FullNodeStub) ClientGetDealStatus(p0 context.Context, p1 uint64) (string, error) { - return "", ErrNotSupported -} - -func (s *FullNodeStruct) ClientGetDealUpdates(p0 context.Context) (<-chan api.DealInfo, error) { - if s.Internal.ClientGetDealUpdates == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientGetDealUpdates(p0) -} - -func (s *FullNodeStub) ClientGetDealUpdates(p0 context.Context) (<-chan api.DealInfo, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientGetRetrievalUpdates(p0 context.Context) (<-chan api.RetrievalInfo, error) { - if s.Internal.ClientGetRetrievalUpdates == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientGetRetrievalUpdates(p0) -} - -func (s *FullNodeStub) ClientGetRetrievalUpdates(p0 context.Context) (<-chan api.RetrievalInfo, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientHasLocal(p0 context.Context, p1 cid.Cid) (bool, error) { - if s.Internal.ClientHasLocal == nil { - return false, ErrNotSupported - } - return s.Internal.ClientHasLocal(p0, p1) -} - -func (s *FullNodeStub) ClientHasLocal(p0 context.Context, p1 cid.Cid) (bool, error) { - return false, ErrNotSupported -} - -func (s *FullNodeStruct) ClientImport(p0 context.Context, p1 api.FileRef) (*api.ImportRes, error) { - if s.Internal.ClientImport == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientImport(p0, p1) -} - -func (s *FullNodeStub) ClientImport(p0 context.Context, p1 api.FileRef) (*api.ImportRes, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientListDataTransfers(p0 context.Context) ([]api.DataTransferChannel, error) { - if s.Internal.ClientListDataTransfers == nil { - return *new([]api.DataTransferChannel), ErrNotSupported - } - return s.Internal.ClientListDataTransfers(p0) -} - -func (s *FullNodeStub) ClientListDataTransfers(p0 context.Context) ([]api.DataTransferChannel, error) { - return *new([]api.DataTransferChannel), ErrNotSupported -} - -func (s *FullNodeStruct) ClientListDeals(p0 context.Context) ([]api.DealInfo, error) { - if s.Internal.ClientListDeals == nil { - return *new([]api.DealInfo), ErrNotSupported - } - return s.Internal.ClientListDeals(p0) -} - -func (s *FullNodeStub) ClientListDeals(p0 context.Context) ([]api.DealInfo, error) { - return *new([]api.DealInfo), ErrNotSupported -} - -func (s *FullNodeStruct) ClientListImports(p0 context.Context) ([]api.Import, error) { - if s.Internal.ClientListImports == nil { - return *new([]api.Import), ErrNotSupported - } - return s.Internal.ClientListImports(p0) -} - -func (s *FullNodeStub) ClientListImports(p0 context.Context) ([]api.Import, error) { - return *new([]api.Import), ErrNotSupported -} - -func (s *FullNodeStruct) ClientListRetrievals(p0 context.Context) ([]api.RetrievalInfo, error) { - if s.Internal.ClientListRetrievals == nil { - return *new([]api.RetrievalInfo), ErrNotSupported - } - return s.Internal.ClientListRetrievals(p0) -} - -func (s *FullNodeStub) ClientListRetrievals(p0 context.Context) ([]api.RetrievalInfo, error) { - return *new([]api.RetrievalInfo), ErrNotSupported -} - -func (s *FullNodeStruct) ClientMinerQueryOffer(p0 context.Context, p1 address.Address, p2 cid.Cid, p3 *cid.Cid) (api.QueryOffer, error) { - if s.Internal.ClientMinerQueryOffer == nil { - return *new(api.QueryOffer), ErrNotSupported - } - return s.Internal.ClientMinerQueryOffer(p0, p1, p2, p3) -} - -func (s *FullNodeStub) ClientMinerQueryOffer(p0 context.Context, p1 address.Address, p2 cid.Cid, p3 *cid.Cid) (api.QueryOffer, error) { - return *new(api.QueryOffer), ErrNotSupported -} - -func (s *FullNodeStruct) ClientQueryAsk(p0 context.Context, p1 peer.ID, p2 address.Address) (*storagemarket.StorageAsk, error) { - if s.Internal.ClientQueryAsk == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientQueryAsk(p0, p1, p2) -} - -func (s *FullNodeStub) ClientQueryAsk(p0 context.Context, p1 peer.ID, p2 address.Address) (*storagemarket.StorageAsk, error) { - return nil, ErrNotSupported -} - -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 imports.ID) error { - return ErrNotSupported -} - -func (s *FullNodeStruct) ClientRestartDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { - if s.Internal.ClientRestartDataTransfer == nil { - return ErrNotSupported - } - return s.Internal.ClientRestartDataTransfer(p0, p1, p2, p3) -} - -func (s *FullNodeStub) ClientRestartDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { - return ErrNotSupported -} - -func (s *FullNodeStruct) ClientRetrieve(p0 context.Context, p1 RetrievalOrder, p2 *api.FileRef) error { - if s.Internal.ClientRetrieve == nil { - return ErrNotSupported - } - return s.Internal.ClientRetrieve(p0, p1, p2) -} - -func (s *FullNodeStub) ClientRetrieve(p0 context.Context, p1 RetrievalOrder, p2 *api.FileRef) error { - return ErrNotSupported -} - -func (s *FullNodeStruct) ClientRetrieveTryRestartInsufficientFunds(p0 context.Context, p1 address.Address) error { - if s.Internal.ClientRetrieveTryRestartInsufficientFunds == nil { - return ErrNotSupported - } - return s.Internal.ClientRetrieveTryRestartInsufficientFunds(p0, p1) -} - -func (s *FullNodeStub) ClientRetrieveTryRestartInsufficientFunds(p0 context.Context, p1 address.Address) error { - return ErrNotSupported -} - -func (s *FullNodeStruct) ClientRetrieveWithEvents(p0 context.Context, p1 RetrievalOrder, p2 *api.FileRef) (<-chan marketevents.RetrievalEvent, error) { - if s.Internal.ClientRetrieveWithEvents == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientRetrieveWithEvents(p0, p1, p2) -} - -func (s *FullNodeStub) ClientRetrieveWithEvents(p0 context.Context, p1 RetrievalOrder, p2 *api.FileRef) (<-chan marketevents.RetrievalEvent, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientStartDeal(p0 context.Context, p1 *api.StartDealParams) (*cid.Cid, error) { - if s.Internal.ClientStartDeal == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientStartDeal(p0, p1) -} - -func (s *FullNodeStub) ClientStartDeal(p0 context.Context, p1 *api.StartDealParams) (*cid.Cid, error) { - return nil, ErrNotSupported -} - -func (s *FullNodeStruct) ClientStatelessDeal(p0 context.Context, p1 *api.StartDealParams) (*cid.Cid, error) { - if s.Internal.ClientStatelessDeal == nil { - return nil, ErrNotSupported - } - return s.Internal.ClientStatelessDeal(p0, p1) -} - -func (s *FullNodeStub) ClientStatelessDeal(p0 context.Context, p1 *api.StartDealParams) (*cid.Cid, error) { - return nil, ErrNotSupported -} - func (s *FullNodeStruct) CreateBackup(p0 context.Context, p1 string) error { if s.Internal.CreateBackup == nil { return ErrNotSupported diff --git a/api/v0api/v0mocks/mock_full.go b/api/v0api/v0mocks/mock_full.go index df67d0876..092d93b67 100644 --- a/api/v0api/v0mocks/mock_full.go +++ b/api/v0api/v0mocks/mock_full.go @@ -20,9 +20,6 @@ import ( address "github.com/filecoin-project/go-address" bitfield "github.com/filecoin-project/go-bitfield" - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - 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" abi "github.com/filecoin-project/go-state-types/abi" big "github.com/filecoin-project/go-state-types/big" @@ -36,13 +33,10 @@ import ( api "github.com/filecoin-project/lotus/api" apitypes "github.com/filecoin-project/lotus/api/types" - v0api "github.com/filecoin-project/lotus/api/v0api" miner1 "github.com/filecoin-project/lotus/chain/actors/builtin/miner" types "github.com/filecoin-project/lotus/chain/types" alerting "github.com/filecoin-project/lotus/journal/alerting" - 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" ) // MockFullNode is a mock of FullNode interface. @@ -455,404 +449,6 @@ func (mr *MockFullNodeMockRecorder) ChainTipSetWeight(arg0, arg1 interface{}) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainTipSetWeight", reflect.TypeOf((*MockFullNode)(nil).ChainTipSetWeight), arg0, arg1) } -// ClientCalcCommP mocks base method. -func (m *MockFullNode) ClientCalcCommP(arg0 context.Context, arg1 string) (*api.CommPRet, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientCalcCommP", arg0, arg1) - ret0, _ := ret[0].(*api.CommPRet) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientCalcCommP indicates an expected call of ClientCalcCommP. -func (mr *MockFullNodeMockRecorder) ClientCalcCommP(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientCalcCommP", reflect.TypeOf((*MockFullNode)(nil).ClientCalcCommP), arg0, arg1) -} - -// ClientCancelDataTransfer mocks base method. -func (m *MockFullNode) ClientCancelDataTransfer(arg0 context.Context, arg1 datatransfer.TransferID, arg2 peer.ID, arg3 bool) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientCancelDataTransfer", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(error) - return ret0 -} - -// ClientCancelDataTransfer indicates an expected call of ClientCancelDataTransfer. -func (mr *MockFullNodeMockRecorder) ClientCancelDataTransfer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientCancelDataTransfer", reflect.TypeOf((*MockFullNode)(nil).ClientCancelDataTransfer), arg0, arg1, arg2, arg3) -} - -// ClientCancelRetrievalDeal mocks base method. -func (m *MockFullNode) ClientCancelRetrievalDeal(arg0 context.Context, arg1 retrievalmarket.DealID) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientCancelRetrievalDeal", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// ClientCancelRetrievalDeal indicates an expected call of ClientCancelRetrievalDeal. -func (mr *MockFullNodeMockRecorder) ClientCancelRetrievalDeal(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientCancelRetrievalDeal", reflect.TypeOf((*MockFullNode)(nil).ClientCancelRetrievalDeal), arg0, arg1) -} - -// ClientDataTransferUpdates mocks base method. -func (m *MockFullNode) ClientDataTransferUpdates(arg0 context.Context) (<-chan api.DataTransferChannel, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientDataTransferUpdates", arg0) - ret0, _ := ret[0].(<-chan api.DataTransferChannel) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientDataTransferUpdates indicates an expected call of ClientDataTransferUpdates. -func (mr *MockFullNodeMockRecorder) ClientDataTransferUpdates(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientDataTransferUpdates", reflect.TypeOf((*MockFullNode)(nil).ClientDataTransferUpdates), arg0) -} - -// ClientDealPieceCID mocks base method. -func (m *MockFullNode) ClientDealPieceCID(arg0 context.Context, arg1 cid.Cid) (api.DataCIDSize, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientDealPieceCID", arg0, arg1) - ret0, _ := ret[0].(api.DataCIDSize) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientDealPieceCID indicates an expected call of ClientDealPieceCID. -func (mr *MockFullNodeMockRecorder) ClientDealPieceCID(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientDealPieceCID", reflect.TypeOf((*MockFullNode)(nil).ClientDealPieceCID), arg0, arg1) -} - -// ClientDealSize mocks base method. -func (m *MockFullNode) ClientDealSize(arg0 context.Context, arg1 cid.Cid) (api.DataSize, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientDealSize", arg0, arg1) - ret0, _ := ret[0].(api.DataSize) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientDealSize indicates an expected call of ClientDealSize. -func (mr *MockFullNodeMockRecorder) ClientDealSize(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientDealSize", reflect.TypeOf((*MockFullNode)(nil).ClientDealSize), arg0, arg1) -} - -// ClientFindData mocks base method. -func (m *MockFullNode) ClientFindData(arg0 context.Context, arg1 cid.Cid, arg2 *cid.Cid) ([]api.QueryOffer, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientFindData", arg0, arg1, arg2) - ret0, _ := ret[0].([]api.QueryOffer) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientFindData indicates an expected call of ClientFindData. -func (mr *MockFullNodeMockRecorder) ClientFindData(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientFindData", reflect.TypeOf((*MockFullNode)(nil).ClientFindData), arg0, arg1, arg2) -} - -// ClientGenCar mocks base method. -func (m *MockFullNode) ClientGenCar(arg0 context.Context, arg1 api.FileRef, arg2 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientGenCar", arg0, arg1, arg2) - ret0, _ := ret[0].(error) - return ret0 -} - -// ClientGenCar indicates an expected call of ClientGenCar. -func (mr *MockFullNodeMockRecorder) ClientGenCar(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGenCar", reflect.TypeOf((*MockFullNode)(nil).ClientGenCar), arg0, arg1, arg2) -} - -// ClientGetDealInfo mocks base method. -func (m *MockFullNode) ClientGetDealInfo(arg0 context.Context, arg1 cid.Cid) (*api.DealInfo, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientGetDealInfo", arg0, arg1) - ret0, _ := ret[0].(*api.DealInfo) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientGetDealInfo indicates an expected call of ClientGetDealInfo. -func (mr *MockFullNodeMockRecorder) ClientGetDealInfo(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetDealInfo", reflect.TypeOf((*MockFullNode)(nil).ClientGetDealInfo), arg0, arg1) -} - -// ClientGetDealStatus mocks base method. -func (m *MockFullNode) ClientGetDealStatus(arg0 context.Context, arg1 uint64) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientGetDealStatus", arg0, arg1) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientGetDealStatus indicates an expected call of ClientGetDealStatus. -func (mr *MockFullNodeMockRecorder) ClientGetDealStatus(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetDealStatus", reflect.TypeOf((*MockFullNode)(nil).ClientGetDealStatus), arg0, arg1) -} - -// ClientGetDealUpdates mocks base method. -func (m *MockFullNode) ClientGetDealUpdates(arg0 context.Context) (<-chan api.DealInfo, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientGetDealUpdates", arg0) - ret0, _ := ret[0].(<-chan api.DealInfo) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientGetDealUpdates indicates an expected call of ClientGetDealUpdates. -func (mr *MockFullNodeMockRecorder) ClientGetDealUpdates(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetDealUpdates", reflect.TypeOf((*MockFullNode)(nil).ClientGetDealUpdates), arg0) -} - -// ClientGetRetrievalUpdates mocks base method. -func (m *MockFullNode) ClientGetRetrievalUpdates(arg0 context.Context) (<-chan api.RetrievalInfo, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientGetRetrievalUpdates", arg0) - ret0, _ := ret[0].(<-chan api.RetrievalInfo) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientGetRetrievalUpdates indicates an expected call of ClientGetRetrievalUpdates. -func (mr *MockFullNodeMockRecorder) ClientGetRetrievalUpdates(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetRetrievalUpdates", reflect.TypeOf((*MockFullNode)(nil).ClientGetRetrievalUpdates), arg0) -} - -// ClientHasLocal mocks base method. -func (m *MockFullNode) ClientHasLocal(arg0 context.Context, arg1 cid.Cid) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientHasLocal", arg0, arg1) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientHasLocal indicates an expected call of ClientHasLocal. -func (mr *MockFullNodeMockRecorder) ClientHasLocal(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientHasLocal", reflect.TypeOf((*MockFullNode)(nil).ClientHasLocal), arg0, arg1) -} - -// ClientImport mocks base method. -func (m *MockFullNode) ClientImport(arg0 context.Context, arg1 api.FileRef) (*api.ImportRes, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientImport", arg0, arg1) - ret0, _ := ret[0].(*api.ImportRes) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientImport indicates an expected call of ClientImport. -func (mr *MockFullNodeMockRecorder) ClientImport(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientImport", reflect.TypeOf((*MockFullNode)(nil).ClientImport), arg0, arg1) -} - -// ClientListDataTransfers mocks base method. -func (m *MockFullNode) ClientListDataTransfers(arg0 context.Context) ([]api.DataTransferChannel, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientListDataTransfers", arg0) - ret0, _ := ret[0].([]api.DataTransferChannel) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientListDataTransfers indicates an expected call of ClientListDataTransfers. -func (mr *MockFullNodeMockRecorder) ClientListDataTransfers(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListDataTransfers", reflect.TypeOf((*MockFullNode)(nil).ClientListDataTransfers), arg0) -} - -// ClientListDeals mocks base method. -func (m *MockFullNode) ClientListDeals(arg0 context.Context) ([]api.DealInfo, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientListDeals", arg0) - ret0, _ := ret[0].([]api.DealInfo) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientListDeals indicates an expected call of ClientListDeals. -func (mr *MockFullNodeMockRecorder) ClientListDeals(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListDeals", reflect.TypeOf((*MockFullNode)(nil).ClientListDeals), arg0) -} - -// ClientListImports mocks base method. -func (m *MockFullNode) ClientListImports(arg0 context.Context) ([]api.Import, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientListImports", arg0) - ret0, _ := ret[0].([]api.Import) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientListImports indicates an expected call of ClientListImports. -func (mr *MockFullNodeMockRecorder) ClientListImports(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListImports", reflect.TypeOf((*MockFullNode)(nil).ClientListImports), arg0) -} - -// ClientListRetrievals mocks base method. -func (m *MockFullNode) ClientListRetrievals(arg0 context.Context) ([]api.RetrievalInfo, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientListRetrievals", arg0) - ret0, _ := ret[0].([]api.RetrievalInfo) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientListRetrievals indicates an expected call of ClientListRetrievals. -func (mr *MockFullNodeMockRecorder) ClientListRetrievals(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListRetrievals", reflect.TypeOf((*MockFullNode)(nil).ClientListRetrievals), arg0) -} - -// ClientMinerQueryOffer mocks base method. -func (m *MockFullNode) ClientMinerQueryOffer(arg0 context.Context, arg1 address.Address, arg2 cid.Cid, arg3 *cid.Cid) (api.QueryOffer, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientMinerQueryOffer", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(api.QueryOffer) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientMinerQueryOffer indicates an expected call of ClientMinerQueryOffer. -func (mr *MockFullNodeMockRecorder) ClientMinerQueryOffer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientMinerQueryOffer", reflect.TypeOf((*MockFullNode)(nil).ClientMinerQueryOffer), arg0, arg1, arg2, arg3) -} - -// ClientQueryAsk mocks base method. -func (m *MockFullNode) ClientQueryAsk(arg0 context.Context, arg1 peer.ID, arg2 address.Address) (*storagemarket.StorageAsk, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientQueryAsk", arg0, arg1, arg2) - ret0, _ := ret[0].(*storagemarket.StorageAsk) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientQueryAsk indicates an expected call of ClientQueryAsk. -func (mr *MockFullNodeMockRecorder) ClientQueryAsk(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientQueryAsk", reflect.TypeOf((*MockFullNode)(nil).ClientQueryAsk), arg0, arg1, arg2) -} - -// ClientRemoveImport mocks base method. -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) - return ret0 -} - -// ClientRemoveImport indicates an expected call of ClientRemoveImport. -func (mr *MockFullNodeMockRecorder) ClientRemoveImport(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRemoveImport", reflect.TypeOf((*MockFullNode)(nil).ClientRemoveImport), arg0, arg1) -} - -// ClientRestartDataTransfer mocks base method. -func (m *MockFullNode) ClientRestartDataTransfer(arg0 context.Context, arg1 datatransfer.TransferID, arg2 peer.ID, arg3 bool) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientRestartDataTransfer", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(error) - return ret0 -} - -// ClientRestartDataTransfer indicates an expected call of ClientRestartDataTransfer. -func (mr *MockFullNodeMockRecorder) ClientRestartDataTransfer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRestartDataTransfer", reflect.TypeOf((*MockFullNode)(nil).ClientRestartDataTransfer), arg0, arg1, arg2, arg3) -} - -// ClientRetrieve mocks base method. -func (m *MockFullNode) ClientRetrieve(arg0 context.Context, arg1 v0api.RetrievalOrder, arg2 *api.FileRef) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientRetrieve", arg0, arg1, arg2) - ret0, _ := ret[0].(error) - return ret0 -} - -// ClientRetrieve indicates an expected call of ClientRetrieve. -func (mr *MockFullNodeMockRecorder) ClientRetrieve(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRetrieve", reflect.TypeOf((*MockFullNode)(nil).ClientRetrieve), arg0, arg1, arg2) -} - -// ClientRetrieveTryRestartInsufficientFunds mocks base method. -func (m *MockFullNode) ClientRetrieveTryRestartInsufficientFunds(arg0 context.Context, arg1 address.Address) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientRetrieveTryRestartInsufficientFunds", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// ClientRetrieveTryRestartInsufficientFunds indicates an expected call of ClientRetrieveTryRestartInsufficientFunds. -func (mr *MockFullNodeMockRecorder) ClientRetrieveTryRestartInsufficientFunds(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRetrieveTryRestartInsufficientFunds", reflect.TypeOf((*MockFullNode)(nil).ClientRetrieveTryRestartInsufficientFunds), arg0, arg1) -} - -// ClientRetrieveWithEvents mocks base method. -func (m *MockFullNode) ClientRetrieveWithEvents(arg0 context.Context, arg1 v0api.RetrievalOrder, arg2 *api.FileRef) (<-chan marketevents.RetrievalEvent, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientRetrieveWithEvents", arg0, arg1, arg2) - ret0, _ := ret[0].(<-chan marketevents.RetrievalEvent) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientRetrieveWithEvents indicates an expected call of ClientRetrieveWithEvents. -func (mr *MockFullNodeMockRecorder) ClientRetrieveWithEvents(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRetrieveWithEvents", reflect.TypeOf((*MockFullNode)(nil).ClientRetrieveWithEvents), arg0, arg1, arg2) -} - -// ClientStartDeal mocks base method. -func (m *MockFullNode) ClientStartDeal(arg0 context.Context, arg1 *api.StartDealParams) (*cid.Cid, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientStartDeal", arg0, arg1) - ret0, _ := ret[0].(*cid.Cid) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientStartDeal indicates an expected call of ClientStartDeal. -func (mr *MockFullNodeMockRecorder) ClientStartDeal(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientStartDeal", reflect.TypeOf((*MockFullNode)(nil).ClientStartDeal), arg0, arg1) -} - -// ClientStatelessDeal mocks base method. -func (m *MockFullNode) ClientStatelessDeal(arg0 context.Context, arg1 *api.StartDealParams) (*cid.Cid, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientStatelessDeal", arg0, arg1) - ret0, _ := ret[0].(*cid.Cid) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ClientStatelessDeal indicates an expected call of ClientStatelessDeal. -func (mr *MockFullNodeMockRecorder) ClientStatelessDeal(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientStatelessDeal", reflect.TypeOf((*MockFullNode)(nil).ClientStatelessDeal), arg0, arg1) -} - // Closing mocks base method. func (m *MockFullNode) Closing(arg0 context.Context) (<-chan struct{}, error) { m.ctrl.T.Helper() diff --git a/api/v0api/v1_wrapper.go b/api/v0api/v1_wrapper.go index 265674e71..97b8ff597 100644 --- a/api/v0api/v1_wrapper.go +++ b/api/v0api/v1_wrapper.go @@ -4,21 +4,16 @@ import ( "context" "github.com/ipfs/go-cid" - "github.com/libp2p/go-libp2p/core/peer" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" - "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/go-state-types/big" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/v1api" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" - marketevents "github.com/filecoin-project/lotus/markets/loggers" ) type WrapperV1Full struct { @@ -210,158 +205,10 @@ func (w *WrapperV1Full) ChainGetRandomnessFromBeacon(ctx context.Context, tsk ty return w.StateGetRandomnessFromBeacon(ctx, personalization, randEpoch, entropy, tsk) } -func (w *WrapperV1Full) ClientRetrieve(ctx context.Context, order RetrievalOrder, ref *api.FileRef) error { - events := make(chan marketevents.RetrievalEvent) - go w.clientRetrieve(ctx, order, ref, events) - - for { - select { - case evt, ok := <-events: - if !ok { // done successfully - return nil - } - - if evt.Err != "" { - return xerrors.Errorf("retrieval failed: %s", evt.Err) - } - case <-ctx.Done(): - return xerrors.Errorf("retrieval timed out") - } - } -} - -func (w *WrapperV1Full) ClientRetrieveWithEvents(ctx context.Context, order RetrievalOrder, ref *api.FileRef) (<-chan marketevents.RetrievalEvent, error) { - events := make(chan marketevents.RetrievalEvent) - go w.clientRetrieve(ctx, order, ref, events) - return events, nil -} - -func readSubscribeEvents(ctx context.Context, dealID retrievalmarket.DealID, subscribeEvents <-chan api.RetrievalInfo, events chan marketevents.RetrievalEvent) error { - for { - var subscribeEvent api.RetrievalInfo - var evt retrievalmarket.ClientEvent - select { - case <-ctx.Done(): - return xerrors.New("Retrieval Timed Out") - case subscribeEvent = <-subscribeEvents: - if subscribeEvent.ID != dealID { - // we can't check the deal ID ahead of time because: - // 1. We need to subscribe before retrieving. - // 2. We won't know the deal ID until after retrieving. - continue - } - if subscribeEvent.Event != nil { - evt = *subscribeEvent.Event - } - } - - select { - case <-ctx.Done(): - return xerrors.New("Retrieval Timed Out") - case events <- marketevents.RetrievalEvent{ - Event: evt, - Status: subscribeEvent.Status, - BytesReceived: subscribeEvent.BytesReceived, - FundsSpent: subscribeEvent.TotalPaid, - }: - } - - switch subscribeEvent.Status { - case retrievalmarket.DealStatusCompleted: - return nil - case retrievalmarket.DealStatusRejected: - return xerrors.Errorf("Retrieval Proposal Rejected: %s", subscribeEvent.Message) - case - retrievalmarket.DealStatusDealNotFound, - retrievalmarket.DealStatusErrored: - return xerrors.Errorf("Retrieval Error: %s", subscribeEvent.Message) - } - } -} - -func (w *WrapperV1Full) clientRetrieve(ctx context.Context, order RetrievalOrder, ref *api.FileRef, events chan marketevents.RetrievalEvent) { - defer close(events) - - finish := func(e error) { - if e != nil { - events <- marketevents.RetrievalEvent{Err: e.Error(), FundsSpent: big.Zero()} - } - } - - var dealID retrievalmarket.DealID - if order.FromLocalCAR == "" { - // Subscribe to events before retrieving to avoid losing events. - subscribeCtx, cancel := context.WithCancel(ctx) - defer cancel() - retrievalEvents, err := w.ClientGetRetrievalUpdates(subscribeCtx) - - if err != nil { - finish(xerrors.Errorf("GetRetrievalUpdates failed: %w", err)) - return - } - - retrievalRes, err := w.FullNode.ClientRetrieve(ctx, api.RetrievalOrder{ - Root: order.Root, - Piece: order.Piece, - Size: order.Size, - Total: order.Total, - UnsealPrice: order.UnsealPrice, - PaymentInterval: order.PaymentInterval, - PaymentIntervalIncrease: order.PaymentIntervalIncrease, - Client: order.Client, - Miner: order.Miner, - MinerPeer: order.MinerPeer, - }) - - if err != nil { - finish(xerrors.Errorf("Retrieve failed: %w", err)) - return - } - - dealID = retrievalRes.DealID - - err = readSubscribeEvents(ctx, retrievalRes.DealID, retrievalEvents, events) - if err != nil { - finish(xerrors.Errorf("Retrieve: %w", err)) - return - } - } - - // If ref is nil, it only fetches the data into the configured blockstore. - if ref == nil { - finish(nil) - return - } - - eref := api.ExportRef{ - Root: order.Root, - FromLocalCAR: order.FromLocalCAR, - DealID: dealID, - } - - if order.DatamodelPathSelector != nil { - s := api.Selector(*order.DatamodelPathSelector) - eref.DAGs = append(eref.DAGs, api.DagSpec{ - DataSelector: &s, - ExportMerkleProof: true, - }) - } - - finish(w.ClientExport(ctx, eref, *ref)) -} - func (w *WrapperV1Full) PaychGet(ctx context.Context, from, to address.Address, amt types.BigInt) (*api.ChannelInfo, error) { return w.FullNode.PaychFund(ctx, from, to, amt) } -func (w *WrapperV1Full) ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.StorageAsk, error) { - a, err := w.FullNode.ClientQueryAsk(ctx, p, miner) - if err != nil { - return nil, err - } - return a.Response, nil -} - func (w *WrapperV1Full) BeaconGetEntry(ctx context.Context, epoch abi.ChainEpoch) (*types.BeaconEntry, error) { return w.StateGetBeaconEntry(ctx, epoch) } diff --git a/api/v1api/latest.go b/api/v1api/latest.go index a1e63b6ad..aefb1543b 100644 --- a/api/v1api/latest.go +++ b/api/v1api/latest.go @@ -12,5 +12,3 @@ type RawFullNodeAPI FullNode func PermissionedFullAPI(a FullNode) FullNode { return api.PermissionedFullAPI(a) } - -type CurioStruct = api.CurioStruct diff --git a/api/version.go b/api/version.go index 124f53dab..9c2113578 100644 --- a/api/version.go +++ b/api/version.go @@ -59,8 +59,6 @@ var ( MinerAPIVersion0 = newVer(1, 5, 0) WorkerAPIVersion0 = newVer(1, 7, 0) - - CurioAPIVersion0 = newVer(1, 0, 0) ) //nolint:varcheck,deadcode diff --git a/blockstore/splitstore/splitstore_prune.go b/blockstore/splitstore/splitstore_prune.go index 08d5b8cca..aebc3c001 100644 --- a/blockstore/splitstore/splitstore_prune.go +++ b/blockstore/splitstore/splitstore_prune.go @@ -47,7 +47,7 @@ var ( PruneThreshold = 7 * build.Finality ) -// GCHotstore runs online GC on the chain state in the hotstore according the to options specified +// GCHotStore runs online GC on the chain state in the hotstore according the to options specified func (s *SplitStore) GCHotStore(opts api.HotGCOpts) error { if opts.Moving { gcOpts := []bstore.BlockstoreGCOption{bstore.WithFullGC(true)} diff --git a/build/actors/v14.tar.zst b/build/actors/v14.tar.zst new file mode 100644 index 0000000000000000000000000000000000000000..19b023f1c84ca9983ce5c7786f6b9ffdb347f575 GIT binary patch literal 1646079 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
vziWurl3m+k^AB$L+H0y>cLl(M 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

KS+f8&6-9$IhO>`59ppXKG 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

ny+!A8cKS@Tf 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%0AGF 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%0ASXO-`WJ#T1>ioOlGz=B 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

g@p^qe3*}_yuW_%y!BZvJh+G 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�&p6 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

V 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

V7e^MD6PDo8C-q+pSv1<6E||q%p(o=X+IX` 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≧NRQ%v zS}qP-@L(KaHe>4#>$-|aRUcgGhxzm(GtsFJ#4*$ALxHLs#QBtz+Ci3Eeno65t2lvD zfBzVpkL&*$m;>z+|L5Ml^<*P&hhGj?&pu^0BSrq^pfOV8EDx`v*@wvQWr6)Rb%VQT zXfNvr^bI@@_x`teD^Y8FN;+R-r{4#M;7ti#2>+xbf;x{ElrfP;>);YV1`m=8CCuLR zBk~YgP9`a^Vk!sT5oEN?BBaJ1VaM}#d1?HJc()0JzsFkKB$vHSfUNXX zdwkmC1Gw69@|OP@j(Nmjr5gtwm!8fP=stF}EimzX){eVxA6O%40-}tF&%VV$pm$%ou+8|m%|whVD!@+02=kaLLhJ)-#X$CI6INO zt3Yc^Suv5CELQEwz$Lh@EFImt@Oghh0{tu+#UMdOs}epLqS%73hR>dOzRtwYSc*Ab zpQUY;CB5bfFhRh~;Y!i`(=t?+yN5n;e}ek(A!??_$2uB9+!f%i*0~+Hd_)!;Q3>EQ z%>u4x=I*AAGUgk*4|uIUoz^9OL%CcI#sV!*J;UgMoE?89W)Alm0pvSca-Q52YvjH( zBnEvZd@k)~6zxF+KYaee((upKy?GoNssWh$0NbN7ePT9X?Nzfr*m-@Sv1F@Qjh7PM zEt$X^Sqo&OjfM7mC6+o7;SF1M{&+IK+qkU0CzkNJ1C6Krnsg(?3QYn>!mJ53G1KNw z9n+_Ei0P9&2}0UG?#9w?2I>RfDp6x|MVViU2AEd>MH6E6eQ6U7smw?=F!xChRMa04 zbLVDkdkj*NNt&3}A*kXM>PJTV2_={78e`{%3IMlj z>l@&seG717wsgXMys;JHfGUT{c6a)9lSke*UxmV_h6Zo7^hHy-OtxaQ=SCZ%HK{`| zFI*ep$1Q^?GRe@6E?T9d9J8BzMZJ*Z`@AIenF$siRPPjb)#(0v^gf4@YAS#=!QrGv z{G=ULa=G+Xd}CPEnS4V(lXZFu+RXRN0?*TXaIUz3B%sOih+QGdW3)Hvm4jMX8k%b(DqoNF%u7HP5y0IJf@z95b7@SR{Iu4@wV+u_QI zVTi$iB#W#9UUh-wWI~U0YgTt$LPsTb4D;^W3k_&5&7V;nAp;gr|5f^^oS?)mS(#@) zf&WU7eqC|+1SN(T+0H|i8W30tu!OT9uuK6XXAi*MnR4B7oNoU6?9$g7orax3scwc6 zQs~J)dt{V!`FOUU3J_s2AIV0Om$&O%W!()h)A5jf}>N2XVkz*%yxVOxgt03i{U12`OztJ zGb=e=c-Kw6fSy-DbJE$WLNPv{VG#!)%sHmlXZXSTa^`i;$8|<7T zU=qP+z+5^7u$-KXtJM9hI3NMJaY!8gsWH=UBgK6Gf4mD#>_g4 zN8O(T4dNL)mcNb`YydZ?3Z}FNL*rk+>>`Q%6tr2$8ysG@9|=2>#lc46gY=&hgumHi zbL~#J3)xl91^B1R_KPyX0iur>XJRFY^LSK$Oh5@SWAjvHf-K02U-%88&3I@=LUtf> zvx3P9;Wqb%V`(Us4Bi%5VPmZgqGHaJ~7wq*i#%rfhXxv*J56D+@GY?C}UcY{|joh#xXeFb2 z6rUJ?P1JbX0dAPO=eTfYPy-{3Mr{aMYLR}iP_R=I5w6;QDX`IWW2iL@42~K}J`rPa z`~K6CI1QS@G;JEn)xG*HoW3T*5JBfG8US?Q>YOlK_0}omq+NvszI3lY6Qam$uOFnj zN|J+lMNLe+UPdDrdB7bK2@`B5`tbCikJvz|R0-*jjy{L67vc7@#s@Z#qay&G?IK)$ z#vtAjVBi%$uKyVj7*L8iFh%$zG-IJyY?H|vJAAC!xf`5JpY7Du5nuJ5v^M4S(iKyGr0;1{tK2c{9BQmm8ZS&2(fs2mCa@N!476iggh z*(Ljm4)3^`64>YGS`lf--!5Hx$Jo2Pq)-bxfBuLuMqc=@DeT>jy&ZcSVs42^Iws+k z$#966dPzhJ#M}THSws^`uo!g?5XL5MAtSAC_I8U%FETAf~z<(yTn z!UHPvr;MR+>IX7R>d(mpfB;ZU_M8|(LjOLjl=U8i<&e=xW-kgs$&Y%DD=X~X>C=TshDvh;ZDXqWTbQ+!Q zJ|0E;+Sk7JUh6tn=kndJFTj00(>Cr+I|$TvY{P7}Tu#J8`EP(J+D<=X=MCuH88hCE z%fH;F8%VQy-@|3@p?!*z!$jGR9KMR;^OdjQV&?(2{@e@wzM|G!>m9aiuNR4O(x&ol zB(2Cv>xy+w7uM(L;)5g4lK`8e_ljJXjv(tr{06=PJg zFB9GHhWLtzk;U|0ZD-O4t>0Uh0fOs?QUdaCF4&%+Yj5Y48l^M zx7@|7Gb+=K3`I>S-qG=Her|wHsz044&Z!3JW`d}6YsEumrLOeGX}#fs1zGH0?^@wl z!De79pTBJ!xOY1j<#wO)4`ciQNI1=_s%&fF!fdyy(u;)v z|BFVXOww{5_d76t4+9h6FQTbD0c0XlzJOPI|-@<>QX1tj{lSGt%I~?~C`Lq^erb#N5#FB@S$;=`qkw{`$mPlo( zL@H&ps~Q!J0yczX9>DPNe^}vVY}mj7157H@pBTif7zG(U@qTjN8zOGnSuSUEf`Jgc zI{H*0lVGkeGWee>IfN2JhvM95>}u$6ST!DJbQC&qPGKLPC^AlDdSrNHc&-TQ&V`a|7G)gYGjeUPB`z}5J4_(=4KbkV_*QKk5XR2|hvZB-|%mkE~I z6U4GC%WARF$Y@+E*$T@J9v3BefVhZUMLyOQJZ!4tt-KJZos zS!?EJ<&!QsF?0eqjz*Z(x~A^5TB`s5|1o?Oi5sj~g9SRcqsv6+5PxT3_c&UA_m2&jYrL+kwKQ`0Ez?@LWTf9 zi~sm1L3q;YDvxO^(7kp%HPa~xe|zv5|~VjvzN6wWp3ZLGakzH zvj_e&N2w#B&&1cKagO_IQqA>e8M%)22neJXh5AM5`@tX$aAbCo;=U~jHf861dhKVZ z4(X@GfRN3k*XXLC^6>EPTChm`cU4eV8P;e>(bstd3nUbIZl~YeXXa_qL(+X(nB#+v z@T!N3kP|!5nRO}W4@D8#dAc!|h)8G+hH`C|hK~;-mWR8;7gVTzIH!+a+MImu{jn;T z*RjB8`i00FNDA-E`d6Yd_D*oCK9}Rq_=S(6Oygx(FW!h<{3qK>o+i!4T-EqnXqAYa z;N*c8qRI%<+M1{`@E@v#)mYFS7+wI;$<-aHCe$KUq>1O)tffAB?V1_o(&9S5I1o!f z@Zw1)M+4i}0I$iYh9lzV>>qw;whobK*bp_udmAk3@&&9pwWZBWak^IHXs3ZSrYbj5 z>;nzP6X;D_nmC`=_poke8thvo~k@<7dhEh5{uh6W28MVfIdQ4A^Kjnbjq&c2*on}@ zwnf}#NNNj9S+Zjir|A8Xb#OmOMwmFcXArc%=ObdRo+KR40t_nE@~ut3Kz&(KRb4opYqS}W zJU5cs9d=p?iJ95$A~J4-EfSq2riKj~=0x)tCJmvVBYJ6`i}TgJ;lU^i$vzQ3kNIo45I2exTS)b1N(>Xd z7*=iX=9Ocm`sqA5K;3J-(SVS{J>XQziC!s$_7`X8IsXtP{`5$s=S*rhxFhxJMHH?D-QzB2A*na~Pb|8UTOGi&(r+^~Ka&>hasmcmk! zseGIT-wZBu^TA$mxz}Y}=tGKSfcn0v7V2|>xP)-yA(Pz1Zh^Bv?mb~^`&023_Ubc@ zvov1PHoE6*O9av%Juu=9hx!9SL{$5Wg+C0?IhJY})Y$z172xT?EU>+opO9bqquIEE z{a@Pl@l9q{ci43E6LdTV>&dmVCf~?9ahjl*F7zEPDc-iX0!WI<6MDY{)2+$ywZN3v zA$zTt@o*XsfK7U80s0h3^p{s~3;0cbC0W7W3lZd|dF>Z(CY+q4-YEac7!Fi$AB#Z12)PI(@8EWZ3iJ+W(2W9h2y>&f zxsOGwS1MCeiWBBN%Oy8(^EKppn#CBKx4khCn@cuIp%j<;g*mSIs1n2Fw>L^D-q$iw z^h*Yx5{L}p&M2jF^gVT-;@A!qttj77LkO+lU{ka;O+(;6N90Lfk^I#)O0nnX28`hU zIRj$(e^^SrQcMSFUzQ>8)2$uX1&`ih<{!Uxm+t@7E$N%1pu%oDAPVz`nvQ1hZ8L6) zf|x~sdE}VZmWjD}*yR|(^|fq zbJ2Wl_i`oHBZb_LDbq=_Aj14IgH&kE(b6kGqrkwWV-lDm(&#hd)MwnR@XAFSfSpF9 z6vKG`wCC>{JI;LMMMKM5F%N}%RktmaD~yqJn2C%|5~drL<%nFD+D?loCR6x|(HVt$Mb-Y5bME~WODnHLF0fFy~HI4|EQ(sCsJowoq? zsIu|^r<9mTew3hM8-ADv^N?PGn1GLjA(s=FUlPDGL0C-M9RdV|>THhA+D*tOr+wRZ z;~Loj+l{@3^8s{zi(Jp6Jr?*GrxM9@;9gh9>8-GmdbmehBT#H5G9JcH|5dlKG;`;c zurM)%lS=f*6&et&+~$D93g9U@5`;=ry|jp?D~&~_XirW!>+291u)Gtz@TlEeq}?vl zQZUjGXXOLgPxH1pAS>TTR*b`Oj`)>lJ6I71s(YlTHq()7Il$}KGUSbAvjF;vyAep6 z&LAh!!F_Z+_e8TZ=|9rWlTT!p*vF|(_egE>^Jj6IWX7(Jm(KslO30$ao|8NIM6HaG zo8UAD0~Qq(6P(=dW%gxD@ik7E5W+2xZomCQZv`6sLvAmUl-g}a4T~_U^7OV1ksr>w z9Hh4MkJlit{ct^rsP`mxltf zv|mZl@TyTzA?IFQZ&i9Lwn?ta>nM%RYFDIcy9y&INdR*alio0^ovz@j_-Z7zWAIHx z=KczzQ`aZ3EEjreza!Ek@Dvfj@( zs106A*Is3@af}2v6mc2LA?V1z%BB>35`n2>S;HdOk;Ge34Y)-^dmi;sbM=)B3QmaH zG8nD41k`W4Y^Ulx^ zj8M9_#~~d@qSuEVp08R5dU(SwfCaCZwKwAB@D?-Gpg2RCdq~MXRhswWKIrFLY|beT zu^>Xcj{o!9iAw60FoI$>KYMq~v^}B8GVxK-kWkHn28z#Ziu_ z^MbY+uI1at;$c>ved1<)H^ zDJ>E5hZ7Nji#H@#cJ$}Y6!pq3t6>onmHXi)Yis3$;2TqYt{l?g5Lwdl(K=)$3fkhj(#I0 zaYx>Y`?ozh1FtX>X*{-|yqp$@1R^K3F>S_HDgRCw@SBKjL;F%>fE4+E*(pV&b1wN{ zIehSpw*o*Gla#OdG@a5R^np0mb~8C#Uy0$mUlf5LXh=!yS0G<6UXz#Z%Ed@cqg3E| z)OeYn%8Z5oO~*rxQg{z@5O;o?Ji^1oI~K6Jt?-vb7IG^Fq<_o@3uo#?o?mx-MM$B^ zLl)gHgFgNb`iYHqJxeJ!>=2Y_vt6Piz*ia!f*Q$yVxCY*!G7pin|Xg?DisQM>{_F|a(mWf=AFWEOd8TWFnGX|+^} z=&S!${i~dzczIGQg}Zh2TS!^9Nr%@q9w9Z6G-;r%uv!B*agrAoIkfctx9IG9keJKM zB)$?UWmSGiagQF#i}_V5m>Ei(LsFx9K4~Z?k{b#xBATB1x(PyyZoa(uZUj;;0%bulqd)$qn{Vu+}KkY8VB z(LwU~OJ2w`xK0yQKFboz8(nn|y@3sb82BOKhT6CaByM7R)hc<0#n&QWMk(4s7A+mV&S}f`cjC|Qd8dgd5Q#)MWKOO%08^ew!9ms-(|WSG!as}_+UpLDt~ zj!h|fvQ;YPxv9`|YtS3kY|3lA4{nkDC@M8xA&L>7Mw7vtKe=u^6lS%iUNY%f?o#*< zUK3S0#&FsNyva8YG6ub^S07w10P*y#1mt9%Hdo8_``5jXP&W+b|D22?<^ib0`<~NP}|hDhh-QxvA4bn}041W@ED+ zXd)b0;*AE|5;7FSLU{pOzXd6L6kbW{h+}U+8!z%+X;{(o51on{qsy& z&Ky?q^Q-nM(1O50X-_SR|NA%K3ou~PIV>}BAGMVQS5spB6b#U|Unp2PnPhni3(<&G4am z`+&Uju)boTgJE)uld`{V+Zjv2-)t!35_3+9Q4&z-fIQ@92!+QtC_q_0f0H7B2uN|5 z^$qBnI#7<8)QMoX2__eSz0&=urOAq-6|DT?WR!ToI)Etz0kfBSqcHQr^Xpn<=J`YV z<*l^jE5QouwVWa9)@O;FN!hJ0;X}&FO!6Sl5}@y4AUN*)ZJIB?C65$6-&Y=Q)^NGw z23sW3+_kuG&g;Y;Joj7w=~;v`PuIVq%d^JdgYK;tQOCNBzzNBR3eYGR6uas z-n&+$!>1m*=qiA$%N3`pX0ED;Az?Y24(lKU`O~>P0Anr!C`?&N$&VaURDO`Sbc%fTrD8Px{j_03eT)P)corKC`7h|xsV#P=^2k-$sSSK`J zUM^tzJy4SKHGs$SexL1mg*-{827p^H%TapORfq2vK?XR#g+>vovQK?yGe9SZ89LW> z;Rd(T0EJd^Cr|DC%lcAjjSe|CvoXCiRFCK&5M{;G2b_e@4S4Yts zX`|TJ@pmvWJ}A`Z7nE|%rk<2rP;0`3r-E4jEHUrq0h^dP#!V@<=ZMMtV80Z|XMjNi z&5Qs*b4Xj20Tg3UaG`VE%0ah|U^OH75M3@)aX4ksPFqcoH8=I-{%_RCOrNmwpE-A} zul=?LBP1)Ms#DHS5xUnspxO&)#H8A==uyMeu2&-^#4Gwm7P#>sbJ#tjW_R==E7_(8 zqs-sl56EJ_CLSCH*FkBd>sXD#gA3$Hba?6GK`4?ilSw=nh7x3KVT1cw=NmVX}b)P4Q`9h((?LNhMS9Op^|w>jsne{rq0yfl zRg<|?Y%$uNtbBbwA&Fw#{|Svl4X&1aeUt##uwXTS)~5pk-np%PTrGlBnUCO(mO1zN zMa#b4>>-@h!br=_i~49|$>2cav_w0g&lJ!ZHh6a`(efI~MI#JWwdV%5`k-56;*Crv zWYZ|#g~wFj!9m)=gejGgin%-$wt-JSMbOFq*oUTj!7+Sd6rv9XkIVpc>*SSN8f#(Z^v z4_#Fj&|wzGn7mGoMrFOlRw;PLV>YY}8^{rU6j`7evSnE^%*ITWI&1A8C5;_GVWMok zM`u3x?sT)QGowF6$`tsj;1Z?PjH8V{32mA@bq4j1et0T)|MGdc*4E1^Hd5L!-W%Za zcaiPSC9(Qu6sCcFy?`p9o6-@Zi^r&eOnWU9T16BSvgAkcLmfK7MAKwWGTJiv{N@4F zL~dC`!dJWt`C(0||CUTA}ZX_S`&fP1&r zj?x%s+!g}@J;~7BHjac7-ZAx3Jnh0=vDaqWR=7zdNvn0)5jq6ihYhgUI{eA0tuCao z<5W4z_w{c}=&U0keC(u+UDjQ|L22)6BH-MthGiPyAg&V*Mp}w;44Uwx21f;~;nCl! zNSZ*q8LlE+Q=Nzt6Eg6f8;g8rWd5v=oN;8jK#xU?C}zAbU`vqtV$e3leizd?nspj! zT5@jc2QGKdOTAgGjMxm;wD!JiZ1CA8BbEId*ba2Hl|6Ewf=W#jg>|i&U5ut?U+~9G zmtLi?QaLl9&kn{GnN-7UAy(Bfd%*9z9-WABV#MSeEQs0gjPp#>1DFj~8yeEo{xp)z zz+D(%z+uk>nJ@D?m(XD29zv8M=v<^y9!g7M|Bn}TqJrK8e#ciNh6#9o^gEG0Y+7?y zg)pyv^{&c5*JVh#zA@C4;9fqeIxSM|B9L;7`V`_5nYX)a*_~~J!kH~QUIsQww0Xd4 zuejbni+aP1>M0pFOMFRSx6rU2#Op3cFR&z=Rs+RTv1@{%CUb(y&mENjet-?p{F08f3vV34$z(ph=K8Y}nV3drW*~3I)NO}Y!1mcLwyf9#^r5<= zP>IYDwe27+)HXKBrm!W*wEG1Y@_5;0_-xmAx}vUxd3OxcH&2rh35m(5JFZRID1`JN zQ6~~cqF^iX<$Nh)5AT^XW%En$1t#`l%%L7q5tT|L<Gg&KDM+oYKLZMP5S%XBauL6*kkQ2!O#?Ap9mW_0Y4G8T(Ff&uq%hsQsw|K(> z3(XpYVQ3wm&#?at%|1Gw&a-<7V-=DZgRbl9(De4^cdNDDIQEU(`wCi4XNN@Nyx_vl!;_w;qtkVSLYxh>^&12$ z*mTp#w43^DYu9a&r^I`PmEWh`A3kVd@3xBTx~1Ee7*e!ni6V)dD-*gqB<_!!vsr(0 zpG!*UnMAP1Bv{UQxld#!PX{6hB4^yixWcbyjg_Zv`(XX+zITFdjjb9}XA(rLrFQp* zo^QwN0ekXrqwJMb?-54bOY9ahtp?tOIG!k7w>4qwZ;E2LWA;vyQj;t<(U^Fp89#&l zCH9jZ>Eo|q^E7N;Xhhw~Mie9J&vkt<6iqyc_sFZM*=)8P%EH3JqC;8G^E}TTih_cI(g8&|QTf6Qac&8$Fz<+0Q$mMvKb3+? z{iai;Q>jd9TTU9w$}Po)qGH}w;#`|)$Y8R;6*DTH*pP5XIHZ|AiSZG<7U8ofE1p6v zM0`nvI*14)ZsG|shNzlQg5%?m+{(jOr>GYJb#&n2T;fn4a5yxV2!|}u3ib@`o=7#3ZF_a0-r%((8?_+$6gXZnbb<20}89(PsY+u4@7v-h8AK{Bl_~-wz zk^i@O_W%F?|DXMTD@fXFvnZf^Y{E_7Rk39B7&-6CF>Ek!z{AIim>5opX7TdBN*wZyd zeQ1uCJ9VdtGgVd9s;Xx-Hp@pjHpXxfeur;QFxavzu=h1heJIMy<#gwXGtJUg%4RZM zB0|q1VzYddV`B^#;dl7@bP7ks$6s9WMZL8L?6K_{|SI%P=uCSo=Ju%JJ3>n#P9Pfn8~DcF(X*UkD0Vx|Ht(Xnl|`1(L78YKqw? zd1>P_cz1gXQWwKV$SSOuG%!Doa#s9K6sdf!8QmfFHr%AsGw?F)R9bDO-9u*+8Ep{i zi+w}0dAS)nzgzngsuEePk01r7w3{V?8n(A}DjaF=)4n0wIM0dy9!12bt<1vM0dL*Z z3FFqN8IfY3meRou7}1nNlr`Ym^ViS-Cc+NEU|BU1N;%+41@|~cA z%Jg3VWja`7GTQ!u3v0zOEy7_1JfxUi@b))rhw^Y7ysTse*x3Rim*=7ptL*$hMr{0y(bR;X z&y}fjyirR(+MH7{#cAc1ASOX*17=}OfwjuZ4IV-@nawrji<{ekM5q`!36Cp_Rty-I z*S4QWk{fpmc=^ala?vjGHb6+E?cxM*Gp?<)t-U($P{;5@Nw=sGqkBe8b|TsrFtUd& z&yDxRlr$Ypc@8fP8Vu`2!g+kl#?|$XG4wMK;C(HIRB9ZB6M5u?`LoxP1Y*kqM@MdL zVRvdCBp;Kx6On-vV}-V8ZD-LNB;I35=^}FJA0_m~S=PSrREq+1un1Kc5wE%`W4mhi z-xL~_<`XB*Y1kfR;7zc{hP@fwTLk4P{f$A z-`LH;KdDs;nTOZi+P<8PGIdc~+dv3zR6KZ6gCS0-jMYD@)$t2U!5GFKJDzdy3O61^ zn|*hqHXInzm@SfWL+VYE6q3*wtFZF@H2P!9$P zID~;OB@*FICv3S(E9(5@SmTMWv-Dd4n%9Yg*U?lg*K3D%-K1l-^wEwx%;;9tLUuk2 z3bV#@*IaVgHmP!-`!75U!#?x1a(lQ`$uVxX6WN(poN@v2M?tl>kY0NZ2jYE$n)*BW zf6y*#j=6+jutfk}Kz>*W>gSNDZ6aJ?aDWNqlUHXw8YhCIZ5EM7ApHvK=Axz7`G>M_ zov0uoZRwXQe9zeQAf;y-oFqM5^hC2-ZxDFaz~%Hqsydv zrm72cO*LO_^E``ZS_p%^0qHpkh!8(aZC(>bv55$_x~KN9dD0jo^l=eR{^#03^Qx(~ zh|PMtrmfMA{~YUeIvmlB5c3^D6r13%UW*dLI`COY**ZwVpgx=$z6_(xQqT8lTb;IE2H? zJoQ#YVjsb+;$b)MaNJR7i8=Fz-)3sO!WE(8?vLV0iGIQBc|)`BMvBtDJZ9@E5p4Fs zPQzrs5PL01&G~(@aI=ceo>yI?QziSl*2|)uIJu@T$iq%NvCYS*Dpr9fmz#kQnLRtw zc%q~X0}mQ3hh2cuG?ICLZ6IHX!aX|OEmv>|B~3Hp6*J?!0J-ccecX5Gs82jp%?PH{ zXdbxFw-arVqhY?0@N(8Lor8VI)$isAhXD#@$TdgmPla@7nEr1(r~TTKTgV2xW;T^Q zDR^|1YAqx6#vm3v;W{s#Qd9@RG(XQJw%pM3G02eko~zt6RaHV=9z9I(C4{bHO>P83w#a1)d-g9v;7 z32%8YoXD4oaIy16Xq}Gs2xJ^?wOBk?`Z6EbIqSd;D|^4=K8!hTc;MphA?I3ToPwRk za=1haA!2XP|JaG@bob%s(JT&oqb7Y~vED56p+!BlU{5UntpJS;+3dib71FH%Qn&re zBs4*h8s3#7{xniNf*;RvkUXtuaUDAVjviAtZtOor6vc7WI1so0FGdu~W;HV`k#tx7 zkXU%wP2VJ@dT-V{L}_s}6B0f#^uN+(%VieOcstmiGn7njya9k;^C{DuV+gJViE7T$ z$&h@~H4zVhjHeuxfFSvaRYl-k(YcG%N9Yn{?Syw=g2HQlWhkR!;!9FNY-jqF~|EdX4*_u9Vd-LghTe zv}i37w@{jp2V-bML&faseI&E!fP|ZOZaB(xHq+(PH|@=pV%Rkg=iSyeKZy=Q!wKdo zRgD4F%q{7q7+xD4{ewx8oI?=Yno&9E@CbQ?M==T?K2Dq;(BR(q8$U6so1@;;^SAXV z`mlu2Vvvci%zTkiwByi_BS7Xr)`WdMwuCiNDnl!Zy8d1;)uVYUXKXOu!?;E~3sHL- zK3y=Nayn{hP-j{P0JS9HvglSw#WC3!GsZ1#L4d|PB>j}}4^?uLUfFnw+UhP~!~nSW z_0~#;XdrOFh+(5|L};;R9tCcaZG6fG|D7*_%5;aCZA zg`~bW7~xCql_Y0`K#Gn+HzI>tW*3+Np}0RhAoXgIXI@{2S`Vl`7G)NB7qc@f>}+pd z6_@p5f|bp)-M#TJwHFt*Uiuov5swEBkuS}n%;I=6I2{=6(N~nD#JOYRbtN@S=I@>d zrF$mRb^*D`@kJ>fA>3I2_+XUIx7k8WbRhuWl=)lUU?tE_P>=IBEVShO;svr%(gYTZ zsEMQ!Hv=4EC^aGvHHnB)%mqfl3kL##10dHc);elh$-QnRZwWu1c4U3*^SbF0dCIp- z&|CvYaLb}N;gk{tIKdIwRBzw`^*ZvsCGffwf{s74hNu{#;me2n0db&Aq=xPhcCJ-) zpZ7UzuY)=E>;XfmTKjbs4W!A%xG8CXWB&j}eYdKg>ZbuMTZA3v%^A_{UE!jH|8}Uz zXX|*n!~!?|z*}L!O5sRxb44#v6fKJ$%n)FSXHxn`JMHjgQHA;j6v5LvLj)+X>Rd>* zecr1#!AFt$W{-DT1Y_4R*E6UL470>1ANw25snaT@0)1rUn-rHNgaJmhDuqhfj^>4> zwf+bo<8aAbPusOPYEKk20fh&aLfu6oZXl6V;2{ZvP(;eOm2V+!6P9BVR-tF_DbSiU zdch^P8^T3Cd%aF;BLt-i6^Fehb=)0~E-gfV3-)1a(cefSzTLX6HDZ|-P&@@+6W+x_ z5B28iReJ@O-1ni5`P~&)ScN;{{qyFeEwBt2^JvQ=6!gkDJfJod3q#c$cKiws*(HH7 zxZ5^Mgz{w@+|89E7YR-=9#;w&gRQv%zC=s@F^KbyD~kk1XmvwXJ453sLDGtr2ii$0 zDVlDY8gR=%+aG-1Y^ygo`3x`+d{^chmnR_? z_`22x+$?nQmT@r$Yr#}9YqC77-c#lQ-*Yrzm#qegkcDD!P@3V&f65N8A&Du;lwY(7 z1w}&JxFb9A)ixWOgt`GvByZCG47+_dT3mBcB6T(A6%Eb&NUvM`m3t8jz#~eya@>-+ z-}R=%7ZFaLMa#ElU6#2n%G*eQkb7cJi!*oj4zw{-#xFO(oV&OP2RBgrqW>LW5AP^r($ ze3$9v__r(f#iiLZq6jH_529-_R)sml^?T8#VK|FxvE^Y0`jgUC?&KhKmOx`wNY>Ar ziG6$}0HtFV*0y3Bzkj9if>UGSo$S`pw;h=2)CokuU~xq}!g{W4(dv_obRoXf&9U?o z64Ok9!UFRh#wOM;PqPQB(L?qA!k+<&_<3R1Lkw$$>s54?XM)vvTQsTOj1hOKwKg0Y zZU@QL#d#zBXY63n}rcs83*XpYe2j1;WyEjLDMVQP>TZo4|0*aP+v15 zzSJOkn(Rr^_%^z4#|+LX*(&0s|Dd9!55I?|-qL$G5_mzqHy0YkdJA>%GMn|FMJMd8WYxu2V$h-yG%ceWh>ot{`k1-h9|! z@5XnHgr#+V#A$$e6d**!HM9o<; z*nq+-vovRXMyr`=09F``f2} zN%PWKg;t_3y@Dx%W#3Wq*dUPvZ91u!2QOwA;1d^)GaV0qztKj%cI(0a*eC0yj5y2HSH}l4K_)hGptZEIXc46BB0+nL%dMZM?1YoU(~6rI^wP ziz+zRJ3dx77`Vqrl002jB4!EN4e;E%uf@*1<`kR2GiG z#aOt6?|3-P!Cr1Dn9A^a+e7J1bx##-p%rXJCTnUvBe7t?as`Hw#CuG6OJOfo=vNRA z&h@V5MW0hZrz|6v0EyS%l%CRHbaTlP9N7Ee@=?a8e$SceXI%H~(;W+?z% zDSY$1Y$xyUP|rO)t2A{vsH1DM2SfTVlihBR{4Qq@w^GIY^dDSR!F*X;wo(#SsvcK2X8|J)p<|Jr9&{ttUC6BSb|+If!Nu z^$Zme&4#8!(4nuP<`8no9`Zv+hAOzRyTKOGAOj*pY#eROHd?U3WJAe_C1P$YVi?fl z0U^rUf*@Rs7&U+~hIHV;5ffP8%{CU47_3|w zfCn2OARr(hAfRiJQUKLl&JX@$!^eKX1mg;0hVkMF;{^{Fc>_JY9;F^2J-8OR78bA| zW6>j~$^ef6JU5;i9vYsRov07oPA`W}RL5ECtjKY4tnkTUWu(zwP|@aCb2Q~(1B_W&ehtY)&6;ilJLZd>2BOKh|q0?grj-M4yD1it|bej=j8UaZIw;Cyc zW(oN8@L`}uK!=9g07s1*G*++?g1d=uv!pS^(kNJh3jkp^?3s}t5A>LVA_N~D>+HvA z0s*%jvI|O*7BTfv$L~mYC*D}^JMnfNu<82a zoy(ZVp^5D0yK3N$*W#^~Ij|{LZTj*>1fgI5X;~5Znkl(7ePUCQr?nb zkZF)_aD#r#%JgPz72*g>$8x@Y^tABLw--NSt~h7S&DI~k_`k}SHAI){R;};*)>^pp zO#5M3SC8@6FXzp6qq$a>sxO%l`M9W=MVx1?uU|QGJ*8DI*2N)`~G{?bfNCp%u1TIPYb{ zw4VjT>TGhWi;coMVU?^7EEg2E2KKYL)(ZQwF{@+Y5|}KwKm#Ed7$MTq89>lL1QHY- zfC7$oYRD3h88U=;jC6L|yB&&9a}xvyLK|kI0bA3gAYxBZq{t4At#xZPSB7Q<4UUZK z)>PPVaIVd@*-*GMZLK!Tsnu#YC~|5ojg1DUt=-_XEeZfwn;NcWV{2_}vl|pG4Gmkn z(b(8(Z0(Hg+1}vJ)M_#{ESDOMhvlmAsx#ABFV-Cl27|cI=kvX}&$eybSl4y^PSrF` z(^SLZaA-9Y3WdU}d7j_m{0iSJvM&9V-y>7{#k1TJ%eV5UE*!eleO-H(9wmkPi(PY< zm-2NBeM1`Ewx~U*%5e zOyWss{N<|M)1UcjLHDbukNPkkjQ{y3{1@+pm;UD0z0_m<=9HbvxpRs7tGx>MxsQ4v z{13kwq*}(mDE#eVP5A=*X#942kqbtl-58X*_Y)X zd!_t)5-aoVhDdJ&aJLI_{ejWGiLXlgroY_p*hBt+-ic?ydnJ0l`g3ridL7D%%zLl=QBYT( z^hxiV4tKraB(nE=FsAjpAEAp-zfK%xgF&=D82VXnsv>NQ4i*h|+z zv^9Ot*}g(Ij)rSoVjGU<2{u?#8>uL_AK&<3_qIB?8KIznLm6_-f%>OH-bzgVZ(~lQrKfLER_&ts zRCh^(371m6Wn_;ufPy4=ofluJM8CpxW}drpxvb~=NT!MJt=bJ#^-HMxqlXrJ38b%o z&cy_}`t~IZLOZ!_)TKEgouhZdJHs4)kCOKL9EIRGbCn)2X`54YE#>MparvHGbt{W6 z1K0ok9VWTOO!&rJy5It+F9Pfk!h_6eD%@QSJ>lYRfuN6*S+AIY`-9PDbCsWaie4R# zw@=c%{Jw=7KRVSm_){Up{$xI$^K6djD=+CwgvAxf&RsomNuLxPNcUB?Y#VY?{E)x+ zqO<(@gl=S5@r)U^{3y~)ZKtrHp~59-9Kr88Wos;dp>F7ZT3#e?cD{6ELn|iQ^okea zCS?m*B*CWoWh5}xZ8K$)mI~1?Bd6k1NoRuzfqq3Z-MS{|j>+ybrHAW+te{>_!amkZR>IDN*>vlY6ZS2hxL^dx*&B`muaR(~l|$Vtq^=NV=%_=? zW=_^j=oNxt+-%$?Qq27Wg9Z|G7fn8}X!IFul+ulk$=*H+p@NMs-{i19+7d$eYHiu7 zZ-GZK7(8&L?x+^t{&u%HRke5F zYUlR3X5Sc#?o5TE(tL6fxN$UQoGOaUwtG>NY3Aj4Yp(oX(%OmF?5?P5Nezy>&U>|?O)4EvRd{yz;fhf|CkkW zz(9R7e1Wg`M~5m_tr@iC0`ojNNDGe~jw-X8N+IuPQjjX>!rj#By-tVJhZYpKwn^-> zX9oKn2bSR|eIj3p*pT5o2fc5zKICnzaKUg=_!dHRQ)vGf7Z5)7R6+FM+$!U0Sz~5T zRcGbVy^NkgExH*GtGCa8S4fEMdLn=>Xmi^tOMzH7U0no5?nQBaCIKrQVnB>{&e8)k zOMlPPCt)$sA(IY@^5#(YZ8|U*?QTI^)d~H477HJfZ&Vd*=@ahj>A>_g12Tq;6eXUS zk_>ne>yXTSln>(LzK;bWj(Re;y|l!>bms!f1VY_*7LJK-gV*>#1aE#}u#BAk^!Fl~ z!Z!dQYIia^V1zILzdrHnibP?!!<`R$WtgB|E@L&b;AM&9icDx>e8Nu(Y4ne!ui=;- zvc`oXg`Nk!iwik?d5}RBgjGbyi@7a>2CvP8MvmIZWE3PSr6ZpS`tr%ZE)J;cOIz?& z2QpA91b-h9;x=_hQ?Pz5!U9DG$)uGC@I#v~HFqUtyFKx$A^QX;_F&|M{wg$7yoQ~c zqUVGPX^%gaTe3rn2LnNLQ=qaQZQE|_!(XWDpp3vepAH#wCOni}qh^)VE%+{n@+Ijmq> zYnyw{6c7l+I0X}1Bq)KG7_Tf1STQ_&R#KXl>8uNoKwu58Q)pPxeul<7_v?fVFxO=p zlC6Rm5bw0+WP)Cgmn;HnSkt^^G2Os&l>=g)yWmVXnXhL(&>v4HE>KIHv6r=;g;-tl zB8Rkro0)^9JXr$PENJ!lkzDm{1Eb?q3aGZ)i#M zAyZKF@wgPqoA7{6>6St5!?(vwLNLejY>Wr%OVP=YW+6GVLcd7~Z*zKB79=PWzAVvG-7N_s!>04!oGuzY@jj`H*sr?g~R^@H#>6e_qoc`RB|7Lf5EoiJlg2eTz3T!>UsQ zeJ{f_w~UY;aXMbh3lbBocJ21vAEtl5#~7s|{vrYWgZq9H5&J78-)`h=)3Wls zI$Rwxe^#U^M3%joGrrPW%*dfO-CQIc&MywaY39*B;7*^nD8{QCZ6zi&)J#SbWF2TC zreo7(e48$?EwmF$7(eiAgHT!-KzXVJfs%ZF%?hKWW9@|uB}$0PeyLt4M+iNGJJ zt6z~qE!{ud_+v|kdf%QLN8ThmT_PVAroEaA?nANkCVb+NL;atLw5T{Myb1T5I$0bF zht1CakOuIC5vpU&-y#P>PrrAGLfjYo7h9fV0K}Il?Y?^G5d_n8-7a~BO)|Rjvn|I2 zLHapKGyW@1BhngqhWtXSXw611!B0LvnqcV4tHi&^628J=s~q4 z!F|VoxV-N?5eTF`8gHw6folrwqBrhqw7XT?*7@o)jmbgqy-g1SzevCeSVg3=K1pn!7X;43u(dW|y%#o<%D8b;fqD(XglezRQ zZtMGIgPWg7zCqMQHSVcRIcGg#0?T*12dE8=p<1u9$gVt*7I9f3LF~Fa+pGkq8cz6p zx%e6qSHaZ;5k$oyF5~u43eHU-ODINmi{PaAMO;naI%YeGS56dND8mEBOV2wl+`-^= zjrM1QA_-x|t8CmZy<`1l=Sas2Gj&dzrWf{BQZ%+n?K$}-C__FT0Yt~#&hC~wTLkzU z8&3Kg?}ysC7Z~pYAguQwVk{|%HLi!Cx^mwnT&q7;-G@Aa3b|8+PL_)gCyc^;fS1>G z=yO$5gP8&QulN>N9cHfkJW~qzJ?&}wg{{2nUjl}R$*NzwDn~IYOf8*W=Kq=*!Wyqx zMN#^8_#aCL{k=q*5Zd`i5zPD^Z%`b1Fu^u;1o~QRyLS#xKH+jZ_P4y20qiV-Hjc7u z)fQ(Pg$8y0(DRGwfk%bB-0U5$w&GGkY4al0w!;xP~1rE|V zvNI``RR&Di+dk5_&v}L~3u`|(a(8mu6}Tna~(Rt zXI9I~hWoo&2aL)t&SO_Plhz&kMy8X3iV0TJQ;uD18)5(4Qp6GYv3-;qNNXF+=CUb1 zyjL%td*vn=zI>mkOWU>11unYqb)%KAz>xrs&gHx7^4PuEZjoBUGO3qOp5M2lNmddJ z@+GQ=+zlrHVFJqBN`_~KoDM{!=HPF6s^6#n0_C&hz-J#Nv)nPJfxL7GUX3K5ZuU~+ z#f2qcC$|0yDJRRw7vKNz(!^fdw0gbMTwg>+FLI2#;0jYd`hon4gX{tgyAy*>@$PVG=Bk4Z={kK z$X>XivC&4nE1_n7jXEI=@HRj)KUmlNgLXp`pg zQ`O|B`dSu>nXEs=wla=#$!ff|=k1_<_CcL3E{f|&&pK4C-k7#tTg$VDhJE6?432+< zw%Mu5KfW^qQg9Kki9Po_Y(=*7wex{Hf;|IkWcFl5sZLs0Oy0fYe%ivec=bI7y@R%o z!w2)~4zqvezS`|i$l&CLR3g2YZE>Q$$DR^X>WLqDo~DQp3h|hF-Hv~>P{=^gR9o23 zP%wWvfekaCtl!N zE_@gUhnlOl6bMB2MYTeXK#lF?CsL@oN0XAt@V$Z;4RNCkeQu#%&$sw3)9EAY^{=z6 zAiX92^p9NSue*li#Uh(J>)p}AM89=h3*uFWsOSvX%Utm!hbqJQuq2lqp?j$^^^F*j zm?ZA(L4tvja{O;Z@WHb;3CspP4=!zEvaWV8f$@HuOcM+I{B3VWns_zj#972^Q*v2P z3r)?>k32l5QsPN)_L?n(1E^Rg-=B-|M%T@)xNI04Yj93PaA%5iFE9Dc7rvqD8$?{z z2G|c1-TzTl*>=GGM?4X}zf@Mcfqtbql=P2Qg_)Ea_9Lmu;J$a%q)5^Xm1a|8mnhu~ zpNR+hTvKxyI6YF>LP?ZJMj%ZuXWGM+WWh=-rENV4K9By^`Ky z4=I9%$CtDomYZ$6*}2JUGr6ytB&ixP{szKkukd{h0Uj>S+F5f0%0avi71)- zGwG*B7f|eIF;Hu#Uye1aMan8UNxVS@Y`{@Oy5VA6UADYzfAc^2aqgFKj!+1Qp~Wxe zH^ks(0%3g}XXQ`Y4Xt6(WYf&!Mu0TT)NA}V%_=4u)eG+ZDwS}(PN+b%WJ0TlLGj1d zQ&?)?yu1^pXW(VM`~c?Qg7KSRwU#wh{VSI850N~yoAvg7^2bEmEkDE-Mq`3s6p3FX zqiPiERuy`9VL#igP0@7<@+PHQcspX3i4uy*`$6EaFwaMtV?NKd$aTV*pS5eL4kx+thLlbEccjDyTy-d!N|y$}puJn<^aJ z`7i+cn>XYO47(uTUN*NTX@K~boLZAkZoj+ZrCW@X1#gEN*8LXht4+mW){tVSAf7wI z6J=urj=9Ii)=febj%J0Uu6bUj>*14sp(Szm7>C#EQ;d%AH~hU@@TBaC2yXhr#)q8d z&H5+8uzIZ*+uOK*SGsbx;Kw3f`sBx>(%n$5k?W@FupEzY!MMN*`H!%oS;ni|crrSl zH(F(t$aqBji&k?dpXK71GL5%u;80BpuOe#4lGw<%RM%n_k}x+=rJ<(yLUDX%BUwt+ z?5aaE!(l=Xte@?}E;WVIKqOC-D+K<;CFZC7MHUt1_H{(h#C-&LM_ry-o>~l$ao{fv zsF5LIWrpWR3J{f2UfOU0{Yps9I{3+Ue2TbtRpD+>V1;0Z;6ZM*zoEz5j2dk-{$KI@ ze=vT2-#H6TY_7_?1lj}a09F9@{6@06E<`9^$9tb~E!2GmycMS9g}QFh{a^CyONnSbh2?jpDxz;05fW3S9AhGn5w`>ygIH<~I~+=}Y*DM`ZT zY}i5fzxosY@%=6_+DO;ahFel^aOkT2!KF2W#jUKamqKlkD1aQX^D0n(AoS(gCbPwe zNiQPg`sZ;`)l~uU(Pu~LKwcwZh)OV!?Cb&i>a+EZXd+zu87_+(T*VEjQ`@x0-2|?C zZo`pm5HDcE0fkw@EoyZa_^o){^orWcM#h3-uwNvR7XwGRU6T6sz&%k%HF%#b*cW-& zh~=NigmUmBM6ow0FebHq^qE$4Cliq=L|nlF+$dvS?{hOi{*4bz@Ex~Eh;_2p#aCQT zrqUwX)G%!V+J{0ch3;OJ%|^IF2Q?0`G%%<+AZ6gG;2*+u!c4mVy;`?5Oe= z&VS^()qF%At&-928ceiaCFC~igMk*FSq=T|FOg&{47$QToBU706p1My!JO_DF{#yg z3c}nR9%7*(j%NEzBxIsRo2x!na4K%r{no}V-*XrMG)1h1jicm2!%K_i-+Dp~Zzahl z!Du6D!hB=7z0|YyUwpAc^U-N@RP};EkA=={&pZ*}n4gv4h_;W#4n#^3Dt;J#p1NFih<8X;2_GYLmWq5 zj`FeU(?g0^@ZUEVf5XXL2lP+*pWzj`PP40}4=CyO+DS4(kq<|EY{$FD?ir=X0#QmB zYE$@ygB0wDn#@aIRec7>ZH&8uWs>HUQU3sPdE^mdBpUjtOo}wjul~hDes$szA;RbH zRkT9k3OSa?86b>|ik$@I`2jN|DBwv849JmCsxlFB*RBASE>-`QX(TfdU{{-Hec)C2 zlvo8bbQdea${CIlkTi(#x^nNqG(Bd%rBg|5#J^fgU7n9V9^j#tTF_K^_hFN-APT+j(EsXF7trF z{+{Pj)L4*HgGmLKFzTt2CE8H}5#k5n5Cd+;`oeUR5 z3p7TIMV+){VJ}NJ(;n$KViBZxWOMs51Y5t)g{%2x!Xid+?i6_nHEAkDbGCf_*|B*) zX5ksXXOz|kcQBCQR?aZ}K?BxlE=D1|MZnihFxaxa2!ER^5m}OqRvFUz%s3Ex?C;v{ z`nEuy*Rb{4S3dNXSL0aW6}$y3)kplk4G|8WR~dlInCpnv8*;=Bj+GZ+^ITAz^|=M2 zDiD6VOA*gnK_n{#`R=LFRPaVn1dB60MA7x);KvBb;N@s^XaIt62t@=fMZ(d0#&F5+ zlmhT7Wo66yZc*JuxAE z5Ct6oJh!d!Wt?rOVJr6-Os3Wz$f{#EX->(#8HQO zDo-pA643(8AeO{&&wf)qx1qHE_mEiYYGJS=Da42LXgHl3j3Snrq<%dRrp-sv3~{@# z!p9)CXpVewWx~$+=8$A*yAAE=drtd#_!GabN1L7W31^&5#IHQgeVL-S)K`8y5v8vc zC)iILjBOb~M5#nyZ}&QBoXbp{d&H7h7p}j?>t4MdeZ}u{o_jnX*`nJ`eD0#ui3wx#?c)0ci$li{t5`X`ACGS@s?f#h^w6=;^w7M}n$Z5x52DG?thn9&r0Ar*{+FGL-nIUW_5SsZ{)y1S#s2HW zmHwpvc`IqQ{t&u(4$>Mr1uCwe?=OxEodeB-!WZH~Z{||OOOrz(p`gXYq~yh&e(+B0 zR7U90N0D68^3q~xjP;2X3;^~9pb0bvMgw8s;9(G85MiJwr7UdCwY8Pop{Zd|7hy0^ zF{xoyVbx$YU}1e=aq)!UJmK)+J@({oX_y_<=q*Fv6qza@sM5#|3pOlch zk(p2k2#JWnD5NO=0W}_#fED3zNJ?=@yHl!5a#3QyUhJ(*^f&#;2=_A4lo#h? zru_6yjt=;@w>H^VpPwQY;pb^1gG_x*C8H2PO+QCHOAVl*qoJqK0B8b~XdGyAX_{$9 zXy$1ih(Bu>eb#WGt)$(gy`&|l6Qk3n^Pr2No28zk+oXG-L8d39C#6@V_o5G?uclw6 z*D%OqZJsqZ<@zzKg01B6&d!+fd1X0cVMw-5q|KAEhR+*dk+J6 zURCmHWzEv-Ds^h ziuOzw{Dq41RF4!~=FdDo*9A%clX4K$|3mmzD@VLyo1XKuUCiWCGW!l)OGVEl@VmnS zj^@`k+HJGl$BA#O9t2+bPjr_8gFmf;<%XA;GUI2i+%i&IUg=V)e0<9{!K|Vp1}GC^ zA7Vu2qk!fQ&pJl|%Sq`esigsJru|Xfl$`Nj$$i%fz0` zOc?^Ih;w5A)0-NvJQ}f`Uu_1>sK&MK_gAf|@?5b%?C1BS(1PJteW^l?w(=>@nMMmy zkM?Jrb#djo^WT-5I_#tV>|SctO*RXVWzpizWs7T6Ctw4c;=lfWCENJ}Hf?iG@~7uR zqS57#vbeu>rQYH$Un$=vwBD|DK>9ALtfqBozS^1vTd4FLS@-rn1>o-l)pJg9r0Owg zW|u$HL_$iDu7_(>rq!{Lg+f^Aet6O%{-A$vX?Fi=A41caZiH5VZ)z8Pr zM*&#f1w)`_SE0zEa}Zh~Pi@dJahu@7YH&pPh5yjoCEF;vF5@XXZWEMzQiY^pjs3DUh=by~GsQ5e@DA6j?&t1o(QdS80S(jJB`hN?kX zam}C}@Z#X=;Kd*a^Z<%TDy-NXIvrjez8Jm+?}Fn>>r26k)E2o zsJMT_8>73UknZ~K?858Oima@ptcv!r)rr&6top?TFl2T<>1KL%rn!D`dUh)OW-2YM z`(`HXaJm@`O1hq_?@wBs4bQ6Jya&_Yio!4CAV9m}BXEpFN0$=FqnVqMAL7GdNEVCp zp@*|!gUJyF$&tgwkVBIY!q~8f)D+ZF=t;1!HjFKe73ejLKOuAi49A*~6(n56_XE{n z-cSak>JV>911q`{?*r4i)E~0XRcVdMif?h3hnh@N^hIH*r1!PtT1i*!P5tiy5Ms@* zHIWsi$JFptoA1h9PN!!O`Q-^DWzV}CTcPxG=rkkO`R_-%)*l7UQq+*5qep~OA?Ei2 z*DK-M6jSW(C$P{gAI-}7)@rDi@R6ay!C?k#K6XAm6D%oMu@MWxQ~WTu{`<$uH2+OR zjf=8orLgz$Q_3V@=vRW>m+$~H!{U1dq0nr+OJ290ZZro3=9t1zdLIOZ2&`7KJsJ`m z;bR#F%OT|E`((wmJoE+Bk5+UTPXd2MgHM%7K)1KJ%5KRX2!E7ra~pL=IJb&2wHy`T zoF#9hl^{ZLXgBa-(g0fr#s-#|%y;ib7M9{tZ~wxVgUtGix8?EA(eMDAH>R*#>vfD| zjR2#`5k&Cr#pp+RMvh}sFw#O-g`r#afhNsdo^ainEbkhY;LM~98&+I6 z&Unz)ti`6eg$_dbqB-`W9gs0YO?us+`$J62@8zPGs};vj+WE1g2{FYCgrm|3i6rMK z&{Q8uVYPw-P>M(c?psQU$(v6-Gzx#+GrY^J{^hB*xqHh8B@U6>`HU-N z$AC<7xnv?1NT|0IHPqs@_Q|tsiKeU8Ld1L$v~Ix2r#Cqny3%l(g+ChcAylUy2r$^tQI8pVDqe~9(qPKg LA4MR5-{=Z_iDca{eXUt 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

yR@%vo@Xu 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

9wJ97W3 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 Jrh<{Qv(OL?4dG!x=vu&iJ&a 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 Q*8Fp^MiH= network.Version23 { + return getFilPowerLocked(ctx, st) + } filMarketLocked, err := getFilMarketLocked(ctx, st) if err != nil { @@ -337,6 +341,7 @@ func (sm *StateManager) GetVMCirculatingSupply(ctx context.Context, height abi.C } func (sm *StateManager) GetVMCirculatingSupplyDetailed(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (api.CirculatingSupply, error) { + nv := sm.GetNetworkVersion(ctx, height) filVested, err := sm.GetFilVested(ctx, height) if err != nil { return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filVested: %w", err) @@ -360,7 +365,7 @@ func (sm *StateManager) GetVMCirculatingSupplyDetailed(ctx context.Context, heig return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filBurnt: %w", err) } - filLocked, err := GetFilLocked(ctx, st) + filLocked, err := GetFilLocked(ctx, st, nv) if err != nil { return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filLocked: %w", err) } @@ -387,6 +392,8 @@ func (sm *StateManager) GetVMCirculatingSupplyDetailed(ctx context.Context, heig func (sm *StateManager) GetCirculatingSupply(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { circ := big.Zero() unCirc := big.Zero() + nv := sm.GetNetworkVersion(ctx, height) + err := st.ForEach(func(a address.Address, actor *types.Actor) error { // this can be a lengthy operation, we need to cancel early when // the context is cancelled to avoid resource exhaustion @@ -415,18 +422,22 @@ func (sm *StateManager) GetCirculatingSupply(ctx context.Context, height abi.Cha unCirc = big.Add(unCirc, actor.Balance) case a == market.Address: - mst, err := market.Load(sm.cs.ActorStore(ctx), actor) - if err != nil { - return err - } + if nv >= network.Version23 { + circ = big.Add(circ, actor.Balance) + } else { + mst, err := market.Load(sm.cs.ActorStore(ctx), actor) + if err != nil { + return err + } - lb, err := mst.TotalLocked() - 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) + circ = big.Add(circ, big.Sub(actor.Balance, lb)) + unCirc = big.Add(unCirc, lb) + } case builtin.IsAccountActor(actor.Code) || builtin.IsPaymentChannelActor(actor.Code) || diff --git a/chain/sub/incoming.go b/chain/sub/incoming.go index b50ddc467..42d270a95 100644 --- a/chain/sub/incoming.go +++ b/chain/sub/incoming.go @@ -16,6 +16,7 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/connmgr" "github.com/libp2p/go-libp2p/core/peer" + mh "github.com/multiformats/go-multihash" "go.opencensus.io/stats" "go.opencensus.io/tag" "golang.org/x/xerrors" @@ -30,16 +31,16 @@ import ( "github.com/filecoin-project/lotus/chain/sub/ratelimit" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/metrics" - "github.com/filecoin-project/lotus/node/impl/client" "github.com/filecoin-project/lotus/node/impl/full" ) var log = logging.Logger("sub") +var DefaultHashFunction = uint64(mh.BLAKE2B_MIN + 31) var msgCidPrefix = cid.Prefix{ Version: 1, Codec: cid.DagCBOR, - MhType: client.DefaultHashFunction, + MhType: DefaultHashFunction, MhLength: 32, } diff --git a/chain/types/ethtypes/eth_1559_transactions.go b/chain/types/ethtypes/eth_1559_transactions.go new file mode 100644 index 000000000..1af259873 --- /dev/null +++ b/chain/types/ethtypes/eth_1559_transactions.go @@ -0,0 +1,342 @@ +package ethtypes + +import ( + "fmt" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" + typescrypto "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" +) + +var _ EthTransaction = (*Eth1559TxArgs)(nil) + +type Eth1559TxArgs struct { + ChainID int `json:"chainId"` + Nonce int `json:"nonce"` + To *EthAddress `json:"to"` + Value big.Int `json:"value"` + MaxFeePerGas big.Int `json:"maxFeePerGas"` + MaxPriorityFeePerGas big.Int `json:"maxPriorityFeePerGas"` + GasLimit int `json:"gasLimit"` + Input []byte `json:"input"` + V big.Int `json:"v"` + R big.Int `json:"r"` + S big.Int `json:"s"` +} + +func (tx *Eth1559TxArgs) ToUnsignedFilecoinMessage(from address.Address) (*types.Message, error) { + if tx.ChainID != build.Eip155ChainId { + return nil, fmt.Errorf("invalid chain id: %d", tx.ChainID) + } + mi, err := getFilecoinMethodInfo(tx.To, tx.Input) + if err != nil { + return nil, xerrors.Errorf("failed to get method info: %w", err) + } + + return &types.Message{ + Version: 0, + To: mi.to, + From: from, + Nonce: uint64(tx.Nonce), + Value: tx.Value, + GasLimit: int64(tx.GasLimit), + GasFeeCap: tx.MaxFeePerGas, + GasPremium: tx.MaxPriorityFeePerGas, + Method: mi.method, + Params: mi.params, + }, nil +} + +func (tx *Eth1559TxArgs) ToRlpUnsignedMsg() ([]byte, error) { + encoded, err := toRlpUnsignedMsg(tx) + if err != nil { + return nil, err + } + return append([]byte{EIP1559TxType}, encoded...), nil +} + +func (tx *Eth1559TxArgs) TxHash() (EthHash, error) { + rlp, err := tx.ToRlpSignedMsg() + if err != nil { + return EmptyEthHash, err + } + + return EthHashFromTxBytes(rlp), nil +} + +func (tx *Eth1559TxArgs) ToRlpSignedMsg() ([]byte, error) { + encoded, err := toRlpSignedMsg(tx, tx.V, tx.R, tx.S) + if err != nil { + return nil, err + } + return append([]byte{EIP1559TxType}, encoded...), nil +} + +func (tx *Eth1559TxArgs) Signature() (*typescrypto.Signature, error) { + r := tx.R.Int.Bytes() + s := tx.S.Int.Bytes() + v := tx.V.Int.Bytes() + + sig := append([]byte{}, padLeadingZeros(r, 32)...) + sig = append(sig, padLeadingZeros(s, 32)...) + if len(v) == 0 { + sig = append(sig, 0) + } else { + sig = append(sig, v[0]) + } + + if len(sig) != 65 { + return nil, xerrors.Errorf("signature is not 65 bytes") + } + return &typescrypto.Signature{ + Type: typescrypto.SigTypeDelegated, Data: sig, + }, nil +} + +func (tx *Eth1559TxArgs) Sender() (address.Address, error) { + return sender(tx) +} + +func (tx *Eth1559TxArgs) Type() int { + return EIP1559TxType +} + +func (tx *Eth1559TxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) { + return sig, nil +} + +func (tx *Eth1559TxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, error) { + from, err := EthAddressFromFilecoinAddress(smsg.Message.From) + if err != nil { + return EthTx{}, xerrors.Errorf("sender was not an eth account") + } + hash, err := tx.TxHash() + if err != nil { + return EthTx{}, err + } + gasFeeCap := EthBigInt(tx.MaxFeePerGas) + gasPremium := EthBigInt(tx.MaxPriorityFeePerGas) + + ethTx := EthTx{ + ChainID: EthUint64(build.Eip155ChainId), + Type: EIP1559TxType, + Nonce: EthUint64(tx.Nonce), + Hash: hash, + To: tx.To, + Value: EthBigInt(tx.Value), + Input: tx.Input, + Gas: EthUint64(tx.GasLimit), + MaxFeePerGas: &gasFeeCap, + MaxPriorityFeePerGas: &gasPremium, + From: from, + R: EthBigInt(tx.R), + S: EthBigInt(tx.S), + V: EthBigInt(tx.V), + } + + return ethTx, nil +} + +func (tx *Eth1559TxArgs) InitialiseSignature(sig typescrypto.Signature) error { + if sig.Type != typescrypto.SigTypeDelegated { + return xerrors.Errorf("RecoverSignature only supports Delegated signature") + } + + if len(sig.Data) != EthEIP1559TxSignatureLen { + return xerrors.Errorf("signature should be 65 bytes long, but got %d bytes", len(sig.Data)) + } + + r_, err := parseBigInt(sig.Data[0:32]) + if err != nil { + return xerrors.Errorf("cannot parse r into EthBigInt") + } + + s_, err := parseBigInt(sig.Data[32:64]) + if err != nil { + return xerrors.Errorf("cannot parse s into EthBigInt") + } + + v_, err := parseBigInt([]byte{sig.Data[64]}) + if err != nil { + return xerrors.Errorf("cannot parse v into EthBigInt") + } + + tx.R = r_ + tx.S = s_ + tx.V = v_ + + return nil +} + +func (tx *Eth1559TxArgs) packTxFields() ([]interface{}, error) { + chainId, err := formatInt(tx.ChainID) + if err != nil { + return nil, err + } + + nonce, err := formatInt(tx.Nonce) + if err != nil { + return nil, err + } + + maxPriorityFeePerGas, err := formatBigInt(tx.MaxPriorityFeePerGas) + if err != nil { + return nil, err + } + + maxFeePerGas, err := formatBigInt(tx.MaxFeePerGas) + if err != nil { + return nil, err + } + + gasLimit, err := formatInt(tx.GasLimit) + if err != nil { + return nil, err + } + + value, err := formatBigInt(tx.Value) + if err != nil { + return nil, err + } + + res := []interface{}{ + chainId, + nonce, + maxPriorityFeePerGas, + maxFeePerGas, + gasLimit, + formatEthAddr(tx.To), + value, + tx.Input, + []interface{}{}, // access list + } + return res, nil +} + +func parseEip1559Tx(data []byte) (*Eth1559TxArgs, error) { + if data[0] != EIP1559TxType { + return nil, xerrors.Errorf("not an EIP-1559 transaction: first byte is not %d", EIP1559TxType) + } + + d, err := DecodeRLP(data[1:]) + if err != nil { + return nil, err + } + decoded, ok := d.([]interface{}) + if !ok { + return nil, xerrors.Errorf("not an EIP-1559 transaction: decoded data is not a list") + } + + if len(decoded) != 12 { + return nil, xerrors.Errorf("not an EIP-1559 transaction: should have 12 elements in the rlp list") + } + + chainId, err := parseInt(decoded[0]) + if err != nil { + return nil, err + } + + nonce, err := parseInt(decoded[1]) + if err != nil { + return nil, err + } + + maxPriorityFeePerGas, err := parseBigInt(decoded[2]) + if err != nil { + return nil, err + } + + maxFeePerGas, err := parseBigInt(decoded[3]) + if err != nil { + return nil, err + } + + gasLimit, err := parseInt(decoded[4]) + if err != nil { + return nil, err + } + + to, err := parseEthAddr(decoded[5]) + if err != nil { + return nil, err + } + + value, err := parseBigInt(decoded[6]) + if err != nil { + return nil, err + } + + input, err := parseBytes(decoded[7]) + if err != nil { + return nil, err + } + + accessList, ok := decoded[8].([]interface{}) + if !ok || (ok && len(accessList) != 0) { + return nil, xerrors.Errorf("access list should be an empty list") + } + + r, err := parseBigInt(decoded[10]) + if err != nil { + return nil, err + } + + s, err := parseBigInt(decoded[11]) + if err != nil { + return nil, err + } + + v, err := parseBigInt(decoded[9]) + if err != nil { + return nil, err + } + + // EIP-1559 and EIP-2930 transactions only support 0 or 1 for v + // Legacy and EIP-155 transactions support other values + // https://github.com/ethers-io/ethers.js/blob/56fabe987bb8c1e4891fdf1e5d3fe8a4c0471751/packages/transactions/src.ts/index.ts#L333 + if !v.Equals(big.NewInt(0)) && !v.Equals(big.NewInt(1)) { + return nil, xerrors.Errorf("EIP-1559 transactions only support 0 or 1 for v") + } + + args := Eth1559TxArgs{ + ChainID: chainId, + Nonce: nonce, + To: to, + MaxPriorityFeePerGas: maxPriorityFeePerGas, + MaxFeePerGas: maxFeePerGas, + GasLimit: gasLimit, + Value: value, + Input: input, + V: v, + R: r, + S: s, + } + return &args, nil +} + +func Eth1559TxArgsFromUnsignedFilecoinMessage(msg *types.Message) (*Eth1559TxArgs, error) { + if msg.Version != 0 { + return nil, fmt.Errorf("unsupported msg version: %d", msg.Version) + } + + params, to, err := getEthParamsAndRecipient(msg) + if err != nil { + return nil, fmt.Errorf("failed to get eth params and recipient: %w", err) + } + + return &Eth1559TxArgs{ + ChainID: build.Eip155ChainId, + Nonce: int(msg.Nonce), + To: to, + Value: msg.Value, + Input: params, + MaxFeePerGas: msg.GasFeeCap, + MaxPriorityFeePerGas: msg.GasPremium, + GasLimit: int(msg.GasLimit), + }, nil +} diff --git a/chain/types/ethtypes/eth_1559_transactions_test.go b/chain/types/ethtypes/eth_1559_transactions_test.go new file mode 100644 index 000000000..69835925f --- /dev/null +++ b/chain/types/ethtypes/eth_1559_transactions_test.go @@ -0,0 +1,241 @@ +package ethtypes + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/crypto/sha3" + + "github.com/filecoin-project/go-address" + gocrypto "github.com/filecoin-project/go-crypto" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/builtin/v10/evm" + init10 "github.com/filecoin-project/go-state-types/builtin/v10/init" + crypto1 "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/lib/sigs" + _ "github.com/filecoin-project/lotus/lib/sigs/delegated" +) + +type TxTestcase struct { + TxJSON string + NosigTx string + Input EthBytes + Output Eth1559TxArgs +} + +func TestEIP1559TxArgs(t *testing.T) { + testcases, err := prepareTxTestcases() + require.Nil(t, err) + require.NotEmpty(t, testcases) + + for i, tc := range testcases { + comment := fmt.Sprintf("case %d: \n%s\n%s", i, tc.TxJSON, hex.EncodeToString(tc.Input)) + + // parse txargs + txArgs, err := parseEip1559Tx(tc.Input) + require.NoError(t, err, comment) + + msgRecovered, err := txArgs.ToRlpUnsignedMsg() + require.NoError(t, err, comment) + require.Equal(t, tc.NosigTx, "0x"+hex.EncodeToString(msgRecovered), comment) + + // verify signatures + from, err := txArgs.Sender() + require.NoError(t, err, comment) + + smsg, err := ToSignedFilecoinMessage(txArgs) + require.NoError(t, err, comment) + + err = sigs.Verify(&smsg.Signature, from, msgRecovered) + require.NoError(t, err, comment) + + // verify data + require.Equal(t, tc.Output.ChainID, txArgs.ChainID, comment) + require.Equal(t, tc.Output.Nonce, txArgs.Nonce, comment) + require.Equal(t, tc.Output.To, txArgs.To, comment) + } +} + +func TestEIP1559Signatures(t *testing.T) { + testcases := []struct { + RawTx string + ExpectedR string + ExpectedS string + ExpectedV string + ExpectErr bool + }{ + { + "0x02f8598401df5e76028301d69083086a5e835532dd808080c080a0457e33227ac7ceee2ef121755e26b872b6fb04221993f9939349bb7b0a3e1595a02d8ef379e1d2a9e30fa61c92623cc9ed72d80cf6a48cfea341cb916bcc0a81bc", + "0x457e33227ac7ceee2ef121755e26b872b6fb04221993f9939349bb7b0a3e1595", + "0x2d8ef379e1d2a9e30fa61c92623cc9ed72d80cf6a48cfea341cb916bcc0a81bc", + "0x0", + false, + }, + { + "0x02f8598401df5e76038301d69083086a5e835532dd808080c001a012a232866dcb0671eb0ddc01fb9c01d6ef384ec892bb29691ed0d2d293052ddfa052a6ae38c6139930db21a00eee2a4caced9a6500991b823d64ec664d003bc4b1", + "0x12a232866dcb0671eb0ddc01fb9c01d6ef384ec892bb29691ed0d2d293052ddf", + "0x52a6ae38c6139930db21a00eee2a4caced9a6500991b823d64ec664d003bc4b1", + "0x1", + false, + }, + { + "0x00", + "", + "", + "", + true, + }, + } + + for _, tc := range testcases { + tx, err := parseEip1559Tx(mustDecodeHex(tc.RawTx)) + if tc.ExpectErr { + require.Error(t, err) + continue + } + require.Nil(t, err) + + sig, err := tx.Signature() + require.Nil(t, err) + + require.NoError(t, tx.InitialiseSignature(*sig)) + + require.Equal(t, tc.ExpectedR, "0x"+tx.R.Text(16)) + require.Equal(t, tc.ExpectedS, "0x"+tx.S.Text(16)) + require.Equal(t, tc.ExpectedV, "0x"+tx.V.Text(16)) + } +} + +func TestTransformParams(t *testing.T) { + constructorParams, err := actors.SerializeParams(&evm.ConstructorParams{ + Initcode: mustDecodeHex("0x1122334455"), + }) + require.Nil(t, err) + + evmActorCid, ok := actors.GetActorCodeID(actorstypes.Version10, "reward") + require.True(t, ok) + + params, err := actors.SerializeParams(&init10.ExecParams{ + CodeCID: evmActorCid, + ConstructorParams: constructorParams, + }) + require.Nil(t, err) + + var exec init10.ExecParams + reader := bytes.NewReader(params) + err1 := exec.UnmarshalCBOR(reader) + require.Nil(t, err1) + + var evmParams evm.ConstructorParams + reader1 := bytes.NewReader(exec.ConstructorParams) + err1 = evmParams.UnmarshalCBOR(reader1) + require.Nil(t, err1) + + require.Equal(t, mustDecodeHex("0x1122334455"), evmParams.Initcode) +} + +func TestEcRecover(t *testing.T) { + rHex := "0x479ff7fa64cf8bf641eb81635d1e8a698530d2f219951d234539e6d074819529" + sHex := "0x4b6146d27be50cdbb2853ba9a42f207af8d730272f1ebe9c9a78aeef1d6aa924" + fromHex := "0x3947D223fc5415f43ea099866AB62B1d4D33814D" + v := byte(0) + + msgHex := "0x02f1030185012a05f2008504a817c800825208942b87d1cb599bc2a606db9a0169fcec96af04ad3a880de0b6b3a764000080c0" + pubKeyHex := "0x048362749392a0e192eff600d21155236c5a0648d300a8e0e44d8617712c7c96384c75825dc5c7595df2a5005fd8a0f7c809119fb9ab36403ed712244fc329348e" + + msg := mustDecodeHex(msgHex) + pubKey := mustDecodeHex(pubKeyHex) + r := mustDecodeHex(rHex) + s := mustDecodeHex(sHex) + from := mustDecodeHex(fromHex) + + sig := append(r, s...) + sig = append(sig, v) + require.Equal(t, 65, len(sig)) + + sha := sha3.NewLegacyKeccak256() + sha.Write(msg) + h := sha.Sum(nil) + + pubk, err := gocrypto.EcRecover(h, sig) + require.Nil(t, err) + require.Equal(t, pubKey, pubk) + + sha.Reset() + sha.Write(pubk[1:]) + h = sha.Sum(nil) + h = h[len(h)-20:] + + require.Equal(t, from, h) +} + +func TestDelegatedSigner(t *testing.T) { + rHex := "0xcf1fa52fae9154ba21d67aeca9b42adfe186eb9e426c441051a8473efd190848" + sHex := "0x0e6c8c79ffaf35fb8f136c8cf6c5656f1f3befad21f2644321aa6dba58d68737" + v := byte(0) + + msgHex := "0x02f08401df5e76038502540be400843b9aca008398968094ff000000000000000000000000000000000003f2832dc6c080c0" + pubKeyHex := "0x04cfecc0520d906cbfea387759246e89d85e2998843e56ad1c41de247ce10b3e4c453aa73c8de13c178d94461b6fa3f8b6f74406ce43d2fbab6992d0b283394242" + + msg := mustDecodeHex(msgHex) + pubk := mustDecodeHex(pubKeyHex) + r := mustDecodeHex(rHex) + s := mustDecodeHex(sHex) + + addrHash, err := EthAddressFromPubKey(pubk) + require.NoError(t, err) + + from, err := address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, addrHash) + require.NoError(t, err) + + sig := append(r, s...) + sig = append(sig, v) + require.Equal(t, 65, len(sig)) + + signature := &crypto1.Signature{ + Type: crypto1.SigTypeDelegated, + Data: sig, + } + + err = sigs.Verify(signature, from, msg) + require.NoError(t, err) +} + +func prepareTxTestcases() ([]TxTestcase, error) { + tcstr := `[{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec8080c080a0f411a73e33523b40c1a916e79e67746bd01a4a4fb4ecfa87b441375a215ddfb4a0551692c1553574fab4c227ca70cb1c121dc3a2ef82179a9c984bd7acc0880a38","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec8080c001a0ed75a56e365c88479bf3f60251a2dd47ae181f1a3d95724581a3f648487b4396a046628bb9734edf4b4c455f2bbd351e43c466f315272cd1927f2c55d9b52e058b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec8080c080a0f411a73e33523b40c1a916e79e67746bd01a4a4fb4ecfa87b441375a215ddfb4a0551692c1553574fab4c227ca70cb1c121dc3a2ef82179a9c984bd7acc0880a38","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec8080c001a0ed75a56e365c88479bf3f60251a2dd47ae181f1a3d95724581a3f648487b4396a046628bb9734edf4b4c455f2bbd351e43c466f315272cd1927f2c55d9b52e058b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88682013a8080808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0706d871013403cf8b965dfa7f2be5a4d185d746da45b21d5a67c667c26d255d6a02e68a14f386aa325ce8e82d30405107d53103d038cf20e40af961ef3a3963608","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84382013a8080808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88782013a81c880808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0df137d0a6733354b2f2419a4ea5fe77d333deca28b2fe091d76190b51c2bae73a0232cbf9c29b8840cbf104ff77360fbf3ca4acda29b5e230636e19ac253ad92de","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84482013a81c880808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec8080c001a03a2880cc65e88d5320067f502a0ffda72111d01f0ebeeea9fbeb812e457aa0f9a020c08483b104dbfbbbffffedc3acdbe8245ca6daf97c0dbab843d747e587d625","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec8080c001a03427daf1639de6bf1b948abeab765b0a6a9170cc6a16d263c71c859f78916b03a01bbbb824b9953b5eb9f3098b4358a7ebb78f3358866eed997de66350ae4c9475","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec8080c001a03a2880cc65e88d5320067f502a0ffda72111d01f0ebeeea9fbeb812e457aa0f9a020c08483b104dbfbbbffffedc3acdbe8245ca6daf97c0dbab843d747e587d625","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec8080c001a03427daf1639de6bf1b948abeab765b0a6a9170cc6a16d263c71c859f78916b03a01bbbb824b9953b5eb9f3098b4358a7ebb78f3358866eed997de66350ae4c9475","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88882013a808082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0b9ebc36653a4800816f71ceacf93a1ee601a136916a3476ea9073a9a55ff026aa0647665249b12e8d1d1773b91844588ed70f65c91bc088ccb259ec0f0a24330d5","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a808082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c88082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0122dd8468dbd34e111e1a5ea1997199be633aa3bc9c1a7ee27dc3a8eda39c29da07cb99cd28ac67f55e507a8b8ef5b931c56cacf79273a4a2969a004a4b4a2864a","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c88082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec8080c080a0c1d020df63cb6db76e3a27a60ba0500a3cdd30f9f47b08733009dc8d610ea29ba05cbafb4c223417526ded0b02b8eb66a73535386d0e62da0e20f3641b532aa406","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec8080c080a090e30d32c6cd3f1ba2109b6a9f1c9fffc50b96a934192edf98adc086299e410ba057db0c136436de2e907942bdaad8e0113cf576f250b336ab652ef094c260dae6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec8080c080a0c1d020df63cb6db76e3a27a60ba0500a3cdd30f9f47b08733009dc8d610ea29ba05cbafb4c223417526ded0b02b8eb66a73535386d0e62da0e20f3641b532aa406","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec8080c080a090e30d32c6cd3f1ba2109b6a9f1c9fffc50b96a934192edf98adc086299e410ba057db0c136436de2e907942bdaad8e0113cf576f250b336ab652ef094c260dae6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88882013a8082ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a016e3f30a612fc802bb64b765325ecf78f2769b879a9acf62f07669f9723335d6a0781bb3444a73819f28233f1eebf8c3a4de288842fd73c2e05a7a7b0c288d5b25","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a8082ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c882ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0b652a447bdcdd1906ed86406ee543ee06023e4f762784c1d3aaf4c3bd85c6a17a0368ae9995e15258f14b74f937e97140a659d052d341674be0c24452257b56b30","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c882ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c001a0b1411f337b69609a256c0e76c57ccf4af87e977c98fd2a889f29281bf623cab4a049bec0fb4773aed870bae9c1cdf1ee398c498f0b436dcd19cae588b4ecd8bdf2","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c080a00b845fec9c96bf593c3501753764e14867d3f5d4bd02051e49329b6810d6513ea070d046e5b38c18c542594b328f02345a8f34ab05fd00db33974f914f7ae31c63","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c001a0b1411f337b69609a256c0e76c57ccf4af87e977c98fd2a889f29281bf623cab4a049bec0fb4773aed870bae9c1cdf1ee398c498f0b436dcd19cae588b4ecd8bdf2","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c080a00b845fec9c96bf593c3501753764e14867d3f5d4bd02051e49329b6810d6513ea070d046e5b38c18c542594b328f02345a8f34ab05fd00db33974f914f7ae31c63","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88a82013a8082ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a02d8215d8408d2f4b83a2e68f4aad6fe5dee97d7ef6a43b02ec413ead2215ac80a0641a43cebd6905e3e324c0dd06585d5ffc9b971b519045999c48e31db7aa7f9d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88a82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0da68784e191ce0806527d389f84b5d15bed3908e1c2cc0d8f0cea7a29eb0dba39f231a0b438b7d0f0f57292c68dc174d4ee6df7add933ab4e0b3789f597a7d3b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec8080c080a04c97162e2d2ab508116a23c522fd816ecd9cb091d4c288afe45c37ee3a8dde34a06ebf67ff15b74d65c276340aaebde8e6ebb8da0d3bbab43deffac8eb1e6a0630","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c080a0d503d409e667c2876ab9e420854cecce4c0092985855234be07f270bfcf3ed4aa07a40deecc8a4448d4dc0e2014b4b23ac5721409c62bffa05aee6938d8447f72d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec8080c080a04c97162e2d2ab508116a23c522fd816ecd9cb091d4c288afe45c37ee3a8dde34a06ebf67ff15b74d65c276340aaebde8e6ebb8da0d3bbab43deffac8eb1e6a0630","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c080a0d503d409e667c2876ab9e420854cecce4c0092985855234be07f270bfcf3ed4aa07a40deecc8a4448d4dc0e2014b4b23ac5721409c62bffa05aee6938d8447f72d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88882013a80808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a059aecc1d365ee0dc56a577d162f04c0912a5c5b62f889cff1acc706ac17a4489a017209b3ec43a10a40c5863a2b7a1ee823380ad42697a5f7d5f537c230583a4c7","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a80808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c8808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0dc1eb40f93e311f3f9a94d8a695db2bbb38973ce097121875885e4bc54f18152a0075da0bd405bb4f5c69034daaf8f40052b941fae5b9f3b8df218d80fb4d7ea99","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c8808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a03d392fd5e83c64554907a55204572aaeec6ffab25f2c73655c6a22344fa02a14a03b9ae94b7dc21108db6dda65125ecaff844f8f43f483bed35f32f6d5d530fe9f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c001a0405e8a430ef6ad4c3403150776af08c255b6f6fbe278d194f88517733c816caca0364203b5bca7953dd863d4cf90c0a77b499ef4a3d5831c4fdf33926c31709c4f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a03d392fd5e83c64554907a55204572aaeec6ffab25f2c73655c6a22344fa02a14a03b9ae94b7dc21108db6dda65125ecaff844f8f43f483bed35f32f6d5d530fe9f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c001a0405e8a430ef6ad4c3403150776af08c255b6f6fbe278d194f88517733c816caca0364203b5bca7953dd863d4cf90c0a77b499ef4a3d5831c4fdf33926c31709c4f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88a82013a808082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a083cf6701aee00872946b6550c059f028f72e3052acb8cc9c25b830ace860e046a03fd969d73e995d43896659f94d3956a17da18451050349e7db6f7881f8c057d3","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a808082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c5a545f2d94e719068d9a43b01879bcb46b56e236dd378dd26ef3b8e4ec8314aa04024b9936960b9b156405e4f3e0b6562518df8778324a927381e380b23f47fb8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c080a0aa406ec7f4901a1777e44b975ff41603b9d46257efdc1ca904a3e7890f2b020ea03bda5c785182cfa2d9f9b7a54f194cd08b9d0f913069a4514ff21e8fa0ef3850","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c080a089fc465c24b4bad898cf900f585eddab6d40189e8d19746da76597f86fbadf51a005732ffa2ebac36646afab9105540b543f74a5c91b441834a2b1930815c2ccc8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c080a0aa406ec7f4901a1777e44b975ff41603b9d46257efdc1ca904a3e7890f2b020ea03bda5c785182cfa2d9f9b7a54f194cd08b9d0f913069a4514ff21e8fa0ef3850","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c080a089fc465c24b4bad898cf900f585eddab6d40189e8d19746da76597f86fbadf51a005732ffa2ebac36646afab9105540b543f74a5c91b441834a2b1930815c2ccc8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88a82013a8082ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a09d9a8ee802486b826348a76346987b3e7331d70ef0c0257ff976ceebef1141a2a07d97d14ed877c16bd932f08a67c374e773ee3337d512ff8241c8d78566a04d46","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a024ad1ec1578f51beb2b574507bda7691a486cdbc9c22add01ad4c1f686beb567a048445e0fe8945b8052e5e87139690c0615a11c52503b226cf23610c999eada40","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a06b382fcbe48de85615ff6e2dcc0c84021beb4abc527878accd36c9c77af84ba8a06a07d34a6896b270538525cb14b0856ceb442714fa85e4c9ee36dedf638935f9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a0ba2586cfb3323fd0f9d7bb38bf9948758a52f156bda66f7100b789760894ad89a01e4bd2ff4eff2c391915141250313ab845401d5e2f71c23691d20a0b3c68cbd9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a06b382fcbe48de85615ff6e2dcc0c84021beb4abc527878accd36c9c77af84ba8a06a07d34a6896b270538525cb14b0856ceb442714fa85e4c9ee36dedf638935f9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a0ba2586cfb3323fd0f9d7bb38bf9948758a52f156bda66f7100b789760894ad89a01e4bd2ff4eff2c391915141250313ab845401d5e2f71c23691d20a0b3c68cbd9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88c82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0f36ff02ab3e90d2de77cdb24423dc39ca5c959429db62cb5c9ed4f0c9e04703aa0476bf841b0602af44039801d4e68648971f63fc2152002b127be6d914d4fc5ca","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84982013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88d82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a08267ae8838a8a5d9c2a761c182b5759184b7672b761278d499c1514fb6e8a495a023aa268f67da7728767e114fdec4d141bf649e0ad931117b5b325834dbf72803","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84a82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec6480c080a011ec4af7fc663080460b70ae8829f47e9cfa1814c616750d359459cbbba55563a0446e4ec9ea504d13dcbef44238e442caad366dbae1ae9408d39c6d902a5577b0","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec6480c001a0b80bc30bef46b3f824d1460685db875ff070f7798c3148c1fc49c01d6acc550ca0437efe7721563800e6a56ac54877a72c7860cd5e17ef4675afe989822ae87759","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec6480c080a011ec4af7fc663080460b70ae8829f47e9cfa1814c616750d359459cbbba55563a0446e4ec9ea504d13dcbef44238e442caad366dbae1ae9408d39c6d902a5577b0","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec6480c001a0b80bc30bef46b3f824d1460685db875ff070f7798c3148c1fc49c01d6acc550ca0437efe7721563800e6a56ac54877a72c7860cd5e17ef4675afe989822ae87759","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88682013a8080808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a06ab9d5988105d28dd090e509c8caabaa7773fc08ec5ef3dfeae532e01938ff69a078bca296df26dd2497a49110e138a49a67a6e232a35524b041d04a10fc583651","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84382013a8080808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88782013a81c880808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a031d51b866a02a9966250d312ed6cb4e083f9131ad8f6bb5814074375093d7536a03f8f819c4011dd54348930b6f98f365de8060b487ada38a62a5617aab6cc6e09","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84482013a81c880808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec6480c001a05bda5ad44c8f9a7516226488cf2d4f53188b40352f35ea7cece8076acda26dbba015373b3b78c88b74c7cca32fd02696a248bb9bea22a09c7a4a17b9e3b629b896","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec6480c080a00d92624cc3335c903077e318204929b4a8c9cd96d94690b0191f8a3bb24e937aa02f1d0315ececf46900154791a732eb8fee9efd0dc998a4e6b892d07ad657a815","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec6480c001a05bda5ad44c8f9a7516226488cf2d4f53188b40352f35ea7cece8076acda26dbba015373b3b78c88b74c7cca32fd02696a248bb9bea22a09c7a4a17b9e3b629b896","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec6480c080a00d92624cc3335c903077e318204929b4a8c9cd96d94690b0191f8a3bb24e937aa02f1d0315ececf46900154791a732eb8fee9efd0dc998a4e6b892d07ad657a815","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88882013a808082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0def168136c0532ec148a9e200e3cc1b22f90c7bbc5d9ef25ac0c5d342e8f3784a022f94642dfc81ba321b3e09879888332fa7c25b623bead7686e3e493c0911b55","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a808082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c88082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0626f43b80260f84cde2c67538c5cfbd328ce85b0f934e8568769e51709b100a7a0283fff5dbfde72b72e2b74c464b1add985d72750be3f4e16ae8ffb4747a40ff2","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c88082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec6480c080a051b109080002dab4aae47139eb92ddea8951ef5ac6dfc3d7fa07621047dbc680a0334aa47a2888a6cc52b8cf3c3635192b66c692416e954822c1c93c3896ff1ead","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec6480c080a009e179e3bad2da6fb5e205e52fd8d1c462007162aabde5a4d6b052dd4fc4f23ca063922c31438835adf2e4424e2e7d5d2702ec65de2e24a72b491ff0004a53865d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec6480c080a051b109080002dab4aae47139eb92ddea8951ef5ac6dfc3d7fa07621047dbc680a0334aa47a2888a6cc52b8cf3c3635192b66c692416e954822c1c93c3896ff1ead","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec6480c080a009e179e3bad2da6fb5e205e52fd8d1c462007162aabde5a4d6b052dd4fc4f23ca063922c31438835adf2e4424e2e7d5d2702ec65de2e24a72b491ff0004a53865d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88882013a8082ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0d3bfebc6597304c6a06491f68d2ac149fc233d28e81af48dd5b1f83e6ff951d2a06668da06d86aba341971dabb58016ca7764cd4b4c1634e3f829dcc8ef8bca4f6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a8082ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c882ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0d45b9fd9a2a3fdf79805cf73b70348037cc69927209a5e3728fe62cbe9543f03a02f5f8477666487ee5148a65ce59f400beac7c208369162b2d555411314d358fb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c882ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c001a02a6a910f7b5f83fda937006021b9c074f4544d5bb37b9b5a1b7045095f461836a038572b25418528bce7e6a3a480cf9fc90a33d9c63b392c2dbc8faf72a1e4ab8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c080a07a6dd661b5da27c809cce22aa186c158fe3b07a484a9395fd9a7a31a2b90636fa02b86f82b661264e27c3fda085b59740d3059335bff91693291afcf93c7ca627c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c001a02a6a910f7b5f83fda937006021b9c074f4544d5bb37b9b5a1b7045095f461836a038572b25418528bce7e6a3a480cf9fc90a33d9c63b392c2dbc8faf72a1e4ab8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c080a07a6dd661b5da27c809cce22aa186c158fe3b07a484a9395fd9a7a31a2b90636fa02b86f82b661264e27c3fda085b59740d3059335bff91693291afcf93c7ca627c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88a82013a8082ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a08c13c10490bc20cb1e55dc54ececb37a6c9cc8d013dbe513feacbb0416f09feba045c4e038759a0901820091e043db326b1bf9a8a1cd046ac72629969497c6a86f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0b904edf8eb9b6beb9cde9e1fae538e12f8d40e9124ace0cba2eee8cbbe77aa10a0788a0bd9a6fb98e7230f5db89be2f5067d1a227ba277b9cb155fb5859c57aae6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec6480c080a08d10a7a81c561391fe88bcb2c1dfbf4f7140fb7884fec0558606e76ffc4eaa91a049fa2a95e0f07a4376df9c6f2e1563ad443ce8369d44c6e1ce8ee521805b3623","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c001a00de6dc2841a25e5ea2dc1e054d69638ec519a9953666930060797cd110cde122a07fd1dcb6319eca7c681cef006efb3f7dcd74ff98a79ce05917d5d1fa7a175b6f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec6480c080a08d10a7a81c561391fe88bcb2c1dfbf4f7140fb7884fec0558606e76ffc4eaa91a049fa2a95e0f07a4376df9c6f2e1563ad443ce8369d44c6e1ce8ee521805b3623","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c001a00de6dc2841a25e5ea2dc1e054d69638ec519a9953666930060797cd110cde122a07fd1dcb6319eca7c681cef006efb3f7dcd74ff98a79ce05917d5d1fa7a175b6f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88882013a80808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a04c43dab94dd746973a1f7f051cc520cc01e93e9c6c55147cef34e5fdc0b182a2a06d148cc6ec017f9aeb6442a17d72e388ffc835950e19abd0c06057520f893542","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a80808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c8808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a025b50c1db31c0ae7aaa73374659201b54b71488efecbb6985dc50015abde7e36a04dd8cf68920de7232ab8d1fb28ab94ac05466c1f9d9a3a658f2054fce7868e2c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c8808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c080a0415ad0a93225eaec617206ec835e362d5e75fd0e1903747c1806270ec2684c7da0487ec1479cdb2affa891ff56413818ec169651c906ab932594b6e5bbb79d4998","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0a46ac278c400ef099ad23ac4ccb066a37db8bb5c4d65e0a347152a499ae9eb92a07505f9c67f0897cbe6f848c9a2164c3c234dab2fea7a4dd6f4436be34080e2ff","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c080a0415ad0a93225eaec617206ec835e362d5e75fd0e1903747c1806270ec2684c7da0487ec1479cdb2affa891ff56413818ec169651c906ab932594b6e5bbb79d4998","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0a46ac278c400ef099ad23ac4ccb066a37db8bb5c4d65e0a347152a499ae9eb92a07505f9c67f0897cbe6f848c9a2164c3c234dab2fea7a4dd6f4436be34080e2ff","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88a82013a808082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0a43aba5078d2da3ecc1ec0c67191f8cf58f29f5b4db7f8d4765ea691ddbd4195a0110e568c803db5ea587b406f452cf49ddf6b6f24d41207973d6c785ffaed1454","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a808082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a00caeadf2fcba95f0deab5ee4899348ecac4a18eeb09317d6f8156b891626d219a0549c5376aba320889c2f7b61fd4a51aec5f9a1d9ed9b26cef0a3bee52fac4989","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c001a07b5568d8a3ec3c7e126f570955db304e31d3f3d7b0c4fd103b6d064a2f6f5e23a030a1b17f299352ae193b8dbce2adda473ccb04e00670f416877762971697606f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c080a07bb69d01062f9d6ecb011ad344bbe08d4eca2f6b192dde45015def4c2e6096e0a03a3df52d753e3293d2fd544f72e62ceae00ea6dcab7229685d7b1873d873d203","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c001a07b5568d8a3ec3c7e126f570955db304e31d3f3d7b0c4fd103b6d064a2f6f5e23a030a1b17f299352ae193b8dbce2adda473ccb04e00670f416877762971697606f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c080a07bb69d01062f9d6ecb011ad344bbe08d4eca2f6b192dde45015def4c2e6096e0a03a3df52d753e3293d2fd544f72e62ceae00ea6dcab7229685d7b1873d873d203","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88a82013a8082ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0621255015626b35acf19629ce318999336441537920f9f3ff1bfd44e54d8abd3a03b3426f8fa963debdfa6b44561772bdebc9524c7f63abd0d947b678f5e966502","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0b73c3ba53fc5a0f7fab636cc2b826c3873cda5d0be9dd2100fdceae7899f3310a0491905f676063924cf847fdf2e488be4606ce351748e5c88d49ed50c8d595c94","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0e60702e3f5c5f56e3d1bc2907015ec889d0557ea14e81f137056471fef0fdb9da066e601e6e55c2e37e2042401b352e81841d492d0fe4f05bfe81bba29c9e6ce1f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a085a947fb201d0b50272e7bb7a056adc9ee6f5904634ed91dbde0d650641b7de3a03635c731769302e955d41f794a63262d5d4d37d117c9db89a6b6bce927b71f42","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0e60702e3f5c5f56e3d1bc2907015ec889d0557ea14e81f137056471fef0fdb9da066e601e6e55c2e37e2042401b352e81841d492d0fe4f05bfe81bba29c9e6ce1f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a085a947fb201d0b50272e7bb7a056adc9ee6f5904634ed91dbde0d650641b7de3a03635c731769302e955d41f794a63262d5d4d37d117c9db89a6b6bce927b71f42","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88c82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0d67e28d31489af5129c4832af814a01e0baa5e5ba6245fe2d3304693ceea48e0a03bc06f1c6dd01a14826c67aa35258c0bbf7c516a9bb21e9190eaa8d3768f49bb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84982013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88d82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0a5368984aca4bc1e3d7ebc7ae4ead5e09ffd3b4b4712d039c19fdac948e5952ea065953ace0a29210440d6a0f05d6b43f482950b463b3be6b23fc63452c94b9446","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84a82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86a82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a086da25ab078729b08cf48da02eb1c1e05fe0f4e5d7b332262b68f4db3dc9b72fa04102c03c7d9f11a6fdb77d6a36d3f07e09b1ceaab0bf4ef1fdc604bcd726f83b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e782013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86b82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0cde92f395919b3205b4260867b11597f9ecf363bc1be9bbd8b5400d3381d64b3a01b9555cfa22ee8615c3033235ebad605d0bef616d08876de26719866fcc4d41e","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e882013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86a82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a086da25ab078729b08cf48da02eb1c1e05fe0f4e5d7b332262b68f4db3dc9b72fa04102c03c7d9f11a6fdb77d6a36d3f07e09b1ceaab0bf4ef1fdc604bcd726f83b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e782013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86b82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0cde92f395919b3205b4260867b11597f9ecf363bc1be9bbd8b5400d3381d64b3a01b9555cfa22ee8615c3033235ebad605d0bef616d08876de26719866fcc4d41e","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e882013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f88e82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a03dd64e48a1ae228665b3f180367997ee96bc60ee226615c900e3d86634044328a00f6cdb24633e75fa65f6b93fce9b084c1f30dd03dde97d01f25c6f10f34d5d9d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84b82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88f82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a07475efeb8dd5bf4ba7efb31ab67a9077401ed71f4e8dd13e7058ce5cfeb5a0f2a01046e93a5258bf320bc392173a49b6fef15976be4c1210f2e367af223ad8c026","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84c82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86c82013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0ca84441c7ba097a7afa5ef9ad7ef70ba58ddfffc06c5d015b5c8553f1632d103a057fee6d92055c9c031a1efa667f3ee554804c4f34a195b6dfc781e1592c20444","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a04055dfcd6e0b7264d3474ba13f76659384e5f365ebc6ba271641481b12bf410ca01ef7d04dc33fdf0c3137e31d8c822ad68bbd4f89ada52db9705bb66813d11583","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86c82013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0ca84441c7ba097a7afa5ef9ad7ef70ba58ddfffc06c5d015b5c8553f1632d103a057fee6d92055c9c031a1efa667f3ee554804c4f34a195b6dfc781e1592c20444","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a04055dfcd6e0b7264d3474ba13f76659384e5f365ebc6ba271641481b12bf410ca01ef7d04dc33fdf0c3137e31d8c822ad68bbd4f89ada52db9705bb66813d11583","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89082013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a02080212bb64a798e1e138e4991ab830cf04d37ffeedf6fde7eba0eb7d972b350a02aff43f9e5ca8d6cea6e918391188fa37bdb91b864eadec705f7c69c4a61bc5a","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84d82013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89182013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0e41c052d72950a563b8ed7fb15855beabea43ff5b038bd6a3ccc6416e3498619a0568bbd7cbff31a47e1d0b9712f382c52e74b7b28cbcb8458974d82a8d54ddc57","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84e82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86c82013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a057c342304f133ff8832d3d16a43571afe905dc9b10afc24c6e99225cca6d8817a00e2155d1904751ce0d2ba01e6475aeae254c02966773f5bc7650e37252a01a92","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0fc2a550a7798085cae28028abbe4829be29e5f3a40af221086831d0e17ca3c83a01ce21f5934b9ca566958e09e89c99fd9ed2dc4acae209a6fb81fd3a6c9879a99","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86c82013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a057c342304f133ff8832d3d16a43571afe905dc9b10afc24c6e99225cca6d8817a00e2155d1904751ce0d2ba01e6475aeae254c02966773f5bc7650e37252a01a92","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0fc2a550a7798085cae28028abbe4829be29e5f3a40af221086831d0e17ca3c83a01ce21f5934b9ca566958e09e89c99fd9ed2dc4acae209a6fb81fd3a6c9879a99","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89082013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0fa33b63666310ca1c72fc5d82639c5b8e2a7638910be7bee23ada9f139c6b891a02012cad8e991beea7dcf0b6e9346b0228699698e183e2fadfc5b9b880601af9b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84d82013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89182013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0bc6ae4e92e7a20d5ff61258653dffda636cee0fd97dd156eac7a1f231f1f2785a0323055e0e0bed496b3fec30be292338d0956ecf8baeeb34458230821589aa7fb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84e82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86e82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0bd2889395392859a83a33bfe549c09d172e1f289de29d4bc9d0a3d25ea8aa71ba075fe92140a08d8e680061852438623c9cd10e211955577d1a3b56e49e960e4e7","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a05553c929ae32692a9f742371ffcfc8c8d2b77f31a7795460297cb78c29e357e8a043e42ca4ed7eb1b8e3546de2364522735d79a2e2ff5d16f7f96d165c5815c80c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86e82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0bd2889395392859a83a33bfe549c09d172e1f289de29d4bc9d0a3d25ea8aa71ba075fe92140a08d8e680061852438623c9cd10e211955577d1a3b56e49e960e4e7","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a05553c929ae32692a9f742371ffcfc8c8d2b77f31a7795460297cb78c29e357e8a043e42ca4ed7eb1b8e3546de2364522735d79a2e2ff5d16f7f96d165c5815c80c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89282013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a055f63a6bef8e23dc437ff4ac9349a59fcde2f72d1879de50b0d3686ff648749da04cf8034df06cf6f15f31bb55979b40eeacbd28fb1d745e608acdc088e22beb66","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84f82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89382013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c4a0253448dad999692c1bf3cfb5de9e95a2e96da4e1f64133ada452a825fe9aa0757b576ceb7a2c494819960ac59e9d3a4e3da384f23c0e88ada758dc265eae94","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85082013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86c82013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a02632c4d8a443afb8d39f91d036fd4915ca3ad2f253b8f93211b4b3ee15566519a009bdc00c8eaaf22f3d7d04b53dbc777fd027a780fb4ddaf01002724ddf2879dd","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a08bda02c15ca37d35d9ad2e2f7731d24dd039f5c6c6f7eaad739daadac6db33e5a044c01e493e10929e4021c69d9df886b211eb349a865df9f0796846ad1cdf23e8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86c82013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a02632c4d8a443afb8d39f91d036fd4915ca3ad2f253b8f93211b4b3ee15566519a009bdc00c8eaaf22f3d7d04b53dbc777fd027a780fb4ddaf01002724ddf2879dd","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a08bda02c15ca37d35d9ad2e2f7731d24dd039f5c6c6f7eaad739daadac6db33e5a044c01e493e10929e4021c69d9df886b211eb349a865df9f0796846ad1cdf23e8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89082013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0ed0db75f41b2b8b89768ce5ad08716aff149dc1d5a2e593140d8964eb2da3229a02e5248cca9b5af340d73271cad4d690f7efa11c9278824aca528eb15d28aec4d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84d82013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89182013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a07108fbbabc45826dbdc8e4cf831240fb39ead7bd4b8ec5d8de64d04e2885e554a04dae4fb4bdbabb9d8f923d579e75ee980da1b4fac5773ec68f395af240f037f0","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84e82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86e82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0130b6723050095faa2e7abc69c2f785e73d333c65fae6cf2835518f970c627d5a00b90bd4f2ded1da0163ab5e81ad76d51aef005d663137347fc550313e1c8b6fc","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0993a50431e82d10d632466d45f8aaffea9a56efa59d529dfd497d3c2a06aabeba0070d3132c6ce1e4ff70b0721d1f4c03ab566b8e2af29d33148033fb3009dc29d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86e82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0130b6723050095faa2e7abc69c2f785e73d333c65fae6cf2835518f970c627d5a00b90bd4f2ded1da0163ab5e81ad76d51aef005d663137347fc550313e1c8b6fc","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0993a50431e82d10d632466d45f8aaffea9a56efa59d529dfd497d3c2a06aabeba0070d3132c6ce1e4ff70b0721d1f4c03ab566b8e2af29d33148033fb3009dc29d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89282013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a09c9d3b0d7b58bfe81a6881b9db184e0ade03c1ad11aa8f1566e2f24f50f85525a06c10cf91f4dbc24d0f78ef09a8e2310d349a034cec7e86e807d7a48ea26161e1","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84f82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89382013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0f8423b51e513618c6a4bdd2696479d91c760e11ea24657dd27fa6eb9b7da8c0ea07e9456113fb034718d1b4f4e09ade1ce78251a8c86f298b152850bc5925156cb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85082013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86e82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0d09b373d45c1bfc1c5d9b5198e69f974d4df456245e2f7a5edd486f3dd2795a9a011396197a670e7b0c4613b7ebf8aee53382930c7bd25c35dda15acae78ec0e2c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0131f5af3ece9a0b723d0c812dbcfc6cb458acf5e0846cc506215fc04d6af66d5a078d0bf7a40cc1ddcebbc4e86fb9a04bfc94f3da94b4a74476883b7b1729f8a44","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86e82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0d09b373d45c1bfc1c5d9b5198e69f974d4df456245e2f7a5edd486f3dd2795a9a011396197a670e7b0c4613b7ebf8aee53382930c7bd25c35dda15acae78ec0e2c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0131f5af3ece9a0b723d0c812dbcfc6cb458acf5e0846cc506215fc04d6af66d5a078d0bf7a40cc1ddcebbc4e86fb9a04bfc94f3da94b4a74476883b7b1729f8a44","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89282013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c286f4ee350eab70273cf9a952537534446a0f39e9bfea7340eabc04396a0e3da01e1302ae987a69836ec2c9266e6fe623db5fcdc566e37084c0c57630c4de8ee6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84f82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89382013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a09dee3fa88e365133a18035618af718a045e1a957f10f50c632f23923fd337b9ba06bbbd59489849803f8c61138932ac1a8361edb4c80789d030542829c0a2b5b7f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85082013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87082013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0c1cb1e2b41e48fecd59d72039147c76993653f061f9ea156b53c377673eef7f1a01822506f755206b60209a12ed3c84446f4fcb4ad602fa7ab7ee4ff2acde19ed6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02ed82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f87182013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a09817043ad22797d2f26ca46697db5f586c38336a171dce2d22d659889e9e9eb5a0369a5d6169586d9c831b6e017aa29fd49eac0636a136bfa5bafb95390fa95b8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ee82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f87082013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0c1cb1e2b41e48fecd59d72039147c76993653f061f9ea156b53c377673eef7f1a01822506f755206b60209a12ed3c84446f4fcb4ad602fa7ab7ee4ff2acde19ed6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02ed82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f87182013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a09817043ad22797d2f26ca46697db5f586c38336a171dce2d22d659889e9e9eb5a0369a5d6169586d9c831b6e017aa29fd49eac0636a136bfa5bafb95390fa95b8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ee82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89482013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a039357ad40087d17551ca2b94723f0394185a993671db02172a7de70c24054852a046c84070dfadd244b358690e5b89c75f3988b21b6614e6e3af2f8ca302d6c42a","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85182013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89582013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c991c81705a4c53a9255e72beb8243638c68f10c63b082755972bbbe15245d12a014f6852ae34c92882559e6810d4372109930a23b522368fdef2c85ce04e27839","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85282013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"}]` + + testcases := []struct { + Input EthBytes `json:"input"` + Output string `json:"output"` + NosigTx string `json:"nosigTx"` + }{} + + err := json.Unmarshal([]byte(tcstr), &testcases) + if err != nil { + return nil, err + } + + res := []TxTestcase{} + for _, tc := range testcases { + tx := Eth1559TxArgs{} + err := json.Unmarshal([]byte(tc.Output), &tx) + if err != nil { + return nil, err + } + res = append(res, TxTestcase{ + Input: tc.Input, + Output: tx, + TxJSON: tc.Output, + NosigTx: tc.NosigTx, + }) + } + + return res, err +} diff --git a/chain/types/ethtypes/eth_legacy_155_transactions.go b/chain/types/ethtypes/eth_legacy_155_transactions.go new file mode 100644 index 000000000..bad8a0bbf --- /dev/null +++ b/chain/types/ethtypes/eth_legacy_155_transactions.go @@ -0,0 +1,303 @@ +package ethtypes + +import ( + "fmt" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" + typescrypto "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" +) + +var _ EthTransaction = (*EthLegacy155TxArgs)(nil) + +// EthLegacy155TxArgs is a legacy Ethereum transaction that uses the EIP-155 chain replay protection mechanism +// by incorporating the chainId in the signature. +// See how the `V` value in the signature is derived from the chainId at +// https://github.com/ethereum/go-ethereum/blob/86a1f0c39494c8f5caddf6bd9fbddd4bdfa944fd/core/types/transaction_signing.go#L424 +// For EthLegacy155TxArgs, the digest that is used to create a signed transaction includes the `ChainID` but the serialised RLP transaction +// does not include the `ChainID` as an explicit field. Instead, the `ChainID` is included in the V value of the signature as mentioned above. +type EthLegacy155TxArgs struct { + legacyTx *EthLegacyHomesteadTxArgs +} + +func NewEthLegacy155TxArgs(tx *EthLegacyHomesteadTxArgs) *EthLegacy155TxArgs { + return &EthLegacy155TxArgs{legacyTx: tx} +} + +func (tx *EthLegacy155TxArgs) GetLegacyTx() *EthLegacyHomesteadTxArgs { + return tx.legacyTx +} + +func (tx *EthLegacy155TxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, error) { + from, err := EthAddressFromFilecoinAddress(smsg.Message.From) + if err != nil { + return EthTx{}, fmt.Errorf("sender was not an eth account") + } + hash, err := tx.TxHash() + if err != nil { + return EthTx{}, fmt.Errorf("failed to get tx hash: %w", err) + } + + gasPrice := EthBigInt(tx.legacyTx.GasPrice) + ethTx := EthTx{ + ChainID: build.Eip155ChainId, + Type: EthLegacyTxType, + Nonce: EthUint64(tx.legacyTx.Nonce), + Hash: hash, + To: tx.legacyTx.To, + Value: EthBigInt(tx.legacyTx.Value), + Input: tx.legacyTx.Input, + Gas: EthUint64(tx.legacyTx.GasLimit), + GasPrice: &gasPrice, + From: from, + R: EthBigInt(tx.legacyTx.R), + S: EthBigInt(tx.legacyTx.S), + V: EthBigInt(tx.legacyTx.V), + } + + return ethTx, nil +} + +func (tx *EthLegacy155TxArgs) ToUnsignedFilecoinMessage(from address.Address) (*types.Message, error) { + if err := validateEIP155ChainId(tx.legacyTx.V); err != nil { + return nil, fmt.Errorf("failed to validate EIP155 chain id: %w", err) + } + return tx.legacyTx.ToUnsignedFilecoinMessage(from) +} + +func (tx *EthLegacy155TxArgs) ToRlpUnsignedMsg() ([]byte, error) { + return toRlpUnsignedMsg(tx) +} + +func (tx *EthLegacy155TxArgs) TxHash() (EthHash, error) { + encoded, err := tx.ToRawTxBytesSigned() + if err != nil { + return EthHash{}, fmt.Errorf("failed to encode rlp signed msg: %w", err) + } + + return EthHashFromTxBytes(encoded), nil +} + +func (tx *EthLegacy155TxArgs) ToRawTxBytesSigned() ([]byte, error) { + packed1, err := tx.packTxFields() + if err != nil { + return nil, err + } + + packed1 = packed1[:len(packed1)-3] // remove chainId, r and s as they are only used for signature verification + + packed2, err := packSigFields(tx.legacyTx.V, tx.legacyTx.R, tx.legacyTx.S) + if err != nil { + return nil, err + } + + encoded, err := EncodeRLP(append(packed1, packed2...)) + if err != nil { + return nil, fmt.Errorf("failed to encode rlp signed msg: %w", err) + } + return encoded, nil +} + +func (tx *EthLegacy155TxArgs) ToRlpSignedMsg() ([]byte, error) { + return toRlpSignedMsg(tx, tx.legacyTx.V, tx.legacyTx.R, tx.legacyTx.S) +} + +func (tx *EthLegacy155TxArgs) Signature() (*typescrypto.Signature, error) { + if err := validateEIP155ChainId(tx.legacyTx.V); err != nil { + return nil, fmt.Errorf("failed to validate EIP155 chain id: %w", err) + } + r := tx.legacyTx.R.Int.Bytes() + s := tx.legacyTx.S.Int.Bytes() + v := tx.legacyTx.V.Int.Bytes() + + sig := append([]byte{}, padLeadingZeros(r, 32)...) + sig = append(sig, padLeadingZeros(s, 32)...) + sig = append(sig, v...) + + // pre-pend a one byte marker so nodes know that this is a legacy transaction + sig = append([]byte{EthLegacy155TxSignaturePrefix}, sig...) + + if len(sig) != EthLegacy155TxSignatureLen0 && len(sig) != EthLegacy155TxSignatureLen1 { + return nil, fmt.Errorf("signature is not %d OR %d bytes; it is %d bytes", EthLegacy155TxSignatureLen0, EthLegacy155TxSignatureLen1, + len(sig)) + } + + return &typescrypto.Signature{ + Type: typescrypto.SigTypeDelegated, Data: sig, + }, nil +} + +func (tx *EthLegacy155TxArgs) Sender() (address.Address, error) { + if err := validateEIP155ChainId(tx.legacyTx.V); err != nil { + return address.Address{}, fmt.Errorf("failed to validate EIP155 chain id: %w", err) + } + return sender(tx) +} + +func (tx *EthLegacy155TxArgs) Type() int { + return EthLegacyTxType +} + +var big8 = big.NewInt(8) + +func (tx *EthLegacy155TxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) { + if len(sig) != EthLegacy155TxSignatureLen0 && len(sig) != EthLegacy155TxSignatureLen1 { + return nil, fmt.Errorf("signature should be %d or %d bytes long but got %d bytes", + EthLegacy155TxSignatureLen0, EthLegacy155TxSignatureLen1, len(sig)) + } + if sig[0] != EthLegacy155TxSignaturePrefix { + return nil, fmt.Errorf("expected signature prefix 0x%x, but got 0x%x", EthLegacy155TxSignaturePrefix, sig[0]) + } + + // Remove the prefix byte as it's only used for legacy transaction identification + sig = sig[1:] + + // Extract the 'v' value from the signature + vValue := big.NewFromGo(big.NewInt(0).SetBytes(sig[64:])) + + if err := validateEIP155ChainId(vValue); err != nil { + return nil, fmt.Errorf("failed to validate EIP155 chain id: %w", err) + } + + // See https://github.com/ethereum/go-ethereum/blob/86a1f0c39494c8f5caddf6bd9fbddd4bdfa944fd/core/types/transaction_signing.go#L424 + chainIdMul := big.Mul(big.NewIntUnsigned(build.Eip155ChainId), big.NewInt(2)) + vValue = big.Sub(vValue, chainIdMul) + vValue = big.Sub(vValue, big8) + + // Adjust 'v' value for compatibility with new transactions: 27 -> 0, 28 -> 1 + if vValue.Equals(big.NewInt(27)) { + sig[64] = 0 + } else if vValue.Equals(big.NewInt(28)) { + sig[64] = 1 + } else { + return nil, fmt.Errorf("invalid 'v' value: expected 27 or 28, got %d", vValue.Int64()) + } + + return sig[0:65], nil +} + +func (tx *EthLegacy155TxArgs) InitialiseSignature(sig typescrypto.Signature) error { + if sig.Type != typescrypto.SigTypeDelegated { + return fmt.Errorf("RecoverSignature only supports Delegated signature") + } + + if len(sig.Data) != EthLegacy155TxSignatureLen0 && len(sig.Data) != EthLegacy155TxSignatureLen1 { + return fmt.Errorf("signature should be %d or %d bytes long, but got %d bytes", EthLegacy155TxSignatureLen0, + EthLegacy155TxSignatureLen1, len(sig.Data)) + } + + if sig.Data[0] != EthLegacy155TxSignaturePrefix { + return fmt.Errorf("expected signature prefix 0x01, but got 0x%x", sig.Data[0]) + } + + // ignore the first byte of the signature as it's only used for legacy transaction identification + r_, err := parseBigInt(sig.Data[1:33]) + if err != nil { + return fmt.Errorf("cannot parse r into EthBigInt: %w", err) + } + + s_, err := parseBigInt(sig.Data[33:65]) + if err != nil { + return fmt.Errorf("cannot parse s into EthBigInt: %w", err) + } + + v_, err := parseBigInt(sig.Data[65:]) + if err != nil { + return fmt.Errorf("cannot parse v into EthBigInt: %w", err) + } + + if err := validateEIP155ChainId(v_); err != nil { + return fmt.Errorf("failed to validate EIP155 chain id: %w", err) + } + + tx.legacyTx.R = r_ + tx.legacyTx.S = s_ + tx.legacyTx.V = v_ + return nil +} + +func (tx *EthLegacy155TxArgs) packTxFields() ([]interface{}, error) { + nonce, err := formatInt(tx.legacyTx.Nonce) + if err != nil { + return nil, err + } + + // format gas price + gasPrice, err := formatBigInt(tx.legacyTx.GasPrice) + if err != nil { + return nil, err + } + + gasLimit, err := formatInt(tx.legacyTx.GasLimit) + if err != nil { + return nil, err + } + + value, err := formatBigInt(tx.legacyTx.Value) + if err != nil { + return nil, err + } + + chainIdBigInt := big.NewIntUnsigned(build.Eip155ChainId) + chainId, err := formatBigInt(chainIdBigInt) + if err != nil { + return nil, err + } + + r, err := formatInt(0) + if err != nil { + return nil, err + } + + s, err := formatInt(0) + if err != nil { + return nil, err + } + + res := []interface{}{ + nonce, + gasPrice, + gasLimit, + formatEthAddr(tx.legacyTx.To), + value, + tx.legacyTx.Input, + chainId, + r, s, + } + return res, nil +} + +func validateEIP155ChainId(v big.Int) error { + chainId := deriveEIP155ChainId(v) + if !chainId.Equals(big.NewIntUnsigned(build.Eip155ChainId)) { + return fmt.Errorf("invalid chain id, expected %d, got %s", build.Eip155ChainId, chainId.String()) + } + return nil +} + +// deriveEIP155ChainId derives the chain id from the given v parameter +func deriveEIP155ChainId(v big.Int) big.Int { + if big.BitLen(v) <= 64 { + vUint64 := v.Uint64() + if vUint64 == 27 || vUint64 == 28 { + return big.NewInt(0) + } + return big.NewIntUnsigned((vUint64 - 35) / 2) + } + + v = big.Sub(v, big.NewInt(35)) + return big.Div(v, big.NewInt(2)) +} + +func calcEIP155TxSignatureLen(chain uint64, v int) int { + chainId := big.NewIntUnsigned(chain) + vVal := big.Add(big.Mul(chainId, big.NewInt(2)), big.NewInt(int64(v))) + vLen := len(vVal.Int.Bytes()) + + // EthLegacyHomesteadTxSignatureLen includes the 1 byte legacy tx marker prefix and also 1 byte for the V value. + // So we subtract 1 to not double count the length of the v value + return EthLegacyHomesteadTxSignatureLen + vLen - 1 +} diff --git a/chain/types/ethtypes/eth_legacy_155_transactions_test.go b/chain/types/ethtypes/eth_legacy_155_transactions_test.go new file mode 100644 index 000000000..fe4b78653 --- /dev/null +++ b/chain/types/ethtypes/eth_legacy_155_transactions_test.go @@ -0,0 +1,188 @@ +package ethtypes + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/big" + builtintypes "github.com/filecoin-project/go-state-types/builtin" +) + +func TestEIP155Tx(t *testing.T) { + txStr := "f86680843b9aca00835dc1ba94c2dca9a18d4a4057921d1bcb22da05e68e46b1d06480820297a0f91ee69c4603c4f21131467ee9e06ad4a96d0a29fa8064db61f3adaea0eb6e92a07181e306bb8f773d94cc3b75e9835de00c004e072e6630c0c46971d38706bb01" + + bz := mustDecodeHex(txStr) + + tx, err := parseLegacyTx(bz) + require.NoError(t, err) + + eth155Tx, ok := tx.(*EthLegacy155TxArgs) + require.True(t, ok) + + // Verify nonce + require.EqualValues(t, 0, eth155Tx.legacyTx.Nonce) + + // Verify recipient address + expectedToAddr, err := ParseEthAddress("0xc2dca9a18d4a4057921d1bcb22da05e68e46b1d0") + require.NoError(t, err) + require.EqualValues(t, expectedToAddr, *eth155Tx.legacyTx.To) + + // Verify sender address + expectedFromAddr, err := ParseEthAddress("0xA2BBB73aC59b256415e91A820b224dbAF2C268FA") + require.NoError(t, err) + sender, err := eth155Tx.Sender() + require.NoError(t, err) + expectedFromFilecoinAddr, err := expectedFromAddr.ToFilecoinAddress() + require.NoError(t, err) + require.EqualValues(t, expectedFromFilecoinAddr, sender) + + // Verify transaction value + expectedValue, ok := big.NewInt(0).SetString("100", 10) + require.True(t, ok) + require.True(t, eth155Tx.legacyTx.Value.Cmp(expectedValue) == 0) + + // Verify gas limit and gas price + expectedGasPrice, ok := big.NewInt(0).SetString("1000000000", 10) + require.True(t, ok) + require.EqualValues(t, 6144442, eth155Tx.legacyTx.GasLimit) + require.True(t, eth155Tx.legacyTx.GasPrice.Cmp(expectedGasPrice) == 0) + + require.Empty(t, eth155Tx.legacyTx.Input) + + // Verify signature values (v, r, s) + expectedV, ok := big.NewInt(0).SetString("0297", 16) + require.True(t, ok) + require.True(t, eth155Tx.legacyTx.V.Cmp(expectedV) == 0) + + expectedR, ok := big.NewInt(0).SetString("f91ee69c4603c4f21131467ee9e06ad4a96d0a29fa8064db61f3adaea0eb6e92", 16) + require.True(t, ok) + require.True(t, eth155Tx.legacyTx.R.Cmp(expectedR) == 0) + + expectedS, ok := big.NewInt(0).SetString("7181e306bb8f773d94cc3b75e9835de00c004e072e6630c0c46971d38706bb01", 16) + require.True(t, ok) + require.True(t, eth155Tx.legacyTx.S.Cmp(expectedS) == 0) + + // Convert to signed Filecoin message and verify fields + smsg, err := ToSignedFilecoinMessage(eth155Tx) + require.NoError(t, err) + + require.EqualValues(t, smsg.Message.From, sender) + + expectedToFilecoinAddr, err := eth155Tx.legacyTx.To.ToFilecoinAddress() + require.NoError(t, err) + require.EqualValues(t, smsg.Message.To, expectedToFilecoinAddr) + require.EqualValues(t, smsg.Message.Value, eth155Tx.legacyTx.Value) + require.EqualValues(t, smsg.Message.GasLimit, eth155Tx.legacyTx.GasLimit) + require.EqualValues(t, smsg.Message.GasFeeCap, eth155Tx.legacyTx.GasPrice) + require.EqualValues(t, smsg.Message.GasPremium, eth155Tx.legacyTx.GasPrice) + require.EqualValues(t, smsg.Message.Nonce, eth155Tx.legacyTx.Nonce) + require.Empty(t, smsg.Message.Params) + require.EqualValues(t, smsg.Message.Method, builtintypes.MethodsEVM.InvokeContract) + + // Convert signed Filecoin message back to Ethereum transaction and verify equality + ethTx, err := EthTransactionFromSignedFilecoinMessage(smsg) + require.NoError(t, err) + convertedLegacyTx, ok := ethTx.(*EthLegacy155TxArgs) + require.True(t, ok) + eth155Tx.legacyTx.Input = nil + require.EqualValues(t, convertedLegacyTx, eth155Tx) + + // Verify EthTx fields + ethTxVal, err := eth155Tx.ToEthTx(smsg) + require.NoError(t, err) + expectedHash, err := eth155Tx.TxHash() + require.NoError(t, err) + require.EqualValues(t, ethTxVal.Hash, expectedHash) + require.Nil(t, ethTxVal.MaxFeePerGas) + require.Nil(t, ethTxVal.MaxPriorityFeePerGas) + require.EqualValues(t, ethTxVal.Gas, eth155Tx.legacyTx.GasLimit) + require.EqualValues(t, ethTxVal.Value, eth155Tx.legacyTx.Value) + require.EqualValues(t, ethTxVal.Nonce, eth155Tx.legacyTx.Nonce) + require.EqualValues(t, ethTxVal.To, eth155Tx.legacyTx.To) + require.EqualValues(t, ethTxVal.From, expectedFromAddr) +} + +func TestDeriveEIP155ChainId(t *testing.T) { + tests := []struct { + name string + v big.Int + expectedChainId big.Int + }{ + { + name: "V equals 27", + v: big.NewInt(27), + expectedChainId: big.NewInt(0), + }, + { + name: "V equals 28", + v: big.NewInt(28), + expectedChainId: big.NewInt(0), + }, + { + name: "V small chain ID", + v: big.NewInt(37), // (37 - 35) / 2 = 1 + expectedChainId: big.NewInt(1), + }, + { + name: "V large chain ID", + v: big.NewInt(1001), // (1001 - 35) / 2 = 483 + expectedChainId: big.NewInt(483), + }, + { + name: "V very large chain ID", + v: big.NewInt(1 << 20), // (1048576 - 35) / 2 = 524770 + expectedChainId: big.NewInt(524270), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := deriveEIP155ChainId(tt.v) + require.True(t, result.Equals(tt.expectedChainId), "Expected %s, got %s for V=%s", tt.expectedChainId.String(), result.String(), tt.v.String()) + }) + } +} + +func TestCalcEIP155TxSignatureLen(t *testing.T) { + tests := []struct { + name string + chainID uint64 + expected int + }{ + { + name: "ChainID that fits in 1 byte", + chainID: 0x01, + expected: EthLegacyHomesteadTxSignatureLen + 1 - 1, + }, + { + name: "ChainID that fits in 2 bytes", + chainID: 0x0100, + expected: EthLegacyHomesteadTxSignatureLen + 2 - 1, + }, + { + name: "ChainID that fits in 3 bytes", + chainID: 0x010000, + expected: EthLegacyHomesteadTxSignatureLen + 3 - 1, + }, + { + name: "ChainID that fits in 4 bytes", + chainID: 0x01000000, + expected: EthLegacyHomesteadTxSignatureLen + 4 - 1, + }, + { + name: "ChainID that fits in 6 bytes", + chainID: 0x010000000000, + expected: EthLegacyHomesteadTxSignatureLen + 6 - 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := calcEIP155TxSignatureLen(tt.chainID, 1) + if result != tt.expected { + t.Errorf("calcEIP155TxSignatureLen(%d) = %d, want %d", tt.chainID, result, tt.expected) + } + }) + } +} diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions.go b/chain/types/ethtypes/eth_legacy_homestead_transactions.go new file mode 100644 index 000000000..fbfe6d6a3 --- /dev/null +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions.go @@ -0,0 +1,228 @@ +package ethtypes + +import ( + "fmt" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" + typescrypto "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/chain/types" +) + +var _ EthTransaction = (*EthLegacyHomesteadTxArgs)(nil) + +type EthLegacyHomesteadTxArgs struct { + Nonce int `json:"nonce"` + GasPrice big.Int `json:"gasPrice"` + GasLimit int `json:"gasLimit"` + To *EthAddress `json:"to"` + Value big.Int `json:"value"` + Input []byte `json:"input"` + V big.Int `json:"v"` + R big.Int `json:"r"` + S big.Int `json:"s"` +} + +func (tx *EthLegacyHomesteadTxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, error) { + from, err := EthAddressFromFilecoinAddress(smsg.Message.From) + if err != nil { + return EthTx{}, fmt.Errorf("sender was not an eth account") + } + hash, err := tx.TxHash() + if err != nil { + return EthTx{}, fmt.Errorf("failed to get tx hash: %w", err) + } + + gasPrice := EthBigInt(tx.GasPrice) + ethTx := EthTx{ + ChainID: EthLegacyHomesteadTxChainID, + Type: EthLegacyTxType, + Nonce: EthUint64(tx.Nonce), + Hash: hash, + To: tx.To, + Value: EthBigInt(tx.Value), + Input: tx.Input, + Gas: EthUint64(tx.GasLimit), + GasPrice: &gasPrice, + From: from, + R: EthBigInt(tx.R), + S: EthBigInt(tx.S), + V: EthBigInt(tx.V), + } + + return ethTx, nil +} + +func (tx *EthLegacyHomesteadTxArgs) ToUnsignedFilecoinMessage(from address.Address) (*types.Message, error) { + mi, err := getFilecoinMethodInfo(tx.To, tx.Input) + if err != nil { + return nil, xerrors.Errorf("failed to get method info: %w", err) + } + + return &types.Message{ + Version: 0, + To: mi.to, + From: from, + Nonce: uint64(tx.Nonce), + Value: tx.Value, + GasLimit: int64(tx.GasLimit), + GasFeeCap: tx.GasPrice, + GasPremium: tx.GasPrice, + Method: mi.method, + Params: mi.params, + }, nil +} + +func (tx *EthLegacyHomesteadTxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) { + if len(sig) != EthLegacyHomesteadTxSignatureLen { + return nil, fmt.Errorf("signature should be %d bytes long (1 byte metadata, %d bytes sig data), but got %d bytes", + EthLegacyHomesteadTxSignatureLen, EthLegacyHomesteadTxSignatureLen-1, len(sig)) + } + if sig[0] != EthLegacyHomesteadTxSignaturePrefix { + return nil, fmt.Errorf("expected signature prefix 0x%x, but got 0x%x", EthLegacyHomesteadTxSignaturePrefix, sig[0]) + } + + // Remove the prefix byte as it's only used for legacy transaction identification + sig = sig[1:] + + // Extract the 'v' value from the signature, which is the last byte in Ethereum signatures + vValue := big.NewFromGo(big.NewInt(0).SetBytes(sig[64:])) + + // Adjust 'v' value for compatibility with new transactions: 27 -> 0, 28 -> 1 + if vValue.Equals(big.NewInt(27)) { + sig[64] = 0 + } else if vValue.Equals(big.NewInt(28)) { + sig[64] = 1 + } else { + return nil, fmt.Errorf("invalid 'v' value: expected 27 or 28, got %d", vValue.Int64()) + } + + return sig, nil +} + +func (tx *EthLegacyHomesteadTxArgs) ToRlpUnsignedMsg() ([]byte, error) { + return toRlpUnsignedMsg(tx) +} + +func (tx *EthLegacyHomesteadTxArgs) TxHash() (EthHash, error) { + rlp, err := tx.ToRlpSignedMsg() + if err != nil { + return EthHash{}, err + } + return EthHashFromTxBytes(rlp), nil +} + +func (tx *EthLegacyHomesteadTxArgs) ToRlpSignedMsg() ([]byte, error) { + return toRlpSignedMsg(tx, tx.V, tx.R, tx.S) +} + +func (tx *EthLegacyHomesteadTxArgs) Signature() (*typescrypto.Signature, error) { + // throw an error if the v value is not 27 or 28 + if !tx.V.Equals(big.NewInt(27)) && !tx.V.Equals(big.NewInt(28)) { + return nil, fmt.Errorf("legacy homestead transactions only support 27 or 28 for v") + } + r := tx.R.Int.Bytes() + s := tx.S.Int.Bytes() + v := tx.V.Int.Bytes() + + sig := append([]byte{}, padLeadingZeros(r, 32)...) + sig = append(sig, padLeadingZeros(s, 32)...) + if len(v) == 0 { + sig = append(sig, 0) + } else { + sig = append(sig, v[0]) + } + // pre-pend a one byte marker so nodes know that this is a legacy transaction + sig = append([]byte{EthLegacyHomesteadTxSignaturePrefix}, sig...) + + if len(sig) != EthLegacyHomesteadTxSignatureLen { + return nil, fmt.Errorf("signature is not %d bytes", EthLegacyHomesteadTxSignatureLen) + } + + return &typescrypto.Signature{ + Type: typescrypto.SigTypeDelegated, Data: sig, + }, nil +} + +func (tx *EthLegacyHomesteadTxArgs) Sender() (address.Address, error) { + return sender(tx) +} + +func (tx *EthLegacyHomesteadTxArgs) Type() int { + return EthLegacyTxType +} + +func (tx *EthLegacyHomesteadTxArgs) InitialiseSignature(sig typescrypto.Signature) error { + if sig.Type != typescrypto.SigTypeDelegated { + return fmt.Errorf("RecoverSignature only supports Delegated signature") + } + + if len(sig.Data) != EthLegacyHomesteadTxSignatureLen { + return fmt.Errorf("signature should be %d bytes long, but got %d bytes", EthLegacyHomesteadTxSignatureLen, len(sig.Data)) + } + + if sig.Data[0] != EthLegacyHomesteadTxSignaturePrefix { + return fmt.Errorf("expected signature prefix 0x01, but got 0x%x", sig.Data[0]) + } + + // ignore the first byte of the signature as it's only used for legacy transaction identification + r_, err := parseBigInt(sig.Data[1:33]) + if err != nil { + return fmt.Errorf("cannot parse r into EthBigInt: %w", err) + } + + s_, err := parseBigInt(sig.Data[33:65]) + if err != nil { + return fmt.Errorf("cannot parse s into EthBigInt: %w", err) + } + + v_, err := parseBigInt([]byte{sig.Data[65]}) + if err != nil { + return fmt.Errorf("cannot parse v into EthBigInt: %w", err) + } + + if !v_.Equals(big.NewInt(27)) && !v_.Equals(big.NewInt(28)) { + return fmt.Errorf("legacy homestead transactions only support 27 or 28 for v") + } + + tx.R = r_ + tx.S = s_ + tx.V = v_ + return nil +} + +func (tx *EthLegacyHomesteadTxArgs) packTxFields() ([]interface{}, error) { + nonce, err := formatInt(tx.Nonce) + if err != nil { + return nil, err + } + + // format gas price + gasPrice, err := formatBigInt(tx.GasPrice) + if err != nil { + return nil, err + } + + gasLimit, err := formatInt(tx.GasLimit) + if err != nil { + return nil, err + } + + value, err := formatBigInt(tx.Value) + if err != nil { + return nil, err + } + + res := []interface{}{ + nonce, + gasPrice, + gasLimit, + formatEthAddr(tx.To), + value, + tx.Input, + } + return res, nil +} diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go b/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go new file mode 100644 index 000000000..cb4b4ac4e --- /dev/null +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go @@ -0,0 +1,299 @@ +package ethtypes + +import ( + "encoding/hex" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/big" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + + "github.com/filecoin-project/lotus/lib/sigs" +) + +func TestEthLegacyHomesteadTxArgs(t *testing.T) { + testcases := []struct { + RawTx string + ExpectedNonce uint64 + ExpectedTo string + ExpectedInput string + ExpectedGasPrice big.Int + ExpectedGasLimit int + ExpectErr bool + }{ + { + "0xf882800182540894095e7baea6a6c7c4c2dfeb977efac326af552d8780a3deadbeef0000000101010010101010101010101010101aaabbbbbbcccccccddddddddd1ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", + 0x0, + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "0xdeadbeef0000000101010010101010101010101010101aaabbbbbbcccccccddddddddd", + big.NewInt(1), + 0x5408, + false, + }, + { + "0xf85f030182520794b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa07778cde41a8a37f6a087622b38bc201bd3e7df06dce067569d4def1b53dba98c", + 0x3, + "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "0x", + big.NewInt(1), + 0x5207, + false, + }, + } + + for i, tc := range testcases { + // parse txargs + tx, err := parseLegacyTx(mustDecodeHex(tc.RawTx)) + require.NoError(t, err) + + msgRecovered, err := tx.ToRlpUnsignedMsg() + require.NoError(t, err) + + // verify signatures + from, err := tx.Sender() + require.NoError(t, err) + + smsg, err := ToSignedFilecoinMessage(tx) + require.NoError(t, err) + + sig := smsg.Signature.Data[:] + sig = sig[1:] + vValue := big.NewInt(0).SetBytes(sig[64:]) + vValue_ := big.Sub(big.NewFromGo(vValue), big.NewInt(27)) + sig[64] = byte(vValue_.Uint64()) + smsg.Signature.Data = sig + + err = sigs.Verify(&smsg.Signature, from, msgRecovered) + require.NoError(t, err) + + txArgs := tx.(*EthLegacyHomesteadTxArgs) + // verify data + require.EqualValues(t, tc.ExpectedNonce, txArgs.Nonce, i) + + expectedTo, err := ParseEthAddress(tc.ExpectedTo) + require.NoError(t, err) + require.EqualValues(t, expectedTo, *txArgs.To, i) + require.EqualValues(t, tc.ExpectedInput, "0x"+hex.EncodeToString(txArgs.Input)) + require.EqualValues(t, tc.ExpectedGasPrice, txArgs.GasPrice) + require.EqualValues(t, tc.ExpectedGasLimit, txArgs.GasLimit) + } +} + +func TestLegacyHomesteadSignatures(t *testing.T) { + testcases := []struct { + RawTx string + ExpectedR string + ExpectedS string + ExpectedV string + ExpectErr bool + ExpectErrMsg string + ExpectVMismatch bool + }{ + { + "0xf882800182540894095e7baea6a6c7c4c2dfeb977efac326af552d8780a3deadbeef0000000101010010101010101010101010101aaabbbbbbcccccccddddddddd1ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", + "0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353", + "0x1fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", + "0x1b", + false, + "", + false, + }, + { + "0xf85f030182520794b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa07778cde41a8a37f6a087622b38bc201bd3e7df06dce067569d4def1b53dba98c", + "0x98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a", + "0x7778cde41a8a37f6a087622b38bc201bd3e7df06dce067569d4def1b53dba98c", + "0x1b", + false, + "", + false, + }, + { + "0xf882800182540894095e7baea6a6c7c4c2dfeb977efac326af552d8780a3deadbeef0000000101010010101010101010101010101aaabbbbbbcccccccddddddddd1ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", + "0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353", + "0x1fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", + "0x1c", + false, + "", + true, + }, + { + "0xf882800182540894095e7baea6a6c7c4c2dfeb977efac326af552d8780a3deadbeef0000000101010010101010101010101010101aaabbbbbbcccccccddddddddd1ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", + "0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353", + "0x1fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", + "0x1f", + false, + "", + true, + }, + { + "0x00", + "", + "", + "", + true, + "not a legacy eth transaction", + false, + }, + } + + for i, tc := range testcases { + tx, err := parseLegacyTx(mustDecodeHex(tc.RawTx)) + if tc.ExpectErr { + require.Error(t, err) + require.Contains(t, err.Error(), tc.ExpectErrMsg) + continue + } + require.Nil(t, err) + + sig, err := tx.Signature() + require.Nil(t, err) + + require.NoError(t, tx.InitialiseSignature(*sig)) + + txArgs := tx.(*EthLegacyHomesteadTxArgs) + + require.Equal(t, tc.ExpectedR, "0x"+txArgs.R.Text(16), i) + require.Equal(t, tc.ExpectedS, "0x"+txArgs.S.Text(16), i) + + if tc.ExpectVMismatch { + require.NotEqual(t, tc.ExpectedV, "0x"+txArgs.V.Text(16), i) + } else { + require.Equal(t, tc.ExpectedV, "0x"+txArgs.V.Text(16), i) + } + } +} + +// https://etherscan.io/getRawTx?tx=0xc55e2b90168af6972193c1f86fa4d7d7b31a29c156665d15b9cd48618b5177ef +// https://tools.deth.net/tx-decoder +func TestEtherScanLegacyRLP(t *testing.T) { + rlp := "0xf8718301efc58506fc23ac008305161594104994f45d9d697ca104e5704a7b77d7fec3537c890821878651a4d70000801ba051222d91a379452395d0abaff981af4cfcc242f25cfaf947dea8245a477731f9a03a997c910b4701cca5d933fb26064ee5af7fe3236ff0ef2b58aa50b25aff8ca5" + bz := mustDecodeHex(rlp) + + tx, err := parseLegacyTx(bz) + require.NoError(t, err) + + ethLegacyTx, ok := tx.(*EthLegacyHomesteadTxArgs) + require.True(t, ok) + + // Verify nonce + require.EqualValues(t, 0x1efc5, ethLegacyTx.Nonce) + + // Verify recipient address + expectedToAddr, err := ParseEthAddress("0x104994f45d9d697ca104e5704a7b77d7fec3537c") + require.NoError(t, err) + require.EqualValues(t, expectedToAddr, *ethLegacyTx.To) + + // Verify sender address + expectedFromAddr, err := ParseEthAddress("0x32Be343B94f860124dC4fEe278FDCBD38C102D88") + require.NoError(t, err) + sender, err := ethLegacyTx.Sender() + require.NoError(t, err) + expectedFromFilecoinAddr, err := expectedFromAddr.ToFilecoinAddress() + require.NoError(t, err) + require.EqualValues(t, expectedFromFilecoinAddr, sender) + + // Verify transaction value + expectedValue, ok := big.NewInt(0).SetString("821878651a4d70000", 16) + require.True(t, ok) + require.True(t, ethLegacyTx.Value.Cmp(expectedValue) == 0) + + // Verify gas limit and gas price + expectedGasPrice, ok := big.NewInt(0).SetString("6fc23ac00", 16) + require.True(t, ok) + require.EqualValues(t, 0x51615, ethLegacyTx.GasLimit) + require.True(t, ethLegacyTx.GasPrice.Cmp(expectedGasPrice) == 0) + + require.Empty(t, ethLegacyTx.Input) + + // Verify signature values (v, r, s) + expectedV, ok := big.NewInt(0).SetString("1b", 16) + require.True(t, ok) + require.True(t, ethLegacyTx.V.Cmp(expectedV) == 0) + + expectedR, ok := big.NewInt(0).SetString("51222d91a379452395d0abaff981af4cfcc242f25cfaf947dea8245a477731f9", 16) + require.True(t, ok) + require.True(t, ethLegacyTx.R.Cmp(expectedR) == 0) + + expectedS, ok := big.NewInt(0).SetString("3a997c910b4701cca5d933fb26064ee5af7fe3236ff0ef2b58aa50b25aff8ca5", 16) + require.True(t, ok) + require.True(t, ethLegacyTx.S.Cmp(expectedS) == 0) + + // Convert to signed Filecoin message and verify fields + smsg, err := ToSignedFilecoinMessage(ethLegacyTx) + require.NoError(t, err) + + require.EqualValues(t, smsg.Message.From, sender) + + expectedToFilecoinAddr, err := ethLegacyTx.To.ToFilecoinAddress() + require.NoError(t, err) + require.EqualValues(t, smsg.Message.To, expectedToFilecoinAddr) + require.EqualValues(t, smsg.Message.Value, ethLegacyTx.Value) + require.EqualValues(t, smsg.Message.GasLimit, ethLegacyTx.GasLimit) + require.EqualValues(t, smsg.Message.GasFeeCap, ethLegacyTx.GasPrice) + require.EqualValues(t, smsg.Message.GasPremium, ethLegacyTx.GasPrice) + require.EqualValues(t, smsg.Message.Nonce, ethLegacyTx.Nonce) + require.Empty(t, smsg.Message.Params) + require.EqualValues(t, smsg.Message.Method, builtintypes.MethodsEVM.InvokeContract) + + // Convert signed Filecoin message back to Ethereum transaction and verify equality + ethTx, err := EthTransactionFromSignedFilecoinMessage(smsg) + require.NoError(t, err) + convertedLegacyTx, ok := ethTx.(*EthLegacyHomesteadTxArgs) + require.True(t, ok) + ethLegacyTx.Input = nil + require.EqualValues(t, convertedLegacyTx, ethLegacyTx) + + // Verify EthTx fields + ethTxVal, err := ethLegacyTx.ToEthTx(smsg) + require.NoError(t, err) + expectedHash, err := ethLegacyTx.TxHash() + require.NoError(t, err) + require.EqualValues(t, ethTxVal.Hash, expectedHash) + require.Nil(t, ethTxVal.MaxFeePerGas) + require.Nil(t, ethTxVal.MaxPriorityFeePerGas) + require.EqualValues(t, ethTxVal.Gas, ethLegacyTx.GasLimit) + require.EqualValues(t, ethTxVal.Value, ethLegacyTx.Value) + require.EqualValues(t, ethTxVal.Nonce, ethLegacyTx.Nonce) + require.EqualValues(t, ethTxVal.To, ethLegacyTx.To) + require.EqualValues(t, ethTxVal.From, expectedFromAddr) +} + +func TestFailurePaths(t *testing.T) { + // Test case for invalid RLP + invalidRLP := "0x08718301efc58506fc23ac008305161594104994f45d9d697ca104e5704a7b77d7fec3537c890821878651a4d70000801ba051222d91a379452395d0abaff981af4cfcc242f25cfaf947dea8245a477731f9a03a997c910b4701cca5d933fb26064ee5af7fe3236ff0ef2b58aa50b25aff8ca5" + decoded, err := hex.DecodeString(strings.TrimPrefix(invalidRLP, "0x")) + require.NoError(t, err) + + _, err = parseLegacyTx(decoded) + require.Error(t, err, "Expected error for invalid RLP") + + // Test case for mangled signature + mangledSignatureRLP := "0xf8718301efc58506fc23ac008305161594104994f45d9d697ca104e5704a7b77d7fec3537c890821878651a4d70000801ba051222d91a379452395d0abaff981af4cfcc242f25cfaf947dea8245a477731f9a03a997c910b4701cca5d933fb26064ee5af7fe3236ff0ef2b58aa50b25aff8ca5" + decodedSig, err := hex.DecodeString(strings.TrimPrefix(mangledSignatureRLP, "0x")) + require.NoError(t, err) + + tx, err := parseLegacyTx(decodedSig) + require.NoError(t, err) + + ethLegacyTx, ok := tx.(*EthLegacyHomesteadTxArgs) + require.True(t, ok) + + // Mangle R value + ethLegacyTx.R = big.Add(ethLegacyTx.R, big.NewInt(1)) + + expectedFromAddr, err := ParseEthAddress("0x32Be343B94f860124dC4fEe278FDCBD38C102D88") + require.NoError(t, err) + expectedFromFilecoinAddr, err := expectedFromAddr.ToFilecoinAddress() + require.NoError(t, err) + + senderAddr, err := ethLegacyTx.Sender() + require.NoError(t, err) + require.NotEqual(t, senderAddr, expectedFromFilecoinAddr, "Expected sender address to not match after mangling R value") + + // Mangle V value + ethLegacyTx.V = big.NewInt(1) + _, err = ethLegacyTx.Sender() + require.Error(t, err, "Expected error when V value is not 27 or 28") +} diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go index a3b1d0150..d1713248a 100644 --- a/chain/types/ethtypes/eth_transactions.go +++ b/chain/types/ethtypes/eth_transactions.go @@ -3,12 +3,12 @@ package ethtypes import ( "bytes" "encoding/binary" + "errors" "fmt" mathbig "math/big" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/crypto/sha3" - "golang.org/x/xerrors" "github.com/filecoin-project/go-address" gocrypto "github.com/filecoin-project/go-crypto" @@ -21,8 +21,50 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) -const Eip1559TxType = 2 +const ( + EthLegacyTxType = 0x00 + EIP1559TxType = 0x02 +) +const ( + EthEIP1559TxSignatureLen = 65 + EthLegacyHomesteadTxSignatureLen = 66 + EthLegacyHomesteadTxSignaturePrefix = 0x01 + EthLegacy155TxSignaturePrefix = 0x02 + EthLegacyHomesteadTxChainID = 0x00 +) + +var ( + EthLegacy155TxSignatureLen0 int + EthLegacy155TxSignatureLen1 int +) + +func init() { + EthLegacy155TxSignatureLen0 = calcEIP155TxSignatureLen(build.Eip155ChainId, 35) + EthLegacy155TxSignatureLen1 = calcEIP155TxSignatureLen(build.Eip155ChainId, 36) +} + +// EthTransaction defines the interface for Ethereum-like transactions. +// It provides methods to convert transactions to various formats, +// retrieve transaction details, and manipulate transaction signatures. +type EthTransaction interface { + Type() int + Sender() (address.Address, error) + Signature() (*typescrypto.Signature, error) + InitialiseSignature(sig typescrypto.Signature) error + ToUnsignedFilecoinMessage(from address.Address) (*types.Message, error) + ToRlpUnsignedMsg() ([]byte, error) + ToRlpSignedMsg() ([]byte, error) + TxHash() (EthHash, error) + ToVerifiableSignature(sig []byte) ([]byte, error) + ToEthTx(*types.SignedMessage) (EthTx, error) +} + +// EthTx represents an Ethereum transaction structure, encapsulating fields that align with the standard Ethereum transaction components. +// This structure can represent both EIP-1559 transactions and legacy Homestead transactions: +// - In EIP-1559 transactions, the `GasPrice` field is set to nil/empty. +// - In legacy Homestead transactions, the `GasPrice` field is populated to specify the fee per unit of gas, while the `MaxFeePerGas` and `MaxPriorityFeePerGas` fields are set to nil/empty. +// Additionally, both the `ChainID` and the `Type` fields are set to 0 in legacy Homestead transactions to differentiate them from EIP-1559 transactions. type EthTx struct { ChainID EthUint64 `json:"chainId"` Nonce EthUint64 `json:"nonce"` @@ -36,189 +78,129 @@ type EthTx struct { Type EthUint64 `json:"type"` Input EthBytes `json:"input"` Gas EthUint64 `json:"gas"` - MaxFeePerGas EthBigInt `json:"maxFeePerGas"` - MaxPriorityFeePerGas EthBigInt `json:"maxPriorityFeePerGas"` + MaxFeePerGas *EthBigInt `json:"maxFeePerGas,omitempty"` + MaxPriorityFeePerGas *EthBigInt `json:"maxPriorityFeePerGas,omitempty"` + GasPrice *EthBigInt `json:"gasPrice,omitempty"` AccessList []EthHash `json:"accessList"` V EthBigInt `json:"v"` R EthBigInt `json:"r"` S EthBigInt `json:"s"` } -type EthTxArgs struct { - ChainID int `json:"chainId"` - Nonce int `json:"nonce"` - To *EthAddress `json:"to"` - Value big.Int `json:"value"` - MaxFeePerGas big.Int `json:"maxFeePerGas"` - MaxPriorityFeePerGas big.Int `json:"maxPriorityFeePerGas"` - GasLimit int `json:"gasLimit"` - Input []byte `json:"input"` - V big.Int `json:"v"` - R big.Int `json:"r"` - S big.Int `json:"s"` +func (tx *EthTx) GasFeeCap() (EthBigInt, error) { + if tx.GasPrice == nil && tx.MaxFeePerGas == nil { + return EthBigInt{}, fmt.Errorf("gas fee cap is not set") + } + if tx.MaxFeePerGas != nil { + return *tx.MaxFeePerGas, nil + } + return *tx.GasPrice, nil } -// EthTxFromSignedEthMessage does NOT populate: -// - BlockHash -// - BlockNumber -// - TransactionIndex -// - Hash -func EthTxFromSignedEthMessage(smsg *types.SignedMessage) (EthTx, error) { - // The from address is always an f410f address, never an ID or other address. - if !IsEthAddress(smsg.Message.From) { - return EthTx{}, xerrors.Errorf("sender must be an eth account, was %s", smsg.Message.From) +func (tx *EthTx) GasPremium() (EthBigInt, error) { + if tx.GasPrice == nil && tx.MaxPriorityFeePerGas == nil { + return EthBigInt{}, fmt.Errorf("gas premium is not set") } - // Probably redundant, but we might as well check. + if tx.MaxPriorityFeePerGas != nil { + return *tx.MaxPriorityFeePerGas, nil + } + + return *tx.GasPrice, nil +} + +func EthTransactionFromSignedFilecoinMessage(smsg *types.SignedMessage) (EthTransaction, error) { + if smsg == nil { + return nil, errors.New("signed message is nil") + } + + // Ensure the signature type is delegated. if smsg.Signature.Type != typescrypto.SigTypeDelegated { - return EthTx{}, xerrors.Errorf("signature is not delegated type, is type: %d", smsg.Signature.Type) + return nil, fmt.Errorf("signature is not delegated type, is type: %d", smsg.Signature.Type) } - txArgs, err := EthTxArgsFromUnsignedEthMessage(&smsg.Message) + // Convert Filecoin address to Ethereum address. + _, err := EthAddressFromFilecoinAddress(smsg.Message.From) if err != nil { - return EthTx{}, xerrors.Errorf("failed to convert the unsigned message: %w", err) + return nil, fmt.Errorf("sender was not an eth account") } - r, s, v, err := RecoverSignature(smsg.Signature) + // Extract Ethereum parameters and recipient from the message. + params, to, err := getEthParamsAndRecipient(&smsg.Message) if err != nil { - return EthTx{}, xerrors.Errorf("failed to recover signature: %w", err) + return nil, fmt.Errorf("failed to parse input params and recipient: %w", err) } - from, err := EthAddressFromFilecoinAddress(smsg.Message.From) - if err != nil { - // This should be impossible as we've already asserted that we have an EthAddress - // sender... - return EthTx{}, xerrors.Errorf("sender was not an eth account") + // Check for supported message version. + if smsg.Message.Version != 0 { + return nil, fmt.Errorf("unsupported msg version: %d", smsg.Message.Version) } - return EthTx{ - Nonce: EthUint64(txArgs.Nonce), - ChainID: EthUint64(txArgs.ChainID), - To: txArgs.To, - From: from, - Value: EthBigInt(txArgs.Value), - Type: Eip1559TxType, - Gas: EthUint64(txArgs.GasLimit), - MaxFeePerGas: EthBigInt(txArgs.MaxFeePerGas), - MaxPriorityFeePerGas: EthBigInt(txArgs.MaxPriorityFeePerGas), - AccessList: []EthHash{}, - V: v, - R: r, - S: s, - Input: txArgs.Input, - }, nil + // Determine the type of transaction based on the signature length + switch len(smsg.Signature.Data) { + case EthEIP1559TxSignatureLen: + tx := Eth1559TxArgs{ + ChainID: build.Eip155ChainId, + Nonce: int(smsg.Message.Nonce), + To: to, + Value: smsg.Message.Value, + Input: params, + MaxFeePerGas: smsg.Message.GasFeeCap, + MaxPriorityFeePerGas: smsg.Message.GasPremium, + GasLimit: int(smsg.Message.GasLimit), + } + if err := tx.InitialiseSignature(smsg.Signature); err != nil { + return nil, fmt.Errorf("failed to initialise signature: %w", err) + } + return &tx, nil + + case EthLegacyHomesteadTxSignatureLen, EthLegacy155TxSignatureLen0, EthLegacy155TxSignatureLen1: + legacyTx := &EthLegacyHomesteadTxArgs{ + Nonce: int(smsg.Message.Nonce), + To: to, + Value: smsg.Message.Value, + Input: params, + GasPrice: smsg.Message.GasFeeCap, + GasLimit: int(smsg.Message.GasLimit), + } + // Process based on the first byte of the signature + switch smsg.Signature.Data[0] { + case EthLegacyHomesteadTxSignaturePrefix: + if err := legacyTx.InitialiseSignature(smsg.Signature); err != nil { + return nil, fmt.Errorf("failed to initialise signature: %w", err) + } + return legacyTx, nil + case EthLegacy155TxSignaturePrefix: + tx := &EthLegacy155TxArgs{ + legacyTx: legacyTx, + } + if err := tx.InitialiseSignature(smsg.Signature); err != nil { + return nil, fmt.Errorf("failed to initialise signature: %w", err) + } + return tx, nil + default: + return nil, fmt.Errorf("unsupported legacy transaction; first byte of signature is %d", smsg.Signature.Data[0]) + } + + default: + return nil, fmt.Errorf("unsupported signature length") + } } -func EthTxArgsFromUnsignedEthMessage(msg *types.Message) (EthTxArgs, error) { - var ( - to *EthAddress - params []byte - err error - ) - - if msg.Version != 0 { - return EthTxArgs{}, xerrors.Errorf("unsupported msg version: %d", msg.Version) - } - - if len(msg.Params) > 0 { - paramsReader := bytes.NewReader(msg.Params) - params, err = cbg.ReadByteArray(paramsReader, uint64(len(msg.Params))) - if err != nil { - return EthTxArgs{}, xerrors.Errorf("failed to read params byte array: %w", err) - } - if paramsReader.Len() != 0 { - return EthTxArgs{}, xerrors.Errorf("extra data found in params") - } - if len(params) == 0 { - return EthTxArgs{}, xerrors.Errorf("non-empty params encode empty byte array") - } - } - - if msg.To == builtintypes.EthereumAddressManagerActorAddr { - if msg.Method != builtintypes.MethodsEAM.CreateExternal { - return EthTxArgs{}, fmt.Errorf("unsupported EAM method") - } - } else if msg.Method == builtintypes.MethodsEVM.InvokeContract { - addr, err := EthAddressFromFilecoinAddress(msg.To) - if err != nil { - return EthTxArgs{}, err - } - to = &addr - } else { - return EthTxArgs{}, - xerrors.Errorf("invalid methodnum %d: only allowed method is InvokeContract(%d)", - msg.Method, builtintypes.MethodsEVM.InvokeContract) - } - - return EthTxArgs{ - ChainID: build.Eip155ChainId, - Nonce: int(msg.Nonce), - To: to, - Value: msg.Value, - Input: params, - MaxFeePerGas: msg.GasFeeCap, - MaxPriorityFeePerGas: msg.GasPremium, - GasLimit: int(msg.GasLimit), - }, nil -} - -func (tx *EthTxArgs) ToUnsignedMessage(from address.Address) (*types.Message, error) { - if tx.ChainID != build.Eip155ChainId { - return nil, xerrors.Errorf("unsupported chain id: %d", tx.ChainID) - } - - var err error - var params []byte - if len(tx.Input) > 0 { - buf := new(bytes.Buffer) - if err = cbg.WriteByteArray(buf, tx.Input); err != nil { - return nil, xerrors.Errorf("failed to write input args: %w", err) - } - params = buf.Bytes() - } - - var to address.Address - var method abi.MethodNum - // nil indicates the EAM, only CreateExternal is allowed - if tx.To == nil { - method = builtintypes.MethodsEAM.CreateExternal - to = builtintypes.EthereumAddressManagerActorAddr - } else { - method = builtintypes.MethodsEVM.InvokeContract - to, err = tx.To.ToFilecoinAddress() - if err != nil { - return nil, xerrors.Errorf("failed to convert To into filecoin addr: %w", err) - } - } - - return &types.Message{ - Version: 0, - To: to, - From: from, - Nonce: uint64(tx.Nonce), - Value: tx.Value, - GasLimit: int64(tx.GasLimit), - GasFeeCap: tx.MaxFeePerGas, - GasPremium: tx.MaxPriorityFeePerGas, - Method: method, - Params: params, - }, nil -} - -func (tx *EthTxArgs) ToSignedMessage() (*types.SignedMessage, error) { +func ToSignedFilecoinMessage(tx EthTransaction) (*types.SignedMessage, error) { from, err := tx.Sender() if err != nil { - return nil, xerrors.Errorf("failed to calculate sender: %w", err) + return nil, fmt.Errorf("failed to calculate sender: %w", err) } - unsignedMsg, err := tx.ToUnsignedMessage(from) + unsignedMsg, err := tx.ToUnsignedFilecoinMessage(from) if err != nil { - return nil, xerrors.Errorf("failed to convert to unsigned msg: %w", err) + return nil, fmt.Errorf("failed to convert to unsigned msg: %w", err) } siggy, err := tx.Signature() if err != nil { - return nil, xerrors.Errorf("failed to calculate signature: %w", err) + return nil, fmt.Errorf("failed to calculate signature: %w", err) } return &types.SignedMessage{ @@ -227,349 +209,91 @@ func (tx *EthTxArgs) ToSignedMessage() (*types.SignedMessage, error) { }, nil } -func (tx *EthTxArgs) HashedOriginalRlpMsg() ([]byte, error) { - msg, err := tx.ToRlpUnsignedMsg() - if err != nil { - return nil, err - } - - hasher := sha3.NewLegacyKeccak256() - hasher.Write(msg) - hash := hasher.Sum(nil) - return hash, nil -} - -func (tx *EthTxArgs) ToRlpUnsignedMsg() ([]byte, error) { - packed, err := tx.packTxFields() - if err != nil { - return nil, err - } - - encoded, err := EncodeRLP(packed) - if err != nil { - return nil, err - } - return append([]byte{0x02}, encoded...), nil -} - -func (tx *EthTx) ToEthTxArgs() EthTxArgs { - return EthTxArgs{ - ChainID: int(tx.ChainID), - Nonce: int(tx.Nonce), - To: tx.To, - Value: big.Int(tx.Value), - MaxFeePerGas: big.Int(tx.MaxFeePerGas), - MaxPriorityFeePerGas: big.Int(tx.MaxPriorityFeePerGas), - GasLimit: int(tx.Gas), - Input: tx.Input, - V: big.Int(tx.V), - R: big.Int(tx.R), - S: big.Int(tx.S), - } -} - -func (tx *EthTx) TxHash() (EthHash, error) { - ethTxArgs := tx.ToEthTxArgs() - return (ðTxArgs).TxHash() -} - -func (tx *EthTxArgs) TxHash() (EthHash, error) { - rlp, err := tx.ToRlpSignedMsg() - if err != nil { - return EmptyEthHash, err - } - - return EthHashFromTxBytes(rlp), nil -} - -func (tx *EthTxArgs) ToRlpSignedMsg() ([]byte, error) { - packed1, err := tx.packTxFields() - if err != nil { - return nil, err - } - - packed2, err := tx.packSigFields() - if err != nil { - return nil, err - } - - encoded, err := EncodeRLP(append(packed1, packed2...)) - if err != nil { - return nil, err - } - return append([]byte{0x02}, encoded...), nil -} - -func (tx *EthTxArgs) packTxFields() ([]interface{}, error) { - chainId, err := formatInt(tx.ChainID) - if err != nil { - return nil, err - } - - nonce, err := formatInt(tx.Nonce) - if err != nil { - return nil, err - } - - maxPriorityFeePerGas, err := formatBigInt(tx.MaxPriorityFeePerGas) - if err != nil { - return nil, err - } - - maxFeePerGas, err := formatBigInt(tx.MaxFeePerGas) - if err != nil { - return nil, err - } - - gasLimit, err := formatInt(tx.GasLimit) - if err != nil { - return nil, err - } - - value, err := formatBigInt(tx.Value) - if err != nil { - return nil, err - } - - res := []interface{}{ - chainId, - nonce, - maxPriorityFeePerGas, - maxFeePerGas, - gasLimit, - formatEthAddr(tx.To), - value, - tx.Input, - []interface{}{}, // access list - } - return res, nil -} - -func (tx *EthTxArgs) packSigFields() ([]interface{}, error) { - r, err := formatBigInt(tx.R) - if err != nil { - return nil, err - } - - s, err := formatBigInt(tx.S) - if err != nil { - return nil, err - } - - v, err := formatBigInt(tx.V) - if err != nil { - return nil, err - } - - res := []interface{}{v, r, s} - return res, nil -} - -func (tx *EthTxArgs) Signature() (*typescrypto.Signature, error) { - r := tx.R.Int.Bytes() - s := tx.S.Int.Bytes() - v := tx.V.Int.Bytes() - - sig := append([]byte{}, padLeadingZeros(r, 32)...) - sig = append(sig, padLeadingZeros(s, 32)...) - if len(v) == 0 { - sig = append(sig, 0) - } else { - sig = append(sig, v[0]) - } - - if len(sig) != 65 { - return nil, fmt.Errorf("signature is not 65 bytes") - } - return &typescrypto.Signature{ - Type: typescrypto.SigTypeDelegated, Data: sig, - }, nil -} - -func (tx *EthTxArgs) Sender() (address.Address, error) { - msg, err := tx.ToRlpUnsignedMsg() - if err != nil { - return address.Undef, err - } - - hasher := sha3.NewLegacyKeccak256() - hasher.Write(msg) - hash := hasher.Sum(nil) - - sig, err := tx.Signature() - if err != nil { - return address.Undef, err - } - - pubk, err := gocrypto.EcRecover(hash, sig.Data) - if err != nil { - return address.Undef, err - } - - ethAddr, err := EthAddressFromPubKey(pubk) - if err != nil { - return address.Undef, err - } - - ea, err := CastEthAddress(ethAddr) - if err != nil { - return address.Undef, err - } - - return ea.ToFilecoinAddress() -} - -func RecoverSignature(sig typescrypto.Signature) (r, s, v EthBigInt, err error) { - if sig.Type != typescrypto.SigTypeDelegated { - return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("RecoverSignature only supports Delegated signature") - } - - if len(sig.Data) != 65 { - return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("signature should be 65 bytes long, but got %d bytes", len(sig.Data)) - } - - r_, err := parseBigInt(sig.Data[0:32]) - if err != nil { - return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("cannot parse r into EthBigInt") - } - - s_, err := parseBigInt(sig.Data[32:64]) - if err != nil { - return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("cannot parse s into EthBigInt") - } - - v_, err := parseBigInt([]byte{sig.Data[64]}) - if err != nil { - return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("cannot parse v into EthBigInt") - } - - return EthBigInt(r_), EthBigInt(s_), EthBigInt(v_), nil -} - -func parseEip1559Tx(data []byte) (*EthTxArgs, error) { - if data[0] != 2 { - return nil, fmt.Errorf("not an EIP-1559 transaction: first byte is not 2") - } - - d, err := DecodeRLP(data[1:]) - if err != nil { - return nil, err - } - decoded, ok := d.([]interface{}) - if !ok { - return nil, fmt.Errorf("not an EIP-1559 transaction: decoded data is not a list") - } - - if len(decoded) != 12 { - return nil, fmt.Errorf("not an EIP-1559 transaction: should have 12 elements in the rlp list") - } - - chainId, err := parseInt(decoded[0]) - if err != nil { - return nil, err - } - - nonce, err := parseInt(decoded[1]) - if err != nil { - return nil, err - } - - maxPriorityFeePerGas, err := parseBigInt(decoded[2]) - if err != nil { - return nil, err - } - - maxFeePerGas, err := parseBigInt(decoded[3]) - if err != nil { - return nil, err - } - - gasLimit, err := parseInt(decoded[4]) - if err != nil { - return nil, err - } - - to, err := parseEthAddr(decoded[5]) - if err != nil { - return nil, err - } - - value, err := parseBigInt(decoded[6]) - if err != nil { - return nil, err - } - - input, err := parseBytes(decoded[7]) - if err != nil { - return nil, err - } - - accessList, ok := decoded[8].([]interface{}) - if !ok || (ok && len(accessList) != 0) { - return nil, fmt.Errorf("access list should be an empty list") - } - - r, err := parseBigInt(decoded[10]) - if err != nil { - return nil, err - } - - s, err := parseBigInt(decoded[11]) - if err != nil { - return nil, err - } - - v, err := parseBigInt(decoded[9]) - if err != nil { - return nil, err - } - - // EIP-1559 and EIP-2930 transactions only support 0 or 1 for v - // Legacy and EIP-155 transactions support other values - // https://github.com/ethers-io/ethers.js/blob/56fabe987bb8c1e4891fdf1e5d3fe8a4c0471751/packages/transactions/src.ts/index.ts#L333 - if !v.Equals(big.NewInt(0)) && !v.Equals(big.NewInt(1)) { - return nil, fmt.Errorf("EIP-1559 transactions only support 0 or 1 for v") - } - - args := EthTxArgs{ - ChainID: chainId, - Nonce: nonce, - To: to, - MaxPriorityFeePerGas: maxPriorityFeePerGas, - MaxFeePerGas: maxFeePerGas, - GasLimit: gasLimit, - Value: value, - Input: input, - V: v, - R: r, - S: s, - } - return &args, nil -} - -func ParseEthTxArgs(data []byte) (*EthTxArgs, error) { +func ParseEthTransaction(data []byte) (EthTransaction, error) { if len(data) == 0 { return nil, fmt.Errorf("empty data") } - if data[0] > 0x7f { - // legacy transaction - return nil, fmt.Errorf("legacy transaction is not supported") - } - - if data[0] == 1 { + switch data[0] { + case 1: // EIP-2930 return nil, fmt.Errorf("EIP-2930 transaction is not supported") - } - - if data[0] == Eip1559TxType { + case EIP1559TxType: // EIP-1559 return parseEip1559Tx(data) + default: + if data[0] > 0x7f { + tx, err := parseLegacyTx(data) + if err != nil { + return nil, fmt.Errorf("failed to parse legacy transaction: %w", err) + } + return tx, nil + } } return nil, fmt.Errorf("unsupported transaction type") } +type methodInfo struct { + to address.Address + method abi.MethodNum + params []byte +} + +func getFilecoinMethodInfo(recipient *EthAddress, input []byte) (*methodInfo, error) { + var params []byte + if len(input) > 0 { + buf := new(bytes.Buffer) + if err := cbg.WriteByteArray(buf, input); err != nil { + return nil, fmt.Errorf("failed to write input args: %w", err) + } + params = buf.Bytes() + } + + var to address.Address + var method abi.MethodNum + + if recipient == nil { + // If recipient is nil, use Ethereum Address Manager Actor and CreateExternal method + method = builtintypes.MethodsEAM.CreateExternal + to = builtintypes.EthereumAddressManagerActorAddr + } else { + // Otherwise, use InvokeContract method and convert EthAddress to Filecoin address + method = builtintypes.MethodsEVM.InvokeContract + var err error + to, err = recipient.ToFilecoinAddress() + if err != nil { + return nil, fmt.Errorf("failed to convert EthAddress to Filecoin address: %w", err) + } + } + + return &methodInfo{ + to: to, + method: method, + params: params, + }, nil +} + +func packSigFields(v, r, s big.Int) ([]interface{}, error) { + rr, err := formatBigInt(r) + if err != nil { + return nil, err + } + + ss, err := formatBigInt(s) + if err != nil { + return nil, err + } + + vv, err := formatBigInt(v) + if err != nil { + return nil, err + } + + res := []interface{}{vv, rr, ss} + return res, nil +} + func padLeadingZeros(data []byte, length int) []byte { if len(data) >= length { return data @@ -667,3 +391,204 @@ func parseEthAddr(v interface{}) (*EthAddress, error) { } return &addr, nil } + +func getEthParamsAndRecipient(msg *types.Message) (params []byte, to *EthAddress, err error) { + if len(msg.Params) > 0 { + paramsReader := bytes.NewReader(msg.Params) + var err error + params, err = cbg.ReadByteArray(paramsReader, uint64(len(msg.Params))) + if err != nil { + return nil, nil, fmt.Errorf("failed to read params byte array: %w", err) + } + if paramsReader.Len() != 0 { + return nil, nil, fmt.Errorf("extra data found in params") + } + if len(params) == 0 { + return nil, nil, fmt.Errorf("non-empty params encode empty byte array") + } + } + + if msg.To == builtintypes.EthereumAddressManagerActorAddr { + if msg.Method != builtintypes.MethodsEAM.CreateExternal { + return nil, nil, fmt.Errorf("unsupported EAM method") + } + } else if msg.Method == builtintypes.MethodsEVM.InvokeContract { + addr, err := EthAddressFromFilecoinAddress(msg.To) + if err != nil { + return nil, nil, err + } + to = &addr + } else { + return nil, nil, + fmt.Errorf("invalid methodnum %d: only allowed method is InvokeContract(%d) or CreateExternal(%d)", + msg.Method, builtintypes.MethodsEVM.InvokeContract, builtintypes.MethodsEAM.CreateExternal) + } + + return params, to, nil +} + +func parseLegacyTx(data []byte) (EthTransaction, error) { + if data[0] <= 0x7f { + return nil, fmt.Errorf("not a legacy eth transaction") + } + + d, err := DecodeRLP(data) + if err != nil { + return nil, err + } + decoded, ok := d.([]interface{}) + if !ok { + return nil, fmt.Errorf("not a Legacy transaction: decoded data is not a list") + } + + if len(decoded) != 9 { + return nil, fmt.Errorf("not a Legacy transaction: should have 9 elements in the rlp list") + } + + nonce, err := parseInt(decoded[0]) + if err != nil { + return nil, err + } + + gasPrice, err := parseBigInt(decoded[1]) + if err != nil { + return nil, err + } + + gasLimit, err := parseInt(decoded[2]) + if err != nil { + return nil, err + } + + to, err := parseEthAddr(decoded[3]) + if err != nil { + return nil, err + } + + value, err := parseBigInt(decoded[4]) + if err != nil { + return nil, err + } + + input, ok := decoded[5].([]byte) + if !ok { + return nil, fmt.Errorf("input is not a byte slice") + } + + v, err := parseBigInt(decoded[6]) + if err != nil { + return nil, err + } + + r, err := parseBigInt(decoded[7]) + if err != nil { + return nil, err + } + + s, err := parseBigInt(decoded[8]) + if err != nil { + return nil, err + } + + tx := &EthLegacyHomesteadTxArgs{ + Nonce: nonce, + GasPrice: gasPrice, + GasLimit: gasLimit, + To: to, + Value: value, + Input: input, + V: v, + R: r, + S: s, + } + + chainId := deriveEIP155ChainId(v) + if chainId.Equals(big.NewInt(0)) { + // This is a legacy Homestead transaction + if !v.Equals(big.NewInt(27)) && !v.Equals(big.NewInt(28)) { + return nil, fmt.Errorf("legacy homestead transactions only support 27 or 28 for v, got %d", v.Uint64()) + } + return tx, nil + } + + // This is a EIP-155 transaction -> ensure chainID protection + if err := validateEIP155ChainId(v); err != nil { + return nil, fmt.Errorf("failed to validate EIP155 chain id: %w", err) + } + + return &EthLegacy155TxArgs{ + legacyTx: tx, + }, nil +} + +type RlpPackable interface { + packTxFields() ([]interface{}, error) +} + +func toRlpUnsignedMsg(tx RlpPackable) ([]byte, error) { + packedFields, err := tx.packTxFields() + if err != nil { + return nil, err + } + encoded, err := EncodeRLP(packedFields) + if err != nil { + return nil, err + } + return encoded, nil +} + +func toRlpSignedMsg(tx RlpPackable, V, R, S big.Int) ([]byte, error) { + packed1, err := tx.packTxFields() + if err != nil { + return nil, err + } + + packed2, err := packSigFields(V, R, S) + if err != nil { + return nil, err + } + + encoded, err := EncodeRLP(append(packed1, packed2...)) + if err != nil { + return nil, fmt.Errorf("failed to encode rlp signed msg: %w", err) + } + return encoded, nil +} + +func sender(tx EthTransaction) (address.Address, error) { + msg, err := tx.ToRlpUnsignedMsg() + if err != nil { + return address.Undef, fmt.Errorf("failed to get rlp unsigned msg: %w", err) + } + + hasher := sha3.NewLegacyKeccak256() + hasher.Write(msg) + hash := hasher.Sum(nil) + + sig, err := tx.Signature() + if err != nil { + return address.Undef, fmt.Errorf("failed to get signature: %w", err) + } + + sigData, err := tx.ToVerifiableSignature(sig.Data) + if err != nil { + return address.Undef, fmt.Errorf("failed to get verifiable signature: %w", err) + } + + pubk, err := gocrypto.EcRecover(hash, sigData) + if err != nil { + return address.Undef, fmt.Errorf("failed to recover pubkey: %w", err) + } + + ethAddr, err := EthAddressFromPubKey(pubk) + if err != nil { + return address.Undef, fmt.Errorf("failed to get eth address from pubkey: %w", err) + } + + ea, err := CastEthAddress(ethAddr) + if err != nil { + return address.Undef, fmt.Errorf("failed to cast eth address: %w", err) + } + + return ea.ToFilecoinAddress() +} diff --git a/chain/types/ethtypes/eth_transactions_test.go b/chain/types/ethtypes/eth_transactions_test.go index 68abc55dd..21e1f0475 100644 --- a/chain/types/ethtypes/eth_transactions_test.go +++ b/chain/types/ethtypes/eth_transactions_test.go @@ -1,251 +1,167 @@ package ethtypes import ( - "bytes" - "encoding/hex" - "encoding/json" - "fmt" "testing" "github.com/stretchr/testify/require" - "golang.org/x/crypto/sha3" "github.com/filecoin-project/go-address" - gocrypto "github.com/filecoin-project/go-crypto" - actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" builtintypes "github.com/filecoin-project/go-state-types/builtin" - "github.com/filecoin-project/go-state-types/builtin/v10/evm" - init10 "github.com/filecoin-project/go-state-types/builtin/v10/init" - crypto1 "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/crypto" - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/lib/sigs" - _ "github.com/filecoin-project/lotus/lib/sigs/delegated" + "github.com/filecoin-project/lotus/chain/types" ) -type TxTestcase struct { - TxJSON string - NosigTx string - Input EthBytes - Output EthTxArgs -} +func TestEthTransactionFromSignedFilecoinMessage(t *testing.T) { + eip1559sig := make([]byte, 65) + eip1559sig[0] = 1 -func TestTxArgs(t *testing.T) { - testcases, err := prepareTxTestcases() - require.Nil(t, err) - require.NotEmpty(t, testcases) + legacySig := make([]byte, 66) + legacySig[0] = 1 + legacySig[65] = 27 - for i, tc := range testcases { - comment := fmt.Sprintf("case %d: \n%s\n%s", i, tc.TxJSON, hex.EncodeToString(tc.Input)) - - // parse txargs - txArgs, err := ParseEthTxArgs(tc.Input) - require.NoError(t, err, comment) - - msgRecovered, err := txArgs.ToRlpUnsignedMsg() - require.NoError(t, err, comment) - require.Equal(t, tc.NosigTx, "0x"+hex.EncodeToString(msgRecovered), comment) - - // verify signatures - from, err := txArgs.Sender() - require.NoError(t, err, comment) - - smsg, err := txArgs.ToSignedMessage() - require.NoError(t, err, comment) - - err = sigs.Verify(&smsg.Signature, from, msgRecovered) - require.NoError(t, err, comment) - - // verify data - require.Equal(t, tc.Output.ChainID, txArgs.ChainID, comment) - require.Equal(t, tc.Output.Nonce, txArgs.Nonce, comment) - require.Equal(t, tc.Output.To, txArgs.To, comment) - } -} - -func TestSignatures(t *testing.T) { - testcases := []struct { - RawTx string - ExpectedR string - ExpectedS string - ExpectedV string - ExpectErr bool - }{ - { - "0x02f8598401df5e76028301d69083086a5e835532dd808080c080a0457e33227ac7ceee2ef121755e26b872b6fb04221993f9939349bb7b0a3e1595a02d8ef379e1d2a9e30fa61c92623cc9ed72d80cf6a48cfea341cb916bcc0a81bc", - `"0x457e33227ac7ceee2ef121755e26b872b6fb04221993f9939349bb7b0a3e1595"`, - `"0x2d8ef379e1d2a9e30fa61c92623cc9ed72d80cf6a48cfea341cb916bcc0a81bc"`, - `"0x0"`, - false, - }, - { - "0x02f8598401df5e76038301d69083086a5e835532dd808080c001a012a232866dcb0671eb0ddc01fb9c01d6ef384ec892bb29691ed0d2d293052ddfa052a6ae38c6139930db21a00eee2a4caced9a6500991b823d64ec664d003bc4b1", - `"0x12a232866dcb0671eb0ddc01fb9c01d6ef384ec892bb29691ed0d2d293052ddf"`, - `"0x52a6ae38c6139930db21a00eee2a4caced9a6500991b823d64ec664d003bc4b1"`, - `"0x1"`, - false, - }, - { - "0x00", - `""`, - `""`, - `""`, - true, - }, - } - - for _, tc := range testcases { - tx, err := ParseEthTxArgs(mustDecodeHex(tc.RawTx)) - if tc.ExpectErr { - require.Error(t, err) - continue - } - require.Nil(t, err) - - sig, err := tx.Signature() - require.Nil(t, err) - - r, s, v, err := RecoverSignature(*sig) - require.Nil(t, err) - - marshaledR, err := r.MarshalJSON() - require.Nil(t, err) - - marshaledS, err := s.MarshalJSON() - require.Nil(t, err) - - marshaledV, err := v.MarshalJSON() - require.Nil(t, err) - - require.Equal(t, tc.ExpectedR, string(marshaledR)) - require.Equal(t, tc.ExpectedS, string(marshaledS)) - require.Equal(t, tc.ExpectedV, string(marshaledV)) - } -} - -func TestTransformParams(t *testing.T) { - constructorParams, err := actors.SerializeParams(&evm.ConstructorParams{ - Initcode: mustDecodeHex("0x1122334455"), - }) - require.Nil(t, err) - - evmActorCid, ok := actors.GetActorCodeID(actorstypes.Version10, "reward") - require.True(t, ok) - - params, err := actors.SerializeParams(&init10.ExecParams{ - CodeCID: evmActorCid, - ConstructorParams: constructorParams, - }) - require.Nil(t, err) - - var exec init10.ExecParams - reader := bytes.NewReader(params) - err1 := exec.UnmarshalCBOR(reader) - require.Nil(t, err1) - - var evmParams evm.ConstructorParams - reader1 := bytes.NewReader(exec.ConstructorParams) - err1 = evmParams.UnmarshalCBOR(reader1) - require.Nil(t, err1) - - require.Equal(t, mustDecodeHex("0x1122334455"), evmParams.Initcode) -} - -func TestEcRecover(t *testing.T) { - rHex := "0x479ff7fa64cf8bf641eb81635d1e8a698530d2f219951d234539e6d074819529" - sHex := "0x4b6146d27be50cdbb2853ba9a42f207af8d730272f1ebe9c9a78aeef1d6aa924" - fromHex := "0x3947D223fc5415f43ea099866AB62B1d4D33814D" - v := byte(0) - - msgHex := "0x02f1030185012a05f2008504a817c800825208942b87d1cb599bc2a606db9a0169fcec96af04ad3a880de0b6b3a764000080c0" - pubKeyHex := "0x048362749392a0e192eff600d21155236c5a0648d300a8e0e44d8617712c7c96384c75825dc5c7595df2a5005fd8a0f7c809119fb9ab36403ed712244fc329348e" - - msg := mustDecodeHex(msgHex) - pubKey := mustDecodeHex(pubKeyHex) - r := mustDecodeHex(rHex) - s := mustDecodeHex(sHex) - from := mustDecodeHex(fromHex) - - sig := append(r, s...) - sig = append(sig, v) - require.Equal(t, 65, len(sig)) - - sha := sha3.NewLegacyKeccak256() - sha.Write(msg) - h := sha.Sum(nil) - - pubk, err := gocrypto.EcRecover(h, sig) - require.Nil(t, err) - require.Equal(t, pubKey, pubk) - - sha.Reset() - sha.Write(pubk[1:]) - h = sha.Sum(nil) - h = h[len(h)-20:] - - require.Equal(t, from, h) -} - -func TestDelegatedSigner(t *testing.T) { - rHex := "0xcf1fa52fae9154ba21d67aeca9b42adfe186eb9e426c441051a8473efd190848" - sHex := "0x0e6c8c79ffaf35fb8f136c8cf6c5656f1f3befad21f2644321aa6dba58d68737" - v := byte(0) - - msgHex := "0x02f08401df5e76038502540be400843b9aca008398968094ff000000000000000000000000000000000003f2832dc6c080c0" pubKeyHex := "0x04cfecc0520d906cbfea387759246e89d85e2998843e56ad1c41de247ce10b3e4c453aa73c8de13c178d94461b6fa3f8b6f74406ce43d2fbab6992d0b283394242" - - msg := mustDecodeHex(msgHex) pubk := mustDecodeHex(pubKeyHex) - r := mustDecodeHex(rHex) - s := mustDecodeHex(sHex) - addrHash, err := EthAddressFromPubKey(pubk) require.NoError(t, err) - from, err := address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, addrHash) require.NoError(t, err) - sig := append(r, s...) - sig = append(sig, v) - require.Equal(t, 65, len(sig)) - - signature := &crypto1.Signature{ - Type: crypto1.SigTypeDelegated, - Data: sig, - } - - err = sigs.Verify(signature, from, msg) + fromEth, err := EthAddressFromFilecoinAddress(from) require.NoError(t, err) -} -func prepareTxTestcases() ([]TxTestcase, error) { - tcstr := `[{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec8080c080a0f411a73e33523b40c1a916e79e67746bd01a4a4fb4ecfa87b441375a215ddfb4a0551692c1553574fab4c227ca70cb1c121dc3a2ef82179a9c984bd7acc0880a38","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec8080c001a0ed75a56e365c88479bf3f60251a2dd47ae181f1a3d95724581a3f648487b4396a046628bb9734edf4b4c455f2bbd351e43c466f315272cd1927f2c55d9b52e058b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec8080c080a0f411a73e33523b40c1a916e79e67746bd01a4a4fb4ecfa87b441375a215ddfb4a0551692c1553574fab4c227ca70cb1c121dc3a2ef82179a9c984bd7acc0880a38","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec8080c001a0ed75a56e365c88479bf3f60251a2dd47ae181f1a3d95724581a3f648487b4396a046628bb9734edf4b4c455f2bbd351e43c466f315272cd1927f2c55d9b52e058b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88682013a8080808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0706d871013403cf8b965dfa7f2be5a4d185d746da45b21d5a67c667c26d255d6a02e68a14f386aa325ce8e82d30405107d53103d038cf20e40af961ef3a3963608","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84382013a8080808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88782013a81c880808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0df137d0a6733354b2f2419a4ea5fe77d333deca28b2fe091d76190b51c2bae73a0232cbf9c29b8840cbf104ff77360fbf3ca4acda29b5e230636e19ac253ad92de","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84482013a81c880808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec8080c001a03a2880cc65e88d5320067f502a0ffda72111d01f0ebeeea9fbeb812e457aa0f9a020c08483b104dbfbbbffffedc3acdbe8245ca6daf97c0dbab843d747e587d625","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec8080c001a03427daf1639de6bf1b948abeab765b0a6a9170cc6a16d263c71c859f78916b03a01bbbb824b9953b5eb9f3098b4358a7ebb78f3358866eed997de66350ae4c9475","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec8080c001a03a2880cc65e88d5320067f502a0ffda72111d01f0ebeeea9fbeb812e457aa0f9a020c08483b104dbfbbbffffedc3acdbe8245ca6daf97c0dbab843d747e587d625","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec8080c001a03427daf1639de6bf1b948abeab765b0a6a9170cc6a16d263c71c859f78916b03a01bbbb824b9953b5eb9f3098b4358a7ebb78f3358866eed997de66350ae4c9475","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88882013a808082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0b9ebc36653a4800816f71ceacf93a1ee601a136916a3476ea9073a9a55ff026aa0647665249b12e8d1d1773b91844588ed70f65c91bc088ccb259ec0f0a24330d5","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a808082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c88082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0122dd8468dbd34e111e1a5ea1997199be633aa3bc9c1a7ee27dc3a8eda39c29da07cb99cd28ac67f55e507a8b8ef5b931c56cacf79273a4a2969a004a4b4a2864a","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c88082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec8080c080a0c1d020df63cb6db76e3a27a60ba0500a3cdd30f9f47b08733009dc8d610ea29ba05cbafb4c223417526ded0b02b8eb66a73535386d0e62da0e20f3641b532aa406","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec8080c080a090e30d32c6cd3f1ba2109b6a9f1c9fffc50b96a934192edf98adc086299e410ba057db0c136436de2e907942bdaad8e0113cf576f250b336ab652ef094c260dae6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec8080c080a0c1d020df63cb6db76e3a27a60ba0500a3cdd30f9f47b08733009dc8d610ea29ba05cbafb4c223417526ded0b02b8eb66a73535386d0e62da0e20f3641b532aa406","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec8080c080a090e30d32c6cd3f1ba2109b6a9f1c9fffc50b96a934192edf98adc086299e410ba057db0c136436de2e907942bdaad8e0113cf576f250b336ab652ef094c260dae6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88882013a8082ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a016e3f30a612fc802bb64b765325ecf78f2769b879a9acf62f07669f9723335d6a0781bb3444a73819f28233f1eebf8c3a4de288842fd73c2e05a7a7b0c288d5b25","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a8082ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c882ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0b652a447bdcdd1906ed86406ee543ee06023e4f762784c1d3aaf4c3bd85c6a17a0368ae9995e15258f14b74f937e97140a659d052d341674be0c24452257b56b30","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c882ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c001a0b1411f337b69609a256c0e76c57ccf4af87e977c98fd2a889f29281bf623cab4a049bec0fb4773aed870bae9c1cdf1ee398c498f0b436dcd19cae588b4ecd8bdf2","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c080a00b845fec9c96bf593c3501753764e14867d3f5d4bd02051e49329b6810d6513ea070d046e5b38c18c542594b328f02345a8f34ab05fd00db33974f914f7ae31c63","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c001a0b1411f337b69609a256c0e76c57ccf4af87e977c98fd2a889f29281bf623cab4a049bec0fb4773aed870bae9c1cdf1ee398c498f0b436dcd19cae588b4ecd8bdf2","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c080a00b845fec9c96bf593c3501753764e14867d3f5d4bd02051e49329b6810d6513ea070d046e5b38c18c542594b328f02345a8f34ab05fd00db33974f914f7ae31c63","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88a82013a8082ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a02d8215d8408d2f4b83a2e68f4aad6fe5dee97d7ef6a43b02ec413ead2215ac80a0641a43cebd6905e3e324c0dd06585d5ffc9b971b519045999c48e31db7aa7f9d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88a82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0da68784e191ce0806527d389f84b5d15bed3908e1c2cc0d8f0cea7a29eb0dba39f231a0b438b7d0f0f57292c68dc174d4ee6df7add933ab4e0b3789f597a7d3b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec8080c080a04c97162e2d2ab508116a23c522fd816ecd9cb091d4c288afe45c37ee3a8dde34a06ebf67ff15b74d65c276340aaebde8e6ebb8da0d3bbab43deffac8eb1e6a0630","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c080a0d503d409e667c2876ab9e420854cecce4c0092985855234be07f270bfcf3ed4aa07a40deecc8a4448d4dc0e2014b4b23ac5721409c62bffa05aee6938d8447f72d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec8080c080a04c97162e2d2ab508116a23c522fd816ecd9cb091d4c288afe45c37ee3a8dde34a06ebf67ff15b74d65c276340aaebde8e6ebb8da0d3bbab43deffac8eb1e6a0630","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c080a0d503d409e667c2876ab9e420854cecce4c0092985855234be07f270bfcf3ed4aa07a40deecc8a4448d4dc0e2014b4b23ac5721409c62bffa05aee6938d8447f72d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88882013a80808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a059aecc1d365ee0dc56a577d162f04c0912a5c5b62f889cff1acc706ac17a4489a017209b3ec43a10a40c5863a2b7a1ee823380ad42697a5f7d5f537c230583a4c7","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a80808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c8808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0dc1eb40f93e311f3f9a94d8a695db2bbb38973ce097121875885e4bc54f18152a0075da0bd405bb4f5c69034daaf8f40052b941fae5b9f3b8df218d80fb4d7ea99","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c8808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a03d392fd5e83c64554907a55204572aaeec6ffab25f2c73655c6a22344fa02a14a03b9ae94b7dc21108db6dda65125ecaff844f8f43f483bed35f32f6d5d530fe9f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c001a0405e8a430ef6ad4c3403150776af08c255b6f6fbe278d194f88517733c816caca0364203b5bca7953dd863d4cf90c0a77b499ef4a3d5831c4fdf33926c31709c4f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a03d392fd5e83c64554907a55204572aaeec6ffab25f2c73655c6a22344fa02a14a03b9ae94b7dc21108db6dda65125ecaff844f8f43f483bed35f32f6d5d530fe9f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c001a0405e8a430ef6ad4c3403150776af08c255b6f6fbe278d194f88517733c816caca0364203b5bca7953dd863d4cf90c0a77b499ef4a3d5831c4fdf33926c31709c4f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88a82013a808082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a083cf6701aee00872946b6550c059f028f72e3052acb8cc9c25b830ace860e046a03fd969d73e995d43896659f94d3956a17da18451050349e7db6f7881f8c057d3","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a808082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c5a545f2d94e719068d9a43b01879bcb46b56e236dd378dd26ef3b8e4ec8314aa04024b9936960b9b156405e4f3e0b6562518df8778324a927381e380b23f47fb8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c080a0aa406ec7f4901a1777e44b975ff41603b9d46257efdc1ca904a3e7890f2b020ea03bda5c785182cfa2d9f9b7a54f194cd08b9d0f913069a4514ff21e8fa0ef3850","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c080a089fc465c24b4bad898cf900f585eddab6d40189e8d19746da76597f86fbadf51a005732ffa2ebac36646afab9105540b543f74a5c91b441834a2b1930815c2ccc8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c080a0aa406ec7f4901a1777e44b975ff41603b9d46257efdc1ca904a3e7890f2b020ea03bda5c785182cfa2d9f9b7a54f194cd08b9d0f913069a4514ff21e8fa0ef3850","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c080a089fc465c24b4bad898cf900f585eddab6d40189e8d19746da76597f86fbadf51a005732ffa2ebac36646afab9105540b543f74a5c91b441834a2b1930815c2ccc8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88a82013a8082ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a09d9a8ee802486b826348a76346987b3e7331d70ef0c0257ff976ceebef1141a2a07d97d14ed877c16bd932f08a67c374e773ee3337d512ff8241c8d78566a04d46","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a024ad1ec1578f51beb2b574507bda7691a486cdbc9c22add01ad4c1f686beb567a048445e0fe8945b8052e5e87139690c0615a11c52503b226cf23610c999eada40","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a06b382fcbe48de85615ff6e2dcc0c84021beb4abc527878accd36c9c77af84ba8a06a07d34a6896b270538525cb14b0856ceb442714fa85e4c9ee36dedf638935f9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a0ba2586cfb3323fd0f9d7bb38bf9948758a52f156bda66f7100b789760894ad89a01e4bd2ff4eff2c391915141250313ab845401d5e2f71c23691d20a0b3c68cbd9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a06b382fcbe48de85615ff6e2dcc0c84021beb4abc527878accd36c9c77af84ba8a06a07d34a6896b270538525cb14b0856ceb442714fa85e4c9ee36dedf638935f9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a0ba2586cfb3323fd0f9d7bb38bf9948758a52f156bda66f7100b789760894ad89a01e4bd2ff4eff2c391915141250313ab845401d5e2f71c23691d20a0b3c68cbd9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88c82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0f36ff02ab3e90d2de77cdb24423dc39ca5c959429db62cb5c9ed4f0c9e04703aa0476bf841b0602af44039801d4e68648971f63fc2152002b127be6d914d4fc5ca","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84982013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88d82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a08267ae8838a8a5d9c2a761c182b5759184b7672b761278d499c1514fb6e8a495a023aa268f67da7728767e114fdec4d141bf649e0ad931117b5b325834dbf72803","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84a82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec6480c080a011ec4af7fc663080460b70ae8829f47e9cfa1814c616750d359459cbbba55563a0446e4ec9ea504d13dcbef44238e442caad366dbae1ae9408d39c6d902a5577b0","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec6480c001a0b80bc30bef46b3f824d1460685db875ff070f7798c3148c1fc49c01d6acc550ca0437efe7721563800e6a56ac54877a72c7860cd5e17ef4675afe989822ae87759","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec6480c080a011ec4af7fc663080460b70ae8829f47e9cfa1814c616750d359459cbbba55563a0446e4ec9ea504d13dcbef44238e442caad366dbae1ae9408d39c6d902a5577b0","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec6480c001a0b80bc30bef46b3f824d1460685db875ff070f7798c3148c1fc49c01d6acc550ca0437efe7721563800e6a56ac54877a72c7860cd5e17ef4675afe989822ae87759","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88682013a8080808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a06ab9d5988105d28dd090e509c8caabaa7773fc08ec5ef3dfeae532e01938ff69a078bca296df26dd2497a49110e138a49a67a6e232a35524b041d04a10fc583651","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84382013a8080808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88782013a81c880808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a031d51b866a02a9966250d312ed6cb4e083f9131ad8f6bb5814074375093d7536a03f8f819c4011dd54348930b6f98f365de8060b487ada38a62a5617aab6cc6e09","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84482013a81c880808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec6480c001a05bda5ad44c8f9a7516226488cf2d4f53188b40352f35ea7cece8076acda26dbba015373b3b78c88b74c7cca32fd02696a248bb9bea22a09c7a4a17b9e3b629b896","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec6480c080a00d92624cc3335c903077e318204929b4a8c9cd96d94690b0191f8a3bb24e937aa02f1d0315ececf46900154791a732eb8fee9efd0dc998a4e6b892d07ad657a815","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec6480c001a05bda5ad44c8f9a7516226488cf2d4f53188b40352f35ea7cece8076acda26dbba015373b3b78c88b74c7cca32fd02696a248bb9bea22a09c7a4a17b9e3b629b896","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec6480c080a00d92624cc3335c903077e318204929b4a8c9cd96d94690b0191f8a3bb24e937aa02f1d0315ececf46900154791a732eb8fee9efd0dc998a4e6b892d07ad657a815","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88882013a808082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0def168136c0532ec148a9e200e3cc1b22f90c7bbc5d9ef25ac0c5d342e8f3784a022f94642dfc81ba321b3e09879888332fa7c25b623bead7686e3e493c0911b55","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a808082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c88082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0626f43b80260f84cde2c67538c5cfbd328ce85b0f934e8568769e51709b100a7a0283fff5dbfde72b72e2b74c464b1add985d72750be3f4e16ae8ffb4747a40ff2","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c88082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec6480c080a051b109080002dab4aae47139eb92ddea8951ef5ac6dfc3d7fa07621047dbc680a0334aa47a2888a6cc52b8cf3c3635192b66c692416e954822c1c93c3896ff1ead","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec6480c080a009e179e3bad2da6fb5e205e52fd8d1c462007162aabde5a4d6b052dd4fc4f23ca063922c31438835adf2e4424e2e7d5d2702ec65de2e24a72b491ff0004a53865d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec6480c080a051b109080002dab4aae47139eb92ddea8951ef5ac6dfc3d7fa07621047dbc680a0334aa47a2888a6cc52b8cf3c3635192b66c692416e954822c1c93c3896ff1ead","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec6480c080a009e179e3bad2da6fb5e205e52fd8d1c462007162aabde5a4d6b052dd4fc4f23ca063922c31438835adf2e4424e2e7d5d2702ec65de2e24a72b491ff0004a53865d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88882013a8082ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0d3bfebc6597304c6a06491f68d2ac149fc233d28e81af48dd5b1f83e6ff951d2a06668da06d86aba341971dabb58016ca7764cd4b4c1634e3f829dcc8ef8bca4f6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a8082ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c882ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0d45b9fd9a2a3fdf79805cf73b70348037cc69927209a5e3728fe62cbe9543f03a02f5f8477666487ee5148a65ce59f400beac7c208369162b2d555411314d358fb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c882ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c001a02a6a910f7b5f83fda937006021b9c074f4544d5bb37b9b5a1b7045095f461836a038572b25418528bce7e6a3a480cf9fc90a33d9c63b392c2dbc8faf72a1e4ab8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c080a07a6dd661b5da27c809cce22aa186c158fe3b07a484a9395fd9a7a31a2b90636fa02b86f82b661264e27c3fda085b59740d3059335bff91693291afcf93c7ca627c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c001a02a6a910f7b5f83fda937006021b9c074f4544d5bb37b9b5a1b7045095f461836a038572b25418528bce7e6a3a480cf9fc90a33d9c63b392c2dbc8faf72a1e4ab8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c080a07a6dd661b5da27c809cce22aa186c158fe3b07a484a9395fd9a7a31a2b90636fa02b86f82b661264e27c3fda085b59740d3059335bff91693291afcf93c7ca627c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88a82013a8082ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a08c13c10490bc20cb1e55dc54ececb37a6c9cc8d013dbe513feacbb0416f09feba045c4e038759a0901820091e043db326b1bf9a8a1cd046ac72629969497c6a86f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0b904edf8eb9b6beb9cde9e1fae538e12f8d40e9124ace0cba2eee8cbbe77aa10a0788a0bd9a6fb98e7230f5db89be2f5067d1a227ba277b9cb155fb5859c57aae6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec6480c080a08d10a7a81c561391fe88bcb2c1dfbf4f7140fb7884fec0558606e76ffc4eaa91a049fa2a95e0f07a4376df9c6f2e1563ad443ce8369d44c6e1ce8ee521805b3623","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c001a00de6dc2841a25e5ea2dc1e054d69638ec519a9953666930060797cd110cde122a07fd1dcb6319eca7c681cef006efb3f7dcd74ff98a79ce05917d5d1fa7a175b6f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec6480c080a08d10a7a81c561391fe88bcb2c1dfbf4f7140fb7884fec0558606e76ffc4eaa91a049fa2a95e0f07a4376df9c6f2e1563ad443ce8369d44c6e1ce8ee521805b3623","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c001a00de6dc2841a25e5ea2dc1e054d69638ec519a9953666930060797cd110cde122a07fd1dcb6319eca7c681cef006efb3f7dcd74ff98a79ce05917d5d1fa7a175b6f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88882013a80808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a04c43dab94dd746973a1f7f051cc520cc01e93e9c6c55147cef34e5fdc0b182a2a06d148cc6ec017f9aeb6442a17d72e388ffc835950e19abd0c06057520f893542","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a80808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c8808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a025b50c1db31c0ae7aaa73374659201b54b71488efecbb6985dc50015abde7e36a04dd8cf68920de7232ab8d1fb28ab94ac05466c1f9d9a3a658f2054fce7868e2c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c8808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c080a0415ad0a93225eaec617206ec835e362d5e75fd0e1903747c1806270ec2684c7da0487ec1479cdb2affa891ff56413818ec169651c906ab932594b6e5bbb79d4998","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0a46ac278c400ef099ad23ac4ccb066a37db8bb5c4d65e0a347152a499ae9eb92a07505f9c67f0897cbe6f848c9a2164c3c234dab2fea7a4dd6f4436be34080e2ff","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c080a0415ad0a93225eaec617206ec835e362d5e75fd0e1903747c1806270ec2684c7da0487ec1479cdb2affa891ff56413818ec169651c906ab932594b6e5bbb79d4998","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0a46ac278c400ef099ad23ac4ccb066a37db8bb5c4d65e0a347152a499ae9eb92a07505f9c67f0897cbe6f848c9a2164c3c234dab2fea7a4dd6f4436be34080e2ff","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88a82013a808082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0a43aba5078d2da3ecc1ec0c67191f8cf58f29f5b4db7f8d4765ea691ddbd4195a0110e568c803db5ea587b406f452cf49ddf6b6f24d41207973d6c785ffaed1454","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a808082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a00caeadf2fcba95f0deab5ee4899348ecac4a18eeb09317d6f8156b891626d219a0549c5376aba320889c2f7b61fd4a51aec5f9a1d9ed9b26cef0a3bee52fac4989","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c001a07b5568d8a3ec3c7e126f570955db304e31d3f3d7b0c4fd103b6d064a2f6f5e23a030a1b17f299352ae193b8dbce2adda473ccb04e00670f416877762971697606f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c080a07bb69d01062f9d6ecb011ad344bbe08d4eca2f6b192dde45015def4c2e6096e0a03a3df52d753e3293d2fd544f72e62ceae00ea6dcab7229685d7b1873d873d203","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c001a07b5568d8a3ec3c7e126f570955db304e31d3f3d7b0c4fd103b6d064a2f6f5e23a030a1b17f299352ae193b8dbce2adda473ccb04e00670f416877762971697606f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c080a07bb69d01062f9d6ecb011ad344bbe08d4eca2f6b192dde45015def4c2e6096e0a03a3df52d753e3293d2fd544f72e62ceae00ea6dcab7229685d7b1873d873d203","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88a82013a8082ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0621255015626b35acf19629ce318999336441537920f9f3ff1bfd44e54d8abd3a03b3426f8fa963debdfa6b44561772bdebc9524c7f63abd0d947b678f5e966502","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0b73c3ba53fc5a0f7fab636cc2b826c3873cda5d0be9dd2100fdceae7899f3310a0491905f676063924cf847fdf2e488be4606ce351748e5c88d49ed50c8d595c94","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0e60702e3f5c5f56e3d1bc2907015ec889d0557ea14e81f137056471fef0fdb9da066e601e6e55c2e37e2042401b352e81841d492d0fe4f05bfe81bba29c9e6ce1f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a085a947fb201d0b50272e7bb7a056adc9ee6f5904634ed91dbde0d650641b7de3a03635c731769302e955d41f794a63262d5d4d37d117c9db89a6b6bce927b71f42","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0e60702e3f5c5f56e3d1bc2907015ec889d0557ea14e81f137056471fef0fdb9da066e601e6e55c2e37e2042401b352e81841d492d0fe4f05bfe81bba29c9e6ce1f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a085a947fb201d0b50272e7bb7a056adc9ee6f5904634ed91dbde0d650641b7de3a03635c731769302e955d41f794a63262d5d4d37d117c9db89a6b6bce927b71f42","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88c82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0d67e28d31489af5129c4832af814a01e0baa5e5ba6245fe2d3304693ceea48e0a03bc06f1c6dd01a14826c67aa35258c0bbf7c516a9bb21e9190eaa8d3768f49bb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84982013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88d82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0a5368984aca4bc1e3d7ebc7ae4ead5e09ffd3b4b4712d039c19fdac948e5952ea065953ace0a29210440d6a0f05d6b43f482950b463b3be6b23fc63452c94b9446","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84a82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86a82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a086da25ab078729b08cf48da02eb1c1e05fe0f4e5d7b332262b68f4db3dc9b72fa04102c03c7d9f11a6fdb77d6a36d3f07e09b1ceaab0bf4ef1fdc604bcd726f83b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e782013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86b82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0cde92f395919b3205b4260867b11597f9ecf363bc1be9bbd8b5400d3381d64b3a01b9555cfa22ee8615c3033235ebad605d0bef616d08876de26719866fcc4d41e","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e882013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86a82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a086da25ab078729b08cf48da02eb1c1e05fe0f4e5d7b332262b68f4db3dc9b72fa04102c03c7d9f11a6fdb77d6a36d3f07e09b1ceaab0bf4ef1fdc604bcd726f83b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e782013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86b82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0cde92f395919b3205b4260867b11597f9ecf363bc1be9bbd8b5400d3381d64b3a01b9555cfa22ee8615c3033235ebad605d0bef616d08876de26719866fcc4d41e","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e882013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f88e82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a03dd64e48a1ae228665b3f180367997ee96bc60ee226615c900e3d86634044328a00f6cdb24633e75fa65f6b93fce9b084c1f30dd03dde97d01f25c6f10f34d5d9d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84b82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88f82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a07475efeb8dd5bf4ba7efb31ab67a9077401ed71f4e8dd13e7058ce5cfeb5a0f2a01046e93a5258bf320bc392173a49b6fef15976be4c1210f2e367af223ad8c026","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84c82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86c82013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0ca84441c7ba097a7afa5ef9ad7ef70ba58ddfffc06c5d015b5c8553f1632d103a057fee6d92055c9c031a1efa667f3ee554804c4f34a195b6dfc781e1592c20444","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a04055dfcd6e0b7264d3474ba13f76659384e5f365ebc6ba271641481b12bf410ca01ef7d04dc33fdf0c3137e31d8c822ad68bbd4f89ada52db9705bb66813d11583","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86c82013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0ca84441c7ba097a7afa5ef9ad7ef70ba58ddfffc06c5d015b5c8553f1632d103a057fee6d92055c9c031a1efa667f3ee554804c4f34a195b6dfc781e1592c20444","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a04055dfcd6e0b7264d3474ba13f76659384e5f365ebc6ba271641481b12bf410ca01ef7d04dc33fdf0c3137e31d8c822ad68bbd4f89ada52db9705bb66813d11583","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89082013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a02080212bb64a798e1e138e4991ab830cf04d37ffeedf6fde7eba0eb7d972b350a02aff43f9e5ca8d6cea6e918391188fa37bdb91b864eadec705f7c69c4a61bc5a","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84d82013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89182013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0e41c052d72950a563b8ed7fb15855beabea43ff5b038bd6a3ccc6416e3498619a0568bbd7cbff31a47e1d0b9712f382c52e74b7b28cbcb8458974d82a8d54ddc57","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84e82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86c82013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a057c342304f133ff8832d3d16a43571afe905dc9b10afc24c6e99225cca6d8817a00e2155d1904751ce0d2ba01e6475aeae254c02966773f5bc7650e37252a01a92","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0fc2a550a7798085cae28028abbe4829be29e5f3a40af221086831d0e17ca3c83a01ce21f5934b9ca566958e09e89c99fd9ed2dc4acae209a6fb81fd3a6c9879a99","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86c82013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a057c342304f133ff8832d3d16a43571afe905dc9b10afc24c6e99225cca6d8817a00e2155d1904751ce0d2ba01e6475aeae254c02966773f5bc7650e37252a01a92","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0fc2a550a7798085cae28028abbe4829be29e5f3a40af221086831d0e17ca3c83a01ce21f5934b9ca566958e09e89c99fd9ed2dc4acae209a6fb81fd3a6c9879a99","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89082013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0fa33b63666310ca1c72fc5d82639c5b8e2a7638910be7bee23ada9f139c6b891a02012cad8e991beea7dcf0b6e9346b0228699698e183e2fadfc5b9b880601af9b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84d82013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89182013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0bc6ae4e92e7a20d5ff61258653dffda636cee0fd97dd156eac7a1f231f1f2785a0323055e0e0bed496b3fec30be292338d0956ecf8baeeb34458230821589aa7fb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84e82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86e82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0bd2889395392859a83a33bfe549c09d172e1f289de29d4bc9d0a3d25ea8aa71ba075fe92140a08d8e680061852438623c9cd10e211955577d1a3b56e49e960e4e7","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a05553c929ae32692a9f742371ffcfc8c8d2b77f31a7795460297cb78c29e357e8a043e42ca4ed7eb1b8e3546de2364522735d79a2e2ff5d16f7f96d165c5815c80c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86e82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0bd2889395392859a83a33bfe549c09d172e1f289de29d4bc9d0a3d25ea8aa71ba075fe92140a08d8e680061852438623c9cd10e211955577d1a3b56e49e960e4e7","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a05553c929ae32692a9f742371ffcfc8c8d2b77f31a7795460297cb78c29e357e8a043e42ca4ed7eb1b8e3546de2364522735d79a2e2ff5d16f7f96d165c5815c80c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89282013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a055f63a6bef8e23dc437ff4ac9349a59fcde2f72d1879de50b0d3686ff648749da04cf8034df06cf6f15f31bb55979b40eeacbd28fb1d745e608acdc088e22beb66","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84f82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89382013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c4a0253448dad999692c1bf3cfb5de9e95a2e96da4e1f64133ada452a825fe9aa0757b576ceb7a2c494819960ac59e9d3a4e3da384f23c0e88ada758dc265eae94","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85082013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86c82013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a02632c4d8a443afb8d39f91d036fd4915ca3ad2f253b8f93211b4b3ee15566519a009bdc00c8eaaf22f3d7d04b53dbc777fd027a780fb4ddaf01002724ddf2879dd","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a08bda02c15ca37d35d9ad2e2f7731d24dd039f5c6c6f7eaad739daadac6db33e5a044c01e493e10929e4021c69d9df886b211eb349a865df9f0796846ad1cdf23e8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86c82013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a02632c4d8a443afb8d39f91d036fd4915ca3ad2f253b8f93211b4b3ee15566519a009bdc00c8eaaf22f3d7d04b53dbc777fd027a780fb4ddaf01002724ddf2879dd","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a08bda02c15ca37d35d9ad2e2f7731d24dd039f5c6c6f7eaad739daadac6db33e5a044c01e493e10929e4021c69d9df886b211eb349a865df9f0796846ad1cdf23e8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89082013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0ed0db75f41b2b8b89768ce5ad08716aff149dc1d5a2e593140d8964eb2da3229a02e5248cca9b5af340d73271cad4d690f7efa11c9278824aca528eb15d28aec4d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84d82013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89182013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a07108fbbabc45826dbdc8e4cf831240fb39ead7bd4b8ec5d8de64d04e2885e554a04dae4fb4bdbabb9d8f923d579e75ee980da1b4fac5773ec68f395af240f037f0","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84e82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86e82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0130b6723050095faa2e7abc69c2f785e73d333c65fae6cf2835518f970c627d5a00b90bd4f2ded1da0163ab5e81ad76d51aef005d663137347fc550313e1c8b6fc","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0993a50431e82d10d632466d45f8aaffea9a56efa59d529dfd497d3c2a06aabeba0070d3132c6ce1e4ff70b0721d1f4c03ab566b8e2af29d33148033fb3009dc29d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86e82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0130b6723050095faa2e7abc69c2f785e73d333c65fae6cf2835518f970c627d5a00b90bd4f2ded1da0163ab5e81ad76d51aef005d663137347fc550313e1c8b6fc","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0993a50431e82d10d632466d45f8aaffea9a56efa59d529dfd497d3c2a06aabeba0070d3132c6ce1e4ff70b0721d1f4c03ab566b8e2af29d33148033fb3009dc29d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89282013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a09c9d3b0d7b58bfe81a6881b9db184e0ade03c1ad11aa8f1566e2f24f50f85525a06c10cf91f4dbc24d0f78ef09a8e2310d349a034cec7e86e807d7a48ea26161e1","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84f82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89382013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0f8423b51e513618c6a4bdd2696479d91c760e11ea24657dd27fa6eb9b7da8c0ea07e9456113fb034718d1b4f4e09ade1ce78251a8c86f298b152850bc5925156cb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85082013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86e82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0d09b373d45c1bfc1c5d9b5198e69f974d4df456245e2f7a5edd486f3dd2795a9a011396197a670e7b0c4613b7ebf8aee53382930c7bd25c35dda15acae78ec0e2c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0131f5af3ece9a0b723d0c812dbcfc6cb458acf5e0846cc506215fc04d6af66d5a078d0bf7a40cc1ddcebbc4e86fb9a04bfc94f3da94b4a74476883b7b1729f8a44","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86e82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0d09b373d45c1bfc1c5d9b5198e69f974d4df456245e2f7a5edd486f3dd2795a9a011396197a670e7b0c4613b7ebf8aee53382930c7bd25c35dda15acae78ec0e2c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0131f5af3ece9a0b723d0c812dbcfc6cb458acf5e0846cc506215fc04d6af66d5a078d0bf7a40cc1ddcebbc4e86fb9a04bfc94f3da94b4a74476883b7b1729f8a44","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89282013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c286f4ee350eab70273cf9a952537534446a0f39e9bfea7340eabc04396a0e3da01e1302ae987a69836ec2c9266e6fe623db5fcdc566e37084c0c57630c4de8ee6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84f82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89382013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a09dee3fa88e365133a18035618af718a045e1a957f10f50c632f23923fd337b9ba06bbbd59489849803f8c61138932ac1a8361edb4c80789d030542829c0a2b5b7f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85082013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87082013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0c1cb1e2b41e48fecd59d72039147c76993653f061f9ea156b53c377673eef7f1a01822506f755206b60209a12ed3c84446f4fcb4ad602fa7ab7ee4ff2acde19ed6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02ed82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f87182013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a09817043ad22797d2f26ca46697db5f586c38336a171dce2d22d659889e9e9eb5a0369a5d6169586d9c831b6e017aa29fd49eac0636a136bfa5bafb95390fa95b8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ee82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f87082013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0c1cb1e2b41e48fecd59d72039147c76993653f061f9ea156b53c377673eef7f1a01822506f755206b60209a12ed3c84446f4fcb4ad602fa7ab7ee4ff2acde19ed6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02ed82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f87182013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a09817043ad22797d2f26ca46697db5f586c38336a171dce2d22d659889e9e9eb5a0369a5d6169586d9c831b6e017aa29fd49eac0636a136bfa5bafb95390fa95b8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ee82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89482013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a039357ad40087d17551ca2b94723f0394185a993671db02172a7de70c24054852a046c84070dfadd244b358690e5b89c75f3988b21b6614e6e3af2f8ca302d6c42a","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85182013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89582013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c991c81705a4c53a9255e72beb8243638c68f10c63b082755972bbbe15245d12a014f6852ae34c92882559e6810d4372109930a23b522368fdef2c85ce04e27839","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85282013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"}]` + to, err := address.NewIDAddress(1) + require.NoError(t, err) - testcases := []struct { - Input EthBytes `json:"input"` - Output string `json:"output"` - NosigTx string `json:"nosigTx"` - }{} + toEth, err := EthAddressFromFilecoinAddress(to) + require.NoError(t, err) - err := json.Unmarshal([]byte(tcstr), &testcases) - if err != nil { - return nil, err + tcs := map[string]struct { + msg *types.SignedMessage + expectedErr string + validateFunc func(t *testing.T, smsg *types.SignedMessage, tx EthTransaction) + }{ + "empty": { + expectedErr: "signed message is nil", + }, + "invalid-signature": { + msg: &types.SignedMessage{ + Message: types.Message{ + To: builtintypes.EthereumAddressManagerActorAddr, + From: from, + Method: builtintypes.MethodsEAM.CreateExternal, + }, + Signature: crypto.Signature{ + Type: crypto.SigTypeDelegated, + Data: []byte{1}, + }, + }, + expectedErr: "unsupported signature length", + }, + "valid-eip1559": { + msg: &types.SignedMessage{ + Message: types.Message{ + From: from, + To: to, + Value: big.NewInt(10), + GasFeeCap: big.NewInt(11), + GasPremium: big.NewInt(12), + GasLimit: 13, + Nonce: 14, + Method: builtintypes.MethodsEVM.InvokeContract, + }, + Signature: crypto.Signature{ + Type: crypto.SigTypeDelegated, + Data: eip1559sig, + }, + }, + validateFunc: func(t *testing.T, smsg *types.SignedMessage, tx EthTransaction) { + eip1559tx := tx.(*Eth1559TxArgs) + require.Equal(t, big.NewInt(10), eip1559tx.Value) + require.Equal(t, big.NewInt(11), eip1559tx.MaxFeePerGas) + require.Equal(t, big.NewInt(12), eip1559tx.MaxPriorityFeePerGas) + require.EqualValues(t, uint64(13), eip1559tx.GasLimit) + require.EqualValues(t, uint64(14), eip1559tx.Nonce) + require.EqualValues(t, toEth, *eip1559tx.To) + require.EqualValues(t, 314, eip1559tx.ChainID) + require.Empty(t, eip1559tx.Input) + + ethTx, err := tx.ToEthTx(smsg) + require.NoError(t, err) + require.EqualValues(t, 314, ethTx.ChainID) + require.EqualValues(t, 14, ethTx.Nonce) + hash, err := eip1559tx.TxHash() + require.NoError(t, err) + require.EqualValues(t, hash, ethTx.Hash) + require.EqualValues(t, fromEth, ethTx.From) + require.EqualValues(t, toEth, *ethTx.To) + require.EqualValues(t, big.NewInt(10), ethTx.Value) + require.EqualValues(t, 13, ethTx.Gas) + require.EqualValues(t, big.NewInt(11), *ethTx.MaxFeePerGas) + require.EqualValues(t, big.NewInt(12), *ethTx.MaxPriorityFeePerGas) + require.Nil(t, ethTx.GasPrice) + require.Empty(t, ethTx.AccessList) + }, + }, + "valid-legacy": { + msg: &types.SignedMessage{ + Message: types.Message{ + From: from, + To: to, + Value: big.NewInt(10), + GasFeeCap: big.NewInt(11), + GasPremium: big.NewInt(12), + GasLimit: 13, + Nonce: 14, + Method: builtintypes.MethodsEVM.InvokeContract, + }, + Signature: crypto.Signature{ + Type: crypto.SigTypeDelegated, + Data: legacySig, + }, + }, + validateFunc: func(t *testing.T, smsg *types.SignedMessage, tx EthTransaction) { + legacyTx := tx.(*EthLegacyHomesteadTxArgs) + require.Equal(t, big.NewInt(10), legacyTx.Value) + require.EqualValues(t, uint64(13), legacyTx.GasLimit) + require.EqualValues(t, uint64(14), legacyTx.Nonce) + require.EqualValues(t, toEth, *legacyTx.To) + require.EqualValues(t, big.NewInt(11), legacyTx.GasPrice) + require.Empty(t, legacyTx.Input) + + ethTx, err := tx.ToEthTx(smsg) + require.NoError(t, err) + require.EqualValues(t, 0, ethTx.ChainID) + require.EqualValues(t, 14, ethTx.Nonce) + hash, err := legacyTx.TxHash() + require.NoError(t, err) + require.EqualValues(t, big.NewInt(11), *ethTx.GasPrice) + require.EqualValues(t, hash, ethTx.Hash) + require.EqualValues(t, fromEth, ethTx.From) + require.EqualValues(t, toEth, *ethTx.To) + require.EqualValues(t, big.NewInt(10), ethTx.Value) + require.EqualValues(t, 13, ethTx.Gas) + require.Nil(t, ethTx.MaxFeePerGas) + require.Nil(t, ethTx.MaxPriorityFeePerGas) + require.Empty(t, ethTx.AccessList) + require.EqualValues(t, big.NewInt(27), ethTx.V) + }, + }, } - res := []TxTestcase{} - for _, tc := range testcases { - tx := EthTxArgs{} - err := json.Unmarshal([]byte(tc.Output), &tx) - if err != nil { - return nil, err - } - res = append(res, TxTestcase{ - Input: tc.Input, - Output: tx, - TxJSON: tc.Output, - NosigTx: tc.NosigTx, + for name, tc := range tcs { + t.Run(name, func(t *testing.T) { + tx, err := EthTransactionFromSignedFilecoinMessage(tc.msg) + if tc.expectedErr != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.expectedErr) + } else { + require.NoError(t, err) + } + if tc.validateFunc != nil { + tc.validateFunc(t, tc.msg, tx) + } }) } - - return res, err } diff --git a/chain/types/ethtypes/eth_types.go b/chain/types/ethtypes/eth_types.go index 893c0721c..3c2b9bec0 100644 --- a/chain/types/ethtypes/eth_types.go +++ b/chain/types/ethtypes/eth_types.go @@ -1011,6 +1011,14 @@ type EthTraceReplayBlockTransaction struct { VmTrace *string `json:"vmTrace"` } +type EthTraceTransaction struct { + *EthTrace + BlockHash EthHash `json:"blockHash"` + BlockNumber int64 `json:"blockNumber"` + TransactionHash EthHash `json:"transactionHash"` + TransactionPosition int `json:"transactionPosition"` +} + type EthCallTraceAction struct { CallType string `json:"callType"` From EthAddress `json:"from"` diff --git a/chain/types/ethtypes/rlp_test.go b/chain/types/ethtypes/rlp_test.go index 0ce6e15d9..0c74cf58c 100644 --- a/chain/types/ethtypes/rlp_test.go +++ b/chain/types/ethtypes/rlp_test.go @@ -192,7 +192,7 @@ func TestDecodeError(t *testing.T) { func TestDecode1(t *testing.T) { b := mustDecodeHex("0x02f8758401df5e7680832c8411832c8411830767f89452963ef50e27e06d72d59fcb4f3c2a687be3cfef880de0b6b3a764000080c080a094b11866f453ad85a980e0e8a2fc98cbaeb4409618c7734a7e12ae2f66fd405da042dbfb1b37af102023830ceeee0e703ffba0b8b3afeb8fe59f405eca9ed61072") - decoded, err := ParseEthTxArgs(b) + decoded, err := parseEip1559Tx(b) require.NoError(t, err) sender, err := decoded.Sender() diff --git a/chain/types/fil.go b/chain/types/fil.go index 960a42f28..227e9442f 100644 --- a/chain/types/fil.go +++ b/chain/types/fil.go @@ -68,7 +68,7 @@ func (f FIL) Nano() string { func (f FIL) Format(s fmt.State, ch rune) { switch ch { case 's', 'v': - fmt.Fprint(s, f.String()) + _, _ = fmt.Fprint(s, f.String()) default: f.Int.Format(s, ch) } diff --git a/cli/clicommands/cmd.go b/cli/clicommands/cmd.go index a37ce329a..791a11927 100644 --- a/cli/clicommands/cmd.go +++ b/cli/clicommands/cmd.go @@ -10,7 +10,6 @@ var Commands = []*cli.Command{ lcli.WithCategory("basic", lcli.SendCmd), lcli.WithCategory("basic", lcli.WalletCmd), lcli.WithCategory("basic", lcli.InfoCmd), - lcli.WithCategory("basic", lcli.ClientCmd), lcli.WithCategory("basic", lcli.MultisigCmd), lcli.WithCategory("basic", lcli.FilplusCmd), lcli.WithCategory("basic", lcli.PaychCmd), diff --git a/cli/client.go b/cli/client.go deleted file mode 100644 index e40a66866..000000000 --- a/cli/client.go +++ /dev/null @@ -1,2468 +0,0 @@ -package cli - -import ( - "bufio" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "math" - "math/rand" - "os" - "path/filepath" - "sort" - "strconv" - "strings" - "sync" - "sync/atomic" - "text/tabwriter" - "time" - - tm "github.com/buger/goterm" - "github.com/chzyer/readline" - "github.com/docker/go-units" - "github.com/fatih/color" - "github.com/ipfs/go-cid" - "github.com/ipfs/go-cidutil/cidenc" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/multiformats/go-multibase" - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - "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/go-state-types/big" - - "github.com/filecoin-project/lotus/api" - lapi "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/v0api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors/builtin" - "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{ - Name: "cid-base", - Hidden: true, - Value: "base32", - Usage: "Multibase encoding used for version 1 CIDs in output.", - DefaultText: "base32", -} - -// GetCidEncoder returns an encoder using the `cid-base` flag if provided, or -// the default (Base32) encoder if not. -func GetCidEncoder(cctx *cli.Context) (cidenc.Encoder, error) { - val := cctx.String("cid-base") - - e := cidenc.Encoder{Base: multibase.MustNewEncoder(multibase.Base32)} - - if val != "" { - var err error - e.Base, err = multibase.EncoderByName(val) - if err != nil { - return e, err - } - } - - return e, nil -} - -var ClientCmd = &cli.Command{ - Name: "client", - Usage: "Make deals, store data, retrieve data", - Subcommands: []*cli.Command{ - WithCategory("storage", clientDealCmd), - WithCategory("storage", clientQueryAskCmd), - WithCategory("storage", clientListDeals), - WithCategory("storage", clientGetDealCmd), - WithCategory("storage", clientListAsksCmd), - WithCategory("storage", clientDealStatsCmd), - WithCategory("storage", clientInspectDealCmd), - WithCategory("data", clientImportCmd), - WithCategory("data", clientDropCmd), - WithCategory("data", clientLocalCmd), - WithCategory("data", clientStat), - WithCategory("retrieval", clientFindCmd), - WithCategory("retrieval", clientQueryRetrievalAskCmd), - WithCategory("retrieval", clientRetrieveCmd), - WithCategory("retrieval", clientRetrieveCatCmd), - WithCategory("retrieval", clientRetrieveLsCmd), - WithCategory("retrieval", clientCancelRetrievalDealCmd), - WithCategory("retrieval", clientListRetrievalsCmd), - WithCategory("util", clientCommPCmd), - WithCategory("util", clientCarGenCmd), - WithCategory("util", clientBalancesCmd), - WithCategory("util", clientListTransfers), - WithCategory("util", clientRestartTransfer), - WithCategory("util", clientCancelTransfer), - }, -} - -var clientImportCmd = &cli.Command{ - Name: "import", - Usage: "Import data", - ArgsUsage: "[inputPath]", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "car", - Usage: "import from a car file instead of a regular file", - }, - &cli.BoolFlag{ - Name: "quiet", - Aliases: []string{"q"}, - Usage: "Output root CID only", - }, - &CidBaseFlag, - }, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - absPath, err := filepath.Abs(cctx.Args().First()) - if err != nil { - return err - } - - ref := lapi.FileRef{ - Path: absPath, - IsCAR: cctx.Bool("car"), - } - c, err := api.ClientImport(ctx, ref) - if err != nil { - return err - } - - encoder, err := GetCidEncoder(cctx) - if err != nil { - return err - } - - if !cctx.Bool("quiet") { - fmt.Printf("Import %d, Root ", c.ImportID) - } - fmt.Println(encoder.Encode(c.Root)) - - return nil - }, -} - -var clientDropCmd = &cli.Command{ - Name: "drop", - Usage: "Remove import", - ArgsUsage: "[import ID...]", - Action: func(cctx *cli.Context) error { - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - var ids []uint64 - for i, s := range cctx.Args().Slice() { - id, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return xerrors.Errorf("parsing %d-th import ID: %w", i, err) - } - - ids = append(ids, id) - } - - for _, id := range ids { - if err := api.ClientRemoveImport(ctx, imports.ID(id)); err != nil { - return xerrors.Errorf("removing import %d: %w", id, err) - } - } - - return nil - }, -} - -var clientCommPCmd = &cli.Command{ - Name: "commP", - Usage: "Calculate the piece-cid (commP) of a CAR file", - ArgsUsage: "[inputFile]", - Flags: []cli.Flag{ - &CidBaseFlag, - }, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - ret, err := api.ClientCalcCommP(ctx, cctx.Args().Get(0)) - if err != nil { - return err - } - - encoder, err := GetCidEncoder(cctx) - if err != nil { - return err - } - - fmt.Println("CID: ", encoder.Encode(ret.Root)) - fmt.Println("Piece size: ", types.SizeStr(types.NewInt(uint64(ret.Size)))) - fmt.Println("Piece size in bytes: ", types.NewInt(uint64(ret.Size))) - return nil - }, -} - -var clientCarGenCmd = &cli.Command{ - Name: "generate-car", - Usage: "Generate a car file from input", - ArgsUsage: "[inputPath outputPath]", - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - if cctx.NArg() != 2 { - return IncorrectNumArgs(cctx) - } - - ref := lapi.FileRef{ - Path: cctx.Args().First(), - IsCAR: false, - } - - op := cctx.Args().Get(1) - - if err = api.ClientGenCar(ctx, ref, op); err != nil { - return err - } - return nil - }, -} - -var clientLocalCmd = &cli.Command{ - Name: "local", - Usage: "List locally imported data", - Flags: []cli.Flag{ - &CidBaseFlag, - }, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - list, err := api.ClientListImports(ctx) - if err != nil { - return err - } - - encoder, err := GetCidEncoder(cctx) - if err != nil { - return err - } - - sort.Slice(list, func(i, j int) bool { - return list[i].Key < list[j].Key - }) - - for _, v := range list { - cidStr := "" - if v.Root != nil { - cidStr = encoder.Encode(*v.Root) - } - - fmt.Printf("%d: %s @%s (%s)\n", v.Key, cidStr, v.FilePath, v.Source) - if v.Err != "" { - fmt.Printf("\terror: %s\n", v.Err) - } - } - return nil - }, -} - -var clientDealCmd = &cli.Command{ - Name: "deal", - Usage: "Initialize storage deal with a miner", - Description: `Make a deal with a miner. -dataCid comes from running 'lotus client import'. -miner is the address of the miner you wish to make a deal with. -price is measured in FIL/Epoch. Miners usually don't accept a bid -lower than their advertised ask (which is in FIL/GiB/Epoch). You can check a miners listed price -with 'lotus client query-ask '. -duration is how long the miner should store the data for, in blocks. -The minimum value is 518400 (6 months).`, - ArgsUsage: "[dataCid miner price duration]", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "manual-piece-cid", - Usage: "manually specify piece commitment for data (dataCid must be to a car file)", - }, - &cli.Int64Flag{ - Name: "manual-piece-size", - Usage: "if manually specifying piece cid, used to specify size (dataCid must be to a car file)", - }, - &cli.BoolFlag{ - Name: "manual-stateless-deal", - Usage: "instructs the node to send an offline deal without registering it with the deallist/fsm", - }, - &cli.StringFlag{ - Name: "from", - Usage: "specify address to fund the deal with", - }, - &cli.Int64Flag{ - Name: "start-epoch", - Usage: "specify the epoch that the deal should start at", - Value: -1, - }, - &cli.BoolFlag{ - Name: "fast-retrieval", - Usage: "indicates that data should be available for fast retrieval", - Value: true, - }, - &cli.BoolFlag{ - Name: "verified-deal", - Usage: "indicate that the deal counts towards verified client total", - DefaultText: "true if client is verified, false otherwise", - }, - &cli.StringFlag{ - Name: "provider-collateral", - Usage: "specify the requested provider collateral the miner should put up", - }, - &CidBaseFlag, - }, - Action: func(cctx *cli.Context) error { - - expectedArgsMsg := "expected 4 args: dataCid, miner, price, duration" - - if !cctx.Args().Present() { - if cctx.Bool("manual-stateless-deal") { - return xerrors.New("--manual-stateless-deal can not be combined with interactive deal mode: you must specify the " + expectedArgsMsg) - } - return interactiveDeal(cctx) - } - - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - afmt := NewAppFmt(cctx.App) - - if cctx.NArg() != 4 { - return IncorrectNumArgs(cctx) - } - - // [data, miner, price, dur] - - data, err := cid.Parse(cctx.Args().Get(0)) - if err != nil { - return err - } - - miner, err := address.NewFromString(cctx.Args().Get(1)) - if err != nil { - return err - } - - price, err := types.ParseFIL(cctx.Args().Get(2)) - if err != nil { - return err - } - - dur, err := strconv.ParseInt(cctx.Args().Get(3), 10, 32) - if err != nil { - return err - } - - var provCol big.Int - if pcs := cctx.String("provider-collateral"); pcs != "" { - pc, err := big.FromString(pcs) - if err != nil { - return fmt.Errorf("failed to parse provider-collateral: %w", err) - } - provCol = pc - } - - if abi.ChainEpoch(dur) < build.MinDealDuration { - return xerrors.Errorf("minimum deal duration is %d blocks", build.MinDealDuration) - } - if abi.ChainEpoch(dur) > build.MaxDealDuration { - return xerrors.Errorf("maximum deal duration is %d blocks", build.MaxDealDuration) - } - - var a address.Address - if from := cctx.String("from"); from != "" { - faddr, err := address.NewFromString(from) - if err != nil { - return xerrors.Errorf("failed to parse 'from' address: %w", err) - } - a = faddr - } else { - def, err := api.WalletDefaultAddress(ctx) - if err != nil { - return err - } - a = def - } - - ref := &storagemarket.DataRef{ - TransferType: storagemarket.TTGraphsync, - Root: data, - } - - if mpc := cctx.String("manual-piece-cid"); mpc != "" { - c, err := cid.Parse(mpc) - if err != nil { - return xerrors.Errorf("failed to parse provided manual piece cid: %w", err) - } - - ref.PieceCid = &c - - psize := cctx.Int64("manual-piece-size") - if psize == 0 { - return xerrors.Errorf("must specify piece size when manually setting cid") - } - - ref.PieceSize = abi.UnpaddedPieceSize(psize) - - ref.TransferType = storagemarket.TTManual - } - - // Check if the address is a verified client - dcap, err := api.StateVerifiedClientStatus(ctx, a, types.EmptyTSK) - if err != nil { - return err - } - - isVerified := dcap != nil - - // If the user has explicitly set the --verified-deal flag - if cctx.IsSet("verified-deal") { - // If --verified-deal is true, but the address is not a verified - // client, return an error - verifiedDealParam := cctx.Bool("verified-deal") - if verifiedDealParam && !isVerified { - return xerrors.Errorf("address %s does not have verified client status", a) - } - - // Override the default - isVerified = verifiedDealParam - } - - sdParams := &lapi.StartDealParams{ - Data: ref, - Wallet: a, - Miner: miner, - EpochPrice: types.BigInt(price), - MinBlocksDuration: uint64(dur), - DealStartEpoch: abi.ChainEpoch(cctx.Int64("start-epoch")), - FastRetrieval: cctx.Bool("fast-retrieval"), - VerifiedDeal: isVerified, - ProviderCollateral: provCol, - } - - var proposal *cid.Cid - if cctx.Bool("manual-stateless-deal") { - if ref.TransferType != storagemarket.TTManual || price.Int64() != 0 { - return xerrors.New("when manual-stateless-deal is enabled, you must also provide a 'price' of 0 and specify 'manual-piece-cid' and 'manual-piece-size'") - } - proposal, err = api.ClientStatelessDeal(ctx, sdParams) - } else { - proposal, err = api.ClientStartDeal(ctx, sdParams) - } - - if err != nil { - return err - } - - encoder, err := GetCidEncoder(cctx) - if err != nil { - return err - } - - afmt.Println(encoder.Encode(*proposal)) - - return nil - }, -} - -func interactiveDeal(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPIV1(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - afmt := NewAppFmt(cctx.App) - - state := "import" - gib := types.NewInt(1 << 30) - - var data cid.Cid - var days int - var maddrs []address.Address - var ask []storagemarket.StorageAsk - var epochPrices []big.Int - var dur time.Duration - var epochs abi.ChainEpoch - var verified bool - var ds lapi.DataCIDSize - - // find - var candidateAsks []QueriedAsk - var budget types.FIL - var dealCount int64 - var medianPing, maxAcceptablePing time.Duration - - var a address.Address - if from := cctx.String("from"); from != "" { - faddr, err := address.NewFromString(from) - if err != nil { - return xerrors.Errorf("failed to parse 'from' address: %w", err) - } - a = faddr - } else { - def, err := api.WalletDefaultAddress(ctx) - if err != nil { - return err - } - a = def - } - - if _, err := api.StateGetActor(ctx, a, types.EmptyTSK); err != nil { - return xerrors.Errorf("address not initialized on chain: %w", err) - } - - fromBal, err := api.WalletBalance(ctx, a) - if err != nil { - return xerrors.Errorf("checking from address balance: %w", err) - } - - printErr := func(err error) { - afmt.Printf("%s %s\n", color.RedString("Error:"), err.Error()) - } - - cs := readline.NewCancelableStdin(afmt.Stdin) - go func() { - <-ctx.Done() - _ = cs.Close() - }() - - rl := bufio.NewReader(cs) - -uiLoop: - for { - // TODO: better exit handling - if err := ctx.Err(); err != nil { - return err - } - - switch state { - case "import": - afmt.Print("Data CID (from " + color.YellowString("lotus client import") + "): ") - - _cidStr, _, err := rl.ReadLine() - cidStr := string(_cidStr) - if err != nil { - printErr(xerrors.Errorf("reading cid string: %w", err)) - continue - } - - data, err = cid.Parse(cidStr) - if err != nil { - printErr(xerrors.Errorf("parsing cid string: %w", err)) - continue - } - - color.Blue(".. calculating data size\n") - ds, err = api.ClientDealPieceCID(ctx, data) - if err != nil { - return err - } - - state = "duration" - case "duration": - afmt.Print("Deal duration (days): ") - - _daystr, _, err := rl.ReadLine() - daystr := string(_daystr) - if err != nil { - return err - } - - _, err = fmt.Sscan(daystr, &days) - if err != nil { - printErr(xerrors.Errorf("parsing duration: %w", err)) - continue - } - - minDealDurationDays := uint64(build.MinDealDuration) / (builtin.SecondsInDay / build.BlockDelaySecs) - if days < int(minDealDurationDays) { - printErr(xerrors.Errorf("minimum duration is %d days, got %d", minDealDurationDays, days)) - continue - } - - maxDealDurationDays := uint64(build.MaxDealDuration) / (builtin.SecondsInDay / build.BlockDelaySecs) - if days > int(maxDealDurationDays) { - printErr(xerrors.Errorf("maximum duration is %d days, got %d", maxDealDurationDays, days)) - continue - } - - dur = 24 * time.Hour * time.Duration(days) - epochs = abi.ChainEpoch(dur / (time.Duration(build.BlockDelaySecs) * time.Second)) - - state = "verified" - case "verified": - ts, err := api.ChainHead(ctx) - if err != nil { - return err - } - - dcap, err := api.StateVerifiedClientStatus(ctx, a, ts.Key()) - if err != nil { - return err - } - - if dcap == nil { - state = "miner" - continue - } - - if dcap.Uint64() < uint64(ds.PieceSize) { - color.Yellow(".. not enough DataCap available for a verified deal\n") - state = "miner" - continue - } - - afmt.Print("\nMake this a verified deal? (yes/no): ") - - _yn, _, err := rl.ReadLine() - yn := string(_yn) - if err != nil { - return err - } - - switch yn { - case "yes": - verified = true - case "no": - verified = false - default: - afmt.Println("Type in full 'yes' or 'no'") - continue - } - - state = "miner" - case "miner": - maddrs = maddrs[:0] - ask = ask[:0] - afmt.Print("Miner Addresses (f0.. f0..), none to find: ") - - _maddrsStr, _, err := rl.ReadLine() - maddrsStr := string(_maddrsStr) - if err != nil { - printErr(xerrors.Errorf("reading miner address: %w", err)) - continue - } - - for _, s := range strings.Fields(maddrsStr) { - maddr, err := address.NewFromString(strings.TrimSpace(s)) - if err != nil { - printErr(xerrors.Errorf("parsing miner address: %w", err)) - continue uiLoop - } - - maddrs = append(maddrs, maddr) - } - - state = "query" - if len(maddrs) == 0 { - state = "find" - } - case "find": - asks, err := GetAsks(ctx, api) - if err != nil { - return err - } - - if len(asks) == 0 { - printErr(xerrors.Errorf("no asks found")) - continue uiLoop - } - - medianPing = asks[len(asks)/2].Ping - var avgPing time.Duration - for _, ask := range asks { - avgPing += ask.Ping - } - avgPing /= time.Duration(len(asks)) - - for _, ask := range asks { - if ask.Ask.MinPieceSize > ds.PieceSize { - continue - } - if ask.Ask.MaxPieceSize < ds.PieceSize { - continue - } - candidateAsks = append(candidateAsks, ask) - } - - afmt.Printf("Found %d candidate asks\n", len(candidateAsks)) - afmt.Printf("Average network latency: %s; Median latency: %s\n", avgPing.Truncate(time.Millisecond), medianPing.Truncate(time.Millisecond)) - state = "max-ping" - case "max-ping": - maxAcceptablePing = medianPing - - afmt.Printf("Maximum network latency (default: %s) (ms): ", maxAcceptablePing.Truncate(time.Millisecond)) - _latStr, _, err := rl.ReadLine() - latStr := string(_latStr) - if err != nil { - printErr(xerrors.Errorf("reading maximum latency: %w", err)) - continue - } - - if latStr != "" { - maxMs, err := strconv.ParseInt(latStr, 10, 64) - if err != nil { - printErr(xerrors.Errorf("parsing FIL: %w", err)) - continue uiLoop - } - - maxAcceptablePing = time.Millisecond * time.Duration(maxMs) - } - - var goodAsks []QueriedAsk - for _, candidateAsk := range candidateAsks { - if candidateAsk.Ping < maxAcceptablePing { - goodAsks = append(goodAsks, candidateAsk) - } - } - - if len(goodAsks) == 0 { - afmt.Printf("no asks left after filtering for network latency\n") - continue uiLoop - } - - afmt.Printf("%d asks left after filtering for network latency\n", len(goodAsks)) - candidateAsks = goodAsks - - state = "find-budget" - case "find-budget": - afmt.Printf("Proposing from %s, Current Balance: %s\n", a, types.FIL(fromBal)) - afmt.Print("Maximum budget (FIL): ") // TODO: Propose some default somehow? - - _budgetStr, _, err := rl.ReadLine() - budgetStr := string(_budgetStr) - if err != nil { - printErr(xerrors.Errorf("reading miner address: %w", err)) - continue - } - - budget, err = types.ParseFIL(budgetStr) - if err != nil { - printErr(xerrors.Errorf("parsing FIL: %w", err)) - continue uiLoop - } - - var goodAsks []QueriedAsk - for _, ask := range candidateAsks { - p := ask.Ask.Price - if verified { - p = ask.Ask.VerifiedPrice - } - - epochPrice := types.BigDiv(types.BigMul(p, types.NewInt(uint64(ds.PieceSize))), gib) - totalPrice := types.BigMul(epochPrice, types.NewInt(uint64(epochs))) - - if totalPrice.LessThan(abi.TokenAmount(budget)) { - goodAsks = append(goodAsks, ask) - } - } - candidateAsks = goodAsks - afmt.Printf("%d asks within budget\n", len(candidateAsks)) - state = "find-count" - case "find-count": - afmt.Print("Deals to make (1): ") - dealcStr, _, err := rl.ReadLine() - if err != nil { - printErr(xerrors.Errorf("reading deal count: %w", err)) - continue - } - - dealCount, err = strconv.ParseInt(string(dealcStr), 10, 64) - if err != nil { - printErr(xerrors.Errorf("reading deal count: invalid number")) - continue - } - - color.Blue(".. Picking miners") - - // TODO: some better strategy (this tries to pick randomly) - var pickedAsks []*storagemarket.StorageAsk - pickLoop: - for i := 0; i < 64; i++ { - rand.Shuffle(len(candidateAsks), func(i, j int) { - candidateAsks[i], candidateAsks[j] = candidateAsks[j], candidateAsks[i] - }) - - remainingBudget := abi.TokenAmount(budget) - pickedAsks = []*storagemarket.StorageAsk{} - - for _, ask := range candidateAsks { - p := ask.Ask.Price - if verified { - p = ask.Ask.VerifiedPrice - } - - epochPrice := types.BigDiv(types.BigMul(p, types.NewInt(uint64(ds.PieceSize))), gib) - totalPrice := types.BigMul(epochPrice, types.NewInt(uint64(epochs))) - - if totalPrice.GreaterThan(remainingBudget) { - continue - } - - pickedAsks = append(pickedAsks, ask.Ask) - remainingBudget = big.Sub(remainingBudget, totalPrice) - - if len(pickedAsks) == int(dealCount) { - break pickLoop - } - } - } - - for _, pickedAsk := range pickedAsks { - maddrs = append(maddrs, pickedAsk.Miner) - ask = append(ask, *pickedAsk) - } - - state = "confirm" - case "query": - color.Blue(".. querying miner asks") - - for _, maddr := range maddrs { - mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - printErr(xerrors.Errorf("failed to get peerID for miner: %w", err)) - state = "miner" - continue uiLoop - } - - a, err := api.ClientQueryAsk(ctx, *mi.PeerId, maddr) - if err != nil { - printErr(xerrors.Errorf("failed to query ask for miner %s: %w", maddr.String(), err)) - state = "miner" - continue uiLoop - } - - ask = append(ask, *a.Response) - } - - // TODO: run more validation - state = "confirm" - case "confirm": - // TODO: do some more or epochs math (round to miner PP, deal start buffer) - - afmt.Printf("-----\n") - afmt.Printf("Proposing from %s\n", a) - afmt.Printf("\tBalance: %s\n", types.FIL(fromBal)) - afmt.Printf("\n") - afmt.Printf("Piece size: %s (Payload size: %s)\n", units.BytesSize(float64(ds.PieceSize)), units.BytesSize(float64(ds.PayloadSize))) - afmt.Printf("Duration: %s\n", dur) - - pricePerGib := big.Zero() - for _, a := range ask { - p := a.Price - if verified { - p = a.VerifiedPrice - } - pricePerGib = big.Add(pricePerGib, p) - epochPrice := types.BigDiv(types.BigMul(p, types.NewInt(uint64(ds.PieceSize))), gib) - epochPrices = append(epochPrices, epochPrice) - - mpow, err := api.StateMinerPower(ctx, a.Miner, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting power (%s): %w", a.Miner, err) - } - - if len(ask) > 1 { - totalPrice := types.BigMul(epochPrice, types.NewInt(uint64(epochs))) - afmt.Printf("Miner %s (Power:%s) price: ~%s (%s per epoch)\n", color.YellowString(a.Miner.String()), color.GreenString(types.SizeStr(mpow.MinerPower.QualityAdjPower)), color.BlueString(types.FIL(totalPrice).String()), types.FIL(epochPrice)) - } - } - - // TODO: price is based on PaddedPieceSize, right? - epochPrice := types.BigDiv(types.BigMul(pricePerGib, types.NewInt(uint64(ds.PieceSize))), gib) - totalPrice := types.BigMul(epochPrice, types.NewInt(uint64(epochs))) - - afmt.Printf("Total price: ~%s (%s per epoch)\n", color.CyanString(types.FIL(totalPrice).String()), types.FIL(epochPrice)) - afmt.Printf("Verified: %v\n", verified) - - state = "accept" - case "accept": - afmt.Print("\nAccept (yes/no): ") - - _yn, _, err := rl.ReadLine() - yn := string(_yn) - if err != nil { - return err - } - - if yn == "no" { - return nil - } - - if yn != "yes" { - afmt.Println("Type in full 'yes' or 'no'") - continue - } - - state = "execute" - case "execute": - color.Blue(".. executing\n") - - for i, maddr := range maddrs { - proposal, err := api.ClientStartDeal(ctx, &lapi.StartDealParams{ - Data: &storagemarket.DataRef{ - TransferType: storagemarket.TTGraphsync, - Root: data, - - PieceCid: &ds.PieceCID, - PieceSize: ds.PieceSize.Unpadded(), - }, - Wallet: a, - Miner: maddr, - EpochPrice: epochPrices[i], - MinBlocksDuration: uint64(epochs), - DealStartEpoch: abi.ChainEpoch(cctx.Int64("start-epoch")), - FastRetrieval: cctx.Bool("fast-retrieval"), - VerifiedDeal: verified, - }) - if err != nil { - return err - } - - encoder, err := GetCidEncoder(cctx) - if err != nil { - return err - } - - afmt.Printf("Deal (%s) CID: %s\n", maddr, color.GreenString(encoder.Encode(*proposal))) - } - - return nil - default: - return xerrors.Errorf("unknown state: %s", state) - } - } -} - -var clientFindCmd = &cli.Command{ - Name: "find", - Usage: "Find data in the network", - ArgsUsage: "[dataCid]", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "pieceCid", - Usage: "require data to be retrieved from a specific Piece CID", - }, - }, - Action: func(cctx *cli.Context) error { - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - file, err := cid.Parse(cctx.Args().First()) - if err != nil { - return err - } - - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - // Check if we already have this data locally - - has, err := api.ClientHasLocal(ctx, file) - if err != nil { - return err - } - - if has { - fmt.Println("LOCAL") - } - - var pieceCid *cid.Cid - if cctx.String("pieceCid") != "" { - parsed, err := cid.Parse(cctx.String("pieceCid")) - if err != nil { - return err - } - pieceCid = &parsed - } - - offers, err := api.ClientFindData(ctx, file, pieceCid) - if err != nil { - return err - } - - for _, offer := range offers { - if offer.Err != "" { - fmt.Printf("ERR %s@%s: %s\n", offer.Miner, offer.MinerPeer.ID, offer.Err) - continue - } - fmt.Printf("RETRIEVAL %s@%s-%s-%s\n", offer.Miner, offer.MinerPeer.ID, types.FIL(offer.MinPrice), types.SizeStr(types.NewInt(offer.Size))) - } - - return nil - }, -} - -var clientQueryRetrievalAskCmd = &cli.Command{ - Name: "retrieval-ask", - Usage: "Get a miner's retrieval ask", - ArgsUsage: "[minerAddress] [data CID]", - Flags: []cli.Flag{ - &cli.Int64Flag{ - Name: "size", - Usage: "data size in bytes", - }, - }, - Action: func(cctx *cli.Context) error { - afmt := NewAppFmt(cctx.App) - if cctx.NArg() != 2 { - return IncorrectNumArgs(cctx) - } - - maddr, err := address.NewFromString(cctx.Args().First()) - if err != nil { - return err - } - - dataCid, err := cid.Parse(cctx.Args().Get(1)) - if err != nil { - return fmt.Errorf("parsing data cid: %w", err) - } - - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - ask, err := api.ClientMinerQueryOffer(ctx, maddr, dataCid, nil) - if err != nil { - return err - } - - afmt.Printf("Ask: %s\n", maddr) - afmt.Printf("Unseal price: %s\n", types.FIL(ask.UnsealPrice)) - afmt.Printf("Price per byte: %s\n", types.FIL(ask.PricePerByte)) - afmt.Printf("Payment interval: %s\n", types.SizeStr(types.NewInt(ask.PaymentInterval))) - afmt.Printf("Payment interval increase: %s\n", types.SizeStr(types.NewInt(ask.PaymentIntervalIncrease))) - - size := cctx.Uint64("size") - if size == 0 { - if ask.Size == 0 { - return nil - } - size = ask.Size - afmt.Printf("Size: %s\n", types.SizeStr(types.NewInt(ask.Size))) - } - transferPrice := types.BigMul(ask.PricePerByte, types.NewInt(size)) - totalPrice := types.BigAdd(ask.UnsealPrice, transferPrice) - afmt.Printf("Total price for %d bytes: %s\n", size, types.FIL(totalPrice)) - - return nil - }, -} - -var clientListRetrievalsCmd = &cli.Command{ - Name: "list-retrievals", - Usage: "List retrieval market deals", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "verbose", - Aliases: []string{"v"}, - Usage: "print verbose deal details", - }, - &cli.BoolFlag{ - Name: "show-failed", - Usage: "show failed/failing deals", - Value: true, - }, - &cli.BoolFlag{ - Name: "completed", - Usage: "show completed retrievals", - }, - &cli.BoolFlag{ - Name: "watch", - Usage: "watch deal updates in real-time, rather than a one time list", - }, - }, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - verbose := cctx.Bool("verbose") - watch := cctx.Bool("watch") - showFailed := cctx.Bool("show-failed") - completed := cctx.Bool("completed") - - localDeals, err := api.ClientListRetrievals(ctx) - if err != nil { - return err - } - - if watch { - updates, err := api.ClientGetRetrievalUpdates(ctx) - if err != nil { - return err - } - - for { - tm.Clear() - tm.MoveCursor(1, 1) - - err = outputRetrievalDeals(ctx, tm.Screen, localDeals, verbose, showFailed, completed) - if err != nil { - return err - } - - tm.Flush() - - select { - case <-ctx.Done(): - return nil - case updated := <-updates: - var found bool - for i, existing := range localDeals { - if existing.ID == updated.ID { - localDeals[i] = updated - found = true - break - } - } - if !found { - localDeals = append(localDeals, updated) - } - } - } - } - - return outputRetrievalDeals(ctx, cctx.App.Writer, localDeals, verbose, showFailed, completed) - }, -} - -func isTerminalError(status retrievalmarket.DealStatus) bool { - // should patch this in go-fil-markets but to solve the problem immediate and not have buggy output - return retrievalmarket.IsTerminalError(status) || status == retrievalmarket.DealStatusErrored || status == retrievalmarket.DealStatusCancelled -} -func outputRetrievalDeals(ctx context.Context, out io.Writer, localDeals []lapi.RetrievalInfo, verbose bool, showFailed bool, completed bool) error { - var deals []api.RetrievalInfo - for _, deal := range localDeals { - if !showFailed && isTerminalError(deal.Status) { - continue - } - if !completed && retrievalmarket.IsTerminalSuccess(deal.Status) { - continue - } - deals = append(deals, deal) - } - - tableColumns := []tablewriter.Column{ - tablewriter.Col("PayloadCID"), - tablewriter.Col("DealId"), - tablewriter.Col("Provider"), - tablewriter.Col("Status"), - tablewriter.Col("PricePerByte"), - tablewriter.Col("Received"), - tablewriter.Col("TotalPaid"), - } - - if verbose { - tableColumns = append(tableColumns, - tablewriter.Col("PieceCID"), - tablewriter.Col("UnsealPrice"), - tablewriter.Col("BytesPaidFor"), - tablewriter.Col("TransferChannelID"), - tablewriter.Col("TransferStatus"), - ) - } - tableColumns = append(tableColumns, tablewriter.NewLineCol("Message")) - - w := tablewriter.New(tableColumns...) - - for _, d := range deals { - w.Write(toRetrievalOutput(d, verbose)) - } - - return w.Flush(out) -} - -func toRetrievalOutput(d api.RetrievalInfo, verbose bool) map[string]interface{} { - - payloadCID := d.PayloadCID.String() - provider := d.Provider.String() - if !verbose { - payloadCID = ellipsis(payloadCID, 8) - provider = ellipsis(provider, 8) - } - - retrievalOutput := map[string]interface{}{ - "PayloadCID": payloadCID, - "DealId": d.ID, - "Provider": provider, - "Status": retrievalStatusString(d.Status), - "PricePerByte": types.FIL(d.PricePerByte), - "Received": units.BytesSize(float64(d.BytesReceived)), - "TotalPaid": types.FIL(d.TotalPaid), - "Message": d.Message, - } - - if verbose { - transferChannelID := "" - if d.TransferChannelID != nil { - transferChannelID = d.TransferChannelID.String() - } - transferStatus := "" - if d.DataTransfer != nil { - transferStatus = datatransfer.Statuses[d.DataTransfer.Status] - } - pieceCID := "" - if d.PieceCID != nil { - pieceCID = d.PieceCID.String() - } - - retrievalOutput["PieceCID"] = pieceCID - retrievalOutput["UnsealPrice"] = types.FIL(d.UnsealPrice) - retrievalOutput["BytesPaidFor"] = units.BytesSize(float64(d.BytesPaidFor)) - retrievalOutput["TransferChannelID"] = transferChannelID - retrievalOutput["TransferStatus"] = transferStatus - } - return retrievalOutput -} - -func retrievalStatusString(status retrievalmarket.DealStatus) string { - s := retrievalmarket.DealStatuses[status] - - switch { - case isTerminalError(status): - return color.RedString(s) - case retrievalmarket.IsTerminalSuccess(status): - return color.GreenString(s) - default: - return s - } -} - -var clientInspectDealCmd = &cli.Command{ - Name: "inspect-deal", - Usage: "Inspect detailed information about deal's lifecycle and the various stages it goes through", - Flags: []cli.Flag{ - &cli.IntFlag{ - Name: "deal-id", - }, - &cli.StringFlag{ - Name: "proposal-cid", - }, - }, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - - ctx := ReqContext(cctx) - return inspectDealCmd(ctx, api, cctx.String("proposal-cid"), cctx.Int("deal-id")) - }, -} - -var clientDealStatsCmd = &cli.Command{ - Name: "deal-stats", - Usage: "Print statistics about local storage deals", - Flags: []cli.Flag{ - &cli.DurationFlag{ - Name: "newer-than", - }, - }, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - localDeals, err := api.ClientListDeals(ctx) - if err != nil { - return err - } - - var totalSize uint64 - byState := map[storagemarket.StorageDealStatus][]uint64{} - for _, deal := range localDeals { - if cctx.IsSet("newer-than") { - if time.Now().Sub(deal.CreationTime) > cctx.Duration("newer-than") { - continue - } - } - - totalSize += deal.Size - byState[deal.State] = append(byState[deal.State], deal.Size) - } - - fmt.Printf("Total: %d deals, %s\n", len(localDeals), types.SizeStr(types.NewInt(totalSize))) - - type stateStat struct { - state storagemarket.StorageDealStatus - count int - bytes uint64 - } - - stateStats := make([]stateStat, 0, len(byState)) - for state, deals := range byState { - if state == storagemarket.StorageDealActive { - state = math.MaxUint64 // for sort - } - - st := stateStat{ - state: state, - count: len(deals), - } - for _, b := range deals { - st.bytes += b - } - - stateStats = append(stateStats, st) - } - - sort.Slice(stateStats, func(i, j int) bool { - return int64(stateStats[i].state) < int64(stateStats[j].state) - }) - - for _, st := range stateStats { - if st.state == math.MaxUint64 { - st.state = storagemarket.StorageDealActive - } - fmt.Printf("%s: %d deals, %s\n", storagemarket.DealStates[st.state], st.count, types.SizeStr(types.NewInt(st.bytes))) - } - - return nil - }, -} - -var clientListAsksCmd = &cli.Command{ - Name: "list-asks", - Usage: "List asks for top miners", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "by-ping", - Usage: "sort by ping", - }, - &cli.StringFlag{ - Name: "output-format", - Value: "text", - Usage: "Either 'text' or 'csv'", - }, - &cli.BoolFlag{ - Name: "protocols", - Usage: "Output supported deal protocols", - }, - }, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPIV1(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - asks, err := GetAsks(ctx, api) - if err != nil { - return err - } - - if cctx.Bool("by-ping") { - sort.Slice(asks, func(i, j int) bool { - return asks[i].Ping < asks[j].Ping - }) - } - pfmt := "%s: min:%s max:%s price:%s/GiB/Epoch verifiedPrice:%s/GiB/Epoch ping:%s protos:%s\n" - if cctx.String("output-format") == "csv" { - fmt.Printf("Miner,Min,Max,Price,VerifiedPrice,Ping,Protocols") - pfmt = "%s,%s,%s,%s,%s,%s,%s\n" - } - - for _, a := range asks { - ask := a.Ask - - protos := "" - if cctx.Bool("protocols") { - protos = "[" + strings.Join(a.DealProtocols, ",") + "]" - } - - fmt.Printf(pfmt, ask.Miner, - types.SizeStr(types.NewInt(uint64(ask.MinPieceSize))), - types.SizeStr(types.NewInt(uint64(ask.MaxPieceSize))), - types.FIL(ask.Price), - types.FIL(ask.VerifiedPrice), - a.Ping, - protos, - ) - } - - return nil - }, -} - -type QueriedAsk struct { - Ask *storagemarket.StorageAsk - DealProtocols []string - - Ping time.Duration -} - -func GetAsks(ctx context.Context, api lapi.FullNode) ([]QueriedAsk, error) { - isTTY := true - if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 { - isTTY = false - } - if isTTY { - color.Blue(".. getting miner list") - } - miners, err := api.StateListMiners(ctx, types.EmptyTSK) - if err != nil { - return nil, xerrors.Errorf("getting miner list: %w", err) - } - - var lk sync.Mutex - var found int64 - var withMinPower []address.Address - done := make(chan struct{}) - - go func() { - defer close(done) - - var wg sync.WaitGroup - wg.Add(len(miners)) - - throttle := make(chan struct{}, 50) - for _, miner := range miners { - throttle <- struct{}{} - go func(miner address.Address) { - defer wg.Done() - defer func() { - <-throttle - }() - - power, err := api.StateMinerPower(ctx, miner, types.EmptyTSK) - if err != nil { - return - } - - if power.HasMinPower { // TODO: Lower threshold - atomic.AddInt64(&found, 1) - lk.Lock() - withMinPower = append(withMinPower, miner) - lk.Unlock() - } - }(miner) - } - - wg.Wait() - }() - -loop: - for { - select { - case <-time.After(150 * time.Millisecond): - if isTTY { - fmt.Printf("\r* Found %d miners with power", atomic.LoadInt64(&found)) - } - case <-done: - break loop - } - } - if isTTY { - fmt.Printf("\r* Found %d miners with power\n", atomic.LoadInt64(&found)) - - color.Blue(".. querying asks") - } - - var asks []QueriedAsk - var queried, got int64 - - done = make(chan struct{}) - go func() { - defer close(done) - - var wg sync.WaitGroup - wg.Add(len(withMinPower)) - - throttle := make(chan struct{}, 50) - for _, miner := range withMinPower { - throttle <- struct{}{} - go func(miner address.Address) { - defer wg.Done() - defer func() { - <-throttle - atomic.AddInt64(&queried, 1) - }() - - ctx, cancel := context.WithTimeout(ctx, 4*time.Second) - defer cancel() - - mi, err := api.StateMinerInfo(ctx, miner, types.EmptyTSK) - if err != nil { - return - } - if mi.PeerId == nil { - return - } - - ask, err := api.ClientQueryAsk(ctx, *mi.PeerId, miner) - if err != nil { - return - } - - rt := time.Now() - _, err = api.ClientQueryAsk(ctx, *mi.PeerId, miner) - if err != nil { - return - } - pingDuration := time.Now().Sub(rt) - - atomic.AddInt64(&got, 1) - lk.Lock() - asks = append(asks, QueriedAsk{ - Ask: ask.Response, - DealProtocols: ask.DealProtocols, - - Ping: pingDuration, - }) - lk.Unlock() - }(miner) - } - - wg.Wait() - }() - -loop2: - for { - select { - case <-time.After(150 * time.Millisecond): - if isTTY { - fmt.Printf("\r* Queried %d asks, got %d responses", atomic.LoadInt64(&queried), atomic.LoadInt64(&got)) - } - case <-done: - break loop2 - } - } - if isTTY { - fmt.Printf("\r* Queried %d asks, got %d responses\n", atomic.LoadInt64(&queried), atomic.LoadInt64(&got)) - } - - sort.Slice(asks, func(i, j int) bool { - return asks[i].Ask.Price.LessThan(asks[j].Ask.Price) - }) - - return asks, nil -} - -var clientQueryAskCmd = &cli.Command{ - Name: "query-ask", - Usage: "Find a miners ask", - ArgsUsage: "[minerAddress]", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "peerid", - Usage: "specify peer ID of node to make query against", - }, - &cli.Int64Flag{ - Name: "size", - Usage: "data size in bytes", - }, - &cli.Int64Flag{ - Name: "duration", - Usage: "deal duration", - }, - }, - Action: func(cctx *cli.Context) error { - afmt := NewAppFmt(cctx.App) - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - maddr, err := address.NewFromString(cctx.Args().First()) - if err != nil { - return err - } - - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - var pid peer.ID - if pidstr := cctx.String("peerid"); pidstr != "" { - p, err := peer.Decode(pidstr) - if err != nil { - return err - } - pid = p - } else { - mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("failed to get peerID for miner: %w", err) - } - - if mi.PeerId == nil || *mi.PeerId == ("SETME") { - return fmt.Errorf("the miner hasn't initialized yet") - } - - pid = *mi.PeerId - } - - ask, err := api.ClientQueryAsk(ctx, pid, maddr) - if err != nil { - return err - } - - afmt.Printf("Ask: %s\n", maddr) - afmt.Printf("Price per GiB: %s\n", types.FIL(ask.Price)) - afmt.Printf("Verified Price per GiB: %s\n", types.FIL(ask.VerifiedPrice)) - afmt.Printf("Max Piece size: %s\n", types.SizeStr(types.NewInt(uint64(ask.MaxPieceSize)))) - afmt.Printf("Min Piece size: %s\n", types.SizeStr(types.NewInt(uint64(ask.MinPieceSize)))) - - size := cctx.Int64("size") - if size == 0 { - return nil - } - perEpoch := types.BigDiv(types.BigMul(ask.Price, types.NewInt(uint64(size))), types.NewInt(1<<30)) - afmt.Printf("Price per Block: %s\n", types.FIL(perEpoch)) - - duration := cctx.Int64("duration") - if duration == 0 { - return nil - } - afmt.Printf("Total Price: %s\n", types.FIL(types.BigMul(perEpoch, types.NewInt(uint64(duration))))) - - return nil - }, -} - -var clientListDeals = &cli.Command{ - Name: "list-deals", - Usage: "List storage market deals", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "verbose", - Aliases: []string{"v"}, - Usage: "print verbose deal details", - }, - &cli.BoolFlag{ - Name: "show-failed", - Usage: "show failed/failing deals", - }, - &cli.BoolFlag{ - Name: "watch", - Usage: "watch deal updates in real-time, rather than a one time list", - }, - }, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - verbose := cctx.Bool("verbose") - watch := cctx.Bool("watch") - showFailed := cctx.Bool("show-failed") - - localDeals, err := api.ClientListDeals(ctx) - if err != nil { - return err - } - - if watch { - updates, err := api.ClientGetDealUpdates(ctx) - if err != nil { - return err - } - - for { - tm.Clear() - tm.MoveCursor(1, 1) - - err = outputStorageDeals(ctx, tm.Screen, api, localDeals, verbose, showFailed) - if err != nil { - return err - } - - tm.Flush() - - select { - case <-ctx.Done(): - return nil - case updated := <-updates: - var found bool - for i, existing := range localDeals { - if existing.ProposalCid.Equals(updated.ProposalCid) { - localDeals[i] = updated - found = true - break - } - } - if !found { - localDeals = append(localDeals, updated) - } - } - } - } - - return outputStorageDeals(ctx, cctx.App.Writer, api, localDeals, verbose, showFailed) - }, -} - -func dealFromDealInfo(ctx context.Context, full v0api.FullNode, head *types.TipSet, v api.DealInfo) deal { - if v.DealID == 0 { - return deal{ - LocalDeal: v, - OnChainDealState: market.EmptyDealState(), - } - } - - onChain, err := full.StateMarketStorageDeal(ctx, v.DealID, head.Key()) - if err != nil { - return deal{LocalDeal: v} - } - - return deal{ - LocalDeal: v, - OnChainDealState: onChain.State.Iface(), - } -} - -func outputStorageDeals(ctx context.Context, out io.Writer, full v0api.FullNode, localDeals []lapi.DealInfo, verbose bool, showFailed bool) error { - sort.Slice(localDeals, func(i, j int) bool { - return localDeals[i].CreationTime.Before(localDeals[j].CreationTime) - }) - - head, err := full.ChainHead(ctx) - if err != nil { - return err - } - - var deals []deal - for _, localDeal := range localDeals { - if showFailed || localDeal.State != storagemarket.StorageDealError { - deals = append(deals, dealFromDealInfo(ctx, full, head, localDeal)) - } - } - - if verbose { - w := tabwriter.NewWriter(out, 2, 4, 2, ' ', 0) - fmt.Fprintf(w, "Created\tDealCid\tDealId\tProvider\tState\tOn Chain?\tSlashed?\tPieceCID\tSize\tPrice\tDuration\tTransferChannelID\tTransferStatus\tVerified\tMessage\n") - for _, d := range deals { - onChain := "N" - if d.OnChainDealState.SectorStartEpoch() != -1 { - onChain = fmt.Sprintf("Y (epoch %d)", d.OnChainDealState.SectorStartEpoch()) - } - - slashed := "N" - if d.OnChainDealState.SlashEpoch() != -1 { - slashed = fmt.Sprintf("Y (epoch %d)", d.OnChainDealState.SlashEpoch()) - } - - price := types.FIL(types.BigMul(d.LocalDeal.PricePerEpoch, types.NewInt(d.LocalDeal.Duration))) - transferChannelID := "" - if d.LocalDeal.TransferChannelID != nil { - transferChannelID = d.LocalDeal.TransferChannelID.String() - } - transferStatus := "" - if d.LocalDeal.DataTransfer != nil { - transferStatus = datatransfer.Statuses[d.LocalDeal.DataTransfer.Status] - // TODO: Include the transferred percentage once this bug is fixed: - // https://github.com/ipfs/go-graphsync/issues/126 - //fmt.Printf("transferred: %d / size: %d\n", d.LocalDeal.DataTransfer.Transferred, d.LocalDeal.Size) - //if d.LocalDeal.Size > 0 { - // pct := (100 * d.LocalDeal.DataTransfer.Transferred) / d.LocalDeal.Size - // transferPct = fmt.Sprintf("%d%%", pct) - //} - } - fmt.Fprintf(w, "%s\t%s\t%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%d\t%s\t%s\t%v\t%s\n", - d.LocalDeal.CreationTime.Format(time.Stamp), - d.LocalDeal.ProposalCid, - d.LocalDeal.DealID, - d.LocalDeal.Provider, - dealStateString(d.LocalDeal.State), - onChain, - slashed, - d.LocalDeal.PieceCID, - types.SizeStr(types.NewInt(d.LocalDeal.Size)), - price, - d.LocalDeal.Duration, - transferChannelID, - transferStatus, - d.LocalDeal.Verified, - d.LocalDeal.Message) - } - return w.Flush() - } - - w := tablewriter.New(tablewriter.Col("DealCid"), - tablewriter.Col("DealId"), - tablewriter.Col("Provider"), - tablewriter.Col("State"), - tablewriter.Col("On Chain?"), - tablewriter.Col("Slashed?"), - tablewriter.Col("PieceCID"), - tablewriter.Col("Size"), - tablewriter.Col("Price"), - tablewriter.Col("Duration"), - tablewriter.Col("Verified"), - tablewriter.NewLineCol("Message")) - - for _, d := range deals { - propcid := ellipsis(d.LocalDeal.ProposalCid.String(), 8) - - onChain := "N" - if d.OnChainDealState.SectorStartEpoch() != -1 { - onChain = fmt.Sprintf("Y (epoch %d)", d.OnChainDealState.SectorStartEpoch()) - } - - slashed := "N" - if d.OnChainDealState.SlashEpoch() != -1 { - slashed = fmt.Sprintf("Y (epoch %d)", d.OnChainDealState.SlashEpoch()) - } - - piece := ellipsis(d.LocalDeal.PieceCID.String(), 8) - - price := types.FIL(types.BigMul(d.LocalDeal.PricePerEpoch, types.NewInt(d.LocalDeal.Duration))) - - w.Write(map[string]interface{}{ - "DealCid": propcid, - "DealId": d.LocalDeal.DealID, - "Provider": d.LocalDeal.Provider, - "State": dealStateString(d.LocalDeal.State), - "On Chain?": onChain, - "Slashed?": slashed, - "PieceCID": piece, - "Size": types.SizeStr(types.NewInt(d.LocalDeal.Size)), - "Price": price, - "Verified": d.LocalDeal.Verified, - "Duration": d.LocalDeal.Duration, - "Message": d.LocalDeal.Message, - }) - } - - return w.Flush(out) -} - -func dealStateString(state storagemarket.StorageDealStatus) string { - s := storagemarket.DealStates[state] - switch state { - case storagemarket.StorageDealError, storagemarket.StorageDealExpired: - return color.RedString(s) - case storagemarket.StorageDealActive: - return color.GreenString(s) - default: - return s - } -} - -type deal struct { - LocalDeal lapi.DealInfo - OnChainDealState market.DealState -} - -var clientGetDealCmd = &cli.Command{ - Name: "get-deal", - Usage: "Print detailed deal information", - ArgsUsage: "[proposalCID]", - Action: func(cctx *cli.Context) error { - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - propcid, err := cid.Decode(cctx.Args().First()) - if err != nil { - return err - } - - di, err := api.ClientGetDealInfo(ctx, propcid) - if err != nil { - return err - } - - out := map[string]interface{}{ - "DealInfo: ": di, - } - - if di.DealID != 0 { - onChain, err := api.StateMarketStorageDeal(ctx, di.DealID, types.EmptyTSK) - if err != nil { - return err - } - - out["OnChain"] = onChain - } - - b, err := json.MarshalIndent(out, "", " ") - if err != nil { - return err - } - fmt.Println(string(b)) - return nil - }, -} - -var clientBalancesCmd = &cli.Command{ - Name: "balances", - Usage: "Print storage market client balances", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "client", - Usage: "specify storage client address", - }, - }, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - var addr address.Address - if clientFlag := cctx.String("client"); clientFlag != "" { - ca, err := address.NewFromString(clientFlag) - if err != nil { - return err - } - - addr = ca - } else { - def, err := api.WalletDefaultAddress(ctx) - if err != nil { - return err - } - addr = def - } - - balance, err := api.StateMarketBalance(ctx, addr, types.EmptyTSK) - if err != nil { - return err - } - - reserved, err := api.MarketGetReserved(ctx, addr) - if err != nil { - return err - } - - avail := big.Sub(big.Sub(balance.Escrow, balance.Locked), reserved) - if avail.LessThan(big.Zero()) { - avail = big.Zero() - } - - fmt.Printf("Client Market Balance for address %s:\n", addr) - - fmt.Printf(" Escrowed Funds: %s\n", types.FIL(balance.Escrow)) - fmt.Printf(" Locked Funds: %s\n", types.FIL(balance.Locked)) - fmt.Printf(" Reserved Funds: %s\n", types.FIL(reserved)) - fmt.Printf(" Available to Withdraw: %s\n", types.FIL(avail)) - - return nil - }, -} - -var clientStat = &cli.Command{ - Name: "stat", - Usage: "Print information about a locally stored file (piece size, etc)", - ArgsUsage: "", - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - dataCid, err := cid.Parse(cctx.Args().First()) - if err != nil { - return fmt.Errorf("parsing data cid: %w", err) - } - - ds, err := api.ClientDealSize(ctx, dataCid) - if err != nil { - return err - } - - fmt.Printf("Piece Size : %v\n", ds.PieceSize) - fmt.Printf("Payload Size: %v\n", ds.PayloadSize) - - return nil - }, -} - -var clientRestartTransfer = &cli.Command{ - Name: "restart-transfer", - Usage: "Force restart a stalled data transfer", - ArgsUsage: "[transferID]", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "peerid", - Usage: "narrow to transfer with specific peer", - }, - &cli.BoolFlag{ - Name: "initiator", - Usage: "specify only transfers where peer is/is not initiator", - Value: true, - }, - }, - Action: func(cctx *cli.Context) error { - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - transferUint, err := strconv.ParseUint(cctx.Args().First(), 10, 64) - if err != nil { - return fmt.Errorf("Error reading transfer ID: %w", err) - } - transferID := datatransfer.TransferID(transferUint) - initiator := cctx.Bool("initiator") - var other peer.ID - if pidstr := cctx.String("peerid"); pidstr != "" { - p, err := peer.Decode(pidstr) - if err != nil { - return err - } - other = p - } else { - channels, err := api.ClientListDataTransfers(ctx) - if err != nil { - return err - } - found := false - for _, channel := range channels { - if channel.IsInitiator == initiator && channel.TransferID == transferID { - other = channel.OtherPeer - found = true - break - } - } - if !found { - return errors.New("unable to find matching data transfer") - } - } - - return api.ClientRestartDataTransfer(ctx, transferID, other, initiator) - }, -} - -var clientCancelTransfer = &cli.Command{ - Name: "cancel-transfer", - Usage: "Force cancel a data transfer", - ArgsUsage: "[transferID]", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "peerid", - Usage: "narrow to transfer with specific peer", - }, - &cli.BoolFlag{ - Name: "initiator", - Usage: "specify only transfers where peer is/is not initiator", - Value: true, - }, - &cli.DurationFlag{ - Name: "cancel-timeout", - Usage: "time to wait for cancel to be sent to storage provider", - Value: 5 * time.Second, - }, - }, - Action: func(cctx *cli.Context) error { - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - transferUint, err := strconv.ParseUint(cctx.Args().First(), 10, 64) - if err != nil { - return fmt.Errorf("Error reading transfer ID: %w", err) - } - transferID := datatransfer.TransferID(transferUint) - initiator := cctx.Bool("initiator") - var other peer.ID - if pidstr := cctx.String("peerid"); pidstr != "" { - p, err := peer.Decode(pidstr) - if err != nil { - return err - } - other = p - } else { - channels, err := api.ClientListDataTransfers(ctx) - if err != nil { - return err - } - found := false - for _, channel := range channels { - if channel.IsInitiator == initiator && channel.TransferID == transferID { - other = channel.OtherPeer - found = true - break - } - } - if !found { - return errors.New("unable to find matching data transfer") - } - } - - timeoutCtx, cancel := context.WithTimeout(ctx, cctx.Duration("cancel-timeout")) - defer cancel() - return api.ClientCancelDataTransfer(timeoutCtx, transferID, other, initiator) - }, -} - -var clientCancelRetrievalDealCmd = &cli.Command{ - Name: "cancel-retrieval", - Usage: "Cancel a retrieval deal by deal ID; this also cancels the associated transfer", - Flags: []cli.Flag{ - &cli.Int64Flag{ - Name: "deal-id", - Usage: "specify retrieval deal by deal ID", - Required: true, - }, - }, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - id := cctx.Int64("deal-id") - if id < 0 { - return errors.New("deal id cannot be negative") - } - - return api.ClientCancelRetrievalDeal(ctx, retrievalmarket.DealID(id)) - }, -} - -var clientListTransfers = &cli.Command{ - Name: "list-transfers", - Usage: "List ongoing data transfers for deals", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "verbose", - Aliases: []string{"v"}, - Usage: "print verbose transfer details", - }, - &cli.BoolFlag{ - Name: "completed", - Usage: "show completed data transfers", - }, - &cli.BoolFlag{ - Name: "watch", - Usage: "watch deal updates in real-time, rather than a one time list", - }, - &cli.BoolFlag{ - Name: "show-failed", - Usage: "show failed/cancelled transfers", - }, - }, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - channels, err := api.ClientListDataTransfers(ctx) - if err != nil { - return err - } - - verbose := cctx.Bool("verbose") - completed := cctx.Bool("completed") - watch := cctx.Bool("watch") - showFailed := cctx.Bool("show-failed") - if watch { - channelUpdates, err := api.ClientDataTransferUpdates(ctx) - if err != nil { - return err - } - - for { - tm.Clear() // Clear current screen - - tm.MoveCursor(1, 1) - - OutputDataTransferChannels(tm.Screen, channels, verbose, completed, showFailed) - - tm.Flush() - - select { - case <-ctx.Done(): - return nil - case channelUpdate := <-channelUpdates: - var found bool - for i, existing := range channels { - if existing.TransferID == channelUpdate.TransferID && - existing.OtherPeer == channelUpdate.OtherPeer && - existing.IsSender == channelUpdate.IsSender && - existing.IsInitiator == channelUpdate.IsInitiator { - channels[i] = channelUpdate - found = true - break - } - } - if !found { - channels = append(channels, channelUpdate) - } - } - } - } - OutputDataTransferChannels(os.Stdout, channels, verbose, completed, showFailed) - return nil - }, -} - -// OutputDataTransferChannels generates table output for a list of channels -func OutputDataTransferChannels(out io.Writer, channels []lapi.DataTransferChannel, verbose, completed, showFailed bool) { - sort.Slice(channels, func(i, j int) bool { - return channels[i].TransferID < channels[j].TransferID - }) - - var receivingChannels, sendingChannels []lapi.DataTransferChannel - for _, channel := range channels { - if !completed && channel.Status == datatransfer.Completed { - continue - } - if !showFailed && (channel.Status == datatransfer.Failed || channel.Status == datatransfer.Cancelled) { - continue - } - if channel.IsSender { - sendingChannels = append(sendingChannels, channel) - } else { - receivingChannels = append(receivingChannels, channel) - } - } - - fmt.Fprintf(out, "Sending Channels\n\n") - w := tablewriter.New(tablewriter.Col("ID"), - tablewriter.Col("Status"), - tablewriter.Col("Sending To"), - tablewriter.Col("Root Cid"), - tablewriter.Col("Initiated?"), - tablewriter.Col("Transferred"), - tablewriter.Col("Voucher"), - tablewriter.NewLineCol("Message")) - for _, channel := range sendingChannels { - w.Write(toChannelOutput("Sending To", channel, verbose)) - } - _ = w.Flush(out) - - fmt.Fprintf(out, "\nReceiving Channels\n\n") - w = tablewriter.New(tablewriter.Col("ID"), - tablewriter.Col("Status"), - tablewriter.Col("Receiving From"), - tablewriter.Col("Root Cid"), - tablewriter.Col("Initiated?"), - tablewriter.Col("Transferred"), - tablewriter.Col("Voucher"), - tablewriter.NewLineCol("Message")) - for _, channel := range receivingChannels { - w.Write(toChannelOutput("Receiving From", channel, verbose)) - } - _ = w.Flush(out) -} - -func channelStatusString(status datatransfer.Status) string { - s := datatransfer.Statuses[status] - switch status { - case datatransfer.Failed, datatransfer.Cancelled: - return color.RedString(s) - case datatransfer.Completed: - return color.GreenString(s) - default: - return s - } -} - -func toChannelOutput(otherPartyColumn string, channel lapi.DataTransferChannel, verbose bool) map[string]interface{} { - rootCid := channel.BaseCID.String() - otherParty := channel.OtherPeer.String() - if !verbose { - rootCid = ellipsis(rootCid, 8) - otherParty = ellipsis(otherParty, 8) - } - - initiated := "N" - if channel.IsInitiator { - initiated = "Y" - } - - voucher := channel.Voucher - if len(voucher) > 40 && !verbose { - voucher = ellipsis(voucher, 37) - } - - return map[string]interface{}{ - "ID": channel.TransferID, - "Status": channelStatusString(channel.Status), - otherPartyColumn: otherParty, - "Root Cid": rootCid, - "Initiated?": initiated, - "Transferred": units.BytesSize(float64(channel.Transferred)), - "Voucher": voucher, - "Message": channel.Message, - } -} - -func ellipsis(s string, length int) string { - if length > 0 && len(s) > length { - return "..." + s[len(s)-length:] - } - return s -} - -func inspectDealCmd(ctx context.Context, api v0api.FullNode, proposalCid string, dealId int) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - deals, err := api.ClientListDeals(ctx) - if err != nil { - return err - } - - var di *lapi.DealInfo - for i, cdi := range deals { - if proposalCid != "" && cdi.ProposalCid.String() == proposalCid { - di = &deals[i] - break - } - - if dealId != 0 && int(cdi.DealID) == dealId { - di = &deals[i] - break - } - } - - if di == nil { - if proposalCid != "" { - return fmt.Errorf("cannot find deal with proposal cid: %s", proposalCid) - } - if dealId != 0 { - return fmt.Errorf("cannot find deal with deal id: %v", dealId) - } - return errors.New("you must specify proposal cid or deal id in order to inspect a deal") - } - - // populate DealInfo.DealStages and DataTransfer.Stages - di, err = api.ClientGetDealInfo(ctx, di.ProposalCid) - if err != nil { - return fmt.Errorf("cannot get deal info for proposal cid: %v", di.ProposalCid) - } - - renderDeal(di) - - return nil -} - -func renderDeal(di *lapi.DealInfo) { - color.Blue("Deal ID: %d\n", int(di.DealID)) - color.Blue("Proposal CID: %s\n\n", di.ProposalCid.String()) - - if di.DealStages == nil { - color.Yellow("Deal was made with an older version of Lotus and Lotus did not collect detailed information about its stages") - return - } - - for _, stg := range di.DealStages.Stages { - msg := fmt.Sprintf("%s %s: %s (expected duration: %s)", color.BlueString("Stage:"), color.BlueString(strings.TrimPrefix(stg.Name, "StorageDeal")), stg.Description, color.GreenString(stg.ExpectedDuration)) - if stg.UpdatedTime.Time().IsZero() { - msg = color.YellowString(msg) - } - fmt.Println(msg) - - for _, l := range stg.Logs { - fmt.Printf(" %s %s\n", color.YellowString(l.UpdatedTime.Time().UTC().Round(time.Second).Format(time.Stamp)), l.Log) - } - - if stg.Name == "StorageDealStartDataTransfer" { - for _, dtStg := range di.DataTransfer.Stages.Stages { - fmt.Printf(" %s %s %s\n", color.YellowString(dtStg.CreatedTime.Time().UTC().Round(time.Second).Format(time.Stamp)), color.BlueString("Data transfer stage:"), color.BlueString(dtStg.Name)) - for _, l := range dtStg.Logs { - fmt.Printf(" %s %s\n", color.YellowString(l.UpdatedTime.Time().UTC().Round(time.Second).Format(time.Stamp)), l.Log) - } - } - } - } -} diff --git a/cli/client_retr.go b/cli/client_retr.go deleted file mode 100644 index fa8164ab5..000000000 --- a/cli/client_retr.go +++ /dev/null @@ -1,550 +0,0 @@ -package cli - -import ( - "bytes" - "context" - "fmt" - "io" - "os" - "sort" - "strings" - "time" - - "github.com/ipfs/boxo/blockservice" - offline "github.com/ipfs/boxo/exchange/offline" - "github.com/ipfs/boxo/ipld/merkledag" - "github.com/ipfs/go-cid" - carv2 "github.com/ipld/go-car/v2" - "github.com/ipld/go-car/v2/blockstore" - "github.com/ipld/go-ipld-prime" - "github.com/ipld/go-ipld-prime/codec/dagjson" - basicnode "github.com/ipld/go-ipld-prime/node/basic" - "github.com/ipld/go-ipld-prime/traversal" - "github.com/ipld/go-ipld-prime/traversal/selector" - "github.com/ipld/go-ipld-prime/traversal/selector/builder" - selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse" - textselector "github.com/ipld/go-ipld-selector-text-lite" - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - "github.com/filecoin-project/go-state-types/big" - - lapi "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/types" - cliutil "github.com/filecoin-project/lotus/cli/util" - "github.com/filecoin-project/lotus/markets/utils" - "github.com/filecoin-project/lotus/node/repo" -) - -const DefaultMaxRetrievePrice = "0" - -func retrieve(ctx context.Context, cctx *cli.Context, fapi lapi.FullNode, sel *lapi.Selector, printf func(string, ...interface{})) (*lapi.ExportRef, error) { - var payer address.Address - var err error - if cctx.String("from") != "" { - payer, err = address.NewFromString(cctx.String("from")) - } else { - payer, err = fapi.WalletDefaultAddress(ctx) - } - if err != nil { - return nil, err - } - - file, err := cid.Parse(cctx.Args().Get(0)) - if err != nil { - return nil, err - } - - var pieceCid *cid.Cid - if cctx.String("pieceCid") != "" { - parsed, err := cid.Parse(cctx.String("pieceCid")) - if err != nil { - return nil, err - } - pieceCid = &parsed - } - - var eref *lapi.ExportRef - if cctx.Bool("allow-local") { - imports, err := fapi.ClientListImports(ctx) - if err != nil { - return nil, err - } - - for _, i := range imports { - if i.Root != nil && i.Root.Equals(file) { - eref = &lapi.ExportRef{ - Root: file, - FromLocalCAR: i.CARPath, - } - break - } - } - } - - // no local found, so make a retrieval - if eref == nil { - var offer lapi.QueryOffer - minerStrAddr := cctx.String("provider") - if minerStrAddr == "" { // Local discovery - offers, err := fapi.ClientFindData(ctx, file, pieceCid) - - var cleaned []lapi.QueryOffer - // filter out offers that errored - for _, o := range offers { - if o.Err == "" { - cleaned = append(cleaned, o) - } - } - - offers = cleaned - - // sort by price low to high - sort.Slice(offers, func(i, j int) bool { - return offers[i].MinPrice.LessThan(offers[j].MinPrice) - }) - if err != nil { - return nil, err - } - - // TODO: parse offer strings from `client find`, make this smarter - if len(offers) < 1 { - fmt.Println("Failed to find file") - return nil, nil - } - offer = offers[0] - } else { // Directed retrieval - minerAddr, err := address.NewFromString(minerStrAddr) - if err != nil { - return nil, err - } - offer, err = fapi.ClientMinerQueryOffer(ctx, minerAddr, file, pieceCid) - if err != nil { - return nil, err - } - } - if offer.Err != "" { - return nil, fmt.Errorf("offer error: %s", offer.Err) - } - - maxPrice := types.MustParseFIL(DefaultMaxRetrievePrice) - - if cctx.String("maxPrice") != "" { - maxPrice, err = types.ParseFIL(cctx.String("maxPrice")) - if err != nil { - return nil, xerrors.Errorf("parsing maxPrice: %w", err) - } - } - - if offer.MinPrice.GreaterThan(big.Int(maxPrice)) { - return nil, xerrors.Errorf("failed to find offer satisfying maxPrice: %s. Try increasing maxPrice", maxPrice) - } - - o := offer.Order(payer) - o.DataSelector = sel - - subscribeEvents, err := fapi.ClientGetRetrievalUpdates(ctx) - if err != nil { - return nil, xerrors.Errorf("error setting up retrieval updates: %w", err) - } - retrievalRes, err := fapi.ClientRetrieve(ctx, o) - if err != nil { - return nil, xerrors.Errorf("error setting up retrieval: %w", err) - } - - start := time.Now() - readEvents: - for { - var evt lapi.RetrievalInfo - select { - case <-ctx.Done(): - return nil, xerrors.New("Retrieval Timed Out") - case evt = <-subscribeEvents: - if evt.ID != retrievalRes.DealID { - // we can't check the deal ID ahead of time because: - // 1. We need to subscribe before retrieving. - // 2. We won't know the deal ID until after retrieving. - continue - } - } - - event := "New" - if evt.Event != nil { - event = retrievalmarket.ClientEvents[*evt.Event] - } - - printf("Recv %s, Paid %s, %s (%s), %s [%d|%d]\n", - types.SizeStr(types.NewInt(evt.BytesReceived)), - types.FIL(evt.TotalPaid), - strings.TrimPrefix(event, "ClientEvent"), - strings.TrimPrefix(retrievalmarket.DealStatuses[evt.Status], "DealStatus"), - time.Now().Sub(start).Truncate(time.Millisecond), - evt.ID, - types.NewInt(evt.BytesReceived), - ) - - switch evt.Status { - case retrievalmarket.DealStatusCompleted: - break readEvents - case retrievalmarket.DealStatusRejected: - return nil, xerrors.Errorf("Retrieval Proposal Rejected: %s", evt.Message) - case retrievalmarket.DealStatusCancelled: - return nil, xerrors.Errorf("Retrieval Proposal Cancelled: %s", evt.Message) - case - retrievalmarket.DealStatusDealNotFound, - retrievalmarket.DealStatusErrored: - return nil, xerrors.Errorf("Retrieval Error: %s", evt.Message) - } - } - - eref = &lapi.ExportRef{ - Root: file, - DealID: retrievalRes.DealID, - } - } - - return eref, nil -} - -var retrFlagsCommon = []cli.Flag{ - &cli.StringFlag{ - Name: "from", - Usage: "address to send transactions from", - }, - &cli.StringFlag{ - Name: "provider", - Usage: "provider to use for retrieval, if not present it'll use local discovery", - Aliases: []string{"miner"}, - }, - &cli.StringFlag{ - Name: "maxPrice", - Usage: fmt.Sprintf("maximum price the client is willing to consider (default: %s FIL)", DefaultMaxRetrievePrice), - }, - &cli.StringFlag{ - Name: "pieceCid", - Usage: "require data to be retrieved from a specific Piece CID", - }, - &cli.BoolFlag{ - Name: "allow-local", - // todo: default to true? - }, -} - -var clientRetrieveCmd = &cli.Command{ - Name: "retrieve", - Usage: "Retrieve data from network", - ArgsUsage: "[dataCid outputPath]", - Description: `Retrieve data from the Filecoin network. - -The retrieve command will attempt to find a provider make a retrieval deal with -them. In case a provider can't be found, it can be specified with the --provider -flag. - -By default the data will be interpreted as DAG-PB UnixFSv1 File. Alternatively -a CAR file containing the raw IPLD graph can be exported by setting the --car -flag. - -Partial Retrieval: - -The --data-selector flag can be used to specify a sub-graph to fetch. The -selector can be specified as either IPLD datamodel text-path selector, or IPLD -json selector. - -In case of unixfs retrieval, the selector must point at a single root node, and -match the entire graph under that node. - -In case of CAR retrieval, the selector must have one common "sub-root" node. - -Examples: - -- Retrieve a file by CID - $ lotus client retrieve Qm... my-file.txt - -- Retrieve a file by CID from f0123 - $ lotus client retrieve --provider f0123 Qm... my-file.txt - -- Retrieve a first file from a specified directory - $ lotus client retrieve --data-selector /Links/0/Hash Qm... my-file.txt -`, - Flags: append([]cli.Flag{ - &cli.BoolFlag{ - Name: "car", - Usage: "Export to a car file instead of a regular file", - }, - &cli.StringFlag{ - Name: "data-selector", - Aliases: []string{"datamodel-path-selector"}, - Usage: "IPLD datamodel text-path selector, or IPLD json selector", - }, - &cli.BoolFlag{ - Name: "car-export-merkle-proof", - Usage: "(requires --data-selector and --car) Export data-selector merkle proof", - }, - }, retrFlagsCommon...), - Action: func(cctx *cli.Context) error { - if cctx.NArg() != 2 { - return IncorrectNumArgs(cctx) - } - - if cctx.Bool("car-export-merkle-proof") { - if !cctx.Bool("car") || !cctx.IsSet("data-selector") { - return ShowHelp(cctx, fmt.Errorf("--car-export-merkle-proof requires --car and --data-selector")) - } - } - - fapi, closer, err := GetFullNodeAPIV1(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - afmt := NewAppFmt(cctx.App) - - var s *lapi.Selector - if sel := lapi.Selector(cctx.String("data-selector")); sel != "" { - s = &sel - } - - eref, err := retrieve(ctx, cctx, fapi, s, afmt.Printf) - if err != nil { - return err - } - if eref == nil { - return xerrors.Errorf("failed to find providers") - } - - if s != nil { - eref.DAGs = append(eref.DAGs, lapi.DagSpec{DataSelector: s, ExportMerkleProof: cctx.Bool("car-export-merkle-proof")}) - } - - err = fapi.ClientExport(ctx, *eref, lapi.FileRef{ - Path: cctx.Args().Get(1), - IsCAR: cctx.Bool("car"), - }) - if err != nil { - return err - } - afmt.Println("Success") - return nil - }, -} - -var clientRetrieveCatCmd = &cli.Command{ - Name: "cat", - Usage: "Show data from network", - ArgsUsage: "[dataCid]", - Flags: append([]cli.Flag{ - &cli.BoolFlag{ - Name: "ipld", - Usage: "list IPLD datamodel links", - }, - &cli.StringFlag{ - Name: "data-selector", - Usage: "IPLD datamodel text-path selector, or IPLD json selector", - }, - }, retrFlagsCommon...), - Action: func(cctx *cli.Context) error { - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - ainfo, err := GetAPIInfo(cctx, repo.FullNode) - if err != nil { - return xerrors.Errorf("could not get API info: %w", err) - } - - fapi, closer, err := GetFullNodeAPIV1(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - afmt := NewAppFmt(cctx.App) - - sel := lapi.Selector(cctx.String("data-selector")) - selp := &sel - if sel == "" { - selp = nil - } - - eref, err := retrieve(ctx, cctx, fapi, selp, afmt.Printf) - if err != nil { - return err - } - - fmt.Println() // separate retrieval events from results - - if sel != "" { - eref.DAGs = append(eref.DAGs, lapi.DagSpec{DataSelector: &sel}) - } - - rc, err := cliutil.ClientExportStream(ainfo.Addr, ainfo.AuthHeader(), *eref, false) - if err != nil { - return err - } - defer rc.Close() // nolint - - _, err = io.Copy(os.Stdout, rc) - return err - }, -} - -func pathToSel(psel string, matchTraversal bool, sub builder.SelectorSpec) (lapi.Selector, error) { - rs, err := textselector.SelectorSpecFromPath(textselector.Expression(psel), matchTraversal, sub) - if err != nil { - return "", xerrors.Errorf("failed to parse path-selector: %w", err) - } - - var b bytes.Buffer - if err := dagjson.Encode(rs.Node(), &b); err != nil { - return "", err - } - - return lapi.Selector(b.String()), nil -} - -var clientRetrieveLsCmd = &cli.Command{ - Name: "ls", - Usage: "List object links", - ArgsUsage: "[dataCid]", - Flags: append([]cli.Flag{ - &cli.BoolFlag{ - Name: "ipld", - Usage: "list IPLD datamodel links", - }, - &cli.IntFlag{ - Name: "depth", - Usage: "list links recursively up to the specified depth", - Value: 1, - }, - &cli.StringFlag{ - Name: "data-selector", - Usage: "IPLD datamodel text-path selector, or IPLD json selector", - }, - }, retrFlagsCommon...), - Action: func(cctx *cli.Context) error { - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - ainfo, err := GetAPIInfo(cctx, repo.FullNode) - if err != nil { - return xerrors.Errorf("could not get API info: %w", err) - } - - fapi, closer, err := GetFullNodeAPIV1(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - afmt := NewAppFmt(cctx.App) - - dataSelector := lapi.Selector(fmt.Sprintf(`{"R":{"l":{"depth":%d},":>":{"a":{">":{"|":[{"@":{}},{".":{}}]}}}}}`, cctx.Int("depth"))) - - if cctx.IsSet("data-selector") { - ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any) - dataSelector, err = pathToSel(cctx.String("data-selector"), cctx.Bool("ipld"), - ssb.ExploreUnion( - ssb.Matcher(), - ssb.ExploreAll( - ssb.ExploreRecursive(selector.RecursionLimitDepth(int64(cctx.Int("depth"))), ssb.ExploreAll(ssb.ExploreUnion(ssb.Matcher(), ssb.ExploreRecursiveEdge()))), - ))) - if err != nil { - return xerrors.Errorf("parsing datamodel path: %w", err) - } - } - - eref, err := retrieve(ctx, cctx, fapi, &dataSelector, afmt.Printf) - if err != nil { - return xerrors.Errorf("retrieve: %w", err) - } - - fmt.Println() // separate retrieval events from results - - eref.DAGs = append(eref.DAGs, lapi.DagSpec{ - DataSelector: &dataSelector, - }) - - rc, err := cliutil.ClientExportStream(ainfo.Addr, ainfo.AuthHeader(), *eref, true) - if err != nil { - return xerrors.Errorf("export: %w", err) - } - defer rc.Close() // nolint - - var memcar bytes.Buffer - _, err = io.Copy(&memcar, rc) - if err != nil { - return err - } - - cbs, err := blockstore.NewReadOnly(&bytesReaderAt{bytes.NewReader(memcar.Bytes())}, nil, - carv2.ZeroLengthSectionAsEOF(true), - blockstore.UseWholeCIDs(true)) - if err != nil { - return xerrors.Errorf("opening car blockstore: %w", err) - } - - roots, err := cbs.Roots() - if err != nil { - return xerrors.Errorf("getting roots: %w", err) - } - - if len(roots) != 1 { - return xerrors.Errorf("expected 1 car root, got %d", len(roots)) - } - dserv := merkledag.NewDAGService(blockservice.New(cbs, offline.Exchange(cbs))) - - if !cctx.Bool("ipld") { - links, err := dserv.GetLinks(ctx, roots[0]) - if err != nil { - return xerrors.Errorf("getting links: %w", err) - } - - for _, link := range links { - fmt.Printf("%s %s\t%d\n", link.Cid, link.Name, link.Size) - } - } else { - jsel := lapi.Selector(fmt.Sprintf(`{"R":{"l":{"depth":%d},":>":{"a":{">":{"|":[{"@":{}},{".":{}}]}}}}}`, cctx.Int("depth"))) - - if cctx.IsSet("data-selector") { - ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any) - jsel, err = pathToSel(cctx.String("data-selector"), false, - ssb.ExploreRecursive(selector.RecursionLimitDepth(int64(cctx.Int("depth"))), ssb.ExploreAll(ssb.ExploreUnion(ssb.Matcher(), ssb.ExploreRecursiveEdge()))), - ) - } - - sel, _ := selectorparse.ParseJSONSelector(string(jsel)) - - if err := utils.TraverseDag( - ctx, - dserv, - roots[0], - sel, - nil, - func(p traversal.Progress, n ipld.Node, r traversal.VisitReason) error { - if r == traversal.VisitReason_SelectionMatch { - fmt.Println(p.Path) - } - return nil - }, - ); err != nil { - return err - } - } - - return err - }, -} - -type bytesReaderAt struct { - btr *bytes.Reader -} - -func (b bytesReaderAt) ReadAt(p []byte, off int64) (n int, err error) { - return b.btr.ReadAt(p, off) -} - -var _ io.ReaderAt = &bytesReaderAt{} diff --git a/cli/cmd.go b/cli/cmd.go index 76c0ab300..9ae8c14b7 100644 --- a/cli/cmd.go +++ b/cli/cmd.go @@ -54,7 +54,6 @@ var GetFullNodeAPIV1 = cliutil.GetFullNodeAPIV1 var GetGatewayAPI = cliutil.GetGatewayAPI var GetStorageMinerAPI = cliutil.GetStorageMinerAPI -var GetMarketsAPI = cliutil.GetMarketsAPI var GetWorkerAPI = cliutil.GetWorkerAPI var CommonCommands = []*cli.Command{ diff --git a/cli/helper.go b/cli/helper.go index fb1899e0a..551422983 100644 --- a/cli/helper.go +++ b/cli/helper.go @@ -76,15 +76,15 @@ func NewAppFmt(a *ufcli.App) *AppFmt { } func (a *AppFmt) Print(args ...interface{}) { - fmt.Fprint(a.app.Writer, args...) + _, _ = fmt.Fprint(a.app.Writer, args...) } func (a *AppFmt) Println(args ...interface{}) { - fmt.Fprintln(a.app.Writer, args...) + _, _ = fmt.Fprintln(a.app.Writer, args...) } func (a *AppFmt) Printf(fmtstr string, args ...interface{}) { - fmt.Fprintf(a.app.Writer, fmtstr, args...) + _, _ = fmt.Fprintf(a.app.Writer, fmtstr, args...) } func (a *AppFmt) Scan(args ...interface{}) (int, error) { diff --git a/cli/info.go b/cli/info.go index 01f64dee9..482f01d98 100644 --- a/cli/info.go +++ b/cli/info.go @@ -3,9 +3,7 @@ package cli import ( "context" "fmt" - "math" "os" - "sort" "strings" "text/tabwriter" "time" @@ -14,7 +12,6 @@ import ( "github.com/fatih/color" "github.com/urfave/cli/v2" - "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/api/v1api" @@ -155,57 +152,6 @@ func infoCmdAct(cctx *cli.Context) error { fmt.Printf("Payment Channels: %v channels\n", len(chs)) } fmt.Println() - - localDeals, err := fullapi.ClientListDeals(ctx) - if err != nil { - return err - } - - var totalSize uint64 - byState := map[storagemarket.StorageDealStatus][]uint64{} - for _, deal := range localDeals { - totalSize += deal.Size - byState[deal.State] = append(byState[deal.State], deal.Size) - } - - fmt.Printf("Deals: %d, %s\n", len(localDeals), types.SizeStr(types.NewInt(totalSize))) - - type stateStat struct { - state storagemarket.StorageDealStatus - count int - bytes uint64 - } - - stateStats := make([]stateStat, 0, len(byState)) - for state, deals := range byState { - if state == storagemarket.StorageDealActive { - state = math.MaxUint64 // for sort - } - - st := stateStat{ - state: state, - count: len(deals), - } - for _, b := range deals { - st.bytes += b - } - - stateStats = append(stateStats, st) - } - - sort.Slice(stateStats, func(i, j int) bool { - return int64(stateStats[i].state) < int64(stateStats[j].state) - }) - - for _, st := range stateStats { - if st.state == math.MaxUint64 { - st.state = storagemarket.StorageDealActive - } - fmt.Printf(" %s: %d deals, %s\n", storagemarket.DealStates[st.state], st.count, types.SizeStr(types.NewInt(st.bytes))) - } - - fmt.Println() - tw := tabwriter.NewWriter(os.Stdout, 6, 6, 2, ' ', 0) s, err := fullapi.NetBandwidthStats(ctx) @@ -214,8 +160,18 @@ func infoCmdAct(cctx *cli.Context) error { } fmt.Printf("Bandwidth:\n") - fmt.Fprintf(tw, "\tTotalIn\tTotalOut\tRateIn\tRateOut\n") - fmt.Fprintf(tw, "\t%s\t%s\t%s/s\t%s/s\n", humanize.Bytes(uint64(s.TotalIn)), humanize.Bytes(uint64(s.TotalOut)), humanize.Bytes(uint64(s.RateIn)), humanize.Bytes(uint64(s.RateOut))) + if _, err := fmt.Fprintf(tw, "\tTotalIn\tTotalOut\tRateIn\tRateOut\n"); err != nil { + return err + } + if _, err := fmt.Fprintf( + tw, + "\t%s\t%s\t%s/s\t%s/s\n", + humanize.Bytes(uint64(s.TotalIn)), + humanize.Bytes(uint64(s.TotalOut)), + humanize.Bytes(uint64(s.RateIn)), + humanize.Bytes(uint64(s.RateOut))); err != nil { + return err + } return tw.Flush() } diff --git a/cli/multisig.go b/cli/multisig.go index 290cf6700..948d50066 100644 --- a/cli/multisig.go +++ b/cli/multisig.go @@ -168,7 +168,7 @@ var msigCreateCmd = &cli.Command{ // check it executed successfully if wait.Receipt.ExitCode.IsError() { - fmt.Fprintln(cctx.App.Writer, "actor creation failed!") + _, _ = fmt.Fprintln(cctx.App.Writer, "actor creation failed!") return err } @@ -178,7 +178,7 @@ var msigCreateCmd = &cli.Command{ if err := execreturn.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil { return err } - fmt.Fprintln(cctx.App.Writer, "Created new multisig: ", execreturn.IDAddress, execreturn.RobustAddress) + _, _ = fmt.Fprintln(cctx.App.Writer, "Created new multisig: ", execreturn.IDAddress, execreturn.RobustAddress) // TODO: maybe register this somewhere return nil @@ -242,25 +242,25 @@ var msigInspectCmd = &cli.Command{ return err } - fmt.Fprintf(cctx.App.Writer, "Balance: %s\n", types.FIL(act.Balance)) - fmt.Fprintf(cctx.App.Writer, "Spendable: %s\n", types.FIL(types.BigSub(act.Balance, locked))) + _, _ = fmt.Fprintf(cctx.App.Writer, "Balance: %s\n", types.FIL(act.Balance)) + _, _ = fmt.Fprintf(cctx.App.Writer, "Spendable: %s\n", types.FIL(types.BigSub(act.Balance, locked))) if cctx.Bool("vesting") { ib, err := mstate.InitialBalance() if err != nil { return err } - fmt.Fprintf(cctx.App.Writer, "InitialBalance: %s\n", types.FIL(ib)) + _, _ = fmt.Fprintf(cctx.App.Writer, "InitialBalance: %s\n", types.FIL(ib)) se, err := mstate.StartEpoch() if err != nil { return err } - fmt.Fprintf(cctx.App.Writer, "StartEpoch: %d\n", se) + _, _ = fmt.Fprintf(cctx.App.Writer, "StartEpoch: %d\n", se) ud, err := mstate.UnlockDuration() if err != nil { return err } - fmt.Fprintf(cctx.App.Writer, "UnlockDuration: %d\n", ud) + _, _ = fmt.Fprintf(cctx.App.Writer, "UnlockDuration: %d\n", ud) } signers, err := mstate.Signers() @@ -271,17 +271,17 @@ var msigInspectCmd = &cli.Command{ if err != nil { return err } - fmt.Fprintf(cctx.App.Writer, "Threshold: %d / %d\n", threshold, len(signers)) - fmt.Fprintln(cctx.App.Writer, "Signers:") + _, _ = fmt.Fprintf(cctx.App.Writer, "Threshold: %d / %d\n", threshold, len(signers)) + _, _ = fmt.Fprintln(cctx.App.Writer, "Signers:") signerTable := tabwriter.NewWriter(cctx.App.Writer, 8, 4, 2, ' ', 0) - fmt.Fprintf(signerTable, "ID\tAddress\n") + _, _ = fmt.Fprintf(signerTable, "ID\tAddress\n") for _, s := range signers { signerActor, err := api.StateAccountKey(ctx, s, types.EmptyTSK) if err != nil { - fmt.Fprintf(signerTable, "%s\t%s\n", s, "N/A") + _, _ = fmt.Fprintf(signerTable, "%s\t%s\n", s, "N/A") } else { - fmt.Fprintf(signerTable, "%s\t%s\n", s, signerActor) + _, _ = fmt.Fprintf(signerTable, "%s\t%s\n", s, signerActor) } } if err := signerTable.Flush(); err != nil { @@ -297,7 +297,7 @@ var msigInspectCmd = &cli.Command{ } decParams := cctx.Bool("decode-params") - fmt.Fprintln(cctx.App.Writer, "Transactions: ", len(pending)) + _, _ = fmt.Fprintln(cctx.App.Writer, "Transactions: ", len(pending)) if len(pending) > 0 { var txids []int64 for txid := range pending { @@ -308,7 +308,7 @@ var msigInspectCmd = &cli.Command{ }) w := tabwriter.NewWriter(cctx.App.Writer, 8, 4, 2, ' ', 0) - fmt.Fprintf(w, "ID\tState\tApprovals\tTo\tValue\tMethod\tParams\n") + _, _ = fmt.Fprintf(w, "ID\tState\tApprovals\tTo\tValue\tMethod\tParams\n") for _, txid := range txids { tx := pending[txid] target := tx.To.String() @@ -320,9 +320,31 @@ var msigInspectCmd = &cli.Command{ if err != nil { if tx.Method == 0 { - fmt.Fprintf(w, "%d\t%s\t%d\t%s\t%s\t%s(%d)\t%s\n", txid, "pending", len(tx.Approved), target, types.FIL(tx.Value), "Send", tx.Method, paramStr) + _, _ = fmt.Fprintf( + w, + "%d\t%s\t%d\t%s\t%s\t%s(%d)\t%s\n", + txid, + "pending", + len(tx.Approved), + target, + types.FIL(tx.Value), + "Send", + tx.Method, + paramStr, + ) } else { - fmt.Fprintf(w, "%d\t%s\t%d\t%s\t%s\t%s(%d)\t%s\n", txid, "pending", len(tx.Approved), target, types.FIL(tx.Value), "new account, unknown method", tx.Method, paramStr) + _, _ = fmt.Fprintf( + w, + "%d\t%s\t%d\t%s\t%s\t%s(%d)\t%s\n", + txid, + "pending", + len(tx.Approved), + target, + types.FIL(tx.Value), + "new account, unknown method", + tx.Method, + paramStr, + ) } } else { method := consensus.NewActorRegistry().Methods[targAct.Code][tx.Method] // TODO: use remote map @@ -341,7 +363,18 @@ var msigInspectCmd = &cli.Command{ paramStr = string(b) } - fmt.Fprintf(w, "%d\t%s\t%d\t%s\t%s\t%s(%d)\t%s\n", txid, "pending", len(tx.Approved), target, types.FIL(tx.Value), method.Name, tx.Method, paramStr) + _, _ = fmt.Fprintf( + w, + "%d\t%s\t%d\t%s\t%s\t%s(%d)\t%s\n", + txid, + "pending", + len(tx.Approved), + target, + types.FIL(tx.Value), + method.Name, + tx.Method, + paramStr, + ) } } if err := w.Flush(); err != nil { @@ -923,7 +956,7 @@ var msigAddProposeCmd = &cli.Command{ msgCid := sm.Cid() - fmt.Fprintln(cctx.App.Writer, "sent add proposal in message: ", msgCid) + _, _ = fmt.Fprintln(cctx.App.Writer, "sent add proposal in message: ", msgCid) wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { diff --git a/cli/net.go b/cli/net.go index 99ee92aef..38a271a29 100644 --- a/cli/net.go +++ b/cli/net.go @@ -479,7 +479,7 @@ var NetBandwidthCmd = &cli.Command{ tw := tabwriter.NewWriter(os.Stdout, 4, 4, 2, ' ', 0) - fmt.Fprintf(tw, "Segment\tTotalIn\tTotalOut\tRateIn\tRateOut\n") + _, _ = fmt.Fprintf(tw, "Segment\tTotalIn\tTotalOut\tRateIn\tRateOut\n") if bypeer { bw, err := api.NetBandwidthStatsByPeer(ctx) @@ -498,7 +498,15 @@ var NetBandwidthCmd = &cli.Command{ for _, p := range peers { s := bw[p] - fmt.Fprintf(tw, "%s\t%s\t%s\t%s/s\t%s/s\n", p, humanize.Bytes(uint64(s.TotalIn)), humanize.Bytes(uint64(s.TotalOut)), humanize.Bytes(uint64(s.RateIn)), humanize.Bytes(uint64(s.RateOut))) + _, _ = fmt.Fprintf( + tw, + "%s\t%s\t%s\t%s/s\t%s/s\n", + p, + humanize.Bytes(uint64(s.TotalIn)), + humanize.Bytes(uint64(s.TotalOut)), + humanize.Bytes(uint64(s.RateIn)), + humanize.Bytes(uint64(s.RateOut)), + ) } } else if byproto { bw, err := api.NetBandwidthStatsByProtocol(ctx) @@ -520,7 +528,15 @@ var NetBandwidthCmd = &cli.Command{ if p == "" { p = "" } - fmt.Fprintf(tw, "%s\t%s\t%s\t%s/s\t%s/s\n", p, humanize.Bytes(uint64(s.TotalIn)), humanize.Bytes(uint64(s.TotalOut)), humanize.Bytes(uint64(s.RateIn)), humanize.Bytes(uint64(s.RateOut))) + _, _ = fmt.Fprintf( + tw, + "%s\t%s\t%s\t%s/s\t%s/s\n", + p, + humanize.Bytes(uint64(s.TotalIn)), + humanize.Bytes(uint64(s.TotalOut)), + humanize.Bytes(uint64(s.RateIn)), + humanize.Bytes(uint64(s.RateOut)), + ) } } else { @@ -529,7 +545,14 @@ var NetBandwidthCmd = &cli.Command{ return err } - fmt.Fprintf(tw, "Total\t%s\t%s\t%s/s\t%s/s\n", humanize.Bytes(uint64(s.TotalIn)), humanize.Bytes(uint64(s.TotalOut)), humanize.Bytes(uint64(s.RateIn)), humanize.Bytes(uint64(s.RateOut))) + _, _ = fmt.Fprintf( + tw, + "Total\t%s\t%s\t%s/s\t%s/s\n", + humanize.Bytes(uint64(s.TotalIn)), + humanize.Bytes(uint64(s.TotalOut)), + humanize.Bytes(uint64(s.RateIn)), + humanize.Bytes(uint64(s.RateOut)), + ) } return tw.Flush() diff --git a/cli/paych.go b/cli/paych.go index 46b043d6a..a2f427fd5 100644 --- a/cli/paych.go +++ b/cli/paych.go @@ -39,11 +39,6 @@ var paychAddFundsCmd = &cli.Command{ Usage: "Add funds to the payment channel between fromAddress and toAddress. Creates the payment channel if it doesn't already exist.", ArgsUsage: "[fromAddress toAddress amount]", Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "restart-retrievals", - Usage: "restart stalled retrieval deals on this payment channel", - Value: true, - }, &cli.BoolFlag{ Name: "reserve", Usage: "mark funds as reserved", @@ -98,11 +93,7 @@ var paychAddFundsCmd = &cli.Command{ return err } - fmt.Fprintln(cctx.App.Writer, chAddr) - restartRetrievals := cctx.Bool("restart-retrievals") - if restartRetrievals { - return api.ClientRetrieveTryRestartInsufficientFunds(ctx, chAddr) - } + _, _ = fmt.Fprintln(cctx.App.Writer, chAddr) return nil }, } @@ -177,23 +168,23 @@ var paychStatusCmd = &cli.Command{ func paychStatus(writer io.Writer, avail *lapi.ChannelAvailableFunds) { if avail.Channel == nil { if avail.PendingWaitSentinel != nil { - fmt.Fprint(writer, "Creating channel\n") - fmt.Fprintf(writer, " From: %s\n", avail.From) - fmt.Fprintf(writer, " To: %s\n", avail.To) - fmt.Fprintf(writer, " Pending Amt: %s\n", types.FIL(avail.PendingAmt)) - fmt.Fprintf(writer, " Wait Sentinel: %s\n", avail.PendingWaitSentinel) + _, _ = fmt.Fprint(writer, "Creating channel\n") + _, _ = fmt.Fprintf(writer, " From: %s\n", avail.From) + _, _ = fmt.Fprintf(writer, " To: %s\n", avail.To) + _, _ = fmt.Fprintf(writer, " Pending Amt: %s\n", types.FIL(avail.PendingAmt)) + _, _ = fmt.Fprintf(writer, " Wait Sentinel: %s\n", avail.PendingWaitSentinel) return } - fmt.Fprint(writer, "Channel does not exist\n") - fmt.Fprintf(writer, " From: %s\n", avail.From) - fmt.Fprintf(writer, " To: %s\n", avail.To) + _, _ = fmt.Fprint(writer, "Channel does not exist\n") + _, _ = fmt.Fprintf(writer, " From: %s\n", avail.From) + _, _ = fmt.Fprintf(writer, " To: %s\n", avail.To) return } if avail.PendingWaitSentinel != nil { - fmt.Fprint(writer, "Adding Funds to channel\n") + _, _ = fmt.Fprint(writer, "Adding Funds to channel\n") } else { - fmt.Fprint(writer, "Channel exists\n") + _, _ = fmt.Fprint(writer, "Channel exists\n") } nameValues := [][]string{ @@ -213,7 +204,7 @@ func paychStatus(writer io.Writer, avail *lapi.ChannelAvailableFunds) { avail.PendingWaitSentinel.String(), }) } - fmt.Fprint(writer, formatNameValues(nameValues)) + _, _ = fmt.Fprint(writer, formatNameValues(nameValues)) } func formatNameValues(nameValues [][]string) string { @@ -249,7 +240,7 @@ var paychListCmd = &cli.Command{ } for _, v := range chs { - fmt.Fprintln(cctx.App.Writer, v.String()) + _, _ = fmt.Fprintln(cctx.App.Writer, v.String()) } return nil }, @@ -290,7 +281,7 @@ var paychSettleCmd = &cli.Command{ return fmt.Errorf("settle message execution failed (exit code %d)", mwait.Receipt.ExitCode) } - fmt.Fprintf(cctx.App.Writer, "Settled channel %s\n", ch) + _, _ = fmt.Fprintf(cctx.App.Writer, "Settled channel %s\n", ch) return nil }, } @@ -330,7 +321,7 @@ var paychCloseCmd = &cli.Command{ return fmt.Errorf("collect message execution failed (exit code %d)", mwait.Receipt.ExitCode) } - fmt.Fprintf(cctx.App.Writer, "Collected funds for channel %s\n", ch) + _, _ = fmt.Fprintf(cctx.App.Writer, "Collected funds for channel %s\n", ch) return nil }, } @@ -390,7 +381,7 @@ var paychVoucherCreateCmd = &cli.Command{ } if v.Voucher == nil { - return fmt.Errorf("Could not create voucher: insufficient funds in channel, shortfall: %d", v.Shortfall) + return fmt.Errorf("could not create voucher: insufficient funds in channel, shortfall: %d", v.Shortfall) } enc, err := EncodedString(v.Voucher) @@ -398,7 +389,7 @@ var paychVoucherCreateCmd = &cli.Command{ return err } - fmt.Fprintln(cctx.App.Writer, enc) + _, _ = fmt.Fprintln(cctx.App.Writer, enc) return nil }, } @@ -434,7 +425,7 @@ var paychVoucherCheckCmd = &cli.Command{ return err } - fmt.Fprintln(cctx.App.Writer, "voucher is valid") + _, _ = fmt.Fprintln(cctx.App.Writer, "voucher is valid") return nil }, } @@ -589,12 +580,16 @@ func outputVoucher(w io.Writer, v *paych.SignedVoucher, export bool) error { } } - fmt.Fprintf(w, "Lane %d, Nonce %d: %s", v.Lane, v.Nonce, types.FIL(v.Amount)) - if export { - fmt.Fprintf(w, "; %s", enc) + if _, err := fmt.Fprintf(w, "Lane %d, Nonce %d: %s", v.Lane, v.Nonce, types.FIL(v.Amount)); err != nil { + return err } - fmt.Fprintln(w) - return nil + if export { + if _, err := fmt.Fprintf(w, "; %s", enc); err != nil { + return err + } + } + _, err := fmt.Fprintln(w) + return err } var paychVoucherSubmitCmd = &cli.Command{ @@ -638,7 +633,7 @@ var paychVoucherSubmitCmd = &cli.Command{ return fmt.Errorf("message execution failed (exit code %d)", mwait.Receipt.ExitCode) } - fmt.Fprintln(cctx.App.Writer, "channel updated successfully") + _, _ = fmt.Fprintln(cctx.App.Writer, "channel updated successfully") return nil }, diff --git a/cli/send.go b/cli/send.go index 89c79e109..a106e8480 100644 --- a/cli/send.go +++ b/cli/send.go @@ -217,7 +217,7 @@ var SendCmd = &cli.Command{ return err } - fmt.Fprintf(cctx.App.Writer, "%s\n", sm.Cid()) + _, _ = fmt.Fprintf(cctx.App.Writer, "%s\n", sm.Cid()) return nil }, } diff --git a/cli/sending_ui.go b/cli/sending_ui.go index c248feb3d..34eb25649 100644 --- a/cli/sending_ui.go +++ b/cli/sending_ui.go @@ -28,7 +28,7 @@ func InteractiveSend(ctx context.Context, cctx *cli.Context, srv ServicesAPI, printer := cctx.App.Writer if xerrors.Is(err, ErrCheckFailed) { if !cctx.Bool("interactive") { - fmt.Fprintf(printer, "Following checks have failed:\n") + _, _ = fmt.Fprintf(printer, "Following checks have failed:\n") printChecks(printer, checks, proto.Message.Cid()) } else { proto, err = resolveChecks(ctx, srv, cctx.App.Writer, proto, checks) @@ -75,11 +75,11 @@ func resolveChecks(ctx context.Context, s ServicesAPI, printer io.Writer, proto *api.MessagePrototype, checkGroups [][]api.MessageCheckStatus, ) (*api.MessagePrototype, error) { - fmt.Fprintf(printer, "Following checks have failed:\n") + _, _ = fmt.Fprintf(printer, "Following checks have failed:\n") printChecks(printer, checkGroups, proto.Message.Cid()) if feeCapBad, baseFee := isFeeCapProblem(checkGroups, proto.Message.Cid()); feeCapBad { - fmt.Fprintf(printer, "Fee of the message can be adjusted\n") + _, _ = fmt.Fprintf(printer, "Fee of the message can be adjusted\n") if askUser(printer, "Do you wish to do that? [Yes/no]: ", true) { var err error proto, err = runFeeCapAdjustmentUI(proto, baseFee) @@ -91,7 +91,7 @@ func resolveChecks(ctx context.Context, s ServicesAPI, printer io.Writer, if err != nil { return nil, err } - fmt.Fprintf(printer, "Following checks still failed:\n") + _, _ = fmt.Fprintf(printer, "Following checks still failed:\n") printChecks(printer, checks, proto.Message.Cid()) } @@ -114,14 +114,14 @@ func printChecks(printer io.Writer, checkGroups [][]api.MessageCheckStatus, prot if !aboutProto { msgName = c.Cid.String() } - fmt.Fprintf(printer, "%s message failed a check %s: %s\n", msgName, c.Code, c.Err) + _, _ = fmt.Fprintf(printer, "%s message failed a check %s: %s\n", msgName, c.Code, c.Err) } } } func askUser(printer io.Writer, q string, def bool) bool { var resp string - fmt.Fprint(printer, q) + _, _ = fmt.Fprint(printer, q) _, _ = fmt.Scanln(&resp) resp = strings.ToLower(resp) if len(resp) == 0 { diff --git a/cli/spcli/actor.go b/cli/spcli/actor.go index 33590de50..d8d355438 100644 --- a/cli/spcli/actor.go +++ b/cli/spcli/actor.go @@ -670,7 +670,7 @@ func ActorProposeChangeWorkerCmd(getActor ActorAddressGetter) *cli.Command { } if !cctx.Bool("really-do-it") { - fmt.Fprintln(cctx.App.Writer, "Pass --really-do-it to actually execute this action") + _, _ = fmt.Fprintln(cctx.App.Writer, "Pass --really-do-it to actually execute this action") return nil } @@ -695,7 +695,7 @@ func ActorProposeChangeWorkerCmd(getActor ActorAddressGetter) *cli.Command { return xerrors.Errorf("mpool push: %w", err) } - fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", smsg.Cid()) + _, _ = fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", smsg.Cid()) // wait for it to get mined into a block wait, err := api.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence) @@ -716,8 +716,8 @@ func ActorProposeChangeWorkerCmd(getActor ActorAddressGetter) *cli.Command { return fmt.Errorf("Proposed worker address change not reflected on chain: expected '%s', found '%s'", na, mi.NewWorker) } - fmt.Fprintf(cctx.App.Writer, "Worker key change to %s successfully sent, change happens at height %d.\n", na, mi.WorkerChangeEpoch) - fmt.Fprintf(cctx.App.Writer, "If you have no active deadlines, call 'confirm-change-worker' at or after height %d to complete.\n", mi.WorkerChangeEpoch) + _, _ = fmt.Fprintf(cctx.App.Writer, "Worker key change to %s successfully sent, change happens at height %d.\n", na, mi.WorkerChangeEpoch) + _, _ = fmt.Fprintf(cctx.App.Writer, "If you have no active deadlines, call 'confirm-change-worker' at or after height %d to complete.\n", mi.WorkerChangeEpoch) return nil }, @@ -942,7 +942,7 @@ func ActorConfirmChangeWorkerCmd(getActor ActorAddressGetter) *cli.Command { // check it executed successfully if wait.Receipt.ExitCode.IsError() { - fmt.Fprintln(cctx.App.Writer, "Worker change failed!") + _, _ = fmt.Fprintln(cctx.App.Writer, "Worker change failed!") return err } @@ -1268,6 +1268,11 @@ var ActorNewMinerCmd = &cli.Command{ Name: "sector-size", Usage: "specify sector size to use for new miner initialisation", }, + &cli.IntFlag{ + Name: "confidence", + Usage: "number of block confirmations to wait for", + Value: int(build.MessageConfidence), + }, }, Action: func(cctx *cli.Context) error { ctx := cctx.Context diff --git a/cli/spcli/sectors.go b/cli/spcli/sectors.go index 95acbcd11..1dad6ada2 100644 --- a/cli/spcli/sectors.go +++ b/cli/spcli/sectors.go @@ -159,6 +159,11 @@ func SectorsStatusCmd(getActorAddress ActorAddressGetter, getOnDiskInfo OnDiskIn return err } + if status == nil { + fmt.Println("Sector status not found on chain") + return nil + } + mid, err := address.IDFromAddress(maddr) if err != nil { return err @@ -261,12 +266,14 @@ func SectorsStatusCmd(getActorAddress ActorAddressGetter, getOnDiskInfo OnDiskIn } if cctx.Bool("log") { - fmt.Printf("--------\nEvent Log:\n") + if getOnDiskInfo != nil { + fmt.Printf("--------\nEvent Log:\n") - for i, l := range status.Log { - fmt.Printf("%d.\t%s:\t[%s]\t%s\n", i, time.Unix(int64(l.Timestamp), 0), l.Kind, l.Message) - if l.Trace != "" { - fmt.Printf("\t%s\n", l.Trace) + for i, l := range status.Log { + fmt.Printf("%d.\t%s:\t[%s]\t%s\n", i, time.Unix(int64(l.Timestamp), 0), l.Kind, l.Message) + if l.Trace != "" { + fmt.Printf("\t%s\n", l.Trace) + } } } } diff --git a/cli/util/api.go b/cli/util/api.go index 7940f67c6..a734b2337 100644 --- a/cli/util/api.go +++ b/cli/util/api.go @@ -456,27 +456,6 @@ func GetWorkerAPI(ctx *cli.Context) (api.Worker, jsonrpc.ClientCloser, error) { return client.NewWorkerRPCV0(ctx.Context, addr, headers) } -func GetMarketsAPI(ctx *cli.Context) (api.StorageMiner, jsonrpc.ClientCloser, error) { - // to support lotus-miner cli tests. - if tn, ok := ctx.App.Metadata["testnode-storage"]; ok { - return tn.(api.StorageMiner), func() {}, nil - } - - addr, headers, err := GetRawAPI(ctx, repo.Markets, "v0") - if err != nil { - return nil, nil, err - } - - if IsVeryVerbose { - _, _ = fmt.Fprintln(ctx.App.Writer, "using markets API v0 endpoint:", addr) - } - - // the markets node is a specialised miner's node, supporting only the - // markets API, which is a subset of the miner API. All non-markets - // operations will error out with "unsupported". - return client.NewStorageMinerRPCV0(ctx.Context, addr, headers) -} - func GetGatewayAPI(ctx *cli.Context) (api.Gateway, jsonrpc.ClientCloser, error) { addr, headers, err := GetRawAPI(ctx, repo.FullNode, "v1") if err != nil { diff --git a/cli/util/retrieval.go b/cli/util/retrieval.go deleted file mode 100644 index ac34fcf3a..000000000 --- a/cli/util/retrieval.go +++ /dev/null @@ -1,77 +0,0 @@ -package cliutil - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "path" - - "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr/net" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/api" -) - -func ApiAddrToUrl(apiAddr string) (*url.URL, error) { - ma, err := multiaddr.NewMultiaddr(apiAddr) - if err == nil { - _, addr, err := manet.DialArgs(ma) - if err != nil { - return nil, err - } - // todo: make cliutil helpers for this - apiAddr = "http://" + addr - } - aa, err := url.Parse(apiAddr) - if err != nil { - return nil, xerrors.Errorf("parsing api address: %w", err) - } - switch aa.Scheme { - case "ws": - aa.Scheme = "http" - case "wss": - aa.Scheme = "https" - } - - return aa, nil -} - -func ClientExportStream(apiAddr string, apiAuth http.Header, eref api.ExportRef, car bool) (io.ReadCloser, error) { - rj, err := json.Marshal(eref) - if err != nil { - return nil, xerrors.Errorf("marshaling export ref: %w", err) - } - - aa, err := ApiAddrToUrl(apiAddr) - if err != nil { - return nil, err - } - - aa.Path = path.Join(aa.Path, "rest/v0/export") - req, err := http.NewRequest("GET", fmt.Sprintf("%s?car=%t&export=%s", aa, car, url.QueryEscape(string(rj))), nil) - if err != nil { - return nil, err - } - - req.Header = apiAuth - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - - if resp.StatusCode != http.StatusOK { - em, err := io.ReadAll(resp.Body) - if err != nil { - return nil, xerrors.Errorf("reading error body: %w", err) - } - - resp.Body.Close() // nolint - return nil, xerrors.Errorf("getting root car: http %d: %s", resp.StatusCode, string(em)) - } - - return resp.Body, nil -} diff --git a/cmd/curio/cli.go b/cmd/curio/cli.go deleted file mode 100644 index 6c9cb7ec6..000000000 --- a/cmd/curio/cli.go +++ /dev/null @@ -1,249 +0,0 @@ -package main - -import ( - "bufio" - "context" - "encoding/base64" - "errors" - "fmt" - "net" - "os" - "time" - - "github.com/BurntSushi/toml" - "github.com/gbrlsnchs/jwt/v3" - manet "github.com/multiformats/go-multiaddr/net" - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-jsonrpc/auth" - - "github.com/filecoin-project/lotus/api" - lcli "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/cmd/curio/deps" - "github.com/filecoin-project/lotus/cmd/curio/rpc" -) - -const providerEnvVar = "CURIO_API_INFO" - -var cliCmd = &cli.Command{ - Name: "cli", - Usage: "Execute cli commands", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "machine", - Usage: "machine host:port (curio run --listen address)", - }, - }, - Before: func(cctx *cli.Context) error { - if os.Getenv(providerEnvVar) != "" { - // set already - return nil - } - if os.Getenv("LOTUS_DOCS_GENERATION") == "1" { - return nil - } - - db, err := deps.MakeDB(cctx) - if err != nil { - return err - } - - ctx := lcli.ReqContext(cctx) - - machine := cctx.String("machine") - if machine == "" { - // interactive picker - var machines []struct { - HostAndPort string `db:"host_and_port"` - LastContact time.Time `db:"last_contact"` - } - - err := db.Select(ctx, &machines, "select host_and_port, last_contact from harmony_machines") - if err != nil { - return xerrors.Errorf("getting machine list: %w", err) - } - - now := time.Now() - fmt.Println("Available machines:") - for i, m := range machines { - // A machine is healthy if contacted not longer than 2 minutes ago - healthStatus := "unhealthy" - if now.Sub(m.LastContact) <= 2*time.Minute { - healthStatus = "healthy" - } - fmt.Printf("%d. %s %s\n", i+1, m.HostAndPort, healthStatus) - } - - fmt.Print("Select: ") - reader := bufio.NewReader(os.Stdin) - input, err := reader.ReadString('\n') - if err != nil { - return xerrors.Errorf("reading selection: %w", err) - } - - var selection int - _, err = fmt.Sscanf(input, "%d", &selection) - if err != nil { - return xerrors.Errorf("parsing selection: %w", err) - } - - if selection < 1 || selection > len(machines) { - return xerrors.New("invalid selection") - } - - machine = machines[selection-1].HostAndPort - } - - var apiKeys []string - { - var dbconfigs []struct { - Config string `db:"config"` - Title string `db:"title"` - } - - err := db.Select(ctx, &dbconfigs, "select config from harmony_config") - if err != nil { - return xerrors.Errorf("getting configs: %w", err) - } - - var seen = make(map[string]struct{}) - - for _, config := range dbconfigs { - var layer struct { - Apis struct { - StorageRPCSecret string - } - } - - if _, err := toml.Decode(config.Config, &layer); err != nil { - return xerrors.Errorf("decode config layer %s: %w", config.Title, err) - } - - if layer.Apis.StorageRPCSecret != "" { - if _, ok := seen[layer.Apis.StorageRPCSecret]; ok { - continue - } - seen[layer.Apis.StorageRPCSecret] = struct{}{} - apiKeys = append(apiKeys, layer.Apis.StorageRPCSecret) - } - } - } - - if len(apiKeys) == 0 { - return xerrors.New("no api keys found in the database") - } - if len(apiKeys) > 1 { - return xerrors.Errorf("multiple api keys found in the database, not supported yet") - } - - var apiToken []byte - { - type jwtPayload struct { - Allow []auth.Permission - } - - p := jwtPayload{ - Allow: api.AllPermissions, - } - - sk, err := base64.StdEncoding.DecodeString(apiKeys[0]) - if err != nil { - return xerrors.Errorf("decode secret: %w", err) - } - - apiToken, err = jwt.Sign(&p, jwt.NewHS256(sk)) - if err != nil { - return xerrors.Errorf("signing token: %w", err) - } - } - - { - - laddr, err := net.ResolveTCPAddr("tcp", machine) - if err != nil { - return xerrors.Errorf("net resolve: %w", err) - } - - if len(laddr.IP) == 0 { - // set localhost - laddr.IP = net.IPv4(127, 0, 0, 1) - } - - ma, err := manet.FromNetAddr(laddr) - if err != nil { - return xerrors.Errorf("net from addr (%v): %w", laddr, err) - } - - token := fmt.Sprintf("%s:%s", string(apiToken), ma) - if err := os.Setenv(providerEnvVar, token); err != nil { - return xerrors.Errorf("setting env var: %w", err) - } - } - - { - api, closer, err := rpc.GetCurioAPI(cctx) - if err != nil { - return err - } - defer closer() - - v, err := api.Version(ctx) - if err != nil { - return xerrors.Errorf("querying version: %w", err) - } - - fmt.Println("remote node version:", v.String()) - } - - return nil - }, - Subcommands: []*cli.Command{ - storageCmd, - logCmd, - waitApiCmd, - }, -} - -var waitApiCmd = &cli.Command{ - Name: "wait-api", - Usage: "Wait for Curio api to come online", - Flags: []cli.Flag{ - &cli.DurationFlag{ - Name: "timeout", - Usage: "duration to wait till fail", - Value: time.Second * 30, - }, - }, - Action: func(cctx *cli.Context) error { - ctx := lcli.ReqContext(cctx) - ctx, cancel := context.WithTimeout(ctx, cctx.Duration("timeout")) - defer cancel() - for { - if ctx.Err() != nil { - break - } - - api, closer, err := rpc.GetCurioAPI(cctx) - if err != nil { - fmt.Printf("Not online yet... (%s)\n", err) - time.Sleep(time.Second) - continue - } - defer closer() - - _, err = api.Version(ctx) - if err != nil { - return err - } - - return nil - } - - if errors.Is(ctx.Err(), context.DeadlineExceeded) { - return fmt.Errorf("timed out waiting for api to come online") - } - - return ctx.Err() - }, -} diff --git a/cmd/curio/config.go b/cmd/curio/config.go deleted file mode 100644 index 16b7d89c3..000000000 --- a/cmd/curio/config.go +++ /dev/null @@ -1,440 +0,0 @@ -package main - -import ( - "context" - "errors" - "fmt" - "io" - "os" - "os/exec" - "path" - "strings" - - "github.com/BurntSushi/toml" - "github.com/fatih/color" - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/cmd/curio/deps" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/node/config" -) - -var configCmd = &cli.Command{ - Name: "config", - Usage: "Manage node config by layers. The layer 'base' will always be applied at Curio start-up.", - Subcommands: []*cli.Command{ - configDefaultCmd, - configSetCmd, - configGetCmd, - configListCmd, - configViewCmd, - configRmCmd, - configEditCmd, - configNewCmd, - }, -} - -var configDefaultCmd = &cli.Command{ - Name: "default", - Aliases: []string{"defaults"}, - Usage: "Print default node config", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "no-comment", - Usage: "don't comment default values", - }, - }, - Action: func(cctx *cli.Context) error { - comment := !cctx.Bool("no-comment") - cfg, err := deps.GetDefaultConfig(comment) - if err != nil { - return err - } - fmt.Print(cfg) - - return nil - }, -} - -var configSetCmd = &cli.Command{ - Name: "set", - Aliases: []string{"add", "update", "create"}, - Usage: "Set a config layer or the base by providing a filename or stdin.", - ArgsUsage: "a layer's file name", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "title", - Usage: "title of the config layer (req'd for stdin)", - }, - }, - Action: func(cctx *cli.Context) error { - args := cctx.Args() - - db, err := deps.MakeDB(cctx) - if err != nil { - return err - } - - name := cctx.String("title") - var stream io.Reader = os.Stdin - if args.Len() != 1 { - if cctx.String("title") == "" { - return errors.New("must have a title for stdin, or a file name") - } - } else { - stream, err = os.Open(args.First()) - if err != nil { - return fmt.Errorf("cannot open file %s: %w", args.First(), err) - } - if name == "" { - name = strings.Split(path.Base(args.First()), ".")[0] - } - } - bytes, err := io.ReadAll(stream) - if err != nil { - return fmt.Errorf("cannot read stream/file %w", err) - } - - curioConfig := config.DefaultCurioConfig() // ensure it's toml - _, err = deps.LoadConfigWithUpgrades(string(bytes), curioConfig) - if err != nil { - return fmt.Errorf("cannot decode file: %w", err) - } - _ = curioConfig - - err = setConfig(db, name, string(bytes)) - - if err != nil { - return fmt.Errorf("unable to save config layer: %w", err) - } - - fmt.Println("Layer " + name + " created/updated") - return nil - }, -} - -func setConfig(db *harmonydb.DB, name, config string) error { - _, err := db.Exec(context.Background(), - `INSERT INTO harmony_config (title, config) VALUES ($1, $2) - ON CONFLICT (title) DO UPDATE SET config = excluded.config`, name, config) - return err -} - -var configGetCmd = &cli.Command{ - Name: "get", - Aliases: []string{"cat", "show"}, - Usage: "Get a config layer by name. You may want to pipe the output to a file, or use 'less'", - ArgsUsage: "layer name", - Action: func(cctx *cli.Context) error { - args := cctx.Args() - if args.Len() != 1 { - return fmt.Errorf("want 1 layer arg, got %d", args.Len()) - } - db, err := deps.MakeDB(cctx) - if err != nil { - return err - } - - cfg, err := getConfig(db, args.First()) - if err != nil { - return err - } - fmt.Println(cfg) - - return nil - }, -} - -func getConfig(db *harmonydb.DB, layer string) (string, error) { - var cfg string - err := db.QueryRow(context.Background(), `SELECT config FROM harmony_config WHERE title=$1`, layer).Scan(&cfg) - if err != nil { - return "", err - } - return cfg, nil -} - -var configListCmd = &cli.Command{ - Name: "list", - Aliases: []string{"ls"}, - Usage: "List config layers present in the DB.", - Flags: []cli.Flag{}, - Action: func(cctx *cli.Context) error { - db, err := deps.MakeDB(cctx) - if err != nil { - return err - } - var res []string - err = db.Select(context.Background(), &res, `SELECT title FROM harmony_config ORDER BY title`) - if err != nil { - return fmt.Errorf("unable to read from db: %w", err) - } - for _, r := range res { - fmt.Println(r) - } - - return nil - }, -} - -var configRmCmd = &cli.Command{ - Name: "remove", - Aliases: []string{"rm", "del", "delete"}, - Usage: "Remove a named config layer.", - Flags: []cli.Flag{}, - Action: func(cctx *cli.Context) error { - args := cctx.Args() - if args.Len() != 1 { - return errors.New("must have exactly 1 arg for the layer name") - } - db, err := deps.MakeDB(cctx) - if err != nil { - return err - } - ct, err := db.Exec(context.Background(), `DELETE FROM harmony_config WHERE title=$1`, args.First()) - if err != nil { - return fmt.Errorf("unable to read from db: %w", err) - } - if ct == 0 { - return fmt.Errorf("no layer named %s", args.First()) - } - - return nil - }, -} -var configViewCmd = &cli.Command{ - Name: "interpret", - Aliases: []string{"view", "stacked", "stack"}, - Usage: "Interpret stacked config layers by this version of curio, with system-generated comments.", - ArgsUsage: "a list of layers to be interpreted as the final config", - Flags: []cli.Flag{ - &cli.StringSliceFlag{ - Name: "layers", - Usage: "comma or space separated list of layers to be interpreted (base is always applied)", - Required: true, - }, - }, - Action: func(cctx *cli.Context) error { - db, err := deps.MakeDB(cctx) - if err != nil { - return err - } - curioConfig, err := deps.GetConfig(cctx, db) - if err != nil { - return err - } - cb, err := config.ConfigUpdate(curioConfig, config.DefaultCurioConfig(), config.Commented(true), config.DefaultKeepUncommented(), config.NoEnv()) - if err != nil { - return xerrors.Errorf("cannot interpret config: %w", err) - } - fmt.Println(string(cb)) - return nil - }, -} - -var configEditCmd = &cli.Command{ - Name: "edit", - Usage: "edit a config layer", - ArgsUsage: "[layer name]", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "editor", - Usage: "editor to use", - Value: "vim", - EnvVars: []string{"EDITOR"}, - }, - &cli.StringFlag{ - Name: "source", - Usage: "source config layer", - DefaultText: "", - }, - &cli.BoolFlag{ - Name: "allow-overwrite", - Usage: "allow overwrite of existing layer if source is a different layer", - }, - &cli.BoolFlag{ - Name: "no-source-diff", - Usage: "save the whole config into the layer, not just the diff", - }, - &cli.BoolFlag{ - Name: "no-interpret-source", - Usage: "do not interpret source layer", - DefaultText: "true if --source is set", - }, - }, - Action: func(cctx *cli.Context) error { - layer := cctx.Args().First() - if layer == "" { - return errors.New("layer name is required") - } - - source := layer - if cctx.IsSet("source") { - source = cctx.String("source") - - if source == layer && !cctx.Bool("allow-owerwrite") { - return errors.New("source and target layers are the same") - } - } - - db, err := deps.MakeDB(cctx) - if err != nil { - return err - } - - sourceConfig, err := getConfig(db, source) - if err != nil { - return xerrors.Errorf("getting source config: %w", err) - } - - if cctx.IsSet("source") && source != layer && !cctx.Bool("no-interpret-source") { - lp := config.DefaultCurioConfig() - if _, err := toml.Decode(sourceConfig, lp); err != nil { - return xerrors.Errorf("parsing source config: %w", err) - } - - cb, err := config.ConfigUpdate(lp, config.DefaultCurioConfig(), config.Commented(true), config.DefaultKeepUncommented(), config.NoEnv()) - if err != nil { - return xerrors.Errorf("interpreting source config: %w", err) - } - sourceConfig = string(cb) - } - - editor := cctx.String("editor") - newConfig, err := edit(editor, sourceConfig) - if err != nil { - return xerrors.Errorf("editing config: %w", err) - } - - toWrite := newConfig - - if cctx.IsSet("source") && !cctx.Bool("no-source-diff") { - updated, err := diff(sourceConfig, newConfig) - if err != nil { - return xerrors.Errorf("computing diff: %w", err) - } - - { - fmt.Printf("%s will write changes as the layer because %s is not set\n", color.YellowString(">"), color.GreenString("--no-source-diff")) - fmt.Println(updated) - fmt.Printf("%s Confirm [y]: ", color.YellowString(">")) - - for { - var confirmBuf [16]byte - n, err := os.Stdin.Read(confirmBuf[:]) - if err != nil { - return xerrors.Errorf("reading confirmation: %w", err) - } - confirm := strings.TrimSpace(string(confirmBuf[:n])) - - if confirm == "" { - confirm = "y" - } - - if confirm[:1] == "y" { - break - } - if confirm[:1] == "n" { - return nil - } - - fmt.Printf("%s Confirm [y]:\n", color.YellowString(">")) - } - } - - toWrite = updated - } - - fmt.Printf("%s Writing config for layer %s\n", color.YellowString(">"), color.GreenString(layer)) - - return setConfig(db, layer, toWrite) - }, -} - -func diff(sourceConf, newConf string) (string, error) { - lpSrc := config.DefaultCurioConfig() - lpNew := config.DefaultCurioConfig() - - _, err := toml.Decode(sourceConf, lpSrc) - if err != nil { - return "", xerrors.Errorf("decoding source config: %w", err) - } - - _, err = toml.Decode(newConf, lpNew) - if err != nil { - return "", xerrors.Errorf("decoding new config: %w", err) - } - - cb, err := config.ConfigUpdate(lpNew, lpSrc, config.Commented(true), config.NoEnv()) - if err != nil { - return "", xerrors.Errorf("interpreting source config: %w", err) - } - - lines := strings.Split(string(cb), "\n") - var outLines []string - var categoryBuf string - - for _, line := range lines { - // drop empty lines - if strings.TrimSpace(line) == "" { - continue - } - // drop lines starting with '#' - if strings.HasPrefix(strings.TrimSpace(line), "#") { - continue - } - // if starting with [, it's a category - if strings.HasPrefix(strings.TrimSpace(line), "[") { - categoryBuf = line - continue - } - - if categoryBuf != "" { - outLines = append(outLines, categoryBuf) - categoryBuf = "" - } - - outLines = append(outLines, line) - } - - return strings.Join(outLines, "\n"), nil -} - -func edit(editor, cfg string) (string, error) { - file, err := os.CreateTemp("", "curio-config-*.toml") - if err != nil { - return "", err - } - - _, err = file.WriteString(cfg) - if err != nil { - return "", err - } - - filePath := file.Name() - - if err := file.Close(); err != nil { - return "", err - } - - defer func() { - _ = os.Remove(filePath) - }() - - cmd := exec.Command(editor, filePath) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err = cmd.Run() - if err != nil { - return "", err - } - - data, err := os.ReadFile(filePath) - if err != nil { - return "", err - } - - return string(data), err -} diff --git a/cmd/curio/config_new.go b/cmd/curio/config_new.go deleted file mode 100644 index 65549bd69..000000000 --- a/cmd/curio/config_new.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/api" - cliutil "github.com/filecoin-project/lotus/cli/util" - "github.com/filecoin-project/lotus/cmd/curio/deps" - "github.com/filecoin-project/lotus/node/repo" -) - -var configNewCmd = &cli.Command{ - Name: "new-cluster", - Usage: "Create new configuration for a new cluster", - ArgsUsage: "[SP actor address...]", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "repo", - EnvVars: []string{"LOTUS_PATH"}, - Hidden: true, - Value: "~/.lotus", - }, - }, - Action: func(cctx *cli.Context) error { - if cctx.Args().Len() < 1 { - return xerrors.New("must specify at least one SP actor address. Use 'lotus-shed miner create' or use 'curio guided-setup'") - } - - ctx := cctx.Context - - db, err := deps.MakeDB(cctx) - if err != nil { - return err - } - - full, closer, err := cliutil.GetFullNodeAPIV1(cctx) - if err != nil { - return xerrors.Errorf("connecting to full node: %w", err) - } - defer closer() - - ainfo, err := cliutil.GetAPIInfo(cctx, repo.FullNode) - if err != nil { - return xerrors.Errorf("could not get API info for FullNode: %w", err) - } - - token, err := full.AuthNew(ctx, api.AllPermissions) - if err != nil { - return err - } - - return deps.CreateMinerConfig(ctx, full, db, cctx.Args().Slice(), fmt.Sprintf("%s:%s", string(token), ainfo.Addr)) - }, -} diff --git a/cmd/curio/config_test.go b/cmd/curio/config_test.go deleted file mode 100644 index 8043017d5..000000000 --- a/cmd/curio/config_test.go +++ /dev/null @@ -1,438 +0,0 @@ -package main - -import ( - "reflect" - "testing" - "time" - - "github.com/invopop/jsonschema" - "github.com/samber/lo" - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/lotus/cmd/curio/deps" - "github.com/filecoin-project/lotus/node/config" -) - -var baseText = ` -[Subsystems] - # EnableWindowPost enables window post to be executed on this curio instance. Each machine in the cluster - # with WindowPoSt enabled will also participate in the window post scheduler. It is possible to have multiple - # machines with WindowPoSt enabled which will provide redundancy, and in case of multiple partitions per deadline, - # will allow for parallel processing of partitions. - # - # It is possible to have instances handling both WindowPoSt and WinningPoSt, which can provide redundancy without - # the need for additional machines. In setups like this it is generally recommended to run - # partitionsPerDeadline+1 machines. - # - # type: bool - #EnableWindowPost = false - - # type: int - #WindowPostMaxTasks = 0 - - # EnableWinningPost enables winning post to be executed on this curio instance. - # Each machine in the cluster with WinningPoSt enabled will also participate in the winning post scheduler. - # It is possible to mix machines with WindowPoSt and WinningPoSt enabled, for details see the EnableWindowPost - # documentation. - # - # type: bool - #EnableWinningPost = false - - # type: int - #WinningPostMaxTasks = 0 - - # EnableParkPiece enables the "piece parking" task to run on this node. This task is responsible for fetching - # pieces from the network and storing them in the storage subsystem until sectors are sealed. This task is - # only applicable when integrating with boost, and should be enabled on nodes which will hold deal data - # from boost until sectors containing the related pieces have the TreeD/TreeR constructed. - # Note that future Curio implementations will have a separate task type for fetching pieces from the internet. - # - # type: bool - #EnableParkPiece = false - - # type: int - #ParkPieceMaxTasks = 0 - - # EnableSealSDR enables SDR tasks to run. SDR is the long sequential computation - # creating 11 layer files in sector cache directory. - # - # SDR is the first task in the sealing pipeline. It's inputs are just the hash of the - # unsealed data (CommD), sector number, miner id, and the seal proof type. - # It's outputs are the 11 layer files in the sector cache directory. - # - # In lotus-miner this was run as part of PreCommit1. - # - # type: bool - #EnableSealSDR = false - - # The maximum amount of SDR tasks that can run simultaneously. Note that the maximum number of tasks will - # also be bounded by resources available on the machine. - # - # type: int - #SealSDRMaxTasks = 0 - - # EnableSealSDRTrees enables the SDR pipeline tree-building task to run. - # This task handles encoding of unsealed data into last sdr layer and building - # of TreeR, TreeC and TreeD. - # - # This task runs after SDR - # TreeD is first computed with optional input of unsealed data - # TreeR is computed from replica, which is first computed as field - # addition of the last SDR layer and the bottom layer of TreeD (which is the unsealed data) - # TreeC is computed from the 11 SDR layers - # The 3 trees will later be used to compute the PoRep proof. - # - # In case of SyntheticPoRep challenges for PoRep will be pre-generated at this step, and trees and layers - # will be dropped. SyntheticPoRep works by pre-generating a very large set of challenges (~30GiB on disk) - # then using a small subset of them for the actual PoRep computation. This allows for significant scratch space - # saving between PreCommit and PoRep generation at the expense of more computation (generating challenges in this step) - # - # In lotus-miner this was run as part of PreCommit2 (TreeD was run in PreCommit1). - # Note that nodes with SDRTrees enabled will also answer to Finalize tasks, - # which just remove unneeded tree data after PoRep is computed. - # - # type: bool - #EnableSealSDRTrees = false - - # The maximum amount of SealSDRTrees tasks that can run simultaneously. Note that the maximum number of tasks will - # also be bounded by resources available on the machine. - # - # type: int - #SealSDRTreesMaxTasks = 0 - - # FinalizeMaxTasks is the maximum amount of finalize tasks that can run simultaneously. - # The finalize task is enabled on all machines which also handle SDRTrees tasks. Finalize ALWAYS runs on whichever - # machine holds sector cache files, as it removes unneeded tree data after PoRep is computed. - # Finalize will run in parallel with the SubmitCommitMsg task. - # - # type: int - #FinalizeMaxTasks = 0 - - # EnableSendPrecommitMsg enables the sending of precommit messages to the chain - # from this curio instance. - # This runs after SDRTrees and uses the output CommD / CommR (roots of TreeD / TreeR) for the message - # - # type: bool - #EnableSendPrecommitMsg = false - - # EnablePoRepProof enables the computation of the porep proof - # - # This task runs after interactive-porep seed becomes available, which happens 150 epochs (75min) after the - # precommit message lands on chain. This task should run on a machine with a GPU. Vanilla PoRep proofs are - # requested from the machine which holds sector cache files which most likely is the machine which ran the SDRTrees - # task. - # - # In lotus-miner this was Commit1 / Commit2 - # - # type: bool - #EnablePoRepProof = false - - # The maximum amount of PoRepProof tasks that can run simultaneously. Note that the maximum number of tasks will - # also be bounded by resources available on the machine. - # - # type: int - #PoRepProofMaxTasks = 0 - - # EnableSendCommitMsg enables the sending of commit messages to the chain - # from this curio instance. - # - # type: bool - #EnableSendCommitMsg = false - - # EnableMoveStorage enables the move-into-long-term-storage task to run on this curio instance. - # This tasks should only be enabled on nodes with long-term storage. - # - # The MoveStorage task is the last task in the sealing pipeline. It moves the sealed sector data from the - # SDRTrees machine into long-term storage. This task runs after the Finalize task. - # - # type: bool - #EnableMoveStorage = false - - # The maximum amount of MoveStorage tasks that can run simultaneously. Note that the maximum number of tasks will - # also be bounded by resources available on the machine. It is recommended that this value is set to a number which - # uses all available network (or disk) bandwidth on the machine without causing bottlenecks. - # - # type: int - #MoveStorageMaxTasks = 0 - - # EnableWebGui enables the web GUI on this curio instance. The UI has minimal local overhead, but it should - # only need to be run on a single machine in the cluster. - # - # type: bool - #EnableWebGui = false - - # The address that should listen for Web GUI requests. - # - # type: string - #GuiAddress = ":4701" - - -[Fees] - # type: types.FIL - #DefaultMaxFee = "0.07 FIL" - - # type: types.FIL - #MaxPreCommitGasFee = "0.025 FIL" - - # type: types.FIL - #MaxCommitGasFee = "0.05 FIL" - - # type: types.FIL - #MaxTerminateGasFee = "0.5 FIL" - - # WindowPoSt is a high-value operation, so the default fee should be high. - # - # type: types.FIL - #MaxWindowPoStGasFee = "5 FIL" - - # type: types.FIL - #MaxPublishDealsFee = "0.05 FIL" - - [Fees.MaxPreCommitBatchGasFee] - # type: types.FIL - #Base = "0 FIL" - - # type: types.FIL - #PerSector = "0.02 FIL" - - [Fees.MaxCommitBatchGasFee] - # type: types.FIL - #Base = "0 FIL" - - # type: types.FIL - #PerSector = "0.03 FIL" - -[[Addresses]] - #PreCommitControl = [] - - #CommitControl = [] - - #TerminateControl = [] - - #DisableOwnerFallback = false - - #DisableWorkerFallback = false - - MinerAddresses = ["t01013"] - - -[[Addresses]] - #PreCommitControl = [] - - #CommitControl = [] - - #TerminateControl = [] - - #DisableOwnerFallback = false - - #DisableWorkerFallback = false - - #MinerAddresses = [] - - -[[Addresses]] - #PreCommitControl = [] - - #CommitControl = [] - - #TerminateControl = [] - - #DisableOwnerFallback = false - - #DisableWorkerFallback = false - - MinerAddresses = ["t01006"] - - -[Proving] - # Maximum number of sector checks to run in parallel. (0 = unlimited) - # - # WARNING: Setting this value too high may make the node crash by running out of stack - # WARNING: Setting this value too low may make sector challenge reading much slower, resulting in failed PoSt due - # to late submission. - # - # After changing this option, confirm that the new value works in your setup by invoking - # 'lotus-miner proving compute window-post 0' - # - # type: int - #ParallelCheckLimit = 32 - - # Maximum amount of time a proving pre-check can take for a sector. If the check times out the sector will be skipped - # - # WARNING: Setting this value too low risks in sectors being skipped even though they are accessible, just reading the - # test challenge took longer than this timeout - # WARNING: Setting this value too high risks missing PoSt deadline in case IO operations related to this sector are - # blocked (e.g. in case of disconnected NFS mount) - # - # type: Duration - #SingleCheckTimeout = "10m0s" - - # Maximum amount of time a proving pre-check can take for an entire partition. If the check times out, sectors in - # the partition which didn't get checked on time will be skipped - # - # WARNING: Setting this value too low risks in sectors being skipped even though they are accessible, just reading the - # test challenge took longer than this timeout - # WARNING: Setting this value too high risks missing PoSt deadline in case IO operations related to this partition are - # blocked or slow - # - # type: Duration - #PartitionCheckTimeout = "20m0s" - - # Disable Window PoSt computation on the lotus-miner process even if no window PoSt workers are present. - # - # WARNING: If no windowPoSt workers are connected, window PoSt WILL FAIL resulting in faulty sectors which will need - # to be recovered. Before enabling this option, make sure your PoSt workers work correctly. - # - # After changing this option, confirm that the new value works in your setup by invoking - # 'lotus-miner proving compute window-post 0' - # - # type: bool - #DisableBuiltinWindowPoSt = false - - # Disable Winning PoSt computation on the lotus-miner process even if no winning PoSt workers are present. - # - # WARNING: If no WinningPoSt workers are connected, Winning PoSt WILL FAIL resulting in lost block rewards. - # Before enabling this option, make sure your PoSt workers work correctly. - # - # type: bool - #DisableBuiltinWinningPoSt = false - - # Disable WindowPoSt provable sector readability checks. - # - # In normal operation, when preparing to compute WindowPoSt, lotus-miner will perform a round of reading challenges - # from all sectors to confirm that those sectors can be proven. Challenges read in this process are discarded, as - # we're only interested in checking that sector data can be read. - # - # When using builtin proof computation (no PoSt workers, and DisableBuiltinWindowPoSt is set to false), this process - # can save a lot of time and compute resources in the case that some sectors are not readable - this is caused by - # the builtin logic not skipping snark computation when some sectors need to be skipped. - # - # When using PoSt workers, this process is mostly redundant, with PoSt workers challenges will be read once, and - # if challenges for some sectors aren't readable, those sectors will just get skipped. - # - # Disabling sector pre-checks will slightly reduce IO load when proving sectors, possibly resulting in shorter - # time to produce window PoSt. In setups with good IO capabilities the effect of this option on proving time should - # be negligible. - # - # NOTE: It likely is a bad idea to disable sector pre-checks in setups with no PoSt workers. - # - # NOTE: Even when this option is enabled, recovering sectors will be checked before recovery declaration message is - # sent to the chain - # - # After changing this option, confirm that the new value works in your setup by invoking - # 'lotus-miner proving compute window-post 0' - # - # type: bool - #DisableWDPoStPreChecks = false - - # Maximum number of partitions to prove in a single SubmitWindowPoSt messace. 0 = network limit (3 in nv21) - # - # A single partition may contain up to 2349 32GiB sectors, or 2300 64GiB sectors. - # // - # Note that setting this value lower may result in less efficient gas use - more messages will be sent, - # to prove each deadline, resulting in more total gas use (but each message will have lower gas limit) - # - # Setting this value above the network limit has no effect - # - # type: int - #MaxPartitionsPerPoStMessage = 0 - - # In some cases when submitting DeclareFaultsRecovered messages, - # there may be too many recoveries to fit in a BlockGasLimit. - # In those cases it may be necessary to set this value to something low (eg 1); - # Note that setting this value lower may result in less efficient gas use - more messages will be sent than needed, - # resulting in more total gas use (but each message will have lower gas limit) - # - # type: int - #MaxPartitionsPerRecoveryMessage = 0 - - # Enable single partition per PoSt Message for partitions containing recovery sectors - # - # In cases when submitting PoSt messages which contain recovering sectors, the default network limit may still be - # too high to fit in the block gas limit. In those cases, it becomes useful to only house the single partition - # with recovering sectors in the post message - # - # Note that setting this value lower may result in less efficient gas use - more messages will be sent, - # to prove each deadline, resulting in more total gas use (but each message will have lower gas limit) - # - # type: bool - #SingleRecoveringPartitionPerPostMessage = false - - -[Journal] - # Events of the form: "system1:event1,system1:event2[,...]" - # - # type: string - #DisabledEvents = "" - - -[Apis] - # ChainApiInfo is the API endpoint for the Lotus daemon. - # - # type: []string - ChainApiInfo = ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJyZWFkIiwid3JpdGUiLCJzaWduIiwiYWRtaW4iXX0.T_jmG4DTs9Zjd7rr78862lT7D2U63uz-zqcUKHwcqaU:/dns/localhost/tcp/1234/http"] - - # RPC Secret for the storage subsystem. - # If integrating with lotus-miner this must match the value from - # cat ~/.lotusminer/keystore/MF2XI2BNNJ3XILLQOJUXMYLUMU | jq -r .PrivateKey - # - # type: string - StorageRPCSecret = "HxHe8YLHiY0LjHVw/WT/4XQkPGgRyCEYk+xiFi0Ob0o=" - -` - -func TestConfig(t *testing.T) { - baseCfg := config.DefaultCurioConfig() - - addr1 := config.CurioAddresses{ - PreCommitControl: []string{}, - CommitControl: []string{}, - TerminateControl: []string{"t3qroiebizgkz7pvj26reg5r5mqiftrt5hjdske2jzjmlacqr2qj7ytjncreih2mvujxoypwpfusmwpipvxncq"}, - DisableOwnerFallback: false, - DisableWorkerFallback: false, - MinerAddresses: []string{"t01000"}, - } - - addr2 := config.CurioAddresses{ - MinerAddresses: []string{"t01001"}, - } - - _, err := deps.LoadConfigWithUpgrades(baseText, baseCfg) - require.NoError(t, err) - - baseCfg.Addresses = append(baseCfg.Addresses, addr1) - baseCfg.Addresses = lo.Filter(baseCfg.Addresses, func(a config.CurioAddresses, _ int) bool { - return len(a.MinerAddresses) > 0 - }) - - _, err = config.ConfigUpdate(baseCfg, config.DefaultCurioConfig(), config.Commented(true), config.DefaultKeepUncommented(), config.NoEnv()) - require.NoError(t, err) - - baseCfg.Addresses = append(baseCfg.Addresses, addr2) - baseCfg.Addresses = lo.Filter(baseCfg.Addresses, func(a config.CurioAddresses, _ int) bool { - return len(a.MinerAddresses) > 0 - }) - - _, err = config.ConfigUpdate(baseCfg, config.DefaultCurioConfig(), config.Commented(true), config.DefaultKeepUncommented(), config.NoEnv()) - require.NoError(t, err) - -} - -func TestCustomConfigDurationJson(t *testing.T) { - ref := new(jsonschema.Reflector) - ref.Mapper = func(i reflect.Type) *jsonschema.Schema { - if i == reflect.TypeOf(config.Duration(time.Second)) { - return &jsonschema.Schema{ - Type: "string", - Format: "duration", - } - } - return nil - } - - sch := ref.Reflect(config.CurioConfig{}) - definitions := sch.Definitions["CurioProvingConfig"] - prop, ok := definitions.Properties.Get("SingleCheckTimeout") - require.True(t, ok) - require.Equal(t, prop.Type, "string") -} diff --git a/cmd/curio/deps/apiinfo.go b/cmd/curio/deps/apiinfo.go deleted file mode 100644 index 0dd96d817..000000000 --- a/cmd/curio/deps/apiinfo.go +++ /dev/null @@ -1,94 +0,0 @@ -package deps - -import ( - "fmt" - "net/http" - - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-jsonrpc" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/client" - "github.com/filecoin-project/lotus/api/v1api" - cliutil "github.com/filecoin-project/lotus/cli/util" -) - -func getFullNodeAPIV1Curio(ctx *cli.Context, ainfoCfg []string, opts ...cliutil.GetFullNodeOption) (v1api.FullNode, jsonrpc.ClientCloser, error) { - if tn, ok := ctx.App.Metadata["testnode-full"]; ok { - return tn.(v1api.FullNode), func() {}, nil - } - - var options cliutil.GetFullNodeOptions - for _, opt := range opts { - opt(&options) - } - - var rpcOpts []jsonrpc.Option - if options.EthSubHandler != nil { - rpcOpts = append(rpcOpts, jsonrpc.WithClientHandler("Filecoin", options.EthSubHandler), jsonrpc.WithClientHandlerAlias("eth_subscription", "Filecoin.EthSubscription")) - } - - var httpHeads []httpHead - version := "v1" - { - if len(ainfoCfg) == 0 { - return nil, nil, xerrors.Errorf("could not get API info: none configured. \nConsider getting base.toml with './curio config get base >/tmp/base.toml' \nthen adding \n[APIs] \n ChainApiInfo = [\" result_from lotus auth api-info --perm=admin \"]\n and updating it with './curio config set /tmp/base.toml'") - } - for _, i := range ainfoCfg { - ainfo := cliutil.ParseApiInfo(i) - addr, err := ainfo.DialArgs(version) - if err != nil { - return nil, nil, xerrors.Errorf("could not get DialArgs: %w", err) - } - httpHeads = append(httpHeads, httpHead{addr: addr, header: ainfo.AuthHeader()}) - } - } - - if cliutil.IsVeryVerbose { - _, _ = fmt.Fprintln(ctx.App.Writer, "using full node API v1 endpoint:", httpHeads[0].addr) - } - - var fullNodes []api.FullNode - var closers []jsonrpc.ClientCloser - - for _, head := range httpHeads { - v1api, closer, err := client.NewFullNodeRPCV1(ctx.Context, head.addr, head.header, rpcOpts...) - if err != nil { - log.Warnf("Not able to establish connection to node with addr: %s, Reason: %s", head.addr, err.Error()) - continue - } - fullNodes = append(fullNodes, v1api) - closers = append(closers, closer) - } - - // When running in cluster mode and trying to establish connections to multiple nodes, fail - // if less than 2 lotus nodes are actually running - if len(httpHeads) > 1 && len(fullNodes) < 2 { - return nil, nil, xerrors.Errorf("Not able to establish connection to more than a single node") - } - - finalCloser := func() { - for _, c := range closers { - c() - } - } - - var v1API api.FullNodeStruct - cliutil.FullNodeProxy(fullNodes, &v1API) - - 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, finalCloser, nil -} - -type httpHead struct { - addr string - header http.Header -} diff --git a/cmd/curio/deps/deps.go b/cmd/curio/deps/deps.go deleted file mode 100644 index 2050a1caf..000000000 --- a/cmd/curio/deps/deps.go +++ /dev/null @@ -1,537 +0,0 @@ -// Package deps provides the dependencies for the curio node. -package deps - -import ( - "context" - "crypto/rand" - "database/sql" - "encoding/base64" - "errors" - "fmt" - "io" - "net" - "net/http" - "net/url" - "os" - "path/filepath" - "strings" - - "github.com/BurntSushi/toml" - "github.com/gbrlsnchs/jwt/v3" - ds "github.com/ipfs/go-datastore" - dssync "github.com/ipfs/go-datastore/sync" - logging "github.com/ipfs/go-log/v2" - "github.com/samber/lo" - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-jsonrpc/auth" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-statestore" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/v1api" - "github.com/filecoin-project/lotus/chain/types" - curio "github.com/filecoin-project/lotus/curiosrc" - "github.com/filecoin-project/lotus/curiosrc/multictladdr" - "github.com/filecoin-project/lotus/journal" - "github.com/filecoin-project/lotus/journal/alerting" - "github.com/filecoin-project/lotus/journal/fsjournal" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/node/config" - "github.com/filecoin-project/lotus/node/modules" - "github.com/filecoin-project/lotus/node/modules/dtypes" - "github.com/filecoin-project/lotus/node/repo" - "github.com/filecoin-project/lotus/storage/paths" - "github.com/filecoin-project/lotus/storage/sealer" - "github.com/filecoin-project/lotus/storage/sealer/ffiwrapper" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -var log = logging.Logger("curio/deps") - -func MakeDB(cctx *cli.Context) (*harmonydb.DB, error) { - // #1 CLI opts - fromCLI := func() (*harmonydb.DB, error) { - dbConfig := config.HarmonyDB{ - Username: cctx.String("db-user"), - Password: cctx.String("db-password"), - Hosts: strings.Split(cctx.String("db-host"), ","), - Database: cctx.String("db-name"), - Port: cctx.String("db-port"), - } - return harmonydb.NewFromConfig(dbConfig) - } - - readToml := func(path string) (*harmonydb.DB, error) { - cfg, err := config.FromFile(path) - if err != nil { - return nil, err - } - if c, ok := cfg.(*config.StorageMiner); ok { - return harmonydb.NewFromConfig(c.HarmonyDB) - } - return nil, errors.New("not a miner config") - } - - // #2 Try local miner config - fromMinerEnv := func() (*harmonydb.DB, error) { - v := os.Getenv("LOTUS_MINER_PATH") - if v == "" { - return nil, errors.New("no miner env") - } - return readToml(filepath.Join(v, "config.toml")) - - } - - fromMiner := func() (*harmonydb.DB, error) { - u, err := os.UserHomeDir() - if err != nil { - return nil, err - } - return readToml(filepath.Join(u, ".lotusminer/config.toml")) - } - fromEnv := func() (*harmonydb.DB, error) { - // #3 Try env - u, err := url.Parse(os.Getenv("CURIO_DB")) - if err != nil { - return nil, errors.New("no db connection string found in CURIO_DB env") - } - cfg := config.DefaultStorageMiner().HarmonyDB - if u.User.Username() != "" { - cfg.Username = u.User.Username() - } - if p, ok := u.User.Password(); ok && p != "" { - cfg.Password = p - } - if u.Hostname() != "" { - cfg.Hosts = []string{u.Hostname()} - } - if u.Port() != "" { - cfg.Port = u.Port() - } - if strings.TrimPrefix(u.Path, "/") != "" { - cfg.Database = strings.TrimPrefix(u.Path, "/") - } - - return harmonydb.NewFromConfig(cfg) - } - - for _, f := range []func() (*harmonydb.DB, error){fromCLI, fromMinerEnv, fromMiner, fromEnv} { - db, err := f() - if err != nil { - continue - } - return db, nil - } - log.Error("No db connection string found. User CLI args or env var: set CURIO_DB=postgres://USER:PASSWORD@HOST:PORT/DATABASE") - return fromCLI() //in-case it's not about bad config. -} - -type JwtPayload struct { - Allow []auth.Permission -} - -func StorageAuth(apiKey string) (sealer.StorageAuth, error) { - if apiKey == "" { - return nil, xerrors.Errorf("no api key provided") - } - - rawKey, err := base64.StdEncoding.DecodeString(apiKey) - if err != nil { - return nil, xerrors.Errorf("decoding api key: %w", err) - } - - key := jwt.NewHS256(rawKey) - - p := JwtPayload{ - Allow: []auth.Permission{"admin"}, - } - - token, err := jwt.Sign(&p, key) - if err != nil { - return nil, err - } - - headers := http.Header{} - headers.Add("Authorization", "Bearer "+string(token)) - return sealer.StorageAuth(headers), nil -} - -func GetDeps(ctx context.Context, cctx *cli.Context) (*Deps, error) { - var deps Deps - return &deps, deps.PopulateRemainingDeps(ctx, cctx, true) -} - -type Deps struct { - Layers []string - Cfg *config.CurioConfig // values - DB *harmonydb.DB // has itest capability - Full api.FullNode - Verif storiface.Verifier - LW *sealer.LocalWorker - As *multictladdr.MultiAddressSelector - Maddrs map[dtypes.MinerAddress]bool - ProofTypes map[abi.RegisteredSealProof]bool - Stor *paths.Remote - Si *paths.DBIndex - LocalStore *paths.Local - LocalPaths *paths.BasicLocalStorage - ListenAddr string -} - -const ( - FlagRepoPath = "repo-path" -) - -func (deps *Deps) PopulateRemainingDeps(ctx context.Context, cctx *cli.Context, makeRepo bool) error { - var err error - if makeRepo { - // Open repo - repoPath := cctx.String(FlagRepoPath) - fmt.Println("repopath", repoPath) - r, err := repo.NewFS(repoPath) - if err != nil { - return err - } - - ok, err := r.Exists() - if err != nil { - return err - } - if !ok { - if err := r.Init(repo.Curio); err != nil { - return err - } - } - } - - if deps.Cfg == nil { - deps.DB, err = MakeDB(cctx) - if err != nil { - return err - } - } - if deps.Layers == nil { - deps.Layers = append([]string{"base"}, cctx.StringSlice("layers")...) // Always stack on top of "base" layer - } - - if deps.Cfg == nil { - // The config feeds into task runners & their helpers - deps.Cfg, err = GetConfig(cctx, deps.DB) - if err != nil { - return xerrors.Errorf("populate config: %w", err) - } - } - - log.Debugw("config", "config", deps.Cfg) - - if deps.Verif == nil { - deps.Verif = ffiwrapper.ProofVerifier - } - - if deps.As == nil { - deps.As, err = curio.AddressSelector(deps.Cfg.Addresses)() - if err != nil { - return err - } - } - - if deps.Si == nil { - de, err := journal.ParseDisabledEvents(deps.Cfg.Journal.DisabledEvents) - if err != nil { - return err - } - j, err := fsjournal.OpenFSJournalPath(cctx.String("journal"), de) - if err != nil { - return err - } - go func() { - <-ctx.Done() - _ = j.Close() - }() - - al := alerting.NewAlertingSystem(j) - deps.Si = paths.NewDBIndex(al, deps.DB) - } - - if deps.Full == nil { - var fullCloser func() - cfgApiInfo := deps.Cfg.Apis.ChainApiInfo - if v := os.Getenv("FULLNODE_API_INFO"); v != "" { - cfgApiInfo = []string{v} - } - deps.Full, fullCloser, err = getFullNodeAPIV1Curio(cctx, cfgApiInfo) - if err != nil { - return err - } - - go func() { - <-ctx.Done() - fullCloser() - }() - } - - deps.LocalPaths = &paths.BasicLocalStorage{ - PathToJSON: cctx.String("storage-json"), - } - - if deps.ListenAddr == "" { - listenAddr := cctx.String("listen") - const unspecifiedAddress = "0.0.0.0" - addressSlice := strings.Split(listenAddr, ":") - if ip := net.ParseIP(addressSlice[0]); ip != nil { - if ip.String() == unspecifiedAddress { - rip, err := deps.DB.GetRoutableIP() - if err != nil { - return err - } - deps.ListenAddr = rip + ":" + addressSlice[1] - } - } - } - if deps.LocalStore == nil { - deps.LocalStore, err = paths.NewLocal(ctx, deps.LocalPaths, deps.Si, []string{"http://" + deps.ListenAddr + "/remote"}) - if err != nil { - return err - } - } - - sa, err := StorageAuth(deps.Cfg.Apis.StorageRPCSecret) - if err != nil { - return xerrors.Errorf(`'%w' while parsing the config toml's - [Apis] - StorageRPCSecret=%v -Get it with: jq .PrivateKey ~/.lotus-miner/keystore/MF2XI2BNNJ3XILLQOJUXMYLUMU`, err, deps.Cfg.Apis.StorageRPCSecret) - } - if deps.Stor == nil { - deps.Stor = paths.NewRemote(deps.LocalStore, deps.Si, http.Header(sa), 10, &paths.DefaultPartialFileHandler{}) - } - if deps.LW == nil { - wstates := statestore.New(dssync.MutexWrap(ds.NewMapDatastore())) - - // todo localWorker isn't the abstraction layer we want to use here, we probably want to go straight to ffiwrapper - // maybe with a curio specific abstraction. LocalWorker does persistent call tracking which we probably - // don't need (ehh.. maybe we do, the async callback system may actually work decently well with harmonytask) - deps.LW = sealer.NewLocalWorker(sealer.WorkerConfig{ - MaxParallelChallengeReads: deps.Cfg.Proving.ParallelCheckLimit, - }, deps.Stor, deps.LocalStore, deps.Si, nil, wstates) - } - if deps.Maddrs == nil { - deps.Maddrs = map[dtypes.MinerAddress]bool{} - } - if len(deps.Maddrs) == 0 { - for _, s := range deps.Cfg.Addresses { - for _, s := range s.MinerAddresses { - addr, err := address.NewFromString(s) - if err != nil { - return err - } - deps.Maddrs[dtypes.MinerAddress(addr)] = true - } - } - } - - if deps.ProofTypes == nil { - deps.ProofTypes = map[abi.RegisteredSealProof]bool{} - } - if len(deps.ProofTypes) == 0 { - for maddr := range deps.Maddrs { - spt, err := modules.SealProofType(maddr, deps.Full) - if err != nil { - return err - } - deps.ProofTypes[spt] = true - } - } - - return nil -} - -func LoadConfigWithUpgrades(text string, curioConfigWithDefaults *config.CurioConfig) (toml.MetaData, error) { - // allow migration from old config format that was limited to 1 wallet setup. - newText := strings.Join(lo.Map(strings.Split(text, "\n"), func(line string, _ int) string { - if strings.EqualFold(line, "[addresses]") { - return "[[addresses]]" - } - return line - }), "\n") - meta, err := toml.Decode(newText, &curioConfigWithDefaults) - for i := range curioConfigWithDefaults.Addresses { - if curioConfigWithDefaults.Addresses[i].PreCommitControl == nil { - curioConfigWithDefaults.Addresses[i].PreCommitControl = []string{} - } - if curioConfigWithDefaults.Addresses[i].CommitControl == nil { - curioConfigWithDefaults.Addresses[i].CommitControl = []string{} - } - if curioConfigWithDefaults.Addresses[i].TerminateControl == nil { - curioConfigWithDefaults.Addresses[i].TerminateControl = []string{} - } - } - return meta, err -} -func GetConfig(cctx *cli.Context, db *harmonydb.DB) (*config.CurioConfig, error) { - curioConfig := config.DefaultCurioConfig() - have := []string{} - layers := append([]string{"base"}, cctx.StringSlice("layers")...) // Always stack on top of "base" layer - for _, layer := range layers { - text := "" - err := db.QueryRow(cctx.Context, `SELECT config FROM harmony_config WHERE title=$1`, layer).Scan(&text) - if err != nil { - if strings.Contains(err.Error(), sql.ErrNoRows.Error()) { - return nil, fmt.Errorf("missing layer '%s' ", layer) - } - if layer == "base" { - return nil, errors.New(`curio defaults to a layer named 'base'. - Either use 'migrate' command or edit a base.toml and upload it with: curio config set base.toml`) - } - return nil, fmt.Errorf("could not read layer '%s': %w", layer, err) - } - - meta, err := LoadConfigWithUpgrades(text, curioConfig) - if err != nil { - return curioConfig, fmt.Errorf("could not read layer, bad toml %s: %w", layer, err) - } - for _, k := range meta.Keys() { - have = append(have, strings.Join(k, " ")) - } - log.Debugw("Using layer", "layer", layer, "config", curioConfig) - } - _ = have // FUTURE: verify that required fields are here. - // If config includes 3rd-party config, consider JSONSchema as a way that - // 3rd-parties can dynamically include config requirements and we can - // validate the config. Because of layering, we must validate @ startup. - return curioConfig, nil -} - -func GetDefaultConfig(comment bool) (string, error) { - c := config.DefaultCurioConfig() - cb, err := config.ConfigUpdate(c, nil, config.Commented(comment), config.DefaultKeepUncommented(), config.NoEnv()) - if err != nil { - return "", err - } - return string(cb), nil -} - -func GetDepsCLI(ctx context.Context, cctx *cli.Context) (*Deps, error) { - db, err := MakeDB(cctx) - if err != nil { - return nil, err - } - - cfg, err := GetConfig(cctx, db) - if err != nil { - return nil, err - } - - full, fullCloser, err := getFullNodeAPIV1Curio(cctx, cfg.Apis.ChainApiInfo) - if err != nil { - return nil, err - } - go func() { - select { - case <-ctx.Done(): - fullCloser() - } - }() - - return &Deps{ - Cfg: cfg, - DB: db, - Full: full, - }, nil -} - -func CreateMinerConfig(ctx context.Context, full v1api.FullNode, db *harmonydb.DB, miners []string, info string) error { - var titles []string - err := db.Select(ctx, &titles, `SELECT title FROM harmony_config WHERE LENGTH(config) > 0`) - if err != nil { - return fmt.Errorf("cannot reach the db. Ensure that Yugabyte flags are set correctly to"+ - " reach Yugabyte: %s", err.Error()) - } - - // setup config - curioConfig := config.DefaultCurioConfig() - - for _, addr := range miners { - maddr, err := address.NewFromString(addr) - if err != nil { - return xerrors.Errorf("Invalid address: %s", addr) - } - - _, err = full.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("Failed to get miner info: %w", err) - } - - curioConfig.Addresses = append(curioConfig.Addresses, config.CurioAddresses{ - PreCommitControl: []string{}, - CommitControl: []string{}, - TerminateControl: []string{}, - DisableOwnerFallback: false, - DisableWorkerFallback: false, - MinerAddresses: []string{addr}, - }) - } - - { - sk, err := io.ReadAll(io.LimitReader(rand.Reader, 32)) - if err != nil { - return err - } - - curioConfig.Apis.StorageRPCSecret = base64.StdEncoding.EncodeToString(sk) - } - - { - curioConfig.Apis.ChainApiInfo = append(curioConfig.Apis.ChainApiInfo, info) - } - - curioConfig.Addresses = lo.Filter(curioConfig.Addresses, func(a config.CurioAddresses, _ int) bool { - return len(a.MinerAddresses) > 0 - }) - - // If no base layer is present - if !lo.Contains(titles, "base") { - cb, err := config.ConfigUpdate(curioConfig, config.DefaultCurioConfig(), config.Commented(true), config.DefaultKeepUncommented(), config.NoEnv()) - if err != nil { - return xerrors.Errorf("Failed to generate default config: %w", err) - } - cfg := string(cb) - _, err = db.Exec(ctx, "INSERT INTO harmony_config (title, config) VALUES ('base', $1)", cfg) - if err != nil { - return xerrors.Errorf("failed to insert the 'base' into the database: %w", err) - } - fmt.Printf("The base layer has been updated with miner[s] %s\n", miners) - return nil - } - - // if base layer is present - baseCfg := config.DefaultCurioConfig() - var baseText string - err = db.QueryRow(ctx, "SELECT config FROM harmony_config WHERE title='base'").Scan(&baseText) - if err != nil { - return xerrors.Errorf("Cannot load base config from database: %w", err) - } - _, err = LoadConfigWithUpgrades(baseText, baseCfg) - if err != nil { - return xerrors.Errorf("Cannot parse base config: %w", err) - } - - baseCfg.Addresses = append(baseCfg.Addresses, curioConfig.Addresses...) - baseCfg.Addresses = lo.Filter(baseCfg.Addresses, func(a config.CurioAddresses, _ int) bool { - return len(a.MinerAddresses) > 0 - }) - - cb, err := config.ConfigUpdate(baseCfg, config.DefaultCurioConfig(), config.Commented(true), config.DefaultKeepUncommented(), config.NoEnv()) - if err != nil { - return xerrors.Errorf("cannot interpret config: %w", err) - } - _, err = db.Exec(ctx, "UPDATE harmony_config SET config=$1 WHERE title='base'", string(cb)) - if err != nil { - return xerrors.Errorf("cannot update base config: %w", err) - } - fmt.Printf("The base layer has been updated with miner[s] %s\n", miners) - return nil -} diff --git a/cmd/curio/guidedsetup/guidedsetup.go b/cmd/curio/guidedsetup/guidedsetup.go deleted file mode 100644 index 1bdb8e784..000000000 --- a/cmd/curio/guidedsetup/guidedsetup.go +++ /dev/null @@ -1,881 +0,0 @@ -// guidedSetup for migration from lotus-miner to Curio -// -// IF STRINGS CHANGED { -// follow instructions at ../internal/translations/translations.go -// } -package guidedsetup - -import ( - "bytes" - "context" - "crypto/rand" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "math/bits" - "net/http" - "os" - "os/signal" - "path" - "reflect" - "strconv" - "strings" - "syscall" - "time" - - "github.com/BurntSushi/toml" - "github.com/charmbracelet/lipgloss" - "github.com/docker/go-units" - "github.com/manifoldco/promptui" - "github.com/mitchellh/go-homedir" - "github.com/samber/lo" - "github.com/urfave/cli/v2" - "golang.org/x/text/language" - "golang.org/x/text/message" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-jsonrpc" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/v1api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/cli/spcli" - cliutil "github.com/filecoin-project/lotus/cli/util" - "github.com/filecoin-project/lotus/cmd/curio/deps" - _ "github.com/filecoin-project/lotus/cmd/curio/internal/translations" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/node/config" - "github.com/filecoin-project/lotus/node/repo" -) - -// URL to upload user-selected fields to help direct developer's focus. -const DeveloperFocusRequestURL = "https://curiostorage.org/cgi-bin/savedata.php" - -var GuidedsetupCmd = &cli.Command{ - Name: "guided-setup", - Usage: "Run the guided setup for migrating from lotus-miner to Curio or Creating a new Curio miner", - Flags: []cli.Flag{ - &cli.StringFlag{ // for cliutil.GetFullNodeAPI - Name: "repo", - EnvVars: []string{"LOTUS_PATH"}, - Hidden: true, - Value: "~/.lotus", - }, - }, - Action: func(cctx *cli.Context) (err error) { - T, say := SetupLanguage() - setupCtrlC(say) - - // Run the migration steps - migrationData := MigrationData{ - T: T, - say: say, - selectTemplates: &promptui.SelectTemplates{ - Help: T("Use the arrow keys to navigate: ↓ ↑ → ← "), - }, - cctx: cctx, - ctx: cctx.Context, - } - - newOrMigrate(&migrationData) - if migrationData.init { - say(header, "This interactive tool creates a new miner actor and creates the basic configuration layer for it.") - say(notice, "This process is partially idempotent. Once a new miner actor has been created and subsequent steps fail, the user need to run 'curio config new-cluster < miner ID >' to finish the configuration.") - for _, step := range newMinerSteps { - step(&migrationData) - } - } else { - say(header, "This interactive tool migrates lotus-miner to Curio in 5 minutes.") - say(notice, "Each step needs your confirmation and can be reversed. Press Ctrl+C to exit at any time.") - - for _, step := range migrationSteps { - step(&migrationData) - } - } - - for _, closer := range migrationData.closers { - closer() - } - return nil - }, -} - -func setupCtrlC(say func(style lipgloss.Style, key message.Reference, a ...interface{})) { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - <-c - say(notice, "Ctrl+C pressed in Terminal") - os.Exit(2) - }() -} - -var ( - header = lipgloss.NewStyle(). - Align(lipgloss.Left). - Foreground(lipgloss.Color("#00FF00")). - Background(lipgloss.Color("#242424")). - BorderStyle(lipgloss.NormalBorder()). - Width(60).Margin(1) - - notice = lipgloss.NewStyle(). - Align(lipgloss.Left). - Bold(true). - Foreground(lipgloss.Color("#CCCCCC")). - Background(lipgloss.Color("#333300")).MarginBottom(1) - - green = lipgloss.NewStyle(). - Align(lipgloss.Left). - Foreground(lipgloss.Color("#00FF00")). - Background(lipgloss.Color("#000000")) - - plain = lipgloss.NewStyle().Align(lipgloss.Left) - - section = lipgloss.NewStyle(). - Align(lipgloss.Left). - Foreground(lipgloss.Color("#000000")). - Background(lipgloss.Color("#FFFFFF")). - Underline(true) - - code = lipgloss.NewStyle(). - Align(lipgloss.Left). - Foreground(lipgloss.Color("#00FF00")). - Background(lipgloss.Color("#f8f9fa")) -) - -func SetupLanguage() (func(key message.Reference, a ...interface{}) string, func(style lipgloss.Style, key message.Reference, a ...interface{})) { - langText := "en" - problem := false - if len(os.Getenv("LANG")) > 1 { - langText = os.Getenv("LANG")[:2] - } else { - problem = true - } - - lang, err := language.Parse(langText) - if err != nil { - lang = language.English - problem = true - fmt.Println("Error parsing language") - } - - langs := message.DefaultCatalog.Languages() - have := lo.SliceToMap(langs, func(t language.Tag) (string, bool) { return t.String(), true }) - if _, ok := have[lang.String()]; !ok { - lang = language.English - problem = true - } - if problem { - _ = os.Setenv("LANG", "en-US") // for later users of this function - notice.Copy().AlignHorizontal(lipgloss.Right). - Render("$LANG=" + langText + " unsupported. Available: " + strings.Join(lo.Keys(have), ", ")) - fmt.Println("Defaulting to English. Please reach out to the Curio team if you would like to have additional language support.") - } - return func(key message.Reference, a ...interface{}) string { - return message.NewPrinter(lang).Sprintf(key, a...) - }, func(sty lipgloss.Style, key message.Reference, a ...interface{}) { - msg := message.NewPrinter(lang).Sprintf(key, a...) - fmt.Println(sty.Render(msg)) - } -} - -func newOrMigrate(d *MigrationData) { - i, _, err := (&promptui.Select{ - Label: d.T("I want to:"), - Items: []string{ - d.T("Migrate from existing Lotus-Miner"), - d.T("Create a new miner")}, - Templates: d.selectTemplates, - }).Run() - if err != nil { - d.say(notice, "Aborting remaining steps.", err.Error()) - os.Exit(1) - } - if i == 1 { - d.init = true - } -} - -type migrationStep func(*MigrationData) - -var migrationSteps = []migrationStep{ - readMinerConfig, // Tells them to be on the miner machine - yugabyteConnect, // Miner is updated - configToDB, // work on base configuration migration. - verifySectors, // Verify the sectors are in the database - doc, - oneLastThing, - complete, -} - -type newMinerStep func(data *MigrationData) - -var newMinerSteps = []newMinerStep{ - stepPresteps, - stepCreateActor, - stepNewMinerConfig, - doc, - oneLastThing, - completeInit, -} - -type MigrationData struct { - T func(key message.Reference, a ...interface{}) string - say func(style lipgloss.Style, key message.Reference, a ...interface{}) - selectTemplates *promptui.SelectTemplates - MinerConfigPath string - MinerConfig *config.StorageMiner - DB *harmonydb.DB - MinerID address.Address - full v1api.FullNode - cctx *cli.Context - closers []jsonrpc.ClientCloser - ctx context.Context - owner address.Address - worker address.Address - sender address.Address - ssize abi.SectorSize - confidence uint64 - init bool -} - -func complete(d *MigrationData) { - stepCompleted(d, d.T("Lotus-Miner to Curio Migration.")) - d.say(plain, "Try the web interface with %s for further guided improvements.", code.Render("curio run --layers=gui")) - d.say(plain, "You can now migrate your market node (%s), if applicable.", "Boost") -} - -func completeInit(d *MigrationData) { - stepCompleted(d, d.T("New Miner initialization complete.")) - d.say(plain, "Try the web interface with %s for further guided improvements.", "--layers=gui") -} - -func configToDB(d *MigrationData) { - d.say(section, "Migrating lotus-miner config.toml to Curio in-database configuration.") - - { - var closer jsonrpc.ClientCloser - var err error - d.full, closer, err = cliutil.GetFullNodeAPIV1(d.cctx) - d.closers = append(d.closers, closer) - if err != nil { - d.say(notice, "Error getting API: %s", err.Error()) - os.Exit(1) - } - } - ainfo, err := cliutil.GetAPIInfo(d.cctx, repo.FullNode) - if err != nil { - d.say(notice, "could not get API info for FullNode: %w", err) - os.Exit(1) - } - token, err := d.full.AuthNew(context.Background(), api.AllPermissions) - if err != nil { - d.say(notice, "Error getting token: %s", err.Error()) - os.Exit(1) - } - - chainApiInfo := fmt.Sprintf("%s:%s", string(token), ainfo.Addr) - - d.MinerID, err = SaveConfigToLayer(d.MinerConfigPath, chainApiInfo) - if err != nil { - d.say(notice, "Error saving config to layer: %s. Aborting Migration", err.Error()) - os.Exit(1) - } -} - -// bucket returns the power's 4 highest bits (rounded down). -func bucket(power *api.MinerPower) uint64 { - rawQAP := power.TotalPower.QualityAdjPower.Uint64() - magnitude := lo.Max([]int{bits.Len64(rawQAP), 5}) - - // shifting erases resolution so we cannot distinguish SPs of similar scales. - return rawQAP >> (uint64(magnitude) - 4) << (uint64(magnitude - 4)) -} - -type uploadType int - -const uploadTypeIndividual uploadType = 0 -const uploadTypeAggregate uploadType = 1 - -// const uploadTypeHint uploadType = 2 -const uploadTypeNothing uploadType = 3 - -func oneLastThing(d *MigrationData) { - d.say(section, "The Curio team wants to improve the software you use. Tell the team you're using `%s`.", "curio") - i, _, err := (&promptui.Select{ - Label: d.T("Select what you want to share with the Curio team."), - Items: []string{ - d.T("Individual Data: Miner ID, Curio version, chain (%s or %s). Signed.", "mainnet", "calibration"), - d.T("Aggregate-Anonymous: version, chain, and Miner power (bucketed)."), - d.T("Hint: I am someone running Curio on whichever chain."), - d.T("Nothing.")}, - Templates: d.selectTemplates, - }).Run() - preference := uploadType(i) - if err != nil { - d.say(notice, "Aborting remaining steps.", err.Error()) - os.Exit(1) - } - if preference != uploadTypeNothing { - msgMap := map[string]any{ - "domain": "curio-newuser", - "net": build.BuildTypeString(), - } - if preference == uploadTypeIndividual || preference == uploadTypeAggregate { - // articles of incorporation - power, err := d.full.StateMinerPower(context.Background(), d.MinerID, types.EmptyTSK) - if err != nil { - d.say(notice, "Error getting miner power: %s", err.Error()) - os.Exit(1) - } - msgMap["version"] = build.BuildVersion - msgMap["net"] = build.BuildType - msgMap["power"] = map[uploadType]uint64{ - uploadTypeIndividual: power.MinerPower.QualityAdjPower.Uint64(), - uploadTypeAggregate: bucket(power)}[preference] - - if preference == uploadTypeIndividual { // Sign it - msgMap["miner_id"] = d.MinerID - msg, err := json.Marshal(msgMap) - if err != nil { - d.say(notice, "Error marshalling message: %s", err.Error()) - os.Exit(1) - } - mi, err := d.full.StateMinerInfo(context.Background(), d.MinerID, types.EmptyTSK) - if err != nil { - d.say(notice, "Error getting miner info: %s", err.Error()) - os.Exit(1) - } - sig, err := d.full.WalletSign(context.Background(), mi.Worker, msg) - if err != nil { - d.say(notice, "Error signing message: %s", err.Error()) - os.Exit(1) - } - msgMap["signature"] = base64.StdEncoding.EncodeToString(sig.Data) - } - } - msg, err := json.Marshal(msgMap) - if err != nil { - d.say(notice, "Error marshalling message: %s", err.Error()) - os.Exit(1) - } - - resp, err := http.DefaultClient.Post(DeveloperFocusRequestURL, "application/json", bytes.NewReader(msg)) - if err != nil { - d.say(notice, "Error sending message: %s", err.Error()) - } - if resp != nil { - defer func() { _ = resp.Body.Close() }() - if resp.StatusCode != 200 { - b, err := io.ReadAll(resp.Body) - if err == nil { - d.say(notice, "Error sending message: Status %s, Message: ", resp.Status, string(b)) - } - } else { - stepCompleted(d, d.T("Message sent.")) - } - } - } -} - -func doc(d *MigrationData) { - d.say(plain, "Documentation: ") - d.say(plain, "The '%s' layer stores common configuration. All curio instances can include it in their %s argument.", "base", "--layers") - d.say(plain, "You can add other layers for per-machine configuration changes.") - - d.say(plain, "Filecoin %s channels: %s and %s", "Slack", "#fil-curio-help", "#fil-curio-dev") - - d.say(plain, "Increase reliability using redundancy: start multiple machines with at-least the post layer: 'curio run --layers=post'") - //d.say(plain, "Point your browser to your web GUI to complete setup with %s and advanced featues.", "Boost") - d.say(plain, "One database can serve multiple miner IDs: Run a migration for each lotus-miner.") -} - -func verifySectors(d *MigrationData) { - var i []int - var lastError string - fmt.Println() - d.say(section, "Please start (or restart) %s now that database credentials are in %s.", "lotus-miner", "config.toml") - d.say(notice, "Waiting for %s to write sectors into Yugabyte.", "lotus-miner") - - mid, err := address.IDFromAddress(d.MinerID) - if err != nil { - d.say(notice, "Error interpreting miner ID: %s: ID: %s", err.Error(), d.MinerID.String()) - os.Exit(1) - } - - for { - err := d.DB.Select(context.Background(), &i, ` - SELECT count(*) FROM sector_location WHERE miner_id=$1`, mid) - if err != nil { - if err.Error() != lastError { - d.say(notice, "Error verifying sectors: %s", err.Error()) - lastError = err.Error() - } - continue - } - if i[0] > 0 { - break - } - fmt.Print(".") - time.Sleep(5 * time.Second) - } - d.say(plain, "The sectors are in the database. The database is ready for %s.", "Curio") - d.say(notice, "Now shut down lotus-miner and lotus-worker and use run %s instead.", code.Render("curio run")) - - _, err = (&promptui.Prompt{Label: d.T("Press return to continue")}).Run() - if err != nil { - d.say(notice, "Aborting migration.") - os.Exit(1) - } - stepCompleted(d, d.T("Sectors verified. %d sector locations found.", i)) -} - -func yugabyteConnect(d *MigrationData) { - harmonyCfg := config.DefaultStorageMiner().HarmonyDB //copy the config to a local variable - if d.MinerConfig != nil { - harmonyCfg = d.MinerConfig.HarmonyDB //copy the config to a local variable - } - var err error - d.DB, err = harmonydb.NewFromConfig(harmonyCfg) - if err != nil { - hcfg := getDBDetails(d) - harmonyCfg = *hcfg - } - - d.say(plain, "Connected to Yugabyte. Schema is current.") - if !reflect.DeepEqual(harmonyCfg, d.MinerConfig.HarmonyDB) || !d.MinerConfig.Subsystems.EnableSectorIndexDB { - d.MinerConfig.HarmonyDB = harmonyCfg - d.MinerConfig.Subsystems.EnableSectorIndexDB = true - - d.say(plain, "Enabling Sector Indexing in the database.") - buf, err := config.ConfigUpdate(d.MinerConfig, config.DefaultStorageMiner()) - if err != nil { - d.say(notice, "Error encoding config.toml: %s", err.Error()) - os.Exit(1) - } - _, err = (&promptui.Prompt{ - Label: d.T("Press return to update %s with Yugabyte info. A Backup file will be written to that folder before changes are made.", "config.toml")}).Run() - if err != nil { - os.Exit(1) - } - p, err := homedir.Expand(d.MinerConfigPath) - if err != nil { - d.say(notice, "Error expanding path: %s", err.Error()) - os.Exit(1) - } - tomlPath := path.Join(p, "config.toml") - stat, err := os.Stat(tomlPath) - if err != nil { - d.say(notice, "Error reading filemode of config.toml: %s", err.Error()) - os.Exit(1) - } - fBackup, err := os.CreateTemp(p, "config-backup-*.toml") - if err != nil { - d.say(notice, "Error creating backup file: %s", err.Error()) - os.Exit(1) - } - fBackupContents, err := os.ReadFile(tomlPath) - if err != nil { - d.say(notice, "Error reading config.toml: %s", err.Error()) - os.Exit(1) - } - _, err = fBackup.Write(fBackupContents) - if err != nil { - d.say(notice, "Error writing backup file: %s", err.Error()) - os.Exit(1) - } - err = fBackup.Close() - if err != nil { - d.say(notice, "Error closing backup file: %s", err.Error()) - os.Exit(1) - } - - filemode := stat.Mode() - err = os.WriteFile(path.Join(p, "config.toml"), buf, filemode) - if err != nil { - d.say(notice, "Error writing config.toml: %s", err.Error()) - os.Exit(1) - } - d.say(section, "Restart Lotus Miner. ") - } - stepCompleted(d, d.T("Connected to Yugabyte")) -} - -func readMinerConfig(d *MigrationData) { - d.say(plain, "To start, ensure your sealing pipeline is drained and shut-down lotus-miner.") - - verifyPath := func(dir string) (*config.StorageMiner, error) { - cfg := config.DefaultStorageMiner() - dir, err := homedir.Expand(dir) - if err != nil { - return nil, err - } - _, err = toml.DecodeFile(path.Join(dir, "config.toml"), &cfg) - return cfg, err - } - - dirs := map[string]*config.StorageMiner{"~/.lotusminer": nil, "~/.lotus-miner-local-net": nil} - if v := os.Getenv("LOTUS_MINER_PATH"); v != "" { - dirs[v] = nil - } - for dir := range dirs { - cfg, err := verifyPath(dir) - if err != nil { - delete(dirs, dir) - } - dirs[dir] = cfg - } - - var otherPath bool - if len(dirs) > 0 { - _, str, err := (&promptui.Select{ - Label: d.T("Select the location of your lotus-miner config directory?"), - Items: append(lo.Keys(dirs), d.T("Other")), - Templates: d.selectTemplates, - }).Run() - if err != nil { - if err.Error() == "^C" { - os.Exit(1) - } - otherPath = true - } else { - if str == d.T("Other") { - otherPath = true - } else { - d.MinerConfigPath = str - d.MinerConfig = dirs[str] - } - } - } - if otherPath { - minerPathEntry: - str, err := (&promptui.Prompt{ - Label: d.T("Enter the path to the configuration directory used by %s", "lotus-miner"), - }).Run() - if err != nil { - d.say(notice, "No path provided, abandoning migration ") - os.Exit(1) - } - cfg, err := verifyPath(str) - if err != nil { - d.say(notice, "Cannot read the config.toml file in the provided directory, Error: %s", err.Error()) - goto minerPathEntry - } - d.MinerConfigPath = str - d.MinerConfig = cfg - } - - // Try to lock Miner repo to verify that lotus-miner is not running - { - r, err := repo.NewFS(d.MinerConfigPath) - if err != nil { - d.say(plain, "Could not create repo from directory: %s. Aborting migration", err.Error()) - os.Exit(1) - } - lr, err := r.Lock(repo.StorageMiner) - if err != nil { - d.say(plain, "Could not lock miner repo. Your miner must be stopped: %s\n Aborting migration", err.Error()) - os.Exit(1) - } - _ = lr.Close() - } - - stepCompleted(d, d.T("Read Miner Config")) -} -func stepCompleted(d *MigrationData, step string) { - fmt.Print(green.Render("✔ ")) - d.say(plain, "Step Complete: %s\n", step) -} - -func stepCreateActor(d *MigrationData) { - d.say(plain, "Initializing a new miner actor.") - - for { - i, _, err := (&promptui.Select{ - Label: d.T("Enter the info to create a new miner"), - Items: []string{ - d.T("Owner Address: %s", d.owner.String()), - d.T("Worker Address: %s", d.worker.String()), - d.T("Sender Address: %s", d.sender.String()), - d.T("Sector Size: %d", d.ssize), - d.T("Confidence epochs: %d", d.confidence), - d.T("Continue to verify the addresses and create a new miner actor.")}, - Size: 6, - Templates: d.selectTemplates, - }).Run() - if err != nil { - d.say(notice, "Miner creation error occurred: %s ", err.Error()) - os.Exit(1) - } - switch i { - case 0: - owner, err := (&promptui.Prompt{ - Label: d.T("Enter the owner address"), - }).Run() - if err != nil { - d.say(notice, "No address provided") - continue - } - ownerAddr, err := address.NewFromString(owner) - if err != nil { - d.say(notice, "Failed to parse the address: %s", err.Error()) - } - d.owner = ownerAddr - case 1, 2: - val, err := (&promptui.Prompt{ - Label: d.T("Enter %s address", []string{"worker", "sender"}[i-1]), - Default: d.owner.String(), - }).Run() - if err != nil { - d.say(notice, err.Error()) - continue - } - addr, err := address.NewFromString(val) - if err != nil { - d.say(notice, "Failed to parse the address: %s", err.Error()) - } - switch i { - case 1: - d.worker = addr - case 2: - d.sender = addr - } - continue - case 3: - val, err := (&promptui.Prompt{ - Label: d.T("Enter the sector size"), - }).Run() - if err != nil { - d.say(notice, "No value provided") - continue - } - sectorSize, err := units.RAMInBytes(val) - if err != nil { - d.say(notice, "Failed to parse sector size: %s", err.Error()) - continue - } - d.ssize = abi.SectorSize(sectorSize) - continue - case 4: - confidenceStr, err := (&promptui.Prompt{ - Label: d.T("Confidence epochs"), - Default: strconv.Itoa(5), - }).Run() - if err != nil { - d.say(notice, err.Error()) - continue - } - confidence, err := strconv.ParseUint(confidenceStr, 10, 64) - if err != nil { - d.say(notice, "Failed to parse confidence: %s", err.Error()) - continue - } - d.confidence = confidence - goto minerInit // break out of the for loop once we have all the values - } - } - -minerInit: - miner, err := spcli.CreateStorageMiner(d.ctx, d.full, d.owner, d.worker, d.sender, d.ssize, d.confidence) - if err != nil { - d.say(notice, "Failed to create the miner actor: %s", err.Error()) - os.Exit(1) - } - - d.MinerID = miner - stepCompleted(d, d.T("Miner %s created successfully", miner.String())) -} - -func stepPresteps(d *MigrationData) { - - // Setup and connect to YugabyteDB - _ = getDBDetails(d) - - // Verify HarmonyDB connection - var titles []string - err := d.DB.Select(d.ctx, &titles, `SELECT title FROM harmony_config WHERE LENGTH(config) > 0`) - if err != nil { - d.say(notice, "Cannot reach the DB: %s", err.Error()) - os.Exit(1) - } - - // Get full node API - full, closer, err := cliutil.GetFullNodeAPIV1(d.cctx) - if err != nil { - d.say(notice, "Error connecting to full node API: %s", err.Error()) - os.Exit(1) - } - d.full = full - d.closers = append(d.closers, closer) - stepCompleted(d, d.T("Pre-initialization steps complete")) -} - -func stepNewMinerConfig(d *MigrationData) { - curioCfg := config.DefaultCurioConfig() - curioCfg.Addresses = append(curioCfg.Addresses, config.CurioAddresses{ - PreCommitControl: []string{}, - CommitControl: []string{}, - TerminateControl: []string{}, - DisableOwnerFallback: false, - DisableWorkerFallback: false, - MinerAddresses: []string{d.MinerID.String()}, - }) - - sk, err := io.ReadAll(io.LimitReader(rand.Reader, 32)) - if err != nil { - d.say(notice, "Failed to generate random bytes for secret: %s", err.Error()) - d.say(notice, "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster %s' to finish the configuration", d.MinerID.String()) - os.Exit(1) - } - - curioCfg.Apis.StorageRPCSecret = base64.StdEncoding.EncodeToString(sk) - - ainfo, err := cliutil.GetAPIInfo(d.cctx, repo.FullNode) - if err != nil { - d.say(notice, "Failed to get API info for FullNode: %w", err) - d.say(notice, "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster %s' to finish the configuration", d.MinerID.String()) - os.Exit(1) - } - - token, err := d.full.AuthNew(d.ctx, api.AllPermissions) - if err != nil { - d.say(notice, "Failed to verify the auth token from daemon node: %s", err.Error()) - d.say(notice, "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster %s' to finish the configuration", d.MinerID.String()) - os.Exit(1) - } - - curioCfg.Apis.ChainApiInfo = append(curioCfg.Apis.ChainApiInfo, fmt.Sprintf("%s:%s", string(token), ainfo.Addr)) - - // write config - var titles []string - err = d.DB.Select(d.ctx, &titles, `SELECT title FROM harmony_config WHERE LENGTH(config) > 0`) - if err != nil { - d.say(notice, "Cannot reach the DB: %s", err.Error()) - d.say(notice, "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster %s' to finish the configuration", d.MinerID.String()) - os.Exit(1) - } - - // If 'base' layer is not present - if !lo.Contains(titles, "base") { - curioCfg.Addresses = lo.Filter(curioCfg.Addresses, func(a config.CurioAddresses, _ int) bool { - return len(a.MinerAddresses) > 0 - }) - cb, err := config.ConfigUpdate(curioCfg, config.DefaultCurioConfig(), config.Commented(true), config.DefaultKeepUncommented(), config.NoEnv()) - if err != nil { - d.say(notice, "Failed to generate default config: %s", err.Error()) - d.say(notice, "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster %s' to finish the configuration", d.MinerID.String()) - os.Exit(1) - } - _, err = d.DB.Exec(d.ctx, "INSERT INTO harmony_config (title, config) VALUES ('base', $1)", string(cb)) - if err != nil { - d.say(notice, "Failed to insert 'base' config layer in database: %s", err.Error()) - d.say(notice, "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster %s' to finish the configuration", d.MinerID.String()) - os.Exit(1) - } - stepCompleted(d, d.T("Configuration 'base' was updated to include this miner's address")) - return - } - - // If base layer is already present - baseCfg := config.DefaultCurioConfig() - var baseText string - - err = d.DB.QueryRow(d.ctx, "SELECT config FROM harmony_config WHERE title='base'").Scan(&baseText) - if err != nil { - d.say(notice, "Failed to load base config from database: %s", err.Error()) - d.say(notice, "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster %s' to finish the configuration", d.MinerID.String()) - os.Exit(1) - } - _, err = deps.LoadConfigWithUpgrades(baseText, baseCfg) - if err != nil { - d.say(notice, "Failed to parse base config: %s", err.Error()) - d.say(notice, "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster %s' to finish the configuration", d.MinerID.String()) - os.Exit(1) - } - - baseCfg.Addresses = append(baseCfg.Addresses, curioCfg.Addresses...) - baseCfg.Addresses = lo.Filter(baseCfg.Addresses, func(a config.CurioAddresses, _ int) bool { - return len(a.MinerAddresses) > 0 - }) - - cb, err := config.ConfigUpdate(baseCfg, config.DefaultCurioConfig(), config.Commented(true), config.DefaultKeepUncommented(), config.NoEnv()) - if err != nil { - d.say(notice, "Failed to regenerate base config: %s", err.Error()) - d.say(notice, "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster %s' to finish the configuration", d.MinerID.String()) - os.Exit(1) - } - _, err = d.DB.Exec(d.ctx, "UPDATE harmony_config SET config=$1 WHERE title='base'", string(cb)) - if err != nil { - d.say(notice, "Failed to insert 'base' config layer in database: %s", err.Error()) - d.say(notice, "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster %s' to finish the configuration", d.MinerID.String()) - os.Exit(1) - } - - stepCompleted(d, d.T("Configuration 'base' was updated to include this miner's address")) -} - -func getDBDetails(d *MigrationData) *config.HarmonyDB { - harmonyCfg := config.DefaultStorageMiner().HarmonyDB - for { - i, _, err := (&promptui.Select{ - Label: d.T("Enter the info to connect to your Yugabyte database installation (https://download.yugabyte.com/)"), - Items: []string{ - d.T("Host: %s", strings.Join(harmonyCfg.Hosts, ",")), - d.T("Port: %s", harmonyCfg.Port), - d.T("Username: %s", harmonyCfg.Username), - d.T("Password: %s", harmonyCfg.Password), - d.T("Database: %s", harmonyCfg.Database), - d.T("Continue to connect and update schema.")}, - Size: 6, - Templates: d.selectTemplates, - }).Run() - if err != nil { - d.say(notice, "Database config error occurred, abandoning migration: %s ", err.Error()) - os.Exit(1) - } - switch i { - case 0: - host, err := (&promptui.Prompt{ - Label: d.T("Enter the Yugabyte database host(s)"), - }).Run() - if err != nil { - d.say(notice, "No host provided") - continue - } - harmonyCfg.Hosts = strings.Split(host, ",") - case 1, 2, 3, 4: - val, err := (&promptui.Prompt{ - Label: d.T("Enter the Yugabyte database %s", []string{"port", "username", "password", "database"}[i-1]), - }).Run() - if err != nil { - d.say(notice, "No value provided") - continue - } - switch i { - case 1: - harmonyCfg.Port = val - case 2: - harmonyCfg.Username = val - case 3: - harmonyCfg.Password = val - case 4: - harmonyCfg.Database = val - } - continue - case 5: - db, err := harmonydb.NewFromConfig(harmonyCfg) - if err != nil { - if err.Error() == "^C" { - os.Exit(1) - } - d.say(notice, "Error connecting to Yugabyte database: %s", err.Error()) - continue - } - d.DB = db - return &harmonyCfg - } - } -} diff --git a/cmd/curio/guidedsetup/shared.go b/cmd/curio/guidedsetup/shared.go deleted file mode 100644 index 6e7d81c03..000000000 --- a/cmd/curio/guidedsetup/shared.go +++ /dev/null @@ -1,258 +0,0 @@ -package guidedsetup - -import ( - "bytes" - "context" - "encoding/base64" - "fmt" - "os" - "path" - "strings" - - "github.com/BurntSushi/toml" - "github.com/ipfs/go-datastore" - "github.com/samber/lo" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - - "github.com/filecoin-project/lotus/cmd/curio/deps" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/node/config" - "github.com/filecoin-project/lotus/node/modules" - "github.com/filecoin-project/lotus/node/repo" -) - -const ( - FlagMinerRepo = "miner-repo" -) - -const FlagMinerRepoDeprecation = "storagerepo" - -func SaveConfigToLayer(minerRepoPath, chainApiInfo string) (minerAddress address.Address, err error) { - _, say := SetupLanguage() - ctx := context.Background() - - r, err := repo.NewFS(minerRepoPath) - if err != nil { - return minerAddress, err - } - - ok, err := r.Exists() - if err != nil { - return minerAddress, err - } - - if !ok { - return minerAddress, fmt.Errorf("repo not initialized at: %s", minerRepoPath) - } - - lr, err := r.LockRO(repo.StorageMiner) - if err != nil { - return minerAddress, fmt.Errorf("locking repo: %w", err) - } - defer func() { - err = lr.Close() - if err != nil { - fmt.Println("error closing repo: ", err) - } - }() - - cfgNode, err := lr.Config() - if err != nil { - return minerAddress, fmt.Errorf("getting node config: %w", err) - } - smCfg := cfgNode.(*config.StorageMiner) - - db, err := harmonydb.NewFromConfig(smCfg.HarmonyDB) - if err != nil { - return minerAddress, fmt.Errorf("could not reach the database. Ensure the Miner config toml's HarmonyDB entry"+ - " is setup to reach Yugabyte correctly: %w", err) - } - - var titles []string - err = db.Select(ctx, &titles, `SELECT title FROM harmony_config WHERE LENGTH(config) > 0`) - if err != nil { - return minerAddress, fmt.Errorf("miner cannot reach the db. Ensure the config toml's HarmonyDB entry"+ - " is setup to reach Yugabyte correctly: %s", err.Error()) - } - - // Copy over identical settings: - - buf, err := os.ReadFile(path.Join(lr.Path(), "config.toml")) - if err != nil { - return minerAddress, fmt.Errorf("could not read config.toml: %w", err) - } - curioCfg := config.DefaultCurioConfig() - - ensureEmptyArrays(curioCfg) - _, err = deps.LoadConfigWithUpgrades(string(buf), curioCfg) - - if err != nil { - return minerAddress, fmt.Errorf("could not decode toml: %w", err) - } - - // Populate Miner Address - mmeta, err := lr.Datastore(ctx, "/metadata") - if err != nil { - return minerAddress, xerrors.Errorf("opening miner metadata datastore: %w", err) - } - defer func() { - // _ = mmeta.Close() - }() - - maddrBytes, err := mmeta.Get(ctx, datastore.NewKey("miner-address")) - if err != nil { - return minerAddress, xerrors.Errorf("getting miner address datastore entry: %w", err) - } - - addr, err := address.NewFromBytes(maddrBytes) - if err != nil { - return minerAddress, xerrors.Errorf("parsing miner actor address: %w", err) - } - - minerAddress = addr - - curioCfg.Addresses = []config.CurioAddresses{{ - MinerAddresses: []string{addr.String()}, - PreCommitControl: smCfg.Addresses.PreCommitControl, - CommitControl: smCfg.Addresses.CommitControl, - TerminateControl: smCfg.Addresses.TerminateControl, - DisableOwnerFallback: smCfg.Addresses.DisableOwnerFallback, - DisableWorkerFallback: smCfg.Addresses.DisableWorkerFallback, - }} - - ks, err := lr.KeyStore() - if err != nil { - return minerAddress, xerrors.Errorf("keystore err: %w", err) - } - js, err := ks.Get(modules.JWTSecretName) - if err != nil { - return minerAddress, xerrors.Errorf("error getting JWTSecretName: %w", err) - } - - curioCfg.Apis.StorageRPCSecret = base64.StdEncoding.EncodeToString(js.PrivateKey) - - curioCfg.Apis.ChainApiInfo = append(curioCfg.Apis.ChainApiInfo, chainApiInfo) - // Express as configTOML - configTOML := &bytes.Buffer{} - if err = toml.NewEncoder(configTOML).Encode(curioCfg); err != nil { - return minerAddress, err - } - - if lo.Contains(titles, "base") { - // append addresses - var baseCfg = config.DefaultCurioConfig() - var baseText string - err = db.QueryRow(ctx, "SELECT config FROM harmony_config WHERE title='base'").Scan(&baseText) - if err != nil { - return minerAddress, xerrors.Errorf("Cannot load base config: %w", err) - } - ensureEmptyArrays(baseCfg) - _, err := deps.LoadConfigWithUpgrades(baseText, baseCfg) - if err != nil { - return minerAddress, xerrors.Errorf("Cannot load base config: %w", err) - } - for _, addr := range baseCfg.Addresses { - if lo.Contains(addr.MinerAddresses, curioCfg.Addresses[0].MinerAddresses[0]) { - goto skipWritingToBase - } - } - // write to base - { - baseCfg.Addresses = append(baseCfg.Addresses, curioCfg.Addresses[0]) - baseCfg.Addresses = lo.Filter(baseCfg.Addresses, func(a config.CurioAddresses, _ int) bool { - return len(a.MinerAddresses) > 0 - }) - if baseCfg.Apis.ChainApiInfo == nil { - baseCfg.Apis.ChainApiInfo = append(baseCfg.Apis.ChainApiInfo, chainApiInfo) - } - if baseCfg.Apis.StorageRPCSecret == "" { - baseCfg.Apis.StorageRPCSecret = curioCfg.Apis.StorageRPCSecret - } - - cb, err := config.ConfigUpdate(baseCfg, config.DefaultCurioConfig(), config.Commented(true), config.DefaultKeepUncommented(), config.NoEnv()) - if err != nil { - return minerAddress, xerrors.Errorf("cannot interpret config: %w", err) - } - _, err = db.Exec(ctx, "UPDATE harmony_config SET config=$1 WHERE title='base'", string(cb)) - if err != nil { - return minerAddress, xerrors.Errorf("cannot update base config: %w", err) - } - say(plain, "Configuration 'base' was updated to include this miner's address (%s) and its wallet setup.", minerAddress) - } - say(plain, "Compare the configurations %s to %s. Changes between the miner IDs other than wallet addreses should be a new, minimal layer for runners that need it.", "base", "mig-"+curioCfg.Addresses[0].MinerAddresses[0]) - skipWritingToBase: - } else { - _, err = db.Exec(ctx, `INSERT INTO harmony_config (title, config) VALUES ('base', $1) - ON CONFLICT(title) DO UPDATE SET config=EXCLUDED.config`, configTOML) - - if err != nil { - return minerAddress, xerrors.Errorf("Cannot insert base config: %w", err) - } - say(notice, "Configuration 'base' was created to resemble this lotus-miner's config.toml .") - } - - { // make a layer representing the migration - layerName := fmt.Sprintf("mig-%s", curioCfg.Addresses[0].MinerAddresses[0]) - _, err = db.Exec(ctx, "DELETE FROM harmony_config WHERE title=$1", layerName) - if err != nil { - return minerAddress, xerrors.Errorf("Cannot delete existing layer: %w", err) - } - - _, err = db.Exec(ctx, "INSERT INTO harmony_config (title, config) VALUES ($1, $2)", layerName, configTOML.String()) - if err != nil { - return minerAddress, xerrors.Errorf("Cannot insert layer after layer created message: %w", err) - } - say(plain, "Layer %s created. ", layerName) - } - - dbSettings := getDBSettings(*smCfg) - say(plain, "To work with the config: ") - fmt.Println(code.Render(`curio ` + dbSettings + ` config edit base`)) - say(plain, `To run Curio: With machine or cgroup isolation, use the command (with example layer selection):`) - fmt.Println(code.Render(`curio ` + dbSettings + ` run --layer=post`)) - return minerAddress, nil -} - -func getDBSettings(smCfg config.StorageMiner) string { - dbSettings := "" - def := config.DefaultStorageMiner().HarmonyDB - if def.Hosts[0] != smCfg.HarmonyDB.Hosts[0] { - dbSettings += ` --db-host="` + strings.Join(smCfg.HarmonyDB.Hosts, ",") + `"` - } - if def.Port != smCfg.HarmonyDB.Port { - dbSettings += " --db-port=" + smCfg.HarmonyDB.Port - } - if def.Username != smCfg.HarmonyDB.Username { - dbSettings += ` --db-user="` + smCfg.HarmonyDB.Username + `"` - } - if def.Password != smCfg.HarmonyDB.Password { - dbSettings += ` --db-password="` + smCfg.HarmonyDB.Password + `"` - } - if def.Database != smCfg.HarmonyDB.Database { - dbSettings += ` --db-name="` + smCfg.HarmonyDB.Database + `"` - } - return dbSettings -} - -func ensureEmptyArrays(cfg *config.CurioConfig) { - if cfg.Addresses == nil { - cfg.Addresses = []config.CurioAddresses{} - } else { - for i := range cfg.Addresses { - if cfg.Addresses[i].PreCommitControl == nil { - cfg.Addresses[i].PreCommitControl = []string{} - } - if cfg.Addresses[i].CommitControl == nil { - cfg.Addresses[i].CommitControl = []string{} - } - if cfg.Addresses[i].TerminateControl == nil { - cfg.Addresses[i].TerminateControl = []string{} - } - } - } - if cfg.Apis.ChainApiInfo == nil { - cfg.Apis.ChainApiInfo = []string{} - } -} diff --git a/cmd/curio/internal/translations/catalog.go b/cmd/curio/internal/translations/catalog.go deleted file mode 100644 index 6e0ad3087..000000000 --- a/cmd/curio/internal/translations/catalog.go +++ /dev/null @@ -1,476 +0,0 @@ -// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT. - -package translations - -import ( - "golang.org/x/text/language" - "golang.org/x/text/message" - "golang.org/x/text/message/catalog" -) - -type dictionary struct { - index []uint32 - data string -} - -func (d *dictionary) Lookup(key string) (data string, ok bool) { - p, ok := messageKeyToIndex[key] - if !ok { - return "", false - } - start, end := d.index[p], d.index[p+1] - if start == end { - return "", false - } - return d.data[start:end], true -} - -func init() { - dict := map[string]catalog.Dictionary{ - "en": &dictionary{index: enIndex, data: enData}, - "ko": &dictionary{index: koIndex, data: koData}, - "zh": &dictionary{index: zhIndex, data: zhData}, - } - fallback := language.MustParse("en") - cat, err := catalog.NewFromMap(dict, catalog.Fallback(fallback)) - if err != nil { - panic(err) - } - message.DefaultCatalog = cat -} - -var messageKeyToIndex = map[string]int{ - "Aborting migration.": 45, - "Aborting remaining steps.": 9, - "Aggregate-Anonymous: version, chain, and Miner power (bucketed).": 22, - "Cannot reach the DB: %s": 90, - "Cannot read the config.toml file in the provided directory, Error: %s": 65, - "Compare the configurations %s to %s. Changes between the miner IDs other than wallet addreses should be a new, minimal layer for runners that need it.": 116, - "Confidence epochs": 86, - "Confidence epochs: %d": 76, - "Configuration 'base' was created to resemble this lotus-miner's config.toml .": 117, - "Configuration 'base' was updated to include this miner's address": 99, - "Configuration 'base' was updated to include this miner's address (%s) and its wallet setup.": 115, - "Connected to Yugabyte": 59, - "Connected to Yugabyte. Schema is current.": 47, - "Continue to connect and update schema.": 109, - "Continue to verify the addresses and create a new miner actor.": 77, - "Could not create repo from directory: %s. Aborting migration": 66, - "Could not lock miner repo. Your miner must be stopped: %s\n Aborting migration": 67, - "Create a new miner": 8, - "Ctrl+C pressed in Terminal": 5, - "Database config error occurred, abandoning migration: %s ": 110, - "Database: %s": 108, - "Documentation: ": 32, - "Each step needs your confirmation and can be reversed. Press Ctrl+C to exit at any time.": 4, - "Enabling Sector Indexing in the database.": 48, - "Enter %s address": 82, - "Enter the Yugabyte database %s": 113, - "Enter the Yugabyte database host(s)": 111, - "Enter the info to connect to your Yugabyte database installation (https://download.yugabyte.com/)": 103, - "Enter the info to create a new miner": 71, - "Enter the owner address": 79, - "Enter the path to the configuration directory used by %s": 63, - "Enter the sector size": 83, - "Error closing backup file: %s": 56, - "Error connecting to Yugabyte database: %s": 114, - "Error connecting to full node API: %s": 91, - "Error creating backup file: %s": 53, - "Error encoding config.toml: %s": 49, - "Error expanding path: %s": 51, - "Error getting API: %s": 15, - "Error getting miner info: %s": 27, - "Error getting miner power: %s": 25, - "Error getting token: %s": 17, - "Error interpreting miner ID: %s: ID: %s": 40, - "Error marshalling message: %s": 26, - "Error reading config.toml: %s": 54, - "Error reading filemode of config.toml: %s": 52, - "Error saving config to layer: %s. Aborting Migration": 18, - "Error sending message: %s": 29, - "Error sending message: Status %s, Message: ": 30, - "Error signing message: %s": 28, - "Error verifying sectors: %s": 41, - "Error writing backup file: %s": 55, - "Error writing config.toml: %s": 57, - "Failed to create the miner actor: %s": 88, - "Failed to generate default config: %s": 97, - "Failed to generate random bytes for secret: %s": 93, - "Failed to get API info for FullNode: %w": 95, - "Failed to insert 'base' config layer in database: %s": 98, - "Failed to load base config from database: %s": 100, - "Failed to parse base config: %s": 101, - "Failed to parse confidence: %s": 87, - "Failed to parse sector size: %s": 85, - "Failed to parse the address: %s": 81, - "Failed to regenerate base config: %s": 102, - "Failed to verify the auth token from daemon node: %s": 96, - "Filecoin %s channels: %s and %s": 35, - "Hint: I am someone running Curio on whichever chain.": 23, - "Host: %s": 104, - "I want to:": 6, - "Increase reliability using redundancy: start multiple machines with at-least the post layer: 'curio run --layers=post'": 36, - "Individual Data: Miner ID, Curio version, chain (%s or %s). Signed.": 21, - "Initializing a new miner actor.": 70, - "Layer %s created. ": 118, - "Lotus-Miner to Curio Migration.": 10, - "Message sent.": 31, - "Migrate from existing Lotus-Miner": 7, - "Migrating lotus-miner config.toml to Curio in-database configuration.": 14, - "Miner %s created successfully": 89, - "Miner creation error occurred: %s ": 78, - "New Miner initialization complete.": 13, - "No address provided": 80, - "No host provided": 112, - "No path provided, abandoning migration ": 64, - "No value provided": 84, - "Nothing.": 24, - "Now shut down lotus-miner and lotus-worker and use run %s instead.": 43, - "One database can serve multiple miner IDs: Run a migration for each lotus-miner.": 37, - "Other": 62, - "Owner Address: %s": 72, - "Password: %s": 107, - "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster %s' to finish the configuration": 94, - "Please start (or restart) %s now that database credentials are in %s.": 38, - "Port: %s": 105, - "Pre-initialization steps complete": 92, - "Press return to continue": 44, - "Press return to update %s with Yugabyte info. A Backup file will be written to that folder before changes are made.": 50, - "Read Miner Config": 68, - "Restart Lotus Miner. ": 58, - "Sector Size: %d": 75, - "Sectors verified. %d sector locations found.": 46, - "Select the location of your lotus-miner config directory?": 61, - "Select what you want to share with the Curio team.": 20, - "Sender Address: %s": 74, - "Step Complete: %s\n": 69, - "The '%s' layer stores common configuration. All curio instances can include it in their %s argument.": 33, - "The Curio team wants to improve the software you use. Tell the team you're using `%s`.": 19, - "The sectors are in the database. The database is ready for %s.": 42, - "This interactive tool creates a new miner actor and creates the basic configuration layer for it.": 1, - "This interactive tool migrates lotus-miner to Curio in 5 minutes.": 3, - "This process is partially idempotent. Once a new miner actor has been created and subsequent steps fail, the user need to run 'curio config new-cluster < miner ID >' to finish the configuration.": 2, - "To run Curio: With machine or cgroup isolation, use the command (with example layer selection):": 120, - "To start, ensure your sealing pipeline is drained and shut-down lotus-miner.": 60, - "To work with the config: ": 119, - "Try the web interface with %s for further guided improvements.": 11, - "Use the arrow keys to navigate: ↓ ↑ → ← ": 0, - "Username: %s": 106, - "Waiting for %s to write sectors into Yugabyte.": 39, - "Worker Address: %s": 73, - "You can add other layers for per-machine configuration changes.": 34, - "You can now migrate your market node (%s), if applicable.": 12, - "could not get API info for FullNode: %w": 16, -} - -var enIndex = []uint32{ // 122 elements - // Entry 0 - 1F - 0x00000000, 0x00000035, 0x00000097, 0x0000015a, - 0x0000019c, 0x000001f5, 0x00000210, 0x0000021b, - 0x0000023d, 0x00000250, 0x0000026a, 0x0000028a, - 0x000002cc, 0x00000309, 0x0000032c, 0x00000372, - 0x0000038b, 0x000003b6, 0x000003d1, 0x00000409, - 0x00000463, 0x00000496, 0x000004e0, 0x00000521, - 0x00000556, 0x0000055f, 0x00000580, 0x000005a1, - 0x000005c1, 0x000005de, 0x000005fb, 0x0000062e, - // Entry 20 - 3F - 0x0000063c, 0x00000650, 0x000006bb, 0x000006fb, - 0x00000724, 0x0000079b, 0x000007ec, 0x00000838, - 0x0000086a, 0x00000898, 0x000008b7, 0x000008f9, - 0x0000093f, 0x00000958, 0x0000096c, 0x0000099c, - 0x000009c6, 0x000009f0, 0x00000a12, 0x00000a89, - 0x00000aa5, 0x00000ad2, 0x00000af4, 0x00000b15, - 0x00000b36, 0x00000b57, 0x00000b78, 0x00000b92, - 0x00000ba8, 0x00000bf5, 0x00000c2f, 0x00000c35, - // Entry 40 - 5F - 0x00000c71, 0x00000c9d, 0x00000ce6, 0x00000d26, - 0x00000d77, 0x00000d89, 0x00000da3, 0x00000dc3, - 0x00000de8, 0x00000dfd, 0x00000e13, 0x00000e29, - 0x00000e3c, 0x00000e55, 0x00000e94, 0x00000ebe, - 0x00000ed6, 0x00000eea, 0x00000f0d, 0x00000f21, - 0x00000f37, 0x00000f49, 0x00000f6c, 0x00000f7e, - 0x00000fa0, 0x00000fc8, 0x00000fe9, 0x00001004, - 0x0000102d, 0x0000104f, 0x00001081, 0x00001118, - // Entry 60 - 7F - 0x00001143, 0x0000117b, 0x000011a4, 0x000011dc, - 0x0000121d, 0x0000124d, 0x00001270, 0x00001298, - 0x000012fa, 0x00001306, 0x00001312, 0x00001322, - 0x00001332, 0x00001342, 0x00001369, 0x000013aa, - 0x000013ce, 0x000013df, 0x00001401, 0x0000142e, - 0x0000148d, 0x0000152a, 0x00001578, 0x00001592, - 0x000015b0, 0x00001610, -} // Size: 512 bytes - -const enData string = "" + // Size: 5648 bytes - "\x04\x00\x01 0\x02Use the arrow keys to navigate: ↓ ↑ → ←\x02This intera" + - "ctive tool creates a new miner actor and creates the basic configuration" + - " layer for it.\x02This process is partially idempotent. Once a new miner" + - " actor has been created and subsequent steps fail, the user need to run " + - "'curio config new-cluster < miner ID >' to finish the configuration.\x02" + - "This interactive tool migrates lotus-miner to Curio in 5 minutes.\x02Eac" + - "h step needs your confirmation and can be reversed. Press Ctrl+C to exit" + - " at any time.\x02Ctrl+C pressed in Terminal\x02I want to:\x02Migrate fro" + - "m existing Lotus-Miner\x02Create a new miner\x02Aborting remaining steps" + - ".\x02Lotus-Miner to Curio Migration.\x02Try the web interface with %[1]s" + - " for further guided improvements.\x02You can now migrate your market nod" + - "e (%[1]s), if applicable.\x02New Miner initialization complete.\x02Migra" + - "ting lotus-miner config.toml to Curio in-database configuration.\x02Erro" + - "r getting API: %[1]s\x02could not get API info for FullNode: %[1]w\x02Er" + - "ror getting token: %[1]s\x02Error saving config to layer: %[1]s. Abortin" + - "g Migration\x02The Curio team wants to improve the software you use. Tel" + - "l the team you're using `%[1]s`.\x02Select what you want to share with t" + - "he Curio team.\x02Individual Data: Miner ID, Curio version, chain (%[1]s" + - " or %[2]s). Signed.\x02Aggregate-Anonymous: version, chain, and Miner po" + - "wer (bucketed).\x02Hint: I am someone running Curio on whichever chain." + - "\x02Nothing.\x02Error getting miner power: %[1]s\x02Error marshalling me" + - "ssage: %[1]s\x02Error getting miner info: %[1]s\x02Error signing message" + - ": %[1]s\x02Error sending message: %[1]s\x04\x00\x01 .\x02Error sending m" + - "essage: Status %[1]s, Message:\x02Message sent.\x04\x00\x01 \x0f\x02Docu" + - "mentation:\x02The '%[1]s' layer stores common configuration. All curio i" + - "nstances can include it in their %[2]s argument.\x02You can add other la" + - "yers for per-machine configuration changes.\x02Filecoin %[1]s channels: " + - "%[2]s and %[3]s\x02Increase reliability using redundancy: start multiple" + - " machines with at-least the post layer: 'curio run --layers=post'\x02One" + - " database can serve multiple miner IDs: Run a migration for each lotus-m" + - "iner.\x02Please start (or restart) %[1]s now that database credentials a" + - "re in %[2]s.\x02Waiting for %[1]s to write sectors into Yugabyte.\x02Err" + - "or interpreting miner ID: %[1]s: ID: %[2]s\x02Error verifying sectors: %" + - "[1]s\x02The sectors are in the database. The database is ready for %[1]s" + - ".\x02Now shut down lotus-miner and lotus-worker and use run %[1]s instea" + - "d.\x02Press return to continue\x02Aborting migration.\x02Sectors verifie" + - "d. %[1]d sector locations found.\x02Connected to Yugabyte. Schema is cur" + - "rent.\x02Enabling Sector Indexing in the database.\x02Error encoding con" + - "fig.toml: %[1]s\x02Press return to update %[1]s with Yugabyte info. A Ba" + - "ckup file will be written to that folder before changes are made.\x02Err" + - "or expanding path: %[1]s\x02Error reading filemode of config.toml: %[1]s" + - "\x02Error creating backup file: %[1]s\x02Error reading config.toml: %[1]" + - "s\x02Error writing backup file: %[1]s\x02Error closing backup file: %[1]" + - "s\x02Error writing config.toml: %[1]s\x04\x00\x01 \x15\x02Restart Lotus " + - "Miner.\x02Connected to Yugabyte\x02To start, ensure your sealing pipelin" + - "e is drained and shut-down lotus-miner.\x02Select the location of your l" + - "otus-miner config directory?\x02Other\x02Enter the path to the configura" + - "tion directory used by %[1]s\x04\x00\x01 '\x02No path provided, abandoni" + - "ng migration\x02Cannot read the config.toml file in the provided directo" + - "ry, Error: %[1]s\x02Could not create repo from directory: %[1]s. Abortin" + - "g migration\x02Could not lock miner repo. Your miner must be stopped: %[" + - "1]s\x0a Aborting migration\x02Read Miner Config\x04\x00\x01\x0a\x15\x02S" + - "tep Complete: %[1]s\x02Initializing a new miner actor.\x02Enter the info" + - " to create a new miner\x02Owner Address: %[1]s\x02Worker Address: %[1]s" + - "\x02Sender Address: %[1]s\x02Sector Size: %[1]d\x02Confidence epochs: %[" + - "1]d\x02Continue to verify the addresses and create a new miner actor." + - "\x04\x00\x01 %\x02Miner creation error occurred: %[1]s\x02Enter the owne" + - "r address\x02No address provided\x02Failed to parse the address: %[1]s" + - "\x02Enter %[1]s address\x02Enter the sector size\x02No value provided" + - "\x02Failed to parse sector size: %[1]s\x02Confidence epochs\x02Failed to" + - " parse confidence: %[1]s\x02Failed to create the miner actor: %[1]s\x02M" + - "iner %[1]s created successfully\x02Cannot reach the DB: %[1]s\x02Error c" + - "onnecting to full node API: %[1]s\x02Pre-initialization steps complete" + - "\x02Failed to generate random bytes for secret: %[1]s\x02Please do not r" + - "un guided-setup again as miner creation is not idempotent. You need to r" + - "un 'curio config new-cluster %[1]s' to finish the configuration\x02Faile" + - "d to get API info for FullNode: %[1]w\x02Failed to verify the auth token" + - " from daemon node: %[1]s\x02Failed to generate default config: %[1]s\x02" + - "Failed to insert 'base' config layer in database: %[1]s\x02Configuration" + - " 'base' was updated to include this miner's address\x02Failed to load ba" + - "se config from database: %[1]s\x02Failed to parse base config: %[1]s\x02" + - "Failed to regenerate base config: %[1]s\x02Enter the info to connect to " + - "your Yugabyte database installation (https://download.yugabyte.com/)\x02" + - "Host: %[1]s\x02Port: %[1]s\x02Username: %[1]s\x02Password: %[1]s\x02Data" + - "base: %[1]s\x02Continue to connect and update schema.\x04\x00\x01 <\x02D" + - "atabase config error occurred, abandoning migration: %[1]s\x02Enter the " + - "Yugabyte database host(s)\x02No host provided\x02Enter the Yugabyte data" + - "base %[1]s\x02Error connecting to Yugabyte database: %[1]s\x02Configurat" + - "ion 'base' was updated to include this miner's address (%[1]s) and its w" + - "allet setup.\x02Compare the configurations %[1]s to %[2]s. Changes betwe" + - "en the miner IDs other than wallet addreses should be a new, minimal lay" + - "er for runners that need it.\x02Configuration 'base' was created to rese" + - "mble this lotus-miner's config.toml .\x04\x00\x01 \x15\x02Layer %[1]s cr" + - "eated.\x04\x00\x01 \x19\x02To work with the config:\x02To run Curio: Wit" + - "h machine or cgroup isolation, use the command (with example layer selec" + - "tion):" - -var koIndex = []uint32{ // 122 elements - // Entry 0 - 1F - 0x00000000, 0x00000044, 0x000000c1, 0x000001c1, - 0x0000020e, 0x00000289, 0x000002aa, 0x000002bc, - 0x000002e5, 0x00000300, 0x00000325, 0x00000348, - 0x000003b2, 0x00000402, 0x00000428, 0x00000481, - 0x000004a0, 0x000004dc, 0x0000050c, 0x00000564, - 0x000005f0, 0x00000629, 0x0000067f, 0x000006bd, - 0x0000070b, 0x00000726, 0x00000760, 0x00000793, - 0x000007cd, 0x000007f7, 0x00000821, 0x00000863, - // Entry 20 - 3F - 0x00000887, 0x00000894, 0x0000091a, 0x0000096c, - 0x00000993, 0x00000a2f, 0x00000ac1, 0x00000b3c, - 0x00000b80, 0x00000bbe, 0x00000be5, 0x00000c50, - 0x00000c9d, 0x00000cc4, 0x00000cdf, 0x00000d2e, - 0x00000d6f, 0x00000daf, 0x00000df6, 0x00000e9c, - 0x00000ecc, 0x00000f1b, 0x00000f3e, 0x00000f5f, - 0x00000f82, 0x00000fa5, 0x00000fe3, 0x00001007, - 0x0000101d, 0x00001088, 0x000010d7, 0x000010de, - // Entry 40 - 5F - 0x00001126, 0x00001178, 0x000011d2, 0x0000123c, - 0x000012cd, 0x000012e5, 0x000012ff, 0x00001323, - 0x00001356, 0x0000136e, 0x00001386, 0x0000139e, - 0x000013b3, 0x000013cb, 0x00001422, 0x0000144d, - 0x00001465, 0x0000148c, 0x000014af, 0x000014c3, - 0x000014d8, 0x000014fc, 0x00001526, 0x00001537, - 0x0000155d, 0x00001583, 0x000015bc, 0x000015f4, - 0x0000162c, 0x0000164b, 0x00001697, 0x00001755, - // Entry 60 - 7F - 0x000017a1, 0x000017ef, 0x00001812, 0x0000186e, - 0x000018be, 0x00001913, 0x00001956, 0x00001995, - 0x00001a03, 0x00001a14, 0x00001a22, 0x00001a3a, - 0x00001a4e, 0x00001a68, 0x00001a92, 0x00001af5, - 0x00001b31, 0x00001b5b, 0x00001b93, 0x00001be7, - 0x00001c60, 0x00001d1a, 0x00001d71, 0x00001da0, - 0x00001dc7, 0x00001e53, -} // Size: 512 bytes - -const koData string = "" + // Size: 7763 bytes - "\x04\x00\x01 ?\x02화살표 키를 사용하여 이동하세요: ↓ ↑ → ←\x02이 대화형 도구는 새로운 채굴자 액터를 생성" + - "하고 그에 대한 기본 구성 레이어를 생성합니다.\x02이 프로세스는 부분적으로 항등원적입니다. 새로운 채굴자 액터가 생성되었고" + - " 후속 단계가 실패하는 경우 사용자는 구성을 완료하기 위해 'curio config new-cluster < 채굴자 ID >'를 " + - "실행해야 합니다.\x02이 대화형 도구는 5분 안에 lotus-miner를 Curio로 이주합니다.\x02각 단계는 확인이 필" + - "요하며 되돌릴 수 있습니다. 언제든지 Ctrl+C를 눌러 종료할 수 있습니다.\x02터미널에서 Ctrl+C가 눌림\x02나는 " + - "원한다:\x02기존의 Lotus-Miner에서 이전하기\x02새로운 채굴자 생성\x02나머지 단계를 중단합니다.\x02Lotu" + - "s-Miner에서 Curio로 이주.\x02%[1]s를 사용하여 웹 인터페이스를 시도하고 더 나은 안내된 개선을 진행하세요." + - "\x02해당하는 경우 이제 시장 노드를 이주할 수 있습니다 (%[1]s).\x02새로운 채굴자 초기화 완료.\x02lotus-mi" + - "ner config.toml을 Curio의 데이터베이스 구성으로 이전 중입니다.\x02API 가져오기 오류: %[1]s\x02Fu" + - "llNode의 API 정보를 가져올 수 없습니다: %[1]w\x02토큰을 가져오는 중 오류 발생: %[1]s\x02레이어에 구성을" + - " 저장하는 중 오류 발생: %[1]s. 마이그레이션 중단\x02Curio 팀은 당신이 사용하는 소프트웨어를 개선하고자 합니다. 팀" + - "에게 `%[1]s`를 사용 중이라고 알려주세요.\x02Curio 팀과 공유하고 싶은 것을 선택하세요.\x02개별 데이터: 채굴" + - "자 ID, Curio 버전, 체인 (%[1]s 또는 %[2]s). 서명됨.\x02집계-익명: 버전, 체인, 및 채굴자 파워 (" + - "버킷).\x02힌트: 나는 어떤 체인에서든 Curio를 실행 중인 사람입니다.\x02아무것도 없습니다.\x02마이너 파워를 가" + - "져오는 중 오류 발생: %[1]s\x02메시지를 마샬하는 중 오류 발생: %[1]s\x02마이너 정보를 가져오는 중 오류 발생" + - ": %[1]s\x02메시지 서명 중 오류 발생: %[1]s\x02메시지 전송 중 오류 발생: %[1]s\x04\x00\x01 =" + - "\x02메시지 전송 중 오류 발생: 상태 %[1]s, 메시지:\x02메시지가 전송되었습니다.\x04\x00\x01 \x08\x02" + - "문서:\x02'%[1]s' 레이어에는 공통 구성이 저장됩니다. 모든 Curio 인스턴스는 %[2]s 인수에 포함시킬 수 있습니" + - "다.\x02기계별 구성 변경을 위해 다른 레이어를 추가할 수 있습니다.\x02Filecoin %[1]s 채널: %[2]s 및 " + - "%[3]s\x02신뢰성 향상을 위한 중복성 사용: 적어도 post 레이어를 사용하여 여러 대의 기계를 시작하십시오: 'curio " + - "run --layers=post'\x02한 개의 데이터베이스는 여러 광부 ID를 제공할 수 있습니다: 각 lotus-miner에 " + - "대해 마이그레이션을 실행하세요.\x02데이터베이스 자격 증명이 %[2]s에 입력되었으므로 지금 %[1]s을 시작하거나 다시 시" + - "작하세요.\x02%[1]s가 Yugabyte에 섹터를 기록하도록 대기 중입니다.\x02광부 ID를 해석하는 중 오류 발생: %" + - "[1]s: ID: %[2]s\x02섹터 확인 중 오류 발생: %[1]s\x02섹터가 데이터베이스에 있습니다. 데이터베이스가 %[1" + - "]s를 위해 준비되었습니다.\x02이제 lotus-miner와 lotus-worker를 종료하고 %[1]s을 실행하세요.\x02계" + - "속하려면 리턴을 누르세요\x02마이그레이션 중단.\x02섹터가 확인되었습니다. %[1]d개의 섹터 위치를 찾았습니다.\x02Y" + - "ugabyte에 연결되었습니다. 스키마가 현재입니다.\x02데이터베이스에서 Sector Indexing을 활성화합니다.\x02co" + - "nfig.toml을 인코딩하는 중 오류가 발생했습니다: %[1]s\x02%[1]s을 Yugabyte 정보로 업데이트하려면 리턴 키" + - "를 누르세요. 변경 사항을 적용하기 전에 해당 폴더에 백업 파일이 작성됩니다.\x02경로를 확장하는 중 오류 발생: %[1]s" + - "\x02config.toml의 파일 모드를 읽는 중 오류가 발생했습니다: %[1]s\x02백업 파일 생성 오류: %[1]s\x02" + - "config.toml 읽기 오류: %[1]s\x02백업 파일 쓰기 오류: %[1]s\x02백업 파일 닫기 오류: %[1]s\x02" + - "config.toml을 쓰는 중 오류가 발생했습니다: %[1]s\x04\x00\x01 \x1f\x02로터스 마이너 재시작.\x02" + - "Yugabyte에 연결됨\x02시작하려면 밀봉 파이프라인이 비어 있고 lotus-miner가 종료되었는지 확인하세요.\x02로터스" + - " 마이너 구성 디렉토리의 위치를 선택하시겠습니까?\x02기타\x02%[1]s에서 사용하는 구성 디렉터리 경로를 입력하세요.\x04" + - "\x00\x01 M\x02경로가 제공되지 않았으므로 마이그레이션을 포기합니다\x02제공된 디렉토리에서 config.toml 파일을" + - " 읽을 수 없습니다. 오류: %[1]s\x02디렉토리에서 저장소를 생성할 수 없습니다: %[1]s. 마이그레이션을 중단합니다." + - "\x02광부 저장소를 잠금 해제할 수 없습니다. 귀하의 광부를 중지해야 합니다: %[1]s\x0a 마이그레이션을 중단합니다." + - "\x02마이너 구성 읽기\x04\x00\x01\x0a\x15\x02단계 완료: %[1]s\x02새 채굴자 액터 초기화 중.\x02" + - "새 채굴자를 생성하기 위한 정보 입력\x02소유자 주소: %[1]s\x02작업자 주소: %[1]s\x02송신자 주소: %[1]" + - "s\x02섹터 크기: %[1]d\x02신뢰 에포크: %[1]d\x02주소를 확인하고 새 채굴자 액터를 생성하려면 계속 진행하세요." + - "\x04\x00\x01 &\x02채굴자 생성 오류 발생: %[1]s\x02소유자 주소 입력\x02주소가 제공되지 않았습니다\x02" + - "주소 구문 분석 실패: %[1]s\x02%[1]s 주소 입력\x02섹터 크기 입력\x02값이 제공되지 않았습니다\x02섹터 크" + - "기 구문 분석 실패: %[1]s\x02신뢰 에포크\x02신뢰도 구문 분석 실패: %[1]s\x02채굴자 액터 생성 실패: %[" + - "1]s\x02%[1]s 채굴자가 성공적으로 생성되었습니다\x02데이터베이스에 연결할 수 없습니다: %[1]s\x02풀 노드 API" + - "에 연결하는 중 오류 발생: %[1]s\x02사전 초기화 단계 완료\x02비밀번호를 위한 랜덤 바이트 생성에 실패했습니다: %" + - "[1]s\x02마이너 생성은 idempotent하지 않으므로 가이드 설정을 다시 실행하지 마십시오. 구성을 완료하려면 'curio" + - " config new-cluster %[1]s'를 실행해야 합니다.\x02FullNode에 대한 API 정보를 가져오는 데 실패했" + - "습니다: %[1]w\x02데몬 노드로부터 인증 토큰을 확인하는 중 오류 발생: %[1]s\x02기본 구성 생성 실패: %[1]" + - "s\x02데이터베이스에 'base' 구성 레이어를 삽입하는 데 실패했습니다: %[1]s\x02이 마이너 주소를 포함한 구성 'ba" + - "se'가 업데이트되었습니다.\x02데이터베이스에서 기본 구성을 로드하는 데 실패했습니다: %[1]s\x02기본 구성을 구문 분석하" + - "는 데 실패했습니다: %[1]s\x02기본 구성을 재생성하는 데 실패했습니다: %[1]s\x02Yugabyte 데이터베이스 설" + - "치에 연결할 정보를 입력하십시오 (https://download.yugabyte.com/)\x02호스트: %[1]s\x02포트" + - ": %[1]s\x02사용자 이름: %[1]s\x02비밀번호: %[1]s\x02데이터베이스: %[1]s\x02계속 연결 및 스키마 " + - "업데이트.\x04\x00\x01 ^\x02데이터베이스 구성 오류가 발생하여 마이그레이션을 포기합니다: %[1]s\x02Yuga" + - "byte 데이터베이스 호스트를 입력하십시오\x02호스트가 제공되지 않았습니다\x02Yugabyte 데이터베이스 %[1]s을 입력하" + - "십시오\x02Yugabyte 데이터베이스에 연결하는 중 오류가 발생했습니다: %[1]s\x02기본 설정 'base'가 이 마이" + - "너의 주소(%[1]s) 및 지갑 설정을 포함하도록 업데이트되었습니다.\x02구성 %[1]s를 %[2]s과 비교하세요. 지갑 주" + - "소 이외의 마이너 ID 사이의 변경 사항은 필요한 실행자를 위한 새로운 최소한의 레이어여야 합니다.\x02'base' 설정이 " + - "이 lotus-miner의 config.toml과 유사하게 만들어졌습니다.\x04\x00\x01 *\x02레이어 %[1]s가 " + - "생성되었습니다.\x04\x00\x01 \x22\x02구성 파일을 사용하려면:\x02Curio를 실행하려면: 기계 또는 cgro" + - "up 격리를 사용하여 다음 명령을 사용하세요 (예제 레이어 선택과 함께):" - -var zhIndex = []uint32{ // 122 elements - // Entry 0 - 1F - 0x00000000, 0x00000033, 0x0000008b, 0x00000134, - 0x0000017c, 0x000001cb, 0x000001e4, 0x000001f1, - 0x00000211, 0x0000022a, 0x00000240, 0x0000025d, - 0x000002a5, 0x000002e6, 0x00000302, 0x00000347, - 0x00000364, 0x0000038d, 0x000003ab, 0x000003e4, - 0x00000438, 0x00000465, 0x000004b4, 0x000004ef, - 0x00000524, 0x0000052e, 0x00000552, 0x00000570, - 0x00000594, 0x000005b2, 0x000005d0, 0x00000605, - // Entry 20 - 3F - 0x00000618, 0x00000627, 0x00000681, 0x000006be, - 0x000006e6, 0x00000745, 0x00000795, 0x000007e8, - 0x0000080e, 0x0000083b, 0x00000859, 0x00000895, - 0x000008d9, 0x000008e9, 0x000008f9, 0x0000092c, - 0x00000959, 0x0000097e, 0x000009a1, 0x00000a19, - 0x00000a37, 0x00000a66, 0x00000a8a, 0x00000aaf, - 0x00000ad3, 0x00000af7, 0x00000b1a, 0x00000b3a, - 0x00000b4f, 0x00000b9a, 0x00000bca, 0x00000bd1, - // Entry 40 - 5F - 0x00000bfb, 0x00000c1f, 0x00000c63, 0x00000c95, - 0x00000cde, 0x00000cf1, 0x00000d0b, 0x00000d2a, - 0x00000d4f, 0x00000d67, 0x00000d7c, 0x00000d94, - 0x00000da8, 0x00000dbf, 0x00000df0, 0x00000e15, - 0x00000e2b, 0x00000e3b, 0x00000e55, 0x00000e69, - 0x00000e7c, 0x00000e89, 0x00000ea9, 0x00000eb9, - 0x00000ed6, 0x00000ef6, 0x00000f10, 0x00000f2d, - 0x00000f5e, 0x00000f77, 0x00000fa0, 0x0000102d, - // Entry 60 - 7F - 0x00001059, 0x00001094, 0x000010b4, 0x000010e5, - 0x00001118, 0x00001145, 0x00001166, 0x0000118c, - 0x000011e6, 0x000011f5, 0x00001204, 0x00001216, - 0x00001225, 0x00001237, 0x00001256, 0x0000128e, - 0x000012b3, 0x000012c3, 0x000012e1, 0x0000130d, - 0x0000135e, 0x000013e0, 0x00001427, 0x00001441, - 0x00001459, 0x000014b0, -} // Size: 512 bytes - -const zhData string = "" + // Size: 5296 bytes - "\x04\x00\x01 .\x02使用箭头键进行导航:↓ ↑ → ←\x02此交互式工具将创建一个新的矿工角色,并为其创建基本配置层。\x02" + - "该过程部分幂等。一旦创建了新的矿工角色,并且随后的步骤失败,用户需要运行 'curio config new-cluster < 矿工 ID" + - " >' 来完成配置。\x02这个交互式工具可以在5分钟内将lotus-miner迁移到Curio。\x02每一步都需要您的确认,并且可以撤销。随" + - "时按Ctrl+C退出。\x02在终端中按下Ctrl+C\x02我想要:\x02从现有的 Lotus-Miner 迁移\x02创建一个新的矿工" + - "\x02中止剩余步骤。\x02Lotus-Miner到Curio迁移。\x02尝试使用%[1]s的网络界面进行更进一步的指导性改进。\x02如果" + - "适用,您现在可以迁移您的市场节点(%[1]s)。\x02新矿工初始化完成。\x02将 lotus-miner config.toml 迁移到" + - " Curio 的数据库配置中。\x02获取 API 时出错:%[1]s\x02无法获取FullNode的API信息:%[1]w\x02获取令牌时" + - "出错:%[1]s\x02保存配置到层时出错:%[1]s。正在中止迁移\x02Curio 团队希望改进您使用的软件。告诉团队您正在使用 `%[" + - "1]s`。\x02选择您想与Curio团队分享的内容。\x02个人数据:矿工 ID,Curio 版本,链(%[1]s 或 %[2]s)。签名。" + - "\x02聚合-匿名:版本,链和矿工算力(分桶)。\x02提示:我是在任何链上运行 Curio 的人。\x02没有。\x02获取矿工功率时出错:%" + - "[1]s\x02整理消息时出错:%[1]s\x02获取矿工信息时出错:%[1]s\x02签署消息时出错:%[1]s\x02发送消息时出错:%[1" + - "]s\x04\x00\x01 0\x02发送消息时出错:状态%[1]s,消息:\x02消息已发送。\x04\x00\x01 \x0a\x02文档" + - ":\x02'%[1]s'层存储通用配置。所有Curio实例都可以在其%[2]s参数中包含它。\x02您可以添加其他层进行每台机器的配置更改。" + - "\x02Filecoin %[1]s 频道:%[2]s 和 %[3]s\x02通过冗余增加可靠性:使用至少后层启动多台机器:'curio run" + - " --layers=post'\x02一个数据库可以服务多个矿工ID:为每个lotus-miner运行迁移。\x02请立即启动(或重新启动)%[" + - "1]s,因为数据库凭据已在%[2]s中。\x02等待%[1]s将扇区写入Yugabyte。\x02解释矿工ID时出错:%[1]s:ID:%[2]" + - "s\x02验证扇区时出错:%[1]s\x02扇区在数据库中。数据库已准备好用于%[1]s。\x02现在关闭lotus-miner和lotus-w" + - "orker,改为使用%[1]s运行。\x02按回车继续\x02中止迁移。\x02扇区已验证。发现了%[1]d个扇区位置。\x02已连接到Yuga" + - "byte。模式是当前的。\x02在数据库中启用扇区索引。\x02编码config.toml时出错:%[1]s\x02按回车键更新 %[1]s 以" + - "包含 Yugabyte 信息。在进行更改之前,将在该文件夹中写入备份文件。\x02扩展路径时出错:%[1]s\x02读取config.tom" + - "l文件模式时出错:%[1]s\x02创建备份文件时出错:%[1]s\x02读取 config.toml 时出错:%[1]s\x02写入备份文件时" + - "出错:%[1]s\x02关闭备份文件时出错:%[1]s\x02写入config.toml时出错:%[1]s\x04\x00\x01 \x1b" + - "\x02重新启动Lotus Miner。\x02已连接到Yugabyte\x02开始之前,请确保您的密封管道已排空并关闭lotus-miner。" + - "\x02选择您的lotus-miner配置目录的位置?\x02其他\x02输入%[1]s使用的配置目录的路径\x04\x00\x01 \x1f" + - "\x02未提供路径,放弃迁移\x02无法读取提供的目录中的config.toml文件,错误:%[1]s\x02无法从目录创建repo:%[1]s" + - "。 中止迁移\x02无法锁定矿工repo。 您的矿工必须停止:%[1]s\x0a 中止迁移\x02读取矿工配置\x04\x00\x01" + - "\x0a\x15\x02步骤完成:%[1]s\x02初始化新的矿工角色。\x02输入创建新矿工所需的信息\x02所有者地址:%[1]s\x02工" + - "作地址:%[1]s\x02发送者地址:%[1]s\x02扇区大小: %[1]d\x02置信度时期: %[1]d\x02继续验证地址并创建新的" + - "矿工角色。\x04\x00\x01 \x02矿工创建错误发生: %[1]s\x02输入所有者地址\x02未提供地址\x02解析地址失败: " + - "%[1]s\x02输入 %[1]s 地址\x02输入扇区大小\x02未提供值\x02解析扇区大小失败: %[1]s\x02置信度时期\x02解析" + - "置信度失败: %[1]s\x02创建矿工角色失败: %[1]s\x02矿工 %[1]s 创建成功\x02无法访问数据库: %[1]s\x02" + - "连接到完整节点 API 时发生错误: %[1]s\x02预初始化步骤完成\x02生成密码的随机字节失败: %[1]s\x02请不要再次运行引" + - "导设置,因为矿工创建不是幂等的。 您需要运行 'curio config new-cluster %[1]s' 来完成配置。\x02无法获取" + - " FullNode 的 API 信息: %[1]w\x02无法验证来自守护进程节点的授权令牌: %[1]s\x02无法生成默认配置: %[1]s" + - "\x02无法将 'base' 配置层插入数据库: %[1]s\x02配置 'base' 已更新以包含此矿工的地址\x02从数据库加载基本配置失败" + - ":%[1]s\x02解析基本配置失败:%[1]s\x02重新生成基本配置失败: %[1]s\x02输入连接到您的Yugabyte数据库安装的" + - "信息(https://download.yugabyte.com/)\x02主机:%[1]s\x02端口:%[1]s\x02用户名:%[1]" + - "s\x02密码:%[1]s\x02数据库:%[1]s\x02继续连接和更新架构。\x04\x00\x01 3\x02发生数据库配置错误,放弃迁移" + - ":%[1]s\x02输入Yugabyte数据库主机(S)\x02未提供主机\x02输入Yugabyte数据库 %[1]s\x02连接到Yug" + - "abyte数据库时出错:%[1]s\x02'base'配置已更新,包括该矿工的地址(%[1]s)及其钱包设置。\x02比较配置%[1]s和%[2" + - "]s。矿工ID之间除了钱包地址的变化应该是需要的运行者的一个新的、最小的层。\x02'base'配置已创建,以类似于这个lotus-miner的" + - "config.toml。\x04\x00\x01 \x15\x02层%[1]s已创建。\x04\x00\x01 \x13\x02要使用配置:" + - "\x02运行Curio:使用机器或cgroup隔离,使用命令(附带示例层选择):" - - // Total table size 20243 bytes (19KiB); checksum: AB52E150 diff --git a/cmd/curio/internal/translations/knowns/main.go b/cmd/curio/internal/translations/knowns/main.go deleted file mode 100644 index a30a94065..000000000 --- a/cmd/curio/internal/translations/knowns/main.go +++ /dev/null @@ -1,82 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "os" - "path" - - "github.com/samber/lo" -) - -func main() { - for _, arg := range os.Args { - handleKnowns(arg) - } -} - -func handleKnowns(pathStart string) { - outpath := path.Join(pathStart, "out.gotext.json") - b, err := os.ReadFile(outpath) - if err != nil { - fmt.Println("cannot open "+outpath+":", err) - return - } - type TMsg struct { - ID string `json:"id"` - Translation string `json:"translation"` - Message string `json:"message"` - Placeholder json.RawMessage `json:"placeholder"` - } - type Dataformat struct { - Language string `json:"language"` - Messages []TMsg `json:"messages"` - } - var outData Dataformat - err = json.NewDecoder(bytes.NewBuffer(b)).Decode(&outData) - if err != nil { - fmt.Println("cannot decode "+outpath+":", err) - return - } - - f, err := os.Open(path.Join(pathStart, "messages.gotext.json")) - if err != nil { - fmt.Println("cannot open "+path.Join(pathStart, "messages.gotext.json")+":", err) - return - } - defer func() { _ = f.Close() }() - - var msgData Dataformat - err = json.NewDecoder(f).Decode(&msgData) - if err != nil { - fmt.Println("cannot decode "+path.Join(pathStart, "messages.gotext.json")+":", err) - return - } - - knowns := map[string]string{} - for _, msg := range msgData.Messages { - knowns[msg.ID] = msg.Translation - } - - toTranslate := lo.Filter(outData.Messages, func(msg TMsg, _ int) bool { - _, ok := knowns[msg.ID] - return !ok - }) - - outData.Messages = toTranslate // drop the "done" messages - var outJSON bytes.Buffer - enc := json.NewEncoder(&outJSON) - enc.SetIndent(" ", " ") - err = enc.Encode(outData) - if err != nil { - fmt.Println("cannot encode "+outpath+":", err) - return - } - err = os.WriteFile(outpath, outJSON.Bytes(), 0644) - if err != nil { - fmt.Println("cannot write "+outpath+":", err) - return - } - fmt.Println("rearranged successfully") -} diff --git a/cmd/curio/internal/translations/locales/en/out.gotext.json b/cmd/curio/internal/translations/locales/en/out.gotext.json deleted file mode 100644 index 604678622..000000000 --- a/cmd/curio/internal/translations/locales/en/out.gotext.json +++ /dev/null @@ -1,1636 +0,0 @@ -{ - "language": "en", - "messages": [ - { - "id": "Use the arrow keys to navigate: ↓ ↑ → ←", - "message": "Use the arrow keys to navigate: ↓ ↑ → ←", - "translation": "Use the arrow keys to navigate: ↓ ↑ → ←", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "This interactive tool creates a new miner actor and creates the basic configuration layer for it.", - "message": "This interactive tool creates a new miner actor and creates the basic configuration layer for it.", - "translation": "This interactive tool creates a new miner actor and creates the basic configuration layer for it.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "This process is partially idempotent. Once a new miner actor has been created and subsequent steps fail, the user need to run 'curio config new-cluster \u003c miner ID \u003e' to finish the configuration.", - "message": "This process is partially idempotent. Once a new miner actor has been created and subsequent steps fail, the user need to run 'curio config new-cluster \u003c miner ID \u003e' to finish the configuration.", - "translation": "This process is partially idempotent. Once a new miner actor has been created and subsequent steps fail, the user need to run 'curio config new-cluster \u003c miner ID \u003e' to finish the configuration.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "This interactive tool migrates lotus-miner to Curio in 5 minutes.", - "message": "This interactive tool migrates lotus-miner to Curio in 5 minutes.", - "translation": "This interactive tool migrates lotus-miner to Curio in 5 minutes.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Each step needs your confirmation and can be reversed. Press Ctrl+C to exit at any time.", - "message": "Each step needs your confirmation and can be reversed. Press Ctrl+C to exit at any time.", - "translation": "Each step needs your confirmation and can be reversed. Press Ctrl+C to exit at any time.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Ctrl+C pressed in Terminal", - "message": "Ctrl+C pressed in Terminal", - "translation": "Ctrl+C pressed in Terminal", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "I want to:", - "message": "I want to:", - "translation": "I want to:", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Migrate from existing Lotus-Miner", - "message": "Migrate from existing Lotus-Miner", - "translation": "Migrate from existing Lotus-Miner", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Create a new miner", - "message": "Create a new miner", - "translation": "Create a new miner", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Aborting remaining steps.", - "message": "Aborting remaining steps.", - "translation": "Aborting remaining steps.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]v", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Lotus-Miner to Curio Migration.", - "message": "Lotus-Miner to Curio Migration.", - "translation": "Lotus-Miner to Curio Migration.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Try the web interface with {Rendercurio_run___layersgui} for further guided improvements.", - "message": "Try the web interface with {Rendercurio_run___layersgui} for further guided improvements.", - "translation": "Try the web interface with {Rendercurio_run___layersgui} for further guided improvements.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Rendercurio_run___layersgui", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "code.Render(\"curio run --layers=gui\")" - } - ], - "fuzzy": true - }, - { - "id": "You can now migrate your market node ({Boost}), if applicable.", - "message": "You can now migrate your market node ({Boost}), if applicable.", - "translation": "You can now migrate your market node ({Boost}), if applicable.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Boost", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "\"Boost\"" - } - ], - "fuzzy": true - }, - { - "id": "New Miner initialization complete.", - "message": "New Miner initialization complete.", - "translation": "New Miner initialization complete.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Migrating lotus-miner config.toml to Curio in-database configuration.", - "message": "Migrating lotus-miner config.toml to Curio in-database configuration.", - "translation": "Migrating lotus-miner config.toml to Curio in-database configuration.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Error getting API: {Error}", - "message": "Error getting API: {Error}", - "translation": "Error getting API: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "could not get API info for FullNode: {Err}", - "message": "could not get API info for FullNode: {Err}", - "translation": "could not get API info for FullNode: {Err}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Err", - "string": "%[1]w", - "type": "error", - "underlyingType": "interface{Error() string}", - "argNum": 1, - "expr": "err" - } - ], - "fuzzy": true - }, - { - "id": "Error getting token: {Error}", - "message": "Error getting token: {Error}", - "translation": "Error getting token: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Error saving config to layer: {Error}. Aborting Migration", - "message": "Error saving config to layer: {Error}. Aborting Migration", - "translation": "Error saving config to layer: {Error}. Aborting Migration", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "The Curio team wants to improve the software you use. Tell the team you're using `{Curio}`.", - "message": "The Curio team wants to improve the software you use. Tell the team you're using `{Curio}`.", - "translation": "The Curio team wants to improve the software you use. Tell the team you're using `{Curio}`.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Curio", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "\"curio\"" - } - ], - "fuzzy": true - }, - { - "id": "Select what you want to share with the Curio team.", - "message": "Select what you want to share with the Curio team.", - "translation": "Select what you want to share with the Curio team.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Individual Data: Miner ID, Curio version, chain ({Mainnet} or {Calibration}). Signed.", - "message": "Individual Data: Miner ID, Curio version, chain ({Mainnet} or {Calibration}). Signed.", - "translation": "Individual Data: Miner ID, Curio version, chain ({Mainnet} or {Calibration}). Signed.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Mainnet", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "\"mainnet\"" - }, - { - "id": "Calibration", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "\"calibration\"" - } - ], - "fuzzy": true - }, - { - "id": "Aggregate-Anonymous: version, chain, and Miner power (bucketed).", - "message": "Aggregate-Anonymous: version, chain, and Miner power (bucketed).", - "translation": "Aggregate-Anonymous: version, chain, and Miner power (bucketed).", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Hint: I am someone running Curio on whichever chain.", - "message": "Hint: I am someone running Curio on whichever chain.", - "translation": "Hint: I am someone running Curio on whichever chain.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Nothing.", - "message": "Nothing.", - "translation": "Nothing.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Error getting miner power: {Error}", - "message": "Error getting miner power: {Error}", - "translation": "Error getting miner power: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Error marshalling message: {Error}", - "message": "Error marshalling message: {Error}", - "translation": "Error marshalling message: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Error getting miner info: {Error}", - "message": "Error getting miner info: {Error}", - "translation": "Error getting miner info: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Error signing message: {Error}", - "message": "Error signing message: {Error}", - "translation": "Error signing message: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Error sending message: {Error}", - "message": "Error sending message: {Error}", - "translation": "Error sending message: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Error sending message: Status {Status}, Message:", - "message": "Error sending message: Status {Status}, Message:", - "translation": "Error sending message: Status {Status}, Message:", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Status", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "resp.Status" - }, - { - "id": "Stringb", - "string": "%[2]v", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "string(b)" - } - ], - "fuzzy": true - }, - { - "id": "Message sent.", - "message": "Message sent.", - "translation": "Message sent.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Documentation:", - "message": "Documentation:", - "translation": "Documentation:", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "The '{Base}' layer stores common configuration. All curio instances can include it in their {__layers} argument.", - "message": "The '{Base}' layer stores common configuration. All curio instances can include it in their {__layers} argument.", - "translation": "The '{Base}' layer stores common configuration. All curio instances can include it in their {__layers} argument.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Base", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "\"base\"" - }, - { - "id": "__layers", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "\"--layers\"" - } - ], - "fuzzy": true - }, - { - "id": "You can add other layers for per-machine configuration changes.", - "message": "You can add other layers for per-machine configuration changes.", - "translation": "You can add other layers for per-machine configuration changes.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Filecoin {Slack} channels: {Fil_curio_help} and {Fil_curio_dev}", - "message": "Filecoin {Slack} channels: {Fil_curio_help} and {Fil_curio_dev}", - "translation": "Filecoin {Slack} channels: {Fil_curio_help} and {Fil_curio_dev}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Slack", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "\"Slack\"" - }, - { - "id": "Fil_curio_help", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "\"#fil-curio-help\"" - }, - { - "id": "Fil_curio_dev", - "string": "%[3]s", - "type": "string", - "underlyingType": "string", - "argNum": 3, - "expr": "\"#fil-curio-dev\"" - } - ], - "fuzzy": true - }, - { - "id": "Increase reliability using redundancy: start multiple machines with at-least the post layer: 'curio run --layers=post'", - "message": "Increase reliability using redundancy: start multiple machines with at-least the post layer: 'curio run --layers=post'", - "translation": "Increase reliability using redundancy: start multiple machines with at-least the post layer: 'curio run --layers=post'", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "One database can serve multiple miner IDs: Run a migration for each lotus-miner.", - "message": "One database can serve multiple miner IDs: Run a migration for each lotus-miner.", - "translation": "One database can serve multiple miner IDs: Run a migration for each lotus-miner.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Please start (or restart) {Lotus_miner} now that database credentials are in {Toml}.", - "message": "Please start (or restart) {Lotus_miner} now that database credentials are in {Toml}.", - "translation": "Please start (or restart) {Lotus_miner} now that database credentials are in {Toml}.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Lotus_miner", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "\"lotus-miner\"" - }, - { - "id": "Toml", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "\"config.toml\"" - } - ], - "fuzzy": true - }, - { - "id": "Waiting for {Lotus_miner} to write sectors into Yugabyte.", - "message": "Waiting for {Lotus_miner} to write sectors into Yugabyte.", - "translation": "Waiting for {Lotus_miner} to write sectors into Yugabyte.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Lotus_miner", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "\"lotus-miner\"" - } - ], - "fuzzy": true - }, - { - "id": "Error interpreting miner ID: {Error}: ID: {String}", - "message": "Error interpreting miner ID: {Error}: ID: {String}", - "translation": "Error interpreting miner ID: {Error}: ID: {String}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - }, - { - "id": "String", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "d.MinerID.String()" - } - ], - "fuzzy": true - }, - { - "id": "Error verifying sectors: {Error}", - "message": "Error verifying sectors: {Error}", - "translation": "Error verifying sectors: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "The sectors are in the database. The database is ready for {Curio}.", - "message": "The sectors are in the database. The database is ready for {Curio}.", - "translation": "The sectors are in the database. The database is ready for {Curio}.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Curio", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "\"Curio\"" - } - ], - "fuzzy": true - }, - { - "id": "Now shut down lotus-miner and lotus-worker and use run {Rendercurio_run} instead.", - "message": "Now shut down lotus-miner and lotus-worker and use run {Rendercurio_run} instead.", - "translation": "Now shut down lotus-miner and lotus-worker and use run {Rendercurio_run} instead.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Rendercurio_run", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "code.Render(\"curio run\")" - } - ], - "fuzzy": true - }, - { - "id": "Press return to continue", - "message": "Press return to continue", - "translation": "Press return to continue", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Aborting migration.", - "message": "Aborting migration.", - "translation": "Aborting migration.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Sectors verified. {I} sector locations found.", - "message": "Sectors verified. {I} sector locations found.", - "translation": "Sectors verified. {I} sector locations found.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "I", - "string": "%[1]d", - "type": "[]int", - "underlyingType": "[]int", - "argNum": 1, - "expr": "i" - } - ], - "fuzzy": true - }, - { - "id": "Connected to Yugabyte. Schema is current.", - "message": "Connected to Yugabyte. Schema is current.", - "translation": "Connected to Yugabyte. Schema is current.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Enabling Sector Indexing in the database.", - "message": "Enabling Sector Indexing in the database.", - "translation": "Enabling Sector Indexing in the database.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Error encoding config.toml: {Error}", - "message": "Error encoding config.toml: {Error}", - "translation": "Error encoding config.toml: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Press return to update {Toml} with Yugabyte info. A Backup file will be written to that folder before changes are made.", - "message": "Press return to update {Toml} with Yugabyte info. A Backup file will be written to that folder before changes are made.", - "translation": "Press return to update {Toml} with Yugabyte info. A Backup file will be written to that folder before changes are made.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Toml", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "\"config.toml\"" - } - ], - "fuzzy": true - }, - { - "id": "Error expanding path: {Error}", - "message": "Error expanding path: {Error}", - "translation": "Error expanding path: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Error reading filemode of config.toml: {Error}", - "message": "Error reading filemode of config.toml: {Error}", - "translation": "Error reading filemode of config.toml: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Error creating backup file: {Error}", - "message": "Error creating backup file: {Error}", - "translation": "Error creating backup file: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Error reading config.toml: {Error}", - "message": "Error reading config.toml: {Error}", - "translation": "Error reading config.toml: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Error writing backup file: {Error}", - "message": "Error writing backup file: {Error}", - "translation": "Error writing backup file: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Error closing backup file: {Error}", - "message": "Error closing backup file: {Error}", - "translation": "Error closing backup file: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Error writing config.toml: {Error}", - "message": "Error writing config.toml: {Error}", - "translation": "Error writing config.toml: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Restart Lotus Miner.", - "message": "Restart Lotus Miner.", - "translation": "Restart Lotus Miner.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Connected to Yugabyte", - "message": "Connected to Yugabyte", - "translation": "Connected to Yugabyte", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "To start, ensure your sealing pipeline is drained and shut-down lotus-miner.", - "message": "To start, ensure your sealing pipeline is drained and shut-down lotus-miner.", - "translation": "To start, ensure your sealing pipeline is drained and shut-down lotus-miner.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Select the location of your lotus-miner config directory?", - "message": "Select the location of your lotus-miner config directory?", - "translation": "Select the location of your lotus-miner config directory?", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Other", - "message": "Other", - "translation": "Other", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Enter the path to the configuration directory used by {Lotus_miner}", - "message": "Enter the path to the configuration directory used by {Lotus_miner}", - "translation": "Enter the path to the configuration directory used by {Lotus_miner}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Lotus_miner", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "\"lotus-miner\"" - } - ], - "fuzzy": true - }, - { - "id": "No path provided, abandoning migration", - "message": "No path provided, abandoning migration", - "translation": "No path provided, abandoning migration", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Cannot read the config.toml file in the provided directory, Error: {Error}", - "message": "Cannot read the config.toml file in the provided directory, Error: {Error}", - "translation": "Cannot read the config.toml file in the provided directory, Error: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Could not create repo from directory: {Error}. Aborting migration", - "message": "Could not create repo from directory: {Error}. Aborting migration", - "translation": "Could not create repo from directory: {Error}. Aborting migration", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Could not lock miner repo. Your miner must be stopped: {Error}\n Aborting migration", - "message": "Could not lock miner repo. Your miner must be stopped: {Error}\n Aborting migration", - "translation": "Could not lock miner repo. Your miner must be stopped: {Error}\n Aborting migration", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Read Miner Config", - "message": "Read Miner Config", - "translation": "Read Miner Config", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Step Complete: {Step}", - "message": "Step Complete: {Step}", - "translation": "Step Complete: {Step}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Step", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "step" - } - ], - "fuzzy": true - }, - { - "id": "Initializing a new miner actor.", - "message": "Initializing a new miner actor.", - "translation": "Initializing a new miner actor.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Enter the info to create a new miner", - "message": "Enter the info to create a new miner", - "translation": "Enter the info to create a new miner", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Owner Address: {String}", - "message": "Owner Address: {String}", - "translation": "Owner Address: {String}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "String", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "d.owner.String()" - } - ], - "fuzzy": true - }, - { - "id": "Worker Address: {String}", - "message": "Worker Address: {String}", - "translation": "Worker Address: {String}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "String", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "d.worker.String()" - } - ], - "fuzzy": true - }, - { - "id": "Sender Address: {String}", - "message": "Sender Address: {String}", - "translation": "Sender Address: {String}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "String", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "d.sender.String()" - } - ], - "fuzzy": true - }, - { - "id": "Sector Size: {Ssize}", - "message": "Sector Size: {Ssize}", - "translation": "Sector Size: {Ssize}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Ssize", - "string": "%[1]d", - "type": "github.com/filecoin-project/go-state-types/abi.SectorSize", - "underlyingType": "uint64", - "argNum": 1, - "expr": "d.ssize" - } - ], - "fuzzy": true - }, - { - "id": "Confidence epochs: {Confidence}", - "message": "Confidence epochs: {Confidence}", - "translation": "Confidence epochs: {Confidence}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Confidence", - "string": "%[1]d", - "type": "uint64", - "underlyingType": "uint64", - "argNum": 1, - "expr": "d.confidence" - } - ], - "fuzzy": true - }, - { - "id": "Continue to verify the addresses and create a new miner actor.", - "message": "Continue to verify the addresses and create a new miner actor.", - "translation": "Continue to verify the addresses and create a new miner actor.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Miner creation error occurred: {Error}", - "message": "Miner creation error occurred: {Error}", - "translation": "Miner creation error occurred: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Enter the owner address", - "message": "Enter the owner address", - "translation": "Enter the owner address", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "No address provided", - "message": "No address provided", - "translation": "No address provided", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Failed to parse the address: {Error}", - "message": "Failed to parse the address: {Error}", - "translation": "Failed to parse the address: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Enter {Stringworker_senderi_1} address", - "message": "Enter {Stringworker_senderi_1} address", - "translation": "Enter {Stringworker_senderi_1} address", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Stringworker_senderi_1", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "[]string{\"worker\", \"sender\"}[i-1]" - } - ], - "fuzzy": true - }, - { - "id": "Enter the sector size", - "message": "Enter the sector size", - "translation": "Enter the sector size", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "No value provided", - "message": "No value provided", - "translation": "No value provided", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Failed to parse sector size: {Error}", - "message": "Failed to parse sector size: {Error}", - "translation": "Failed to parse sector size: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Confidence epochs", - "message": "Confidence epochs", - "translation": "Confidence epochs", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Failed to parse confidence: {Error}", - "message": "Failed to parse confidence: {Error}", - "translation": "Failed to parse confidence: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Failed to create the miner actor: {Error}", - "message": "Failed to create the miner actor: {Error}", - "translation": "Failed to create the miner actor: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Miner {String} created successfully", - "message": "Miner {String} created successfully", - "translation": "Miner {String} created successfully", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "String", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "miner.String()" - } - ], - "fuzzy": true - }, - { - "id": "Cannot reach the DB: {Error}", - "message": "Cannot reach the DB: {Error}", - "translation": "Cannot reach the DB: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Error connecting to full node API: {Error}", - "message": "Error connecting to full node API: {Error}", - "translation": "Error connecting to full node API: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Pre-initialization steps complete", - "message": "Pre-initialization steps complete", - "translation": "Pre-initialization steps complete", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Failed to generate random bytes for secret: {Error}", - "message": "Failed to generate random bytes for secret: {Error}", - "translation": "Failed to generate random bytes for secret: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster {String}' to finish the configuration", - "message": "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster {String}' to finish the configuration", - "translation": "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster {String}' to finish the configuration", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "String", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "d.MinerID.String()" - } - ], - "fuzzy": true - }, - { - "id": "Failed to get API info for FullNode: {Err}", - "message": "Failed to get API info for FullNode: {Err}", - "translation": "Failed to get API info for FullNode: {Err}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Err", - "string": "%[1]w", - "type": "error", - "underlyingType": "interface{Error() string}", - "argNum": 1, - "expr": "err" - } - ], - "fuzzy": true - }, - { - "id": "Failed to verify the auth token from daemon node: {Error}", - "message": "Failed to verify the auth token from daemon node: {Error}", - "translation": "Failed to verify the auth token from daemon node: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Failed to generate default config: {Error}", - "message": "Failed to generate default config: {Error}", - "translation": "Failed to generate default config: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Failed to insert 'base' config layer in database: {Error}", - "message": "Failed to insert 'base' config layer in database: {Error}", - "translation": "Failed to insert 'base' config layer in database: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Configuration 'base' was updated to include this miner's address", - "message": "Configuration 'base' was updated to include this miner's address", - "translation": "Configuration 'base' was updated to include this miner's address", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Failed to load base config from database: {Error}", - "message": "Failed to load base config from database: {Error}", - "translation": "Failed to load base config from database: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Failed to parse base config: {Error}", - "message": "Failed to parse base config: {Error}", - "translation": "Failed to parse base config: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Failed to regenerate base config: {Error}", - "message": "Failed to regenerate base config: {Error}", - "translation": "Failed to regenerate base config: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Enter the info to connect to your Yugabyte database installation (https://download.yugabyte.com/)", - "message": "Enter the info to connect to your Yugabyte database installation (https://download.yugabyte.com/)", - "translation": "Enter the info to connect to your Yugabyte database installation (https://download.yugabyte.com/)", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Host: {Hosts_}", - "message": "Host: {Hosts_}", - "translation": "Host: {Hosts_}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Hosts_", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "strings.Join(harmonyCfg.Hosts, \",\")" - } - ], - "fuzzy": true - }, - { - "id": "Port: {Port}", - "message": "Port: {Port}", - "translation": "Port: {Port}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Port", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "harmonyCfg.Port" - } - ], - "fuzzy": true - }, - { - "id": "Username: {Username}", - "message": "Username: {Username}", - "translation": "Username: {Username}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Username", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "harmonyCfg.Username" - } - ], - "fuzzy": true - }, - { - "id": "Password: {Password}", - "message": "Password: {Password}", - "translation": "Password: {Password}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Password", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "harmonyCfg.Password" - } - ], - "fuzzy": true - }, - { - "id": "Database: {Database}", - "message": "Database: {Database}", - "translation": "Database: {Database}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Database", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "harmonyCfg.Database" - } - ], - "fuzzy": true - }, - { - "id": "Continue to connect and update schema.", - "message": "Continue to connect and update schema.", - "translation": "Continue to connect and update schema.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Database config error occurred, abandoning migration: {Error}", - "message": "Database config error occurred, abandoning migration: {Error}", - "translation": "Database config error occurred, abandoning migration: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Enter the Yugabyte database host(s)", - "message": "Enter the Yugabyte database host(s)", - "translation": "Enter the Yugabyte database host(s)", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "No host provided", - "message": "No host provided", - "translation": "No host provided", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Enter the Yugabyte database {Stringport_username_password_databasei_1}", - "message": "Enter the Yugabyte database {Stringport_username_password_databasei_1}", - "translation": "Enter the Yugabyte database {Stringport_username_password_databasei_1}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Stringport_username_password_databasei_1", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "[]string{\"port\", \"username\", \"password\", \"database\"}[i-1]" - } - ], - "fuzzy": true - }, - { - "id": "Error connecting to Yugabyte database: {Error}", - "message": "Error connecting to Yugabyte database: {Error}", - "translation": "Error connecting to Yugabyte database: {Error}", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ], - "fuzzy": true - }, - { - "id": "Configuration 'base' was updated to include this miner's address ({MinerAddress}) and its wallet setup.", - "message": "Configuration 'base' was updated to include this miner's address ({MinerAddress}) and its wallet setup.", - "translation": "Configuration 'base' was updated to include this miner's address ({MinerAddress}) and its wallet setup.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "MinerAddress", - "string": "%[1]s", - "type": "github.com/filecoin-project/go-address.Address", - "underlyingType": "struct{str string}", - "argNum": 1, - "expr": "minerAddress" - } - ], - "fuzzy": true - }, - { - "id": "Compare the configurations {Base} to {MinerAddresses0}. Changes between the miner IDs other than wallet addreses should be a new, minimal layer for runners that need it.", - "message": "Compare the configurations {Base} to {MinerAddresses0}. Changes between the miner IDs other than wallet addreses should be a new, minimal layer for runners that need it.", - "translation": "Compare the configurations {Base} to {MinerAddresses0}. Changes between the miner IDs other than wallet addreses should be a new, minimal layer for runners that need it.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "Base", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "\"base\"" - }, - { - "id": "MinerAddresses0", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "\"mig-\" + curioCfg.Addresses[0].MinerAddresses[0]" - } - ], - "fuzzy": true - }, - { - "id": "Configuration 'base' was created to resemble this lotus-miner's config.toml .", - "message": "Configuration 'base' was created to resemble this lotus-miner's config.toml .", - "translation": "Configuration 'base' was created to resemble this lotus-miner's config.toml .", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Layer {LayerName} created.", - "message": "Layer {LayerName} created.", - "translation": "Layer {LayerName} created.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "LayerName", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "layerName" - } - ], - "fuzzy": true - }, - { - "id": "To work with the config:", - "message": "To work with the config:", - "translation": "To work with the config:", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "To run Curio: With machine or cgroup isolation, use the command (with example layer selection):", - "message": "To run Curio: With machine or cgroup isolation, use the command (with example layer selection):", - "translation": "To run Curio: With machine or cgroup isolation, use the command (with example layer selection):", - "translatorComment": "Copied from source.", - "fuzzy": true - } - ] -} \ No newline at end of file diff --git a/cmd/curio/internal/translations/locales/ko/messages.gotext.json b/cmd/curio/internal/translations/locales/ko/messages.gotext.json deleted file mode 100644 index bac1140e8..000000000 --- a/cmd/curio/internal/translations/locales/ko/messages.gotext.json +++ /dev/null @@ -1,1106 +0,0 @@ -{ - "language": "ko", - "messages": [ - { - "id": "This interactive tool will walk you through migration of Curio.\nPress Ctrl+C to exit at any time.", - "message": "This interactive tool will walk you through migration of Curio.\nPress Ctrl+C to exit at any time.", - "translation": "이 대화형 도구는 Curio 마이그레이션 과정을 안내합니다.\n언제든지 종료하려면 Ctrl+C를 누르십시오." - }, - { - "id": "This tool confirms each action it does.", - "message": "This tool confirms each action it does.", - "translation": "이 도구는 수행하는 각 작업을 확인합니다." - }, - { - "id": "Ctrl+C pressed in Terminal", - "message": "Ctrl+C pressed in Terminal", - "translation": "터미널에서 Ctrl+C가 눌림" - }, - { - "id": "Verifying Sectors exist in Yugabyte.", - "message": "Verifying Sectors exist in Yugabyte.", - "translation": "Yugabyte에 섹터가 존재하는지 확인 중." - }, - { - "id": "Error verifying sectors: {Error}", - "message": "Error verifying sectors: {Error}", - "translation": "섹터 확인 중 오류 발생: {Error}", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ] - }, - { - "id": "Sectors verified. {I} sectors found.", - "message": "Sectors verified. {I} sectors found.", - "translation": "섹터가 확인되었습니다. {I}개의 섹터가 발견되었습니다.", - "placeholders": [ - { - "id": "I", - "string": "%[1]d", - "type": "[]int", - "underlyingType": "[]int", - "argNum": 1, - "expr": "i" - } - ] - }, - { - "id": "Never remove the database info from the config.toml for lotus-miner as it avoids double PoSt.", - "message": "Never remove the database info from the config.toml for lotus-miner as it avoids double PoSt.", - "translation": "로터스 마이너의 config.toml에서 데이터베이스 정보를 제거하지 마십시오. 두 번의 PoSt를 피하기 위함입니다." - }, - { - "id": "Enter the info to connect to your Yugabyte database installation (https://download.yugabyte.com/)", - "message": "Enter the info to connect to your Yugabyte database installation (https://download.yugabyte.com/)", - "translation": "Yugabyte 데이터베이스 설치에 연결할 정보를 입력하십시오 (https://download.yugabyte.com/)" - }, - { - "id": "Host: {Hosts_}", - "message": "Host: {Hosts_}", - "translation": "호스트: {Hosts_}", - "placeholders": [ - { - "id": "Hosts_", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "strings.Join(harmonycfg.Hosts, \",\")" - } - ] - }, - { - "id": "Port: {Port}", - "message": "Port: {Port}", - "translation": "포트: {Port}", - "placeholders": [ - { - "id": "Port", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "harmonycfg.Port" - } - ] - }, - { - "id": "Username: {Username}", - "message": "Username: {Username}", - "translation": "사용자 이름: {Username}", - "placeholders": [ - { - "id": "Username", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "harmonycfg.Username" - } - ] - }, - { - "id": "Password: {Password}", - "message": "Password: {Password}", - "translation": "비밀번호: {Password}", - "placeholders": [ - { - "id": "Password", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "harmonycfg.Password" - } - ] - }, - { - "id": "Database: {Database}", - "message": "Database: {Database}", - "translation": "데이터베이스: {Database}", - "placeholders": [ - { - "id": "Database", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "harmonycfg.Database" - } - ] - }, - { - "id": "Continue to connect and update schema.", - "message": "Continue to connect and update schema.", - "translation": "계속 연결 및 스키마 업데이트." - }, - { - "id": "Database config error occurred, abandoning migration: {Error}", - "message": "Database config error occurred, abandoning migration: {Error}", - "translation": "데이터베이스 구성 오류가 발생하여 마이그레이션을 포기합니다: {Error}", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ] - }, - { - "id": "Enter the Yugabyte database host(s)", - "message": "Enter the Yugabyte database host(s)", - "translation": "Yugabyte 데이터베이스 호스트를 입력하십시오" - }, - { - "id": "No host provided", - "message": "No host provided", - "translation": "호스트가 제공되지 않았습니다" - }, - { - "id": "Enter the Yugabyte database {Stringport_username_password_databasei_1}", - "message": "Enter the Yugabyte database {Stringport_username_password_databasei_1}", - "translation": "Yugabyte 데이터베이스 {Stringport_username_password_databasei_1}을 입력하십시오", - "placeholders": [ - { - "id": "Stringport_username_password_databasei_1", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "[]string{\"port\", \"username\", \"password\", \"database\"}[i-1]" - } - ] - }, - { - "id": "No value provided", - "message": "No value provided", - "translation": "값이 제공되지 않았습니다" - }, - { - "id": "Error connecting to Yugabyte database: {Error}", - "message": "Error connecting to Yugabyte database: {Error}", - "translation": "Yugabyte 데이터베이스에 연결하는 중 오류가 발생했습니다: {Error}", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ] - }, - { - "id": "Connected to Yugabyte. Schema is current.", - "message": "Connected to Yugabyte. Schema is current.", - "translation": "Yugabyte에 연결되었습니다. 스키마가 현재입니다." - }, - { - "id": "Error encoding config.toml: {Error}", - "message": "Error encoding config.toml: {Error}", - "translation": "config.toml을 인코딩하는 중 오류가 발생했습니다: {Error}", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ] - }, - { - "id": "Error reading filemode of config.toml: {Error}", - "message": "Error reading filemode of config.toml: {Error}", - "translation": "config.toml의 파일 모드를 읽는 중 오류가 발생했습니다: {Error}", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ] - }, - { - "id": "Error writing config.toml: {Error}", - "message": "Error writing config.toml: {Error}", - "translation": "config.toml을 쓰는 중 오류가 발생했습니다: {Error}", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ] - }, - { - "id": "Restart Lotus Miner.", - "message": "Restart Lotus Miner.", - "translation": "로터스 마이너 재시작." - }, - { - "id": "Connected to Yugabyte", - "message": "Connected to Yugabyte", - "translation": "Yugabyte에 연결됨" - }, - { - "id": "Select the location of your lotus-miner config directory?", - "message": "Select the location of your lotus-miner config directory?", - "translation": "로터스 마이너 구성 디렉토리의 위치를 선택하시겠습니까?" - }, - { - "id": "Other", - "message": "Other", - "translation": "기타" - }, - { - "id": "Enter the path to the configuration directory used by lotus-miner", - "message": "Enter the path to the configuration directory used by lotus-miner", - "translation": "로터스 마이너에서 사용하는 구성 디렉토리의 경로를 입력하십시오" - }, - { - "id": "No path provided, abandoning migration", - "message": "No path provided, abandoning migration", - "translation": "경로가 제공되지 않았으므로 마이그레이션을 포기합니다" - }, - { - "id": "Cannot read the config.toml file in the provided directory, Error: {Error}", - "message": "Cannot read the config.toml file in the provided directory, Error: {Error}", - "translation": "제공된 디렉토리에서 config.toml 파일을 읽을 수 없습니다. 오류: {Error}", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ] - }, - { - "id": "Read Miner Config", - "message": "Read Miner Config", - "translation": "마이너 구성 읽기" - }, - { - "id": "Completed Step: {Step}", - "message": "Completed Step: {Step}", - "translation": "단계 완료: {Step}", - "placeholders": [ - { - "id": "Step", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "step" - } - ] - }, - { - "id": "This interactive tool migrates lotus-miner to Curio in 5 minutes.", - "translation": "이 대화형 도구는 5분 안에 lotus-miner를 Curio로 이주합니다.", - "message": "This interactive tool migrates lotus-miner to Curio in 5 minutes.", - "placeholder": null - }, - { - "id": "Each step needs your confirmation and can be reversed. Press Ctrl+C to exit at any time.", - "translation": "각 단계는 확인이 필요하며 되돌릴 수 있습니다. 언제든지 Ctrl+C를 눌러 종료할 수 있습니다.", - "message": "Each step needs your confirmation and can be reversed. Press Ctrl+C to exit at any time.", - "placeholder": null - }, - { - "id": "Use the arrow keys to navigate: ↓ ↑ → ←", - "translation": "화살표 키를 사용하여 이동하세요: ↓ ↑ → ←", - "message": "Use the arrow keys to navigate: ↓ ↑ → ←", - "placeholder": null - }, - { - "id": "Lotus-Miner to Curio Migration.", - "translation": "Lotus-Miner에서 Curio로 이주.", - "message": "Lotus-Miner to Curio Migration.", - "placeholder": null - }, - { - "id": "Try the web interface with for further guided improvements.", - "translation": "더 나은 안내를 위해 웹 인터페이스를 사용해보세요.", - "message": "Try the web interface with for further guided improvements.", - "placeholder": null - }, - { - "id": "You can now migrate your market node ({Boost}), if applicable.", - "translation": "해당하는 경우 이제 시장 노드를 이주할 수 있습니다 ({Boost}).", - "message": "You can now migrate your market node ({Boost}), if applicable.", - "placeholder": null - }, - { - "id": "Migrating config.toml to database.", - "translation": "config.toml을 데이터베이스로 이주 중입니다.", - "message": "Migrating config.toml to database.", - "placeholder": null - }, - { - "id": "Error reading from database: {Error}. Aborting Migration.", - "translation": "데이터베이스에서 읽는 중 오류 발생: {Error}. 마이그레이션 중단.", - "message": "Error reading from database: {Error}. Aborting Migration.", - "placeholder": null - }, - { - "id": "cannot read API: {Error}. Aborting Migration", - "translation": "API를 읽을 수 없습니다: {Error}. 마이그레이션 중단", - "message": "cannot read API: {Error}. Aborting Migration", - "placeholder": null - }, - { - "id": "Error saving config to layer: {Error}. Aborting Migration", - "translation": "레이어에 구성을 저장하는 중 오류 발생: {Error}. 마이그레이션 중단", - "message": "Error saving config to layer: {Error}. Aborting Migration", - "placeholder": null - }, - { - "id": "Protocol Labs wants to improve the software you use. Tell the team you're using Curio.", - "translation": "Protocol Labs는 당신이 사용하는 소프트웨어를 개선하고 싶어합니다. Curio를 사용 중이라고 팀에 알려주세요.", - "message": "Protocol Labs wants to improve the software you use. Tell the team you're using Curio.", - "placeholder": null - }, - { - "id": "Select what you want to share with the Curio team.", - "translation": "Curio 팀과 공유하고 싶은 것을 선택하세요.", - "message": "Select what you want to share with the Curio team.", - "placeholder": null - }, - { - "id": "Individual Data: Miner ID, Curio version, net ({Mainnet} or {Testnet}). Signed.", - "translation": "개별 데이터: 마이너 ID, Curio 버전, 네트워크 ({Mainnet} 또는 {Testnet}). 서명됨.", - "message": "Individual Data: Miner ID, Curio version, net ({Mainnet} or {Testnet}). Signed.", - "placeholder": null - }, - { - "id": "Aggregate-Anonymous: version, net, and Miner power (bucketed).", - "translation": "집계-익명: 버전, 네트워크, 그리고 마이너 파워 (버킷).", - "message": "Aggregate-Anonymous: version, net, and Miner power (bucketed).", - "placeholder": null - }, - { - "id": "Hint: I am someone running Curio on net.", - "translation": "힌트: 네트워크에서 Curio를 실행 중인 사람입니다.", - "message": "Hint: I am someone running Curio on net.", - "placeholder": null - }, - { - "id": "Nothing.", - "translation": "아무것도 없습니다.", - "message": "Nothing.", - "placeholder": null - }, - { - "id": "Aborting remaining steps.", - "translation": "나머지 단계를 중단합니다.", - "message": "Aborting remaining steps.", - "placeholder": null - }, - { - "id": "Error connecting to lotus node: {Error}", - "translation": "로터스 노드에 연결하는 중 오류 발생: {Error}", - "message": "Error connecting to lotus node: {Error}", - "placeholder": null - }, - { - "id": "Error getting miner power: {Error}", - "translation": "마이너 파워를 가져오는 중 오류 발생: {Error}", - "message": "Error getting miner power: {Error}", - "placeholder": null - }, - { - "id": "Error marshalling message: {Error}", - "translation": "메시지를 마샬하는 중 오류 발생: {Error}", - "message": "Error marshalling message: {Error}", - "placeholder": null - }, - { - "id": "Error getting miner info: {Error}", - "translation": "마이너 정보를 가져오는 중 오류 발생: {Error}", - "message": "Error getting miner info: {Error}", - "placeholder": null - }, - { - "id": "Error signing message: {Error}", - "translation": "메시지 서명 중 오류 발생: {Error}", - "message": "Error signing message: {Error}", - "placeholder": null - }, - { - "id": "Error sending message: {Error}", - "translation": "메시지 전송 중 오류 발생: {Error}", - "message": "Error sending message: {Error}", - "placeholder": null - }, - { - "id": "Error sending message: Status {Status}, Message:", - "translation": "메시지 전송 중 오류 발생: 상태 {Status}, 메시지:", - "message": "Error sending message: Status {Status}, Message:", - "placeholder": null - }, - { - "id": "Message sent.", - "translation": "메시지가 전송되었습니다.", - "message": "Message sent.", - "placeholder": null - }, - { - "id": "Documentation:", - "translation": "문서:", - "message": "Documentation:", - "placeholder": null - }, - { - "id": "The '{Base}' layer stores common configuration. All curio instances can include it in their {__layers} argument.", - "translation": "'{Base}' 레이어에는 공통 구성이 저장됩니다. 모든 Curio 인스턴스는 {__layers} 인수에 포함시킬 수 있습니다.", - "message": "The '{Base}' layer stores common configuration. All curio instances can include it in their {__layers} argument.", - "placeholder": null - }, - { - "id": "You can add other layers for per-machine configuration changes.", - "translation": "기계별 구성 변경을 위해 다른 레이어를 추가할 수 있습니다.", - "message": "You can add other layers for per-machine configuration changes.", - "placeholder": null - }, - { - "id": "Join {Fil_curio_help} in Filecoin {Slack} for help.", - "translation": "도움을 위해 Filecoin {Slack}의 {Fil_curio_help}에 가입하세요.", - "message": "Join {Fil_curio_help} in Filecoin {Slack} for help.", - "placeholder": null - }, - { - "id": "Join {Fil_curio_dev} in Filecoin {Slack} to follow development and feedback!", - "translation": "개발과 피드백을 따르려면 Filecoin {Slack}의 {Fil_curio_dev}에 가입하세요!", - "message": "Join {Fil_curio_dev} in Filecoin {Slack} to follow development and feedback!", - "placeholder": null - }, - { - "id": "Want PoST redundancy? Run many Curio instances with the '{Post}' layer.", - "translation": "PoST 중복성이 필요하신가요? '{Post}' 레이어와 함께 여러 Curio 인스턴스를 실행하세요.", - "message": "Want PoST redundancy? Run many Curio instances with the '{Post}' layer.", - "placeholder": null - }, - { - "id": "Point your browser to your web GUI to complete setup with {Boost} and advanced featues.", - "translation": "브라우저를 웹 GUI로 이동하여 {Boost} 및 고급 기능으로 설정을 완료하세요.", - "message": "Point your browser to your web GUI to complete setup with {Boost} and advanced featues.", - "placeholder": null - }, - { - "id": "For SPs with multiple Miner IDs, run 1 migration per lotus-miner all to the same 1 database. The cluster will serve all Miner IDs.", - "translation": "여러 마이너 ID가 있는 SP의 경우 각 lotus-miner당 1회 마이그레이션을 동일한 1개의 데이터베이스로 모두 실행하세요. 클러스터는 모든 마이너 ID를 제공합니다.", - "message": "For SPs with multiple Miner IDs, run 1 migration per lotus-miner all to the same 1 database. The cluster will serve all Miner IDs.", - "placeholder": null - }, - { - "id": "Please start {Lotus_miner} now that database credentials are in {Toml}.", - "translation": "데이터베이스 자격 증명이 {Toml}에 있으므로 이제 {Lotus_miner}를 시작하세요.", - "message": "Please start {Lotus_miner} now that database credentials are in {Toml}.", - "placeholder": null - }, - { - "id": "Waiting for {Lotus_miner} to write sectors into Yugabyte.", - "translation": "{Lotus_miner}가 Yugabyte에 섹터를 기록하도록 대기 중입니다.", - "message": "Waiting for {Lotus_miner} to write sectors into Yugabyte.", - "placeholder": null - }, - { - "id": "The sectors are in the database. The database is ready for {Curio}.", - "translation": "섹터가 데이터베이스에 있습니다. 데이터베이스가 {Curio}를 위해 준비되었습니다.", - "message": "The sectors are in the database. The database is ready for {Curio}.", - "placeholder": null - }, - { - "id": "Now shut down lotus-miner and move the systems to {Curio}.", - "translation": "이제 lotus-miner를 종료하고 시스템을 {Curio}로 이동하세요.", - "message": "Now shut down lotus-miner and move the systems to {Curio}.", - "placeholder": null - }, - { - "id": "Press return to continue", - "translation": "계속하려면 리턴을 누르세요", - "message": "Press return to continue", - "placeholder": null - }, - { - "id": "Aborting migration.", - "translation": "마이그레이션 중단.", - "message": "Aborting migration.", - "placeholder": null - }, - { - "id": "Sectors verified. {I} sector locations found.", - "translation": "섹터가 확인되었습니다. {I}개의 섹터 위치를 찾았습니다.", - "message": "Sectors verified. {I} sector locations found.", - "placeholder": null - }, - { - "id": "Press return to update {Toml} with Yugabyte info. Backup the file now.", - "translation": "{Toml}을 Yugabyte 정보로 업데이트하려면 리턴을 누르세요. 지금 파일을 백업하세요.", - "message": "Press return to update {Toml} with Yugabyte info. Backup the file now.", - "placeholder": null - }, - { - "id": "To start, ensure your sealing pipeline is drained and shut-down lotus-miner.", - "translation": "시작하려면 밀봉 파이프라인이 비어 있고 lotus-miner가 종료되었는지 확인하세요.", - "message": "To start, ensure your sealing pipeline is drained and shut-down lotus-miner.", - "placeholder": null - }, - { - "id": "Enter the path to the configuration directory used by {Lotus_miner}", - "translation": "{Lotus_miner}에서 사용하는 구성 디렉터리 경로를 입력하세요.", - "message": "Enter the path to the configuration directory used by {Lotus_miner}", - "placeholder": null - }, - { - "id": "Step Complete: {Step}", - "translation": "단계 완료: {Step}", - "message": "Step Complete: {Step}", - "placeholder": null - }, - { - "id": "Configuration 'base' was updated to include this miner's address and its wallet setup.", - "translation": "이 마이너의 주소와 지갑 설정을 포함하도록 구성 'base'가 업데이트되었습니다.", - "message": "Configuration 'base' was updated to include this miner's address and its wallet setup.", - "placeholder": null - }, - { - "id": "Compare the configurations {Base} to {MinerAddresses0}. Changes between the miner IDs other than wallet addreses should be a new, minimal layer for runners that need it.", - "translation": "구성 {Base}를 {MinerAddresses0}과 비교하세요. 지갑 주소 이외의 마이너 ID 사이의 변경 사항은 필요한 실행자를 위한 새로운 최소한의 레이어여야 합니다.", - "message": "Compare the configurations {Base} to {MinerAddresses0}. Changes between the miner IDs other than wallet addreses should be a new, minimal layer for runners that need it.", - "placeholder": null - }, - { - "id": "Configuration 'base' was created to include this miner's address and its wallet setup.", - "translation": "이 마이너의 주소와 지갑 설정을 포함하도록 구성 'base'가 생성되었습니다.", - "message": "Configuration 'base' was created to include this miner's address and its wallet setup.", - "placeholder": null - }, - { - "id": "Layer {LayerName} created.", - "translation": "레이어 {LayerName}가 생성되었습니다.", - "message": "Layer {LayerName} created.", - "placeholder": null - }, - { - "id": "To work with the config: \\n", - "translation": "구성을 사용하려면: \\n", - "message": "To work with the config: \\n", - "placeholder": null - }, - { - "id": "To run Curio: With machine or cgroup isolation, use the command (with example layer selection):", - "translation": "Curio를 실행하려면: 기계 또는 cgroup 격리를 사용하여 다음 명령을 사용하세요 (예제 레이어 선택과 함께):", - "message": "To run Curio: With machine or cgroup isolation, use the command (with example layer selection):", - "placeholder": null - }, - { - "id": "Try the web interface with {__layersgui} for further guided improvements.", - "translation": "더 많은 안내를 위해 {__layersgui}를 사용하여 웹 인터페이스를 시도하세요.", - "message": "Try the web interface with {__layersgui} for further guided improvements.", - "placeholder": null - }, - { - "id": "Error connecting to lotus node: {Error} {Error_1}", - "translation": "lotus 노드에 연결하는 중 오류 발생: {Error} {Error_1}", - "message": "Error connecting to lotus node: {Error} {Error_1}", - "placeholder": null - }, - { - "id": "could not get API info for FullNode: {Err}", - "translation": "FullNode의 API 정보를 가져올 수 없습니다: {Err}", - "message": "could not get API info for FullNode: {Err}", - "placeholder": null - }, - { - "id": "Error getting token: {Error}", - "translation": "토큰을 가져오는 중 오류 발생: {Error}", - "message": "Error getting token: {Error}", - "placeholder": null - }, - { - "id": "Filecoin {Slack} channels: {Fil_curio_help} and {Fil_curio_dev}", - "translation": "Filecoin {Slack} 채널: {Fil_curio_help} 및 {Fil_curio_dev}", - "message": "Filecoin {Slack} channels: {Fil_curio_help} and {Fil_curio_dev}", - "placeholder": null - }, - { - "id": "Start multiple Curio instances with the '{Post}' layer to redundancy.", - "translation": "'{Post}' 레이어로 여러 Curio 인스턴스를 시작하여 중복성을 확보하세요.", - "message": "Start multiple Curio instances with the '{Post}' layer to redundancy.", - "placeholder": null - }, - { - "id": "One database can serve multiple miner IDs: Run a migration for each lotus-miner.", - "translation": "한 개의 데이터베이스는 여러 광부 ID를 제공할 수 있습니다: 각 lotus-miner에 대해 마이그레이션을 실행하세요.", - "message": "One database can serve multiple miner IDs: Run a migration for each lotus-miner.", - "placeholder": null - }, - { - "id": "Please start (or restart) {Lotus_miner} now that database credentials are in {Toml}.", - "translation": "데이터베이스 자격 증명이 {Toml}에 입력되었으므로 지금 {Lotus_miner}을 시작하거나 다시 시작하세요.", - "message": "Please start (or restart) {Lotus_miner} now that database credentials are in {Toml}.", - "placeholder": null - }, - { - "id": "Error interpreting miner ID: {Error}: ID: {String}", - "translation": "광부 ID를 해석하는 중 오류 발생: {Error}: ID: {String}", - "message": "Error interpreting miner ID: {Error}: ID: {String}", - "placeholder": null - }, - { - "id": "Enabling Sector Indexing in the database.", - "translation": "데이터베이스에서 Sector Indexing을 활성화합니다.", - "message": "Enabling Sector Indexing in the database.", - "placeholder": null - }, - { - "id": "Error expanding path: {Error}", - "translation": "경로를 확장하는 중 오류 발생: {Error}", - "message": "Error expanding path: {Error}", - "placeholder": null - }, - { - "id": "Could not create repo from directory: {Error}. Aborting migration", - "translation": "디렉토리에서 저장소를 생성할 수 없습니다: {Error}. 마이그레이션을 중단합니다.", - "message": "Could not create repo from directory: {Error}. Aborting migration", - "placeholder": null - }, - { - "id": "Could not lock miner repo. Your miner must be stopped: {Error}\n Aborting migration", - "translation": "광부 저장소를 잠금 해제할 수 없습니다. 귀하의 광부를 중지해야 합니다: {Error}\n 마이그레이션을 중단합니다.", - "message": "Could not lock miner repo. Your miner must be stopped: {Error}\n Aborting migration", - "placeholder": null - }, - { - "id": "To work with the config:", - "translation": "구성 파일을 사용하려면:", - "message": "To work with the config:", - "placeholder": null - }, - { - "id": "This interactive tool creates a new miner actor and creates the basic configuration layer for it.", - "translation": "이 대화형 도구는 새로운 채굴자 액터를 생성하고 그에 대한 기본 구성 레이어를 생성합니다.", - "message": "This interactive tool creates a new miner actor and creates the basic configuration layer for it.", - "placeholder": null - }, - { - "id": "This process is partially idempotent. Once a new miner actor has been created and subsequent steps fail, the user need to run 'curio config new-cluster {Arg_1}' to finish the configuration.", - "translation": "이 프로세스는 부분적으로 idempotent합니다. 새로운 채굴자 액터가 생성되었고 후속 단계가 실패하면 사용자는 구성을 완료하기 위해 'curio config new-cluster {Arg_1}'를 실행해야 합니다.", - "message": "This process is partially idempotent. Once a new miner actor has been created and subsequent steps fail, the user need to run 'curio config new-cluster {Arg_1}' to finish the configuration.", - "placeholder": null - }, - { - "id": "Choose if you with to create a new miner or migrate from existing Lotus-Miner", - "translation": "새 채굴자를 생성할지 기존의 Lotus-Miner에서 이전할지 선택하세요.", - "message": "Choose if you with to create a new miner or migrate from existing Lotus-Miner", - "placeholder": null - }, - { - "id": "Migrate from existing Lotus-Miner", - "translation": "기존의 Lotus-Miner에서 이전하기", - "message": "Migrate from existing Lotus-Miner", - "placeholder": null - }, - { - "id": "Create a new miner", - "translation": "새로운 채굴자 생성", - "message": "Create a new miner", - "placeholder": null - }, - { - "id": "New Miner initialization complete.", - "translation": "새로운 채굴자 초기화 완료.", - "message": "New Miner initialization complete.", - "placeholder": null - }, - { - "id": "Migrating lotus-miner config.toml to Curio in-database configuration.", - "translation": "lotus-miner config.toml을 Curio의 데이터베이스 구성으로 이전 중입니다.", - "message": "Migrating lotus-miner config.toml to Curio in-database configuration.", - "placeholder": null - }, - { - "id": "Error getting API: {Error}", - "translation": "API 가져오기 오류: {Error}", - "message": "Error getting API: {Error}", - "placeholder": null - }, - { - "id": "The Curio team wants to improve the software you use. Tell the team you're using {Curio}.", - "translation": "Curio 팀은 당신이 사용하는 소프트웨어를 개선하고자 합니다. 팀에게 {Curio}를 사용 중이라고 알려주세요.", - "message": "The Curio team wants to improve the software you use. Tell the team you're using {Curio}.", - "placeholder": null - }, - { - "id": "Individual Data: Miner ID, Curio version, chain ({Mainnet} or {Calibration}). Signed.", - "translation": "개별 데이터: 채굴자 ID, Curio 버전, 체인 ({Mainnet} 또는 {Calibration}). 서명됨.", - "message": "Individual Data: Miner ID, Curio version, chain ({Mainnet} or {Calibration}). Signed.", - "placeholder": null - }, - { - "id": "Aggregate-Anonymous: version, chain, and Miner power (bucketed).", - "translation": "집계-익명: 버전, 체인, 및 채굴자 파워 (버킷).", - "message": "Aggregate-Anonymous: version, chain, and Miner power (bucketed).", - "placeholder": null - }, - { - "id": "Hint: I am someone running Curio on whichever chain.", - "translation": "힌트: 나는 어떤 체인에서든 Curio를 실행 중인 사람입니다.", - "message": "Hint: I am someone running Curio on whichever chain.", - "placeholder": null - }, - { - "id": "Press return to update {Toml} with Yugabyte info. A Backup file will be written to that folder before changes are made.", - "translation": "{Toml}을 Yugabyte 정보로 업데이트하려면 리턴 키를 누르세요. 변경 사항을 적용하기 전에 해당 폴더에 백업 파일이 작성됩니다.", - "message": "Press return to update {Toml} with Yugabyte info. A Backup file will be written to that folder before changes are made.", - "placeholder": null - }, - { - "id": "Error creating backup file: {Error}", - "translation": "백업 파일 생성 오류: {Error}", - "message": "Error creating backup file: {Error}", - "placeholder": null - }, - { - "id": "Error reading config.toml: {Error}", - "translation": "config.toml 읽기 오류: {Error}", - "message": "Error reading config.toml: {Error}", - "placeholder": null - }, - { - "id": "Error writing backup file: {Error}", - "translation": "백업 파일 쓰기 오류: {Error}", - "message": "Error writing backup file: {Error}", - "placeholder": null - }, - { - "id": "Error closing backup file: {Error}", - "translation": "백업 파일 닫기 오류: {Error}", - "message": "Error closing backup file: {Error}", - "placeholder": null - }, - { - "id": "Initializing a new miner actor.", - "translation": "새 채굴자 액터 초기화 중.", - "message": "Initializing a new miner actor.", - "placeholder": null - }, - { - "id": "Enter the info to create a new miner", - "translation": "새 채굴자를 생성하기 위한 정보 입력", - "message": "Enter the info to create a new miner", - "placeholder": null - }, - { - "id": "Owner Address: {String}", - "translation": "소유자 주소: {String}", - "message": "Owner Address: {String}", - "placeholder": null - }, - { - "id": "Worker Address: {String}", - "translation": "작업자 주소: {String}", - "message": "Worker Address: {String}", - "placeholder": null - }, - { - "id": "Sender Address: {String}", - "translation": "송신자 주소: {String}", - "message": "Sender Address: {String}", - "placeholder": null - }, - { - "id": "Sector Size: {Ssize}", - "translation": "섹터 크기: {Ssize}", - "message": "Sector Size: {Ssize}", - "placeholder": null - }, - { - "id": "Confidence: {Confidence}", - "translation": "신뢰도: {Confidence}", - "message": "Confidence: {Confidence}", - "placeholder": null - }, - { - "id": "Continue to verify the addresses and create a new miner actor.", - "translation": "주소를 확인하고 새 채굴자 액터를 생성하려면 계속 진행하세요.", - "message": "Continue to verify the addresses and create a new miner actor.", - "placeholder": null - }, - { - "id": "Miner creation error occurred: {Error}", - "translation": "채굴자 생성 오류 발생: {Error}", - "message": "Miner creation error occurred: {Error}", - "placeholder": null - }, - { - "id": "Enter the owner address", - "translation": "소유자 주소 입력", - "message": "Enter the owner address", - "placeholder": null - }, - { - "id": "No address provided", - "translation": "주소가 제공되지 않았습니다", - "message": "No address provided", - "placeholder": null - }, - { - "id": "Failed to parse the address: {Error}", - "translation": "주소 구문 분석 실패: {Error}", - "message": "Failed to parse the address: {Error}", - "placeholder": null - }, - { - "id": "Enter {Stringworker_senderi_1} address", - "translation": "{Stringworker_senderi_1} 주소 입력", - "message": "Enter {Stringworker_senderi_1} address", - "placeholder": null - }, - { - "id": "Enter the sector size", - "translation": "섹터 크기 입력", - "message": "Enter the sector size", - "placeholder": null - }, - { - "id": "Failed to parse sector size: {Error}", - "translation": "섹터 크기 구문 분석 실패: {Error}", - "message": "Failed to parse sector size: {Error}", - "placeholder": null - }, - { - "id": "Enter the confidence", - "translation": "신뢰도 입력", - "message": "Enter the confidence", - "placeholder": null - }, - { - "id": "Failed to parse confidence: {Error}", - "translation": "신뢰도 구문 분석 실패: {Error}", - "message": "Failed to parse confidence: {Error}", - "placeholder": null - }, - { - "id": "Failed to create the miner actor: {Error}", - "translation": "채굴자 액터 생성 실패: {Error}", - "message": "Failed to create the miner actor: {Error}", - "placeholder": null - }, - { - "id": "Miner {String} created successfully", - "translation": "{String} 채굴자가 성공적으로 생성되었습니다", - "message": "Miner {String} created successfully", - "placeholder": null - }, - { - "id": "Cannot reach the DB: {Error}", - "translation": "데이터베이스에 연결할 수 없습니다: {Error}", - "message": "Cannot reach the DB: {Error}", - "placeholder": null - }, - { - "id": "Error connecting to full node API: {Error}", - "translation": "풀 노드 API에 연결하는 중 오류 발생: {Error}", - "message": "Error connecting to full node API: {Error}", - "placeholder": null - }, - { - "id": "Pre-initialization steps complete", - "translation": "사전 초기화 단계 완료", - "message": "Pre-initialization steps complete", - "placeholder": null - }, - { - "id": "Failed to random bytes for secret: {Error}", - "translation": "비밀을 위한 랜덤 바이트 생성 실패: {Error}", - "message": "Failed to random bytes for secret: {Error}", - "placeholder": null - }, - { - "id": "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster {String}' to finish the configuration", - "translation": "마이너 생성은 idempotent하지 않으므로 가이드 설정을 다시 실행하지 마십시오. 구성을 완료하려면 'curio config new-cluster {String}'를 실행해야 합니다.", - "message": "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster {String}' to finish the configuration", - "placeholder": null - }, - { - "id": "Failed to verify the auth token from daemon node: {Error}", - "translation": "데몬 노드로부터 인증 토큰을 확인하는 중 오류 발생: {Error}", - "message": "Failed to verify the auth token from daemon node: {Error}", - "placeholder": null - }, - { - "id": "Failed to encode the config: {Error}", - "translation": "구성을 인코딩하는 중 오류 발생: {Error}", - "message": "Failed to encode the config: {Error}", - "placeholder": null - }, - { - "id": "Failed to generate default config: {Error}", - "translation": "기본 구성 생성 실패: {Error}", - "message": "Failed to generate default config: {Error}", - "placeholder": null - }, - { - "id": "Failed to inset 'base' config layer in database: {Error}", - "translation": "데이터베이스에 'base' 구성 레이어 삽입 실패: {Error}", - "message": "Failed to inset 'base' config layer in database: {Error}", - "placeholder": null - }, - { - "id": "Failed to inset '{String}' config layer in database: {Error}", - "translation": "데이터베이스에 '{String}' 구성 레이어 삽입 실패: {Error}", - "message": "Failed to inset '{String}' config layer in database: {Error}", - "placeholder": null - }, - { - "id": "New Curio configuration layer '{String}' created", - "translation": "새로운 Curio 구성 레이어 '{String}'가 생성되었습니다", - "message": "New Curio configuration layer '{String}' created", - "placeholder": null - }, - { - "id": "The Curio team wants to improve the software you use. Tell the team you're using `{Curio}`.", - "translation": "Curio 팀은 당신이 사용하는 소프트웨어를 개선하고자 합니다. 팀에게 `{Curio}`를 사용 중이라고 알려주세요.", - "message": "The Curio team wants to improve the software you use. Tell the team you're using `{Curio}`.", - "placeholder": null - }, - { - "id": "Confidence epochs: {Confidence}", - "translation": "신뢰 에포크: {Confidence}", - "message": "Confidence epochs: {Confidence}", - "placeholder": null - }, - { - "id": "Failed to generate random bytes for secret: {Error}", - "translation": "비밀번호를 위한 랜덤 바이트 생성에 실패했습니다: {Error}", - "message": "Failed to generate random bytes for secret: {Error}", - "placeholder": null - }, - { - "id": "Failed to get API info for FullNode: {Err}", - "translation": "FullNode에 대한 API 정보를 가져오는 데 실패했습니다: {Err}", - "message": "Failed to get API info for FullNode: {Err}", - "placeholder": null - }, - { - "id": "Failed to insert 'base' config layer in database: {Error}", - "translation": "데이터베이스에 'base' 구성 레이어를 삽입하는 데 실패했습니다: {Error}", - "message": "Failed to insert 'base' config layer in database: {Error}", - "placeholder": null - }, - { - "id": "Failed to insert '{String}' config layer in database: {Error}", - "translation": "데이터베이스에 '{String}' 구성 레이어를 삽입하는 데 실패했습니다: {Error}", - "message": "Failed to insert '{String}' config layer in database: {Error}", - "placeholder": null - }, - { - "id": "This process is partially idempotent. Once a new miner actor has been created and subsequent steps fail, the user need to run 'curio config new-cluster \u003c miner ID \u003e' to finish the configuration.", - "translation": "이 프로세스는 부분적으로 항등원적입니다. 새로운 채굴자 액터가 생성되었고 후속 단계가 실패하는 경우 사용자는 구성을 완료하기 위해 'curio config new-cluster \u003c 채굴자 ID \u003e'를 실행해야 합니다.", - "message": "This process is partially idempotent. Once a new miner actor has been created and subsequent steps fail, the user need to run 'curio config new-cluster \u003c miner ID \u003e' to finish the configuration.", - "placeholder": null - }, - { - "id": "Confidence epochs", - "translation": "신뢰 에포크", - "message": "Confidence epochs", - "placeholder": null - }, - { - "id": "Increase reliability using redundancy: start multiple machines with at-least the post layer: 'curio run --layers=post'", - "translation": "신뢰성 향상을 위한 중복성 사용: 적어도 post 레이어를 사용하여 여러 대의 기계를 시작하십시오: 'curio run --layers=post'", - "message": "Increase reliability using redundancy: start multiple machines with at-least the post layer: 'curio run --layers=post'", - "placeholder": null - }, - { - "id": "I want to:", - "translation": "나는 원한다:", - "message": "I want to:", - "placeholder": null - }, - { - "id": "Configuration 'base' was updated to include this miner's address", - "translation": "이 마이너 주소를 포함한 구성 'base'가 업데이트되었습니다.", - "message": "Configuration 'base' was updated to include this miner's address", - "placeholder": null - }, - { - "id": "Cannot load base config: {Error}", - "translation": "기본 구성을 불러올 수 없습니다: {Error}", - "message": "Cannot load base config: {Error}", - "placeholder": null - }, - { - "id": "Failed to load base config: {Error}", - "translation": "기본 구성을 로드하는 데 실패했습니다: {Error}", - "message": "Failed to load base config: {Error}", - "placeholder": null - }, - { - "id": "Failed to regenerate base config: {Error}", - "translation": "기본 구성을 재생성하는 데 실패했습니다: {Error}", - "message": "Failed to regenerate base config: {Error}", - "placeholder": null - }, - { - "id": "Failed to load base config from database: {Error}", - "translation": "데이터베이스에서 기본 구성을 로드하는 데 실패했습니다: {Error}", - "message": "Failed to load base config from database: {Error}", - "placeholder": null - }, - { - "id": "Failed to parse base config: {Error}", - "translation": "기본 구성을 구문 분석하는 데 실패했습니다: {Error}", - "message": "Failed to parse base config: {Error}", - "placeholder": null - }, - { - "id": "Try the web interface with {Rendercurio_run___layersgui} for further guided improvements.", - "translation": "{Rendercurio_run___layersgui}를 사용하여 웹 인터페이스를 시도하고 더 나은 안내된 개선을 진행하세요.", - "message": "Try the web interface with {Rendercurio_run___layersgui} for further guided improvements.", - "placeholder": null - }, - { - "id": "Now shut down lotus-miner and lotus-worker and use run {Rendercurio_run} instead.", - "translation": "이제 lotus-miner와 lotus-worker를 종료하고 {Rendercurio_run}을 실행하세요.", - "message": "Now shut down lotus-miner and lotus-worker and use run {Rendercurio_run} instead.", - "placeholder": null - }, - { - "id": "Configuration 'base' was updated to include this miner's address ({MinerAddress}) and its wallet setup.", - "translation": "기본 설정 'base'가 이 마이너의 주소({MinerAddress}) 및 지갑 설정을 포함하도록 업데이트되었습니다.", - "message": "Configuration 'base' was updated to include this miner's address ({MinerAddress}) and its wallet setup.", - "placeholder": null - }, - { - "id": "Configuration 'base' was created to resemble this lotus-miner's config.toml .", - "translation": "'base' 설정이 이 lotus-miner의 config.toml과 유사하게 만들어졌습니다.", - "message": "Configuration 'base' was created to resemble this lotus-miner's config.toml .", - "placeholder": null - } - ] -} \ No newline at end of file diff --git a/cmd/curio/internal/translations/locales/ko/out.gotext.json b/cmd/curio/internal/translations/locales/ko/out.gotext.json deleted file mode 100644 index 8e0014cd4..000000000 --- a/cmd/curio/internal/translations/locales/ko/out.gotext.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "language": "ko", - "messages": [] - } diff --git a/cmd/curio/internal/translations/locales/zh/messages.gotext.json b/cmd/curio/internal/translations/locales/zh/messages.gotext.json deleted file mode 100644 index 1e2608fa8..000000000 --- a/cmd/curio/internal/translations/locales/zh/messages.gotext.json +++ /dev/null @@ -1,1076 +0,0 @@ -{ - "language": "zh", - "messages": [ - { - "id": "This interactive tool will walk you through migration of Curio.\nPress Ctrl+C to exit at any time.", - "message": "This interactive tool will walk you through migration of Curio.\nPress Ctrl+C to exit at any time.", - "translation": "此互动工具将引导您完成Curio的迁移。\n随时按Ctrl+C退出。" - }, - { - "id": "This tool confirms each action it does.", - "message": "This tool confirms each action it does.", - "translation": "此工具确认其执行的每个操作。" - }, - { - "id": "Ctrl+C pressed in Terminal", - "message": "Ctrl+C pressed in Terminal", - "translation": "在终端中按下Ctrl+C" - }, - { - "id": "Verifying Sectors exist in Yugabyte.", - "message": "Verifying Sectors exist in Yugabyte.", - "translation": "正在验证Yugabyte中的扇区是否存在。" - }, - { - "id": "Error verifying sectors: {Error}", - "message": "Error verifying sectors: {Error}", - "translation": "验证扇区时出错:{Error}", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ] - }, - { - "id": "Sectors verified. {I} sectors found.", - "message": "Sectors verified. {I} sectors found.", - "translation": "已验证扇区。找到了{I}个扇区。", - "placeholders": [ - { - "id": "I", - "string": "%[1]d", - "type": "[]int", - "underlyingType": "[]int", - "argNum": 1, - "expr": "i" - } - ] - }, - { - "id": "Never remove the database info from the config.toml for lotus-miner as it avoids double PoSt.", - "message": "Never remove the database info from the config.toml for lotus-miner as it avoids double PoSt.", - "translation": "从config.toml中永远不要删除lotus-miner的数据库信息,因为它避免了双PoSt。" - }, - { - "id": "Enter the info to connect to your Yugabyte database installation (https://download.yugabyte.com/)", - "message": "Enter the info to connect to your Yugabyte database installation (https://download.yugabyte.com/)", - "translation": "输入连接到您的Yugabyte数据库安装的信息(https://download.yugabyte.com/)" - }, - { - "id": "Host: {Hosts_}", - "message": "Host: {Hosts_}", - "translation": "主机:{Hosts_}", - "placeholders": [ - { - "id": "Hosts_", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "strings.Join(harmonycfg.Hosts, \",\")" - } - ] - }, - { - "id": "Port: {Port}", - "message": "Port: {Port}", - "translation": "端口:{Port}", - "placeholders": [ - { - "id": "Port", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "harmonycfg.Port" - } - ] - }, - { - "id": "Username: {Username}", - "message": "Username: {Username}", - "translation": "用户名:{Username}", - "placeholders": [ - { - "id": "Username", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "harmonycfg.Username" - } - ] - }, - { - "id": "Password: {Password}", - "message": "Password: {Password}", - "translation": "密码:{Password}", - "placeholders": [ - { - "id": "Password", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "harmonycfg.Password" - } - ] - }, - { - "id": "Database: {Database}", - "message": "Database: {Database}", - "translation": "数据库:{Database}", - "placeholders": [ - { - "id": "Database", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "harmonycfg.Database" - } - ] - }, - { - "id": "Continue to connect and update schema.", - "message": "Continue to connect and update schema.", - "translation": "继续连接和更新架构。" - }, - { - "id": "Database config error occurred, abandoning migration: {Error}", - "message": "Database config error occurred, abandoning migration: {Error}", - "translation": "发生数据库配置错误,放弃迁移:{Error}", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ] - }, - { - "id": "Enter the Yugabyte database host(s)", - "message": "Enter the Yugabyte database host(s)", - "translation": "输入Yugabyte数据库主机(S)" - }, - { - "id": "No host provided", - "message": "No host provided", - "translation": "未提供主机" - }, - { - "id": "Enter the Yugabyte database {Stringport_username_password_databasei_1}", - "message": "Enter the Yugabyte database {Stringport_username_password_databasei_1}", - "translation": "输入Yugabyte数据库 {Stringport_username_password_databasei_1}", - "placeholders": [ - { - "id": "Stringport_username_password_databasei_1", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "[]string{\"port\", \"username\", \"password\", \"database\"}[i-1]" - } - ] - }, - { - "id": "No value provided", - "message": "No value provided", - "translation": "未提供值" - }, - { - "id": "Error connecting to Yugabyte database: {Error}", - "message": "Error connecting to Yugabyte database: {Error}", - "translation": "连接到Yugabyte数据库时出错:{Error}", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ] - }, - { - "id": "Connected to Yugabyte. Schema is current.", - "message": "Connected to Yugabyte. Schema is current.", - "translation": "已连接到Yugabyte。模式是当前的。" - }, - { - "id": "Error encoding config.toml: {Error}", - "message": "Error encoding config.toml: {Error}", - "translation": "编码config.toml时出错:{Error}", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ] - }, - { - "id": "Error reading filemode of config.toml: {Error}", - "message": "Error reading filemode of config.toml: {Error}", - "translation": "读取config.toml文件模式时出错:{Error}", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ] - }, - { - "id": "Error writing config.toml: {Error}", - "message": "Error writing config.toml: {Error}", - "translation": "写入config.toml时出错:{Error}", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ] - }, - { - "id": "Restart Lotus Miner.", - "message": "Restart Lotus Miner.", - "translation": "重新启动Lotus Miner。" - }, - { - "id": "Connected to Yugabyte", - "message": "Connected to Yugabyte", - "translation": "已连接到Yugabyte" - }, - { - "id": "Select the location of your lotus-miner config directory?", - "message": "Select the location of your lotus-miner config directory?", - "translation": "选择您的lotus-miner配置目录的位置?" - }, - { - "id": "Other", - "message": "Other", - "translation": "其他" - }, - { - "id": "Enter the path to the configuration directory used by lotus-miner", - "message": "Enter the path to the configuration directory used by lotus-miner", - "translation": "输入lotus-miner使用的配置目录的路径" - }, - { - "id": "No path provided, abandoning migration", - "message": "No path provided, abandoning migration", - "translation": "未提供路径,放弃迁移" - }, - { - "id": "Cannot read the config.toml file in the provided directory, Error: {Error}", - "message": "Cannot read the config.toml file in the provided directory, Error: {Error}", - "translation": "无法读取提供的目录中的config.toml文件,错误:{Error}", - "placeholders": [ - { - "id": "Error", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "err.Error()" - } - ] - }, - { - "id": "Read Miner Config", - "message": "Read Miner Config", - "translation": "读取矿工配置" - }, - { - "id": "Completed Step: {Step}", - "message": "Completed Step: {Step}", - "translation": "完成步骤:{Step}", - "placeholders": [ - { - "id": "Step", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "step" - } - ] - }, - { - "id": "This interactive tool migrates lotus-miner to Curio in 5 minutes.", - "translation": "这个交互式工具可以在5分钟内将lotus-miner迁移到Curio。", - "message": "This interactive tool migrates lotus-miner to Curio in 5 minutes.", - "placeholder": null - }, - { - "id": "Each step needs your confirmation and can be reversed. Press Ctrl+C to exit at any time.", - "translation": "每一步都需要您的确认,并且可以撤销。随时按Ctrl+C退出。", - "message": "Each step needs your confirmation and can be reversed. Press Ctrl+C to exit at any time.", - "placeholder": null - }, - { - "id": "Use the arrow keys to navigate: ↓ ↑ → ←", - "translation": "使用箭头键进行导航:↓ ↑ → ←", - "message": "Use the arrow keys to navigate: ↓ ↑ → ←", - "placeholder": null - }, - { - "id": "Lotus-Miner to Curio Migration.", - "translation": "Lotus-Miner到Curio迁移。", - "message": "Lotus-Miner to Curio Migration.", - "placeholder": null - }, - { - "id": "Try the web interface with for further guided improvements.", - "translation": "尝试使用网页界面进行进一步的指导改进。", - "message": "Try the web interface with for further guided improvements.", - "placeholder": null - }, - { - "id": "You can now migrate your market node ({Boost}), if applicable.", - "translation": "如果适用,您现在可以迁移您的市场节点({Boost})。", - "message": "You can now migrate your market node ({Boost}), if applicable.", - "placeholder": null - }, - { - "id": "Migrating config.toml to database.", - "translation": "正在将config.toml迁移到数据库。", - "message": "Migrating config.toml to database.", - "placeholder": null - }, - { - "id": "Error reading from database: {Error}. Aborting Migration.", - "translation": "读取数据库时出错:{Error}。正在中止迁移。", - "message": "Error reading from database: {Error}. Aborting Migration.", - "placeholder": null - }, - { - "id": "cannot read API: {Error}. Aborting Migration", - "translation": "无法读取API:{Error}。正在中止迁移", - "message": "cannot read API: {Error}. Aborting Migration", - "placeholder": null - }, - { - "id": "Error saving config to layer: {Error}. Aborting Migration", - "translation": "保存配置到层时出错:{Error}。正在中止迁移", - "message": "Error saving config to layer: {Error}. Aborting Migration", - "placeholder": null - }, - { - "id": "Protocol Labs wants to improve the software you use. Tell the team you're using Curio.", - "translation": "Protocol Labs希望改进您使用的软件。告诉团队您正在使用Curio。", - "message": "Protocol Labs wants to improve the software you use. Tell the team you're using Curio.", - "placeholder": null - }, - { - "id": "Select what you want to share with the Curio team.", - "translation": "选择您想与Curio团队分享的内容。", - "message": "Select what you want to share with the Curio team.", - "placeholder": null - }, - { - "id": "Individual Data: Miner ID, Curio version, net ({Mainnet} or {Testnet}). Signed.", - "translation": "个人数据:矿工ID、Curio版本、网络({Mainnet}或{Testnet})。已签名。", - "message": "Individual Data: Miner ID, Curio version, net ({Mainnet} or {Testnet}). Signed.", - "placeholder": null - }, - { - "id": "Aggregate-Anonymous: version, net, and Miner power (bucketed).", - "translation": "聚合-匿名:版本、网络和矿工功率(分桶)。", - "message": "Aggregate-Anonymous: version, net, and Miner power (bucketed).", - "placeholder": null - }, - { - "id": "Hint: I am someone running Curio on net.", - "translation": "提示:我是在网络上运行Curio的人。", - "message": "Hint: I am someone running Curio on net.", - "placeholder": null - }, - { - "id": "Nothing.", - "translation": "没有。", - "message": "Nothing.", - "placeholder": null - }, - { - "id": "Aborting remaining steps.", - "translation": "中止剩余步骤。", - "message": "Aborting remaining steps.", - "placeholder": null - }, - { - "id": "Error connecting to lotus node: {Error}", - "translation": "连接到莲花节点时出错:{Error}", - "message": "Error connecting to lotus node: {Error}", - "placeholder": null - }, - { - "id": "Error getting miner power: {Error}", - "translation": "获取矿工功率时出错:{Error}", - "message": "Error getting miner power: {Error}", - "placeholder": null - }, - { - "id": "Error marshalling message: {Error}", - "translation": "整理消息时出错:{Error}", - "message": "Error marshalling message: {Error}", - "placeholder": null - }, - { - "id": "Error getting miner info: {Error}", - "translation": "获取矿工信息时出错:{Error}", - "message": "Error getting miner info: {Error}", - "placeholder": null - }, - { - "id": "Error signing message: {Error}", - "translation": "签署消息时出错:{Error}", - "message": "Error signing message: {Error}", - "placeholder": null - }, - { - "id": "Error sending message: {Error}", - "translation": "发送消息时出错:{Error}", - "message": "Error sending message: {Error}", - "placeholder": null - }, - { - "id": "Error sending message: Status {Status}, Message:", - "translation": "发送消息时出错:状态{Status},消息:", - "message": "Error sending message: Status {Status}, Message:", - "placeholder": null - }, - { - "id": "Message sent.", - "translation": "消息已发送。", - "message": "Message sent.", - "placeholder": null - }, - { - "id": "Documentation:", - "translation": "文档:", - "message": "Documentation:", - "placeholder": null - }, - { - "id": "The '{Base}' layer stores common configuration. All curio instances can include it in their {__layers} argument.", - "translation": "'{Base}'层存储通用配置。所有Curio实例都可以在其{__layers}参数中包含它。", - "message": "The '{Base}' layer stores common configuration. All curio instances can include it in their {__layers} argument.", - "placeholder": null - }, - { - "id": "You can add other layers for per-machine configuration changes.", - "translation": "您可以添加其他层进行每台机器的配置更改。", - "message": "You can add other layers for per-machine configuration changes.", - "placeholder": null - }, - { - "id": "Join {Fil_curio_help} in Filecoin {Slack} for help.", - "translation": "加入Filecoin {Slack}中的{Fil_curio_help}寻求帮助。", - "message": "Join {Fil_curio_help} in Filecoin {Slack} for help.", - "placeholder": null - }, - { - "id": "Join {Fil_curio_dev} in Filecoin {Slack} to follow development and feedback!", - "translation": "加入Filecoin {Slack}中的{Fil_curio_dev}来跟踪开发和反馈!", - "message": "Join {Fil_curio_dev} in Filecoin {Slack} to follow development and feedback!", - "placeholder": null - }, - { - "id": "Want PoST redundancy? Run many Curio instances with the '{Post}' layer.", - "translation": "需要PoST冗余?使用'{Post}'层运行多个Curio实例。", - "message": "Want PoST redundancy? Run many Curio instances with the '{Post}' layer.", - "placeholder": null - }, - { - "id": "Point your browser to your web GUI to complete setup with {Boost} and advanced featues.", - "translation": "将您的浏览器指向您的网络GUI,以使用{Boost}和高级功能完成设置。", - "message": "Point your browser to your web GUI to complete setup with {Boost} and advanced featues.", - "placeholder": null - }, - { - "id": "For SPs with multiple Miner IDs, run 1 migration per lotus-miner all to the same 1 database. The cluster will serve all Miner IDs.", - "translation": "对于具有多个矿工ID的SP,针对所有lotus-miner运行1次迁移到同一个数据库。集群将服务所有矿工ID。", - "message": "For SPs with multiple Miner IDs, run 1 migration per lotus-miner all to the same 1 database. The cluster will serve all Miner IDs.", - "placeholder": null - }, - { - "id": "Please start {Lotus_miner} now that database credentials are in {Toml}.", - "translation": "现在数据库凭证在{Toml}中,请启动{Lotus_miner}。", - "message": "Please start {Lotus_miner} now that database credentials are in {Toml}.", - "placeholder": null - }, - { - "id": "Waiting for {Lotus_miner} to write sectors into Yugabyte.", - "translation": "等待{Lotus_miner}将扇区写入Yugabyte。", - "message": "Waiting for {Lotus_miner} to write sectors into Yugabyte.", - "placeholder": null - }, - { - "id": "The sectors are in the database. The database is ready for {Curio}.", - "translation": "扇区在数据库中。数据库已准备好用于{Curio}。", - "message": "The sectors are in the database. The database is ready for {Curio}.", - "placeholder": null - }, - { - "id": "Now shut down lotus-miner and move the systems to {Curio}.", - "translation": "现在关闭lotus-miner并将系统移至{Curio}。", - "message": "Now shut down lotus-miner and move the systems to {Curio}.", - "placeholder": null - }, - { - "id": "Press return to continue", - "translation": "按回车继续", - "message": "Press return to continue", - "placeholder": null - }, - { - "id": "Aborting migration.", - "translation": "中止迁移。", - "message": "Aborting migration.", - "placeholder": null - }, - { - "id": "Sectors verified. {I} sector locations found.", - "translation": "扇区已验证。发现了{I}个扇区位置。", - "message": "Sectors verified. {I} sector locations found.", - "placeholder": null - }, - { - "id": "Press return to update {Toml} with Yugabyte info. Backup the file now.", - "translation": "按回车更新{Toml}以获取Yugabyte信息。现在备份文件。", - "message": "Press return to update {Toml} with Yugabyte info. Backup the file now.", - "placeholder": null - }, - { - "id": "To start, ensure your sealing pipeline is drained and shut-down lotus-miner.", - "translation": "开始之前,请确保您的密封管道已排空并关闭lotus-miner。", - "message": "To start, ensure your sealing pipeline is drained and shut-down lotus-miner.", - "placeholder": null - }, - { - "id": "Enter the path to the configuration directory used by {Lotus_miner}", - "translation": "输入{Lotus_miner}使用的配置目录的路径", - "message": "Enter the path to the configuration directory used by {Lotus_miner}", - "placeholder": null - }, - { - "id": "Step Complete: {Step}", - "translation": "步骤完成:{Step}", - "message": "Step Complete: {Step}", - "placeholder": null - }, - { - "id": "Configuration 'base' was updated to include this miner's address and its wallet setup.", - "translation": "配置'base'已更新,包含了这个矿工的地址和其钱包设置。", - "message": "Configuration 'base' was updated to include this miner's address and its wallet setup.", - "placeholder": null - }, - { - "id": "Compare the configurations {Base} to {MinerAddresses0}. Changes between the miner IDs other than wallet addreses should be a new, minimal layer for runners that need it.", - "translation": "比较配置{Base}和{MinerAddresses0}。矿工ID之间除了钱包地址的变化应该是需要的运行者的一个新的、最小的层。", - "message": "Compare the configurations {Base} to {MinerAddresses0}. Changes between the miner IDs other than wallet addreses should be a new, minimal layer for runners that need it.", - "placeholder": null - }, - { - "id": "Configuration 'base' was created to include this miner's address and its wallet setup.", - "translation": "配置'base'已创建,包括了这个矿工的地址和其钱包设置。", - "message": "Configuration 'base' was created to include this miner's address and its wallet setup.", - "placeholder": null - }, - { - "id": "Layer {LayerName} created.", - "translation": "层{LayerName}已创建。", - "message": "Layer {LayerName} created.", - "placeholder": null - }, - { - "id": "To work with the config: \\n", - "translation": "要使用配置:\\n", - "message": "To work with the config: \\n", - "placeholder": null - }, - { - "id": "To run Curio: With machine or cgroup isolation, use the command (with example layer selection):", - "translation": "运行Curio:使用机器或cgroup隔离,使用命令(附带示例层选择):", - "message": "To run Curio: With machine or cgroup isolation, use the command (with example layer selection):", - "placeholder": null - }, - { - "id": "Try the web interface with {__layersgui} for further guided improvements.", - "translation": "尝试使用{__layersgui}的Web界面进行进一步引导式改进。", - "message": "Try the web interface with {__layersgui} for further guided improvements.", - "placeholder": null - }, - { - "id": "Error connecting to lotus node: {Error} {Error_1}", - "translation": "连接到lotus节点时出错:{Error} {Error_1}", - "message": "Error connecting to lotus node: {Error} {Error_1}", - "placeholder": null - }, - { - "id": "could not get API info for FullNode: {Err}", - "translation": "无法获取FullNode的API信息:{Err}", - "message": "could not get API info for FullNode: {Err}", - "placeholder": null - }, - { - "id": "Error getting token: {Error}", - "translation": "获取令牌时出错:{Error}", - "message": "Error getting token: {Error}", - "placeholder": null - }, - { - "id": "Filecoin {Slack} channels: {Fil_curio_help} and {Fil_curio_dev}", - "translation": "Filecoin {Slack} 频道:{Fil_curio_help} 和 {Fil_curio_dev}", - "message": "Filecoin {Slack} channels: {Fil_curio_help} and {Fil_curio_dev}", - "placeholder": null - }, - { - "id": "Start multiple Curio instances with the '{Post}' layer to redundancy.", - "translation": "使用'{Post}'层启动多个Curio实例以实现冗余。", - "message": "Start multiple Curio instances with the '{Post}' layer to redundancy.", - "placeholder": null - }, - { - "id": "One database can serve multiple miner IDs: Run a migration for each lotus-miner.", - "translation": "一个数据库可以服务多个矿工ID:为每个lotus-miner运行迁移。", - "message": "One database can serve multiple miner IDs: Run a migration for each lotus-miner.", - "placeholder": null - }, - { - "id": "Please start (or restart) {Lotus_miner} now that database credentials are in {Toml}.", - "translation": "请立即启动(或重新启动){Lotus_miner},因为数据库凭据已在{Toml}中。", - "message": "Please start (or restart) {Lotus_miner} now that database credentials are in {Toml}.", - "placeholder": null - }, - { - "id": "Error interpreting miner ID: {Error}: ID: {String}", - "translation": "解释矿工ID时出错:{Error}:ID:{String}", - "message": "Error interpreting miner ID: {Error}: ID: {String}", - "placeholder": null - }, - { - "id": "Enabling Sector Indexing in the database.", - "translation": "在数据库中启用扇区索引。", - "message": "Enabling Sector Indexing in the database.", - "placeholder": null - }, - { - "id": "Error expanding path: {Error}", - "translation": "扩展路径时出错:{Error}", - "message": "Error expanding path: {Error}", - "placeholder": null - }, - { - "id": "Could not create repo from directory: {Error}. Aborting migration", - "translation": "无法从目录创建repo:{Error}。 中止迁移", - "message": "Could not create repo from directory: {Error}. Aborting migration", - "placeholder": null - }, - { - "id": "Could not lock miner repo. Your miner must be stopped: {Error}\n Aborting migration", - "translation": "无法锁定矿工repo。 您的矿工必须停止:{Error}\n 中止迁移", - "message": "Could not lock miner repo. Your miner must be stopped: {Error}\n Aborting migration", - "placeholder": null - }, - { - "id": "To work with the config:", - "translation": "要使用配置:", - "message": "To work with the config:", - "placeholder": null - }, - { - "id": "This interactive tool creates a new miner actor and creates the basic configuration layer for it.", - "translation": "此交互式工具将创建一个新的矿工角色,并为其创建基本配置层。", - "message": "This interactive tool creates a new miner actor and creates the basic configuration layer for it.", - "placeholder": null - }, - { - "id": "This process is partially idempotent. Once a new miner actor has been created and subsequent steps fail, the user need to run 'curio config new-cluster {Arg_1}' to finish the configuration.", - "translation": "此过程在某种程度上是幂等的。一旦创建了新的矿工角色,并且后续步骤失败,用户需要运行'curio config new-cluster {Arg_1}'来完成配置。", - "message": "This process is partially idempotent. Once a new miner actor has been created and subsequent steps fail, the user need to run 'curio config new-cluster {Arg_1}' to finish the configuration.", - "placeholder": null - }, - { - "id": "Choose if you with to create a new miner or migrate from existing Lotus-Miner", - "translation": "选择您是否要创建新矿工或从现有的 Lotus-Miner 迁移", - "message": "Choose if you with to create a new miner or migrate from existing Lotus-Miner", - "placeholder": null - }, - { - "id": "Migrate from existing Lotus-Miner", - "translation": "从现有的 Lotus-Miner 迁移", - "message": "Migrate from existing Lotus-Miner", - "placeholder": null - }, - { - "id": "Create a new miner", - "translation": "创建一个新的矿工", - "message": "Create a new miner", - "placeholder": null - }, - { - "id": "New Miner initialization complete.", - "translation": "新矿工初始化完成。", - "message": "New Miner initialization complete.", - "placeholder": null - }, - { - "id": "Migrating lotus-miner config.toml to Curio in-database configuration.", - "translation": "将 lotus-miner config.toml 迁移到 Curio 的数据库配置中。", - "message": "Migrating lotus-miner config.toml to Curio in-database configuration.", - "placeholder": null - }, - { - "id": "Error getting API: {Error}", - "translation": "获取 API 时出错:{Error}", - "message": "Error getting API: {Error}", - "placeholder": null - }, - { - "id": "The Curio team wants to improve the software you use. Tell the team you're using `{Curio}`.", - "translation": "Curio 团队希望改进您使用的软件。告诉团队您正在使用 `{Curio}`。", - "message": "The Curio team wants to improve the software you use. Tell the team you're using `{Curio}`.", - "placeholder": null - }, - { - "id": "Individual Data: Miner ID, Curio version, chain ({Mainnet} or {Calibration}). Signed.", - "translation": "个人数据:矿工 ID,Curio 版本,链({Mainnet} 或 {Calibration})。签名。", - "message": "Individual Data: Miner ID, Curio version, chain ({Mainnet} or {Calibration}). Signed.", - "placeholder": null - }, - { - "id": "Aggregate-Anonymous: version, chain, and Miner power (bucketed).", - "translation": "聚合-匿名:版本,链和矿工算力(分桶)。", - "message": "Aggregate-Anonymous: version, chain, and Miner power (bucketed).", - "placeholder": null - }, - { - "id": "Hint: I am someone running Curio on whichever chain.", - "translation": "提示:我是在任何链上运行 Curio 的人。", - "message": "Hint: I am someone running Curio on whichever chain.", - "placeholder": null - }, - { - "id": "Press return to update {Toml} with Yugabyte info. A Backup file will be written to that folder before changes are made.", - "translation": "按回车键更新 {Toml} 以包含 Yugabyte 信息。在进行更改之前,将在该文件夹中写入备份文件。", - "message": "Press return to update {Toml} with Yugabyte info. A Backup file will be written to that folder before changes are made.", - "placeholder": null - }, - { - "id": "Error creating backup file: {Error}", - "translation": "创建备份文件时出错:{Error}", - "message": "Error creating backup file: {Error}", - "placeholder": null - }, - { - "id": "Error reading config.toml: {Error}", - "translation": "读取 config.toml 时出错:{Error}", - "message": "Error reading config.toml: {Error}", - "placeholder": null - }, - { - "id": "Error writing backup file: {Error}", - "translation": "写入备份文件时出错:{Error}", - "message": "Error writing backup file: {Error}", - "placeholder": null - }, - { - "id": "Error closing backup file: {Error}", - "translation": "关闭备份文件时出错:{Error}", - "message": "Error closing backup file: {Error}", - "placeholder": null - }, - { - "id": "Initializing a new miner actor.", - "translation": "初始化新的矿工角色。", - "message": "Initializing a new miner actor.", - "placeholder": null - }, - { - "id": "Enter the info to create a new miner", - "translation": "输入创建新矿工所需的信息", - "message": "Enter the info to create a new miner", - "placeholder": null - }, - { - "id": "Owner Address: {String}", - "translation": "所有者地址:{String}", - "message": "Owner Address: {String}", - "placeholder": null - }, - { - "id": "Worker Address: {String}", - "translation": "工作地址:{String}", - "message": "Worker Address: {String}", - "placeholder": null - }, - { - "id": "Sender Address: {String}", - "translation": "发送者地址:{String}", - "message": "Sender Address: {String}", - "placeholder": null - }, - { - "id": "Sector Size: {Ssize}", - "translation": "扇区大小: {Ssize}", - "message": "Sector Size: {Ssize}", - "placeholder": null - }, - { - "id": "Confidence epochs: {Confidence}", - "translation": "置信度时期: {Confidence}", - "message": "Confidence epochs: {Confidence}", - "placeholder": null - }, - { - "id": "Continue to verify the addresses and create a new miner actor.", - "translation": "继续验证地址并创建新的矿工角色。", - "message": "Continue to verify the addresses and create a new miner actor.", - "placeholder": null - }, - { - "id": "Miner creation error occurred: {Error}", - "translation": "矿工创建错误发生: {Error}", - "message": "Miner creation error occurred: {Error}", - "placeholder": null - }, - { - "id": "Enter the owner address", - "translation": "输入所有者地址", - "message": "Enter the owner address", - "placeholder": null - }, - { - "id": "No address provided", - "translation": "未提供地址", - "message": "No address provided", - "placeholder": null - }, - { - "id": "Failed to parse the address: {Error}", - "translation": "解析地址失败: {Error}", - "message": "Failed to parse the address: {Error}", - "placeholder": null - }, - { - "id": "Enter {Stringworker_senderi_1} address", - "translation": "输入 {Stringworker_senderi_1} 地址", - "message": "Enter {Stringworker_senderi_1} address", - "placeholder": null - }, - { - "id": "Enter the sector size", - "translation": "输入扇区大小", - "message": "Enter the sector size", - "placeholder": null - }, - { - "id": "Failed to parse sector size: {Error}", - "translation": "解析扇区大小失败: {Error}", - "message": "Failed to parse sector size: {Error}", - "placeholder": null - }, - { - "id": "Enter the confidence", - "translation": "输入置信度", - "message": "Enter the confidence", - "placeholder": null - }, - { - "id": "Failed to parse confidence: {Error}", - "translation": "解析置信度失败: {Error}", - "message": "Failed to parse confidence: {Error}", - "placeholder": null - }, - { - "id": "Failed to create the miner actor: {Error}", - "translation": "创建矿工角色失败: {Error}", - "message": "Failed to create the miner actor: {Error}", - "placeholder": null - }, - { - "id": "Miner {String} created successfully", - "translation": "矿工 {String} 创建成功", - "message": "Miner {String} created successfully", - "placeholder": null - }, - { - "id": "Cannot reach the DB: {Error}", - "translation": "无法访问数据库: {Error}", - "message": "Cannot reach the DB: {Error}", - "placeholder": null - }, - { - "id": "Error connecting to full node API: {Error}", - "translation": "连接到完整节点 API 时发生错误: {Error}", - "message": "Error connecting to full node API: {Error}", - "placeholder": null - }, - { - "id": "Pre-initialization steps complete", - "translation": "预初始化步骤完成", - "message": "Pre-initialization steps complete", - "placeholder": null - }, - { - "id": "Failed to generate random bytes for secret: {Error}", - "translation": "生成密码的随机字节失败: {Error}", - "message": "Failed to generate random bytes for secret: {Error}", - "placeholder": null - }, - { - "id": "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster {String}' to finish the configuration", - "translation": "请不要再次运行引导设置,因为矿工创建不是幂等的。 您需要运行 'curio config new-cluster {String}' 来完成配置。", - "message": "Please do not run guided-setup again as miner creation is not idempotent. You need to run 'curio config new-cluster {String}' to finish the configuration", - "placeholder": null - }, - { - "id": "Failed to get API info for FullNode: {Err}", - "translation": "无法获取 FullNode 的 API 信息: {Err}", - "message": "Failed to get API info for FullNode: {Err}", - "placeholder": null - }, - { - "id": "Failed to verify the auth token from daemon node: {Error}", - "translation": "无法验证来自守护进程节点的授权令牌: {Error}", - "message": "Failed to verify the auth token from daemon node: {Error}", - "placeholder": null - }, - { - "id": "Failed to encode the config: {Error}", - "translation": "无法编码配置: {Error}", - "message": "Failed to encode the config: {Error}", - "placeholder": null - }, - { - "id": "Failed to generate default config: {Error}", - "translation": "无法生成默认配置: {Error}", - "message": "Failed to generate default config: {Error}", - "placeholder": null - }, - { - "id": "Failed to insert 'base' config layer in database: {Error}", - "translation": "无法将 'base' 配置层插入数据库: {Error}", - "message": "Failed to insert 'base' config layer in database: {Error}", - "placeholder": null - }, - { - "id": "Failed to insert '{String}' config layer in database: {Error}", - "translation": "无法将 '{String}' 配置层插入数据库: {Error}", - "message": "Failed to insert '{String}' config layer in database: {Error}", - "placeholder": null - }, - { - "id": "New Curio configuration layer '{String}' created", - "translation": "新的 Curio 配置层 '{String}' 已创建", - "message": "New Curio configuration layer '{String}' created", - "placeholder": null - }, - { - "id": "This process is partially idempotent. Once a new miner actor has been created and subsequent steps fail, the user need to run 'curio config new-cluster \u003c miner ID \u003e' to finish the configuration.", - "translation": "该过程部分幂等。一旦创建了新的矿工角色,并且随后的步骤失败,用户需要运行 'curio config new-cluster \u003c 矿工 ID \u003e' 来完成配置。", - "message": "This process is partially idempotent. Once a new miner actor has been created and subsequent steps fail, the user need to run 'curio config new-cluster \u003c miner ID \u003e' to finish the configuration.", - "placeholder": null - }, - { - "id": "Confidence epochs", - "translation": "置信度时期", - "message": "Confidence epochs", - "placeholder": null - }, - { - "id": "Increase reliability using redundancy: start multiple machines with at-least the post layer: 'curio run --layers=post'", - "translation": "通过冗余增加可靠性:使用至少后层启动多台机器:'curio run --layers=post'", - "message": "Increase reliability using redundancy: start multiple machines with at-least the post layer: 'curio run --layers=post'", - "placeholder": null - }, - { - "id": "I want to:", - "translation": "我想要:", - "message": "I want to:", - "placeholder": null - }, - { - "id": "Configuration 'base' was updated to include this miner's address", - "translation": "配置 'base' 已更新以包含此矿工的地址", - "message": "Configuration 'base' was updated to include this miner's address", - "placeholder": null - }, - { - "id": "Cannot load base config: {Error}", - "translation": "无法加载基本配置: {Error}", - "message": "Cannot load base config: {Error}", - "placeholder": null - }, - { - "id": "Failed to load base config: {Error}", - "translation": "加载基本配置失败: {Error}", - "message": "Failed to load base config: {Error}", - "placeholder": null - }, - { - "id": "Failed to regenerate base config: {Error}", - "translation": "重新生成基本配置失败: {Error}", - "message": "Failed to regenerate base config: {Error}", - "placeholder": null - }, - { - "id": "Failed to load base config from database: {Error}", - "translation": "从数据库加载基本配置失败:{Error}", - "message": "Failed to load base config from database: {Error}", - "placeholder": null - }, - { - "id": "Failed to parse base config: {Error}", - "translation": "解析基本配置失败:{Error}", - "message": "Failed to parse base config: {Error}", - "placeholder": null - }, - { - "id": "Try the web interface with {Rendercurio_run___layersgui} for further guided improvements.", - "translation": "尝试使用{Rendercurio_run___layersgui}的网络界面进行更进一步的指导性改进。", - "message": "Try the web interface with {Rendercurio_run___layersgui} for further guided improvements.", - "placeholder": null - }, - { - "id": "Now shut down lotus-miner and lotus-worker and use run {Rendercurio_run} instead.", - "translation": "现在关闭lotus-miner和lotus-worker,改为使用{Rendercurio_run}运行。", - "message": "Now shut down lotus-miner and lotus-worker and use run {Rendercurio_run} instead.", - "placeholder": null - }, - { - "id": "Configuration 'base' was updated to include this miner's address ({MinerAddress}) and its wallet setup.", - "translation": "'base'配置已更新,包括该矿工的地址({MinerAddress})及其钱包设置。", - "message": "Configuration 'base' was updated to include this miner's address ({MinerAddress}) and its wallet setup.", - "placeholder": null - }, - { - "id": "Configuration 'base' was created to resemble this lotus-miner's config.toml .", - "translation": "'base'配置已创建,以类似于这个lotus-miner的config.toml。", - "message": "Configuration 'base' was created to resemble this lotus-miner's config.toml .", - "placeholder": null - } - ] -} \ No newline at end of file diff --git a/cmd/curio/internal/translations/locales/zh/out.gotext.json b/cmd/curio/internal/translations/locales/zh/out.gotext.json deleted file mode 100644 index bb9d25e4c..000000000 --- a/cmd/curio/internal/translations/locales/zh/out.gotext.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "language": "zh", - "messages": [] - } diff --git a/cmd/curio/internal/translations/translations.go b/cmd/curio/internal/translations/translations.go deleted file mode 100644 index 361e8e894..000000000 --- a/cmd/curio/internal/translations/translations.go +++ /dev/null @@ -1,27 +0,0 @@ -// Usage: -// To UPDATE translations: -// -// 1. add/change strings in guidedsetup folder that use d.T() or d.say(). -// -// 2. run `go generate` in the cmd/curio/internal/translations/ folder. -// -// 3. ChatGPT 3.5 can translate the ./locales/??/out.gotext.json files' -// which ONLY include the un-translated messages. -// APPEND to the messages.gotext.json files's array. -// -// ChatGPT fuss: -// - on a good day, you may need to hit "continue generating". -// - > 60? you'll need to give it sections of the file. -// -// 4. Re-import with `go generate` again. -// -// To ADD a language: -// 1. Add it to the list in updateLang.sh -// 2. Run `go generate` in the cmd/curio/internal/translations/ folder. -// 3. Follow the "Update translations" steps here. -// 4. Code will auto-detect the new language and use it. -// -// FUTURE Reliability: OpenAPI automation. -package translations - -//go:generate ./updateLang.sh diff --git a/cmd/curio/internal/translations/updateLang.sh b/cmd/curio/internal/translations/updateLang.sh deleted file mode 100755 index 984f63fd5..000000000 --- a/cmd/curio/internal/translations/updateLang.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -#OP: Only run if some file in ../guidedsetup* is newer than catalog.go -# Change this condition if using translations more widely. -if [ "$(find ../../guidedsetup/* -newer catalog.go)" ] || [ "$(find locales/* -newer catalog.go)" ]; then - gotext -srclang=en update -out=catalog.go -lang=en,zh,ko github.com/filecoin-project/lotus/cmd/curio/guidedsetup - go run knowns/main.go locales/zh locales/ko -fi diff --git a/cmd/curio/log.go b/cmd/curio/log.go deleted file mode 100644 index 0af41a679..000000000 --- a/cmd/curio/log.go +++ /dev/null @@ -1,105 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - lcli "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/cmd/curio/rpc" -) - -var logCmd = &cli.Command{ - Name: "log", - Usage: "Manage logging", - Subcommands: []*cli.Command{ - LogList, - LogSetLevel, - }, -} - -var LogList = &cli.Command{ - Name: "list", - Usage: "List log systems", - Action: func(cctx *cli.Context) error { - minerApi, closer, err := rpc.GetCurioAPI(cctx) - if err != nil { - return err - } - defer closer() - - ctx := lcli.ReqContext(cctx) - - systems, err := minerApi.LogList(ctx) - if err != nil { - return err - } - - for _, system := range systems { - fmt.Println(system) - } - - return nil - }, -} - -var LogSetLevel = &cli.Command{ - Name: "set-level", - Usage: "Set log level", - ArgsUsage: "[level]", - Description: `Set the log level for logging systems: - - The system flag can be specified multiple times. - - eg) log set-level --system chain --system chainxchg debug - - Available Levels: - debug - info - warn - error - - Environment Variables: - GOLOG_LOG_LEVEL - Default log level for all log systems - GOLOG_LOG_FMT - Change output log format (json, nocolor) - GOLOG_FILE - Write logs to file - GOLOG_OUTPUT - Specify whether to output to file, stderr, stdout or a combination, i.e. file+stderr -`, - Flags: []cli.Flag{ - &cli.StringSliceFlag{ - Name: "system", - Usage: "limit to log system", - Value: &cli.StringSlice{}, - }, - }, - Action: func(cctx *cli.Context) error { - minerApi, closer, err := rpc.GetCurioAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - - if !cctx.Args().Present() { - return fmt.Errorf("level is required") - } - - systems := cctx.StringSlice("system") - if len(systems) == 0 { - var err error - systems, err = minerApi.LogList(ctx) - if err != nil { - return err - } - } - - for _, system := range systems { - if err := minerApi.LogSetLevel(ctx, system, cctx.Args().First()); err != nil { - return xerrors.Errorf("setting log level on %s: %v", system, err) - } - } - - return nil - }, -} diff --git a/cmd/curio/main.go b/cmd/curio/main.go deleted file mode 100644 index 9a092dad0..000000000 --- a/cmd/curio/main.go +++ /dev/null @@ -1,189 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - "os/signal" - "runtime/pprof" - "syscall" - - "github.com/docker/go-units" - "github.com/fatih/color" - logging "github.com/ipfs/go-log/v2" - "github.com/mitchellh/go-homedir" - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-paramfetch" - - "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/cmd/curio/deps" - "github.com/filecoin-project/lotus/cmd/curio/guidedsetup" - "github.com/filecoin-project/lotus/lib/lotuslog" - "github.com/filecoin-project/lotus/lib/tracing" - "github.com/filecoin-project/lotus/node/repo" -) - -var log = logging.Logger("main") - -const ( - FlagMinerRepo = "miner-repo" -) - -func setupCloseHandler() { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - <-c - fmt.Println("\r- Ctrl+C pressed in Terminal") - _ = pprof.Lookup("goroutine").WriteTo(os.Stdout, 1) - panic(1) - }() -} - -func main() { - - lotuslog.SetupLogLevels() - - local := []*cli.Command{ - cliCmd, - runCmd, - stopCmd, - configCmd, - testCmd, - webCmd, - guidedsetup.GuidedsetupCmd, - sealCmd, - marketCmd, - fetchParamCmd, - } - - jaeger := tracing.SetupJaegerTracing("curio") - defer func() { - if jaeger != nil { - _ = jaeger.ForceFlush(context.Background()) - } - }() - - for _, cmd := range local { - cmd := cmd - originBefore := cmd.Before - cmd.Before = func(cctx *cli.Context) error { - if jaeger != nil { - _ = jaeger.Shutdown(cctx.Context) - } - jaeger = tracing.SetupJaegerTracing("curio/" + cmd.Name) - - if cctx.IsSet("color") { - color.NoColor = !cctx.Bool("color") - } - - if originBefore != nil { - return originBefore(cctx) - } - - return nil - } - } - - app := &cli.App{ - Name: "curio", - Usage: "Filecoin decentralized storage network provider", - Version: build.UserVersion(), - EnableBashCompletion: true, - Before: func(c *cli.Context) error { - setupCloseHandler() - return nil - }, - Flags: []cli.Flag{ - &cli.BoolFlag{ - // examined in the Before above - Name: "color", - Usage: "use color in display output", - DefaultText: "depends on output being a TTY", - }, - &cli.StringFlag{ - Name: "panic-reports", - EnvVars: []string{"CURIO_PANIC_REPORT_PATH"}, - Hidden: true, - Value: "~/.curio", // should follow --repo default - }, - &cli.StringFlag{ - Name: "db-host", - EnvVars: []string{"CURIO_DB_HOST", "CURIO_HARMONYDB_HOSTS"}, - Usage: "Command separated list of hostnames for yugabyte cluster", - Value: "127.0.0.1", - }, - &cli.StringFlag{ - Name: "db-name", - EnvVars: []string{"CURIO_DB_NAME", "CURIO_HARMONYDB_NAME"}, - Value: "yugabyte", - }, - &cli.StringFlag{ - Name: "db-user", - EnvVars: []string{"CURIO_DB_USER", "CURIO_HARMONYDB_USERNAME"}, - Value: "yugabyte", - }, - &cli.StringFlag{ - Name: "db-password", - EnvVars: []string{"CURIO_DB_PASSWORD", "CURIO_HARMONYDB_PASSWORD"}, - Value: "yugabyte", - }, - &cli.StringFlag{ - Name: "db-port", - EnvVars: []string{"CURIO_DB_PORT", "CURIO_HARMONYDB_PORT"}, - Value: "5433", - }, - &cli.StringFlag{ - Name: deps.FlagRepoPath, - EnvVars: []string{"CURIO_REPO_PATH"}, - Value: "~/.curio", - }, - cliutil.FlagVeryVerbose, - }, - Commands: local, - After: func(c *cli.Context) error { - if r := recover(); r != nil { - p, err := homedir.Expand(c.String(FlagMinerRepo)) - if err != nil { - log.Errorw("could not expand repo path for panic report", "error", err) - panic(r) - } - - // Generate report in CURIO_PATH and re-raise panic - build.GeneratePanicReport(c.String("panic-reports"), p, c.App.Name) - panic(r) - } - return nil - }, - } - app.Setup() - app.Metadata["repoType"] = repo.Curio - lcli.RunApp(app) -} - -var fetchParamCmd = &cli.Command{ - Name: "fetch-params", - Usage: "Fetch proving parameters", - ArgsUsage: "[sectorSize]", - Action: func(cctx *cli.Context) error { - if cctx.NArg() != 1 { - return xerrors.Errorf("incorrect number of arguments") - } - sectorSizeInt, err := units.RAMInBytes(cctx.Args().First()) - if err != nil { - return xerrors.Errorf("error parsing sector size (specify as \"32GiB\", for instance): %w", err) - } - sectorSize := uint64(sectorSizeInt) - - err = paramfetch.GetParams(lcli.ReqContext(cctx), build.ParametersJSON(), build.SrsJSON(), sectorSize) - if err != nil { - return xerrors.Errorf("fetching proof parameters: %w", err) - } - - return nil - }, -} diff --git a/cmd/curio/market.go b/cmd/curio/market.go deleted file mode 100644 index cc562db93..000000000 --- a/cmd/curio/market.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "fmt" - "sort" - - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/cmd/curio/deps" - "github.com/filecoin-project/lotus/curiosrc/market/lmrpc" -) - -var marketCmd = &cli.Command{ - Name: "market", - Subcommands: []*cli.Command{ - marketRPCInfoCmd, - }, -} - -var marketRPCInfoCmd = &cli.Command{ - Flags: []cli.Flag{ - &cli.StringSliceFlag{ - Name: "layers", - Usage: "list of layers to be interpreted (atop defaults). Default: base", - }, - }, - Action: func(cctx *cli.Context) error { - db, err := deps.MakeDB(cctx) - if err != nil { - return err - } - - cfg, err := deps.GetConfig(cctx, db) - if err != nil { - return xerrors.Errorf("get config: %w", err) - } - - ts, err := lmrpc.MakeTokens(cfg) - if err != nil { - return xerrors.Errorf("make tokens: %w", err) - } - - var addrTokens []struct { - Address string - Token string - } - - for address, s := range ts { - addrTokens = append(addrTokens, struct { - Address string - Token string - }{ - Address: address.String(), - Token: s, - }) - } - - sort.Slice(addrTokens, func(i, j int) bool { - return addrTokens[i].Address < addrTokens[j].Address - }) - - for _, at := range addrTokens { - fmt.Printf("[lotus-miner/boost compatible] %s %s\n", at.Address, at.Token) - } - - return nil - }, - Name: "rpc-info", -} diff --git a/cmd/curio/migrate.go b/cmd/curio/migrate.go deleted file mode 100644 index 06ab7d0f9..000000000 --- a/cmd/curio/migrate.go +++ /dev/null @@ -1 +0,0 @@ -package main diff --git a/cmd/curio/pipeline.go b/cmd/curio/pipeline.go deleted file mode 100644 index 1c3f5d94a..000000000 --- a/cmd/curio/pipeline.go +++ /dev/null @@ -1,135 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" - lcli "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/cmd/curio/deps" - "github.com/filecoin-project/lotus/curiosrc/seal" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" -) - -var sealCmd = &cli.Command{ - Name: "seal", - Usage: "Manage the sealing pipeline", - Subcommands: []*cli.Command{ - sealStartCmd, - }, -} - -var sealStartCmd = &cli.Command{ - Name: "start", - Usage: "Start new sealing operations manually", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "actor", - Usage: "Specify actor address to start sealing sectors for", - Required: true, - }, - &cli.BoolFlag{ - Name: "now", - Usage: "Start sealing sectors for all actors now (not on schedule)", - }, - &cli.BoolFlag{ - Name: "cc", - Usage: "Start sealing new CC sectors", - }, - &cli.IntFlag{ - Name: "count", - Usage: "Number of sectors to start", - Value: 1, - }, - &cli.BoolFlag{ - Name: "synthetic", - Usage: "Use synthetic PoRep", - Value: false, // todo implement synthetic - }, - &cli.StringSliceFlag{ - Name: "layers", - Usage: "list of layers to be interpreted (atop defaults). Default: base", - }, - }, - Action: func(cctx *cli.Context) error { - if !cctx.Bool("now") { - return xerrors.Errorf("schedule not implemented, use --now") - } - if !cctx.IsSet("actor") { - return cli.ShowCommandHelp(cctx, "start") - } - if !cctx.Bool("cc") { - return xerrors.Errorf("only CC sectors supported for now") - } - - act, err := address.NewFromString(cctx.String("actor")) - if err != nil { - return xerrors.Errorf("parsing --actor: %w", err) - } - - ctx := lcli.ReqContext(cctx) - dep, err := deps.GetDepsCLI(ctx, cctx) - if err != nil { - return err - } - - /* - create table sectors_sdr_pipeline ( - sp_id bigint not null, - sector_number bigint not null, - - -- at request time - create_time timestamp not null, - reg_seal_proof int not null, - comm_d_cid text not null, - - [... other not relevant fields] - */ - - mid, err := address.IDFromAddress(act) - if err != nil { - return xerrors.Errorf("getting miner id: %w", err) - } - - mi, err := dep.Full.StateMinerInfo(ctx, act, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting miner info: %w", err) - } - - nv, err := dep.Full.StateNetworkVersion(ctx, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting network version: %w", err) - } - - wpt := mi.WindowPoStProofType - spt, err := miner.PreferredSealProofTypeFromWindowPoStType(nv, wpt, cctx.Bool("synthetic")) - if err != nil { - return xerrors.Errorf("getting seal proof type: %w", err) - } - - num, err := seal.AllocateSectorNumbers(ctx, dep.Full, dep.DB, act, cctx.Int("count"), func(tx *harmonydb.Tx, numbers []abi.SectorNumber) (bool, error) { - for _, n := range numbers { - _, err := tx.Exec("insert into sectors_sdr_pipeline (sp_id, sector_number, reg_seal_proof) values ($1, $2, $3)", mid, n, spt) - if err != nil { - return false, xerrors.Errorf("inserting into sectors_sdr_pipeline: %w", err) - } - } - return true, nil - }) - if err != nil { - return xerrors.Errorf("allocating sector numbers: %w", err) - } - - for _, number := range num { - fmt.Println(number) - } - - return nil - }, -} diff --git a/cmd/curio/proving.go b/cmd/curio/proving.go deleted file mode 100644 index 3b5a3e0e4..000000000 --- a/cmd/curio/proving.go +++ /dev/null @@ -1,204 +0,0 @@ -package main - -import ( - "context" - "database/sql" - "encoding/json" - "errors" - "fmt" - "os" - "time" - - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/dline" - - "github.com/filecoin-project/lotus/cmd/curio/deps" - curio "github.com/filecoin-project/lotus/curiosrc" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" -) - -var testCmd = &cli.Command{ - Name: "test", - Usage: "Utility functions for testing", - Subcommands: []*cli.Command{ - //provingInfoCmd, - wdPostCmd, - }, - Before: func(cctx *cli.Context) error { - return nil - }, -} - -var wdPostCmd = &cli.Command{ - Name: "window-post", - Aliases: []string{"wd", "windowpost", "wdpost"}, - Usage: "Compute a proof-of-spacetime for a sector (requires the sector to be pre-sealed). These will not send to the chain.", - Subcommands: []*cli.Command{ - wdPostHereCmd, - wdPostTaskCmd, - }, -} - -// wdPostTaskCmd writes to harmony_task and wdpost_partition_tasks, then waits for the result. -// It is intended to be used to test the windowpost scheduler. -// The end of the compute task puts the task_id onto wdpost_proofs, which is read by the submit task. -// The submit task will not send test tasks to the chain, and instead will write the result to harmony_test. -// The result is read by this command, and printed to stdout. -var wdPostTaskCmd = &cli.Command{ - Name: "task", - Aliases: []string{"scheduled", "schedule", "async", "asynchronous"}, - Usage: "Test the windowpost scheduler by running it on the next available curio. ", - Flags: []cli.Flag{ - &cli.Uint64Flag{ - Name: "deadline", - Usage: "deadline to compute WindowPoSt for ", - Value: 0, - }, - &cli.StringSliceFlag{ - Name: "layers", - Usage: "list of layers to be interpreted (atop defaults). Default: base", - }, - }, - Action: func(cctx *cli.Context) error { - ctx := context.Background() - - deps, err := deps.GetDeps(ctx, cctx) - if err != nil { - return xerrors.Errorf("get config: %w", err) - } - - ts, err := deps.Full.ChainHead(ctx) - if err != nil { - return xerrors.Errorf("cannot get chainhead %w", err) - } - ht := ts.Height() - - // It's not important to be super-accurate as it's only for basic testing. - addr, err := address.NewFromString(deps.Cfg.Addresses[0].MinerAddresses[0]) - if err != nil { - return xerrors.Errorf("cannot get miner address %w", err) - } - maddr, err := address.IDFromAddress(addr) - if err != nil { - return xerrors.Errorf("cannot get miner id %w", err) - } - var taskId int64 - - _, err = deps.DB.BeginTransaction(ctx, func(tx *harmonydb.Tx) (commit bool, err error) { - err = tx.QueryRow(`INSERT INTO harmony_task (name, posted_time, added_by) VALUES ('WdPost', CURRENT_TIMESTAMP, 123) RETURNING id`).Scan(&taskId) - if err != nil { - log.Error("inserting harmony_task: ", err) - return false, xerrors.Errorf("inserting harmony_task: %w", err) - } - _, err = tx.Exec(`INSERT INTO wdpost_partition_tasks - (task_id, sp_id, proving_period_start, deadline_index, partition_index) VALUES ($1, $2, $3, $4, $5)`, - taskId, maddr, ht, cctx.Uint64("deadline"), 0) - if err != nil { - log.Error("inserting wdpost_partition_tasks: ", err) - return false, xerrors.Errorf("inserting wdpost_partition_tasks: %w", err) - } - _, err = tx.Exec("INSERT INTO harmony_test (task_id) VALUES ($1)", taskId) - if err != nil { - return false, xerrors.Errorf("inserting into harmony_tests: %w", err) - } - return true, nil - }, harmonydb.OptionRetry()) - if err != nil { - return xerrors.Errorf("writing SQL transaction: %w", err) - } - fmt.Printf("Inserted task %v. Waiting for success ", taskId) - var result sql.NullString - for { - time.Sleep(time.Second) - err = deps.DB.QueryRow(ctx, `SELECT result FROM harmony_test WHERE task_id=$1`, taskId).Scan(&result) - if err != nil { - return xerrors.Errorf("reading result from harmony_test: %w", err) - } - if result.Valid { - break - } - fmt.Print(".") - } - fmt.Println() - log.Infof("Result: %s", result.String) - return nil - }, -} - -// This command is intended to be used to verify PoSt compute performance. -// It will not send any messages to the chain. Since it can compute any deadline, output may be incorrectly timed for the chain. -// The entire processing happens in this process while you wait. It does not use the scheduler. -var wdPostHereCmd = &cli.Command{ - Name: "here", - Aliases: []string{"cli"}, - Usage: "Compute WindowPoSt for performance and configuration testing.", - Description: `Note: This command is intended to be used to verify PoSt compute performance. -It will not send any messages to the chain. Since it can compute any deadline, output may be incorrectly timed for the chain.`, - ArgsUsage: "[deadline index]", - Flags: []cli.Flag{ - &cli.Uint64Flag{ - Name: "deadline", - Usage: "deadline to compute WindowPoSt for ", - Value: 0, - }, - &cli.StringSliceFlag{ - Name: "layers", - Usage: "list of layers to be interpreted (atop defaults). Default: base", - }, - &cli.StringFlag{ - Name: "storage-json", - Usage: "path to json file containing storage config", - Value: "~/.curio/storage.json", - }, - &cli.Uint64Flag{ - Name: "partition", - Usage: "partition to compute WindowPoSt for", - Value: 0, - }, - }, - Action: func(cctx *cli.Context) error { - - ctx := context.Background() - deps, err := deps.GetDeps(ctx, cctx) - if err != nil { - return err - } - - wdPostTask, wdPoStSubmitTask, derlareRecoverTask, err := curio.WindowPostScheduler( - ctx, deps.Cfg.Fees, deps.Cfg.Proving, deps.Full, deps.Verif, deps.LW, nil, nil, - deps.As, deps.Maddrs, deps.DB, deps.Stor, deps.Si, deps.Cfg.Subsystems.WindowPostMaxTasks) - if err != nil { - return err - } - _, _ = wdPoStSubmitTask, derlareRecoverTask - - if len(deps.Maddrs) == 0 { - return errors.New("no miners to compute WindowPoSt for") - } - head, err := deps.Full.ChainHead(ctx) - if err != nil { - return xerrors.Errorf("failed to get chain head: %w", err) - } - - di := dline.NewInfo(head.Height(), cctx.Uint64("deadline"), 0, 0, 0, 10 /*challenge window*/, 0, 0) - - for maddr := range deps.Maddrs { - out, err := wdPostTask.DoPartition(ctx, head, address.Address(maddr), di, cctx.Uint64("partition")) - if err != nil { - fmt.Println("Error computing WindowPoSt for miner", maddr, err) - continue - } - fmt.Println("Computed WindowPoSt for miner", maddr, ":") - err = json.NewEncoder(os.Stdout).Encode(out) - if err != nil { - fmt.Println("Could not encode WindowPoSt output for miner", maddr, err) - continue - } - } - - return nil - }, -} diff --git a/cmd/curio/rpc/rpc.go b/cmd/curio/rpc/rpc.go deleted file mode 100644 index 1b2bb25e6..000000000 --- a/cmd/curio/rpc/rpc.go +++ /dev/null @@ -1,325 +0,0 @@ -// Package rpc provides all direct access to this node. -package rpc - -import ( - "context" - "encoding/base64" - "encoding/json" - "net" - "net/http" - "net/url" - "os" - "path/filepath" - "time" - - "github.com/gbrlsnchs/jwt/v3" - "github.com/google/uuid" - "github.com/gorilla/mux" - logging "github.com/ipfs/go-log/v2" - "github.com/mitchellh/go-homedir" - "github.com/urfave/cli/v2" - "go.opencensus.io/tag" - "golang.org/x/sync/errgroup" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-jsonrpc" - "github.com/filecoin-project/go-jsonrpc/auth" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/client" - cliutil "github.com/filecoin-project/lotus/cli/util" - "github.com/filecoin-project/lotus/cmd/curio/deps" - "github.com/filecoin-project/lotus/curiosrc/market" - "github.com/filecoin-project/lotus/curiosrc/web" - "github.com/filecoin-project/lotus/lib/rpcenc" - "github.com/filecoin-project/lotus/metrics" - "github.com/filecoin-project/lotus/metrics/proxy" - "github.com/filecoin-project/lotus/node/repo" - "github.com/filecoin-project/lotus/storage/paths" - "github.com/filecoin-project/lotus/storage/sealer/fsutil" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -const metaFile = "sectorstore.json" - -var log = logging.Logger("curio/rpc") -var permissioned = os.Getenv("LOTUS_DISABLE_AUTH_PERMISSIONED") != "1" - -func CurioHandler( - authv func(ctx context.Context, token string) ([]auth.Permission, error), - remote http.HandlerFunc, - a api.Curio, - permissioned bool) http.Handler { - mux := mux.NewRouter() - readerHandler, readerServerOpt := rpcenc.ReaderParamDecoder() - rpcServer := jsonrpc.NewServer(jsonrpc.WithServerErrors(api.RPCErrors), readerServerOpt) - - wapi := proxy.MetricedAPI[api.Curio, api.CurioStruct](a) - if permissioned { - wapi = api.PermissionedAPI[api.Curio, api.CurioStruct](wapi) - } - - rpcServer.Register("Filecoin", wapi) - rpcServer.AliasMethod("rpc.discover", "Filecoin.Discover") - - mux.Handle("/rpc/v0", rpcServer) - mux.Handle("/rpc/streams/v0/push/{uuid}", readerHandler) - mux.PathPrefix("/remote").HandlerFunc(remote) - mux.PathPrefix("/").Handler(http.DefaultServeMux) // pprof - - if !permissioned { - return mux - } - - ah := &auth.Handler{ - Verify: authv, - Next: mux.ServeHTTP, - } - return ah -} - -type CurioAPI struct { - *deps.Deps - paths.SectorIndex - ShutdownChan chan struct{} -} - -func (p *CurioAPI) Version(context.Context) (api.Version, error) { - return api.CurioAPIVersion0, nil -} -func (p *CurioAPI) StorageDetachLocal(ctx context.Context, path string) error { - path, err := homedir.Expand(path) - if err != nil { - return xerrors.Errorf("expanding local path: %w", err) - } - - // check that we have the path opened - lps, err := p.LocalStore.Local(ctx) - if err != nil { - return xerrors.Errorf("getting local path list: %w", err) - } - - var localPath *storiface.StoragePath - for _, lp := range lps { - if lp.LocalPath == path { - lp := lp // copy to make the linter happy - localPath = &lp - break - } - } - if localPath == nil { - return xerrors.Errorf("no local paths match '%s'", path) - } - - // drop from the persisted storage.json - var found bool - if err := p.LocalPaths.SetStorage(func(sc *storiface.StorageConfig) { - out := make([]storiface.LocalPath, 0, len(sc.StoragePaths)) - for _, storagePath := range sc.StoragePaths { - if storagePath.Path != path { - out = append(out, storagePath) - continue - } - found = true - } - sc.StoragePaths = out - }); err != nil { - return xerrors.Errorf("set storage config: %w", err) - } - if !found { - // maybe this is fine? - return xerrors.Errorf("path not found in storage.json") - } - - // unregister locally, drop from sector index - return p.LocalStore.ClosePath(ctx, localPath.ID) -} - -func (p *CurioAPI) StorageLocal(ctx context.Context) (map[storiface.ID]string, error) { - ps, err := p.LocalStore.Local(ctx) - if err != nil { - return nil, err - } - - var out = make(map[storiface.ID]string) - for _, path := range ps { - out[path.ID] = path.LocalPath - } - - return out, nil -} - -func (p *CurioAPI) StorageStat(ctx context.Context, id storiface.ID) (fsutil.FsStat, error) { - return p.Stor.FsStat(ctx, id) -} - -func (p *CurioAPI) AllocatePieceToSector(ctx context.Context, maddr address.Address, piece api.PieceDealInfo, rawSize int64, source url.URL, header http.Header) (api.SectorOffset, error) { - di := market.NewPieceIngester(p.Deps.DB, p.Deps.Full) - - return di.AllocatePieceToSector(ctx, maddr, piece, rawSize, source, header) -} - -// Trigger shutdown -func (p *CurioAPI) Shutdown(context.Context) error { - close(p.ShutdownChan) - return nil -} - -func (p *CurioAPI) StorageInit(ctx context.Context, path string, opts storiface.LocalStorageMeta) error { - path, err := homedir.Expand(path) - if err != nil { - return xerrors.Errorf("expanding local path: %w", err) - } - - if err := os.MkdirAll(path, 0755); err != nil { - if !os.IsExist(err) { - return err - } - } - _, err = os.Stat(filepath.Join(path, metaFile)) - if !os.IsNotExist(err) { - if err == nil { - return xerrors.Errorf("path is already initialized") - } - return err - } - if opts.ID == "" { - opts.ID = storiface.ID(uuid.New().String()) - } - if !(opts.CanStore || opts.CanSeal) { - return xerrors.Errorf("must specify at least one of --store or --seal") - } - b, err := json.MarshalIndent(opts, "", " ") - if err != nil { - return xerrors.Errorf("marshaling storage config: %w", err) - } - if err := os.WriteFile(filepath.Join(path, metaFile), b, 0644); err != nil { - return xerrors.Errorf("persisting storage metadata (%s): %w", filepath.Join(path, metaFile), err) - } - return nil -} - -func (p *CurioAPI) StorageAddLocal(ctx context.Context, path string) error { - path, err := homedir.Expand(path) - if err != nil { - return xerrors.Errorf("expanding local path: %w", err) - } - - if err := p.LocalStore.OpenPath(ctx, path); err != nil { - return xerrors.Errorf("opening local path: %w", err) - } - - if err := p.LocalPaths.SetStorage(func(sc *storiface.StorageConfig) { - sc.StoragePaths = append(sc.StoragePaths, storiface.LocalPath{Path: path}) - }); err != nil { - return xerrors.Errorf("get storage config: %w", err) - } - - return nil -} - -func (p *CurioAPI) LogList(ctx context.Context) ([]string, error) { - return logging.GetSubsystems(), nil -} - -func (p *CurioAPI) LogSetLevel(ctx context.Context, subsystem, level string) error { - return logging.SetLogLevel(subsystem, level) -} - -func ListenAndServe(ctx context.Context, dependencies *deps.Deps, shutdownChan chan struct{}) error { - fh := &paths.FetchHandler{Local: dependencies.LocalStore, PfHandler: &paths.DefaultPartialFileHandler{}} - remoteHandler := func(w http.ResponseWriter, r *http.Request) { - if !auth.HasPerm(r.Context(), nil, api.PermAdmin) { - w.WriteHeader(401) - _ = json.NewEncoder(w).Encode(struct{ Error string }{"unauthorized: missing admin permission"}) - return - } - - fh.ServeHTTP(w, r) - } - - var authVerify func(context.Context, string) ([]auth.Permission, error) - { - privateKey, err := base64.StdEncoding.DecodeString(dependencies.Cfg.Apis.StorageRPCSecret) - if err != nil { - return xerrors.Errorf("decoding storage rpc secret: %w", err) - } - authVerify = func(ctx context.Context, token string) ([]auth.Permission, error) { - var payload deps.JwtPayload - if _, err := jwt.Verify([]byte(token), jwt.NewHS256(privateKey), &payload); err != nil { - return nil, xerrors.Errorf("JWT Verification failed: %w", err) - } - - return payload.Allow, nil - } - } - // Serve the RPC. - srv := &http.Server{ - Handler: CurioHandler( - authVerify, - remoteHandler, - &CurioAPI{dependencies, dependencies.Si, shutdownChan}, - permissioned), - ReadHeaderTimeout: time.Minute * 3, - BaseContext: func(listener net.Listener) context.Context { - ctx, _ := tag.New(context.Background(), tag.Upsert(metrics.APIInterface, "lotus-worker")) - return ctx - }, - Addr: dependencies.ListenAddr, - } - - log.Infof("Setting up RPC server at %s", dependencies.ListenAddr) - eg := errgroup.Group{} - eg.Go(srv.ListenAndServe) - - if dependencies.Cfg.Subsystems.EnableWebGui { - web, err := web.GetSrv(ctx, dependencies) - if err != nil { - return err - } - - go func() { - <-ctx.Done() - log.Warn("Shutting down...") - if err := srv.Shutdown(context.TODO()); err != nil { - log.Errorf("shutting down RPC server failed: %s", err) - } - if err := web.Shutdown(context.Background()); err != nil { - log.Errorf("shutting down web server failed: %s", err) - } - log.Warn("Graceful shutdown successful") - }() - - uiAddress := dependencies.Cfg.Subsystems.GuiAddress - if uiAddress == "" || uiAddress[0] == ':' { - uiAddress = "localhost" + uiAddress - } - log.Infof("GUI: http://%s", uiAddress) - eg.Go(web.ListenAndServe) - } - return eg.Wait() -} - -func GetCurioAPI(ctx *cli.Context) (api.Curio, jsonrpc.ClientCloser, error) { - addr, headers, err := cliutil.GetRawAPI(ctx, repo.Curio, "v0") - if err != nil { - return nil, nil, err - } - - u, err := url.Parse(addr) - if err != nil { - return nil, nil, xerrors.Errorf("parsing miner api URL: %w", err) - } - - switch u.Scheme { - case "ws": - u.Scheme = "http" - case "wss": - u.Scheme = "https" - } - - addr = u.String() - - return client.NewCurioRpc(ctx.Context, addr, headers) -} diff --git a/cmd/curio/run.go b/cmd/curio/run.go deleted file mode 100644 index cacacfc0f..000000000 --- a/cmd/curio/run.go +++ /dev/null @@ -1,196 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - "strings" - - "github.com/pkg/errors" - "github.com/urfave/cli/v2" - "go.opencensus.io/stats" - "go.opencensus.io/tag" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/build" - lcli "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/cmd/curio/deps" - "github.com/filecoin-project/lotus/cmd/curio/rpc" - "github.com/filecoin-project/lotus/cmd/curio/tasks" - "github.com/filecoin-project/lotus/curiosrc/market/lmrpc" - "github.com/filecoin-project/lotus/lib/ulimit" - "github.com/filecoin-project/lotus/metrics" - "github.com/filecoin-project/lotus/node" -) - -type stackTracer interface { - StackTrace() errors.StackTrace -} - -var runCmd = &cli.Command{ - Name: "run", - Usage: "Start a Curio process", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "listen", - Usage: "host address and port the worker api will listen on", - Value: "0.0.0.0:12300", - EnvVars: []string{"LOTUS_WORKER_LISTEN"}, - }, - &cli.BoolFlag{ - Name: "nosync", - Usage: "don't check full-node sync status", - }, - &cli.BoolFlag{ - Name: "halt-after-init", - Usage: "only run init, then return", - Hidden: true, - }, - &cli.BoolFlag{ - Name: "manage-fdlimit", - Usage: "manage open file limit", - Value: true, - }, - &cli.StringFlag{ - Name: "storage-json", - Usage: "path to json file containing storage config", - Value: "~/.curio/storage.json", - }, - &cli.StringFlag{ - Name: "journal", - Usage: "path to journal files", - Value: "~/.curio/", - }, - &cli.StringSliceFlag{ - Name: "layers", - Aliases: []string{"l", "layer"}, - Usage: "list of layers to be interpreted (atop defaults). Default: base", - }, - }, - Action: func(cctx *cli.Context) (err error) { - defer func() { - if err != nil { - if err, ok := err.(stackTracer); ok { - for _, f := range err.StackTrace() { - fmt.Printf("%+s:%d\n", f, f) - } - } - } - }() - if !cctx.Bool("enable-gpu-proving") { - err := os.Setenv("BELLMAN_NO_GPU", "true") - if err != nil { - return err - } - } - - if err := os.MkdirAll(os.TempDir(), 0755); err != nil { - log.Errorf("ensuring tempdir exists: %s", err) - } - - ctx, _ := tag.New(lcli.DaemonContext(cctx), - tag.Insert(metrics.Version, build.BuildVersion), - tag.Insert(metrics.Commit, build.CurrentCommit), - tag.Insert(metrics.NodeType, "curio"), - ) - shutdownChan := make(chan struct{}) - { - var ctxclose func() - ctx, ctxclose = context.WithCancel(ctx) - go func() { - <-shutdownChan - ctxclose() - }() - } - // Register all metric views - /* - if err := view.Register( - metrics.MinerNodeViews..., - ); err != nil { - log.Fatalf("Cannot register the view: %v", err) - } - */ - // Set the metric to one so it is published to the exporter - stats.Record(ctx, metrics.LotusInfo.M(1)) - - if cctx.Bool("manage-fdlimit") { - if _, _, err := ulimit.ManageFdLimit(); err != nil { - log.Errorf("setting file descriptor limit: %s", err) - } - } - - dependencies := &deps.Deps{} - err = dependencies.PopulateRemainingDeps(ctx, cctx, true) - if err != nil { - return err - } - - taskEngine, err := tasks.StartTasks(ctx, dependencies) - - if err != nil { - return nil - } - defer taskEngine.GracefullyTerminate() - - if err := lmrpc.ServeCurioMarketRPCFromConfig(dependencies.DB, dependencies.Full, dependencies.Cfg); err != nil { - return xerrors.Errorf("starting market RPCs: %w", err) - } - - err = rpc.ListenAndServe(ctx, dependencies, shutdownChan) // Monitor for shutdown. - if err != nil { - return err - } - - finishCh := node.MonitorShutdown(shutdownChan) //node.ShutdownHandler{Component: "rpc server", StopFunc: rpcStopper}, - //node.ShutdownHandler{Component: "curio", StopFunc: stop}, - - <-finishCh - return nil - }, -} - -var webCmd = &cli.Command{ - Name: "web", - Usage: "Start Curio web interface", - Description: `Start an instance of Curio web interface. - This creates the 'web' layer if it does not exist, then calls run with that layer.`, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "listen", - Usage: "Address to listen on", - Value: "127.0.0.1:4701", - }, - &cli.BoolFlag{ - Name: "nosync", - Usage: "don't check full-node sync status", - }, - &cli.StringSliceFlag{ - Name: "layers", - Usage: "list of layers to be interpreted (atop defaults). Default: base", - }, - }, - Action: func(cctx *cli.Context) error { - - db, err := deps.MakeDB(cctx) - if err != nil { - return err - } - - webtxt, err := getConfig(db, "web") - if err != nil || webtxt == "" { - - s := `[Susbystems] - EnableWebGui = true - ` - if err = setConfig(db, "web", s); err != nil { - return err - } - } - layers := append([]string{"web"}, cctx.StringSlice("layers")...) - err = cctx.Set("layers", strings.Join(layers, ",")) - if err != nil { - return err - } - return runCmd.Action(cctx) - }, -} diff --git a/cmd/curio/stop.go b/cmd/curio/stop.go deleted file mode 100644 index eb61a34fa..000000000 --- a/cmd/curio/stop.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - _ "net/http/pprof" - - "github.com/urfave/cli/v2" - - lcli "github.com/filecoin-project/lotus/cli" -) - -var stopCmd = &cli.Command{ - Name: "stop", - Usage: "Stop a running Curio process", - Flags: []cli.Flag{}, - Action: func(cctx *cli.Context) error { - - api, closer, err := lcli.GetAPI(cctx) - if err != nil { - return err - } - defer closer() - - err = api.Shutdown(lcli.ReqContext(cctx)) - if err != nil { - return err - } - - return nil - }, -} diff --git a/cmd/curio/storage.go b/cmd/curio/storage.go deleted file mode 100644 index 2fa6d2d52..000000000 --- a/cmd/curio/storage.go +++ /dev/null @@ -1,499 +0,0 @@ -package main - -import ( - "fmt" - "math/bits" - "sort" - "strconv" - "strings" - "time" - - "github.com/docker/go-units" - "github.com/fatih/color" - "github.com/google/uuid" - "github.com/mitchellh/go-homedir" - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/chain/types" - lcli "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/cmd/curio/rpc" - "github.com/filecoin-project/lotus/storage/sealer/fsutil" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -var storageCmd = &cli.Command{ - Name: "storage", - Usage: "manage sector storage", - Description: `Sectors can be stored across many filesystem paths. These -commands provide ways to manage the storage the miner will used to store sectors -long term for proving (references as 'store') as well as how sectors will be -stored while moving through the sealing pipeline (references as 'seal').`, - Subcommands: []*cli.Command{ - storageAttachCmd, - storageDetachCmd, - storageListCmd, - storageFindCmd, - /*storageDetachCmd, - storageRedeclareCmd, - storageCleanupCmd, - storageLocks,*/ - }, -} - -var storageAttachCmd = &cli.Command{ - Name: "attach", - Usage: "attach local storage path", - ArgsUsage: "[path]", - Description: `Storage can be attached to the miner using this command. The storage volume -list is stored local to the miner in storage.json set in curio run. We do not -recommend manually modifying this value without further understanding of the -storage system. - -Each storage volume contains a configuration file which describes the -capabilities of the volume. When the '--init' flag is provided, this file will -be created using the additional flags. - -Weight -A high weight value means data will be more likely to be stored in this path - -Seal -Data for the sealing process will be stored here - -Store -Finalized sectors that will be moved here for long term storage and be proven -over time - `, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "init", - Usage: "initialize the path first", - }, - &cli.Uint64Flag{ - Name: "weight", - Usage: "(for init) path weight", - Value: 10, - }, - &cli.BoolFlag{ - Name: "seal", - Usage: "(for init) use path for sealing", - }, - &cli.BoolFlag{ - Name: "store", - Usage: "(for init) use path for long-term storage", - }, - &cli.StringFlag{ - Name: "max-storage", - Usage: "(for init) limit storage space for sectors (expensive for very large paths!)", - }, - &cli.StringSliceFlag{ - Name: "groups", - Usage: "path group names", - }, - &cli.StringSliceFlag{ - Name: "allow-to", - Usage: "path groups allowed to pull data from this path (allow all if not specified)", - }, - }, - Action: func(cctx *cli.Context) error { - minerApi, closer, err := rpc.GetCurioAPI(cctx) - if err != nil { - return err - } - - defer closer() - ctx := lcli.ReqContext(cctx) - - if cctx.NArg() != 1 { - return lcli.IncorrectNumArgs(cctx) - } - - p, err := homedir.Expand(cctx.Args().First()) - if err != nil { - return xerrors.Errorf("expanding path: %w", err) - } - - if cctx.Bool("init") { - var maxStor int64 - if cctx.IsSet("max-storage") { - maxStor, err = units.RAMInBytes(cctx.String("max-storage")) - if err != nil { - return xerrors.Errorf("parsing max-storage: %w", err) - } - } - - cfg := storiface.LocalStorageMeta{ - ID: storiface.ID(uuid.New().String()), - Weight: cctx.Uint64("weight"), - CanSeal: cctx.Bool("seal"), - CanStore: cctx.Bool("store"), - MaxStorage: uint64(maxStor), - Groups: cctx.StringSlice("groups"), - AllowTo: cctx.StringSlice("allow-to"), - } - - if !(cfg.CanStore || cfg.CanSeal) { - return xerrors.Errorf("must specify at least one of --store or --seal") - } - - if err := minerApi.StorageInit(ctx, p, cfg); err != nil { - return xerrors.Errorf("init storage: %w", err) - } - } - - return minerApi.StorageAddLocal(ctx, p) - }, -} - -var storageDetachCmd = &cli.Command{ - Name: "detach", - Usage: "detach local storage path", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "really-do-it", - }, - }, - ArgsUsage: "[path]", - Action: func(cctx *cli.Context) error { - minerApi, closer, err := rpc.GetCurioAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - - if cctx.NArg() != 1 { - return lcli.IncorrectNumArgs(cctx) - } - - p, err := homedir.Expand(cctx.Args().First()) - if err != nil { - return xerrors.Errorf("expanding path: %w", err) - } - - if !cctx.Bool("really-do-it") { - return xerrors.Errorf("pass --really-do-it to execute the action") - } - - return minerApi.StorageDetachLocal(ctx, p) - }, -} - -var storageListCmd = &cli.Command{ - Name: "list", - Usage: "list local storage paths", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "local", - Usage: "only list local storage paths", - }, - }, - Subcommands: []*cli.Command{ - //storageListSectorsCmd, - }, - Action: func(cctx *cli.Context) error { - minerApi, closer, err := rpc.GetCurioAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - - st, err := minerApi.StorageList(ctx) - if err != nil { - return err - } - - local, err := minerApi.StorageLocal(ctx) - if err != nil { - return err - } - - type fsInfo struct { - storiface.ID - sectors []storiface.Decl - stat fsutil.FsStat - } - - sorted := make([]fsInfo, 0, len(st)) - for id, decls := range st { - if cctx.Bool("local") { - if _, ok := local[id]; !ok { - continue - } - } - - st, err := minerApi.StorageStat(ctx, id) - if err != nil { - sorted = append(sorted, fsInfo{ID: id, sectors: decls}) - continue - } - - sorted = append(sorted, fsInfo{id, decls, st}) - } - - sort.Slice(sorted, func(i, j int) bool { - if sorted[i].stat.Capacity != sorted[j].stat.Capacity { - return sorted[i].stat.Capacity > sorted[j].stat.Capacity - } - return sorted[i].ID < sorted[j].ID - }) - - for _, s := range sorted { - - var cnt [5]int - for _, decl := range s.sectors { - for i := range cnt { - if decl.SectorFileType&(1< 98: - percCol = color.FgRed - case usedPercent > 90: - percCol = color.FgYellow - } - - set := (st.Capacity - st.FSAvailable) * barCols / st.Capacity - used := (st.Capacity - (st.FSAvailable + st.Reserved)) * barCols / st.Capacity - reserved := set - used - bar := safeRepeat("#", int(used)) + safeRepeat("*", int(reserved)) + safeRepeat(" ", int(barCols-set)) - - desc := "" - if st.Max > 0 { - desc = " (filesystem)" - } - - fmt.Printf("\t[%s] %s/%s %s%s\n", color.New(percCol).Sprint(bar), - types.SizeStr(types.NewInt(uint64(st.Capacity-st.FSAvailable))), - types.SizeStr(types.NewInt(uint64(st.Capacity))), - color.New(percCol).Sprintf("%d%%", usedPercent), desc) - } - - // optional configured limit bar - if st.Max > 0 { - usedPercent := st.Used * 100 / st.Max - - percCol := color.FgGreen - switch { - case usedPercent > 98: - percCol = color.FgRed - case usedPercent > 90: - percCol = color.FgYellow - } - - set := st.Used * barCols / st.Max - used := (st.Used + st.Reserved) * barCols / st.Max - reserved := set - used - bar := safeRepeat("#", int(used)) + safeRepeat("*", int(reserved)) + safeRepeat(" ", int(barCols-set)) - - fmt.Printf("\t[%s] %s/%s %s (limit)\n", color.New(percCol).Sprint(bar), - types.SizeStr(types.NewInt(uint64(st.Used))), - types.SizeStr(types.NewInt(uint64(st.Max))), - color.New(percCol).Sprintf("%d%%", usedPercent)) - } - - fmt.Printf("\t%s; %s; %s; %s; %s; Reserved: %s\n", - color.YellowString("Unsealed: %d", cnt[0]), - color.GreenString("Sealed: %d", cnt[1]), - color.BlueString("Caches: %d", cnt[2]), - color.GreenString("Updated: %d", cnt[3]), - color.BlueString("Update-caches: %d", cnt[4]), - types.SizeStr(types.NewInt(uint64(st.Reserved)))) - - si, err := minerApi.StorageInfo(ctx, s.ID) - if err != nil { - return err - } - - fmt.Print("\t") - if si.CanSeal || si.CanStore { - fmt.Printf("Weight: %d; Use: ", si.Weight) - if si.CanSeal { - fmt.Print(color.MagentaString("Seal ")) - } - if si.CanStore { - fmt.Print(color.CyanString("Store")) - } - } else { - fmt.Print(color.HiYellowString("Use: ReadOnly")) - } - fmt.Println() - - if len(si.Groups) > 0 { - fmt.Printf("\tGroups: %s\n", strings.Join(si.Groups, ", ")) - } - if len(si.AllowTo) > 0 { - fmt.Printf("\tAllowTo: %s\n", strings.Join(si.AllowTo, ", ")) - } - - if len(si.AllowTypes) > 0 || len(si.DenyTypes) > 0 { - denied := storiface.FTAll.SubAllowed(si.AllowTypes, si.DenyTypes) - allowed := storiface.FTAll ^ denied - - switch { - case bits.OnesCount64(uint64(allowed)) == 0: - fmt.Printf("\tAllow Types: %s\n", color.RedString("None")) - case bits.OnesCount64(uint64(allowed)) < bits.OnesCount64(uint64(denied)): - fmt.Printf("\tAllow Types: %s\n", color.GreenString(strings.Join(allowed.Strings(), " "))) - default: - fmt.Printf("\tDeny Types: %s\n", color.RedString(strings.Join(denied.Strings(), " "))) - } - } - - if localPath, ok := local[s.ID]; ok { - fmt.Printf("\tLocal: %s\n", color.GreenString(localPath)) - } - for i, l := range si.URLs { - var rtt string - if _, ok := local[s.ID]; !ok && i == 0 { - rtt = " (latency: " + ping.Truncate(time.Microsecond*100).String() + ")" - } - - fmt.Printf("\tURL: %s%s\n", l, rtt) // TODO; try pinging maybe?? print latency? - } - fmt.Println() - } - - return nil - }, -} - -type storedSector struct { - id storiface.ID - store storiface.SectorStorageInfo - types map[storiface.SectorFileType]bool -} - -var storageFindCmd = &cli.Command{ - Name: "find", - Usage: "find sector in the storage system", - ArgsUsage: "[miner address] [sector number]", - Action: func(cctx *cli.Context) error { - minerApi, closer, err := rpc.GetCurioAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - - if cctx.NArg() != 2 { - return lcli.IncorrectNumArgs(cctx) - } - - maddr := cctx.Args().First() - ma, err := address.NewFromString(maddr) - if err != nil { - return xerrors.Errorf("parsing miner address: %w", err) - } - - mid, err := address.IDFromAddress(ma) - if err != nil { - return err - } - - if !cctx.Args().Present() { - return xerrors.New("Usage: lotus-miner storage find [sector number]") - } - - snum, err := strconv.ParseUint(cctx.Args().Get(1), 10, 64) - if err != nil { - return err - } - - sid := abi.SectorID{ - Miner: abi.ActorID(mid), - Number: abi.SectorNumber(snum), - } - - sectorTypes := []storiface.SectorFileType{ - storiface.FTUnsealed, storiface.FTSealed, storiface.FTCache, storiface.FTUpdate, storiface.FTUpdateCache, - } - - byId := make(map[storiface.ID]*storedSector) - for _, sectorType := range sectorTypes { - infos, err := minerApi.StorageFindSector(ctx, sid, sectorType, 0, false) - if err != nil { - return xerrors.Errorf("finding sector type %d: %w", sectorType, err) - } - - for _, info := range infos { - sts, ok := byId[info.ID] - if !ok { - sts = &storedSector{ - id: info.ID, - store: info, - types: make(map[storiface.SectorFileType]bool), - } - byId[info.ID] = sts - } - sts.types[sectorType] = true - } - } - - local, err := minerApi.StorageLocal(ctx) - if err != nil { - return err - } - - var out []*storedSector - for _, sector := range byId { - out = append(out, sector) - } - sort.Slice(out, func(i, j int) bool { - return out[i].id < out[j].id - }) - - for _, info := range out { - var types []string - for sectorType, present := range info.types { - if present { - types = append(types, sectorType.String()) - } - } - sort.Strings(types) // Optional: Sort types for consistent output - fmt.Printf("In %s (%s)\n", info.id, strings.Join(types, ", ")) - fmt.Printf("\tSealing: %t; Storage: %t\n", info.store.CanSeal, info.store.CanStore) - if localPath, ok := local[info.id]; ok { - fmt.Printf("\tLocal (%s)\n", localPath) - } else { - fmt.Printf("\tRemote\n") - } - for _, l := range info.store.URLs { - fmt.Printf("\tURL: %s\n", l) - } - } - - return nil - }, -} diff --git a/cmd/curio/tasks/tasks.go b/cmd/curio/tasks/tasks.go deleted file mode 100644 index 71923018d..000000000 --- a/cmd/curio/tasks/tasks.go +++ /dev/null @@ -1,240 +0,0 @@ -// Package tasks contains tasks that can be run by the curio command. -package tasks - -import ( - "context" - "sort" - "strings" - "time" - - logging "github.com/ipfs/go-log/v2" - "github.com/samber/lo" - "golang.org/x/exp/maps" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - - "github.com/filecoin-project/lotus/cmd/curio/deps" - curio "github.com/filecoin-project/lotus/curiosrc" - "github.com/filecoin-project/lotus/curiosrc/chainsched" - "github.com/filecoin-project/lotus/curiosrc/ffi" - "github.com/filecoin-project/lotus/curiosrc/gc" - "github.com/filecoin-project/lotus/curiosrc/message" - "github.com/filecoin-project/lotus/curiosrc/piece" - "github.com/filecoin-project/lotus/curiosrc/seal" - "github.com/filecoin-project/lotus/curiosrc/winning" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/lazy" - "github.com/filecoin-project/lotus/lib/must" - "github.com/filecoin-project/lotus/node/modules" - "github.com/filecoin-project/lotus/node/modules/dtypes" -) - -var log = logging.Logger("curio/deps") - -func StartTasks(ctx context.Context, dependencies *deps.Deps) (*harmonytask.TaskEngine, error) { - cfg := dependencies.Cfg - db := dependencies.DB - full := dependencies.Full - verif := dependencies.Verif - lw := dependencies.LW - as := dependencies.As - maddrs := dependencies.Maddrs - stor := dependencies.Stor - lstor := dependencies.LocalStore - si := dependencies.Si - var activeTasks []harmonytask.TaskInterface - - sender, sendTask := message.NewSender(full, full, db) - activeTasks = append(activeTasks, sendTask) - - chainSched := chainsched.New(full) - - var needProofParams bool - - /////////////////////////////////////////////////////////////////////// - ///// Task Selection - /////////////////////////////////////////////////////////////////////// - { - // PoSt - - if cfg.Subsystems.EnableWindowPost { - wdPostTask, wdPoStSubmitTask, derlareRecoverTask, err := curio.WindowPostScheduler( - ctx, cfg.Fees, cfg.Proving, full, verif, lw, sender, chainSched, - as, maddrs, db, stor, si, cfg.Subsystems.WindowPostMaxTasks) - - if err != nil { - return nil, err - } - activeTasks = append(activeTasks, wdPostTask, wdPoStSubmitTask, derlareRecoverTask) - needProofParams = true - } - - if cfg.Subsystems.EnableWinningPost { - winPoStTask := winning.NewWinPostTask(cfg.Subsystems.WinningPostMaxTasks, db, lw, verif, full, maddrs) - activeTasks = append(activeTasks, winPoStTask) - needProofParams = true - } - } - - slrLazy := lazy.MakeLazy(func() (*ffi.SealCalls, error) { - return ffi.NewSealCalls(stor, lstor, si), nil - }) - - { - // Piece handling - if cfg.Subsystems.EnableParkPiece { - parkPieceTask := piece.NewParkPieceTask(db, must.One(slrLazy.Val()), cfg.Subsystems.ParkPieceMaxTasks) - cleanupPieceTask := piece.NewCleanupPieceTask(db, must.One(slrLazy.Val()), 0) - activeTasks = append(activeTasks, parkPieceTask, cleanupPieceTask) - } - } - - hasAnySealingTask := cfg.Subsystems.EnableSealSDR || - cfg.Subsystems.EnableSealSDRTrees || - cfg.Subsystems.EnableSendPrecommitMsg || - cfg.Subsystems.EnablePoRepProof || - cfg.Subsystems.EnableMoveStorage || - cfg.Subsystems.EnableSendCommitMsg - { - // Sealing - - var sp *seal.SealPoller - var slr *ffi.SealCalls - if hasAnySealingTask { - sp = seal.NewPoller(db, full) - go sp.RunPoller(ctx) - - slr = must.One(slrLazy.Val()) - } - - // NOTE: Tasks with the LEAST priority are at the top - if cfg.Subsystems.EnableSealSDR { - sdrTask := seal.NewSDRTask(full, db, sp, slr, cfg.Subsystems.SealSDRMaxTasks) - activeTasks = append(activeTasks, sdrTask) - } - if cfg.Subsystems.EnableSealSDRTrees { - treesTask := seal.NewTreesTask(sp, db, slr, cfg.Subsystems.SealSDRTreesMaxTasks) - finalizeTask := seal.NewFinalizeTask(cfg.Subsystems.FinalizeMaxTasks, sp, slr, db) - activeTasks = append(activeTasks, treesTask, finalizeTask) - } - if cfg.Subsystems.EnableSendPrecommitMsg { - precommitTask := seal.NewSubmitPrecommitTask(sp, db, full, sender, as, cfg.Fees.MaxPreCommitGasFee) - activeTasks = append(activeTasks, precommitTask) - } - if cfg.Subsystems.EnablePoRepProof { - porepTask := seal.NewPoRepTask(db, full, sp, slr, cfg.Subsystems.PoRepProofMaxTasks) - activeTasks = append(activeTasks, porepTask) - needProofParams = true - } - if cfg.Subsystems.EnableMoveStorage { - moveStorageTask := seal.NewMoveStorageTask(sp, slr, db, cfg.Subsystems.MoveStorageMaxTasks) - activeTasks = append(activeTasks, moveStorageTask) - } - if cfg.Subsystems.EnableSendCommitMsg { - commitTask := seal.NewSubmitCommitTask(sp, db, full, sender, as, cfg.Fees.MaxCommitGasFee) - activeTasks = append(activeTasks, commitTask) - } - } - - if hasAnySealingTask { - // Sealing nodes maintain storage index when bored - storageEndpointGcTask := gc.NewStorageEndpointGC(si, stor, db) - activeTasks = append(activeTasks, storageEndpointGcTask) - } - - if needProofParams { - for spt := range dependencies.ProofTypes { - if err := modules.GetParams(true)(spt); err != nil { - return nil, xerrors.Errorf("getting params: %w", err) - } - } - } - - minerAddresses := make([]string, 0, len(maddrs)) - for k := range maddrs { - minerAddresses = append(minerAddresses, address.Address(k).String()) - } - - log.Infow("This Curio instance handles", - "miner_addresses", minerAddresses, - "tasks", lo.Map(activeTasks, func(t harmonytask.TaskInterface, _ int) string { return t.TypeDetails().Name })) - - // harmony treats the first task as highest priority, so reverse the order - // (we could have just appended to this list in the reverse order, but defining - // tasks in pipeline order is more intuitive) - activeTasks = lo.Reverse(activeTasks) - - ht, err := harmonytask.New(db, activeTasks, dependencies.ListenAddr) - if err != nil { - return nil, err - } - go machineDetails(dependencies, activeTasks, ht.ResourcesAvailable().MachineID) - - if hasAnySealingTask { - watcher, err := message.NewMessageWatcher(db, ht, chainSched, full) - if err != nil { - return nil, err - } - _ = watcher - } - - if cfg.Subsystems.EnableWindowPost || hasAnySealingTask { - go chainSched.Run(ctx) - } - - return ht, nil -} - -func machineDetails(deps *deps.Deps, activeTasks []harmonytask.TaskInterface, machineID int) { - taskNames := lo.Map(activeTasks, func(item harmonytask.TaskInterface, _ int) string { - return item.TypeDetails().Name - }) - - miners := lo.Map(maps.Keys(deps.Maddrs), func(item dtypes.MinerAddress, _ int) string { - return address.Address(item).String() - }) - sort.Strings(miners) - - _, err := deps.DB.Exec(context.Background(), `INSERT INTO harmony_machine_details - (tasks, layers, startup_time, miners, machine_id) VALUES ($1, $2, $3, $4, $5) - ON CONFLICT (machine_id) DO UPDATE SET tasks=$1, layers=$2, startup_time=$3, miners=$4`, - strings.Join(taskNames, ","), strings.Join(deps.Layers, ","), - time.Now(), strings.Join(miners, ","), machineID) - - if err != nil { - log.Errorf("failed to update machine details: %s", err) - return - } - - // maybePostWarning - if !lo.Contains(taskNames, "WdPost") && !lo.Contains(taskNames, "WinPost") { - // Maybe we aren't running a PoSt for these miners? - var allMachines []struct { - MachineID int `db:"machine_id"` - Miners string `db:"miners"` - Tasks string `db:"tasks"` - } - err := deps.DB.Select(context.Background(), &allMachines, `SELECT machine_id, miners, tasks FROM harmony_machine_details`) - if err != nil { - log.Errorf("failed to get machine details: %s", err) - return - } - - for _, miner := range miners { - var myPostIsHandled bool - for _, m := range allMachines { - if !lo.Contains(strings.Split(m.Miners, ","), miner) { - continue - } - if lo.Contains(strings.Split(m.Tasks, ","), "WdPost") && lo.Contains(strings.Split(m.Tasks, ","), "WinPost") { - myPostIsHandled = true - break - } - } - if !myPostIsHandled { - log.Errorf("No PoSt tasks are running for miner %s. Start handling PoSts immediately with:\n\tcurio run --layers=\"post\" ", miner) - } - } - } -} diff --git a/cmd/lotus-bench/cli.go b/cmd/lotus-bench/cli.go index 4379036d3..526db8e22 100644 --- a/cmd/lotus-bench/cli.go +++ b/cmd/lotus-bench/cli.go @@ -304,9 +304,9 @@ func (c *CMD) Stop() { func (c *CMD) Report() { total := time.Since(c.start) - fmt.Fprintf(c.w, "[%s]:\n", c.cmd) - fmt.Fprintf(c.w, "- Options:\n") - fmt.Fprintf(c.w, " - concurrency: %d\n", c.concurrency) - fmt.Fprintf(c.w, " - qps: %d\n", c.qps) + fmt.Fprintf(c.w, "[%s]:\n", c.cmd) //nolint:errcheck + fmt.Fprintf(c.w, "- Options:\n") //nolint:errcheck + fmt.Fprintf(c.w, " - concurrency: %d\n", c.concurrency) //nolint:errcheck + fmt.Fprintf(c.w, " - qps: %d\n", c.qps) //nolint:errcheck c.reporter.Print(total, c.w) } diff --git a/cmd/lotus-bench/main.go b/cmd/lotus-bench/main.go index 1a7a0d087..8aa6768aa 100644 --- a/cmd/lotus-bench/main.go +++ b/cmd/lotus-bench/main.go @@ -100,7 +100,7 @@ func main() { app := &cli.App{ Name: "lotus-bench", Usage: "Benchmark performance of lotus on your hardware", - Version: build.UserVersion(), + Version: string(build.NodeUserVersion()), DisableSliceFlagSeparator: true, Commands: []*cli.Command{ proveCmd, diff --git a/cmd/lotus-bench/reporter.go b/cmd/lotus-bench/reporter.go index 7ade7b19d..d33f94c7d 100644 --- a/cmd/lotus-bench/reporter.go +++ b/cmd/lotus-bench/reporter.go @@ -93,16 +93,16 @@ func (r *Reporter) Print(elapsed time.Duration, w io.Writer) { totalLatency += latency } - fmt.Fprintf(w, "- Total Requests: %d\n", nrReq) - fmt.Fprintf(w, "- Total Duration: %dms\n", elapsed.Milliseconds()) - fmt.Fprintf(w, "- Requests/sec: %f\n", float64(nrReq)/elapsed.Seconds()) - fmt.Fprintf(w, "- Avg latency: %dms\n", totalLatency/nrReq) - fmt.Fprintf(w, "- Median latency: %dms\n", r.latencies[nrReq/2]) - fmt.Fprintf(w, "- Latency distribution:\n") + fmt.Fprintf(w, "- Total Requests: %d\n", nrReq) //nolint:errcheck + fmt.Fprintf(w, "- Total Duration: %dms\n", elapsed.Milliseconds()) //nolint:errcheck + fmt.Fprintf(w, "- Requests/sec: %f\n", float64(nrReq)/elapsed.Seconds()) //nolint:errcheck + fmt.Fprintf(w, "- Avg latency: %dms\n", totalLatency/nrReq) //nolint:errcheck + fmt.Fprintf(w, "- Median latency: %dms\n", r.latencies[nrReq/2]) //nolint:errcheck + fmt.Fprintf(w, "- Latency distribution:\n") //nolint:errcheck percentiles := []float64{0.1, 0.5, 0.9, 0.95, 0.99, 0.999} for _, p := range percentiles { idx := int64(p * float64(nrReq)) - fmt.Fprintf(w, " %s%% in %dms\n", fmt.Sprintf("%.2f", p*100.0), r.latencies[idx]) + _, _ = fmt.Fprintf(w, " %s%% in %dms\n", fmt.Sprintf("%.2f", p*100.0), r.latencies[idx]) } // create a simple histogram with 10 buckets spanning the range of latency @@ -135,19 +135,19 @@ func (r *Reporter) Print(elapsed time.Duration, w io.Writer) { } // print the histogram using a tabwriter which will align the columns nicely - fmt.Fprintf(w, "- Histogram:\n") + _, _ = fmt.Fprintf(w, "- Histogram:\n") const padding = 2 tabWriter := tabwriter.NewWriter(w, 0, 0, padding, ' ', tabwriter.AlignRight|tabwriter.Debug) for i := 0; i < nrBucket; i++ { ratio := float64(buckets[i].cnt) / float64(nrReq) bars := strings.Repeat("#", int(ratio*100)) - fmt.Fprintf(tabWriter, " %d-%dms\t%d\t%s (%s%%)\n", buckets[i].start, buckets[i].end, buckets[i].cnt, bars, fmt.Sprintf("%.2f", ratio*100)) + _, _ = fmt.Fprintf(tabWriter, " %d-%dms\t%d\t%s (%s%%)\n", buckets[i].start, buckets[i].end, buckets[i].cnt, bars, fmt.Sprintf("%.2f", ratio*100)) } tabWriter.Flush() //nolint:errcheck - fmt.Fprintf(w, "- Status codes:\n") + _, _ = fmt.Fprintf(w, "- Status codes:\n") for code, cnt := range r.statusCodes { - fmt.Fprintf(w, " [%d]: %d\n", code, cnt) + _, _ = fmt.Fprintf(w, " [%d]: %d\n", code, cnt) } // print the 10 most occurring errors (in case error values are not unique) @@ -163,12 +163,12 @@ func (r *Reporter) Print(elapsed time.Duration, w io.Writer) { sort.Slice(sortedErrors, func(i, j int) bool { return sortedErrors[i].cnt > sortedErrors[j].cnt }) - fmt.Fprintf(w, "- Errors (top 10):\n") + _, _ = fmt.Fprintf(w, "- Errors (top 10):\n") for i, se := range sortedErrors { if i > 10 { break } - fmt.Fprintf(w, " [%s]: %d\n", se.err, se.cnt) + _, _ = fmt.Fprintf(w, " [%s]: %d\n", se.err, se.cnt) } } diff --git a/cmd/lotus-bench/rpc.go b/cmd/lotus-bench/rpc.go index 4af4bdb27..547d6fb49 100644 --- a/cmd/lotus-bench/rpc.go +++ b/cmd/lotus-bench/rpc.go @@ -395,10 +395,10 @@ func (rpc *RPCMethod) Stop() { func (rpc *RPCMethod) Report() { total := time.Since(rpc.start) - fmt.Fprintf(rpc.w, "[%s]:\n", rpc.method) - fmt.Fprintf(rpc.w, "- Options:\n") - fmt.Fprintf(rpc.w, " - concurrency: %d\n", rpc.concurrency) - fmt.Fprintf(rpc.w, " - params: %s\n", rpc.params) - fmt.Fprintf(rpc.w, " - qps: %d\n", rpc.qps) + fmt.Fprintf(rpc.w, "[%s]:\n", rpc.method) //nolint:errcheck + fmt.Fprintf(rpc.w, "- Options:\n") //nolint:errcheck + fmt.Fprintf(rpc.w, " - concurrency: %d\n", rpc.concurrency) //nolint:errcheck + fmt.Fprintf(rpc.w, " - params: %s\n", rpc.params) //nolint:errcheck + fmt.Fprintf(rpc.w, " - qps: %d\n", rpc.qps) //nolint:errcheck rpc.reporter.Print(total, rpc.w) } diff --git a/cmd/lotus-fountain/main.go b/cmd/lotus-fountain/main.go index 36d5faf0c..191a91da4 100644 --- a/cmd/lotus-fountain/main.go +++ b/cmd/lotus-fountain/main.go @@ -41,7 +41,7 @@ func main() { app := &cli.App{ Name: "lotus-fountain", Usage: "Devnet token distribution utility", - Version: build.UserVersion(), + Version: string(build.NodeUserVersion()), Flags: []cli.Flag{ &cli.StringFlag{ Name: "repo", diff --git a/cmd/lotus-gateway/main.go b/cmd/lotus-gateway/main.go index 35a43e18b..2c5279ed4 100644 --- a/cmd/lotus-gateway/main.go +++ b/cmd/lotus-gateway/main.go @@ -40,7 +40,7 @@ func main() { app := &cli.App{ Name: "lotus-gateway", Usage: "Public API server for lotus", - Version: build.UserVersion(), + Version: string(build.NodeUserVersion()), Flags: []cli.Flag{ &cli.StringFlag{ Name: "repo", diff --git a/cmd/lotus-health/main.go b/cmd/lotus-health/main.go index 59c81e7c9..8e58624b6 100644 --- a/cmd/lotus-health/main.go +++ b/cmd/lotus-health/main.go @@ -36,7 +36,7 @@ func main() { app := &cli.App{ Name: "lotus-health", Usage: "Tools for monitoring lotus daemon health", - Version: build.UserVersion(), + Version: string(build.NodeUserVersion()), Commands: local, Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/cmd/lotus-miner/allinfo_test.go b/cmd/lotus-miner/allinfo_test.go index 2388f2f7a..a0735fc97 100644 --- a/cmd/lotus-miner/allinfo_test.go +++ b/cmd/lotus-miner/allinfo_test.go @@ -2,7 +2,6 @@ package main import ( - "context" "flag" "testing" "time" @@ -43,11 +42,5 @@ func TestMinerAllInfo(t *testing.T) { t.Run("pre-info-all", run) - //stm: @CLIENT_DATA_IMPORT_001, @CLIENT_STORAGE_DEALS_GET_001 - dh := kit.NewDealHarness(t, client, miner, miner) - deal, res, inPath := dh.MakeOnlineDeal(context.Background(), kit.MakeFullDealParams{Rseed: 6}) - outPath := dh.PerformRetrieval(context.Background(), deal, res.Root, false) - kit.AssertFilesEqual(t, inPath, outPath) - t.Run("post-info-all", run) } diff --git a/cmd/lotus-miner/dagstore.go b/cmd/lotus-miner/dagstore.go deleted file mode 100644 index c0e37f63b..000000000 --- a/cmd/lotus-miner/dagstore.go +++ /dev/null @@ -1,282 +0,0 @@ -package main - -import ( - "fmt" - "os" - "strings" - - "github.com/fatih/color" - "github.com/ipfs/go-cid" - "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, - dagstoreRegisterShardCmd, - dagstoreInitializeShardCmd, - dagstoreRecoverShardCmd, - dagstoreInitializeAllCmd, - dagstoreGcCmd, - dagstoreLookupPiecesCmd, - }, -} - -var dagstoreListShardsCmd = &cli.Command{ - Name: "list-shards", - Usage: "List all shards known to the dagstore, with their current status", - Action: func(cctx *cli.Context) error { - 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 - } - - return printTableShards(shards) - }, -} - -var dagstoreRegisterShardCmd = &cli.Command{ - Name: "register-shard", - ArgsUsage: "[key]", - Usage: "Register a shard", - Action: func(cctx *cli.Context) error { - if cctx.NArg() != 1 { - return lcli.IncorrectNumArgs(cctx) - } - - marketsAPI, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - ctx := lcli.ReqContext(cctx) - - shardKey := cctx.Args().First() - err = marketsAPI.DagstoreRegisterShard(ctx, shardKey) - if err != nil { - return err - } - - fmt.Println("Registered shard " + shardKey) - return nil - }, -} - -var dagstoreInitializeShardCmd = &cli.Command{ - Name: "initialize-shard", - ArgsUsage: "[key]", - Usage: "Initialize the specified shard", - Action: func(cctx *cli.Context) error { - if cctx.NArg() != 1 { - return lcli.IncorrectNumArgs(cctx) - } - - 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", - Action: func(cctx *cli.Context) error { - if cctx.NArg() != 1 { - return lcli.IncorrectNumArgs(cctx) - } - - 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", - }, - }, - Action: func(cctx *cli.Context) error { - 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", - Action: func(cctx *cli.Context) error { - 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 - }, -} - -func printTableShards(shards []api.DagstoreShardInfo) error { - 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 { - trimmedState := strings.TrimPrefix(s.State, "ShardState") - if c, ok := colors[s.State]; ok { - return color.New(c).Sprint(trimmedState) - } - return trimmedState - }(), - "Error": s.Error, - } - tw.Write(m) - } - return tw.Flush(os.Stdout) -} - -var dagstoreLookupPiecesCmd = &cli.Command{ - Name: "lookup-pieces", - Usage: "Lookup pieces that a given CID belongs to", - ArgsUsage: "", - Action: func(cctx *cli.Context) error { - if cctx.NArg() != 1 { - return lcli.IncorrectNumArgs(cctx) - } - - cidStr := cctx.Args().First() - cid, err := cid.Parse(cidStr) - if err != nil { - return fmt.Errorf("invalid CID: %w", err) - } - - marketsApi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - ctx := lcli.ReqContext(cctx) - - shards, err := marketsApi.DagstoreLookupPieces(ctx, cid) - if err != nil { - return err - } - - return printTableShards(shards) - }, -} diff --git a/cmd/lotus-miner/index_provider.go b/cmd/lotus-miner/index_provider.go deleted file mode 100644 index 2b6838a4b..000000000 --- a/cmd/lotus-miner/index_provider.go +++ /dev/null @@ -1,62 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/ipfs/go-cid" - "github.com/urfave/cli/v2" - - lcli "github.com/filecoin-project/lotus/cli" -) - -var indexProvCmd = &cli.Command{ - Name: "index", - Usage: "Manage the index provider on the markets subsystem", - Subcommands: []*cli.Command{ - indexProvAnnounceCmd, - indexProvAnnounceAllCmd, - }, -} - -var indexProvAnnounceCmd = &cli.Command{ - Name: "announce", - ArgsUsage: "", - Usage: "Announce a deal to indexers so they can download its index", - Action: func(cctx *cli.Context) error { - if cctx.NArg() != 1 { - return lcli.IncorrectNumArgs(cctx) - } - - proposalCidStr := cctx.Args().First() - proposalCid, err := cid.Parse(proposalCidStr) - if err != nil { - return fmt.Errorf("invalid deal proposal CID: %w", err) - } - - marketsApi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - ctx := lcli.ReqContext(cctx) - - return marketsApi.IndexerAnnounceDeal(ctx, proposalCid) - }, -} - -var indexProvAnnounceAllCmd = &cli.Command{ - Name: "announce-all", - Usage: "Announce all active deals to indexers so they can download the indices", - Action: func(cctx *cli.Context) error { - marketsApi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - ctx := lcli.ReqContext(cctx) - - return marketsApi.IndexerAnnounceAllDeals(ctx) - }, -} diff --git a/cmd/lotus-miner/info_all.go b/cmd/lotus-miner/info_all.go index 5b83467a2..253d2befc 100644 --- a/cmd/lotus-miner/info_all.go +++ b/cmd/lotus-miner/info_all.go @@ -112,72 +112,6 @@ var infoAllCmd = &cli.Command{ fmt.Println("ERROR: ", err) } - fmt.Println("\n#: Storage Ask") - if err := getAskCmd.Action(cctx); err != nil { - fmt.Println("ERROR: ", err) - } - - fmt.Println("\n#: Storage Deals") - { - fs := &flag.FlagSet{} - for _, f := range dealsListCmd.Flags { - if err := f.Apply(fs); err != nil { - fmt.Println("ERROR: ", err) - } - } - if err := fs.Parse([]string{"--verbose"}); err != nil { - fmt.Println("ERROR: ", err) - } - - if err := dealsListCmd.Action(cli.NewContext(cctx.App, fs, cctx)); err != nil { - fmt.Println("ERROR: ", err) - } - } - - fmt.Println("\n#: Storage Deals JSON") - { - fs := &flag.FlagSet{} - for _, f := range dealsListCmd.Flags { - if err := f.Apply(fs); err != nil { - fmt.Println("ERROR: ", err) - } - } - if err := fs.Parse([]string{"--verbose", "--format=json"}); err != nil { - fmt.Println("ERROR: ", err) - } - - if err := dealsListCmd.Action(cli.NewContext(cctx.App, fs, cctx)); err != nil { - fmt.Println("ERROR: ", err) - } - } - - fmt.Println("\n#: Data Transfers") - { - fs := &flag.FlagSet{} - for _, f := range transfersListCmd.Flags { - if err := f.Apply(fs); err != nil { - fmt.Println("ERROR: ", err) - } - } - if err := fs.Parse([]string{"--verbose", "--completed", "--show-failed"}); err != nil { - fmt.Println("ERROR: ", err) - } - - if err := transfersListCmd.Action(cli.NewContext(cctx.App, fs, cctx)); err != nil { - fmt.Println("ERROR: ", err) - } - } - - fmt.Println("\n#: DAGStore shards") - if err := dagstoreListShardsCmd.Action(cctx); err != nil { - fmt.Println("ERROR: ", err) - } - - fmt.Println("\n#: Pending Batch Deals") - if err := dealsPendingPublish.Action(cctx); err != nil { - fmt.Println("ERROR: ", err) - } - fmt.Println("\n#: Pending Batch Terminations") if err := sectorsTerminatePendingCmd.Action(cctx); err != nil { fmt.Println("ERROR: ", err) @@ -217,11 +151,6 @@ var infoAllCmd = &cli.Command{ fmt.Println("ERROR: ", err) } - fmt.Println("\n#: Sector Refs") - if err := sectorsRefsCmd.Action(cctx); err != nil { - fmt.Println("ERROR: ", err) - } - // Very Very Verbose info fmt.Println("\n#: Per Sector Info") diff --git a/cmd/lotus-miner/init.go b/cmd/lotus-miner/init.go index 9ab4e8b05..621cb078e 100644 --- a/cmd/lotus-miner/init.go +++ b/cmd/lotus-miner/init.go @@ -129,7 +129,6 @@ var initCmd = &cli.Command{ }, Subcommands: []*cli.Command{ restoreCmd, - serviceCmd, }, Action: func(cctx *cli.Context) error { log.Info("Initializing lotus miner") diff --git a/cmd/lotus-miner/init_service.go b/cmd/lotus-miner/init_service.go deleted file mode 100644 index 876313941..000000000 --- a/cmd/lotus-miner/init_service.go +++ /dev/null @@ -1,159 +0,0 @@ -package main - -import ( - "context" - "strings" - - "github.com/libp2p/go-libp2p/core/peer" - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/big" - - "github.com/filecoin-project/lotus/api" - lapi "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/client" - lcli "github.com/filecoin-project/lotus/cli" - cliutil "github.com/filecoin-project/lotus/cli/util" - "github.com/filecoin-project/lotus/node/config" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -const ( - MarketsService = "markets" -) - -var serviceCmd = &cli.Command{ - Name: "service", - Usage: "Initialize a lotus miner sub-service", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "config", - Usage: "config file (config.toml)", - Required: true, - }, - &cli.BoolFlag{ - Name: "nosync", - Usage: "don't check full-node sync status", - }, - &cli.StringSliceFlag{ - Name: "type", - Usage: "type of service to be enabled", - }, - &cli.StringFlag{ - Name: "api-sealer", - Usage: "sealer API info (lotus-miner auth api-info --perm=admin)", - }, - &cli.StringFlag{ - Name: "api-sector-index", - Usage: "sector Index API info (lotus-miner auth api-info --perm=admin)", - }, - }, - ArgsUsage: "[backupFile]", - Action: func(cctx *cli.Context) error { - ctx := lcli.ReqContext(cctx) - log.Info("Initializing lotus miner service") - - es := EnabledServices(cctx.StringSlice("type")) - - if len(es) == 0 { - return xerrors.Errorf("at least one module must be enabled") - } - - // we should remove this as soon as we have more service types and not just `markets` - if !es.Contains(MarketsService) { - return xerrors.Errorf("markets module must be enabled") - } - - if !cctx.IsSet("api-sealer") { - return xerrors.Errorf("--api-sealer is required without the sealer module enabled") - } - if !cctx.IsSet("api-sector-index") { - return xerrors.Errorf("--api-sector-index is required without the sector storage module enabled") - } - - repoPath := cctx.String(FlagMarketsRepo) - if repoPath == "" { - return xerrors.Errorf("please provide Lotus markets repo path via flag %s", FlagMarketsRepo) - } - - if err := restore(ctx, cctx, repoPath, &storiface.StorageConfig{}, func(cfg *config.StorageMiner) error { - cfg.Subsystems.EnableMarkets = es.Contains(MarketsService) - cfg.Subsystems.EnableMining = false - cfg.Subsystems.EnableSealing = false - cfg.Subsystems.EnableSectorStorage = false - - if !cfg.Subsystems.EnableSealing { - ai, err := checkApiInfo(ctx, cctx.String("api-sealer")) - if err != nil { - return xerrors.Errorf("checking sealer API: %w", err) - } - cfg.Subsystems.SealerApiInfo = ai - } - - if !cfg.Subsystems.EnableSectorStorage { - ai, err := checkApiInfo(ctx, cctx.String("api-sector-index")) - if err != nil { - return xerrors.Errorf("checking sector index API: %w", err) - } - cfg.Subsystems.SectorIndexApiInfo = ai - } - - return nil - }, func(api lapi.FullNode, maddr address.Address, peerid peer.ID, mi api.MinerInfo) error { - if es.Contains(MarketsService) { - log.Info("Configuring miner actor") - - if err := configureStorageMiner(ctx, api, maddr, peerid, big.Zero(), cctx.Uint64("confidence")); err != nil { - return err - } - } - - return nil - }); err != nil { - return err - } - - return nil - }, -} - -type EnabledServices []string - -func (es EnabledServices) Contains(name string) bool { - for _, s := range es { - if s == name { - return true - } - } - return false -} - -func checkApiInfo(ctx context.Context, ai string) (string, error) { - ai = strings.TrimPrefix(strings.TrimSpace(ai), "MINER_API_INFO=") - info := cliutil.ParseApiInfo(ai) - addr, err := info.DialArgs("v0") - if err != nil { - return "", xerrors.Errorf("could not get DialArgs: %w", err) - } - - log.Infof("Checking api version of %s", addr) - - api, closer, err := client.NewStorageMinerRPCV0(ctx, addr, info.AuthHeader()) - if err != nil { - return "", err - } - defer closer() - - v, err := api.Version(ctx) - if err != nil { - return "", xerrors.Errorf("checking version: %w", err) - } - - if !v.APIVersion.EqMajorMinor(lapi.MinerAPIVersion0) { - return "", xerrors.Errorf("remote service API version didn't match (expected %s, remote %s)", lapi.MinerAPIVersion0, v.APIVersion) - } - - return ai, nil -} diff --git a/cmd/lotus-miner/main.go b/cmd/lotus-miner/main.go index 1fc7abfa8..fd0ea62db 100644 --- a/cmd/lotus-miner/main.go +++ b/cmd/lotus-miner/main.go @@ -43,16 +43,10 @@ func main() { backupCmd, lcli.WithCategory("chain", actorCmd), lcli.WithCategory("chain", infoCmd), - lcli.WithCategory("market", setHidden(storageDealsCmd)), - lcli.WithCategory("market", setHidden(retrievalDealsCmd)), - lcli.WithCategory("market", setHidden(dataTransfersCmd)), - lcli.WithCategory("market", setHidden(dagstoreCmd)), - lcli.WithCategory("market", setHidden(indexProvCmd)), lcli.WithCategory("storage", sectorsCmd), lcli.WithCategory("storage", provingCmd), lcli.WithCategory("storage", storageCmd), lcli.WithCategory("storage", sealingCmd), - lcli.WithCategory("retrieval", setHidden(piecesCmd)), } jaeger := tracing.SetupJaegerTracing("lotus") @@ -101,7 +95,7 @@ func main() { app := &cli.App{ Name: "lotus-miner", Usage: "Filecoin decentralized storage network miner", - Version: build.UserVersion(), + Version: string(build.MinerUserVersion()), EnableBashCompletion: true, Flags: []cli.Flag{ &cli.StringFlag{ @@ -159,7 +153,7 @@ func main() { After: func(c *cli.Context) error { if r := recover(); r != nil { // Generate report in LOTUS_PATH and re-raise panic - build.GeneratePanicReport(c.String("panic-reports"), c.String(FlagMinerRepo), c.App.Name) + build.GenerateMinerPanicReport(c.String("panic-reports"), c.String(FlagMinerRepo), c.App.Name) panic(r) } return nil @@ -193,11 +187,6 @@ func getActorAddress(ctx context.Context, cctx *cli.Context) (maddr address.Addr return maddr, nil } -func setHidden(cmd *cli.Command) *cli.Command { - cmd.Hidden = true - return cmd -} - func LMActorOrEnvGetter(cctx *cli.Context) (address.Address, error) { return getActorAddress(cctx.Context, cctx) } diff --git a/cmd/lotus-miner/market.go b/cmd/lotus-miner/market.go deleted file mode 100644 index 29eb662a7..000000000 --- a/cmd/lotus-miner/market.go +++ /dev/null @@ -1,1039 +0,0 @@ -package main - -import ( - "bufio" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "sort" - "strconv" - "text/tabwriter" - "time" - - tm "github.com/buger/goterm" - "github.com/docker/go-units" - "github.com/ipfs/go-cid" - "github.com/ipfs/go-cidutil/cidenc" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/multiformats/go-multibase" - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - cborutil "github.com/filecoin-project/go-cbor-util" - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/types" - lcli "github.com/filecoin-project/lotus/cli" -) - -var CidBaseFlag = cli.StringFlag{ - Name: "cid-base", - Hidden: true, - Value: "base32", - Usage: "Multibase encoding used for version 1 CIDs in output.", - DefaultText: "base32", -} - -// GetCidEncoder returns an encoder using the `cid-base` flag if provided, or -// the default (Base32) encoder if not. -func GetCidEncoder(cctx *cli.Context) (cidenc.Encoder, error) { - val := cctx.String("cid-base") - - e := cidenc.Encoder{Base: multibase.MustNewEncoder(multibase.Base32)} - - if val != "" { - var err error - e.Base, err = multibase.EncoderByName(val) - if err != nil { - return e, err - } - } - - return e, nil -} - -var storageDealSelectionCmd = &cli.Command{ - Name: "selection", - Usage: "Configure acceptance criteria for storage deal proposals", - Subcommands: []*cli.Command{ - storageDealSelectionShowCmd, - storageDealSelectionResetCmd, - storageDealSelectionRejectCmd, - }, -} - -var storageDealSelectionShowCmd = &cli.Command{ - Name: "list", - Usage: "List storage deal proposal selection criteria", - Action: func(cctx *cli.Context) error { - smapi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - onlineOk, err := smapi.DealsConsiderOnlineStorageDeals(lcli.DaemonContext(cctx)) - if err != nil { - return err - } - - offlineOk, err := smapi.DealsConsiderOfflineStorageDeals(lcli.DaemonContext(cctx)) - if err != nil { - return err - } - - fmt.Printf("considering online storage deals: %t\n", onlineOk) - fmt.Printf("considering offline storage deals: %t\n", offlineOk) - - return nil - }, -} - -var storageDealSelectionResetCmd = &cli.Command{ - Name: "reset", - Usage: "Reset storage deal proposal selection criteria to default values", - Action: func(cctx *cli.Context) error { - smapi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - err = smapi.DealsSetConsiderOnlineStorageDeals(lcli.DaemonContext(cctx), true) - if err != nil { - return err - } - - err = smapi.DealsSetConsiderOfflineStorageDeals(lcli.DaemonContext(cctx), true) - if err != nil { - return err - } - - err = smapi.DealsSetConsiderVerifiedStorageDeals(lcli.DaemonContext(cctx), true) - if err != nil { - return err - } - - err = smapi.DealsSetConsiderUnverifiedStorageDeals(lcli.DaemonContext(cctx), true) - if err != nil { - return err - } - - return nil - }, -} - -var storageDealSelectionRejectCmd = &cli.Command{ - Name: "reject", - Usage: "Configure criteria which necessitate automatic rejection", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "online", - }, - &cli.BoolFlag{ - Name: "offline", - }, - &cli.BoolFlag{ - Name: "verified", - }, - &cli.BoolFlag{ - Name: "unverified", - }, - }, - Action: func(cctx *cli.Context) error { - smapi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - if cctx.Bool("online") { - err = smapi.DealsSetConsiderOnlineStorageDeals(lcli.DaemonContext(cctx), false) - if err != nil { - return err - } - } - - if cctx.Bool("offline") { - err = smapi.DealsSetConsiderOfflineStorageDeals(lcli.DaemonContext(cctx), false) - if err != nil { - return err - } - } - - if cctx.Bool("verified") { - err = smapi.DealsSetConsiderVerifiedStorageDeals(lcli.DaemonContext(cctx), false) - if err != nil { - return err - } - } - - if cctx.Bool("unverified") { - err = smapi.DealsSetConsiderUnverifiedStorageDeals(lcli.DaemonContext(cctx), false) - if err != nil { - return err - } - } - - return nil - }, -} - -var setAskCmd = &cli.Command{ - Name: "set-ask", - Usage: "Configure the miner's ask", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "price", - Usage: "Set the price of the ask for unverified deals (specified as FIL / GiB / Epoch) to `PRICE`.", - Required: true, - }, - &cli.StringFlag{ - Name: "verified-price", - Usage: "Set the price of the ask for verified deals (specified as FIL / GiB / Epoch) to `PRICE`", - Required: true, - }, - &cli.StringFlag{ - Name: "min-piece-size", - Usage: "Set minimum piece size (w/bit-padding, in bytes) in ask to `SIZE`", - DefaultText: "256B", - Value: "256B", - }, - &cli.StringFlag{ - Name: "max-piece-size", - Usage: "Set maximum piece size (w/bit-padding, in bytes) in ask to `SIZE`", - DefaultText: "miner sector size", - Value: "0", - }, - }, - Action: func(cctx *cli.Context) error { - ctx := lcli.DaemonContext(cctx) - - minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) - if err != nil { - return err - } - defer closer() - - marketsApi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - pri, err := types.ParseFIL(cctx.String("price")) - if err != nil { - return err - } - - vpri, err := types.ParseFIL(cctx.String("verified-price")) - if err != nil { - return err - } - - dur, err := time.ParseDuration("720h0m0s") - if err != nil { - return xerrors.Errorf("cannot parse duration: %w", err) - } - - qty := dur.Seconds() / float64(build.BlockDelaySecs) - - min, err := units.RAMInBytes(cctx.String("min-piece-size")) - if err != nil { - return xerrors.Errorf("cannot parse min-piece-size to quantity of bytes: %w", err) - } - - if min < 256 { - return xerrors.New("minimum piece size (w/bit-padding) is 256B") - } - - max, err := units.RAMInBytes(cctx.String("max-piece-size")) - if err != nil { - return xerrors.Errorf("cannot parse max-piece-size to quantity of bytes: %w", err) - } - - maddr, err := minerApi.ActorAddress(ctx) - if err != nil { - return err - } - - ssize, err := minerApi.ActorSectorSize(ctx, maddr) - if err != nil { - return err - } - - smax := int64(ssize) - - if max == 0 { - max = smax - } - - if max > smax { - return xerrors.Errorf("max piece size (w/bit-padding) %s cannot exceed miner sector size %s", types.SizeStr(types.NewInt(uint64(max))), types.SizeStr(types.NewInt(uint64(smax)))) - } - - return marketsApi.MarketSetAsk(ctx, types.BigInt(pri), types.BigInt(vpri), abi.ChainEpoch(qty), abi.PaddedPieceSize(min), abi.PaddedPieceSize(max)) - }, -} - -var getAskCmd = &cli.Command{ - Name: "get-ask", - Usage: "Print the miner's ask", - Flags: []cli.Flag{}, - Action: func(cctx *cli.Context) error { - ctx := lcli.DaemonContext(cctx) - - fnapi, closer, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - - smapi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - sask, err := smapi.MarketGetAsk(ctx) - if err != nil { - return err - } - - var ask *storagemarket.StorageAsk - if sask != nil && sask.Ask != nil { - ask = sask.Ask - } - - w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) - fmt.Fprintf(w, "Price per GiB/Epoch\tVerified\tMin. Piece Size (padded)\tMax. Piece Size (padded)\tExpiry (Epoch)\tExpiry (Appx. Rem. Time)\tSeq. No.\n") - if ask == nil { - fmt.Fprintf(w, "\n") - - return w.Flush() - } - - head, err := fnapi.ChainHead(ctx) - if err != nil { - return err - } - - dlt := ask.Expiry - head.Height() - rem := "" - if dlt > 0 { - rem = (time.Second * time.Duration(int64(dlt)*int64(build.BlockDelaySecs))).String() - } - - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\t%s\t%d\n", types.FIL(ask.Price), types.FIL(ask.VerifiedPrice), types.SizeStr(types.NewInt(uint64(ask.MinPieceSize))), types.SizeStr(types.NewInt(uint64(ask.MaxPieceSize))), ask.Expiry, rem, ask.SeqNo) - - return w.Flush() - }, -} - -var storageDealsCmd = &cli.Command{ - Name: "storage-deals", - Usage: "Manage storage deals and related configuration", - Subcommands: []*cli.Command{ - dealsImportDataCmd, - dealsListCmd, - storageDealSelectionCmd, - setAskCmd, - getAskCmd, - setBlocklistCmd, - getBlocklistCmd, - resetBlocklistCmd, - setSealDurationCmd, - dealsPendingPublish, - dealsRetryPublish, - }, -} - -var dealsImportDataCmd = &cli.Command{ - Name: "import-data", - Usage: "Manually import data for a deal", - ArgsUsage: " ", - Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - ctx := lcli.DaemonContext(cctx) - - if cctx.NArg() != 2 { - return lcli.IncorrectNumArgs(cctx) - } - - propCid, err := cid.Decode(cctx.Args().Get(0)) - if err != nil { - return err - } - - fpath := cctx.Args().Get(1) - - return api.DealsImportData(ctx, propCid, fpath) - - }, -} - -var dealsListCmd = &cli.Command{ - Name: "list", - Usage: "List all deals for this miner", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "format", - Usage: "output format of data, supported: table, json", - Value: "table", - }, - &cli.BoolFlag{ - Name: "verbose", - Aliases: []string{"v"}, - }, - &cli.BoolFlag{ - Name: "watch", - Usage: "watch deal updates in real-time, rather than a one time list", - }, - }, - Action: func(cctx *cli.Context) error { - switch cctx.String("format") { - case "table": - return listDealsWithTable(cctx) - case "json": - return listDealsWithJSON(cctx) - } - - return fmt.Errorf("unknown format: %s; use `table` or `json`", cctx.String("format")) - }, -} - -func listDealsWithTable(cctx *cli.Context) error { - api, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - ctx := lcli.DaemonContext(cctx) - - deals, err := api.MarketListIncompleteDeals(ctx) - if err != nil { - return err - } - - verbose := cctx.Bool("verbose") - watch := cctx.Bool("watch") - - if watch { - updates, err := api.MarketGetDealUpdates(ctx) - if err != nil { - return err - } - - for { - tm.Clear() - tm.MoveCursor(1, 1) - - err = outputStorageDealsTable(tm.Output, deals, verbose) - if err != nil { - return err - } - - tm.Flush() - - select { - case <-ctx.Done(): - return nil - case updated := <-updates: - var found bool - for i, existing := range deals { - if existing.ProposalCid.Equals(updated.ProposalCid) { - deals[i] = updated - found = true - break - } - } - if !found { - deals = append(deals, updated) - } - } - } - } - - return outputStorageDealsTable(os.Stdout, deals, verbose) -} - -func outputStorageDealsTable(out io.Writer, deals []storagemarket.MinerDeal, verbose bool) error { - sort.Slice(deals, func(i, j int) bool { - return deals[i].CreationTime.Time().Before(deals[j].CreationTime.Time()) - }) - - w := tabwriter.NewWriter(out, 2, 4, 2, ' ', 0) - - if verbose { - _, _ = fmt.Fprintf(w, "Creation\tVerified\tProposalCid\tDealId\tState\tClient\tSize\tPrice\tDuration\tTransferChannelID\tMessage\n") - } else { - _, _ = fmt.Fprintf(w, "ProposalCid\tDealId\tState\tClient\tSize\tPrice\tDuration\n") - } - - for _, deal := range deals { - propcid := deal.ProposalCid.String() - if !verbose { - propcid = "..." + propcid[len(propcid)-8:] - } - - fil := types.FIL(types.BigMul(deal.Proposal.StoragePricePerEpoch, types.NewInt(uint64(deal.Proposal.Duration())))) - - if verbose { - _, _ = fmt.Fprintf(w, "%s\t%t\t", deal.CreationTime.Time().Format(time.Stamp), deal.Proposal.VerifiedDeal) - } - - _, _ = fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\t%s", propcid, deal.DealID, storagemarket.DealStates[deal.State], deal.Proposal.Client, units.BytesSize(float64(deal.Proposal.PieceSize)), fil, deal.Proposal.Duration()) - if verbose { - tchid := "" - if deal.TransferChannelId != nil { - tchid = deal.TransferChannelId.String() - } - _, _ = fmt.Fprintf(w, "\t%s", tchid) - _, _ = fmt.Fprintf(w, "\t%s", deal.Message) - } - - _, _ = fmt.Fprintln(w) - } - - return w.Flush() -} - -var getBlocklistCmd = &cli.Command{ - Name: "get-blocklist", - Usage: "List the contents of the miner's piece CID blocklist", - Flags: []cli.Flag{ - &CidBaseFlag, - }, - Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - blocklist, err := api.DealsPieceCidBlocklist(lcli.DaemonContext(cctx)) - if err != nil { - return err - } - - encoder, err := GetCidEncoder(cctx) - if err != nil { - return err - } - - for idx := range blocklist { - fmt.Println(encoder.Encode(blocklist[idx])) - } - - return nil - }, -} - -var setBlocklistCmd = &cli.Command{ - Name: "set-blocklist", - Usage: "Set the miner's list of blocklisted piece CIDs", - ArgsUsage: "[ (optional, will read from stdin if omitted)]", - Flags: []cli.Flag{}, - Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - scanner := bufio.NewScanner(os.Stdin) - if cctx.Args().Present() && cctx.Args().First() != "-" { - absPath, err := filepath.Abs(cctx.Args().First()) - if err != nil { - return err - } - - file, err := os.Open(absPath) - if err != nil { - log.Fatal(err) - } - defer file.Close() //nolint:errcheck - - scanner = bufio.NewScanner(file) - } - - var blocklist []cid.Cid - for scanner.Scan() { - decoded, err := cid.Decode(scanner.Text()) - if err != nil { - return err - } - - blocklist = append(blocklist, decoded) - } - - err = scanner.Err() - if err != nil { - return err - } - - return api.DealsSetPieceCidBlocklist(lcli.DaemonContext(cctx), blocklist) - }, -} - -var resetBlocklistCmd = &cli.Command{ - Name: "reset-blocklist", - Usage: "Remove all entries from the miner's piece CID blocklist", - Flags: []cli.Flag{}, - Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - return api.DealsSetPieceCidBlocklist(lcli.DaemonContext(cctx), []cid.Cid{}) - }, -} - -var setSealDurationCmd = &cli.Command{ - Name: "set-seal-duration", - 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.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - if cctx.NArg() != 1 { - return lcli.IncorrectNumArgs(cctx) - } - - hs, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) - if err != nil { - return xerrors.Errorf("could not parse duration: %w", err) - } - - delay := hs * uint64(time.Minute) - - return nodeApi.SectorSetExpectedSealDuration(ctx, time.Duration(delay)) - }, -} - -var dataTransfersCmd = &cli.Command{ - Name: "data-transfers", - Usage: "Manage data transfers", - Subcommands: []*cli.Command{ - transfersListCmd, - marketRestartTransfer, - marketCancelTransfer, - transfersDiagnosticsCmd, - }, -} - -var marketRestartTransfer = &cli.Command{ - Name: "restart", - Usage: "Force restart a stalled data transfer", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "peerid", - Usage: "narrow to transfer with specific peer", - }, - &cli.BoolFlag{ - Name: "initiator", - Usage: "specify only transfers where peer is/is not initiator", - Value: false, - }, - }, - Action: func(cctx *cli.Context) error { - if !cctx.Args().Present() { - return cli.ShowCommandHelp(cctx, cctx.Command.Name) - } - nodeApi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - - transferUint, err := strconv.ParseUint(cctx.Args().First(), 10, 64) - if err != nil { - return fmt.Errorf("Error reading transfer ID: %w", err) - } - transferID := datatransfer.TransferID(transferUint) - initiator := cctx.Bool("initiator") - var other peer.ID - if pidstr := cctx.String("peerid"); pidstr != "" { - p, err := peer.Decode(pidstr) - if err != nil { - return err - } - other = p - } else { - channels, err := nodeApi.MarketListDataTransfers(ctx) - if err != nil { - return err - } - found := false - for _, channel := range channels { - if channel.IsInitiator == initiator && channel.TransferID == transferID { - other = channel.OtherPeer - found = true - break - } - } - if !found { - return errors.New("unable to find matching data transfer") - } - } - - return nodeApi.MarketRestartDataTransfer(ctx, transferID, other, initiator) - }, -} - -var marketCancelTransfer = &cli.Command{ - Name: "cancel", - Usage: "Force cancel a data transfer", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "peerid", - Usage: "narrow to transfer with specific peer", - }, - &cli.BoolFlag{ - Name: "initiator", - Usage: "specify only transfers where peer is/is not initiator", - Value: false, - }, - &cli.DurationFlag{ - Name: "cancel-timeout", - Usage: "time to wait for cancel to be sent to client", - Value: 5 * time.Second, - }, - }, - Action: func(cctx *cli.Context) error { - if !cctx.Args().Present() { - return cli.ShowCommandHelp(cctx, cctx.Command.Name) - } - nodeApi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - - transferUint, err := strconv.ParseUint(cctx.Args().First(), 10, 64) - if err != nil { - return fmt.Errorf("Error reading transfer ID: %w", err) - } - transferID := datatransfer.TransferID(transferUint) - initiator := cctx.Bool("initiator") - var other peer.ID - if pidstr := cctx.String("peerid"); pidstr != "" { - p, err := peer.Decode(pidstr) - if err != nil { - return err - } - other = p - } else { - channels, err := nodeApi.MarketListDataTransfers(ctx) - if err != nil { - return err - } - found := false - for _, channel := range channels { - if channel.IsInitiator == initiator && channel.TransferID == transferID { - other = channel.OtherPeer - found = true - break - } - } - if !found { - return errors.New("unable to find matching data transfer") - } - } - - timeoutCtx, cancel := context.WithTimeout(ctx, cctx.Duration("cancel-timeout")) - defer cancel() - return nodeApi.MarketCancelDataTransfer(timeoutCtx, transferID, other, initiator) - }, -} - -var transfersListCmd = &cli.Command{ - Name: "list", - Usage: "List ongoing data transfers for this miner", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "verbose", - Aliases: []string{"v"}, - Usage: "print verbose transfer details", - }, - &cli.BoolFlag{ - Name: "completed", - Usage: "show completed data transfers", - }, - &cli.BoolFlag{ - Name: "watch", - Usage: "watch deal updates in real-time, rather than a one time list", - }, - &cli.BoolFlag{ - Name: "show-failed", - Usage: "show failed/cancelled transfers", - }, - }, - Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - - channels, err := api.MarketListDataTransfers(ctx) - if err != nil { - return err - } - - verbose := cctx.Bool("verbose") - completed := cctx.Bool("completed") - watch := cctx.Bool("watch") - showFailed := cctx.Bool("show-failed") - if watch { - channelUpdates, err := api.MarketDataTransferUpdates(ctx) - if err != nil { - return err - } - - for { - tm.Clear() // Clear current screen - - tm.MoveCursor(1, 1) - - lcli.OutputDataTransferChannels(tm.Screen, channels, verbose, completed, showFailed) - - tm.Flush() - - select { - case <-ctx.Done(): - return nil - case channelUpdate := <-channelUpdates: - var found bool - for i, existing := range channels { - if existing.TransferID == channelUpdate.TransferID && - existing.OtherPeer == channelUpdate.OtherPeer && - existing.IsSender == channelUpdate.IsSender && - existing.IsInitiator == channelUpdate.IsInitiator { - channels[i] = channelUpdate - found = true - break - } - } - if !found { - channels = append(channels, channelUpdate) - } - } - } - } - lcli.OutputDataTransferChannels(os.Stdout, channels, verbose, completed, showFailed) - return nil - }, -} - -var transfersDiagnosticsCmd = &cli.Command{ - Name: "diagnostics", - Usage: "Get detailed diagnostics on active transfers with a specific peer", - Flags: []cli.Flag{}, - Action: func(cctx *cli.Context) error { - if !cctx.Args().Present() { - return cli.ShowCommandHelp(cctx, cctx.Command.Name) - } - api, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - - targetPeer, err := peer.Decode(cctx.Args().First()) - if err != nil { - return err - } - diagnostics, err := api.MarketDataTransferDiagnostics(ctx, targetPeer) - if err != nil { - return err - } - out, err := json.MarshalIndent(diagnostics, "", "\t") - if err != nil { - return err - } - fmt.Println(string(out)) - return nil - }, -} - -var dealsPendingPublish = &cli.Command{ - Name: "pending-publish", - Usage: "list deals waiting in publish queue", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "publish-now", - Usage: "send a publish message now", - }, - }, - Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - - if cctx.Bool("publish-now") { - if err := api.MarketPublishPendingDeals(ctx); err != nil { - return xerrors.Errorf("publishing deals: %w", err) - } - fmt.Println("triggered deal publishing") - return nil - } - - pending, err := api.MarketPendingDeals(ctx) - if err != nil { - return xerrors.Errorf("getting pending deals: %w", err) - } - - if len(pending.Deals) > 0 { - endsIn := pending.PublishPeriodStart.Add(pending.PublishPeriod).Sub(time.Now()) - w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) - _, _ = fmt.Fprintf(w, "Publish period: %s (ends in %s)\n", pending.PublishPeriod, endsIn.Round(time.Second)) - _, _ = fmt.Fprintf(w, "First deal queued at: %s\n", pending.PublishPeriodStart) - _, _ = fmt.Fprintf(w, "Deals will be published at: %s\n", pending.PublishPeriodStart.Add(pending.PublishPeriod)) - _, _ = fmt.Fprintf(w, "%d deals queued to be published:\n", len(pending.Deals)) - _, _ = fmt.Fprintf(w, "ProposalCID\tClient\tSize\n") - for _, deal := range pending.Deals { - proposalNd, err := cborutil.AsIpld(&deal) // nolint - if err != nil { - return err - } - - _, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", proposalNd.Cid(), deal.Proposal.Client, units.BytesSize(float64(deal.Proposal.PieceSize))) - } - return w.Flush() - } - - fmt.Println("No deals queued to be published") - return nil - }, -} - -var dealsRetryPublish = &cli.Command{ - Name: "retry-publish", - Usage: "retry publishing a deal", - ArgsUsage: "", - Action: func(cctx *cli.Context) error { - if !cctx.Args().Present() { - return cli.ShowCommandHelp(cctx, cctx.Command.Name) - } - api, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - - propcid := cctx.Args().First() - fmt.Printf("retrying deal with proposal-cid: %s\n", propcid) - - cid, err := cid.Decode(propcid) - if err != nil { - return err - } - if err := api.MarketRetryPublishDeal(ctx, cid); err != nil { - return xerrors.Errorf("retrying publishing deal: %w", err) - } - fmt.Println("retried to publish deal") - return nil - }, -} - -func listDealsWithJSON(cctx *cli.Context) error { - node, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - ctx := lcli.DaemonContext(cctx) - - deals, err := node.MarketListIncompleteDeals(ctx) - if err != nil { - return err - } - - channels, err := node.MarketListDataTransfers(ctx) - if err != nil { - return err - } - - sort.Slice(deals, func(i, j int) bool { - return deals[i].CreationTime.Time().Before(deals[j].CreationTime.Time()) - }) - - channelsByTransferID := map[datatransfer.TransferID]api.DataTransferChannel{} - for _, c := range channels { - channelsByTransferID[c.TransferID] = c - } - - w := json.NewEncoder(os.Stdout) - - for _, deal := range deals { - val := struct { - DateTime string `json:"datetime"` - VerifiedDeal bool `json:"verified-deal"` - ProposalCID string `json:"proposal-cid"` - DealID abi.DealID `json:"deal-id"` - DealStatus string `json:"deal-status"` - Client string `json:"client"` - PieceSize string `json:"piece-size"` - Price types.FIL `json:"price"` - DurationEpochs abi.ChainEpoch `json:"duration-epochs"` - TransferID *datatransfer.TransferID `json:"transfer-id,omitempty"` - TransferStatus string `json:"transfer-status,omitempty"` - TransferredData string `json:"transferred-data,omitempty"` - }{} - - val.DateTime = deal.CreationTime.Time().Format(time.RFC3339) - val.VerifiedDeal = deal.Proposal.VerifiedDeal - val.ProposalCID = deal.ProposalCid.String() - val.DealID = deal.DealID - val.DealStatus = storagemarket.DealStates[deal.State] - val.Client = deal.Proposal.Client.String() - val.PieceSize = units.BytesSize(float64(deal.Proposal.PieceSize)) - val.Price = types.FIL(types.BigMul(deal.Proposal.StoragePricePerEpoch, types.NewInt(uint64(deal.Proposal.Duration())))) - val.DurationEpochs = deal.Proposal.Duration() - - if deal.TransferChannelId != nil { - if c, ok := channelsByTransferID[deal.TransferChannelId.ID]; ok { - val.TransferID = &c.TransferID - val.TransferStatus = datatransfer.Statuses[c.Status] - val.TransferredData = units.BytesSize(float64(c.Transferred)) - } - } - - err := w.Encode(val) - if err != nil { - return err - } - } - - return nil -} diff --git a/cmd/lotus-miner/pieces.go b/cmd/lotus-miner/pieces.go deleted file mode 100644 index a64142237..000000000 --- a/cmd/lotus-miner/pieces.go +++ /dev/null @@ -1,193 +0,0 @@ -package main - -import ( - "fmt" - "os" - "text/tabwriter" - - "github.com/ipfs/go-cid" - "github.com/urfave/cli/v2" - - lcli "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/lib/tablewriter" -) - -var piecesCmd = &cli.Command{ - Name: "pieces", - Usage: "interact with the piecestore", - Description: "The piecestore is a database that tracks and manages data that is made available to the retrieval market", - Subcommands: []*cli.Command{ - piecesListPiecesCmd, - piecesListCidInfosCmd, - piecesInfoCmd, - piecesCidInfoCmd, - }, -} - -var piecesListPiecesCmd = &cli.Command{ - Name: "list-pieces", - Usage: "list registered pieces", - Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - - pieceCids, err := nodeApi.PiecesListPieces(ctx) - if err != nil { - return err - } - - for _, pc := range pieceCids { - fmt.Println(pc) - } - return nil - }, -} - -var piecesListCidInfosCmd = &cli.Command{ - Name: "list-cids", - Usage: "list registered payload CIDs", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "verbose", - Aliases: []string{"v"}, - }, - }, - Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - - cids, err := nodeApi.PiecesListCidInfos(ctx) - if err != nil { - return err - } - - w := tablewriter.New(tablewriter.Col("CID"), - tablewriter.Col("Piece"), - tablewriter.Col("BlockOffset"), - tablewriter.Col("BlockLen"), - tablewriter.Col("Deal"), - tablewriter.Col("Sector"), - tablewriter.Col("DealOffset"), - tablewriter.Col("DealLen"), - ) - - for _, c := range cids { - if !cctx.Bool("verbose") { - fmt.Println(c) - continue - } - - ci, err := nodeApi.PiecesGetCIDInfo(ctx, c) - if err != nil { - fmt.Printf("Error getting CID info: %s\n", err) - continue - } - - for _, location := range ci.PieceBlockLocations { - pi, err := nodeApi.PiecesGetPieceInfo(ctx, location.PieceCID) - if err != nil { - fmt.Printf("Error getting piece info: %s\n", err) - continue - } - - for _, deal := range pi.Deals { - w.Write(map[string]interface{}{ - "CID": c, - "Piece": location.PieceCID, - "BlockOffset": location.RelOffset, - "BlockLen": location.BlockSize, - "Deal": deal.DealID, - "Sector": deal.SectorID, - "DealOffset": deal.Offset, - "DealLen": deal.Length, - }) - } - } - } - - if cctx.Bool("verbose") { - return w.Flush(os.Stdout) - } - - return nil - }, -} - -var piecesInfoCmd = &cli.Command{ - Name: "piece-info", - Usage: "get registered information for a given piece CID", - Action: func(cctx *cli.Context) error { - if !cctx.Args().Present() { - return lcli.ShowHelp(cctx, fmt.Errorf("must specify piece cid")) - } - - nodeApi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - - c, err := cid.Decode(cctx.Args().First()) - if err != nil { - return err - } - - pi, err := nodeApi.PiecesGetPieceInfo(ctx, c) - if err != nil { - return err - } - - fmt.Println("Piece: ", pi.PieceCID) - w := tabwriter.NewWriter(os.Stdout, 4, 4, 2, ' ', 0) - fmt.Fprintln(w, "Deals:\nDealID\tSectorID\tLength\tOffset") - for _, d := range pi.Deals { - fmt.Fprintf(w, "%d\t%d\t%d\t%d\n", d.DealID, d.SectorID, d.Length, d.Offset) - } - return w.Flush() - }, -} - -var piecesCidInfoCmd = &cli.Command{ - Name: "cid-info", - Usage: "get registered information for a given payload CID", - Action: func(cctx *cli.Context) error { - if !cctx.Args().Present() { - return lcli.ShowHelp(cctx, fmt.Errorf("must specify payload cid")) - } - - nodeApi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - - c, err := cid.Decode(cctx.Args().First()) - if err != nil { - return err - } - - ci, err := nodeApi.PiecesGetCIDInfo(ctx, c) - if err != nil { - return err - } - - fmt.Println("Info for: ", ci.CID) - - w := tabwriter.NewWriter(os.Stdout, 4, 4, 2, ' ', 0) - fmt.Fprintf(w, "PieceCid\tOffset\tSize\n") - for _, loc := range ci.PieceBlockLocations { - fmt.Fprintf(w, "%s\t%d\t%d\n", loc.PieceCID, loc.RelOffset, loc.BlockSize) - } - return w.Flush() - }, -} diff --git a/cmd/lotus-miner/retrieval-deals.go b/cmd/lotus-miner/retrieval-deals.go deleted file mode 100644 index 42b0fa1f6..000000000 --- a/cmd/lotus-miner/retrieval-deals.go +++ /dev/null @@ -1,231 +0,0 @@ -package main - -import ( - "fmt" - "os" - "text/tabwriter" - - "github.com/docker/go-units" - "github.com/urfave/cli/v2" - - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/chain/types" - lcli "github.com/filecoin-project/lotus/cli" -) - -var retrievalDealsCmd = &cli.Command{ - Name: "retrieval-deals", - Usage: "Manage retrieval deals and related configuration", - Subcommands: []*cli.Command{ - retrievalDealSelectionCmd, - retrievalSetAskCmd, - retrievalGetAskCmd, - }, -} - -var retrievalDealSelectionCmd = &cli.Command{ - Name: "selection", - Usage: "Configure acceptance criteria for retrieval deal proposals", - Subcommands: []*cli.Command{ - retrievalDealSelectionShowCmd, - retrievalDealSelectionResetCmd, - retrievalDealSelectionRejectCmd, - }, -} - -var retrievalDealSelectionShowCmd = &cli.Command{ - Name: "list", - Usage: "List retrieval deal proposal selection criteria", - Action: func(cctx *cli.Context) error { - smapi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - onlineOk, err := smapi.DealsConsiderOnlineRetrievalDeals(lcli.DaemonContext(cctx)) - if err != nil { - return err - } - - offlineOk, err := smapi.DealsConsiderOfflineRetrievalDeals(lcli.DaemonContext(cctx)) - if err != nil { - return err - } - - fmt.Printf("considering online retrieval deals: %t\n", onlineOk) - fmt.Printf("considering offline retrieval deals: %t\n", offlineOk) - - return nil - }, -} - -var retrievalDealSelectionResetCmd = &cli.Command{ - Name: "reset", - Usage: "Reset retrieval deal proposal selection criteria to default values", - Action: func(cctx *cli.Context) error { - smapi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - err = smapi.DealsSetConsiderOnlineRetrievalDeals(lcli.DaemonContext(cctx), true) - if err != nil { - return err - } - - err = smapi.DealsSetConsiderOfflineRetrievalDeals(lcli.DaemonContext(cctx), true) - if err != nil { - return err - } - - return nil - }, -} - -var retrievalDealSelectionRejectCmd = &cli.Command{ - Name: "reject", - Usage: "Configure criteria which necessitate automatic rejection", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "online", - }, - &cli.BoolFlag{ - Name: "offline", - }, - }, - Action: func(cctx *cli.Context) error { - smapi, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - if cctx.Bool("online") { - err = smapi.DealsSetConsiderOnlineRetrievalDeals(lcli.DaemonContext(cctx), false) - if err != nil { - return err - } - } - - if cctx.Bool("offline") { - err = smapi.DealsSetConsiderOfflineRetrievalDeals(lcli.DaemonContext(cctx), false) - if err != nil { - return err - } - } - - return nil - }, -} - -var retrievalSetAskCmd = &cli.Command{ - Name: "set-ask", - Usage: "Configure the provider's retrieval ask", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "price", - Usage: "Set the price of the ask for retrievals (FIL/GiB)", - }, - &cli.StringFlag{ - Name: "unseal-price", - Usage: "Set the price to unseal", - }, - &cli.StringFlag{ - Name: "payment-interval", - Usage: "Set the payment interval (in bytes) for retrieval", - DefaultText: "1MiB", - }, - &cli.StringFlag{ - Name: "payment-interval-increase", - Usage: "Set the payment interval increase (in bytes) for retrieval", - DefaultText: "1MiB", - }, - }, - Action: func(cctx *cli.Context) error { - ctx := lcli.DaemonContext(cctx) - - api, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - ask, err := api.MarketGetRetrievalAsk(ctx) - if err != nil { - return err - } - - if cctx.IsSet("price") { - v, err := types.ParseFIL(cctx.String("price")) - if err != nil { - return err - } - ask.PricePerByte = types.BigDiv(types.BigInt(v), types.NewInt(1<<30)) - } - - if cctx.IsSet("unseal-price") { - v, err := types.ParseFIL(cctx.String("unseal-price")) - if err != nil { - return err - } - ask.UnsealPrice = abi.TokenAmount(v) - } - - if cctx.IsSet("payment-interval") { - v, err := units.RAMInBytes(cctx.String("payment-interval")) - if err != nil { - return err - } - ask.PaymentInterval = uint64(v) - } - - if cctx.IsSet("payment-interval-increase") { - v, err := units.RAMInBytes(cctx.String("payment-interval-increase")) - if err != nil { - return err - } - ask.PaymentIntervalIncrease = uint64(v) - } - - return api.MarketSetRetrievalAsk(ctx, ask) - }, -} - -var retrievalGetAskCmd = &cli.Command{ - Name: "get-ask", - Usage: "Get the provider's current retrieval ask configured by the provider in the ask-store using the set-ask CLI command", - Flags: []cli.Flag{}, - Action: func(cctx *cli.Context) error { - ctx := lcli.DaemonContext(cctx) - - api, closer, err := lcli.GetMarketsAPI(cctx) - if err != nil { - return err - } - defer closer() - - ask, err := api.MarketGetRetrievalAsk(ctx) - if err != nil { - return err - } - - w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) - _, _ = fmt.Fprintf(w, "Price per Byte\tUnseal Price\tPayment Interval\tPayment Interval Increase\n") - if ask == nil { - _, _ = fmt.Fprintf(w, "\n") - return w.Flush() - } - - _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", - types.FIL(ask.PricePerByte), - types.FIL(ask.UnsealPrice), - units.BytesSize(float64(ask.PaymentInterval)), - units.BytesSize(float64(ask.PaymentIntervalIncrease)), - ) - return w.Flush() - - }, -} diff --git a/cmd/lotus-miner/run.go b/cmd/lotus-miner/run.go index 93dfea2fc..e09968165 100644 --- a/cmd/lotus-miner/run.go +++ b/cmd/lotus-miner/run.go @@ -20,7 +20,6 @@ import ( "github.com/filecoin-project/lotus/lib/ulimit" "github.com/filecoin-project/lotus/metrics" "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/config" "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/node/repo" ) @@ -57,7 +56,7 @@ var runCmd = &cli.Command{ } ctx, _ := tag.New(lcli.DaemonContext(cctx), - tag.Insert(metrics.Version, build.BuildVersion), + tag.Insert(metrics.Version, build.MinerBuildVersion), tag.Insert(metrics.Commit, build.CurrentCommit), tag.Insert(metrics.NodeType, "miner"), ) @@ -121,16 +120,6 @@ var runCmd = &cli.Command{ if err != nil { return err } - c, err := lr.Config() - if err != nil { - return err - } - cfg, ok := c.(*config.StorageMiner) - if !ok { - return xerrors.Errorf("invalid config for repo, got: %T", c) - } - - bootstrapLibP2P := cfg.Subsystems.EnableMarkets err = lr.Close() if err != nil { @@ -141,7 +130,7 @@ var runCmd = &cli.Command{ var minerapi api.StorageMiner stop, err := node.New(ctx, - node.StorageMiner(&minerapi, cfg.Subsystems), + node.StorageMiner(&minerapi), node.Override(new(dtypes.ShutdownChan), shutdownChan), node.Base(), node.Repo(r), @@ -161,20 +150,6 @@ var runCmd = &cli.Command{ return xerrors.Errorf("getting API endpoint: %w", err) } - if bootstrapLibP2P { - log.Infof("Bootstrapping libp2p network with full node") - - // Bootstrap with full node - remoteAddrs, err := nodeApi.NetAddrsListen(ctx) - if err != nil { - return xerrors.Errorf("getting full node libp2p address: %w", err) - } - - if err := minerapi.NetConnect(ctx, remoteAddrs); err != nil { - return xerrors.Errorf("connecting to full node (libp2p): %w", err) - } - } - log.Infof("Remote version %s", v) // Instantiate the miner node handler. diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index cf32f4248..c3217a1a7 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -588,7 +588,7 @@ var sectorsRefsCmd = &cli.Command{ Name: "refs", Usage: "List References to sectors", Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetMarketsAPI(cctx) + nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } diff --git a/cmd/lotus-pcr/main.go b/cmd/lotus-pcr/main.go index 199810e03..a4a246f33 100644 --- a/cmd/lotus-pcr/main.go +++ b/cmd/lotus-pcr/main.go @@ -70,7 +70,7 @@ func main() { A single message will be produced per miner totaling their refund for all PreCommitSector messages in a tipset. `, - Version: build.UserVersion(), + Version: string(build.NodeUserVersion()), Flags: []cli.Flag{ &cli.StringFlag{ Name: "lotus-path", diff --git a/cmd/lotus-seed/main.go b/cmd/lotus-seed/main.go index 9deae560e..dd70cce21 100644 --- a/cmd/lotus-seed/main.go +++ b/cmd/lotus-seed/main.go @@ -38,7 +38,7 @@ func main() { app := &cli.App{ Name: "lotus-seed", Usage: "Seal sectors for genesis miner", - Version: build.UserVersion(), + Version: string(build.NodeUserVersion()), Flags: []cli.Flag{ &cli.StringFlag{ Name: "sector-dir", diff --git a/cmd/lotus-shed/actor.go b/cmd/lotus-shed/actor.go index 2acf89076..c7eb949c2 100644 --- a/cmd/lotus-shed/actor.go +++ b/cmd/lotus-shed/actor.go @@ -719,12 +719,12 @@ var actorControlSet = &cli.Command{ return err } - fmt.Fprintln(cctx.App.Writer, hex.EncodeToString(msgBytes)) + _, _ = fmt.Fprintln(cctx.App.Writer, hex.EncodeToString(msgBytes)) return nil } if !cctx.Bool("really-do-it") { - fmt.Fprintln(cctx.App.Writer, "Pass --really-do-it to actually execute this action") + _, _ = fmt.Fprintln(cctx.App.Writer, "Pass --really-do-it to actually execute this action") return nil } @@ -760,7 +760,7 @@ var actorProposeChangeWorker = &cli.Command{ } if !cctx.Bool("really-do-it") { - fmt.Fprintln(cctx.App.Writer, "Pass --really-do-it to actually execute this action") + _, _ = fmt.Fprintln(cctx.App.Writer, "Pass --really-do-it to actually execute this action") return nil } @@ -840,7 +840,7 @@ var actorProposeChangeWorker = &cli.Command{ return xerrors.Errorf("mpool push: %w", err) } - fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", smsg.Cid()) + _, _ = fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", smsg.Cid()) // wait for it to get mined into a block wait, err := nodeAPI.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence) @@ -850,7 +850,7 @@ var actorProposeChangeWorker = &cli.Command{ // check it executed successfully if wait.Receipt.ExitCode.IsError() { - fmt.Fprintln(cctx.App.Writer, "Propose worker change failed!") + _, _ = fmt.Fprintln(cctx.App.Writer, "Propose worker change failed!") return err } @@ -862,8 +862,8 @@ var actorProposeChangeWorker = &cli.Command{ return fmt.Errorf("Proposed worker address change not reflected on chain: expected '%s', found '%s'", na, mi.NewWorker) } - fmt.Fprintf(cctx.App.Writer, "Worker key change to %s successfully proposed.\n", na) - fmt.Fprintf(cctx.App.Writer, "Call 'confirm-change-worker' at or after height %d to complete.\n", mi.WorkerChangeEpoch) + _, _ = fmt.Fprintf(cctx.App.Writer, "Worker key change to %s successfully proposed.\n", na) + _, _ = fmt.Fprintf(cctx.App.Writer, "Call 'confirm-change-worker' at or after height %d to complete.\n", mi.WorkerChangeEpoch) return nil }, @@ -890,7 +890,7 @@ var actorConfirmChangeWorker = &cli.Command{ } if !cctx.Bool("really-do-it") { - fmt.Fprintln(cctx.App.Writer, "Pass --really-do-it to actually execute this action") + _, _ = fmt.Fprintln(cctx.App.Writer, "Pass --really-do-it to actually execute this action") return nil } @@ -961,7 +961,7 @@ var actorConfirmChangeWorker = &cli.Command{ return xerrors.Errorf("mpool push: %w", err) } - fmt.Fprintln(cctx.App.Writer, "Confirm Message CID:", smsg.Cid()) + _, _ = fmt.Fprintln(cctx.App.Writer, "Confirm Message CID:", smsg.Cid()) // wait for it to get mined into a block wait, err := nodeAPI.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence) @@ -971,7 +971,7 @@ var actorConfirmChangeWorker = &cli.Command{ // check it executed successfully if wait.Receipt.ExitCode.IsError() { - fmt.Fprintln(cctx.App.Writer, "Worker change failed!") + _, _ = fmt.Fprintln(cctx.App.Writer, "Worker change failed!") return err } diff --git a/cmd/lotus-shed/deal.go b/cmd/lotus-shed/deal.go deleted file mode 100644 index 3a350976e..000000000 --- a/cmd/lotus-shed/deal.go +++ /dev/null @@ -1,284 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "io" - "net/http" - "net/http/httptest" - "net/url" - "os" - - "github.com/fatih/color" - "github.com/mitchellh/go-homedir" - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - cborutil "github.com/filecoin-project/go-cbor-util" - commcid "github.com/filecoin-project/go-fil-commcid" - commp "github.com/filecoin-project/go-fil-commp-hashhash" - "github.com/filecoin-project/go-jsonrpc" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/go-state-types/builtin" - "github.com/filecoin-project/go-state-types/builtin/v9/market" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/types" - lcli "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/lib/must" -) - -var lpUtilCmd = &cli.Command{ - Name: "curio-util", - Usage: "curio utility commands", - Subcommands: []*cli.Command{ - lpUtilStartDealCmd, - }, -} - -var lpUtilStartDealCmd = &cli.Command{ - Name: "start-deal", - Usage: "start a deal with a specific curio instance", - ArgsUsage: "[dataFile] [miner]", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "curio-rpc", - Value: "http://127.0.0.1:12300", - }, - }, - Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 2 { - return xerrors.Errorf("expected 2 arguments") - } - - maddr, err := address.NewFromString(cctx.Args().Get(1)) - if err != nil { - return xerrors.Errorf("parse miner address: %w", err) - } - - full, closer, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return err - } - - defer closer() - ctx := lcli.ReqContext(cctx) - - defAddr, err := full.WalletDefaultAddress(ctx) - if err != nil { - return xerrors.Errorf("get default address: %w", err) - } - - // open rpc - var rpc api.CurioStruct - closer2, err := jsonrpc.NewMergeClient(ctx, cctx.String("curio-rpc"), "Filecoin", []interface{}{&rpc.Internal}, nil) - if err != nil { - return xerrors.Errorf("open rpc: %w", err) - } - defer closer2() - - v, err := rpc.Version(ctx) - if err != nil { - return xerrors.Errorf("rpc version: %w", err) - } - - fmt.Printf("* curio version: %s\n", v.String()) - - // open data file - data, err := homedir.Expand(cctx.Args().Get(0)) - if err != nil { - return xerrors.Errorf("get data file: %w", err) - } - - df, err := os.Open(data) - if err != nil { - return xerrors.Errorf("open data file: %w", err) - } - - dstat, err := df.Stat() - if err != nil { - return xerrors.Errorf("stat data file: %w", err) - } - - // compute commd - color.Green("> computing piece CID\n") - - writer := new(commp.Calc) - _, err = io.Copy(writer, df) - if err != nil { - return xerrors.Errorf("compute commd copy: %w", err) - } - - commp, pps, err := writer.Digest() - if err != nil { - return xerrors.Errorf("compute commd: %w", err) - } - - pieceCid, err := commcid.PieceCommitmentV1ToCID(commp) - if err != nil { - return xerrors.Errorf("make pieceCid: %w", err) - } - - fmt.Printf("* piece CID: %s\n", pieceCid) - fmt.Printf("* piece size: %d\n", pps) - - // start serving the file - color.Green("> starting temp http server\n") - - deleteCalled := make(chan struct{}) - - mux := http.NewServeMux() - mux.HandleFunc("/"+pieceCid.String(), func(w http.ResponseWriter, r *http.Request) { - // log request and method - color.Blue("< %s %s\n", r.Method, r.URL) - - if r.Method == http.MethodDelete { - close(deleteCalled) - return - } - - http.ServeFile(w, r, data) - }) - - ts := httptest.NewServer(mux) - - dataUrl, err := url.Parse(ts.URL) - if err != nil { - return xerrors.Errorf("parse data url: %w", err) - } - dataUrl.Path = "/" + pieceCid.String() - - fmt.Printf("* data url: %s\n", dataUrl) - - // publish the deal - color.Green("> publishing deal\n") - - head, err := full.ChainHead(ctx) - if err != nil { - return xerrors.Errorf("get chain head: %w", err) - } - - verif := false - - bds, err := full.StateDealProviderCollateralBounds(ctx, abi.PaddedPieceSize(pps), verif, head.Key()) - if err != nil { - return xerrors.Errorf("get provider collateral bounds: %w", err) - } - - pcoll := big.Mul(bds.Min, big.NewInt(2)) - - dealProposal := market.DealProposal{ - PieceCID: pieceCid, - PieceSize: abi.PaddedPieceSize(pps), - VerifiedDeal: verif, - Client: defAddr, - Provider: maddr, - Label: must.One(market.NewLabelFromString("lotus-shed-made-this")), - StartEpoch: head.Height() + 2000, - EndEpoch: head.Height() + 2880*300, - StoragePricePerEpoch: big.Zero(), - ProviderCollateral: pcoll, - ClientCollateral: big.Zero(), - } - - pbuf, err := cborutil.Dump(&dealProposal) - if err != nil { - return xerrors.Errorf("dump deal proposal: %w", err) - } - - sig, err := full.WalletSign(ctx, defAddr, pbuf) - if err != nil { - return xerrors.Errorf("sign deal proposal: %w", err) - } - - params := market.PublishStorageDealsParams{ - Deals: []market.ClientDealProposal{ - { - Proposal: dealProposal, - ClientSignature: *sig, - }, - }, - } - - var buf bytes.Buffer - err = params.MarshalCBOR(&buf) - if err != nil { - return xerrors.Errorf("marshal params: %w", err) - } - - msg := &types.Message{ - To: builtin.StorageMarketActorAddr, - From: defAddr, - Method: builtin.MethodsMarket.PublishStorageDeals, - Params: buf.Bytes(), - } - - smsg, err := full.MpoolPushMessage(ctx, msg, nil) - if err != nil { - return xerrors.Errorf("push message: %w", err) - } - - fmt.Printf("* PSD message cid: %s\n", smsg.Cid()) - - // wait for deal to be published - color.Green("> waiting for PublishStorageDeals to land on chain\n") - - rcpt, err := full.StateWaitMsg(ctx, smsg.Cid(), 3) - if err != nil { - return xerrors.Errorf("wait message: %w", err) - } - - if rcpt.Receipt.ExitCode != 0 { - return xerrors.Errorf("publish deal failed: exit code %d", rcpt.Receipt.ExitCode) - } - - // parse results - var ret market.PublishStorageDealsReturn - err = ret.UnmarshalCBOR(bytes.NewReader(rcpt.Receipt.Return)) - if err != nil { - return xerrors.Errorf("unmarshal return: %w", err) - } - - if len(ret.IDs) != 1 { - return xerrors.Errorf("expected 1 deal id, got %d", len(ret.IDs)) - } - - dealId := ret.IDs[0] - - fmt.Printf("* deal id: %d\n", dealId) - - // start deal - color.Green("> starting deal\n") - - pcid := smsg.Cid() - - pdi := api.PieceDealInfo{ - PublishCid: &pcid, - DealID: dealId, - DealProposal: &dealProposal, - DealSchedule: api.DealSchedule{ - StartEpoch: dealProposal.StartEpoch, - EndEpoch: dealProposal.EndEpoch, - }, - KeepUnsealed: true, - } - - soff, err := rpc.AllocatePieceToSector(ctx, maddr, pdi, dstat.Size(), *dataUrl, nil) - if err != nil { - return xerrors.Errorf("allocate piece to sector: %w", err) - } - - fmt.Printf("* sector offset: %d\n", soff) - - // wait for delete call on the file - color.Green("> waiting for file to be deleted (on sector finalize)\n") - - <-deleteCalled - - fmt.Println("* done") - - return nil - }, -} diff --git a/cmd/lotus-shed/eth.go b/cmd/lotus-shed/eth.go index fde4f96f6..46bd1b43c 100644 --- a/cmd/lotus-shed/eth.go +++ b/cmd/lotus-shed/eth.go @@ -88,16 +88,16 @@ var computeEthHashCmd = &cli.Command{ switch msg := msg.(type) { case *types.SignedMessage: - tx, err := ethtypes.EthTxFromSignedEthMessage(msg) + tx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(msg) if err != nil { return fmt.Errorf("failed to convert from signed message: %w", err) } - tx.Hash, err = tx.TxHash() + hash, err := tx.TxHash() if err != nil { return fmt.Errorf("failed to call TxHash: %w", err) } - fmt.Println(tx.Hash) + fmt.Println(hash) default: return fmt.Errorf("not a signed message") } diff --git a/cmd/lotus-shed/indexes.go b/cmd/lotus-shed/indexes.go index 12ebe0082..334b7f104 100644 --- a/cmd/lotus-shed/indexes.go +++ b/cmd/lotus-shed/indexes.go @@ -7,6 +7,7 @@ import ( "path" "path/filepath" "strings" + "time" "github.com/mitchellh/go-homedir" "github.com/urfave/cli/v2" @@ -23,6 +24,13 @@ import ( lcli "github.com/filecoin-project/lotus/cli" ) +const ( + // same as in chain/events/index.go + eventExists = `SELECT MAX(id) FROM event WHERE height=? AND tipset_key=? AND tipset_key_cid=? AND emitter_addr=? AND event_index=? AND message_cid=? AND message_index=?` + insertEvent = `INSERT OR IGNORE INTO event(height, tipset_key, tipset_key_cid, emitter_addr, event_index, message_cid, message_index, reverted) VALUES(?, ?, ?, ?, ?, ?, ?, ?)` + insertEntry = `INSERT OR IGNORE INTO event_entry(event_id, indexed, flags, key, codec, value) VALUES(?, ?, ?, ?, ?, ?)` +) + func withCategory(cat string, cmd *cli.Command) *cli.Command { cmd.Category = strings.ToUpper(cat) return cmd @@ -54,6 +62,16 @@ var backfillEventsCmd = &cli.Command{ Value: 2000, Usage: "the number of epochs to backfill", }, + &cli.BoolFlag{ + Name: "temporary-index", + Value: false, + Usage: "use a temporary index to speed up the backfill process", + }, + &cli.BoolFlag{ + Name: "vacuum", + Value: false, + Usage: "run VACUUM on the database after backfilling is complete; this will reclaim space from deleted rows, but may take a long time", + }, }, Action: func(cctx *cli.Context) error { srv, err := lcli.GetFullNodeServices(cctx) @@ -92,8 +110,12 @@ var backfillEventsCmd = &cli.Command{ return err } + log.Infof( + "WARNING: If this command is run against a node that is currently collecting events with DisableHistoricFilterAPI=false, " + + "it may cause the node to fail to record recent events due to the need to obtain an exclusive lock on the database for writes.") + dbPath := path.Join(basePath, "sqlite", "events.db") - db, err := sql.Open("sqlite3", dbPath) + db, err := sql.Open("sqlite3", dbPath+"?_txlock=immediate") if err != nil { return err } @@ -105,6 +127,14 @@ var backfillEventsCmd = &cli.Command{ } }() + if cctx.Bool("temporary-index") { + log.Info("creating temporary index (tmp_event_backfill_index) on event table to speed up backfill") + _, err := db.Exec("CREATE INDEX IF NOT EXISTS tmp_event_backfill_index ON event (height, tipset_key, tipset_key_cid, emitter_addr, event_index, message_cid, message_index, reverted);") + if err != nil { + return err + } + } + addressLookups := make(map[abi.ActorID]address.Address) // TODO: We don't need this address resolution anymore once https://github.com/filecoin-project/lotus/issues/11594 lands @@ -133,26 +163,36 @@ var backfillEventsCmd = &cli.Command{ var totalEventsAffected int64 var totalEntriesAffected int64 + stmtEventExists, err := db.Prepare(eventExists) + if err != nil { + return err + } + stmtInsertEvent, err := db.Prepare(insertEvent) + if err != nil { + return err + } + stmtInsertEntry, err := db.Prepare(insertEntry) + if err != nil { + return err + } + processHeight := func(ctx context.Context, cnt int, msgs []lapi.Message, receipts []*types.MessageReceipt) error { - tx, err := db.BeginTx(ctx, nil) - if err != nil { - return fmt.Errorf("failed to start transaction: %w", err) + var tx *sql.Tx + for { + var err error + tx, err = db.BeginTx(ctx, nil) + if err != nil { + if err.Error() == "database is locked" { + log.Warnf("database is locked, retrying in 200ms") + time.Sleep(200 * time.Millisecond) + continue + } + return err + } + break } defer tx.Rollback() //nolint:errcheck - stmtSelectEvent, err := tx.Prepare("SELECT MAX(id) from event WHERE height=? AND tipset_key=? and tipset_key_cid=? and emitter_addr=? and event_index=? and message_cid=? and message_index=? and reverted=false") - if err != nil { - return err - } - stmtEvent, err := tx.Prepare("INSERT INTO event (height, tipset_key, tipset_key_cid, emitter_addr, event_index, message_cid, message_index, reverted) VALUES(?, ?, ?, ?, ?, ?, ?, ?)") - if err != nil { - return err - } - stmtEntry, err := tx.Prepare("INSERT INTO event_entry(event_id, indexed, flags, key, codec, value) VALUES(?, ?, ?, ?, ?, ?)") - if err != nil { - return err - } - var eventsAffected int64 var entriesAffected int64 @@ -192,7 +232,7 @@ var backfillEventsCmd = &cli.Command{ // select the highest event id that exists in database, or null if none exists var entryID sql.NullInt64 - err = stmtSelectEvent.QueryRow( + err = tx.Stmt(stmtEventExists).QueryRow( currTs.Height(), currTs.Key().Bytes(), tsKeyCid.Bytes(), @@ -211,7 +251,7 @@ var backfillEventsCmd = &cli.Command{ } // event does not exist, lets backfill it - res, err := tx.Stmt(stmtEvent).Exec( + res, err := tx.Stmt(stmtInsertEvent).Exec( currTs.Height(), // height currTs.Key().Bytes(), // tipset_key tsKeyCid.Bytes(), // tipset_key_cid @@ -238,7 +278,7 @@ var backfillEventsCmd = &cli.Command{ // backfill the event entries for _, entry := range event.Entries { - _, err := tx.Stmt(stmtEntry).Exec( + _, err := tx.Stmt(stmtInsertEntry).Exec( entryID.Int64, // event_id isIndexedValue(entry.Flags), // indexed []byte{entry.Flags}, // flags @@ -312,6 +352,22 @@ var backfillEventsCmd = &cli.Command{ log.Infof("backfilling events complete, totalEventsAffected:%d, totalEntriesAffected:%d", totalEventsAffected, totalEntriesAffected) + if cctx.Bool("temporary-index") { + log.Info("dropping temporary index (tmp_event_backfill_index) on event table") + _, err := db.Exec("DROP INDEX IF EXISTS tmp_event_backfill_index;") + if err != nil { + fmt.Printf("ERROR: dropping index: %s", err) + } + } + + if cctx.Bool("vacuum") { + log.Info("running VACUUM on the database") + _, err := db.Exec("VACUUM;") + if err != nil { + return fmt.Errorf("failed to run VACUUM on the database: %w", err) + } + } + return nil }, } @@ -581,17 +637,17 @@ var backfillTxHashCmd = &cli.Command{ continue } - tx, err := ethtypes.EthTxFromSignedEthMessage(smsg) + tx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(smsg) if err != nil { return fmt.Errorf("failed to convert from signed message: %w at epoch: %d", err, epoch) } - tx.Hash, err = tx.TxHash() + hash, err := tx.TxHash() if err != nil { return fmt.Errorf("failed to calculate hash for ethTx: %w at epoch: %d", err, epoch) } - res, err := insertStmt.Exec(tx.Hash.String(), smsg.Cid().String()) + res, err := insertStmt.Exec(hash.String(), smsg.Cid().String()) if err != nil { return fmt.Errorf("error inserting tx mapping to db: %s at epoch: %d", err, epoch) } @@ -602,7 +658,7 @@ var backfillTxHashCmd = &cli.Command{ } if rowsAffected > 0 { - log.Debugf("Inserted txhash %s, cid: %s at epoch: %d", tx.Hash.String(), smsg.Cid().String(), epoch) + log.Debugf("Inserted txhash %s, cid: %s at epoch: %d", hash.String(), smsg.Cid().String(), epoch) } totalRowsAffected += rowsAffected diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index 4770f7145..911da346e 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -48,7 +48,6 @@ func main() { proofsCmd, verifRegCmd, marketCmd, - miscCmd, mpoolCmd, helloCmd, genesisVerifyCmd, @@ -92,13 +91,12 @@ func main() { mismatchesCmd, blockCmd, adlCmd, - lpUtilCmd, } app := &cli.App{ Name: "lotus-shed", Usage: "A place for all the lotus tools", - Version: build.UserVersion(), + Version: string(build.NodeUserVersion()), Commands: local, Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/cmd/lotus-shed/miner-multisig.go b/cmd/lotus-shed/miner-multisig.go index e8394b17a..0b7042e83 100644 --- a/cmd/lotus-shed/miner-multisig.go +++ b/cmd/lotus-shed/miner-multisig.go @@ -93,7 +93,7 @@ var mmProposeWithdrawBalance = &cli.Command{ return xerrors.Errorf("proposing message: %w", err) } - fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", pcid) + _, _ = fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", pcid) // wait for it to get mined into a block wait, err := api.StateWaitMsg(ctx, pcid, build.MessageConfidence) @@ -103,7 +103,7 @@ var mmProposeWithdrawBalance = &cli.Command{ // check it executed successfully if wait.Receipt.ExitCode.IsError() { - fmt.Fprintln(cctx.App.Writer, "Propose owner change tx failed!") + _, _ = fmt.Fprintln(cctx.App.Writer, "Propose owner change tx failed!") return err } @@ -172,7 +172,7 @@ var mmApproveWithdrawBalance = &cli.Command{ return xerrors.Errorf("approving message: %w", err) } - fmt.Fprintln(cctx.App.Writer, "Approve Message CID:", acid) + _, _ = fmt.Fprintln(cctx.App.Writer, "Approve Message CID:", acid) // wait for it to get mined into a block wait, err := api.StateWaitMsg(ctx, acid, build.MessageConfidence) @@ -182,7 +182,7 @@ var mmApproveWithdrawBalance = &cli.Command{ // check it executed successfully if wait.Receipt.ExitCode.IsError() { - fmt.Fprintln(cctx.App.Writer, "Approve owner change tx failed!") + _, _ = fmt.Fprintln(cctx.App.Writer, "Approve owner change tx failed!") return err } @@ -253,7 +253,7 @@ var mmProposeChangeOwner = &cli.Command{ return xerrors.Errorf("proposing message: %w", err) } - fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", pcid) + _, _ = fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", pcid) // wait for it to get mined into a block wait, err := api.StateWaitMsg(ctx, pcid, build.MessageConfidence) @@ -263,7 +263,7 @@ var mmProposeChangeOwner = &cli.Command{ // check it executed successfully if wait.Receipt.ExitCode.IsError() { - fmt.Fprintln(cctx.App.Writer, "Propose owner change tx failed!") + _, _ = fmt.Fprintln(cctx.App.Writer, "Propose owner change tx failed!") return err } @@ -343,7 +343,7 @@ var mmApproveChangeOwner = &cli.Command{ return xerrors.Errorf("approving message: %w", err) } - fmt.Fprintln(cctx.App.Writer, "Approve Message CID:", acid) + _, _ = fmt.Fprintln(cctx.App.Writer, "Approve Message CID:", acid) // wait for it to get mined into a block wait, err := api.StateWaitMsg(ctx, acid, build.MessageConfidence) @@ -353,7 +353,7 @@ var mmApproveChangeOwner = &cli.Command{ // check it executed successfully if wait.Receipt.ExitCode.IsError() { - fmt.Fprintln(cctx.App.Writer, "Approve owner change tx failed!") + _, _ = fmt.Fprintln(cctx.App.Writer, "Approve owner change tx failed!") return err } @@ -416,8 +416,8 @@ var mmProposeChangeWorker = &cli.Command{ } } else { if mi.NewWorker == newAddr { - fmt.Fprintf(cctx.App.Writer, "Worker key change to %s successfully proposed.\n", na) - fmt.Fprintf(cctx.App.Writer, "Call 'confirm-change-worker' at or after height %d to complete.\n", mi.WorkerChangeEpoch) + _, _ = fmt.Fprintf(cctx.App.Writer, "Worker key change to %s successfully proposed.\n", na) + _, _ = fmt.Fprintf(cctx.App.Writer, "Call 'confirm-change-worker' at or after height %d to complete.\n", mi.WorkerChangeEpoch) return fmt.Errorf("change to worker address %s already pending", na) } } @@ -427,8 +427,8 @@ var mmProposeChangeWorker = &cli.Command{ NewControlAddrs: mi.ControlAddresses, } - fmt.Fprintf(cctx.App.Writer, "newAddr: %s\n", newAddr) - fmt.Fprintf(cctx.App.Writer, "NewControlAddrs: %s\n", mi.ControlAddresses) + _, _ = fmt.Fprintf(cctx.App.Writer, "newAddr: %s\n", newAddr) + _, _ = fmt.Fprintf(cctx.App.Writer, "NewControlAddrs: %s\n", mi.ControlAddresses) sp, err := actors.SerializeParams(cwp) if err != nil { @@ -440,7 +440,7 @@ var mmProposeChangeWorker = &cli.Command{ return xerrors.Errorf("proposing message: %w", err) } - fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", pcid) + _, _ = fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", pcid) // wait for it to get mined into a block wait, err := api.StateWaitMsg(ctx, pcid, build.MessageConfidence) @@ -450,7 +450,7 @@ var mmProposeChangeWorker = &cli.Command{ // check it executed successfully if wait.Receipt.ExitCode.IsError() { - fmt.Fprintln(cctx.App.Writer, "Propose worker change tx failed!") + _, _ = fmt.Fprintln(cctx.App.Writer, "Propose worker change tx failed!") return err } @@ -632,7 +632,7 @@ var mmConfirmChangeWorker = &cli.Command{ return xerrors.Errorf("proposing message: %w", err) } - fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", pcid) + _, _ = fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", pcid) // wait for it to get mined into a block wait, err := api.StateWaitMsg(ctx, pcid, build.MessageConfidence) @@ -642,7 +642,7 @@ var mmConfirmChangeWorker = &cli.Command{ // check it executed successfully if wait.Receipt.ExitCode.IsError() { - fmt.Fprintln(cctx.App.Writer, "Propose worker change tx failed!") + _, _ = fmt.Fprintln(cctx.App.Writer, "Propose worker change tx failed!") return err } @@ -839,7 +839,7 @@ var mmProposeControlSet = &cli.Command{ return xerrors.Errorf("proposing message: %w", err) } - fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", pcid) + _, _ = fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", pcid) // wait for it to get mined into a block wait, err := api.StateWaitMsg(ctx, pcid, build.MessageConfidence) @@ -849,7 +849,7 @@ var mmProposeControlSet = &cli.Command{ // check it executed successfully if wait.Receipt.ExitCode.IsError() { - fmt.Fprintln(cctx.App.Writer, "Propose worker change tx failed!") + _, _ = fmt.Fprintln(cctx.App.Writer, "Propose worker change tx failed!") return err } diff --git a/cmd/lotus-shed/miner.go b/cmd/lotus-shed/miner.go index 2f9b4ecf1..827fbf415 100644 --- a/cmd/lotus-shed/miner.go +++ b/cmd/lotus-shed/miner.go @@ -220,6 +220,13 @@ var minerCreateCmd = &cli.Command{ Name: "create", Usage: "sends a create miner message", ArgsUsage: "[sender] [owner] [worker] [sector size]", + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "confidence", + Usage: "number of block confirmations to wait for", + Value: int(build.MessageConfidence), + }, + }, Action: func(cctx *cli.Context) error { wapi, closer, err := lcli.GetFullNodeAPI(cctx) if err != nil { @@ -274,7 +281,7 @@ var minerCreateCmd = &cli.Command{ log.Infof("Initializing worker account %s, message: %s", worker, signed.Cid()) log.Infof("Waiting for confirmation") - mw, err := wapi.StateWaitMsg(ctx, signed.Cid(), build.MessageConfidence) + mw, err := wapi.StateWaitMsg(ctx, signed.Cid(), uint64(cctx.Int("confidence"))) if err != nil { return xerrors.Errorf("waiting for worker init: %w", err) } diff --git a/cmd/lotus-shed/misc.go b/cmd/lotus-shed/misc.go deleted file mode 100644 index cfda362c4..000000000 --- a/cmd/lotus-shed/misc.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - - "github.com/urfave/cli/v2" - - "github.com/filecoin-project/go-fil-markets/storagemarket" -) - -var miscCmd = &cli.Command{ - Name: "misc", - Usage: "Assorted unsorted commands for various purposes", - Flags: []cli.Flag{}, - Subcommands: []*cli.Command{ - dealStateMappingCmd, - }, -} - -var dealStateMappingCmd = &cli.Command{ - Name: "deal-state", - Action: func(cctx *cli.Context) error { - if !cctx.Args().Present() { - return cli.ShowCommandHelp(cctx, cctx.Command.Name) - } - - num, err := strconv.Atoi(cctx.Args().First()) - if err != nil { - return err - } - - ststr, ok := storagemarket.DealStates[uint64(num)] - if !ok { - return fmt.Errorf("no such deal state %d", num) - } - fmt.Println(ststr) - return nil - }, -} diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index b92fa4b2f..8438b67b4 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -66,17 +66,17 @@ func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) e startTime := time.Unix(int64(start.MinTimestamp()), 0) duration := headTime.Sub(startTime) - fmt.Fprintf(tw, "Num:\t%s\n", sim.Name()) - fmt.Fprintf(tw, "Head:\t%s\n", head) - fmt.Fprintf(tw, "Start Epoch:\t%d\n", firstEpoch) - fmt.Fprintf(tw, "End Epoch:\t%d\n", headEpoch) - fmt.Fprintf(tw, "Length:\t%d\n", headEpoch-firstEpoch) - fmt.Fprintf(tw, "Start Date:\t%s\n", startTime) - fmt.Fprintf(tw, "End Date:\t%s\n", headTime) - fmt.Fprintf(tw, "Duration:\t%.2f day(s)\n", duration.Hours()/24) - fmt.Fprintf(tw, "Capacity:\t%s\n", types.SizeStr(powerNow.RawBytePower)) - fmt.Fprintf(tw, "Daily Capacity Growth:\t%s/day\n", types.SizeStr(growthRate)) - fmt.Fprintf(tw, "Network Version:\t%d\n", sim.GetNetworkVersion()) + fmt.Fprintf(tw, "Num:\t%s\n", sim.Name()) //nolint:errcheck + fmt.Fprintf(tw, "Head:\t%s\n", head) //nolint:errcheck + fmt.Fprintf(tw, "Start Epoch:\t%d\n", firstEpoch) //nolint:errcheck + fmt.Fprintf(tw, "End Epoch:\t%d\n", headEpoch) //nolint:errcheck + fmt.Fprintf(tw, "Length:\t%d\n", headEpoch-firstEpoch) //nolint:errcheck + fmt.Fprintf(tw, "Start Date:\t%s\n", startTime) //nolint:errcheck + fmt.Fprintf(tw, "End Date:\t%s\n", headTime) //nolint:errcheck + fmt.Fprintf(tw, "Duration:\t%.2f day(s)\n", duration.Hours()/24) //nolint:errcheck + fmt.Fprintf(tw, "Capacity:\t%s\n", types.SizeStr(powerNow.RawBytePower)) //nolint:errcheck + fmt.Fprintf(tw, "Daily Capacity Growth:\t%s/day\n", types.SizeStr(growthRate)) //nolint:errcheck + fmt.Fprintf(tw, "Network Version:\t%d\n", sim.GetNetworkVersion()) //nolint:errcheck return tw.Flush() } diff --git a/cmd/lotus-sim/info_capacity.go b/cmd/lotus-sim/info_capacity.go index a92d2cde4..cca56ca57 100644 --- a/cmd/lotus-sim/info_capacity.go +++ b/cmd/lotus-sim/info_capacity.go @@ -60,7 +60,7 @@ var infoCapacityGrowthSimCommand = &cli.Command{ ) lastPower = newPower lastHeight = newEpoch - fmt.Fprintf(cctx.App.Writer, "%s/day\n", types.SizeStr(growthRate)) + _, _ = fmt.Fprintf(cctx.App.Writer, "%s/day\n", types.SizeStr(growthRate)) } return cctx.Err() }, diff --git a/cmd/lotus-sim/info_state.go b/cmd/lotus-sim/info_state.go index 125dae81d..d6cb2ea41 100644 --- a/cmd/lotus-sim/info_state.go +++ b/cmd/lotus-sim/info_state.go @@ -128,7 +128,7 @@ var infoStateGrowthSimCommand = &cli.Command{ return err } - fmt.Fprintf(cctx.App.Writer, "%d: %s\n", ts.Height(), types.SizeStr(types.NewInt(parentStateSize))) + _, _ = fmt.Fprintf(cctx.App.Writer, "%d: %s\n", ts.Height(), types.SizeStr(types.NewInt(parentStateSize))) } ts, err = sim.Node.Chainstore.LoadTipSet(cctx.Context, ts.Parents()) diff --git a/cmd/lotus-sim/info_wdpost.go b/cmd/lotus-sim/info_wdpost.go index 426e85ca8..19c4fa619 100644 --- a/cmd/lotus-sim/info_wdpost.go +++ b/cmd/lotus-sim/info_wdpost.go @@ -35,7 +35,7 @@ var infoWindowPostBandwidthSimCommand = &cli.Command{ var postGas, totalGas int64 printStats := func() { - fmt.Fprintf(cctx.App.Writer, "%.4f%%\n", float64(100*postGas)/float64(totalGas)) + _, _ = fmt.Fprintf(cctx.App.Writer, "%.4f%%\n", float64(100*postGas)/float64(totalGas)) } idx := 0 err = sim.Walk(cctx.Context, 0, func( diff --git a/cmd/lotus-sim/list.go b/cmd/lotus-sim/list.go index 37e767b9a..1273df545 100644 --- a/cmd/lotus-sim/list.go +++ b/cmd/lotus-sim/list.go @@ -31,7 +31,7 @@ var listSimCommand = &cli.Command{ return err } head := sim.GetHead() - fmt.Fprintf(tw, "%s\t%s\t%s\n", name, head.Height(), head.Key()) + _, _ = fmt.Fprintf(tw, "%s\t%s\t%s\n", name, head.Height(), head.Key()) } return tw.Flush() }, diff --git a/cmd/lotus-sim/profile.go b/cmd/lotus-sim/profile.go index 63e0ef3bd..e95fc4696 100644 --- a/cmd/lotus-sim/profile.go +++ b/cmd/lotus-sim/profile.go @@ -83,9 +83,9 @@ func profileOnSignal(cctx *cli.Context, signals ...os.Signal) { case context.Canceled: return case nil: - fmt.Fprintf(cctx.App.ErrWriter, "Wrote profile to %q\n", fname) + _, _ = fmt.Fprintf(cctx.App.ErrWriter, "Wrote profile to %q\n", fname) default: - fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to write profile: %s\n", err) + _, _ = fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to write profile: %s\n", err) } case <-cctx.Done(): return diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go index a985fdf9e..91a8f3640 100644 --- a/cmd/lotus-sim/run.go +++ b/cmd/lotus-sim/run.go @@ -51,22 +51,22 @@ Signals: return err } - fmt.Fprintf(cctx.App.Writer, "advanced to %d %s\n", ts.Height(), ts.Key()) + _, _ = fmt.Fprintf(cctx.App.Writer, "advanced to %d %s\n", ts.Height(), ts.Key()) // Print select { case <-ch: - fmt.Fprintln(cctx.App.Writer, "---------------------") + _, _ = fmt.Fprintln(cctx.App.Writer, "---------------------") if err := printInfo(cctx.Context, sim, cctx.App.Writer); err != nil { - fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to print info: %s\n", err) + _, _ = fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to print info: %s\n", err) } - fmt.Fprintln(cctx.App.Writer, "---------------------") + _, _ = fmt.Fprintln(cctx.App.Writer, "---------------------") case <-cctx.Context.Done(): return cctx.Err() default: } } - fmt.Fprintln(cctx.App.Writer, "simulation done") + _, _ = fmt.Fprintln(cctx.App.Writer, "simulation done") return err }, } diff --git a/cmd/lotus-sim/upgrade.go b/cmd/lotus-sim/upgrade.go index dfc726d6b..90f87baa3 100644 --- a/cmd/lotus-sim/upgrade.go +++ b/cmd/lotus-sim/upgrade.go @@ -48,10 +48,10 @@ var upgradeList = &cli.Command{ } tw := tabwriter.NewWriter(cctx.App.Writer, 8, 8, 0, ' ', 0) - fmt.Fprintf(tw, "version\theight\tepochs\tmigration\texpensive") + _, _ = fmt.Fprintf(tw, "version\theight\tepochs\tmigration\texpensive") epoch := sim.GetHead().Height() for _, upgrade := range upgrades { - fmt.Fprintf( + _, _ = fmt.Fprintf( tw, "%d\t%d\t%+d\t%t\t%t", upgrade.Network, upgrade.Height, upgrade.Height-epoch, upgrade.Migration != nil, diff --git a/cmd/lotus-sim/util.go b/cmd/lotus-sim/util.go index cd15cca0d..811c9b6eb 100644 --- a/cmd/lotus-sim/util.go +++ b/cmd/lotus-sim/util.go @@ -10,9 +10,8 @@ import ( ) func open(cctx *cli.Context) (*simulation.Node, error) { - _, _, err := ulimit.ManageFdLimit() - if err != nil { - fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to raise ulimit: %s\n", err) + if _, _, err := ulimit.ManageFdLimit(); err != nil { + _, _ = fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to raise ulimit: %s\n", err) } return simulation.OpenNode(cctx.Context, cctx.String("repo")) } diff --git a/cmd/lotus-stats/main.go b/cmd/lotus-stats/main.go index 20971c1f3..16c625952 100644 --- a/cmd/lotus-stats/main.go +++ b/cmd/lotus-stats/main.go @@ -42,7 +42,7 @@ func main() { app := &cli.App{ Name: "lotus-stats", Usage: "Collect basic information about a filecoin network using lotus", - Version: build.UserVersion(), + Version: string(build.NodeUserVersion()), Flags: []cli.Flag{ &cli.StringFlag{ Name: "lotus-path", diff --git a/cmd/lotus-wallet/main.go b/cmd/lotus-wallet/main.go index 8360dae15..84818ecb1 100644 --- a/cmd/lotus-wallet/main.go +++ b/cmd/lotus-wallet/main.go @@ -52,7 +52,7 @@ func main() { app := &cli.App{ Name: "lotus-wallet", Usage: "Basic external wallet", - Version: build.UserVersion(), + Version: string(build.NodeUserVersion()), Description: ` lotus-wallet provides a remote wallet service for lotus. diff --git a/cmd/lotus-worker/main.go b/cmd/lotus-worker/main.go index 41af11bdd..15283a30b 100644 --- a/cmd/lotus-worker/main.go +++ b/cmd/lotus-worker/main.go @@ -70,7 +70,7 @@ func main() { app := &cli.App{ Name: "lotus-worker", Usage: "Remote miner worker", - Version: build.UserVersion(), + Version: string(build.MinerUserVersion()), EnableBashCompletion: true, Flags: []cli.Flag{ &cli.StringFlag{ @@ -104,7 +104,7 @@ func main() { After: func(c *cli.Context) error { if r := recover(); r != nil { // Generate report in LOTUS_PANIC_REPORT_PATH and re-raise panic - build.GeneratePanicReport(c.String("panic-reports"), c.String(FlagWorkerRepo), c.App.Name) + build.GenerateMinerPanicReport(c.String("panic-reports"), c.String(FlagWorkerRepo), c.App.Name) panic(r) } return nil diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 369938020..ba2936e2a 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -209,7 +209,7 @@ var DaemonCmd = &cli.Command{ } ctx, _ := tag.New(context.Background(), - tag.Insert(metrics.Version, build.BuildVersion), + tag.Insert(metrics.Version, build.NodeBuildVersion), tag.Insert(metrics.Commit, build.CurrentCommit), tag.Insert(metrics.NodeType, "chain"), ) diff --git a/cmd/lotus/main.go b/cmd/lotus/main.go index fce9a6136..6b29f6cf8 100644 --- a/cmd/lotus/main.go +++ b/cmd/lotus/main.go @@ -72,7 +72,7 @@ func main() { app := &cli.App{ Name: "lotus", Usage: "Filecoin decentralized storage network client", - Version: build.UserVersion(), + Version: string(build.NodeUserVersion()), EnableBashCompletion: true, Flags: []cli.Flag{ &cli.StringFlag{ @@ -107,7 +107,7 @@ func main() { After: func(c *cli.Context) error { if r := recover(); r != nil { // Generate report in LOTUS_PATH and re-raise panic - build.GeneratePanicReport(c.String("panic-reports"), c.String("repo"), c.App.Name) + build.GenerateNodePanicReport(c.String("panic-reports"), c.String("repo"), c.App.Name) panic(r) } return nil diff --git a/cmd/sptool/actor.go b/cmd/sptool/actor.go deleted file mode 100644 index f17568a31..000000000 --- a/cmd/sptool/actor.go +++ /dev/null @@ -1,141 +0,0 @@ -package main - -import ( - "fmt" - "os" - "strings" - - "github.com/fatih/color" - "github.com/urfave/cli/v2" - - "github.com/filecoin-project/go-address" - - builtin2 "github.com/filecoin-project/lotus/chain/actors/builtin" - "github.com/filecoin-project/lotus/chain/types" - lcli "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/cli/spcli" - "github.com/filecoin-project/lotus/lib/tablewriter" -) - -var actorCmd = &cli.Command{ - Name: "actor", - Usage: "Manage Filecoin Miner Actor Metadata", - Subcommands: []*cli.Command{ - spcli.ActorSetAddrsCmd(SPTActorGetter), - spcli.ActorWithdrawCmd(SPTActorGetter), - spcli.ActorRepayDebtCmd(SPTActorGetter), - spcli.ActorSetPeeridCmd(SPTActorGetter), - spcli.ActorSetOwnerCmd(SPTActorGetter), - spcli.ActorControlCmd(SPTActorGetter, actorControlListCmd(SPTActorGetter)), - spcli.ActorProposeChangeWorkerCmd(SPTActorGetter), - spcli.ActorConfirmChangeWorkerCmd(SPTActorGetter), - spcli.ActorCompactAllocatedCmd(SPTActorGetter), - spcli.ActorProposeChangeBeneficiaryCmd(SPTActorGetter), - spcli.ActorConfirmChangeBeneficiaryCmd(SPTActorGetter), - spcli.ActorNewMinerCmd, - }, -} - -func actorControlListCmd(getActor spcli.ActorAddressGetter) *cli.Command { - return &cli.Command{ - Name: "list", - Usage: "Get currently set control addresses. Note: This excludes most roles as they are not known to the immediate chain state.", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "verbose", - }, - }, - Action: func(cctx *cli.Context) error { - api, acloser, err := lcli.GetFullNodeAPIV1(cctx) - if err != nil { - return err - } - defer acloser() - - ctx := lcli.ReqContext(cctx) - - maddr, err := getActor(cctx) - if err != nil { - return err - } - - mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - tw := tablewriter.New( - tablewriter.Col("name"), - tablewriter.Col("ID"), - tablewriter.Col("key"), - tablewriter.Col("use"), - tablewriter.Col("balance"), - ) - - post := map[address.Address]struct{}{} - - for _, ca := range mi.ControlAddresses { - post[ca] = struct{}{} - } - - printKey := func(name string, a address.Address) { - var actor *types.Actor - if actor, err = api.StateGetActor(ctx, a, types.EmptyTSK); err != nil { - fmt.Printf("%s\t%s: error getting actor: %s\n", name, a, err) - return - } - b := actor.Balance - - var k = a - // 'a' maybe a 'robust', in that case, 'StateAccountKey' returns an error. - if builtin2.IsAccountActor(actor.Code) { - if k, err = api.StateAccountKey(ctx, a, types.EmptyTSK); err != nil { - fmt.Printf("%s\t%s: error getting account key: %s\n", name, a, err) - return - } - } - kstr := k.String() - if !cctx.Bool("verbose") { - if len(kstr) > 9 { - kstr = kstr[:6] + "..." - } - } - - bstr := types.FIL(b).String() - switch { - case b.LessThan(types.FromFil(10)): - bstr = color.RedString(bstr) - case b.LessThan(types.FromFil(50)): - bstr = color.YellowString(bstr) - default: - bstr = color.GreenString(bstr) - } - - var uses []string - if a == mi.Worker { - uses = append(uses, color.YellowString("other")) - } - if _, ok := post[a]; ok { - uses = append(uses, color.GreenString("post")) - } - - tw.Write(map[string]interface{}{ - "name": name, - "ID": a, - "key": kstr, - "use": strings.Join(uses, " "), - "balance": bstr, - }) - } - - printKey("owner", mi.Owner) - printKey("worker", mi.Worker) - printKey("beneficiary", mi.Beneficiary) - for i, ca := range mi.ControlAddresses { - printKey(fmt.Sprintf("control-%d", i), ca) - } - - return tw.Flush(os.Stdout) - }, - } -} diff --git a/cmd/sptool/main.go b/cmd/sptool/main.go deleted file mode 100644 index 7970b8db3..000000000 --- a/cmd/sptool/main.go +++ /dev/null @@ -1,84 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - "os/signal" - - logging "github.com/ipfs/go-log/v2" - "github.com/urfave/cli/v2" - - "github.com/filecoin-project/go-address" - - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/cli/spcli" -) - -var log = logging.Logger("sptool") - -func main() { - local := []*cli.Command{ - actorCmd, - spcli.InfoCmd(SPTActorGetter), - sectorsCmd, - provingCmd, - //multiSigCmd, - } - - app := &cli.App{ - Name: "sptool", - Usage: "Manage Filecoin Miner Actor", - Version: build.UserVersion(), - Commands: local, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "repo", - EnvVars: []string{"LOTUS_PATH"}, - Hidden: true, - Value: "~/.lotus", // TODO: Consider XDG_DATA_HOME - }, - &cli.StringFlag{ - Name: "log-level", - Value: "info", - }, - &cli.StringFlag{ - Name: "actor", - Required: os.Getenv("LOTUS_DOCS_GENERATION") != "1", - Usage: "miner actor to manage", - EnvVars: []string{"SP_ADDRESS"}, - }, - }, - Before: func(cctx *cli.Context) error { - return logging.SetLogLevel("sptool", cctx.String("sptool")) - }, - } - - // terminate early on ctrl+c - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - ctx, cancel := context.WithCancel(context.Background()) - go func() { - <-c - cancel() - fmt.Println("Received interrupt, shutting down... Press CTRL+C again to force shutdown") - <-c - fmt.Println("Forcing stop") - os.Exit(1) - }() - - if err := app.RunContext(ctx, os.Args); err != nil { - log.Errorf("%+v", err) - os.Exit(1) - return - } - -} - -func SPTActorGetter(cctx *cli.Context) (address.Address, error) { - addr, err := address.NewFromString(cctx.String("actor")) - if err != nil { - return address.Undef, fmt.Errorf("parsing address: %w", err) - } - return addr, nil -} diff --git a/cmd/sptool/proving.go b/cmd/sptool/proving.go deleted file mode 100644 index 87c67b5f4..000000000 --- a/cmd/sptool/proving.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import ( - "github.com/urfave/cli/v2" - - "github.com/filecoin-project/lotus/cli/spcli" -) - -var provingCmd = &cli.Command{ - Name: "proving", - Usage: "View proving information", - Subcommands: []*cli.Command{ - spcli.ProvingInfoCmd(SPTActorGetter), - spcli.ProvingDeadlinesCmd(SPTActorGetter), - spcli.ProvingDeadlineInfoCmd(SPTActorGetter), - spcli.ProvingFaultsCmd(SPTActorGetter), - }, -} diff --git a/cmd/sptool/sector.go b/cmd/sptool/sector.go deleted file mode 100644 index 8f341b5cc..000000000 --- a/cmd/sptool/sector.go +++ /dev/null @@ -1,355 +0,0 @@ -package main - -import ( - "fmt" - "os" - "sort" - - "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" - - "github.com/filecoin-project/lotus/blockstore" - "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" - lcli "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/cli/spcli" - cliutil "github.com/filecoin-project/lotus/cli/util" - "github.com/filecoin-project/lotus/lib/tablewriter" -) - -var sectorsCmd = &cli.Command{ - Name: "sectors", - Usage: "interact with sector store", - Subcommands: []*cli.Command{ - spcli.SectorsStatusCmd(SPTActorGetter, nil), - sectorsListCmd, // in-house b/c chain-only is so different. Needs Curio *web* implementation - spcli.SectorPreCommitsCmd(SPTActorGetter), - spcli.SectorsCheckExpireCmd(SPTActorGetter), - sectorsExpiredCmd, // in-house b/c chain-only is so different - spcli.SectorsExtendCmd(SPTActorGetter), - spcli.TerminateSectorCmd(SPTActorGetter), - spcli.SectorsCompactPartitionsCmd(SPTActorGetter), - }} - -var sectorsExpiredCmd = &cli.Command{ - Name: "expired", - Usage: "Get or cleanup expired sectors", - Flags: []cli.Flag{ - &cli.Int64Flag{ - Name: "expired-epoch", - Usage: "epoch at which to check sector expirations", - DefaultText: "WinningPoSt lookback epoch", - }, - }, - Action: func(cctx *cli.Context) error { - fullApi, nCloser, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return xerrors.Errorf("getting fullnode api: %w", err) - } - defer nCloser() - ctx := lcli.ReqContext(cctx) - - head, err := fullApi.ChainHead(ctx) - if err != nil { - return xerrors.Errorf("getting chain head: %w", err) - } - - lbEpoch := abi.ChainEpoch(cctx.Int64("expired-epoch")) - if !cctx.IsSet("expired-epoch") { - nv, err := fullApi.StateNetworkVersion(ctx, head.Key()) - if err != nil { - return xerrors.Errorf("getting network version: %w", err) - } - - lbEpoch = head.Height() - policy.GetWinningPoStSectorSetLookback(nv) - if lbEpoch < 0 { - return xerrors.Errorf("too early to terminate sectors") - } - } - - if cctx.IsSet("confirm-remove-count") && !cctx.IsSet("expired-epoch") { - return xerrors.Errorf("--expired-epoch must be specified with --confirm-remove-count") - } - - lbts, err := fullApi.ChainGetTipSetByHeight(ctx, lbEpoch, head.Key()) - if err != nil { - return xerrors.Errorf("getting lookback tipset: %w", err) - } - - maddr, err := SPTActorGetter(cctx) - if err != nil { - return xerrors.Errorf("getting actor address: %w", err) - } - - // toCheck is a working bitfield which will only contain terminated sectors - toCheck := bitfield.New() - { - sectors, err := fullApi.StateMinerSectors(ctx, maddr, nil, lbts.Key()) - if err != nil { - return xerrors.Errorf("getting sector on chain info: %w", err) - } - - for _, sector := range sectors { - if sector.Expiration <= lbts.Height() { - toCheck.Set(uint64(sector.SectorNumber)) - } - } - } - - mact, err := fullApi.StateGetActor(ctx, maddr, lbts.Key()) - 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 - } - - alloc, err := mas.GetAllocatedSectors() - if err != nil { - return xerrors.Errorf("getting allocated sectors: %w", err) - } - - // only allocated sectors can be expired, - toCheck, err = bitfield.IntersectBitField(toCheck, *alloc) - if err != nil { - return xerrors.Errorf("intersecting bitfields: %w", err) - } - - if err := mas.ForEachDeadline(func(dlIdx uint64, dl miner.Deadline) error { - return dl.ForEachPartition(func(partIdx uint64, part miner.Partition) error { - live, err := part.LiveSectors() - if err != nil { - return err - } - - toCheck, err = bitfield.SubtractBitField(toCheck, live) - if err != nil { - return err - } - - unproven, err := part.UnprovenSectors() - if err != nil { - return err - } - - toCheck, err = bitfield.SubtractBitField(toCheck, unproven) - - return err - }) - }); err != nil { - return err - } - - err = mas.ForEachPrecommittedSector(func(pci miner.SectorPreCommitOnChainInfo) error { - toCheck.Unset(uint64(pci.Info.SectorNumber)) - return nil - }) - if err != nil { - return err - } - - // toCheck now only contains sectors which either failed to precommit or are expired/terminated - fmt.Printf("Sectors that either failed to precommit or are expired/terminated:\n") - - err = toCheck.ForEach(func(u uint64) error { - fmt.Println(abi.SectorNumber(u)) - - return nil - }) - if err != nil { - return err - } - - return nil - }, -} - -var sectorsListCmd = &cli.Command{ - Name: "list", - Usage: "List sectors", - Flags: []cli.Flag{ - /* - &cli.BoolFlag{ - Name: "show-removed", - Usage: "show removed sectors", - Aliases: []string{"r"}, - }, - &cli.BoolFlag{ - Name: "fast", - Usage: "don't show on-chain info for better performance", - Aliases: []string{"f"}, - }, - &cli.BoolFlag{ - Name: "events", - Usage: "display number of events the sector has received", - Aliases: []string{"e"}, - }, - &cli.BoolFlag{ - Name: "initial-pledge", - Usage: "display initial pledge", - Aliases: []string{"p"}, - }, - &cli.BoolFlag{ - Name: "seal-time", - Usage: "display how long it took for the sector to be sealed", - Aliases: []string{"t"}, - }, - &cli.StringFlag{ - Name: "states", - Usage: "filter sectors by a comma-separated list of states", - }, - &cli.BoolFlag{ - Name: "unproven", - Usage: "only show sectors which aren't in the 'Proving' state", - Aliases: []string{"u"}, - }, - */ - }, - Subcommands: []*cli.Command{ - //sectorsListUpgradeBoundsCmd, - }, - Action: func(cctx *cli.Context) error { - fullApi, closer2, err := lcli.GetFullNodeAPI(cctx) // TODO: consider storing full node address in config - if err != nil { - return err - } - defer closer2() - - ctx := lcli.ReqContext(cctx) - - maddr, err := SPTActorGetter(cctx) - if err != nil { - return err - } - - head, err := fullApi.ChainHead(ctx) - if err != nil { - return err - } - - activeSet, err := fullApi.StateMinerActiveSectors(ctx, maddr, head.Key()) - if err != nil { - return err - } - activeIDs := make(map[abi.SectorNumber]struct{}, len(activeSet)) - for _, info := range activeSet { - activeIDs[info.SectorNumber] = struct{}{} - } - - sset, err := fullApi.StateMinerSectors(ctx, maddr, nil, head.Key()) - if err != nil { - return err - } - commitedIDs := make(map[abi.SectorNumber]struct{}, len(sset)) - for _, info := range sset { - commitedIDs[info.SectorNumber] = struct{}{} - } - - sort.Slice(sset, func(i, j int) bool { - return sset[i].SectorNumber < sset[j].SectorNumber - }) - - tw := tablewriter.New( - tablewriter.Col("ID"), - tablewriter.Col("State"), - tablewriter.Col("OnChain"), - tablewriter.Col("Active"), - tablewriter.Col("Expiration"), - tablewriter.Col("SealTime"), - tablewriter.Col("Events"), - tablewriter.Col("Deals"), - tablewriter.Col("DealWeight"), - tablewriter.Col("VerifiedPower"), - tablewriter.Col("Pledge"), - tablewriter.NewLineCol("Error"), - tablewriter.NewLineCol("RecoveryTimeout")) - - fast := cctx.Bool("fast") - - for _, st := range sset { - s := st.SectorNumber - _, inSSet := commitedIDs[s] - _, inASet := activeIDs[s] - - const verifiedPowerGainMul = 9 - dw, vp := .0, .0 - { - rdw := big.Add(st.DealWeight, st.VerifiedDealWeight) - dw = float64(big.Div(rdw, big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) - vp = float64(big.Div(big.Mul(st.VerifiedDealWeight, big.NewInt(verifiedPowerGainMul)), big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) - } - - var deals int - for _, deal := range st.DealIDs { - if deal != 0 { - deals++ - } - } - - exp := st.Expiration - // if st.OnTime > 0 && st.OnTime < exp { - // exp = st.OnTime // Can be different when the sector was CC upgraded - // } - - m := map[string]interface{}{ - "ID": s, - //"State": color.New(spcli.StateOrder[sealing.SectorState(st.State)].Col).Sprint(st.State), - "OnChain": yesno(inSSet), - "Active": yesno(inASet), - } - - if deals > 0 { - m["Deals"] = color.GreenString("%d", deals) - } else { - m["Deals"] = color.BlueString("CC") - // if st.ToUpgrade { - // m["Deals"] = color.CyanString("CC(upgrade)") - // } - } - - if !fast { - if !inSSet { - m["Expiration"] = "n/a" - } else { - m["Expiration"] = cliutil.EpochTime(head.Height(), exp) - // if st.Early > 0 { - // m["RecoveryTimeout"] = color.YellowString(cliutil.EpochTime(head.Height(), st.Early)) - // } - } - if inSSet && cctx.Bool("initial-pledge") { - m["Pledge"] = types.FIL(st.InitialPledge).Short() - } - } - - if !fast && deals > 0 { - m["DealWeight"] = units.BytesSize(dw) - if vp > 0 { - m["VerifiedPower"] = color.GreenString(units.BytesSize(vp)) - } - } - - tw.Write(m) - } - - return tw.Flush(os.Stdout) - }, -} - -func yesno(b bool) string { - if b { - return color.GreenString("YES") - } - return color.RedString("NO") -} diff --git a/curiosrc/address.go b/curiosrc/address.go deleted file mode 100644 index 6d1738f2d..000000000 --- a/curiosrc/address.go +++ /dev/null @@ -1,64 +0,0 @@ -package curio - -import ( - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/curiosrc/multictladdr" - "github.com/filecoin-project/lotus/node/config" -) - -func AddressSelector(addrConf []config.CurioAddresses) func() (*multictladdr.MultiAddressSelector, error) { - return func() (*multictladdr.MultiAddressSelector, error) { - as := &multictladdr.MultiAddressSelector{ - MinerMap: make(map[address.Address]api.AddressConfig), - } - if addrConf == nil { - return as, nil - } - - for _, addrConf := range addrConf { - for _, minerID := range addrConf.MinerAddresses { - tmp := api.AddressConfig{ - DisableOwnerFallback: addrConf.DisableOwnerFallback, - DisableWorkerFallback: addrConf.DisableWorkerFallback, - } - - for _, s := range addrConf.PreCommitControl { - addr, err := address.NewFromString(s) - if err != nil { - return nil, xerrors.Errorf("parsing precommit control address: %w", err) - } - - tmp.PreCommitControl = append(tmp.PreCommitControl, addr) - } - - for _, s := range addrConf.CommitControl { - addr, err := address.NewFromString(s) - if err != nil { - return nil, xerrors.Errorf("parsing commit control address: %w", err) - } - - tmp.CommitControl = append(tmp.CommitControl, addr) - } - - for _, s := range addrConf.TerminateControl { - addr, err := address.NewFromString(s) - if err != nil { - return nil, xerrors.Errorf("parsing terminate control address: %w", err) - } - - tmp.TerminateControl = append(tmp.TerminateControl, addr) - } - a, err := address.NewFromString(minerID) - if err != nil { - return nil, xerrors.Errorf("parsing miner address %s: %w", minerID, err) - } - as.MinerMap[a] = tmp - } - } - return as, nil - } -} diff --git a/curiosrc/builder.go b/curiosrc/builder.go deleted file mode 100644 index 3cd4bd0cd..000000000 --- a/curiosrc/builder.go +++ /dev/null @@ -1,46 +0,0 @@ -package curio - -import ( - "context" - "time" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/curiosrc/chainsched" - "github.com/filecoin-project/lotus/curiosrc/message" - "github.com/filecoin-project/lotus/curiosrc/multictladdr" - "github.com/filecoin-project/lotus/curiosrc/window" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/node/config" - dtypes "github.com/filecoin-project/lotus/node/modules/dtypes" - "github.com/filecoin-project/lotus/storage/paths" - "github.com/filecoin-project/lotus/storage/sealer" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -//var log = logging.Logger("provider") - -func WindowPostScheduler(ctx context.Context, fc config.CurioFees, pc config.CurioProvingConfig, - api api.FullNode, verif storiface.Verifier, lw *sealer.LocalWorker, sender *message.Sender, chainSched *chainsched.CurioChainSched, - as *multictladdr.MultiAddressSelector, addresses map[dtypes.MinerAddress]bool, db *harmonydb.DB, - stor paths.Store, idx paths.SectorIndex, max int) (*window.WdPostTask, *window.WdPostSubmitTask, *window.WdPostRecoverDeclareTask, error) { - - // todo config - ft := window.NewSimpleFaultTracker(stor, idx, pc.ParallelCheckLimit, time.Duration(pc.SingleCheckTimeout), time.Duration(pc.PartitionCheckTimeout)) - - computeTask, err := window.NewWdPostTask(db, api, ft, lw, verif, chainSched, addresses, max) - if err != nil { - return nil, nil, nil, err - } - - submitTask, err := window.NewWdPostSubmitTask(chainSched, sender, db, api, fc.MaxWindowPoStGasFee, as) - if err != nil { - return nil, nil, nil, err - } - - recoverTask, err := window.NewWdPostRecoverDeclareTask(sender, db, api, ft, as, chainSched, fc.MaxWindowPoStGasFee, addresses) - if err != nil { - return nil, nil, nil, err - } - - return computeTask, submitTask, recoverTask, nil -} diff --git a/curiosrc/chainsched/chain_sched.go b/curiosrc/chainsched/chain_sched.go deleted file mode 100644 index 42a387fbc..000000000 --- a/curiosrc/chainsched/chain_sched.go +++ /dev/null @@ -1,136 +0,0 @@ -package chainsched - -import ( - "context" - "time" - - logging "github.com/ipfs/go-log/v2" - "go.opencensus.io/trace" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/store" - "github.com/filecoin-project/lotus/chain/types" -) - -var log = logging.Logger("curio/chainsched") - -type NodeAPI interface { - ChainHead(context.Context) (*types.TipSet, error) - ChainNotify(context.Context) (<-chan []*api.HeadChange, error) -} - -type CurioChainSched struct { - api NodeAPI - - callbacks []UpdateFunc - started bool -} - -func New(api NodeAPI) *CurioChainSched { - return &CurioChainSched{ - api: api, - } -} - -type UpdateFunc func(ctx context.Context, revert, apply *types.TipSet) error - -func (s *CurioChainSched) AddHandler(ch UpdateFunc) error { - if s.started { - return xerrors.Errorf("cannot add handler after start") - } - - s.callbacks = append(s.callbacks, ch) - return nil -} - -func (s *CurioChainSched) Run(ctx context.Context) { - s.started = true - - var ( - notifs <-chan []*api.HeadChange - err error - gotCur bool - ) - - // not fine to panic after this point - for { - if notifs == nil { - notifs, err = s.api.ChainNotify(ctx) - if err != nil { - log.Errorf("ChainNotify error: %+v", err) - - build.Clock.Sleep(10 * time.Second) - continue - } - - gotCur = false - log.Info("restarting chain scheduler") - } - - select { - case changes, ok := <-notifs: - if !ok { - log.Warn("chain notifs channel closed") - notifs = nil - continue - } - - if !gotCur { - if len(changes) != 1 { - log.Errorf("expected first notif to have len = 1") - continue - } - chg := changes[0] - if chg.Type != store.HCCurrent { - log.Errorf("expected first notif to tell current ts") - continue - } - - ctx, span := trace.StartSpan(ctx, "CurioChainSched.headChange") - - s.update(ctx, nil, chg.Val) - - span.End() - gotCur = true - continue - } - - ctx, span := trace.StartSpan(ctx, "CurioChainSched.headChange") - - var lowest, highest *types.TipSet = nil, nil - - for _, change := range changes { - if change.Val == nil { - log.Errorf("change.Val was nil") - } - switch change.Type { - case store.HCRevert: - lowest = change.Val - case store.HCApply: - highest = change.Val - } - } - - s.update(ctx, lowest, highest) - - span.End() - case <-ctx.Done(): - return - } - } -} - -func (s *CurioChainSched) update(ctx context.Context, revert, apply *types.TipSet) { - if apply == nil { - log.Error("no new tipset in CurioChainSched.update") - return - } - - for _, ch := range s.callbacks { - if err := ch(ctx, revert, apply); err != nil { - log.Errorf("handling head updates in curio chain sched: %+v", err) - } - } -} diff --git a/curiosrc/ffi/piece_funcs.go b/curiosrc/ffi/piece_funcs.go deleted file mode 100644 index a548f5cc2..000000000 --- a/curiosrc/ffi/piece_funcs.go +++ /dev/null @@ -1,76 +0,0 @@ -package ffi - -import ( - "context" - "io" - "os" - "time" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -func (sb *SealCalls) WritePiece(ctx context.Context, taskID *harmonytask.TaskID, pieceID storiface.PieceNumber, size int64, data io.Reader) error { - // todo: config(?): allow setting PathStorage for this - // todo storage reservations - paths, _, done, err := sb.sectors.AcquireSector(ctx, taskID, pieceID.Ref(), storiface.FTNone, storiface.FTPiece, storiface.PathSealing) - if err != nil { - return err - } - defer done() - - dest := paths.Piece - tempDest := dest + ".tmp" - - destFile, err := os.OpenFile(tempDest, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) - if err != nil { - return xerrors.Errorf("creating temp piece file '%s': %w", tempDest, err) - } - - removeTemp := true - defer func() { - if removeTemp { - rerr := os.Remove(tempDest) - if rerr != nil { - log.Errorf("removing temp file: %+v", rerr) - } - } - }() - - copyStart := time.Now() - - n, err := io.CopyBuffer(destFile, io.LimitReader(data, size), make([]byte, 8<<20)) - if err != nil { - _ = destFile.Close() - return xerrors.Errorf("copying piece data: %w", err) - } - - if err := destFile.Close(); err != nil { - return xerrors.Errorf("closing temp piece file: %w", err) - } - - if n != size { - return xerrors.Errorf("short write: %d", n) - } - - copyEnd := time.Now() - - log.Infow("wrote parked piece", "piece", pieceID, "size", size, "duration", copyEnd.Sub(copyStart), "dest", dest, "MiB/s", float64(size)/(1<<20)/copyEnd.Sub(copyStart).Seconds()) - - if err := os.Rename(tempDest, dest); err != nil { - return xerrors.Errorf("rename temp piece to dest %s -> %s: %w", tempDest, dest, err) - } - - removeTemp = false - return nil -} - -func (sb *SealCalls) PieceReader(ctx context.Context, id storiface.PieceNumber) (io.ReadCloser, error) { - return sb.sectors.storage.ReaderSeq(ctx, id.Ref(), storiface.FTPiece) -} - -func (sb *SealCalls) RemovePiece(ctx context.Context, id storiface.PieceNumber) error { - return sb.sectors.storage.Remove(ctx, id.Ref().ID, storiface.FTPiece, true, nil) -} diff --git a/curiosrc/ffi/sdr_funcs.go b/curiosrc/ffi/sdr_funcs.go deleted file mode 100644 index e9ce62831..000000000 --- a/curiosrc/ffi/sdr_funcs.go +++ /dev/null @@ -1,627 +0,0 @@ -package ffi - -import ( - "context" - "encoding/json" - "fmt" - "io" - "os" - "path/filepath" - - "github.com/KarpelesLab/reflink" - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - "github.com/puzpuzpuz/xsync/v2" - "golang.org/x/xerrors" - - ffi "github.com/filecoin-project/filecoin-ffi" - commcid "github.com/filecoin-project/go-fil-commcid" - "github.com/filecoin-project/go-state-types/abi" - proof2 "github.com/filecoin-project/go-state-types/proof" - - "github.com/filecoin-project/lotus/curiosrc/proof" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/storage/paths" - "github.com/filecoin-project/lotus/storage/sealer/proofpaths" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -var log = logging.Logger("lpffi") - -/* -type ExternPrecommit2 func(ctx context.Context, sector storiface.SectorRef, cache, sealed string, pc1out storiface.PreCommit1Out) (sealedCID cid.Cid, unsealedCID cid.Cid, err error) - - type ExternalSealer struct { - PreCommit2 ExternPrecommit2 - } -*/ -type SealCalls struct { - sectors *storageProvider - - /*// externCalls cointain overrides for calling alternative sealing logic - externCalls ExternalSealer*/ -} - -func NewSealCalls(st *paths.Remote, ls *paths.Local, si paths.SectorIndex) *SealCalls { - return &SealCalls{ - sectors: &storageProvider{ - storage: st, - localStore: ls, - sindex: si, - storageReservations: xsync.NewIntegerMapOf[harmonytask.TaskID, *StorageReservation](), - }, - } -} - -type storageProvider struct { - storage *paths.Remote - localStore *paths.Local - sindex paths.SectorIndex - storageReservations *xsync.MapOf[harmonytask.TaskID, *StorageReservation] -} - -func (l *storageProvider) AcquireSector(ctx context.Context, taskID *harmonytask.TaskID, sector storiface.SectorRef, existing, allocate storiface.SectorFileType, sealing storiface.PathType) (fspaths, ids storiface.SectorPaths, release func(), err error) { - var paths, storageIDs storiface.SectorPaths - var releaseStorage func() - - var ok bool - var resv *StorageReservation - if taskID != nil { - resv, ok = l.storageReservations.Load(*taskID) - } - if ok && resv != nil { - if resv.Alloc != allocate || resv.Existing != existing { - // this should never happen, only when task definition is wrong - return storiface.SectorPaths{}, storiface.SectorPaths{}, nil, xerrors.Errorf("storage reservation type mismatch") - } - - log.Debugw("using existing storage reservation", "task", taskID, "sector", sector, "existing", existing, "allocate", allocate) - - paths = resv.Paths - storageIDs = resv.PathIDs - releaseStorage = resv.Release - - if len(existing.AllSet()) > 0 { - // there are some "existing" files in the reservation. Some of them may need fetching, so call l.storage.AcquireSector - // (which unlike in the reservation code will be called on the paths.Remote instance) to ensure that the files are - // present locally. Note that we do not care about 'allocate' reqeuests, those files don't exist, and are just - // proposed paths with a reservation of space. - - _, checkPathIDs, err := l.storage.AcquireSector(ctx, sector, existing, storiface.FTNone, sealing, storiface.AcquireMove, storiface.AcquireInto(storiface.PathsWithIDs{Paths: paths, IDs: storageIDs})) - if err != nil { - return storiface.SectorPaths{}, storiface.SectorPaths{}, nil, xerrors.Errorf("acquire reserved existing files: %w", err) - } - - // assert that checkPathIDs is the same as storageIDs - if storageIDs.Subset(existing) != checkPathIDs.Subset(existing) { - return storiface.SectorPaths{}, storiface.SectorPaths{}, nil, xerrors.Errorf("acquire reserved existing files: pathIDs mismatch %#v != %#v", storageIDs, checkPathIDs) - } - } - } else { - // No related reservation, acquire storage as usual - - var err error - paths, storageIDs, err = l.storage.AcquireSector(ctx, sector, existing, allocate, sealing, storiface.AcquireMove) - if err != nil { - return storiface.SectorPaths{}, storiface.SectorPaths{}, nil, err - } - - releaseStorage, err = l.localStore.Reserve(ctx, sector, allocate, storageIDs, storiface.FSOverheadSeal) - if err != nil { - return storiface.SectorPaths{}, storiface.SectorPaths{}, nil, xerrors.Errorf("reserving storage space: %w", err) - } - } - - log.Debugf("acquired sector %d (e:%d; a:%d): %v", sector, existing, allocate, paths) - - return paths, storageIDs, func() { - releaseStorage() - - for _, fileType := range storiface.PathTypes { - if fileType&allocate == 0 { - continue - } - - sid := storiface.PathByType(storageIDs, fileType) - if err := l.sindex.StorageDeclareSector(ctx, storiface.ID(sid), sector.ID, fileType, true); err != nil { - log.Errorf("declare sector error: %+v", err) - } - } - }, nil -} - -func (sb *SealCalls) GenerateSDR(ctx context.Context, taskID harmonytask.TaskID, sector storiface.SectorRef, ticket abi.SealRandomness, commKcid cid.Cid) error { - paths, pathIDs, releaseSector, err := sb.sectors.AcquireSector(ctx, &taskID, sector, storiface.FTNone, storiface.FTCache, storiface.PathSealing) - if err != nil { - return xerrors.Errorf("acquiring sector paths: %w", err) - } - defer releaseSector() - - // prepare SDR params - commp, err := commcid.CIDToDataCommitmentV1(commKcid) - if err != nil { - return xerrors.Errorf("computing commK: %w", err) - } - - replicaID, err := sector.ProofType.ReplicaId(sector.ID.Miner, sector.ID.Number, ticket, commp) - if err != nil { - return xerrors.Errorf("computing replica id: %w", err) - } - - // make sure the cache dir is empty - if err := os.RemoveAll(paths.Cache); err != nil { - return xerrors.Errorf("removing cache dir: %w", err) - } - if err := os.MkdirAll(paths.Cache, 0755); err != nil { - return xerrors.Errorf("mkdir cache dir: %w", err) - } - - // generate new sector key - err = ffi.GenerateSDR( - sector.ProofType, - paths.Cache, - replicaID, - ) - if err != nil { - return xerrors.Errorf("generating SDR %d (%s): %w", sector.ID.Number, paths.Unsealed, err) - } - - if err := sb.ensureOneCopy(ctx, sector.ID, pathIDs, storiface.FTCache); err != nil { - return xerrors.Errorf("ensure one copy: %w", err) - } - - return nil -} - -// ensureOneCopy makes sure that there is only one version of sector data. -// Usually called after a successful operation was done successfully on sector data. -func (sb *SealCalls) ensureOneCopy(ctx context.Context, sid abi.SectorID, pathIDs storiface.SectorPaths, fts storiface.SectorFileType) error { - if !pathIDs.HasAllSet(fts) { - return xerrors.Errorf("ensure one copy: not all paths are set") - } - - for _, fileType := range fts.AllSet() { - pid := storiface.PathByType(pathIDs, fileType) - keepIn := []storiface.ID{storiface.ID(pid)} - - log.Debugw("ensureOneCopy", "sector", sid, "type", fileType, "keep", keepIn) - - if err := sb.sectors.storage.Remove(ctx, sid, fileType, true, keepIn); err != nil { - return err - } - } - - return nil -} - -func (sb *SealCalls) TreeDRC(ctx context.Context, task *harmonytask.TaskID, sector storiface.SectorRef, unsealed cid.Cid, size abi.PaddedPieceSize, data io.Reader, unpaddedData bool) (scid cid.Cid, ucid cid.Cid, err error) { - p1o, err := sb.makePhase1Out(unsealed, sector.ProofType) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("make phase1 output: %w", err) - } - - paths, pathIDs, releaseSector, err := sb.sectors.AcquireSector(ctx, task, sector, storiface.FTCache, storiface.FTSealed, storiface.PathSealing) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("acquiring sector paths: %w", err) - } - defer releaseSector() - - defer func() { - if err != nil { - clerr := removeDRCTrees(paths.Cache) - if clerr != nil { - log.Errorw("removing tree files after TreeDRC error", "error", clerr, "exec-error", err, "sector", sector, "cache", paths.Cache) - } - } - }() - - treeDUnsealed, err := proof.BuildTreeD(data, unpaddedData, filepath.Join(paths.Cache, proofpaths.TreeDName), size) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("building tree-d: %w", err) - } - - if treeDUnsealed != unsealed { - return cid.Undef, cid.Undef, xerrors.Errorf("tree-d cid mismatch with supplied unsealed cid") - } - - { - // create sector-sized file at paths.Sealed; PC2 transforms it into a sealed sector in-place - ssize, err := sector.ProofType.SectorSize() - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("getting sector size: %w", err) - } - - { - // copy TreeD prefix to sealed sector, SealPreCommitPhase2 will mutate it in place into the sealed sector - - // first try reflink + truncate, that should be way faster - err := reflink.Always(filepath.Join(paths.Cache, proofpaths.TreeDName), paths.Sealed) - if err == nil { - err = os.Truncate(paths.Sealed, int64(ssize)) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("truncating reflinked sealed file: %w", err) - } - } else { - log.Errorw("reflink treed -> sealed failed, falling back to slow copy, use single scratch btrfs or xfs filesystem", "error", err, "sector", sector, "cache", paths.Cache, "sealed", paths.Sealed) - - // fallback to slow copy, copy ssize bytes from treed to sealed - dst, err := os.OpenFile(paths.Sealed, os.O_WRONLY|os.O_CREATE, 0644) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("opening sealed sector file: %w", err) - } - src, err := os.Open(filepath.Join(paths.Cache, proofpaths.TreeDName)) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("opening treed sector file: %w", err) - } - - _, err = io.CopyN(dst, src, int64(ssize)) - derr := dst.Close() - _ = src.Close() - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("copying treed -> sealed: %w", err) - } - if derr != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("closing sealed file: %w", derr) - } - } - } - } - - sl, uns, err := ffi.SealPreCommitPhase2(p1o, paths.Cache, paths.Sealed) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("computing seal proof: %w", err) - } - - if uns != unsealed { - return cid.Undef, cid.Undef, xerrors.Errorf("unsealed cid changed after sealing") - } - - if err := sb.ensureOneCopy(ctx, sector.ID, pathIDs, storiface.FTCache|storiface.FTSealed); err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("ensure one copy: %w", err) - } - - return sl, uns, nil -} - -func removeDRCTrees(cache string) error { - // list files in cache - files, err := os.ReadDir(cache) - if err != nil { - return xerrors.Errorf("listing cache: %w", err) - } - - for _, file := range files { - if proofpaths.IsTreeFile(file.Name()) { - err := os.Remove(filepath.Join(cache, file.Name())) - if err != nil { - return xerrors.Errorf("removing tree file: %w", err) - } - } - } - - return nil -} - -func (sb *SealCalls) GenerateSynthPoRep() { - panic("todo") -} - -func (sb *SealCalls) PoRepSnark(ctx context.Context, sn storiface.SectorRef, sealed, unsealed cid.Cid, ticket abi.SealRandomness, seed abi.InteractiveSealRandomness) ([]byte, error) { - vproof, err := sb.sectors.storage.GeneratePoRepVanillaProof(ctx, sn, sealed, unsealed, ticket, seed) - if err != nil { - return nil, xerrors.Errorf("failed to generate vanilla proof: %w", err) - } - - proof, err := ffi.SealCommitPhase2(vproof, sn.ID.Number, sn.ID.Miner) - if err != nil { - return nil, xerrors.Errorf("computing seal proof failed: %w", err) - } - - ok, err := ffi.VerifySeal(proof2.SealVerifyInfo{ - SealProof: sn.ProofType, - SectorID: sn.ID, - DealIDs: nil, - Randomness: ticket, - InteractiveRandomness: seed, - Proof: proof, - SealedCID: sealed, - UnsealedCID: unsealed, - }) - if err != nil { - return nil, xerrors.Errorf("failed to verify proof: %w", err) - } - if !ok { - return nil, xerrors.Errorf("porep failed to validate") - } - - return proof, nil -} - -func (sb *SealCalls) makePhase1Out(unsCid cid.Cid, spt abi.RegisteredSealProof) ([]byte, error) { - commd, err := commcid.CIDToDataCommitmentV1(unsCid) - if err != nil { - return nil, xerrors.Errorf("make uns cid: %w", err) - } - - type Config struct { - ID string `json:"id"` - Path string `json:"path"` - RowsToDiscard int `json:"rows_to_discard"` - Size int `json:"size"` - } - - type Labels struct { - H *string `json:"_h"` // proofs want this.. - Labels []Config `json:"labels"` - } - - var phase1Output struct { - CommD [32]byte `json:"comm_d"` - Config Config `json:"config"` // TreeD - Labels map[string]*Labels `json:"labels"` - RegisteredProof string `json:"registered_proof"` - } - - copy(phase1Output.CommD[:], commd) - - phase1Output.Config.ID = "tree-d" - phase1Output.Config.Path = "/placeholder" - phase1Output.Labels = map[string]*Labels{} - - switch spt { - case abi.RegisteredSealProof_StackedDrg2KiBV1_1, abi.RegisteredSealProof_StackedDrg2KiBV1_1_Feat_SyntheticPoRep: - phase1Output.Config.RowsToDiscard = 0 - phase1Output.Config.Size = 127 - phase1Output.Labels["StackedDrg2KiBV1"] = &Labels{} - phase1Output.RegisteredProof = "StackedDrg2KiBV1_1" - - for i := 0; i < 2; i++ { - phase1Output.Labels["StackedDrg2KiBV1"].Labels = append(phase1Output.Labels["StackedDrg2KiBV1"].Labels, Config{ - ID: fmt.Sprintf("layer-%d", i+1), - Path: "/placeholder", - RowsToDiscard: 0, - Size: 64, - }) - } - - case abi.RegisteredSealProof_StackedDrg8MiBV1_1, abi.RegisteredSealProof_StackedDrg8MiBV1_1_Feat_SyntheticPoRep: - phase1Output.Config.RowsToDiscard = 0 - phase1Output.Config.Size = 524287 - phase1Output.Labels["StackedDrg8MiBV1"] = &Labels{} - phase1Output.RegisteredProof = "StackedDrg8MiBV1_1" - - for i := 0; i < 2; i++ { - phase1Output.Labels["StackedDrg8MiBV1"].Labels = append(phase1Output.Labels["StackedDrg8MiBV1"].Labels, Config{ - ID: fmt.Sprintf("layer-%d", i+1), - Path: "/placeholder", - RowsToDiscard: 0, - Size: 262144, - }) - } - - case abi.RegisteredSealProof_StackedDrg512MiBV1_1: - phase1Output.Config.RowsToDiscard = 0 - phase1Output.Config.Size = 33554431 - phase1Output.Labels["StackedDrg512MiBV1"] = &Labels{} - phase1Output.RegisteredProof = "StackedDrg512MiBV1_1" - - for i := 0; i < 2; i++ { - phase1Output.Labels["StackedDrg512MiBV1"].Labels = append(phase1Output.Labels["StackedDrg512MiBV1"].Labels, Config{ - ID: fmt.Sprintf("layer-%d", i+1), - Path: "placeholder", - RowsToDiscard: 0, - Size: 16777216, - }) - } - - case abi.RegisteredSealProof_StackedDrg32GiBV1_1: - phase1Output.Config.RowsToDiscard = 0 - phase1Output.Config.Size = 2147483647 - phase1Output.Labels["StackedDrg32GiBV1"] = &Labels{} - phase1Output.RegisteredProof = "StackedDrg32GiBV1_1" - - for i := 0; i < 11; i++ { - phase1Output.Labels["StackedDrg32GiBV1"].Labels = append(phase1Output.Labels["StackedDrg32GiBV1"].Labels, Config{ - ID: fmt.Sprintf("layer-%d", i+1), - Path: "/placeholder", - RowsToDiscard: 0, - Size: 1073741824, - }) - } - - case abi.RegisteredSealProof_StackedDrg64GiBV1_1: - phase1Output.Config.RowsToDiscard = 0 - phase1Output.Config.Size = 4294967295 - phase1Output.Labels["StackedDrg64GiBV1"] = &Labels{} - phase1Output.RegisteredProof = "StackedDrg64GiBV1_1" - - for i := 0; i < 11; i++ { - phase1Output.Labels["StackedDrg64GiBV1"].Labels = append(phase1Output.Labels["StackedDrg64GiBV1"].Labels, Config{ - ID: fmt.Sprintf("layer-%d", i+1), - Path: "/placeholder", - RowsToDiscard: 0, - Size: 2147483648, - }) - } - - default: - panic("proof type not handled") - } - - return json.Marshal(phase1Output) -} - -func (sb *SealCalls) LocalStorage(ctx context.Context) ([]storiface.StoragePath, error) { - return sb.sectors.localStore.Local(ctx) -} - -func (sb *SealCalls) FinalizeSector(ctx context.Context, sector storiface.SectorRef, keepUnsealed bool) error { - alloc := storiface.FTNone - if keepUnsealed { - // note: In Curio we don't write the unsealed file in any of the previous stages, it's only written here from tree-d - alloc = storiface.FTUnsealed - } - - sectorPaths, pathIDs, releaseSector, err := sb.sectors.AcquireSector(ctx, nil, sector, storiface.FTCache, alloc, storiface.PathSealing) - if err != nil { - return xerrors.Errorf("acquiring sector paths: %w", err) - } - defer releaseSector() - - ssize, err := sector.ProofType.SectorSize() - if err != nil { - return xerrors.Errorf("getting sector size: %w", err) - } - - if keepUnsealed { - // tree-d contains exactly unsealed data in the prefix, so - // * we move it to a temp file - // * we truncate the temp file to the sector size - // * we move the temp file to the unsealed location - - // temp path in cache where we'll move tree-d before truncating - // it is in the cache directory so that we can use os.Rename to move it - // to unsealed (which may be on a different filesystem) - tempUnsealed := filepath.Join(sectorPaths.Cache, storiface.SectorName(sector.ID)) - - _, terr := os.Stat(tempUnsealed) - tempUnsealedExists := terr == nil - - // First handle an edge case where we have already gone through this step, - // but ClearCache or later steps failed. In that case we'll see tree-d missing and unsealed present - - if _, err := os.Stat(filepath.Join(sectorPaths.Cache, proofpaths.TreeDName)); err != nil { - if os.IsNotExist(err) { - // check that unsealed exists and is the right size - st, err := os.Stat(sectorPaths.Unsealed) - if err != nil { - if os.IsNotExist(err) { - if tempUnsealedExists { - // unsealed file does not exist, but temp unsealed file does - // so we can just resume where the previous attempt left off - goto retryUnsealedMove - } - return xerrors.Errorf("neither unsealed file nor temp-unsealed file exists") - } - return xerrors.Errorf("stat unsealed file: %w", err) - } - if st.Size() != int64(ssize) { - if tempUnsealedExists { - // unsealed file exists but is the wrong size, and temp unsealed file exists - // so we can just resume where the previous attempt left off with some cleanup - - if err := os.Remove(sectorPaths.Unsealed); err != nil { - return xerrors.Errorf("removing unsealed file from last attempt: %w", err) - } - - goto retryUnsealedMove - } - return xerrors.Errorf("unsealed file is not the right size: %d != %d and temp unsealed is missing", st.Size(), ssize) - } - - // all good, just log that this edge case happened - log.Warnw("unsealed file exists but tree-d is missing, skipping move", "sector", sector.ID, "unsealed", sectorPaths.Unsealed, "cache", sectorPaths.Cache) - goto afterUnsealedMove - } - return xerrors.Errorf("stat tree-d file: %w", err) - } - - // If the state in clean do the move - - // move tree-d to temp file - if err := os.Rename(filepath.Join(sectorPaths.Cache, proofpaths.TreeDName), tempUnsealed); err != nil { - return xerrors.Errorf("moving tree-d to temp file: %w", err) - } - - retryUnsealedMove: - - // truncate sealed file to sector size - if err := os.Truncate(tempUnsealed, int64(ssize)); err != nil { - return xerrors.Errorf("truncating unsealed file to sector size: %w", err) - } - - // move temp file to unsealed location - if err := paths.Move(tempUnsealed, sectorPaths.Unsealed); err != nil { - return xerrors.Errorf("move temp unsealed sector to final location (%s -> %s): %w", tempUnsealed, sectorPaths.Unsealed, err) - } - } - -afterUnsealedMove: - if err := ffi.ClearCache(uint64(ssize), sectorPaths.Cache); err != nil { - return xerrors.Errorf("clearing cache: %w", err) - } - - if err := sb.ensureOneCopy(ctx, sector.ID, pathIDs, storiface.FTCache|alloc); err != nil { - return xerrors.Errorf("ensure one copy: %w", err) - } - - return nil -} - -func (sb *SealCalls) MoveStorage(ctx context.Context, sector storiface.SectorRef, taskID *harmonytask.TaskID) error { - // only move the unsealed file if it still exists and needs moving - moveUnsealed := storiface.FTUnsealed - { - found, unsealedPathType, err := sb.sectorStorageType(ctx, sector, storiface.FTUnsealed) - if err != nil { - return xerrors.Errorf("checking cache storage type: %w", err) - } - - if !found || unsealedPathType == storiface.PathStorage { - moveUnsealed = storiface.FTNone - } - } - - toMove := storiface.FTCache | storiface.FTSealed | moveUnsealed - - var opts []storiface.AcquireOption - if taskID != nil { - resv, ok := sb.sectors.storageReservations.Load(*taskID) - // if the reservation is missing MoveStorage will simply create one internally. This is fine as the reservation - // will only be missing when the node is restarting, which means that the missing reservations will get recreated - // anyways, and before we start claiming other tasks. - if ok { - defer resv.Release() - - if resv.Alloc != storiface.FTNone { - return xerrors.Errorf("task %d has storage reservation with alloc", taskID) - } - if resv.Existing != toMove|storiface.FTUnsealed { - return xerrors.Errorf("task %d has storage reservation with different existing", taskID) - } - - opts = append(opts, storiface.AcquireInto(storiface.PathsWithIDs{Paths: resv.Paths, IDs: resv.PathIDs})) - } - } - - err := sb.sectors.storage.MoveStorage(ctx, sector, toMove, opts...) - if err != nil { - return xerrors.Errorf("moving storage: %w", err) - } - - for _, fileType := range toMove.AllSet() { - if err := sb.sectors.storage.RemoveCopies(ctx, sector.ID, fileType); err != nil { - return xerrors.Errorf("rm copies (t:%s, s:%v): %w", fileType, sector, err) - } - } - - return nil -} - -func (sb *SealCalls) sectorStorageType(ctx context.Context, sector storiface.SectorRef, ft storiface.SectorFileType) (sectorFound bool, ptype storiface.PathType, err error) { - stores, err := sb.sectors.sindex.StorageFindSector(ctx, sector.ID, ft, 0, false) - if err != nil { - return false, "", xerrors.Errorf("finding sector: %w", err) - } - if len(stores) == 0 { - return false, "", nil - } - - for _, store := range stores { - if store.CanSeal { - return true, storiface.PathSealing, nil - } - } - - return true, storiface.PathStorage, nil -} diff --git a/curiosrc/ffi/task_storage.go b/curiosrc/ffi/task_storage.go deleted file mode 100644 index f01a472fa..000000000 --- a/curiosrc/ffi/task_storage.go +++ /dev/null @@ -1,231 +0,0 @@ -package ffi - -import ( - "context" - "sync" - "time" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - storagePaths "github.com/filecoin-project/lotus/storage/paths" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -type SectorRef struct { - SpID int64 `db:"sp_id"` - SectorNumber int64 `db:"sector_number"` - RegSealProof abi.RegisteredSealProof `db:"reg_seal_proof"` -} - -func (sr SectorRef) ID() abi.SectorID { - return abi.SectorID{ - Miner: abi.ActorID(sr.SpID), - Number: abi.SectorNumber(sr.SectorNumber), - } -} - -func (sr SectorRef) Ref() storiface.SectorRef { - return storiface.SectorRef{ - ID: sr.ID(), - ProofType: sr.RegSealProof, - } -} - -type TaskStorage struct { - sc *SealCalls - - alloc, existing storiface.SectorFileType - ssize abi.SectorSize - pathType storiface.PathType - - taskToSectorRef func(taskID harmonytask.TaskID) (SectorRef, error) -} - -type ReleaseStorageFunc func() // free storage reservation - -type StorageReservation struct { - SectorRef SectorRef - Release ReleaseStorageFunc - Paths storiface.SectorPaths - PathIDs storiface.SectorPaths - - Alloc, Existing storiface.SectorFileType -} - -func (sb *SealCalls) Storage(taskToSectorRef func(taskID harmonytask.TaskID) (SectorRef, error), alloc, existing storiface.SectorFileType, ssize abi.SectorSize, pathType storiface.PathType) *TaskStorage { - return &TaskStorage{ - sc: sb, - alloc: alloc, - existing: existing, - ssize: ssize, - pathType: pathType, - taskToSectorRef: taskToSectorRef, - } -} - -func (t *TaskStorage) HasCapacity() bool { - ctx := context.Background() - - paths, err := t.sc.sectors.sindex.StorageBestAlloc(ctx, t.alloc, t.ssize, t.pathType, storagePaths.NoMinerFilter) - if err != nil { - log.Errorf("finding best alloc in HasCapacity: %+v", err) - return false - } - - local, err := t.sc.sectors.localStore.Local(ctx) - if err != nil { - log.Errorf("getting local storage: %+v", err) - return false - } - - for _, path := range paths { - if t.pathType == storiface.PathStorage && !path.CanStore { - continue // we want to store, and this isn't a store path - } - if t.pathType == storiface.PathSealing && !path.CanSeal { - continue // we want to seal, and this isn't a seal path - } - - // check if this path is on this node - var found bool - for _, storagePath := range local { - if storagePath.ID == path.ID { - found = true - break - } - } - if !found { - // this path isn't on this node - continue - } - - // StorageBestAlloc already checks that there is enough space; Not atomic like reserving space, but it's - // good enough for HasCapacity - return true - } - - return false // no path found -} - -func (t *TaskStorage) Claim(taskID int) error { - // TaskStorage Claim Attempts to reserve storage for the task - // A: Create a reservation for files to be allocated - // B: Create a reservation for existing files to be fetched into local storage - // C: Create a reservation for existing files in local storage which may be extended (e.g. sector cache when computing Trees) - - ctx := context.Background() - - sectorRef, err := t.taskToSectorRef(harmonytask.TaskID(taskID)) - if err != nil { - return xerrors.Errorf("getting sector ref: %w", err) - } - - // storage writelock sector - lkctx, cancel := context.WithCancel(ctx) - - requestedTypes := t.alloc | t.existing - - lockAcquireTimuout := time.Second * 10 - lockAcquireTimer := time.NewTimer(lockAcquireTimuout) - - go func() { - defer cancel() - - select { - case <-lockAcquireTimer.C: - case <-ctx.Done(): - } - }() - - if err := t.sc.sectors.sindex.StorageLock(lkctx, sectorRef.ID(), storiface.FTNone, requestedTypes); err != nil { - // timer will expire - return xerrors.Errorf("claim StorageLock: %w", err) - } - - if !lockAcquireTimer.Stop() { - // timer expired, so lkctx is done, and that means the lock was acquired and dropped.. - return xerrors.Errorf("failed to acquire lock") - } - defer func() { - // make sure we release the sector lock - lockAcquireTimer.Reset(0) - }() - - // First see what we have locally. We are putting allocate and existing together because local acquire will look - // for existing files for allocate requests, separately existing files which aren't found locally will be need to - // be fetched, so we will need to create reservations for that too. - // NOTE localStore.AcquireSector does not open or create any files, nor does it reserve space. It only proposes - // paths to be used. - pathsFs, pathIDs, err := t.sc.sectors.localStore.AcquireSector(ctx, sectorRef.Ref(), storiface.FTNone, requestedTypes, t.pathType, storiface.AcquireMove) - if err != nil { - return err - } - - // reserve the space - release, err := t.sc.sectors.localStore.Reserve(ctx, sectorRef.Ref(), requestedTypes, pathIDs, storiface.FSOverheadSeal) - if err != nil { - return err - } - - var releaseOnce sync.Once - releaseFunc := func() { - releaseOnce.Do(release) - } - - sres := &StorageReservation{ - SectorRef: sectorRef, - Release: releaseFunc, - Paths: pathsFs, - PathIDs: pathIDs, - - Alloc: t.alloc, - Existing: t.existing, - } - - t.sc.sectors.storageReservations.Store(harmonytask.TaskID(taskID), sres) - - log.Debugw("claimed storage", "task_id", taskID, "sector", sectorRef.ID(), "paths", pathsFs) - - // note: we drop the sector writelock on return; THAT IS INTENTIONAL, this code runs in CanAccept, which doesn't - // guarantee that the work for this sector will happen on this node; SDR CanAccept just ensures that the node can - // run the job, harmonytask is what ensures that only one SDR runs at a time - return nil -} - -func (t *TaskStorage) MarkComplete(taskID int) error { - // MarkComplete is ALWAYS called after the task is done or not scheduled - // If Claim is called and returns without errors, MarkComplete with the same - // taskID is guaranteed to eventually be called - - sectorRef, err := t.taskToSectorRef(harmonytask.TaskID(taskID)) - if err != nil { - return xerrors.Errorf("getting sector ref: %w", err) - } - - sres, ok := t.sc.sectors.storageReservations.Load(harmonytask.TaskID(taskID)) - if !ok { - return xerrors.Errorf("no reservation found for task %d", taskID) - } - - if sectorRef != sres.SectorRef { - return xerrors.Errorf("reservation sector ref doesn't match task sector ref: %+v != %+v", sectorRef, sres.SectorRef) - } - - log.Debugw("marking storage complete", "task_id", taskID, "sector", sectorRef.ID(), "paths", sres.Paths) - - // remove the reservation - t.sc.sectors.storageReservations.Delete(harmonytask.TaskID(taskID)) - - // release the reservation - sres.Release() - - // note: this only frees the reservation, allocated sectors are declared in AcquireSector which is aware of - // the reservation - return nil -} - -var _ resources.Storage = &TaskStorage{} diff --git a/curiosrc/gc/storage_endpoint_gc.go b/curiosrc/gc/storage_endpoint_gc.go deleted file mode 100644 index 45783f353..000000000 --- a/curiosrc/gc/storage_endpoint_gc.go +++ /dev/null @@ -1,287 +0,0 @@ -package gc - -import ( - "context" - "strings" - "sync" - "time" - - logging "github.com/ipfs/go-log/v2" - "github.com/samber/lo" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/lib/result" - "github.com/filecoin-project/lotus/storage/paths" - "github.com/filecoin-project/lotus/storage/sealer/fsutil" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -var log = logging.Logger("curiogc") - -const StorageEndpointGCInterval = 21 * time.Minute -const StorageEndpointDeadTime = StorageEndpointGCInterval * 6 // ~2h -const MaxParallelEndpointChecks = 32 - -type StorageEndpointGC struct { - si *paths.DBIndex - remote *paths.Remote - db *harmonydb.DB -} - -func NewStorageEndpointGC(si *paths.DBIndex, remote *paths.Remote, db *harmonydb.DB) *StorageEndpointGC { - return &StorageEndpointGC{ - si: si, - remote: remote, - db: db, - } -} - -func (s *StorageEndpointGC) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - /* - 1. Get all storage paths + urls (endpoints) - 2. Ping each url, record results - 3. Update sector_path_url_liveness with success/failure - 4.1 If a URL was consistently down for StorageEndpointDeadTime, remove it from the storage_path table - 4.2 Remove storage paths with no URLs remaining - 4.2.1 in the same transaction remove sector refs to the dead path - */ - - ctx := context.Background() - - var pathRefs []struct { - StorageID storiface.ID `db:"storage_id"` - Urls string `db:"urls"` - LastHeartbeat *time.Time `db:"last_heartbeat"` - } - - err = s.db.Select(ctx, &pathRefs, `SELECT storage_id, urls, last_heartbeat FROM storage_path`) - if err != nil { - return false, xerrors.Errorf("getting path metadata: %w", err) - } - - type pingResult struct { - storageID storiface.ID - url string - - res result.Result[fsutil.FsStat] - } - - var pingResults []pingResult - var resultLk sync.Mutex - var resultThrottle = make(chan struct{}, MaxParallelEndpointChecks) - - for _, pathRef := range pathRefs { - pathRef := pathRef - urls := strings.Split(pathRef.Urls, paths.URLSeparator) - - for _, url := range urls { - url := url - - select { - case resultThrottle <- struct{}{}: - case <-ctx.Done(): - return false, ctx.Err() - } - - go func() { - defer func() { - <-resultThrottle - }() - - st, err := s.remote.StatUrl(ctx, url, pathRef.StorageID) - - res := pingResult{ - storageID: pathRef.StorageID, - url: url, - res: result.Wrap(st, err), - } - - resultLk.Lock() - pingResults = append(pingResults, res) - resultLk.Unlock() - }() - } - } - - // Wait for all pings to finish - for i := 0; i < MaxParallelEndpointChecks; i++ { - select { - case resultThrottle <- struct{}{}: - case <-ctx.Done(): - return false, ctx.Err() - } - } - - // Update the liveness table - - /* - create table sector_path_url_liveness ( - storage_id text, - url text, - - last_checked timestamp not null, - last_live timestamp, - last_dead timestamp, - last_dead_reason text, - - primary key (storage_id, url), - - foreign key (storage_id) references storage_path (storage_id) on delete cascade - ) - */ - - currentTime := time.Now().UTC() - - committed, err := s.db.BeginTransaction(ctx, func(tx *harmonydb.Tx) (bool, error) { - for _, pingResult := range pingResults { - var lastLive, lastDead, lastDeadReason interface{} - if pingResult.res.Error == nil { - lastLive = currentTime.UTC() - lastDead = nil - lastDeadReason = nil - } else { - lastLive = nil - lastDead = currentTime.UTC() - lastDeadReason = pingResult.res.Error.Error() - } - - // This function updates the liveness data for a URL in the `sector_path_url_liveness` table. - // - // On conflict, where the same `storage_id` and `url` are found: - // - last_checked is always updated to the current timestamp. - // - last_live is updated to the new `last_live` if it is not null; otherwise, it retains the existing value. - // - last_dead is conditionally updated based on two criteria: - // 1. It is set to the new `last_dead` if the existing `last_dead` is null (indicating this is the first recorded failure). - // 2. It is updated to the new `last_dead` if there has been a live instance recorded after the most recent dead timestamp, indicating the resource was alive again before this new failure. - // 3. It retains the existing value if none of the above conditions are met. - // - last_dead_reason is updated similarly to `last_live`, using COALESCE to prefer the new reason if it's provided. - _, err := tx.Exec(` - INSERT INTO sector_path_url_liveness (storage_id, url, last_checked, last_live, last_dead, last_dead_reason) - VALUES ($1, $2, $3, $4, $5, $6) - ON CONFLICT (storage_id, url) DO UPDATE - SET last_checked = EXCLUDED.last_checked, - last_live = COALESCE(EXCLUDED.last_live, sector_path_url_liveness.last_live), - last_dead = CASE - WHEN sector_path_url_liveness.last_dead IS NULL THEN EXCLUDED.last_dead - WHEN sector_path_url_liveness.last_dead IS NOT NULL AND sector_path_url_liveness.last_live > sector_path_url_liveness.last_dead THEN EXCLUDED.last_dead - ELSE sector_path_url_liveness.last_dead - END, - last_dead_reason = COALESCE(EXCLUDED.last_dead_reason, sector_path_url_liveness.last_dead_reason) - `, pingResult.storageID, pingResult.url, currentTime, lastLive, lastDead, lastDeadReason) - if err != nil { - return false, xerrors.Errorf("updating liveness data: %w", err) - } - } - - return true, nil - }, harmonydb.OptionRetry()) - if err != nil { - return false, xerrors.Errorf("sector_path_url_liveness update: %w", err) - } - if !committed { - return false, xerrors.Errorf("sector_path_url_liveness update: transaction didn't commit") - } - - /////// - // Now we do the actual database cleanup - if !stillOwned() { - return false, xerrors.Errorf("task no longer owned") - } - - committed, err = s.db.BeginTransaction(ctx, func(tx *harmonydb.Tx) (bool, error) { - // Identify URLs that are consistently down - var deadURLs []struct { - StorageID storiface.ID - URL string - } - err = tx.Select(&deadURLs, ` - SELECT storage_id, url FROM sector_path_url_liveness - WHERE last_dead > COALESCE(last_live, '1970-01-01') AND last_dead < $1 - `, currentTime.Add(-StorageEndpointDeadTime).UTC()) - if err != nil { - return false, xerrors.Errorf("selecting dead URLs: %w", err) - } - - log.Debugw("dead urls", "dead_urls", deadURLs) - - // Remove dead URLs from storage_path entries and handle path cleanup - for _, du := range deadURLs { - // Fetch the current URLs for the storage path - var URLs string - err = tx.QueryRow("SELECT urls FROM storage_path WHERE storage_id = $1", du.StorageID).Scan(&URLs) - if err != nil { - return false, xerrors.Errorf("fetching storage paths: %w", err) - } - - // Filter out the dead URL using lo.Reject and prepare the updated list - urls := strings.Split(URLs, paths.URLSeparator) - urls = lo.Reject(urls, func(u string, _ int) bool { - return u == du.URL - }) - - log.Debugw("filtered urls", "urls", urls, "dead_url", du.URL, "storage_id", du.StorageID) - - if len(urls) == 0 { - // If no URLs left, remove the storage path entirely - _, err = tx.Exec("DELETE FROM storage_path WHERE storage_id = $1", du.StorageID) - if err != nil { - return false, xerrors.Errorf("deleting storage path: %w", err) - } - _, err = tx.Exec("DELETE FROM sector_location WHERE storage_id = $1", du.StorageID) - if err != nil { - return false, xerrors.Errorf("deleting sector locations: %w", err) - } - } else { - // Update the storage path with the filtered URLs - newURLs := strings.Join(urls, paths.URLSeparator) - _, err = tx.Exec("UPDATE storage_path SET urls = $1 WHERE storage_id = $2", newURLs, du.StorageID) - if err != nil { - return false, xerrors.Errorf("updating storage path urls: %w", err) - } - // Remove sector_path_url_liveness entry - _, err = tx.Exec("DELETE FROM sector_path_url_liveness WHERE storage_id = $1 AND url = $2", du.StorageID, du.URL) - if err != nil { - return false, xerrors.Errorf("deleting sector_path_url_liveness entry: %w", err) - } - } - } - - return true, nil - }, harmonydb.OptionRetry()) - if err != nil { - return false, xerrors.Errorf("removing dead URLs and cleaning storage paths: %w", err) - } - if !committed { - return false, xerrors.Errorf("transaction for removing dead URLs and cleaning paths did not commit") - } - - return true, nil -} - -func (s *StorageEndpointGC) CanAccept(ids []harmonytask.TaskID, engine *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - id := ids[0] - return &id, nil -} - -func (s *StorageEndpointGC) TypeDetails() harmonytask.TaskTypeDetails { - return harmonytask.TaskTypeDetails{ - Max: 1, - Name: "StorageMetaGC", - Cost: resources.Resources{ - Cpu: 1, - Ram: 64 << 20, - Gpu: 0, - }, - IAmBored: harmonytask.SingletonTaskAdder(StorageEndpointGCInterval, s), - } -} - -func (s *StorageEndpointGC) Adder(taskFunc harmonytask.AddTaskFunc) { - // lazy endpoint, added when bored - return -} - -var _ harmonytask.TaskInterface = &StorageEndpointGC{} diff --git a/curiosrc/market/deal_ingest.go b/curiosrc/market/deal_ingest.go deleted file mode 100644 index ea382717a..000000000 --- a/curiosrc/market/deal_ingest.go +++ /dev/null @@ -1,137 +0,0 @@ -package market - -import ( - "context" - "encoding/json" - "net/http" - "net/url" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-bitfield" - "github.com/filecoin-project/go-padreader" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/network" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/curiosrc/seal" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" -) - -type Ingester interface { - AllocatePieceToSector(ctx context.Context, maddr address.Address, piece api.PieceDealInfo, rawSize int64, source url.URL, header http.Header) (api.SectorOffset, error) -} - -type PieceIngesterApi interface { - StateMinerInfo(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error) - StateMinerAllocated(ctx context.Context, a address.Address, key types.TipSetKey) (*bitfield.BitField, error) - StateNetworkVersion(ctx context.Context, key types.TipSetKey) (network.Version, error) -} - -type PieceIngester struct { - db *harmonydb.DB - api PieceIngesterApi -} - -func NewPieceIngester(db *harmonydb.DB, api PieceIngesterApi) *PieceIngester { - return &PieceIngester{db: db, api: api} -} - -func (p *PieceIngester) AllocatePieceToSector(ctx context.Context, maddr address.Address, piece api.PieceDealInfo, rawSize int64, source url.URL, header http.Header) (api.SectorOffset, error) { - mi, err := p.api.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return api.SectorOffset{}, err - } - - if piece.DealProposal.PieceSize != abi.PaddedPieceSize(mi.SectorSize) { - return api.SectorOffset{}, xerrors.Errorf("only full sector pieces supported for now") - } - - // check raw size - if piece.DealProposal.PieceSize != padreader.PaddedSize(uint64(rawSize)).Padded() { - return api.SectorOffset{}, xerrors.Errorf("raw size doesn't match padded piece size") - } - - // add initial piece + to a sector - nv, err := p.api.StateNetworkVersion(ctx, types.EmptyTSK) - if err != nil { - return api.SectorOffset{}, xerrors.Errorf("getting network version: %w", err) - } - - synth := false // todo synthetic porep config - spt, err := miner.PreferredSealProofTypeFromWindowPoStType(nv, mi.WindowPoStProofType, synth) - if err != nil { - return api.SectorOffset{}, xerrors.Errorf("getting seal proof type: %w", err) - } - - mid, err := address.IDFromAddress(maddr) - if err != nil { - return api.SectorOffset{}, xerrors.Errorf("getting miner ID: %w", err) - } - - num, err := seal.AllocateSectorNumbers(ctx, p.api, p.db, maddr, 1, func(tx *harmonydb.Tx, numbers []abi.SectorNumber) (bool, error) { - if len(numbers) != 1 { - return false, xerrors.Errorf("expected one sector number") - } - n := numbers[0] - - _, err := tx.Exec("INSERT INTO sectors_sdr_pipeline (sp_id, sector_number, reg_seal_proof) VALUES ($1, $2, $3)", mid, n, spt) - if err != nil { - return false, xerrors.Errorf("inserting into sectors_sdr_pipeline: %w", err) - } - - dataHdrJson, err := json.Marshal(header) - if err != nil { - return false, xerrors.Errorf("json.Marshal(header): %w", err) - } - - dealProposalJson, err := json.Marshal(piece.DealProposal) - if err != nil { - return false, xerrors.Errorf("json.Marshal(piece.DealProposal): %w", err) - } - - _, err = tx.Exec(`INSERT INTO sectors_sdr_initial_pieces (sp_id, - sector_number, - piece_index, - - piece_cid, - piece_size, - - data_url, - data_headers, - data_raw_size, - data_delete_on_finalize, - - f05_publish_cid, - f05_deal_id, - f05_deal_proposal, - f05_deal_start_epoch, - f05_deal_end_epoch) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)`, - mid, n, 0, - piece.DealProposal.PieceCID, piece.DealProposal.PieceSize, - source.String(), dataHdrJson, rawSize, !piece.KeepUnsealed, - piece.PublishCid, piece.DealID, dealProposalJson, piece.DealSchedule.StartEpoch, piece.DealSchedule.EndEpoch) - if err != nil { - return false, xerrors.Errorf("inserting into sectors_sdr_initial_pieces: %w", err) - } - - return true, nil - }) - if err != nil { - return api.SectorOffset{}, xerrors.Errorf("allocating sector numbers: %w", err) - } - - if len(num) != 1 { - return api.SectorOffset{}, xerrors.Errorf("expected one sector number") - } - - // After we insert the piece/sector_pipeline entries, the lpseal/poller will take it from here - - return api.SectorOffset{ - Sector: num[0], - Offset: 0, - }, nil -} diff --git a/curiosrc/market/fakelm/iface.go b/curiosrc/market/fakelm/iface.go deleted file mode 100644 index 1bc91b35e..000000000 --- a/curiosrc/market/fakelm/iface.go +++ /dev/null @@ -1,33 +0,0 @@ -package fakelm - -import ( - "context" - - "github.com/google/uuid" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -// MinimalLMApi is a subset of the LotusMiner API that is exposed by Curio -// for consumption by boost -type MinimalLMApi interface { - ActorAddress(context.Context) (address.Address, error) - - WorkerJobs(context.Context) (map[uuid.UUID][]storiface.WorkerJob, error) - - SectorsStatus(ctx context.Context, sid abi.SectorNumber, showOnChainInfo bool) (api.SectorInfo, error) - - SectorsList(context.Context) ([]abi.SectorNumber, error) - SectorsSummary(ctx context.Context) (map[api.SectorState]int, error) - - SectorsListInStates(context.Context, []api.SectorState) ([]abi.SectorNumber, error) - - StorageRedeclareLocal(context.Context, *storiface.ID, bool) error - - ComputeDataCid(ctx context.Context, pieceSize abi.UnpaddedPieceSize, pieceData storiface.Data) (abi.PieceInfo, error) - SectorAddPieceToAny(ctx context.Context, size abi.UnpaddedPieceSize, r storiface.Data, d api.PieceDealInfo) (api.SectorOffset, error) -} diff --git a/curiosrc/market/fakelm/lmimpl.go b/curiosrc/market/fakelm/lmimpl.go deleted file mode 100644 index 9dc19e627..000000000 --- a/curiosrc/market/fakelm/lmimpl.go +++ /dev/null @@ -1,348 +0,0 @@ -package fakelm - -import ( - "context" - "encoding/base64" - "net/http" - "net/url" - - "github.com/gbrlsnchs/jwt/v3" - "github.com/google/uuid" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-jsonrpc/auth" - "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/lotus/api" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/curiosrc/market" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/node/config" - "github.com/filecoin-project/lotus/storage/paths" - sealing "github.com/filecoin-project/lotus/storage/pipeline" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -type LMRPCProvider struct { - si paths.SectorIndex - full api.FullNode - - maddr address.Address // lotus-miner RPC is single-actor - minerID abi.ActorID - - ssize abi.SectorSize - - pi market.Ingester - db *harmonydb.DB - conf *config.CurioConfig -} - -func NewLMRPCProvider(si paths.SectorIndex, full api.FullNode, maddr address.Address, minerID abi.ActorID, ssize abi.SectorSize, pi market.Ingester, db *harmonydb.DB, conf *config.CurioConfig) *LMRPCProvider { - return &LMRPCProvider{ - si: si, - full: full, - maddr: maddr, - minerID: minerID, - ssize: ssize, - pi: pi, - db: db, - conf: conf, - } -} - -func (l *LMRPCProvider) ActorAddress(ctx context.Context) (address.Address, error) { - return l.maddr, nil -} - -func (l *LMRPCProvider) WorkerJobs(ctx context.Context) (map[uuid.UUID][]storiface.WorkerJob, error) { - // correct enough - return map[uuid.UUID][]storiface.WorkerJob{}, nil -} - -func (l *LMRPCProvider) SectorsStatus(ctx context.Context, sid abi.SectorNumber, showOnChainInfo bool) (api.SectorInfo, error) { - si, err := l.si.StorageFindSector(ctx, abi.SectorID{Miner: l.minerID, Number: sid}, storiface.FTSealed|storiface.FTCache, 0, false) - if err != nil { - return api.SectorInfo{}, err - } - - var ssip []struct { - PieceCID *string `db:"piece_cid"` - DealID *int64 `db:"f05_deal_id"` - } - - err = l.db.Select(ctx, &ssip, "SELECT ssip.piece_cid, ssip.f05_deal_id FROM sectors_sdr_pipeline p LEFT JOIN sectors_sdr_initial_pieces ssip ON p.sp_id = ssip.sp_id AND p.sector_number = ssip.sector_number WHERE p.sp_id = $1 AND p.sector_number = $2", l.minerID, sid) - if err != nil { - return api.SectorInfo{}, err - } - - var deals []abi.DealID - if len(ssip) > 0 { - for _, d := range ssip { - if d.DealID != nil { - deals = append(deals, abi.DealID(*d.DealID)) - } - } - } else { - osi, err := l.full.StateSectorGetInfo(ctx, l.maddr, sid, types.EmptyTSK) - if err != nil { - return api.SectorInfo{}, err - } - - if osi != nil { - deals = osi.DealIDs - } - } - - spt, err := miner.SealProofTypeFromSectorSize(l.ssize, network.Version20, false) // good enough, just need this for ssize anyways - if err != nil { - return api.SectorInfo{}, err - } - - if len(si) == 0 { - state := api.SectorState(sealing.UndefinedSectorState) - if len(ssip) > 0 { - state = api.SectorState(sealing.PreCommit1) - } - - return api.SectorInfo{ - SectorID: sid, - State: state, - CommD: nil, - CommR: nil, - Proof: nil, - Deals: deals, - Pieces: nil, - Ticket: api.SealTicket{}, - Seed: api.SealSeed{}, - PreCommitMsg: nil, - CommitMsg: nil, - Retries: 0, - ToUpgrade: false, - ReplicaUpdateMessage: nil, - LastErr: "", - Log: nil, - SealProof: spt, - Activation: 0, - Expiration: 0, - DealWeight: big.Zero(), - VerifiedDealWeight: big.Zero(), - InitialPledge: big.Zero(), - OnTime: 0, - Early: 0, - }, nil - } - - var state = api.SectorState(sealing.Proving) - if !si[0].CanStore { - state = api.SectorState(sealing.PreCommit2) - } - - // todo improve this with on-chain info - return api.SectorInfo{ - SectorID: sid, - State: state, - CommD: nil, - CommR: nil, - Proof: nil, - Deals: deals, - Pieces: nil, - Ticket: api.SealTicket{}, - Seed: api.SealSeed{}, - PreCommitMsg: nil, - CommitMsg: nil, - Retries: 0, - ToUpgrade: false, - ReplicaUpdateMessage: nil, - LastErr: "", - Log: nil, - - SealProof: spt, - Activation: 0, - Expiration: 0, - DealWeight: big.Zero(), - VerifiedDealWeight: big.Zero(), - InitialPledge: big.Zero(), - OnTime: 0, - Early: 0, - }, nil -} - -func (l *LMRPCProvider) SectorsList(ctx context.Context) ([]abi.SectorNumber, error) { - decls, err := l.si.StorageList(ctx) - if err != nil { - return nil, err - } - - var out []abi.SectorNumber - for _, decl := range decls { - for _, s := range decl { - if s.Miner != l.minerID { - continue - } - - out = append(out, s.SectorID.Number) - } - } - - return out, nil -} - -type sectorParts struct { - sealed, unsealed, cache bool - inStorage bool -} - -func (l *LMRPCProvider) SectorsSummary(ctx context.Context) (map[api.SectorState]int, error) { - decls, err := l.si.StorageList(ctx) - if err != nil { - return nil, err - } - - states := map[abi.SectorID]sectorParts{} - for si, decll := range decls { - sinfo, err := l.si.StorageInfo(ctx, si) - if err != nil { - return nil, err - } - - for _, decl := range decll { - if decl.Miner != l.minerID { - continue - } - - state := states[abi.SectorID{Miner: decl.Miner, Number: decl.SectorID.Number}] - state.sealed = state.sealed || decl.Has(storiface.FTSealed) - state.unsealed = state.unsealed || decl.Has(storiface.FTUnsealed) - state.cache = state.cache || decl.Has(storiface.FTCache) - state.inStorage = state.inStorage || sinfo.CanStore - states[abi.SectorID{Miner: decl.Miner, Number: decl.SectorID.Number}] = state - } - } - - out := map[api.SectorState]int{} - for _, state := range states { - switch { - case state.sealed && state.inStorage: - out[api.SectorState(sealing.Proving)]++ - default: - // not even close to correct, but good enough for now - out[api.SectorState(sealing.PreCommit1)]++ - } - } - - return out, nil -} - -func (l *LMRPCProvider) SectorsListInStates(ctx context.Context, want []api.SectorState) ([]abi.SectorNumber, error) { - decls, err := l.si.StorageList(ctx) - if err != nil { - return nil, err - } - - wantProving, wantPrecommit1 := false, false - for _, s := range want { - switch s { - case api.SectorState(sealing.Proving): - wantProving = true - case api.SectorState(sealing.PreCommit1): - wantPrecommit1 = true - } - } - - states := map[abi.SectorID]sectorParts{} - - for si, decll := range decls { - sinfo, err := l.si.StorageInfo(ctx, si) - if err != nil { - return nil, err - } - - for _, decl := range decll { - if decl.Miner != l.minerID { - continue - } - - state := states[abi.SectorID{Miner: decl.Miner, Number: decl.SectorID.Number}] - state.sealed = state.sealed || decl.Has(storiface.FTSealed) - state.unsealed = state.unsealed || decl.Has(storiface.FTUnsealed) - state.cache = state.cache || decl.Has(storiface.FTCache) - state.inStorage = state.inStorage || sinfo.CanStore - states[abi.SectorID{Miner: decl.Miner, Number: decl.SectorID.Number}] = state - } - } - var out []abi.SectorNumber - - for id, state := range states { - switch { - case state.sealed && state.inStorage: - if wantProving { - out = append(out, id.Number) - } - default: - // not even close to correct, but good enough for now - if wantPrecommit1 { - out = append(out, id.Number) - } - } - } - - return out, nil -} - -func (l *LMRPCProvider) StorageRedeclareLocal(ctx context.Context, id *storiface.ID, b bool) error { - // so this rescans and redeclares sectors on lotus-miner; whyyy is boost even calling this? - - return nil -} - -func (l *LMRPCProvider) IsUnsealed(ctx context.Context, sectorNum abi.SectorNumber, offset abi.UnpaddedPieceSize, length abi.UnpaddedPieceSize) (bool, error) { - sectorID := abi.SectorID{Miner: l.minerID, Number: sectorNum} - - si, err := l.si.StorageFindSector(ctx, sectorID, storiface.FTUnsealed, 0, false) - if err != nil { - return false, err - } - - // yes, yes, technically sectors can be partially unsealed, but that is never done in practice - // and can't even be easily done with the current implementation - return len(si) > 0, nil -} - -func (l *LMRPCProvider) ComputeDataCid(ctx context.Context, pieceSize abi.UnpaddedPieceSize, pieceData storiface.Data) (abi.PieceInfo, error) { - return abi.PieceInfo{}, xerrors.Errorf("not supported") -} - -func (l *LMRPCProvider) SectorAddPieceToAny(ctx context.Context, size abi.UnpaddedPieceSize, r storiface.Data, d api.PieceDealInfo) (api.SectorOffset, error) { - if d.DealProposal.PieceSize != abi.PaddedPieceSize(l.ssize) { - return api.SectorOffset{}, xerrors.Errorf("only full-sector pieces are supported") - } - - return api.SectorOffset{}, xerrors.Errorf("not supported, use AllocatePieceToSector") -} - -func (l *LMRPCProvider) AllocatePieceToSector(ctx context.Context, maddr address.Address, piece api.PieceDealInfo, rawSize int64, source url.URL, header http.Header) (api.SectorOffset, error) { - return l.pi.AllocatePieceToSector(ctx, maddr, piece, rawSize, source, header) -} - -func (l *LMRPCProvider) AuthNew(ctx context.Context, perms []auth.Permission) ([]byte, error) { - type jwtPayload struct { - Allow []auth.Permission - } - - p := jwtPayload{ - Allow: perms, - } - - sk, err := base64.StdEncoding.DecodeString(l.conf.Apis.StorageRPCSecret) - if err != nil { - return nil, xerrors.Errorf("decode secret: %w", err) - } - - return jwt.Sign(&p, jwt.NewHS256(sk)) -} - -var _ MinimalLMApi = &LMRPCProvider{} diff --git a/curiosrc/market/lmrpc/lmrpc.go b/curiosrc/market/lmrpc/lmrpc.go deleted file mode 100644 index c80a02cc6..000000000 --- a/curiosrc/market/lmrpc/lmrpc.go +++ /dev/null @@ -1,499 +0,0 @@ -package lmrpc - -import ( - "context" - "fmt" - "io" - "net" - "net/http" - "net/url" - "strconv" - "strings" - "sync" - "time" - - "github.com/google/uuid" - logging "github.com/ipfs/go-log/v2" - manet "github.com/multiformats/go-multiaddr/net" - "github.com/yugabyte/pgx/v5" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/types" - cumarket "github.com/filecoin-project/lotus/curiosrc/market" - "github.com/filecoin-project/lotus/curiosrc/market/fakelm" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/nullreader" - "github.com/filecoin-project/lotus/metrics/proxy" - "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/config" - "github.com/filecoin-project/lotus/storage/paths" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -var log = logging.Logger("lmrpc") - -const backpressureWaitTime = 30 * time.Second - -func ServeCurioMarketRPCFromConfig(db *harmonydb.DB, full api.FullNode, cfg *config.CurioConfig) error { - return forEachMarketRPC(cfg, func(maddr string, listen string) error { - addr, err := address.NewFromString(maddr) - if err != nil { - return xerrors.Errorf("parsing actor address: %w", err) - } - - go func() { - err := ServeCurioMarketRPC(db, full, addr, cfg, listen) - if err != nil { - log.Errorf("failed to serve market rpc: %s", err) - } - }() - - return nil - }) -} - -func MakeTokens(cfg *config.CurioConfig) (map[address.Address]string, error) { - out := map[address.Address]string{} - - err := forEachMarketRPC(cfg, func(smaddr string, listen string) error { - ctx := context.Background() - - laddr, err := net.ResolveTCPAddr("tcp", listen) - if err != nil { - return xerrors.Errorf("net resolve: %w", err) - } - - if len(laddr.IP) == 0 || laddr.IP.IsUnspecified() { - return xerrors.Errorf("market rpc server listen address must be a specific address, not %s (probably missing bind IP)", listen) - } - - // need minimal provider with just the config - lp := fakelm.NewLMRPCProvider(nil, nil, address.Undef, 0, 0, nil, nil, cfg) - - tok, err := lp.AuthNew(ctx, api.AllPermissions) - if err != nil { - return err - } - - // parse listen into multiaddr - ma, err := manet.FromNetAddr(laddr) - if err != nil { - return xerrors.Errorf("net from addr (%v): %w", laddr, err) - } - - maddr, err := address.NewFromString(smaddr) - if err != nil { - return xerrors.Errorf("parsing actor address: %w", err) - } - - token := fmt.Sprintf("%s:%s", tok, ma) - out[maddr] = token - - return nil - }) - - return out, err -} - -func forEachMarketRPC(cfg *config.CurioConfig, cb func(string, string) error) error { - for n, server := range cfg.Subsystems.BoostAdapters { - n := n - - // server: [f0.. actor address]:[bind address] - // bind address is either a numeric port or a full address - - // first split at first : to get the actor address and the bind address - split := strings.SplitN(server, ":", 2) - - // if the split length is not 2, return an error - if len(split) != 2 { - return fmt.Errorf("bad market rpc server config %d %s, expected [f0.. actor address]:[bind address]", n, server) - } - - // get the actor address and the bind address - strMaddr, strListen := split[0], split[1] - - maddr, err := address.NewFromString(strMaddr) - if err != nil { - return xerrors.Errorf("parsing actor address: %w", err) - } - - // check the listen address - if strListen == "" { - return fmt.Errorf("bad market rpc server config %d %s, expected [f0.. actor address]:[bind address]", n, server) - } - // if listen address is numeric, prepend the default host - if _, err := strconv.Atoi(strListen); err == nil { - strListen = "0.0.0.0:" + strListen - } - // check if the listen address is a valid address - if _, _, err := net.SplitHostPort(strListen); err != nil { - return fmt.Errorf("bad market rpc server config %d %s, expected [f0.. actor address]:[bind address]", n, server) - } - - log.Infow("Starting market RPC server", "actor", maddr, "listen", strListen) - - if err := cb(strMaddr, strListen); err != nil { - return err - } - } - - return nil -} - -func ServeCurioMarketRPC(db *harmonydb.DB, full api.FullNode, maddr address.Address, conf *config.CurioConfig, listen string) error { - ctx := context.Background() - - pin := cumarket.NewPieceIngester(db, full) - - si := paths.NewDBIndex(nil, db) - - mid, err := address.IDFromAddress(maddr) - if err != nil { - return xerrors.Errorf("getting miner id: %w", err) - } - - mi, err := full.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting miner info: %w", err) - } - - lp := fakelm.NewLMRPCProvider(si, full, maddr, abi.ActorID(mid), mi.SectorSize, pin, db, conf) - - laddr, err := net.ResolveTCPAddr("tcp", listen) - if err != nil { - return xerrors.Errorf("net resolve: %w", err) - } - - if len(laddr.IP) == 0 || laddr.IP.IsUnspecified() { - return xerrors.Errorf("market rpc server listen address must be a specific address, not %s (probably missing bind IP)", listen) - } - rootUrl := url.URL{ - Scheme: "http", - Host: laddr.String(), - } - - ast := api.StorageMinerStruct{} - - ast.CommonStruct.Internal.Version = func(ctx context.Context) (api.APIVersion, error) { - return api.APIVersion{ - Version: "curio-proxy-v0", - APIVersion: api.MinerAPIVersion0, - BlockDelay: build.BlockDelaySecs, - }, nil - } - - ast.CommonStruct.Internal.AuthNew = lp.AuthNew - - ast.Internal.ActorAddress = lp.ActorAddress - ast.Internal.WorkerJobs = lp.WorkerJobs - ast.Internal.SectorsStatus = lp.SectorsStatus - ast.Internal.SectorsList = lp.SectorsList - ast.Internal.SectorsSummary = lp.SectorsSummary - ast.Internal.SectorsListInStates = lp.SectorsListInStates - ast.Internal.StorageRedeclareLocal = lp.StorageRedeclareLocal - ast.Internal.ComputeDataCid = lp.ComputeDataCid - - type pieceInfo struct { - data storiface.Data - size abi.UnpaddedPieceSize - - done chan struct{} - } - - pieceInfoLk := new(sync.Mutex) - pieceInfos := map[uuid.UUID][]pieceInfo{} - - ast.Internal.SectorAddPieceToAny = func(ctx context.Context, pieceSize abi.UnpaddedPieceSize, pieceData storiface.Data, deal api.PieceDealInfo) (api.SectorOffset, error) { - origPieceData := pieceData - defer func() { - closer, ok := origPieceData.(io.Closer) - if !ok { - log.Warnf("DataCid: cannot close pieceData reader %T because it is not an io.Closer", origPieceData) - return - } - if err := closer.Close(); err != nil { - log.Warnw("closing pieceData in DataCid", "error", err) - } - }() - - pi := pieceInfo{ - data: pieceData, - size: pieceSize, - - done: make(chan struct{}), - } - - pieceUUID := uuid.New() - - //color.Blue("%s %s piece assign request with id %s", deal.DealProposal.PieceCID, deal.DealProposal.Provider, pieceUUID) - log.Infow("piece assign request", "piece_cid", deal.DealProposal.PieceCID, "provider", deal.DealProposal.Provider, "piece_uuid", pieceUUID) - - pieceInfoLk.Lock() - pieceInfos[pieceUUID] = append(pieceInfos[pieceUUID], pi) - pieceInfoLk.Unlock() - - // /piece?piece_cid=xxxx - dataUrl := rootUrl - dataUrl.Path = "/piece" - dataUrl.RawQuery = "piece_id=" + pieceUUID.String() - - // add piece entry - - var refID int64 - var pieceWasCreated bool - - for { - var backpressureWait bool - - comm, err := db.BeginTransaction(ctx, func(tx *harmonydb.Tx) (commit bool, err error) { - // BACKPRESSURE - wait, err := maybeApplyBackpressure(tx, conf.Ingest) - if err != nil { - return false, xerrors.Errorf("backpressure checks: %w", err) - } - if wait { - backpressureWait = true - return false, nil - } - - var pieceID int64 - // Attempt to select the piece ID first - err = tx.QueryRow(`SELECT id FROM parked_pieces WHERE piece_cid = $1`, deal.DealProposal.PieceCID.String()).Scan(&pieceID) - - if err != nil { - if err == pgx.ErrNoRows { - // Piece does not exist, attempt to insert - err = tx.QueryRow(` - INSERT INTO parked_pieces (piece_cid, piece_padded_size, piece_raw_size) - VALUES ($1, $2, $3) - ON CONFLICT (piece_cid) DO NOTHING - RETURNING id`, deal.DealProposal.PieceCID.String(), int64(pieceSize.Padded()), int64(pieceSize)).Scan(&pieceID) - if err != nil { - return false, xerrors.Errorf("inserting new parked piece and getting id: %w", err) - } - pieceWasCreated = true // New piece was created - } else { - // Some other error occurred during select - return false, xerrors.Errorf("checking existing parked piece: %w", err) - } - } else { - pieceWasCreated = false // Piece already exists, no new piece was created - } - - // Add parked_piece_ref - err = tx.QueryRow(`INSERT INTO parked_piece_refs (piece_id, data_url) - VALUES ($1, $2) RETURNING ref_id`, pieceID, dataUrl.String()).Scan(&refID) - if err != nil { - return false, xerrors.Errorf("inserting parked piece ref: %w", err) - } - - // If everything went well, commit the transaction - return true, nil // This will commit the transaction - }, harmonydb.OptionRetry()) - if err != nil { - return api.SectorOffset{}, xerrors.Errorf("inserting parked piece: %w", err) - } - if !comm { - if backpressureWait { - // Backpressure was applied, wait and try again - select { - case <-time.After(backpressureWaitTime): - case <-ctx.Done(): - return api.SectorOffset{}, xerrors.Errorf("context done while waiting for backpressure: %w", ctx.Err()) - } - continue - } - - return api.SectorOffset{}, xerrors.Errorf("piece tx didn't commit") - } - - break - } - - // wait for piece to be parked - if pieceWasCreated { - <-pi.done - } else { - // If the piece was not created, we need to close the done channel - close(pi.done) - - go func() { - // close the data reader (drain to eof if it's not a closer) - if closer, ok := pieceData.(io.Closer); ok { - if err := closer.Close(); err != nil { - log.Warnw("closing pieceData in DataCid", "error", err) - } - } else { - log.Warnw("pieceData is not an io.Closer", "type", fmt.Sprintf("%T", pieceData)) - - _, err := io.Copy(io.Discard, pieceData) - if err != nil { - log.Warnw("draining pieceData in DataCid", "error", err) - } - } - }() - } - pieceIDUrl := url.URL{ - Scheme: "pieceref", - Opaque: fmt.Sprintf("%d", refID), - } - - // make a sector - so, err := pin.AllocatePieceToSector(ctx, maddr, deal, int64(pieceSize), pieceIDUrl, nil) - if err != nil { - return api.SectorOffset{}, err - } - - log.Infow("piece assigned to sector", "piece_cid", deal.DealProposal.PieceCID, "sector", so.Sector, "offset", so.Offset) - - return so, nil - } - - ast.Internal.StorageList = si.StorageList - ast.Internal.StorageDetach = si.StorageDetach - ast.Internal.StorageReportHealth = si.StorageReportHealth - ast.Internal.StorageDeclareSector = si.StorageDeclareSector - ast.Internal.StorageDropSector = si.StorageDropSector - ast.Internal.StorageFindSector = si.StorageFindSector - ast.Internal.StorageInfo = si.StorageInfo - ast.Internal.StorageBestAlloc = si.StorageBestAlloc - ast.Internal.StorageLock = si.StorageLock - ast.Internal.StorageTryLock = si.StorageTryLock - ast.Internal.StorageGetLocks = si.StorageGetLocks - - var pieceHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { - // /piece?piece_id=xxxx - pieceUUID := r.URL.Query().Get("piece_id") - - pu, err := uuid.Parse(pieceUUID) - if err != nil { - http.Error(w, "bad piece id", http.StatusBadRequest) - return - } - - if r.Method != http.MethodGet { - http.Error(w, "bad method", http.StatusMethodNotAllowed) - return - } - - fmt.Printf("%s request for piece from %s\n", pieceUUID, r.RemoteAddr) - - pieceInfoLk.Lock() - pis, ok := pieceInfos[pu] - if !ok { - http.Error(w, "piece not found", http.StatusNotFound) - log.Warnw("piece not found", "piece_uuid", pu) - pieceInfoLk.Unlock() - return - } - - // pop - pi := pis[0] - pis = pis[1:] - - pieceInfos[pu] = pis - if len(pis) == 0 { - delete(pieceInfos, pu) - } - - pieceInfoLk.Unlock() - - start := time.Now() - - pieceData := io.LimitReader(io.MultiReader( - pi.data, - nullreader.Reader{}, - ), int64(pi.size)) - - n, err := io.Copy(w, pieceData) - close(pi.done) - - took := time.Since(start) - mbps := float64(n) / (1024 * 1024) / took.Seconds() - - if err != nil { - log.Errorf("copying piece data: %s", err) - return - } - - log.Infow("piece served", "piece_uuid", pu, "size", float64(n)/(1024*1024), "duration", took, "speed", mbps) - } - - finalApi := proxy.LoggingAPI[api.StorageMiner, api.StorageMinerStruct](&ast) - - mh, err := node.MinerHandler(finalApi, false) // todo permissioned - if err != nil { - return err - } - - mux := http.NewServeMux() - mux.Handle("/piece", pieceHandler) - mux.Handle("/", mh) - - server := &http.Server{ - Addr: listen, - Handler: mux, - ReadTimeout: 48 * time.Hour, - WriteTimeout: 48 * time.Hour, // really high because we block until pieces are saved in PiecePark - } - - return server.ListenAndServe() -} - -func maybeApplyBackpressure(tx *harmonydb.Tx, cfg config.CurioIngestConfig) (wait bool, err error) { - var bufferedSDR, bufferedTrees, bufferedPoRep int - err = tx.QueryRow(`WITH BufferedSDR AS ( - SELECT SUM(buffered_count) AS buffered_sdr_count - FROM ( - SELECT COUNT(p.task_id_sdr) - COUNT(t.owner_id) AS buffered_count - FROM sectors_sdr_pipeline p - LEFT JOIN harmony_task t ON p.task_id_sdr = t.id - WHERE p.after_sdr = false - UNION ALL - SELECT COUNT(1) AS buffered_count - FROM parked_pieces - WHERE complete = false - ) AS subquery -), - BufferedTrees AS ( - SELECT COUNT(p.task_id_tree_r) - COUNT(t.owner_id) AS buffered_trees_count - FROM sectors_sdr_pipeline p - LEFT JOIN harmony_task t ON p.task_id_tree_r = t.id - WHERE p.after_sdr = true AND p.after_tree_r = false - ), - BufferedPoRep AS ( - SELECT COUNT(p.task_id_porep) - COUNT(t.owner_id) AS buffered_porep_count - FROM sectors_sdr_pipeline p - LEFT JOIN harmony_task t ON p.task_id_porep = t.id - WHERE p.after_tree_r = true AND p.after_porep = false - ) -SELECT - (SELECT buffered_sdr_count FROM BufferedSDR) AS total_buffered, - (SELECT buffered_trees_count FROM BufferedTrees) AS buffered_trees_count, - (SELECT buffered_porep_count FROM BufferedPoRep) AS buffered_porep_count -`).Scan(&bufferedSDR, &bufferedTrees, &bufferedPoRep) - if err != nil { - return false, xerrors.Errorf("counting parked pieces: %w", err) - } - - if cfg.MaxQueueSDR != 0 && bufferedSDR > cfg.MaxQueueSDR { - log.Debugw("backpressure", "reason", "too many SDR tasks", "buffered", bufferedSDR, "max", cfg.MaxQueueSDR) - return true, nil - } - if cfg.MaxQueueTrees != 0 && bufferedTrees > cfg.MaxQueueTrees { - log.Debugw("backpressure", "reason", "too many tree tasks", "buffered", bufferedTrees, "max", cfg.MaxQueueTrees) - return true, nil - } - if cfg.MaxQueuePoRep != 0 && bufferedPoRep > cfg.MaxQueuePoRep { - log.Debugw("backpressure", "reason", "too many PoRep tasks", "buffered", bufferedPoRep, "max", cfg.MaxQueuePoRep) - return true, nil - } - - return false, nil -} diff --git a/curiosrc/message/sender.go b/curiosrc/message/sender.go deleted file mode 100644 index 614bc0be2..000000000 --- a/curiosrc/message/sender.go +++ /dev/null @@ -1,396 +0,0 @@ -package message - -import ( - "bytes" - "context" - "time" - - "github.com/google/uuid" - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - "go.uber.org/multierr" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/big" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/lib/promise" -) - -var log = logging.Logger("curio/message") - -var SendLockedWait = 100 * time.Millisecond - -type SenderAPI interface { - StateAccountKey(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) - GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error) - WalletBalance(ctx context.Context, addr address.Address) (big.Int, error) - MpoolGetNonce(context.Context, address.Address) (uint64, error) - MpoolPush(context.Context, *types.SignedMessage) (cid.Cid, error) -} - -type SignerAPI interface { - WalletSignMessage(context.Context, address.Address, *types.Message) (*types.SignedMessage, error) -} - -// Sender abstracts away highly-available message sending with coordination through -// HarmonyDB. It make sure that nonces are assigned transactionally, and that -// messages are correctly broadcasted to the network. It ensures that messages -// are sent serially, and that failures to send don't cause nonce gaps. -type Sender struct { - api SenderAPI - - sendTask *SendTask - - db *harmonydb.DB -} - -type SendTask struct { - sendTF promise.Promise[harmonytask.AddTaskFunc] - - api SenderAPI - signer SignerAPI - - db *harmonydb.DB -} - -func (s *SendTask) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - ctx := context.TODO() - - // get message from db - - var dbMsg struct { - FromKey string `db:"from_key"` - ToAddr string `db:"to_addr"` - - UnsignedData []byte `db:"unsigned_data"` - UnsignedCid string `db:"unsigned_cid"` - - // may not be null if we have somehow already signed but failed to send this message - Nonce *uint64 `db:"nonce"` - SignedData []byte `db:"signed_data"` - } - - err = s.db.QueryRow(ctx, ` - SELECT from_key, nonce, to_addr, unsigned_data, unsigned_cid - FROM message_sends - WHERE send_task_id = $1`, taskID).Scan( - &dbMsg.FromKey, &dbMsg.Nonce, &dbMsg.ToAddr, &dbMsg.UnsignedData, &dbMsg.UnsignedCid) - if err != nil { - return false, xerrors.Errorf("getting message from db: %w", err) - } - - // deserialize the message - var msg types.Message - err = msg.UnmarshalCBOR(bytes.NewReader(dbMsg.UnsignedData)) - if err != nil { - return false, xerrors.Errorf("unmarshaling unsigned db message: %w", err) - } - - // get db send lock - for { - // check if we still own the task - if !stillOwned() { - return false, xerrors.Errorf("lost ownership of task") - } - - // try to acquire lock - cn, err := s.db.Exec(ctx, ` - INSERT INTO message_send_locks (from_key, task_id, claimed_at) - VALUES ($1, $2, CURRENT_TIMESTAMP) ON CONFLICT (from_key) DO UPDATE - SET task_id = EXCLUDED.task_id, claimed_at = CURRENT_TIMESTAMP - WHERE message_send_locks.task_id = $2;`, dbMsg.FromKey, taskID) - if err != nil { - return false, xerrors.Errorf("acquiring send lock: %w", err) - } - - if cn == 1 { - // we got the lock - break - } - - // we didn't get the lock, wait a bit and try again - log.Infow("waiting for send lock", "task_id", taskID, "from", dbMsg.FromKey) - time.Sleep(SendLockedWait) - } - - // defer release db send lock - defer func() { - _, err2 := s.db.Exec(ctx, ` - DELETE from message_send_locks WHERE from_key = $1 AND task_id = $2`, dbMsg.FromKey, taskID) - if err2 != nil { - log.Errorw("releasing send lock", "task_id", taskID, "from", dbMsg.FromKey, "error", err2) - - // make sure harmony retries this task so that we eventually release this lock - done = false - err = multierr.Append(err, xerrors.Errorf("releasing send lock: %w", err2)) - } - }() - - // assign nonce IF NOT ASSIGNED (max(api.MpoolGetNonce, db nonce+1)) - var sigMsg *types.SignedMessage - - if dbMsg.Nonce == nil { - msgNonce, err := s.api.MpoolGetNonce(ctx, msg.From) - if err != nil { - return false, xerrors.Errorf("getting nonce from mpool: %w", err) - } - - // get nonce from db - var dbNonce *uint64 - r := s.db.QueryRow(ctx, ` - SELECT MAX(nonce) FROM message_sends WHERE from_key = $1 AND send_success = true`, msg.From.String()) - if err := r.Scan(&dbNonce); err != nil { - return false, xerrors.Errorf("getting nonce from db: %w", err) - } - - if dbNonce != nil && *dbNonce+1 > msgNonce { - msgNonce = *dbNonce + 1 - } - - msg.Nonce = msgNonce - - // sign message - sigMsg, err = s.signer.WalletSignMessage(ctx, msg.From, &msg) - if err != nil { - return false, xerrors.Errorf("signing message: %w", err) - } - - data, err := sigMsg.Serialize() - if err != nil { - return false, xerrors.Errorf("serializing message: %w", err) - } - - jsonBytes, err := sigMsg.MarshalJSON() - if err != nil { - return false, xerrors.Errorf("marshaling message: %w", err) - } - - // write to db - - n, err := s.db.Exec(ctx, ` - UPDATE message_sends SET nonce = $1, signed_data = $2, signed_json = $3, signed_cid = $4 - WHERE send_task_id = $5`, - msg.Nonce, data, string(jsonBytes), sigMsg.Cid().String(), taskID) - if err != nil { - return false, xerrors.Errorf("updating db record: %w", err) - } - if n != 1 { - log.Errorw("updating db record: expected 1 row to be affected, got %d", n) - return false, xerrors.Errorf("updating db record: expected 1 row to be affected, got %d", n) - } - } else { - // Note: this handles an unlikely edge-case: - // We have previously signed the message but either failed to send it or failed to update the db - // note that when that happens the likely cause is the curio process losing its db connection - // or getting killed before it can update the db. In that case the message lock will still be held - // so it will be safe to rebroadcast the signed message - - // deserialize the signed message - sigMsg = new(types.SignedMessage) - err = sigMsg.UnmarshalCBOR(bytes.NewReader(dbMsg.SignedData)) - if err != nil { - return false, xerrors.Errorf("unmarshaling signed db message: %w", err) - } - } - - // send! - _, err = s.api.MpoolPush(ctx, sigMsg) - - // persist send result - var sendSuccess = err == nil - var sendError string - if err != nil { - sendError = err.Error() - } - - _, err = s.db.Exec(ctx, ` - UPDATE message_sends SET send_success = $1, send_error = $2, send_time = CURRENT_TIMESTAMP - WHERE send_task_id = $3`, sendSuccess, sendError, taskID) - if err != nil { - return false, xerrors.Errorf("updating db record: %w", err) - } - - return true, nil -} - -func (s *SendTask) CanAccept(ids []harmonytask.TaskID, engine *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - if len(ids) == 0 { - // probably can't happen, but panicking is bad - return nil, nil - } - - if s.signer == nil { - // can't sign messages here - return nil, nil - } - - return &ids[0], nil -} - -func (s *SendTask) TypeDetails() harmonytask.TaskTypeDetails { - return harmonytask.TaskTypeDetails{ - Max: 1024, - Name: "SendMessage", - Cost: resources.Resources{ - Cpu: 0, - Gpu: 0, - Ram: 1 << 20, - }, - MaxFailures: 1000, - Follows: nil, - } -} - -func (s *SendTask) Adder(taskFunc harmonytask.AddTaskFunc) { - s.sendTF.Set(taskFunc) -} - -var _ harmonytask.TaskInterface = &SendTask{} - -// NewSender creates a new Sender. -func NewSender(api SenderAPI, signer SignerAPI, db *harmonydb.DB) (*Sender, *SendTask) { - st := &SendTask{ - api: api, - signer: signer, - db: db, - } - - return &Sender{ - api: api, - db: db, - - sendTask: st, - }, st -} - -// Send atomically assigns a nonce, signs, and pushes a message -// to mempool. -// maxFee is only used when GasFeeCap/GasPremium fields aren't specified -// -// When maxFee is set to 0, Send will guess appropriate fee -// based on current chain conditions -// -// Send behaves much like fullnodeApi.MpoolPushMessage, but it coordinates -// through HarmonyDB, making it safe to broadcast messages from multiple independent -// API nodes -// -// Send is also currently more strict about required parameters than MpoolPushMessage -func (s *Sender) Send(ctx context.Context, msg *types.Message, mss *api.MessageSendSpec, reason string) (cid.Cid, error) { - if mss == nil { - return cid.Undef, xerrors.Errorf("MessageSendSpec cannot be nil") - } - if (mss.MsgUuid != uuid.UUID{}) { - return cid.Undef, xerrors.Errorf("MessageSendSpec.MsgUuid must be zero") - } - - fromA, err := s.api.StateAccountKey(ctx, msg.From, types.EmptyTSK) - if err != nil { - return cid.Undef, xerrors.Errorf("getting key address: %w", err) - } - - msg.From = fromA - - if msg.Nonce != 0 { - return cid.Undef, xerrors.Errorf("Send expects message nonce to be 0, was %d", msg.Nonce) - } - - msg, err = s.api.GasEstimateMessageGas(ctx, msg, mss, types.EmptyTSK) - if err != nil { - return cid.Undef, xerrors.Errorf("GasEstimateMessageGas error: %w", err) - } - - b, err := s.api.WalletBalance(ctx, msg.From) - if err != nil { - return cid.Undef, xerrors.Errorf("mpool push: getting origin balance: %w", err) - } - - requiredFunds := big.Add(msg.Value, msg.RequiredFunds()) - if b.LessThan(requiredFunds) { - return cid.Undef, xerrors.Errorf("mpool push: not enough funds: %s < %s", b, requiredFunds) - } - - // push the task - taskAdder := s.sendTask.sendTF.Val(ctx) - - unsBytes := new(bytes.Buffer) - err = msg.MarshalCBOR(unsBytes) - if err != nil { - return cid.Undef, xerrors.Errorf("marshaling message: %w", err) - } - - var sendTaskID *harmonytask.TaskID - taskAdder(func(id harmonytask.TaskID, tx *harmonydb.Tx) (shouldCommit bool, seriousError error) { - _, err := tx.Exec(`insert into message_sends (from_key, to_addr, send_reason, unsigned_data, unsigned_cid, send_task_id) values ($1, $2, $3, $4, $5, $6)`, - msg.From.String(), msg.To.String(), reason, unsBytes.Bytes(), msg.Cid().String(), id) - if err != nil { - return false, xerrors.Errorf("inserting message into db: %w", err) - } - - sendTaskID = &id - - return true, nil - }) - - if sendTaskID == nil { - return cid.Undef, xerrors.Errorf("failed to add task") - } - - // wait for exec - var ( - pollInterval = 50 * time.Millisecond - pollIntervalMul = 2 - maxPollInterval = 5 * time.Second - pollLoops = 0 - - sigCid cid.Cid - sendErr error - ) - - for { - var err error - var sigCidStr, sendError *string - var sendSuccess *bool - - err = s.db.QueryRow(ctx, `select signed_cid, send_success, send_error from message_sends where send_task_id = $1`, &sendTaskID).Scan(&sigCidStr, &sendSuccess, &sendError) - if err != nil { - return cid.Undef, xerrors.Errorf("getting cid for task: %w", err) - } - - if sendSuccess == nil { - time.Sleep(pollInterval) - pollLoops++ - pollInterval *= time.Duration(pollIntervalMul) - if pollInterval > maxPollInterval { - pollInterval = maxPollInterval - } - - continue - } - - if sigCidStr == nil || sendError == nil { - // should never happen because sendSuccess is already not null here - return cid.Undef, xerrors.Errorf("got null values for sigCidStr or sendError, this should never happen") - } - - if !*sendSuccess { - sendErr = xerrors.Errorf("send error: %s", *sendError) - } else { - sigCid, err = cid.Parse(*sigCidStr) - if err != nil { - return cid.Undef, xerrors.Errorf("parsing signed cid: %w", err) - } - } - - break - } - - log.Infow("sent message", "cid", sigCid, "task_id", sendTaskID, "send_error", sendErr, "poll_loops", pollLoops) - - return sigCid, sendErr -} diff --git a/curiosrc/message/watch.go b/curiosrc/message/watch.go deleted file mode 100644 index 2253df284..000000000 --- a/curiosrc/message/watch.go +++ /dev/null @@ -1,214 +0,0 @@ -package message - -import ( - "context" - "encoding/json" - "sync/atomic" - - "github.com/ipfs/go-cid" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/curiosrc/chainsched" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" -) - -const MinConfidence = 6 - -type MessageWaiterApi interface { - StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) - ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) - ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error) - StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) - ChainGetMessage(ctx context.Context, mc cid.Cid) (*types.Message, error) -} - -type MessageWatcher struct { - db *harmonydb.DB - ht *harmonytask.TaskEngine - api MessageWaiterApi - - stopping, stopped chan struct{} - - updateCh chan struct{} - bestTs atomic.Pointer[types.TipSetKey] -} - -func NewMessageWatcher(db *harmonydb.DB, ht *harmonytask.TaskEngine, pcs *chainsched.CurioChainSched, api MessageWaiterApi) (*MessageWatcher, error) { - mw := &MessageWatcher{ - db: db, - ht: ht, - api: api, - stopping: make(chan struct{}), - stopped: make(chan struct{}), - updateCh: make(chan struct{}), - } - go mw.run() - if err := pcs.AddHandler(mw.processHeadChange); err != nil { - return nil, err - } - return mw, nil -} - -func (mw *MessageWatcher) run() { - defer close(mw.stopped) - - for { - select { - case <-mw.stopping: - // todo cleanup assignments - return - case <-mw.updateCh: - mw.update() - } - } -} - -func (mw *MessageWatcher) update() { - ctx := context.Background() - - tsk := *mw.bestTs.Load() - - ts, err := mw.api.ChainGetTipSet(ctx, tsk) - if err != nil { - log.Errorf("failed to get tipset: %+v", err) - return - } - - lbts, err := mw.api.ChainGetTipSetByHeight(ctx, ts.Height()-MinConfidence, tsk) - if err != nil { - log.Errorf("failed to get tipset: %+v", err) - return - } - lbtsk := lbts.Key() - - machineID := mw.ht.ResourcesAvailable().MachineID - - // first if we see pending messages with null owner, assign them to ourselves - { - n, err := mw.db.Exec(ctx, `UPDATE message_waits SET waiter_machine_id = $1 WHERE waiter_machine_id IS NULL AND executed_tsk_cid IS NULL`, machineID) - if err != nil { - log.Errorf("failed to assign pending messages: %+v", err) - return - } - if n > 0 { - log.Debugw("assigned pending messages to ourselves", "assigned", n) - } - } - - // get messages assigned to us - var msgs []struct { - Cid string `db:"signed_message_cid"` - From string `db:"from_key"` - Nonce uint64 `db:"nonce"` - - FromAddr address.Address `db:"-"` - } - - // really large limit in case of things getting stuck and backlogging severely - err = mw.db.Select(ctx, &msgs, `SELECT signed_message_cid, from_key, nonce FROM message_waits - JOIN message_sends ON signed_message_cid = signed_cid - WHERE waiter_machine_id = $1 LIMIT 10000`, machineID) - if err != nil { - log.Errorf("failed to get assigned messages: %+v", err) - return - } - - // get address/nonce set to check - toCheck := make(map[address.Address]uint64) - - for i := range msgs { - msgs[i].FromAddr, err = address.NewFromString(msgs[i].From) - if err != nil { - log.Errorf("failed to parse from address: %+v", err) - return - } - toCheck[msgs[i].FromAddr] = 0 - } - - // get the nonce for each address - for addr := range toCheck { - act, err := mw.api.StateGetActor(ctx, addr, lbtsk) - if err != nil { - log.Errorf("failed to get actor: %+v", err) - return - } - - toCheck[addr] = act.Nonce - } - - // check if any of the messages we have assigned to us are now on chain, and have been for MinConfidence epochs - for _, msg := range msgs { - if msg.Nonce > toCheck[msg.FromAddr] { - continue // definitely not on chain yet - } - - look, err := mw.api.StateSearchMsg(ctx, lbtsk, cid.MustParse(msg.Cid), api.LookbackNoLimit, false) - if err != nil { - log.Errorf("failed to search for message: %+v", err) - return - } - - if look == nil { - continue // not on chain yet (or not executed yet) - } - - tskCid, err := look.TipSet.Cid() - if err != nil { - log.Errorf("failed to get tipset cid: %+v", err) - return - } - - emsg, err := mw.api.ChainGetMessage(ctx, look.Message) - if err != nil { - log.Errorf("failed to get message: %+v", err) - return - } - - execMsg, err := json.Marshal(emsg) - if err != nil { - log.Errorf("failed to marshal message: %+v", err) - return - } - - // record in db - _, err = mw.db.Exec(ctx, `UPDATE message_waits SET - waiter_machine_id = NULL, - executed_tsk_cid = $1, executed_tsk_epoch = $2, - executed_msg_cid = $3, executed_msg_data = $4, - executed_rcpt_exitcode = $5, executed_rcpt_return = $6, executed_rcpt_gas_used = $7 - WHERE signed_message_cid = $8`, tskCid, look.Height, - look.Message, execMsg, - look.Receipt.ExitCode, look.Receipt.Return, look.Receipt.GasUsed, - msg.Cid) - if err != nil { - log.Errorf("failed to update message wait: %+v", err) - return - } - } -} - -func (mw *MessageWatcher) Stop(ctx context.Context) error { - close(mw.stopping) - select { - case <-mw.stopped: - case <-ctx.Done(): - return ctx.Err() - } - - return nil -} - -func (mw *MessageWatcher) processHeadChange(ctx context.Context, revert *types.TipSet, apply *types.TipSet) error { - best := apply.Key() - mw.bestTs.Store(&best) - select { - case mw.updateCh <- struct{}{}: - default: - } - return nil -} diff --git a/curiosrc/multictladdr/multiaddresses.go b/curiosrc/multictladdr/multiaddresses.go deleted file mode 100644 index af751ff17..000000000 --- a/curiosrc/multictladdr/multiaddresses.go +++ /dev/null @@ -1,81 +0,0 @@ -package multictladdr - -import ( - "context" - - logging "github.com/ipfs/go-log/v2" - - "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/lotus/api" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/storage/ctladdr" -) - -var log = logging.Logger("curio/multictladdr") - -type MultiAddressSelector struct { - MinerMap map[address.Address]api.AddressConfig -} - -func (as *MultiAddressSelector) AddressFor(ctx context.Context, a ctladdr.NodeApi, minerID address.Address, mi api.MinerInfo, use api.AddrUse, goodFunds, minFunds abi.TokenAmount) (address.Address, abi.TokenAmount, error) { - if as == nil { - // should only happen in some tests - log.Warnw("smart address selection disabled, using worker address") - return mi.Worker, big.Zero(), nil - } - - tmp := as.MinerMap[minerID] - - var addrs []address.Address - switch use { - case api.PreCommitAddr: - addrs = append(addrs, tmp.PreCommitControl...) - case api.CommitAddr: - addrs = append(addrs, tmp.CommitControl...) - case api.TerminateSectorsAddr: - addrs = append(addrs, tmp.TerminateControl...) - case api.DealPublishAddr: - addrs = append(addrs, tmp.DealPublishControl...) - default: - defaultCtl := map[address.Address]struct{}{} - for _, a := range mi.ControlAddresses { - defaultCtl[a] = struct{}{} - } - delete(defaultCtl, mi.Owner) - delete(defaultCtl, mi.Worker) - - configCtl := append([]address.Address{}, tmp.PreCommitControl...) - configCtl = append(configCtl, tmp.CommitControl...) - configCtl = append(configCtl, tmp.TerminateControl...) - configCtl = append(configCtl, tmp.DealPublishControl...) - - for _, addr := range configCtl { - if addr.Protocol() != address.ID { - var err error - addr, err = a.StateLookupID(ctx, addr, types.EmptyTSK) - if err != nil { - log.Warnw("looking up control address", "address", addr, "error", err) - continue - } - } - - delete(defaultCtl, addr) - } - - for a := range defaultCtl { - addrs = append(addrs, a) - } - } - - if len(addrs) == 0 || !tmp.DisableWorkerFallback { - addrs = append(addrs, mi.Worker) - } - if !tmp.DisableOwnerFallback { - addrs = append(addrs, mi.Owner) - } - - return ctladdr.PickAddress(ctx, a, mi, goodFunds, minFunds, addrs) -} diff --git a/curiosrc/piece/task_cleanup_piece.go b/curiosrc/piece/task_cleanup_piece.go deleted file mode 100644 index ed22ccb46..000000000 --- a/curiosrc/piece/task_cleanup_piece.go +++ /dev/null @@ -1,130 +0,0 @@ -package piece - -import ( - "context" - "time" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/curiosrc/ffi" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/lib/promise" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -type CleanupPieceTask struct { - max int - db *harmonydb.DB - sc *ffi.SealCalls - - TF promise.Promise[harmonytask.AddTaskFunc] -} - -func NewCleanupPieceTask(db *harmonydb.DB, sc *ffi.SealCalls, max int) *CleanupPieceTask { - pt := &CleanupPieceTask{ - db: db, - sc: sc, - - max: max, - } - go pt.pollCleanupTasks(context.Background()) - return pt -} - -func (c *CleanupPieceTask) pollCleanupTasks(ctx context.Context) { - for { - // select pieces with no refs and null cleanup_task_id - var pieceIDs []struct { - ID storiface.PieceNumber `db:"id"` - } - - err := c.db.Select(ctx, &pieceIDs, `SELECT id FROM parked_pieces WHERE cleanup_task_id IS NULL AND (SELECT count(*) FROM parked_piece_refs WHERE piece_id = parked_pieces.id) = 0`) - if err != nil { - log.Errorf("failed to get parked pieces: %s", err) - time.Sleep(PieceParkPollInterval) - continue - } - - if len(pieceIDs) == 0 { - time.Sleep(PieceParkPollInterval) - continue - } - - for _, pieceID := range pieceIDs { - pieceID := pieceID - - // create a task for each piece - c.TF.Val(ctx)(func(id harmonytask.TaskID, tx *harmonydb.Tx) (shouldCommit bool, err error) { - // update - n, err := tx.Exec(`UPDATE parked_pieces SET cleanup_task_id = $1 WHERE id = $2 AND (SELECT count(*) FROM parked_piece_refs WHERE piece_id = parked_pieces.id) = 0`, id, pieceID.ID) - if err != nil { - return false, xerrors.Errorf("updating parked piece: %w", err) - } - - // commit only if we updated the piece - return n > 0, nil - }) - } - } -} - -func (c *CleanupPieceTask) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - ctx := context.Background() - - // select by cleanup_task_id - var pieceID int64 - - err = c.db.QueryRow(ctx, "SELECT id FROM parked_pieces WHERE cleanup_task_id = $1", taskID).Scan(&pieceID) - if err != nil { - return false, xerrors.Errorf("query parked_piece: %w", err) - } - - // delete from parked_pieces where id = $1 where ref count = 0 - // note: we delete from the db first because that guarantees that the piece is no longer in use - // if storage delete fails, it will be retried later is other cleanup tasks - n, err := c.db.Exec(ctx, "DELETE FROM parked_pieces WHERE id = $1 AND (SELECT count(*) FROM parked_piece_refs WHERE piece_id = $1) = 0", pieceID) - if err != nil { - return false, xerrors.Errorf("delete parked_piece: %w", err) - } - - if n == 0 { - return true, nil - } - - // remove from storage - err = c.sc.RemovePiece(ctx, storiface.PieceNumber(pieceID)) - if err != nil { - log.Errorw("remove piece", "piece_id", pieceID, "error", err) - } - - return true, nil -} - -func (c *CleanupPieceTask) CanAccept(ids []harmonytask.TaskID, engine *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - // the remove call runs on paths.Remote storage, so it doesn't really matter where it runs - - id := ids[0] - return &id, nil -} - -func (c *CleanupPieceTask) TypeDetails() harmonytask.TaskTypeDetails { - return harmonytask.TaskTypeDetails{ - Max: c.max, - Name: "DropPiece", - Cost: resources.Resources{ - Cpu: 1, - Gpu: 0, - Ram: 64 << 20, - Storage: nil, - }, - MaxFailures: 10, - } -} - -func (c *CleanupPieceTask) Adder(taskFunc harmonytask.AddTaskFunc) { - c.TF.Set(taskFunc) -} - -var _ harmonytask.TaskInterface = &CleanupPieceTask{} diff --git a/curiosrc/piece/task_park_piece.go b/curiosrc/piece/task_park_piece.go deleted file mode 100644 index 68a94a295..000000000 --- a/curiosrc/piece/task_park_piece.go +++ /dev/null @@ -1,221 +0,0 @@ -package piece - -import ( - "context" - "encoding/json" - "strconv" - "time" - - logging "github.com/ipfs/go-log/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/curiosrc/ffi" - "github.com/filecoin-project/lotus/curiosrc/seal" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/lib/promise" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -var log = logging.Logger("lppiece") -var PieceParkPollInterval = time.Second * 15 - -// ParkPieceTask gets a piece from some origin, and parks it in storage -// Pieces are always f00, piece ID is mapped to pieceCID in the DB -type ParkPieceTask struct { - db *harmonydb.DB - sc *ffi.SealCalls - - TF promise.Promise[harmonytask.AddTaskFunc] - - max int -} - -func NewParkPieceTask(db *harmonydb.DB, sc *ffi.SealCalls, max int) *ParkPieceTask { - pt := &ParkPieceTask{ - db: db, - sc: sc, - - max: max, - } - - go pt.pollPieceTasks(context.Background()) - return pt -} - -func (p *ParkPieceTask) pollPieceTasks(ctx context.Context) { - for { - // select parked pieces with no task_id - var pieceIDs []struct { - ID storiface.PieceNumber `db:"id"` - } - - err := p.db.Select(ctx, &pieceIDs, `SELECT id FROM parked_pieces WHERE complete = FALSE AND task_id IS NULL`) - if err != nil { - log.Errorf("failed to get parked pieces: %s", err) - time.Sleep(PieceParkPollInterval) - continue - } - - if len(pieceIDs) == 0 { - time.Sleep(PieceParkPollInterval) - continue - } - - for _, pieceID := range pieceIDs { - pieceID := pieceID - - // create a task for each piece - p.TF.Val(ctx)(func(id harmonytask.TaskID, tx *harmonydb.Tx) (shouldCommit bool, err error) { - // update - n, err := tx.Exec(`UPDATE parked_pieces SET task_id = $1 WHERE id = $2 AND complete = FALSE AND task_id IS NULL`, id, pieceID.ID) - if err != nil { - return false, xerrors.Errorf("updating parked piece: %w", err) - } - - // commit only if we updated the piece - return n > 0, nil - }) - } - } -} - -func (p *ParkPieceTask) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - ctx := context.Background() - - // Define a struct to hold piece data. - var piecesData []struct { - PieceID int64 `db:"id"` - PieceCreatedAt time.Time `db:"created_at"` - PieceCID string `db:"piece_cid"` - Complete bool `db:"complete"` - PiecePaddedSize int64 `db:"piece_padded_size"` - PieceRawSize string `db:"piece_raw_size"` - } - - // Select the piece data using the task ID. - err = p.db.Select(ctx, &piecesData, ` - SELECT id, created_at, piece_cid, complete, piece_padded_size, piece_raw_size - FROM parked_pieces - WHERE task_id = $1 - `, taskID) - if err != nil { - return false, xerrors.Errorf("fetching piece data: %w", err) - } - - if len(piecesData) == 0 { - return false, xerrors.Errorf("no piece data found for task_id: %d", taskID) - } - - pieceData := piecesData[0] - - if pieceData.Complete { - log.Warnw("park piece task already complete", "task_id", taskID, "piece_cid", pieceData.PieceCID) - return true, nil - } - - // Define a struct for reference data. - var refData []struct { - DataURL string `db:"data_url"` - DataHeaders json.RawMessage `db:"data_headers"` - } - - // Now, select the first reference data that has a URL. - err = p.db.Select(ctx, &refData, ` - SELECT data_url, data_headers - FROM parked_piece_refs - WHERE piece_id = $1 AND data_url IS NOT NULL - LIMIT 1 - `, pieceData.PieceID) - if err != nil { - return false, xerrors.Errorf("fetching reference data: %w", err) - } - - if len(refData) == 0 { - return false, xerrors.Errorf("no refs found for piece_id: %d", pieceData.PieceID) - } - - // Convert piece_raw_size from string to int64. - pieceRawSize, err := strconv.ParseInt(pieceData.PieceRawSize, 10, 64) - if err != nil { - return false, xerrors.Errorf("parsing piece raw size: %w", err) - } - - if refData[0].DataURL != "" { - upr := &seal.UrlPieceReader{ - Url: refData[0].DataURL, - RawSize: pieceRawSize, - } - defer func() { - _ = upr.Close() - }() - - pnum := storiface.PieceNumber(pieceData.PieceID) - - if err := p.sc.WritePiece(ctx, &taskID, pnum, pieceRawSize, upr); err != nil { - return false, xerrors.Errorf("write piece: %w", err) - } - - // Update the piece as complete after a successful write. - _, err = p.db.Exec(ctx, `UPDATE parked_pieces SET complete = TRUE WHERE id = $1`, pieceData.PieceID) - if err != nil { - return false, xerrors.Errorf("marking piece as complete: %w", err) - } - - return true, nil - } - - // If no URL is found, this indicates an issue since at least one URL is expected. - return false, xerrors.Errorf("no data URL found for piece_id: %d", pieceData.PieceID) -} - -func (p *ParkPieceTask) CanAccept(ids []harmonytask.TaskID, engine *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - id := ids[0] - return &id, nil -} - -func (p *ParkPieceTask) TypeDetails() harmonytask.TaskTypeDetails { - const maxSizePiece = 64 << 30 - - return harmonytask.TaskTypeDetails{ - Max: p.max, - Name: "ParkPiece", - Cost: resources.Resources{ - Cpu: 1, - Gpu: 0, - Ram: 64 << 20, - Storage: p.sc.Storage(p.taskToRef, storiface.FTPiece, storiface.FTNone, maxSizePiece, storiface.PathSealing), - }, - MaxFailures: 10, - } -} - -func (p *ParkPieceTask) taskToRef(id harmonytask.TaskID) (ffi.SectorRef, error) { - var pieceIDs []struct { - ID storiface.PieceNumber `db:"id"` - } - - err := p.db.Select(context.Background(), &pieceIDs, `SELECT id FROM parked_pieces WHERE task_id = $1`, id) - if err != nil { - return ffi.SectorRef{}, xerrors.Errorf("getting piece id: %w", err) - } - - if len(pieceIDs) != 1 { - return ffi.SectorRef{}, xerrors.Errorf("expected 1 piece id, got %d", len(pieceIDs)) - } - - pref := pieceIDs[0].ID.Ref() - - return ffi.SectorRef{ - SpID: int64(pref.ID.Miner), - SectorNumber: int64(pref.ID.Number), - RegSealProof: pref.ProofType, - }, nil -} - -func (p *ParkPieceTask) Adder(taskFunc harmonytask.AddTaskFunc) { - p.TF.Set(taskFunc) -} - -var _ harmonytask.TaskInterface = &ParkPieceTask{} diff --git a/curiosrc/proof/treed_build.go b/curiosrc/proof/treed_build.go deleted file mode 100644 index 7145c9257..000000000 --- a/curiosrc/proof/treed_build.go +++ /dev/null @@ -1,292 +0,0 @@ -package proof - -import ( - "io" - "math/bits" - "os" - "runtime" - "sync" - "time" - - "github.com/hashicorp/go-multierror" - "github.com/ipfs/go-cid" - pool "github.com/libp2p/go-buffer-pool" - "github.com/minio/sha256-simd" - "golang.org/x/xerrors" - - commcid "github.com/filecoin-project/go-fil-commcid" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/storage/sealer/fr32" -) - -const nodeSize = 32 -const threadChunkSize = 1 << 20 - -func hashChunk(data [][]byte) { - l1Nodes := len(data[0]) / nodeSize / 2 - - d := sha256.New() - - sumBuf := make([]byte, nodeSize) - - for i := 0; i < l1Nodes; i++ { - levels := bits.TrailingZeros(^uint(i)) + 1 - - inNode := i * 2 // at level 0 - outNode := i - - for l := 0; l < levels; l++ { - d.Reset() - inNodeData := data[l][inNode*nodeSize : (inNode+2)*nodeSize] - d.Write(inNodeData) - copy(data[l+1][outNode*nodeSize:(outNode+1)*nodeSize], d.Sum(sumBuf[:0])) - // set top bits to 00 - data[l+1][outNode*nodeSize+nodeSize-1] &= 0x3f - - inNode-- - inNode >>= 1 - outNode >>= 1 - } - } -} - -func BuildTreeD(data io.Reader, unpaddedData bool, outPath string, size abi.PaddedPieceSize) (_ cid.Cid, err error) { - out, err := os.Create(outPath) - if err != nil { - return cid.Undef, err - } - defer func() { - cerr := out.Close() - - if err != nil { - // remove the file, it's probably bad - rerr := os.Remove(outPath) - if rerr != nil { - err = multierror.Append(err, rerr) - } - } - - if cerr != nil { - err = multierror.Append(err, cerr) - } - }() - - outSize := treeSize(size) - - // allocate space for the tree - err = out.Truncate(int64(outSize)) - if err != nil { - return cid.Undef, err - } - - // setup buffers - maxThreads := int64(size) / threadChunkSize - if maxThreads > int64(runtime.NumCPU())*15/10 { - maxThreads = int64(runtime.NumCPU()) * 15 / 10 - } - if maxThreads < 1 { - maxThreads = 1 - } - - // allocate buffers - var bufLk sync.Mutex - workerBuffers := make([][][]byte, maxThreads) // [worker][level][levelSize] - - for i := range workerBuffers { - workerBuffer := make([][]byte, 1) - - bottomBufSize := int64(threadChunkSize) - if bottomBufSize > int64(size) { - bottomBufSize = int64(size) - } - workerBuffer[0] = pool.Get(int(bottomBufSize)) - - // append levels until we get to a 32 byte level - for len(workerBuffer[len(workerBuffer)-1]) > 32 { - newLevel := pool.Get(len(workerBuffer[len(workerBuffer)-1]) / 2) - workerBuffer = append(workerBuffer, newLevel) - } - workerBuffers[i] = workerBuffer - } - - // prepare apex buffer - var apexBuf [][]byte - { - apexBottomSize := uint64(size) / uint64(len(workerBuffers[0][0])) - if apexBottomSize == 0 { - apexBottomSize = 1 - } - - apexBuf = make([][]byte, 1) - apexBuf[0] = pool.Get(int(apexBottomSize * nodeSize)) - for len(apexBuf[len(apexBuf)-1]) > 32 { - newLevel := pool.Get(len(apexBuf[len(apexBuf)-1]) / 2) - apexBuf = append(apexBuf, newLevel) - } - } - - // defer free pool buffers - defer func() { - for _, workerBuffer := range workerBuffers { - for _, level := range workerBuffer { - pool.Put(level) - } - } - for _, level := range apexBuf { - pool.Put(level) - } - }() - - // start processing - var processed uint64 - var workWg sync.WaitGroup - var errLock sync.Mutex - var oerr error - - for processed < uint64(size) { - // get a buffer - bufLk.Lock() - if len(workerBuffers) == 0 { - bufLk.Unlock() - time.Sleep(50 * time.Microsecond) - continue - } - - // pop last - workBuffer := workerBuffers[len(workerBuffers)-1] - workerBuffers = workerBuffers[:len(workerBuffers)-1] - - bufLk.Unlock() - - // before reading check that we didn't get a write error - errLock.Lock() - if oerr != nil { - errLock.Unlock() - return cid.Undef, oerr - } - errLock.Unlock() - - // read data into the bottom level - // note: the bottom level will never be too big; data is power of two - // size, and if it's smaller than a single buffer, we only have one - // smaller buffer - - processedSize := uint64(len(workBuffer[0])) - if unpaddedData { - workBuffer[0] = workBuffer[0][:abi.PaddedPieceSize(len(workBuffer[0])).Unpadded()] - } - - _, err := io.ReadFull(data, workBuffer[0]) - if err != nil && err != io.EOF { - return cid.Undef, err - } - - // start processing - workWg.Add(1) - go func(startOffset uint64) { - defer workWg.Done() - - if unpaddedData { - paddedBuf := pool.Get(int(abi.UnpaddedPieceSize(len(workBuffer[0])).Padded())) - fr32.PadSingle(workBuffer[0], paddedBuf) - pool.Put(workBuffer[0]) - workBuffer[0] = paddedBuf - } - hashChunk(workBuffer) - - // persist apex - { - apexHash := workBuffer[len(workBuffer)-1] - hashPos := startOffset / uint64(len(workBuffer[0])) * nodeSize - - copy(apexBuf[0][hashPos:hashPos+nodeSize], apexHash) - } - - // write results - offsetInLayer := startOffset - for layer, layerData := range workBuffer { - - // layerOff is outSize:bits[most significant bit - layer] - layerOff := layerOffset(uint64(size), layer) - dataOff := offsetInLayer + layerOff - offsetInLayer /= 2 - - _, werr := out.WriteAt(layerData, int64(dataOff)) - if werr != nil { - errLock.Lock() - oerr = multierror.Append(oerr, werr) - errLock.Unlock() - return - } - } - - // return buffer - bufLk.Lock() - workerBuffers = append(workerBuffers, workBuffer) - bufLk.Unlock() - }(processed) - - processed += processedSize - } - - workWg.Wait() - - if oerr != nil { - return cid.Undef, oerr - } - - threadLayers := bits.Len(uint(len(workerBuffers[0][0])) / nodeSize) - - if len(apexBuf) > 0 { - // hash the apex - hashChunk(apexBuf) - - // write apex - for apexLayer, layerData := range apexBuf { - if apexLayer == 0 { - continue - } - layer := apexLayer + threadLayers - 1 - - layerOff := layerOffset(uint64(size), layer) - _, werr := out.WriteAt(layerData, int64(layerOff)) - if werr != nil { - return cid.Undef, xerrors.Errorf("write apex: %w", werr) - } - } - } - - var commp [32]byte - copy(commp[:], apexBuf[len(apexBuf)-1]) - - commCid, err := commcid.DataCommitmentV1ToCID(commp[:]) - if err != nil { - return cid.Undef, err - } - - return commCid, nil -} - -func treeSize(data abi.PaddedPieceSize) uint64 { - bytesToAlloc := uint64(data) - - // append bytes until we get to nodeSize - for todo := bytesToAlloc; todo > nodeSize; todo /= 2 { - bytesToAlloc += todo / 2 - } - - return bytesToAlloc -} - -func layerOffset(size uint64, layer int) uint64 { - allOnes := uint64(0xffff_ffff_ffff_ffff) - - // get 'layer' bits set to 1 - layerOnes := allOnes >> uint64(64-layer) - - // shift layerOnes to the left such that the highest bit is at the same position as the highest bit in size (which is power-of-two) - sizeBitPos := bits.Len64(size) - 1 - layerOnes <<= sizeBitPos - (layer - 1) - return layerOnes -} diff --git a/curiosrc/proof/treed_build_test.go b/curiosrc/proof/treed_build_test.go deleted file mode 100644 index f69e98322..000000000 --- a/curiosrc/proof/treed_build_test.go +++ /dev/null @@ -1,516 +0,0 @@ -package proof - -import ( - "bufio" - "bytes" - "crypto/rand" - "fmt" - "io" - "os" - "path/filepath" - "runtime" - "testing" - - pool "github.com/libp2p/go-buffer-pool" - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/storage/pipeline/lib/nullreader" -) - -func TestTreeSize(t *testing.T) { - require.Equal(t, uint64(32), treeSize(abi.PaddedPieceSize(32))) - require.Equal(t, uint64(64+32), treeSize(abi.PaddedPieceSize(64))) - require.Equal(t, uint64(128+64+32), treeSize(abi.PaddedPieceSize(128))) - require.Equal(t, uint64(256+128+64+32), treeSize(abi.PaddedPieceSize(256))) -} - -func TestTreeLayerOffset(t *testing.T) { - require.Equal(t, uint64(0), layerOffset(128, 0)) - require.Equal(t, uint64(128), layerOffset(128, 1)) - require.Equal(t, uint64(128+64), layerOffset(128, 2)) - require.Equal(t, uint64(128+64+32), layerOffset(128, 3)) -} - -func TestHashChunk(t *testing.T) { - chunk := make([]byte, 64) - chunk[0] = 0x01 - - out := make([]byte, 32) - - data := [][]byte{chunk, out} - hashChunk(data) - - // 16 ab ab 34 1f b7 f3 70 e2 7e 4d ad cf 81 76 6d - // d0 df d0 ae 64 46 94 77 bb 2c f6 61 49 38 b2 2f - expect := []byte{ - 0x16, 0xab, 0xab, 0x34, 0x1f, 0xb7, 0xf3, 0x70, - 0xe2, 0x7e, 0x4d, 0xad, 0xcf, 0x81, 0x76, 0x6d, - 0xd0, 0xdf, 0xd0, 0xae, 0x64, 0x46, 0x94, 0x77, - 0xbb, 0x2c, 0xf6, 0x61, 0x49, 0x38, 0xb2, 0x2f, - } - - require.Equal(t, expect, out) -} - -func TestHashChunk2L(t *testing.T) { - data0 := make([]byte, 128) - data0[0] = 0x01 - - l1 := make([]byte, 64) - l2 := make([]byte, 32) - - data := [][]byte{data0, l1, l2} - hashChunk(data) - - // 16 ab ab 34 1f b7 f3 70 e2 7e 4d ad cf 81 76 6d - // d0 df d0 ae 64 46 94 77 bb 2c f6 61 49 38 b2 2f - expectL1Left := []byte{ - 0x16, 0xab, 0xab, 0x34, 0x1f, 0xb7, 0xf3, 0x70, - 0xe2, 0x7e, 0x4d, 0xad, 0xcf, 0x81, 0x76, 0x6d, - 0xd0, 0xdf, 0xd0, 0xae, 0x64, 0x46, 0x94, 0x77, - 0xbb, 0x2c, 0xf6, 0x61, 0x49, 0x38, 0xb2, 0x2f, - } - - // f5 a5 fd 42 d1 6a 20 30 27 98 ef 6e d3 09 97 9b - // 43 00 3d 23 20 d9 f0 e8 ea 98 31 a9 27 59 fb 0b - expectL1Rest := []byte{ - 0xf5, 0xa5, 0xfd, 0x42, 0xd1, 0x6a, 0x20, 0x30, - 0x27, 0x98, 0xef, 0x6e, 0xd3, 0x09, 0x97, 0x9b, - 0x43, 0x00, 0x3d, 0x23, 0x20, 0xd9, 0xf0, 0xe8, - 0xea, 0x98, 0x31, 0xa9, 0x27, 0x59, 0xfb, 0x0b, - } - - require.Equal(t, expectL1Left, l1[:32]) - require.Equal(t, expectL1Rest, l1[32:]) - - // 0d d6 da e4 1c 2f 75 55 01 29 59 4f b6 44 e4 a8 - // 42 cf af b3 16 a2 d5 93 21 e3 88 fe 84 a1 ec 2f - expectL2 := []byte{ - 0x0d, 0xd6, 0xda, 0xe4, 0x1c, 0x2f, 0x75, 0x55, - 0x01, 0x29, 0x59, 0x4f, 0xb6, 0x44, 0xe4, 0xa8, - 0x42, 0xcf, 0xaf, 0xb3, 0x16, 0xa2, 0xd5, 0x93, - 0x21, 0xe3, 0x88, 0xfe, 0x84, 0xa1, 0xec, 0x2f, - } - - require.Equal(t, expectL2, l2) -} - -func Test2K(t *testing.T) { - data := make([]byte, 2048) - data[0] = 0x01 - - tempFile := filepath.Join(t.TempDir(), "tree.dat") - - commd, err := BuildTreeD(bytes.NewReader(data), false, tempFile, 2048) - require.NoError(t, err) - fmt.Println(commd) - - // dump tree.dat - dat, err := os.ReadFile(tempFile) - require.NoError(t, err) - - for i, b := range dat { - // 32 values per line - if i%32 == 0 { - fmt.Println() - - // line offset hexdump style - fmt.Printf("%04x: ", i) - } - fmt.Printf("%02x ", b) - } - fmt.Println() - - require.Equal(t, "baga6ea4seaqovgk4kr4eoifujh6jfmdqvw3m6zrvyjqzu6s6abkketui6jjoydi", commd.String()) - -} - -const expectD8M = `00000000: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -* -00800000: 16 ab ab 34 1f b7 f3 70 e2 7e 4d ad cf 81 76 6d d0 df d0 ae 64 46 94 77 bb 2c f6 61 49 38 b2 2f -00800020: f5 a5 fd 42 d1 6a 20 30 27 98 ef 6e d3 09 97 9b 43 00 3d 23 20 d9 f0 e8 ea 98 31 a9 27 59 fb 0b -* -00c00000: 0d d6 da e4 1c 2f 75 55 01 29 59 4f b6 44 e4 a8 42 cf af b3 16 a2 d5 93 21 e3 88 fe 84 a1 ec 2f -00c00020: 37 31 bb 99 ac 68 9f 66 ee f5 97 3e 4a 94 da 18 8f 4d dc ae 58 07 24 fc 6f 3f d6 0d fd 48 83 33 -* -00e00000: 11 b1 c4 80 05 21 d5 e5 83 4a de b3 70 7c 74 15 9f f3 37 b0 96 16 3c 94 31 16 73 40 e7 b1 17 1d -00e00020: 64 2a 60 7e f8 86 b0 04 bf 2c 19 78 46 3a e1 d4 69 3a c0 f4 10 eb 2d 1b 7a 47 fe 20 5e 5e 75 0f -* -00f00000: ec 69 25 55 9b cc 52 84 0a 22 38 5b 2b 6b 35 b4 50 14 50 04 28 f4 59 fe c1 23 01 0f e7 ef 18 1c -00f00020: 57 a2 38 1a 28 65 2b f4 7f 6b ef 7a ca 67 9b e4 ae de 58 71 ab 5c f3 eb 2c 08 11 44 88 cb 85 26 -* -00f80000: 3d d2 eb 19 3e e2 f0 47 34 87 bf 4b 83 aa 3a bd a9 c8 4e fa e5 52 6d 8a fd 61 2d 5d 9e 3d 79 34 -00f80020: 1f 7a c9 59 55 10 e0 9e a4 1c 46 0b 17 64 30 bb 32 2c d6 fb 41 2e c5 7c b1 7d 98 9a 43 10 37 2f -* -00fc0000: ea 99 5c 54 78 47 20 b4 49 fc 92 b0 70 ad b6 cf 66 35 c2 61 9a 7a 5e 00 54 a2 4e 88 f2 52 ec 0d -00fc0020: fc 7e 92 82 96 e5 16 fa ad e9 86 b2 8f 92 d4 4a 4f 24 b9 35 48 52 23 37 6a 79 90 27 bc 18 f8 33 -* -00fe0000: b9 97 02 8b 06 d7 2e 96 07 86 79 58 e1 5f 8d 07 b7 ae 37 ab 29 ab 3f a9 de fe c9 8e aa 37 6e 28 -00fe0020: 08 c4 7b 38 ee 13 bc 43 f4 1b 91 5c 0e ed 99 11 a2 60 86 b3 ed 62 40 1b f9 d5 8b 8d 19 df f6 24 -* -00ff0000: a0 c4 4f 7b a4 4c d2 3c 2e bf 75 98 7b e8 98 a5 63 80 73 b2 f9 11 cf ee ce 14 5a 77 58 0c 6c 12 -00ff0020: b2 e4 7b fb 11 fa cd 94 1f 62 af 5c 75 0f 3e a5 cc 4d f5 17 d5 c4 f1 6d b2 b4 d7 7b ae c1 a3 2f -* -00ff8000: 89 2d 2b 00 a5 c1 54 10 94 ca 65 de 21 3b bd 45 90 14 15 ed d1 10 17 cd 29 f3 ed 75 73 02 a0 3f -00ff8020: f9 22 61 60 c8 f9 27 bf dc c4 18 cd f2 03 49 31 46 00 8e ae fb 7d 02 19 4d 5e 54 81 89 00 51 08 -* -00ffc000: 22 48 54 8b ba a5 8f e2 db 0b 07 18 c1 d7 20 1f ed 64 c7 8d 7d 22 88 36 b2 a1 b2 f9 42 0b ef 3c -00ffc020: 2c 1a 96 4b b9 0b 59 eb fe 0f 6d a2 9a d6 5a e3 e4 17 72 4a 8f 7c 11 74 5a 40 ca c1 e5 e7 40 11 -* -00ffe000: 1c 6a 48 08 3e 17 49 90 ef c0 56 ec b1 44 75 1d e2 76 d8 a5 1c 3d 93 d7 4c 81 92 48 ab 78 cc 30 -00ffe020: fe e3 78 ce f1 64 04 b1 99 ed e0 b1 3e 11 b6 24 ff 9d 78 4f bb ed 87 8d 83 29 7e 79 5e 02 4f 02 -* -00fff000: 0a b4 26 38 1b 72 cd 3b b3 e3 c7 82 18 fe 1f 18 3b 3a 19 db c4 d9 26 94 30 03 cd 01 b6 d1 8d 0b -00fff020: 8e 9e 24 03 fa 88 4c f6 23 7f 60 df 25 f8 3e e4 0d ca 9e d8 79 eb 6f 63 52 d1 50 84 f5 ad 0d 3f -* -00fff800: 16 0d 87 17 1b e7 ae e4 20 a3 54 24 cf df 4f fe a2 fd 7b 94 58 89 58 f3 45 11 57 fc 39 8f 34 26 -00fff820: 75 2d 96 93 fa 16 75 24 39 54 76 e3 17 a9 85 80 f0 09 47 af b7 a3 05 40 d6 25 a9 29 1c c1 2a 07 -* -00fffc00: 1f 40 60 11 da 08 f8 09 80 63 97 dc 1c 57 b9 87 83 37 5a 59 5d d6 81 42 6c 1e cd d4 3c ab e3 3c -00fffc20: 70 22 f6 0f 7e f6 ad fa 17 11 7a 52 61 9e 30 ce a8 2c 68 07 5a df 1c 66 77 86 ec 50 6e ef 2d 19 -* -00fffe00: 51 4e dd 2f 6f 8f 6d fd 54 b0 d1 20 7b b7 06 df 85 c5 a3 19 0e af 38 72 37 20 c5 07 56 67 7f 14 -00fffe20: d9 98 87 b9 73 57 3a 96 e1 13 93 64 52 36 c1 7b 1f 4c 70 34 d7 23 c7 a9 9f 70 9b b4 da 61 16 2b -* -00ffff00: 5a 1d 84 74 85 a3 4b 28 08 93 a9 cf b2 8b 54 44 67 12 8b eb c0 22 bd de c1 04 be ca b4 f4 81 31 -00ffff20: d0 b5 30 db b0 b4 f2 5c 5d 2f 2a 28 df ee 80 8b 53 41 2a 02 93 1f 18 c4 99 f5 a2 54 08 6b 13 26 -* -00ffff80: c5 fb f3 f9 4c c2 2b 3c 51 ad c1 ea af e9 4b a0 9f b2 73 f3 73 d2 10 1f 12 0b 11 c6 85 21 66 2f -00ffffa0: 84 c0 42 1b a0 68 5a 01 bf 79 5a 23 44 06 4f e4 24 bd 52 a9 d2 43 77 b3 94 ff 4c 4b 45 68 e8 11 -00ffffc0: 23 40 4a 88 80 f9 cb c7 20 39 cb 86 14 35 9c 28 34 84 55 70 fe 95 19 0b bd 4d 93 41 42 e8 25 2c -` - -func Test8MiB(t *testing.T) { - data := make([]byte, 8<<20) - data[0] = 0x01 - - tempFile := filepath.Join(t.TempDir(), "tree.dat") - - commd, err := BuildTreeD(bytes.NewReader(data), false, tempFile, 8<<20) - require.NoError(t, err) - fmt.Println(commd) - - // dump tree.dat - dat, err := os.ReadFile(tempFile) - require.NoError(t, err) - - actualD := hexPrint32LDedup(bytes.NewReader(dat)) - fmt.Println(actualD) - - require.EqualValues(t, expectD8M, actualD) - require.Equal(t, "baga6ea4seaqcgqckrcapts6hea44xbqugwocqneekvyp5fizbo6u3e2biluckla", commd.String()) -} - -func Test8MiBUnpad(t *testing.T) { - data := make([]byte, abi.PaddedPieceSize(8<<20).Unpadded()) - data[0] = 0x01 - - tempFile := filepath.Join(t.TempDir(), "tree.dat") - - commd, err := BuildTreeD(bytes.NewReader(data), true, tempFile, 8<<20) - require.NoError(t, err) - fmt.Println(commd) - - // dump tree.dat - dat, err := os.ReadFile(tempFile) - require.NoError(t, err) - - actualD := hexPrint32LDedup(bytes.NewReader(dat)) - fmt.Println(actualD) - - require.EqualValues(t, expectD8M, actualD) - require.Equal(t, "baga6ea4seaqcgqckrcapts6hea44xbqugwocqneekvyp5fizbo6u3e2biluckla", commd.String()) -} - -/*func Test32Golden(t *testing.T) { - datFile, err := os.Open("../../seal/cac/sc-02-data-tree-d.dat") - require.NoError(t, err) - - bufReader := bufio.NewReaderSize(datFile, 1<<20) - - actualD := hexPrint32LDedup(bufReader) - fmt.Println(actualD) -} -*/ - -var expect32Null = `00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -* -800000000: f5 a5 fd 42 d1 6a 20 30 27 98 ef 6e d3 09 97 9b 43 00 3d 23 20 d9 f0 e8 ea 98 31 a9 27 59 fb 0b -* -c00000000: 37 31 bb 99 ac 68 9f 66 ee f5 97 3e 4a 94 da 18 8f 4d dc ae 58 07 24 fc 6f 3f d6 0d fd 48 83 33 -* -e00000000: 64 2a 60 7e f8 86 b0 04 bf 2c 19 78 46 3a e1 d4 69 3a c0 f4 10 eb 2d 1b 7a 47 fe 20 5e 5e 75 0f -* -f00000000: 57 a2 38 1a 28 65 2b f4 7f 6b ef 7a ca 67 9b e4 ae de 58 71 ab 5c f3 eb 2c 08 11 44 88 cb 85 26 -* -f80000000: 1f 7a c9 59 55 10 e0 9e a4 1c 46 0b 17 64 30 bb 32 2c d6 fb 41 2e c5 7c b1 7d 98 9a 43 10 37 2f -* -fc0000000: fc 7e 92 82 96 e5 16 fa ad e9 86 b2 8f 92 d4 4a 4f 24 b9 35 48 52 23 37 6a 79 90 27 bc 18 f8 33 -* -fe0000000: 08 c4 7b 38 ee 13 bc 43 f4 1b 91 5c 0e ed 99 11 a2 60 86 b3 ed 62 40 1b f9 d5 8b 8d 19 df f6 24 -* -ff0000000: b2 e4 7b fb 11 fa cd 94 1f 62 af 5c 75 0f 3e a5 cc 4d f5 17 d5 c4 f1 6d b2 b4 d7 7b ae c1 a3 2f -* -ff8000000: f9 22 61 60 c8 f9 27 bf dc c4 18 cd f2 03 49 31 46 00 8e ae fb 7d 02 19 4d 5e 54 81 89 00 51 08 -* -ffc000000: 2c 1a 96 4b b9 0b 59 eb fe 0f 6d a2 9a d6 5a e3 e4 17 72 4a 8f 7c 11 74 5a 40 ca c1 e5 e7 40 11 -* -ffe000000: fe e3 78 ce f1 64 04 b1 99 ed e0 b1 3e 11 b6 24 ff 9d 78 4f bb ed 87 8d 83 29 7e 79 5e 02 4f 02 -* -fff000000: 8e 9e 24 03 fa 88 4c f6 23 7f 60 df 25 f8 3e e4 0d ca 9e d8 79 eb 6f 63 52 d1 50 84 f5 ad 0d 3f -* -fff800000: 75 2d 96 93 fa 16 75 24 39 54 76 e3 17 a9 85 80 f0 09 47 af b7 a3 05 40 d6 25 a9 29 1c c1 2a 07 -* -fffc00000: 70 22 f6 0f 7e f6 ad fa 17 11 7a 52 61 9e 30 ce a8 2c 68 07 5a df 1c 66 77 86 ec 50 6e ef 2d 19 -* -fffe00000: d9 98 87 b9 73 57 3a 96 e1 13 93 64 52 36 c1 7b 1f 4c 70 34 d7 23 c7 a9 9f 70 9b b4 da 61 16 2b -* -ffff00000: d0 b5 30 db b0 b4 f2 5c 5d 2f 2a 28 df ee 80 8b 53 41 2a 02 93 1f 18 c4 99 f5 a2 54 08 6b 13 26 -* -ffff80000: 84 c0 42 1b a0 68 5a 01 bf 79 5a 23 44 06 4f e4 24 bd 52 a9 d2 43 77 b3 94 ff 4c 4b 45 68 e8 11 -* -ffffc0000: 65 f2 9e 5d 98 d2 46 c3 8b 38 8c fc 06 db 1f 6b 02 13 03 c5 a2 89 00 0b dc e8 32 a9 c3 ec 42 1c -* -ffffe0000: a2 24 75 08 28 58 50 96 5b 7e 33 4b 31 27 b0 c0 42 b1 d0 46 dc 54 40 21 37 62 7c d8 79 9c e1 3a -* -fffff0000: da fd ab 6d a9 36 44 53 c2 6d 33 72 6b 9f ef e3 43 be 8f 81 64 9e c0 09 aa d3 fa ff 50 61 75 08 -* -fffff8000: d9 41 d5 e0 d6 31 4a 99 5c 33 ff bd 4f be 69 11 8d 73 d4 e5 fd 2c d3 1f 0f 7c 86 eb dd 14 e7 06 -* -fffffc000: 51 4c 43 5c 3d 04 d3 49 a5 36 5f bd 59 ff c7 13 62 91 11 78 59 91 c1 a3 c5 3a f2 20 79 74 1a 2f -* -fffffe000: ad 06 85 39 69 d3 7d 34 ff 08 e0 9f 56 93 0a 4a d1 9a 89 de f6 0c bf ee 7e 1d 33 81 c1 e7 1c 37 -* -ffffff000: 39 56 0e 7b 13 a9 3b 07 a2 43 fd 27 20 ff a7 cb 3e 1d 2e 50 5a b3 62 9e 79 f4 63 13 51 2c da 06 -* -ffffff800: cc c3 c0 12 f5 b0 5e 81 1a 2b bf dd 0f 68 33 b8 42 75 b4 7b f2 29 c0 05 2a 82 48 4f 3c 1a 5b 3d -* -ffffffc00: 7d f2 9b 69 77 31 99 e8 f2 b4 0b 77 91 9d 04 85 09 ee d7 68 e2 c7 29 7b 1f 14 37 03 4f c3 c6 2c -* -ffffffe00: 66 ce 05 a3 66 75 52 cf 45 c0 2b cc 4e 83 92 91 9b de ac 35 de 2f f5 62 71 84 8e 9f 7b 67 51 07 -* -fffffff00: d8 61 02 18 42 5a b5 e9 5b 1c a6 23 9d 29 a2 e4 20 d7 06 a9 6f 37 3e 2f 9c 9a 91 d7 59 d1 9b 01 -* -fffffff80: 6d 36 4b 1e f8 46 44 1a 5a 4a 68 86 23 14 ac c0 a4 6f 01 67 17 e5 34 43 e8 39 ee df 83 c2 85 3c -* -fffffffc0: 07 7e 5f de 35 c5 0a 93 03 a5 50 09 e3 49 8a 4e be df f3 9c 42 b7 10 b7 30 d8 ec 7a c7 af a6 3e -` - -func Test32G(t *testing.T) { - if os.Getenv("LOTUS_TEST_LARGE_SECTORS") != "1" { - t.Skip("skipping large sector test without env LOTUS_TEST_LARGE_SECTORS=1") - } - - data := nullreader.NewNullReader(abi.PaddedPieceSize(32 << 30).Unpadded()) - - tempFile := filepath.Join(t.TempDir(), "tree.dat") - - commd, err := BuildTreeD(data, true, tempFile, 32<<30) - require.NoError(t, err) - fmt.Println(commd) - - // dump tree.dat - datFile, err := os.Open(tempFile) - require.NoError(t, err) - defer func() { - require.NoError(t, datFile.Close()) - }() - - actualD := hexPrint32LDedup(bufio.NewReaderSize(datFile, 1<<20)) - fmt.Println(actualD) - - require.EqualValues(t, expect32Null, actualD) - require.Equal(t, "baga6ea4seaqao7s73y24kcutaosvacpdjgfe5pw76ooefnyqw4ynr3d2y6x2mpq", commd.String()) -} - -func hexPrint32LDedup(r io.Reader) string { - var prevLine []byte - var outStr string - var duplicateLine bool - buffer := make([]byte, 32) - offset := 0 - - for { - n, err := r.Read(buffer) - if err == io.EOF { - break - } - if err != nil { - // Handle the error according to your application's requirements - fmt.Println("Error reading:", err) - break - } - - if string(prevLine) == string(buffer) { - // Mark as duplicate and skip processing - duplicateLine = true - } else { - if duplicateLine { - // Output a marker for the previous duplicate line - outStr += "*\n" - duplicateLine = false - } - // Convert to hex and output - outStr += fmt.Sprintf("%08x: %s\n", offset, toHex(buffer)) - - // Update prevLine - if len(prevLine) != 32 { - prevLine = make([]byte, 32) - } - copy(prevLine, buffer) - } - - offset += n - } - - // If the last line was a duplicate, ensure we mark it - if duplicateLine { - outStr += "*\n" - } - - return outStr -} - -func toHex(data []byte) string { - var hexStr string - for _, b := range data { - hexStr += fmt.Sprintf("%02x ", b) - } - return hexStr -} - -func BenchmarkHashChunk(b *testing.B) { - const benchSize = 1024 * 1024 - - // Generate 1 MiB of random data - randomData := make([]byte, benchSize) - if _, err := rand.Read(randomData); err != nil { - b.Fatalf("Failed to generate random data: %v", err) - } - - // Prepare data structure for hashChunk - data := make([][]byte, 1) - data[0] = randomData - - // append levels until we get to a 32 byte level - for len(data[len(data)-1]) > 32 { - newLevel := make([]byte, len(data[len(data)-1])/2) - data = append(data, newLevel) - } - - b.SetBytes(benchSize) // Set the number of bytes for the benchmark - - b.ResetTimer() // Start the timer after setup - - for i := 0; i < b.N; i++ { - hashChunk(data) - // Use the result in some way to avoid compiler optimization - _ = data[1] - } -} - -func BenchmarkBuildTreeD512M(b *testing.B) { - const dataSize = 512 * 1024 * 1024 // 512 MiB - - // Generate 512 MiB of random data - data := make([]byte, dataSize) - if _, err := rand.Read(data); err != nil { - b.Fatalf("Failed to generate random data: %v", err) - } - - // preallocate NumCPU+1 1MiB/512k/256k/... - // with Pool.Get / Pool.Put, so that they are in the pool - { - nc := runtime.NumCPU() - bufs := [][]byte{} - for i := 0; i < nc+1; i++ { - for sz := 1 << 20; sz > 32; sz >>= 1 { - b := pool.Get(sz) - bufs = append(bufs, b) - } - } - for _, b := range bufs { - pool.Put(b) - } - } - - /*if b.N == 1 { - b.N = 10 - }*/ - - b.SetBytes(int64(dataSize)) // Set the number of bytes for the benchmark - - for i := 0; i < b.N; i++ { - // Create a temporary file for each iteration - tempFile, err := os.CreateTemp("", "tree.dat") - if err != nil { - b.Fatalf("Failed to create temporary file: %v", err) - } - tempFilePath := tempFile.Name() - err = tempFile.Close() - if err != nil { - b.Fatalf("Failed to close temporary file: %v", err) - } - - b.StartTimer() // Start the timer for the BuildTreeD operation - - _, err = BuildTreeD(bytes.NewReader(data), false, tempFilePath, dataSize) - if err != nil { - b.Fatalf("BuildTreeD failed: %v", err) - } - - b.StopTimer() // Stop the timer after BuildTreeD completes - - // Clean up the temporary file - err = os.Remove(tempFilePath) - if err != nil { - b.Fatalf("Failed to remove temporary file: %v", err) - } - } -} - -func TestLayerOffset(t *testing.T) { - { - size := uint64(2048) - - require.Equal(t, uint64(0), layerOffset(size, 0)) - require.Equal(t, size, layerOffset(size, 1)) - require.Equal(t, size+(size/2), layerOffset(size, 2)) - require.Equal(t, size+(size/2)+(size/4), layerOffset(size, 3)) - require.Equal(t, size+(size/2)+(size/4)+(size/8), layerOffset(size, 4)) - require.Equal(t, size+(size/2)+(size/4)+(size/8)+(size/16), layerOffset(size, 5)) - } - - { - size := uint64(32 << 30) - maxLayers := 30 - - for i := 0; i <= maxLayers; i++ { - var expect uint64 - for j := 0; j < i; j++ { - expect += size >> uint64(j) - } - - fmt.Printf("layer %d: %d\n", i, expect) - require.Equal(t, expect, layerOffset(size, i)) - } - } - - { - size := uint64(64 << 30) - maxLayers := 31 - - for i := 0; i <= maxLayers; i++ { - var expect uint64 - for j := 0; j < i; j++ { - expect += size >> uint64(j) - } - - fmt.Printf("layer %d: %d\n", i, expect) - require.Equal(t, expect, layerOffset(size, i)) - } - } -} diff --git a/curiosrc/seal/README.md b/curiosrc/seal/README.md deleted file mode 100644 index b148e4204..000000000 --- a/curiosrc/seal/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Curio Sealer - -## Overview - -The Curio sealer is a collection of harmony tasks and a common poller -which implement the sealing functionality of the Filecoin protocol. - -## Pipeline Tasks - -* SDR pipeline - * `SDR` - Generate SDR layers - * `SDRTrees` - Generate tree files (TreeD, TreeR, TreeC) - * `PreCommitSubmit` - Submit precommit message to the network - * `PoRep` - Generate PoRep proof - * `CommitSubmit` - Submit commit message to the network - -# Poller - -The poller is a background process running on every node which runs any of the -SDR pipeline tasks. It periodically checks the state of sectors in the SDR pipeline -and schedules any tasks to run which will move the sector along the pipeline. - -# Error Handling - -* Pipeline tasks are expected to always finish successfully as harmonytask tasks. - If a sealing task encounters an error, it should mark the sector pipeline entry - as failed and exit without erroring. The poller will then figure out a recovery - strategy for the sector. diff --git a/curiosrc/seal/finalize_pieces.go b/curiosrc/seal/finalize_pieces.go deleted file mode 100644 index 354eed141..000000000 --- a/curiosrc/seal/finalize_pieces.go +++ /dev/null @@ -1,51 +0,0 @@ -package seal - -import ( - "context" - "net/url" - "strconv" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" -) - -func DropSectorPieceRefs(ctx context.Context, db *harmonydb.DB, sid abi.SectorID) error { - //_, err := db.Exec(ctx, `SELECT FROM sectors_sdr_initial_pieces WHERE sp_id = $1 AND sector_number = $2`, sid.Miner, sid.Number) - - var PieceURL []struct { - URL string `db:"data_url"` - } - - err := db.Select(ctx, &PieceURL, `SELECT data_url FROM sectors_sdr_initial_pieces WHERE sp_id = $1 AND sector_number = $2`, sid.Miner, sid.Number) - if err != nil { - return xerrors.Errorf("getting piece url: %w", err) - } - - for _, pu := range PieceURL { - gourl, err := url.Parse(pu.URL) - if err != nil { - log.Errorw("failed to parse piece url", "url", pu.URL, "error", err, "miner", sid.Miner, "sector", sid.Number) - continue - } - - if gourl.Scheme == "pieceref" { - refID, err := strconv.ParseInt(gourl.Opaque, 10, 64) - if err != nil { - log.Errorw("failed to parse piece ref id", "url", pu.URL, "error", err, "miner", sid.Miner, "sector", sid.Number) - continue - } - - n, err := db.Exec(ctx, `DELETE FROM parked_piece_refs WHERE ref_id = $1`, refID) - if err != nil { - log.Errorw("failed to delete piece ref", "url", pu.URL, "error", err, "miner", sid.Miner, "sector", sid.Number) - } - - log.Debugw("deleted piece ref", "url", pu.URL, "miner", sid.Miner, "sector", sid.Number, "rows", n) - } - } - - return err -} diff --git a/curiosrc/seal/poller.go b/curiosrc/seal/poller.go deleted file mode 100644 index 568280bdb..000000000 --- a/curiosrc/seal/poller.go +++ /dev/null @@ -1,285 +0,0 @@ -package seal - -import ( - "context" - "time" - - logging "github.com/ipfs/go-log/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/promise" -) - -var log = logging.Logger("lpseal") - -const ( - pollerSDR = iota - pollerTrees - pollerPrecommitMsg - pollerPoRep - pollerCommitMsg - pollerFinalize - pollerMoveStorage - - numPollers -) - -const sealPollerInterval = 10 * time.Second -const seedEpochConfidence = 3 - -type SealPollerAPI interface { - StateSectorPreCommitInfo(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*miner.SectorPreCommitOnChainInfo, error) - StateSectorGetInfo(ctx context.Context, maddr address.Address, sectorNumber abi.SectorNumber, tsk types.TipSetKey) (*miner.SectorOnChainInfo, error) - ChainHead(context.Context) (*types.TipSet, error) -} - -type SealPoller struct { - db *harmonydb.DB - api SealPollerAPI - - pollers [numPollers]promise.Promise[harmonytask.AddTaskFunc] -} - -func NewPoller(db *harmonydb.DB, api SealPollerAPI) *SealPoller { - return &SealPoller{ - db: db, - api: api, - } -} - -func (s *SealPoller) RunPoller(ctx context.Context) { - ticker := time.NewTicker(sealPollerInterval) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - if err := s.poll(ctx); err != nil { - log.Errorw("polling failed", "error", err) - } - } - } -} - -/* -NOTE: TaskIDs are ONLY set while the tasks are executing or waiting to execute. - This means that there are ~4 states each task can be in: -* Not run, and dependencies not solved (dependencies are 'After' fields of previous stages), task is null, After is false -* Not run, and dependencies solved, task is null, After is false -* Running or queued, task is set, After is false -* Finished, task is null, After is true -*/ - -type pollTask struct { - SpID int64 `db:"sp_id"` - SectorNumber int64 `db:"sector_number"` - - TaskSDR *int64 `db:"task_id_sdr"` - AfterSDR bool `db:"after_sdr"` - - TaskTreeD *int64 `db:"task_id_tree_d"` - AfterTreeD bool `db:"after_tree_d"` - - TaskTreeC *int64 `db:"task_id_tree_c"` - AfterTreeC bool `db:"after_tree_c"` - - TaskTreeR *int64 `db:"task_id_tree_r"` - AfterTreeR bool `db:"after_tree_r"` - - TaskPrecommitMsg *int64 `db:"task_id_precommit_msg"` - AfterPrecommitMsg bool `db:"after_precommit_msg"` - - AfterPrecommitMsgSuccess bool `db:"after_precommit_msg_success"` - SeedEpoch *int64 `db:"seed_epoch"` - - TaskPoRep *int64 `db:"task_id_porep"` - PoRepProof []byte `db:"porep_proof"` - AfterPoRep bool `db:"after_porep"` - - TaskFinalize *int64 `db:"task_id_finalize"` - AfterFinalize bool `db:"after_finalize"` - - TaskMoveStorage *int64 `db:"task_id_move_storage"` - AfterMoveStorage bool `db:"after_move_storage"` - - TaskCommitMsg *int64 `db:"task_id_commit_msg"` - AfterCommitMsg bool `db:"after_commit_msg"` - - AfterCommitMsgSuccess bool `db:"after_commit_msg_success"` - - Failed bool `db:"failed"` - FailedReason string `db:"failed_reason"` -} - -func (s *SealPoller) poll(ctx context.Context) error { - var tasks []pollTask - - err := s.db.Select(ctx, &tasks, `SELECT - sp_id, sector_number, - task_id_sdr, after_sdr, - task_id_tree_d, after_tree_d, - task_id_tree_c, after_tree_c, - task_id_tree_r, after_tree_r, - task_id_precommit_msg, after_precommit_msg, - after_precommit_msg_success, seed_epoch, - task_id_porep, porep_proof, after_porep, - task_id_finalize, after_finalize, - task_id_move_storage, after_move_storage, - task_id_commit_msg, after_commit_msg, - after_commit_msg_success, - failed, failed_reason - FROM sectors_sdr_pipeline WHERE after_commit_msg_success != TRUE OR after_move_storage != TRUE`) - if err != nil { - return err - } - - for _, task := range tasks { - task := task - if task.Failed { - continue - } - - ts, err := s.api.ChainHead(ctx) - if err != nil { - return xerrors.Errorf("getting chain head: %w", err) - } - - s.pollStartSDR(ctx, task) - s.pollStartSDRTrees(ctx, task) - s.pollStartPrecommitMsg(ctx, task) - s.mustPoll(s.pollPrecommitMsgLanded(ctx, task)) - s.pollStartPoRep(ctx, task, ts) - s.pollStartFinalize(ctx, task, ts) - s.pollStartMoveStorage(ctx, task) - s.pollStartCommitMsg(ctx, task) - s.mustPoll(s.pollCommitMsgLanded(ctx, task)) - } - - return nil -} - -func (s *SealPoller) pollStartSDR(ctx context.Context, task pollTask) { - if !task.AfterSDR && task.TaskSDR == nil && s.pollers[pollerSDR].IsSet() { - s.pollers[pollerSDR].Val(ctx)(func(id harmonytask.TaskID, tx *harmonydb.Tx) (shouldCommit bool, seriousError error) { - n, err := tx.Exec(`UPDATE sectors_sdr_pipeline SET task_id_sdr = $1 WHERE sp_id = $2 AND sector_number = $3 AND task_id_sdr IS NULL`, id, task.SpID, task.SectorNumber) - if err != nil { - return false, xerrors.Errorf("update sectors_sdr_pipeline: %w", err) - } - if n != 1 { - return false, xerrors.Errorf("expected to update 1 row, updated %d", n) - } - - return true, nil - }) - } -} - -func (t pollTask) afterSDR() bool { - return t.AfterSDR -} - -func (s *SealPoller) pollStartSDRTrees(ctx context.Context, task pollTask) { - if !task.AfterTreeD && !task.AfterTreeC && !task.AfterTreeR && - task.TaskTreeD == nil && task.TaskTreeC == nil && task.TaskTreeR == nil && - s.pollers[pollerTrees].IsSet() && task.AfterSDR { - - s.pollers[pollerTrees].Val(ctx)(func(id harmonytask.TaskID, tx *harmonydb.Tx) (shouldCommit bool, seriousError error) { - n, err := tx.Exec(`UPDATE sectors_sdr_pipeline SET task_id_tree_d = $1, task_id_tree_c = $1, task_id_tree_r = $1 - WHERE sp_id = $2 AND sector_number = $3 AND after_sdr = TRUE AND task_id_tree_d IS NULL AND task_id_tree_c IS NULL AND task_id_tree_r IS NULL`, id, task.SpID, task.SectorNumber) - if err != nil { - return false, xerrors.Errorf("update sectors_sdr_pipeline: %w", err) - } - if n != 1 { - return false, xerrors.Errorf("expected to update 1 row, updated %d", n) - } - - return true, nil - }) - } -} - -func (t pollTask) afterTrees() bool { - return t.AfterTreeD && t.AfterTreeC && t.AfterTreeR && t.afterSDR() -} - -func (t pollTask) afterPrecommitMsg() bool { - return t.AfterPrecommitMsg && t.afterTrees() -} - -func (t pollTask) afterPrecommitMsgSuccess() bool { - return t.AfterPrecommitMsgSuccess && t.afterPrecommitMsg() -} - -func (s *SealPoller) pollStartPoRep(ctx context.Context, task pollTask, ts *types.TipSet) { - if s.pollers[pollerPoRep].IsSet() && task.afterPrecommitMsgSuccess() && task.SeedEpoch != nil && - task.TaskPoRep == nil && !task.AfterPoRep && - ts.Height() >= abi.ChainEpoch(*task.SeedEpoch+seedEpochConfidence) { - - s.pollers[pollerPoRep].Val(ctx)(func(id harmonytask.TaskID, tx *harmonydb.Tx) (shouldCommit bool, seriousError error) { - n, err := tx.Exec(`UPDATE sectors_sdr_pipeline SET task_id_porep = $1 WHERE sp_id = $2 AND sector_number = $3 AND task_id_porep IS NULL`, id, task.SpID, task.SectorNumber) - if err != nil { - return false, xerrors.Errorf("update sectors_sdr_pipeline: %w", err) - } - if n != 1 { - return false, xerrors.Errorf("expected to update 1 row, updated %d", n) - } - - return true, nil - }) - } -} - -func (t pollTask) afterPoRep() bool { - return t.AfterPoRep && t.afterPrecommitMsgSuccess() -} - -func (s *SealPoller) pollStartFinalize(ctx context.Context, task pollTask, ts *types.TipSet) { - if s.pollers[pollerFinalize].IsSet() && task.afterPoRep() && !task.AfterFinalize && task.TaskFinalize == nil { - s.pollers[pollerFinalize].Val(ctx)(func(id harmonytask.TaskID, tx *harmonydb.Tx) (shouldCommit bool, seriousError error) { - n, err := tx.Exec(`UPDATE sectors_sdr_pipeline SET task_id_finalize = $1 WHERE sp_id = $2 AND sector_number = $3 AND task_id_finalize IS NULL`, id, task.SpID, task.SectorNumber) - if err != nil { - return false, xerrors.Errorf("update sectors_sdr_pipeline: %w", err) - } - if n != 1 { - return false, xerrors.Errorf("expected to update 1 row, updated %d", n) - } - - return true, nil - }) - } -} - -func (t pollTask) afterFinalize() bool { - return t.AfterFinalize && t.afterPoRep() -} - -func (s *SealPoller) pollStartMoveStorage(ctx context.Context, task pollTask) { - if s.pollers[pollerMoveStorage].IsSet() && task.afterFinalize() && !task.AfterMoveStorage && task.TaskMoveStorage == nil { - s.pollers[pollerMoveStorage].Val(ctx)(func(id harmonytask.TaskID, tx *harmonydb.Tx) (shouldCommit bool, seriousError error) { - n, err := tx.Exec(`UPDATE sectors_sdr_pipeline SET task_id_move_storage = $1 WHERE sp_id = $2 AND sector_number = $3 AND task_id_move_storage IS NULL`, id, task.SpID, task.SectorNumber) - if err != nil { - return false, xerrors.Errorf("update sectors_sdr_pipeline: %w", err) - } - if n != 1 { - return false, xerrors.Errorf("expected to update 1 row, updated %d", n) - } - - return true, nil - }) - } -} - -func (s *SealPoller) mustPoll(err error) { - if err != nil { - log.Errorw("poller operation failed", "error", err) - } -} diff --git a/curiosrc/seal/poller_commit_msg.go b/curiosrc/seal/poller_commit_msg.go deleted file mode 100644 index 9a88129b0..000000000 --- a/curiosrc/seal/poller_commit_msg.go +++ /dev/null @@ -1,108 +0,0 @@ -package seal - -import ( - "context" - - "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/exitcode" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" -) - -func (s *SealPoller) pollStartCommitMsg(ctx context.Context, task pollTask) { - if task.afterPoRep() && len(task.PoRepProof) > 0 && task.TaskCommitMsg == nil && !task.AfterCommitMsg && s.pollers[pollerCommitMsg].IsSet() { - s.pollers[pollerCommitMsg].Val(ctx)(func(id harmonytask.TaskID, tx *harmonydb.Tx) (shouldCommit bool, seriousError error) { - n, err := tx.Exec(`UPDATE sectors_sdr_pipeline SET task_id_commit_msg = $1 WHERE sp_id = $2 AND sector_number = $3 AND task_id_commit_msg IS NULL`, id, task.SpID, task.SectorNumber) - if err != nil { - return false, xerrors.Errorf("update sectors_sdr_pipeline: %w", err) - } - if n != 1 { - return false, xerrors.Errorf("expected to update 1 row, updated %d", n) - } - - return true, nil - }) - } -} - -func (s *SealPoller) pollCommitMsgLanded(ctx context.Context, task pollTask) error { - if task.AfterCommitMsg && !task.AfterCommitMsgSuccess && s.pollers[pollerCommitMsg].IsSet() { - var execResult []dbExecResult - - err := s.db.Select(ctx, &execResult, `SELECT spipeline.precommit_msg_cid, spipeline.commit_msg_cid, executed_tsk_cid, executed_tsk_epoch, executed_msg_cid, executed_rcpt_exitcode, executed_rcpt_gas_used - FROM sectors_sdr_pipeline spipeline - JOIN message_waits ON spipeline.commit_msg_cid = message_waits.signed_message_cid - WHERE sp_id = $1 AND sector_number = $2 AND executed_tsk_epoch IS NOT NULL`, task.SpID, task.SectorNumber) - if err != nil { - log.Errorw("failed to query message_waits", "error", err) - } - - if len(execResult) > 0 { - maddr, err := address.NewIDAddress(uint64(task.SpID)) - if err != nil { - return err - } - - if exitcode.ExitCode(execResult[0].ExecutedRcptExitCode) != exitcode.Ok { - return s.pollCommitMsgFail(ctx, task, execResult[0]) - } - - si, err := s.api.StateSectorGetInfo(ctx, maddr, abi.SectorNumber(task.SectorNumber), types.EmptyTSK) - if err != nil { - return xerrors.Errorf("get sector info: %w", err) - } - - if si == nil { - log.Errorw("todo handle missing sector info (not found after cron)", "sp", task.SpID, "sector", task.SectorNumber, "exec_epoch", execResult[0].ExecutedTskEpoch, "exec_tskcid", execResult[0].ExecutedTskCID, "msg_cid", execResult[0].ExecutedMsgCID) - // todo handdle missing sector info (not found after cron) - } else { - // yay! - - _, err := s.db.Exec(ctx, `UPDATE sectors_sdr_pipeline SET - after_commit_msg_success = TRUE, commit_msg_tsk = $1 - WHERE sp_id = $2 AND sector_number = $3 AND after_commit_msg_success = FALSE`, - execResult[0].ExecutedTskCID, task.SpID, task.SectorNumber) - if err != nil { - return xerrors.Errorf("update sectors_sdr_pipeline: %w", err) - } - } - } - } - - return nil -} - -func (s *SealPoller) pollCommitMsgFail(ctx context.Context, task pollTask, execResult dbExecResult) error { - switch exitcode.ExitCode(execResult.ExecutedRcptExitCode) { - case exitcode.SysErrInsufficientFunds: - fallthrough - case exitcode.SysErrOutOfGas: - // just retry - return s.pollRetryCommitMsgSend(ctx, task, execResult) - default: - return xerrors.Errorf("commit message failed with exit code %s", exitcode.ExitCode(execResult.ExecutedRcptExitCode)) - } -} - -func (s *SealPoller) pollRetryCommitMsgSend(ctx context.Context, task pollTask, execResult dbExecResult) error { - if execResult.CommitMsgCID == nil { - return xerrors.Errorf("commit msg cid was nil") - } - - // make the pipeline entry seem like precommit send didn't happen, next poll loop will retry - - _, err := s.db.Exec(ctx, `UPDATE sectors_sdr_pipeline SET - commit_msg_cid = NULL, task_id_commit_msg = NULL, after_commit_msg = FALSE - WHERE commit_msg_cid = $1 AND sp_id = $2 AND sector_number = $3 AND after_commit_msg_success = FALSE`, - *execResult.CommitMsgCID, task.SpID, task.SectorNumber) - if err != nil { - return xerrors.Errorf("update sectors_sdr_pipeline to retry precommit msg send: %w", err) - } - - return nil -} diff --git a/curiosrc/seal/poller_precommit_msg.go b/curiosrc/seal/poller_precommit_msg.go deleted file mode 100644 index 4372cbb92..000000000 --- a/curiosrc/seal/poller_precommit_msg.go +++ /dev/null @@ -1,119 +0,0 @@ -package seal - -import ( - "context" - - "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/exitcode" - - "github.com/filecoin-project/lotus/chain/actors/policy" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" -) - -func (s *SealPoller) pollStartPrecommitMsg(ctx context.Context, task pollTask) { - if task.TaskPrecommitMsg == nil && !task.AfterPrecommitMsg && task.afterTrees() && s.pollers[pollerPrecommitMsg].IsSet() { - s.pollers[pollerPrecommitMsg].Val(ctx)(func(id harmonytask.TaskID, tx *harmonydb.Tx) (shouldCommit bool, seriousError error) { - n, err := tx.Exec(`UPDATE sectors_sdr_pipeline SET task_id_precommit_msg = $1 WHERE sp_id = $2 AND sector_number = $3 AND task_id_precommit_msg IS NULL AND after_tree_r = TRUE AND after_tree_d = TRUE`, id, task.SpID, task.SectorNumber) - if err != nil { - return false, xerrors.Errorf("update sectors_sdr_pipeline: %w", err) - } - if n != 1 { - return false, xerrors.Errorf("expected to update 1 row, updated %d", n) - } - - return true, nil - }) - } -} - -type dbExecResult struct { - PrecommitMsgCID *string `db:"precommit_msg_cid"` - CommitMsgCID *string `db:"commit_msg_cid"` - - ExecutedTskCID string `db:"executed_tsk_cid"` - ExecutedTskEpoch int64 `db:"executed_tsk_epoch"` - ExecutedMsgCID string `db:"executed_msg_cid"` - - ExecutedRcptExitCode int64 `db:"executed_rcpt_exitcode"` - ExecutedRcptGasUsed int64 `db:"executed_rcpt_gas_used"` -} - -func (s *SealPoller) pollPrecommitMsgLanded(ctx context.Context, task pollTask) error { - if task.AfterPrecommitMsg && !task.AfterPrecommitMsgSuccess { - var execResult []dbExecResult - - err := s.db.Select(ctx, &execResult, `SELECT spipeline.precommit_msg_cid, spipeline.commit_msg_cid, executed_tsk_cid, executed_tsk_epoch, executed_msg_cid, executed_rcpt_exitcode, executed_rcpt_gas_used - FROM sectors_sdr_pipeline spipeline - JOIN message_waits ON spipeline.precommit_msg_cid = message_waits.signed_message_cid - WHERE sp_id = $1 AND sector_number = $2 AND executed_tsk_epoch IS NOT NULL`, task.SpID, task.SectorNumber) - if err != nil { - log.Errorw("failed to query message_waits", "error", err) - } - - if len(execResult) > 0 { - if exitcode.ExitCode(execResult[0].ExecutedRcptExitCode) != exitcode.Ok { - return s.pollPrecommitMsgFail(ctx, task, execResult[0]) - } - - maddr, err := address.NewIDAddress(uint64(task.SpID)) - if err != nil { - return err - } - - pci, err := s.api.StateSectorPreCommitInfo(ctx, maddr, abi.SectorNumber(task.SectorNumber), types.EmptyTSK) - if err != nil { - return xerrors.Errorf("get precommit info: %w", err) - } - - if pci != nil { - randHeight := pci.PreCommitEpoch + policy.GetPreCommitChallengeDelay() - - _, err := s.db.Exec(ctx, `UPDATE sectors_sdr_pipeline SET - seed_epoch = $1, precommit_msg_tsk = $2, after_precommit_msg_success = TRUE - WHERE sp_id = $3 AND sector_number = $4 AND seed_epoch IS NULL`, - randHeight, execResult[0].ExecutedTskCID, task.SpID, task.SectorNumber) - if err != nil { - return xerrors.Errorf("update sectors_sdr_pipeline: %w", err) - } - } // todo handle missing precommit info (eg expired precommit) - - } - } - - return nil -} - -func (s *SealPoller) pollPrecommitMsgFail(ctx context.Context, task pollTask, execResult dbExecResult) error { - switch exitcode.ExitCode(execResult.ExecutedRcptExitCode) { - case exitcode.SysErrInsufficientFunds: - fallthrough - case exitcode.SysErrOutOfGas: - // just retry - return s.pollRetryPrecommitMsgSend(ctx, task, execResult) - default: - return xerrors.Errorf("precommit message failed with exit code %s", exitcode.ExitCode(execResult.ExecutedRcptExitCode)) - } -} - -func (s *SealPoller) pollRetryPrecommitMsgSend(ctx context.Context, task pollTask, execResult dbExecResult) error { - if execResult.PrecommitMsgCID == nil { - return xerrors.Errorf("precommit msg cid was nil") - } - - // make the pipeline entry seem like precommit send didn't happen, next poll loop will retry - - _, err := s.db.Exec(ctx, `UPDATE sectors_sdr_pipeline SET - precommit_msg_cid = NULL, task_id_precommit_msg = NULL, after_precommit_msg = FALSE - WHERE precommit_msg_cid = $1 AND sp_id = $2 AND sector_number = $3 AND after_precommit_msg_success = FALSE`, - *execResult.PrecommitMsgCID, task.SpID, task.SectorNumber) - if err != nil { - return xerrors.Errorf("update sectors_sdr_pipeline to retry precommit msg send: %w", err) - } - - return nil -} diff --git a/curiosrc/seal/sector_num_alloc.go b/curiosrc/seal/sector_num_alloc.go deleted file mode 100644 index 010ebee39..000000000 --- a/curiosrc/seal/sector_num_alloc.go +++ /dev/null @@ -1,127 +0,0 @@ -package seal - -import ( - "context" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-bitfield" - rlepluslazy "github.com/filecoin-project/go-bitfield/rle" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" -) - -type AllocAPI interface { - StateMinerAllocated(context.Context, address.Address, types.TipSetKey) (*bitfield.BitField, error) -} - -func AllocateSectorNumbers(ctx context.Context, a AllocAPI, db *harmonydb.DB, maddr address.Address, count int, txcb ...func(*harmonydb.Tx, []abi.SectorNumber) (bool, error)) ([]abi.SectorNumber, error) { - chainAlloc, err := a.StateMinerAllocated(ctx, maddr, types.EmptyTSK) - if err != nil { - return nil, xerrors.Errorf("getting on-chain allocated sector numbers: %w", err) - } - - mid, err := address.IDFromAddress(maddr) - if err != nil { - return nil, xerrors.Errorf("getting miner id: %w", err) - } - - var res []abi.SectorNumber - - comm, err := db.BeginTransaction(ctx, func(tx *harmonydb.Tx) (commit bool, err error) { - res = nil // reset result in case of retry - - // query from db, if exists unmarsal to bitfield - var dbAllocated bitfield.BitField - var rawJson []byte - - err = tx.QueryRow("SELECT COALESCE(allocated, '[0]') from sectors_allocated_numbers sa FULL OUTER JOIN (SELECT 1) AS d ON TRUE WHERE sp_id = $1 OR sp_id IS NULL", mid).Scan(&rawJson) - if err != nil { - return false, xerrors.Errorf("querying allocated sector numbers: %w", err) - } - - if rawJson != nil { - err = dbAllocated.UnmarshalJSON(rawJson) - if err != nil { - return false, xerrors.Errorf("unmarshaling allocated sector numbers: %w", err) - } - } - - if err := dbAllocated.UnmarshalJSON(rawJson); err != nil { - return false, xerrors.Errorf("unmarshaling allocated sector numbers: %w", err) - } - - merged, err := bitfield.MergeBitFields(*chainAlloc, dbAllocated) - if err != nil { - return false, xerrors.Errorf("merging allocated sector numbers: %w", err) - } - - allAssignable, err := bitfield.NewFromIter(&rlepluslazy.RunSliceIterator{Runs: []rlepluslazy.Run{ - { - Val: true, - Len: abi.MaxSectorNumber, - }, - }}) - if err != nil { - return false, xerrors.Errorf("creating assignable sector numbers: %w", err) - } - - inverted, err := bitfield.SubtractBitField(allAssignable, merged) - if err != nil { - return false, xerrors.Errorf("subtracting allocated sector numbers: %w", err) - } - - toAlloc, err := inverted.Slice(0, uint64(count)) - if err != nil { - return false, xerrors.Errorf("getting slice of allocated sector numbers: %w", err) - } - - err = toAlloc.ForEach(func(u uint64) error { - res = append(res, abi.SectorNumber(u)) - return nil - }) - if err != nil { - return false, xerrors.Errorf("iterating allocated sector numbers: %w", err) - } - - toPersist, err := bitfield.MergeBitFields(merged, toAlloc) - if err != nil { - return false, xerrors.Errorf("merging allocated sector numbers: %w", err) - } - - rawJson, err = toPersist.MarshalJSON() - if err != nil { - return false, xerrors.Errorf("marshaling allocated sector numbers: %w", err) - } - - _, err = tx.Exec("INSERT INTO sectors_allocated_numbers(sp_id, allocated) VALUES($1, $2) ON CONFLICT(sp_id) DO UPDATE SET allocated = $2", mid, rawJson) - if err != nil { - return false, xerrors.Errorf("persisting allocated sector numbers: %w", err) - } - - for i, f := range txcb { - commit, err = f(tx, res) - if err != nil { - return false, xerrors.Errorf("executing tx callback %d: %w", i, err) - } - - if !commit { - return false, nil - } - } - - return true, nil - }, harmonydb.OptionRetry()) - - if err != nil { - return nil, xerrors.Errorf("allocating sector numbers: %w", err) - } - if !comm { - return nil, xerrors.Errorf("allocating sector numbers: commit failed") - } - - return res, nil -} diff --git a/curiosrc/seal/task_finalize.go b/curiosrc/seal/task_finalize.go deleted file mode 100644 index 2b362d7be..000000000 --- a/curiosrc/seal/task_finalize.go +++ /dev/null @@ -1,156 +0,0 @@ -package seal - -import ( - "context" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/curiosrc/ffi" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -type FinalizeTask struct { - max int - sp *SealPoller - sc *ffi.SealCalls - db *harmonydb.DB -} - -func NewFinalizeTask(max int, sp *SealPoller, sc *ffi.SealCalls, db *harmonydb.DB) *FinalizeTask { - return &FinalizeTask{ - max: max, - sp: sp, - sc: sc, - db: db, - } -} - -func (f *FinalizeTask) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - var tasks []struct { - SpID int64 `db:"sp_id"` - SectorNumber int64 `db:"sector_number"` - RegSealProof int64 `db:"reg_seal_proof"` - } - - ctx := context.Background() - - err = f.db.Select(ctx, &tasks, ` - SELECT sp_id, sector_number, reg_seal_proof FROM sectors_sdr_pipeline WHERE task_id_finalize = $1`, taskID) - if err != nil { - return false, xerrors.Errorf("getting task: %w", err) - } - - if len(tasks) != 1 { - return false, xerrors.Errorf("expected one task") - } - task := tasks[0] - - var keepUnsealed bool - - if err := f.db.QueryRow(ctx, `SELECT COALESCE(BOOL_OR(NOT data_delete_on_finalize), FALSE) FROM sectors_sdr_initial_pieces WHERE sp_id = $1 AND sector_number = $2`, task.SpID, task.SectorNumber).Scan(&keepUnsealed); err != nil { - return false, err - } - - sector := storiface.SectorRef{ - ID: abi.SectorID{ - Miner: abi.ActorID(task.SpID), - Number: abi.SectorNumber(task.SectorNumber), - }, - ProofType: abi.RegisteredSealProof(task.RegSealProof), - } - - err = f.sc.FinalizeSector(ctx, sector, keepUnsealed) - if err != nil { - return false, xerrors.Errorf("finalizing sector: %w", err) - } - - if err := DropSectorPieceRefs(ctx, f.db, sector.ID); err != nil { - return false, xerrors.Errorf("dropping sector piece refs: %w", err) - } - - // set after_finalize - _, err = f.db.Exec(ctx, `update sectors_sdr_pipeline set after_finalize=true where task_id_finalize=$1`, taskID) - if err != nil { - return false, xerrors.Errorf("updating task: %w", err) - } - - return true, nil -} - -func (f *FinalizeTask) CanAccept(ids []harmonytask.TaskID, engine *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - var tasks []struct { - TaskID harmonytask.TaskID `db:"task_id_finalize"` - SpID int64 `db:"sp_id"` - SectorNumber int64 `db:"sector_number"` - StorageID string `db:"storage_id"` - } - - if storiface.FTCache != 4 { - panic("storiface.FTCache != 4") - } - - ctx := context.Background() - - indIDs := make([]int64, len(ids)) - for i, id := range ids { - indIDs[i] = int64(id) - } - - err := f.db.Select(ctx, &tasks, ` - SELECT p.task_id_finalize, p.sp_id, p.sector_number, l.storage_id FROM sectors_sdr_pipeline p - INNER JOIN sector_location l ON p.sp_id = l.miner_id AND p.sector_number = l.sector_num - WHERE task_id_finalize = ANY ($1) AND l.sector_filetype = 4 -`, indIDs) - if err != nil { - return nil, xerrors.Errorf("getting tasks: %w", err) - } - - ls, err := f.sc.LocalStorage(ctx) - if err != nil { - return nil, xerrors.Errorf("getting local storage: %w", err) - } - - acceptables := map[harmonytask.TaskID]bool{} - - for _, t := range ids { - acceptables[t] = true - } - - for _, t := range tasks { - if _, ok := acceptables[t.TaskID]; !ok { - continue - } - - for _, l := range ls { - if string(l.ID) == t.StorageID { - return &t.TaskID, nil - } - } - } - - return nil, nil -} - -func (f *FinalizeTask) TypeDetails() harmonytask.TaskTypeDetails { - return harmonytask.TaskTypeDetails{ - Max: f.max, - Name: "Finalize", - Cost: resources.Resources{ - Cpu: 1, - Gpu: 0, - Ram: 100 << 20, - }, - MaxFailures: 10, - } -} - -func (f *FinalizeTask) Adder(taskFunc harmonytask.AddTaskFunc) { - f.sp.pollers[pollerFinalize].Set(taskFunc) -} - -var _ harmonytask.TaskInterface = &FinalizeTask{} diff --git a/curiosrc/seal/task_movestorage.go b/curiosrc/seal/task_movestorage.go deleted file mode 100644 index 6037a390d..000000000 --- a/curiosrc/seal/task_movestorage.go +++ /dev/null @@ -1,176 +0,0 @@ -package seal - -import ( - "context" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/curiosrc/ffi" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -type MoveStorageTask struct { - sp *SealPoller - sc *ffi.SealCalls - db *harmonydb.DB - - max int -} - -func NewMoveStorageTask(sp *SealPoller, sc *ffi.SealCalls, db *harmonydb.DB, max int) *MoveStorageTask { - return &MoveStorageTask{ - max: max, - sp: sp, - sc: sc, - db: db, - } -} - -func (m *MoveStorageTask) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - var tasks []struct { - SpID int64 `db:"sp_id"` - SectorNumber int64 `db:"sector_number"` - RegSealProof int64 `db:"reg_seal_proof"` - } - - ctx := context.Background() - - err = m.db.Select(ctx, &tasks, ` - SELECT sp_id, sector_number, reg_seal_proof FROM sectors_sdr_pipeline WHERE task_id_move_storage = $1`, taskID) - if err != nil { - return false, xerrors.Errorf("getting task: %w", err) - } - if len(tasks) != 1 { - return false, xerrors.Errorf("expected one task") - } - task := tasks[0] - - sector := storiface.SectorRef{ - ID: abi.SectorID{ - Miner: abi.ActorID(task.SpID), - Number: abi.SectorNumber(task.SectorNumber), - }, - ProofType: abi.RegisteredSealProof(task.RegSealProof), - } - - err = m.sc.MoveStorage(ctx, sector, &taskID) - if err != nil { - return false, xerrors.Errorf("moving storage: %w", err) - } - - _, err = m.db.Exec(ctx, `UPDATE sectors_sdr_pipeline SET after_move_storage = true WHERE task_id_move_storage = $1`, taskID) - if err != nil { - return false, xerrors.Errorf("updating task: %w", err) - } - - return true, nil -} - -func (m *MoveStorageTask) CanAccept(ids []harmonytask.TaskID, engine *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - - ctx := context.Background() - /* - - var tasks []struct { - TaskID harmonytask.TaskID `db:"task_id_finalize"` - SpID int64 `db:"sp_id"` - SectorNumber int64 `db:"sector_number"` - StorageID string `db:"storage_id"` - } - - indIDs := make([]int64, len(ids)) - for i, id := range ids { - indIDs[i] = int64(id) - } - err := m.db.Select(ctx, &tasks, ` - select p.task_id_move_storage, p.sp_id, p.sector_number, l.storage_id from sectors_sdr_pipeline p - inner join sector_location l on p.sp_id=l.miner_id and p.sector_number=l.sector_num - where task_id_move_storage in ($1) and l.sector_filetype=4`, indIDs) - if err != nil { - return nil, xerrors.Errorf("getting tasks: %w", err) - } - - ls, err := m.sc.LocalStorage(ctx) - if err != nil { - return nil, xerrors.Errorf("getting local storage: %w", err) - } - - acceptables := map[harmonytask.TaskID]bool{} - - for _, t := range ids { - acceptables[t] = true - } - - for _, t := range tasks { - - } - - todo some smarts - * yield a schedule cycle/s if we have moves already in progress - */ - - //// - ls, err := m.sc.LocalStorage(ctx) - if err != nil { - return nil, xerrors.Errorf("getting local storage: %w", err) - } - var haveStorage bool - for _, l := range ls { - if l.CanStore { - haveStorage = true - break - } - } - - if !haveStorage { - return nil, nil - } - - id := ids[0] - return &id, nil -} - -func (m *MoveStorageTask) TypeDetails() harmonytask.TaskTypeDetails { - ssize := abi.SectorSize(32 << 30) // todo task details needs taskID to get correct sector size - if isDevnet { - ssize = abi.SectorSize(2 << 20) - } - - return harmonytask.TaskTypeDetails{ - Max: m.max, - Name: "MoveStorage", - Cost: resources.Resources{ - Cpu: 1, - Gpu: 0, - Ram: 128 << 20, - Storage: m.sc.Storage(m.taskToSector, storiface.FTNone, storiface.FTCache|storiface.FTSealed|storiface.FTUnsealed, ssize, storiface.PathStorage), - }, - MaxFailures: 10, - } -} - -func (m *MoveStorageTask) taskToSector(id harmonytask.TaskID) (ffi.SectorRef, error) { - var refs []ffi.SectorRef - - err := m.db.Select(context.Background(), &refs, `SELECT sp_id, sector_number, reg_seal_proof FROM sectors_sdr_pipeline WHERE task_id_move_storage = $1`, id) - if err != nil { - return ffi.SectorRef{}, xerrors.Errorf("getting sector ref: %w", err) - } - - if len(refs) != 1 { - return ffi.SectorRef{}, xerrors.Errorf("expected 1 sector ref, got %d", len(refs)) - } - - return refs[0], nil -} - -func (m *MoveStorageTask) Adder(taskFunc harmonytask.AddTaskFunc) { - m.sp.pollers[pollerMoveStorage].Set(taskFunc) -} - -var _ harmonytask.TaskInterface = &MoveStorageTask{} diff --git a/curiosrc/seal/task_porep.go b/curiosrc/seal/task_porep.go deleted file mode 100644 index 58e307bc0..000000000 --- a/curiosrc/seal/task_porep.go +++ /dev/null @@ -1,216 +0,0 @@ -package seal - -import ( - "bytes" - "context" - "strings" - - "github.com/ipfs/go-cid" - "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/crypto" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/curiosrc/ffi" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -type PoRepAPI interface { - ChainHead(context.Context) (*types.TipSet, error) - StateGetRandomnessFromBeacon(context.Context, crypto.DomainSeparationTag, abi.ChainEpoch, []byte, types.TipSetKey) (abi.Randomness, error) -} - -type PoRepTask struct { - db *harmonydb.DB - api PoRepAPI - sp *SealPoller - sc *ffi.SealCalls - - max int -} - -func NewPoRepTask(db *harmonydb.DB, api PoRepAPI, sp *SealPoller, sc *ffi.SealCalls, maxPoRep int) *PoRepTask { - return &PoRepTask{ - db: db, - api: api, - sp: sp, - sc: sc, - max: maxPoRep, - } -} - -func (p *PoRepTask) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - ctx := context.Background() - - var sectorParamsArr []struct { - SpID int64 `db:"sp_id"` - SectorNumber int64 `db:"sector_number"` - RegSealProof abi.RegisteredSealProof `db:"reg_seal_proof"` - TicketEpoch abi.ChainEpoch `db:"ticket_epoch"` - TicketValue []byte `db:"ticket_value"` - SeedEpoch abi.ChainEpoch `db:"seed_epoch"` - SealedCID string `db:"tree_r_cid"` - UnsealedCID string `db:"tree_d_cid"` - } - - err = p.db.Select(ctx, §orParamsArr, ` - SELECT sp_id, sector_number, reg_seal_proof, ticket_epoch, ticket_value, seed_epoch, tree_r_cid, tree_d_cid - FROM sectors_sdr_pipeline - WHERE task_id_porep = $1`, taskID) - if err != nil { - return false, err - } - if len(sectorParamsArr) != 1 { - return false, xerrors.Errorf("expected 1 sector params, got %d", len(sectorParamsArr)) - } - sectorParams := sectorParamsArr[0] - - sealed, err := cid.Parse(sectorParams.SealedCID) - if err != nil { - return false, xerrors.Errorf("failed to parse sealed cid: %w", err) - } - - unsealed, err := cid.Parse(sectorParams.UnsealedCID) - if err != nil { - return false, xerrors.Errorf("failed to parse unsealed cid: %w", err) - } - - ts, err := p.api.ChainHead(ctx) - if err != nil { - return false, xerrors.Errorf("failed to get chain head: %w", err) - } - - maddr, err := address.NewIDAddress(uint64(sectorParams.SpID)) - if err != nil { - return false, xerrors.Errorf("failed to create miner address: %w", err) - } - - buf := new(bytes.Buffer) - if err := maddr.MarshalCBOR(buf); err != nil { - return false, xerrors.Errorf("failed to marshal miner address: %w", err) - } - - rand, err := p.api.StateGetRandomnessFromBeacon(ctx, crypto.DomainSeparationTag_InteractiveSealChallengeSeed, sectorParams.SeedEpoch, buf.Bytes(), ts.Key()) - if err != nil { - return false, xerrors.Errorf("failed to get randomness for computing seal proof: %w", err) - } - - sr := storiface.SectorRef{ - ID: abi.SectorID{ - Miner: abi.ActorID(sectorParams.SpID), - Number: abi.SectorNumber(sectorParams.SectorNumber), - }, - ProofType: sectorParams.RegSealProof, - } - - // COMPUTE THE PROOF! - - proof, err := p.sc.PoRepSnark(ctx, sr, sealed, unsealed, sectorParams.TicketValue, abi.InteractiveSealRandomness(rand)) - if err != nil { - end, err := p.recoverErrors(ctx, sectorParams.SpID, sectorParams.SectorNumber, err) - if err != nil { - return false, xerrors.Errorf("recover errors: %w", err) - } - if end { - // done, but the error handling has stored a different than success state - return true, nil - } - - return false, xerrors.Errorf("failed to compute seal proof: %w", err) - } - - // store success! - n, err := p.db.Exec(ctx, `UPDATE sectors_sdr_pipeline - SET after_porep = TRUE, seed_value = $3, porep_proof = $4 - WHERE sp_id = $1 AND sector_number = $2`, - sectorParams.SpID, sectorParams.SectorNumber, []byte(rand), proof) - if err != nil { - return false, xerrors.Errorf("store sdr success: updating pipeline: %w", err) - } - if n != 1 { - return false, xerrors.Errorf("store sdr success: updated %d rows", n) - } - - return true, nil -} - -func (p *PoRepTask) CanAccept(ids []harmonytask.TaskID, engine *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - // todo sort by priority - - id := ids[0] - return &id, nil -} - -func (p *PoRepTask) TypeDetails() harmonytask.TaskTypeDetails { - res := harmonytask.TaskTypeDetails{ - Max: p.max, - Name: "PoRep", - Cost: resources.Resources{ - Cpu: 1, - Gpu: 1, - Ram: 50 << 30, // todo correct value - MachineID: 0, - }, - MaxFailures: 5, - Follows: nil, - } - - if isDevnet { - res.Cost.Ram = 1 << 30 - } - - return res -} - -func (p *PoRepTask) Adder(taskFunc harmonytask.AddTaskFunc) { - p.sp.pollers[pollerPoRep].Set(taskFunc) -} - -func (p *PoRepTask) recoverErrors(ctx context.Context, spid, snum int64, cerr error) (end bool, err error) { - const ( - // rust-fil-proofs error strings - // https://github.com/filecoin-project/rust-fil-proofs/blob/3f018b51b6327b135830899d237a7ba181942d7e/storage-proofs-porep/src/stacked/vanilla/proof.rs#L454C1-L463 - errstrInvalidCommD = "Invalid comm_d detected at challenge_index" - errstrInvalidCommR = "Invalid comm_r detected at challenge_index" - errstrInvalidEncoding = "Invalid encoding proof generated at layer" - ) - - if cerr == nil { - return false, xerrors.Errorf("nil error") - } - - switch { - case strings.Contains(cerr.Error(), errstrInvalidCommD): - fallthrough - case strings.Contains(cerr.Error(), errstrInvalidCommR): - // todo: it might be more optimal to just retry the Trees compute first. - // Invalid CommD/R likely indicates a problem with the data computed in that step - // For now for simplicity just retry the whole thing - fallthrough - case strings.Contains(cerr.Error(), errstrInvalidEncoding): - n, err := p.db.Exec(ctx, `UPDATE sectors_sdr_pipeline - SET after_porep = FALSE, after_sdr = FALSE, after_tree_d = FALSE, - after_tree_r = FALSE, after_tree_c = FALSE - WHERE sp_id = $1 AND sector_number = $2`, - spid, snum) - if err != nil { - return false, xerrors.Errorf("store sdr success: updating pipeline: %w", err) - } - if n != 1 { - return false, xerrors.Errorf("store sdr success: updated %d rows", n) - } - - return true, nil - - default: - // if end is false the original error will be returned by the caller - return false, nil - } -} - -var _ harmonytask.TaskInterface = &PoRepTask{} diff --git a/curiosrc/seal/task_sdr.go b/curiosrc/seal/task_sdr.go deleted file mode 100644 index 4c1164e05..000000000 --- a/curiosrc/seal/task_sdr.go +++ /dev/null @@ -1,239 +0,0 @@ -package seal - -import ( - "bytes" - "context" - - "github.com/ipfs/go-cid" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-commp-utils/nonffi" - "github.com/filecoin-project/go-commp-utils/zerocomm" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/crypto" - - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors/policy" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/curiosrc/ffi" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -var isDevnet = build.BlockDelaySecs < 30 - -type SDRAPI interface { - ChainHead(context.Context) (*types.TipSet, error) - StateGetRandomnessFromTickets(context.Context, crypto.DomainSeparationTag, abi.ChainEpoch, []byte, types.TipSetKey) (abi.Randomness, error) -} - -type SDRTask struct { - api SDRAPI - db *harmonydb.DB - sp *SealPoller - - sc *ffi.SealCalls - - max int -} - -func NewSDRTask(api SDRAPI, db *harmonydb.DB, sp *SealPoller, sc *ffi.SealCalls, maxSDR int) *SDRTask { - return &SDRTask{ - api: api, - db: db, - sp: sp, - sc: sc, - max: maxSDR, - } -} - -func (s *SDRTask) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - ctx := context.Background() - - var sectorParamsArr []struct { - SpID int64 `db:"sp_id"` - SectorNumber int64 `db:"sector_number"` - RegSealProof abi.RegisteredSealProof `db:"reg_seal_proof"` - } - - err = s.db.Select(ctx, §orParamsArr, ` - SELECT sp_id, sector_number, reg_seal_proof - FROM sectors_sdr_pipeline - WHERE task_id_sdr = $1`, taskID) - if err != nil { - return false, xerrors.Errorf("getting sector params: %w", err) - } - - if len(sectorParamsArr) != 1 { - return false, xerrors.Errorf("expected 1 sector params, got %d", len(sectorParamsArr)) - } - sectorParams := sectorParamsArr[0] - - var pieces []struct { - PieceIndex int64 `db:"piece_index"` - PieceCID string `db:"piece_cid"` - PieceSize int64 `db:"piece_size"` - } - - err = s.db.Select(ctx, &pieces, ` - SELECT piece_index, piece_cid, piece_size - FROM sectors_sdr_initial_pieces - WHERE sp_id = $1 AND sector_number = $2 ORDER BY piece_index ASC`, sectorParams.SpID, sectorParams.SectorNumber) - if err != nil { - return false, xerrors.Errorf("getting pieces: %w", err) - } - - ssize, err := sectorParams.RegSealProof.SectorSize() - if err != nil { - return false, xerrors.Errorf("getting sector size: %w", err) - } - - var commd cid.Cid - - if len(pieces) > 0 { - pieceInfos := make([]abi.PieceInfo, len(pieces)) - for i, p := range pieces { - c, err := cid.Parse(p.PieceCID) - if err != nil { - return false, xerrors.Errorf("parsing piece cid: %w", err) - } - - pieceInfos[i] = abi.PieceInfo{ - Size: abi.PaddedPieceSize(p.PieceSize), - PieceCID: c, - } - } - - commd, err = nonffi.GenerateUnsealedCID(sectorParams.RegSealProof, pieceInfos) - if err != nil { - return false, xerrors.Errorf("computing CommD: %w", err) - } - } else { - commd = zerocomm.ZeroPieceCommitment(abi.PaddedPieceSize(ssize).Unpadded()) - } - - sref := storiface.SectorRef{ - ID: abi.SectorID{ - Miner: abi.ActorID(sectorParams.SpID), - Number: abi.SectorNumber(sectorParams.SectorNumber), - }, - ProofType: sectorParams.RegSealProof, - } - - // get ticket - maddr, err := address.NewIDAddress(uint64(sectorParams.SpID)) - if err != nil { - return false, xerrors.Errorf("getting miner address: %w", err) - } - - // FAIL: api may be down - // FAIL-RESP: rely on harmony retry - ticket, ticketEpoch, err := s.getTicket(ctx, maddr) - if err != nil { - return false, xerrors.Errorf("getting ticket: %w", err) - } - - // do the SDR!! - - // FAIL: storage may not have enough space - // FAIL-RESP: rely on harmony retry - - // LATEFAIL: compute error in sdr - // LATEFAIL-RESP: Check in Trees task should catch this; Will retry computing - // Trees; After one retry, it should return the sector to the - // SDR stage; max number of retries should be configurable - - err = s.sc.GenerateSDR(ctx, taskID, sref, ticket, commd) - if err != nil { - return false, xerrors.Errorf("generating sdr: %w", err) - } - - // store success! - n, err := s.db.Exec(ctx, `UPDATE sectors_sdr_pipeline - SET after_sdr = true, ticket_epoch = $3, ticket_value = $4 - WHERE sp_id = $1 AND sector_number = $2`, - sectorParams.SpID, sectorParams.SectorNumber, ticketEpoch, []byte(ticket)) - if err != nil { - return false, xerrors.Errorf("store sdr success: updating pipeline: %w", err) - } - if n != 1 { - return false, xerrors.Errorf("store sdr success: updated %d rows", n) - } - - return true, nil -} - -func (s *SDRTask) getTicket(ctx context.Context, maddr address.Address) (abi.SealRandomness, abi.ChainEpoch, error) { - ts, err := s.api.ChainHead(ctx) - if err != nil { - return nil, 0, xerrors.Errorf("getting chain head: %w", err) - } - - ticketEpoch := ts.Height() - policy.SealRandomnessLookback - buf := new(bytes.Buffer) - if err := maddr.MarshalCBOR(buf); err != nil { - return nil, 0, xerrors.Errorf("marshaling miner address: %w", err) - } - - rand, err := s.api.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_SealRandomness, ticketEpoch, buf.Bytes(), ts.Key()) - if err != nil { - return nil, 0, xerrors.Errorf("getting randomness from tickets: %w", err) - } - - return abi.SealRandomness(rand), ticketEpoch, nil -} - -func (s *SDRTask) CanAccept(ids []harmonytask.TaskID, engine *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - id := ids[0] - return &id, nil -} - -func (s *SDRTask) TypeDetails() harmonytask.TaskTypeDetails { - ssize := abi.SectorSize(32 << 30) // todo task details needs taskID to get correct sector size - if isDevnet { - ssize = abi.SectorSize(2 << 20) - } - - res := harmonytask.TaskTypeDetails{ - Max: s.max, - Name: "SDR", - Cost: resources.Resources{ // todo offset for prefetch? - Cpu: 4, // todo multicore sdr - Gpu: 0, - Ram: 54 << 30, - Storage: s.sc.Storage(s.taskToSector, storiface.FTCache, storiface.FTNone, ssize, storiface.PathSealing), - }, - MaxFailures: 2, - Follows: nil, - } - - if isDevnet { - res.Cost.Ram = 1 << 30 - } - - return res -} - -func (s *SDRTask) Adder(taskFunc harmonytask.AddTaskFunc) { - s.sp.pollers[pollerSDR].Set(taskFunc) -} - -func (s *SDRTask) taskToSector(id harmonytask.TaskID) (ffi.SectorRef, error) { - var refs []ffi.SectorRef - - err := s.db.Select(context.Background(), &refs, `SELECT sp_id, sector_number, reg_seal_proof FROM sectors_sdr_pipeline WHERE task_id_sdr = $1`, id) - if err != nil { - return ffi.SectorRef{}, xerrors.Errorf("getting sector ref: %w", err) - } - - if len(refs) != 1 { - return ffi.SectorRef{}, xerrors.Errorf("expected 1 sector ref, got %d", len(refs)) - } - - return refs[0], nil -} - -var _ harmonytask.TaskInterface = &SDRTask{} diff --git a/curiosrc/seal/task_submit_commit.go b/curiosrc/seal/task_submit_commit.go deleted file mode 100644 index d7f133db7..000000000 --- a/curiosrc/seal/task_submit_commit.go +++ /dev/null @@ -1,178 +0,0 @@ -package seal - -import ( - "bytes" - "context" - - "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/builtin" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/curiosrc/message" - "github.com/filecoin-project/lotus/curiosrc/multictladdr" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/storage/ctladdr" -) - -type SubmitCommitAPI interface { - ChainHead(context.Context) (*types.TipSet, error) - StateMinerInfo(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error) - StateMinerInitialPledgeCollateral(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (big.Int, error) - StateSectorPreCommitInfo(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*miner.SectorPreCommitOnChainInfo, error) - ctladdr.NodeApi -} - -type SubmitCommitTask struct { - sp *SealPoller - db *harmonydb.DB - api SubmitCommitAPI - - sender *message.Sender - as *multictladdr.MultiAddressSelector - - maxFee types.FIL -} - -func NewSubmitCommitTask(sp *SealPoller, db *harmonydb.DB, api SubmitCommitAPI, sender *message.Sender, as *multictladdr.MultiAddressSelector, maxFee types.FIL) *SubmitCommitTask { - return &SubmitCommitTask{ - sp: sp, - db: db, - api: api, - sender: sender, - as: as, - - maxFee: maxFee, - } -} - -func (s *SubmitCommitTask) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - ctx := context.Background() - - var sectorParamsArr []struct { - SpID int64 `db:"sp_id"` - SectorNumber int64 `db:"sector_number"` - Proof []byte `db:"porep_proof"` - } - - err = s.db.Select(ctx, §orParamsArr, ` - SELECT sp_id, sector_number, porep_proof - FROM sectors_sdr_pipeline - WHERE task_id_commit_msg = $1`, taskID) - if err != nil { - return false, xerrors.Errorf("getting sector params: %w", err) - } - - if len(sectorParamsArr) != 1 { - return false, xerrors.Errorf("expected 1 sector params, got %d", len(sectorParamsArr)) - } - sectorParams := sectorParamsArr[0] - - maddr, err := address.NewIDAddress(uint64(sectorParams.SpID)) - if err != nil { - return false, xerrors.Errorf("getting miner address: %w", err) - } - - params := miner.ProveCommitSectorParams{ - SectorNumber: abi.SectorNumber(sectorParams.SectorNumber), - Proof: sectorParams.Proof, - } - - enc := new(bytes.Buffer) - if err := params.MarshalCBOR(enc); err != nil { - return false, xerrors.Errorf("could not serialize commit params: %w", err) - } - - ts, err := s.api.ChainHead(ctx) - if err != nil { - return false, xerrors.Errorf("getting chain head: %w", err) - } - - mi, err := s.api.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return false, xerrors.Errorf("getting miner info: %w", err) - } - - pci, err := s.api.StateSectorPreCommitInfo(ctx, maddr, abi.SectorNumber(sectorParams.SectorNumber), ts.Key()) - if err != nil { - return false, xerrors.Errorf("getting precommit info: %w", err) - } - if pci == nil { - return false, xerrors.Errorf("precommit info not found on chain") - } - - collateral, err := s.api.StateMinerInitialPledgeCollateral(ctx, maddr, pci.Info, ts.Key()) - if err != nil { - return false, xerrors.Errorf("getting initial pledge collateral: %w", err) - } - - collateral = big.Sub(collateral, pci.PreCommitDeposit) - if collateral.LessThan(big.Zero()) { - collateral = big.Zero() - } - - a, _, err := s.as.AddressFor(ctx, s.api, maddr, mi, api.CommitAddr, collateral, big.Zero()) - if err != nil { - return false, xerrors.Errorf("getting address for precommit: %w", err) - } - - msg := &types.Message{ - To: maddr, - From: a, - Method: builtin.MethodsMiner.ProveCommitSector, // todo ddo provecommit3 - Params: enc.Bytes(), - Value: collateral, // todo config for pulling from miner balance!! - } - - mss := &api.MessageSendSpec{ - MaxFee: abi.TokenAmount(s.maxFee), - } - - mcid, err := s.sender.Send(ctx, msg, mss, "commit") - if err != nil { - return false, xerrors.Errorf("pushing message to mpool: %w", err) - } - - _, err = s.db.Exec(ctx, `UPDATE sectors_sdr_pipeline SET commit_msg_cid = $1, after_commit_msg = TRUE WHERE sp_id = $2 AND sector_number = $3`, mcid, sectorParams.SpID, sectorParams.SectorNumber) - if err != nil { - return false, xerrors.Errorf("updating commit_msg_cid: %w", err) - } - - _, err = s.db.Exec(ctx, `INSERT INTO message_waits (signed_message_cid) VALUES ($1)`, mcid) - if err != nil { - return false, xerrors.Errorf("inserting into message_waits: %w", err) - } - - return true, nil -} - -func (s *SubmitCommitTask) CanAccept(ids []harmonytask.TaskID, engine *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - id := ids[0] - return &id, nil -} - -func (s *SubmitCommitTask) TypeDetails() harmonytask.TaskTypeDetails { - return harmonytask.TaskTypeDetails{ - Max: 128, - Name: "CommitSubmit", - Cost: resources.Resources{ - Cpu: 0, - Gpu: 0, - Ram: 1 << 20, - }, - MaxFailures: 16, - } -} - -func (s *SubmitCommitTask) Adder(taskFunc harmonytask.AddTaskFunc) { - s.sp.pollers[pollerCommitMsg].Set(taskFunc) -} - -var _ harmonytask.TaskInterface = &SubmitCommitTask{} diff --git a/curiosrc/seal/task_submit_precommit.go b/curiosrc/seal/task_submit_precommit.go deleted file mode 100644 index d42bcbe0d..000000000 --- a/curiosrc/seal/task_submit_precommit.go +++ /dev/null @@ -1,300 +0,0 @@ -package seal - -import ( - "bytes" - "context" - - "github.com/ipfs/go-cid" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - actorstypes "github.com/filecoin-project/go-state-types/actors" - "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/go-state-types/builtin" - miner12 "github.com/filecoin-project/go-state-types/builtin/v12/miner" - "github.com/filecoin-project/go-state-types/network" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/actors/policy" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/curiosrc/message" - "github.com/filecoin-project/lotus/curiosrc/multictladdr" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/storage/ctladdr" -) - -type SubmitPrecommitTaskApi interface { - ChainHead(context.Context) (*types.TipSet, error) - StateMinerPreCommitDepositForPower(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (big.Int, error) - StateMinerInfo(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error) - StateNetworkVersion(context.Context, types.TipSetKey) (network.Version, error) - ctladdr.NodeApi -} - -type SubmitPrecommitTask struct { - sp *SealPoller - db *harmonydb.DB - api SubmitPrecommitTaskApi - sender *message.Sender - as *multictladdr.MultiAddressSelector - - maxFee types.FIL -} - -func NewSubmitPrecommitTask(sp *SealPoller, db *harmonydb.DB, api SubmitPrecommitTaskApi, sender *message.Sender, as *multictladdr.MultiAddressSelector, maxFee types.FIL) *SubmitPrecommitTask { - return &SubmitPrecommitTask{ - sp: sp, - db: db, - api: api, - sender: sender, - as: as, - - maxFee: maxFee, - } -} - -func (s *SubmitPrecommitTask) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - ctx := context.Background() - - // 1. Load sector info - - var sectorParamsArr []struct { - SpID int64 `db:"sp_id"` - SectorNumber int64 `db:"sector_number"` - RegSealProof abi.RegisteredSealProof `db:"reg_seal_proof"` - TicketEpoch abi.ChainEpoch `db:"ticket_epoch"` - SealedCID string `db:"tree_r_cid"` - UnsealedCID string `db:"tree_d_cid"` - } - - err = s.db.Select(ctx, §orParamsArr, ` - SELECT sp_id, sector_number, reg_seal_proof, ticket_epoch, tree_r_cid, tree_d_cid - FROM sectors_sdr_pipeline - WHERE task_id_precommit_msg = $1`, taskID) - if err != nil { - return false, xerrors.Errorf("getting sector params: %w", err) - } - - if len(sectorParamsArr) != 1 { - return false, xerrors.Errorf("expected 1 sector params, got %d", len(sectorParamsArr)) - } - sectorParams := sectorParamsArr[0] - - maddr, err := address.NewIDAddress(uint64(sectorParams.SpID)) - if err != nil { - return false, xerrors.Errorf("getting miner address: %w", err) - } - - sealedCID, err := cid.Parse(sectorParams.SealedCID) - if err != nil { - return false, xerrors.Errorf("parsing sealed CID: %w", err) - } - - unsealedCID, err := cid.Parse(sectorParams.UnsealedCID) - if err != nil { - return false, xerrors.Errorf("parsing unsealed CID: %w", err) - } - - // 2. Prepare message params - - head, err := s.api.ChainHead(ctx) - if err != nil { - return false, xerrors.Errorf("getting chain head: %w", err) - } - - params := miner.PreCommitSectorBatchParams2{} - - expiration := sectorParams.TicketEpoch + miner12.MaxSectorExpirationExtension - - params.Sectors = append(params.Sectors, miner.SectorPreCommitInfo{ - SealProof: sectorParams.RegSealProof, - SectorNumber: abi.SectorNumber(sectorParams.SectorNumber), - SealedCID: sealedCID, - SealRandEpoch: sectorParams.TicketEpoch, - Expiration: expiration, - }) - - { - var pieces []struct { - PieceIndex int64 `db:"piece_index"` - PieceCID string `db:"piece_cid"` - PieceSize int64 `db:"piece_size"` - - F05DealID int64 `db:"f05_deal_id"` - F05DealEndEpoch int64 `db:"f05_deal_end_epoch"` - F05DealStartEpoch int64 `db:"f05_deal_start_epoch"` - } - - err = s.db.Select(ctx, &pieces, ` - SELECT piece_index, piece_cid, piece_size, f05_deal_id, f05_deal_end_epoch, f05_deal_start_epoch - FROM sectors_sdr_initial_pieces - WHERE sp_id = $1 AND sector_number = $2 ORDER BY piece_index ASC`, sectorParams.SpID, sectorParams.SectorNumber) - if err != nil { - return false, xerrors.Errorf("getting pieces: %w", err) - } - - if len(pieces) > 1 { - return false, xerrors.Errorf("too many pieces") // todo support multiple pieces - } - - if len(pieces) > 0 { - params.Sectors[0].UnsealedCid = &unsealedCID - params.Sectors[0].Expiration = abi.ChainEpoch(pieces[0].F05DealEndEpoch) - - if abi.ChainEpoch(pieces[0].F05DealStartEpoch) < head.Height() { - // deal start epoch is in the past, can't precommit this sector anymore - _, perr := s.db.Exec(ctx, `UPDATE sectors_sdr_pipeline - SET failed = TRUE, failed_at = NOW(), failed_reason = 'past-start-epoch', failed_reason_msg = 'precommit: start epoch is in the past' - WHERE task_id_precommit_msg = $1`, taskID) - if perr != nil { - return false, xerrors.Errorf("persisting precommit start epoch expiry: %w", perr) - } - return true, xerrors.Errorf("deal start epoch is in the past") - } - - for _, p := range pieces { - params.Sectors[0].DealIDs = append(params.Sectors[0].DealIDs, abi.DealID(p.F05DealID)) - } - } - } - - nv, err := s.api.StateNetworkVersion(ctx, types.EmptyTSK) - if err != nil { - return false, xerrors.Errorf("getting network version: %w", err) - } - av, err := actorstypes.VersionForNetwork(nv) - if err != nil { - return false, xerrors.Errorf("failed to get actors version: %w", err) - } - msd, err := policy.GetMaxProveCommitDuration(av, sectorParams.RegSealProof) - if err != nil { - return false, xerrors.Errorf("failed to get max prove commit duration: %w", err) - } - - if minExpiration := sectorParams.TicketEpoch + policy.MaxPreCommitRandomnessLookback + msd + miner.MinSectorExpiration; params.Sectors[0].Expiration < minExpiration { - params.Sectors[0].Expiration = minExpiration - } - - // 3. Check precommit - - { - record, err := s.checkPrecommit(ctx, params) - if err != nil { - if record { - _, perr := s.db.Exec(ctx, `UPDATE sectors_sdr_pipeline - SET failed = TRUE, failed_at = NOW(), failed_reason = 'precommit-check', failed_reason_msg = $1 - WHERE task_id_precommit_msg = $2`, err.Error(), taskID) - if perr != nil { - return false, xerrors.Errorf("persisting precommit check error: %w", perr) - } - } - - return record, xerrors.Errorf("checking precommit: %w", err) - } - } - - // 4. Prepare and send message - - var pbuf bytes.Buffer - if err := params.MarshalCBOR(&pbuf); err != nil { - return false, xerrors.Errorf("serializing params: %w", err) - } - - collateral, err := s.api.StateMinerPreCommitDepositForPower(ctx, maddr, params.Sectors[0], types.EmptyTSK) - if err != nil { - return false, xerrors.Errorf("getting precommit deposit: %w", err) - } - - mi, err := s.api.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return false, xerrors.Errorf("getting miner info: %w", err) - } - - a, _, err := s.as.AddressFor(ctx, s.api, maddr, mi, api.PreCommitAddr, collateral, big.Zero()) - if err != nil { - return false, xerrors.Errorf("getting address for precommit: %w", err) - } - - msg := &types.Message{ - To: maddr, - From: a, - Method: builtin.MethodsMiner.PreCommitSectorBatch2, - Params: pbuf.Bytes(), - Value: collateral, // todo config for pulling from miner balance!! - } - - mss := &api.MessageSendSpec{ - MaxFee: abi.TokenAmount(s.maxFee), - } - - mcid, err := s.sender.Send(ctx, msg, mss, "precommit") - if err != nil { - return false, xerrors.Errorf("sending message: %w", err) - } - - // set precommit_msg_cid - _, err = s.db.Exec(ctx, `UPDATE sectors_sdr_pipeline - SET precommit_msg_cid = $1, after_precommit_msg = TRUE - WHERE task_id_precommit_msg = $2`, mcid, taskID) - if err != nil { - return false, xerrors.Errorf("updating precommit_msg_cid: %w", err) - } - - _, err = s.db.Exec(ctx, `INSERT INTO message_waits (signed_message_cid) VALUES ($1)`, mcid) - if err != nil { - return false, xerrors.Errorf("inserting into message_waits: %w", err) - } - - return true, nil -} - -func (s *SubmitPrecommitTask) checkPrecommit(ctx context.Context, params miner.PreCommitSectorBatchParams2) (record bool, err error) { - if len(params.Sectors) != 1 { - return false, xerrors.Errorf("expected 1 sector") - } - - preCommitInfo := params.Sectors[0] - - head, err := s.api.ChainHead(ctx) - if err != nil { - return false, xerrors.Errorf("getting chain head: %w", err) - } - height := head.Height() - - //never commit P2 message before, check ticket expiration - ticketEarliest := height - policy.MaxPreCommitRandomnessLookback - - if preCommitInfo.SealRandEpoch < ticketEarliest { - return true, xerrors.Errorf("ticket expired: seal height: %d, head: %d", preCommitInfo.SealRandEpoch+policy.SealRandomnessLookback, height) - } - - return true, nil -} - -func (s *SubmitPrecommitTask) CanAccept(ids []harmonytask.TaskID, engine *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - id := ids[0] - return &id, nil -} - -func (s *SubmitPrecommitTask) TypeDetails() harmonytask.TaskTypeDetails { - return harmonytask.TaskTypeDetails{ - Max: 1024, - Name: "PreCommitSubmit", - Cost: resources.Resources{ - Cpu: 0, - Gpu: 0, - Ram: 1 << 20, - }, - MaxFailures: 16, - } -} - -func (s *SubmitPrecommitTask) Adder(taskFunc harmonytask.AddTaskFunc) { - s.sp.pollers[pollerPrecommitMsg].Set(taskFunc) -} - -var _ harmonytask.TaskInterface = &SubmitPrecommitTask{} diff --git a/curiosrc/seal/task_trees.go b/curiosrc/seal/task_trees.go deleted file mode 100644 index 7994c354a..000000000 --- a/curiosrc/seal/task_trees.go +++ /dev/null @@ -1,326 +0,0 @@ -package seal - -import ( - "context" - "io" - "net/http" - "net/url" - "strconv" - - "github.com/ipfs/go-cid" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-commp-utils/nonffi" - "github.com/filecoin-project/go-commp-utils/zerocomm" - "github.com/filecoin-project/go-padreader" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/curiosrc/ffi" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/storage/pipeline/lib/nullreader" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -type TreesTask struct { - sp *SealPoller - db *harmonydb.DB - sc *ffi.SealCalls - - max int -} - -func NewTreesTask(sp *SealPoller, db *harmonydb.DB, sc *ffi.SealCalls, maxTrees int) *TreesTask { - return &TreesTask{ - sp: sp, - db: db, - sc: sc, - - max: maxTrees, - } -} - -func (t *TreesTask) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - ctx := context.Background() - - var sectorParamsArr []struct { - SpID int64 `db:"sp_id"` - SectorNumber int64 `db:"sector_number"` - RegSealProof abi.RegisteredSealProof `db:"reg_seal_proof"` - } - - err = t.db.Select(ctx, §orParamsArr, ` - SELECT sp_id, sector_number, reg_seal_proof - FROM sectors_sdr_pipeline - WHERE task_id_tree_r = $1 AND task_id_tree_c = $1 AND task_id_tree_d = $1`, taskID) - if err != nil { - return false, xerrors.Errorf("getting sector params: %w", err) - } - - if len(sectorParamsArr) != 1 { - return false, xerrors.Errorf("expected 1 sector params, got %d", len(sectorParamsArr)) - } - sectorParams := sectorParamsArr[0] - - var pieces []struct { - PieceIndex int64 `db:"piece_index"` - PieceCID string `db:"piece_cid"` - PieceSize int64 `db:"piece_size"` - - DataUrl *string `db:"data_url"` - DataHeaders *[]byte `db:"data_headers"` - DataRawSize *int64 `db:"data_raw_size"` - } - - err = t.db.Select(ctx, &pieces, ` - SELECT piece_index, piece_cid, piece_size, data_url, data_headers, data_raw_size - FROM sectors_sdr_initial_pieces - WHERE sp_id = $1 AND sector_number = $2 ORDER BY piece_index ASC`, sectorParams.SpID, sectorParams.SectorNumber) - if err != nil { - return false, xerrors.Errorf("getting pieces: %w", err) - } - - ssize, err := sectorParams.RegSealProof.SectorSize() - if err != nil { - return false, xerrors.Errorf("getting sector size: %w", err) - } - - var commd cid.Cid - var dataReader io.Reader - var unpaddedData bool - - var closers []io.Closer - defer func() { - for _, c := range closers { - if err := c.Close(); err != nil { - log.Errorw("error closing piece reader", "error", err) - } - } - }() - - if len(pieces) > 0 { - pieceInfos := make([]abi.PieceInfo, len(pieces)) - pieceReaders := make([]io.Reader, len(pieces)) - - for i, p := range pieces { - // make pieceInfo - c, err := cid.Parse(p.PieceCID) - if err != nil { - return false, xerrors.Errorf("parsing piece cid: %w", err) - } - - pieceInfos[i] = abi.PieceInfo{ - Size: abi.PaddedPieceSize(p.PieceSize), - PieceCID: c, - } - - // make pieceReader - if p.DataUrl != nil { - dataUrl := *p.DataUrl - - goUrl, err := url.Parse(dataUrl) - if err != nil { - return false, xerrors.Errorf("parsing data URL: %w", err) - } - - if goUrl.Scheme == "pieceref" { - // url is to a piece reference - - refNum, err := strconv.ParseInt(goUrl.Opaque, 10, 64) - if err != nil { - return false, xerrors.Errorf("parsing piece reference number: %w", err) - } - - // get pieceID - var pieceID []struct { - PieceID storiface.PieceNumber `db:"piece_id"` - } - err = t.db.Select(ctx, &pieceID, `SELECT piece_id FROM parked_piece_refs WHERE ref_id = $1`, refNum) - if err != nil { - return false, xerrors.Errorf("getting pieceID: %w", err) - } - - if len(pieceID) != 1 { - return false, xerrors.Errorf("expected 1 pieceID, got %d", len(pieceID)) - } - - pr, err := t.sc.PieceReader(ctx, pieceID[0].PieceID) - if err != nil { - return false, xerrors.Errorf("getting piece reader: %w", err) - } - - closers = append(closers, pr) - - pieceReaders[i], _ = padreader.New(pr, uint64(*p.DataRawSize)) - } else { - pieceReaders[i], _ = padreader.New(&UrlPieceReader{ - Url: dataUrl, - RawSize: *p.DataRawSize, - }, uint64(*p.DataRawSize)) - } - - } else { // padding piece (w/o fr32 padding, added in TreeD) - pieceReaders[i] = nullreader.NewNullReader(abi.PaddedPieceSize(p.PieceSize).Unpadded()) - } - } - - commd, err = nonffi.GenerateUnsealedCID(sectorParams.RegSealProof, pieceInfos) - if err != nil { - return false, xerrors.Errorf("computing CommD: %w", err) - } - - dataReader = io.MultiReader(pieceReaders...) - unpaddedData = true - } else { - commd = zerocomm.ZeroPieceCommitment(abi.PaddedPieceSize(ssize).Unpadded()) - dataReader = nullreader.NewNullReader(abi.UnpaddedPieceSize(ssize)) - unpaddedData = false // nullreader includes fr32 zero bits - } - - sref := storiface.SectorRef{ - ID: abi.SectorID{ - Miner: abi.ActorID(sectorParams.SpID), - Number: abi.SectorNumber(sectorParams.SectorNumber), - }, - ProofType: sectorParams.RegSealProof, - } - - // D / R / C - sealed, unsealed, err := t.sc.TreeDRC(ctx, &taskID, sref, commd, abi.PaddedPieceSize(ssize), dataReader, unpaddedData) - if err != nil { - return false, xerrors.Errorf("computing tree d, r and c: %w", err) - } - - // todo synth porep - - // todo porep challenge check - - n, err := t.db.Exec(ctx, `UPDATE sectors_sdr_pipeline - SET after_tree_r = true, after_tree_c = true, after_tree_d = true, tree_r_cid = $3, tree_d_cid = $4 - WHERE sp_id = $1 AND sector_number = $2`, - sectorParams.SpID, sectorParams.SectorNumber, sealed, unsealed) - if err != nil { - return false, xerrors.Errorf("store sdr-trees success: updating pipeline: %w", err) - } - if n != 1 { - return false, xerrors.Errorf("store sdr-trees success: updated %d rows", n) - } - - return true, nil -} - -func (t *TreesTask) CanAccept(ids []harmonytask.TaskID, engine *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - id := ids[0] - return &id, nil -} - -func (t *TreesTask) TypeDetails() harmonytask.TaskTypeDetails { - ssize := abi.SectorSize(32 << 30) // todo task details needs taskID to get correct sector size - if isDevnet { - ssize = abi.SectorSize(2 << 20) - } - - return harmonytask.TaskTypeDetails{ - Max: t.max, - Name: "SDRTrees", - Cost: resources.Resources{ - Cpu: 1, - Gpu: 1, - Ram: 8000 << 20, // todo - Storage: t.sc.Storage(t.taskToSector, storiface.FTSealed, storiface.FTCache, ssize, storiface.PathSealing), - }, - MaxFailures: 3, - Follows: nil, - } -} - -func (t *TreesTask) Adder(taskFunc harmonytask.AddTaskFunc) { - t.sp.pollers[pollerTrees].Set(taskFunc) -} - -func (t *TreesTask) taskToSector(id harmonytask.TaskID) (ffi.SectorRef, error) { - var refs []ffi.SectorRef - - err := t.db.Select(context.Background(), &refs, `SELECT sp_id, sector_number, reg_seal_proof FROM sectors_sdr_pipeline WHERE task_id_tree_r = $1`, id) - if err != nil { - return ffi.SectorRef{}, xerrors.Errorf("getting sector ref: %w", err) - } - - if len(refs) != 1 { - return ffi.SectorRef{}, xerrors.Errorf("expected 1 sector ref, got %d", len(refs)) - } - - return refs[0], nil -} - -type UrlPieceReader struct { - Url string - RawSize int64 // the exact number of bytes read, if we read more or less that's an error - - readSoFar int64 - closed bool - active io.ReadCloser // auto-closed on EOF -} - -func (u *UrlPieceReader) Read(p []byte) (n int, err error) { - // Check if we have already read the required amount of data - if u.readSoFar >= u.RawSize { - return 0, io.EOF - } - - // If 'active' is nil, initiate the HTTP request - if u.active == nil { - resp, err := http.Get(u.Url) - if err != nil { - return 0, err - } - - // Set 'active' to the response body - u.active = resp.Body - } - - // Calculate the maximum number of bytes we can read without exceeding RawSize - toRead := u.RawSize - u.readSoFar - if int64(len(p)) > toRead { - p = p[:toRead] - } - - n, err = u.active.Read(p) - - // Update the number of bytes read so far - u.readSoFar += int64(n) - - // If the number of bytes read exceeds RawSize, return an error - if u.readSoFar > u.RawSize { - return n, xerrors.New("read beyond the specified RawSize") - } - - // If EOF is reached, close the reader - if err == io.EOF { - cerr := u.active.Close() - u.closed = true - if cerr != nil { - log.Errorf("error closing http piece reader: %s", cerr) - } - - // if we're below the RawSize, return an unexpected EOF error - if u.readSoFar < u.RawSize { - log.Errorw("unexpected EOF", "readSoFar", u.readSoFar, "rawSize", u.RawSize, "url", u.Url) - return n, io.ErrUnexpectedEOF - } - } - - return n, err -} - -func (u *UrlPieceReader) Close() error { - if !u.closed { - u.closed = true - return u.active.Close() - } - - return nil -} - -var _ harmonytask.TaskInterface = &TreesTask{} diff --git a/curiosrc/seal/task_trees_test.go b/curiosrc/seal/task_trees_test.go deleted file mode 100644 index b65ddd4e8..000000000 --- a/curiosrc/seal/task_trees_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package seal - -import ( - "io" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/require" -) - -// TestUrlPieceReader_Read tests various scenarios of reading data from UrlPieceReader -func TestUrlPieceReader_Read(t *testing.T) { - // Create a test server - testData := "This is a test string." - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err := io.WriteString(w, testData) - require.NoError(t, err) - })) - defer ts.Close() - - tests := []struct { - name string - rawSize int64 - expected string - expectError bool - expectEOF bool - }{ - {"ReadExact", int64(len(testData)), testData, false, true}, - {"ReadLess", 10, testData[:10], false, false}, - {"ReadMore", int64(len(testData)) + 10, "", true, false}, - } - - for _, tt := range tests { - tt := tt - - t.Run(tt.name, func(t *testing.T) { - reader := UrlPieceReader{ - Url: ts.URL, - RawSize: tt.rawSize, - } - buffer, err := io.ReadAll(&reader) - if err != nil { - if (err != io.EOF && !tt.expectError) || (err == io.EOF && !tt.expectEOF) { - t.Errorf("Read() error = %v, expectError %v, expectEOF %v", err, tt.expectError, tt.expectEOF) - } - } else { - if got := string(buffer); got != tt.expected { - t.Errorf("Read() got = %v, expected %v", got, tt.expected) - } - } - }) - } -} - -// TestUrlPieceReader_Read_Error tests the error handling of UrlPieceReader -func TestUrlPieceReader_Read_Error(t *testing.T) { - // Simulate a server that returns an error - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, "error", http.StatusInternalServerError) - })) - defer ts.Close() - - reader := UrlPieceReader{ - Url: ts.URL, - RawSize: 100, - } - buffer := make([]byte, 200) - - _, err := reader.Read(buffer) - if err == nil { - t.Errorf("Expected an error, but got nil") - } -} diff --git a/curiosrc/web/api/apihelper/apihelper.go b/curiosrc/web/api/apihelper/apihelper.go deleted file mode 100644 index 07c7898e8..000000000 --- a/curiosrc/web/api/apihelper/apihelper.go +++ /dev/null @@ -1,19 +0,0 @@ -package apihelper - -import ( - "net/http" - "runtime/debug" - - logging "github.com/ipfs/go-log/v2" -) - -var log = logging.Logger("lp/web/apihelper") - -func OrHTTPFail(w http.ResponseWriter, err error) { - if err != nil { - w.WriteHeader(500) - _, _ = w.Write([]byte(err.Error())) - log.Errorw("http fail", "err", err, "stack", string(debug.Stack())) - panic(err) - } -} diff --git a/curiosrc/web/api/config/config.go b/curiosrc/web/api/config/config.go deleted file mode 100644 index 6f9598f7f..000000000 --- a/curiosrc/web/api/config/config.go +++ /dev/null @@ -1,179 +0,0 @@ -package config - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - "reflect" - "time" - - "github.com/BurntSushi/toml" - "github.com/gorilla/mux" - "github.com/invopop/jsonschema" - - "github.com/filecoin-project/lotus/cmd/curio/deps" - "github.com/filecoin-project/lotus/curiosrc/web/api/apihelper" - "github.com/filecoin-project/lotus/node/config" -) - -type cfg struct { - *deps.Deps -} - -func Routes(r *mux.Router, deps *deps.Deps) { - c := &cfg{deps} - // At menu.html: - r.Methods("GET").Path("/layers").HandlerFunc(c.getLayers) - r.Methods("GET").Path("/topo").HandlerFunc(c.topo) - - // At edit.html: - r.Methods("GET").Path("/schema").HandlerFunc(getSch) - r.Methods("GET").Path("/layers/{layer}").HandlerFunc(c.getLayer) - r.Methods("POST").Path("/layers/{layer}").HandlerFunc(c.setLayer) - r.Methods("GET").Path("/default").HandlerFunc(c.def) -} -func getSch(w http.ResponseWriter, r *http.Request) { - ref := jsonschema.Reflector{ - Mapper: func(i reflect.Type) *jsonschema.Schema { - if i == reflect.TypeOf(config.Duration(time.Second)) { - return &jsonschema.Schema{ - Type: "string", - Format: "duration", - } - } - return nil - }, - } - sch := ref.Reflect(config.CurioConfig{}) - // add comments - for k, doc := range config.Doc { - item, ok := sch.Definitions[k] - if !ok { - continue - } - for _, line := range doc { - item, ok := item.Properties.Get(line.Name) - if !ok { - continue - } - if line.Comment != "" { - extra := make(map[string]any) - type options struct { - InfoText string `json:"infoText"` - } - opt := options{ - InfoText: line.Comment, - } - extra["options"] = opt - item.Extras = extra - } - } - } - - var allOpt func(s *jsonschema.Schema) - allOpt = func(s *jsonschema.Schema) { - s.Required = []string{} - for _, v := range s.Definitions { - v.Required = []string{} - - allOpt(v) - } - } - allOpt(sch) - - apihelper.OrHTTPFail(w, json.NewEncoder(w).Encode(sch)) -} - -func (c *cfg) getLayers(w http.ResponseWriter, r *http.Request) { - var layers []string - apihelper.OrHTTPFail(w, c.DB.Select(context.Background(), &layers, `SELECT title FROM harmony_config ORDER BY title`)) - apihelper.OrHTTPFail(w, json.NewEncoder(w).Encode(layers)) -} - -func (c *cfg) getLayer(w http.ResponseWriter, r *http.Request) { - var layer string - apihelper.OrHTTPFail(w, c.DB.QueryRow(context.Background(), `SELECT config FROM harmony_config WHERE title = $1`, mux.Vars(r)["layer"]).Scan(&layer)) - - // Read the TOML into a struct - configStruct := map[string]any{} // NOT CurioConfig b/c we want to preserve unsets - _, err := toml.Decode(layer, &configStruct) - apihelper.OrHTTPFail(w, err) - - // Encode the struct as JSON - jsonData, err := json.Marshal(configStruct) - apihelper.OrHTTPFail(w, err) - - // Write the JSON response - w.Header().Set("Content-Type", "application/json") - _, err = w.Write(jsonData) - apihelper.OrHTTPFail(w, err) -} - -func (c *cfg) setLayer(w http.ResponseWriter, r *http.Request) { - layer := mux.Vars(r)["layer"] - var configStruct map[string]any - dec := json.NewDecoder(r.Body) - dec.UseNumber() // JSON lib by default treats number is float64() - apihelper.OrHTTPFail(w, dec.Decode(&configStruct)) - - //Encode the struct as TOML - var tomlData bytes.Buffer - err := toml.NewEncoder(&tomlData).Encode(configStruct) - apihelper.OrHTTPFail(w, err) - - configStr := tomlData.String() - - // Generate a full commented string if this is base layer - if layer == "base" { - // Parse the into CurioConfig TOML - curioCfg := config.DefaultCurioConfig() - _, err = deps.LoadConfigWithUpgrades(tomlData.String(), curioCfg) - apihelper.OrHTTPFail(w, err) - cb, err := config.ConfigUpdate(curioCfg, config.DefaultCurioConfig(), config.Commented(true), config.DefaultKeepUncommented(), config.NoEnv()) - apihelper.OrHTTPFail(w, err) - configStr = string(cb) - } - - //Write the TOML to the database - _, err = c.DB.Exec(context.Background(), `INSERT INTO harmony_config (title, config) VALUES ($1, $2) ON CONFLICT (title) DO UPDATE SET config = $2`, layer, configStr) - apihelper.OrHTTPFail(w, err) -} - -func (c *cfg) topo(w http.ResponseWriter, r *http.Request) { - var topology []struct { - Server string `db:"server"` - CPU int `db:"cpu"` - GPU int `db:"gpu"` - RAM int `db:"ram"` - LayersCSV string `db:"layers"` - TasksCSV string `db:"tasks"` - } - apihelper.OrHTTPFail(w, c.DB.Select(context.Background(), &topology, ` - SELECT - m.host_and_port as server, - cpu, gpu, ram, layers, tasks - FROM harmony_machines m JOIN harmony_machine_details d ON m.id=d.machine_id - ORDER BY server`)) - w.Header().Set("Content-Type", "application/json") - apihelper.OrHTTPFail(w, json.NewEncoder(w).Encode(topology)) -} - -func (c *cfg) def(w http.ResponseWriter, r *http.Request) { - cb, err := config.ConfigUpdate(config.DefaultCurioConfig(), nil, config.Commented(false), config.DefaultKeepUncommented(), config.NoEnv()) - apihelper.OrHTTPFail(w, err) - - // Read the TOML into a struct - configStruct := map[string]any{} // NOT CurioConfig b/c we want to preserve unsets - _, err = toml.Decode(string(cb), &configStruct) - apihelper.OrHTTPFail(w, err) - - // Encode the struct as JSON - jsonData, err := json.Marshal(configStruct) - apihelper.OrHTTPFail(w, err) - - // Write the JSON response - w.Header().Set("Content-Type", "application/json") - _, err = w.Write(jsonData) - apihelper.OrHTTPFail(w, err) -} diff --git a/curiosrc/web/api/debug/debug.go b/curiosrc/web/api/debug/debug.go deleted file mode 100644 index c0e89ab8e..000000000 --- a/curiosrc/web/api/debug/debug.go +++ /dev/null @@ -1,229 +0,0 @@ -// Package debug provides the API for various debug endpoints in curio. -package debug - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "sort" - "sync" - "time" - - "github.com/BurntSushi/toml" - "github.com/gorilla/mux" - logging "github.com/ipfs/go-log/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/api/client" - "github.com/filecoin-project/lotus/build" - cliutil "github.com/filecoin-project/lotus/cli/util" - "github.com/filecoin-project/lotus/cmd/curio/deps" -) - -var log = logging.Logger("curio/web/debug") - -type debug struct { - *deps.Deps -} - -func Routes(r *mux.Router, deps *deps.Deps) { - d := debug{deps} - r.HandleFunc("/chain-state-sse", d.chainStateSSE) -} - -type rpcInfo struct { - Address string - CLayers []string - Reachable bool - SyncState string - Version string -} - -func (d *debug) chainStateSSE(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Headers", "Content-Type") - w.Header().Set("Content-Type", "text/event-stream") - w.Header().Set("Cache-Control", "no-cache") - w.Header().Set("Connection", "keep-alive") - - ctx := r.Context() - - for { - - type minimalApiInfo struct { - Apis struct { - ChainApiInfo []string - } - } - - rpcInfos := map[string]minimalApiInfo{} // config name -> api info - confNameToAddr := map[string]string{} // config name -> api address - - err := forEachConfig[minimalApiInfo](d, func(name string, info minimalApiInfo) error { - if len(info.Apis.ChainApiInfo) == 0 { - return nil - } - - rpcInfos[name] = info - - for _, addr := range info.Apis.ChainApiInfo { - ai := cliutil.ParseApiInfo(addr) - confNameToAddr[name] = ai.Addr - } - - return nil - }) - if err != nil { - log.Errorw("getting api info", "error", err) - return - } - - dedup := map[string]bool{} // for dedup by address - - infos := map[string]rpcInfo{} // api address -> rpc info - var infosLk sync.Mutex - - var wg sync.WaitGroup - for _, info := range rpcInfos { - ai := cliutil.ParseApiInfo(info.Apis.ChainApiInfo[0]) - if dedup[ai.Addr] { - continue - } - dedup[ai.Addr] = true - wg.Add(1) - go func() { - defer wg.Done() - var clayers []string - for layer, a := range confNameToAddr { - if a == ai.Addr { - clayers = append(clayers, layer) - } - } - - myinfo := rpcInfo{ - Address: ai.Addr, - Reachable: false, - CLayers: clayers, - } - defer func() { - infosLk.Lock() - defer infosLk.Unlock() - infos[ai.Addr] = myinfo - }() - da, err := ai.DialArgs("v1") - if err != nil { - log.Warnw("DialArgs", "error", err) - return - } - - ah := ai.AuthHeader() - - v1api, closer, err := client.NewFullNodeRPCV1(ctx, da, ah) - if err != nil { - log.Warnf("Not able to establish connection to node with addr: %s", ai.Addr) - return - } - defer closer() - - ver, err := v1api.Version(ctx) - if err != nil { - log.Warnw("Version", "error", err) - return - } - - head, err := v1api.ChainHead(ctx) - if err != nil { - log.Warnw("ChainHead", "error", err) - return - } - - var syncState string - switch { - case time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs*3/2): // within 1.5 epochs - syncState = "ok" - case time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs*5): // within 5 epochs - syncState = fmt.Sprintf("slow (%s behind)", time.Since(time.Unix(int64(head.MinTimestamp()), 0)).Truncate(time.Second)) - default: - syncState = fmt.Sprintf("behind (%s behind)", time.Since(time.Unix(int64(head.MinTimestamp()), 0)).Truncate(time.Second)) - } - - myinfo = rpcInfo{ - Address: ai.Addr, - CLayers: clayers, - Reachable: true, - Version: ver.Version, - SyncState: syncState, - } - }() - } - wg.Wait() - - var infoList []rpcInfo - for _, i := range infos { - infoList = append(infoList, i) - } - sort.Slice(infoList, func(i, j int) bool { - return infoList[i].Address < infoList[j].Address - }) - - fmt.Fprintf(w, "data: ") - err = json.NewEncoder(w).Encode(&infoList) - if err != nil { - log.Warnw("json encode", "error", err) - return - } - fmt.Fprintf(w, "\n\n") - if f, ok := w.(http.Flusher); ok { - f.Flush() - } - - time.Sleep(time.Duration(build.BlockDelaySecs) * time.Second) - - select { // stop running if there is reader. - case <-ctx.Done(): - return - default: - } - } -} - -func forEachConfig[T any](a *debug, cb func(name string, v T) error) error { - confs, err := a.loadConfigs(context.Background()) - if err != nil { - return err - } - - for name, tomlStr := range confs { // todo for-each-config - var info T - if err := toml.Unmarshal([]byte(tomlStr), &info); err != nil { - return xerrors.Errorf("unmarshaling %s config: %w", name, err) - } - - if err := cb(name, info); err != nil { - return xerrors.Errorf("cb: %w", err) - } - } - - return nil -} - -func (d *debug) loadConfigs(ctx context.Context) (map[string]string, error) { - //err := db.QueryRow(cctx.Context, `SELECT config FROM harmony_config WHERE title=$1`, layer).Scan(&text) - - rows, err := d.DB.Query(ctx, `SELECT title, config FROM harmony_config`) - if err != nil { - return nil, xerrors.Errorf("getting db configs: %w", err) - } - - configs := make(map[string]string) - for rows.Next() { - var title, config string - if err := rows.Scan(&title, &config); err != nil { - return nil, xerrors.Errorf("scanning db configs: %w", err) - } - configs[title] = config - } - - return configs, nil -} diff --git a/curiosrc/web/api/routes.go b/curiosrc/web/api/routes.go deleted file mode 100644 index cf56257ee..000000000 --- a/curiosrc/web/api/routes.go +++ /dev/null @@ -1,17 +0,0 @@ -// Package api provides the HTTP API for the lotus curio web gui. -package api - -import ( - "github.com/gorilla/mux" - - "github.com/filecoin-project/lotus/cmd/curio/deps" - "github.com/filecoin-project/lotus/curiosrc/web/api/config" - "github.com/filecoin-project/lotus/curiosrc/web/api/debug" - "github.com/filecoin-project/lotus/curiosrc/web/api/sector" -) - -func Routes(r *mux.Router, deps *deps.Deps) { - debug.Routes(r.PathPrefix("/debug").Subrouter(), deps) - config.Routes(r.PathPrefix("/config").Subrouter(), deps) - sector.Routes(r.PathPrefix("/sector").Subrouter(), deps) -} diff --git a/curiosrc/web/api/sector/sector.go b/curiosrc/web/api/sector/sector.go deleted file mode 100644 index e3b4b3c15..000000000 --- a/curiosrc/web/api/sector/sector.go +++ /dev/null @@ -1,378 +0,0 @@ -package sector - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "sync" - "time" - - "github.com/docker/go-units" - "github.com/gorilla/mux" - "github.com/samber/lo" - - "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/builtin/v9/market" - - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/cli/spcli" - "github.com/filecoin-project/lotus/cmd/curio/deps" - "github.com/filecoin-project/lotus/curiosrc/web/api/apihelper" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -const verifiedPowerGainMul = 9 - -type cfg struct { - *deps.Deps -} - -func Routes(r *mux.Router, deps *deps.Deps) { - c := &cfg{deps} - // At menu.html: - r.Methods("GET").Path("/all").HandlerFunc(c.getSectors) - r.Methods("POST").Path("/terminate").HandlerFunc(c.terminateSectors) -} - -func (c *cfg) terminateSectors(w http.ResponseWriter, r *http.Request) { - var in []struct { - MinerID int - Sector int - } - apihelper.OrHTTPFail(w, json.NewDecoder(r.Body).Decode(&in)) - toDel := map[int][]int{} - for _, s := range in { - toDel[s.MinerID] = append(toDel[s.MinerID], s.Sector) - } - - for minerInt, sectors := range toDel { - maddr, err := address.NewIDAddress(uint64(minerInt)) - apihelper.OrHTTPFail(w, err) - mi, err := c.Full.StateMinerInfo(r.Context(), maddr, types.EmptyTSK) - apihelper.OrHTTPFail(w, err) - _, err = spcli.TerminateSectors(r.Context(), c.Full, maddr, sectors, mi.Worker) - apihelper.OrHTTPFail(w, err) - for _, sectorNumber := range sectors { - id := abi.SectorID{Miner: abi.ActorID(minerInt), Number: abi.SectorNumber(sectorNumber)} - apihelper.OrHTTPFail(w, c.Stor.Remove(r.Context(), id, storiface.FTAll, true, nil)) - } - } -} - -func (c *cfg) getSectors(w http.ResponseWriter, r *http.Request) { - // TODO get sector info from chain and from database, then fold them together - // and return the result. - type sector struct { - MinerID int64 `db:"miner_id"` - SectorNum int64 `db:"sector_num"` - SectorFiletype int `db:"sector_filetype" json:"-"` // Useless? - HasSealed bool - HasUnsealed bool - HasSnap bool - ExpiresAt abi.ChainEpoch // map to Duration - IsOnChain bool - IsFilPlus bool - SealInfo string - Proving bool - Flag bool - DealWeight string - Deals string - //StorageID string `db:"storage_id"` // map to serverName - // Activation abi.ChainEpoch // map to time.Time. advanced view only - // DealIDs []abi.DealID // advanced view only - //ExpectedDayReward abi.TokenAmount - //SealProof abi.RegisteredSealProof - } - - type piece struct { - Size int64 `db:"piece_size"` - DealID uint64 `db:"f05_deal_id"` - Proposal json.RawMessage `db:"f05_deal_proposal"` - Manifest json.RawMessage `db:"direct_piece_activation_manifest"` - Miner int64 `db:"sp_id"` - Sector int64 `db:"sector_number"` - } - var sectors []sector - var pieces []piece - apihelper.OrHTTPFail(w, c.DB.Select(r.Context(), §ors, `SELECT - miner_id, sector_num, SUM(sector_filetype) as sector_filetype - FROM sector_location WHERE sector_filetype != 32 - GROUP BY miner_id, sector_num - ORDER BY miner_id, sector_num`)) - minerToAddr := map[int64]address.Address{} - head, err := c.Full.ChainHead(r.Context()) - apihelper.OrHTTPFail(w, err) - - type sectorID struct { - mID int64 - sNum uint64 - } - sectorIdx := map[sectorID]int{} - for i, s := range sectors { - sectors[i].HasSealed = s.SectorFiletype&int(storiface.FTSealed) != 0 || s.SectorFiletype&int(storiface.FTUpdate) != 0 - sectors[i].HasUnsealed = s.SectorFiletype&int(storiface.FTUnsealed) != 0 - sectors[i].HasSnap = s.SectorFiletype&int(storiface.FTUpdate) != 0 - sectorIdx[sectorID{s.MinerID, uint64(s.SectorNum)}] = i - if _, ok := minerToAddr[s.MinerID]; !ok { - minerToAddr[s.MinerID], err = address.NewIDAddress(uint64(s.MinerID)) - apihelper.OrHTTPFail(w, err) - } - } - - // Get all pieces - apihelper.OrHTTPFail(w, c.DB.Select(r.Context(), &pieces, `SELECT - sp_id, sector_number, piece_size, f05_deal_id, f05_deal_proposal, direct_piece_activation_manifest - FROM sectors_sdr_initial_pieces - ORDER BY sp_id, sector_number`)) - pieceIndex := map[sectorID][]int{} - for i, piece := range pieces { - piece := piece - cur := pieceIndex[sectorID{mID: piece.Miner, sNum: uint64(piece.Sector)}] - pieceIndex[sectorID{mID: piece.Miner, sNum: uint64(piece.Sector)}] = append(cur, i) - } - - for minerID, maddr := range minerToAddr { - onChainInfo, err := c.getCachedSectorInfo(w, r, maddr, head.Key()) - apihelper.OrHTTPFail(w, err) - for _, chainy := range onChainInfo { - st := chainy.onChain - if i, ok := sectorIdx[sectorID{minerID, uint64(st.SectorNumber)}]; ok { - sectors[i].IsOnChain = true - sectors[i].ExpiresAt = st.Expiration - sectors[i].IsFilPlus = st.VerifiedDealWeight.GreaterThan(st.DealWeight) - if ss, err := st.SealProof.SectorSize(); err == nil { - sectors[i].SealInfo = ss.ShortString() - } - sectors[i].Proving = chainy.active - if st.Expiration < head.Height() { - sectors[i].Flag = true // Flag expired sectors - } - - dw, vp := .0, .0 - f05, ddo := 0, 0 - var pi []piece - if j, ok := pieceIndex[sectorID{sectors[i].MinerID, uint64(sectors[i].SectorNum)}]; ok { - for _, k := range j { - pi = append(pi, pieces[k]) - } - } - estimate := st.Expiration-st.Activation <= 0 || sectors[i].HasSnap - if estimate { - for _, p := range pi { - if p.Proposal != nil { - var prop *market.DealProposal - apihelper.OrHTTPFail(w, json.Unmarshal(p.Proposal, &prop)) - dw += float64(prop.PieceSize) - if prop.VerifiedDeal { - vp += float64(prop.PieceSize) * verifiedPowerGainMul - } - f05++ - } - if p.Manifest != nil { - var pam *miner.PieceActivationManifest - apihelper.OrHTTPFail(w, json.Unmarshal(p.Manifest, &pam)) - dw += float64(pam.Size) - if pam.VerifiedAllocationKey != nil { - vp += float64(pam.Size) * verifiedPowerGainMul - } - ddo++ - } - } - } else { - rdw := big.Add(st.DealWeight, st.VerifiedDealWeight) - dw = float64(big.Div(rdw, big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) - vp = float64(big.Div(big.Mul(st.VerifiedDealWeight, big.NewInt(verifiedPowerGainMul)), big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) - for _, deal := range st.DealIDs { - - if deal > 0 { - f05++ - } - } - // DDO info is not on chain - for _, piece := range pieces { - if piece.Manifest != nil { - //var pam *miner.PieceActivationManifest - //apihelper.OrHTTPFail(w, json.Unmarshal(piece.Manifest, pam)) - //dw += float64(pam.Size) - //if pam.VerifiedAllocationKey != nil { - // vp += float64(pam.Size) * verifiedPowerGainMul - //} - ddo++ - } - } - } - sectors[i].DealWeight = "CC" - if dw > 0 { - sectors[i].DealWeight = fmt.Sprintf("%s", units.BytesSize(dw)) - } - if vp > 0 { - sectors[i].DealWeight = fmt.Sprintf("%s", units.BytesSize(vp)) - } - sectors[i].Deals = fmt.Sprintf("Market: %d, DDO: %d", f05, ddo) - } else { - // sector is on chain but not in db - s := sector{ - MinerID: minerID, - SectorNum: int64(chainy.onChain.SectorNumber), - IsOnChain: true, - ExpiresAt: chainy.onChain.Expiration, - IsFilPlus: chainy.onChain.VerifiedDealWeight.GreaterThan(chainy.onChain.DealWeight), - Proving: chainy.active, - Flag: true, // All such sectors should be flagged to be terminated - } - if ss, err := chainy.onChain.SealProof.SectorSize(); err == nil { - s.SealInfo = ss.ShortString() - } - sectors = append(sectors, s) - } - /* - info, err := c.Full.StateSectorGetInfo(r.Context(), minerToAddr[s], abi.SectorNumber(uint64(sectors[i].SectorNum)), headKey) - if err != nil { - sectors[i].IsValid = false - continue - }*/ - } - } - - // Add deal details to sectors which are not on chain - for i := range sectors { - if !sectors[i].IsOnChain { - var pi []piece - dw, vp := .0, .0 - f05, ddo := 0, 0 - - // Find if there are any deals for this sector - if j, ok := pieceIndex[sectorID{sectors[i].MinerID, uint64(sectors[i].SectorNum)}]; ok { - for _, k := range j { - pi = append(pi, pieces[k]) - } - } - - if len(pi) > 0 { - for _, piece := range pi { - if piece.Proposal != nil { - var prop *market.DealProposal - apihelper.OrHTTPFail(w, json.Unmarshal(piece.Proposal, &prop)) - dw += float64(prop.PieceSize) - if prop.VerifiedDeal { - vp += float64(prop.PieceSize) * verifiedPowerGainMul - } - f05++ - } - if piece.Manifest != nil { - var pam *miner.PieceActivationManifest - apihelper.OrHTTPFail(w, json.Unmarshal(piece.Manifest, &pam)) - dw += float64(pam.Size) - if pam.VerifiedAllocationKey != nil { - vp += float64(pam.Size) * verifiedPowerGainMul - } - ddo++ - } - } - } - if dw > 0 { - sectors[i].DealWeight = fmt.Sprintf("%s", units.BytesSize(dw)) - } else if vp > 0 { - sectors[i].DealWeight = fmt.Sprintf("%s", units.BytesSize(vp)) - } else { - sectors[i].DealWeight = "CC" - } - sectors[i].Deals = fmt.Sprintf("Market: %d, DDO: %d", f05, ddo) - } - } - apihelper.OrHTTPFail(w, json.NewEncoder(w).Encode(map[string]any{"data": sectors})) -} - -type sectorInfo struct { - onChain *miner.SectorOnChainInfo - active bool -} - -type sectorCacheEntry struct { - sectors []sectorInfo - loading chan struct{} - time.Time -} - -const cacheTimeout = 30 * time.Minute - -var mx sync.Mutex -var sectorInfoCache = map[address.Address]sectorCacheEntry{} - -// getCachedSectorInfo returns the sector info for the given miner address, -// either from the cache or by querying the chain. -// Cache can be invalidated by setting the "sector_refresh" cookie to "true". -// This is thread-safe. -// Parallel requests share the chain's first response. -func (c *cfg) getCachedSectorInfo(w http.ResponseWriter, r *http.Request, maddr address.Address, headKey types.TipSetKey) ([]sectorInfo, error) { - mx.Lock() - v, ok := sectorInfoCache[maddr] - mx.Unlock() - - if ok && v.loading != nil { - <-v.loading - mx.Lock() - v, ok = sectorInfoCache[maddr] - mx.Unlock() - } - - shouldRefreshCookie, found := lo.Find(r.Cookies(), func(item *http.Cookie) bool { return item.Name == "sector_refresh" }) - shouldRefresh := found && shouldRefreshCookie.Value == "true" - w.Header().Set("Set-Cookie", "sector_refresh=; Max-Age=0; Path=/") - - if !ok || time.Since(v.Time) > cacheTimeout || shouldRefresh { - v = sectorCacheEntry{nil, make(chan struct{}), time.Now()} - mx.Lock() - sectorInfoCache[maddr] = v - mx.Unlock() - - // Intentionally not using the context from the request, as this is a cache - onChainInfo, err := c.Full.StateMinerSectors(context.Background(), maddr, nil, headKey) - if err != nil { - mx.Lock() - delete(sectorInfoCache, maddr) - close(v.loading) - mx.Unlock() - return nil, err - } - active, err := c.Full.StateMinerActiveSectors(context.Background(), maddr, headKey) - if err != nil { - mx.Lock() - delete(sectorInfoCache, maddr) - close(v.loading) - mx.Unlock() - return nil, err - } - activebf := bitfield.New() - for i := range active { - activebf.Set(uint64(active[i].SectorNumber)) - } - infos := make([]sectorInfo, len(onChainInfo)) - for i, info := range onChainInfo { - info := info - set, err := activebf.IsSet(uint64(info.SectorNumber)) - if err != nil { - mx.Lock() - delete(sectorInfoCache, maddr) - close(v.loading) - mx.Unlock() - return nil, err - } - infos[i] = sectorInfo{ - onChain: info, - active: set, - } - } - mx.Lock() - sectorInfoCache[maddr] = sectorCacheEntry{infos, nil, time.Now()} - close(v.loading) - mx.Unlock() - return infos, nil - } - return v.sectors, nil -} diff --git a/curiosrc/web/hapi/robust_rpc.go b/curiosrc/web/hapi/robust_rpc.go deleted file mode 100644 index c10b43a03..000000000 --- a/curiosrc/web/hapi/robust_rpc.go +++ /dev/null @@ -1,102 +0,0 @@ -package hapi - -import ( - "context" - "time" - - lru "github.com/hashicorp/golang-lru/v2" - blocks "github.com/ipfs/go-block-format" - - "github.com/filecoin-project/lotus/api/client" - "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/chain/store" - cliutil "github.com/filecoin-project/lotus/cli/util" - "github.com/filecoin-project/lotus/lib/must" -) - -var ChainBlockCache = must.One(lru.New[blockstore.MhString, blocks.Block](4096)) - -func (a *app) watchRpc() { - ticker := time.NewTicker(watchInterval) - for { - err := a.updateRpc(context.TODO()) - if err != nil { - log.Errorw("updating rpc info", "error", err) - } - select { - case <-ticker.C: - } - } -} - -type minimalApiInfo struct { - Apis struct { - ChainApiInfo []string - } -} - -func (a *app) updateRpc(ctx context.Context) error { - rpcInfos := map[string]minimalApiInfo{} // config name -> api info - confNameToAddr := map[string]string{} // config name -> api address - - err := forEachConfig[minimalApiInfo](a, func(name string, info minimalApiInfo) error { - if len(info.Apis.ChainApiInfo) == 0 { - return nil - } - - rpcInfos[name] = info - - for _, addr := range info.Apis.ChainApiInfo { - ai := cliutil.ParseApiInfo(addr) - confNameToAddr[name] = ai.Addr - } - - return nil - }) - if err != nil { - return err - } - - apiInfos := map[string][]byte{} // api address -> token - - // for dedup by address - for _, info := range rpcInfos { - ai := cliutil.ParseApiInfo(info.Apis.ChainApiInfo[0]) - apiInfos[ai.Addr] = ai.Token - } - - a.rpcInfoLk.Lock() - - // todo improve this shared rpc logic - if a.workingApi == nil { - for addr, token := range apiInfos { - ai := cliutil.APIInfo{ - Addr: addr, - Token: token, - } - - da, err := ai.DialArgs("v1") - if err != nil { - continue - } - - ah := ai.AuthHeader() - - v1api, closer, err := client.NewFullNodeRPCV1(ctx, da, ah) - if err != nil { - continue - } - go func() { - <-ctx.Done() - closer() - }() - - a.workingApi = v1api - a.stor = store.ActorStore(ctx, blockstore.NewReadCachedBlockstore(blockstore.NewAPIBlockstore(a.workingApi), ChainBlockCache)) - } - } - - a.rpcInfoLk.Unlock() - - return nil -} diff --git a/curiosrc/web/hapi/routes.go b/curiosrc/web/hapi/routes.go deleted file mode 100644 index 61724ec0a..000000000 --- a/curiosrc/web/hapi/routes.go +++ /dev/null @@ -1,58 +0,0 @@ -package hapi - -import ( - "embed" - "text/template" - - "github.com/gorilla/mux" - logging "github.com/ipfs/go-log/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/cmd/curio/deps" -) - -//go:embed web/* -var templateFS embed.FS - -func Routes(r *mux.Router, deps *deps.Deps) error { - t, err := makeTemplate().ParseFS(templateFS, "web/*") - if err != nil { - return xerrors.Errorf("parse templates: %w", err) - } - - a := &app{ - db: deps.DB, - t: t, - } - - go a.watchRpc() - go a.watchActor() - - // index page (simple info) - r.HandleFunc("/simpleinfo/actorsummary", a.actorSummary) - r.HandleFunc("/simpleinfo/machines", a.indexMachines) - r.HandleFunc("/simpleinfo/tasks", a.indexTasks) - r.HandleFunc("/simpleinfo/taskhistory", a.indexTasksHistory) - r.HandleFunc("/simpleinfo/pipeline-porep", a.indexPipelinePorep) - - // pipeline-porep page - r.HandleFunc("/pipeline-porep/sectors", a.pipelinePorepSectors) - - // node info page - r.HandleFunc("/node/{id}", a.nodeInfo) - - // sector info page - r.HandleFunc("/sector/{sp}/{id}", a.sectorInfo) - return nil -} - -func makeTemplate() *template.Template { - return template.New("").Funcs(template.FuncMap{ - "toHumanBytes": func(b int64) string { - return types.SizeStr(types.NewInt(uint64(b))) - }, - }) -} - -var log = logging.Logger("curio/web") diff --git a/curiosrc/web/hapi/simpleinfo.go b/curiosrc/web/hapi/simpleinfo.go deleted file mode 100644 index 287e11233..000000000 --- a/curiosrc/web/hapi/simpleinfo.go +++ /dev/null @@ -1,857 +0,0 @@ -package hapi - -import ( - "bytes" - "context" - "fmt" - "net/http" - "os" - "sort" - "strconv" - "strings" - "sync" - "text/template" - "time" - - "github.com/gorilla/mux" - "github.com/samber/lo" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - - "github.com/filecoin-project/lotus/api/v1api" - "github.com/filecoin-project/lotus/chain/actors/adt" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/must" - "github.com/filecoin-project/lotus/storage/paths" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -type app struct { - db *harmonydb.DB - t *template.Template - - rpcInfoLk sync.Mutex - workingApi v1api.FullNode - stor adt.Store - - actorInfoLk sync.Mutex - actorInfos []actorInfo -} - -type actorInfo struct { - Address string - CLayers []string - - QualityAdjustedPower string - RawBytePower string - - ActorBalance, ActorAvailable, WorkerBalance string - - Win1, Win7, Win30 int64 - - Deadlines []actorDeadline -} - -type actorDeadline struct { - Empty bool - Current bool - Proven bool - PartFaulty bool - Faulty bool -} - -func (a *app) actorSummary(w http.ResponseWriter, r *http.Request) { - a.actorInfoLk.Lock() - defer a.actorInfoLk.Unlock() - - a.executeTemplate(w, "actor_summary", a.actorInfos) -} - -func (a *app) indexMachines(w http.ResponseWriter, r *http.Request) { - s, err := a.clusterMachineSummary(r.Context()) - if err != nil { - log.Errorf("cluster machine summary: %v", err) - http.Error(w, "internal server error", http.StatusInternalServerError) - return - } - - a.executeTemplate(w, "cluster_machines", s) -} - -func (a *app) indexTasks(w http.ResponseWriter, r *http.Request) { - s, err := a.clusterTaskSummary(r.Context()) - if err != nil { - log.Errorf("cluster task summary: %v", err) - http.Error(w, "internal server error", http.StatusInternalServerError) - return - } - - a.executeTemplate(w, "cluster_tasks", s) -} - -func (a *app) indexTasksHistory(w http.ResponseWriter, r *http.Request) { - s, err := a.clusterTaskHistorySummary(r.Context()) - if err != nil { - log.Errorf("cluster task history summary: %v", err) - http.Error(w, "internal server error", http.StatusInternalServerError) - return - } - - a.executeTemplate(w, "cluster_task_history", s) -} - -func (a *app) indexPipelinePorep(w http.ResponseWriter, r *http.Request) { - s, err := a.porepPipelineSummary(r.Context()) - if err != nil { - log.Errorf("porep pipeline summary: %v", err) - http.Error(w, "internal server error", http.StatusInternalServerError) - return - } - - a.executeTemplate(w, "pipeline_porep", s) -} - -func (a *app) nodeInfo(writer http.ResponseWriter, request *http.Request) { - params := mux.Vars(request) - - id, ok := params["id"] - if !ok { - http.Error(writer, "missing id", http.StatusBadRequest) - return - } - - intid, err := strconv.ParseInt(id, 10, 64) - if err != nil { - http.Error(writer, "invalid id", http.StatusBadRequest) - return - } - - mi, err := a.clusterNodeInfo(request.Context(), intid) - if err != nil { - log.Errorf("machine info: %v", err) - http.Error(writer, "internal server error", http.StatusInternalServerError) - return - } - - a.executePageTemplate(writer, "node_info", "Node Info", mi) -} - -func (a *app) sectorInfo(w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - - id, ok := params["id"] - if !ok { - http.Error(w, "missing id", http.StatusBadRequest) - return - } - - intid, err := strconv.ParseInt(id, 10, 64) - if err != nil { - http.Error(w, "invalid id", http.StatusBadRequest) - return - } - - sp, ok := params["sp"] - if !ok { - http.Error(w, "missing sp", http.StatusBadRequest) - return - } - - maddr, err := address.NewFromString(sp) - if err != nil { - http.Error(w, "invalid sp", http.StatusBadRequest) - return - } - - spid, err := address.IDFromAddress(maddr) - if err != nil { - http.Error(w, "invalid sp", http.StatusBadRequest) - return - } - - ctx := r.Context() - var tasks []PipelineTask - - err = a.db.Select(ctx, &tasks, `SELECT - sp_id, sector_number, - create_time, - task_id_sdr, after_sdr, - task_id_tree_d, after_tree_d, - task_id_tree_c, after_tree_c, - task_id_tree_r, after_tree_r, - task_id_precommit_msg, after_precommit_msg, - after_precommit_msg_success, seed_epoch, - task_id_porep, porep_proof, after_porep, - task_id_finalize, after_finalize, - task_id_move_storage, after_move_storage, - task_id_commit_msg, after_commit_msg, - after_commit_msg_success, - failed, failed_reason - FROM sectors_sdr_pipeline WHERE sp_id = $1 AND sector_number = $2`, spid, intid) - if err != nil { - http.Error(w, xerrors.Errorf("failed to fetch pipeline task info: %w", err).Error(), http.StatusInternalServerError) - return - } - - if len(tasks) == 0 { - http.Error(w, "sector not found", http.StatusInternalServerError) - return - } - - head, err := a.workingApi.ChainHead(ctx) - if err != nil { - http.Error(w, xerrors.Errorf("failed to fetch chain head: %w", err).Error(), http.StatusInternalServerError) - return - } - epoch := head.Height() - - mbf, err := a.getMinerBitfields(ctx, maddr, a.stor) - if err != nil { - http.Error(w, xerrors.Errorf("failed to load miner bitfields: %w", err).Error(), http.StatusInternalServerError) - return - } - - task := tasks[0] - - afterSeed := task.SeedEpoch != nil && *task.SeedEpoch <= int64(epoch) - - var sectorLocations []struct { - CanSeal, CanStore bool - FileType storiface.SectorFileType `db:"sector_filetype"` - StorageID string `db:"storage_id"` - Urls string `db:"urls"` - } - - err = a.db.Select(ctx, §orLocations, `SELECT p.can_seal, p.can_store, l.sector_filetype, l.storage_id, p.urls FROM sector_location l - JOIN storage_path p ON l.storage_id = p.storage_id - WHERE l.sector_num = $1 and l.miner_id = $2 ORDER BY p.can_seal, p.can_store, l.storage_id`, intid, spid) - if err != nil { - http.Error(w, xerrors.Errorf("failed to fetch sector locations: %w", err).Error(), http.StatusInternalServerError) - return - } - - type fileLocations struct { - StorageID string - Urls []string - } - - type locationTable struct { - PathType *string - PathTypeRowSpan int - - FileType *string - FileTypeRowSpan int - - Locations []fileLocations - } - locs := []locationTable{} - - for i, loc := range sectorLocations { - loc := loc - - urlList := strings.Split(loc.Urls, paths.URLSeparator) - - fLoc := fileLocations{ - StorageID: loc.StorageID, - Urls: urlList, - } - - var pathTypeStr *string - var fileTypeStr *string - pathTypeRowSpan := 1 - fileTypeRowSpan := 1 - - pathType := "None" - if loc.CanSeal && loc.CanStore { - pathType = "Seal/Store" - } else if loc.CanSeal { - pathType = "Seal" - } else if loc.CanStore { - pathType = "Store" - } - pathTypeStr = &pathType - - fileType := loc.FileType.String() - fileTypeStr = &fileType - - if i > 0 { - prevNonNilPathTypeLoc := i - 1 - for prevNonNilPathTypeLoc > 0 && locs[prevNonNilPathTypeLoc].PathType == nil { - prevNonNilPathTypeLoc-- - } - if *locs[prevNonNilPathTypeLoc].PathType == *pathTypeStr { - pathTypeRowSpan = 0 - pathTypeStr = nil - locs[prevNonNilPathTypeLoc].PathTypeRowSpan++ - // only if we have extended path type we may need to extend file type - - prevNonNilFileTypeLoc := i - 1 - for prevNonNilFileTypeLoc > 0 && locs[prevNonNilFileTypeLoc].FileType == nil { - prevNonNilFileTypeLoc-- - } - if *locs[prevNonNilFileTypeLoc].FileType == *fileTypeStr { - fileTypeRowSpan = 0 - fileTypeStr = nil - locs[prevNonNilFileTypeLoc].FileTypeRowSpan++ - } - } - } - - locTable := locationTable{ - PathType: pathTypeStr, - PathTypeRowSpan: pathTypeRowSpan, - FileType: fileTypeStr, - FileTypeRowSpan: fileTypeRowSpan, - Locations: []fileLocations{fLoc}, - } - - locs = append(locs, locTable) - - } - - // TaskIDs - taskIDs := map[int64]struct{}{} - var htasks []taskSummary - { - // get non-nil task IDs - appendNonNil := func(id *int64) { - if id != nil { - taskIDs[*id] = struct{}{} - } - } - appendNonNil(task.TaskSDR) - appendNonNil(task.TaskTreeD) - appendNonNil(task.TaskTreeC) - appendNonNil(task.TaskTreeR) - appendNonNil(task.TaskPrecommitMsg) - appendNonNil(task.TaskPoRep) - appendNonNil(task.TaskFinalize) - appendNonNil(task.TaskMoveStorage) - appendNonNil(task.TaskCommitMsg) - - if len(taskIDs) > 0 { - ids := lo.Keys(taskIDs) - - var dbtasks []struct { - OwnerID *string `db:"owner_id"` - HostAndPort *string `db:"host_and_port"` - TaskID int64 `db:"id"` - Name string `db:"name"` - UpdateTime time.Time `db:"update_time"` - } - err = a.db.Select(ctx, &dbtasks, `SELECT t.owner_id, hm.host_and_port, t.id, t.name, t.update_time FROM harmony_task t LEFT JOIN curio.harmony_machines hm ON hm.id = t.owner_id WHERE t.id = ANY($1)`, ids) - if err != nil { - http.Error(w, fmt.Sprintf("failed to fetch task names: %v", err), http.StatusInternalServerError) - return - } - - for _, tn := range dbtasks { - htasks = append(htasks, taskSummary{ - Name: tn.Name, - SincePosted: time.Since(tn.UpdateTime.Local()).Round(time.Second).String(), - Owner: tn.HostAndPort, - OwnerID: tn.OwnerID, - ID: tn.TaskID, - }) - } - } - } - - mi := struct { - SectorNumber int64 - PipelinePoRep sectorListEntry - - Locations []locationTable - Tasks []taskSummary - }{ - SectorNumber: intid, - PipelinePoRep: sectorListEntry{ - PipelineTask: tasks[0], - AfterSeed: afterSeed, - - ChainAlloc: must.One(mbf.alloc.IsSet(uint64(task.SectorNumber))), - ChainSector: must.One(mbf.sectorSet.IsSet(uint64(task.SectorNumber))), - ChainActive: must.One(mbf.active.IsSet(uint64(task.SectorNumber))), - ChainUnproven: must.One(mbf.unproven.IsSet(uint64(task.SectorNumber))), - ChainFaulty: must.One(mbf.faulty.IsSet(uint64(task.SectorNumber))), - }, - - Locations: locs, - Tasks: htasks, - } - - a.executePageTemplate(w, "sector_info", "Sector Info", mi) -} - -var templateDev = os.Getenv("CURIO_WEB_DEV") == "1" - -func (a *app) executeTemplate(w http.ResponseWriter, name string, data interface{}) { - if templateDev { - fs := os.DirFS("./curiosrc/web/hapi/web") - a.t = template.Must(makeTemplate().ParseFS(fs, "*")) - } - if err := a.t.ExecuteTemplate(w, name, data); err != nil { - log.Errorf("execute template %s: %v", name, err) - http.Error(w, "internal server error", http.StatusInternalServerError) - } -} - -func (a *app) executePageTemplate(w http.ResponseWriter, name, title string, data interface{}) { - if templateDev { - fs := os.DirFS("./curiosrc/web/hapi/web") - a.t = template.Must(makeTemplate().ParseFS(fs, "*")) - } - var contentBuf bytes.Buffer - if err := a.t.ExecuteTemplate(&contentBuf, name, data); err != nil { - log.Errorf("execute template %s: %v", name, err) - http.Error(w, "internal server error", http.StatusInternalServerError) - } - a.executeTemplate(w, "root", map[string]interface{}{ - "PageTitle": title, - "Content": contentBuf.String(), - }) -} - -type machineRecentTask struct { - TaskName string - Success int64 - Fail int64 -} - -type machineSummary struct { - Address string - ID int64 - SinceContact string - - RecentTasks []*machineRecentTask -} - -type taskSummary struct { - Name string - SincePosted string - Owner, OwnerID *string - ID int64 -} - -type taskHistorySummary struct { - Name string - TaskID int64 - - Posted, Start, Queued, Took string - - Result bool - Err string - - CompletedBy string -} - -func (a *app) clusterMachineSummary(ctx context.Context) ([]machineSummary, error) { - // First get task summary for tasks completed in the last 24 hours - // NOTE: This query uses harmony_task_history_work_index, task history may get big - tsrows, err := a.db.Query(ctx, `SELECT hist.completed_by_host_and_port, hist.name, hist.result, count(1) FROM harmony_task_history hist - WHERE hist.work_end > now() - INTERVAL '1 day' - GROUP BY hist.completed_by_host_and_port, hist.name, hist.result - ORDER BY completed_by_host_and_port ASC`) - if err != nil { - return nil, err - } - defer tsrows.Close() - - // Map of machine -> task -> recent task - taskSummaries := map[string]map[string]*machineRecentTask{} - - for tsrows.Next() { - var taskName string - var result bool - var count int64 - var machine string - - if err := tsrows.Scan(&machine, &taskName, &result, &count); err != nil { - return nil, err - } - - if _, ok := taskSummaries[machine]; !ok { - taskSummaries[machine] = map[string]*machineRecentTask{} - } - - if _, ok := taskSummaries[machine][taskName]; !ok { - taskSummaries[machine][taskName] = &machineRecentTask{TaskName: taskName} - } - - if result { - taskSummaries[machine][taskName].Success = count - } else { - taskSummaries[machine][taskName].Fail = count - } - } - - // Then machine summary - rows, err := a.db.Query(ctx, "SELECT id, host_and_port, last_contact FROM harmony_machines order by host_and_port asc") - if err != nil { - return nil, err // Handle error - } - defer rows.Close() - - var summaries []machineSummary - for rows.Next() { - var m machineSummary - var lastContact time.Time - - if err := rows.Scan(&m.ID, &m.Address, &lastContact); err != nil { - return nil, err // Handle error - } - - m.SinceContact = time.Since(lastContact).Round(time.Second).String() - - // Add recent tasks - if ts, ok := taskSummaries[m.Address]; ok { - for _, t := range ts { - m.RecentTasks = append(m.RecentTasks, t) - } - sort.Slice(m.RecentTasks, func(i, j int) bool { - return m.RecentTasks[i].TaskName < m.RecentTasks[j].TaskName - }) - } - - summaries = append(summaries, m) - } - return summaries, nil -} - -func (a *app) clusterTaskSummary(ctx context.Context) ([]taskSummary, error) { - rows, err := a.db.Query(ctx, "SELECT t.id, t.name, t.update_time, t.owner_id, hm.host_and_port FROM harmony_task t LEFT JOIN curio.harmony_machines hm ON hm.id = t.owner_id ORDER BY t.update_time ASC, t.owner_id") - if err != nil { - return nil, err // Handle error - } - defer rows.Close() - - var summaries []taskSummary - for rows.Next() { - var t taskSummary - var posted time.Time - - if err := rows.Scan(&t.ID, &t.Name, &posted, &t.OwnerID, &t.Owner); err != nil { - return nil, err // Handle error - } - - t.SincePosted = time.Since(posted).Round(time.Second).String() - - summaries = append(summaries, t) - } - return summaries, nil -} - -func (a *app) clusterTaskHistorySummary(ctx context.Context) ([]taskHistorySummary, error) { - rows, err := a.db.Query(ctx, "SELECT id, name, task_id, posted, work_start, work_end, result, err, completed_by_host_and_port FROM harmony_task_history ORDER BY work_end DESC LIMIT 15") - if err != nil { - return nil, err // Handle error - } - defer rows.Close() - - var summaries []taskHistorySummary - for rows.Next() { - var t taskHistorySummary - var posted, start, end time.Time - - if err := rows.Scan(&t.TaskID, &t.Name, &t.TaskID, &posted, &start, &end, &t.Result, &t.Err, &t.CompletedBy); err != nil { - return nil, err // Handle error - } - - t.Posted = posted.Local().Round(time.Second).Format("02 Jan 06 15:04") - t.Start = start.Local().Round(time.Second).Format("02 Jan 06 15:04") - //t.End = end.Local().Round(time.Second).Format("02 Jan 06 15:04") - - t.Queued = start.Sub(posted).Round(time.Second).String() - if t.Queued == "0s" { - t.Queued = start.Sub(posted).Round(time.Millisecond).String() - } - - t.Took = end.Sub(start).Round(time.Second).String() - if t.Took == "0s" { - t.Took = end.Sub(start).Round(time.Millisecond).String() - } - - summaries = append(summaries, t) - } - return summaries, nil -} - -type porepPipelineSummary struct { - Actor string - - CountSDR int - CountTrees int - CountPrecommitMsg int - CountWaitSeed int - CountPoRep int - CountCommitMsg int - CountDone int - CountFailed int -} - -func (a *app) porepPipelineSummary(ctx context.Context) ([]porepPipelineSummary, error) { - rows, err := a.db.Query(ctx, ` - SELECT - sp_id, - COUNT(*) FILTER (WHERE after_sdr = false) as CountSDR, - COUNT(*) FILTER (WHERE (after_tree_d = false OR after_tree_c = false OR after_tree_r = false) AND after_sdr = true) as CountTrees, - COUNT(*) FILTER (WHERE after_tree_r = true and after_precommit_msg = false) as CountPrecommitMsg, - COUNT(*) FILTER (WHERE after_precommit_msg_success = false AND after_precommit_msg = true) as CountWaitSeed, - COUNT(*) FILTER (WHERE after_porep = false AND after_precommit_msg_success = true) as CountPoRep, - COUNT(*) FILTER (WHERE after_commit_msg_success = false AND after_porep = true) as CountCommitMsg, - COUNT(*) FILTER (WHERE after_commit_msg_success = true) as CountDone, - COUNT(*) FILTER (WHERE failed = true) as CountFailed - FROM - sectors_sdr_pipeline - GROUP BY sp_id`) - if err != nil { - return nil, xerrors.Errorf("query: %w", err) - } - defer rows.Close() - - var summaries []porepPipelineSummary - for rows.Next() { - var summary porepPipelineSummary - if err := rows.Scan(&summary.Actor, &summary.CountSDR, &summary.CountTrees, &summary.CountPrecommitMsg, &summary.CountWaitSeed, &summary.CountPoRep, &summary.CountCommitMsg, &summary.CountDone, &summary.CountFailed); err != nil { - return nil, xerrors.Errorf("scan: %w", err) - } - summary.Actor = "f0" + summary.Actor - - summaries = append(summaries, summary) - } - return summaries, nil -} - -type machineInfo struct { - Info struct { - Host string - ID int64 - LastContact string - CPU int64 - Memory int64 - GPU int64 - } - - // Storage - Storage []struct { - ID string - Weight int64 - MaxStorage int64 - CanSeal bool - CanStore bool - Groups string - AllowTo string - AllowTypes string - DenyTypes string - Capacity int64 - Available int64 - FSAvailable int64 - Reserved int64 - Used int64 - AllowMiners string - DenyMiners string - LastHeartbeat time.Time - HeartbeatErr *string - - UsedPercent float64 - ReservedPercent float64 - } - - /*TotalStorage struct { - MaxStorage int64 - UsedStorage int64 - - MaxSealStorage int64 - UsedSealStorage int64 - - MaxStoreStorage int64 - UsedStoreStorage int64 - }*/ - - // Tasks - RunningTasks []struct { - ID int64 - Task string - Posted string - - PoRepSector, PoRepSectorSP *int64 - } - - FinishedTasks []struct { - ID int64 - Task string - Posted string - Start string - Queued string - Took string - Outcome string - Message string - } -} - -func (a *app) clusterNodeInfo(ctx context.Context, id int64) (*machineInfo, error) { - rows, err := a.db.Query(ctx, "SELECT id, host_and_port, last_contact, cpu, ram, gpu FROM harmony_machines WHERE id=$1 ORDER BY host_and_port ASC", id) - if err != nil { - return nil, err // Handle error - } - defer rows.Close() - - var summaries []machineInfo - if rows.Next() { - var m machineInfo - var lastContact time.Time - - if err := rows.Scan(&m.Info.ID, &m.Info.Host, &lastContact, &m.Info.CPU, &m.Info.Memory, &m.Info.GPU); err != nil { - return nil, err - } - - m.Info.LastContact = time.Since(lastContact).Round(time.Second).String() - - summaries = append(summaries, m) - } - - if len(summaries) == 0 { - return nil, xerrors.Errorf("machine not found") - } - - // query storage info - rows2, err := a.db.Query(ctx, "SELECT storage_id, weight, max_storage, can_seal, can_store, groups, allow_to, allow_types, deny_types, capacity, available, fs_available, reserved, used, allow_miners, deny_miners, last_heartbeat, heartbeat_err FROM storage_path WHERE urls LIKE '%' || $1 || '%'", summaries[0].Info.Host) - if err != nil { - return nil, err - } - - defer rows2.Close() - - for rows2.Next() { - var s struct { - ID string - Weight int64 - MaxStorage int64 - CanSeal bool - CanStore bool - Groups string - AllowTo string - AllowTypes string - DenyTypes string - Capacity int64 - Available int64 - FSAvailable int64 - Reserved int64 - Used int64 - AllowMiners string - DenyMiners string - LastHeartbeat time.Time - HeartbeatErr *string - - UsedPercent float64 - ReservedPercent float64 - } - if err := rows2.Scan(&s.ID, &s.Weight, &s.MaxStorage, &s.CanSeal, &s.CanStore, &s.Groups, &s.AllowTo, &s.AllowTypes, &s.DenyTypes, &s.Capacity, &s.Available, &s.FSAvailable, &s.Reserved, &s.Used, &s.AllowMiners, &s.DenyMiners, &s.LastHeartbeat, &s.HeartbeatErr); err != nil { - return nil, err - } - - s.UsedPercent = float64(s.Capacity-s.FSAvailable) * 100 / float64(s.Capacity) - s.ReservedPercent = float64(s.Capacity-(s.FSAvailable+s.Reserved))*100/float64(s.Capacity) - s.UsedPercent - - summaries[0].Storage = append(summaries[0].Storage, s) - } - - // tasks - rows3, err := a.db.Query(ctx, "SELECT id, name, posted_time FROM harmony_task WHERE owner_id=$1", summaries[0].Info.ID) - if err != nil { - return nil, err - } - - defer rows3.Close() - - for rows3.Next() { - var t struct { - ID int64 - Task string - Posted string - - PoRepSector *int64 - PoRepSectorSP *int64 - } - - var posted time.Time - if err := rows3.Scan(&t.ID, &t.Task, &posted); err != nil { - return nil, err - } - t.Posted = time.Since(posted).Round(time.Second).String() - - { - // try to find in the porep pipeline - rows4, err := a.db.Query(ctx, `SELECT sp_id, sector_number FROM sectors_sdr_pipeline - WHERE task_id_sdr=$1 - OR task_id_tree_d=$1 - OR task_id_tree_c=$1 - OR task_id_tree_r=$1 - OR task_id_precommit_msg=$1 - OR task_id_porep=$1 - OR task_id_commit_msg=$1 - OR task_id_finalize=$1 - OR task_id_move_storage=$1 - `, t.ID) - if err != nil { - return nil, err - } - - if rows4.Next() { - var spid int64 - var sector int64 - if err := rows4.Scan(&spid, §or); err != nil { - return nil, err - } - t.PoRepSector = §or - t.PoRepSectorSP = &spid - } - - rows4.Close() - } - - summaries[0].RunningTasks = append(summaries[0].RunningTasks, t) - } - - rows5, err := a.db.Query(ctx, `SELECT name, task_id, posted, work_start, work_end, result, err FROM harmony_task_history WHERE completed_by_host_and_port = $1 ORDER BY work_end DESC LIMIT 15`, summaries[0].Info.Host) - if err != nil { - return nil, err - } - defer rows5.Close() - - for rows5.Next() { - var ft struct { - ID int64 - Task string - Posted string - Start string - Queued string - Took string - Outcome string - - Message string - } - - var posted, start, end time.Time - var result bool - if err := rows5.Scan(&ft.Task, &ft.ID, &posted, &start, &end, &result, &ft.Message); err != nil { - return nil, err - } - - ft.Outcome = "Success" - if !result { - ft.Outcome = "Failed" - } - - // Format the times and durations - ft.Posted = posted.Format("02 Jan 06 15:04 MST") - ft.Start = start.Format("02 Jan 06 15:04 MST") - ft.Queued = fmt.Sprintf("%s", start.Sub(posted).Round(time.Second).String()) - ft.Took = fmt.Sprintf("%s", end.Sub(start).Round(time.Second)) - - summaries[0].FinishedTasks = append(summaries[0].FinishedTasks, ft) - } - - return &summaries[0], nil -} diff --git a/curiosrc/web/hapi/simpleinfo_pipeline_porep.go b/curiosrc/web/hapi/simpleinfo_pipeline_porep.go deleted file mode 100644 index a37cd14ab..000000000 --- a/curiosrc/web/hapi/simpleinfo_pipeline_porep.go +++ /dev/null @@ -1,195 +0,0 @@ -package hapi - -import ( - "context" - "net/http" - "time" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-bitfield" - - "github.com/filecoin-project/lotus/chain/actors/adt" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/lib/must" -) - -type PipelineTask struct { - SpID int64 `db:"sp_id"` - SectorNumber int64 `db:"sector_number"` - - CreateTime time.Time `db:"create_time"` - - TaskSDR *int64 `db:"task_id_sdr"` - AfterSDR bool `db:"after_sdr"` - - TaskTreeD *int64 `db:"task_id_tree_d"` - AfterTreeD bool `db:"after_tree_d"` - - TaskTreeC *int64 `db:"task_id_tree_c"` - AfterTreeC bool `db:"after_tree_c"` - - TaskTreeR *int64 `db:"task_id_tree_r"` - AfterTreeR bool `db:"after_tree_r"` - - TaskPrecommitMsg *int64 `db:"task_id_precommit_msg"` - AfterPrecommitMsg bool `db:"after_precommit_msg"` - - AfterPrecommitMsgSuccess bool `db:"after_precommit_msg_success"` - SeedEpoch *int64 `db:"seed_epoch"` - - TaskPoRep *int64 `db:"task_id_porep"` - PoRepProof []byte `db:"porep_proof"` - AfterPoRep bool `db:"after_porep"` - - TaskFinalize *int64 `db:"task_id_finalize"` - AfterFinalize bool `db:"after_finalize"` - - TaskMoveStorage *int64 `db:"task_id_move_storage"` - AfterMoveStorage bool `db:"after_move_storage"` - - TaskCommitMsg *int64 `db:"task_id_commit_msg"` - AfterCommitMsg bool `db:"after_commit_msg"` - - AfterCommitMsgSuccess bool `db:"after_commit_msg_success"` - - Failed bool `db:"failed"` - FailedReason string `db:"failed_reason"` -} - -type sectorListEntry struct { - PipelineTask - - Address address.Address - CreateTime string - AfterSeed bool - - ChainAlloc, ChainSector, ChainActive, ChainUnproven, ChainFaulty bool -} - -type minerBitfields struct { - alloc, sectorSet, active, unproven, faulty bitfield.BitField -} - -func (a *app) pipelinePorepSectors(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var tasks []PipelineTask - - err := a.db.Select(ctx, &tasks, `SELECT - sp_id, sector_number, - create_time, - task_id_sdr, after_sdr, - task_id_tree_d, after_tree_d, - task_id_tree_c, after_tree_c, - task_id_tree_r, after_tree_r, - task_id_precommit_msg, after_precommit_msg, - after_precommit_msg_success, seed_epoch, - task_id_porep, porep_proof, after_porep, - task_id_finalize, after_finalize, - task_id_move_storage, after_move_storage, - task_id_commit_msg, after_commit_msg, - after_commit_msg_success, - failed, failed_reason - FROM sectors_sdr_pipeline order by sp_id, sector_number`) // todo where constrain list - if err != nil { - http.Error(w, xerrors.Errorf("failed to fetch pipeline tasks: %w", err).Error(), http.StatusInternalServerError) - return - } - - head, err := a.workingApi.ChainHead(ctx) - if err != nil { - http.Error(w, xerrors.Errorf("failed to fetch chain head: %w", err).Error(), http.StatusInternalServerError) - return - } - epoch := head.Height() - - minerBitfieldCache := map[address.Address]minerBitfields{} - - sectorList := make([]sectorListEntry, 0, len(tasks)) - for _, task := range tasks { - task := task - - task.CreateTime = task.CreateTime.Local() - - addr, err := address.NewIDAddress(uint64(task.SpID)) - if err != nil { - http.Error(w, xerrors.Errorf("failed to create actor address: %w", err).Error(), http.StatusInternalServerError) - return - } - - mbf, ok := minerBitfieldCache[addr] - if !ok { - mbf, err := a.getMinerBitfields(ctx, addr, a.stor) - if err != nil { - http.Error(w, xerrors.Errorf("failed to load miner bitfields: %w", err).Error(), http.StatusInternalServerError) - return - } - minerBitfieldCache[addr] = mbf - } - - afterSeed := task.SeedEpoch != nil && *task.SeedEpoch <= int64(epoch) - - sectorList = append(sectorList, sectorListEntry{ - PipelineTask: task, - Address: addr, - CreateTime: task.CreateTime.Format(time.DateTime), - AfterSeed: afterSeed, - - ChainAlloc: must.One(mbf.alloc.IsSet(uint64(task.SectorNumber))), - ChainSector: must.One(mbf.sectorSet.IsSet(uint64(task.SectorNumber))), - ChainActive: must.One(mbf.active.IsSet(uint64(task.SectorNumber))), - ChainUnproven: must.One(mbf.unproven.IsSet(uint64(task.SectorNumber))), - ChainFaulty: must.One(mbf.faulty.IsSet(uint64(task.SectorNumber))), - }) - } - - a.executeTemplate(w, "pipeline_porep_sectors", sectorList) -} - -func (a *app) getMinerBitfields(ctx context.Context, addr address.Address, stor adt.Store) (minerBitfields, error) { - act, err := a.workingApi.StateGetActor(ctx, addr, types.EmptyTSK) - if err != nil { - return minerBitfields{}, xerrors.Errorf("failed to load actor: %w", err) - } - - mas, err := miner.Load(stor, act) - if err != nil { - return minerBitfields{}, xerrors.Errorf("failed to load miner actor: %w", err) - } - - activeSectors, err := miner.AllPartSectors(mas, miner.Partition.ActiveSectors) - if err != nil { - return minerBitfields{}, xerrors.Errorf("failed to load active sectors: %w", err) - } - - allSectors, err := miner.AllPartSectors(mas, miner.Partition.AllSectors) - if err != nil { - return minerBitfields{}, xerrors.Errorf("failed to load all sectors: %w", err) - } - - unproved, err := miner.AllPartSectors(mas, miner.Partition.UnprovenSectors) - if err != nil { - return minerBitfields{}, xerrors.Errorf("failed to load unproven sectors: %w", err) - } - - faulty, err := miner.AllPartSectors(mas, miner.Partition.FaultySectors) - if err != nil { - return minerBitfields{}, xerrors.Errorf("failed to load faulty sectors: %w", err) - } - - alloc, err := mas.GetAllocatedSectors() - if err != nil { - return minerBitfields{}, xerrors.Errorf("failed to load allocated sectors: %w", err) - } - - return minerBitfields{ - alloc: *alloc, - sectorSet: allSectors, - active: activeSectors, - unproven: unproved, - faulty: faulty, - }, nil -} diff --git a/curiosrc/web/hapi/watch_actor.go b/curiosrc/web/hapi/watch_actor.go deleted file mode 100644 index 51e1f51e7..000000000 --- a/curiosrc/web/hapi/watch_actor.go +++ /dev/null @@ -1,286 +0,0 @@ -package hapi - -import ( - "context" - "sort" - "time" - - "github.com/BurntSushi/toml" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - - "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/store" - "github.com/filecoin-project/lotus/chain/types" -) - -const watchInterval = time.Second * 10 - -func (a *app) watchActor() { - ticker := time.NewTicker(watchInterval) - for { - err := a.updateActor(context.TODO()) - if err != nil { - log.Errorw("updating rpc info", "error", err) - } - select { - case <-ticker.C: - } - } -} - -type minimalActorInfo struct { - Addresses []struct { - MinerAddresses []string - } -} - -var startedAt = time.Now() - -func (a *app) updateActor(ctx context.Context) error { - a.rpcInfoLk.Lock() - api := a.workingApi - a.rpcInfoLk.Unlock() - - stor := store.ActorStore(ctx, blockstore.NewReadCachedBlockstore(blockstore.NewAPIBlockstore(a.workingApi), ChainBlockCache)) - - if api == nil { - if time.Since(startedAt) > time.Second*10 { - log.Warnw("no working api yet") - } - return nil - } - - var actorInfos []actorInfo - - confNameToAddr := map[address.Address][]string{} // address -> config names - - err := forEachConfig[minimalActorInfo](a, func(name string, info minimalActorInfo) error { - for _, aset := range info.Addresses { - for _, addr := range aset.MinerAddresses { - a, err := address.NewFromString(addr) - if err != nil { - return xerrors.Errorf("parsing address: %w", err) - } - confNameToAddr[a] = append(confNameToAddr[a], name) - } - } - - return nil - }) - if err != nil { - return err - } - - wins, err := a.spWins(ctx) - if err != nil { - return xerrors.Errorf("getting sp wins: %w", err) - } - - for addr, cnames := range confNameToAddr { - p, err := api.StateMinerPower(ctx, addr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting miner power: %w", err) - } - - dls, err := api.StateMinerDeadlines(ctx, addr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting deadlines: %w", err) - } - - mact, err := api.StateGetActor(ctx, addr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting actor: %w", err) - } - - mas, err := miner.Load(stor, mact) - if err != nil { - return err - } - - outDls := []actorDeadline{} - - for dlidx := range dls { - p, err := api.StateMinerPartitions(ctx, addr, uint64(dlidx), types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting partition: %w", err) - } - - dl := actorDeadline{ - Empty: false, - Current: false, // todo - Proven: false, - PartFaulty: false, - Faulty: false, - } - - var live, faulty uint64 - - for _, part := range p { - l, err := part.LiveSectors.Count() - if err != nil { - return xerrors.Errorf("getting live sectors: %w", err) - } - live += l - - f, err := part.FaultySectors.Count() - if err != nil { - return xerrors.Errorf("getting faulty sectors: %w", err) - } - faulty += f - } - - dl.Empty = live == 0 - dl.Proven = live > 0 && faulty == 0 - dl.PartFaulty = faulty > 0 - dl.Faulty = faulty > 0 && faulty == live - - outDls = append(outDls, dl) - } - - pd, err := api.StateMinerProvingDeadline(ctx, addr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting proving deadline: %w", err) - } - - if len(outDls) != 48 { - return xerrors.Errorf("expected 48 deadlines, got %d", len(outDls)) - } - - outDls[pd.Index].Current = true - - avail, err := mas.AvailableBalance(mact.Balance) - if err != nil { - return xerrors.Errorf("getting available balance: %w", err) - } - - mi, err := mas.Info() - if err != nil { - return xerrors.Errorf("getting miner info: %w", err) - } - - wbal, err := api.WalletBalance(ctx, mi.Worker) - if err != nil { - return xerrors.Errorf("getting worker balance: %w", err) - } - - sort.Strings(cnames) - - actorInfos = append(actorInfos, actorInfo{ - Address: addr.String(), - CLayers: cnames, - QualityAdjustedPower: types.DeciStr(p.MinerPower.QualityAdjPower), - RawBytePower: types.DeciStr(p.MinerPower.RawBytePower), - Deadlines: outDls, - - ActorBalance: types.FIL(mact.Balance).Short(), - ActorAvailable: types.FIL(avail).Short(), - WorkerBalance: types.FIL(wbal).Short(), - - Win1: wins[addr].Win1, // note: zero values are fine here - Win7: wins[addr].Win7, - Win30: wins[addr].Win30, - }) - } - - sort.Slice(actorInfos, func(i, j int) bool { - return actorInfos[i].Address < actorInfos[j].Address - }) - - a.actorInfoLk.Lock() - a.actorInfos = actorInfos - a.actorInfoLk.Unlock() - - return nil -} - -func (a *app) loadConfigs(ctx context.Context) (map[string]string, error) { - rows, err := a.db.Query(ctx, `SELECT title, config FROM harmony_config`) - if err != nil { - return nil, xerrors.Errorf("getting db configs: %w", err) - } - - configs := make(map[string]string) - for rows.Next() { - var title, config string - if err := rows.Scan(&title, &config); err != nil { - return nil, xerrors.Errorf("scanning db configs: %w", err) - } - configs[title] = config - } - - return configs, nil -} - -type wins struct { - SpID int64 `db:"sp_id"` - Win1 int64 `db:"win1"` - Win7 int64 `db:"win7"` - Win30 int64 `db:"win30"` -} - -func (a *app) spWins(ctx context.Context) (map[address.Address]wins, error) { - var w []wins - - // note: this query uses mining_tasks_won_sp_id_base_compute_time_index - err := a.db.Select(ctx, &w, `WITH wins AS ( - SELECT - sp_id, - base_compute_time, - won - FROM - mining_tasks - WHERE - won = true - AND base_compute_time > NOW() - INTERVAL '30 days' - ) - - SELECT - sp_id, - COUNT(*) FILTER (WHERE base_compute_time > NOW() - INTERVAL '1 day') AS "win1", - COUNT(*) FILTER (WHERE base_compute_time > NOW() - INTERVAL '7 days') AS "win7", - COUNT(*) FILTER (WHERE base_compute_time > NOW() - INTERVAL '30 days') AS "win30" - FROM - wins - GROUP BY - sp_id - ORDER BY - sp_id`) - if err != nil { - return nil, xerrors.Errorf("query win counts: %w", err) - } - - wm := make(map[address.Address]wins) - for _, wi := range w { - ma, err := address.NewIDAddress(uint64(wi.SpID)) - if err != nil { - return nil, xerrors.Errorf("parsing miner address: %w", err) - } - - wm[ma] = wi - } - - return wm, nil -} - -func forEachConfig[T any](a *app, cb func(name string, v T) error) error { - confs, err := a.loadConfigs(context.Background()) - if err != nil { - return err - } - - for name, tomlStr := range confs { - var info T - if err := toml.Unmarshal([]byte(tomlStr), &info); err != nil { - return xerrors.Errorf("unmarshaling %s config: %w", name, err) - } - - if err := cb(name, info); err != nil { - return xerrors.Errorf("cb: %w", err) - } - } - - return nil -} diff --git a/curiosrc/web/hapi/web/actor_summary.gohtml b/curiosrc/web/hapi/web/actor_summary.gohtml deleted file mode 100644 index bf577d802..000000000 --- a/curiosrc/web/hapi/web/actor_summary.gohtml +++ /dev/null @@ -1,30 +0,0 @@ -{{define "actor_summary"}} -{{range .}} - - {{.Address}} - - {{range .CLayers}} - {{.}} - {{end}} - - {{.QualityAdjustedPower}} - -

- - {{.ActorBalance}} - {{.ActorAvailable}} - {{.WorkerBalance}} - - - - - -
1day:  {{.Win1}}
7day:  {{.Win7}}
30day: {{.Win30}}
- - -{{end}} -{{end}} \ No newline at end of file diff --git a/curiosrc/web/hapi/web/chain_rpcs.gohtml b/curiosrc/web/hapi/web/chain_rpcs.gohtml deleted file mode 100644 index 5705da395..000000000 --- a/curiosrc/web/hapi/web/chain_rpcs.gohtml +++ /dev/null @@ -1,15 +0,0 @@ -{{define "chain_rpcs"}} -{{range .}} - - {{.Address}} - - {{range .CLayers}} - {{.}} - {{end}} - - {{if .Reachable}}ok{{else}}FAIL{{end}} - {{if eq "ok" .SyncState}}ok{{else}}{{.SyncState}}{{end}} - {{.Version}} - -{{end}} -{{end}} diff --git a/curiosrc/web/hapi/web/cluster_machines.gohtml b/curiosrc/web/hapi/web/cluster_machines.gohtml deleted file mode 100644 index 5fb18b52c..000000000 --- a/curiosrc/web/hapi/web/cluster_machines.gohtml +++ /dev/null @@ -1,12 +0,0 @@ -{{define "cluster_machines"}} -{{range .}} - -
{{.Address}} - {{.ID}} - {{.SinceContact}} - {{range .RecentTasks}} - {{.TaskName}}:{{.Success}}{{if ne 0 .Fail}}({{.Fail}}){{end}} - {{end}} - -{{end}} -{{end}} diff --git a/curiosrc/web/hapi/web/cluster_task_history.gohtml b/curiosrc/web/hapi/web/cluster_task_history.gohtml deleted file mode 100644 index f95dbb2b2..000000000 --- a/curiosrc/web/hapi/web/cluster_task_history.gohtml +++ /dev/null @@ -1,19 +0,0 @@ -{{define "cluster_task_history"}} - {{range .}} - - {{.Name}} - {{.TaskID}} - {{.CompletedBy}} - {{.Posted}} - {{.Start}} - {{.Queued}} - {{.Took}} - {{if .Result}}success{{else}}error{{end}} - -
- {{.Err}} -
- - - {{end}} -{{end}} diff --git a/curiosrc/web/hapi/web/cluster_tasks.gohtml b/curiosrc/web/hapi/web/cluster_tasks.gohtml deleted file mode 100644 index b7b3faec0..000000000 --- a/curiosrc/web/hapi/web/cluster_tasks.gohtml +++ /dev/null @@ -1,10 +0,0 @@ -{{define "cluster_tasks"}} - {{range .}} - - {{.Name}} - {{.ID}} - {{.SincePosted}} - {{if ne nil .OwnerID}}{{.Owner}}{{end}} - - {{end}} -{{end}} diff --git a/curiosrc/web/hapi/web/node_info.gohtml b/curiosrc/web/hapi/web/node_info.gohtml deleted file mode 100644 index 16f60a475..000000000 --- a/curiosrc/web/hapi/web/node_info.gohtml +++ /dev/null @@ -1,100 +0,0 @@ -{{define "node_info"}} -

Info

- - - - - - - - - - - - - - - - - - - -
HostIDLast ContactCPUMemoryGPUDebug
{{.Info.Host}}{{.Info.ID}}{{.Info.LastContact}}{{.Info.CPU}}{{toHumanBytes .Info.Memory}}{{.Info.GPU}}[pprof]
-
-

Storage

- - - - - - - - - - {{range .Storage}} - - - - - - - - - {{end}} - -
IDTypeCapacityAvailableReserved
{{.ID}} - {{if and (not .CanSeal) (not .CanStore)}}ReadOnly{{end}} - {{if and (.CanSeal) (not .CanStore)}}Seal{{end}} - {{if and (not .CanSeal) (.CanStore)}}Store{{end}} - {{if and (.CanSeal) (.CanStore)}}Seal+Store{{end}} - {{toHumanBytes .Capacity}}{{toHumanBytes .Available}}{{toHumanBytes .Reserved}} -
-
-
-
-
-
-

Tasks

-

Running

- - - - - - - - {{range .RunningTasks}} - - - - - - - {{end}} -
IDTaskPostedSector
{{.ID}}{{.Task}}{{.Posted}}{{if ne nil .PoRepSector}}f0{{.PoRepSectorSP}}:{{.PoRepSector}}{{end}}
-

Recently Finished

- - - - - - - - - - - - {{range .FinishedTasks}} - - - - - - - - - - - {{end}} -
IDTaskPostedStartQueuedTookOutcomeMessage
{{.ID}}{{.Task}}{{.Posted}}{{.Start}}{{.Queued}}{{.Took}}{{.Outcome}}{{.Message}}
-{{end}} diff --git a/curiosrc/web/hapi/web/pipeline_porep_sectors.gohtml b/curiosrc/web/hapi/web/pipeline_porep_sectors.gohtml deleted file mode 100644 index 82f0ad196..000000000 --- a/curiosrc/web/hapi/web/pipeline_porep_sectors.gohtml +++ /dev/null @@ -1,200 +0,0 @@ -{{define "sector_porep_state"}} - - - - - - - - - - - - - - - - - - - - - - - - - -
-
SDR
-
- {{if .AfterSDR}}done{{else}} - {{if ne .TaskSDR nil}}T:{{.TaskSDR}}{{else}}--{{end}} - {{end}} -
-
-
TreeC
-
- {{if .AfterTreeC}}done{{else}} - {{if ne .TaskTreeC nil}}T:{{.TaskTreeC}}{{else}}--{{end}} - {{end}} -
-
-
PComm Msg
-
- {{if .AfterPrecommitMsg}}done{{else}} - {{if ne .TaskPrecommitMsg nil}}T:{{.TaskPrecommitMsg}}{{else}}--{{end}} - {{end}} -
-
-
PComm Wait
-
- {{if .AfterPrecommitMsgSuccess}}done{{else}} - -- - {{end}} -
-
-
Wait Seed
-
- {{if .AfterSeed}}done{{else}} - {{if ne .SeedEpoch nil}}@{{.SeedEpoch}}{{else}}--{{end}} - {{end}} -
-
-
PoRep
-
- {{if .AfterPoRep}}done{{else}} - {{if ne .TaskPoRep nil}}T:{{.TaskPoRep}}{{else}}--{{end}} - {{end}} -
-
-
Clear Cache
-
- {{if .AfterFinalize}}done{{else}} - {{if ne .TaskFinalize nil}}T:{{.TaskFinalize}}{{else}}--{{end}} - {{end}} -
-
-
Move Storage
-
- {{if .AfterMoveStorage}}done{{else}} - {{if ne .TaskMoveStorage nil}}T:{{.TaskMoveStorage}}{{else}}--{{end}} - {{end}} -
-
-
On Chain
-
{{if .ChainSector}}yes{{else}}{{if .ChainAlloc}}allocated{{else}}no{{end}}{{end}}
-
-
TreeD
-
- {{if .AfterTreeD}}done{{else}} - {{if ne .TaskTreeD nil}}T:{{.TaskTreeD}}{{else}}--{{end}} - {{end}} -
-
-
TreeR
-
- {{if .AfterTreeR}}done{{else}} - {{if ne .TaskTreeR nil}}T:{{.TaskTreeR}}{{else}}--{{end}} - {{end}} -
-
-
Commit Msg
-
- {{if .AfterCommitMsg}}done{{else}} - {{if ne .TaskCommitMsg nil}}T:{{.TaskCommitMsg}}{{else}}--{{end}} - {{end}} -
-
-
Commit Wait
-
- {{if .AfterCommitMsgSuccess}}done{{else}} - -- - {{end}} -
-
-
Active
-
{{if .ChainActive}}yes{{else}} - {{if .ChainUnproven}}unproven{{else}} - {{if .ChainFaulty}}faulty{{else}}no{{end}} - {{end}} - {{end}} -
-
-{{end}} - -{{define "pipeline_porep_sectors"}} - {{range .}} - - {{.Address}} - {{.CreateTime}} - - {{template "sector_porep_state" .}} - - - DETAILS - - - - - {{.SectorNumber}} - - - {{end}} -{{end}} diff --git a/curiosrc/web/hapi/web/pipline_porep.gohtml b/curiosrc/web/hapi/web/pipline_porep.gohtml deleted file mode 100644 index 5e7c7f7c6..000000000 --- a/curiosrc/web/hapi/web/pipline_porep.gohtml +++ /dev/null @@ -1,15 +0,0 @@ -{{define "pipeline_porep"}} - {{range .}} - - {{.Actor}} - {{.CountSDR}} - {{.CountTrees}} - {{.CountPrecommitMsg}} - {{.CountWaitSeed}} - {{.CountPoRep}} - {{.CountCommitMsg}} - {{.CountDone}} - {{.CountFailed}} - - {{end}} -{{end}} diff --git a/curiosrc/web/hapi/web/root.gohtml b/curiosrc/web/hapi/web/root.gohtml deleted file mode 100644 index 114db6462..000000000 --- a/curiosrc/web/hapi/web/root.gohtml +++ /dev/null @@ -1,26 +0,0 @@ -{{define "root"}} - - - {{.PageTitle}} - - - - - - - - - -
-
-

{{.PageTitle}}

-
-
-
-
- {{.Content}} -
-
- - -{{end}} diff --git a/curiosrc/web/hapi/web/sector_info.gohtml b/curiosrc/web/hapi/web/sector_info.gohtml deleted file mode 100644 index afa96a923..000000000 --- a/curiosrc/web/hapi/web/sector_info.gohtml +++ /dev/null @@ -1,57 +0,0 @@ -{{define "sector_info"}} -

Sector {{.SectorNumber}}

-
-

PoRep Pipeline

- {{template "sector_porep_state" .PipelinePoRep}} -
-
-

Storage

- - - - - - - - {{range .Locations}} - - {{if .PathType}} - - {{end}} - {{if .FileType}} - - {{end}} - - - - {{range $i, $loc := .Locations}} - {{if gt $i 0}} - - - - - {{end}} - {{end}} - {{end}} -
Path TypeFile TypePath IDHost
{{.PathType}}{{.FileType}}{{(index .Locations 0).StorageID}}{{range (index .Locations 0).Urls}}

{{.}}

{{end}}
{{$loc.StorageID}}{{range $loc.Urls}}

{{.}}

{{end}}
-
-
-

Tasks

- - - - - - - - {{range .Tasks}} - - - - - - - {{end}} -
Task TypeTask IDPostedWorker
{{.Name}}{{.ID}}{{.SincePosted}}{{if ne nil .OwnerID}}{{.Owner}}{{end}}
-
-{{end}} diff --git a/curiosrc/web/srv.go b/curiosrc/web/srv.go deleted file mode 100644 index b16a9f9af..000000000 --- a/curiosrc/web/srv.go +++ /dev/null @@ -1,82 +0,0 @@ -// Package web defines the HTTP web server for static files and endpoints. -package web - -import ( - "context" - "embed" - "io" - "io/fs" - "net" - "net/http" - "os" - "path" - "strings" - "time" - - "github.com/gorilla/mux" - "go.opencensus.io/tag" - - "github.com/filecoin-project/lotus/cmd/curio/deps" - "github.com/filecoin-project/lotus/curiosrc/web/api" - "github.com/filecoin-project/lotus/curiosrc/web/hapi" - "github.com/filecoin-project/lotus/metrics" -) - -//go:embed static -var static embed.FS - -var basePath = "/static/" - -// An dev mode hack for no-restart changes to static and templates. -// You still need to recomplie the binary for changes to go code. -var webDev = os.Getenv("CURIO_WEB_DEV") == "1" - -func GetSrv(ctx context.Context, deps *deps.Deps) (*http.Server, error) { - mx := mux.NewRouter() - err := hapi.Routes(mx.PathPrefix("/hapi").Subrouter(), deps) - if err != nil { - return nil, err - } - api.Routes(mx.PathPrefix("/api").Subrouter(), deps) - - var static fs.FS = static - if webDev { - basePath = "" - static = os.DirFS("curiosrc/web/static") - } - - mx.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // If the request is for a directory, redirect to the index file. - if strings.HasSuffix(r.URL.Path, "/") { - r.URL.Path += "index.html" - } - - file, err := static.Open(path.Join(basePath, r.URL.Path)[1:]) - if err != nil { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte("404 Not Found")) - return - } - defer func() { _ = file.Close() }() - - fileInfo, err := file.Stat() - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte("500 Internal Server Error")) - return - } - - http.ServeContent(w, r, fileInfo.Name(), fileInfo.ModTime(), file.(io.ReadSeeker)) - }) - - return &http.Server{ - Handler: http.HandlerFunc(mx.ServeHTTP), - BaseContext: func(listener net.Listener) context.Context { - ctx, _ := tag.New(context.Background(), tag.Upsert(metrics.APIInterface, "curio")) - return ctx - }, - Addr: deps.Cfg.Subsystems.GuiAddress, - ReadTimeout: time.Minute * 3, - ReadHeaderTimeout: time.Minute * 3, // lint - }, nil -} diff --git a/curiosrc/web/static/chain-connectivity.mjs b/curiosrc/web/static/chain-connectivity.mjs deleted file mode 100644 index bf4c80f04..000000000 --- a/curiosrc/web/static/chain-connectivity.mjs +++ /dev/null @@ -1,59 +0,0 @@ -import { LitElement, html, css } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js'; -window.customElements.define('chain-connectivity', class MyElement extends LitElement { - constructor() { - super(); - this.data = []; - this.loadData(); - } - loadData() { - const eventSource = new EventSource('/api/debug/chain-state-sse'); - eventSource.onmessage = (event) => { - this.data = JSON.parse(event.data); - super.requestUpdate(); - }; - eventSource.onerror = (error) => { - console.error('Error:', error); - loadData(); - }; - }; - - static get styles() { - return [css` - :host { - box-sizing: border-box; /* Don't forgert this to include padding/border inside width calculation */ - } - .success { - color: green; - } - .warning { - color: yellow; - } - .error { - color: red; - } - `]; - } - render = () => html` - - - - - - - - - - - - - ${this.data.map(item => html` - - - - - - - `)} - -
RPC AddressReachabilitySync StatusVersion
${item.Address}${item.Reachable ? html`ok` : html`FAIL`}${item.SyncState === "ok" ? html`ok` : html`${item.SyncState}`}${item.Version}
` -}); diff --git a/curiosrc/web/static/config/edit.html b/curiosrc/web/static/config/edit.html deleted file mode 100644 index 2802316a9..000000000 --- a/curiosrc/web/static/config/edit.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - JSON Schema Editor - - - - - - - - - -
-
-
-
- -
- -
- - -
- -
- -
-
-
- - diff --git a/curiosrc/web/static/config/index.html b/curiosrc/web/static/config/index.html deleted file mode 100644 index 769929e53..000000000 --- a/curiosrc/web/static/config/index.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - Configuration Editor - - - - - - - - -
-
-
-
-

Configuration Editor

-

Click on a layer to edit its configuration

- -
-
-
-
-
- - - \ No newline at end of file diff --git a/curiosrc/web/static/favicon.svg b/curiosrc/web/static/favicon.svg deleted file mode 100644 index 91f132959..000000000 --- a/curiosrc/web/static/favicon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/curiosrc/web/static/index.html b/curiosrc/web/static/index.html deleted file mode 100644 index 0d734d6b7..000000000 --- a/curiosrc/web/static/index.html +++ /dev/null @@ -1,201 +0,0 @@ - - - - Curio Cluster Overview - - - - - - - -
-
-
-
-

Chain Connectivity

- -
-
-
-
-
- -
-
-
-

Cluster Machines

- - - - - - - - - - - -
HostIDLast ContactTasks (24h)
-
-
-
-
-
- -
-
-
-

PoRep Pipeline

- - - - - - - - - - - - - - - - -
AddressSDRTreesPrecommit MsgWait SeedPoRepCommit MsgDoneFailed
-
-
-
-
-
- -
-
-
-

Actor Summary

- - - - - - - - - - - - - - - -
AddressConfig Layers AvailableQaPDeadlinesBalanceAvailableWorkerWins
-
-
-
-
-
- - -
-
-
-

Recently Finished Tasks

- - - - - - - - - - - - - - - - -
NameIDExecutorPostedStartQueuedTookOutcomeMessage
-
-
-
-
-
- -
-
-
-

Cluster Tasks

- - - - - - - - - - - -
TaskIDPostedOwner
-
-
-
-
-
-
-
- - - \ No newline at end of file diff --git a/curiosrc/web/static/pipeline_porep.html b/curiosrc/web/static/pipeline_porep.html deleted file mode 100644 index c609aea6f..000000000 --- a/curiosrc/web/static/pipeline_porep.html +++ /dev/null @@ -1,98 +0,0 @@ - - - Lotus Provider PoRep Pipeline - - - - - - - - - -
-
-

Lotus Provider PoRep Pipeline

-
-
-
-
-
-
-
-

Sectors

- - - - - - - - - - -
Sector IDCreate TimeState
-
-
-
-
-
- - \ No newline at end of file diff --git a/curiosrc/web/static/sector/index.html b/curiosrc/web/static/sector/index.html deleted file mode 100644 index 9ac5559cd..000000000 --- a/curiosrc/web/static/sector/index.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - Sector List - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - - - -
Loading...
-
-
-
-
- - - - \ No newline at end of file diff --git a/curiosrc/web/static/ux/curio-ux.mjs b/curiosrc/web/static/ux/curio-ux.mjs deleted file mode 100644 index 0b883d1d7..000000000 --- a/curiosrc/web/static/ux/curio-ux.mjs +++ /dev/null @@ -1,100 +0,0 @@ -import {LitElement, css, html} from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js'; - -//import 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.esm.js'; - - -class CurioUX extends LitElement { - static styles = css` -\ .curio-slot { - } - :host { - display: block; - margin: 2px 3px; - } - - `; - connectedCallback() { - super.connectedCallback(); - //"https://unpkg.com/@cds/core/global.min.css", - //"https://unpkg.com/@cds/city/css/bundles/default.min.css", - //"https://unpkg.com/@cds/core/styles/theme.dark.min.css", - //"https://unpkg.com/@clr/ui/clr-ui.min.css", - - document.head.innerHTML += ` - - - - - -` - - document.documentElement.lang = 'en'; - - // how Bootstrap & DataTables expect dark mode declared. - document.documentElement.classList.add('dark'); - - this.messsage = this.getCookieMessage(); - } - - render() { - return html` -
- - - ${this.message? html``: html``} - -
- - `; - } - - getCookieMessage() { - const name = 'message'; - const cookies = document.cookie.split(';'); - for (let i = 0; i < cookies.length; i++) { - const cookie = cookies[i].trim(); - if (cookie.startsWith(name + '=')) { - var val = cookie.substring(name.length + 1); - document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; - return val; - } - } - return null; - } - -}; - -customElements.define('curio-ux', CurioUX); \ No newline at end of file diff --git a/curiosrc/web/static/ux/main.css b/curiosrc/web/static/ux/main.css deleted file mode 100644 index c73002ff6..000000000 --- a/curiosrc/web/static/ux/main.css +++ /dev/null @@ -1,53 +0,0 @@ -@import url('https://fonts.cdnfonts.com/css/metropolis-2'); - -html { - min-height: 100vh; - background: rgb(11, 22, 34); - padding: 0; -} -body { - margin: 0; - padding: 3px 4px; - background: rgb(11, 22, 34); -} -curio-ux { - /* To resemble Clarity Design */ - color: rgb(227, 234, 237); - font-family: Metropolis, monospace; - font-weight: 400; - background: rgb(11, 22, 34); -} - - -.app-head { - width: 100%; -} -.head-left { - display: inline-block; -} -.head-right { - display: inline-block; - float: right; -} - -a { - text-decoration: none; -} - -a:link, a:visited { - color: #adf7ad; -} - -a:hover { - color: #88cc60; -} - -.success { - color: greenyellow; -} -.warning { - color: yellow; -} -.error { - color: red; -} diff --git a/curiosrc/window/compute_do.go b/curiosrc/window/compute_do.go deleted file mode 100644 index fcde14d82..000000000 --- a/curiosrc/window/compute_do.go +++ /dev/null @@ -1,442 +0,0 @@ -package window - -import ( - "bytes" - "context" - "sort" - "sync" - "time" - - "github.com/ipfs/go-cid" - "go.uber.org/multierr" - "golang.org/x/xerrors" - - ffi "github.com/filecoin-project/filecoin-ffi" - "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/builtin" - miner2 "github.com/filecoin-project/go-state-types/builtin/v9/miner" - "github.com/filecoin-project/go-state-types/crypto" - "github.com/filecoin-project/go-state-types/dline" - "github.com/filecoin-project/go-state-types/proof" - proof7 "github.com/filecoin-project/specs-actors/v7/actors/runtime/proof" - - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/storage/sealer" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -const disablePreChecks = false // todo config - -func (t *WdPostTask) DoPartition(ctx context.Context, ts *types.TipSet, maddr address.Address, di *dline.Info, partIdx uint64) (out *miner2.SubmitWindowedPoStParams, err error) { - defer func() { - if r := recover(); r != nil { - log.Errorf("recover: %s", r) - err = xerrors.Errorf("panic in doPartition: %s", r) - } - }() - - buf := new(bytes.Buffer) - if err := maddr.MarshalCBOR(buf); err != nil { - return nil, xerrors.Errorf("failed to marshal address to cbor: %w", err) - } - - headTs, err := t.api.ChainHead(ctx) - if err != nil { - return nil, xerrors.Errorf("getting current head: %w", err) - } - - rand, err := t.api.StateGetRandomnessFromBeacon(ctx, crypto.DomainSeparationTag_WindowedPoStChallengeSeed, di.Challenge, buf.Bytes(), headTs.Key()) - if err != nil { - return nil, xerrors.Errorf("failed to get chain randomness from beacon for window post (ts=%d; deadline=%d): %w", ts.Height(), di, err) - } - - parts, err := t.api.StateMinerPartitions(ctx, maddr, di.Index, ts.Key()) - if err != nil { - return nil, xerrors.Errorf("getting partitions: %w", err) - } - - if partIdx >= uint64(len(parts)) { - return nil, xerrors.Errorf("invalid partIdx %d (deadline has %d partitions)", partIdx, len(parts)) - } - - partition := parts[partIdx] - - params := miner2.SubmitWindowedPoStParams{ - Deadline: di.Index, - Partitions: make([]miner2.PoStPartition, 0, 1), - Proofs: nil, - } - - var partitions []miner2.PoStPartition - var xsinfos []proof7.ExtendedSectorInfo - - { - toProve, err := bitfield.SubtractBitField(partition.LiveSectors, partition.FaultySectors) - if err != nil { - return nil, xerrors.Errorf("removing faults from set of sectors to prove: %w", err) - } - /*if manual { - // this is a check run, we want to prove faulty sectors, even - // if they are not declared as recovering. - toProve = partition.LiveSectors - }*/ - toProve, err = bitfield.MergeBitFields(toProve, partition.RecoveringSectors) - if err != nil { - return nil, xerrors.Errorf("adding recoveries to set of sectors to prove: %w", err) - } - - good, err := toProve.Copy() - if err != nil { - return nil, xerrors.Errorf("copy toProve: %w", err) - } - if !disablePreChecks { - good, err = checkSectors(ctx, t.api, t.faultTracker, maddr, toProve, ts.Key()) - if err != nil { - return nil, xerrors.Errorf("checking sectors to skip: %w", err) - } - } - - /*good, err = bitfield.SubtractBitField(good, postSkipped) - if err != nil { - return nil, xerrors.Errorf("toProve - postSkipped: %w", err) - } - - post skipped is legacy retry mechanism, shouldn't be needed anymore - */ - - skipped, err := bitfield.SubtractBitField(toProve, good) - if err != nil { - return nil, xerrors.Errorf("toProve - good: %w", err) - } - - sc, err := skipped.Count() - if err != nil { - return nil, xerrors.Errorf("getting skipped sector count: %w", err) - } - - skipCount := sc - - ssi, err := t.sectorsForProof(ctx, maddr, good, partition.AllSectors, ts) - if err != nil { - return nil, xerrors.Errorf("getting sorted sector info: %w", err) - } - - if len(ssi) == 0 { - return nil, xerrors.Errorf("no sectors to prove") - } - - xsinfos = append(xsinfos, ssi...) - partitions = append(partitions, miner2.PoStPartition{ - Index: partIdx, - Skipped: skipped, - }) - - log.Infow("running window post", - "chain-random", rand, - "deadline", di, - "height", ts.Height(), - "skipped", skipCount) - - tsStart := build.Clock.Now() - - mid, err := address.IDFromAddress(maddr) - if err != nil { - return nil, err - } - - nv, err := t.api.StateNetworkVersion(ctx, ts.Key()) - if err != nil { - return nil, xerrors.Errorf("getting network version: %w", err) - } - - ppt, err := xsinfos[0].SealProof.RegisteredWindowPoStProofByNetworkVersion(nv) - if err != nil { - return nil, xerrors.Errorf("failed to get window post type: %w", err) - } - - postOut, ps, err := t.generateWindowPoSt(ctx, ppt, abi.ActorID(mid), xsinfos, append(abi.PoStRandomness{}, rand...)) - elapsed := time.Since(tsStart) - log.Infow("computing window post", "partition", partIdx, "elapsed", elapsed, "skip", len(ps), "err", err) - if err != nil { - log.Errorf("error generating window post: %s", err) - } - - if err == nil { - // If we proved nothing, something is very wrong. - if len(postOut) == 0 { - log.Errorf("len(postOut) == 0") - return nil, xerrors.Errorf("received no proofs back from generate window post") - } - - headTs, err := t.api.ChainHead(ctx) - if err != nil { - return nil, xerrors.Errorf("getting current head: %w", err) - } - - checkRand, err := t.api.StateGetRandomnessFromBeacon(ctx, crypto.DomainSeparationTag_WindowedPoStChallengeSeed, di.Challenge, buf.Bytes(), headTs.Key()) - if err != nil { - return nil, xerrors.Errorf("failed to get chain randomness from beacon for window post (ts=%d; deadline=%d): %w", ts.Height(), di, err) - } - - if !bytes.Equal(checkRand, rand) { - // this is a check from legacy code, there it would retry with new randomness. - // here we don't retry because the current network version uses beacon randomness - // which should never change. We do keep this check tho to detect potential issues. - return nil, xerrors.Errorf("post generation randomness was different from random beacon") - } - - sinfos := make([]proof7.SectorInfo, len(xsinfos)) - for i, xsi := range xsinfos { - sinfos[i] = proof7.SectorInfo{ - SealProof: xsi.SealProof, - SectorNumber: xsi.SectorNumber, - SealedCID: xsi.SealedCID, - } - } - if correct, err := t.verifier.VerifyWindowPoSt(ctx, proof.WindowPoStVerifyInfo{ - Randomness: abi.PoStRandomness(checkRand), - Proofs: postOut, - ChallengedSectors: sinfos, - Prover: abi.ActorID(mid), - }); err != nil { // revive:disable-line:empty-block - /*log.Errorw("window post verification failed", "post", postOut, "error", err) - time.Sleep(5 * time.Second) - continue todo retry loop */ - } else if !correct { - _ = correct - /*log.Errorw("generated incorrect window post proof", "post", postOut, "error", err) - continue todo retry loop*/ - } - - // Proof generation successful, stop retrying - //somethingToProve = true - params.Partitions = partitions - params.Proofs = postOut - //break - - return ¶ms, nil - } - } - - return nil, xerrors.Errorf("failed to generate window post") -} - -type CheckSectorsAPI interface { - StateMinerSectors(ctx context.Context, addr address.Address, bf *bitfield.BitField, tsk types.TipSetKey) ([]*miner.SectorOnChainInfo, error) -} - -func checkSectors(ctx context.Context, api CheckSectorsAPI, ft sealer.FaultTracker, - maddr address.Address, check bitfield.BitField, tsk types.TipSetKey) (bitfield.BitField, error) { - mid, err := address.IDFromAddress(maddr) - if err != nil { - return bitfield.BitField{}, xerrors.Errorf("failed to convert to ID addr: %w", err) - } - - sectorInfos, err := api.StateMinerSectors(ctx, maddr, &check, tsk) - if err != nil { - return bitfield.BitField{}, xerrors.Errorf("failed to get sector infos: %w", err) - } - - type checkSector struct { - sealed cid.Cid - update bool - } - - sectors := make(map[abi.SectorNumber]checkSector) - var tocheck []storiface.SectorRef - for _, info := range sectorInfos { - sectors[info.SectorNumber] = checkSector{ - sealed: info.SealedCID, - update: info.SectorKeyCID != nil, - } - tocheck = append(tocheck, storiface.SectorRef{ - ProofType: info.SealProof, - ID: abi.SectorID{ - Miner: abi.ActorID(mid), - Number: info.SectorNumber, - }, - }) - } - - if len(tocheck) == 0 { - return bitfield.BitField{}, nil - } - - pp, err := tocheck[0].ProofType.RegisteredWindowPoStProof() - if err != nil { - return bitfield.BitField{}, xerrors.Errorf("failed to get window PoSt proof: %w", err) - } - pp, err = pp.ToV1_1PostProof() - if err != nil { - return bitfield.BitField{}, xerrors.Errorf("failed to convert to v1_1 post proof: %w", err) - } - - bad, err := ft.CheckProvable(ctx, pp, tocheck, func(ctx context.Context, id abi.SectorID) (cid.Cid, bool, error) { - s, ok := sectors[id.Number] - if !ok { - return cid.Undef, false, xerrors.Errorf("sealed CID not found") - } - return s.sealed, s.update, nil - }) - if err != nil { - return bitfield.BitField{}, xerrors.Errorf("checking provable sectors: %w", err) - } - for id := range bad { - delete(sectors, id.Number) - } - - log.Warnw("Checked sectors", "checked", len(tocheck), "good", len(sectors)) - - sbf := bitfield.New() - for s := range sectors { - sbf.Set(uint64(s)) - } - - return sbf, nil -} - -func (t *WdPostTask) sectorsForProof(ctx context.Context, maddr address.Address, goodSectors, allSectors bitfield.BitField, ts *types.TipSet) ([]proof7.ExtendedSectorInfo, error) { - sset, err := t.api.StateMinerSectors(ctx, maddr, &goodSectors, ts.Key()) - if err != nil { - return nil, err - } - - if len(sset) == 0 { - return nil, nil - } - - sectorByID := make(map[uint64]proof7.ExtendedSectorInfo, len(sset)) - for _, sector := range sset { - sectorByID[uint64(sector.SectorNumber)] = proof7.ExtendedSectorInfo{ - SectorNumber: sector.SectorNumber, - SealedCID: sector.SealedCID, - SealProof: sector.SealProof, - SectorKey: sector.SectorKeyCID, - } - } - - proofSectors := make([]proof7.ExtendedSectorInfo, 0, len(sset)) - if err := allSectors.ForEach(func(sectorNo uint64) error { - if info, found := sectorByID[sectorNo]; found { - proofSectors = append(proofSectors, info) - } //else { - //skip - // todo: testing: old logic used to put 'substitute' sectors here - // that probably isn't needed post nv19, but we do need to check that - //} - return nil - }); err != nil { - return nil, xerrors.Errorf("iterating partition sector bitmap: %w", err) - } - - return proofSectors, nil -} - -func (t *WdPostTask) generateWindowPoSt(ctx context.Context, ppt abi.RegisteredPoStProof, minerID abi.ActorID, sectorInfo []proof.ExtendedSectorInfo, randomness abi.PoStRandomness) ([]proof.PoStProof, []abi.SectorID, error) { - var retErr error - randomness[31] &= 0x3f - - out := make([]proof.PoStProof, 0) - - if len(sectorInfo) == 0 { - return nil, nil, xerrors.New("generate window post len(sectorInfo)=0") - } - - maxPartitionSize, err := builtin.PoStProofWindowPoStPartitionSectors(ppt) // todo proxy through chain/actors - if err != nil { - return nil, nil, xerrors.Errorf("get sectors count of partition failed:%+v", err) - } - - // The partitions number of this batch - // ceil(sectorInfos / maxPartitionSize) - partitionCount := uint64((len(sectorInfo) + int(maxPartitionSize) - 1) / int(maxPartitionSize)) - if partitionCount > 1 { - return nil, nil, xerrors.Errorf("generateWindowPoSt partitionCount:%d, only support 1", partitionCount) - } - - log.Infof("generateWindowPoSt maxPartitionSize:%d partitionCount:%d", maxPartitionSize, partitionCount) - - var skipped []abi.SectorID - var flk sync.Mutex - cctx, cancel := context.WithCancel(ctx) - defer cancel() - - sort.Slice(sectorInfo, func(i, j int) bool { - return sectorInfo[i].SectorNumber < sectorInfo[j].SectorNumber - }) - - sectorNums := make([]abi.SectorNumber, len(sectorInfo)) - sectorMap := make(map[abi.SectorNumber]proof.ExtendedSectorInfo) - for i, s := range sectorInfo { - sectorNums[i] = s.SectorNumber - sectorMap[s.SectorNumber] = s - } - - postChallenges, err := ffi.GeneratePoStFallbackSectorChallenges(ppt, minerID, randomness, sectorNums) - if err != nil { - return nil, nil, xerrors.Errorf("generating fallback challenges: %v", err) - } - - proofList := make([]ffi.PartitionProof, partitionCount) - var wg sync.WaitGroup - wg.Add(int(partitionCount)) - - for partIdx := uint64(0); partIdx < partitionCount; partIdx++ { - go func(partIdx uint64) { - defer wg.Done() - - sectors := make([]storiface.PostSectorChallenge, 0) - for i := uint64(0); i < maxPartitionSize; i++ { - si := i + partIdx*maxPartitionSize - if si >= uint64(len(postChallenges.Sectors)) { - break - } - - snum := postChallenges.Sectors[si] - sinfo := sectorMap[snum] - - sectors = append(sectors, storiface.PostSectorChallenge{ - SealProof: sinfo.SealProof, - SectorNumber: snum, - SealedCID: sinfo.SealedCID, - Challenge: postChallenges.Challenges[snum], - Update: sinfo.SectorKey != nil, - }) - } - - pr, err := t.prover.GenerateWindowPoStAdv(cctx, ppt, minerID, sectors, int(partIdx), randomness, true) - sk := pr.Skipped - - if err != nil || len(sk) > 0 { - log.Errorf("generateWindowPost part:%d, skipped:%d, sectors: %d, err: %+v", partIdx, len(sk), len(sectors), err) - flk.Lock() - skipped = append(skipped, sk...) - - if err != nil { - retErr = multierr.Append(retErr, xerrors.Errorf("partitionIndex:%d err:%+v", partIdx, err)) - } - flk.Unlock() - } - - proofList[partIdx] = ffi.PartitionProof(pr.PoStProofs) - }(partIdx) - } - - wg.Wait() - - if len(skipped) > 0 { - log.Warnw("generateWindowPoSt skipped sectors", "skipped", len(skipped)) - } - - postProofs, err := ffi.MergeWindowPoStPartitionProofs(ppt, proofList) - if err != nil { - return nil, skipped, xerrors.Errorf("merge windowPoSt partition proofs: %v", err) - } - - out = append(out, *postProofs) - return out, skipped, retErr -} diff --git a/curiosrc/window/compute_task.go b/curiosrc/window/compute_task.go deleted file mode 100644 index 541a2d5e2..000000000 --- a/curiosrc/window/compute_task.go +++ /dev/null @@ -1,443 +0,0 @@ -package window - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "sort" - "strings" - - logging "github.com/ipfs/go-log/v2" - "github.com/samber/lo" - "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/dline" - "github.com/filecoin-project/go-state-types/network" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/curiosrc/chainsched" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/lib/harmony/taskhelp" - "github.com/filecoin-project/lotus/lib/promise" - "github.com/filecoin-project/lotus/node/modules/dtypes" - "github.com/filecoin-project/lotus/storage/sealer" - "github.com/filecoin-project/lotus/storage/sealer/sealtasks" - "github.com/filecoin-project/lotus/storage/sealer/storiface" - "github.com/filecoin-project/lotus/storage/wdpost" -) - -var log = logging.Logger("curio/window") - -var EpochsPerDeadline = miner.WPoStProvingPeriod() / abi.ChainEpoch(miner.WPoStPeriodDeadlines) - -type WdPostTaskDetails struct { - Ts *types.TipSet - Deadline *dline.Info -} - -type WDPoStAPI interface { - ChainHead(context.Context) (*types.TipSet, error) - ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error) - StateMinerProvingDeadline(context.Context, address.Address, types.TipSetKey) (*dline.Info, error) - StateMinerInfo(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error) - ChainGetTipSetAfterHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) - StateMinerPartitions(context.Context, address.Address, uint64, types.TipSetKey) ([]api.Partition, error) - StateGetRandomnessFromBeacon(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte, tsk types.TipSetKey) (abi.Randomness, error) - StateNetworkVersion(context.Context, types.TipSetKey) (network.Version, error) - StateMinerSectors(context.Context, address.Address, *bitfield.BitField, types.TipSetKey) ([]*miner.SectorOnChainInfo, error) -} - -type ProverPoSt interface { - GenerateWindowPoStAdv(ctx context.Context, ppt abi.RegisteredPoStProof, mid abi.ActorID, sectors []storiface.PostSectorChallenge, partitionIdx int, randomness abi.PoStRandomness, allowSkip bool) (storiface.WindowPoStResult, error) -} - -type WdPostTask struct { - api WDPoStAPI - db *harmonydb.DB - - faultTracker sealer.FaultTracker - prover ProverPoSt - verifier storiface.Verifier - - windowPoStTF promise.Promise[harmonytask.AddTaskFunc] - - actors map[dtypes.MinerAddress]bool - max int -} - -type wdTaskIdentity struct { - SpID uint64 `db:"sp_id"` - ProvingPeriodStart abi.ChainEpoch `db:"proving_period_start"` - DeadlineIndex uint64 `db:"deadline_index"` - PartitionIndex uint64 `db:"partition_index"` -} - -func NewWdPostTask(db *harmonydb.DB, - api WDPoStAPI, - faultTracker sealer.FaultTracker, - prover ProverPoSt, - verifier storiface.Verifier, - pcs *chainsched.CurioChainSched, - actors map[dtypes.MinerAddress]bool, - max int, -) (*WdPostTask, error) { - t := &WdPostTask{ - db: db, - api: api, - - faultTracker: faultTracker, - prover: prover, - verifier: verifier, - - actors: actors, - max: max, - } - - if pcs != nil { - if err := pcs.AddHandler(t.processHeadChange); err != nil { - return nil, err - } - } - - return t, nil -} - -func (t *WdPostTask) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - log.Debugw("WdPostTask.Do()", "taskID", taskID) - - var spID, pps, dlIdx, partIdx uint64 - - err = t.db.QueryRow(context.Background(), - `Select sp_id, proving_period_start, deadline_index, partition_index - from wdpost_partition_tasks - where task_id = $1`, taskID).Scan( - &spID, &pps, &dlIdx, &partIdx, - ) - if err != nil { - log.Errorf("WdPostTask.Do() failed to queryRow: %v", err) - return false, err - } - - head, err := t.api.ChainHead(context.Background()) - if err != nil { - log.Errorf("WdPostTask.Do() failed to get chain head: %v", err) - return false, err - } - - deadline := wdpost.NewDeadlineInfo(abi.ChainEpoch(pps), dlIdx, head.Height()) - - var testTask *int - isTestTask := func() bool { - if testTask != nil { - return *testTask > 0 - } - - testTask = new(int) - err := t.db.QueryRow(context.Background(), `SELECT COUNT(*) FROM harmony_test WHERE task_id = $1`, taskID).Scan(testTask) - if err != nil { - log.Errorf("WdPostTask.Do() failed to queryRow: %v", err) - return false - } - - return *testTask > 0 - } - - if deadline.PeriodElapsed() && !isTestTask() { - log.Errorf("WdPost removed stale task: %v %v", taskID, deadline) - return true, nil - } - - if deadline.Challenge > head.Height() { - if isTestTask() { - deadline = wdpost.NewDeadlineInfo(abi.ChainEpoch(pps)-deadline.WPoStProvingPeriod, dlIdx, head.Height()-deadline.WPoStProvingPeriod) - log.Warnw("Test task is in the future, adjusting to past", "taskID", taskID, "deadline", deadline) - } - } - - maddr, err := address.NewIDAddress(spID) - if err != nil { - log.Errorf("WdPostTask.Do() failed to NewIDAddress: %v", err) - return false, err - } - - ts, err := t.api.ChainGetTipSetAfterHeight(context.Background(), deadline.Challenge, head.Key()) - if err != nil { - log.Errorf("WdPostTask.Do() failed to ChainGetTipSetAfterHeight: %v", err) - return false, err - } - - postOut, err := t.DoPartition(context.Background(), ts, maddr, deadline, partIdx) - if err != nil { - log.Errorf("WdPostTask.Do() failed to doPartition: %v", err) - return false, err - } - - var msgbuf bytes.Buffer - if err := postOut.MarshalCBOR(&msgbuf); err != nil { - return false, xerrors.Errorf("marshaling PoSt: %w", err) - } - - if isTestTask() { - // Do not send test tasks to the chain but to harmony_test & stdout. - - data, err := json.MarshalIndent(map[string]any{ - "sp_id": spID, - "proving_period_start": pps, - "deadline": deadline.Index, - "partition": partIdx, - "submit_at_epoch": deadline.Open, - "submit_by_epoch": deadline.Close, - "proof_params": msgbuf.Bytes(), - }, "", " ") - if err != nil { - return false, xerrors.Errorf("marshaling message: %w", err) - } - ctx := context.Background() - _, err = t.db.Exec(ctx, `UPDATE harmony_test SET result=$1 WHERE task_id=$2`, string(data), taskID) - if err != nil { - return false, xerrors.Errorf("updating harmony_test: %w", err) - } - log.Infof("SKIPPED sending test message to chain. SELECT * FROM harmony_test WHERE task_id= %v", taskID) - return true, nil // nothing committed - } - // Insert into wdpost_proofs table - n, err := t.db.Exec(context.Background(), - `INSERT INTO wdpost_proofs ( - sp_id, - proving_period_start, - deadline, - partition, - submit_at_epoch, - submit_by_epoch, - proof_params) - VALUES ($1, $2, $3, $4, $5, $6, $7)`, - spID, - pps, - deadline.Index, - partIdx, - deadline.Open, - deadline.Close, - msgbuf.Bytes(), - ) - - if err != nil { - log.Errorf("WdPostTask.Do() failed to insert into wdpost_proofs: %v", err) - return false, err - } - if n != 1 { - log.Errorf("WdPostTask.Do() failed to insert into wdpost_proofs: %v", err) - return false, err - } - - return true, nil -} - -func entToStr[T any](t T, i int) string { - return fmt.Sprint(t) -} - -func (t *WdPostTask) CanAccept(ids []harmonytask.TaskID, te *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - // GetEpoch - ts, err := t.api.ChainHead(context.Background()) - - if err != nil { - return nil, err - } - - // GetData for tasks - type wdTaskDef struct { - TaskID harmonytask.TaskID - SpID uint64 - ProvingPeriodStart abi.ChainEpoch - DeadlineIndex uint64 - PartitionIndex uint64 - - dlInfo *dline.Info `pgx:"-"` - } - var tasks []wdTaskDef - - err = t.db.Select(context.Background(), &tasks, - `Select - task_id, - sp_id, - proving_period_start, - deadline_index, - partition_index - from wdpost_partition_tasks - where task_id IN (SELECT unnest(string_to_array($1, ','))::bigint)`, strings.Join(lo.Map(ids, entToStr[harmonytask.TaskID]), ",")) - if err != nil { - return nil, err - } - - // Accept those past deadline, then delete them in Do(). - for i := range tasks { - tasks[i].dlInfo = wdpost.NewDeadlineInfo(tasks[i].ProvingPeriodStart, tasks[i].DeadlineIndex, ts.Height()) - - if tasks[i].dlInfo.PeriodElapsed() { - // note: Those may be test tasks - return &tasks[i].TaskID, nil - } - } - - // todo fix the block below - // workAdderMutex is held by taskTypeHandler.considerWork, which calls this CanAccept - // te.ResourcesAvailable will try to get that lock again, which will deadlock - - // Discard those too big for our free RAM - /*freeRAM := te.ResourcesAvailable().Ram - tasks = lo.Filter(tasks, func(d wdTaskDef, _ int) bool { - maddr, err := address.NewIDAddress(tasks[0].Sp_id) - if err != nil { - log.Errorf("WdPostTask.CanAccept() failed to NewIDAddress: %v", err) - return false - } - - mi, err := t.api.StateMinerInfo(context.Background(), maddr, ts.Key()) - if err != nil { - log.Errorf("WdPostTask.CanAccept() failed to StateMinerInfo: %v", err) - return false - } - - spt, err := policy.GetSealProofFromPoStProof(mi.WindowPoStProofType) - if err != nil { - log.Errorf("WdPostTask.CanAccept() failed to GetSealProofFromPoStProof: %v", err) - return false - } - - return res[spt].MaxMemory <= freeRAM - })*/ - if len(tasks) == 0 { - log.Infof("RAM too small for any WDPost task") - return nil, nil - } - - // Ignore those with too many failures unless they are the only ones left. - tasks, _ = taskhelp.SliceIfFound(tasks, func(d wdTaskDef) bool { - var r int - err := t.db.QueryRow(context.Background(), `SELECT COUNT(*) - FROM harmony_task_history - WHERE task_id = $1 AND result = false`, d.TaskID).Scan(&r) - if err != nil { - log.Errorf("WdPostTask.CanAccept() failed to queryRow: %v", err) - } - return r < 2 - }) - - // Select the one closest to the deadline - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].dlInfo.Open < tasks[j].dlInfo.Open - }) - - return &tasks[0].TaskID, nil -} - -var res = storiface.ResourceTable[sealtasks.TTGenerateWindowPoSt] - -func (t *WdPostTask) TypeDetails() harmonytask.TaskTypeDetails { - return harmonytask.TaskTypeDetails{ - Name: "WdPost", - Max: t.max, - MaxFailures: 3, - Follows: nil, - Cost: resources.Resources{ - Cpu: 1, - - // todo set to something for 32/64G sector sizes? Technically windowPoSt is happy on a CPU - // but it will use a GPU if available - Gpu: 0, - - // RAM of smallest proof's max is listed here - Ram: lo.Reduce(lo.Keys(res), func(i uint64, k abi.RegisteredSealProof, _ int) uint64 { - if res[k].MaxMemory < i { - return res[k].MaxMemory - } - return i - }, 1<<63), - }, - } -} - -func (t *WdPostTask) Adder(taskFunc harmonytask.AddTaskFunc) { - t.windowPoStTF.Set(taskFunc) -} - -func (t *WdPostTask) processHeadChange(ctx context.Context, revert, apply *types.TipSet) error { - for act := range t.actors { - maddr := address.Address(act) - - aid, err := address.IDFromAddress(maddr) - if err != nil { - return xerrors.Errorf("getting miner ID: %w", err) - } - - di, err := t.api.StateMinerProvingDeadline(ctx, maddr, apply.Key()) - if err != nil { - return err - } - - if !di.PeriodStarted() { - return nil // not proving anything yet - } - - partitions, err := t.api.StateMinerPartitions(ctx, maddr, di.Index, apply.Key()) - if err != nil { - return xerrors.Errorf("getting partitions: %w", err) - } - - // TODO: Batch Partitions?? - - for pidx := range partitions { - tid := wdTaskIdentity{ - SpID: aid, - ProvingPeriodStart: di.PeriodStart, - DeadlineIndex: di.Index, - PartitionIndex: uint64(pidx), - } - - tf := t.windowPoStTF.Val(ctx) - if tf == nil { - return xerrors.Errorf("no task func") - } - - tf(func(id harmonytask.TaskID, tx *harmonydb.Tx) (bool, error) { - return t.addTaskToDB(id, tid, tx) - }) - } - } - - return nil -} - -func (t *WdPostTask) addTaskToDB(taskId harmonytask.TaskID, taskIdent wdTaskIdentity, tx *harmonydb.Tx) (bool, error) { - - _, err := tx.Exec( - `INSERT INTO wdpost_partition_tasks ( - task_id, - sp_id, - proving_period_start, - deadline_index, - partition_index - ) VALUES ($1, $2, $3, $4, $5)`, - taskId, - taskIdent.SpID, - taskIdent.ProvingPeriodStart, - taskIdent.DeadlineIndex, - taskIdent.PartitionIndex, - ) - if err != nil { - return false, xerrors.Errorf("insert partition task: %w", err) - } - - return true, nil -} - -var _ harmonytask.TaskInterface = &WdPostTask{} diff --git a/curiosrc/window/faults_simple.go b/curiosrc/window/faults_simple.go deleted file mode 100644 index 64f5e8650..000000000 --- a/curiosrc/window/faults_simple.go +++ /dev/null @@ -1,152 +0,0 @@ -package window - -import ( - "context" - "crypto/rand" - "fmt" - "sync" - "time" - - "golang.org/x/xerrors" - - ffi "github.com/filecoin-project/filecoin-ffi" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/storage/paths" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -type SimpleFaultTracker struct { - storage paths.Store - index paths.SectorIndex - - parallelCheckLimit int // todo live config? - singleCheckTimeout time.Duration - partitionCheckTimeout time.Duration -} - -func NewSimpleFaultTracker(storage paths.Store, index paths.SectorIndex, - parallelCheckLimit int, singleCheckTimeout time.Duration, partitionCheckTimeout time.Duration) *SimpleFaultTracker { - return &SimpleFaultTracker{ - storage: storage, - index: index, - - parallelCheckLimit: parallelCheckLimit, - singleCheckTimeout: singleCheckTimeout, - partitionCheckTimeout: partitionCheckTimeout, - } -} - -func (m *SimpleFaultTracker) CheckProvable(ctx context.Context, pp abi.RegisteredPoStProof, sectors []storiface.SectorRef, rg storiface.RGetter) (map[abi.SectorID]string, error) { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - if rg == nil { - return nil, xerrors.Errorf("rg is nil") - } - - var bad = make(map[abi.SectorID]string) - var badLk sync.Mutex - - var postRand abi.PoStRandomness = make([]byte, abi.RandomnessLength) - _, _ = rand.Read(postRand) - postRand[31] &= 0x3f - - limit := m.parallelCheckLimit - if limit <= 0 { - limit = len(sectors) - } - throttle := make(chan struct{}, limit) - - addBad := func(s abi.SectorID, reason string) { - badLk.Lock() - bad[s] = reason - badLk.Unlock() - } - - if m.partitionCheckTimeout > 0 { - var cancel2 context.CancelFunc - ctx, cancel2 = context.WithTimeout(ctx, m.partitionCheckTimeout) - defer cancel2() - } - - var wg sync.WaitGroup - wg.Add(len(sectors)) - - for _, sector := range sectors { - select { - case throttle <- struct{}{}: - case <-ctx.Done(): - addBad(sector.ID, fmt.Sprintf("waiting for check worker: %s", ctx.Err())) - wg.Done() - continue - } - - go func(sector storiface.SectorRef) { - defer wg.Done() - defer func() { - <-throttle - }() - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - commr, update, err := rg(ctx, sector.ID) - if err != nil { - log.Warnw("CheckProvable Sector FAULT: getting commR", "sector", sector, "sealed", "err", err) - addBad(sector.ID, fmt.Sprintf("getting commR: %s", err)) - return - } - - toLock := storiface.FTSealed | storiface.FTCache - if update { - toLock = storiface.FTUpdate | storiface.FTUpdateCache - } - - locked, err := m.index.StorageTryLock(ctx, sector.ID, toLock, storiface.FTNone) - if err != nil { - addBad(sector.ID, fmt.Sprintf("tryLock error: %s", err)) - return - } - - if !locked { - log.Warnw("CheckProvable Sector FAULT: can't acquire read lock", "sector", sector) - addBad(sector.ID, "can't acquire read lock") - return - } - - ch, err := ffi.GeneratePoStFallbackSectorChallenges(pp, sector.ID.Miner, postRand, []abi.SectorNumber{ - sector.ID.Number, - }) - if err != nil { - log.Warnw("CheckProvable Sector FAULT: generating challenges", "sector", sector, "err", err) - addBad(sector.ID, fmt.Sprintf("generating fallback challenges: %s", err)) - return - } - - vctx := ctx - - if m.singleCheckTimeout > 0 { - var cancel2 context.CancelFunc - vctx, cancel2 = context.WithTimeout(ctx, m.singleCheckTimeout) - defer cancel2() - } - - _, err = m.storage.GenerateSingleVanillaProof(vctx, sector.ID.Miner, storiface.PostSectorChallenge{ - SealProof: sector.ProofType, - SectorNumber: sector.ID.Number, - SealedCID: commr, - Challenge: ch.Challenges[sector.ID.Number], - Update: update, - }, pp) - if err != nil { - log.Warnw("CheckProvable Sector FAULT: generating vanilla proof", "sector", sector, "err", err) - addBad(sector.ID, fmt.Sprintf("generating vanilla proof: %s", err)) - return - } - }(sector) - } - - wg.Wait() - - return bad, nil -} diff --git a/curiosrc/window/recover_task.go b/curiosrc/window/recover_task.go deleted file mode 100644 index 1ed110978..000000000 --- a/curiosrc/window/recover_task.go +++ /dev/null @@ -1,324 +0,0 @@ -package window - -import ( - "context" - - "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/builtin" - "github.com/filecoin-project/go-state-types/dline" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/curiosrc/chainsched" - "github.com/filecoin-project/lotus/curiosrc/message" - "github.com/filecoin-project/lotus/curiosrc/multictladdr" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/lib/promise" - "github.com/filecoin-project/lotus/node/modules/dtypes" - "github.com/filecoin-project/lotus/storage/sealer" - "github.com/filecoin-project/lotus/storage/wdpost" -) - -type WdPostRecoverDeclareTask struct { - sender *message.Sender - db *harmonydb.DB - api WdPostRecoverDeclareTaskApi - faultTracker sealer.FaultTracker - - maxDeclareRecoveriesGasFee types.FIL - as *multictladdr.MultiAddressSelector - actors map[dtypes.MinerAddress]bool - - startCheckTF promise.Promise[harmonytask.AddTaskFunc] -} - -type WdPostRecoverDeclareTaskApi interface { - ChainHead(context.Context) (*types.TipSet, error) - StateMinerProvingDeadline(context.Context, address.Address, types.TipSetKey) (*dline.Info, error) - StateMinerPartitions(context.Context, address.Address, uint64, types.TipSetKey) ([]api.Partition, error) - StateMinerInfo(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error) - StateMinerSectors(ctx context.Context, addr address.Address, bf *bitfield.BitField, tsk types.TipSetKey) ([]*miner.SectorOnChainInfo, error) - - GasEstimateMessageGas(context.Context, *types.Message, *api.MessageSendSpec, types.TipSetKey) (*types.Message, error) - GasEstimateFeeCap(context.Context, *types.Message, int64, types.TipSetKey) (types.BigInt, error) - GasEstimateGasPremium(_ context.Context, nblocksincl uint64, sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error) - - WalletBalance(context.Context, address.Address) (types.BigInt, error) - WalletHas(context.Context, address.Address) (bool, error) - StateAccountKey(context.Context, address.Address, types.TipSetKey) (address.Address, error) - StateLookupID(context.Context, address.Address, types.TipSetKey) (address.Address, error) -} - -func NewWdPostRecoverDeclareTask(sender *message.Sender, - db *harmonydb.DB, - api WdPostRecoverDeclareTaskApi, - faultTracker sealer.FaultTracker, - as *multictladdr.MultiAddressSelector, - pcs *chainsched.CurioChainSched, - - maxDeclareRecoveriesGasFee types.FIL, - actors map[dtypes.MinerAddress]bool) (*WdPostRecoverDeclareTask, error) { - t := &WdPostRecoverDeclareTask{ - sender: sender, - db: db, - api: api, - faultTracker: faultTracker, - - maxDeclareRecoveriesGasFee: maxDeclareRecoveriesGasFee, - as: as, - actors: actors, - } - - if pcs != nil { - if err := pcs.AddHandler(t.processHeadChange); err != nil { - return nil, err - } - } - - return t, nil -} - -func (w *WdPostRecoverDeclareTask) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - log.Debugw("WdPostRecoverDeclareTask.Do()", "taskID", taskID) - ctx := context.Background() - - var spID, pps, dlIdx, partIdx uint64 - - err = w.db.QueryRow(context.Background(), - `Select sp_id, proving_period_start, deadline_index, partition_index - from wdpost_recovery_tasks - where task_id = $1`, taskID).Scan( - &spID, &pps, &dlIdx, &partIdx, - ) - if err != nil { - log.Errorf("WdPostRecoverDeclareTask.Do() failed to queryRow: %v", err) - return false, err - } - - head, err := w.api.ChainHead(context.Background()) - if err != nil { - log.Errorf("WdPostRecoverDeclareTask.Do() failed to get chain head: %v", err) - return false, err - } - - deadline := wdpost.NewDeadlineInfo(abi.ChainEpoch(pps), dlIdx, head.Height()) - - if deadline.FaultCutoffPassed() { - log.Errorf("WdPostRecover removed stale task: %v %v", taskID, deadline) - return true, nil - } - - maddr, err := address.NewIDAddress(spID) - if err != nil { - log.Errorf("WdPostTask.Do() failed to NewIDAddress: %v", err) - return false, err - } - - partitions, err := w.api.StateMinerPartitions(context.Background(), maddr, dlIdx, head.Key()) - if err != nil { - log.Errorf("WdPostRecoverDeclareTask.Do() failed to get partitions: %v", err) - return false, err - } - - if partIdx >= uint64(len(partitions)) { - log.Errorf("WdPostRecoverDeclareTask.Do() failed to get partitions: partIdx >= len(partitions)") - return false, err - } - - partition := partitions[partIdx] - - unrecovered, err := bitfield.SubtractBitField(partition.FaultySectors, partition.RecoveringSectors) - if err != nil { - return false, xerrors.Errorf("subtracting recovered set from fault set: %w", err) - } - - uc, err := unrecovered.Count() - if err != nil { - return false, xerrors.Errorf("counting unrecovered sectors: %w", err) - } - - if uc == 0 { - log.Warnw("nothing to declare recovered", "maddr", maddr, "deadline", deadline, "partition", partIdx) - return true, nil - } - - recovered, err := checkSectors(ctx, w.api, w.faultTracker, maddr, unrecovered, head.Key()) - if err != nil { - return false, xerrors.Errorf("checking unrecovered sectors: %w", err) - } - - // if all sectors failed to recover, don't declare recoveries - recoveredCount, err := recovered.Count() - if err != nil { - return false, xerrors.Errorf("counting recovered sectors: %w", err) - } - - if recoveredCount == 0 { - log.Warnw("no sectors recovered", "maddr", maddr, "deadline", deadline, "partition", partIdx) - return true, nil - } - - recDecl := miner.RecoveryDeclaration{ - Deadline: dlIdx, - Partition: partIdx, - Sectors: recovered, - } - - params := &miner.DeclareFaultsRecoveredParams{ - Recoveries: []miner.RecoveryDeclaration{recDecl}, - } - - enc, aerr := actors.SerializeParams(params) - if aerr != nil { - return false, xerrors.Errorf("could not serialize declare recoveries parameters: %w", aerr) - } - - msg := &types.Message{ - To: maddr, - Method: builtin.MethodsMiner.DeclareFaultsRecovered, - Params: enc, - Value: types.NewInt(0), - } - - msg, mss, err := preparePoStMessage(w.api, w.as, maddr, msg, abi.TokenAmount(w.maxDeclareRecoveriesGasFee)) - if err != nil { - return false, xerrors.Errorf("sending declare recoveries message: %w", err) - } - - mc, err := w.sender.Send(ctx, msg, mss, "declare-recoveries") - if err != nil { - return false, xerrors.Errorf("sending declare recoveries message: %w", err) - } - - log.Debugw("WdPostRecoverDeclareTask.Do() sent declare recoveries message", "maddr", maddr, "deadline", deadline, "partition", partIdx, "mc", mc) - return true, nil -} - -func (w *WdPostRecoverDeclareTask) CanAccept(ids []harmonytask.TaskID, engine *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - if len(ids) == 0 { - // probably can't happen, but panicking is bad - return nil, nil - } - - if w.sender == nil { - // we can't send messages - return nil, nil - } - - return &ids[0], nil -} - -func (w *WdPostRecoverDeclareTask) TypeDetails() harmonytask.TaskTypeDetails { - return harmonytask.TaskTypeDetails{ - Max: 128, - Name: "WdPostRecover", - Cost: resources.Resources{ - Cpu: 1, - Gpu: 0, - Ram: 128 << 20, - }, - MaxFailures: 10, - Follows: nil, - } -} - -func (w *WdPostRecoverDeclareTask) Adder(taskFunc harmonytask.AddTaskFunc) { - w.startCheckTF.Set(taskFunc) -} - -func (w *WdPostRecoverDeclareTask) processHeadChange(ctx context.Context, revert, apply *types.TipSet) error { - tf := w.startCheckTF.Val(ctx) - - for act := range w.actors { - maddr := address.Address(act) - - aid, err := address.IDFromAddress(maddr) - if err != nil { - return xerrors.Errorf("getting miner ID: %w", err) - } - - di, err := w.api.StateMinerProvingDeadline(ctx, maddr, apply.Key()) - if err != nil { - return err - } - - if !di.PeriodStarted() { - return nil // not proving anything yet - } - - // declaring two deadlines ahead - declDeadline := (di.Index + 2) % di.WPoStPeriodDeadlines - - pps := di.PeriodStart - if declDeadline != di.Index+2 { - pps = di.NextPeriodStart() - } - - partitions, err := w.api.StateMinerPartitions(ctx, maddr, declDeadline, apply.Key()) - if err != nil { - return xerrors.Errorf("getting partitions: %w", err) - } - - for pidx, partition := range partitions { - unrecovered, err := bitfield.SubtractBitField(partition.FaultySectors, partition.RecoveringSectors) - if err != nil { - return xerrors.Errorf("subtracting recovered set from fault set: %w", err) - } - - uc, err := unrecovered.Count() - if err != nil { - return xerrors.Errorf("counting unrecovered sectors: %w", err) - } - - if uc == 0 { - log.Debugw("WdPostRecoverDeclareTask.processHeadChange() uc == 0, skipping", "maddr", maddr, "declDeadline", declDeadline, "pidx", pidx) - continue - } - - tid := wdTaskIdentity{ - SpID: aid, - ProvingPeriodStart: pps, - DeadlineIndex: declDeadline, - PartitionIndex: uint64(pidx), - } - - tf(func(id harmonytask.TaskID, tx *harmonydb.Tx) (bool, error) { - return w.addTaskToDB(id, tid, tx) - }) - } - } - - return nil -} - -func (w *WdPostRecoverDeclareTask) addTaskToDB(taskId harmonytask.TaskID, taskIdent wdTaskIdentity, tx *harmonydb.Tx) (bool, error) { - _, err := tx.Exec( - `INSERT INTO wdpost_recovery_tasks ( - task_id, - sp_id, - proving_period_start, - deadline_index, - partition_index - ) VALUES ($1, $2, $3, $4, $5)`, - taskId, - taskIdent.SpID, - taskIdent.ProvingPeriodStart, - taskIdent.DeadlineIndex, - taskIdent.PartitionIndex, - ) - if err != nil { - return false, xerrors.Errorf("insert partition task: %w", err) - } - - return true, nil -} - -var _ harmonytask.TaskInterface = &WdPostRecoverDeclareTask{} diff --git a/curiosrc/window/submit_task.go b/curiosrc/window/submit_task.go deleted file mode 100644 index 330fd0509..000000000 --- a/curiosrc/window/submit_task.go +++ /dev/null @@ -1,307 +0,0 @@ -package window - -import ( - "bytes" - "context" - - "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/builtin" - "github.com/filecoin-project/go-state-types/builtin/v9/miner" - "github.com/filecoin-project/go-state-types/crypto" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/curiosrc/chainsched" - "github.com/filecoin-project/lotus/curiosrc/message" - "github.com/filecoin-project/lotus/curiosrc/multictladdr" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/lib/promise" - "github.com/filecoin-project/lotus/storage/wdpost" -) - -type WdPoStSubmitTaskApi interface { - ChainHead(context.Context) (*types.TipSet, error) - - WalletBalance(context.Context, address.Address) (types.BigInt, error) - WalletHas(context.Context, address.Address) (bool, error) - - StateAccountKey(context.Context, address.Address, types.TipSetKey) (address.Address, error) - StateLookupID(context.Context, address.Address, types.TipSetKey) (address.Address, error) - StateMinerInfo(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error) - StateGetRandomnessFromTickets(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte, tsk types.TipSetKey) (abi.Randomness, error) - - GasEstimateMessageGas(context.Context, *types.Message, *api.MessageSendSpec, types.TipSetKey) (*types.Message, error) - GasEstimateFeeCap(context.Context, *types.Message, int64, types.TipSetKey) (types.BigInt, error) - GasEstimateGasPremium(_ context.Context, nblocksincl uint64, sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error) -} - -type WdPostSubmitTask struct { - sender *message.Sender - db *harmonydb.DB - api WdPoStSubmitTaskApi - - maxWindowPoStGasFee types.FIL - as *multictladdr.MultiAddressSelector - - submitPoStTF promise.Promise[harmonytask.AddTaskFunc] -} - -func NewWdPostSubmitTask(pcs *chainsched.CurioChainSched, send *message.Sender, db *harmonydb.DB, api WdPoStSubmitTaskApi, maxWindowPoStGasFee types.FIL, as *multictladdr.MultiAddressSelector) (*WdPostSubmitTask, error) { - res := &WdPostSubmitTask{ - sender: send, - db: db, - api: api, - - maxWindowPoStGasFee: maxWindowPoStGasFee, - as: as, - } - - if pcs != nil { - if err := pcs.AddHandler(res.processHeadChange); err != nil { - return nil, err - } - } - - return res, nil -} - -func (w *WdPostSubmitTask) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - log.Debugw("WdPostSubmitTask.Do", "taskID", taskID) - - var spID uint64 - var deadline uint64 - var partition uint64 - var pps, submitAtEpoch, submitByEpoch abi.ChainEpoch - var earlyParamBytes []byte - var dbTask uint64 - - err = w.db.QueryRow( - context.Background(), `SELECT sp_id, proving_period_start, deadline, partition, submit_at_epoch, submit_by_epoch, proof_params, submit_task_id - FROM wdpost_proofs WHERE submit_task_id = $1`, taskID, - ).Scan(&spID, &pps, &deadline, &partition, &submitAtEpoch, &submitByEpoch, &earlyParamBytes, &dbTask) - if err != nil { - return false, xerrors.Errorf("query post proof: %w", err) - } - - if dbTask != uint64(taskID) { - return false, xerrors.Errorf("taskID mismatch: %d != %d", dbTask, taskID) - } - - head, err := w.api.ChainHead(context.Background()) - if err != nil { - return false, xerrors.Errorf("getting chain head: %w", err) - } - - if head.Height() > submitByEpoch { - // we missed the deadline, no point in submitting - log.Errorw("missed submit deadline", "spID", spID, "deadline", deadline, "partition", partition, "submitByEpoch", submitByEpoch, "headHeight", head.Height()) - return true, nil - } - - if head.Height() < submitAtEpoch { - log.Errorw("submit epoch not reached", "spID", spID, "deadline", deadline, "partition", partition, "submitAtEpoch", submitAtEpoch, "headHeight", head.Height()) - return false, xerrors.Errorf("submit epoch not reached: %d < %d", head.Height(), submitAtEpoch) - } - - dlInfo := wdpost.NewDeadlineInfo(pps, deadline, head.Height()) - - var params miner.SubmitWindowedPoStParams - if err := params.UnmarshalCBOR(bytes.NewReader(earlyParamBytes)); err != nil { - return false, xerrors.Errorf("unmarshaling proof message: %w", err) - } - - commEpoch := dlInfo.Challenge - - commRand, err := w.api.StateGetRandomnessFromTickets(context.Background(), crypto.DomainSeparationTag_PoStChainCommit, commEpoch, nil, head.Key()) - if err != nil { - err = xerrors.Errorf("failed to get chain randomness from tickets for windowPost (epoch=%d): %w", commEpoch, err) - log.Errorf("submitPoStMessage failed: %+v", err) - - return false, xerrors.Errorf("getting post commit randomness: %w", err) - } - - params.ChainCommitEpoch = commEpoch - params.ChainCommitRand = commRand - - var pbuf bytes.Buffer - if err := params.MarshalCBOR(&pbuf); err != nil { - return false, xerrors.Errorf("marshaling proof message: %w", err) - } - - maddr, err := address.NewIDAddress(spID) - if err != nil { - return false, xerrors.Errorf("invalid miner address: %w", err) - } - - msg := &types.Message{ - To: maddr, - Method: builtin.MethodsMiner.SubmitWindowedPoSt, - Params: pbuf.Bytes(), - Value: big.Zero(), - } - - msg, mss, err := preparePoStMessage(w.api, w.as, maddr, msg, abi.TokenAmount(w.maxWindowPoStGasFee)) - if err != nil { - return false, xerrors.Errorf("preparing proof message: %w", err) - } - - ctx := context.Background() - smsg, err := w.sender.Send(ctx, msg, mss, "wdpost") - if err != nil { - return false, xerrors.Errorf("sending proof message: %w", err) - } - - // set message_cid in the wdpost_proofs entry - - _, err = w.db.Exec(ctx, `UPDATE wdpost_proofs SET message_cid = $1 WHERE sp_id = $2 AND proving_period_start = $3 AND deadline = $4 AND partition = $5`, smsg.String(), spID, pps, deadline, partition) - if err != nil { - return true, xerrors.Errorf("updating wdpost_proofs: %w", err) - } - - return true, nil -} - -func (w *WdPostSubmitTask) CanAccept(ids []harmonytask.TaskID, engine *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - if len(ids) == 0 { - // probably can't happen, but panicking is bad - return nil, nil - } - - if w.sender == nil { - // we can't send messages - return nil, nil - } - - return &ids[0], nil -} - -func (w *WdPostSubmitTask) TypeDetails() harmonytask.TaskTypeDetails { - return harmonytask.TaskTypeDetails{ - Max: 128, - Name: "WdPostSubmit", - Cost: resources.Resources{ - Cpu: 0, - Gpu: 0, - Ram: 10 << 20, - }, - MaxFailures: 10, - Follows: nil, // ?? - } -} - -func (w *WdPostSubmitTask) Adder(taskFunc harmonytask.AddTaskFunc) { - w.submitPoStTF.Set(taskFunc) -} - -func (w *WdPostSubmitTask) processHeadChange(ctx context.Context, revert, apply *types.TipSet) error { - tf := w.submitPoStTF.Val(ctx) - - qry, err := w.db.Query(ctx, `SELECT sp_id, proving_period_start, deadline, partition, submit_at_epoch FROM wdpost_proofs WHERE submit_task_id IS NULL AND submit_at_epoch <= $1`, apply.Height()) - if err != nil { - return err - } - defer qry.Close() - - for qry.Next() { - var spID int64 - var pps int64 - var deadline uint64 - var partition uint64 - var submitAtEpoch uint64 - if err := qry.Scan(&spID, &pps, &deadline, &partition, &submitAtEpoch); err != nil { - return xerrors.Errorf("scan submittable posts: %w", err) - } - - tf(func(id harmonytask.TaskID, tx *harmonydb.Tx) (shouldCommit bool, err error) { - // update in transaction iff submit_task_id is still null - res, err := tx.Exec(`UPDATE wdpost_proofs SET submit_task_id = $1 WHERE sp_id = $2 AND proving_period_start = $3 AND deadline = $4 AND partition = $5 AND submit_task_id IS NULL`, id, spID, pps, deadline, partition) - if err != nil { - return false, xerrors.Errorf("query ready proof: %w", err) - } - if res != 1 { - return false, nil - } - - return true, nil - }) - } - if err := qry.Err(); err != nil { - return err - } - - return nil -} - -type MsgPrepAPI interface { - StateMinerInfo(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error) - GasEstimateMessageGas(context.Context, *types.Message, *api.MessageSendSpec, types.TipSetKey) (*types.Message, error) - GasEstimateFeeCap(context.Context, *types.Message, int64, types.TipSetKey) (types.BigInt, error) - GasEstimateGasPremium(ctx context.Context, nblocksincl uint64, sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error) - - WalletBalance(context.Context, address.Address) (types.BigInt, error) - WalletHas(context.Context, address.Address) (bool, error) - StateAccountKey(context.Context, address.Address, types.TipSetKey) (address.Address, error) - StateLookupID(context.Context, address.Address, types.TipSetKey) (address.Address, error) -} - -func preparePoStMessage(w MsgPrepAPI, as *multictladdr.MultiAddressSelector, maddr address.Address, msg *types.Message, maxFee abi.TokenAmount) (*types.Message, *api.MessageSendSpec, error) { - mi, err := w.StateMinerInfo(context.Background(), maddr, types.EmptyTSK) - if err != nil { - return nil, nil, xerrors.Errorf("error getting miner info: %w", err) - } - - // set the worker as a fallback - msg.From = mi.Worker - - mss := &api.MessageSendSpec{ - MaxFee: maxFee, - } - - // (optimal) initial estimation with some overestimation that guarantees - // block inclusion within the next 20 tipsets. - gm, err := w.GasEstimateMessageGas(context.Background(), msg, mss, types.EmptyTSK) - if err != nil { - log.Errorw("estimating gas", "error", err) - return nil, nil, xerrors.Errorf("estimating gas: %w", err) - } - *msg = *gm - - // calculate a more frugal estimation; premium is estimated to guarantee - // inclusion within 5 tipsets, and fee cap is estimated for inclusion - // within 4 tipsets. - minGasFeeMsg := *msg - - minGasFeeMsg.GasPremium, err = w.GasEstimateGasPremium(context.Background(), 5, msg.From, msg.GasLimit, types.EmptyTSK) - if err != nil { - log.Errorf("failed to estimate minimum gas premium: %+v", err) - minGasFeeMsg.GasPremium = msg.GasPremium - } - - minGasFeeMsg.GasFeeCap, err = w.GasEstimateFeeCap(context.Background(), &minGasFeeMsg, 4, types.EmptyTSK) - if err != nil { - log.Errorf("failed to estimate minimum gas fee cap: %+v", err) - minGasFeeMsg.GasFeeCap = msg.GasFeeCap - } - - // goodFunds = funds needed for optimal inclusion probability. - // minFunds = funds needed for more speculative inclusion probability. - goodFunds := big.Add(minGasFeeMsg.RequiredFunds(), minGasFeeMsg.Value) - minFunds := big.Min(big.Add(minGasFeeMsg.RequiredFunds(), minGasFeeMsg.Value), goodFunds) - - from, _, err := as.AddressFor(context.Background(), w, maddr, mi, api.PoStAddr, goodFunds, minFunds) - if err != nil { - return nil, nil, xerrors.Errorf("error getting address: %w", err) - } - - msg.From = from - - return msg, mss, nil -} - -var _ harmonytask.TaskInterface = &WdPostSubmitTask{} diff --git a/curiosrc/winning/winning_task.go b/curiosrc/winning/winning_task.go deleted file mode 100644 index 920a73394..000000000 --- a/curiosrc/winning/winning_task.go +++ /dev/null @@ -1,688 +0,0 @@ -package winning - -import ( - "bytes" - "context" - "crypto/rand" - "encoding/binary" - "encoding/json" - "time" - - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - "golang.org/x/xerrors" - - ffi "github.com/filecoin-project/filecoin-ffi" - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/crypto" - "github.com/filecoin-project/go-state-types/network" - prooftypes "github.com/filecoin-project/go-state-types/proof" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/gen" - lrand "github.com/filecoin-project/lotus/chain/rand" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/lib/promise" - "github.com/filecoin-project/lotus/node/modules/dtypes" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -var log = logging.Logger("curio/winning") - -type WinPostTask struct { - max int - db *harmonydb.DB - - prover ProverWinningPoSt - verifier storiface.Verifier - - api WinPostAPI - actors map[dtypes.MinerAddress]bool - - mineTF promise.Promise[harmonytask.AddTaskFunc] -} - -type WinPostAPI interface { - ChainHead(context.Context) (*types.TipSet, error) - ChainTipSetWeight(context.Context, types.TipSetKey) (types.BigInt, error) - ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error) - - StateGetBeaconEntry(context.Context, abi.ChainEpoch) (*types.BeaconEntry, error) - SyncSubmitBlock(context.Context, *types.BlockMsg) error - StateGetRandomnessFromBeacon(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte, tsk types.TipSetKey) (abi.Randomness, error) - StateGetRandomnessFromTickets(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte, tsk types.TipSetKey) (abi.Randomness, error) - StateNetworkVersion(context.Context, types.TipSetKey) (network.Version, error) - StateMinerInfo(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error) - - MinerGetBaseInfo(context.Context, address.Address, abi.ChainEpoch, types.TipSetKey) (*api.MiningBaseInfo, error) - MinerCreateBlock(context.Context, *api.BlockTemplate) (*types.BlockMsg, error) - MpoolSelect(context.Context, types.TipSetKey, float64) ([]*types.SignedMessage, error) - - WalletSign(context.Context, address.Address, []byte) (*crypto.Signature, error) -} - -type ProverWinningPoSt interface { - GenerateWinningPoSt(ctx context.Context, ppt abi.RegisteredPoStProof, minerID abi.ActorID, sectorInfo []storiface.PostSectorChallenge, randomness abi.PoStRandomness) ([]prooftypes.PoStProof, error) -} - -func NewWinPostTask(max int, db *harmonydb.DB, prover ProverWinningPoSt, verifier storiface.Verifier, api WinPostAPI, actors map[dtypes.MinerAddress]bool) *WinPostTask { - t := &WinPostTask{ - max: max, - db: db, - prover: prover, - verifier: verifier, - api: api, - actors: actors, - } - // TODO: run warmup - - go t.mineBasic(context.TODO()) - - return t -} - -func (t *WinPostTask) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - log.Debugw("WinPostTask.Do()", "taskID", taskID) - - ctx := context.TODO() - - type BlockCID struct { - CID string - } - - type MiningTaskDetails struct { - SpID uint64 - Epoch uint64 - BlockCIDs []BlockCID - CompTime time.Time - } - - var details MiningTaskDetails - - // First query to fetch from mining_tasks - err = t.db.QueryRow(ctx, `SELECT sp_id, epoch, base_compute_time FROM mining_tasks WHERE task_id = $1`, taskID).Scan(&details.SpID, &details.Epoch, &details.CompTime) - if err != nil { - return false, xerrors.Errorf("query mining base info fail: %w", err) - } - - // Second query to fetch from mining_base_block - rows, err := t.db.Query(ctx, `SELECT block_cid FROM mining_base_block WHERE task_id = $1`, taskID) - if err != nil { - return false, xerrors.Errorf("query mining base blocks fail: %w", err) - } - defer rows.Close() - - for rows.Next() { - var cid BlockCID - if err := rows.Scan(&cid.CID); err != nil { - return false, err - } - details.BlockCIDs = append(details.BlockCIDs, cid) - } - - if err := rows.Err(); err != nil { - return false, xerrors.Errorf("query mining base blocks fail (rows.Err): %w", err) - } - - // construct base - maddr, err := address.NewIDAddress(details.SpID) - if err != nil { - return false, err - } - - var bcids []cid.Cid - for _, c := range details.BlockCIDs { - bcid, err := cid.Parse(c.CID) - if err != nil { - return false, err - } - bcids = append(bcids, bcid) - } - - tsk := types.NewTipSetKey(bcids...) - baseTs, err := t.api.ChainGetTipSet(ctx, tsk) - if err != nil { - return false, xerrors.Errorf("loading base tipset: %w", err) - } - - base := MiningBase{ - TipSet: baseTs, - AddRounds: abi.ChainEpoch(details.Epoch) - baseTs.Height() - 1, - ComputeTime: details.CompTime, - } - - persistNoWin := func() (bool, error) { - n, err := t.db.Exec(ctx, `UPDATE mining_base_block SET no_win = true WHERE task_id = $1`, taskID) - if err != nil { - return false, xerrors.Errorf("marking base as not-won: %w", err) - } - log.Debugw("persisted no-win", "rows", n) - - if n == 0 { - return false, xerrors.Errorf("persist no win: no rows updated") - } - - return true, nil - } - - // ensure we have a beacon entry for the epoch we're mining on - round := base.epoch() - - _ = retry1(func() (*types.BeaconEntry, error) { - return t.api.StateGetBeaconEntry(ctx, round) - }) - - // MAKE A MINING ATTEMPT!! - log.Debugw("attempting to mine a block", "tipset", types.LogCids(base.TipSet.Cids()), "null-rounds", base.AddRounds) - - mbi, err := t.api.MinerGetBaseInfo(ctx, maddr, round, base.TipSet.Key()) - if err != nil { - return false, xerrors.Errorf("failed to get mining base info: %w", err) - } - if mbi == nil { - // not eligible to mine on this base, we're done here - log.Debugw("WinPoSt not eligible to mine on this base", "tipset", types.LogCids(base.TipSet.Cids())) - return persistNoWin() - } - - if !mbi.EligibleForMining { - // slashed or just have no power yet, we're done here - log.Debugw("WinPoSt not eligible for mining", "tipset", types.LogCids(base.TipSet.Cids())) - return persistNoWin() - } - - if len(mbi.Sectors) == 0 { - log.Warnw("WinPoSt no sectors to mine", "tipset", types.LogCids(base.TipSet.Cids())) - return false, xerrors.Errorf("no sectors selected for winning PoSt") - } - - var rbase types.BeaconEntry - var bvals []types.BeaconEntry - var eproof *types.ElectionProof - - // winner check - { - bvals = mbi.BeaconEntries - rbase = mbi.PrevBeaconEntry - if len(bvals) > 0 { - rbase = bvals[len(bvals)-1] - } - - eproof, err = gen.IsRoundWinner(ctx, round, maddr, rbase, mbi, t.api) - if err != nil { - log.Warnw("WinPoSt failed to check if we win next round", "error", err) - return false, xerrors.Errorf("failed to check if we win next round: %w", err) - } - - if eproof == nil { - // not a winner, we're done here - log.Debugw("WinPoSt not a winner", "tipset", types.LogCids(base.TipSet.Cids())) - return persistNoWin() - } - } - - log.Infow("WinPostTask won election", "tipset", types.LogCids(base.TipSet.Cids()), "miner", maddr, "round", round, "eproof", eproof) - - // winning PoSt - var wpostProof []prooftypes.PoStProof - { - buf := new(bytes.Buffer) - if err := maddr.MarshalCBOR(buf); err != nil { - err = xerrors.Errorf("failed to marshal miner address: %w", err) - return false, err - } - - brand, err := lrand.DrawRandomnessFromBase(rbase.Data, crypto.DomainSeparationTag_WinningPoStChallengeSeed, round, buf.Bytes()) - if err != nil { - err = xerrors.Errorf("failed to get randomness for winning post: %w", err) - return false, err - } - - prand := abi.PoStRandomness(brand) - prand[31] &= 0x3f // make into fr - - sectorNums := make([]abi.SectorNumber, len(mbi.Sectors)) - for i, s := range mbi.Sectors { - sectorNums[i] = s.SectorNumber - } - - ppt, err := mbi.Sectors[0].SealProof.RegisteredWinningPoStProof() - if err != nil { - return false, xerrors.Errorf("mapping sector seal proof type to post proof type: %w", err) - } - - postChallenges, err := ffi.GeneratePoStFallbackSectorChallenges(ppt, abi.ActorID(details.SpID), prand, sectorNums) - if err != nil { - return false, xerrors.Errorf("generating election challenges: %v", err) - } - - sectorChallenges := make([]storiface.PostSectorChallenge, len(mbi.Sectors)) - for i, s := range mbi.Sectors { - sectorChallenges[i] = storiface.PostSectorChallenge{ - SealProof: s.SealProof, - SectorNumber: s.SectorNumber, - SealedCID: s.SealedCID, - Challenge: postChallenges.Challenges[s.SectorNumber], - Update: s.SectorKey != nil, - } - } - - wpostProof, err = t.prover.GenerateWinningPoSt(ctx, ppt, abi.ActorID(details.SpID), sectorChallenges, prand) - if err != nil { - err = xerrors.Errorf("failed to compute winning post proof: %w", err) - return false, err - } - } - - log.Infow("WinPostTask winning PoSt computed", "tipset", types.LogCids(base.TipSet.Cids()), "miner", maddr, "round", round, "proofs", wpostProof) - - ticket, err := t.computeTicket(ctx, maddr, &rbase, round, base.TipSet.MinTicket(), mbi) - if err != nil { - return false, xerrors.Errorf("scratching ticket failed: %w", err) - } - - // get pending messages early, - msgs, err := t.api.MpoolSelect(ctx, base.TipSet.Key(), ticket.Quality()) - if err != nil { - return false, xerrors.Errorf("failed to select messages for block: %w", err) - } - - log.Infow("WinPostTask selected messages", "tipset", types.LogCids(base.TipSet.Cids()), "miner", maddr, "round", round, "messages", len(msgs)) - - // equivocation handling - { - // This next block exists to "catch" equivocating miners, - // who submit 2 blocks at the same height at different times in order to split the network. - // To safeguard against this, we make sure it's been EquivocationDelaySecs since our base was calculated, - // then re-calculate it. - // If the daemon detected equivocated blocks, those blocks will no longer be in the new base. - time.Sleep(time.Until(base.ComputeTime.Add(time.Duration(build.EquivocationDelaySecs) * time.Second))) - - bestTs, err := t.api.ChainHead(ctx) - if err != nil { - return false, xerrors.Errorf("failed to get chain head: %w", err) - } - - headWeight, err := t.api.ChainTipSetWeight(ctx, bestTs.Key()) - if err != nil { - return false, xerrors.Errorf("failed to get chain head weight: %w", err) - } - - baseWeight, err := t.api.ChainTipSetWeight(ctx, base.TipSet.Key()) - if err != nil { - return false, xerrors.Errorf("failed to get base weight: %w", err) - } - if types.BigCmp(headWeight, baseWeight) <= 0 { - bestTs = base.TipSet - } - - // If the base has changed, we take the _intersection_ of our old base and new base, - // thus ejecting blocks from any equivocating miners, without taking any new blocks. - if bestTs.Height() == base.TipSet.Height() && !bestTs.Equals(base.TipSet) { - log.Warnf("base changed from %s to %s, taking intersection", base.TipSet.Key(), bestTs.Key()) - newBaseMap := map[cid.Cid]struct{}{} - for _, newBaseBlk := range bestTs.Cids() { - newBaseMap[newBaseBlk] = struct{}{} - } - - refreshedBaseBlocks := make([]*types.BlockHeader, 0, len(base.TipSet.Cids())) - for _, baseBlk := range base.TipSet.Blocks() { - if _, ok := newBaseMap[baseBlk.Cid()]; ok { - refreshedBaseBlocks = append(refreshedBaseBlocks, baseBlk) - } - } - - if len(refreshedBaseBlocks) != 0 && len(refreshedBaseBlocks) != len(base.TipSet.Blocks()) { - refreshedBase, err := types.NewTipSet(refreshedBaseBlocks) - if err != nil { - return false, xerrors.Errorf("failed to create new tipset when refreshing: %w", err) - } - - if !base.TipSet.MinTicket().Equals(refreshedBase.MinTicket()) { - log.Warn("recomputing ticket due to base refresh") - - ticket, err = t.computeTicket(ctx, maddr, &rbase, round, refreshedBase.MinTicket(), mbi) - if err != nil { - return false, xerrors.Errorf("failed to refresh ticket: %w", err) - } - } - - log.Warn("re-selecting messages due to base refresh") - // refresh messages, as the selected messages may no longer be valid - msgs, err = t.api.MpoolSelect(ctx, refreshedBase.Key(), ticket.Quality()) - if err != nil { - return false, xerrors.Errorf("failed to re-select messages for block: %w", err) - } - - base.TipSet = refreshedBase - } - } - } - - log.Infow("WinPostTask base ready", "tipset", types.LogCids(base.TipSet.Cids()), "miner", maddr, "round", round, "ticket", ticket) - - // block construction - var blockMsg *types.BlockMsg - { - uts := base.TipSet.MinTimestamp() + build.BlockDelaySecs*(uint64(base.AddRounds)+1) - - blockMsg, err = t.api.MinerCreateBlock(context.TODO(), &api.BlockTemplate{ - Miner: maddr, - Parents: base.TipSet.Key(), - Ticket: ticket, - Eproof: eproof, - BeaconValues: bvals, - Messages: msgs, - Epoch: round, - Timestamp: uts, - WinningPoStProof: wpostProof, - }) - if err != nil { - return false, xerrors.Errorf("failed to create block: %w", err) - } - } - - log.Infow("WinPostTask block ready", "tipset", types.LogCids(base.TipSet.Cids()), "miner", maddr, "round", round, "block", blockMsg.Header.Cid(), "timestamp", blockMsg.Header.Timestamp) - - // persist in db - { - bhjson, err := json.Marshal(blockMsg.Header) - if err != nil { - return false, xerrors.Errorf("failed to marshal block header: %w", err) - } - - _, err = t.db.Exec(ctx, `UPDATE mining_tasks - SET won = true, mined_cid = $2, mined_header = $3, mined_at = $4 - WHERE task_id = $1`, taskID, blockMsg.Header.Cid(), string(bhjson), time.Now().UTC()) - if err != nil { - return false, xerrors.Errorf("failed to update mining task: %w", err) - } - } - - // wait until block timestamp - { - log.Infow("WinPostTask waiting for block timestamp", "tipset", types.LogCids(base.TipSet.Cids()), "miner", maddr, "round", round, "block", blockMsg.Header.Cid(), "until", time.Unix(int64(blockMsg.Header.Timestamp), 0)) - time.Sleep(time.Until(time.Unix(int64(blockMsg.Header.Timestamp), 0))) - } - - // submit block!! - { - log.Infow("WinPostTask submitting block", "tipset", types.LogCids(base.TipSet.Cids()), "miner", maddr, "round", round, "block", blockMsg.Header.Cid()) - if err := t.api.SyncSubmitBlock(ctx, blockMsg); err != nil { - return false, xerrors.Errorf("failed to submit block: %w", err) - } - } - - log.Infow("mined a block", "tipset", types.LogCids(blockMsg.Header.Parents), "height", blockMsg.Header.Height, "miner", maddr, "cid", blockMsg.Header.Cid()) - - // persist that we've submitted the block - { - _, err = t.db.Exec(ctx, `UPDATE mining_tasks - SET submitted_at = $2 - WHERE task_id = $1`, taskID, time.Now().UTC()) - if err != nil { - return false, xerrors.Errorf("failed to update mining task: %w", err) - } - } - - return true, nil -} - -func (t *WinPostTask) CanAccept(ids []harmonytask.TaskID, engine *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - if len(ids) == 0 { - // probably can't happen, but panicking is bad - return nil, nil - } - - // select lowest epoch - var lowestEpoch abi.ChainEpoch - var lowestEpochID = ids[0] - for _, id := range ids { - var epoch uint64 - err := t.db.QueryRow(context.Background(), `SELECT epoch FROM mining_tasks WHERE task_id = $1`, id).Scan(&epoch) - if err != nil { - return nil, err - } - - if lowestEpoch == 0 || abi.ChainEpoch(epoch) < lowestEpoch { - lowestEpoch = abi.ChainEpoch(epoch) - lowestEpochID = id - } - } - - return &lowestEpochID, nil -} - -func (t *WinPostTask) TypeDetails() harmonytask.TaskTypeDetails { - return harmonytask.TaskTypeDetails{ - Name: "WinPost", - Max: t.max, - MaxFailures: 3, - Follows: nil, - Cost: resources.Resources{ - Cpu: 1, - - // todo set to something for 32/64G sector sizes? Technically windowPoSt is happy on a CPU - // but it will use a GPU if available - Gpu: 0, - - Ram: 1 << 30, // todo arbitrary number - }, - } -} - -func (t *WinPostTask) Adder(taskFunc harmonytask.AddTaskFunc) { - t.mineTF.Set(taskFunc) -} - -// MiningBase is the tipset on top of which we plan to construct our next block. -// Refer to godocs on GetBestMiningCandidate. -type MiningBase struct { - TipSet *types.TipSet - ComputeTime time.Time - AddRounds abi.ChainEpoch -} - -func (mb MiningBase) epoch() abi.ChainEpoch { - // return the epoch that will result from mining on this base - return mb.TipSet.Height() + mb.AddRounds + 1 -} - -func (mb MiningBase) baseTime() time.Time { - tsTime := time.Unix(int64(mb.TipSet.MinTimestamp()), 0) - roundDelay := build.BlockDelaySecs * uint64(mb.AddRounds+1) - tsTime = tsTime.Add(time.Duration(roundDelay) * time.Second) - return tsTime -} - -func (mb MiningBase) afterPropDelay() time.Time { - return mb.baseTime().Add(time.Duration(build.PropagationDelaySecs) * time.Second).Add(randTimeOffset(time.Second)) -} - -func (t *WinPostTask) mineBasic(ctx context.Context) { - var workBase MiningBase - - taskFn := t.mineTF.Val(ctx) - - // initialize workbase - { - head := retry1(func() (*types.TipSet, error) { - return t.api.ChainHead(ctx) - }) - - workBase = MiningBase{ - TipSet: head, - AddRounds: 0, - ComputeTime: time.Now(), - } - } - - /* - - /- T+0 == workBase.baseTime - | - >--------*------*--------[wait until next round]-----> - | - |- T+PD == workBase.afterPropDelay+(~1s) - |- Here we acquire the new workBase, and start a new round task - \- Then we loop around, and wait for the next head - - time --> - */ - - for { - // limit the rate at which we mine blocks to at least EquivocationDelaySecs - // this is to prevent races on devnets in catch up mode. Acts as a minimum - // delay for the sleep below. - time.Sleep(time.Duration(build.EquivocationDelaySecs)*time.Second + time.Second) - - // wait for *NEXT* propagation delay - time.Sleep(time.Until(workBase.afterPropDelay())) - - // check current best candidate - maybeBase := retry1(func() (*types.TipSet, error) { - return t.api.ChainHead(ctx) - }) - - if workBase.TipSet.Equals(maybeBase) { - // workbase didn't change in the new round so we have a null round here - workBase.AddRounds++ - log.Debugw("workbase update", "tipset", workBase.TipSet.Cids(), "nulls", workBase.AddRounds, "lastUpdate", time.Since(workBase.ComputeTime), "type", "same-tipset") - } else { - btsw := retry1(func() (types.BigInt, error) { - return t.api.ChainTipSetWeight(ctx, maybeBase.Key()) - }) - - ltsw := retry1(func() (types.BigInt, error) { - return t.api.ChainTipSetWeight(ctx, workBase.TipSet.Key()) - }) - - if types.BigCmp(btsw, ltsw) <= 0 { - // new tipset for some reason has less weight than the old one, assume null round here - // NOTE: the backing node may have reorged, or manually changed head - workBase.AddRounds++ - log.Debugw("workbase update", "tipset", workBase.TipSet.Cids(), "nulls", workBase.AddRounds, "lastUpdate", time.Since(workBase.ComputeTime), "type", "prefer-local-weight") - } else { - // new tipset has more weight, so we should mine on it, no null round here - log.Debugw("workbase update", "tipset", workBase.TipSet.Cids(), "nulls", workBase.AddRounds, "lastUpdate", time.Since(workBase.ComputeTime), "type", "prefer-new-tipset") - - workBase = MiningBase{ - TipSet: maybeBase, - AddRounds: 0, - ComputeTime: time.Now(), - } - } - } - - // dispatch mining task - // (note equivocation prevention is handled by the mining code) - - baseEpoch := workBase.TipSet.Height() - - for act := range t.actors { - spID, err := address.IDFromAddress(address.Address(act)) - if err != nil { - log.Errorf("failed to get spID from address %s: %s", act, err) - continue - } - - taskFn(func(id harmonytask.TaskID, tx *harmonydb.Tx) (shouldCommit bool, seriousError error) { - // First we check if the mining base includes blocks we may have mined previously to avoid getting slashed - // select mining_tasks where epoch==base_epoch if win=true to maybe get base block cid which has to be included in our tipset - var baseBlockCids []string - err := tx.Select(&baseBlockCids, `SELECT mined_cid FROM mining_tasks WHERE epoch = $1 AND sp_id = $2 AND won = true`, baseEpoch, spID) - if err != nil { - return false, xerrors.Errorf("querying mining_tasks: %w", err) - } - if len(baseBlockCids) >= 1 { - baseBlockCid := baseBlockCids[0] - c, err := cid.Parse(baseBlockCid) - if err != nil { - return false, xerrors.Errorf("parsing mined_cid: %w", err) - } - - // we have mined in the previous round, make sure that our block is included in the tipset - // if it's not we risk getting slashed - - var foundOurs bool - for _, c2 := range workBase.TipSet.Cids() { - if c == c2 { - foundOurs = true - break - } - } - if !foundOurs { - log.Errorw("our block was not included in the tipset, aborting", "tipset", workBase.TipSet.Cids(), "ourBlock", c) - return false, xerrors.Errorf("our block was not included in the tipset, aborting") - } - } - - _, err = tx.Exec(`INSERT INTO mining_tasks (task_id, sp_id, epoch, base_compute_time) VALUES ($1, $2, $3, $4)`, id, spID, workBase.epoch(), workBase.ComputeTime.UTC()) - if err != nil { - return false, xerrors.Errorf("inserting mining_tasks: %w", err) - } - - for _, c := range workBase.TipSet.Cids() { - _, err = tx.Exec(`INSERT INTO mining_base_block (task_id, sp_id, block_cid) VALUES ($1, $2, $3)`, id, spID, c) - if err != nil { - return false, xerrors.Errorf("inserting mining base blocks: %w", err) - } - } - - return true, nil // no errors, commit the transaction - }) - } - } -} - -func (t *WinPostTask) computeTicket(ctx context.Context, maddr address.Address, brand *types.BeaconEntry, round abi.ChainEpoch, chainRand *types.Ticket, mbi *api.MiningBaseInfo) (*types.Ticket, error) { - buf := new(bytes.Buffer) - if err := maddr.MarshalCBOR(buf); err != nil { - return nil, xerrors.Errorf("failed to marshal address to cbor: %w", err) - } - - if round > build.UpgradeSmokeHeight { - buf.Write(chainRand.VRFProof) - } - - input, err := lrand.DrawRandomnessFromBase(brand.Data, crypto.DomainSeparationTag_TicketProduction, round-build.TicketRandomnessLookback, buf.Bytes()) - if err != nil { - return nil, err - } - - vrfOut, err := gen.ComputeVRF(ctx, t.api.WalletSign, mbi.WorkerKey, input) - if err != nil { - return nil, err - } - - return &types.Ticket{ - VRFProof: vrfOut, - }, nil -} - -func randTimeOffset(width time.Duration) time.Duration { - buf := make([]byte, 8) - _, _ = rand.Reader.Read(buf) - val := time.Duration(binary.BigEndian.Uint64(buf) % uint64(width)) - - return val - (width / 2) -} - -func retry1[R any](f func() (R, error)) R { - for { - r, err := f() - if err == nil { - return r - } - - log.Errorw("error in mining loop, retrying", "error", err) - time.Sleep(time.Second) - } -} - -var _ harmonytask.TaskInterface = &WinPostTask{} diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index 802cd3ce5..ce09667e5 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -23,58 +23,14 @@ * [ComputeWindowPoSt](#ComputeWindowPoSt) * [Create](#Create) * [CreateBackup](#CreateBackup) -* [Dagstore](#Dagstore) - * [DagstoreGC](#DagstoreGC) - * [DagstoreInitializeAll](#DagstoreInitializeAll) - * [DagstoreInitializeShard](#DagstoreInitializeShard) - * [DagstoreListShards](#DagstoreListShards) - * [DagstoreLookupPieces](#DagstoreLookupPieces) - * [DagstoreRecoverShard](#DagstoreRecoverShard) - * [DagstoreRegisterShard](#DagstoreRegisterShard) -* [Deals](#Deals) - * [DealsConsiderOfflineRetrievalDeals](#DealsConsiderOfflineRetrievalDeals) - * [DealsConsiderOfflineStorageDeals](#DealsConsiderOfflineStorageDeals) - * [DealsConsiderOnlineRetrievalDeals](#DealsConsiderOnlineRetrievalDeals) - * [DealsConsiderOnlineStorageDeals](#DealsConsiderOnlineStorageDeals) - * [DealsConsiderUnverifiedStorageDeals](#DealsConsiderUnverifiedStorageDeals) - * [DealsConsiderVerifiedStorageDeals](#DealsConsiderVerifiedStorageDeals) - * [DealsImportData](#DealsImportData) - * [DealsList](#DealsList) - * [DealsPieceCidBlocklist](#DealsPieceCidBlocklist) - * [DealsSetConsiderOfflineRetrievalDeals](#DealsSetConsiderOfflineRetrievalDeals) - * [DealsSetConsiderOfflineStorageDeals](#DealsSetConsiderOfflineStorageDeals) - * [DealsSetConsiderOnlineRetrievalDeals](#DealsSetConsiderOnlineRetrievalDeals) - * [DealsSetConsiderOnlineStorageDeals](#DealsSetConsiderOnlineStorageDeals) - * [DealsSetConsiderUnverifiedStorageDeals](#DealsSetConsiderUnverifiedStorageDeals) - * [DealsSetConsiderVerifiedStorageDeals](#DealsSetConsiderVerifiedStorageDeals) - * [DealsSetPieceCidBlocklist](#DealsSetPieceCidBlocklist) * [I](#I) * [ID](#ID) -* [Indexer](#Indexer) - * [IndexerAnnounceAllDeals](#IndexerAnnounceAllDeals) - * [IndexerAnnounceDeal](#IndexerAnnounceDeal) * [Log](#Log) * [LogAlerts](#LogAlerts) * [LogList](#LogList) * [LogSetLevel](#LogSetLevel) * [Market](#Market) - * [MarketCancelDataTransfer](#MarketCancelDataTransfer) - * [MarketDataTransferDiagnostics](#MarketDataTransferDiagnostics) - * [MarketDataTransferUpdates](#MarketDataTransferUpdates) - * [MarketGetAsk](#MarketGetAsk) - * [MarketGetDealUpdates](#MarketGetDealUpdates) - * [MarketGetRetrievalAsk](#MarketGetRetrievalAsk) - * [MarketImportDealData](#MarketImportDealData) - * [MarketListDataTransfers](#MarketListDataTransfers) * [MarketListDeals](#MarketListDeals) - * [MarketListIncompleteDeals](#MarketListIncompleteDeals) - * [MarketListRetrievalDeals](#MarketListRetrievalDeals) - * [MarketPendingDeals](#MarketPendingDeals) - * [MarketPublishPendingDeals](#MarketPublishPendingDeals) - * [MarketRestartDataTransfer](#MarketRestartDataTransfer) - * [MarketRetryPublishDeal](#MarketRetryPublishDeal) - * [MarketSetAsk](#MarketSetAsk) - * [MarketSetRetrievalAsk](#MarketSetRetrievalAsk) * [Mining](#Mining) * [MiningBase](#MiningBase) * [Net](#Net) @@ -101,11 +57,6 @@ * [NetPubsubScores](#NetPubsubScores) * [NetSetLimit](#NetSetLimit) * [NetStat](#NetStat) -* [Pieces](#Pieces) - * [PiecesGetCIDInfo](#PiecesGetCIDInfo) - * [PiecesGetPieceInfo](#PiecesGetPieceInfo) - * [PiecesListCidInfos](#PiecesListCidInfos) - * [PiecesListPieces](#PiecesListPieces) * [Pledge](#Pledge) * [PledgeSector](#PledgeSector) * [Recover](#Recover) @@ -475,7 +426,7 @@ Inputs: ], "Bw==", 10101, - 22 + 23 ] ``` @@ -556,400 +507,6 @@ Inputs: Response: `{}` -## Dagstore - - -### DagstoreGC -DagstoreGC runs garbage collection on the DAG store. - - -Perms: admin - -Inputs: `null` - -Response: -```json -[ - { - "Key": "baga6ea4seaqecmtz7iak33dsfshi627abz4i4665dfuzr3qfs4bmad6dx3iigdq", - "Success": false, - "Error": "\u003cerror\u003e" - } -] -``` - -### 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: -```json -[ - { - "Key": "baga6ea4seaqecmtz7iak33dsfshi627abz4i4665dfuzr3qfs4bmad6dx3iigdq", - "State": "ShardStateAvailable", - "Error": "\u003cerror\u003e" - } -] -``` - -### DagstoreLookupPieces -DagstoreLookupPieces returns information about shards that contain the given CID. - - -Perms: admin - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: -```json -[ - { - "Key": "baga6ea4seaqecmtz7iak33dsfshi627abz4i4665dfuzr3qfs4bmad6dx3iigdq", - "State": "ShardStateAvailable", - "Error": "\u003cerror\u003e" - } -] -``` - -### 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: `{}` - -### DagstoreRegisterShard -DagstoreRegisterShard registers a shard manually with dagstore with given pieceCID - - -Perms: admin - -Inputs: -```json -[ - "string value" -] -``` - -Response: `{}` - -## Deals - - -### DealsConsiderOfflineRetrievalDeals - - -Perms: admin - -Inputs: `null` - -Response: `true` - -### DealsConsiderOfflineStorageDeals - - -Perms: admin - -Inputs: `null` - -Response: `true` - -### DealsConsiderOnlineRetrievalDeals - - -Perms: admin - -Inputs: `null` - -Response: `true` - -### DealsConsiderOnlineStorageDeals - - -Perms: admin - -Inputs: `null` - -Response: `true` - -### DealsConsiderUnverifiedStorageDeals - - -Perms: admin - -Inputs: `null` - -Response: `true` - -### DealsConsiderVerifiedStorageDeals - - -Perms: admin - -Inputs: `null` - -Response: `true` - -### DealsImportData - - -Perms: admin - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "string value" -] -``` - -Response: `{}` - -### DealsList - - -Perms: admin - -Inputs: `null` - -Response: -```json -[ - { - "Proposal": { - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1032, - "VerifiedDeal": true, - "Client": "f01234", - "Provider": "f01234", - "Label": "", - "StartEpoch": 10101, - "EndEpoch": 10101, - "StoragePricePerEpoch": "0", - "ProviderCollateral": "0", - "ClientCollateral": "0" - }, - "State": { - "SectorStartEpoch": 10101, - "LastUpdatedEpoch": 10101, - "SlashEpoch": 10101 - } - } -] -``` - -### DealsPieceCidBlocklist - - -Perms: admin - -Inputs: `null` - -Response: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -### DealsSetConsiderOfflineRetrievalDeals - - -Perms: admin - -Inputs: -```json -[ - true -] -``` - -Response: `{}` - -### DealsSetConsiderOfflineStorageDeals - - -Perms: admin - -Inputs: -```json -[ - true -] -``` - -Response: `{}` - -### DealsSetConsiderOnlineRetrievalDeals - - -Perms: admin - -Inputs: -```json -[ - true -] -``` - -Response: `{}` - -### DealsSetConsiderOnlineStorageDeals - - -Perms: admin - -Inputs: -```json -[ - true -] -``` - -Response: `{}` - -### DealsSetConsiderUnverifiedStorageDeals - - -Perms: admin - -Inputs: -```json -[ - true -] -``` - -Response: `{}` - -### DealsSetConsiderVerifiedStorageDeals - - -Perms: admin - -Inputs: -```json -[ - true -] -``` - -Response: `{}` - -### DealsSetPieceCidBlocklist - - -Perms: admin - -Inputs: -```json -[ - [ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } - ] -] -``` - -Response: `{}` - ## I @@ -962,37 +519,6 @@ Inputs: `null` Response: `"12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf"` -## Indexer - - -### IndexerAnnounceAllDeals -IndexerAnnounceAllDeals informs the indexer nodes aboutall active deals. - - -Perms: admin - -Inputs: `null` - -Response: `{}` - -### IndexerAnnounceDeal -IndexerAnnounceDeal informs indexer nodes that a new deal was received, -so they can download its index - - -Perms: admin - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: `{}` - ## Log @@ -1058,344 +584,6 @@ Response: `{}` ## Market -### MarketCancelDataTransfer -MarketCancelDataTransfer cancels a data transfer with the given transfer ID and other peer - - -Perms: write - -Inputs: -```json -[ - 3, - "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - true -] -``` - -Response: `{}` - -### MarketDataTransferDiagnostics -MarketDataTransferDiagnostics generates debugging information about current data transfers over graphsync - - -Perms: write - -Inputs: -```json -[ - "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf" -] -``` - -Response: -```json -{ - "ReceivingTransfers": [ - { - "RequestID": {}, - "RequestState": "string value", - "IsCurrentChannelRequest": true, - "ChannelID": { - "Initiator": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Responder": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "ID": 3 - }, - "ChannelState": { - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } - }, - "Diagnostics": [ - "string value" - ] - } - ], - "SendingTransfers": [ - { - "RequestID": {}, - "RequestState": "string value", - "IsCurrentChannelRequest": true, - "ChannelID": { - "Initiator": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Responder": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "ID": 3 - }, - "ChannelState": { - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } - }, - "Diagnostics": [ - "string value" - ] - } - ] -} -``` - -### MarketDataTransferUpdates - - -Perms: write - -Inputs: `null` - -Response: -```json -{ - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } -} -``` - -### MarketGetAsk - - -Perms: read - -Inputs: `null` - -Response: -```json -{ - "Ask": { - "Price": "0", - "VerifiedPrice": "0", - "MinPieceSize": 1032, - "MaxPieceSize": 1032, - "Miner": "f01234", - "Timestamp": 10101, - "Expiry": 10101, - "SeqNo": 42 - }, - "Signature": { - "Type": 2, - "Data": "Ynl0ZSBhcnJheQ==" - } -} -``` - -### MarketGetDealUpdates - - -Perms: read - -Inputs: `null` - -Response: -```json -{ - "Proposal": { - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1032, - "VerifiedDeal": true, - "Client": "f01234", - "Provider": "f01234", - "Label": "", - "StartEpoch": 10101, - "EndEpoch": 10101, - "StoragePricePerEpoch": "0", - "ProviderCollateral": "0", - "ClientCollateral": "0" - }, - "ClientSignature": { - "Type": 2, - "Data": "Ynl0ZSBhcnJheQ==" - }, - "ProposalCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "AddFundsCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PublishCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Miner": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Client": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "State": 42, - "PiecePath": ".lotusminer/fstmp123", - "MetadataPath": ".lotusminer/fstmp123", - "SlashEpoch": 10101, - "FastRetrieval": true, - "Message": "string value", - "FundsReserved": "0", - "Ref": { - "TransferType": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1024, - "RawBlockSize": 42 - }, - "AvailableForRetrieval": true, - "DealID": 5432, - "CreationTime": "0001-01-01T00:00:00Z", - "TransferChannelId": { - "Initiator": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Responder": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "ID": 3 - }, - "SectorNumber": 9, - "InboundCAR": "string value" -} -``` - -### MarketGetRetrievalAsk - - -Perms: read - -Inputs: `null` - -Response: -```json -{ - "PricePerByte": "0", - "UnsealPrice": "0", - "PaymentInterval": 42, - "PaymentIntervalIncrease": 42 -} -``` - -### MarketImportDealData - - -Perms: write - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "string value" -] -``` - -Response: `{}` - -### MarketListDataTransfers - - -Perms: write - -Inputs: `null` - -Response: -```json -[ - { - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } - } -] -``` - ### MarketListDeals @@ -1423,6 +611,7 @@ Response: "ClientCollateral": "0" }, "State": { + "SectorNumber": 9, "SectorStartEpoch": 10101, "LastUpdatedEpoch": 10101, "SlashEpoch": 10101 @@ -1431,211 +620,6 @@ Response: ] ``` -### MarketListIncompleteDeals - - -Perms: read - -Inputs: `null` - -Response: -```json -[ - { - "Proposal": { - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1032, - "VerifiedDeal": true, - "Client": "f01234", - "Provider": "f01234", - "Label": "", - "StartEpoch": 10101, - "EndEpoch": 10101, - "StoragePricePerEpoch": "0", - "ProviderCollateral": "0", - "ClientCollateral": "0" - }, - "ClientSignature": { - "Type": 2, - "Data": "Ynl0ZSBhcnJheQ==" - }, - "ProposalCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "AddFundsCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PublishCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Miner": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Client": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "State": 42, - "PiecePath": ".lotusminer/fstmp123", - "MetadataPath": ".lotusminer/fstmp123", - "SlashEpoch": 10101, - "FastRetrieval": true, - "Message": "string value", - "FundsReserved": "0", - "Ref": { - "TransferType": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1024, - "RawBlockSize": 42 - }, - "AvailableForRetrieval": true, - "DealID": 5432, - "CreationTime": "0001-01-01T00:00:00Z", - "TransferChannelId": { - "Initiator": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Responder": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "ID": 3 - }, - "SectorNumber": 9, - "InboundCAR": "string value" - } -] -``` - -### MarketListRetrievalDeals -MarketListRetrievalDeals is deprecated, returns empty list - - -Perms: read - -Inputs: `null` - -Response: -```json -[ - {} -] -``` - -### MarketPendingDeals - - -Perms: write - -Inputs: `null` - -Response: -```json -{ - "Deals": [ - { - "Proposal": { - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1032, - "VerifiedDeal": true, - "Client": "f01234", - "Provider": "f01234", - "Label": "", - "StartEpoch": 10101, - "EndEpoch": 10101, - "StoragePricePerEpoch": "0", - "ProviderCollateral": "0", - "ClientCollateral": "0" - }, - "ClientSignature": { - "Type": 2, - "Data": "Ynl0ZSBhcnJheQ==" - } - } - ], - "PublishPeriodStart": "0001-01-01T00:00:00Z", - "PublishPeriod": 60000000000 -} -``` - -### MarketPublishPendingDeals - - -Perms: admin - -Inputs: `null` - -Response: `{}` - -### MarketRestartDataTransfer -MarketRestartDataTransfer attempts to restart a data transfer with the given transfer ID and other peer - - -Perms: write - -Inputs: -```json -[ - 3, - "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - true -] -``` - -Response: `{}` - -### MarketRetryPublishDeal - - -Perms: admin - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: `{}` - -### MarketSetAsk - - -Perms: admin - -Inputs: -```json -[ - "0", - "0", - 10101, - 1032, - 1032 -] -``` - -Response: `{}` - -### MarketSetRetrievalAsk - - -Perms: admin - -Inputs: -```json -[ - { - "PricePerByte": "0", - "UnsealPrice": "0", - "PaymentInterval": 42, - "PaymentIntervalIncrease": 42 - } -] -``` - -Response: `{}` - ## Mining @@ -2159,104 +1143,6 @@ Response: } ``` -## Pieces - - -### PiecesGetCIDInfo - - -Perms: read - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: -```json -{ - "CID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceBlockLocations": [ - { - "RelOffset": 42, - "BlockSize": 42, - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } - } - ] -} -``` - -### PiecesGetPieceInfo - - -Perms: read - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: -```json -{ - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Deals": [ - { - "DealID": 5432, - "SectorID": 9, - "Offset": 1032, - "Length": 1032 - } - ] -} -``` - -### PiecesListCidInfos - - -Perms: read - -Inputs: `null` - -Response: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -### PiecesListPieces - - -Perms: read - -Inputs: `null` - -Response: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - ## Pledge @@ -2795,8 +1681,7 @@ Response: [ "Mining", "Sealing", - "SectorStorage", - "Markets" + "SectorStorage" ] ``` diff --git a/documentation/en/api-v0-methods.md b/documentation/en/api-v0-methods.md index 9a8c4821b..bd58748ee 100644 --- a/documentation/en/api-v0-methods.md +++ b/documentation/en/api-v0-methods.md @@ -34,34 +34,6 @@ * [ChainSetHead](#ChainSetHead) * [ChainStatObj](#ChainStatObj) * [ChainTipSetWeight](#ChainTipSetWeight) -* [Client](#Client) - * [ClientCalcCommP](#ClientCalcCommP) - * [ClientCancelDataTransfer](#ClientCancelDataTransfer) - * [ClientCancelRetrievalDeal](#ClientCancelRetrievalDeal) - * [ClientDataTransferUpdates](#ClientDataTransferUpdates) - * [ClientDealPieceCID](#ClientDealPieceCID) - * [ClientDealSize](#ClientDealSize) - * [ClientFindData](#ClientFindData) - * [ClientGenCar](#ClientGenCar) - * [ClientGetDealInfo](#ClientGetDealInfo) - * [ClientGetDealStatus](#ClientGetDealStatus) - * [ClientGetDealUpdates](#ClientGetDealUpdates) - * [ClientGetRetrievalUpdates](#ClientGetRetrievalUpdates) - * [ClientHasLocal](#ClientHasLocal) - * [ClientImport](#ClientImport) - * [ClientListDataTransfers](#ClientListDataTransfers) - * [ClientListDeals](#ClientListDeals) - * [ClientListImports](#ClientListImports) - * [ClientListRetrievals](#ClientListRetrievals) - * [ClientMinerQueryOffer](#ClientMinerQueryOffer) - * [ClientQueryAsk](#ClientQueryAsk) - * [ClientRemoveImport](#ClientRemoveImport) - * [ClientRestartDataTransfer](#ClientRestartDataTransfer) - * [ClientRetrieve](#ClientRetrieve) - * [ClientRetrieveTryRestartInsufficientFunds](#ClientRetrieveTryRestartInsufficientFunds) - * [ClientRetrieveWithEvents](#ClientRetrieveWithEvents) - * [ClientStartDeal](#ClientStartDeal) - * [ClientStatelessDeal](#ClientStatelessDeal) * [Create](#Create) * [CreateBackup](#CreateBackup) * [Gas](#Gas) @@ -1091,1054 +1063,6 @@ Inputs: Response: `"0"` -## Client -The Client methods all have to do with interacting with the storage and -retrieval markets as a client - - -### ClientCalcCommP -ClientCalcCommP calculates the CommP for a specified file - - -Perms: write - -Inputs: -```json -[ - "string value" -] -``` - -Response: -```json -{ - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Size": 1024 -} -``` - -### ClientCancelDataTransfer -ClientCancelDataTransfer cancels a data transfer with the given transfer ID and other peer - - -Perms: write - -Inputs: -```json -[ - 3, - "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - true -] -``` - -Response: `{}` - -### ClientCancelRetrievalDeal -ClientCancelRetrievalDeal cancels an ongoing retrieval deal based on DealID - - -Perms: write - -Inputs: -```json -[ - 5 -] -``` - -Response: `{}` - -### ClientDataTransferUpdates - - -Perms: write - -Inputs: `null` - -Response: -```json -{ - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } -} -``` - -### ClientDealPieceCID -ClientCalcCommP calculates the CommP and data size of the specified CID - - -Perms: read - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: -```json -{ - "PayloadSize": 9, - "PieceSize": 1032, - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -} -``` - -### ClientDealSize -ClientDealSize calculates real deal data size - - -Perms: read - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: -```json -{ - "PayloadSize": 9, - "PieceSize": 1032 -} -``` - -### ClientFindData -ClientFindData identifies peers that have a certain file, and returns QueryOffers (one per peer). - - -Perms: read - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: -```json -[ - { - "Err": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Piece": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Size": 42, - "MinPrice": "0", - "UnsealPrice": "0", - "PricePerByte": "0", - "PaymentInterval": 42, - "PaymentIntervalIncrease": 42, - "Miner": "f01234", - "MinerPeer": { - "Address": "f01234", - "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } - } - } -] -``` - -### ClientGenCar -ClientGenCar generates a CAR file for the specified file. - - -Perms: write - -Inputs: -```json -[ - { - "Path": "string value", - "IsCAR": true - }, - "string value" -] -``` - -Response: `{}` - -### ClientGetDealInfo -ClientGetDealInfo returns the latest information about a given deal. - - -Perms: read - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: -```json -{ - "ProposalCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "State": 42, - "Message": "string value", - "DealStages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "ExpectedDuration": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - }, - "Provider": "f01234", - "DataRef": { - "TransferType": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1024, - "RawBlockSize": 42 - }, - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Size": 42, - "PricePerEpoch": "0", - "Duration": 42, - "DealID": 5432, - "CreationTime": "0001-01-01T00:00:00Z", - "Verified": true, - "TransferChannelID": { - "Initiator": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Responder": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "ID": 3 - }, - "DataTransfer": { - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } - } -} -``` - -### ClientGetDealStatus -ClientGetDealStatus returns status given a code - - -Perms: read - -Inputs: -```json -[ - 42 -] -``` - -Response: `"string value"` - -### ClientGetDealUpdates -ClientGetDealUpdates returns the status of updated deals - - -Perms: write - -Inputs: `null` - -Response: -```json -{ - "ProposalCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "State": 42, - "Message": "string value", - "DealStages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "ExpectedDuration": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - }, - "Provider": "f01234", - "DataRef": { - "TransferType": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1024, - "RawBlockSize": 42 - }, - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Size": 42, - "PricePerEpoch": "0", - "Duration": 42, - "DealID": 5432, - "CreationTime": "0001-01-01T00:00:00Z", - "Verified": true, - "TransferChannelID": { - "Initiator": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Responder": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "ID": 3 - }, - "DataTransfer": { - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } - } -} -``` - -### ClientGetRetrievalUpdates -ClientGetRetrievalUpdates returns status of updated retrieval deals - - -Perms: write - -Inputs: `null` - -Response: -```json -{ - "PayloadCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "ID": 5, - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PricePerByte": "0", - "UnsealPrice": "0", - "Status": 0, - "Message": "string value", - "Provider": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "BytesReceived": 42, - "BytesPaidFor": 42, - "TotalPaid": "0", - "TransferChannelID": { - "Initiator": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Responder": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "ID": 3 - }, - "DataTransfer": { - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } - }, - "Event": 5 -} -``` - -### ClientHasLocal -ClientHasLocal indicates whether a certain CID is locally stored. - - -Perms: write - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: `true` - -### ClientImport -ClientImport imports file under the specified path into filestore. - - -Perms: admin - -Inputs: -```json -[ - { - "Path": "string value", - "IsCAR": true - } -] -``` - -Response: -```json -{ - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "ImportID": 50 -} -``` - -### ClientListDataTransfers -ClientListTransfers returns the status of all ongoing transfers of data - - -Perms: write - -Inputs: `null` - -Response: -```json -[ - { - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } - } -] -``` - -### ClientListDeals -ClientListDeals returns information about the deals made by the local client. - - -Perms: write - -Inputs: `null` - -Response: -```json -[ - { - "ProposalCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "State": 42, - "Message": "string value", - "DealStages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "ExpectedDuration": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - }, - "Provider": "f01234", - "DataRef": { - "TransferType": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1024, - "RawBlockSize": 42 - }, - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Size": 42, - "PricePerEpoch": "0", - "Duration": 42, - "DealID": 5432, - "CreationTime": "0001-01-01T00:00:00Z", - "Verified": true, - "TransferChannelID": { - "Initiator": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Responder": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "ID": 3 - }, - "DataTransfer": { - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } - } - } -] -``` - -### ClientListImports -ClientListImports lists imported files and their root CIDs - - -Perms: write - -Inputs: `null` - -Response: -```json -[ - { - "Key": 50, - "Err": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Source": "string value", - "FilePath": "string value", - "CARPath": "string value" - } -] -``` - -### ClientListRetrievals -ClientQueryAsk returns a signed StorageAsk from the specified miner. -ClientListRetrievals returns information about retrievals made by the local client - - -Perms: write - -Inputs: `null` - -Response: -```json -[ - { - "PayloadCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "ID": 5, - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PricePerByte": "0", - "UnsealPrice": "0", - "Status": 0, - "Message": "string value", - "Provider": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "BytesReceived": 42, - "BytesPaidFor": 42, - "TotalPaid": "0", - "TransferChannelID": { - "Initiator": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Responder": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "ID": 3 - }, - "DataTransfer": { - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } - }, - "Event": 5 - } -] -``` - -### ClientMinerQueryOffer -ClientMinerQueryOffer returns a QueryOffer for the specific miner and file. - - -Perms: read - -Inputs: -```json -[ - "f01234", - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: -```json -{ - "Err": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Piece": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Size": 42, - "MinPrice": "0", - "UnsealPrice": "0", - "PricePerByte": "0", - "PaymentInterval": 42, - "PaymentIntervalIncrease": 42, - "Miner": "f01234", - "MinerPeer": { - "Address": "f01234", - "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } - } -} -``` - -### ClientQueryAsk - - -Perms: read - -Inputs: -```json -[ - "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "f01234" -] -``` - -Response: -```json -{ - "Price": "0", - "VerifiedPrice": "0", - "MinPieceSize": 1032, - "MaxPieceSize": 1032, - "Miner": "f01234", - "Timestamp": 10101, - "Expiry": 10101, - "SeqNo": 42 -} -``` - -### ClientRemoveImport -ClientRemoveImport removes file import - - -Perms: admin - -Inputs: -```json -[ - 50 -] -``` - -Response: `{}` - -### ClientRestartDataTransfer -ClientRestartDataTransfer attempts to restart a data transfer with the given transfer ID and other peer - - -Perms: write - -Inputs: -```json -[ - 3, - "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - true -] -``` - -Response: `{}` - -### ClientRetrieve -ClientRetrieve initiates the retrieval of a file, as specified in the order. - - -Perms: admin - -Inputs: -```json -[ - { - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Piece": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "DatamodelPathSelector": "Links/21/Hash/Links/42/Hash", - "Size": 42, - "FromLocalCAR": "string value", - "Total": "0", - "UnsealPrice": "0", - "PaymentInterval": 42, - "PaymentIntervalIncrease": 42, - "Client": "f01234", - "Miner": "f01234", - "MinerPeer": { - "Address": "f01234", - "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } - } - }, - { - "Path": "string value", - "IsCAR": true - } -] -``` - -Response: `{}` - -### ClientRetrieveTryRestartInsufficientFunds -ClientRetrieveTryRestartInsufficientFunds attempts to restart stalled retrievals on a given payment channel -which are stuck due to insufficient funds - - -Perms: write - -Inputs: -```json -[ - "f01234" -] -``` - -Response: `{}` - -### ClientRetrieveWithEvents -ClientRetrieveWithEvents initiates the retrieval of a file, as specified in the order, and provides a channel -of status updates. - - -Perms: admin - -Inputs: -```json -[ - { - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Piece": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "DatamodelPathSelector": "Links/21/Hash/Links/42/Hash", - "Size": 42, - "FromLocalCAR": "string value", - "Total": "0", - "UnsealPrice": "0", - "PaymentInterval": 42, - "PaymentIntervalIncrease": 42, - "Client": "f01234", - "Miner": "f01234", - "MinerPeer": { - "Address": "f01234", - "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } - } - }, - { - "Path": "string value", - "IsCAR": true - } -] -``` - -Response: -```json -{ - "Event": 5, - "Status": 0, - "BytesReceived": 42, - "FundsSpent": "0", - "Err": "string value" -} -``` - -### ClientStartDeal -ClientStartDeal proposes a deal with a miner. - - -Perms: admin - -Inputs: -```json -[ - { - "Data": { - "TransferType": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1024, - "RawBlockSize": 42 - }, - "Wallet": "f01234", - "Miner": "f01234", - "EpochPrice": "0", - "MinBlocksDuration": 42, - "ProviderCollateral": "0", - "DealStartEpoch": 10101, - "FastRetrieval": true, - "VerifiedDeal": true - } -] -``` - -Response: -```json -{ - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" -} -``` - -### ClientStatelessDeal -ClientStatelessDeal fire-and-forget-proposes an offline deal to a miner without subsequent tracking. - - -Perms: write - -Inputs: -```json -[ - { - "Data": { - "TransferType": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1024, - "RawBlockSize": 42 - }, - "Wallet": "f01234", - "Miner": "f01234", - "EpochPrice": "0", - "MinBlocksDuration": 42, - "ProviderCollateral": "0", - "DealStartEpoch": 10101, - "FastRetrieval": true, - "VerifiedDeal": true - } -] -``` - -Response: -```json -{ - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" -} -``` - ## Create @@ -4697,9 +3621,6 @@ Inputs: `null` Response: `"0001-01-01T00:00:00Z"` ## State -The State methods are used to query, inspect, and interact with chain state. -Most methods take a TipSetKey as a parameter. The state looked up is the parent state of the tipset. -A nil TipSetKey can be provided as a param, this will cause the heaviest tipset in the chain to be used. ### StateAccountKey @@ -4734,7 +3655,7 @@ Perms: read Inputs: ```json [ - 22 + 23 ] ``` @@ -4749,7 +3670,7 @@ Perms: read Inputs: ```json [ - 22 + 23 ] ``` @@ -5561,7 +4482,8 @@ Response: "UpgradeThunderHeight": 10101, "UpgradeWatermelonHeight": 10101, "UpgradeDragonHeight": 10101, - "UpgradePhoenixHeight": 10101 + "UpgradePhoenixHeight": 10101, + "UpgradeAussieHeight": 10101 }, "Eip155ChainID": 123 } @@ -5840,6 +4762,7 @@ Response: "ClientCollateral": "0" }, "State": { + "SectorNumber": 9, "SectorStartEpoch": 10101, "LastUpdatedEpoch": 10101, "SlashEpoch": 10101 @@ -5918,6 +4841,7 @@ Response: "ClientCollateral": "0" }, "State": { + "SectorNumber": 9, "SectorStartEpoch": 10101, "LastUpdatedEpoch": 10101, "SlashEpoch": 10101 @@ -6485,7 +5409,7 @@ Inputs: ] ``` -Response: `22` +Response: `23` ### StateReadState StateReadState returns the indicated actor's state. diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index 8dab4f35f..5951b134c 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -37,35 +37,6 @@ * [ChainSetHead](#ChainSetHead) * [ChainStatObj](#ChainStatObj) * [ChainTipSetWeight](#ChainTipSetWeight) -* [Client](#Client) - * [ClientCalcCommP](#ClientCalcCommP) - * [ClientCancelDataTransfer](#ClientCancelDataTransfer) - * [ClientCancelRetrievalDeal](#ClientCancelRetrievalDeal) - * [ClientDataTransferUpdates](#ClientDataTransferUpdates) - * [ClientDealPieceCID](#ClientDealPieceCID) - * [ClientDealSize](#ClientDealSize) - * [ClientExport](#ClientExport) - * [ClientFindData](#ClientFindData) - * [ClientGenCar](#ClientGenCar) - * [ClientGetDealInfo](#ClientGetDealInfo) - * [ClientGetDealStatus](#ClientGetDealStatus) - * [ClientGetDealUpdates](#ClientGetDealUpdates) - * [ClientGetRetrievalUpdates](#ClientGetRetrievalUpdates) - * [ClientHasLocal](#ClientHasLocal) - * [ClientImport](#ClientImport) - * [ClientListDataTransfers](#ClientListDataTransfers) - * [ClientListDeals](#ClientListDeals) - * [ClientListImports](#ClientListImports) - * [ClientListRetrievals](#ClientListRetrievals) - * [ClientMinerQueryOffer](#ClientMinerQueryOffer) - * [ClientQueryAsk](#ClientQueryAsk) - * [ClientRemoveImport](#ClientRemoveImport) - * [ClientRestartDataTransfer](#ClientRestartDataTransfer) - * [ClientRetrieve](#ClientRetrieve) - * [ClientRetrieveTryRestartInsufficientFunds](#ClientRetrieveTryRestartInsufficientFunds) - * [ClientRetrieveWait](#ClientRetrieveWait) - * [ClientStartDeal](#ClientStartDeal) - * [ClientStatelessDeal](#ClientStatelessDeal) * [Create](#Create) * [CreateBackup](#CreateBackup) * [Eth](#Eth) @@ -106,6 +77,7 @@ * [EthSyncing](#EthSyncing) * [EthTraceBlock](#EthTraceBlock) * [EthTraceReplayBlockTransactions](#EthTraceReplayBlockTransactions) + * [EthTraceTransaction](#EthTraceTransaction) * [EthUninstallFilter](#EthUninstallFilter) * [EthUnsubscribe](#EthUnsubscribe) * [Filecoin](#Filecoin) @@ -1253,1054 +1225,6 @@ Inputs: Response: `"0"` -## Client -The Client methods all have to do with interacting with the storage and -retrieval markets as a client - - -### ClientCalcCommP -ClientCalcCommP calculates the CommP for a specified file - - -Perms: write - -Inputs: -```json -[ - "string value" -] -``` - -Response: -```json -{ - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Size": 1024 -} -``` - -### ClientCancelDataTransfer -ClientCancelDataTransfer cancels a data transfer with the given transfer ID and other peer - - -Perms: write - -Inputs: -```json -[ - 3, - "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - true -] -``` - -Response: `{}` - -### ClientCancelRetrievalDeal -ClientCancelRetrievalDeal cancels an ongoing retrieval deal based on DealID - - -Perms: write - -Inputs: -```json -[ - 5 -] -``` - -Response: `{}` - -### ClientDataTransferUpdates - - -Perms: write - -Inputs: `null` - -Response: -```json -{ - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } -} -``` - -### ClientDealPieceCID -ClientCalcCommP calculates the CommP and data size of the specified CID - - -Perms: read - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: -```json -{ - "PayloadSize": 9, - "PieceSize": 1032, - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -} -``` - -### ClientDealSize -ClientDealSize calculates real deal data size - - -Perms: read - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: -```json -{ - "PayloadSize": 9, - "PieceSize": 1032 -} -``` - -### ClientExport -ClientExport exports a file stored in the local filestore to a system file - - -Perms: admin - -Inputs: -```json -[ - { - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "DAGs": [ - { - "DataSelector": "Links/21/Hash/Links/42/Hash", - "ExportMerkleProof": true - } - ], - "FromLocalCAR": "string value", - "DealID": 5 - }, - { - "Path": "string value", - "IsCAR": true - } -] -``` - -Response: `{}` - -### ClientFindData -ClientFindData identifies peers that have a certain file, and returns QueryOffers (one per peer). - - -Perms: read - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: -```json -[ - { - "Err": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Piece": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Size": 42, - "MinPrice": "0", - "UnsealPrice": "0", - "PricePerByte": "0", - "PaymentInterval": 42, - "PaymentIntervalIncrease": 42, - "Miner": "f01234", - "MinerPeer": { - "Address": "f01234", - "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } - } - } -] -``` - -### ClientGenCar -ClientGenCar generates a CAR file for the specified file. - - -Perms: write - -Inputs: -```json -[ - { - "Path": "string value", - "IsCAR": true - }, - "string value" -] -``` - -Response: `{}` - -### ClientGetDealInfo -ClientGetDealInfo returns the latest information about a given deal. - - -Perms: read - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: -```json -{ - "ProposalCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "State": 42, - "Message": "string value", - "DealStages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "ExpectedDuration": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - }, - "Provider": "f01234", - "DataRef": { - "TransferType": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1024, - "RawBlockSize": 42 - }, - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Size": 42, - "PricePerEpoch": "0", - "Duration": 42, - "DealID": 5432, - "CreationTime": "0001-01-01T00:00:00Z", - "Verified": true, - "TransferChannelID": { - "Initiator": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Responder": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "ID": 3 - }, - "DataTransfer": { - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } - } -} -``` - -### ClientGetDealStatus -ClientGetDealStatus returns status given a code - - -Perms: read - -Inputs: -```json -[ - 42 -] -``` - -Response: `"string value"` - -### ClientGetDealUpdates -ClientGetDealUpdates returns the status of updated deals - - -Perms: write - -Inputs: `null` - -Response: -```json -{ - "ProposalCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "State": 42, - "Message": "string value", - "DealStages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "ExpectedDuration": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - }, - "Provider": "f01234", - "DataRef": { - "TransferType": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1024, - "RawBlockSize": 42 - }, - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Size": 42, - "PricePerEpoch": "0", - "Duration": 42, - "DealID": 5432, - "CreationTime": "0001-01-01T00:00:00Z", - "Verified": true, - "TransferChannelID": { - "Initiator": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Responder": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "ID": 3 - }, - "DataTransfer": { - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } - } -} -``` - -### ClientGetRetrievalUpdates -ClientGetRetrievalUpdates returns status of updated retrieval deals - - -Perms: write - -Inputs: `null` - -Response: -```json -{ - "PayloadCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "ID": 5, - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PricePerByte": "0", - "UnsealPrice": "0", - "Status": 0, - "Message": "string value", - "Provider": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "BytesReceived": 42, - "BytesPaidFor": 42, - "TotalPaid": "0", - "TransferChannelID": { - "Initiator": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Responder": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "ID": 3 - }, - "DataTransfer": { - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } - }, - "Event": 5 -} -``` - -### ClientHasLocal -ClientHasLocal indicates whether a certain CID is locally stored. - - -Perms: write - -Inputs: -```json -[ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: `true` - -### ClientImport -ClientImport imports file under the specified path into filestore. - - -Perms: admin - -Inputs: -```json -[ - { - "Path": "string value", - "IsCAR": true - } -] -``` - -Response: -```json -{ - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "ImportID": 50 -} -``` - -### ClientListDataTransfers -ClientListTransfers returns the status of all ongoing transfers of data - - -Perms: write - -Inputs: `null` - -Response: -```json -[ - { - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } - } -] -``` - -### ClientListDeals -ClientListDeals returns information about the deals made by the local client. - - -Perms: write - -Inputs: `null` - -Response: -```json -[ - { - "ProposalCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "State": 42, - "Message": "string value", - "DealStages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "ExpectedDuration": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - }, - "Provider": "f01234", - "DataRef": { - "TransferType": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1024, - "RawBlockSize": 42 - }, - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Size": 42, - "PricePerEpoch": "0", - "Duration": 42, - "DealID": 5432, - "CreationTime": "0001-01-01T00:00:00Z", - "Verified": true, - "TransferChannelID": { - "Initiator": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Responder": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "ID": 3 - }, - "DataTransfer": { - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } - } - } -] -``` - -### ClientListImports -ClientListImports lists imported files and their root CIDs - - -Perms: write - -Inputs: `null` - -Response: -```json -[ - { - "Key": 50, - "Err": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Source": "string value", - "FilePath": "string value", - "CARPath": "string value" - } -] -``` - -### ClientListRetrievals -ClientListRetrievals returns information about retrievals made by the local client - - -Perms: write - -Inputs: `null` - -Response: -```json -[ - { - "PayloadCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "ID": 5, - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PricePerByte": "0", - "UnsealPrice": "0", - "Status": 0, - "Message": "string value", - "Provider": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "BytesReceived": 42, - "BytesPaidFor": 42, - "TotalPaid": "0", - "TransferChannelID": { - "Initiator": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Responder": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "ID": 3 - }, - "DataTransfer": { - "TransferID": 3, - "Status": 1, - "BaseCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "IsInitiator": true, - "IsSender": true, - "Voucher": "string value", - "Message": "string value", - "OtherPeer": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "Transferred": 42, - "Stages": { - "Stages": [ - { - "Name": "string value", - "Description": "string value", - "CreatedTime": "0001-01-01T00:00:00Z", - "UpdatedTime": "0001-01-01T00:00:00Z", - "Logs": [ - { - "Log": "string value", - "UpdatedTime": "0001-01-01T00:00:00Z" - } - ] - } - ] - } - }, - "Event": 5 - } -] -``` - -### ClientMinerQueryOffer -ClientMinerQueryOffer returns a QueryOffer for the specific miner and file. - - -Perms: read - -Inputs: -```json -[ - "f01234", - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } -] -``` - -Response: -```json -{ - "Err": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Piece": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Size": 42, - "MinPrice": "0", - "UnsealPrice": "0", - "PricePerByte": "0", - "PaymentInterval": 42, - "PaymentIntervalIncrease": 42, - "Miner": "f01234", - "MinerPeer": { - "Address": "f01234", - "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } - } -} -``` - -### ClientQueryAsk -ClientQueryAsk returns a signed StorageAsk from the specified miner. - - -Perms: read - -Inputs: -```json -[ - "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "f01234" -] -``` - -Response: -```json -{ - "Response": { - "Price": "0", - "VerifiedPrice": "0", - "MinPieceSize": 1032, - "MaxPieceSize": 1032, - "Miner": "f01234", - "Timestamp": 10101, - "Expiry": 10101, - "SeqNo": 42 - }, - "DealProtocols": [ - "string value" - ] -} -``` - -### ClientRemoveImport -ClientRemoveImport removes file import - - -Perms: admin - -Inputs: -```json -[ - 50 -] -``` - -Response: `{}` - -### ClientRestartDataTransfer -ClientRestartDataTransfer attempts to restart a data transfer with the given transfer ID and other peer - - -Perms: write - -Inputs: -```json -[ - 3, - "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - true -] -``` - -Response: `{}` - -### ClientRetrieve -ClientRetrieve initiates the retrieval of a file, as specified in the order. - - -Perms: admin - -Inputs: -```json -[ - { - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Piece": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "DataSelector": "Links/21/Hash/Links/42/Hash", - "Size": 42, - "Total": "0", - "UnsealPrice": "0", - "PaymentInterval": 42, - "PaymentIntervalIncrease": 42, - "Client": "f01234", - "Miner": "f01234", - "MinerPeer": { - "Address": "f01234", - "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - } - }, - "RemoteStore": "00000000-0000-0000-0000-000000000000" - } -] -``` - -Response: -```json -{ - "DealID": 5 -} -``` - -### ClientRetrieveTryRestartInsufficientFunds -ClientRetrieveTryRestartInsufficientFunds attempts to restart stalled retrievals on a given payment channel -which are stuck due to insufficient funds - - -Perms: write - -Inputs: -```json -[ - "f01234" -] -``` - -Response: `{}` - -### ClientRetrieveWait -ClientRetrieveWait waits for retrieval to be complete - - -Perms: admin - -Inputs: -```json -[ - 5 -] -``` - -Response: `{}` - -### ClientStartDeal -ClientStartDeal proposes a deal with a miner. - - -Perms: admin - -Inputs: -```json -[ - { - "Data": { - "TransferType": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1024, - "RawBlockSize": 42 - }, - "Wallet": "f01234", - "Miner": "f01234", - "EpochPrice": "0", - "MinBlocksDuration": 42, - "ProviderCollateral": "0", - "DealStartEpoch": 10101, - "FastRetrieval": true, - "VerifiedDeal": true - } -] -``` - -Response: -```json -{ - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" -} -``` - -### ClientStatelessDeal -ClientStatelessDeal fire-and-forget-proposes an offline deal to a miner without subsequent tracking. - - -Perms: write - -Inputs: -```json -[ - { - "Data": { - "TransferType": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceCid": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceSize": 1024, - "RawBlockSize": 42 - }, - "Wallet": "f01234", - "Miner": "f01234", - "EpochPrice": "0", - "MinBlocksDuration": 42, - "ProviderCollateral": "0", - "DealStartEpoch": 10101, - "FastRetrieval": true, - "VerifiedDeal": true - } -] -``` - -Response: -```json -{ - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" -} -``` - ## Create @@ -2728,6 +1652,7 @@ Response: "gas": "0x5", "maxFeePerGas": "0x0", "maxPriorityFeePerGas": "0x0", + "gasPrice": "0x0", "accessList": [ "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" ], @@ -2767,6 +1692,7 @@ Response: "gas": "0x5", "maxFeePerGas": "0x0", "maxPriorityFeePerGas": "0x0", + "gasPrice": "0x0", "accessList": [ "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" ], @@ -2805,6 +1731,7 @@ Response: "gas": "0x5", "maxFeePerGas": "0x0", "maxPriorityFeePerGas": "0x0", + "gasPrice": "0x0", "accessList": [ "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" ], @@ -2844,6 +1771,7 @@ Response: "gas": "0x5", "maxFeePerGas": "0x0", "maxPriorityFeePerGas": "0x0", + "gasPrice": "0x0", "accessList": [ "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" ], @@ -3171,6 +2099,39 @@ Response: ] ``` +### EthTraceTransaction +Implmements OpenEthereum-compatible API method trace_transaction + + +Perms: read + +Inputs: +```json +[ + "string value" +] +``` + +Response: +```json +[ + { + "type": "string value", + "error": "string value", + "subtraces": 123, + "traceAddress": [ + 123 + ], + "action": {}, + "result": {}, + "blockHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "blockNumber": 9, + "transactionHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "transactionPosition": 123 + } +] +``` + ### EthUninstallFilter Uninstalls a filter with given id. @@ -6298,7 +5259,7 @@ Perms: read Inputs: ```json [ - 22 + 23 ] ``` @@ -6313,7 +5274,7 @@ Perms: read Inputs: ```json [ - 22 + 23 ] ``` @@ -7223,7 +6184,8 @@ Response: "UpgradeThunderHeight": 10101, "UpgradeWatermelonHeight": 10101, "UpgradeDragonHeight": 10101, - "UpgradePhoenixHeight": 10101 + "UpgradePhoenixHeight": 10101, + "UpgradeAussieHeight": 10101 }, "Eip155ChainID": 123 } @@ -7528,6 +6490,7 @@ Response: "ClientCollateral": "0" }, "State": { + "SectorNumber": 9, "SectorStartEpoch": 10101, "LastUpdatedEpoch": 10101, "SlashEpoch": 10101 @@ -7606,6 +6569,7 @@ Response: "ClientCollateral": "0" }, "State": { + "SectorNumber": 9, "SectorStartEpoch": 10101, "LastUpdatedEpoch": 10101, "SlashEpoch": 10101 @@ -8201,7 +7165,7 @@ Inputs: ] ``` -Response: `22` +Response: `23` ### StateReadState StateReadState returns the indicated actor's state. diff --git a/documentation/en/cli-curio.md b/documentation/en/cli-curio.md deleted file mode 100644 index 9f578154c..000000000 --- a/documentation/en/cli-curio.md +++ /dev/null @@ -1,563 +0,0 @@ -# curio -``` -NAME: - curio - Filecoin decentralized storage network provider - -USAGE: - curio [global options] command [command options] [arguments...] - -VERSION: - 1.27.0 - -COMMANDS: - cli Execute cli commands - run Start a Curio process - stop Stop a running Curio process - config Manage node config by layers. The layer 'base' will always be applied at Curio start-up. - test Utility functions for testing - web Start Curio web interface - guided-setup Run the guided setup for migrating from lotus-miner to Curio or Creating a new Curio miner - seal Manage the sealing pipeline - market - fetch-params Fetch proving parameters - help, h Shows a list of commands or help for one command - -GLOBAL OPTIONS: - --color use color in display output (default: depends on output being a TTY) - --db-host value Command separated list of hostnames for yugabyte cluster (default: "127.0.0.1") [$CURIO_DB_HOST, $CURIO_HARMONYDB_HOSTS] - --db-name value (default: "yugabyte") [$CURIO_DB_NAME, $CURIO_HARMONYDB_NAME] - --db-user value (default: "yugabyte") [$CURIO_DB_USER, $CURIO_HARMONYDB_USERNAME] - --db-password value (default: "yugabyte") [$CURIO_DB_PASSWORD, $CURIO_HARMONYDB_PASSWORD] - --db-port value (default: "5433") [$CURIO_DB_PORT, $CURIO_HARMONYDB_PORT] - --repo-path value (default: "~/.curio") [$CURIO_REPO_PATH] - --vv enables very verbose mode, useful for debugging the CLI (default: false) - --help, -h show help - --version, -v print the version -``` - -## curio cli -``` -NAME: - curio cli - Execute cli commands - -USAGE: - curio cli command [command options] [arguments...] - -COMMANDS: - storage manage sector storage - log Manage logging - wait-api Wait for Curio api to come online - help, h Shows a list of commands or help for one command - -OPTIONS: - --machine value machine host:port (curio run --listen address) - --help, -h show help -``` - -### curio cli storage -``` -NAME: - curio cli storage - manage sector storage - -USAGE: - curio cli storage command [command options] [arguments...] - -DESCRIPTION: - Sectors can be stored across many filesystem paths. These - commands provide ways to manage the storage the miner will used to store sectors - long term for proving (references as 'store') as well as how sectors will be - stored while moving through the sealing pipeline (references as 'seal'). - -COMMANDS: - attach attach local storage path - detach detach local storage path - list list local storage paths - find find sector in the storage system - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -#### curio cli storage attach -``` -NAME: - curio cli storage attach - attach local storage path - -USAGE: - curio cli storage attach [command options] [path] - -DESCRIPTION: - Storage can be attached to the miner using this command. The storage volume - list is stored local to the miner in storage.json set in curio run. We do not - recommend manually modifying this value without further understanding of the - storage system. - - Each storage volume contains a configuration file which describes the - capabilities of the volume. When the '--init' flag is provided, this file will - be created using the additional flags. - - Weight - A high weight value means data will be more likely to be stored in this path - - Seal - Data for the sealing process will be stored here - - Store - Finalized sectors that will be moved here for long term storage and be proven - over time - - -OPTIONS: - --init initialize the path first (default: false) - --weight value (for init) path weight (default: 10) - --seal (for init) use path for sealing (default: false) - --store (for init) use path for long-term storage (default: false) - --max-storage value (for init) limit storage space for sectors (expensive for very large paths!) - --groups value [ --groups value ] path group names - --allow-to value [ --allow-to value ] path groups allowed to pull data from this path (allow all if not specified) - --help, -h show help -``` - -#### curio cli storage detach -``` -NAME: - curio cli storage detach - detach local storage path - -USAGE: - curio cli storage detach [command options] [path] - -OPTIONS: - --really-do-it (default: false) - --help, -h show help -``` - -#### curio cli storage list -``` -NAME: - curio cli storage list - list local storage paths - -USAGE: - curio cli storage list [command options] [arguments...] - -OPTIONS: - --local only list local storage paths (default: false) - --help, -h show help -``` - -#### curio cli storage find -``` -NAME: - curio cli storage find - find sector in the storage system - -USAGE: - curio cli storage find [command options] [miner address] [sector number] - -OPTIONS: - --help, -h show help -``` - -### curio cli log -``` -NAME: - curio cli log - Manage logging - -USAGE: - curio cli log command [command options] [arguments...] - -COMMANDS: - list List log systems - set-level Set log level - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -#### curio cli log list -``` -NAME: - curio cli log list - List log systems - -USAGE: - curio cli log list [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -#### curio cli log set-level -``` -NAME: - curio cli log set-level - Set log level - -USAGE: - curio cli log set-level [command options] [level] - -DESCRIPTION: - Set the log level for logging systems: - - The system flag can be specified multiple times. - - eg) log set-level --system chain --system chainxchg debug - - Available Levels: - debug - info - warn - error - - Environment Variables: - GOLOG_LOG_LEVEL - Default log level for all log systems - GOLOG_LOG_FMT - Change output log format (json, nocolor) - GOLOG_FILE - Write logs to file - GOLOG_OUTPUT - Specify whether to output to file, stderr, stdout or a combination, i.e. file+stderr - - -OPTIONS: - --system value [ --system value ] limit to log system - --help, -h show help -``` - -### curio cli wait-api -``` -NAME: - curio cli wait-api - Wait for Curio api to come online - -USAGE: - curio cli wait-api [command options] [arguments...] - -OPTIONS: - --timeout value duration to wait till fail (default: 30s) - --help, -h show help -``` - -## curio run -``` -NAME: - curio run - Start a Curio process - -USAGE: - curio run [command options] [arguments...] - -OPTIONS: - --listen value host address and port the worker api will listen on (default: "0.0.0.0:12300") [$LOTUS_WORKER_LISTEN] - --nosync don't check full-node sync status (default: false) - --manage-fdlimit manage open file limit (default: true) - --storage-json value path to json file containing storage config (default: "~/.curio/storage.json") - --journal value path to journal files (default: "~/.curio/") - --layers value, -l value, --layer value [ --layers value, -l value, --layer value ] list of layers to be interpreted (atop defaults). Default: base - --help, -h show help -``` - -## curio stop -``` -NAME: - curio stop - Stop a running Curio process - -USAGE: - curio stop [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -## curio config -``` -NAME: - curio config - Manage node config by layers. The layer 'base' will always be applied at Curio start-up. - -USAGE: - curio config command [command options] [arguments...] - -COMMANDS: - default, defaults Print default node config - set, add, update, create Set a config layer or the base by providing a filename or stdin. - get, cat, show Get a config layer by name. You may want to pipe the output to a file, or use 'less' - list, ls List config layers present in the DB. - interpret, view, stacked, stack Interpret stacked config layers by this version of curio, with system-generated comments. - remove, rm, del, delete Remove a named config layer. - edit edit a config layer - new-cluster Create new configuration for a new cluster - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -### curio config default -``` -NAME: - curio config default - Print default node config - -USAGE: - curio config default [command options] [arguments...] - -OPTIONS: - --no-comment don't comment default values (default: false) - --help, -h show help -``` - -### curio config set -``` -NAME: - curio config set - Set a config layer or the base by providing a filename or stdin. - -USAGE: - curio config set [command options] a layer's file name - -OPTIONS: - --title value title of the config layer (req'd for stdin) - --help, -h show help -``` - -### curio config get -``` -NAME: - curio config get - Get a config layer by name. You may want to pipe the output to a file, or use 'less' - -USAGE: - curio config get [command options] layer name - -OPTIONS: - --help, -h show help -``` - -### curio config list -``` -NAME: - curio config list - List config layers present in the DB. - -USAGE: - curio config list [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -### curio config interpret -``` -NAME: - curio config interpret - Interpret stacked config layers by this version of curio, with system-generated comments. - -USAGE: - curio config interpret [command options] a list of layers to be interpreted as the final config - -OPTIONS: - --layers value [ --layers value ] comma or space separated list of layers to be interpreted (base is always applied) - --help, -h show help -``` - -### curio config remove -``` -NAME: - curio config remove - Remove a named config layer. - -USAGE: - curio config remove [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -### curio config edit -``` -NAME: - curio config edit - edit a config layer - -USAGE: - curio config edit [command options] [layer name] - -OPTIONS: - --editor value editor to use (default: "vim") [$EDITOR] - --source value source config layer (default: ) - --allow-overwrite allow overwrite of existing layer if source is a different layer (default: false) - --no-source-diff save the whole config into the layer, not just the diff (default: false) - --no-interpret-source do not interpret source layer (default: true if --source is set) - --help, -h show help -``` - -### curio config new-cluster -``` -NAME: - curio config new-cluster - Create new configuration for a new cluster - -USAGE: - curio config new-cluster [command options] [SP actor address...] - -OPTIONS: - --help, -h show help -``` - -## curio test -``` -NAME: - curio test - Utility functions for testing - -USAGE: - curio test command [command options] [arguments...] - -COMMANDS: - window-post, wd, windowpost, wdpost Compute a proof-of-spacetime for a sector (requires the sector to be pre-sealed). These will not send to the chain. - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -### curio test window-post -``` -NAME: - curio test window-post - Compute a proof-of-spacetime for a sector (requires the sector to be pre-sealed). These will not send to the chain. - -USAGE: - curio test window-post command [command options] [arguments...] - -COMMANDS: - here, cli Compute WindowPoSt for performance and configuration testing. - task, scheduled, schedule, async, asynchronous Test the windowpost scheduler by running it on the next available curio. - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -#### curio test window-post here -``` -NAME: - curio test window-post here - Compute WindowPoSt for performance and configuration testing. - -USAGE: - curio test window-post here [command options] [deadline index] - -DESCRIPTION: - Note: This command is intended to be used to verify PoSt compute performance. - It will not send any messages to the chain. Since it can compute any deadline, output may be incorrectly timed for the chain. - -OPTIONS: - --deadline value deadline to compute WindowPoSt for (default: 0) - --layers value [ --layers value ] list of layers to be interpreted (atop defaults). Default: base - --storage-json value path to json file containing storage config (default: "~/.curio/storage.json") - --partition value partition to compute WindowPoSt for (default: 0) - --help, -h show help -``` - -#### curio test window-post task -``` -NAME: - curio test window-post task - Test the windowpost scheduler by running it on the next available curio. - -USAGE: - curio test window-post task [command options] [arguments...] - -OPTIONS: - --deadline value deadline to compute WindowPoSt for (default: 0) - --layers value [ --layers value ] list of layers to be interpreted (atop defaults). Default: base - --help, -h show help -``` - -## curio web -``` -NAME: - curio web - Start Curio web interface - -USAGE: - curio web [command options] [arguments...] - -DESCRIPTION: - Start an instance of Curio web interface. - This creates the 'web' layer if it does not exist, then calls run with that layer. - -OPTIONS: - --listen value Address to listen on (default: "127.0.0.1:4701") - --nosync don't check full-node sync status (default: false) - --layers value [ --layers value ] list of layers to be interpreted (atop defaults). Default: base - --help, -h show help -``` - -## curio guided-setup -``` -NAME: - curio guided-setup - Run the guided setup for migrating from lotus-miner to Curio or Creating a new Curio miner - -USAGE: - curio guided-setup [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -## curio seal -``` -NAME: - curio seal - Manage the sealing pipeline - -USAGE: - curio seal command [command options] [arguments...] - -COMMANDS: - start Start new sealing operations manually - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -### curio seal start -``` -NAME: - curio seal start - Start new sealing operations manually - -USAGE: - curio seal start [command options] [arguments...] - -OPTIONS: - --actor value Specify actor address to start sealing sectors for - --now Start sealing sectors for all actors now (not on schedule) (default: false) - --cc Start sealing new CC sectors (default: false) - --count value Number of sectors to start (default: 1) - --synthetic Use synthetic PoRep (default: false) - --layers value [ --layers value ] list of layers to be interpreted (atop defaults). Default: base - --help, -h show help -``` - -## curio market -``` -NAME: - curio market - -USAGE: - curio market command [command options] [arguments...] - -COMMANDS: - rpc-info - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -### curio market rpc-info -``` -NAME: - curio market rpc-info - -USAGE: - curio market rpc-info [command options] [arguments...] - -OPTIONS: - --layers value [ --layers value ] list of layers to be interpreted (atop defaults). Default: base - --help, -h show help -``` - -## curio fetch-params -``` -NAME: - curio fetch-params - Fetch proving parameters - -USAGE: - curio fetch-params [command options] [sectorSize] - -OPTIONS: - --help, -h show help -``` diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index b54508224..d8f15e823 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.27.0 + 1.27.1-rc1 COMMANDS: init Initialize a lotus miner repo @@ -49,7 +49,6 @@ USAGE: COMMANDS: restore Initialize a lotus miner repo from a backup - service Initialize a lotus miner sub-service help, h Shows a list of commands or help for one command OPTIONS: @@ -84,23 +83,6 @@ OPTIONS: --help, -h show help ``` -### lotus-miner init service -``` -NAME: - lotus-miner init service - Initialize a lotus miner sub-service - -USAGE: - lotus-miner init service [command options] [backupFile] - -OPTIONS: - --config value config file (config.toml) - --nosync don't check full-node sync status (default: false) - --type value [ --type value ] type of service to be enabled - --api-sealer value sealer API info (lotus-miner auth api-info --perm=admin) - --api-sector-index value sector Index API info (lotus-miner auth api-info --perm=admin) - --help, -h show help -``` - ## lotus-miner run ``` NAME: diff --git a/documentation/en/cli-lotus-provider.md b/documentation/en/cli-lotus-provider.md deleted file mode 100644 index 0fae817d2..000000000 --- a/documentation/en/cli-lotus-provider.md +++ /dev/null @@ -1,410 +0,0 @@ -# lotus-provider -``` -NAME: - lotus-provider - Filecoin decentralized storage network provider - -USAGE: - lotus-provider [global options] command [command options] [arguments...] - -VERSION: - 1.26.3 - -COMMANDS: - run Start a lotus provider process - stop Stop a running lotus provider - config Manage node config by layers. The layer 'base' will always be applied. - test Utility functions for testing - version Print version - help, h Shows a list of commands or help for one command - DEVELOPER: - auth Manage RPC permissions - log Manage logging - wait-api Wait for lotus api to come online - fetch-params Fetch proving parameters - -GLOBAL OPTIONS: - --color use color in display output (default: depends on output being a TTY) - --db-host value Command separated list of hostnames for yugabyte cluster (default: "yugabyte") [$LOTUS_DB_HOST] - --db-name value (default: "yugabyte") [$LOTUS_DB_NAME, $LOTUS_HARMONYDB_HOSTS] - --db-user value (default: "yugabyte") [$LOTUS_DB_USER, $LOTUS_HARMONYDB_USERNAME] - --db-password value (default: "yugabyte") [$LOTUS_DB_PASSWORD, $LOTUS_HARMONYDB_PASSWORD] - --layers value (default: "base") [$LOTUS_LAYERS, $LOTUS_CONFIG_LAYERS] - --repo-path value (default: "~/.lotusprovider") [$LOTUS_REPO_PATH] - --vv enables very verbose mode, useful for debugging the CLI (default: false) - --help, -h show help - --version, -v print the version -``` - -## lotus-provider run -``` -NAME: - lotus-provider run - Start a lotus provider process - -USAGE: - lotus-provider run [command options] [arguments...] - -OPTIONS: - --listen value host address and port the worker api will listen on (default: "0.0.0.0:12300") [$LOTUS_WORKER_LISTEN] - --nosync don't check full-node sync status (default: false) - --manage-fdlimit manage open file limit (default: true) - --layers value [ --layers value ] list of layers to be interpreted (atop defaults). Default: base (default: "base") - --storage-json value path to json file containing storage config (default: "~/.lotus-provider/storage.json") - --journal value path to journal files (default: "~/.lotus-provider/") - --help, -h show help -``` - -## lotus-provider stop -``` -NAME: - lotus-provider stop - Stop a running lotus provider - -USAGE: - lotus-provider stop [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -## lotus-provider config -``` -NAME: - lotus-provider config - Manage node config by layers. The layer 'base' will always be applied. - -USAGE: - lotus-provider config command [command options] [arguments...] - -COMMANDS: - default, defaults Print default node config - set, add, update, create Set a config layer or the base by providing a filename or stdin. - get, cat, show Get a config layer by name. You may want to pipe the output to a file, or use 'less' - list, ls List config layers you can get. - interpret, view, stacked, stack Interpret stacked config layers by this version of lotus-provider, with system-generated comments. - remove, rm, del, delete Remove a named config layer. - from-miner Express a database config (for lotus-provider) from an existing miner. - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -### lotus-provider config default -``` -NAME: - lotus-provider config default - Print default node config - -USAGE: - lotus-provider config default [command options] [arguments...] - -OPTIONS: - --no-comment don't comment default values (default: false) - --help, -h show help -``` - -### lotus-provider config set -``` -NAME: - lotus-provider config set - Set a config layer or the base by providing a filename or stdin. - -USAGE: - lotus-provider config set [command options] a layer's file name - -OPTIONS: - --title value title of the config layer (req'd for stdin) - --help, -h show help -``` - -### lotus-provider config get -``` -NAME: - lotus-provider config get - Get a config layer by name. You may want to pipe the output to a file, or use 'less' - -USAGE: - lotus-provider config get [command options] layer name - -OPTIONS: - --help, -h show help -``` - -### lotus-provider config list -``` -NAME: - lotus-provider config list - List config layers you can get. - -USAGE: - lotus-provider config list [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -### lotus-provider config interpret -``` -NAME: - lotus-provider config interpret - Interpret stacked config layers by this version of lotus-provider, with system-generated comments. - -USAGE: - lotus-provider config interpret [command options] a list of layers to be interpreted as the final config - -OPTIONS: - --layers value [ --layers value ] comma or space separated list of layers to be interpreted (default: "base") - --help, -h show help -``` - -### lotus-provider config remove -``` -NAME: - lotus-provider config remove - Remove a named config layer. - -USAGE: - lotus-provider config remove [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -### lotus-provider config from-miner -``` -NAME: - lotus-provider config from-miner - Express a database config (for lotus-provider) from an existing miner. - -USAGE: - lotus-provider config from-miner [command options] [arguments...] - -DESCRIPTION: - Express a database config (for lotus-provider) from an existing miner. - -OPTIONS: - --miner-repo value, --storagerepo value Specify miner repo path. flag(storagerepo) and env(LOTUS_STORAGE_PATH) are DEPRECATION, will REMOVE SOON (default: "~/.lotusminer") [$LOTUS_MINER_PATH, $LOTUS_STORAGE_PATH] - --to-layer value, -t value The layer name for this data push. 'base' is recommended for single-miner setup. - --overwrite, -o Use this with --to-layer to replace an existing layer (default: false) - --help, -h show help -``` - -## lotus-provider test -``` -NAME: - lotus-provider test - Utility functions for testing - -USAGE: - lotus-provider test command [command options] [arguments...] - -COMMANDS: - window-post, wd, windowpost, wdpost Compute a proof-of-spacetime for a sector (requires the sector to be pre-sealed). These will not send to the chain. - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -### lotus-provider test window-post -``` -NAME: - lotus-provider test window-post - Compute a proof-of-spacetime for a sector (requires the sector to be pre-sealed). These will not send to the chain. - -USAGE: - lotus-provider test window-post command [command options] [arguments...] - -COMMANDS: - here, cli Compute WindowPoSt for performance and configuration testing. - task, scheduled, schedule, async, asynchronous Test the windowpost scheduler by running it on the next available lotus-provider. - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -#### lotus-provider test window-post here -``` -NAME: - lotus-provider test window-post here - Compute WindowPoSt for performance and configuration testing. - -USAGE: - lotus-provider test window-post here [command options] [deadline index] - -DESCRIPTION: - Note: This command is intended to be used to verify PoSt compute performance. - It will not send any messages to the chain. Since it can compute any deadline, output may be incorrectly timed for the chain. - -OPTIONS: - --deadline value deadline to compute WindowPoSt for (default: 0) - --layers value [ --layers value ] list of layers to be interpreted (atop defaults). Default: base (default: "base") - --storage-json value path to json file containing storage config (default: "~/.lotus-provider/storage.json") - --partition value partition to compute WindowPoSt for (default: 0) - --help, -h show help -``` - -#### lotus-provider test window-post task -``` -NAME: - lotus-provider test window-post task - Test the windowpost scheduler by running it on the next available lotus-provider. - -USAGE: - lotus-provider test window-post task [command options] [arguments...] - -OPTIONS: - --deadline value deadline to compute WindowPoSt for (default: 0) - --layers value [ --layers value ] list of layers to be interpreted (atop defaults). Default: base (default: "base") - --help, -h show help -``` - -## lotus-provider version -``` -NAME: - lotus-provider version - Print version - -USAGE: - lotus-provider version [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -## lotus-provider auth -``` -NAME: - lotus-provider auth - Manage RPC permissions - -USAGE: - lotus-provider auth command [command options] [arguments...] - -COMMANDS: - create-token Create token - api-info Get token with API info required to connect to this node - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -### lotus-provider auth create-token -``` -NAME: - lotus-provider auth create-token - Create token - -USAGE: - lotus-provider auth create-token [command options] [arguments...] - -OPTIONS: - --perm value permission to assign to the token, one of: read, write, sign, admin - --help, -h show help -``` - -### lotus-provider auth api-info -``` -NAME: - lotus-provider auth api-info - Get token with API info required to connect to this node - -USAGE: - lotus-provider auth api-info [command options] [arguments...] - -OPTIONS: - --perm value permission to assign to the token, one of: read, write, sign, admin - --help, -h show help -``` - -## lotus-provider log -``` -NAME: - lotus-provider log - Manage logging - -USAGE: - lotus-provider log command [command options] [arguments...] - -COMMANDS: - list List log systems - set-level Set log level - alerts Get alert states - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -### lotus-provider log list -``` -NAME: - lotus-provider log list - List log systems - -USAGE: - lotus-provider log list [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -### lotus-provider log set-level -``` -NAME: - lotus-provider log set-level - Set log level - -USAGE: - lotus-provider log set-level [command options] [level] - -DESCRIPTION: - Set the log level for logging systems: - - The system flag can be specified multiple times. - - eg) log set-level --system chain --system chainxchg debug - - Available Levels: - debug - info - warn - error - - Environment Variables: - GOLOG_LOG_LEVEL - Default log level for all log systems - GOLOG_LOG_FMT - Change output log format (json, nocolor) - GOLOG_FILE - Write logs to file - GOLOG_OUTPUT - Specify whether to output to file, stderr, stdout or a combination, i.e. file+stderr - - -OPTIONS: - --system value [ --system value ] limit to log system - --help, -h show help -``` - -### lotus-provider log alerts -``` -NAME: - lotus-provider log alerts - Get alert states - -USAGE: - lotus-provider log alerts [command options] [arguments...] - -OPTIONS: - --all get all (active and inactive) alerts (default: false) - --help, -h show help -``` - -## lotus-provider wait-api -``` -NAME: - lotus-provider wait-api - Wait for lotus api to come online - -USAGE: - lotus-provider wait-api [command options] [arguments...] - -CATEGORY: - DEVELOPER - -OPTIONS: - --timeout value duration to wait till fail (default: 30s) - --help, -h show help -``` - -## lotus-provider fetch-params -``` -NAME: - lotus-provider fetch-params - Fetch proving parameters - -USAGE: - lotus-provider fetch-params [command options] [sectorSize] - -CATEGORY: - DEVELOPER - -OPTIONS: - --help, -h show help -``` diff --git a/documentation/en/cli-lotus-worker.md b/documentation/en/cli-lotus-worker.md index 441ba14ab..af55aea04 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.27.0 + 1.27.1-rc1 COMMANDS: run Start lotus worker diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index a501dd732..b3bfea70c 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.27.0 + 1.27.1-rc1 COMMANDS: daemon Start a lotus daemon process @@ -19,7 +19,6 @@ COMMANDS: send Send funds between accounts wallet Manage wallet info Print node info - client Make deals, store data, retrieve data msig Interact with a multisig wallet filplus Interact with the verified registry actor used by Filplus paych Manage payment channels @@ -403,515 +402,6 @@ OPTIONS: --help, -h show help ``` -## lotus client -``` -NAME: - lotus client - Make deals, store data, retrieve data - -USAGE: - lotus client command [command options] [arguments...] - -COMMANDS: - help, h Shows a list of commands or help for one command - DATA: - import Import data - drop Remove import - local List locally imported data - stat Print information about a locally stored file (piece size, etc) - RETRIEVAL: - find Find data in the network - retrieval-ask Get a miner's retrieval ask - retrieve Retrieve data from network - cat Show data from network - ls List object links - cancel-retrieval Cancel a retrieval deal by deal ID; this also cancels the associated transfer - list-retrievals List retrieval market deals - STORAGE: - deal Initialize storage deal with a miner - query-ask Find a miners ask - list-deals List storage market deals - get-deal Print detailed deal information - list-asks List asks for top miners - deal-stats Print statistics about local storage deals - inspect-deal Inspect detailed information about deal's lifecycle and the various stages it goes through - UTIL: - commP Calculate the piece-cid (commP) of a CAR file - generate-car Generate a car file from input - balances Print storage market client balances - list-transfers List ongoing data transfers for deals - restart-transfer Force restart a stalled data transfer - cancel-transfer Force cancel a data transfer - -OPTIONS: - --help, -h show help -``` - -### lotus client import -``` -NAME: - lotus client import - Import data - -USAGE: - lotus client import [command options] [inputPath] - -CATEGORY: - DATA - -OPTIONS: - --car import from a car file instead of a regular file (default: false) - --quiet, -q Output root CID only (default: false) - --help, -h show help -``` - -### lotus client drop -``` -NAME: - lotus client drop - Remove import - -USAGE: - lotus client drop [command options] [import ID...] - -CATEGORY: - DATA - -OPTIONS: - --help, -h show help -``` - -### lotus client local -``` -NAME: - lotus client local - List locally imported data - -USAGE: - lotus client local [command options] [arguments...] - -CATEGORY: - DATA - -OPTIONS: - --help, -h show help -``` - -### lotus client stat -``` -NAME: - lotus client stat - Print information about a locally stored file (piece size, etc) - -USAGE: - lotus client stat [command options] - -CATEGORY: - DATA - -OPTIONS: - --help, -h show help -``` - -### lotus client find -``` -NAME: - lotus client find - Find data in the network - -USAGE: - lotus client find [command options] [dataCid] - -CATEGORY: - RETRIEVAL - -OPTIONS: - --pieceCid value require data to be retrieved from a specific Piece CID - --help, -h show help -``` - -### lotus client retrieval-ask -``` -NAME: - lotus client retrieval-ask - Get a miner's retrieval ask - -USAGE: - lotus client retrieval-ask [command options] [minerAddress] [data CID] - -CATEGORY: - RETRIEVAL - -OPTIONS: - --size value data size in bytes (default: 0) - --help, -h show help -``` - -### lotus client retrieve -``` -NAME: - lotus client retrieve - Retrieve data from network - -USAGE: - lotus client retrieve [command options] [dataCid outputPath] - -CATEGORY: - RETRIEVAL - -DESCRIPTION: - Retrieve data from the Filecoin network. - - The retrieve command will attempt to find a provider make a retrieval deal with - them. In case a provider can't be found, it can be specified with the --provider - flag. - - By default the data will be interpreted as DAG-PB UnixFSv1 File. Alternatively - a CAR file containing the raw IPLD graph can be exported by setting the --car - flag. - - Partial Retrieval: - - The --data-selector flag can be used to specify a sub-graph to fetch. The - selector can be specified as either IPLD datamodel text-path selector, or IPLD - json selector. - - In case of unixfs retrieval, the selector must point at a single root node, and - match the entire graph under that node. - - In case of CAR retrieval, the selector must have one common "sub-root" node. - - Examples: - - - Retrieve a file by CID - $ lotus client retrieve Qm... my-file.txt - - - Retrieve a file by CID from f0123 - $ lotus client retrieve --provider f0123 Qm... my-file.txt - - - Retrieve a first file from a specified directory - $ lotus client retrieve --data-selector /Links/0/Hash Qm... my-file.txt - - -OPTIONS: - --car Export to a car file instead of a regular file (default: false) - --data-selector value, --datamodel-path-selector value IPLD datamodel text-path selector, or IPLD json selector - --car-export-merkle-proof (requires --data-selector and --car) Export data-selector merkle proof (default: false) - --from value address to send transactions from - --provider value, --miner value provider to use for retrieval, if not present it'll use local discovery - --maxPrice value maximum price the client is willing to consider (default: 0 FIL) - --pieceCid value require data to be retrieved from a specific Piece CID - --allow-local (default: false) - --help, -h show help -``` - -### lotus client cat -``` -NAME: - lotus client cat - Show data from network - -USAGE: - lotus client cat [command options] [dataCid] - -CATEGORY: - RETRIEVAL - -OPTIONS: - --ipld list IPLD datamodel links (default: false) - --data-selector value IPLD datamodel text-path selector, or IPLD json selector - --from value address to send transactions from - --provider value, --miner value provider to use for retrieval, if not present it'll use local discovery - --maxPrice value maximum price the client is willing to consider (default: 0 FIL) - --pieceCid value require data to be retrieved from a specific Piece CID - --allow-local (default: false) - --help, -h show help -``` - -### lotus client ls -``` -NAME: - lotus client ls - List object links - -USAGE: - lotus client ls [command options] [dataCid] - -CATEGORY: - RETRIEVAL - -OPTIONS: - --ipld list IPLD datamodel links (default: false) - --depth value list links recursively up to the specified depth (default: 1) - --data-selector value IPLD datamodel text-path selector, or IPLD json selector - --from value address to send transactions from - --provider value, --miner value provider to use for retrieval, if not present it'll use local discovery - --maxPrice value maximum price the client is willing to consider (default: 0 FIL) - --pieceCid value require data to be retrieved from a specific Piece CID - --allow-local (default: false) - --help, -h show help -``` - -### lotus client cancel-retrieval -``` -NAME: - lotus client cancel-retrieval - Cancel a retrieval deal by deal ID; this also cancels the associated transfer - -USAGE: - lotus client cancel-retrieval [command options] [arguments...] - -CATEGORY: - RETRIEVAL - -OPTIONS: - --deal-id value specify retrieval deal by deal ID (default: 0) - --help, -h show help -``` - -### lotus client list-retrievals -``` -NAME: - lotus client list-retrievals - List retrieval market deals - -USAGE: - lotus client list-retrievals [command options] [arguments...] - -CATEGORY: - RETRIEVAL - -OPTIONS: - --verbose, -v print verbose deal details (default: false) - --show-failed show failed/failing deals (default: true) - --completed show completed retrievals (default: false) - --watch watch deal updates in real-time, rather than a one time list (default: false) - --help, -h show help -``` - -### lotus client deal -``` -NAME: - lotus client deal - Initialize storage deal with a miner - -USAGE: - lotus client deal [command options] [dataCid miner price duration] - -CATEGORY: - STORAGE - -DESCRIPTION: - Make a deal with a miner. - dataCid comes from running 'lotus client import'. - miner is the address of the miner you wish to make a deal with. - price is measured in FIL/Epoch. Miners usually don't accept a bid - lower than their advertised ask (which is in FIL/GiB/Epoch). You can check a miners listed price - with 'lotus client query-ask '. - duration is how long the miner should store the data for, in blocks. - The minimum value is 518400 (6 months). - -OPTIONS: - --manual-piece-cid value manually specify piece commitment for data (dataCid must be to a car file) - --manual-piece-size value if manually specifying piece cid, used to specify size (dataCid must be to a car file) (default: 0) - --manual-stateless-deal instructs the node to send an offline deal without registering it with the deallist/fsm (default: false) - --from value specify address to fund the deal with - --start-epoch value specify the epoch that the deal should start at (default: -1) - --fast-retrieval indicates that data should be available for fast retrieval (default: true) - --verified-deal indicate that the deal counts towards verified client total (default: true if client is verified, false otherwise) - --provider-collateral value specify the requested provider collateral the miner should put up - --help, -h show help -``` - -### lotus client query-ask -``` -NAME: - lotus client query-ask - Find a miners ask - -USAGE: - lotus client query-ask [command options] [minerAddress] - -CATEGORY: - STORAGE - -OPTIONS: - --peerid value specify peer ID of node to make query against - --size value data size in bytes (default: 0) - --duration value deal duration (default: 0) - --help, -h show help -``` - -### lotus client list-deals -``` -NAME: - lotus client list-deals - List storage market deals - -USAGE: - lotus client list-deals [command options] [arguments...] - -CATEGORY: - STORAGE - -OPTIONS: - --verbose, -v print verbose deal details (default: false) - --show-failed show failed/failing deals (default: false) - --watch watch deal updates in real-time, rather than a one time list (default: false) - --help, -h show help -``` - -### lotus client get-deal -``` -NAME: - lotus client get-deal - Print detailed deal information - -USAGE: - lotus client get-deal [command options] [proposalCID] - -CATEGORY: - STORAGE - -OPTIONS: - --help, -h show help -``` - -### lotus client list-asks -``` -NAME: - lotus client list-asks - List asks for top miners - -USAGE: - lotus client list-asks [command options] [arguments...] - -CATEGORY: - STORAGE - -OPTIONS: - --by-ping sort by ping (default: false) - --output-format value Either 'text' or 'csv' (default: "text") - --protocols Output supported deal protocols (default: false) - --help, -h show help -``` - -### lotus client deal-stats -``` -NAME: - lotus client deal-stats - Print statistics about local storage deals - -USAGE: - lotus client deal-stats [command options] [arguments...] - -CATEGORY: - STORAGE - -OPTIONS: - --newer-than value (default: 0s) - --help, -h show help -``` - -### lotus client inspect-deal -``` -NAME: - lotus client inspect-deal - Inspect detailed information about deal's lifecycle and the various stages it goes through - -USAGE: - lotus client inspect-deal [command options] [arguments...] - -CATEGORY: - STORAGE - -OPTIONS: - --deal-id value (default: 0) - --proposal-cid value - --help, -h show help -``` - -### lotus client commP -``` -NAME: - lotus client commP - Calculate the piece-cid (commP) of a CAR file - -USAGE: - lotus client commP [command options] [inputFile] - -CATEGORY: - UTIL - -OPTIONS: - --help, -h show help -``` - -### lotus client generate-car -``` -NAME: - lotus client generate-car - Generate a car file from input - -USAGE: - lotus client generate-car [command options] [inputPath outputPath] - -CATEGORY: - UTIL - -OPTIONS: - --help, -h show help -``` - -### lotus client balances -``` -NAME: - lotus client balances - Print storage market client balances - -USAGE: - lotus client balances [command options] [arguments...] - -CATEGORY: - UTIL - -OPTIONS: - --client value specify storage client address - --help, -h show help -``` - -### lotus client list-transfers -``` -NAME: - lotus client list-transfers - List ongoing data transfers for deals - -USAGE: - lotus client list-transfers [command options] [arguments...] - -CATEGORY: - UTIL - -OPTIONS: - --verbose, -v print verbose transfer details (default: false) - --completed show completed data transfers (default: false) - --watch watch deal updates in real-time, rather than a one time list (default: false) - --show-failed show failed/cancelled transfers (default: false) - --help, -h show help -``` - -### lotus client restart-transfer -``` -NAME: - lotus client restart-transfer - Force restart a stalled data transfer - -USAGE: - lotus client restart-transfer [command options] [transferID] - -CATEGORY: - UTIL - -OPTIONS: - --peerid value narrow to transfer with specific peer - --initiator specify only transfers where peer is/is not initiator (default: true) - --help, -h show help -``` - -### lotus client cancel-transfer -``` -NAME: - lotus client cancel-transfer - Force cancel a data transfer - -USAGE: - lotus client cancel-transfer [command options] [transferID] - -CATEGORY: - UTIL - -OPTIONS: - --peerid value narrow to transfer with specific peer - --initiator specify only transfers where peer is/is not initiator (default: true) - --cancel-timeout value time to wait for cancel to be sent to storage provider (default: 5s) - --help, -h show help -``` - ## lotus msig ``` NAME: @@ -1380,9 +870,8 @@ USAGE: lotus paych add-funds [command options] [fromAddress toAddress amount] OPTIONS: - --restart-retrievals restart stalled retrieval deals on this payment channel (default: true) - --reserve mark funds as reserved (default: false) - --help, -h show help + --reserve mark funds as reserved (default: false) + --help, -h show help ``` ### lotus paych list diff --git a/documentation/en/cli-sptool.md b/documentation/en/cli-sptool.md deleted file mode 100644 index 3a53fe7a3..000000000 --- a/documentation/en/cli-sptool.md +++ /dev/null @@ -1,474 +0,0 @@ -# sptool -``` -NAME: - sptool - Manage Filecoin Miner Actor - -USAGE: - sptool [global options] command [command options] [arguments...] - -VERSION: - 1.27.0 - -COMMANDS: - actor Manage Filecoin Miner Actor Metadata - info Print miner actor info - sectors interact with sector store - proving View proving information - help, h Shows a list of commands or help for one command - -GLOBAL OPTIONS: - --log-level value (default: "info") - --actor value miner actor to manage [$SP_ADDRESS] - --help, -h show help - --version, -v print the version -``` - -## sptool actor -``` -NAME: - sptool actor - Manage Filecoin Miner Actor Metadata - -USAGE: - sptool actor command [command options] [arguments...] - -COMMANDS: - set-addresses, set-addrs set addresses that your miner can be publicly dialed on - withdraw withdraw available balance to beneficiary - repay-debt pay down a miner's debt - set-peer-id set the peer id of your miner - set-owner Set owner address (this command should be invoked twice, first with the old owner as the senderAddress, and then with the new owner) - control Manage control addresses - propose-change-worker Propose a worker address change - confirm-change-worker Confirm a worker address change - compact-allocated compact allocated sectors bitfield - propose-change-beneficiary Propose a beneficiary address change - confirm-change-beneficiary Confirm a beneficiary address change - new-miner Initializes a new miner actor - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -### sptool actor set-addresses -``` -NAME: - sptool actor set-addresses - set addresses that your miner can be publicly dialed on - -USAGE: - sptool actor set-addresses [command options] - -OPTIONS: - --from value optionally specify the account to send the message from - --gas-limit value set gas limit (default: 0) - --unset unset address (default: false) - --help, -h show help -``` - -### sptool actor withdraw -``` -NAME: - sptool actor withdraw - withdraw available balance to beneficiary - -USAGE: - sptool actor withdraw [command options] [amount (FIL)] - -OPTIONS: - --confidence value number of block confirmations to wait for (default: 5) - --beneficiary send withdraw message from the beneficiary address (default: false) - --help, -h show help -``` - -### sptool actor repay-debt -``` -NAME: - sptool actor repay-debt - pay down a miner's debt - -USAGE: - sptool actor repay-debt [command options] [amount (FIL)] - -OPTIONS: - --from value optionally specify the account to send funds from - --help, -h show help -``` - -### sptool actor set-peer-id -``` -NAME: - sptool actor set-peer-id - set the peer id of your miner - -USAGE: - sptool actor set-peer-id [command options] - -OPTIONS: - --gas-limit value set gas limit (default: 0) - --help, -h show help -``` - -### sptool actor set-owner -``` -NAME: - sptool actor set-owner - Set owner address (this command should be invoked twice, first with the old owner as the senderAddress, and then with the new owner) - -USAGE: - sptool actor set-owner [command options] [newOwnerAddress senderAddress] - -OPTIONS: - --really-do-it Actually send transaction performing the action (default: false) - --help, -h show help -``` - -### sptool actor control -``` -NAME: - sptool actor control - Manage control addresses - -USAGE: - sptool actor control command [command options] [arguments...] - -COMMANDS: - list Get currently set control addresses. Note: This excludes most roles as they are not known to the immediate chain state. - set Set control address(-es) - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -#### sptool actor control list -``` -NAME: - sptool actor control list - Get currently set control addresses. Note: This excludes most roles as they are not known to the immediate chain state. - -USAGE: - sptool actor control list [command options] [arguments...] - -OPTIONS: - --verbose (default: false) - --help, -h show help -``` - -#### sptool actor control set -``` -NAME: - sptool actor control set - Set control address(-es) - -USAGE: - sptool actor control set [command options] [...address] - -OPTIONS: - --really-do-it Actually send transaction performing the action (default: false) - --help, -h show help -``` - -### sptool actor propose-change-worker -``` -NAME: - sptool actor propose-change-worker - Propose a worker address change - -USAGE: - sptool actor propose-change-worker [command options] [address] - -OPTIONS: - --really-do-it Actually send transaction performing the action (default: false) - --help, -h show help -``` - -### sptool actor confirm-change-worker -``` -NAME: - sptool actor confirm-change-worker - Confirm a worker address change - -USAGE: - sptool actor confirm-change-worker [command options] [address] - -OPTIONS: - --really-do-it Actually send transaction performing the action (default: false) - --help, -h show help -``` - -### sptool actor compact-allocated -``` -NAME: - sptool actor compact-allocated - compact allocated sectors bitfield - -USAGE: - sptool actor compact-allocated [command options] [arguments...] - -OPTIONS: - --mask-last-offset value Mask sector IDs from 0 to 'highest_allocated - offset' (default: 0) - --mask-upto-n value Mask sector IDs from 0 to 'n' (default: 0) - --really-do-it Actually send transaction performing the action (default: false) - --help, -h show help -``` - -### sptool actor propose-change-beneficiary -``` -NAME: - sptool actor propose-change-beneficiary - Propose a beneficiary address change - -USAGE: - sptool actor propose-change-beneficiary [command options] [beneficiaryAddress quota expiration] - -OPTIONS: - --really-do-it Actually send transaction performing the action (default: false) - --overwrite-pending-change Overwrite the current beneficiary change proposal (default: false) - --actor value specify the address of miner actor - --help, -h show help -``` - -### sptool actor confirm-change-beneficiary -``` -NAME: - sptool actor confirm-change-beneficiary - Confirm a beneficiary address change - -USAGE: - sptool actor confirm-change-beneficiary [command options] [minerID] - -OPTIONS: - --really-do-it Actually send transaction performing the action (default: false) - --existing-beneficiary send confirmation from the existing beneficiary address (default: false) - --new-beneficiary send confirmation from the new beneficiary address (default: false) - --help, -h show help -``` - -### sptool actor new-miner -``` -NAME: - sptool actor new-miner - Initializes a new miner actor - -USAGE: - sptool actor new-miner [command options] [arguments...] - -OPTIONS: - --worker value, -w value worker key to use for new miner initialisation - --owner value, -o value owner key to use for new miner initialisation - --from value, -f value address to send actor(miner) creation message from - --sector-size value specify sector size to use for new miner initialisation - --help, -h show help -``` - -## sptool info -``` -NAME: - sptool info - Print miner actor info - -USAGE: - sptool info [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -## sptool sectors -``` -NAME: - sptool sectors - interact with sector store - -USAGE: - sptool sectors command [command options] [arguments...] - -COMMANDS: - status Get the seal status of a sector by its number - list List sectors - precommits Print on-chain precommit info - check-expire Inspect expiring sectors - expired Get or cleanup expired sectors - extend Extend expiring sectors while not exceeding each sector's max life - terminate Forcefully terminate a sector (WARNING: This means losing power and pay a one-time termination penalty(including collateral) for the terminated sector) - compact-partitions removes dead sectors from partitions and reduces the number of partitions used if possible - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -### sptool sectors status -``` -NAME: - sptool sectors status - Get the seal status of a sector by its number - -USAGE: - sptool sectors status [command options] - -OPTIONS: - --log, -l display event log (default: false) - --on-chain-info, -c show sector on chain info (default: false) - --partition-info, -p show partition related info (default: false) - --proof print snark proof bytes as hex (default: false) - --help, -h show help -``` - -### sptool sectors list -``` -NAME: - sptool sectors list - List sectors - -USAGE: - sptool sectors list [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -### sptool sectors precommits -``` -NAME: - sptool sectors precommits - Print on-chain precommit info - -USAGE: - sptool sectors precommits [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -### sptool sectors check-expire -``` -NAME: - sptool sectors check-expire - Inspect expiring sectors - -USAGE: - sptool 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 -``` - -### sptool sectors expired -``` -NAME: - sptool sectors expired - Get or cleanup expired sectors - -USAGE: - sptool sectors expired [command options] [arguments...] - -OPTIONS: - --expired-epoch value epoch at which to check sector expirations (default: WinningPoSt lookback epoch) - --help, -h show help -``` - -### sptool sectors extend -``` -NAME: - sptool sectors extend - Extend expiring sectors while not exceeding each sector's max life - -USAGE: - sptool sectors extend [command options] - -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) - --only-cc only extend CC sectors (useful for making sector ready for snap upgrade) (default: false) - --drop-claims drop claims for sectors that can be extended, but only by dropping some of their verified power claims (default: false) - --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") - --max-sectors value the maximum number of sectors contained in each message (default: 0) - --really-do-it pass this flag to really extend sectors, otherwise will only print out json representation of parameters (default: false) - --help, -h show help -``` - -### sptool sectors terminate -``` -NAME: - sptool sectors terminate - Forcefully terminate a sector (WARNING: This means losing power and pay a one-time termination penalty(including collateral) for the terminated sector) - -USAGE: - sptool sectors terminate [command options] [sectorNum1 sectorNum2 ...] - -OPTIONS: - --actor value specify the address of miner actor - --really-do-it pass this flag if you know what you are doing (default: false) - --from value specify the address to send the terminate message from - --help, -h show help -``` - -### sptool sectors compact-partitions -``` -NAME: - sptool sectors compact-partitions - removes dead sectors from partitions and reduces the number of partitions used if possible - -USAGE: - sptool sectors compact-partitions [command options] [arguments...] - -OPTIONS: - --deadline value the deadline to compact the partitions in (default: 0) - --partitions value [ --partitions value ] list of partitions to compact sectors in - --really-do-it Actually send transaction performing the action (default: false) - --help, -h show help -``` - -## sptool proving -``` -NAME: - sptool proving - View proving information - -USAGE: - sptool proving command [command options] [arguments...] - -COMMANDS: - info View current state information - deadlines View the current proving period deadlines information - deadline View the current proving period deadline information by its index - faults View the currently known proving faulty sectors information - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -### sptool proving info -``` -NAME: - sptool proving info - View current state information - -USAGE: - sptool proving info [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -### sptool proving deadlines -``` -NAME: - sptool proving deadlines - View the current proving period deadlines information - -USAGE: - sptool proving deadlines [command options] [arguments...] - -OPTIONS: - --all, -a Count all sectors (only live sectors are counted by default) (default: false) - --help, -h show help -``` - -### sptool proving deadline -``` -NAME: - sptool proving deadline - View the current proving period deadline information by its index - -USAGE: - sptool proving deadline [command options] - -OPTIONS: - --sector-nums, -n Print sector/fault numbers belonging to this deadline (default: false) - --bitfield, -b Print partition bitfield stats (default: false) - --help, -h show help -``` - -### sptool proving faults -``` -NAME: - sptool proving faults - View the currently known proving faulty sectors information - -USAGE: - sptool proving faults [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` diff --git a/documentation/en/default-curio-config.toml b/documentation/en/default-curio-config.toml deleted file mode 100644 index afcb7608a..000000000 --- a/documentation/en/default-curio-config.toml +++ /dev/null @@ -1,366 +0,0 @@ -[Subsystems] - # EnableWindowPost enables window post to be executed on this curio instance. Each machine in the cluster - # with WindowPoSt enabled will also participate in the window post scheduler. It is possible to have multiple - # machines with WindowPoSt enabled which will provide redundancy, and in case of multiple partitions per deadline, - # will allow for parallel processing of partitions. - # - # It is possible to have instances handling both WindowPoSt and WinningPoSt, which can provide redundancy without - # the need for additional machines. In setups like this it is generally recommended to run - # partitionsPerDeadline+1 machines. - # - # type: bool - #EnableWindowPost = false - - # type: int - #WindowPostMaxTasks = 0 - - # EnableWinningPost enables winning post to be executed on this curio instance. - # Each machine in the cluster with WinningPoSt enabled will also participate in the winning post scheduler. - # It is possible to mix machines with WindowPoSt and WinningPoSt enabled, for details see the EnableWindowPost - # documentation. - # - # type: bool - #EnableWinningPost = false - - # type: int - #WinningPostMaxTasks = 0 - - # EnableParkPiece enables the "piece parking" task to run on this node. This task is responsible for fetching - # pieces from the network and storing them in the storage subsystem until sectors are sealed. This task is - # only applicable when integrating with boost, and should be enabled on nodes which will hold deal data - # from boost until sectors containing the related pieces have the TreeD/TreeR constructed. - # Note that future Curio implementations will have a separate task type for fetching pieces from the internet. - # - # type: bool - #EnableParkPiece = false - - # type: int - #ParkPieceMaxTasks = 0 - - # EnableSealSDR enables SDR tasks to run. SDR is the long sequential computation - # creating 11 layer files in sector cache directory. - # - # SDR is the first task in the sealing pipeline. It's inputs are just the hash of the - # unsealed data (CommD), sector number, miner id, and the seal proof type. - # It's outputs are the 11 layer files in the sector cache directory. - # - # In lotus-miner this was run as part of PreCommit1. - # - # type: bool - #EnableSealSDR = false - - # The maximum amount of SDR tasks that can run simultaneously. Note that the maximum number of tasks will - # also be bounded by resources available on the machine. - # - # type: int - #SealSDRMaxTasks = 0 - - # EnableSealSDRTrees enables the SDR pipeline tree-building task to run. - # This task handles encoding of unsealed data into last sdr layer and building - # of TreeR, TreeC and TreeD. - # - # This task runs after SDR - # TreeD is first computed with optional input of unsealed data - # TreeR is computed from replica, which is first computed as field - # addition of the last SDR layer and the bottom layer of TreeD (which is the unsealed data) - # TreeC is computed from the 11 SDR layers - # The 3 trees will later be used to compute the PoRep proof. - # - # In case of SyntheticPoRep challenges for PoRep will be pre-generated at this step, and trees and layers - # will be dropped. SyntheticPoRep works by pre-generating a very large set of challenges (~30GiB on disk) - # then using a small subset of them for the actual PoRep computation. This allows for significant scratch space - # saving between PreCommit and PoRep generation at the expense of more computation (generating challenges in this step) - # - # In lotus-miner this was run as part of PreCommit2 (TreeD was run in PreCommit1). - # Note that nodes with SDRTrees enabled will also answer to Finalize tasks, - # which just remove unneeded tree data after PoRep is computed. - # - # type: bool - #EnableSealSDRTrees = false - - # The maximum amount of SealSDRTrees tasks that can run simultaneously. Note that the maximum number of tasks will - # also be bounded by resources available on the machine. - # - # type: int - #SealSDRTreesMaxTasks = 0 - - # FinalizeMaxTasks is the maximum amount of finalize tasks that can run simultaneously. - # The finalize task is enabled on all machines which also handle SDRTrees tasks. Finalize ALWAYS runs on whichever - # machine holds sector cache files, as it removes unneeded tree data after PoRep is computed. - # Finalize will run in parallel with the SubmitCommitMsg task. - # - # type: int - #FinalizeMaxTasks = 0 - - # EnableSendPrecommitMsg enables the sending of precommit messages to the chain - # from this curio instance. - # This runs after SDRTrees and uses the output CommD / CommR (roots of TreeD / TreeR) for the message - # - # type: bool - #EnableSendPrecommitMsg = false - - # EnablePoRepProof enables the computation of the porep proof - # - # This task runs after interactive-porep seed becomes available, which happens 150 epochs (75min) after the - # precommit message lands on chain. This task should run on a machine with a GPU. Vanilla PoRep proofs are - # requested from the machine which holds sector cache files which most likely is the machine which ran the SDRTrees - # task. - # - # In lotus-miner this was Commit1 / Commit2 - # - # type: bool - #EnablePoRepProof = false - - # The maximum amount of PoRepProof tasks that can run simultaneously. Note that the maximum number of tasks will - # also be bounded by resources available on the machine. - # - # type: int - #PoRepProofMaxTasks = 0 - - # EnableSendCommitMsg enables the sending of commit messages to the chain - # from this curio instance. - # - # type: bool - #EnableSendCommitMsg = false - - # EnableMoveStorage enables the move-into-long-term-storage task to run on this curio instance. - # This tasks should only be enabled on nodes with long-term storage. - # - # The MoveStorage task is the last task in the sealing pipeline. It moves the sealed sector data from the - # SDRTrees machine into long-term storage. This task runs after the Finalize task. - # - # type: bool - #EnableMoveStorage = false - - # The maximum amount of MoveStorage tasks that can run simultaneously. Note that the maximum number of tasks will - # also be bounded by resources available on the machine. It is recommended that this value is set to a number which - # uses all available network (or disk) bandwidth on the machine without causing bottlenecks. - # - # type: int - #MoveStorageMaxTasks = 0 - - # BoostAdapters is a list of tuples of miner address and port/ip to listen for market (e.g. boost) requests. - # This interface is compatible with the lotus-miner RPC, implementing a subset needed for storage market operations. - # Strings should be in the format "actor:port" or "actor:ip:port". Default listen address is 0.0.0.0 - # Example: "f0123:32100", "f0123:127.0.0.1:32100". Multiple addresses can be specified. - # - # When a market node like boost gives Curio's market RPC a deal to placing into a sector, Curio will first store the - # deal data in a temporary location "Piece Park" before assigning it to a sector. This requires that at least one - # node in the cluster has the EnableParkPiece option enabled and has sufficient scratch space to store the deal data. - # This is different from lotus-miner which stored the deal data into an "unsealed" sector as soon as the deal was - # received. Deal data in PiecePark is accessed when the sector TreeD and TreeR are computed, but isn't needed for - # the initial SDR layers computation. Pieces in PiecePark are removed after all sectors referencing the piece are - # sealed. - # - # To get API info for boost configuration run 'curio market rpc-info' - # - # NOTE: All deal data will flow through this service, so it should be placed on a machine running boost or on - # a machine which handles ParkPiece tasks. - # - # type: []string - #BoostAdapters = [] - - # EnableWebGui enables the web GUI on this curio instance. The UI has minimal local overhead, but it should - # only need to be run on a single machine in the cluster. - # - # type: bool - #EnableWebGui = false - - # The address that should listen for Web GUI requests. - # - # type: string - #GuiAddress = ":4701" - - -[Fees] - # type: types.FIL - #DefaultMaxFee = "0.07 FIL" - - # type: types.FIL - #MaxPreCommitGasFee = "0.025 FIL" - - # type: types.FIL - #MaxCommitGasFee = "0.05 FIL" - - # type: types.FIL - #MaxTerminateGasFee = "0.5 FIL" - - # WindowPoSt is a high-value operation, so the default fee should be high. - # - # type: types.FIL - #MaxWindowPoStGasFee = "5 FIL" - - # type: types.FIL - #MaxPublishDealsFee = "0.05 FIL" - - [Fees.MaxPreCommitBatchGasFee] - # type: types.FIL - #Base = "0 FIL" - - # type: types.FIL - #PerSector = "0.02 FIL" - - [Fees.MaxCommitBatchGasFee] - # type: types.FIL - #Base = "0 FIL" - - # type: types.FIL - #PerSector = "0.03 FIL" - - -[[Addresses]] - #PreCommitControl = [] - - #CommitControl = [] - - #TerminateControl = [] - - #DisableOwnerFallback = false - - #DisableWorkerFallback = false - - #MinerAddresses = [] - - -[Proving] - # Maximum number of sector checks to run in parallel. (0 = unlimited) - # - # WARNING: Setting this value too high may make the node crash by running out of stack - # WARNING: Setting this value too low may make sector challenge reading much slower, resulting in failed PoSt due - # to late submission. - # - # After changing this option, confirm that the new value works in your setup by invoking - # 'lotus-miner proving compute window-post 0' - # - # type: int - #ParallelCheckLimit = 32 - - # Maximum amount of time a proving pre-check can take for a sector. If the check times out the sector will be skipped - # - # WARNING: Setting this value too low risks in sectors being skipped even though they are accessible, just reading the - # test challenge took longer than this timeout - # WARNING: Setting this value too high risks missing PoSt deadline in case IO operations related to this sector are - # blocked (e.g. in case of disconnected NFS mount) - # - # type: Duration - #SingleCheckTimeout = "10m0s" - - # Maximum amount of time a proving pre-check can take for an entire partition. If the check times out, sectors in - # the partition which didn't get checked on time will be skipped - # - # WARNING: Setting this value too low risks in sectors being skipped even though they are accessible, just reading the - # test challenge took longer than this timeout - # WARNING: Setting this value too high risks missing PoSt deadline in case IO operations related to this partition are - # blocked or slow - # - # type: Duration - #PartitionCheckTimeout = "20m0s" - - # Disable WindowPoSt provable sector readability checks. - # - # In normal operation, when preparing to compute WindowPoSt, lotus-miner will perform a round of reading challenges - # from all sectors to confirm that those sectors can be proven. Challenges read in this process are discarded, as - # we're only interested in checking that sector data can be read. - # - # When using builtin proof computation (no PoSt workers, and DisableBuiltinWindowPoSt is set to false), this process - # can save a lot of time and compute resources in the case that some sectors are not readable - this is caused by - # the builtin logic not skipping snark computation when some sectors need to be skipped. - # - # When using PoSt workers, this process is mostly redundant, with PoSt workers challenges will be read once, and - # if challenges for some sectors aren't readable, those sectors will just get skipped. - # - # Disabling sector pre-checks will slightly reduce IO load when proving sectors, possibly resulting in shorter - # time to produce window PoSt. In setups with good IO capabilities the effect of this option on proving time should - # be negligible. - # - # NOTE: It likely is a bad idea to disable sector pre-checks in setups with no PoSt workers. - # - # NOTE: Even when this option is enabled, recovering sectors will be checked before recovery declaration message is - # sent to the chain - # - # After changing this option, confirm that the new value works in your setup by invoking - # 'lotus-miner proving compute window-post 0' - # - # type: bool - #DisableWDPoStPreChecks = false - - # Maximum number of partitions to prove in a single SubmitWindowPoSt messace. 0 = network limit (3 in nv21) - # - # A single partition may contain up to 2349 32GiB sectors, or 2300 64GiB sectors. - # // - # Note that setting this value lower may result in less efficient gas use - more messages will be sent, - # to prove each deadline, resulting in more total gas use (but each message will have lower gas limit) - # - # Setting this value above the network limit has no effect - # - # type: int - #MaxPartitionsPerPoStMessage = 0 - - # In some cases when submitting DeclareFaultsRecovered messages, - # there may be too many recoveries to fit in a BlockGasLimit. - # In those cases it may be necessary to set this value to something low (eg 1); - # Note that setting this value lower may result in less efficient gas use - more messages will be sent than needed, - # resulting in more total gas use (but each message will have lower gas limit) - # - # type: int - #MaxPartitionsPerRecoveryMessage = 0 - - # Enable single partition per PoSt Message for partitions containing recovery sectors - # - # In cases when submitting PoSt messages which contain recovering sectors, the default network limit may still be - # too high to fit in the block gas limit. In those cases, it becomes useful to only house the single partition - # with recovering sectors in the post message - # - # Note that setting this value lower may result in less efficient gas use - more messages will be sent, - # to prove each deadline, resulting in more total gas use (but each message will have lower gas limit) - # - # type: bool - #SingleRecoveringPartitionPerPostMessage = false - - -[Ingest] - # Maximum number of sectors that can be queued waiting for SDR to start processing. - # 0 = unlimited - # Note: This mechanism will delay taking deal data from markets, providing backpressure to the market subsystem. - # The SDR queue includes deals which are in the process of entering the sealing pipeline - size of this queue - # will also impact the maximum number of ParkPiece tasks which can run concurrently. - # - # SDR queue is the first queue in the sealing pipeline, meaning that it should be used as the primary backpressure mechanism. - # - # type: int - #MaxQueueSDR = 8 - - # Maximum number of sectors that can be queued waiting for SDRTrees to start processing. - # 0 = unlimited - # Note: This mechanism will delay taking deal data from markets, providing backpressure to the market subsystem. - # In case of the trees tasks it is possible that this queue grows more than this limit, the backpressure is only - # applied to sectors entering the pipeline. - # - # type: int - #MaxQueueTrees = 0 - - # Maximum number of sectors that can be queued waiting for PoRep to start processing. - # 0 = unlimited - # Note: This mechanism will delay taking deal data from markets, providing backpressure to the market subsystem. - # Like with the trees tasks, it is possible that this queue grows more than this limit, the backpressure is only - # applied to sectors entering the pipeline. - # - # type: int - #MaxQueuePoRep = 0 - - -[Journal] - # Events of the form: "system1:event1,system1:event2[,...]" - # - # type: string - #DisabledEvents = "" - - -[Apis] - # RPC Secret for the storage subsystem. - # If integrating with lotus-miner this must match the value from - # cat ~/.lotusminer/keystore/MF2XI2BNNJ3XILLQOJUXMYLUMU | jq -r .PrivateKey - # - # type: string - #StorageRPCSecret = "" - diff --git a/documentation/en/default-lotus-config.toml b/documentation/en/default-lotus-config.toml index 8d3c6a427..2971a4e91 100644 --- a/documentation/en/default-lotus-config.toml +++ b/documentation/en/default-lotus-config.toml @@ -128,30 +128,6 @@ #TracerSourceAuth = "" -[Client] - # The maximum number of simultaneous data transfers between the client - # and storage providers for storage deals - # - # type: uint64 - # env var: LOTUS_CLIENT_SIMULTANEOUSTRANSFERSFORSTORAGE - #SimultaneousTransfersForStorage = 20 - - # The maximum number of simultaneous data transfers between the client - # and storage providers for retrieval deals - # - # type: uint64 - # env var: LOTUS_CLIENT_SIMULTANEOUSTRANSFERSFORRETRIEVAL - #SimultaneousTransfersForRetrieval = 20 - - # Require that retrievals perform no on-chain operations. Paid retrievals - # without existing payment channels with available funds will fail instead - # of automatically performing on-chain operations. - # - # type: bool - # env var: LOTUS_CLIENT_OFFCHAINRETRIEVAL - #OffChainRetrieval = false - - [Wallet] # type: string # env var: LOTUS_WALLET_REMOTEBACKEND diff --git a/documentation/en/default-lotus-miner-config.toml b/documentation/en/default-lotus-miner-config.toml index 17fd24fa3..bc2c37436 100644 --- a/documentation/en/default-lotus-miner-config.toml +++ b/documentation/en/default-lotus-miner-config.toml @@ -141,10 +141,6 @@ # env var: LOTUS_SUBSYSTEMS_ENABLESECTORSTORAGE #EnableSectorStorage = true - # type: bool - # env var: LOTUS_SUBSYSTEMS_ENABLEMARKETS - #EnableMarkets = false - # When enabled, the sector index will reside in an external database # as opposed to the local KV store in the miner process # This is useful to allow workers to bypass the lotus miner to access sector information @@ -188,190 +184,12 @@ [Dealmaking] - # When enabled, the miner can accept online deals - # - # type: bool - # env var: LOTUS_DEALMAKING_CONSIDERONLINESTORAGEDEALS - #ConsiderOnlineStorageDeals = true - - # When enabled, the miner can accept offline deals - # - # type: bool - # env var: LOTUS_DEALMAKING_CONSIDEROFFLINESTORAGEDEALS - #ConsiderOfflineStorageDeals = true - - # When enabled, the miner can accept retrieval deals - # - # type: bool - # env var: LOTUS_DEALMAKING_CONSIDERONLINERETRIEVALDEALS - #ConsiderOnlineRetrievalDeals = true - - # When enabled, the miner can accept offline retrieval deals - # - # type: bool - # env var: LOTUS_DEALMAKING_CONSIDEROFFLINERETRIEVALDEALS - #ConsiderOfflineRetrievalDeals = true - - # When enabled, the miner can accept verified deals - # - # type: bool - # env var: LOTUS_DEALMAKING_CONSIDERVERIFIEDSTORAGEDEALS - #ConsiderVerifiedStorageDeals = true - - # When enabled, the miner can accept unverified deals - # - # type: bool - # env var: LOTUS_DEALMAKING_CONSIDERUNVERIFIEDSTORAGEDEALS - #ConsiderUnverifiedStorageDeals = true - - # A list of Data CIDs to reject when making deals - # - # type: []cid.Cid - # env var: LOTUS_DEALMAKING_PIECECIDBLOCKLIST - #PieceCidBlocklist = [] - - # Maximum expected amount of time getting the deal into a sealed sector will take - # This includes the time the deal will need to get transferred and published - # before being assigned to a sector - # - # type: Duration - # env var: LOTUS_DEALMAKING_EXPECTEDSEALDURATION - #ExpectedSealDuration = "24h0m0s" - - # Maximum amount of time proposed deal StartEpoch can be in future - # - # type: Duration - # env var: LOTUS_DEALMAKING_MAXDEALSTARTDELAY - #MaxDealStartDelay = "336h0m0s" - - # When a deal is ready to publish, the amount of time to wait for more - # deals to be ready to publish before publishing them all as a batch - # - # type: Duration - # env var: LOTUS_DEALMAKING_PUBLISHMSGPERIOD - #PublishMsgPeriod = "1h0m0s" - - # The maximum number of deals to include in a single PublishStorageDeals - # message - # - # type: uint64 - # env var: LOTUS_DEALMAKING_MAXDEALSPERPUBLISHMSG - #MaxDealsPerPublishMsg = 8 - - # The maximum collateral that the provider will put up against a deal, - # as a multiplier of the minimum collateral bound - # - # type: uint64 - # env var: LOTUS_DEALMAKING_MAXPROVIDERCOLLATERALMULTIPLIER - #MaxProviderCollateralMultiplier = 2 - - # The maximum allowed disk usage size in bytes of staging deals not yet - # passed to the sealing node by the markets service. 0 is unlimited. - # - # type: int64 - # env var: LOTUS_DEALMAKING_MAXSTAGINGDEALSBYTES - #MaxStagingDealsBytes = 0 - - # The maximum number of parallel online data transfers for storage deals - # - # type: uint64 - # env var: LOTUS_DEALMAKING_SIMULTANEOUSTRANSFERSFORSTORAGE - #SimultaneousTransfersForStorage = 20 - - # The maximum number of simultaneous data transfers from any single client - # for storage deals. - # Unset by default (0), and values higher than SimultaneousTransfersForStorage - # will have no effect; i.e. the total number of simultaneous data transfers - # across all storage clients is bound by SimultaneousTransfersForStorage - # regardless of this number. - # - # type: uint64 - # env var: LOTUS_DEALMAKING_SIMULTANEOUSTRANSFERSFORSTORAGEPERCLIENT - #SimultaneousTransfersForStoragePerClient = 0 - - # The maximum number of parallel online data transfers for retrieval deals - # - # type: uint64 - # env var: LOTUS_DEALMAKING_SIMULTANEOUSTRANSFERSFORRETRIEVAL - #SimultaneousTransfersForRetrieval = 20 - # Minimum start epoch buffer to give time for sealing of sector with deal. # # type: uint64 # env var: LOTUS_DEALMAKING_STARTEPOCHSEALINGBUFFER #StartEpochSealingBuffer = 480 - # A command used for fine-grained evaluation of storage deals - # see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details - # - # type: string - # env var: LOTUS_DEALMAKING_FILTER - #Filter = "" - - # A command used for fine-grained evaluation of retrieval deals - # see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details - # - # type: string - # env var: LOTUS_DEALMAKING_RETRIEVALFILTER - #RetrievalFilter = "" - - [Dealmaking.RetrievalPricing] - # env var: LOTUS_DEALMAKING_RETRIEVALPRICING_STRATEGY - #Strategy = "default" - - [Dealmaking.RetrievalPricing.Default] - # env var: LOTUS_DEALMAKING_RETRIEVALPRICING_DEFAULT_VERIFIEDDEALSFREETRANSFER - #VerifiedDealsFreeTransfer = true - - [Dealmaking.RetrievalPricing.External] - # env var: LOTUS_DEALMAKING_RETRIEVALPRICING_EXTERNAL_PATH - #Path = "" - - -[IndexProvider] - # Enable set whether to enable indexing announcement to the network and expose endpoints that - # allow indexer nodes to process announcements. Enabled by default. - # - # type: bool - # env var: LOTUS_INDEXPROVIDER_ENABLE - #Enable = true - - # EntriesCacheCapacity sets the maximum capacity to use for caching the indexing advertisement - # entries. Defaults to 1024 if not specified. The cache is evicted using LRU policy. The - # maximum storage used by the cache is a factor of EntriesCacheCapacity, EntriesChunkSize and - # the length of multihashes being advertised. For example, advertising 128-bit long multihashes - # with the default EntriesCacheCapacity, and EntriesChunkSize means the cache size can grow to - # 256MiB when full. - # - # type: int - # env var: LOTUS_INDEXPROVIDER_ENTRIESCACHECAPACITY - #EntriesCacheCapacity = 1024 - - # EntriesChunkSize sets the maximum number of multihashes to include in a single entries chunk. - # Defaults to 16384 if not specified. Note that chunks are chained together for indexing - # advertisements that include more multihashes than the configured EntriesChunkSize. - # - # type: int - # env var: LOTUS_INDEXPROVIDER_ENTRIESCHUNKSIZE - #EntriesChunkSize = 16384 - - # TopicName sets the topic name on which the changes to the advertised content are announced. - # If not explicitly specified, the topic name is automatically inferred from the network name - # in following format: '/indexer/ingest/' - # Defaults to empty, which implies the topic name is inferred from network name. - # - # type: string - # env var: LOTUS_INDEXPROVIDER_TOPICNAME - #TopicName = "" - - # PurgeCacheOnStart sets whether to clear any cached entries chunks when the provider engine - # starts. By default, the cache is rehydrated from previously cached entries stored in - # datastore if any is present. - # - # type: bool - # env var: LOTUS_INDEXPROVIDER_PURGECACHEONSTART - #PurgeCacheOnStart = false - [Proving] # Maximum number of sector checks to run in parallel. (0 = unlimited) @@ -896,63 +714,6 @@ #DisableWorkerFallback = false -[DAGStore] - # 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) - # - # type: string - # env var: LOTUS_DAGSTORE_ROOTDIR - #RootDir = "" - - # The maximum amount of indexing jobs that can run simultaneously. - # 0 means unlimited. - # Default value: 5. - # - # type: int - # env var: LOTUS_DAGSTORE_MAXCONCURRENTINDEX - #MaxConcurrentIndex = 5 - - # The maximum amount of unsealed deals that can be fetched simultaneously - # from the storage subsystem. 0 means unlimited. - # Default value: 0 (unlimited). - # - # type: int - # env var: LOTUS_DAGSTORE_MAXCONCURRENTREADYFETCHES - #MaxConcurrentReadyFetches = 0 - - # The maximum amount of unseals that can be processed simultaneously - # from the storage subsystem. 0 means unlimited. - # Default value: 0 (unlimited). - # - # type: int - # env var: LOTUS_DAGSTORE_MAXCONCURRENTUNSEALS - #MaxConcurrentUnseals = 5 - - # The maximum number of simultaneous inflight API calls to the storage - # subsystem. - # Default value: 100. - # - # type: int - # env var: LOTUS_DAGSTORE_MAXCONCURRENCYSTORAGECALLS - #MaxConcurrencyStorageCalls = 100 - - # The time between calls to periodic dagstore GC, in time.Duration string - # representation, e.g. 1m, 5m, 1h. - # Default value: 1 minute. - # - # type: Duration - # env var: LOTUS_DAGSTORE_GCINTERVAL - #GCInterval = "1m0s" - - [HarmonyDB] # HOSTS is a list of hostnames to nodes running YugabyteDB # in a cluster. Only 1 is required diff --git a/documentation/misc/Building_a_network_skeleton.md b/documentation/misc/Building_a_network_skeleton.md index 5aea6f706..2e7dbf2ca 100644 --- a/documentation/misc/Building_a_network_skeleton.md +++ b/documentation/misc/Building_a_network_skeleton.md @@ -17,8 +17,7 @@ Each repository has its own set of steps that need to be followed. This guide wi 3. Clone the [go-state-types](https://github.com/filecoin-project/go-state-types) repository. -4. In your Lotus repository, add `replace github.com/filecoin-project/go-state-types => ../go-state-types` to the very end of your Lotus `go.mod` file. - - This ensures that your local clone copy of `go-state-types` is used. Any changes you make there will be reflected in your Lotus project. +4. Clone the [lotus](https://github.com/filecoin-project/lotus) repository. ## Ref-FVM Checklist @@ -29,17 +28,33 @@ Each repository has its own set of steps that need to be followed. This guide wi - In fvm/src/machine/default.rs, locate the new function within your machine context. You'll find a SUPPORTED_VERSIONS constant that sets the range of supported network versions. Update this range to include the new network version. Do this by replacing the existing feature flag nvXX-dev and NetworkVersion::VXX with the new ones corresponding to your new network version. - In `shared/src/version/mod.rs`, in the `NetworkVersion` implementation, you will find a series of constants representing different network versions. To add a new network version, you need to declare a new constant: `pub const (VXX+1): Self = Self(XX+1);` -You can take a look at [this Ref-FVM PR as a reference](https://github.com/filecoin-project/ref-fvm/pull/1929), which added the skeleton for network version 22. +You can take a look at [this Ref-FVM PR as a reference](https://github.com/filecoin-project/ref-fvm/pull/2000), which added the skeleton for network version 23. You can also check out the [releasing primary FVM crates checklist here](https://github.com/filecoin-project/ref-fvm/blob/master/CONTRIBUTING.md#primary-fvm-crates) + +2. In a seperate PR bump the Ref-FVM version: + + - Bump the version in the root Cargo.toml file. + - Bump the fvm, fvm_shared and fvm_sdk versions in the `workspace` section in `ref-fvm/cargo.toml` + 1. `fvm→version` + 2. `fvm_shared→version` + 3. `fvm_sdk→version` + 4. `fvm_integration_tests→version` + - Update the cargo.lock file by running `cargo check --all` + - Make sure the `CHANGELOG.md` files in each of `fvm`, `sdk`, and `shared` are all up-to-date (look + through `git log -- path/to/crate`), set the release date & version, and add a new "Unreleased" + section. It may be appropriate to duplicate some entries across these crates if the changes are + relevant to multiple crates. + +You can take a look at [this PR as a reference](https://github.com/filecoin-project/ref-fvm/pull/2002). Wait for the PR to be merged, then the reviewer will publish a new release. ## Filecoin-FFI Checklist 1. Update the `TryFrom` implementation for `EngineVersion` in `rust/src/fvm/engine.rs` - Add the new network version number (XX+1) to the existing match arm for the network version. -2. Patch the FVM-dependency (fvm3) in `rust/cargo.toml` to use the custom branch of the FVM created in the [Ref-FVM Checklist](#ref-fvm-checklist)) - - Add `features = ["your-ref-fvm-branch"]` to tell Cargo to use you Ref-FVM branch. +2. Patch the FVM-dependency (fvm4 and fvm4_shared) in `rust/cargo.toml` to use the newly published Ref-FVM release. + - Add `features = ["nvXX+1-dev"]`. -You can take a look at this [Filecoin-FFI PR as a reference](https://github.com/filecoin-project/filecoin-ffi/pull/438), which added the skeleton for network version 22. +You can take a look at this [Filecoin-FFI PR as a reference](https://github.com/filecoin-project/filecoin-ffi/pull/454), which added the skeleton for network version 23. ## Go-State-Types Checklist @@ -53,11 +68,25 @@ You can take a look at this [Filecoin-FFI PR as a reference](https://github.com/ - In `func VersionForNetwork` add `case network.Version(XX+1): return Version(XX+1), nil`. - Add the new version to the gen step of the makefile. - Add `$(GO_BIN) run ./builtin/v(XX+1)/gen/gen.go`. + - Commit the above changes with a `create base nvXX+1 skeleton` message so its easier to review. + - In /builtin/vXX+1/migration, delete all the migration files that are specific to the previous network upgrade: + - Commit the above changes with a `Delete migration specific for nvXX` message so its easier to review. + - Check your `/builtin/vXX+1/check.go` file, and see if there is any Invariant TODOs that stems from the previous migration that needs to be cleaned up. + +You can take a look at this [Go-State-Types PR as a reference](https://github.com/filecoin-project/go-state-types/pull/257), which added the skeleton for network version 23. + +2. In a second PR based off your first PR, add a simple migration for the network upgrade: + + - Copy the system.go template [^1], and add it to your `/builtin/vXX+1/migration` folder. + - Copy the top.go template [^2], and add it to your `/builtin/vXX+1/migration` folder. + +You can take a look at this [Go-State-Types PR as a reference](https://github.com/filecoin-project/go-state-types/pull/258), which added added a simple migration for network version 23. -You can take a look at this [Go-State-Types PR as a reference](https://github.com/filecoin-project/go-state-types/pull/232), which added the skeleton for network version 22. - ## Lotus Checklist +1. In your Lotus repository, add `replace github.com/filecoin-project/go-state-types => ../go-state-types` to the very end of your Lotus `go.mod` file. + - This ensures that your local clone copy of `go-state-types` is used. Any changes you make there will be reflected in your Lotus project. + 1. Import new actors: - Create a mock actor-bundle for the new network version. @@ -99,8 +128,8 @@ You can take a look at this [Go-State-Types PR as a reference](https://github.co 4. Update `chain/consensus/filcns/upgrades.go`. - Import `nv(XX+1) "github.com/filecoin-project/go-state-types/builtin/v(XX+1)/migration`. - - Add Schedule. [^1] - - Add Migration. [^2] + - Add Schedule. [^3] + - Add Migration. [^4] 5. Add actorstype to the NewActorRegistry in `/chain/consensus/computestate.go`. - Add `inv.Register(actorstypes.Version(XX+1), vm.ActorsVersionPredicate(actorstypes.Version(XX+1)), builtin.MakeRegistry(actorstypes.Version(XX+1))`. @@ -118,18 +147,171 @@ You can take a look at this [Go-State-Types PR as a reference](https://github.co 10. Run `make docsgen-cli`. -And you're done! This should create a network upgrade skeleton that you are able to run locally with your local go-state-types clones, and a mock Actors-bundle. This will allow you to: +And you're done! These are all the steps necessary to create a network upgrade skeleton that you will be able to run in a local devnet, and creates a basis where you can start testing new FIPs. When running a local developer network from this Lotus branch, bringing in all it dependencies, you should be able to: - Have a local developer network that starts at the current network version. -- Be able to see the Actor CIDs/Actor version for the mock v12-bundle through `lotus state actor-cids --network-version XX+1` +- Be able to see the Actor CIDs/Actor version for the mock Actor-bundle through `lotus state actor-cids --network-version XX+1` - Have a successful pre-migration. -- Complete Migration at upgrade epoch, but fail immidiately after the upgrade. +- Complete the migration at upgrade epoch, with a succesful upgrade. +- Sync the new network version with the mock actor bundle, and be able to see that you are on a new network version with `lotus state network-version` -You can take a look at this [Lotus PR as a reference](https://github.com/filecoin-project/lotus/pull/11432), which added the skeleton for network version 22. +You can take a look at this [Lotus PR as a reference](https://github.com/filecoin-project/lotus/pull/11897), which added the skeleton for network version 23. -// TODO: Create a video-tutorial going through all the steps +[^1]: Here is system.go template for a simple migration: -[^1]: Here is an example of how you can add a schedule: + ```go + package migration + + import ( + "context" + + system14 "github.com/filecoin-project/go-state-types/builtin/v14/system" + + "github.com/filecoin-project/go-state-types/migration" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + ) + + // System Actor migrator + type systemActorMigrator struct { + OutCodeCID cid.Cid + ManifestData cid.Cid + } + + func (m systemActorMigrator) MigratedCodeCID() cid.Cid { + return m.OutCodeCID + } + + func (m systemActorMigrator) MigrateState(ctx context.Context, store cbor.IpldStore, in migration.ActorMigrationInput) (*migration.ActorMigrationResult, error) { + // The ManifestData itself is already in the blockstore + state := system14.State{BuiltinActors: m.ManifestData} + stateHead, err := store.Put(ctx, &state) + if err != nil { + return nil, err + } + + return &migration.ActorMigrationResult{ + NewCodeCID: m.OutCodeCID, + NewHead: stateHead, + }, nil + } + + func (m systemActorMigrator) Deferred() bool { + return false + } + ``` + +[^2]: Here is top.go template for a simple migration: + + ```go + package migration + + import ( + "context" + + adt14 "github.com/filecoin-project/go-state-types/builtin/v14/util/adt" + + system13 "github.com/filecoin-project/go-state-types/builtin/v13/system" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/go-state-types/migration" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + ) + + // MigrateStateTree Migrates the filecoin state tree starting from the global state tree and upgrading all actor state. + // The store must support concurrent writes (even if the configured worker count is 1). + func MigrateStateTree(ctx context.Context, store cbor.IpldStore, newManifestCID cid.Cid, actorsRootIn cid.Cid, priorEpoch abi.ChainEpoch, cfg migration.Config, log migration.Logger, cache migration.MigrationCache) (cid.Cid, error) { + if cfg.MaxWorkers <= 0 { + return cid.Undef, xerrors.Errorf("invalid migration config with %d workers", cfg.MaxWorkers) + } + + adtStore := adt14.WrapStore(ctx, store) + + // Load input and output state trees + actorsIn, err := builtin.LoadTree(adtStore, actorsRootIn) + if err != nil { + return cid.Undef, xerrors.Errorf("loading state tree: %w", err) + } + + // load old manifest data + systemActor, ok, err := actorsIn.GetActorV5(builtin.SystemActorAddr) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to get system actor: %w", err) + } + + if !ok { + return cid.Undef, xerrors.New("didn't find system actor") + } + + var systemState system13.State + if err := store.Get(ctx, systemActor.Head, &systemState); err != nil { + return cid.Undef, xerrors.Errorf("failed to get system actor state: %w", err) + } + + var oldManifestData manifest.ManifestData + if err := store.Get(ctx, systemState.BuiltinActors, &oldManifestData); err != nil { + return cid.Undef, xerrors.Errorf("failed to get old manifest data: %w", err) + } + + // load new manifest + var newManifest manifest.Manifest + if err := adtStore.Get(ctx, newManifestCID, &newManifest); err != nil { + return cid.Undef, xerrors.Errorf("error reading actor manifest: %w", err) + } + + if err := newManifest.Load(ctx, adtStore); err != nil { + return cid.Undef, xerrors.Errorf("error loading actor manifest: %w", err) + } + + // Maps prior version code CIDs to migration functions. + migrations := make(map[cid.Cid]migration.ActorMigration) + // Set of prior version code CIDs for actors to defer during iteration, for explicit migration afterwards. + deferredCodeIDs := make(map[cid.Cid]struct{}) + + for _, oldEntry := range oldManifestData.Entries { + newCodeCID, ok := newManifest.Get(oldEntry.Name) + if !ok { + return cid.Undef, xerrors.Errorf("code cid for %s actor not found in new manifest", oldEntry.Name) + } + migrations[oldEntry.Code] = migration.CachedMigration(cache, migration.CodeMigrator{OutCodeCID: newCodeCID}) + } + + // migrations that migrate both code and state, override entries in `migrations` + + // The System Actor + + newSystemCodeCID, ok := newManifest.Get(manifest.SystemKey) + if !ok { + return cid.Undef, xerrors.Errorf("code cid for system actor not found in new manifest") + } + + migrations[systemActor.Code] = systemActorMigrator{OutCodeCID: newSystemCodeCID, ManifestData: newManifest.Data} + + if len(migrations)+len(deferredCodeIDs) != len(oldManifestData.Entries) { + return cid.Undef, xerrors.Errorf("incomplete migration specification with %d code CIDs, need %d", len(migrations)+len(deferredCodeIDs), len(oldManifestData.Entries)) + } + + actorsOut, err := migration.RunMigration(ctx, cfg, cache, store, log, actorsIn, migrations) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to run migration: %w", err) + } + + outCid, err := actorsOut.Flush() + if err != nil { + return cid.Undef, xerrors.Errorf("failed to flush actorsOut: %w", err) + } + + return outCid, nil + } + ``` + +[^3]: Here is an example of how you can add a schedule: ```go { @@ -148,7 +330,7 @@ You can take a look at this [Lotus PR as a reference](https://github.com/filecoi This schedule should be added to the `DefaultUpgradeSchedule` function, specifically within the `updates` array. -[^2]: Here is an example of how you can add a migration: +[^4]: Here is an example of how you can add a migration: ```go func PreUpgradeActorsV(XX+1)(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { @@ -189,7 +371,7 @@ You can take a look at this [Lotus PR as a reference](https://github.com/filecoi } newRoot, err := upgradeActorsV(XX+1)Common(ctx, sm, cache, root, epoch, ts, config) if err != nil { - return cid.Undef, xerrors.Errorf("migrating actors v11 state: %w", err) + return cid.Undef, xerrors.Errorf("migrating actors vXX state: %w", err) } return newRoot, nil } @@ -214,21 +396,21 @@ You can take a look at this [Lotus PR as a reference](https://github.com/filecoi if stateRoot.Version != types.StateTreeVersion5 { return cid.Undef, xerrors.Errorf( - "expected state root version 5 for actors v(XX+1) upgrade, got %d", + "expected state root version 5 for actors vXX+1 upgrade, got %d", stateRoot.Version, ) } manifest, ok := actors.GetManifest(actorstypes.Version(XX+1)) if !ok { - return cid.Undef, xerrors.Errorf("no manifest CID for v(XX+1) upgrade") + return cid.Undef, xerrors.Errorf("no manifest CID for vXX+1 upgrade") } // Perform the migration newHamtRoot, err := nv(XX+1).MigrateStateTree(ctx, adtStore, manifest, stateRoot.Actors, epoch, config, migrationLogger{}, cache) if err != nil { - return cid.Undef, xerrors.Errorf("upgrading to actors v11: %w", err) + return cid.Undef, xerrors.Errorf("upgrading to actors vXX+1: %w", err) } // Persist the result. @@ -252,4 +434,4 @@ You can take a look at this [Lotus PR as a reference](https://github.com/filecoi return newRoot, nil } - ``` \ No newline at end of file + ``` diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index ed08caaf8..081367cae 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit ed08caaf8778e1b6def83efd37fce41574214353 +Subproject commit 081367cae7cdfe87d8b7240a9c3767ce86a40b05 diff --git a/gateway/node.go b/gateway/node.go index e9c695c4a..0d7a04109 100644 --- a/gateway/node.go +++ b/gateway/node.go @@ -146,6 +146,7 @@ type TargetAPI interface { Web3ClientVersion(ctx context.Context) (string, error) EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error) EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) + EthTraceTransaction(ctx context.Context, txHash string) ([]*ethtypes.EthTraceTransaction, error) GetActorEventsRaw(ctx context.Context, filter *types.ActorEventFilter) ([]*types.ActorEvent, error) SubscribeActorEventsRaw(ctx context.Context, filter *types.ActorEventFilter) (<-chan *types.ActorEvent, error) diff --git a/gateway/proxy_eth.go b/gateway/proxy_eth.go index 218cc189d..eca6ae2bf 100644 --- a/gateway/proxy_eth.go +++ b/gateway/proxy_eth.go @@ -621,6 +621,14 @@ func (gw *Node) EthTraceReplayBlockTransactions(ctx context.Context, blkNum stri return gw.target.EthTraceReplayBlockTransactions(ctx, blkNum, traceTypes) } +func (gw *Node) EthTraceTransaction(ctx context.Context, txHash string) ([]*ethtypes.EthTraceTransaction, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + + return gw.target.EthTraceTransaction(ctx, txHash) +} + var EthMaxFiltersPerConn = 16 // todo make this configurable func addUserFilterLimited(ctx context.Context, cb func() (ethtypes.EthFilterID, error)) (ethtypes.EthFilterID, error) { diff --git a/gen/inlinegen-data.json b/gen/inlinegen-data.json index 70c8fff61..fd5569436 100644 --- a/gen/inlinegen-data.json +++ b/gen/inlinegen-data.json @@ -1,7 +1,7 @@ { - "actorVersions": [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], - "latestActorsVersion": 13, + "actorVersions": [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], + "latestActorsVersion": 14, - "networkVersions": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22], - "latestNetworkVersion": 22 + "networkVersions": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23], + "latestNetworkVersion": 23 } diff --git a/go.mod b/go.mod index 5bef6d7fa..d47529e54 100644 --- a/go.mod +++ b/go.mod @@ -12,12 +12,9 @@ require ( github.com/DataDog/zstd v1.4.5 github.com/GeertJohan/go.rice v1.0.3 github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee - github.com/KarpelesLab/reflink v1.0.1 github.com/Kubuxu/imtui v0.0.0-20210401140320-41663d68d0fa github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 - github.com/buger/goterm v1.0.3 - github.com/charmbracelet/lipgloss v0.10.0 github.com/chzyer/readline v1.5.1 github.com/containerd/cgroups v1.1.0 github.com/coreos/go-systemd/v22 v22.5.0 @@ -32,7 +29,6 @@ require ( github.com/elastic/gosigar v0.14.2 github.com/etclabscore/go-openrpc-reflect v0.0.36 github.com/fatih/color v1.15.0 - github.com/filecoin-project/dagstore v0.5.2 github.com/filecoin-project/filecoin-ffi v0.30.4-0.20220519234331-bfd1f5f9fe38 github.com/filecoin-project/go-address v1.1.0 github.com/filecoin-project/go-amt-ipld/v4 v4.3.0 @@ -41,15 +37,12 @@ require ( github.com/filecoin-project/go-commp-utils v0.1.3 github.com/filecoin-project/go-commp-utils/nonffi v0.0.0-20220905160352-62059082a837 github.com/filecoin-project/go-crypto v0.0.1 - github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc8 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.28.3 github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0 - github.com/filecoin-project/go-jsonrpc v0.3.1 + github.com/filecoin-project/go-jsonrpc v0.3.2 github.com/filecoin-project/go-padreader v0.0.1 github.com/filecoin-project/go-paramfetch v0.0.4 - github.com/filecoin-project/go-state-types v0.13.3 + github.com/filecoin-project/go-state-types v0.14.0-dev github.com/filecoin-project/go-statemachine v1.0.3 github.com/filecoin-project/go-statestore v0.2.0 github.com/filecoin-project/go-storedcounter v0.1.0 @@ -65,7 +58,7 @@ require ( github.com/filecoin-project/test-vectors/schema v0.0.7 github.com/gbrlsnchs/jwt/v3 v3.0.1 github.com/gdamore/tcell/v2 v2.2.0 - github.com/georgysavva/scany/v2 v2.0.0 + github.com/georgysavva/scany/v2 v2.1.3 github.com/go-openapi/spec v0.19.11 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 @@ -91,23 +84,19 @@ require ( github.com/ipfs/go-ds-leveldb v0.5.0 github.com/ipfs/go-ds-measure v0.2.0 github.com/ipfs/go-fs-lock v0.0.7 - github.com/ipfs/go-graphsync v0.17.0 - github.com/ipfs/go-ipfs-blocksutil v0.0.1 github.com/ipfs/go-ipld-cbor v0.1.0 github.com/ipfs/go-ipld-format v0.6.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-metrics-interface v0.0.1 github.com/ipfs/go-metrics-prometheus v0.0.2 - github.com/ipfs/go-unixfsnode v1.9.0 github.com/ipld/go-car v0.6.2 github.com/ipld/go-car/v2 v2.13.1 - github.com/ipld/go-codec-dagpb v1.6.0 github.com/ipld/go-ipld-prime v0.21.0 github.com/ipld/go-ipld-selector-text-lite v0.0.1 github.com/ipni/go-libipni v0.0.8 - github.com/ipni/index-provider v0.12.0 - github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa + github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 github.com/kelseyhightower/envconfig v1.4.0 + github.com/klauspost/compress v1.17.8 github.com/koalacxr/quantile v0.0.1 github.com/libp2p/go-buffer-pool v0.1.0 github.com/libp2p/go-libp2p v0.34.1 @@ -121,17 +110,14 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-sqlite3 v1.14.16 github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 - github.com/minio/sha256-simd v1.0.1 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 github.com/multiformats/go-multiaddr v0.12.4 github.com/multiformats/go-multiaddr-dns v0.3.1 - github.com/multiformats/go-multibase v0.2.0 github.com/multiformats/go-multicodec v0.9.0 github.com/multiformats/go-multihash v0.2.3 github.com/multiformats/go-varint v0.0.7 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 - github.com/pkg/errors v0.9.1 github.com/polydawn/refmt v0.89.0 github.com/prometheus/client_golang v1.19.1 github.com/puzpuzpuz/xsync/v2 v2.4.0 @@ -160,12 +146,10 @@ require ( go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.23.0 - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 golang.org/x/net v0.25.0 golang.org/x/sync v0.7.0 golang.org/x/sys v0.20.0 golang.org/x/term v0.20.0 - golang.org/x/text v0.15.0 golang.org/x/time v0.5.0 golang.org/x/tools v0.21.0 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 @@ -180,12 +164,9 @@ require ( github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/akavel/rsrc v0.8.0 // indirect - github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bep/debounce v1.2.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -204,7 +185,6 @@ require ( github.com/etclabscore/go-jsonschema-walk v0.0.6 // indirect github.com/filecoin-project/go-amt-ipld/v2 v2.1.0 // indirect github.com/filecoin-project/go-amt-ipld/v3 v3.1.0 // indirect - github.com/filecoin-project/go-ds-versioning v0.1.2 // indirect github.com/filecoin-project/go-hamt-ipld v0.1.5 // indirect github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0 // indirect github.com/flynn/noise v1.1.0 // indirect @@ -227,12 +207,11 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20240509144519-723abb6459b7 // indirect - github.com/hannahhoward/cbor-gen-for v0.0.0-20230214144701-5d17c9d5243c // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/iancoleman/orderedmap v0.1.0 // indirect - github.com/ipfs/go-bitfield v1.1.0 // indirect github.com/ipfs/go-blockservice v0.5.2 // indirect github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect github.com/ipfs/go-ipfs-delay v0.0.1 // indirect @@ -241,33 +220,28 @@ require ( github.com/ipfs/go-ipfs-pq v0.0.3 // indirect github.com/ipfs/go-ipfs-util v0.0.3 // indirect github.com/ipfs/go-ipld-legacy v0.2.1 // indirect - github.com/ipfs/go-libipfs v0.7.0 // indirect github.com/ipfs/go-log v1.0.5 // indirect github.com/ipfs/go-merkledag v0.11.0 // indirect github.com/ipfs/go-peertaskqueue v0.8.1 // indirect github.com/ipfs/go-verifcid v0.0.3 // indirect - github.com/ipld/go-ipld-adl-hamt v0.0.0-20220616142416-9004dbd839e0 // indirect + github.com/ipld/go-codec-dagpb v1.6.0 // indirect github.com/ipsn/go-secp256k1 v0.0.0-20180726113642-9d62b9f0bc52 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect github.com/jackc/pgx/v5 v5.4.1 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect - github.com/jbenet/go-random v0.0.0-20190219211222-123a90aedc0c // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/goprocess v0.1.4 // indirect github.com/jessevdk/go-flags v1.4.0 // indirect github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/jpillora/backoff v1.0.0 // indirect github.com/kilic/bls12-381 v0.1.0 // indirect - github.com/klauspost/compress v1.17.8 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/koron/go-ssdp v0.0.4 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.1.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect - github.com/libp2p/go-libp2p-gostream v0.6.0 // indirect github.com/libp2p/go-libp2p-kbucket v0.6.3 // indirect github.com/libp2p/go-nat v0.2.0 // indirect github.com/libp2p/go-netroute v0.2.1 // indirect @@ -282,11 +256,11 @@ require ( github.com/miekg/dns v1.1.59 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect + github.com/minio/sha256-simd v1.0.1 // indirect github.com/mr-tron/base58 v1.2.0 // indirect - github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.15.2 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect + github.com/multiformats/go-multibase v0.2.0 // indirect github.com/multiformats/go-multistream v0.5.0 // indirect github.com/nikkolasg/hexjson v0.1.0 // indirect github.com/nkovacs/streamquote v1.0.0 // indirect @@ -311,6 +285,7 @@ require ( github.com/pion/transport/v2 v2.2.5 // indirect github.com/pion/turn/v2 v2.1.6 // indirect github.com/pion/webrtc/v3 v3.2.40 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.53.0 // indirect @@ -325,7 +300,6 @@ require ( github.com/sirupsen/logrus v1.9.2 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/tidwall/gjson v1.14.4 // indirect - github.com/twmb/murmur3 v1.1.6 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.0.1 // indirect github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect @@ -343,7 +317,9 @@ require ( go.uber.org/dig v1.17.1 // indirect go.uber.org/mock v0.4.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/mod v0.17.0 // indirect + golang.org/x/text v0.15.0 // indirect gonum.org/v1/gonum v0.15.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect google.golang.org/grpc v1.64.0 // indirect diff --git a/go.sum b/go.sum index 4de7bda14..43c6e4085 100644 --- a/go.sum +++ b/go.sum @@ -36,13 +36,11 @@ contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxa contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ= 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= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -60,7 +58,6 @@ github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee h1:8doiS7ib3zi6/K1 github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee/go.mod h1:W0GbEAA4uFNYOGG2cJpmFJ04E6SD1NLELPYZB57/7AY= github.com/Jorropo/jsync v1.0.1 h1:6HgRolFZnsdfzRUj+ImB9og1JYOxQoReSywkHOGSaUU= github.com/Jorropo/jsync v1.0.1/go.mod h1:jCOZj3vrBCri3bSU3ErUYvevKlnbssrXeCivybS5ABQ= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/Kubuxu/imtui v0.0.0-20210401140320-41663d68d0fa h1:1PPxEyGdIGVkX/kqMvLJ95a1dGS1Sz7tpNEgehEYYt0= github.com/Kubuxu/imtui v0.0.0-20210401140320-41663d68d0fa/go.mod h1:WUmMvh9wMtqj1Xhf1hf3kp9RvL+y6odtdYxpyZjb90U= @@ -73,17 +70,13 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/Stebalien/go-bitfield v0.0.1/go.mod h1:GNjFpasyUVkHMsfEOk8EFLJ9syQ6SI+XWrX9Wf2XH0s= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 h1:T3+cD5fYvuH36h7EZq+TDpm+d8a6FSD4pQsbmuGGQ8o= @@ -97,20 +90,9 @@ github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8V github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4L0zgAOR8lTQK9VlyBVVd7G4omaOQs= github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/ardanlabs/darwin/v2 v2.0.0 h1:XCisQMgQ5EG+ZvSEcADEo+pyfIMKyWAGnn5o2TgriYE= github.com/ardanlabs/darwin/v2 v2.0.0/go.mod h1:MubZ2e9DAYGaym0mClSOi183NYahrrfKxvSy1HMhoes= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -121,16 +103,10 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= -github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190605094302-a0d1e3e36d50/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= -github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= -github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= github.com/btcsuite/btcd v0.24.0 h1:gL3uHE/IaFj6fcZSu03SvqPMSx7s/dPzfpG/atRwWdo= github.com/btcsuite/btcd v0.24.0/go.mod h1:K4IDc1593s8jKXIF7yS7yCTSxrknB9z0STzc2j6XgE4= github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE= @@ -143,24 +119,17 @@ github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9 github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/buger/goterm v1.0.3 h1:7V/HeAQHrzPk/U4BvyH2g9u+xbUW9nr4yRPyG59W4fM= -github.com/buger/goterm v1.0.3/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -168,9 +137,6 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= -github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= -github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= @@ -183,15 +149,12 @@ github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38 github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.9.1 h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4= github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY= -github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/cockroach-go/v2 v2.2.0 h1:/5znzg5n373N/3ESjHF5SMLxiW4RKB05Ql//KWfeTFs= github.com/cockroachdb/cockroach-go/v2 v2.2.0/go.mod h1:u3MiKYGupPPjkn3ozknpMUpxPaNLTFWAya419/zv6eI= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= @@ -200,13 +163,10 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 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/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -215,7 +175,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHH github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crackcomm/go-gitignore v0.0.0-20231225121904-e25f5bc08668 h1:ZFUue+PNxmHlu7pYv+IYMtqlaO/0VwaGEqKepZf9JpA= github.com/crackcomm/go-gitignore v0.0.0-20231225121904-e25f5bc08668/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis= @@ -225,7 +184,6 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= @@ -237,19 +195,14 @@ github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0 github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e h1:lj77EKYUpYXTd8CD/+QMIf8b6OIOTsfEBSXiAzuEHTU= github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e/go.mod h1:3ZQK6DMPSz/QZ73jlWxBtUhNA8xZx7LzUFSq/OfP8vk= github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= -github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= -github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= -github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= github.com/dgraph-io/badger/v2 v2.2007.3/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= -github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= @@ -263,14 +216,9 @@ github.com/drand/kyber v1.3.0 h1:TVd7+xoRgKQ4Ck1viNLPFy6IWhuZM36Bq6zDXD8Asls= github.com/drand/kyber v1.3.0/go.mod h1:f+mNHjiGT++CuueBrpeMhFNdKZAsy0tu03bKq9D5LPA= github.com/drand/kyber-bls12381 v0.3.1 h1:KWb8l/zYTP5yrvKTgvhOrk2eNPscbMiUOIeWBnmUxGo= github.com/drand/kyber-bls12381 v0.3.1/go.mod h1:H4y9bLPu7KZA/1efDg+jtJ7emKx+ro3PU7/jWUVt140= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elastic/go-elasticsearch/v7 v7.14.0 h1:extp3jos/rwJn3J+lgbaGlwAgs0TVsIHme00GyNAyX4= github.com/elastic/go-elasticsearch/v7 v7.14.0/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4= github.com/elastic/go-sysinfo v1.7.0 h1:4vVvcfi255+8+TyQ7TYUTEK3A+G8v5FLE+ZKYL1z1Dg= @@ -280,7 +228,6 @@ github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6 github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4= github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -289,14 +236,11 @@ github.com/etclabscore/go-jsonschema-walk v0.0.6 h1:DrNzoKWKd8f8XB5nFGBY00IcjakR github.com/etclabscore/go-jsonschema-walk v0.0.6/go.mod h1:VdfDY72AFAiUhy0ZXEaWSpveGjMT5JcDIm903NGqFwQ= github.com/etclabscore/go-openrpc-reflect v0.0.36 h1:kSqNB2U8RVoW4si+4fsv13NGNkRAQ5j78zTUx1qiehk= github.com/etclabscore/go-openrpc-reflect v0.0.36/go.mod h1:0404Ky3igAasAOpyj1eESjstTyneBAIk5PgJFbK4s5E= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.8.0/go.mod h1:3l45GVGkyrnYNl9HoIjnp2NnNWvh6hLAqD8yTfGjnw8= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/filecoin-project/dagstore v0.5.2 h1:Nd6oXdnolbbVhpMpkYT5PJHOjQp4OBSntHpMV5pxj3c= -github.com/filecoin-project/dagstore v0.5.2/go.mod h1:mdqKzYrRBHf1pRMthYfMv3n37oOw0Tkx7+TxPt240M0= 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/go.mod h1:jr8JxKsYx+lQlQZmF5i2U0Z+cGQ59wMIps/8YW/lDj8= github.com/filecoin-project/go-address v1.1.0 h1:ofdtUtEsNxkIxkDw67ecSmvtzaVSdcea4boAmLbnHfE= @@ -324,17 +268,9 @@ github.com/filecoin-project/go-commp-utils/nonffi v0.0.0-20220905160352-62059082 github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03/go.mod h1:+viYnvGtUTgJRdy6oaeF4MTFKAfatX071MPDPBL11EQ= github.com/filecoin-project/go-crypto v0.0.1 h1:AcvpSGGCgjaY8y1az6AMfKQWreF/pWO2JJGLl6gCq6o= github.com/filecoin-project/go-crypto v0.0.1/go.mod h1:+viYnvGtUTgJRdy6oaeF4MTFKAfatX071MPDPBL11EQ= -github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc8 h1:EWC89lM/tJAjyzaxZ624clq3oyHLoLjISfoyG+WIu9s= -github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc8/go.mod h1:mK3/NbSljx3Kr335+IXEe8gcdEPA2eZXJaNhodK9bAI= -github.com/filecoin-project/go-ds-versioning v0.1.2 h1:to4pTadv3IeV1wvgbCbN6Vqd+fu+7tveXgv/rCEZy6w= -github.com/filecoin-project/go-ds-versioning v0.1.2/go.mod h1:C9/l9PnB1+mwPa26BBVpCjG/XQCB0yj/q5CK2J8X1I4= 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-commcid v0.1.0 h1:3R4ds1A9r6cr8mvZBfMYxTS88OqLYEo6roi+GiIeOh8= github.com/filecoin-project/go-fil-commcid v0.1.0/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= -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.28.3 h1:2cFu7tLZYrfNz4LnxjgERaVD7k5+Wwp0H76mnnTGPBk= -github.com/filecoin-project/go-fil-markets v1.28.3/go.mod h1:eryxo/oVgIxaR5g5CNr9PlvZOi+u/bak0IsPL/PT1hk= 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= @@ -342,15 +278,13 @@ github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0/go.mod h1:7aWZdaQ1b16BVoQUYR+ github.com/filecoin-project/go-hamt-ipld/v3 v3.0.1/go.mod h1:gXpNmr3oQx8l3o7qkGyDjJjYSRX7hp/FGOStdqrWyDI= github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0 h1:rVVNq0x6RGQIzCo1iiJlGFm9AGIZzeifggxtKMU7zmI= github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0/go.mod h1:bxmzgT8tmeVQA1/gvBwFmYdT8SOFUwB3ovSUfG1Ux0g= -github.com/filecoin-project/go-jsonrpc v0.3.1 h1:qwvAUc5VwAkooquKJmfz9R2+F8znhiqcNHYjEp/NM10= -github.com/filecoin-project/go-jsonrpc v0.3.1/go.mod h1:jBSvPTl8V1N7gSTuCR4bis8wnQnIjHbRPpROol6iQKM= +github.com/filecoin-project/go-jsonrpc v0.3.2 h1:uuAWTZe6B3AUUta+O26HlycGoej/yiaI1fXp3Du+D3I= +github.com/filecoin-project/go-jsonrpc v0.3.2/go.mod h1:jBSvPTl8V1N7gSTuCR4bis8wnQnIjHbRPpROol6iQKM= github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20/go.mod h1:mPn+LRRd5gEKNAtc+r3ScpW2JRU/pj4NBKdADYWHiak= github.com/filecoin-project/go-padreader v0.0.1 h1:8h2tVy5HpoNbr2gBRr+WD6zV6VD6XHig+ynSGJg8ZOs= github.com/filecoin-project/go-padreader v0.0.1/go.mod h1:VYVPJqwpsfmtoHnAmPx6MUwmrK6HIcDqZJiuZhtmfLQ= github.com/filecoin-project/go-paramfetch v0.0.4 h1:H+Me8EL8T5+79z/KHYQQcT8NVOzYVqXIi7nhb48tdm8= github.com/filecoin-project/go-paramfetch v0.0.4/go.mod h1:1FH85P8U+DUEmWk1Jkw3Bw7FrwTVUNHk/95PSPG+dts= -github.com/filecoin-project/go-retrieval-types v1.2.0 h1:fz6DauLVP3GRg7UuW7HZ6sE+GTmaUW70DTXBF1r9cK0= -github.com/filecoin-project/go-retrieval-types v1.2.0/go.mod h1:ojW6wSw2GPyoRDBGqw1K6JxUcbfa5NOSIiyQEeh7KK0= github.com/filecoin-project/go-state-types v0.0.0-20200903145444-247639ffa6ad/go.mod h1:IQ0MBPnonv35CJHtWSN3YY1Hz2gkPru1Q9qoaYLxx9I= github.com/filecoin-project/go-state-types v0.0.0-20200928172055-2df22083d8ab/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-state-types v0.0.0-20201102161440-c8033295a1fc/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= @@ -358,9 +292,8 @@ github.com/filecoin-project/go-state-types v0.1.0/go.mod h1:ezYnPf0bNkTsDibL/psS github.com/filecoin-project/go-state-types v0.1.6/go.mod h1:UwGVoMsULoCK+bWjEdd/xLCvLAQFBC7EDT477SKml+Q= github.com/filecoin-project/go-state-types v0.1.10/go.mod h1:UwGVoMsULoCK+bWjEdd/xLCvLAQFBC7EDT477SKml+Q= github.com/filecoin-project/go-state-types v0.13.1/go.mod h1:cHpOPup9H1g2T29dKHAjC2sc7/Ef5ypjuW9A3I+e9yY= -github.com/filecoin-project/go-state-types v0.13.3 h1:9JPkC0E6HDtfHbaOIrFiCDzT/Z0jRTb9En4Y4Ci/b3w= -github.com/filecoin-project/go-state-types v0.13.3/go.mod h1:cHpOPup9H1g2T29dKHAjC2sc7/Ef5ypjuW9A3I+e9yY= -github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe/go.mod h1:FGwQgZAt2Gh5mjlwJUlVB62JeYdo+if0xWxSEfBD9ig= +github.com/filecoin-project/go-state-types v0.14.0-dev h1:bDwq1S28D7EC/uDmKU8vvNcdFw/YDsNq09pe3zeV5h4= +github.com/filecoin-project/go-state-types v0.14.0-dev/go.mod h1:cHpOPup9H1g2T29dKHAjC2sc7/Ef5ypjuW9A3I+e9yY= github.com/filecoin-project/go-statemachine v1.0.3 h1:N07o6alys+V1tNoSTi4WuuoeNC4erS/6jE74+NsgQuk= github.com/filecoin-project/go-statemachine v1.0.3/go.mod h1:jZdXXiHa61n4NmgWFG4w8tnqgvZVHYbJ3yW7+y8bF54= github.com/filecoin-project/go-statestore v0.1.0/go.mod h1:LFc9hD+fRxPqiHiaqUEZOinUJB4WARkRfNl10O7kTnI= @@ -391,15 +324,11 @@ github.com/filecoin-project/specs-actors/v8 v8.0.1/go.mod h1:UYIPg65iPWoFw5NEftR github.com/filecoin-project/test-vectors/schema v0.0.7 h1:hhrcxLnQR2Oe6fjk63hZXG1fWQGyxgCVXOOlAlR/D9A= github.com/filecoin-project/test-vectors/schema v0.0.7/go.mod h1:WqdmeJrz0V37wp7DucRR/bvrScZffqaCyIk9G0BGw1o= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= -github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= @@ -413,8 +342,8 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdk github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell/v2 v2.2.0 h1:vSyEgKwraXPSOkvCk7IwOSyX+Pv3V2cV9CikJMXg4U4= github.com/gdamore/tcell/v2 v2.2.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU= -github.com/georgysavva/scany/v2 v2.0.0 h1:RGXqxDv4row7/FYoK8MRXAZXqoWF/NM+NP0q50k3DKU= -github.com/georgysavva/scany/v2 v2.0.0/go.mod h1:sigOdh+0qb/+aOs3TVhehVT10p8qJL7K/Zhyz8vWo38= +github.com/georgysavva/scany/v2 v2.1.3 h1:Zd4zm/ej79Den7tBSU2kaTDPAH64suq4qlQdhiBeGds= +github.com/georgysavva/scany/v2 v2.1.3/go.mod h1:fqp9yHZzM/PFVa3/rYEC57VmDx+KDch0LoqrJzkvtos= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= @@ -426,7 +355,6 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= @@ -437,7 +365,6 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -461,7 +388,6 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.19.8/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-openapi/swag v0.19.11 h1:RFTu/dlFySpyVvJDfp/7674JY4SDglYWKztbiIGFpmc= github.com/go-openapi/swag v0.19.11/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -476,13 +402,10 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -491,9 +414,7 @@ github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -545,7 +466,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -554,7 +474,6 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN 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= -github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -569,10 +488,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20240509144519-723abb6459b7 h1:velgFPYr1X9TDwLIfkV7fWqsFlf7TeP11M/7kPd/dVI= github.com/google/pprof v0.0.0-20240509144519-723abb6459b7/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= 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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -585,55 +502,32 @@ github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORR github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gregdhill/go-openrpc v0.0.0-20220114144539-ae6f44720487 h1:NyaWOSkqFK1d9o+HLfnMIGzrHuUUPeBNIZyi5Zoe/lY= github.com/gregdhill/go-openrpc v0.0.0-20220114144539-ae6f44720487/go.mod h1:a1eRkbhd3DYpRH2lnuUsVG+QMTI+v0hGnsis8C9hMrA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 h1:BpJ2o0OR5FV7vrkDYfXYVJQeMNWa8RhklZOpW2ITAIQ= github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= -github.com/hannahhoward/cbor-gen-for v0.0.0-20230214144701-5d17c9d5243c h1:iiD+p+U0M6n/FsO6XIZuOgobnNa48FxtyYFfWwLttUQ= -github.com/hannahhoward/cbor-gen-for v0.0.0-20230214144701-5d17c9d5243c/go.mod h1:jvfsLIxk0fY/2BKSQ1xf2406AKA5dwMmKKv0ADcOfN8= github.com/hannahhoward/go-pubsub v0.0.0-20200423002714-8d62886cc36e h1:3YKHER4nmd7b5qy5t0GWDTwSn4OyRgfAXSmo6VnryBY= github.com/hannahhoward/go-pubsub v0.0.0-20200423002714-8d62886cc36e/go.mod h1:I8h3MITA53gN9OnWGCgaMa0JWVRdXthWw4M3CPM54OY= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= @@ -644,12 +538,7 @@ github.com/hashicorp/golang-lru/arc/v2 v2.0.7/go.mod h1:Pe7gBlGdc8clY5LJ0LpJXMt5 github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= @@ -665,7 +554,6 @@ github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6 h1:8UsGZ2rr2ksmEru6lTo github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= @@ -679,7 +567,6 @@ github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbG github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= 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.5.1/go.mod h1:P+ckC87ri1xFLvk74NlXdP0Kj9RmWAh4+H78sC6Qopo= github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ= github.com/ipfs/go-bitswap v0.11.0/go.mod h1:05aE8H3XOU+LXpTedeAS0OZpcO1WFsj5niYQH9a1Tmk= github.com/ipfs/go-block-format v0.0.1/go.mod h1:DK/YYcsSUIVAFNwo/KZCdIIbpN0ROH/baNLgayt4pFc= @@ -688,7 +575,6 @@ github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WW github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= github.com/ipfs/go-blockservice v0.1.0/go.mod h1:hzmMScl1kXHg3M2BjTymbVPjv627N7sYcvYaKbop39M= -github.com/ipfs/go-blockservice v0.2.1/go.mod h1:k6SiwmgyYgs4M/qt+ww6amPeUH9EISLRBnvUurKJhi8= github.com/ipfs/go-blockservice v0.5.2 h1:in9Bc+QcXwd1apOVM7Un9t8tixPKdaHQFdLSUM1Xgk8= github.com/ipfs/go-blockservice v0.5.2/go.mod h1:VpMblFEqG67A/H2sHKAemeH9vlURVavlysbdUI632yk= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= @@ -700,7 +586,6 @@ github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67Fexh 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/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= -github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= github.com/ipfs/go-cid v0.2.0/go.mod h1:P+HXFDF4CVhaVayiEb4wkAy7zBHxBwsJyt0Y5U6MLro= github.com/ipfs/go-cid v0.3.2/go.mod h1:gQ8pKqT/sUxGY+tIwy1RPpAojYu7jAyCp5Tz1svoupw= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= @@ -712,10 +597,6 @@ github.com/ipfs/go-datastore v0.0.5/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAK github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= github.com/ipfs/go-datastore v0.3.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= -github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= -github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= -github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= -github.com/ipfs/go-datastore v0.4.5/go.mod h1:eXTcaaiN6uOlVCLS9GjJUJtlvJfM3xk23w3fyfrmmJs= github.com/ipfs/go-datastore v0.5.0/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk= github.com/ipfs/go-datastore v0.5.1/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk= github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= @@ -723,16 +604,11 @@ github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8 github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= -github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= -github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= -github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= github.com/ipfs/go-ds-badger v0.3.0 h1:xREL3V0EH9S219kFFueOYJJTcjgNSZ2HY1iSvN7U1Ro= github.com/ipfs/go-ds-badger v0.3.0/go.mod h1:1ke6mXNqeV8K3y5Ak2bAA0osoTfmxUdupVCGm4QUIek= github.com/ipfs/go-ds-badger2 v0.1.3 h1:Zo9JicXJ1DmXTN4KOw7oPXkspZ0AWHcAFCP1tQKnegg= github.com/ipfs/go-ds-badger2 v0.1.3/go.mod h1:TPhhljfrgewjbtuL/tczP8dNrBYwwk+SdPYbms/NO9w= github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= -github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= -github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ds-leveldb v0.5.0 h1:s++MEBbD3ZKc9/8/njrn4flZLnCuY9I79v94gBUNumo= github.com/ipfs/go-ds-leveldb v0.5.0/go.mod h1:d3XG9RUDzQ6V4SHi8+Xgj9j1XuEk1z82lquxrVbml/Q= github.com/ipfs/go-ds-measure v0.2.0 h1:sG4goQe0KDTccHMyT45CY1XyUbxe5VwTKpg2LjApYyQ= @@ -740,12 +616,8 @@ github.com/ipfs/go-ds-measure v0.2.0/go.mod h1:SEUD/rE2PwRa4IQEC5FuNAmjJCyYObZr9 github.com/ipfs/go-fs-lock v0.0.6/go.mod h1:OTR+Rj9sHiRubJh3dRhD15Juhd/+w6VPOY28L7zESmM= github.com/ipfs/go-fs-lock v0.0.7 h1:6BR3dajORFrFTkb5EpCUFIAypsoxpGpDSVUdFwzgL9U= github.com/ipfs/go-fs-lock v0.0.7/go.mod h1:Js8ka+FNYmgQRLrRXzU3CB/+Csr1BwrRilEcvYrHhhc= -github.com/ipfs/go-graphsync v0.17.0 h1:1gh10v94G/vSGzfApVtbZSvSKkK906Y+2sRqewjDTm4= -github.com/ipfs/go-graphsync v0.17.0/go.mod h1:HXHiTRIw3wrN3InMwdV+IzpBAtreEf/KqFlEibhfVgo= 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.2.1/go.mod h1:jGesd8EtCM3/zPgx+qr0/feTXGUeRai6adgwC+Q+JvE= -github.com/ipfs/go-ipfs-blockstore v1.1.2/go.mod h1:w51tNR9y5+QXB0wkNcHt4O2aSZjTdqaEWaQdSxEyUOY= github.com/ipfs/go-ipfs-blockstore v1.2.0/go.mod h1:eh8eTFLiINYNSNawfZOC7HOxNTxpB1PFuA5E1m/7exE= github.com/ipfs/go-ipfs-blockstore v1.3.1 h1:cEI9ci7V0sRNivqaOr0elDsamxXFxJMMMy7PTTDQNsQ= github.com/ipfs/go-ipfs-blockstore v1.3.1/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE= @@ -758,30 +630,22 @@ github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1Y github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-ds-help v0.0.1/go.mod h1:gtP9xRaZXqIQRh1HRpp595KbBEdgqWFxefeVKOV8sxo= -github.com/ipfs/go-ipfs-ds-help v0.1.1/go.mod h1:SbBafGJuGsPI/QL3j9Fc5YPLeAu+SzOkI0gFwAg+mOs= github.com/ipfs/go-ipfs-ds-help v1.1.0/go.mod h1:YR5+6EaebOhfcqVCyqemItCLthrpVNot+rsOU/5IatU= github.com/ipfs/go-ipfs-ds-help v1.1.1 h1:B5UJOH52IbcfS56+Ul+sv8jnIV10lbjLF5eOO0C66Nw= github.com/ipfs/go-ipfs-ds-help v1.1.1/go.mod h1:75vrVCkSdSFidJscs8n4W+77AtTpCIAdDGAwjitJMIo= github.com/ipfs/go-ipfs-exchange-interface v0.0.1/go.mod h1:c8MwfHjtQjPoDyiy9cFquVtVHkO9b9Ob3FG91qJnWCM= -github.com/ipfs/go-ipfs-exchange-interface v0.1.0/go.mod h1:ych7WPlyHqFvCi/uQI48zLZuAWVP5iTQPXEfVaw5WEI= github.com/ipfs/go-ipfs-exchange-interface v0.2.1 h1:jMzo2VhLKSHbVe+mHNzYgs95n0+t0Q69GQ5WhRDZV/s= github.com/ipfs/go-ipfs-exchange-interface v0.2.1/go.mod h1:MUsYn6rKbG6CTtsDp+lKJPmVt3ZrCViNyH3rfPGsZ2E= github.com/ipfs/go-ipfs-exchange-offline v0.0.1/go.mod h1:WhHSFCVYX36H/anEKQboAzpUws3x7UeEGkzQc3iNkM0= -github.com/ipfs/go-ipfs-exchange-offline v0.1.1/go.mod h1:vTiBRIbzSwDD0OWm+i3xeT0mO7jG2cbJYatp3HPk5XY= github.com/ipfs/go-ipfs-exchange-offline v0.3.0 h1:c/Dg8GDPzixGd0MC8Jh6mjOwU57uYokgWRFidfvEkuA= github.com/ipfs/go-ipfs-exchange-offline v0.3.0/go.mod h1:MOdJ9DChbb5u37M1IcbrRB02e++Z7521fMxqCNRrz9s= github.com/ipfs/go-ipfs-files v0.0.3/go.mod h1:INEFm0LL2LWXBhNJ2PMIIb2w45hpXgPjNoE7yA8Y1d4= github.com/ipfs/go-ipfs-files v0.0.4/go.mod h1:INEFm0LL2LWXBhNJ2PMIIb2w45hpXgPjNoE7yA8Y1d4= -github.com/ipfs/go-ipfs-files v0.3.0 h1:fallckyc5PYjuMEitPNrjRfpwl7YFt69heCOUhsbGxQ= -github.com/ipfs/go-ipfs-files v0.3.0/go.mod h1:xAUtYMwB+iu/dtf6+muHNSFQCJG2dSiStR2P6sn9tIM= -github.com/ipfs/go-ipfs-posinfo v0.0.1 h1:Esoxj+1JgSjX0+ylc0hUmJCOv6V2vFoZiETLR6OtpRs= github.com/ipfs/go-ipfs-posinfo v0.0.1/go.mod h1:SwyeVP+jCwiDu0C313l/8jg6ZxM0qqtlt2a0vILTc1A= github.com/ipfs/go-ipfs-pq v0.0.1/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= -github.com/ipfs/go-ipfs-pq v0.0.2/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= github.com/ipfs/go-ipfs-pq v0.0.3 h1:YpoHVJB+jzK15mr/xsWC574tyDLkezVrDNeaalQBsTE= github.com/ipfs/go-ipfs-pq v0.0.3/go.mod h1:btNw5hsHBpRcSSgZtiNm/SLj5gYIZ18AKtv3kERkRb4= github.com/ipfs/go-ipfs-routing v0.1.0/go.mod h1:hYoUkJLyAUKhF58tysKpids8RNDPO42BVMgK5dNsoqY= -github.com/ipfs/go-ipfs-routing v0.2.1/go.mod h1:xiNNiwgjmLqPS1cimvAw6EyB9rkVDbiocA4yY+wRNLM= github.com/ipfs/go-ipfs-routing v0.3.0 h1:9W/W3N+g+y4ZDeffSgqhgo7BsBSJwPMcyssET9OWevc= github.com/ipfs/go-ipfs-routing v0.3.0/go.mod h1:dKqtTFIql7e1zYsEuWLyuOU+E0WJWW8JjbTPLParDWo= github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= @@ -803,24 +667,15 @@ github.com/ipfs/go-ipld-format v0.3.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxn github.com/ipfs/go-ipld-format v0.4.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= github.com/ipfs/go-ipld-format v0.6.0 h1:VEJlA2kQ3LqFSIm5Vu6eIlSxD/Ze90xtc4Meten1F5U= github.com/ipfs/go-ipld-format v0.6.0/go.mod h1:g4QVMTn3marU3qXchwjpKPKgJv+zF+OlaKMyhJ4LHPg= -github.com/ipfs/go-ipld-legacy v0.1.0/go.mod h1:86f5P/srAmh9GcIcWQR9lfFLZPrIyyXQeVlOWeeWEuI= github.com/ipfs/go-ipld-legacy v0.2.1 h1:mDFtrBpmU7b//LzLSypVrXsD8QxkEWxu5qVxN99/+tk= github.com/ipfs/go-ipld-legacy v0.2.1/go.mod h1:782MOUghNzMO2DER0FlBR94mllfdCJCkTtDtPM51otM= -github.com/ipfs/go-libipfs v0.7.0 h1:Mi54WJTODaOL2/ZSm5loi3SwI3jI2OuFWUrQIkJ5cpM= -github.com/ipfs/go-libipfs v0.7.0/go.mod h1:KsIf/03CqhICzyRGyGo68tooiBE2iFbI/rXW7FhAYr0= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= github.com/ipfs/go-log v1.0.0/go.mod h1:JO7RzlMK6rA+CIxFMLOuB6Wf5b81GDiKElL7UPSIKjA= -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/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= github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= -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/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= @@ -831,7 +686,6 @@ github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOL github.com/ipfs/go-merkledag v0.2.3/go.mod h1:SQiXrtSts3KGNmgOzMICy5c0POOpUNQLvB3ClKnBAlk= github.com/ipfs/go-merkledag v0.2.4/go.mod h1:SQiXrtSts3KGNmgOzMICy5c0POOpUNQLvB3ClKnBAlk= github.com/ipfs/go-merkledag v0.3.2/go.mod h1:fvkZNNZixVW6cKSZ/JfLlON5OlgTXNdRLz0p6QG/I2M= -github.com/ipfs/go-merkledag v0.5.1/go.mod h1:cLMZXx8J08idkp5+id62iVftUQV+HlYJ3PIhDfZsjA4= github.com/ipfs/go-merkledag v0.11.0 h1:DgzwK5hprESOzS4O1t/wi6JDpyVQdvm9Bs59N/jqfBY= github.com/ipfs/go-merkledag v0.11.0/go.mod h1:Q4f/1ezvBiJV0YCIXvt51W/9/kqJGH4I1LsA7+djsM4= github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= @@ -839,12 +693,9 @@ github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j github.com/ipfs/go-metrics-prometheus v0.0.2 h1:9i2iljLg12S78OhC6UAiXi176xvQGiZaGVF1CUVdE+s= github.com/ipfs/go-metrics-prometheus v0.0.2/go.mod h1:ELLU99AQQNi+zX6GCGm2lAgnzdSH3u5UVlCdqSXnEks= github.com/ipfs/go-peertaskqueue v0.1.0/go.mod h1:Jmk3IyCcfl1W3jTW3YpghSwSEC6IJ3Vzz/jUmWw8Z0U= -github.com/ipfs/go-peertaskqueue v0.7.0/go.mod h1:M/akTIE/z1jGNXMU7kFB4TeSEFvj68ow0Rrb04donIU= github.com/ipfs/go-peertaskqueue v0.8.1 h1:YhxAs1+wxb5jk7RvS0LHdyiILpNmRIRnZVztekOF0pg= github.com/ipfs/go-peertaskqueue v0.8.1/go.mod h1:Oxxd3eaK279FxeydSPPVGHzbwVeHjatZ2GA8XD+KbPU= github.com/ipfs/go-unixfs v0.2.2-0.20190827150610-868af2e9e5cb/go.mod h1:IwAAgul1UQIcNZzKPYZWOCijryFBeCV79cNubPzol+k= -github.com/ipfs/go-unixfs v0.4.5 h1:wj8JhxvV1G6CD7swACwSKYa+NgtdWC1RUit+gFnymDU= -github.com/ipfs/go-unixfs v0.4.5/go.mod h1:BIznJNvt/gEx/ooRMI4Us9K8+qeGO7vx1ohnbk8gjFg= github.com/ipfs/go-unixfsnode v1.9.0 h1:ubEhQhr22sPAKO2DNsyVBW7YB/zA8Zkif25aBvz8rc8= github.com/ipfs/go-unixfsnode v1.9.0/go.mod h1:HxRu9HYHOjK6HUqFBAi++7DVoWAHn0o4v/nZ/VA+0g8= github.com/ipfs/go-verifcid v0.0.1/go.mod h1:5Hrva5KBeIog4A+UpqlaIU+DEstipcJYQQZc0g37pY0= @@ -853,38 +704,28 @@ github.com/ipfs/go-verifcid v0.0.3/go.mod h1:gcCtGniVzelKrbk9ooUSX/pM3xlH73fZZJD github.com/ipld/go-car v0.1.0/go.mod h1:RCWzaUh2i4mOEkB3W45Vc+9jnS/M6Qay5ooytiBHl3g= github.com/ipld/go-car v0.6.2 h1:Hlnl3Awgnq8icK+ze3iRghk805lu8YNq3wlREDTF2qc= github.com/ipld/go-car v0.6.2/go.mod h1:oEGXdwp6bmxJCZ+rARSkDliTeYnVzv3++eXajZ+Bmr8= -github.com/ipld/go-car/v2 v2.1.1/go.mod h1:+2Yvf0Z3wzkv7NeI69i8tuZ+ft7jyjPYIWZzeVNeFcI= github.com/ipld/go-car/v2 v2.13.1 h1:KnlrKvEPEzr5IZHKTXLAEub+tPrzeAFQVRlSQvuxBO4= github.com/ipld/go-car/v2 v2.13.1/go.mod h1:QkdjjFNGit2GIkpQ953KBwowuoukoM75nP/JI1iDJdo= github.com/ipld/go-codec-dagpb v1.2.0/go.mod h1:6nBN7X7h8EOsEejZGqC7tej5drsdBAXbMHyBT+Fne5s= -github.com/ipld/go-codec-dagpb v1.3.0/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA= github.com/ipld/go-codec-dagpb v1.6.0 h1:9nYazfyu9B1p3NAgfVdpRco3Fs2nFC72DqVsMj6rOcc= github.com/ipld/go-codec-dagpb v1.6.0/go.mod h1:ANzFhfP2uMJxRBr8CE+WQWs5UsNa0pYtmKZ+agnUw9s= -github.com/ipld/go-ipld-adl-hamt v0.0.0-20220616142416-9004dbd839e0 h1:QAI/Ridj0+foHD6epbxmB4ugxz9B4vmNdYSmQLGa05E= -github.com/ipld/go-ipld-adl-hamt v0.0.0-20220616142416-9004dbd839e0/go.mod h1:odxGcpiQZLzP5+yGu84Ljo8y3EzCvNAQKEodHNsHLXA= github.com/ipld/go-ipld-prime v0.0.2-0.20191108012745-28a82f04c785/go.mod h1:bDDSvVz7vaK12FNvMeRYnpRFkSUPNQOiCYQezMD/P3w= github.com/ipld/go-ipld-prime v0.9.0/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= -github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= github.com/ipld/go-ipld-prime v0.10.0/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= -github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHtEaLglS3ZeV8= -github.com/ipld/go-ipld-prime v0.14.0/go.mod h1:9ASQLwUFLptCov6lIYc70GRB4V7UTyLD0IJtrDJe6ZM= github.com/ipld/go-ipld-prime v0.19.0/go.mod h1:Q9j3BaVXwaA3o5JUDNvptDDr/x8+F7FG6XJ8WI3ILg4= github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E= github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ= github.com/ipld/go-ipld-prime-proto v0.0.0-20191113031812-e32bd156a1e5/go.mod h1:gcvzoEDBjwycpXt3LBE061wT9f46szXGHAmj9uoP6fU= -github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73/go.mod h1:2PJ0JgxyB08t0b2WKrcuqI3di0V+5n6RS/LTUJhkoxY= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd h1:gMlw/MhNr2Wtp5RwGdsW23cs+yCuj9k2ON7i9MiJlRo= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd/go.mod h1:wZ8hH8UxeryOs4kJEJaiui/s00hDSbE37OKsL47g+Sw= github.com/ipld/go-ipld-selector-text-lite v0.0.1 h1:lNqFsQpBHc3p5xHob2KvEg/iM5dIFn6iw4L/Hh+kS1Y= github.com/ipld/go-ipld-selector-text-lite v0.0.1/go.mod h1:U2CQmFb+uWzfIEF3I1arrDa5rwtj00PrpiwwCO+k1RM= github.com/ipni/go-libipni v0.0.8 h1:0wLfZRSBG84swmZwmaLKul/iB/FlBkkl9ZcR1ub+Z+w= github.com/ipni/go-libipni v0.0.8/go.mod h1:paYP9U4N3/vOzGCuN9kU972vtvw9JUcQjOKyiCFGwRk= -github.com/ipni/index-provider v0.12.0 h1:R3F6dxxKNv4XkE4GJZNLOG0bDEbBQ/S5iztXwSD8jhQ= -github.com/ipni/index-provider v0.12.0/go.mod h1:GhyrADJp7n06fqoc1djzkvL4buZYHzV8SoWrlxEo5F4= github.com/ipsn/go-secp256k1 v0.0.0-20180726113642-9d62b9f0bc52 h1:QG4CGBqCeuBo6aZlGAamSkxWdgWfZGeE49eUOWJPA4c= github.com/ipsn/go-secp256k1 v0.0.0-20180726113642-9d62b9f0bc52/go.mod h1:fdg+/X9Gg4AsAIzWpEHwnqd+QY3b7lajxyjE1m4hkq4= -github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= -github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= +github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= @@ -899,7 +740,6 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= -github.com/jbenet/go-random v0.0.0-20190219211222-123a90aedc0c h1:uUx61FiAa1GI6ZmVd2wf2vULeQZIKG66eybjNXKYCz4= github.com/jbenet/go-random v0.0.0-20190219211222-123a90aedc0c/go.mod h1:sdx1xVM9UuLw1tXnhJWN3piypTUO3vCIHYmG15KE/dU= github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs= github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= @@ -912,24 +752,19 @@ github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0 github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -940,7 +775,6 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= @@ -956,7 +790,6 @@ github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8 github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 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.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= @@ -967,7 +800,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.2/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= -github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -988,7 +820,6 @@ github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2 github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/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-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= @@ -996,68 +827,23 @@ github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QT github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= -github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= -github.com/libp2p/go-conn-security-multistream v0.2.1/go.mod h1:cR1d8gA0Hr59Fj6NhaTpFhJZrjSYuNmhpT2r25zYR70= -github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= -github.com/libp2p/go-eventbus v0.2.1/go.mod h1:jc2S4SoEVPP48H9Wpzm5aiGwUCBMfGhVhhBjyhhCJs8= github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= -github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= github.com/libp2p/go-libp2p v0.1.0/go.mod h1:6D/2OBauqLUoqcADOJpn9WbKqvaM07tDw68qHM0BxUM= github.com/libp2p/go-libp2p v0.1.1/go.mod h1:I00BRo1UuUSdpuc8Q2mN7yDF/oTUTRAX6JWpTiK9Rp8= -github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54= -github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= -github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= -github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= -github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= github.com/libp2p/go-libp2p v0.34.1 h1:fxn9vyLo7vJcXQRNvdRbyPjbzuQgi2UiqC8hEbn8a18= github.com/libp2p/go-libp2p v0.34.1/go.mod h1:snyJQix4ET6Tj+LeI0VPjjxTtdWpeOhYt5lEY0KirkQ= github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= github.com/libp2p/go-libp2p-autonat v0.1.0/go.mod h1:1tLf2yXxiE/oKGtDwPYWTSYG3PtvYlJmg7NeVtPRqH8= -github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= -github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= -github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= -github.com/libp2p/go-libp2p-autonat v0.2.2/go.mod h1:HsM62HkqZmHR2k1xgX34WuWDzk/nBwNHoeyyT4IWV6A= -github.com/libp2p/go-libp2p-autonat v0.4.2/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= -github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= -github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vMl+t4d8ugUBRQ4SqaNQ= github.com/libp2p/go-libp2p-circuit v0.1.0/go.mod h1:Ahq4cY3V9VJcHcn1SBXjr78AbFkZeIRmfunbA7pmFh8= -github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= -github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= -github.com/libp2p/go-libp2p-circuit v0.4.0/go.mod h1:t/ktoFIUzM6uLQ+o1G6NuBl2ANhBKN9Bc8jRIk31MoA= github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= github.com/libp2p/go-libp2p-core v0.0.2/go.mod h1:9dAcntw/n46XycV4RnlBq3BpgrmyUi9LuoTNdPrbUco= github.com/libp2p/go-libp2p-core v0.0.3/go.mod h1:j+YQMNz9WNSkNezXOsahp9kwZBKBvxLpKD316QWSJXE= -github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= -github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= -github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= -github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= -github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw= -github.com/libp2p/go-libp2p-core v0.3.1/go.mod h1:thvWy0hvaSBhnVBaW37BvzgVV68OUhgJJLAa6almrII= -github.com/libp2p/go-libp2p-core v0.4.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= -github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= -github.com/libp2p/go-libp2p-core v0.5.1/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= -github.com/libp2p/go-libp2p-core v0.5.4/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= -github.com/libp2p/go-libp2p-core v0.5.5/go.mod h1:vj3awlOr9+GMZJFH9s4mpt9RHHgGqeHCopzbYKZdRjM= -github.com/libp2p/go-libp2p-core v0.5.6/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= -github.com/libp2p/go-libp2p-core v0.5.7/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= -github.com/libp2p/go-libp2p-core v0.6.0/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= -github.com/libp2p/go-libp2p-core v0.7.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-core v0.8.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-core v0.8.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-core v0.8.2/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-core v0.9.0/go.mod h1:ESsbz31oC3C1AvMJoGx26RTuCkNhmkSRCqZ0kQtJ2/8= github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= github.com/libp2p/go-libp2p-discovery v0.1.0/go.mod h1:4F/x+aldVHjHDHuX85x1zWoFTGElt8HnoDzwkFZm29g= -github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= -github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= -github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= -github.com/libp2p/go-libp2p-gostream v0.6.0 h1:QfAiWeQRce6pqnYfmIVWJFXNdDyfiR/qkCnjyaZUPYU= -github.com/libp2p/go-libp2p-gostream v0.6.0/go.mod h1:Nywu0gYZwfj7Jc91PQvbGU8dIpqbQQkjWgDuOrFaRdA= github.com/libp2p/go-libp2p-kad-dht v0.25.2 h1:FOIk9gHoe4YRWXTu8SY9Z1d0RILol0TrtApsMDPjAVQ= github.com/libp2p/go-libp2p-kad-dht v0.25.2/go.mod h1:6za56ncRHYXX4Nc2vn8z7CZK0P4QiMcrn77acKLM2Oo= github.com/libp2p/go-libp2p-kbucket v0.6.3 h1:p507271wWzpy2f1XxPzCQG9NiN6R6lHL9GiSErbQQo0= @@ -1065,145 +851,61 @@ github.com/libp2p/go-libp2p-kbucket v0.6.3/go.mod h1:RCseT7AH6eJWxxk2ol03xtP9pEH github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= -github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo= -github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= -github.com/libp2p/go-libp2p-mplex v0.4.0/go.mod h1:yCyWJE2sc6TBTnFpjvLuEJgTSw/u+MamvzILKdX7asw= -github.com/libp2p/go-libp2p-mplex v0.4.1/go.mod h1:cmy+3GfqfM1PceHTLL7zQzAAYaryDu6iPSC+CIb094g= github.com/libp2p/go-libp2p-nat v0.0.4/go.mod h1:N9Js/zVtAXqaeT99cXgTV9e75KpnWCvVOiGzlcHmBbY= -github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= -github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= -github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= -github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= -github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ= -github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= -github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= -github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= -github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= -github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= github.com/libp2p/go-libp2p-pubsub v0.11.0 h1:+JvS8Kty0OiyUiN0i8H5JbaCgjnJTRnTHe4rU88dLFc= github.com/libp2p/go-libp2p-pubsub v0.11.0/go.mod h1:QEb+hEV9WL9wCiUAnpY29FZR6W3zK8qYlaml8R4q6gQ= -github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= github.com/libp2p/go-libp2p-routing-helpers v0.7.3 h1:u1LGzAMVRK9Nqq5aYDVOiq/HaB93U9WWczBzGyAC5ZY= github.com/libp2p/go-libp2p-routing-helpers v0.7.3/go.mod h1:cN4mJAD/7zfPKXBcs9ze31JGYAZgzdABEm+q/hkswb8= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= -github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= -github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= -github.com/libp2p/go-libp2p-secio v0.2.2/go.mod h1:wP3bS+m5AUnFA+OFO7Er03uO1mncHG0uVwGrwvjYlNY= github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4= -github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= -github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= -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.5.0/go.mod h1:sU9i6BoHE0Ve5SKz3y9WfKrh8dUat6JknzUehFx8xW4= github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= -github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= -github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= -github.com/libp2p/go-libp2p-testing v0.1.2-0.20200422005655-8775583591d8/go.mod h1:Qy8sAncLKpwXtS2dSnDOP8ktexIAHKu+J+pnZOFZLTc= -github.com/libp2p/go-libp2p-testing v0.3.0/go.mod h1:efZkql4UZ7OVsEfaxNHZPzIehtsBXMrXnCfJIgDti5g= -github.com/libp2p/go-libp2p-testing v0.4.0/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= -github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= 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.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= github.com/libp2p/go-libp2p-yamux v0.2.1/go.mod h1:1FBXiHDk1VyRM1C0aez2bCfHQ4vMZKkAQzZbkSQt5fI= -github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= -github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= -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.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= -github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= -github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= github.com/libp2p/go-maddr-filter v0.1.0 h1:4ACqZKw8AqiuJfwFGq1CYDFugfXTOos+qQ3DETkhtCE= github.com/libp2p/go-maddr-filter v0.1.0/go.mod h1:VzZhTXkMucEGGEOSKddrwGiOv0tUhgnKqNEmIAz/bPU= github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0= github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= -github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= -github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= -github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= -github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.3/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= -github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= -github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= github.com/libp2p/go-nat v0.0.3/go.mod h1:88nUEt0k0JD45Bk93NIwDqjlhiOwOoV36GchpcVc1yI= -github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= -github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= github.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk= github.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk= -github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= -github.com/libp2p/go-netroute v0.1.3/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= -github.com/libp2p/go-netroute v0.1.5/go.mod h1:V1SR3AaECRkEQCoFFzYwVYWvYIEtlxx89+O3qcpCl4A= -github.com/libp2p/go-netroute v0.1.6/go.mod h1:AqhkMh0VuWmfgtxKPp3Oc1LdU5QSWS7wl0QLhSZqXxQ= github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= -github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= -github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= -github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= -github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= -github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= -github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= -github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= -github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= -github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= -github.com/libp2p/go-sockaddr v0.1.0/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= -github.com/libp2p/go-sockaddr v0.1.1/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= -github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA= github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= -github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= -github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= -github.com/libp2p/go-tcp-transport v0.2.3/go.mod h1:9dvr03yqrPyYGIEN6Dy5UvdJZjyPFvl1S/igQ5QD1SU= github.com/libp2p/go-testutil v0.1.0/go.mod h1:81b2n5HypcVyrCg/MJx4Wgfp/VHojytjVe/gLzZ2Ehc= github.com/libp2p/go-ws-transport v0.1.0/go.mod h1:rjw1MG1LU9YDC6gzmwObkPd/Sqwhw7yT74kj3raBFuo= -github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= -github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= -github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.2.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= -github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= -github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= -github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= github.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCypkQ= github.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE= github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magik6k/reflink v1.0.2-patch1 h1:NXSgQugcESI8Z/jBtuAI83YsZuRauY9i9WOyOnJ7Vns= -github.com/magik6k/reflink v1.0.2-patch1/go.mod h1:WGkTOKNjd1FsJKBw3mu4JvrPEDJyJJ+JPtxBkbPoCok= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -1213,9 +915,6 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= -github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= -github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= -github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -1225,7 +924,6 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 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.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -1233,20 +931,15 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= @@ -1266,14 +959,8 @@ github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= -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= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1286,10 +973,6 @@ github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjW github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= github.com/multiformats/go-base32 v0.0.4/go.mod h1:jNLFzjPZtp3aIARHbJRZIaPuspdH0J6q39uUM5pnABM= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= @@ -1300,38 +983,23 @@ github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= -github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= -github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE= github.com/multiformats/go-multiaddr v0.2.2/go.mod h1:NtfXiOtHvghW9KojvtySjH5y0u0xW5UouOmQQrn6a3Y= -github.com/multiformats/go-multiaddr v0.3.0/go.mod h1:dF9kph9wfJ+3VLAaeBqo9Of8x4fJxp6ggJGteB8HQTI= -github.com/multiformats/go-multiaddr v0.3.1/go.mod h1:uPbspcUPd5AfaP6ql3ujFY+QWzmBD8uLLL4bXW0XfGc= -github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9xQkib3fyB+nZXHLag0= github.com/multiformats/go-multiaddr v0.12.4 h1:rrKqpY9h+n80EwhhC/kkcunCZZ7URIF8yN1WEUt2Hvc= github.com/multiformats/go-multiaddr v0.12.4/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= -github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU= -github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= -github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= -github.com/multiformats/go-multiaddr-net v0.1.2/go.mod h1:QsWt3XK/3hwvNxZJp92iMQKME1qHfpYmyIjFVsSOY6Y= -github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= -github.com/multiformats/go-multiaddr-net v0.1.4/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= -github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= -github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= -github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= -github.com/multiformats/go-multicodec v0.3.1-0.20210902112759-1539a079fd61/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= github.com/multiformats/go-multicodec v0.6.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= @@ -1343,31 +1011,19 @@ github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpK github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= -github.com/multiformats/go-multihash v0.1.0/go.mod h1:RJlXsxt6vHGaia+S8We0ErjhojtKzPP2AH4+kYM7k84= github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= -github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= -github.com/multiformats/go-multistream v0.2.1/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= -github.com/multiformats/go-multistream v0.2.2/go.mod h1:UIcnm7Zuo8HKG+HkWgfQsGL+/MIEhyTqbODbIUwSXKs= github.com/multiformats/go-multistream v0.5.0 h1:5htLSLl7lvJk3xx3qT/8Zm9J4K8vEOf/QGkvOGQAyiE= github.com/multiformats/go-multistream v0.5.0/go.mod h1:n6tMZiwiP2wUsR8DgfDWw1dydlEqV3l6N3/GBsX6ILA= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= -github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= 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= @@ -1378,28 +1034,21 @@ github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOW github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU= github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE= github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 h1:CznVS40zms0Dj5he4ERo+fRPtO0qxUk8lA8Xu3ddet0= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333/go.mod h1:Ag6rSXkHIckQmjFBCweJEEt1mrTPBv8b9W4aU/NQWfI= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -1407,30 +1056,18 @@ github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e h1:4cPxUYdgaGzZIT5/j0IfqOrrXmq6bG8AwvwisMXpdrg= github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w= github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= -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/pion/datachannel v1.5.6 h1:1IxKJntfSlYkpUj8LlYRSWpYiTTC02nUrOE8T3DqGeg= github.com/pion/datachannel v1.5.6/go.mod h1:1eKT6Q85pRnr2mHiWHxJwO50SfZRtWHTsNIVb/NfGW4= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= @@ -1479,7 +1116,6 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polydawn/refmt v0.0.0-20190221155625-df39d6c2d992/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= @@ -1489,15 +1125,11 @@ github.com/polydawn/refmt v0.0.0-20190809202753-05966cbd336a/go.mod h1:uIp+gprXx github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= @@ -1505,20 +1137,15 @@ github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5 github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= @@ -1528,10 +1155,8 @@ github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3 github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= @@ -1552,29 +1177,22 @@ github.com/raulk/clock v1.1.0 h1:dpb29+UKMbLqiU/jqIJptgLR1nn23HLgMY0sTCDza5Y= github.com/raulk/clock v1.1.0/go.mod h1:3MpVxdZ/ODBQDxbN+kzshf5OSZwPjtMDx6BBXBmOeY0= github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -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/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sercand/kuberesolver/v4 v4.0.0 h1:frL7laPDG/lFm5n98ODmWnn+cvPpzlkf3LhzuPhcHP4= github.com/sercand/kuberesolver/v4 v4.0.0/go.mod h1:F4RGyuRmMAjeXHKL+w4P7AwUnPceEAPAhxUgXZjKgvM= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -1607,7 +1225,6 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx 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/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -1622,9 +1239,6 @@ github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:s github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= -github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= @@ -1634,16 +1248,10 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= 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/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -1681,21 +1289,15 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/triplewz/poseidon v0.0.0-20230828015038-79d8165c88ed h1:C8H2ql+vCBhEi7d3vMBBbdCAKv9s/thfPyLEuSvFpMU= github.com/triplewz/poseidon v0.0.0-20230828015038-79d8165c88ed/go.mod h1:QYG1d0B4YZD7TgF6qZndTTu4rxUGFCCZAQRDanDj+9c= -github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= -github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli/v2 v2.0.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/urfave/cli/v2 v2.25.5 h1:d0NIAyhh5shGscroL7ek/Ya9QYQE0KNabJgiUinIQkc= github.com/urfave/cli/v2 v2.25.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -1704,7 +1306,6 @@ github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8W github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= -github.com/warpfork/go-testmark v0.3.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= github.com/warpfork/go-testmark v0.10.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= github.com/warpfork/go-testmark v0.12.1 h1:rMgCpJfwy1sJ50x0M0NgyphxYYPMOODIJHhsXyEHU0s= github.com/warpfork/go-testmark v0.12.1/go.mod h1:kHwy7wfvGSPh1rQJYKayD4AbtNaeyZdcGi9tNJTaa5Y= @@ -1725,13 +1326,11 @@ github.com/whyrusleeping/cbor-gen v0.0.0-20191216205031-b047b6acb3c0/go.mod h1:x 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-20200414195334-429a0b5e922e/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= github.com/whyrusleeping/cbor-gen v0.0.0-20200504204219-64967432584d/go.mod h1:W5MvapuoHRP8rz4vxjwCK1pDqF1aQcWsV5PZ+AHbqdg= -github.com/whyrusleeping/cbor-gen v0.0.0-20200710004633-5379fc63235d/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20200715143311-227fab5a2377/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20200723185710-6a3894a6352b/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20200806213330-63aa96ca5488/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20200810223238-211df3b9e24c/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= 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-20210303213153-67a261a1d291/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20220323183124-98fa8256a799/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= @@ -1743,23 +1342,19 @@ github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9 github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= -github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= github.com/whyrusleeping/go-notifier v0.0.0-20170827234753-097c5d47330f/go.mod h1:cZNvX9cFybI01GriPRMXDtczuvUhgbcYr9iCGaNlRv8= github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= github.com/whyrusleeping/mdns v0.0.0-20180901202407-ef14215e6b30/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= -github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 h1:E9S12nwJwEOXe2d6gT6qxdvqMnNq+VnSsKPgm2ZZNds= github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= -github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xorcare/golden v0.6.0/go.mod h1:7T39/ZMvaSEZlBPoYfVFmsBLmUl3uz9IuzWj/U6FtvQ= github.com/xorcare/golden v0.6.1-0.20191112154924-b87f686d7542 h1:oWgZJmC1DorFZDpfMfWg7xk29yEOZiXmo/wZl+utTI8= github.com/xorcare/golden v0.6.1-0.20191112154924-b87f686d7542/go.mod h1:7T39/ZMvaSEZlBPoYfVFmsBLmUl3uz9IuzWj/U6FtvQ= @@ -1786,42 +1381,31 @@ go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= go.dedis.ch/protobuf v1.0.11 h1:FTYVIEzY/bfl37lu3pR4lIj+F9Vp1jE8oh91VmxKgLo= go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -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.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= go.opentelemetry.io/otel/bridge/opencensus v0.39.0 h1:YHivttTaDhbZIHuPlg1sWsy2P5gj57vzqPfkHItgbwQ= go.opentelemetry.io/otel/bridge/opencensus v0.39.0/go.mod h1:vZ4537pNjFDXEx//WldAR6Ro2LC8wwmFC76njAXwNPE= go.opentelemetry.io/otel/exporters/jaeger v1.14.0 h1:CjbUNd4iN2hHmWekmOqZ+zSCU+dzZppG8XsV+A3oc8Q= go.opentelemetry.io/otel/exporters/jaeger v1.14.0/go.mod h1:4Ay9kk5vELRrbg5z4cpP9EtmQRFap2Wb0woPG4lujZA= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= -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/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI= go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= -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= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -1832,25 +1416,20 @@ go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc= go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= go.uber.org/fx v1.21.1 h1:RqBh3cYdzZS0uqwVeEjOX2p73dddLpym315myy/Bpb0= go.uber.org/fx v1.21.1/go.mod h1:HT2M7d7RHo+ebKGh9NRcrsrHHfpZ60nW3QRubMRfv48= -go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -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/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 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= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= -go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= @@ -1862,7 +1441,6 @@ go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEb golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1875,22 +1453,14 @@ golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -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/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/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= @@ -1906,7 +1476,6 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v 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= @@ -1914,8 +1483,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/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-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -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-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= @@ -1933,12 +1500,10 @@ golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPI 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/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -1946,19 +1511,15 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -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= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1973,9 +1534,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1997,9 +1556,7 @@ golang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -2045,13 +1602,10 @@ golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180202135801-37707fdb30a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2062,14 +1616,12 @@ golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190302025703-b6889370fb10/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190524122548-abf6ff778158/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2079,14 +1631,12 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2112,18 +1662,12 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/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-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= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/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/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-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/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-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2194,7 +1738,6 @@ golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -2214,17 +1757,13 @@ golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 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-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -2245,7 +1784,6 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 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/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -2263,7 +1801,6 @@ gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -2297,7 +1834,6 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -2332,23 +1868,17 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= @@ -2364,7 +1894,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= @@ -2375,20 +1904,13 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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= gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= -gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -2413,7 +1935,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -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= lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= @@ -2423,7 +1944,5 @@ lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/itests/api_test.go b/itests/api_test.go index ff43bd5c0..e3a41256e 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -74,7 +74,7 @@ func (ts *apiSuite) testVersion(t *testing.T) { versions := strings.Split(v.Version, "+") require.NotZero(t, len(versions), "empty version") - require.Equal(t, versions[0], build.BuildVersion) + require.Equal(t, versions[0], build.NodeBuildVersion) } func (ts *apiSuite) testID(t *testing.T) { @@ -116,11 +116,11 @@ func (ts *apiSuite) testConnectTwo(t *testing.T) { return len(peerIDs) } - require.Equal(t, countPeerIDs(peers), 2, "node one doesn't have 2 peers") + require.Equal(t, countPeerIDs(peers), 1, "node one doesn't have 1 peer") peers, err = two.NetPeers(ctx) require.NoError(t, err) - require.Equal(t, countPeerIDs(peers), 2, "node one doesn't have 2 peers") + require.Equal(t, countPeerIDs(peers), 1, "node one doesn't have 1 peer") } func (ts *apiSuite) testSearchMsg(t *testing.T) { diff --git a/itests/batch_deal_test.go b/itests/batch_deal_test.go deleted file mode 100644 index 21db9f08d..000000000 --- a/itests/batch_deal_test.go +++ /dev/null @@ -1,147 +0,0 @@ -// stm: #integration -package itests - -import ( - "context" - "fmt" - "sort" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" - - "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/markets/storageadapter" - "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/config" - "github.com/filecoin-project/lotus/node/modules" - "github.com/filecoin-project/lotus/node/modules/dtypes" - "github.com/filecoin-project/lotus/storage/pipeline/sealiface" -) - -func TestBatchDealInput(t *testing.T) { - //stm: @MINER_SECTOR_STATUS_001, @MINER_SECTOR_LIST_001 - kit.QuietMiningLogs() - - var ( - blockTime = 10 * time.Millisecond - - // For these tests where the block time is artificially short, just use - // a deal start epoch that is guaranteed to be far enough in the future - // so that the deal starts sealing in time - dealStartEpoch = abi.ChainEpoch(2 << 12) - ) - - run := func(piece, deals, expectSectors int) func(t *testing.T) { - return func(t *testing.T) { - t.Logf("batchtest start") - - ctx := context.Background() - - publishPeriod := 10 * time.Second - maxDealsPerMsg := uint64(deals) - - // Set max deals per publish deals message to maxDealsPerMsg - opts := kit.ConstructorOpts(node.Options( - node.Override( - new(*storageadapter.DealPublisher), - storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ - Period: publishPeriod, - MaxDealsPerMsg: maxDealsPerMsg, - })), - node.Override(new(dtypes.GetSealingConfigFunc), func() (dtypes.GetSealingConfigFunc, error) { - return func() (sealiface.Config, error) { - cfg := config.DefaultStorageMiner() - sc := modules.ToSealingConfig(cfg.Dealmaking, cfg.Sealing) - sc.MaxWaitDealsSectors = 2 - sc.MaxSealingSectors = 1 - sc.MaxSealingSectorsForDeals = 3 - sc.AlwaysKeepUnsealedCopy = true - sc.WaitDealsDelay = time.Hour - sc.AggregateCommits = false - - return sc, nil - }, nil - }), - )) - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts, kit.ThroughRPC()) - ens.InterconnectAll().BeginMining(blockTime) - dh := kit.NewDealHarness(t, client, miner, miner) - - err := miner.MarketSetAsk(ctx, big.Zero(), big.Zero(), 200, 128, 32<<30) - require.NoError(t, err) - - t.Logf("batchtest ask set") - - checkNoPadding := func() { - sl, err := miner.SectorsListNonGenesis(ctx) - require.NoError(t, err) - - sort.Slice(sl, func(i, j int) bool { - return sl[i] < sl[j] - }) - - for _, snum := range sl { - si, err := miner.SectorsStatus(ctx, snum, false) - require.NoError(t, err) - - // fmt.Printf("S %d: %+v %s\n", snum, si.Deals, si.State) - - for _, deal := range si.Deals { - if deal == 0 { - fmt.Printf("sector %d had a padding piece!\n", snum) - } - } - } - } - - // Starts a deal and waits until it's published - runDealTillSeal := func(rseed int) { - res, _, _, err := kit.CreateImportFile(ctx, client, rseed, piece) - require.NoError(t, err) - - dp := dh.DefaultStartDealParams() - dp.Data.Root = res.Root - dp.DealStartEpoch = dealStartEpoch - - deal := dh.StartDeal(ctx, dp) - dh.WaitDealSealed(ctx, deal, false, true, checkNoPadding) - } - - // Run maxDealsPerMsg deals in parallel - done := make(chan struct{}, maxDealsPerMsg) - for rseed := 0; rseed < int(maxDealsPerMsg); rseed++ { - rseed := rseed - go func() { - runDealTillSeal(rseed) - done <- struct{}{} - }() - } - - t.Logf("batchtest deals started") - - // Wait for maxDealsPerMsg of the deals to be published - for i := 0; i < int(maxDealsPerMsg); i++ { - <-done - } - - t.Logf("batchtest deals published") - - checkNoPadding() - - t.Logf("batchtest no padding") - - sl, err := miner.SectorsListNonGenesis(ctx) - require.NoError(t, err) - require.Equal(t, len(sl), expectSectors) - - t.Logf("batchtest done") - } - } - - t.Run("4-p1600B", run(1600, 4, 4)) - t.Run("4-p513B", run(513, 4, 2)) -} diff --git a/itests/cli_test.go b/itests/cli_test.go deleted file mode 100644 index d2a087635..000000000 --- a/itests/cli_test.go +++ /dev/null @@ -1,27 +0,0 @@ -// stm: #integration -package itests - -import ( - "os" - "testing" - "time" - - "github.com/filecoin-project/lotus/cli/clicommands" - "github.com/filecoin-project/lotus/itests/kit" -) - -// TestClient does a basic test to exercise the client CLI commands. -func TestClient(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - //stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001 - _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit.QuietMiningLogs() - - blockTime := 5 * time.Millisecond - client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) - ens.InterconnectAll().BeginMining(blockTime) - kit.RunClientTest(t, clicommands.Commands, client) -} diff --git a/itests/curio_test.go b/itests/curio_test.go deleted file mode 100644 index 997352dd3..000000000 --- a/itests/curio_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package itests - -import ( - "context" - "testing" - "time" - - "github.com/docker/go-units" - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/cli/spcli" - "github.com/filecoin-project/lotus/cmd/curio/deps" - "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/node/config" - "github.com/filecoin-project/lotus/node/impl" -) - -func TestCurioNewActor(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - full, miner, esemble := kit.EnsembleMinimal(t, - kit.LatestActorsAt(-1), - kit.MockProofs(), - kit.WithSectorIndexDB(), - ) - - esemble.Start() - blockTime := 100 * time.Millisecond - esemble.BeginMining(blockTime) - - db := miner.BaseAPI.(*impl.StorageMinerAPI).HarmonyDB - - var titles []string - err := db.Select(ctx, &titles, `SELECT title FROM harmony_config WHERE LENGTH(config) > 0`) - require.NoError(t, err) - require.NotEmpty(t, titles) - require.NotContains(t, titles, "base") - - addr := miner.OwnerKey.Address - sectorSizeInt, err := units.RAMInBytes("8MiB") - require.NoError(t, err) - - maddr, err := spcli.CreateStorageMiner(ctx, full, addr, addr, addr, abi.SectorSize(sectorSizeInt), 0) - require.NoError(t, err) - - err = deps.CreateMinerConfig(ctx, full, db, []string{maddr.String()}, "FULL NODE API STRING") - require.NoError(t, err) - - err = db.Select(ctx, &titles, `SELECT title FROM harmony_config WHERE LENGTH(config) > 0`) - require.NoError(t, err) - require.Contains(t, titles, "base") - baseCfg := config.DefaultCurioConfig() - var baseText string - - err = db.QueryRow(ctx, "SELECT config FROM harmony_config WHERE title='base'").Scan(&baseText) - require.NoError(t, err) - _, err = deps.LoadConfigWithUpgrades(baseText, baseCfg) - require.NoError(t, err) - - require.NotNil(t, baseCfg.Addresses) - require.GreaterOrEqual(t, len(baseCfg.Addresses), 1) - - require.Contains(t, baseCfg.Addresses[0].MinerAddresses, maddr.String()) -} diff --git a/itests/deals_512mb_test.go b/itests/deals_512mb_test.go deleted file mode 100644 index 7b55204d9..000000000 --- a/itests/deals_512mb_test.go +++ /dev/null @@ -1,51 +0,0 @@ -// stm: #integration -package itests - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/itests/kit" -) - -func TestStorageDealMissingBlock(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - - //stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001 - //stm: @CLIENT_STORAGE_DEALS_LIST_IMPORTS_001 - ctx := context.Background() - - kit.QuietMiningLogs() - - client, miner, ens := kit.EnsembleMinimal(t, - kit.MockProofs(), - kit.SectorSize(512<<20), // 512MiB sectors. - ) - ens.InterconnectAll().BeginMining(50 * time.Millisecond) - - dh := kit.NewDealHarness(t, client, miner, miner) - - client.WaitTillChain(ctx, kit.HeightAtLeast(5)) - - res, _ := client.CreateImportFile(ctx, 0, 64<<20) // 64MiB file. - list, err := client.ClientListImports(ctx) - require.NoError(t, err) - require.Len(t, list, 1) - require.Equal(t, res.Root, *list[0].Root) - - dp := dh.DefaultStartDealParams() - dp.Data.Root = res.Root - dp.FastRetrieval = true - dp.EpochPrice = abi.NewTokenAmount(62500000) // minimum asking price. - deal := dh.StartDeal(ctx, dp) - - dh.WaitDealSealed(ctx, deal, false, false, nil) -} diff --git a/itests/deals_anycid_test.go b/itests/deals_anycid_test.go deleted file mode 100644 index c17441090..000000000 --- a/itests/deals_anycid_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package itests - -import ( - "bufio" - "context" - "os" - "testing" - "time" - - dag "github.com/ipfs/boxo/ipld/merkledag" - "github.com/ipfs/go-cid" - ipldcbor "github.com/ipfs/go-ipld-cbor" - format "github.com/ipfs/go-ipld-format" - "github.com/ipld/go-car" - "github.com/ipld/go-car/v2/blockstore" - selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse" - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/modules" - "github.com/filecoin-project/lotus/node/modules/dtypes" -) - -func TestDealRetrieveByAnyCid(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode") - } - - ctx := context.Background() - - kit.QuietMiningLogs() - - // For these tests where the block time is artificially short, just use - // a deal start epoch that is guaranteed to be far enough in the future - // so that the deal starts sealing in time - startEpoch := abi.ChainEpoch(2 << 12) - - // Override the dependency injection for the blockstore accessor, so that - // we can get a reference to the blockstore containing our deal later in - // the test - var bsa storagemarket.BlockstoreAccessor - bsaFn := func(importmgr dtypes.ClientImportMgr) storagemarket.BlockstoreAccessor { - bsa = modules.StorageBlockstoreAccessor(importmgr) - return bsa - } - bsaOpt := kit.ConstructorOpts(node.Override(new(storagemarket.BlockstoreAccessor), bsaFn)) - - // Allow 8MB sectors - eightMBSectorsOpt := kit.SectorSize(8 << 20) - - // Create a client, and a miner with its own full node - _, client, miner, ens := kit.EnsembleTwoOne(t, kit.MockProofs(), bsaOpt, eightMBSectorsOpt) - ens.InterconnectAll().BeginMining(250 * time.Millisecond) - - dh := kit.NewDealHarness(t, client, miner, miner) - - // Generate a DAG with multiple levels, so that we can test the case where - // the client requests a CID for a block which is not the root block but - // does have a subtree below it in the DAG - dagOpts := kit.GeneratedDAGOpts{ - // Max size of a block - ChunkSize: 1024, - // Max links from a block to other blocks - Maxlinks: 10, - } - carv1FilePath, _ := kit.CreateRandomCARv1(t, 5, 100*1024, dagOpts) - res, err := client.ClientImport(ctx, api.FileRef{Path: carv1FilePath, IsCAR: true}) - require.NoError(t, err) - - // Get the blockstore for the file - bs, err := bsa.Get(res.Root) - require.NoError(t, err) - - // Get all CIDs from the file - sc := car.NewSelectiveCar(ctx, bs, []car.Dag{{Root: res.Root, Selector: selectorparse.CommonSelector_ExploreAllRecursively}}) - prepared, err := sc.Prepare() - require.NoError(t, err) - - reg := format.Registry{} - reg.Register(cid.DagProtobuf, dag.DecodeProtobufBlock) - reg.Register(cid.DagCBOR, ipldcbor.DecodeBlock) - reg.Register(cid.Raw, dag.DecodeRawBlock) - - cids := prepared.Cids() - for i, c := range cids { - blk, err := bs.Get(ctx, c) - require.NoError(t, err) - - nd, err := reg.Decode(blk) - require.NoError(t, err) - - t.Log(i, c, len(nd.Links())) - } - - // Create a storage deal - dp := dh.DefaultStartDealParams() - dp.Data.Root = res.Root - dp.DealStartEpoch = startEpoch - dp.EpochPrice = abi.NewTokenAmount(62500000) // minimum asking price - dealCid := dh.StartDeal(ctx, dp) - - // Wait for the deal to be sealed - dh.WaitDealSealed(ctx, dealCid, false, false, nil) - - ask, err := miner.MarketGetRetrievalAsk(ctx) - require.NoError(t, err) - ask.PricePerByte = abi.NewTokenAmount(0) - ask.UnsealPrice = abi.NewTokenAmount(0) - err = miner.MarketSetRetrievalAsk(ctx, ask) - require.NoError(t, err) - - // Fetch the deal data - info, err := client.ClientGetDealInfo(ctx, *dealCid) - require.NoError(t, err) - - // Make retrievals against CIDs at different levels in the DAG - cidIndices := []int{1, 11, 27, 32, 47} - for _, val := range cidIndices { - t.Logf("performing retrieval for cid at index %d", val) - - targetCid := cids[val] - offer, err := client.ClientMinerQueryOffer(ctx, miner.ActorAddr, targetCid, &info.PieceCID) - require.NoError(t, err) - require.Empty(t, offer.Err) - - // retrieve in a CAR file and ensure roots match - outputCar := dh.PerformRetrieval(ctx, dealCid, targetCid, true, offer) - _, err = os.Stat(outputCar) - require.NoError(t, err) - f, err := os.Open(outputCar) - require.NoError(t, err) - ch, err := car.ReadHeader(bufio.NewReader(f)) - require.NoError(t, err) - require.EqualValues(t, ch.Roots[0], targetCid) - require.NoError(t, f.Close()) - - // create CAR from original file starting at targetCid and ensure it matches the retrieved CAR file. - tmp, err := os.CreateTemp(t.TempDir(), "randcarv1") - require.NoError(t, err) - rd, err := blockstore.OpenReadOnly(carv1FilePath, blockstore.UseWholeCIDs(true)) - require.NoError(t, err) - err = car.NewSelectiveCar( - ctx, - rd, - []car.Dag{{ - Root: targetCid, - Selector: selectorparse.CommonSelector_ExploreAllRecursively, - }}, - ).Write(tmp) - require.NoError(t, err) - require.NoError(t, tmp.Close()) - require.NoError(t, rd.Close()) - - kit.AssertFilesEqual(t, tmp.Name(), outputCar) - t.Log("car files match") - } -} diff --git a/itests/deals_concurrent_test.go b/itests/deals_concurrent_test.go deleted file mode 100644 index a106836bd..000000000 --- a/itests/deals_concurrent_test.go +++ /dev/null @@ -1,212 +0,0 @@ -// stm: #integration -package itests - -import ( - "context" - "fmt" - "sync" - "testing" - "time" - - provider "github.com/ipni/index-provider" - "github.com/stretchr/testify/require" - - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - "github.com/filecoin-project/go-fil-markets/shared_testutil" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/modules" - "github.com/filecoin-project/lotus/node/modules/dtypes" - "github.com/filecoin-project/lotus/node/repo" -) - -// TestDealWithMarketAndMinerNode is running concurrently a number of storage and retrieval deals towards a miner -// architecture where the `mining/sealing/proving` node is a separate process from the `markets` node -func TestDealWithMarketAndMinerNode(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - if testing.Short() { - t.Skip("skipping test in short mode") - } - - t.Skip("skipping due to flakiness: see #6956") - - kit.QuietMiningLogs() - - // For these tests where the block time is artificially short, just use - // a deal start epoch that is guaranteed to be far enough in the future - // so that the deal starts sealing in time - startEpoch := abi.ChainEpoch(8 << 10) - - runTest := func(t *testing.T, n int, fastRetrieval bool, carExport bool) { - api.RunningNodeType = api.NodeMiner // TODO(anteva): fix me - - idxProv := shared_testutil.NewMockIndexProvider() - idxProvOpt := kit.ConstructorOpts(node.Override(new(provider.Interface), idxProv)) - client, main, market, _ := kit.EnsembleWithMinerAndMarketNodes(t, kit.ThroughRPC(), idxProvOpt) - - dh := kit.NewDealHarness(t, client, main, market) - - dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{ - N: n, - FastRetrieval: fastRetrieval, - CarExport: carExport, - StartEpoch: startEpoch, - IndexProvider: idxProv, - }) - } - - // this test is expensive because we don't use mock proofs; do a single cycle. - cycles := []int{4} - for _, n := range cycles { - n := n - 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, false, true) }) - t.Run(ns+"-stdretrieval-NoCAR", func(t *testing.T) { runTest(t, n, false, false) }) - } -} - -func TestDealCyclesConcurrent(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - - //stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001 - if testing.Short() { - t.Skip("skipping test in short mode") - } - - kit.QuietMiningLogs() - - // For these tests where the block time is artificially short, just use - // a deal start epoch that is guaranteed to be far enough in the future - // so that the deal starts sealing in time - startEpoch := abi.ChainEpoch(2 << 12) - - runTest := func(t *testing.T, n int, fastRetrieval bool, carExport bool) { - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) - ens.InterconnectAll().BeginMining(250 * time.Millisecond) - dh := kit.NewDealHarness(t, client, miner, miner) - - dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{ - N: n, - FastRetrieval: fastRetrieval, - CarExport: carExport, - StartEpoch: startEpoch, - }) - } - - // this test is cheap because we use mock proofs, do various cycles - cycles := []int{2, 4, 8, 16} - for _, n := range cycles { - n := n - 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, false, true) }) - t.Run(ns+"-stdretrieval-NoCAR", func(t *testing.T) { runTest(t, n, false, false) }) - } -} - -func TestSimultanenousTransferLimit(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - t.Skip("skipping as flaky #7152") - - if testing.Short() { - t.Skip("skipping test in short mode") - } - - kit.QuietMiningLogs() - - // For these tests where the block time is artificially short, just use - // a deal start epoch that is guaranteed to be far enough in the future - // so that the deal starts sealing in time - startEpoch := abi.ChainEpoch(2 << 12) - - const ( - graphsyncThrottle = 2 - concurrency = 20 - ) - runTest := func(t *testing.T) { - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ConstructorOpts( - node.ApplyIf(node.IsType(repo.StorageMiner), node.Override(new(dtypes.StagingGraphsync), modules.StagingGraphsync(graphsyncThrottle, 0, graphsyncThrottle))), - node.Override(new(dtypes.Graphsync), modules.Graphsync(graphsyncThrottle, graphsyncThrottle)), - )) - ens.InterconnectAll().BeginMining(250 * time.Millisecond) - dh := kit.NewDealHarness(t, client, miner, miner) - - ctx, cancel := context.WithCancel(context.Background()) - - du, err := miner.MarketDataTransferUpdates(ctx) - require.NoError(t, err) - - var maxOngoing int - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - - ongoing := map[datatransfer.TransferID]struct{}{} - - for { - select { - case u := <-du: - t.Logf("%d - %s", u.TransferID, datatransfer.Statuses[u.Status]) - if u.Status == datatransfer.Ongoing && u.Transferred > 0 { - ongoing[u.TransferID] = struct{}{} - } else { - delete(ongoing, u.TransferID) - } - - if len(ongoing) > maxOngoing { - maxOngoing = len(ongoing) - } - case <-ctx.Done(): - return - } - } - }() - - t.Logf("running concurrent deals: %d", concurrency) - - dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{ - N: concurrency, - FastRetrieval: true, - StartEpoch: startEpoch, - }) - - t.Logf("all deals finished") - - cancel() - wg.Wait() - - // The eventing systems across go-data-transfer and go-graphsync - // are racy, and that's why we can't enforce graphsyncThrottle exactly, - // without making this test racy. - // - // Essentially what could happen is that the graphsync layer starts the - // next transfer before the go-data-transfer FSM has the opportunity to - // move the previously completed transfer to the next stage, thus giving - // the appearance that more than graphsyncThrottle transfers are - // in progress. - // - // Concurrency (20) is x10 higher than graphsyncThrottle (2), so if all - // 20 transfers are not happening at once, we know the throttle is - // in effect. Thus we are a little bit lenient here to account for the - // above races and allow up to graphsyncThrottle*2. - require.LessOrEqual(t, maxOngoing, graphsyncThrottle*2) - } - - runTest(t) -} diff --git a/itests/deals_max_staging_deals_test.go b/itests/deals_max_staging_deals_test.go deleted file mode 100644 index 738a1e2fe..000000000 --- a/itests/deals_max_staging_deals_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// stm: #integration -package itests - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/itests/kit" -) - -func TestMaxStagingDeals(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - - //stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001 - //stm: @CLIENT_STORAGE_DEALS_LIST_IMPORTS_001 - ctx := context.Background() - - kit.QuietMiningLogs() - - client, miner, ens := kit.EnsembleMinimal(t, - kit.MockProofs(), - kit.WithMaxStagingDealsBytes(8192), // max 8KB staging deals - kit.SectorSize(512<<20), // 512MiB sectors. - ) - ens.InterconnectAll().BeginMining(200 * time.Millisecond) - - dh := kit.NewDealHarness(t, client, miner, miner) - - client.WaitTillChain(ctx, kit.HeightAtLeast(5)) - - res, _ := client.CreateImportFile(ctx, 0, 8192) // 8KB file - list, err := client.ClientListImports(ctx) - require.NoError(t, err) - require.Len(t, list, 1) - - res2, _ := client.CreateImportFile(ctx, 0, 4096) - list, err = client.ClientListImports(ctx) - require.NoError(t, err) - require.Len(t, list, 2) - - // first deal stays in staging area, and is not yet passed to the sealing subsystem - dp := dh.DefaultStartDealParams() - dp.Data.Root = res.Root - dp.FastRetrieval = true - dp.EpochPrice = abi.NewTokenAmount(62500000) // minimum asking price. - deal := dh.StartDeal(ctx, dp) - - time.Sleep(1 * time.Second) - - // expecting second deal to fail since staging area is full - dp.Data.Root = res2.Root - dp.FastRetrieval = true - dp.EpochPrice = abi.NewTokenAmount(62500000) // minimum asking price. - deal2 := dh.StartDeal(ctx, dp) - - _ = deal - - err = dh.ExpectDealFailure(ctx, deal2, "cannot accept deal as miner is overloaded at the moment") - if err != nil { - t.Fatal(err) - } -} diff --git a/itests/deals_offline_test.go b/itests/deals_offline_test.go deleted file mode 100644 index 997d7723a..000000000 --- a/itests/deals_offline_test.go +++ /dev/null @@ -1,107 +0,0 @@ -// stm: #integration -package itests - -import ( - "context" - "path/filepath" - "testing" - "time" - - "github.com/stretchr/testify/require" - - commcid "github.com/filecoin-project/go-fil-commcid" - commp "github.com/filecoin-project/go-fil-commp-hashhash" - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-state-types/abi" - - lapi "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/itests/kit" -) - -func TestOfflineDealFlow(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - - //stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001 - //stm: @CLIENT_DATA_CALCULATE_COMMP_001, @CLIENT_DATA_GENERATE_CAR_001, @CLIENT_DATA_GET_DEAL_PIECE_CID_001, @CLIENT_DATA_GET_DEAL_PIECE_CID_001 - runTest := func(t *testing.T, fastRet bool, upscale abi.PaddedPieceSize) { - ctx := context.Background() - client, miner, ens := kit.EnsembleMinimal(t, kit.WithAllSubsystems()) // no mock proofs - ens.InterconnectAll().BeginMining(250 * time.Millisecond) - - dh := kit.NewDealHarness(t, client, miner, miner) - - // Create a random file and import on the client. - res, inFile := client.CreateImportFile(ctx, 1, 200) - - // Get the piece size and commP - rootCid := res.Root - pieceInfo, err := client.ClientDealPieceCID(ctx, rootCid) - require.NoError(t, err) - t.Log("FILE CID:", rootCid) - - // test whether padding works as intended - if upscale > 0 { - newRawCp, err := commp.PadCommP( - pieceInfo.PieceCID.Hash()[len(pieceInfo.PieceCID.Hash())-32:], - uint64(pieceInfo.PieceSize), - uint64(upscale), - ) - require.NoError(t, err) - - pieceInfo.PieceSize = upscale - pieceInfo.PieceCID, err = commcid.DataCommitmentV1ToCID(newRawCp) - require.NoError(t, err) - } - - dp := dh.DefaultStartDealParams() - dp.DealStartEpoch = abi.ChainEpoch(4 << 10) - dp.FastRetrieval = fastRet - // Replace with params for manual storage deal (offline deal) - dp.Data = &storagemarket.DataRef{ - TransferType: storagemarket.TTManual, - Root: rootCid, - PieceCid: &pieceInfo.PieceCID, - PieceSize: pieceInfo.PieceSize.Unpadded(), - } - - proposalCid := dh.StartDeal(ctx, dp) - - //stm: @CLIENT_STORAGE_DEALS_GET_001 - // Wait for the deal to reach StorageDealCheckForAcceptance on the client - cd, err := client.ClientGetDealInfo(ctx, *proposalCid) - require.NoError(t, err) - require.Eventually(t, func() bool { - cd, _ := client.ClientGetDealInfo(ctx, *proposalCid) - return cd.State == storagemarket.StorageDealCheckForAcceptance - }, 30*time.Second, 1*time.Second, "actual deal status is %s", storagemarket.DealStates[cd.State]) - - // Create a CAR file from the raw file - carFileDir := t.TempDir() - carFilePath := filepath.Join(carFileDir, "out.car") - err = client.ClientGenCar(ctx, lapi.FileRef{Path: inFile}, carFilePath) - require.NoError(t, err) - - // Import the CAR file on the miner - this is the equivalent to - // transferring the file across the wire in a normal (non-offline) deal - err = miner.DealsImportData(ctx, *proposalCid, carFilePath) - require.NoError(t, err) - - // Wait for the deal to be published - dh.WaitDealPublished(ctx, proposalCid) - - t.Logf("deal published, retrieving") - - // Retrieve the deal - outFile := dh.PerformRetrieval(ctx, proposalCid, rootCid, false) - - kit.AssertFilesEqual(t, inFile, outFile) - - } - - t.Run("stdretrieval", func(t *testing.T) { runTest(t, false, 0) }) - t.Run("fastretrieval", func(t *testing.T) { runTest(t, true, 0) }) - t.Run("fastretrieval", func(t *testing.T) { runTest(t, true, 1024) }) -} diff --git a/itests/deals_padding_test.go b/itests/deals_padding_test.go deleted file mode 100644 index aaca45360..000000000 --- a/itests/deals_padding_test.go +++ /dev/null @@ -1,84 +0,0 @@ -// stm: #integration -package itests - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/require" - - commcid "github.com/filecoin-project/go-fil-commcid" - commp "github.com/filecoin-project/go-fil-commp-hashhash" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/itests/kit" -) - -func TestDealPadding(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - - //stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001 - //stm: @CLIENT_DATA_GET_DEAL_PIECE_CID_001 - kit.QuietMiningLogs() - - var blockTime = 250 * time.Millisecond - startEpoch := abi.ChainEpoch(2 << 12) - - client, miner, ens := kit.EnsembleMinimal(t, kit.ThroughRPC(), kit.WithAllSubsystems()) // no mock proofs. - ens.InterconnectAll().BeginMining(blockTime) - dh := kit.NewDealHarness(t, client, miner, miner) - - ctx := context.Background() - client.WaitTillChain(ctx, kit.BlocksMinedByAll(miner.ActorAddr)) - - // Create a random file, would originally be a 256-byte sector - res, inFile := client.CreateImportFile(ctx, 1, 200) - - // Get the piece size and commP - pieceInfo, err := client.ClientDealPieceCID(ctx, res.Root) - require.NoError(t, err) - t.Log("FILE CID:", res.Root) - - runTest := func(t *testing.T, upscale abi.PaddedPieceSize) { - // test whether padding works as intended - newRawCp, err := commp.PadCommP( - pieceInfo.PieceCID.Hash()[len(pieceInfo.PieceCID.Hash())-32:], - uint64(pieceInfo.PieceSize), - uint64(upscale), - ) - require.NoError(t, err) - - pcid, err := commcid.DataCommitmentV1ToCID(newRawCp) - require.NoError(t, err) - - dp := dh.DefaultStartDealParams() - dp.Data.Root = res.Root - dp.Data.PieceCid = &pcid - dp.Data.PieceSize = upscale.Unpadded() - dp.DealStartEpoch = startEpoch - proposalCid := dh.StartDeal(ctx, dp) - - // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this - time.Sleep(time.Second) - - //stm: @CLIENT_STORAGE_DEALS_GET_001 - di, err := client.ClientGetDealInfo(ctx, *proposalCid) - require.NoError(t, err) - require.True(t, di.PieceCID.Equals(pcid)) - - dh.WaitDealSealed(ctx, proposalCid, false, false, nil) - - // Retrieve the deal - outFile := dh.PerformRetrieval(ctx, proposalCid, res.Root, false) - - kit.AssertFilesEqual(t, inFile, outFile) - } - - t.Run("padQuarterSector", func(t *testing.T) { runTest(t, 512) }) - t.Run("padHalfSector", func(t *testing.T) { runTest(t, 1024) }) - t.Run("padFullSector", func(t *testing.T) { runTest(t, 2048) }) -} diff --git a/itests/deals_partial_retrieval_dm-level_test.go b/itests/deals_partial_retrieval_dm-level_test.go deleted file mode 100644 index c03d07aac..000000000 --- a/itests/deals_partial_retrieval_dm-level_test.go +++ /dev/null @@ -1,267 +0,0 @@ -// stm: #integration -package itests - -import ( - "context" - "fmt" - "io" - "os" - "testing" - "time" - - blocks "github.com/ipfs/go-block-format" - "github.com/ipfs/go-cid" - "github.com/ipld/go-car" - textselector "github.com/ipld/go-ipld-selector-text-lite" - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/api" - api0 "github.com/filecoin-project/lotus/api/v0api" - "github.com/filecoin-project/lotus/itests/kit" -) - -// please talk to @ribasushi or @mikeal before modifying these test: there are -// downstream dependencies on ADL-less operation -var ( - adlFixtureCar = "fixtures/adl_test.car" - adlFixtureRoot, _ = cid.Parse("bafybeiaigxwanoxyeuzyiknhrg6io6kobfbm37ozcips6qdwumub2gaomy") - adlFixtureCommp, _ = cid.Parse("baga6ea4seaqjnmnrv4qsfz2rnda54mvo5al22dwpguhn2pmep63gl7bbqqqraai") - adlFixturePieceSize = abi.PaddedPieceSize(1024) - dmSelector = api.Selector("Links/0/Hash") - dmTextSelector = textselector.Expression(dmSelector) - dmExpectedResult = "NO ADL" - dmExpectedCarBlockCount = 4 - dmDagSpec = []api.DagSpec{{DataSelector: &dmSelector, ExportMerkleProof: true}} -) - -func TestDMLevelPartialRetrieval(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - - //stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001 - //stm: @CLIENT_RETRIEVAL_RETRIEVE_001, @CLIENT_RETRIEVAL_FIND_001 - ctx := context.Background() - - kit.QuietMiningLogs() - client, miner, ens := kit.EnsembleMinimal(t, kit.ThroughRPC()) - dh := kit.NewDealHarness(t, client, miner, miner) - ens.InterconnectAll().BeginMiningMustPost(50 * time.Millisecond) - - _, err := client.ClientImport(ctx, api.FileRef{Path: adlFixtureCar, IsCAR: true}) - require.NoError(t, err) - - caddr, err := client.WalletDefaultAddress(ctx) - require.NoError(t, err) - - // - // test retrieval from local car 1st - require.NoError(t, testDMExportAsCar( - ctx, client, api.ExportRef{ - FromLocalCAR: adlFixtureCar, - Root: adlFixtureRoot, - DAGs: dmDagSpec, - }, t.TempDir(), - )) - require.NoError(t, testDMExportAsFile( - ctx, client, api.ExportRef{ - FromLocalCAR: adlFixtureCar, - Root: adlFixtureRoot, - DAGs: dmDagSpec, - }, t.TempDir(), - )) - - // - // ensure V0 continues functioning as expected - require.NoError(t, tesV0RetrievalAsCar( - ctx, client, api0.RetrievalOrder{ - FromLocalCAR: adlFixtureCar, - Root: adlFixtureRoot, - DatamodelPathSelector: &dmTextSelector, - }, t.TempDir(), - )) - require.NoError(t, testV0RetrievalAsFile( - ctx, client, api0.RetrievalOrder{ - FromLocalCAR: adlFixtureCar, - Root: adlFixtureRoot, - DatamodelPathSelector: &dmTextSelector, - }, t.TempDir(), - )) - - // - // now perform a storage/retrieval deal as well, and retest - dp := dh.DefaultStartDealParams() - dp.Data = &storagemarket.DataRef{ - Root: adlFixtureRoot, - PieceCid: &adlFixtureCommp, - PieceSize: adlFixturePieceSize.Unpadded(), - } - proposalCid := dh.StartDeal(ctx, dp) - - // Wait for the deal to reach StorageDealCheckForAcceptance on the client - cd, err := client.ClientGetDealInfo(ctx, *proposalCid) - require.NoError(t, err) - require.Eventually(t, func() bool { - cd, _ := client.ClientGetDealInfo(ctx, *proposalCid) - return cd.State == storagemarket.StorageDealCheckForAcceptance - }, 30*time.Second, 1*time.Second, "actual deal status is %s", storagemarket.DealStates[cd.State]) - - dh.WaitDealSealed(ctx, proposalCid, false, false, nil) - - offers, err := client.ClientFindData(ctx, adlFixtureRoot, nil) - require.NoError(t, err) - require.NotEmpty(t, offers, "no offers") - - retOrder := offers[0].Order(caddr) - retOrder.DataSelector = &dmSelector - - rr, err := client.ClientRetrieve(ctx, retOrder) - require.NoError(t, err) - - err = client.ClientRetrieveWait(ctx, rr.DealID) - require.NoError(t, err) - - require.NoError(t, testDMExportAsCar( - ctx, client, api.ExportRef{ - DealID: rr.DealID, - Root: adlFixtureRoot, - DAGs: dmDagSpec, - }, t.TempDir(), - )) - require.NoError(t, testDMExportAsFile( - ctx, client, api.ExportRef{ - DealID: rr.DealID, - Root: adlFixtureRoot, - DAGs: dmDagSpec, - }, t.TempDir(), - )) - -} - -func testDMExportAsFile(ctx context.Context, client *kit.TestFullNode, expDirective api.ExportRef, tempDir string) error { - out := tempDir + string(os.PathSeparator) + "exp-test" + expDirective.Root.String() - - fileDest := api.FileRef{ - Path: out, - } - err := client.ClientExport(ctx, expDirective, fileDest) - if err != nil { - return err - } - - f, err := os.Open(out) - if err != nil { - return err - } - - defer f.Close() //nolint:errcheck - - return validateDMUnixFile(f) -} -func testV0RetrievalAsFile(ctx context.Context, client *kit.TestFullNode, retOrder api0.RetrievalOrder, tempDir string) error { - out := tempDir + string(os.PathSeparator) + "exp-test" + retOrder.Root.String() - - cv0 := &api0.WrapperV1Full{FullNode: client.FullNode} - err := cv0.ClientRetrieve(ctx, retOrder, &api.FileRef{ - Path: out, - }) - if err != nil { - return err - } - - f, err := os.Open(out) - if err != nil { - return err - } - - defer f.Close() //nolint:errcheck - - return validateDMUnixFile(f) -} -func validateDMUnixFile(r io.Reader) error { - data, err := io.ReadAll(r) - if err != nil { - return err - } - if string(data) != dmExpectedResult { - return fmt.Errorf("retrieved data mismatch: expected '%s' got '%s'", dmExpectedResult, data) - } - - return nil -} - -func testDMExportAsCar(ctx context.Context, client *kit.TestFullNode, expDirective api.ExportRef, tempDir string) error { - out, err := os.CreateTemp(tempDir, "exp-test") - if err != nil { - return err - } - defer out.Close() //nolint:errcheck - - carDest := api.FileRef{ - IsCAR: true, - Path: out.Name(), - } - err = client.ClientExport(ctx, expDirective, carDest) - if err != nil { - return err - } - - return validateDMCar(out) -} -func tesV0RetrievalAsCar(ctx context.Context, client *kit.TestFullNode, retOrder api0.RetrievalOrder, tempDir string) error { - out, err := os.CreateTemp(tempDir, "exp-test") - if err != nil { - return err - } - defer out.Close() //nolint:errcheck - - cv0 := &api0.WrapperV1Full{FullNode: client.FullNode} - err = cv0.ClientRetrieve(ctx, retOrder, &api.FileRef{ - Path: out.Name(), - IsCAR: true, - }) - if err != nil { - return err - } - - return validateDMCar(out) -} -func validateDMCar(r io.Reader) error { - cr, err := car.NewCarReader(r) - if err != nil { - return err - } - - if len(cr.Header.Roots) != 1 { - return fmt.Errorf("expected a single root in result car, got %d", len(cr.Header.Roots)) - } else if cr.Header.Roots[0].String() != adlFixtureRoot.String() { - return fmt.Errorf("expected root cid '%s', got '%s'", adlFixtureRoot.String(), cr.Header.Roots[0].String()) - } - - blks := make([]blocks.Block, 0) - for { - b, err := cr.Next() - if err == io.EOF { - break - } else if err != nil { - return err - } - - blks = append(blks, b) - } - - if len(blks) != dmExpectedCarBlockCount { - return fmt.Errorf("expected a car file with %d blocks, got one with %d instead", dmExpectedCarBlockCount, len(blks)) - } - - data := fmt.Sprintf("%s%s", blks[2].RawData(), blks[3].RawData()) - if data != dmExpectedResult { - return fmt.Errorf("retrieved data mismatch: expected '%s' got '%s'", dmExpectedResult, data) - } - - return nil -} diff --git a/itests/deals_partial_retrieval_test.go b/itests/deals_partial_retrieval_test.go deleted file mode 100644 index 0bbf23da0..000000000 --- a/itests/deals_partial_retrieval_test.go +++ /dev/null @@ -1,256 +0,0 @@ -// stm: #integration -package itests - -import ( - "context" - "fmt" - "io" - "os" - "testing" - "time" - - blocks "github.com/ipfs/go-block-format" - "github.com/ipfs/go-cid" - "github.com/ipld/go-car" - "github.com/stretchr/testify/require" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/itests/kit" -) - -// use the mainnet carfile as text fixture: it will always be here -// https://dweb.link/ipfs/bafy2bzacecnamqgqmifpluoeldx7zzglxcljo6oja4vrmtj7432rphldpdmm2/8/1/8/1/0/1/0 -var ( - sourceCar = "../build/genesis/mainnet.car" - carRoot, _ = cid.Parse("bafy2bzacecnamqgqmifpluoeldx7zzglxcljo6oja4vrmtj7432rphldpdmm2") - carCommp, _ = cid.Parse("baga6ea4seaqmrivgzei3fmx5qxtppwankmtou6zvigyjaveu3z2zzwhysgzuina") - selectedCid, _ = cid.Parse("bafkqaetgnfwc6mjpon2g64tbm5sxa33xmvza") - carPieceSize = abi.PaddedPieceSize(2097152) - textSelector = api.Selector("8/1/8/1/0/1/0") - textSelectorNonLink = api.Selector("8/1/8/1/0/1") - textSelectorNonexistent = api.Selector("42") - expectedResult = "fil/1/storagepower" -) - -func TestPartialRetrieval(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - - //stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001 - //stm: @CLIENT_RETRIEVAL_RETRIEVE_001 - ctx := context.Background() - - kit.QuietMiningLogs() - client, miner, ens := kit.EnsembleMinimal(t, kit.ThroughRPC(), kit.MockProofs(), kit.SectorSize(512<<20)) - dh := kit.NewDealHarness(t, client, miner, miner) - ens.InterconnectAll().BeginMining(50 * time.Millisecond) - - _, err := client.ClientImport(ctx, api.FileRef{Path: sourceCar, IsCAR: true}) - require.NoError(t, err) - - caddr, err := client.WalletDefaultAddress(ctx) - require.NoError(t, err) - - // first test retrieval from local car, then do an actual deal - for _, exportMerkleProof := range []bool{false, true} { - for _, fullCycle := range []bool{false, true} { - - var retOrder api.RetrievalOrder - var eref api.ExportRef - - if !fullCycle { - eref.FromLocalCAR = sourceCar - } else { - dp := dh.DefaultStartDealParams() - dp.Data = &storagemarket.DataRef{ - // FIXME: figure out how to do this with an online partial transfer - TransferType: storagemarket.TTManual, - Root: carRoot, - PieceCid: &carCommp, - PieceSize: carPieceSize.Unpadded(), - } - proposalCid := dh.StartDeal(ctx, dp) - - // Wait for the deal to reach StorageDealCheckForAcceptance on the client - cd, err := client.ClientGetDealInfo(ctx, *proposalCid) - require.NoError(t, err) - require.Eventually(t, func() bool { - cd, _ := client.ClientGetDealInfo(ctx, *proposalCid) - return cd.State == storagemarket.StorageDealCheckForAcceptance - }, 30*time.Second, 1*time.Second, "actual deal status is %s", storagemarket.DealStates[cd.State]) - - err = miner.DealsImportData(ctx, *proposalCid, sourceCar) - require.NoError(t, err) - - // Wait for the deal to be published, we should be able to start retrieval right away - dh.WaitDealPublished(ctx, proposalCid) - - offers, err := client.ClientFindData(ctx, carRoot, nil) - require.NoError(t, err) - require.NotEmpty(t, offers, "no offers") - - retOrder = offers[0].Order(caddr) - } - - retOrder.DataSelector = &textSelector - eref.DAGs = append(eref.DAGs, api.DagSpec{ - DataSelector: &textSelector, - ExportMerkleProof: exportMerkleProof, - }) - eref.Root = carRoot - - // test retrieval of either data or constructing a partial selective-car - for _, retrieveAsCar := range []bool{false, true} { - outFile := t.TempDir() + string(os.PathSeparator) + "ret-file" + retOrder.Root.String() - - require.NoError(t, testGenesisRetrieval( - ctx, - client, - retOrder, - eref, - &api.FileRef{ - Path: outFile, - IsCAR: retrieveAsCar, - }, - )) - - // UGH if I do not sleep here, I get things like: - /* - retrieval failed: Retrieve failed: there is an active retrieval deal with peer 12D3KooWK9fB9a3HZ4PQLVmEQ6pweMMn5CAyKtumB71CPTnuBDi6 for payload CID bafy2bzacecnamqgqmifpluoeldx7zzglxcljo6oja4vrmtj7432rphldpdmm2 (retrieval deal ID 1631259332180384709, state DealStatusFinalizingBlockstore) - existing deal must be cancelled before starting a new retrieval deal: - github.com/filecoin-project/lotus/node/impl/client.(*API).ClientRetrieve - /home/circleci/project/node/impl/client/client.go:774 - */ - time.Sleep(time.Second) - } - } - } - - // ensure non-existent paths fail - require.EqualError( - t, - testGenesisRetrieval( - ctx, - client, - api.RetrievalOrder{ - Root: carRoot, - DataSelector: &textSelectorNonexistent, - }, - api.ExportRef{ - Root: carRoot, - FromLocalCAR: sourceCar, - DAGs: []api.DagSpec{{DataSelector: &textSelectorNonexistent}}, - }, - &api.FileRef{}, - ), - fmt.Sprintf("parsing dag spec: path selection does not match a node within %s", carRoot), - ) - - // ensure non-boundary retrievals fail - require.EqualError( - t, - testGenesisRetrieval( - ctx, - client, - api.RetrievalOrder{ - Root: carRoot, - DataSelector: &textSelectorNonLink, - }, - api.ExportRef{ - Root: carRoot, - FromLocalCAR: sourceCar, - DAGs: []api.DagSpec{{DataSelector: &textSelectorNonLink}}, - }, - &api.FileRef{}, - ), - fmt.Sprintf("parsing dag spec: error while locating partial retrieval sub-root: unsupported selection path '%s' does not correspond to a block boundary (a.k.a. CID link)", textSelectorNonLink), - ) -} - -func testGenesisRetrieval(ctx context.Context, client *kit.TestFullNode, retOrder api.RetrievalOrder, eref api.ExportRef, retRef *api.FileRef) error { - - if retOrder.Total.Nil() { - retOrder.Total = big.Zero() - } - if retOrder.UnsealPrice.Nil() { - retOrder.UnsealPrice = big.Zero() - } - - if eref.FromLocalCAR == "" { - rr, err := client.ClientRetrieve(ctx, retOrder) - if err != nil { - return err - } - eref.DealID = rr.DealID - - if err := client.ClientRetrieveWait(ctx, rr.DealID); err != nil { - return xerrors.Errorf("retrieval wait: %w", err) - } - } - - err := client.ClientExport(ctx, eref, *retRef) - if err != nil { - return err - } - - outFile, err := os.Open(retRef.Path) - if err != nil { - return err - } - - defer outFile.Close() //nolint:errcheck - - var data []byte - if !retRef.IsCAR { - - data, err = io.ReadAll(outFile) - if err != nil { - return err - } - - } else { - - cr, err := car.NewCarReader(outFile) - if err != nil { - return err - } - - if len(cr.Header.Roots) != 1 { - return fmt.Errorf("expected a single root in result car, got %d", len(cr.Header.Roots)) - } else if eref.DAGs[0].ExportMerkleProof && cr.Header.Roots[0].String() != carRoot.String() { - return fmt.Errorf("expected root cid '%s', got '%s'", carRoot.String(), cr.Header.Roots[0].String()) - } else if !eref.DAGs[0].ExportMerkleProof && cr.Header.Roots[0].String() != selectedCid.String() { - return fmt.Errorf("expected root cid '%s', got '%s'", selectedCid.String(), cr.Header.Roots[0].String()) - } - - blks := make([]blocks.Block, 0) - for { - b, err := cr.Next() - if err == io.EOF { - break - } else if err != nil { - return err - } - - blks = append(blks, b) - } - - if (eref.DAGs[0].ExportMerkleProof && len(blks) != 3) || (!eref.DAGs[0].ExportMerkleProof && len(blks) != 1) { - return fmt.Errorf("expected a car file with 3/1 blocks, got one with %d instead", len(blks)) - } - - data = blks[len(blks)-1].RawData() - } - - if string(data) != expectedResult { - return fmt.Errorf("retrieved data mismatch: expected '%s' got '%s'", expectedResult, data) - } - - return nil -} diff --git a/itests/deals_power_test.go b/itests/deals_power_test.go deleted file mode 100644 index 57483cde7..000000000 --- a/itests/deals_power_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// stm: #integration -package itests - -import ( - "context" - "testing" - "time" - - "github.com/filecoin-project/lotus/itests/kit" -) - -func TestFirstDealEnablesMining(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - - //stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001 - // test making a deal with a fresh miner, and see if it starts to mine. - if testing.Short() { - t.Skip("skipping test in short mode") - } - - kit.QuietMiningLogs() - - var ( - client kit.TestFullNode - genMiner kit.TestMiner // bootstrap - provider kit.TestMiner // no sectors, will need to create one - ) - - ens := kit.NewEnsemble(t, kit.MockProofs()) - ens.FullNode(&client) - ens.Miner(&genMiner, &client, kit.WithAllSubsystems()) - ens.Miner(&provider, &client, kit.WithAllSubsystems(), kit.PresealSectors(0)) - ens.Start().InterconnectAll().BeginMining(50 * time.Millisecond) - - ctx := context.Background() - - dh := kit.NewDealHarness(t, &client, &provider, &provider) - - ref, _ := client.CreateImportFile(ctx, 5, 0) - - t.Log("FILE CID:", ref.Root) - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - // start a goroutine to monitor head changes from the client - // once the provider has mined a block, thanks to the power acquired from the deal, - // we pass the test. - providerMined := make(chan struct{}) - - go func() { - _ = client.WaitTillChain(ctx, kit.BlocksMinedByAll(provider.ActorAddr)) - close(providerMined) - }() - - // now perform the deal. - dp := dh.DefaultStartDealParams() - dp.Data.Root = ref.Root - deal := dh.StartDeal(ctx, dp) - - // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this - time.Sleep(time.Second) - - dh.WaitDealSealed(ctx, deal, false, false, nil) - - <-providerMined -} diff --git a/itests/deals_pricing_test.go b/itests/deals_pricing_test.go deleted file mode 100644 index f2301eee8..000000000 --- a/itests/deals_pricing_test.go +++ /dev/null @@ -1,150 +0,0 @@ -// stm: #integration -package itests - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -func TestQuotePriceForUnsealedRetrieval(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - - //stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001 - var ( - ctx = context.Background() - blocktime = 50 * time.Millisecond - ) - - kit.QuietMiningLogs() - - client, miner, ens := kit.EnsembleMinimal(t) - ens.InterconnectAll().BeginMiningMustPost(blocktime) - - var ( - ppb = int64(1) - unsealPrice = int64(77) - ) - - // Set unsealed price to non-zero - ask, err := miner.MarketGetRetrievalAsk(ctx) - require.NoError(t, err) - ask.PricePerByte = abi.NewTokenAmount(ppb) - ask.UnsealPrice = abi.NewTokenAmount(unsealPrice) - err = miner.MarketSetRetrievalAsk(ctx, ask) - require.NoError(t, err) - - dh := kit.NewDealHarness(t, client, miner, miner) - - deal1, res1, _ := dh.MakeOnlineDeal(ctx, kit.MakeFullDealParams{Rseed: 6}) - - // one more storage deal for the same data - _, res2, _ := dh.MakeOnlineDeal(ctx, kit.MakeFullDealParams{Rseed: 6}) - require.Equal(t, res1.Root, res2.Root) - - //stm: @CLIENT_STORAGE_DEALS_GET_001 - // Retrieval - dealInfo, err := client.ClientGetDealInfo(ctx, *deal1) - require.NoError(t, err) - - //stm: @CLIENT_RETRIEVAL_FIND_001 - // fetch quote -> zero for unsealed price since unsealed file already exists. - offers, err := client.ClientFindData(ctx, res1.Root, &dealInfo.PieceCID) - require.NoError(t, err) - require.Len(t, offers, 2) - require.Equal(t, offers[0], offers[1]) - require.Equal(t, uint64(0), offers[0].UnsealPrice.Uint64()) - require.Equal(t, dealInfo.Size*uint64(ppb), offers[0].MinPrice.Uint64()) - - // remove ONLY one unsealed file - //stm: @STORAGE_LIST_001, @MINER_SECTOR_LIST_001 - ss, err := miner.StorageList(context.Background()) - require.NoError(t, err) - _, err = miner.SectorsListNonGenesis(ctx) - require.NoError(t, err) - - //stm: @STORAGE_DROP_SECTOR_001, @STORAGE_LIST_001 -iLoop: - for storeID, sd := range ss { - for _, sector := range sd { - err := miner.StorageDropSector(ctx, storeID, sector.SectorID, storiface.FTUnsealed) - require.NoError(t, err) - break iLoop // remove ONLY one - } - } - - //stm: @CLIENT_RETRIEVAL_FIND_001 - // get retrieval quote -> zero for unsealed price as unsealed file exists. - offers, err = client.ClientFindData(ctx, res1.Root, &dealInfo.PieceCID) - require.NoError(t, err) - require.Len(t, offers, 2) - require.Equal(t, offers[0], offers[1]) - require.Equal(t, uint64(0), offers[0].UnsealPrice.Uint64()) - require.Equal(t, dealInfo.Size*uint64(ppb), offers[0].MinPrice.Uint64()) - - // remove the other unsealed file as well - ss, err = miner.StorageList(context.Background()) - require.NoError(t, err) - _, err = miner.SectorsListNonGenesis(ctx) - require.NoError(t, err) - for storeID, sd := range ss { - for _, sector := range sd { - require.NoError(t, miner.StorageDropSector(ctx, storeID, sector.SectorID, storiface.FTUnsealed)) - } - } - - //stm: @CLIENT_RETRIEVAL_FIND_001 - // fetch quote -> non-zero for unseal price as we no more unsealed files. - offers, err = client.ClientFindData(ctx, res1.Root, &dealInfo.PieceCID) - require.NoError(t, err) - require.Len(t, offers, 2) - require.Equal(t, offers[0], offers[1]) - require.Equal(t, uint64(unsealPrice), offers[0].UnsealPrice.Uint64()) - total := (dealInfo.Size * uint64(ppb)) + uint64(unsealPrice) - require.Equal(t, total, offers[0].MinPrice.Uint64()) -} - -func TestZeroPricePerByteRetrieval(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - if testing.Short() { - t.Skip("skipping test in short mode") - } - - kit.QuietMiningLogs() - - var ( - blockTime = 10 * time.Millisecond - startEpoch = abi.ChainEpoch(2 << 12) - ) - - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) - ens.InterconnectAll().BeginMiningMustPost(blockTime) - - ctx := context.Background() - - ask, err := miner.MarketGetRetrievalAsk(ctx) - require.NoError(t, err) - - ask.PricePerByte = abi.NewTokenAmount(0) - err = miner.MarketSetRetrievalAsk(ctx, ask) - require.NoError(t, err) - - dh := kit.NewDealHarness(t, client, miner, miner) - dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{ - N: 1, - StartEpoch: startEpoch, - }) -} diff --git a/itests/deals_publish_test.go b/itests/deals_publish_test.go deleted file mode 100644 index 43f4eeb05..000000000 --- a/itests/deals_publish_test.go +++ /dev/null @@ -1,143 +0,0 @@ -// stm: #integration -package itests - -import ( - "bytes" - "context" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-state-types/abi" - market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/chain/wallet/key" - "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/markets/storageadapter" - "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/config" - "github.com/filecoin-project/lotus/node/modules" - "github.com/filecoin-project/lotus/storage/ctladdr" -) - -func TestPublishDealsBatching(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - - //stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001 - var ( - ctx = context.Background() - publishPeriod = 10 * time.Second - maxDealsPerMsg = uint64(2) // Set max deals per publish deals message to 2 - startEpoch = abi.ChainEpoch(2 << 12) - ) - - kit.QuietMiningLogs() - - publisherKey, err := key.GenerateKey(types.KTSecp256k1) - require.NoError(t, err) - - opts := node.Options( - node.Override(new(*storageadapter.DealPublisher), - storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ - Period: publishPeriod, - MaxDealsPerMsg: maxDealsPerMsg, - }), - ), - node.Override(new(*ctladdr.AddressSelector), modules.AddressSelector(&config.MinerAddressConfig{ - DealPublishControl: []string{ - publisherKey.Address.String(), - }, - DisableOwnerFallback: true, - DisableWorkerFallback: true, - })), - ) - - client, miner, ens := kit.EnsembleMinimal(t, kit.Account(publisherKey, types.FromFil(10)), kit.MockProofs(), kit.ConstructorOpts(opts)) - ens.InterconnectAll().BeginMining(10 * time.Millisecond) - - _, err = client.WalletImport(ctx, &publisherKey.KeyInfo) - require.NoError(t, err) - - miner.SetControlAddresses(publisherKey.Address) - - dh := kit.NewDealHarness(t, client, miner, miner) - - // Starts a deal and waits until it's published - runDealTillPublish := func(rseed int) { - res, _ := client.CreateImportFile(ctx, rseed, 0) - - upds, err := client.ClientGetDealUpdates(ctx) - require.NoError(t, err) - - dp := dh.DefaultStartDealParams() - dp.Data.Root = res.Root - dp.DealStartEpoch = startEpoch - dh.StartDeal(ctx, dp) - - // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this - time.Sleep(time.Second) - - done := make(chan struct{}) - go func() { - for upd := range upds { - if upd.DataRef.Root == res.Root && upd.State == storagemarket.StorageDealAwaitingPreCommit { - done <- struct{}{} - } - } - }() - <-done - } - - // Run three deals in parallel - done := make(chan struct{}, maxDealsPerMsg+1) - for rseed := 1; rseed <= 3; rseed++ { - rseed := rseed - go func() { - runDealTillPublish(rseed) - done <- struct{}{} - }() - } - - // Wait for two of the deals to be published - for i := 0; i < int(maxDealsPerMsg); i++ { - <-done - } - - // Expect a single PublishStorageDeals message that includes the first two deals - //stm: @CHAIN_STATE_LIST_MESSAGES_001 - msgCids, err := client.StateListMessages(ctx, &api.MessageMatch{To: market.Address}, types.EmptyTSK, 1) - require.NoError(t, err) - count := 0 - for _, msgCid := range msgCids { - msg, err := client.ChainGetMessage(ctx, msgCid) - require.NoError(t, err) - - if msg.Method == market.Methods.PublishStorageDeals { - count++ - var pubDealsParams market2.PublishStorageDealsParams - err = pubDealsParams.UnmarshalCBOR(bytes.NewReader(msg.Params)) - require.NoError(t, err) - require.Len(t, pubDealsParams.Deals, int(maxDealsPerMsg)) - require.Equal(t, publisherKey.Address.String(), msg.From.String()) - } - } - require.Equal(t, 1, count) - - // The third deal should be published once the publish period expires. - // Allow a little padding as it takes a moment for the state change to - // be noticed by the client. - padding := 10 * time.Second - select { - case <-time.After(publishPeriod + padding): - require.Fail(t, "Expected 3rd deal to be published once publish period elapsed") - case <-done: // Success - } -} diff --git a/itests/deals_remote_retrieval_test.go b/itests/deals_remote_retrieval_test.go deleted file mode 100644 index c0a37e69e..000000000 --- a/itests/deals_remote_retrieval_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package itests - -import ( - "bytes" - "context" - "fmt" - "io" - "net/url" - "os" - "path" - "testing" - "time" - - "github.com/google/uuid" - "github.com/gorilla/websocket" - "github.com/ipld/go-car" - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/api" - bstore "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/itests/kit" -) - -func TestNetStoreRetrieval(t *testing.T) { - kit.QuietMiningLogs() - - blocktime := 5 * time.Millisecond - ctx := context.Background() - - full, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) - ens.InterconnectAll().BeginMining(blocktime) - - time.Sleep(5 * time.Second) - - // For these tests where the block time is artificially short, just use - // a deal start epoch that is guaranteed to be far enough in the future - // so that the deal starts sealing in time - dealStartEpoch := abi.ChainEpoch(2 << 12) - - rseed := 7 - - dh := kit.NewDealHarness(t, full, miner, miner) - dealCid, res, _ := dh.MakeOnlineDeal(context.Background(), kit.MakeFullDealParams{ - Rseed: rseed, - StartEpoch: dealStartEpoch, - UseCARFileForStorageDeal: true, - }) - - // create deal store - id := uuid.New() - rstore := bstore.NewMemorySync() - - au, err := url.Parse(full.ListenURL) - require.NoError(t, err) - - switch au.Scheme { - case "http": - au.Scheme = "ws" - case "https": - au.Scheme = "wss" - } - - au.Path = path.Join(au.Path, "/rest/v0/store/"+id.String()) - - conn, _, err := websocket.DefaultDialer.Dial(au.String(), nil) - require.NoError(t, err) - - _ = bstore.HandleNetBstoreWS(ctx, rstore, conn) - - dh.PerformRetrievalWithOrder(ctx, dealCid, res.Root, false, func(offer api.QueryOffer, address address.Address) api.RetrievalOrder { - order := offer.Order(address) - - order.RemoteStore = &id - - return order - }) - - // check blockstore blocks - carv1FilePath, _ := kit.CreateRandomCARv1(t, rseed, 200) - cb, err := os.ReadFile(carv1FilePath) - require.NoError(t, err) - - cr, err := car.NewCarReader(bytes.NewReader(cb)) - require.NoError(t, err) - - var blocks int - for { - cb, err := cr.Next() - if err == io.EOF { - fmt.Println("blocks: ", blocks) - return - } - require.NoError(t, err) - - sb, err := rstore.Get(ctx, cb.Cid()) - require.NoError(t, err) - require.EqualValues(t, cb.RawData(), sb.RawData()) - - blocks++ - } -} diff --git a/itests/deals_retry_deal_no_funds_test.go b/itests/deals_retry_deal_no_funds_test.go deleted file mode 100644 index 650b2436e..000000000 --- a/itests/deals_retry_deal_no_funds_test.go +++ /dev/null @@ -1,188 +0,0 @@ -// stm: #integration -package itests - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/chain/wallet/key" - "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/markets/storageadapter" - "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/config" - "github.com/filecoin-project/lotus/node/modules" - "github.com/filecoin-project/lotus/storage/ctladdr" -) - -var ( - publishPeriod = 1 * time.Second - maxDealsPerMsg = uint64(2) // Set max deals per publish deals message to 2 - - blockTime = 3 * time.Millisecond -) - -func TestDealsRetryLackOfFunds(t *testing.T) { - t.Run("cover-gas", func(t *testing.T) { - testDealsRetryLackOfFunds(t, types.NewInt(1020000000000)) - }) - t.Run("empty", func(t *testing.T) { - testDealsRetryLackOfFunds(t, types.NewInt(1)) - }) -} - -func testDealsRetryLackOfFunds(t *testing.T, publishStorageAccountFunds abi.TokenAmount) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - - //stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001 - //stm: @CLIENT_STORAGE_DEALS_LIST_IMPORTS_001 - ctx := context.Background() - - kit.QuietMiningLogs() - - // Allow 8MB sectors - eightMBSectorsOpt := kit.SectorSize(8 << 20) - - publishStorageDealKey, err := key.GenerateKey(types.KTSecp256k1) - require.NoError(t, err) - - opts := node.Options( - node.Override(new(*storageadapter.DealPublisher), - storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ - Period: publishPeriod, - MaxDealsPerMsg: maxDealsPerMsg, - }), - ), - node.Override(new(*ctladdr.AddressSelector), modules.AddressSelector(&config.MinerAddressConfig{ - DealPublishControl: []string{ - publishStorageDealKey.Address.String(), - }, - DisableOwnerFallback: true, - DisableWorkerFallback: true, - })), - ) - - minerFullNode, clientFullNode, miner, ens := kit.EnsembleTwoOne(t, kit.Account(publishStorageDealKey, publishStorageAccountFunds), kit.ConstructorOpts(opts), kit.MockProofs(), eightMBSectorsOpt) - - kit.QuietMiningLogs() - - ens. - Start(). - InterconnectAll(). - BeginMining(blockTime) - - _, err = minerFullNode.WalletImport(ctx, &publishStorageDealKey.KeyInfo) - require.NoError(t, err) - - miner.SetControlAddresses(publishStorageDealKey.Address) - - dh := kit.NewDealHarness(t, clientFullNode, miner, miner) - - res, _ := clientFullNode.CreateImportFile(ctx, 0, 4<<20) // 4MiB file. - list, err := clientFullNode.ClientListImports(ctx) - require.NoError(t, err) - require.Len(t, list, 1) - require.Equal(t, res.Root, *list[0].Root) - - dp := dh.DefaultStartDealParams() - dp.Data.Root = res.Root - dp.FastRetrieval = true - dp.EpochPrice = abi.NewTokenAmount(62500000) // minimum asking price. - deal := dh.StartDeal(ctx, dp) - - propcid := *deal - - go func() { - time.Sleep(30 * time.Second) - - kit.SendFunds(ctx, t, minerFullNode, publishStorageDealKey.Address, types.FromFil(1)) - - err := miner.MarketRetryPublishDeal(ctx, propcid) - if err != nil { - panic(err) - } - }() - - dh.WaitDealSealed(ctx, deal, false, false, nil) -} - -func TestDealsRetryLackOfFunds_blockInPublishDeal(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - //stm: @CLIENT_STORAGE_DEALS_LIST_IMPORTS_001 - ctx := context.Background() - kit.QuietMiningLogs() - - // Allow 8MB sectors - eightMBSectorsOpt := kit.SectorSize(8 << 20) - - publishStorageDealKey, err := key.GenerateKey(types.KTSecp256k1) - require.NoError(t, err) - - opts := node.Options( - node.Override(new(*storageadapter.DealPublisher), - storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ - Period: publishPeriod, - MaxDealsPerMsg: maxDealsPerMsg, - }), - ), - node.Override(new(*ctladdr.AddressSelector), modules.AddressSelector(&config.MinerAddressConfig{ - DealPublishControl: []string{ - publishStorageDealKey.Address.String(), - }, - DisableOwnerFallback: true, - DisableWorkerFallback: true, - })), - ) - - publishStorageAccountFunds := types.NewInt(1020000000000) - minerFullNode, clientFullNode, miner, ens := kit.EnsembleTwoOne(t, kit.Account(publishStorageDealKey, publishStorageAccountFunds), kit.ConstructorOpts(opts), kit.MockProofs(), eightMBSectorsOpt) - - kit.QuietMiningLogs() - - ens. - Start(). - InterconnectAll(). - BeginMining(blockTime) - - _, err = minerFullNode.WalletImport(ctx, &publishStorageDealKey.KeyInfo) - require.NoError(t, err) - - miner.SetControlAddresses(publishStorageDealKey.Address) - - dh := kit.NewDealHarness(t, clientFullNode, miner, miner) - - res, _ := clientFullNode.CreateImportFile(ctx, 0, 4<<20) // 4MiB file. - list, err := clientFullNode.ClientListImports(ctx) - require.NoError(t, err) - require.Len(t, list, 1) - require.Equal(t, res.Root, *list[0].Root) - - dp := dh.DefaultStartDealParams() - dp.Data.Root = res.Root - dp.FastRetrieval = true - dp.EpochPrice = abi.NewTokenAmount(62500000) // minimum asking price. - deal := dh.StartDeal(ctx, dp) - - dealSealed := make(chan struct{}) - go func() { - dh.WaitDealSealedQuiet(ctx, deal, false, false, nil) - dealSealed <- struct{}{} - }() - - select { - case <-dealSealed: - t.Fatal("deal shouldn't have sealed") - case <-time.After(time.Second * 15): - } -} diff --git a/itests/deals_test.go b/itests/deals_test.go deleted file mode 100644 index a6953d07e..000000000 --- a/itests/deals_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// stm: #integration -package itests - -import ( - "testing" - "time" - - "github.com/filecoin-project/lotus/itests/kit" -) - -func TestDealsWithSealingAndRPC(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - - //stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001 - if testing.Short() { - t.Skip("skipping test in short mode") - } - - kit.QuietMiningLogs() - - client, miner, ens := kit.EnsembleMinimal(t, kit.ThroughRPC(), kit.WithAllSubsystems()) // no mock proofs. - ens.InterconnectAll().BeginMiningMustPost(250 * time.Millisecond) - dh := kit.NewDealHarness(t, client, miner, miner) - - t.Run("stdretrieval", func(t *testing.T) { - dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1}) - }) - - t.Run("fastretrieval", func(t *testing.T) { - dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1, FastRetrieval: true}) - }) - - t.Run("fastretrieval-twodeals-sequential", func(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/eth_account_abstraction_test.go b/itests/eth_account_abstraction_test.go index 5ca672674..58e122e16 100644 --- a/itests/eth_account_abstraction_test.go +++ b/itests/eth_account_abstraction_test.go @@ -74,7 +74,7 @@ func TestEthAccountAbstraction(t *testing.T) { msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) require.NoError(t, err) - txArgs, err := ethtypes.EthTxArgsFromUnsignedEthMessage(msgFromPlaceholder) + txArgs, err := ethtypes.Eth1559TxArgsFromUnsignedFilecoinMessage(msgFromPlaceholder) require.NoError(t, err) digest, err := txArgs.ToRlpUnsignedMsg() @@ -111,7 +111,7 @@ func TestEthAccountAbstraction(t *testing.T) { msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) require.NoError(t, err) - txArgs, err = ethtypes.EthTxArgsFromUnsignedEthMessage(msgFromPlaceholder) + txArgs, err = ethtypes.Eth1559TxArgsFromUnsignedFilecoinMessage(msgFromPlaceholder) require.NoError(t, err) digest, err = txArgs.ToRlpUnsignedMsg() @@ -185,7 +185,7 @@ func TestEthAccountAbstractionFailure(t *testing.T) { require.NoError(t, err) msgFromPlaceholder.Value = abi.TokenAmount(types.MustParseFIL("1000")) - txArgs, err := ethtypes.EthTxArgsFromUnsignedEthMessage(msgFromPlaceholder) + txArgs, err := ethtypes.Eth1559TxArgsFromUnsignedFilecoinMessage(msgFromPlaceholder) require.NoError(t, err) digest, err := txArgs.ToRlpUnsignedMsg() @@ -224,7 +224,7 @@ func TestEthAccountAbstractionFailure(t *testing.T) { msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) require.NoError(t, err) - txArgs, err = ethtypes.EthTxArgsFromUnsignedEthMessage(msgFromPlaceholder) + txArgs, err = ethtypes.Eth1559TxArgsFromUnsignedFilecoinMessage(msgFromPlaceholder) require.NoError(t, err) digest, err = txArgs.ToRlpUnsignedMsg() @@ -285,7 +285,7 @@ func TestEthAccountAbstractionFailsFromEvmActor(t *testing.T) { maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) require.NoError(t, err) - tx := ethtypes.EthTxArgs{ + tx := ethtypes.Eth1559TxArgs{ ChainID: build.Eip155ChainId, Value: big.Zero(), Nonce: 0, @@ -302,7 +302,7 @@ func TestEthAccountAbstractionFailsFromEvmActor(t *testing.T) { client.EVM().SubmitTransaction(ctx, &tx) - smsg, err := tx.ToSignedMessage() + smsg, err := ethtypes.ToSignedFilecoinMessage(&tx) require.NoError(t, err) ml, err := client.StateWaitMsg(ctx, smsg.Cid(), 1, api.LookbackNoLimit, true) diff --git a/itests/eth_conformance_test.go b/itests/eth_conformance_test.go index 9c1b2ae34..13d42e741 100644 --- a/itests/eth_conformance_test.go +++ b/itests/eth_conformance_test.go @@ -463,7 +463,7 @@ func createRawSignedEthTx(ctx context.Context, t *testing.T, client *kit.TestFul maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) require.NoError(t, err) - tx := ethtypes.EthTxArgs{ + tx := ethtypes.Eth1559TxArgs{ ChainID: build.Eip155ChainId, Value: big.NewInt(100), Nonce: 0, diff --git a/itests/eth_deploy_test.go b/itests/eth_deploy_test.go index 68861f98f..b3a4c26f7 100644 --- a/itests/eth_deploy_test.go +++ b/itests/eth_deploy_test.go @@ -73,7 +73,7 @@ func TestDeployment(t *testing.T) { require.NoError(t, err) // now deploy a contract from the placeholder, and validate it went well - tx := ethtypes.EthTxArgs{ + tx := ethtypes.Eth1559TxArgs{ ChainID: build.Eip155ChainId, Value: big.Zero(), Nonce: 0, diff --git a/itests/eth_hash_lookup_test.go b/itests/eth_hash_lookup_test.go index 5edd61d4e..1610e2458 100644 --- a/itests/eth_hash_lookup_test.go +++ b/itests/eth_hash_lookup_test.go @@ -61,7 +61,7 @@ func TestTransactionHashLookup(t *testing.T) { require.NoError(t, err) // now deploy a contract from the embryo, and validate it went well - tx := ethtypes.EthTxArgs{ + tx := ethtypes.Eth1559TxArgs{ ChainID: build.Eip155ChainId, Value: big.Zero(), Nonce: 0, @@ -367,7 +367,7 @@ func TestEthGetMessageCidByTransactionHashEthTx(t *testing.T) { require.NoError(t, err) // now deploy a contract from the embryo, and validate it went well - tx := ethtypes.EthTxArgs{ + tx := ethtypes.Eth1559TxArgs{ ChainID: build.Eip155ChainId, Value: big.Zero(), Nonce: 0, @@ -385,7 +385,7 @@ func TestEthGetMessageCidByTransactionHashEthTx(t *testing.T) { sender, err := tx.Sender() require.NoError(t, err) - unsignedMessage, err := tx.ToUnsignedMessage(sender) + unsignedMessage, err := tx.ToUnsignedFilecoinMessage(sender) require.NoError(t, err) rawTxHash, err := tx.TxHash() diff --git a/itests/eth_legacy_transactions_test.go b/itests/eth_legacy_transactions_test.go new file mode 100644 index 000000000..791f6794d --- /dev/null +++ b/itests/eth_legacy_transactions_test.go @@ -0,0 +1,400 @@ +package itests + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "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/lotus/build" + "github.com/filecoin-project/lotus/chain/consensus/filcns" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/itests/kit" +) + +func TestLegacyValueTransferValidSignature(t *testing.T) { + blockTime := 100 * time.Millisecond + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) + + ens.InterconnectAll().BeginMining(blockTime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // create a new Ethereum account + key, ethAddr, deployer := client.EVM().NewAccount() + _, ethAddr2, _ := client.EVM().NewAccount() + + kit.SendFunds(ctx, t, client, deployer, types.FromFil(1000)) + + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ + From: ðAddr, + To: ðAddr2, + Value: ethtypes.EthBigInt(big.NewInt(100)), + }}) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) + require.NoError(t, err) + fmt.Println("gas limit is", gaslimit) + + tx := ethtypes.EthLegacyHomesteadTxArgs{ + Value: big.NewInt(100), + Nonce: 0, + To: ðAddr2, + GasPrice: types.NanoFil, + GasLimit: int(gaslimit), + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + + client.EVM().SignLegacyHomesteadTransaction(&tx, key.PrivateKey) + // Mangle signature + tx.V.Int.Xor(tx.V.Int, big.NewInt(1).Int) + + signed, err := tx.ToRlpSignedMsg() + require.NoError(t, err) + // Submit transaction with bad signature + _, err = client.EVM().EthSendRawTransaction(ctx, signed) + require.Error(t, err) + + // Submit transaction with valid signature + client.EVM().SignLegacyHomesteadTransaction(&tx, key.PrivateKey) + + hash := client.EVM().SubmitTransaction(ctx, &tx) + + receipt, err := client.EVM().WaitTransaction(ctx, hash) + require.NoError(t, err) + require.NotNil(t, receipt) + require.EqualValues(t, ethAddr, receipt.From) + require.EqualValues(t, ethAddr2, *receipt.To) + require.EqualValues(t, hash, receipt.TransactionHash) + + // Success. + require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status) + + // Validate that we sent the expected transaction. + ethTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + require.Nil(t, ethTx.MaxPriorityFeePerGas) + require.Nil(t, ethTx.MaxFeePerGas) + + require.EqualValues(t, ethAddr, ethTx.From) + require.EqualValues(t, ethAddr2, *ethTx.To) + require.EqualValues(t, tx.Nonce, ethTx.Nonce) + require.EqualValues(t, hash, ethTx.Hash) + require.EqualValues(t, tx.Value, ethTx.Value) + require.EqualValues(t, 0, ethTx.Type) + require.EqualValues(t, 0, ethTx.ChainID) + require.EqualValues(t, ethtypes.EthBytes{}, ethTx.Input) + require.EqualValues(t, tx.GasLimit, ethTx.Gas) + require.EqualValues(t, tx.GasPrice, *ethTx.GasPrice) + require.EqualValues(t, tx.R, ethTx.R) + require.EqualValues(t, tx.S, ethTx.S) + require.EqualValues(t, tx.V, ethTx.V) +} + +func TestLegacyEIP155ValueTransferValidSignatureFailsNV22(t *testing.T) { + blockTime := 100 * time.Millisecond + + nv23Height := 10 + // We will move to NV23 at epoch 10 + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.UpgradeSchedule(stmgr.Upgrade{ + Network: network.Version22, + Height: -1, + }, stmgr.Upgrade{ + Network: network.Version23, + Height: abi.ChainEpoch(nv23Height), + Migration: filcns.UpgradeActorsV13, + })) + + ens.InterconnectAll().BeginMining(blockTime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // create a new Ethereum account + key, ethAddr, deployer := client.EVM().NewAccount() + _, ethAddr2, _ := client.EVM().NewAccount() + + kit.SendFunds(ctx, t, client, deployer, types.FromFil(1000)) + + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ + From: ðAddr, + To: ðAddr2, + Value: ethtypes.EthBigInt(big.NewInt(100)), + }}) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) + require.NoError(t, err) + + legacyTx := ðtypes.EthLegacyHomesteadTxArgs{ + Value: big.NewInt(100), + Nonce: 0, + To: ðAddr2, + GasPrice: types.NanoFil, + GasLimit: int(gaslimit), + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + tx := ethtypes.NewEthLegacy155TxArgs(legacyTx) + + // TX will fail as we're still at NV22 + client.EVM().SignLegacyEIP155Transaction(tx, key.PrivateKey, big.NewInt(build.Eip155ChainId)) + + signed, err := tx.ToRawTxBytesSigned() + require.NoError(t, err) + + _, err = client.EVM().EthSendRawTransaction(ctx, signed) + require.Error(t, err) + require.Contains(t, err.Error(), "network version should be atleast NV23 for sending legacy ETH transactions") +} + +func TestLegacyEIP155ValueTransferValidSignature(t *testing.T) { + blockTime := 100 * time.Millisecond + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) + + ens.InterconnectAll().BeginMining(blockTime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // create a new Ethereum account + key, ethAddr, deployer := client.EVM().NewAccount() + _, ethAddr2, _ := client.EVM().NewAccount() + + kit.SendFunds(ctx, t, client, deployer, types.FromFil(1000)) + + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ + From: ðAddr, + To: ðAddr2, + Value: ethtypes.EthBigInt(big.NewInt(100)), + }}) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) + require.NoError(t, err) + + legacyTx := ðtypes.EthLegacyHomesteadTxArgs{ + Value: big.NewInt(100), + Nonce: 0, + To: ðAddr2, + GasPrice: types.NanoFil, + GasLimit: int(gaslimit), + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + tx := ethtypes.NewEthLegacy155TxArgs(legacyTx) + + client.EVM().SignLegacyEIP155Transaction(tx, key.PrivateKey, big.NewInt(build.Eip155ChainId)) + // Mangle signature + innerTx := tx.GetLegacyTx() + innerTx.V.Int.Xor(innerTx.V.Int, big.NewInt(1).Int) + + signed, err := tx.ToRawTxBytesSigned() + require.NoError(t, err) + // Submit transaction with bad signature + _, err = client.EVM().EthSendRawTransaction(ctx, signed) + require.Error(t, err) + + // Submit transaction with valid signature but incorrect chain ID + client.EVM().SignLegacyEIP155Transaction(tx, key.PrivateKey, big.NewInt(build.Eip155ChainId)) + + signed, err = tx.ToRawTxBytesSigned() + require.NoError(t, err) + + hash, err := client.EVM().EthSendRawTransaction(ctx, signed) + require.NoError(t, err) + + receipt, err := client.EVM().WaitTransaction(ctx, hash) + require.NoError(t, err) + require.NotNil(t, receipt) + require.EqualValues(t, ethAddr, receipt.From) + require.EqualValues(t, ethAddr2, *receipt.To) + require.EqualValues(t, hash, receipt.TransactionHash) + + // Success. + require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status) + + // Validate that we sent the expected transaction. + ethTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + require.Nil(t, ethTx.MaxPriorityFeePerGas) + require.Nil(t, ethTx.MaxFeePerGas) + + innerTx = tx.GetLegacyTx() + require.EqualValues(t, ethAddr, ethTx.From) + require.EqualValues(t, ethAddr2, *ethTx.To) + require.EqualValues(t, innerTx.Nonce, ethTx.Nonce) + require.EqualValues(t, hash, ethTx.Hash) + require.EqualValues(t, innerTx.Value, ethTx.Value) + require.EqualValues(t, 0, ethTx.Type) + require.EqualValues(t, build.Eip155ChainId, ethTx.ChainID) + require.EqualValues(t, ethtypes.EthBytes{}, ethTx.Input) + require.EqualValues(t, innerTx.GasLimit, ethTx.Gas) + require.EqualValues(t, innerTx.GasPrice, *ethTx.GasPrice) + require.EqualValues(t, innerTx.R, ethTx.R) + require.EqualValues(t, innerTx.S, ethTx.S) + require.EqualValues(t, innerTx.V, ethTx.V) +} + +func TestLegacyContractInvocation(t *testing.T) { + blockTime := 100 * time.Millisecond + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) + + ens.InterconnectAll().BeginMining(blockTime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // create a new Ethereum account + key, ethAddr, deployer := client.EVM().NewAccount() + // send some funds to the f410 address + kit.SendFunds(ctx, t, client, deployer, types.FromFil(10)) + + // DEPLOY CONTRACT + tx, err := deployLegacyContractTx(ctx, t, client, ethAddr) + require.NoError(t, err) + + client.EVM().SignLegacyHomesteadTransaction(tx, key.PrivateKey) + // Mangle signature + tx.V.Int.Xor(tx.V.Int, big.NewInt(1).Int) + + signed, err := tx.ToRlpSignedMsg() + require.NoError(t, err) + // Submit transaction with bad signature + _, err = client.EVM().EthSendRawTransaction(ctx, signed) + require.Error(t, err) + + // Submit transaction with valid signature + client.EVM().SignLegacyHomesteadTransaction(tx, key.PrivateKey) + + hash := client.EVM().SubmitTransaction(ctx, tx) + + receipt, err := client.EVM().WaitTransaction(ctx, hash) + require.NoError(t, err) + require.NotNil(t, receipt) + require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status) + + // Get contract address. + contractAddr := client.EVM().ComputeContractAddress(ethAddr, 0) + + // INVOKE CONTRACT + + // Params + // entry point for getBalance - f8b2cb4f + // address - ff00000000000000000000000000000000000064 + params, err := hex.DecodeString("f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064") + require.NoError(t, err) + + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ + From: ðAddr, + To: &contractAddr, + Data: params, + }}) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) + require.NoError(t, err) + + maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) + require.NoError(t, err) + + invokeTx := ethtypes.EthLegacyHomesteadTxArgs{ + To: &contractAddr, + Value: big.Zero(), + Nonce: 1, + GasPrice: big.Int(maxPriorityFeePerGas), + GasLimit: int(gaslimit), + Input: params, + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + + client.EVM().SignLegacyHomesteadTransaction(&invokeTx, key.PrivateKey) + // Mangle signature + invokeTx.V.Int.Xor(invokeTx.V.Int, big.NewInt(1).Int) + + signed, err = invokeTx.ToRlpSignedMsg() + require.NoError(t, err) + // Submit transaction with bad signature + _, err = client.EVM().EthSendRawTransaction(ctx, signed) + require.Error(t, err) + + // Submit transaction with valid signature + client.EVM().SignLegacyHomesteadTransaction(&invokeTx, key.PrivateKey) + hash = client.EVM().SubmitTransaction(ctx, &invokeTx) + + receipt, err = client.EVM().WaitTransaction(ctx, hash) + require.NoError(t, err) + require.NotNil(t, receipt) + + // Success. + require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status) + + // Validate that we correctly computed the gas outputs. + mCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) + require.NoError(t, err) + require.NotNil(t, mCid) + + invokResult, err := client.StateReplay(ctx, types.EmptyTSK, *mCid) + require.NoError(t, err) + require.EqualValues(t, invokResult.GasCost.GasUsed, big.NewInt(int64(receipt.GasUsed))) + effectiveGasPrice := big.Div(invokResult.GasCost.TotalCost, invokResult.GasCost.GasUsed) + require.EqualValues(t, effectiveGasPrice, big.Int(receipt.EffectiveGasPrice)) +} + +func deployLegacyContractTx(ctx context.Context, t *testing.T, client *kit.TestFullNode, ethAddr ethtypes.EthAddress) (*ethtypes.EthLegacyHomesteadTxArgs, error) { + // install contract + contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ + From: ðAddr, + Data: contract, + }}) + if err != nil { + return nil, err + } + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) + if err != nil { + return nil, err + } + + maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) + if err != nil { + return nil, err + } + + // now deploy a contract from the embryo, and validate it went well + return ðtypes.EthLegacyHomesteadTxArgs{ + Value: big.Zero(), + Nonce: 0, + GasPrice: big.Int(maxPriorityFeePerGas), + GasLimit: int(gaslimit), + Input: contract, + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + }, nil +} diff --git a/itests/eth_transactions_test.go b/itests/eth_transactions_test.go index 9e9fb7b87..3cb11d4a3 100644 --- a/itests/eth_transactions_test.go +++ b/itests/eth_transactions_test.go @@ -64,7 +64,7 @@ func TestValueTransferValidSignature(t *testing.T) { maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) require.NoError(t, err) - tx := ethtypes.EthTxArgs{ + tx := ethtypes.Eth1559TxArgs{ ChainID: build.Eip155ChainId, Value: big.NewInt(100), Nonce: 0, @@ -113,30 +113,13 @@ func TestValueTransferValidSignature(t *testing.T) { require.EqualValues(t, 2, ethTx.Type) require.EqualValues(t, ethtypes.EthBytes{}, ethTx.Input) require.EqualValues(t, tx.GasLimit, ethTx.Gas) - require.EqualValues(t, tx.MaxFeePerGas, ethTx.MaxFeePerGas) - require.EqualValues(t, tx.MaxPriorityFeePerGas, ethTx.MaxPriorityFeePerGas) + require.EqualValues(t, tx.MaxFeePerGas, *ethTx.MaxFeePerGas) + require.EqualValues(t, tx.MaxPriorityFeePerGas, *ethTx.MaxPriorityFeePerGas) require.EqualValues(t, tx.V, ethTx.V) require.EqualValues(t, tx.R, ethTx.R) require.EqualValues(t, tx.S, ethTx.S) } -func TestLegacyTransaction(t *testing.T) { - blockTime := 100 * time.Millisecond - client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) - - ens.InterconnectAll().BeginMining(blockTime) - - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - // This is a legacy style transaction obtained from etherscan - // Tx details: https://etherscan.io/getRawTx?tx=0x0763262208d89efeeb50c8bb05b50c537903fe9d7bdef3b223fd1f5f69f69b32 - txBytes, err := hex.DecodeString("f86f830131cf8504a817c800825208942cf1e5a8250ded8835694ebeb90cfa0237fcb9b1882ec4a5251d1100008026a0f5f8d2244d619e211eeb634acd1bea0762b7b4c97bba9f01287c82bfab73f911a015be7982898aa7cc6c6f27ff33e999e4119d6cd51330353474b98067ff56d930") - require.NoError(t, err) - _, err = client.EVM().EthSendRawTransaction(ctx, txBytes) - require.ErrorContains(t, err, "legacy transaction is not supported") -} - func TestContractDeploymentValidSignature(t *testing.T) { blockTime := 100 * time.Millisecond client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) @@ -255,7 +238,7 @@ func TestContractInvocation(t *testing.T) { maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) require.NoError(t, err) - invokeTx := ethtypes.EthTxArgs{ + invokeTx := ethtypes.Eth1559TxArgs{ ChainID: build.Eip155ChainId, To: &contractAddr, Value: big.Zero(), @@ -363,7 +346,7 @@ func TestGetBlockByNumber(t *testing.T) { require.Equal(t, types.FromFil(10).Int, bal.Int) } -func deployContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr ethtypes.EthAddress, contract []byte) (*ethtypes.EthTxArgs, error) { +func deployContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr ethtypes.EthAddress, contract []byte) (*ethtypes.Eth1559TxArgs, error) { gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ From: ðAddr, Data: contract, @@ -383,7 +366,7 @@ func deployContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr eth } // now deploy a contract from the embryo, and validate it went well - return ðtypes.EthTxArgs{ + return ðtypes.Eth1559TxArgs{ ChainID: build.Eip155ChainId, Value: big.Zero(), Nonce: 0, @@ -607,3 +590,57 @@ func TestEthTxFromNativeAccount_InvalidReceiver(t *testing.T) { require.NoError(t, err) require.EqualValues(t, &expectedTo, tx.To) } + +func TestTraceTransaction(t *testing.T) { + blockTime := 100 * time.Millisecond + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) + + ens.InterconnectAll().BeginMining(blockTime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // install contract + contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + // create a new Ethereum account + key, ethAddr, deployer := client.EVM().NewAccount() + // send some funds to the f410 address + kit.SendFunds(ctx, t, client, deployer, types.FromFil(10)) + + // DEPLOY CONTRACT + tx, err := deployContractTx(ctx, client, ethAddr, contract) + require.NoError(t, err) + + client.EVM().SignTransaction(tx, key.PrivateKey) + hash := client.EVM().SubmitTransaction(ctx, tx) + + // EthTraceTransaction errors when tx hash is not found + nonExistentTxHash := "0x0000000000000000000000000000000000000000000000000000000000000000" + traces, err := client.EthTraceTransaction(ctx, nonExistentTxHash) + require.Error(t, err) + require.Contains(t, err.Error(), "transaction not found") + require.Nil(t, traces) + + // EthTraceTransaction errors when a trace for pending transactions is requested + traces, err = client.EthTraceTransaction(ctx, hash.String()) + require.Error(t, err) + require.Contains(t, err.Error(), "no trace for pending transactions") + require.Nil(t, traces) + + receipt, err := client.EVM().WaitTransaction(ctx, hash) + require.NoError(t, err) + require.NotNil(t, receipt) + require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status) + + // get trace and verify values + traces, err = client.EthTraceTransaction(ctx, hash.String()) + require.NoError(t, err) + require.NotNil(t, traces) + require.EqualValues(t, traces[0].TransactionHash, hash) + require.EqualValues(t, traces[0].BlockNumber, receipt.BlockNumber) +} diff --git a/itests/fevm_test.go b/itests/fevm_test.go index 2dcb8ef1d..c2b5e2aa9 100644 --- a/itests/fevm_test.go +++ b/itests/fevm_test.go @@ -376,7 +376,7 @@ func TestFEVMTestApp(t *testing.T) { } -// TestFEVMTestApp creates a contract that just has a self destruct feature and calls it +// TestFEVMTestConstructor creates a contract that just has a self destruct feature and calls it func TestFEVMTestConstructor(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() @@ -407,7 +407,7 @@ func TestFEVMAutoSelfDestruct(t *testing.T) { require.NoError(t, err) } -// TestFEVMTestApp creates a contract that just has a self destruct feature and calls it +// TestFEVMTestSendToContract creates a contract that just has a self destruct feature and calls it func TestFEVMTestSendToContract(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() @@ -678,7 +678,7 @@ func TestFEVMRecursiveActorCallEstimate(t *testing.T) { nonce, err := client.MpoolGetNonce(ctx, ethFilAddr) require.NoError(t, err) - tx := ðtypes.EthTxArgs{ + tx := ðtypes.Eth1559TxArgs{ ChainID: build.Eip155ChainId, To: &contractAddr, Value: big.Zero(), @@ -695,7 +695,7 @@ func TestFEVMRecursiveActorCallEstimate(t *testing.T) { client.EVM().SignTransaction(tx, key.PrivateKey) hash := client.EVM().SubmitTransaction(ctx, tx) - smsg, err := tx.ToSignedMessage() + smsg, err := ethtypes.ToSignedFilecoinMessage(tx) require.NoError(t, err) _, err = client.StateWaitMsg(ctx, smsg.Cid(), 0, 0, false) @@ -834,7 +834,7 @@ func TestFEVMBareTransferTriggersSmartContractLogic(t *testing.T) { maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) require.NoError(t, err) - tx := ethtypes.EthTxArgs{ + tx := ethtypes.Eth1559TxArgs{ ChainID: build.Eip155ChainId, Value: big.NewInt(100), Nonce: 0, diff --git a/itests/gateway_test.go b/itests/gateway_test.go index 2dc4e1034..b994d6de3 100644 --- a/itests/gateway_test.go +++ b/itests/gateway_test.go @@ -24,7 +24,6 @@ import ( "github.com/filecoin-project/lotus/api/client" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/cli/clicommands" "github.com/filecoin-project/lotus/gateway" "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/itests/multisig" @@ -194,46 +193,6 @@ func TestGatewayMsigCLI(t *testing.T) { multisig.RunMultisigTests(t, lite) } -func TestGatewayDealFlow(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - kit.QuietMiningLogs() - - blocktime := 5 * time.Millisecond - ctx := context.Background() - nodes := startNodesWithFunds(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit) - - time.Sleep(5 * time.Second) - - // For these tests where the block time is artificially short, just use - // a deal start epoch that is guaranteed to be far enough in the future - // so that the deal starts sealing in time - dealStartEpoch := abi.ChainEpoch(2 << 12) - - dh := kit.NewDealHarness(t, nodes.lite, nodes.miner, nodes.miner) - dealCid, res, _ := dh.MakeOnlineDeal(context.Background(), kit.MakeFullDealParams{ - Rseed: 6, - StartEpoch: dealStartEpoch, - }) - dh.PerformRetrieval(ctx, dealCid, res.Root, false) -} - -func TestGatewayCLIDealFlow(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - kit.QuietMiningLogs() - - blocktime := 5 * time.Millisecond - ctx := context.Background() - nodes := startNodesWithFunds(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit) - - kit.RunClientTest(t, clicommands.Commands, nodes.lite) -} - type testNodes struct { lite *kit.TestFullNode full *kit.TestFullNode diff --git a/itests/harmonytask_test.go b/itests/harmonytask_test.go deleted file mode 100644 index 94024e3e1..000000000 --- a/itests/harmonytask_test.go +++ /dev/null @@ -1,303 +0,0 @@ -package itests - -import ( - "context" - "errors" - "fmt" - "sort" - "sync" - "testing" - "time" - - logging "github.com/ipfs/go-log/v2" - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/harmonytask" - "github.com/filecoin-project/lotus/lib/harmony/resources" - "github.com/filecoin-project/lotus/node/impl" -) - -type task1 struct { - toAdd []int - myPersonalTableLock sync.Mutex - myPersonalTable map[harmonytask.TaskID]int // This would typically be a DB table - WorkCompleted []string -} - -func withDbSetup(t *testing.T, f func(*kit.TestMiner)) { - _, miner, _ := kit.EnsembleMinimal(t, - kit.LatestActorsAt(-1), - kit.MockProofs(), - kit.WithSectorIndexDB(), - ) - _ = logging.SetLogLevel("harmonytask", "debug") - - f(miner) -} - -func (t *task1) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - if !stillOwned() { - return false, errors.New("Why not still owned?") - } - t.myPersonalTableLock.Lock() - defer t.myPersonalTableLock.Unlock() - t.WorkCompleted = append(t.WorkCompleted, fmt.Sprintf("taskResult%d", t.myPersonalTable[taskID])) - return true, nil -} -func (t *task1) CanAccept(list []harmonytask.TaskID, e *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - return &list[0], nil -} -func (t *task1) TypeDetails() harmonytask.TaskTypeDetails { - return harmonytask.TaskTypeDetails{ - Max: 100, - Name: "ThingOne", - MaxFailures: 1, - Cost: resources.Resources{ - Cpu: 1, - Ram: 100 << 10, // at 100kb, it's tiny - }, - } -} -func (t *task1) Adder(add harmonytask.AddTaskFunc) { - for _, vTmp := range t.toAdd { - v := vTmp - add(func(tID harmonytask.TaskID, tx *harmonydb.Tx) (bool, error) { - t.myPersonalTableLock.Lock() - defer t.myPersonalTableLock.Unlock() - - t.myPersonalTable[tID] = v - return true, nil - }) - } -} - -func init() { - //logging.SetLogLevel("harmonydb", "debug") - //logging.SetLogLevel("harmonytask", "debug") -} - -func TestHarmonyTasks(t *testing.T) { - //t.Parallel() - withDbSetup(t, func(m *kit.TestMiner) { - cdb := m.BaseAPI.(*impl.StorageMinerAPI).HarmonyDB - t1 := &task1{ - toAdd: []int{56, 73}, - myPersonalTable: map[harmonytask.TaskID]int{}, - } - harmonytask.POLL_DURATION = time.Millisecond * 100 - e, err := harmonytask.New(cdb, []harmonytask.TaskInterface{t1}, "test:1") - require.NoError(t, err) - time.Sleep(time.Second) // do the work. FLAKYNESS RISK HERE. - e.GracefullyTerminate() - expected := []string{"taskResult56", "taskResult73"} - sort.Strings(t1.WorkCompleted) - require.Equal(t, expected, t1.WorkCompleted, "unexpected results") - }) -} - -type passthru struct { - dtl harmonytask.TaskTypeDetails - do func(tID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) - canAccept func(list []harmonytask.TaskID, e *harmonytask.TaskEngine) (*harmonytask.TaskID, error) - adder func(add harmonytask.AddTaskFunc) -} - -func (t *passthru) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - return t.do(taskID, stillOwned) -} -func (t *passthru) CanAccept(list []harmonytask.TaskID, e *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - return t.canAccept(list, e) -} -func (t *passthru) TypeDetails() harmonytask.TaskTypeDetails { - return t.dtl -} -func (t *passthru) Adder(add harmonytask.AddTaskFunc) { - if t.adder != nil { - t.adder(add) - } -} - -// Common stuff -var dtl = harmonytask.TaskTypeDetails{Name: "foo", Max: -1, Cost: resources.Resources{}} -var lettersMutex sync.Mutex - -func fooLetterAdder(t *testing.T, cdb *harmonydb.DB) *passthru { - return &passthru{ - dtl: dtl, - canAccept: func(list []harmonytask.TaskID, e *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - return nil, nil - }, - adder: func(add harmonytask.AddTaskFunc) { - for _, vTmp := range []string{"A", "B"} { - v := vTmp - add(func(tID harmonytask.TaskID, tx *harmonydb.Tx) (bool, error) { - _, err := tx.Exec("INSERT INTO itest_scratch (some_int, content) VALUES ($1,$2)", tID, v) - require.NoError(t, err) - return true, nil - }) - } - }, - } -} -func fooLetterSaver(t *testing.T, cdb *harmonydb.DB, dest *[]string) *passthru { - return &passthru{ - dtl: dtl, - canAccept: func(list []harmonytask.TaskID, e *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - return &list[0], nil - }, - do: func(tID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - var content string - err = cdb.QueryRow(context.Background(), - "SELECT content FROM itest_scratch WHERE some_int=$1", tID).Scan(&content) - require.NoError(t, err) - lettersMutex.Lock() - defer lettersMutex.Unlock() - *dest = append(*dest, content) - return true, nil - }, - } -} - -func TestHarmonyTasksWith2PartiesPolling(t *testing.T) { - //t.Parallel() - withDbSetup(t, func(m *kit.TestMiner) { - cdb := m.BaseAPI.(*impl.StorageMinerAPI).HarmonyDB - senderParty := fooLetterAdder(t, cdb) - var dest []string - workerParty := fooLetterSaver(t, cdb, &dest) - harmonytask.POLL_DURATION = time.Millisecond * 100 - sender, err := harmonytask.New(cdb, []harmonytask.TaskInterface{senderParty}, "test:1") - require.NoError(t, err) - worker, err := harmonytask.New(cdb, []harmonytask.TaskInterface{workerParty}, "test:2") - require.NoError(t, err) - time.Sleep(time.Second) // do the work. FLAKYNESS RISK HERE. - sender.GracefullyTerminate() - worker.GracefullyTerminate() - sort.Strings(dest) - require.Equal(t, []string{"A", "B"}, dest) - }) -} - -func TestWorkStealing(t *testing.T) { - //t.Parallel() - withDbSetup(t, func(m *kit.TestMiner) { - cdb := m.BaseAPI.(*impl.StorageMinerAPI).HarmonyDB - ctx := context.Background() - - // The dead worker will be played by a few SQL INSERTS. - _, err := cdb.Exec(ctx, `INSERT INTO harmony_machines - (id, last_contact,host_and_port, cpu, ram, gpu) - VALUES (300, DATE '2000-01-01', 'test:1', 4, 400000, 1)`) - require.ErrorIs(t, err, nil) - _, err = cdb.Exec(ctx, `INSERT INTO harmony_task - (id, name, owner_id, posted_time, added_by) - VALUES (1234, 'foo', 300, DATE '2000-01-01', 300)`) - require.ErrorIs(t, err, nil) - _, err = cdb.Exec(ctx, "INSERT INTO itest_scratch (some_int, content) VALUES (1234, 'M')") - require.ErrorIs(t, err, nil) - - harmonytask.POLL_DURATION = time.Millisecond * 100 - harmonytask.CLEANUP_FREQUENCY = time.Millisecond * 100 - var dest []string - worker, err := harmonytask.New(cdb, []harmonytask.TaskInterface{fooLetterSaver(t, cdb, &dest)}, "test:2") - require.ErrorIs(t, err, nil) - time.Sleep(time.Second) // do the work. FLAKYNESS RISK HERE. - worker.GracefullyTerminate() - require.Equal(t, []string{"M"}, dest) - }) -} - -func TestTaskRetry(t *testing.T) { - //t.Parallel() - withDbSetup(t, func(m *kit.TestMiner) { - cdb := m.BaseAPI.(*impl.StorageMinerAPI).HarmonyDB - senderParty := fooLetterAdder(t, cdb) - harmonytask.POLL_DURATION = time.Millisecond * 100 - sender, err := harmonytask.New(cdb, []harmonytask.TaskInterface{senderParty}, "test:1") - require.NoError(t, err) - - alreadyFailed := map[string]bool{} - var dest []string - fails2xPerMsg := &passthru{ - dtl: dtl, - canAccept: func(list []harmonytask.TaskID, e *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - return &list[0], nil - }, - do: func(tID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - var content string - err = cdb.QueryRow(context.Background(), - "SELECT content FROM itest_scratch WHERE some_int=$1", tID).Scan(&content) - require.NoError(t, err) - lettersMutex.Lock() - defer lettersMutex.Unlock() - if !alreadyFailed[content] { - alreadyFailed[content] = true - return false, errors.New("intentional 'error'") - } - dest = append(dest, content) - return true, nil - }, - } - rcv, err := harmonytask.New(cdb, []harmonytask.TaskInterface{fails2xPerMsg}, "test:2") - require.NoError(t, err) - time.Sleep(time.Second) - sender.GracefullyTerminate() - rcv.GracefullyTerminate() - sort.Strings(dest) - require.Equal(t, []string{"A", "B"}, dest) - type hist struct { - TaskID int - Result bool - Err string - } - var res []hist - require.NoError(t, cdb.Select(context.Background(), &res, - `SELECT task_id, result, err FROM harmony_task_history - ORDER BY result DESC, task_id`)) - - require.Equal(t, []hist{ - {1, true, ""}, - {2, true, ""}, - {1, false, "error: intentional 'error'"}, - {2, false, "error: intentional 'error'"}}, res) - }) -} - -func TestBoredom(t *testing.T) { - //t.Parallel() - withDbSetup(t, func(m *kit.TestMiner) { - cdb := m.BaseAPI.(*impl.StorageMinerAPI).HarmonyDB - harmonytask.POLL_DURATION = time.Millisecond * 100 - var taskID harmonytask.TaskID - var ran bool - boredParty := &passthru{ - dtl: harmonytask.TaskTypeDetails{ - Name: "boredTest", - Max: -1, - Cost: resources.Resources{}, - IAmBored: func(add harmonytask.AddTaskFunc) error { - add(func(tID harmonytask.TaskID, tx *harmonydb.Tx) (bool, error) { - taskID = tID - return true, nil - }) - return nil - }, - }, - canAccept: func(list []harmonytask.TaskID, e *harmonytask.TaskEngine) (*harmonytask.TaskID, error) { - require.Equal(t, harmonytask.WorkSourceIAmBored, e.WorkOrigin) - return &list[0], nil - }, - do: func(tID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) { - require.Equal(t, taskID, tID) - ran = true - return true, nil - }, - } - ht, err := harmonytask.New(cdb, []harmonytask.TaskInterface{boredParty}, "test:1") - require.NoError(t, err) - require.Eventually(t, func() bool { return ran }, time.Second, time.Millisecond*100) - ht.GracefullyTerminate() - }) -} diff --git a/itests/kit/blockminer.go b/itests/kit/blockminer.go index 40d23a6cd..758425678 100644 --- a/itests/kit/blockminer.go +++ b/itests/kit/blockminer.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/go-state-types/abi" @@ -20,6 +21,7 @@ import ( "github.com/filecoin-project/go-state-types/dline" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/api/v1api" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/miner" ) @@ -29,11 +31,13 @@ type BlockMiner struct { t *testing.T miner *TestMiner - nextNulls int64 - pause chan struct{} - unpause chan struct{} - wg sync.WaitGroup - cancel context.CancelFunc + nextNulls int64 + postWatchMiners []address.Address + postWatchMinersLk sync.Mutex + pause chan struct{} + unpause chan struct{} + wg sync.WaitGroup + cancel context.CancelFunc } func NewBlockMiner(t *testing.T, miner *TestMiner) *BlockMiner { @@ -46,19 +50,58 @@ func NewBlockMiner(t *testing.T, miner *TestMiner) *BlockMiner { } } +type minerDeadline struct { + addr address.Address + deadline dline.Info +} + +type minerDeadlines []minerDeadline + +func (mds minerDeadlines) CloseList() []abi.ChainEpoch { + var ret []abi.ChainEpoch + for _, md := range mds { + ret = append(ret, md.deadline.Last()) + } + return ret +} + +func (mds minerDeadlines) MinerStringList() []string { + var ret []string + for _, md := range mds { + ret = append(ret, md.addr.String()) + } + return ret +} + +// FilterByLast returns a new minerDeadlines with only the deadlines that have a Last() epoch +// greater than or equal to last. +func (mds minerDeadlines) FilterByLast(last abi.ChainEpoch) minerDeadlines { + var ret minerDeadlines + for _, md := range mds { + if last >= md.deadline.Last() { + ret = append(ret, md) + } + } + return ret +} + type partitionTracker struct { + minerAddr address.Address partitions []api.Partition posted bitfield.BitField } -func newPartitionTracker(ctx context.Context, dlIdx uint64, bm *BlockMiner) *partitionTracker { - dlines, err := bm.miner.FullNode.StateMinerDeadlines(ctx, bm.miner.ActorAddr, types.EmptyTSK) - require.NoError(bm.t, err) +// newPartitionTracker creates a new partitionTracker that tracks the deadline index dlIdx for the +// given minerAddr. It uses the BlockMiner bm to interact with the chain. +func newPartitionTracker(ctx context.Context, t *testing.T, client v1api.FullNode, minerAddr address.Address, dlIdx uint64) *partitionTracker { + dlines, err := client.StateMinerDeadlines(ctx, minerAddr, types.EmptyTSK) + require.NoError(t, err) dl := dlines[dlIdx] - parts, err := bm.miner.FullNode.StateMinerPartitions(ctx, bm.miner.ActorAddr, dlIdx, types.EmptyTSK) - require.NoError(bm.t, err) + parts, err := client.StateMinerPartitions(ctx, minerAddr, dlIdx, types.EmptyTSK) + require.NoError(t, err) return &partitionTracker{ + minerAddr: minerAddr, partitions: parts, posted: dl.PostSubmissions, } @@ -74,11 +117,11 @@ func (p *partitionTracker) done(t *testing.T) bool { return uint64(len(p.partitions)) == p.count(t) } -func (p *partitionTracker) recordIfPost(t *testing.T, bm *BlockMiner, msg *types.Message) (ret bool) { +func (p *partitionTracker) recordIfPost(t *testing.T, msg *types.Message) (ret bool) { defer func() { ret = p.done(t) }() - if !(msg.To == bm.miner.ActorAddr) { + if !(msg.To == p.minerAddr) { return } if msg.Method != builtin.MethodsMiner.SubmitWindowedPoSt { @@ -92,19 +135,18 @@ func (p *partitionTracker) recordIfPost(t *testing.T, bm *BlockMiner, msg *types return } -func (bm *BlockMiner) forcePoSt(ctx context.Context, ts *types.TipSet, dlinfo *dline.Info) { - - tracker := newPartitionTracker(ctx, dlinfo.Index, bm) +func (bm *BlockMiner) forcePoSt(ctx context.Context, ts *types.TipSet, minerAddr address.Address, dlinfo dline.Info) { + tracker := newPartitionTracker(ctx, bm.t, bm.miner.FullNode, minerAddr, dlinfo.Index) if !tracker.done(bm.t) { // need to wait for post bm.t.Logf("expect %d partitions proved but only see %d", len(tracker.partitions), tracker.count(bm.t)) - poolEvts, err := bm.miner.FullNode.MpoolSub(ctx) //subscribe before checking pending so we don't miss any events + poolEvts, err := bm.miner.FullNode.MpoolSub(ctx) // subscribe before checking pending so we don't miss any events require.NoError(bm.t, err) // First check pending messages we'll mine this epoch msgs, err := bm.miner.FullNode.MpoolPending(ctx, types.EmptyTSK) require.NoError(bm.t, err) for _, msg := range msgs { - if tracker.recordIfPost(bm.t, bm, &msg.Message) { + if tracker.recordIfPost(bm.t, &msg.Message) { fmt.Printf("found post in mempool pending\n") } } @@ -114,13 +156,13 @@ func (bm *BlockMiner) forcePoSt(ctx context.Context, ts *types.TipSet, dlinfo *d msgs, err := bm.miner.FullNode.ChainGetBlockMessages(ctx, bc) require.NoError(bm.t, err) for _, msg := range msgs.BlsMessages { - if tracker.recordIfPost(bm.t, bm, msg) { + if tracker.recordIfPost(bm.t, msg) { fmt.Printf("found post in message of prev tipset\n") } } for _, msg := range msgs.SecpkMessages { - if tracker.recordIfPost(bm.t, bm, &msg.Message) { + if tracker.recordIfPost(bm.t, &msg.Message) { fmt.Printf("found post in message of prev tipset\n") } } @@ -139,7 +181,7 @@ func (bm *BlockMiner) forcePoSt(ctx context.Context, ts *types.TipSet, dlinfo *d bm.t.Logf("pool event: %d", evt.Type) if evt.Type == api.MpoolAdd { bm.t.Logf("incoming message %v", evt.Message) - if tracker.recordIfPost(bm.t, bm, &evt.Message.Message) { + if tracker.recordIfPost(bm.t, &evt.Message.Message) { fmt.Printf("found post in mempool evt\n") break POOL } @@ -151,11 +193,24 @@ func (bm *BlockMiner) forcePoSt(ctx context.Context, ts *types.TipSet, dlinfo *d } } +// WatchMinerForPost adds a miner to the list of miners that the BlockMiner will watch for window +// post submissions when using MineBlocksMustPost. This is useful when we have more than just the +// BlockMiner submitting posts, particularly in the case of UnmanagedMiners which don't participate +// in block mining. +func (bm *BlockMiner) WatchMinerForPost(minerAddr address.Address) { + bm.postWatchMinersLk.Lock() + bm.postWatchMiners = append(bm.postWatchMiners, minerAddr) + bm.postWatchMinersLk.Unlock() +} + // Like MineBlocks but refuses to mine until the window post scheduler has wdpost messages in the mempool // and everything shuts down if a post fails. It also enforces that every block mined succeeds func (bm *BlockMiner) MineBlocksMustPost(ctx context.Context, blocktime time.Duration) { time.Sleep(time.Second) + // watch for our own window posts + bm.WatchMinerForPost(bm.miner.ActorAddr) + // wrap context in a cancellable context. ctx, bm.cancel = context.WithCancel(ctx) bm.wg.Add(1) @@ -182,11 +237,25 @@ func (bm *BlockMiner) MineBlocksMustPost(ctx context.Context, blocktime time.Dur ts, err := bm.miner.FullNode.ChainHead(ctx) require.NoError(bm.t, err) - dlinfo, err := bm.miner.FullNode.StateMinerProvingDeadline(ctx, bm.miner.ActorAddr, ts.Key()) - require.NoError(bm.t, err) - if ts.Height()+5+abi.ChainEpoch(nulls) >= dlinfo.Last() { // Next block brings us past the last epoch in dline, we need to wait for miner to post - bm.t.Logf("forcing post to get in before deadline closes at %d", dlinfo.Last()) - bm.forcePoSt(ctx, ts, dlinfo) + // Get current deadline information for all miners, then filter by the ones that are about to + // close so we can force a post for them. + bm.postWatchMinersLk.Lock() + var impendingDeadlines minerDeadlines + for _, minerAddr := range bm.postWatchMiners { + dlinfo, err := bm.miner.FullNode.StateMinerProvingDeadline(ctx, minerAddr, ts.Key()) + require.NoError(bm.t, err) + require.NotNil(bm.t, dlinfo, "no deadline info for miner %s", minerAddr) + impendingDeadlines = append(impendingDeadlines, minerDeadline{addr: minerAddr, deadline: *dlinfo}) + } + bm.postWatchMinersLk.Unlock() + impendingDeadlines = impendingDeadlines.FilterByLast(ts.Height() + 5 + abi.ChainEpoch(nulls)) + + if len(impendingDeadlines) > 0 { + // Next block brings us too close for at least one deadline, we need to wait for miners to post + bm.t.Logf("forcing post to get in if due before deadline closes at %v for %v", impendingDeadlines.CloseList(), impendingDeadlines.MinerStringList()) + for _, md := range impendingDeadlines { + bm.forcePoSt(ctx, ts, md.addr, md.deadline) + } } var target abi.ChainEpoch @@ -216,10 +285,13 @@ func (bm *BlockMiner) MineBlocksMustPost(ctx context.Context, blocktime time.Dur return } if !success { - // if we are mining a new null block and it brings us past deadline boundary we need to wait for miner to post - if ts.Height()+5+abi.ChainEpoch(nulls+i) >= dlinfo.Last() { - bm.t.Logf("forcing post to get in before deadline closes at %d", dlinfo.Last()) - bm.forcePoSt(ctx, ts, dlinfo) + // if we are mining a new null block and it brings us past deadline boundary we need to wait for miners to post + impendingDeadlines = impendingDeadlines.FilterByLast(ts.Height() + 5 + abi.ChainEpoch(nulls+i)) + if len(impendingDeadlines) > 0 { + bm.t.Logf("forcing post to get in if due before deadline closes at %v for %v", impendingDeadlines.CloseList(), impendingDeadlines.MinerStringList()) + for _, md := range impendingDeadlines { + bm.forcePoSt(ctx, ts, md.addr, md.deadline) + } } } } @@ -378,4 +450,7 @@ func (bm *BlockMiner) Stop() { close(bm.pause) bm.pause = nil } + bm.postWatchMinersLk.Lock() + bm.postWatchMiners = nil + bm.postWatchMinersLk.Unlock() } diff --git a/itests/kit/client.go b/itests/kit/client.go deleted file mode 100644 index 18e4259e4..000000000 --- a/itests/kit/client.go +++ /dev/null @@ -1,161 +0,0 @@ -package kit - -import ( - "context" - "fmt" - "math/rand" - "os" - "path/filepath" - "regexp" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/require" - lcli "github.com/urfave/cli/v2" - - "github.com/filecoin-project/specs-actors/v2/actors/builtin" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/types" -) - -// RunClientTest exercises some of the Client CLI commands -func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode *TestFullNode) { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - // Create mock CLI - mockCLI := NewMockCLI(ctx, t, cmds, api.NodeFull) - clientCLI := mockCLI.Client(clientNode.ListenAddr) - - // Get the Miner address - addrs, err := clientNode.StateListMiners(ctx, types.EmptyTSK) - require.NoError(t, err) - require.Len(t, addrs, 1) - - minerAddr := addrs[0] - fmt.Println("Miner:", minerAddr) - - // client query-ask - out := clientCLI.RunCmd("client", "query-ask", minerAddr.String()) - require.Regexp(t, regexp.MustCompile("Ask:"), out) - - // Create a deal (non-interactive) - // client deal --start-epoch= 1000000attofil - res, _, _, err := CreateImportFile(ctx, clientNode, 1, 0) - - require.NoError(t, err) - startEpoch := fmt.Sprintf("--start-epoch=%d", 2<<12) - dataCid := res.Root - price := "1000000attofil" - duration := fmt.Sprintf("%d", build.MinDealDuration) - out = clientCLI.RunCmd("client", "deal", startEpoch, dataCid.String(), minerAddr.String(), price, duration) - fmt.Println("client deal", out) - - // Create a deal (interactive) - // client deal - // - // (in days) - // - // "no" (verified Client) - // "yes" (confirm deal) - res, _, _, err = CreateImportFile(ctx, clientNode, 2, 0) - require.NoError(t, err) - dataCid2 := res.Root - duration = fmt.Sprintf("%d", build.MinDealDuration/builtin.EpochsInDay) - cmd := []string{"client", "deal"} - interactiveCmds := []string{ - dataCid2.String(), - duration, - minerAddr.String(), - "no", - "yes", - } - out = clientCLI.RunInteractiveCmd(cmd, interactiveCmds) - fmt.Println("client deal:\n", out) - - // Wait for provider to start sealing deal - dealStatus := "" - for { - // client list-deals - out = clientCLI.RunCmd("client", "list-deals", "--show-failed") - fmt.Println("list-deals:\n", out) - - lines := strings.Split(out, "\n") - require.GreaterOrEqual(t, len(lines), 2) - re := regexp.MustCompile(`\s+`) - parts := re.Split(lines[1], -1) - if len(parts) < 4 { - require.Fail(t, "bad list-deals output format") - } - dealStatus = parts[3] - fmt.Println(" Deal status:", dealStatus) - - st := CategorizeDealState(dealStatus) - require.NotEqual(t, TestDealStateFailed, st) - if st == TestDealStateComplete { - break - } - - time.Sleep(time.Second) - } - - // client retrieval-ask --size=1 - out = clientCLI.RunCmd("client", "retrieval-ask", "--size=1", minerAddr.String(), dataCid.String()) - require.Regexp(t, regexp.MustCompile("Ask:"), out) - fmt.Println("retrieval ask:\n", out) - - // Retrieve the first file from the Miner - // client retrieve - tmpdir, err := os.MkdirTemp(os.TempDir(), "test-cli-client") - require.NoError(t, err) - path := filepath.Join(tmpdir, "outfile.dat") - - // Wait for client retrieve to succeed. - for { - out = clientCLI.RunCmd("client", "retrieve", dataCid.String(), path) - fmt.Println("retrieve:\n", out) - if strings.Contains(out, "Success") { - break - } - } -} - -func CreateImportFile(ctx context.Context, client api.FullNode, rseed int, size int) (res *api.ImportRes, path string, data []byte, err error) { - data, path, err = createRandomFile(rseed, size) - if err != nil { - return nil, "", nil, err - } - - res, err = client.ClientImport(ctx, api.FileRef{Path: path}) - if err != nil { - return nil, "", nil, err - } - return res, path, data, nil -} - -func createRandomFile(rseed, size int) ([]byte, string, error) { - if size == 0 { - size = 1600 - } - data := make([]byte, size) - _, err := rand.New(rand.NewSource(int64(rseed))).Read(data) - if err != nil { - return nil, "", err - } - - dir, err := os.MkdirTemp(os.TempDir(), "test-make-deal-") - if err != nil { - return nil, "", err - } - - path := filepath.Join(dir, "sourcefile.dat") - err = os.WriteFile(path, data, 0644) - if err != nil { - return nil, "", err - } - - return data, path, nil -} diff --git a/itests/kit/deals.go b/itests/kit/deals.go deleted file mode 100644 index eb6b58667..000000000 --- a/itests/kit/deals.go +++ /dev/null @@ -1,483 +0,0 @@ -package kit - -import ( - "context" - "errors" - "fmt" - "os" - "strings" - "testing" - "time" - - "github.com/ipfs/boxo/files" - dag "github.com/ipfs/boxo/ipld/merkledag" - dstest "github.com/ipfs/boxo/ipld/merkledag/test" - unixfile "github.com/ipfs/boxo/ipld/unixfs/file" - "github.com/ipfs/go-cid" - ipldcbor "github.com/ipfs/go-ipld-cbor" - ipld "github.com/ipfs/go-ipld-format" - "github.com/ipld/go-car" - _ "github.com/ipld/go-ipld-prime/codec/dagcbor" - "github.com/stretchr/testify/require" - "golang.org/x/sync/errgroup" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - "github.com/filecoin-project/go-fil-markets/shared_testutil" - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-state-types/abi" - - "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/storage/pipeline" -) - -type DealHarness struct { - t *testing.T - client *TestFullNode - main *TestMiner - market *TestMiner -} - -type MakeFullDealParams struct { - Rseed int - FastRet bool - StartEpoch abi.ChainEpoch - UseCARFileForStorageDeal bool - - // SuspendUntilCryptoeconStable suspends deal-making, until cryptoecon - // parameters are stabilised. This affects projected collateral, and tests - // will fail in network version 13 and higher if deals are started too soon - // after network birth. - // - // The reason is that the formula for collateral calculation takes - // circulating supply into account: - // - // [portion of power this deal will be] * [~1% of tokens]. - // - // In the first epochs after genesis, the total circulating supply is - // changing dramatically in percentual terms. Therefore, if the deal is - // proposed too soon, by the time it gets published on chain, the quoted - // provider collateral will no longer be valid. - // - // The observation is that deals fail with: - // - // GasEstimateMessageGas error: estimating gas used: message execution - // failed: exit 16, reason: Provider collateral out of bounds. (RetCode=16) - // - // Enabling this will suspend deal-making until the network has reached a - // height of 300. - SuspendUntilCryptoeconStable bool -} - -// NewDealHarness creates a test harness that contains testing utilities for deals. -func NewDealHarness(t *testing.T, client *TestFullNode, main *TestMiner, market *TestMiner) *DealHarness { - return &DealHarness{ - t: t, - client: client, - main: main, - market: market, - } -} - -// MakeOnlineDeal makes an online deal, generating a random file with the -// supplied seed, and setting the specified fast retrieval flag and start epoch -// on the storage deal. It returns when the deal is sealed. -// -// 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) { - deal, res, path = dh.StartRandomDeal(ctx, params) - - fmt.Printf("WAIT DEAL SEALEDS START\n") - dh.WaitDealSealed(ctx, deal, false, false, nil) - fmt.Printf("WAIT DEAL SEALEDS END\n") - return deal, res, path -} - -func (dh *DealHarness) StartRandomDeal(ctx context.Context, params MakeFullDealParams) (deal *cid.Cid, res *api.ImportRes, path string) { - 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) - - if params.SuspendUntilCryptoeconStable { - dh.t.Logf("deal-making suspending until cryptecon parameters have stabilised") - ts := dh.client.WaitTillChain(ctx, HeightAtLeast(300)) - dh.t.Logf("deal-making continuing; current height is %d", ts.Height()) - } - - dp := dh.DefaultStartDealParams() - dp.Data.Root = res.Root - dp.DealStartEpoch = params.StartEpoch - dp.FastRetrieval = params.FastRet - deal = dh.StartDeal(ctx, dp) - - return deal, res, path -} - -func (dh *DealHarness) DefaultStartDealParams() api.StartDealParams { - dp := api.StartDealParams{ - Data: &storagemarket.DataRef{TransferType: storagemarket.TTGraphsync}, - EpochPrice: types.NewInt(1000000), - MinBlocksDuration: uint64(build.MinDealDuration), - } - - var err error - dp.Miner, err = dh.main.ActorAddress(context.Background()) - require.NoError(dh.t, err) - - dp.Wallet, err = dh.client.WalletDefaultAddress(context.Background()) - require.NoError(dh.t, err) - - return dp -} - -// StartDeal starts a storage deal between the client and the miner. -func (dh *DealHarness) StartDeal(ctx context.Context, dealParams api.StartDealParams) *cid.Cid { - dealProposalCid, err := dh.client.ClientStartDeal(ctx, &dealParams) - require.NoError(dh.t, err) - return dealProposalCid -} - -// WaitDealSealed waits until the deal is sealed. -func (dh *DealHarness) WaitDealSealed(ctx context.Context, deal *cid.Cid, noseal, noSealStart bool, cb func()) { -loop: - for { - di, err := dh.client.ClientGetDealInfo(ctx, *deal) - require.NoError(dh.t, err) - - switch di.State { - case storagemarket.StorageDealAwaitingPreCommit, storagemarket.StorageDealSealing: - if noseal { - return - } - if !noSealStart { - dh.StartSealingWaiting(ctx) - } - case storagemarket.StorageDealProposalRejected: - dh.t.Fatal("deal rejected") - case storagemarket.StorageDealFailing: - dh.t.Fatal("deal failed") - case storagemarket.StorageDealError: - dh.t.Fatal("deal errored", di.Message) - case storagemarket.StorageDealActive: - dh.t.Log("COMPLETE", di) - break loop - } - - mds, err := dh.market.MarketListIncompleteDeals(ctx) - require.NoError(dh.t, err) - - var minerState storagemarket.StorageDealStatus - for _, md := range mds { - if md.DealID == di.DealID { - minerState = md.State - break - } - } - - dh.t.Logf("Deal %d state: client:%s provider:%s\n", di.DealID, storagemarket.DealStates[di.State], storagemarket.DealStates[minerState]) - time.Sleep(time.Second / 2) - if cb != nil { - cb() - } - } - fmt.Printf("WAIT DEAL SEALED LOOP BROKEN\n") -} - -// WaitDealSealedQuiet waits until the deal is sealed, without logging anything. -func (dh *DealHarness) WaitDealSealedQuiet(ctx context.Context, deal *cid.Cid, noseal, noSealStart bool, cb func()) { -loop: - for { - di, err := dh.client.ClientGetDealInfo(ctx, *deal) - require.NoError(dh.t, err) - - switch di.State { - case storagemarket.StorageDealAwaitingPreCommit, storagemarket.StorageDealSealing: - if noseal { - return - } - if !noSealStart { - dh.StartSealingWaiting(ctx) - } - case storagemarket.StorageDealProposalRejected: - dh.t.Fatal("deal rejected") - case storagemarket.StorageDealFailing: - dh.t.Fatal("deal failed") - case storagemarket.StorageDealError: - dh.t.Fatal("deal errored", di.Message) - case storagemarket.StorageDealActive: - break loop - } - - _, err = dh.market.MarketListIncompleteDeals(ctx) - require.NoError(dh.t, err) - - time.Sleep(time.Second / 2) - if cb != nil { - cb() - } - } -} - -func (dh *DealHarness) ExpectDealFailure(ctx context.Context, deal *cid.Cid, errs string) error { - for { - di, err := dh.client.ClientGetDealInfo(ctx, *deal) - require.NoError(dh.t, err) - - switch di.State { - case storagemarket.StorageDealAwaitingPreCommit, storagemarket.StorageDealSealing: - return fmt.Errorf("deal is sealing, and we expected an error: %s", errs) - case storagemarket.StorageDealProposalRejected: - if strings.Contains(di.Message, errs) { - return nil - } - return fmt.Errorf("unexpected error: %s ; expected: %s", di.Message, errs) - case storagemarket.StorageDealFailing: - if strings.Contains(di.Message, errs) { - return nil - } - return fmt.Errorf("unexpected error: %s ; expected: %s", di.Message, errs) - case storagemarket.StorageDealError: - if strings.Contains(di.Message, errs) { - return nil - } - return fmt.Errorf("unexpected error: %s ; expected: %s", di.Message, errs) - case storagemarket.StorageDealActive: - return errors.New("expected to get an error, but didn't get one") - } - - mds, err := dh.market.MarketListIncompleteDeals(ctx) - require.NoError(dh.t, err) - - var minerState storagemarket.StorageDealStatus - for _, md := range mds { - if md.DealID == di.DealID { - minerState = md.State - break - } - } - - dh.t.Logf("Deal %d state: client:%s provider:%s\n", di.DealID, storagemarket.DealStates[di.State], storagemarket.DealStates[minerState]) - time.Sleep(time.Second / 2) - } -} - -// WaitDealPublished waits until the deal is published. -func (dh *DealHarness) WaitDealPublished(ctx context.Context, deal *cid.Cid) { - subCtx, cancel := context.WithCancel(ctx) - defer cancel() - - updates, err := dh.market.MarketGetDealUpdates(subCtx) - require.NoError(dh.t, err) - - for { - select { - case <-ctx.Done(): - dh.t.Fatal("context timeout") - case di := <-updates: - if deal.Equals(di.ProposalCid) { - switch di.State { - case storagemarket.StorageDealProposalRejected: - dh.t.Fatal("deal rejected") - case storagemarket.StorageDealFailing: - dh.t.Fatal("deal failed") - case storagemarket.StorageDealError: - dh.t.Fatal("deal errored", di.Message) - case storagemarket.StorageDealFinalizing, storagemarket.StorageDealAwaitingPreCommit, storagemarket.StorageDealSealing, storagemarket.StorageDealActive: - dh.t.Log("COMPLETE", di) - return - } - dh.t.Log("Deal state: ", storagemarket.DealStates[di.State]) - } - } - } -} - -func (dh *DealHarness) StartSealingWaiting(ctx context.Context) { - snums, err := dh.main.SectorsListNonGenesis(ctx) - require.NoError(dh.t, err) - for _, snum := range snums { - si, err := dh.main.SectorsStatus(ctx, snum, false) - require.NoError(dh.t, err) - - dh.t.Logf("Sector state <%d>-[%d]:, %s", snum, si.SealProof, si.State) - if si.State == api.SectorState(sealing.WaitDeals) { - require.NoError(dh.t, dh.main.SectorStartSealing(ctx, snum)) - } - - dh.main.FlushSealingBatches(ctx) - } -} - -func (dh *DealHarness) PerformRetrieval(ctx context.Context, deal *cid.Cid, root cid.Cid, carExport bool, offers ...api.QueryOffer) (path string) { - return dh.PerformRetrievalWithOrder(ctx, deal, root, carExport, func(offer api.QueryOffer, a address.Address) api.RetrievalOrder { - return offer.Order(a) - }, offers...) -} - -func (dh *DealHarness) PerformRetrievalWithOrder(ctx context.Context, deal *cid.Cid, root cid.Cid, carExport bool, makeOrder func(api.QueryOffer, address.Address) api.RetrievalOrder, offers ...api.QueryOffer) (path string) { - var offer api.QueryOffer - if len(offers) == 0 { - // perform retrieval. - info, err := dh.client.ClientGetDealInfo(ctx, *deal) - require.NoError(dh.t, err) - - offers, err := dh.client.ClientFindData(ctx, root, &info.PieceCID) - require.NoError(dh.t, err) - require.NotEmpty(dh.t, offers, "no offers") - offer = offers[0] - } else { - offer = offers[0] - } - - carFile := dh.t.TempDir() + string(os.PathSeparator) + "ret-car-" + root.String() - - caddr, err := dh.client.WalletDefaultAddress(ctx) - require.NoError(dh.t, err) - - updatesCtx, cancel := context.WithCancel(ctx) - updates, err := dh.client.ClientGetRetrievalUpdates(updatesCtx) - require.NoError(dh.t, err) - - order := makeOrder(offer, caddr) - - retrievalRes, err := dh.client.ClientRetrieve(ctx, order) - require.NoError(dh.t, err) -consumeEvents: - for { - var evt api.RetrievalInfo - select { - case <-updatesCtx.Done(): - dh.t.Fatal("Retrieval Timed Out") - case evt = <-updates: - if evt.ID != retrievalRes.DealID { - continue - } - } - switch evt.Status { - case retrievalmarket.DealStatusCompleted: - break consumeEvents - case retrievalmarket.DealStatusRejected: - dh.t.Fatalf("Retrieval Proposal Rejected: %s", evt.Message) - case - retrievalmarket.DealStatusDealNotFound, - retrievalmarket.DealStatusErrored: - dh.t.Fatalf("Retrieval Error: %s", evt.Message) - } - } - cancel() - - if order.RemoteStore != nil { - // if we're retrieving into a remote store, skip export - return "" - } - - require.NoError(dh.t, dh.client.ClientExport(ctx, - api.ExportRef{ - Root: root, - DealID: retrievalRes.DealID, - }, - api.FileRef{ - Path: carFile, - IsCAR: carExport, - })) - - ret := carFile - - return ret -} - -func (dh *DealHarness) ExtractFileFromCAR(ctx context.Context, file *os.File) string { - bserv := dstest.Bserv() - ch, err := car.LoadCar(ctx, bserv.Blockstore(), file) - require.NoError(dh.t, err) - - blk, err := bserv.GetBlock(ctx, ch.Roots[0]) - require.NoError(dh.t, err) - - reg := ipld.Registry{} - reg.Register(cid.DagProtobuf, dag.DecodeProtobufBlock) - reg.Register(cid.DagCBOR, ipldcbor.DecodeBlock) - reg.Register(cid.Raw, dag.DecodeRawBlock) - - nd, err := reg.Decode(blk) - require.NoError(dh.t, err) - - dserv := dag.NewDAGService(bserv) - - fil, err := unixfile.NewUnixfsFile(ctx, dserv, nd) - require.NoError(dh.t, err) - - tmpfile := dh.t.TempDir() + string(os.PathSeparator) + "file-in-car" + nd.Cid().String() - - err = files.WriteTo(fil, tmpfile) - require.NoError(dh.t, err) - - return tmpfile -} - -type RunConcurrentDealsOpts struct { - N int - FastRetrieval bool - CarExport bool - StartEpoch abi.ChainEpoch - UseCARFileForStorageDeal bool - IndexProvider *shared_testutil.MockIndexProvider -} - -func (dh *DealHarness) RunConcurrentDeals(opts RunConcurrentDealsOpts) { - ctx := context.Background() - errgrp, _ := errgroup.WithContext(context.Background()) - for i := 0; i < opts.N; i++ { - i := i - errgrp.Go(func() (err error) { - defer dh.t.Logf("finished concurrent deal %d/%d", i, opts.N) - defer func() { - // This is necessary because golang can't deal with test - // failures being reported from children goroutines ¯\_(ツ)_/¯ - if r := recover(); r != nil { - err = fmt.Errorf("deal failed: %s", r) - } - }() - - 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, - UseCARFileForStorageDeal: opts.UseCARFileForStorageDeal, - }) - - // Check that the storage provider announced the deal to indexers - if opts.IndexProvider != nil { - notifs := opts.IndexProvider.GetNotifs() - _, ok := notifs[string(deal.Bytes())] - require.True(dh.t, ok) - } - - dh.t.Logf("retrieving deal %d/%d", i, opts.N) - - outPath := dh.PerformRetrieval(context.Background(), deal, res.Root, opts.CarExport) - - if opts.CarExport { - f, err := os.Open(outPath) - require.NoError(dh.t, err) - actualFile := dh.ExtractFileFromCAR(ctx, f) - require.NoError(dh.t, f.Close()) - - AssertFilesEqual(dh.t, inPath, actualFile) - } else { - AssertFilesEqual(dh.t, inPath, outPath) - } - - return nil - }) - } - require.NoError(dh.t, errgrp.Wait()) -} diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index d635f98d4..d8f6e7f91 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -20,7 +20,6 @@ import ( "github.com/libp2p/go-libp2p/core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" - "github.com/urfave/cli/v2" "github.com/filecoin-project/go-address" cborutil "github.com/filecoin-project/go-cbor-util" @@ -46,16 +45,11 @@ import ( "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet/key" - "github.com/filecoin-project/lotus/cmd/curio/deps" - "github.com/filecoin-project/lotus/cmd/curio/rpc" - "github.com/filecoin-project/lotus/cmd/curio/tasks" "github.com/filecoin-project/lotus/cmd/lotus-seed/seed" "github.com/filecoin-project/lotus/cmd/lotus-worker/sealworker" "github.com/filecoin-project/lotus/gateway" "github.com/filecoin-project/lotus/genesis" "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/markets/idxprov" - "github.com/filecoin-project/lotus/markets/idxprov/idxprov_test" lotusminer "github.com/filecoin-project/lotus/miner" "github.com/filecoin-project/lotus/node" "github.com/filecoin-project/lotus/node/config" @@ -124,17 +118,17 @@ type Ensemble struct { options *ensembleOpts inactive struct { - fullnodes []*TestFullNode - providernodes []*TestProviderNode - miners []*TestMiner - workers []*TestWorker + fullnodes []*TestFullNode + miners []*TestMiner + unmanagedMiners []*TestUnmanagedMiner + workers []*TestWorker } active struct { - fullnodes []*TestFullNode - providernodes []*TestProviderNode - miners []*TestMiner - workers []*TestWorker - bms map[*TestMiner]*BlockMiner + fullnodes []*TestFullNode + miners []*TestMiner + unmanagedMiners []*TestUnmanagedMiner + workers []*TestWorker + bms map[*TestMiner]*BlockMiner } genesis struct { version network.Version @@ -227,20 +221,6 @@ func (n *Ensemble) FullNode(full *TestFullNode, opts ...NodeOpt) *Ensemble { return n } -// FullNode enrolls a new Provider node. -func (n *Ensemble) Provider(lp *TestProviderNode, opts ...NodeOpt) *Ensemble { - options := DefaultNodeOpts - for _, o := range opts { - err := o(&options) - require.NoError(n.t, err) - } - - *lp = TestProviderNode{t: n.t, options: options, Deps: &deps.Deps{}} - - n.inactive.providernodes = append(n.inactive.providernodes, lp) - return n -} - // Miner enrolls a new miner, using the provided full node for chain // interactions. func (n *Ensemble) MinerEnroll(minerNode *TestMiner, full *TestFullNode, opts ...NodeOpt) *Ensemble { @@ -261,9 +241,7 @@ func (n *Ensemble) MinerEnroll(minerNode *TestMiner, full *TestFullNode, opts .. tdir, err := os.MkdirTemp("", "preseal-memgen") require.NoError(n.t, err) - minerCnt := len(n.inactive.miners) + len(n.active.miners) - - actorAddr, err := address.NewIDAddress(genesis2.MinerStart + uint64(minerCnt)) + actorAddr, err := address.NewIDAddress(genesis2.MinerStart + n.minerCount()) require.NoError(n.t, err) if options.mainMiner != nil { @@ -335,12 +313,25 @@ func (n *Ensemble) AddInactiveMiner(m *TestMiner) { n.inactive.miners = append(n.inactive.miners, m) } +func (n *Ensemble) AddInactiveUnmanagedMiner(m *TestUnmanagedMiner) { + n.inactive.unmanagedMiners = append(n.inactive.unmanagedMiners, m) +} + func (n *Ensemble) Miner(minerNode *TestMiner, full *TestFullNode, opts ...NodeOpt) *Ensemble { n.MinerEnroll(minerNode, full, opts...) n.AddInactiveMiner(minerNode) return n } +func (n *Ensemble) UnmanagedMiner(full *TestFullNode, opts ...NodeOpt) (*TestUnmanagedMiner, *Ensemble) { + actorAddr, err := address.NewIDAddress(genesis2.MinerStart + n.minerCount()) + require.NoError(n.t, err) + + minerNode := NewTestUnmanagedMiner(n.t, full, actorAddr, opts...) + n.AddInactiveUnmanagedMiner(minerNode) + return minerNode, n +} + // Worker enrolls a new worker, using the provided full node for chain // interactions. func (n *Ensemble) Worker(minerNode *TestMiner, worker *TestWorker, opts ...NodeOpt) *Ensemble { @@ -623,12 +614,10 @@ func (n *Ensemble) Start() *Ensemble { n.t.Fatalf("invalid config from repo, got: %T", c) } cfg.Common.API.RemoteListenAddress = m.RemoteListener.Addr().String() - cfg.Subsystems.EnableMarkets = m.options.subsystems.Has(SMarkets) cfg.Subsystems.EnableMining = m.options.subsystems.Has(SMining) cfg.Subsystems.EnableSealing = m.options.subsystems.Has(SSealing) cfg.Subsystems.EnableSectorStorage = m.options.subsystems.Has(SSectorStorage) cfg.Subsystems.EnableSectorIndexDB = m.options.subsystems.Has(SHarmony) - cfg.Dealmaking.MaxStagingDealsBytes = m.options.maxStagingDealsBytes if m.options.mainMiner != nil { token, err := m.options.mainMiner.FullNode.AuthNew(ctx, api.AllPermissions) @@ -714,7 +703,7 @@ func (n *Ensemble) Start() *Ensemble { m.FullNode = &minerCopy opts := []node.Option{ - node.StorageMiner(&m.StorageMiner, cfg.Subsystems), + node.StorageMiner(&m.StorageMiner), node.Base(), node.Repo(r), node.Test(), @@ -757,13 +746,6 @@ func (n *Ensemble) Start() *Ensemble { } }), } - - if m.options.subsystems.Has(SMarkets) { - opts = append(opts, - node.Override(new(idxprov.MeshCreator), idxprov_test.NewNoopMeshCreator), - ) - } - // append any node builder options. opts = append(opts, m.options.extraNodeOpts...) @@ -836,6 +818,79 @@ func (n *Ensemble) Start() *Ensemble { // to active, so clear the slice. n.inactive.miners = n.inactive.miners[:0] + // Create all inactive manual miners. + for _, m := range n.inactive.unmanagedMiners { + proofType, err := miner.WindowPoStProofTypeFromSectorSize(m.options.sectorSize, n.genesis.version) + require.NoError(n.t, err) + + params, aerr := actors.SerializeParams(&power3.CreateMinerParams{ + Owner: m.OwnerKey.Address, + Worker: m.OwnerKey.Address, + WindowPoStProofType: proofType, + Peer: abi.PeerID(m.Libp2p.PeerID), + }) + require.NoError(n.t, aerr) + + createStorageMinerMsg := &types.Message{ + From: m.OwnerKey.Address, + To: power.Address, + Value: big.Zero(), + + Method: power.Methods.CreateMiner, + Params: params, + } + signed, err := m.FullNode.FullNode.MpoolPushMessage(ctx, createStorageMinerMsg, &api.MessageSendSpec{ + MsgUuid: uuid.New(), + }) + require.NoError(n.t, err) + + mw, err := m.FullNode.FullNode.StateWaitMsg(ctx, signed.Cid(), build.MessageConfidence, api.LookbackNoLimit, true) + require.NoError(n.t, err) + require.Equal(n.t, exitcode.Ok, mw.Receipt.ExitCode) + + var retval power3.CreateMinerReturn + err = retval.UnmarshalCBOR(bytes.NewReader(mw.Receipt.Return)) + require.NoError(n.t, err, "failed to create miner") + + m.ActorAddr = retval.IDAddress + + has, err := m.FullNode.WalletHas(ctx, m.OwnerKey.Address) + require.NoError(n.t, err) + + // Only import the owner's full key into our companion full node, if we + // don't have it still. + if !has { + _, err = m.FullNode.WalletImport(ctx, &m.OwnerKey.KeyInfo) + require.NoError(n.t, err) + } + + enc, err := actors.SerializeParams(&miner2.ChangePeerIDParams{NewID: abi.PeerID(m.Libp2p.PeerID)}) + require.NoError(n.t, err) + + msg := &types.Message{ + From: m.OwnerKey.Address, + To: m.ActorAddr, + Method: builtin.MethodsMiner.ChangePeerID, + Params: enc, + Value: types.NewInt(0), + } + + _, err2 := m.FullNode.MpoolPushMessage(ctx, msg, &api.MessageSendSpec{ + MsgUuid: uuid.New(), + }) + require.NoError(n.t, err2) + + minerCopy := *m.FullNode + minerCopy.FullNode = modules.MakeUuidWrapper(minerCopy.FullNode) + m.FullNode = &minerCopy + + n.active.unmanagedMiners = append(n.active.unmanagedMiners, m) + } + + // If we are here, we have processed all inactive manual miners and moved them + // to active, so clear the slice. + n.inactive.unmanagedMiners = n.inactive.unmanagedMiners[:0] + // --------------------- // WORKERS // --------------------- @@ -902,28 +957,6 @@ func (n *Ensemble) Start() *Ensemble { // to active, so clear the slice. n.inactive.workers = n.inactive.workers[:0] - for _, p := range n.inactive.providernodes { - - // TODO setup config with options - err := p.Deps.PopulateRemainingDeps(context.Background(), &cli.Context{}, false) - require.NoError(n.t, err) - - shutdownChan := make(chan struct{}) - taskEngine, err := tasks.StartTasks(ctx, p.Deps) - if err != nil { - return nil - } - defer taskEngine.GracefullyTerminate() - - err = rpc.ListenAndServe(ctx, p.Deps, shutdownChan) // Monitor for shutdown. - require.NoError(n.t, err) - finishCh := node.MonitorShutdown(shutdownChan) //node.ShutdownHandler{Component: "rpc server", StopFunc: rpcStopper}, - //node.ShutdownHandler{Component: "provider", StopFunc: stop}, - - <-finishCh - - n.active.providernodes = append(n.active.providernodes, p) - } // --------------------- // MISC // --------------------- @@ -958,15 +991,6 @@ func (n *Ensemble) Start() *Ensemble { // InterconnectAll connects all miners and full nodes to one another. func (n *Ensemble) InterconnectAll() *Ensemble { - // connect full nodes to miners. - for _, from := range n.active.fullnodes { - for _, to := range n.active.miners { - // []*TestMiner to []api.CommonAPI type coercion not possible - // so cannot use variadic form. - n.Connect(from, to) - } - } - // connect full nodes between each other, skipping ourselves. last := len(n.active.fullnodes) - 1 for i, from := range n.active.fullnodes { @@ -1065,6 +1089,10 @@ func (n *Ensemble) BeginMining(blocktime time.Duration, miners ...*TestMiner) [] return bms } +func (n *Ensemble) minerCount() uint64 { + return uint64(len(n.inactive.miners) + len(n.active.miners) + len(n.inactive.unmanagedMiners) + len(n.active.unmanagedMiners)) +} + func (n *Ensemble) generateGenesis() *genesis.Template { var verifRoot = gen.DefaultVerifregRootkeyActor if k := n.options.verifiedRoot.key; k != nil { diff --git a/itests/kit/ensemble_opts_nv.go b/itests/kit/ensemble_opts_nv.go index 18b531e13..03239efe9 100644 --- a/itests/kit/ensemble_opts_nv.go +++ b/itests/kit/ensemble_opts_nv.go @@ -35,12 +35,12 @@ func LatestActorsAt(upgradeHeight abi.ChainEpoch) EnsembleOpt { }) /* inline-gen start */ return UpgradeSchedule(stmgr.Upgrade{ - Network: network.Version21, + Network: network.Version22, Height: -1, }, stmgr.Upgrade{ - Network: network.Version22, + Network: network.Version23, Height: upgradeHeight, - Migration: filcns.UpgradeActorsV13, + Migration: filcns.UpgradeActorsV14, }) /* inline-gen end */ } diff --git a/itests/kit/ensemble_presets.go b/itests/kit/ensemble_presets.go index 68b85fde0..c3c17d4d9 100644 --- a/itests/kit/ensemble_presets.go +++ b/itests/kit/ensemble_presets.go @@ -2,7 +2,6 @@ package kit import ( "testing" - "time" ) // EnsembleMinimal creates and starts an Ensemble with a single full node and a single miner. @@ -37,29 +36,6 @@ func EnsembleWorker(t *testing.T, opts ...interface{}) (*TestFullNode, *TestMine return &full, &miner, &worker, ens } -func EnsembleWithMinerAndMarketNodes(t *testing.T, opts ...interface{}) (*TestFullNode, *TestMiner, *TestMiner, *Ensemble) { - eopts, nopts := siftOptions(t, opts) - - var ( - fullnode TestFullNode - main, market TestMiner - ) - - mainNodeOpts := []NodeOpt{WithSubsystems(SSealing, SSectorStorage, SMining), DisableLibp2p()} - mainNodeOpts = append(mainNodeOpts, nopts...) - - blockTime := 100 * time.Millisecond - ens := NewEnsemble(t, eopts...).FullNode(&fullnode, nopts...).Miner(&main, &fullnode, mainNodeOpts...).Start() - ens.BeginMining(blockTime) - - marketNodeOpts := []NodeOpt{OwnerAddr(fullnode.DefaultKey), MainMiner(&main), WithSubsystems(SMarkets)} - marketNodeOpts = append(marketNodeOpts, nopts...) - - ens.Miner(&market, &fullnode, marketNodeOpts...).Start().Connect(market, fullnode) - - return &fullnode, &main, &market, ens -} - // EnsembleTwoOne creates and starts an Ensemble with two full nodes and one miner. // It does not interconnect nodes nor does it begin mining. // @@ -101,21 +77,6 @@ func EnsembleOneTwo(t *testing.T, opts ...interface{}) (*TestFullNode, *TestMine return &full, &one, &two, ens } -// EnsembleProvider creates and starts an Ensemble with a single full node and a single provider. -// It does not interconnect nodes nor does it begin mining. -func EnsembleProvider(t *testing.T, opts ...interface{}) (*TestFullNode, *TestProviderNode, *Ensemble) { - opts = append(opts, WithAllSubsystems()) - - eopts, nopts := siftOptions(t, opts) - - var ( - full TestFullNode - provider TestProviderNode - ) - ens := NewEnsemble(t, eopts...).FullNode(&full, nopts...).Provider(&provider, nopts...).Start() - return &full, &provider, ens -} - func siftOptions(t *testing.T, opts []interface{}) (eopts []EnsembleOpt, nopts []NodeOpt) { for _, v := range opts { switch o := v.(type) { diff --git a/itests/kit/evm.go b/itests/kit/evm.go index 0d7af2578..865d5c2f2 100644 --- a/itests/kit/evm.go +++ b/itests/kit/evm.go @@ -44,6 +44,49 @@ func (f *TestFullNode) EVM() *EVM { return &EVM{f} } +// SignLegacyHomesteadTransaction signs a legacy Homstead Ethereum transaction in place with the supplied private key. +func (e *EVM) SignLegacyEIP155Transaction(tx *ethtypes.EthLegacy155TxArgs, privKey []byte, chainID big.Int) { + preimage, err := tx.ToRlpUnsignedMsg() + require.NoError(e.t, err) + + // sign the RLP payload + signature, err := sigs.Sign(crypto.SigTypeDelegated, privKey, preimage) + require.NoError(e.t, err) + + signature.Data = append([]byte{ethtypes.EthLegacy155TxSignaturePrefix}, signature.Data...) + + chainIdMul := big.Mul(chainID, big.NewInt(2)) + vVal := big.Add(chainIdMul, big.NewIntUnsigned(35)) + + switch signature.Data[len(signature.Data)-1] { + case 0: + vVal = big.Add(vVal, big.NewInt(0)) + case 1: + vVal = big.Add(vVal, big.NewInt(1)) + } + + signature.Data = append(signature.Data[:65], vVal.Int.Bytes()...) + + err = tx.InitialiseSignature(*signature) + require.NoError(e.t, err) +} + +// SignLegacyHomesteadTransaction signs a legacy Homstead Ethereum transaction in place with the supplied private key. +func (e *EVM) SignLegacyHomesteadTransaction(tx *ethtypes.EthLegacyHomesteadTxArgs, privKey []byte) { + preimage, err := tx.ToRlpUnsignedMsg() + require.NoError(e.t, err) + + // sign the RLP payload + signature, err := sigs.Sign(crypto.SigTypeDelegated, privKey, preimage) + require.NoError(e.t, err) + + signature.Data = append([]byte{ethtypes.EthLegacyHomesteadTxSignaturePrefix}, signature.Data...) + signature.Data[len(signature.Data)-1] += 27 + + err = tx.InitialiseSignature(*signature) + require.NoError(e.t, err) +} + func (e *EVM) DeployContractWithValue(ctx context.Context, sender address.Address, bytecode []byte, value big.Int) eam.CreateReturn { require := require.New(e.t) @@ -208,7 +251,7 @@ func (e *EVM) AssertAddressBalanceConsistent(ctx context.Context, addr address.A } // SignTransaction signs an Ethereum transaction in place with the supplied private key. -func (e *EVM) SignTransaction(tx *ethtypes.EthTxArgs, privKey []byte) { +func (e *EVM) SignTransaction(tx *ethtypes.Eth1559TxArgs, privKey []byte) { preimage, err := tx.ToRlpUnsignedMsg() require.NoError(e.t, err) @@ -216,16 +259,12 @@ func (e *EVM) SignTransaction(tx *ethtypes.EthTxArgs, privKey []byte) { signature, err := sigs.Sign(crypto.SigTypeDelegated, privKey, preimage) require.NoError(e.t, err) - r, s, v, err := ethtypes.RecoverSignature(*signature) + err = tx.InitialiseSignature(*signature) require.NoError(e.t, err) - - tx.V = big.Int(v) - tx.R = big.Int(r) - tx.S = big.Int(s) } // SubmitTransaction submits the transaction via the Eth endpoint. -func (e *EVM) SubmitTransaction(ctx context.Context, tx *ethtypes.EthTxArgs) ethtypes.EthHash { +func (e *EVM) SubmitTransaction(ctx context.Context, tx ethtypes.EthTransaction) ethtypes.EthHash { signed, err := tx.ToRlpSignedMsg() require.NoError(e.t, err) diff --git a/itests/kit/node_full.go b/itests/kit/node_full.go index c36f05a75..c71667a99 100644 --- a/itests/kit/node_full.go +++ b/itests/kit/node_full.go @@ -12,6 +12,7 @@ import ( "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" @@ -22,7 +23,6 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet/key" cliutil "github.com/filecoin-project/lotus/cli/util" - "github.com/filecoin-project/lotus/cmd/curio/deps" "github.com/filecoin-project/lotus/gateway" "github.com/filecoin-project/lotus/node" ) @@ -55,17 +55,6 @@ type TestFullNode struct { options nodeOpts } -// TestProviderNode represents a Provider node enrolled in an Ensemble. -type TestProviderNode struct { - v1api.CurioStruct - - t *testing.T - - *deps.Deps - - options nodeOpts -} - func MergeFullNodes(fullNodes []*TestFullNode) *TestFullNode { var wrappedFullNode TestFullNode var fns api.FullNodeStruct @@ -86,22 +75,6 @@ func (f TestFullNode) Shutdown(ctx context.Context) error { return f.Stop(ctx) } -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) { - path = CreateRandomFile(f.t, rseed, size) - res, err := f.ClientImport(ctx, api.FileRef{Path: path}) - require.NoError(f.t, err) - return res, path -} - // WaitTillChain waits until a specified chain condition is met. It returns // the first tipset where the condition is met. func (f *TestFullNode) WaitTillChain(ctx context.Context, pred ChainPredicate) *types.TipSet { @@ -125,6 +98,30 @@ func (f *TestFullNode) WaitTillChain(ctx context.Context, pred ChainPredicate) * return nil } +// WaitTillChain waits until a specified chain condition is met. It returns +// the first tipset where the condition is met. +func (f *TestFullNode) WaitTillChainOrError(ctx context.Context, pred ChainPredicate) (*types.TipSet, error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + heads, err := f.ChainNotify(ctx) + if err != nil { + return nil, err + } + + for chg := range heads { + for _, c := range chg { + if c.Type != "apply" { + continue + } + if ts := c.Val; pred(ts) { + return ts, nil + } + } + } + return nil, xerrors.New("chain condition not met") +} + func (f *TestFullNode) WaitForSectorActive(ctx context.Context, t *testing.T, sn abi.SectorNumber, maddr address.Address) { for { active, err := f.StateMinerActiveSectors(ctx, maddr, types.EmptyTSK) diff --git a/itests/kit/node_miner.go b/itests/kit/node_miner.go index ee2ee3eaa..2e6a2b80a 100644 --- a/itests/kit/node_miner.go +++ b/itests/kit/node_miner.go @@ -32,8 +32,7 @@ import ( type MinerSubsystem int const ( - SMarkets MinerSubsystem = 1 << iota - SMining + SMining MinerSubsystem = 1 << iota SSealing SSectorStorage diff --git a/itests/kit/node_opts.go b/itests/kit/node_opts.go index 1f4f9f6a4..89aee322a 100644 --- a/itests/kit/node_opts.go +++ b/itests/kit/node_opts.go @@ -84,7 +84,6 @@ type NodeOpt func(opts *nodeOpts) error func WithAllSubsystems() NodeOpt { return func(opts *nodeOpts) error { - opts.subsystems = opts.subsystems.Add(SMarkets) opts.subsystems = opts.subsystems.Add(SMining) opts.subsystems = opts.subsystems.Add(SSealing) opts.subsystems = opts.subsystems.Add(SSectorStorage) diff --git a/itests/kit/node_unmanaged.go b/itests/kit/node_unmanaged.go new file mode 100644 index 000000000..562146f56 --- /dev/null +++ b/itests/kit/node_unmanaged.go @@ -0,0 +1,1072 @@ +package kit + +import ( + "bytes" + "context" + "crypto/rand" + "fmt" + "io" + "os" + "path/filepath" + "testing" + + "github.com/ipfs/go-cid" + libp2pcrypto "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" + cbg "github.com/whyrusleeping/cbor-gen" + + ffi "github.com/filecoin-project/filecoin-ffi" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/builtin" + miner14 "github.com/filecoin-project/go-state-types/builtin/v14/miner" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/proof" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/wallet/key" +) + +// TestUnmanagedMiner is a miner that's not managed by the storage/infrastructure, all tasks must be manually executed, managed and scheduled by the test or test kit. +// Note: `TestUnmanagedMiner` is not thread safe and assumes linear access of it's methods +type TestUnmanagedMiner struct { + t *testing.T + options nodeOpts + + cacheDir string + unsealedSectorDir string + sealedSectorDir string + currentSectorNum abi.SectorNumber + + cacheDirPaths map[abi.SectorNumber]string + unsealedSectorPaths map[abi.SectorNumber]string + sealedSectorPaths map[abi.SectorNumber]string + sealedCids map[abi.SectorNumber]cid.Cid + unsealedCids map[abi.SectorNumber]cid.Cid + sealTickets map[abi.SectorNumber]abi.SealRandomness + + proofType map[abi.SectorNumber]abi.RegisteredSealProof + + ActorAddr address.Address + OwnerKey *key.Key + FullNode *TestFullNode + Libp2p struct { + PeerID peer.ID + PrivKey libp2pcrypto.PrivKey + } +} + +type WindowPostResp struct { + Posted bool + Error error +} + +func NewTestUnmanagedMiner(t *testing.T, full *TestFullNode, actorAddr address.Address, opts ...NodeOpt) *TestUnmanagedMiner { + require.NotNil(t, full, "full node required when instantiating miner") + + options := DefaultNodeOpts + for _, o := range opts { + err := o(&options) + require.NoError(t, err) + } + + privkey, _, err := libp2pcrypto.GenerateEd25519Key(rand.Reader) + require.NoError(t, err) + + require.NotNil(t, options.ownerKey, "owner key is required for initializing a miner") + + peerId, err := peer.IDFromPrivateKey(privkey) + require.NoError(t, err) + tmpDir := t.TempDir() + + cacheDir := filepath.Join(tmpDir, fmt.Sprintf("cache-%s", actorAddr)) + unsealedSectorDir := filepath.Join(tmpDir, fmt.Sprintf("unsealed-%s", actorAddr)) + sealedSectorDir := filepath.Join(tmpDir, fmt.Sprintf("sealed-%s", actorAddr)) + + _ = os.Mkdir(cacheDir, 0755) + _ = os.Mkdir(unsealedSectorDir, 0755) + _ = os.Mkdir(sealedSectorDir, 0755) + + tm := TestUnmanagedMiner{ + t: t, + options: options, + cacheDir: cacheDir, + unsealedSectorDir: unsealedSectorDir, + sealedSectorDir: sealedSectorDir, + + unsealedSectorPaths: make(map[abi.SectorNumber]string), + cacheDirPaths: make(map[abi.SectorNumber]string), + sealedSectorPaths: make(map[abi.SectorNumber]string), + sealedCids: make(map[abi.SectorNumber]cid.Cid), + unsealedCids: make(map[abi.SectorNumber]cid.Cid), + sealTickets: make(map[abi.SectorNumber]abi.SealRandomness), + + ActorAddr: actorAddr, + OwnerKey: options.ownerKey, + FullNode: full, + currentSectorNum: 101, + proofType: make(map[abi.SectorNumber]abi.RegisteredSealProof), + } + tm.Libp2p.PeerID = peerId + tm.Libp2p.PrivKey = privkey + + return &tm +} + +func (tm *TestUnmanagedMiner) AssertNoPower(ctx context.Context) { + p := tm.CurrentPower(ctx) + tm.t.Logf("Miner %s RBP: %v, QaP: %v", tm.ActorAddr, p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + require.True(tm.t, p.MinerPower.RawBytePower.IsZero()) +} + +func (tm *TestUnmanagedMiner) CurrentPower(ctx context.Context) *api.MinerPower { + head, err := tm.FullNode.ChainHead(ctx) + require.NoError(tm.t, err) + + p, err := tm.FullNode.StateMinerPower(ctx, tm.ActorAddr, head.Key()) + require.NoError(tm.t, err) + + return p +} + +func (tm *TestUnmanagedMiner) AssertPower(ctx context.Context, raw uint64, qa uint64) { + req := require.New(tm.t) + p := tm.CurrentPower(ctx) + tm.t.Logf("Miner %s RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), tm.ActorAddr, p.MinerPower.RawBytePower.String()) + req.Equal(raw, p.MinerPower.RawBytePower.Uint64()) + req.Equal(qa, p.MinerPower.QualityAdjPower.Uint64()) +} + +func (tm *TestUnmanagedMiner) mkAndSavePiecesToOnboard(_ context.Context, sectorNumber abi.SectorNumber, pt abi.RegisteredSealProof) []abi.PieceInfo { + paddedPieceSize := abi.PaddedPieceSize(tm.options.sectorSize) + unpaddedPieceSize := paddedPieceSize.Unpadded() + + // Generate random bytes for the piece + randomBytes := make([]byte, unpaddedPieceSize) + _, err := io.ReadFull(rand.Reader, randomBytes) + require.NoError(tm.t, err) + + // Create a temporary file for the first piece + pieceFileA := requireTempFile(tm.t, bytes.NewReader(randomBytes), uint64(unpaddedPieceSize)) + + // Generate the piece CID from the file + pieceCIDA, err := ffi.GeneratePieceCIDFromFile(pt, pieceFileA, unpaddedPieceSize) + require.NoError(tm.t, err) + + // Reset file offset to the beginning after CID generation + _, err = pieceFileA.Seek(0, io.SeekStart) + require.NoError(tm.t, err) + + unsealedSectorFile := requireTempFile(tm.t, bytes.NewReader([]byte{}), 0) + defer func() { + _ = unsealedSectorFile.Close() + }() + + // Write the piece to the staged sector file without alignment + writtenBytes, pieceCID, err := ffi.WriteWithoutAlignment(pt, pieceFileA, unpaddedPieceSize, unsealedSectorFile) + require.NoError(tm.t, err) + require.EqualValues(tm.t, unpaddedPieceSize, writtenBytes) + require.True(tm.t, pieceCID.Equals(pieceCIDA)) + + // Create a struct for the piece info + publicPieces := []abi.PieceInfo{{ + Size: paddedPieceSize, + PieceCID: pieceCIDA, + }} + + // Create a temporary file for the sealed sector + sealedSectorFile := requireTempFile(tm.t, bytes.NewReader([]byte{}), 0) + defer func() { + _ = sealedSectorFile.Close() + }() + + // Update paths for the sector + tm.sealedSectorPaths[sectorNumber] = sealedSectorFile.Name() + tm.unsealedSectorPaths[sectorNumber] = unsealedSectorFile.Name() + tm.cacheDirPaths[sectorNumber] = filepath.Join(tm.cacheDir, fmt.Sprintf("%d", sectorNumber)) + + // Ensure the cache directory exists + _ = os.Mkdir(tm.cacheDirPaths[sectorNumber], 0755) + + return publicPieces +} + +func (tm *TestUnmanagedMiner) makeAndSaveCCSector(_ context.Context, sectorNumber abi.SectorNumber) { + requirements := require.New(tm.t) + + // Create cache directory + cacheDirPath := filepath.Join(tm.cacheDir, fmt.Sprintf("%d", sectorNumber)) + requirements.NoError(os.Mkdir(cacheDirPath, 0755)) + tm.t.Logf("Miner %s: Sector %d: created cache directory at %s", tm.ActorAddr, sectorNumber, cacheDirPath) + + // Define paths for unsealed and sealed sectors + unsealedSectorPath := filepath.Join(tm.unsealedSectorDir, fmt.Sprintf("%d", sectorNumber)) + sealedSectorPath := filepath.Join(tm.sealedSectorDir, fmt.Sprintf("%d", sectorNumber)) + unsealedSize := abi.PaddedPieceSize(tm.options.sectorSize).Unpadded() + + // Write unsealed sector file + requirements.NoError(os.WriteFile(unsealedSectorPath, make([]byte, unsealedSize), 0644)) + tm.t.Logf("Miner %s: Sector %d: wrote unsealed CC sector to %s", tm.ActorAddr, sectorNumber, unsealedSectorPath) + + // Write sealed sector file + requirements.NoError(os.WriteFile(sealedSectorPath, make([]byte, tm.options.sectorSize), 0644)) + tm.t.Logf("Miner %s: Sector %d: wrote sealed CC sector to %s", tm.ActorAddr, sectorNumber, sealedSectorPath) + + // Update paths in the struct + tm.unsealedSectorPaths[sectorNumber] = unsealedSectorPath + tm.sealedSectorPaths[sectorNumber] = sealedSectorPath + tm.cacheDirPaths[sectorNumber] = cacheDirPath +} + +func (tm *TestUnmanagedMiner) OnboardSectorWithPiecesAndRealProofs(ctx context.Context, proofType abi.RegisteredSealProof) (abi.SectorNumber, chan WindowPostResp, + context.CancelFunc) { + req := require.New(tm.t) + sectorNumber := tm.currentSectorNum + tm.currentSectorNum++ + + // Step 1: Wait for the pre-commitseal randomness to be available (we can only draw seal randomness from tipsets that have already achieved finality) + preCommitSealRand := tm.waitPreCommitSealRandomness(ctx, sectorNumber) + + // Step 2: Build a sector with non 0 Pieces that we want to onboard + pieces := tm.mkAndSavePiecesToOnboard(ctx, sectorNumber, proofType) + + // Step 3: Generate a Pre-Commit for the CC sector -> this persists the proof on the `TestUnmanagedMiner` Miner State + tm.generatePreCommit(ctx, sectorNumber, preCommitSealRand, proofType, pieces) + + // Step 4 : Submit the Pre-Commit to the network + unsealedCid := tm.unsealedCids[sectorNumber] + r, err := tm.submitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ + Sectors: []miner14.SectorPreCommitInfo{{ + Expiration: 2880 * 300, + SectorNumber: sectorNumber, + SealProof: TestSpt, + SealedCID: tm.sealedCids[sectorNumber], + SealRandEpoch: preCommitSealRand, + UnsealedCid: &unsealedCid, + }}, + }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + // Step 5: Generate a ProveCommit for the CC sector + waitSeedRandomness := tm.proveCommitWaitSeed(ctx, sectorNumber) + + proveCommit := tm.generateProveCommit(ctx, sectorNumber, proofType, waitSeedRandomness, pieces) + + // Step 6: Submit the ProveCommit to the network + tm.t.Log("Submitting ProveCommitSector ...") + + var manifest []miner14.PieceActivationManifest + for _, piece := range pieces { + manifest = append(manifest, miner14.PieceActivationManifest{ + CID: piece.PieceCID, + Size: piece.Size, + }) + } + + r, err = tm.submitMessage(ctx, &miner14.ProveCommitSectors3Params{ + SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: sectorNumber, Pieces: manifest}}, + SectorProofs: [][]byte{proveCommit}, + RequireActivationSuccess: true, + }, 1, builtin.MethodsMiner.ProveCommitSectors3) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + tm.proofType[sectorNumber] = proofType + + respCh := make(chan WindowPostResp, 1) + + wdCtx, cancelF := context.WithCancel(ctx) + go tm.wdPostLoop(wdCtx, sectorNumber, respCh, false, tm.sealedCids[sectorNumber], tm.sealedSectorPaths[sectorNumber], tm.cacheDirPaths[sectorNumber]) + + return sectorNumber, respCh, cancelF +} + +func (tm *TestUnmanagedMiner) OnboardSectorWithPiecesAndMockProofs(ctx context.Context, proofType abi.RegisteredSealProof) (abi.SectorNumber, chan WindowPostResp, + context.CancelFunc) { + req := require.New(tm.t) + sectorNumber := tm.currentSectorNum + tm.currentSectorNum++ + + // Step 1: Wait for the pre-commitseal randomness to be available (we can only draw seal randomness from tipsets that have already achieved finality) + preCommitSealRand := tm.waitPreCommitSealRandomness(ctx, sectorNumber) + + // Step 2: Build a sector with non 0 Pieces that we want to onboard + pieces := []abi.PieceInfo{{ + Size: abi.PaddedPieceSize(tm.options.sectorSize), + PieceCID: cid.MustParse("baga6ea4seaqjtovkwk4myyzj56eztkh5pzsk5upksan6f5outesy62bsvl4dsha"), + }} + + // Step 3: Generate a Pre-Commit for the CC sector -> this persists the proof on the `TestUnmanagedMiner` Miner State + tm.sealedCids[sectorNumber] = cid.MustParse("bagboea4b5abcatlxechwbp7kjpjguna6r6q7ejrhe6mdp3lf34pmswn27pkkiekz") + tm.unsealedCids[sectorNumber] = cid.MustParse("baga6ea4seaqjtovkwk4myyzj56eztkh5pzsk5upksan6f5outesy62bsvl4dsha") + + // Step 4 : Submit the Pre-Commit to the network + unsealedCid := tm.unsealedCids[sectorNumber] + r, err := tm.submitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ + Sectors: []miner14.SectorPreCommitInfo{{ + Expiration: 2880 * 300, + SectorNumber: sectorNumber, + SealProof: TestSpt, + SealedCID: tm.sealedCids[sectorNumber], + SealRandEpoch: preCommitSealRand, + UnsealedCid: &unsealedCid, + }}, + }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + // Step 5: Generate a ProveCommit for the CC sector + _ = tm.proveCommitWaitSeed(ctx, sectorNumber) + sectorProof := []byte{0xde, 0xad, 0xbe, 0xef} + + // Step 6: Submit the ProveCommit to the network + tm.t.Log("Submitting ProveCommitSector ...") + + var manifest []miner14.PieceActivationManifest + for _, piece := range pieces { + manifest = append(manifest, miner14.PieceActivationManifest{ + CID: piece.PieceCID, + Size: piece.Size, + }) + } + + r, err = tm.submitMessage(ctx, &miner14.ProveCommitSectors3Params{ + SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: sectorNumber, Pieces: manifest}}, + SectorProofs: [][]byte{sectorProof}, + RequireActivationSuccess: true, + }, 1, builtin.MethodsMiner.ProveCommitSectors3) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + tm.proofType[sectorNumber] = proofType + + respCh := make(chan WindowPostResp, 1) + + wdCtx, cancelF := context.WithCancel(ctx) + go tm.wdPostLoop(wdCtx, sectorNumber, respCh, true, tm.sealedCids[sectorNumber], tm.sealedSectorPaths[sectorNumber], tm.cacheDirPaths[sectorNumber]) + + return sectorNumber, respCh, cancelF +} + +func (tm *TestUnmanagedMiner) mkStagedFileWithPieces(pt abi.RegisteredSealProof) ([]abi.PieceInfo, string) { + paddedPieceSize := abi.PaddedPieceSize(tm.options.sectorSize) + unpaddedPieceSize := paddedPieceSize.Unpadded() + + // Generate random bytes for the piece + randomBytes := make([]byte, unpaddedPieceSize) + _, err := io.ReadFull(rand.Reader, randomBytes) + require.NoError(tm.t, err) + + // Create a temporary file for the first piece + pieceFileA := requireTempFile(tm.t, bytes.NewReader(randomBytes), uint64(unpaddedPieceSize)) + + // Generate the piece CID from the file + pieceCIDA, err := ffi.GeneratePieceCIDFromFile(pt, pieceFileA, unpaddedPieceSize) + require.NoError(tm.t, err) + + // Reset file offset to the beginning after CID generation + _, err = pieceFileA.Seek(0, io.SeekStart) + require.NoError(tm.t, err) + + unsealedSectorFile := requireTempFile(tm.t, bytes.NewReader([]byte{}), 0) + defer func() { + _ = unsealedSectorFile.Close() + }() + + // Write the piece to the staged sector file without alignment + writtenBytes, pieceCID, err := ffi.WriteWithoutAlignment(pt, pieceFileA, unpaddedPieceSize, unsealedSectorFile) + require.NoError(tm.t, err) + require.EqualValues(tm.t, unpaddedPieceSize, writtenBytes) + require.True(tm.t, pieceCID.Equals(pieceCIDA)) + + // Create a struct for the piece info + publicPieces := []abi.PieceInfo{{ + Size: paddedPieceSize, + PieceCID: pieceCIDA, + }} + + return publicPieces, unsealedSectorFile.Name() +} + +func (tm *TestUnmanagedMiner) SnapDealWithRealProofs(ctx context.Context, proofType abi.RegisteredSealProof, sectorNumber abi.SectorNumber) { + // generate sector key + pieces, unsealedPath := tm.mkStagedFileWithPieces(proofType) + updateProofType := abi.SealProofInfos[proofType].UpdateProof + + s, err := os.Stat(tm.sealedSectorPaths[sectorNumber]) + require.NoError(tm.t, err) + + randomBytes := make([]byte, s.Size()) + _, err = io.ReadFull(rand.Reader, randomBytes) + require.NoError(tm.t, err) + + updatePath := requireTempFile(tm.t, bytes.NewReader(randomBytes), uint64(s.Size())) + require.NoError(tm.t, updatePath.Close()) + updateDir := filepath.Join(tm.t.TempDir(), fmt.Sprintf("update-%d", sectorNumber)) + require.NoError(tm.t, os.MkdirAll(updateDir, 0700)) + + newSealed, newUnsealed, err := ffi.SectorUpdate.EncodeInto(updateProofType, updatePath.Name(), updateDir, + tm.sealedSectorPaths[sectorNumber], tm.cacheDirPaths[sectorNumber], unsealedPath, pieces) + require.NoError(tm.t, err) + + vp, err := ffi.SectorUpdate.GenerateUpdateVanillaProofs(updateProofType, tm.sealedCids[sectorNumber], + newSealed, newUnsealed, updatePath.Name(), updateDir, tm.sealedSectorPaths[sectorNumber], + tm.cacheDirPaths[sectorNumber]) + require.NoError(tm.t, err) + + snapProof, err := ffi.SectorUpdate.GenerateUpdateProofWithVanilla(updateProofType, tm.sealedCids[sectorNumber], + newSealed, newUnsealed, vp) + require.NoError(tm.t, err) + tm.waitForMutableDeadline(ctx, sectorNumber) + + // submit proof + var manifest []miner14.PieceActivationManifest + for _, piece := range pieces { + manifest = append(manifest, miner14.PieceActivationManifest{ + CID: piece.PieceCID, + Size: piece.Size, + }) + } + + head, err := tm.FullNode.ChainHead(ctx) + require.NoError(tm.t, err) + + sl, err := tm.FullNode.StateSectorPartition(ctx, tm.ActorAddr, sectorNumber, head.Key()) + require.NoError(tm.t, err) + + params := &miner14.ProveReplicaUpdates3Params{ + SectorUpdates: []miner14.SectorUpdateManifest{ + { + Sector: sectorNumber, + Deadline: sl.Deadline, + Partition: sl.Partition, + NewSealedCID: newSealed, + Pieces: manifest, + }, + }, + SectorProofs: [][]byte{snapProof}, + UpdateProofsType: updateProofType, + RequireActivationSuccess: true, + RequireNotificationSuccess: false, + } + r, err := tm.submitMessage(ctx, params, 1, builtin.MethodsMiner.ProveReplicaUpdates3) + require.NoError(tm.t, err) + require.True(tm.t, r.Receipt.ExitCode.IsSuccess()) +} + +func (tm *TestUnmanagedMiner) waitForMutableDeadline(ctx context.Context, sectorNum abi.SectorNumber) { + ts, err := tm.FullNode.ChainHead(ctx) + require.NoError(tm.t, err) + + sl, err := tm.FullNode.StateSectorPartition(ctx, tm.ActorAddr, sectorNum, ts.Key()) + require.NoError(tm.t, err) + + dlinfo, err := tm.FullNode.StateMinerProvingDeadline(ctx, tm.ActorAddr, ts.Key()) + require.NoError(tm.t, err) + + sectorDeadlineOpen := sl.Deadline == dlinfo.Index + sectorDeadlineNext := (dlinfo.Index+1)%dlinfo.WPoStPeriodDeadlines == sl.Deadline + immutable := sectorDeadlineOpen || sectorDeadlineNext + + // Sleep for immutable epochs + if immutable { + dlineEpochsRemaining := dlinfo.NextOpen() - ts.Height() + var targetEpoch abi.ChainEpoch + if sectorDeadlineOpen { + // sleep for remainder of deadline + targetEpoch = ts.Height() + dlineEpochsRemaining + } else { + // sleep for remainder of deadline and next one + targetEpoch = ts.Height() + dlineEpochsRemaining + dlinfo.WPoStChallengeWindow + } + _, err := tm.FullNode.WaitTillChainOrError(ctx, HeightAtLeast(targetEpoch+5)) + require.NoError(tm.t, err) + } +} + +func (tm *TestUnmanagedMiner) OnboardCCSectorWithMockProofs(ctx context.Context, proofType abi.RegisteredSealProof) (abi.SectorNumber, chan WindowPostResp, + context.CancelFunc) { + req := require.New(tm.t) + sectorNumber := tm.currentSectorNum + tm.currentSectorNum++ + + // Step 1: Wait for the pre-commitseal randomness to be available (we can only draw seal randomness from tipsets that have already achieved finality) + preCommitSealRand := tm.waitPreCommitSealRandomness(ctx, sectorNumber) + + tm.sealedCids[sectorNumber] = cid.MustParse("bagboea4b5abcatlxechwbp7kjpjguna6r6q7ejrhe6mdp3lf34pmswn27pkkiekz") + + // Step 4 : Submit the Pre-Commit to the network + r, err := tm.submitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ + Sectors: []miner14.SectorPreCommitInfo{{ + Expiration: 2880 * 300, + SectorNumber: sectorNumber, + SealProof: TestSpt, + SealedCID: tm.sealedCids[sectorNumber], + SealRandEpoch: preCommitSealRand, + }}, + }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + // Step 5: Generate a ProveCommit for the CC sector + _ = tm.proveCommitWaitSeed(ctx, sectorNumber) + sectorProof := []byte{0xde, 0xad, 0xbe, 0xef} + + // Step 6: Submit the ProveCommit to the network + tm.t.Log("Submitting ProveCommitSector ...") + + r, err = tm.submitMessage(ctx, &miner14.ProveCommitSectors3Params{ + SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: sectorNumber}}, + SectorProofs: [][]byte{sectorProof}, + RequireActivationSuccess: true, + }, 0, builtin.MethodsMiner.ProveCommitSectors3) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + tm.proofType[sectorNumber] = proofType + + respCh := make(chan WindowPostResp, 1) + + wdCtx, cancelF := context.WithCancel(ctx) + go tm.wdPostLoop(wdCtx, sectorNumber, respCh, true, tm.sealedCids[sectorNumber], tm.sealedSectorPaths[sectorNumber], tm.cacheDirPaths[sectorNumber]) + + return sectorNumber, respCh, cancelF +} + +func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, proofType abi.RegisteredSealProof) (abi.SectorNumber, chan WindowPostResp, + context.CancelFunc) { + req := require.New(tm.t) + sectorNumber := tm.currentSectorNum + tm.currentSectorNum++ + + // --------------------Create pre-commit for the CC sector -> we'll just pre-commit `sector size` worth of 0s for this CC sector + + // Step 1: Wait for the pre-commitseal randomness to be available (we can only draw seal randomness from tipsets that have already achieved finality) + preCommitSealRand := tm.waitPreCommitSealRandomness(ctx, sectorNumber) + + // Step 2: Write empty bytes that we want to seal i.e. create our CC sector + tm.makeAndSaveCCSector(ctx, sectorNumber) + + // Step 3: Generate a Pre-Commit for the CC sector -> this persists the proof on the `TestUnmanagedMiner` Miner State + tm.generatePreCommit(ctx, sectorNumber, preCommitSealRand, proofType, []abi.PieceInfo{}) + + // Step 4 : Submit the Pre-Commit to the network + r, err := tm.submitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ + Sectors: []miner14.SectorPreCommitInfo{{ + Expiration: 2880 * 300, + SectorNumber: sectorNumber, + SealProof: TestSpt, + SealedCID: tm.sealedCids[sectorNumber], + SealRandEpoch: preCommitSealRand, + }}, + }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + // Step 5: Generate a ProveCommit for the CC sector + waitSeedRandomness := tm.proveCommitWaitSeed(ctx, sectorNumber) + + proveCommit := tm.generateProveCommit(ctx, sectorNumber, proofType, waitSeedRandomness, []abi.PieceInfo{}) + + // Step 6: Submit the ProveCommit to the network + tm.t.Log("Submitting ProveCommitSector ...") + + r, err = tm.submitMessage(ctx, &miner14.ProveCommitSectors3Params{ + SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: sectorNumber}}, + SectorProofs: [][]byte{proveCommit}, + RequireActivationSuccess: true, + }, 0, builtin.MethodsMiner.ProveCommitSectors3) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + tm.proofType[sectorNumber] = proofType + + respCh := make(chan WindowPostResp, 1) + + wdCtx, cancelF := context.WithCancel(ctx) + go tm.wdPostLoop(wdCtx, sectorNumber, respCh, false, tm.sealedCids[sectorNumber], tm.sealedSectorPaths[sectorNumber], tm.cacheDirPaths[sectorNumber]) + + return sectorNumber, respCh, cancelF +} + +func (tm *TestUnmanagedMiner) wdPostLoop(ctx context.Context, sectorNumber abi.SectorNumber, respCh chan WindowPostResp, withMockProofs bool, sealedCid cid.Cid, sealedPath, cacheDir string) { + go func() { + var firstPost bool + + writeRespF := func(respErr error) { + var send WindowPostResp + if respErr == nil { + if firstPost { + return // already reported on our first post, no error to report, don't send anything + } + send.Posted = true + firstPost = true + } else { + if ctx.Err() == nil { + tm.t.Logf("Sector %d: WindowPoSt submission failed: %s", sectorNumber, respErr) + } + send.Error = respErr + } + select { + case respCh <- send: + case <-ctx.Done(): + default: + } + } + + var postCount int + for ctx.Err() == nil { + currentEpoch, nextPost, err := tm.calculateNextPostEpoch(ctx, sectorNumber) + tm.t.Logf("Activating sector %d, next post %d, current epoch %d", sectorNumber, nextPost, currentEpoch) + if err != nil { + writeRespF(err) + return + } + + if nextPost > currentEpoch { + if _, err := tm.FullNode.WaitTillChainOrError(ctx, HeightAtLeast(nextPost)); err != nil { + writeRespF(err) + return + } + } + + err = tm.submitWindowPost(ctx, sectorNumber, withMockProofs, sealedCid, sealedPath, cacheDir) + writeRespF(err) // send an error, or first post, or nothing if no error and this isn't the first post + postCount++ + tm.t.Logf("Sector %d: WindowPoSt #%d submitted", sectorNumber, postCount) + } + }() +} + +func (tm *TestUnmanagedMiner) SubmitPostDispute(ctx context.Context, sectorNumber abi.SectorNumber) error { + tm.t.Logf("Miner %s: Starting dispute submission for sector %d", tm.ActorAddr, sectorNumber) + + head, err := tm.FullNode.ChainHead(ctx) + if err != nil { + return fmt.Errorf("MinerB(%s): failed to get chain head: %w", tm.ActorAddr, err) + } + + sp, err := tm.FullNode.StateSectorPartition(ctx, tm.ActorAddr, sectorNumber, head.Key()) + if err != nil { + return fmt.Errorf("MinerB(%s): failed to get sector partition for sector %d: %w", tm.ActorAddr, sectorNumber, err) + } + + di, err := tm.FullNode.StateMinerProvingDeadline(ctx, tm.ActorAddr, head.Key()) + if err != nil { + return fmt.Errorf("MinerB(%s): failed to get proving deadline for sector %d: %w", tm.ActorAddr, sectorNumber, err) + } + + disputeEpoch := di.Close + 5 + tm.t.Logf("Miner %s: Sector %d - Waiting %d epochs until epoch %d to submit dispute", tm.ActorAddr, sectorNumber, disputeEpoch-head.Height(), disputeEpoch) + + tm.FullNode.WaitTillChain(ctx, HeightAtLeast(disputeEpoch)) + + tm.t.Logf("Miner %s: Sector %d - Disputing WindowedPoSt to confirm validity at epoch %d", tm.ActorAddr, sectorNumber, disputeEpoch) + + _, err = tm.submitMessage(ctx, &miner14.DisputeWindowedPoStParams{ + Deadline: sp.Deadline, + PoStIndex: 0, + }, 1, builtin.MethodsMiner.DisputeWindowedPoSt) + return err +} + +func (tm *TestUnmanagedMiner) submitWindowPost(ctx context.Context, sectorNumber abi.SectorNumber, withMockProofs bool, sealedCid cid.Cid, sealedPath, cacheDir string) error { + tm.t.Logf("Miner(%s): WindowPoST(%d): Running WindowPoSt ...\n", tm.ActorAddr, sectorNumber) + + head, err := tm.FullNode.ChainHead(ctx) + if err != nil { + return fmt.Errorf("Miner(%s): failed to get chain head: %w", tm.ActorAddr, err) + } + + sp, err := tm.FullNode.StateSectorPartition(ctx, tm.ActorAddr, sectorNumber, head.Key()) + if err != nil { + return fmt.Errorf("Miner(%s): failed to get sector partition for sector %d: %w", tm.ActorAddr, sectorNumber, err) + } + + di, err := tm.FullNode.StateMinerProvingDeadline(ctx, tm.ActorAddr, head.Key()) + if err != nil { + return fmt.Errorf("Miner(%s): failed to get proving deadline for sector %d: %w", tm.ActorAddr, sectorNumber, err) + } + tm.t.Logf("Miner(%s): WindowPoST(%d): SectorPartition: %+v, ProvingDeadline: %+v\n", tm.ActorAddr, sectorNumber, sp, di) + if di.Index != sp.Deadline { + return fmt.Errorf("Miner(%s): sector %d is not in the deadline %d, but %d", tm.ActorAddr, sectorNumber, sp.Deadline, di.Index) + } + + var proofBytes []byte + if withMockProofs { + proofBytes = []byte{0xde, 0xad, 0xbe, 0xef} + } else { + proofBytes, err = tm.generateWindowPost(ctx, sectorNumber, sealedCid, sealedPath, cacheDir) + if err != nil { + return fmt.Errorf("Miner(%s): failed to generate window post for sector %d: %w", tm.ActorAddr, sectorNumber, err) + } + } + + tm.t.Logf("Miner(%s): WindowedPoSt(%d) Submitting ...\n", tm.ActorAddr, sectorNumber) + + chainRandomnessEpoch := di.Challenge + chainRandomness, err := tm.FullNode.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_PoStChainCommit, chainRandomnessEpoch, + nil, head.Key()) + if err != nil { + return fmt.Errorf("Miner(%s): failed to get chain randomness for sector %d: %w", tm.ActorAddr, sectorNumber, err) + } + + minerInfo, err := tm.FullNode.StateMinerInfo(ctx, tm.ActorAddr, head.Key()) + if err != nil { + return fmt.Errorf("Miner(%s): failed to get miner info for sector %d: %w", tm.ActorAddr, sectorNumber, err) + } + + r, err := tm.submitMessage(ctx, &miner14.SubmitWindowedPoStParams{ + ChainCommitEpoch: chainRandomnessEpoch, + ChainCommitRand: chainRandomness, + Deadline: sp.Deadline, + Partitions: []miner14.PoStPartition{{Index: sp.Partition}}, + Proofs: []proof.PoStProof{{PoStProof: minerInfo.WindowPoStProofType, ProofBytes: proofBytes}}, + }, 0, builtin.MethodsMiner.SubmitWindowedPoSt) + if err != nil { + return fmt.Errorf("Miner(%s): failed to submit window post for sector %d: %w", tm.ActorAddr, sectorNumber, err) + } + + if !r.Receipt.ExitCode.IsSuccess() { + return fmt.Errorf("Miner(%s): submitting PoSt for sector %d failed: %s", tm.ActorAddr, sectorNumber, r.Receipt.ExitCode) + } + + tm.t.Logf("Miner(%s): WindowedPoSt(%d) Submitted ...\n", tm.ActorAddr, sectorNumber) + + return nil +} + +func (tm *TestUnmanagedMiner) generateWindowPost( + ctx context.Context, + sectorNumber abi.SectorNumber, + sealedCid cid.Cid, + sealedPath string, + cacheDir string, +) ([]byte, error) { + head, err := tm.FullNode.ChainHead(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get chain head: %w", err) + } + + minerInfo, err := tm.FullNode.StateMinerInfo(ctx, tm.ActorAddr, head.Key()) + if err != nil { + return nil, fmt.Errorf("failed to get miner info: %w", err) + } + + di, err := tm.FullNode.StateMinerProvingDeadline(ctx, tm.ActorAddr, types.EmptyTSK) + if err != nil { + return nil, fmt.Errorf("failed to get proving deadline: %w", err) + } + + minerAddrBytes := new(bytes.Buffer) + if err := tm.ActorAddr.MarshalCBOR(minerAddrBytes); err != nil { + return nil, fmt.Errorf("failed to marshal miner address: %w", err) + } + + rand, err := tm.FullNode.StateGetRandomnessFromBeacon(ctx, crypto.DomainSeparationTag_WindowedPoStChallengeSeed, di.Challenge, minerAddrBytes.Bytes(), head.Key()) + if err != nil { + return nil, fmt.Errorf("failed to get randomness: %w", err) + } + postRand := abi.PoStRandomness(rand) + postRand[31] &= 0x3f // make fr32 compatible + + privateSectorInfo := ffi.PrivateSectorInfo{ + SectorInfo: proof.SectorInfo{ + SealProof: tm.proofType[sectorNumber], + SectorNumber: sectorNumber, + SealedCID: sealedCid, + }, + CacheDirPath: cacheDir, + PoStProofType: minerInfo.WindowPoStProofType, + SealedSectorPath: sealedPath, + } + + actorIdNum, err := address.IDFromAddress(tm.ActorAddr) + if err != nil { + return nil, fmt.Errorf("failed to get actor ID: %w", err) + } + actorId := abi.ActorID(actorIdNum) + + windowProofs, faultySectors, err := ffi.GenerateWindowPoSt(actorId, ffi.NewSortedPrivateSectorInfo(privateSectorInfo), postRand) + if err != nil { + return nil, fmt.Errorf("failed to generate window post: %w", err) + } + if len(faultySectors) > 0 { + return nil, fmt.Errorf("post failed for sectors: %v", faultySectors) + } + if len(windowProofs) != 1 { + return nil, fmt.Errorf("expected 1 proof, got %d", len(windowProofs)) + } + if windowProofs[0].PoStProof != minerInfo.WindowPoStProofType { + return nil, fmt.Errorf("expected proof type %d, got %d", minerInfo.WindowPoStProofType, windowProofs[0].PoStProof) + } + proofBytes := windowProofs[0].ProofBytes + + info := proof.WindowPoStVerifyInfo{ + Randomness: postRand, + Proofs: []proof.PoStProof{{PoStProof: minerInfo.WindowPoStProofType, ProofBytes: proofBytes}}, + ChallengedSectors: []proof.SectorInfo{{SealProof: tm.proofType[sectorNumber], SectorNumber: sectorNumber, SealedCID: sealedCid}}, + Prover: actorId, + } + + verified, err := ffi.VerifyWindowPoSt(info) + if err != nil { + return nil, fmt.Errorf("failed to verify window post: %w", err) + } + if !verified { + return nil, fmt.Errorf("window post verification failed") + } + + return proofBytes, nil +} +func (tm *TestUnmanagedMiner) waitPreCommitSealRandomness(ctx context.Context, sectorNumber abi.SectorNumber) abi.ChainEpoch { + // We want to draw seal randomness from a tipset that has already achieved finality as PreCommits are expensive to re-generate. + // Check if we already have an epoch that is already final and wait for such an epoch if we don't have one. + head, err := tm.FullNode.ChainHead(ctx) + require.NoError(tm.t, err) + + var sealRandEpoch abi.ChainEpoch + if head.Height() > policy.SealRandomnessLookback { + sealRandEpoch = head.Height() - policy.SealRandomnessLookback + } else { + sealRandEpoch = policy.SealRandomnessLookback + tm.t.Logf("Miner %s waiting for at least epoch %d for seal randomness for sector %d (current epoch %d)...", tm.ActorAddr, sealRandEpoch+5, + sectorNumber, head.Height()) + tm.FullNode.WaitTillChain(ctx, HeightAtLeast(sealRandEpoch+5)) + } + + tm.t.Logf("Miner %s using seal randomness from epoch %d for head %d for sector %d", tm.ActorAddr, sealRandEpoch, head.Height(), sectorNumber) + + return sealRandEpoch +} + +// calculateNextPostEpoch calculates the first epoch of the deadline proving window +// that is desired for the given sector for the specified miner. +// This function returns the current epoch and the calculated proving epoch. +func (tm *TestUnmanagedMiner) calculateNextPostEpoch( + ctx context.Context, + sectorNumber abi.SectorNumber, +) (abi.ChainEpoch, abi.ChainEpoch, error) { + // Retrieve the current blockchain head + head, err := tm.FullNode.ChainHead(ctx) + if err != nil { + return 0, 0, fmt.Errorf("failed to get chain head: %w", err) + } + + // Fetch the sector partition for the given sector number + sp, err := tm.FullNode.StateSectorPartition(ctx, tm.ActorAddr, sectorNumber, head.Key()) + if err != nil { + return 0, 0, fmt.Errorf("failed to get sector partition: %w", err) + } + + tm.t.Logf("Miner %s: WindowPoST(%d): SectorPartition: %+v", tm.ActorAddr, sectorNumber, sp) + + // Obtain the proving deadline information for the miner + di, err := tm.FullNode.StateMinerProvingDeadline(ctx, tm.ActorAddr, head.Key()) + if err != nil { + return 0, 0, fmt.Errorf("failed to get proving deadline: %w", err) + } + + tm.t.Logf("Miner %s: WindowPoST(%d): ProvingDeadline: %+v", tm.ActorAddr, sectorNumber, di) + + // Calculate the start of the period, adjusting if the current deadline has passed + periodStart := di.PeriodStart + if di.PeriodStart < di.CurrentEpoch && sp.Deadline <= di.Index { + // If the deadline has passed in the current proving period, calculate for the next period + periodStart += di.WPoStProvingPeriod + } + + // Calculate the exact epoch when proving should occur + provingEpoch := periodStart + (di.WPoStProvingPeriod/abi.ChainEpoch(di.WPoStPeriodDeadlines))*abi.ChainEpoch(sp.Deadline) + + tm.t.Logf("Miner %s: WindowPoST(%d): next ProvingEpoch: %d", tm.ActorAddr, sectorNumber, provingEpoch) + + return di.CurrentEpoch, provingEpoch, nil +} + +func (tm *TestUnmanagedMiner) generatePreCommit( + ctx context.Context, + sectorNumber abi.SectorNumber, + sealRandEpoch abi.ChainEpoch, + proofType abi.RegisteredSealProof, + pieceInfo []abi.PieceInfo, +) { + req := require.New(tm.t) + tm.t.Logf("Miner %s: Generating proof type %d PreCommit for sector %d...", tm.ActorAddr, proofType, sectorNumber) + + head, err := tm.FullNode.ChainHead(ctx) + req.NoError(err, "Miner %s: Failed to get chain head for sector %d", tm.ActorAddr, sectorNumber) + + minerAddrBytes := new(bytes.Buffer) + req.NoError(tm.ActorAddr.MarshalCBOR(minerAddrBytes), "Miner %s: Failed to marshal address for sector %d", tm.ActorAddr, sectorNumber) + + rand, err := tm.FullNode.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_SealRandomness, sealRandEpoch, minerAddrBytes.Bytes(), head.Key()) + req.NoError(err, "Miner %s: Failed to get randomness for sector %d", tm.ActorAddr, sectorNumber) + sealTickets := abi.SealRandomness(rand) + + tm.t.Logf("Miner %s: Running proof type %d SealPreCommitPhase1 for sector %d...", tm.ActorAddr, proofType, sectorNumber) + + actorIdNum, err := address.IDFromAddress(tm.ActorAddr) + req.NoError(err, "Miner %s: Failed to get actor ID for sector %d", tm.ActorAddr, sectorNumber) + actorId := abi.ActorID(actorIdNum) + + pc1, err := ffi.SealPreCommitPhase1( + proofType, + tm.cacheDirPaths[sectorNumber], + tm.unsealedSectorPaths[sectorNumber], + tm.sealedSectorPaths[sectorNumber], + sectorNumber, + actorId, + sealTickets, + pieceInfo, + ) + req.NoError(err, "Miner %s: SealPreCommitPhase1 failed for sector %d", tm.ActorAddr, sectorNumber) + req.NotNil(pc1, "Miner %s: SealPreCommitPhase1 returned nil for sector %d", tm.ActorAddr, sectorNumber) + + tm.t.Logf("Miner %s: Running proof type %d SealPreCommitPhase2 for sector %d...", tm.ActorAddr, proofType, sectorNumber) + + sealedCid, unsealedCid, err := ffi.SealPreCommitPhase2( + pc1, + tm.cacheDirPaths[sectorNumber], + tm.sealedSectorPaths[sectorNumber], + ) + req.NoError(err, "Miner %s: SealPreCommitPhase2 failed for sector %d", tm.ActorAddr, sectorNumber) + + tm.t.Logf("Miner %s: Unsealed CID for sector %d: %s", tm.ActorAddr, sectorNumber, unsealedCid) + tm.t.Logf("Miner %s: Sealed CID for sector %d: %s", tm.ActorAddr, sectorNumber, sealedCid) + + tm.sealTickets[sectorNumber] = sealTickets + tm.sealedCids[sectorNumber] = sealedCid + tm.unsealedCids[sectorNumber] = unsealedCid +} + +func (tm *TestUnmanagedMiner) proveCommitWaitSeed(ctx context.Context, sectorNumber abi.SectorNumber) abi.InteractiveSealRandomness { + req := require.New(tm.t) + head, err := tm.FullNode.ChainHead(ctx) + req.NoError(err) + + tm.t.Logf("Miner %s: Fetching pre-commit info for sector %d...", tm.ActorAddr, sectorNumber) + preCommitInfo, err := tm.FullNode.StateSectorPreCommitInfo(ctx, tm.ActorAddr, sectorNumber, head.Key()) + req.NoError(err) + seedRandomnessHeight := preCommitInfo.PreCommitEpoch + policy.GetPreCommitChallengeDelay() + + tm.t.Logf("Miner %s: Waiting %d epochs for seed randomness at epoch %d (current epoch %d) for sector %d...", tm.ActorAddr, seedRandomnessHeight-head.Height(), seedRandomnessHeight, head.Height(), sectorNumber) + tm.FullNode.WaitTillChain(ctx, HeightAtLeast(seedRandomnessHeight+5)) + + minerAddrBytes := new(bytes.Buffer) + req.NoError(tm.ActorAddr.MarshalCBOR(minerAddrBytes)) + + head, err = tm.FullNode.ChainHead(ctx) + req.NoError(err) + + tm.t.Logf("Miner %s: Fetching seed randomness for sector %d...", tm.ActorAddr, sectorNumber) + rand, err := tm.FullNode.StateGetRandomnessFromBeacon(ctx, crypto.DomainSeparationTag_InteractiveSealChallengeSeed, seedRandomnessHeight, minerAddrBytes.Bytes(), head.Key()) + req.NoError(err) + seedRandomness := abi.InteractiveSealRandomness(rand) + + tm.t.Logf("Miner %s: Obtained seed randomness for sector %d: %x", tm.ActorAddr, sectorNumber, seedRandomness) + return seedRandomness +} + +func (tm *TestUnmanagedMiner) generateProveCommit( + ctx context.Context, + sectorNumber abi.SectorNumber, + proofType abi.RegisteredSealProof, + seedRandomness abi.InteractiveSealRandomness, + pieces []abi.PieceInfo, +) []byte { + tm.t.Logf("Miner %s: Generating proof type %d Sector Proof for sector %d...", tm.ActorAddr, proofType, sectorNumber) + req := require.New(tm.t) + + actorIdNum, err := address.IDFromAddress(tm.ActorAddr) + req.NoError(err) + actorId := abi.ActorID(actorIdNum) + + tm.t.Logf("Miner %s: Running proof type %d SealCommitPhase1 for sector %d...", tm.ActorAddr, proofType, sectorNumber) + + scp1, err := ffi.SealCommitPhase1( + proofType, + tm.sealedCids[sectorNumber], + tm.unsealedCids[sectorNumber], + tm.cacheDirPaths[sectorNumber], + tm.sealedSectorPaths[sectorNumber], + sectorNumber, + actorId, + tm.sealTickets[sectorNumber], + seedRandomness, + pieces, + ) + req.NoError(err) + + tm.t.Logf("Miner %s: Running proof type %d SealCommitPhase2 for sector %d...", tm.ActorAddr, proofType, sectorNumber) + + sectorProof, err := ffi.SealCommitPhase2(scp1, sectorNumber, actorId) + req.NoError(err) + + tm.t.Logf("Miner %s: Got proof type %d sector proof of length %d for sector %d", tm.ActorAddr, proofType, len(sectorProof), sectorNumber) + + return sectorProof +} + +func (tm *TestUnmanagedMiner) submitMessage( + ctx context.Context, + params cbg.CBORMarshaler, + value uint64, + method abi.MethodNum, +) (*api.MsgLookup, error) { + enc, aerr := actors.SerializeParams(params) + if aerr != nil { + return nil, aerr + } + + tm.t.Logf("Submitting message for miner %s with method number %d", tm.ActorAddr, method) + + m, err := tm.FullNode.MpoolPushMessage(ctx, &types.Message{ + To: tm.ActorAddr, + From: tm.OwnerKey.Address, + Value: types.FromFil(value), + Method: method, + Params: enc, + }, nil) + if err != nil { + return nil, err + } + + tm.t.Logf("Pushed message with CID: %s for miner %s", m.Cid(), tm.ActorAddr) + + msg, err := tm.FullNode.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) + if err != nil { + return nil, err + } + + tm.t.Logf("Message with CID: %s has been confirmed on-chain for miner %s", m.Cid(), tm.ActorAddr) + + return msg, nil +} + +func requireTempFile(t *testing.T, fileContentsReader io.Reader, size uint64) *os.File { + // Create a temporary file + tempFile, err := os.CreateTemp(t.TempDir(), "") + require.NoError(t, err) + + // Copy contents from the reader to the temporary file + bytesCopied, err := io.Copy(tempFile, fileContentsReader) + require.NoError(t, err) + + // Ensure the expected size matches the copied size + require.EqualValues(t, size, bytesCopied) + + // Synchronize the file's content to disk + require.NoError(t, tempFile.Sync()) + + // Reset the file pointer to the beginning of the file + _, err = tempFile.Seek(0, io.SeekStart) + require.NoError(t, err) + + return tempFile +} diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go new file mode 100644 index 000000000..f10c8b7c1 --- /dev/null +++ b/itests/manual_onboarding_test.go @@ -0,0 +1,174 @@ +package itests + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/itests/kit" +) + +const defaultSectorSize = abi.SectorSize(2 << 10) // 2KiB + +// Manually onboard CC sectors, bypassing lotus-miner onboarding pathways +func TestManualSectorOnboarding(t *testing.T) { + req := require.New(t) + + for _, withMockProofs := range []bool{true, false} { + testName := "WithRealProofs" + if withMockProofs { + testName = "WithMockProofs" + } + t.Run(testName, func(t *testing.T) { + if testName == "WithRealProofs" { + kit.Expensive(t) + } + kit.QuietMiningLogs() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var ( + // need to pick a balance value so that the test is not racy on CI by running through it's WindowPostDeadlines too fast + blocktime = 2 * time.Millisecond + client kit.TestFullNode + minerA kit.TestMiner // A is a standard genesis miner + ) + + // Setup and begin mining with a single miner (A) + // Miner A will only be a genesis Miner with power allocated in the genesis block and will not onboard any sectors from here on + kitOpts := []kit.EnsembleOpt{} + if withMockProofs { + kitOpts = append(kitOpts, kit.MockProofs()) + } + ens := kit.NewEnsemble(t, kitOpts...). + FullNode(&client, kit.SectorSize(defaultSectorSize)). + // preseal more than the default number of sectors to ensure that the genesis miner has power + // because our unmanaged miners won't produce blocks so we may get null rounds + Miner(&minerA, &client, kit.PresealSectors(5), kit.SectorSize(defaultSectorSize), kit.WithAllSubsystems()). + Start(). + InterconnectAll() + blockMiners := ens.BeginMiningMustPost(blocktime) + req.Len(blockMiners, 1) + blockMiner := blockMiners[0] + + // Instantiate MinerB to manually handle sector onboarding and power acquisition through sector activation. + // Unlike other miners managed by the Lotus Miner storage infrastructure, MinerB operates independently, + // performing all related tasks manually. Managed by the TestKit, MinerB has the capability to utilize actual proofs + // for the processes of sector onboarding and activation. + nodeOpts := []kit.NodeOpt{kit.SectorSize(defaultSectorSize), kit.OwnerAddr(client.DefaultKey)} + minerB, ens := ens.UnmanagedMiner(&client, nodeOpts...) + // MinerC is similar to MinerB, but onboards pieces instead of a pure CC sector + minerC, ens := ens.UnmanagedMiner(&client, nodeOpts...) + + ens.Start() + + build.Clock.Sleep(time.Second) + + t.Log("Checking initial power ...") + + // Miner A should have power as it has already onboarded sectors in the genesis block + head, err := client.ChainHead(ctx) + req.NoError(err) + p, err := client.StateMinerPower(ctx, minerA.ActorAddr, head.Key()) + req.NoError(err) + t.Logf("MinerA RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + + // Miner B should have no power as it has yet to onboard and activate any sectors + minerB.AssertNoPower(ctx) + + // Miner C should have no power as it has yet to onboard and activate any sectors + minerC.AssertNoPower(ctx) + + // ---- Miner B onboards a CC sector + var bSectorNum abi.SectorNumber + var bRespCh chan kit.WindowPostResp + var bWdPostCancelF context.CancelFunc + + if withMockProofs { + bSectorNum, bRespCh, bWdPostCancelF = minerB.OnboardCCSectorWithMockProofs(ctx, kit.TestSpt) + } else { + bSectorNum, bRespCh, bWdPostCancelF = minerB.OnboardCCSectorWithRealProofs(ctx, kit.TestSpt) + } + // Miner B should still not have power as power can only be gained after sector is activated i.e. the first WindowPost is submitted for it + minerB.AssertNoPower(ctx) + // Ensure that the block miner checks for and waits for posts during the appropriate proving window from our new miner with a sector + blockMiner.WatchMinerForPost(minerB.ActorAddr) + + // --- Miner C onboards sector with data/pieces + var cSectorNum abi.SectorNumber + var cRespCh chan kit.WindowPostResp + + if withMockProofs { + cSectorNum, cRespCh, _ = minerC.OnboardSectorWithPiecesAndMockProofs(ctx, kit.TestSpt) + } else { + cSectorNum, cRespCh, _ = minerC.OnboardSectorWithPiecesAndRealProofs(ctx, kit.TestSpt) + } + // Miner C should still not have power as power can only be gained after sector is activated i.e. the first WindowPost is submitted for it + minerC.AssertNoPower(ctx) + // Ensure that the block miner checks for and waits for posts during the appropriate proving window from our new miner with a sector + blockMiner.WatchMinerForPost(minerC.ActorAddr) + + // Wait till both miners' sectors have had their first post and are activated and check that this is reflected in miner power + waitTillActivatedAndAssertPower(ctx, t, minerB, bRespCh, bSectorNum, uint64(defaultSectorSize), withMockProofs) + waitTillActivatedAndAssertPower(ctx, t, minerC, cRespCh, cSectorNum, uint64(defaultSectorSize), withMockProofs) + + // Miner B has activated the CC sector -> upgrade it with snapdeals + // Note: We can't activate a sector with mock proofs as the WdPost is successfully disputed and so no point + // in snapping it as snapping is only for activated sectors + if !withMockProofs { + minerB.SnapDealWithRealProofs(ctx, kit.TestSpt, bSectorNum) + // cancel the WdPost for the CC sector as the corresponding CommR is no longer valid + bWdPostCancelF() + } + }) + } +} + +func waitTillActivatedAndAssertPower(ctx context.Context, t *testing.T, miner *kit.TestUnmanagedMiner, respCh chan kit.WindowPostResp, sector abi.SectorNumber, + sectorSize uint64, withMockProofs bool) { + req := require.New(t) + // wait till sector is activated + select { + case resp := <-respCh: + req.NoError(resp.Error) + req.True(resp.Posted) + case <-ctx.Done(): + t.Fatal("timed out waiting for sector activation") + } + + // Fetch on-chain sector properties + head, err := miner.FullNode.ChainHead(ctx) + req.NoError(err) + + soi, err := miner.FullNode.StateSectorGetInfo(ctx, miner.ActorAddr, sector, head.Key()) + req.NoError(err) + t.Logf("Miner %s SectorOnChainInfo %d: %+v", miner.ActorAddr.String(), sector, soi) + + _ = miner.FullNode.WaitTillChain(ctx, kit.HeightAtLeast(head.Height()+5)) + + t.Log("Checking power after PoSt ...") + + // Miner B should now have power + miner.AssertPower(ctx, sectorSize, sectorSize) + + if withMockProofs { + // WindowPost Dispute should succeed as we are using mock proofs + err := miner.SubmitPostDispute(ctx, sector) + require.NoError(t, err) + } else { + // WindowPost Dispute should fail + assertDisputeFails(ctx, t, miner, sector) + } +} + +func assertDisputeFails(ctx context.Context, t *testing.T, miner *kit.TestUnmanagedMiner, sector abi.SectorNumber) { + err := miner.SubmitPostDispute(ctx, sector) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to dispute valid post") + require.Contains(t, err.Error(), "(RetCode=16)") +} diff --git a/itests/path_type_filters_test.go b/itests/path_type_filters_test.go deleted file mode 100644 index a2e204932..000000000 --- a/itests/path_type_filters_test.go +++ /dev/null @@ -1,200 +0,0 @@ -package itests - -import ( - "context" - "strings" - "testing" - "time" - - logging "github.com/ipfs/go-log/v2" - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/storage/sealer/sealtasks" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -func TestPathTypeFilters(t *testing.T) { - kit.QuietMiningLogs() - - runTest := func(t *testing.T, name string, asserts func(t *testing.T, ctx context.Context, miner *kit.TestMiner, run func())) { - t.Run(name, func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - _ = logging.SetLogLevel("storageminer", "INFO") - - var ( - client kit.TestFullNode - miner kit.TestMiner - wiw, wdw kit.TestWorker - ) - ens := kit.NewEnsemble(t, kit.LatestActorsAt(-1)). - FullNode(&client, kit.ThroughRPC()). - Miner(&miner, &client, kit.WithAllSubsystems(), kit.ThroughRPC(), kit.PresealSectors(2), kit.NoStorage()). - Worker(&miner, &wiw, kit.ThroughRPC(), kit.NoStorage(), kit.WithTaskTypes([]sealtasks.TaskType{sealtasks.TTGenerateWinningPoSt})). - Worker(&miner, &wdw, kit.ThroughRPC(), kit.NoStorage(), kit.WithTaskTypes([]sealtasks.TaskType{sealtasks.TTGenerateWindowPoSt})). - Start() - - ens.InterconnectAll().BeginMiningMustPost(2 * time.Millisecond) - - asserts(t, ctx, &miner, func() { - dh := kit.NewDealHarness(t, &client, &miner, &miner) - dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1}) - }) - }) - } - - runTest(t, "invalid-type-alert", func(t *testing.T, ctx context.Context, miner *kit.TestMiner, run func()) { - slU := miner.AddStorage(ctx, t, func(meta *storiface.LocalStorageMeta) { - meta.CanSeal = true - meta.AllowTypes = []string{"unsealed", "seeled"} - }) - - storlist, err := miner.StorageList(ctx) - require.NoError(t, err) - - require.Len(t, storlist, 2) // 1 path we've added + preseal - - si, err := miner.StorageInfo(ctx, slU) - require.NoError(t, err) - - // check that bad entries are filtered - require.Len(t, si.DenyTypes, 0) - require.Len(t, si.AllowTypes, 1) - require.Equal(t, "unsealed", si.AllowTypes[0]) - - as, err := miner.LogAlerts(ctx) - require.NoError(t, err) - - var found bool - for _, a := range as { - if a.Active && a.Type.System == "sector-index" && strings.HasPrefix(a.Type.Subsystem, "pathconf-") { - require.False(t, found) - require.Contains(t, string(a.LastActive.Message), "unknown sector file type 'seeled'") - found = true - } - } - require.True(t, found) - }) - - runTest(t, "seal-to-stor-unseal-allowdeny", func(t *testing.T, ctx context.Context, miner *kit.TestMiner, run func()) { - // allow all types in the sealing path - sealScratch := miner.AddStorage(ctx, t, func(meta *storiface.LocalStorageMeta) { - meta.CanSeal = true - }) - - // unsealed storage - unsStor := miner.AddStorage(ctx, t, func(meta *storiface.LocalStorageMeta) { - meta.CanStore = true - meta.AllowTypes = []string{"unsealed"} - }) - - // other storage - sealStor := miner.AddStorage(ctx, t, func(meta *storiface.LocalStorageMeta) { - meta.CanStore = true - meta.DenyTypes = []string{"unsealed"} - }) - - storlist, err := miner.StorageList(ctx) - require.NoError(t, err) - - require.Len(t, storlist, 4) // 3 paths we've added + preseal - - run() - - storlist, err = miner.StorageList(ctx) - require.NoError(t, err) - - require.Len(t, storlist[sealScratch], 0) - require.Len(t, storlist[unsStor], 1) - require.Len(t, storlist[sealStor], 1) - - require.Equal(t, storiface.FTUnsealed, storlist[unsStor][0].SectorFileType) - require.Equal(t, storiface.FTSealed|storiface.FTCache, storlist[sealStor][0].SectorFileType) - }) - - runTest(t, "sealstor-unseal-allowdeny", func(t *testing.T, ctx context.Context, miner *kit.TestMiner, run func()) { - // unsealed storage - unsStor := miner.AddStorage(ctx, t, func(meta *storiface.LocalStorageMeta) { - meta.CanStore = true - meta.CanSeal = true - meta.AllowTypes = []string{"unsealed"} - }) - - // other storage - sealStor := miner.AddStorage(ctx, t, func(meta *storiface.LocalStorageMeta) { - meta.CanStore = true - meta.CanSeal = true - meta.DenyTypes = []string{"unsealed"} - }) - - storlist, err := miner.StorageList(ctx) - require.NoError(t, err) - - require.Len(t, storlist, 3) // 2 paths we've added + preseal - - run() - - storlist, err = miner.StorageList(ctx) - require.NoError(t, err) - - require.Len(t, storlist[unsStor], 1) - require.Len(t, storlist[sealStor], 1) - - require.Equal(t, storiface.FTUnsealed, storlist[unsStor][0].SectorFileType) - require.Equal(t, storiface.FTSealed|storiface.FTCache, storlist[sealStor][0].SectorFileType) - }) - - runTest(t, "seal-store-allseparate", func(t *testing.T, ctx context.Context, miner *kit.TestMiner, run func()) { - // sealing stores - slU := miner.AddStorage(ctx, t, func(meta *storiface.LocalStorageMeta) { - meta.CanSeal = true - meta.AllowTypes = []string{"unsealed"} - }) - slS := miner.AddStorage(ctx, t, func(meta *storiface.LocalStorageMeta) { - meta.CanSeal = true - meta.AllowTypes = []string{"sealed"} - }) - slC := miner.AddStorage(ctx, t, func(meta *storiface.LocalStorageMeta) { - meta.CanSeal = true - meta.AllowTypes = []string{"cache"} - }) - - // storage stores - stU := miner.AddStorage(ctx, t, func(meta *storiface.LocalStorageMeta) { - meta.CanStore = true - meta.AllowTypes = []string{"unsealed"} - }) - stS := miner.AddStorage(ctx, t, func(meta *storiface.LocalStorageMeta) { - meta.CanStore = true - meta.AllowTypes = []string{"sealed"} - }) - stC := miner.AddStorage(ctx, t, func(meta *storiface.LocalStorageMeta) { - meta.CanStore = true - meta.AllowTypes = []string{"cache"} - }) - - storlist, err := miner.StorageList(ctx) - require.NoError(t, err) - - require.Len(t, storlist, 7) // 6 paths we've added + preseal - - run() - - storlist, err = miner.StorageList(ctx) - require.NoError(t, err) - - require.Len(t, storlist[slU], 0) - require.Len(t, storlist[slS], 0) - require.Len(t, storlist[slC], 0) - - require.Len(t, storlist[stU], 1) - require.Len(t, storlist[stS], 1) - require.Len(t, storlist[stC], 1) - - require.Equal(t, storiface.FTUnsealed, storlist[stU][0].SectorFileType) - require.Equal(t, storiface.FTSealed, storlist[stS][0].SectorFileType) - require.Equal(t, storiface.FTCache, storlist[stC][0].SectorFileType) - }) -} diff --git a/itests/sector_finalize_early_test.go b/itests/sector_finalize_early_test.go deleted file mode 100644 index 1b8fcb346..000000000 --- a/itests/sector_finalize_early_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// stm: #integration -package itests - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/node/config" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -func TestDealsWithFinalizeEarly(t *testing.T) { - //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, - //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01 - //stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 - //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 - - //stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001 - //stm: @STORAGE_INFO_001 - if testing.Short() { - t.Skip("skipping test in short mode") - } - - kit.QuietMiningLogs() - - var blockTime = 50 * time.Millisecond - - // We use two miners so that in case the actively tested miner misses PoSt, we still have a blockchain - client, miner, poster, ens := kit.EnsembleOneTwo(t, kit.ThroughRPC(), kit.MutateSealingConfig(func(sc *config.SealingConfig) { sc.FinalizeEarly = true })) // no mock proofs. - ens.InterconnectAll().BeginMiningMustPost(blockTime, poster) - dh := kit.NewDealHarness(t, client, miner, miner) - - ctx := context.Background() - - miner.AddStorage(ctx, t, func(meta *storiface.LocalStorageMeta) { - meta.Weight = 1000000000 - meta.CanSeal = true - }) - miner.AddStorage(ctx, t, func(meta *storiface.LocalStorageMeta) { - meta.Weight = 1000000000 - meta.CanStore = true - }) - - //stm: @STORAGE_LIST_001 - sl, err := miner.StorageList(ctx) - require.NoError(t, err) - for si, d := range sl { - i, err := miner.StorageInfo(ctx, si) - require.NoError(t, err) - - fmt.Printf("stor d:%d %+v\n", len(d), i) - } - - t.Run("single", func(t *testing.T) { - dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1}) - }) - - //stm: @STORAGE_LIST_001 - sl, err = miner.StorageList(ctx) - require.NoError(t, err) - for si, d := range sl { - i, err := miner.StorageInfo(ctx, si) - require.NoError(t, err) - - fmt.Printf("stor d:%d %+v\n", len(d), i) - } -} diff --git a/itests/supply_test.go b/itests/supply_test.go new file mode 100644 index 000000000..5c603338d --- /dev/null +++ b/itests/supply_test.go @@ -0,0 +1,205 @@ +package itests + +import ( + "bytes" + "context" + "testing" + "time" + + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "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/builtin" + "github.com/filecoin-project/go-state-types/builtin/v9/market" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/itests/kit" +) + +func TestCirciulationSupplyUpgrade(t *testing.T) { + kit.QuietMiningLogs() + ctx := context.Background() + + // Choosing something divisible by epochs per day to remove error with simple deal duration + lockedClientBalance := big.Mul(abi.NewTokenAmount(11_520_000), abi.NewTokenAmount(1e18)) + lockedProviderBalance := big.Mul(abi.NewTokenAmount(1_000_000), abi.NewTokenAmount(1e18)) + var height0 abi.ChainEpoch + var height1 abi.ChainEpoch + // Lock collateral in market on nv22 network + fullNode0, blockMiner0, ens0 := kit.EnsembleMinimal(t, + kit.GenesisNetworkVersion(network.Version22), + kit.MockProofs(), + ) + { + + worker0 := blockMiner0.OwnerKey.Address + ens0.InterconnectAll().BeginMining(50 * time.Millisecond) + + // Lock collateral in market actor + wallet0, err := fullNode0.WalletDefaultAddress(ctx) + require.NoError(t, err) + + // Add 1 FIL to cover provider collateral + c00, err := fullNode0.MarketAddBalance(ctx, wallet0, wallet0, lockedClientBalance) + require.NoError(t, err) + fullNode0.WaitMsg(ctx, c00) + c01, err := blockMiner0.FullNode.MarketAddBalance(ctx, worker0, blockMiner0.ActorAddr, lockedProviderBalance) + require.NoError(t, err) + fullNode0.WaitMsg(ctx, c01) + + psd0, err := fullNode0.MpoolPushMessage(ctx, + makePSDMessage( + ctx, + t, + blockMiner0.ActorAddr, + worker0, + wallet0, + lockedProviderBalance, + lockedClientBalance, + fullNode0.WalletSign, + ), + nil, + ) + require.NoError(t, err) + fullNode0.WaitMsg(ctx, psd0.Cid()) + head, err := fullNode0.ChainHead(ctx) + require.NoError(t, err) + height0 = head.Height() + } + + // Lock collateral in market on nv23 network + fullNode1, blockMiner1, ens1 := kit.EnsembleMinimal(t, + kit.GenesisNetworkVersion(network.Version23), + kit.MockProofs(), + ) + { + worker1 := blockMiner1.OwnerKey.Address + ens1.InterconnectAll().BeginMining(50 * time.Millisecond) + + // Lock collateral in market actor + wallet1, err := fullNode1.WalletDefaultAddress(ctx) + require.NoError(t, err) + c10, err := fullNode1.MarketAddBalance(ctx, wallet1, wallet1, lockedClientBalance) + require.NoError(t, err) + fullNode1.WaitMsg(ctx, c10) + c11, err := blockMiner1.FullNode.MarketAddBalance(ctx, worker1, blockMiner1.ActorAddr, lockedProviderBalance) + require.NoError(t, err) + fullNode1.WaitMsg(ctx, c11) + + psd1, err := fullNode1.MpoolPushMessage(ctx, + makePSDMessage( + ctx, + t, + blockMiner1.ActorAddr, + worker1, + wallet1, + lockedProviderBalance, + lockedClientBalance, + fullNode1.WalletSign, + ), + nil, + ) + require.NoError(t, err) + fullNode1.WaitMsg(ctx, psd1.Cid()) + head, err := fullNode1.ChainHead(ctx) + require.NoError(t, err) + height1 = head.Height() + } + + // Measure each circulating supply at the latest height where market balance was locked + // This allows us to normalize against fluctuations in circulating supply based on the underlying + // dynamics irrelevant to this change + + max := height0 + if height0 < height1 { + max = height1 + } + max = max + 1 // Measure supply at height after the deal locking funds was published + + // Let both chains catch up + fullNode0.WaitTillChain(ctx, func(ts *types.TipSet) bool { + return ts.Height() >= max + }) + fullNode1.WaitTillChain(ctx, func(ts *types.TipSet) bool { + return ts.Height() >= max + }) + + ts0, err := fullNode0.ChainGetTipSetByHeight(ctx, max, types.EmptyTSK) + require.NoError(t, err) + ts1, err := fullNode1.ChainGetTipSetByHeight(ctx, max, types.EmptyTSK) + require.NoError(t, err) + + nv22Supply, err := fullNode0.StateVMCirculatingSupplyInternal(ctx, ts0.Key()) + require.NoError(t, err, "Failed to fetch nv22 circulating supply") + nv23Supply, err := fullNode1.StateVMCirculatingSupplyInternal(ctx, ts1.Key()) + require.NoError(t, err, "Failed to fetch nv23 circulating supply") + + // Unfortunately there's still some non-determinism in supply dynamics so check for equality within a tolerance + tolerance := big.Mul(abi.NewTokenAmount(1000), abi.NewTokenAmount(1e18)) + totalLocked := big.Sum(lockedClientBalance, lockedProviderBalance) + diff := big.Sub( + big.Sum(totalLocked, nv23Supply.FilLocked), + nv22Supply.FilLocked, + ) + assert.Equal(t, -1, big.Cmp( + diff.Abs(), + tolerance), "Difference from expected locked supply between versions exceeds tolerance") +} + +// Message will be valid and lock funds but the data is fake so the deal will never be activated +func makePSDMessage( + ctx context.Context, + t *testing.T, + provider, + worker, + client address.Address, + providerLocked, + clientLocked abi.TokenAmount, + signFunc func(context.Context, address.Address, []byte) (*crypto.Signature, error)) *types.Message { + + dummyCid, err := cid.Parse("baga6ea4seaqflae5c3k2odz4sqfufmrmoegplhk5jbq3co4fgmmy56yc2qfh4aq") + require.NoError(t, err) + + duration := 2880 * 200 // 200 days + ppe := big.Div(clientLocked, big.NewInt(2880*200)) + proposal := market.DealProposal{ + PieceCID: dummyCid, + PieceSize: abi.PaddedPieceSize(128), + VerifiedDeal: false, + Client: client, + Provider: provider, + ClientCollateral: big.Zero(), + ProviderCollateral: providerLocked, + StartEpoch: 10000, + EndEpoch: 10000 + abi.ChainEpoch(duration), + StoragePricePerEpoch: ppe, + } + buf := bytes.NewBuffer(nil) + require.NoError(t, proposal.MarshalCBOR(buf)) + sig, err := signFunc(ctx, client, buf.Bytes()) + require.NoError(t, err) + // Publish storage deal + params, err := actors.SerializeParams(&market.PublishStorageDealsParams{ + Deals: []market.ClientDealProposal{ + { + Proposal: proposal, + ClientSignature: *sig, + }, + }, + }) + require.NoError(t, err) + return &types.Message{ + To: builtin.StorageMarketActorAddr, + From: worker, + Value: types.NewInt(0), + Method: builtin.MethodsMarket.PublishStorageDeals, + Params: params, + } +} diff --git a/itests/worker_test.go b/itests/worker_test.go index 31ec40b59..b3b8edd76 100644 --- a/itests/worker_test.go +++ b/itests/worker_test.go @@ -585,82 +585,3 @@ waitForProof: require.NoError(t, params.UnmarshalCBOR(bytes.NewBuffer(slmsg.Params))) require.Equal(t, abi.RegisteredPoStProof_StackedDrgWindow2KiBV1_1, params.Proofs[0].PoStProof) } - -func TestWorkerPledgeExpireCommit(t *testing.T) { - kit.QuietMiningLogs() - _ = logging.SetLogLevel("sectors", "debug") - - var tasksNoC2 = kit.WithTaskTypes([]sealtasks.TaskType{sealtasks.TTAddPiece, sealtasks.TTDataCid, sealtasks.TTPreCommit1, sealtasks.TTPreCommit2, sealtasks.TTCommit2, - sealtasks.TTUnseal, sealtasks.TTFetch, sealtasks.TTCommit1, sealtasks.TTFinalize, sealtasks.TTFinalizeUnsealed}) - - fc := config.DefaultStorageMiner().Fees - fc.MaxCommitGasFee = types.FIL(abi.NewTokenAmount(10000)) // 10000 attofil, way too low for anything to land - - ctx := context.Background() - client, miner, worker, ens := kit.EnsembleWorker(t, kit.WithAllSubsystems(), kit.ThroughRPC(), kit.WithNoLocalSealing(true), - kit.MutateSealingConfig(func(sc *config.SealingConfig) { - sc.AggregateCommits = true - }), - kit.ConstructorOpts( - node.Override(new(*sealing.Sealing), modules.SealingPipeline(fc)), - ), - kit.SplitstoreDisable(), // disable splitstore because messages which take a long time may get dropped - tasksNoC2) // no mock proofs - - ens.InterconnectAll().BeginMiningMustPost(2 * time.Millisecond) - - e, err := worker.Enabled(ctx) - require.NoError(t, err) - require.True(t, e) - - dh := kit.NewDealHarness(t, client, miner, miner) - - startEpoch := abi.ChainEpoch(4 << 10) - - dh.StartRandomDeal(ctx, kit.MakeFullDealParams{ - Rseed: 7, - StartEpoch: startEpoch, - }) - - var sn abi.SectorNumber - - require.Eventually(t, func() bool { - s, err := miner.SectorsListNonGenesis(ctx) - require.NoError(t, err) - if len(s) == 0 { - return false - } - if len(s) > 1 { - t.Fatalf("expected 1 sector, got %d", len(s)) - } - sn = s[0] - return true - }, 30*time.Second, 1*time.Second) - - t.Log("sector", sn) - - t.Log("sector committing") - - // wait until after startEpoch - client.WaitTillChain(ctx, kit.HeightAtLeast(startEpoch+20)) - - t.Log("after start") - - sstate, err := miner.SectorsStatus(ctx, sn, false) - require.NoError(t, err) - require.Equal(t, api.SectorState(sealing.SubmitCommitAggregate), sstate.State) - - _, err = miner.SectorCommitFlush(ctx) - require.NoError(t, err) - - require.Eventually(t, func() bool { - sstate, err := miner.SectorsStatus(ctx, sn, false) - require.NoError(t, err) - - t.Logf("sector state: %s", sstate.State) - - return sstate.State == api.SectorState(sealing.Removed) - }, 30*time.Second, 1*time.Second) - - t.Log("sector removed") -} diff --git a/itests/worker_upgrade_test.go b/itests/worker_upgrade_test.go deleted file mode 100644 index b253a26a5..000000000 --- a/itests/worker_upgrade_test.go +++ /dev/null @@ -1,170 +0,0 @@ -package itests - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/node/config" - sealing "github.com/filecoin-project/lotus/storage/pipeline" - "github.com/filecoin-project/lotus/storage/sealer/sealtasks" - "github.com/filecoin-project/lotus/storage/sealer/storiface" -) - -func TestWorkerUpgradeAbortCleanup(t *testing.T) { - ctx := context.Background() - blockTime := 1 * time.Millisecond - kit.QuietMiningLogs() - - client, miner, ens := kit.EnsembleMinimal(t, kit.WithAllSubsystems(), kit.ThroughRPC(), kit.WithNoLocalSealing(true), - kit.NoStorage(), // no storage to have better control over path settings - kit.MutateSealingConfig(func(sc *config.SealingConfig) { sc.FinalizeEarly = true })) // no mock proofs - - var worker kit.TestWorker - ens.Worker(miner, &worker, kit.ThroughRPC(), kit.NoStorage(), // no storage to have better control over path settings - kit.WithTaskTypes([]sealtasks.TaskType{ - sealtasks.TTFetch, sealtasks.TTAddPiece, - sealtasks.TTCommit1, sealtasks.TTFinalize, sealtasks.TTFinalizeUnsealed, sealtasks.TTPreCommit1, sealtasks.TTPreCommit2, sealtasks.TTCommit2, - sealtasks.TTReplicaUpdate, // only first update step, later steps will not run and we'll abort - }), - ) - - ens.Start().InterconnectAll().BeginMiningMustPost(blockTime) - - maddr, err := miner.ActorAddress(ctx) - if err != nil { - t.Fatal(err) - } - - // get storage paths - - // store-only path on the miner - miner.AddStorage(ctx, t, func(cfg *storiface.LocalStorageMeta) { - cfg.CanSeal = false - cfg.CanStore = true - }) - - mlocal, err := miner.StorageLocal(ctx) - require.NoError(t, err) - require.Len(t, mlocal, 2) // genesis and one local - - // we want a seal-only path on the worker disconnected from miner path - worker.AddStorage(ctx, t, func(cfg *storiface.LocalStorageMeta) { - cfg.CanSeal = true - cfg.CanStore = false - }) - - wpaths, err := worker.Paths(ctx) - require.NoError(t, err) - require.Len(t, wpaths, 1) - - // check sectors in paths - checkSectors := func(miners, workers storiface.SectorFileType) { - paths, err := miner.StorageList(ctx) - require.NoError(t, err) - require.Len(t, paths, 3) // genesis, miner, worker - - // first loop for debugging - for id, decls := range paths { - pinfo, err := miner.StorageInfo(ctx, id) - require.NoError(t, err) - - switch { - case id == wpaths[0].ID: // worker path - fmt.Println("Worker Decls ", len(decls), decls) - case !pinfo.CanStore && !pinfo.CanSeal: // genesis path - fmt.Println("Genesis Decls ", len(decls), decls) - default: // miner path - fmt.Println("Miner Decls ", len(decls), decls) - } - } - - for id, decls := range paths { - pinfo, err := miner.StorageInfo(ctx, id) - require.NoError(t, err) - - switch { - case id == wpaths[0].ID: // worker path - if workers != storiface.FTNone { - require.Len(t, decls, 1) - require.EqualValues(t, workers.Strings(), decls[0].SectorFileType.Strings()) - } else { - require.Len(t, decls, 0) - } - case !pinfo.CanStore && !pinfo.CanSeal: // genesis path - require.Len(t, decls, kit.DefaultPresealsPerBootstrapMiner) - default: // miner path - if miners != storiface.FTNone { - require.Len(t, decls, 1) - require.EqualValues(t, miners.Strings(), decls[0].SectorFileType.Strings()) - } else { - require.Len(t, decls, 0) - } - } - } - } - checkSectors(storiface.FTNone, storiface.FTNone) - - // get a sector for upgrading - miner.PledgeSectors(ctx, 1, 0, nil) - sl, err := miner.SectorsListNonGenesis(ctx) - require.NoError(t, err) - require.Len(t, sl, 1, "expected 1 sector") - - snum := sl[0] - - checkSectors(storiface.FTCache|storiface.FTSealed, storiface.FTNone) - - client.WaitForSectorActive(ctx, t, snum, maddr) - - // make available - err = miner.SectorMarkForUpgrade(ctx, snum, true) - require.NoError(t, err) - - // Start a deal - - dh := kit.NewDealHarness(t, client, miner, miner) - res, _ := client.CreateImportFile(ctx, 123, 0) - dp := dh.DefaultStartDealParams() - dp.Data.Root = res.Root - deal := dh.StartDeal(ctx, dp) - - // wait for the deal to be in a sector - dh.WaitDealSealed(ctx, deal, true, false, nil) - - // wait for replica update to happen - require.Eventually(t, func() bool { - sstate, err := miner.SectorsStatus(ctx, snum, false) - require.NoError(t, err) - return sstate.State == api.SectorState(sealing.ProveReplicaUpdate) - }, 10*time.Second, 50*time.Millisecond) - - // check that the sector was copied to the worker - checkSectors(storiface.FTCache|storiface.FTSealed, storiface.FTCache|storiface.FTSealed|storiface.FTUnsealed|storiface.FTUpdate|storiface.FTUpdateCache) - - // abort upgrade - err = miner.SectorAbortUpgrade(ctx, snum) - require.NoError(t, err) - - // the task is stuck in scheduler, so manually abort the task to get the sector fsm moving - si := miner.SchedInfo(ctx) - err = miner.SealingRemoveRequest(ctx, si.SchedInfo.Requests[0].SchedId) - require.NoError(t, err) - - var lastState api.SectorState - require.Eventually(t, func() bool { - sstate, err := miner.SectorsStatus(ctx, snum, false) - require.NoError(t, err) - lastState = sstate.State - - return sstate.State == api.SectorState(sealing.Proving) - }, 10*time.Second, 50*time.Millisecond, "last state was %s", &lastState) - - // check that nothing was left on the worker - checkSectors(storiface.FTCache|storiface.FTSealed, storiface.FTNone) -} diff --git a/lib/filler/filler.go b/lib/filler/filler.go new file mode 100644 index 000000000..42bb4fa99 --- /dev/null +++ b/lib/filler/filler.go @@ -0,0 +1,43 @@ +package filler + +import ( + "math/bits" + + "github.com/filecoin-project/go-state-types/abi" +) + +func FillersFromRem(in abi.UnpaddedPieceSize) ([]abi.UnpaddedPieceSize, error) { + // Convert to in-sector bytes for easier math: + // + // Sector size to user bytes ratio is constant, e.g. for 1024B we have 1016B + // of user-usable data. + // + // (1024/1016 = 128/127) + // + // Given that we can get sector size by simply adding 1/127 of the user + // bytes + // + // (we convert to sector bytes as they are nice round binary numbers) + + toFill := uint64(in + (in / 127)) + + // We need to fill the sector with pieces that are powers of 2. Conveniently + // computers store numbers in binary, which means we can look at 1s to get + // all the piece sizes we need to fill the sector. It also means that number + // of pieces is the number of 1s in the number of remaining bytes to fill + out := make([]abi.UnpaddedPieceSize, bits.OnesCount64(toFill)) + for i := range out { + // Extract the next lowest non-zero bit + next := bits.TrailingZeros64(toFill) + psize := uint64(1) << next + // e.g: if the number is 0b010100, psize will be 0b000100 + + // set that bit to 0 by XORing it, so the next iteration looks at the + // next bit + toFill ^= psize + + // Add the piece size to the list of pieces we need to create + out[i] = abi.PaddedPieceSize(psize).Unpadded() + } + return out, nil +} diff --git a/storage/pipeline/utils_test.go b/lib/filler/filler_test.go similarity index 96% rename from storage/pipeline/utils_test.go rename to lib/filler/filler_test.go index fb9548a28..a9b62da30 100644 --- a/storage/pipeline/utils_test.go +++ b/lib/filler/filler_test.go @@ -1,4 +1,4 @@ -package sealing +package filler import ( "testing" @@ -9,7 +9,7 @@ import ( ) func testFill(t *testing.T, n abi.UnpaddedPieceSize, exp []abi.UnpaddedPieceSize) { - f, err := fillersFromRem(n) + f, err := FillersFromRem(n) assert.NoError(t, err) assert.Equal(t, exp, f) diff --git a/lib/harmony/harmonydb/harmonydb.go b/lib/harmony/harmonydb/harmonydb.go index 56b5acdfe..28b071c2e 100644 --- a/lib/harmony/harmonydb/harmonydb.go +++ b/lib/harmony/harmonydb/harmonydb.go @@ -25,7 +25,7 @@ import ( type ITestID string -// ItestNewID see ITestWithID doc +// ITestNewID see ITestWithID doc func ITestNewID() ITestID { return ITestID(strconv.Itoa(rand.Intn(99999))) } @@ -278,7 +278,10 @@ func (db *DB) upgrade() error { logger.Error("weird embed file read err") return err } - for _, s := range strings.Split(string(file), ";") { // Implement the changes. + + logger.Infow("Upgrading", "file", name, "size", len(file)) + + for _, s := range parseSQLStatements(string(file)) { // Implement the changes. if len(strings.TrimSpace(s)) == 0 { continue } @@ -299,3 +302,40 @@ func (db *DB) upgrade() error { } return nil } + +func parseSQLStatements(sqlContent string) []string { + var statements []string + var currentStatement strings.Builder + + lines := strings.Split(sqlContent, "\n") + var inFunction bool + + for _, line := range lines { + trimmedLine := strings.TrimSpace(line) + if trimmedLine == "" || strings.HasPrefix(trimmedLine, "--") { + // Skip empty lines and comments. + continue + } + + // Detect function blocks starting or ending. + if strings.Contains(trimmedLine, "$$") { + inFunction = !inFunction + } + + // Add the line to the current statement. + currentStatement.WriteString(line + "\n") + + // If we're not in a function and the line ends with a semicolon, or we just closed a function block. + if (!inFunction && strings.HasSuffix(trimmedLine, ";")) || (strings.Contains(trimmedLine, "$$") && !inFunction) { + statements = append(statements, currentStatement.String()) + currentStatement.Reset() + } + } + + // Add any remaining statement not followed by a semicolon (should not happen in well-formed SQL but just in case). + if currentStatement.Len() > 0 { + statements = append(statements, currentStatement.String()) + } + + return statements +} diff --git a/lib/harmony/harmonydb/sql/20230719-harmony.sql b/lib/harmony/harmonydb/sql/20230719-harmony.sql deleted file mode 100644 index e7b1795c5..000000000 --- a/lib/harmony/harmonydb/sql/20230719-harmony.sql +++ /dev/null @@ -1,51 +0,0 @@ -/* For HarmonyTask base implementation. */ - -CREATE TABLE harmony_machines ( - id SERIAL PRIMARY KEY NOT NULL, - last_contact TIMESTAMP NOT NULL DEFAULT current_timestamp, - host_and_port varchar(300) NOT NULL, - cpu INTEGER NOT NULL, - ram BIGINT NOT NULL, - gpu FLOAT NOT NULL -); - -CREATE TABLE harmony_task ( - id SERIAL PRIMARY KEY NOT NULL, - initiated_by INTEGER, - update_time TIMESTAMP NOT NULL DEFAULT current_timestamp, - posted_time TIMESTAMP NOT NULL, - owner_id INTEGER REFERENCES harmony_machines (id) ON DELETE SET NULL, - added_by INTEGER NOT NULL, - previous_task INTEGER, - name varchar(16) NOT NULL -); -COMMENT ON COLUMN harmony_task.initiated_by IS 'The task ID whose completion occasioned this task.'; -COMMENT ON COLUMN harmony_task.owner_id IS 'The foreign key to harmony_machines.'; -COMMENT ON COLUMN harmony_task.name IS 'The name of the task type.'; -COMMENT ON COLUMN harmony_task.owner_id IS 'may be null if between owners or not yet taken'; -COMMENT ON COLUMN harmony_task.update_time IS 'When it was last modified. not a heartbeat'; - -CREATE TABLE harmony_task_history ( - id SERIAL PRIMARY KEY NOT NULL, - task_id INTEGER NOT NULL, - name VARCHAR(16) NOT NULL, - posted TIMESTAMP NOT NULL, - work_start TIMESTAMP NOT NULL, - work_end TIMESTAMP NOT NULL, - result BOOLEAN NOT NULL, - err varchar -); -COMMENT ON COLUMN harmony_task_history.result IS 'Use to detemine if this was a successful run.'; - -CREATE TABLE harmony_task_follow ( - id SERIAL PRIMARY KEY NOT NULL, - owner_id INTEGER NOT NULL REFERENCES harmony_machines (id) ON DELETE CASCADE, - to_type VARCHAR(16) NOT NULL, - from_type VARCHAR(16) NOT NULL -); - -CREATE TABLE harmony_task_impl ( - id SERIAL PRIMARY KEY NOT NULL, - owner_id INTEGER NOT NULL REFERENCES harmony_machines (id) ON DELETE CASCADE, - name VARCHAR(16) NOT NULL -); \ No newline at end of file diff --git a/lib/harmony/harmonydb/sql/20230823-wdpost.sql b/lib/harmony/harmonydb/sql/20230823-wdpost.sql deleted file mode 100644 index c6f993d76..000000000 --- a/lib/harmony/harmonydb/sql/20230823-wdpost.sql +++ /dev/null @@ -1,48 +0,0 @@ -create table wdpost_partition_tasks -( - task_id bigint not null - constraint wdpost_partition_tasks_pk - primary key, - sp_id bigint not null, - proving_period_start bigint not null, - deadline_index bigint not null, - partition_index bigint not null, - constraint wdpost_partition_tasks_identity_key - unique (sp_id, proving_period_start, deadline_index, partition_index) -); - -comment on column wdpost_partition_tasks.task_id is 'harmonytask task ID'; -comment on column wdpost_partition_tasks.sp_id is 'storage provider ID'; -comment on column wdpost_partition_tasks.proving_period_start is 'proving period start'; -comment on column wdpost_partition_tasks.deadline_index is 'deadline index within the proving period'; -comment on column wdpost_partition_tasks.partition_index is 'partition index within the deadline'; - -create table wdpost_proofs -( - sp_id bigint not null, - proving_period_start bigint not null, - deadline bigint not null, - partition bigint not null, - submit_at_epoch bigint not null, - submit_by_epoch bigint not null, - proof_params bytea, - - submit_task_id bigint, - message_cid text, - - constraint wdpost_proofs_identity_key - unique (sp_id, proving_period_start, deadline, partition) -); - -create table wdpost_recovery_tasks -( - task_id bigint not null - constraint wdpost_recovery_tasks_pk - primary key, - sp_id bigint not null, - proving_period_start bigint not null, - deadline_index bigint not null, - partition_index bigint not null, - constraint wdpost_recovery_tasks_identity_key - unique (sp_id, proving_period_start, deadline_index, partition_index) -); \ No newline at end of file diff --git a/lib/harmony/harmonydb/sql/20230919-config.sql b/lib/harmony/harmonydb/sql/20230919-config.sql deleted file mode 100644 index 84699a0d5..000000000 --- a/lib/harmony/harmonydb/sql/20230919-config.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE TABLE harmony_config ( - id SERIAL PRIMARY KEY NOT NULL, - title VARCHAR(300) UNIQUE NOT NULL, - config TEXT NOT NULL -); \ No newline at end of file diff --git a/lib/harmony/harmonydb/sql/20231103-chain_sends.sql b/lib/harmony/harmonydb/sql/20231103-chain_sends.sql deleted file mode 100644 index e70cf3738..000000000 --- a/lib/harmony/harmonydb/sql/20231103-chain_sends.sql +++ /dev/null @@ -1,55 +0,0 @@ -create table message_sends -( - from_key text not null, - to_addr text not null, - send_reason text not null, - send_task_id bigint not null, - - unsigned_data bytea not null, - unsigned_cid text not null, - - nonce bigint, - signed_data bytea, - signed_json jsonb, - signed_cid text, - - send_time timestamp default null, - send_success boolean default null, - send_error text, - - constraint message_sends_pk - primary key (send_task_id, from_key) -); - -comment on column message_sends.from_key is 'text f[1/3/4]... address'; -comment on column message_sends.to_addr is 'text f[0/1/2/3/4]... address'; -comment on column message_sends.send_reason is 'optional description of send reason'; -comment on column message_sends.send_task_id is 'harmony task id of the send task'; - -comment on column message_sends.unsigned_data is 'unsigned message data'; -comment on column message_sends.unsigned_cid is 'unsigned message cid'; - -comment on column message_sends.nonce is 'assigned message nonce, set while the send task is executing'; -comment on column message_sends.signed_data is 'signed message data, set while the send task is executing'; -comment on column message_sends.signed_cid is 'signed message cid, set while the send task is executing'; - -comment on column message_sends.send_time is 'time when the send task was executed, set after pushing the message to the network'; -comment on column message_sends.send_success is 'whether this message was broadcasted to the network already, null if not yet attempted, true if successful, false if failed'; -comment on column message_sends.send_error is 'error message if send_success is false'; - -create unique index message_sends_success_index - on message_sends (from_key, nonce) - where send_success is not false; - -comment on index message_sends_success_index is -'message_sends_success_index enforces sender/nonce uniqueness, it is a conditional index that only indexes rows where send_success is not false. This allows us to have multiple rows with the same sender/nonce, as long as only one of them was successfully broadcasted (true) to the network or is in the process of being broadcasted (null).'; - -create table message_send_locks -( - from_key text not null, - task_id bigint not null, - claimed_at timestamp not null, - - constraint message_send_locks_pk - primary key (from_key) -); diff --git a/lib/harmony/harmonydb/sql/20231110-mining_tasks.sql b/lib/harmony/harmonydb/sql/20231110-mining_tasks.sql deleted file mode 100644 index 15b478f4d..000000000 --- a/lib/harmony/harmonydb/sql/20231110-mining_tasks.sql +++ /dev/null @@ -1,39 +0,0 @@ -create table mining_tasks -( - task_id bigint not null - constraint mining_tasks_pk - primary key, - sp_id bigint not null, - epoch bigint not null, - base_compute_time timestamp not null, - - won bool not null default false, - mined_cid text, - mined_header jsonb, - mined_at timestamp, - - submitted_at timestamp, - - constraint mining_tasks_sp_epoch - unique (sp_id, epoch) -); - -create table mining_base_block -( - id bigserial not null - constraint mining_base_block_pk - primary key, - task_id bigint not null - constraint mining_base_block_mining_tasks_task_id_fk - references mining_tasks - on delete cascade, - sp_id bigint, - block_cid text not null, - - no_win bool not null default false, - - constraint mining_base_block_pk2 - unique (sp_id, task_id, block_cid) -); - -CREATE UNIQUE INDEX mining_base_block_cid_k ON mining_base_block (sp_id, block_cid) WHERE no_win = false; diff --git a/lib/harmony/harmonydb/sql/20231113-harmony_taskhistory_oops.sql b/lib/harmony/harmonydb/sql/20231113-harmony_taskhistory_oops.sql deleted file mode 100644 index 7a71d98ae..000000000 --- a/lib/harmony/harmonydb/sql/20231113-harmony_taskhistory_oops.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE harmony_task_history ADD COLUMN completed_by_host_and_port varchar(300) NOT NULL; \ No newline at end of file diff --git a/lib/harmony/harmonydb/sql/20231120-testing1.sql b/lib/harmony/harmonydb/sql/20231120-testing1.sql deleted file mode 100644 index 71daaef69..000000000 --- a/lib/harmony/harmonydb/sql/20231120-testing1.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE TABLE harmony_test ( - task_id bigint - constraint harmony_test_pk - primary key, - options text, - result text -); -ALTER TABLE wdpost_proofs ADD COLUMN test_task_id bigint; \ No newline at end of file diff --git a/lib/harmony/harmonydb/sql/20231217-sdr-pipeline.sql b/lib/harmony/harmonydb/sql/20231217-sdr-pipeline.sql deleted file mode 100644 index 31f103139..000000000 --- a/lib/harmony/harmonydb/sql/20231217-sdr-pipeline.sql +++ /dev/null @@ -1,135 +0,0 @@ --- NOTE: task_ids can be the same between different task types and between different sectors --- e.g. SN-supraseal doing 128 sdr/TreeC/TreeR with the same task_id - -create table sectors_sdr_pipeline ( - sp_id bigint not null, - sector_number bigint not null, - - -- at request time - create_time timestamp not null default current_timestamp, - reg_seal_proof int not null, - - -- sdr - ticket_epoch bigint, - ticket_value bytea, - - task_id_sdr bigint, - after_sdr bool not null default false, - - -- tree D - tree_d_cid text, -- commd from treeD compute, should match comm_d_cid - - task_id_tree_d bigint, - after_tree_d bool not null default false, - - -- tree C - task_id_tree_c bigint, - after_tree_c bool not null default false, - - -- tree R - tree_r_cid text, -- commr from treeR compute - - task_id_tree_r bigint, - after_tree_r bool not null default false, - - -- precommit message sending - precommit_msg_cid text, - - task_id_precommit_msg bigint, - after_precommit_msg bool not null default false, - - -- precommit message wait - seed_epoch bigint, - precommit_msg_tsk bytea, - after_precommit_msg_success bool not null default false, - - -- seed - seed_value bytea, - - -- Commit (PoRep snark) - task_id_porep bigint, - porep_proof bytea, - after_porep bool not null default false, - - -- Finalize (trim cache) - task_id_finalize bigint, - after_finalize bool not null default false, - - -- MoveStorage (move data to storage) - task_id_move_storage bigint, - after_move_storage bool not null default false, - - -- Commit message sending - commit_msg_cid text, - - task_id_commit_msg bigint, - after_commit_msg bool not null default false, - - -- Commit message wait - commit_msg_tsk bytea, - after_commit_msg_success bool not null default false, - - -- Failure handling - failed bool not null default false, - failed_at timestamp, - failed_reason varchar(20) not null default '', - failed_reason_msg text not null default '', - - -- foreign key - -- note: those foreign keys are a part of the retry mechanism. If a task - -- fails due to retry limit, it will drop the assigned task_id, and the - -- poller will reassign the task to a new node if it deems the task is - -- still valid to be retried. - foreign key (task_id_sdr) references harmony_task (id) on delete set null, - foreign key (task_id_tree_d) references harmony_task (id) on delete set null, - foreign key (task_id_tree_c) references harmony_task (id) on delete set null, - foreign key (task_id_tree_r) references harmony_task (id) on delete set null, - foreign key (task_id_precommit_msg) references harmony_task (id) on delete set null, - foreign key (task_id_porep) references harmony_task (id) on delete set null, - foreign key (task_id_finalize) references harmony_task (id) on delete set null, - foreign key (task_id_move_storage) references harmony_task (id) on delete set null, - foreign key (task_id_commit_msg) references harmony_task (id) on delete set null, - - -- constraints - primary key (sp_id, sector_number) -); - -create table sectors_sdr_initial_pieces ( - sp_id bigint not null, - sector_number bigint not null, - - piece_index bigint not null, - piece_cid text not null, - piece_size bigint not null, -- padded size - - -- data source - data_url text not null, - data_headers jsonb not null default '{}', - data_raw_size bigint not null, - data_delete_on_finalize bool not null, - - -- deal info - f05_publish_cid text, - f05_deal_id bigint, - f05_deal_proposal jsonb, - f05_deal_start_epoch bigint, - f05_deal_end_epoch bigint, - - -- ddo deal info - -- added in 20240402-sdr-pipeline-ddo-deal-info.sql - -- direct_start_epoch bigint, - -- direct_end_epoch bigint, - -- direct_piece_activation_manifest jsonb, - - -- foreign key - foreign key (sp_id, sector_number) references sectors_sdr_pipeline (sp_id, sector_number) on delete cascade, - - primary key (sp_id, sector_number, piece_index) -); - -comment on column sectors_sdr_initial_pieces.piece_size is 'padded size of the piece'; - -create table sectors_allocated_numbers ( - sp_id bigint not null primary key, - allocated jsonb not null -); diff --git a/lib/harmony/harmonydb/sql/20231225-message-waits.sql b/lib/harmony/harmonydb/sql/20231225-message-waits.sql deleted file mode 100644 index 4143f3a56..000000000 --- a/lib/harmony/harmonydb/sql/20231225-message-waits.sql +++ /dev/null @@ -1,13 +0,0 @@ -create table message_waits ( - signed_message_cid text primary key, - waiter_machine_id int references harmony_machines (id) on delete set null, - - executed_tsk_cid text, - executed_tsk_epoch bigint, - executed_msg_cid text, - executed_msg_data jsonb, - - executed_rcpt_exitcode bigint, - executed_rcpt_return bytea, - executed_rcpt_gas_used bigint -) diff --git a/lib/harmony/harmonydb/sql/20240212-common-layers.sql b/lib/harmony/harmonydb/sql/20240212-common-layers.sql deleted file mode 100644 index cf72e1750..000000000 --- a/lib/harmony/harmonydb/sql/20240212-common-layers.sql +++ /dev/null @@ -1,42 +0,0 @@ -INSERT INTO harmony_config (title, config) VALUES - ('post', ' - [Subsystems] - EnableWindowPost = true - EnableWinningPost = true - '), - - ('gui', ' - [Subsystems] - EnableWebGui = true - '), - - ('seal', ' - [Subsystems] - EnableSealSDR = true - EnableSealSDRTrees = true - EnableSendPrecommitMsg = true - EnablePoRepProof = true - EnableSendCommitMsg = true - EnableMoveStorage = true - '), - - ('seal-gpu', ' - [Subsystems] - EnableSealSDRTrees = true - EnableSendPrecommitMsg = true - '), - ('seal-snark', ' - [Subsystems] - EnablePoRepProof = true - EnableSendCommitMsg = true - '), - ('sdr', ' - [Subsystems] - EnableSealSDR = true - '), - - ('storage', ' - [Subsystems] - EnableMoveStorage = true - ') - ON CONFLICT (title) DO NOTHING; -- SPs may have these names defined already. \ No newline at end of file diff --git a/lib/harmony/harmonydb/sql/20240228-piece-park.sql b/lib/harmony/harmonydb/sql/20240228-piece-park.sql deleted file mode 100644 index 9ee6b447f..000000000 --- a/lib/harmony/harmonydb/sql/20240228-piece-park.sql +++ /dev/null @@ -1,35 +0,0 @@ -create table parked_pieces ( - id bigserial primary key, - created_at timestamp default current_timestamp, - - piece_cid text not null, - piece_padded_size bigint not null, - piece_raw_size bigint not null, - - complete boolean not null default false, - task_id bigint default null, - - cleanup_task_id bigint default null, - - foreign key (task_id) references harmony_task (id) on delete set null, - foreign key (cleanup_task_id) references harmony_task (id) on delete set null, - unique (piece_cid) -); - -/* - * This table is used to keep track of the references to the parked pieces - * so that we can delete them when they are no longer needed. - * - * All references into the parked_pieces table should be done through this table. - * - * data_url is optional for refs which also act as data sources. - */ -create table parked_piece_refs ( - ref_id bigserial primary key, - piece_id bigint not null, - - data_url text, - data_headers jsonb not null default '{}', - - foreign key (piece_id) references parked_pieces(id) on delete cascade -); diff --git a/lib/harmony/harmonydb/sql/20240317-web-summary-index.sql b/lib/harmony/harmonydb/sql/20240317-web-summary-index.sql deleted file mode 100644 index e1c7e7a79..000000000 --- a/lib/harmony/harmonydb/sql/20240317-web-summary-index.sql +++ /dev/null @@ -1,8 +0,0 @@ -/* Used for webui clusterMachineSummary */ --- NOTE: This index is changed in 20240420-web-task-indexes.sql -CREATE INDEX harmony_task_history_work_index - ON harmony_task_history (completed_by_host_and_port ASC, name ASC, result ASC, work_end DESC); - -/* Used for webui actorSummary sp wins */ -CREATE INDEX mining_tasks_won_sp_id_base_compute_time_index - ON mining_tasks (won ASC, sp_id ASC, base_compute_time DESC); diff --git a/lib/harmony/harmonydb/sql/20240402-sdr-pipeline-ddo-deal-info.sql b/lib/harmony/harmonydb/sql/20240402-sdr-pipeline-ddo-deal-info.sql deleted file mode 100644 index 2230ef574..000000000 --- a/lib/harmony/harmonydb/sql/20240402-sdr-pipeline-ddo-deal-info.sql +++ /dev/null @@ -1,8 +0,0 @@ -ALTER TABLE sectors_sdr_initial_pieces - ADD COLUMN direct_start_epoch BIGINT; - -ALTER TABLE sectors_sdr_initial_pieces - ADD COLUMN direct_end_epoch BIGINT; - -ALTER TABLE sectors_sdr_initial_pieces - ADD COLUMN direct_piece_activation_manifest JSONB; diff --git a/lib/harmony/harmonydb/sql/20240404-machine_detail.sql b/lib/harmony/harmonydb/sql/20240404-machine_detail.sql deleted file mode 100644 index ae6de0951..000000000 --- a/lib/harmony/harmonydb/sql/20240404-machine_detail.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE harmony_machine_details ( - id SERIAL PRIMARY KEY, - tasks TEXT, - layers TEXT, - startup_time TIMESTAMP, - miners TEXT, - machine_id INTEGER, - FOREIGN KEY (machine_id) REFERENCES harmony_machines(id) ON DELETE CASCADE -); - -CREATE UNIQUE INDEX machine_details_machine_id ON harmony_machine_details(machine_id); - diff --git a/lib/harmony/harmonydb/sql/20240416-harmony_singleton_task.sql b/lib/harmony/harmonydb/sql/20240416-harmony_singleton_task.sql deleted file mode 100644 index d565cfa47..000000000 --- a/lib/harmony/harmonydb/sql/20240416-harmony_singleton_task.sql +++ /dev/null @@ -1,8 +0,0 @@ -create table harmony_task_singletons ( - task_name varchar(255) not null, - task_id bigint, - last_run_time timestamp, - - primary key (task_name), - foreign key (task_id) references harmony_task (id) on delete set null -); diff --git a/lib/harmony/harmonydb/sql/20240417-sector_index_gc.sql b/lib/harmony/harmonydb/sql/20240417-sector_index_gc.sql deleted file mode 100644 index e9771d9f3..000000000 --- a/lib/harmony/harmonydb/sql/20240417-sector_index_gc.sql +++ /dev/null @@ -1,13 +0,0 @@ -create table sector_path_url_liveness ( - storage_id text, - url text, - - last_checked timestamp not null, - last_live timestamp, - last_dead timestamp, - last_dead_reason text, - - primary key (storage_id, url), - - foreign key (storage_id) references storage_path (storage_id) on delete cascade -) diff --git a/lib/harmony/harmonydb/sql/20240420-web-task-indexes.sql b/lib/harmony/harmonydb/sql/20240420-web-task-indexes.sql deleted file mode 100644 index 33eb9e736..000000000 --- a/lib/harmony/harmonydb/sql/20240420-web-task-indexes.sql +++ /dev/null @@ -1,9 +0,0 @@ -DROP INDEX harmony_task_history_work_index; - -/* - This structure improves clusterMachineSummary query better than the old version, - while at the same time also being usable by clusterTaskHistorySummary (which wasn't - the case with the old index). - */ -create index harmony_task_history_work_index - on harmony_task_history (work_end desc, completed_by_host_and_port asc, name asc, result asc); diff --git a/lib/harmony/harmonydb/userfuncs.go b/lib/harmony/harmonydb/userfuncs.go index 1f39504b8..da9d1a598 100644 --- a/lib/harmony/harmonydb/userfuncs.go +++ b/lib/harmony/harmonydb/userfuncs.go @@ -114,6 +114,10 @@ func (d dbscanRows) Columns() ([]string, error) { }), nil } +func (d dbscanRows) NextResultSet() bool { + return false +} + /* Select multiple rows into a slice using name matching Ex: diff --git a/lib/harmony/harmonytask/doc.go b/lib/harmony/harmonytask/doc.go deleted file mode 100644 index f9e5a9898..000000000 --- a/lib/harmony/harmonytask/doc.go +++ /dev/null @@ -1,71 +0,0 @@ -/* -Package harmonytask implements a pure (no task logic), distributed -task manager. This clean interface allows a task implementer to completely -avoid being concerned with task scheduling and management. -It's based on the idea of tasks as small units of work broken from other -work by hardware, parallelizabilty, reliability, or any other reason. -Workers will be Greedy: vaccuuming up their favorite jobs from a list. -Once 1 task is accepted, harmonydb tries to get other task runner -machines to accept work (round robin) before trying again to accept. -* -Mental Model: - - Things that block tasks: - - task not registered for any running server - - max was specified and reached - - resource exhaustion - - CanAccept() interface (per-task implmentation) does not accept it. - Ways tasks start: - - DB Read every 3 seconds - - Task was added (to db) by this process - Ways tasks get added: - - Async Listener task (for chain, etc) - - Followers: Tasks get added because another task completed - When Follower collectors run: - - If both sides are process-local, then this process will pick it up. - - If properly registered already, the http endpoint will be tried to start it. - - Otherwise, at the listen interval during db scrape it will be found. - How duplicate tasks are avoided: - - that's up to the task definition, but probably a unique key - -* -To use: -1.Implement TaskInterface for a new task. -2. Have New() receive this & all other ACTIVE implementations. -* -* -As we are not expecting DBAs in this database, it's important to know -what grows uncontrolled. The only growing harmony_* table is -harmony_task_history (somewhat quickly). These will need a -clean-up for after the task data could never be acted upon. -but the design **requires** extraInfo tables to grow until the task's -info could not possibly be used by a following task, including slow -release rollout. This would normally be in the order of months old. -* -Other possible enhancements include more collaborative coordination -to assign a task to machines closer to the data. - -__Database_Behavior__ -harmony_task is the list of work that has not been completed. - - AddTaskFunc manages the additions, but is designed to have its - transactions failed-out on overlap with a similar task already written. - It's up to the TaskInterface implementer to discover this overlap via - some other table it uses (since overlap can mean very different things). - -harmony_task_history - - This holds transactions that completed or saw too many retries. It also - serves as input for subsequent (follower) tasks to kick off. This is not - done machine-internally because a follower may not be on the same machine - as the previous task. - -harmony_task_machines - - Managed by lib/harmony/resources, this is a reference to machines registered - via the resources. This registration does not obligate the machine to - anything, but serves as a discovery mechanism. Paths are hostnames + ports - which are presumed to support http, but this assumption is only used by - the task system. -*/ -package harmonytask diff --git a/lib/harmony/harmonytask/harmonytask.go b/lib/harmony/harmonytask/harmonytask.go deleted file mode 100644 index 0c66891d0..000000000 --- a/lib/harmony/harmonytask/harmonytask.go +++ /dev/null @@ -1,387 +0,0 @@ -package harmonytask - -import ( - "context" - "fmt" - "strconv" - "sync/atomic" - "time" - - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/harmony/resources" -) - -// Consts (except for unit test) -var POLL_DURATION = time.Second * 3 // Poll for Work this frequently -var POLL_NEXT_DURATION = 100 * time.Millisecond // After scheduling a task, wait this long before scheduling another -var CLEANUP_FREQUENCY = 5 * time.Minute // Check for dead workers this often * everyone -var FOLLOW_FREQUENCY = 1 * time.Minute // Check for work to follow this often - -type TaskTypeDetails struct { - // Max returns how many tasks this machine can run of this type. - // Zero (default) or less means unrestricted. - Max int - - // Name is the task name to be added to the task list. - Name string - - // Peak costs to Do() the task. - Cost resources.Resources - - // Max Failure count before the job is dropped. - // 0 = retry forever - MaxFailures uint - - // Follow another task's completion via this task's creation. - // The function should populate extraInfo from data - // available from the previous task's tables, using the given TaskID. - // It should also return success if the trigger succeeded. - // NOTE: if refatoring tasks, see if your task is - // necessary. Ex: Is the sector state correct for your stage to run? - Follows map[string]func(TaskID, AddTaskFunc) (bool, error) - - // IAmBored is called (when populated) when there's capacity but no work. - // Tasks added will be proposed to CanAccept() on this machine. - // CanAccept() can read taskEngine's WorkOrigin string to learn about a task. - // Ex: make new CC sectors, clean-up, or retrying pipelines that failed in later states. - IAmBored func(AddTaskFunc) error -} - -// TaskInterface must be implemented in order to have a task used by harmonytask. -type TaskInterface interface { - // Do the task assigned. Call stillOwned before making single-writer-only - // changes to ensure the work has not been stolen. - // This is the ONLY function that should attempt to do the work, and must - // ONLY be called by harmonytask. - // Indicate if the task no-longer needs scheduling with done=true including - // cases where it's past the deadline. - Do(taskID TaskID, stillOwned func() bool) (done bool, err error) - - // CanAccept should return if the task can run on this machine. It should - // return null if the task type is not allowed on this machine. - // It should select the task it most wants to accomplish. - // It is also responsible for determining & reserving disk space (including scratch). - CanAccept([]TaskID, *TaskEngine) (*TaskID, error) - - // TypeDetails() returns static details about how this task behaves and - // how this machine will run it. Read once at the beginning. - TypeDetails() TaskTypeDetails - - // This listener will consume all external sources continuously for work. - // Do() may also be called from a backlog of work. This must not - // start doing the work (it still must be scheduled). - // Note: Task de-duplication should happen in ExtraInfoFunc by - // returning false, typically by determining from the tx that the work - // exists already. The easy way is to have a unique joint index - // across all fields that will be common. - // Adder should typically only add its own task type, but multiple - // is possible for when 1 trigger starts 2 things. - // Usage Example: - // func (b *BazType)Adder(addTask AddTaskFunc) { - // for { - // bazMaker := <- bazChannel - // addTask("baz", func(t harmonytask.TaskID, txn db.Transaction) (bool, error) { - // _, err := txn.Exec(`INSERT INTO bazInfoTable (taskID, qix, mot) - // VALUES ($1,$2,$3)`, id, bazMaker.qix, bazMaker.mot) - // if err != nil { - // scream(err) - // return false - // } - // return true - // }) - // } - // } - Adder(AddTaskFunc) -} - -// AddTaskFunc is responsible for adding a task's details "extra info" to the DB. -// It should return true if the task should be added, false if it was already there. -// This is typically accomplished with a "unique" index on your detals table that -// would cause the insert to fail. -// The error indicates that instead of a conflict (which we should ignore) that we -// actually have a serious problem that needs to be logged with context. -type AddTaskFunc func(extraInfo func(TaskID, *harmonydb.Tx) (shouldCommit bool, seriousError error)) - -type TaskEngine struct { - // Static After New() - ctx context.Context - handlers []*taskTypeHandler - db *harmonydb.DB - reg *resources.Reg - grace context.CancelFunc - taskMap map[string]*taskTypeHandler - ownerID int - follows map[string][]followStruct - hostAndPort string - - // synchronous to the single-threaded poller - lastFollowTime time.Time - lastCleanup atomic.Value - WorkOrigin string -} -type followStruct struct { - f func(TaskID, AddTaskFunc) (bool, error) - h *taskTypeHandler - name string -} - -type TaskID int - -// New creates all the task definitions. Note that TaskEngine -// knows nothing about the tasks themselves and serves to be a -// generic container for common work -func New( - db *harmonydb.DB, - impls []TaskInterface, - hostnameAndPort string) (*TaskEngine, error) { - - reg, err := resources.Register(db, hostnameAndPort) - if err != nil { - return nil, fmt.Errorf("cannot get resources: %w", err) - } - ctx, grace := context.WithCancel(context.Background()) - e := &TaskEngine{ - ctx: ctx, - grace: grace, - db: db, - reg: reg, - ownerID: reg.Resources.MachineID, // The current number representing "hostAndPort" - taskMap: make(map[string]*taskTypeHandler, len(impls)), - follows: make(map[string][]followStruct), - hostAndPort: hostnameAndPort, - } - e.lastCleanup.Store(time.Now()) - for _, c := range impls { - h := taskTypeHandler{ - TaskInterface: c, - TaskTypeDetails: c.TypeDetails(), - TaskEngine: e, - } - - if len(h.Name) > 16 { - return nil, fmt.Errorf("task name too long: %s, max 16 characters", h.Name) - } - - e.handlers = append(e.handlers, &h) - e.taskMap[h.TaskTypeDetails.Name] = &h - } - - // resurrect old work - { - var taskRet []struct { - ID int - Name string - } - - err := db.Select(e.ctx, &taskRet, `SELECT id, name from harmony_task WHERE owner_id=$1`, e.ownerID) - if err != nil { - return nil, err - } - for _, w := range taskRet { - // edge-case: if old assignments are not available tasks, unlock them. - h := e.taskMap[w.Name] - if h == nil { - _, err := db.Exec(e.ctx, `UPDATE harmony_task SET owner=NULL WHERE id=$1`, w.ID) - if err != nil { - log.Errorw("Cannot remove self from owner field", "error", err) - continue // not really fatal, but not great - } - } - if !h.considerWork(WorkSourceRecover, []TaskID{TaskID(w.ID)}) { - log.Errorw("Strange: Unable to accept previously owned task", "id", w.ID, "type", w.Name) - } - } - } - for _, h := range e.handlers { - go h.Adder(h.AddTask) - } - go e.poller() - - return e, nil -} - -// GracefullyTerminate hangs until all present tasks have completed. -// Call this to cleanly exit the process. As some processes are long-running, -// passing a deadline will ignore those still running (to be picked-up later). -func (e *TaskEngine) GracefullyTerminate() { - - // call the cancel func to avoid picking up any new tasks. Running tasks have context.Background() - // Call shutdown to stop posting heartbeat to DB. - e.grace() - e.reg.Shutdown() - - // If there are any Post tasks then wait till Timeout and check again - // When no Post tasks are active, break out of loop and call the shutdown function - for { - timeout := time.Millisecond - for _, h := range e.handlers { - if h.TaskTypeDetails.Name == "WinPost" && h.Count.Load() > 0 { - timeout = time.Second - log.Infof("node shutdown deferred for %f seconds", timeout.Seconds()) - continue - } - if h.TaskTypeDetails.Name == "WdPost" && h.Count.Load() > 0 { - timeout = time.Second * 3 - log.Infof("node shutdown deferred for %f seconds due to running WdPost task", timeout.Seconds()) - continue - } - - if h.TaskTypeDetails.Name == "WdPostSubmit" && h.Count.Load() > 0 { - timeout = time.Second - log.Infof("node shutdown deferred for %f seconds due to running WdPostSubmit task", timeout.Seconds()) - continue - } - - if h.TaskTypeDetails.Name == "WdPostRecover" && h.Count.Load() > 0 { - timeout = time.Second - log.Infof("node shutdown deferred for %f seconds due to running WdPostRecover task", timeout.Seconds()) - continue - } - - // Test tasks for itest - if h.TaskTypeDetails.Name == "ThingOne" && h.Count.Load() > 0 { - timeout = time.Second - log.Infof("node shutdown deferred for %f seconds due to running itest task", timeout.Seconds()) - continue - } - } - if timeout > time.Millisecond { - time.Sleep(timeout) - continue - } - break - } - - return -} - -func (e *TaskEngine) poller() { - nextWait := POLL_NEXT_DURATION - for { - select { - case <-time.After(nextWait): // Find work periodically - case <-e.ctx.Done(): ///////////////////// Graceful exit - return - } - nextWait = POLL_DURATION - - accepted := e.pollerTryAllWork() - if accepted { - nextWait = POLL_NEXT_DURATION - } - if time.Since(e.lastFollowTime) > FOLLOW_FREQUENCY { - e.followWorkInDB() - } - } -} - -// followWorkInDB implements "Follows" -func (e *TaskEngine) followWorkInDB() { - // Step 1: What are we following? - var lastFollowTime time.Time - lastFollowTime, e.lastFollowTime = e.lastFollowTime, time.Now() - - for fromName, srcs := range e.follows { - var cList []int // Which work is done (that we follow) since we last checked? - err := e.db.Select(e.ctx, &cList, `SELECT h.task_id FROM harmony_task_history - WHERE h.work_end>$1 AND h.name=$2`, lastFollowTime.UTC(), fromName) - if err != nil { - log.Error("Could not query DB: ", err) - return - } - for _, src := range srcs { - for _, workAlreadyDone := range cList { // Were any tasks made to follow these tasks? - var ct int - err := e.db.QueryRow(e.ctx, `SELECT COUNT(*) FROM harmony_task - WHERE name=$1 AND previous_task=$2`, src.h.Name, workAlreadyDone).Scan(&ct) - if err != nil { - log.Error("Could not query harmony_task: ", err) - return // not recoverable here - } - if ct > 0 { - continue - } - // we need to create this task - b, err := src.h.Follows[fromName](TaskID(workAlreadyDone), src.h.AddTask) - if err != nil { - log.Errorw("Could not follow: ", "error", err) - continue - } - if !b { - // But someone may have beaten us to it. - log.Debugf("Unable to add task %s following Task(%d, %s)", src.h.Name, workAlreadyDone, fromName) - } - } - } - } -} - -// pollerTryAllWork starts the next 1 task -func (e *TaskEngine) pollerTryAllWork() bool { - if time.Since(e.lastCleanup.Load().(time.Time)) > CLEANUP_FREQUENCY { - e.lastCleanup.Store(time.Now()) - resources.CleanupMachines(e.ctx, e.db) - } - for _, v := range e.handlers { - if err := v.AssertMachineHasCapacity(); err != nil { - log.Debugf("skipped scheduling %s type tasks on due to %s", v.Name, err.Error()) - continue - } - var unownedTasks []TaskID - err := e.db.Select(e.ctx, &unownedTasks, `SELECT id - FROM harmony_task - WHERE owner_id IS NULL AND name=$1 - ORDER BY update_time`, v.Name) - if err != nil { - log.Error("Unable to read work ", err) - continue - } - if len(unownedTasks) > 0 { - accepted := v.considerWork(WorkSourcePoller, unownedTasks) - if accepted { - return true // accept new work slowly and in priority order - } - log.Warn("Work not accepted for " + strconv.Itoa(len(unownedTasks)) + " " + v.Name + " task(s)") - } - } - // if no work was accepted, are we bored? Then find work in priority order. - for _, v := range e.handlers { - v := v - if v.AssertMachineHasCapacity() != nil { - continue - } - if v.TaskTypeDetails.IAmBored != nil { - var added []TaskID - err := v.TaskTypeDetails.IAmBored(func(extraInfo func(TaskID, *harmonydb.Tx) (shouldCommit bool, seriousError error)) { - v.AddTask(func(tID TaskID, tx *harmonydb.Tx) (shouldCommit bool, seriousError error) { - b, err := extraInfo(tID, tx) - if err == nil { - added = append(added, tID) - } - return b, err - }) - }) - if err != nil { - log.Error("IAmBored failed: ", err) - continue - } - if added != nil { // tiny chance a fail could make these bogus, but considerWork should then fail. - v.considerWork(WorkSourceIAmBored, added) - } - } - } - - return false -} - -// ResourcesAvailable determines what resources are still unassigned. -func (e *TaskEngine) ResourcesAvailable() resources.Resources { - tmp := e.reg.Resources - for _, t := range e.handlers { - ct := t.Count.Load() - tmp.Cpu -= int(ct) * t.Cost.Cpu - tmp.Gpu -= float64(ct) * t.Cost.Gpu - tmp.Ram -= uint64(ct) * t.Cost.Ram - } - return tmp -} diff --git a/lib/harmony/harmonytask/singleton_task.go b/lib/harmony/harmonytask/singleton_task.go deleted file mode 100644 index 2395f343c..000000000 --- a/lib/harmony/harmonytask/singleton_task.go +++ /dev/null @@ -1,52 +0,0 @@ -package harmonytask - -import ( - "errors" - "time" - - "github.com/yugabyte/pgx/v5" - - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/lib/passcall" -) - -func SingletonTaskAdder(minInterval time.Duration, task TaskInterface) func(AddTaskFunc) error { - return passcall.Every(minInterval, func(add AddTaskFunc) error { - taskName := task.TypeDetails().Name - - add(func(taskID TaskID, tx *harmonydb.Tx) (shouldCommit bool, err error) { - var existingTaskID *int64 - var lastRunTime time.Time - - // Query to check the existing task entry - err = tx.QueryRow(`SELECT task_id, last_run_time FROM harmony_task_singletons WHERE task_name = $1`, taskName).Scan(&existingTaskID, &lastRunTime) - if err != nil { - if !errors.Is(err, pgx.ErrNoRows) { - return false, err // return error if query failed and it's not because of missing row - } - } - - now := time.Now().UTC() - // Determine if the task should run based on the absence of a record or outdated last_run_time - shouldRun := err == pgx.ErrNoRows || (existingTaskID == nil && lastRunTime.Add(minInterval).Before(now)) - if !shouldRun { - return false, nil - } - - // Conditionally insert or update the task entry - n, err := tx.Exec(` - INSERT INTO harmony_task_singletons (task_name, task_id, last_run_time) - VALUES ($1, $2, $3) - ON CONFLICT (task_name) DO UPDATE - SET task_id = COALESCE(harmony_task_singletons.task_id, $2), - last_run_time = $3 - WHERE harmony_task_singletons.task_id IS NULL - `, taskName, taskID, now) - if err != nil { - return false, err - } - return n > 0, nil - }) - return nil - }) -} diff --git a/lib/harmony/harmonytask/task_type_handler.go b/lib/harmony/harmonytask/task_type_handler.go deleted file mode 100644 index a8c6e58b8..000000000 --- a/lib/harmony/harmonytask/task_type_handler.go +++ /dev/null @@ -1,295 +0,0 @@ -package harmonytask - -import ( - "context" - "errors" - "fmt" - "runtime" - "strconv" - "sync/atomic" - "time" - - logging "github.com/ipfs/go-log/v2" - - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" -) - -var log = logging.Logger("harmonytask") - -type taskTypeHandler struct { - TaskInterface - TaskTypeDetails - TaskEngine *TaskEngine - Count atomic.Int32 -} - -func (h *taskTypeHandler) AddTask(extra func(TaskID, *harmonydb.Tx) (bool, error)) { - var tID TaskID - retryWait := time.Millisecond * 100 -retryAddTask: - _, err := h.TaskEngine.db.BeginTransaction(h.TaskEngine.ctx, func(tx *harmonydb.Tx) (bool, error) { - // create taskID (from DB) - err := tx.QueryRow(`INSERT INTO harmony_task (name, added_by, posted_time) - VALUES ($1, $2, CURRENT_TIMESTAMP) RETURNING id`, h.Name, h.TaskEngine.ownerID).Scan(&tID) - if err != nil { - return false, fmt.Errorf("could not insert into harmonyTask: %w", err) - } - return extra(tID, tx) - }) - - if err != nil { - if harmonydb.IsErrUniqueContraint(err) { - log.Debugf("addtask(%s) saw unique constraint, so it's added already.", h.Name) - return - } - if harmonydb.IsErrSerialization(err) { - time.Sleep(retryWait) - retryWait *= 2 - goto retryAddTask - } - log.Errorw("Could not add task. AddTasFunc failed", "error", err, "type", h.Name) - return - } -} - -const ( - WorkSourcePoller = "poller" - WorkSourceRecover = "recovered" - WorkSourceIAmBored = "bored" -) - -// considerWork is called to attempt to start work on a task-id of this task type. -// It presumes single-threaded calling, so there should not be a multi-threaded re-entry. -// The only caller should be the one work poller thread. This does spin off other threads, -// but those should not considerWork. Work completing may lower the resource numbers -// unexpectedly, but that will not invalidate work being already able to fit. -func (h *taskTypeHandler) considerWork(from string, ids []TaskID) (workAccepted bool) { -top: - if len(ids) == 0 { - return true // stop looking for takers - } - - // 1. Can we do any more of this task type? - // NOTE: 0 is the default value, so this way people don't need to worry about - // this setting unless they want to limit the number of tasks of this type. - if h.Max > 0 && int(h.Count.Load()) >= h.Max { - log.Debugw("did not accept task", "name", h.Name, "reason", "at max already") - return false - } - - // 2. Can we do any more work? From here onward, we presume the resource - // story will not change, so single-threaded calling is best. - err := h.AssertMachineHasCapacity() - if err != nil { - log.Debugw("did not accept task", "name", h.Name, "reason", "at capacity already: "+err.Error()) - return false - } - - h.TaskEngine.WorkOrigin = from - - // 3. What does the impl say? -canAcceptAgain: - tID, err := h.CanAccept(ids, h.TaskEngine) - - h.TaskEngine.WorkOrigin = "" - - if err != nil { - log.Error(err) - return false - } - if tID == nil { - log.Infow("did not accept task", "task_id", ids[0], "reason", "CanAccept() refused", "name", h.Name) - return false - } - - releaseStorage := func() { - } - if h.TaskTypeDetails.Cost.Storage != nil { - if err = h.TaskTypeDetails.Cost.Storage.Claim(int(*tID)); err != nil { - log.Infow("did not accept task", "task_id", strconv.Itoa(int(*tID)), "reason", "storage claim failed", "name", h.Name, "error", err) - - if len(ids) > 1 { - var tryAgain = make([]TaskID, 0, len(ids)-1) - for _, id := range ids { - if id != *tID { - tryAgain = append(tryAgain, id) - } - } - ids = tryAgain - goto canAcceptAgain - } - - return false - } - releaseStorage = func() { - if err := h.TaskTypeDetails.Cost.Storage.MarkComplete(int(*tID)); err != nil { - log.Errorw("Could not release storage", "error", err) - } - } - } - - // if recovering we don't need to try to claim anything because those tasks are already claimed by us - if from != WorkSourceRecover { - // 4. Can we claim the work for our hostname? - ct, err := h.TaskEngine.db.Exec(h.TaskEngine.ctx, "UPDATE harmony_task SET owner_id=$1 WHERE id=$2 AND owner_id IS NULL", h.TaskEngine.ownerID, *tID) - if err != nil { - log.Error(err) - - releaseStorage() - return false - } - if ct == 0 { - log.Infow("did not accept task", "task_id", strconv.Itoa(int(*tID)), "reason", "already Taken", "name", h.Name) - releaseStorage() - - var tryAgain = make([]TaskID, 0, len(ids)-1) - for _, id := range ids { - if id != *tID { - tryAgain = append(tryAgain, id) - } - } - ids = tryAgain - goto top - } - } - - h.Count.Add(1) - go func() { - log.Infow("Beginning work on Task", "id", *tID, "from", from, "name", h.Name) - - var done bool - var doErr error - workStart := time.Now() - - defer func() { - if r := recover(); r != nil { - stackSlice := make([]byte, 4092) - sz := runtime.Stack(stackSlice, false) - log.Error("Recovered from a serious error "+ - "while processing "+h.Name+" task "+strconv.Itoa(int(*tID))+": ", r, - " Stack: ", string(stackSlice[:sz])) - } - h.Count.Add(-1) - - releaseStorage() - h.recordCompletion(*tID, workStart, done, doErr) - if done { - for _, fs := range h.TaskEngine.follows[h.Name] { // Do we know of any follows for this task type? - if _, err := fs.f(*tID, fs.h.AddTask); err != nil { - log.Error("Could not follow", "error", err, "from", h.Name, "to", fs.name) - } - } - } - }() - - done, doErr = h.Do(*tID, func() bool { - var owner int - // Background here because we don't want GracefulRestart to block this save. - err := h.TaskEngine.db.QueryRow(context.Background(), - `SELECT owner_id FROM harmony_task WHERE id=$1`, *tID).Scan(&owner) - if err != nil { - log.Error("Cannot determine ownership: ", err) - return false - } - return owner == h.TaskEngine.ownerID - }) - if doErr != nil { - log.Errorw("Do() returned error", "type", h.Name, "id", strconv.Itoa(int(*tID)), "error", doErr) - } - }() - return true -} - -func (h *taskTypeHandler) recordCompletion(tID TaskID, workStart time.Time, done bool, doErr error) { - workEnd := time.Now() - retryWait := time.Millisecond * 100 -retryRecordCompletion: - cm, err := h.TaskEngine.db.BeginTransaction(h.TaskEngine.ctx, func(tx *harmonydb.Tx) (bool, error) { - var postedTime time.Time - err := tx.QueryRow(`SELECT posted_time FROM harmony_task WHERE id=$1`, tID).Scan(&postedTime) - - if err != nil { - return false, fmt.Errorf("could not log completion: %w ", err) - } - result := "unspecified error" - if done { - _, err = tx.Exec("DELETE FROM harmony_task WHERE id=$1", tID) - if err != nil { - - return false, fmt.Errorf("could not log completion: %w", err) - } - result = "" - if doErr != nil { - result = "non-failing error: " + doErr.Error() - } - } else { - if doErr != nil { - result = "error: " + doErr.Error() - } - var deleteTask bool - if h.MaxFailures > 0 { - ct := uint(0) - err = tx.QueryRow(`SELECT count(*) FROM harmony_task_history - WHERE task_id=$1 AND result=FALSE`, tID).Scan(&ct) - if err != nil { - return false, fmt.Errorf("could not read task history: %w", err) - } - if ct >= h.MaxFailures { - deleteTask = true - } - } - if deleteTask { - _, err = tx.Exec("DELETE FROM harmony_task WHERE id=$1", tID) - if err != nil { - return false, fmt.Errorf("could not delete failed job: %w", err) - } - // Note: Extra Info is left laying around for later review & clean-up - } else { - _, err := tx.Exec(`UPDATE harmony_task SET owner_id=NULL WHERE id=$1`, tID) - if err != nil { - return false, fmt.Errorf("could not disown failed task: %v %v", tID, err) - } - } - } - _, err = tx.Exec(`INSERT INTO harmony_task_history - (task_id, name, posted, work_start, work_end, result, completed_by_host_and_port, err) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, tID, h.Name, postedTime.UTC(), workStart.UTC(), workEnd.UTC(), done, h.TaskEngine.hostAndPort, result) - if err != nil { - return false, fmt.Errorf("could not write history: %w", err) - } - return true, nil - }) - if err != nil { - if harmonydb.IsErrSerialization(err) { - time.Sleep(retryWait) - retryWait *= 2 - goto retryRecordCompletion - } - log.Error("Could not record transaction: ", err) - return - } - if !cm { - log.Error("Committing the task records failed") - } -} - -func (h *taskTypeHandler) AssertMachineHasCapacity() error { - r := h.TaskEngine.ResourcesAvailable() - - if r.Cpu-h.Cost.Cpu < 0 { - return errors.New("Did not accept " + h.Name + " task: out of cpu") - } - if h.Cost.Ram > r.Ram { - return errors.New("Did not accept " + h.Name + " task: out of RAM") - } - if r.Gpu-h.Cost.Gpu < 0 { - return errors.New("Did not accept " + h.Name + " task: out of available GPU") - } - - if h.TaskTypeDetails.Cost.Storage != nil { - if !h.TaskTypeDetails.Cost.Storage.HasCapacity() { - return errors.New("Did not accept " + h.Name + " task: out of available Storage") - } - } - return nil -} diff --git a/lib/harmony/resources/getGPU.go b/lib/harmony/resources/getGPU.go deleted file mode 100644 index 62d5c091e..000000000 --- a/lib/harmony/resources/getGPU.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build !darwin -// +build !darwin - -package resources - -import ( - "os" - "strconv" - "strings" - - ffi "github.com/filecoin-project/filecoin-ffi" -) - -func getGPUDevices() float64 { // GPU boolean - if nstr := os.Getenv("HARMONY_OVERRIDE_GPUS"); nstr != "" { - n, err := strconv.ParseFloat(nstr, 64) - if err != nil { - logger.Errorf("parsing HARMONY_OVERRIDE_GPUS failed: %+v", err) - } else { - return n - } - } - - gpus, err := ffi.GetGPUDevices() - logger.Infow("GPUs", "list", gpus) - if err != nil { - logger.Errorf("getting gpu devices failed: %+v", err) - } - all := strings.ToLower(strings.Join(gpus, ",")) - if len(gpus) > 1 || strings.Contains(all, "ati") || strings.Contains(all, "nvidia") { - return float64(len(gpus)) - } - return 0 -} diff --git a/lib/harmony/resources/getGPU_darwin.go b/lib/harmony/resources/getGPU_darwin.go deleted file mode 100644 index a9c0a33cd..000000000 --- a/lib/harmony/resources/getGPU_darwin.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build darwin -// +build darwin - -package resources - -func getGPUDevices() float64 { - return 10000.0 // unserious value intended for non-production use. -} diff --git a/lib/harmony/resources/memsys.go b/lib/harmony/resources/memsys.go deleted file mode 100644 index 1a45b5b22..000000000 --- a/lib/harmony/resources/memsys.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build darwin || freebsd || openbsd || dragonfly || netbsd -// +build darwin freebsd openbsd dragonfly netbsd - -package resources - -import ( - "encoding/binary" - "syscall" -) - -func sysctlUint64(name string) (uint64, error) { - s, err := syscall.Sysctl(name) - if err != nil { - return 0, err - } - // hack because the string conversion above drops a \0 - b := []byte(s) - if len(b) < 8 { - b = append(b, 0) - } - return binary.LittleEndian.Uint64(b), nil -} diff --git a/lib/harmony/resources/miniopencl/cl.h b/lib/harmony/resources/miniopencl/cl.h deleted file mode 100644 index e90fb7692..000000000 --- a/lib/harmony/resources/miniopencl/cl.h +++ /dev/null @@ -1,17 +0,0 @@ - -#ifndef CL_H -#define CL_H - -#define CL_USE_DEPRECATED_OPENCL_1_1_APIS -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#define CL_USE_DEPRECATED_OPENCL_2_0_APIS - -#define CL_TARGET_OPENCL_VERSION 300 - -#ifdef __APPLE__ -#include "OpenCL/opencl.h" -#else -#include "CL/opencl.h" -#endif - -#endif /* CL_H */ \ No newline at end of file diff --git a/lib/harmony/resources/miniopencl/mini_opencl.go b/lib/harmony/resources/miniopencl/mini_opencl.go deleted file mode 100644 index d2486a88f..000000000 --- a/lib/harmony/resources/miniopencl/mini_opencl.go +++ /dev/null @@ -1,93 +0,0 @@ -// Package cl was borrowed from the go-opencl library which is more complex and -// doesn't compile well for our needs. -package cl - -// #include "cl.h" -import "C" -import ( - "fmt" - "unsafe" -) - -const maxPlatforms = 32 - -type Platform struct { - id C.cl_platform_id -} - -// Obtain the list of platforms available. -func GetPlatforms() ([]*Platform, error) { - var platformIds [maxPlatforms]C.cl_platform_id - var nPlatforms C.cl_uint - err := C.clGetPlatformIDs(C.cl_uint(maxPlatforms), &platformIds[0], &nPlatforms) - if err == -1001 { // No platforms found - return nil, nil - } - if err != C.CL_SUCCESS { - return nil, toError(err) - } - platforms := make([]*Platform, nPlatforms) - for i := 0; i < int(nPlatforms); i++ { - platforms[i] = &Platform{id: platformIds[i]} - } - return platforms, nil -} - -const maxDeviceCount = 64 - -type DeviceType uint - -const ( - DeviceTypeAll DeviceType = C.CL_DEVICE_TYPE_ALL -) - -type Device struct { - id C.cl_device_id -} - -func (p *Platform) GetAllDevices() ([]*Device, error) { - var deviceIds [maxDeviceCount]C.cl_device_id - var numDevices C.cl_uint - var platformId C.cl_platform_id - if p != nil { - platformId = p.id - } - if err := C.clGetDeviceIDs(platformId, C.cl_device_type(DeviceTypeAll), C.cl_uint(maxDeviceCount), &deviceIds[0], &numDevices); err != C.CL_SUCCESS { - return nil, toError(err) - } - if numDevices > maxDeviceCount { - numDevices = maxDeviceCount - } - devices := make([]*Device, numDevices) - for i := 0; i < int(numDevices); i++ { - devices[i] = &Device{id: deviceIds[i]} - } - return devices, nil -} - -func toError(code C.cl_int) error { - return ErrOther(code) -} - -type ErrOther int - -func (e ErrOther) Error() string { - return fmt.Sprintf("OpenCL: error %d", int(e)) -} - -// Size of global device memory in bytes. -func (d *Device) GlobalMemSize() int64 { - val, _ := d.getInfoUlong(C.CL_DEVICE_GLOBAL_MEM_SIZE, true) - return val -} - -func (d *Device) getInfoUlong(param C.cl_device_info, panicOnError bool) (int64, error) { - var val C.cl_ulong - if err := C.clGetDeviceInfo(d.id, param, C.size_t(unsafe.Sizeof(val)), unsafe.Pointer(&val), nil); err != C.CL_SUCCESS { - if panicOnError { - panic("Should never fail") - } - return 0, toError(err) - } - return int64(val), nil -} diff --git a/lib/harmony/resources/resources.go b/lib/harmony/resources/resources.go deleted file mode 100644 index 33bc80d6f..000000000 --- a/lib/harmony/resources/resources.go +++ /dev/null @@ -1,165 +0,0 @@ -package resources - -import ( - "bytes" - "context" - "os/exec" - "regexp" - "runtime" - "sync/atomic" - "time" - - "github.com/elastic/go-sysinfo" - logging "github.com/ipfs/go-log/v2" - "golang.org/x/sys/unix" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/lib/harmony/harmonydb" -) - -var LOOKS_DEAD_TIMEOUT = 10 * time.Minute // Time w/o minute heartbeats - -type Resources struct { - Cpu int - Gpu float64 - Ram uint64 - MachineID int - Storage -} - -// Optional Storage management. -type Storage interface { - HasCapacity() bool - - // This allows some other system to claim space for this task. - Claim(taskID int) error - - // This allows some other system to consider the task done. - // It's up to the caller to remove the data, if that applies. - MarkComplete(taskID int) error -} -type Reg struct { - Resources - shutdown atomic.Bool -} - -var logger = logging.Logger("harmonytask") - -var lotusRE = regexp.MustCompile("lotus-worker|lotus-harmony|yugabyted|yb-master|yb-tserver") - -func Register(db *harmonydb.DB, hostnameAndPort string) (*Reg, error) { - var reg Reg - var err error - reg.Resources, err = getResources() - if err != nil { - return nil, err - } - ctx := context.Background() - { // Learn our owner_id while updating harmony_machines - var ownerID *int - - // Upsert query with last_contact update, fetch the machine ID - // (note this isn't a simple insert .. on conflict because host_and_port isn't unique) - err := db.QueryRow(ctx, ` - WITH upsert AS ( - UPDATE harmony_machines - SET cpu = $2, ram = $3, gpu = $4, last_contact = CURRENT_TIMESTAMP - WHERE host_and_port = $1 - RETURNING id - ), - inserted AS ( - INSERT INTO harmony_machines (host_and_port, cpu, ram, gpu, last_contact) - SELECT $1, $2, $3, $4, CURRENT_TIMESTAMP - WHERE NOT EXISTS (SELECT id FROM upsert) - RETURNING id - ) - SELECT id FROM upsert - UNION ALL - SELECT id FROM inserted; - `, hostnameAndPort, reg.Cpu, reg.Ram, reg.Gpu).Scan(&ownerID) - if err != nil { - return nil, xerrors.Errorf("inserting machine entry: %w", err) - } - if ownerID == nil { - return nil, xerrors.Errorf("no owner id") - } - - reg.MachineID = *ownerID - - cleaned := CleanupMachines(context.Background(), db) - logger.Infow("Cleaned up machines", "count", cleaned) - } - go func() { - for { - time.Sleep(time.Minute) - if reg.shutdown.Load() { - return - } - _, err := db.Exec(ctx, `UPDATE harmony_machines SET last_contact=CURRENT_TIMESTAMP where id=$1`, reg.MachineID) - if err != nil { - logger.Error("Cannot keepalive ", err) - } - } - }() - - return ®, nil -} - -func CleanupMachines(ctx context.Context, db *harmonydb.DB) int { - ct, err := db.Exec(ctx, - `DELETE FROM harmony_machines WHERE last_contact < CURRENT_TIMESTAMP - INTERVAL '1 MILLISECOND' * $1 `, - LOOKS_DEAD_TIMEOUT.Milliseconds()) // ms enables unit testing to change timeout. - if err != nil { - logger.Warn("unable to delete old machines: ", err) - } - return ct -} - -func (res *Reg) Shutdown() { - res.shutdown.Store(true) -} - -func getResources() (res Resources, err error) { - b, err := exec.Command(`ps`, `-ef`).CombinedOutput() - if err != nil { - logger.Warn("Could not safety check for 2+ processes: ", err) - } else { - found := 0 - for _, b := range bytes.Split(b, []byte("\n")) { - if lotusRE.Match(b) { - found++ - } - } - if found > 1 { - logger.Warn("curio's defaults are for running alone. Use task maximums or CGroups.") - } - } - - h, err := sysinfo.Host() - if err != nil { - return Resources{}, err - } - - mem, err := h.Memory() - if err != nil { - return Resources{}, err - } - - res = Resources{ - Cpu: runtime.NumCPU(), - Ram: mem.Available, - Gpu: getGPUDevices(), - } - - return res, nil -} - -func DiskFree(path string) (uint64, error) { - s := unix.Statfs_t{} - err := unix.Statfs(path, &s) - if err != nil { - return 0, err - } - - return s.Bfree * uint64(s.Bsize), nil -} diff --git a/lib/harmony/taskhelp/common.go b/lib/harmony/taskhelp/common.go deleted file mode 100644 index eaeb4a1bf..000000000 --- a/lib/harmony/taskhelp/common.go +++ /dev/null @@ -1,19 +0,0 @@ -package taskhelp - -// SubsetIf returns a subset of the slice for which the predicate is true. -// It does not allocate memory, but rearranges the list in place. -// A non-zero list input will always return a non-zero list. -// The return value is the subset and a boolean indicating whether the subset was sliced. -func SliceIfFound[T any](slice []T, f func(T) bool) ([]T, bool) { - ct := 0 - for i, v := range slice { - if f(v) { - slice[ct], slice[i] = slice[i], slice[ct] - ct++ - } - } - if ct == 0 { - return slice, false - } - return slice[:ct], true -} diff --git a/lib/passcall/every.go b/lib/passcall/every.go deleted file mode 100644 index f39543063..000000000 --- a/lib/passcall/every.go +++ /dev/null @@ -1,28 +0,0 @@ -package passcall - -import ( - "sync" - "time" -) - -// Every is a helper function that will call the provided callback -// function at most once every `passEvery` duration. If the function is called -// more frequently than that, it will return nil and not call the callback. -func Every[P, R any](passInterval time.Duration, cb func(P) R) func(P) R { - var lastCall time.Time - var lk sync.Mutex - - return func(param P) R { - lk.Lock() - defer lk.Unlock() - - if time.Since(lastCall) < passInterval { - return *new(R) - } - - defer func() { - lastCall = time.Now() - }() - return cb(param) - } -} diff --git a/lib/unixfs/filestore.go b/lib/unixfs/filestore.go deleted file mode 100644 index f50e933b6..000000000 --- a/lib/unixfs/filestore.go +++ /dev/null @@ -1,159 +0,0 @@ -package unixfs - -import ( - "context" - "fmt" - "io" - "os" - - "github.com/ipfs/boxo/blockservice" - bstore "github.com/ipfs/boxo/blockstore" - chunker "github.com/ipfs/boxo/chunker" - offline "github.com/ipfs/boxo/exchange/offline" - "github.com/ipfs/boxo/files" - "github.com/ipfs/boxo/ipld/merkledag" - "github.com/ipfs/boxo/ipld/unixfs/importer/balanced" - ihelper "github.com/ipfs/boxo/ipld/unixfs/importer/helpers" - "github.com/ipfs/go-cid" - "github.com/ipfs/go-cidutil" - ipld "github.com/ipfs/go-ipld-format" - mh "github.com/multiformats/go-multihash" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-fil-markets/stores" - - "github.com/filecoin-project/lotus/build" -) - -var DefaultHashFunction = uint64(mh.BLAKE2B_MIN + 31) - -func CidBuilder() (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 -} - -// CreateFilestore 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 CreateFilestore(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 := os.CreateTemp("", "") - 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 := Build(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 := Build(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 -} - -// Build builds a UnixFS DAG out of the supplied reader, -// and imports the DAG into the supplied service. -func Build(ctx context.Context, reader io.Reader, into bstore.Blockstore, filestore bool) (cid.Cid, error) { - b, err := CidBuilder() - 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/lib/unixfs/filestore_test.go b/lib/unixfs/filestore_test.go deleted file mode 100644 index 868698bce..000000000 --- a/lib/unixfs/filestore_test.go +++ /dev/null @@ -1,128 +0,0 @@ -// stm: #unit -package unixfs - -import ( - "bytes" - "context" - "io" - "os" - "strings" - "testing" - - "github.com/ipfs/boxo/blockservice" - offline "github.com/ipfs/boxo/exchange/offline" - "github.com/ipfs/boxo/files" - "github.com/ipfs/boxo/ipld/merkledag" - unixfile "github.com/ipfs/boxo/ipld/unixfs/file" - "github.com/ipfs/go-cid" - 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" -) - -// This test uses a full "dense" CARv2, and not a filestore (positional mapping). -func TestRoundtripUnixFS_Dense(t *testing.T) { - // stm: @CLIENT_DATA_IMPORT_002 - 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 := Build(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 := io.ReadAll(fo) - require.NoError(t, err) - require.NoError(t, fo.Close()) - require.Equal(t, inputContents, bz2) -} - -func TestRoundtripUnixFS_Filestore(t *testing.T) { - // stm: @CLIENT_DATA_IMPORT_001 - ctx := context.Background() - - inputPath, inputContents := genInputFile(t) - defer os.Remove(inputPath) //nolint:errcheck - - dst := newTmpFile(t) - defer os.Remove(dst) //nolint:errcheck - - root, err := CreateFilestore(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 := io.ReadAll(fo) - require.NoError(t, err) - require.NoError(t, fo.Close()) - require.Equal(t, inputContents, bz2) -} - -// creates a new tempdir each time, guaranteeing uniqueness -func newTmpFile(t *testing.T) string { - return t.TempDir() + string(os.PathSeparator) + "tmp" -} - -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/markets/dagstore/blockstore.go b/markets/dagstore/blockstore.go deleted file mode 100644 index 0ba68f549..000000000 --- a/markets/dagstore/blockstore.go +++ /dev/null @@ -1,34 +0,0 @@ -package dagstore - -import ( - "context" - "io" - - bstore "github.com/ipfs/boxo/blockstore" - blocks "github.com/ipfs/go-block-format" - "github.com/ipfs/go-cid" - "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(context.Context, cid.Cid) error { - return xerrors.Errorf("DeleteBlock called but not implemented") -} - -func (b *Blockstore) Put(context.Context, blocks.Block) error { - return xerrors.Errorf("Put called but not implemented") -} - -func (b *Blockstore) PutMany(context.Context, []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 deleted file mode 100644 index 9f7b56df358e0e3a0eb3734743b13cb3d88a12f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1875 zcmd;Dm|m7zRGgWg$HagJcCbPO1Q{XpNj5YEqukLD7!85Z5Eu;s`iDRd%Su5i)ABfJaO z8OQqmf3Er3H)fG_$ 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 = io.ReadAll(rd) - require.NoError(t, err) - require.NoError(t, rd.Close()) - require.Equal(t, []byte("testing"), bz) -} - -func TestLotusMountDeserialize(t *testing.T) { - //stm: @MARKET_DAGSTORE_DESERIALIZE_CID_001 - 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) { - //stm: @MARKET_DAGSTORE_FETCH_UNSEALED_PIECE_001, @MARKET_DAGSTORE_GET_UNPADDED_CAR_SIZE_001 - //stm: @MARKET_DAGSTORE_IS_PIECE_UNSEALED_001 - 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.NewMockMinerAPI(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 deleted file mode 100644 index a929ad1fc..000000000 --- a/markets/dagstore/wrapper.go +++ /dev/null @@ -1,436 +0,0 @@ -package dagstore - -import ( - "context" - "errors" - "fmt" - "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" - carindex "github.com/ipld/go-car/v2/index" - "github.com/libp2p/go-libp2p/core/host" - ldbopts "github.com/syndtr/goleveldb/leveldb/opt" - "golang.org/x/xerrors" - - "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" - "github.com/filecoin-project/go-statemachine/fsm" - - "github.com/filecoin-project/lotus/node/config" -) - -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 - gcInterval time.Duration -} - -var _ stores.DAGStoreWrapper = (*Wrapper)(nil) - -func NewDAGStore(cfg config.DAGStoreConfig, minerApi MinerAPI, h host.Host) (*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) - - 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: %w", err) - } - - topIndex := index.NewInverted(dstore) - dcfg := dagstore.Config{ - TransientsDir: transientsDir, - IndexRepo: irepo, - Datastore: dstore, - MountRegistry: registry, - FailureCh: failureCh, - TopLevelIndex: topIndex, - // 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, - 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 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) 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) DestroyShard(ctx context.Context, pieceCid cid.Cid, resch chan dagstore.ShardResult) error { - key := shard.KeyFromCID(pieceCid) - - opts := dagstore.DestroyOpts{} - - err := w.dagst.DestroyShard(ctx, key, resch, opts) - - if err != nil { - return xerrors.Errorf("failed to schedule destroy shard for piece CID %s: %w", pieceCid, err) - } - log.Debugf("successfully submitted destroy Shard request for piece CID %s", pieceCid) - - 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() -} - -// Get all the pieces that contain a block -func (w *Wrapper) GetPiecesContainingBlock(blockCID cid.Cid) ([]cid.Cid, error) { - // Pieces are stored as "shards" in the DAG store - shardKeys, err := w.dagst.ShardsContainingMultihash(w.ctx, blockCID.Hash()) - if err != nil { - return nil, xerrors.Errorf("getting pieces containing block %s: %w", blockCID, err) - } - - // Convert from shard key to cid - pieceCids := make([]cid.Cid, 0, len(shardKeys)) - for _, k := range shardKeys { - c, err := cid.Parse(k.String()) - if err != nil { - prefix := fmt.Sprintf("getting pieces containing block %s:", blockCID) - return nil, xerrors.Errorf("%s converting shard key %s to piece cid: %w", prefix, k, err) - } - - pieceCids = append(pieceCids, c) - } - - return pieceCids, nil -} - -func (w *Wrapper) GetIterableIndexForPiece(pieceCid cid.Cid) (carindex.IterableIndex, error) { - return w.dagst.GetIterableIndex(shard.KeyFromCID(pieceCid)) -} - -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 deleted file mode 100644 index db2c9768b..000000000 --- a/markets/dagstore/wrapper_migration_test.go +++ /dev/null @@ -1,153 +0,0 @@ -// stm: #integration -package dagstore - -import ( - "context" - "io" - "testing" - - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/dagstore" - "github.com/filecoin-project/dagstore/mount" - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - "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/go-state-types/abi" - markettypes "github.com/filecoin-project/go-state-types/builtin/v9/market" - - "github.com/filecoin-project/lotus/node/config" -) - -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 - //stm: @MARKET_DAGSTORE_MIGRATE_DEALS_001 - State: storagemarket.StorageDealSealing, - SectorNumber: unsealedSector1, - ClientDealProposal: markettypes.ClientDealProposal{ - Proposal: markettypes.DealProposal{ - PieceCID: pieceCidUnsealed, - }, - }, - }, { - // Should be registered with lazy registration (because sector is sealed) - State: storagemarket.StorageDealSealing, - SectorNumber: sealedSector, - ClientDealProposal: markettypes.ClientDealProposal{ - Proposal: markettypes.DealProposal{ - PieceCID: pieceCidSealed, - }, - }, - }, { - // Should be ignored because deal is no longer active - //stm: @MARKET_DAGSTORE_MIGRATE_DEALS_003 - State: storagemarket.StorageDealError, - SectorNumber: unsealedSector2, - ClientDealProposal: markettypes.ClientDealProposal{ - Proposal: markettypes.DealProposal{ - PieceCID: pieceCidUnsealed2, - }, - }, - }, { - // Should be ignored because deal is not yet sealing - State: storagemarket.StorageDealFundsReserved, - SectorNumber: unsealedSector3, - ClientDealProposal: markettypes.ClientDealProposal{ - Proposal: markettypes.DealProposal{ - PieceCID: pieceCidUnsealed3, - }, - }, - }} - - cfg := config.DefaultStorageMiner().DAGStore - cfg.RootDir = t.TempDir() - - h, err := mocknet.New().GenPeer() - require.NoError(t, err) - - mapi := NewMinerAPI(ps, &wrappedSA{sa}, 10, 5) - dagst, w, err := NewDAGStore(cfg, mapi, h) - 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) - - //stm: @MARKET_DAGSTORE_GET_ALL_SHARDS_001 - info := dagst.AllShardsInfo() - require.Len(t, info, 2) - for _, i := range info { - require.Equal(t, dagstore.ShardStateNew, i.ShardState) - } - - // Run register shard migration again - //stm: @MARKET_DAGSTORE_MIGRATE_DEALS_002 - migrated, err = w.MigrateDeals(ctx, deals) - require.False(t, migrated) - require.NoError(t, err) - - // ps.VerifyExpectations(t) -} - -type wrappedSA struct { - retrievalmarket.SectorAccessor -} - -func (w *wrappedSA) UnsealSectorAt(ctx context.Context, sectorID abi.SectorNumber, pieceOffset abi.UnpaddedPieceSize, length abi.UnpaddedPieceSize) (mount.Reader, error) { - r, err := w.UnsealSector(ctx, sectorID, pieceOffset, length) - if err != nil { - return nil, err - } - return struct { - io.ReadCloser - io.Seeker - io.ReaderAt - }{ - ReadCloser: r, - Seeker: nil, - ReaderAt: nil, - }, err -} - -var _ SectorAccessor = &wrappedSA{} diff --git a/markets/dagstore/wrapper_test.go b/markets/dagstore/wrapper_test.go deleted file mode 100644 index f3b5e1b52..000000000 --- a/markets/dagstore/wrapper_test.go +++ /dev/null @@ -1,262 +0,0 @@ -// stm: #unit -package dagstore - -import ( - "bytes" - "context" - "os" - "testing" - "time" - - "github.com/ipfs/go-cid" - carindex "github.com/ipld/go-car/v2/index" - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - mh "github.com/multiformats/go-multihash" - "github.com/stretchr/testify/require" - "golang.org/x/xerrors" - - "github.com/filecoin-project/dagstore" - "github.com/filecoin-project/dagstore/mount" - "github.com/filecoin-project/dagstore/shard" - - "github.com/filecoin-project/lotus/node/config" -) - -// TestWrapperAcquireRecovery verifies that if acquire shard returns a "not found" -// error, the wrapper will attempt to register the shard then reacquire -func TestWrapperAcquireRecoveryDestroy(t *testing.T) { - ctx := context.Background() - pieceCid, err := cid.Parse("bafkqaaa") - require.NoError(t, err) - - h, err := mocknet.New().GenPeer() - 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{}, h) - 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), - destroy: make(chan shard.Key, 1), - } - w.dagst = mock - - //stm: @MARKET_DAGSTORE_ACQUIRE_SHARD_002 - 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) - - // Destroy the shard - dr := make(chan dagstore.ShardResult, 1) - err = w.DestroyShard(ctx, pieceCid, dr) - require.NoError(t, err) - - dctx, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() - select { - case <-dctx.Done(): - require.Fail(t, "failed to call destroy") - case k := <-mock.destroy: - require.Equal(t, k.String(), pieceCid.String()) - } - - var dcount int - dch, err := mybs.AllKeysChan(ctx) - require.NoError(t, err) - for range dch { - count++ - } - require.Equal(t, dcount, 0) -} - -// TestWrapperBackground verifies the behaviour of the background go routine -func TestWrapperBackground(t *testing.T) { - ctx := context.Background() - h, err := mocknet.New().GenPeer() - 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{}, h) - 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 - //stm: @MARKET_DAGSTORE_START_001 - err = w.Start(ctx) - require.NoError(t, err) - - // Expect GC to be called automatically - //stm: @MARKET_DAGSTORE_START_002 - 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 - //stm: @MARKET_DAGSTORE_CLOSE_001 - 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 - destroy chan shard.Key - close chan struct{} -} - -func (m *mockDagStore) GetIterableIndex(key shard.Key) (carindex.IterableIndex, error) { - return nil, nil -} - -func (m *mockDagStore) ShardsContainingMultihash(ctx context.Context, h mh.Multihash) ([]shard.Key, error) { - return nil, nil -} - -func (m *mockDagStore) GetShardKeysForCid(c cid.Cid) ([]shard.Key, error) { - panic("implement me") -} - -func (m *mockDagStore) DestroyShard(ctx context.Context, key shard.Key, out chan dagstore.ShardResult, _ dagstore.DestroyOpts) error { - m.destroy <- key - out <- dagstore.ShardResult{Key: key} - return nil -} - -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(context.Context, cid.Cid) (mount.Reader, 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/dealfilter/cli.go b/markets/dealfilter/cli.go deleted file mode 100644 index af832bfa0..000000000 --- a/markets/dealfilter/cli.go +++ /dev/null @@ -1,62 +0,0 @@ -package dealfilter - -import ( - "bytes" - "context" - "encoding/json" - "os/exec" - - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - "github.com/filecoin-project/go-fil-markets/storagemarket" - - "github.com/filecoin-project/lotus/node/modules/dtypes" -) - -func CliStorageDealFilter(cmd string) dtypes.StorageDealFilter { - return func(ctx context.Context, deal storagemarket.MinerDeal) (bool, string, error) { - d := struct { - storagemarket.MinerDeal - DealType string - }{ - MinerDeal: deal, - DealType: "storage", - } - return runDealFilter(ctx, cmd, d) - } -} - -func CliRetrievalDealFilter(cmd string) dtypes.RetrievalDealFilter { - return func(ctx context.Context, deal retrievalmarket.ProviderDealState) (bool, string, error) { - d := struct { - retrievalmarket.ProviderDealState - DealType string - }{ - ProviderDealState: deal, - DealType: "retrieval", - } - return runDealFilter(ctx, cmd, d) - } -} - -func runDealFilter(ctx context.Context, cmd string, deal interface{}) (bool, string, error) { - j, err := json.MarshalIndent(deal, "", " ") - if err != nil { - return false, "", err - } - - var out bytes.Buffer - - c := exec.Command("sh", "-c", cmd) - c.Stdin = bytes.NewReader(j) - c.Stdout = &out - c.Stderr = &out - - switch err := c.Run().(type) { - case nil: - return true, "", nil - case *exec.ExitError: - return false, out.String(), nil - default: - return false, "filter cmd run error", err - } -} diff --git a/markets/idxprov/idxprov_test/noop.go b/markets/idxprov/idxprov_test/noop.go deleted file mode 100644 index 535c13d25..000000000 --- a/markets/idxprov/idxprov_test/noop.go +++ /dev/null @@ -1,16 +0,0 @@ -package idxprov_test - -import ( - "context" -) - -type NoopMeshCreator struct { -} - -func NewNoopMeshCreator() *NoopMeshCreator { - return &NoopMeshCreator{} -} - -func (mc NoopMeshCreator) Connect(ctx context.Context) error { - return nil -} diff --git a/markets/idxprov/mesh.go b/markets/idxprov/mesh.go deleted file mode 100644 index e69e213ad..000000000 --- a/markets/idxprov/mesh.go +++ /dev/null @@ -1,59 +0,0 @@ -package idxprov - -import ( - "context" - "fmt" - - logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/peer" - - "github.com/filecoin-project/lotus/api/v1api" -) - -var log = logging.Logger("idxprov") - -const protectTag = "index-provider-gossipsub" - -type MeshCreator interface { - Connect(ctx context.Context) error -} - -type Libp2pMeshCreator struct { - fullnodeApi v1api.FullNode - marketsHost host.Host -} - -func (mc Libp2pMeshCreator) Connect(ctx context.Context) error { - - // Add the markets host ID to list of daemon's protected peers first, before any attempt to - // connect to full node over libp2p. - marketsPeerID := mc.marketsHost.ID() - if err := mc.fullnodeApi.NetProtectAdd(ctx, []peer.ID{marketsPeerID}); err != nil { - return fmt.Errorf("failed to call NetProtectAdd on the full node, err: %w", err) - } - - faddrs, err := mc.fullnodeApi.NetAddrsListen(ctx) - if err != nil { - return fmt.Errorf("failed to fetch full node listen addrs, err: %w", err) - } - - // Connect from the full node, ask it to protect the connection and protect the connection on - // markets end too. Connection is initiated form full node to avoid the need to expose libp2p port on full node - if err := mc.fullnodeApi.NetConnect(ctx, peer.AddrInfo{ - ID: mc.marketsHost.ID(), - Addrs: mc.marketsHost.Addrs(), - }); err != nil { - return fmt.Errorf("failed to connect to index provider host from full node: %w", err) - } - mc.marketsHost.ConnManager().Protect(faddrs.ID, protectTag) - - log.Debugw("successfully connected to full node and asked it protect indexer provider peer conn", "fullNodeInfo", faddrs.String(), - "peerId", marketsPeerID) - - return nil -} - -func NewMeshCreator(fullnodeApi v1api.FullNode, marketsHost host.Host) MeshCreator { - return Libp2pMeshCreator{fullnodeApi, marketsHost} -} diff --git a/markets/journal.go b/markets/journal.go deleted file mode 100644 index 9c9c5be9c..000000000 --- a/markets/journal.go +++ /dev/null @@ -1,76 +0,0 @@ -package markets - -import ( - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - "github.com/filecoin-project/go-fil-markets/storagemarket" - - "github.com/filecoin-project/lotus/journal" -) - -type StorageClientEvt struct { - Event string - Deal storagemarket.ClientDeal -} - -type StorageProviderEvt struct { - Event string - Deal storagemarket.MinerDeal -} - -type RetrievalClientEvt struct { - Event string - Deal retrievalmarket.ClientDealState -} - -type RetrievalProviderEvt struct { - Event string - Deal retrievalmarket.ProviderDealState -} - -// StorageClientJournaler records journal events from the storage client. -func StorageClientJournaler(j journal.Journal, evtType journal.EventType) func(event storagemarket.ClientEvent, deal storagemarket.ClientDeal) { - return func(event storagemarket.ClientEvent, deal storagemarket.ClientDeal) { - j.RecordEvent(evtType, func() interface{} { - return StorageClientEvt{ - Event: storagemarket.ClientEvents[event], - Deal: deal, - } - }) - } -} - -// StorageProviderJournaler records journal events from the storage provider. -func StorageProviderJournaler(j journal.Journal, evtType journal.EventType) func(event storagemarket.ProviderEvent, deal storagemarket.MinerDeal) { - return func(event storagemarket.ProviderEvent, deal storagemarket.MinerDeal) { - j.RecordEvent(evtType, func() interface{} { - return StorageProviderEvt{ - Event: storagemarket.ProviderEvents[event], - Deal: deal, - } - }) - } -} - -// RetrievalClientJournaler records journal events from the retrieval client. -func RetrievalClientJournaler(j journal.Journal, evtType journal.EventType) func(event retrievalmarket.ClientEvent, deal retrievalmarket.ClientDealState) { - return func(event retrievalmarket.ClientEvent, deal retrievalmarket.ClientDealState) { - j.RecordEvent(evtType, func() interface{} { - return RetrievalClientEvt{ - Event: retrievalmarket.ClientEvents[event], - Deal: deal, - } - }) - } -} - -// RetrievalProviderJournaler records journal events from the retrieval provider. -func RetrievalProviderJournaler(j journal.Journal, evtType journal.EventType) func(event retrievalmarket.ProviderEvent, deal retrievalmarket.ProviderDealState) { - return func(event retrievalmarket.ProviderEvent, deal retrievalmarket.ProviderDealState) { - j.RecordEvent(evtType, func() interface{} { - return RetrievalProviderEvt{ - Event: retrievalmarket.ProviderEvents[event], - Deal: deal, - } - }) - } -} diff --git a/markets/loggers/loggers.go b/markets/loggers/loggers.go deleted file mode 100644 index e066c9843..000000000 --- a/markets/loggers/loggers.go +++ /dev/null @@ -1,76 +0,0 @@ -package marketevents - -import ( - logging "github.com/ipfs/go-log/v2" - - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-state-types/abi" -) - -var log = logging.Logger("markets") - -// StorageClientLogger logs events from the storage client -func StorageClientLogger(event storagemarket.ClientEvent, deal storagemarket.ClientDeal) { - log.Infow("storage client event", "name", storagemarket.ClientEvents[event], "proposal CID", deal.ProposalCid, "state", storagemarket.DealStates[deal.State], "message", deal.Message) -} - -// StorageProviderLogger logs events from the storage provider -func StorageProviderLogger(event storagemarket.ProviderEvent, deal storagemarket.MinerDeal) { - log.Infow("storage provider event", "name", storagemarket.ProviderEvents[event], "proposal CID", deal.ProposalCid, "state", storagemarket.DealStates[deal.State], "message", deal.Message) -} - -// RetrievalClientLogger logs events from the retrieval client -func RetrievalClientLogger(event retrievalmarket.ClientEvent, deal retrievalmarket.ClientDealState) { - method := log.Infow - if event == retrievalmarket.ClientEventBlocksReceived { - method = log.Debugw - } - method("retrieval client event", "name", retrievalmarket.ClientEvents[event], "deal ID", deal.ID, "state", retrievalmarket.DealStatuses[deal.Status], "message", deal.Message) -} - -// RetrievalProviderLogger logs events from the retrieval provider -func RetrievalProviderLogger(event retrievalmarket.ProviderEvent, deal retrievalmarket.ProviderDealState) { - method := log.Infow - if event == retrievalmarket.ProviderEventBlockSent { - method = log.Debugw - } - method("retrieval provider event", "name", retrievalmarket.ProviderEvents[event], "deal ID", deal.ID, "receiver", deal.Receiver, "state", retrievalmarket.DealStatuses[deal.Status], "message", deal.Message) -} - -// DataTransferLogger logs events from the data transfer module -func DataTransferLogger(event datatransfer.Event, state datatransfer.ChannelState) { - log.Debugw("data transfer event", - "name", datatransfer.Events[event.Code], - "status", datatransfer.Statuses[state.Status()], - "transfer ID", state.TransferID(), - "channel ID", state.ChannelID(), - "sent", state.Sent(), - "received", state.Received(), - "queued", state.Queued(), - "received count", state.ReceivedCidsTotal(), - "total size", state.TotalSize(), - "remote peer", state.OtherPeer(), - "event message", event.Message, - "channel message", state.Message()) -} - -// ReadyLogger returns a function to log the results of module initialization -func ReadyLogger(module string) func(error) { - return func(err error) { - if err != nil { - log.Errorw("module initialization error", "module", module, "err", err) - } else { - log.Infow("module ready", "module", module) - } - } -} - -type RetrievalEvent struct { - Event retrievalmarket.ClientEvent - Status retrievalmarket.DealStatus - BytesReceived uint64 - FundsSpent abi.TokenAmount - Err string -} diff --git a/markets/pricing/cli.go b/markets/pricing/cli.go deleted file mode 100644 index 48f56628f..000000000 --- a/markets/pricing/cli.go +++ /dev/null @@ -1,50 +0,0 @@ -package pricing - -import ( - "bytes" - "context" - "encoding/json" - "os/exec" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - - "github.com/filecoin-project/lotus/node/modules/dtypes" -) - -func ExternalRetrievalPricingFunc(cmd string) dtypes.RetrievalPricingFunc { - return func(ctx context.Context, pricingInput retrievalmarket.PricingInput) (retrievalmarket.Ask, error) { - return runPricingFunc(ctx, cmd, pricingInput) - } -} - -func runPricingFunc(_ context.Context, cmd string, params interface{}) (retrievalmarket.Ask, error) { - j, err := json.Marshal(params) - if err != nil { - return retrievalmarket.Ask{}, err - } - - var out bytes.Buffer - var errb bytes.Buffer - - c := exec.Command("sh", "-c", cmd) - c.Stdin = bytes.NewReader(j) - c.Stdout = &out - c.Stderr = &errb - - switch err := c.Run().(type) { - case nil: - bz := out.Bytes() - resp := retrievalmarket.Ask{} - - if err := json.Unmarshal(bz, &resp); err != nil { - return resp, xerrors.Errorf("failed to parse pricing output %s, err=%w", string(bz), err) - } - return resp, nil - case *exec.ExitError: - return retrievalmarket.Ask{}, xerrors.Errorf("pricing func exited with error: %s", errb.String()) - default: - return retrievalmarket.Ask{}, xerrors.Errorf("pricing func cmd run error: %w", err) - } -} diff --git a/markets/retrievaladapter/client.go b/markets/retrievaladapter/client.go deleted file mode 100644 index 34bc24896..000000000 --- a/markets/retrievaladapter/client.go +++ /dev/null @@ -1,127 +0,0 @@ -package retrievaladapter - -import ( - "context" - - "github.com/ipfs/go-cid" - "github.com/multiformats/go-multiaddr" - - "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" - paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/node/impl/full" - payapi "github.com/filecoin-project/lotus/node/impl/paych" -) - -type retrievalClientNode struct { - forceOffChain bool - - chainAPI full.ChainAPI - payAPI payapi.PaychAPI - stateAPI full.StateAPI -} - -// NewRetrievalClientNode returns a new node adapter for a retrieval client that talks to the -// Lotus Node -func NewRetrievalClientNode(forceOffChain bool, payAPI payapi.PaychAPI, chainAPI full.ChainAPI, stateAPI full.StateAPI) retrievalmarket.RetrievalClientNode { - return &retrievalClientNode{ - forceOffChain: forceOffChain, - chainAPI: chainAPI, - payAPI: payAPI, - stateAPI: stateAPI, - } -} - -// GetOrCreatePaymentChannel sets up a new payment channel if one does not exist -// between a client and a miner and ensures the client has the given amount of -// funds available in the channel. -func (rcn *retrievalClientNode) GetOrCreatePaymentChannel(ctx context.Context, clientAddress address.Address, minerAddress address.Address, clientFundsAvailable abi.TokenAmount, tok shared.TipSetToken) (address.Address, cid.Cid, error) { - // TODO: respect the provided TipSetToken (a serialized TipSetKey) when - // querying the chain - ci, err := rcn.payAPI.PaychGet(ctx, clientAddress, minerAddress, clientFundsAvailable, api.PaychGetOpts{ - OffChain: rcn.forceOffChain, - }) - if err != nil { - log.Errorw("paych get failed", "error", err) - return address.Undef, cid.Undef, err - } - - return ci.Channel, ci.WaitSentinel, nil -} - -// Allocate late creates a lane within a payment channel so that calls to -// CreatePaymentVoucher will automatically make vouchers only for the difference -// in total -func (rcn *retrievalClientNode) AllocateLane(ctx context.Context, paymentChannel address.Address) (uint64, error) { - return rcn.payAPI.PaychAllocateLane(ctx, paymentChannel) -} - -// CreatePaymentVoucher creates a new payment voucher in the given lane for a -// given payment channel so that all the payment vouchers in the lane add up -// to the given amount (so the payment voucher will be for the difference) -func (rcn *retrievalClientNode) CreatePaymentVoucher(ctx context.Context, paymentChannel address.Address, amount abi.TokenAmount, lane uint64, tok shared.TipSetToken) (*paychtypes.SignedVoucher, error) { - // TODO: respect the provided TipSetToken (a serialized TipSetKey) when - // querying the chain - voucher, err := rcn.payAPI.PaychVoucherCreate(ctx, paymentChannel, amount, lane) - if err != nil { - return nil, err - } - if voucher.Voucher == nil { - return nil, retrievalmarket.NewShortfallError(voucher.Shortfall) - } - return voucher.Voucher, nil -} - -func (rcn *retrievalClientNode) GetChainHead(ctx context.Context) (shared.TipSetToken, abi.ChainEpoch, error) { - head, err := rcn.chainAPI.ChainHead(ctx) - if err != nil { - return nil, 0, err - } - - return head.Key().Bytes(), head.Height(), nil -} - -func (rcn *retrievalClientNode) WaitForPaymentChannelReady(ctx context.Context, messageCID cid.Cid) (address.Address, error) { - return rcn.payAPI.PaychGetWaitReady(ctx, messageCID) -} - -func (rcn *retrievalClientNode) CheckAvailableFunds(ctx context.Context, paymentChannel address.Address) (retrievalmarket.ChannelAvailableFunds, error) { - - channelAvailableFunds, err := rcn.payAPI.PaychAvailableFunds(ctx, paymentChannel) - if err != nil { - return retrievalmarket.ChannelAvailableFunds{}, err - } - return retrievalmarket.ChannelAvailableFunds{ - ConfirmedAmt: channelAvailableFunds.ConfirmedAmt, - PendingAmt: channelAvailableFunds.PendingAmt, - PendingWaitSentinel: channelAvailableFunds.PendingWaitSentinel, - QueuedAmt: channelAvailableFunds.QueuedAmt, - VoucherReedeemedAmt: channelAvailableFunds.VoucherReedeemedAmt, - }, nil -} - -func (rcn *retrievalClientNode) GetKnownAddresses(ctx context.Context, p retrievalmarket.RetrievalPeer, encodedTs shared.TipSetToken) ([]multiaddr.Multiaddr, error) { - tsk, err := types.TipSetKeyFromBytes(encodedTs) - if err != nil { - return nil, err - } - mi, err := rcn.stateAPI.StateMinerInfo(ctx, p.Address, tsk) - if err != nil { - return nil, err - } - multiaddrs := make([]multiaddr.Multiaddr, 0, len(mi.Multiaddrs)) - for _, a := range mi.Multiaddrs { - maddr, err := multiaddr.NewMultiaddrBytes(a) - if err != nil { - return nil, err - } - multiaddrs = append(multiaddrs, maddr) - } - - return multiaddrs, nil -} diff --git a/markets/retrievaladapter/client_blockstore.go b/markets/retrievaladapter/client_blockstore.go deleted file mode 100644 index 30fc5c73a..000000000 --- a/markets/retrievaladapter/client_blockstore.go +++ /dev/null @@ -1,166 +0,0 @@ -package retrievaladapter - -import ( - "fmt" - "path/filepath" - "sync" - - bstore "github.com/ipfs/boxo/blockstore" - "github.com/ipfs/go-cid" - "github.com/ipld/go-car/v2/blockstore" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - - "github.com/filecoin-project/lotus/api" - lbstore "github.com/filecoin-project/lotus/blockstore" -) - -// 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 -} - -func NewAPIBlockstoreAdapter(sub retrievalmarket.BlockstoreAccessor) *APIBlockstoreAccessor { - return &APIBlockstoreAccessor{ - sub: sub, - retrStores: map[retrievalmarket.DealID]api.RemoteStoreID{}, - remoteStores: map[api.RemoteStoreID]bstore.Blockstore{}, - } -} - -// APIBlockstoreAccessor adds support to API-specified remote blockstores -type APIBlockstoreAccessor struct { - sub retrievalmarket.BlockstoreAccessor - - retrStores map[retrievalmarket.DealID]api.RemoteStoreID - remoteStores map[api.RemoteStoreID]bstore.Blockstore - - accessLk sync.Mutex -} - -func (a *APIBlockstoreAccessor) Get(id retrievalmarket.DealID, payloadCID retrievalmarket.PayloadCID) (bstore.Blockstore, error) { - a.accessLk.Lock() - defer a.accessLk.Unlock() - - as, has := a.retrStores[id] - if !has { - return a.sub.Get(id, payloadCID) - } - - return a.remoteStores[as], nil -} - -func (a *APIBlockstoreAccessor) Done(id retrievalmarket.DealID) error { - a.accessLk.Lock() - defer a.accessLk.Unlock() - - if _, has := a.retrStores[id]; has { - delete(a.retrStores, id) - return nil - } - return a.sub.Done(id) -} - -func (a *APIBlockstoreAccessor) RegisterDealToRetrievalStore(id retrievalmarket.DealID, sid api.RemoteStoreID) error { - a.accessLk.Lock() - defer a.accessLk.Unlock() - - if _, has := a.retrStores[id]; has { - return xerrors.Errorf("apistore for deal %d already registered", id) - } - if _, has := a.remoteStores[sid]; !has { - return xerrors.Errorf("remote store not found") - } - - a.retrStores[id] = sid - return nil -} - -func (a *APIBlockstoreAccessor) RegisterApiStore(sid api.RemoteStoreID, st *lbstore.NetworkStore) error { - a.accessLk.Lock() - defer a.accessLk.Unlock() - - if _, has := a.remoteStores[sid]; has { - return xerrors.Errorf("remote store already registered with this uuid") - } - - a.remoteStores[sid] = st - - st.OnClose(func() { - a.accessLk.Lock() - defer a.accessLk.Unlock() - - if _, has := a.remoteStores[sid]; has { - delete(a.remoteStores, sid) - } - }) - return nil -} - -var _ retrievalmarket.BlockstoreAccessor = &APIBlockstoreAccessor{} - -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 deleted file mode 100644 index 453474d4e..000000000 --- a/markets/retrievaladapter/provider.go +++ /dev/null @@ -1,108 +0,0 @@ -package retrievaladapter - -import ( - "context" - - "github.com/hashicorp/go-multierror" - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - "golang.org/x/xerrors" - - "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" - paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" - - "github.com/filecoin-project/lotus/api/v1api" - "github.com/filecoin-project/lotus/chain/types" -) - -var log = logging.Logger("retrievaladapter") - -type retrievalProviderNode struct { - 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(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) { - tsk, err := types.TipSetKeyFromBytes(tok) - if err != nil { - return address.Undef, err - } - - mi, err := rpn.full.StateMinerInfo(ctx, miner, tsk) - return mi.Worker, err -} - -func (rpn *retrievalProviderNode) SavePaymentVoucher(ctx context.Context, paymentChannel address.Address, voucher *paychtypes.SignedVoucher, proof []byte, expectedAmount abi.TokenAmount, tok shared.TipSetToken) (abi.TokenAmount, error) { - // TODO: respect the provided TipSetToken (a serialized TipSetKey) when - // querying the chain - added, err := rpn.full.PaychVoucherAdd(ctx, paymentChannel, voucher, proof, expectedAmount) - return added, err -} - -func (rpn *retrievalProviderNode) GetChainHead(ctx context.Context) (shared.TipSetToken, abi.ChainEpoch, error) { - head, err := rpn.full.ChainHead(ctx) - if err != nil { - return nil, 0, err - } - - return head.Key().Bytes(), head.Height(), nil -} - -// 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. -func (rpn *retrievalProviderNode) GetRetrievalPricingInput(ctx context.Context, pieceCID cid.Cid, storageDeals []abi.DealID) (retrievalmarket.PricingInput, error) { - resp := retrievalmarket.PricingInput{} - - head, err := rpn.full.ChainHead(ctx) - if err != nil { - return resp, xerrors.Errorf("failed to get chain head: %w", err) - } - tsk := head.Key() - - var mErr error - - for _, dealID := range storageDeals { - ds, err := rpn.full.StateMarketStorageDeal(ctx, dealID, tsk) - if err != nil { - log.Warnf("failed to look up deal %d on chain: err=%w", dealID, err) - mErr = multierror.Append(mErr, err) - continue - } - if ds.Proposal.VerifiedDeal { - resp.VerifiedDeal = true - } - - if ds.Proposal.PieceCID.Equals(pieceCID) { - resp.PieceSize = ds.Proposal.PieceSize.Unpadded() - } - - // If we've discovered a verified deal with the required PieceCID, we don't need - // to lookup more deals and we're done. - if resp.VerifiedDeal && resp.PieceSize != 0 { - break - } - } - - // Note: The piece size can never actually be zero. We only use it to here - // to assert that we didn't find a matching piece. - if resp.PieceSize == 0 { - if mErr == nil { - return resp, xerrors.New("failed to find matching piece") - } - - return resp, xerrors.Errorf("failed to fetch storage deal state: %w", mErr) - } - - return resp, nil -} diff --git a/markets/retrievaladapter/provider_test.go b/markets/retrievaladapter/provider_test.go deleted file mode 100644 index b7b5039d6..000000000 --- a/markets/retrievaladapter/provider_test.go +++ /dev/null @@ -1,206 +0,0 @@ -// stm: #unit -package retrievaladapter - -import ( - "context" - "testing" - - "github.com/golang/mock/gomock" - "github.com/ipfs/go-cid" - "github.com/stretchr/testify/require" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - testnet "github.com/filecoin-project/go-fil-markets/shared_testutil" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/mocks" - "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/types" -) - -func TestGetPricingInput(t *testing.T) { - //stm: @CHAIN_STATE_MARKET_STORAGE_DEAL_001 - ctx := context.Background() - tsk := &types.TipSet{} - key := tsk.Key() - - pcid := testnet.GenerateCids(1)[0] - deals := []abi.DealID{1, 2} - paddedSize := abi.PaddedPieceSize(128) - unpaddedSize := paddedSize.Unpadded() - - tcs := map[string]struct { - pieceCid cid.Cid - deals []abi.DealID - fFnc func(node *mocks.MockFullNode) - - expectedErrorStr string - expectedVerified bool - expectedPieceSize abi.UnpaddedPieceSize - }{ - "error when fails to fetch chain head": { - fFnc: func(n *mocks.MockFullNode) { - n.EXPECT().ChainHead(gomock.Any()).Return(tsk, xerrors.New("chain head error")).Times(1) - }, - expectedErrorStr: "chain head error", - }, - - "error when no piece matches": { - fFnc: func(n *mocks.MockFullNode) { - out1 := &api.MarketDeal{ - Proposal: market.DealProposal{ - PieceCID: testnet.GenerateCids(1)[0], - }, - } - out2 := &api.MarketDeal{ - Proposal: market.DealProposal{ - PieceCID: testnet.GenerateCids(1)[0], - }, - } - - n.EXPECT().ChainHead(gomock.Any()).Return(tsk, nil).Times(1) - gomock.InOrder( - n.EXPECT().StateMarketStorageDeal(gomock.Any(), deals[0], key).Return(out1, nil), - n.EXPECT().StateMarketStorageDeal(gomock.Any(), deals[1], key).Return(out2, nil), - ) - - }, - expectedErrorStr: "failed to find matching piece", - }, - - "error when fails to fetch deal state": { - fFnc: func(n *mocks.MockFullNode) { - out1 := &api.MarketDeal{ - Proposal: market.DealProposal{ - PieceCID: pcid, - PieceSize: paddedSize, - }, - } - out2 := &api.MarketDeal{ - Proposal: market.DealProposal{ - PieceCID: testnet.GenerateCids(1)[0], - VerifiedDeal: true, - }, - } - - n.EXPECT().ChainHead(gomock.Any()).Return(tsk, nil).Times(1) - gomock.InOrder( - n.EXPECT().StateMarketStorageDeal(gomock.Any(), deals[0], key).Return(out1, xerrors.New("error 1")), - n.EXPECT().StateMarketStorageDeal(gomock.Any(), deals[1], key).Return(out2, xerrors.New("error 2")), - ) - - }, - expectedErrorStr: "failed to fetch storage deal state", - }, - - "verified is true even if one deal is verified and we get the correct piecesize": { - fFnc: func(n *mocks.MockFullNode) { - out1 := &api.MarketDeal{ - Proposal: market.DealProposal{ - PieceCID: pcid, - PieceSize: paddedSize, - }, - } - out2 := &api.MarketDeal{ - Proposal: market.DealProposal{ - PieceCID: testnet.GenerateCids(1)[0], - VerifiedDeal: true, - }, - } - - n.EXPECT().ChainHead(gomock.Any()).Return(tsk, nil).Times(1) - gomock.InOrder( - n.EXPECT().StateMarketStorageDeal(gomock.Any(), deals[0], key).Return(out1, nil), - n.EXPECT().StateMarketStorageDeal(gomock.Any(), deals[1], key).Return(out2, nil), - ) - - }, - expectedPieceSize: unpaddedSize, - expectedVerified: true, - }, - - "success even if one deal state fetch errors out but the other deal is verified and has the required piececid": { - fFnc: func(n *mocks.MockFullNode) { - out1 := &api.MarketDeal{ - Proposal: market.DealProposal{ - PieceCID: testnet.GenerateCids(1)[0], - }, - } - out2 := &api.MarketDeal{ - Proposal: market.DealProposal{ - PieceCID: pcid, - PieceSize: paddedSize, - VerifiedDeal: true, - }, - } - - n.EXPECT().ChainHead(gomock.Any()).Return(tsk, nil).Times(1) - gomock.InOrder( - n.EXPECT().StateMarketStorageDeal(gomock.Any(), deals[0], key).Return(out1, xerrors.New("some error")), - n.EXPECT().StateMarketStorageDeal(gomock.Any(), deals[1], key).Return(out2, nil), - ) - - }, - expectedPieceSize: unpaddedSize, - expectedVerified: true, - }, - - "verified is false if both deals are unverified and we get the correct piece size": { - fFnc: func(n *mocks.MockFullNode) { - out1 := &api.MarketDeal{ - Proposal: market.DealProposal{ - PieceCID: pcid, - PieceSize: paddedSize, - VerifiedDeal: false, - }, - } - out2 := &api.MarketDeal{ - Proposal: market.DealProposal{ - PieceCID: testnet.GenerateCids(1)[0], - VerifiedDeal: false, - }, - } - - n.EXPECT().ChainHead(gomock.Any()).Return(tsk, nil).Times(1) - gomock.InOrder( - n.EXPECT().StateMarketStorageDeal(gomock.Any(), deals[0], key).Return(out1, nil), - n.EXPECT().StateMarketStorageDeal(gomock.Any(), deals[1], key).Return(out2, nil), - ) - - }, - expectedPieceSize: unpaddedSize, - expectedVerified: false, - }, - } - - for name, tc := range tcs { - tc := tc - t.Run(name, func(t *testing.T) { - mockCtrl := gomock.NewController(t) - // when test is done, assert expectations on all mock objects. - defer mockCtrl.Finish() - - mockFull := mocks.NewMockFullNode(mockCtrl) - rpn := &retrievalProviderNode{ - full: mockFull, - } - if tc.fFnc != nil { - tc.fFnc(mockFull) - } - - resp, err := rpn.GetRetrievalPricingInput(ctx, pcid, deals) - - if tc.expectedErrorStr != "" { - require.Error(t, err) - require.Contains(t, err.Error(), tc.expectedErrorStr) - require.Equal(t, retrievalmarket.PricingInput{}, resp) - } else { - require.NoError(t, err) - require.Equal(t, tc.expectedPieceSize, resp.PieceSize) - require.Equal(t, tc.expectedVerified, resp.VerifiedDeal) - } - }) - } -} diff --git a/markets/sectoraccessor/sectoraccessor.go b/markets/sectoraccessor/sectoraccessor.go deleted file mode 100644 index 9b709d3b5..000000000 --- a/markets/sectoraccessor/sectoraccessor.go +++ /dev/null @@ -1,136 +0,0 @@ -package sectoraccessor - -import ( - "context" - "io" - - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - "golang.org/x/xerrors" - - "github.com/filecoin-project/dagstore/mount" - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/v1api" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/markets/dagstore" - "github.com/filecoin-project/lotus/node/modules/dtypes" - "github.com/filecoin-project/lotus/storage/sealer" - "github.com/filecoin-project/lotus/storage/sealer/storiface" - "github.com/filecoin-project/lotus/storage/sectorblocks" -) - -var log = logging.Logger("sectoraccessor") - -type sectorAccessor struct { - maddr address.Address - secb sectorblocks.SectorBuilder - pp sealer.PieceProvider - full v1api.FullNode -} - -var _ retrievalmarket.SectorAccessor = (*sectorAccessor)(nil) - -func NewSectorAccessor(maddr dtypes.MinerAddress, secb sectorblocks.SectorBuilder, pp sealer.PieceProvider, full v1api.FullNode) dagstore.SectorAccessor { - return §orAccessor{address.Address(maddr), secb, pp, full} -} - -func (sa *sectorAccessor) UnsealSector(ctx context.Context, sectorID abi.SectorNumber, pieceOffset abi.UnpaddedPieceSize, length abi.UnpaddedPieceSize) (io.ReadCloser, error) { - return sa.UnsealSectorAt(ctx, sectorID, pieceOffset, length) -} - -func (sa *sectorAccessor) UnsealSectorAt(ctx context.Context, sectorID abi.SectorNumber, pieceOffset abi.UnpaddedPieceSize, length abi.UnpaddedPieceSize) (mount.Reader, error) { - log.Debugf("get sector %d, pieceOffset %d, length %d", sectorID, pieceOffset, 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 := storiface.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, pieceOffset %d, length %d from miner %d", sectorID, pieceOffset, length, mid) - r, unsealed, err := sa.pp.ReadPiece(ctx, ref, storiface.UnpaddedByteIndex(pieceOffset), 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 := storiface.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/api.go b/markets/storageadapter/api.go deleted file mode 100644 index b93ffdfbb..000000000 --- a/markets/storageadapter/api.go +++ /dev/null @@ -1,55 +0,0 @@ -package storageadapter - -import ( - "context" - - blocks "github.com/ipfs/go-block-format" - "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/lotus/blockstore" - "github.com/filecoin-project/lotus/chain/actors/adt" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" -) - -type apiWrapper struct { - api interface { - StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) - ChainReadObj(context.Context, cid.Cid) ([]byte, error) - ChainHasObj(context.Context, cid.Cid) (bool, error) - ChainPutObj(context.Context, blocks.Block) error - } -} - -func (ca *apiWrapper) diffPreCommits(ctx context.Context, actor address.Address, pre, cur types.TipSetKey) (*miner.PreCommitChanges, error) { - store := adt.WrapStore(ctx, cbor.NewCborStore(blockstore.NewAPIBlockstore(ca.api))) - - preAct, err := ca.api.StateGetActor(ctx, actor, pre) - if err != nil { - return nil, xerrors.Errorf("getting pre actor: %w", err) - } - curAct, err := ca.api.StateGetActor(ctx, actor, cur) - if err != nil { - return nil, xerrors.Errorf("getting cur actor: %w", err) - } - - preSt, err := miner.Load(store, preAct) - if err != nil { - return nil, xerrors.Errorf("loading miner actor: %w", err) - } - curSt, err := miner.Load(store, curAct) - if err != nil { - return nil, xerrors.Errorf("loading miner actor: %w", err) - } - - diff, err := miner.DiffPreCommits(preSt, curSt) - if err != nil { - return nil, xerrors.Errorf("diff precommits: %w", err) - } - - return diff, err -} diff --git a/markets/storageadapter/client.go b/markets/storageadapter/client.go deleted file mode 100644 index eaff4e166..000000000 --- a/markets/storageadapter/client.go +++ /dev/null @@ -1,446 +0,0 @@ -package storageadapter - -// this file implements storagemarket.StorageClientNode - -import ( - "bytes" - "context" - - "github.com/ipfs/go-cid" - "go.uber.org/fx" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - cborutil "github.com/filecoin-project/go-cbor-util" - "github.com/filecoin-project/go-fil-markets/shared" - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" - markettypes "github.com/filecoin-project/go-state-types/builtin/v9/market" - "github.com/filecoin-project/go-state-types/crypto" - "github.com/filecoin-project/go-state-types/exitcode" - builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" - marketactor "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/events" - "github.com/filecoin-project/lotus/chain/events/state" - "github.com/filecoin-project/lotus/chain/market" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/lib/sigs" - "github.com/filecoin-project/lotus/markets/utils" - "github.com/filecoin-project/lotus/node/impl/full" - "github.com/filecoin-project/lotus/node/modules/helpers" -) - -type ClientNodeAdapter struct { - *clientApi - - fundmgr *market.FundManager - ev *events.Events - dsMatcher *dealStateMatcher - scMgr *SectorCommittedManager -} - -type clientApi struct { - full.ChainAPI - full.StateAPI - full.MpoolAPI -} - -func NewClientNodeAdapter(mctx helpers.MetricsCtx, lc fx.Lifecycle, stateapi full.StateAPI, chain full.ChainAPI, mpool full.MpoolAPI, fundmgr *market.FundManager) (storagemarket.StorageClientNode, error) { - capi := &clientApi{chain, stateapi, mpool} - ctx := helpers.LifecycleCtx(mctx, lc) - - ev, err := events.NewEvents(ctx, capi) - if err != nil { - return nil, err - } - a := &ClientNodeAdapter{ - clientApi: capi, - - fundmgr: fundmgr, - ev: ev, - dsMatcher: newDealStateMatcher(state.NewStatePredicates(state.WrapFastAPI(capi))), - } - a.scMgr = NewSectorCommittedManager(ev, a, &apiWrapper{api: capi}) - return a, nil -} - -func (c *ClientNodeAdapter) ListStorageProviders(ctx context.Context, encodedTs shared.TipSetToken) ([]*storagemarket.StorageProviderInfo, error) { - tsk, err := types.TipSetKeyFromBytes(encodedTs) - if err != nil { - return nil, err - } - - addresses, err := c.StateListMiners(ctx, tsk) - if err != nil { - return nil, err - } - - var out []*storagemarket.StorageProviderInfo - - for _, addr := range addresses { - mi, err := c.GetMinerInfo(ctx, addr, encodedTs) - if err != nil { - return nil, err - } - - out = append(out, mi) - } - - return out, nil -} - -func (c *ClientNodeAdapter) VerifySignature(ctx context.Context, sig crypto.Signature, addr address.Address, input []byte, encodedTs shared.TipSetToken) (bool, error) { - addr, err := c.StateAccountKey(ctx, addr, types.EmptyTSK) - if err != nil { - return false, err - } - - err = sigs.Verify(&sig, addr, input) - return err == nil, err -} - -// Adds funds with the StorageMinerActor for a storage participant. Used by both providers and clients. -func (c *ClientNodeAdapter) AddFunds(ctx context.Context, addr address.Address, amount abi.TokenAmount) (cid.Cid, error) { - // (Provider Node API) - smsg, err := c.MpoolPushMessage(ctx, &types.Message{ - To: marketactor.Address, - From: addr, - Value: amount, - Method: builtin6.MethodsMarket.AddBalance, - }, nil) - if err != nil { - return cid.Undef, err - } - - return smsg.Cid(), nil -} - -func (c *ClientNodeAdapter) ReserveFunds(ctx context.Context, wallet, addr address.Address, amt abi.TokenAmount) (cid.Cid, error) { - return c.fundmgr.Reserve(ctx, wallet, addr, amt) -} - -func (c *ClientNodeAdapter) ReleaseFunds(ctx context.Context, addr address.Address, amt abi.TokenAmount) error { - return c.fundmgr.Release(addr, amt) -} - -func (c *ClientNodeAdapter) GetBalance(ctx context.Context, addr address.Address, encodedTs shared.TipSetToken) (storagemarket.Balance, error) { - tsk, err := types.TipSetKeyFromBytes(encodedTs) - if err != nil { - return storagemarket.Balance{}, err - } - - bal, err := c.StateMarketBalance(ctx, addr, tsk) - if err != nil { - return storagemarket.Balance{}, err - } - - return utils.ToSharedBalance(bal), nil -} - -// ValidatePublishedDeal validates that the provided deal has appeared on chain and references the same ClientDeal -// returns the Deal id if there is no error -// TODO: Don't return deal ID -func (c *ClientNodeAdapter) ValidatePublishedDeal(ctx context.Context, deal storagemarket.ClientDeal) (abi.DealID, error) { - log.Infow("DEAL ACCEPTED!") - - pubmsg, err := c.ChainGetMessage(ctx, *deal.PublishMessage) - if err != nil { - return 0, xerrors.Errorf("getting deal publish message: %w", err) - } - - mi, err := c.StateMinerInfo(ctx, deal.Proposal.Provider, types.EmptyTSK) - if err != nil { - return 0, xerrors.Errorf("getting miner worker failed: %w", err) - } - - fromid, err := c.StateLookupID(ctx, pubmsg.From, types.EmptyTSK) - if err != nil { - return 0, xerrors.Errorf("failed to resolve from msg ID addr: %w", err) - } - - var pubOk bool - pubAddrs := append([]address.Address{mi.Worker, mi.Owner}, mi.ControlAddresses...) - for _, a := range pubAddrs { - if fromid == a { - pubOk = true - break - } - } - - if !pubOk { - return 0, xerrors.Errorf("deal wasn't published by storage provider: from=%s, provider=%s,%+v", pubmsg.From, deal.Proposal.Provider, pubAddrs) - } - - if pubmsg.To != marketactor.Address { - return 0, xerrors.Errorf("deal publish message wasn't set to StorageMarket actor (to=%s)", pubmsg.To) - } - - if pubmsg.Method != builtin6.MethodsMarket.PublishStorageDeals { - return 0, xerrors.Errorf("deal publish message called incorrect method (method=%s)", pubmsg.Method) - } - - var params markettypes.PublishStorageDealsParams - if err := params.UnmarshalCBOR(bytes.NewReader(pubmsg.Params)); err != nil { - return 0, err - } - - dealIdx := -1 - for i, storageDeal := range params.Deals { - // TODO: make it less hacky - sd := storageDeal - eq, err := cborutil.Equals(&deal.ClientDealProposal, &sd) - if err != nil { - return 0, err - } - if eq { - dealIdx = i - break - } - } - - if dealIdx == -1 { - return 0, xerrors.Errorf("deal publish didn't contain our deal (message cid: %s)", deal.PublishMessage) - } - - // TODO: timeout - ret, err := c.StateWaitMsg(ctx, *deal.PublishMessage, build.MessageConfidence, api.LookbackNoLimit, true) - if err != nil { - return 0, xerrors.Errorf("waiting for deal publish message: %w", err) - } - if ret.Receipt.ExitCode != 0 { - return 0, xerrors.Errorf("deal publish failed: exit=%d", ret.Receipt.ExitCode) - } - - nv, err := c.StateNetworkVersion(ctx, ret.TipSet) - if err != nil { - return 0, xerrors.Errorf("getting network version: %w", err) - } - - res, err := marketactor.DecodePublishStorageDealsReturn(ret.Receipt.Return, nv) - if err != nil { - return 0, xerrors.Errorf("decoding deal publish return: %w", err) - } - - dealIDs, err := res.DealIDs() - if err != nil { - return 0, xerrors.Errorf("getting dealIDs: %w", err) - } - - if dealIdx >= len(params.Deals) { - return 0, xerrors.Errorf( - "deal index %d out of bounds of deals (len %d) in publish deals message %s", - dealIdx, len(params.Deals), pubmsg.Cid()) - } - - valid, outIdx, err := res.IsDealValid(uint64(dealIdx)) - if err != nil { - return 0, xerrors.Errorf("determining deal validity: %w", err) - } - - if !valid { - return 0, xerrors.New("deal was invalid at publication") - } - - return dealIDs[outIdx], nil -} - -var clientOverestimation = struct { - numerator int64 - denominator int64 -}{ - numerator: 12, - denominator: 10, -} - -func (c *ClientNodeAdapter) DealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, isVerified bool) (abi.TokenAmount, abi.TokenAmount, error) { - bounds, err := c.StateDealProviderCollateralBounds(ctx, size, isVerified, types.EmptyTSK) - if err != nil { - return abi.TokenAmount{}, abi.TokenAmount{}, err - } - - min := big.Mul(bounds.Min, big.NewInt(clientOverestimation.numerator)) - min = big.Div(min, big.NewInt(clientOverestimation.denominator)) - return min, bounds.Max, nil -} - -// TODO: Remove dealID parameter, change publishCid to be cid.Cid (instead of pointer) -func (c *ClientNodeAdapter) OnDealSectorPreCommitted(ctx context.Context, provider address.Address, dealID abi.DealID, proposal markettypes.DealProposal, publishCid *cid.Cid, cb storagemarket.DealSectorPreCommittedCallback) error { - return c.scMgr.OnDealSectorPreCommitted(ctx, provider, proposal, *publishCid, cb) -} - -// TODO: Remove dealID parameter, change publishCid to be cid.Cid (instead of pointer) -func (c *ClientNodeAdapter) OnDealSectorCommitted(ctx context.Context, provider address.Address, dealID abi.DealID, sectorNumber abi.SectorNumber, proposal markettypes.DealProposal, publishCid *cid.Cid, cb storagemarket.DealSectorCommittedCallback) error { - return c.scMgr.OnDealSectorCommitted(ctx, provider, sectorNumber, proposal, *publishCid, cb) -} - -// TODO: Replace dealID parameter with DealProposal -func (c *ClientNodeAdapter) OnDealExpiredOrSlashed(ctx context.Context, dealID abi.DealID, onDealExpired storagemarket.DealExpiredCallback, onDealSlashed storagemarket.DealSlashedCallback) error { - head, err := c.ChainHead(ctx) - if err != nil { - return xerrors.Errorf("client: failed to get chain head: %w", err) - } - - sd, err := c.StateMarketStorageDeal(ctx, dealID, head.Key()) - if err != nil { - return xerrors.Errorf("client: failed to look up deal %d on chain: %w", dealID, err) - } - - // Called immediately to check if the deal has already expired or been slashed - checkFunc := func(ctx context.Context, ts *types.TipSet) (done bool, more bool, err error) { - if ts == nil { - // keep listening for events - return false, true, nil - } - - // Check if the deal has already expired - if sd.Proposal.EndEpoch <= ts.Height() { - onDealExpired(nil) - return true, false, nil - } - - // If there is no deal assume it's already been slashed - if sd.State.SectorStartEpoch < 0 { - onDealSlashed(ts.Height(), nil) - return true, false, nil - } - - // No events have occurred yet, so return - // done: false, more: true (keep listening for events) - return false, true, nil - } - - // Called when there was a match against the state change we're looking for - // and the chain has advanced to the confidence height - stateChanged := func(ts *types.TipSet, ts2 *types.TipSet, states events.StateChange, h abi.ChainEpoch) (more bool, err error) { - // Check if the deal has already expired - if ts2 == nil || sd.Proposal.EndEpoch <= ts2.Height() { - onDealExpired(nil) - return false, nil - } - - // Timeout waiting for state change - if states == nil { - log.Error("timed out waiting for deal expiry") - return false, nil - } - - changedDeals, ok := states.(state.ChangedDeals) - if !ok { - panic("Expected state.ChangedDeals") - } - - deal, ok := changedDeals[dealID] - if !ok { - // No change to deal - return true, nil - } - - // Deal was slashed - if deal.To == nil { - onDealSlashed(ts2.Height(), nil) - return false, nil - } - - return true, nil - } - - // Called when there was a chain reorg and the state change was reverted - revert := func(ctx context.Context, ts *types.TipSet) error { - // TODO: Is it ok to just ignore this? - log.Warn("deal state reverted; TODO: actually handle this!") - return nil - } - - // Watch for state changes to the deal - match := c.dsMatcher.matcher(ctx, dealID) - - // Wait until after the end epoch for the deal and then timeout - timeout := (sd.Proposal.EndEpoch - head.Height()) + 1 - if err := c.ev.StateChanged(checkFunc, stateChanged, revert, int(build.MessageConfidence)+1, timeout, match); err != nil { - return xerrors.Errorf("failed to set up state changed handler: %w", err) - } - - return nil -} - -func (c *ClientNodeAdapter) SignProposal(ctx context.Context, signer address.Address, proposal markettypes.DealProposal) (*markettypes.ClientDealProposal, error) { - // TODO: output spec signed proposal - buf, err := cborutil.Dump(&proposal) - if err != nil { - return nil, err - } - - signer, err = c.StateAccountKey(ctx, signer, types.EmptyTSK) - if err != nil { - return nil, err - } - - sig, err := c.Wallet.WalletSign(ctx, signer, buf, api.MsgMeta{ - Type: api.MTDealProposal, - }) - if err != nil { - return nil, err - } - - return &markettypes.ClientDealProposal{ - Proposal: proposal, - ClientSignature: *sig, - }, nil -} - -func (c *ClientNodeAdapter) GetDefaultWalletAddress(ctx context.Context) (address.Address, error) { - addr, err := c.DefWallet.GetDefault() - return addr, err -} - -func (c *ClientNodeAdapter) GetChainHead(ctx context.Context) (shared.TipSetToken, abi.ChainEpoch, error) { - head, err := c.ChainHead(ctx) - if err != nil { - return nil, 0, err - } - - return head.Key().Bytes(), head.Height(), nil -} - -func (c *ClientNodeAdapter) WaitForMessage(ctx context.Context, mcid cid.Cid, cb func(code exitcode.ExitCode, bytes []byte, finalCid cid.Cid, err error) error) error { - receipt, err := c.StateWaitMsg(ctx, mcid, build.MessageConfidence, api.LookbackNoLimit, true) - if err != nil { - return cb(0, nil, cid.Undef, err) - } - return cb(receipt.Receipt.ExitCode, receipt.Receipt.Return, receipt.Message, nil) -} - -func (c *ClientNodeAdapter) GetMinerInfo(ctx context.Context, addr address.Address, encodedTs shared.TipSetToken) (*storagemarket.StorageProviderInfo, error) { - tsk, err := types.TipSetKeyFromBytes(encodedTs) - if err != nil { - return nil, err - } - mi, err := c.StateMinerInfo(ctx, addr, tsk) - if err != nil { - return nil, err - } - - out := utils.NewStorageProviderInfo(addr, mi.Worker, mi.SectorSize, *mi.PeerId, mi.Multiaddrs) - return &out, nil -} - -func (c *ClientNodeAdapter) SignBytes(ctx context.Context, signer address.Address, b []byte) (*crypto.Signature, error) { - signer, err := c.StateAccountKey(ctx, signer, types.EmptyTSK) - if err != nil { - return nil, err - } - - localSignature, err := c.Wallet.WalletSign(ctx, signer, b, api.MsgMeta{ - Type: api.MTUnknown, // TODO: pass type here - }) - if err != nil { - return nil, err - } - return localSignature, nil -} - -var _ storagemarket.StorageClientNode = &ClientNodeAdapter{} diff --git a/markets/storageadapter/client_blockstore.go b/markets/storageadapter/client_blockstore.go deleted file mode 100644 index dc7e3f82a..000000000 --- a/markets/storageadapter/client_blockstore.go +++ /dev/null @@ -1,102 +0,0 @@ -package storageadapter - -import ( - "sync" - - blockstore "github.com/ipfs/boxo/blockstore" - "github.com/ipfs/go-cid" - "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 deleted file mode 100644 index 6a274e593..000000000 --- a/markets/storageadapter/dealpublisher.go +++ /dev/null @@ -1,466 +0,0 @@ -package storageadapter - -import ( - "context" - "fmt" - "strings" - "sync" - "time" - - "github.com/ipfs/go-cid" - "go.uber.org/fx" - "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/builtin" - "github.com/filecoin-project/go-state-types/builtin/v9/market" - "github.com/filecoin-project/go-state-types/exitcode" - - "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/types" - "github.com/filecoin-project/lotus/node/config" - "github.com/filecoin-project/lotus/storage/ctladdr" -) - -type dealPublisherAPI interface { - ChainHead(context.Context) (*types.TipSet, error) - MpoolPushMessage(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec) (*types.SignedMessage, error) - StateMinerInfo(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error) - - WalletBalance(context.Context, address.Address) (types.BigInt, error) - WalletHas(context.Context, address.Address) (bool, error) - StateAccountKey(context.Context, address.Address, types.TipSetKey) (address.Address, error) - StateLookupID(context.Context, address.Address, types.TipSetKey) (address.Address, error) - StateCall(context.Context, *types.Message, types.TipSetKey) (*api.InvocResult, error) -} - -// DealPublisher batches deal publishing so that many deals can be included in -// a single publish message. This saves gas for miners that publish deals -// frequently. -// When a deal is submitted, the DealPublisher waits a configurable amount of -// time for other deals to be submitted before sending the publish message. -// There is a configurable maximum number of deals that can be included in one -// message. When the limit is reached the DealPublisher immediately submits a -// publish message with all deals in the queue. -type DealPublisher struct { - api dealPublisherAPI - as *ctladdr.AddressSelector - - ctx context.Context - Shutdown context.CancelFunc - - maxDealsPerPublishMsg uint64 - publishPeriod time.Duration - publishSpec *api.MessageSendSpec - - lk sync.Mutex - pending []*pendingDeal - cancelWaitForMoreDeals context.CancelFunc - publishPeriodStart time.Time - startEpochSealingBuffer abi.ChainEpoch -} - -// A deal that is queued to be published -type pendingDeal struct { - ctx context.Context - deal market.ClientDealProposal - Result chan publishResult -} - -// The result of publishing a deal -type publishResult struct { - msgCid cid.Cid - err error -} - -func newPendingDeal(ctx context.Context, deal market.ClientDealProposal) *pendingDeal { - return &pendingDeal{ - ctx: ctx, - deal: deal, - Result: make(chan publishResult), - } -} - -type PublishMsgConfig struct { - // The amount of time to wait for more deals to arrive before - // publishing - Period time.Duration - // The maximum number of deals to include in a single PublishStorageDeals - // message - MaxDealsPerMsg uint64 - // Minimum start epoch buffer to give time for sealing of sector with deal - StartEpochSealingBuffer uint64 -} - -func NewDealPublisher( - feeConfig *config.MinerFeeConfig, - publishMsgCfg PublishMsgConfig, -) func(lc fx.Lifecycle, full api.FullNode, as *ctladdr.AddressSelector) *DealPublisher { - return func(lc fx.Lifecycle, full api.FullNode, as *ctladdr.AddressSelector) *DealPublisher { - maxFee := abi.NewTokenAmount(0) - if feeConfig != nil { - maxFee = abi.TokenAmount(feeConfig.MaxPublishDealsFee) - } - publishSpec := &api.MessageSendSpec{MaxFee: maxFee} - dp := newDealPublisher(full, as, publishMsgCfg, publishSpec) - lc.Append(fx.Hook{ - OnStop: func(ctx context.Context) error { - dp.Shutdown() - return nil - }, - }) - return dp - } -} - -func newDealPublisher( - dpapi dealPublisherAPI, - as *ctladdr.AddressSelector, - publishMsgCfg PublishMsgConfig, - publishSpec *api.MessageSendSpec, -) *DealPublisher { - ctx, cancel := context.WithCancel(context.Background()) - return &DealPublisher{ - api: dpapi, - as: as, - ctx: ctx, - Shutdown: cancel, - maxDealsPerPublishMsg: publishMsgCfg.MaxDealsPerMsg, - publishPeriod: publishMsgCfg.Period, - startEpochSealingBuffer: abi.ChainEpoch(publishMsgCfg.StartEpochSealingBuffer), - publishSpec: publishSpec, - } -} - -// PendingDeals returns the list of deals that are queued up to be published -func (p *DealPublisher) PendingDeals() api.PendingDealInfo { - p.lk.Lock() - defer p.lk.Unlock() - - // Filter out deals whose context has been cancelled - deals := make([]*pendingDeal, 0, len(p.pending)) - for _, dl := range p.pending { - if dl.ctx.Err() == nil { - deals = append(deals, dl) - } - } - - pending := make([]market.ClientDealProposal, len(deals)) - for i, deal := range deals { - pending[i] = deal.deal - } - - return api.PendingDealInfo{ - Deals: pending, - PublishPeriodStart: p.publishPeriodStart, - PublishPeriod: p.publishPeriod, - } -} - -// ForcePublishPendingDeals publishes all pending deals without waiting for -// the publish period to elapse -func (p *DealPublisher) ForcePublishPendingDeals() { - p.lk.Lock() - defer p.lk.Unlock() - - log.Infof("force publishing deals") - p.publishAllDeals() -} - -func (p *DealPublisher) Publish(ctx context.Context, deal market.ClientDealProposal) (cid.Cid, error) { - pdeal := newPendingDeal(ctx, deal) - - // Add the deal to the queue - p.processNewDeal(pdeal) - - // Wait for the deal to be submitted - select { - case <-ctx.Done(): - return cid.Undef, ctx.Err() - case res := <-pdeal.Result: - return res.msgCid, res.err - } -} - -func (p *DealPublisher) processNewDeal(pdeal *pendingDeal) { - p.lk.Lock() - defer p.lk.Unlock() - - // Filter out any cancelled deals - p.filterCancelledDeals() - - // If all deals have been cancelled, clear the wait-for-deals timer - if len(p.pending) == 0 && p.cancelWaitForMoreDeals != nil { - p.cancelWaitForMoreDeals() - p.cancelWaitForMoreDeals = nil - } - - // Make sure the new deal hasn't been cancelled - if pdeal.ctx.Err() != nil { - return - } - - pdealPropCid, err := pdeal.deal.Proposal.Cid() - if err != nil { - log.Warn("failed to calculate proposal CID for new pending Deal with piece cid %s", pdeal.deal.Proposal.PieceCID) - return - } - - // Sanity check that new deal isn't already in the queue - for _, pd := range p.pending { - pdPropCid, err := pd.deal.Proposal.Cid() - if err != nil { - log.Warn("failed to calculate proposal CID for pending Deal already in publish queue with piece cid %s", pd.deal.Proposal.PieceCID) - return - } - - if pdPropCid.Equals(pdealPropCid) { - log.Warn("tried to process new pending deal with piece CID %s that is already in publish queue; returning", pdeal.deal.Proposal.PieceCID) - return - } - } - - // Add the new deal to the queue - p.pending = append(p.pending, pdeal) - 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 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 - } - - // Otherwise wait for more deals to arrive or the timeout to be reached - p.waitForMoreDeals() -} - -func (p *DealPublisher) waitForMoreDeals() { - // Check if we're already waiting for deals - if !p.publishPeriodStart.IsZero() { - elapsed := build.Clock.Since(p.publishPeriodStart) - log.Infof("%s elapsed of / %s until publish deals queue is published", - elapsed, p.publishPeriod) - return - } - - // 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) - - // Create the timer _before_ taking the current time so publishPeriod+timeout is always >= - // the actual timer timeout. - timer := build.Clock.Timer(p.publishPeriod) - - p.publishPeriodStart = build.Clock.Now() - p.cancelWaitForMoreDeals = cancel - - go func() { - select { - case <-ctx.Done(): - timer.Stop() - case <-timer.C: - p.lk.Lock() - defer p.lk.Unlock() - - // The timeout has expired so publish all pending deals - log.Infof("publish deals queue period of %s has expired, publishing deals", p.publishPeriod) - p.publishAllDeals() - } - }() -} - -func (p *DealPublisher) publishAllDeals() { - // If the timeout hasn't yet been cancelled, cancel it - if p.cancelWaitForMoreDeals != nil { - p.cancelWaitForMoreDeals() - p.cancelWaitForMoreDeals = nil - p.publishPeriodStart = time.Time{} - } - - // Filter out any deals that have been cancelled - p.filterCancelledDeals() - deals := p.pending - p.pending = nil - - // Send the publish message - go p.publishReady(deals) -} - -func (p *DealPublisher) publishReady(ready []*pendingDeal) { - if len(ready) == 0 { - return - } - - // onComplete is called when the publish message has been sent or there - // was an error - onComplete := func(pd *pendingDeal, msgCid cid.Cid, err error) { - // Send the publish result on the pending deal's Result channel - res := publishResult{ - msgCid: msgCid, - err: err, - } - select { - case <-p.ctx.Done(): - case <-pd.ctx.Done(): - case pd.Result <- res: - } - } - - // Validate each deal to make sure it can be published - validated := make([]*pendingDeal, 0, len(ready)) - deals := make([]market.ClientDealProposal, 0, len(ready)) - for _, pd := range ready { - // Validate the deal - if err := p.validateDeal(pd.deal); err != nil { - // Validation failed, complete immediately with an error - go onComplete(pd, cid.Undef, xerrors.Errorf("publish validation failed: %w", err)) - continue - } - - validated = append(validated, pd) - deals = append(deals, pd.deal) - } - - // Send the publish message - msgCid, err := p.publishDealProposals(deals) - - // Signal that each deal has been published - for _, pd := range validated { - go onComplete(pd, msgCid, err) - } -} - -// validateDeal checks that the deal proposal start epoch hasn't already -// elapsed -func (p *DealPublisher) validateDeal(deal market.ClientDealProposal) error { - start := time.Now() - - pcid, err := deal.Proposal.Cid() - if err != nil { - return xerrors.Errorf("computing proposal cid: %w", err) - } - - head, err := p.api.ChainHead(p.ctx) - if err != nil { - return err - } - if head.Height()+p.startEpochSealingBuffer > deal.Proposal.StartEpoch { - return xerrors.Errorf( - "cannot publish deal with piece CID %s: current epoch %d has passed deal proposal start epoch %d", - deal.Proposal.PieceCID, head.Height(), deal.Proposal.StartEpoch) - } - - mi, err := p.api.StateMinerInfo(p.ctx, deal.Proposal.Provider, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting provider info: %w", err) - } - - params, err := actors.SerializeParams(&market.PublishStorageDealsParams{ - Deals: []market.ClientDealProposal{deal}, - }) - if err != nil { - return xerrors.Errorf("serializing PublishStorageDeals params failed: %w", err) - } - - addr, _, err := p.as.AddressFor(p.ctx, p.api, mi, api.DealPublishAddr, big.Zero(), big.Zero()) - if err != nil { - return xerrors.Errorf("selecting address for publishing deals: %w", err) - } - - res, err := p.api.StateCall(p.ctx, &types.Message{ - To: builtin.StorageMarketActorAddr, - From: addr, - Value: types.NewInt(0), - Method: builtin.MethodsMarket.PublishStorageDeals, - Params: params, - }, head.Key()) - if err != nil { - return xerrors.Errorf("simulating deal publish message: %w", err) - } - if res.MsgRct.ExitCode != exitcode.Ok { - return xerrors.Errorf("simulating deal publish message: non-zero exitcode %s; message: %s", res.MsgRct.ExitCode, res.Error) - } - - took := time.Now().Sub(start) - log.Infow("validating deal", "took", took, "proposal", pcid) - - return nil -} - -// Sends the publish message -func (p *DealPublisher) publishDealProposals(deals []market.ClientDealProposal) (cid.Cid, error) { - if len(deals) == 0 { - return cid.Undef, nil - } - - log.Infof("publishing %d deals in publish deals queue with piece CIDs: %s", len(deals), pieceCids(deals)) - - provider := deals[0].Proposal.Provider - for _, dl := range deals { - if dl.Proposal.Provider != provider { - msg := fmt.Sprintf("publishing %d deals failed: ", len(deals)) + - "not all deals are for same provider: " + - fmt.Sprintf("deal with piece CID %s is for provider %s ", deals[0].Proposal.PieceCID, deals[0].Proposal.Provider) + - fmt.Sprintf("but deal with piece CID %s is for provider %s", dl.Proposal.PieceCID, dl.Proposal.Provider) - return cid.Undef, xerrors.Errorf(msg) - } - } - - mi, err := p.api.StateMinerInfo(p.ctx, provider, types.EmptyTSK) - if err != nil { - return cid.Undef, err - } - - params, err := actors.SerializeParams(&market.PublishStorageDealsParams{ - Deals: deals, - }) - - if err != nil { - return cid.Undef, xerrors.Errorf("serializing PublishStorageDeals params failed: %w", err) - } - - addr, _, err := p.as.AddressFor(p.ctx, p.api, mi, api.DealPublishAddr, big.Zero(), big.Zero()) - if err != nil { - return cid.Undef, xerrors.Errorf("selecting address for publishing deals: %w", err) - } - - smsg, err := p.api.MpoolPushMessage(p.ctx, &types.Message{ - To: builtin.StorageMarketActorAddr, - From: addr, - Value: types.NewInt(0), - Method: builtin.MethodsMarket.PublishStorageDeals, - Params: params, - }, p.publishSpec) - - if err != nil { - return cid.Undef, err - } - return smsg.Cid(), nil -} - -func pieceCids(deals []market.ClientDealProposal) string { - cids := make([]string, 0, len(deals)) - for _, dl := range deals { - cids = append(cids, dl.Proposal.PieceCID.String()) - } - return strings.Join(cids, ", ") -} - -// filter out deals that have been cancelled -func (p *DealPublisher) filterCancelledDeals() { - filtered := p.pending[:0] - for _, pd := range p.pending { - if pd.ctx.Err() != nil { - continue - } - filtered = append(filtered, pd) - } - p.pending = filtered -} diff --git a/markets/storageadapter/dealpublisher_test.go b/markets/storageadapter/dealpublisher_test.go deleted file mode 100644 index 35169bf41..000000000 --- a/markets/storageadapter/dealpublisher_test.go +++ /dev/null @@ -1,423 +0,0 @@ -// stm: #unit -package storageadapter - -import ( - "bytes" - "context" - "testing" - "time" - - "github.com/ipfs/go-cid" - "github.com/raulk/clock" - "github.com/stretchr/testify/require" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - markettypes "github.com/filecoin-project/go-state-types/builtin/v9/market" - "github.com/filecoin-project/go-state-types/crypto" - "github.com/filecoin-project/go-state-types/exitcode" - tutils "github.com/filecoin-project/specs-actors/v2/support/testing" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/types" -) - -func TestDealPublisher(t *testing.T) { - //stm: @MARKET_DEAL_PUBLISHER_PUBLISH_001, @MARKET_DEAL_PUBLISHER_GET_PENDING_DEALS_001 - oldClock := build.Clock - t.Cleanup(func() { build.Clock = oldClock }) - mc := clock.NewMock() - build.Clock = mc - - testCases := []struct { - name string - publishPeriod time.Duration - maxDealsPerMsg uint64 - dealCountWithinPublishPeriod int - ctxCancelledWithinPublishPeriod int - expiredDeals int - dealCountAfterPublishPeriod int - expectedDealsPerMsg []int - failOne bool - }{{ - name: "publish one deal within publish period", - publishPeriod: 10 * time.Millisecond, - maxDealsPerMsg: 5, - dealCountWithinPublishPeriod: 1, - dealCountAfterPublishPeriod: 0, - expectedDealsPerMsg: []int{1}, - }, { - name: "publish two deals within publish period", - publishPeriod: 10 * time.Millisecond, - maxDealsPerMsg: 5, - dealCountWithinPublishPeriod: 2, - dealCountAfterPublishPeriod: 0, - expectedDealsPerMsg: []int{2}, - }, { - name: "publish one deal within publish period, and one after", - publishPeriod: 10 * time.Millisecond, - maxDealsPerMsg: 5, - dealCountWithinPublishPeriod: 1, - dealCountAfterPublishPeriod: 1, - expectedDealsPerMsg: []int{1, 1}, - }, { - name: "publish deals that exceed max deals per message within publish period, and one after", - publishPeriod: 10 * time.Millisecond, - maxDealsPerMsg: 2, - dealCountWithinPublishPeriod: 3, - dealCountAfterPublishPeriod: 1, - expectedDealsPerMsg: []int{2, 1, 1}, - }, { - name: "ignore deals with cancelled context", - publishPeriod: 10 * time.Millisecond, - maxDealsPerMsg: 5, - dealCountWithinPublishPeriod: 2, - ctxCancelledWithinPublishPeriod: 2, - dealCountAfterPublishPeriod: 1, - expectedDealsPerMsg: []int{2, 1}, - }, { - name: "ignore expired deals", - publishPeriod: 10 * time.Millisecond, - maxDealsPerMsg: 5, - dealCountWithinPublishPeriod: 2, - expiredDeals: 2, - dealCountAfterPublishPeriod: 1, - expectedDealsPerMsg: []int{2, 1}, - }, { - name: "zero config", - publishPeriod: 0, - maxDealsPerMsg: 0, - dealCountWithinPublishPeriod: 2, - ctxCancelledWithinPublishPeriod: 0, - dealCountAfterPublishPeriod: 2, - expectedDealsPerMsg: []int{1, 1, 1, 1}, - }, { - name: "one deal failing doesn't fail the entire batch", - publishPeriod: 10 * time.Millisecond, - maxDealsPerMsg: 5, - dealCountWithinPublishPeriod: 2, - dealCountAfterPublishPeriod: 0, - failOne: true, - expectedDealsPerMsg: []int{1}, - }} - - 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 - dp := newDealPublisher(dpapi, nil, PublishMsgConfig{ - Period: tc.publishPeriod, - MaxDealsPerMsg: tc.maxDealsPerMsg, - }, &api.MessageSendSpec{MaxFee: abi.NewTokenAmount(1)}) - - // Keep a record of the deals that were submitted to be published - var dealsToPublish []markettypes.ClientDealProposal - - // Publish deals within publish period - for i := 0; i < tc.dealCountWithinPublishPeriod; i++ { - if tc.failOne && i == 1 { - publishDeal(t, dp, i, false, false) - } else { - deal := publishDeal(t, dp, 0, false, false) - dealsToPublish = append(dealsToPublish, deal) - } - } - for i := 0; i < tc.ctxCancelledWithinPublishPeriod; i++ { - publishDeal(t, dp, 0, true, false) - } - for i := 0; i < tc.expiredDeals; i++ { - publishDeal(t, dp, 0, false, true) - } - - // Wait until publish period has elapsed - 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++ { - deal := publishDeal(t, dp, 0, false, false) - 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) - }) - } -} - -func TestForcePublish(t *testing.T) { - //stm: @MARKET_DEAL_PUBLISHER_PUBLISH_001, @MARKET_DEAL_PUBLISHER_GET_PENDING_DEALS_001 - //stm: @MARKET_DEAL_PUBLISHER_FORCE_PUBLISH_ALL_001 - dpapi := newDPAPI(t) - - // Create a deal publisher - start := build.Clock.Now() - publishPeriod := time.Hour - dp := newDealPublisher(dpapi, nil, PublishMsgConfig{ - Period: publishPeriod, - MaxDealsPerMsg: 10, - }, &api.MessageSendSpec{MaxFee: abi.NewTokenAmount(1)}) - - // Queue three deals for publishing, one with a cancelled context - var dealsToPublish []markettypes.ClientDealProposal - // 1. Regular deal - deal := publishDeal(t, dp, 0, false, false) - dealsToPublish = append(dealsToPublish, deal) - // 2. Deal with cancelled context - publishDeal(t, dp, 0, true, false) - // 3. Regular deal - deal = publishDeal(t, dp, 0, false, false) - dealsToPublish = append(dealsToPublish, deal) - - // Allow a moment for them to be queued - build.Clock.Sleep(10 * time.Millisecond) - - // Should be two deals in the pending deals list - // (deal with cancelled context is ignored) - pendingInfo := dp.PendingDeals() - 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(build.Clock.Now())) - - // Force publish all pending deals - dp.ForcePublishPendingDeals() - - // Should be no pending deals - pendingInfo = dp.PendingDeals() - require.Len(t, pendingInfo.Deals, 0) - - // Make sure the expected deals were published - checkPublishedDeals(t, dpapi, dealsToPublish, []int{2}) -} - -func publishDeal(t *testing.T, dp *DealPublisher, invalid int, ctxCancelled bool, expired bool) markettypes.ClientDealProposal { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - pctx := ctx - if ctxCancelled { - pctx, cancel = context.WithCancel(ctx) - cancel() - } - - startEpoch := abi.ChainEpoch(20) - if expired { - startEpoch = abi.ChainEpoch(5) - } - deal := markettypes.ClientDealProposal{ - Proposal: markettypes.DealProposal{ - PieceCID: generateCids(1)[0], - Client: getClientActor(t), - Provider: getProviderActor(t), - StartEpoch: startEpoch, - EndEpoch: abi.ChainEpoch(120), - PieceSize: abi.PaddedPieceSize(invalid), // pass invalid into StateCall below - }, - ClientSignature: crypto.Signature{ - Type: crypto.SigTypeSecp256k1, - Data: []byte("signature data"), - }, - } - - go func() { - _, err := dp.Publish(pctx, deal) - - // If the test has completed just bail out without checking for errors - if ctx.Err() != nil { - return - } - - if ctxCancelled || expired || invalid == 1 { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }() - - return deal -} - -func checkPublishedDeals(t *testing.T, dpapi *dpAPI, dealsToPublish []markettypes.ClientDealProposal, expectedDealsPerMsg []int) { - // For each message that was expected to be sent - var publishedDeals []markettypes.ClientDealProposal - for _, expectedDealsInMsg := range expectedDealsPerMsg { - // Should have called StateMinerInfo with the provider address - stateMinerInfoAddr := <-dpapi.stateMinerInfoCalls - require.Equal(t, getProviderActor(t), stateMinerInfoAddr) - - // Check the fields of the message that was sent - msg := <-dpapi.pushedMsgs - require.Equal(t, getWorkerActor(t), msg.From) - require.Equal(t, market.Address, msg.To) - require.Equal(t, market.Methods.PublishStorageDeals, msg.Method) - - // Check that the expected number of deals was included in the message - var params markettypes.PublishStorageDealsParams - err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)) - require.NoError(t, err) - require.Len(t, params.Deals, expectedDealsInMsg) - - // Keep track of the deals that were sent - for _, d := range params.Deals { - publishedDeals = append(publishedDeals, d) - } - } - - // Verify that all deals that were submitted to be published were - // sent out (we do this by ensuring all the piece CIDs are present) - require.True(t, matchPieceCids(publishedDeals, dealsToPublish)) -} - -func matchPieceCids(sent []markettypes.ClientDealProposal, exp []markettypes.ClientDealProposal) bool { - cidsA := dealPieceCids(sent) - cidsB := dealPieceCids(exp) - - if len(cidsA) != len(cidsB) { - return false - } - - s1 := cid.NewSet() - for _, c := range cidsA { - s1.Add(c) - } - - for _, c := range cidsB { - if !s1.Has(c) { - return false - } - } - - return true -} - -func dealPieceCids(deals []markettypes.ClientDealProposal) []cid.Cid { - cids := make([]cid.Cid, 0, len(deals)) - for _, dl := range deals { - cids = append(cids, dl.Proposal.PieceCID) - } - return cids -} - -type dpAPI struct { - t *testing.T - worker address.Address - - stateMinerInfoCalls chan address.Address - pushedMsgs chan *types.Message -} - -func newDPAPI(t *testing.T) *dpAPI { - return &dpAPI{ - t: t, - worker: getWorkerActor(t), - stateMinerInfoCalls: make(chan address.Address, 128), - pushedMsgs: make(chan *types.Message, 128), - } -} - -func (d *dpAPI) ChainHead(ctx context.Context) (*types.TipSet, error) { - dummyCid, err := cid.Parse("bafkqaaa") - require.NoError(d.t, err) - return types.NewTipSet([]*types.BlockHeader{{ - Miner: tutils.NewActorAddr(d.t, "miner"), - Height: abi.ChainEpoch(10), - ParentStateRoot: dummyCid, - Messages: dummyCid, - ParentMessageReceipts: dummyCid, - BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, - BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, - }}) -} - -func (d *dpAPI) StateMinerInfo(ctx context.Context, address address.Address, key types.TipSetKey) (api.MinerInfo, error) { - d.stateMinerInfoCalls <- address - return api.MinerInfo{Worker: d.worker}, nil -} - -func (d *dpAPI) MpoolPushMessage(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec) (*types.SignedMessage, error) { - d.pushedMsgs <- msg - return &types.SignedMessage{Message: *msg}, nil -} - -func (d *dpAPI) WalletBalance(ctx context.Context, a address.Address) (types.BigInt, error) { - panic("don't call me") -} - -func (d *dpAPI) WalletHas(ctx context.Context, a address.Address) (bool, error) { - panic("don't call me") -} - -func (d *dpAPI) StateAccountKey(ctx context.Context, a address.Address, key types.TipSetKey) (address.Address, error) { - panic("don't call me") -} - -func (d *dpAPI) StateLookupID(ctx context.Context, a address.Address, key types.TipSetKey) (address.Address, error) { - panic("don't call me") -} - -func (d *dpAPI) StateCall(ctx context.Context, message *types.Message, key types.TipSetKey) (*api.InvocResult, error) { - var p markettypes.PublishStorageDealsParams - if err := p.UnmarshalCBOR(bytes.NewReader(message.Params)); err != nil { - return nil, xerrors.Errorf("unmarshal market params: %w", err) - } - - exit := exitcode.Ok - if p.Deals[0].Proposal.PieceSize == 1 { - exit = exitcode.ErrIllegalState - } - return &api.InvocResult{MsgRct: &types.MessageReceipt{ExitCode: exit}}, nil -} - -func getClientActor(t *testing.T) address.Address { - return tutils.NewActorAddr(t, "client") -} - -func getWorkerActor(t *testing.T) address.Address { - return tutils.NewActorAddr(t, "worker") -} - -func getProviderActor(t *testing.T) address.Address { - return tutils.NewActorAddr(t, "provider") -} diff --git a/markets/storageadapter/dealstatematcher.go b/markets/storageadapter/dealstatematcher.go deleted file mode 100644 index 8d5598eae..000000000 --- a/markets/storageadapter/dealstatematcher.go +++ /dev/null @@ -1,85 +0,0 @@ -package storageadapter - -import ( - "context" - "sync" - - "github.com/filecoin-project/go-state-types/abi" - - actorsmarket "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/events" - "github.com/filecoin-project/lotus/chain/events/state" - "github.com/filecoin-project/lotus/chain/types" -) - -// dealStateMatcher caches the DealStates for the most recent -// old/new tipset combination -type dealStateMatcher struct { - preds *state.StatePredicates - - lk sync.Mutex - oldTsk types.TipSetKey - newTsk types.TipSetKey - oldDealStateRoot actorsmarket.DealStates - newDealStateRoot actorsmarket.DealStates -} - -func newDealStateMatcher(preds *state.StatePredicates) *dealStateMatcher { - return &dealStateMatcher{preds: preds} -} - -// matcher returns a function that checks if the state of the given dealID -// has changed. -// It caches the DealStates for the most recent old/new tipset combination. -func (mc *dealStateMatcher) matcher(ctx context.Context, dealID abi.DealID) events.StateMatchFunc { - // The function that is called to check if the deal state has changed for - // the target deal ID - dealStateChangedForID := mc.preds.DealStateChangedForIDs([]abi.DealID{dealID}) - - // The match function is called by the events API to check if there's - // been a state change for the deal with the target deal ID - match := func(oldTs, newTs *types.TipSet) (bool, events.StateChange, error) { - mc.lk.Lock() - defer mc.lk.Unlock() - - // Check if we've already fetched the DealStates for the given tipsets - if mc.oldTsk == oldTs.Key() && mc.newTsk == newTs.Key() { - // If we fetch the DealStates and there is no difference between - // them, they are stored as nil. So we can just bail out. - if mc.oldDealStateRoot == nil || mc.newDealStateRoot == nil { - return false, nil, nil - } - - // Check if the deal state has changed for the target ID - return dealStateChangedForID(ctx, mc.oldDealStateRoot, mc.newDealStateRoot) - } - - // We haven't already fetched the DealStates for the given tipsets, so - // do so now - - // Replace dealStateChangedForID with a function that records the - // DealStates so that we can cache them - var oldDealStateRootSaved, newDealStateRootSaved actorsmarket.DealStates - recorder := func(ctx context.Context, oldDealStateRoot, newDealStateRoot actorsmarket.DealStates) (changed bool, user state.UserData, err error) { - // Record DealStates - oldDealStateRootSaved = oldDealStateRoot - newDealStateRootSaved = newDealStateRoot - - return dealStateChangedForID(ctx, oldDealStateRoot, newDealStateRoot) - } - - // Call the match function - dealDiff := mc.preds.OnStorageMarketActorChanged( - mc.preds.OnDealStateChanged(recorder)) - matched, data, err := dealDiff(ctx, oldTs.Key(), newTs.Key()) - - // Save the recorded DealStates for the tipsets - mc.oldTsk = oldTs.Key() - mc.newTsk = newTs.Key() - mc.oldDealStateRoot = oldDealStateRootSaved - mc.newDealStateRoot = newDealStateRootSaved - - return matched, data, err - } - return match -} diff --git a/markets/storageadapter/dealstatematcher_test.go b/markets/storageadapter/dealstatematcher_test.go deleted file mode 100644 index 9a46e4af9..000000000 --- a/markets/storageadapter/dealstatematcher_test.go +++ /dev/null @@ -1,155 +0,0 @@ -// stm: #unit -package storageadapter - -import ( - "context" - "testing" - - "github.com/ipfs/go-cid" - cbornode "github.com/ipfs/go-ipld-cbor" - "github.com/stretchr/testify/require" - "golang.org/x/sync/errgroup" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" - market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" - adt2 "github.com/filecoin-project/specs-actors/v2/actors/util/adt" - - bstore "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/chain/events" - "github.com/filecoin-project/lotus/chain/events/state" - test "github.com/filecoin-project/lotus/chain/events/state/mock" - "github.com/filecoin-project/lotus/chain/types" -) - -func TestDealStateMatcher(t *testing.T) { - //stm: @CHAIN_STATE_GET_ACTOR_001 - ctx := context.Background() - bs := bstore.NewMemorySync() - store := adt2.WrapStore(ctx, cbornode.NewCborStore(bs)) - - deal1 := &market2.DealState{ - SectorStartEpoch: 1, - LastUpdatedEpoch: 2, - } - deal2 := &market2.DealState{ - SectorStartEpoch: 4, - LastUpdatedEpoch: 5, - } - deal3 := &market2.DealState{ - SectorStartEpoch: 7, - LastUpdatedEpoch: 8, - } - deals1 := map[abi.DealID]*market2.DealState{ - abi.DealID(1): deal1, - } - deals2 := map[abi.DealID]*market2.DealState{ - abi.DealID(1): deal2, - } - deals3 := map[abi.DealID]*market2.DealState{ - abi.DealID(1): deal3, - } - - deal1StateC := createMarketState(ctx, t, store, deals1) - deal2StateC := createMarketState(ctx, t, store, deals2) - deal3StateC := createMarketState(ctx, t, store, deals3) - - minerAddr, err := address.NewFromString("t00") - require.NoError(t, err) - ts1, err := test.MockTipset(minerAddr, 1) - require.NoError(t, err) - ts2, err := test.MockTipset(minerAddr, 2) - require.NoError(t, err) - ts3, err := test.MockTipset(minerAddr, 3) - require.NoError(t, err) - - api := test.NewMockAPI(bs) - api.SetActor(ts1.Key(), &types.Actor{Code: builtin2.StorageMarketActorCodeID, Head: deal1StateC}) - api.SetActor(ts2.Key(), &types.Actor{Code: builtin2.StorageMarketActorCodeID, Head: deal2StateC}) - api.SetActor(ts3.Key(), &types.Actor{Code: builtin2.StorageMarketActorCodeID, Head: deal3StateC}) - - t.Run("caching", func(t *testing.T) { - dsm := newDealStateMatcher(state.NewStatePredicates(api)) - matcher := dsm.matcher(ctx, abi.DealID(1)) - - // Call matcher with tipsets that have the same state - ok, stateChange, err := matcher(ts1, ts1) - require.NoError(t, err) - require.False(t, ok) - require.Nil(t, stateChange) - // Should call StateGetActor once for each tipset - require.Equal(t, 2, api.StateGetActorCallCount()) - - // Call matcher with tipsets that have different state - api.ResetCallCounts() - ok, stateChange, err = matcher(ts1, ts2) - require.NoError(t, err) - require.True(t, ok) - require.NotNil(t, stateChange) - // Should call StateGetActor once for each tipset - require.Equal(t, 2, api.StateGetActorCallCount()) - - // Call matcher again with the same tipsets as above, should be cached - api.ResetCallCounts() - ok, stateChange, err = matcher(ts1, ts2) - require.NoError(t, err) - require.True(t, ok) - require.NotNil(t, stateChange) - // Should not call StateGetActor (because it should hit the cache) - require.Equal(t, 0, api.StateGetActorCallCount()) - - // Call matcher with different tipsets, should not be cached - api.ResetCallCounts() - ok, stateChange, err = matcher(ts2, ts3) - require.NoError(t, err) - require.True(t, ok) - require.NotNil(t, stateChange) - // Should call StateGetActor once for each tipset - require.Equal(t, 2, api.StateGetActorCallCount()) - }) - - t.Run("parallel", func(t *testing.T) { - api.ResetCallCounts() - dsm := newDealStateMatcher(state.NewStatePredicates(api)) - matcher := dsm.matcher(ctx, abi.DealID(1)) - - // Call matcher with lots of go-routines in parallel - var eg errgroup.Group - res := make([]struct { - ok bool - stateChange events.StateChange - }, 20) - for i := 0; i < len(res); i++ { - i := i - eg.Go(func() error { - ok, stateChange, err := matcher(ts1, ts2) - res[i].ok = ok - res[i].stateChange = stateChange - return err - }) - } - err := eg.Wait() - require.NoError(t, err) - - // All go-routines should have got the same (cached) result - for i := 1; i < len(res); i++ { - require.Equal(t, res[i].ok, res[i-1].ok) - require.Equal(t, res[i].stateChange, res[i-1].stateChange) - } - - // Only one go-routine should have called StateGetActor - // (once for each tipset) - require.Equal(t, 2, api.StateGetActorCallCount()) - }) -} - -func createMarketState(ctx context.Context, t *testing.T, store adt2.Store, deals map[abi.DealID]*market2.DealState) cid.Cid { - dealRootCid := test.CreateDealAMT(ctx, t, store, deals) - state := test.CreateEmptyMarketState(t, store) - state.States = dealRootCid - - stateC, err := store.Put(ctx, state) - require.NoError(t, err) - return stateC -} diff --git a/markets/storageadapter/ondealsectorcommitted.go b/markets/storageadapter/ondealsectorcommitted.go deleted file mode 100644 index 54ddb73b3..000000000 --- a/markets/storageadapter/ondealsectorcommitted.go +++ /dev/null @@ -1,418 +0,0 @@ -package storageadapter - -import ( - "bytes" - "context" - "sync" - - "github.com/ipfs/go-cid" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-bitfield" - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/builtin" - miner2 "github.com/filecoin-project/go-state-types/builtin/v11/miner" - "github.com/filecoin-project/go-state-types/builtin/v8/miner" - "github.com/filecoin-project/go-state-types/builtin/v9/market" - - "github.com/filecoin-project/lotus/build" - lminer "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/events" - "github.com/filecoin-project/lotus/chain/types" - pipeline "github.com/filecoin-project/lotus/storage/pipeline" -) - -type eventsCalledAPI interface { - Called(ctx context.Context, check events.CheckFunc, msgHnd events.MsgHandler, rev events.RevertHandler, confidence int, timeout abi.ChainEpoch, mf events.MsgMatchFunc) error -} - -type dealInfoAPI interface { - GetCurrentDealInfo(ctx context.Context, tsk types.TipSetKey, proposal *market.DealProposal, publishCid cid.Cid) (pipeline.CurrentDealInfo, error) -} - -type diffPreCommitsAPI interface { - diffPreCommits(ctx context.Context, actor address.Address, pre, cur types.TipSetKey) (*lminer.PreCommitChanges, error) -} - -type SectorCommittedManager struct { - ev eventsCalledAPI - dealInfo dealInfoAPI - dpc diffPreCommitsAPI -} - -func NewSectorCommittedManager(ev eventsCalledAPI, tskAPI pipeline.CurrentDealInfoAPI, dpcAPI diffPreCommitsAPI) *SectorCommittedManager { - dim := &pipeline.CurrentDealInfoManager{ - CDAPI: tskAPI, - } - return newSectorCommittedManager(ev, dim, dpcAPI) -} - -func newSectorCommittedManager(ev eventsCalledAPI, dealInfo dealInfoAPI, dpcAPI diffPreCommitsAPI) *SectorCommittedManager { - return &SectorCommittedManager{ - ev: ev, - dealInfo: dealInfo, - dpc: dpcAPI, - } -} - -func (mgr *SectorCommittedManager) OnDealSectorPreCommitted(ctx context.Context, provider address.Address, proposal market.DealProposal, publishCid cid.Cid, callback storagemarket.DealSectorPreCommittedCallback) error { - // Ensure callback is only called once - var once sync.Once - cb := func(sectorNumber abi.SectorNumber, isActive bool, err error) { - once.Do(func() { - callback(sectorNumber, isActive, err) - }) - } - - // First check if the deal is already active, and if so, bail out - checkFunc := func(ctx context.Context, ts *types.TipSet) (done bool, more bool, err error) { - dealInfo, isActive, err := mgr.checkIfDealAlreadyActive(ctx, ts, &proposal, publishCid) - if err != nil { - // Note: the error returned from here will end up being returned - // from OnDealSectorPreCommitted so no need to call the callback - // with the error - return false, false, xerrors.Errorf("failed to check deal activity: %w", err) - } - - if isActive { - // Deal is already active, bail out - cb(0, true, nil) - return true, false, nil - } - - // Check that precommits which landed between when the deal was published - // and now don't already contain the deal we care about. - // (this can happen when the precommit lands vary quickly (in tests), or - // when the client node was down after the deal was published, and when - // the precommit containing it landed on chain) - - diff, err := mgr.dpc.diffPreCommits(ctx, provider, dealInfo.PublishMsgTipSet, ts.Key()) - if err != nil { - return false, false, xerrors.Errorf("failed to diff precommits: %w", err) - } - - for _, info := range diff.Added { - for _, d := range info.Info.DealIDs { - if d == dealInfo.DealID { - cb(info.Info.SectorNumber, false, nil) - return true, false, nil - } - } - } - - // Not yet active, start matching against incoming messages - return false, true, nil - } - - // Watch for a pre-commit message to the provider. - matchEvent := func(msg *types.Message) (bool, error) { - matched := msg.To == provider && (msg.Method == builtin.MethodsMiner.PreCommitSector || - msg.Method == builtin.MethodsMiner.PreCommitSectorBatch || - msg.Method == builtin.MethodsMiner.PreCommitSectorBatch2 || - msg.Method == builtin.MethodsMiner.ProveReplicaUpdates) - return matched, nil - } - - // The deal must be accepted by the deal proposal start epoch, so timeout - // if the chain reaches that epoch - timeoutEpoch := proposal.StartEpoch + 1 - - // Check if the message params included the deal ID we're looking for. - called := func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error) { - defer func() { - if err != nil { - cb(0, false, xerrors.Errorf("handling applied event: %w", err)) - } - }() - - // If the deal hasn't been activated by the proposed start epoch, the - // deal will timeout (when msg == nil it means the timeout epoch was reached) - if msg == nil { - err = xerrors.Errorf("deal with piece CID %s was not activated by proposed deal start epoch %d", proposal.PieceCID, proposal.StartEpoch) - return false, err - } - - // Ignore the pre-commit message if it was not executed successfully - if rec.ExitCode != 0 { - return true, nil - } - - // When there is a reorg, the deal ID may change, so get the - // current deal ID from the publish message CID - res, err := mgr.dealInfo.GetCurrentDealInfo(ctx, ts.Key(), &proposal, publishCid) - if err != nil { - return false, xerrors.Errorf("failed to get dealinfo: %w", err) - } - - // If this is a replica update method that succeeded the deal is active - if msg.Method == builtin.MethodsMiner.ProveReplicaUpdates { - sn, err := dealSectorInReplicaUpdateSuccess(msg, rec, res) - if err != nil { - return false, err - } - if sn != nil { - cb(*sn, true, nil) - return false, nil - } - // Didn't find the deal ID in this message, so keep looking - return true, nil - } - - // Extract the message parameters - sn, err := dealSectorInPreCommitMsg(msg, res) - if err != nil { - return false, xerrors.Errorf("failed to extract message params: %w", err) - } - - if sn != nil { - cb(*sn, false, nil) - } - - // Didn't find the deal ID in this message, so keep looking - return true, nil - } - - revert := func(ctx context.Context, ts *types.TipSet) error { - log.Warn("deal pre-commit reverted; TODO: actually handle this!") - // TODO: Just go back to DealSealing? - return nil - } - - if err := mgr.ev.Called(ctx, checkFunc, called, revert, int(build.MessageConfidence+1), timeoutEpoch, matchEvent); err != nil { - return xerrors.Errorf("failed to set up called handler: %w", err) - } - - return nil -} - -func (mgr *SectorCommittedManager) OnDealSectorCommitted(ctx context.Context, provider address.Address, sectorNumber abi.SectorNumber, proposal market.DealProposal, publishCid cid.Cid, callback storagemarket.DealSectorCommittedCallback) error { - // Ensure callback is only called once - var once sync.Once - cb := func(err error) { - once.Do(func() { - callback(err) - }) - } - - // First check if the deal is already active, and if so, bail out - checkFunc := func(ctx context.Context, ts *types.TipSet) (done bool, more bool, err error) { - _, isActive, err := mgr.checkIfDealAlreadyActive(ctx, ts, &proposal, publishCid) - if err != nil { - // Note: the error returned from here will end up being returned - // from OnDealSectorCommitted so no need to call the callback - // with the error - return false, false, err - } - - if isActive { - // Deal is already active, bail out - cb(nil) - return true, false, nil - } - - // Not yet active, start matching against incoming messages - return false, true, nil - } - - // Match a prove-commit sent to the provider with the given sector number - matchEvent := func(msg *types.Message) (matched bool, err error) { - if msg.To != provider { - return false, nil - } - - return sectorInCommitMsg(msg, sectorNumber) - } - - // The deal must be accepted by the deal proposal start epoch, so timeout - // if the chain reaches that epoch - timeoutEpoch := proposal.StartEpoch + 1 - - called := func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error) { - defer func() { - if err != nil { - cb(xerrors.Errorf("handling applied event: %w", err)) - } - }() - - // If the deal hasn't been activated by the proposed start epoch, the - // deal will timeout (when msg == nil it means the timeout epoch was reached) - if msg == nil { - err := xerrors.Errorf("deal with piece CID %s was not activated by proposed deal start epoch %d", proposal.PieceCID, proposal.StartEpoch) - return false, err - } - - // Ignore the prove-commit message if it was not executed successfully - if rec.ExitCode != 0 { - return true, nil - } - - // Get the deal info - res, err := mgr.dealInfo.GetCurrentDealInfo(ctx, ts.Key(), &proposal, publishCid) - if err != nil { - return false, xerrors.Errorf("failed to look up deal on chain: %w", err) - } - - // Make sure the deal is active - if res.MarketDeal.State.SectorStartEpoch < 1 { - return false, xerrors.Errorf("deal wasn't active: deal=%d, parentState=%s, h=%d", res.DealID, ts.ParentState(), ts.Height()) - } - - log.Infof("Storage deal %d activated at epoch %d", res.DealID, res.MarketDeal.State.SectorStartEpoch) - - cb(nil) - - return false, nil - } - - revert := func(ctx context.Context, ts *types.TipSet) error { - log.Warn("deal activation reverted; TODO: actually handle this!") - // TODO: Just go back to DealSealing? - return nil - } - - if err := mgr.ev.Called(ctx, checkFunc, called, revert, int(build.MessageConfidence+1), timeoutEpoch, matchEvent); err != nil { - return xerrors.Errorf("failed to set up called handler: %w", err) - } - - return nil -} - -func dealSectorInReplicaUpdateSuccess(msg *types.Message, rec *types.MessageReceipt, res pipeline.CurrentDealInfo) (*abi.SectorNumber, error) { - var params miner.ProveReplicaUpdatesParams - if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { - return nil, xerrors.Errorf("unmarshal prove replica update: %w", err) - } - - var seekUpdate miner.ReplicaUpdate - var found bool - for _, update := range params.Updates { - for _, did := range update.Deals { - if did == res.DealID { - seekUpdate = update - found = true - break - } - } - } - if !found { - return nil, nil - } - - // check that this update passed validation steps - var successBf bitfield.BitField - if err := successBf.UnmarshalCBOR(bytes.NewReader(rec.Return)); err != nil { - return nil, xerrors.Errorf("unmarshal return value: %w", err) - } - success, err := successBf.IsSet(uint64(seekUpdate.SectorID)) - if err != nil { - return nil, xerrors.Errorf("failed to check success of replica update: %w", err) - } - if !success { - return nil, xerrors.Errorf("replica update %d failed", seekUpdate.SectorID) - } - return &seekUpdate.SectorID, nil -} - -// dealSectorInPreCommitMsg tries to find a sector containing the specified deal -func dealSectorInPreCommitMsg(msg *types.Message, res pipeline.CurrentDealInfo) (*abi.SectorNumber, error) { - switch msg.Method { - case builtin.MethodsMiner.PreCommitSector: - var params miner.SectorPreCommitInfo - if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { - return nil, xerrors.Errorf("unmarshal pre commit: %w", err) - } - - // Check through the deal IDs associated with this message - for _, did := range params.DealIDs { - if did == res.DealID { - // Found the deal ID in this message. Callback with the sector ID. - return ¶ms.SectorNumber, nil - } - } - case builtin.MethodsMiner.PreCommitSectorBatch: - var params miner.PreCommitSectorBatchParams - if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { - return nil, xerrors.Errorf("unmarshal pre commit: %w", err) - } - - for _, precommit := range params.Sectors { - // Check through the deal IDs associated with this message - for _, did := range precommit.DealIDs { - if did == res.DealID { - // Found the deal ID in this message. Callback with the sector ID. - return &precommit.SectorNumber, nil - } - } - } - case builtin.MethodsMiner.PreCommitSectorBatch2: - var params miner2.PreCommitSectorBatchParams2 - if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { - return nil, xerrors.Errorf("unmarshal pre commit: %w", err) - } - - for _, precommit := range params.Sectors { - // Check through the deal IDs associated with this message - for _, did := range precommit.DealIDs { - if did == res.DealID { - // Found the deal ID in this message. Callback with the sector ID. - return &precommit.SectorNumber, nil - } - } - } - default: - return nil, xerrors.Errorf("unexpected method %d", msg.Method) - } - - return nil, nil -} - -// sectorInCommitMsg checks if the provided message commits specified sector -func sectorInCommitMsg(msg *types.Message, sectorNumber abi.SectorNumber) (bool, error) { - switch msg.Method { - case builtin.MethodsMiner.ProveCommitSector: - var params miner.ProveCommitSectorParams - if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { - return false, xerrors.Errorf("failed to unmarshal prove commit sector params: %w", err) - } - - return params.SectorNumber == sectorNumber, nil - - case builtin.MethodsMiner.ProveCommitAggregate: - var params miner.ProveCommitAggregateParams - if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { - return false, xerrors.Errorf("failed to unmarshal prove commit sector params: %w", err) - } - - set, err := params.SectorNumbers.IsSet(uint64(sectorNumber)) - if err != nil { - return false, xerrors.Errorf("checking if sectorNumber is set in commit aggregate message: %w", err) - } - - return set, nil - - default: - return false, nil - } -} - -func (mgr *SectorCommittedManager) checkIfDealAlreadyActive(ctx context.Context, ts *types.TipSet, proposal *market.DealProposal, publishCid cid.Cid) (pipeline.CurrentDealInfo, bool, error) { - res, err := mgr.dealInfo.GetCurrentDealInfo(ctx, ts.Key(), proposal, publishCid) - if err != nil { - // TODO: This may be fine for some errors - return res, false, xerrors.Errorf("failed to look up deal on chain: %w", err) - } - - // Sector was slashed - if res.MarketDeal.State.SlashEpoch > 0 { - return res, false, xerrors.Errorf("deal %d was slashed at epoch %d", res.DealID, res.MarketDeal.State.SlashEpoch) - } - - // Sector with deal is already active - if res.MarketDeal.State.SectorStartEpoch > 0 { - return res, true, nil - } - - return res, false, nil -} diff --git a/markets/storageadapter/ondealsectorcommitted_test.go b/markets/storageadapter/ondealsectorcommitted_test.go deleted file mode 100644 index e3d318780..000000000 --- a/markets/storageadapter/ondealsectorcommitted_test.go +++ /dev/null @@ -1,583 +0,0 @@ -// stm: #unit -package storageadapter - -import ( - "bytes" - "context" - "errors" - "fmt" - "math/rand" - "testing" - "time" - - blocks "github.com/ipfs/go-block-format" - "github.com/ipfs/go-cid" - "github.com/stretchr/testify/require" - "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/builtin" - markettypes "github.com/filecoin-project/go-state-types/builtin/v9/market" - minertypes "github.com/filecoin-project/go-state-types/builtin/v9/miner" - "github.com/filecoin-project/go-state-types/cbor" - tutils "github.com/filecoin-project/specs-actors/v2/support/testing" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/events" - test "github.com/filecoin-project/lotus/chain/events/state/mock" - "github.com/filecoin-project/lotus/chain/types" - pipeline "github.com/filecoin-project/lotus/storage/pipeline" -) - -func TestOnDealSectorPreCommitted(t *testing.T) { - label, err := markettypes.NewLabelFromString("success") - require.NoError(t, err) - - provider := address.TestAddress - ctx := context.Background() - publishCid := generateCids(1)[0] - sealedCid := generateCids(1)[0] - pieceCid := generateCids(1)[0] - dealID := abi.DealID(rand.Uint64()) - sectorNumber := abi.SectorNumber(rand.Uint64()) - proposal := market.DealProposal{ - PieceCID: pieceCid, - PieceSize: abi.PaddedPieceSize(rand.Uint64()), - Client: tutils.NewActorAddr(t, "client"), - Provider: tutils.NewActorAddr(t, "provider"), - StoragePricePerEpoch: abi.NewTokenAmount(1), - ProviderCollateral: abi.NewTokenAmount(1), - ClientCollateral: abi.NewTokenAmount(1), - Label: label, - } - unfinishedDeal := &api.MarketDeal{ - Proposal: proposal, - State: api.MarketDealState{ - SectorStartEpoch: -1, - LastUpdatedEpoch: 2, - }, - } - activeDeal := &api.MarketDeal{ - Proposal: proposal, - State: api.MarketDealState{ - SectorStartEpoch: 1, - LastUpdatedEpoch: 2, - }, - } - slashedDeal := &api.MarketDeal{ - Proposal: proposal, - State: api.MarketDealState{ - SectorStartEpoch: 1, - LastUpdatedEpoch: 2, - SlashEpoch: 2, - }, - } - type testCase struct { - currentDealInfo pipeline.CurrentDealInfo - currentDealInfoErr error - currentDealInfoErr2 error - preCommitDiff *miner.PreCommitChanges - matchStates []matchState - dealStartEpochTimeout bool - expectedCBCallCount uint64 - expectedCBSectorNumber abi.SectorNumber - expectedCBIsActive bool - expectedCBError error - expectedError error - } - testCases := map[string]testCase{ - "normal sequence": { - currentDealInfo: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: unfinishedDeal, - }, - matchStates: []matchState{ - { - msg: makeMessage(t, provider, builtin.MethodsMiner.PreCommitSector, &minertypes.PreCommitSectorParams{ - SectorNumber: sectorNumber, - SealedCID: sealedCid, - DealIDs: []abi.DealID{dealID}, - }), - }, - }, - expectedCBCallCount: 1, - expectedCBIsActive: false, - expectedCBSectorNumber: sectorNumber, - }, - "ignores unsuccessful pre-commit message": { - currentDealInfo: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: unfinishedDeal, - }, - matchStates: []matchState{ - { - msg: makeMessage(t, provider, builtin.MethodsMiner.PreCommitSector, &minertypes.PreCommitSectorParams{ - SectorNumber: sectorNumber, - SealedCID: sealedCid, - DealIDs: []abi.DealID{dealID}, - }), - // non-zero exit code indicates unsuccessful pre-commit message - receipt: &types.MessageReceipt{ExitCode: 1}, - }, - }, - expectedCBCallCount: 0, - }, - "deal already pre-committed": { - currentDealInfo: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: unfinishedDeal, - }, - preCommitDiff: &miner.PreCommitChanges{ - Added: []minertypes.SectorPreCommitOnChainInfo{{ - Info: minertypes.SectorPreCommitInfo{ - SectorNumber: sectorNumber, - DealIDs: []abi.DealID{dealID}, - }, - }}, - }, - expectedCBCallCount: 1, - expectedCBIsActive: false, - expectedCBSectorNumber: sectorNumber, - }, - "error getting current deal info in check func": { - currentDealInfoErr: errors.New("something went wrong"), - expectedCBCallCount: 0, - expectedError: xerrors.Errorf("failed to set up called handler: failed to check deal activity: failed to look up deal on chain: something went wrong"), - }, - "sector already active": { - currentDealInfo: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: activeDeal, - }, - expectedCBCallCount: 1, - expectedCBIsActive: true, - }, - "sector was slashed": { - currentDealInfo: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: slashedDeal, - PublishMsgTipSet: types.EmptyTSK, - }, - expectedCBCallCount: 0, - expectedError: xerrors.Errorf("failed to set up called handler: failed to check deal activity: deal %d was slashed at epoch %d", dealID, slashedDeal.State.SlashEpoch), - }, - "error getting current deal info in called func": { - currentDealInfo: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: unfinishedDeal, - }, - currentDealInfoErr2: errors.New("something went wrong"), - matchStates: []matchState{ - { - msg: makeMessage(t, provider, builtin.MethodsMiner.PreCommitSector, &minertypes.PreCommitSectorParams{ - SectorNumber: sectorNumber, - SealedCID: sealedCid, - DealIDs: []abi.DealID{dealID}, - }), - }, - }, - expectedCBCallCount: 1, - expectedCBError: errors.New("handling applied event: failed to get dealinfo: something went wrong"), - }, - "proposed deal epoch timeout": { - currentDealInfo: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: activeDeal, - }, - dealStartEpochTimeout: true, - expectedCBCallCount: 1, - expectedCBError: xerrors.Errorf("handling applied event: deal with piece CID %s was not activated by proposed deal start epoch 0", unfinishedDeal.Proposal.PieceCID), - }, - } - runTestCase := func(testCase string, data testCase) { - t.Run(testCase, func(t *testing.T) { - checkTs, err := test.MockTipset(provider, rand.Uint64()) - require.NoError(t, err) - matchMessages := make([]matchMessage, len(data.matchStates)) - for i, ms := range data.matchStates { - matchTs, err := test.MockTipset(provider, rand.Uint64()) - require.NoError(t, err) - matchMessages[i] = matchMessage{ - curH: 5, - msg: ms.msg, - msgReceipt: ms.receipt, - ts: matchTs, - } - } - eventsAPI := &fakeEvents{ - Ctx: ctx, - CheckTs: checkTs, - MatchMessages: matchMessages, - DealStartEpochTimeout: data.dealStartEpochTimeout, - } - cbCallCount := uint64(0) - var cbSectorNumber abi.SectorNumber - var cbIsActive bool - var cbError error - cb := func(secNum abi.SectorNumber, isActive bool, err error) { - cbCallCount++ - cbSectorNumber = secNum - cbIsActive = isActive - cbError = err - } - - mockPCAPI := &mockPreCommitsAPI{ - PCChanges: data.preCommitDiff, - } - mockDIAPI := &mockDealInfoAPI{ - CurrentDealInfo: data.currentDealInfo, - CurrentDealInfo2: data.currentDealInfo, - Err: data.currentDealInfoErr, - Err2: data.currentDealInfoErr2, - } - scm := newSectorCommittedManager(eventsAPI, mockDIAPI, mockPCAPI) - //stm: @MARKET_ADAPTER_ON_SECTOR_PRE_COMMIT_001 - err = scm.OnDealSectorPreCommitted(ctx, provider, proposal, publishCid, cb) - if data.expectedError == nil { - require.NoError(t, err) - } else { - require.EqualError(t, err, data.expectedError.Error()) - } - require.Equal(t, data.expectedCBSectorNumber, cbSectorNumber) - require.Equal(t, data.expectedCBIsActive, cbIsActive) - require.Equal(t, data.expectedCBCallCount, cbCallCount) - if data.expectedCBError == nil { - require.NoError(t, cbError) - } else { - require.EqualError(t, cbError, data.expectedCBError.Error()) - } - }) - } - for testCase, data := range testCases { - runTestCase(testCase, data) - } -} - -func TestOnDealSectorCommitted(t *testing.T) { - label, err := markettypes.NewLabelFromString("success") - require.NoError(t, err) - - provider := address.TestAddress - publishCid := generateCids(1)[0] - pieceCid := generateCids(1)[0] - dealID := abi.DealID(rand.Uint64()) - sectorNumber := abi.SectorNumber(rand.Uint64()) - proposal := market.DealProposal{ - PieceCID: pieceCid, - PieceSize: abi.PaddedPieceSize(rand.Uint64()), - Client: tutils.NewActorAddr(t, "client"), - Provider: tutils.NewActorAddr(t, "provider"), - StoragePricePerEpoch: abi.NewTokenAmount(1), - ProviderCollateral: abi.NewTokenAmount(1), - ClientCollateral: abi.NewTokenAmount(1), - Label: label, - } - unfinishedDeal := &api.MarketDeal{ - Proposal: proposal, - State: api.MarketDealState{ - SectorStartEpoch: -1, - LastUpdatedEpoch: 2, - }, - } - activeDeal := &api.MarketDeal{ - Proposal: proposal, - State: api.MarketDealState{ - SectorStartEpoch: 1, - LastUpdatedEpoch: 2, - }, - } - slashedDeal := &api.MarketDeal{ - Proposal: proposal, - State: api.MarketDealState{ - SectorStartEpoch: 1, - LastUpdatedEpoch: 2, - SlashEpoch: 2, - }, - } - type testCase struct { - currentDealInfo pipeline.CurrentDealInfo - currentDealInfoErr error - currentDealInfo2 pipeline.CurrentDealInfo - currentDealInfoErr2 error - matchStates []matchState - dealStartEpochTimeout bool - expectedCBCallCount uint64 - expectedCBError error - expectedError error - } - testCases := map[string]testCase{ - "normal sequence": { - currentDealInfo: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: unfinishedDeal, - }, - currentDealInfo2: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: activeDeal, - }, - matchStates: []matchState{ - { - msg: makeMessage(t, provider, builtin.MethodsMiner.ProveCommitSector, &minertypes.ProveCommitSectorParams{ - SectorNumber: sectorNumber, - }), - }, - }, - expectedCBCallCount: 1, - }, - "ignores unsuccessful prove-commit message": { - currentDealInfo: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: unfinishedDeal, - }, - currentDealInfo2: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: activeDeal, - }, - matchStates: []matchState{ - { - msg: makeMessage(t, provider, builtin.MethodsMiner.ProveCommitSector, &minertypes.ProveCommitSectorParams{ - SectorNumber: sectorNumber, - }), - // Exit-code 1 means the prove-commit was unsuccessful - receipt: &types.MessageReceipt{ExitCode: 1}, - }, - }, - expectedCBCallCount: 0, - }, - "error getting current deal info in check func": { - currentDealInfoErr: errors.New("something went wrong"), - expectedCBCallCount: 0, - expectedError: xerrors.Errorf("failed to set up called handler: failed to look up deal on chain: something went wrong"), - }, - "sector already active": { - currentDealInfo: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: activeDeal, - }, - expectedCBCallCount: 1, - }, - "sector was slashed": { - currentDealInfo: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: slashedDeal, - }, - expectedCBCallCount: 0, - expectedError: xerrors.Errorf("failed to set up called handler: deal %d was slashed at epoch %d", dealID, slashedDeal.State.SlashEpoch), - }, - "error getting current deal info in called func": { - currentDealInfo: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: unfinishedDeal, - }, - currentDealInfoErr2: errors.New("something went wrong"), - matchStates: []matchState{ - { - msg: makeMessage(t, provider, builtin.MethodsMiner.ProveCommitSector, &minertypes.ProveCommitSectorParams{ - SectorNumber: sectorNumber, - }), - }, - }, - expectedCBCallCount: 1, - expectedCBError: xerrors.Errorf("handling applied event: failed to look up deal on chain: something went wrong"), - }, - "proposed deal epoch timeout": { - currentDealInfo: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: unfinishedDeal, - }, - dealStartEpochTimeout: true, - expectedCBCallCount: 1, - expectedCBError: xerrors.Errorf("handling applied event: deal with piece CID %s was not activated by proposed deal start epoch 0", unfinishedDeal.Proposal.PieceCID), - }, - "got prove-commit but deal not active": { - currentDealInfo: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: unfinishedDeal, - }, - currentDealInfo2: pipeline.CurrentDealInfo{ - DealID: dealID, - MarketDeal: unfinishedDeal, - }, - matchStates: []matchState{ - { - msg: makeMessage(t, provider, builtin.MethodsMiner.ProveCommitSector, &minertypes.ProveCommitSectorParams{ - SectorNumber: sectorNumber, - }), - }, - }, - expectedCBCallCount: 1, - expectedCBError: xerrors.Errorf("handling applied event: deal wasn't active: deal=%d, parentState=bafkqaaa, h=5", dealID), - }, - } - runTestCase := func(testCase string, data testCase) { - t.Run(testCase, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - checkTs, err := test.MockTipset(provider, rand.Uint64()) - require.NoError(t, err) - matchMessages := make([]matchMessage, len(data.matchStates)) - for i, ms := range data.matchStates { - matchTs, err := test.MockTipset(provider, rand.Uint64()) - require.NoError(t, err) - matchMessages[i] = matchMessage{ - curH: 5, - msg: ms.msg, - msgReceipt: ms.receipt, - ts: matchTs, - } - } - eventsAPI := &fakeEvents{ - Ctx: ctx, - CheckTs: checkTs, - MatchMessages: matchMessages, - DealStartEpochTimeout: data.dealStartEpochTimeout, - } - cbCallCount := uint64(0) - var cbError error - cb := func(err error) { - cbCallCount++ - cbError = err - } - mockPCAPI := &mockPreCommitsAPI{} - mockDIAPI := &mockDealInfoAPI{ - CurrentDealInfo: data.currentDealInfo, - CurrentDealInfo2: data.currentDealInfo2, - Err: data.currentDealInfoErr, - Err2: data.currentDealInfoErr2, - } - scm := newSectorCommittedManager(eventsAPI, mockDIAPI, mockPCAPI) - //stm: @MARKET_ADAPTER_ON_SECTOR_COMMIT_001 - err = scm.OnDealSectorCommitted(ctx, provider, sectorNumber, proposal, publishCid, cb) - if data.expectedError == nil { - require.NoError(t, err) - } else { - require.EqualError(t, err, data.expectedError.Error()) - } - require.Equal(t, data.expectedCBCallCount, cbCallCount) - if data.expectedCBError == nil { - require.NoError(t, cbError) - } else { - require.EqualError(t, cbError, data.expectedCBError.Error()) - } - }) - } - for testCase, data := range testCases { - runTestCase(testCase, data) - } -} - -type matchState struct { - msg *types.Message - receipt *types.MessageReceipt -} - -type matchMessage struct { - curH abi.ChainEpoch - msg *types.Message - msgReceipt *types.MessageReceipt - ts *types.TipSet - doesRevert bool -} -type fakeEvents struct { - Ctx context.Context - CheckTs *types.TipSet - MatchMessages []matchMessage - DealStartEpochTimeout bool -} - -func (fe *fakeEvents) Called(ctx context.Context, check events.CheckFunc, msgHnd events.MsgHandler, rev events.RevertHandler, confidence int, timeout abi.ChainEpoch, mf events.MsgMatchFunc) error { - if fe.DealStartEpochTimeout { - msgHnd(nil, nil, nil, 100) // nolint:errcheck - return nil - } - - _, more, err := check(ctx, fe.CheckTs) - if err != nil { - return err - } - if !more { - return nil - } - for _, matchMessage := range fe.MatchMessages { - matched, err := mf(matchMessage.msg) - if err != nil { - return err - } - if matched { - receipt := matchMessage.msgReceipt - if receipt == nil { - receipt = &types.MessageReceipt{ExitCode: 0} - } - more, err := msgHnd(matchMessage.msg, receipt, matchMessage.ts, matchMessage.curH) - if err != nil { - // error is handled through a callback rather than being returned - return nil - } - if matchMessage.doesRevert { - err := rev(ctx, matchMessage.ts) - if err != nil { - return err - } - } - if !more { - return nil - } - } - } - return nil -} - -func makeMessage(t *testing.T, to address.Address, method abi.MethodNum, params cbor.Marshaler) *types.Message { - buf := new(bytes.Buffer) - err := params.MarshalCBOR(buf) - require.NoError(t, err) - return &types.Message{ - To: to, - Method: method, - Params: buf.Bytes(), - } -} - -var seq int - -func generateCids(n int) []cid.Cid { - cids := make([]cid.Cid, 0, n) - for i := 0; i < n; i++ { - c := blocks.NewBlock([]byte(fmt.Sprint(seq))).Cid() - seq++ - cids = append(cids, c) - } - return cids -} - -type mockPreCommitsAPI struct { - PCChanges *miner.PreCommitChanges - Err error -} - -func (m *mockPreCommitsAPI) diffPreCommits(ctx context.Context, actor address.Address, pre, cur types.TipSetKey) (*miner.PreCommitChanges, error) { - pcc := &miner.PreCommitChanges{} - if m.PCChanges != nil { - pcc = m.PCChanges - } - return pcc, m.Err -} - -type mockDealInfoAPI struct { - count int - CurrentDealInfo pipeline.CurrentDealInfo - Err error - CurrentDealInfo2 pipeline.CurrentDealInfo - Err2 error -} - -func (m *mockDealInfoAPI) GetCurrentDealInfo(ctx context.Context, tsk types.TipSetKey, proposal *market.DealProposal, publishCid cid.Cid) (pipeline.CurrentDealInfo, error) { - m.count++ - if m.count == 2 { - return m.CurrentDealInfo2, m.Err2 - } - return m.CurrentDealInfo, m.Err -} diff --git a/markets/storageadapter/provider.go b/markets/storageadapter/provider.go deleted file mode 100644 index 11742c879..000000000 --- a/markets/storageadapter/provider.go +++ /dev/null @@ -1,441 +0,0 @@ -package storageadapter - -// this file implements storagemarket.StorageProviderNode - -import ( - "context" - "time" - - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - "go.uber.org/fx" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-fil-markets/shared" - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-state-types/abi" - markettypes "github.com/filecoin-project/go-state-types/builtin/v9/market" - "github.com/filecoin-project/go-state-types/crypto" - "github.com/filecoin-project/go-state-types/exitcode" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/v1api" - "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/events" - "github.com/filecoin-project/lotus/chain/events/state" - "github.com/filecoin-project/lotus/chain/types" - "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/helpers" - pipeline "github.com/filecoin-project/lotus/storage/pipeline" - "github.com/filecoin-project/lotus/storage/pipeline/piece" - "github.com/filecoin-project/lotus/storage/sectorblocks" -) - -var addPieceRetryWait = 5 * time.Minute -var addPieceRetryTimeout = 6 * time.Hour -var defaultMaxProviderCollateralMultiplier = uint64(2) -var log = logging.Logger("storageadapter") - -type ProviderNodeAdapter struct { - v1api.FullNode - - secb *sectorblocks.SectorBlocks - ev *events.Events - - dealPublisher *DealPublisher - - addBalanceSpec *api.MessageSendSpec - maxDealCollateralMultiplier uint64 - dsMatcher *dealStateMatcher - scMgr *SectorCommittedManager -} - -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, error) { - return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, secb *sectorblocks.SectorBlocks, full v1api.FullNode, dealPublisher *DealPublisher) (storagemarket.StorageProviderNode, error) { - ctx := helpers.LifecycleCtx(mctx, lc) - - ev, err := events.NewEvents(ctx, full) - if err != nil { - return nil, err - } - na := &ProviderNodeAdapter{ - FullNode: full, - - secb: secb, - ev: ev, - dealPublisher: dealPublisher, - dsMatcher: newDealStateMatcher(state.NewStatePredicates(state.WrapFastAPI(full))), - } - if fc != nil { - na.addBalanceSpec = &api.MessageSendSpec{MaxFee: abi.TokenAmount(fc.MaxMarketBalanceAddFee)} - } - na.maxDealCollateralMultiplier = defaultMaxProviderCollateralMultiplier - if dc != nil { - na.maxDealCollateralMultiplier = dc.MaxProviderCollateralMultiplier - } - na.scMgr = NewSectorCommittedManager(ev, na, &apiWrapper{api: full}) - - return na, nil - } -} - -func (n *ProviderNodeAdapter) PublishDeals(ctx context.Context, deal storagemarket.MinerDeal) (cid.Cid, error) { - return n.dealPublisher.Publish(ctx, deal.ClientDealProposal) -} - -func (n *ProviderNodeAdapter) OnDealComplete(ctx context.Context, deal storagemarket.MinerDeal, pieceSize abi.UnpaddedPieceSize, pieceData shared.ReadSeekStarter) (*storagemarket.PackingResult, error) { - if deal.PublishCid == nil { - return nil, xerrors.Errorf("deal.PublishCid can't be nil") - } - - sdInfo := piece.PieceDealInfo{ - DealID: deal.DealID, - DealProposal: &deal.Proposal, - PublishCid: deal.PublishCid, - DealSchedule: piece.DealSchedule{ - StartEpoch: deal.ClientDealProposal.Proposal.StartEpoch, - EndEpoch: deal.ClientDealProposal.Proposal.EndEpoch, - }, - KeepUnsealed: deal.FastRetrieval, - } - - // Attempt to add the piece to the sector - p, offset, err := n.secb.AddPiece(ctx, pieceSize, pieceData, sdInfo) - curTime := build.Clock.Now() - for build.Clock.Since(curTime) < addPieceRetryTimeout { - // Check if there was an error because of too many sectors being sealed - if !xerrors.Is(err, pipeline.ErrTooManySectorsSealing) { - if err != nil { - log.Errorf("failed to addPiece for deal %d, err: %v", deal.DealID, err) - } - - // There was either a fatal error or no error. In either case - // don't retry AddPiece - break - } - - // The piece could not be added to the sector because there are too - // many sectors being sealed, back-off for a while before trying again - select { - case <-build.Clock.After(addPieceRetryWait): - // Reset the reader to the start - err = pieceData.SeekStart() - if err != nil { - return nil, xerrors.Errorf("failed to reset piece reader to start before retrying AddPiece for deal %d: %w", deal.DealID, err) - } - - // Attempt to add the piece again - p, offset, err = n.secb.AddPiece(ctx, pieceSize, pieceData, sdInfo) - case <-ctx.Done(): - return nil, xerrors.New("context expired while waiting to retry AddPiece") - } - } - - if err != nil { - return nil, xerrors.Errorf("AddPiece failed: %s", err) - } - log.Warnf("New Deal: deal %d", deal.DealID) - - return &storagemarket.PackingResult{ - SectorNumber: p, - Offset: offset, - Size: pieceSize.Padded(), - }, nil -} - -func (n *ProviderNodeAdapter) VerifySignature(ctx context.Context, sig crypto.Signature, addr address.Address, input []byte, encodedTs shared.TipSetToken) (bool, error) { - addr, err := n.StateAccountKey(ctx, addr, types.EmptyTSK) - if err != nil { - return false, err - } - - err = sigs.Verify(&sig, addr, input) - return err == nil, err -} - -func (n *ProviderNodeAdapter) GetMinerWorkerAddress(ctx context.Context, maddr address.Address, tok shared.TipSetToken) (address.Address, error) { - tsk, err := types.TipSetKeyFromBytes(tok) - if err != nil { - return address.Undef, err - } - - mi, err := n.StateMinerInfo(ctx, maddr, tsk) - if err != nil { - return address.Address{}, err - } - return mi.Worker, nil -} - -func (n *ProviderNodeAdapter) GetProofType(ctx context.Context, maddr address.Address, tok shared.TipSetToken) (abi.RegisteredSealProof, error) { - tsk, err := types.TipSetKeyFromBytes(tok) - if err != nil { - return 0, err - } - - mi, err := n.StateMinerInfo(ctx, maddr, tsk) - if err != nil { - return 0, err - } - - nver, err := n.StateNetworkVersion(ctx, tsk) - if err != nil { - return 0, err - } - - // false because this variance is not consumed. - const configWantSynthetic = false - - return miner.PreferredSealProofTypeFromWindowPoStType(nver, mi.WindowPoStProofType, configWantSynthetic) -} - -func (n *ProviderNodeAdapter) SignBytes(ctx context.Context, signer address.Address, b []byte) (*crypto.Signature, error) { - signer, err := n.StateAccountKey(ctx, signer, types.EmptyTSK) - if err != nil { - return nil, err - } - - localSignature, err := n.WalletSign(ctx, signer, b) - if err != nil { - return nil, err - } - return localSignature, nil -} - -func (n *ProviderNodeAdapter) ReserveFunds(ctx context.Context, wallet, addr address.Address, amt abi.TokenAmount) (cid.Cid, error) { - return n.MarketReserveFunds(ctx, wallet, addr, amt) -} - -func (n *ProviderNodeAdapter) ReleaseFunds(ctx context.Context, addr address.Address, amt abi.TokenAmount) error { - return n.MarketReleaseFunds(ctx, addr, amt) -} - -// Adds funds with the StorageMinerActor for a storage participant. Used by both providers and clients. -func (n *ProviderNodeAdapter) AddFunds(ctx context.Context, addr address.Address, amount abi.TokenAmount) (cid.Cid, error) { - // (Provider Node API) - smsg, err := n.MpoolPushMessage(ctx, &types.Message{ - To: market.Address, - From: addr, - Value: amount, - Method: market.Methods.AddBalance, - }, n.addBalanceSpec) - if err != nil { - return cid.Undef, err - } - - return smsg.Cid(), nil -} - -func (n *ProviderNodeAdapter) GetBalance(ctx context.Context, addr address.Address, encodedTs shared.TipSetToken) (storagemarket.Balance, error) { - tsk, err := types.TipSetKeyFromBytes(encodedTs) - if err != nil { - return storagemarket.Balance{}, err - } - - bal, err := n.StateMarketBalance(ctx, addr, tsk) - if err != nil { - return storagemarket.Balance{}, err - } - - return utils.ToSharedBalance(bal), nil -} - -// TODO: why doesnt this method take in a sector ID? -func (n *ProviderNodeAdapter) LocatePieceForDealWithinSector(ctx context.Context, dealID abi.DealID, encodedTs shared.TipSetToken) (sectorID abi.SectorNumber, offset abi.PaddedPieceSize, length abi.PaddedPieceSize, err error) { - refs, err := n.secb.GetRefs(ctx, dealID) - if err != nil { - return 0, 0, 0, err - } - if len(refs) == 0 { - return 0, 0, 0, xerrors.New("no sector information for deal ID") - } - - // TODO: better strategy (e.g. look for already unsealed) - var best api.SealedRef - var bestSi api.SectorInfo - for _, r := range refs { - si, err := n.secb.SectorBuilder.SectorsStatus(ctx, r.SectorID, false) - if err != nil { - return 0, 0, 0, xerrors.Errorf("getting sector info: %w", err) - } - if si.State == api.SectorState(pipeline.Proving) { - best = r - bestSi = si - break - } - } - if bestSi.State == api.SectorState(pipeline.UndefinedSectorState) { - return 0, 0, 0, xerrors.New("no sealed sector found") - } - return best.SectorID, best.Offset, best.Size.Padded(), nil -} - -func (n *ProviderNodeAdapter) DealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, isVerified bool) (abi.TokenAmount, abi.TokenAmount, error) { - bounds, err := n.StateDealProviderCollateralBounds(ctx, size, isVerified, types.EmptyTSK) - if err != nil { - return abi.TokenAmount{}, abi.TokenAmount{}, err - } - - // The maximum amount of collateral that the provider will put into escrow - // for a deal is calculated as a multiple of the minimum bounded amount - max := types.BigMul(bounds.Min, types.NewInt(n.maxDealCollateralMultiplier)) - - return bounds.Min, max, nil -} - -// TODO: Remove dealID parameter, change publishCid to be cid.Cid (instead of pointer) -func (n *ProviderNodeAdapter) OnDealSectorPreCommitted(ctx context.Context, provider address.Address, dealID abi.DealID, proposal markettypes.DealProposal, publishCid *cid.Cid, cb storagemarket.DealSectorPreCommittedCallback) error { - return n.scMgr.OnDealSectorPreCommitted(ctx, provider, proposal, *publishCid, cb) -} - -// TODO: Remove dealID parameter, change publishCid to be cid.Cid (instead of pointer) -func (n *ProviderNodeAdapter) OnDealSectorCommitted(ctx context.Context, provider address.Address, dealID abi.DealID, sectorNumber abi.SectorNumber, proposal markettypes.DealProposal, publishCid *cid.Cid, cb storagemarket.DealSectorCommittedCallback) error { - return n.scMgr.OnDealSectorCommitted(ctx, provider, sectorNumber, proposal, *publishCid, cb) -} - -func (n *ProviderNodeAdapter) GetChainHead(ctx context.Context) (shared.TipSetToken, abi.ChainEpoch, error) { - head, err := n.ChainHead(ctx) - if err != nil { - return nil, 0, err - } - - return head.Key().Bytes(), head.Height(), nil -} - -func (n *ProviderNodeAdapter) WaitForMessage(ctx context.Context, mcid cid.Cid, cb func(code exitcode.ExitCode, bytes []byte, finalCid cid.Cid, err error) error) error { - receipt, err := n.StateWaitMsg(ctx, mcid, 2*build.MessageConfidence, api.LookbackNoLimit, true) - if err != nil { - return cb(0, nil, cid.Undef, err) - } - return cb(receipt.Receipt.ExitCode, receipt.Receipt.Return, receipt.Message, nil) -} - -func (n *ProviderNodeAdapter) WaitForPublishDeals(ctx context.Context, publishCid cid.Cid, proposal markettypes.DealProposal) (*storagemarket.PublishDealsWaitResult, error) { - // Wait for deal to be published (plus additional time for confidence) - receipt, err := n.StateWaitMsg(ctx, publishCid, 2*build.MessageConfidence, api.LookbackNoLimit, true) - if err != nil { - return nil, xerrors.Errorf("WaitForPublishDeals errored: %w", err) - } - if receipt.Receipt.ExitCode != exitcode.Ok { - return nil, xerrors.Errorf("WaitForPublishDeals exit code: %s", receipt.Receipt.ExitCode) - } - - // The deal ID may have changed since publish if there was a reorg, so - // get the current deal ID - head, err := n.ChainHead(ctx) - if err != nil { - return nil, xerrors.Errorf("WaitForPublishDeals failed to get chain head: %w", err) - } - - res, err := n.scMgr.dealInfo.GetCurrentDealInfo(ctx, head.Key(), &proposal, publishCid) - if err != nil { - return nil, xerrors.Errorf("WaitForPublishDeals getting deal info errored: %w", err) - } - - return &storagemarket.PublishDealsWaitResult{DealID: res.DealID, FinalCid: receipt.Message}, nil -} - -func (n *ProviderNodeAdapter) GetDataCap(ctx context.Context, addr address.Address, encodedTs shared.TipSetToken) (*abi.StoragePower, error) { - tsk, err := types.TipSetKeyFromBytes(encodedTs) - if err != nil { - return nil, err - } - - sp, err := n.StateVerifiedClientStatus(ctx, addr, tsk) - return sp, err -} - -func (n *ProviderNodeAdapter) OnDealExpiredOrSlashed(ctx context.Context, dealID abi.DealID, onDealExpired storagemarket.DealExpiredCallback, onDealSlashed storagemarket.DealSlashedCallback) error { - head, err := n.ChainHead(ctx) - if err != nil { - return xerrors.Errorf("client: failed to get chain head: %w", err) - } - - sd, err := n.StateMarketStorageDeal(ctx, dealID, head.Key()) - if err != nil { - return xerrors.Errorf("client: failed to look up deal %d on chain: %w", dealID, err) - } - - // Called immediately to check if the deal has already expired or been slashed - checkFunc := func(ctx context.Context, ts *types.TipSet) (done bool, more bool, err error) { - if ts == nil { - // keep listening for events - return false, true, nil - } - - // Check if the deal has already expired - if sd.Proposal.EndEpoch <= ts.Height() { - onDealExpired(nil) - return true, false, nil - } - - // If there is no deal assume it's already been slashed - if sd.State.SectorStartEpoch < 0 { - onDealSlashed(ts.Height(), nil) - return true, false, nil - } - - // No events have occurred yet, so return - // done: false, more: true (keep listening for events) - return false, true, nil - } - - // Called when there was a match against the state change we're looking for - // and the chain has advanced to the confidence height - stateChanged := func(ts *types.TipSet, ts2 *types.TipSet, states events.StateChange, h abi.ChainEpoch) (more bool, err error) { - // Check if the deal has already expired - if ts2 == nil || sd.Proposal.EndEpoch <= ts2.Height() { - onDealExpired(nil) - return false, nil - } - - // Timeout waiting for state change - if states == nil { - log.Error("timed out waiting for deal expiry") - return false, nil - } - - changedDeals, ok := states.(state.ChangedDeals) - if !ok { - panic("Expected state.ChangedDeals") - } - - deal, ok := changedDeals[dealID] - if !ok { - // No change to deal - return true, nil - } - - // Deal was slashed - if deal.To == nil { - onDealSlashed(ts2.Height(), nil) - return false, nil - } - - return true, nil - } - - // Called when there was a chain reorg and the state change was reverted - revert := func(ctx context.Context, ts *types.TipSet) error { - // TODO: Is it ok to just ignore this? - log.Warn("deal state reverted; TODO: actually handle this!") - return nil - } - - // Watch for state changes to the deal - match := n.dsMatcher.matcher(ctx, dealID) - - // Wait until after the end epoch for the deal and then timeout - timeout := (sd.Proposal.EndEpoch - head.Height()) + 1 - if err := n.ev.StateChanged(checkFunc, stateChanged, revert, int(build.MessageConfidence)+1, timeout, match); err != nil { - return xerrors.Errorf("failed to set up state changed handler: %w", err) - } - - return nil -} - -var _ storagemarket.StorageProviderNode = &ProviderNodeAdapter{} diff --git a/markets/utils/converters.go b/markets/utils/converters.go deleted file mode 100644 index 9562de695..000000000 --- a/markets/utils/converters.go +++ /dev/null @@ -1,39 +0,0 @@ -package utils - -import ( - "github.com/libp2p/go-libp2p/core/peer" - "github.com/multiformats/go-multiaddr" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" - - "github.com/filecoin-project/lotus/api" -) - -func NewStorageProviderInfo(address address.Address, miner address.Address, sectorSize abi.SectorSize, peer peer.ID, addrs []abi.Multiaddrs) storagemarket.StorageProviderInfo { - multiaddrs := make([]multiaddr.Multiaddr, 0, len(addrs)) - for _, a := range addrs { - maddr, err := multiaddr.NewMultiaddrBytes(a) - if err != nil { - return storagemarket.StorageProviderInfo{} - } - multiaddrs = append(multiaddrs, maddr) - } - - return storagemarket.StorageProviderInfo{ - Address: address, - Worker: miner, - SectorSize: uint64(sectorSize), - PeerID: peer, - Addrs: multiaddrs, - } -} - -func ToSharedBalance(bal api.MarketBalance) storagemarket.Balance { - return storagemarket.Balance{ - Locked: bal.Locked, - Available: big.Sub(bal.Escrow, bal.Locked), - } -} diff --git a/markets/utils/selectors.go b/markets/utils/selectors.go deleted file mode 100644 index 1b8a62401..000000000 --- a/markets/utils/selectors.go +++ /dev/null @@ -1,98 +0,0 @@ -package utils - -import ( - "bytes" - "context" - "fmt" - "io" - - // must be imported to init() raw-codec support - _ "github.com/ipld/go-ipld-prime/codec/raw" - - "github.com/ipfs/go-cid" - mdagipld "github.com/ipfs/go-ipld-format" - "github.com/ipfs/go-unixfsnode" - dagpb "github.com/ipld/go-codec-dagpb" - "github.com/ipld/go-ipld-prime" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" - basicnode "github.com/ipld/go-ipld-prime/node/basic" - "github.com/ipld/go-ipld-prime/traversal" - "github.com/ipld/go-ipld-prime/traversal/selector" - selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse" -) - -func TraverseDag( - ctx context.Context, - ds mdagipld.DAGService, - startFrom cid.Cid, - optionalSelector ipld.Node, - onOpen func(node mdagipld.Node) error, - visitCallback traversal.AdvVisitFn, -) error { - - if optionalSelector == nil { - optionalSelector = selectorparse.CommonSelector_MatchAllRecursively - } - - parsedSelector, err := selector.ParseSelector(optionalSelector) - if err != nil { - return err - } - - // not sure what this is for TBH: we also provide ctx in &traversal.Config{} - linkContext := ipld.LinkContext{Ctx: ctx} - - // this is what allows us to understand dagpb - nodePrototypeChooser := dagpb.AddSupportToChooser( - func(ipld.Link, ipld.LinkContext) (ipld.NodePrototype, error) { - return basicnode.Prototype.Any, nil - }, - ) - - // this is how we implement GETs - linkSystem := cidlink.DefaultLinkSystem() - linkSystem.StorageReadOpener = func(lctx ipld.LinkContext, lnk ipld.Link) (io.Reader, error) { - cl, isCid := lnk.(cidlink.Link) - if !isCid { - return nil, fmt.Errorf("unexpected link type %#v", lnk) - } - - node, err := ds.Get(lctx.Ctx, cl.Cid) - if err != nil { - return nil, err - } - - if onOpen != nil { - if err := onOpen(node); err != nil { - return nil, err - } - } - - return bytes.NewBuffer(node.RawData()), nil - } - unixfsnode.AddUnixFSReificationToLinkSystem(&linkSystem) - - // this is how we pull the start node out of the DS - startLink := cidlink.Link{Cid: startFrom} - startNodePrototype, err := nodePrototypeChooser(startLink, linkContext) - if err != nil { - return err - } - startNode, err := linkSystem.Load( - linkContext, - startLink, - startNodePrototype, - ) - if err != nil { - return err - } - - // this is the actual execution, invoking the supplied callback - return traversal.Progress{ - Cfg: &traversal.Config{ - Ctx: ctx, - LinkSystem: linkSystem, - LinkTargetNodePrototypeChooser: nodePrototypeChooser, - }, - }.WalkAdv(startNode, parsedSelector, visitCallback) -} diff --git a/node/builder.go b/node/builder.go index 1cd4823d5..cadd5c64c 100644 --- a/node/builder.go +++ b/node/builder.go @@ -33,7 +33,6 @@ import ( _ "github.com/filecoin-project/lotus/lib/sigs/bls" _ "github.com/filecoin-project/lotus/lib/sigs/delegated" _ "github.com/filecoin-project/lotus/lib/sigs/secp" - "github.com/filecoin-project/lotus/markets/storageadapter" "github.com/filecoin-project/lotus/node/config" "github.com/filecoin-project/lotus/node/impl/common" "github.com/filecoin-project/lotus/node/impl/net" @@ -69,9 +68,7 @@ var ( AutoNATSvcKey = special{10} // Libp2p option BandwidthReporterKey = special{11} // Libp2p option ConnGaterKey = special{12} // Libp2p option - DAGStoreKey = special{13} // constructor returns multiple values ResourceManagerKey = special{14} // Libp2p option - UserAgentKey = special{15} // Libp2p option ) type invoke int @@ -91,7 +88,6 @@ const ( CheckFDLimit CheckFvmConcurrency CheckUDPBufferSize - LegacyMarketsEOL // libp2p PstoreAddSelfKeysKey @@ -108,7 +104,6 @@ const ( HandleIncomingBlocksKey HandleIncomingMessagesKey - HandleMigrateClientFundsKey HandlePaymentChannelManagerKey RelayIndexerMessagesKey @@ -266,12 +261,13 @@ func Base() Option { } // Config sets up constructors based on the provided Config -func ConfigCommon(cfg *config.Common, enableLibp2pNode bool) Option { +func ConfigCommon(cfg *config.Common, buildVersion build.BuildVersion, enableLibp2pNode bool) Option { // setup logging early lotuslog.SetLevelsFromConfig(cfg.Logging.SubsystemLevels) return Options( func(s *Settings) error { s.Config = true; return nil }, + Override(new(build.BuildVersion), buildVersion), Override(new(dtypes.APIEndpoint), func() (dtypes.APIEndpoint, error) { return multiaddr.NewMultiaddr(cfg.API.ListenAddress) }), @@ -396,7 +392,6 @@ func Test() Option { Unset(RunPeerMgrKey), Unset(new(*peermgr.PeerMgr)), Override(new(beacon.Schedule), testing.RandomBeacon), - Override(new(*storageadapter.DealPublisher), storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{})), Override(new(index.MsgIndex), modules.DummyMsgIndex), ) } diff --git a/node/builder_chain.go b/node/builder_chain.go index 0b40e4530..b273a168c 100644 --- a/node/builder_chain.go +++ b/node/builder_chain.go @@ -6,12 +6,8 @@ import ( "go.uber.org/fx" "golang.org/x/xerrors" - "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" - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain" "github.com/filecoin-project/lotus/chain/beacon" "github.com/filecoin-project/lotus/chain/consensus" @@ -32,8 +28,6 @@ import ( ledgerwallet "github.com/filecoin-project/lotus/chain/wallet/ledger" "github.com/filecoin-project/lotus/chain/wallet/remotewallet" "github.com/filecoin-project/lotus/lib/peermgr" - "github.com/filecoin-project/lotus/markets/retrievaladapter" - "github.com/filecoin-project/lotus/markets/storageadapter" "github.com/filecoin-project/lotus/node/config" "github.com/filecoin-project/lotus/node/hello" "github.com/filecoin-project/lotus/node/impl" @@ -104,9 +98,6 @@ var ChainNode = Options( Override(new(*messagepool.MessagePool), modules.MessagePool), Override(new(*dtypes.MpoolLocker), new(dtypes.MpoolLocker)), - // Shared graphsync (markets, serving chain) - Override(new(dtypes.Graphsync), modules.Graphsync(config.DefaultFullNode().Client.SimultaneousTransfersForStorage, config.DefaultFullNode().Client.SimultaneousTransfersForRetrieval)), - // Service: Wallet Override(new(*messagesigner.MessageSigner), messagesigner.NewMessageSigner), Override(new(messagesigner.MsgSigner), func(ms *messagesigner.MessageSigner) *messagesigner.MessageSigner { return ms }), @@ -121,23 +112,8 @@ var ChainNode = Options( Override(HandlePaymentChannelManagerKey, modules.HandlePaychManager), Override(SettlePaymentChannelsKey, settler.SettlePaymentChannels), - // Markets (common) - Override(new(*discoveryimpl.Local), modules.NewLocalDiscovery), - - // Markets (retrieval) - Override(new(discovery.PeerResolver), modules.RetrievalResolver), - Override(new(retrievalmarket.BlockstoreAccessor), modules.RetrievalBlockstoreAccessor), - Override(new(retrievalmarket.RetrievalClient), modules.RetrievalClient(false)), - 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(*retrievaladapter.APIBlockstoreAccessor), retrievaladapter.NewAPIBlockstoreAdapter), - Override(new(storagemarket.StorageClient), modules.StorageClient), - Override(new(storagemarket.StorageClientNode), storageadapter.NewClientNodeAdapter), - Override(HandleMigrateClientFundsKey, modules.HandleMigrateClientFunds), Override(new(*full.GasPriceCache), full.NewGasPriceCache), @@ -184,7 +160,7 @@ func ConfigFullNode(c interface{}) Option { enableLibp2pNode := true // always enable libp2p for full nodes return Options( - ConfigCommon(&cfg.Common, enableLibp2pNode), + ConfigCommon(&cfg.Common, build.NodeUserVersion(), enableLibp2pNode), Override(new(dtypes.UniversalBlockstore), modules.UniversalBlockstore), @@ -224,14 +200,6 @@ func ConfigFullNode(c interface{}) Option { // as it enables us to serve logs in eth_getTransactionReceipt. If(cfg.Fevm.EnableEthRPC || cfg.Events.EnableActorEventsAPI, Override(StoreEventsKey, modules.EnableStoringEvents)), - Override(new(dtypes.ClientImportMgr), modules.ClientImportMgr), - - Override(new(dtypes.ClientBlockstore), modules.ClientBlockstore), - - Override(new(dtypes.Graphsync), modules.Graphsync(cfg.Client.SimultaneousTransfersForStorage, cfg.Client.SimultaneousTransfersForRetrieval)), - - Override(new(retrievalmarket.RetrievalClient), modules.RetrievalClient(cfg.Client.OffChainRetrieval)), - 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 08c71ba19..fddec7785 100644 --- a/node/builder_miner.go +++ b/node/builder_miner.go @@ -2,16 +2,10 @@ package node import ( "errors" - "time" - provider "github.com/ipni/index-provider" "go.uber.org/fx" "golang.org/x/xerrors" - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - rmnet "github.com/filecoin-project/go-fil-markets/retrievalmarket/network" - "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/api" @@ -20,12 +14,6 @@ import ( "github.com/filecoin-project/lotus/chain/gen" "github.com/filecoin-project/lotus/chain/gen/slashfilter" "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - "github.com/filecoin-project/lotus/markets/dagstore" - "github.com/filecoin-project/lotus/markets/dealfilter" - "github.com/filecoin-project/lotus/markets/idxprov" - "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" "github.com/filecoin-project/lotus/node/impl" @@ -62,21 +50,6 @@ func ConfigStorageMiner(c interface{}) Option { return Error(xerrors.Errorf("invalid config from repo, got: %T", c)) } - pricingConfig := cfg.Dealmaking.RetrievalPricing - if pricingConfig.Strategy == config.RetrievalPricingExternalMode { - if pricingConfig.External == nil { - return Error(xerrors.New("retrieval pricing policy has been to set to external but external policy config is nil")) - } - - if pricingConfig.External.Path == "" { - return Error(xerrors.New("retrieval pricing policy has been to set to external but external script path is empty")) - } - } else if pricingConfig.Strategy != config.RetrievalPricingDefaultMode { - return Error(xerrors.New("retrieval pricing policy must be either default or external")) - } - - enableLibp2pNode := cfg.Subsystems.EnableMarkets // we enable libp2p nodes if the storage market subsystem is enabled, otherwise we don't - return Options( Override(new(v1api.FullNode), modules.MakeUuidWrapper), @@ -84,7 +57,7 @@ func ConfigStorageMiner(c interface{}) Option { Override(new(dtypes.DrandSchedule), modules.BuiltinDrandConfig), Override(new(dtypes.BootstrapPeers), modules.BuiltinBootstrap), Override(new(dtypes.DrandBootstrap), modules.DrandBootstrap), - ConfigCommon(&cfg.Common, enableLibp2pNode), + ConfigCommon(&cfg.Common, build.NodeUserVersion(), false), Override(CheckFDLimit, modules.CheckFdLimit(build.MinerFDLimit)), // recommend at least 100k FD limit to miners @@ -93,7 +66,6 @@ func ConfigStorageMiner(c interface{}) Option { Override(new(*paths.Local), modules.LocalStorage), Override(new(*paths.Remote), modules.RemoteStorage), Override(new(paths.Store), From(new(*paths.Remote))), - Override(new(dtypes.RetrievalPricingFunc), modules.RetrievalPricingFunc(cfg.Dealmaking)), If(cfg.Subsystems.EnableMining || cfg.Subsystems.EnableSealing, Override(GetParamsKey, modules.GetParams(!cfg.Proving.DisableBuiltinWindowPoSt || !cfg.Proving.DisableBuiltinWinningPoSt || cfg.Storage.AllowCommit || cfg.Storage.AllowProveReplicaUpdate2)), @@ -164,88 +136,6 @@ func ConfigStorageMiner(c interface{}) Option { Override(new(paths.SectorIndex), From(new(modules.MinerSealingService))), ), - If(cfg.Subsystems.EnableMarkets, - - // Alert that legacy-markets is being deprecated - Override(LegacyMarketsEOL, modules.LegacyMarketsEOL), - - // Markets - Override(new(dtypes.StagingBlockstore), modules.StagingBlockstore), - Override(new(dtypes.StagingGraphsync), modules.StagingGraphsync(cfg.Dealmaking.SimultaneousTransfersForStorage, cfg.Dealmaking.SimultaneousTransfersForStoragePerClient, cfg.Dealmaking.SimultaneousTransfersForRetrieval)), - Override(new(dtypes.ProviderPieceStore), modules.NewProviderPieceStore), - Override(new(*sectorblocks.SectorBlocks), sectorblocks.NewSectorBlocks), - - // Markets (retrieval deps) - Override(new(sectorstorage.PieceProvider), sectorstorage.NewPieceProvider), - Override(new(dtypes.RetrievalPricingFunc), modules.RetrievalPricingFunc(config.DealmakingConfig{ - RetrievalPricing: &config.RetrievalPricing{ - Strategy: config.RetrievalPricingDefaultMode, - Default: &config.RetrievalPricingDefault{}, - }, - })), - Override(new(dtypes.RetrievalPricingFunc), modules.RetrievalPricingFunc(cfg.Dealmaking)), - - // DAG Store - Override(new(dagstore.MinerAPI), modules.NewMinerAPI(cfg.DAGStore)), - Override(DAGStoreKey, modules.DAGStore(cfg.DAGStore)), - - // Markets (retrieval) - Override(new(dagstore.SectorAccessor), sectoraccessor.NewSectorAccessor), - Override(new(retrievalmarket.SectorAccessor), From(new(dagstore.SectorAccessor))), - Override(new(retrievalmarket.RetrievalProviderNode), retrievaladapter.NewRetrievalProviderNode), - Override(new(rmnet.RetrievalMarketNetwork), modules.RetrievalNetwork), - Override(new(retrievalmarket.RetrievalProvider), modules.RetrievalProvider), - Override(new(dtypes.RetrievalDealFilter), modules.RetrievalDealFilter(nil)), - Override(HandleRetrievalKey, modules.HandleRetrieval), - - // Markets (storage) - Override(new(dtypes.ProviderTransferNetwork), modules.NewProviderTransferNetwork), - Override(new(dtypes.ProviderTransport), modules.NewProviderTransport), - Override(new(dtypes.ProviderDataTransfer), modules.NewProviderDataTransfer), - Override(new(idxprov.MeshCreator), idxprov.NewMeshCreator), - Override(new(provider.Interface), modules.IndexProvider(cfg.IndexProvider)), - Override(new(*storedask.StoredAsk), modules.NewStorageAsk), - Override(new(dtypes.StorageDealFilter), modules.BasicDealFilter(cfg.Dealmaking, nil)), - Override(new(storagemarket.StorageProvider), modules.StorageProvider), - Override(new(*storageadapter.DealPublisher), storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{})), - Override(HandleMigrateProviderFundsKey, modules.HandleMigrateProviderFunds), - Override(HandleDealsKey, modules.HandleDeals), - - // Config (todo: get a real property system) - Override(new(dtypes.ConsiderOnlineStorageDealsConfigFunc), modules.NewConsiderOnlineStorageDealsConfigFunc), - Override(new(dtypes.SetConsiderOnlineStorageDealsConfigFunc), modules.NewSetConsideringOnlineStorageDealsFunc), - Override(new(dtypes.ConsiderOnlineRetrievalDealsConfigFunc), modules.NewConsiderOnlineRetrievalDealsConfigFunc), - Override(new(dtypes.SetConsiderOnlineRetrievalDealsConfigFunc), modules.NewSetConsiderOnlineRetrievalDealsConfigFunc), - Override(new(dtypes.StorageDealPieceCidBlocklistConfigFunc), modules.NewStorageDealPieceCidBlocklistConfigFunc), - Override(new(dtypes.SetStorageDealPieceCidBlocklistConfigFunc), modules.NewSetStorageDealPieceCidBlocklistConfigFunc), - Override(new(dtypes.ConsiderOfflineStorageDealsConfigFunc), modules.NewConsiderOfflineStorageDealsConfigFunc), - Override(new(dtypes.SetConsiderOfflineStorageDealsConfigFunc), modules.NewSetConsideringOfflineStorageDealsFunc), - Override(new(dtypes.ConsiderOfflineRetrievalDealsConfigFunc), modules.NewConsiderOfflineRetrievalDealsConfigFunc), - Override(new(dtypes.SetConsiderOfflineRetrievalDealsConfigFunc), modules.NewSetConsiderOfflineRetrievalDealsConfigFunc), - Override(new(dtypes.ConsiderVerifiedStorageDealsConfigFunc), modules.NewConsiderVerifiedStorageDealsConfigFunc), - Override(new(dtypes.SetConsiderVerifiedStorageDealsConfigFunc), modules.NewSetConsideringVerifiedStorageDealsFunc), - Override(new(dtypes.ConsiderUnverifiedStorageDealsConfigFunc), modules.NewConsiderUnverifiedStorageDealsConfigFunc), - Override(new(dtypes.SetConsiderUnverifiedStorageDealsConfigFunc), modules.NewSetConsideringUnverifiedStorageDealsFunc), - Override(new(dtypes.SetExpectedSealDurationFunc), modules.NewSetExpectedSealDurationFunc), - Override(new(dtypes.GetExpectedSealDurationFunc), modules.NewGetExpectedSealDurationFunc), - Override(new(dtypes.SetMaxDealStartDelayFunc), modules.NewSetMaxDealStartDelayFunc), - Override(new(dtypes.GetMaxDealStartDelayFunc), modules.NewGetMaxDealStartDelayFunc), - - If(cfg.Dealmaking.Filter != "", - Override(new(dtypes.StorageDealFilter), modules.BasicDealFilter(cfg.Dealmaking, dealfilter.CliStorageDealFilter(cfg.Dealmaking.Filter))), - ), - - If(cfg.Dealmaking.RetrievalFilter != "", - Override(new(dtypes.RetrievalDealFilter), modules.RetrievalDealFilter(dealfilter.CliRetrievalDealFilter(cfg.Dealmaking.RetrievalFilter))), - ), - Override(new(*storageadapter.DealPublisher), storageadapter.NewDealPublisher(&cfg.Fees, storageadapter.PublishMsgConfig{ - Period: time.Duration(cfg.Dealmaking.PublishMsgPeriod), - MaxDealsPerMsg: cfg.Dealmaking.MaxDealsPerPublishMsg, - StartEpochSealingBuffer: cfg.Dealmaking.StartEpochSealingBuffer, - })), - Override(new(storagemarket.StorageProviderNode), storageadapter.NewProviderNodeAdapter(&cfg.Fees, &cfg.Dealmaking)), - ), - Override(new(config.SealerConfig), cfg.Storage), Override(new(config.ProvingConfig), cfg.Proving), Override(new(config.HarmonyDB), cfg.HarmonyDB), @@ -254,7 +144,7 @@ func ConfigStorageMiner(c interface{}) Option { ) } -func StorageMiner(out *api.StorageMiner, subsystemsCfg config.MinerSubsystemConfig) Option { +func StorageMiner(out *api.StorageMiner) Option { return Options( ApplyIf(func(s *Settings) bool { return s.Config }, Error(errors.New("the StorageMiner option must be set before Config option")), @@ -262,7 +152,6 @@ func StorageMiner(out *api.StorageMiner, subsystemsCfg config.MinerSubsystemConf func(s *Settings) error { s.nodeType = repo.StorageMiner - s.enableLibp2pNode = subsystemsCfg.EnableMarkets return nil }, diff --git a/node/config/def.go b/node/config/def.go index 2dd4b77eb..d8b8e0bab 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -2,12 +2,8 @@ package config import ( "encoding" - "os" - "strconv" "time" - "github.com/ipfs/go-cid" - "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/network" @@ -18,24 +14,6 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) -const ( - // RetrievalPricingDefault configures the node to use the default retrieval pricing policy. - RetrievalPricingDefaultMode = "default" - // RetrievalPricingExternal configures the node to use the external retrieval pricing script - // configured by the user. - RetrievalPricingExternalMode = "external" -) - -// MaxTraversalLinks configures the maximum number of links to traverse in a DAG while calculating -// CommP and traversing a DAG with graphsync; invokes a budget on DAG depth and density. -var MaxTraversalLinks uint64 = 32 * (1 << 20) - -func init() { - if envMaxTraversal, err := strconv.ParseUint(os.Getenv("LOTUS_MAX_TRAVERSAL_LINKS"), 10, 64); err == nil { - MaxTraversalLinks = envMaxTraversal - } -} - func (b *BatchFeeConfig) FeeForSectors(nSectors int) abi.TokenAmount { return big.Add(big.Int(b.Base), big.Mul(big.NewInt(int64(nSectors)), big.Int(b.PerSector))) } @@ -77,8 +55,6 @@ func defCommon() Common { } } -var DefaultSimultaneousTransfers = uint64(20) - func DefaultDefaultMaxFee() types.FIL { return types.MustParseFIL("0.07") } @@ -90,10 +66,7 @@ func DefaultFullNode() *FullNode { Fees: FeeConfig{ DefaultMaxFee: DefaultDefaultMaxFee(), }, - Client: Client{ - SimultaneousTransfersForStorage: DefaultSimultaneousTransfers, - SimultaneousTransfersForRetrieval: DefaultSimultaneousTransfers, - }, + Chainstore: Chainstore{ EnableSplitstore: true, Splitstore: Splitstore{ @@ -193,52 +166,13 @@ func DefaultStorageMiner() *StorageMiner { }, Dealmaking: DealmakingConfig{ - ConsiderOnlineStorageDeals: true, - ConsiderOfflineStorageDeals: true, - ConsiderOnlineRetrievalDeals: true, - ConsiderOfflineRetrievalDeals: true, - ConsiderVerifiedStorageDeals: true, - ConsiderUnverifiedStorageDeals: true, - PieceCidBlocklist: []cid.Cid{}, - // TODO: It'd be nice to set this based on sector size - MaxDealStartDelay: Duration(time.Hour * 24 * 14), - ExpectedSealDuration: Duration(time.Hour * 24), - PublishMsgPeriod: Duration(time.Hour), - MaxDealsPerPublishMsg: 8, - MaxProviderCollateralMultiplier: 2, - - SimultaneousTransfersForStorage: DefaultSimultaneousTransfers, - SimultaneousTransfersForStoragePerClient: 0, - SimultaneousTransfersForRetrieval: DefaultSimultaneousTransfers, - StartEpochSealingBuffer: 480, // 480 epochs buffer == 4 hours from adding deal to sector to sector being sealed - - RetrievalPricing: &RetrievalPricing{ - Strategy: RetrievalPricingDefaultMode, - Default: &RetrievalPricingDefault{ - VerifiedDealsFreeTransfer: true, - }, - External: &RetrievalPricingExternal{ - Path: "", - }, - }, - }, - - IndexProvider: IndexProviderConfig{ - Enable: true, - EntriesCacheCapacity: 1024, - EntriesChunkSize: 16384, - // The default empty TopicName means it is inferred from network name, in the following - // format: "/indexer/ingest/" - TopicName: "", - PurgeCacheOnStart: false, }, Subsystems: MinerSubsystemConfig{ EnableMining: true, EnableSealing: true, EnableSectorStorage: true, - EnableMarkets: false, EnableSectorIndexDB: false, }, @@ -270,12 +204,6 @@ func DefaultStorageMiner() *StorageMiner { DealPublishControl: []string{}, }, - DAGStore: DAGStoreConfig{ - MaxConcurrentIndex: 5, - MaxConcurrencyStorageCalls: 100, - MaxConcurrentUnseals: 5, - GCInterval: Duration(1 * time.Minute), - }, HarmonyDB: HarmonyDB{ Hosts: []string{"127.0.0.1"}, Username: "yugabyte", @@ -327,46 +255,3 @@ const ( // worker. The scheduler may assign any task to this worker. ResourceFilteringDisabled = ResourceFilteringStrategy("disabled") ) - -func DefaultCurioConfig() *CurioConfig { - return &CurioConfig{ - Subsystems: CurioSubsystemsConfig{ - GuiAddress: ":4701", - BoostAdapters: []string{}, - }, - Fees: CurioFees{ - DefaultMaxFee: DefaultDefaultMaxFee(), - MaxPreCommitGasFee: types.MustParseFIL("0.025"), - MaxCommitGasFee: types.MustParseFIL("0.05"), - - MaxPreCommitBatchGasFee: BatchFeeConfig{ - Base: types.MustParseFIL("0"), - PerSector: types.MustParseFIL("0.02"), - }, - MaxCommitBatchGasFee: BatchFeeConfig{ - Base: types.MustParseFIL("0"), - PerSector: types.MustParseFIL("0.03"), // enough for 6 agg and 1nFIL base fee - }, - - MaxTerminateGasFee: types.MustParseFIL("0.5"), - MaxWindowPoStGasFee: types.MustParseFIL("5"), - MaxPublishDealsFee: types.MustParseFIL("0.05"), - }, - Addresses: []CurioAddresses{{ - PreCommitControl: []string{}, - CommitControl: []string{}, - TerminateControl: []string{}, - MinerAddresses: []string{}, - }}, - Proving: CurioProvingConfig{ - ParallelCheckLimit: 32, - PartitionCheckTimeout: Duration(20 * time.Minute), - SingleCheckTimeout: Duration(10 * time.Minute), - }, - Ingest: CurioIngestConfig{ - MaxQueueSDR: 8, // default to 8 sectors before sdr - MaxQueueTrees: 0, // default don't use this limit - MaxQueuePoRep: 0, // default don't use this limit - }, - } -} diff --git a/node/config/def_test.go b/node/config/def_test.go index 627b65a56..2edcce2b5 100644 --- a/node/config/def_test.go +++ b/node/config/def_test.go @@ -79,9 +79,3 @@ func TestDefaultMinerRoundtrip(t *testing.T) { fmt.Println(c2) require.True(t, reflect.DeepEqual(c, c2)) } - -func TestDefaultStorageMiner_IsEmpty(t *testing.T) { - subject := DefaultStorageMiner() - require.True(t, subject.IndexProvider.Enable) - require.Equal(t, "", subject.IndexProvider.TopicName) -} diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 74549163e..3f66344c8 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -85,30 +85,6 @@ your node if metadata log is disabled`, Comment: ``, }, }, - "Client": { - { - Name: "SimultaneousTransfersForStorage", - Type: "uint64", - - Comment: `The maximum number of simultaneous data transfers between the client -and storage providers for storage deals`, - }, - { - Name: "SimultaneousTransfersForRetrieval", - Type: "uint64", - - Comment: `The maximum number of simultaneous data transfers between the client -and storage providers for retrieval deals`, - }, - { - Name: "OffChainRetrieval", - Type: "bool", - - Comment: `Require that retrievals perform no on-chain operations. Paid retrievals -without existing payment channels with available funds will fail instead -of automatically performing on-chain operations.`, - }, - }, "Common": { { Name: "API", @@ -183,6 +159,30 @@ over the worker address if this flag is set.`, Comment: `MinerAddresses are the addresses of the miner actors to use for sending messages`, }, }, + "CurioAlerting": { + { + Name: "PagerDutyEventURL", + Type: "string", + + Comment: `PagerDutyEventURL is URL for PagerDuty.com Events API v2 URL. Events sent to this API URL are ultimately +routed to a PagerDuty.com service and processed. +The default is sufficient for integration with the stock commercial PagerDuty.com company's service.`, + }, + { + Name: "PageDutyIntegrationKey", + Type: "string", + + Comment: `PageDutyIntegrationKey is the integration key for a PagerDuty.com service. You can find this unique service +identifier in the integration page for the service.`, + }, + { + Name: "MinimumWalletBalance", + Type: "types.FIL", + + Comment: `MinimumWalletBalance is the minimum balance all active wallets. If the balance is below this value, an +alerts will be triggered for the wallet`, + }, + }, "CurioConfig": { { Name: "Subsystems", @@ -224,6 +224,12 @@ over the worker address if this flag is set.`, Name: "Apis", Type: "ApisConfig", + Comment: ``, + }, + { + Name: "Alerting", + Type: "CurioAlerting", + Comment: ``, }, }, @@ -584,8 +590,8 @@ uses all available network (or disk) bandwidth on the machine without causing bo Comment: `BoostAdapters is a list of tuples of miner address and port/ip to listen for market (e.g. boost) requests. This interface is compatible with the lotus-miner RPC, implementing a subset needed for storage market operations. -Strings should be in the format "actor:port" or "actor:ip:port". Default listen address is 0.0.0.0 -Example: "f0123:32100", "f0123:127.0.0.1:32100". Multiple addresses can be specified. +Strings should be in the format "actor:ip:port". IP cannot be 0.0.0.0. We recommend using a private IP. +Example: "f0123:127.0.0.1:32100". Multiple addresses can be specified. When a market node like boost gives Curio's market RPC a deal to placing into a sector, Curio will first store the deal data in a temporary location "Piece Park" before assigning it to a sector. This requires that at least one @@ -614,197 +620,13 @@ only need to be run on a single machine in the cluster.`, Comment: `The address that should listen for Web GUI requests.`, }, }, - "DAGStoreConfig": { - { - 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: "MaxConcurrentUnseals", - Type: "int", - - Comment: `The maximum amount of unseals that can be processed 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": { - { - Name: "ConsiderOnlineStorageDeals", - Type: "bool", - - Comment: `When enabled, the miner can accept online deals`, - }, - { - Name: "ConsiderOfflineStorageDeals", - Type: "bool", - - Comment: `When enabled, the miner can accept offline deals`, - }, - { - Name: "ConsiderOnlineRetrievalDeals", - Type: "bool", - - Comment: `When enabled, the miner can accept retrieval deals`, - }, - { - Name: "ConsiderOfflineRetrievalDeals", - Type: "bool", - - Comment: `When enabled, the miner can accept offline retrieval deals`, - }, - { - Name: "ConsiderVerifiedStorageDeals", - Type: "bool", - - Comment: `When enabled, the miner can accept verified deals`, - }, - { - Name: "ConsiderUnverifiedStorageDeals", - Type: "bool", - - Comment: `When enabled, the miner can accept unverified deals`, - }, - { - Name: "PieceCidBlocklist", - Type: "[]cid.Cid", - - Comment: `A list of Data CIDs to reject when making deals`, - }, - { - Name: "ExpectedSealDuration", - Type: "Duration", - - Comment: `Maximum expected amount of time getting the deal into a sealed sector will take -This includes the time the deal will need to get transferred and published -before being assigned to a sector`, - }, - { - Name: "MaxDealStartDelay", - Type: "Duration", - - Comment: `Maximum amount of time proposed deal StartEpoch can be in future`, - }, - { - Name: "PublishMsgPeriod", - Type: "Duration", - - Comment: `When a deal is ready to publish, the amount of time to wait for more -deals to be ready to publish before publishing them all as a batch`, - }, - { - Name: "MaxDealsPerPublishMsg", - Type: "uint64", - - Comment: `The maximum number of deals to include in a single PublishStorageDeals -message`, - }, - { - Name: "MaxProviderCollateralMultiplier", - Type: "uint64", - - Comment: `The maximum collateral that the provider will put up against a deal, -as a multiplier of the minimum collateral bound`, - }, - { - Name: "MaxStagingDealsBytes", - Type: "int64", - - Comment: `The maximum allowed disk usage size in bytes of staging deals not yet -passed to the sealing node by the markets service. 0 is unlimited.`, - }, - { - Name: "SimultaneousTransfersForStorage", - Type: "uint64", - - Comment: `The maximum number of parallel online data transfers for storage deals`, - }, - { - Name: "SimultaneousTransfersForStoragePerClient", - Type: "uint64", - - Comment: `The maximum number of simultaneous data transfers from any single client -for storage deals. -Unset by default (0), and values higher than SimultaneousTransfersForStorage -will have no effect; i.e. the total number of simultaneous data transfers -across all storage clients is bound by SimultaneousTransfersForStorage -regardless of this number.`, - }, - { - Name: "SimultaneousTransfersForRetrieval", - Type: "uint64", - - Comment: `The maximum number of parallel online data transfers for retrieval deals`, - }, { Name: "StartEpochSealingBuffer", Type: "uint64", Comment: `Minimum start epoch buffer to give time for sealing of sector with deal.`, }, - { - Name: "Filter", - Type: "string", - - Comment: `A command used for fine-grained evaluation of storage deals -see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details`, - }, - { - Name: "RetrievalFilter", - Type: "string", - - Comment: `A command used for fine-grained evaluation of retrieval deals -see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details`, - }, - { - Name: "RetrievalPricing", - Type: "*RetrievalPricing", - - Comment: ``, - }, }, "EventsConfig": { { @@ -927,12 +749,6 @@ Set to 0 to keep all mappings`, }, }, "FullNode": { - { - Name: "Client", - Type: "Client", - - Comment: ``, - }, { Name: "Wallet", Type: "Wallet", @@ -1018,51 +834,6 @@ in a cluster. Only 1 is required`, EnableMsgIndex enables indexing of messages on chain.`, }, }, - "IndexProviderConfig": { - { - Name: "Enable", - Type: "bool", - - Comment: `Enable set whether to enable indexing announcement to the network and expose endpoints that -allow indexer nodes to process announcements. Enabled by default.`, - }, - { - Name: "EntriesCacheCapacity", - Type: "int", - - Comment: `EntriesCacheCapacity sets the maximum capacity to use for caching the indexing advertisement -entries. Defaults to 1024 if not specified. The cache is evicted using LRU policy. The -maximum storage used by the cache is a factor of EntriesCacheCapacity, EntriesChunkSize and -the length of multihashes being advertised. For example, advertising 128-bit long multihashes -with the default EntriesCacheCapacity, and EntriesChunkSize means the cache size can grow to -256MiB when full.`, - }, - { - Name: "EntriesChunkSize", - Type: "int", - - Comment: `EntriesChunkSize sets the maximum number of multihashes to include in a single entries chunk. -Defaults to 16384 if not specified. Note that chunks are chained together for indexing -advertisements that include more multihashes than the configured EntriesChunkSize.`, - }, - { - Name: "TopicName", - Type: "string", - - Comment: `TopicName sets the topic name on which the changes to the advertised content are announced. -If not explicitly specified, the topic name is automatically inferred from the network name -in following format: '/indexer/ingest/' -Defaults to empty, which implies the topic name is inferred from network name.`, - }, - { - Name: "PurgeCacheOnStart", - Type: "bool", - - Comment: `PurgeCacheOnStart sets whether to clear any cached entries chunks when the provider engine -starts. By default, the cache is rehydrated from previously cached entries stored in -datastore if any is present.`, - }, - }, "JournalConfig": { { Name: "DisabledEvents", @@ -1263,12 +1034,6 @@ over the worker address if this flag is set.`, Comment: ``, }, - { - Name: "EnableMarkets", - Type: "bool", - - Comment: ``, - }, { Name: "EnableSectorIndexDB", Type: "bool", @@ -1499,46 +1264,6 @@ This property is used only if ElasticSearchTracer propery is set.`, Comment: `Auth token that will be passed with logs to elasticsearch - used for weighted peers score.`, }, }, - "RetrievalPricing": { - { - Name: "Strategy", - Type: "string", - - Comment: ``, - }, - { - Name: "Default", - Type: "*RetrievalPricingDefault", - - Comment: ``, - }, - { - Name: "External", - Type: "*RetrievalPricingExternal", - - Comment: ``, - }, - }, - "RetrievalPricingDefault": { - { - Name: "VerifiedDealsFreeTransfer", - Type: "bool", - - Comment: `VerifiedDealsFreeTransfer configures zero fees for data transfer for a retrieval deal -of a payloadCid that belongs to a verified storage deal. -This parameter is ONLY applicable if the retrieval pricing policy strategy has been configured to "default". -default value is true`, - }, - }, - "RetrievalPricingExternal": { - { - Name: "Path", - Type: "string", - - Comment: `Path of the external script that will be run to price a retrieval deal. -This parameter is ONLY applicable if the retrieval pricing policy strategy has been configured to "external".`, - }, - }, "SealerConfig": { { Name: "ParallelFetchLimit", @@ -1956,12 +1681,6 @@ HotstoreMaxSpaceTarget - HotstoreMaxSpaceSafetyBuffer`, Comment: ``, }, - { - Name: "IndexProvider", - Type: "IndexProviderConfig", - - Comment: ``, - }, { Name: "Proving", Type: "ProvingConfig", @@ -1992,12 +1711,6 @@ HotstoreMaxSpaceTarget - HotstoreMaxSpaceSafetyBuffer`, Comment: ``, }, - { - Name: "DAGStore", - Type: "DAGStoreConfig", - - Comment: ``, - }, { Name: "HarmonyDB", Type: "HarmonyDB", diff --git a/node/config/load.go b/node/config/load.go index 1d5a87458..b8fe5dcbd 100644 --- a/node/config/load.go +++ b/node/config/load.go @@ -81,7 +81,7 @@ func FromReader(reader io.Reader, def interface{}, opts ...LoadCfgOpt) (interfac } for _, d := range movedFields { if md.IsDefined(d.Field...) { - fmt.Fprintf( + _, _ = fmt.Fprintf( warningOut, "WARNING: Use of deprecated configuration option '%s' will be removed in a future release, use '%s' instead\n", strings.Join(d.Field, "."), diff --git a/node/config/types.go b/node/config/types.go index c15df320f..c6c7aaef4 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -1,8 +1,6 @@ package config import ( - "github.com/ipfs/go-cid" - "github.com/filecoin-project/lotus/chain/types" ) @@ -22,7 +20,6 @@ type Common struct { // FullNode is a full node config type FullNode struct { Common - Client Client Wallet Wallet Fees FeeConfig Chainstore Chainstore @@ -53,17 +50,14 @@ type Logging struct { type StorageMiner struct { Common - Subsystems MinerSubsystemConfig - Dealmaking DealmakingConfig - IndexProvider IndexProviderConfig - Proving ProvingConfig - Sealing SealingConfig - Storage SealerConfig - Fees MinerFeeConfig - Addresses MinerAddressConfig - DAGStore DAGStoreConfig - - HarmonyDB HarmonyDB + Subsystems MinerSubsystemConfig + Dealmaking DealmakingConfig + Proving ProvingConfig + Sealing SealingConfig + Storage SealerConfig + Fees MinerFeeConfig + Addresses MinerAddressConfig + HarmonyDB HarmonyDB } type CurioConfig struct { @@ -77,6 +71,7 @@ type CurioConfig struct { Ingest CurioIngestConfig Journal JournalConfig Apis ApisConfig + Alerting CurioAlerting } type ApisConfig struct { @@ -203,8 +198,8 @@ type CurioSubsystemsConfig struct { // BoostAdapters is a list of tuples of miner address and port/ip to listen for market (e.g. boost) requests. // This interface is compatible with the lotus-miner RPC, implementing a subset needed for storage market operations. - // Strings should be in the format "actor:port" or "actor:ip:port". Default listen address is 0.0.0.0 - // Example: "f0123:32100", "f0123:127.0.0.1:32100". Multiple addresses can be specified. + // Strings should be in the format "actor:ip:port". IP cannot be 0.0.0.0. We recommend using a private IP. + // Example: "f0123:127.0.0.1:32100". Multiple addresses can be specified. // // When a market node like boost gives Curio's market RPC a deal to placing into a sector, Curio will first store the // deal data in a temporary location "Piece Park" before assigning it to a sector. This requires that at least one @@ -228,50 +223,10 @@ type CurioSubsystemsConfig struct { GuiAddress string } -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 amount of unseals that can be processed simultaneously - // from the storage subsystem. 0 means unlimited. - // Default value: 0 (unlimited). - MaxConcurrentUnseals 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 { EnableMining bool EnableSealing bool EnableSectorStorage bool - EnableMarkets bool // When enabled, the sector index will reside in an external database // as opposed to the local KV store in the miner process @@ -302,111 +257,8 @@ type MinerSubsystemConfig struct { } type DealmakingConfig struct { - // When enabled, the miner can accept online deals - ConsiderOnlineStorageDeals bool - // When enabled, the miner can accept offline deals - ConsiderOfflineStorageDeals bool - // When enabled, the miner can accept retrieval deals - ConsiderOnlineRetrievalDeals bool - // When enabled, the miner can accept offline retrieval deals - ConsiderOfflineRetrievalDeals bool - // When enabled, the miner can accept verified deals - ConsiderVerifiedStorageDeals bool - // When enabled, the miner can accept unverified deals - ConsiderUnverifiedStorageDeals bool - // A list of Data CIDs to reject when making deals - PieceCidBlocklist []cid.Cid - // Maximum expected amount of time getting the deal into a sealed sector will take - // This includes the time the deal will need to get transferred and published - // before being assigned to a sector - ExpectedSealDuration Duration - // Maximum amount of time proposed deal StartEpoch can be in future - MaxDealStartDelay Duration - // When a deal is ready to publish, the amount of time to wait for more - // deals to be ready to publish before publishing them all as a batch - PublishMsgPeriod Duration - // The maximum number of deals to include in a single PublishStorageDeals - // message - MaxDealsPerPublishMsg uint64 - // The maximum collateral that the provider will put up against a deal, - // as a multiplier of the minimum collateral bound - MaxProviderCollateralMultiplier uint64 - // The maximum allowed disk usage size in bytes of staging deals not yet - // passed to the sealing node by the markets service. 0 is unlimited. - MaxStagingDealsBytes int64 - // The maximum number of parallel online data transfers for storage deals - SimultaneousTransfersForStorage uint64 - // The maximum number of simultaneous data transfers from any single client - // for storage deals. - // Unset by default (0), and values higher than SimultaneousTransfersForStorage - // will have no effect; i.e. the total number of simultaneous data transfers - // across all storage clients is bound by SimultaneousTransfersForStorage - // regardless of this number. - SimultaneousTransfersForStoragePerClient uint64 - // The maximum number of parallel online data transfers for retrieval deals - SimultaneousTransfersForRetrieval uint64 // Minimum start epoch buffer to give time for sealing of sector with deal. StartEpochSealingBuffer uint64 - - // A command used for fine-grained evaluation of storage deals - // see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details - Filter string - // A command used for fine-grained evaluation of retrieval deals - // see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details - RetrievalFilter string - - RetrievalPricing *RetrievalPricing -} - -type IndexProviderConfig struct { - // Enable set whether to enable indexing announcement to the network and expose endpoints that - // allow indexer nodes to process announcements. Enabled by default. - Enable bool - - // EntriesCacheCapacity sets the maximum capacity to use for caching the indexing advertisement - // entries. Defaults to 1024 if not specified. The cache is evicted using LRU policy. The - // maximum storage used by the cache is a factor of EntriesCacheCapacity, EntriesChunkSize and - // the length of multihashes being advertised. For example, advertising 128-bit long multihashes - // with the default EntriesCacheCapacity, and EntriesChunkSize means the cache size can grow to - // 256MiB when full. - EntriesCacheCapacity int - - // EntriesChunkSize sets the maximum number of multihashes to include in a single entries chunk. - // Defaults to 16384 if not specified. Note that chunks are chained together for indexing - // advertisements that include more multihashes than the configured EntriesChunkSize. - EntriesChunkSize int - - // TopicName sets the topic name on which the changes to the advertised content are announced. - // If not explicitly specified, the topic name is automatically inferred from the network name - // in following format: '/indexer/ingest/' - // Defaults to empty, which implies the topic name is inferred from network name. - TopicName string - - // PurgeCacheOnStart sets whether to clear any cached entries chunks when the provider engine - // starts. By default, the cache is rehydrated from previously cached entries stored in - // datastore if any is present. - PurgeCacheOnStart bool -} - -type RetrievalPricing struct { - Strategy string // possible values: "default", "external" - - Default *RetrievalPricingDefault - External *RetrievalPricingExternal -} - -type RetrievalPricingExternal struct { - // Path of the external script that will be run to price a retrieval deal. - // This parameter is ONLY applicable if the retrieval pricing policy strategy has been configured to "external". - Path string -} - -type RetrievalPricingDefault struct { - // VerifiedDealsFreeTransfer configures zero fees for data transfer for a retrieval deal - // of a payloadCid that belongs to a verified storage deal. - // This parameter is ONLY applicable if the retrieval pricing policy strategy has been configured to "default". - // default value is true - VerifiedDealsFreeTransfer bool } type ProvingConfig struct { @@ -965,20 +817,6 @@ type Splitstore struct { } // // Full Node -type Client struct { - // The maximum number of simultaneous data transfers between the client - // and storage providers for storage deals - SimultaneousTransfersForStorage uint64 - // The maximum number of simultaneous data transfers between the client - // and storage providers for retrieval deals - SimultaneousTransfersForRetrieval uint64 - - // Require that retrievals perform no on-chain operations. Paid retrievals - // without existing payment channels with available funds will fail instead - // of automatically performing on-chain operations. - OffChainRetrieval bool -} - type Wallet struct { RemoteBackend string EnableLedger bool @@ -1109,3 +947,18 @@ type FaultReporterConfig struct { // rewards. This address should have adequate funds to cover gas fees. ConsensusFaultReporterAddress string } + +type CurioAlerting struct { + // PagerDutyEventURL is URL for PagerDuty.com Events API v2 URL. Events sent to this API URL are ultimately + // routed to a PagerDuty.com service and processed. + // The default is sufficient for integration with the stock commercial PagerDuty.com company's service. + PagerDutyEventURL string + + // PageDutyIntegrationKey is the integration key for a PagerDuty.com service. You can find this unique service + // identifier in the integration page for the service. + PageDutyIntegrationKey string + + // MinimumWalletBalance is the minimum balance all active wallets. If the balance is below this value, an + // alerts will be triggered for the wallet + MinimumWalletBalance types.FIL +} diff --git a/node/hello/hello.go b/node/hello/hello.go index cd1645d3e..ef15ece16 100644 --- a/node/hello/hello.go +++ b/node/hello/hello.go @@ -7,6 +7,7 @@ import ( "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" inet "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" @@ -121,7 +122,10 @@ func (hs *Service) HandleStream(s inet.Stream) { hs.pmgr.AddFilecoinPeer(s.Conn().RemotePeer()) } - ts, err := hs.syncer.FetchTipSet(context.Background(), s.Conn().RemotePeer(), types.NewTipSetKey(hmsg.HeaviestTipSet...)) + // We're trying to fetch the tipset from the peer that just said hello to us. No point in + // triggering any dials. + ctx := network.WithNoDial(context.Background(), "fetching filecoin hello tipset") + ts, err := hs.syncer.FetchTipSet(ctx, s.Conn().RemotePeer(), types.NewTipSetKey(hmsg.HeaviestTipSet...)) if err != nil { log.Errorf("failed to fetch tipset from peer during hello: %+v", err) return diff --git a/node/impl/client/car_helpers.go b/node/impl/client/car_helpers.go deleted file mode 100644 index c638b4bef..000000000 --- a/node/impl/client/car_helpers.go +++ /dev/null @@ -1,91 +0,0 @@ -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 deleted file mode 100644 index c7bb252a1..000000000 --- a/node/impl/client/client.go +++ /dev/null @@ -1,1536 +0,0 @@ -package client - -import ( - "bufio" - "bytes" - "context" - "errors" - "fmt" - "io" - "os" - "sort" - "strings" - "sync" - "time" - - "github.com/ipfs/boxo/blockservice" - bstore "github.com/ipfs/boxo/blockstore" - offline "github.com/ipfs/boxo/exchange/offline" - "github.com/ipfs/boxo/files" - "github.com/ipfs/boxo/ipld/merkledag" - unixfile "github.com/ipfs/boxo/ipld/unixfs/file" - "github.com/ipfs/go-cid" - format "github.com/ipfs/go-ipld-format" - logging "github.com/ipfs/go-log/v2" - "github.com/ipld/go-car" - "github.com/ipld/go-car/util" - carv2 "github.com/ipld/go-car/v2" - carv2bs "github.com/ipld/go-car/v2/blockstore" - "github.com/ipld/go-ipld-prime" - "github.com/ipld/go-ipld-prime/datamodel" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" - basicnode "github.com/ipld/go-ipld-prime/node/basic" - "github.com/ipld/go-ipld-prime/traversal" - "github.com/ipld/go-ipld-prime/traversal/selector" - "github.com/ipld/go-ipld-prime/traversal/selector/builder" - selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse" - textselector "github.com/ipld/go-ipld-selector-text-lite" - "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/multiformats/go-multibase" - "go.uber.org/fx" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - cborutil "github.com/filecoin-project/go-cbor-util" - "github.com/filecoin-project/go-commp-utils/writer" - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - "github.com/filecoin-project/go-fil-markets/discovery" - rm "github.com/filecoin-project/go-fil-markets/retrievalmarket" - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-fil-markets/storagemarket/network" - "github.com/filecoin-project/go-fil-markets/stores" - "github.com/filecoin-project/go-padreader" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" - markettypes "github.com/filecoin-project/go-state-types/builtin/v9/market" - "github.com/filecoin-project/go-state-types/dline" - - "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/lib/unixfs" - "github.com/filecoin-project/lotus/markets/retrievaladapter" - "github.com/filecoin-project/lotus/markets/storageadapter" - "github.com/filecoin-project/lotus/markets/utils" - "github.com/filecoin-project/lotus/node/config" - "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" - "github.com/filecoin-project/lotus/node/repo/imports" -) - -var log = logging.Logger("client") - -var DefaultHashFunction = unixfs.DefaultHashFunction - -// 8 days ~= SealDuration + PreCommit + MaxProveCommitDuration + 8 hour buffer -const dealStartBufferHours uint64 = 8 * 24 -const DefaultDAGStoreDir = "dagstore" - -type API struct { - fx.In - - full.ChainAPI - full.WalletAPI - paych.PaychAPI - full.StateAPI - - SMDealClient storagemarket.StorageClient - RetDiscovery discovery.PeerResolver - Retrieval rm.RetrievalClient - Chain *store.ChainStore - - // accessors for imports and retrievals. - Imports dtypes.ClientImportMgr - StorageBlockstoreAccessor storagemarket.BlockstoreAccessor - RtvlBlockstoreAccessor rm.BlockstoreAccessor - ApiBlockstoreAccessor *retrievaladapter.APIBlockstoreAccessor - - DataTransfer dtypes.ClientDataTransfer - Host host.Host - - Repo repo.LockedRepo -} - -func calcDealExpiration(minDuration uint64, md *dline.Info, startEpoch abi.ChainEpoch) abi.ChainEpoch { - // Make sure we give some time for the miner to seal - minExp := startEpoch + abi.ChainEpoch(minDuration) - - // Align on miners ProvingPeriodBoundary - exp := minExp + md.WPoStProvingPeriod - (minExp % md.WPoStProvingPeriod) + (md.PeriodStart % md.WPoStProvingPeriod) - 1 - // Should only be possible for miners created around genesis - for exp < minExp { - exp += md.WPoStProvingPeriod - } - - return exp -} - -// importManager converts the injected type to the required type. -func (a *API) importManager() *imports.Manager { - return a.Imports -} - -func (a *API) ClientStartDeal(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) { - return a.dealStarter(ctx, params, false) -} - -func (a *API) ClientStatelessDeal(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) { - return a.dealStarter(ctx, params, true) -} - -func (a *API) dealStarter(ctx context.Context, params *api.StartDealParams, isStateless bool) (*cid.Cid, error) { - if isStateless { - if params.Data.TransferType != storagemarket.TTManual { - return nil, xerrors.Errorf("invalid transfer type %s for stateless storage deal", params.Data.TransferType) - } - if !params.EpochPrice.IsZero() { - return nil, xerrors.New("stateless storage deals can only be initiated with storage price of 0") - } - } else if params.Data.TransferType == storagemarket.TTGraphsync { - 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(ctx, 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) - if err != nil { - return nil, xerrors.Errorf("failed resolving params.Wallet addr (%s): %w", params.Wallet, err) - } - - exist, err := a.WalletHas(ctx, walletKey) - if err != nil { - return nil, xerrors.Errorf("failed getting addr from wallet (%s): %w", params.Wallet, err) - } - if !exist { - return nil, xerrors.Errorf("provided address doesn't exist in wallet") - } - - mi, err := a.StateMinerInfo(ctx, params.Miner, types.EmptyTSK) - if err != nil { - return nil, xerrors.Errorf("failed getting peer ID: %w", err) - } - - md, err := a.StateMinerProvingDeadline(ctx, params.Miner, types.EmptyTSK) - if err != nil { - return nil, xerrors.Errorf("failed getting miner's deadline info: %w", err) - } - - if uint64(params.Data.PieceSize.Padded()) > uint64(mi.SectorSize) { - return nil, xerrors.New("data doesn't fit in a sector") - } - - dealStart := params.DealStartEpoch - if dealStart <= 0 { // unset, or explicitly 'epoch undefined' - ts, err := a.ChainHead(ctx) - if err != nil { - return nil, xerrors.Errorf("failed getting chain height: %w", err) - } - - blocksPerHour := 60 * 60 / build.BlockDelaySecs - dealStart = ts.Height() + abi.ChainEpoch(dealStartBufferHours*blocksPerHour) // TODO: Get this from storage ask - } - - networkVersion, err := a.StateNetworkVersion(ctx, types.EmptyTSK) - if err != nil { - return nil, xerrors.Errorf("failed to get network version: %w", err) - } - - st, err := miner.PreferredSealProofTypeFromWindowPoStType(networkVersion, mi.WindowPoStProofType, false) - if err != nil { - return nil, xerrors.Errorf("failed to get seal proof type: %w", err) - } - - // regular flow - if !isStateless { - providerInfo := utils.NewStorageProviderInfo(params.Miner, mi.Worker, mi.SectorSize, *mi.PeerId, mi.Multiaddrs) - - result, err := a.SMDealClient.ProposeStorageDeal(ctx, storagemarket.ProposeStorageDealParams{ - Addr: params.Wallet, - Info: &providerInfo, - Data: params.Data, - StartEpoch: dealStart, - EndEpoch: calcDealExpiration(params.MinBlocksDuration, md, dealStart), - Price: params.EpochPrice, - Collateral: params.ProviderCollateral, - Rt: st, - FastRetrieval: params.FastRetrieval, - VerifiedDeal: params.VerifiedDeal, - }) - - if err != nil { - return nil, xerrors.Errorf("failed to start deal: %w", err) - } - - return &result.ProposalCid, nil - } - - // - // stateless flow from here to the end - // - - label, err := markettypes.NewLabelFromString(params.Data.Root.Encode(multibase.MustNewEncoder('u'))) - if err != nil { - return nil, xerrors.Errorf("failed to encode label: %w", err) - } - - dealProposal := &markettypes.DealProposal{ - PieceCID: *params.Data.PieceCid, - PieceSize: params.Data.PieceSize.Padded(), - Client: walletKey, - Provider: params.Miner, - Label: label, - StartEpoch: dealStart, - EndEpoch: calcDealExpiration(params.MinBlocksDuration, md, dealStart), - StoragePricePerEpoch: big.Zero(), - ProviderCollateral: params.ProviderCollateral, - ClientCollateral: big.Zero(), - VerifiedDeal: params.VerifiedDeal, - } - - if dealProposal.ProviderCollateral.IsZero() { - networkCollateral, err := a.StateDealProviderCollateralBounds(ctx, params.Data.PieceSize.Padded(), params.VerifiedDeal, types.EmptyTSK) - if err != nil { - return nil, xerrors.Errorf("failed to determine minimum provider collateral: %w", err) - } - dealProposal.ProviderCollateral = networkCollateral.Min - } - - dealProposalSerialized, err := cborutil.Dump(dealProposal) - if err != nil { - return nil, xerrors.Errorf("failed to serialize deal proposal: %w", err) - } - - dealProposalSig, err := a.WalletSign(ctx, walletKey, dealProposalSerialized) - if err != nil { - return nil, xerrors.Errorf("failed to sign proposal : %w", err) - } - - dealProposalSigned := &markettypes.ClientDealProposal{ - Proposal: *dealProposal, - ClientSignature: *dealProposalSig, - } - dStream, err := network.NewFromLibp2pHost(a.Host, - // params duplicated from .../node/modules/client.go - // https://github.com/filecoin-project/lotus/pull/5961#discussion_r629768011 - network.RetryParameters(time.Second, 5*time.Minute, 15, 5), - ).NewDealStream(ctx, *mi.PeerId) - if err != nil { - return nil, xerrors.Errorf("opening dealstream to %s/%s failed: %w", params.Miner, *mi.PeerId, err) - } - - if err = dStream.WriteDealProposal(network.Proposal{ - FastRetrieval: true, - DealProposal: dealProposalSigned, - Piece: &storagemarket.DataRef{ - TransferType: storagemarket.TTManual, - Root: params.Data.Root, - PieceCid: params.Data.PieceCid, - PieceSize: params.Data.PieceSize, - }, - }); err != nil { - return nil, xerrors.Errorf("sending deal proposal failed: %w", err) - } - - resp, _, err := dStream.ReadDealResponse() - if err != nil { - return nil, xerrors.Errorf("reading proposal response failed: %w", err) - } - - dealProposalIpld, err := cborutil.AsIpld(dealProposalSigned) - if err != nil { - return nil, xerrors.Errorf("serializing proposal node failed: %w", err) - } - - if !dealProposalIpld.Cid().Equals(resp.Response.Proposal) { - return nil, xerrors.Errorf("provider returned proposal cid %s but we expected %s", resp.Response.Proposal, dealProposalIpld.Cid()) - } - - if resp.Response.State != storagemarket.StorageDealWaitingForData { - return nil, xerrors.Errorf("provider returned unexpected state %d for proposal %s, with message: %s", resp.Response.State, resp.Response.Proposal, resp.Response.Message) - } - - return &resp.Response.Proposal, nil -} - -func (a *API) ClientListDeals(ctx context.Context) ([]api.DealInfo, error) { - deals, err := a.SMDealClient.ListLocalDeals(ctx) - if err != nil { - return nil, err - } - - // Get a map of transfer ID => DataTransfer - dataTransfersByID, err := a.transfersByID(ctx) - if err != nil { - return nil, err - } - - out := make([]api.DealInfo, len(deals)) - for k, v := range deals { - // Find the data transfer associated with this deal - var transferCh *api.DataTransferChannel - if v.TransferChannelID != nil { - if ch, ok := dataTransfersByID[*v.TransferChannelID]; ok { - transferCh = &ch - } - } - - out[k] = a.newDealInfoWithTransfer(transferCh, v) - } - - return out, nil -} - -func (a *API) transfersByID(ctx context.Context) (map[datatransfer.ChannelID]api.DataTransferChannel, error) { - inProgressChannels, err := a.DataTransfer.InProgressChannels(ctx) - if err != nil { - return nil, err - } - - dataTransfersByID := make(map[datatransfer.ChannelID]api.DataTransferChannel, len(inProgressChannels)) - for id, channelState := range inProgressChannels { - ch := api.NewDataTransferChannel(a.Host.ID(), channelState) - dataTransfersByID[id] = ch - } - return dataTransfersByID, nil -} - -func (a *API) ClientGetDealInfo(ctx context.Context, d cid.Cid) (*api.DealInfo, error) { - v, err := a.SMDealClient.GetLocalDeal(ctx, d) - if err != nil { - return nil, err - } - - di := a.newDealInfo(ctx, v) - return &di, nil -} - -func (a *API) ClientGetDealUpdates(ctx context.Context) (<-chan api.DealInfo, error) { - updates := make(chan api.DealInfo) - - unsub := a.SMDealClient.SubscribeToEvents(func(_ storagemarket.ClientEvent, deal storagemarket.ClientDeal) { - updates <- a.newDealInfo(ctx, deal) - }) - - go func() { - defer unsub() - <-ctx.Done() - }() - - return updates, nil -} - -func (a *API) newDealInfo(ctx context.Context, v storagemarket.ClientDeal) api.DealInfo { - // Find the data transfer associated with this deal - var transferCh *api.DataTransferChannel - if v.TransferChannelID != nil { - state, err := a.DataTransfer.ChannelState(ctx, *v.TransferChannelID) - - // Note: If there was an error just ignore it, as the data transfer may - // be not found if it's no longer active - if err == nil { - ch := api.NewDataTransferChannel(a.Host.ID(), state) - ch.Stages = state.Stages() - transferCh = &ch - } - } - - di := a.newDealInfoWithTransfer(transferCh, v) - di.DealStages = v.DealStages - return di -} - -func (a *API) newDealInfoWithTransfer(transferCh *api.DataTransferChannel, v storagemarket.ClientDeal) api.DealInfo { - return api.DealInfo{ - ProposalCid: v.ProposalCid, - DataRef: v.DataRef, - State: v.State, - Message: v.Message, - Provider: v.Proposal.Provider, - PieceCID: v.Proposal.PieceCID, - Size: uint64(v.Proposal.PieceSize.Unpadded()), - PricePerEpoch: v.Proposal.StoragePricePerEpoch, - Duration: uint64(v.Proposal.Duration()), - DealID: v.DealID, - CreationTime: v.CreationTime.Time(), - Verified: v.Proposal.VerifiedDeal, - TransferChannelID: v.TransferChannelID, - DataTransfer: transferCh, - } -} - -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 -} - -func (a *API) ClientFindData(ctx context.Context, root cid.Cid, piece *cid.Cid) ([]api.QueryOffer, error) { - peers, err := a.RetDiscovery.GetPeers(root) - if err != nil { - return nil, err - } - - out := make([]api.QueryOffer, 0, len(peers)) - for _, p := range peers { - if piece != nil && !piece.Equals(*p.PieceCID) { - continue - } - - // do not rely on local data with respect to peer id - // fetch an up-to-date miner peer id from chain - mi, err := a.StateMinerInfo(ctx, p.Address, types.EmptyTSK) - if err != nil { - return nil, err - } - pp := rm.RetrievalPeer{ - Address: p.Address, - ID: *mi.PeerId, - } - - out = append(out, a.makeRetrievalQuery(ctx, pp, root, piece, rm.QueryParams{})) - } - - return out, nil -} - -func (a *API) ClientMinerQueryOffer(ctx context.Context, miner address.Address, root cid.Cid, piece *cid.Cid) (api.QueryOffer, error) { - mi, err := a.StateMinerInfo(ctx, miner, types.EmptyTSK) - if err != nil { - return api.QueryOffer{}, err - } - rp := rm.RetrievalPeer{ - Address: miner, - ID: *mi.PeerId, - } - return a.makeRetrievalQuery(ctx, rp, root, piece, rm.QueryParams{}), nil -} - -func (a *API) makeRetrievalQuery(ctx context.Context, rp rm.RetrievalPeer, payload cid.Cid, piece *cid.Cid, qp rm.QueryParams) api.QueryOffer { - queryResponse, err := a.Retrieval.Query(ctx, rp, payload, qp) - if err != nil { - return api.QueryOffer{Err: err.Error(), Miner: rp.Address, MinerPeer: rp} - } - var errStr string - switch queryResponse.Status { - case rm.QueryResponseAvailable: - errStr = "" - case rm.QueryResponseUnavailable: - errStr = fmt.Sprintf("retrieval query offer was unavailable: %s", queryResponse.Message) - case rm.QueryResponseError: - errStr = fmt.Sprintf("retrieval query offer errored: %s", queryResponse.Message) - } - - return api.QueryOffer{ - Root: payload, - Piece: piece, - Size: queryResponse.Size, - MinPrice: queryResponse.PieceRetrievalPrice(), - UnsealPrice: queryResponse.UnsealPrice, - PricePerByte: queryResponse.MinPricePerByte, - PaymentInterval: queryResponse.MaxPaymentInterval, - PaymentIntervalIncrease: queryResponse.MaxPaymentIntervalIncrease, - Miner: queryResponse.PaymentAddress, // TODO: check - MinerPeer: rp, - Err: errStr, - } -} - -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, xerrors.Errorf("failed to create import: %w", err) - } - - if ref.IsCAR { - // user gave us a CAR file, 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 root") - } - 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 = unixfs.CreateFilestore(ctx, ref.Path, carPath) - if err != nil { - return nil, xerrors.Errorf("failed to import file using unixfs: %w", err) - } - } - - if err = imgr.AddLabel(id, imports.LSource, "import"); err != nil { - return nil, err - } - 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: root, - ImportID: id, - }, nil -} - -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) -} - -// 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) - - // write payload to temp file - id, err := a.importManager().CreateImport() - if err != nil { - return cid.Undef, err - } - if err := a.importManager().AddLabel(id, imports.LSource, "import-local"); err != nil { - return cid.Undef, err - } - - path, err := a.importManager().AllocateCAR(id) - if err != nil { - return cid.Undef, err - } - - // 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. - - b, err := unixfs.CidBuilder() - if err != nil { - return cid.Undef, err - } - - // 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, xerrors.Errorf("failed to calculate placeholder root: %w", err) - } - - bs, err := carv2bs.OpenReadWrite(path, []cid.Cid{placeholderRoot}, carv2bs.UseWholeCIDs(true)) - if err != nil { - return cid.Undef, xerrors.Errorf("failed to create carv2 read/write blockstore: %w", err) - } - - root, err := unixfs.Build(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, err := reader.DataReader() - if err != nil { - return cid.Undef, fmt.Errorf("failed to get car data reader: %w", err) - } - 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(_ 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(ids)) - for i, id := range ids { - info, err := a.importManager().Info(id) - if err != nil { - out[i] = api.Import{ - Key: id, - Err: xerrors.Errorf("getting info: %w", err).Error(), - } - continue - } - - ai := api.Import{ - Key: id, - Source: info.Labels[imports.LSource], - FilePath: info.Labels[imports.LFileName], - CARPath: info.Labels[imports.LCARPath], - } - - if info.Labels[imports.LRootCid] != "" { - c, err := cid.Parse(info.Labels[imports.LRootCid]) - if err != nil { - ai.Err = err.Error() - } else { - ai.Root = &c - } - } - - out[i] = ai - } - - return out, nil -} - -func (a *API) ClientCancelRetrievalDeal(ctx context.Context, dealID rm.DealID) error { - cerr := make(chan error) - go func() { - err := a.Retrieval.CancelDeal(dealID) - - select { - case cerr <- err: - case <-ctx.Done(): - } - }() - - select { - case err := <-cerr: - if err != nil { - return xerrors.Errorf("failed to cancel retrieval deal: %w", err) - } - - return nil - case <-ctx.Done(): - return xerrors.Errorf("context timeout while canceling retrieval deal: %w", ctx.Err()) - } -} - -func getDataSelector(dps *api.Selector, matchPath bool) (datamodel.Node, error) { - sel := selectorparse.CommonSelector_ExploreAllRecursively - if dps != nil { - - if strings.HasPrefix(string(*dps), "{") { - var err error - sel, err = selectorparse.ParseJSONSelector(string(*dps)) - if err != nil { - return nil, xerrors.Errorf("failed to parse json-selector '%s': %w", *dps, err) - } - } else { - ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any) - - selspec, err := textselector.SelectorSpecFromPath( - textselector.Expression(*dps), matchPath, - - ssb.ExploreRecursive( - selector.RecursionLimitNone(), - ssb.ExploreUnion(ssb.Matcher(), ssb.ExploreAll(ssb.ExploreRecursiveEdge())), - ), - ) - if err != nil { - return nil, xerrors.Errorf("failed to parse text-selector '%s': %w", *dps, err) - } - - sel = selspec.Node() - log.Infof("partial retrieval of datamodel-path-selector %s/*", *dps) - } - } - - return sel, nil -} - -func (a *API) ClientRetrieve(ctx context.Context, params api.RetrievalOrder) (*api.RestrievalRes, error) { - sel, err := getDataSelector(params.DataSelector, false) - if err != nil { - return nil, err - } - - di, err := a.doRetrieval(ctx, params, sel) - if err != nil { - return nil, err - } - - return &api.RestrievalRes{ - DealID: di, - }, nil -} - -func (a *API) doRetrieval(ctx context.Context, order api.RetrievalOrder, sel datamodel.Node) (rm.DealID, error) { - if order.MinerPeer == nil || order.MinerPeer.ID == "" { - mi, err := a.StateMinerInfo(ctx, order.Miner, types.EmptyTSK) - if err != nil { - return 0, err - } - - order.MinerPeer = &rm.RetrievalPeer{ - ID: *mi.PeerId, - Address: order.Miner, - } - } - - if order.Total.Int == nil { - return 0, xerrors.Errorf("cannot make retrieval deal for null total") - } - - if order.Size == 0 { - return 0, xerrors.Errorf("cannot make retrieval deal for zero bytes") - } - - ppb := types.BigDiv(big.Sub(order.Total, order.UnsealPrice), types.NewInt(order.Size)) - - params, err := rm.NewParamsV1(ppb, order.PaymentInterval, order.PaymentIntervalIncrease, sel, order.Piece, order.UnsealPrice) - if err != nil { - return 0, xerrors.Errorf("Error in retrieval params: %s", err) - } - - id := a.Retrieval.NextID() - - if order.RemoteStore != nil { - if err := a.ApiBlockstoreAccessor.RegisterDealToRetrievalStore(id, *order.RemoteStore); err != nil { - return 0, xerrors.Errorf("registering api store: %w", err) - } - } - - id, err = a.Retrieval.Retrieve( - ctx, - id, - order.Root, - params, - order.Total, - *order.MinerPeer, - order.Client, - order.Miner, - ) - - if err != nil { - return 0, xerrors.Errorf("Retrieve failed: %w", err) - } - - return id, nil -} - -func (a *API) ClientRetrieveWait(ctx context.Context, deal rm.DealID) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - subscribeEvents := make(chan rm.ClientDealState, 1) - - unsubscribe := a.Retrieval.SubscribeToEvents(func(event rm.ClientEvent, state rm.ClientDealState) { - // We'll check the deal IDs inside consumeAllEvents. - if state.ID != deal { - return - } - select { - case <-ctx.Done(): - case subscribeEvents <- state: - } - }) - defer unsubscribe() - - { - state, err := a.Retrieval.GetDeal(deal) - if err != nil { - return xerrors.Errorf("getting deal state: %w", err) - } - select { - case subscribeEvents <- state: - default: // already have an event queued from the subscription - } - } - - for { - select { - case <-ctx.Done(): - return xerrors.New("Retrieval Timed Out") - case state := <-subscribeEvents: - switch state.Status { - case rm.DealStatusCompleted: - return nil - case rm.DealStatusRejected: - return xerrors.Errorf("Retrieval Proposal Rejected: %s", state.Message) - case rm.DealStatusCancelled: - return xerrors.Errorf("Retrieval was cancelled externally: %s", state.Message) - case - rm.DealStatusDealNotFound, - rm.DealStatusErrored: - return xerrors.Errorf("Retrieval Error: %s", state.Message) - } - } - } -} - -type ExportDest struct { - Writer io.Writer - Path string -} - -func (ed *ExportDest) doWrite(cb func(io.Writer) error) error { - if ed.Writer != nil { - return cb(ed.Writer) - } - - f, err := os.OpenFile(ed.Path, os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return err - } - - if err := cb(f); err != nil { - _ = f.Close() - return err - } - - return f.Close() -} - -func (a *API) ClientExport(ctx context.Context, exportRef api.ExportRef, ref api.FileRef) error { - return a.ClientExportInto(ctx, exportRef, ref.IsCAR, ExportDest{Path: ref.Path}) -} - -func (a *API) ClientExportInto(ctx context.Context, exportRef api.ExportRef, car bool, dest ExportDest) error { - proxyBss, retrieveIntoIPFS := a.RtvlBlockstoreAccessor.(*retrievaladapter.ProxyBlockstoreAccessor) - carBss, retrieveIntoCAR := a.RtvlBlockstoreAccessor.(*retrievaladapter.CARBlockstoreAccessor) - carPath := exportRef.FromLocalCAR - - if carPath == "" { - if !retrieveIntoIPFS && !retrieveIntoCAR { - return xerrors.Errorf("unsupported retrieval blockstore accessor") - } - - if retrieveIntoCAR { - carPath = carBss.PathFor(exportRef.DealID) - } - } - - var retrievalBs bstore.Blockstore - if retrieveIntoIPFS { - retrievalBs = proxyBss.Blockstore - } else { - cbs, err := stores.ReadOnlyFilestore(carPath) - if err != nil { - return err - } - defer cbs.Close() //nolint:errcheck - retrievalBs = cbs - } - - dserv := merkledag.NewDAGService(blockservice.New(retrievalBs, offline.Exchange(retrievalBs))) - - // Are we outputting a CAR? - if car { - // not IPFS and we do full selection - just extract the CARv1 from the CARv2 we stored the retrieval in - if !retrieveIntoIPFS && len(exportRef.DAGs) == 0 && dest.Writer == nil { - return carv2.ExtractV1File(carPath, dest.Path) - } - } - - roots, err := parseDagSpec(ctx, exportRef.Root, exportRef.DAGs, dserv, car) - if err != nil { - return xerrors.Errorf("parsing dag spec: %w", err) - } - if car { - return a.outputCAR(ctx, dserv, retrievalBs, exportRef.Root, roots, dest) - } - - if len(roots) != 1 { - return xerrors.Errorf("unixfs retrieval requires one root node, got %d", len(roots)) - } - - return a.outputUnixFS(ctx, roots[0].root, dserv, dest) -} - -func (a *API) outputCAR(ctx context.Context, ds format.DAGService, bs bstore.Blockstore, root cid.Cid, dags []dagSpec, dest ExportDest) error { - // generating a CARv1 from the configured blockstore - roots := make([]cid.Cid, len(dags)) - for i, dag := range dags { - roots[i] = dag.root - } - - var lk sync.Mutex - - return dest.doWrite(func(w io.Writer) error { - - if err := car.WriteHeader(&car.CarHeader{ - Roots: roots, - Version: 1, - }, w); err != nil { - return fmt.Errorf("failed to write car header: %s", err) - } - - cs := cid.NewSet() - - for _, dagSpec := range dags { - dagSpec := dagSpec - - if err := utils.TraverseDag( - ctx, - ds, - root, - dagSpec.selector, - func(node format.Node) error { - // if we're exporting merkle proofs for this dag, export all nodes read by the traversal - if dagSpec.exportAll { - lk.Lock() - defer lk.Unlock() - if cs.Visit(node.Cid()) { - err := util.LdWrite(w, node.Cid().Bytes(), node.RawData()) - if err != nil { - return xerrors.Errorf("writing block data: %w", err) - } - } - } - return nil - }, - func(p traversal.Progress, n ipld.Node, r traversal.VisitReason) error { - if !dagSpec.exportAll && r == traversal.VisitReason_SelectionMatch { - var c cid.Cid - if p.LastBlock.Link == nil { - c = root - } else { - cidLnk, castOK := p.LastBlock.Link.(cidlink.Link) - if !castOK { - return xerrors.Errorf("cidlink cast unexpectedly failed on '%s'", p.LastBlock.Link) - } - - c = cidLnk.Cid - } - - if cs.Visit(c) { - nb, err := bs.Get(ctx, c) - if err != nil { - return xerrors.Errorf("getting block data: %w", err) - } - - err = util.LdWrite(w, c.Bytes(), nb.RawData()) - if err != nil { - return xerrors.Errorf("writing block data: %w", err) - } - } - - return nil - } - return nil - }, - ); err != nil { - return xerrors.Errorf("error while traversing car dag: %w", err) - } - } - - return nil - }) -} - -func (a *API) outputUnixFS(ctx context.Context, root cid.Cid, ds format.DAGService, dest ExportDest) error { - nd, err := ds.Get(ctx, root) - if err != nil { - return xerrors.Errorf("ClientRetrieve: %w", err) - } - file, err := unixfile.NewUnixfsFile(ctx, ds, nd) - if err != nil { - return xerrors.Errorf("ClientRetrieve: %w", err) - } - - if dest.Writer == nil { - return files.WriteTo(file, dest.Path) - } - - switch f := file.(type) { - case files.File: - _, err = io.Copy(dest.Writer, f) - if err != nil { - return err - } - return nil - default: - return fmt.Errorf("file type %T is not supported", nd) - } -} - -type dagSpec struct { - root cid.Cid - selector ipld.Node - exportAll bool -} - -func parseDagSpec(ctx context.Context, root cid.Cid, dsp []api.DagSpec, ds format.DAGService, car bool) ([]dagSpec, error) { - if len(dsp) == 0 { - return []dagSpec{ - { - root: root, - selector: nil, - }, - }, nil - } - - out := make([]dagSpec, len(dsp)) - for i, spec := range dsp { - out[i].exportAll = spec.ExportMerkleProof - - if spec.DataSelector == nil { - return nil, xerrors.Errorf("invalid DagSpec at position %d: `DataSelector` can not be nil", i) - } - - // reify selector - var err error - out[i].selector, err = getDataSelector(spec.DataSelector, car && spec.ExportMerkleProof) - if err != nil { - return nil, err - } - - // find the pointed-at root node within the containing ds - var rsn ipld.Node - - if strings.HasPrefix(string(*spec.DataSelector), "{") { - var err error - rsn, err = selectorparse.ParseJSONSelector(string(*spec.DataSelector)) - if err != nil { - return nil, xerrors.Errorf("failed to parse json-selector '%s': %w", *spec.DataSelector, err) - } - } else { - selspec, _ := textselector.SelectorSpecFromPath(textselector.Expression(*spec.DataSelector), car && spec.ExportMerkleProof, nil) //nolint:errcheck - rsn = selspec.Node() - } - - var newRoot cid.Cid - var errHalt = errors.New("halt walk") - if err := utils.TraverseDag( - ctx, - ds, - root, - rsn, - nil, - func(p traversal.Progress, n ipld.Node, r traversal.VisitReason) error { - if r == traversal.VisitReason_SelectionMatch { - if !car && p.LastBlock.Path.String() != p.Path.String() { - return xerrors.Errorf("unsupported selection path '%s' does not correspond to a block boundary (a.k.a. CID link)", p.Path.String()) - } - - if p.LastBlock.Link == nil { - // this is likely the root node that we've matched here - newRoot = root - return errHalt - } - - cidLnk, castOK := p.LastBlock.Link.(cidlink.Link) - if !castOK { - return xerrors.Errorf("cidlink cast unexpectedly failed on '%s'", p.LastBlock.Link) - } - - newRoot = cidLnk.Cid - - return errHalt - } - return nil - }, - ); err != nil && err != errHalt { - return nil, xerrors.Errorf("error while locating partial retrieval sub-root: %w", err) - } - - if newRoot == cid.Undef { - return nil, xerrors.Errorf("path selection does not match a node within %s", root) - } - - out[i].root = newRoot - } - - return out, nil -} - -func (a *API) ClientListRetrievals(ctx context.Context) ([]api.RetrievalInfo, error) { - deals, err := a.Retrieval.ListDeals() - if err != nil { - return nil, err - } - dataTransfersByID, err := a.transfersByID(ctx) - if err != nil { - return nil, err - } - out := make([]api.RetrievalInfo, 0, len(deals)) - for _, v := range deals { - // Find the data transfer associated with this deal - var transferCh *api.DataTransferChannel - if v.ChannelID != nil { - if ch, ok := dataTransfersByID[*v.ChannelID]; ok { - transferCh = &ch - } - } - out = append(out, a.newRetrievalInfoWithTransfer(transferCh, v)) - } - sort.Slice(out, func(a, b int) bool { - return out[a].ID < out[b].ID - }) - return out, nil -} - -func (a *API) ClientGetRetrievalUpdates(ctx context.Context) (<-chan api.RetrievalInfo, error) { - updates := make(chan api.RetrievalInfo) - - unsub := a.Retrieval.SubscribeToEvents(func(evt rm.ClientEvent, deal rm.ClientDealState) { - update := a.newRetrievalInfo(ctx, deal) - update.Event = &evt - select { - case updates <- update: - case <-ctx.Done(): - } - }) - - go func() { - defer unsub() - <-ctx.Done() - }() - - return updates, nil -} - -func (a *API) newRetrievalInfoWithTransfer(ch *api.DataTransferChannel, deal rm.ClientDealState) api.RetrievalInfo { - return api.RetrievalInfo{ - PayloadCID: deal.PayloadCID, - ID: deal.ID, - PieceCID: deal.PieceCID, - PricePerByte: deal.PricePerByte, - UnsealPrice: deal.UnsealPrice, - Status: deal.Status, - Message: deal.Message, - Provider: deal.Sender, - BytesReceived: deal.TotalReceived, - BytesPaidFor: deal.BytesPaidFor, - TotalPaid: deal.FundsSpent, - TransferChannelID: deal.ChannelID, - DataTransfer: ch, - } -} - -func (a *API) newRetrievalInfo(ctx context.Context, v rm.ClientDealState) api.RetrievalInfo { - // Find the data transfer associated with this deal - var transferCh *api.DataTransferChannel - if v.ChannelID != nil { - state, err := a.DataTransfer.ChannelState(ctx, *v.ChannelID) - - // Note: If there was an error just ignore it, as the data transfer may - // be not found if it's no longer active - if err == nil { - ch := api.NewDataTransferChannel(a.Host.ID(), state) - ch.Stages = state.Stages() - transferCh = &ch - } - } - - return a.newRetrievalInfoWithTransfer(transferCh, v) -} - -const dealProtoPrefix = "/fil/storage/mk/" - -func (a *API) ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*api.StorageAsk, error) { - mi, err := a.StateMinerInfo(ctx, miner, types.EmptyTSK) - if err != nil { - return nil, xerrors.Errorf("failed getting miner info: %w", err) - } - - info := utils.NewStorageProviderInfo(miner, mi.Worker, mi.SectorSize, p, mi.Multiaddrs) - ask, err := a.SMDealClient.GetAsk(ctx, info) - if err != nil { - return nil, err - } - res := &api.StorageAsk{ - Response: ask, - } - - ps, err := a.Host.Peerstore().GetProtocols(p) - if err != nil { - return nil, err - } - for _, s := range ps { - if strings.HasPrefix(string(s), dealProtoPrefix) { - res.DealProtocols = append(res.DealProtocols, string(s)) - } - } - sort.Strings(res.DealProtocols) - - return res, nil -} - -func (a *API) ClientCalcCommP(ctx context.Context, inpath string) (*api.CommPRet, error) { - rdr, err := os.Open(inpath) - if err != nil { - return nil, err - } - defer rdr.Close() //nolint:errcheck - - // check that the data is a car file; if it's not, retrieval won't work - _, err = car.ReadHeader(bufio.NewReader(rdr)) - if err != nil { - return nil, xerrors.Errorf("not a car file: %w", err) - } - - if _, err := rdr.Seek(0, io.SeekStart); err != nil { - return nil, xerrors.Errorf("seek to start: %w", err) - } - - w := &writer.Writer{} - _, err = io.CopyBuffer(w, rdr, make([]byte, writer.CommPBuf)) - if err != nil { - return nil, xerrors.Errorf("copy into commp writer: %w", err) - } - - commp, err := w.Sum() - if err != nil { - return nil, xerrors.Errorf("computing commP failed: %w", err) - } - - return &api.CommPRet{ - Root: commp.PieceCID, - Size: commp.PieceSize.Unpadded(), - }, nil -} - -type lenWriter int64 - -func (w *lenWriter) Write(p []byte) (n int, err error) { - *w += lenWriter(len(p)) - return len(p), nil -} - -func (a *API) ClientDealSize(ctx context.Context, root cid.Cid) (api.DataSize, error) { - bs, onDone, err := a.dealBlockstore(root) - if err != nil { - return api.DataSize{}, err - } - defer onDone() - - dag := merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs))) - - var w lenWriter - err = car.WriteCar(ctx, dag, []cid.Cid{root}, &w) - if err != nil { - return api.DataSize{}, err - } - - up := padreader.PaddedSize(uint64(w)) - - return api.DataSize{ - PayloadSize: int64(w), - PieceSize: up.Padded(), - }, nil -} - -func (a *API) ClientDealPieceCID(ctx context.Context, root cid.Cid) (api.DataCIDSize, error) { - 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) - if err != nil { - return api.DataCIDSize{}, err - } - - if err := bw.Flush(); err != nil { - return api.DataCIDSize{}, err - } - - dataCIDSize, err := w.Sum() - return api.DataCIDSize(dataCIDSize), err -} - -func (a *API) ClientGenCar(ctx context.Context, ref api.FileRef, outputPath string) error { - // create a temporary import to represent this job and obtain a staging CAR. - id, err := a.importManager().CreateImport() - if err != nil { - return xerrors.Errorf("failed to create temporary import: %w", err) - } - defer a.importManager().Remove(id) //nolint:errcheck - - tmp, err := a.importManager().AllocateCAR(id) - if err != nil { - 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 := unixfs.CreateFilestore(ctx, ref.Path, tmp) - if err != nil { - return xerrors.Errorf("failed to import file using unixfs: %w", err) - } - - // 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 - - f, err := os.Create(outputPath) - if err != nil { - return err - } - - // build a dense deterministic CAR (dense = containing filled leaves) - if err := car.NewSelectiveCar( - ctx, - fs, - []car.Dag{{ - Root: root, - Selector: selectorparse.CommonSelector_ExploreAllRecursively, - }}, - car.MaxTraversalLinks(config.MaxTraversalLinks), - ).Write( - f, - ); err != nil { - return xerrors.Errorf("failed to write CAR to output file: %w", err) - } - - return f.Close() -} - -func (a *API) ClientListDataTransfers(ctx context.Context) ([]api.DataTransferChannel, error) { - inProgressChannels, err := a.DataTransfer.InProgressChannels(ctx) - if err != nil { - return nil, err - } - - apiChannels := make([]api.DataTransferChannel, 0, len(inProgressChannels)) - for _, channelState := range inProgressChannels { - apiChannels = append(apiChannels, api.NewDataTransferChannel(a.Host.ID(), channelState)) - } - - return apiChannels, nil -} - -func (a *API) ClientDataTransferUpdates(ctx context.Context) (<-chan api.DataTransferChannel, error) { - channels := make(chan api.DataTransferChannel) - - unsub := a.DataTransfer.SubscribeToEvents(func(evt datatransfer.Event, channelState datatransfer.ChannelState) { - channel := api.NewDataTransferChannel(a.Host.ID(), channelState) - select { - case <-ctx.Done(): - case channels <- channel: - } - }) - - go func() { - defer unsub() - <-ctx.Done() - }() - - return channels, nil -} - -func (a *API) ClientRestartDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error { - selfPeer := a.Host.ID() - if isInitiator { - return a.DataTransfer.RestartDataTransferChannel(ctx, datatransfer.ChannelID{Initiator: selfPeer, Responder: otherPeer, ID: transferID}) - } - return a.DataTransfer.RestartDataTransferChannel(ctx, datatransfer.ChannelID{Initiator: otherPeer, Responder: selfPeer, ID: transferID}) -} - -func (a *API) ClientCancelDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error { - selfPeer := a.Host.ID() - if isInitiator { - return a.DataTransfer.CloseDataTransferChannel(ctx, datatransfer.ChannelID{Initiator: selfPeer, Responder: otherPeer, ID: transferID}) - } - return a.DataTransfer.CloseDataTransferChannel(ctx, datatransfer.ChannelID{Initiator: otherPeer, Responder: selfPeer, ID: transferID}) -} - -func (a *API) ClientRetrieveTryRestartInsufficientFunds(ctx context.Context, paymentChannel address.Address) error { - return a.Retrieval.TryRestartInsufficientFunds(paymentChannel) -} - -func (a *API) ClientGetDealStatus(ctx context.Context, statusCode uint64) (string, error) { - ststr, ok := storagemarket.DealStates[statusCode] - if !ok { - return "", fmt.Errorf("no such deal state %d", statusCode) - } - - 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 deleted file mode 100644 index 67a350131..000000000 --- a/node/impl/client/client_test.go +++ /dev/null @@ -1,136 +0,0 @@ -// stm: #unit -package client - -import ( - "bytes" - "context" - "embed" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/ipfs/boxo/blockservice" - blockstore "github.com/ipfs/boxo/blockstore" - offline "github.com/ipfs/boxo/exchange/offline" - "github.com/ipfs/boxo/files" - "github.com/ipfs/boxo/ipld/merkledag" - unixfile "github.com/ipfs/boxo/ipld/unixfs/file" - "github.com/ipfs/go-cid" - "github.com/ipfs/go-datastore" - dssync "github.com/ipfs/go-datastore/sync" - "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) { - // stm: @CLIENT_STORAGE_DEALS_IMPORT_LOCAL_001, @CLIENT_RETRIEVAL_FIND_001 - 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) - - // stm: @CLIENT_STORAGE_DEALS_LIST_IMPORTS_001 - 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)) - - // stm: @CLIENT_DATA_HAS_LOCAL_001 - local, err := a.ClientHasLocal(ctx, root) - require.NoError(t, err) - require.True(t, local) - - order := api.ExportRef{ - 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.ClientExport(ctx, order, api.FileRef{ - Path: out1, - }) - require.NoError(t, err) - - outBytes, err := os.ReadFile(out1) - require.NoError(t, err) - require.Equal(t, b, outBytes) - - err = a.ClientExport(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()) - r, err := exported.DataReader() - require.NoError(t, err) - _, err = car.LoadCar(ctx, bs, r) - 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 := os.ReadFile(exportedPath) - require.NoError(t, err) - - // compare original file to recreated unixfs file. - require.Equal(t, b, exportedBytes) -} diff --git a/node/impl/client/testdata/duplicate_blocks.txt b/node/impl/client/testdata/duplicate_blocks.txt deleted file mode 100644 index 53695d7b9..000000000 --- a/node/impl/client/testdata/duplicate_blocks.txt +++ /dev/null @@ -1 +0,0 @@ -aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd \ No newline at end of file diff --git a/node/impl/client/testdata/payload.txt b/node/impl/client/testdata/payload.txt deleted file mode 100644 index fd4a2f3c1..000000000 --- a/node/impl/client/testdata/payload.txt +++ /dev/null @@ -1,49 +0,0 @@ -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 deleted file mode 100644 index 16fb150f5..000000000 --- a/node/impl/client/testdata/payload2.txt +++ /dev/null @@ -1,49 +0,0 @@ -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/common/common.go b/node/impl/common/common.go index eff6b58b8..5c4dd6a7f 100644 --- a/node/impl/common/common.go +++ b/node/impl/common/common.go @@ -24,6 +24,8 @@ var session = uuid.New() type CommonAPI struct { fx.In + BuildVersion build.BuildVersion + Alerting *alerting.Alerting APISecret *dtypes.APIAlg ShutdownChan dtypes.ShutdownChan @@ -63,7 +65,7 @@ func (a *CommonAPI) Version(context.Context) (api.APIVersion, error) { } return api.APIVersion{ - Version: build.UserVersion(), + Version: string(a.BuildVersion), APIVersion: v, BlockDelay: build.BlockDelaySecs, diff --git a/node/impl/full.go b/node/impl/full.go index 527a55384..aef7a75cb 100644 --- a/node/impl/full.go +++ b/node/impl/full.go @@ -9,7 +9,6 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/node/impl/client" "github.com/filecoin-project/lotus/node/impl/common" "github.com/filecoin-project/lotus/node/impl/full" "github.com/filecoin-project/lotus/node/impl/market" @@ -25,7 +24,6 @@ type FullNodeAPI struct { common.CommonAPI net.NetAPI full.ChainAPI - client.API full.MpoolAPI full.GasAPI market.MarketAPI diff --git a/node/impl/full/dummy.go b/node/impl/full/dummy.go index 1c191afba..867d5f5a4 100644 --- a/node/impl/full/dummy.go +++ b/node/impl/full/dummy.go @@ -187,6 +187,10 @@ func (e *EthModuleDummy) EthTraceReplayBlockTransactions(ctx context.Context, bl return nil, ErrModuleDisabled } +func (e *EthModuleDummy) EthTraceTransaction(ctx context.Context, txHash string) ([]*ethtypes.EthTraceTransaction, error) { + return nil, ErrModuleDisabled +} + var _ EthModuleAPI = &EthModuleDummy{} var _ EthEventAPI = &EthModuleDummy{} diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index e7aeafa90..3b6bb408f 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -75,6 +75,7 @@ type EthModuleAPI interface { Web3ClientVersion(ctx context.Context) (string, error) EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error) EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) + EthTraceTransaction(ctx context.Context, txHash string) ([]*ethtypes.EthTraceTransaction, error) } type EthEventAPI interface { @@ -294,14 +295,16 @@ func (a *EthModule) EthGetTransactionByHashLimited(ctx context.Context, txHash * // This should be "fine" as anyone using an "Ethereum-centric" block // explorer shouldn't care about seeing pending messages from native // accounts. - tx, err := ethtypes.EthTxFromSignedEthMessage(p) + ethtx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(p) if err != nil { return nil, fmt.Errorf("could not convert Filecoin message into tx: %w", err) } - tx.Hash, err = tx.TxHash() + + tx, err := ethtx.ToEthTx(p) if err != nil { - return nil, fmt.Errorf("could not compute tx hash for eth txn: %w", err) + return nil, fmt.Errorf("could not convert Eth transaction to EthTx: %w", err) } + return &tx, nil } } @@ -817,12 +820,12 @@ func (a *EthModule) EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) } func (a *EthModule) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) { - txArgs, err := ethtypes.ParseEthTxArgs(rawTx) + txArgs, err := ethtypes.ParseEthTransaction(rawTx) if err != nil { return ethtypes.EmptyEthHash, err } - smsg, err := txArgs.ToSignedMessage() + smsg, err := ethtypes.ToSignedFilecoinMessage(txArgs) if err != nil { return ethtypes.EmptyEthHash, err } @@ -836,7 +839,7 @@ func (a *EthModule) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.Et } func (a *EthModule) Web3ClientVersion(ctx context.Context) (string, error) { - return build.UserVersion(), nil + return string(build.NodeUserVersion()), nil } func (a *EthModule) EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error) { @@ -974,6 +977,51 @@ func (a *EthModule) EthTraceReplayBlockTransactions(ctx context.Context, blkNum return allTraces, nil } +func (a *EthModule) EthTraceTransaction(ctx context.Context, txHash string) ([]*ethtypes.EthTraceTransaction, error) { + + // convert from string to internal type + ethTxHash, err := ethtypes.ParseEthHash(txHash) + if err != nil { + return nil, xerrors.Errorf("cannot parse eth hash: %w", err) + } + + tx, err := a.EthGetTransactionByHash(ctx, ðTxHash) + if err != nil { + return nil, xerrors.Errorf("cannot get transaction by hash: %w", err) + } + + if tx == nil { + return nil, xerrors.Errorf("transaction not found") + } + + // tx.BlockNumber is nil when the transaction is still in the mpool/pending + if tx.BlockNumber == nil { + return nil, xerrors.Errorf("no trace for pending transactions") + } + + blockTraces, err := a.EthTraceBlock(ctx, strconv.FormatUint(uint64(*tx.BlockNumber), 10)) + if err != nil { + return nil, xerrors.Errorf("cannot get trace for block: %w", err) + } + + txTraces := make([]*ethtypes.EthTraceTransaction, 0, len(blockTraces)) + for _, blockTrace := range blockTraces { + if blockTrace.TransactionHash == ethTxHash { + // Create a new EthTraceTransaction from the block trace + txTrace := ethtypes.EthTraceTransaction{ + EthTrace: blockTrace.EthTrace, + BlockHash: blockTrace.BlockHash, + BlockNumber: blockTrace.BlockNumber, + TransactionHash: blockTrace.TransactionHash, + TransactionPosition: blockTrace.TransactionPosition, + } + txTraces = append(txTraces, &txTrace) + } + } + + return txTraces, nil +} + func (a *EthModule) applyMessage(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (res *api.InvocResult, err error) { ts, err := a.Chain.GetTipSetFromKey(ctx, tsk) if err != nil { diff --git a/node/impl/full/eth_events.go b/node/impl/full/eth_events.go index 7baba1e81..b82fe264f 100644 --- a/node/impl/full/eth_events.go +++ b/node/impl/full/eth_events.go @@ -266,7 +266,7 @@ func (e *ethSubscription) addFilter(ctx context.Context, f filter.Filter) { e.filters = append(e.filters, f) } -// sendOut processes the final subscription queue. It's here in case the subscriber +// startOut processes the final subscription queue. It's here in case the subscriber // is slow, and we need to buffer the messages. func (e *ethSubscription) startOut(ctx context.Context) { for { diff --git a/node/impl/full/eth_utils.go b/node/impl/full/eth_utils.go index a4b1c66bb..56cc1e094 100644 --- a/node/impl/full/eth_utils.go +++ b/node/impl/full/eth_utils.go @@ -250,7 +250,6 @@ func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTx return ethtypes.EthBlock{}, xerrors.Errorf("failed to convert msg to ethTx: %w", err) } - tx.ChainID = ethtypes.EthUint64(build.Eip155ChainId) tx.BlockHash = &blkHash tx.BlockNumber = &bn tx.TransactionIndex = &ti @@ -449,7 +448,7 @@ func ethTxHashFromMessageCid(ctx context.Context, c cid.Cid, sa StateAPI) (ethty func ethTxHashFromSignedMessage(smsg *types.SignedMessage) (ethtypes.EthHash, error) { if smsg.Signature.Type == crypto.SigTypeDelegated { - tx, err := ethtypes.EthTxFromSignedEthMessage(smsg) + tx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(smsg) if err != nil { return ethtypes.EthHash{}, xerrors.Errorf("failed to convert from signed message: %w", err) } @@ -468,14 +467,13 @@ func newEthTxFromSignedMessage(smsg *types.SignedMessage, st *state.StateTree) ( // This is an eth tx if smsg.Signature.Type == crypto.SigTypeDelegated { - tx, err = ethtypes.EthTxFromSignedEthMessage(smsg) + ethTx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(smsg) if err != nil { return ethtypes.EthTx{}, xerrors.Errorf("failed to convert from signed message: %w", err) } - - tx.Hash, err = tx.TxHash() + tx, err = ethTx.ToEthTx(smsg) if err != nil { - return ethtypes.EthTx{}, xerrors.Errorf("failed to calculate hash for ethTx: %w", err) + return ethtypes.EthTx{}, xerrors.Errorf("failed to convert from signed message: %w", err) } } else if smsg.Signature.Type == crypto.SigTypeSecp256k1 { // Secp Filecoin Message tx, err = ethTxFromNativeMessage(smsg.VMMessage(), st) @@ -535,6 +533,9 @@ func ethTxFromNativeMessage(msg *types.Message, st *state.StateTree) (ethtypes.E codec = uint64(multicodec.Cbor) } + maxFeePerGas := ethtypes.EthBigInt(msg.GasFeeCap) + maxPriorityFeePerGas := ethtypes.EthBigInt(msg.GasPremium) + // We decode as a native call first. ethTx := ethtypes.EthTx{ To: &to, @@ -543,10 +544,10 @@ func ethTxFromNativeMessage(msg *types.Message, st *state.StateTree) (ethtypes.E Nonce: ethtypes.EthUint64(msg.Nonce), ChainID: ethtypes.EthUint64(build.Eip155ChainId), Value: ethtypes.EthBigInt(msg.Value), - Type: ethtypes.Eip1559TxType, + Type: ethtypes.EIP1559TxType, Gas: ethtypes.EthUint64(msg.GasLimit), - MaxFeePerGas: ethtypes.EthBigInt(msg.GasFeeCap), - MaxPriorityFeePerGas: ethtypes.EthBigInt(msg.GasPremium), + MaxFeePerGas: &maxFeePerGas, + MaxPriorityFeePerGas: &maxPriorityFeePerGas, AccessList: []ethtypes.EthHash{}, } @@ -653,6 +654,7 @@ func newEthTxFromMessageLookup(ctx context.Context, msgLookup *api.MsgLookup, tx tx.BlockHash = &blkHash tx.BlockNumber = &bn tx.TransactionIndex = &ti + return tx, nil } @@ -714,7 +716,18 @@ func newEthTxReceipt(ctx context.Context, tx ethtypes.EthTx, lookup *api.MsgLook } baseFee := parentTs.Blocks()[0].ParentBaseFee - gasOutputs := vm.ComputeGasOutputs(lookup.Receipt.GasUsed, int64(tx.Gas), baseFee, big.Int(tx.MaxFeePerGas), big.Int(tx.MaxPriorityFeePerGas), true) + + gasFeeCap, err := tx.GasFeeCap() + if err != nil { + return api.EthTxReceipt{}, xerrors.Errorf("failed to get gas fee cap: %w", err) + } + gasPremium, err := tx.GasPremium() + if err != nil { + return api.EthTxReceipt{}, xerrors.Errorf("failed to get gas premium: %w", err) + } + + gasOutputs := vm.ComputeGasOutputs(lookup.Receipt.GasUsed, int64(tx.Gas), baseFee, big.Int(gasFeeCap), + big.Int(gasPremium), true) totalSpent := big.Sum(gasOutputs.BaseFeeBurn, gasOutputs.MinerTip, gasOutputs.OverEstimationBurn) effectiveGasPrice := big.Zero() diff --git a/node/impl/full/state.go b/node/impl/full/state.go index 6a4f6d553..49e978055 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -1624,7 +1624,7 @@ func (a *StateAPI) StateMinerSectorAllocated(ctx context.Context, maddr address. return mas.IsAllocated(s) } -// StateVerifiedClientStatus returns the data cap for the given address. +// StateVerifierStatus returns the data cap for the given address. // Returns zero if there is no entry in the data cap table for the // address. func (a *StateAPI) StateVerifierStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) { @@ -1962,6 +1962,7 @@ func (a *StateAPI) StateGetNetworkParams(ctx context.Context) (*api.NetworkParam UpgradeWatermelonHeight: build.UpgradeWatermelonHeight, UpgradeDragonHeight: build.UpgradeDragonHeight, UpgradePhoenixHeight: build.UpgradePhoenixHeight, + UpgradeAussieHeight: build.UpgradeAussieHeight, }, }, nil } diff --git a/node/impl/full/txhashmanager.go b/node/impl/full/txhashmanager.go index 64c488d37..ba5de0fcb 100644 --- a/node/impl/full/txhashmanager.go +++ b/node/impl/full/txhashmanager.go @@ -85,11 +85,12 @@ func (m *EthTxHashManager) ProcessSignedMessage(ctx context.Context, msg *types. return } - ethTx, err := ethtypes.EthTxFromSignedEthMessage(msg) + ethTx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(msg) if err != nil { log.Errorf("error converting filecoin message to eth tx: %s", err) return } + txHash, err := ethTx.TxHash() if err != nil { log.Errorf("error hashing transaction: %s", err) diff --git a/node/impl/storminer.go b/node/impl/storminer.go index bd4824940..911d772d8 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -3,34 +3,18 @@ package impl import ( "context" "encoding/json" - "errors" "fmt" "net/http" - "os" - "sort" "strconv" "time" "github.com/google/uuid" "github.com/ipfs/go-cid" - "github.com/ipfs/go-graphsync" - gsimpl "github.com/ipfs/go-graphsync/impl" - "github.com/ipfs/go-graphsync/peerstate" - "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/peer" "go.uber.org/fx" "golang.org/x/xerrors" - "github.com/filecoin-project/dagstore" - "github.com/filecoin-project/dagstore/shard" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - gst "github.com/filecoin-project/go-data-transfer/v2/transport/graphsync" - "github.com/filecoin-project/go-fil-markets/piecestore" - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - "github.com/filecoin-project/go-fil-markets/storagemarket" - filmktsstore "github.com/filecoin-project/go-fil-markets/stores" "github.com/filecoin-project/go-jsonrpc/auth" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" @@ -46,8 +30,6 @@ import ( "github.com/filecoin-project/lotus/chain/gen" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/lib/harmony/harmonydb" - mktsdagstore "github.com/filecoin-project/lotus/markets/dagstore" - "github.com/filecoin-project/lotus/markets/storageadapter" "github.com/filecoin-project/lotus/miner" "github.com/filecoin-project/lotus/node/modules" "github.com/filecoin-project/lotus/node/modules/dtypes" @@ -76,18 +58,7 @@ type StorageMinerAPI struct { RemoteStore *paths.Remote // Markets - 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"` - StagingGraphsync dtypes.StagingGraphsync `optional:"true"` - Transport dtypes.ProviderTransport `optional:"true"` - DealPublisher *storageadapter.DealPublisher `optional:"true"` - SectorBlocks *sectorblocks.SectorBlocks `optional:"true"` - Host host.Host `optional:"true"` - DAGStore *dagstore.DAGStore `optional:"true"` - DAGStoreWrapper *mktsdagstore.Wrapper `optional:"true"` + SectorBlocks *sectorblocks.SectorBlocks `optional:"true"` // Miner / storage Miner *sealing.Sealing `optional:"true"` @@ -106,24 +77,10 @@ type StorageMinerAPI struct { // StorageService is populated when we're not the main storage node (e.g. we're a markets node) StorageService modules.MinerStorageService `optional:"true"` - ConsiderOnlineStorageDealsConfigFunc dtypes.ConsiderOnlineStorageDealsConfigFunc `optional:"true"` - SetConsiderOnlineStorageDealsConfigFunc dtypes.SetConsiderOnlineStorageDealsConfigFunc `optional:"true"` - ConsiderOnlineRetrievalDealsConfigFunc dtypes.ConsiderOnlineRetrievalDealsConfigFunc `optional:"true"` - SetConsiderOnlineRetrievalDealsConfigFunc dtypes.SetConsiderOnlineRetrievalDealsConfigFunc `optional:"true"` - StorageDealPieceCidBlocklistConfigFunc dtypes.StorageDealPieceCidBlocklistConfigFunc `optional:"true"` - SetStorageDealPieceCidBlocklistConfigFunc dtypes.SetStorageDealPieceCidBlocklistConfigFunc `optional:"true"` - ConsiderOfflineStorageDealsConfigFunc dtypes.ConsiderOfflineStorageDealsConfigFunc `optional:"true"` - SetConsiderOfflineStorageDealsConfigFunc dtypes.SetConsiderOfflineStorageDealsConfigFunc `optional:"true"` - ConsiderOfflineRetrievalDealsConfigFunc dtypes.ConsiderOfflineRetrievalDealsConfigFunc `optional:"true"` - SetConsiderOfflineRetrievalDealsConfigFunc dtypes.SetConsiderOfflineRetrievalDealsConfigFunc `optional:"true"` - ConsiderVerifiedStorageDealsConfigFunc dtypes.ConsiderVerifiedStorageDealsConfigFunc `optional:"true"` - SetConsiderVerifiedStorageDealsConfigFunc dtypes.SetConsiderVerifiedStorageDealsConfigFunc `optional:"true"` - ConsiderUnverifiedStorageDealsConfigFunc dtypes.ConsiderUnverifiedStorageDealsConfigFunc `optional:"true"` - SetConsiderUnverifiedStorageDealsConfigFunc dtypes.SetConsiderUnverifiedStorageDealsConfigFunc `optional:"true"` - SetSealingConfigFunc dtypes.SetSealingConfigFunc `optional:"true"` - GetSealingConfigFunc dtypes.GetSealingConfigFunc `optional:"true"` - GetExpectedSealDurationFunc dtypes.GetExpectedSealDurationFunc `optional:"true"` - SetExpectedSealDurationFunc dtypes.SetExpectedSealDurationFunc `optional:"true"` + SetSealingConfigFunc dtypes.SetSealingConfigFunc `optional:"true"` + GetSealingConfigFunc dtypes.GetSealingConfigFunc `optional:"true"` + GetExpectedSealDurationFunc dtypes.GetExpectedSealDurationFunc `optional:"true"` + SetExpectedSealDurationFunc dtypes.SetExpectedSealDurationFunc `optional:"true"` HarmonyDB *harmonydb.DB `optional:"true"` } @@ -533,16 +490,6 @@ func (sm *StorageMinerAPI) SealingRemoveRequest(ctx context.Context, schedId uui return sm.StorageMgr.RemoveSchedRequest(ctx, schedId) } -func (sm *StorageMinerAPI) MarketImportDealData(ctx context.Context, propCid cid.Cid, path string) error { - fi, err := os.Open(path) - if err != nil { - return xerrors.Errorf("failed to open file: %w", err) - } - defer fi.Close() //nolint:errcheck - - return sm.StorageProvider.ImportDataForDeal(ctx, propCid, fi) -} - func (sm *StorageMinerAPI) listDeals(ctx context.Context) ([]*api.MarketDeal, error) { ts, err := sm.Full.ChainHead(ctx) if err != nil { @@ -569,671 +516,6 @@ func (sm *StorageMinerAPI) MarketListDeals(ctx context.Context) ([]*api.MarketDe return sm.listDeals(ctx) } -func (sm *StorageMinerAPI) MarketListRetrievalDeals(ctx context.Context) ([]struct{}, error) { - return []struct{}{}, nil -} - -func (sm *StorageMinerAPI) MarketGetDealUpdates(ctx context.Context) (<-chan storagemarket.MinerDeal, error) { - results := make(chan storagemarket.MinerDeal) - unsub := sm.StorageProvider.SubscribeToEvents(func(evt storagemarket.ProviderEvent, deal storagemarket.MinerDeal) { - select { - case results <- deal: - case <-ctx.Done(): - } - }) - go func() { - <-ctx.Done() - unsub() - close(results) - }() - return results, nil -} - -func (sm *StorageMinerAPI) MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error) { - return sm.StorageProvider.ListLocalDeals() -} - -func (sm *StorageMinerAPI) MarketSetAsk(ctx context.Context, price types.BigInt, verifiedPrice types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error { - options := []storagemarket.StorageAskOption{ - storagemarket.MinPieceSize(minPieceSize), - storagemarket.MaxPieceSize(maxPieceSize), - } - - return sm.StorageProvider.SetAsk(price, verifiedPrice, duration, options...) -} - -func (sm *StorageMinerAPI) MarketGetAsk(ctx context.Context) (*storagemarket.SignedStorageAsk, error) { - return sm.StorageProvider.GetAsk(), nil -} - -func (sm *StorageMinerAPI) MarketSetRetrievalAsk(ctx context.Context, rask *retrievalmarket.Ask) error { - sm.RetrievalProvider.SetAsk(rask) - return nil -} - -func (sm *StorageMinerAPI) MarketGetRetrievalAsk(ctx context.Context) (*retrievalmarket.Ask, error) { - return sm.RetrievalProvider.GetAsk(), nil -} - -func (sm *StorageMinerAPI) MarketListDataTransfers(ctx context.Context) ([]api.DataTransferChannel, error) { - inProgressChannels, err := sm.DataTransfer.InProgressChannels(ctx) - if err != nil { - return nil, err - } - - apiChannels := make([]api.DataTransferChannel, 0, len(inProgressChannels)) - for _, channelState := range inProgressChannels { - apiChannels = append(apiChannels, api.NewDataTransferChannel(sm.Host.ID(), channelState)) - } - - return apiChannels, nil -} - -func (sm *StorageMinerAPI) MarketRestartDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error { - selfPeer := sm.Host.ID() - if isInitiator { - return sm.DataTransfer.RestartDataTransferChannel(ctx, datatransfer.ChannelID{Initiator: selfPeer, Responder: otherPeer, ID: transferID}) - } - return sm.DataTransfer.RestartDataTransferChannel(ctx, datatransfer.ChannelID{Initiator: otherPeer, Responder: selfPeer, ID: transferID}) -} - -func (sm *StorageMinerAPI) MarketCancelDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error { - selfPeer := sm.Host.ID() - if isInitiator { - return sm.DataTransfer.CloseDataTransferChannel(ctx, datatransfer.ChannelID{Initiator: selfPeer, Responder: otherPeer, ID: transferID}) - } - return sm.DataTransfer.CloseDataTransferChannel(ctx, datatransfer.ChannelID{Initiator: otherPeer, Responder: selfPeer, ID: transferID}) -} - -func (sm *StorageMinerAPI) MarketDataTransferUpdates(ctx context.Context) (<-chan api.DataTransferChannel, error) { - channels := make(chan api.DataTransferChannel) - - unsub := sm.DataTransfer.SubscribeToEvents(func(evt datatransfer.Event, channelState datatransfer.ChannelState) { - channel := api.NewDataTransferChannel(sm.Host.ID(), channelState) - select { - case <-ctx.Done(): - case channels <- channel: - } - }) - - go func() { - defer unsub() - <-ctx.Done() - }() - - return channels, nil -} - -func (sm *StorageMinerAPI) MarketDataTransferDiagnostics(ctx context.Context, mpid peer.ID) (*api.TransferDiagnostics, error) { - gsTransport, ok := sm.Transport.(*gst.Transport) - if !ok { - return nil, errors.New("api only works for graphsync as transport") - } - graphsyncConcrete, ok := sm.StagingGraphsync.(*gsimpl.GraphSync) - if !ok { - return nil, errors.New("api only works for non-mock graphsync implementation") - } - - inProgressChannels, err := sm.DataTransfer.InProgressChannels(ctx) - if err != nil { - return nil, err - } - - allReceivingChannels := make(map[datatransfer.ChannelID]datatransfer.ChannelState) - allSendingChannels := make(map[datatransfer.ChannelID]datatransfer.ChannelState) - for channelID, channel := range inProgressChannels { - if channel.OtherPeer() != mpid { - continue - } - if channel.Status() == datatransfer.Completed { - continue - } - if channel.Status() == datatransfer.Failed || channel.Status() == datatransfer.Cancelled { - continue - } - if channel.SelfPeer() == channel.Sender() { - allSendingChannels[channelID] = channel - } else { - allReceivingChannels[channelID] = channel - } - } - - // gather information about active transport channels - transportChannels := gsTransport.ChannelsForPeer(mpid) - // gather information about graphsync state for peer - gsPeerState := graphsyncConcrete.PeerState(mpid) - - sendingTransfers := sm.generateTransfers(ctx, transportChannels.SendingChannels, gsPeerState.IncomingState, allSendingChannels) - receivingTransfers := sm.generateTransfers(ctx, transportChannels.ReceivingChannels, gsPeerState.OutgoingState, allReceivingChannels) - - return &api.TransferDiagnostics{ - SendingTransfers: sendingTransfers, - ReceivingTransfers: receivingTransfers, - }, nil -} - -// generate transfers matches graphsync state and data transfer state for a given peer -// to produce detailed output on what's happening with a transfer -func (sm *StorageMinerAPI) generateTransfers(ctx context.Context, - transportChannels map[datatransfer.ChannelID]gst.ChannelGraphsyncRequests, - gsPeerState peerstate.PeerState, - allChannels map[datatransfer.ChannelID]datatransfer.ChannelState) []*api.GraphSyncDataTransfer { - tc := &transferConverter{ - matchedChannelIds: make(map[datatransfer.ChannelID]struct{}), - matchedRequests: make(map[graphsync.RequestID]*api.GraphSyncDataTransfer), - gsDiagnostics: gsPeerState.Diagnostics(), - requestStates: gsPeerState.RequestStates, - allChannels: allChannels, - } - - // iterate through all operating data transfer transport channels - for channelID, channelRequests := range transportChannels { - originalState, err := sm.DataTransfer.ChannelState(ctx, channelID) - var baseDiagnostics []string - var channelState *api.DataTransferChannel - if err != nil { - baseDiagnostics = append(baseDiagnostics, fmt.Sprintf("Unable to lookup channel state: %s", err)) - } else { - cs := api.NewDataTransferChannel(sm.Host.ID(), originalState) - channelState = &cs - } - // add the current request for this channel - tc.convertTransfer(channelID, true, channelState, baseDiagnostics, channelRequests.Current, true) - for _, requestID := range channelRequests.Previous { - // add any previous requests that were cancelled for a restart - tc.convertTransfer(channelID, true, channelState, baseDiagnostics, requestID, false) - } - } - - // collect any graphsync data for channels we don't have any data transfer data for - tc.collectRemainingTransfers() - - return tc.transfers -} - -type transferConverter struct { - matchedChannelIds map[datatransfer.ChannelID]struct{} - matchedRequests map[graphsync.RequestID]*api.GraphSyncDataTransfer - transfers []*api.GraphSyncDataTransfer - gsDiagnostics map[graphsync.RequestID][]string - requestStates graphsync.RequestStates - allChannels map[datatransfer.ChannelID]datatransfer.ChannelState -} - -// convert transfer assembles transfer and diagnostic data for a given graphsync/data-transfer request -func (tc *transferConverter) convertTransfer(channelID datatransfer.ChannelID, hasChannelID bool, channelState *api.DataTransferChannel, baseDiagnostics []string, - requestID graphsync.RequestID, isCurrentChannelRequest bool) { - diagnostics := baseDiagnostics - state, hasState := tc.requestStates[requestID] - stateString := state.String() - if !hasState { - stateString = "no graphsync state found" - } - var channelIDPtr *datatransfer.ChannelID - if !hasChannelID { - diagnostics = append(diagnostics, fmt.Sprintf("No data transfer channel id for GraphSync request ID %s", requestID)) - } else { - channelIDPtr = &channelID - if isCurrentChannelRequest && !hasState { - diagnostics = append(diagnostics, fmt.Sprintf("No current request state for data transfer channel id %s", channelID)) - } else if !isCurrentChannelRequest && hasState { - diagnostics = append(diagnostics, fmt.Sprintf("Graphsync request %s is a previous request on data transfer channel id %s that was restarted, but it is still running", requestID, channelID)) - } - } - diagnostics = append(diagnostics, tc.gsDiagnostics[requestID]...) - transfer := &api.GraphSyncDataTransfer{ - RequestID: &requestID, - RequestState: stateString, - IsCurrentChannelRequest: isCurrentChannelRequest, - ChannelID: channelIDPtr, - ChannelState: channelState, - Diagnostics: diagnostics, - } - tc.transfers = append(tc.transfers, transfer) - tc.matchedRequests[requestID] = transfer - if hasChannelID { - tc.matchedChannelIds[channelID] = struct{}{} - } -} - -func (tc *transferConverter) collectRemainingTransfers() { - for requestID := range tc.requestStates { - if _, ok := tc.matchedRequests[requestID]; !ok { - tc.convertTransfer(datatransfer.ChannelID{}, false, nil, nil, requestID, false) - } - } - for requestID := range tc.gsDiagnostics { - if _, ok := tc.matchedRequests[requestID]; !ok { - tc.convertTransfer(datatransfer.ChannelID{}, false, nil, nil, requestID, false) - } - } - for channelID, channelState := range tc.allChannels { - if _, ok := tc.matchedChannelIds[channelID]; !ok { - channelID := channelID - cs := api.NewDataTransferChannel(channelState.SelfPeer(), channelState) - transfer := &api.GraphSyncDataTransfer{ - RequestID: nil, - RequestState: "graphsync state unknown", - IsCurrentChannelRequest: false, - ChannelID: &channelID, - ChannelState: &cs, - Diagnostics: []string{"data transfer with no open transport channel, cannot determine linked graphsync request"}, - } - tc.transfers = append(tc.transfers, transfer) - } - } -} - -func (sm *StorageMinerAPI) MarketPendingDeals(ctx context.Context) (api.PendingDealInfo, error) { - return sm.DealPublisher.PendingDeals(), nil -} - -func (sm *StorageMinerAPI) MarketRetryPublishDeal(ctx context.Context, propcid cid.Cid) error { - return sm.StorageProvider.RetryDealPublishing(propcid) -} - -func (sm *StorageMinerAPI) MarketPublishPendingDeals(ctx context.Context) error { - sm.DealPublisher.ForcePublishPendingDeals() - 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) DagstoreRegisterShard(ctx context.Context, key string) error { - if sm.DAGStore == nil { - return fmt.Errorf("dagstore not available on this node") - } - - // First check if the shard has already been registered - k := shard.KeyFromString(key) - _, err := sm.DAGStore.GetShardInfo(k) - if err == nil { - // Shard already registered, nothing further to do - return nil - } - // If the shard is not registered we would expect ErrShardUnknown - if !errors.Is(err, dagstore.ErrShardUnknown) { - return fmt.Errorf("getting shard info from DAG store: %w", err) - } - - pieceCid, err := cid.Parse(key) - if err != nil { - return fmt.Errorf("parsing shard key as piece cid: %w", err) - } - - if err = filmktsstore.RegisterShardSync(ctx, sm.DAGStoreWrapper, pieceCid, "", true); err != nil { - return fmt.Errorf("failed to register shard: %w", err) - } - - return 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 { - if throttle != nil { - 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) - - if throttle != nil { - 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) IndexerAnnounceDeal(ctx context.Context, proposalCid cid.Cid) error { - return sm.StorageProvider.AnnounceDealToIndexer(ctx, proposalCid) -} - -func (sm *StorageMinerAPI) IndexerAnnounceAllDeals(ctx context.Context) error { - return sm.StorageProvider.AnnounceAllDealsToIndexer(ctx) -} - -func (sm *StorageMinerAPI) DagstoreLookupPieces(ctx context.Context, cid cid.Cid) ([]api.DagstoreShardInfo, error) { - if sm.DAGStore == nil { - return nil, fmt.Errorf("dagstore not available on this node") - } - - keys, err := sm.DAGStore.TopLevelIndex.GetShardsForMultihash(ctx, cid.Hash()) - if err != nil { - return nil, err - } - - var ret []api.DagstoreShardInfo - - for _, k := range keys { - shard, err := sm.DAGStore.GetShardInfo(k) - if err != nil { - return nil, err - } - - ret = append(ret, api.DagstoreShardInfo{ - Key: k.String(), - State: shard.ShardState.String(), - Error: func() string { - if shard.Error == nil { - return "" - } - return shard.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) DealsList(ctx context.Context) ([]*api.MarketDeal, error) { - return sm.listDeals(ctx) -} - -func (sm *StorageMinerAPI) RetrievalDealsList(ctx context.Context) (map[retrievalmarket.ProviderDealIdentifier]retrievalmarket.ProviderDealState, error) { - return sm.RetrievalProvider.ListDeals(), nil -} - -func (sm *StorageMinerAPI) DealsConsiderOnlineStorageDeals(ctx context.Context) (bool, error) { - return sm.ConsiderOnlineStorageDealsConfigFunc() -} - -func (sm *StorageMinerAPI) DealsSetConsiderOnlineStorageDeals(ctx context.Context, b bool) error { - return sm.SetConsiderOnlineStorageDealsConfigFunc(b) -} - -func (sm *StorageMinerAPI) DealsConsiderOnlineRetrievalDeals(ctx context.Context) (bool, error) { - return sm.ConsiderOnlineRetrievalDealsConfigFunc() -} - -func (sm *StorageMinerAPI) DealsSetConsiderOnlineRetrievalDeals(ctx context.Context, b bool) error { - return sm.SetConsiderOnlineRetrievalDealsConfigFunc(b) -} - -func (sm *StorageMinerAPI) DealsConsiderOfflineStorageDeals(ctx context.Context) (bool, error) { - return sm.ConsiderOfflineStorageDealsConfigFunc() -} - -func (sm *StorageMinerAPI) DealsSetConsiderOfflineStorageDeals(ctx context.Context, b bool) error { - return sm.SetConsiderOfflineStorageDealsConfigFunc(b) -} - -func (sm *StorageMinerAPI) DealsConsiderOfflineRetrievalDeals(ctx context.Context) (bool, error) { - return sm.ConsiderOfflineRetrievalDealsConfigFunc() -} - -func (sm *StorageMinerAPI) DealsSetConsiderOfflineRetrievalDeals(ctx context.Context, b bool) error { - return sm.SetConsiderOfflineRetrievalDealsConfigFunc(b) -} - -func (sm *StorageMinerAPI) DealsConsiderVerifiedStorageDeals(ctx context.Context) (bool, error) { - return sm.ConsiderVerifiedStorageDealsConfigFunc() -} - -func (sm *StorageMinerAPI) DealsSetConsiderVerifiedStorageDeals(ctx context.Context, b bool) error { - return sm.SetConsiderVerifiedStorageDealsConfigFunc(b) -} - -func (sm *StorageMinerAPI) DealsConsiderUnverifiedStorageDeals(ctx context.Context) (bool, error) { - return sm.ConsiderUnverifiedStorageDealsConfigFunc() -} - -func (sm *StorageMinerAPI) DealsSetConsiderUnverifiedStorageDeals(ctx context.Context, b bool) error { - return sm.SetConsiderUnverifiedStorageDealsConfigFunc(b) -} - func (sm *StorageMinerAPI) DealsGetExpectedSealDurationFunc(ctx context.Context) (time.Duration, error) { return sm.GetExpectedSealDurationFunc() } @@ -1242,24 +524,6 @@ func (sm *StorageMinerAPI) DealsSetExpectedSealDurationFunc(ctx context.Context, return sm.SetExpectedSealDurationFunc(d) } -func (sm *StorageMinerAPI) DealsImportData(ctx context.Context, deal cid.Cid, fname string) error { - fi, err := os.Open(fname) - if err != nil { - return xerrors.Errorf("failed to open given file: %w", err) - } - defer fi.Close() //nolint:errcheck - - return sm.StorageProvider.ImportDataForDeal(ctx, deal, fi) -} - -func (sm *StorageMinerAPI) DealsPieceCidBlocklist(ctx context.Context) ([]cid.Cid, error) { - return sm.StorageDealPieceCidBlocklistConfigFunc() -} - -func (sm *StorageMinerAPI) DealsSetPieceCidBlocklist(ctx context.Context, cids []cid.Cid) error { - return sm.SetStorageDealPieceCidBlocklistConfigFunc(cids) -} - func (sm *StorageMinerAPI) StorageAddLocal(ctx context.Context, path string) error { if sm.StorageMgr == nil { return xerrors.Errorf("no storage manager") @@ -1283,32 +547,6 @@ func (sm *StorageMinerAPI) StorageRedeclareLocal(ctx context.Context, id *storif return sm.StorageMgr.RedeclareLocalStorage(ctx, id, dropMissing) } - -func (sm *StorageMinerAPI) PiecesListPieces(ctx context.Context) ([]cid.Cid, error) { - return sm.PieceStore.ListPieceInfoKeys() -} - -func (sm *StorageMinerAPI) PiecesListCidInfos(ctx context.Context) ([]cid.Cid, error) { - return sm.PieceStore.ListCidInfoKeys() -} - -func (sm *StorageMinerAPI) PiecesGetPieceInfo(ctx context.Context, pieceCid cid.Cid) (*piecestore.PieceInfo, error) { - pi, err := sm.PieceStore.GetPieceInfo(pieceCid) - if err != nil { - return nil, err - } - return &pi, nil -} - -func (sm *StorageMinerAPI) PiecesGetCIDInfo(ctx context.Context, payloadCid cid.Cid) (*piecestore.CIDInfo, error) { - ci, err := sm.PieceStore.GetCIDInfo(payloadCid) - if err != nil { - return nil, err - } - - return &ci, nil -} - func (sm *StorageMinerAPI) CreateBackup(ctx context.Context, fpath string) error { return backup(ctx, sm.DS, fpath) } diff --git a/node/modules/alerts.go b/node/modules/alerts.go index 9976c6d0e..e0aa0977a 100644 --- a/node/modules/alerts.go +++ b/node/modules/alerts.go @@ -100,16 +100,6 @@ func CheckUDPBufferSize(wanted int) func(al *alerting.Alerting) { } } -func LegacyMarketsEOL(al *alerting.Alerting) { - // Add alert if lotus-miner legacy markets subsystem is still in use - alert := al.AddAlertType("system", "EOL") - - // Alert with a message to migrate to Boost or similar markets subsystems - al.Raise(alert, map[string]string{ - "message": "The lotus-miner legacy markets subsystem is deprecated and will be removed in a future release. Please migrate to [Boost](https://boost.filecoin.io) or similar markets subsystems.", - }) -} - func CheckFvmConcurrency() func(al *alerting.Alerting) { return func(al *alerting.Alerting) { fvmConcurrency, ok := os.LookupEnv("LOTUS_FVM_CONCURRENCY") diff --git a/node/modules/client.go b/node/modules/client.go deleted file mode 100644 index 9d8eef421..000000000 --- a/node/modules/client.go +++ /dev/null @@ -1,218 +0,0 @@ -package modules - -import ( - "bytes" - "context" - "os" - "path/filepath" - "time" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" - "github.com/libp2p/go-libp2p/core/host" - "go.uber.org/fx" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-data-transfer/v2/channelmonitor" - dtimpl "github.com/filecoin-project/go-data-transfer/v2/impl" - dtnet "github.com/filecoin-project/go-data-transfer/v2/network" - dtgstransport "github.com/filecoin-project/go-data-transfer/v2/transport/graphsync" - "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" - retrievalimpl "github.com/filecoin-project/go-fil-markets/retrievalmarket/impl" - rmnet "github.com/filecoin-project/go-fil-markets/retrievalmarket/network" - "github.com/filecoin-project/go-fil-markets/storagemarket" - storageimpl "github.com/filecoin-project/go-fil-markets/storagemarket/impl" - smnet "github.com/filecoin-project/go-fil-markets/storagemarket/network" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/chain/market" - "github.com/filecoin-project/lotus/journal" - "github.com/filecoin-project/lotus/markets" - marketevents "github.com/filecoin-project/lotus/markets/loggers" - "github.com/filecoin-project/lotus/markets/retrievaladapter" - "github.com/filecoin-project/lotus/markets/storageadapter" - "github.com/filecoin-project/lotus/node/config" - "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/imports" -) - -func HandleMigrateClientFunds(lc fx.Lifecycle, mctx helpers.MetricsCtx, ds dtypes.MetadataDS, wallet full.WalletAPI, fundMgr *market.FundManager) { - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - addr, err := wallet.WalletDefaultAddress(ctx) - // nothing to be done if there is no default address - if err != nil { - return nil - } - b, err := ds.Get(helpers.LifecycleCtx(mctx, lc), datastore.NewKey("/marketfunds/client")) - if err != nil { - if xerrors.Is(err, datastore.ErrNotFound) { - return nil - } - log.Errorf("client funds migration - getting datastore value: %v", err) - return nil - } - - var value abi.TokenAmount - if err = value.UnmarshalCBOR(bytes.NewReader(b)); err != nil { - log.Errorf("client funds migration - unmarshalling datastore value: %v", err) - return nil - } - _, err = fundMgr.Reserve(ctx, addr, addr, value) - if err != nil { - log.Errorf("client funds migration - reserving funds (wallet %s, addr %s, funds %d): %v", - addr, addr, value, err) - return nil - } - - return ds.Delete(helpers.LifecycleCtx(mctx, lc), datastore.NewKey("/marketfunds/client")) - }, - }) -} - -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) - } - - ns := namespace.Wrap(ds, datastore.NewKey("/client")) - return imports.NewManager(ns, dir), nil -} - -// 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(blockstore.FromDatastore(datastore.NewMapDatastore())) -} - -// NewClientGraphsyncDataTransfer returns a data transfer manager that just -// uses the clients's Client DAG service for transfers -func NewClientGraphsyncDataTransfer(lc fx.Lifecycle, h host.Host, gs dtypes.Graphsync, ds dtypes.MetadataDS, r repo.LockedRepo) (dtypes.ClientDataTransfer, error) { - // go-data-transfer protocol retries: - // 1s, 5s, 25s, 2m5s, 5m x 11 ~= 1 hour - dtRetryParams := dtnet.RetryParameters(time.Second, 5*time.Minute, 15, 5) - net := dtnet.NewFromLibp2pHost(h, dtRetryParams) - - dtDs := namespace.Wrap(ds, datastore.NewKey("/datatransfer/client/transfers")) - transport := dtgstransport.NewTransport(h.ID(), gs) - - // data-transfer push / pull channel restart configuration: - dtRestartConfig := dtimpl.ChannelRestartConfig(channelmonitor.Config{ - // Disable Accept and Complete timeouts until this issue is resolved: - // https://github.com/filecoin-project/lotus/issues/6343# - // Wait for the other side to respond to an Open channel message - AcceptTimeout: 0, - // Wait for the other side to send a Complete message once all - // data has been sent / received - CompleteTimeout: 0, - - // When an error occurs, wait a little while until all related errors - // have fired before sending a restart message - RestartDebounce: 10 * time.Second, - // After sending a restart, wait for at least 1 minute before sending another - RestartBackoff: time.Minute, - // After trying to restart 3 times, give up and fail the transfer - MaxConsecutiveRestarts: 3, - }) - dt, err := dtimpl.NewDataTransfer(dtDs, net, transport, dtRestartConfig) - if err != nil { - return nil, err - } - - dt.OnReady(marketevents.ReadyLogger("client data transfer")) - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - dt.SubscribeToEvents(marketevents.DataTransferLogger) - return dt.Start(ctx) - }, - OnStop: func(ctx context.Context) error { - return dt.Stop(ctx) - }, - }) - return dt, nil -} - -// NewClientDatastore creates a datastore for the client to store its deals -func NewClientDatastore(ds dtypes.MetadataDS) dtypes.ClientDatastore { - return namespace.Wrap(ds, datastore.NewKey("/deals/client")) -} - -// 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, dataTransfer, discovery, deals, scn, accessor, storageimpl.DealPollingInterval(time.Second), storageimpl.MaxTraversalLinks(config.MaxTraversalLinks)) - if err != nil { - return nil, err - } - c.OnReady(marketevents.ReadyLogger("storage client")) - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - c.SubscribeToEvents(marketevents.StorageClientLogger) - - evtType := j.RegisterEventType("markets/storage/client", "state_change") - c.SubscribeToEvents(markets.StorageClientJournaler(j, evtType)) - - return c.Start(ctx) - }, - OnStop: func(context.Context) error { - return c.Stop() - }, - }) - return c, nil -} - -// RetrievalClient creates a new retrieval client attached to the client blockstore -func RetrievalClient(forceOffChain bool) func(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 *retrievaladapter.APIBlockstoreAccessor, j journal.Journal) (retrievalmarket.RetrievalClient, error) { - return func(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 *retrievaladapter.APIBlockstoreAccessor, j journal.Journal) (retrievalmarket.RetrievalClient, error) { - adapter := retrievaladapter.NewRetrievalClientNode(forceOffChain, payAPI, chainAPI, stateAPI) - network := rmnet.NewFromLibp2pHost(h) - ds = namespace.Wrap(ds, datastore.NewKey("/retrievals/client")) - client, err := retrievalimpl.NewClient(network, dt, adapter, resolver, ds, accessor) - if err != nil { - return nil, err - } - client.OnReady(marketevents.ReadyLogger("retrieval client")) - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - client.SubscribeToEvents(marketevents.RetrievalClientLogger) - - evtType := j.RegisterEventType("markets/retrieval/client", "state_change") - client.SubscribeToEvents(markets.RetrievalClientJournaler(j, evtType)) - - return client.Start(ctx) - }, - }) - return client, nil - } -} diff --git a/node/modules/dtypes/miner.go b/node/modules/dtypes/miner.go index 24bcc714c..8e3a50cf1 100644 --- a/node/modules/dtypes/miner.go +++ b/node/modules/dtypes/miner.go @@ -1,14 +1,11 @@ package dtypes import ( - "context" "time" "github.com/ipfs/go-cid" "github.com/filecoin-project/go-address" - "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/lotus/storage/pipeline/sealiface" @@ -89,11 +86,3 @@ type SetExpectedSealDurationFunc func(time.Duration) error // GetExpectedSealDurationFunc is a function which reads from miner // too determine how long sealing is expected to take type GetExpectedSealDurationFunc func() (time.Duration, error) - -type SetMaxDealStartDelayFunc func(time.Duration) error -type GetMaxDealStartDelayFunc func() (time.Duration, error) - -type StorageDealFilter func(ctx context.Context, deal storagemarket.MinerDeal) (bool, string, error) -type RetrievalDealFilter func(ctx context.Context, deal retrievalmarket.ProviderDealState) (bool, string, error) - -type RetrievalPricingFunc func(ctx context.Context, dealPricingParams retrievalmarket.PricingInput) (retrievalmarket.Ask, error) diff --git a/node/modules/dtypes/storage.go b/node/modules/dtypes/storage.go index 7f0466f1f..102f6b67c 100644 --- a/node/modules/dtypes/storage.go +++ b/node/modules/dtypes/storage.go @@ -4,16 +4,8 @@ import ( bserv "github.com/ipfs/boxo/blockservice" exchange "github.com/ipfs/boxo/exchange" "github.com/ipfs/go-datastore" - "github.com/ipfs/go-graphsync" - - datatransfer "github.com/filecoin-project/go-data-transfer/v2" - dtnet "github.com/filecoin-project/go-data-transfer/v2/network" - "github.com/filecoin-project/go-fil-markets/piecestore" - "github.com/filecoin-project/go-fil-markets/storagemarket/impl/requestvalidation" - "github.com/filecoin-project/go-statestore" "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/node/repo/imports" ) // MetadataDS stores metadata. By default it's namespaced under /metadata in @@ -67,26 +59,3 @@ type ( type ChainBitswap exchange.Interface type ChainBlockService bserv.BlockService - -type ClientImportMgr *imports.Manager -type ClientBlockstore blockstore.BasicBlockstore -type ClientDealStore *statestore.StateStore -type ClientRequestValidator *requestvalidation.UnifiedRequestValidator -type ClientDatastore datastore.Batching - -type Graphsync graphsync.GraphExchange - -// ClientDataTransfer is a data transfer manager for the client -type ClientDataTransfer datatransfer.Manager - -type ProviderDealStore *statestore.StateStore -type ProviderPieceStore piecestore.PieceStore - -type ProviderRequestValidator *requestvalidation.UnifiedRequestValidator - -// ProviderDataTransfer is a data transfer manager for the provider -type ProviderDataTransfer datatransfer.Manager -type ProviderTransferNetwork dtnet.DataTransferNetwork -type ProviderTransport datatransfer.Transport -type StagingBlockstore blockstore.BasicBlockstore -type StagingGraphsync graphsync.GraphExchange diff --git a/node/modules/graphsync.go b/node/modules/graphsync.go deleted file mode 100644 index ca69cd2d2..000000000 --- a/node/modules/graphsync.go +++ /dev/null @@ -1,101 +0,0 @@ -package modules - -import ( - "context" - "time" - - "github.com/ipfs/go-graphsync" - graphsyncimpl "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/libp2p/go-libp2p/core/peer" - "go.opencensus.io/stats" - "go.uber.org/fx" - - "github.com/filecoin-project/lotus/metrics" - "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/node/repo" -) - -// Graphsync creates a graphsync instance from the given loader and storer -func Graphsync(parallelTransfersForStorage uint64, parallelTransfersForRetrieval uint64) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, r repo.LockedRepo, clientBs dtypes.ClientBlockstore, chainBs dtypes.ExposedBlockstore, h host.Host) (dtypes.Graphsync, error) { - return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, r repo.LockedRepo, clientBs dtypes.ClientBlockstore, chainBs dtypes.ExposedBlockstore, h host.Host) (dtypes.Graphsync, error) { - graphsyncNetwork := gsnet.NewFromLibp2pHost(h) - lsys := storeutil.LinkSystemForBlockstore(clientBs) - - gs := graphsyncimpl.New(helpers.LifecycleCtx(mctx, lc), - graphsyncNetwork, - lsys, - graphsyncimpl.RejectAllRequestsByDefault(), - graphsyncimpl.MaxInProgressIncomingRequests(parallelTransfersForStorage), - graphsyncimpl.MaxInProgressOutgoingRequests(parallelTransfersForRetrieval), - graphsyncimpl.MaxLinksPerIncomingRequests(config.MaxTraversalLinks), - graphsyncimpl.MaxLinksPerOutgoingRequests(config.MaxTraversalLinks)) - chainLinkSystem := storeutil.LinkSystemForBlockstore(chainBs) - err := gs.RegisterPersistenceOption("chainstore", chainLinkSystem) - if err != nil { - return nil, err - } - gs.RegisterIncomingRequestHook(func(p peer.ID, requestData graphsync.RequestData, hookActions graphsync.IncomingRequestHookActions) { - _, has := requestData.Extension("chainsync") - if has { - // TODO: we should confirm the selector is a reasonable one before we validate - // TODO: this code will get more complicated and should probably not live here eventually - hookActions.ValidateRequest() - hookActions.UsePersistenceOption("chainstore") - } - }) - gs.RegisterOutgoingRequestHook(func(p peer.ID, requestData graphsync.RequestData, hookActions graphsync.OutgoingRequestHookActions) { - _, has := requestData.Extension("chainsync") - if has { - hookActions.UsePersistenceOption("chainstore") - } - }) - - graphsyncStats(mctx, lc, gs) - - return gs, nil - } -} - -func graphsyncStats(mctx helpers.MetricsCtx, lc fx.Lifecycle, gs dtypes.Graphsync) { - stopStats := make(chan struct{}) - lc.Append(fx.Hook{ - OnStart: func(context.Context) error { - go func() { - t := time.NewTicker(10 * time.Second) - for { - select { - case <-t.C: - - st := gs.Stats() - stats.Record(mctx, metrics.GraphsyncReceivingPeersCount.M(int64(st.OutgoingRequests.TotalPeers))) - stats.Record(mctx, metrics.GraphsyncReceivingActiveCount.M(int64(st.OutgoingRequests.Active))) - stats.Record(mctx, metrics.GraphsyncReceivingCountCount.M(int64(st.OutgoingRequests.Pending))) - stats.Record(mctx, metrics.GraphsyncReceivingTotalMemoryAllocated.M(int64(st.IncomingResponses.TotalAllocatedAllPeers))) - stats.Record(mctx, metrics.GraphsyncReceivingTotalPendingAllocations.M(int64(st.IncomingResponses.TotalPendingAllocations))) - stats.Record(mctx, metrics.GraphsyncReceivingPeersPending.M(int64(st.IncomingResponses.NumPeersWithPendingAllocations))) - stats.Record(mctx, metrics.GraphsyncSendingPeersCount.M(int64(st.IncomingRequests.TotalPeers))) - stats.Record(mctx, metrics.GraphsyncSendingActiveCount.M(int64(st.IncomingRequests.Active))) - stats.Record(mctx, metrics.GraphsyncSendingCountCount.M(int64(st.IncomingRequests.Pending))) - stats.Record(mctx, metrics.GraphsyncSendingTotalMemoryAllocated.M(int64(st.OutgoingResponses.TotalAllocatedAllPeers))) - stats.Record(mctx, metrics.GraphsyncSendingTotalPendingAllocations.M(int64(st.OutgoingResponses.TotalPendingAllocations))) - stats.Record(mctx, metrics.GraphsyncSendingPeersPending.M(int64(st.OutgoingResponses.NumPeersWithPendingAllocations))) - - case <-stopStats: - return - } - } - }() - - return nil - }, - OnStop: func(ctx context.Context) error { - close(stopStats) - return nil - }, - }) -} diff --git a/node/modules/lp2p/host.go b/node/modules/lp2p/host.go index 9c140b41e..405bb8692 100644 --- a/node/modules/lp2p/host.go +++ b/node/modules/lp2p/host.go @@ -38,7 +38,7 @@ func Peerstore() (peerstore.Peerstore, error) { return pstoremem.NewPeerstore() } -func Host(mctx helpers.MetricsCtx, lc fx.Lifecycle, params P2PHostIn) (RawHost, error) { +func Host(mctx helpers.MetricsCtx, buildVersion build.BuildVersion, lc fx.Lifecycle, params P2PHostIn) (RawHost, error) { pkey := params.Peerstore.PrivKey(params.ID) if pkey == nil { return nil, fmt.Errorf("missing private key for node ID: %s", params.ID) @@ -49,7 +49,7 @@ func Host(mctx helpers.MetricsCtx, lc fx.Lifecycle, params P2PHostIn) (RawHost, libp2p.Peerstore(params.Peerstore), libp2p.NoListenAddrs, libp2p.Ping(true), - libp2p.UserAgent("lotus-" + build.UserVersion()), + libp2p.UserAgent("lotus-" + string(buildVersion)), } for _, o := range params.Opts { opts = append(opts, o...) diff --git a/node/modules/services.go b/node/modules/services.go index f3dd443d9..9c90ba130 100644 --- a/node/modules/services.go +++ b/node/modules/services.go @@ -6,8 +6,6 @@ import ( "strconv" "time" - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/event" "github.com/libp2p/go-libp2p/core/host" @@ -17,9 +15,6 @@ import ( "go.uber.org/fx" "golang.org/x/xerrors" - "github.com/filecoin-project/go-fil-markets/discovery" - discoveryimpl "github.com/filecoin-project/go-fil-markets/discovery/impl" - "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain" "github.com/filecoin-project/lotus/chain/beacon" @@ -34,7 +29,6 @@ import ( "github.com/filecoin-project/lotus/journal" "github.com/filecoin-project/lotus/journal/fsjournal" "github.com/filecoin-project/lotus/lib/peermgr" - marketevents "github.com/filecoin-project/lotus/markets/loggers" "github.com/filecoin-project/lotus/node/hello" "github.com/filecoin-project/lotus/node/impl/full" "github.com/filecoin-project/lotus/node/modules/dtypes" @@ -224,24 +218,6 @@ func RelayIndexerMessages(lc fx.Lifecycle, ps *pubsub.PubSub, nn dtypes.NetworkN return nil } -func NewLocalDiscovery(lc fx.Lifecycle, ds dtypes.MetadataDS) (*discoveryimpl.Local, error) { - local, err := discoveryimpl.NewLocal(namespace.Wrap(ds, datastore.NewKey("/deals/local"))) - if err != nil { - return nil, err - } - local.OnReady(marketevents.ReadyLogger("discovery")) - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - return local.Start(ctx) - }, - }) - return local, nil -} - -func RetrievalResolver(l *discoveryimpl.Local) discovery.PeerResolver { - return discoveryimpl.Multi(l) -} - type RandomBeaconParams struct { fx.In diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 1b9988b95..dd39ec2ae 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -1,53 +1,28 @@ package modules import ( - "bytes" "context" "errors" - "fmt" "net/http" - "os" - "path/filepath" "strings" "time" "github.com/google/uuid" - "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" - provider "github.com/ipni/index-provider" - "github.com/libp2p/go-libp2p/core/host" "go.uber.org/fx" "go.uber.org/multierr" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" - dtimpl "github.com/filecoin-project/go-data-transfer/v2/impl" - dtnet "github.com/filecoin-project/go-data-transfer/v2/network" - dtgstransport "github.com/filecoin-project/go-data-transfer/v2/transport/graphsync" - piecefilestore "github.com/filecoin-project/go-fil-markets/filestore" - piecestoreimpl "github.com/filecoin-project/go-fil-markets/piecestore/impl" - "github.com/filecoin-project/go-fil-markets/retrievalmarket" - retrievalimpl "github.com/filecoin-project/go-fil-markets/retrievalmarket/impl" - rmnet "github.com/filecoin-project/go-fil-markets/retrievalmarket/network" - "github.com/filecoin-project/go-fil-markets/shared" - "github.com/filecoin-project/go-fil-markets/storagemarket" - storageimpl "github.com/filecoin-project/go-fil-markets/storagemarket/impl" - "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-paramfetch" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-statestore" "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" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/events" @@ -55,11 +30,6 @@ import ( "github.com/filecoin-project/lotus/chain/gen/slashfilter" "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" - "github.com/filecoin-project/lotus/markets/idxprov" - 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" @@ -332,163 +302,6 @@ func WindowPostScheduler(fc config.MinerFeeConfig, pc config.ProvingConfig) func } } -func HandleRetrieval(host host.Host, lc fx.Lifecycle, m retrievalmarket.RetrievalProvider, j journal.Journal) { - m.OnReady(marketevents.ReadyLogger("retrieval provider")) - lc.Append(fx.Hook{ - - OnStart: func(ctx context.Context) error { - m.SubscribeToEvents(marketevents.RetrievalProviderLogger) - - evtType := j.RegisterEventType("markets/retrieval/provider", "state_change") - m.SubscribeToEvents(markets.RetrievalProviderJournaler(j, evtType)) - - return m.Start(ctx) - }, - OnStop: func(context.Context) error { - return m.Stop() - }, - }) -} - -func HandleDeals(mctx helpers.MetricsCtx, lc fx.Lifecycle, host host.Host, h storagemarket.StorageProvider, j journal.Journal) { - ctx := helpers.LifecycleCtx(mctx, lc) - h.OnReady(marketevents.ReadyLogger("storage provider")) - lc.Append(fx.Hook{ - OnStart: func(context.Context) error { - h.SubscribeToEvents(marketevents.StorageProviderLogger) - - evtType := j.RegisterEventType("markets/storage/provider", "state_change") - h.SubscribeToEvents(markets.StorageProviderJournaler(j, evtType)) - - return h.Start(ctx) - }, - OnStop: func(context.Context) error { - return h.Stop() - }, - }) -} - -func HandleMigrateProviderFunds(lc fx.Lifecycle, ds dtypes.MetadataDS, node api.FullNode, minerAddress dtypes.MinerAddress) { - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - b, err := ds.Get(ctx, datastore.NewKey("/marketfunds/provider")) - if err != nil { - if xerrors.Is(err, datastore.ErrNotFound) { - return nil - } - return err - } - - var value abi.TokenAmount - if err = value.UnmarshalCBOR(bytes.NewReader(b)); err != nil { - return err - } - ts, err := node.ChainHead(ctx) - if err != nil { - log.Errorf("provider funds migration - getting chain head: %v", err) - return nil - } - - mi, err := node.StateMinerInfo(ctx, address.Address(minerAddress), ts.Key()) - if err != nil { - log.Errorf("provider funds migration - getting miner info %s: %v", minerAddress, err) - return nil - } - - _, err = node.MarketReserveFunds(ctx, mi.Worker, address.Address(minerAddress), value) - if err != nil { - log.Errorf("provider funds migration - reserving funds (wallet %s, addr %s, funds %d): %v", - mi.Worker, minerAddress, value, err) - return nil - } - - return ds.Delete(ctx, datastore.NewKey("/marketfunds/provider")) - }, - }) -} - -// NewProviderTransferNetwork sets up the libp2p2 protocol networking for data transfer -func NewProviderTransferNetwork(h host.Host) dtypes.ProviderTransferNetwork { - return dtnet.NewFromLibp2pHost(h) -} - -// NewProviderTransport sets up a data transfer transport over graphsync -func NewProviderTransport(h host.Host, gs dtypes.StagingGraphsync) dtypes.ProviderTransport { - return dtgstransport.NewTransport(h.ID(), gs) -} - -// NewProviderDataTransfer returns a data transfer manager -func NewProviderDataTransfer(lc fx.Lifecycle, net dtypes.ProviderTransferNetwork, transport dtypes.ProviderTransport, ds dtypes.MetadataDS, r repo.LockedRepo) (dtypes.ProviderDataTransfer, error) { - dtDs := namespace.Wrap(ds, datastore.NewKey("/datatransfer/provider/transfers")) - - dt, err := dtimpl.NewDataTransfer(dtDs, net, transport) - if err != nil { - return nil, err - } - - dt.OnReady(marketevents.ReadyLogger("provider data transfer")) - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - dt.SubscribeToEvents(marketevents.DataTransferLogger) - return dt.Start(ctx) - }, - OnStop: func(ctx context.Context) error { - return dt.Stop(ctx) - }, - }) - return dt, nil -} - -// NewProviderPieceStore creates a statestore for storing metadata about pieces -// shared by the storage and retrieval providers -func NewProviderPieceStore(lc fx.Lifecycle, ds dtypes.MetadataDS) (dtypes.ProviderPieceStore, error) { - ps, err := piecestoreimpl.NewPieceStore(namespace.Wrap(ds, datastore.NewKey("/storagemarket"))) - if err != nil { - return nil, err - } - ps.OnReady(marketevents.ReadyLogger("piecestore")) - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - return ps.Start(ctx) - }, - }) - return ps, 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) { - ctx := helpers.LifecycleCtx(mctx, lc) - stagingds, err := r.Datastore(ctx, "/staging") - if err != nil { - return nil, err - } - - return blockstore.FromDatastore(stagingds), nil -} - -// StagingGraphsync creates a graphsync instance which reads and writes blocks -// to the StagingBlockstore -func StagingGraphsync(parallelTransfersForStorage uint64, parallelTransfersForStoragePerPeer uint64, parallelTransfersForRetrieval uint64) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, ibs dtypes.StagingBlockstore, h host.Host) dtypes.StagingGraphsync { - return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, ibs dtypes.StagingBlockstore, h host.Host) dtypes.StagingGraphsync { - graphsyncNetwork := gsnet.NewFromLibp2pHost(h) - lsys := storeutil.LinkSystemForBlockstore(ibs) - gs := graphsync.New(helpers.LifecycleCtx(mctx, lc), - graphsyncNetwork, - lsys, - graphsync.RejectAllRequestsByDefault(), - graphsync.MaxInProgressIncomingRequests(parallelTransfersForRetrieval), - graphsync.MaxInProgressIncomingRequestsPerPeer(parallelTransfersForStoragePerPeer), - graphsync.MaxInProgressOutgoingRequests(parallelTransfersForStorage), - graphsync.MaxLinksPerIncomingRequests(config.MaxTraversalLinks), - graphsync.MaxLinksPerOutgoingRequests(config.MaxTraversalLinks)) - - graphsyncStats(mctx, lc, gs) - - return gs - } -} - func SetupBlockProducer(lc fx.Lifecycle, ds dtypes.MetadataDS, api v1api.FullNode, epp gen.WinningPoStProver, sf *slashfilter.SlashFilter, j journal.Journal) (*lotusminer.Miner, error) { minerAddr, err := minerAddrFromDS(ds) if err != nil { @@ -512,273 +325,6 @@ func SetupBlockProducer(lc fx.Lifecycle, ds dtypes.MetadataDS, api v1api.FullNod return m, nil } -func NewStorageAsk(ctx helpers.MetricsCtx, fapi v1api.FullNode, ds dtypes.MetadataDS, minerAddress dtypes.MinerAddress, spn storagemarket.StorageProviderNode) (*storedask.StoredAsk, error) { - - mi, err := fapi.StateMinerInfo(ctx, address.Address(minerAddress), types.EmptyTSK) - if err != nil { - return nil, err - } - - providerDs := namespace.Wrap(ds, datastore.NewKey("/deals/provider")) - // legacy this was mistake where this key was place -- so we move the legacy key if need be - err = shared.MoveKey(providerDs, "/latest-ask", "/storage-ask/latest") - if err != nil { - return nil, err - } - return storedask.NewStoredAsk(namespace.Wrap(providerDs, datastore.NewKey("/storage-ask")), datastore.NewKey("latest"), spn, address.Address(minerAddress), - storagemarket.MaxPieceSize(abi.PaddedPieceSize(mi.SectorSize))) -} - -func BasicDealFilter(cfg config.DealmakingConfig, user dtypes.StorageDealFilter) func(onlineOk dtypes.ConsiderOnlineStorageDealsConfigFunc, - offlineOk dtypes.ConsiderOfflineStorageDealsConfigFunc, - verifiedOk dtypes.ConsiderVerifiedStorageDealsConfigFunc, - unverifiedOk dtypes.ConsiderUnverifiedStorageDealsConfigFunc, - blocklistFunc dtypes.StorageDealPieceCidBlocklistConfigFunc, - expectedSealTimeFunc dtypes.GetExpectedSealDurationFunc, - startDelay dtypes.GetMaxDealStartDelayFunc, - spn storagemarket.StorageProviderNode, - r repo.LockedRepo, -) dtypes.StorageDealFilter { - return func(onlineOk dtypes.ConsiderOnlineStorageDealsConfigFunc, - offlineOk dtypes.ConsiderOfflineStorageDealsConfigFunc, - verifiedOk dtypes.ConsiderVerifiedStorageDealsConfigFunc, - unverifiedOk dtypes.ConsiderUnverifiedStorageDealsConfigFunc, - blocklistFunc dtypes.StorageDealPieceCidBlocklistConfigFunc, - expectedSealTimeFunc dtypes.GetExpectedSealDurationFunc, - startDelay dtypes.GetMaxDealStartDelayFunc, - spn storagemarket.StorageProviderNode, - r repo.LockedRepo, - ) dtypes.StorageDealFilter { - - return func(ctx context.Context, deal storagemarket.MinerDeal) (bool, string, error) { - b, err := onlineOk() - if err != nil { - return false, "miner error", err - } - - if deal.Ref != nil && deal.Ref.TransferType != storagemarket.TTManual && !b { - log.Warnf("online storage deal consideration disabled; rejecting storage deal proposal from client: %s", deal.Client.String()) - return false, "miner is not considering online storage deals", nil - } - - b, err = offlineOk() - if err != nil { - return false, "miner error", err - } - - if deal.Ref != nil && deal.Ref.TransferType == storagemarket.TTManual && !b { - log.Warnf("offline storage deal consideration disabled; rejecting storage deal proposal from client: %s", deal.Client.String()) - return false, "miner is not accepting offline storage deals", nil - } - - b, err = verifiedOk() - if err != nil { - return false, "miner error", err - } - - if deal.Proposal.VerifiedDeal && !b { - log.Warnf("verified storage deal consideration disabled; rejecting storage deal proposal from client: %s", deal.Client.String()) - return false, "miner is not accepting verified storage deals", nil - } - - b, err = unverifiedOk() - if err != nil { - return false, "miner error", err - } - - if !deal.Proposal.VerifiedDeal && !b { - log.Warnf("unverified storage deal consideration disabled; rejecting storage deal proposal from client: %s", deal.Client.String()) - return false, "miner is not accepting unverified storage deals", nil - } - - blocklist, err := blocklistFunc() - if err != nil { - return false, "miner error", err - } - - for idx := range blocklist { - if deal.Proposal.PieceCID.Equals(blocklist[idx]) { - log.Warnf("piece CID in proposal %s is blocklisted; rejecting storage deal proposal from client: %s", deal.Proposal.PieceCID, deal.Client.String()) - return false, fmt.Sprintf("miner has blocklisted piece CID %s", deal.Proposal.PieceCID), nil - } - } - - sealDuration, err := expectedSealTimeFunc() - if err != nil { - return false, "miner error", err - } - - sealEpochs := sealDuration / (time.Duration(build.BlockDelaySecs) * time.Second) - _, ht, err := spn.GetChainHead(ctx) - if err != nil { - return false, "failed to get chain head", err - } - earliest := abi.ChainEpoch(sealEpochs) + ht - if deal.Proposal.StartEpoch < earliest { - log.Warnw("proposed deal would start before sealing can be completed; rejecting storage deal proposal from client", "piece_cid", deal.Proposal.PieceCID, "client", deal.Client.String(), "seal_duration", sealDuration, "earliest", earliest, "curepoch", ht) - return false, fmt.Sprintf("cannot seal a sector before %s", deal.Proposal.StartEpoch), nil - } - - sd, err := startDelay() - if err != nil { - return false, "miner error", err - } - - dir := filepath.Join(r.Path(), StagingAreaDirName) - diskUsageBytes, err := r.DiskUsage(dir) - if err != nil { - return false, "miner error", err - } - - if cfg.MaxStagingDealsBytes != 0 && diskUsageBytes >= cfg.MaxStagingDealsBytes { - log.Errorw("proposed deal rejected because there are too many deals in the staging area at the moment", "MaxStagingDealsBytes", cfg.MaxStagingDealsBytes, "DiskUsageBytes", diskUsageBytes) - return false, "cannot accept deal as miner is overloaded at the moment - there are too many staging deals being processed", nil - } - - // Reject if it's more than 7 days in the future - // TODO: read from cfg - maxStartEpoch := earliest + abi.ChainEpoch(uint64(sd.Seconds())/build.BlockDelaySecs) - if deal.Proposal.StartEpoch > maxStartEpoch { - return false, fmt.Sprintf("deal start epoch is too far in the future: %s > %s", deal.Proposal.StartEpoch, maxStartEpoch), nil - } - - if user != nil { - return user(ctx, deal) - } - - return true, "", nil - } - } -} - -func StorageProvider(minerAddress dtypes.MinerAddress, - storedAsk *storedask.StoredAsk, - h host.Host, ds dtypes.MetadataDS, - r repo.LockedRepo, - pieceStore dtypes.ProviderPieceStore, - indexer provider.Interface, - dataTransfer dtypes.ProviderDataTransfer, - spn storagemarket.StorageProviderNode, - df dtypes.StorageDealFilter, - dsw *dagstore.Wrapper, - meshCreator idxprov.MeshCreator, -) (storagemarket.StorageProvider, error) { - net := smnet.NewFromLibp2pHost(h) - - dir := filepath.Join(r.Path(), StagingAreaDirName) - - // 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, - dsw, - indexer, - pieceStore, - dataTransfer, - spn, - address.Address(minerAddress), - storedAsk, - meshCreator, - opt, - ) -} - -func RetrievalDealFilter(userFilter dtypes.RetrievalDealFilter) func(onlineOk dtypes.ConsiderOnlineRetrievalDealsConfigFunc, - offlineOk dtypes.ConsiderOfflineRetrievalDealsConfigFunc) dtypes.RetrievalDealFilter { - return func(onlineOk dtypes.ConsiderOnlineRetrievalDealsConfigFunc, - offlineOk dtypes.ConsiderOfflineRetrievalDealsConfigFunc) dtypes.RetrievalDealFilter { - return func(ctx context.Context, state retrievalmarket.ProviderDealState) (bool, string, error) { - b, err := onlineOk() - if err != nil { - return false, "miner error", err - } - - if !b { - log.Warn("online retrieval deal consideration disabled; rejecting retrieval deal proposal from client") - return false, "miner is not accepting online retrieval deals", nil - } - - b, err = offlineOk() - if err != nil { - return false, "miner error", err - } - - if !b { - log.Info("offline retrieval has not been implemented yet") - } - - if userFilter != nil { - return userFilter(ctx, state) - } - - return true, "", nil - } - } -} - -func RetrievalNetwork(h host.Host) rmnet.RetrievalMarketNetwork { - return rmnet.NewFromLibp2pHost(h) -} - -// RetrievalPricingFunc configures the pricing function to use for retrieval deals. -func RetrievalPricingFunc(cfg config.DealmakingConfig) func(_ dtypes.ConsiderOnlineRetrievalDealsConfigFunc, - _ dtypes.ConsiderOfflineRetrievalDealsConfigFunc) dtypes.RetrievalPricingFunc { - - return func(_ dtypes.ConsiderOnlineRetrievalDealsConfigFunc, - _ dtypes.ConsiderOfflineRetrievalDealsConfigFunc) dtypes.RetrievalPricingFunc { - if cfg.RetrievalPricing.Strategy == config.RetrievalPricingExternalMode { - return pricing.ExternalRetrievalPricingFunc(cfg.RetrievalPricing.External.Path) - } - - return retrievalimpl.DefaultPricingFunc(cfg.RetrievalPricing.Default.VerifiedDealsFreeTransfer) - } -} - -// RetrievalProvider creates a new retrieval provider attached to the provider blockstore -func RetrievalProvider( - maddr dtypes.MinerAddress, - adapter retrievalmarket.RetrievalProviderNode, - sa retrievalmarket.SectorAccessor, - netwk rmnet.RetrievalMarketNetwork, - ds dtypes.MetadataDS, - pieceStore dtypes.ProviderPieceStore, - dt dtypes.ProviderDataTransfer, - pricingFnc dtypes.RetrievalPricingFunc, - userFilter dtypes.RetrievalDealFilter, - dagStore *dagstore.Wrapper, -) (retrievalmarket.RetrievalProvider, error) { - opt := retrievalimpl.DealDeciderOpt(retrievalimpl.DealDecider(userFilter)) - - retrievalmarket.DefaultPricePerByte = big.Zero() // todo: for whatever reason this is a global var in markets - - 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") var ManagerWorkPrefix = datastore.NewKey("/stmgr/calls") @@ -838,153 +384,6 @@ func StorageAuthWithURL(apiInfo string) interface{} { } } -func NewConsiderOnlineStorageDealsConfigFunc(r repo.LockedRepo) (dtypes.ConsiderOnlineStorageDealsConfigFunc, error) { - return func() (out bool, err error) { - err = readDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - out = cfg.ConsiderOnlineStorageDeals - }) - return - }, nil -} - -func NewSetConsideringOnlineStorageDealsFunc(r repo.LockedRepo) (dtypes.SetConsiderOnlineStorageDealsConfigFunc, error) { - return func(b bool) (err error) { - err = mutateDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - cfg.ConsiderOnlineStorageDeals = b - c.SetDealmakingConfig(cfg) - }) - return - }, nil -} - -func NewConsiderOnlineRetrievalDealsConfigFunc(r repo.LockedRepo) (dtypes.ConsiderOnlineRetrievalDealsConfigFunc, error) { - return func() (out bool, err error) { - err = readDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - out = cfg.ConsiderOnlineRetrievalDeals - }) - return - }, nil -} - -func NewSetConsiderOnlineRetrievalDealsConfigFunc(r repo.LockedRepo) (dtypes.SetConsiderOnlineRetrievalDealsConfigFunc, error) { - return func(b bool) (err error) { - err = mutateDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - cfg.ConsiderOnlineRetrievalDeals = b - c.SetDealmakingConfig(cfg) - }) - return - }, nil -} - -func NewStorageDealPieceCidBlocklistConfigFunc(r repo.LockedRepo) (dtypes.StorageDealPieceCidBlocklistConfigFunc, error) { - return func() (out []cid.Cid, err error) { - err = readDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - out = cfg.PieceCidBlocklist - }) - return - }, nil -} - -func NewSetStorageDealPieceCidBlocklistConfigFunc(r repo.LockedRepo) (dtypes.SetStorageDealPieceCidBlocklistConfigFunc, error) { - return func(blocklist []cid.Cid) (err error) { - err = mutateDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - cfg.PieceCidBlocklist = blocklist - c.SetDealmakingConfig(cfg) - }) - return - }, nil -} - -func NewConsiderOfflineStorageDealsConfigFunc(r repo.LockedRepo) (dtypes.ConsiderOfflineStorageDealsConfigFunc, error) { - return func() (out bool, err error) { - err = readDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - out = cfg.ConsiderOfflineStorageDeals - }) - return - }, nil -} - -func NewSetConsideringOfflineStorageDealsFunc(r repo.LockedRepo) (dtypes.SetConsiderOfflineStorageDealsConfigFunc, error) { - return func(b bool) (err error) { - err = mutateDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - cfg.ConsiderOfflineStorageDeals = b - c.SetDealmakingConfig(cfg) - }) - return - }, nil -} - -func NewConsiderOfflineRetrievalDealsConfigFunc(r repo.LockedRepo) (dtypes.ConsiderOfflineRetrievalDealsConfigFunc, error) { - return func() (out bool, err error) { - err = readDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - out = cfg.ConsiderOfflineRetrievalDeals - }) - return - }, nil -} - -func NewSetConsiderOfflineRetrievalDealsConfigFunc(r repo.LockedRepo) (dtypes.SetConsiderOfflineRetrievalDealsConfigFunc, error) { - return func(b bool) (err error) { - err = mutateDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - cfg.ConsiderOfflineRetrievalDeals = b - c.SetDealmakingConfig(cfg) - }) - return - }, nil -} - -func NewConsiderVerifiedStorageDealsConfigFunc(r repo.LockedRepo) (dtypes.ConsiderVerifiedStorageDealsConfigFunc, error) { - return func() (out bool, err error) { - err = readDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - out = cfg.ConsiderVerifiedStorageDeals - }) - return - }, nil -} - -func NewSetConsideringVerifiedStorageDealsFunc(r repo.LockedRepo) (dtypes.SetConsiderVerifiedStorageDealsConfigFunc, error) { - return func(b bool) (err error) { - err = mutateDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - cfg.ConsiderVerifiedStorageDeals = b - c.SetDealmakingConfig(cfg) - }) - return - }, nil -} - -func NewConsiderUnverifiedStorageDealsConfigFunc(r repo.LockedRepo) (dtypes.ConsiderUnverifiedStorageDealsConfigFunc, error) { - return func() (out bool, err error) { - err = readDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - out = cfg.ConsiderUnverifiedStorageDeals - }) - return - }, nil -} - -func NewSetConsideringUnverifiedStorageDealsFunc(r repo.LockedRepo) (dtypes.SetConsiderUnverifiedStorageDealsConfigFunc, error) { - return func(b bool) (err error) { - err = mutateDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - cfg.ConsiderUnverifiedStorageDeals = b - c.SetDealmakingConfig(cfg) - }) - return - }, nil -} - func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error) { return func(cfg sealiface.Config) (err error) { err = mutateSealingCfg(r, func(c config.SealingConfiger) { @@ -1092,48 +491,6 @@ func NewGetSealConfigFunc(r repo.LockedRepo) (dtypes.GetSealingConfigFunc, error }, nil } -func NewSetExpectedSealDurationFunc(r repo.LockedRepo) (dtypes.SetExpectedSealDurationFunc, error) { - return func(delay time.Duration) (err error) { - err = mutateDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - cfg.ExpectedSealDuration = config.Duration(delay) - c.SetDealmakingConfig(cfg) - }) - return - }, nil -} - -func NewGetExpectedSealDurationFunc(r repo.LockedRepo) (dtypes.GetExpectedSealDurationFunc, error) { - return func() (out time.Duration, err error) { - err = readDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - out = time.Duration(cfg.ExpectedSealDuration) - }) - return - }, nil -} - -func NewSetMaxDealStartDelayFunc(r repo.LockedRepo) (dtypes.SetMaxDealStartDelayFunc, error) { - return func(delay time.Duration) (err error) { - err = mutateDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - cfg.MaxDealStartDelay = config.Duration(delay) - c.SetDealmakingConfig(cfg) - }) - return - }, nil -} - -func NewGetMaxDealStartDelayFunc(r repo.LockedRepo) (dtypes.GetMaxDealStartDelayFunc, error) { - return func() (out time.Duration, err error) { - err = readDealmakingCfg(r, func(c config.DealmakingConfiger) { - cfg := c.GetDealmakingConfig() - out = time.Duration(cfg.MaxDealStartDelay) - }) - return - }, nil -} - func readSealingCfg(r repo.LockedRepo, accessor func(config.DealmakingConfiger, config.SealingConfiger)) error { raw, err := r.Config() if err != nil { @@ -1171,91 +528,6 @@ func mutateSealingCfg(r repo.LockedRepo, mutator func(config.SealingConfiger)) e return multierr.Combine(typeErr, setConfigErr) } -func readDealmakingCfg(r repo.LockedRepo, accessor func(config.DealmakingConfiger)) error { - raw, err := r.Config() - if err != nil { - return err - } - - cfg, ok := raw.(config.DealmakingConfiger) - if !ok { - return xerrors.New("expected config with dealmaking config trait") - } - - accessor(cfg) - - return nil -} - -func mutateDealmakingCfg(r repo.LockedRepo, mutator func(config.DealmakingConfiger)) error { - var typeErr error - - setConfigErr := r.SetConfig(func(raw interface{}) { - cfg, ok := raw.(config.DealmakingConfiger) - if !ok { - typeErr = errors.New("expected config with dealmaking config trait") - return - } - - mutator(cfg) - }) - - 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) @@ -1266,8 +538,6 @@ func ExtractEnabledMinerSubsystems(cfg config.MinerSubsystemConfig) (res api.Min if cfg.EnableSectorStorage { res = append(res, api.SubsystemSectorStorage) } - if cfg.EnableMarkets { - res = append(res, api.SubsystemMarkets) - } + return res } diff --git a/node/modules/storageminer_dagstore.go b/node/modules/storageminer_dagstore.go deleted file mode 100644 index 620e69090..000000000 --- a/node/modules/storageminer_dagstore.go +++ /dev/null @@ -1,94 +0,0 @@ -package modules - -import ( - "context" - "fmt" - "os" - "path/filepath" - "strconv" - - "github.com/libp2p/go-libp2p/core/host" - "go.uber.org/fx" - "golang.org/x/xerrors" - - "github.com/filecoin-project/dagstore" - - 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(cfg config.DAGStoreConfig) func(fx.Lifecycle, repo.LockedRepo, dtypes.ProviderPieceStore, mdagstore.SectorAccessor) (mdagstore.MinerAPI, error) { - return func(lc fx.Lifecycle, r repo.LockedRepo, pieceStore dtypes.ProviderPieceStore, sa mdagstore.SectorAccessor) (mdagstore.MinerAPI, error) { - // 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, cfg.MaxConcurrentUnseals) - 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(cfg config.DAGStoreConfig) func(lc fx.Lifecycle, r repo.LockedRepo, minerAPI mdagstore.MinerAPI, h host.Host) (*dagstore.DAGStore, *mdagstore.Wrapper, error) { - return func(lc fx.Lifecycle, r repo.LockedRepo, minerAPI mdagstore.MinerAPI, h host.Host) (*dagstore.DAGStore, *mdagstore.Wrapper, error) { - // 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, h) - 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 - } -} diff --git a/node/modules/storageminer_idxprov.go b/node/modules/storageminer_idxprov.go deleted file mode 100644 index 777c59386..000000000 --- a/node/modules/storageminer_idxprov.go +++ /dev/null @@ -1,117 +0,0 @@ -package modules - -import ( - "context" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" - provider "github.com/ipni/index-provider" - "github.com/ipni/index-provider/engine" - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/host" - "go.uber.org/fx" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/node/config" - "github.com/filecoin-project/lotus/node/modules/dtypes" -) - -type IdxProv struct { - fx.In - - fx.Lifecycle - Datastore dtypes.MetadataDS -} - -func IndexProvider(cfg config.IndexProviderConfig) func(params IdxProv, marketHost host.Host, dt dtypes.ProviderDataTransfer, maddr dtypes.MinerAddress, ps *pubsub.PubSub, nn dtypes.NetworkName) (provider.Interface, error) { - return func(args IdxProv, marketHost host.Host, dt dtypes.ProviderDataTransfer, maddr dtypes.MinerAddress, ps *pubsub.PubSub, nn dtypes.NetworkName) (provider.Interface, error) { - topicName := cfg.TopicName - // If indexer topic name is left empty, infer it from the network name. - if topicName == "" { - // Use the same mechanism as the Dependency Injection (DI) to construct the topic name, - // so that we are certain it is consistent with the name allowed by the subscription - // filter. - // - // See: lp2p.GossipSub. - topicName = build.IndexerIngestTopic(nn) - log.Debugw("Inferred indexer topic from network name", "topic", topicName) - } - - ipds := namespace.Wrap(args.Datastore, datastore.NewKey("/index-provider")) - addrs := marketHost.Addrs() - addrsString := make([]string, 0, len(addrs)) - for _, addr := range addrs { - addrsString = append(addrsString, addr.String()) - } - var opts = []engine.Option{ - engine.WithDatastore(ipds), - engine.WithHost(marketHost), - engine.WithRetrievalAddrs(addrsString...), - engine.WithEntriesCacheCapacity(cfg.EntriesCacheCapacity), - engine.WithChainedEntries(cfg.EntriesChunkSize), - engine.WithTopicName(topicName), - engine.WithPurgeCacheOnStart(cfg.PurgeCacheOnStart), - } - - llog := log.With( - "idxProvEnabled", cfg.Enable, - "pid", marketHost.ID(), - "topic", topicName, - "retAddrs", marketHost.Addrs()) - // If announcements to the network are enabled, then set options for datatransfer publisher. - if cfg.Enable { - // Join the indexer topic using the market's pubsub instance. Otherwise, the provider - // engine would create its own instance of pubsub down the line in dagsync, which has - // no validators by default. - t, err := ps.Join(topicName) - if err != nil { - llog.Errorw("Failed to join indexer topic", "err", err) - return nil, xerrors.Errorf("joining indexer topic %s: %w", topicName, err) - } - - // Get the miner ID and set as extra gossip data. - // The extra data is required by the lotus-specific index-provider gossip message validators. - ma := address.Address(maddr) - opts = append(opts, - engine.WithPublisherKind(engine.DataTransferPublisher), - engine.WithDataTransfer(dt), - engine.WithExtraGossipData(ma.Bytes()), - engine.WithTopic(t), - ) - llog = llog.With("extraGossipData", ma, "publisher", "data-transfer") - } else { - opts = append(opts, engine.WithPublisherKind(engine.NoPublisher)) - llog = llog.With("publisher", "none") - } - - // Instantiate the index provider engine. - e, err := engine.New(opts...) - if err != nil { - return nil, xerrors.Errorf("creating indexer provider engine: %w", err) - } - llog.Info("Instantiated index provider engine") - - args.Lifecycle.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - // Note that the OnStart context is cancelled after startup. Its use in e.Start is - // to start up gossipsub publishers and restore cache, all of which are completed - // before e.Start returns. Therefore, it is fine to reuse the give context. - if err := e.Start(ctx); err != nil { - return xerrors.Errorf("starting indexer provider engine: %w", err) - } - log.Infof("Started index provider engine") - return nil - }, - OnStop: func(_ context.Context) error { - if err := e.Shutdown(); err != nil { - return xerrors.Errorf("shutting down indexer provider engine: %w", err) - } - return nil - }, - }) - return e, nil - } -} diff --git a/node/modules/storageminer_idxprov_test.go b/node/modules/storageminer_idxprov_test.go deleted file mode 100644 index 434577bab..000000000 --- a/node/modules/storageminer_idxprov_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package modules_test - -import ( - "context" - "strings" - "testing" - "time" - - "github.com/ipfs/go-datastore" - provider "github.com/ipni/index-provider" - "github.com/libp2p/go-libp2p" - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/host" - "github.com/stretchr/testify/require" - "go.uber.org/fx" - - "github.com/filecoin-project/go-address" - - "github.com/filecoin-project/lotus/node/config" - "github.com/filecoin-project/lotus/node/modules" - "github.com/filecoin-project/lotus/node/modules/dtypes" -) - -func Test_IndexProviderTopic(t *testing.T) { - tests := []struct { - name string - givenAllowedTopics []string - givenConfiguredTopic string - givenNetworkName dtypes.NetworkName - wantErr string - }{ - { - name: "Joins configured topic when allowed", - givenAllowedTopics: []string{"fish"}, - givenConfiguredTopic: "fish", - }, - { - name: "Joins topic inferred from network name when allowed", - givenAllowedTopics: []string{"/indexer/ingest/fish"}, - givenNetworkName: "fish", - }, - { - name: "Fails to join configured topic when disallowed", - givenAllowedTopics: []string{"/indexer/ingest/fish"}, - givenConfiguredTopic: "lobster", - wantErr: "joining indexer topic lobster: topic is not allowed by the subscription filter", - }, - { - name: "Fails to join topic inferred from network name when disallowed", - givenAllowedTopics: []string{"/indexer/ingest/fish"}, - givenNetworkName: "lobster", - wantErr: "joining indexer topic /indexer/ingest/lobster: topic is not allowed by the subscription filter", - }, - } - - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - h, err := libp2p.New() - require.NoError(t, err) - defer func() { - require.NoError(t, h.Close()) - }() - - filter := pubsub.WithSubscriptionFilter(pubsub.NewAllowlistSubscriptionFilter(test.givenAllowedTopics...)) - ps, err := pubsub.NewGossipSub(ctx, h, filter) - require.NoError(t, err) - - app := fx.New( - fx.Provide( - func() host.Host { return h }, - func() dtypes.NetworkName { return test.givenNetworkName }, - func() dtypes.MinerAddress { return dtypes.MinerAddress(address.TestAddress) }, - func() dtypes.ProviderDataTransfer { return nil }, - func() *pubsub.PubSub { return ps }, - func() dtypes.MetadataDS { return datastore.NewMapDatastore() }, - modules.IndexProvider(config.IndexProviderConfig{ - Enable: true, - TopicName: test.givenConfiguredTopic, - EntriesChunkSize: 16384, - }), - ), - fx.Invoke(func(p provider.Interface) {}), - ) - err = app.Start(ctx) - - if test.wantErr == "" { - require.NoError(t, err) - err = app.Stop(ctx) - require.NoError(t, err) - } else { - require.True(t, strings.HasSuffix(err.Error(), test.wantErr)) - } - }) - } -} diff --git a/node/repo/fsrepo.go b/node/repo/fsrepo.go index ec35f8f30..1ffc1509f 100644 --- a/node/repo/fsrepo.go +++ b/node/repo/fsrepo.go @@ -185,30 +185,6 @@ func (worker) APIInfoEnvVars() (primary string, fallbacks []string, deprecated [ return "WORKER_API_INFO", nil, nil } -type curio struct{} - -var Curio curio - -func (curio) Type() string { - return "Curio" -} - -func (curio) Config() interface{} { - return &struct{}{} -} - -func (curio) APIFlags() []string { - return []string{"curio-api-url"} -} - -func (curio) RepoFlags() []string { - return []string{"curio-repo"} -} - -func (curio) APIInfoEnvVars() (primary string, fallbacks []string, deprecated []string) { - return "CURIO_API_INFO", nil, nil -} - var Wallet wallet type wallet struct { diff --git a/node/repo/imports/manager.go b/node/repo/imports/manager.go deleted file mode 100644 index a3648b6b0..000000000 --- a/node/repo/imports/manager.go +++ /dev/null @@ -1,275 +0,0 @@ -package imports - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - "strconv" - - "github.com/ipfs/go-cid" - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" - "github.com/ipfs/go-datastore/query" - logging "github.com/ipfs/go-log/v2" - "golang.org/x/xerrors" - - "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) { - ctx := context.TODO() - 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(ctx, 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) { - ctx := context.TODO() - meta, err := m.ds.Get(ctx, 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(ctx, 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 { - ctx := context.TODO() - meta, err := m.ds.Get(ctx, 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(ctx, id.dsKey(), meta) -} - -// List returns all import IDs known by this Manager. -func (m *Manager) List() ([]ID, error) { - ctx := context.TODO() - var keys []ID - - qres, err := m.ds.Query(ctx, 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) { - ctx := context.TODO() - - meta, err := m.ds.Get(ctx, 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 { - ctx := context.TODO() - if err := m.ds.Delete(ctx, 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/rpc.go b/node/rpc.go index 7a47d1b68..ede1b924c 100644 --- a/node/rpc.go +++ b/node/rpc.go @@ -2,8 +2,6 @@ package node import ( "context" - "encoding/json" - "fmt" "net" "net/http" _ "net/http/pprof" @@ -11,10 +9,7 @@ import ( "strconv" "time" - "github.com/google/uuid" "github.com/gorilla/mux" - "github.com/gorilla/websocket" - "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr/net" @@ -27,12 +22,10 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/v0api" "github.com/filecoin-project/lotus/api/v1api" - bstore "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/lib/rpcenc" "github.com/filecoin-project/lotus/metrics" "github.com/filecoin-project/lotus/metrics/proxy" "github.com/filecoin-project/lotus/node/impl" - "github.com/filecoin-project/lotus/node/impl/client" ) var rpclog = logging.Logger("rpc") @@ -98,33 +91,6 @@ func FullNodeHandler(a v1api.FullNode, permissioned bool, opts ...jsonrpc.Server serveRpc("/rpc/v1", fnapi) serveRpc("/rpc/v0", v0) - // Import handler - handleImportFunc := handleImport(a.(*impl.FullNodeAPI)) - handleExportFunc := handleExport(a.(*impl.FullNodeAPI)) - handleRemoteStoreFunc := handleRemoteStore(a.(*impl.FullNodeAPI)) - if permissioned { - importAH := &auth.Handler{ - Verify: a.AuthVerify, - Next: handleImportFunc, - } - m.Handle("/rest/v0/import", importAH) - exportAH := &auth.Handler{ - Verify: a.AuthVerify, - Next: handleExportFunc, - } - m.Handle("/rest/v0/export", exportAH) - - storeAH := &auth.Handler{ - Verify: a.AuthVerify, - Next: handleRemoteStoreFunc, - } - m.Handle("/rest/v0/store/{uuid}", storeAH) - } else { - m.HandleFunc("/rest/v0/import", handleImportFunc) - m.HandleFunc("/rest/v0/export", handleExportFunc) - m.HandleFunc("/rest/v0/store/{uuid}", handleRemoteStoreFunc) - } - // debugging m.Handle("/debug/metrics", metrics.Exporter()) m.Handle("/debug/pprof-set/block", handleFractionOpt("BlockProfileRate", runtime.SetBlockProfileRate)) @@ -191,61 +157,6 @@ func MinerHandler(a api.StorageMiner, permissioned bool) (http.Handler, error) { return rootMux, nil } -func handleImport(a *impl.FullNodeAPI) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - if r.Method != "PUT" { - w.WriteHeader(404) - return - } - if !auth.HasPerm(r.Context(), nil, api.PermWrite) { - w.WriteHeader(401) - _ = json.NewEncoder(w).Encode(struct{ Error string }{"unauthorized: missing write permission"}) - return - } - - c, err := a.ClientImportLocal(r.Context(), r.Body) - if err != nil { - w.WriteHeader(500) - _ = json.NewEncoder(w).Encode(struct{ Error string }{err.Error()}) - return - } - w.WriteHeader(200) - err = json.NewEncoder(w).Encode(struct{ Cid cid.Cid }{c}) - if err != nil { - rpclog.Errorf("/rest/v0/import: Writing response failed: %+v", err) - return - } - } -} - -func handleExport(a *impl.FullNodeAPI) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - if r.Method != "GET" { - w.WriteHeader(404) - return - } - if !auth.HasPerm(r.Context(), nil, api.PermWrite) { - w.WriteHeader(401) - _ = json.NewEncoder(w).Encode(struct{ Error string }{"unauthorized: missing write permission"}) - return - } - - var eref api.ExportRef - if err := json.Unmarshal([]byte(r.FormValue("export")), &eref); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - car := r.FormValue("car") == "true" - - err := a.ClientExportInto(r.Context(), eref, car, client.ExportDest{Writer: w}) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } -} - func handleFractionOpt(name string, setter func(int)) http.HandlerFunc { return func(rw http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { @@ -272,34 +183,3 @@ func handleFractionOpt(name string, setter func(int)) http.HandlerFunc { setter(fr) } } - -var upgrader = websocket.Upgrader{ - CheckOrigin: func(r *http.Request) bool { - return true - }, -} - -func handleRemoteStore(a *impl.FullNodeAPI) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - id, err := uuid.Parse(vars["uuid"]) - if err != nil { - http.Error(w, fmt.Sprintf("parse uuid: %s", err), http.StatusBadRequest) - return - } - - c, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Error(err) - w.WriteHeader(500) - return - } - - nstore := bstore.NewNetworkStoreWS(c) - if err := a.ApiBlockstoreAccessor.RegisterApiStore(id, nstore); err != nil { - log.Errorw("registering api bstore", "error", err) - _ = c.Close() - return - } - } -} diff --git a/scripts/curio.service b/scripts/curio.service deleted file mode 100644 index 967a788fa..000000000 --- a/scripts/curio.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=Curio -After=network.target -After=lotus-daemon.service - -[Service] -ExecStart=/usr/local/bin/curio run -Environment=GOLOG_FILE="/var/log/curio/curio.log" -Environment=GOLOG_LOG_FMT="json" -LimitNOFILE=1000000 -[Install] -WantedBy=multi-user.target diff --git a/scripts/generate-lotus-cli.py b/scripts/generate-lotus-cli.py index 14e85cf9f..f7ec70bbb 100644 --- a/scripts/generate-lotus-cli.py +++ b/scripts/generate-lotus-cli.py @@ -58,5 +58,3 @@ if __name__ == "__main__": generate_lotus_cli('lotus') generate_lotus_cli('lotus-miner') generate_lotus_cli('lotus-worker') - generate_lotus_cli('curio') - generate_lotus_cli('sptool') diff --git a/scripts/publish-checksums.sh b/scripts/publish-checksums.sh index 8e5cdfe35..ff80f388b 100755 --- a/scripts/publish-checksums.sh +++ b/scripts/publish-checksums.sh @@ -9,7 +9,7 @@ if [ -z "${GITHUB_TOKEN}" ]; then exit 1 fi -if [ "$GITHUB_REF" != refs/tags/* ]; then +if [[ "$GITHUB_REF" != refs/tags/* ]]; then echo "$GITHUB_REF is not a tag, publish failed" exit 1 fi diff --git a/scripts/version-check.sh b/scripts/version-check.sh index 20aeda4cc..4099c800e 100755 --- a/scripts/version-check.sh +++ b/scripts/version-check.sh @@ -32,7 +32,7 @@ function validate_lotus_version_matches_tag(){ _lotus_path=$1 -if [[ "$GITHUB_REF" != refs/tags/* ]]; then +if [[ "$GITHUB_REF" == refs/tags/* ]]; then validate_lotus_version_matches_tag "${_lotus_path}" "${GITHUB_REF#refs/tags/}" else echo "$GITHUB_REF is not a tag, skipping version check" diff --git a/storage/paths/db_index.go b/storage/paths/db_index.go index 499c425be..e6def4551 100644 --- a/storage/paths/db_index.go +++ b/storage/paths/db_index.go @@ -226,8 +226,8 @@ func (dbi *DBIndex) StorageAttach(ctx context.Context, si storiface.StorageInfo, // Insert storage id _, err = tx.Exec( - "INSERT INTO storage_path "+ - "Values($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, NOW(), $16, $17)", + "INSERT INTO storage_path (storage_id, urls, weight, max_storage, can_seal, can_store, groups, allow_to, allow_types, deny_types, capacity, available, fs_available, reserved, used, last_heartbeat, heartbeat_err, allow_miners, deny_miners)"+ + "Values($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, NOW(), NULL, $16, $17)", si.ID, strings.Join(si.URLs, ","), si.Weight, @@ -406,7 +406,7 @@ func (dbi *DBIndex) StorageDeclareSector(ctx context.Context, storageID storifac } } else { _, err = tx.Exec( - "INSERT INTO sector_location "+ + "INSERT INTO sector_location (miner_id, sector_num, sector_filetype, storage_id, is_primary)"+ "values($1, $2, $3, $4, $5)", s.Miner, s.Number, ft, storageID, primary) if err != nil { diff --git a/storage/paths/interface.go b/storage/paths/interface.go index d3dce8886..088e2340b 100644 --- a/storage/paths/interface.go +++ b/storage/paths/interface.go @@ -47,7 +47,7 @@ type Store interface { FsStat(ctx context.Context, id storiface.ID) (fsutil.FsStat, error) - Reserve(ctx context.Context, sid storiface.SectorRef, ft storiface.SectorFileType, storageIDs storiface.SectorPaths, overheadTab map[storiface.SectorFileType]int) (func(), error) + Reserve(ctx context.Context, sid storiface.SectorRef, ft storiface.SectorFileType, storageIDs storiface.SectorPaths, overheadTab map[storiface.SectorFileType]int, minFreePercentage float64) (func(), error) GenerateSingleVanillaProof(ctx context.Context, minerID abi.ActorID, si storiface.PostSectorChallenge, ppt abi.RegisteredPoStProof) ([]byte, error) GeneratePoRepVanillaProof(ctx context.Context, sr storiface.SectorRef, sealed, unsealed cid.Cid, ticket abi.SealRandomness, seed abi.InteractiveSealRandomness) ([]byte, error) diff --git a/storage/paths/local.go b/storage/paths/local.go index 244def377..07223ad53 100644 --- a/storage/paths/local.go +++ b/storage/paths/local.go @@ -36,6 +36,8 @@ type LocalStorage interface { const MetaFile = "sectorstore.json" +const MinFreeStoragePercentage = float64(0) + type Local struct { localStorage LocalStorage index SectorIndex @@ -460,13 +462,13 @@ func (st *Local) reportStorage(ctx context.Context) { } } -func (st *Local) Reserve(ctx context.Context, sid storiface.SectorRef, ft storiface.SectorFileType, storageIDs storiface.SectorPaths, overheadTab map[storiface.SectorFileType]int) (release func(), err error) { - var ssize abi.SectorSize - ssize, err = sid.ProofType.SectorSize() +func (st *Local) Reserve(ctx context.Context, sid storiface.SectorRef, ft storiface.SectorFileType, + storageIDs storiface.SectorPaths, overheadTab map[storiface.SectorFileType]int, minFreePercentage float64) (func(), error) { + ssize, err := sid.ProofType.SectorSize() if err != nil { return nil, err } - release = func() {} + release := func() {} st.localLk.Lock() @@ -501,10 +503,18 @@ func (st *Local) Reserve(ctx context.Context, sid storiface.SectorRef, ft storif resvOnDisk = overhead } - if stat.Available < overhead-resvOnDisk { + overheadOnDisk := overhead - resvOnDisk + + if stat.Available < overheadOnDisk { return nil, storiface.Err(storiface.ErrTempAllocateSpace, xerrors.Errorf("can't reserve %d bytes in '%s' (id:%s), only %d available", overhead, p.local, id, stat.Available)) } + freePercentag := (float64(stat.Available-overheadOnDisk) / float64(stat.Available)) * 100.0 + + if freePercentag < minFreePercentage { + return nil, storiface.Err(storiface.ErrTempAllocateSpace, xerrors.Errorf("can't reserve %d bytes in '%s' (id:%s), free disk percentage %f will be lower than minimum %f", overhead, p.local, id, freePercentag, minFreePercentage)) + } + resID := sectorFile{sid.ID, fileType} log.Debugw("reserve add", "id", id, "sector", sid, "fileType", fileType, "overhead", overhead, "reserved-before", p.reserved, "reserved-after", p.reserved+overhead) @@ -523,7 +533,7 @@ func (st *Local) Reserve(ctx context.Context, sid storiface.SectorRef, ft storif } } - return + return release, nil } // DoubleCallWrap wraps a function to make sure it's not called twice @@ -533,7 +543,7 @@ func DoubleCallWrap(f func()) func() { curStack := make([]byte, 20480) curStack = curStack[:runtime.Stack(curStack, false)] if len(stack) > 0 { - log.Warnf("double call from:\n%s\nBut originally from:", curStack, stack) + log.Warnf("double call from:\n%s\nBut originally from:\n%s", curStack, stack) return } stack = curStack diff --git a/storage/paths/mocks/store.go b/storage/paths/mocks/store.go index 1224e6b57..1303cf069 100644 --- a/storage/paths/mocks/store.go +++ b/storage/paths/mocks/store.go @@ -154,16 +154,16 @@ func (mr *MockStoreMockRecorder) RemoveCopies(arg0, arg1, arg2 interface{}) *gom } // Reserve mocks base method. -func (m *MockStore) Reserve(arg0 context.Context, arg1 storiface.SectorRef, arg2 storiface.SectorFileType, arg3 storiface.SectorPaths, arg4 map[storiface.SectorFileType]int) (func(), error) { +func (m *MockStore) Reserve(arg0 context.Context, arg1 storiface.SectorRef, arg2 storiface.SectorFileType, arg3 storiface.SectorPaths, arg4 map[storiface.SectorFileType]int, arg5 float64) (func(), error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Reserve", arg0, arg1, arg2, arg3, arg4) + ret := m.ctrl.Call(m, "Reserve", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(func()) ret1, _ := ret[1].(error) return ret0, ret1 } // Reserve indicates an expected call of Reserve. -func (mr *MockStoreMockRecorder) Reserve(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockStoreMockRecorder) Reserve(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reserve", reflect.TypeOf((*MockStore)(nil).Reserve), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reserve", reflect.TypeOf((*MockStore)(nil).Reserve), arg0, arg1, arg2, arg3, arg4, arg5) } diff --git a/storage/paths/remote.go b/storage/paths/remote.go index ab2754863..2cad97537 100644 --- a/storage/paths/remote.go +++ b/storage/paths/remote.go @@ -177,7 +177,7 @@ func (r *Remote) AcquireSector(ctx context.Context, s storiface.SectorRef, exist // If any path types weren't found in local storage, try fetching them // First reserve storage - releaseStorage, err := r.local.Reserve(ctx, s, toFetch, fetchIDs, overheadTable) + releaseStorage, err := r.local.Reserve(ctx, s, toFetch, fetchIDs, overheadTable, MinFreeStoragePercentage) if err != nil { return storiface.SectorPaths{}, storiface.SectorPaths{}, xerrors.Errorf("reserving storage space: %w", err) } @@ -812,7 +812,7 @@ func (r *Remote) ReaderSeq(ctx context.Context, s storiface.SectorRef, ft storif return nil, xerrors.Errorf("failed to read sector %v from remote(%d): %w", s, ft, storiface.ErrSectorNotFound) } -func (r *Remote) Reserve(ctx context.Context, sid storiface.SectorRef, ft storiface.SectorFileType, storageIDs storiface.SectorPaths, overheadTab map[storiface.SectorFileType]int) (func(), error) { +func (r *Remote) Reserve(ctx context.Context, sid storiface.SectorRef, ft storiface.SectorFileType, storageIDs storiface.SectorPaths, overheadTab map[storiface.SectorFileType]int, minFreePercentage float64) (func(), error) { log.Warnf("reserve called on remote store, sectorID: %v", sid.ID) return func() { diff --git a/storage/pipeline/numassign.go b/storage/pipeline/numassign.go index 60dddec98..aaeb023ed 100644 --- a/storage/pipeline/numassign.go +++ b/storage/pipeline/numassign.go @@ -212,7 +212,7 @@ func (m *Sealing) NumReserve(ctx context.Context, name string, reserving bitfiel return m.numReserveLocked(ctx, name, reserving, force) } -// NumReserve creates a new sector reservation +// numReserveLocked creates a new sector reservation func (m *Sealing) numReserveLocked(ctx context.Context, name string, reserving bitfield.BitField, force bool) error { rk, err := reservationKey(name) if err != nil { diff --git a/storage/pipeline/piece/piece_info.go b/storage/pipeline/piece/piece_info.go index 48e15751a..7c34e94e7 100644 --- a/storage/pipeline/piece/piece_info.go +++ b/storage/pipeline/piece/piece_info.go @@ -170,6 +170,15 @@ func (ds *PieceDealInfo) String() string { } } +func (ds *PieceDealInfo) Size() abi.PaddedPieceSize { + switch { + case ds.isBuiltinMarketDeal(): + return ds.DealProposal.PieceSize + default: + return ds.PieceActivationManifest.Size + } +} + func (ds *PieceDealInfo) KeepUnsealedRequested() bool { return ds.KeepUnsealed } diff --git a/storage/pipeline/states_sealing.go b/storage/pipeline/states_sealing.go index 81ee85853..795cabdb3 100644 --- a/storage/pipeline/states_sealing.go +++ b/storage/pipeline/states_sealing.go @@ -33,6 +33,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/lib/filler" "github.com/filecoin-project/lotus/storage/pipeline/lib/nullreader" "github.com/filecoin-project/lotus/storage/sealer/storiface" ) @@ -88,7 +89,7 @@ func (m *Sealing) handlePacking(ctx statemachine.Context, sector SectorInfo) err return xerrors.Errorf("too much data in sector: %d > %d", allocated, ubytes) } - fillerSizes, err := fillersFromRem(ubytes - allocated) + fillerSizes, err := filler.FillersFromRem(ubytes - allocated) if err != nil { return err } diff --git a/storage/pipeline/utils.go b/storage/pipeline/utils.go index ac519b6ac..373859f6d 100644 --- a/storage/pipeline/utils.go +++ b/storage/pipeline/utils.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "math/bits" "github.com/ipfs/go-cid" "golang.org/x/xerrors" @@ -18,42 +17,6 @@ import ( "github.com/filecoin-project/lotus/storage/pipeline/sealiface" ) -func fillersFromRem(in abi.UnpaddedPieceSize) ([]abi.UnpaddedPieceSize, error) { - // Convert to in-sector bytes for easier math: - // - // Sector size to user bytes ratio is constant, e.g. for 1024B we have 1016B - // of user-usable data. - // - // (1024/1016 = 128/127) - // - // Given that we can get sector size by simply adding 1/127 of the user - // bytes - // - // (we convert to sector bytes as they are nice round binary numbers) - - toFill := uint64(in + (in / 127)) - - // We need to fill the sector with pieces that are powers of 2. Conveniently - // computers store numbers in binary, which means we can look at 1s to get - // all the piece sizes we need to fill the sector. It also means that number - // of pieces is the number of 1s in the number of remaining bytes to fill - out := make([]abi.UnpaddedPieceSize, bits.OnesCount64(toFill)) - for i := range out { - // Extract the next lowest non-zero bit - next := bits.TrailingZeros64(toFill) - psize := uint64(1) << next - // e.g: if the number is 0b010100, psize will be 0b000100 - - // set that bit to 0 by XORing it, so the next iteration looks at the - // next bit - toFill ^= psize - - // Add the piece size to the list of pieces we need to create - out[i] = abi.PaddedPieceSize(psize).Unpadded() - } - return out, nil -} - func (m *Sealing) ListSectors() ([]SectorInfo, error) { var sectors []SectorInfo if err := m.sectors.List(§ors); err != nil { diff --git a/storage/sealer/ffiwrapper/sealer_cgo.go b/storage/sealer/ffiwrapper/sealer_cgo.go index f16130331..80f06ad0c 100644 --- a/storage/sealer/ffiwrapper/sealer_cgo.go +++ b/storage/sealer/ffiwrapper/sealer_cgo.go @@ -1421,6 +1421,13 @@ func GenerateUnsealedCID(proofType abi.RegisteredSealProof, pieces []abi.PieceIn return ffi.GenerateUnsealedCID(proofType, allPieces) } +func (sb *Sealer) GenerateSingleVanillaProof( + replica ffi.PrivateSectorInfo, + challenges []uint64, +) ([]byte, error) { + return ffi.GenerateSingleVanillaProof(replica, challenges) +} + func (sb *Sealer) GenerateWinningPoStWithVanilla(ctx context.Context, proofType abi.RegisteredPoStProof, minerID abi.ActorID, randomness abi.PoStRandomness, vanillas [][]byte) ([]proof.PoStProof, error) { return ffi.GenerateWinningPoStWithVanilla(proofType, minerID, randomness, vanillas) } diff --git a/storage/sealer/mock/mock.go b/storage/sealer/mock/mock.go index e33be8477..958a246a7 100644 --- a/storage/sealer/mock/mock.go +++ b/storage/sealer/mock/mock.go @@ -13,7 +13,6 @@ import ( logging "github.com/ipfs/go-log/v2" "golang.org/x/xerrors" - "github.com/filecoin-project/dagstore/mount" commpffi "github.com/filecoin-project/go-commp-utils/ffiwrapper" commcid "github.com/filecoin-project/go-fil-commcid" "github.com/filecoin-project/go-state-types/abi" @@ -435,7 +434,7 @@ func (mgr *SectorMgr) GenerateWindowPoStWithVanilla(ctx context.Context, proofTy panic("implement me") } -func (mgr *SectorMgr) ReadPiece(ctx context.Context, sector storiface.SectorRef, offset storiface.UnpaddedByteIndex, size abi.UnpaddedPieceSize, ticket abi.SealRandomness, unsealed cid.Cid) (mount.Reader, bool, error) { +func (mgr *SectorMgr) ReadPiece(ctx context.Context, sector storiface.SectorRef, offset storiface.UnpaddedByteIndex, size abi.UnpaddedPieceSize, ticket abi.SealRandomness, unsealed cid.Cid) (storiface.Reader, bool, error) { off := storiface.UnpaddedByteIndex(0) var piece cid.Cid diff --git a/storage/sealer/piece_provider.go b/storage/sealer/piece_provider.go index 0e992b679..3d177665a 100644 --- a/storage/sealer/piece_provider.go +++ b/storage/sealer/piece_provider.go @@ -10,7 +10,6 @@ import ( pool "github.com/libp2p/go-buffer-pool" "golang.org/x/xerrors" - "github.com/filecoin-project/dagstore/mount" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/storage/paths" @@ -29,7 +28,7 @@ type PieceProvider interface { // default in most cases, but this might matter with future PoRep) // startOffset is added to the pieceOffset to get the starting reader offset. // The number of bytes that can be read is pieceSize-startOffset - ReadPiece(ctx context.Context, sector storiface.SectorRef, pieceOffset storiface.UnpaddedByteIndex, pieceSize abi.UnpaddedPieceSize, ticket abi.SealRandomness, unsealed cid.Cid) (mount.Reader, bool, error) + ReadPiece(ctx context.Context, sector storiface.SectorRef, pieceOffset storiface.UnpaddedByteIndex, pieceSize abi.UnpaddedPieceSize, ticket abi.SealRandomness, unsealed cid.Cid) (storiface.Reader, bool, error) IsUnsealed(ctx context.Context, sector storiface.SectorRef, offset storiface.UnpaddedByteIndex, size abi.UnpaddedPieceSize) (bool, error) } @@ -73,7 +72,7 @@ func (p *pieceProvider) IsUnsealed(ctx context.Context, sector storiface.SectorR // It will NOT try to schedule an Unseal of a sealed sector file for the read. // // Returns a nil reader if the piece does NOT exist in any unsealed file or there is no unsealed file for the given sector on any of the workers. -func (p *pieceProvider) tryReadUnsealedPiece(ctx context.Context, pc cid.Cid, sector storiface.SectorRef, pieceOffset storiface.UnpaddedByteIndex, pieceSize abi.UnpaddedPieceSize) (mount.Reader, error) { +func (p *pieceProvider) tryReadUnsealedPiece(ctx context.Context, pc cid.Cid, sector storiface.SectorRef, pieceOffset storiface.UnpaddedByteIndex, pieceSize abi.UnpaddedPieceSize) (storiface.Reader, error) { // acquire a lock purely for reading unsealed sectors ctx, cancel := context.WithCancel(ctx) if err := p.index.StorageLock(ctx, sector.ID, storiface.FTUnsealed, storiface.FTNone); err != nil { @@ -169,7 +168,7 @@ var _ io.Closer = funcCloser(nil) // If we do NOT have an existing unsealed file containing the given piece thus causing us to schedule an Unseal, // the returned boolean parameter will be set to true. // If we have an existing unsealed file containing the given piece, the returned boolean will be set to false. -func (p *pieceProvider) ReadPiece(ctx context.Context, sector storiface.SectorRef, pieceOffset storiface.UnpaddedByteIndex, size abi.UnpaddedPieceSize, ticket abi.SealRandomness, unsealed cid.Cid) (mount.Reader, bool, error) { +func (p *pieceProvider) ReadPiece(ctx context.Context, sector storiface.SectorRef, pieceOffset storiface.UnpaddedByteIndex, size abi.UnpaddedPieceSize, ticket abi.SealRandomness, unsealed cid.Cid) (storiface.Reader, bool, error) { if err := pieceOffset.Valid(); err != nil { return nil, false, xerrors.Errorf("pieceOffset is not valid: %w", err) } @@ -224,3 +223,5 @@ func (p *pieceProvider) ReadPiece(ctx context.Context, sector storiface.SectorRe return r, uns, nil } + +var _ storiface.Reader = &pieceReader{} diff --git a/storage/sealer/piece_reader.go b/storage/sealer/piece_reader.go index 7a7cd1841..37fb4488c 100644 --- a/storage/sealer/piece_reader.go +++ b/storage/sealer/piece_reader.go @@ -12,7 +12,6 @@ import ( "go.opencensus.io/tag" "golang.org/x/xerrors" - "github.com/filecoin-project/dagstore/mount" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/metrics" @@ -303,5 +302,3 @@ func (p *pieceReader) readInto(b []byte, off int64) (n int, err error) { return n, cerr } - -var _ mount.Reader = (*pieceReader)(nil) diff --git a/storage/sealer/proofpaths/cachefiles.go b/storage/sealer/proofpaths/cachefiles.go index 628ab1585..cbb6839ce 100644 --- a/storage/sealer/proofpaths/cachefiles.go +++ b/storage/sealer/proofpaths/cachefiles.go @@ -53,3 +53,11 @@ func SDRLayers(spt abi.RegisteredSealProof) (int, error) { return 0, fmt.Errorf("unsupported proof type: %v", spt) } } + +func IsTreeRCFile(baseName string) bool { + return IsFileTreeRLast(baseName) || IsFileTreeC(baseName) +} + +func IsTreeDFile(baseName string) bool { + return IsFileTreeD(baseName) +} diff --git a/storage/sealer/storiface/storage.go b/storage/sealer/storiface/storage.go index 143c3b5d5..91ab12805 100644 --- a/storage/sealer/storiface/storage.go +++ b/storage/sealer/storiface/storage.go @@ -13,6 +13,16 @@ import ( type Data = io.Reader +// Reader is a fully-featured Reader. It is the +// union of the standard IO sequential access method (Read), with seeking +// ability (Seek), as well random access (ReadAt). +type Reader interface { + io.Closer + io.Reader + io.ReaderAt + io.Seeker +} + type SectorRef struct { ID abi.SectorID ProofType abi.RegisteredSealProof diff --git a/storage/sealer/worker_local.go b/storage/sealer/worker_local.go index 417a15e62..82f939b10 100644 --- a/storage/sealer/worker_local.go +++ b/storage/sealer/worker_local.go @@ -150,19 +150,19 @@ type localWorkerPathProvider struct { } func (l *localWorkerPathProvider) AcquireSector(ctx context.Context, sector storiface.SectorRef, existing storiface.SectorFileType, allocate storiface.SectorFileType, sealing storiface.PathType) (storiface.SectorPaths, func(), error) { - paths, storageIDs, err := l.w.storage.AcquireSector(ctx, sector, existing, allocate, sealing, l.op) + spaths, storageIDs, err := l.w.storage.AcquireSector(ctx, sector, existing, allocate, sealing, l.op) if err != nil { return storiface.SectorPaths{}, nil, err } - releaseStorage, err := l.w.localStore.Reserve(ctx, sector, allocate, storageIDs, storiface.FSOverheadSeal) + releaseStorage, err := l.w.localStore.Reserve(ctx, sector, allocate, storageIDs, storiface.FSOverheadSeal, paths.MinFreeStoragePercentage) if err != nil { return storiface.SectorPaths{}, nil, xerrors.Errorf("reserving storage space: %w", err) } - log.Debugf("acquired sector %d (e:%d; a:%d): %v", sector, existing, allocate, paths) + log.Debugf("acquired sector %d (e:%d; a:%d): %v", sector, existing, allocate, spaths) - return paths, func() { + return spaths, func() { releaseStorage() for _, fileType := range storiface.PathTypes { @@ -675,6 +675,8 @@ func (l *LocalWorker) GenerateWindowPoStAdv(ctx context.Context, ppt abi.Registe go func(i int, s storiface.PostSectorChallenge) { defer wg.Done() + ctx := ctx + defer func() { if l.challengeThrottle != nil { <-l.challengeThrottle

2R?e3Z21bPu@atPY6kN2XRM( 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≠H_%8N~s`d1sN|EO*tNgJ>@f(=WIAa_#;*gbk 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ð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