Introduce the concept of trust.

This commit is contained in:
2025-04-10 23:12:53 +02:00
parent 02690cf994
commit 3d7fdadc0b
4 changed files with 142 additions and 45 deletions
+15 -3
View File
@@ -35,10 +35,15 @@ The opposite is true too, a known company can have identified itself as the
owner and that we can advertise the trust to be higher. owner and that we can advertise the trust to be higher.
In hour generated metadata this is reflected by the (token) category itself In our generated metadata this is reflected by the (token) category itself
having a trust, and each metadata file linked having it's own specific having a trust, and each metadata file linked having it's own specific
trust. trust.
As a suggestion of policy, if a publisher of metadata has inconsistent data,
for instance they failed to have 18+ tags in their metadata file while this
would be appropriate, then what we suggest is to simply not include this
teams data in the registry until such a time that this omission has been fixed.
# Config format # Config format
In the trust subdir of the input directory you can place many text files In the trust subdir of the input directory you can place many text files
@@ -49,6 +54,13 @@ Fields are read from top to bottom, you can mention the same field various
times such that the item is processed in order. times such that the item is processed in order.
* trust=ultimate * trust=ultimate
The trust level is given. none, marginal, good, ultimate The trust level is given. none, marginal, good, high, ultimate
* domain=bitcoincash.org * domain=bitcoincash.org
Provides the source of the Provides the source of the metadata in dns form.
* category=deadbeef0124
A 64 character, hex-encoded token category. Which will then inherit
the most recently set trust.
* authbase=deadbeef0124
A 64 character, hex-encoded authbase (txid). Which will then inherit
the most recently set trust. Allowing for all metadata files indicated
by it to likewise inherit this trust.
+5 -5
View File
@@ -24,8 +24,8 @@ void DownloadJob::start()
connect (reply, &QNetworkReply::finished, this, [=]() { connect (reply, &QNetworkReply::finished, this, [=]() {
if (reply->error() != QNetworkReply::NoError) { if (reply->error() != QNetworkReply::NoError) {
// TODO mark file as not available somehow. // TODO mark file as not available somehow.
logCritical() << "Download failed" << m_sourceUrl.toString(); logWarning() << "Download failed" << m_sourceUrl.toString();
logCritical() << reply->errorString(); logInfo() << reply->errorString();
} }
else { else {
QFileInfo info(m_targetFilePath); QFileInfo info(m_targetFilePath);
@@ -44,12 +44,12 @@ void DownloadJob::start()
}); });
connect (reply, &QNetworkReply::errorOccurred, this, [=](QNetworkReply::NetworkError error) { connect (reply, &QNetworkReply::errorOccurred, this, [=](QNetworkReply::NetworkError error) {
logFatal() << "error occurred" << error; logInfo() << "Download error occurred" << error << "on" << m_sourceUrl.toString();
}); });
connect (reply, &QNetworkReply::sslErrors, this, [=](const QList<QSslError> &error) { connect (reply, &QNetworkReply::sslErrors, this, [=](const QList<QSslError> &error) {
logFatal() << "ssl errors"; logWarning() << "ssl errors on" << m_sourceUrl.toString();
for (const auto &e : error) { for (const auto &e : error) {
logFatal() << e.error() << e.errorString(); logWarning() << e.error() << e.errorString();
} }
}); });
} }
+111 -37
View File
@@ -29,6 +29,40 @@ static bool walk(QJsonObject &item, const QStringList &steps) {
return true; return true;
} }
static Processor::Trust trustFromString(const QString &string) {
auto lc = string.toLower();
if (lc == "none")
return Processor::NoTrust;
if (lc == "marginal")
return Processor::MarginalTrust;
if (lc == "good")
return Processor::GoodTrust;
if (lc == "high")
return Processor::HighTrust;
if (lc == "ultimate")
return Processor::UltimateTrust;
logWarning() << "Unrecognized trust passed:" << string;
return Processor::NoTrust;
}
static QString trustToString(Processor::Trust trust) {
switch (trust) {
case Processor::NoTrust:
return "none";
case Processor::MarginalTrust:
return "marginal";
case Processor::GoodTrust:
return "good";
case Processor::HighTrust:
return "high";
case Processor::UltimateTrust:
return "ultimate";
default:
assert(false);
break;
}
}
Processor::Processor(const QString &inDir, const QString &outDir) Processor::Processor(const QString &inDir, const QString &outDir)
: m_inDir(inDir), : m_inDir(inDir),
@@ -68,7 +102,8 @@ void Processor::run2()
for (auto *source : m_sources) { for (auto *source : m_sources) {
assert(source); assert(source);
assert(source->identities.size() > 0); if (source->identities.empty()) // created by a trust file, but seems no data.
continue;
QString outPath = m_outDir + source->hash + ".json"; QString outPath = m_outDir + source->hash + ".json";
if (!QFile::exists(outPath)) { if (!QFile::exists(outPath)) {
logCritical() << "Placing BCMR " << source->identities.at(0)->name << "as" << source->hash; logCritical() << "Placing BCMR " << source->identities.at(0)->name << "as" << source->hash;
@@ -124,10 +159,12 @@ void Processor::run2()
MetaIdentity *id = owner->idForGroupId(mg->id); MetaIdentity *id = owner->idForGroupId(mg->id);
o.insert("name", id->name); o.insert("name", id->name);
o.insert("timestamp", id->timestampStr); o.insert("timestamp", id->timestampStr);
o.insert("trust", trustToString(owner->trust));
sources.append(o); sources.append(o);
} }
QJsonObject root; QJsonObject root;
root.insert("sources", sources); root.insert("sources", sources);
root.insert("trust", trustToString(mg->trust));
QJsonDocument doc; QJsonDocument doc;
doc.setObject(root); doc.setObject(root);
auto memData = doc.toJson(QJsonDocument::Compact); auto memData = doc.toJson(QJsonDocument::Compact);
@@ -183,24 +220,27 @@ void Processor::runDownloadQueue()
logInfo() << "Starting download" << one->sourceUrl().toString(); logInfo() << "Starting download" << one->sourceUrl().toString();
logInfo() << "for" << one->targetFilePath(); logInfo() << "for" << one->targetFilePath();
// we should iterate all BCMRs that may have the same path already on the filesystem. // we should iterate all BCMRs that may have the same path already on the filesystem.
for (auto &group : m_groups) { if (id) {
if ((group.second->type == AuthBase && id->identity == group.second->id) for (auto &group : m_groups) {
|| (group.second->type != AuthBase && id->category == group.second->id)) { assert(id);
for (auto *owner : group.second->owners) { if ((group.second->type == AuthBase && id->identity == group.second->id)
QString target = m_outDir + owner->hash; || (group.second->type != AuthBase && id->category == group.second->id)) {
if (one->targetFilePath().startsWith(target)) // that's 'me' for (auto *owner : group.second->owners) {
continue; QString target = m_outDir + owner->hash;
target += one->targetFilePath().mid(target.size()); if (one->targetFilePath().startsWith(target)) // that's 'me'
if (QFile::exists(target)) { continue;
logInfo() << "Found in cache, skipping download"; target += one->targetFilePath().mid(target.size());
if (QFile::exists(target)) {
logInfo() << "Found in cache, skipping download";
QFileInfo info(one->targetFilePath()); QFileInfo info(one->targetFilePath());
QDir("/").mkpath(info.absolutePath()); QDir("/").mkpath(info.absolutePath());
QFile::copy(target, one->targetFilePath()); QFile::copy(target, one->targetFilePath());
m_downloadJobs.removeAll(one); m_downloadJobs.removeAll(one);
one->deleteLater(); one->deleteLater();
QTimer::singleShot(1, this, SLOT(runDownloadQueue())); QTimer::singleShot(1, this, SLOT(runDownloadQueue()));
return; return;
}
} }
} }
} }
@@ -268,16 +308,13 @@ void Processor::parseBCMR(const QString &path)
if (revision.isEmpty()) if (revision.isEmpty())
continue; continue;
if (me == nullptr) { if (me == nullptr) {
me = new MetaBCMR(); me = fetchOrCreate(path);
me->origFilename = path;
// calc hash // calc hash
CSHA256 hasher; CSHA256 hasher;
hasher.write(data.constData(), data.size()); hasher.write(data.constData(), data.size());
char buf[CSHA256::OUTPUT_SIZE]; char buf[CSHA256::OUTPUT_SIZE];
hasher.finalize(buf); hasher.finalize(buf);
me->hash = QByteArray(buf, sizeof(buf)).toHex(); me->hash = QByteArray(buf, sizeof(buf)).toHex();
m_sources.push_back(me);
} }
MetaIdentity *id = new MetaIdentity(); MetaIdentity *id = new MetaIdentity();
id->name = revision["name"].toString(); id->name = revision["name"].toString();
@@ -305,6 +342,7 @@ void Processor::parseBCMR(const QString &path)
id->category = cat; id->category = cat;
MetaGroup *mg = fetchOrCreate(cat, Category); MetaGroup *mg = fetchOrCreate(cat, Category);
id->tokens.push_back(mg); id->tokens.push_back(mg);
mg->trust = std::max(mg->trust, me->trust);
mg->owners.insert(me); mg->owners.insert(me);
// walk all nfts and extract image and icon resource urls // walk all nfts and extract image and icon resource urls
auto types = token; auto types = token;
@@ -331,31 +369,35 @@ void Processor::parseBCMR(const QString &path)
if (iter == m_groups.end()) { if (iter == m_groups.end()) {
// the identity was not duplicated as a category, then. // the identity was not duplicated as a category, then.
// we conclude it was an authbase // we conclude it was an authbase
fetchOrCreate(id->identity, AuthBase)->owners.insert(me); auto *group = fetchOrCreate(id->identity, AuthBase);
group->owners.insert(me);
group->trust = me->trust;
} }
} }
} }
void Processor::parseTrustFile(const QString &path) void Processor::parseTrustFile(const QString &path)
{ {
// TODO move the cache to a in/bcmrs/downloaded/trust_[filename].domain file
QFileInfo me(path); QFileInfo me(path);
QFileInfo cache(me.absolutePath() + "/../bcmrs/downloaded/" + me.fileName());
if (cache.exists()) {
QDateTime marker = QDateTime::currentDateTimeUtc().addDays(-7);
if (cache.lastModified() > marker) {
logInfo() << "Skipping check of" << path;
return;
}
}
QFile in(path); QFile in(path);
if (!in.open(QIODevice::ReadOnly)) { if (!in.open(QIODevice::ReadOnly)) {
logCritical() << "Failed to read" << path; logCritical() << "Failed to read" << path;
return; return;
} }
Trust trust = NoTrust;
for (const QString &line : QString::fromUtf8(in.readAll()).split('\n')) { for (const QString &line : QString::fromUtf8(in.readAll()).split('\n')) {
if (line.startsWith("domain=")) { if (line.startsWith("domain=")) {
// .well-known/bitcoin-cash-metadata-registry.json QFileInfo cache(me.absolutePath() + "/../bcmrs/downloaded/" + me.fileName());
if (trust != NoTrust)
fetchOrCreate(cache.absoluteFilePath())->trust = trust;
if (cache.exists()) {
QDateTime marker = QDateTime::currentDateTimeUtc().addDays(-7);
if (cache.lastModified() > marker) {
logInfo() << "Skipping check of" << path;
continue;
}
}
QUrl location; QUrl location;
location.setHost(line.mid(7).trimmed()); location.setHost(line.mid(7).trimmed());
location.setPath("/.well-known/bitcoin-cash-metadata-registry.json"); location.setPath("/.well-known/bitcoin-cash-metadata-registry.json");
@@ -369,11 +411,30 @@ void Processor::parseTrustFile(const QString &path)
}); });
m_downloadJobs.append(job); m_downloadJobs.append(job);
} }
// if (line.startsWith("trust=")) { else if (line.startsWith("trust=")) {
// //TODO trust = trustFromString(line.mid(6).trimmed());
// } }
} else if (line.startsWith("category=")) {
auto cat = line.mid(9).trimmed();
if (cat.length() != 64) {
logWarning() << "category line in" << path << "has wrong format.";
continue;
}
if (trust != NoTrust)
fetchOrCreate(cat, Category)->trust = trust;
}
else if (line.startsWith("authbase=")) {
auto ab = line.mid(9).trimmed();
if (ab.length() != 64) {
logWarning() << "authbase line in" << path << "has wrong format.";
continue;
}
if (trust != NoTrust)
fetchOrCreate(ab, AuthBase)->trust = trust;
}
}
} }
void Processor::parseBCHTx(QFile &file) void Processor::parseBCHTx(QFile &file)
@@ -382,6 +443,19 @@ void Processor::parseBCHTx(QFile &file)
logFatal() << "parse tx is a TODO"; logFatal() << "parse tx is a TODO";
} }
Processor::MetaBCMR *Processor::fetchOrCreate(const QString &bcmrFileName)
{
for (auto *s : m_sources) {
if (s->origFilename == bcmrFileName) {
return s;
}
}
MetaBCMR *meta = new MetaBCMR();
meta->origFilename = bcmrFileName;
m_sources.push_back(meta);
return meta;
}
bool Processor::offline() const bool Processor::offline() const
{ {
return m_offline; return m_offline;
+11
View File
@@ -21,6 +21,14 @@ public:
bool offline() const; bool offline() const;
void setOffline(bool newOffline); void setOffline(bool newOffline);
enum Trust {
NoTrust,
MarginalTrust,
GoodTrust,
HighTrust,
UltimateTrust
};
private slots: private slots:
void runDownloadQueue(); void runDownloadQueue();
void run2(); void run2();
@@ -47,16 +55,19 @@ private:
QSet<QString> resources; QSet<QString> resources;
}; };
struct MetaBCMR { struct MetaBCMR {
Trust trust = NoTrust;
QString hash; QString hash;
QString origFilename; QString origFilename;
std::vector<MetaIdentity*> identities; std::vector<MetaIdentity*> identities;
MetaIdentity* idForGroupId(const QString &groupId) const; MetaIdentity* idForGroupId(const QString &groupId) const;
}; };
MetaBCMR *fetchOrCreate(const QString &bcmrFileName);
struct MetaGroup { struct MetaGroup {
GroupType type; GroupType type;
QString id; QString id;
Trust trust = NoTrust;
std::set<MetaBCMR*> owners; std::set<MetaBCMR*> owners;
}; };