Skip to content

Commit 9c6c22e

Browse files
committed
Address remaining PR #720 IT/signing review comments (verified against live rippled develop)
- SponsorshipIT: extend testPreFundedFeeSponsorshipNoCosign, testSponsorshipTransferSingleSigneeSingleSponsor, and testSponsorshipTransferMultiSponseeMultiSponsor to verify on-ledger state (balances, Sponsorship.feeAmount, AccountRoot.sponsor/sponsoringAccountCount) instead of only asserting tesSUCCESS. - SponsorshipIT: add testSponsorCreatedAccountPayment covering Payment.tfSponsorCreatedAccount, verified end-to-end against rippleci/xrpld:develop in standalone mode. - BcDerivedKeySignatureServiceTest: add known-good signature byte assertions to sponsorSignEd/Ec and sponsorMultiSignEd/Ec, matching the existing sibling multiSignEc pattern. All changes verified locally: 10/10 SponsorshipIT tests pass against rippleci/xrpld:develop (standalone), 36/36 BcDerivedKeySignatureServiceTest pass, full core suite (73473 tests) passes, checkstyle clean on both modules.
1 parent feb7906 commit 9c6c22e

2 files changed

Lines changed: 116 additions & 6 deletions

File tree

xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/bc/BcDerivedKeySignatureServiceTest.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,7 +1096,10 @@ void sponsorSignEd() {
10961096
final Callable<Boolean> signedCallable = () -> {
10971097
Signature signature = this.derivedKeySignatureService.sponsorSign(privateKeyReference, paymentTransaction);
10981098
assertThat(signature).isNotNull();
1099-
assertThat(signature.base16Value()).isNotEmpty();
1099+
assertThat(signature.base16Value()).isEqualTo(
1100+
"F0968CC4FC05040788E3F5E28D8BFE21F34B059143D769B54F8668CBD805BDE5C7AAF748EC60DD877B193FD16CB32A91D9539" +
1101+
"C4C04851099C4B45E5FD1566401"
1102+
);
11001103
// Verify signature is deterministic
11011104
Signature signature2 = this.derivedKeySignatureService.sponsorSign(privateKeyReference, paymentTransaction);
11021105
assertThat(signature.base16Value()).isEqualTo(signature2.base16Value());
@@ -1137,7 +1140,10 @@ void sponsorSignEc() {
11371140
final Callable<Boolean> signedCallable = () -> {
11381141
Signature signature = this.derivedKeySignatureService.sponsorSign(privateKeyReference, paymentTransaction);
11391142
assertThat(signature).isNotNull();
1140-
assertThat(signature.base16Value()).isNotEmpty();
1143+
assertThat(signature.base16Value()).isEqualTo(
1144+
"3045022100CA3EE6AF48AA49EEF7964E4BF5E3FA879476FFE836F91740344415CD3B34A25B02205DCFC440B19BECBE3C5FF29F" +
1145+
"F0C662C33AE2DDD10AC4B1859C419D0BB32B8AC6"
1146+
);
11411147
// Verify signature is deterministic for SECP256K1
11421148
Signature signature2 = this.derivedKeySignatureService.sponsorSign(privateKeyReference, paymentTransaction);
11431149
assertThat(signature.base16Value()).isEqualTo(signature2.base16Value());
@@ -1178,7 +1184,10 @@ void sponsorMultiSignEd() {
11781184
final Callable<Boolean> signedCallable = () -> {
11791185
Signature signature = this.derivedKeySignatureService.sponsorMultiSign(privateKeyReference, paymentTransaction);
11801186
assertThat(signature).isNotNull();
1181-
assertThat(signature.base16Value()).isNotEmpty();
1187+
assertThat(signature.base16Value()).isEqualTo(
1188+
"8DFF4D545A289D7F0659DB61759C1EFDA36093432F1B2B6E99BA3B8A5EF4343125B6470441CE7579F42FD9B29BAC728E087BA" +
1189+
"8260E911583B3B3B6038FF5740D"
1190+
);
11821191
// Verify signature is deterministic
11831192
Signature signature2 = this.derivedKeySignatureService.sponsorMultiSign(privateKeyReference, paymentTransaction);
11841193
assertThat(signature.base16Value()).isEqualTo(signature2.base16Value());
@@ -1219,7 +1228,10 @@ void sponsorMultiSignEc() {
12191228
final Callable<Boolean> signedCallable = () -> {
12201229
Signature signature = this.derivedKeySignatureService.sponsorMultiSign(privateKeyReference, paymentTransaction);
12211230
assertThat(signature).isNotNull();
1222-
assertThat(signature.base16Value()).isNotEmpty();
1231+
assertThat(signature.base16Value()).isEqualTo(
1232+
"304502210088CBBE3DE6CCA7306072B28DF8785157E9D6A4037E6F895B7D93C4106C208EFF02204CE2EB8A9A1077358408201" +
1233+
"052D46EC7AB4BEA09CB981E2FEAB17812BBC52E96"
1234+
);
12231235
// Verify signature is deterministic for SECP256K1
12241236
Signature signature2 = this.derivedKeySignatureService.sponsorMultiSign(privateKeyReference, paymentTransaction);
12251237
assertThat(signature.base16Value()).isEqualTo(signature2.base16Value());

xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/SponsorshipIT.java

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.xrpl.xrpl4j.client.JsonRpcClientErrorException;
3232
import org.xrpl.xrpl4j.crypto.keys.KeyPair;
3333
import org.xrpl.xrpl4j.crypto.keys.PublicKey;
34+
import org.xrpl.xrpl4j.crypto.keys.Seed;
3435
import org.xrpl.xrpl4j.crypto.signing.MultiSignedTransaction;
3536
import org.xrpl.xrpl4j.crypto.signing.Signature;
3637
import org.xrpl.xrpl4j.crypto.signing.SingleSignedTransaction;
@@ -47,9 +48,11 @@
4748
import org.xrpl.xrpl4j.model.client.transactions.SubmitMultiSignedResult;
4849
import org.xrpl.xrpl4j.model.client.transactions.SubmitResult;
4950
import org.xrpl.xrpl4j.model.client.transactions.TransactionResult;
51+
import org.xrpl.xrpl4j.model.flags.PaymentFlags;
5052
import org.xrpl.xrpl4j.model.flags.SponsorFlags;
5153
import org.xrpl.xrpl4j.model.flags.SponsorshipSetFlags;
5254
import org.xrpl.xrpl4j.model.flags.SponsorshipTransferFlags;
55+
import org.xrpl.xrpl4j.model.ledger.AccountRootObject;
5356
import org.xrpl.xrpl4j.model.ledger.CheckObject;
5457
import org.xrpl.xrpl4j.model.ledger.SignerEntry;
5558
import org.xrpl.xrpl4j.model.ledger.SignerEntryWrapper;
@@ -252,13 +255,14 @@ void testPreFundedFeeSponsorshipNoCosign() throws JsonRpcClientErrorException, J
252255

253256
// Sponsor creates SponsorshipSet without RequireSignForFee flag
254257
AccountInfoResult sponsorAccountInfo = scanForResult(() -> getValidatedAccountInfo(sponsorAddress));
258+
XrpCurrencyAmount feeAmount = XrpCurrencyAmount.ofDrops(500000);
255259

256260
SponsorshipSet sponsorshipSet = SponsorshipSet.builder()
257261
.account(sponsorAddress)
258262
.fee(feeResult.drops().openLedgerFee())
259263
.sequence(sponsorAccountInfo.accountData().sequence())
260264
.sponsee(sponseeAddress)
261-
.feeAmount(XrpCurrencyAmount.ofDrops(500000))
265+
.feeAmount(feeAmount)
262266
.signingPublicKey(sponsorKeyPair.publicKey())
263267
.build();
264268

@@ -272,11 +276,13 @@ void testPreFundedFeeSponsorshipNoCosign() throws JsonRpcClientErrorException, J
272276

273277
// Sponsee submits payment using pre-funded fee (no sponsor signature needed)
274278
AccountInfoResult sponseeAccountInfo = scanForResult(() -> getValidatedAccountInfo(sponseeAddress));
279+
XrpCurrencyAmount sponseeInitialBalance = sponseeAccountInfo.accountData().balance();
280+
XrpCurrencyAmount paymentAmount = XrpCurrencyAmount.ofDrops(50000);
275281

276282
Payment payment = Payment.builder()
277283
.account(sponseeAddress)
278284
.destination(destAddress)
279-
.amount(XrpCurrencyAmount.ofDrops(50000))
285+
.amount(paymentAmount)
280286
.fee(feeResult.drops().openLedgerFee())
281287
.sequence(sponseeAccountInfo.accountData().sequence())
282288
.sponsor(sponsorAddress)
@@ -290,6 +296,72 @@ void testPreFundedFeeSponsorshipNoCosign() throws JsonRpcClientErrorException, J
290296

291297
SubmitResult<Payment> paymentResult = xrplClient.submit(signedPayment);
292298
assertThat(paymentResult.engineResult()).isEqualTo(SUCCESS_STATUS);
299+
scanForResult(() -> getValidatedTransaction(paymentResult.transactionResult().hash(), Payment.class));
300+
301+
// Assert the fee was paid from the pre-funded Sponsorship object, not the sponsee's own balance:
302+
// the sponsee's balance drops only by the payment amount, and the Sponsorship's FeeAmount decreases
303+
// by the network fee.
304+
XrpCurrencyAmount sponseeFinalBalance = scanForResult(
305+
() -> getValidatedAccountInfo(sponseeAddress)
306+
).accountData().balance();
307+
XrpCurrencyAmount expectedSponseeBalance = XrpCurrencyAmount.ofDrops(
308+
sponseeInitialBalance.value().longValue() - paymentAmount.value().longValue()
309+
);
310+
assertThat(sponseeFinalBalance).isEqualTo(expectedSponseeBalance);
311+
312+
SponsorshipObject updatedSponsorship = getSponsorshipObject(sponsorAddress, sponseeAddress);
313+
XrpCurrencyAmount expectedFeeAmount = XrpCurrencyAmount.ofDrops(
314+
feeAmount.value().longValue() - feeResult.drops().openLedgerFee().value().longValue()
315+
);
316+
assertThat(updatedSponsorship.feeAmount()).hasValue(expectedFeeAmount);
317+
}
318+
319+
/**
320+
* Test: {@code Payment.tfSponsorCreatedAccount} sponsors a brand-new destination account.
321+
*
322+
* <ol>
323+
* <li>Alice sends a Payment with {@code tfSponsorCreatedAccount} set to a fresh, never-before-seen
324+
* destination address, with an {@code Amount} below the normal account reserve requirement
325+
* (allowed per spec section 11.3 when this flag is enabled).</li>
326+
* <li>Assert the new account was created and its {@code AccountRoot.Sponsor} field points to Alice.</li>
327+
* </ol>
328+
*/
329+
@Test
330+
void testSponsorCreatedAccountPayment() throws JsonRpcClientErrorException, JsonProcessingException {
331+
KeyPair aliceKeyPair = createRandomAccountEd25519();
332+
Address aliceAddress = aliceKeyPair.publicKey().deriveAddress();
333+
334+
// A fresh, never-funded keypair. This account does not yet exist on the ledger.
335+
KeyPair newAccountKeyPair = Seed.ed25519Seed().deriveKeyPair();
336+
Address newAccountAddress = newAccountKeyPair.publicKey().deriveAddress();
337+
338+
FeeResult feeResult = xrplClient.fee();
339+
AccountInfoResult aliceAccountInfo = scanForResult(() -> getValidatedAccountInfo(aliceAddress));
340+
341+
Payment sponsoredAccountCreate = Payment.builder()
342+
.account(aliceAddress)
343+
.destination(newAccountAddress)
344+
.amount(XrpCurrencyAmount.ofDrops(1)) // Below the normal reserve; allowed with this flag.
345+
.fee(feeResult.drops().openLedgerFee())
346+
.sequence(aliceAccountInfo.accountData().sequence())
347+
.flags(PaymentFlags.builder().tfSponsorCreatedAccount(true).build())
348+
.signingPublicKey(aliceKeyPair.publicKey())
349+
.build();
350+
351+
SingleSignedTransaction<Payment> signedPayment = signatureService.sign(
352+
aliceKeyPair.privateKey(), sponsoredAccountCreate
353+
);
354+
SubmitResult<Payment> paymentResult = xrplClient.submit(signedPayment);
355+
assertThat(paymentResult.engineResult()).isEqualTo(SUCCESS_STATUS);
356+
logInfo(sponsoredAccountCreate.transactionType(), paymentResult.transactionResult().hash());
357+
358+
scanForResult(() -> getValidatedTransaction(paymentResult.transactionResult().hash(), Payment.class));
359+
360+
// Assert the new account exists and is sponsored by Alice.
361+
AccountRootObject newAccountData = scanForResult(
362+
() -> getValidatedAccountInfo(newAccountAddress)
363+
).accountData();
364+
assertThat(newAccountData.sponsor()).hasValue(aliceAddress);
293365
}
294366
}
295367

@@ -694,6 +766,19 @@ void testSponsorshipTransferSingleSigneeSingleSponsor() throws JsonRpcClientErro
694766
() -> getValidatedTransaction(finalTx.hash(), SponsorshipTransfer.class)
695767
);
696768
assertThat(validatedTx.metadata().get().transactionResult()).isEqualTo(SUCCESS_STATUS);
769+
770+
// Assert the account-level reserve sponsorship was actually established on-ledger: the
771+
// sponsee's AccountRoot now names the new sponsor, and the new sponsor's SponsoringAccountCount
772+
// increased.
773+
AccountRootObject sponseeAccountData = scanForResult(
774+
() -> getValidatedAccountInfo(sponseeAddress)
775+
).accountData();
776+
assertThat(sponseeAccountData.sponsor()).hasValue(newSponsorAddress);
777+
778+
AccountRootObject newSponsorAccountData = scanForResult(
779+
() -> getValidatedAccountInfo(newSponsorAddress)
780+
).accountData();
781+
assertThat(newSponsorAccountData.sponsoringAccountCount()).hasValue(UnsignedInteger.ONE);
697782
}
698783

699784
/**
@@ -835,6 +920,19 @@ void testSponsorshipTransferMultiSponseeMultiSponsor() throws JsonRpcClientError
835920
() -> getValidatedTransaction(finalTx.hash(), SponsorshipTransfer.class)
836921
);
837922
assertThat(validatedTx.metadata().get().transactionResult()).isEqualTo(SUCCESS_STATUS);
923+
924+
// Assert the account-level reserve sponsorship was actually established on-ledger: the
925+
// sponsee's AccountRoot now names the sponsor account, and the sponsor's SponsoringAccountCount
926+
// increased.
927+
AccountRootObject sponseeAccountData = scanForResult(
928+
() -> getValidatedAccountInfo(sponseeAddress)
929+
).accountData();
930+
assertThat(sponseeAccountData.sponsor()).hasValue(sponsorAddress);
931+
932+
AccountRootObject sponsorAccountData = scanForResult(
933+
() -> getValidatedAccountInfo(sponsorAddress)
934+
).accountData();
935+
assertThat(sponsorAccountData.sponsoringAccountCount()).hasValue(UnsignedInteger.ONE);
838936
}
839937
}
840938

0 commit comments

Comments
 (0)