Compare commits

..

4 Commits

Author SHA1 Message Date
Richard Holland
9320f0fddc fix get_big_block bug, try to fix overallocation of memory 2024-08-19 13:20:06 +10:00
Richard Holland
b0a7783dbc add delete to snug 2024-08-18 20:33:34 +10:00
Richard Holland
224e78ac81 clang 2024-08-18 16:46:46 +10:00
Richard Holland
fbfd8c1e0a snugdb 2024-08-18 16:37:02 +10:00
25 changed files with 1126 additions and 1009 deletions

View File

@@ -30,7 +30,7 @@ jobs:
git diff --exit-code | tee "clang-format.patch"
- name: Upload patch
if: failure() && steps.assert.outcome == 'failure'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v2
continue-on-error: true
with:
name: clang-format.patch

View File

@@ -18,7 +18,7 @@ jobs:
git diff --exit-code | tee "levelization.patch"
- name: Upload patch
if: failure() && steps.assert.outcome == 'failure'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v2
continue-on-error: true
with:
name: levelization.patch

View File

@@ -542,6 +542,7 @@ target_sources (rippled PRIVATE
src/ripple/nodestore/backend/NuDBFactory.cpp
src/ripple/nodestore/backend/NullFactory.cpp
src/ripple/nodestore/backend/RocksDBFactory.cpp
src/ripple/nodestore/backend/SnugDBFactory.cpp
src/ripple/nodestore/impl/BatchWriter.cpp
src/ripple/nodestore/impl/Database.cpp
src/ripple/nodestore/impl/DatabaseNodeImp.cpp

View File

@@ -1,4 +1,4 @@
# Xahau
# Xahau
**Note:** Throughout this README, references to "we" or "our" pertain to the community and contributors involved in the Xahau network. It does not imply a legal entity or a specific collection of individuals.
@@ -68,4 +68,4 @@ git-subtree. See those directories' README files for more details.
- **Testnet & Faucet**: Test applications and obtain test XAH at [xahau-test.net](https://xahau-test.net) and use the testnet explorer at [explorer.xahau.network](https://explorer.xahau.network).
- **Supporting Wallets**: A list of wallets that support XAH and Xahau-based assets.
- [Xumm](https://xumm.app)
- [Crossmark](https://crossmark.io)
- [Crossmark](https://crossmark.io)

View File

@@ -134,12 +134,8 @@ RCLConsensus::Adaptor::acquireLedger(LedgerHash const& hash)
acquiringLedger_ = hash;
app_.getJobQueue().addJob(
jtADVANCE,
"getConsensusLedger1",
[id = hash, &app = app_, this]() {
JLOG(j_.debug())
<< "JOB advanceLedger getConsensusLedger1 started";
app.getInboundLedgers().acquireAsync(
jtADVANCE, "getConsensusLedger", [id = hash, &app = app_]() {
app.getInboundLedgers().acquire(
id, 0, InboundLedger::Reason::CONSENSUS);
});
}

View File

@@ -135,10 +135,8 @@ RCLValidationsAdaptor::acquire(LedgerHash const& hash)
Application* pApp = &app_;
app_.getJobQueue().addJob(
jtADVANCE, "getConsensusLedger2", [pApp, hash, this]() {
JLOG(j_.debug())
<< "JOB advanceLedger getConsensusLedger2 started";
pApp->getInboundLedgers().acquireAsync(
jtADVANCE, "getConsensusLedger", [pApp, hash]() {
pApp->getInboundLedgers().acquire(
hash, 0, InboundLedger::Reason::CONSENSUS);
});
return std::nullopt;
@@ -154,9 +152,7 @@ void
handleNewValidation(
Application& app,
std::shared_ptr<STValidation> const& val,
std::string const& source,
BypassAccept const bypassAccept,
std::optional<beast::Journal> j)
std::string const& source)
{
auto const& signingKey = val->getSignerPublic();
auto const& hash = val->getLedgerHash();
@@ -181,23 +177,7 @@ handleNewValidation(
if (outcome == ValStatus::current)
{
if (val->isTrusted())
{
// Was: app.getLedgerMaster().checkAccept(hash, seq);
// https://github.com/XRPLF/rippled/commit/fbbea9e6e25795a8a6bd1bf64b780771933a9579
if (bypassAccept == BypassAccept::yes)
{
assert(j.has_value());
if (j.has_value())
{
JLOG(j->trace()) << "Bypassing checkAccept for validation "
<< val->getLedgerHash();
}
}
else
{
app.getLedgerMaster().checkAccept(hash, seq);
}
}
app.getLedgerMaster().checkAccept(hash, seq);
return;
}

View File

@@ -25,16 +25,12 @@
#include <ripple/protocol/Protocol.h>
#include <ripple/protocol/RippleLedgerHash.h>
#include <ripple/protocol/STValidation.h>
#include <optional>
#include <set>
#include <vector>
namespace ripple {
class Application;
enum class BypassAccept : bool { no = false, yes };
/** Wrapper over STValidation for generic Validation code
Wraps an STValidation for compatibility with the generic validation code.
@@ -252,9 +248,7 @@ void
handleNewValidation(
Application& app,
std::shared_ptr<STValidation> const& val,
std::string const& source,
BypassAccept const bypassAccept = BypassAccept::no,
std::optional<beast::Journal> j = std::nullopt);
std::string const& source);
} // namespace ripple

View File

@@ -38,21 +38,10 @@ public:
virtual ~InboundLedgers() = default;
// VFALCO TODO Should this be called findOrAdd ?
// Callers should use this if they possibly need an authoritative
// response immediately.
//
virtual std::shared_ptr<Ledger const>
acquire(uint256 const& hash, std::uint32_t seq, InboundLedger::Reason) = 0;
// Callers should use this if they are known to be executing on the Job
// Queue. TODO review whether all callers of acquire() can use this
// instead. Inbound ledger acquisition is asynchronous anyway.
virtual void
acquireAsync(
uint256 const& hash,
std::uint32_t seq,
InboundLedger::Reason reason) = 0;
virtual std::shared_ptr<InboundLedger>
find(LedgerHash const& hash) = 0;

View File

@@ -560,7 +560,7 @@ InboundLedger::trigger(std::shared_ptr<Peer> const& peer, TriggerReason reason)
return;
}
if (auto stream = journal_.debug())
if (auto stream = journal_.trace())
{
if (peer)
stream << "Trigger acquiring ledger " << hash_ << " from " << peer;

View File

@@ -28,7 +28,6 @@
#include <ripple/core/JobQueue.h>
#include <ripple/nodestore/DatabaseShard.h>
#include <ripple/protocol/jss.h>
#include <exception>
#include <memory>
#include <mutex>
#include <vector>
@@ -142,37 +141,6 @@ public:
return inbound->getLedger();
}
void
acquireAsync(
uint256 const& hash,
std::uint32_t seq,
InboundLedger::Reason reason) override
{
std::unique_lock lock(acquiresMutex_);
try
{
if (pendingAcquires_.contains(hash))
return;
pendingAcquires_.insert(hash);
lock.unlock();
acquire(hash, seq, reason);
}
catch (std::exception const& e)
{
JLOG(j_.warn())
<< "Exception thrown for acquiring new inbound ledger " << hash
<< ": " << e.what();
}
catch (...)
{
JLOG(j_.warn())
<< "Unknown exception thrown for acquiring new inbound ledger "
<< hash;
}
lock.lock();
pendingAcquires_.erase(hash);
}
std::shared_ptr<InboundLedger>
find(uint256 const& hash) override
{
@@ -458,9 +426,6 @@ private:
beast::insight::Counter mCounter;
std::unique_ptr<PeerSetBuilder> mPeerSetBuilder;
std::set<uint256> pendingAcquires_;
std::mutex acquiresMutex_;
};
//------------------------------------------------------------------------------

View File

@@ -70,9 +70,7 @@
#include <boost/asio/ip/host_name.hpp>
#include <boost/asio/steady_timer.hpp>
#include <exception>
#include <mutex>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
@@ -778,9 +776,6 @@ private:
StateAccounting accounting_{};
std::set<uint256> pendingValidations_;
std::mutex validationsMutex_;
private:
struct Stats
{
@@ -1147,12 +1142,8 @@ NetworkOPsImp::submitTransaction(std::shared_ptr<STTx const> const& iTrans)
// Enforce Network bar for emitted txn
if (view->rules().enabled(featureHooks) && hook::isEmittedTxn(*iTrans))
{
// RH NOTE: Warning removed here due to ConsesusSet using this function
// which continually triggers this bar. Doesn't seem dangerous, just
// annoying.
// JLOG(m_journal.warn())
// << "Submitted transaction invalid: EmitDetails present.";
JLOG(m_journal.warn())
<< "Submitted transaction invalid: EmitDetails present.";
return;
}
@@ -1164,11 +1155,7 @@ NetworkOPsImp::submitTransaction(std::shared_ptr<STTx const> const& iTrans)
if ((flags & SF_BAD) != 0)
{
// RH NOTE: Warning removed here due to ConsesusSet using this function
// which continually triggers this bar. Doesn't seem dangerous, just
// annoying.
// JLOG(m_journal.warn()) << "Submitted transaction cached bad";
JLOG(m_journal.warn()) << "Submitted transaction cached bad";
return;
}
@@ -1796,8 +1783,7 @@ NetworkOPsImp::checkLastClosedLedger(
}
JLOG(m_journal.warn()) << "We are not running on the consensus ledger";
JLOG(m_journal.info()) << "Our LCL: " << ourClosed->info().hash
<< getJson({*ourClosed, {}});
JLOG(m_journal.info()) << "Our LCL: " << getJson({*ourClosed, {}});
JLOG(m_journal.info()) << "Net LCL " << closedLedger;
if ((mMode == OperatingMode::TRACKING) || (mMode == OperatingMode::FULL))
@@ -2351,37 +2337,7 @@ NetworkOPsImp::recvValidation(
JLOG(m_journal.trace())
<< "recvValidation " << val->getLedgerHash() << " from " << source;
// handleNewValidation(app_, val, source);
// https://github.com/XRPLF/rippled/commit/fbbea9e6e25795a8a6bd1bf64b780771933a9579
std::unique_lock lock(validationsMutex_);
BypassAccept bypassAccept = BypassAccept::no;
try
{
if (pendingValidations_.contains(val->getLedgerHash()))
bypassAccept = BypassAccept::yes;
else
pendingValidations_.insert(val->getLedgerHash());
lock.unlock();
handleNewValidation(app_, val, source, bypassAccept, m_journal);
}
catch (std::exception const& e)
{
JLOG(m_journal.warn())
<< "Exception thrown for handling new validation "
<< val->getLedgerHash() << ": " << e.what();
}
catch (...)
{
JLOG(m_journal.warn())
<< "Unknown exception thrown for handling new validation "
<< val->getLedgerHash();
}
if (bypassAccept == BypassAccept::no)
{
lock.lock();
pendingValidations_.erase(val->getLedgerHash());
lock.unlock();
}
handleNewValidation(app_, val, source);
pubValidation(val);

View File

@@ -111,7 +111,7 @@ public:
std::uint32_t minimumTxnInLedgerSA = 1000;
/// Number of transactions per ledger that fee escalation "works
/// towards".
std::uint32_t targetTxnInLedger = 1000;
std::uint32_t targetTxnInLedger = 256;
/** Optional maximum allowed value of transactions per ledger before
fee escalation kicks in. By default, the maximum is an emergent
property of network, validator, and consensus performance. This

View File

@@ -173,11 +173,6 @@ updateLedgerDBs(
auto const sParentHash{to_string(ledger->info().parentHash)};
auto const sDrops{to_string(ledger->info().drops)};
auto const closingTime{
ledger->info().closeTime.time_since_epoch().count()};
auto const prevClosingTime{
ledger->info().parentCloseTime.time_since_epoch().count()};
auto const closeTimeRes{ledger->info().closeTimeResolution.count()};
auto const sAccountHash{to_string(ledger->info().accountHash)};
auto const sTxHash{to_string(ledger->info().txHash)};
@@ -193,8 +188,11 @@ updateLedgerDBs(
":closingTime, :prevClosingTime, :closeTimeRes,"
":closeFlags, :accountSetHash, :transSetHash);",
soci::use(sHash), soci::use(ledgerSeq), soci::use(sParentHash),
soci::use(sDrops), soci::use(closingTime),
soci::use(prevClosingTime), soci::use(closeTimeRes),
soci::use(sDrops),
soci::use(ledger->info().closeTime.time_since_epoch().count()),
soci::use(
ledger->info().parentCloseTime.time_since_epoch().count()),
soci::use(ledger->info().closeTimeResolution.count()),
soci::use(ledger->info().closeFlags), soci::use(sAccountHash),
soci::use(sTxHash);

View File

@@ -205,20 +205,19 @@ insertPeerReservation(
PublicKey const& nodeId,
std::string const& description)
{
auto const sNodeId = toBase58(TokenType::NodePublic, nodeId);
session << "INSERT INTO PeerReservations (PublicKey, Description) "
"VALUES (:nodeId, :desc) "
"ON CONFLICT (PublicKey) DO UPDATE SET "
"Description=excluded.Description",
soci::use(sNodeId), soci::use(description);
soci::use(toBase58(TokenType::NodePublic, nodeId)),
soci::use(description);
}
void
deletePeerReservation(soci::session& session, PublicKey const& nodeId)
{
auto const sNodeId = toBase58(TokenType::NodePublic, nodeId);
session << "DELETE FROM PeerReservations WHERE PublicKey = :nodeId",
soci::use(sNodeId);
soci::use(toBase58(TokenType::NodePublic, nodeId));
}
bool

View File

@@ -1921,13 +1921,6 @@ Transactor::operator()()
STObject const meta = metaRaw.getAsObject();
uint32_t lgrCur = view().seq();
bool const has240819 = view().rules().enabled(fix240819);
bool const has240911 = view().rules().enabled(fix240911);
auto const& sfRewardFields =
*(ripple::SField::knownCodeToField.at(917511 - has240819));
// iterate all affected balances
for (auto const& node : meta.getFieldArray(sfAffectedNodes))
{
@@ -1939,7 +1932,7 @@ Transactor::operator()()
if (nodeType != ltACCOUNT_ROOT || metaType == sfDeletedNode)
continue;
if (!node.isFieldPresent(sfRewardFields) ||
if (!node.isFieldPresent(sfFinalFields) ||
!node.isFieldPresent(sfLedgerIndex))
continue;
@@ -1955,7 +1948,7 @@ Transactor::operator()()
continue;
STObject& finalFields = (const_cast<STObject&>(node))
.getField(sfRewardFields)
.getField(sfFinalFields)
.downcast<STObject>();
if (!finalFields.isFieldPresent(sfBalance))
@@ -1972,11 +1965,7 @@ Transactor::operator()()
uint32_t lgrElapsed = lgrCur - lgrLast;
// overflow safety
if (!has240911 &&
(lgrElapsed > lgrCur || lgrElapsed > lgrLast ||
lgrElapsed == 0))
continue;
if (has240911 && (lgrElapsed > lgrCur || lgrElapsed == 0))
if (lgrElapsed > lgrCur || lgrElapsed > lgrLast || lgrElapsed == 0)
continue;
uint64_t accum = sle->getFieldU64(sfRewardAccumulator);

View File

@@ -240,7 +240,7 @@ public:
bool LEDGER_REPLAY = false;
// Work queue limits
int MAX_TRANSACTIONS = 1000;
int MAX_TRANSACTIONS = 250;
static constexpr int MAX_JOB_QUEUE_TX = 1000;
static constexpr int MIN_JOB_QUEUE_TX = 100;

View File

@@ -91,10 +91,8 @@ ApplyView::dirAdd(
return page;
}
bool const capped = !rules().enabled(fixPageCap);
// Check whether we're out of pages.
if (++page >= dirNodeMaxPages && capped)
if (++page >= dirNodeMaxPages)
return std::nullopt;
// We are about to create a new node; we'll link it to

View File

@@ -0,0 +1,336 @@
//------------------------------------------------------------------------------
/*
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 <ripple/basics/contract.h>
#include <ripple/nodestore/Factory.h>
#include <ripple/nodestore/Manager.h>
#include <ripple/nodestore/impl/DecodedBlob.h>
#include <ripple/nodestore/impl/EncodedBlob.h>
#include <ripple/nodestore/impl/codec.h>
#include <boost/filesystem.hpp>
#include "snug.hpp"
#include <cassert>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <exception>
#include <memory>
namespace ripple {
namespace NodeStore {
class SnugDBBackend : public Backend
{
private:
static constexpr uint64_t BUFFER_SIZE =
256ULL * 1024ULL * 1024ULL; // 256 Mib read buffer per thread
public:
beast::Journal const j_;
std::string const name_;
std::unique_ptr<snug::SnugDB> db_;
Scheduler& scheduler_;
SnugDBBackend(
Section const& keyValues,
Scheduler& scheduler,
beast::Journal journal)
: j_(journal), name_(get(keyValues, "path")), scheduler_(scheduler)
{
if (name_.empty())
throw std::runtime_error(
"nodestore: Missing path in SnugDB backend");
}
~SnugDBBackend() override
{
try
{
// close can throw and we don't want the destructor to throw.
db_ = nullptr;
}
catch (std::exception const& e)
{
JLOG(j_.warn()) << "SnugDB threw on destruction: " << e.what();
// Don't allow exceptions to propagate out of destructors.
}
}
std::string
getName() override
{
return name_;
}
void
open(bool createIfMissing, uint64_t appType, uint64_t uid, uint64_t salt)
override
{
if (db_)
{
assert(false);
JLOG(j_.error()) << "database is already open";
return;
}
std::string path = name_ + "/" + std::to_string(uid) + "-" +
std::to_string(appType) + "-" + std::to_string(salt);
boost::filesystem::create_directories(path);
db_ = std::make_unique<snug::SnugDB>(path);
}
bool
isOpen() override
{
return db_ != nullptr;
}
void
open(bool createIfMissing) override
{
open(createIfMissing, 0, 0, 0);
}
void
close() override
{
db_ = nullptr;
}
Status
fetch(void const* key, std::shared_ptr<NodeObject>* pno) override
{
if (!db_)
return backendError;
pno->reset();
static thread_local std::unique_ptr<uint8_t[]> thread_buffer =
std::make_unique<uint8_t[]>(BUFFER_SIZE);
uint8_t* ptr = &(thread_buffer[0]);
uint64_t len = BUFFER_SIZE;
int result = db_->read_entry(
static_cast<uint8_t*>(const_cast<void*>(key)), ptr, &len);
if (0)
{
std::stringstream ss;
const unsigned char* bytes = static_cast<const unsigned char*>(key);
for (int i = 0; i < 32; ++i)
{
ss << std::setfill('0') << std::setw(2) << std::hex
<< static_cast<int>(bytes[i]);
}
std::string key_hex = ss.str();
// Print the result using printf
printf(
"snug fetch: len=%zu result=%zu key=%s\n",
len,
result,
key_hex.c_str());
}
if (result == 1)
return notFound;
if (result == 0)
{
DecodedBlob decoded(key, ptr, len);
if (!decoded.wasOk())
return dataCorrupt;
*pno = decoded.createObject();
return ok;
}
return backendError;
}
std::pair<std::vector<std::shared_ptr<NodeObject>>, Status>
fetchBatch(std::vector<uint256 const*> const& hashes) override
{
std::vector<std::shared_ptr<NodeObject>> results;
results.reserve(hashes.size());
for (auto const& h : hashes)
{
std::shared_ptr<NodeObject> nObj;
Status status = fetch(h->begin(), &nObj);
if (status != ok)
results.push_back({});
else
results.push_back(nObj);
}
return {results, ok};
}
void
do_insert(std::shared_ptr<NodeObject> const& no)
{
EncodedBlob e(no);
if (0)
{
std::stringstream ss;
const unsigned char* bytes = static_cast<const unsigned char*>(
const_cast<void*>(e.getKey()));
for (int i = 0; i < 32; ++i)
ss << std::setfill('0') << std::setw(2) << std::hex
<< static_cast<int>(bytes[i]);
std::string key_hex = ss.str();
std::cout << "snugdb write: len=" << e.getSize()
<< ", key=" << key_hex << "\n";
}
int out = db_->write_entry(
static_cast<uint8_t*>(const_cast<void*>(e.getKey())),
static_cast<uint8_t*>(const_cast<void*>(e.getData())),
e.getSize());
if (out != 0)
throw std::runtime_error(
"SnugDB could not write entry. Disk full? error" +
std::to_string(out));
}
void
store(std::shared_ptr<NodeObject> const& no) override
{
BatchWriteReport report;
report.writeCount = 1;
auto const start = std::chrono::steady_clock::now();
do_insert(no);
report.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start);
scheduler_.onBatchWrite(report);
}
void
storeBatch(Batch const& batch) override
{
BatchWriteReport report;
report.writeCount = batch.size();
auto const start = std::chrono::steady_clock::now();
for (auto const& e : batch)
do_insert(e);
report.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start);
scheduler_.onBatchWrite(report);
}
void
sync() override
{
}
void
for_each(std::function<void(std::shared_ptr<NodeObject>)> f) override
{
db_->visit_all(
[](uint8_t* key, uint8_t* data, uint64_t len, void* fp) -> void {
DecodedBlob decoded(key, data, len);
if (!decoded.wasOk())
{
throw std::runtime_error(
"Missing or corrupted data in snugdb");
return;
}
std::function<void(std::shared_ptr<NodeObject>)> f =
*(reinterpret_cast<
std::function<void(std::shared_ptr<NodeObject>)>*>(fp));
f(decoded.createObject());
},
reinterpret_cast<void*>(&f));
}
int
getWriteLoad() override
{
return 0;
}
void
setDeletePath() override
{
}
void
verify() override
{
}
int
fdRequired() const override
{
return 3;
}
};
//------------------------------------------------------------------------------
class SnugDBFactory : public Factory
{
public:
SnugDBFactory()
{
Manager::instance().insert(*this);
}
~SnugDBFactory() override
{
Manager::instance().erase(*this);
}
std::string
getName() const override
{
return "SnugDB";
}
std::unique_ptr<Backend>
createInstance(
size_t keyBytes,
Section const& keyValues,
std::size_t burstSize,
Scheduler& scheduler,
beast::Journal journal) override
{
return std::make_unique<SnugDBBackend>(keyValues, scheduler, journal);
}
std::unique_ptr<Backend>
createInstance(
size_t keyBytes,
Section const& keyValues,
std::size_t burstSize,
Scheduler& scheduler,
nudb::context& context,
beast::Journal journal) override
{
return std::make_unique<SnugDBBackend>(keyValues, scheduler, journal);
}
};
static SnugDBFactory snugDBFactory;
} // namespace NodeStore
} // namespace ripple

View File

@@ -0,0 +1,741 @@
#include <algorithm>
#include <array>
#include <fcntl.h>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <vector>
#define MMAPFLAGS (PROT_READ | PROT_WRITE | MAP_NORESERVE), MAP_SHARED
namespace snug {
int
compare_entries_reverse(const void* a, const void* b)
{
const uint64_t* a_key = static_cast<const uint64_t*>(a);
const uint64_t* b_key = static_cast<const uint64_t*>(b);
// Unrolled comparison of 4 uint64_t values (4 * 8 = 32 bytes)
if (b_key[0] > a_key[0])
return 1;
if (b_key[0] < a_key[0])
return -1;
if (b_key[1] > a_key[1])
return 1;
if (b_key[1] < a_key[1])
return -1;
if (b_key[2] > a_key[2])
return 1;
if (b_key[2] < a_key[2])
return -1;
if (b_key[3] > a_key[3])
return 1;
if (b_key[3] < a_key[3])
return -1;
return 0; // Keys are equal
}
class SnugDB
{
private:
static constexpr uint64_t SNUGSIZE =
256ull * 1024ull * 1024ull * 1024ull; // 256 GiB
static constexpr uint64_t BIGSIZE =
10ull * 1024ull * 1024ull * 1024ull * 1024ull; // 10 TiB
static constexpr size_t BUCKET_COUNT = 1048576;
std::unique_ptr<std::shared_mutex[]> mutexes =
std::make_unique<std::shared_mutex[]>(BUCKET_COUNT);
// each file snug.0 snug.1 ... is mmaped and the pointer
uint8_t* mapped_files[1024];
uint64_t mapped_files_count{0};
uint8_t* big_file; // this file has 64kib blocks in it which are used
// as an overflow for large blobs
std::mutex big_file_mutex; // locked when incrementing the "next new block"
// pointer
// only used when adding a new file
std::mutex mapped_files_count_mutex;
std::string const path;
// 0 = success
// 1 = could not open
// 2 = could not seek
// 3 = could not write at end of file
int
alloc_file(char const* fn, uint64_t size)
{
int fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0)
return 1;
// must be a multiple of bufsize
if (lseek(fd, size, SEEK_SET) == -1)
{
close(fd);
unlink(fn);
return 2;
}
if (write(fd, "", 1) != 1)
{
close(fd);
unlink(fn);
return 3;
}
close(fd);
return 0;
}
// 0 = file exists and is right size
int
check_file(char const* fn, uint64_t size)
{
struct stat st;
int file_exists = (stat(fn, &st) == 0);
if (!file_exists)
return 1;
if (st.st_size != size + 1)
return 2;
return 0;
}
#define OFFSET(byte0, byte1, byte2) \
(((((uint64_t)(byte0 & 0xFFU)) << 12) + \
(((uint64_t)(byte1 & 0xFFU)) << 4) + ((uint64_t)(byte2 & 0xFU))) \
<< 18)
// check if 32 bytes are 0, which they will be for a zero entry
#define IS_ZERO_ENTRY(x) \
(*((uint64_t*)((x) + 0)) == 0 && *((uint64_t*)((x) + 8)) == 0 && \
*((uint64_t*)((x) + 16)) == 0 && *((uint64_t*)((x) + 24)) == 0)
#define IS_ENTRY(x, y) \
(*((uint64_t*)((x) + 0)) == *((uint64_t*)((y) + 0)) && \
*((uint64_t*)((x) + 8)) == *((uint64_t*)((y) + 8)) && \
*((uint64_t*)((x) + 16)) == *((uint64_t*)((y) + 16)) && \
*((uint64_t*)((x) + 24)) == *((uint64_t*)((y) + 24)))
#define WRITE_KEY(x /* dst */, y /* src */, flags) \
{ \
*((uint64_t*)((x) + 0)) = *((uint64_t*)((y) + 0)); \
*((uint64_t*)((x) + 8)) = *((uint64_t*)((y) + 8)); \
*((uint64_t*)((x) + 16)) = *((uint64_t*)((y) + 16)); \
*((uint64_t*)((x) + 24)) = *((uint64_t*)((y) + 24)); \
*((uint64_t*)((x) + 32)) = flags; \
}
// if an entry exceeds 984 bytes then the overflow is written
// into the snug.big file in a linked list of 32kib blocks
// the first of those blocks is a control block
uint64_t
get_big_block()
{
std::unique_lock<std::mutex> lock(big_file_mutex);
uint64_t free_blocks = *((uint64_t*)(big_file + 8));
if (free_blocks == 0)
{
// no free blocks, allocate a new one
uint64_t next_block = *((uint64_t*)big_file);
// special edge case, first block ever allocated:
if (!next_block)
next_block += 32768;
*((uint64_t*)(big_file)) = next_block + 32768;
if (next_block + 32768 > BIGSIZE)
return 0;
return next_block;
}
// grab the nth one
uint8_t* offset = big_file + 16 + 8 * (free_blocks - 1);
// decrement free block counter
*(uint64_t*)(big_file + 8) -= 1;
return *((uint64_t*)offset);
}
void
unalloc_blocks(uint64_t next_block)
{
if (next_block != 0)
{
// scope the lock only if called with non-zero nextblock
std::unique_lock<std::mutex> lock(big_file_mutex);
do
{
uint64_t free_blocks = *((uint64_t*)(big_file + 8));
if (free_blocks >= 4095)
break;
uint8_t* offset = big_file + 16 + 8 * free_blocks;
*((uint64_t*)offset) = next_block;
*((uint64_t*)(big_file + 8)) += 1;
uint8_t* big_ptr = big_file + next_block;
uint64_t previous = next_block;
next_block = *((uint64_t*)(big_file + next_block));
// clear the pointer on the old block
*((uint64_t*)(big_file + previous)) = 0;
} while (next_block != 0);
}
}
/*
* First big entry is control block:
* 0 - 7: The next free new block
* 8 - 15: The number of free blocks blow
* 16 - 23 [... repeating]: The next free unused block
*/
/*
* Big entry format:
* 0 - 7: next block in chain, if any.
* 8 - 32767: payload
*/
// return 0 = failure
// > 0 = first block in the chain
uint64_t
write_big_entry_internal(uint8_t* data, ssize_t len, uint64_t next_block)
{
uint64_t first_block = 0;
uint64_t* last_block_ptr = 0;
do
{
// if next_block is populated we follow an existing pathway
// otherwise allocate a new block now
if (!next_block)
next_block = get_big_block();
if (!next_block)
return 0;
if (!first_block)
first_block = next_block;
if (last_block_ptr)
*last_block_ptr = next_block;
uint8_t* big_ptr = big_file + next_block;
// copy to the block
ssize_t to_write = len > 32760 ? 32760 : len;
memcpy(big_ptr + 8, data, to_write);
data += to_write;
len -= to_write;
next_block = *((uint64_t*)big_ptr);
last_block_ptr = (uint64_t*)big_ptr;
} while (len > 0);
// if there's a dangling chain we'll unallocate it
if (next_block != 0)
unalloc_blocks(next_block);
return first_block;
}
/*
* Entry format:
* 0 - 31: the 32 byte key
* 32 - 39: flags (high 4 bytes are flags, low 4 are size)
* 40 - 1023: data (up to 984 bytes)
*/
// 0 = success
// 1 = bucket full
// 2 = big blocks full
int
write_entry_internal(
uint8_t* data,
uint8_t* key,
uint8_t* val,
uint32_t len)
{
// find the entry
uint64_t offset = OFFSET(key[0], key[1], (key[2] >> 4));
// lock the bucket for writing
std::unique_lock<std::shared_mutex> lock(mutexes[offset >> 18]);
uint8_t* start = data + offset;
for (int i = 0; i < 256 * 1024; i += 1024)
{
bool const found = IS_ENTRY(start + i, key);
if (!found && !IS_ZERO_ENTRY(start + i))
continue;
// special edge case: the key doesn't exist and they're trying to
// delete it
if (!found && len == 0)
return 0;
// read flags
uint64_t flags = *((uint64_t*)(start + i + 32));
// big entries are tricky
bool const old_big = (flags >> 32) != 0;
bool const new_big = len > 984;
if (new_big)
{
// write_big_entry_internal(uint8_t* data, ssize_t len, uint64_t
// next_block)
uint64_t first_block = write_big_entry_internal(
val + 984, len - 984, (old_big ? (flags >> 32) : 0));
if (first_block == 0) // error state
{
if (old_big)
unalloc_blocks(flags >> 32);
return 2;
}
flags = (first_block << 32) + len;
}
else if (old_big) // big blocks exist but new value is small
{
// unallocate the old chain
unalloc_blocks(flags >> 32);
}
if (!new_big)
flags = len;
if (len == 0)
{
// deletion requests are written as zero keys
memset(start + i, 0, 1024);
}
else
{
/// write entry
WRITE_KEY(start + i, key, flags);
memcpy(start + i + 40, val, (len > 984 ? 984 : len));
}
// sort the bucket backwards so 0's appear at the end
qsort(start, 256, 1024, compare_entries_reverse);
return 0;
}
/// file (bucket) full
return 1;
}
// out_len carries the length of the output buffer when calling and is
// replaced with the length of the data found when returning
int
read_entry_internal(
uint8_t* data,
uint8_t* key,
uint8_t* val_out,
uint64_t* out_len)
{
uint64_t buf_len = *out_len;
// find the entry
uint64_t offset = OFFSET(key[0], key[1], (key[2] >> 4));
uint8_t* start = data + offset;
// lock the bucket for reading
std::shared_lock<std::shared_mutex> lock(mutexes[offset >> 18]);
for (int i = 0; i < 256 * 1024; i += 1024)
{
if (IS_ZERO_ENTRY(start + i))
return 1;
if (!IS_ENTRY(start + i, key))
continue;
// read out the value
uint64_t flags = *((uint64_t*)(start + i + 32));
uint32_t size = flags & 0xFFFFFFFFUL;
uint64_t next_block = flags >> 32;
if (size > buf_len)
return 2;
*out_len = size;
size_t to_read = size > 984 ? 984 : size;
memcpy(val_out, start + i + 40, to_read);
val_out += to_read;
size -= to_read;
// big block read logic
while (size > 0)
{
// follow big block pointers
if (!next_block)
{
printf("End while size=%d\n", size);
return 3;
}
uint8_t* big_ptr = big_file + next_block;
to_read = size > 32760 ? 32760 : size;
memcpy(val_out, big_ptr + 8, to_read);
val_out += to_read;
size -= to_read;
next_block = *((uint64_t*)big_ptr);
}
return 0;
}
return 1;
}
void
setup()
{
struct stat path_stat;
if (stat(path.c_str(), &path_stat) != 0)
throw std::runtime_error(
"Error checking path: " + path + " - " +
std::string(strerror(errno)));
if (!S_ISDIR(path_stat.st_mode))
throw std::runtime_error("Path is not a directory: " + path);
if (access(path.c_str(), R_OK | W_OK | X_OK) != 0)
throw std::runtime_error(
"Insufficient permissions for path: " + path);
// Search for existing snug files sequentially
std::vector<std::string> snug_files;
for (int file_index = 0; file_index < 1024; ++file_index)
{
std::string filename = "snug." + std::to_string(file_index);
std::string full_path = path + "/" + filename;
if (access(full_path.c_str(), F_OK) != 0)
break;
snug_files.push_back(filename);
}
// If no files found, create snug.0
if (snug_files.empty())
{
std::string new_file = path + "/snug.0";
int result = alloc_file(new_file.c_str(), SNUGSIZE);
if (result != 0)
throw std::runtime_error(
"Failed to create initial file: " + new_file);
snug_files.push_back("snug.0");
}
// Memory map all files
for (const auto& file : snug_files)
{
std::string full_path = path + "/" + file;
if (check_file(full_path.c_str(), SNUGSIZE) != 0)
throw std::runtime_error("File was the wrong size: " + file);
int fd = open(full_path.c_str(), O_RDWR);
if (fd == -1)
throw std::runtime_error("Unable to open file: " + full_path);
struct stat file_stat;
if (fstat(fd, &file_stat) == -1)
{
close(fd);
throw std::runtime_error(
"Unable to get file stats: " + full_path);
}
void* mapped = mmap(nullptr, file_stat.st_size, MMAPFLAGS, fd, 0);
close(fd); // Can close fd after mmap
if (mapped == MAP_FAILED)
throw std::runtime_error("Unable to mmap file: " + full_path);
mapped_files[mapped_files_count++] = static_cast<uint8_t*>(mapped);
}
// create and map snug.big overflow file
{
std::string new_file = path + "/snug.big";
if (check_file(new_file.c_str(), BIGSIZE) != 0)
{
int result = alloc_file(new_file.c_str(), BIGSIZE);
if (result != 0)
throw std::runtime_error(
"Failed to create initial file: " + new_file);
}
int fd = open(new_file.c_str(), O_RDWR);
if (fd == -1)
throw std::runtime_error("Unable to open file: " + new_file);
struct stat file_stat;
if (fstat(fd, &file_stat) == -1)
{
close(fd);
throw std::runtime_error(
"Unable to get file stats: " + new_file);
}
void* mapped = mmap(nullptr, file_stat.st_size, MMAPFLAGS, fd, 0);
close(fd); // Can close fd after mmap
if (mapped == MAP_FAILED)
throw std::runtime_error("Unable to mmap file: " + new_file);
big_file = static_cast<uint8_t*>(mapped);
}
}
public:
SnugDB(std::string path_) : path(path_)
{
setup();
}
~SnugDB()
{
// Unmap all files in destructor
// RH TODO: consider lock here
for (int i = 0; i < mapped_files_count; ++i)
munmap(mapped_files[i], SNUGSIZE);
// unmap the big file
munmap(big_file, BIGSIZE);
}
int
write_entry(uint8_t* key, uint8_t* val, ssize_t len)
{
for (size_t i = 0; i < mapped_files_count; ++i)
{
int result = write_entry_internal(mapped_files[i], key, val, len);
if (result == 0)
return 0;
if (result != 1) // only bucket full falls through
return result;
}
// All existing files are full, allocate a new one
{
// acquire the mutex
const std::lock_guard<std::mutex> lock(mapped_files_count_mutex);
std::string new_file =
path + "/snug." + std::to_string(mapped_files_count);
int alloc_result = alloc_file(new_file.c_str(), SNUGSIZE);
if (alloc_result != 0)
return alloc_result +
10; // Return error code from alloc_file if it fails (+10)
int fd = open(new_file.c_str(), O_RDWR);
if (fd == -1)
return 1; // Return 1 for open failure
struct stat file_stat;
if (fstat(fd, &file_stat) == -1)
{
close(fd);
return 2; // Return 2 for fstat failure
}
void* mapped = mmap(
nullptr,
file_stat.st_size,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0);
close(fd); // Can close fd after mmap
if (mapped == MAP_FAILED)
return 3; // Return 3 for mmap failure
// add the new file to the map, and increment the counter
mapped_files[mapped_files_count] = static_cast<uint8_t*>(mapped);
// this is the last possible thing done
mapped_files_count++;
}
// finally write the entry
// RH TODO: consider adding a recursion guard here
return write_entry(key, val, len);
}
int
read_entry(uint8_t* key, uint8_t* val_out, uint64_t* out_len_orig)
{
for (size_t i = 0; i < mapped_files_count; ++i)
{
uint64_t out_len = *out_len_orig;
int result =
read_entry_internal(mapped_files[i], key, val_out, &out_len);
if (result == 0)
{
*out_len_orig = out_len;
return 0; // Entry found and read successfully
}
if (result == 2)
return 2; // Output buffer too small
}
// Entry not found in any file
return 1;
}
void
visit_all(
void (*f)(uint8_t*, uint8_t*, uint64_t, void* /*opaque caller val*/),
void* opaque)
{
// to visit all we only need to check snug.0 to begin with
// we go to the first bucket
// if we find no entries there we go to the next bucket
// if we find entries there then we need to count them,
// if we find 256 entries there then we go to snug.1 and so on until we
// run out we merge sort the entries into a list for the visit
for (uint64_t bucket = 0; bucket < BUCKET_COUNT; ++bucket)
{
// acquire the bucket lock
std::shared_lock<std::shared_mutex> lock(mutexes[bucket]);
// check the bucket
uint8_t* ptr = mapped_files[0] + (bucket << 18);
if (*((uint64_t*)(ptr + 32)) == 0)
continue;
// if (IS_ZERO_ENTRY(ptr))
// continue;
// live bucket, collect entries
std::vector<uint8_t*> entries;
{
// need to acquire the mutex to prevent a race condition
// where a new file is being added while we're searching
const std::lock_guard<std::mutex> lock(
mapped_files_count_mutex);
// preallocate worst case scenario, RIP memory
entries.reserve(mapped_files_count * 256);
for (int i = 0; i < mapped_files_count; ++i)
{
uint8_t* ptr = mapped_files[i] + (bucket << 18);
for (int entry_count = 0;
!IS_ZERO_ENTRY(ptr) && entry_count < 256;
++entry_count, ptr += 1024)
entries.push_back(ptr);
}
}
if (entries.empty())
continue;
// sort the entries
std::sort(
entries.begin(),
entries.end(),
[](const uint8_t* a, const uint8_t* b) {
return memcmp(a, b, 32) < 0;
});
for (auto e : entries)
{
// visitation
uint8_t* entry = &e[0];
uint64_t flags = *((uint64_t*)(entry + 32));
uint64_t next_block = flags >> 32;
uint64_t size = flags & 0xFFFFFFFFULL;
if (size <= 984)
{
f(entry, entry + 40, size, opaque);
continue;
}
// copy big entry to a buffer
std::unique_ptr<uint8_t[]> copybuf =
std::make_unique<uint8_t[]>(size);
uint8_t* data = &(copybuf[0]);
memcpy(data, entry + 40, 984);
data += 984;
size -= 984;
// big block read logic
while (size > 0)
{
// follow big block pointers
if (!next_block)
{
printf("End while size=%lu\n", size);
return;
}
uint8_t* big_ptr = big_file + next_block;
uint64_t to_read = size > 32760 ? 32760 : size;
memcpy(data, big_ptr + 8, to_read);
data += to_read;
size -= to_read;
next_block = *((uint64_t*)big_ptr);
}
f(entry, data, (flags & 0xFFFFFFFFULL), opaque);
}
}
}
};
} // namespace snug

View File

@@ -74,7 +74,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 73;
static constexpr std::size_t numFeatures = 70;
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
@@ -358,9 +358,6 @@ extern uint256 const fixXahauV2;
extern uint256 const featureRemit;
extern uint256 const featureZeroB2M;
extern uint256 const fixNSDelete;
extern uint256 const fix240819;
extern uint256 const fixPageCap;
extern uint256 const fix240911;
} // namespace ripple

View File

@@ -464,9 +464,6 @@ REGISTER_FIX (fixXahauV2, Supported::yes, VoteBehavior::De
REGISTER_FEATURE(Remit, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(ZeroB2M, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX (fixNSDelete, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX (fix240819, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX (fixPageCap, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX (fix240911, Supported::yes, VoteBehavior::DefaultYes);
// The following amendments are obsolete, but must remain supported
// because they could potentially get enabled.

View File

@@ -256,8 +256,6 @@ BaseWSPeer<Handler, Impl>::close(
return post(strand_, [self = impl().shared_from_this(), reason] {
self->close(reason);
});
if (do_close_)
return;
do_close_ = true;
if (wq_.empty())
{

View File

@@ -106,14 +106,6 @@ public:
return {};
}
virtual void
acquireAsync(
uint256 const& hash,
std::uint32_t seq,
InboundLedger::Reason reason) override
{
}
virtual std::shared_ptr<InboundLedger>
find(LedgerHash const& hash) override
{

View File

@@ -19,8 +19,6 @@
#include <ripple/app/hook/Enum.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/tx/impl/SetHook.h>
#include <ripple/json/json_reader.h>
#include <ripple/json/json_writer.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/jss.h>
#include <test/app/SetHook_wasm.h>
@@ -89,147 +87,6 @@ public:
// fee unit tests, the rest of the time we want to ignore it.
#define HSFEE fee(100'000'000)
#define M(m) memo(m, "", "")
std::unique_ptr<Config>
makePageCapConfig(
FeatureBitset features,
uint32_t networkID,
std::string fee,
std::string a_res,
std::string o_res,
uint32_t ledgerID)
{
using namespace jtx;
Json::Value jsonValue;
Json::Reader reader;
std::string base_genesis = R"json({
"ledger": {
"accepted": true,
"accountState": [
{
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Balance": "100000000000000000",
"Flags": 0,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 0,
"PreviousTxnID": "A92EF82C3C68F771927E3892A2F708F12CBD492EF68A860F042E4053C8EC6C8D",
"PreviousTxnLgrSeq": 0,
"Sequence": 1,
"index": "2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8"
},
{
"Amendments": [],
"Flags": 0,
"LedgerEntryType": "Amendments",
"index": "7DB0788C020F02780A673DC74757F23823FA3014C1866E72CC4CD8B226CD6EF4"
},
{
"BaseFee": "A",
"Flags": 0,
"LedgerEntryType": "FeeSettings",
"ReferenceFeeUnits": 10,
"ReserveBase": 1000000,
"ReserveIncrement": 200000,
"XahauActivationLgrSeq": 0,
"index": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A651"
},
{
"Flags": 0,
"IndexNext": "40000",
"IndexPrevious": "3fffe",
"Indexes": [],
"LedgerEntryType": "DirectoryNode",
"Owner": "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
"RootIndex": "EC1A88838E9ADA27E8ED583917C1530D2F48C3A3C93F50EDAD662D662E9BCC76",
"index": "EC1A88838E9ADA27E8ED583917C1530D2F48C3A3C93F50EDAD662D662E9BCC76"
},
{
"Flags": 0,
"IndexNext": "3fffe",
"IndexPrevious": "3fffd",
"Indexes": [],
"LedgerEntryType": "DirectoryNode",
"Owner": "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
"RootIndex": "EC1A88838E9ADA27E8ED583917C1530D2F48C3A3C93F50EDAD662D662E9BCC76",
"index": "4A5F3F9E6762A4F89FFCD385FF2309E1F7D1309321BFEEA61D5C9ACB768DB61B"
},
{
"Flags": 0,
"Indexes": [],
"LedgerEntryType": "DirectoryNode",
"Owner": "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
"RootIndex": "A33EC6BB85FB5674074C4A3A43373BB17645308F3EAE1933E3E35252162B217D",
"index": "A33EC6BB85FB5674074C4A3A43373BB17645308F3EAE1933E3E35252162B217D"
},
{
"Account": "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
"Balance": "99999899000000",
"Flags": 8388608,
"LedgerEntryType": "AccountRoot",
"HookNamespaces": [ "0000000000000000000000000000000000000000000000000000000000000000" ],
"HookStateCount": 8388576,
"OwnerCount": 8388577,
"PreviousTxnID": "A92EF82C3C68F771927E3892A2F708F12CBD492EF68A860F042E4053C8EC6C8D",
"PreviousTxnLgrSeq": 0,
"Sequence": 3,
"index": "92FA6A9FC8EA6018D5D16532D7795C91BFB0831355BDFDA177E86C8BF997985F"
}
],
"account_hash": "5DF3A98772FB73E782B8740E87885C6BAD9BA486422E3626DEF968AD2CB2C514",
"close_flags": 0,
"close_time": 0,
"close_time_human": "2000-Jan-01 00:00:00.000000",
"close_time_resolution": 10,
"closed": true,
"hash": "56DA0940767AC2F17F0E384F04816002403D0756432B9D503DDA20128A2AAF11",
"ledger_hash": "56DA0940767AC2F17F0E384F04816002403D0756432B9D503DDA20128A2AAF11",
"ledger_index": "0",
"parent_close_time": 0,
"parent_hash": "56DA0940767AC2F17F0E384F04816002403D0756432B9D503DDA20128A2AAF11",
"seqNum": "5",
"totalCoins": "100000000000000000",
"total_coins": "100000000000000000",
"transaction_hash": "9A77D1D1A4B36DA77B9C4DC63FDEB8F821741D157802F9C42A6ED86003D8B4A0",
"transactions": []
},
"ledger_current_index": 0,
"status": "success",
"validated": true
})json";
reader.parse(base_genesis, jsonValue);
foreachFeature(features, [&](uint256 const& feature) {
std::string featureName = featureToName(feature);
std::optional<uint256> featureHash =
getRegisteredFeature(featureName);
if (featureHash.has_value())
{
std::string hashString = to_string(featureHash.value());
jsonValue["ledger"]["accountState"][1]["Amendments"].append(
hashString);
}
});
jsonValue["ledger_current_index"] = ledgerID;
jsonValue["ledger"]["ledger_index"] = to_string(ledgerID);
jsonValue["ledger"]["seqNum"] = to_string(ledgerID);
return envconfig([&](std::unique_ptr<Config> cfg) {
cfg->NETWORK_ID = networkID;
cfg->START_LEDGER = jsonValue.toStyledString();
cfg->START_UP = Config::LOAD_JSON;
Section config;
config.append(
{"reference_fee = " + fee,
"account_reserve = " + a_res,
"owner_reserve = " + o_res});
auto setup = setup_FeeVote(config);
cfg->FEES = setup;
return cfg;
});
}
void
testHooksOwnerDir(FeatureBitset features)
{
@@ -1071,73 +928,6 @@ public:
}
}
void
testPageCap(FeatureBitset features)
{
testcase("Test page cap");
using namespace jtx;
test::jtx::Env env{
*this,
makePageCapConfig(features, 21337, "10", "1000000", "200000", 0),
features};
bool const hasFix = env.current()->rules().enabled(fixPageCap);
auto const alice = Account{"alice"};
env.memoize(alice);
auto const bob = Account{"bob"};
env.fund(XRP(10000000), bob);
auto const preHookCount = (*env.le(alice))[sfHookStateCount];
auto const preOwnerCount = (*env.le(alice))[sfOwnerCount];
std::string hook =
"0061736D01000000012A0660057F7F7F7F7F017E60027F7F017E60027F7F017F60"
"047F7F7F7F017E60037F7F7E017E60017F017E02520603656E7605747261636500"
"0003656E760C686F6F6B5F6163636F756E74000103656E76025F67000203656E76"
"057374617465000303656E760973746174655F736574000303656E760661636365"
"70740004030201050503010002062B077F0141B088040B7F004180080B7F0041A2"
"080B7F004180080B7F0041B088040B7F0041000B7F0041010B07080104686F6F6B"
"00060ABF830001BB830002017F017E230041D0006B220124002001200036024C41"
"900841114180084110410010001A200141306A2200411410011A4101410110021A"
"200141286A41082000411410031A2001200131002F200131002842388620013100"
"294230867C200131002A4228867C200131002B4220867C200131002C4218867C20"
"0131002D4210867C200131002E4208867C7C3703202001410036021C0340419280"
"80807841C90110021A200128021C41C8014E4504402001200134021C2001290320"
"42C8017E7C370310200141106A220041082000410810041A2001200128021C4101"
"6A36021C0C010B0B2001200129032042017C3703202001200141286A220036020C"
"200128020C200129032042388842FF01833C0000200128020C2001290320423088"
"42FF01833C0001200128020C200129032042288842FF01833C0002200128020C20"
"0129032042208842FF01833C0003200128020C200129032042188842FF01833C00"
"04200128020C200129032042108842FF01833C0005200128020C20012903204208"
"8842FF01833C0006200128020C200129032042FF01833C00072000410820014130"
"6A411410041A4180084110421C1005200141D0006A24000B0B2801004180080B21"
"426173652E633A2043616C6C65642E0022426173652E633A2043616C6C65642E2"
"2";
// install the hook on alice
env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0),
M("set fix_page_cap"),
HSFEE);
env.close();
env(invoke::invoke(alice),
M("test simple"),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
BEAST_EXPECT(
(*env.le(alice))[sfHookStateCount] == hasFix ? preHookCount + 200
: preHookCount + 64);
BEAST_EXPECT(
(*env.le(alice))[sfOwnerCount] == hasFix ? preHookCount + 202
: preHookCount + 66);
}
void
testCreate(FeatureBitset features)
{
@@ -11971,7 +11761,6 @@ public:
testNSDelete(features);
testNSDeletePartial(features);
testPageCap(features);
testWasm(features);
test_accept(features);
@@ -12073,8 +11862,6 @@ public:
testWithFeatures(sa - fixXahauV2);
testWithFeatures(sa - fixXahauV1 - fixXahauV2);
testWithFeatures(sa - fixXahauV1 - fixXahauV2 - fixNSDelete);
testWithFeatures(
sa - fixXahauV1 - fixXahauV2 - fixNSDelete - fixPageCap);
}
private:

View File

@@ -3968,8 +3968,8 @@ struct XahauGenesis_test : public beast::unit_test::suite
using namespace std::chrono_literals;
testcase("test claim reward valid without unl report");
Env env{*this, envconfig(), features - featureXahauGenesis};
bool const has240819 = env.current()->rules().enabled(fix240819);
Env env{
*this, envconfig(), supported_amendments() - featureXahauGenesis};
double const rateDrops = 0.00333333333 * 1'000'000;
STAmount const feesXRP = XRP(1);
@@ -4050,12 +4050,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
// validate account fields
STAmount const postUser = preUser + netReward;
BEAST_EXPECT(expectAccountFields(
env,
user,
preLedger,
preLedger + 1,
has240819 ? (preUser - feesXRP) : postUser,
preTime));
env, user, preLedger, preLedger + 1, postUser, preTime));
env(claimReward(user, env.master), fee(feesXRP), ter(tecHOOK_REJECTED));
env.close();
@@ -4100,12 +4095,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
// validate account fields
STAmount const postUser1 = preUser1 + netReward1;
BEAST_EXPECT(expectAccountFields(
env,
user,
preLedger1,
preLedger1 + 1,
has240819 ? (preUser1 - feesXRP) : postUser1,
preTime1));
env, user, preLedger1, preLedger1 + 1, postUser1, preTime1));
}
void
@@ -4229,14 +4219,8 @@ struct XahauGenesis_test : public beast::unit_test::suite
// validate account fields
STAmount const postUser = preUser + netReward;
bool const has240819 = env.current()->rules().enabled(fix240819);
BEAST_EXPECT(expectAccountFields(
env,
user,
preLedger,
preLedger + 1,
has240819 ? (preUser - feesXRP) : postUser,
preTime));
env, user, preLedger, preLedger + 1, postUser, preTime));
}
void
@@ -4368,15 +4352,10 @@ struct XahauGenesis_test : public beast::unit_test::suite
// validate account fields
STAmount const postAlice = preAlice + netReward + l1Reward;
bool const boolResult = withXahauV1 ? true : false;
bool const has240819 = env.current()->rules().enabled(fix240819);
BEAST_EXPECT(
expectAccountFields(
env,
alice,
preLedger,
preLedger + 1,
has240819 ? (preAlice - feesXRP) : postAlice,
preTime) == boolResult);
env, alice, preLedger, preLedger + 1, postAlice, preTime) ==
boolResult);
}
}
@@ -4388,7 +4367,6 @@ struct XahauGenesis_test : public beast::unit_test::suite
testcase("test claim reward optin optout");
Env env{*this, envconfig(), features - featureXahauGenesis};
bool const has240819 = env.current()->rules().enabled(fix240819);
double const rateDrops = 0.00333333333 * 1'000'000;
STAmount const feesXRP = XRP(1);
@@ -4458,12 +4436,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
// validate account fields
STAmount const postUser = preUser + netReward;
BEAST_EXPECT(expectAccountFields(
env,
user,
preLedger,
preLedger + 1,
has240819 ? (preUser - feesXRP) : postUser,
preTime));
env, user, preLedger, preLedger + 1, postUser, preTime));
// opt out of claim rewards
env(claimReward(user, std::nullopt, 1), fee(feesXRP), ter(tesSUCCESS));
@@ -4488,7 +4461,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
user,
preLedger1,
preLedger1 + 1,
has240819 ? (env.balance(user) + feesXRP) : env.balance(user),
env.balance(user),
preTime1));
}
@@ -4570,14 +4543,8 @@ struct XahauGenesis_test : public beast::unit_test::suite
// validate account fields
STAmount const postUser = preUser + netReward;
bool const has240819 = env.current()->rules().enabled(fix240819);
BEAST_EXPECT(expectAccountFields(
env,
user,
preLedger,
has240819 ? preLedger : preLedger + 1,
has240819 ? (preUser - feesXRP) : postUser,
preTime));
env, user, preLedger, preLedger + 1, postUser, preTime));
}
void
@@ -4651,14 +4618,8 @@ struct XahauGenesis_test : public beast::unit_test::suite
// validate account fields
STAmount const postUser = preUser + netReward;
bool const has240819 = env.current()->rules().enabled(fix240819);
BEAST_EXPECT(expectAccountFields(
env,
user,
preLedger,
preLedger + 1,
has240819 ? (preUser - feesXRP) : postUser,
preTime));
env, user, preLedger, preLedger + 1, postUser, preTime));
}
void
@@ -4863,13 +4824,13 @@ struct XahauGenesis_test : public beast::unit_test::suite
Env env{
*this,
makeGenesisConfig(
features - featureXahauGenesis,
supported_amendments() - featureXahauGenesis,
21337,
"10",
"1000000",
"200000",
0),
features - featureXahauGenesis};
supported_amendments() - featureXahauGenesis};
STAmount const feesXRP = XRP(1);
@@ -4929,7 +4890,8 @@ struct XahauGenesis_test : public beast::unit_test::suite
using namespace std::chrono_literals;
testcase("test compound interest over 12 claims");
Env env{*this, envconfig(), features - featureXahauGenesis};
Env env{
*this, envconfig(), supported_amendments() - featureXahauGenesis};
double const rateDrops = 0.00333333333 * 1'000'000;
STAmount const feesXRP = XRP(1);
@@ -5003,14 +4965,8 @@ struct XahauGenesis_test : public beast::unit_test::suite
// validate account fields
STAmount const postUser = preUser + netReward;
bool const has240819 = env.current()->rules().enabled(fix240819);
BEAST_EXPECT(expectAccountFields(
env,
user,
preLedger,
preLedger + 1,
has240819 ? (preUser - feesXRP) : postUser,
preTime));
env, user, preLedger, preLedger + 1, postUser, preTime));
}
STAmount const endBal = env.balance(user);
@@ -5020,550 +4976,6 @@ struct XahauGenesis_test : public beast::unit_test::suite
BEAST_EXPECT(asPercent == 4);
}
void
testDeposit(FeatureBitset features)
{
using namespace jtx;
using namespace std::chrono_literals;
testcase("test deposit");
Env env{*this, envconfig(), features - featureXahauGenesis};
double const rateDrops = 0.00333333333 * 1'000'000;
STAmount const feesXRP = XRP(1);
auto const user = Account("user");
env.fund(XRP(1000), user);
env.close();
// setup governance
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
auto const david = Account("david");
auto const edward = Account("edward");
env.fund(XRP(10000), alice, bob, carol, david, edward);
env.close();
std::vector<AccountID> initial_members_ids{
alice.id(), bob.id(), carol.id(), david.id(), edward.id()};
setupGov(env, initial_members_ids);
// update reward delay
{
// this will be the new reward delay
// 100
std::vector<uint8_t> vote_data{
0x00U, 0x80U, 0xC6U, 0xA4U, 0x7EU, 0x8DU, 0x03U, 0x55U};
updateTopic(
env, alice, bob, carol, david, edward, 'R', 'D', vote_data);
}
// verify unl report does not exist
BEAST_EXPECT(hasUNLReport(env) == false);
// opt in claim reward
env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS));
env.close();
env(pay(alice, user, XRP(1000)));
env.close();
// close ledgers
for (int i = 0; i < 10; ++i)
{
env.close(10s);
}
// close claim ledger & time
STAmount const preUser = env.balance(user);
NetClock::time_point const preTime = lastClose(env);
std::uint32_t const preLedger = env.current()->seq();
auto const [acct, acctSle] = accountKeyAndSle(*env.current(), user);
// claim reward
env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS));
env.close();
// trigger emitted txn
env.close();
// calculate rewards
bool const has240819 = env.current()->rules().enabled(fix240819);
STAmount const netReward =
rewardUserAmount(*acctSle, preLedger, rateDrops);
BEAST_EXPECT(netReward == (has240819 ? XRP(6.383333) : XRP(6.663333)));
// validate account fields
STAmount const postUser = preUser + netReward;
BEAST_EXPECT(expectAccountFields(
env,
user,
preLedger,
preLedger + 1,
has240819 ? (preUser - feesXRP) : postUser,
preTime));
BEAST_EXPECT(
postUser == (has240819 ? XRP(2005.383333) : XRP(2005.663333)));
}
void
testDepositWithdraw(FeatureBitset features)
{
using namespace jtx;
using namespace std::chrono_literals;
testcase("test deposit withdraw");
Env env{*this, envconfig(), features - featureXahauGenesis};
double const rateDrops = 0.00333333333 * 1'000'000;
STAmount const feesXRP = XRP(1);
auto const user = Account("user");
env.fund(XRP(1000), user);
env.close();
// setup governance
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
auto const david = Account("david");
auto const edward = Account("edward");
env.fund(XRP(10000), alice, bob, carol, david, edward);
env.close();
std::vector<AccountID> initial_members_ids{
alice.id(), bob.id(), carol.id(), david.id(), edward.id()};
setupGov(env, initial_members_ids);
// update reward delay
{
// this will be the new reward delay
// 100
std::vector<uint8_t> vote_data{
0x00U, 0x80U, 0xC6U, 0xA4U, 0x7EU, 0x8DU, 0x03U, 0x55U};
updateTopic(
env, alice, bob, carol, david, edward, 'R', 'D', vote_data);
}
// verify unl report does not exist
BEAST_EXPECT(hasUNLReport(env) == false);
// opt in claim reward
env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS));
env.close();
env(pay(alice, user, XRP(1000)));
env.close();
env(pay(user, alice, XRP(1000)));
env.close();
// close ledgers
for (int i = 0; i < 10; ++i)
{
env.close(10s);
}
// close claim ledger & time
STAmount const preUser = env.balance(user);
NetClock::time_point const preTime = lastClose(env);
std::uint32_t const preLedger = env.current()->seq();
auto const [acct, acctSle] = accountKeyAndSle(*env.current(), user);
// claim reward
env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS));
env.close();
// trigger emitted txn
env.close();
// calculate rewards
bool const has240819 = env.current()->rules().enabled(fix240819);
STAmount const netReward =
rewardUserAmount(*acctSle, preLedger, rateDrops);
BEAST_EXPECT(netReward == XRP(3.583333));
// validate account fields
STAmount const postUser = preUser + netReward;
BEAST_EXPECT(expectAccountFields(
env,
user,
preLedger,
preLedger + 1,
has240819 ? (preUser - feesXRP) : postUser,
preTime));
BEAST_EXPECT(postUser == XRP(1002.583323));
}
void
testDepositLate(FeatureBitset features)
{
using namespace jtx;
using namespace std::chrono_literals;
testcase("test deposit late");
Env env{*this, envconfig(), features - featureXahauGenesis};
double const rateDrops = 0.00333333333 * 1'000'000;
STAmount const feesXRP = XRP(1);
auto const user = Account("user");
env.fund(XRP(1000), user);
env.close();
// setup governance
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
auto const david = Account("david");
auto const edward = Account("edward");
env.fund(XRP(10000), alice, bob, carol, david, edward);
env.close();
std::vector<AccountID> initial_members_ids{
alice.id(), bob.id(), carol.id(), david.id(), edward.id()};
setupGov(env, initial_members_ids);
// update reward delay
{
// this will be the new reward delay
// 100
std::vector<uint8_t> vote_data{
0x00U, 0x80U, 0xC6U, 0xA4U, 0x7EU, 0x8DU, 0x03U, 0x55U};
updateTopic(
env, alice, bob, carol, david, edward, 'R', 'D', vote_data);
}
// verify unl report does not exist
BEAST_EXPECT(hasUNLReport(env) == false);
// opt in claim reward
env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS));
env.close();
// close ledgers
for (int i = 0; i < 10; ++i)
{
env.close(10s);
}
env(pay(alice, user, XRP(1000)));
env.close();
// close claim ledger & time
STAmount const preUser = env.balance(user);
NetClock::time_point const preTime = lastClose(env);
std::uint32_t const preLedger = env.current()->seq();
auto const [acct, acctSle] = accountKeyAndSle(*env.current(), user);
// claim reward
env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS));
env.close();
// trigger emitted txn
env.close();
// calculate rewards
bool const has240819 = env.current()->rules().enabled(fix240819);
STAmount const netReward =
rewardUserAmount(*acctSle, preLedger, rateDrops);
BEAST_EXPECT(netReward == (has240819 ? XRP(3.606666) : XRP(6.663333)));
// validate account fields
STAmount const postUser = preUser + netReward;
BEAST_EXPECT(expectAccountFields(
env,
user,
preLedger,
preLedger + 1,
has240819 ? (preUser - feesXRP) : postUser,
preTime));
BEAST_EXPECT(
postUser == (has240819 ? XRP(2002.606666) : XRP(2005.663333)));
}
void
testDepositWithdrawLate(FeatureBitset features)
{
using namespace jtx;
using namespace std::chrono_literals;
testcase("test deposit late withdraw");
Env env{*this, envconfig(), features - featureXahauGenesis};
double const rateDrops = 0.00333333333 * 1'000'000;
STAmount const feesXRP = XRP(1);
auto const user = Account("user");
env.fund(XRP(1000), user);
env.close();
// setup governance
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
auto const david = Account("david");
auto const edward = Account("edward");
env.fund(XRP(10000), alice, bob, carol, david, edward);
env.close();
std::vector<AccountID> initial_members_ids{
alice.id(), bob.id(), carol.id(), david.id(), edward.id()};
setupGov(env, initial_members_ids);
// update reward delay
{
// this will be the new reward delay
// 100
std::vector<uint8_t> vote_data{
0x00U, 0x80U, 0xC6U, 0xA4U, 0x7EU, 0x8DU, 0x03U, 0x55U};
updateTopic(
env, alice, bob, carol, david, edward, 'R', 'D', vote_data);
}
// verify unl report does not exist
BEAST_EXPECT(hasUNLReport(env) == false);
// opt in claim reward
env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS));
env.close();
// close ledgers
for (int i = 0; i < 10; ++i)
{
env.close(10s);
}
env(pay(alice, user, XRP(1000)));
env.close();
env(pay(user, alice, XRP(1000)));
env.close();
// close claim ledger & time
STAmount const preUser = env.balance(user);
NetClock::time_point const preTime = lastClose(env);
std::uint32_t const preLedger = env.current()->seq();
auto const [acct, acctSle] = accountKeyAndSle(*env.current(), user);
// claim reward
env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS));
env.close();
// trigger emitted txn
env.close();
// calculate rewards
bool const has240819 = env.current()->rules().enabled(fix240819);
STAmount const netReward =
rewardUserAmount(*acctSle, preLedger, rateDrops);
BEAST_EXPECT(netReward == (has240819 ? XRP(3.583333) : XRP(6.149999)));
// validate account fields
STAmount const postUser = preUser + netReward;
BEAST_EXPECT(expectAccountFields(
env,
user,
preLedger,
preLedger + 1,
has240819 ? (preUser - feesXRP) : postUser,
preTime));
BEAST_EXPECT(
postUser == (has240819 ? XRP(1002.583323) : XRP(1005.149989)));
}
void
testNoClaim(FeatureBitset features)
{
using namespace jtx;
using namespace std::chrono_literals;
testcase("test no claim");
Env env{*this, envconfig(), features - featureXahauGenesis};
double const rateDrops = 0.00333333333 * 1'000'000;
STAmount const feesXRP = XRP(1);
auto const user = Account("user");
env.fund(XRP(1000), user);
env.close();
// setup governance
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
auto const david = Account("david");
auto const edward = Account("edward");
env.fund(XRP(10000), alice, bob, carol, david, edward);
env.close();
std::vector<AccountID> initial_members_ids{
alice.id(), bob.id(), carol.id(), david.id(), edward.id()};
setupGov(env, initial_members_ids);
// update reward delay
{
// this will be the new reward delay
// 100
std::vector<uint8_t> vote_data{
0x00U, 0x80U, 0xC6U, 0xA4U, 0x7EU, 0x8DU, 0x03U, 0x55U};
updateTopic(
env, alice, bob, carol, david, edward, 'R', 'D', vote_data);
}
// verify unl report does not exist
BEAST_EXPECT(hasUNLReport(env) == false);
// opt in claim reward
env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS));
env.close();
// close ledgers (2 cycles)
for (int i = 0; i < 20; ++i)
{
env.close(10s);
}
// close claim ledger & time
STAmount const preUser = env.balance(user);
NetClock::time_point const preTime = lastClose(env);
std::uint32_t const preLedger = env.current()->seq();
auto const [acct, acctSle] = accountKeyAndSle(*env.current(), user);
// claim reward
env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS));
env.close();
// trigger emitted txn
env.close();
// calculate rewards
bool const hasFix = env.current()->rules().enabled(fix240819) &&
env.current()->rules().enabled(fix240911);
STAmount const netReward =
rewardUserAmount(*acctSle, preLedger, rateDrops);
BEAST_EXPECT(netReward == (hasFix ? XRP(3.329999) : XRP(3.329999)));
// validate account fields
STAmount const postUser = preUser + netReward;
BEAST_EXPECT(expectAccountFields(
env,
user,
preLedger,
preLedger + 1,
hasFix ? (preUser - feesXRP) : postUser,
preTime));
BEAST_EXPECT(
postUser == (hasFix ? XRP(1002.329999) : XRP(1002.329999)));
}
void
testNoClaimLate(FeatureBitset features)
{
using namespace jtx;
using namespace std::chrono_literals;
testcase("test no claim late");
Env env{*this, envconfig(), features - featureXahauGenesis};
double const rateDrops = 0.00333333333 * 1'000'000;
STAmount const feesXRP = XRP(1);
auto const user = Account("user");
env.fund(XRP(1000), user);
env.close();
// setup governance
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
auto const david = Account("david");
auto const edward = Account("edward");
env.fund(XRP(10000), alice, bob, carol, david, edward);
env.close();
std::vector<AccountID> initial_members_ids{
alice.id(), bob.id(), carol.id(), david.id(), edward.id()};
setupGov(env, initial_members_ids);
// update reward delay
{
// this will be the new reward delay
// 100
std::vector<uint8_t> vote_data{
0x00U, 0x80U, 0xC6U, 0xA4U, 0x7EU, 0x8DU, 0x03U, 0x55U};
updateTopic(
env, alice, bob, carol, david, edward, 'R', 'D', vote_data);
}
// verify unl report does not exist
BEAST_EXPECT(hasUNLReport(env) == false);
// opt in claim reward
env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS));
env.close();
// close ledgers (2 cycles)
for (int i = 0; i < 20; ++i)
{
env.close(10s);
}
env(pay(alice, user, XRP(1000)));
env.close();
// close claim ledger & time
STAmount const preUser = env.balance(user);
NetClock::time_point const preTime = lastClose(env);
std::uint32_t const preLedger = env.current()->seq();
auto const [acct, acctSle] = accountKeyAndSle(*env.current(), user);
// claim reward
env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS));
env.close();
// trigger emitted txn
env.close();
// calculate rewards
bool const hasFix = env.current()->rules().enabled(fix240819) &&
env.current()->rules().enabled(fix240911);
STAmount const netReward =
rewardUserAmount(*acctSle, preLedger, rateDrops);
BEAST_EXPECT(netReward == (hasFix ? XRP(3.479999) : XRP(6.663333)));
// validate account fields
STAmount const postUser = preUser + netReward;
BEAST_EXPECT(expectAccountFields(
env,
user,
preLedger,
preLedger + 1,
hasFix ? (preUser - feesXRP) : postUser,
preTime));
BEAST_EXPECT(
postUser == (hasFix ? XRP(2002.479999) : XRP(2005.663333)));
}
void
testRewardHookWithFeats(FeatureBitset features)
{
@@ -5582,12 +4994,6 @@ struct XahauGenesis_test : public beast::unit_test::suite
testInvalidElapsed0(features);
testInvalidElapsedNegative(features);
testCompoundInterest(features);
testDeposit(features);
testDepositWithdraw(features);
testDepositLate(features);
testDepositWithdrawLate(features);
testNoClaim(features);
testNoClaimLate(features);
}
void
@@ -5607,8 +5013,6 @@ struct XahauGenesis_test : public beast::unit_test::suite
auto const sa = supported_amendments();
testGovernHookWithFeats(sa);
testRewardHookWithFeats(sa);
testRewardHookWithFeats(sa - fix240819);
testRewardHookWithFeats(sa - fix240819 - fix240911);
}
};