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