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 (!sigObject.isMember(jss::SigningPubKey))
78 {
79 // autofill SigningPubKey
80 sigObject[jss::SigningPubKey] = "";
81 }
82
83 if (sigObject.isMember(jss::Signers))
84 {
85 if (!sigObject[jss::Signers].isArray())
86 return RPC::invalid_field_error("tx.Signers");
87 // check multisigned signers
88 for (unsigned index = 0; index < sigObject[jss::Signers].size();
89 index++)
90 {
91 auto& signer = sigObject[jss::Signers][index];
92 if (!signer.isObject() || !signer.isMember(jss::Signer) ||
93 !signer[jss::Signer].isObject())
95 "tx.Signers[" + std::to_string(index) + "]");
96
97 if (!signer[jss::Signer].isMember(jss::SigningPubKey))
98 {
99 // autofill SigningPubKey
100 signer[jss::Signer][jss::SigningPubKey] = "";
101 }
102
103 if (!signer[jss::Signer].isMember(jss::TxnSignature))
104 {
105 // autofill TxnSignature
106 signer[jss::Signer][jss::TxnSignature] = "";
107 }
108 else if (signer[jss::Signer][jss::TxnSignature] != "")
109 {
110 // Transaction must not be signed
111 return rpcError(rpcTX_SIGNED);
112 }
113 }
114 }
115
116 if (!sigObject.isMember(jss::TxnSignature))
117 {
118 // autofill TxnSignature
119 sigObject[jss::TxnSignature] = "";
120 }
121 else if (sigObject[jss::TxnSignature] != "")
122 {
123 // Transaction must not be signed
124 return rpcError(rpcTX_SIGNED);
125 }
126 return std::nullopt;
127}
128
131{
132 if (!tx_json.isMember(jss::Fee))
133 {
134 // autofill Fee
135 // Must happen after all the other autofills happen
136 // Error handling/messaging works better that way
137 auto feeOrError = RPC::getCurrentNetworkFee(
138 context.role,
139 context.app.config(),
140 context.app.getFeeTrack(),
141 context.app.getTxQ(),
142 context.app,
143 tx_json);
144 if (feeOrError.isMember(jss::error))
145 return feeOrError;
146 tx_json[jss::Fee] = feeOrError;
147 }
148
149 if (auto error = autofillSignature(tx_json))
150 return *error;
151
152 if (!tx_json.isMember(jss::Sequence))
153 {
154 auto const seq = getAutofillSequence(tx_json, context);
155 if (!seq)
156 return seq.error();
157 tx_json[sfSequence.jsonName] = *seq;
158 }
159
160 if (!tx_json.isMember(jss::NetworkID))
161 {
162 auto const networkId = context.app.config().NETWORK_ID;
163 if (networkId > 1024)
164 tx_json[jss::NetworkID] = to_string(networkId);
165 }
166
167 return std::nullopt;
168}
169
170static Json::Value
172{
173 Json::Value tx_json;
174
175 if (params.isMember(jss::tx_blob))
176 {
177 if (params.isMember(jss::tx_json))
178 {
180 "Can only include one of `tx_blob` and `tx_json`.");
181 }
182
183 auto const tx_blob = params[jss::tx_blob];
184 if (!tx_blob.isString())
185 {
186 return RPC::invalid_field_error(jss::tx_blob);
187 }
188
189 auto unHexed = strUnHex(tx_blob.asString());
190 if (!unHexed || unHexed->empty())
191 return RPC::invalid_field_error(jss::tx_blob);
192
193 try
194 {
195 SerialIter sitTrans(makeSlice(*unHexed));
196 tx_json = STObject(std::ref(sitTrans), sfGeneric)
198 }
199 catch (std::runtime_error const&)
200 {
201 return RPC::invalid_field_error(jss::tx_blob);
202 }
203 }
204 else if (params.isMember(jss::tx_json))
205 {
206 tx_json = params[jss::tx_json];
207 if (!tx_json.isObject())
208 {
209 return RPC::object_field_error(jss::tx_json);
210 }
211 }
212 else
213 {
215 "Neither `tx_blob` nor `tx_json` included.");
216 }
217
218 // basic sanity checks for transaction shape
219 if (!tx_json.isMember(jss::TransactionType))
220 {
221 return RPC::missing_field_error("tx.TransactionType");
222 }
223
224 if (!tx_json.isMember(jss::Account))
225 {
226 return RPC::missing_field_error("tx.Account");
227 }
228
229 return tx_json;
230}
231
232static Json::Value
234{
235 Json::Value jvResult;
236 // Process the transaction
237 OpenView view = *context.app.openLedger().current();
238 auto const result = context.app.getTxQ().apply(
239 context.app,
240 view,
241 transaction->getSTransaction(),
243 context.j);
244
245 jvResult[jss::applied] = result.applied;
246 jvResult[jss::ledger_index] = view.seq();
247
248 bool const isBinaryOutput = context.params.get(jss::binary, false).asBool();
249
250 // Convert the TER to human-readable values
251 std::string token;
252 std::string message;
253 if (transResultInfo(result.ter, token, message))
254 {
255 // Engine result
256 jvResult[jss::engine_result] = token;
257 jvResult[jss::engine_result_code] = result.ter;
258 jvResult[jss::engine_result_message] = message;
259 }
260 else
261 {
262 // shouldn't be hit
263 // LCOV_EXCL_START
264 jvResult[jss::engine_result] = "unknown";
265 jvResult[jss::engine_result_code] = result.ter;
266 jvResult[jss::engine_result_message] = "unknown";
267 // LCOV_EXCL_STOP
268 }
269
270 if (token == "tesSUCCESS")
271 {
272 jvResult[jss::engine_result_message] =
273 "The simulated transaction would have been applied.";
274 }
275
276 if (result.metadata)
277 {
278 if (isBinaryOutput)
279 {
280 auto const metaBlob =
281 result.metadata->getAsObject().getSerializer().getData();
282 jvResult[jss::meta_blob] = strHex(makeSlice(metaBlob));
283 }
284 else
285 {
286 jvResult[jss::meta] = result.metadata->getJson(JsonOptions::none);
288 jvResult[jss::meta],
289 view,
290 transaction->getSTransaction(),
291 *result.metadata);
293 jvResult, transaction->getSTransaction(), *result.metadata);
295 jvResult[jss::meta],
296 transaction->getSTransaction(),
297 *result.metadata);
298 }
299 }
300
301 if (isBinaryOutput)
302 {
303 auto const txBlob =
304 transaction->getSTransaction()->getSerializer().getData();
305 jvResult[jss::tx_blob] = strHex(makeSlice(txBlob));
306 }
307 else
308 {
309 jvResult[jss::tx_json] = transaction->getJson(JsonOptions::none);
310 }
311
312 return jvResult;
313}
314
315// {
316// tx_blob: <string> XOR tx_json: <object>,
317// binary: <bool>
318// }
321{
323
324 Json::Value tx_json; // the tx as a JSON
325
326 // check validity of `binary` param
327 if (context.params.isMember(jss::binary) &&
328 !context.params[jss::binary].isBool())
329 {
330 return RPC::invalid_field_error(jss::binary);
331 }
332
333 for (auto const field :
334 {jss::secret, jss::seed, jss::seed_hex, jss::passphrase})
335 {
336 if (context.params.isMember(field))
337 {
338 return RPC::invalid_field_error(field);
339 }
340 }
341
342 // get JSON equivalent of transaction
343 tx_json = getTxJsonFromParams(context.params);
344 if (tx_json.isMember(jss::error))
345 return tx_json;
346
347 // autofill fields if they're not included (e.g. `Fee`, `Sequence`)
348 if (auto error = autofillTx(tx_json, context))
349 return *error;
350
351 STParsedJSONObject parsed(std::string(jss::tx_json), tx_json);
352 if (!parsed.object.has_value())
353 return parsed.error;
354
356 try
357 {
358 stTx = std::make_shared<STTx>(std::move(parsed.object.value()));
359 }
360 catch (std::exception& e)
361 {
363 jvResult[jss::error] = "invalidTransaction";
364 jvResult[jss::error_exception] = e.what();
365 return jvResult;
366 }
367
368 if (stTx->getTxnType() == ttBATCH)
369 {
371 }
372
373 std::string reason;
374 auto transaction = std::make_shared<Transaction>(stTx, reason, context.app);
375 // Actually run the transaction through the transaction processor
376 try
377 {
378 return simulateTxn(context, transaction);
379 }
380 // LCOV_EXCL_START this is just in case, so rippled doesn't crash
381 catch (std::exception const& e)
382 {
384 jvResult[jss::error] = "internalSimulate";
385 jvResult[jss::error_exception] = e.what();
386 return jvResult;
387 }
388 // LCOV_EXCL_STOP
389}
390
391} // 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:853
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:1607
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:729
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:130
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:320
static Json::Value getTxJsonFromParams(Json::Value const &params)
Definition Simulate.cpp:171
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:233
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
SField const sfGeneric
static std::optional< Json::Value > autofillSignature(Json::Value &sigObject)
Definition Simulate.cpp:75
@ 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)