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