Compare commits

..

6 Commits

Author SHA1 Message Date
Richard Holland
738a4c1e87 move attestation blocking logic to applySteps, add error code 2023-12-03 22:29:12 +00:00
Richard Holland
df9ed403ad bug fixes 2023-12-03 21:39:55 +00:00
Richard Holland
deb4b34b9b add last ledger seq enforcement, accid attestations 2023-12-03 16:19:18 +00:00
Richard Holland
d0baeab32f attestation consensus stuff. compiling not tested 2023-12-03 15:38:05 +00:00
Richard Holland
72016c3e0e bug fixes 2023-12-03 11:17:29 +00:00
Richard Holland
54211fb63b start of featureAttestations 2023-12-03 10:51:08 +00:00
28 changed files with 493 additions and 435 deletions

View File

@@ -455,13 +455,13 @@ target_sources (rippled PRIVATE
src/ripple/app/tx/impl/GenesisMint.cpp
src/ripple/app/tx/impl/Import.cpp
src/ripple/app/tx/impl/Invoke.cpp
src/ripple/app/tx/impl/Attest.cpp
src/ripple/app/tx/impl/SetSignerList.cpp
src/ripple/app/tx/impl/SetTrust.cpp
src/ripple/app/tx/impl/SignerEntries.cpp
src/ripple/app/tx/impl/Taker.cpp
src/ripple/app/tx/impl/Transactor.cpp
src/ripple/app/tx/impl/URIToken.cpp
src/ripple/app/tx/impl/Cadastre.cpp
src/ripple/app/tx/impl/apply.cpp
src/ripple/app/tx/impl/applySteps.cpp
src/ripple/app/hook/impl/applyHook.cpp

View File

@@ -891,19 +891,13 @@ hook::removeHookNamespaceEntry(ripple::SLE& sleAccount, ripple::uint256 ns)
bool
hook::canHook(ripple::TxType txType, ripple::uint256 hookOn)
{
uint16_t tt = safe_cast<uint16_t>(txType);
// only the low order byte is used to determine HookOn
tt &= 0xFFU;
// invert ttHOOK_SET bit
hookOn ^= UINT256_BIT[ttHOOK_SET];
// invert entire field
hookOn = ~hookOn;
return (hookOn & UINT256_BIT[tt]) != beast::zero;
return (hookOn & UINT256_BIT[txType]) != beast::zero;
}
// Update HookState ledger objects for the hook... only called after accept()

View File

@@ -0,0 +1,182 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/tx/impl/Attest.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
namespace ripple {
TxConsequences
Attest::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
Attest::preflight(PreflightContext const& ctx)
{
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
auto const flags = ctx.tx.getFlags();
if (flags & tfAttestMask)
return temINVALID_FLAG;
bool const hasTxnID = ctx.tx.isFieldPresent(sfAttestedTxnID);
bool const hasAccID = ctx.tx.isFieldPresent(sfAttestedAccID);
if ((hasTxnID && hasAccID) || (!hasTxnID && !hasAccID))
{
JLOG(ctx.j.warn())
<< "Attest: must specify exactly one of: AttestedTxnID, AttestedAccID";
return temMALFORMED;
}
return preflight2(ctx);
}
TER
Attest::preclaim(PreclaimContext const& ctx)
{
if (!ctx.view.rules().enabled(featureAttestations))
return temDISABLED;
auto const id = ctx.tx[sfAccount];
auto const sle = ctx.view.read(keylet::account(id));
if (!sle)
return terNO_ACCOUNT;
Keylet kl =
ctx.tx.isFieldPresent(sfAttestedTxnID)
? keylet::attestationTxn(id, ctx.tx.getFieldH256(sfAttestedTxnID))
: keylet::attestationAcc(id, ctx.tx.getAccountID(sfAttestedAccID));
uint32_t flags = ctx.tx.getFlags();
bool const isDelete = (flags & tfDeleteAttestation) == tfDeleteAttestation;
bool const exists = ctx.view.exists(kl);
if (exists && !isDelete)
return tecDUPLICATE;
else if (!exists && isDelete)
return tecNO_ENTRY;
return tesSUCCESS;
}
TER
Attest::doApply()
{
auto j = ctx_.app.journal("View");
auto const sle = view().peek(keylet::account(account_));
if (!sle)
return tefINTERNAL;
Keylet kl =
ctx_.tx.isFieldPresent(sfAttestedTxnID)
? keylet::attestationTxn(account_, ctx_.tx.getFieldH256(sfAttestedTxnID))
: keylet::attestationAcc(account_, ctx_.tx.getAccountID(sfAttestedAccID));
uint32_t flags = ctx_.tx.getFlags();
bool const isDelete = (flags & tfDeleteAttestation) == tfDeleteAttestation;
bool const exists = view().exists(kl);
// check for sufficient reserves
if (!isDelete)
{
STAmount const reserve{
view().fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)};
STAmount const afterFee =
mPriorBalance - ctx_.tx.getFieldAmount(sfFee).xrp();
if (afterFee > mPriorBalance || afterFee < reserve)
return tecINSUFFICIENT_RESERVE;
}
// delete mode
if (exists && isDelete)
{
auto sleA = view().peek(kl);
AccountID owner = sleA->getAccountID(sfOwner);
auto const page = (*sleA)[sfOwnerNode];
if (!view().dirRemove(
keylet::ownerDir(owner), page, kl.key, true))
{
JLOG(j.fatal())
<< "Could not remove Attestation from owner directory";
return tefBAD_LEDGER;
}
view().erase(sleA);
adjustOwnerCount(view(), sle, -1, j);
return tesSUCCESS;
}
// create mode
else if (!exists && !isDelete)
{
auto sleA = std::make_shared<SLE>(kl);
sleA->setFieldU16(sfLedgerEntryType, ltATTESTATION);
sleA->setAccountID(sfOwner, account_);
if (ctx_.tx.isFieldPresent(sfAttestedTxnID))
sleA->setFieldH256(sfAttestedTxnID, ctx_.tx.getFieldH256(sfAttestedTxnID));
else
sleA->setAccountID(sfAttestedAccID, ctx_.tx.getAccountID(sfAttestedAccID));
auto const page = view().dirInsert(
keylet::ownerDir(account_), kl, describeOwnerDir(account_));
JLOG(j_.trace())
<< "Adding Attestation to owner directory " << to_string(kl.key)
<< ": " << (page ? "success" : "failure");
if (!page)
return tecDIR_FULL;
sleA->setFieldU64(sfOwnerNode, *page);
view().insert(sleA);
adjustOwnerCount(view(), sle, 1, j);
return tesSUCCESS;
}
// everything else is invalid
else
return tecINTERNAL;
}
XRPAmount
Attest::calculateBaseFee(ReadView const& view, STTx const& tx)
{
return Transactor::calculateBaseFee(view, tx);
}
} // namespace ripple

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 Ripple Labs Inc.
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,22 +17,22 @@
*/
//==============================================================================
#ifndef RIPPLE_TX_CADASTRE_H_INCLUDED
#define RIPPLE_TX_CADASTRE_H_INCLUDED
#ifndef RIPPLE_TX_ATTEST_H_INCLUDED
#define RIPPLE_TX_ATTEST_H_INCLUDED
#include <ripple/app/ledger/Ledger.h>
#include <ripple/app/tx/impl/Transactor.h>
#include <ripple/basics/Log.h>
#include <ripple/core/Config.h>
#include <ripple/protocol/Indexes.h>
namespace ripple {
class Cadastre : public Transactor
class Attest : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
explicit Cadastre(ApplyContext& ctx) : Transactor(ctx)
explicit Attest(ApplyContext& ctx) : Transactor(ctx)
{
}

View File

@@ -1,207 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/tx/impl/Cadastre.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/app/tx/impl/URIToken.h>
namespace ripple {
TxConsequences
Cadastre::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, TxConsequences::normal};
}
/*
add(jss::Cadastre,
ltCADASTRE,
{
{sfOwner, soeREQUIRED},
{sfOwnerNode, soeREQUIRED},
{sfAssociation, soeOPTIONAL},
{sfAssociationNode, soeOPTIONAL},
{sfLocationX, soeREQUIRED},
{sfLocationY, soeREQUIRED},
{sfUniverse, soeREQUIRED},
{sfDisplayURI, soeOPTIONAL},
{sfBroadcastURI, soeOPTIONAL},
{sfCadastreCount, soeOPTIONAL}, // for 0x8000,0x8000 tile only
},
commonFields);
ttCADASTRE_MINT = 0x005D, // HookOn = 93
ttCADASTRE_BURN = 0x015D,
ttCADASTRE_CREATE_SELL_OFFER = 0x025D,
ttCADASTRE_CANCEL_SELL_OFFER = 0x035D,
ttCADASTRE_BUY = 0x045D,
ttCADASTRE_SET = 0x055D,
*/
NotTEC
Cadastre::preflight(PreflightContext const& ctx)
{
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
auto& tx = ctx.tx;
// the validation for amount is the same regardless of which txn is appears
// on
if (ctx.tx.isFieldPresent(sfAmount))
{
auto amt = ctx.tx.getFieldAmount(sfAmount);
if (!isLegalNet(amt) || amt.signum() < 0)
{
JLOG(ctx.j.warn()) << "Malformed transaction. Negative or "
"invalid amount/currency specified.";
return temBAD_AMOUNT;
}
if (isBadCurrency(amt.getCurrency()))
{
JLOG(ctx.j.warn()) << "Malformed transaction. Bad currency.";
return temBAD_CURRENCY;
}
if (amt == beast::zero && !ctx.tx.isFieldPresent(sfDestination))
{
if (tt == ttCADASTRE_BUY)
{
// buy operation does not specify a destination, and can have a
// zero amount pass
}
else
{
JLOG(ctx.j.warn()) << "Malformed transaction. "
<< "If no sell-to destination is specified "
"then a non-zero price must be set.";
return temMALFORMED;
}
}
}
if (ctx.tx.isFieldPresent(sfDestination) &&
!ctx.tx.isFieldPresent(sfAmount))
return temMALFORMED;
auto checkURI = [&ctx](SField const& f) -> TER
{
if (ctx.tx.isFieldPresent(f))
{
auto const& vl = ctx.tx.getFieldVL(f);
if (vl.size() < 1 || vl.size() > 256)
{
JLOG(ctx.j.warn())
<< "Cadastre: Malformed transaction, "
<< f.getName()
<< " was invalid size (too big/small)";
return temMALFORMED;
}
if (!URIToken::validateUTF8(ctx.tx.getFieldVL(sfDisplayURI)))
{
JLOG(ctx.j.warn())
<< "Cadastre: Malformed transaction, "
<< f.getName()
<< " was not valid utf-8";
return temMALFORMED;
}
}
return tesSUCCESS;
};
checkURI(sfDisplayURI);
checkURI(sfBroadcastURI);
return preflight2(ctx);
}
TER
Cadastre::preclaim(PreclaimContext const& ctx)
{
if (!ctx.view.rules().enabled(featureHooks))
return temDISABLED;
auto const id = ctx.tx[sfAccount];
auto const sle = ctx.view.read(keylet::account(id));
if (!sle)
return terNO_ACCOUNT;
if (ctx.tx.isFieldPresent(sfDestination))
{
if (!ctx.view.exists(keylet::account(ctx.tx[sfDestination])))
return tecNO_TARGET;
}
auto const tt = tx.getTxnType();
switch(tt)
{
case ttCADASTRE_MINT:
case ttCADASTRE_SET:
{
break;
}
case ttCADASTRE_BURN:
{
break;
}
case ttCADASTRE_CREATE_SELL_OFFER:
{
break;
}
case ttCADASTRE_CANCEL_SELL_OFFER:
{
break;
}
case ttCADASTRE_BUY:
{
break;
}
}
return tesSUCCESS;
}
TER
Cadastre::doApply()
{
// everything happens in the hooks!
return tesSUCCESS;
}
XRPAmount
Cadastre::calculateBaseFee(ReadView const& view, STTx const& tx)
{
XRPAmount extraFee{0};
return Transactor::calculateBaseFee(view, tx) + extraFee;
}
} // namespace ripple

View File

@@ -492,7 +492,7 @@ LedgerEntryTypesMatch::visitEntry(
case ltURI_TOKEN:
case ltIMPORT_VLSEQ:
case ltUNL_REPORT:
case ltCADASTRE:
case ltATTESTATION:
break;
default:
invalidTypeAdded_ = true;

View File

@@ -85,6 +85,54 @@ preflight0(PreflightContext const& ctx)
return temINVALID;
}
// don't allow attestations unless enabled
if (ctx.tx.isFieldPresent(sfAttesters))
{
if (!ctx.rules.enabled(featureAttestations))
return temDISABLED;
// make sure they can't spam millions of attesters
auto const& attesters = ctx.tx.getFieldArray(sfAttesters);
if (attesters.empty() || attesters.size() > 32)
{
JLOG(ctx.j.warn())
<< "applyTransaction: attesters array too big (max 32) or empty.";
return temMALFORMED;
}
// enforce that it must specify a last ledger seq
if (!ctx.tx.isFieldPresent(sfLastLedgerSequence))
{
JLOG(ctx.j.warn())
<< "applyTransaction: txns with attesters must specify a last ledger sequence <= cur + 256";
return temMALFORMED;
}
// sanity check entries
std::set<AccountID> used;
for (auto const& attester: attesters)
{
if (attester.getFName() != sfAttesterEntry)
{
JLOG(ctx.j.warn())
<< "applyTransaction: attesters array contained non AttesterEntry object.";
return temMALFORMED;
}
AccountID const& id = attester.getAccountID(sfAccount);
if (used.find(id) != used.end())
{
JLOG(ctx.j.warn())
<< "applyTransaction: attesters array contained duplicate attester ID.";
return temMALFORMED;
}
used.emplace(id);
}
}
return tesSUCCESS;
}
@@ -1928,6 +1976,62 @@ Transactor::operator()()
}
}
if (applied && ctx_.tx.isFieldPresent(sfAttesters) && view().rules().enabled(featureAttestations))
{
// delete used attestation objects
auto const& attesters = ctx_.tx.getFieldArray(sfAttesters);
auto const txid = ctx_.tx.getTransactionID();
auto const& j = ctx_.app.journal("View");
for (auto const& attester : attesters)
{
Keylet kl = keylet::attestationTxn(attester.getAccountID(sfAccount), txid);
if (!view().exists(kl))
{
JLOG(j.warn())
<< "Transactor: Warning!!! Attestation does not exist at end of attested txn "
<< txid;
continue;
}
// remove from dir
auto sleA = view().peek(kl);
if (!sleA->isFieldPresent(sfAttestedTxnID))
{
JLOG(j.warn())
<< "Transactor: Warning!!! Attestation is of the wrong type at end of attested txn "
<< txid;
continue;
}
AccountID owner = sleA->getAccountID(sfOwner);
auto sle = view().peek(keylet::account(owner));
if (!sle)
{
JLOG(j.warn())
<< "Transactor: Warning!!! Attester account does not exist at the end of attested txn "
<< txid;
continue;
}
auto const page = (*sleA)[sfOwnerNode];
if (!view().dirRemove(
keylet::ownerDir(owner), page, kl.key, true))
{
JLOG(j.warn())
<< "Could not remove Attestation from owner directory";
continue;
}
view().erase(sleA);
adjustOwnerCount(view(), sle, -1, j);
}
}
// Post-application (Weak TSH/AAW) Hooks are executed here.
// These TSH do not have the ability to rollback.
// The callback, if any, is also executed here.

View File

@@ -160,6 +160,7 @@ public:
{
// Most transactors do nothing
// after checkSeq/Fee/Sign.
return tesSUCCESS;
}
/////////////////////////////////////////////////////

View File

@@ -103,7 +103,57 @@ URIToken::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
if (!validateUTF8(uri))
if (!([](std::vector<uint8_t> const& u) -> bool {
// this code is from
// https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c
uint8_t const* s = (uint8_t const*)u.data();
uint8_t const* end = s + u.size();
while (s < end)
{
if (*s < 0x80)
/* 0xxxxxxx */
s++;
else if ((s[0] & 0xe0) == 0xc0)
{
/* 110XXXXx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 ||
(s[0] & 0xfe) == 0xc0) /* overlong? */
return false;
else
s += 2;
}
else if ((s[0] & 0xf0) == 0xe0)
{
/* 1110XXXX 10Xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
(s[0] == 0xe0 &&
(s[1] & 0xe0) == 0x80) || /* overlong? */
(s[0] == 0xed &&
(s[1] & 0xe0) == 0xa0) || /* surrogate? */
(s[0] == 0xef && s[1] == 0xbf &&
(s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */
return false;
else
s += 3;
}
else if ((s[0] & 0xf8) == 0xf0)
{
/* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
(s[3] & 0xc0) != 0x80 ||
(s[0] == 0xf0 &&
(s[1] & 0xf0) == 0x80) || /* overlong? */
(s[0] == 0xf4 && s[1] > 0x8f) ||
s[0] > 0xf4) /* > U+10FFFF? */
return false;
else
s += 4;
}
else
return false;
}
return true;
})(uri))
{
JLOG(ctx.j.warn()) << "Malformed transaction. URI must be a "
"valid utf-8 string.";

View File

@@ -30,56 +30,6 @@ namespace ripple {
class URIToken : public Transactor
{
public:
bool inline static validateUTF8(std::vector<uint8_t> const& u)
{
// this code is from
// https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c
uint8_t const* s = (uint8_t const*)u.data();
uint8_t const* end = s + u.size();
while (s < end)
{
if (*s < 0x80)
/* 0xxxxxxx */
s++;
else if ((s[0] & 0xe0) == 0xc0)
{
/* 110XXXXx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 ||
(s[0] & 0xfe) == 0xc0) /* overlong? */
return false;
else
s += 2;
}
else if ((s[0] & 0xf0) == 0xe0)
{
/* 1110XXXX 10Xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
(s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || /* overlong? */
(s[0] == 0xed && (s[1] & 0xe0) == 0xa0) || /* surrogate? */
(s[0] == 0xef && s[1] == 0xbf &&
(s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */
return false;
else
s += 3;
}
else if ((s[0] & 0xf8) == 0xf0)
{
/* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
(s[3] & 0xc0) != 0x80 ||
(s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || /* overlong? */
(s[0] == 0xf4 && s[1] > 0x8f) ||
s[0] > 0xf4) /* > U+10FFFF? */
return false;
else
s += 4;
}
else
return false;
}
return true;
}
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit URIToken(ApplyContext& ctx) : Transactor(ctx)

View File

@@ -32,6 +32,7 @@
#include <ripple/app/tx/impl/GenesisMint.h>
#include <ripple/app/tx/impl/Import.h>
#include <ripple/app/tx/impl/Invoke.h>
#include <ripple/app/tx/impl/Attest.h>
#include <ripple/app/tx/impl/NFTokenAcceptOffer.h>
#include <ripple/app/tx/impl/NFTokenBurn.h>
#include <ripple/app/tx/impl/NFTokenCancelOffer.h>
@@ -45,7 +46,6 @@
#include <ripple/app/tx/impl/SetSignerList.h>
#include <ripple/app/tx/impl/SetTrust.h>
#include <ripple/app/tx/impl/URIToken.h>
#include <ripple/app/tx/impl/Cadastre.h>
namespace ripple {
@@ -165,19 +165,14 @@ invoke_preflight(PreflightContext const& ctx)
return invoke_preflight_helper<Import>(ctx);
case ttINVOKE:
return invoke_preflight_helper<Invoke>(ctx);
case ttATTEST:
return invoke_preflight_helper<Attest>(ctx);
case ttURITOKEN_MINT:
case ttURITOKEN_BURN:
case ttURITOKEN_BUY:
case ttURITOKEN_CREATE_SELL_OFFER:
case ttURITOKEN_CANCEL_SELL_OFFER:
return invoke_preflight_helper<URIToken>(ctx);
case ttCADASTRE_MINT:
case ttCADASTRE_BURN:
case ttCADASTRE_CREATE_SELL_OFFER:
case ttCADASTRE_CANCEL_SELL_OFFER:
case ttCADASTRE_BUY:
case ttCADASTRE_SET:
return invoke_preflight_helper<Cadastre>(ctx);
default:
assert(false);
return {temUNKNOWN, TxConsequences{temUNKNOWN}};
@@ -217,6 +212,27 @@ invoke_preclaim(PreclaimContext const& ctx)
if (result != tesSUCCESS)
return result;
if (ctx.tx.isFieldPresent(sfAttesters) && ctx.view.rules().enabled(featureAttestations))
{
// check last ledger sequence
if (!ctx.tx.isFieldPresent(sfLastLedgerSequence))
return tecINTERNAL;
if (ctx.tx.getFieldU32(sfLastLedgerSequence) > ctx.view.seq() + 256)
return tecLAST_LEDGER_SEQ_TOO_HIGH;
// check if the required attestations are present on ledger
auto const& attesters = ctx.tx.getFieldArray(sfAttesters);
auto const txid = ctx.tx.getTransactionID();
// each required attestation must exist on the ledger to allow the txn through
// otherwise it gets marked retry
for (auto const& attester : attesters)
if (!ctx.view.exists(keylet::attestationTxn(attester.getAccountID(sfAccount), txid)))
return terAWAITING_ATTESTATION;
}
}
return T::preclaim(ctx);
@@ -291,19 +307,14 @@ invoke_preclaim(PreclaimContext const& ctx)
return invoke_preclaim<Import>(ctx);
case ttINVOKE:
return invoke_preclaim<Invoke>(ctx);
case ttATTEST:
return invoke_preclaim<Attest>(ctx);
case ttURITOKEN_MINT:
case ttURITOKEN_BURN:
case ttURITOKEN_BUY:
case ttURITOKEN_CREATE_SELL_OFFER:
case ttURITOKEN_CANCEL_SELL_OFFER:
return invoke_preclaim<URIToken>(ctx);
case ttCADASTRE_MINT:
case ttCADASTRE_BURN:
case ttCADASTRE_CREATE_SELL_OFFER:
case ttCADASTRE_CANCEL_SELL_OFFER:
case ttCADASTRE_BUY:
case ttCADASTRE_SET:
return invoke_preclaim<Cadastre>(ctx);
default:
assert(false);
return temUNKNOWN;
@@ -379,19 +390,14 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
return Import::calculateBaseFee(view, tx);
case ttINVOKE:
return Invoke::calculateBaseFee(view, tx);
case ttATTEST:
return Attest::calculateBaseFee(view, tx);
case ttURITOKEN_MINT:
case ttURITOKEN_BURN:
case ttURITOKEN_BUY:
case ttURITOKEN_CREATE_SELL_OFFER:
case ttURITOKEN_CANCEL_SELL_OFFER:
return URIToken::calculateBaseFee(view, tx);
case ttCADASTRE_MINT:
case ttCADASTRE_BURN:
case ttCADASTRE_CREATE_SELL_OFFER:
case ttCADASTRE_CANCEL_SELL_OFFER:
case ttCADASTRE_BUY:
case ttCADASTRE_SET:
return Cadastre::calculateBaseFee(view, tx);
default:
assert(false);
return XRPAmount{0};
@@ -566,6 +572,10 @@ invoke_apply(ApplyContext& ctx)
Invoke p(ctx);
return p();
}
case ttATTEST: {
Attest p(ctx);
return p();
}
case ttURITOKEN_MINT:
case ttURITOKEN_BURN:
case ttURITOKEN_BUY:
@@ -574,15 +584,6 @@ invoke_apply(ApplyContext& ctx)
URIToken p(ctx);
return p();
}
case ttCADASTRE_MINT:
case ttCADASTRE_BURN:
case ttCADASTRE_CREATE_SELL_OFFER:
case ttCADASTRE_CANCEL_SELL_OFFER:
case ttCADASTRE_BUY:
case ttCADASTRE_SET: {
Cadastre p(ctx);
return p();
}
default:
assert(false);
return {temUNKNOWN, false};

View File

@@ -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 = 66;
static constexpr std::size_t numFeatures = 67;
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
@@ -354,7 +354,7 @@ extern uint256 const featureImport;
extern uint256 const featureXahauGenesis;
extern uint256 const featureHooksUpdate1;
extern uint256 const fixURITokenV1;
extern uint256 const featureAttestations;
} // namespace ripple
#endif

View File

@@ -295,11 +295,13 @@ Keylet
import_vlseq(PublicKey const& key) noexcept;
Keylet
uritoken(AccountID const& issuer, Blob const& uri);
uritoken(AccountID const& issuer, Blob const& uri) noexcept;
Keylet
cadastre(uint256 const& universe, uint16_t locx, uint16_t locy);
attestationTxn(AccountID const& issuer, uint256 const& txnid) noexcept;
Keylet
attestationAcc(AccountID const& issuer, AccountID const& issuee) noexcept;
} // namespace keylet

View File

@@ -52,6 +52,12 @@ namespace ripple {
// clang-format off
enum LedgerEntryType : std::uint16_t
{
/** A ledger object which describes an attested txn
\sa kelet::attestation
*/
ltATTESTATION = 0x0041,
/** A ledger object which describes an account.
\sa keylet::account
@@ -179,15 +185,6 @@ enum LedgerEntryType : std::uint16_t
*/
ltUNL_REPORT = 0x0052,
/** A ledger object that contains cadastral information about a unit of virtual land
* in a given universe. The block at 0x8000, 0x8000 is the center of the universe
* and has special rights and privileges, namely it is the god block whose hooks are
* strongly executed whenever dealings with land in that universe occur.
*
* \sa keylet::cadastre
*/
ltCADASTRE = 0x004B,
//---------------------------------------------------------------------------
/** A special type, matching any ledger entry type.

View File

@@ -354,8 +354,6 @@ extern SF_UINT16 const sfHookStateChangeCount;
extern SF_UINT16 const sfHookEmitCount;
extern SF_UINT16 const sfHookExecutionIndex;
extern SF_UINT16 const sfHookApiVersion;
extern SF_UINT16 const sfLocationX;
extern SF_UINT16 const sfLocationY;
// 32-bit integers (common)
extern SF_UINT32 const sfNetworkID;
@@ -435,8 +433,6 @@ extern SF_UINT64 const sfReferenceCount;
extern SF_UINT64 const sfRewardAccumulator;
extern SF_UINT64 const sfAccountCount;
extern SF_UINT64 const sfAccountIndex;
extern SF_UINT64 const sfAssociationNode;
extern SF_UINT64 const sfCadastreCount;
// 128-bit
extern SF_UINT128 const sfEmailHash;
@@ -487,7 +483,7 @@ extern SF_UINT256 const sfURITokenID;
extern SF_UINT256 const sfGovernanceFlags;
extern SF_UINT256 const sfGovernanceMarks;
extern SF_UINT256 const sfEmittedTxnID;
extern SF_UINT256 const sfUniverse;
extern SF_UINT256 const sfAttestedTxnID;
// currency amount (common)
extern SF_AMOUNT const sfAmount;
@@ -542,8 +538,6 @@ extern SF_VL const sfHookReturnString;
extern SF_VL const sfHookParameterName;
extern SF_VL const sfHookParameterValue;
extern SF_VL const sfBlob;
extern SF_VL const sfBroadcastURI;
extern SF_VL const sfDisplayURI;
// account
extern SF_ACCOUNT const sfAccount;
@@ -559,7 +553,8 @@ extern SF_ACCOUNT const sfEmitCallback;
// account (uncommon)
extern SF_ACCOUNT const sfHookAccount;
extern SF_ACCOUNT const sfNFTokenMinter;
extern SF_ACCOUNT const sfAssociation;
extern SF_ACCOUNT const sfInform;
extern SF_ACCOUNT const sfAttestedAccID;
// path set
extern SField const sfPaths;
@@ -570,6 +565,7 @@ extern SF_VECTOR256 const sfHashes;
extern SF_VECTOR256 const sfAmendments;
extern SF_VECTOR256 const sfNFTokenOffers;
extern SF_VECTOR256 const sfHookNamespaces;
extern SF_VECTOR256 const sfURITokenIDs;
// inner object
// OBJECT/1 is reserved for end of object
@@ -598,6 +594,9 @@ extern SField const sfHookGrant;
extern SField const sfActiveValidator;
extern SField const sfImportVLKey;
extern SField const sfHookEmission;
extern SField const sfMintURIToken;
extern SField const sfAmountEntry;
extern SField const sfAttesterEntry;
// array of objects (common)
// ARRAY/1 is reserved for end of array
@@ -625,6 +624,8 @@ extern SField const sfGenesisMints;
extern SField const sfActiveValidators;
extern SField const sfImportVLKeys;
extern SField const sfHookEmissions;
extern SField const sfAmounts;
extern SField const sfAttesters;
//------------------------------------------------------------------------------

View File

@@ -223,8 +223,9 @@ enum TERcodes : TERUnderlyingType {
terQUEUED, // Transaction is being held in TxQ until fee drops
terPRE_TICKET, // Ticket is not yet in ledger but might be on its way
terNO_AMM, // RESERVED - AMM
terNO_HOOK // Transaction requires a non-existent hook definition
terNO_HOOK, // Transaction requires a non-existent hook definition
// (referenced by sfHookHash)
terAWAITING_ATTESTATION,
};
//------------------------------------------------------------------------------
@@ -337,6 +338,7 @@ enum TECcodes : TERUnderlyingType {
tecXCHAIN_PAYMENT_FAILED = 184, // RESERVED - XCHAIN
tecXCHAIN_SELF_COMMIT = 185, // RESERVED - XCHAIN
tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR = 186, // RESERVED - XCHAIN
tecLAST_LEDGER_SEQ_TOO_HIGH = 187,
tecLAST_POSSIBLE_ENTRY = 255,
};

View File

@@ -132,6 +132,10 @@ constexpr std::uint32_t const tfStrongTSH = 0x00008000;
constexpr std::uint32_t const tfNFTokenMintOldMask =
~(tfUniversal | tfBurnable | tfOnlyXRP | tfTrustLine | tfTransferable | tfStrongTSH);
// Attest flags:
constexpr std::uint32_t tfDeleteAttestation = 0x00000001;
constexpr std::uint32_t tfAttestMask = ~tfDeleteAttestation;
// Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between
// accounts allowed a TrustLine to be added to the issuer of that token
// without explicit permission from that issuer. This was enabled by

View File

@@ -146,14 +146,12 @@ enum TxType : std::uint16_t
ttURITOKEN_CREATE_SELL_OFFER = 48,
ttURITOKEN_CANCEL_SELL_OFFER = 49,
/* This transaction mints, burns or updates cadastral tiles. */
ttCADASTRE_MINT = 0x005D, // HookOn = 93
ttCADASTRE_BURN = 0x015D,
ttCADASTRE_CREATE_SELL_OFFER = 0x025D,
ttCADASTRE_CANCEL_SELL_OFFER = 0x035D,
ttCADASTRE_BUY = 0x045D,
ttCADASTRE_SET = 0x055D,
/** This transaction acts as an attestation for another txn currently in the TxQ */
ttATTEST = 94,
/* A payment transactor that delivers only the exact amounts specified, creating accounts and TLs as needed
* that the sender pays for. */
ttREMIT = 95,
/** This transaction can only be used by the genesis account, which is controlled exclusively by
* rewards/governance hooks, to print new XRP to be delivered directly to an array of destinations,

View File

@@ -460,7 +460,7 @@ REGISTER_FEATURE(Import, Supported::yes, VoteBehavior::De
REGISTER_FEATURE(XahauGenesis, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(HooksUpdate1, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX (fixURITokenV1, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(Attestations, Supported::yes, VoteBehavior::DefaultNo);
// The following amendments are obsolete, but must remain supported
// because they could potentially get enabled.

View File

@@ -45,6 +45,8 @@ namespace ripple {
*/
enum class LedgerNameSpace : std::uint16_t {
ACCOUNT = 'a',
ATTESTATION_ACC = 'A',
ATTESTATION_TXN = 't',
DIR_NODE = 'd',
TRUST_LINE = 'r',
OFFER = 'o',
@@ -72,7 +74,6 @@ enum class LedgerNameSpace : std::uint16_t {
URI_TOKEN = 'U',
IMPORT_VLSEQ = 'I',
UNL_REPORT = 'R',
CADASTRE = 'K',
// No longer used or supported. Left here to reserve the space
// to avoid accidental reuse.
@@ -436,7 +437,7 @@ nft_sells(uint256 const& id) noexcept
}
Keylet
uritoken(AccountID const& issuer, Blob const& uri)
uritoken(AccountID const& issuer, Blob const& uri) noexcept
{
return {
ltURI_TOKEN,
@@ -444,13 +445,14 @@ uritoken(AccountID const& issuer, Blob const& uri)
LedgerNameSpace::URI_TOKEN, issuer, Slice{uri.data(), uri.size()})};
}
Keylet
cadastre(uint256 const& universe, uint16_t locx, uint16_t locy)
Keylet attestationTxn(AccountID const& issuer, uint256 const& txnid) noexcept
{
return {
ltCADASTRE,
indexHash(
LedgerNameSpace::CADASTRE, universe, locx, locy)};
return {ltATTESTATION, indexHash(LedgerNameSpace::ATTESTATION_TXN, issuer, txnid)};
}
Keylet attestationAcc(AccountID const& issuer, AccountID const& issuee) noexcept
{
return {ltATTESTATION, indexHash(LedgerNameSpace::ATTESTATION_ACC, issuer, issuee)};
}
} // namespace keylet

View File

@@ -141,6 +141,27 @@ InnerObjectFormats::InnerObjectFormats()
{sfPublicKey, soeREQUIRED},
{sfAccount, soeOPTIONAL},
});
add(sfAmountEntry.jsonName.c_str(),
sfAmountEntry.getCode(),
{
{sfAmount, soeREQUIRED},
{sfFlags, soeOPTIONAL},
});
add(sfMintURIToken.jsonName.c_str(),
sfMintURIToken.getCode(),
{
{sfURI, soeREQUIRED},
{sfDigest, soeOPTIONAL},
{sfFlags, soeOPTIONAL},
});
add(sfAttesterEntry.jsonName.c_str(),
sfAttesterEntry.getCode(),
{
{sfAccount, soeREQUIRED},
});
}
InnerObjectFormats const&

View File

@@ -363,19 +363,15 @@ LedgerFormats::LedgerFormats()
},
commonFields);
add(jss::Cadastre,
ltCADASTRE,
add(jss::Attestation,
ltATTESTATION,
{
{sfOwner, soeREQUIRED},
{sfOwnerNode, soeREQUIRED},
{sfAssociation, soeOPTIONAL},
{sfAssociationNode, soeOPTIONAL},
{sfLocationX, soeREQUIRED},
{sfLocationY, soeREQUIRED},
{sfUniverse, soeREQUIRED},
{sfDisplayURI, soeOPTIONAL},
{sfBroadcastURI, soeOPTIONAL},
{sfCadastreCount, soeOPTIONAL}, // for 0x8000,0x8000 tile only
{sfAttestedTxnID, soeOPTIONAL},
{sfAttestedAccID, soeOPTIONAL},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
},
commonFields);

View File

@@ -102,8 +102,6 @@ CONSTRUCT_TYPED_SFIELD(sfHookStateChangeCount, "HookStateChangeCount", UINT16,
CONSTRUCT_TYPED_SFIELD(sfHookEmitCount, "HookEmitCount", UINT16, 18);
CONSTRUCT_TYPED_SFIELD(sfHookExecutionIndex, "HookExecutionIndex", UINT16, 19);
CONSTRUCT_TYPED_SFIELD(sfHookApiVersion, "HookApiVersion", UINT16, 20);
CONSTRUCT_TYPED_SFIELD(sfLocationX, "LocationX", UINT16, 99);
CONSTRUCT_TYPED_SFIELD(sfLocationY, "LocationY", UINT16, 98);
// 32-bit integers (common)
CONSTRUCT_TYPED_SFIELD(sfNetworkID, "NetworkID", UINT32, 1);
@@ -185,8 +183,6 @@ CONSTRUCT_TYPED_SFIELD(sfEmitBurden, "EmitBurden", UINT64,
CONSTRUCT_TYPED_SFIELD(sfHookInstructionCount, "HookInstructionCount", UINT64, 17);
CONSTRUCT_TYPED_SFIELD(sfHookReturnCode, "HookReturnCode", UINT64, 18);
CONSTRUCT_TYPED_SFIELD(sfReferenceCount, "ReferenceCount", UINT64, 19);
CONSTRUCT_TYPED_SFIELD(sfCadastreCount, "CadastreCount", UINT64, 96);
CONSTRUCT_TYPED_SFIELD(sfAssociationNode, "AssociationNode", UINT64, 97);
CONSTRUCT_TYPED_SFIELD(sfAccountIndex, "AccountIndex", UINT64, 98);
CONSTRUCT_TYPED_SFIELD(sfAccountCount, "AccountCount", UINT64, 99);
CONSTRUCT_TYPED_SFIELD(sfRewardAccumulator, "RewardAccumulator", UINT64, 100);
@@ -240,7 +236,7 @@ CONSTRUCT_TYPED_SFIELD(sfURITokenID, "URITokenID", UINT256,
CONSTRUCT_TYPED_SFIELD(sfGovernanceFlags, "GovernanceFlags", UINT256, 99);
CONSTRUCT_TYPED_SFIELD(sfGovernanceMarks, "GovernanceMarks", UINT256, 98);
CONSTRUCT_TYPED_SFIELD(sfEmittedTxnID, "EmittedTxnID", UINT256, 97);
CONSTRUCT_TYPED_SFIELD(sfUniverse, "Universe", UINT256, 96);
CONSTRUCT_TYPED_SFIELD(sfAttestedTxnID, "AttestedTxnID", UINT256, 96);
// currency amount (common)
CONSTRUCT_TYPED_SFIELD(sfAmount, "Amount", AMOUNT, 1);
@@ -295,8 +291,6 @@ CONSTRUCT_TYPED_SFIELD(sfHookReturnString, "HookReturnString", VL,
CONSTRUCT_TYPED_SFIELD(sfHookParameterName, "HookParameterName", VL, 24);
CONSTRUCT_TYPED_SFIELD(sfHookParameterValue, "HookParameterValue", VL, 25);
CONSTRUCT_TYPED_SFIELD(sfBlob, "Blob", VL, 26);
CONSTRUCT_TYPED_SFIELD(sfBroadcastURI, "BroadcastURI", VL, 99);
CONSTRUCT_TYPED_SFIELD(sfDisplayURI, "DisplayURI", VL, 98);
// account
CONSTRUCT_TYPED_SFIELD(sfAccount, "Account", ACCOUNT, 1);
@@ -312,7 +306,8 @@ CONSTRUCT_TYPED_SFIELD(sfEmitCallback, "EmitCallback", ACCOUNT,
// account (uncommon)
CONSTRUCT_TYPED_SFIELD(sfHookAccount, "HookAccount", ACCOUNT, 16);
CONSTRUCT_TYPED_SFIELD(sfAssociation, "Association", ACCOUNT, 99);
CONSTRUCT_TYPED_SFIELD(sfInform, "Inform", ACCOUNT, 99);
CONSTRUCT_TYPED_SFIELD(sfAttestedAccID, "AttestedAccID", ACCOUNT, 98);
// vector of 256-bit
CONSTRUCT_TYPED_SFIELD(sfIndexes, "Indexes", VECTOR256, 1, SField::sMD_Never);
@@ -320,6 +315,7 @@ CONSTRUCT_TYPED_SFIELD(sfHashes, "Hashes", VECTOR25
CONSTRUCT_TYPED_SFIELD(sfAmendments, "Amendments", VECTOR256, 3);
CONSTRUCT_TYPED_SFIELD(sfNFTokenOffers, "NFTokenOffers", VECTOR256, 4);
CONSTRUCT_TYPED_SFIELD(sfHookNamespaces, "HookNamespaces", VECTOR256, 5);
CONSTRUCT_TYPED_SFIELD(sfURITokenIDs, "URITokenIDs", VECTOR256, 99);
// path set
CONSTRUCT_UNTYPED_SFIELD(sfPaths, "Paths", PATHSET, 1);
@@ -354,6 +350,9 @@ CONSTRUCT_UNTYPED_SFIELD(sfGenesisMint, "GenesisMint", OBJECT,
CONSTRUCT_UNTYPED_SFIELD(sfActiveValidator, "ActiveValidator", OBJECT, 95);
CONSTRUCT_UNTYPED_SFIELD(sfImportVLKey, "ImportVLKey", OBJECT, 94);
CONSTRUCT_UNTYPED_SFIELD(sfHookEmission, "HookEmission", OBJECT, 93);
CONSTRUCT_UNTYPED_SFIELD(sfMintURIToken, "MintURIToken", OBJECT, 92);
CONSTRUCT_UNTYPED_SFIELD(sfAmountEntry, "AmountEntry", OBJECT, 91);
CONSTRUCT_UNTYPED_SFIELD(sfAttesterEntry, "AttesterEntry", OBJECT, 90);
// array of objects
// ARRAY/1 is reserved for end of array
@@ -378,6 +377,8 @@ CONSTRUCT_UNTYPED_SFIELD(sfGenesisMints, "GenesisMints", ARRAY,
CONSTRUCT_UNTYPED_SFIELD(sfActiveValidators, "ActiveValidators", ARRAY, 95);
CONSTRUCT_UNTYPED_SFIELD(sfImportVLKeys, "ImportVLKeys", ARRAY, 94);
CONSTRUCT_UNTYPED_SFIELD(sfHookEmissions, "HookEmissions", ARRAY, 93);
CONSTRUCT_UNTYPED_SFIELD(sfAmounts, "Amounts", ARRAY, 92);
CONSTRUCT_UNTYPED_SFIELD(sfAttesters, "Attesters", ARRAY, 91);
// clang-format on

View File

@@ -91,6 +91,7 @@ transResults()
MAKE_ERROR(tecHOOK_REJECTED, "Rejected by hook on sending or receiving account."),
MAKE_ERROR(tecREQUIRES_FLAG, "The transaction or part-thereof requires a flag that wasn't set."),
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
MAKE_ERROR(tecLAST_LEDGER_SEQ_TOO_HIGH, "The sfLastLedgerSequence was higher than seq + 256."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
MAKE_ERROR(tefBAD_AUTH, "Transaction's public key is not authorized."),
@@ -188,6 +189,7 @@ transResults()
MAKE_ERROR(terQUEUED, "Held until escalated fee drops."),
MAKE_ERROR(terPRE_TICKET, "Ticket is not yet in ledger."),
MAKE_ERROR(terNO_HOOK, "No hook with that hash exists on the ledger."),
MAKE_ERROR(terAWAITING_ATTESTATION, "Transaction is waiting for attesters to attest."),
MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."),
};

View File

@@ -44,6 +44,7 @@ TxFormats::TxFormats()
{sfNetworkID, soeOPTIONAL},
{sfHookParameters, soeOPTIONAL},
{sfOperationLimit, soeOPTIONAL},
{sfAttesters, soeOPTIONAL},
};
add(jss::AccountSet,
@@ -116,6 +117,21 @@ TxFormats::TxFormats()
},
commonFields);
add(jss::Remit,
ttREMIT,
{
{sfDestination, soeREQUIRED},
{sfAmounts, soeOPTIONAL},
{sfURITokenIDs, soeOPTIONAL},
{sfMintURIToken, soeOPTIONAL},
{sfInvoiceID, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL},
{sfTicketSequence, soeOPTIONAL},
{sfBlob, soeOPTIONAL},
{sfInform, soeOPTIONAL},
},
commonFields);
add(jss::EscrowCreate,
ttESCROW_CREATE,
{
@@ -442,68 +458,12 @@ TxFormats::TxFormats()
},
commonFields);
add(jss::CadastreMint,
ttCADASTRE_MINT,
add(jss::Attest,
ttATTEST,
{
{sfDestination, soeOPTIONAL},
{sfAmount, soeOPTIONAL},
{sfAssociation, soeOPTIONAL},
{sfBroadcastURI, soeOPTIONAL},
{sfDisplayURI, soeOPTIONAL},
{sfLocationX, soeREQUIRED},
{sfLocationY, soeREQUIRED},
{sfUniverse, soeREQUIRED},
},
commonFields);
add(jss::CadastreBurn,
ttCADASTRE_BURN,
{
{sfLocationX, soeREQUIRED},
{sfLocationY, soeREQUIRED},
{sfUniverse, soeREQUIRED},
},
commonFields);
add(jss::CadastreCreateSellOffer,
ttCADASTRE_CREATE_SELL_OFFER,
{
{sfDestination, soeOPTIONAL},
{sfAmount, soeREQUIRED},
{sfLocationX, soeREQUIRED},
{sfLocationY, soeREQUIRED},
{sfUniverse, soeREQUIRED},
},
commonFields);
add(jss::CadastreCancelSellOffer,
ttCADASTRE_CANCEL_SELL_OFFER,
{
{sfLocationX, soeREQUIRED},
{sfLocationY, soeREQUIRED},
{sfUniverse, soeREQUIRED},
},
commonFields);
add(jss::CadastreBuy,
ttCADASTRE_BUY,
{
{sfAmount, soeREQUIRED},
{sfLocationX, soeREQUIRED},
{sfLocationY, soeREQUIRED},
{sfUniverse, soeREQUIRED},
},
commonFields);
add(jss::CadastreSet,
ttCADASTRE_SET,
{
{sfAssociation, soeOPTIONAL},
{sfBroadcastURI, soeOPTIONAL},
{sfDisplayURI, soeOPTIONAL},
{sfLocationX, soeREQUIRED},
{sfLocationY, soeREQUIRED},
{sfUniverse, soeREQUIRED},
{sfAttestedTxnID, soeOPTIONAL},
{sfAttestedAccID, soeOPTIONAL},
{sfTicketSequence, soeOPTIONAL},
},
commonFields);
}

View File

@@ -50,14 +50,10 @@ JSS(AccountSet); // transaction type.
JSS(Amendments); // ledger type.
JSS(Amount); // in: TransactionSign; field.
JSS(Authorize); // field
JSS(Attest);
JSS(AttesterEntry);
JSS(Attestation);
JSS(Blob);
JSS(Cadastre); // ledger type
JSS(CadastreMint); // txn type
JSS(CadastreBurn); // txn type
JSS(CadastreCreateSellOffer); // txn type
JSS(CadastreCancelSellOffer); // txn type
JSS(CadastreBuy); // txn type
JSS(CadastreSet); // txn type
JSS(Check); // ledger type.
JSS(CheckCancel); // transaction type.
JSS(CheckCash); // transaction type.
@@ -124,6 +120,7 @@ JSS(Payment); // transaction type.
JSS(PaymentChannelClaim); // transaction type.
JSS(PaymentChannelCreate); // transaction type.
JSS(PaymentChannelFund); // transaction type.
JSS(Remit); // transaction type.
JSS(RippleState); // ledger type.
JSS(SLE_hit_rate); // out: GetCounts.
JSS(SetFee); // transaction type.

View File

@@ -422,11 +422,11 @@ doServerDefinitions(RPC::JsonContext& context)
uint32_t curLgrSeq = context.ledgerMaster.getValidatedLedger()->info().seq;
// static values used for cache
static thread_local uint32_t lastGenerated = 0; // last ledger seq it was generated
static thread_local Json::Value lastFeatures{
Json::objectValue}; // the actual features JSON last generated
static thread_local uint256 lastFeatureHash; // the hash of the features JSON last time
// it was generated
static uint32_t lastGenerated = 0; // last ledger seq it was generated
static Json::Value lastFeatures{
Json::objectValue}; // the actual features JSON last generated
static uint256 lastFeatureHash; // the hash of the features JSON last time
// it was generated
// if a flag ledger has passed since it was last generated, regenerate it,
// update the cache above

View File

@@ -17,8 +17,8 @@
*/
//==============================================================================
#include <test/jtx/Env.h>
#include <ripple/protocol/jss.h>
#include <test/jtx/Env.h>
#include <test/jtx/acctdelete.h>
namespace ripple {