diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index fb3795315..c014216a9 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -1070,9 +1070,15 @@ void NetworkOPsImp::apply (std::unique_lock& batchLock) if (changed) reportFeeChange(); + boost::optional validatedLedgerIndex; + if (auto const l = m_ledgerMaster.getValidatedLedger()) + validatedLedgerIndex = l->info().seq; + auto newOL = app_.openLedger().current(); for (TransactionStatus& e : transactions) { + e.transaction->clearSubmitResult(); + if (e.applied) { pubProposedTransaction (newOL, @@ -1117,6 +1123,7 @@ void NetworkOPsImp::apply (std::unique_lock& batchLock) t, false, false, FailHard::no); t->setApplying(); } + e.transaction->setApplied(); } else if (e.result == tefPAST_SEQ) { @@ -1133,6 +1140,8 @@ void NetworkOPsImp::apply (std::unique_lock& batchLock) // kicked out of the queue, and this will try to // put it back. m_ledgerMaster.addHeldTransaction(e.transaction); + e.transaction->setQueued(); + e.transaction->setKept(); } else if (isTerRetry (e.result)) { @@ -1147,6 +1156,7 @@ void NetworkOPsImp::apply (std::unique_lock& batchLock) << "Transaction should be held: " << e.result; e.transaction->setStatus (HELD); m_ledgerMaster.addHeldTransaction (e.transaction); + e.transaction->setKept(); } } else @@ -1161,6 +1171,7 @@ void NetworkOPsImp::apply (std::unique_lock& batchLock) m_localTX->push_back ( m_ledgerMaster.getCurrentLedgerIndex(), e.transaction->getSTransaction()); + e.transaction->setKept(); } if (e.applied || ((mMode != OperatingMode::FULL) && @@ -1184,8 +1195,18 @@ void NetworkOPsImp::apply (std::unique_lock& batchLock) app_.overlay().foreach (send_if_not ( std::make_shared (tx, protocol::mtTRANSACTION), peer_in_set(*toSkip))); + e.transaction->setBroadcast(); } } + + if (validatedLedgerIndex) + { + auto [fee, accountSeq, availableSeq] = + app_.getTxQ().getTxRequiredFeeAndSeq( + *newOL, e.transaction->getSTransaction()); + e.transaction->setCurrentLedgerState( + *validatedLedgerIndex, fee, accountSeq, availableSeq); + } } } diff --git a/src/ripple/app/misc/Transaction.h b/src/ripple/app/misc/Transaction.h index 1e180f596..c829f6986 100644 --- a/src/ripple/app/misc/Transaction.h +++ b/src/ripple/app/misc/Transaction.h @@ -148,6 +148,133 @@ public: mApplying = false; } + struct SubmitResult + { + /** + * @brief clear Clear all states + */ + void clear() + { + applied = false; + broadcast = false; + queued = false; + kept = false; + } + + /** + * @brief any Get true of any state is true + * @return True if any state if true + */ + bool any() const + { + return applied || broadcast || queued || kept; + } + + bool applied = false; + bool broadcast = false; + bool queued = false; + bool kept = false; + }; + + /** + * @brief getSubmitResult Return submit result + * @return SubmitResult struct + */ + SubmitResult getSubmitResult() const + { + return submitResult_; + } + + /** + * @brief clearSubmitResult Clear all flags in SubmitResult + */ + void clearSubmitResult() + { + submitResult_.clear(); + } + + /** + * @brief setApplied Set this flag once was applied to open ledger + */ + void setApplied() + { + submitResult_.applied = true; + } + + /** + * @brief setQueued Set this flag once was put into heldtxns queue + */ + void setQueued() + { + submitResult_.queued = true; + } + + /** + * @brief setBroadcast Set this flag once was broadcasted via network + */ + void setBroadcast() + { + submitResult_.broadcast = true; + } + + /** + * @brief setKept Set this flag once was put to localtxns queue + */ + void setKept() + { + submitResult_.kept = true; + } + + struct CurrentLedgerState + { + CurrentLedgerState() = delete; + + CurrentLedgerState( + LedgerIndex li, + XRPAmount fee, + std::uint32_t accSeqNext, + std::uint32_t accSeqAvail) + : validatedLedger{li} + , minFeeRequired{fee} + , accountSeqNext{accSeqNext} + , accountSeqAvail{accSeqAvail} + { + } + + LedgerIndex validatedLedger; + XRPAmount minFeeRequired; + std::uint32_t accountSeqNext; + std::uint32_t accountSeqAvail; + }; + + /** + * @brief getCurrentLedgerState Get current ledger state of transaction + * @return Current ledger state + */ + boost::optional + getCurrentLedgerState() const + { + return currentLedgerState_; + } + + /** + * @brief setCurrentLedgerState Set current ledger state of transaction + * @param validatedLedger Number of last validated ledger + * @param fee minimum Fee required for the transaction + * @param accountSeq First valid account sequence in current ledger + * @param availableSeq First available sequence for the transaction + */ + void + setCurrentLedgerState( + LedgerIndex validatedLedger, + XRPAmount fee, + std::uint32_t accountSeq, + std::uint32_t availableSeq) + { + currentLedgerState_.emplace( + validatedLedger, fee, accountSeq, availableSeq); + } + Json::Value getJson (JsonOptions options, bool binary = false) const; static pointer @@ -169,6 +296,11 @@ private: TER mResult = temUNCERTAIN; bool mApplying = false; + /** different ways for transaction to be accepted */ + SubmitResult submitResult_; + + boost::optional currentLedgerState_; + std::shared_ptr mTransaction; Application& mApp; beast::Journal j_; diff --git a/src/ripple/app/misc/TxQ.h b/src/ripple/app/misc/TxQ.h index 0d29b28a7..85265ce48 100644 --- a/src/ripple/app/misc/TxQ.h +++ b/src/ripple/app/misc/TxQ.h @@ -319,6 +319,26 @@ public: Metrics getMetrics(OpenView const& view) const; + struct FeeAndSeq + { + XRPAmount fee; + std::uint32_t accountSeq; + std::uint32_t availableSeq; + }; + + /** + * @brief Returns minimum required fee for tx and two sequences: + * first vaild sequence for this account in current ledger + * and first available sequence for transaction + * @param view current open ledger + * @param tx the transaction + * @return minimum required fee, first sequence in the ledger + * and first available sequence + */ + FeeAndSeq + getTxRequiredFeeAndSeq(OpenView const& view, + std::shared_ptr const& tx) const; + /** Returns information about the transactions currently in the queue for the account. diff --git a/src/ripple/app/misc/impl/TxQ.cpp b/src/ripple/app/misc/impl/TxQ.cpp index 4e474c7b9..70be61d7d 100644 --- a/src/ripple/app/misc/impl/TxQ.cpp +++ b/src/ripple/app/misc/impl/TxQ.cpp @@ -1393,6 +1393,41 @@ TxQ::getMetrics(OpenView const& view) const return result; } +TxQ::FeeAndSeq +TxQ::getTxRequiredFeeAndSeq(OpenView const& view, + std::shared_ptr const& tx) const +{ + auto const account = (*tx)[sfAccount]; + + std::lock_guard lock(mutex_); + + auto const snapshot = feeMetrics_.getSnapshot(); + auto const baseFee = calculateBaseFee(view, *tx); + auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view); + + auto const accountSeq = [&view, &account]() -> std::uint32_t { + auto const sle = view.read(keylet::account(account)); + if (sle) + return (*sle)[sfSequence]; + return 0; + }(); + + auto availableSeq = accountSeq; + + if (auto iter {byAccount_.find(account)}; iter != byAccount_.end()) + { + auto& txQAcct = iter->second; + for (auto const& [seq, _] : txQAcct.transactions) + { + (void)_; + if (seq >= availableSeq) + availableSeq = seq + 1; + } + } + + return {fee * baseFee / baseLevel, accountSeq, availableSeq}; +} + auto TxQ::getAccountTxs(AccountID const& account, ReadView const& view) const -> std::map diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 244e5aada..f7a7f563f 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -100,7 +100,7 @@ JSS ( TransactionType ); // in: TransactionSign. JSS ( TransferRate ); // in: TransferRate. JSS ( TrustSet ); // transaction type. JSS ( aborted ); // out: InboundLedger -JSS ( accepted ); // out: LedgerToJson, OwnerInfo +JSS ( accepted ); // out: LedgerToJson, OwnerInfo, SubmitTransaction JSS ( account ); // in/out: many JSS ( accountState ); // out: LedgerToJson JSS ( accountTreeHash ); // out: ledger/Ledger.cpp @@ -109,6 +109,8 @@ JSS ( account_hash ); // out: LedgerToJson JSS ( account_id ); // out: WalletPropose JSS ( account_objects ); // out: AccountObjects JSS ( account_root ); // in: LedgerEntry +JSS ( account_sequence_next ); // out: SubmitTransaction +JSS ( account_sequence_available ); // out: SubmitTransaction JSS ( accounts ); // in: LedgerEntry, Subscribe, // handlers/Ledger, Unsubscribe JSS ( accounts_proposed ); // in: Subscribe, Unsubscribe @@ -121,6 +123,7 @@ JSS ( alternatives ); // out: PathRequest, RipplePathFind JSS ( amendment_blocked ); // out: NetworkOPs JSS ( amendments ); // in: AccountObjects, out: NetworkOPs JSS ( amount ); // out: AccountChannels +JSS ( applied ); // out: SubmitTransaction JSS ( asks ); // out: Subscribe JSS ( assets ); // out: GatewayBalances JSS ( authorized ); // out: AccountLines @@ -140,6 +143,7 @@ JSS ( binary ); // in: AccountTX, LedgerEntry, JSS ( books ); // in: Subscribe, Unsubscribe JSS ( both ); // in: Subscribe, Unsubscribe JSS ( both_sides ); // in: Subscribe, Unsubscribe +JSS ( broadcast ); // out: SubmitTransaction JSS ( build_path ); // in: TransactionSign JSS ( build_version ); // out: NetworkOPs JSS ( cancel_after ); // out: AccountChannels @@ -271,6 +275,7 @@ JSS ( job_queue ); JSS ( jobs ); JSS ( jsonrpc ); // json version JSS ( jq_trans_overflow ); // JobQueue transaction limit overflow. +JSS ( kept ); // out: SubmitTransaction JSS ( key ); // out JSS ( key_type ); // in/out: WalletPropose, TransactionSign JSS ( latency ); // out: PeerImp @@ -371,6 +376,7 @@ JSS ( offers ); // out: NetworkOPs, AccountOffers, Subscribe JSS ( offline ); // in: TransactionSign JSS ( offset ); // in/out: AccountTxOld JSS ( open ); // out: handlers/Ledger +JSS ( open_ledger_cost ); // out: SubmitTransaction JSS ( open_ledger_fee ); // out: TxQ JSS ( open_ledger_level ); // out: TxQ JSS ( owner ); // in: LedgerEntry, out: NetworkOPs @@ -412,7 +418,7 @@ JSS ( quality_in ); // out: AccountLines JSS ( quality_out ); // out: AccountLines JSS ( queue ); // in: AccountInfo JSS ( queue_data ); // out: AccountInfo -JSS ( queued ); +JSS ( queued ); // out: SubmitTransaction JSS ( queued_duration_us ); JSS ( random ); // out: Random JSS ( raw_meta ); // out: AcceptedLedgerTx @@ -540,6 +546,7 @@ JSS ( validator_list_expires ); // out: NetworkOps, ValidatorList JSS ( validator_list ); // out: NetworkOps, ValidatorList JSS ( validators ); JSS ( validated_ledger ); // out: NetworkOPs +JSS ( validated_ledger_index ); // out: SubmitTransaction JSS ( validated_ledgers ); // out: NetworkOPs JSS ( validation_key ); // out: ValidationCreate, ValidationSeed JSS ( validation_private_key ); // out: ValidationCreate diff --git a/src/ripple/rpc/handlers/Submit.cpp b/src/ripple/rpc/handlers/Submit.cpp index 495bd554c..c1ecd6874 100644 --- a/src/ripple/rpc/handlers/Submit.cpp +++ b/src/ripple/rpc/handlers/Submit.cpp @@ -148,6 +148,26 @@ Json::Value doSubmit (RPC::Context& context) jvResult[jss::engine_result] = sToken; jvResult[jss::engine_result_code] = tpTrans->getResult (); jvResult[jss::engine_result_message] = sHuman; + + auto const submitResult = tpTrans->getSubmitResult(); + + jvResult[jss::accepted] = submitResult.any(); + jvResult[jss::applied] = submitResult.applied; + jvResult[jss::broadcast] = submitResult.broadcast; + jvResult[jss::queued] = submitResult.queued; + jvResult[jss::kept] = submitResult.kept; + + if (auto currentLedgerState = tpTrans->getCurrentLedgerState()) + { + jvResult[jss::account_sequence_next] + = safe_cast(currentLedgerState->accountSeqNext); + jvResult[jss::account_sequence_available] + = safe_cast(currentLedgerState->accountSeqAvail); + jvResult[jss::open_ledger_cost] + = to_string(currentLedgerState->minFeeRequired); + jvResult[jss::validated_ledger_index] + = safe_cast(currentLedgerState->validatedLedger); + } } return jvResult;