rippled
Loading...
Searching...
No Matches
DepositPreauth.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2018 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/misc/CredentialHelpers.h>
21#include <xrpld/app/tx/detail/DepositPreauth.h>
22#include <xrpld/ledger/View.h>
23#include <xrpl/basics/Log.h>
24#include <xrpl/protocol/Feature.h>
25#include <xrpl/protocol/Indexes.h>
26#include <xrpl/protocol/TxFlags.h>
27
28#include <optional>
29
30namespace ripple {
31
34{
35 if (!ctx.rules.enabled(featureDepositPreauth))
36 return temDISABLED;
37
38 bool const authArrPresent = ctx.tx.isFieldPresent(sfAuthorizeCredentials);
39 bool const unauthArrPresent =
40 ctx.tx.isFieldPresent(sfUnauthorizeCredentials);
41 int const authCredPresent =
42 static_cast<int>(authArrPresent) + static_cast<int>(unauthArrPresent);
43
44 if (authCredPresent && !ctx.rules.enabled(featureCredentials))
45 return temDISABLED;
46
47 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
48 return ret;
49
50 auto& tx = ctx.tx;
51
52 if (tx.getFlags() & tfUniversalMask)
53 {
54 JLOG(ctx.j.trace()) << "Malformed transaction: Invalid flags set.";
55 return temINVALID_FLAG;
56 }
57
58 auto const optAuth = ctx.tx[~sfAuthorize];
59 auto const optUnauth = ctx.tx[~sfUnauthorize];
60 int const authPresent = static_cast<int>(optAuth.has_value()) +
61 static_cast<int>(optUnauth.has_value());
62
63 if (authPresent + authCredPresent != 1)
64 {
65 // There can only be 1 field out of 4 or the transaction is malformed.
66 JLOG(ctx.j.trace())
67 << "Malformed transaction: "
68 "Invalid Authorize and Unauthorize field combination.";
69 return temMALFORMED;
70 }
71
72 if (authPresent)
73 {
74 // Make sure that the passed account is valid.
75 AccountID const& target(optAuth ? *optAuth : *optUnauth);
76 if (!target)
77 {
78 JLOG(ctx.j.trace())
79 << "Malformed transaction: Authorized or Unauthorized "
80 "field zeroed.";
82 }
83
84 // An account may not preauthorize itself.
85 if (optAuth && (target == ctx.tx[sfAccount]))
86 {
87 JLOG(ctx.j.trace())
88 << "Malformed transaction: Attempting to DepositPreauth self.";
90 }
91 }
92 else
93 {
94 if (auto err = credentials::checkArray(
95 ctx.tx.getFieldArray(
96 authArrPresent ? sfAuthorizeCredentials
97 : sfUnauthorizeCredentials),
99 ctx.j);
100 !isTesSuccess(err))
101 return err;
102 }
103
104 return preflight2(ctx);
105}
106
107TER
109{
110 AccountID const account(ctx.tx[sfAccount]);
111
112 // Determine which operation we're performing: authorizing or unauthorizing.
113 if (ctx.tx.isFieldPresent(sfAuthorize))
114 {
115 // Verify that the Authorize account is present in the ledger.
116 AccountID const auth{ctx.tx[sfAuthorize]};
117 if (!ctx.view.exists(keylet::account(auth)))
118 return tecNO_TARGET;
119
120 // Verify that the Preauth entry they asked to add is not already
121 // in the ledger.
122 if (ctx.view.exists(keylet::depositPreauth(account, auth)))
123 return tecDUPLICATE;
124 }
125 else if (ctx.tx.isFieldPresent(sfUnauthorize))
126 {
127 // Verify that the Preauth entry they asked to remove is in the ledger.
128 if (!ctx.view.exists(
129 keylet::depositPreauth(account, ctx.tx[sfUnauthorize])))
130 return tecNO_ENTRY;
131 }
132 else if (ctx.tx.isFieldPresent(sfAuthorizeCredentials))
133 {
134 STArray const& authCred(ctx.tx.getFieldArray(sfAuthorizeCredentials));
136 for (auto const& o : authCred)
137 {
138 auto const& issuer = o[sfIssuer];
139 if (!ctx.view.exists(keylet::account(issuer)))
140 return tecNO_ISSUER;
141 auto [it, ins] = sorted.emplace(issuer, o[sfCredentialType]);
142 if (!ins)
143 return tefINTERNAL;
144 }
145
146 // Verify that the Preauth entry they asked to add is not already
147 // in the ledger.
148 if (ctx.view.exists(keylet::depositPreauth(account, sorted)))
149 return tecDUPLICATE;
150 }
151 else if (ctx.tx.isFieldPresent(sfUnauthorizeCredentials))
152 {
153 // Verify that the Preauth entry is in the ledger.
155 account,
157 ctx.tx.getFieldArray(sfUnauthorizeCredentials)))))
158 return tecNO_ENTRY;
159 }
160 return tesSUCCESS;
161}
162
163TER
165{
166 if (ctx_.tx.isFieldPresent(sfAuthorize))
167 {
168 auto const sleOwner = view().peek(keylet::account(account_));
169 if (!sleOwner)
170 return {tefINTERNAL};
171
172 // A preauth counts against the reserve of the issuing account, but we
173 // check the starting balance because we want to allow dipping into the
174 // reserve to pay fees.
175 {
176 STAmount const reserve{view().fees().accountReserve(
177 sleOwner->getFieldU32(sfOwnerCount) + 1)};
178
179 if (mPriorBalance < reserve)
181 }
182
183 // Preclaim already verified that the Preauth entry does not yet exist.
184 // Create and populate the Preauth entry.
185 AccountID const auth{ctx_.tx[sfAuthorize]};
186 Keylet const preauthKeylet = keylet::depositPreauth(account_, auth);
187 auto slePreauth = std::make_shared<SLE>(preauthKeylet);
188
189 slePreauth->setAccountID(sfAccount, account_);
190 slePreauth->setAccountID(sfAuthorize, auth);
191 view().insert(slePreauth);
192
193 auto const page = view().dirInsert(
195 preauthKeylet,
197
198 JLOG(j_.trace()) << "Adding DepositPreauth to owner directory "
199 << to_string(preauthKeylet.key) << ": "
200 << (page ? "success" : "failure");
201
202 if (!page)
203 return tecDIR_FULL;
204
205 slePreauth->setFieldU64(sfOwnerNode, *page);
206
207 // If we succeeded, the new entry counts against the creator's reserve.
208 adjustOwnerCount(view(), sleOwner, 1, j_);
209 }
210 else if (ctx_.tx.isFieldPresent(sfUnauthorize))
211 {
212 auto const preauth =
213 keylet::depositPreauth(account_, ctx_.tx[sfUnauthorize]);
214
215 return DepositPreauth::removeFromLedger(view(), preauth.key, j_);
216 }
217 else if (ctx_.tx.isFieldPresent(sfAuthorizeCredentials))
218 {
219 auto const sleOwner = view().peek(keylet::account(account_));
220 if (!sleOwner)
221 return tefINTERNAL;
222
223 // A preauth counts against the reserve of the issuing account, but we
224 // check the starting balance because we want to allow dipping into the
225 // reserve to pay fees.
226 {
227 STAmount const reserve{view().fees().accountReserve(
228 sleOwner->getFieldU32(sfOwnerCount) + 1)};
229
230 if (mPriorBalance < reserve)
232 }
233
234 // Preclaim already verified that the Preauth entry does not yet exist.
235 // Create and populate the Preauth entry.
236
237 auto const sortedTX = credentials::makeSorted(
238 ctx_.tx.getFieldArray(sfAuthorizeCredentials));
239 STArray sortedLE(sfAuthorizeCredentials, sortedTX.size());
240 for (auto const& p : sortedTX)
241 {
242 auto cred = STObject::makeInnerObject(sfCredential);
243 cred.setAccountID(sfIssuer, p.first);
244 cred.setFieldVL(sfCredentialType, p.second);
245 sortedLE.push_back(std::move(cred));
246 }
247
248 Keylet const preauthKey = keylet::depositPreauth(account_, sortedTX);
249 auto slePreauth = std::make_shared<SLE>(preauthKey);
250 if (!slePreauth)
251 return tefINTERNAL;
252
253 slePreauth->setAccountID(sfAccount, account_);
254 slePreauth->peekFieldArray(sfAuthorizeCredentials) =
255 std::move(sortedLE);
256
257 view().insert(slePreauth);
258
259 auto const page = view().dirInsert(
261
262 JLOG(j_.trace()) << "Adding DepositPreauth to owner directory "
263 << to_string(preauthKey.key) << ": "
264 << (page ? "success" : "failure");
265
266 if (!page)
267 return tecDIR_FULL;
268
269 slePreauth->setFieldU64(sfOwnerNode, *page);
270
271 // If we succeeded, the new entry counts against the creator's reserve.
272 adjustOwnerCount(view(), sleOwner, 1, j_);
273 }
274 else if (ctx_.tx.isFieldPresent(sfUnauthorizeCredentials))
275 {
276 auto const preauthKey = keylet::depositPreauth(
277 account_,
279 ctx_.tx.getFieldArray(sfUnauthorizeCredentials)));
280 return DepositPreauth::removeFromLedger(view(), preauthKey.key, j_);
281 }
282
283 return tesSUCCESS;
284}
285
286TER
288 ApplyView& view,
289 uint256 const& preauthIndex,
291{
292 // Existence already checked in preclaim and DeleteAccount
293 auto const slePreauth{view.peek(keylet::depositPreauth(preauthIndex))};
294 if (!slePreauth)
295 {
296 JLOG(j.warn()) << "Selected DepositPreauth does not exist.";
297 return tecNO_ENTRY;
298 }
299
300 AccountID const account{(*slePreauth)[sfAccount]};
301 std::uint64_t const page{(*slePreauth)[sfOwnerNode]};
302 if (!view.dirRemove(keylet::ownerDir(account), page, preauthIndex, false))
303 {
304 JLOG(j.fatal()) << "Unable to delete DepositPreauth from owner.";
305 return tefBAD_LEDGER;
306 }
307
308 // If we succeeded, update the DepositPreauth owner's reserve.
309 auto const sleOwner = view.peek(keylet::account(account));
310 if (!sleOwner)
311 return tefINTERNAL;
312
313 adjustOwnerCount(view, sleOwner, -1, j);
314
315 // Remove DepositPreauth from ledger.
316 view.erase(slePreauth);
317
318 return tesSUCCESS;
319}
320
321} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:60
Stream fatal() const
Definition: Journal.h:352
Stream trace() const
Severity stream access functions.
Definition: Journal.h:322
Stream warn() const
Definition: Journal.h:340
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:140
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
Definition: ApplyView.cpp:189
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition: ApplyView.h:314
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void erase(std::shared_ptr< SLE > const &sle)=0
Remove a peeked SLE.
static NotTEC preflight(PreflightContext const &ctx)
static TER removeFromLedger(ApplyView &view, uint256 const &delIndex, beast::Journal j)
static TER preclaim(PreclaimContext const &ctx)
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
void push_back(STObject const &object)
Definition: STArray.h:212
const STArray & getFieldArray(SField const &field) const
Definition: STObject.cpp:686
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:484
static STObject makeInnerObject(SField const &name)
Definition: STObject.cpp:95
AccountID const account_
Definition: Transactor.h:91
ApplyView & view()
Definition: Transactor.h:107
beast::Journal const j_
Definition: Transactor.h:89
XRPAmount mPriorBalance
Definition: Transactor.h:92
ApplyContext & ctx_
Definition: Transactor.h:88
T emplace(T... args)
NotTEC checkArray(STArray const &credentials, unsigned maxSize, beast::Journal j)
std::set< std::pair< AccountID, Slice > > makeSorted(STArray const &credentials)
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:175
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:365
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition: Indexes.cpp:333
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
bool isTesSuccess(TER x)
Definition: TER.h:656
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition: View.cpp:924
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:81
@ tefBAD_LEDGER
Definition: TER.h:170
@ tefINTERNAL
Definition: TER.h:173
static bool adjustOwnerCount(ApplyContext &ctx, int count)
Definition: SetOracle.cpp:185
std::size_t constexpr maxCredentialsArraySize
The maximum number of credentials can be passed in array.
Definition: Protocol.h:107
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:133
@ tecNO_ENTRY
Definition: TER.h:293
@ tecNO_ISSUER
Definition: TER.h:286
@ tecNO_TARGET
Definition: TER.h:291
@ tecDIR_FULL
Definition: TER.h:274
@ tecDUPLICATE
Definition: TER.h:302
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:294
@ tesSUCCESS
Definition: TER.h:242
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
constexpr std::uint32_t tfUniversalMask
Definition: TxFlags.h:62
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:587
@ temMALFORMED
Definition: TER.h:87
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
@ temCANNOT_PREAUTH_SELF
Definition: TER.h:120
@ temINVALID_ACCOUNT_ID
Definition: TER.h:119
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
Definition: protocol/Fees.h:49
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:39
uint256 key
Definition: Keylet.h:40
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:53
ReadView const & view
Definition: Transactor.h:56
State information when preflighting a tx.
Definition: Transactor.h:32
beast::Journal const j
Definition: Transactor.h:38