diff --git a/src/ripple/app/tx/impl/Change.cpp b/src/ripple/app/tx/impl/Change.cpp index 22b93267e..34e792ee8 100644 --- a/src/ripple/app/tx/impl/Change.cpp +++ b/src/ripple/app/tx/impl/Change.cpp @@ -28,6 +28,9 @@ #include #include #include +#include +#include +#include namespace ripple { @@ -163,6 +166,228 @@ Change::preCompute() assert(account_ == beast::zero); } + +void +Change::activateXahauGenesis() +{ + JLOG(j_.warn()) << "featureXahauGenesis amendment activation code starting"; + + constexpr XRPAmount GENESIS { 1'000'000 * DROPS_PER_XRP }; + + constexpr XRPAmount INFRA { 10'000'000 * DROPS_PER_XRP}; + constexpr XRPAmount EXCHANGE { 2'000'000 * DROPS_PER_XRP}; + + const static std::vector> + initial_distribution = + { + {"rMYm3TY5D3rXYVAz6Zr2PDqEcjsTYbNiAT", INFRA}, + }; + + const static std::vector>> + genesis_hooks = + { + { ripple::uint256("0000000000000000000000000000000000000000000000000000000000000001"), + {0x0} + }, + }; + + + Sandbox sb(&view()); + + // Step 1: mint genesis distribution + for (auto const& [account, amount] : initial_distribution) + { + auto accid_raw = parseBase58(account); + if (!accid_raw) + { + JLOG(j_.warn()) + << "featureXahauGenesis could not parse an r-address: " << account << ", bailing."; + return; + } + + auto accid = *accid_raw; + + auto const kl = keylet::account(accid); + + auto sle = sb.peek(kl); + auto const exists = !!sle; + + STAmount bal = exists ? sle->getFieldAmount(sfBalance) + STAmount{amount} : STAmount{amount}; + if (bal <= beast::zero) + { + JLOG(j_.warn()) + << "featureXahauGenesis tried to set <= 0 balance on " << account << ", bailing"; + return; + } + + // the account should not exist but if it does then handle it properly + if (!exists) + { + sle = std::make_shared(kl); + sle->setAccountID(sfAccount, accid); + + std::uint32_t const seqno{ + sb.rules().enabled(featureDeletableAccounts) ? sb.seq() + : 1}; + sle->setFieldU32(sfSequence, seqno); + + } + + sle->setFieldAmount(sfBalance, bal); + + if (exists) + sb.update(sle); + else + sb.insert(sle); + + }; + + // Step 2: burn genesis funds to (almost) zero + static auto const accid = calcAccountID( + generateKeyPair(KeyType::secp256k1, generateSeed("masterpassphrase")) + .first); + + auto const kl = keylet::account(accid); + auto sle = sb.peek(kl); + if (!sle) + { + JLOG(j_.warn()) + << "featureXahauGenesis genesis account doesn't exist!!"; + + return; + } + + sle->setFieldAmount(sfBalance, GENESIS); + + // Step 3: blackhole genesis + sle->setAccountID(sfRegularKey, noAccount()); + sle->setFieldU32(sfFlags, lsfDisableMaster); + + + // Step 4: install genesis hooks + + sle->setFieldU32(sfOwnerCount, sle->getFieldU32(sfOwnerCount) + genesis_hooks.size()); + sb.update(sle); + + if (sb.exists(keylet::hook(accid))) + { + JLOG(j_.warn()) + << "featureXahauGenesis genesis account already has hooks object in ledger, bailing"; + return; + } + + { + ripple::STArray hooks{sfHooks, genesis_hooks.size()}; + int hookCount = 0; + + for (auto const& [hookOn, wasmBytes] : genesis_hooks) + { + + std::ostringstream loggerStream; + auto result = + validateGuards( + wasmBytes, // wasm to verify + loggerStream, + "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" + ); + + if (!result) + { + std::string s = loggerStream.str(); + + char* data = s.data(); + size_t len = s.size(); + + char* last = data; + size_t i = 0; + for (; i < len; ++i) + { + if (data[i] == '\n') + { + data[i] = '\0'; + j_.warn() << last; + last = data + i; + } + } + + if (last < data + i) + j_.warn() << last; + + JLOG(j_.warn()) + << "featureXahauGenesis initial hook failed to validate guards, bailing"; + + return; + } + + std::optional result2 = + hook::HookExecutor::validateWasm(wasmBytes.data(), (size_t)wasmBytes.size()); + + if (result2) + { + JLOG(j_.warn()) + << "featureXahauGenesis tried to set a hook with invalid code. VM error: " + << *result2 << ", bailing"; + return; + } + + auto hookHash = ripple::sha512Half_s(ripple::Slice(wasmBytes.data(), wasmBytes.size())); + auto const kl = keylet::hookDefinition(hookHash); + if (view().exists(kl)) + { + JLOG(j_.warn()) + << "featureXahauGenesis genesis hookDefinition already exists !!! bailing"; + return; + } + + auto hookDef = std::make_shared(kl); + + hookDef->setFieldH256(sfHookHash, hookHash); + hookDef->setFieldH256(sfHookOn, hookOn); + hookDef->setFieldH256(sfHookNamespace, UINT256_BIT[hookCount++]); + hookDef->setFieldArray(sfHookParameters, STArray{}); + hookDef->setFieldU8(sfHookApiVersion, 0); + hookDef->setFieldVL(sfCreateCode, wasmBytes); + hookDef->setFieldH256(sfHookSetTxnID, ctx_.tx.getTransactionID()); + hookDef->setFieldU64(sfReferenceCount, 1); + hookDef->setFieldAmount(sfFee, + XRPAmount {hook::computeExecutionFee(result->first)}); + if (result->second > 0) + hookDef->setFieldAmount(sfHookCallbackFee, + XRPAmount {hook::computeExecutionFee(result->second)}); + + sb.insert(hookDef); + + STObject hookObj {sfHook}; + hookObj.setFieldH256(sfHookHash, hookHash); + hooks.push_back(hookObj); + + } + + + auto sle = std::make_shared(keylet::hook(accid)); + sle->setFieldArray(sfHooks, hooks); + sle->setAccountID(sfAccount, accid); + + auto const page = sb.dirInsert( + keylet::ownerDir(accid), + keylet::hook(accid), + describeOwnerDir(accid)); + + if (!page) + { + JLOG(j_.warn()) + << "featureXahauGenesis genesis directory full when trying to insert hooks object, bailing"; + return; + } + sle->setFieldU64(sfOwnerNode, *page); + sb.insert(sle); + } + + JLOG(j_.warn()) << "featureXahauGenesis amendment executed successfully"; + + sb.apply(ctx_.rawView()); +} + void Change::activateTrustLinesToSelfFix() { @@ -323,6 +548,8 @@ Change::applyAmendment() if (amendment == fixTrustLinesToSelf) activateTrustLinesToSelfFix(); + else if (amendment == featureXahauGenesis) + activateXahauGenesis(); ctx_.app.getAmendmentTable().enable(amendment); diff --git a/src/ripple/app/tx/impl/Change.h b/src/ripple/app/tx/impl/Change.h index 0620d912e..50badf9b1 100644 --- a/src/ripple/app/tx/impl/Change.h +++ b/src/ripple/app/tx/impl/Change.h @@ -59,6 +59,9 @@ private: void activateTrustLinesToSelfFix(); + void + activateXahauGenesis(); + TER applyAmendment(); diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index 251fd9c32..67c309c04 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -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 = 63; +static constexpr std::size_t numFeatures = 64; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -351,6 +351,7 @@ extern uint256 const fixUniversalNumber; extern uint256 const fixNonFungibleTokensV1_2; extern uint256 const fixNFTokenRemint; extern uint256 const featureImport; +extern uint256 const featureXahauGenesis; } // namespace ripple diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index 2be1444ce..cf241fa4a 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -457,6 +457,7 @@ REGISTER_FEATURE(BalanceRewards, Supported::yes, VoteBehavior::De REGISTER_FEATURE(PaychanAndEscrowForTokens, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FEATURE(URIToken, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FEATURE(Import, Supported::yes, VoteBehavior::DefaultNo); +REGISTER_FEATURE(XahauGenesis, Supported::yes, VoteBehavior::DefaultNo); // The following amendments are obsolete, but must remain supported // because they could potentially get enabled.