Xls 68 sponsored fees reserves updated#720
Conversation
…rp/lending-protocol
Per XLS-0068, the Sponsorship ledger object carries a SponseeNode field (UInt64, nth=32). SponsorshipObject.java already serializes it via @JsonProperty("SponseeNode"), but the field was absent from definitions.json, so any binary-codec round-trip of a Sponsorship object emitted by rippled would fail to decode. Matches rippled source: sfields.macro line 155 TYPED_SFIELD(sfSponseeNode, UINT64, 32)
Merges main (Dynamic MPT XLS-94D, XLS-65 Vault, XLS-66 Lending Protocol, Issue polymorphism refactor, Q2 dep upgrades, bug fixes) while preserving XLS-68 sponsored fees changes (sponsorSign/sponsorMultiSign, SponsorshipObject, SponsorshipSet/Transfer, SponsorSignature, SponsorshipValidations). Resolved conflicts: - Vault/Loan/LoanBroker objects: took main's AssetAmount→Amount refactor - Transaction.java/TransactionType/LedgerObject: merged both sides' additions - AbstractTransactionSigner: removed duplicate counterpartySign methods, fixed signingHelper→signatureHelper, moved overloads adjacent for checkstyle - MetaMpTokenIssuanceObject: removed duplicate domainId() method - Deleted orphaned AssetAmountDeserializer/Serializer and AssetAmountTest - RippledContainer: kept legleux/xrpld:sponsor image for CI
…y check Replaced explicit (Consumer<CreateContainerCmd>) cast with an inferred lambda so that the docker-java-api transitive dependency is no longer directly referenced in source, fixing the dependency:analyze-only failure.
…devnet/testnet - Add docker-java-api as explicit test dependency in integration-tests pom.xml to satisfy dependency:analyze-only (RippledContainer directly uses CreateContainerCmd bytecode regardless of the source-level cast) - Annotate SponsorshipIT with @DisabledIf to skip on devnet/testnet/clio environments where the featureSponsorship amendment is not yet enabled, matching the pattern used by BatchTransactionIT, LendingProtocolIT, SingleAssetVaultIT, and XChainIT
| @JsonProperty("SponsorSignature") | ||
| Optional<SponsorSignature> sponsorSignature(); | ||
|
|
||
| // TODO: Add Granular Permission fields (SponsorFee, SponsorReserve) for sponsorship once #689 is merged. |
There was a problem hiding this comment.
TODO nees to be addressed.
There was a problem hiding this comment.
Removed this TODO since the merged sponsorfee/sponsorreserve no longer uses granular permissions.
| Objects.requireNonNull(transaction); | ||
|
|
||
| // Validate sponsorship fields per XLS-0068 | ||
| SponsorshipValidations.validateSponsorFields(transaction); |
There was a problem hiding this comment.
We shouldn't inject statically like this. If we have a dependency like SponsorshipValidations then we need to inject via the constructor. This will allow proper mocking for testing.
That said, I'm not convinced that this check should occur here in SignatureUtils. This function is merely serializing bytes. The correctness or non-correctness checks seem like they should be in the transaction itself, or possibly in the signing function itself (as opposed to here).
If we want to consider adding an @Check default function in Transaction.java that might be a good candidate. Transaction.java is not itself an immutable, but we do have some prior art here where we annotation transactionType() function with @Value.Default. The immutables codegen picks this up and applies it to all Transactions.
There was a problem hiding this comment.
Moved this into a @Value.Check in Transaction.java. Also removed the SponsorshipValidations calls from SignatureUtils.java
| * @return An {@link UnsignedByteArray}. | ||
| */ | ||
| @Beta | ||
| public UnsignedByteArray toSponsorSignableBytes(final Transaction transaction) { |
There was a problem hiding this comment.
This method doesn't seem any different from toSignableBytes(final Transaction transaction). Is there any reason we have it like this (i.e., with a different name)?
| Objects.requireNonNull(privateKeyable); | ||
| Objects.requireNonNull(transaction); | ||
|
|
||
| final UnsignedByteArray signableTransactionBytes = this.signatureUtils.toSponsorSignableBytes(transaction); |
There was a problem hiding this comment.
I might be missing something, but if privateKeyable is the Sponsor's private key, then couldn't this function simply call this.signatureUtils.toSignableBytes(transaction) instead?
If that answer is "yes", then do we need sponsorSign at all?
I see that we need a special function for sponsorMultiSign, so even if this is a redudant function, I kind of like the API part of it. In that case, could this just be implemented like this?
@Override
public <T extends Transaction> Signature sponsorSign(final P privateKeyable, final T transaction) {
Objects.requireNonNull(privateKeyable);
Objects.requireNonNull(transaction);
return this.sign(privateKeyable, transaction).signature();
}| } | ||
|
|
||
| @Override | ||
| public <T extends Transaction> Signature sponsorSign(final P privateKeyable, final T transaction) { |
There was a problem hiding this comment.
Should this return a SingleSignedTransaction<T> instead of a `Signature?
I'm inclined to say "no" because sign that returns a SingleSignedTransaction<T> appears to be an anomaly rather than a pattern, but this is still probably worth discussing.
There was a problem hiding this comment.
Left it as if for now. Looks like only 1 other function returns SingleSignedTransaction<T> while all other functions (like multiSign, counterpartySign, sponsorMultiSIgn) return Signature
| * Its API is subject to change.</p> | ||
| */ | ||
| @Beta | ||
| protected static final PaymentFlags SPONSOR_CREATED_ACCOUNT = new PaymentFlags(0x00080000L); |
There was a problem hiding this comment.
I don't see this change reflected in PaymentFlagsTests.
There was a problem hiding this comment.
Added coverage for tfSponsorCreatedAccount in PaymentFlagsTests.java
| */ | ||
| @Beta | ||
| @JsonProperty("Sponsor") | ||
| Optional<Address> sponsor(); |
There was a problem hiding this comment.
Let's be sure to update unit tests for this. In particular, we'll want to make sure serialization works with and without this field being present.
There was a problem hiding this comment.
Added testJsonWithSponsor to PayChannelObjectJsonTests.java
| */ | ||
| @Beta | ||
| @JsonProperty("Sponsor") | ||
| Optional<Address> sponsor(); |
There was a problem hiding this comment.
Let's be sure to update unit tests for this. In particular, we'll want to make sure serialization works with and without this field being present.
There was a problem hiding this comment.
The sponsor field has since been removed from OfferObject
| */ | ||
| @Beta | ||
| @JsonProperty("Sponsor") | ||
| Optional<Address> sponsor(); |
There was a problem hiding this comment.
Let's be sure to update unit tests for this (e.g., EscrowObjectJsonTests). In particular, we'll want to make sure serialization works with and without this field being present.
We should do this for any ledger object that gets a sponsor.
There was a problem hiding this comment.
Added testJsonWithSponsor in EscrowObjectJsonTests.java
| */ | ||
| @Beta | ||
| @JsonProperty("LowSponsor") | ||
| Optional<Address> lowSponsor(); |
There was a problem hiding this comment.
Let's be sure to update unit tests for this. In particular, we'll want to make sure serialization works with and without this field being present.
There was a problem hiding this comment.
Added testJsonWithSponsors in RippleStateObjectJsonTests.java
| @Value.Immutable | ||
| @JsonSerialize(as = ImmutableSponsorSignature.class) | ||
| @JsonDeserialize(as = ImmutableSponsorSignature.class) | ||
| public interface SponsorSignature { |
There was a problem hiding this comment.
I think this class needs to move to package org.xrpl.xrpl4j.crypto.signing;
There was a problem hiding this comment.
Now moved to org.xrpl.xrpl4j.crypto.signing and moved the associated test to xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing
There was a problem hiding this comment.
Shouldn't we move this to package org.xrpl.xrpl4j.model.transactions similar to BatchSignerWrapper and CounterpartySignature?
rippled's Sponsor implementation moved on significantly since this PR was written against #5887. Realigns the Java model with the current xrplf/sponsor branch: - SponsorshipSet/SponsorshipTransfer tx type codes: 86/85 -> 91/90 - ReserveCount renamed to RemainingOwnerCount and renumbered (72->73); SponsorFlags nth 73->74; ObjectID nth 39->41 - SponsorshipTransfer gains a Sponsee field for ending a third party's sponsorship, validated to only be set with tfSponsorshipEnd - SponsorshipSetFlags gains the missing Clear variants (tfSponsorshipClearRequireSignForFee/Reserve) - Sponsor field removed from OfferObject/NfTokenOfferObject/ TicketObject (excluded from rippled's current scope) and added to MpTokenObject/MpTokenIssuanceObject/SignerListObject (now in scope) - SponsorshipTransfer requires a SponsorSignature for account-level tfSponsorshipReassign, matching rippled's fix for the forced-transfer-without-consent vulnerability
The short {@link Beta} reference doesn't resolve in the Immutables-generated
classes, which don't import com.google.common.annotations.Beta, breaking the
javadoc:jar build (attach-javadocs).
| private XrplAdminClient xrplAdminClient; | ||
| private boolean started; | ||
|
|
||
| private static final String RIPPLED_DOCKER_IMAGE = "legleux/xrpld:sponsor"; |
There was a problem hiding this comment.
🟡 Severity: MEDIUM
The Docker image was changed from the official rippleci/rippled:develop to a personal Docker Hub account (legleux/xrpld:sponsor). Combined with PullPolicy.alwaysPull(), this exposes CI environments to supply-chain compromise if the personal account is hijacked or the image is tampered with.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Pin the Docker image by its content digest (SHA256 hash) instead of using a mutable tag. This prevents supply-chain attacks where the image content at the sponsor tag could be changed after initial verification. Replace the tag-based reference with a digest-based one:
- Determine the current image digest:
docker inspect --format='{{index .RepoDigests 0}}' legleux/xrpld:sponsor - Use the digest in the image reference, e.g.:
"legleux/xrpld@sha256:<digest>" - Add a comment explaining this is a temporary image pending merge of rippled PR #5887 into the official
rippleci/rippledimage, and should be reverted once available.
Additionally, consider switching back to the official rippleci/rippled image once the sponsored fees feature is included in the official build.
There was a problem hiding this comment.
The docker image has been changed to rippleci/rippled:develop
The CI-pinned rippled test image (legleux/xrpld:sponsor) is a snapshot that predates rippled's transaction-type renumbering on the xrplf/sponsor branch. It still expects SponsorshipSet=86 and SponsorshipTransfer=85, and rejects the current upstream values (91/90) with "Invalid transaction type". Reverting just the type codes restores compatibility with what CI can actually exercise today; the rest of the rebase (field renames, new flags, ledger-object scope, consent check) is unaffected since none of it is gated by tx type.
- Move SponsorSignature to crypto.signing package (sappenin) - Move sponsorship field validation from SignatureUtils into a Transaction @Value.Check, and remove the now-redundant toSponsorSignableBytes/sponsorSign special-casing (sappenin) - Add missing @OverRide annotations on SponsorshipSetFlags (code quality bot) - Add missing unit test coverage for PaymentFlags.tfSponsorCreatedAccount and the new sponsor fields on PayChannelObject/EscrowObject/ RippleStateObject (sappenin) - Fix SponsorshipSet.flags() to return SponsorshipSetFlags instead of the generic TransactionFlags, and add field-level temMALFORMED-style validation to SponsorshipSet and SponsorshipTransfer matching rippled's merged preflight logic (Patel-Raj11) - Replace the personal legleux/xrpld:sponsor Docker image in RippledContainer with the official rippleci/xrpld:develop image, now that rippled#7350 has merged sponsor support into develop (sappenin, depthfirst-app)
The direct dependency was only needed for withCreateContainerCmdModifier's CreateContainerCmd type, which was removed along with the personal legleux/xrpld:sponsor image in the prior commit.
Pulls in main's Permission Delegation feature (#689), Issue/Vault refactor, and other independent changes. Resolved conflicts: - SignatureUtils.java: kept our toSponsorMultiSignableBytes addition - MpTokenIssuanceObject.java: kept both our sponsor() and main's referenceHolding() additions - Transaction.java: kept both our sponsorship @Value.Check (checkSponsorshipFields) and main's delegate field/check (validateDelegateNotSameAsAccount) - definitions.json: resolved an actual nth collision between our ObjectID field and main's new ReferenceHolding field (both wrongly claimed nth 39 for Hash256). Verified against rippled's authoritative sfields.macro and corrected ObjectID to nth 41 (its real assignment). - BatchTest.java: the anonymous Transaction impl now overrides both sponsor()/sponsorFlags()/sponsorSignature() and delegate() - PaymentJsonTests.java: split what was actually two independent new test methods (testPaymentWithSponsorCreatedAccountFlag and testJsonWithDelegate) that the diff had conflated
Cross-checked every Sponsor-related SField against rippled's authoritative sfields.macro (XRPLF/rippled develop). Found and fixed: - ObjectID (SponsorshipTransfer): was nth 39, collided with main's new ReferenceHolding field during the recent merge; real value is 41 - SponsoredOwnerCount: 69 -> 70 - SponsoringOwnerCount: 70 -> 71 - SponsoringAccountCount: 71 -> 72 - ReserveCount: 72 -> 73, and renamed to RemainingOwnerCount (its real wire name) on both the SponsorshipSet transaction and the Sponsorship ledger object - SponsorFlags: 73 -> 74 These five UInt32 fields were each off by one because rippled has an unrelated sfConfidentialBalanceVersion field at nth 69 (from a separate, unimplemented-in-xrpl4j feature) that our numbering didn't account for. Without this fix, any binary-encoded transaction using these fields would carry the wrong field code and be rejected or misinterpreted by a real rippled node.
Rippled removed its SponsorFee/SponsorReserve GranularPermission implementation before merging Sponsor support into develop, so sponsorship no longer uses granular permissions.
The generated ImmutableSponsorSignature.java doesn't inherit
SponsorSignature's imports, so the unqualified {@link Signer}
reference (valid in the source file via its own import) failed to
resolve in the generated class, breaking the javadoc build in CI.
Fully-qualifying the reference fixes it regardless of imports.
The rippleci/xrpld:develop image renamed the Batch amendment to BatchV1_1, so rippled aborted on startup with 'Unknown feature: Batch', causing every integration test to fail with ExceptionInInitializerError in AbstractIT. Update xrpld.cfg to the current amendment name.
The develop image assigns SponsorshipSet=91 and SponsorshipTransfer=90, but xrpl4j's definitions.json used 86/85 (which now map to ConfidentialMPTMergeInbox/ConfidentialMPTConvert in develop). This caused rippled to reject SponsorshipIT transactions with 'Field MPTokenIssuanceID is required but missing'. Also fix SponseeNode UInt64 nth (32->33) to match.
rippled develop (commit 530e09d) defines the SponsorshipTransfer tf flags in the 0x0001xxxx range, but xrpl4j used 0x00000001/2/4. This caused develop to reject SponsorshipTransfer transactions with temINVALID_FLAG, failing 4 SponsorshipIT tests. Align the values: tfSponsorshipEnd 0x00000001 -> 0x00010000 tfSponsorshipCreate 0x00000002 -> 0x00020000 tfSponsorshipReassign 0x00000004 -> 0x00040000 Update the corresponding unit-test and JSON-fixture expectations.
| */ | ||
| @Beta | ||
| @JsonProperty("Sponsor") | ||
| Optional<Address> sponsor(); |
There was a problem hiding this comment.
Based on current rippled source of truth these ledger objects should have the Sponsor field (expect RippleState):
AccountRoot , Check , Escrow , PayChannel , MPToken , MPTokenIssuance , SignerList , DepositPreauth , Credential , Delegate , Sponsorship , and RippleState ( HighSponsor / LowSponsor ).
Can we add the missing ones both in ledger objects and Meta* objects?
| @@ -1 +1 @@ | |||
| package org.xrpl.xrpl4j.model.client.accounts; | |||
There was a problem hiding this comment.
Commenting here -- LedgerEntryRequestParams is missing sponsorship. We can add that by referencing other ledger entry lookups and have integration test exercise it.
There was a problem hiding this comment.
Added SponsorshipLedgerEntryParams and sponsorship() to LedgerEntryRequestParams
| @Value.Immutable | ||
| @JsonSerialize(as = ImmutableSponsorSignature.class) | ||
| @JsonDeserialize(as = ImmutableSponsorSignature.class) | ||
| public interface SponsorSignature { |
There was a problem hiding this comment.
Shouldn't we move this to package org.xrpl.xrpl4j.model.transactions similar to BatchSignerWrapper and CounterpartySignature?
| * @throws IllegalStateException if validation fails. | ||
| */ | ||
| @Value.Check | ||
| default SponsorSignature checkAndNormalize() { |
There was a problem hiding this comment.
Not sure if interaction of Sponsorship and Batch were taken into account, but some of the checks might needs to be relaxed here since when a transaction appears in a Batch transaction, it shouldn't have signature fields to be empty.
There was a problem hiding this comment.
Relaxed SponsorSignature.checkAndNormalize() here.
…shipSet tests, fee helper
…ons, regenerate definitions.json from server_definitions, add sponsored filter and ledger_entry lookup, relax SponsorSignature for inner-Batch txns
| LedgerEntryResult<SponsorshipObject> sponsorshipEntry = xrplClient.ledgerEntry( | ||
| LedgerEntryRequestParams.sponsorship( | ||
| SponsorshipLedgerEntryParams.builder() | ||
| .owner(sponsorAddress) | ||
| .sponsee(sponseeAddress) | ||
| .build(), | ||
| LedgerSpecifier.VALIDATED | ||
| ) | ||
| ); |
| LedgerEntryResult<SponsorshipObject> sponsorshipByIndex = xrplClient.ledgerEntry( | ||
| LedgerEntryRequestParams.index(expectedSponsorship.index(), SponsorshipObject.class, LedgerSpecifier.VALIDATED) | ||
| ); |
…tRoot.Sponsor field, fix javadoc build failure
…_entry handler (sponsor, not owner)
This PR supports the new amendment for sponsored fees and reserves for the java library.
Changes here use XRPLF/rippled#5887