Skip to content

Commit 2aca6b7

Browse files
committed
Add back LoanSet counterparty logic
1 parent 6f7f5cd commit 2aca6b7

2 files changed

Lines changed: 80 additions & 4 deletions

File tree

  • xrpl4j-core/src

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,9 @@ default BatchFlags flags() {
149149
* extra one. xrpl4j's own construction-time validation ({@link #checkBatchSigners()}) enforces only that no
150150
* required signer is missing, deferring the stricter extra-signer check to the server. Each inner contributes:
151151
* <ul>
152-
* <li>its <em>initiator</em> — its {@code Delegate} when one is present, otherwise its {@code Account}; and</li>
153-
* <li>its {@code Sponsor}, when the inner also carries a {@code SponsorSignature}.</li>
152+
* <li>its <em>initiator</em> — its {@code Delegate} when one is present, otherwise its {@code Account};</li>
153+
* <li>its {@code Sponsor}, when the inner also carries a {@code SponsorSignature}; and</li>
154+
* <li>a {@code LoanSet}'s {@code Counterparty}, when present.</li>
154155
* </ul>
155156
* The outer {@link #account()} is excluded throughout, because it authorises its own inner transactions with the
156157
* signature it puts on the Batch itself.
@@ -171,6 +172,10 @@ default Set<Address> requiredSigners() {
171172
if (innerTransaction.sponsorSignature().isPresent()) {
172173
innerTransaction.sponsor().ifPresent(signers::add);
173174
}
175+
// A LoanSet's Counterparty must also sign.
176+
if (innerTransaction instanceof LoanSet) {
177+
((LoanSet) innerTransaction).counterparty().ifPresent(signers::add);
178+
}
174179
return signers.build();
175180
})
176181
.filter(address -> !address.equals(outerAccount))
@@ -346,8 +351,9 @@ default Batch checkBatchSigners() {
346351
);
347352

348353
// Check 4: When BatchSigners is non-empty, every account required to sign an inner transaction must have a
349-
// corresponding BatchSigner entry. The required-signer set (initiator and sponsor, with the outer account
350-
// excluded) is derived by requiredSigners(), which is the single source of truth shared with fee computation.
354+
// corresponding BatchSigner entry. The required-signer set (initiator, sponsor, and LoanSet counterparty, with
355+
// the outer account excluded) is derived by requiredSigners(), which is the single source of truth shared with
356+
// fee computation.
351357
if (!this.batchSigners().isEmpty()) {
352358
final Set<Address> actualSignerAccounts = this.batchSigners().stream()
353359
.map(wrapper -> wrapper.batchSigner().account())

xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/BatchTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.xrpl.xrpl4j.crypto.keys.Seed;
3232
import org.xrpl.xrpl4j.crypto.signing.Signature;
3333
import org.xrpl.xrpl4j.model.flags.BatchFlags;
34+
import org.xrpl.xrpl4j.model.flags.LoanSetFlags;
3435
import org.xrpl.xrpl4j.model.flags.PaymentFlags;
3536
import org.xrpl.xrpl4j.model.flags.SponsorFlags;
3637
import org.xrpl.xrpl4j.model.flags.TransactionFlags;
@@ -516,6 +517,63 @@ void testBatchSignerFromAccountIsNotSufficientWhenDelegateIsSet() {
516517
.hasMessageContaining(delegateAccount.value());
517518
}
518519

520+
@Test
521+
void testBatchSignerRequiredForLoanSetCounterparty() {
522+
// A LoanSet inner's Counterparty must also sign the Batch (in addition to the LoanSet Account).
523+
Address innerAccount = Seed.ed25519Seed().deriveKeyPair().publicKey().deriveAddress();
524+
Address counterpartyAccount = Seed.ed25519Seed().deriveKeyPair().publicKey().deriveAddress();
525+
PublicKey innerKey = Seed.ed25519Seed().deriveKeyPair().publicKey();
526+
PublicKey counterpartyKey = Seed.ed25519Seed().deriveKeyPair().publicKey();
527+
528+
List<RawTransactionWrapper> transactions = Lists.newArrayList(
529+
RawTransactionWrapper.of(createInnerLoanSet(innerAccount, counterpartyAccount, UnsignedInteger.ONE)),
530+
RawTransactionWrapper.of(createInnerPayment(ACCOUNT, UnsignedInteger.valueOf(2)))
531+
);
532+
533+
// Should fail because BatchSigners is missing a signature from the Counterparty.
534+
assertThatThrownBy(() -> Batch.builder()
535+
.account(ACCOUNT)
536+
.fee(XrpCurrencyAmount.ofDrops(100))
537+
.sequence(UnsignedInteger.ONE)
538+
.flags(BatchFlags.ALL_OR_NOTHING)
539+
.rawTransactions(transactions)
540+
.batchSigners(Lists.newArrayList(
541+
BatchSignerWrapper.of(BatchSigner.builder()
542+
.account(innerAccount)
543+
.signingPublicKey(innerKey)
544+
.transactionSignature(Signature.fromBase16("00112233"))
545+
.build()
546+
)))
547+
.build()
548+
).isInstanceOf(IllegalArgumentException.class)
549+
.hasMessageContaining("BatchSigners must contain signatures from all accounts with inner transactions")
550+
.hasMessageContaining(counterpartyAccount.value());
551+
552+
// Should succeed once BatchSigners includes both the Account and the Counterparty.
553+
Batch batch = Batch.builder()
554+
.account(ACCOUNT)
555+
.fee(XrpCurrencyAmount.ofDrops(100))
556+
.sequence(UnsignedInteger.ONE)
557+
.flags(BatchFlags.ALL_OR_NOTHING)
558+
.rawTransactions(transactions)
559+
.batchSigners(Lists.newArrayList(
560+
BatchSignerWrapper.of(BatchSigner.builder()
561+
.account(innerAccount)
562+
.signingPublicKey(innerKey)
563+
.transactionSignature(Signature.fromBase16("00112233"))
564+
.build()
565+
),
566+
BatchSignerWrapper.of(BatchSigner.builder()
567+
.account(counterpartyAccount)
568+
.signingPublicKey(counterpartyKey)
569+
.transactionSignature(Signature.fromBase16("44556677"))
570+
.build()
571+
)))
572+
.build();
573+
574+
assertThat(batch.batchSigners()).hasSize(2);
575+
}
576+
519577
@Test
520578
void testBatchWithOuterSignerAsOnlyInnerAccount() {
521579
// Create inner transactions all from the outer signer account
@@ -1251,6 +1309,18 @@ private Payment createInnerPayment(Address account, Address delegate, UnsignedIn
12511309
.build();
12521310
}
12531311

1312+
private LoanSet createInnerLoanSet(Address account, Address counterparty, UnsignedInteger sequence) {
1313+
return LoanSet.builder()
1314+
.account(account)
1315+
.counterparty(counterparty)
1316+
.fee(XrpCurrencyAmount.ofDrops(0))
1317+
.sequence(sequence)
1318+
.flags(LoanSetFlags.of(TransactionFlags.INNER_BATCH_TXN.getValue()))
1319+
.loanBrokerId(Hash256.of("C031EFE677CDEF1C5F43475B374A16F990EE184F76015CB7548D34B500F72BFB"))
1320+
.principalRequested(Amount.of("1000000"))
1321+
.build();
1322+
}
1323+
12541324
private List<RawTransactionWrapper> createInnerTransactions(int count) {
12551325
return IntStream.range(0, count)
12561326
.mapToObj(i -> RawTransactionWrapper.of(

0 commit comments

Comments
 (0)