mirror of
https://github.com/Xahau/xahaud.git
synced 2026-07-31 19:10:10 +00:00
feat: implement uvtxn pattern for ttEXPORT_SIGN
Port the UNL Validator Transaction (UVTxn) pattern from the RNG feature to allow validators to submit signed ttEXPORT_SIGN transactions without requiring a funded account. Changes: - Add isUVTx() to identify UVTxn transaction types - Add inUNLReport() templates to check validator UNLReport membership - Add getValidationSecretKey() to Application for signing - Modify Transactor for UVTxn bypasses (fee, seq, signature checks) - Add makeExportSignTxns() to generate validator signatures - Hook into RCLConsensus to submit ttEXPORT_SIGN during accept - Update applySteps.cpp routing for ttEXPORT_SIGN - Remove direct ttEXPORT_SIGN injection from TxQ::accept Note: Currently uses Change transactor with UVTx branches. May refactor to dedicated ExportSign transactor class.
This commit is contained in:
@@ -35,6 +35,7 @@
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/app/misc/ValidatorKeys.h>
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <ripple/app/tx/impl/Change.h>
|
||||
#include <ripple/basics/random.h>
|
||||
#include <ripple/beast/core/LexicalCast.h>
|
||||
#include <ripple/consensus/LedgerTiming.h>
|
||||
@@ -652,6 +653,16 @@ RCLConsensus::Adaptor::doAccept(
|
||||
tapNONE,
|
||||
"consensus",
|
||||
[&](OpenView& view, beast::Journal j) {
|
||||
//@@start export-sign-submit
|
||||
// Generate and submit ttEXPORT_SIGN UVTxns if we're a
|
||||
// validator on the UNLReport
|
||||
if (view.rules().enabled(featureExport))
|
||||
{
|
||||
auto exportSignTxns = makeExportSignTxns(view, app_, j_);
|
||||
for (auto const& tx : exportSignTxns)
|
||||
app_.getOPs().submitTransaction(tx);
|
||||
}
|
||||
//@@end export-sign-submit
|
||||
return app_.getTxQ().accept(app_, view);
|
||||
});
|
||||
|
||||
|
||||
@@ -599,6 +599,12 @@ public:
|
||||
return validatorKeys_.publicKey;
|
||||
}
|
||||
|
||||
SecretKey const&
|
||||
getValidationSecretKey() const override
|
||||
{
|
||||
return validatorKeys_.secretKey;
|
||||
}
|
||||
|
||||
ValidatorKeys const&
|
||||
getValidatorKeys() const override
|
||||
{
|
||||
|
||||
@@ -240,6 +240,10 @@ public:
|
||||
|
||||
virtual PublicKey const&
|
||||
getValidationPublicKey() const = 0;
|
||||
|
||||
virtual SecretKey const&
|
||||
getValidationSecretKey() const = 0;
|
||||
|
||||
virtual ValidatorKeys const&
|
||||
getValidatorKeys() const = 0;
|
||||
virtual Resource::Manager&
|
||||
|
||||
@@ -1580,8 +1580,6 @@ TxQ::accept(Application& app, OpenView& view)
|
||||
|
||||
// execution to here means we're a validator and on the UNLReport
|
||||
|
||||
AccountID signingAcc = calcAccountID(keys.publicKey);
|
||||
|
||||
Keylet const exportedDirKeylet{keylet::exportedDir()};
|
||||
if (dirIsEmpty(view, exportedDirKeylet))
|
||||
break;
|
||||
@@ -1715,65 +1713,10 @@ TxQ::accept(Application& app, OpenView& view)
|
||||
continue;
|
||||
}
|
||||
|
||||
// this ledger is the one after the exported txn was added to
|
||||
// the directory so generate the export sign txns
|
||||
|
||||
auto s = std::make_shared<ripple::Serializer>();
|
||||
exported.add(*s);
|
||||
SerialIter sitTrans(s->slice());
|
||||
try
|
||||
{
|
||||
auto const& stpTrans =
|
||||
std::make_shared<STTx const>(std::ref(sitTrans));
|
||||
|
||||
if (!stpTrans->isFieldPresent(sfAccount) ||
|
||||
stpTrans->getAccountID(sfAccount) == beast::zero)
|
||||
{
|
||||
JLOG(j_.warn()) << "Hook: Export failure: "
|
||||
<< "sfAccount missing or zero.";
|
||||
// RH TODO: if this ever happens the entry should be
|
||||
// gracefully removed (somehow)
|
||||
continue;
|
||||
}
|
||||
|
||||
auto seq = view.info().seq;
|
||||
auto txnHash = stpTrans->getTransactionID();
|
||||
|
||||
Serializer s = buildMultiSigningData(*stpTrans, signingAcc);
|
||||
|
||||
auto multisig =
|
||||
ripple::sign(keys.publicKey, keys.secretKey, s.slice());
|
||||
|
||||
STTx exportSignTx(ttEXPORT_SIGN, [&](auto& obj) {
|
||||
obj.set(([&]() {
|
||||
auto inner = std::make_unique<STObject>(sfSigner);
|
||||
inner->setFieldVL(sfSigningPubKey, keys.publicKey);
|
||||
inner->setAccountID(sfAccount, signingAcc);
|
||||
inner->setFieldVL(sfTxnSignature, multisig);
|
||||
return inner;
|
||||
})());
|
||||
obj.setFieldU32(sfLedgerSequence, seq);
|
||||
obj.setFieldH256(sfTransactionHash, txnHash);
|
||||
});
|
||||
|
||||
// submit to the ledger
|
||||
{
|
||||
uint256 txID = exportSignTx.getTransactionID();
|
||||
auto s = std::make_shared<ripple::Serializer>();
|
||||
exportSignTx.add(*s);
|
||||
app.getHashRouter().setFlags(txID, SF_PRIVATE2);
|
||||
app.getHashRouter().setFlags(txID, SF_EMITTED);
|
||||
view.rawTxInsert(txID, std::move(s), nullptr);
|
||||
ledgerChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
catch (std::exception& e)
|
||||
{
|
||||
JLOG(j_.warn())
|
||||
<< "ExportedTxn Processing: Failure: " << e.what()
|
||||
<< "\n";
|
||||
}
|
||||
// ttEXPORT_SIGN transactions are now submitted by validators
|
||||
// as UVTxns (UNL Validator Transactions) via
|
||||
// makeExportSignTxns, rather than being injected here as
|
||||
// pseudo-transactions.
|
||||
|
||||
} while (cdirNext(
|
||||
view, exportedDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
|
||||
@@ -2172,13 +2115,16 @@ TxQ::tryDirectApply(
|
||||
const bool isFirstImport = !sleAccount &&
|
||||
view.rules().enabled(featureImport) && tx->getTxnType() == ttIMPORT;
|
||||
|
||||
// UVTxns don't require an account
|
||||
const bool accRequired = !(isFirstImport || isUVTx(*tx));
|
||||
|
||||
// Don't attempt to direct apply if the account is not in the ledger.
|
||||
if (!sleAccount && !isFirstImport)
|
||||
if (!sleAccount && accRequired)
|
||||
return {};
|
||||
|
||||
std::optional<SeqProxy> txSeqProx;
|
||||
|
||||
if (!isFirstImport)
|
||||
if (accRequired)
|
||||
{
|
||||
SeqProxy const acctSeqProx =
|
||||
SeqProxy::sequence((*sleAccount)[sfSequence]);
|
||||
@@ -2191,7 +2137,7 @@ TxQ::tryDirectApply(
|
||||
}
|
||||
|
||||
FeeLevel64 const requiredFeeLevel =
|
||||
isFirstImport ? FeeLevel64{0} : [this, &view, flags]() {
|
||||
!accRequired ? FeeLevel64{0} : [this, &view, flags]() {
|
||||
std::lock_guard lock(mutex_);
|
||||
return getRequiredFeeLevel(
|
||||
view, flags, feeMetrics_.getSnapshot(), lock);
|
||||
|
||||
@@ -23,14 +23,17 @@
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/app/misc/AmendmentTable.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/misc/ValidatorKeys.h>
|
||||
#include <ripple/app/tx/impl/Change.h>
|
||||
#include <ripple/app/tx/impl/SetSignerList.h>
|
||||
#include <ripple/app/tx/impl/XahauGenesis.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/ledger/OpenView.h>
|
||||
#include <ripple/ledger/Sandbox.h>
|
||||
#include <ripple/protocol/AccountID.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/Sign.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <string_view>
|
||||
|
||||
@@ -43,34 +46,57 @@ Change::preflight(PreflightContext const& ctx)
|
||||
if (!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
auto account = ctx.tx.getAccountID(sfAccount);
|
||||
if (account != beast::zero)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change: Bad source id";
|
||||
return temBAD_SRC_ACCOUNT;
|
||||
}
|
||||
// ttEXPORT_SIGN is a UVTx (UNL Validator Transaction), not a pseudo-tx.
|
||||
// It has a real account, signature, and goes through normal validation.
|
||||
bool const isUVTx = ctx.tx.getTxnType() == ttEXPORT_SIGN;
|
||||
|
||||
// No point in going any further if the transaction fee is malformed.
|
||||
auto const fee = ctx.tx.getFieldAmount(sfFee);
|
||||
if (!fee.native() || fee != beast::zero)
|
||||
if (!isUVTx)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change: invalid fee";
|
||||
return temBAD_FEE;
|
||||
}
|
||||
auto account = ctx.tx.getAccountID(sfAccount);
|
||||
if (account != beast::zero)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change: Bad source id";
|
||||
return temBAD_SRC_ACCOUNT;
|
||||
}
|
||||
|
||||
if (!ctx.tx.getSigningPubKey().empty() || !ctx.tx.getSignature().empty() ||
|
||||
ctx.tx.isFieldPresent(sfSigners))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change: Bad signature";
|
||||
return temBAD_SIGNATURE;
|
||||
}
|
||||
// No point in going any further if the transaction fee is malformed.
|
||||
auto const fee = ctx.tx.getFieldAmount(sfFee);
|
||||
if (!fee.native() || fee != beast::zero)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change: invalid fee";
|
||||
return temBAD_FEE;
|
||||
}
|
||||
|
||||
if (ctx.tx.getFieldU32(sfSequence) != 0 ||
|
||||
ctx.tx.isFieldPresent(sfPreviousTxnID))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change: Bad sequence";
|
||||
return temBAD_SEQUENCE;
|
||||
if (!ctx.tx.getSigningPubKey().empty() ||
|
||||
!ctx.tx.getSignature().empty() || ctx.tx.isFieldPresent(sfSigners))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change: Bad signature";
|
||||
return temBAD_SIGNATURE;
|
||||
}
|
||||
|
||||
if (ctx.tx.getFieldU32(sfSequence) != 0 ||
|
||||
ctx.tx.isFieldPresent(sfPreviousTxnID))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change: Bad sequence";
|
||||
return temBAD_SEQUENCE;
|
||||
}
|
||||
}
|
||||
//@@start uvtx-preflight
|
||||
else
|
||||
{
|
||||
// UVTx preflight checks
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (!ctx.rules.enabled(featureExport))
|
||||
return temDISABLED;
|
||||
|
||||
if (auto const ret = preflight2(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
//@@end uvtx-preflight
|
||||
|
||||
if (ctx.tx.getTxnType() == ttUNL_MODIFY &&
|
||||
!ctx.rules.enabled(featureNegativeUNL))
|
||||
@@ -112,12 +138,38 @@ Change::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// If tapOPEN_LEDGER is resurrected into ApplyFlags,
|
||||
// this block can be moved to preflight.
|
||||
if (ctx.view.open())
|
||||
// UVTxns like ttEXPORT_SIGN can be applied in the open ledger.
|
||||
bool const isUVTx = ctx.tx.getTxnType() == ttEXPORT_SIGN;
|
||||
if (ctx.view.open() && !isUVTx)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change transaction against open ledger";
|
||||
return temINVALID;
|
||||
}
|
||||
|
||||
//@@start uvtx-preclaim
|
||||
// UVTx (ttEXPORT_SIGN) validation: signer must be in UNLReport
|
||||
if (isUVTx)
|
||||
{
|
||||
if (!ctx.view.rules().enabled(featureExport))
|
||||
return temDISABLED;
|
||||
|
||||
auto const& pkSignerField = ctx.tx.getSigningPubKey();
|
||||
if (!publicKeyType(makeSlice(pkSignerField)))
|
||||
return tefBAD_AUTH;
|
||||
|
||||
PublicKey pkSigner{makeSlice(pkSignerField)};
|
||||
|
||||
if (!inUNLReport(ctx.view, ctx.app, pkSigner, ctx.j))
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "ExportSign: Txn Account isn't in the UNLReport.";
|
||||
return tefFAILURE;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
//@@end uvtx-preclaim
|
||||
|
||||
switch (ctx.tx.getTxnType())
|
||||
{
|
||||
case ttFEE:
|
||||
@@ -221,8 +273,10 @@ Change::doApply()
|
||||
return applyUNLReport();
|
||||
case ttEXPORT:
|
||||
return applyExport();
|
||||
//@@start export-sign-route
|
||||
case ttEXPORT_SIGN:
|
||||
return applyExportSign();
|
||||
//@@end export-sign-route
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
@@ -1293,4 +1347,143 @@ Change::applyUNLModify()
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<STTx const>>
|
||||
makeExportSignTxns(OpenView& view, Application& app, beast::Journal const& j)
|
||||
{
|
||||
std::vector<std::shared_ptr<STTx const>> result;
|
||||
|
||||
if (!view.rules().enabled(featureExport))
|
||||
return result;
|
||||
|
||||
JLOG(j.debug()) << "EXPORT_SIGN processing: started";
|
||||
|
||||
auto const seq = view.info().seq;
|
||||
|
||||
// if we're not a validator we do nothing here
|
||||
if (app.getValidationPublicKey().empty())
|
||||
return result;
|
||||
|
||||
auto const& keys = app.getValidatorKeys();
|
||||
|
||||
if (keys.configInvalid())
|
||||
return result;
|
||||
|
||||
PublicKey pkSigning = app.getValidationPublicKey();
|
||||
auto const pk = app.validatorManifests().getMasterKey(pkSigning);
|
||||
|
||||
// Only continue if we're on the UNLReport
|
||||
if (!inUNLReport(view, app, pk, j))
|
||||
return result;
|
||||
|
||||
AccountID signingAcc = calcAccountID(pkSigning);
|
||||
|
||||
Keylet const exportedDirKeylet{keylet::exportedDir()};
|
||||
if (dirIsEmpty(view, exportedDirKeylet))
|
||||
return result;
|
||||
|
||||
std::shared_ptr<SLE const> sleDirNode{};
|
||||
unsigned int uDirEntry{0};
|
||||
uint256 dirEntry{beast::zero};
|
||||
|
||||
if (!cdirFirst(
|
||||
view, exportedDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
|
||||
return result;
|
||||
|
||||
do
|
||||
{
|
||||
Keylet const itemKeylet{ltCHILD, dirEntry};
|
||||
auto sleItem = view.read(itemKeylet);
|
||||
if (!sleItem)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "ExportedTxn processing: directory node in ledger " << seq
|
||||
<< " has index to object that is missing: "
|
||||
<< to_string(dirEntry);
|
||||
continue;
|
||||
}
|
||||
|
||||
LedgerEntryType const nodeType{
|
||||
safe_cast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
|
||||
|
||||
if (nodeType != ltEXPORTED_TXN)
|
||||
{
|
||||
JLOG(j.warn()) << "ExportedTxn processing: exported directory "
|
||||
"contained non ltEXPORTED_TXN type";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const& exported = const_cast<ripple::STLedgerEntry&>(*sleItem)
|
||||
.getField(sfExportedTxn)
|
||||
.downcast<STObject>();
|
||||
|
||||
auto exportedLgrSeq = exported.getFieldU32(sfLedgerSequence);
|
||||
|
||||
// Only sign transactions that were added in the previous ledger
|
||||
if (exportedLgrSeq != seq - 1)
|
||||
continue;
|
||||
|
||||
auto s = std::make_shared<ripple::Serializer>();
|
||||
exported.add(*s);
|
||||
SerialIter sitTrans(s->slice());
|
||||
try
|
||||
{
|
||||
auto const& stpTrans =
|
||||
std::make_shared<STTx const>(std::ref(sitTrans));
|
||||
|
||||
if (!stpTrans->isFieldPresent(sfAccount) ||
|
||||
stpTrans->getAccountID(sfAccount) == beast::zero)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "Hook: Export failure: sfAccount missing or zero.";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto txnHash = stpTrans->getTransactionID();
|
||||
|
||||
// Build the multisig for the inner exported transaction
|
||||
Serializer sigData = buildMultiSigningData(*stpTrans, signingAcc);
|
||||
auto multisig =
|
||||
ripple::sign(keys.publicKey, keys.secretKey, sigData.slice());
|
||||
|
||||
// Create the ttEXPORT_SIGN transaction
|
||||
auto exportSignTx =
|
||||
std::make_shared<STTx>(ttEXPORT_SIGN, [&](auto& obj) {
|
||||
obj.set(([&]() {
|
||||
auto inner = std::make_unique<STObject>(sfSigner);
|
||||
inner->setFieldVL(sfSigningPubKey, keys.publicKey);
|
||||
inner->setAccountID(sfAccount, signingAcc);
|
||||
inner->setFieldVL(sfTxnSignature, multisig);
|
||||
return inner;
|
||||
})());
|
||||
obj.setFieldU32(sfLedgerSequence, seq);
|
||||
obj.setFieldH256(sfTransactionHash, txnHash);
|
||||
obj.setAccountID(sfAccount, calcAccountID(pk));
|
||||
obj.setFieldU32(sfSequence, 0);
|
||||
obj.setFieldVL(sfSigningPubKey, pkSigning.slice());
|
||||
obj.setFieldU32(sfFlags, tfFullyCanonicalSig);
|
||||
|
||||
if (app.config().NETWORK_ID > 1024)
|
||||
obj.setFieldU32(sfNetworkID, app.config().NETWORK_ID);
|
||||
});
|
||||
|
||||
// Sign the outer transaction using our ephemeral key
|
||||
exportSignTx->sign(pkSigning, app.getValidationSecretKey());
|
||||
|
||||
JLOG(j.debug())
|
||||
<< "EXPORT_SIGN txn: " << exportSignTx->getFullText();
|
||||
|
||||
result.push_back(exportSignTx);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
JLOG(j.warn()) << "ExportedTxn Processing: Failure: " << e.what()
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
} while (
|
||||
cdirNext(view, exportedDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/tx/impl/Transactor.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/ledger/OpenView.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -84,6 +85,13 @@ private:
|
||||
applyUNLReport();
|
||||
};
|
||||
|
||||
/**
|
||||
* If this validator is on the UNLReport, generate signed ttEXPORT_SIGN
|
||||
* transactions for any exported transactions that need signing.
|
||||
*/
|
||||
std::vector<std::shared_ptr<STTx const>>
|
||||
makeExportSignTxns(OpenView& view, Application& app, beast::Journal const& j);
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -273,6 +273,12 @@ Transactor::calculateHookChainFee(
|
||||
XRPAmount
|
||||
Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
//@@start uvtx-fee
|
||||
// UVTxns (UNL Validator Transactions) have zero fee
|
||||
if (isUVTx(tx))
|
||||
return XRPAmount{0};
|
||||
//@@end uvtx-fee
|
||||
|
||||
// Returns the fee in fee units.
|
||||
|
||||
// The computation has two parts:
|
||||
@@ -442,8 +448,19 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
|
||||
// Only check fee is sufficient when the ledger is open.
|
||||
if (ctx.view.open())
|
||||
{
|
||||
auto const feeDue =
|
||||
minimumFee(ctx.app, baseFee, ctx.view.fees(), ctx.flags);
|
||||
auto feeDue = minimumFee(ctx.app, baseFee, ctx.view.fees(), ctx.flags);
|
||||
|
||||
// UVTxns from validators in UNLReport don't have to pay a fee
|
||||
if (ctx.view.rules().enabled(featureExport) && isUVTx(ctx.tx))
|
||||
{
|
||||
auto const& pkSignerField = ctx.tx.getSigningPubKey();
|
||||
if (publicKeyType(makeSlice(pkSignerField)))
|
||||
{
|
||||
PublicKey pkSigner{makeSlice(pkSignerField)};
|
||||
if (inUNLReport(ctx.view, ctx.app, pkSigner, ctx.j))
|
||||
feeDue = beast::zero;
|
||||
}
|
||||
}
|
||||
|
||||
if (feePaid < feeDue)
|
||||
{
|
||||
@@ -461,6 +478,10 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
|
||||
auto const sle = ctx.view.read(keylet::account(id));
|
||||
if (!sle)
|
||||
{
|
||||
// UVTxns don't need an underlying account
|
||||
if (isUVTx(ctx.tx))
|
||||
return tesSUCCESS;
|
||||
|
||||
if (ctx.tx.getTxnType() == ttIMPORT)
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfIssuer))
|
||||
@@ -541,6 +562,16 @@ Transactor::checkSeqProxy(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//@@start uvtx-seq
|
||||
// UVTxns with seq=0 are allowed without an account
|
||||
if (isUVTx(tx) && t_seqProx.isSeq() && tx[sfSequence] == 0)
|
||||
{
|
||||
JLOG(j.trace()) << "applyTransaction: allowing UVTx with seq=0 "
|
||||
<< toBase58(id);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
//@@end uvtx-seq
|
||||
|
||||
JLOG(j.trace())
|
||||
<< "applyTransaction: delay: source account does not exist "
|
||||
<< toBase58(id);
|
||||
@@ -625,7 +656,10 @@ Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx)
|
||||
ctx.view.rules().enabled(featureImport) &&
|
||||
ctx.tx.getTxnType() == ttIMPORT && !ctx.tx.isFieldPresent(sfIssuer);
|
||||
|
||||
if (!sle && !isFirstImport)
|
||||
// UVTxns don't require an underlying account
|
||||
bool const accRequired = !(isFirstImport || isUVTx(ctx.tx));
|
||||
|
||||
if (!sle && accRequired)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "applyTransaction: delay: source account does not exist "
|
||||
@@ -781,12 +815,13 @@ Transactor::apply()
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
|
||||
// sle must exist except for transactions
|
||||
// that allow zero account. (and ttIMPORT)
|
||||
// that allow zero account. (ttIMPORT and UVTxns)
|
||||
assert(
|
||||
sle != nullptr || account_ == beast::zero ||
|
||||
view().rules().enabled(featureImport) &&
|
||||
ctx_.tx.getTxnType() == ttIMPORT &&
|
||||
!ctx_.tx.isFieldPresent(sfIssuer));
|
||||
!ctx_.tx.isFieldPresent(sfIssuer) ||
|
||||
isUVTx(ctx_.tx));
|
||||
|
||||
if (sle)
|
||||
{
|
||||
@@ -848,19 +883,28 @@ NotTEC
|
||||
Transactor::checkSingleSign(PreclaimContext const& ctx)
|
||||
{
|
||||
// Check that the value in the signing key slot is a public key.
|
||||
auto const pkSigner = ctx.tx.getSigningPubKey();
|
||||
if (!publicKeyType(makeSlice(pkSigner)))
|
||||
auto const& pkSignerField = ctx.tx.getSigningPubKey();
|
||||
if (!publicKeyType(makeSlice(pkSignerField)))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "checkSingleSign: signing public key type is unknown";
|
||||
return tefBAD_AUTH; // FIXME: should be better error!
|
||||
}
|
||||
|
||||
PublicKey pkSigner{makeSlice(pkSignerField)};
|
||||
|
||||
// Look up the account.
|
||||
auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
|
||||
auto const idSigner = calcAccountID(pkSigner);
|
||||
auto const idAccount = ctx.tx.getAccountID(sfAccount);
|
||||
auto const sleAccount = ctx.view.read(keylet::account(idAccount));
|
||||
|
||||
//@@start uvtx-sign
|
||||
// UVTxns of the approved type don't need an underlying account
|
||||
// and can be signed with the manifest ephemeral key
|
||||
if (isUVTx(ctx.tx) && inUNLReport(ctx.view, ctx.app, pkSigner, ctx.j))
|
||||
return tesSUCCESS;
|
||||
//@@end uvtx-sign
|
||||
|
||||
if (!sleAccount)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
|
||||
@@ -147,12 +147,16 @@ invoke_preflight(PreflightContext const& ctx)
|
||||
return invoke_preflight_helper<CreateTicket>(ctx);
|
||||
case ttTRUST_SET:
|
||||
return invoke_preflight_helper<SetTrust>(ctx);
|
||||
//@@start export-sign-preflight
|
||||
case ttAMENDMENT:
|
||||
case ttFEE:
|
||||
case ttUNL_MODIFY:
|
||||
case ttUNL_REPORT:
|
||||
case ttEMIT_FAILURE:
|
||||
case ttEXPORT:
|
||||
case ttEXPORT_SIGN:
|
||||
return invoke_preflight_helper<Change>(ctx);
|
||||
//@@end export-sign-preflight
|
||||
case ttHOOK_SET:
|
||||
return invoke_preflight_helper<SetHook>(ctx);
|
||||
case ttNFTOKEN_MINT:
|
||||
@@ -278,12 +282,16 @@ invoke_preclaim(PreclaimContext const& ctx)
|
||||
return invoke_preclaim<SetTrust>(ctx);
|
||||
case ttHOOK_SET:
|
||||
return invoke_preclaim<SetHook>(ctx);
|
||||
//@@start export-sign-preclaim
|
||||
case ttAMENDMENT:
|
||||
case ttFEE:
|
||||
case ttUNL_MODIFY:
|
||||
case ttUNL_REPORT:
|
||||
case ttEMIT_FAILURE:
|
||||
case ttEXPORT:
|
||||
case ttEXPORT_SIGN:
|
||||
return invoke_preclaim<Change>(ctx);
|
||||
//@@end export-sign-preclaim
|
||||
case ttNFTOKEN_MINT:
|
||||
return invoke_preclaim<NFTokenMint>(ctx);
|
||||
case ttNFTOKEN_BURN:
|
||||
@@ -369,6 +377,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
return SetTrust::calculateBaseFee(view, tx);
|
||||
case ttHOOK_SET:
|
||||
return SetHook::calculateBaseFee(view, tx);
|
||||
//@@start export-sign-fee
|
||||
case ttAMENDMENT:
|
||||
case ttFEE:
|
||||
case ttUNL_MODIFY:
|
||||
@@ -377,6 +386,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
case ttEXPORT_SIGN:
|
||||
case ttEXPORT:
|
||||
return Change::calculateBaseFee(view, tx);
|
||||
//@@end export-sign-fee
|
||||
case ttNFTOKEN_MINT:
|
||||
return NFTokenMint::calculateBaseFee(view, tx);
|
||||
case ttNFTOKEN_BURN:
|
||||
@@ -542,6 +552,7 @@ invoke_apply(ApplyContext& ctx)
|
||||
SetHook p(ctx);
|
||||
return p();
|
||||
}
|
||||
//@@start export-sign-apply
|
||||
case ttAMENDMENT:
|
||||
case ttFEE:
|
||||
case ttUNL_MODIFY:
|
||||
@@ -552,6 +563,7 @@ invoke_apply(ApplyContext& ctx)
|
||||
Change p(ctx);
|
||||
return p();
|
||||
}
|
||||
//@@end export-sign-apply
|
||||
case ttNFTOKEN_MINT: {
|
||||
NFTokenMint p(ctx);
|
||||
return p();
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#ifndef RIPPLE_LEDGER_VIEW_H_INCLUDED
|
||||
#define RIPPLE_LEDGER_VIEW_H_INCLUDED
|
||||
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/app/misc/Manifest.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/beast/utility/Journal.h>
|
||||
#include <ripple/core/Config.h>
|
||||
@@ -1094,6 +1096,74 @@ trustTransferLockedBalance(
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an account (derived from a validator's master public key) is
|
||||
* in the UNLReport's ActiveValidators list.
|
||||
*/
|
||||
template <class V>
|
||||
bool
|
||||
inUNLReport(V const& view, AccountID const& id, beast::Journal const& j)
|
||||
{
|
||||
auto const seq = view.info().seq;
|
||||
static uint32_t lastLgrSeq = 0;
|
||||
static std::map<AccountID, bool> cache;
|
||||
|
||||
// for the first 256 ledgers we're just saying everyone is in the UNLReport
|
||||
// because otherwise testing is very difficult.
|
||||
if (seq < 256)
|
||||
return true;
|
||||
|
||||
if (lastLgrSeq != seq)
|
||||
{
|
||||
cache.clear();
|
||||
lastLgrSeq = seq;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cache.find(id) != cache.end())
|
||||
return cache[id];
|
||||
}
|
||||
|
||||
// Check if account is on UNLReport
|
||||
auto const unlRep = view.read(keylet::UNLReport());
|
||||
if (!unlRep || !unlRep->isFieldPresent(sfActiveValidators))
|
||||
{
|
||||
JLOG(j.debug()) << "UNLReport missing";
|
||||
|
||||
// ensure we keep the cache invalid when in this state
|
||||
lastLgrSeq = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const& avs = unlRep->getFieldArray(sfActiveValidators);
|
||||
for (auto const& av : avs)
|
||||
{
|
||||
if (av.getAccountID(sfAccount) == id)
|
||||
return cache[id] = true;
|
||||
}
|
||||
|
||||
return cache[id] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a public key (or its master key via manifest lookup) is
|
||||
* in the UNLReport's ActiveValidators list.
|
||||
*/
|
||||
template <class V>
|
||||
bool
|
||||
inUNLReport(
|
||||
V const& view,
|
||||
Application& app,
|
||||
PublicKey const& pk,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
PublicKey uvPk = app.validatorManifests().getMasterKey(pk);
|
||||
|
||||
return inUNLReport(view, calcAccountID(pk), j) ||
|
||||
(uvPk != pk && inUNLReport(view, calcAccountID(uvPk), j));
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -171,6 +171,10 @@ sterilize(STTx const& stx);
|
||||
bool
|
||||
isPseudoTx(STObject const& tx);
|
||||
|
||||
/** Check whether a transaction is a UNL Validator Transaction (UVTx) */
|
||||
bool
|
||||
isUVTx(STObject const& tx);
|
||||
|
||||
inline STTx::STTx(SerialIter&& sit) : STTx(sit)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -615,7 +615,19 @@ isPseudoTx(STObject const& tx)
|
||||
|
||||
auto tt = safe_cast<TxType>(*t);
|
||||
return tt == ttAMENDMENT || tt == ttFEE || tt == ttUNL_MODIFY ||
|
||||
tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON;
|
||||
tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON ||
|
||||
tt == ttEXPORT;
|
||||
}
|
||||
|
||||
bool
|
||||
isUVTx(STObject const& tx)
|
||||
{
|
||||
auto t = tx[~sfTransactionType];
|
||||
if (!t)
|
||||
return false;
|
||||
|
||||
auto tt = safe_cast<TxType>(*t);
|
||||
return tt == ttEXPORT_SIGN;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
Reference in New Issue
Block a user