Compare commits

..

24 Commits

Author SHA1 Message Date
Richard Holland
ccf3b38fad add startafter to corn 2025-10-21 09:44:03 +11:00
tequ
86a9ea999f clang-format 2025-10-17 15:06:05 +09:00
RichardAH
0755fb186a Update src/ripple/protocol/jss.h
Co-authored-by: tequ <git@tequ.dev>
2025-10-17 16:04:02 +10:00
RichardAH
526154b97a Update src/ripple/protocol/jss.h
Co-authored-by: tequ <git@tequ.dev>
2025-10-17 16:03:46 +10:00
tequ
052135a800 add CronSetFlags to ServerDefinition 2025-10-17 10:29:21 +09:00
tequ
793249d031 SetCron -> CronSet 2025-10-17 10:28:43 +09:00
tequ
c1e011a16a return tesSuccess when deleting cron without cron object 2025-10-16 17:07:01 +09:00
tequ
caa486f19c additional defensive check, malformed check, 2025-10-16 15:34:30 +09:00
tequ
106abfa9e0 remove alreadyExists case 2025-10-16 15:14:43 +09:00
tequ
88828bbf63 add TSH tests for ttCron, ttCronSet 2025-10-16 11:47:20 +09:00
tequ
cf3db6eb42 Merge branch 'dev' into cronjob 2025-10-15 22:48:08 +09:00
tequ
9b1f3ebdd7 use tfCronUnset to delete Cron 2025-10-15 21:00:19 +09:00
tequ
e64692fc8b add ttCron tests 2025-10-15 20:45:47 +09:00
tequ
ac1bf88596 add tests for CronSet 2025-10-15 15:14:59 +09:00
tequ
0d7dd0597d clang-format 2025-10-15 13:40:39 +09:00
Richard Holland
3dddb907c2 hash accids to help randomize cron order for the same timepoint 2025-10-14 17:56:38 +11:00
RichardAH
9d6ea9ac60 Merge branch 'dev' into cronjob 2025-10-14 15:56:39 +10:00
Richard Holland
e9a414cff2 add comment block to keylet::cron, fix accidental TxType casts 2025-10-14 16:43:11 +11:00
Richard Holland
fe1b424bea move static field checks to SetCron preflight, ensure a useful fee is computed. 2025-10-14 16:30:01 +11:00
Richard Holland
b1c366761f add sfOwnerNode to ltCRON 2025-10-14 15:43:31 +11:00
Richard Holland
82af6d9eee 365 days 2025-10-14 11:52:33 +11:00
RichardAH
20e6e62660 Merge branch 'dev' into cronjob 2025-10-14 10:51:45 +10:00
Richard Holland
1d6066127c whoops 2025-10-14 11:16:49 +11:00
Richard Holland
d3d5c757fe initial version of featureCron... compiling untested 2025-10-14 10:58:28 +11:00
17 changed files with 157 additions and 256 deletions

View File

@@ -176,10 +176,9 @@ existing maintainer without a vote.
## Current Maintainers ## Current Maintainers
* [Richard Holland](https://github.com/RichardAH) (XRPL Labs + INFTF) * [Richard Holland](https://github.com/RichardAH) (XRPL Labs + XRP Ledger Foundation)
* [Denis Angell](https://github.com/dangell7) (XRPL Labs + INFTF) * [Denis Angell](https://github.com/dangell7) (XRPL Labs + XRP Ledger Foundation)
* [Wietse Wind](https://github.com/WietseWind) (XRPL Labs + INFTF) * [Wietse Wind](https://github.com/WietseWind) (XRPL Labs + XRP Ledger Foundation)
* [tequ](https://github.com/tequdev) (Independent + INFTF)
[1]: https://docs.github.com/en/get-started/quickstart/contributing-to-projects [1]: https://docs.github.com/en/get-started/quickstart/contributing-to-projects

View File

@@ -62,9 +62,6 @@
#define sfEmitGeneration ((2U << 16U) + 46U) #define sfEmitGeneration ((2U << 16U) + 46U)
#define sfLockCount ((2U << 16U) + 49U) #define sfLockCount ((2U << 16U) + 49U)
#define sfFirstNFTokenSequence ((2U << 16U) + 50U) #define sfFirstNFTokenSequence ((2U << 16U) + 50U)
#define sfStartTime ((2U << 16U) + 93U)
#define sfRepeatCount ((2U << 16U) + 94U)
#define sfDelaySeconds ((2U << 16U) + 95U)
#define sfXahauActivationLgrSeq ((2U << 16U) + 96U) #define sfXahauActivationLgrSeq ((2U << 16U) + 96U)
#define sfImportSequence ((2U << 16U) + 97U) #define sfImportSequence ((2U << 16U) + 97U)
#define sfRewardTime ((2U << 16U) + 98U) #define sfRewardTime ((2U << 16U) + 98U)
@@ -132,7 +129,6 @@
#define sfGovernanceFlags ((5U << 16U) + 99U) #define sfGovernanceFlags ((5U << 16U) + 99U)
#define sfGovernanceMarks ((5U << 16U) + 98U) #define sfGovernanceMarks ((5U << 16U) + 98U)
#define sfEmittedTxnID ((5U << 16U) + 97U) #define sfEmittedTxnID ((5U << 16U) + 97U)
#define sfCron ((5U << 16U) + 95U)
#define sfAmount ((6U << 16U) + 1U) #define sfAmount ((6U << 16U) + 1U)
#define sfBalance ((6U << 16U) + 2U) #define sfBalance ((6U << 16U) + 2U)
#define sfLimitAmount ((6U << 16U) + 3U) #define sfLimitAmount ((6U << 16U) + 3U)

View File

@@ -31,8 +31,6 @@
#define ttURITOKEN_BUY 47 #define ttURITOKEN_BUY 47
#define ttURITOKEN_CREATE_SELL_OFFER 48 #define ttURITOKEN_CREATE_SELL_OFFER 48
#define ttURITOKEN_CANCEL_SELL_OFFER 49 #define ttURITOKEN_CANCEL_SELL_OFFER 49
#define ttCRON 92
#define ttCRON_SET 93
#define ttREMIT 95 #define ttREMIT 95
#define ttGENESIS_MINT 96 #define ttGENESIS_MINT 96
#define ttIMPORT 97 #define ttIMPORT 97

View File

@@ -94,6 +94,21 @@ Change::preflight(PreflightContext const& ctx)
"of sfImportVLKey, sfActiveValidator"; "of sfImportVLKey, sfActiveValidator";
return temMALFORMED; return temMALFORMED;
} }
// if we do specify import_vl_keys in config then we won't approve keys
// that aren't on our list
if (ctx.tx.isFieldPresent(sfImportVLKey) &&
!ctx.app.config().IMPORT_VL_KEYS.empty())
{
auto const& inner = const_cast<ripple::STTx&>(ctx.tx)
.getField(sfImportVLKey)
.downcast<STObject>();
auto const pk = inner.getFieldVL(sfPublicKey);
std::string const strPk = strHex(makeSlice(pk));
if (ctx.app.config().IMPORT_VL_KEYS.find(strPk) ==
ctx.app.config().IMPORT_VL_KEYS.end())
return telIMPORT_VL_KEY_NOT_RECOGNISED;
}
} }
return tesSUCCESS; return tesSUCCESS;
@@ -153,42 +168,9 @@ Change::preclaim(PreclaimContext const& ctx)
return tesSUCCESS; return tesSUCCESS;
case ttAMENDMENT: case ttAMENDMENT:
case ttUNL_MODIFY: case ttUNL_MODIFY:
case ttUNL_REPORT:
case ttEMIT_FAILURE: case ttEMIT_FAILURE:
return tesSUCCESS; return tesSUCCESS;
case ttUNL_REPORT: {
if (!ctx.tx.isFieldPresent(sfImportVLKey) ||
ctx.app.config().IMPORT_VL_KEYS.empty())
return tesSUCCESS;
// if we do specify import_vl_keys in config then we won't approve
// keys that aren't on our list and/or aren't in the ledger object
auto const& inner = const_cast<ripple::STTx&>(ctx.tx)
.getField(sfImportVLKey)
.downcast<STObject>();
auto const pkBlob = inner.getFieldVL(sfPublicKey);
std::string const strPk = strHex(makeSlice(pkBlob));
if (ctx.app.config().IMPORT_VL_KEYS.find(strPk) !=
ctx.app.config().IMPORT_VL_KEYS.end())
return tesSUCCESS;
auto const pkType = publicKeyType(makeSlice(pkBlob));
if (!pkType)
return tefINTERNAL;
PublicKey const pk(makeSlice(pkBlob));
// check on ledger
if (auto const unlRep = ctx.view.read(keylet::UNLReport());
unlRep && unlRep->isFieldPresent(sfImportVLKeys))
{
auto const& vlKeys = unlRep->getFieldArray(sfImportVLKeys);
for (auto const& k : vlKeys)
if (PublicKey(k[sfPublicKey]) == pk)
return tesSUCCESS;
}
return telIMPORT_VL_KEY_NOT_RECOGNISED;
}
default: default:
return temUNKNOWN; return temUNKNOWN;
} }

View File

@@ -121,11 +121,11 @@ Cron::doApply()
uint32_t delay = sleCron->getFieldU32(sfDelaySeconds); uint32_t delay = sleCron->getFieldU32(sfDelaySeconds);
uint32_t recur = sleCron->getFieldU32(sfRepeatCount); uint32_t recur = sleCron->getFieldU32(sfRepeatCount);
uint32_t lastStartTime = sleCron->getFieldU32(sfStartTime); uint32_t currentTime = view.parentCloseTime().time_since_epoch().count();
// do all this sanity checking before we modify the ledger... // do all this sanity checking before we modify the ledger...
uint32_t afterTime = lastStartTime + delay; uint32_t afterTime = currentTime + delay;
if (afterTime < lastStartTime) if (afterTime < currentTime)
return tefINTERNAL; return tefINTERNAL;
// in all circumstances the Cron object is deleted... // in all circumstances the Cron object is deleted...
@@ -163,7 +163,6 @@ Cron::doApply()
sleCron->setFieldU64(sfOwnerNode, *page); sleCron->setFieldU64(sfOwnerNode, *page);
sleCron->setFieldU32(sfDelaySeconds, delay); sleCron->setFieldU32(sfDelaySeconds, delay);
sleCron->setFieldU32(sfRepeatCount, recur - 1); sleCron->setFieldU32(sfRepeatCount, recur - 1);
sleCron->setFieldU32(sfStartTime, afterTime);
sleCron->setAccountID(sfOwner, id); sleCron->setAccountID(sfOwner, id);
sle->setFieldH256(sfCron, klCron.key); sle->setFieldH256(sfCron, klCron.key);

View File

@@ -51,74 +51,80 @@ SetCron::preflight(PreflightContext const& ctx)
return temINVALID_FLAG; return temINVALID_FLAG;
} }
// DelaySeconds (D), RepeatCount (R), StartTime (S) // StartAfter(s) DelaySeconds (D), RepeatCount (R)
// DRS - Set Cron with Delay and Repeat and StartTime
// DR- - Invalid(StartTime is required) // SDR - Set Cron with After, Delay and Repeat
// D-S - Invalid (both DelaySeconds and RepeatCount are required) // SD- - Invalid, if repeat count isn't included then only start or delay
// -RS - Invalid (both DelaySeconds and RepeatCount are required) // S-R - Invalid
// --S - Onetime cron with StartTime only // S-- - Set Cron with After for a once off execution
// -- - Clear any existing cron (succeeds even if there isn't one) / with
// -DR - Set Cron with Delay and Repeat
// -D- - Set Cron (once off) with Delay only (repat implicitly 0)
// --R - Invalid
// --- - Clear any existing cron (succeeds even if there isn't one) / with
// tfCronUnset flag set // tfCronUnset flag set
bool const hasStart = tx.isFieldPresent(sfStartAfter);
bool const hasDelay = tx.isFieldPresent(sfDelaySeconds); bool const hasDelay = tx.isFieldPresent(sfDelaySeconds);
bool const hasRepeat = tx.isFieldPresent(sfRepeatCount); bool const hasRepeat = tx.isFieldPresent(sfRepeatCount);
bool const hasStartTime = tx.isFieldPresent(sfStartTime);
// unset is a special case, handle first
if (tx.isFlag(tfCronUnset)) if (tx.isFlag(tfCronUnset))
{ {
// delete operation if (hasDelay || hasRepeat || hasStart)
if (hasDelay || hasRepeat || hasStartTime)
{ {
JLOG(j.debug()) << "SetCron: tfCronUnset flag cannot be used with " JLOG(j.debug()) << "SetCron: tfCronUnset flag cannot be used with "
"DelaySeconds, RepeatCount or StartTime."; "DelaySeconds or RepeatCount.";
return temMALFORMED; return temMALFORMED;
} }
return preflight2(ctx);
}
if (hasStart)
{
if (hasRepeat && hasDelay)
{
// valid, this is a fully specified cron
// fall through to validate other fields
}
else if (!hasRepeat && !hasDelay)
{
// valid this is a once off cron
// no other fields to validate, done
return preflight2(ctx);
} }
else else
{ {
// create operation // invalid, must specify both or neither repeat and delay count with
// startafter
if (!hasStartTime) JLOG(j.debug()) << "SetCron: StartAfter can only be used with "
{ "either both or neither of "
JLOG(j.debug()) "DelaySeconds and RepeatCount.";
<< "SetCron: StartTime is required. Use StartTime=0 for "
"immediate execution, or specify a future timestamp.";
return temMALFORMED; return temMALFORMED;
} }
}
if ((!hasDelay && hasRepeat) || (hasDelay && !hasRepeat)) if (!hasDelay)
{ {
JLOG(j.debug()) JLOG(j.debug()) << "SetCron: DelaySeconds or StartAfter must be "
<< "SetCron: DelaySeconds and RepeatCount must both be present " "specified to create a cron.";
"for recurring crons, or both absent for one-off crons.";
return temMALFORMED; return temMALFORMED;
} }
// check delay is not too high // check delay is not too high
if (hasDelay)
{
auto delay = tx.getFieldU32(sfDelaySeconds); auto delay = tx.getFieldU32(sfDelaySeconds);
if (delay > 31536000UL /* 365 days in seconds */) if (delay > 31536000UL /* 365 days in seconds */)
{ {
JLOG(j.debug()) JLOG(j.debug()) << "SetCron: DelaySeconds was too high. (max 365 "
<< "SetCron: DelaySeconds was too high. (max 365 "
"days in seconds)."; "days in seconds).";
return temMALFORMED; return temMALFORMED;
} }
}
// check repeat is not too high // check repeat is not too high
if (hasRepeat) if (hasRepeat)
{ {
auto recur = tx.getFieldU32(sfRepeatCount); auto recur = tx.getFieldU32(sfRepeatCount);
if (recur == 0)
{
JLOG(j.debug())
<< "SetCron: RepeatCount must be greater than 0."
"For one-time execution, omit DelaySeconds and "
"RepeatCount.";
return temMALFORMED;
}
if (recur > 256) if (recur > 256)
{ {
JLOG(j.debug()) JLOG(j.debug())
@@ -127,7 +133,6 @@ SetCron::preflight(PreflightContext const& ctx)
return temMALFORMED; return temMALFORMED;
} }
} }
}
return preflight2(ctx); return preflight2(ctx);
} }
@@ -135,28 +140,30 @@ SetCron::preflight(PreflightContext const& ctx)
TER TER
SetCron::preclaim(PreclaimContext const& ctx) SetCron::preclaim(PreclaimContext const& ctx)
{ {
if (ctx.tx.isFieldPresent(sfStartTime) && if (ctx.tx.isFieldPresent(sfStartAfter))
ctx.tx.getFieldU32(sfStartTime) != 0)
{ {
// StartTime 0 means the cron will execute immediately uint32_t currentTime =
auto const startTime = ctx.tx.getFieldU32(sfStartTime);
auto const parentCloseTime =
ctx.view.parentCloseTime().time_since_epoch().count(); ctx.view.parentCloseTime().time_since_epoch().count();
uint32_t afterTime = ctx.tx.getFieldU32(sfStartAfter);
if (startTime < parentCloseTime) if (afterTime <= currentTime)
{ {
JLOG(ctx.j.debug()) << "SetCron: StartTime must be in the future " // we'll pass this as though they meant execute asap, similar to a
"(or 0 for immediate execution)"; // delay of 0
return tecEXPIRED; return tesSUCCESS;
} }
if (startTime > ctx.view.parentCloseTime().time_since_epoch().count() + uint32_t waitSeconds = afterTime - currentTime;
365 * 24 * 60 * 60)
if (waitSeconds > afterTime)
return tefINTERNAL;
if (waitSeconds >> 31536000UL /* 365 days in seconds */)
{ {
JLOG(ctx.j.debug()) << "SetCron: StartTime is too far in the " JLOG(ctx.j.debug())
"future (max 365 days)."; << "SetCron: DelaySeconds was too high. (max 365 "
return tecEXPIRED; "days in seconds).";
return tecSTART_AFTER_TOO_HIGH;
} }
} }
return tesSUCCESS; return tesSUCCESS;
@@ -174,7 +181,7 @@ SetCron::doApply()
// ledger. // ledger.
uint32_t delay{0}; uint32_t delay{0};
uint32_t recur{0}; uint32_t recur{0};
uint32_t startTime{0}; uint32_t after{0};
if (!isDelete) if (!isDelete)
{ {
@@ -182,14 +189,20 @@ SetCron::doApply()
delay = tx.getFieldU32(sfDelaySeconds); delay = tx.getFieldU32(sfDelaySeconds);
if (tx.isFieldPresent(sfRepeatCount)) if (tx.isFieldPresent(sfRepeatCount))
recur = tx.getFieldU32(sfRepeatCount); recur = tx.getFieldU32(sfRepeatCount);
if (tx.isFieldPresent(sfStartTime))
{
startTime = tx.getFieldU32(sfStartTime);
if (startTime == 0)
startTime = view.parentCloseTime().time_since_epoch().count();
}
} }
uint32_t currentTime = view.parentCloseTime().time_since_epoch().count();
// do all this sanity checking before we modify the ledger...
// even for a delete operation this will fall through without incident
uint32_t afterTime = tx.isFieldPresent(sfStartAfter)
? tx.getFieldU32(sfStartAfter)
: currentTime + delay;
if (afterTime < currentTime)
return tefINTERNAL;
AccountID const& id = tx.getAccountID(sfAccount); AccountID const& id = tx.getAccountID(sfAccount);
auto sle = view.peek(keylet::account(id)); auto sle = view.peek(keylet::account(id));
if (!sle) if (!sle)
@@ -239,7 +252,7 @@ SetCron::doApply()
// execution to here means we're creating a new Cron object and adding it to // execution to here means we're creating a new Cron object and adding it to
// the user's owner dir // the user's owner dir
Keylet klCron = keylet::cron(startTime, id); Keylet klCron = keylet::cron(afterTime, id);
std::shared_ptr<SLE> sleCron = std::make_shared<SLE>(klCron); std::shared_ptr<SLE> sleCron = std::make_shared<SLE>(klCron);
@@ -263,7 +276,6 @@ SetCron::doApply()
adjustOwnerCount(view, sle, 1, j_); adjustOwnerCount(view, sle, 1, j_);
// set the fields // set the fields
sleCron->setFieldU32(sfStartTime, startTime);
sleCron->setFieldU32(sfDelaySeconds, delay); sleCron->setFieldU32(sfDelaySeconds, delay);
sleCron->setFieldU32(sfRepeatCount, recur); sleCron->setFieldU32(sfRepeatCount, recur);
sleCron->setAccountID(sfOwner, id); sleCron->setAccountID(sfOwner, id);
@@ -282,18 +294,18 @@ SetCron::calculateBaseFee(ReadView const& view, STTx const& tx)
{ {
auto const baseFee = Transactor::calculateBaseFee(view, tx); auto const baseFee = Transactor::calculateBaseFee(view, tx);
auto const hasRepeat = tx.isFieldPresent(sfRepeatCount);
if (tx.isFlag(tfCronUnset)) if (tx.isFlag(tfCronUnset))
// delete cron // delete cron
return baseFee; return baseFee;
auto const repeatCount =
tx.isFieldPresent(sfRepeatCount) ? tx.getFieldU32(sfRepeatCount) : 0;
// factor a cost based on the total number of txns expected // factor a cost based on the total number of txns expected
// for RepeatCount of 0 we have this txn (SetCron) and the // for RepeatCount of 0 we have this txn (SetCron) and the
// single Cron txn (2). For a RepeatCount of 1 we have this txn, // single Cron txn (2). For a RepeatCount of 1 we have this txn,
// the first time the cron executes, and the second time (3). // the first time the cron executes, and the second time (3).
uint32_t const additionalExpectedExecutions = 1 + repeatCount; uint32_t const additionalExpectedExecutions =
hasRepeat ? tx.getFieldU32(sfRepeatCount) + 1 : 1;
auto const additionalFee = baseFee * additionalExpectedExecutions; auto const additionalFee = baseFee * additionalExpectedExecutions;
if (baseFee + additionalFee < baseFee) if (baseFee + additionalFee < baseFee)

View File

@@ -412,7 +412,7 @@ extern SF_UINT32 const sfImportSequence;
extern SF_UINT32 const sfXahauActivationLgrSeq; extern SF_UINT32 const sfXahauActivationLgrSeq;
extern SF_UINT32 const sfDelaySeconds; extern SF_UINT32 const sfDelaySeconds;
extern SF_UINT32 const sfRepeatCount; extern SF_UINT32 const sfRepeatCount;
extern SF_UINT32 const sfStartTime; extern SF_UINT32 const sfStartAfter;
// 64-bit integers (common) // 64-bit integers (common)
extern SF_UINT64 const sfIndexNext; extern SF_UINT64 const sfIndexNext;

View File

@@ -343,6 +343,7 @@ enum TECcodes : TERUnderlyingType {
tecINSUF_RESERVE_SELLER = 187, tecINSUF_RESERVE_SELLER = 187,
tecIMMUTABLE = 188, tecIMMUTABLE = 188,
tecTOO_MANY_REMARKS = 189, tecTOO_MANY_REMARKS = 189,
tecSTART_AFTER_TOO_HIGH = 190,
tecLAST_POSSIBLE_ENTRY = 255, tecLAST_POSSIBLE_ENTRY = 255,
}; };

View File

@@ -371,7 +371,6 @@ LedgerFormats::LedgerFormats()
ltCRON, ltCRON,
{ {
{sfOwner, soeREQUIRED}, {sfOwner, soeREQUIRED},
{sfStartTime, soeREQUIRED},
{sfDelaySeconds, soeREQUIRED}, {sfDelaySeconds, soeREQUIRED},
{sfRepeatCount, soeREQUIRED}, {sfRepeatCount, soeREQUIRED},
{sfOwnerNode, soeREQUIRED}, {sfOwnerNode, soeREQUIRED},

View File

@@ -157,7 +157,7 @@ CONSTRUCT_TYPED_SFIELD(sfLockCount, "LockCount", UINT32,
CONSTRUCT_TYPED_SFIELD(sfFirstNFTokenSequence, "FirstNFTokenSequence", UINT32, 50); CONSTRUCT_TYPED_SFIELD(sfFirstNFTokenSequence, "FirstNFTokenSequence", UINT32, 50);
CONSTRUCT_TYPED_SFIELD(sfStartTime, "StartTime", UINT32, 93); CONSTRUCT_TYPED_SFIELD(sfStartAfter, "StartAfter", UINT32, 93);
CONSTRUCT_TYPED_SFIELD(sfRepeatCount, "RepeatCount", UINT32, 94); CONSTRUCT_TYPED_SFIELD(sfRepeatCount, "RepeatCount", UINT32, 94);
CONSTRUCT_TYPED_SFIELD(sfDelaySeconds, "DelaySeconds", UINT32, 95); CONSTRUCT_TYPED_SFIELD(sfDelaySeconds, "DelaySeconds", UINT32, 95);
CONSTRUCT_TYPED_SFIELD(sfXahauActivationLgrSeq, "XahauActivationLgrSeq",UINT32, 96); CONSTRUCT_TYPED_SFIELD(sfXahauActivationLgrSeq, "XahauActivationLgrSeq",UINT32, 96);

View File

@@ -94,6 +94,7 @@ transResults()
MAKE_ERROR(tecINSUF_RESERVE_SELLER, "The seller of an object has insufficient reserves, and thus cannot complete the sale."), MAKE_ERROR(tecINSUF_RESERVE_SELLER, "The seller of an object has insufficient reserves, and thus cannot complete the sale."),
MAKE_ERROR(tecIMMUTABLE, "The remark is marked immutable on the object, and therefore cannot be updated."), MAKE_ERROR(tecIMMUTABLE, "The remark is marked immutable on the object, and therefore cannot be updated."),
MAKE_ERROR(tecTOO_MANY_REMARKS, "The number of remarks on the object would exceed the limit of 32."), MAKE_ERROR(tecTOO_MANY_REMARKS, "The number of remarks on the object would exceed the limit of 32."),
MAKE_ERROR(tecSTART_AFTER_TOO_HIGH, "The proposed StartAfter time is greater than one year away."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."), MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."), MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
MAKE_ERROR(tefBAD_AUTH, "Transaction's public key is not authorized."), MAKE_ERROR(tefBAD_AUTH, "Transaction's public key is not authorized."),

View File

@@ -486,7 +486,7 @@ TxFormats::TxFormats()
{ {
{sfDelaySeconds, soeOPTIONAL}, {sfDelaySeconds, soeOPTIONAL},
{sfRepeatCount, soeOPTIONAL}, {sfRepeatCount, soeOPTIONAL},
{sfStartTime, soeOPTIONAL}, {sfStartAfter, soeOPTIONAL},
}, },
commonFields); commonFields);
} }

View File

@@ -48,13 +48,9 @@ struct Cron_test : public beast::unit_test::suite
auto const expectResult = auto const expectResult =
withCron ? ter(tesSUCCESS) : ter(temDISABLED); withCron ? ter(tesSUCCESS) : ter(temDISABLED);
auto tx = cron::set(alice);
// CLAIM // CLAIM
env(cron::set(alice), env(cron::set(alice), cron::delay(100), fee(XRP(1)), expectResult);
cron::startTime(0),
cron::repeat(100),
cron::delay(100),
fee(XRP(1)),
expectResult);
env.close(); env.close();
} }
} }
@@ -74,10 +70,9 @@ struct Cron_test : public beast::unit_test::suite
env.fund(XRP(1000), alice); env.fund(XRP(1000), alice);
env.close(); env.close();
// create // create with RepeatCount
auto expected = baseFee * 2 + baseFee * 256; auto expected = baseFee * 2 + baseFee * 256;
env(cron::set(alice), env(cron::set(alice),
cron::startTime(0),
cron::delay(356 * 24 * 60 * 60), cron::delay(356 * 24 * 60 * 60),
cron::repeat(256), cron::repeat(256),
fee(expected - 1), fee(expected - 1),
@@ -85,13 +80,26 @@ struct Cron_test : public beast::unit_test::suite
env.close(); env.close();
env(cron::set(alice), env(cron::set(alice),
cron::startTime(0),
cron::delay(356 * 24 * 60 * 60), cron::delay(356 * 24 * 60 * 60),
cron::repeat(256), cron::repeat(256),
fee(expected), fee(expected),
ter(tesSUCCESS)); ter(tesSUCCESS));
env.close(); env.close();
// create with no RepeatCount
expected = baseFee * 2;
env(cron::set(alice),
cron::delay(356 * 24 * 60 * 60),
fee(expected - 1),
ter(telINSUF_FEE_P));
env.close();
env(cron::set(alice),
cron::delay(356 * 24 * 60 * 60),
fee(expected),
ter(tesSUCCESS));
env.close();
// delete // delete
expected = baseFee; expected = baseFee;
env(cron::set(alice), env(cron::set(alice),
@@ -135,47 +143,30 @@ struct Cron_test : public beast::unit_test::suite
// temMALFORMED // temMALFORMED
{ {
// Invalid DelaySeconds and RepeatCount and StartTime are not // Invalid both DelaySeconds and RepeatCount are not specified
// specified
env(cron::set(alice), ter(temMALFORMED)); env(cron::set(alice), ter(temMALFORMED));
// Invalid DelaySeconds and RepeatCount combination with StartTime // Invalid DelaySeconds and RepeatCount combination
env(cron::set(alice), // (only RepeatCount specified)
cron::startTime(100), env(cron::set(alice), cron::repeat(256), ter(temMALFORMED));
cron::delay(356 * 24 * 60 * 60),
ter(temMALFORMED));
env(cron::set(alice),
cron::startTime(100),
cron::repeat(256),
ter(temMALFORMED));
// Invalid DelaySeconds // Invalid DelaySeconds
env(cron::set(alice), env(cron::set(alice),
cron::startTime(100),
cron::delay(365 * 24 * 60 * 60 + 1), cron::delay(365 * 24 * 60 * 60 + 1),
cron::repeat(256), cron::repeat(256),
ter(temMALFORMED)); ter(temMALFORMED));
// Invalid RepeatCount // Invalid RepeatCount
env(cron::set(alice), env(cron::set(alice),
cron::startTime(100),
cron::delay(365 * 24 * 60 * 60), cron::delay(365 * 24 * 60 * 60),
cron::repeat(257), cron::repeat(257),
ter(temMALFORMED)); ter(temMALFORMED));
// Invalid with tfCronUnset flag // Invalid tfCronUnset flag
env(cron::set(alice), env(cron::set(alice),
cron::delay(365 * 24 * 60 * 60), cron::delay(365 * 24 * 60 * 60),
txflags(tfCronUnset), txflags(tfCronUnset),
ter(temMALFORMED)); ter(temMALFORMED));
env(cron::set(alice),
cron::repeat(100),
txflags(tfCronUnset),
ter(temMALFORMED));
env(cron::set(alice),
cron::startTime(100),
txflags(tfCronUnset),
ter(temMALFORMED));
} }
} }
@@ -188,25 +179,9 @@ struct Cron_test : public beast::unit_test::suite
auto const alice = Account("alice"); auto const alice = Account("alice");
Env env{*this, features | featureCron}; Env env{*this, features | featureCron};
env.fund(XRP(1000), alice);
env.close();
// Past StartTime // there is no check in preclaim
env(cron::set(alice), BEAST_EXPECT(true);
cron::startTime(
env.timeKeeper().now().time_since_epoch().count() - 1),
fee(XRP(1)),
ter(tecEXPIRED));
env.close();
// Too far Future StartTime
env(cron::set(alice),
cron::startTime(
env.timeKeeper().now().time_since_epoch().count() +
365 * 24 * 60 * 60 + 1),
fee(XRP(1)),
ter(tecEXPIRED));
env.close();
} }
void void
@@ -224,10 +199,7 @@ struct Cron_test : public beast::unit_test::suite
auto const aliceOwnerCount = ownerCount(env, alice); auto const aliceOwnerCount = ownerCount(env, alice);
// create cron // create cron
auto parentCloseTime =
env.current()->parentCloseTime().time_since_epoch().count();
env(cron::set(alice), env(cron::set(alice),
cron::startTime(parentCloseTime + 356 * 24 * 60 * 60),
cron::delay(356 * 24 * 60 * 60), cron::delay(356 * 24 * 60 * 60),
cron::repeat(256), cron::repeat(256),
fee(XRP(1)), fee(XRP(1)),
@@ -247,15 +219,9 @@ struct Cron_test : public beast::unit_test::suite
BEAST_EXPECT( BEAST_EXPECT(
cronSle->getFieldU32(sfDelaySeconds) == 356 * 24 * 60 * 60); cronSle->getFieldU32(sfDelaySeconds) == 356 * 24 * 60 * 60);
BEAST_EXPECT(cronSle->getFieldU32(sfRepeatCount) == 256); BEAST_EXPECT(cronSle->getFieldU32(sfRepeatCount) == 256);
BEAST_EXPECT(
cronSle->getFieldU32(sfStartTime) ==
parentCloseTime + 356 * 24 * 60 * 60);
// update cron // update cron
parentCloseTime =
env.current()->parentCloseTime().time_since_epoch().count();
env(cron::set(alice), env(cron::set(alice),
cron::startTime(0),
cron::delay(100), cron::delay(100),
cron::repeat(10), cron::repeat(10),
fee(XRP(1)), fee(XRP(1)),
@@ -277,7 +243,6 @@ struct Cron_test : public beast::unit_test::suite
BEAST_EXPECT(cronSle2); BEAST_EXPECT(cronSle2);
BEAST_EXPECT(cronSle2->getFieldU32(sfDelaySeconds) == 100); BEAST_EXPECT(cronSle2->getFieldU32(sfDelaySeconds) == 100);
BEAST_EXPECT(cronSle2->getFieldU32(sfRepeatCount) == 10); BEAST_EXPECT(cronSle2->getFieldU32(sfRepeatCount) == 10);
BEAST_EXPECT(cronSle2->getFieldU32(sfStartTime) == parentCloseTime);
// delete cron // delete cron
env(cron::set(alice), env(cron::set(alice),
@@ -324,7 +289,6 @@ struct Cron_test : public beast::unit_test::suite
auto repeatCount = 10; auto repeatCount = 10;
env(cron::set(alice), env(cron::set(alice),
cron::startTime(baseTime + 100),
cron::delay(100), cron::delay(100),
cron::repeat(repeatCount), cron::repeat(repeatCount),
fee(XRP(1))); fee(XRP(1)));
@@ -347,7 +311,7 @@ struct Cron_test : public beast::unit_test::suite
} }
// close after 100 seconds passed // close after 100 seconds passed
env.close(10s); env.close();
auto txns = env.closed()->txs; auto txns = env.closed()->txs;
auto size = std::distance(txns.begin(), txns.end()); auto size = std::distance(txns.begin(), txns.end());
@@ -384,7 +348,8 @@ struct Cron_test : public beast::unit_test::suite
cronSle->getAccountID(sfOwner) == alice.id()); cronSle->getAccountID(sfOwner) == alice.id());
// set new base time // set new base time
baseTime = baseTime + 100; baseTime =
env.timeKeeper().now().time_since_epoch().count();
lastCronKeylet = cronKeylet; lastCronKeylet = cronKeylet;
} }
else else
@@ -415,7 +380,7 @@ struct Cron_test : public beast::unit_test::suite
for (auto const& account : accounts) for (auto const& account : accounts)
{ {
env(cron::set(account), cron::startTime(0), fee(XRP(1))); env(cron::set(account), cron::delay(0), fee(XRP(1)));
} }
env.close(); env.close();

View File

@@ -6297,7 +6297,6 @@ private:
// cron set // cron set
env(cron::set(account), env(cron::set(account),
cron::startTime(0),
cron::delay(100), cron::delay(100),
cron::repeat(1), cron::repeat(1),
fee(XRP(1)), fee(XRP(1)),
@@ -6333,11 +6332,8 @@ private:
env.fund(XRP(1000), account); env.fund(XRP(1000), account);
env.close(); env.close();
auto const baseTime =
env.current()->parentCloseTime().time_since_epoch().count();
// cron set // cron set
env(cron::set(account), env(cron::set(account),
cron::startTime(baseTime + 100),
cron::delay(100), cron::delay(100),
cron::repeat(1), cron::repeat(1),
fee(XRP(1)), fee(XRP(1)),

View File

@@ -357,32 +357,6 @@ class UNLReport_test : public beast::unit_test::suite
BEAST_EXPECT(isImportVL(env, ivlKeys[0]) == true); BEAST_EXPECT(isImportVL(env, ivlKeys[0]) == true);
BEAST_EXPECT(isImportVL(env, ivlKeys[1]) == false); BEAST_EXPECT(isImportVL(env, ivlKeys[1]) == false);
BEAST_EXPECT(isActiveValidator(env, vlKeys[0]) == true); BEAST_EXPECT(isActiveValidator(env, vlKeys[0]) == true);
// now test unrecognised keys that are already present in the ledger
// object (flap fix)
l = std::make_shared<Ledger>(
*l, env.app().timeKeeper().closeTime());
// insert a ttUNL_REPORT pseudo into the open ledger
env.app().openLedger().modify(
[&](OpenView& view, beast::Journal j) -> bool {
STTx tx = createUNLRTx(l->seq(), ivlKeys[1], vlKeys[0]);
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;
});
BEAST_EXPECT(hasUNLReport(env) == true);
// close the ledger
env.close();
BEAST_EXPECT(isImportVL(env, ivlKeys[0]) == true);
BEAST_EXPECT(isImportVL(env, ivlKeys[1]) == false);
BEAST_EXPECT(isActiveValidator(env, vlKeys[0]) == true);
} }
} }

View File

@@ -34,21 +34,6 @@ namespace cron {
Json::Value Json::Value
set(jtx::Account const& account); set(jtx::Account const& account);
/** Sets the optional StartTime on a JTx. */
class startTime
{
private:
uint32_t startTime_;
public:
explicit startTime(uint32_t startTime) : startTime_(startTime)
{
}
void
operator()(Env&, JTx& jtx) const;
};
/** Sets the optional DelaySeconds on a JTx. */ /** Sets the optional DelaySeconds on a JTx. */
class delay class delay
{ {

View File

@@ -37,12 +37,6 @@ set(jtx::Account const& account)
return jv; return jv;
} }
void
startTime::operator()(Env& env, JTx& jt) const
{
jt.jv[sfStartTime.jsonName] = startTime_;
}
void void
delay::operator()(Env& env, JTx& jt) const delay::operator()(Env& env, JTx& jt) const
{ {