Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Git with refs types refactor #53

Open
wants to merge 21 commits into
base: ipfs-develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ffe4112
Change content address data types
Ericson2314 Jul 2, 2020
0d20c9d
WIP start converting the rest
Ericson2314 Jul 2, 2020
7e2133e
Merge remote-tracking branch 'obsidian/git-with-refs' into git-with-r…
Ericson2314 Jul 6, 2020
f307db3
Merge branch 'ipfs-develop' of github.com:obsidiansystems/nix into gi…
Ericson2314 Jul 6, 2020
4287b89
Allow storings some tree and not just hash for IPFS
Ericson2314 Jul 6, 2020
acbda9c
WIP
Ericson2314 Jul 7, 2020
b6249fb
Add missing header
Ericson2314 Jul 7, 2020
8476d8c
Merge branch 'ipfs-develop' into git-with-refs-strawman
Ericson2314 Jul 8, 2020
842dd4f
Use `IPFSGitTreeNode` typedef after all
Ericson2314 Jul 8, 2020
34f000c
Merge branch 'ipfs-develop' into git-with-refs-strawman
Ericson2314 Jul 8, 2020
e723ced
remove cid commands
Ericson2314 Jul 8, 2020
caac4c4
Make ipfs.cc with bigger definitions
Ericson2314 Jul 9, 2020
17e267d
WIP on store-api
Ericson2314 Jul 9, 2020
2da7fec
Get store-api.cc building
Ericson2314 Jul 9, 2020
82521a9
Merge branch 'ipfs-develop' of github.com:obsidiansystems/nix into gi…
Ericson2314 Jul 9, 2020
4eb4d42
Get ipfs.cc building
Ericson2314 Jul 9, 2020
3cb966a
Tiny step towards getting the IPFS store to compile
Ericson2314 Jul 9, 2020
3366d8c
Merge branch 'ipfs-develop' of github.com:obsidiansystems/nix into gi…
Ericson2314 Jul 9, 2020
76540c9
Everything compiles!
Ericson2314 Jul 9, 2020
3bd223b
Merge branch 'ipfs-develop' of github.com:obsidiansystems/nix into gi…
Ericson2314 Jul 9, 2020
db22255
Merge remote-tracking branch 'obsidian/ipfs-develop' into git-with-re…
Ericson2314 Jul 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
408 changes: 209 additions & 199 deletions src/libstore/content-address.cc

Large diffs are not rendered by default.

64 changes: 38 additions & 26 deletions src/libstore/content-address.hh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <variant>

#include "hash.hh"
#include "ipfs.hh"
#include "path.hh"

namespace nix {
Expand All @@ -30,9 +31,10 @@ struct FixedOutputHash {
std::string printMethodAlgo() const;
};

struct IPFSHash {
Hash hash;
};
template<template <typename> class Ref>
struct IPFSGitTreeNodeT;

typedef IPFSGitTreeNodeT<IPFSHashWithOptValue> IPFSGitTreeNode;

/*
We've accumulated several types of content-addressed paths over the years;
Expand All @@ -48,7 +50,7 @@ struct IPFSHash {
typedef std::variant<
TextHash, // for paths computed by makeTextPath() / addTextToStore
FixedOutputHash, // for path computed by makeFixedOutputPath
IPFSHash
IPFSHash<IPFSGitTreeNode>
> LegacyContentAddress;

/* Compute the prefix to the hash algorithm which indicates how the files were
Expand Down Expand Up @@ -139,44 +141,54 @@ struct FixedOutputInfo : FixedOutputHash {
PathReferences<StorePath> references;
};

// pair of cid and name for ipfs references
typedef std::pair<std::string, std::string> IPFSRef;
template<typename Underlying>
struct ContentAddressT;

struct IPFSInfo {
Hash hash;
template<template <typename> class Ref>
struct IPFSGitTreeNodeT {
Hash gitTree;
// References for the paths
PathReferences<IPFSRef> references;
PathReferences<ContentAddressT<Ref<IPFSGitTreeNodeT<Ref>>>> references;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would you ever need more than one level of PathReferences? We can always query the daemon once we have the cid of a reference.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More importantly, when would we ever fill in more than one layer of references? We handle each path on its own, so no room to share information like this between queryPathInfo calls.

};

// just the cid, which is a hash of ipfsinfo
struct IPFSCid {
std::string cid;
};
// FIXME names
typedef std::variant<
TextInfo,
FixedOutputInfo,
IPFSHashWithOptValue<IPFSGitTreeNode>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this duplicate data because IPFSHashWithOptValue has a hash, but we can also compute the hash from IPFSGitTreeNode?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be better to make IPFSHashWithOptValue a variant of either a hash or a value.

> ContentAddressWithoutName;

struct ContentAddress {
template<typename Underlying>
struct ContentAddressT {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a little duplication here would be much better than doing this. What even is ContentAddressT? If you really want to use it, you should call it something like NameWrapper or something.

std::string name;
std::variant<
TextInfo,
FixedOutputInfo,
IPFSInfo,
IPFSCid
> info;

bool operator < (const ContentAddress & other) const
Underlying info;

bool operator < (const ContentAddressT<Underlying> & other) const
{
return name < other.name;
// FIXME second field
}
};

typedef ContentAddressT<ContentAddressWithoutName> ContentAddress;

std::string renderContentAddress(ContentAddress ca);

ContentAddress parseContentAddress(std::string_view rawCa);

void to_json(nlohmann::json& j, const ContentAddress & c);
void from_json(const nlohmann::json& j, ContentAddress & c);
template<template <typename> class Ref>
void to_json(nlohmann::json& j, const IPFSGitTreeNodeT<Ref> & c);
template<template <typename> class Ref>
void from_json(const nlohmann::json& j, IPFSGitTreeNodeT<Ref> & c);

template<typename T>
void to_json(nlohmann::json& j, const ContentAddressT<T> & c);
template<typename T>
void from_json(const nlohmann::json& j, ContentAddressT<T> & c);

void to_json(nlohmann::json& j, const PathReferences<IPFSRef> & c);
void from_json(const nlohmann::json& j, PathReferences<IPFSRef> & c);
template<typename T>
void to_json(nlohmann::json& j, const PathReferences<T> & c);
template<typename T>
void from_json(const nlohmann::json& j, PathReferences<T> & c);

}
4 changes: 2 additions & 2 deletions src/libstore/ipfs-binary-cache-store.cc
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ std::optional<std::string> IPFSBinaryCacheStore::getCidFromCA(ContentAddress ca)
assert(ca_.hash.type == htSHA1);
return "f01781114" + ca_.hash.to_string(Base16, false);
}
} else if (std::holds_alternative<IPFSInfo>(ca.info))
} else if (std::holds_alternative<IPFSHashWithOptValue<IPFSGitTreeNode>>(ca.info))
return computeIPFSCid(ca);
else if (std::holds_alternative<IPFSCid>(ca.info))
return std::get<IPFSCid>(ca.info).cid;
Expand Down Expand Up @@ -655,7 +655,7 @@ void IPFSBinaryCacheStore::addToStore(const ValidPathInfo & info, Source & narSo

return;
}
} else if (info.ca && std::holds_alternative<IPFSHash>(*info.ca)) {
} else if (info.ca && std::holds_alternative<IPFSHash<IPSFGitTreeNode>>(*info.ca)) {
auto nar = make_ref<std::string>(narSource.drain());

AutoDelete tmpDir(createTempDir(), true);
Expand Down
101 changes: 27 additions & 74 deletions src/libstore/store-api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -215,74 +215,29 @@ StorePath Store::makeTextPath(std::string_view name, const TextInfo & info) cons
name);
}

static HashType getMultiHashTag(int tag)
// FIXME move to content-address.cc
Hash computeIPFSHash(const IPFSGitTreeNode & info)
{
switch (tag) {
case 0x11: {
return htSHA1;
}
case 0x12: {
return htSHA256;
}
default: {
throw Error("tag '%i' is an unknown hash type", tag);
}
}
}

static std::vector<uint8_t> packMultihash(std::string cid)
{
std::vector<uint8_t> result;
assert(cid[0] == 'f');
result.push_back(0x00);
result.push_back(std::stoi(cid.substr(1, 2), nullptr, 16));
result.push_back(std::stoi(cid.substr(3, 2), nullptr, 16));
result.push_back(std::stoi(cid.substr(5, 2), nullptr, 16));
result.push_back(std::stoi(cid.substr(7, 2), nullptr, 16));
HashType ht = getMultiHashTag(std::stoi(cid.substr(5, 2), nullptr, 16));
Hash hash = Hash::parseAny(cid.substr(9), ht);
for (unsigned int i = 0; i < hash.hashSize; i++)
result.push_back(hash.hash[i]);
return result;
}
nlohmann::json j { info };

Hash computeIPFSHash(const ContentAddress & info, HashType ht)
{
nlohmann::json j = info;
auto pack = [&](auto & ref) {
ref = ref.template get<IPFSHash<IPFSGitTreeNode>>().packForCBOR();
};

// replace {"/": ...} with packed multihash
// ipfs converts automatically between the two
j.at("cid") = nlohmann::json::binary(packMultihash(j.at("cid").at("/").get<std::string>()), 42);
pack(j.at("gitTree"));
for (auto & ref : j.at("references").at("references"))
ref.at("cid") = nlohmann::json::binary(packMultihash(ref.at("cid").at("/").get<std::string>()), 42);
pack(ref);

std::vector<std::uint8_t> cbor = nlohmann::json::to_cbor(j);
return hashString(ht, std::string(cbor.begin(), cbor.end()));
return hashString(htSHA256, std::string(cbor.begin(), cbor.end()));
}

std::string computeIPFSCid(const ContentAddress & info)
StorePath Store::makeIPFSPath(std::string_view name, IPFSHash<IPFSGitTreeNode> hash) const
{
return "f01711220" + computeIPFSHash(info, htSHA256).to_string(Base16, false);
}

StorePath Store::makeIPFSPath(IPFSRef ref) const
{
string type = "ipfs";

// copy pase from makeStorePath
string s = type + ":" + ref.first + ":" + storeDir + ":" + std::string(ref.second);
auto h = compressHash(hashString(htSHA256, s), 20);
return StorePath(h, ref.second);
}

StorePath Store::makeIPFSPath(std::string cid, std::string name) const
{
return makeIPFSPath(IPFSRef(cid, name));
}

StorePath Store::makeIPFSPath(const ContentAddress & info) const
{
return makeIPFSPath(computeIPFSCid(info), info.name);
// `to_string` use IPFS-style multi* encoding for forward-compat.
return makeStorePath("ipfs-git", hash.hash, name);
}

StorePath Store::makeFixedOutputPathFromCA(const ContentAddress & info) const
Expand All @@ -295,12 +250,9 @@ StorePath Store::makeFixedOutputPathFromCA(const ContentAddress & info) const
[&](FixedOutputInfo foi) {
return makeFixedOutputPath(info.name, foi);
},
[&](IPFSInfo io) {
return makeIPFSPath(info);
[&](IPFSHash<IPFSGitTreeNode> ih) {
return makeIPFSPath(info.name, ih);
},
[&](IPFSCid ic) {
return makeIPFSPath(ic.cid, info.name);
}
}, info.info);
}

Expand Down Expand Up @@ -974,15 +926,17 @@ std::optional<ContentAddress> ValidPathInfo::fullContentAddressOpt() const
TextInfo info { th };
assert(!hasSelfReference);
info.references = references;
return std::variant<TextInfo, FixedOutputInfo, IPFSInfo, IPFSCid> { info };
return ContentAddressWithoutName { info };
},
[&](FixedOutputHash foh) {
FixedOutputInfo info { foh };
info.references = static_cast<PathReferences<StorePath>>(*this);
return std::variant<TextInfo, FixedOutputInfo, IPFSInfo, IPFSCid> { info };
return ContentAddressWithoutName { info };
},
[&](IPFSHash io) {
return std::variant<TextInfo, FixedOutputInfo, IPFSInfo, IPFSCid> { IPFSCid { "f01711220" + io.hash.to_string(Base16, false) } };
[&](IPFSHash<IPFSGitTreeNode> io) {
return ContentAddressWithoutName {
IPFSHashWithOptValue<IPFSGitTreeNode> { io },
};
},
}, *ca),
};
Expand Down Expand Up @@ -1047,14 +1001,13 @@ ValidPathInfo::ValidPathInfo(
*(static_cast<PathReferences<StorePath> *>(this)) = foi.references;
this->ca = FixedOutputHash { (FixedOutputHash) std::move(foi) };
},
[this, &store, info](IPFSInfo foi) {
this->hasSelfReference = foi.references.hasSelfReference;
for (auto & ref : foi.references.references)
this->references.insert(store.makeIPFSPath(ref));
this->ca = IPFSHash { computeIPFSHash(info, htSHA256) };
},
[](IPFSCid foi) {
throw Error("cannot make a valid path from a cid");
[this, &store](IPFSHashWithOptValue<IPFSGitTreeNode> ih) {
this->ca = static_cast<IPFSHash<IPFSGitTreeNode>>(ih);
if (ih.value) {
this->hasSelfReference = ih.value->references.hasSelfReference;
for (auto & ref : ih.value->references.references)
this->references.insert(store.makeIPFSPath(ref.name, ref.info));
}
},
}, std::move(info.info));
}
Expand Down
5 changes: 2 additions & 3 deletions src/libstore/store-api.hh
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ public:

StorePath makeIPFSPath(const ContentAddress & info) const;

StorePath makeIPFSPath(IPFSRef ref) const;
StorePath makeIPFSPath(std::string_view name, IPFSHash<IPFSGitTreeNode> ref) const;

StorePath makeIPFSPath(std::string cid, std::string name) const;

Expand Down Expand Up @@ -771,8 +771,7 @@ std::pair<std::string, Store::Params> splitUriAndParams(const std::string & uri)

std::optional<ContentAddress> getDerivationCA(const BasicDerivation & drv);

Hash computeIPFSHash(const ContentAddress & info);
std::string computeIPFSCid(const ContentAddress & info);
Hash computeIPFSHash(const IPFSGitTreeNode & info);


}
4 changes: 4 additions & 0 deletions src/libutil/hash.hh
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ struct Hash

HashType type;

/* Using and arbitrary dummy value is bad, but very useful for
<nlohmann/json> so we do it anways */
Hash() : Hash { htSHA256 } { }

/* Create a zero-filled hash object. */
Hash(HashType type);

Expand Down
41 changes: 41 additions & 0 deletions src/libutil/ipfs.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include "ipfs.hh"

namespace nix::untyped {

// "f01711220" base-16, sha-256
// "f01781114" base-16, sha-1

std::string toString(Hash hash) {
std::string prefix;
switch (hash.type) {
case htSHA1: prefix = "f01711220";
case htSHA256: prefix = "f01781114";
default: throw Error("hash type '%s' we don't yet export to IPFS", printHashType(hash.type));
}
return prefix + hash.to_string(Base16, false);
}

Hash fromString(std::string_view cid) {
auto prefix = cid.substr(0, 9);
HashType algo = prefix == "f01711220" ? htSHA256
: prefix == "f01781114" ? htSHA1
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f0178 means it's the git codec, which should be an error for the "full" content address at least

: throw Error("cid '%s' is wrong type for ipfs hash", cid);
return Hash::parseNonSRIUnprefixed(cid.substr(9), algo);
}

std::vector<uint8_t> pack(Hash hash)
{
auto cid = toString(hash);
std::vector<uint8_t> result;
assert(cid[0] == 'f');
result.push_back(0x00);
result.push_back(std::stoi(cid.substr(1, 2), nullptr, 16));
result.push_back(std::stoi(cid.substr(3, 2), nullptr, 16));
result.push_back(std::stoi(cid.substr(5, 2), nullptr, 16));
result.push_back(std::stoi(cid.substr(7, 2), nullptr, 16));
for (unsigned int i = 0; i < hash.hashSize; i++)
result.push_back(hash.hash[i]);
return result;
}

}
Loading