Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/XRPLF/xrpl4j into mpt-amounts
Browse files Browse the repository at this point in the history
  • Loading branch information
nkramer44 committed Nov 9, 2024
2 parents ae996c6 + 18f9dab commit 4133b2e
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 20 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ current [BOM](https://howtodoinjava.com/maven/maven-bom-bill-of-materials-depend
<dependency>
<groupId>org.xrpl</groupId>
<artifactId>xrpl4j-bom</artifactId>
<version>3.5.1</version>
<version>4.0.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Expand Down Expand Up @@ -225,7 +225,7 @@ canonical JSON encoding). Read more about each here:

Xrpl4j is structured as a Maven multi-module project, with the following modules:

- **xrpl4j-core**: [![javadoc](https://javadoc.io/badge2/org.xrpl/xrpl4j-binary-codec/javadoc.svg?color=blue)](https://javadoc.io/doc/org.xrpl/xrpl4j-binary-codec)
- **xrpl4j-core**: [![javadoc](https://javadoc.io/badge2/org.xrpl/xrpl4j-core/javadoc.svg?color=blue)](https://javadoc.io/doc/org.xrpl/xrpl4j-core)
- Provides core primitives like seeds, public/private keys definitions (supports secp256k1 and ed25519 key types
and signing algorithms), signature interfaces, address and binary codecs etc. Also provides Java objects which model XRP Ledger objects,
as well as request parameters and response results for the `rippled` websocket and JSON RPC APIs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,14 @@ default LedgerEntryType ledgerEntryType() {
@JsonProperty("AMMID")
Optional<Hash256> ammId();

/**
* An arbitrary 256-bit value that users can set.
*
* @return An {@link Optional} {@link String}.
*/
@JsonProperty("WalletLocator")
Optional<String> walletLocator();

/**
* The unique ID of this {@link AccountRootObject} ledger object.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
Expand Down Expand Up @@ -261,6 +263,7 @@ default AccountSet normalizeSetFlag() {
* @return An {@link Optional} of type {@link String} containing the domain.
*/
@JsonProperty("Domain")
@JsonInclude(Include.NON_ABSENT)
Optional<String> domain();

/**
Expand All @@ -278,6 +281,7 @@ default AccountSet normalizeSetFlag() {
* @return An {@link Optional} of type {@link String} containing the messaging public key.
*/
@JsonProperty("MessageKey")
@JsonInclude(Include.NON_ABSENT)
Optional<String> messageKey();

/**
Expand Down Expand Up @@ -307,6 +311,23 @@ default AccountSet normalizeSetFlag() {
@JsonProperty("NFTokenMinter")
Optional<Address> mintAccount();

/**
* An arbitrary 256-bit value. If specified, the value is stored as part of the account but has no inherent meaning
* or requirements.
*
* @return The 256-bit value as a hex encoded {@link String}.
*/
@JsonProperty("WalletLocator")
Optional<String> walletLocator();

/**
* Not used. This field is valid in AccountSet transactions but does nothing.
*
* @return An optionally present {@link UnsignedInteger}.
*/
@JsonProperty("WalletSize")
Optional<UnsignedInteger> walletSize();

/**
* Check email hash length.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,42 @@ public void serializeAccountSetTransactionWithNetworkId() throws JsonProcessingE
assertSerializesAndDeserializes(accountSet, expectedBinary);
}

@Test
public void serializeAccountSetTransactionWithPresentOptionalStringFields() throws JsonProcessingException {
AccountSet accountSet = AccountSet.builder()
.account(Address.of("rpP2GdsQwenNnFPefbXFgiTvEgJWQpq8Rw"))
.fee(XrpCurrencyAmount.ofDrops(10))
.sequence(UnsignedInteger.valueOf(10598))
.networkId(NetworkId.of(UnsignedInteger.MAX_VALUE))
.domain("ABCD")
.messageKey("03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB")
.emailHash("F9879D71855B5FF21E4963273A886BFC")
.walletLocator("F9879D71855B5FF21E4963273A886BFCF9879D71855B5FF21E4963273A886BFC")
.build();

String expectedBinary = "12000321FFFFFFFF240000296641F9879D71855B5FF21E4963273A886BFC57F98" +
"79D71855B5FF21E4963273A886BFCF9879D71855B5FF21E4963273A886BFC68400000000000000A722103AB" +
"40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB73007702ABCD81140F3D0C7D2" +
"CFAB2EC8295451F0B3CA038E8E9CDCD";
assertSerializesAndDeserializes(accountSet, expectedBinary);
}

@Test
public void serializeAccountSetTransactionWithEmptyStrings() throws JsonProcessingException {
AccountSet accountSet = AccountSet.builder()
.account(Address.of("rpP2GdsQwenNnFPefbXFgiTvEgJWQpq8Rw"))
.fee(XrpCurrencyAmount.ofDrops(10))
.sequence(UnsignedInteger.valueOf(10598))
.networkId(NetworkId.of(UnsignedInteger.MAX_VALUE))
.domain("")
.messageKey("")
.build();

String expectedBinary = "12000321FFFFFFFF240000296668400000000000000A72007300" +
"770081140F3D0C7D2CFAB2EC8295451F0B3CA038E8E9CDCD";
assertSerializesAndDeserializes(accountSet, expectedBinary);
}

@Test
public void serializeAccountSetTransactionWithEmptyFlags() throws JsonProcessingException {
AccountSet accountSet = AccountSet.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,28 @@
import org.xrpl.xrpl4j.model.flags.TransactionFlags;
import org.xrpl.xrpl4j.model.jackson.ObjectMapperFactory;
import org.xrpl.xrpl4j.model.transactions.Address;
import org.xrpl.xrpl4j.model.transactions.Memo;
import org.xrpl.xrpl4j.model.transactions.MemoWrapper;
import org.xrpl.xrpl4j.model.transactions.Payment;
import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount;

import java.util.Arrays;
import java.util.Collections;

/**
* Unit tests for {@link SingleSignedTransaction}.
*/
class SignedTransactionTest {

/**
* This test constructs the transaction found here:
* https://livenet.xrpl.org/transactions/A7AE53FE15B02E6E2F3C610FB4BA30B12392EB110F1D5E8C20880555E8639B05 to check
* that the hash that's on livenet matches what this library computes. The hash you see in this test is different than
* the hash found on livenet because the real transaction did not set any flags on the transaction and {@link Payment}
* requires a flags field (Even if you set flags to 0, it affects the hash). However, we made {@link Payment#flags()}
* nullable during development and verified that the hashes match, so we are confident that our hash calculation is
* accurate.
* This test constructs the transaction with hash A7AE53FE15B02E6E2F3C610FB4BA30B12392EB110F1D5E8C20880555E8639B05 to
* check that the hash that's on livenet matches what this library computes. The hash you see in this test is
* different from the hash found on livenet because the real transaction did not set any flags on the transaction and
* {@link Payment} requires a flags field (Even if you set flags to 0, it affects the hash). However, we made
* {@link Payment#flags()} nullable during development and verified that the hashes match, so we are confident that
* our hash calculation is accurate.
*
* @see "https://livenet.xrpl.org/transactions/A7AE53FE15B02E6E2F3C610FB4BA30B12392EB110F1D5E8C20880555E8639B05"
*/
@Test
public void computesCorrectTransactionHash() throws JsonProcessingException {
Expand All @@ -65,31 +71,132 @@ public void computesCorrectTransactionHash() throws JsonProcessingException {
.destinationTag(UnsignedInteger.valueOf(371969))
.build();

final Signature signature = Signature.fromBase16(
"304502210093257D8E88D2A92CE55977641F72CCD235AB76B1AE189BE3377F30A69B131C49" +
"02200B79836114069F0D331418D05818908D85DE755AE5C2DDF42E9637FE1C11754F"
);

final Payment signedPayment = Payment.builder().from(unsignedTransaction)
.transactionSignature(Signature.fromBase16(
"304502210093257D8E88D2A92CE55977641F72CCD235AB76B1AE189BE3377F30A6" +
"9B131C4902200B79836114069F0D331418D05818908D85DE755AE5C2DDF42E9637FE1C11754F"
.transactionSignature(signature)
.build();

SingleSignedTransaction<Payment> signedTransaction = SingleSignedTransaction.<Payment>builder()
.signedTransaction(signedPayment)
.signature(signature)
.unsignedTransaction(unsignedTransaction)
.build();

String expectedHash = "F847C96B2EEB0609F16C9DB9D74A0CB123B5EAF5B626207977335BF0A1EF53C3";
assertThat(signedTransaction.hash().value()).isEqualTo(expectedHash);
assertThat(signedTransaction.unsignedTransaction()).isEqualTo(unsignedTransaction);
assertThat(signedTransaction.signedTransaction()).isEqualTo(signedPayment);
assertThat(signedTransaction.signedTransactionBytes().hexValue()).isEqualTo(
XrplBinaryCodec.getInstance().encode(ObjectMapperFactory.create().writeValueAsString(signedPayment))
);
}

/**
* This test constructs the transaction with hash 1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997 to
* check that the hash that's on livenet matches what this library computes.
*
* @see "https://livenet.xrpl.org/transactions/1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997"
*/
@Test
public void computesCorrectTransactionHashWithUnsetFlags() throws JsonProcessingException {
final Payment unsignedTransaction = Payment.builder()
.account(Address.of("rGWx7VAsnwVKRbPFPpvy8Lo4nFf5xjj6Zb"))
.amount(XrpCurrencyAmount.ofDrops(1))
.destination(Address.of("rxRpSNb1VktvzBz8JF2oJC6qaww6RZ7Lw"))
.fee(XrpCurrencyAmount.ofDrops(12))
.flags(PaymentFlags.of(TransactionFlags.UNSET.getValue())) // 0
.lastLedgerSequence(UnsignedInteger.valueOf(86481544))
.memos(Collections.singletonList(
MemoWrapper.builder()
.memo(Memo.builder()
.memoData("7B226F70223A226D696E74222C22616D6F756E74223A22313030303030303030222C22677061223A2230227D")
.build())
.build()
))
.sequence(UnsignedInteger.valueOf(84987644))
.signingPublicKey(
PublicKey.fromBase16EncodedPublicKey("ED05DC98B76FCD734BD44CDF153C34F79728485D2F24F9381CF7A284223EA258CE")
)
.build();

final Signature signature = Signature.builder().value(
UnsignedByteArray.of(BaseEncoding.base16()
.decode("304502210093257D8E88D2A92CE55977641F72CCD235AB76B1AE189BE3377F30A69B131C49" +
"02200B79836114069F0D331418D05818908D85DE755AE5C2DDF42E9637FE1C11754F"))
).build();
final Signature signature = Signature.fromBase16(
"ED6F91CCF14EE94EB072C7671A397A313E3E5CBDAFE773BB6B2F07A0E75A7E65F84B5516268DAEE12902265256" +
"EA1EF046B200148E14FF4E720C06519FD7F40F"
);

final Payment signedPayment = Payment.builder().from(unsignedTransaction)
.transactionSignature(signature)
.build();

SingleSignedTransaction<Payment> signedTransaction = SingleSignedTransaction.<Payment>builder()
.signedTransaction(signedPayment)
.signature(signature)
.unsignedTransaction(unsignedTransaction)
.build();

String expectedHash = "F847C96B2EEB0609F16C9DB9D74A0CB123B5EAF5B626207977335BF0A1EF53C3";
String expectedHash = "1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997";
assertThat(signedTransaction.hash().value()).isEqualTo(expectedHash);
assertThat(signedTransaction.unsignedTransaction()).isEqualTo(unsignedTransaction);
assertThat(signedTransaction.signedTransaction()).isEqualTo(signedPayment);
assertThat(signedTransaction.signedTransactionBytes().hexValue()).isEqualTo(
XrplBinaryCodec.getInstance().encode(ObjectMapperFactory.create().writeValueAsString(signedPayment))
);
}

/**
* This test constructs the transaction with hash 1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997 to
* check that the hash that's on livenet _does not_ match when the signature is supplied incorrectly (i.e., this test
* validates that a transaction's signature is always used to compute a transaction hash).
*
* @see "https://livenet.xrpl.org/transactions/1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997"
*/
@Test
public void computesIncorrectTransactionHashWithoutSignature() throws JsonProcessingException {
final Payment unsignedTransaction = Payment.builder()
.account(Address.of("rGWx7VAsnwVKRbPFPpvy8Lo4nFf5xjj6Zb"))
.amount(XrpCurrencyAmount.ofDrops(1))
.destination(Address.of("rxRpSNb1VktvzBz8JF2oJC6qaww6RZ7Lw"))
.fee(XrpCurrencyAmount.ofDrops(12))
.flags(PaymentFlags.of(TransactionFlags.UNSET.getValue())) // 0
.lastLedgerSequence(UnsignedInteger.valueOf(86481544))
.memos(Collections.singletonList(
MemoWrapper.builder()
.memo(Memo.builder()
.memoData("7B226F70223A226D696E74222C22616D6F756E74223A22313030303030303030222C22677061223A2230227D")
.build())
.build()
))
.sequence(UnsignedInteger.valueOf(84987644))
.signingPublicKey(
PublicKey.fromBase16EncodedPublicKey("ED05DC98B76FCD734BD44CDF153C34F79728485D2F24F9381CF7A284223EA258CE")
)
.build();

final Signature emptySignature = Signature.fromBase16("");

final Payment signedPayment = Payment.builder().from(unsignedTransaction)
.transactionSignature(emptySignature)
.build();

SingleSignedTransaction<Payment> signedTransaction = SingleSignedTransaction.<Payment>builder()
.signedTransaction(signedPayment)
.signature(emptySignature)
.unsignedTransaction(unsignedTransaction)
.build();

String expectedHash = "1A1953AC3BA3123254AA912CE507514A6AAD05EED8981A870B45F604936F0997";
assertThat(signedTransaction.hash().value()).isNotEqualTo(expectedHash);
assertThat(signedTransaction.hash().value()).isEqualTo(
"8E0EDE65ECE8A03ABDD7926B994B2F6F14514FDBD46714F4F511143A1F01A6D0"
);
assertThat(signedTransaction.unsignedTransaction()).isEqualTo(unsignedTransaction);
assertThat(signedTransaction.signedTransaction()).isEqualTo(signedPayment);
assertThat(signedTransaction.signedTransactionBytes().hexValue()).isEqualTo(
XrplBinaryCodec.getInstance().encode(ObjectMapperFactory.create().writeValueAsString(signedPayment))
);
}
}
Loading

0 comments on commit 4133b2e

Please sign in to comment.