Compare commits

..

12 Commits

Author SHA1 Message Date
Ed Hennis
fe179afb51 Merge branch 'develop' into ximinez/sync-script 2026-05-20 07:21:29 -04:00
Ed Hennis
59c7ad68f2 Merge branch 'develop' into ximinez/sync-script 2026-05-14 20:39:57 -04:00
Ed Hennis
53768ded97 Merge branch 'develop' into ximinez/sync-script 2026-05-14 10:49:20 -04:00
Ed Hennis
7dbd98d3eb Merge branch 'develop' into ximinez/sync-script 2026-05-13 12:04:40 -04:00
Ed Hennis
283e1742ed Merge branch 'develop' into ximinez/sync-script 2026-05-12 20:12:06 -04:00
Ed Hennis
71a1aa95d1 Merge branch 'develop' into ximinez/sync-script 2026-05-07 18:11:00 -04:00
Ed Hennis
eb6ff26f3d Merge branch 'develop' into ximinez/sync-script 2026-05-07 14:20:09 -04:00
Ed Hennis
43c8c719ff Merge branch 'develop' into ximinez/sync-script 2026-05-07 13:29:28 -04:00
Ed Hennis
a83f3e25fd Merge branch 'develop' into ximinez/sync-script 2026-05-06 22:35:21 -04:00
Ed Hennis
96166dcfa0 Merge branch 'develop' into ximinez/sync-script 2026-05-06 14:19:02 -04:00
Ed Hennis
cf8a3588ca Merge branch 'develop' into ximinez/sync-script 2026-05-05 15:21:38 -04:00
Ed Hennis
97059761a6 chore: Add script to cherry-pick new commits from develop to a release 2026-05-04 19:56:08 -04:00
10 changed files with 194 additions and 390 deletions

159
bin/git/sync-to-release.sh Executable file
View File

@@ -0,0 +1,159 @@
#!/bin/bash -e
set -o pipefail
_usage()
{
self=$( basename -- $( echo $0 ) )
cat << USAGE
Usage: $self release_branch develop_branch working_branch [new_version]
This script is intended to be used to synchronize changes between two
branches where the "release_branch" has a few insignificant changes (e.g.
version numbers) compared to the "develop_branch", which has active
ongoing development. It has not (yet) been tested in scenarios where the
branches have significantly diverged, or where one has been merged to
the other.
release_branch: Usually a pending release branch, this is the low
activity branch which needs to be updated from develop_branch.
develop_branch: The active development branch.
working_branch: This branch will be created from the release_branch to
have the new commits applied. If it exists, it will be assumed to have
been created by an earlier invocation, and will be used for
comparisons instead of release_branch. When the script is done, this
branch will NOT be pushed. The user must manually push the branch and
create a pull request.
new_version: OPTIONAL. If provided, will update the version string in
BuildInfo.cpp to this value, and create a version_setting commit.
USAGE
if [[ $# -ne 0 ]]; then
echo "Error: ${@}"
echo
fi
exit 1
}
if [[ $# -lt 3 || $# -gt 4 ]]; then
_usage
fi
release_branch="$1"
shift
develop_branch="$1"
shift
working_branch="$1"
shift
if [[ $# -ne 0 ]]; then
new_version="$1"
shift
fi
_run()
{
echo
echo "\$ ${@}"
"${@}"
}
if git rev-parse --quiet --verify --end-of-options "refs/heads/${working_branch}" > /dev/null; then
release_branch="${working_branch}"
_run git checkout "${working_branch}"
else
_run git checkout --no-track -B "${working_branch}" "${release_branch}"
fi
# Fetch if it hasn't been done in a while
if find "$( git rev-parse --git-dir )/FETCH_HEAD" -type f -mmin +10 | grep -q "FETCH"; then
_run git fetch --all
fi
temp=$( mktemp )
# The "word-diff" option doesn't seem to have any meaningful effect on the output.
# This may make this script fragile across git versions with differing output formats.
_run git range-diff --word-diff=porcelain --no-patch --right-only \
"${release_branch}"..."${develop_branch}" | tee "${temp}"
first_commit=$( cat "${temp}" | \
awk '( $1 == "-:" && $2 ~ /-+/ && $3 == ">" ) { print $5; exit; }' )
if [[ "${first_commit}" == "" && "${new_version}" == "" ]]; then
_usage No divergence found
exit 1
fi
if [[ "${first_commit}" == "" ]]; then
echo -e "\nNo divergence found. Continue to update version number"
else
echo -e "\nFound first unique commit in ${develop_branch}: ${first_commit}"
first_commit=$( git rev-parse --quiet "${first_commit}" )
echo "Full commit ID is: ${first_commit}"
merge_base=$( git merge-base --octopus "${first_commit}" "${develop_branch}" )
if [[ "${first_commit}" != "${merge_base}" ]]; then
echo "Bad first commit: ${first_commit} is not an ancestor of ${develop_branch}"
exit 1
fi
_run git diff "${first_commit}~" "${release_branch}"
read -n 1 -s -p "Does the diff look reasonable? (y to continue / any other key to abort)" continue
echo
if [[ ! ( "${continue}" =~ [yY] ) ]]; then
echo Abort
exit 1
fi
# Dilemma: if the cherry-pick is interrupted, for example, because of a conflict, the script
# will abort, and can not easily be resumed. This will prevent the final step(s) from running.
if ! _run git cherry-pick --empty=drop "${first_commit}~..${develop_branch}"; then
# Solution: if the cherry-pick is interrupted, run the command again.
if [[ "${new_version}" != "" ]]; then
echo
echo "Cherry pick failed or was interrupted. Resolve issues, finish the"
echo " cherry pick, then run this script again to with the same parameters"
echo " to update the version number."
fi
exit 1
fi
if [[ "${new_version}" == "" ]]; then
# If there is no version number change to make, we're done.
exit 0
fi
fi
# There should only be one matching file, but loop in case not.
buildfiles=( $( git ls-files | grep "BuildInfo.cpp" ) )
for build in "${buildfiles[@]}"; do
sed 's/\(^.*versionString =\).*$/\1 "'${new_version}'"/' ${build} > version.cpp && \
_run diff "${build}" version.cpp && exit 1 || \
mv -vi version.cpp ${build}
git add ${build}
done
_run git commit -S -m "Set version to ${new_version}"
_run git log --oneline --first-parent ${release_branch}^..
cat << PUSH
-------------------------------------------------------------------
This script will not push to remote. Verify everything is correct,
then push, and create a PR as described in CONTRIBUTING.md.
-------------------------------------------------------------------
PUSH

View File

@@ -1044,11 +1044,10 @@
# The online delete process checks periodically
# that xrpld is still in sync with the network,
# and that the validated ledger is less than
# 'age_threshold_seconds' old, and that all
# recent ledgers are available. If not, then continue
# 'age_threshold_seconds' old. If not, then continue
# sleeping for this number of seconds and
# checking until healthy.
# Default is 1.
# Default is 5.
#
# Notes:
# The 'node_db' entry configures the primary, persistent storage.

View File

@@ -5,21 +5,17 @@
#include <test/jtx/noop.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/misc/SHAMapStore.h>
#include <xrpld/core/Config.h>
#include <xrpl/basics/ToString.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <cstdint>
#include <memory>
#include <sstream>
#include <vector>
namespace xrpl::test {
@@ -115,74 +111,6 @@ class LedgerMaster_test : public beast::unit_test::Suite
}
}
void
testCompleteLedgerRange(FeatureBitset features)
{
// Note that this test is intentionally very similar to
// SHAMapStore_test::testLedgerGaps, but has a different
// focus.
testcase("Complete Ledger operations");
using namespace test::jtx;
auto const deleteInterval = 8;
Env env{*this, envconfig([](auto cfg) {
return online_delete(std::move(cfg), deleteInterval);
})};
auto const alice = Account("alice");
env.fund(XRP(1000), alice);
env.close();
auto& lm = env.app().getLedgerMaster();
LedgerIndex minSeq = 2;
LedgerIndex maxSeq = env.closed()->header().seq;
auto& store = env.app().getSHAMapStore();
store.rendezvous();
LedgerIndex lastRotated = store.getLastRotated();
BEAST_EXPECTS(maxSeq == 3, to_string(maxSeq));
BEAST_EXPECTS(lm.getCompleteLedgers() == "2-3", lm.getCompleteLedgers());
BEAST_EXPECTS(lastRotated == 3, to_string(lastRotated));
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2);
// Close enough ledgers to rotate a few times
for (int i = 0; i < 24; ++i)
{
for (int t = 0; t < 3; ++t)
{
env(noop(alice));
}
env.close();
store.rendezvous();
++maxSeq;
if (maxSeq == lastRotated + deleteInterval)
{
minSeq = lastRotated;
lastRotated = store.getLastRotated();
BEAST_EXPECT(lastRotated = maxSeq + 2);
}
BEAST_EXPECTS(
env.closed()->header().seq == maxSeq, to_string(env.closed()->header().seq));
BEAST_EXPECTS(store.getLastRotated() == lastRotated, to_string(store.getLastRotated()));
std::stringstream expectedRange;
expectedRange << minSeq << "-" << maxSeq;
BEAST_EXPECTS(lm.getCompleteLedgers() == expectedRange.str(), lm.getCompleteLedgers());
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2);
}
}
public:
void
run() override
@@ -196,7 +124,6 @@ public:
testWithFeats(FeatureBitset features)
{
testTxnIdFromIndex(features);
testCompleteLedgerRange(features);
}
};

View File

@@ -1,9 +1,7 @@
#include <test/jtx/Env.h>
#include <test/jtx/amount.h>
#include <test/jtx/envconfig.h>
#include <test/jtx/noop.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/main/Application.h>
#include <xrpld/app/main/NodeStoreScheduler.h>
#include <xrpld/app/misc/SHAMapStore.h>
@@ -12,7 +10,6 @@
#include <xrpld/core/ConfigSections.h>
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
@@ -32,9 +29,7 @@
#include <map>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <thread>
#include <utility>
namespace xrpl::test {
@@ -46,8 +41,10 @@ class SHAMapStore_test : public beast::unit_test::Suite
static auto
onlineDelete(std::unique_ptr<Config> cfg)
{
using namespace jtx;
return online_delete(std::move(cfg), kDeleteInterval);
cfg->LEDGER_HISTORY = kDeleteInterval;
auto& section = cfg->section(ConfigSection::nodeDatabase());
section.set("online_delete", std::to_string(kDeleteInterval));
return cfg;
}
static auto
@@ -231,10 +228,9 @@ public:
store.rendezvous();
BEAST_EXPECT(env.closed()->header().seq == kDeleteInterval + 3);
BEAST_EXPECT(store.getLastRotated() == kDeleteInterval + 3);
lastRotated = store.getLastRotated();
BEAST_EXPECT(lastRotated == kDeleteInterval + 5);
BEAST_EXPECT(lastRotated == 13);
BEAST_EXPECT(lastRotated == 11);
// That took care of the fake hashes
ledgerCheck(env, kDeleteInterval + 1, 3);
@@ -242,10 +238,8 @@ public:
accountTransactionCheck(env, 2 * kDeleteInterval);
// The last iteration of this loop should trigger a rotate
for (auto i = lastRotated - 3; i < lastRotated + kDeleteInterval - 1; ++i)
for (auto i = lastRotated - 1; i < lastRotated + kDeleteInterval - 1; ++i)
{
BEAST_EXPECT(store.getLastRotated() == lastRotated);
env.close();
ledgerTmp = env.rpc("ledger", "current");
@@ -261,7 +255,7 @@ public:
store.rendezvous();
BEAST_EXPECT(store.getLastRotated() == kDeleteInterval + lastRotated + 2);
BEAST_EXPECT(store.getLastRotated() == kDeleteInterval + lastRotated);
ledgerCheck(env, kDeleteInterval + 1, lastRotated);
transactionCheck(env, 0);
@@ -394,8 +388,8 @@ public:
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
lastRotated = store.getLastRotated();
BEAST_EXPECT(lastRotated == ledgerSeq + 1);
BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
lastRotated = ledgerSeq - 1;
for (; ledgerSeq < lastRotated + kDeleteInterval; ++ledgerSeq)
{
@@ -420,10 +414,10 @@ public:
store.rendezvous();
ledgerCheck(env, ledgerSeq - firstBatch - 2, firstBatch + 2);
ledgerCheck(env, ledgerSeq - firstBatch, firstBatch);
lastRotated = store.getLastRotated();
BEAST_EXPECT(lastRotated == ledgerSeq + 1);
BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
lastRotated = ledgerSeq - 1;
// This does not kick off a cleanup
canDelete = env.rpc("can_delete", "always");
@@ -456,13 +450,13 @@ public:
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
lastRotated = store.getLastRotated();
BEAST_EXPECT(lastRotated == ledgerSeq + 1);
BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
lastRotated = ledgerSeq - 1;
// This does not kick off a cleanup
canDelete = env.rpc("can_delete", "now");
BEAST_EXPECT(!RPC::containsError(canDelete[jss::result]));
BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == lastRotated);
BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == ledgerSeq - 1);
for (; ledgerSeq < lastRotated + kDeleteInterval; ++ledgerSeq)
{
@@ -489,8 +483,8 @@ public:
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
lastRotated = store.getLastRotated();
BEAST_EXPECT(lastRotated == ledgerSeq + 1);
BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
lastRotated = ledgerSeq - 1;
}
std::unique_ptr<NodeStore::Backend>
@@ -590,187 +584,6 @@ public:
BEAST_EXPECT(dbr->getName() == "3");
}
void
testLedgerGaps()
{
// Note that this test is intentionally very similar to
// LedgerMaster_test::testCompleteLedgerRange, but has a different
// focus.
testcase("Wait for ledger gaps to fill in");
using namespace test::jtx;
Env env{*this, envconfig(onlineDelete)};
auto& ns = env.app().getNodeStore();
std::map<LedgerIndex, uint256> hashes;
auto storeHash = [&]() {
hashes.emplace(env.current()->header().seq, env.current()->header().hash);
auto const& root = ns.fetchNodeObject(hashes[env.current()->header().seq]);
BEAST_EXPECT(root);
};
auto failureMessage = [&](char const* label, auto expected, auto actual) {
std::stringstream ss;
ss << label << ": Expected: " << expected << ", Got: " << actual;
return ss.str();
};
auto const alice = Account("alice");
env.fund(XRP(1000), alice);
env.close();
storeHash();
auto& lm = env.app().getLedgerMaster();
LedgerIndex minSeq = 2;
LedgerIndex maxSeq = env.closed()->header().seq;
auto& store = env.app().getSHAMapStore();
store.rendezvous();
LedgerIndex lastRotated = store.getLastRotated();
BEAST_EXPECTS(maxSeq == 3, std::to_string(maxSeq));
BEAST_EXPECTS(lm.getCompleteLedgers() == "2-3", lm.getCompleteLedgers());
BEAST_EXPECTS(lastRotated == 3, to_string(lastRotated));
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2);
// Close enough ledgers to rotate a few times
while (maxSeq < 28)
{
for (int t = 0; t < 3; ++t)
{
env(noop(alice));
}
env.close();
storeHash();
store.rendezvous();
++maxSeq;
if (maxSeq + 1 == lastRotated + kDeleteInterval)
{
using namespace std::chrono_literals;
// The next ledger will trigger a rotation. Delete the
// current ledger from LedgerMaster.
std::this_thread::sleep_for(100ms);
LedgerIndex const deleteSeq = maxSeq;
while (!lm.haveLedger(deleteSeq))
{
std::this_thread::sleep_for(100ms);
}
lm.clearLedger(deleteSeq);
auto expectedRange = [](auto minSeq, auto deleteSeq, auto maxSeq) {
std::stringstream expectedRange;
expectedRange << minSeq << "-" << (deleteSeq - 1);
if (deleteSeq + 1 == maxSeq)
{
expectedRange << "," << maxSeq;
}
else if (deleteSeq < maxSeq)
{
expectedRange << "," << (deleteSeq + 1) << "-" << maxSeq;
}
return expectedRange.str();
};
BEAST_EXPECTS(
lm.getCompleteLedgers() == expectedRange(minSeq, deleteSeq, maxSeq),
failureMessage(
"Complete ledgers",
expectedRange(minSeq, deleteSeq, maxSeq),
lm.getCompleteLedgers()));
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 1);
// Close another ledger, which will trigger a rotation, but the
// rotation will be stuck until the missing ledger is filled in.
env.close();
storeHash();
// DO NOT CALL rendezvous()! You'll end up with a deadlock.
++maxSeq;
// Nothing has changed
BEAST_EXPECTS(
store.getLastRotated() == lastRotated,
failureMessage("lastRotated", lastRotated, store.getLastRotated()));
BEAST_EXPECTS(
lm.getCompleteLedgers() == expectedRange(minSeq, deleteSeq, maxSeq),
failureMessage(
"Complete ledgers",
expectedRange(minSeq, deleteSeq, maxSeq),
lm.getCompleteLedgers()));
// Close 5 more ledgers, waiting one second in between to
// simulate the ledger making progress while online delete waits
// for the missing ledger to be filled in.
// This ensures the healthWait check has time to run and
// detect the gap.
for (int l = 0; l < 5; ++l)
{
env.close();
storeHash();
// DO NOT CALL rendezvous()! You'll end up with a deadlock.
++maxSeq;
// Nothing has changed
BEAST_EXPECTS(
store.getLastRotated() == lastRotated,
failureMessage("lastRotated", lastRotated, store.getLastRotated()));
BEAST_EXPECTS(
lm.getCompleteLedgers() == expectedRange(minSeq, deleteSeq, maxSeq),
failureMessage(
"Complete Ledgers",
expectedRange(minSeq, deleteSeq, maxSeq),
lm.getCompleteLedgers()));
std::this_thread::sleep_for(1s);
}
// Put the missing ledger back in LedgerMaster
lm.setLedgerRangePresent(deleteSeq, deleteSeq);
// Wait for the rotation to finish
store.rendezvous();
minSeq = lastRotated;
lastRotated = maxSeq + 2;
// Bypass caching and try to load the ledger roots from the node
// store
for (auto const& [seq, hash] : hashes)
{
auto const nodeObject = ns.fetchNodeObject(hash);
std::stringstream ss;
ss << "minSeq: " << minSeq << ", maxSeq: " << maxSeq << ", search: " << seq
<< ". Should " << (seq < minSeq ? "NOT " : "") << "be found";
if (seq < minSeq)
BEAST_EXPECTS(!nodeObject, ss.str());
else
BEAST_EXPECTS(nodeObject, ss.str());
}
}
BEAST_EXPECT(maxSeq != lastRotated + kDeleteInterval);
BEAST_EXPECTS(
env.closed()->header().seq == maxSeq,
failureMessage("maxSeq", maxSeq, env.closed()->header().seq));
BEAST_EXPECTS(
store.getLastRotated() == lastRotated,
failureMessage("lastRotated", lastRotated, store.getLastRotated()));
std::stringstream expectedRange;
expectedRange << minSeq << "-" << maxSeq;
BEAST_EXPECTS(
lm.getCompleteLedgers() == expectedRange.str(),
failureMessage("CompleteLedgers", expectedRange.str(), lm.getCompleteLedgers()));
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2);
}
}
void
run() override
{
@@ -778,7 +591,6 @@ public:
testAutomatic();
testCanDelete();
testRotate();
testLedgerGaps();
}
};

View File

@@ -56,17 +56,6 @@ envconfig(F&& modfunc, Args&&... args)
return modfunc(envconfig(), std::forward<Args>(args)...);
}
/// @brief adjust config to enable online_delete
///
/// @param cfg config instance to be modified
///
/// @param deleteInterval how many new ledgers should be available before
/// rotating. Defaults to 8, because the standalone minimum is 8.
///
/// @return unique_ptr to Config instance
std::unique_ptr<Config>
online_delete(std::unique_ptr<Config> cfg, std::uint32_t deleteInterval = 8);
/// @brief adjust config so no admin ports are enabled
///
/// this is intended for use with envconfig, as in

View File

@@ -6,10 +6,8 @@
#include <xrpld/core/ConfigSections.h>
#include <atomic>
#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace xrpl::test {
@@ -61,15 +59,6 @@ setupConfigForUnitTests(Config& cfg)
namespace jtx {
std::unique_ptr<Config>
online_delete(std::unique_ptr<Config> cfg, std::uint32_t deleteInterval)
{
cfg->LEDGER_HISTORY = deleteInterval;
auto& section = cfg->section(ConfigSection::nodeDatabase());
section.set("online_delete", std::to_string(deleteInterval));
return cfg;
}
std::unique_ptr<Config>
noAdmin(std::unique_ptr<Config> cfg)
{

View File

@@ -105,10 +105,7 @@ public:
failedSave(std::uint32_t seq, uint256 const& hash);
std::string
getCompleteLedgers() const;
std::size_t
missingFromCompleteLedgerRange(LedgerIndex first, LedgerIndex last) const;
getCompleteLedgers();
/** Apply held transactions to the open ledger
This is normally called as we close the ledger.
@@ -325,7 +322,7 @@ private:
// A set of transactions to replay during the next close
std::unique_ptr<LedgerReplay> replayData_;
std::recursive_mutex mutable completeLock_;
std::recursive_mutex completeLock_;
RangeSet<std::uint32_t> completeLedgers_;
// Publish thread is running.

View File

@@ -1568,34 +1568,12 @@ LedgerMaster::getPublishedLedger()
}
std::string
LedgerMaster::getCompleteLedgers() const
LedgerMaster::getCompleteLedgers()
{
std::scoped_lock const sl(completeLock_);
return to_string(completeLedgers_);
}
std::size_t
LedgerMaster::missingFromCompleteLedgerRange(LedgerIndex first, LedgerIndex last) const
{
// Make a copy of the range to avoid holding the lock
auto const range = [&] {
std::scoped_lock const sl(completeLock_);
return completeLedgers_;
}();
std::size_t missing = 0;
for (LedgerIndex idx = first; idx <= last; ++idx)
{
if (!boost::icl::contains(range, idx))
{
++missing;
}
}
return missing;
}
std::optional<NetClock::time_point>
LedgerMaster::getCloseTimeBySeq(LedgerIndex ledgerIndex)
{

View File

@@ -292,17 +292,6 @@ SHAMapStoreImp::run()
bool const readyToRotate = validatedSeq >= lastRotated + deleteInterval_ &&
canDelete_ >= lastRotated - 1 && healthWait() == HealthResult::KeepGoing;
JLOG(journal_.debug()) << "run: Setting lastGoodValidatedLedger_ to " << validatedSeq;
{
// Note that this is set after the healthWait() check, so that we
// don't start the rotation until the validated ledger is fully
// processed. It is not guaranteed to be done at this point. It also
// allows the testLedgerGaps unit test to work.
std::unique_lock<std::mutex> const lock(mutex_);
lastGoodValidatedLedger_ = validatedSeq;
}
// will delete up to (not including) lastRotated
if (readyToRotate)
{
@@ -310,8 +299,7 @@ SHAMapStoreImp::run()
<< lastRotated << " deleteInterval " << deleteInterval_
<< " canDelete_ " << canDelete_ << " state "
<< app_.getOPs().strOperatingMode(false) << " age "
<< ledgerMaster_->getValidatedLedgerAge().count()
<< "s. Complete ledgers: " << ledgerMaster_->getCompleteLedgers();
<< ledgerMaster_->getValidatedLedgerAge().count() << 's';
clearPrior(lastRotated);
if (healthWait() == HealthResult::Stopping)
@@ -357,13 +345,7 @@ SHAMapStoreImp::run()
if (healthWait() == HealthResult::Stopping)
return;
// The rotation process takes time, and more ledgers - possibly many
// more - may have been written to the now-archive database before
// the writable database was created. Update the validatedSeq to the
// current validated ledger sequence plus a buffer to ensure that
// all modified nodes for that ledger forward are all written to the
// new writable database.
lastRotated = ledgerMaster_->getValidLedgerIndex() + 2;
lastRotated = validatedSeq;
dbRotating_->rotate(
std::move(newBackend),
@@ -377,9 +359,7 @@ SHAMapStoreImp::run()
clearCaches(validatedSeq);
});
JLOG(journal_.warn()) << "finished rotation. validatedSeq: " << validatedSeq
<< ", lastRotated: " << lastRotated
<< ". Complete ledgers: " << ledgerMaster_->getCompleteLedgers();
JLOG(journal_.warn()) << "finished rotation " << validatedSeq;
}
}
}
@@ -619,41 +599,22 @@ SHAMapStoreImp::clearPrior(LedgerIndex lastRotated)
SHAMapStoreImp::HealthResult
SHAMapStoreImp::healthWait()
{
auto index = ledgerMaster_->getValidLedgerIndex();
auto age = ledgerMaster_->getValidatedLedgerAge();
OperatingMode mode = netOPs_->getOperatingMode();
std::unique_lock lock(mutex_);
auto numMissing =
ledgerMaster_->missingFromCompleteLedgerRange(lastGoodValidatedLedger_, index);
while (!stop_ && (mode != OperatingMode::FULL || age > ageThreshold_ || numMissing > 0))
while (!stop_ && (mode != OperatingMode::FULL || age > ageThreshold_))
{
// this value shouldn't change, so grab it while we have the
// lock
auto const lowerBound = lastGoodValidatedLedger_;
lock.unlock();
auto const stream =
mode != OperatingMode::FULL || age > ageThreshold_ ? journal_.warn() : journal_.info();
JLOG(stream) << "Waiting " << recoveryWaitTime_.count()
<< "s for node to stabilize. state: "
<< app_.getOPs().strOperatingMode(mode, false) << ". age " << age.count()
<< "s. Missing ledgers: " << numMissing << ". Expect: " << lowerBound << "-"
<< index << ". Complete ledgers: " << ledgerMaster_->getCompleteLedgers();
JLOG(journal_.warn()) << "Waiting " << recoveryWaitTime_.count()
<< "s for node to stabilize. state: "
<< app_.getOPs().strOperatingMode(mode, false) << ". age "
<< age.count() << 's';
std::this_thread::sleep_for(recoveryWaitTime_);
index = ledgerMaster_->getValidLedgerIndex();
age = ledgerMaster_->getValidatedLedgerAge();
mode = netOPs_->getOperatingMode();
numMissing =
lowerBound == 0 ? 0 : ledgerMaster_->missingFromCompleteLedgerRange(lowerBound, index);
lock.lock();
}
JLOG(journal_.debug()) << "healthWait: Setting lastGoodValidatedLedger_ to " << index;
lastGoodValidatedLedger_ = index;
return stop_ ? HealthResult::Stopping : HealthResult::KeepGoing;
}

View File

@@ -70,11 +70,6 @@ private:
std::thread thread_;
bool stop_ = false;
bool healthy_ = true;
// Used to prevent ledger gaps from forming during online deletion. Keeps
// track of the last validated ledger that was processed without gaps. There
// are no guarantees about gaps while online delete is not running. For
// that, use advisory_delete and check for gaps externally.
LedgerIndex lastGoodValidatedLedger_ = 0;
mutable std::condition_variable cond_;
mutable std::condition_variable rendezvous_;
mutable std::mutex mutex_;
@@ -88,11 +83,11 @@ private:
std::uint32_t deleteBatch_ = 100;
std::chrono::milliseconds backOff_{100};
std::chrono::seconds ageThreshold_{60};
/// If the node is out of sync, or any recent ledgers are not
/// available during an online_delete healthWait() call, sleep
/// the thread for this time, and continue checking until recovery.
/// If the node is out of sync during an online_delete healthWait()
/// call, sleep the thread for this time, and continue checking until
/// recovery.
/// See also: "recovery_wait_seconds" in xrpld-example.cfg
std::chrono::seconds recoveryWaitTime_{1};
std::chrono::seconds recoveryWaitTime_{5};
// these do not exist upon SHAMapStore creation, but do exist
// as of run() or before
@@ -209,8 +204,6 @@ private:
enum class HealthResult { Stopping, KeepGoing };
[[nodiscard]] HealthResult
healthWait();
bool
hasCompleteRange(LedgerIndex first, LedgerIndex last);
public:
void