mirror of
https://github.com/Xahau/xahaud.git
synced 2026-10-17 01:08:50 +00:00
Compare commits
2 Commits
credit-not
...
testnet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
096a2e455c | ||
|
|
f70eaba373 |
@@ -34,6 +34,10 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
// Xahau testnet (NetworkID 21338) only. Registered as unsupported here; the
|
||||
// server promotes it to Supported::yes / DefaultYes at startup if and only
|
||||
// if it is configured with network_id 21338 (see Application.cpp).
|
||||
XRPL_FIX (XahauTestnetGenesis, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(RNG, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PWALoader, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (20261005, Supported::yes, VoteBehavior::DefaultYes)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/misc/AmendmentTable.h>
|
||||
#include <xrpld/app/misc/HashRouter.h>
|
||||
#include <xrpld/app/tx/apply.h>
|
||||
#include <xrpld/app/tx/detail/XahauGenesis.h>
|
||||
@@ -5891,6 +5892,236 @@ struct XahauGenesis_test : public beast::unit_test::suite
|
||||
testNoClaimLate(features);
|
||||
}
|
||||
|
||||
// inject an enabling ttAMENDMENT pseudo for `amendment` and close
|
||||
void
|
||||
injectAmendment(jtx::Env& env, uint256 const& amendment)
|
||||
{
|
||||
uint32_t const seq =
|
||||
env.app().getLedgerMaster().getClosedLedger()->info().seq + 1;
|
||||
env.app().openLedger().modify(
|
||||
[&](OpenView& view, beast::Journal j) -> bool {
|
||||
STTx tx(ttAMENDMENT, [&](auto& obj) {
|
||||
obj.setAccountID(sfAccount, AccountID());
|
||||
obj.setFieldH256(sfAmendment, amendment);
|
||||
obj.setFieldU32(sfLedgerSequence, seq);
|
||||
});
|
||||
uint256 txID = tx.getTransactionID();
|
||||
auto s = std::make_shared<ripple::Serializer>();
|
||||
tx.add(*s);
|
||||
env.app().getHashRouter().setFlags(txID, SF_PRIVATE2);
|
||||
view.rawTxInsert(txID, std::move(s), nullptr);
|
||||
return true;
|
||||
});
|
||||
env.close();
|
||||
}
|
||||
|
||||
// check the genesis account is in the shape fixXahauTestnetGenesis
|
||||
// leaves it in: blackholed, governance + reward hooks, Evernode-only seat,
|
||||
// no hook state
|
||||
void
|
||||
checkTestnetGenesis(jtx::Env& env, AccountID const& evernode)
|
||||
{
|
||||
auto const acc = env.le(keylet::account(genesisAccID));
|
||||
BEAST_REQUIRE(!!acc);
|
||||
BEAST_EXPECT(acc->getAccountID(sfRegularKey) == noAccount());
|
||||
BEAST_EXPECT(acc->getFieldU32(sfFlags) == lsfDisableMaster);
|
||||
BEAST_EXPECT(!acc->isFieldPresent(sfHookStateCount));
|
||||
BEAST_EXPECT(!acc->isFieldPresent(sfHookNamespaces));
|
||||
// governance: 1 + IRR, IRD, IS\0, IMC; reward: 1
|
||||
BEAST_EXPECT(acc->getFieldU32(sfOwnerCount) == 6);
|
||||
BEAST_EXPECT(!env.le(keylet::signers(genesisAccID)));
|
||||
BEAST_EXPECT(!env.le(keylet::hookStateDir(genesisAccID, beast::zero)));
|
||||
|
||||
auto const hooksLE = env.le(keylet::hook(genesisAccID));
|
||||
BEAST_REQUIRE(!!hooksLE);
|
||||
auto const& hooks = hooksLE->getFieldArray(sfHooks);
|
||||
BEAST_REQUIRE(hooks.size() == 2);
|
||||
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == governHookHash);
|
||||
BEAST_EXPECT(hooks[1].getFieldH256(sfHookHash) == rewardHookHash);
|
||||
|
||||
std::vector<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>
|
||||
expected{XahauGenesis::GovernanceParameters};
|
||||
expected.emplace_back(
|
||||
std::vector<uint8_t>{'I', 'S', 0},
|
||||
std::vector<uint8_t>(evernode.data(), evernode.data() + 20));
|
||||
expected.emplace_back(
|
||||
std::vector<uint8_t>{'I', 'M', 'C'}, std::vector<uint8_t>{1});
|
||||
|
||||
auto const& params = hooks[0].getFieldArray(sfHookParameters);
|
||||
BEAST_REQUIRE(params.size() == expected.size());
|
||||
for (std::size_t i = 0; i < expected.size(); ++i)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
params[i].getFieldVL(sfHookParameterName) == expected[i].first);
|
||||
BEAST_EXPECT(
|
||||
params[i].getFieldVL(sfHookParameterValue) ==
|
||||
expected[i].second);
|
||||
}
|
||||
BEAST_EXPECT(hooks[1].getFieldArray(sfHookParameters).empty());
|
||||
|
||||
for (auto const& h : {governHookHash, rewardHookHash})
|
||||
{
|
||||
auto const def = env.le(keylet::hookDefinition(h));
|
||||
BEAST_EXPECT(def && def->getFieldU64(sfReferenceCount) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testXahauTestnetGenesisFix(FeatureBitset features)
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("fixXahauTestnetGenesis");
|
||||
|
||||
auto const evernodePK = parseBase58<PublicKey>(
|
||||
TokenType::NodePublic,
|
||||
"nHUetpkzTgf8c6A1DTGemRrhL4nMjsiJrtcsmmg75ufzV5EDMbWU");
|
||||
BEAST_REQUIRE(!!evernodePK);
|
||||
AccountID const evernode = calcAccountID(*evernodePK);
|
||||
|
||||
auto stateAt = [&](Env& env, std::vector<uint8_t> const& k) {
|
||||
uint8_t key[32]{};
|
||||
std::copy(k.begin(), k.end(), key + 32 - k.size());
|
||||
return env.le(keylet::hookState(
|
||||
genesisAccID, uint256::fromVoid(key), beast::zero));
|
||||
};
|
||||
std::vector<uint8_t> const mcKey{'M', 'C'};
|
||||
|
||||
// only supported on 21338
|
||||
{
|
||||
Env env{*this, makeNetworkConfig(21337), features};
|
||||
BEAST_EXPECT(!env.app().getAmendmentTable().isSupported(
|
||||
fixXahauTestnetGenesis));
|
||||
}
|
||||
{
|
||||
Env env{*this, makeNetworkConfig(21338), features};
|
||||
BEAST_EXPECT(env.app().getAmendmentTable().isSupported(
|
||||
fixXahauTestnetGenesis));
|
||||
}
|
||||
|
||||
// wrongly seated governance with stale hook state gets reset
|
||||
{
|
||||
Env env{
|
||||
*this,
|
||||
makeNetworkConfig(21338),
|
||||
features - featureXahauGenesis};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const invoker = Account("invoker");
|
||||
auto const user = Account("user");
|
||||
env.fund(XRP(10000), alice, bob, invoker, user);
|
||||
env.close();
|
||||
|
||||
XahauGenesis::TestNonGovernanceDistribution.clear();
|
||||
XahauGenesis::TestL1Membership.clear();
|
||||
XahauGenesis::TestL2Membership.clear();
|
||||
XahauGenesis::TestL1Membership.emplace_back(
|
||||
alice.human(), XRPAmount(10000000000));
|
||||
XahauGenesis::TestL1Membership.emplace_back(
|
||||
bob.human(), XRPAmount(10000000000));
|
||||
activate(__LINE__, env, true, false, true, {}, 2 + 3 + 2);
|
||||
XahauGenesis::TestL1Membership.clear();
|
||||
|
||||
Json::Value invoke;
|
||||
invoke[jss::TransactionType] = "Invoke";
|
||||
invoke[jss::Account] = invoker.human();
|
||||
invoke[jss::Destination] = env.master.human();
|
||||
env(invoke, fee(XRP(10)));
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto const mc = stateAt(env, mcKey);
|
||||
BEAST_REQUIRE(!!mc);
|
||||
BEAST_EXPECT(
|
||||
mc->getFieldVL(sfHookStateData) == std::vector<uint8_t>{2});
|
||||
}
|
||||
|
||||
injectAmendment(env, fixXahauTestnetGenesis);
|
||||
BEAST_EXPECT(
|
||||
env.current()->rules().enabled(fixXahauTestnetGenesis));
|
||||
|
||||
checkTestnetGenesis(env, evernode);
|
||||
BEAST_EXPECT(!stateAt(env, mcKey));
|
||||
|
||||
// governance re-initialises from the new parameters
|
||||
env(invoke, fee(XRP(10)));
|
||||
env.close();
|
||||
{
|
||||
auto const mc = stateAt(env, mcKey);
|
||||
BEAST_REQUIRE(!!mc);
|
||||
BEAST_EXPECT(
|
||||
mc->getFieldVL(sfHookStateData) == std::vector<uint8_t>{1});
|
||||
auto const seat = stateAt(
|
||||
env,
|
||||
std::vector<uint8_t>(
|
||||
evernode.data(), evernode.data() + 20));
|
||||
BEAST_EXPECT(
|
||||
seat &&
|
||||
seat->getFieldVL(sfHookStateData) ==
|
||||
std::vector<uint8_t>{0});
|
||||
BEAST_EXPECT(!stateAt(
|
||||
env,
|
||||
std::vector<uint8_t>(
|
||||
alice.id().data(), alice.id().data() + 20)));
|
||||
}
|
||||
|
||||
// alice no longer holds a seat
|
||||
env(vote(
|
||||
__LINE__,
|
||||
alice,
|
||||
env.master,
|
||||
'R',
|
||||
'R',
|
||||
{1, 2, 3, 4, 5, 6, 7, 8}),
|
||||
fee(XRP(1)),
|
||||
ter(tecHOOK_REJECTED));
|
||||
env.close();
|
||||
|
||||
// reward claim reaches the reward hook
|
||||
env(claimReward(user, env.master), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// enabling it again is refused and nothing is reset
|
||||
auto const ownerCount = env.le(keylet::account(genesisAccID))
|
||||
->getFieldU32(sfOwnerCount);
|
||||
injectAmendment(env, fixXahauTestnetGenesis);
|
||||
BEAST_EXPECT(stateAt(env, mcKey));
|
||||
BEAST_EXPECT(
|
||||
env.le(keylet::account(genesisAccID))
|
||||
->getFieldU32(sfOwnerCount) == ownerCount);
|
||||
}
|
||||
|
||||
// never-configured genesis with a regular key, signer list and a
|
||||
// foreign hook gets configured and blackholed
|
||||
{
|
||||
Env env{*this, makeNetworkConfig(21338), features};
|
||||
auto const alice = Account("alice");
|
||||
auto const user = Account("user");
|
||||
env.fund(XRP(10000), alice, user);
|
||||
env.memoize(env.master);
|
||||
env(genesis::setAcceptHook(env.master), fee(XRP(10)));
|
||||
env(signers(env.master, 1, {{alice, 1}}));
|
||||
env(regkey(env.master, alice));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(keylet::hookDefinition(acceptHookHash)));
|
||||
|
||||
injectAmendment(env, fixXahauTestnetGenesis);
|
||||
|
||||
checkTestnetGenesis(env, evernode);
|
||||
BEAST_EXPECT(!env.le(keylet::hookDefinition(acceptHookHash)));
|
||||
|
||||
env(claimReward(user, env.master), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// never touches state off 21338 even if enabled
|
||||
{
|
||||
Env env{*this, makeNetworkConfig(21337), features};
|
||||
injectAmendment(env, fixXahauTestnetGenesis);
|
||||
BEAST_EXPECT(!env.le(keylet::hook(genesisAccID)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testGovernHookWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -5900,6 +6131,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
|
||||
testWithRegularKey(features);
|
||||
testGovernanceL1(features);
|
||||
testGovernanceL2(features);
|
||||
testXahauTestnetGenesisFix(features);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -1242,10 +1242,10 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
|
||||
|
||||
// Configure the amendments the server supports
|
||||
{
|
||||
auto const supported = []() {
|
||||
auto const supported = [this]() {
|
||||
auto const& amendments = detail::supportedAmendments();
|
||||
std::vector<AmendmentTable::FeatureInfo> supported;
|
||||
supported.reserve(amendments.size());
|
||||
supported.reserve(amendments.size() + 1);
|
||||
for (auto const& [a, vote] : amendments)
|
||||
{
|
||||
auto const f = ripple::getRegisteredFeature(a);
|
||||
@@ -1254,6 +1254,16 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
|
||||
if (f)
|
||||
supported.emplace_back(a, *f, vote);
|
||||
}
|
||||
|
||||
// fixXahauTestnetGenesis is registered Supported::no. It is only
|
||||
// supported (and voted for) by servers configured for the Xahau
|
||||
// testnet. Everywhere else it stays unsupported.
|
||||
if (config_->NETWORK_ID == 21338)
|
||||
supported.emplace_back(
|
||||
"fixXahauTestnetGenesis",
|
||||
fixXahauTestnetGenesis,
|
||||
VoteBehavior::DefaultYes);
|
||||
|
||||
return supported;
|
||||
}();
|
||||
Section const& downVoted = config_->section(SECTION_VETO_AMENDMENTS);
|
||||
|
||||
@@ -988,6 +988,335 @@ Change::activateXahauGenesis()
|
||||
ctx_.rawView().rawDestroyXRP(destroyedXRP);
|
||||
}
|
||||
|
||||
// fixXahauTestnetGenesis: one-off repair of the genesis account on the Xahau
|
||||
// testnet (NetworkID 21338). The genesis hooks there were never set up the
|
||||
// way featureXahauGenesis sets them up on mainnet, so reward claims misbehave.
|
||||
//
|
||||
// This runs exactly once: only from applyAmendment(), at the moment the
|
||||
// amendment is enabled, which is after the tefALREADY check on the
|
||||
// Amendments object. It is never reached from any rules().enabled() path.
|
||||
//
|
||||
// It resets the genesis account to the mainnet shape:
|
||||
// - signer list removed, regular key blackholed, master disabled
|
||||
// - every existing hook removed and every hook state entry wiped, so the
|
||||
// governance hook re-runs its initial setup on the next Invoke
|
||||
// - governance + reward hooks installed from the current binaries, with
|
||||
// only Evernode's L1 seat filled (IMC = 1) and no L2 tables
|
||||
// Balances are not touched and nothing is minted or burned.
|
||||
void
|
||||
Change::activateXahauTestnetGenesisFix()
|
||||
{
|
||||
JLOG(j_.warn()) << "fixXahauTestnetGenesis amendment activation starting";
|
||||
|
||||
using namespace XahauGenesis;
|
||||
|
||||
// Belt and braces: the amendment is only Supported on 21338, but never
|
||||
// touch state on any other network even if it were somehow enabled.
|
||||
if (ctx_.app.config().NETWORK_ID != 21338)
|
||||
{
|
||||
JLOG(j_.warn()) << "fixXahauTestnetGenesis: NetworkID is not 21338, "
|
||||
"no changes made";
|
||||
return;
|
||||
}
|
||||
|
||||
auto [ng_entries, l1_entries, l2_entries, gov_params] =
|
||||
normalizeXahauGenesis(
|
||||
{}, TestnetL1Membership, {}, GovernanceParameters, j_);
|
||||
|
||||
if (l1_entries.size() != 1 || !l2_entries.empty())
|
||||
{
|
||||
JLOG(j_.warn()) << "fixXahauTestnetGenesis: expected exactly one L1 "
|
||||
"seat and no L2 tables, bailing";
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::tuple<
|
||||
uint256, // hook on
|
||||
std::vector<uint8_t>, // hook code
|
||||
std::vector<
|
||||
std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>>> const
|
||||
genesis_hooks = {
|
||||
{GovernanceHookOn, GovernanceHook, gov_params},
|
||||
{RewardHookOn, RewardHook, {}}};
|
||||
|
||||
Sandbox sb(&view());
|
||||
|
||||
static auto const accid = calcAccountID(
|
||||
generateKeyPair(KeyType::secp256k1, generateSeed("masterpassphrase"))
|
||||
.first);
|
||||
|
||||
if (!sb.exists(keylet::account(accid)))
|
||||
{
|
||||
JLOG(j_.warn()) << "fixXahauTestnetGenesis: genesis account doesn't "
|
||||
"exist, bailing";
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 1: remove any signer list. This adjusts OwnerCount itself, so it
|
||||
// happens before we start our own OwnerCount bookkeeping below.
|
||||
if (sb.exists(keylet::signers(accid)))
|
||||
{
|
||||
if (auto const ter =
|
||||
SetSignerList::removeFromLedger(ctx_.app, sb, accid, j_);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(j_.warn()) << "fixXahauTestnetGenesis: could not remove "
|
||||
"signer list: "
|
||||
<< transToken(ter) << ", bailing";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto sle = sb.peek(keylet::account(accid));
|
||||
std::int64_t ownerCount = sle->getFieldU32(sfOwnerCount);
|
||||
|
||||
// Step 2: blackhole
|
||||
sle->setAccountID(sfRegularKey, noAccount());
|
||||
sle->setFieldU32(sfFlags, lsfDisableMaster);
|
||||
|
||||
// Step 3: release every existing hook. Definitions are only decremented
|
||||
// here; zero-reference ones are erased after the new hooks are installed
|
||||
// so a definition we are about to reuse is never erased and re-inserted.
|
||||
std::set<uint256> releasedDefs;
|
||||
auto sleHooks = sb.peek(keylet::hook(accid));
|
||||
if (sleHooks && sleHooks->isFieldPresent(sfHooks))
|
||||
{
|
||||
for (auto const& h : sleHooks->getFieldArray(sfHooks))
|
||||
{
|
||||
ownerCount -= SetHook::computeHookReserve(h);
|
||||
|
||||
if (!h.isFieldPresent(sfHookHash))
|
||||
continue;
|
||||
|
||||
auto const defKL =
|
||||
keylet::hookDefinition(h.getFieldH256(sfHookHash));
|
||||
auto def = sb.peek(defKL);
|
||||
if (!def)
|
||||
continue;
|
||||
|
||||
std::uint64_t const rc = def->isFieldPresent(sfReferenceCount)
|
||||
? def->getFieldU64(sfReferenceCount)
|
||||
: 0;
|
||||
def->setFieldU64(sfReferenceCount, rc > 0 ? rc - 1 : 0);
|
||||
sb.update(def);
|
||||
releasedDefs.emplace(defKL.key);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: wipe all hook state, in every namespace
|
||||
{
|
||||
std::uint16_t const scale = sle->isFieldPresent(sfHookStateScale)
|
||||
? sle->getFieldU16(sfHookStateScale)
|
||||
: 1;
|
||||
std::uint32_t const stateCount = sle->isFieldPresent(sfHookStateCount)
|
||||
? sle->getFieldU32(sfHookStateCount)
|
||||
: 0;
|
||||
|
||||
std::set<uint256> namespaces{uint256{beast::zero}};
|
||||
if (sle->isFieldPresent(sfHookNamespaces))
|
||||
for (auto const& ns : sle->getFieldV256(sfHookNamespaces))
|
||||
namespaces.emplace(ns);
|
||||
|
||||
for (auto const& ns : namespaces)
|
||||
{
|
||||
auto const dirKL = keylet::hookStateDir(accid, ns);
|
||||
if (!sb.exists(dirKL))
|
||||
continue;
|
||||
|
||||
std::vector<uint256> entries;
|
||||
std::shared_ptr<SLE const> page;
|
||||
unsigned int index{0};
|
||||
uint256 entry;
|
||||
if (cdirFirst(sb, dirKL.key, page, index, entry))
|
||||
{
|
||||
do
|
||||
{
|
||||
entries.push_back(entry);
|
||||
} while (cdirNext(sb, dirKL.key, page, index, entry));
|
||||
}
|
||||
|
||||
for (auto const& key : entries)
|
||||
{
|
||||
auto item = sb.peek(Keylet{ltHOOK_STATE, key});
|
||||
if (!item)
|
||||
{
|
||||
JLOG(j_.warn()) << "fixXahauTestnetGenesis: hook state "
|
||||
"directory references a missing or "
|
||||
"non-state entry, bailing";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sb.dirRemove(dirKL, (*item)[sfOwnerNode], key, false))
|
||||
{
|
||||
JLOG(j_.warn()) << "fixXahauTestnetGenesis: could not "
|
||||
"remove hook state entry, bailing";
|
||||
return;
|
||||
}
|
||||
sb.erase(item);
|
||||
}
|
||||
|
||||
// dirRemove drops the root once empty; this is just in case
|
||||
if (auto dir = sb.peek(dirKL))
|
||||
sb.erase(dir);
|
||||
}
|
||||
|
||||
ownerCount -= static_cast<std::int64_t>(stateCount) * scale;
|
||||
|
||||
if (sle->isFieldPresent(sfHookStateCount))
|
||||
sle->makeFieldAbsent(sfHookStateCount);
|
||||
if (sle->isFieldPresent(sfHookStateScale))
|
||||
sle->makeFieldAbsent(sfHookStateScale);
|
||||
if (sle->isFieldPresent(sfHookNamespaces))
|
||||
sle->makeFieldAbsent(sfHookNamespaces);
|
||||
}
|
||||
|
||||
// Step 5: install governance + reward hooks
|
||||
STArray hooks{sfHooks, genesis_hooks.size()};
|
||||
std::int64_t hookReserve = 0;
|
||||
for (auto const& [hookOn, wasmBytes, params] : genesis_hooks)
|
||||
{
|
||||
auto const hookHash = ripple::sha512Half_s(
|
||||
ripple::Slice(wasmBytes.data(), wasmBytes.size()));
|
||||
auto const defKL = keylet::hookDefinition(hookHash);
|
||||
|
||||
STObject hookObj{sfHook};
|
||||
hookObj.setFieldH256(sfHookHash, hookHash);
|
||||
|
||||
if (auto def = sb.peek(defKL))
|
||||
{
|
||||
// Reuse the existing definition. It may have been created by an
|
||||
// ordinary SetHook with other defaults, so pin the fields the
|
||||
// genesis hooks depend on at the hook level where they differ.
|
||||
std::uint64_t const rc = def->isFieldPresent(sfReferenceCount)
|
||||
? def->getFieldU64(sfReferenceCount)
|
||||
: 0;
|
||||
def->setFieldU64(sfReferenceCount, rc + 1);
|
||||
sb.update(def);
|
||||
|
||||
if (!def->isFieldPresent(sfHookOn) ||
|
||||
def->getFieldH256(sfHookOn) != hookOn ||
|
||||
def->isFieldPresent(sfHookOnIncoming) ||
|
||||
def->isFieldPresent(sfHookOnOutgoing))
|
||||
hookObj.setFieldH256(sfHookOn, hookOn);
|
||||
|
||||
if (def->getFieldH256(sfHookNamespace) != beast::zero)
|
||||
hookObj.setFieldH256(sfHookNamespace, uint256{beast::zero});
|
||||
|
||||
if (def->isFieldPresent(sfHookCanEmit))
|
||||
hookObj.setFieldH256(sfHookCanEmit, UINT256_BIT[ttHOOK_SET]);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::ostringstream loggerStream;
|
||||
auto result = validateGuards(
|
||||
wasmBytes,
|
||||
loggerStream,
|
||||
"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
hook_api::getImportWhitelist(ctx_.view().rules()),
|
||||
hook_api::getGuardRulesVersion(ctx_.view().rules()));
|
||||
|
||||
if (!result)
|
||||
{
|
||||
JLOG(j_.warn()) << "fixXahauTestnetGenesis: genesis hook "
|
||||
"failed guard validation, bailing: "
|
||||
<< loggerStream.str();
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto err = hook::HookExecutor::validateWasm(
|
||||
wasmBytes.data(), (size_t)wasmBytes.size()))
|
||||
{
|
||||
JLOG(j_.warn()) << "fixXahauTestnetGenesis: genesis hook "
|
||||
"failed wasm validation, bailing: "
|
||||
<< *err;
|
||||
return;
|
||||
}
|
||||
|
||||
// def is null here (the peek above found nothing)
|
||||
def = std::make_shared<SLE>(defKL);
|
||||
def->setFieldH256(sfHookHash, hookHash);
|
||||
def->setFieldH256(sfHookOn, hookOn);
|
||||
def->setFieldH256(sfHookNamespace, uint256{beast::zero});
|
||||
def->setFieldU16(sfHookApiVersion, 0);
|
||||
def->setFieldVL(sfCreateCode, wasmBytes);
|
||||
def->setFieldH256(sfHookSetTxnID, ctx_.tx.getTransactionID());
|
||||
def->setFieldU64(sfReferenceCount, 1);
|
||||
def->setFieldAmount(
|
||||
sfFee, XRPAmount{hook::computeExecutionFee(result->first)});
|
||||
if (result->second > 0)
|
||||
def->setFieldAmount(
|
||||
sfHookCallbackFee,
|
||||
XRPAmount{hook::computeExecutionFee(result->second)});
|
||||
sb.insert(def);
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<STObject> vec;
|
||||
for (auto const& [k, v] : params)
|
||||
{
|
||||
STObject param(sfHookParameter);
|
||||
param.setFieldVL(sfHookParameterName, k);
|
||||
param.setFieldVL(sfHookParameterValue, v);
|
||||
vec.emplace_back(std::move(param));
|
||||
}
|
||||
hookObj.setFieldArray(
|
||||
sfHookParameters, STArray(vec, sfHookParameters));
|
||||
}
|
||||
|
||||
hookReserve += SetHook::computeHookReserve(hookObj);
|
||||
hooks.push_back(hookObj);
|
||||
}
|
||||
|
||||
if (sleHooks)
|
||||
{
|
||||
sleHooks->setFieldArray(sfHooks, hooks);
|
||||
sb.update(sleHooks);
|
||||
}
|
||||
else
|
||||
{
|
||||
sleHooks = std::make_shared<SLE>(keylet::hook(accid));
|
||||
sleHooks->setFieldArray(sfHooks, hooks);
|
||||
sleHooks->setAccountID(sfAccount, accid);
|
||||
|
||||
auto const page = sb.dirInsert(
|
||||
keylet::ownerDir(accid),
|
||||
keylet::hook(accid),
|
||||
describeOwnerDir(accid));
|
||||
if (!page)
|
||||
{
|
||||
JLOG(j_.warn()) << "fixXahauTestnetGenesis: genesis directory full "
|
||||
"when inserting hooks object, bailing";
|
||||
return;
|
||||
}
|
||||
sleHooks->setFieldU64(sfOwnerNode, *page);
|
||||
sb.insert(sleHooks);
|
||||
}
|
||||
|
||||
// Step 6: drop definitions nothing references any more
|
||||
for (auto const& key : releasedDefs)
|
||||
{
|
||||
auto def = sb.peek(Keylet{ltHOOK_DEFINITION, key});
|
||||
if (def && def->isFieldPresent(sfReferenceCount) &&
|
||||
def->getFieldU64(sfReferenceCount) == 0)
|
||||
sb.erase(def);
|
||||
}
|
||||
|
||||
// Step 7: owner count
|
||||
ownerCount += hookReserve;
|
||||
if (ownerCount < 0)
|
||||
{
|
||||
JLOG(j_.warn()) << "fixXahauTestnetGenesis: owner count went negative ("
|
||||
<< ownerCount << "), clamping to hook reserve";
|
||||
ownerCount = hookReserve;
|
||||
}
|
||||
sle->setFieldU32(sfOwnerCount, static_cast<std::uint32_t>(ownerCount));
|
||||
sb.update(sle);
|
||||
|
||||
sb.apply(ctx_.rawView());
|
||||
|
||||
JLOG(j_.warn()) << "fixXahauTestnetGenesis amendment executed successfully";
|
||||
}
|
||||
|
||||
void
|
||||
Change::activateTrustLinesToSelfFix()
|
||||
{
|
||||
@@ -1150,6 +1479,8 @@ Change::applyAmendment()
|
||||
activateTrustLinesToSelfFix();
|
||||
else if (amendment == featureXahauGenesis)
|
||||
activateXahauGenesis();
|
||||
else if (amendment == fixXahauTestnetGenesis)
|
||||
activateXahauTestnetGenesisFix();
|
||||
|
||||
ctx_.app.getAmendmentTable().enable(amendment);
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@ private:
|
||||
void
|
||||
activateXahauGenesis();
|
||||
|
||||
void
|
||||
activateXahauTestnetGenesisFix();
|
||||
|
||||
TER
|
||||
applyAmendment();
|
||||
|
||||
|
||||
@@ -115,6 +115,15 @@ inline std::vector<std::pair<std::string, XRPAmount>> const L1Membership{
|
||||
TableAmount}, // L2 Table Exchanges
|
||||
};
|
||||
|
||||
// Governance seating used by fixXahauTestnetGenesis on the Xahau testnet
|
||||
// (NetworkID 21338). Only Evernode's L1 seat is filled, every other seat is
|
||||
// left empty and there are no L2 tables. The amount is unused: the fix does
|
||||
// not mint or burn anything.
|
||||
inline std::vector<std::pair<std::string, XRPAmount>> const TestnetL1Membership{
|
||||
{"nHUetpkzTgf8c6A1DTGemRrhL4nMjsiJrtcsmmg75ufzV5EDMbWU",
|
||||
InfraAmount}, // Evernode
|
||||
};
|
||||
|
||||
// this data structure is used for testing the amendment only,
|
||||
// if the ttEnableAmendment has an optional flag (that cannot be added in
|
||||
// production) then whatever is in this array is used
|
||||
|
||||
Reference in New Issue
Block a user