From aa2b8f0ac7a394f9413e229b601cbf25e4e6bd4b Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 9 Jun 2020 18:46:08 -0500 Subject: [PATCH 01/18] Support POST in file transfer This adds a post bool that can be used to POST content instead of GET or PUT it. --- src/libstore/filetransfer.cc | 2 ++ src/libstore/filetransfer.hh | 1 + 2 files changed, 3 insertions(+) diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc index b96fd084d9c..c907f628b26 100644 --- a/src/libstore/filetransfer.cc +++ b/src/libstore/filetransfer.cc @@ -282,6 +282,8 @@ struct curlFileTransfer : public FileTransfer if (request.head) curl_easy_setopt(req, CURLOPT_NOBODY, 1); + else if (request.post) + curl_easy_setopt(req, CURLOPT_POST, 1); if (request.data) { curl_easy_setopt(req, CURLOPT_UPLOAD, 1L); diff --git a/src/libstore/filetransfer.hh b/src/libstore/filetransfer.hh index 0fbda4c22ab..6f05c929c08 100644 --- a/src/libstore/filetransfer.hh +++ b/src/libstore/filetransfer.hh @@ -39,6 +39,7 @@ struct FileTransferRequest std::string expectedETag; bool verifyTLS = true; bool head = false; + bool post = false; size_t tries = fileTransferSettings.tries; unsigned int baseRetryTimeMs = 250; ActivityId parentAct; From f7f27a28756d83f653e70264f272acbf007316da Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 9 Jun 2020 18:46:35 -0500 Subject: [PATCH 02/18] Support data with POST in file transfer need to use a multipart form to correctly insert the data. this mirrors how curl -Ffile=asdf works. --- src/libstore/filetransfer.cc | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc index c907f628b26..353c3d098ad 100644 --- a/src/libstore/filetransfer.cc +++ b/src/libstore/filetransfer.cc @@ -286,10 +286,18 @@ struct curlFileTransfer : public FileTransfer curl_easy_setopt(req, CURLOPT_POST, 1); if (request.data) { - curl_easy_setopt(req, CURLOPT_UPLOAD, 1L); - curl_easy_setopt(req, CURLOPT_READFUNCTION, readCallbackWrapper); - curl_easy_setopt(req, CURLOPT_READDATA, this); - curl_easy_setopt(req, CURLOPT_INFILESIZE_LARGE, (curl_off_t) request.data->length()); + if (request.post) { + // based off of https://curl.haxx.se/libcurl/c/postit2.html + curl_mime *form = curl_mime_init(req); + curl_mimepart *field = curl_mime_addpart(form); + curl_mime_data(field, request.data->data(), request.data->length()); + curl_easy_setopt(req, CURLOPT_MIMEPOST, form); + } else { + curl_easy_setopt(req, CURLOPT_UPLOAD, 1L); + curl_easy_setopt(req, CURLOPT_READFUNCTION, readCallbackWrapper); + curl_easy_setopt(req, CURLOPT_READDATA, this); + curl_easy_setopt(req, CURLOPT_INFILESIZE_LARGE, (curl_off_t) request.data->length()); + } } if (request.verifyTLS) { From aa0e736b007230874e9742b82627c85cd5050c2e Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 9 Jun 2020 18:49:42 -0500 Subject: [PATCH 03/18] Consolidate ipfs-binary-cache-store.cc to one file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes a number of changes: - Use ipfs:// and ipns:// for URI, this is a proper URI and now documented by ipfs - Remove ipfs.hh, move everything into store - Store daemonUri, which is the, replacing individual API settings - Remove support for gateways - Remove nar info cache - ipfs should do all of the caching for us - Only retry getFile once - if it doesn’t work the ipfs daemon is probably down - Use /object/stat to check if file exists instead of cat --- src/libstore/ipfs-binary-cache-store.cc | 101 +++++++----------------- src/libstore/ipfs.hh | 32 -------- 2 files changed, 30 insertions(+), 103 deletions(-) delete mode 100644 src/libstore/ipfs.hh diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index 5a2d4b591cf..69bf38dcb72 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -3,7 +3,6 @@ #include "binary-cache-store.hh" #include "filetransfer.hh" #include "nar-info-disk-cache.hh" -#include "ipfs.hh" namespace nix { @@ -15,28 +14,9 @@ class IPFSBinaryCacheStore : public BinaryCacheStore private: std::string cacheUri; + std::string daemonUri; - /* Host where a IPFS API can be reached (usually localhost) */ - std::string ipfsAPIHost; - /* Port where a IPFS API can be reached (usually 5001) */ - uint16_t ipfsAPIPort; - /* Whether to use a IPFS Gateway instead of the API */ - bool useIpfsGateway; - /* Where to find a IPFS Gateway */ - std::string ipfsGatewayURL; - - std::string constructIPFSRequest(const std::string & path) { - std::string uri; - std::string ipfsPath = cacheUri + "/" + path; - if (useIpfsGateway == false) { - uri = ipfs::buildAPIURL(ipfsAPIHost, ipfsAPIPort) + - "/cat" + - ipfs::buildQuery({{"arg", ipfsPath}}); - } else { - uri = ipfsGatewayURL + ipfsPath; - } - return uri; - } + std::string ipfsPath; public: @@ -44,19 +24,20 @@ class IPFSBinaryCacheStore : public BinaryCacheStore const Params & params, const Path & _cacheUri) : BinaryCacheStore(params) , cacheUri(_cacheUri) - , ipfsAPIHost(get(params, "host").value_or("127.0.0.1")) - , ipfsAPIPort(std::stoi(get(params, "port").value_or("5001"))) - , useIpfsGateway(get(params, "use_gateway").value_or("0") == "1") - , ipfsGatewayURL(get(params, "gateway").value_or("https://ipfs.io")) { if (cacheUri.back() == '/') cacheUri.pop_back(); - /* - * A cache is still useful since the IPFS API or - * gateway may have a higher latency when not running on - * localhost - */ - diskCache = getNarInfoDiskCache(); + + if (hasPrefix(cacheUri, "ipfs://")) + ipfsPath = "/ipfs/" + std::string(cacheUri, 7); + else if (hasPrefix(cacheUri, "ipns://")) + ipfsPath = "/ipns/" + std::string(cacheUri, 7); + else + throw Error("unknown IPFS URI '%s'", cacheUri); + + std::string ipfsAPIHost(get(params, "host").value_or("127.0.0.1")); + std::string ipfsAPIPort(get(params, "port").value_or("5001")); + daemonUri = "http://" + ipfsAPIHost + ":" + ipfsAPIPort; } std::string getUri() override @@ -66,42 +47,28 @@ class IPFSBinaryCacheStore : public BinaryCacheStore void init() override { - if (auto cacheInfo = diskCache->cacheExists(getUri())) { - wantMassQuery.setDefault(cacheInfo->wantMassQuery ? "true" : "false"); - priority.setDefault(fmt("%d", cacheInfo->priority)); - } else { - try { - BinaryCacheStore::init(); - } catch (UploadToIPFS &) { - throw Error(format("‘%s’ does not appear to be a binary cache") % cacheUri); - } - diskCache->createCache(cacheUri, storeDir, wantMassQuery, priority); } + BinaryCacheStore::init(); } protected: bool fileExists(const std::string & path) override { - /* - * TODO: Try a local mount first, best to share code with - * LocalBinaryCacheStore - */ + auto uri = daemonUri + "/api/v0/object/stat?arg=" + getFileTransfer()->urlEncode(ipfsPath + "/" + path); - /* TODO: perform ipfs ls instead instead of trying to fetch it */ - auto uri = constructIPFSRequest(path); + FileTransferRequest request(uri); + request.post = true; + request.tries = 1; try { - FileTransferRequest request(uri); - //request.showProgress = FileTransferRequest::no; - request.tries = 5; - if (useIpfsGateway) - request.head = true; - getFileTransfer()->download(request); - return true; + auto res = getFileTransfer()->download(request); + auto json = nlohmann::json::parse(*res.data); + + return json.find("Hash") != json.end(); } catch (FileTransferError & e) { - if (e.error == FileTransfer::NotFound) - return false; - throw; + // probably should verify this is a not found error but + // ipfs gives us a 500 + return false; } } @@ -113,14 +80,10 @@ class IPFSBinaryCacheStore : public BinaryCacheStore void getFile(const std::string & path, Callback> callback) noexcept override { - /* - * TODO: Try local mount first, best to share code with - * LocalBinaryCacheStore - */ - auto uri = constructIPFSRequest(path); + auto uri = daemonUri + "/api/v0/cat?arg=" + getFileTransfer()->urlEncode(ipfsPath + "/" + path); + FileTransferRequest request(uri); - //request.showProgress = FileTransferRequest::no; - request.tries = 8; + request.tries = 1; auto callbackPtr = std::make_shared(std::move(callback)); @@ -145,12 +108,8 @@ static RegisterStoreImplementation regStore([]( const std::string & uri, const Store::Params & params) -> std::shared_ptr { - /* - * TODO: maybe use ipfs:/ fs:/ipfs/ - * https://github.com/ipfs/go-ipfs/issues/1678#issuecomment-157478515 - */ - if (uri.substr(0, strlen("/ipfs/")) != "/ipfs/" && - uri.substr(0, strlen("/ipns/")) != "/ipns/") + if (uri.substr(0, strlen("ipfs://")) != "ipfs://" && + uri.substr(0, strlen("ipns://")) != "ipns://") return 0; auto store = std::make_shared(params, uri); store->init(); diff --git a/src/libstore/ipfs.hh b/src/libstore/ipfs.hh deleted file mode 100644 index 00e99dbbf09..00000000000 --- a/src/libstore/ipfs.hh +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include - -#include "types.hh" -#include "filetransfer.hh" - -namespace nix { -namespace ipfs { - -MakeError (CommandError, Error); - -inline std::string buildAPIURL(const std::string & host, - uint16_t port = 5001, - const std::string & version = "v0") -{ - return "http://" + host + ":" + std::to_string(port) + "/api/" + version; -} - -inline std::string buildQuery(const std::vector> & params = {}) { - std::string query = "?stream-channels=true&json=true&encoding=json"; - for (auto& param : params) { - std::string key = getFileTransfer()->urlEncode(param.first); - std::string value = getFileTransfer()->urlEncode(param.second); - query += "&" + key + "=" + value; - } - return query; -} - -} -} From adec10c8d80ae78d39e29ee8380afbe0934fe025 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 9 Jun 2020 18:52:40 -0500 Subject: [PATCH 04/18] Use /api/v0/version to check if ipfs daemon is running To check if IPFS daemon works, we need to do a test that should alway work. In the future, we may want to have a min IPFS daemon that we support, but for now any version will work. --- src/libstore/ipfs-binary-cache-store.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index 69bf38dcb72..debdbd5cdcb 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -1,4 +1,5 @@ #include +#include #include "binary-cache-store.hh" #include "filetransfer.hh" @@ -38,6 +39,15 @@ class IPFSBinaryCacheStore : public BinaryCacheStore std::string ipfsAPIHost(get(params, "host").value_or("127.0.0.1")); std::string ipfsAPIPort(get(params, "port").value_or("5001")); daemonUri = "http://" + ipfsAPIHost + ":" + ipfsAPIPort; + + // Check the IPFS daemon is running + FileTransferRequest request(daemonUri + "/api/v0/version"); + request.post = true; + request.tries = 1; + auto res = getFileTransfer()->download(request); + auto versionInfo = nlohmann::json::parse(*res.data); + if (versionInfo.find("Version") == versionInfo.end()) + throw Error("daemon for IPFS is not running properly"); } std::string getUri() override From e39ce69c379e5ab0a47d1e399b5236540e8f883e Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 9 Jun 2020 18:55:52 -0500 Subject: [PATCH 05/18] =?UTF-8?q?Throw=20error=20if=20/ipfs/=20is=20used?= =?UTF-8?q?=20but=20path=20doesn=E2=80=99t=20exist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is okay for /ipns/ because we can always mutate it, but it’s a hard error for ipfs. --- src/libstore/ipfs-binary-cache-store.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index debdbd5cdcb..afeea684ea7 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -48,6 +48,10 @@ class IPFSBinaryCacheStore : public BinaryCacheStore auto versionInfo = nlohmann::json::parse(*res.data); if (versionInfo.find("Version") == versionInfo.end()) throw Error("daemon for IPFS is not running properly"); + + // root should already exist + if (!fileExists("") && hasPrefix(ipfsPath, "/ipfs/")) + throw Error("path '%s' is not found", ipfsPath); } std::string getUri() override From f33f8e6dae5a470aeda681144ab3c3e83f9c4938 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 9 Jun 2020 18:57:02 -0500 Subject: [PATCH 06/18] Support uploading with /ipns/ in ipfs-binary-cache-store This is really slow, but is useful to test things. It does a few API calls to work: - /api/v0/add : to add the new file - /api/v0/object/patch/add-link : to insert the file into unixfs - /api/v0/name/publish : to publish the new ipfs object to ipns The last step is the slow part since we need to wait for it to clear. --- src/libstore/ipfs-binary-cache-store.cc | 40 ++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index afeea684ea7..1203577f27d 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -88,7 +88,45 @@ class IPFSBinaryCacheStore : public BinaryCacheStore void upsertFile(const std::string & path, const std::string & data, const std::string & mimeType) override { - throw UploadToIPFS("uploading to an IPFS binary cache is not supported"); + if (hasPrefix(ipfsPath, "/ipfs/")) + throw Error("%s is immutable, cannot modify", ipfsPath); + + // TODO: use callbacks + + auto req1 = FileTransferRequest(daemonUri + "/api/v0/add"); + req1.data = std::make_shared(data); + req1.post = true; + req1.tries = 1; + try { + auto res1 = getFileTransfer()->upload(req1); + auto json1 = nlohmann::json::parse(*res1.data); + + auto addedPath = "/ipfs/" + (std::string) json1["Hash"]; + + auto uri1 = daemonUri + "/api/v0/object/patch/add-link?create=true"; + uri1 += "&arg=" + getFileTransfer()->urlEncode(ipfsPath); + uri1 += "&arg=" + getFileTransfer()->urlEncode(path); + uri1 += "&arg=" + getFileTransfer()->urlEncode(addedPath); + + auto req2 = FileTransferRequest(uri1); + req2.post = true; + req2.tries = 1; + auto res2 = getFileTransfer()->download(req2); + auto json2 = nlohmann::json::parse(*res2.data); + + auto newRoot = json2["Hash"]; + + auto uri2 = daemonUri + "/api/v0/name/publish?arg=" + getFileTransfer()->urlEncode(newRoot); + uri2 += "&key=" + std::string(ipfsPath, 6); + + // WARNING: this can be really slow + auto req3 = FileTransferRequest(uri2); + req3.post = true; + req3.tries = 1; + getFileTransfer()->download(req3); + } catch (FileTransferError & e) { + throw UploadToIPFS("while uploading to IPFS binary cache at '%s': %s", cacheUri, e.msg()); + } } void getFile(const std::string & path, From 37393d188d76e63d22f579d0288560563124981d Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 9 Jun 2020 18:58:47 -0500 Subject: [PATCH 07/18] =?UTF-8?q?Create=20file=20if=20nix-cache-info=20doe?= =?UTF-8?q?sn=E2=80=99t=20exist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/libstore/ipfs-binary-cache-store.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index 1203577f27d..238a8078e24 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -61,6 +61,9 @@ class IPFSBinaryCacheStore : public BinaryCacheStore void init() override { + std::string cacheInfoFile = "nix-cache-info"; + if (!fileExists(cacheInfoFile)) { + upsertFile(cacheInfoFile, "StoreDir: " + storeDir + "\n", "text/x-nix-cache-info"); } BinaryCacheStore::init(); } From a9343ed69e53ec4dfadfa25894842bb987c40ad8 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 9 Jun 2020 19:42:57 -0500 Subject: [PATCH 08/18] Use POST for IPFS getFile --- src/libstore/ipfs-binary-cache-store.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index 238a8078e24..46257c9e127 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -138,6 +138,7 @@ class IPFSBinaryCacheStore : public BinaryCacheStore auto uri = daemonUri + "/api/v0/cat?arg=" + getFileTransfer()->urlEncode(ipfsPath + "/" + path); FileTransferRequest request(uri); + request.post = true; request.tries = 1; auto callbackPtr = std::make_shared(std::move(callback)); From ab712b4b4d45d5017722764bcaeb20b28ff37300 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 9 Jun 2020 19:53:13 -0500 Subject: [PATCH 09/18] Ignore 500 errors in ipfs binary cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit unfortunately we don’t have a good way to handle this for now, avoid rethrowing and just accept 500 --- src/libstore/ipfs-binary-cache-store.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index 46257c9e127..74a58a5386d 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -148,9 +148,7 @@ class IPFSBinaryCacheStore : public BinaryCacheStore try { (*callbackPtr)(result.get().data); } catch (FileTransferError & e) { - if (e.error == FileTransfer::NotFound || e.error == FileTransfer::Forbidden) - return (*callbackPtr)(std::shared_ptr()); - callbackPtr->rethrow(); + return (*callbackPtr)(std::shared_ptr()); } catch (...) { callbackPtr->rethrow(); } From 3530ab10b02aa75c66bf5d619f9f1aacd539ac73 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 10 Jun 2020 10:46:02 -0500 Subject: [PATCH 10/18] Use offline to speed up IPFS publish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this doesn’t wait for the final response, but updates ipns internally. --- src/libstore/ipfs-binary-cache-store.cc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index 74a58a5386d..df13013e8c6 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -72,7 +72,7 @@ class IPFSBinaryCacheStore : public BinaryCacheStore bool fileExists(const std::string & path) override { - auto uri = daemonUri + "/api/v0/object/stat?arg=" + getFileTransfer()->urlEncode(ipfsPath + "/" + path); + auto uri = daemonUri + "/api/v0/object/stat?offline=true&arg=" + getFileTransfer()->urlEncode(ipfsPath + "/" + path); FileTransferRequest request(uri); request.post = true; @@ -106,7 +106,7 @@ class IPFSBinaryCacheStore : public BinaryCacheStore auto addedPath = "/ipfs/" + (std::string) json1["Hash"]; - auto uri1 = daemonUri + "/api/v0/object/patch/add-link?create=true"; + auto uri1 = daemonUri + "/api/v0/object/patch/add-link?offline=true&create=true"; uri1 += "&arg=" + getFileTransfer()->urlEncode(ipfsPath); uri1 += "&arg=" + getFileTransfer()->urlEncode(path); uri1 += "&arg=" + getFileTransfer()->urlEncode(addedPath); @@ -119,10 +119,9 @@ class IPFSBinaryCacheStore : public BinaryCacheStore auto newRoot = json2["Hash"]; - auto uri2 = daemonUri + "/api/v0/name/publish?arg=" + getFileTransfer()->urlEncode(newRoot); + auto uri2 = daemonUri + "/api/v0/name/publish?offline=true&arg=" + getFileTransfer()->urlEncode(newRoot); uri2 += "&key=" + std::string(ipfsPath, 6); - // WARNING: this can be really slow auto req3 = FileTransferRequest(uri2); req3.post = true; req3.tries = 1; @@ -135,7 +134,7 @@ class IPFSBinaryCacheStore : public BinaryCacheStore void getFile(const std::string & path, Callback> callback) noexcept override { - auto uri = daemonUri + "/api/v0/cat?arg=" + getFileTransfer()->urlEncode(ipfsPath + "/" + path); + auto uri = daemonUri + "/api/v0/cat?offline=true&arg=" + getFileTransfer()->urlEncode(ipfsPath + "/" + path); FileTransferRequest request(uri); request.post = true; From c135e05a5eb7074bbb156df6d108dbe9ce9809e5 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 10 Jun 2020 11:39:31 -0500 Subject: [PATCH 11/18] Keep track of in progress upsert To avoid race conditions while doing /object/patch/add-link & /name/publish, we need to lock some state. This adds a bool for inProgressUpsert so that we can know when other threads are also trying to publish to IPNS. --- src/libstore/ipfs-binary-cache-store.cc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index df13013e8c6..270624a1eaf 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -19,6 +19,12 @@ class IPFSBinaryCacheStore : public BinaryCacheStore std::string ipfsPath; + struct State + { + bool inProgressUpsert = false; + }; + Sync _state; + public: IPFSBinaryCacheStore( @@ -106,6 +112,13 @@ class IPFSBinaryCacheStore : public BinaryCacheStore auto addedPath = "/ipfs/" + (std::string) json1["Hash"]; + auto state(_state.lock()); + + if (state->inProgressUpsert) + throw Error("a modification to the IPNS is already in progress"); + + state->inProgressUpsert = true; + auto uri1 = daemonUri + "/api/v0/object/patch/add-link?offline=true&create=true"; uri1 += "&arg=" + getFileTransfer()->urlEncode(ipfsPath); uri1 += "&arg=" + getFileTransfer()->urlEncode(path); @@ -126,6 +139,8 @@ class IPFSBinaryCacheStore : public BinaryCacheStore req3.post = true; req3.tries = 1; getFileTransfer()->download(req3); + + state->inProgressUpsert = false; } catch (FileTransferError & e) { throw UploadToIPFS("while uploading to IPFS binary cache at '%s': %s", cacheUri, e.msg()); } From 6be117fcadb4e9bb7326e02f0aa165225cf70272 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 10 Jun 2020 12:32:07 -0500 Subject: [PATCH 12/18] Use addrType enum in IPFSBinaryCacheStore --- src/libstore/ipfs-binary-cache-store.cc | 43 +++++++++++++++++-------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index 270624a1eaf..40db6450e67 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -17,7 +17,20 @@ class IPFSBinaryCacheStore : public BinaryCacheStore std::string cacheUri; std::string daemonUri; - std::string ipfsPath; + std::string ipfsHash; + + enum struct AddrType { IPFS, IPNS } addrType; + + std::string getIpfsPath() { + switch (addrType) { + case AddrType::IPFS: { + return "/ipfs/" + ipfsHash; + } + case AddrType::IPNS: { + return "/ipns/" + ipfsHash; + } + } + } struct State { @@ -35,10 +48,14 @@ class IPFSBinaryCacheStore : public BinaryCacheStore if (cacheUri.back() == '/') cacheUri.pop_back(); - if (hasPrefix(cacheUri, "ipfs://")) - ipfsPath = "/ipfs/" + std::string(cacheUri, 7); - else if (hasPrefix(cacheUri, "ipns://")) - ipfsPath = "/ipns/" + std::string(cacheUri, 7); + if (hasPrefix(cacheUri, "ipfs://")) { + ipfsHash = std::string(cacheUri, 7); + addrType = AddrType::IPFS; + } + else if (hasPrefix(cacheUri, "ipns://")) { + ipfsHash = std::string(cacheUri, 7); + addrType = AddrType::IPNS; + } else throw Error("unknown IPFS URI '%s'", cacheUri); @@ -56,8 +73,8 @@ class IPFSBinaryCacheStore : public BinaryCacheStore throw Error("daemon for IPFS is not running properly"); // root should already exist - if (!fileExists("") && hasPrefix(ipfsPath, "/ipfs/")) - throw Error("path '%s' is not found", ipfsPath); + if (!fileExists("") && addrType == AddrType::IPFS) + throw Error("path '%s' is not found", getIpfsPath()); } std::string getUri() override @@ -78,7 +95,7 @@ class IPFSBinaryCacheStore : public BinaryCacheStore bool fileExists(const std::string & path) override { - auto uri = daemonUri + "/api/v0/object/stat?offline=true&arg=" + getFileTransfer()->urlEncode(ipfsPath + "/" + path); + auto uri = daemonUri + "/api/v0/object/stat?offline=true&arg=" + getFileTransfer()->urlEncode(getIpfsPath()); FileTransferRequest request(uri); request.post = true; @@ -97,8 +114,8 @@ class IPFSBinaryCacheStore : public BinaryCacheStore void upsertFile(const std::string & path, const std::string & data, const std::string & mimeType) override { - if (hasPrefix(ipfsPath, "/ipfs/")) - throw Error("%s is immutable, cannot modify", ipfsPath); + if (addrType == AddrType::IPFS) + throw Error("%s is immutable, cannot modify", getIpfsPath()); // TODO: use callbacks @@ -120,7 +137,7 @@ class IPFSBinaryCacheStore : public BinaryCacheStore state->inProgressUpsert = true; auto uri1 = daemonUri + "/api/v0/object/patch/add-link?offline=true&create=true"; - uri1 += "&arg=" + getFileTransfer()->urlEncode(ipfsPath); + uri1 += "&arg=" + getFileTransfer()->urlEncode(getIpfsPath()); uri1 += "&arg=" + getFileTransfer()->urlEncode(path); uri1 += "&arg=" + getFileTransfer()->urlEncode(addedPath); @@ -133,7 +150,7 @@ class IPFSBinaryCacheStore : public BinaryCacheStore auto newRoot = json2["Hash"]; auto uri2 = daemonUri + "/api/v0/name/publish?offline=true&arg=" + getFileTransfer()->urlEncode(newRoot); - uri2 += "&key=" + std::string(ipfsPath, 6); + uri2 += "&key=" + std::string(getIpfsPath(), 6); auto req3 = FileTransferRequest(uri2); req3.post = true; @@ -149,7 +166,7 @@ class IPFSBinaryCacheStore : public BinaryCacheStore void getFile(const std::string & path, Callback> callback) noexcept override { - auto uri = daemonUri + "/api/v0/cat?offline=true&arg=" + getFileTransfer()->urlEncode(ipfsPath + "/" + path); + auto uri = daemonUri + "/api/v0/cat?offline=true&arg=" + getFileTransfer()->urlEncode(getIpfsPath() + "/" + path); FileTransferRequest request(uri); request.post = true; From b62782dadf42ae69e4bd92c695e00773715304c6 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 10 Jun 2020 14:48:10 -0500 Subject: [PATCH 13/18] Cleanup --- src/libstore/ipfs-binary-cache-store.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index 40db6450e67..7f82768aa1f 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -85,9 +85,8 @@ class IPFSBinaryCacheStore : public BinaryCacheStore void init() override { std::string cacheInfoFile = "nix-cache-info"; - if (!fileExists(cacheInfoFile)) { + if (!fileExists(cacheInfoFile)) upsertFile(cacheInfoFile, "StoreDir: " + storeDir + "\n", "text/x-nix-cache-info"); - } BinaryCacheStore::init(); } From bc012e51917422f2b23eb34c5735d0d403f18832 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 10 Jun 2020 15:38:20 -0500 Subject: [PATCH 14/18] Add sync() to store API this is called at the end of a sequence of addToStores calls to tell the store that it can now commit the data. --- src/libstore/store-api.cc | 2 ++ src/libstore/store-api.hh | 4 ++++ src/nix-store/nix-store.cc | 5 +++++ src/nix/add-to-store.cc | 1 + src/nix/make-content-addressable.cc | 2 ++ 5 files changed, 14 insertions(+) diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 095363d0c9b..5f86f4dba14 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -680,6 +680,8 @@ void copyPaths(ref srcStore, ref dstStore, const StorePathSet & st nrDone++; showProgress(); }); + + dstStore->sync(); } diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh index b1e25fc7d66..7c6ee3595b4 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -680,6 +680,10 @@ public: virtual void createUser(const std::string & userName, uid_t userId) { } + /* Sync writes to commits written data, usually a no-op. */ + virtual void sync() + { }; + protected: Stats stats; diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index 3a3060ad82d..bdde54dcff5 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -167,6 +167,8 @@ static void opAdd(Strings opFlags, Strings opArgs) for (auto & i : opArgs) cout << fmt("%s\n", store->printStorePath(store->addToStore(std::string(baseNameOf(i)), i))); + + store->sync(); } @@ -188,6 +190,8 @@ static void opAddFixed(Strings opFlags, Strings opArgs) for (auto & i : opArgs) cout << fmt("%s\n", store->printStorePath(store->addToStore(std::string(baseNameOf(i)), i, recursive, hashAlgo))); + + store->sync(); } @@ -952,6 +956,7 @@ static void opServe(Strings opFlags, Strings opArgs) SizedSource sizedSource(in, info.narSize); store->addToStore(info, sizedSource, NoRepair, NoCheckSigs); + store->sync(); // consume all the data that has been sent before continuing. sizedSource.drainAll(); diff --git a/src/nix/add-to-store.cc b/src/nix/add-to-store.cc index f43f774c1c8..8b7a5997a11 100644 --- a/src/nix/add-to-store.cc +++ b/src/nix/add-to-store.cc @@ -53,6 +53,7 @@ struct CmdAddToStore : MixDryRun, StoreCommand if (!dryRun) { auto source = StringSource { *sink.s }; store->addToStore(info, source); + store->sync(); } logger->stdout("%s", store->printStorePath(info.path)); diff --git a/src/nix/make-content-addressable.cc b/src/nix/make-content-addressable.cc index 3e7ff544d6d..96bc119b32f 100644 --- a/src/nix/make-content-addressable.cc +++ b/src/nix/make-content-addressable.cc @@ -100,6 +100,8 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON remappings.insert_or_assign(std::move(path), std::move(info.path)); } + + store->sync(); } }; From 168cb9ee066c11310fc48f3abbdcc8ec8f33b53a Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 10 Jun 2020 15:39:10 -0500 Subject: [PATCH 15/18] Fixup fileExists method in IPFSBinaryCacheStore --- src/libstore/ipfs-binary-cache-store.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index 7f82768aa1f..9c63cfab27f 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -94,7 +94,7 @@ class IPFSBinaryCacheStore : public BinaryCacheStore bool fileExists(const std::string & path) override { - auto uri = daemonUri + "/api/v0/object/stat?offline=true&arg=" + getFileTransfer()->urlEncode(getIpfsPath()); + auto uri = daemonUri + "/api/v0/object/stat?arg=" + getFileTransfer()->urlEncode(getIpfsPath() + "/" + path); FileTransferRequest request(uri); request.post = true; From 816ff043802793deda1b6813ba72c017693c8b89 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 10 Jun 2020 15:40:24 -0500 Subject: [PATCH 16/18] Store IPFS path in state This uses the Sync primitive to store an ipfsPath that is written to as we add stuff to IPNS. IPNS is still optional. When finished, we do a publish on the ipnsPath. --- src/libstore/ipfs-binary-cache-store.cc | 124 ++++++++++++------------ 1 file changed, 63 insertions(+), 61 deletions(-) diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index 9c63cfab27f..45a7ae7d2b5 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -17,24 +17,15 @@ class IPFSBinaryCacheStore : public BinaryCacheStore std::string cacheUri; std::string daemonUri; - std::string ipfsHash; - - enum struct AddrType { IPFS, IPNS } addrType; - std::string getIpfsPath() { - switch (addrType) { - case AddrType::IPFS: { - return "/ipfs/" + ipfsHash; - } - case AddrType::IPNS: { - return "/ipns/" + ipfsHash; - } - } + auto state(_state.lock()); + return state->ipfsPath; } + std::optional ipnsPath; struct State { - bool inProgressUpsert = false; + std::string ipfsPath; }; Sync _state; @@ -45,19 +36,17 @@ class IPFSBinaryCacheStore : public BinaryCacheStore : BinaryCacheStore(params) , cacheUri(_cacheUri) { + auto state(_state.lock()); + if (cacheUri.back() == '/') cacheUri.pop_back(); - if (hasPrefix(cacheUri, "ipfs://")) { - ipfsHash = std::string(cacheUri, 7); - addrType = AddrType::IPFS; - } - else if (hasPrefix(cacheUri, "ipns://")) { - ipfsHash = std::string(cacheUri, 7); - addrType = AddrType::IPNS; - } + if (hasPrefix(cacheUri, "ipfs://")) + state->ipfsPath = "/ipfs/" + std::string(cacheUri, 7); + else if (hasPrefix(cacheUri, "ipns://")) + ipnsPath = "/ipns/" + std::string(cacheUri, 7); else - throw Error("unknown IPFS URI '%s'", cacheUri); + throw Error("unknown IPNS URI '%s'", cacheUri); std::string ipfsAPIHost(get(params, "host").value_or("127.0.0.1")); std::string ipfsAPIPort(get(params, "port").value_or("5001")); @@ -72,9 +61,19 @@ class IPFSBinaryCacheStore : public BinaryCacheStore if (versionInfo.find("Version") == versionInfo.end()) throw Error("daemon for IPFS is not running properly"); - // root should already exist - if (!fileExists("") && addrType == AddrType::IPFS) - throw Error("path '%s' is not found", getIpfsPath()); + // Resolve the IPNS name to an IPFS object + if (ipnsPath) { + debug("Resolving IPFS object of '%s', this could take a while.", *ipnsPath); + auto uri = daemonUri + "/api/v0/name/resolve?offline=true&arg=" + getFileTransfer()->urlEncode(*ipnsPath); + FileTransferRequest request(uri); + request.post = true; + request.tries = 1; + auto res = getFileTransfer()->download(request); + auto json = nlohmann::json::parse(*res.data); + if (json.find("Path") == json.end()) + throw Error("daemon for IPFS is not running properly"); + state->ipfsPath = json["Path"]; + } } std::string getUri() override @@ -111,52 +110,55 @@ class IPFSBinaryCacheStore : public BinaryCacheStore } } - void upsertFile(const std::string & path, const std::string & data, const std::string & mimeType) override + // IPNS publish can be slow, we try to do it rarely. + void sync() override { - if (addrType == AddrType::IPFS) - throw Error("%s is immutable, cannot modify", getIpfsPath()); + if (!ipnsPath) + return; - // TODO: use callbacks - - auto req1 = FileTransferRequest(daemonUri + "/api/v0/add"); - req1.data = std::make_shared(data); - req1.post = true; - req1.tries = 1; - try { - auto res1 = getFileTransfer()->upload(req1); - auto json1 = nlohmann::json::parse(*res1.data); + auto state(_state.lock()); - auto addedPath = "/ipfs/" + (std::string) json1["Hash"]; + debug("Publishing '%s' to '%s', this could take a while.", state->ipfsPath, *ipnsPath); - auto state(_state.lock()); + auto uri = daemonUri + "/api/v0/name/publish?offline=true&arg=" + getFileTransfer()->urlEncode(state->ipfsPath); + uri += "&key=" + std::string(*ipnsPath, 6); - if (state->inProgressUpsert) - throw Error("a modification to the IPNS is already in progress"); - - state->inProgressUpsert = true; + auto req = FileTransferRequest(uri); + req.post = true; + req.tries = 1; + getFileTransfer()->download(req); + } - auto uri1 = daemonUri + "/api/v0/object/patch/add-link?offline=true&create=true"; - uri1 += "&arg=" + getFileTransfer()->urlEncode(getIpfsPath()); - uri1 += "&arg=" + getFileTransfer()->urlEncode(path); - uri1 += "&arg=" + getFileTransfer()->urlEncode(addedPath); + void addLink(std::string name, std::string ipfsObject) + { + auto state(_state.lock()); - auto req2 = FileTransferRequest(uri1); - req2.post = true; - req2.tries = 1; - auto res2 = getFileTransfer()->download(req2); - auto json2 = nlohmann::json::parse(*res2.data); + auto uri = daemonUri + "/api/v0/object/patch/add-link?create=true"; + uri += "&arg=" + getFileTransfer()->urlEncode(state->ipfsPath); + uri += "&arg=" + getFileTransfer()->urlEncode(name); + uri += "&arg=" + getFileTransfer()->urlEncode(ipfsObject); - auto newRoot = json2["Hash"]; + auto req = FileTransferRequest(uri); + req.post = true; + req.tries = 1; + auto res = getFileTransfer()->download(req); + auto json = nlohmann::json::parse(*res.data); - auto uri2 = daemonUri + "/api/v0/name/publish?offline=true&arg=" + getFileTransfer()->urlEncode(newRoot); - uri2 += "&key=" + std::string(getIpfsPath(), 6); + state->ipfsPath = "/ipfs/" + (std::string) json["Hash"]; + } - auto req3 = FileTransferRequest(uri2); - req3.post = true; - req3.tries = 1; - getFileTransfer()->download(req3); + void upsertFile(const std::string & path, const std::string & data, const std::string & mimeType) override + { + // TODO: use callbacks - state->inProgressUpsert = false; + auto req = FileTransferRequest(daemonUri + "/api/v0/add"); + req.data = std::make_shared(data); + req.post = true; + req.tries = 1; + try { + auto res = getFileTransfer()->upload(req); + auto json = nlohmann::json::parse(*res.data); + addLink(path, "/ipfs/" + (std::string) json["Hash"]); } catch (FileTransferError & e) { throw UploadToIPFS("while uploading to IPFS binary cache at '%s': %s", cacheUri, e.msg()); } @@ -165,7 +167,7 @@ class IPFSBinaryCacheStore : public BinaryCacheStore void getFile(const std::string & path, Callback> callback) noexcept override { - auto uri = daemonUri + "/api/v0/cat?offline=true&arg=" + getFileTransfer()->urlEncode(getIpfsPath() + "/" + path); + auto uri = daemonUri + "/api/v0/cat?arg=" + getFileTransfer()->urlEncode(getIpfsPath() + "/" + path); FileTransferRequest request(uri); request.post = true; From 03a90fc9485b8152a701f84048876a3d299d3751 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Thu, 11 Jun 2020 00:16:56 -0400 Subject: [PATCH 17/18] ipnsPath -> optIpnsPath --- src/libstore/ipfs-binary-cache-store.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index 45a7ae7d2b5..783966471f2 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -21,7 +21,7 @@ class IPFSBinaryCacheStore : public BinaryCacheStore auto state(_state.lock()); return state->ipfsPath; } - std::optional ipnsPath; + std::optional optIpnsPath; struct State { @@ -44,7 +44,7 @@ class IPFSBinaryCacheStore : public BinaryCacheStore if (hasPrefix(cacheUri, "ipfs://")) state->ipfsPath = "/ipfs/" + std::string(cacheUri, 7); else if (hasPrefix(cacheUri, "ipns://")) - ipnsPath = "/ipns/" + std::string(cacheUri, 7); + optIpnsPath = "/ipns/" + std::string(cacheUri, 7); else throw Error("unknown IPNS URI '%s'", cacheUri); @@ -62,9 +62,9 @@ class IPFSBinaryCacheStore : public BinaryCacheStore throw Error("daemon for IPFS is not running properly"); // Resolve the IPNS name to an IPFS object - if (ipnsPath) { - debug("Resolving IPFS object of '%s', this could take a while.", *ipnsPath); - auto uri = daemonUri + "/api/v0/name/resolve?offline=true&arg=" + getFileTransfer()->urlEncode(*ipnsPath); + if (optIpnsPath) { + debug("Resolving IPFS object of '%s', this could take a while.", *optIpnsPath); + auto uri = daemonUri + "/api/v0/name/resolve?offline=true&arg=" + getFileTransfer()->urlEncode(*optIpnsPath); FileTransferRequest request(uri); request.post = true; request.tries = 1; @@ -113,15 +113,15 @@ class IPFSBinaryCacheStore : public BinaryCacheStore // IPNS publish can be slow, we try to do it rarely. void sync() override { - if (!ipnsPath) + if (!optIpnsPath) return; auto state(_state.lock()); - debug("Publishing '%s' to '%s', this could take a while.", state->ipfsPath, *ipnsPath); + debug("Publishing '%s' to '%s', this could take a while.", state->ipfsPath, *optIpnsPath); auto uri = daemonUri + "/api/v0/name/publish?offline=true&arg=" + getFileTransfer()->urlEncode(state->ipfsPath); - uri += "&key=" + std::string(*ipnsPath, 6); + uri += "&key=" + std::string(*optIpnsPath, 6); auto req = FileTransferRequest(uri); req.post = true; From 25c5ced81d6af521b035349c52035aa19d9e4147 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Thu, 11 Jun 2020 00:29:34 -0400 Subject: [PATCH 18/18] Avoid too many `optIpnsPath` It isn't very safe, so best to do it once right after the condition. If only we had real pattern matching... --- src/libstore/ipfs-binary-cache-store.cc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index 783966471f2..8de3b035934 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -63,8 +63,9 @@ class IPFSBinaryCacheStore : public BinaryCacheStore // Resolve the IPNS name to an IPFS object if (optIpnsPath) { - debug("Resolving IPFS object of '%s', this could take a while.", *optIpnsPath); - auto uri = daemonUri + "/api/v0/name/resolve?offline=true&arg=" + getFileTransfer()->urlEncode(*optIpnsPath); + auto ipnsPath = *optIpnsPath; + debug("Resolving IPFS object of '%s', this could take a while.", ipnsPath); + auto uri = daemonUri + "/api/v0/name/resolve?offline=true&arg=" + getFileTransfer()->urlEncode(ipnsPath); FileTransferRequest request(uri); request.post = true; request.tries = 1; @@ -115,13 +116,14 @@ class IPFSBinaryCacheStore : public BinaryCacheStore { if (!optIpnsPath) return; + auto ipnsPath = *optIpnsPath; auto state(_state.lock()); - debug("Publishing '%s' to '%s', this could take a while.", state->ipfsPath, *optIpnsPath); + debug("Publishing '%s' to '%s', this could take a while.", state->ipfsPath, ipnsPath); auto uri = daemonUri + "/api/v0/name/publish?offline=true&arg=" + getFileTransfer()->urlEncode(state->ipfsPath); - uri += "&key=" + std::string(*optIpnsPath, 6); + uri += "&key=" + std::string(ipnsPath, 6); auto req = FileTransferRequest(uri); req.post = true;