Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/maven/org.apache.maven.plugins-ma…
Browse files Browse the repository at this point in the history
…ven-compiler-plugin-3.12.1
  • Loading branch information
sappenin authored Oct 21, 2024
2 parents 7875beb + eaf05aa commit 8be98b1
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 91 deletions.
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@
<jackson.version>2.14.2</jackson.version>
<feign.version>12.3</feign.version>
<slf4j.version>2.0.7</slf4j.version>
<junit-jupiter.version>5.10.0</junit-jupiter.version>
<junit-jupiter.version>5.10.1</junit-jupiter.version>
<guava.version>32.1.1-jre</guava.version>
</properties>

Expand Down Expand Up @@ -375,7 +375,7 @@
<!-- org.apache.maven.plugins:maven-source-plugin -->
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>3.1.0</version>
<version>3.3.0</version>
<executions>
<execution>
<id>attach-sources</id>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public interface LedgerEntryResult<T extends LedgerObject> extends XrplResult {
/**
* Construct a {@code LedgerEntryResult} builder.
*
* @param <T> The type of {@link LedgerObject}.
*
* @return An {@link ImmutableLedgerEntryResult.Builder}.
*/
static <T extends LedgerObject> ImmutableLedgerEntryResult.Builder<T> builder() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.xrpl.xrpl4j.model.jackson.modules;

/*-
* ========================LICENSE_START=================================
* xrpl4j :: model
* %%
* Copyright (C) 2020 - 2022 XRPL Foundation and its contributors
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =========================LICENSE_END==================================
*/

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.google.common.primitives.UnsignedLong;
import org.xrpl.xrpl4j.model.transactions.SetFee;
import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount;

import java.io.IOException;

/**
* Custom Jackson deserializer for {@link XrpCurrencyAmount} instances found in {@link SetFee}.
*
* <p>Before the <a href="https://xrpl.org/resources/known-amendments/#xrpfees">XRPFees amendment</a>, a {@link SetFee}
* transaction serializes its `BaseFee` to a hex string. After the
* <a href="https://xrpl.org/resources/known-amendments/#xrpfees">XRPFees amendment</a>, a {@link SetFee} transaction
* serializes its `BaseFee` to a decimal string.
*
* @see "https://xrpl.org/resources/known-amendments/#xrpfees"
*/
public class BaseFeeDropsDeserializer extends StdDeserializer<XrpCurrencyAmount> {

/**
* No-args constructor.
*/
public BaseFeeDropsDeserializer() {
super(XrpCurrencyAmount.class);
}

@Override
public XrpCurrencyAmount deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
// Pre-XRPFees, SetFee transactions serialize `BaseFee` to a hex string. Post XRPFees SetFee transactions
// have a `BaseFeeDrops` field which is a decimal string.
if (jsonParser.currentName().equals("BaseFee")) {
return XrpCurrencyAmount.of(UnsignedLong.valueOf(jsonParser.getText(), 16));
} else {
return XrpCurrencyAmount.ofDrops(jsonParser.getValueAsLong());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,19 @@ default EscrowFinish normalizeCondition() {
// In this case, we should try to read conditionRawValue to a Condition. If that fails, condition()
// will remain empty, otherwise we will set condition().
try {
Condition condition = CryptoConditionReader.readCondition(
BaseEncoding.base16().decode(conditionRawValue().get().toUpperCase(Locale.US))
);
byte[] conditionRawValueBytes = BaseEncoding.base16()
.decode(conditionRawValue().get().toUpperCase(Locale.US));
Condition condition = CryptoConditionReader.readCondition(conditionRawValueBytes);

// CryptoConditionReader.readCondition can succeed if the first part of the condition raw value
// is a valid condition, but the raw value has extra bytes afterward. Here, we check that the parsed
// condition is equivalent to the raw value when re-written.
if (!Arrays.equals(CryptoConditionWriter.writeCondition(condition), conditionRawValueBytes)) {
logger.warn("EscrowFinish Condition was malformed: mismatch between raw value and parsed condition. " +
"conditionRawValue() will contain the condition value, but condition() will be empty.");
return this;
}

return EscrowFinish.builder().from(this)
.condition(condition)
.build();
Expand Down Expand Up @@ -300,9 +310,19 @@ default EscrowFinish normalizeFulfillment() {
// In this case, we should try to read fulfillmentRawValue to a Condition. If that fails, fulfillment()
// will remain empty, otherwise we will set fulfillment().
try {
Fulfillment<?> fulfillment = CryptoConditionReader.readFulfillment(
BaseEncoding.base16().decode(fulfillmentRawValue().get().toUpperCase(Locale.US))
);
byte[] fulfillmentRawValueBytes = BaseEncoding.base16()
.decode(fulfillmentRawValue().get().toUpperCase(Locale.US));
Fulfillment<?> fulfillment = CryptoConditionReader.readFulfillment(fulfillmentRawValueBytes);

// CryptoConditionReader.readFulfillment can succeed if the first part of the fulfillment raw value
// is a valid fulfillment, but the raw value has extra bytes afterward. Here, we check that the parsed
// fulfillment is equivalent to the raw value when re-written.
if (!Arrays.equals(CryptoConditionWriter.writeFulfillment(fulfillment), fulfillmentRawValueBytes)) {
logger.warn("EscrowFinish Fulfillment was malformed: mismatch between raw value and parsed fulfillment. " +
"fulfillmentRawValue() will contain the fulfillment value, but fulfillment() will be empty.");
return this;
}

return EscrowFinish.builder().from(this)
.fulfillment(fulfillment)
.build();
Expand All @@ -318,7 +338,7 @@ default EscrowFinish normalizeFulfillment() {
}

} catch (DerEncodingException e) {
// This should never happen. CryptoconditionWriter.writeCondition errantly declares that it can throw
// This should never happen. CryptoconditionWriter.writeFulfillment errantly declares that it can throw
// a DerEncodingException, but nowhere in its implementation does it throw.
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand All @@ -20,12 +20,15 @@
* =========================LICENSE_END==================================
*/

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.primitives.UnsignedInteger;
import org.immutables.value.Value;
import org.xrpl.xrpl4j.model.client.common.LedgerIndex;
import org.xrpl.xrpl4j.model.jackson.modules.BaseFeeDropsDeserializer;

import java.util.Optional;

Expand Down Expand Up @@ -53,34 +56,92 @@ static ImmutableSetFee.Builder builder() {
* The charge, in drops of XRP, for the reference transaction, as hex. (This is the transaction cost before scaling
* for load.)
*
* @return A hex {@link String} basefee value.
* <p>This method only exists for historical purposes. When deserialized from a {@link SetFee} transaction from
* ledgers prior to the {@code XRPFees} amendment, this field will still be set based on {@link #baseFeeDrops()}.
*
* @return A hex {@link String} baseFee value.
*/
@Value.Derived
@JsonIgnore
default String baseFee() {
return baseFeeDrops().value().toString(16);
}

/**
* The charge, in drops of XRP, for the reference transaction (This is the transaction cost before scaling for load).
*
* <p>This field will also be populated with the {@code BaseFee} value from any {@link SetFee} transactions
* that occurred before the XRPFees amendment took effect.</p>
*
* @return An {@link XrpCurrencyAmount}.
*/
@JsonProperty("BaseFee")
String baseFee();
@JsonProperty("BaseFeeDrops")
@JsonAlias("BaseFee")
@JsonDeserialize(using = BaseFeeDropsDeserializer.class)
XrpCurrencyAmount baseFeeDrops();

/**
* The cost, in fee units, of the reference transaction.
* The cost, in fee units, of the reference transaction. {@link SetFee} transactions deserialized from ledgers prior
* to the {@code XRPFees} amendment will always have this field, but transactions deserialized from ledgers post
* {@code XRPFees} activation will never have this field.
*
* @return An {@link UnsignedInteger} cost of ref transaction.
*/
@JsonProperty("ReferenceFeeUnits")
UnsignedInteger referenceFeeUnits();
Optional<UnsignedInteger> referenceFeeUnits();

/**
* The base reserve, in drops.
*
* @return An {@link UnsignedInteger} base reverse value in {@link org.xrpl.xrpl4j.model.client.fees.FeeDrops}.
* <p>This method only exists for historical purposes. When deserialized from a {@link SetFee} transaction from
* ledgers prior to the {@code XRPFees} amendment, this field will still be set based on {@link #reserveBaseDrops()}}.
*
* @return An {@link UnsignedInteger} base reserve value in {@link org.xrpl.xrpl4j.model.client.fees.FeeDrops}.
*/
@JsonProperty("ReserveBase")
UnsignedInteger reserveBase();
@Value.Derived
@JsonIgnore
default UnsignedInteger reserveBase() {
return UnsignedInteger.valueOf(reserveBaseDrops().value().longValue());
}

/**
* The base reserve, as an {@link XrpCurrencyAmount}.
*
* <p>This field will also be populated with the {@code ReserveBase} value from any {@link SetFee} transactions
* that occurred before the XRPFees amendment took effect.</p>
*
* @return An {@link XrpCurrencyAmount}.
*/
@JsonProperty("ReserveBaseDrops")
@JsonAlias("ReserveBase")
XrpCurrencyAmount reserveBaseDrops();

/**
* The incremental reserve, in drops.
*
* <p>This method only exists for historical purposes. When deserialized from a {@link SetFee} transaction from
* ledgers prior to the {@code XRPFees} amendment, this field will still be set based on
* {@link #reserveIncrementDrops()}.
*
* @return An {@link UnsignedInteger} incremental reserve in {@link org.xrpl.xrpl4j.model.client.fees.FeeDrops}.
*/
@JsonProperty("ReserveIncrement")
UnsignedInteger reserveIncrement();
@Value.Derived
@JsonIgnore
default UnsignedInteger reserveIncrement() {
return UnsignedInteger.valueOf(reserveIncrementDrops().value().longValue());
}

/**
* The incremental reserve, as an {@link XrpCurrencyAmount}.
*
* <p>This field will also be populated with the {@code ReserveIncrement} value from any {@link SetFee} transactions
* that occurred before the XRPFees amendment took effect.</p>
*
* @return An {@link XrpCurrencyAmount}.
*/
@JsonProperty("ReserveIncrementDrops")
@JsonAlias("ReserveIncrement")
XrpCurrencyAmount reserveIncrementDrops();

/**
* The index of the ledger version where this pseudo-transaction appears. This distinguishes the pseudo-transaction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.google.common.primitives.UnsignedInteger;
import com.ripple.cryptoconditions.Condition;
import com.ripple.cryptoconditions.CryptoConditionReader;
import com.ripple.cryptoconditions.CryptoConditionWriter;
import com.ripple.cryptoconditions.Fulfillment;
import com.ripple.cryptoconditions.PreimageSha256Condition;
import com.ripple.cryptoconditions.PreimageSha256Fulfillment;
Expand Down Expand Up @@ -176,6 +177,29 @@ void normalizeWithNoConditionAndRawValueForMalformedCondition() {
assertThat(actual.conditionRawValue()).isNotEmpty().get().isEqualTo("1234");
}

/**
* This tests the case where conditionRawValue is present and is parseable to a Condition but when the
* parsed Condition is written to a byte array, the value differs from the conditionRawValue bytes. This
* can occur if the condition raw value contains a valid condition in the first 32 bytes, but also includes
* extra bytes afterward.
*/
@Test
void normalizeConditionWithRawValueThatIsParseableButNotValid() {
EscrowFinish actual = EscrowFinish.builder()
.fee(XrpCurrencyAmount.ofDrops(1))
.account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
.sequence(UnsignedInteger.ONE)
.owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
.offerSequence(UnsignedInteger.ZERO)
.conditionRawValue(GOOD_CONDITION_STR + GOOD_CONDITION_STR)
.build();

assertThat(actual.condition()).isEmpty();
assertThat(actual.conditionRawValue()).isNotEmpty().get().isEqualTo(
GOOD_CONDITION_STR + GOOD_CONDITION_STR
);
}

@Test
void normalizeWithNoConditionAndRawValueForBadHexLengthCondition() {
EscrowFinish actual = EscrowFinish.builder()
Expand Down Expand Up @@ -284,6 +308,29 @@ void normalizeWithNoFulfillmentAndRawValueForMalformedFulfillment() {
assertThat(actual.fulfillmentRawValue()).isNotEmpty().get().isEqualTo("1234");
}

/**
* This tests the case where fulfillmentRawValue is present and is parseable to a Fulfillment<?> but when the
* parsed Fulfillment is written to a byte array, the value differs from the fulfillmentRawValue bytes. This
* can occur if the fulfillment raw value contains a valid fulfillment in the first 32 bytes, but also includes
* extra bytes afterward, such as in transaction 138543329687544CDAFCD3AB0DCBFE9C4F8E710397747BA7155F19426F493C8D.
*/
@Test
void normalizeFulfillmentWithRawValueThatIsParseableButNotValid() {
EscrowFinish actual = EscrowFinish.builder()
.fee(XrpCurrencyAmount.ofDrops(1))
.account(Address.of("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59Ba"))
.sequence(UnsignedInteger.ONE)
.owner(Address.of("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"))
.offerSequence(UnsignedInteger.ZERO)
.fulfillmentRawValue(GOOD_FULFILLMENT_STR + GOOD_FULFILLMENT_STR)
.build();

assertThat(actual.fulfillment()).isEmpty();
assertThat(actual.fulfillmentRawValue()).isNotEmpty().get().isEqualTo(
GOOD_FULFILLMENT_STR + GOOD_FULFILLMENT_STR
);
}

@Test
void normalizeWithNoFulfillmentAndRawValueForBadHexLengthFulfillment() {
EscrowFinish actual = EscrowFinish.builder()
Expand Down
Loading

0 comments on commit 8be98b1

Please sign in to comment.