xrpld
Loading...
Searching...
No Matches
SignerListSet.cpp
1#include <xrpl/tx/transactors/account/SignerListSet.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/utility/Journal.h>
5#include <xrpl/beast/utility/instrumentation.h>
6#include <xrpl/core/ServiceRegistry.h>
7#include <xrpl/ledger/ApplyView.h>
8#include <xrpl/ledger/ReadView.h>
9#include <xrpl/ledger/helpers/AccountRootHelpers.h>
10#include <xrpl/ledger/helpers/DirectoryHelpers.h>
11#include <xrpl/protocol/AccountID.h>
12#include <xrpl/protocol/Feature.h>
13#include <xrpl/protocol/Indexes.h>
14#include <xrpl/protocol/Keylet.h>
15#include <xrpl/protocol/LedgerFormats.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/TER.h>
22#include <xrpl/protocol/TxFlags.h>
23#include <xrpl/protocol/XRPAmount.h>
24#include <xrpl/tx/SignerEntries.h>
25#include <xrpl/tx/Transactor.h>
26
27#include <algorithm>
28#include <cstddef>
29#include <cstdint>
30#include <memory>
31#include <tuple>
32#include <utility>
33#include <vector>
34
35namespace xrpl {
36
37// We're prepared for there to be multiple signer lists in the future,
38// but we don't need them yet. So for the time being we're manually
39// setting the sfSignerListID to zero in all cases.
41
44{
45 // Check the quorum. A non-zero quorum means we're creating or replacing
46 // the list. A zero quorum means we're destroying the list.
47 auto const quorum = tx[sfSignerQuorum];
50
51 bool const hasSignerEntries(tx.isFieldPresent(sfSignerEntries));
52 if ((quorum != 0u) && hasSignerEntries)
53 {
54 auto signers = SignerEntries::deserialize(tx, j, "transaction");
55
56 if (!signers)
57 return std::make_tuple(signers.error(), quorum, sign, op);
58
59 std::sort(signers->begin(), signers->end());
60
61 // Save deserialized list for later.
62 sign = std::move(*signers);
63 op = Operation::Set;
64 }
65 else if ((quorum == 0) && !hasSignerEntries)
66 {
68 }
69
70 return std::make_tuple(tesSUCCESS, quorum, sign, op);
71}
72
75{
76 // 0 means "Allow any flags"
77 return ctx.rules.enabled(fixInvalidTxFlags) ? tfUniversalMask : 0;
78}
79
82{
83 auto const result = determineOperation(ctx.tx, ctx.flags, ctx.j);
84
85 if (!isTesSuccess(std::get<0>(result)))
86 return std::get<0>(result);
87
88 if (std::get<3>(result) == Operation::Unknown)
89 {
90 // Neither a set nor a destroy. Malformed.
91 JLOG(ctx.j.trace()) << "Malformed transaction: Invalid signer set list format.";
92 return temMALFORMED;
93 }
94
95 if (std::get<3>(result) == Operation::Set)
96 {
97 // Validate our settings.
98 auto const account = ctx.tx.getAccountID(sfAccount);
100 std::get<1>(result), std::get<2>(result), account, ctx.j, ctx.rules);
101 if (!isTesSuccess(ter))
102 {
103 return ter;
104 }
105 }
106
107 return tesSUCCESS;
108}
109
110TER
112{
113 // Perform the operation preCompute() decided on.
114 switch (do_)
115 {
116 case Operation::Set:
117 return replaceSignerList();
118
120 return destroySignerList();
121
122 default:
123 break;
124 }
125 // LCOV_EXCL_START
126 UNREACHABLE("xrpl::SignerListSet::doApply : invalid operation");
127 return temMALFORMED;
128 // LCOV_EXCL_STOP
129}
130
131void
133{
134 // Get the quorum and operation info.
135 auto result = determineOperation(ctx_.tx, view().flags(), j_);
136 XRPL_ASSERT(
137 isTesSuccess(std::get<0>(result)),
138 "xrpl::SignerListSet::preCompute : result is tesSUCCESS");
139 XRPL_ASSERT(
140 std::get<3>(result) != Operation::Unknown,
141 "xrpl::SignerListSet::preCompute : result is known operation");
142
143 quorum_ = std::get<1>(result);
144 signers_ = std::get<2>(result);
145 do_ = std::get<3>(result);
146
148}
149
150// The return type is signed so it is compatible with the 3rd argument
151// of adjustOwnerCount() (which must be signed).
152static int
154{
155 // We always compute the full change in OwnerCount, taking into account:
156 // o The fact that we're adding/removing a SignerList and
157 // o Accounting for the number of entries in the list.
158 // We can get away with that because lists are not adjusted incrementally;
159 // we add or remove an entire list.
160 //
161 // The rule is:
162 // o Simply having a SignerList costs 2 OwnerCount units.
163 // o And each signer in the list costs 1 more OwnerCount unit.
164 // So, at a minimum, adding a SignerList with 1 entry costs 3 OwnerCount
165 // units. A SignerList with 8 entries would cost 10 OwnerCount units.
166 //
167 // The static_cast should always be safe since entryCount should always
168 // be in the range from 1 to 32.
169 // We've got a lot of room to grow.
170 XRPL_ASSERT(
171 entryCount >= STTx::kMinMultiSigners,
172 "xrpl::signerCountBasedOwnerCountDelta : minimum signers");
173 XRPL_ASSERT(
174 entryCount <= STTx::kMaxMultiSigners,
175 "xrpl::signerCountBasedOwnerCountDelta : maximum signers");
176 return 2 + static_cast<int>(entryCount);
177}
178
179static TER
181 ServiceRegistry& registry,
182 ApplyView& view,
183 Keylet const& accountKeylet,
184 Keylet const& ownerDirKeylet,
185 Keylet const& signerListKeylet,
187{
188 // We have to examine the current SignerList so we know how much to
189 // reduce the OwnerCount.
190 SLE::pointer const signers = view.peek(signerListKeylet);
191
192 // If the signer list doesn't exist we've already succeeded in deleting it.
193 if (!signers)
194 return tesSUCCESS;
195
196 // There are two different ways that the OwnerCount could be managed.
197 // If the lsfOneOwnerCount bit is set then remove just one owner count.
198 // Otherwise use the pre-MultiSignReserve amendment calculation.
199 int removeFromOwnerCount = -1;
200 if (!signers->isFlag(lsfOneOwnerCount))
201 {
202 STArray const& actualList = signers->getFieldArray(sfSignerEntries);
203 removeFromOwnerCount =
204 signerCountBasedOwnerCountDelta(actualList.size(), view.rules()) * -1;
205 }
206
207 // Remove the node from the account directory.
208 auto const hint = (*signers)[sfOwnerNode];
209
210 if (!view.dirRemove(ownerDirKeylet, hint, signerListKeylet.key, false))
211 {
212 // LCOV_EXCL_START
213 JLOG(j.fatal()) << "Unable to delete SignerList from owner.";
214 return tefBAD_LEDGER;
215 // LCOV_EXCL_STOP
216 }
217
219 view, view.peek(accountKeylet), removeFromOwnerCount, registry.getJournal("View"));
220
221 view.erase(signers);
222
223 return tesSUCCESS;
224}
225
226TER
228 ServiceRegistry& registry,
230 AccountID const& account,
232{
233 auto const accountKeylet = keylet::account(account);
234 auto const ownerDirKeylet = keylet::ownerDir(account);
235 auto const signerListKeylet = keylet::signerList(account);
236
238 registry, view, accountKeylet, ownerDirKeylet, signerListKeylet, j);
239}
240
241NotTEC
243 std::uint32_t quorum,
245 AccountID const& account,
247 Rules const& rules)
248{
249 // Reject if there are too many or too few entries in the list.
250 {
251 std::size_t const signerCount = signers.size();
252 if (signerCount < STTx::kMinMultiSigners || signerCount > STTx::kMaxMultiSigners)
253 {
254 JLOG(j.trace()) << "Too many or too few signers in signer list.";
255 return temMALFORMED;
256 }
257 }
258
259 // Make sure there are no duplicate signers.
260 XRPL_ASSERT(
261 std::ranges::is_sorted(signers),
262 "xrpl::SignerListSet::validateQuorumAndSignerEntries : sorted "
263 "signers");
264 if (std::ranges::adjacent_find(signers) != signers.end())
265 {
266 JLOG(j.trace()) << "Duplicate signers in signer list";
267 return temBAD_SIGNER;
268 }
269
270 // Make sure no signers reference this account. Also make sure the
271 // quorum can be reached.
272 std::uint64_t allSignersWeight(0);
273 for (auto const& signer : signers)
274 {
275 std::uint32_t const weight = signer.weight;
276 if (weight <= 0)
277 {
278 JLOG(j.trace()) << "Every signer must have a positive weight.";
279 return temBAD_WEIGHT;
280 }
281
282 allSignersWeight += signer.weight;
283
284 if (signer.account == account)
285 {
286 JLOG(j.trace()) << "A signer may not self reference account.";
287 return temBAD_SIGNER;
288 }
289 // Don't verify that the signer accounts exist. Non-existent accounts
290 // may be phantom accounts (which are permitted).
291 }
292 if ((quorum <= 0) || (allSignersWeight < quorum))
293 {
294 JLOG(j.trace()) << "Quorum is unreachable";
295 return temBAD_QUORUM;
296 }
297 return tesSUCCESS;
298}
299
300TER
302{
303 auto const accountKeylet = keylet::account(accountID_);
304 auto const ownerDirKeylet = keylet::ownerDir(accountID_);
305 auto const signerListKeylet = keylet::signerList(accountID_);
306
307 // This may be either a create or a replace. Preemptively remove any
308 // old signer list. May reduce the reserve, so this is done before
309 // checking the reserve.
310 if (TER const ter = removeSignersFromLedger(
311 ctx_.registry, view(), accountKeylet, ownerDirKeylet, signerListKeylet, j_))
312 return ter;
313
314 auto const sle = view().peek(accountKeylet);
315 if (!sle)
316 return tefINTERNAL; // LCOV_EXCL_LINE
317
318 // Compute new reserve. Verify the account has funds to meet the reserve.
319 std::uint32_t const oldOwnerCount{(*sle)[sfOwnerCount]};
320
321 static constexpr int kAddedOwnerCount = 1;
322 std::uint32_t const flags{lsfOneOwnerCount};
323
324 XRPAmount const newReserve{view().fees().accountReserve(oldOwnerCount + kAddedOwnerCount)};
325
326 // We check the reserve against the starting balance because we want to
327 // allow dipping into the reserve to pay fees. This behavior is consistent
328 // with TicketCreate.
329 if (preFeeBalance_ < newReserve)
331
332 // Everything's ducky. Add the ltSIGNER_LIST to the ledger.
333 auto signerList = std::make_shared<SLE>(signerListKeylet);
334 view().insert(signerList);
335 writeSignersToSLE(signerList, flags);
336
337 auto viewJ = ctx_.registry.get().getJournal("View");
338 // Add the signer list to the account's directory.
339 auto const page =
340 ctx_.view().dirInsert(ownerDirKeylet, signerListKeylet, describeOwnerDir(accountID_));
341
342 JLOG(j_.trace()) << "Create signer list for account " << toBase58(accountID_) << ": "
343 << (page ? "success" : "failure");
344
345 if (!page)
346 return tecDIR_FULL; // LCOV_EXCL_LINE
347
348 signerList->setFieldU64(sfOwnerNode, *page);
349
350 // If we succeeded, the new entry counts against the
351 // creator's reserve.
352 adjustOwnerCount(view(), sle, kAddedOwnerCount, viewJ);
353 return tesSUCCESS;
354}
355
356TER
358{
359 auto const accountKeylet = keylet::account(accountID_);
360 // Destroying the signer list is only allowed if either the master key
361 // is enabled or there is a regular key.
362 SLE::pointer const ledgerEntry = view().peek(accountKeylet);
363 if (!ledgerEntry)
364 return tefINTERNAL; // LCOV_EXCL_LINE
365
366 if ((ledgerEntry->isFlag(lsfDisableMaster)) && (!ledgerEntry->isFieldPresent(sfRegularKey)))
368
369 auto const ownerDirKeylet = keylet::ownerDir(accountID_);
370 auto const signerListKeylet = keylet::signerList(accountID_);
372 ctx_.registry, view(), accountKeylet, ownerDirKeylet, signerListKeylet, j_);
373}
374
375void
377{
378 // Assign the quorum, default SignerListID, and flags.
379 if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
380 {
381 ledgerEntry->setAccountID(sfOwner, accountID_);
382 }
383 ledgerEntry->setFieldU32(sfSignerQuorum, quorum_);
384 ledgerEntry->setFieldU32(sfSignerListID, kDefaultSignerListId);
385 if (flags != 0u) // Only set flags if they are non-default (default is zero).
386 ledgerEntry->setFieldU32(sfFlags, flags);
387
388 // Create the SignerListArray one SignerEntry at a time.
389 STArray toLedger(signers_.size());
390 for (auto const& entry : signers_)
391 {
392 toLedger.pushBack(STObject::makeInnerObject(sfSignerEntry));
393 STObject& obj = toLedger.back();
394 obj.reserve(2);
395 obj[sfAccount] = entry.account;
396 obj[sfSignerWeight] = entry.weight;
397
398 // This is a defensive check to make absolutely sure we will never write
399 // a tag into the ledger.
400 if (entry.tag)
401 obj.setFieldH256(sfWalletLocator, *(entry.tag));
402 }
403
404 // Assign the SignerEntries.
405 ledgerEntry->setFieldArray(sfSignerEntries, toLedger);
406}
407
408void
410{
411 // No transaction-specific invariants yet (future work).
412}
413
414bool
416 STTx const&,
417 TER,
418 XRPAmount,
419 ReadView const&,
420 beast::Journal const&)
421{
422 // No transaction-specific invariants yet (future work).
423 return true;
424}
425
426} // namespace xrpl
T adjacent_find(T... args)
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
const_iterator begin() const
UInt size() const
Number of values in array or object.
const_iterator end() const
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.
virtual void insert(SLE::ref sle)=0
Insert a new state SLE.
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.
A view into a ledger.
Definition ReadView.h:31
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
Rules controlling protocol behavior.
Definition Rules.h:33
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:171
size_type size() const
Definition STArray.h:240
void pushBack(STObject const &object)
Definition STArray.h:204
STObject & back()
Definition STArray.h:185
std::shared_ptr< STLedgerEntry > pointer
std::shared_ptr< STLedgerEntry const > const & const_ref
void reserve(std::size_t n)
Definition STObject.h:973
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:454
static STObject makeInnerObject(SField const &name)
Definition STObject.cpp:74
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:633
void setFieldH256(SField const &field, uint256 const &)
Definition STObject.cpp:757
static constexpr std::size_t kMinMultiSigners
Definition STTx.h:33
static constexpr std::size_t kMaxMultiSigners
Definition STTx.h:34
Service registry for dependency injection.
virtual beast::Journal getJournal(std::string const &name)=0
static std::expected< std::vector< SignerEntry >, NotTEC > deserialize(STObject const &obj, beast::Journal journal, std::string_view annotation)
static NotTEC preflight(PreflightContext const &ctx)
void preCompute() override
static std::tuple< NotTEC, std::uint32_t, std::vector< SignerEntries::SignerEntry >, Operation > determineOperation(STTx const &tx, ApplyFlags flags, beast::Journal j)
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
static NotTEC validateQuorumAndSignerEntries(std::uint32_t quorum, std::vector< SignerEntries::SignerEntry > const &signers, AccountID const &account, beast::Journal j, Rules const &)
TER doApply() override
std::vector< SignerEntries::SignerEntry > signers_
static TER removeFromLedger(ServiceRegistry &registry, ApplyView &view, AccountID const &account, beast::Journal j)
void writeSignersToSLE(SLE::pointer const &ledgerEntry, std::uint32_t flags) const
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override
Inspect a single ledger entry modified by this transaction.
beast::Journal const j_
Definition Transactor.h:118
ApplyView & view()
Definition Transactor.h:136
AccountID const accountID_
Definition Transactor.h:120
XRPAmount preFeeBalance_
Definition Transactor.h:121
virtual void preCompute()
ApplyContext & ctx_
Definition Transactor.h:116
T is_sorted(T... args)
T make_shared(T... args)
T make_tuple(T... args)
Keylet signerList(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:316
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
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
static std::uint32_t const kDefaultSignerListId
@ tefBAD_LEDGER
Definition TER.h:160
@ tefINTERNAL
Definition TER.h:163
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:93
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:594
void adjustOwnerCount(ApplyView &view, SLE::ref sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Returns a function that sets the owner on a directory SLE.
ApplyFlags
Definition ApplyView.h:12
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
@ temMALFORMED
Definition TER.h:73
@ temBAD_QUORUM
Definition TER.h:102
@ temBAD_SIGNER
Definition TER.h:101
@ temBAD_WEIGHT
Definition TER.h:103
static int signerCountBasedOwnerCountDelta(std::size_t entryCount, Rules const &rules)
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
Buffer sign(PublicKey const &pk, SecretKey const &sk, Slice const &message)
Generate a signature for a message.
TERSubset< CanCvtToTER > TER
Definition TER.h:634
@ tecDIR_FULL
Definition TER.h:285
@ tecNO_ALTERNATIVE_KEY
Definition TER.h:294
@ tecINSUFFICIENT_RESERVE
Definition TER.h:305
constexpr FlagValue tfUniversalMask
Definition TxFlags.h:45
static TER removeSignersFromLedger(ServiceRegistry &registry, ApplyView &view, Keylet const &accountKeylet, Keylet const &ownerDirKeylet, Keylet const &signerListKeylet, beast::Journal j)
@ tesSUCCESS
Definition TER.h:240
T sort(T... args)
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:19
uint256 key
Definition Keylet.h:20
State information when preflighting a tx.
Definition Transactor.h:18
beast::Journal const j
Definition Transactor.h:25