xrpld
Loading...
Searching...
No Matches
CredentialHelpers.cpp
1#include <xrpl/ledger/helpers/CredentialHelpers.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Slice.h>
5#include <xrpl/basics/base_uint.h>
6#include <xrpl/basics/chrono.h>
7#include <xrpl/beast/utility/Journal.h>
8#include <xrpl/ledger/ApplyView.h>
9#include <xrpl/ledger/ReadView.h>
10#include <xrpl/ledger/helpers/AccountRootHelpers.h>
11#include <xrpl/protocol/AccountID.h>
12#include <xrpl/protocol/Feature.h>
13#include <xrpl/protocol/Indexes.h>
14#include <xrpl/protocol/LedgerFormats.h>
15#include <xrpl/protocol/Protocol.h>
16#include <xrpl/protocol/SField.h>
17#include <xrpl/protocol/STArray.h>
18#include <xrpl/protocol/STLedgerEntry.h>
19#include <xrpl/protocol/STObject.h>
20#include <xrpl/protocol/STTx.h>
21#include <xrpl/protocol/STVector256.h>
22#include <xrpl/protocol/TER.h>
23#include <xrpl/protocol/digest.h>
24
25#include <cstdint>
26#include <expected>
27#include <limits>
28#include <set>
29#include <unordered_set>
30#include <utility>
31#include <vector>
32
33namespace xrpl {
34namespace credentials {
35
36bool
37checkExpired(SLE const& sleCredential, NetClock::time_point const& closed)
38{
39 std::uint32_t const exp =
40 sleCredential[~sfExpiration].value_or(std::numeric_limits<std::uint32_t>::max());
41 std::uint32_t const now = closed.time_since_epoch().count();
42 return now > exp;
43}
44
45[[nodiscard]]
46static std::expected<bool, TER>
48{
49 auto const closeTime = view.header().parentCloseTime;
50 bool foundExpired = false;
51
52 for (auto const& h : arr)
53 {
54 // Credentials already checked in preclaim. Look only for expired here.
55 auto const k = keylet::credential(h);
56 auto const sleCred = view.peek(k);
57
58 if (sleCred && checkExpired(*sleCred, closeTime))
59 {
60 JLOG(j.trace()) << "Credentials are expired. Cred: " << sleCred->getText();
61 // delete expired credentials even if the transaction failed
62 auto const err = deleteSLE(view, sleCred, j);
63 if (view.rules().enabled(fixCleanup3_1_3) && !isTesSuccess(err))
64 return std::unexpected(err);
65 foundExpired = true;
66 }
67 }
68
69 return foundExpired;
70}
71
72TER
74{
75 if (!sleCredential)
76 return tecNO_ENTRY;
77
78 auto delSLE = [&view, &sleCredential, j](
79 AccountID const& account, SField const& node, bool isOwner) -> TER {
80 auto const sleAccount = view.peek(keylet::account(account));
81 if (!sleAccount)
82 {
83 // LCOV_EXCL_START
84 JLOG(j.fatal()) << "Internal error: can't retrieve Owner account.";
85 return tecINTERNAL;
86 // LCOV_EXCL_STOP
87 }
88
89 // Remove object from owner directory
90 std::uint64_t const page = sleCredential->getFieldU64(node);
91 if (!view.dirRemove(keylet::ownerDir(account), page, sleCredential->key(), false))
92 {
93 // LCOV_EXCL_START
94 JLOG(j.fatal()) << "Unable to delete Credential from owner.";
95 return tefBAD_LEDGER;
96 // LCOV_EXCL_STOP
97 }
98
99 if (isOwner)
100 adjustOwnerCount(view, sleAccount, -1, j);
101
102 return tesSUCCESS;
103 };
104
105 auto const issuer = sleCredential->getAccountID(sfIssuer);
106 auto const subject = sleCredential->getAccountID(sfSubject);
107 bool const accepted = sleCredential->isFlag(lsfAccepted);
108
109 auto err = delSLE(issuer, sfIssuerNode, !accepted || (subject == issuer));
110 if (!isTesSuccess(err))
111 return err;
112
113 if (subject != issuer)
114 {
115 err = delSLE(subject, sfSubjectNode, accepted);
116 if (!isTesSuccess(err))
117 return err;
118 }
119
120 // Remove object from ledger
121 view.erase(sleCredential);
122
123 return tesSUCCESS;
124}
125
126NotTEC
128{
129 if (!tx.isFieldPresent(sfCredentialIDs))
130 return tesSUCCESS;
131
132 auto const& credentials = tx.getFieldV256(sfCredentialIDs);
133 if (credentials.empty() || (credentials.size() > kMaxCredentialsArraySize))
134 {
135 JLOG(j.trace()) << "Malformed transaction: Credentials array size is invalid: "
136 << credentials.size();
137 return temMALFORMED;
138 }
139
141 for (auto const& cred : credentials)
142 {
143 auto [it, ins] = duplicates.insert(cred);
144 if (!ins)
145 {
146 JLOG(j.trace()) << "Malformed transaction: duplicates in credentials.";
147 return temMALFORMED;
148 }
149 }
150
151 return tesSUCCESS;
152}
153
154TER
155valid(STTx const& tx, ReadView const& view, AccountID const& src, beast::Journal j)
156{
157 if (!tx.isFieldPresent(sfCredentialIDs))
158 return tesSUCCESS;
159
160 auto const& credIDs(tx.getFieldV256(sfCredentialIDs));
161 for (auto const& h : credIDs)
162 {
163 auto const sleCred = view.read(keylet::credential(h));
164 if (!sleCred)
165 {
166 JLOG(j.trace()) << "Credential doesn't exist. Cred: " << h;
167 return tecBAD_CREDENTIALS;
168 }
169
170 if (sleCred->getAccountID(sfSubject) != src)
171 {
172 JLOG(j.trace()) << "Credential doesn't belong to the source account. Cred: " << h;
173 return tecBAD_CREDENTIALS;
174 }
175
176 if (!sleCred->isFlag(lsfAccepted))
177 {
178 JLOG(j.trace()) << "Credential isn't accepted. Cred: " << h;
179 return tecBAD_CREDENTIALS;
180 }
181
182 // Expiration checks are in doApply
183 }
184
185 return tesSUCCESS;
186}
187
188TER
189validDomain(ReadView const& view, uint256 domainID, AccountID const& subject)
190{
191 // Note, permissioned domain objects can be deleted at any time
192 auto const slePD = view.read(keylet::permissionedDomain(domainID));
193 if (!slePD)
194 return tecOBJECT_NOT_FOUND;
195
196 auto const closeTime = view.header().parentCloseTime;
197 bool foundExpired = false;
198 for (auto const& h : slePD->getFieldArray(sfAcceptedCredentials))
199 {
200 auto const issuer = h.getAccountID(sfIssuer);
201 auto const type = h.getFieldVL(sfCredentialType);
202 auto const keyletCredential = keylet::credential(subject, issuer, makeSlice(type));
203 auto const sleCredential = view.read(keyletCredential);
204
205 // We cannot delete expired credentials, that would require ApplyView&
206 // However we can check if credentials are expired. Expected transaction
207 // flow is to use `validDomain` in preclaim, converting tecEXPIRED to
208 // tesSUCCESS, then proceed to call `verifyValidDomain` in doApply. This
209 // allows expired credentials to be deleted by any transaction.
210 if (sleCredential)
211 {
212 if (checkExpired(*sleCredential, closeTime))
213 {
214 foundExpired = true;
215 continue;
216 }
217 if (sleCredential->isFlag(lsfAccepted))
218 {
219 return tesSUCCESS;
220 }
221
222 continue;
223 }
224 }
225
226 return foundExpired ? tecEXPIRED : tecNO_AUTH;
227}
228
229TER
230authorizedDepositPreauth(ReadView const& view, STVector256 const& credIDs, AccountID const& dst)
231{
234 lifeExtender.reserve(credIDs.size());
235 for (auto const& h : credIDs)
236 {
237 auto sleCred = view.read(keylet::credential(h));
238 if (!sleCred) // already checked in preclaim
239 return tefINTERNAL; // LCOV_EXCL_LINE
240
241 auto [it, ins] = sorted.emplace((*sleCred)[sfIssuer], (*sleCred)[sfCredentialType]);
242 if (!ins)
243 return tefINTERNAL; // LCOV_EXCL_LINE
244 lifeExtender.push_back(std::move(sleCred));
245 }
246
247 if (!view.exists(keylet::depositPreauth(dst, sorted)))
248 return tecNO_PERMISSION;
249
250 return tesSUCCESS;
251}
252
255{
257 for (auto const& cred : credentials)
258 {
259 auto [it, ins] = out.emplace(cred[sfIssuer], cred[sfCredentialType]);
260 if (!ins)
261 return {};
262 }
263 return out;
264}
265
266NotTEC
267checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j)
268{
269 if (credentials.empty() || (credentials.size() > maxSize))
270 {
271 JLOG(j.trace()) << "Malformed transaction: "
272 "Invalid credentials size: "
273 << credentials.size();
275 }
276
278 for (auto const& credential : credentials)
279 {
280 auto const& issuer = credential[sfIssuer];
281 if (!issuer)
282 {
283 JLOG(j.trace()) << "Malformed transaction: "
284 "Issuer account is invalid: "
285 << to_string(issuer);
287 }
288
289 auto const ct = credential[sfCredentialType];
290 if (ct.empty() || (ct.size() > kMaxCredentialTypeLength))
291 {
292 JLOG(j.trace()) << "Malformed transaction: "
293 "Invalid credentialType size: "
294 << ct.size();
295 return temMALFORMED;
296 }
297
298 auto [it, ins] = duplicates.insert(sha512Half(issuer, ct));
299 if (!ins)
300 {
301 JLOG(j.trace()) << "Malformed transaction: "
302 "duplicates in credentials.";
303 return temMALFORMED;
304 }
305 }
306
307 return tesSUCCESS;
308}
309
310} // namespace credentials
311
312TER
313verifyValidDomain(ApplyView& view, AccountID const& account, uint256 domainID, beast::Journal j)
314{
315 auto const slePD = view.read(keylet::permissionedDomain(domainID));
316 if (!slePD)
317 return tecOBJECT_NOT_FOUND;
318
319 // Collect all matching credentials on a side, so we can remove expired ones
320 // We may finish the loop with this collection empty, it's fine.
322 for (auto const& h : slePD->getFieldArray(sfAcceptedCredentials))
323 {
324 auto const issuer = h.getAccountID(sfIssuer);
325 auto const type = h.getFieldVL(sfCredentialType);
326 auto const keyletCredential = keylet::credential(account, issuer, makeSlice(type));
327 if (view.exists(keyletCredential))
328 credentials.pushBack(keyletCredential.key);
329 }
330
331 auto const foundExpired = credentials::removeExpired(view, credentials, j);
332 if (!foundExpired.has_value())
333 return foundExpired.error();
334
335 for (auto const& h : credentials)
336 {
337 auto sleCredential = view.read(keylet::credential(h));
338 if (!sleCredential)
339 continue; // expired, i.e. deleted in credentials::removeExpired
340
341 if (sleCredential->isFlag(lsfAccepted))
342 return tesSUCCESS;
343 }
344
345 return *foundExpired ? tecEXPIRED : tecNO_PERMISSION;
346}
347
348TER
350 STTx const& tx,
351 ReadView const& view,
352 AccountID const& src,
353 AccountID const& dst,
354 SLE::const_ref sleDst,
356{
357 // If depositPreauth is enabled, then an account that requires
358 // authorization has at least two ways to get a payment in:
359 // 1. If src == dst, or
360 // 2. If src is deposit preauthorized by dst (either by account or by
361 // credentials).
362
363 if (sleDst && ((sleDst->getFlags() & lsfDepositAuth) != 0u))
364 {
365 if (src != dst)
366 {
367 if (!view.exists(keylet::depositPreauth(dst, src)))
368 {
369 return !tx.isFieldPresent(sfCredentialIDs)
372 view, tx.getFieldV256(sfCredentialIDs), dst);
373 }
374 }
375 }
376
377 return tesSUCCESS;
378}
379
380TER
382{
383 if (tx.isFieldPresent(sfCredentialIDs))
384 {
385 auto const foundExpired =
386 credentials::removeExpired(view, tx.getFieldV256(sfCredentialIDs), j);
387 if (!foundExpired.has_value())
388 return foundExpired.error();
389 if (*foundExpired)
390 return tecEXPIRED;
391 }
392
393 return tesSUCCESS;
394}
395
396TER
398 STTx const& tx,
399 ApplyView& view,
400 AccountID const& src,
401 AccountID const& dst,
402 SLE::const_ref sleDst,
404{
405 if (auto const err = cleanupExpiredCredentials(tx, view, j); !isTesSuccess(err))
406 return err;
407
408 return checkDepositPreauth(tx, view, src, dst, sleDst, j);
409}
410
411} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream fatal() const
Definition Journal.h:321
Stream trace() const
Severity stream access functions.
Definition Journal.h:291
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:118
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
virtual void erase(SLE::ref sle)=0
Remove a peeked SLE.
std::chrono::time_point< NetClock > time_point
Definition chrono.h:46
A view into a ledger.
Definition ReadView.h:31
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
virtual LedgerHeader const & header() const =0
Returns information about the ledger.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:171
Identifies fields.
Definition SField.h:130
std::shared_ptr< STLedgerEntry > const & ref
std::shared_ptr< STLedgerEntry const > const & const_ref
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:454
STVector256 const & getFieldV256(SField const &field) const
Definition STObject.cpp:661
std::size_t size() const
T emplace(T... args)
T insert(T... args)
T max(T... args)
TER validDomain(ReadView const &view, uint256 domainID, AccountID const &subject)
TER deleteSLE(ApplyView &view, SLE::ref sleCredential, beast::Journal j)
std::set< std::pair< AccountID, Slice > > makeSorted(STArray const &credentials)
static std::expected< bool, TER > removeExpired(ApplyView &view, STVector256 const &arr, beast::Journal const j)
NotTEC checkFields(STTx const &tx, beast::Journal j)
TER valid(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
NotTEC checkArray(STArray const &credentials, unsigned maxSize, beast::Journal j)
TER authorizedDepositPreauth(ReadView const &view, STVector256 const &ctx, AccountID const &dst)
bool checkExpired(SLE const &sleCredential, NetClock::time_point const &closed)
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:328
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:357
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition Indexes.cpp:569
Keylet credential(AccountID const &subject, AccountID const &issuer, Slice const &credType) noexcept
Definition Indexes.cpp:545
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition digest.h:204
constexpr std::size_t kMaxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition Protocol.h:225
TER verifyValidDomain(ApplyView &view, AccountID const &account, uint256 domainID, beast::Journal j)
@ tefBAD_LEDGER
Definition TER.h:160
@ tefINTERNAL
Definition TER.h:163
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
STLedgerEntry SLE
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:594
constexpr std::size_t kMaxCredentialsArraySize
The maximum number of credentials can be passed in array.
Definition Protocol.h:228
void adjustOwnerCount(ApplyView &view, SLE::ref sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
TER cleanupExpiredCredentials(STTx const &tx, ApplyView &view, beast::Journal j)
Remove expired credentials referenced by the transaction.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
@ temARRAY_TOO_LARGE
Definition TER.h:127
@ temMALFORMED
Definition TER.h:73
@ temARRAY_EMPTY
Definition TER.h:126
@ temINVALID_ACCOUNT_ID
Definition TER.h:105
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
TERSubset< CanCvtToTER > TER
Definition TER.h:634
@ tecNO_ENTRY
Definition TER.h:304
@ tecOBJECT_NOT_FOUND
Definition TER.h:324
@ tecNO_AUTH
Definition TER.h:298
@ tecINTERNAL
Definition TER.h:308
@ tecBAD_CREDENTIALS
Definition TER.h:357
@ tecEXPIRED
Definition TER.h:312
@ tecNO_PERMISSION
Definition TER.h:303
BaseUInt< 256 > uint256
Definition base_uint.h:562
@ tesSUCCESS
Definition TER.h:240
std::enable_if_t< std::is_same_v< T, char >||std::is_same_v< T, unsigned char >, Slice > makeSlice(std::array< T, N > const &a)
Definition Slice.h:215
TER checkDepositPreauth(STTx const &tx, ReadView const &view, AccountID const &src, AccountID const &dst, std::shared_ptr< SLE const > const &sleDst, beast::Journal j)
Check whether src is authorized to deposit to dst.
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, SLE::const_ref sleDst, beast::Journal j)
T push_back(T... args)
T reserve(T... args)
NetClock::time_point parentCloseTime
T time_since_epoch(T... args)
T unexpected(T... args)