Support Cron Amendment (#32)

This commit is contained in:
tequ
2025-10-17 17:44:07 +09:00
committed by GitHub
parent b5f15ac075
commit e454c61994
10 changed files with 252 additions and 2 deletions

View File

@@ -29,6 +29,7 @@
"LEDGER_ENTRY_TYPES": { "LEDGER_ENTRY_TYPES": {
"Invalid": -1, "Invalid": -1,
"AccountRoot": 97, "AccountRoot": 97,
"Cron": 65,
"DirectoryNode": 100, "DirectoryNode": 100,
"RippleState": 114, "RippleState": 114,
"Ticket": 84, "Ticket": 84,
@@ -798,6 +799,26 @@
"type": "UInt32" "type": "UInt32"
} }
], ],
[
"RepeatCount",
{
"nth": 94,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "UInt32"
}
],
[
"DelaySeconds",
{
"nth": 95,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "UInt32"
}
],
[ [
"XahauActivationLgrSeq", "XahauActivationLgrSeq",
{ {
@@ -1488,6 +1509,16 @@
"type": "Hash256" "type": "Hash256"
} }
], ],
[
"Cron",
{
"nth": 95,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Hash256"
}
],
[ [
"Amount", "Amount",
{ {
@@ -2835,6 +2866,8 @@
"URITokenBuy": 47, "URITokenBuy": 47,
"URITokenCreateSellOffer": 48, "URITokenCreateSellOffer": 48,
"URITokenCancelSellOffer": 49, "URITokenCancelSellOffer": 49,
"Cron": 92,
"CronSet": 93,
"SetRemarks": 94, "SetRemarks": 94,
"Remit": 95, "Remit": 95,
"GenesisMint": 96, "GenesisMint": 96,

View File

@@ -74,7 +74,13 @@ class XrplDefinitionsBase {
.filter(([_key, value]) => value >= 0) .filter(([_key, value]) => value >= 0)
.map(([key, _value]) => key) .map(([key, _value]) => key)
const ignoreList = ['EnableAmendment', 'SetFee', 'UNLModify', 'EmitFailure'] const ignoreList = [
'EnableAmendment',
'SetFee',
'UNLModify',
'EmitFailure',
'Cron',
]
this.transactionMap = Object.assign( this.transactionMap = Object.assign(
{}, {},
...Object.entries(enums.TRANSACTION_TYPES) ...Object.entries(enums.TRANSACTION_TYPES)

View File

@@ -4,6 +4,11 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
## Unreleased Changes ## Unreleased Changes
### Added
* Support for Cron Amendment
## 4.0.1 (2025-10-03)
### Added ### Added
* parseTransactionFlags as a utility function in the xrpl package to streamline transactions flags-to-map conversion * parseTransactionFlags as a utility function in the xrpl package to streamline transactions flags-to-map conversion
* Support for XLS-77d Deep-Freeze amendment * Support for XLS-77d Deep-Freeze amendment

View File

@@ -84,6 +84,8 @@ export default interface AccountRoot extends BaseLedgerEntry, HasPreviousTxnID {
GovernanceMarks?: string GovernanceMarks?: string
AccountIndex?: number AccountIndex?: number
TouchCount?: number TouchCount?: number
/* The cron job that is associated with this account. */
Cron?: string
} }
/** /**

View File

@@ -0,0 +1,17 @@
import { BaseTransaction } from './common'
/**
* Cron job to be executed.
*
* @category Pseudo Transaction Models
*/
export interface Cron extends BaseTransaction {
TransactionType: 'Cron'
/**
* The ledger index where this pseudo-transaction appears.
* This distinguishes the pseudo-transaction from other occurrences of the same change.
*/
LedgerSequence: number
/** The owner of the cron job. */
Owner: string
}

View File

@@ -0,0 +1,68 @@
import { ValidationError } from '../../errors'
import { BaseTransaction, validateBaseTransaction } from './common'
/**
* Transaction Flags for an CronSet Transaction.
*
* @category Transaction Flags
*/
export enum CronSetFlags {
/**
* If set, indicates that the user would like to unset the cron job.
*/
tfCronUnset = 0x00000001,
}
/**
* CronSet is a transaction model that allows an account to set a cron job.
*
* @category Transaction Models
*/
export interface CronSet extends BaseTransaction {
TransactionType: 'CronSet'
Flags?: number | CronSetFlags
RepeatCount?: number
DelaySeconds?: number
}
const MAX_REPEAT_COUNT = 256
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- seconds in a year
const MIN_DELAY_SECONDS = 365 * 24 * 60 * 60
/**
* Verify the form and type of an CronSet at runtime.
*
* @param tx - An CronSet Transaction.
* @throws When the CronSet is Malformed.
*/
export function validateCronSet(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
if (tx.Flags === CronSetFlags.tfCronUnset) {
if (tx.RepeatCount !== undefined || tx.DelaySeconds !== undefined) {
throw new ValidationError(
'CronSet: RepeatCount and DelaySeconds must not be set when Flags is set to tfCronUnset',
)
}
}
if (tx.RepeatCount !== undefined && typeof tx.RepeatCount !== 'number') {
throw new ValidationError('CronSet: RepeatCount must be a number')
}
if (tx.RepeatCount !== undefined && tx.RepeatCount > MAX_REPEAT_COUNT) {
throw new ValidationError(
`CronSet: RepeatCount must be less than ${MAX_REPEAT_COUNT}`,
)
}
if (tx.DelaySeconds !== undefined && typeof tx.DelaySeconds !== 'number') {
throw new ValidationError('CronSet: DelaySeconds must be a number')
}
if (tx.DelaySeconds !== undefined && tx.DelaySeconds > MIN_DELAY_SECONDS) {
throw new ValidationError(
`CronSet: DelaySeconds must be less than ${MIN_DELAY_SECONDS}`,
)
}
}

View File

@@ -17,6 +17,8 @@ export { CheckCancel } from './checkCancel'
export { CheckCash } from './checkCash' export { CheckCash } from './checkCash'
export { CheckCreate } from './checkCreate' export { CheckCreate } from './checkCreate'
export { ClaimReward, ClaimRewardFlags } from './claimReward' export { ClaimReward, ClaimRewardFlags } from './claimReward'
export { Cron } from './cron'
export { CronSet, CronSetFlags } from './cronSet'
export { DepositPreauth } from './depositPreauth' export { DepositPreauth } from './depositPreauth'
export { EscrowCancel } from './escrowCancel' export { EscrowCancel } from './escrowCancel'
export { EscrowCreate } from './escrowCreate' export { EscrowCreate } from './escrowCreate'

View File

@@ -12,6 +12,8 @@ import { CheckCreate, validateCheckCreate } from './checkCreate'
import { ClaimReward, validateClaimReward } from './claimReward' import { ClaimReward, validateClaimReward } from './claimReward'
import { Clawback, validateClawback } from './clawback' import { Clawback, validateClawback } from './clawback'
import { BaseTransaction, isIssuedCurrency } from './common' import { BaseTransaction, isIssuedCurrency } from './common'
import { Cron } from './cron'
import { CronSet, validateCronSet } from './cronSet'
import { DepositPreauth, validateDepositPreauth } from './depositPreauth' import { DepositPreauth, validateDepositPreauth } from './depositPreauth'
import { EnableAmendment } from './enableAmendment' import { EnableAmendment } from './enableAmendment'
import { EscrowCancel, validateEscrowCancel } from './escrowCancel' import { EscrowCancel, validateEscrowCancel } from './escrowCancel'
@@ -68,6 +70,7 @@ export type SubmittableTransaction =
| CheckCreate | CheckCreate
| ClaimReward | ClaimReward
| Clawback | Clawback
| CronSet
| DepositPreauth | DepositPreauth
| EscrowCancel | EscrowCancel
| EscrowCreate | EscrowCreate
@@ -98,7 +101,7 @@ export type SubmittableTransaction =
* *
* @category Transaction Models * @category Transaction Models
*/ */
export type PseudoTransaction = EnableAmendment | SetFee | UNLModify export type PseudoTransaction = Cron | EnableAmendment | SetFee | UNLModify
/** /**
* All transactions that can live on the XAHL * All transactions that can live on the XAHL
@@ -210,6 +213,10 @@ export function validate(transaction: Record<string, unknown>): void {
validateClawback(tx) validateClawback(tx)
break break
case 'CronSet':
validateCronSet(tx)
break
case 'DepositPreauth': case 'DepositPreauth':
validateDepositPreauth(tx) validateDepositPreauth(tx)
break break

View File

@@ -8,6 +8,7 @@ import {
} from '../ledger/AccountRoot' } from '../ledger/AccountRoot'
import { AccountSetTfFlags } from '../transactions/accountSet' import { AccountSetTfFlags } from '../transactions/accountSet'
import { GlobalFlags } from '../transactions/common' import { GlobalFlags } from '../transactions/common'
import { CronSetFlags } from '../transactions/cronSet'
import { OfferCreateFlags } from '../transactions/offerCreate' import { OfferCreateFlags } from '../transactions/offerCreate'
import { PaymentFlags } from '../transactions/payment' import { PaymentFlags } from '../transactions/payment'
import { PaymentChannelClaimFlags } from '../transactions/paymentChannelClaim' import { PaymentChannelClaimFlags } from '../transactions/paymentChannelClaim'
@@ -52,6 +53,7 @@ const txToFlag = {
PaymentChannelClaim: PaymentChannelClaimFlags, PaymentChannelClaim: PaymentChannelClaimFlags,
Payment: PaymentFlags, Payment: PaymentFlags,
TrustSet: TrustSetFlags, TrustSet: TrustSetFlags,
CronSet: CronSetFlags,
} }
/** /**

View File

@@ -0,0 +1,108 @@
import { assert } from 'chai'
import { validate, ValidationError } from '../../src'
import {
CronSetFlags,
validateCronSet,
} from '../../src/models/transactions/cronSet'
/**
* CronSet Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('CronSet', function () {
it(`verifies valid CronSet`, function () {
let validCronSet = {
TransactionType: 'CronSet',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Fee: '100',
RepeatCount: 256,
DelaySeconds: 365 * 24 * 60 * 60,
} as any
assert.doesNotThrow(() => validateCronSet(validCronSet))
assert.doesNotThrow(() => validate(validCronSet))
validCronSet = {
TransactionType: 'CronSet',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Fee: '100',
Flags: CronSetFlags.tfCronUnset,
} as any
assert.doesNotThrow(() => validateCronSet(validCronSet))
assert.doesNotThrow(() => validate(validCronSet))
validCronSet = {
TransactionType: 'CronSet',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Fee: '100',
Flags: { tfCronUnset: true },
} as any
assert.doesNotThrow(() => validateCronSet(validCronSet))
assert.doesNotThrow(() => validate(validCronSet))
})
it(`throws w/ invalid Delete Operation`, function () {
const invalidDeleteOperation = {
TransactionType: 'CronSet',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Flags: CronSetFlags.tfCronUnset,
RepeatCount: 1,
DelaySeconds: 1,
Fee: '100',
} as any
assert.throws(
() => validateCronSet(invalidDeleteOperation),
ValidationError,
'CronSet: RepeatCount and DelaySeconds must not be set when Flags is set to tfCronUnset',
)
assert.throws(
() => validate(invalidDeleteOperation),
ValidationError,
'CronSet: RepeatCount and DelaySeconds must not be set when Flags is set to tfCronUnset',
)
})
it(`throws w/ invalid RepeatCount`, function () {
const invalidRepeatCount = {
TransactionType: 'CronSet',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
RepeatCount: 257,
Fee: '100',
} as any
assert.throws(
() => validateCronSet(invalidRepeatCount),
ValidationError,
'CronSet: RepeatCount must be less than 256',
)
assert.throws(
() => validate(invalidRepeatCount),
ValidationError,
'CronSet: RepeatCount must be less than 256',
)
})
it(`throws w/ invalid DelaySeconds`, function () {
const invalidDelaySeconds = {
TransactionType: 'CronSet',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
DelaySeconds: 365 * 24 * 60 * 60 + 1,
Fee: '100',
} as any
assert.throws(
() => validateCronSet(invalidDelaySeconds),
ValidationError,
`CronSet: DelaySeconds must be less than ${365 * 24 * 60 * 60}`,
)
assert.throws(
() => validate(invalidDelaySeconds),
ValidationError,
`CronSet: DelaySeconds must be less than ${365 * 24 * 60 * 60}`,
)
})
})