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/GRPCHandlers.h>
28#include <xrpld/rpc/detail/TransactionSign.h>
29
30#include <xrpl/protocol/ErrorCodes.h>
31#include <xrpl/protocol/RPCErr.h>
32#include <xrpl/protocol/STParsedJSON.h>
33#include <xrpl/resource/Fees.h>
34
35namespace ripple {
36
37static Expected<std::uint32_t, Json::Value>
39{
40 // autofill Sequence
41 bool const hasTicketSeq = tx_json.isMember(sfTicketSequence.jsonName);
42 auto const& accountStr = tx_json[jss::Account];
43 if (!accountStr.isString())
44 {
45 // sanity check, should fail earlier
46 // LCOV_EXCL_START
47 return Unexpected(RPC::invalid_field_error("tx.Account"));
48 // LCOV_EXCL_STOP
49 }
50 auto const srcAddressID = parseBase58<AccountID>(accountStr.asString());
51 if (!srcAddressID.has_value())
52 {
55 }
57 context.app.openLedger().current()->read(
58 keylet::account(*srcAddressID));
59 if (!hasTicketSeq && !sle)
60 {
61 JLOG(context.app.journal("Simulate").debug())
62 << "Failed to find source account "
63 << "in current ledger: " << toBase58(*srcAddressID);
64
66 }
67
68 return hasTicketSeq ? 0 : context.app.getTxQ().nextQueuableSeq(sle).value();
69}
70
73{
74 if (!tx_json.isMember(jss::Fee))
75 {
76 // autofill Fee
77 // Must happen after all the other autofills happen
78 // Error handling/messaging works better that way
79 auto feeOrError = RPC::getCurrentNetworkFee(
80 context.role,
81 context.app.config(),
82 context.app.getFeeTrack(),
83 context.app.getTxQ(),
84 context.app,
85 tx_json);
86 if (feeOrError.isMember(jss::error))
87 return feeOrError;
88 tx_json[jss::Fee] = feeOrError;
89 }
90
91 if (!tx_json.isMember(jss::SigningPubKey))
92 {
93 // autofill SigningPubKey
94 tx_json[jss::SigningPubKey] = "";
95 }
96
97 if (tx_json.isMember(jss::Signers))
98 {
99 if (!tx_json[jss::Signers].isArray())
100 return RPC::invalid_field_error("tx.Signers");
101 // check multisigned signers
102 for (unsigned index = 0; index < tx_json[jss::Signers].size(); index++)
103 {
104 auto& signer = tx_json[jss::Signers][index];
105 if (!signer.isObject() || !signer.isMember(jss::Signer) ||
106 !signer[jss::Signer].isObject())
108 "tx.Signers[" + std::to_string(index) + "]");
109
110 if (!signer[jss::Signer].isMember(jss::SigningPubKey))
111 {
112 // autofill SigningPubKey
113 signer[jss::Signer][jss::SigningPubKey] = "";
114 }
115
116 if (!signer[jss::Signer].isMember(jss::TxnSignature))
117 {
118 // autofill TxnSignature
119 signer[jss::Signer][jss::TxnSignature] = "";
120 }
121 else if (signer[jss::Signer][jss::TxnSignature] != "")
122 {
123 // Transaction must not be signed
124 return rpcError(rpcTX_SIGNED);
125 }
126 }
127 }
128
129 if (!tx_json.isMember(jss::TxnSignature))
130 {
131 // autofill TxnSignature
132 tx_json[jss::TxnSignature] = "";
133 }
134 else if (tx_json[jss::TxnSignature] != "")
135 {
136 // Transaction must not be signed
137 return rpcError(rpcTX_SIGNED);
138 }
139
140 if (!tx_json.isMember(jss::Sequence))
141 {
142 auto const seq = getAutofillSequence(tx_json, context);
143 if (!seq)
144 return seq.error();
145 tx_json[sfSequence.jsonName] = *seq;
146 }
147
148 if (!tx_json.isMember(jss::NetworkID))
149 {
150 auto const networkId = context.app.config().NETWORK_ID;
151 if (networkId > 1024)
152 tx_json[jss::NetworkID] = to_string(networkId);
153 }
154
155 return std::nullopt;
156}
157
158static Json::Value
160{
161 Json::Value tx_json;
162
163 if (params.isMember(jss::tx_blob))
164 {
165 if (params.isMember(jss::tx_json))
166 {
168 "Can only include one of `tx_blob` and `tx_json`.");
169 }
170
171 auto const tx_blob = params[jss::tx_blob];
172 if (!tx_blob.isString())
173 {
174 return RPC::invalid_field_error(jss::tx_blob);
175 }
176
177 auto unHexed = strUnHex(tx_blob.asString());
178 if (!unHexed || unHexed->empty())
179 return RPC::invalid_field_error(jss::tx_blob);
180
181 try
182 {
183 SerialIter sitTrans(makeSlice(*unHexed));
184 tx_json = STObject(std::ref(sitTrans), sfGeneric)
186 }
187 catch (std::runtime_error const&)
188 {
189 return RPC::invalid_field_error(jss::tx_blob);
190 }
191 }
192 else if (params.isMember(jss::tx_json))
193 {
194 tx_json = params[jss::tx_json];
195 if (!tx_json.isObject())
196 {
197 return RPC::object_field_error(jss::tx_json);
198 }
199 }
200 else
201 {
203 "Neither `tx_blob` nor `tx_json` included.");
204 }
205
206 // basic sanity checks for transaction shape
207 if (!tx_json.isMember(jss::TransactionType))
208 {
209 return RPC::missing_field_error("tx.TransactionType");
210 }
211
212 if (!tx_json.isMember(jss::Account))
213 {
214 return RPC::missing_field_error("tx.Account");
215 }
216
217 return tx_json;
218}
219
220static Json::Value
222{
223 Json::Value jvResult;
224 // Process the transaction
225 OpenView view = *context.app.openLedger().current();
226 auto const result = context.app.getTxQ().apply(
227 context.app,
228 view,
229 transaction->getSTransaction(),
231 context.j);
232
233 jvResult[jss::applied] = result.applied;
234 jvResult[jss::ledger_index] = view.seq();
235
236 bool const isBinaryOutput = context.params.get(jss::binary, false).asBool();
237
238 // Convert the TER to human-readable values
239 std::string token;
240 std::string message;
241 if (transResultInfo(result.ter, token, message))
242 {
243 // Engine result
244 jvResult[jss::engine_result] = token;
245 jvResult[jss::engine_result_code] = result.ter;
246 jvResult[jss::engine_result_message] = message;
247 }
248 else
249 {
250 // shouldn't be hit
251 // LCOV_EXCL_START
252 jvResult[jss::engine_result] = "unknown";
253 jvResult[jss::engine_result_code] = result.ter;
254 jvResult[jss::engine_result_message] = "unknown";
255 // LCOV_EXCL_STOP
256 }
257
258 if (token == "tesSUCCESS")
259 {
260 jvResult[jss::engine_result_message] =
261 "The simulated transaction would have been applied.";
262 }
263
264 if (result.metadata)
265 {
266 if (isBinaryOutput)
267 {
268 auto const metaBlob =
269 result.metadata->getAsObject().getSerializer().getData();
270 jvResult[jss::meta_blob] = strHex(makeSlice(metaBlob));
271 }
272 else
273 {
274 jvResult[jss::meta] = result.metadata->getJson(JsonOptions::none);
275 }
276 }
277
278 if (isBinaryOutput)
279 {
280 auto const txBlob =
281 transaction->getSTransaction()->getSerializer().getData();
282 jvResult[jss::tx_blob] = strHex(makeSlice(txBlob));
283 }
284 else
285 {
286 jvResult[jss::tx_json] = transaction->getJson(JsonOptions::none);
287 }
288
289 return jvResult;
290}
291
292// {
293// tx_blob: <string> XOR tx_json: <object>,
294// binary: <bool>
295// }
298{
300
301 Json::Value tx_json; // the tx as a JSON
302
303 // check validity of `binary` param
304 if (context.params.isMember(jss::binary) &&
305 !context.params[jss::binary].isBool())
306 {
307 return RPC::invalid_field_error(jss::binary);
308 }
309
310 for (auto const field :
311 {jss::secret, jss::seed, jss::seed_hex, jss::passphrase})
312 {
313 if (context.params.isMember(field))
314 {
315 return RPC::invalid_field_error(field);
316 }
317 }
318
319 // get JSON equivalent of transaction
320 tx_json = getTxJsonFromParams(context.params);
321 if (tx_json.isMember(jss::error))
322 return tx_json;
323
324 // autofill fields if they're not included (e.g. `Fee`, `Sequence`)
325 if (auto error = autofillTx(tx_json, context))
326 return *error;
327
328 STParsedJSONObject parsed(std::string(jss::tx_json), tx_json);
329 if (!parsed.object.has_value())
330 return parsed.error;
331
333 try
334 {
335 stTx = std::make_shared<STTx>(std::move(parsed.object.value()));
336 }
337 catch (std::exception& e)
338 {
340 jvResult[jss::error] = "invalidTransaction";
341 jvResult[jss::error_exception] = e.what();
342 return jvResult;
343 }
344
345 if (stTx->getTxnType() == ttBATCH)
346 {
348 }
349
350 std::string reason;
351 auto transaction = std::make_shared<Transaction>(stTx, reason, context.app);
352 // Actually run the transaction through the transaction processor
353 try
354 {
355 return simulateTxn(context, transaction);
356 }
357 // LCOV_EXCL_START this is just in case, so rippled doesn't crash
358 catch (std::exception const& e)
359 {
361 jvResult[jss::error] = "internalSimulate";
362 jvResult[jss::error_exception] = e.what();
363 return jvResult;
364 }
365 // LCOV_EXCL_STOP
366}
367
368} // namespace ripple
Represents a JSON value.
Definition: json_value.h:149
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:719
bool isObject() const
bool isBool() const
bool asBool() const
Definition: json_value.cpp:632
bool isMember(char const *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:962
Value get(UInt index, Value const &defaultValue) const
If the array contains at least index+1 elements, returns the element value, otherwise returns default...
Definition: json_value.cpp:854
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.
Definition: OpenLedger.cpp:51
Writable ledger view that accumulates state and tx changes.
Definition: OpenView.h:66
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition: ReadView.h:119
Json::Value getJson(JsonOptions=JsonOptions::none) const override
Definition: STObject.cpp:825
Holds the serialized result of parsing an input JSON object.
Definition: STParsedJSON.h:33
std::optional< STObject > object
The STObject if the parse was successful.
Definition: STParsedJSON.h:51
Json::Value error
On failure, an appropriate set of error values.
Definition: STParsedJSON.h:54
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
@ 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.
Definition: ErrorCodes.cpp:188
Json::Value invalid_field_error(std::string const &name)
Definition: ErrorCodes.h:320
Json::Value make_param_error(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition: ErrorCodes.h:266
std::string invalid_field_message(std::string const &name)
Definition: ErrorCodes.h:308
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:296
Json::Value missing_field_error(std::string const &name)
Definition: ErrorCodes.h:278
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.
Definition: AccountID.cpp:114
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:72
static Expected< std::uint32_t, Json::Value > getAutofillSequence(Json::Value const &tx_json, RPC::JsonContext &context)
Definition: Simulate.cpp:38
@ 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:297
SField const sfGeneric
static Json::Value getTxJsonFromParams(Json::Value const &params)
Definition: Simulate.cpp:159
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:221
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
@ tapDRY_RUN
Definition: ApplyView.h:50
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
Json::Value params
Definition: Context.h:63
T to_string(T... args)
T what(T... args)