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 return std::nullopt;
148}
149
150static Json::Value
152{
153 Json::Value tx_json;
154
155 if (params.isMember(jss::tx_blob))
156 {
157 if (params.isMember(jss::tx_json))
158 {
160 "Can only include one of `tx_blob` and `tx_json`.");
161 }
162
163 auto const tx_blob = params[jss::tx_blob];
164 if (!tx_blob.isString())
165 {
166 return RPC::invalid_field_error(jss::tx_blob);
167 }
168
169 auto unHexed = strUnHex(tx_blob.asString());
170 if (!unHexed || unHexed->empty())
171 return RPC::invalid_field_error(jss::tx_blob);
172
173 try
174 {
175 SerialIter sitTrans(makeSlice(*unHexed));
176 tx_json = STObject(std::ref(sitTrans), sfGeneric)
178 }
179 catch (std::runtime_error const&)
180 {
181 return RPC::invalid_field_error(jss::tx_blob);
182 }
183 }
184 else if (params.isMember(jss::tx_json))
185 {
186 tx_json = params[jss::tx_json];
187 if (!tx_json.isObject())
188 {
189 return RPC::object_field_error(jss::tx_json);
190 }
191 }
192 else
193 {
195 "Neither `tx_blob` nor `tx_json` included.");
196 }
197
198 // basic sanity checks for transaction shape
199 if (!tx_json.isMember(jss::TransactionType))
200 {
201 return RPC::missing_field_error("tx.TransactionType");
202 }
203
204 if (!tx_json.isMember(jss::Account))
205 {
206 return RPC::missing_field_error("tx.Account");
207 }
208
209 return tx_json;
210}
211
212static Json::Value
214{
215 Json::Value jvResult;
216 // Process the transaction
217 OpenView view = *context.app.openLedger().current();
218 auto const result = context.app.getTxQ().apply(
219 context.app,
220 view,
221 transaction->getSTransaction(),
223 context.j);
224
225 jvResult[jss::applied] = result.applied;
226 jvResult[jss::ledger_index] = view.seq();
227
228 const bool isBinaryOutput = context.params.get(jss::binary, false).asBool();
229
230 // Convert the TER to human-readable values
231 std::string token;
232 std::string message;
233 if (transResultInfo(result.ter, token, message))
234 {
235 // Engine result
236 jvResult[jss::engine_result] = token;
237 jvResult[jss::engine_result_code] = result.ter;
238 jvResult[jss::engine_result_message] = message;
239 }
240 else
241 {
242 // shouldn't be hit
243 // LCOV_EXCL_START
244 jvResult[jss::engine_result] = "unknown";
245 jvResult[jss::engine_result_code] = result.ter;
246 jvResult[jss::engine_result_message] = "unknown";
247 // LCOV_EXCL_STOP
248 }
249
250 if (token == "tesSUCCESS")
251 {
252 jvResult[jss::engine_result_message] =
253 "The simulated transaction would have been applied.";
254 }
255
256 if (result.metadata)
257 {
258 if (isBinaryOutput)
259 {
260 auto const metaBlob =
261 result.metadata->getAsObject().getSerializer().getData();
262 jvResult[jss::meta_blob] = strHex(makeSlice(metaBlob));
263 }
264 else
265 {
266 jvResult[jss::meta] = result.metadata->getJson(JsonOptions::none);
267 }
268 }
269
270 if (isBinaryOutput)
271 {
272 auto const txBlob =
273 transaction->getSTransaction()->getSerializer().getData();
274 jvResult[jss::tx_blob] = strHex(makeSlice(txBlob));
275 }
276 else
277 {
278 jvResult[jss::tx_json] = transaction->getJson(JsonOptions::none);
279 }
280
281 return jvResult;
282}
283
284// {
285// tx_blob: <string> XOR tx_json: <object>,
286// binary: <bool>
287// }
290{
292
293 Json::Value tx_json; // the tx as a JSON
294
295 // check validity of `binary` param
296 if (context.params.isMember(jss::binary) &&
297 !context.params[jss::binary].isBool())
298 {
299 return RPC::invalid_field_error(jss::binary);
300 }
301
302 // get JSON equivalent of transaction
303 tx_json = getTxJsonFromParams(context.params);
304 if (tx_json.isMember(jss::error))
305 return tx_json;
306
307 // autofill fields if they're not included (e.g. `Fee`, `Sequence`)
308 if (auto error = autofillTx(tx_json, context))
309 return *error;
310
311 STParsedJSONObject parsed(std::string(jss::tx_json), tx_json);
312 if (!parsed.object.has_value())
313 return parsed.error;
314
316 try
317 {
318 stTx = std::make_shared<STTx>(std::move(parsed.object.value()));
319 }
320 catch (std::exception& e)
321 {
323 jvResult[jss::error] = "invalidTransaction";
324 jvResult[jss::error_exception] = e.what();
325 return jvResult;
326 }
327
328 std::string reason;
329 auto transaction = std::make_shared<Transaction>(stTx, reason, context.app);
330 // Actually run the transaction through the transaction processor
331 try
332 {
333 return simulateTxn(context, transaction);
334 }
335 // LCOV_EXCL_START this is just in case, so rippled doesn't crash
336 catch (std::exception const& e)
337 {
339 jvResult[jss::error] = "internalSimulate";
340 jvResult[jss::error_exception] = e.what();
341 return jvResult;
342 }
343 // LCOV_EXCL_STOP
344}
345
346} // 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:317
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
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:289
SField const sfGeneric
static Json::Value getTxJsonFromParams(Json::Value const &params)
Definition: Simulate.cpp:151
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:213
@ 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)