Skip to content

Commit 32ad5de

Browse files
committed
reuse requiredSigners in checkBatchSigners
1 parent d63ce0d commit 32ad5de

1 file changed

Lines changed: 32 additions & 34 deletions

File tree

  • xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions

xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/Batch.java

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,19 @@ default BatchFlags flags() {
145145
* The set of accounts that must appear in {@link #batchSigners()}, derived from the inner transactions.
146146
*
147147
* <p>rippled builds the identical set in {@code Batch::preflightSigValidated} and rejects any {@code BatchSigners}
148-
* array that does not match it exactly. Each inner contributes its <em>initiator</em> — its {@code Delegate} when
149-
* one is present, otherwise its {@code Account} — plus its {@code Sponsor} when the inner also carries a
150-
* {@code SponsorSignature}. The outer {@link #account()} is excluded throughout, because it authorises its own
151-
* inner transactions with the signature it puts on the Batch itself.
148+
* array that does not match it exactly. Each inner contributes:
149+
* <ul>
150+
* <li>its <em>initiator</em> — its {@code Delegate} when one is present, otherwise its {@code Account};</li>
151+
* <li>its {@code Sponsor}, when the inner also carries a {@code SponsorSignature}; and</li>
152+
* <li>a {@code LoanSet}'s {@code Counterparty}, when present.</li>
153+
* </ul>
154+
* The outer {@link #account()} is excluded throughout, because it authorises its own inner transactions with the
155+
* signature it puts on the Batch itself.
152156
*
153-
* <p>rippled additionally reads an inner's {@code Counterparty}, but only {@code LoanSet} carries that field and
154-
* {@code LoanSet} may not be an inner transaction, so that case cannot arise today and is not modelled here.
157+
* <p>The {@code Counterparty} branch is unreachable today — {@code LoanSet} is the only type carrying that field,
158+
* and rippled currently bars the entire Lending (XLS-66) and Single Asset Vault (XLS-65) families from being Batch
159+
* inners via its {@code kDisabledTxTypes} list. It is modelled anyway so that this derivation matches rippled's the
160+
* moment those families are permitted in Batches.
155161
*
156162
* @return An unmodifiable {@link Set} of {@link Address}es that must sign this Batch, which may be empty when every
157163
* inner transaction belongs to the outer account.
@@ -161,11 +167,20 @@ default Set<Address> requiredSigners() {
161167
final Address outerAccount = this.account();
162168
return this.rawTransactions().stream()
163169
.map(RawTransactionWrapper::rawTransaction)
164-
.flatMap(innerTransaction -> Stream.concat(
165-
Stream.of(innerTransaction.delegate().orElseGet(innerTransaction::account)),
166-
innerTransaction.sponsorSignature().isPresent() ?
167-
innerTransaction.sponsor().map(Stream::of).orElseGet(Stream::empty) : Stream.empty()
168-
))
170+
.flatMap(innerTransaction -> {
171+
final Stream.Builder<Address> signers = Stream.builder();
172+
// Initiator: the Delegate signs on behalf of the account holder when present, otherwise the Account.
173+
signers.add(innerTransaction.delegate().orElseGet(innerTransaction::account));
174+
// A Sponsor must also sign when the inner carries a SponsorSignature.
175+
if (innerTransaction.sponsorSignature().isPresent()) {
176+
innerTransaction.sponsor().ifPresent(signers::add);
177+
}
178+
// A LoanSet's Counterparty must also sign (currently unreachable; see the Javadoc above).
179+
if (innerTransaction instanceof LoanSet) {
180+
((LoanSet) innerTransaction).counterparty().ifPresent(signers::add);
181+
}
182+
return signers.build();
183+
})
169184
.filter(address -> !address.equals(outerAccount))
170185
.collect(Collectors.collectingAndThen(Collectors.toCollection(LinkedHashSet::new), Collections::unmodifiableSet));
171186
}
@@ -338,34 +353,17 @@ default Batch checkBatchSigners() {
338353
firstSignerMatchingOuterAccount.orElse(null)
339354
);
340355

341-
// Check 4: When BatchSigners is non-empty, every account required to sign an inner transaction (excluding
342-
// the outer account) must have a corresponding BatchSigner entry.
356+
// Check 4: When BatchSigners is non-empty, every account required to sign an inner transaction must have a
357+
// corresponding BatchSigner entry. The required-signer set (initiator, sponsor, and — once permitted —
358+
// LoanSet counterparty, with the outer account excluded) is derived by requiredSigners(), which is the single
359+
// source of truth shared with fee computation.
343360
if (!this.batchSigners().isEmpty()) {
344-
// Compute the set of accounts that require signatures (excluding the outer account, which signs the Batch
345-
// itself). For each inner transaction, the required signer is its Delegate if present (the delegate signs
346-
// on behalf of the account holder), otherwise the transaction's Account. A LoanSet's Counterparty must also
347-
// sign, if present. Note: rippled also requires a Sponsor to sign when SponsorSignature is present, but
348-
// xrpl4j does not yet model sfSponsor/sfSponsorSignature, so that case cannot be checked here.
349-
final Set<Address> requiredSignerAccounts = this.rawTransactions().stream()
350-
.flatMap(wrapper -> {
351-
final Transaction innerTransaction = wrapper.rawTransaction();
352-
final Stream.Builder<Address> requiredSigners = Stream.builder();
353-
requiredSigners.add(innerTransaction.delegate().orElseGet(innerTransaction::account));
354-
if (innerTransaction instanceof LoanSet) {
355-
((LoanSet) innerTransaction).counterparty().ifPresent(requiredSigners::add);
356-
}
357-
return requiredSigners.build();
358-
})
359-
.filter(account -> !account.equals(this.account()))
360-
.collect(Collectors.toSet());
361-
362-
// Compute the set of accounts that actually provided signatures
363361
final Set<Address> actualSignerAccounts = this.batchSigners().stream()
364362
.map(wrapper -> wrapper.batchSigner().account())
365363
.collect(Collectors.toSet());
366364

367-
// Find the first inner-transaction account (excluding the outer account) that has no BatchSigner entry.
368-
final Optional<Address> missingSignerAccount = requiredSignerAccounts.stream()
365+
// Find the first required signer that has no BatchSigner entry.
366+
final Optional<Address> missingSignerAccount = this.requiredSigners().stream()
369367
.filter(account -> !actualSignerAccounts.contains(account))
370368
.findFirst();
371369

0 commit comments

Comments
 (0)