Skip to content

fetchTree attempts download despite narHash existing in store #12751

Open
@Warbo

Description

@Warbo

Describe the bug

The output of fetchTree { type = "file"; narHash = "..."; url = "..."; } seems to depend only on the narHash, since fetching the same file from different URLs gives the same outPath.

However, unlike a fixed-output derivation, fetchTree will try to perform the download (or at least attempts to connect to the URL) even if the outPath already exists. I think this is due to checking a URL-based cache, but not checking whether the store path already exists.

Steps To Reproduce

Use fetchTree with a narHash to fetch a file from a URL, and note its outPath. Then try it with the same narHash and a different URL. It will attempt to connect/download, even though we already have that outPath.

Here's a concrete example, fetching the same file from multiple IPFS gateways (I got this IPFS CID using printf 'hello world' | ipfs block add):

Fetch from ipfs.io with an empty narHash, to find what the narHash should be (I'm on NixOS, but using Nix 2.27 for some unrelated git-hashing fixes):

$ nix repl
Nix 2.27.0pre19700101_dirty
Type :? for help.
nix-repl> builtins.fetchTree { type = "file"; url = "https://ipfs.io/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e"; narHash = ""; }

error:
       … while calling the 'fetchTree' builtin
         at «string»:1:1:
            1| builtins.fetchTree { type = "file"; url = "https://ipfs.io/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e"; narHash = ""; }
             | ^

       … while fetching the input 'https://ipfs.io/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e?narHash=sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%3D'

       error: NAR hash mismatch in input 'https://ipfs.io/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e?narHash=sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%3D', expected 'sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' but got 'sha256-rkUEKu9bFIg12wLQRf6JtMCf+eR22rABoUvAMi0/IJM='

Fetching with that narHash (the file seems to have been cached):

nix-repl> builtins.fetchTree { type = "file"; url = "https://ipfs.io/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e"; narHash = "sha256-rkUEKu9bFIg12wLQRf6JtMCf+eR22rABoUvAMi0/IJM="; }
{
  narHash = "sha256-rkUEKu9bFIg12wLQRf6JtMCf+eR22rABoUvAMi0/IJM=";
  outPath = "/nix/store/0csgnsbvjfr2axpryskr9v7l43bzjvnd-source";
}

Now we know the narHash, try fetching the same file from a different URL:

nix-repl> builtins.fetchTree { type = "file"; url = "https://cloudflare-ipfs.com/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e"; narHash = "sha256-rkUEKu9bFIg12wLQRf6JtMCf+eR22rABoUvAMi0/IJM="; }

warning: error: unable to download 'https://cloudflare-ipfs.com/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e': Could not resolve hostname (6) Could not resolve host: cloudflare-ipfs.com; retrying in 303 ms
warning: error: unable to download 'https://cloudflare-ipfs.com/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e': Could not resolve hostname (6) Could not resolve host: cloudflare-ipfs.com; retrying in 645 ms
warning: error: unable to download 'https://cloudflare-ipfs.com/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e': Could not resolve hostname (6) Could not resolve host: cloudflare-ipfs.com; retrying in 1049 ms
warning: error: unable to download 'https://cloudflare-ipfs.com/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e': Could not resolve hostname (6) Could not resolve host: cloudflare-ipfs.com; retrying in 2426 ms
error:
       … while calling the 'fetchTree' builtin
         at «string»:1:1:
            1| builtins.fetchTree { type = "file"; url = "https://cloudflare-ipfs.com/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e"; narHash = "sha256-rkUEKu9bFIg12wLQRf6JtMCf+eR22rABoUvAMi0/IJM="; }
             | ^

       … while fetching the input 'https://cloudflare-ipfs.com/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e?narHash=sha256-rkUEKu9bFIg12wLQRf6JtMCf%2BeR22rABoUvAMi0/IJM%3D'

       error: unable to download 'https://cloudflare-ipfs.com/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e': Could not resolve hostname (6) Could not resolve host: cloudflare-ipfs.com
[0.0 MiB DL]

Cloudflare have shut down that IPFS gateway, but we still attempted to connect to it despite already having that file in our store.

If we try another, working URL then it will re-download the file, but the output is identical to using the original ipfs.io URL:

nix-repl> builtins.fetchTree { type = "file"; url = "https://gateway.pinata.cloud/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e"; narHash = "sha256-rkUEKu9bFIg12wLQRf6JtMCf+eR22rABoUvAMi0/IJM="; }
{
  narHash = "sha256-rkUEKu9bFIg12wLQRf6JtMCf+eR22rABoUvAMi0/IJM=";
  outPath = "/nix/store/0csgnsbvjfr2axpryskr9v7l43bzjvnd-source";
}

Expected behavior

If the outPath already exists in our store, then those fetchTree calls should return the { narHash = "..."; outPath = "..."; } result immediately, without attempting to download the URL.

Metadata

nix-env (Nix) 2.27.0pre19700101_dirty

Additional context

Related issues:

I'm currently working around this in a rather clunky way, by using a fixed-output derivation that uses /bin/sh to make a copy of the fetched file. This way, I can query whether the outPath already exists without having to call fetchTree (I use /dev/null instead):

with rec {
  inherit (builtins) currentSystem derivation fetchTree getEnv pathExists;

  override = getEnv "IPFS_GATEWAY";
  gateway = if override == "" then "https://ipfs.io" else override;

  fixed = src: derivation {
    name = "source";
    builder = "/bin/sh";
    system = currentSystem;
    outputHashMode = "nar";
    outputHash = narHash;
    args = [
      "-c"
      ''read -r -d "" content < ${src}; printf '%s\n' "$content" > "$out"''
    ];
  };
  existing = (fixed "/dev/null").outPath;
  file = if pathExists existing then existing else fixed (fetchTree {
    inherit narHash;
    type = "file";
    url = "${gateway}/ipfs/${cid}";
  });
};
file

Checklist


Add 👍 to issues you find important.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugfetchingNetworking with the outside (non-Nix) world, input locking

    Type

    No type

    Projects

    Status

    To triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions