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/Transaction.h>
4#include <xrpld/app/misc/TxQ.h>
5#include <xrpld/rpc/Context.h>
6#include <xrpld/rpc/DeliveredAmount.h>
7#include <xrpld/rpc/GRPCHandlers.h>
8#include <xrpld/rpc/MPTokenIssuanceID.h>
9#include <xrpld/rpc/detail/TransactionSign.h>
10
11#include <xrpl/core/HashRouter.h>
12#include <xrpl/core/NetworkIDService.h>
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#include <xrpl/tx/apply.h>
19
20namespace xrpl {
21
22static Expected<std::uint32_t, Json::Value>
24{
25 // autofill Sequence
26 bool const hasTicketSeq = tx_json.isMember(sfTicketSequence.jsonName);
27 auto const& accountStr = tx_json[jss::Account];
28 if (!accountStr.isString())
29 {
30 // sanity check, should fail earlier
31 // LCOV_EXCL_START
32 return Unexpected(RPC::invalid_field_error("tx.Account"));
33 // LCOV_EXCL_STOP
34 }
35 auto const srcAddressID = parseBase58<AccountID>(accountStr.asString());
36 if (!srcAddressID.has_value())
37 {
38 return Unexpected(
40 }
42 context.app.getOpenLedger().current()->read(keylet::account(*srcAddressID));
43 if (!hasTicketSeq && !sle)
44 {
45 JLOG(context.app.getJournal("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(); index++)
70 {
71 auto& signer = sigObject[jss::Signers][index];
72 if (!signer.isObject() || !signer.isMember(jss::Signer) ||
73 !signer[jss::Signer].isObject())
74 return RPC::invalid_field_error("tx.Signers[" + std::to_string(index) + "]");
75
76 if (!signer[jss::Signer].isMember(jss::SigningPubKey))
77 {
78 // autofill SigningPubKey
79 signer[jss::Signer][jss::SigningPubKey] = "";
80 }
81
82 if (!signer[jss::Signer].isMember(jss::TxnSignature))
83 {
84 // autofill TxnSignature
85 signer[jss::Signer][jss::TxnSignature] = "";
86 }
87 else if (signer[jss::Signer][jss::TxnSignature] != "")
88 {
89 // Transaction must not be signed
90 return rpcError(rpcTX_SIGNED);
91 }
92 }
93 }
94
95 if (!sigObject.isMember(jss::TxnSignature))
96 {
97 // autofill TxnSignature
98 sigObject[jss::TxnSignature] = "";
99 }
100 else if (sigObject[jss::TxnSignature] != "")
101 {
102 // Transaction must not be signed
103 return rpcError(rpcTX_SIGNED);
104 }
105 return std::nullopt;
106}
107
110{
111 if (!tx_json.isMember(jss::Fee))
112 {
113 // autofill Fee
114 // Must happen after all the other autofills happen
115 // Error handling/messaging works better that way
116 auto feeOrError = RPC::getCurrentNetworkFee(
117 context.role,
118 context.app.config(),
119 context.app.getFeeTrack(),
120 context.app.getTxQ(),
121 context.app,
122 tx_json);
123 if (feeOrError.isMember(jss::error))
124 return feeOrError;
125 tx_json[jss::Fee] = feeOrError;
126 }
127
128 if (auto error = autofillSignature(tx_json))
129 return error;
130
131 if (!tx_json.isMember(jss::Sequence))
132 {
133 auto const seq = getAutofillSequence(tx_json, context);
134 if (!seq)
135 return seq.error();
136 tx_json[sfSequence.jsonName] = *seq;
137 }
138
139 if (!tx_json.isMember(jss::NetworkID))
140 {
141 auto const networkId = context.app.getNetworkIDService().getNetworkID();
142 if (networkId > 1024)
143 tx_json[jss::NetworkID] = to_string(networkId);
144 }
145
146 return std::nullopt;
147}
148
149static Json::Value
151{
152 Json::Value tx_json;
153
154 if (params.isMember(jss::tx_blob))
155 {
156 if (params.isMember(jss::tx_json))
157 {
158 return RPC::make_param_error("Can only include one of `tx_blob` and `tx_json`.");
159 }
160
161 auto const tx_blob = params[jss::tx_blob];
162 if (!tx_blob.isString())
163 {
164 return RPC::invalid_field_error(jss::tx_blob);
165 }
166
167 auto unHexed = strUnHex(tx_blob.asString());
168 if (!unHexed || unHexed->empty())
169 return RPC::invalid_field_error(jss::tx_blob);
170
171 try
172 {
173 SerialIter sitTrans(makeSlice(*unHexed));
174 tx_json = STObject(std::ref(sitTrans), sfGeneric).getJson(JsonOptions::none);
175 }
176 catch (std::runtime_error const&)
177 {
178 return RPC::invalid_field_error(jss::tx_blob);
179 }
180 }
181 else if (params.isMember(jss::tx_json))
182 {
183 tx_json = params[jss::tx_json];
184 if (!tx_json.isObject())
185 {
186 return RPC::object_field_error(jss::tx_json);
187 }
188 }
189 else
190 {
191 return RPC::make_param_error("Neither `tx_blob` nor `tx_json` included.");
192 }
193
194 // basic sanity checks for transaction shape
195 if (!tx_json.isMember(jss::TransactionType))
196 {
197 return RPC::missing_field_error("tx.TransactionType");
198 }
199
200 if (!tx_json.isMember(jss::Account))
201 {
202 return RPC::missing_field_error("tx.Account");
203 }
204
205 return tx_json;
206}
207
208static Json::Value
210{
211 Json::Value jvResult;
212 // Process the transaction
213 OpenView view = *context.app.getOpenLedger().current();
214 auto const result = context.app.getTxQ().apply(
215 context.app, view, transaction->getSTransaction(), tapDRY_RUN, context.j);
216
217 jvResult[jss::applied] = result.applied;
218 jvResult[jss::ledger_index] = view.seq();
219
220 bool const isBinaryOutput = context.params.get(jss::binary, false).asBool();
221
222 // Convert the TER to human-readable values
223 std::string token;
224 std::string message;
225 if (transResultInfo(result.ter, token, message))
226 {
227 // Engine result
228 jvResult[jss::engine_result] = token;
229 jvResult[jss::engine_result_code] = result.ter;
230 jvResult[jss::engine_result_message] = message;
231 }
232 else
233 {
234 // shouldn't be hit
235 // LCOV_EXCL_START
236 jvResult[jss::engine_result] = "unknown";
237 jvResult[jss::engine_result_code] = result.ter;
238 jvResult[jss::engine_result_message] = "unknown";
239 // LCOV_EXCL_STOP
240 }
241
242 if (token == "tesSUCCESS")
243 {
244 jvResult[jss::engine_result_message] = "The simulated transaction would have been applied.";
245 }
246
247 if (result.metadata)
248 {
249 if (isBinaryOutput)
250 {
251 auto const metaBlob = result.metadata->getAsObject().getSerializer().getData();
252 jvResult[jss::meta_blob] = strHex(makeSlice(metaBlob));
253 }
254 else
255 {
256 jvResult[jss::meta] = result.metadata->getJson(JsonOptions::none);
258 jvResult[jss::meta], view, transaction->getSTransaction(), *result.metadata);
260 jvResult, transaction->getSTransaction(), *result.metadata);
262 jvResult[jss::meta], transaction->getSTransaction(), *result.metadata);
263 }
264 }
265
266 if (isBinaryOutput)
267 {
268 auto const txBlob = transaction->getSTransaction()->getSerializer().getData();
269 jvResult[jss::tx_blob] = strHex(makeSlice(txBlob));
270 }
271 else
272 {
273 jvResult[jss::tx_json] = transaction->getJson(JsonOptions::none);
274 }
275
276 return jvResult;
277}
278
279// {
280// tx_blob: <string> XOR tx_json: <object>,
281// binary: <bool>
282// }
285{
287
288 Json::Value tx_json; // the tx as a JSON
289
290 // check validity of `binary` param
291 if (context.params.isMember(jss::binary) && !context.params[jss::binary].isBool())
292 {
293 return RPC::invalid_field_error(jss::binary);
294 }
295
296 for (auto const field : {jss::secret, jss::seed, jss::seed_hex, jss::passphrase})
297 {
298 if (context.params.isMember(field))
299 {
300 return RPC::invalid_field_error(field);
301 }
302 }
303
304 // get JSON equivalent of transaction
305 tx_json = getTxJsonFromParams(context.params);
306 if (tx_json.isMember(jss::error))
307 return tx_json;
308
309 // autofill fields if they're not included (e.g. `Fee`, `Sequence`)
310 if (auto error = autofillTx(tx_json, context))
311 return *error;
312
313 STParsedJSONObject parsed(std::string(jss::tx_json), tx_json);
314 if (!parsed.object.has_value())
315 return parsed.error;
316
318 try
319 {
320 stTx = std::make_shared<STTx>(std::move(parsed.object.value()));
321 }
322 catch (std::exception& e)
323 {
325 jvResult[jss::error] = "invalidTransaction";
326 jvResult[jss::error_exception] = e.what();
327 return jvResult;
328 }
329
330 if (stTx->getTxnType() == ttBATCH)
331 {
333 }
334
335 std::string reason;
336 auto transaction = std::make_shared<Transaction>(stTx, reason, context.app);
337 // Actually run the transaction through the transaction processor
338 try
339 {
340 return simulateTxn(context, transaction);
341 }
342 // LCOV_EXCL_START this is just in case, so rippled doesn't crash
343 catch (std::exception const& e)
344 {
346 jvResult[jss::error] = "internalSimulate";
347 jvResult[jss::error_exception] = e.what();
348 return jvResult;
349 }
350 // LCOV_EXCL_STOP
351}
352
353} // namespace xrpl
Represents a JSON value.
Definition json_value.h:130
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:301
virtual Config & config()=0
virtual std::uint32_t getNetworkID() const noexcept=0
Get the configured network ID.
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:45
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:97
Json::Value getJson(JsonOptions=JsonOptions::none) const override
Definition STObject.cpp:831
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:62
virtual TxQ & getTxQ()=0
virtual beast::Journal getJournal(std::string const &name)=0
virtual OpenLedger & getOpenLedger()=0
virtual NetworkIDService & getNetworkIDService()=0
virtual LoadFeeTrack & getFeeTrack()=0
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:686
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:1530
T is_same_v
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:26
Json::Value invalid_field_error(std::string const &name)
Definition ErrorCodes.h:273
Json::Value missing_field_error(std::string const &name)
Definition ErrorCodes.h:231
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:249
void insertMPTokenIssuanceID(Json::Value &response, std::shared_ptr< STTx const > const &transaction, TxMeta const &transactionMeta)
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 make_param_error(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition ErrorCodes.h:219
void insertNFTSyntheticInJson(Json::Value &, std::shared_ptr< STTx const > const &, TxMeta const &)
Adds common synthetic fields to transaction-related JSON responses.
std::string invalid_field_message(std::string const &name)
Definition ErrorCodes.h:261
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
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:5
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
static std::optional< Json::Value > autofillTx(Json::Value &tx_json, RPC::JsonContext &context)
Definition Simulate.cpp:109
SField const sfGeneric
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:92
bool transResultInfo(TER code, std::string &token, std::string &text)
Definition TER.cpp:228
static Json::Value simulateTxn(RPC::JsonContext &context, std::shared_ptr< Transaction > transaction)
Definition Simulate.cpp:209
static Expected< std::uint32_t, Json::Value > getAutofillSequence(Json::Value const &tx_json, RPC::JsonContext &context)
Definition Simulate.cpp:23
static std::optional< Json::Value > autofillSignature(Json::Value &sigObject)
Definition Simulate.cpp:56
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
static Json::Value getTxJsonFromParams(Json::Value const &params)
Definition Simulate.cpp:150
Json::Value rpcError(error_code_i iError)
Definition RPCErr.cpp:12
@ tapDRY_RUN
Definition ApplyView.h:29
Json::Value doSimulate(RPC::JsonContext &)
Definition Simulate.cpp:284
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:215
@ rpcTX_SIGNED
Definition ErrorCodes.h:135
@ rpcNOT_IMPL
Definition ErrorCodes.h:111
@ rpcSRC_ACT_NOT_FOUND
Definition ErrorCodes.h:102
@ rpcSRC_ACT_MALFORMED
Definition ErrorCodes.h:100
T ref(T... args)
beast::Journal const j
Definition Context.h:20
Application & app
Definition Context.h:21
Resource::Charge & loadType
Definition Context.h:22
Json::Value params
Definition Context.h:43
T to_string(T... args)
T what(T... args)