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