mirror of
https://github.com/Xahau/xahaud.git
synced 2026-10-11 06:18:03 +00:00
Compare commits
4 Commits
ci/nix-fix
...
unlreport-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a2b0785f3 | ||
|
|
345b7543e9 | ||
|
|
d9cc66afe9 | ||
|
|
7c5cceac82 |
151
.github/workflows/xahau-ga-nix.yml
vendored
151
.github/workflows/xahau-ga-nix.yml
vendored
@@ -8,12 +8,6 @@ on:
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
full_matrix:
|
||||
description: "Force full matrix (6 configs)"
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -30,37 +24,14 @@ jobs:
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: escape double quotes
|
||||
id: escape
|
||||
shell: bash
|
||||
env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
run: |
|
||||
ESCAPED_PR_TITLE="${PR_TITLE//\"/\\\"}"
|
||||
echo "title=${ESCAPED_PR_TITLE}" >> "$GITHUB_OUTPUT"
|
||||
- name: Generate build matrix
|
||||
id: set-matrix
|
||||
shell: python
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
|
||||
# Full matrix with all 6 compiler configurations
|
||||
# Each configuration includes all parameters needed by the build job
|
||||
full_matrix = [
|
||||
{
|
||||
"compiler_id": "gcc-11-libstdcxx",
|
||||
"compiler": "gcc",
|
||||
"cc": "gcc-11",
|
||||
"cxx": "g++-11",
|
||||
"compiler_version": 11,
|
||||
"stdlib": "default",
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
},
|
||||
matrix = [
|
||||
{
|
||||
"compiler_id": "gcc-13-libstdcxx",
|
||||
"compiler": "gcc",
|
||||
@@ -93,17 +64,6 @@ jobs:
|
||||
"grpc/*": ["-Wno-missing-template-arg-list-after-template-kw"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"compiler_id": "clang-14-libstdcxx-gcc11",
|
||||
"compiler": "clang",
|
||||
"cc": "clang-14",
|
||||
"cxx": "clang++-14",
|
||||
"compiler_version": 14,
|
||||
"stdlib": "libstdcxx",
|
||||
"clang_gcc_toolchain": 11,
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
},
|
||||
{
|
||||
"compiler_id": "clang-16-libstdcxx-gcc13",
|
||||
"compiler": "clang",
|
||||
@@ -114,117 +74,8 @@ jobs:
|
||||
"clang_gcc_toolchain": 13,
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
},
|
||||
{
|
||||
"compiler_id": "clang-17-libcxx",
|
||||
"compiler": "clang",
|
||||
"cc": "clang-17",
|
||||
"cxx": "clang++-17",
|
||||
"compiler_version": 17,
|
||||
"stdlib": "libcxx",
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
},
|
||||
{
|
||||
# Clang 18 - testing if it's faster than Clang 17 with libc++
|
||||
# Requires patching Conan v1 settings.yml to add version 18
|
||||
"compiler_id": "clang-18-libcxx",
|
||||
"compiler": "clang",
|
||||
"cc": "clang-18",
|
||||
"cxx": "clang++-18",
|
||||
"compiler_version": 18,
|
||||
"stdlib": "libcxx",
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
}
|
||||
]
|
||||
|
||||
# Minimal matrix for PRs and feature branches
|
||||
minimal_matrix = [
|
||||
full_matrix[1], # gcc-13 (middle-ground gcc)
|
||||
full_matrix[2], # clang-20 llvm-cov coverage
|
||||
full_matrix[3] # clang-14 (mature, stable clang)
|
||||
]
|
||||
|
||||
# Determine which matrix to use based on the target branch
|
||||
ref = "${{ github.ref }}"
|
||||
base_ref = "${{ github.base_ref }}" # For PRs, this is the target branch
|
||||
event_name = "${{ github.event_name }}"
|
||||
pr_title = """${{ steps.escape.outputs.title }}"""
|
||||
pr_labels = """${{ join(github.event.pull_request.labels.*.name, ',') }}"""
|
||||
pr_head_sha = "${{ github.event.pull_request.head.sha }}"
|
||||
|
||||
# Get commit message - for PRs, fetch via API since head_commit.message is empty
|
||||
if event_name == "pull_request" and pr_head_sha:
|
||||
try:
|
||||
url = f"https://api.github.com/repos/${{ github.repository }}/commits/{pr_head_sha}"
|
||||
req = urllib.request.Request(url, headers={
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
"Authorization": f"Bearer {os.environ.get('GH_TOKEN', '')}"
|
||||
})
|
||||
with urllib.request.urlopen(req) as response:
|
||||
data = json.load(response)
|
||||
commit_message = data["commit"]["message"]
|
||||
except Exception as e:
|
||||
print(f"Failed to fetch commit message: {e}")
|
||||
commit_message = ""
|
||||
else:
|
||||
commit_message = """${{ github.event.head_commit.message }}"""
|
||||
|
||||
# Debug logging
|
||||
print(f"Event: {event_name}")
|
||||
print(f"Ref: {ref}")
|
||||
print(f"Base ref: {base_ref}")
|
||||
print(f"PR head SHA: {pr_head_sha}")
|
||||
print(f"PR title: {pr_title}")
|
||||
print(f"PR labels: {pr_labels}")
|
||||
print(f"Commit message: {commit_message}")
|
||||
|
||||
# Manual trigger input to force full matrix.
|
||||
manual_full = "${{ github.event.inputs.full_matrix || 'false' }}" == "true"
|
||||
|
||||
# Label/manual overrides, while preserving existing title/commit behavior.
|
||||
force_full = (
|
||||
manual_full
|
||||
or "[ci-nix-full-matrix]" in commit_message
|
||||
or "[ci-nix-full-matrix]" in pr_title
|
||||
or ("ci-full-build" in pr_labels and "ci-nix-full-matrix" in pr_labels)
|
||||
)
|
||||
force_min = (
|
||||
"ci-full-build" in pr_labels
|
||||
)
|
||||
print(f"Force full matrix: {force_full}")
|
||||
print(f"Force min matrix: {force_min}")
|
||||
|
||||
# Check if this is targeting a main branch
|
||||
# For PRs: check base_ref (target branch)
|
||||
# For pushes: check ref (current branch)
|
||||
main_branches = ["refs/heads/dev", "refs/heads/release", "refs/heads/candidate"]
|
||||
|
||||
if force_full:
|
||||
# Override: always use full matrix if forced by manual input or label.
|
||||
use_full = True
|
||||
elif force_min:
|
||||
# Override: always use minimal matrix if ci-full-build label is present.
|
||||
use_full = False
|
||||
elif event_name == "pull_request":
|
||||
# For PRs, base_ref is just the branch name (e.g., "dev", not "refs/heads/dev")
|
||||
# Check if the PR targets release or candidate (more critical branches)
|
||||
use_full = base_ref in ["release", "candidate"]
|
||||
else:
|
||||
# For pushes, ref is the full reference (e.g., "refs/heads/dev")
|
||||
use_full = ref in main_branches
|
||||
|
||||
# Select the appropriate matrix
|
||||
if use_full:
|
||||
if force_full:
|
||||
print(f"Using FULL matrix (7 configs (build x6 + clang-20 llvm-cov coverage)) - forced by [ci-nix-full-matrix] tag")
|
||||
else:
|
||||
print(f"Using FULL matrix (7 configs (build x6 + clang-20 llvm-cov coverage)) - targeting main branch")
|
||||
matrix = full_matrix
|
||||
else:
|
||||
print(f"Using MINIMAL matrix (3 configs) - feature branch/PR")
|
||||
matrix = minimal_matrix
|
||||
|
||||
# Add runs_on based on job_type
|
||||
for entry in matrix:
|
||||
|
||||
@@ -1203,6 +1203,96 @@ class UNLReportVoteNewValidator_test : public beast::unit_test::suite
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Regression test: UNLReport proposals must not be gated on the N-UNL local
|
||||
* reliability threshold.
|
||||
*
|
||||
* The local node (validator 0) validates exactly `myVals` of the 256 scored
|
||||
* ledgers, validator 1 validates none (so it is an N-UNL disable candidate and
|
||||
* absent from the report), every other validator validates all of them.
|
||||
*
|
||||
* == myVals <= 128: not active ourselves -> no UNLReport, no UNLModify
|
||||
* == 129 .. 229: UNLReport proposed, not reliable enough to vote on N-UNL
|
||||
* == >= 230: UNLReport and one UNLModify (230 used to fall into the
|
||||
* "Too many!" branch and fail)
|
||||
*/
|
||||
class UNLReportVoteLocalReliability_test : public beast::unit_test::suite
|
||||
{
|
||||
void
|
||||
testDoVoting()
|
||||
{
|
||||
testcase("Do Voting with local reliability below N-UNL threshold");
|
||||
|
||||
struct Case
|
||||
{
|
||||
std::uint32_t myVals;
|
||||
bool expectReport;
|
||||
bool expectModify;
|
||||
};
|
||||
|
||||
std::array<Case, 6> const cases = {{
|
||||
{128, false, false},
|
||||
{129, true, false},
|
||||
{229, true, false},
|
||||
{230, true, true},
|
||||
{231, true, true},
|
||||
{256, true, true},
|
||||
}};
|
||||
|
||||
for (bool const withVLImport : {true, false})
|
||||
{
|
||||
for (auto const& c : cases)
|
||||
{
|
||||
std::uint32_t const numNodes = 20;
|
||||
URNetworkHistory history = {
|
||||
*this, {numNodes, 0, false, false, withVLImport, {}}};
|
||||
BEAST_EXPECT(history.goodHistory);
|
||||
if (!history.goodHistory)
|
||||
continue;
|
||||
|
||||
// scored ledgers are [lastSeq - 256, lastSeq - 1]
|
||||
auto const lastSeq = history.lastLedger()->seq();
|
||||
history.walkHistoryAndAddValidations(
|
||||
[&](std::shared_ptr<Ledger const> const& l,
|
||||
std::size_t idx) -> bool {
|
||||
if (idx == 1)
|
||||
return false;
|
||||
if (idx != 0)
|
||||
return true;
|
||||
return l->seq() + 256 < lastSeq + c.myVals;
|
||||
});
|
||||
|
||||
NegativeUNLVote vote(
|
||||
history.UNLNodeIDs[0],
|
||||
history.env.journal,
|
||||
history.env.app());
|
||||
auto txSet = std::make_shared<SHAMap>(
|
||||
SHAMapType::TRANSACTION, history.env.app().getNodeFamily());
|
||||
vote.doVoting(
|
||||
history.lastLedger(),
|
||||
history.UNLKeySet,
|
||||
history.validations,
|
||||
txSet);
|
||||
|
||||
std::size_t const expectReport =
|
||||
c.expectReport ? numNodes - 1 + (withVLImport ? 1 : 0) : 0;
|
||||
std::size_t const expectModify = c.expectModify ? 1 : 0;
|
||||
std::string const ctx = "myVals=" + std::to_string(c.myVals) +
|
||||
" withVLImport=" + std::to_string(withVLImport);
|
||||
|
||||
BEAST_EXPECTS(countUNLRTx(txSet) == expectReport, ctx);
|
||||
BEAST_EXPECTS(unl::countTx(txSet) == expectModify, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testDoVoting();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(UNLReport, ledger, ripple);
|
||||
BEAST_DEFINE_TESTSUITE(UNLReportNoAmendment, ledger, ripple);
|
||||
BEAST_DEFINE_TESTSUITE(UNLReportFork, consensus, ripple);
|
||||
@@ -1215,6 +1305,7 @@ BEAST_DEFINE_TESTSUITE_PRIO(
|
||||
ripple,
|
||||
1);
|
||||
BEAST_DEFINE_TESTSUITE(UNLReportVoteNewValidator, consensus, ripple);
|
||||
BEAST_DEFINE_TESTSUITE(UNLReportVoteLocalReliability, consensus, ripple);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -348,6 +348,17 @@ RCLConsensus::Adaptor::onClose(
|
||||
make_shamapitem(tx.first->getTransactionID(), s.slice()));
|
||||
}
|
||||
|
||||
// Keep enough validation history for the next flag ledger's score table
|
||||
// even when we don't vote this round (not proposing / wrong LCL).
|
||||
// Otherwise toKeep_ goes stale, validations older than
|
||||
// validationSET_EXPIRES (10 min, < 256 ledgers) get swept, and we fail
|
||||
// the next flag's score table too.
|
||||
if (prevLedger->isVotingLedger())
|
||||
{
|
||||
auto const seq = prevLedger->info().seq + 1;
|
||||
app_.getValidations().setSeqToKeep(seq - 1, seq + FLAG_LEDGER_INTERVAL);
|
||||
}
|
||||
|
||||
// Add pseudo-transactions to the set
|
||||
if (app_.config().standalone() || (proposing && !wrongLCL))
|
||||
{
|
||||
|
||||
@@ -58,9 +58,19 @@ NegativeUNLVote::doVoting(
|
||||
unlNodeIDs.emplace(nid);
|
||||
}
|
||||
|
||||
// Build a reliability score table of validators
|
||||
if (std::optional<hash_map<NodeID, std::uint32_t>> scoreTable =
|
||||
buildScoreTable(prevLedger, unlNodeIDs, validations))
|
||||
// Build a reliability score table of validators. This only fails if
|
||||
// there isn't enough ledger history; it does NOT apply the local
|
||||
// reliability gate (that is N-UNL specific, see below).
|
||||
auto const scoreTable =
|
||||
buildRawScoreTable(prevLedger, unlNodeIDs, validations);
|
||||
if (!scoreTable)
|
||||
return;
|
||||
|
||||
auto const seq = prevLedger->info().seq + 1;
|
||||
|
||||
// N-UNL voting: only vote to disable/re-enable others if we ourselves
|
||||
// have been reliable over the last flag ledger interval.
|
||||
if (localNodeReliableForNUNL(*scoreTable, seq))
|
||||
{
|
||||
// build next negUnl
|
||||
auto negUnlKeys = prevLedger->negativeUNL();
|
||||
@@ -82,7 +92,6 @@ NegativeUNLVote::doVoting(
|
||||
}
|
||||
}
|
||||
|
||||
auto const seq = prevLedger->info().seq + 1;
|
||||
purgeNewValidators(seq);
|
||||
|
||||
// Process the table and find all candidates to disable or to re-enable
|
||||
@@ -109,14 +118,33 @@ NegativeUNLVote::doVoting(
|
||||
"ripple::NegativeUNLVote::doVoting : found node to enable");
|
||||
addTx(seq, nidToKeyMap.at(n), ToReEnable, initialSet);
|
||||
}
|
||||
}
|
||||
|
||||
// do reporting when enabled
|
||||
if (prevLedger->rules().enabled(featureXahauGenesis) &&
|
||||
scoreTable->size() > 0)
|
||||
// UNLReport. Previously this sat behind the N-UNL local reliability gate
|
||||
// (>= 90% of our own validations on-chain in the last 256 ledgers). Every
|
||||
// ttUNL_REPORT is its own deterministic txn and is voted on individually
|
||||
// in consensus, so one proposer's skewed view can't change the outcome;
|
||||
// but gating the whole batch per-proposer made the reports all-or-nothing:
|
||||
// whenever fewer than ~half the UNL cleared the 90% bar the entire report
|
||||
// lost the dispute and the flag ledger carried no UNLReport at all.
|
||||
// Use the same bar the report itself uses for "active".
|
||||
if (prevLedger->rules().enabled(featureXahauGenesis) &&
|
||||
!scoreTable->empty())
|
||||
{
|
||||
auto const it = scoreTable->find(myId_);
|
||||
auto const myScore = it == scoreTable->end() ? 0u : it->second;
|
||||
if (myScore > (FLAG_LEDGER_INTERVAL >> 1))
|
||||
{
|
||||
addReportingTx(seq, *scoreTable, nidToKeyMap, initialSet);
|
||||
addImportVLTx(seq, initialSet);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.debug())
|
||||
<< "R-UNL: ledger " << seq << ". Local node only issued "
|
||||
<< myScore << " validations in last " << FLAG_LEDGER_INTERVAL
|
||||
<< " ledgers, not proposing UNLReport.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +305,7 @@ NegativeUNLVote::choose(
|
||||
}
|
||||
|
||||
std::optional<hash_map<NodeID, std::uint32_t>>
|
||||
NegativeUNLVote::buildScoreTable(
|
||||
NegativeUNLVote::buildRawScoreTable(
|
||||
std::shared_ptr<Ledger const> const& prevLedger,
|
||||
hash_set<NodeID> const& unl,
|
||||
RCLValidations& validations)
|
||||
@@ -327,6 +355,14 @@ NegativeUNLVote::buildScoreTable(
|
||||
}
|
||||
}
|
||||
|
||||
return scoreTable;
|
||||
}
|
||||
|
||||
bool
|
||||
NegativeUNLVote::localNodeReliableForNUNL(
|
||||
hash_map<NodeID, std::uint32_t> const& scoreTable,
|
||||
LedgerIndex seq) const
|
||||
{
|
||||
// Return false if the validation message history or local node's
|
||||
// participation in the history is not good.
|
||||
auto const myValidationCount = [&]() -> std::uint32_t {
|
||||
@@ -341,23 +377,33 @@ NegativeUNLVote::buildScoreTable(
|
||||
<< " validations in last " << FLAG_LEDGER_INTERVAL
|
||||
<< " ledgers."
|
||||
<< " The reliability measurement could be wrong.";
|
||||
return {};
|
||||
return false;
|
||||
}
|
||||
else if (
|
||||
myValidationCount > negativeUNLMinLocalValsToVote &&
|
||||
myValidationCount <= FLAG_LEDGER_INTERVAL)
|
||||
{
|
||||
return scoreTable;
|
||||
}
|
||||
else
|
||||
if (myValidationCount > FLAG_LEDGER_INTERVAL)
|
||||
{
|
||||
// cannot happen because validations.getTrustedForLedger does not
|
||||
// return multiple validations of the same ledger from a validator.
|
||||
JLOG(j_.error()) << "N-UNL: ledger " << seq << ". Local node issued "
|
||||
<< myValidationCount << " validations in last "
|
||||
<< FLAG_LEDGER_INTERVAL << " ledgers. Too many!";
|
||||
return {};
|
||||
return false;
|
||||
}
|
||||
// NB: the old code used `>` here, so exactly 230 fell through to the
|
||||
// "Too many!" branch.
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<hash_map<NodeID, std::uint32_t>>
|
||||
NegativeUNLVote::buildScoreTable(
|
||||
std::shared_ptr<Ledger const> const& prevLedger,
|
||||
hash_set<NodeID> const& unl,
|
||||
RCLValidations& validations)
|
||||
{
|
||||
auto scoreTable = buildRawScoreTable(prevLedger, unl, validations);
|
||||
if (scoreTable &&
|
||||
!localNodeReliableForNUNL(*scoreTable, prevLedger->info().seq + 1))
|
||||
return {};
|
||||
return scoreTable;
|
||||
}
|
||||
|
||||
NegativeUNLVote::Candidates const
|
||||
|
||||
@@ -192,6 +192,26 @@ private:
|
||||
NodeID
|
||||
choose(uint256 const& randomPadData, std::vector<NodeID> const& candidates);
|
||||
|
||||
/**
|
||||
* Count trusted full validations per UNL validator over the last
|
||||
* FLAG_LEDGER_INTERVAL ledgers. Fails only on insufficient ledger
|
||||
* history; does not apply the local reliability gate.
|
||||
*/
|
||||
std::optional<hash_map<NodeID, std::uint32_t>>
|
||||
buildRawScoreTable(
|
||||
std::shared_ptr<Ledger const> const& prevLedger,
|
||||
hash_set<NodeID> const& unl,
|
||||
RCLValidations& validations);
|
||||
|
||||
/**
|
||||
* True if the local node issued enough validations in the score table
|
||||
* to be trusted to vote on N-UNL changes.
|
||||
*/
|
||||
bool
|
||||
localNodeReliableForNUNL(
|
||||
hash_map<NodeID, std::uint32_t> const& scoreTable,
|
||||
LedgerIndex seq) const;
|
||||
|
||||
/**
|
||||
* Build a reliability measurement score table of validators' validation
|
||||
* messages in the last flag ledger period.
|
||||
|
||||
Reference in New Issue
Block a user