mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-28 06:25:49 +00:00
Compare commits
4 Commits
ledger_siz
...
snugdb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9320f0fddc | ||
|
|
b0a7783dbc | ||
|
|
224e78ac81 | ||
|
|
fbfd8c1e0a |
2
.github/workflows/clang-format.yml
vendored
2
.github/workflows/clang-format.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/levelization.yml
vendored
2
.github/workflows/levelization.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
336
src/ripple/nodestore/backend/SnugDBFactory.cpp
Normal file
336
src/ripple/nodestore/backend/SnugDBFactory.cpp
Normal 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
|
||||
741
src/ripple/nodestore/backend/snug.hpp
Normal file
741
src/ripple/nodestore/backend/snug.hpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user