Compare commits

..

6 Commits

Author SHA1 Message Date
tequ
60bb552780 clang-format 2026-04-27 14:37:05 +09:00
tequ
8cfd5c3eec Merge remote-tracking branch 'upstream/dev' into HookAdministrator 2026-04-27 14:29:23 +09:00
tequ
0825bddd87 Enhance new account creation at SetHook
- Add owner count and account index to AccountRoot
- Increment account count at FeeSetting
2026-01-10 11:42:06 +09:00
tequ
07008da032 Update sfcodes.h 2026-01-05 19:15:51 +09:00
tequ
90b009d63c Merge remote-tracking branch 'upstream/dev' into HookAdministrator 2026-01-05 19:14:07 +09:00
tequ
19e2036115 HookAdministrator Amendment 2026-01-05 19:13:57 +09:00
10 changed files with 146 additions and 7 deletions

View File

@@ -240,6 +240,7 @@
#define sfLockingChainDoor ((8U << 16U) + 22U)
#define sfIssuingChainDoor ((8U << 16U) + 23U)
#define sfSubject ((8U << 16U) + 24U)
#define sfHookAdministrator ((8U << 16U) + 98U)
#define sfInform ((8U << 16U) + 99U)
#define sfIndexes ((19U << 16U) + 1U)
#define sfHashes ((19U << 16U) + 2U)

View File

@@ -80,7 +80,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 = 113;
static constexpr std::size_t numFeatures = 114;
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated

View File

@@ -223,6 +223,11 @@ constexpr std::uint32_t const tfNFTokenCancelOfferMask = ~tfUniversal;
// NFTokenAcceptOffer flags:
constexpr std::uint32_t const tfNFTokenAcceptOfferMask = ~tfUniversal;
enum SetHookFlags : uint32_t {
tfNewAccount = 0x00000001,
};
constexpr std::uint32_t const tfSetHookMask = ~(tfUniversal | tfNewAccount);
// URIToken mask
constexpr std::uint32_t const tfURITokenMintMask = ~(tfUniversal | tfBurnable);
constexpr std::uint32_t const tfURITokenNonMintMask = ~tfUniversal;

View File

@@ -31,6 +31,7 @@
// If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h.
XRPL_FEATURE(HookAdministrator, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo)

View File

@@ -262,6 +262,7 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
{sfHookStateScale, soeOPTIONAL},
{sfCron, soeOPTIONAL},
{sfAMMID, soeOPTIONAL},
{sfHookAdministrator, soeOPTIONAL},
}))
/** A ledger object which contains a list of object identifiers.

View File

@@ -317,6 +317,7 @@ TYPED_SFIELD(sfAttestationRewardAccount, ACCOUNT, 21)
TYPED_SFIELD(sfLockingChainDoor, ACCOUNT, 22)
TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23)
TYPED_SFIELD(sfSubject, ACCOUNT, 24)
TYPED_SFIELD(sfHookAdministrator, ACCOUNT, 98)
TYPED_SFIELD(sfInform, ACCOUNT, 99)
// vector of 256-bit

View File

@@ -195,6 +195,7 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, ({
/** This transaction type installs a hook. */
TRANSACTION(ttHOOK_SET, 22, SetHook, ({
{sfHooks, soeREQUIRED},
{sfDestination, soeOPTIONAL},
}))
/** This transaction mints a new NFT. */

View File

@@ -330,6 +330,9 @@ DeleteAccount::preclaim(PreclaimContext const& ctx)
if (sleAccount->isFieldPresent(sfHookNamespaces) ||
sleAccount->isFieldPresent(sfHooks))
return tecHAS_OBLIGATIONS;
if (sleAccount->isFieldPresent(sfHookAdministrator))
return tecHAS_OBLIGATIONS;
}
// When fixNFTokenRemint is enabled, we don't allow an account to be

View File

@@ -1037,7 +1037,9 @@ ValidNewAccountRoot::finalize(
if ((tt == ttPAYMENT || tt == ttIMPORT || tt == ttGENESIS_MINT ||
tt == ttREMIT || tt == ttAMM_CREATE ||
tt == ttXCHAIN_ADD_CLAIM_ATTESTATION ||
tt == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) &&
tt == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION ||
(tt == ttHOOK_SET &&
view.rules().enabled(featureHookAdministrator))) &&
isTesSuccess(result))
{
std::uint32_t const startingSeq{

View File

@@ -675,6 +675,21 @@ SetHook::calculateBaseFee(ReadView const& view, STTx const& tx)
TER
SetHook::preclaim(ripple::PreclaimContext const& ctx)
{
if (ctx.tx.isFieldPresent(sfHookAdministrator))
{
auto const& administrator = ctx.tx.getAccountID(sfHookAdministrator);
auto const& sle = ctx.view.read(keylet::account(administrator));
if (!sle)
return tecNO_DST;
if (!sle->isFieldPresent(sfHookAdministrator))
return tecNO_PERMISSION;
if (sle->getAccountID(sfHookAdministrator) !=
ctx.tx.getAccountID(sfAccount))
return tecNO_PERMISSION;
}
auto const& hookSets = ctx.tx.getFieldArray(sfHooks);
for (auto const& hookSetObj : hookSets)
@@ -714,12 +729,46 @@ SetHook::preflight(PreflightContext const& ctx)
return ret;
if (ctx.rules.enabled(fixInvalidTxFlags) &&
ctx.tx.getFlags() & tfUniversalMask)
ctx.tx.getFlags() & tfSetHookMask)
{
JLOG(ctx.j.trace()) << "SetHook: Invalid flags set.";
return temINVALID_FLAG;
}
if (ctx.tx.isFlag(tfNewAccount) &&
!ctx.rules.enabled(featureHookAdministrator))
{
JLOG(ctx.j.trace()) << "SetHook: New account flag set but hook "
"administrator amendment is not enabled.";
return temDISABLED;
}
if (ctx.tx.isFieldPresent(sfDestination))
{
if (!ctx.rules.enabled(featureHookAdministrator))
{
JLOG(ctx.j.trace())
<< "HookSet: Hook administrator amendment not enabled.";
return temDISABLED;
}
if (ctx.tx.isFlag(tfNewAccount))
{
JLOG(ctx.j.trace())
<< "HookSet: Both new account flag and destination set. "
"New account flag and destination cannot be set at the same "
"time.";
return temMALFORMED;
}
if (ctx.tx.getAccountID(sfDestination) ==
ctx.tx.getAccountID(sfAccount))
{
JLOG(ctx.j.trace()) << "HookSet: Redundant hook administrator.";
return temREDUNDANT;
}
}
if (!ctx.tx.isFieldPresent(sfHooks))
{
JLOG(ctx.j.trace())
@@ -1255,6 +1304,23 @@ struct KeyletComparator
}
};
AccountID
randomAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey)
{
// This number must not be changed without an amendment
constexpr std::uint16_t maxAccountAttempts = 256;
for (std::uint16_t i = 0; i < maxAccountAttempts; ++i)
{
ripesha_hasher rsh;
auto const hash = sha512Half(i, view.info().parentHash, pseudoOwnerKey);
rsh(hash.data(), hash.size());
AccountID const ret{static_cast<ripesha_hasher::result_type>(rsh)};
if (!view.read(keylet::account(ret)))
return ret;
}
return beast::zero;
}
TER
SetHook::setHook()
{
@@ -1274,11 +1340,69 @@ SetHook::setHook()
.app = ctx_.app,
.rules = ctx_.view().rules()};
const int blobMax = hook::maxHookWasmSize();
auto const accountKeylet = keylet::account(account_);
auto const hookKeylet = keylet::hook(account_);
auto targetAccount = ctx.tx[~sfDestination].value_or(account_);
if (ctx_.tx.isFlag(tfNewAccount))
{
// create the new account
auto const newAccount = randomAccountAddress(ctx_.view(), uint256{});
if (newAccount == beast::zero)
return tecDUPLICATE;
auto accountSLE = view().peek(accountKeylet);
auto sleNewAccount = std::make_shared<SLE>(keylet::account(newAccount));
sleNewAccount->setAccountID(sfAccount, newAccount);
sleNewAccount->setFieldAmount(sfBalance, STAmount{});
sleNewAccount->setFieldU32(sfOwnerCount, 1); // ltHook
std::uint32_t const seqno{
ctx_.view().rules().enabled(featureXahauGenesis)
? ctx_.view().info().parentCloseTime.time_since_epoch().count()
: ctx_.view().rules().enabled(featureDeletableAccounts)
? ctx_.view().seq()
: 1};
sleNewAccount->setFieldU32(sfSequence, seqno);
sleNewAccount->setFieldU32(sfFlags, lsfDisableMaster);
sleNewAccount->setAccountID(sfHookAdministrator, account_);
auto sleFees = view().peek(keylet::fees());
if (sleFees && view().rules().enabled(featureXahauGenesis))
{
auto actIdx = sleFees->isFieldPresent(sfAccountCount)
? sleFees->getFieldU64(sfAccountCount)
: 0;
sleNewAccount->setFieldU64(sfAccountIndex, actIdx);
sleFees->setFieldU64(sfAccountCount, actIdx + 1);
view().update(sleFees);
}
// fund AccountReserve + ObjectReserve (ltHook)
auto const requiredDrops = ctx_.view().fees().accountReserve(1);
auto sourceSle = ctx_.view().peek(keylet::account(account_));
if (!sourceSle)
return tefINTERNAL;
auto const sourceCurrentReserve = ctx_.view().fees().accountReserve(
sourceSle->getFieldU32(sfOwnerCount));
auto const sourceBalance = sourceSle->getFieldAmount(sfBalance).xrp();
if (sourceBalance < sourceCurrentReserve + requiredDrops)
return tecUNFUNDED;
sourceSle->setFieldAmount(sfBalance, sourceBalance - requiredDrops);
ctx_.view().update(sourceSle);
sleNewAccount->setFieldAmount(sfBalance, requiredDrops);
ctx_.view().insert(sleNewAccount);
targetAccount = newAccount;
}
const int blobMax = hook::maxHookWasmSize();
auto const hookKeylet = keylet::hook(targetAccount);
auto accountSLE = view().peek(keylet::account(targetAccount));
ripple::STArray newHooks{sfHooks, 8};
auto newHookSLE = std::make_shared<SLE>(hookKeylet);