rippled
Loading...
Searching...
No Matches
Simulate.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2024 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/ledger/LedgerMaster.h>
21#include <xrpld/app/ledger/OpenLedger.h>
22#include <xrpld/app/misc/HashRouter.h>
23#include <xrpld/app/misc/Transaction.h>
24#include <xrpld/app/misc/TxQ.h>
25#include <xrpld/app/tx/apply.h>
26#include <xrpld/rpc/Context.h>
27#include <xrpld/rpc/DeliveredAmount.h>
28#include <xrpld/rpc/GRPCHandlers.h>
29#include <xrpld/rpc/MPTokenIssuanceID.h>
30#include <xrpld/rpc/detail/TransactionSign.h>
31
32#include <xrpl/protocol/ErrorCodes.h>
33#include <xrpl/protocol/NFTSyntheticSerializer.h>
34#include <xrpl/protocol/RPCErr.h>
35#include <xrpl/protocol/STParsedJSON.h>
36#include <xrpl/resource/Fees.h>
37
38namespace ripple {
39
40static Expected<std::uint32_t, Json::Value>
42{
43 // autofill Sequence
44 bool const hasTicketSeq = tx_json.isMember(sfTicketSequence.jsonName);
45 auto const& accountStr = tx_json[jss::Account];
46 if (!accountStr.isString())
47 {
48 // sanity check, should fail earlier
49 // LCOV_EXCL_START
50 return Unexpected(RPC::invalid_field_error("tx.Account"));
51 // LCOV_EXCL_STOP
52 }
53 auto const srcAddressID = parseBase58<AccountID>(accountStr.asString());
54 if (!srcAddressID.has_value())
55 {
58 }
60 context.app.openLedger().current()->read(
61 keylet::account(*srcAddressID));
62 if (!hasTicketSeq && !sle)
63 {
64 JLOG(context.app.journal("Simulate").debug())
65 << "Failed to find source account "
66 << "in current ledger: " << toBase58(*srcAddressID);
67
69 }
70
71 return hasTicketSeq ? 0 : context.app.getTxQ().nextQueuableSeq(sle).value();
72}
73
76{
77 if (!tx_json.isMember(jss::Fee))
78 {
79 // autofill Fee
80 // Must happen after all the other autofills happen
81 // Error handling/messaging works better that way
82 auto feeOrError = RPC::getCurrentNetworkFee(
83 context.role,
84 context.app.config(),
85 context.app.getFeeTrack(),
86 context.app.getTxQ(),
87 context.app,
88 tx_json);
89 if (feeOrError.isMember(jss::error))
90 return feeOrError;
91 tx_json[jss::Fee] = feeOrError;
92 }
93
94 if (!tx_json.isMember(jss::SigningPubKey))
95 {
96 // autofill SigningPubKey
97 tx_json[jss::SigningPubKey] = "";
98 }
99
100 if (tx_json.isMember(jss::Signers))
101 {
102 if (!tx_json[jss::Signers].isArray())
103 return RPC::invalid_field_error("tx.Signers");
104 // check multisigned signers
105 for (unsigned index = 0; index < tx_json[jss::Signers].size(); index++)
106 {
107 auto& signer = tx_json[jss::Signers][index];
108 if (!signer.isObject() || !signer.isMember(jss::Signer) ||
109 !signer[jss::Signer].isObject())
111 "tx.Signers[" + std::to_string(index) + "]");
112
113 if (!signer[jss::Signer].isMember(jss::SigningPubKey))
114 {
115 // autofill SigningPubKey
116 signer[jss::Signer][jss::SigningPubKey] = "";
117 }
118
119 if (!signer[jss::Signer].isMember(jss::TxnSignature))
120 {
121 // autofill TxnSignature
122 signer[jss::Signer][jss::TxnSignature] = "";
123 }
124 else if (signer[jss::Signer][jss::TxnSignature] != "")
125 {
126 // Transaction must not be signed
127 return rpcError(rpcTX_SIGNED);
128 }
129 }
130 }
131
132 if (!tx_json.isMember(jss::TxnSignature))
133 {
134 // autofill TxnSignature
135 tx_json[jss::TxnSignature] = "";
136 }
137 else if (tx_json[jss::TxnSignature] != "")
138 {
139 // Transaction must not be signed
140 return rpcError(rpcTX_SIGNED);
141 }
142
143 if (!tx_json.isMember(jss::Sequence))
144 {
145 auto const seq = getAutofillSequence(tx_json, context);
146 if (!seq)
147 return seq.error();
148 tx_json[sfSequence.jsonName] = *seq;
149 }
150
151 if (!tx_json.isMember(jss::NetworkID))
152 {
153 auto const networkId = context.app.config().NETWORK_ID;
154 if (networkId > 1024)
155 tx_json[jss::NetworkID] = to_string(networkId);
156 }
157
158 return std::nullopt;
159}
160
161static Json::Value
163{
164 Json::Value tx_json;
165
166 if (params.isMember(jss::tx_blob))
167 {
168 if (params.isMember(jss::tx_json))
169 {
171 "Can only include one of `tx_blob` and `tx_json`.");
172 }
173
174 auto const tx_blob = params[jss::tx_blob];
175 if (!tx_blob.isString())
176 {
177 return RPC::invalid_field_error(jss::tx_blob);
178 }
179
180 auto unHexed = strUnHex(tx_blob.asString());
181 if (!unHexed || unHexed->empty())
182 return RPC::invalid_field_error(jss::tx_blob);
183
184 try
185 {
186 SerialIter sitTrans(makeSlice(*unHexed));
187 tx_json = STObject(std::ref(sitTrans), sfGeneric)
189 }
190 catch (std::runtime_error const&)
191 {
192 return RPC::invalid_field_error(jss::tx_blob);
193 }
194 }
195 else if (params.isMember(jss::tx_json))
196 {
197 tx_json = params[jss::tx_json];
198 if (!tx_json.isObject())
199 {
200 return RPC::object_field_error(jss::tx_json);
201 }
202 }
203 else
204 {
206 "Neither `tx_blob` nor `tx_json` included.");
207 }
208
209 // basic sanity checks for transaction shape
210 if (!tx_json.isMember(jss::TransactionType))
211 {
212 return RPC::missing_field_error("tx.TransactionType");
213 }
214
215 if (!tx_json.isMember(jss::Account))
216 {
217 return RPC::missing_field_error("tx.Account");
218 }
219
220 return tx_json;
221}
222
223static Json::Value
225{
226 Json::Value jvResult;
227 // Process the transaction
228 OpenView view = *context.app.openLedger().current();
229 auto const result = context.app.getTxQ().apply(
230 context.app,
231 view,
232 transaction->getSTransaction(),
234 context.j);
235
236 jvResult[jss::applied] = result.applied;
237 jvResult[jss::ledger_index] = view.seq();
238
239 bool const isBinaryOutput = context.params.get(jss::binary, false).asBool();
240
241 // Convert the TER to human-readable values
242 std::string token;
243 std::string message;
244 if (transResultInfo(result.ter, token, message))
245 {
246 // Engine result
247 jvResult[jss::engine_result] = token;
248 jvResult[jss::engine_result_code] = result.ter;
249 jvResult[jss::engine_result_message] = message;
250 }
251 else
252 {
253 // shouldn't be hit
254 // LCOV_EXCL_START
255 jvResult[jss::engine_result] = "unknown";
256 jvResult[jss::engine_result_code] = result.ter;
257 jvResult[jss::engine_result_message] = "unknown";
258 // LCOV_EXCL_STOP
259 }
260
261 if (token == "tesSUCCESS")
262 {
263 jvResult[jss::engine_result_message] =
264 "The simulated transaction would have been applied.";
265 }
266
267 if (result.metadata)
268 {
269 if (isBinaryOutput)
270 {
271 auto const metaBlob =
272 result.metadata->getAsObject().getSerializer().getData();
273 jvResult[jss::meta_blob] = strHex(makeSlice(metaBlob));
274 }
275 else
276 {
277 jvResult[jss::meta] = result.metadata->getJson(JsonOptions::none);
279 jvResult[jss::meta],
280 view,
281 transaction->getSTransaction(),
282 *result.metadata);
284 jvResult, transaction->getSTransaction(), *result.metadata);
286 jvResult[jss::meta],
287 transaction->getSTransaction(),
288 *result.metadata);
289 }
290 }
291
292 if (isBinaryOutput)
293 {
294 auto const txBlob =
295 transaction->getSTransaction()->getSerializer().getData();
296 jvResult[jss::tx_blob] = strHex(makeSlice(txBlob));
297 }
298 else
299 {
300 jvResult[jss::tx_json] = transaction->getJson(JsonOptions::none);
301 }
302
303 return jvResult;
304}
305
306// {
307// tx_blob: <string> XOR tx_json: <object>,
308// binary: <bool>
309// }
312{
314
315 Json::Value tx_json; // the tx as a JSON
316
317 // check validity of `binary` param
318 if (context.params.isMember(jss::binary) &&
319 !context.params[jss::binary].isBool())
320 {
321 return RPC::invalid_field_error(jss::binary);
322 }
323
324 for (auto const field :
325 {jss::secret, jss::seed, jss::seed_hex, jss::passphrase})
326 {
327 if (context.params.isMember(field))
328 {
329 return RPC::invalid_field_error(field);
330 }
331 }
332
333 // get JSON equivalent of transaction
334 tx_json = getTxJsonFromParams(context.params);
335 if (tx_json.isMember(jss::error))
336 return tx_json;
337
338 // autofill fields if they're not included (e.g. `Fee`, `Sequence`)
339 if (auto error = autofillTx(tx_json, context))
340 return *error;
341
342 STParsedJSONObject parsed(std::string(jss::tx_json), tx_json);
343 if (!parsed.object.has_value())
344 return parsed.error;
345
347 try
348 {
349 stTx = std::make_shared<STTx>(std::move(parsed.object.value()));
350 }
351 catch (std::exception& e)
352 {
354 jvResult[jss::error] = "invalidTransaction";
355 jvResult[jss::error_exception] = e.what();
356 return jvResult;
357 }
358
359 if (stTx->getTxnType() == ttBATCH)
360 {
362 }
363
364 std::string reason;
365 auto transaction = std::make_shared<Transaction>(stTx, reason, context.app);
366 // Actually run the transaction through the transaction processor
367 try
368 {
369 return simulateTxn(context, transaction);
370 }
371 // LCOV_EXCL_START this is just in case, so rippled doesn't crash
372 catch (std::exception const& e)
373 {
375 jvResult[jss::error] = "internalSimulate";
376 jvResult[jss::error_exception] = e.what();
377 return jvResult;
378 }
379 // LCOV_EXCL_STOP
380}
381
382} // namespace ripple
Represents a JSON value.
Definition json_value.h:149
UInt size() const
Number of values in array or object.
bool isObject() const
bool isBool() const
bool asBool() const
bool isMember(char const *key) const
Return true if the object has a member named key.
Value get(UInt index, Value const &defaultValue) const
If the array contains at least index+1 elements, returns the element value, otherwise returns default...
Stream debug() const
Definition Journal.h:328
virtual Config & config()=0
virtual LoadFeeTrack & getFeeTrack()=0
virtual OpenLedger & openLedger()=0
virtual beast::Journal journal(std::string const &name)=0
virtual TxQ & getTxQ()=0
uint32_t NETWORK_ID
Definition Config.h:156
std::shared_ptr< OpenView const > current() const
Returns a view to the current open ledger.
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:65
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:118
Json::Value getJson(JsonOptions=JsonOptions::none) const override
Definition STObject.cpp:825
Holds the serialized result of parsing an input JSON object.
std::optional< STObject > object
The STObject if the parse was successful.
Json::Value error
On failure, an appropriate set of error values.
constexpr std::uint32_t value() const
Definition SeqProxy.h:82
SeqProxy nextQueuableSeq(std::shared_ptr< SLE const > const &sleAccount) const
Return the next sequence that would go in the TxQ for an account.
Definition TxQ.cpp:1609
ApplyResult apply(Application &app, OpenView &view, std::shared_ptr< STTx const > const &tx, ApplyFlags flags, beast::Journal j)
Add a new transaction to the open ledger, hold it in the queue, or reject it.
Definition TxQ.cpp:730
T is_same_v
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:45
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Json::Value invalid_field_error(std::string const &name)
Definition ErrorCodes.h:325
void insertNFTSyntheticInJson(Json::Value &, std::shared_ptr< STTx const > const &, TxMeta const &)
Adds common synthetic fields to transaction-related JSON responses.
Json::Value make_param_error(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition ErrorCodes.h:271
void insertMPTokenIssuanceID(Json::Value &response, std::shared_ptr< STTx const > const &transaction, TxMeta const &transactionMeta)
std::string invalid_field_message(std::string const &name)
Definition ErrorCodes.h:313
Json::Value getCurrentNetworkFee(Role const role, Config const &config, LoadFeeTrack const &feeTrack, TxQ const &txQ, Application const &app, Json::Value const &tx, int mult, int div)
Json::Value object_field_error(std::string const &name)
Definition ErrorCodes.h:301
void insertDeliveredAmount(Json::Value &meta, ReadView const &, std::shared_ptr< STTx const > const &serializedTx, TxMeta const &)
Add a delivered_amount field to the meta input/output parameter.
Json::Value missing_field_error(std::string const &name)
Definition ErrorCodes.h:283
Charge const feeMediumBurdenRPC
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
static std::optional< Json::Value > autofillTx(Json::Value &tx_json, RPC::JsonContext &context)
Definition Simulate.cpp:75
static Expected< std::uint32_t, Json::Value > getAutofillSequence(Json::Value const &tx_json, RPC::JsonContext &context)
Definition Simulate.cpp:41
@ rpcSRC_ACT_NOT_FOUND
Definition ErrorCodes.h:122
@ rpcTX_SIGNED
Definition ErrorCodes.h:155
@ rpcSRC_ACT_MALFORMED
Definition ErrorCodes.h:120
@ rpcNOT_IMPL
Definition ErrorCodes.h:131
Json::Value doSimulate(RPC::JsonContext &)
Definition Simulate.cpp:311
static Json::Value getTxJsonFromParams(Json::Value const &params)
Definition Simulate.cpp:162
Json::Value rpcError(int iError)
Definition RPCErr.cpp:31
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:30
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
Definition Slice.h:244
static Json::Value simulateTxn(RPC::JsonContext &context, std::shared_ptr< Transaction > transaction)
Definition Simulate.cpp:224
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
SField const sfGeneric
@ tapDRY_RUN
Definition ApplyView.h:49
bool transResultInfo(TER code, std::string &token, std::string &text)
Definition TER.cpp:249
T ref(T... args)
Resource::Charge & loadType
Definition Context.h:42
Application & app
Definition Context.h:41
beast::Journal const j
Definition Context.h:40
T to_string(T... args)
T what(T... args)