mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-07 10:47:05 +00:00
Compare commits
12 Commits
ximinez/on
...
ximinez/sy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe179afb51 | ||
|
|
59c7ad68f2 | ||
|
|
53768ded97 | ||
|
|
7dbd98d3eb | ||
|
|
283e1742ed | ||
|
|
71a1aa95d1 | ||
|
|
eb6ff26f3d | ||
|
|
43c8c719ff | ||
|
|
a83f3e25fd | ||
|
|
96166dcfa0 | ||
|
|
cf8a3588ca | ||
|
|
97059761a6 |
159
bin/git/sync-to-release.sh
Executable file
159
bin/git/sync-to-release.sh
Executable 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
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user