mirror of
https://github.com/Xahau/xahaud.git
synced 2026-09-24 14:20:14 +00:00
Compare commits
21 Commits
emit-atomi
...
harness-fo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2260e3b0c9 | ||
|
|
fe43214c4b | ||
|
|
bf6fa019ba | ||
|
|
1f8e30537f | ||
|
|
54604e7a9a | ||
|
|
d3d5e86dec | ||
|
|
99465e29e1 | ||
|
|
d8096685ee | ||
|
|
52f112d52b | ||
|
|
cf2ed8c299 | ||
|
|
15eed3a497 | ||
|
|
3d7c3236fc | ||
|
|
1da084b3b8 | ||
|
|
541edc48c9 | ||
|
|
cd9fb29eec | ||
|
|
fd85c5d122 | ||
|
|
723f4f28b6 | ||
|
|
9e7805e5aa | ||
|
|
cff7ea906c | ||
|
|
15739bf13d | ||
|
|
e08de61273 |
25
.github/workflows/rng-tripwire.yml
vendored
Normal file
25
.github/workflows/rng-tripwire.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: rng-tripwire
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check for vendor-defined random algorithms
|
||||
run: python3 Builds/rng_tripwire/rng_tripwire.py
|
||||
- name: What happened?
|
||||
if: failure()
|
||||
env:
|
||||
MESSAGE: |
|
||||
A standard-library random algorithm showed up outside the
|
||||
allowlist. These algorithms are not the same on every standard
|
||||
library, so a new call site can change a deterministic trace.
|
||||
|
||||
Either draw through ripple::rand_int, or add the path to
|
||||
Builds/rng_tripwire/allowlist.txt with a one-line justification.
|
||||
Comments are ignored. See Builds/rng_tripwire/allowlist.txt.
|
||||
run: |
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
13
Builds/rng_tripwire/allowlist.txt
Normal file
13
Builds/rng_tripwire/allowlist.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
# path-prefix justification
|
||||
# A hit under src/ or include/ is accepted only when its path starts with one
|
||||
# of these prefixes. The justification is for reviewers; the script ignores it.
|
||||
|
||||
src/test/ Tests are not on the consensus schedule.
|
||||
include/xrpl/basics/random.h Seeding overloads only. rand_int does not call these distributions.
|
||||
include/xrpl/basics/hardened_hash.h One-time hash salt drawn from OS entropy.
|
||||
include/xrpl/crypto/csprng.h CSPRNG declaration. OS entropy, not a consensus draw.
|
||||
src/libxrpl/crypto/csprng.cpp OS entropy for the CSPRNG.
|
||||
src/xrpld/peerfinder/ default_prng shuffles. Not the harness engine.
|
||||
src/xrpld/overlay/detail/PeerImp.cpp Fault-injection seeds. RuntimeConfig only, not the harness engine.
|
||||
src/xrpld/app/consensus/ConsensusExtensions.cpp Fault-injection seeds. RuntimeConfig only, not the harness engine.
|
||||
src/xrpld/overlay/detail/OverlayImpl.cpp std::shuffle of relay peers. Runs only when TX_REDUCE_RELAY_ENABLE is on, and that flag is off. Uses app_.getPrng when it does run.
|
||||
125
Builds/rng_tripwire/rng_tripwire.py
Normal file
125
Builds/rng_tripwire/rng_tripwire.py
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fail if a vendor-defined <random> algorithm appears outside the allowlist.
|
||||
|
||||
Scans src/ and include/ for the standard algorithms whose mapping is not
|
||||
portable across standard libraries. Comments are ignored. A hit is accepted
|
||||
only when its path is listed in allowlist.txt. Unlisted hits are printed as
|
||||
file:line and the process exits non-zero.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ALGORITHMS = (
|
||||
"uniform_int_distribution",
|
||||
"uniform_real_distribution",
|
||||
"bernoulli_distribution",
|
||||
"binomial_distribution",
|
||||
"negative_binomial_distribution",
|
||||
"geometric_distribution",
|
||||
"poisson_distribution",
|
||||
"exponential_distribution",
|
||||
"gamma_distribution",
|
||||
"weibull_distribution",
|
||||
"extreme_value_distribution",
|
||||
"normal_distribution",
|
||||
"lognormal_distribution",
|
||||
"chi_squared_distribution",
|
||||
"cauchy_distribution",
|
||||
"fisher_f_distribution",
|
||||
"student_t_distribution",
|
||||
"discrete_distribution",
|
||||
"piecewise_constant_distribution",
|
||||
"piecewise_linear_distribution",
|
||||
"sample",
|
||||
"shuffle",
|
||||
"generate_canonical",
|
||||
"random_device",
|
||||
)
|
||||
|
||||
HIT = re.compile(r"\bstd::(?:" + "|".join(ALGORITHMS) + r")\b")
|
||||
SUFFIXES = {".h", ".hh", ".hpp", ".cpp", ".cc", ".cxx", ".ipp", ".inc"}
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
here = Path(__file__).resolve().parent
|
||||
for candidate in (here, *here.parents):
|
||||
if (candidate / "src").is_dir() and (candidate / "include").is_dir():
|
||||
return candidate
|
||||
sys.exit("rng_tripwire: cannot find the repository root")
|
||||
|
||||
|
||||
def load_allowlist(path: Path) -> list[tuple[str, str]]:
|
||||
entries = []
|
||||
for raw in path.read_text().splitlines():
|
||||
line = raw.split("#", 1)[0].strip()
|
||||
if not line:
|
||||
continue
|
||||
prefix, _, why = line.partition(" ")
|
||||
entries.append((prefix.strip(), why.strip()))
|
||||
return entries
|
||||
|
||||
|
||||
def allowed(rel: str, entries: list[tuple[str, str]]) -> bool:
|
||||
for prefix, _why in entries:
|
||||
if rel == prefix or rel.startswith(prefix):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def strip_comments(text: str) -> list[str]:
|
||||
lines = []
|
||||
in_block = False
|
||||
for line in text.splitlines():
|
||||
out = []
|
||||
i = 0
|
||||
while i < len(line):
|
||||
if in_block:
|
||||
end = line.find("*/", i)
|
||||
if end < 0:
|
||||
i = len(line)
|
||||
break
|
||||
in_block = False
|
||||
i = end + 2
|
||||
continue
|
||||
if line.startswith("//", i):
|
||||
break
|
||||
if line.startswith("/*", i):
|
||||
in_block = True
|
||||
i += 2
|
||||
continue
|
||||
out.append(line[i])
|
||||
i += 1
|
||||
lines.append("".join(out))
|
||||
return lines
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = repo_root()
|
||||
allow_path = Path(__file__).resolve().parent / "allowlist.txt"
|
||||
entries = load_allowlist(allow_path)
|
||||
hits = []
|
||||
for base in ("src", "include"):
|
||||
for path in sorted((root / base).rglob("*")):
|
||||
if not path.is_file() or path.suffix not in SUFFIXES:
|
||||
continue
|
||||
rel = path.relative_to(root).as_posix()
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except UnicodeError:
|
||||
text = path.read_text(encoding="latin-1")
|
||||
for number, line in enumerate(strip_comments(text), start=1):
|
||||
if HIT.search(line):
|
||||
hits.append((allowed(rel, entries), f"{rel}:{number}"))
|
||||
unlisted = [item for ok, item in hits if not ok]
|
||||
for ok, item in hits:
|
||||
print(("ALLOW " if ok else "HIT ") + item)
|
||||
print(f"{len(hits)} hits, {len(unlisted)} unlisted")
|
||||
for item in unlisted:
|
||||
print(item)
|
||||
return 1 if unlisted else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/beast/xor_shift_engine.h>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@@ -49,6 +50,87 @@ namespace detail {
|
||||
// Determines if a type can be called like an Engine
|
||||
template <class Engine, class Result = typename Engine::result_type>
|
||||
using is_engine = std::is_invocable_r<Result, Engine>;
|
||||
|
||||
// 64 bits from the engine. Width comes from max()-min(), not from the
|
||||
// storage type: a 32-bit engine may use a 64-bit result_type.
|
||||
template <class Engine>
|
||||
std::uint64_t
|
||||
randomU64(Engine& engine)
|
||||
{
|
||||
static_assert(std::is_unsigned_v<typename Engine::result_type>);
|
||||
static_assert(
|
||||
std::numeric_limits<typename Engine::result_type>::digits <= 64);
|
||||
static_assert(Engine::min() < Engine::max());
|
||||
auto const draw = [&engine]() -> std::uint64_t {
|
||||
return static_cast<std::uint64_t>(engine() - Engine::min());
|
||||
};
|
||||
|
||||
// A wrapped cardinality of 0 means 2^64 values: one full-range draw.
|
||||
constexpr auto span =
|
||||
static_cast<std::uint64_t>(Engine::max() - Engine::min());
|
||||
constexpr std::uint64_t range = span + 1u;
|
||||
constexpr bool full = range == 0;
|
||||
constexpr bool powerOfTwo = full || (range & (range - 1u)) == 0;
|
||||
|
||||
if constexpr (powerOfTwo)
|
||||
{
|
||||
constexpr int width = full ? 64 : std::bit_width(range) - 1;
|
||||
if constexpr (width >= 64)
|
||||
return draw();
|
||||
|
||||
std::uint64_t value = 0;
|
||||
int filled = 0;
|
||||
while (filled < 64)
|
||||
{
|
||||
auto const take = width < (64 - filled) ? width : (64 - filled);
|
||||
auto const mask = (std::uint64_t{1} << take) - 1;
|
||||
value |= (draw() & mask) << filled;
|
||||
filled += take;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// [rand.adapt.ibits] for w = 64. R is not a power of two.
|
||||
constexpr int m = std::bit_width(range) - 1;
|
||||
constexpr int nCeil = (64 + m - 1) / m;
|
||||
constexpr int w0Try = 64 / nCeil;
|
||||
constexpr auto y0Try = (std::uint64_t{1} << w0Try) * (range >> w0Try);
|
||||
constexpr bool bump =
|
||||
(range - y0Try) > (y0Try / static_cast<std::uint64_t>(nCeil));
|
||||
constexpr int n = bump ? nCeil + 1 : nCeil;
|
||||
constexpr int w0 = 64 / n;
|
||||
constexpr int n0 = n - (64 % n);
|
||||
constexpr auto y0 = (std::uint64_t{1} << w0) * (range >> w0);
|
||||
constexpr auto y1 =
|
||||
(std::uint64_t{1} << (w0 + 1)) * (range >> (w0 + 1));
|
||||
// R == 3 gives n == 65 and w0 == 0. The first group still
|
||||
// consumes its draw even though it contributes no output bits.
|
||||
static_assert(w0 >= 0 && w0 < 63);
|
||||
|
||||
std::uint64_t word = 0;
|
||||
for (int k = 0; k != n0; ++k)
|
||||
{
|
||||
std::uint64_t u;
|
||||
do
|
||||
{
|
||||
u = draw();
|
||||
} while (u >= y0);
|
||||
word = (word << w0) + (u & ((std::uint64_t{1} << w0) - 1));
|
||||
}
|
||||
for (int k = n0; k != n; ++k)
|
||||
{
|
||||
std::uint64_t u;
|
||||
do
|
||||
{
|
||||
u = draw();
|
||||
} while (u >= y1);
|
||||
constexpr int bits = w0 + 1;
|
||||
word = (word << bits) + (u & ((std::uint64_t{1} << bits) - 1));
|
||||
}
|
||||
return word;
|
||||
}
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
/** Return the default random engine.
|
||||
@@ -116,10 +198,24 @@ rand_int(Engine& engine, Integral min, Integral max)
|
||||
{
|
||||
XRPL_ASSERT(max > min, "ripple::rand_int : max over min inputs");
|
||||
|
||||
// This should have no state and constructing it should
|
||||
// be very cheap. If that turns out not to be the case
|
||||
// it could be hand-optimized.
|
||||
return std::uniform_int_distribution<Integral>(min, max)(engine);
|
||||
// Closed interval. Rejection sampling keeps the result uniform and the
|
||||
// same on libc++ and libstdc++ for a given engine sequence.
|
||||
using U = std::make_unsigned_t<Integral>;
|
||||
auto const span = static_cast<U>(static_cast<U>(max) - static_cast<U>(min));
|
||||
auto const count = static_cast<std::uint64_t>(span) + 1u;
|
||||
if (count == 0)
|
||||
return static_cast<Integral>(detail::randomU64(engine));
|
||||
|
||||
// Values below this threshold are the leftover that would bias x % count.
|
||||
auto const slack = static_cast<std::uint64_t>(-count) % count;
|
||||
std::uint64_t draw;
|
||||
do
|
||||
{
|
||||
draw = detail::randomU64(engine);
|
||||
} while (draw < slack);
|
||||
|
||||
auto const offset = static_cast<U>(draw % count);
|
||||
return static_cast<Integral>(static_cast<U>(static_cast<U>(min) + offset));
|
||||
}
|
||||
|
||||
template <class Integral>
|
||||
|
||||
113
src/test/app/TransactionState_test.cpp
Normal file
113
src/test/app/TransactionState_test.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
#include <test/jtx.h>
|
||||
|
||||
#include <xrpld/app/misc/Transaction.h>
|
||||
#include <xrpld/rpc/CTID.h>
|
||||
|
||||
#include <future>
|
||||
#include <thread>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class TransactionState_test : public beast::unit_test::suite
|
||||
{
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testcase(
|
||||
"concurrent response readers observe coherent locator snapshots");
|
||||
using namespace jtx;
|
||||
Env env(*this, envconfig([](std::unique_ptr<Config> config) {
|
||||
config->NETWORK_ID = 11;
|
||||
return config;
|
||||
}));
|
||||
Account const alice("alice");
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
env(noop(alice));
|
||||
if (!BEAST_EXPECT(!env.tx()->isFieldPresent(sfNetworkID)))
|
||||
return;
|
||||
|
||||
std::string reason;
|
||||
Transaction transaction(env.tx(), reason, env.app());
|
||||
BEAST_EXPECT(reason.empty());
|
||||
auto const& reader = transaction;
|
||||
auto const ctidA = RPC::encodeCTID(101, 7, 11);
|
||||
auto const ctidB = RPC::encodeCTID(202, 9, 22);
|
||||
if (!BEAST_EXPECT(ctidA && ctidB))
|
||||
return;
|
||||
|
||||
auto setState = [&](bool second) {
|
||||
std::uint32_t const ledger = second ? 202 : 101;
|
||||
transaction.setStatus(
|
||||
COMMITTED, ledger, second ? 9 : 7, second ? 22 : 11);
|
||||
transaction.setCurrentLedgerState(
|
||||
ledger, XRPAmount{ledger}, ledger + 1, ledger + 2);
|
||||
transaction.setResult(second ? TER{terQUEUED} : TER{tesSUCCESS});
|
||||
transaction.clearSubmitResult();
|
||||
transaction.setApplied();
|
||||
transaction.setQueued();
|
||||
transaction.setBroadcast();
|
||||
transaction.setKept();
|
||||
};
|
||||
setState(false);
|
||||
|
||||
std::promise<void> ready;
|
||||
auto start = ready.get_future().share();
|
||||
constexpr int iterations = 10000;
|
||||
int snapshots = 0;
|
||||
int inconsistent = 0;
|
||||
std::thread writer([&] {
|
||||
start.wait();
|
||||
for (int i = 0; i < iterations; ++i)
|
||||
setState(i % 2 != 0);
|
||||
});
|
||||
std::thread observer([&] {
|
||||
start.wait();
|
||||
for (int i = 0; i < iterations; ++i)
|
||||
{
|
||||
auto const json = reader.getJson(JsonOptions::none);
|
||||
auto const ledger = json[jss::ledger_index].asUInt();
|
||||
auto const ctid = json[jss::ctid].asString();
|
||||
if (!((ledger == 101 && ctid == *ctidA) ||
|
||||
(ledger == 202 && ctid == *ctidB)))
|
||||
++inconsistent;
|
||||
auto const state = reader.getCurrentLedgerState();
|
||||
if (!state ||
|
||||
state->minFeeRequired !=
|
||||
XRPAmount{state->validatedLedger} ||
|
||||
state->accountSeqNext != state->validatedLedger + 1 ||
|
||||
state->accountSeqAvail != state->validatedLedger + 2)
|
||||
++inconsistent;
|
||||
auto const result = reader.getResult();
|
||||
if (result != tesSUCCESS && result != terQUEUED)
|
||||
++inconsistent;
|
||||
if (!reader.isValidated() || reader.getStatus() != COMMITTED)
|
||||
++inconsistent;
|
||||
auto const flags = reader.getSubmitResult();
|
||||
(void)flags;
|
||||
++snapshots;
|
||||
}
|
||||
});
|
||||
ready.set_value();
|
||||
writer.join();
|
||||
observer.join();
|
||||
|
||||
BEAST_EXPECT(snapshots == iterations);
|
||||
BEAST_EXPECT(inconsistent == 0);
|
||||
BEAST_EXPECT(reader.getLedger() == 202);
|
||||
BEAST_EXPECT(reader.getResult() == terQUEUED);
|
||||
auto flags = reader.getSubmitResult();
|
||||
BEAST_EXPECT(
|
||||
flags.applied && flags.queued && flags.broadcast && flags.kept);
|
||||
flags.clear();
|
||||
BEAST_EXPECT(reader.getSubmitResult().any());
|
||||
transaction.clearSubmitResult();
|
||||
BEAST_EXPECT(!reader.getSubmitResult().any());
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(TransactionState, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
299
src/test/basics/random_test.cpp
Normal file
299
src/test/basics/random_test.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/beast/xor_shift_engine.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <random>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
// 32-bit output stored in a 32-bit result.
|
||||
struct Bits32
|
||||
{
|
||||
using result_type = std::uint32_t;
|
||||
result_type n = 0;
|
||||
static constexpr result_type
|
||||
min()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static constexpr result_type
|
||||
max()
|
||||
{
|
||||
return 0xffffffffu;
|
||||
}
|
||||
result_type
|
||||
operator()()
|
||||
{
|
||||
return n++;
|
||||
}
|
||||
};
|
||||
|
||||
// 32-bit output stored in a 64-bit result. This is the Linux mt19937 shape.
|
||||
struct Bits32In64
|
||||
{
|
||||
using result_type = std::uint64_t;
|
||||
result_type n = 0;
|
||||
static constexpr result_type
|
||||
min()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static constexpr result_type
|
||||
max()
|
||||
{
|
||||
return 0xffffffffu;
|
||||
}
|
||||
result_type
|
||||
operator()()
|
||||
{
|
||||
return n++;
|
||||
}
|
||||
};
|
||||
|
||||
// Nonzero minimum, power-of-two range of 256 values.
|
||||
struct NonzeroMin
|
||||
{
|
||||
using result_type = std::uint32_t;
|
||||
result_type n = 0;
|
||||
static constexpr result_type
|
||||
min()
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
static constexpr result_type
|
||||
max()
|
||||
{
|
||||
return 5 + 255;
|
||||
}
|
||||
result_type
|
||||
operator()()
|
||||
{
|
||||
return static_cast<result_type>(min() + (n++ % 256));
|
||||
}
|
||||
};
|
||||
|
||||
// A valid three-value engine exercises the zero-bit first group in
|
||||
// independent_bits_engine. Cycling values make draw consumption explicit.
|
||||
template <class Result, Result Minimum = 0>
|
||||
struct ThreeValues
|
||||
{
|
||||
using result_type = Result;
|
||||
std::size_t calls = 0;
|
||||
|
||||
static constexpr result_type
|
||||
min()
|
||||
{
|
||||
return Minimum;
|
||||
}
|
||||
|
||||
static constexpr result_type
|
||||
max()
|
||||
{
|
||||
return Minimum + 2;
|
||||
}
|
||||
|
||||
result_type
|
||||
operator()()
|
||||
{
|
||||
return static_cast<result_type>(Minimum + (calls++ % 3));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class random_test : public beast::unit_test::suite
|
||||
{
|
||||
// 8-bit model of reject-then-modulo. slack = 2^w % count, drop the
|
||||
// low slack words, then modulo. Every accepted result must have the
|
||||
// same number of preimages.
|
||||
void
|
||||
testReducedMapping()
|
||||
{
|
||||
testcase("8-bit reject-then-modulo is uniform");
|
||||
constexpr int universe = 256;
|
||||
for (int count = 1; count <= universe; ++count)
|
||||
{
|
||||
int const slack = universe % count;
|
||||
std::array<int, 256> hits{};
|
||||
int accepted = 0;
|
||||
for (int raw = 0; raw < universe; ++raw)
|
||||
{
|
||||
if (raw < slack)
|
||||
continue;
|
||||
++hits[raw % count];
|
||||
++accepted;
|
||||
}
|
||||
BEAST_EXPECT(accepted % count == 0);
|
||||
auto const each = accepted / count;
|
||||
for (int result = 0; result < count; ++result)
|
||||
BEAST_EXPECT(hits[result] == each);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Engine, class Integral>
|
||||
void
|
||||
expectInRange(Engine& engine, Integral min, Integral max, int samples)
|
||||
{
|
||||
for (int i = 0; i < samples; ++i)
|
||||
{
|
||||
auto const value = rand_int(engine, min, max);
|
||||
BEAST_EXPECT(value >= min);
|
||||
BEAST_EXPECT(value <= max);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
testEngineVectors()
|
||||
{
|
||||
testcase("engine range vectors and draw consumption");
|
||||
|
||||
beast::xor_shift_engine full{1};
|
||||
beast::xor_shift_engine fullTwin{1};
|
||||
BEAST_EXPECT(detail::randomU64(full) == fullTwin());
|
||||
BEAST_EXPECT(full() == fullTwin());
|
||||
|
||||
Bits32 narrow;
|
||||
Bits32 narrowTwin;
|
||||
auto const narrowWord = detail::randomU64(narrow);
|
||||
auto const nLow = static_cast<std::uint64_t>(narrowTwin());
|
||||
auto const nHigh = static_cast<std::uint64_t>(narrowTwin());
|
||||
BEAST_EXPECT(narrowWord == (nLow | (nHigh << 32)));
|
||||
BEAST_EXPECT(narrow() == narrowTwin());
|
||||
|
||||
Bits32In64 wideStore;
|
||||
Bits32In64 wideStoreTwin;
|
||||
auto const wideWord = detail::randomU64(wideStore);
|
||||
auto const wLow = wideStoreTwin();
|
||||
auto const wHigh = wideStoreTwin();
|
||||
BEAST_EXPECT(wideWord == (wLow | (wHigh << 32)));
|
||||
BEAST_EXPECT(wideStore() == wideStoreTwin());
|
||||
|
||||
NonzeroMin shifted;
|
||||
NonzeroMin shiftedTwin;
|
||||
std::uint64_t composed = 0;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
auto const piece =
|
||||
static_cast<std::uint64_t>(shiftedTwin() - NonzeroMin::min());
|
||||
composed |= piece << (8 * i);
|
||||
}
|
||||
BEAST_EXPECT(detail::randomU64(shifted) == composed);
|
||||
BEAST_EXPECT(shifted() == shiftedTwin());
|
||||
|
||||
std::minstd_rand uneven{12345};
|
||||
std::minstd_rand unevenCopy{12345};
|
||||
std::independent_bits_engine<std::minstd_rand, 64, std::uint64_t> ibits{
|
||||
unevenCopy};
|
||||
for (int i = 0; i < 8; ++i)
|
||||
BEAST_EXPECT(detail::randomU64(uneven) == ibits());
|
||||
}
|
||||
|
||||
void
|
||||
testZeroBitGroup()
|
||||
{
|
||||
testcase("three-value engines retain the zero-bit group draw");
|
||||
auto const check = [&](auto engine) {
|
||||
using Engine = decltype(engine);
|
||||
std::independent_bits_engine<Engine, 64, std::uint64_t> reference{
|
||||
engine};
|
||||
|
||||
// R=3: n=65, w0=0, n0=1, y0=3, y1=2. Discard the
|
||||
// first draw, then take 64 bits, rejecting normalized value 2.
|
||||
// For this cycle the accepted bits are 1010...10 (97 draws).
|
||||
auto const first = detail::randomU64(engine);
|
||||
BEAST_EXPECT(first == 0xaaaaaaaaaaaaaaaaULL);
|
||||
BEAST_EXPECT(engine.calls == 97);
|
||||
BEAST_EXPECT(first == reference());
|
||||
BEAST_EXPECT(engine.calls == reference.base().calls);
|
||||
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
BEAST_EXPECT(detail::randomU64(engine) == reference());
|
||||
BEAST_EXPECT(engine.calls == reference.base().calls);
|
||||
}
|
||||
|
||||
// Also exercise the public closed-range mapper with the same
|
||||
// normalized stream, including the runtime fault-hook range.
|
||||
for (auto const count : {10u, 10'000u})
|
||||
{
|
||||
auto const n = static_cast<std::uint64_t>(count);
|
||||
auto const slack = static_cast<std::uint64_t>(-n) % n;
|
||||
std::uint64_t expected;
|
||||
do
|
||||
{
|
||||
expected = reference();
|
||||
} while (expected < slack);
|
||||
BEAST_EXPECT(rand_int(engine, 0u, count - 1) == expected % n);
|
||||
BEAST_EXPECT(engine.calls == reference.base().calls);
|
||||
}
|
||||
};
|
||||
|
||||
check(ThreeValues<std::uint32_t>{});
|
||||
check(ThreeValues<std::uint64_t>{});
|
||||
check(ThreeValues<std::uint32_t, 5>{});
|
||||
check(ThreeValues<
|
||||
std::uint64_t,
|
||||
std::numeric_limits<std::uint64_t>::max() - 2>{});
|
||||
}
|
||||
|
||||
void
|
||||
testBounds()
|
||||
{
|
||||
testcase("closed bounds, including signed endpoints and 2^40");
|
||||
|
||||
auto check = [&](auto engine) {
|
||||
expectInRange(engine, 0, 10, 64);
|
||||
expectInRange(engine, 0, 9999, 64);
|
||||
expectInRange(engine, -20, 20, 64);
|
||||
expectInRange(engine, -5, 15, 64);
|
||||
expectInRange(
|
||||
engine, std::uint64_t{0}, (std::uint64_t{1} << 40) + 123u, 8);
|
||||
};
|
||||
|
||||
check(beast::xor_shift_engine{7});
|
||||
check(Bits32{});
|
||||
check(Bits32In64{});
|
||||
check(NonzeroMin{});
|
||||
check(std::minstd_rand{7});
|
||||
check(std::mt19937{7});
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testReducedMapping();
|
||||
testEngineVectors();
|
||||
testZeroBitGroup();
|
||||
testBounds();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(random, basics, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -24,9 +24,13 @@
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
@@ -234,9 +238,40 @@ multi_runner_base<IsParent>::multi_runner_base()
|
||||
{
|
||||
if (IsParent)
|
||||
{
|
||||
// cleanup any leftover state for any previous failed runs
|
||||
boost::interprocess::shared_memory_object::remove(shared_mem_name_);
|
||||
boost::interprocess::message_queue::remove(message_queue_name_);
|
||||
// One name pair per parent process. Spawned children inherit the
|
||||
// environment; the in-process single-job child reads it too.
|
||||
auto const pid =
|
||||
std::to_string(static_cast<unsigned long long>(::getpid()));
|
||||
shared_mem_name_ = std::string(shared_mem_prefix_) + "." + pid;
|
||||
message_queue_name_ =
|
||||
std::string(message_queue_prefix_) + "." + pid;
|
||||
if (::setenv(shared_mem_env_, shared_mem_name_.c_str(), 1) != 0 ||
|
||||
::setenv(message_queue_env_, message_queue_name_.c_str(), 1) !=
|
||||
0)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"failed to publish unit-test ipc names");
|
||||
}
|
||||
|
||||
// Drop a leftover object for this pid. Do not touch another
|
||||
// process's names.
|
||||
boost::interprocess::shared_memory_object::remove(
|
||||
shared_mem_name_.c_str());
|
||||
boost::interprocess::message_queue::remove(
|
||||
message_queue_name_.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
char const* const shm = std::getenv(shared_mem_env_);
|
||||
char const* const mq = std::getenv(message_queue_env_);
|
||||
if (shm == nullptr || shm[0] == '\0' || mq == nullptr ||
|
||||
mq[0] == '\0')
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"unit-test child missing parent ipc names");
|
||||
}
|
||||
shared_mem_name_ = shm;
|
||||
message_queue_name_ = mq;
|
||||
}
|
||||
|
||||
shared_mem_ = boost::interprocess::shared_memory_object{
|
||||
@@ -244,7 +279,7 @@ multi_runner_base<IsParent>::multi_runner_base()
|
||||
IsParent,
|
||||
boost::interprocess::create_only_t,
|
||||
boost::interprocess::open_only_t>{},
|
||||
shared_mem_name_,
|
||||
shared_mem_name_.c_str(),
|
||||
boost::interprocess::read_write};
|
||||
|
||||
if (IsParent)
|
||||
@@ -253,7 +288,7 @@ multi_runner_base<IsParent>::multi_runner_base()
|
||||
message_queue_ =
|
||||
std::make_unique<boost::interprocess::message_queue>(
|
||||
boost::interprocess::create_only,
|
||||
message_queue_name_,
|
||||
message_queue_name_.c_str(),
|
||||
/*max messages*/ 16,
|
||||
/*max message size*/ 1 << 20);
|
||||
}
|
||||
@@ -261,7 +296,8 @@ multi_runner_base<IsParent>::multi_runner_base()
|
||||
{
|
||||
message_queue_ =
|
||||
std::make_unique<boost::interprocess::message_queue>(
|
||||
boost::interprocess::open_only, message_queue_name_);
|
||||
boost::interprocess::open_only,
|
||||
message_queue_name_.c_str());
|
||||
}
|
||||
|
||||
region_ = boost::interprocess::mapped_region{
|
||||
@@ -275,8 +311,12 @@ multi_runner_base<IsParent>::multi_runner_base()
|
||||
{
|
||||
if (IsParent)
|
||||
{
|
||||
boost::interprocess::shared_memory_object::remove(shared_mem_name_);
|
||||
boost::interprocess::message_queue::remove(message_queue_name_);
|
||||
if (!shared_mem_name_.empty())
|
||||
boost::interprocess::shared_memory_object::remove(
|
||||
shared_mem_name_.c_str());
|
||||
if (!message_queue_name_.empty())
|
||||
boost::interprocess::message_queue::remove(
|
||||
message_queue_name_.c_str());
|
||||
}
|
||||
throw;
|
||||
}
|
||||
@@ -288,8 +328,9 @@ multi_runner_base<IsParent>::~multi_runner_base()
|
||||
if (IsParent)
|
||||
{
|
||||
inner_->~inner();
|
||||
boost::interprocess::shared_memory_object::remove(shared_mem_name_);
|
||||
boost::interprocess::message_queue::remove(message_queue_name_);
|
||||
boost::interprocess::shared_memory_object::remove(
|
||||
shared_mem_name_.c_str());
|
||||
boost::interprocess::message_queue::remove(message_queue_name_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -152,11 +152,20 @@ class multi_runner_base
|
||||
print_results(S& s);
|
||||
};
|
||||
|
||||
static constexpr const char* shared_mem_name_ = "RippledUnitTestSharedMem";
|
||||
// name of the message queue a multi_runner_child will use to communicate
|
||||
// with multi_runner_parent
|
||||
static constexpr const char* message_queue_name_ =
|
||||
// Prefixes only. The parent appends ".<pid>" and publishes the full names
|
||||
// in the environment before a child is constructed or spawned. Fixed
|
||||
// names collided: every --unittest process, including one job, removes
|
||||
// and recreates these objects.
|
||||
static constexpr char const* shared_mem_prefix_ =
|
||||
"RippledUnitTestSharedMem";
|
||||
static constexpr char const* message_queue_prefix_ =
|
||||
"RippledUnitTestMessageQueue";
|
||||
static constexpr char const* shared_mem_env_ = "XRPLD_UNIT_TEST_SHARED_MEM";
|
||||
static constexpr char const* message_queue_env_ =
|
||||
"XRPLD_UNIT_TEST_MESSAGE_QUEUE";
|
||||
|
||||
std::string shared_mem_name_;
|
||||
std::string message_queue_name_;
|
||||
|
||||
// `inner_` will be created in shared memory
|
||||
inner* inner_;
|
||||
|
||||
@@ -64,6 +64,7 @@ public:
|
||||
bool
|
||||
isComplete() const
|
||||
{
|
||||
ScopedLockType const sl(mtx_);
|
||||
return complete_;
|
||||
}
|
||||
|
||||
@@ -71,18 +72,21 @@ public:
|
||||
bool
|
||||
isFailed() const
|
||||
{
|
||||
ScopedLockType const sl(mtx_);
|
||||
return failed_;
|
||||
}
|
||||
|
||||
std::shared_ptr<Ledger const>
|
||||
getLedger() const
|
||||
{
|
||||
ScopedLockType const sl(mtx_);
|
||||
return mLedger;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
getSeq() const
|
||||
{
|
||||
ScopedLockType const sl(mtx_);
|
||||
return mSeq;
|
||||
}
|
||||
|
||||
@@ -109,12 +113,14 @@ public:
|
||||
void
|
||||
touch()
|
||||
{
|
||||
ScopedLockType const sl(mtx_);
|
||||
mLastAction = m_clock.now();
|
||||
}
|
||||
|
||||
clock_type::time_point
|
||||
getLastAction() const
|
||||
{
|
||||
ScopedLockType const sl(mtx_);
|
||||
return mLastAction;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,14 +28,12 @@
|
||||
#include <xrpld/overlay/Overlay.h>
|
||||
#include <xrpld/shamap/SHAMapNodeID.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
|
||||
#include <boost/iterator/function_output_iterator.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -434,6 +432,8 @@ InboundLedger::pmDowncast()
|
||||
void
|
||||
InboundLedger::done()
|
||||
{
|
||||
ScopedLockType const sl(mtx_);
|
||||
|
||||
if (mSignaled)
|
||||
return;
|
||||
|
||||
@@ -471,14 +471,14 @@ InboundLedger::done()
|
||||
// We hold the PeerSet lock, so must dispatch
|
||||
app_.getJobQueue().addJob(
|
||||
jtLEDGER_DATA, "AcquisitionDone", [self = shared_from_this()]() {
|
||||
if (self->complete_ && !self->failed_)
|
||||
if (self->isComplete() && !self->isFailed())
|
||||
{
|
||||
self->app_.getLedgerMaster().checkAccept(self->getLedger());
|
||||
self->app_.getLedgerMaster().tryAdvance();
|
||||
}
|
||||
else
|
||||
self->app_.getInboundLedgers().logFailure(
|
||||
self->hash_, self->mSeq);
|
||||
self->hash_, self->getSeq());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1039,11 +1039,12 @@ InboundLedger::gotData(
|
||||
std::weak_ptr<Peer> peer,
|
||||
std::shared_ptr<protocol::TMLedgerData> const& data)
|
||||
{
|
||||
std::lock_guard sl(mReceivedDataLock);
|
||||
|
||||
ScopedLockType const stateLock(mtx_);
|
||||
if (isDone())
|
||||
return false;
|
||||
|
||||
std::lock_guard sl(mReceivedDataLock);
|
||||
|
||||
mReceivedData.emplace_back(peer, data);
|
||||
|
||||
if (mReceiveDispatched)
|
||||
@@ -1213,32 +1214,43 @@ struct PeerDataCounts
|
||||
|
||||
// call F with the `peer` parameter with a random sample of at most n values
|
||||
// of the counts vector.
|
||||
template <class F>
|
||||
template <class F, class URBG>
|
||||
void
|
||||
sampleN(std::size_t n, F&& f)
|
||||
sampleN(std::size_t n, F&& f, URBG&& rng)
|
||||
{
|
||||
if (counts.empty())
|
||||
return;
|
||||
|
||||
auto outFunc = [&f](auto&& v) { f(v.first); };
|
||||
std::minstd_rand rng{std::random_device{}()};
|
||||
#if _MSC_VER
|
||||
// Id order makes the walk independent of map iteration.
|
||||
std::vector<std::pair<std::shared_ptr<Peer>, int>> population(
|
||||
counts.begin(), counts.end());
|
||||
std::sort(
|
||||
population.begin(),
|
||||
population.end(),
|
||||
[](auto const& a, auto const& b) {
|
||||
return a.first->id() < b.first->id();
|
||||
});
|
||||
std::vector<std::pair<std::shared_ptr<Peer>, int>> s;
|
||||
s.reserve(n);
|
||||
std::sample(
|
||||
counts.begin(), counts.end(), std::back_inserter(s), n, rng);
|
||||
for (auto& v : s)
|
||||
// Knuth's Algorithm S. std::sample's selection differs between
|
||||
// libstdc++ and libc++. When every remaining peer must be kept,
|
||||
// take it without rand_int: that call rejects a zero-width range.
|
||||
auto need = std::min(n, population.size());
|
||||
auto remaining = population.size();
|
||||
for (auto& item : population)
|
||||
{
|
||||
outFunc(v);
|
||||
if (need == 0)
|
||||
break;
|
||||
if (need == remaining ||
|
||||
rand_int(rng, std::size_t{0}, remaining - 1) < need)
|
||||
{
|
||||
s.push_back(std::move(item));
|
||||
--need;
|
||||
}
|
||||
--remaining;
|
||||
}
|
||||
#else
|
||||
std::sample(
|
||||
counts.begin(),
|
||||
counts.end(),
|
||||
boost::make_function_output_iterator(outFunc),
|
||||
n,
|
||||
rng);
|
||||
#endif
|
||||
for (auto& v : s)
|
||||
f(v.first);
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
@@ -1288,9 +1300,14 @@ InboundLedger::runData()
|
||||
// Select a random sample of the peers that gives us the most nodes that are
|
||||
// useful
|
||||
dataCounts.prune();
|
||||
dataCounts.sampleN(maxUsefulPeers, [&](std::shared_ptr<Peer> const& peer) {
|
||||
trigger(peer, TriggerReason::reply);
|
||||
});
|
||||
// dev has no Application::getPrng. Peer selection is outside the
|
||||
// harness schedule, so draw from the per-thread default engine.
|
||||
dataCounts.sampleN(
|
||||
maxUsefulPeers,
|
||||
[&](std::shared_ptr<Peer> const& peer) {
|
||||
trigger(peer, TriggerReason::reply);
|
||||
},
|
||||
default_prng());
|
||||
}
|
||||
|
||||
Json::Value
|
||||
|
||||
@@ -181,12 +181,15 @@ LedgerMaster::getPublishedLedgerAge()
|
||||
std::chrono::seconds ret = app_.timeKeeper().closeTime().time_since_epoch();
|
||||
ret -= pubClose;
|
||||
ret = (ret > 0s) ? ret : 0s;
|
||||
static std::chrono::seconds lastRet = -1s;
|
||||
static std::atomic<std::chrono::seconds::rep> lastRet{-1};
|
||||
auto const retCount = ret.count();
|
||||
auto observedLastRet = lastRet.load(std::memory_order_relaxed);
|
||||
|
||||
if (ret != lastRet)
|
||||
if (retCount != observedLastRet &&
|
||||
lastRet.compare_exchange_strong(
|
||||
observedLastRet, retCount, std::memory_order_relaxed))
|
||||
{
|
||||
JLOG(m_journal.trace()) << "Published ledger age is " << ret.count();
|
||||
lastRet = ret;
|
||||
JLOG(m_journal.trace()) << "Published ledger age is " << retCount;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -206,12 +209,15 @@ LedgerMaster::getValidatedLedgerAge()
|
||||
std::chrono::seconds ret = app_.timeKeeper().closeTime().time_since_epoch();
|
||||
ret -= valClose;
|
||||
ret = (ret > 0s) ? ret : 0s;
|
||||
static std::chrono::seconds lastRet = -1s;
|
||||
static std::atomic<std::chrono::seconds::rep> lastRet{-1};
|
||||
auto const retCount = ret.count();
|
||||
auto observedLastRet = lastRet.load(std::memory_order_relaxed);
|
||||
|
||||
if (ret != lastRet)
|
||||
if (retCount != observedLastRet &&
|
||||
lastRet.compare_exchange_strong(
|
||||
observedLastRet, retCount, std::memory_order_relaxed))
|
||||
{
|
||||
JLOG(m_journal.trace()) << "Validated ledger age is " << ret.count();
|
||||
lastRet = ret;
|
||||
JLOG(m_journal.trace()) << "Validated ledger age is " << retCount;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -2271,7 +2277,11 @@ LedgerMaster::doAdvance(std::unique_lock<std::recursive_mutex>& sl)
|
||||
}
|
||||
|
||||
app_.getOPs().clearNeedNetworkLedger();
|
||||
progress = newPFWork("pf:newLedger", sl);
|
||||
// Publishing is progress even without pathfinding clients. Keep
|
||||
// the shutdown guard so this loop cannot re-enter history work
|
||||
// after the Application starts stopping.
|
||||
newPFWork("pf:newLedger", sl);
|
||||
progress = !app_.isStopping();
|
||||
}
|
||||
if (progress)
|
||||
mAdvanceWork = true;
|
||||
|
||||
@@ -1741,6 +1741,10 @@ ApplicationImp::startGenesisLedger()
|
||||
auto const next =
|
||||
std::make_shared<Ledger>(*genesis, timeKeeper().closeTime());
|
||||
next->updateSkipList();
|
||||
// Consensus-built ledgers flush their state trees, but this genesis
|
||||
// successor bypasses that path. Persist its state before it can be
|
||||
// advertised as complete or loaded by a restarted node.
|
||||
next->stateMap().flushDirty(hotACCOUNT_NODE);
|
||||
XRPL_ASSERT(
|
||||
next->read(keylet::fees()),
|
||||
"ripple::ApplicationImp::startGenesisLedger : valid ledger fees");
|
||||
|
||||
@@ -302,6 +302,9 @@ runUnitTests(
|
||||
args.emplace_back("--unittest-child");
|
||||
}
|
||||
|
||||
// Children inherit XRPLD_UNIT_TEST_SHARED_MEM and
|
||||
// XRPLD_UNIT_TEST_MESSAGE_QUEUE, published by multi_runner_parent.
|
||||
// Do not pass an environment that drops those variables.
|
||||
for (std::size_t i = 0; i < num_jobs; ++i)
|
||||
children.emplace_back(
|
||||
boost::process::exe = exe_name, boost::process::args = args);
|
||||
|
||||
@@ -383,6 +383,11 @@ public:
|
||||
reportFeeChange() override;
|
||||
void
|
||||
reportConsensusStateChange(ConsensusPhase phase);
|
||||
void
|
||||
reportConsensusStateChangeIfNeeded(
|
||||
ConsensusPhase phase,
|
||||
std::unique_ptr<std::stringstream> const& clog,
|
||||
bool logPhase);
|
||||
|
||||
void
|
||||
updateLocalTx(ReadView const& view) override;
|
||||
@@ -674,12 +679,14 @@ private:
|
||||
|
||||
RCLConsensus mConsensus;
|
||||
|
||||
ConsensusPhase mLastConsensusPhase;
|
||||
std::mutex lastConsensusPhaseMutex_;
|
||||
ConsensusPhase mLastConsensusPhase{ConsensusPhase::open};
|
||||
|
||||
LedgerMaster& m_ledgerMaster;
|
||||
|
||||
SubInfoMapType mSubAccount;
|
||||
SubInfoMapType mSubRTAccount;
|
||||
bool firstLedgerPublished_{true}; // Guarded by mSubLock.
|
||||
|
||||
subRpcMapType mRpcSubMap;
|
||||
|
||||
@@ -1010,14 +1017,8 @@ NetworkOPsImp::processHeartbeatTimer()
|
||||
|
||||
mConsensus.timerEntry(app_.timeKeeper().closeTime(), clog.ss());
|
||||
|
||||
CLOG(clog.ss()) << "consensus phase " << to_string(mLastConsensusPhase);
|
||||
const ConsensusPhase currPhase = mConsensus.phase();
|
||||
if (mLastConsensusPhase != currPhase)
|
||||
{
|
||||
reportConsensusStateChange(currPhase);
|
||||
mLastConsensusPhase = currPhase;
|
||||
CLOG(clog.ss()) << " changed to " << to_string(mLastConsensusPhase);
|
||||
}
|
||||
reportConsensusStateChangeIfNeeded(currPhase, clog.ss(), true);
|
||||
CLOG(clog.ss()) << ". ";
|
||||
|
||||
setHeartbeatTimer();
|
||||
@@ -2094,11 +2095,7 @@ NetworkOPsImp::beginConsensus(
|
||||
clog);
|
||||
|
||||
const ConsensusPhase currPhase = mConsensus.phase();
|
||||
if (mLastConsensusPhase != currPhase)
|
||||
{
|
||||
reportConsensusStateChange(currPhase);
|
||||
mLastConsensusPhase = currPhase;
|
||||
}
|
||||
reportConsensusStateChangeIfNeeded(currPhase, clog, false);
|
||||
|
||||
JLOG(m_journal.debug()) << "Initiating consensus engine";
|
||||
return true;
|
||||
@@ -3168,11 +3165,10 @@ NetworkOPsImp::pubLedger(std::shared_ptr<ReadView const> const& lpAccepted)
|
||||
}
|
||||
|
||||
{
|
||||
static bool firstTime = true;
|
||||
if (firstTime)
|
||||
if (firstLedgerPublished_)
|
||||
{
|
||||
// First validated ledger, start delayed SubAccountHistory
|
||||
firstTime = false;
|
||||
firstLedgerPublished_ = false;
|
||||
for (auto& outer : mSubAccountHistory)
|
||||
{
|
||||
for (auto& inner : outer.second)
|
||||
@@ -3225,6 +3221,24 @@ NetworkOPsImp::reportConsensusStateChange(ConsensusPhase phase)
|
||||
[this, phase]() { pubConsensus(phase); });
|
||||
}
|
||||
|
||||
void
|
||||
NetworkOPsImp::reportConsensusStateChangeIfNeeded(
|
||||
ConsensusPhase phase,
|
||||
std::unique_ptr<std::stringstream> const& clog,
|
||||
bool logPhase)
|
||||
{
|
||||
std::scoped_lock const lock(lastConsensusPhaseMutex_);
|
||||
if (logPhase)
|
||||
CLOG(clog) << "consensus phase " << to_string(mLastConsensusPhase);
|
||||
if (mLastConsensusPhase != phase)
|
||||
{
|
||||
reportConsensusStateChange(phase);
|
||||
mLastConsensusPhase = phase;
|
||||
if (logPhase)
|
||||
CLOG(clog) << " changed to " << to_string(mLastConsensusPhase);
|
||||
}
|
||||
}
|
||||
|
||||
inline void
|
||||
NetworkOPsImp::updateLocalTx(ReadView const& view)
|
||||
{
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxMeta.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
@@ -100,30 +101,35 @@ public:
|
||||
LedgerIndex
|
||||
getLedger() const
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
return mLedgerIndex;
|
||||
}
|
||||
|
||||
bool
|
||||
isValidated() const
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
return mLedgerIndex != 0;
|
||||
}
|
||||
|
||||
TransStatus
|
||||
getStatus() const
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
TER
|
||||
getResult()
|
||||
getResult() const
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
return mResult;
|
||||
}
|
||||
|
||||
void
|
||||
setResult(TER terResult)
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
mResult = terResult;
|
||||
}
|
||||
|
||||
@@ -137,12 +143,14 @@ public:
|
||||
void
|
||||
setStatus(TransStatus status)
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
mStatus = status;
|
||||
}
|
||||
|
||||
void
|
||||
setLedger(LedgerIndex ledger)
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
mLedgerIndex = ledger;
|
||||
}
|
||||
|
||||
@@ -212,6 +220,7 @@ public:
|
||||
SubmitResult
|
||||
getSubmitResult() const
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
return submitResult_;
|
||||
}
|
||||
|
||||
@@ -221,6 +230,7 @@ public:
|
||||
void
|
||||
clearSubmitResult()
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
submitResult_.clear();
|
||||
}
|
||||
|
||||
@@ -230,6 +240,7 @@ public:
|
||||
void
|
||||
setApplied()
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
submitResult_.applied = true;
|
||||
}
|
||||
|
||||
@@ -239,6 +250,7 @@ public:
|
||||
void
|
||||
setQueued()
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
submitResult_.queued = true;
|
||||
}
|
||||
|
||||
@@ -248,6 +260,7 @@ public:
|
||||
void
|
||||
setBroadcast()
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
submitResult_.broadcast = true;
|
||||
}
|
||||
|
||||
@@ -257,6 +270,7 @@ public:
|
||||
void
|
||||
setKept()
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
submitResult_.kept = true;
|
||||
}
|
||||
|
||||
@@ -289,6 +303,7 @@ public:
|
||||
std::optional<CurrentLedgerState>
|
||||
getCurrentLedgerState() const
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
return currentLedgerState_;
|
||||
}
|
||||
|
||||
@@ -306,6 +321,7 @@ public:
|
||||
std::uint32_t accountSeq,
|
||||
std::uint32_t availableSeq)
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
currentLedgerState_.emplace(
|
||||
validatedLedger, fee, accountSeq, availableSeq);
|
||||
}
|
||||
@@ -391,6 +407,9 @@ private:
|
||||
|
||||
uint256 mTransactionID;
|
||||
|
||||
// Response and locator state is read by RPC/relay while NetworkOPs
|
||||
// updates it. Snapshot the related locator fields together for JSON.
|
||||
mutable std::mutex mutableStateMutex_;
|
||||
LedgerIndex mLedgerIndex = 0;
|
||||
std::optional<uint32_t> mTxnSeq;
|
||||
std::optional<uint16_t> mNetworkID;
|
||||
|
||||
@@ -534,6 +534,8 @@ private:
|
||||
FeeLevel64 const feeLevel;
|
||||
/// Transaction ID.
|
||||
TxID const txID;
|
||||
// Updated only while absent from the intrusive byFee_ index.
|
||||
uint256 parentHashSortKey;
|
||||
/// Account submitting the transaction.
|
||||
AccountID const account;
|
||||
/// Expiration ledger for the transaction
|
||||
@@ -589,16 +591,6 @@ private:
|
||||
*/
|
||||
static constexpr int retriesAllowed = 10;
|
||||
|
||||
/** The hash of the parent ledger.
|
||||
|
||||
This is used to pseudo-randomize the transaction order when
|
||||
populating byFee_, by XORing it with the transaction hash (txID).
|
||||
Using a single static and doing the XOR operation every time was
|
||||
tested to be as fast or faster than storing the computed "sort key",
|
||||
and obviously uses less memory.
|
||||
*/
|
||||
static LedgerHash parentHashComp;
|
||||
|
||||
public:
|
||||
/// Constructor
|
||||
MaybeTx(
|
||||
@@ -608,6 +600,12 @@ private:
|
||||
ApplyFlags const flags,
|
||||
PreflightResult const& pfresult);
|
||||
|
||||
void
|
||||
setParentHashSortKey(LedgerHash const& parentHash)
|
||||
{
|
||||
parentHashSortKey = txID ^ parentHash;
|
||||
}
|
||||
|
||||
/// Attempt to apply the queued transaction to the open ledger.
|
||||
ApplyResult
|
||||
apply(Application& app, OpenView& view, beast::Journal j);
|
||||
@@ -663,8 +661,7 @@ private:
|
||||
operator()(const MaybeTx& lhs, const MaybeTx& rhs) const
|
||||
{
|
||||
if (lhs.feeLevel == rhs.feeLevel)
|
||||
return (lhs.txID ^ MaybeTx::parentHashComp) <
|
||||
(rhs.txID ^ MaybeTx::parentHashComp);
|
||||
return lhs.parentHashSortKey < rhs.parentHashSortKey;
|
||||
return lhs.feeLevel > rhs.feeLevel;
|
||||
}
|
||||
};
|
||||
@@ -798,9 +795,7 @@ private:
|
||||
*/
|
||||
std::optional<size_t> maxSize_;
|
||||
|
||||
/**
|
||||
parentHash_ used for logging only
|
||||
*/
|
||||
/// Parent hash used to salt newly queued candidates.
|
||||
LedgerHash parentHash_{beast::zero};
|
||||
|
||||
/** Most queue operations are done under the master lock,
|
||||
|
||||
@@ -64,6 +64,7 @@ Transaction::setStatus(
|
||||
std::optional<std::uint32_t> tseq,
|
||||
std::optional<std::uint16_t> netID)
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
mStatus = ts;
|
||||
mLedgerIndex = lseq;
|
||||
if (tseq)
|
||||
@@ -167,37 +168,47 @@ Transaction::getJson(JsonOptions options, bool binary) const
|
||||
Json::Value ret(
|
||||
mTransaction->getJson(options & ~JsonOptions::include_date, binary));
|
||||
|
||||
LedgerIndex ledgerIndex;
|
||||
std::optional<std::uint32_t> transactionSeq;
|
||||
std::optional<std::uint16_t> networkID;
|
||||
{
|
||||
std::scoped_lock const lock(mutableStateMutex_);
|
||||
ledgerIndex = mLedgerIndex;
|
||||
transactionSeq = mTxnSeq;
|
||||
networkID = mNetworkID;
|
||||
}
|
||||
|
||||
// NOTE Binary STTx::getJson output might not be a JSON object
|
||||
if (ret.isObject() && mLedgerIndex)
|
||||
if (ret.isObject() && ledgerIndex)
|
||||
{
|
||||
if (!(options & JsonOptions::disable_API_prior_V2))
|
||||
{
|
||||
// Behaviour before API version 2
|
||||
ret[jss::inLedger] = mLedgerIndex;
|
||||
ret[jss::inLedger] = ledgerIndex;
|
||||
}
|
||||
|
||||
// TODO: disable_API_prior_V3 to disable output of both `date` and
|
||||
// `ledger_index` elements (taking precedence over include_date)
|
||||
ret[jss::ledger_index] = mLedgerIndex;
|
||||
ret[jss::ledger_index] = ledgerIndex;
|
||||
|
||||
if (options & JsonOptions::include_date)
|
||||
{
|
||||
auto ct = mApp.getLedgerMaster().getCloseTimeBySeq(mLedgerIndex);
|
||||
auto ct = mApp.getLedgerMaster().getCloseTimeBySeq(ledgerIndex);
|
||||
if (ct)
|
||||
ret[jss::date] = ct->time_since_epoch().count();
|
||||
}
|
||||
|
||||
// compute outgoing CTID
|
||||
// override local network id if it's explicitly in the txn
|
||||
std::optional netID = mNetworkID;
|
||||
auto netID = networkID;
|
||||
if (mTransaction->isFieldPresent(sfNetworkID))
|
||||
netID = mTransaction->getFieldU32(sfNetworkID);
|
||||
|
||||
if (mTxnSeq && netID && *mTxnSeq <= 0xFFFFU && *netID < 0xFFFFU &&
|
||||
mLedgerIndex < 0xFFFFFFFUL)
|
||||
if (transactionSeq && netID && *transactionSeq <= 0xFFFFU &&
|
||||
*netID < 0xFFFFU && ledgerIndex < 0xFFFFFFFUL)
|
||||
{
|
||||
std::optional<std::string> ctid =
|
||||
RPC::encodeCTID(mLedgerIndex, *mTxnSeq, *netID);
|
||||
RPC::encodeCTID(ledgerIndex, *transactionSeq, *netID);
|
||||
if (ctid)
|
||||
ret[jss::ctid] = *ctid;
|
||||
}
|
||||
|
||||
@@ -292,8 +292,6 @@ TxQ::FeeMetrics::escalatedSeriesFeeLevel(
|
||||
return {totalFeeLevel.has_value(), *totalFeeLevel};
|
||||
}
|
||||
|
||||
LedgerHash TxQ::MaybeTx::parentHashComp{};
|
||||
|
||||
TxQ::MaybeTx::MaybeTx(
|
||||
std::shared_ptr<STTx const> const& txn_,
|
||||
TxID const& txID_,
|
||||
@@ -303,6 +301,7 @@ TxQ::MaybeTx::MaybeTx(
|
||||
: txn(txn_)
|
||||
, feeLevel(feeLevel_)
|
||||
, txID(txID_)
|
||||
, parentHashSortKey(txID_)
|
||||
, account(txn_->getAccountID(sfAccount))
|
||||
, firstValid(getFirstLedgerSequence(*txn_))
|
||||
, lastValid(getLastLedgerSequence(*txn_))
|
||||
@@ -1365,6 +1364,7 @@ TxQ::apply(
|
||||
{tx, transactionID, feeLevelPaid, flags, pfresult});
|
||||
|
||||
// Then index it into the byFee lookup.
|
||||
candidate.setParentHashSortKey(parentHash_);
|
||||
byFee_.insert(candidate);
|
||||
JLOG(j_.debug()) << "Added transaction " << candidate.txID
|
||||
<< " with result " << transToken(pfresult.ter) << " from "
|
||||
@@ -1850,12 +1850,11 @@ TxQ::accept(Application& app, OpenView& view)
|
||||
// time, create a new list and merge the old list into it.
|
||||
byFee_.clear();
|
||||
|
||||
MaybeTx::parentHashComp = parentHash;
|
||||
|
||||
for (auto& [_, account] : byAccount_)
|
||||
{
|
||||
for (auto& [_, candidate] : account.transactions)
|
||||
{
|
||||
candidate.setParentHashSortKey(parentHash);
|
||||
byFee_.insert(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,13 +297,12 @@ saveValidatedLedger(
|
||||
}
|
||||
|
||||
{
|
||||
static boost::format deleteLedger(
|
||||
"DELETE FROM Ledgers WHERE LedgerSeq = %u;");
|
||||
static boost::format deleteTrans1(
|
||||
boost::format deleteLedger("DELETE FROM Ledgers WHERE LedgerSeq = %u;");
|
||||
boost::format deleteTrans1(
|
||||
"DELETE FROM Transactions WHERE LedgerSeq = %u;");
|
||||
static boost::format deleteTrans2(
|
||||
boost::format deleteTrans2(
|
||||
"DELETE FROM AccountTransactions WHERE LedgerSeq = %u;");
|
||||
static boost::format deleteAcctTrans(
|
||||
boost::format deleteAcctTrans(
|
||||
"DELETE FROM AccountTransactions WHERE TransID = '%s';");
|
||||
|
||||
{
|
||||
|
||||
@@ -310,6 +310,9 @@ class Validations
|
||||
// Sequence of the largest validation received from each node
|
||||
hash_map<NodeID, SeqEnforcer<Seq>> seqEnforcers_;
|
||||
|
||||
// Each validation store owns its keep-range refresh schedule.
|
||||
std::chrono::steady_clock::time_point refreshTime_{};
|
||||
|
||||
//! Validations from listed nodes, indexed by ledger id (partial and full)
|
||||
beast::aged_unordered_map<
|
||||
ID,
|
||||
@@ -735,13 +738,12 @@ public:
|
||||
{
|
||||
// We only need to refresh the keep range when it's just about
|
||||
// to expire. Track the next time we need to refresh.
|
||||
static std::chrono::steady_clock::time_point refreshTime;
|
||||
if (auto const now = byLedger_.clock().now();
|
||||
refreshTime <= now)
|
||||
refreshTime_ <= now)
|
||||
{
|
||||
// The next refresh time is shortly before the expiration
|
||||
// time from now.
|
||||
refreshTime = now + parms_.validationSET_EXPIRES -
|
||||
refreshTime_ = now + parms_.validationSET_EXPIRES -
|
||||
parms_.validationFRESHNESS;
|
||||
|
||||
for (auto i = byLedger_.begin(); i != byLedger_.end(); ++i)
|
||||
|
||||
@@ -658,8 +658,7 @@ private:
|
||||
// to discard duplicate message from the same peer. A message
|
||||
// is aged after IDLED seconds. A message received IDLED seconds
|
||||
// after it was relayed is ignored by PeerImp.
|
||||
inline static messages peersWithMessage_{
|
||||
beast::get_abstract_clock<clock_type>()};
|
||||
messages peersWithMessage_{beast::get_abstract_clock<clock_type>()};
|
||||
};
|
||||
|
||||
template <typename clock_type>
|
||||
|
||||
@@ -285,7 +285,8 @@ parseMessageContent(MessageHeader const& header, Buffers const& buffers)
|
||||
if (payloadSize == 0 || !m->ParseFromArray(payload.data(), payloadSize))
|
||||
return {};
|
||||
}
|
||||
else if (!m->ParseFromZeroCopyStream(&stream))
|
||||
else if (!m->ParseFromBoundedZeroCopyStream(
|
||||
&stream, static_cast<int>(header.payload_wire_size)))
|
||||
return {};
|
||||
|
||||
return m;
|
||||
|
||||
Reference in New Issue
Block a user