From 90f3ad5b583ebd1abf8d378a19bdbbe201a483fe Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Sun, 20 Mar 2022 20:29:25 -0700 Subject: [PATCH 01/29] #410: updated xdr definitions for Protocol 19, add SignedPayload Signer from CAP40 into StrKey encoding/decoding. --- CHANGELOG.md | 6 + readme.md | 6 + src/main/java/org/stellar/sdk/Predicate.java | 19 +- .../org/stellar/sdk/SignedPayloadSigner.java | 45 +++ src/main/java/org/stellar/sdk/Signer.java | 24 ++ src/main/java/org/stellar/sdk/StrKey.java | 73 +++- .../java/org/stellar/sdk/Transaction.java | 29 +- .../sdk/xdr/AccountEntryExtensionV2.java | 30 +- .../sdk/xdr/AccountEntryExtensionV3.java | 107 ++++++ .../stellar/sdk/xdr/AllowTrustResultCode.java | 2 +- src/main/java/org/stellar/sdk/xdr/Asset.java | 61 ++-- .../org/stellar/sdk/xdr/ClaimPredicate.java | 38 +- .../stellar/sdk/xdr/ClaimableBalanceID.java | 30 +- .../sdk/xdr/ClaimableBalanceIDType.java | 5 +- .../org/stellar/sdk/xdr/CryptoKeyType.java | 3 + .../java/org/stellar/sdk/xdr/Duration.java | 60 ++++ .../org/stellar/sdk/xdr/ExtensionPoint.java | 79 +++++ .../org/stellar/sdk/xdr/HashIDPreimage.java | 327 ++++++++++++++++++ .../org/stellar/sdk/xdr/LedgerBounds.java | 84 +++++ .../sdk/xdr/LedgerHeaderExtensionV1.java | 6 +- .../stellar/sdk/xdr/LedgerHeaderFlags.java | 3 +- .../org/stellar/sdk/xdr/OfferEntryFlags.java | 2 +- .../org/stellar/sdk/xdr/PreconditionType.java | 52 +++ .../org/stellar/sdk/xdr/Preconditions.java | 123 +++++++ .../org/stellar/sdk/xdr/PreconditionsV2.java | 206 +++++++++++ .../java/org/stellar/sdk/xdr/SignerKey.java | 107 +++++- .../org/stellar/sdk/xdr/SignerKeyType.java | 5 +- .../org/stellar/sdk/xdr/StellarValue.java | 6 +- .../java/org/stellar/sdk/xdr/Transaction.java | 42 +-- .../org/stellar/sdk/xdr/TrustLineEntry.java | 12 +- .../stellar/sdk/SetOptionsOperationTest.java | 43 +++ src/test/java/org/stellar/sdk/SignerTest.java | 45 +++ src/test/java/org/stellar/sdk/StrKeyTest.java | 63 +++- .../java/org/stellar/sdk/TransactionTest.java | 13 +- .../sdk/xdr/AccountEntryDecodeTest.java | 50 +++ xdr/Stellar-ledger-entries.x | 39 ++- xdr/Stellar-ledger.x | 12 +- xdr/Stellar-transaction.x | 67 +++- xdr/Stellar-types.x | 21 +- 39 files changed, 1757 insertions(+), 188 deletions(-) create mode 100644 src/main/java/org/stellar/sdk/SignedPayloadSigner.java create mode 100644 src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV3.java create mode 100644 src/main/java/org/stellar/sdk/xdr/Duration.java create mode 100644 src/main/java/org/stellar/sdk/xdr/ExtensionPoint.java create mode 100644 src/main/java/org/stellar/sdk/xdr/HashIDPreimage.java create mode 100644 src/main/java/org/stellar/sdk/xdr/LedgerBounds.java create mode 100644 src/main/java/org/stellar/sdk/xdr/PreconditionType.java create mode 100644 src/main/java/org/stellar/sdk/xdr/Preconditions.java create mode 100644 src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java create mode 100644 src/test/java/org/stellar/sdk/SetOptionsOperationTest.java create mode 100644 src/test/java/org/stellar/sdk/SignerTest.java create mode 100644 src/test/java/org/stellar/sdk/xdr/AccountEntryDecodeTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index b745eb23b..aa6114035 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ As this project is pre 1.0, breaking changes may happen for minor version bumps. A breaking change will get clearly notified in this log. +## 0.32.0 (Pending) + +* Update XDR definitions and auto-generated classes to support upcoming protocol 19 release ([#276](https://github.com/stellar/java-stellar-sdk/pull/276)). +* Extend StrKey implementation to handle [CAP 40 Payload Signer](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0040.md) ([#276](https://github.com/stellar/java-stellar-sdk/pull/276)). + + ## 0.31.0 * Fixed NPE on TrustlineCreatedEffectResponse.getAsset() for liquidity pool asset type. diff --git a/readme.md b/readme.md index 2cb0a606b..a1a2c1202 100644 --- a/readme.md +++ b/readme.md @@ -38,3 +38,9 @@ For information on how to contribute, please refer to our [contribution guide](h ## License java-stellar-sdk is licensed under an Apache-2.0 license. See the [LICENSE](https://github.com/stellar/java-stellar-sdk/blob/master/LICENSE) file for details. + +## xdr to jave code generation +All the java source files in org.stellar.sdk.xdr package are generated using the `xdrgen` command from the [stellar/xdrgen](https://github.com/stellar/xdrgen) +``` +xdrgen -o ./src/main/java/org/stellar/sdk/xdr -l java -n org.stellar.sdk.xdr ./xdr/Stellar-types.x ./xdr/Stellar-SCP.x ./xdr/Stellar-overlay.x ./xdr/Stellar-ledger-entries.x ./xdr/Stellar-ledger.x ./xdr/Stellar-transaction.x +``` \ No newline at end of file diff --git a/src/main/java/org/stellar/sdk/Predicate.java b/src/main/java/org/stellar/sdk/Predicate.java index 045fc1eec..35d7c7617 100644 --- a/src/main/java/org/stellar/sdk/Predicate.java +++ b/src/main/java/org/stellar/sdk/Predicate.java @@ -4,7 +4,10 @@ import com.google.common.collect.Lists; import org.stellar.sdk.xdr.ClaimPredicate; import org.stellar.sdk.xdr.ClaimPredicateType; +import org.stellar.sdk.xdr.Duration; import org.stellar.sdk.xdr.Int64; +import org.stellar.sdk.xdr.TimePoint; +import org.stellar.sdk.xdr.Uint64; import org.threeten.bp.Instant; import java.util.List; @@ -34,9 +37,9 @@ public static Predicate fromXdr(org.stellar.sdk.xdr.ClaimPredicate xdr) { case CLAIM_PREDICATE_NOT: return new Not(fromXdr(xdr.getNotPredicate())); case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME: - return new RelBefore(xdr.getRelBefore().getInt64()); + return new RelBefore(xdr.getRelBefore().getDuration().getInt64()); case CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME: - return new AbsBefore(xdr.getAbsBefore().getInt64()); + return new AbsBefore(xdr.getAbsBefore().getTimePoint().getUint64()); default: throw new IllegalArgumentException("Unknown asset type " + xdr.getDiscriminant()); } @@ -209,9 +212,11 @@ public int hashCode() { public ClaimPredicate toXdr() { org.stellar.sdk.xdr.ClaimPredicate xdr = new org.stellar.sdk.xdr.ClaimPredicate(); xdr.setDiscriminant(ClaimPredicateType.CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME); - Int64 t = new Int64(); - t.setInt64(epochSeconds); - xdr.setAbsBefore(t); + Uint64 t = new Uint64(); + t.setUint64(epochSeconds); + TimePoint beforeTime = new TimePoint(); + beforeTime.setTimePoint(t); + xdr.setAbsBefore(beforeTime); return xdr; } } @@ -246,7 +251,9 @@ public ClaimPredicate toXdr() { xdr.setDiscriminant(ClaimPredicateType.CLAIM_PREDICATE_BEFORE_RELATIVE_TIME); Int64 t = new Int64(); t.setInt64(secondsSinceClose); - xdr.setRelBefore(t); + Duration beforeTime = new Duration(); + beforeTime.setDuration(t); + xdr.setRelBefore(beforeTime); return xdr; } } diff --git a/src/main/java/org/stellar/sdk/SignedPayloadSigner.java b/src/main/java/org/stellar/sdk/SignedPayloadSigner.java new file mode 100644 index 000000000..06e573a5c --- /dev/null +++ b/src/main/java/org/stellar/sdk/SignedPayloadSigner.java @@ -0,0 +1,45 @@ +package org.stellar.sdk; + +/** + * Data model for the signed payload signer + */ +public class SignedPayloadSigner { + private String accountId; + private byte[] payload; + + /** + * constructor + * + * @param accountId - the StrKey format of a stellar AccountId + * @param payload - the raw payload for a signed payload + */ + public SignedPayloadSigner(String accountId, byte[] payload) { + this.accountId = accountId; + this.payload = payload; + } + + /** + * get the StrKey encoded representation of a stellar account id + * @return stellar account id in StrKey encoding + */ + public String getEncodedAccountId() { + return accountId; + } + + /** + * get the binary format of a stellar account id, a Ed25519 public key + * @return stellar account id in binary format + */ + public byte[] getDecodedAccountId() { + return StrKey.decodeStellarAccountId(accountId); + } + + /** + * get the payload that signatures are produced from. + * @see + * @return + */ + public byte[] getPayload() { + return payload; + } +} diff --git a/src/main/java/org/stellar/sdk/Signer.java b/src/main/java/org/stellar/sdk/Signer.java index 8f1e91ecf..cbf356af0 100644 --- a/src/main/java/org/stellar/sdk/Signer.java +++ b/src/main/java/org/stellar/sdk/Signer.java @@ -4,12 +4,15 @@ import org.stellar.sdk.xdr.SignerKeyType; import org.stellar.sdk.xdr.Uint256; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; /** * Signer is a helper class that creates {@link org.stellar.sdk.xdr.SignerKey} objects. */ public class Signer { + public static final int SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH = 64; + /** * Create ed25519PublicKey {@link org.stellar.sdk.xdr.SignerKey} from * a {@link org.stellar.sdk.KeyPair} @@ -72,6 +75,27 @@ public static SignerKey preAuthTx(byte[] hash) { return signerKey; } + /** + * Create SignerKey {@link org.stellar.sdk.xdr.SignerKey} from {@link org.stellar.sdk.SignedPayloadSigner} + * + * @param signedPayloadSigner - signed payload values + * @return org.stellar.sdk.xdr.SignerKey + */ + public static SignerKey signedPayload(SignedPayloadSigner signedPayloadSigner) { + checkNotNull(signedPayloadSigner.getEncodedAccountId(), "accountId cannot be null"); + checkArgument(signedPayloadSigner.getPayload().length <= SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH ); + + SignerKey signerKey = new SignerKey(); + SignerKey.SignerKeyEd25519SignedPayload payloadSigner = new SignerKey.SignerKeyEd25519SignedPayload(); + payloadSigner.setPayload(signedPayloadSigner.getPayload()); + payloadSigner.setEd25519(createUint256(signedPayloadSigner.getDecodedAccountId())); + + signerKey.setDiscriminant(SignerKeyType.SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD); + signerKey.setEd25519SignedPayload(payloadSigner); + + return signerKey; + } + private static Uint256 createUint256(byte[] hash) { if (hash.length != 32) { throw new RuntimeException("hash must be 32 bytes long"); diff --git a/src/main/java/org/stellar/sdk/StrKey.java b/src/main/java/org/stellar/sdk/StrKey.java index 438814718..7a0b787b9 100644 --- a/src/main/java/org/stellar/sdk/StrKey.java +++ b/src/main/java/org/stellar/sdk/StrKey.java @@ -1,14 +1,29 @@ package org.stellar.sdk; -import com.google.common.io.BaseEncoding; import com.google.common.base.Optional; +import com.google.common.io.BaseEncoding; import com.google.common.primitives.Bytes; import com.google.common.primitives.Longs; -import org.stellar.sdk.xdr.*; - -import java.io.*; +import org.stellar.sdk.xdr.AccountID; +import org.stellar.sdk.xdr.CryptoKeyType; +import org.stellar.sdk.xdr.MuxedAccount; +import org.stellar.sdk.xdr.PublicKey; +import org.stellar.sdk.xdr.PublicKeyType; +import org.stellar.sdk.xdr.Uint256; +import org.stellar.sdk.xdr.Uint64; +import org.stellar.sdk.xdr.XdrDataInputStream; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.CharArrayWriter; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.util.Arrays; +import static org.stellar.sdk.Signer.SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH; + class StrKey { public static final int ACCOUNT_ID_ADDRESS_LENGTH = 56; @@ -18,7 +33,8 @@ public enum VersionByte { MUXED((byte)(12 << 3)), // M SEED((byte)(18 << 3)), // S PRE_AUTH_TX((byte)(19 << 3)), // T - SHA256_HASH((byte)(23 << 3)); // X + SHA256_HASH((byte)(23 << 3)), // X + SIGNED_PAYLOAD((byte)(15 << 3)); // P private final byte value; VersionByte(byte value) { this.value = value; @@ -49,6 +65,25 @@ public static String encodeStellarAccountId(AccountID accountID) { return String.valueOf(encoded); } + public static String encodeSignedPayload(SignedPayloadSigner signedPayloadSigner) { + if (signedPayloadSigner.getPayload().length > SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH) { + throw new FormatException("invalid payload length, must be less than " + SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH); + } + try { + ByteArrayOutputStream record = new ByteArrayOutputStream(); + DataOutputStream dataStream = new DataOutputStream(record); + dataStream.write(signedPayloadSigner.getDecodedAccountId()); + dataStream.writeInt(signedPayloadSigner.getPayload().length); + dataStream.write(signedPayloadSigner.getPayload()); + int padding = signedPayloadSigner.getPayload().length % 4 > 0 ? 4 - signedPayloadSigner.getPayload().length % 4 : 0; + dataStream.write(new byte[padding]); + char[] encoded = encodeCheck(VersionByte.SIGNED_PAYLOAD, record.toByteArray()); + return String.valueOf(encoded); + } catch (Exception ex) { + throw new FormatException(ex.getMessage()); + } + } + public static String encodeStellarMuxedAccount(MuxedAccount muxedAccount) { switch (muxedAccount.getDiscriminant()) { case KEY_TYPE_MUXED_ED25519: @@ -154,6 +189,22 @@ public static byte[] decodeStellarSecretSeed(char[] data) { return decodeCheck(VersionByte.SEED, data); } + public static SignedPayloadSigner decodeSignedPayload(char[] data) { + try { + byte[] signedPayloadRaw = decodeCheck(VersionByte.SIGNED_PAYLOAD, data); + DataInputStream dataStream = new DataInputStream(new ByteArrayInputStream(signedPayloadRaw)); + byte[] binaryAccountId = new byte[32]; + dataStream.read(binaryAccountId); + int payloadLength = dataStream.readInt(); + byte[] payload = new byte[payloadLength]; + dataStream.read(payload); + + return new SignedPayloadSigner(encodeStellarAccountId(binaryAccountId), payload ); + } catch (Exception ex) { + throw new FormatException(ex.getMessage()); + } + } + public static String encodePreAuthTx(byte[] data) { char[] encoded = encodeCheck(VersionByte.PRE_AUTH_TX, data); return String.valueOf(encoded); @@ -177,20 +228,20 @@ protected static char[] encodeCheck(VersionByte versionByte, byte[] data) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); outputStream.write(versionByte.getValue()); outputStream.write(data); - byte payload[] = outputStream.toByteArray(); - byte checksum[] = StrKey.calculateChecksum(payload); + byte[] payload = outputStream.toByteArray(); + byte[] checksum = StrKey.calculateChecksum(payload); outputStream.write(checksum); - byte unencoded[] = outputStream.toByteArray(); + byte[] unencoded = outputStream.toByteArray(); if (VersionByte.SEED != versionByte) { - return StrKey.base32Encoding.encode(unencoded).toCharArray(); + return base32Encoding.encode(unencoded).toCharArray(); } // Why not use base32Encoding.encode here? // We don't want secret seed to be stored as String in memory because of security reasons. It's impossible // to erase it from memory when we want it to be erased (ASAP). CharArrayWriter charArrayWriter = new CharArrayWriter(unencoded.length); - OutputStream charOutputStream = StrKey.base32Encoding.encodingStream(charArrayWriter); + OutputStream charOutputStream = base32Encoding.encodingStream(charArrayWriter); charOutputStream.write(unencoded); char[] charsEncoded = charArrayWriter.toCharArray(); @@ -242,7 +293,7 @@ protected static byte[] decodeCheck(VersionByte versionByte, char[] encoded) { } } - byte[] decoded = StrKey.base32Encoding.decode(java.nio.CharBuffer.wrap(encoded)); + byte[] decoded = base32Encoding.decode(java.nio.CharBuffer.wrap(encoded)); byte decodedVersionByte = decoded[0]; byte[] payload = Arrays.copyOfRange(decoded, 0, decoded.length-2); byte[] data = Arrays.copyOfRange(payload, 1, payload.length); diff --git a/src/main/java/org/stellar/sdk/Transaction.java b/src/main/java/org/stellar/sdk/Transaction.java index fc90f502f..082d41d20 100644 --- a/src/main/java/org/stellar/sdk/Transaction.java +++ b/src/main/java/org/stellar/sdk/Transaction.java @@ -1,7 +1,23 @@ package org.stellar.sdk; import com.google.common.base.Objects; -import org.stellar.sdk.xdr.*; +import org.stellar.sdk.xdr.ClaimableBalanceID; +import org.stellar.sdk.xdr.ClaimableBalanceIDType; +import org.stellar.sdk.xdr.DecoratedSignature; +import org.stellar.sdk.xdr.EnvelopeType; +import org.stellar.sdk.xdr.Hash; +import org.stellar.sdk.xdr.Int64; +import org.stellar.sdk.xdr.OperationID; +import org.stellar.sdk.xdr.PreconditionType; +import org.stellar.sdk.xdr.Preconditions; +import org.stellar.sdk.xdr.SequenceNumber; +import org.stellar.sdk.xdr.TransactionEnvelope; +import org.stellar.sdk.xdr.TransactionSignaturePayload; +import org.stellar.sdk.xdr.TransactionV0; +import org.stellar.sdk.xdr.TransactionV0Envelope; +import org.stellar.sdk.xdr.TransactionV1Envelope; +import org.stellar.sdk.xdr.Uint32; +import org.stellar.sdk.xdr.XdrDataOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -188,7 +204,11 @@ private org.stellar.sdk.xdr.Transaction toV1Xdr(AccountConverter accountConverte v1Tx.setSourceAccount(accountConverter.encode(mSourceAccount)); v1Tx.setOperations(operations); v1Tx.setMemo(mMemo.toXdr()); - v1Tx.setTimeBounds(mTimeBounds == null ? null : mTimeBounds.toXdr()); + Preconditions.Builder preconditions = new Preconditions.Builder().discriminant(PreconditionType.PRECOND_NONE); + if (mTimeBounds != null) { + preconditions.discriminant(PreconditionType.PRECOND_TIME).timeBounds(mTimeBounds.toXdr()); + } + v1Tx.setCond(preconditions.build()); v1Tx.setExt(ext); return v1Tx; @@ -228,9 +248,12 @@ public static Transaction fromV0EnvelopeXdr(TransactionV0Envelope envelope, Netw public static Transaction fromV1EnvelopeXdr(AccountConverter accountConverter, TransactionV1Envelope envelope, Network network) { int mFee = envelope.getTx().getFee().getUint32(); + TimeBounds mTimeBounds = null; Long mSequenceNumber = envelope.getTx().getSeqNum().getSequenceNumber().getInt64(); Memo mMemo = Memo.fromXdr(envelope.getTx().getMemo()); - TimeBounds mTimeBounds = TimeBounds.fromXdr(envelope.getTx().getTimeBounds()); + if (envelope.getTx().getCond().getDiscriminant().equals(PreconditionType.PRECOND_TIME)) { + mTimeBounds = TimeBounds.fromXdr(envelope.getTx().getCond().getTimeBounds()); + } Operation[] mOperations = new Operation[envelope.getTx().getOperations().length]; for (int i = 0; i < envelope.getTx().getOperations().length; i++) { diff --git a/src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV2.java b/src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV2.java index d92131ea2..1911d96f5 100644 --- a/src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV2.java +++ b/src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV2.java @@ -4,9 +4,9 @@ package org.stellar.sdk.xdr; -import java.io.IOException; - import com.google.common.base.Objects; + +import java.io.IOException; import java.util.Arrays; // === xdr source ============================================================ @@ -21,6 +21,8 @@ // { // case 0: // void; +// case 3: +// AccountEntryExtensionV3 v3; // } // ext; // }; @@ -140,18 +142,32 @@ public Integer getDiscriminant() { public void setDiscriminant(Integer value) { this.v = value; } + private AccountEntryExtensionV3 v3; + public AccountEntryExtensionV3 getV3() { + return this.v3; + } + public void setV3(AccountEntryExtensionV3 value) { + this.v3 = value; + } public static final class Builder { private Integer discriminant; + private AccountEntryExtensionV3 v3; public Builder discriminant(Integer discriminant) { this.discriminant = discriminant; return this; } + public Builder v3(AccountEntryExtensionV3 v3) { + this.v3 = v3; + return this; + } + public AccountEntryExtensionV2Ext build() { AccountEntryExtensionV2Ext val = new AccountEntryExtensionV2Ext(); val.setDiscriminant(discriminant); + val.setV3(v3); return val; } } @@ -163,6 +179,9 @@ public static void encode(XdrDataOutputStream stream, AccountEntryExtensionV2Ext switch (encodedAccountEntryExtensionV2Ext.getDiscriminant()) { case 0: break; + case 3: + AccountEntryExtensionV3.encode(stream, encodedAccountEntryExtensionV2Ext.v3); + break; } } public void encode(XdrDataOutputStream stream) throws IOException { @@ -175,12 +194,15 @@ public static AccountEntryExtensionV2Ext decode(XdrDataInputStream stream) throw switch (decodedAccountEntryExtensionV2Ext.getDiscriminant()) { case 0: break; + case 3: + decodedAccountEntryExtensionV2Ext.v3 = AccountEntryExtensionV3.decode(stream); + break; } return decodedAccountEntryExtensionV2Ext; } @Override public int hashCode() { - return Objects.hashCode(this.v); + return Objects.hashCode(this.v3, this.v); } @Override public boolean equals(Object object) { @@ -189,7 +211,7 @@ public boolean equals(Object object) { } AccountEntryExtensionV2Ext other = (AccountEntryExtensionV2Ext) object; - return Objects.equal(this.v, other.v); + return Objects.equal(this.v3, other.v3) && Objects.equal(this.v, other.v); } } diff --git a/src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV3.java b/src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV3.java new file mode 100644 index 000000000..a63b1dee5 --- /dev/null +++ b/src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV3.java @@ -0,0 +1,107 @@ +// Automatically generated by xdrgen +// DO NOT EDIT or your changes may be overwritten + +package org.stellar.sdk.xdr; + + +import com.google.common.base.Objects; + +import java.io.IOException; + +// === xdr source ============================================================ + +// struct AccountEntryExtensionV3 +// { +// // We can use this to add more fields, or because it is first, to +// // change AccountEntryExtensionV3 into a union. +// ExtensionPoint ext; +// +// // Ledger number at which `seqNum` took on its present value. +// uint32 seqLedger; +// +// // Time at which `seqNum` took on its present value. +// TimePoint seqTime; +// }; + +// =========================================================================== +public class AccountEntryExtensionV3 implements XdrElement { + public AccountEntryExtensionV3 () {} + private ExtensionPoint ext; + public ExtensionPoint getExt() { + return this.ext; + } + public void setExt(ExtensionPoint value) { + this.ext = value; + } + private Uint32 seqLedger; + public Uint32 getSeqLedger() { + return this.seqLedger; + } + public void setSeqLedger(Uint32 value) { + this.seqLedger = value; + } + private TimePoint seqTime; + public TimePoint getSeqTime() { + return this.seqTime; + } + public void setSeqTime(TimePoint value) { + this.seqTime = value; + } + public static void encode(XdrDataOutputStream stream, AccountEntryExtensionV3 encodedAccountEntryExtensionV3) throws IOException{ + ExtensionPoint.encode(stream, encodedAccountEntryExtensionV3.ext); + Uint32.encode(stream, encodedAccountEntryExtensionV3.seqLedger); + TimePoint.encode(stream, encodedAccountEntryExtensionV3.seqTime); + } + public void encode(XdrDataOutputStream stream) throws IOException { + encode(stream, this); + } + public static AccountEntryExtensionV3 decode(XdrDataInputStream stream) throws IOException { + AccountEntryExtensionV3 decodedAccountEntryExtensionV3 = new AccountEntryExtensionV3(); + decodedAccountEntryExtensionV3.ext = ExtensionPoint.decode(stream); + decodedAccountEntryExtensionV3.seqLedger = Uint32.decode(stream); + decodedAccountEntryExtensionV3.seqTime = TimePoint.decode(stream); + return decodedAccountEntryExtensionV3; + } + @Override + public int hashCode() { + return Objects.hashCode(this.ext, this.seqLedger, this.seqTime); + } + @Override + public boolean equals(Object object) { + if (!(object instanceof AccountEntryExtensionV3)) { + return false; + } + + AccountEntryExtensionV3 other = (AccountEntryExtensionV3) object; + return Objects.equal(this.ext, other.ext) && Objects.equal(this.seqLedger, other.seqLedger) && Objects.equal(this.seqTime, other.seqTime); + } + + public static final class Builder { + private ExtensionPoint ext; + private Uint32 seqLedger; + private TimePoint seqTime; + + public Builder ext(ExtensionPoint ext) { + this.ext = ext; + return this; + } + + public Builder seqLedger(Uint32 seqLedger) { + this.seqLedger = seqLedger; + return this; + } + + public Builder seqTime(TimePoint seqTime) { + this.seqTime = seqTime; + return this; + } + + public AccountEntryExtensionV3 build() { + AccountEntryExtensionV3 val = new AccountEntryExtensionV3(); + val.setExt(ext); + val.setSeqLedger(seqLedger); + val.setSeqTime(seqTime); + return val; + } + } +} diff --git a/src/main/java/org/stellar/sdk/xdr/AllowTrustResultCode.java b/src/main/java/org/stellar/sdk/xdr/AllowTrustResultCode.java index b51667c2d..45447edc0 100644 --- a/src/main/java/org/stellar/sdk/xdr/AllowTrustResultCode.java +++ b/src/main/java/org/stellar/sdk/xdr/AllowTrustResultCode.java @@ -21,7 +21,7 @@ // ALLOW_TRUST_CANT_REVOKE = -4, // source account can't revoke trust, // ALLOW_TRUST_SELF_NOT_ALLOWED = -5, // trusting self is not allowed // ALLOW_TRUST_LOW_RESERVE = -6 // claimable balances can't be created -// // on revoke due to low reserves +// // on revoke due to low reserves // }; // =========================================================================== diff --git a/src/main/java/org/stellar/sdk/xdr/Asset.java b/src/main/java/org/stellar/sdk/xdr/Asset.java index 3093903bf..0e9ccc672 100644 --- a/src/main/java/org/stellar/sdk/xdr/Asset.java +++ b/src/main/java/org/stellar/sdk/xdr/Asset.java @@ -4,10 +4,10 @@ package org.stellar.sdk.xdr; -import java.io.IOException; - import com.google.common.base.Objects; +import java.io.IOException; + // === xdr source ============================================================ // union Asset switch (AssetType type) @@ -79,44 +79,39 @@ public Asset build() { } public static void encode(XdrDataOutputStream stream, Asset encodedAsset) throws IOException { - //Xdrgen::AST::Identifier - //AssetType - stream.writeInt(encodedAsset.getDiscriminant().getValue()); - switch (encodedAsset.getDiscriminant()) { - case ASSET_TYPE_NATIVE: - break; - case ASSET_TYPE_CREDIT_ALPHANUM4: - AlphaNum4.encode(stream, encodedAsset.alphaNum4); - break; - case ASSET_TYPE_CREDIT_ALPHANUM12: - AlphaNum12.encode(stream, encodedAsset.alphaNum12); - break; - case ASSET_TYPE_POOL_SHARE: - throw new RuntimeException("Invalid asset type"); - } + //Xdrgen::AST::Identifier + //AssetType + stream.writeInt(encodedAsset.getDiscriminant().getValue()); + switch (encodedAsset.getDiscriminant()) { + case ASSET_TYPE_NATIVE: + break; + case ASSET_TYPE_CREDIT_ALPHANUM4: + AlphaNum4.encode(stream, encodedAsset.alphaNum4); + break; + case ASSET_TYPE_CREDIT_ALPHANUM12: + AlphaNum12.encode(stream, encodedAsset.alphaNum12); + break; + } } public void encode(XdrDataOutputStream stream) throws IOException { encode(stream, this); } public static Asset decode(XdrDataInputStream stream) throws IOException { - Asset decodedAsset = new Asset(); - AssetType discriminant = AssetType.decode(stream); - decodedAsset.setDiscriminant(discriminant); - switch (decodedAsset.getDiscriminant()) { - case ASSET_TYPE_NATIVE: - break; - case ASSET_TYPE_CREDIT_ALPHANUM4: - decodedAsset.alphaNum4 = AlphaNum4.decode(stream); - break; - case ASSET_TYPE_CREDIT_ALPHANUM12: - decodedAsset.alphaNum12 = AlphaNum12.decode(stream); - break; - case ASSET_TYPE_POOL_SHARE: - throw new RuntimeException("Invalid asset type"); - } + Asset decodedAsset = new Asset(); + AssetType discriminant = AssetType.decode(stream); + decodedAsset.setDiscriminant(discriminant); + switch (decodedAsset.getDiscriminant()) { + case ASSET_TYPE_NATIVE: + break; + case ASSET_TYPE_CREDIT_ALPHANUM4: + decodedAsset.alphaNum4 = AlphaNum4.decode(stream); + break; + case ASSET_TYPE_CREDIT_ALPHANUM12: + decodedAsset.alphaNum12 = AlphaNum12.decode(stream); + break; + } return decodedAsset; } - @Override public int hashCode() { return Objects.hashCode(this.alphaNum4, this.alphaNum12, this.type); diff --git a/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java b/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java index b87de7dd7..404b6282d 100644 --- a/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java +++ b/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java @@ -4,9 +4,9 @@ package org.stellar.sdk.xdr; -import java.io.IOException; - import com.google.common.base.Objects; + +import java.io.IOException; import java.util.Arrays; // === xdr source ============================================================ @@ -22,10 +22,10 @@ // case CLAIM_PREDICATE_NOT: // ClaimPredicate* notPredicate; // case CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME: -// int64 absBefore; // Predicate will be true if closeTime < absBefore +// TimePoint absBefore; // Predicate will be true if closeTime < absBefore // case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME: -// int64 relBefore; // Seconds since closeTime of the ledger in which the -// // ClaimableBalanceEntry was created +// Duration relBefore; // Seconds since closeTime of the ledger in which the +// // ClaimableBalanceEntry was created // }; // =========================================================================== @@ -59,18 +59,18 @@ public ClaimPredicate getNotPredicate() { public void setNotPredicate(ClaimPredicate value) { this.notPredicate = value; } - private Int64 absBefore; - public Int64 getAbsBefore() { + private TimePoint absBefore; + public TimePoint getAbsBefore() { return this.absBefore; } - public void setAbsBefore(Int64 value) { + public void setAbsBefore(TimePoint value) { this.absBefore = value; } - private Int64 relBefore; - public Int64 getRelBefore() { + private Duration relBefore; + public Duration getRelBefore() { return this.relBefore; } - public void setRelBefore(Int64 value) { + public void setRelBefore(Duration value) { this.relBefore = value; } @@ -79,8 +79,8 @@ public static final class Builder { private ClaimPredicate[] andPredicates; private ClaimPredicate[] orPredicates; private ClaimPredicate notPredicate; - private Int64 absBefore; - private Int64 relBefore; + private TimePoint absBefore; + private Duration relBefore; public Builder discriminant(ClaimPredicateType discriminant) { this.discriminant = discriminant; @@ -102,12 +102,12 @@ public Builder notPredicate(ClaimPredicate notPredicate) { return this; } - public Builder absBefore(Int64 absBefore) { + public Builder absBefore(TimePoint absBefore) { this.absBefore = absBefore; return this; } - public Builder relBefore(Int64 relBefore) { + public Builder relBefore(Duration relBefore) { this.relBefore = relBefore; return this; } @@ -154,10 +154,10 @@ public static void encode(XdrDataOutputStream stream, ClaimPredicate encodedClai } break; case CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME: - Int64.encode(stream, encodedClaimPredicate.absBefore); + TimePoint.encode(stream, encodedClaimPredicate.absBefore); break; case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME: - Int64.encode(stream, encodedClaimPredicate.relBefore); + Duration.encode(stream, encodedClaimPredicate.relBefore); break; } } @@ -192,10 +192,10 @@ public static ClaimPredicate decode(XdrDataInputStream stream) throws IOExceptio } break; case CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME: - decodedClaimPredicate.absBefore = Int64.decode(stream); + decodedClaimPredicate.absBefore = TimePoint.decode(stream); break; case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME: - decodedClaimPredicate.relBefore = Int64.decode(stream); + decodedClaimPredicate.relBefore = Duration.decode(stream); break; } return decodedClaimPredicate; diff --git a/src/main/java/org/stellar/sdk/xdr/ClaimableBalanceID.java b/src/main/java/org/stellar/sdk/xdr/ClaimableBalanceID.java index d57036769..c75a39d1e 100644 --- a/src/main/java/org/stellar/sdk/xdr/ClaimableBalanceID.java +++ b/src/main/java/org/stellar/sdk/xdr/ClaimableBalanceID.java @@ -4,18 +4,16 @@ package org.stellar.sdk.xdr; -import java.io.IOException; - import com.google.common.base.Objects; +import java.io.IOException; + // === xdr source ============================================================ // union ClaimableBalanceID switch (ClaimableBalanceIDType type) // { // case CLAIMABLE_BALANCE_ID_TYPE_V0: // Hash v0; -// case CLAIMABLE_BALANCE_ID_TYPE_FROM_POOL_REVOKE: -// Hash fromPoolRevoke; // }; // =========================================================================== @@ -35,18 +33,10 @@ public Hash getV0() { public void setV0(Hash value) { this.v0 = value; } - private Hash fromPoolRevoke; - public Hash getFromPoolRevoke() { - return this.fromPoolRevoke; - } - public void setFromPoolRevoke(Hash value) { - this.fromPoolRevoke = value; - } public static final class Builder { private ClaimableBalanceIDType discriminant; private Hash v0; - private Hash fromPoolRevoke; public Builder discriminant(ClaimableBalanceIDType discriminant) { this.discriminant = discriminant; @@ -58,16 +48,10 @@ public Builder v0(Hash v0) { return this; } - public Builder fromPoolRevoke(Hash fromPoolRevoke) { - this.fromPoolRevoke = fromPoolRevoke; - return this; - } - public ClaimableBalanceID build() { ClaimableBalanceID val = new ClaimableBalanceID(); val.setDiscriminant(discriminant); val.setV0(v0); - val.setFromPoolRevoke(fromPoolRevoke); return val; } } @@ -80,9 +64,6 @@ public static void encode(XdrDataOutputStream stream, ClaimableBalanceID encoded case CLAIMABLE_BALANCE_ID_TYPE_V0: Hash.encode(stream, encodedClaimableBalanceID.v0); break; - case CLAIMABLE_BALANCE_ID_TYPE_FROM_POOL_REVOKE: - Hash.encode(stream, encodedClaimableBalanceID.fromPoolRevoke); - break; } } public void encode(XdrDataOutputStream stream) throws IOException { @@ -96,15 +77,12 @@ public static ClaimableBalanceID decode(XdrDataInputStream stream) throws IOExce case CLAIMABLE_BALANCE_ID_TYPE_V0: decodedClaimableBalanceID.v0 = Hash.decode(stream); break; - case CLAIMABLE_BALANCE_ID_TYPE_FROM_POOL_REVOKE: - decodedClaimableBalanceID.fromPoolRevoke = Hash.decode(stream); - break; } return decodedClaimableBalanceID; } @Override public int hashCode() { - return Objects.hashCode(this.v0, this.fromPoolRevoke, this.type); + return Objects.hashCode(this.v0, this.type); } @Override public boolean equals(Object object) { @@ -113,6 +91,6 @@ public boolean equals(Object object) { } ClaimableBalanceID other = (ClaimableBalanceID) object; - return Objects.equal(this.v0, other.v0) && Objects.equal(this.fromPoolRevoke, other.fromPoolRevoke) && Objects.equal(this.type, other.type); + return Objects.equal(this.v0, other.v0) && Objects.equal(this.type, other.type); } } diff --git a/src/main/java/org/stellar/sdk/xdr/ClaimableBalanceIDType.java b/src/main/java/org/stellar/sdk/xdr/ClaimableBalanceIDType.java index 63c44220c..308ad5c89 100644 --- a/src/main/java/org/stellar/sdk/xdr/ClaimableBalanceIDType.java +++ b/src/main/java/org/stellar/sdk/xdr/ClaimableBalanceIDType.java @@ -11,14 +11,12 @@ // enum ClaimableBalanceIDType // { -// CLAIMABLE_BALANCE_ID_TYPE_V0 = 0, -// CLAIMABLE_BALANCE_ID_TYPE_FROM_POOL_REVOKE = 1 +// CLAIMABLE_BALANCE_ID_TYPE_V0 = 0 // }; // =========================================================================== public enum ClaimableBalanceIDType implements XdrElement { CLAIMABLE_BALANCE_ID_TYPE_V0(0), - CLAIMABLE_BALANCE_ID_TYPE_FROM_POOL_REVOKE(1), ; private int mValue; @@ -34,7 +32,6 @@ public static ClaimableBalanceIDType decode(XdrDataInputStream stream) throws IO int value = stream.readInt(); switch (value) { case 0: return CLAIMABLE_BALANCE_ID_TYPE_V0; - case 1: return CLAIMABLE_BALANCE_ID_TYPE_FROM_POOL_REVOKE; default: throw new RuntimeException("Unknown enum value: " + value); } diff --git a/src/main/java/org/stellar/sdk/xdr/CryptoKeyType.java b/src/main/java/org/stellar/sdk/xdr/CryptoKeyType.java index bfaf14e5c..72d7e81a8 100644 --- a/src/main/java/org/stellar/sdk/xdr/CryptoKeyType.java +++ b/src/main/java/org/stellar/sdk/xdr/CryptoKeyType.java @@ -14,6 +14,7 @@ // KEY_TYPE_ED25519 = 0, // KEY_TYPE_PRE_AUTH_TX = 1, // KEY_TYPE_HASH_X = 2, +// KEY_TYPE_ED25519_SIGNED_PAYLOAD = 3, // // MUXED enum values for supported type are derived from the enum values // // above by ORing them with 0x100 // KEY_TYPE_MUXED_ED25519 = 0x100 @@ -24,6 +25,7 @@ public enum CryptoKeyType implements XdrElement { KEY_TYPE_ED25519(0), KEY_TYPE_PRE_AUTH_TX(1), KEY_TYPE_HASH_X(2), + KEY_TYPE_ED25519_SIGNED_PAYLOAD(3), KEY_TYPE_MUXED_ED25519(256), ; private int mValue; @@ -42,6 +44,7 @@ public static CryptoKeyType decode(XdrDataInputStream stream) throws IOException case 0: return KEY_TYPE_ED25519; case 1: return KEY_TYPE_PRE_AUTH_TX; case 2: return KEY_TYPE_HASH_X; + case 3: return KEY_TYPE_ED25519_SIGNED_PAYLOAD; case 256: return KEY_TYPE_MUXED_ED25519; default: throw new RuntimeException("Unknown enum value: " + value); diff --git a/src/main/java/org/stellar/sdk/xdr/Duration.java b/src/main/java/org/stellar/sdk/xdr/Duration.java new file mode 100644 index 000000000..56507997c --- /dev/null +++ b/src/main/java/org/stellar/sdk/xdr/Duration.java @@ -0,0 +1,60 @@ +// Automatically generated by xdrgen +// DO NOT EDIT or your changes may be overwritten + +package org.stellar.sdk.xdr; + + +import com.google.common.base.Objects; + +import java.io.IOException; + +// === xdr source ============================================================ + +// typedef int64 Duration; + +// =========================================================================== +public class Duration implements XdrElement { + private Int64 Duration; + + public Duration() {} + + public Duration(Int64 Duration) { + this.Duration = Duration; + } + + public Int64 getDuration() { + return this.Duration; + } + + public void setDuration(Int64 value) { + this.Duration = value; + } + + public static void encode(XdrDataOutputStream stream, Duration encodedDuration) throws IOException { + Int64.encode(stream, encodedDuration.Duration); + } + + public void encode(XdrDataOutputStream stream) throws IOException { + encode(stream, this); + } + public static Duration decode(XdrDataInputStream stream) throws IOException { + Duration decodedDuration = new Duration(); + decodedDuration.Duration = Int64.decode(stream); + return decodedDuration; + } + + @Override + public int hashCode() { + return Objects.hashCode(this.Duration); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Duration)) { + return false; + } + + Duration other = (Duration) object; + return Objects.equal(this.Duration, other.Duration); + } +} diff --git a/src/main/java/org/stellar/sdk/xdr/ExtensionPoint.java b/src/main/java/org/stellar/sdk/xdr/ExtensionPoint.java new file mode 100644 index 000000000..fb785dc66 --- /dev/null +++ b/src/main/java/org/stellar/sdk/xdr/ExtensionPoint.java @@ -0,0 +1,79 @@ +// Automatically generated by xdrgen +// DO NOT EDIT or your changes may be overwritten + +package org.stellar.sdk.xdr; + + +import com.google.common.base.Objects; + +import java.io.IOException; + +// === xdr source ============================================================ + +// union ExtensionPoint switch (int v) { +// case 0: +// void; +// }; + +// =========================================================================== +public class ExtensionPoint implements XdrElement { + public ExtensionPoint () {} + Integer v; + public Integer getDiscriminant() { + return this.v; + } + public void setDiscriminant(Integer value) { + this.v = value; + } + + public static final class Builder { + private Integer discriminant; + + public Builder discriminant(Integer discriminant) { + this.discriminant = discriminant; + return this; + } + + public ExtensionPoint build() { + ExtensionPoint val = new ExtensionPoint(); + val.setDiscriminant(discriminant); + return val; + } + } + + public static void encode(XdrDataOutputStream stream, ExtensionPoint encodedExtensionPoint) throws IOException { + //Xdrgen::AST::Typespecs::Int + //Integer + stream.writeInt(encodedExtensionPoint.getDiscriminant().intValue()); + switch (encodedExtensionPoint.getDiscriminant()) { + case 0: + break; + } + } + public void encode(XdrDataOutputStream stream) throws IOException { + encode(stream, this); + } + public static ExtensionPoint decode(XdrDataInputStream stream) throws IOException { + ExtensionPoint decodedExtensionPoint = new ExtensionPoint(); + Integer discriminant = stream.readInt(); + decodedExtensionPoint.setDiscriminant(discriminant); + switch (decodedExtensionPoint.getDiscriminant()) { + case 0: + break; + } + return decodedExtensionPoint; + } + @Override + public int hashCode() { + return Objects.hashCode(this.v); + } + @Override + public boolean equals(Object object) { + if (!(object instanceof ExtensionPoint)) { + return false; + } + + ExtensionPoint other = (ExtensionPoint) object; + return Objects.equal(this.v, other.v); + } +} diff --git a/src/main/java/org/stellar/sdk/xdr/HashIDPreimage.java b/src/main/java/org/stellar/sdk/xdr/HashIDPreimage.java new file mode 100644 index 000000000..559d9a584 --- /dev/null +++ b/src/main/java/org/stellar/sdk/xdr/HashIDPreimage.java @@ -0,0 +1,327 @@ +// Automatically generated by xdrgen +// DO NOT EDIT or your changes may be overwritten + +package org.stellar.sdk.xdr; + + +import com.google.common.base.Objects; + +import java.io.IOException; + +// === xdr source ============================================================ + +// union HashIDPreimage switch (EnvelopeType type) +// { +// case ENVELOPE_TYPE_OP_ID: +// struct +// { +// AccountID sourceAccount; +// SequenceNumber seqNum; +// uint32 opNum; +// } operationID; +// case ENVELOPE_TYPE_POOL_REVOKE_OP_ID: +// struct +// { +// AccountID sourceAccount; +// SequenceNumber seqNum; +// uint32 opNum; +// PoolID liquidityPoolID; +// Asset asset; +// } revokeID; +// }; + +// =========================================================================== +public class HashIDPreimage implements XdrElement { + public HashIDPreimage () {} + EnvelopeType type; + public EnvelopeType getDiscriminant() { + return this.type; + } + public void setDiscriminant(EnvelopeType value) { + this.type = value; + } + private HashIDPreimageOperationID operationID; + public HashIDPreimageOperationID getOperationID() { + return this.operationID; + } + public void setOperationID(HashIDPreimageOperationID value) { + this.operationID = value; + } + private HashIDPreimageRevokeID revokeID; + public HashIDPreimageRevokeID getRevokeID() { + return this.revokeID; + } + public void setRevokeID(HashIDPreimageRevokeID value) { + this.revokeID = value; + } + + public static final class Builder { + private EnvelopeType discriminant; + private HashIDPreimageOperationID operationID; + private HashIDPreimageRevokeID revokeID; + + public Builder discriminant(EnvelopeType discriminant) { + this.discriminant = discriminant; + return this; + } + + public Builder operationID(HashIDPreimageOperationID operationID) { + this.operationID = operationID; + return this; + } + + public Builder revokeID(HashIDPreimageRevokeID revokeID) { + this.revokeID = revokeID; + return this; + } + + public HashIDPreimage build() { + HashIDPreimage val = new HashIDPreimage(); + val.setDiscriminant(discriminant); + val.setOperationID(operationID); + val.setRevokeID(revokeID); + return val; + } + } + + public static void encode(XdrDataOutputStream stream, HashIDPreimage encodedHashIDPreimage) throws IOException { + //Xdrgen::AST::Identifier + //EnvelopeType + stream.writeInt(encodedHashIDPreimage.getDiscriminant().getValue()); + switch (encodedHashIDPreimage.getDiscriminant()) { + case ENVELOPE_TYPE_OP_ID: + HashIDPreimageOperationID.encode(stream, encodedHashIDPreimage.operationID); + break; + case ENVELOPE_TYPE_POOL_REVOKE_OP_ID: + HashIDPreimageRevokeID.encode(stream, encodedHashIDPreimage.revokeID); + break; + } + } + public void encode(XdrDataOutputStream stream) throws IOException { + encode(stream, this); + } + public static HashIDPreimage decode(XdrDataInputStream stream) throws IOException { + HashIDPreimage decodedHashIDPreimage = new HashIDPreimage(); + EnvelopeType discriminant = EnvelopeType.decode(stream); + decodedHashIDPreimage.setDiscriminant(discriminant); + switch (decodedHashIDPreimage.getDiscriminant()) { + case ENVELOPE_TYPE_OP_ID: + decodedHashIDPreimage.operationID = HashIDPreimageOperationID.decode(stream); + break; + case ENVELOPE_TYPE_POOL_REVOKE_OP_ID: + decodedHashIDPreimage.revokeID = HashIDPreimageRevokeID.decode(stream); + break; + } + return decodedHashIDPreimage; + } + @Override + public int hashCode() { + return Objects.hashCode(this.operationID, this.revokeID, this.type); + } + @Override + public boolean equals(Object object) { + if (!(object instanceof HashIDPreimage)) { + return false; + } + + HashIDPreimage other = (HashIDPreimage) object; + return Objects.equal(this.operationID, other.operationID) && Objects.equal(this.revokeID, other.revokeID) && Objects.equal(this.type, other.type); + } + + public static class HashIDPreimageOperationID { + public HashIDPreimageOperationID () {} + private AccountID sourceAccount; + public AccountID getSourceAccount() { + return this.sourceAccount; + } + public void setSourceAccount(AccountID value) { + this.sourceAccount = value; + } + private SequenceNumber seqNum; + public SequenceNumber getSeqNum() { + return this.seqNum; + } + public void setSeqNum(SequenceNumber value) { + this.seqNum = value; + } + private Uint32 opNum; + public Uint32 getOpNum() { + return this.opNum; + } + public void setOpNum(Uint32 value) { + this.opNum = value; + } + public static void encode(XdrDataOutputStream stream, HashIDPreimageOperationID encodedHashIDPreimageOperationID) throws IOException{ + AccountID.encode(stream, encodedHashIDPreimageOperationID.sourceAccount); + SequenceNumber.encode(stream, encodedHashIDPreimageOperationID.seqNum); + Uint32.encode(stream, encodedHashIDPreimageOperationID.opNum); + } + public void encode(XdrDataOutputStream stream) throws IOException { + encode(stream, this); + } + public static HashIDPreimageOperationID decode(XdrDataInputStream stream) throws IOException { + HashIDPreimageOperationID decodedHashIDPreimageOperationID = new HashIDPreimageOperationID(); + decodedHashIDPreimageOperationID.sourceAccount = AccountID.decode(stream); + decodedHashIDPreimageOperationID.seqNum = SequenceNumber.decode(stream); + decodedHashIDPreimageOperationID.opNum = Uint32.decode(stream); + return decodedHashIDPreimageOperationID; + } + @Override + public int hashCode() { + return Objects.hashCode(this.sourceAccount, this.seqNum, this.opNum); + } + @Override + public boolean equals(Object object) { + if (!(object instanceof HashIDPreimageOperationID)) { + return false; + } + + HashIDPreimageOperationID other = (HashIDPreimageOperationID) object; + return Objects.equal(this.sourceAccount, other.sourceAccount) && Objects.equal(this.seqNum, other.seqNum) && Objects.equal(this.opNum, other.opNum); + } + + public static final class Builder { + private AccountID sourceAccount; + private SequenceNumber seqNum; + private Uint32 opNum; + + public Builder sourceAccount(AccountID sourceAccount) { + this.sourceAccount = sourceAccount; + return this; + } + + public Builder seqNum(SequenceNumber seqNum) { + this.seqNum = seqNum; + return this; + } + + public Builder opNum(Uint32 opNum) { + this.opNum = opNum; + return this; + } + + public HashIDPreimageOperationID build() { + HashIDPreimageOperationID val = new HashIDPreimageOperationID(); + val.setSourceAccount(sourceAccount); + val.setSeqNum(seqNum); + val.setOpNum(opNum); + return val; + } + } + + } + public static class HashIDPreimageRevokeID { + public HashIDPreimageRevokeID () {} + private AccountID sourceAccount; + public AccountID getSourceAccount() { + return this.sourceAccount; + } + public void setSourceAccount(AccountID value) { + this.sourceAccount = value; + } + private SequenceNumber seqNum; + public SequenceNumber getSeqNum() { + return this.seqNum; + } + public void setSeqNum(SequenceNumber value) { + this.seqNum = value; + } + private Uint32 opNum; + public Uint32 getOpNum() { + return this.opNum; + } + public void setOpNum(Uint32 value) { + this.opNum = value; + } + private PoolID liquidityPoolID; + public PoolID getLiquidityPoolID() { + return this.liquidityPoolID; + } + public void setLiquidityPoolID(PoolID value) { + this.liquidityPoolID = value; + } + private Asset asset; + public Asset getAsset() { + return this.asset; + } + public void setAsset(Asset value) { + this.asset = value; + } + public static void encode(XdrDataOutputStream stream, HashIDPreimageRevokeID encodedHashIDPreimageRevokeID) throws IOException{ + AccountID.encode(stream, encodedHashIDPreimageRevokeID.sourceAccount); + SequenceNumber.encode(stream, encodedHashIDPreimageRevokeID.seqNum); + Uint32.encode(stream, encodedHashIDPreimageRevokeID.opNum); + PoolID.encode(stream, encodedHashIDPreimageRevokeID.liquidityPoolID); + Asset.encode(stream, encodedHashIDPreimageRevokeID.asset); + } + public void encode(XdrDataOutputStream stream) throws IOException { + encode(stream, this); + } + public static HashIDPreimageRevokeID decode(XdrDataInputStream stream) throws IOException { + HashIDPreimageRevokeID decodedHashIDPreimageRevokeID = new HashIDPreimageRevokeID(); + decodedHashIDPreimageRevokeID.sourceAccount = AccountID.decode(stream); + decodedHashIDPreimageRevokeID.seqNum = SequenceNumber.decode(stream); + decodedHashIDPreimageRevokeID.opNum = Uint32.decode(stream); + decodedHashIDPreimageRevokeID.liquidityPoolID = PoolID.decode(stream); + decodedHashIDPreimageRevokeID.asset = Asset.decode(stream); + return decodedHashIDPreimageRevokeID; + } + @Override + public int hashCode() { + return Objects.hashCode(this.sourceAccount, this.seqNum, this.opNum, this.liquidityPoolID, this.asset); + } + @Override + public boolean equals(Object object) { + if (!(object instanceof HashIDPreimageRevokeID)) { + return false; + } + + HashIDPreimageRevokeID other = (HashIDPreimageRevokeID) object; + return Objects.equal(this.sourceAccount, other.sourceAccount) && Objects.equal(this.seqNum, other.seqNum) && Objects.equal(this.opNum, other.opNum) && Objects.equal(this.liquidityPoolID, other.liquidityPoolID) && Objects.equal(this.asset, other.asset); + } + + public static final class Builder { + private AccountID sourceAccount; + private SequenceNumber seqNum; + private Uint32 opNum; + private PoolID liquidityPoolID; + private Asset asset; + + public Builder sourceAccount(AccountID sourceAccount) { + this.sourceAccount = sourceAccount; + return this; + } + + public Builder seqNum(SequenceNumber seqNum) { + this.seqNum = seqNum; + return this; + } + + public Builder opNum(Uint32 opNum) { + this.opNum = opNum; + return this; + } + + public Builder liquidityPoolID(PoolID liquidityPoolID) { + this.liquidityPoolID = liquidityPoolID; + return this; + } + + public Builder asset(Asset asset) { + this.asset = asset; + return this; + } + + public HashIDPreimageRevokeID build() { + HashIDPreimageRevokeID val = new HashIDPreimageRevokeID(); + val.setSourceAccount(sourceAccount); + val.setSeqNum(seqNum); + val.setOpNum(opNum); + val.setLiquidityPoolID(liquidityPoolID); + val.setAsset(asset); + return val; + } + } + + } +} diff --git a/src/main/java/org/stellar/sdk/xdr/LedgerBounds.java b/src/main/java/org/stellar/sdk/xdr/LedgerBounds.java new file mode 100644 index 000000000..c55bb91ad --- /dev/null +++ b/src/main/java/org/stellar/sdk/xdr/LedgerBounds.java @@ -0,0 +1,84 @@ +// Automatically generated by xdrgen +// DO NOT EDIT or your changes may be overwritten + +package org.stellar.sdk.xdr; + + +import com.google.common.base.Objects; + +import java.io.IOException; + +// === xdr source ============================================================ + +// struct LedgerBounds +// { +// uint32 minLedger; +// uint32 maxLedger; +// }; + +// =========================================================================== +public class LedgerBounds implements XdrElement { + public LedgerBounds () {} + private Uint32 minLedger; + public Uint32 getMinLedger() { + return this.minLedger; + } + public void setMinLedger(Uint32 value) { + this.minLedger = value; + } + private Uint32 maxLedger; + public Uint32 getMaxLedger() { + return this.maxLedger; + } + public void setMaxLedger(Uint32 value) { + this.maxLedger = value; + } + public static void encode(XdrDataOutputStream stream, LedgerBounds encodedLedgerBounds) throws IOException{ + Uint32.encode(stream, encodedLedgerBounds.minLedger); + Uint32.encode(stream, encodedLedgerBounds.maxLedger); + } + public void encode(XdrDataOutputStream stream) throws IOException { + encode(stream, this); + } + public static LedgerBounds decode(XdrDataInputStream stream) throws IOException { + LedgerBounds decodedLedgerBounds = new LedgerBounds(); + decodedLedgerBounds.minLedger = Uint32.decode(stream); + decodedLedgerBounds.maxLedger = Uint32.decode(stream); + return decodedLedgerBounds; + } + @Override + public int hashCode() { + return Objects.hashCode(this.minLedger, this.maxLedger); + } + @Override + public boolean equals(Object object) { + if (!(object instanceof LedgerBounds)) { + return false; + } + + LedgerBounds other = (LedgerBounds) object; + return Objects.equal(this.minLedger, other.minLedger) && Objects.equal(this.maxLedger, other.maxLedger); + } + + public static final class Builder { + private Uint32 minLedger; + private Uint32 maxLedger; + + public Builder minLedger(Uint32 minLedger) { + this.minLedger = minLedger; + return this; + } + + public Builder maxLedger(Uint32 maxLedger) { + this.maxLedger = maxLedger; + return this; + } + + public LedgerBounds build() { + LedgerBounds val = new LedgerBounds(); + val.setMinLedger(minLedger); + val.setMaxLedger(maxLedger); + return val; + } + } +} diff --git a/src/main/java/org/stellar/sdk/xdr/LedgerHeaderExtensionV1.java b/src/main/java/org/stellar/sdk/xdr/LedgerHeaderExtensionV1.java index a78303c1d..6be2aaa41 100644 --- a/src/main/java/org/stellar/sdk/xdr/LedgerHeaderExtensionV1.java +++ b/src/main/java/org/stellar/sdk/xdr/LedgerHeaderExtensionV1.java @@ -4,15 +4,15 @@ package org.stellar.sdk.xdr; -import java.io.IOException; - import com.google.common.base.Objects; +import java.io.IOException; + // === xdr source ============================================================ // struct LedgerHeaderExtensionV1 // { -// uint32 flags; // UpgradeFlags +// uint32 flags; // LedgerHeaderFlags // // union switch (int v) // { diff --git a/src/main/java/org/stellar/sdk/xdr/LedgerHeaderFlags.java b/src/main/java/org/stellar/sdk/xdr/LedgerHeaderFlags.java index 4c898b73d..99d3c02f7 100644 --- a/src/main/java/org/stellar/sdk/xdr/LedgerHeaderFlags.java +++ b/src/main/java/org/stellar/sdk/xdr/LedgerHeaderFlags.java @@ -10,8 +10,7 @@ // === xdr source ============================================================ // enum LedgerHeaderFlags -// { // masks for each flag -// +// { // DISABLE_LIQUIDITY_POOL_TRADING_FLAG = 0x1, // DISABLE_LIQUIDITY_POOL_DEPOSIT_FLAG = 0x2, // DISABLE_LIQUIDITY_POOL_WITHDRAWAL_FLAG = 0x4 diff --git a/src/main/java/org/stellar/sdk/xdr/OfferEntryFlags.java b/src/main/java/org/stellar/sdk/xdr/OfferEntryFlags.java index 3aa9758f6..32bd84157 100644 --- a/src/main/java/org/stellar/sdk/xdr/OfferEntryFlags.java +++ b/src/main/java/org/stellar/sdk/xdr/OfferEntryFlags.java @@ -11,7 +11,7 @@ // enum OfferEntryFlags // { -// // issuer has authorized account to perform transactions with its credit +// // an offer with this flag will not act on and take a reverse offer of equal price // PASSIVE_FLAG = 1 // }; diff --git a/src/main/java/org/stellar/sdk/xdr/PreconditionType.java b/src/main/java/org/stellar/sdk/xdr/PreconditionType.java new file mode 100644 index 000000000..6636f2560 --- /dev/null +++ b/src/main/java/org/stellar/sdk/xdr/PreconditionType.java @@ -0,0 +1,52 @@ +// Automatically generated by xdrgen +// DO NOT EDIT or your changes may be overwritten + +package org.stellar.sdk.xdr; + + +import java.io.IOException; + + +// === xdr source ============================================================ + +// enum PreconditionType { +// PRECOND_NONE = 0, +// PRECOND_TIME = 1, +// PRECOND_V2 = 2 +// }; + +// =========================================================================== +public enum PreconditionType implements XdrElement { + PRECOND_NONE(0), + PRECOND_TIME(1), + PRECOND_V2(2), + ; + private int mValue; + + PreconditionType(int value) { + mValue = value; + } + + public int getValue() { + return mValue; + } + + public static PreconditionType decode(XdrDataInputStream stream) throws IOException { + int value = stream.readInt(); + switch (value) { + case 0: return PRECOND_NONE; + case 1: return PRECOND_TIME; + case 2: return PRECOND_V2; + default: + throw new RuntimeException("Unknown enum value: " + value); + } + } + + public static void encode(XdrDataOutputStream stream, PreconditionType value) throws IOException { + stream.writeInt(value.getValue()); + } + + public void encode(XdrDataOutputStream stream) throws IOException { + encode(stream, this); + } +} diff --git a/src/main/java/org/stellar/sdk/xdr/Preconditions.java b/src/main/java/org/stellar/sdk/xdr/Preconditions.java new file mode 100644 index 000000000..7eb39c140 --- /dev/null +++ b/src/main/java/org/stellar/sdk/xdr/Preconditions.java @@ -0,0 +1,123 @@ +// Automatically generated by xdrgen +// DO NOT EDIT or your changes may be overwritten + +package org.stellar.sdk.xdr; + + +import com.google.common.base.Objects; + +import java.io.IOException; + +// === xdr source ============================================================ + +// union Preconditions switch (PreconditionType type) { +// case PRECOND_NONE: +// void; +// case PRECOND_TIME: +// TimeBounds timeBounds; +// case PRECOND_V2: +// PreconditionsV2 v2; +// }; + +// =========================================================================== +public class Preconditions implements XdrElement { + public Preconditions () {} + PreconditionType type; + public PreconditionType getDiscriminant() { + return this.type; + } + public void setDiscriminant(PreconditionType value) { + this.type = value; + } + private TimeBounds timeBounds; + public TimeBounds getTimeBounds() { + return this.timeBounds; + } + public void setTimeBounds(TimeBounds value) { + this.timeBounds = value; + } + private PreconditionsV2 v2; + public PreconditionsV2 getV2() { + return this.v2; + } + public void setV2(PreconditionsV2 value) { + this.v2 = value; + } + + public static final class Builder { + private PreconditionType discriminant; + private TimeBounds timeBounds; + private PreconditionsV2 v2; + + public Builder discriminant(PreconditionType discriminant) { + this.discriminant = discriminant; + return this; + } + + public Builder timeBounds(TimeBounds timeBounds) { + this.timeBounds = timeBounds; + return this; + } + + public Builder v2(PreconditionsV2 v2) { + this.v2 = v2; + return this; + } + + public Preconditions build() { + Preconditions val = new Preconditions(); + val.setDiscriminant(discriminant); + val.setTimeBounds(timeBounds); + val.setV2(v2); + return val; + } + } + + public static void encode(XdrDataOutputStream stream, Preconditions encodedPreconditions) throws IOException { + //Xdrgen::AST::Identifier + //PreconditionType + stream.writeInt(encodedPreconditions.getDiscriminant().getValue()); + switch (encodedPreconditions.getDiscriminant()) { + case PRECOND_NONE: + break; + case PRECOND_TIME: + TimeBounds.encode(stream, encodedPreconditions.timeBounds); + break; + case PRECOND_V2: + PreconditionsV2.encode(stream, encodedPreconditions.v2); + break; + } + } + public void encode(XdrDataOutputStream stream) throws IOException { + encode(stream, this); + } + public static Preconditions decode(XdrDataInputStream stream) throws IOException { + Preconditions decodedPreconditions = new Preconditions(); + PreconditionType discriminant = PreconditionType.decode(stream); + decodedPreconditions.setDiscriminant(discriminant); + switch (decodedPreconditions.getDiscriminant()) { + case PRECOND_NONE: + break; + case PRECOND_TIME: + decodedPreconditions.timeBounds = TimeBounds.decode(stream); + break; + case PRECOND_V2: + decodedPreconditions.v2 = PreconditionsV2.decode(stream); + break; + } + return decodedPreconditions; + } + @Override + public int hashCode() { + return Objects.hashCode(this.timeBounds, this.v2, this.type); + } + @Override + public boolean equals(Object object) { + if (!(object instanceof Preconditions)) { + return false; + } + + Preconditions other = (Preconditions) object; + return Objects.equal(this.timeBounds, other.timeBounds) && Objects.equal(this.v2, other.v2) && Objects.equal(this.type, other.type); + } +} diff --git a/src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java b/src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java new file mode 100644 index 000000000..dc098c1d4 --- /dev/null +++ b/src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java @@ -0,0 +1,206 @@ +// Automatically generated by xdrgen +// DO NOT EDIT or your changes may be overwritten + +package org.stellar.sdk.xdr; + + +import com.google.common.base.Objects; + +import java.io.IOException; +import java.util.Arrays; + +// === xdr source ============================================================ + +// struct PreconditionsV2 { +// TimeBounds *timeBounds; +// +// // Transaciton only valid for ledger numbers n such that +// // minLedger <= n < maxLedger +// LedgerBounds *ledgerBounds; +// +// // If NULL, only valid when sourceAccount's sequence number +// // is seqNum - 1. Otherwise, valid when sourceAccount's +// // sequence number n satisfies minSeqNum <= n < tx.seqNum. +// // Note that after execution the account's sequence number +// // is always raised to tx.seqNum, and a transaction is not +// // valid if tx.seqNum is too high to ensure replay protection. +// SequenceNumber *minSeqNum; +// +// // For the transaction to be valid, the current ledger time must +// // be at least minSeqAge greater than sourceAccount's seqTime. +// Duration minSeqAge; +// +// // For the transaction to be valid, the current ledger number +// // must be at least minSeqLedgerGap greater than sourceAccount's +// // seqLedger. +// uint32 minSeqLedgerGap; +// +// // For the transaction to be valid, there must be a signature +// // corresponding to every Signer in this array, even if the +// // signature is not otherwise required by the sourceAccount or +// // operations. +// SignerKey extraSigners<2>; +// }; + +// =========================================================================== +public class PreconditionsV2 implements XdrElement { + public PreconditionsV2 () {} + private TimeBounds timeBounds; + public TimeBounds getTimeBounds() { + return this.timeBounds; + } + public void setTimeBounds(TimeBounds value) { + this.timeBounds = value; + } + private LedgerBounds ledgerBounds; + public LedgerBounds getLedgerBounds() { + return this.ledgerBounds; + } + public void setLedgerBounds(LedgerBounds value) { + this.ledgerBounds = value; + } + private SequenceNumber minSeqNum; + public SequenceNumber getMinSeqNum() { + return this.minSeqNum; + } + public void setMinSeqNum(SequenceNumber value) { + this.minSeqNum = value; + } + private Duration minSeqAge; + public Duration getMinSeqAge() { + return this.minSeqAge; + } + public void setMinSeqAge(Duration value) { + this.minSeqAge = value; + } + private Uint32 minSeqLedgerGap; + public Uint32 getMinSeqLedgerGap() { + return this.minSeqLedgerGap; + } + public void setMinSeqLedgerGap(Uint32 value) { + this.minSeqLedgerGap = value; + } + private SignerKey[] extraSigners; + public SignerKey[] getExtraSigners() { + return this.extraSigners; + } + public void setExtraSigners(SignerKey[] value) { + this.extraSigners = value; + } + public static void encode(XdrDataOutputStream stream, PreconditionsV2 encodedPreconditionsV2) throws IOException{ + if (encodedPreconditionsV2.timeBounds != null) { + stream.writeInt(1); + TimeBounds.encode(stream, encodedPreconditionsV2.timeBounds); + } else { + stream.writeInt(0); + } + if (encodedPreconditionsV2.ledgerBounds != null) { + stream.writeInt(1); + LedgerBounds.encode(stream, encodedPreconditionsV2.ledgerBounds); + } else { + stream.writeInt(0); + } + if (encodedPreconditionsV2.minSeqNum != null) { + stream.writeInt(1); + SequenceNumber.encode(stream, encodedPreconditionsV2.minSeqNum); + } else { + stream.writeInt(0); + } + Duration.encode(stream, encodedPreconditionsV2.minSeqAge); + Uint32.encode(stream, encodedPreconditionsV2.minSeqLedgerGap); + int extraSignerssize = encodedPreconditionsV2.getExtraSigners().length; + stream.writeInt(extraSignerssize); + for (int i = 0; i < extraSignerssize; i++) { + SignerKey.encode(stream, encodedPreconditionsV2.extraSigners[i]); + } + } + public void encode(XdrDataOutputStream stream) throws IOException { + encode(stream, this); + } + public static PreconditionsV2 decode(XdrDataInputStream stream) throws IOException { + PreconditionsV2 decodedPreconditionsV2 = new PreconditionsV2(); + int timeBoundsPresent = stream.readInt(); + if (timeBoundsPresent != 0) { + decodedPreconditionsV2.timeBounds = TimeBounds.decode(stream); + } + int ledgerBoundsPresent = stream.readInt(); + if (ledgerBoundsPresent != 0) { + decodedPreconditionsV2.ledgerBounds = LedgerBounds.decode(stream); + } + int minSeqNumPresent = stream.readInt(); + if (minSeqNumPresent != 0) { + decodedPreconditionsV2.minSeqNum = SequenceNumber.decode(stream); + } + decodedPreconditionsV2.minSeqAge = Duration.decode(stream); + decodedPreconditionsV2.minSeqLedgerGap = Uint32.decode(stream); + int extraSignerssize = stream.readInt(); + decodedPreconditionsV2.extraSigners = new SignerKey[extraSignerssize]; + for (int i = 0; i < extraSignerssize; i++) { + decodedPreconditionsV2.extraSigners[i] = SignerKey.decode(stream); + } + return decodedPreconditionsV2; + } + @Override + public int hashCode() { + return Objects.hashCode(this.timeBounds, this.ledgerBounds, this.minSeqNum, this.minSeqAge, this.minSeqLedgerGap, Arrays.hashCode(this.extraSigners)); + } + @Override + public boolean equals(Object object) { + if (!(object instanceof PreconditionsV2)) { + return false; + } + + PreconditionsV2 other = (PreconditionsV2) object; + return Objects.equal(this.timeBounds, other.timeBounds) && Objects.equal(this.ledgerBounds, other.ledgerBounds) && Objects.equal(this.minSeqNum, other.minSeqNum) && Objects.equal(this.minSeqAge, other.minSeqAge) && Objects.equal(this.minSeqLedgerGap, other.minSeqLedgerGap) && Arrays.equals(this.extraSigners, other.extraSigners); + } + + public static final class Builder { + private TimeBounds timeBounds; + private LedgerBounds ledgerBounds; + private SequenceNumber minSeqNum; + private Duration minSeqAge; + private Uint32 minSeqLedgerGap; + private SignerKey[] extraSigners; + + public Builder timeBounds(TimeBounds timeBounds) { + this.timeBounds = timeBounds; + return this; + } + + public Builder ledgerBounds(LedgerBounds ledgerBounds) { + this.ledgerBounds = ledgerBounds; + return this; + } + + public Builder minSeqNum(SequenceNumber minSeqNum) { + this.minSeqNum = minSeqNum; + return this; + } + + public Builder minSeqAge(Duration minSeqAge) { + this.minSeqAge = minSeqAge; + return this; + } + + public Builder minSeqLedgerGap(Uint32 minSeqLedgerGap) { + this.minSeqLedgerGap = minSeqLedgerGap; + return this; + } + + public Builder extraSigners(SignerKey[] extraSigners) { + this.extraSigners = extraSigners; + return this; + } + + public PreconditionsV2 build() { + PreconditionsV2 val = new PreconditionsV2(); + val.setTimeBounds(timeBounds); + val.setLedgerBounds(ledgerBounds); + val.setMinSeqNum(minSeqNum); + val.setMinSeqAge(minSeqAge); + val.setMinSeqLedgerGap(minSeqLedgerGap); + val.setExtraSigners(extraSigners); + return val; + } + } +} diff --git a/src/main/java/org/stellar/sdk/xdr/SignerKey.java b/src/main/java/org/stellar/sdk/xdr/SignerKey.java index 98a9d64bd..303c661ae 100644 --- a/src/main/java/org/stellar/sdk/xdr/SignerKey.java +++ b/src/main/java/org/stellar/sdk/xdr/SignerKey.java @@ -4,10 +4,11 @@ package org.stellar.sdk.xdr; -import java.io.IOException; - import com.google.common.base.Objects; +import java.io.IOException; +import java.util.Arrays; + // === xdr source ============================================================ // union SignerKey switch (SignerKeyType type) @@ -20,6 +21,13 @@ // case SIGNER_KEY_TYPE_HASH_X: // /* Hash of random 256 bit preimage X */ // uint256 hashX; +// case SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD: +// struct { +// /* Public key that must sign the payload. */ +// uint256 ed25519; +// /* Payload to be raw signed by ed25519. */ +// opaque payload<64>; +// } ed25519SignedPayload; // }; // =========================================================================== @@ -53,12 +61,20 @@ public Uint256 getHashX() { public void setHashX(Uint256 value) { this.hashX = value; } + private SignerKeyEd25519SignedPayload ed25519SignedPayload; + public SignerKeyEd25519SignedPayload getEd25519SignedPayload() { + return this.ed25519SignedPayload; + } + public void setEd25519SignedPayload(SignerKeyEd25519SignedPayload value) { + this.ed25519SignedPayload = value; + } public static final class Builder { private SignerKeyType discriminant; private Uint256 ed25519; private Uint256 preAuthTx; private Uint256 hashX; + private SignerKeyEd25519SignedPayload ed25519SignedPayload; public Builder discriminant(SignerKeyType discriminant) { this.discriminant = discriminant; @@ -80,12 +96,18 @@ public Builder hashX(Uint256 hashX) { return this; } + public Builder ed25519SignedPayload(SignerKeyEd25519SignedPayload ed25519SignedPayload) { + this.ed25519SignedPayload = ed25519SignedPayload; + return this; + } + public SignerKey build() { SignerKey val = new SignerKey(); val.setDiscriminant(discriminant); val.setEd25519(ed25519); val.setPreAuthTx(preAuthTx); val.setHashX(hashX); + val.setEd25519SignedPayload(ed25519SignedPayload); return val; } } @@ -104,6 +126,9 @@ public static void encode(XdrDataOutputStream stream, SignerKey encodedSignerKey case SIGNER_KEY_TYPE_HASH_X: Uint256.encode(stream, encodedSignerKey.hashX); break; + case SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD: + SignerKeyEd25519SignedPayload.encode(stream, encodedSignerKey.ed25519SignedPayload); + break; } } public void encode(XdrDataOutputStream stream) throws IOException { @@ -123,12 +148,15 @@ public static SignerKey decode(XdrDataInputStream stream) throws IOException { case SIGNER_KEY_TYPE_HASH_X: decodedSignerKey.hashX = Uint256.decode(stream); break; + case SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD: + decodedSignerKey.ed25519SignedPayload = SignerKeyEd25519SignedPayload.decode(stream); + break; } return decodedSignerKey; } @Override public int hashCode() { - return Objects.hashCode(this.ed25519, this.preAuthTx, this.hashX, this.type); + return Objects.hashCode(this.ed25519, this.preAuthTx, this.hashX, this.ed25519SignedPayload, this.type); } @Override public boolean equals(Object object) { @@ -137,6 +165,77 @@ public boolean equals(Object object) { } SignerKey other = (SignerKey) object; - return Objects.equal(this.ed25519, other.ed25519) && Objects.equal(this.preAuthTx, other.preAuthTx) && Objects.equal(this.hashX, other.hashX) && Objects.equal(this.type, other.type); + return Objects.equal(this.ed25519, other.ed25519) && Objects.equal(this.preAuthTx, other.preAuthTx) && Objects.equal(this.hashX, other.hashX) && Objects.equal(this.ed25519SignedPayload, other.ed25519SignedPayload) && Objects.equal(this.type, other.type); + } + + public static class SignerKeyEd25519SignedPayload { + public SignerKeyEd25519SignedPayload () {} + private Uint256 ed25519; + public Uint256 getEd25519() { + return this.ed25519; + } + public void setEd25519(Uint256 value) { + this.ed25519 = value; + } + private byte[] payload; + public byte[] getPayload() { + return this.payload; + } + public void setPayload(byte[] value) { + this.payload = value; + } + public static void encode(XdrDataOutputStream stream, SignerKeyEd25519SignedPayload encodedSignerKeyEd25519SignedPayload) throws IOException{ + Uint256.encode(stream, encodedSignerKeyEd25519SignedPayload.ed25519); + int payloadsize = encodedSignerKeyEd25519SignedPayload.payload.length; + stream.writeInt(payloadsize); + stream.write(encodedSignerKeyEd25519SignedPayload.getPayload(), 0, payloadsize); + } + public void encode(XdrDataOutputStream stream) throws IOException { + encode(stream, this); + } + public static SignerKeyEd25519SignedPayload decode(XdrDataInputStream stream) throws IOException { + SignerKeyEd25519SignedPayload decodedSignerKeyEd25519SignedPayload = new SignerKeyEd25519SignedPayload(); + decodedSignerKeyEd25519SignedPayload.ed25519 = Uint256.decode(stream); + int payloadsize = stream.readInt(); + decodedSignerKeyEd25519SignedPayload.payload = new byte[payloadsize]; + stream.read(decodedSignerKeyEd25519SignedPayload.payload, 0, payloadsize); + return decodedSignerKeyEd25519SignedPayload; + } + @Override + public int hashCode() { + return Objects.hashCode(this.ed25519, Arrays.hashCode(this.payload)); + } + @Override + public boolean equals(Object object) { + if (!(object instanceof SignerKeyEd25519SignedPayload)) { + return false; + } + + SignerKeyEd25519SignedPayload other = (SignerKeyEd25519SignedPayload) object; + return Objects.equal(this.ed25519, other.ed25519) && Arrays.equals(this.payload, other.payload); + } + + public static final class Builder { + private Uint256 ed25519; + private byte[] payload; + + public Builder ed25519(Uint256 ed25519) { + this.ed25519 = ed25519; + return this; + } + + public Builder payload(byte[] payload) { + this.payload = payload; + return this; + } + + public SignerKeyEd25519SignedPayload build() { + SignerKeyEd25519SignedPayload val = new SignerKeyEd25519SignedPayload(); + val.setEd25519(ed25519); + val.setPayload(payload); + return val; + } + } + } } diff --git a/src/main/java/org/stellar/sdk/xdr/SignerKeyType.java b/src/main/java/org/stellar/sdk/xdr/SignerKeyType.java index d5e610028..4000eaf2e 100644 --- a/src/main/java/org/stellar/sdk/xdr/SignerKeyType.java +++ b/src/main/java/org/stellar/sdk/xdr/SignerKeyType.java @@ -13,7 +13,8 @@ // { // SIGNER_KEY_TYPE_ED25519 = KEY_TYPE_ED25519, // SIGNER_KEY_TYPE_PRE_AUTH_TX = KEY_TYPE_PRE_AUTH_TX, -// SIGNER_KEY_TYPE_HASH_X = KEY_TYPE_HASH_X +// SIGNER_KEY_TYPE_HASH_X = KEY_TYPE_HASH_X, +// SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD = KEY_TYPE_ED25519_SIGNED_PAYLOAD // }; // =========================================================================== @@ -21,6 +22,7 @@ public enum SignerKeyType implements XdrElement { SIGNER_KEY_TYPE_ED25519(0), SIGNER_KEY_TYPE_PRE_AUTH_TX(1), SIGNER_KEY_TYPE_HASH_X(2), + SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD(3), ; private int mValue; @@ -38,6 +40,7 @@ public static SignerKeyType decode(XdrDataInputStream stream) throws IOException case 0: return SIGNER_KEY_TYPE_ED25519; case 1: return SIGNER_KEY_TYPE_PRE_AUTH_TX; case 2: return SIGNER_KEY_TYPE_HASH_X; + case 3: return SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD; default: throw new RuntimeException("Unknown enum value: " + value); } diff --git a/src/main/java/org/stellar/sdk/xdr/StellarValue.java b/src/main/java/org/stellar/sdk/xdr/StellarValue.java index 6954c7e9c..c841d5903 100644 --- a/src/main/java/org/stellar/sdk/xdr/StellarValue.java +++ b/src/main/java/org/stellar/sdk/xdr/StellarValue.java @@ -4,9 +4,9 @@ package org.stellar.sdk.xdr; -import java.io.IOException; - import com.google.common.base.Objects; + +import java.io.IOException; import java.util.Arrays; // === xdr source ============================================================ @@ -20,7 +20,7 @@ // // this is a vector of encoded 'LedgerUpgrade' so that nodes can drop // // unknown steps during consensus if needed. // // see notes below on 'LedgerUpgrade' for more detail -// // max size is dictated by number of upgrade types ( room for future) +// // max size is dictated by number of upgrade types (+ room for future) // UpgradeType upgrades<6>; // // // reserved for future use diff --git a/src/main/java/org/stellar/sdk/xdr/Transaction.java b/src/main/java/org/stellar/sdk/xdr/Transaction.java index acb2290c9..728be1b57 100644 --- a/src/main/java/org/stellar/sdk/xdr/Transaction.java +++ b/src/main/java/org/stellar/sdk/xdr/Transaction.java @@ -4,9 +4,9 @@ package org.stellar.sdk.xdr; -import java.io.IOException; - import com.google.common.base.Objects; + +import java.io.IOException; import java.util.Arrays; // === xdr source ============================================================ @@ -22,8 +22,8 @@ // // sequence number to consume in the account // SequenceNumber seqNum; // -// // validity range (inclusive) for the last ledger close time -// TimeBounds* timeBounds; +// // validity conditions +// Preconditions cond; // // Memo memo; // @@ -62,12 +62,12 @@ public SequenceNumber getSeqNum() { public void setSeqNum(SequenceNumber value) { this.seqNum = value; } - private TimeBounds timeBounds; - public TimeBounds getTimeBounds() { - return this.timeBounds; + private Preconditions cond; + public Preconditions getCond() { + return this.cond; } - public void setTimeBounds(TimeBounds value) { - this.timeBounds = value; + public void setCond(Preconditions value) { + this.cond = value; } private Memo memo; public Memo getMemo() { @@ -94,12 +94,7 @@ public static void encode(XdrDataOutputStream stream, Transaction encodedTransac MuxedAccount.encode(stream, encodedTransaction.sourceAccount); Uint32.encode(stream, encodedTransaction.fee); SequenceNumber.encode(stream, encodedTransaction.seqNum); - if (encodedTransaction.timeBounds != null) { - stream.writeInt(1); - TimeBounds.encode(stream, encodedTransaction.timeBounds); - } else { - stream.writeInt(0); - } + Preconditions.encode(stream, encodedTransaction.cond); Memo.encode(stream, encodedTransaction.memo); int operationssize = encodedTransaction.getOperations().length; stream.writeInt(operationssize); @@ -116,10 +111,7 @@ public static Transaction decode(XdrDataInputStream stream) throws IOException { decodedTransaction.sourceAccount = MuxedAccount.decode(stream); decodedTransaction.fee = Uint32.decode(stream); decodedTransaction.seqNum = SequenceNumber.decode(stream); - int timeBoundsPresent = stream.readInt(); - if (timeBoundsPresent != 0) { - decodedTransaction.timeBounds = TimeBounds.decode(stream); - } + decodedTransaction.cond = Preconditions.decode(stream); decodedTransaction.memo = Memo.decode(stream); int operationssize = stream.readInt(); decodedTransaction.operations = new Operation[operationssize]; @@ -131,7 +123,7 @@ public static Transaction decode(XdrDataInputStream stream) throws IOException { } @Override public int hashCode() { - return Objects.hashCode(this.sourceAccount, this.fee, this.seqNum, this.timeBounds, this.memo, Arrays.hashCode(this.operations), this.ext); + return Objects.hashCode(this.sourceAccount, this.fee, this.seqNum, this.cond, this.memo, Arrays.hashCode(this.operations), this.ext); } @Override public boolean equals(Object object) { @@ -140,14 +132,14 @@ public boolean equals(Object object) { } Transaction other = (Transaction) object; - return Objects.equal(this.sourceAccount, other.sourceAccount) && Objects.equal(this.fee, other.fee) && Objects.equal(this.seqNum, other.seqNum) && Objects.equal(this.timeBounds, other.timeBounds) && Objects.equal(this.memo, other.memo) && Arrays.equals(this.operations, other.operations) && Objects.equal(this.ext, other.ext); + return Objects.equal(this.sourceAccount, other.sourceAccount) && Objects.equal(this.fee, other.fee) && Objects.equal(this.seqNum, other.seqNum) && Objects.equal(this.cond, other.cond) && Objects.equal(this.memo, other.memo) && Arrays.equals(this.operations, other.operations) && Objects.equal(this.ext, other.ext); } public static final class Builder { private MuxedAccount sourceAccount; private Uint32 fee; private SequenceNumber seqNum; - private TimeBounds timeBounds; + private Preconditions cond; private Memo memo; private Operation[] operations; private TransactionExt ext; @@ -167,8 +159,8 @@ public Builder seqNum(SequenceNumber seqNum) { return this; } - public Builder timeBounds(TimeBounds timeBounds) { - this.timeBounds = timeBounds; + public Builder cond(Preconditions cond) { + this.cond = cond; return this; } @@ -192,7 +184,7 @@ public Transaction build() { val.setSourceAccount(sourceAccount); val.setFee(fee); val.setSeqNum(seqNum); - val.setTimeBounds(timeBounds); + val.setCond(cond); val.setMemo(memo); val.setOperations(operations); val.setExt(ext); diff --git a/src/main/java/org/stellar/sdk/xdr/TrustLineEntry.java b/src/main/java/org/stellar/sdk/xdr/TrustLineEntry.java index 668c07d37..1a0417c29 100644 --- a/src/main/java/org/stellar/sdk/xdr/TrustLineEntry.java +++ b/src/main/java/org/stellar/sdk/xdr/TrustLineEntry.java @@ -4,18 +4,18 @@ package org.stellar.sdk.xdr; -import java.io.IOException; - import com.google.common.base.Objects; +import java.io.IOException; + // === xdr source ============================================================ // struct TrustLineEntry // { -// AccountID accountID; // account this trustline belongs to -// TrustLineAsset asset; // type of asset (with issuer) -// int64 balance; // how much of this asset the user has. -// // Asset defines the unit for this; +// AccountID accountID; // account this trustline belongs to +// TrustLineAsset asset; // type of asset (with issuer) +// int64 balance; // how much of this asset the user has. +// // Asset defines the unit for this; // // int64 limit; // balance cannot be above this // uint32 flags; // see TrustLineFlags diff --git a/src/test/java/org/stellar/sdk/SetOptionsOperationTest.java b/src/test/java/org/stellar/sdk/SetOptionsOperationTest.java new file mode 100644 index 000000000..ece16d1aa --- /dev/null +++ b/src/test/java/org/stellar/sdk/SetOptionsOperationTest.java @@ -0,0 +1,43 @@ +package org.stellar.sdk; + +import com.google.common.io.BaseEncoding; +import org.junit.Test; +import org.stellar.sdk.xdr.SignerKey; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +public class SetOptionsOperationTest { + KeyPair source = KeyPair.fromSecretSeed("SC4CGETADVYTCR5HEAVZRB3DZQY5Y4J7RFNJTRA6ESMHIPEZUSTE2QDK"); + + @Test + public void testPaylodSignerKey() { + SetOptionsOperation.Builder builder = new SetOptionsOperation.Builder(); + String payloadSignerStrKey = "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"; + + byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".toUpperCase()); + SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner(payloadSignerStrKey, payload); + SignerKey signerKey = Signer.signedPayload(signedPayloadSigner); + + builder.setSigner(signerKey, 1); + builder.setSourceAccount(source.getAccountId()); + + SetOptionsOperation operation = builder.build(); + + org.stellar.sdk.xdr.Operation xdr = operation.toXdr(AccountConverter.enableMuxed()); + SetOptionsOperation parsedOperation = (SetOptionsOperation) Operation.fromXdr(AccountConverter.enableMuxed(), xdr); + + // verify round trip between xdr and pojo + assertEquals(source.getAccountId(), parsedOperation.getSourceAccount()); + assertArrayEquals(signedPayloadSigner.getDecodedAccountId(), parsedOperation.getSigner().getEd25519SignedPayload().getEd25519().getUint256()); + assertArrayEquals(signedPayloadSigner.getPayload(), parsedOperation.getSigner().getEd25519SignedPayload().getPayload()); + + // verify serialized xdr emitted with signed payload + assertEquals( + "AAAAAQAAAAC7JAuE3XvquOnbsgv2SRztjuk4RoBVefQ0rlrFMMQvfAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + + "AAAAAAAAAAAAAAAEAAAADPww0v5OtDZlx0EzMkPcFURyDiq2XNKSi+w16A/x/6JoAAAAgAQIDBAUGBwgJCgsMDQ4PEBES" + + "ExQVFhcYGRobHB0eHyAAAAAB", + operation.toXdrBase64(AccountConverter.enableMuxed())); + } + +} diff --git a/src/test/java/org/stellar/sdk/SignerTest.java b/src/test/java/org/stellar/sdk/SignerTest.java new file mode 100644 index 000000000..8b6a81cec --- /dev/null +++ b/src/test/java/org/stellar/sdk/SignerTest.java @@ -0,0 +1,45 @@ +package org.stellar.sdk; + +import com.google.common.io.BaseEncoding; +import org.junit.Test; +import org.stellar.sdk.xdr.SignerKey; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.fail; + +public class SignerTest { + + @Test + public void itCreatesSignedPayloadSigner() { + String accountStrKey = "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"; + + byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".toUpperCase()); + SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner(accountStrKey, payload); + SignerKey signerKey = Signer.signedPayload(signedPayloadSigner); + + assertArrayEquals(signerKey.getEd25519SignedPayload().getPayload(), payload); + assertArrayEquals(signerKey.getEd25519SignedPayload().getEd25519().getUint256(),signedPayloadSigner.getDecodedAccountId()); + } + + @Test + public void itFailsWhenInvalidParameters() { + String accountStrKey = "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTnotgood"; + byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".toUpperCase()); + SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner(accountStrKey, payload); + + try { + SignerKey signerKey = Signer.signedPayload(signedPayloadSigner); + fail("should not create a payload signer if invalid account"); + } catch (IllegalArgumentException ignored) {} + + accountStrKey = "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"; + payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2001".toUpperCase()); + signedPayloadSigner = new SignedPayloadSigner(accountStrKey, payload); + try { + SignerKey signerKey = Signer.signedPayload(signedPayloadSigner); + fail("should not create a payload signer if payload > max length"); + } catch (IllegalArgumentException ignored) {} + + } + +} diff --git a/src/test/java/org/stellar/sdk/StrKeyTest.java b/src/test/java/org/stellar/sdk/StrKeyTest.java index 689ba0a52..e0dc89a0e 100644 --- a/src/test/java/org/stellar/sdk/StrKeyTest.java +++ b/src/test/java/org/stellar/sdk/StrKeyTest.java @@ -1,5 +1,6 @@ package org.stellar.sdk; +import com.google.common.io.BaseEncoding; import org.junit.Test; import org.stellar.sdk.xdr.AccountID; import org.stellar.sdk.xdr.CryptoKeyType; @@ -7,7 +8,10 @@ import java.io.IOException; -import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + public class StrKeyTest { @Test @@ -45,6 +49,7 @@ public void testDecodedVersionByte() { assertEquals(StrKey.decodeVersionByte("MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVAAAAAAAAAAAAAJLK"), StrKey.VersionByte.MUXED); assertEquals(StrKey.decodeVersionByte("TAQCSRX2RIDJNHFIFHWD63X7D7D6TRT5Y2S6E3TEMXTG5W3OECHZ2OG4"), StrKey.VersionByte.PRE_AUTH_TX); assertEquals(StrKey.decodeVersionByte("XDRPF6NZRR7EEVO7ESIWUDXHAOMM2QSKIQQBJK6I2FB7YKDZES5UCLWD"), StrKey.VersionByte.SHA256_HASH); + assertEquals(StrKey.decodeVersionByte("PDPYP7E6NEYZSVOTV6M23OFM2XRIMPDUJABHGHHH2Y67X7JL25GW6AAAAAAAAAAAAAAJEVA"), StrKey.VersionByte.SIGNED_PAYLOAD); } @Test() @@ -129,6 +134,62 @@ public void testRoundTripHashXFromBytes() { assertArrayEquals(data, StrKey.decodeCheck(StrKey.VersionByte.SHA256_HASH, hashX.toCharArray())); } + @Test + public void testValidSignedPayloadEncode() { + // Valid signed payload with an ed25519 public key and a 32-byte payload. + byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".toUpperCase()); + SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner("GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ", payload); + String encoded = StrKey.encodeSignedPayload(signedPayloadSigner); + assertEquals(encoded, "PA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAQACAQDAQCQMBYIBEFAWDANBYHRAEISCMKBKFQXDAMRUGY4DUPB6IBZGM"); + + // Valid signed payload with an ed25519 public key and a 29-byte payload. + payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d".toUpperCase()); + signedPayloadSigner = new SignedPayloadSigner("GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ", payload); + encoded = StrKey.encodeSignedPayload(signedPayloadSigner); + assertEquals(encoded, "PA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAOQCAQDAQCQMBYIBEFAWDANBYHRAEISCMKBKFQXDAMRUGY4DUAAAAFGBU"); + } + + @Test + public void testInvalidSignedPayloadEncode() { + byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2001".toUpperCase()); + SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner("GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ", payload); + try { + StrKey.encodeSignedPayload(signedPayloadSigner); + fail("should not encode signed payloads > 64"); + } catch (FormatException ignored){} + + payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".toUpperCase()); + signedPayloadSigner = new SignedPayloadSigner("GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KV_notgood", payload); + try { + StrKey.encodeSignedPayload(signedPayloadSigner); + fail("should not encode when accountid is not valid strkey"); + } catch (FormatException ignored){} + } + + @Test + public void testRoundTripSignedPayloadVersionByte() { + byte[] data = rawBytes( + // ed25519 + 0x36, 0x3e, 0xaa, 0x38, 0x67, 0x84, 0x1f, 0xba, + 0xd0, 0xf4, 0xed, 0x88, 0xc7, 0x79, 0xe4, 0xfe, + 0x66, 0xe5, 0x6a, 0x24, 0x70, 0xdc, 0x98, 0xc0, + 0xec, 0x9c, 0x07, 0x3d, 0x05, 0xc7, 0xb1, 0x03, + // payload length + 0x00, 0x00, 0x00, 0x09, + // payload + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + // padding + 0x00, 0x00, 0x00); + + String hashX = "PA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAAAAAEQAAAAAAAAAAAAAAAAAABBXA"; + assertEquals( + hashX, + String.valueOf(StrKey.encodeCheck(StrKey.VersionByte.SIGNED_PAYLOAD, data)) + ); + assertArrayEquals(data, StrKey.decodeCheck(StrKey.VersionByte.SIGNED_PAYLOAD, hashX.toCharArray())); + } + @Test public void testDecodeEmpty() { try { diff --git a/src/test/java/org/stellar/sdk/TransactionTest.java b/src/test/java/org/stellar/sdk/TransactionTest.java index 681d18d2b..74bba77f8 100644 --- a/src/test/java/org/stellar/sdk/TransactionTest.java +++ b/src/test/java/org/stellar/sdk/TransactionTest.java @@ -9,7 +9,10 @@ import java.security.SecureRandom; import java.util.Arrays; -import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class TransactionTest { @@ -121,8 +124,8 @@ public void testBuilderTimeBounds() throws FormatException, IOException { ); org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); - assertEquals(decodedTransaction.getV1().getTx().getTimeBounds().getMinTime().getTimePoint().getUint64().longValue(), 42); - assertEquals(decodedTransaction.getV1().getTx().getTimeBounds().getMaxTime().getTimePoint().getUint64().longValue(), 1337); + assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMinTime().getTimePoint().getUint64().longValue(), 42); + assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMaxTime().getTimePoint().getUint64().longValue(), 1337); Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); @@ -299,8 +302,8 @@ public void testBuilderTimeBoundsNoMaxTime() throws FormatException, IOException ); org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); - assertEquals(decodedTransaction.getV1().getTx().getTimeBounds().getMinTime().getTimePoint().getUint64().longValue(), 42); - assertEquals(decodedTransaction.getV1().getTx().getTimeBounds().getMaxTime().getTimePoint().getUint64().longValue(), 0); + assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMinTime().getTimePoint().getUint64().longValue(), 42); + assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMaxTime().getTimePoint().getUint64().longValue(), 0); } @Test diff --git a/src/test/java/org/stellar/sdk/xdr/AccountEntryDecodeTest.java b/src/test/java/org/stellar/sdk/xdr/AccountEntryDecodeTest.java new file mode 100644 index 000000000..0163f5b7d --- /dev/null +++ b/src/test/java/org/stellar/sdk/xdr/AccountEntryDecodeTest.java @@ -0,0 +1,50 @@ +package org.stellar.sdk.xdr; + +import com.google.common.io.BaseEncoding; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import static org.junit.Assert.assertArrayEquals; + +public class AccountEntryDecodeTest { + + BaseEncoding base64Encoding = BaseEncoding.base64(); + + @Test + public void testDecodeSignerPayload() throws IOException { + AccountEntry.Builder bldr = new AccountEntry.Builder(); + Signer signer = new Signer(); + SignerKey signerKey = new SignerKey(); + signerKey.setDiscriminant(SignerKeyType.SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD); + signerKey.setEd25519SignedPayload(new SignerKey.SignerKeyEd25519SignedPayload()); + signerKey.getEd25519SignedPayload().setPayload(new byte[]{1,2,3,4}); + signerKey.getEd25519SignedPayload().setEd25519(new Uint256(new byte[32])); + signer.setKey(signerKey); + signer.setWeight(new Uint32(1)); + bldr.signers(new Signer[]{signer}); + bldr.accountID(new AccountID(new PublicKey.Builder().discriminant(PublicKeyType.PUBLIC_KEY_TYPE_ED25519).ed25519(new Uint256(new byte[32])).build())); + bldr.seqNum(new SequenceNumber(new Int64(1L))); + bldr.balance(new Int64(0L)); + bldr.numSubEntries(new Uint32(0)); + bldr.flags(new Uint32(0)); + bldr.homeDomain(new String32(new XdrString(""))); + bldr.thresholds(new Thresholds(new byte[3])); + bldr.ext(new AccountEntry.AccountEntryExt.Builder().discriminant(0).build()); + + AccountEntry xdr = bldr.build(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + XdrDataOutputStream outputStream = new XdrDataOutputStream(baos); + AccountEntry.encode(outputStream, xdr); + String encodedXdr = base64Encoding.encode(baos.toByteArray()); + + byte[] decodedbBytes = base64Encoding.decode(encodedXdr); + + AccountEntry accountEntry = AccountEntry.decode(new XdrDataInputStream(new ByteArrayInputStream(decodedbBytes))); + assertArrayEquals(new byte[32], accountEntry.getSigners()[0].getKey().getEd25519SignedPayload().getEd25519().getUint256()); + assertArrayEquals(new byte[]{1,2,3,4}, accountEntry.getSigners()[0].getKey().getEd25519SignedPayload().getPayload()); + } +} diff --git a/xdr/Stellar-ledger-entries.x b/xdr/Stellar-ledger-entries.x index 198e93dd8..a1bb8e726 100644 --- a/xdr/Stellar-ledger-entries.x +++ b/xdr/Stellar-ledger-entries.x @@ -13,6 +13,7 @@ typedef string string32<32>; typedef string string64<64>; typedef int64 SequenceNumber; typedef uint64 TimePoint; +typedef int64 Duration; typedef opaque DataValue<64>; typedef Hash PoolID; // SHA256(LiquidityPoolParameters) @@ -133,6 +134,19 @@ const MAX_SIGNERS = 20; typedef AccountID* SponsorshipDescriptor; +struct AccountEntryExtensionV3 +{ + // We can use this to add more fields, or because it is first, to + // change AccountEntryExtensionV3 into a union. + ExtensionPoint ext; + + // Ledger number at which `seqNum` took on its present value. + uint32 seqLedger; + + // Time at which `seqNum` took on its present value. + TimePoint seqTime; +}; + struct AccountEntryExtensionV2 { uint32 numSponsored; @@ -143,6 +157,8 @@ struct AccountEntryExtensionV2 { case 0: void; + case 3: + AccountEntryExtensionV3 v3; } ext; }; @@ -257,10 +273,10 @@ struct TrustLineEntryExtensionV2 struct TrustLineEntry { - AccountID accountID; // account this trustline belongs to - TrustLineAsset asset; // type of asset (with issuer) - int64 balance; // how much of this asset the user has. - // Asset defines the unit for this; + AccountID accountID; // account this trustline belongs to + TrustLineAsset asset; // type of asset (with issuer) + int64 balance; // how much of this asset the user has. + // Asset defines the unit for this; int64 limit; // balance cannot be above this uint32 flags; // see TrustLineFlags @@ -290,7 +306,7 @@ struct TrustLineEntry enum OfferEntryFlags { - // issuer has authorized account to perform transactions with its credit + // an offer with this flag will not act on and take a reverse offer of equal price PASSIVE_FLAG = 1 }; @@ -368,10 +384,10 @@ case CLAIM_PREDICATE_OR: case CLAIM_PREDICATE_NOT: ClaimPredicate* notPredicate; case CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME: - int64 absBefore; // Predicate will be true if closeTime < absBefore + TimePoint absBefore; // Predicate will be true if closeTime < absBefore case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME: - int64 relBefore; // Seconds since closeTime of the ledger in which the - // ClaimableBalanceEntry was created + Duration relBefore; // Seconds since closeTime of the ledger in which the + // ClaimableBalanceEntry was created }; enum ClaimantType @@ -391,16 +407,13 @@ case CLAIMANT_TYPE_V0: enum ClaimableBalanceIDType { - CLAIMABLE_BALANCE_ID_TYPE_V0 = 0, - CLAIMABLE_BALANCE_ID_TYPE_FROM_POOL_REVOKE = 1 + CLAIMABLE_BALANCE_ID_TYPE_V0 = 0 }; union ClaimableBalanceID switch (ClaimableBalanceIDType type) { case CLAIMABLE_BALANCE_ID_TYPE_V0: Hash v0; -case CLAIMABLE_BALANCE_ID_TYPE_FROM_POOL_REVOKE: - Hash fromPoolRevoke; }; enum ClaimableBalanceFlags @@ -576,4 +589,4 @@ enum EnvelopeType ENVELOPE_TYPE_OP_ID = 6, ENVELOPE_TYPE_POOL_REVOKE_OP_ID = 7 }; -} +} \ No newline at end of file diff --git a/xdr/Stellar-ledger.x b/xdr/Stellar-ledger.x index 8a45985c8..b33e85a57 100644 --- a/xdr/Stellar-ledger.x +++ b/xdr/Stellar-ledger.x @@ -33,7 +33,7 @@ struct StellarValue // this is a vector of encoded 'LedgerUpgrade' so that nodes can drop // unknown steps during consensus if needed. // see notes below on 'LedgerUpgrade' for more detail - // max size is dictated by number of upgrade types ( room for future) + // max size is dictated by number of upgrade types (+ room for future) UpgradeType upgrades<6>; // reserved for future use @@ -47,11 +47,10 @@ struct StellarValue ext; }; -const MASK_LEDGERHEADER_FLAGS = 0x7; +const MASK_LEDGER_HEADER_FLAGS = 0x7; enum LedgerHeaderFlags -{ // masks for each flag - +{ DISABLE_LIQUIDITY_POOL_TRADING_FLAG = 0x1, DISABLE_LIQUIDITY_POOL_DEPOSIT_FLAG = 0x2, DISABLE_LIQUIDITY_POOL_WITHDRAWAL_FLAG = 0x4 @@ -59,7 +58,7 @@ enum LedgerHeaderFlags struct LedgerHeaderExtensionV1 { - uint32 flags; // UpgradeFlags + uint32 flags; // LedgerHeaderFlags union switch (int v) { @@ -69,7 +68,6 @@ struct LedgerHeaderExtensionV1 ext; }; - /* The LedgerHeader is the highest level structure representing the * state of a ledger, cryptographically linked to previous ledgers. */ @@ -365,4 +363,4 @@ union LedgerCloseMeta switch (int v) case 0: LedgerCloseMetaV0 v0; }; -} +} \ No newline at end of file diff --git a/xdr/Stellar-transaction.x b/xdr/Stellar-transaction.x index 19d0240d0..cd31b3a33 100644 --- a/xdr/Stellar-transaction.x +++ b/xdr/Stellar-transaction.x @@ -527,7 +527,7 @@ struct Operation body; }; -union OperationID switch (EnvelopeType type) +union HashIDPreimage switch (EnvelopeType type) { case ENVELOPE_TYPE_OP_ID: struct @@ -535,7 +535,7 @@ case ENVELOPE_TYPE_OP_ID: AccountID sourceAccount; SequenceNumber seqNum; uint32 opNum; - } id; + } operationID; case ENVELOPE_TYPE_POOL_REVOKE_OP_ID: struct { @@ -544,7 +544,7 @@ case ENVELOPE_TYPE_POOL_REVOKE_OP_ID: uint32 opNum; PoolID liquidityPoolID; Asset asset; - } revokeId; + } revokeID; }; enum MemoType @@ -576,6 +576,58 @@ struct TimeBounds TimePoint maxTime; // 0 here means no maxTime }; +struct LedgerBounds +{ + uint32 minLedger; + uint32 maxLedger; +}; + +struct PreconditionsV2 { + TimeBounds *timeBounds; + + // Transaciton only valid for ledger numbers n such that + // minLedger <= n < maxLedger + LedgerBounds *ledgerBounds; + + // If NULL, only valid when sourceAccount's sequence number + // is seqNum - 1. Otherwise, valid when sourceAccount's + // sequence number n satisfies minSeqNum <= n < tx.seqNum. + // Note that after execution the account's sequence number + // is always raised to tx.seqNum, and a transaction is not + // valid if tx.seqNum is too high to ensure replay protection. + SequenceNumber *minSeqNum; + + // For the transaction to be valid, the current ledger time must + // be at least minSeqAge greater than sourceAccount's seqTime. + Duration minSeqAge; + + // For the transaction to be valid, the current ledger number + // must be at least minSeqLedgerGap greater than sourceAccount's + // seqLedger. + uint32 minSeqLedgerGap; + + // For the transaction to be valid, there must be a signature + // corresponding to every Signer in this array, even if the + // signature is not otherwise required by the sourceAccount or + // operations. + SignerKey extraSigners<2>; +}; + +enum PreconditionType { + PRECOND_NONE = 0, + PRECOND_TIME = 1, + PRECOND_V2 = 2 +}; + +union Preconditions switch (PreconditionType type) { + case PRECOND_NONE: + void; + case PRECOND_TIME: + TimeBounds timeBounds; + case PRECOND_V2: + PreconditionsV2 v2; +}; + // maximum number of operations per transaction const MAX_OPS_PER_TX = 100; @@ -627,8 +679,8 @@ struct Transaction // sequence number to consume in the account SequenceNumber seqNum; - // validity range (inclusive) for the last ledger close time - TimeBounds* timeBounds; + // validity conditions + Preconditions cond; Memo memo; @@ -1082,7 +1134,7 @@ enum AllowTrustResultCode ALLOW_TRUST_CANT_REVOKE = -4, // source account can't revoke trust, ALLOW_TRUST_SELF_NOT_ALLOWED = -5, // trusting self is not allowed ALLOW_TRUST_LOW_RESERVE = -6 // claimable balances can't be created - // on revoke due to low reserves + // on revoke due to low reserves }; union AllowTrustResult switch (AllowTrustResultCode code) @@ -1415,7 +1467,6 @@ default: void; }; - /* High level Operation Result */ enum OperationResultCode { @@ -1582,4 +1633,4 @@ struct TransactionResult } ext; }; -} +} \ No newline at end of file diff --git a/xdr/Stellar-types.x b/xdr/Stellar-types.x index 8f7d5c206..7d5a1467c 100644 --- a/xdr/Stellar-types.x +++ b/xdr/Stellar-types.x @@ -14,11 +14,20 @@ typedef int int32; typedef unsigned hyper uint64; typedef hyper int64; +// An ExtensionPoint is always marshaled as a 32-bit 0 value. At a +// later point, it can be replaced by a different union so as to +// extend a structure. +union ExtensionPoint switch (int v) { +case 0: + void; +}; + enum CryptoKeyType { KEY_TYPE_ED25519 = 0, KEY_TYPE_PRE_AUTH_TX = 1, KEY_TYPE_HASH_X = 2, + KEY_TYPE_ED25519_SIGNED_PAYLOAD = 3, // MUXED enum values for supported type are derived from the enum values // above by ORing them with 0x100 KEY_TYPE_MUXED_ED25519 = 0x100 @@ -33,7 +42,8 @@ enum SignerKeyType { SIGNER_KEY_TYPE_ED25519 = KEY_TYPE_ED25519, SIGNER_KEY_TYPE_PRE_AUTH_TX = KEY_TYPE_PRE_AUTH_TX, - SIGNER_KEY_TYPE_HASH_X = KEY_TYPE_HASH_X + SIGNER_KEY_TYPE_HASH_X = KEY_TYPE_HASH_X, + SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD = KEY_TYPE_ED25519_SIGNED_PAYLOAD }; union PublicKey switch (PublicKeyType type) @@ -52,6 +62,13 @@ case SIGNER_KEY_TYPE_PRE_AUTH_TX: case SIGNER_KEY_TYPE_HASH_X: /* Hash of random 256 bit preimage X */ uint256 hashX; +case SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD: + struct { + /* Public key that must sign the payload. */ + uint256 ed25519; + /* Payload to be raw signed by ed25519. */ + opaque payload<64>; + } ed25519SignedPayload; }; // variable size as the size depends on the signature scheme used @@ -80,4 +97,4 @@ struct HmacSha256Mac { opaque mac[32]; }; -} +} \ No newline at end of file From 5bf518bfd24895520b5e4f391ad71470eda584af Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Mon, 21 Mar 2022 15:20:21 -0700 Subject: [PATCH 02/29] #410: use xdr data types for predicate time dimensions, incorporate some pr feedback --- CHANGELOG.md | 8 ++++ src/main/java/org/stellar/sdk/Predicate.java | 46 ++++++++----------- .../org/stellar/sdk/SignedPayloadSigner.java | 2 +- .../sdk/responses/PredicateDeserializer.java | 11 +++-- .../ClaimableBalancePageDeserializerTest.java | 2 +- .../sdk/responses/EffectDeserializerTest.java | 4 +- .../responses/OperationDeserializerTest.java | 2 +- 7 files changed, 39 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa6114035..2b62f7446 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ As this project is pre 1.0, breaking changes may happen for minor version bumps. * Update XDR definitions and auto-generated classes to support upcoming protocol 19 release ([#276](https://github.com/stellar/java-stellar-sdk/pull/276)). * Extend StrKey implementation to handle [CAP 40 Payload Signer](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0040.md) ([#276](https://github.com/stellar/java-stellar-sdk/pull/276)). +### Breaking changes + +* Predicate.AbsBefore + * `Predicate.AbsBefore(org.stellar.sdk.xdr.TimePoint)` + * `org.stellar.sdk.xdr.TimePoint Predicate.AbsBefore.getTimestampSeconds()` +* Predicate.RelBefore + * `Predicate.RelBefore(org.stellar.sdk.xdr.Duration)` + * `org.stellar.sdk.xdr.Duration Predicate.RelBefore.getSecondsSinceClose()` ## 0.31.0 diff --git a/src/main/java/org/stellar/sdk/Predicate.java b/src/main/java/org/stellar/sdk/Predicate.java index 35d7c7617..d2cea4591 100644 --- a/src/main/java/org/stellar/sdk/Predicate.java +++ b/src/main/java/org/stellar/sdk/Predicate.java @@ -37,9 +37,9 @@ public static Predicate fromXdr(org.stellar.sdk.xdr.ClaimPredicate xdr) { case CLAIM_PREDICATE_NOT: return new Not(fromXdr(xdr.getNotPredicate())); case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME: - return new RelBefore(xdr.getRelBefore().getDuration().getInt64()); + return new RelBefore(xdr.getRelBefore()); case CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME: - return new AbsBefore(xdr.getAbsBefore().getTimePoint().getUint64()); + return new AbsBefore(xdr.getAbsBefore()); default: throw new IllegalArgumentException("Unknown asset type " + xdr.getDiscriminant()); } @@ -181,18 +181,18 @@ public ClaimPredicate toXdr() { } public static class AbsBefore extends Predicate { - private final long epochSeconds; + private final TimePoint timePoint; - public AbsBefore(long epochSeconds) { - this.epochSeconds = epochSeconds; + public AbsBefore(TimePoint timePoint) { + this.timePoint = timePoint; } - public long getTimestampSeconds() { - return epochSeconds; + public TimePoint getTimestampSeconds() { + return timePoint; } public Instant getDate() { - return Instant.ofEpochSecond(epochSeconds); + return Instant.ofEpochSecond(timePoint.getTimePoint().getUint64()); } @Override @@ -200,36 +200,32 @@ public boolean equals(Object o) { if (this == o) { return true; } - return (getClass() == o.getClass()) && Objects.equal(epochSeconds, ((AbsBefore)o).epochSeconds); + return (getClass() == o.getClass()) && Objects.equal(timePoint, ((AbsBefore)o).timePoint); } @Override public int hashCode() { - return Objects.hashCode(epochSeconds); + return Objects.hashCode(timePoint); } @Override public ClaimPredicate toXdr() { org.stellar.sdk.xdr.ClaimPredicate xdr = new org.stellar.sdk.xdr.ClaimPredicate(); xdr.setDiscriminant(ClaimPredicateType.CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME); - Uint64 t = new Uint64(); - t.setUint64(epochSeconds); - TimePoint beforeTime = new TimePoint(); - beforeTime.setTimePoint(t); - xdr.setAbsBefore(beforeTime); + xdr.setAbsBefore(timePoint); return xdr; } } public static class RelBefore extends Predicate { - private final long secondsSinceClose; + private final Duration duration; - public RelBefore(long secondsSinceClose) { - this.secondsSinceClose = secondsSinceClose; + public RelBefore(Duration secondsSinceClose) { + this.duration = secondsSinceClose; } - public long getSecondsSinceClose() { - return secondsSinceClose; + public Duration getSecondsSinceClose() { + return duration; } @Override @@ -237,23 +233,19 @@ public boolean equals(Object o) { if (this == o) { return true; } - return (getClass() == o.getClass()) && Objects.equal(secondsSinceClose, ((RelBefore)o).secondsSinceClose); + return (getClass() == o.getClass()) && Objects.equal(duration, ((RelBefore)o).duration); } @Override public int hashCode() { - return Objects.hashCode(secondsSinceClose); + return Objects.hashCode(duration); } @Override public ClaimPredicate toXdr() { org.stellar.sdk.xdr.ClaimPredicate xdr = new org.stellar.sdk.xdr.ClaimPredicate(); xdr.setDiscriminant(ClaimPredicateType.CLAIM_PREDICATE_BEFORE_RELATIVE_TIME); - Int64 t = new Int64(); - t.setInt64(secondsSinceClose); - Duration beforeTime = new Duration(); - beforeTime.setDuration(t); - xdr.setRelBefore(beforeTime); + xdr.setRelBefore(duration); return xdr; } } diff --git a/src/main/java/org/stellar/sdk/SignedPayloadSigner.java b/src/main/java/org/stellar/sdk/SignedPayloadSigner.java index 06e573a5c..70b60d763 100644 --- a/src/main/java/org/stellar/sdk/SignedPayloadSigner.java +++ b/src/main/java/org/stellar/sdk/SignedPayloadSigner.java @@ -19,7 +19,7 @@ public SignedPayloadSigner(String accountId, byte[] payload) { } /** - * get the StrKey encoded representation of a stellar account id + * get the StrKey encoded representation of a signed payload signer * @return stellar account id in StrKey encoding */ public String getEncodedAccountId() { diff --git a/src/main/java/org/stellar/sdk/responses/PredicateDeserializer.java b/src/main/java/org/stellar/sdk/responses/PredicateDeserializer.java index 6c129750f..1a51824e0 100644 --- a/src/main/java/org/stellar/sdk/responses/PredicateDeserializer.java +++ b/src/main/java/org/stellar/sdk/responses/PredicateDeserializer.java @@ -3,6 +3,10 @@ import com.google.common.collect.Lists; import com.google.gson.*; import org.stellar.sdk.Predicate; +import org.stellar.sdk.xdr.Duration; +import org.stellar.sdk.xdr.Int64; +import org.stellar.sdk.xdr.TimePoint; +import org.stellar.sdk.xdr.Uint64; import org.threeten.bp.Instant; import java.lang.reflect.Type; @@ -39,15 +43,14 @@ public Predicate deserialize(JsonElement json, Type typeOfT, JsonDeserialization // backwards compatible for abs before, uses the newer abs_before_epoch if available from server // otherwise if abs_before_epoch is absent, it will fall back to original attempt to parse abs_before date string if (obj.has("abs_before_epoch")) { - return new Predicate.AbsBefore(obj.get("abs_before_epoch").getAsLong()); + return new Predicate.AbsBefore(new TimePoint(new Uint64(obj.get("abs_before_epoch").getAsLong()))); } else if (obj.has("abs_before")) { String formattedDate = obj.get("abs_before").getAsString(); - return new Predicate.AbsBefore(Instant.parse(formattedDate).getEpochSecond()); + return new Predicate.AbsBefore(new TimePoint(new Uint64(Instant.parse(formattedDate).getEpochSecond()))); } if (obj.has("rel_before")) { - Long relBefore = obj.get("rel_before").getAsLong(); - return new Predicate.RelBefore(relBefore); + return new Predicate.RelBefore(new Duration(new Int64(obj.get("rel_before").getAsLong()))); } throw new IllegalArgumentException("Unsupported predicate: "+json.toString()); diff --git a/src/test/java/org/stellar/sdk/responses/ClaimableBalancePageDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/ClaimableBalancePageDeserializerTest.java index b53516305..01bd459ed 100644 --- a/src/test/java/org/stellar/sdk/responses/ClaimableBalancePageDeserializerTest.java +++ b/src/test/java/org/stellar/sdk/responses/ClaimableBalancePageDeserializerTest.java @@ -34,7 +34,7 @@ public void testDeserialize() { Predicate.AbsBefore absBefore = (Predicate.AbsBefore)or.getInner().get(0); Predicate.RelBefore relBefore = (Predicate.RelBefore)or.getInner().get(1); assertEquals(absBefore.getDate().toString(), "2020-09-28T17:57:04Z"); - assertEquals(relBefore.getSecondsSinceClose(), 12); + assertEquals(relBefore.getSecondsSinceClose().getDuration().getInt64().longValue(), 12L); assertEquals(claimableBalancePage.getRecords().get(1).getId(), "00000000ae76f49e8513d0922b6bcbc8a3f5c4c0a5161871f27924e08724646acab56cd3"); assertEquals(claimableBalancePage.getRecords().get(1).getAsset(), Asset.create("native")); diff --git a/src/test/java/org/stellar/sdk/responses/EffectDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/EffectDeserializerTest.java index 431c8e89e..28db4fceb 100644 --- a/src/test/java/org/stellar/sdk/responses/EffectDeserializerTest.java +++ b/src/test/java/org/stellar/sdk/responses/EffectDeserializerTest.java @@ -284,7 +284,7 @@ public void testDeserializeClaimableBalanceClaimantCreatedEffect() { assertEquals(effect.getBalanceId(), "0000000071d3336fa6b6cf81fcbeda85a503ccfabc786ab1066594716f3f9551ea4b89ca"); assertEquals(effect.getType(), "claimable_balance_claimant_created"); assertSame(effect.getPredicate().getClass(), Predicate.AbsBefore.class); - assertEquals(((Predicate.AbsBefore)effect.getPredicate()).getTimestampSeconds(), 1234567890982222222L); + assertEquals(((Predicate.AbsBefore)effect.getPredicate()).getTimestampSeconds().getTimePoint().getUint64().longValue(), 1234567890982222222L); } @Test @@ -322,7 +322,7 @@ public void testBackwardsCompatAbsBeforeEpoch() { assertEquals(effect.getBalanceId(), "0000000071d3336fa6b6cf81fcbeda85a503ccfabc786ab1066594716f3f9551ea4b89ca"); assertEquals(effect.getType(), "claimable_balance_claimant_created"); assertSame(effect.getPredicate().getClass(), Predicate.AbsBefore.class); - assertEquals(((Predicate.AbsBefore)effect.getPredicate()).getTimestampSeconds(), 1637479450L); + assertEquals(((Predicate.AbsBefore)effect.getPredicate()).getTimestampSeconds().getTimePoint().getUint64().longValue(), 1637479450L); } @Test diff --git a/src/test/java/org/stellar/sdk/responses/OperationDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/OperationDeserializerTest.java index 19bde0c35..25a8bece7 100644 --- a/src/test/java/org/stellar/sdk/responses/OperationDeserializerTest.java +++ b/src/test/java/org/stellar/sdk/responses/OperationDeserializerTest.java @@ -1567,7 +1567,7 @@ public void testDeserializeCreateClaimableBalanceOperation() { assertEquals(operation.getClaimants().size(), 2); assertEquals(operation.getClaimants().get(0).getDestination(), "GBQECQVAS2FJ7DLCUXDASZAJQLWPXNTCR2DGBPKQDO3QS66TJXLRHFIK"); assertSame(operation.getClaimants().get(0).getPredicate().getClass(), Predicate.AbsBefore.class); - assertEquals(((Predicate.AbsBefore)operation.getClaimants().get(0).getPredicate()).getTimestampSeconds(), 1234567890982222222L); + assertEquals(((Predicate.AbsBefore)operation.getClaimants().get(0).getPredicate()).getTimestampSeconds().getTimePoint().getUint64().longValue(), 1234567890982222222L); assertEquals(operation.getClaimants().get(1).getDestination(), "GAKFBRS24U3PEN6XDMEX4JEV5NGBARS2ZFF5O4CF3JL464SQSD4MDCPF"); assertSame(operation.getClaimants().get(1).getPredicate().getClass(), Predicate.Unconditional.class); assertEquals(operation.getType(), "create_claimable_balance"); From c44b1de9fbe6eb0b5bd98fe22b7771082b185384 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Mon, 21 Mar 2022 22:01:36 -0700 Subject: [PATCH 03/29] #410: use xdr encoding/decoding for signed payload (de)ser, use AccountID for signed payload pojo to check discriminant on public key types --- .../org/stellar/sdk/SignedPayloadSigner.java | 39 ++++++++++++------- src/main/java/org/stellar/sdk/Signer.java | 4 +- src/main/java/org/stellar/sdk/StrKey.java | 33 ++++++++-------- .../stellar/sdk/SetOptionsOperationTest.java | 4 +- .../stellar/sdk/SignedPayloadSignerTest.java | 18 +++++++++ src/test/java/org/stellar/sdk/SignerTest.java | 20 +++------- src/test/java/org/stellar/sdk/StrKeyTest.java | 13 ++----- 7 files changed, 74 insertions(+), 57 deletions(-) create mode 100644 src/test/java/org/stellar/sdk/SignedPayloadSignerTest.java diff --git a/src/main/java/org/stellar/sdk/SignedPayloadSigner.java b/src/main/java/org/stellar/sdk/SignedPayloadSigner.java index 70b60d763..1564fe9e6 100644 --- a/src/main/java/org/stellar/sdk/SignedPayloadSigner.java +++ b/src/main/java/org/stellar/sdk/SignedPayloadSigner.java @@ -1,37 +1,50 @@ package org.stellar.sdk; +import org.stellar.sdk.xdr.AccountID; +import org.stellar.sdk.xdr.PublicKey; +import org.stellar.sdk.xdr.PublicKeyType; +import org.stellar.sdk.xdr.Uint256; + +import static com.google.common.base.Preconditions.checkNotNull; + + /** * Data model for the signed payload signer */ public class SignedPayloadSigner { - private String accountId; + private AccountID accountId; private byte[] payload; /** * constructor * - * @param accountId - the StrKey format of a stellar AccountId + * @param accountId - the xdr AccountID * @param payload - the raw payload for a signed payload */ - public SignedPayloadSigner(String accountId, byte[] payload) { - this.accountId = accountId; - this.payload = payload; + public SignedPayloadSigner(AccountID accountId, byte[] payload) { + this.accountId = checkNotNull(accountId); + this.payload = checkNotNull(payload); } /** - * get the StrKey encoded representation of a signed payload signer - * @return stellar account id in StrKey encoding + * construcxtor + * + * @param ed25519PublicKey raw bytes of a ED25519 public key + * @param payload the raw payload for a signed payload */ - public String getEncodedAccountId() { - return accountId; + public SignedPayloadSigner(byte[] ed25519PublicKey, byte[] payload ) { + this(new AccountID( + new PublicKey.Builder() + .discriminant(PublicKeyType.PUBLIC_KEY_TYPE_ED25519) + .ed25519(new Uint256(ed25519PublicKey)).build()), payload); } /** - * get the binary format of a stellar account id, a Ed25519 public key - * @return stellar account id in binary format + * get the account that represents the signed payload signer + * @return stellar account */ - public byte[] getDecodedAccountId() { - return StrKey.decodeStellarAccountId(accountId); + public AccountID getAccountId() { + return accountId; } /** diff --git a/src/main/java/org/stellar/sdk/Signer.java b/src/main/java/org/stellar/sdk/Signer.java index cbf356af0..50b1501e7 100644 --- a/src/main/java/org/stellar/sdk/Signer.java +++ b/src/main/java/org/stellar/sdk/Signer.java @@ -82,13 +82,13 @@ public static SignerKey preAuthTx(byte[] hash) { * @return org.stellar.sdk.xdr.SignerKey */ public static SignerKey signedPayload(SignedPayloadSigner signedPayloadSigner) { - checkNotNull(signedPayloadSigner.getEncodedAccountId(), "accountId cannot be null"); + checkNotNull(signedPayloadSigner.getAccountId(), "accountId cannot be null"); checkArgument(signedPayloadSigner.getPayload().length <= SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH ); SignerKey signerKey = new SignerKey(); SignerKey.SignerKeyEd25519SignedPayload payloadSigner = new SignerKey.SignerKeyEd25519SignedPayload(); payloadSigner.setPayload(signedPayloadSigner.getPayload()); - payloadSigner.setEd25519(createUint256(signedPayloadSigner.getDecodedAccountId())); + payloadSigner.setEd25519(signedPayloadSigner.getAccountId().getAccountID().getEd25519()); signerKey.setDiscriminant(SignerKeyType.SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD); signerKey.setEd25519SignedPayload(payloadSigner); diff --git a/src/main/java/org/stellar/sdk/StrKey.java b/src/main/java/org/stellar/sdk/StrKey.java index 7a0b787b9..298909f4d 100644 --- a/src/main/java/org/stellar/sdk/StrKey.java +++ b/src/main/java/org/stellar/sdk/StrKey.java @@ -9,15 +9,15 @@ import org.stellar.sdk.xdr.MuxedAccount; import org.stellar.sdk.xdr.PublicKey; import org.stellar.sdk.xdr.PublicKeyType; +import org.stellar.sdk.xdr.SignerKey; import org.stellar.sdk.xdr.Uint256; import org.stellar.sdk.xdr.Uint64; import org.stellar.sdk.xdr.XdrDataInputStream; +import org.stellar.sdk.xdr.XdrDataOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.CharArrayWriter; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; @@ -69,14 +69,17 @@ public static String encodeSignedPayload(SignedPayloadSigner signedPayloadSigner if (signedPayloadSigner.getPayload().length > SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH) { throw new FormatException("invalid payload length, must be less than " + SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH); } + if (!signedPayloadSigner.getAccountId().getAccountID().getDiscriminant().equals(PublicKeyType.PUBLIC_KEY_TYPE_ED25519)) { + throw new FormatException("invalid payload signer, only ED25519 public key accounts are supported currently"); + } try { + SignerKey.SignerKeyEd25519SignedPayload xdrPayloadSigner = new SignerKey.SignerKeyEd25519SignedPayload(); + xdrPayloadSigner.setPayload(signedPayloadSigner.getPayload()); + xdrPayloadSigner.setEd25519(signedPayloadSigner.getAccountId().getAccountID().getEd25519()); + ByteArrayOutputStream record = new ByteArrayOutputStream(); - DataOutputStream dataStream = new DataOutputStream(record); - dataStream.write(signedPayloadSigner.getDecodedAccountId()); - dataStream.writeInt(signedPayloadSigner.getPayload().length); - dataStream.write(signedPayloadSigner.getPayload()); - int padding = signedPayloadSigner.getPayload().length % 4 > 0 ? 4 - signedPayloadSigner.getPayload().length % 4 : 0; - dataStream.write(new byte[padding]); + xdrPayloadSigner.encode(new XdrDataOutputStream(record)); + char[] encoded = encodeCheck(VersionByte.SIGNED_PAYLOAD, record.toByteArray()); return String.valueOf(encoded); } catch (Exception ex) { @@ -192,14 +195,12 @@ public static byte[] decodeStellarSecretSeed(char[] data) { public static SignedPayloadSigner decodeSignedPayload(char[] data) { try { byte[] signedPayloadRaw = decodeCheck(VersionByte.SIGNED_PAYLOAD, data); - DataInputStream dataStream = new DataInputStream(new ByteArrayInputStream(signedPayloadRaw)); - byte[] binaryAccountId = new byte[32]; - dataStream.read(binaryAccountId); - int payloadLength = dataStream.readInt(); - byte[] payload = new byte[payloadLength]; - dataStream.read(payload); - - return new SignedPayloadSigner(encodeStellarAccountId(binaryAccountId), payload ); + + SignerKey.SignerKeyEd25519SignedPayload xdrPayloadSigner = SignerKey.SignerKeyEd25519SignedPayload.decode( + new XdrDataInputStream(new ByteArrayInputStream(signedPayloadRaw)) + ); + + return new SignedPayloadSigner( xdrPayloadSigner.getEd25519().getUint256(), xdrPayloadSigner.getPayload()); } catch (Exception ex) { throw new FormatException(ex.getMessage()); } diff --git a/src/test/java/org/stellar/sdk/SetOptionsOperationTest.java b/src/test/java/org/stellar/sdk/SetOptionsOperationTest.java index ece16d1aa..23c072316 100644 --- a/src/test/java/org/stellar/sdk/SetOptionsOperationTest.java +++ b/src/test/java/org/stellar/sdk/SetOptionsOperationTest.java @@ -16,7 +16,7 @@ public void testPaylodSignerKey() { String payloadSignerStrKey = "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"; byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".toUpperCase()); - SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner(payloadSignerStrKey, payload); + SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner(StrKey.decodeStellarAccountId(payloadSignerStrKey), payload); SignerKey signerKey = Signer.signedPayload(signedPayloadSigner); builder.setSigner(signerKey, 1); @@ -29,7 +29,7 @@ public void testPaylodSignerKey() { // verify round trip between xdr and pojo assertEquals(source.getAccountId(), parsedOperation.getSourceAccount()); - assertArrayEquals(signedPayloadSigner.getDecodedAccountId(), parsedOperation.getSigner().getEd25519SignedPayload().getEd25519().getUint256()); + assertEquals(signedPayloadSigner.getAccountId().getAccountID().getEd25519(), parsedOperation.getSigner().getEd25519SignedPayload().getEd25519()); assertArrayEquals(signedPayloadSigner.getPayload(), parsedOperation.getSigner().getEd25519SignedPayload().getPayload()); // verify serialized xdr emitted with signed payload diff --git a/src/test/java/org/stellar/sdk/SignedPayloadSignerTest.java b/src/test/java/org/stellar/sdk/SignedPayloadSignerTest.java new file mode 100644 index 000000000..9869b3298 --- /dev/null +++ b/src/test/java/org/stellar/sdk/SignedPayloadSignerTest.java @@ -0,0 +1,18 @@ +package org.stellar.sdk; + +import org.junit.Test; +import org.stellar.sdk.xdr.AccountID; + +import static org.junit.Assert.fail; + +public class SignedPayloadSignerTest { + @Test + public void itFailsWhenAccoutIDIsNull() { + try { + new SignedPayloadSigner( + (AccountID)null, + new byte[]{}); + fail("should not create when accountid is null"); + } catch (NullPointerException ignored){} + } +} diff --git a/src/test/java/org/stellar/sdk/SignerTest.java b/src/test/java/org/stellar/sdk/SignerTest.java index 8b6a81cec..e0d755091 100644 --- a/src/test/java/org/stellar/sdk/SignerTest.java +++ b/src/test/java/org/stellar/sdk/SignerTest.java @@ -5,6 +5,7 @@ import org.stellar.sdk.xdr.SignerKey; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class SignerTest { @@ -14,27 +15,18 @@ public void itCreatesSignedPayloadSigner() { String accountStrKey = "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"; byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".toUpperCase()); - SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner(accountStrKey, payload); + SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner(StrKey.decodeStellarAccountId(accountStrKey), payload); SignerKey signerKey = Signer.signedPayload(signedPayloadSigner); assertArrayEquals(signerKey.getEd25519SignedPayload().getPayload(), payload); - assertArrayEquals(signerKey.getEd25519SignedPayload().getEd25519().getUint256(),signedPayloadSigner.getDecodedAccountId()); + assertEquals(signerKey.getEd25519SignedPayload().getEd25519(),signedPayloadSigner.getAccountId().getAccountID().getEd25519()); } @Test public void itFailsWhenInvalidParameters() { - String accountStrKey = "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTnotgood"; - byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".toUpperCase()); - SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner(accountStrKey, payload); - - try { - SignerKey signerKey = Signer.signedPayload(signedPayloadSigner); - fail("should not create a payload signer if invalid account"); - } catch (IllegalArgumentException ignored) {} - - accountStrKey = "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"; - payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2001".toUpperCase()); - signedPayloadSigner = new SignedPayloadSigner(accountStrKey, payload); + String accountStrKey = "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"; + byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2001".toUpperCase()); + SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner(StrKey.decodeStellarAccountId(accountStrKey), payload); try { SignerKey signerKey = Signer.signedPayload(signedPayloadSigner); fail("should not create a payload signer if payload > max length"); diff --git a/src/test/java/org/stellar/sdk/StrKeyTest.java b/src/test/java/org/stellar/sdk/StrKeyTest.java index e0dc89a0e..c6a43e352 100644 --- a/src/test/java/org/stellar/sdk/StrKeyTest.java +++ b/src/test/java/org/stellar/sdk/StrKeyTest.java @@ -138,13 +138,13 @@ public void testRoundTripHashXFromBytes() { public void testValidSignedPayloadEncode() { // Valid signed payload with an ed25519 public key and a 32-byte payload. byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".toUpperCase()); - SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner("GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ", payload); + SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner(StrKey.decodeStellarAccountId("GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"), payload); String encoded = StrKey.encodeSignedPayload(signedPayloadSigner); assertEquals(encoded, "PA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAQACAQDAQCQMBYIBEFAWDANBYHRAEISCMKBKFQXDAMRUGY4DUPB6IBZGM"); // Valid signed payload with an ed25519 public key and a 29-byte payload. payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d".toUpperCase()); - signedPayloadSigner = new SignedPayloadSigner("GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ", payload); + signedPayloadSigner = new SignedPayloadSigner(StrKey.decodeStellarAccountId("GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"), payload); encoded = StrKey.encodeSignedPayload(signedPayloadSigner); assertEquals(encoded, "PA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAOQCAQDAQCQMBYIBEFAWDANBYHRAEISCMKBKFQXDAMRUGY4DUAAAAFGBU"); } @@ -152,18 +152,11 @@ public void testValidSignedPayloadEncode() { @Test public void testInvalidSignedPayloadEncode() { byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2001".toUpperCase()); - SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner("GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ", payload); + SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner(StrKey.decodeStellarAccountId("GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"), payload); try { StrKey.encodeSignedPayload(signedPayloadSigner); fail("should not encode signed payloads > 64"); } catch (FormatException ignored){} - - payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".toUpperCase()); - signedPayloadSigner = new SignedPayloadSigner("GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KV_notgood", payload); - try { - StrKey.encodeSignedPayload(signedPayloadSigner); - fail("should not encode when accountid is not valid strkey"); - } catch (FormatException ignored){} } @Test From 163b582c5d7c1ba9cb11ff69ca626c517af7c1be Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Tue, 22 Mar 2022 11:39:08 -0700 Subject: [PATCH 04/29] #410: maintain backwards compliant Predicate --- src/main/java/org/stellar/sdk/Predicate.java | 21 +++++++++--- .../org/stellar/sdk/SignedPayloadSigner.java | 2 +- .../ClaimableBalancePageDeserializerTest.java | 2 +- .../sdk/responses/EffectDeserializerTest.java | 34 ++++++++++++++++--- .../responses/OperationDeserializerTest.java | 32 +++++++++++++++-- 5 files changed, 77 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/stellar/sdk/Predicate.java b/src/main/java/org/stellar/sdk/Predicate.java index d2cea4591..fe908b4d0 100644 --- a/src/main/java/org/stellar/sdk/Predicate.java +++ b/src/main/java/org/stellar/sdk/Predicate.java @@ -180,6 +180,9 @@ public ClaimPredicate toXdr() { } } + /** + * Represents a predicate based on a maximum date and time. + */ public static class AbsBefore extends Predicate { private final TimePoint timePoint; @@ -187,8 +190,11 @@ public AbsBefore(TimePoint timePoint) { this.timePoint = timePoint; } - public TimePoint getTimestampSeconds() { - return timePoint; + public AbsBefore(long epochSeconds) { + this(new TimePoint(new Uint64(epochSeconds))); + } + public long getTimestampSeconds() { + return timePoint.getTimePoint().getUint64(); } public Instant getDate() { @@ -217,6 +223,9 @@ public ClaimPredicate toXdr() { } } + /** + * Represents predicate based on maximum length of time + */ public static class RelBefore extends Predicate { private final Duration duration; @@ -224,8 +233,12 @@ public RelBefore(Duration secondsSinceClose) { this.duration = secondsSinceClose; } - public Duration getSecondsSinceClose() { - return duration; + public RelBefore(long secondsSinceClose) { + this(new Duration(new Int64(secondsSinceClose))); + } + + public long getSecondsSinceClose() { + return duration.getDuration().getInt64(); } @Override diff --git a/src/main/java/org/stellar/sdk/SignedPayloadSigner.java b/src/main/java/org/stellar/sdk/SignedPayloadSigner.java index 1564fe9e6..ea23dd173 100644 --- a/src/main/java/org/stellar/sdk/SignedPayloadSigner.java +++ b/src/main/java/org/stellar/sdk/SignedPayloadSigner.java @@ -27,7 +27,7 @@ public SignedPayloadSigner(AccountID accountId, byte[] payload) { } /** - * construcxtor + * constructor * * @param ed25519PublicKey raw bytes of a ED25519 public key * @param payload the raw payload for a signed payload diff --git a/src/test/java/org/stellar/sdk/responses/ClaimableBalancePageDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/ClaimableBalancePageDeserializerTest.java index 01bd459ed..b7b054ae8 100644 --- a/src/test/java/org/stellar/sdk/responses/ClaimableBalancePageDeserializerTest.java +++ b/src/test/java/org/stellar/sdk/responses/ClaimableBalancePageDeserializerTest.java @@ -34,7 +34,7 @@ public void testDeserialize() { Predicate.AbsBefore absBefore = (Predicate.AbsBefore)or.getInner().get(0); Predicate.RelBefore relBefore = (Predicate.RelBefore)or.getInner().get(1); assertEquals(absBefore.getDate().toString(), "2020-09-28T17:57:04Z"); - assertEquals(relBefore.getSecondsSinceClose().getDuration().getInt64().longValue(), 12L); + assertEquals(relBefore.getSecondsSinceClose(), 12L); assertEquals(claimableBalancePage.getRecords().get(1).getId(), "00000000ae76f49e8513d0922b6bcbc8a3f5c4c0a5161871f27924e08724646acab56cd3"); assertEquals(claimableBalancePage.getRecords().get(1).getAsset(), Asset.create("native")); diff --git a/src/test/java/org/stellar/sdk/responses/EffectDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/EffectDeserializerTest.java index 28db4fceb..c9bc17c76 100644 --- a/src/test/java/org/stellar/sdk/responses/EffectDeserializerTest.java +++ b/src/test/java/org/stellar/sdk/responses/EffectDeserializerTest.java @@ -1,13 +1,37 @@ package org.stellar.sdk.responses; import junit.framework.TestCase; - import org.junit.Test; -import org.stellar.sdk.Asset; import org.stellar.sdk.AssetAmount; import org.stellar.sdk.AssetTypeNative; import org.stellar.sdk.Predicate; -import org.stellar.sdk.responses.effects.*; +import org.stellar.sdk.responses.effects.AccountCreatedEffectResponse; +import org.stellar.sdk.responses.effects.AccountCreditedEffectResponse; +import org.stellar.sdk.responses.effects.AccountDebitedEffectResponse; +import org.stellar.sdk.responses.effects.AccountFlagsUpdatedEffectResponse; +import org.stellar.sdk.responses.effects.AccountHomeDomainUpdatedEffectResponse; +import org.stellar.sdk.responses.effects.AccountInflationDestinationUpdatedEffectResponse; +import org.stellar.sdk.responses.effects.AccountRemovedEffectResponse; +import org.stellar.sdk.responses.effects.AccountThresholdsUpdatedEffectResponse; +import org.stellar.sdk.responses.effects.ClaimableBalanceClaimantCreatedEffectResponse; +import org.stellar.sdk.responses.effects.ClaimableBalanceClawedBackEffectResponse; +import org.stellar.sdk.responses.effects.DataCreatedEffectResponse; +import org.stellar.sdk.responses.effects.DataRemovedEffectResponse; +import org.stellar.sdk.responses.effects.DataUpdatedEffectResponse; +import org.stellar.sdk.responses.effects.EffectResponse; +import org.stellar.sdk.responses.effects.LiquidityPoolTradeEffectResponse; +import org.stellar.sdk.responses.effects.SequenceBumpedEffectResponse; +import org.stellar.sdk.responses.effects.SignerCreatedEffectResponse; +import org.stellar.sdk.responses.effects.SignerRemovedEffectResponse; +import org.stellar.sdk.responses.effects.SignerUpdatedEffectResponse; +import org.stellar.sdk.responses.effects.TradeEffectResponse; +import org.stellar.sdk.responses.effects.TrustlineAuthorizedEffectResponse; +import org.stellar.sdk.responses.effects.TrustlineAuthorizedToMaintainLiabilitiesEffectResponse; +import org.stellar.sdk.responses.effects.TrustlineCreatedEffectResponse; +import org.stellar.sdk.responses.effects.TrustlineDeauthorizedEffectResponse; +import org.stellar.sdk.responses.effects.TrustlineFlagsUpdatedEffectResponse; +import org.stellar.sdk.responses.effects.TrustlineRemovedEffectResponse; +import org.stellar.sdk.responses.effects.TrustlineUpdatedEffectResponse; import org.stellar.sdk.xdr.LiquidityPoolType; import java.util.Arrays; @@ -284,7 +308,7 @@ public void testDeserializeClaimableBalanceClaimantCreatedEffect() { assertEquals(effect.getBalanceId(), "0000000071d3336fa6b6cf81fcbeda85a503ccfabc786ab1066594716f3f9551ea4b89ca"); assertEquals(effect.getType(), "claimable_balance_claimant_created"); assertSame(effect.getPredicate().getClass(), Predicate.AbsBefore.class); - assertEquals(((Predicate.AbsBefore)effect.getPredicate()).getTimestampSeconds().getTimePoint().getUint64().longValue(), 1234567890982222222L); + assertEquals(((Predicate.AbsBefore)effect.getPredicate()).getTimestampSeconds(), 1234567890982222222L); } @Test @@ -322,7 +346,7 @@ public void testBackwardsCompatAbsBeforeEpoch() { assertEquals(effect.getBalanceId(), "0000000071d3336fa6b6cf81fcbeda85a503ccfabc786ab1066594716f3f9551ea4b89ca"); assertEquals(effect.getType(), "claimable_balance_claimant_created"); assertSame(effect.getPredicate().getClass(), Predicate.AbsBefore.class); - assertEquals(((Predicate.AbsBefore)effect.getPredicate()).getTimestampSeconds().getTimePoint().getUint64().longValue(), 1637479450L); + assertEquals(((Predicate.AbsBefore)effect.getPredicate()).getTimestampSeconds(), 1637479450L); } @Test diff --git a/src/test/java/org/stellar/sdk/responses/OperationDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/OperationDeserializerTest.java index 25a8bece7..9ee801760 100644 --- a/src/test/java/org/stellar/sdk/responses/OperationDeserializerTest.java +++ b/src/test/java/org/stellar/sdk/responses/OperationDeserializerTest.java @@ -4,8 +4,34 @@ import com.google.common.collect.Lists; import junit.framework.TestCase; import org.junit.Test; -import org.stellar.sdk.*; -import org.stellar.sdk.responses.operations.*; +import org.stellar.sdk.Asset; +import org.stellar.sdk.AssetAmount; +import org.stellar.sdk.AssetTypeNative; +import org.stellar.sdk.AssetTypePoolShare; +import org.stellar.sdk.FormatException; +import org.stellar.sdk.Predicate; +import org.stellar.sdk.Price; +import org.stellar.sdk.responses.operations.AccountMergeOperationResponse; +import org.stellar.sdk.responses.operations.AllowTrustOperationResponse; +import org.stellar.sdk.responses.operations.BumpSequenceOperationResponse; +import org.stellar.sdk.responses.operations.ChangeTrustOperationResponse; +import org.stellar.sdk.responses.operations.ClaimClaimableBalanceOperationResponse; +import org.stellar.sdk.responses.operations.ClawbackClaimableBalanceOperationResponse; +import org.stellar.sdk.responses.operations.ClawbackOperationResponse; +import org.stellar.sdk.responses.operations.CreateAccountOperationResponse; +import org.stellar.sdk.responses.operations.CreateClaimableBalanceOperationResponse; +import org.stellar.sdk.responses.operations.EndSponsoringFutureReservesOperationResponse; +import org.stellar.sdk.responses.operations.InflationOperationResponse; +import org.stellar.sdk.responses.operations.LiquidityPoolDepositOperationResponse; +import org.stellar.sdk.responses.operations.LiquidityPoolWithdrawOperationResponse; +import org.stellar.sdk.responses.operations.ManageBuyOfferOperationResponse; +import org.stellar.sdk.responses.operations.ManageDataOperationResponse; +import org.stellar.sdk.responses.operations.OperationResponse; +import org.stellar.sdk.responses.operations.PathPaymentStrictReceiveOperationResponse; +import org.stellar.sdk.responses.operations.PathPaymentStrictSendOperationResponse; +import org.stellar.sdk.responses.operations.PaymentOperationResponse; +import org.stellar.sdk.responses.operations.SetOptionsOperationResponse; +import org.stellar.sdk.responses.operations.SetTrustLineFlagsOperationResponse; import java.math.BigInteger; import java.util.Arrays; @@ -1567,7 +1593,7 @@ public void testDeserializeCreateClaimableBalanceOperation() { assertEquals(operation.getClaimants().size(), 2); assertEquals(operation.getClaimants().get(0).getDestination(), "GBQECQVAS2FJ7DLCUXDASZAJQLWPXNTCR2DGBPKQDO3QS66TJXLRHFIK"); assertSame(operation.getClaimants().get(0).getPredicate().getClass(), Predicate.AbsBefore.class); - assertEquals(((Predicate.AbsBefore)operation.getClaimants().get(0).getPredicate()).getTimestampSeconds().getTimePoint().getUint64().longValue(), 1234567890982222222L); + assertEquals(((Predicate.AbsBefore)operation.getClaimants().get(0).getPredicate()).getTimestampSeconds(), 1234567890982222222L); assertEquals(operation.getClaimants().get(1).getDestination(), "GAKFBRS24U3PEN6XDMEX4JEV5NGBARS2ZFF5O4CF3JL464SQSD4MDCPF"); assertSame(operation.getClaimants().get(1).getPredicate().getClass(), Predicate.Unconditional.class); assertEquals(operation.getType(), "create_claimable_balance"); From c239785aa2297b374b7bd58cbf64ac2ab024dc5d Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Tue, 22 Mar 2022 21:20:38 -0700 Subject: [PATCH 05/29] #411: upgrade gradle to 7.4 --- build.gradle | 100 +++----- gradle/wrapper/gradle-wrapper.jar | Bin 56177 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 282 ++++++++++++++--------- gradlew.bat | 43 ++-- 5 files changed, 227 insertions(+), 200 deletions(-) diff --git a/build.gradle b/build.gradle index 6b46d6e8b..097f6426c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,37 +1,28 @@ buildscript { - ext.okhttpclientVersion="3.11.0" - repositories { - jcenter() - } - dependencies { - classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0' - classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4' - } + ext.okhttpclientVersion= '3.11.0' } -apply plugin: "com.github.johnrengelman.shadow" // provides shading -apply plugin: 'java' -apply plugin: 'maven' // needed to add the install task for jitpack.io -apply plugin: 'com.github.ben-manes.versions' // gradle dependencyUpdates -Drevision=release -apply plugin: 'project-report' // gradle htmlDependencyReport +plugins { + id "io.freefair.lombok" version "6.4.1" + id "com.github.johnrengelman.shadow" version "7.1.2" + id "java" + id "com.github.ben-manes.versions" version "0.42.0" + id "project-report" + id "maven-publish" +} sourceCompatibility = 1.6 -version = '0.31.0' +version = '0.32.0' group = 'stellar' jar { manifest { - attributes 'Implementation-Title': 'stellar-sdk', - 'Implementation-Version': version + attributes ( + "Implementation-Title" : "stellar-sdk", + "Implementation-Version" : project.getVersion() + ) } archiveName 'stellar-sdk.jar' - duplicatesStrategy DuplicatesStrategy.EXCLUDE - from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } -} - -artifacts { - // make sure the non shaded jar is not - archives shadowJar } repositories { @@ -39,52 +30,21 @@ repositories { } dependencies { - compile "com.squareup.okhttp3:okhttp:${okhttpclientVersion}" - compile "com.squareup.okhttp3:okhttp-sse:${okhttpclientVersion}" - compile 'com.moandjiezana.toml:toml4j:0.7.2' + implementation "com.squareup.okhttp3:okhttp:${okhttpclientVersion}" + implementation "com.squareup.okhttp3:okhttp-sse:${okhttpclientVersion}" + implementation 'com.moandjiezana.toml:toml4j:0.7.2' // use the android version because we don't want java 8 stuff - compile 'com.google.guava:guava:26.0-android' - compile 'com.google.code.gson:gson:2.8.5' - compile 'commons-io:commons-io:2.6' - compile 'net.i2p.crypto:eddsa:0.3.0' - compile 'org.threeten:threetenbp:1.4.4' - - testCompile 'org.mockito:mockito-core:2.21.0' - testCompile "com.squareup.okhttp3:mockwebserver:${okhttpclientVersion}" - testCompile 'junit:junit:4.12' - testCompile 'javax.xml.bind:jaxb-api:2.3.0' -} - -project.configurations.compile -jar.enabled = false - -// make sure the install task creates the shadowjar; this is called by jitpack.ios -install.dependsOn(shadowJar); - -// we need this so we can get to the installer and deployer below so we can remove the dependencies -uploadArchives { - repositories { - mavenDeployer { - } - } -} - -def installer = install.repositories.mavenInstaller -def deployer = uploadArchives.repositories.mavenDeployer -[installer, deployer]*.pom*.whenConfigured {pom -> - // force pom dependencies to be empty because we are shading everything - pom.dependencies = [] -} - -shadowJar { - archiveName 'stellar-sdk.jar' - relocate 'com.','shadow.com.' - relocate 'net.','shadow.net.' - relocate 'javax.annotation', 'shadow.javax.annotation' - relocate 'org.apache','shadow.org.apache' - relocate 'org.jvnet','shadow.org.jvnet' - relocate 'org.codehaus','shadow.org.codehaus' - relocate 'org.checkerframework','shadow.org.checkerframework' - relocate 'okhttp3','shadow.okhttp3' - relocate 'okio','shadow.okio' + implementation 'com.google.guava:guava:26.0-android' + implementation 'com.google.code.gson:gson:2.8.5' + implementation 'commons-io:commons-io:2.6' + implementation 'net.i2p.crypto:eddsa:0.3.0' + implementation 'org.threeten:threetenbp:1.4.4' + + testImplementation 'org.mockito:mockito-core:2.21.0' + testImplementation "com.squareup.okhttp3:mockwebserver:${okhttpclientVersion}" + testImplementation 'junit:junit:4.12' + testImplementation 'javax.xml.bind:jaxb-api:2.3.0' +} +tasks.named('test') { + useJUnitPlatform() } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 29953ea141f55e3b8fc691d31b5ca8816d89fa87..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch delta 51682 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 H*I)k+3{@?X delta 48268 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 SuAZPD0}yG#BS;tw?Ee8_(p5YF diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e0b3fb8d7..00e33edef 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index cccdd3d51..1b6c78733 100755 --- a/gradlew +++ b/gradlew @@ -1,78 +1,129 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -89,84 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index e95643d6a..ac1b06f93 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell From 1717767a293a462658c29f5f006d9511523ef3d3 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Wed, 23 Mar 2022 10:42:57 -0700 Subject: [PATCH 06/29] #410: renamed parameter to 'signer' per feedback on pr --- .../org/stellar/sdk/SignedPayloadSigner.java | 18 +++++++++--------- src/main/java/org/stellar/sdk/Signer.java | 4 ++-- src/main/java/org/stellar/sdk/StrKey.java | 4 ++-- .../stellar/sdk/SetOptionsOperationTest.java | 2 +- src/test/java/org/stellar/sdk/SignerTest.java | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/stellar/sdk/SignedPayloadSigner.java b/src/main/java/org/stellar/sdk/SignedPayloadSigner.java index ea23dd173..a629f1544 100644 --- a/src/main/java/org/stellar/sdk/SignedPayloadSigner.java +++ b/src/main/java/org/stellar/sdk/SignedPayloadSigner.java @@ -12,39 +12,39 @@ * Data model for the signed payload signer */ public class SignedPayloadSigner { - private AccountID accountId; + private AccountID signerAccountId; private byte[] payload; /** * constructor * - * @param accountId - the xdr AccountID + * @param signerAccountId - the xdr AccountID * @param payload - the raw payload for a signed payload */ - public SignedPayloadSigner(AccountID accountId, byte[] payload) { - this.accountId = checkNotNull(accountId); + public SignedPayloadSigner(AccountID signerAccountId, byte[] payload) { + this.signerAccountId = checkNotNull(signerAccountId); this.payload = checkNotNull(payload); } /** * constructor * - * @param ed25519PublicKey raw bytes of a ED25519 public key + * @param signerED25519PublicKey raw bytes of a ED25519 public key for the signer account * @param payload the raw payload for a signed payload */ - public SignedPayloadSigner(byte[] ed25519PublicKey, byte[] payload ) { + public SignedPayloadSigner(byte[] signerED25519PublicKey, byte[] payload ) { this(new AccountID( new PublicKey.Builder() .discriminant(PublicKeyType.PUBLIC_KEY_TYPE_ED25519) - .ed25519(new Uint256(ed25519PublicKey)).build()), payload); + .ed25519(new Uint256(signerED25519PublicKey)).build()), payload); } /** * get the account that represents the signed payload signer * @return stellar account */ - public AccountID getAccountId() { - return accountId; + public AccountID getSignerAccountId() { + return signerAccountId; } /** diff --git a/src/main/java/org/stellar/sdk/Signer.java b/src/main/java/org/stellar/sdk/Signer.java index 50b1501e7..3966730bb 100644 --- a/src/main/java/org/stellar/sdk/Signer.java +++ b/src/main/java/org/stellar/sdk/Signer.java @@ -82,13 +82,13 @@ public static SignerKey preAuthTx(byte[] hash) { * @return org.stellar.sdk.xdr.SignerKey */ public static SignerKey signedPayload(SignedPayloadSigner signedPayloadSigner) { - checkNotNull(signedPayloadSigner.getAccountId(), "accountId cannot be null"); + checkNotNull(signedPayloadSigner.getSignerAccountId(), "accountId cannot be null"); checkArgument(signedPayloadSigner.getPayload().length <= SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH ); SignerKey signerKey = new SignerKey(); SignerKey.SignerKeyEd25519SignedPayload payloadSigner = new SignerKey.SignerKeyEd25519SignedPayload(); payloadSigner.setPayload(signedPayloadSigner.getPayload()); - payloadSigner.setEd25519(signedPayloadSigner.getAccountId().getAccountID().getEd25519()); + payloadSigner.setEd25519(signedPayloadSigner.getSignerAccountId().getAccountID().getEd25519()); signerKey.setDiscriminant(SignerKeyType.SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD); signerKey.setEd25519SignedPayload(payloadSigner); diff --git a/src/main/java/org/stellar/sdk/StrKey.java b/src/main/java/org/stellar/sdk/StrKey.java index 298909f4d..688556385 100644 --- a/src/main/java/org/stellar/sdk/StrKey.java +++ b/src/main/java/org/stellar/sdk/StrKey.java @@ -69,13 +69,13 @@ public static String encodeSignedPayload(SignedPayloadSigner signedPayloadSigner if (signedPayloadSigner.getPayload().length > SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH) { throw new FormatException("invalid payload length, must be less than " + SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH); } - if (!signedPayloadSigner.getAccountId().getAccountID().getDiscriminant().equals(PublicKeyType.PUBLIC_KEY_TYPE_ED25519)) { + if (!signedPayloadSigner.getSignerAccountId().getAccountID().getDiscriminant().equals(PublicKeyType.PUBLIC_KEY_TYPE_ED25519)) { throw new FormatException("invalid payload signer, only ED25519 public key accounts are supported currently"); } try { SignerKey.SignerKeyEd25519SignedPayload xdrPayloadSigner = new SignerKey.SignerKeyEd25519SignedPayload(); xdrPayloadSigner.setPayload(signedPayloadSigner.getPayload()); - xdrPayloadSigner.setEd25519(signedPayloadSigner.getAccountId().getAccountID().getEd25519()); + xdrPayloadSigner.setEd25519(signedPayloadSigner.getSignerAccountId().getAccountID().getEd25519()); ByteArrayOutputStream record = new ByteArrayOutputStream(); xdrPayloadSigner.encode(new XdrDataOutputStream(record)); diff --git a/src/test/java/org/stellar/sdk/SetOptionsOperationTest.java b/src/test/java/org/stellar/sdk/SetOptionsOperationTest.java index 23c072316..13d0a20b7 100644 --- a/src/test/java/org/stellar/sdk/SetOptionsOperationTest.java +++ b/src/test/java/org/stellar/sdk/SetOptionsOperationTest.java @@ -29,7 +29,7 @@ public void testPaylodSignerKey() { // verify round trip between xdr and pojo assertEquals(source.getAccountId(), parsedOperation.getSourceAccount()); - assertEquals(signedPayloadSigner.getAccountId().getAccountID().getEd25519(), parsedOperation.getSigner().getEd25519SignedPayload().getEd25519()); + assertEquals(signedPayloadSigner.getSignerAccountId().getAccountID().getEd25519(), parsedOperation.getSigner().getEd25519SignedPayload().getEd25519()); assertArrayEquals(signedPayloadSigner.getPayload(), parsedOperation.getSigner().getEd25519SignedPayload().getPayload()); // verify serialized xdr emitted with signed payload diff --git a/src/test/java/org/stellar/sdk/SignerTest.java b/src/test/java/org/stellar/sdk/SignerTest.java index e0d755091..0fca163dd 100644 --- a/src/test/java/org/stellar/sdk/SignerTest.java +++ b/src/test/java/org/stellar/sdk/SignerTest.java @@ -19,7 +19,7 @@ public void itCreatesSignedPayloadSigner() { SignerKey signerKey = Signer.signedPayload(signedPayloadSigner); assertArrayEquals(signerKey.getEd25519SignedPayload().getPayload(), payload); - assertEquals(signerKey.getEd25519SignedPayload().getEd25519(),signedPayloadSigner.getAccountId().getAccountID().getEd25519()); + assertEquals(signerKey.getEd25519SignedPayload().getEd25519(),signedPayloadSigner.getSignerAccountId().getAccountID().getEd25519()); } @Test From 49b477d5c24682dbe3dfa52fed0f9882a3c38d36 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Wed, 23 Mar 2022 11:03:04 -0700 Subject: [PATCH 07/29] #410: regenerated all xdr from latest .x source on p19 branch --- .../sdk/xdr/AccountEntryExtensionV2.java | 4 +- .../sdk/xdr/AccountEntryExtensionV3.java | 4 +- src/main/java/org/stellar/sdk/xdr/Asset.java | 4 +- .../org/stellar/sdk/xdr/ClaimPredicate.java | 4 +- .../stellar/sdk/xdr/ClaimableBalanceID.java | 4 +- .../java/org/stellar/sdk/xdr/Duration.java | 4 +- .../org/stellar/sdk/xdr/ExtensionPoint.java | 4 +- .../org/stellar/sdk/xdr/HashIDPreimage.java | 4 +- .../org/stellar/sdk/xdr/LedgerBounds.java | 4 +- .../sdk/xdr/LedgerHeaderExtensionV1.java | 4 +- .../java/org/stellar/sdk/xdr/MessageType.java | 6 +- .../org/stellar/sdk/xdr/Preconditions.java | 4 +- .../org/stellar/sdk/xdr/PreconditionsV2.java | 4 +- .../java/org/stellar/sdk/xdr/SendMore.java | 67 +++++++++++++++++++ .../java/org/stellar/sdk/xdr/SignerKey.java | 4 +- .../org/stellar/sdk/xdr/StellarMessage.java | 26 ++++++- .../org/stellar/sdk/xdr/StellarValue.java | 4 +- .../java/org/stellar/sdk/xdr/Transaction.java | 4 +- .../org/stellar/sdk/xdr/TrustLineEntry.java | 4 +- xdr/Stellar-overlay.x | 11 ++- 20 files changed, 138 insertions(+), 36 deletions(-) create mode 100644 src/main/java/org/stellar/sdk/xdr/SendMore.java diff --git a/src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV2.java b/src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV2.java index 1911d96f5..54ae3dc9f 100644 --- a/src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV2.java +++ b/src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV2.java @@ -4,9 +4,9 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; + +import com.google.common.base.Objects; import java.util.Arrays; // === xdr source ============================================================ diff --git a/src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV3.java b/src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV3.java index a63b1dee5..44a0890d0 100644 --- a/src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV3.java +++ b/src/main/java/org/stellar/sdk/xdr/AccountEntryExtensionV3.java @@ -4,10 +4,10 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; +import com.google.common.base.Objects; + // === xdr source ============================================================ // struct AccountEntryExtensionV3 diff --git a/src/main/java/org/stellar/sdk/xdr/Asset.java b/src/main/java/org/stellar/sdk/xdr/Asset.java index 0e9ccc672..542c98471 100644 --- a/src/main/java/org/stellar/sdk/xdr/Asset.java +++ b/src/main/java/org/stellar/sdk/xdr/Asset.java @@ -4,10 +4,10 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; +import com.google.common.base.Objects; + // === xdr source ============================================================ // union Asset switch (AssetType type) diff --git a/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java b/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java index 404b6282d..a200c875a 100644 --- a/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java +++ b/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java @@ -4,9 +4,9 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; + +import com.google.common.base.Objects; import java.util.Arrays; // === xdr source ============================================================ diff --git a/src/main/java/org/stellar/sdk/xdr/ClaimableBalanceID.java b/src/main/java/org/stellar/sdk/xdr/ClaimableBalanceID.java index c75a39d1e..b3e261ef2 100644 --- a/src/main/java/org/stellar/sdk/xdr/ClaimableBalanceID.java +++ b/src/main/java/org/stellar/sdk/xdr/ClaimableBalanceID.java @@ -4,10 +4,10 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; +import com.google.common.base.Objects; + // === xdr source ============================================================ // union ClaimableBalanceID switch (ClaimableBalanceIDType type) diff --git a/src/main/java/org/stellar/sdk/xdr/Duration.java b/src/main/java/org/stellar/sdk/xdr/Duration.java index 56507997c..9a467a8c9 100644 --- a/src/main/java/org/stellar/sdk/xdr/Duration.java +++ b/src/main/java/org/stellar/sdk/xdr/Duration.java @@ -4,10 +4,10 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; +import com.google.common.base.Objects; + // === xdr source ============================================================ // typedef int64 Duration; diff --git a/src/main/java/org/stellar/sdk/xdr/ExtensionPoint.java b/src/main/java/org/stellar/sdk/xdr/ExtensionPoint.java index fb785dc66..fb017c313 100644 --- a/src/main/java/org/stellar/sdk/xdr/ExtensionPoint.java +++ b/src/main/java/org/stellar/sdk/xdr/ExtensionPoint.java @@ -4,10 +4,10 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; +import com.google.common.base.Objects; + // === xdr source ============================================================ // union ExtensionPoint switch (int v) { diff --git a/src/main/java/org/stellar/sdk/xdr/HashIDPreimage.java b/src/main/java/org/stellar/sdk/xdr/HashIDPreimage.java index 559d9a584..bb659ebe4 100644 --- a/src/main/java/org/stellar/sdk/xdr/HashIDPreimage.java +++ b/src/main/java/org/stellar/sdk/xdr/HashIDPreimage.java @@ -4,10 +4,10 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; +import com.google.common.base.Objects; + // === xdr source ============================================================ // union HashIDPreimage switch (EnvelopeType type) diff --git a/src/main/java/org/stellar/sdk/xdr/LedgerBounds.java b/src/main/java/org/stellar/sdk/xdr/LedgerBounds.java index c55bb91ad..3a71b51eb 100644 --- a/src/main/java/org/stellar/sdk/xdr/LedgerBounds.java +++ b/src/main/java/org/stellar/sdk/xdr/LedgerBounds.java @@ -4,10 +4,10 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; +import com.google.common.base.Objects; + // === xdr source ============================================================ // struct LedgerBounds diff --git a/src/main/java/org/stellar/sdk/xdr/LedgerHeaderExtensionV1.java b/src/main/java/org/stellar/sdk/xdr/LedgerHeaderExtensionV1.java index 6be2aaa41..33cd29320 100644 --- a/src/main/java/org/stellar/sdk/xdr/LedgerHeaderExtensionV1.java +++ b/src/main/java/org/stellar/sdk/xdr/LedgerHeaderExtensionV1.java @@ -4,10 +4,10 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; +import com.google.common.base.Objects; + // === xdr source ============================================================ // struct LedgerHeaderExtensionV1 diff --git a/src/main/java/org/stellar/sdk/xdr/MessageType.java b/src/main/java/org/stellar/sdk/xdr/MessageType.java index c0278ebed..dfd269193 100644 --- a/src/main/java/org/stellar/sdk/xdr/MessageType.java +++ b/src/main/java/org/stellar/sdk/xdr/MessageType.java @@ -33,7 +33,9 @@ // HELLO = 13, // // SURVEY_REQUEST = 14, -// SURVEY_RESPONSE = 15 +// SURVEY_RESPONSE = 15, +// +// SEND_MORE = 16 // }; // =========================================================================== @@ -53,6 +55,7 @@ public enum MessageType implements XdrElement { HELLO(13), SURVEY_REQUEST(14), SURVEY_RESPONSE(15), + SEND_MORE(16), ; private int mValue; @@ -82,6 +85,7 @@ public static MessageType decode(XdrDataInputStream stream) throws IOException { case 13: return HELLO; case 14: return SURVEY_REQUEST; case 15: return SURVEY_RESPONSE; + case 16: return SEND_MORE; default: throw new RuntimeException("Unknown enum value: " + value); } diff --git a/src/main/java/org/stellar/sdk/xdr/Preconditions.java b/src/main/java/org/stellar/sdk/xdr/Preconditions.java index 7eb39c140..88e91a80e 100644 --- a/src/main/java/org/stellar/sdk/xdr/Preconditions.java +++ b/src/main/java/org/stellar/sdk/xdr/Preconditions.java @@ -4,10 +4,10 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; +import com.google.common.base.Objects; + // === xdr source ============================================================ // union Preconditions switch (PreconditionType type) { diff --git a/src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java b/src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java index dc098c1d4..d68b48064 100644 --- a/src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java +++ b/src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java @@ -4,9 +4,9 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; + +import com.google.common.base.Objects; import java.util.Arrays; // === xdr source ============================================================ diff --git a/src/main/java/org/stellar/sdk/xdr/SendMore.java b/src/main/java/org/stellar/sdk/xdr/SendMore.java new file mode 100644 index 000000000..72b2cc9e7 --- /dev/null +++ b/src/main/java/org/stellar/sdk/xdr/SendMore.java @@ -0,0 +1,67 @@ +// Automatically generated by xdrgen +// DO NOT EDIT or your changes may be overwritten + +package org.stellar.sdk.xdr; + + +import java.io.IOException; + +import com.google.common.base.Objects; + +// === xdr source ============================================================ + +// struct SendMore +// { +// uint32 numMessages; +// }; + +// =========================================================================== +public class SendMore implements XdrElement { + public SendMore () {} + private Uint32 numMessages; + public Uint32 getNumMessages() { + return this.numMessages; + } + public void setNumMessages(Uint32 value) { + this.numMessages = value; + } + public static void encode(XdrDataOutputStream stream, SendMore encodedSendMore) throws IOException{ + Uint32.encode(stream, encodedSendMore.numMessages); + } + public void encode(XdrDataOutputStream stream) throws IOException { + encode(stream, this); + } + public static SendMore decode(XdrDataInputStream stream) throws IOException { + SendMore decodedSendMore = new SendMore(); + decodedSendMore.numMessages = Uint32.decode(stream); + return decodedSendMore; + } + @Override + public int hashCode() { + return Objects.hashCode(this.numMessages); + } + @Override + public boolean equals(Object object) { + if (!(object instanceof SendMore)) { + return false; + } + + SendMore other = (SendMore) object; + return Objects.equal(this.numMessages, other.numMessages); + } + + public static final class Builder { + private Uint32 numMessages; + + public Builder numMessages(Uint32 numMessages) { + this.numMessages = numMessages; + return this; + } + + public SendMore build() { + SendMore val = new SendMore(); + val.setNumMessages(numMessages); + return val; + } + } +} diff --git a/src/main/java/org/stellar/sdk/xdr/SignerKey.java b/src/main/java/org/stellar/sdk/xdr/SignerKey.java index 303c661ae..a6aea4d82 100644 --- a/src/main/java/org/stellar/sdk/xdr/SignerKey.java +++ b/src/main/java/org/stellar/sdk/xdr/SignerKey.java @@ -4,9 +4,9 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; + +import com.google.common.base.Objects; import java.util.Arrays; // === xdr source ============================================================ diff --git a/src/main/java/org/stellar/sdk/xdr/StellarMessage.java b/src/main/java/org/stellar/sdk/xdr/StellarMessage.java index 0048adb27..8ac2536ca 100644 --- a/src/main/java/org/stellar/sdk/xdr/StellarMessage.java +++ b/src/main/java/org/stellar/sdk/xdr/StellarMessage.java @@ -49,6 +49,8 @@ // SCPEnvelope envelope; // case GET_SCP_STATE: // uint32 getSCPLedgerSeq; // ledger seq requested ; if 0, requests the latest +// case SEND_MORE: +// SendMore sendMoreMessage; // }; // =========================================================================== @@ -159,6 +161,13 @@ public Uint32 getGetSCPLedgerSeq() { public void setGetSCPLedgerSeq(Uint32 value) { this.getSCPLedgerSeq = value; } + private SendMore sendMoreMessage; + public SendMore getSendMoreMessage() { + return this.sendMoreMessage; + } + public void setSendMoreMessage(SendMore value) { + this.sendMoreMessage = value; + } public static final class Builder { private MessageType discriminant; @@ -176,6 +185,7 @@ public static final class Builder { private SCPQuorumSet qSet; private SCPEnvelope envelope; private Uint32 getSCPLedgerSeq; + private SendMore sendMoreMessage; public Builder discriminant(MessageType discriminant) { this.discriminant = discriminant; @@ -252,6 +262,11 @@ public Builder getSCPLedgerSeq(Uint32 getSCPLedgerSeq) { return this; } + public Builder sendMoreMessage(SendMore sendMoreMessage) { + this.sendMoreMessage = sendMoreMessage; + return this; + } + public StellarMessage build() { StellarMessage val = new StellarMessage(); val.setDiscriminant(discriminant); @@ -269,6 +284,7 @@ public StellarMessage build() { val.setQSet(qSet); val.setEnvelope(envelope); val.setGetSCPLedgerSeq(getSCPLedgerSeq); + val.setSendMoreMessage(sendMoreMessage); return val; } } @@ -326,6 +342,9 @@ public static void encode(XdrDataOutputStream stream, StellarMessage encodedStel case GET_SCP_STATE: Uint32.encode(stream, encodedStellarMessage.getSCPLedgerSeq); break; + case SEND_MORE: + SendMore.encode(stream, encodedStellarMessage.sendMoreMessage); + break; } } public void encode(XdrDataOutputStream stream) throws IOException { @@ -384,12 +403,15 @@ public static StellarMessage decode(XdrDataInputStream stream) throws IOExceptio case GET_SCP_STATE: decodedStellarMessage.getSCPLedgerSeq = Uint32.decode(stream); break; + case SEND_MORE: + decodedStellarMessage.sendMoreMessage = SendMore.decode(stream); + break; } return decodedStellarMessage; } @Override public int hashCode() { - return Objects.hashCode(this.error, this.hello, this.auth, this.dontHave, Arrays.hashCode(this.peers), this.txSetHash, this.txSet, this.transaction, this.signedSurveyRequestMessage, this.signedSurveyResponseMessage, this.qSetHash, this.qSet, this.envelope, this.getSCPLedgerSeq, this.type); + return Objects.hashCode(this.error, this.hello, this.auth, this.dontHave, Arrays.hashCode(this.peers), this.txSetHash, this.txSet, this.transaction, this.signedSurveyRequestMessage, this.signedSurveyResponseMessage, this.qSetHash, this.qSet, this.envelope, this.getSCPLedgerSeq, this.sendMoreMessage, this.type); } @Override public boolean equals(Object object) { @@ -398,6 +420,6 @@ public boolean equals(Object object) { } StellarMessage other = (StellarMessage) object; - return Objects.equal(this.error, other.error) && Objects.equal(this.hello, other.hello) && Objects.equal(this.auth, other.auth) && Objects.equal(this.dontHave, other.dontHave) && Arrays.equals(this.peers, other.peers) && Objects.equal(this.txSetHash, other.txSetHash) && Objects.equal(this.txSet, other.txSet) && Objects.equal(this.transaction, other.transaction) && Objects.equal(this.signedSurveyRequestMessage, other.signedSurveyRequestMessage) && Objects.equal(this.signedSurveyResponseMessage, other.signedSurveyResponseMessage) && Objects.equal(this.qSetHash, other.qSetHash) && Objects.equal(this.qSet, other.qSet) && Objects.equal(this.envelope, other.envelope) && Objects.equal(this.getSCPLedgerSeq, other.getSCPLedgerSeq) && Objects.equal(this.type, other.type); + return Objects.equal(this.error, other.error) && Objects.equal(this.hello, other.hello) && Objects.equal(this.auth, other.auth) && Objects.equal(this.dontHave, other.dontHave) && Arrays.equals(this.peers, other.peers) && Objects.equal(this.txSetHash, other.txSetHash) && Objects.equal(this.txSet, other.txSet) && Objects.equal(this.transaction, other.transaction) && Objects.equal(this.signedSurveyRequestMessage, other.signedSurveyRequestMessage) && Objects.equal(this.signedSurveyResponseMessage, other.signedSurveyResponseMessage) && Objects.equal(this.qSetHash, other.qSetHash) && Objects.equal(this.qSet, other.qSet) && Objects.equal(this.envelope, other.envelope) && Objects.equal(this.getSCPLedgerSeq, other.getSCPLedgerSeq) && Objects.equal(this.sendMoreMessage, other.sendMoreMessage) && Objects.equal(this.type, other.type); } } diff --git a/src/main/java/org/stellar/sdk/xdr/StellarValue.java b/src/main/java/org/stellar/sdk/xdr/StellarValue.java index c841d5903..7fd7f1640 100644 --- a/src/main/java/org/stellar/sdk/xdr/StellarValue.java +++ b/src/main/java/org/stellar/sdk/xdr/StellarValue.java @@ -4,9 +4,9 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; + +import com.google.common.base.Objects; import java.util.Arrays; // === xdr source ============================================================ diff --git a/src/main/java/org/stellar/sdk/xdr/Transaction.java b/src/main/java/org/stellar/sdk/xdr/Transaction.java index 728be1b57..37b9a7994 100644 --- a/src/main/java/org/stellar/sdk/xdr/Transaction.java +++ b/src/main/java/org/stellar/sdk/xdr/Transaction.java @@ -4,9 +4,9 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; + +import com.google.common.base.Objects; import java.util.Arrays; // === xdr source ============================================================ diff --git a/src/main/java/org/stellar/sdk/xdr/TrustLineEntry.java b/src/main/java/org/stellar/sdk/xdr/TrustLineEntry.java index 1a0417c29..c22caec9a 100644 --- a/src/main/java/org/stellar/sdk/xdr/TrustLineEntry.java +++ b/src/main/java/org/stellar/sdk/xdr/TrustLineEntry.java @@ -4,10 +4,10 @@ package org.stellar.sdk.xdr; -import com.google.common.base.Objects; - import java.io.IOException; +import com.google.common.base.Objects; + // === xdr source ============================================================ // struct TrustLineEntry diff --git a/xdr/Stellar-overlay.x b/xdr/Stellar-overlay.x index a4ab6b077..9e3a083d3 100644 --- a/xdr/Stellar-overlay.x +++ b/xdr/Stellar-overlay.x @@ -22,6 +22,11 @@ struct Error string msg<100>; }; +struct SendMore +{ + uint32 numMessages; +}; + struct AuthCert { Curve25519Public pubkey; @@ -93,7 +98,9 @@ enum MessageType HELLO = 13, SURVEY_REQUEST = 14, - SURVEY_RESPONSE = 15 + SURVEY_RESPONSE = 15, + + SEND_MORE = 16 }; struct DontHave @@ -214,6 +221,8 @@ case SCP_MESSAGE: SCPEnvelope envelope; case GET_SCP_STATE: uint32 getSCPLedgerSeq; // ledger seq requested ; if 0, requests the latest +case SEND_MORE: + SendMore sendMoreMessage; }; union AuthenticatedMessage switch (uint32 v) From 5cae54b6e80cdc45c2af3628f33bd8ad5d715bd0 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Thu, 24 Mar 2022 14:07:43 -0700 Subject: [PATCH 08/29] #411: separated tx builder from tx, using tx preconditions.timebounds instead of tx.timebounds --- CHANGELOG.md | 11 +- build.gradle | 2 +- src/main/java/org/stellar/sdk/Account.java | 7 +- .../org/stellar/sdk/FeeBumpTransaction.java | 41 +- .../java/org/stellar/sdk/Sep10Challenge.java | 9 +- .../stellar/sdk/SequenceNumberStrategy.java | 13 + .../sdk/SequentialSequenceNumberStrategy.java | 13 + .../java/org/stellar/sdk/Transaction.java | 239 ++------- .../org/stellar/sdk/TransactionBuilder.java | 253 +++++++++ .../sdk/TransactionBuilderAccount.java | 7 +- .../sdk/responses/AccountResponse.java | 5 + .../stellar/sdk/ClaimableBalanceIdTest.java | 16 +- .../stellar/sdk/FeeBumpTransactionTest.java | 6 +- .../java/org/stellar/sdk/OperationTest.java | 31 +- .../org/stellar/sdk/Sep10ChallengeTest.java | 399 ++++++-------- src/test/java/org/stellar/sdk/ServerTest.java | 55 +- .../stellar/sdk/TransactionBuilderTest.java | 489 ++++++++++++++++++ .../java/org/stellar/sdk/TransactionTest.java | 434 ++-------------- 18 files changed, 1134 insertions(+), 896 deletions(-) create mode 100644 src/main/java/org/stellar/sdk/SequenceNumberStrategy.java create mode 100644 src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java create mode 100644 src/main/java/org/stellar/sdk/TransactionBuilder.java create mode 100644 src/test/java/org/stellar/sdk/TransactionBuilderTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b62f7446..e957af13a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,15 +6,14 @@ As this project is pre 1.0, breaking changes may happen for minor version bumps. * Update XDR definitions and auto-generated classes to support upcoming protocol 19 release ([#276](https://github.com/stellar/java-stellar-sdk/pull/276)). * Extend StrKey implementation to handle [CAP 40 Payload Signer](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0040.md) ([#276](https://github.com/stellar/java-stellar-sdk/pull/276)). +* Extended Transaction submission settings, additional new Preconditions can be added now, refer to [CAP 21 Transaction Preconditions](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0021.md). ### Breaking changes -* Predicate.AbsBefore - * `Predicate.AbsBefore(org.stellar.sdk.xdr.TimePoint)` - * `org.stellar.sdk.xdr.TimePoint Predicate.AbsBefore.getTimestampSeconds()` -* Predicate.RelBefore - * `Predicate.RelBefore(org.stellar.sdk.xdr.Duration)` - * `org.stellar.sdk.xdr.Duration Predicate.RelBefore.getSecondsSinceClose()` +* org.stellar.sdk.Transaction + * deprecated `addTimeBounds()` use `addPreconditions()` instead + * deprecated `setTimeout()` use `addPreconditions()` instead + * deprecated `Transaction.Builder` use TransactionBuilder instead ## 0.31.0 diff --git a/build.gradle b/build.gradle index 097f6426c..7d8ed9a03 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,6 @@ buildscript { } plugins { - id "io.freefair.lombok" version "6.4.1" id "com.github.johnrengelman.shadow" version "7.1.2" id "java" id "com.github.ben-manes.versions" version "0.42.0" @@ -44,6 +43,7 @@ dependencies { testImplementation "com.squareup.okhttp3:mockwebserver:${okhttpclientVersion}" testImplementation 'junit:junit:4.12' testImplementation 'javax.xml.bind:jaxb-api:2.3.0' + testImplementation group: 'org.junit.vintage', name: 'junit-vintage-engine', version: '4.12.1' } tasks.named('test') { useJUnitPlatform() diff --git a/src/main/java/org/stellar/sdk/Account.java b/src/main/java/org/stellar/sdk/Account.java index 1ab4531b3..9e399cbf2 100644 --- a/src/main/java/org/stellar/sdk/Account.java +++ b/src/main/java/org/stellar/sdk/Account.java @@ -7,7 +7,7 @@ /** * Represents an account in Stellar network with it's sequence number. * Account object is required to build a {@link Transaction}. - * @see org.stellar.sdk.Transaction.Builder + * @see TransactionBuilder */ public class Account implements TransactionBuilderAccount { private final String mAccountId; @@ -38,6 +38,11 @@ public Long getSequenceNumber() { return mSequenceNumber; } + @Override + public void setSequenceNumber(long seqNum) { + mSequenceNumber = seqNum; + } + @Override public Long getIncrementedSequenceNumber() { return mSequenceNumber + 1; diff --git a/src/main/java/org/stellar/sdk/FeeBumpTransaction.java b/src/main/java/org/stellar/sdk/FeeBumpTransaction.java index afc048c02..f77988ab9 100644 --- a/src/main/java/org/stellar/sdk/FeeBumpTransaction.java +++ b/src/main/java/org/stellar/sdk/FeeBumpTransaction.java @@ -2,7 +2,13 @@ import com.google.common.base.Objects; import com.google.common.collect.Lists; -import org.stellar.sdk.xdr.*; +import org.stellar.sdk.xdr.DecoratedSignature; +import org.stellar.sdk.xdr.EnvelopeType; +import org.stellar.sdk.xdr.FeeBumpTransactionEnvelope; +import org.stellar.sdk.xdr.Int64; +import org.stellar.sdk.xdr.PreconditionsV2; +import org.stellar.sdk.xdr.TransactionEnvelope; +import org.stellar.sdk.xdr.TransactionSignaturePayload; import java.util.Arrays; @@ -113,21 +119,30 @@ public static class Builder { * @param accountConverter The AccountConverter which will be used to encode the fee account. * @param inner The inner transaction which will be fee bumped. */ - public Builder(AccountConverter accountConverter, Transaction inner) { - inner = checkNotNull(inner, "inner cannot be null"); + public Builder(AccountConverter accountConverter, final Transaction inner) { + checkNotNull(inner, "inner cannot be null"); EnvelopeType txType = inner.toEnvelopeXdr().getDiscriminant(); this.mAccountConverter = checkNotNull(accountConverter, "accountConverter cannot be null"); if (inner.toEnvelopeXdr().getDiscriminant() == EnvelopeType.ENVELOPE_TYPE_TX_V0) { - this.mInner = new Transaction( - inner.accountConverter, - inner.getSourceAccount(), - inner.getFee(), - inner.getSequenceNumber(), - inner.getOperations(), - inner.getMemo(), - inner.getTimeBounds(), - inner.getNetwork() - ); + this.mInner = new TransactionBuilder(inner.accountConverter, new Account(inner.getSourceAccount(), inner.getSequenceNumber()), inner.getNetwork()) + .setBaseFee((int)inner.getFee()) + .addOperations(Arrays.asList(inner.getOperations())) + .addMemo(inner.getMemo()) + .addSequenceNumberStrategy(new SequenceNumberStrategy() { + @Override + public long getSequenceNumber(TransactionBuilderAccount account) { + // set the tx seq num to same as that of the inner tx + return inner.getSequenceNumber(); + } + + @Override + public void setSequenceNumber(long newSequenceNumber, TransactionBuilderAccount account) { + //no-op, account instance is local to this scope, not external, no need to update it. + } + }) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(inner.getTimeBounds().toXdr()).build()) + .build(); + this.mInner.mSignatures = Lists.newArrayList(inner.mSignatures); } else { this.mInner = inner; diff --git a/src/main/java/org/stellar/sdk/Sep10Challenge.java b/src/main/java/org/stellar/sdk/Sep10Challenge.java index 50f97e2f3..7e83df903 100644 --- a/src/main/java/org/stellar/sdk/Sep10Challenge.java +++ b/src/main/java/org/stellar/sdk/Sep10Challenge.java @@ -11,7 +11,12 @@ import java.io.IOException; import java.security.SecureRandom; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; public class Sep10Challenge { static final int GRACE_PERIOD_SECONDS = 5 * 60; @@ -88,7 +93,7 @@ public static Transaction newChallenge( .setSourceAccount(sourceAccount.getAccountId()) .build(); - Transaction.Builder builder = new Transaction.Builder(AccountConverter.enableMuxed(), sourceAccount, network) + TransactionBuilder builder = new TransactionBuilder(AccountConverter.enableMuxed(), sourceAccount, network) .addTimeBounds(timebounds) .setBaseFee(100) .addOperation(domainNameOperation) diff --git a/src/main/java/org/stellar/sdk/SequenceNumberStrategy.java b/src/main/java/org/stellar/sdk/SequenceNumberStrategy.java new file mode 100644 index 000000000..0e8fd77c3 --- /dev/null +++ b/src/main/java/org/stellar/sdk/SequenceNumberStrategy.java @@ -0,0 +1,13 @@ +package org.stellar.sdk; + +public interface SequenceNumberStrategy { + + /** + * callback to provide a custom routine to calculate the desired tx sequnce nuymber + * @param account the source account of transaction. + * @return the sequnce number to apply on transaction. + */ + long getSequenceNumber(TransactionBuilderAccount account); + + void setSequenceNumber(long newSequenceNumber, TransactionBuilderAccount account); +} diff --git a/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java b/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java new file mode 100644 index 000000000..d4c25b1d7 --- /dev/null +++ b/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java @@ -0,0 +1,13 @@ +package org.stellar.sdk; + +public class SequentialSequenceNumberStrategy implements SequenceNumberStrategy{ + @Override + public long getSequenceNumber(TransactionBuilderAccount account) { + return account.getIncrementedSequenceNumber(); + } + + @Override + public void setSequenceNumber(long newSequnceNumber, TransactionBuilderAccount account) { + account.incrementSequenceNumber(); + } +} diff --git a/src/main/java/org/stellar/sdk/Transaction.java b/src/main/java/org/stellar/sdk/Transaction.java index 082d41d20..b7794a274 100644 --- a/src/main/java/org/stellar/sdk/Transaction.java +++ b/src/main/java/org/stellar/sdk/Transaction.java @@ -10,6 +10,7 @@ import org.stellar.sdk.xdr.OperationID; import org.stellar.sdk.xdr.PreconditionType; import org.stellar.sdk.xdr.Preconditions; +import org.stellar.sdk.xdr.PreconditionsV2; import org.stellar.sdk.xdr.SequenceNumber; import org.stellar.sdk.xdr.TransactionEnvelope; import org.stellar.sdk.xdr.TransactionSignaturePayload; @@ -21,10 +22,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.List; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -38,7 +36,7 @@ public class Transaction extends AbstractTransaction { private final long mSequenceNumber; private final Operation[] mOperations; private final Memo mMemo; - private final TimeBounds mTimeBounds; + private final PreconditionsV2 preconditions; private EnvelopeType envelopeType = EnvelopeType.ENVELOPE_TYPE_TX; Transaction( @@ -48,18 +46,17 @@ public class Transaction extends AbstractTransaction { long sequenceNumber, Operation[] operations, Memo memo, - TimeBounds timeBounds, + PreconditionsV2 preconditions, Network network ) { super(accountConverter, network); this.mSourceAccount = checkNotNull(sourceAccount, "sourceAccount cannot be null"); - this.mSequenceNumber = checkNotNull(sequenceNumber, "sequenceNumber cannot be null"); + this.mSequenceNumber = sequenceNumber; this.mOperations = checkNotNull(operations, "operations cannot be null"); checkArgument(operations.length > 0, "At least one operation required"); - + this.preconditions = preconditions; this.mFee = fee; this.mMemo = memo != null ? memo : Memo.none(); - this.mTimeBounds = timeBounds; } // setEnvelopeType is only used in tests which is why this method is package protected @@ -90,10 +87,21 @@ public Memo getMemo() { } /** - * @return TimeBounds, or null (representing no time restrictions) + * @return Preconditions + */ + public PreconditionsV2 getPreconditions() { + return preconditions; + } + + /** + * @return TimeBounds, or null (representing no time restrictions, i.e. infinite) */ public TimeBounds getTimeBounds() { - return mTimeBounds; + TimeBounds timeBounds = TimeBounds.fromXdr(preconditions.getTimeBounds()); + if (timeBounds == null || (timeBounds.getMinTime() < 1 && timeBounds.getMaxTime() < 1)) { + return null; + } + return timeBounds; } /** @@ -173,7 +181,7 @@ private TransactionV0 toXdr() { transaction.setSourceAccountEd25519(StrKey.encodeToXDRAccountId(this.mSourceAccount).getAccountID().getEd25519()); transaction.setOperations(operations); transaction.setMemo(mMemo.toXdr()); - transaction.setTimeBounds(mTimeBounds == null ? null : mTimeBounds.toXdr()); + transaction.setTimeBounds(preconditions == null || preconditions.getTimeBounds() == null ? null : preconditions.getTimeBounds()); transaction.setExt(ext); return transaction; } @@ -204,11 +212,11 @@ private org.stellar.sdk.xdr.Transaction toV1Xdr(AccountConverter accountConverte v1Tx.setSourceAccount(accountConverter.encode(mSourceAccount)); v1Tx.setOperations(operations); v1Tx.setMemo(mMemo.toXdr()); - Preconditions.Builder preconditions = new Preconditions.Builder().discriminant(PreconditionType.PRECOND_NONE); - if (mTimeBounds != null) { - preconditions.discriminant(PreconditionType.PRECOND_TIME).timeBounds(mTimeBounds.toXdr()); + Preconditions.Builder preconditionsBuilder = new Preconditions.Builder().discriminant(PreconditionType.PRECOND_NONE); + if (preconditions != null && preconditions.getTimeBounds() != null) { + preconditionsBuilder.discriminant(PreconditionType.PRECOND_TIME).timeBounds(preconditions.getTimeBounds()); } - v1Tx.setCond(preconditions.build()); + v1Tx.setCond(preconditionsBuilder.build()); v1Tx.setExt(ext); return v1Tx; @@ -218,7 +226,8 @@ public static Transaction fromV0EnvelopeXdr(AccountConverter accountConverter, T int mFee = envelope.getTx().getFee().getUint32(); Long mSequenceNumber = envelope.getTx().getSeqNum().getSequenceNumber().getInt64(); Memo mMemo = Memo.fromXdr(envelope.getTx().getMemo()); - TimeBounds mTimeBounds = TimeBounds.fromXdr(envelope.getTx().getTimeBounds()); + PreconditionsV2.Builder preconditionsV2Builder = new PreconditionsV2.Builder(); + preconditionsV2Builder.timeBounds(envelope.getTx().getTimeBounds()); Operation[] mOperations = new Operation[envelope.getTx().getOperations().length]; for (int i = 0; i < envelope.getTx().getOperations().length; i++) { @@ -232,7 +241,7 @@ public static Transaction fromV0EnvelopeXdr(AccountConverter accountConverter, T mSequenceNumber, mOperations, mMemo, - mTimeBounds, + preconditionsV2Builder.build(), network ); transaction.setEnvelopeType(EnvelopeType.ENVELOPE_TYPE_TX_V0); @@ -248,11 +257,15 @@ public static Transaction fromV0EnvelopeXdr(TransactionV0Envelope envelope, Netw public static Transaction fromV1EnvelopeXdr(AccountConverter accountConverter, TransactionV1Envelope envelope, Network network) { int mFee = envelope.getTx().getFee().getUint32(); - TimeBounds mTimeBounds = null; Long mSequenceNumber = envelope.getTx().getSeqNum().getSequenceNumber().getInt64(); Memo mMemo = Memo.fromXdr(envelope.getTx().getMemo()); + PreconditionsV2 preconditionsV2 = new PreconditionsV2(); + if (envelope.getTx().getCond().getDiscriminant().equals(PreconditionType.PRECOND_TIME)) { - mTimeBounds = TimeBounds.fromXdr(envelope.getTx().getCond().getTimeBounds()); + preconditionsV2 = new PreconditionsV2.Builder().timeBounds(envelope.getTx().getCond().getTimeBounds()).build(); + } + if (envelope.getTx().getCond().getDiscriminant().equals(PreconditionType.PRECOND_V2) && envelope.getTx().getCond().getV2().getTimeBounds() != null) { + preconditionsV2 = envelope.getTx().getCond().getV2(); } Operation[] mOperations = new Operation[envelope.getTx().getOperations().length]; @@ -267,7 +280,7 @@ public static Transaction fromV1EnvelopeXdr(AccountConverter accountConverter, T mSequenceNumber, mOperations, mMemo, - mTimeBounds, + preconditionsV2, network ); @@ -308,172 +321,6 @@ public TransactionEnvelope toEnvelopeXdr() { return xdr; } - /** - * Builds a new Transaction object. - */ - public static class Builder { - private final TransactionBuilderAccount mSourceAccount; - private final AccountConverter mAccountConverter; - private Memo mMemo; - private TimeBounds mTimeBounds; - List mOperations; - private boolean timeoutSet; - private Integer mBaseFee; - private Network mNetwork; - - public static final long TIMEOUT_INFINITE = 0; - - /** - * Construct a new transaction builder. - * @param sourceAccount The source account for this transaction. This account is the account - * who will use a sequence number. When build() is called, the account object's sequence number - * will be incremented. - */ - public Builder(AccountConverter accountConverter, TransactionBuilderAccount sourceAccount, Network network) { - mAccountConverter = checkNotNull(accountConverter, "accountConverter cannot be null"); - mSourceAccount = checkNotNull(sourceAccount, "sourceAccount cannot be null"); - mOperations = Collections.synchronizedList(new ArrayList()); - mNetwork = checkNotNull(network, "Network cannot be null"); - } - - /** - * Construct a new transaction builder. - * @param sourceAccount The source account for this transaction. This account is the account - * who will use a sequence number. When build() is called, the account object's sequence number - * will be incremented. - */ - public Builder(TransactionBuilderAccount sourceAccount, Network network) { - this(AccountConverter.enableMuxed(), sourceAccount, network); - } - - public int getOperationsCount() { - return mOperations.size(); - } - - /** - * Adds a new operation to this transaction. - * @param operation - * @return Builder object so you can chain methods. - * @see Operation - */ - public Builder addOperation(Operation operation) { - checkNotNull(operation, "operation cannot be null"); - mOperations.add(operation); - return this; - } - - /** - * Adds a memo to this transaction. - * @param memo - * @return Builder object so you can chain methods. - * @see Memo - */ - public Builder addMemo(Memo memo) { - if (mMemo != null) { - throw new RuntimeException("Memo has been already added."); - } - checkNotNull(memo, "memo cannot be null"); - mMemo = memo; - return this; - } - - /** - * Adds a time-bounds to this transaction. - * @param timeBounds - * @return Builder object so you can chain methods. - * @see TimeBounds - */ - public Builder addTimeBounds(TimeBounds timeBounds) { - if (mTimeBounds != null) { - throw new RuntimeException("TimeBounds has been already added."); - } - checkNotNull(timeBounds, "timeBounds cannot be null"); - mTimeBounds = timeBounds; - return this; - } - - /** - * Because of the distributed nature of the Stellar network it is possible that the status of your transaction - * will be determined after a long time if the network is highly congested. - * If you want to be sure to receive the status of the transaction within a given period you should set the - * {@link TimeBounds} with maxTime on the transaction (this is what setTimeout does - * internally; if there's minTime set but no maxTime it will be added). - * Call to Builder.setTimeout is required if Transaction does not have max_time set. - * If you don't want to set timeout, use TIMEOUT_INFINITE. In general you should set - * TIMEOUT_INFINITE only in smart contracts. - * Please note that Horizon may still return 504 Gateway Timeout error, even for short timeouts. - * In such case you need to resubmit the same transaction again without making any changes to receive a status. - * This method is using the machine system time (UTC), make sure it is set correctly. - * @param timeout Timeout in seconds. - * @see TimeBounds - * @return - */ - public Builder setTimeout(long timeout) { - if (mTimeBounds != null && mTimeBounds.getMaxTime() > 0) { - throw new RuntimeException("TimeBounds.max_time has been already set - setting timeout would overwrite it."); - } - - if (timeout < 0) { - throw new RuntimeException("timeout cannot be negative"); - } - - timeoutSet = true; - if (timeout > 0) { - long timeoutTimestamp = System.currentTimeMillis() / 1000L + timeout; - if (mTimeBounds == null) { - mTimeBounds = new TimeBounds(0, timeoutTimestamp); - } else { - mTimeBounds = new TimeBounds(mTimeBounds.getMinTime(), timeoutTimestamp); - } - } - - return this; - } - - public Builder setBaseFee(int baseFee) { - if (baseFee < MIN_BASE_FEE) { - throw new IllegalArgumentException("baseFee cannot be smaller than the BASE_FEE (" + MIN_BASE_FEE + "): " + baseFee); - } - - this.mBaseFee = baseFee; - return this; - } - - /** - * Builds a transaction. It will increment sequence number of the source account. - */ - public Transaction build() { - // Ensure setTimeout called or maxTime is set - if ((mTimeBounds == null || mTimeBounds != null && mTimeBounds.getMaxTime() == 0) && !timeoutSet) { - throw new RuntimeException("TimeBounds has to be set or you must call setTimeout(TIMEOUT_INFINITE)."); - } - - if (mBaseFee == null) { - throw new RuntimeException("mBaseFee has to be set. you must call setBaseFee()."); - } - - if (mNetwork == null) { - throw new NoNetworkSelectedException(); - } - - Operation[] operations = new Operation[mOperations.size()]; - operations = mOperations.toArray(operations); - Transaction transaction = new Transaction( - mAccountConverter, - mSourceAccount.getAccountId(), - operations.length * mBaseFee, - mSourceAccount.getIncrementedSequenceNumber(), - operations, - mMemo, - mTimeBounds, - mNetwork - ); - // Increment sequence number when there were no exceptions when creating a transaction - mSourceAccount.incrementSequenceNumber(); - return transaction; - } - } - @Override public int hashCode() { return Objects.hashCode( @@ -483,7 +330,7 @@ public int hashCode() { this.mSequenceNumber, Arrays.hashCode(this.mOperations), this.mMemo, - this.mTimeBounds, + this.preconditions, this.mSignatures, this.mNetwork ); @@ -502,8 +349,24 @@ public boolean equals(Object object) { Objects.equal(this.mSequenceNumber, other.mSequenceNumber) && Arrays.equals(this.mOperations, other.mOperations) && Objects.equal(this.mMemo, other.mMemo) && - Objects.equal(this.mTimeBounds, other.mTimeBounds) && + Objects.equal(this.preconditions, other.preconditions) && Objects.equal(this.mNetwork, other.mNetwork) && Objects.equal(this.mSignatures, other.mSignatures); } + + /** + * Maintain backwards compatibility references to Transaction.Builder + * + * @deprecated will be removed in upcoming releases. Use TransactionBuilder instead. + * @see org.stellar.sdk.TransactionBuilder + */ + public static class Builder extends TransactionBuilder { + public Builder(AccountConverter accountConverter, TransactionBuilderAccount sourceAccount, Network network) { + super(accountConverter, sourceAccount, network); + } + + public Builder(TransactionBuilderAccount sourceAccount, Network network) { + super(sourceAccount, network); + } + } } diff --git a/src/main/java/org/stellar/sdk/TransactionBuilder.java b/src/main/java/org/stellar/sdk/TransactionBuilder.java new file mode 100644 index 000000000..6f249c7db --- /dev/null +++ b/src/main/java/org/stellar/sdk/TransactionBuilder.java @@ -0,0 +1,253 @@ +package org.stellar.sdk; + +import org.stellar.sdk.xdr.PreconditionsV2; +import org.stellar.sdk.xdr.TimePoint; +import org.stellar.sdk.xdr.Uint64; + +import java.util.Collection; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Lists.newArrayList; + +/** + * Builds a new Transaction object. + */ +public class TransactionBuilder { + private final TransactionBuilderAccount mSourceAccount; + private final AccountConverter mAccountConverter; + private Memo mMemo; + private List mOperations; + private Integer mBaseFee; + private Network mNetwork; + private SequenceNumberStrategy sequenceNumberStrategy; + private PreconditionsV2 preconditions; + + public static final long TIMEOUT_INFINITE = 0; + public static final long MAX_EXTRA_SIGNERS_COUNT = 2; + + /** + * Construct a new transaction builder. + * + * @param accountConverter the account id formatter, choose to support muxed or not. + * @param sourceAccount the source account for this transaction. This account is the account + * who will use a sequence number. When build() is called, the account object's sequence number + * will be incremented. + * @param network the testnet or pubnet network to use + */ + public TransactionBuilder(AccountConverter accountConverter, TransactionBuilderAccount sourceAccount, Network network) { + mAccountConverter = checkNotNull(accountConverter, "accountConverter cannot be null"); + mSourceAccount = checkNotNull(sourceAccount, "sourceAccount cannot be null"); + mNetwork = checkNotNull(network, "Network cannot be null"); + mOperations = newArrayList(); + preconditions = new PreconditionsV2(); + sequenceNumberStrategy = new SequentialSequenceNumberStrategy(); + } + + /** + * Construct a new transaction builder. + * + * @param sourceAccount the source account for this transaction. This account is the account + * who will use a sequence number. When build() is called, the account object's sequence number + * will be incremented. + * @param network the testnet or pubnet network to use + */ + public TransactionBuilder(TransactionBuilderAccount sourceAccount, Network network) { + this(AccountConverter.enableMuxed(), sourceAccount, network); + } + + public int getOperationsCount() { + return mOperations.size(); + } + + /** + * Adds a new operation to this transaction. + * + * @param operation + * @return Builder object so you can chain methods. + * @see Operation + */ + public TransactionBuilder addOperation(Operation operation) { + checkNotNull(operation, "operation cannot be null"); + mOperations.add(operation); + return this; + } + + /** + * Adds operation to this transaction. + * + * @param operations list of operations + * @return Builder object so you can chain methods. + * @see Operation + */ + public TransactionBuilder addOperations(Collection operations) { + checkNotNull(operations, "operations cannot be null"); + mOperations.addAll(operations); + return this; + } + + /** + * Adds preconditions. + * For details of all preconditions on transaction refer to CAP-21 + * + * @param preconditions the tx PreConditions + * @return updated Builder object + */ + public TransactionBuilder addPreconditions(PreconditionsV2 preconditions) { + checkNotNull(preconditions, "preconditions cannot be null"); + if (preconditions.getExtraSigners() != null && preconditions.getExtraSigners().length > MAX_EXTRA_SIGNERS_COUNT) { + throw new FormatException("Invalid preconditions, too many extra signers, can only have up to " + MAX_EXTRA_SIGNERS_COUNT); + } + this.preconditions = preconditions; + return this; + } + + /** + * Add a sequnce number resolution strategy. The Strategy is a callback that can determine the + * actual sequence number applied to the tx being built. + * + * @param sequenceNumberStrategy the sequence number strategy that defines how to get a sequence number for the tx + * and if/how to set source account sequence number after tx is built. + * @return updated Builder object + */ + public TransactionBuilder addSequenceNumberStrategy(SequenceNumberStrategy sequenceNumberStrategy) { + this.sequenceNumberStrategy = sequenceNumberStrategy; + return this; + } + + /** + * Adds a memo to this transaction. + * + * @param memo + * @return Builder object so you can chain methods. + * @see Memo + */ + public TransactionBuilder addMemo(Memo memo) { + if (mMemo != null) { + throw new RuntimeException("Memo has been already added."); + } + checkNotNull(memo, "memo cannot be null"); + mMemo = memo; + return this; + } + + /** + * Adds a time-bounds to this transaction. + * + * @param timeBounds tx can be accepted within this time bound range + * @return Builder object so you can chain methods. + * @see TimeBounds + * @deprecated this method will be removed in upcoming releases, use addPreconditions() instead for more control over preconditions. + */ + public TransactionBuilder addTimeBounds(TimeBounds timeBounds) { + checkNotNull(timeBounds, "timeBounds cannot be null"); + preconditions.setTimeBounds(timeBounds.toXdr()); + return this; + } + + /** + * Because of the distributed nature of the Stellar network it is possible that the status of your transaction + * will be determined after a long time if the network is highly congested. + * If you want to be sure to receive the status of the transaction within a given period you should set the + * {@link TimeBounds} with maxTime on the transaction (this is what setTimeout does + * internally; if there's minTime set but no maxTime it will be added). + * Call to Builder.setTimeout is required if Transaction does not have max_time set. + * If you don't want to set timeout, use TIMEOUT_INFINITE. In general you should set + * TIMEOUT_INFINITE only in smart contracts. + * Please note that Horizon may still return 504 Gateway Timeout error, even for short timeouts. + * In such case you need to resubmit the same transaction again without making any changes to receive a status. + * This method is using the machine system time (UTC), make sure it is set correctly. + * + * @param timeout Timeout in seconds. + * @return updated Builder + * @see TimeBounds + * @deprecated this method will be removed in upcoming releases, use addPreconditions() instead + * for more control over preconditions. + */ + public TransactionBuilder setTimeout(long timeout) { + if (preconditions.getTimeBounds() != null && preconditions.getTimeBounds().getMaxTime().getTimePoint().getUint64() > 0) { + throw new RuntimeException("TimeBounds.max_time has been already set - setting timeout would overwrite it."); + } + + if (timeout < 0) { + throw new RuntimeException("timeout cannot be negative"); + } + + if (timeout > 0) { + long timeoutTimestamp = System.currentTimeMillis() / 1000L + timeout; + if (preconditions.getTimeBounds() == null) { + // no timebounds settings yet, so timeout is conveyed on the tx as a new timebounds in precondition + // with a range of genesis to (current time + timeout interval) + preconditions.setTimeBounds(buildTimeBounds(0L, timeoutTimestamp)); + } else { + // an existing timebounds in precondition, so timeout is conveyed on the tx as the current timebounds + // min time to a max time of (current time + timeout interval) + preconditions.getTimeBounds().setMaxTime(new TimePoint(new Uint64(timeoutTimestamp))); + } + } + + if (timeout == 0) { + if (preconditions.getTimeBounds() == null) { + // no timebounds settings yet, so infinite timeout is conveyed on the tx in a new timebounds precondition + // with a range of genesis to infinite + preconditions.setTimeBounds(buildTimeBounds(0L, 0L)); + } else { + // an existing timebounds in precondition, so timeout is conveyed on the tx as the current timebounds min time + // to a max time of infinite + preconditions.getTimeBounds().setMaxTime(new TimePoint(new Uint64(0L))); + } + } + + return this; + } + + public TransactionBuilder setBaseFee(int baseFee) { + if (baseFee < AbstractTransaction.MIN_BASE_FEE) { + throw new IllegalArgumentException("baseFee cannot be smaller than the BASE_FEE (" + AbstractTransaction.MIN_BASE_FEE + "): " + baseFee); + } + + this.mBaseFee = baseFee; + return this; + } + + /** + * Builds a transaction. It will use the SequenceNumberStrategy to determine how to update source account + * sequence number + */ + public Transaction build() { + if (preconditions.getTimeBounds() == null) { + throw new FormatException("Timebounds is a mandatory precondition that must be set. Use TimeBounds(0,0) for infinite timeout"); + } + + if (mBaseFee == null) { + throw new FormatException("mBaseFee has to be set. you must call setBaseFee()."); + } + + if (mNetwork == null) { + throw new NoNetworkSelectedException(); + } + + long sequenceNumber = sequenceNumberStrategy.getSequenceNumber(mSourceAccount); + + Operation[] operations = new Operation[mOperations.size()]; + operations = mOperations.toArray(operations); + Transaction transaction = new Transaction( + mAccountConverter, + mSourceAccount.getAccountId(), + operations.length * mBaseFee, + sequenceNumber, + operations, + mMemo, + preconditions, + mNetwork + ); + // Increment sequence number when there were no exceptions when creating a transaction + sequenceNumberStrategy.setSequenceNumber(sequenceNumber, mSourceAccount); + return transaction; + } + + public static org.stellar.sdk.xdr.TimeBounds buildTimeBounds(long minTime, long maxTime) { + return new org.stellar.sdk.xdr.TimeBounds.Builder().minTime(new TimePoint(new Uint64(minTime))) + .maxTime(new TimePoint(new Uint64(maxTime))).build(); + } +} diff --git a/src/main/java/org/stellar/sdk/TransactionBuilderAccount.java b/src/main/java/org/stellar/sdk/TransactionBuilderAccount.java index 86a2308ea..a11ca967a 100644 --- a/src/main/java/org/stellar/sdk/TransactionBuilderAccount.java +++ b/src/main/java/org/stellar/sdk/TransactionBuilderAccount.java @@ -1,7 +1,7 @@ package org.stellar.sdk; /** - * Specifies interface for Account object used in {@link org.stellar.sdk.Transaction.Builder} + * Specifies interface for Account object used in {@link TransactionBuilder} */ public interface TransactionBuilderAccount { /** @@ -19,6 +19,11 @@ public interface TransactionBuilderAccount { */ Long getSequenceNumber(); + /** + * Set current sequence number on this Account. + */ + void setSequenceNumber(long seqNum); + /** * Returns sequence number incremented by one, but does not increment internal counter. */ diff --git a/src/main/java/org/stellar/sdk/responses/AccountResponse.java b/src/main/java/org/stellar/sdk/responses/AccountResponse.java index fc33840e7..fcbfa42e8 100644 --- a/src/main/java/org/stellar/sdk/responses/AccountResponse.java +++ b/src/main/java/org/stellar/sdk/responses/AccountResponse.java @@ -74,6 +74,11 @@ public Long getSequenceNumber() { return sequenceNumber; } + @Override + public void setSequenceNumber(long seqNum) { + sequenceNumber = seqNum; + } + @Override public Long getIncrementedSequenceNumber() { return new Long(sequenceNumber + 1); diff --git a/src/test/java/org/stellar/sdk/ClaimableBalanceIdTest.java b/src/test/java/org/stellar/sdk/ClaimableBalanceIdTest.java index be8e7b1fa..642ec2144 100644 --- a/src/test/java/org/stellar/sdk/ClaimableBalanceIdTest.java +++ b/src/test/java/org/stellar/sdk/ClaimableBalanceIdTest.java @@ -26,11 +26,11 @@ public void testClaimableBalanceIds() throws IOException { new Predicate.Unconditional() ) )).build(); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), new Account(sourceAccount, 123l), Network.TESTNET) + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), new Account(sourceAccount, 123l), Network.TESTNET) .addOperation(op0) .addOperation(new BumpSequenceOperation.Builder(2l).build()) .addOperation(op0) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); @@ -51,18 +51,18 @@ public void testClaimableBalanceIds() throws IOException { new Predicate.Unconditional() ) )).setSourceAccount("GABXJTV7ELEB2TQZKJYEGXBUIG6QODJULKJDI65KZMIZZG2EACJU5EA7").build(); - transaction = new Transaction.Builder(AccountConverter.enableMuxed(), new Account(sourceAccount, 123l), Network.TESTNET) + transaction = new TransactionBuilder(AccountConverter.enableMuxed(), new Account(sourceAccount, 123l), Network.TESTNET) .addOperation(opWithSourceAccount) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); // operation source account does not affect claimable balance id assertEquals(expectedIdIndex0, transaction.getClaimableBalanceId(0)); - transaction = new Transaction.Builder(AccountConverter.enableMuxed(), new Account(sourceAccount, 124l), Network.TESTNET) + transaction = new TransactionBuilder(AccountConverter.enableMuxed(), new Account(sourceAccount, 124l), Network.TESTNET) .addOperation(opWithSourceAccount) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); @@ -78,10 +78,10 @@ public void testClaimableBalanceIds() throws IOException { muxedAccount.setMed25519(med); - transaction = new Transaction.Builder(AccountConverter.enableMuxed(), new Account(StrKey.encodeStellarMuxedAccount(muxedAccount), 123l), Network.TESTNET) + transaction = new TransactionBuilder(AccountConverter.enableMuxed(), new Account(StrKey.encodeStellarMuxedAccount(muxedAccount), 123l), Network.TESTNET) .addOperation(op0) .addOperation(new BumpSequenceOperation.Builder(2l).build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); diff --git a/src/test/java/org/stellar/sdk/FeeBumpTransactionTest.java b/src/test/java/org/stellar/sdk/FeeBumpTransactionTest.java index 5d3a5753e..c2ae03995 100644 --- a/src/test/java/org/stellar/sdk/FeeBumpTransactionTest.java +++ b/src/test/java/org/stellar/sdk/FeeBumpTransactionTest.java @@ -5,7 +5,9 @@ import java.io.IOException; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.fail; public class FeeBumpTransactionTest { @@ -13,7 +15,7 @@ private Transaction createInnerTransaction(int baseFee) { KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); Account account = new Account(source.getAccountId(), 2908908335136768L); - Transaction inner = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) + Transaction inner = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new PaymentOperation.Builder( "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ", new AssetTypeNative(), diff --git a/src/test/java/org/stellar/sdk/OperationTest.java b/src/test/java/org/stellar/sdk/OperationTest.java index 4d8531355..ae7b64157 100644 --- a/src/test/java/org/stellar/sdk/OperationTest.java +++ b/src/test/java/org/stellar/sdk/OperationTest.java @@ -1,13 +1,12 @@ package org.stellar.sdk; import com.google.common.collect.Lists; +import com.google.common.io.BaseEncoding; import org.junit.Test; import org.stellar.sdk.xdr.SignerKey; import org.stellar.sdk.xdr.TrustLineFlags; import org.stellar.sdk.xdr.XdrDataInputStream; -import com.google.common.io.BaseEncoding; - import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Arrays; @@ -518,9 +517,9 @@ public void testSetOptionsOperationPreAuthTxSigner() { long sequenceNumber = 2908908335136768L; Account account = new Account(source.getAccountId(), sequenceNumber); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); @@ -535,21 +534,17 @@ public void testSetOptionsOperationPreAuthTxSigner() { org.stellar.sdk.xdr.Operation xdr = operation.toXdr(AccountConverter.enableMuxed()); SetOptionsOperation parsedOperation = (SetOptionsOperation) SetOptionsOperation.fromXdr(AccountConverter.enableMuxed(), xdr); - assertEquals(null, parsedOperation.getInflationDestination()); - assertEquals(null, parsedOperation.getClearFlags()); - assertEquals(null, parsedOperation.getSetFlags()); - assertEquals(null, parsedOperation.getMasterKeyWeight()); - assertEquals(null, parsedOperation.getLowThreshold()); - assertEquals(null, parsedOperation.getMediumThreshold()); - assertEquals(null, parsedOperation.getHighThreshold()); - assertEquals(null, parsedOperation.getHomeDomain()); + assertEquals(operation.getInflationDestination(), parsedOperation.getInflationDestination()); + assertEquals(operation.getClearFlags(), parsedOperation.getClearFlags()); + assertEquals(operation.getSetFlags(), parsedOperation.getSetFlags()); + assertEquals(operation.getMasterKeyWeight(), parsedOperation.getMasterKeyWeight()); + assertEquals(operation.getLowThreshold(), parsedOperation.getLowThreshold()); + assertEquals(operation.getMediumThreshold(), parsedOperation.getMediumThreshold()); + assertEquals(operation.getHighThreshold(), parsedOperation.getHighThreshold()); + assertEquals(operation.getHomeDomain(), parsedOperation.getHomeDomain()); assertTrue(Arrays.equals(transaction.hash(), parsedOperation.getSigner().getPreAuthTx().getUint256())); - assertEquals(new Integer(10), parsedOperation.getSignerWeight()); - assertEquals(opSource.getAccountId(), parsedOperation.getSourceAccount()); - - assertEquals( - "AAAAAQAAAAC7JAuE3XvquOnbsgv2SRztjuk4RoBVefQ0rlrFMMQvfAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAB1vRBIRC3w7ZH5rQa17hIBKUwZTvBP4kNmSP7jVyw1fQAAAAK", - operation.toXdrBase64(AccountConverter.enableMuxed())); + assertEquals(operation.getSignerWeight(), parsedOperation.getSignerWeight()); + assertEquals(operation.getSourceAccount(), parsedOperation.getSourceAccount()); } @Test diff --git a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java index b20485d81..6a6c24225 100644 --- a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java +++ b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java @@ -4,7 +4,10 @@ import com.google.common.io.BaseEncoding; import org.junit.Test; import org.stellar.sdk.xdr.EnvelopeType; +import org.stellar.sdk.xdr.PreconditionsV2; +import org.stellar.sdk.xdr.TimePoint; import org.stellar.sdk.xdr.TransactionEnvelope; +import org.stellar.sdk.xdr.Uint64; import java.io.IOException; import java.security.SecureRandom; @@ -13,7 +16,10 @@ import java.util.HashSet; import java.util.Set; -import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class Sep10ChallengeTest { @@ -218,7 +224,7 @@ public void testReadChallengeTransactionRejectsMuxedClient() throws InvalidSep10 long end = now + 300; TimeBounds timeBounds = new TimeBounds(now, end); - Transaction transaction = Sep10Challenge.newChallenge( + final Transaction transaction = Sep10Challenge.newChallenge( server, network, client.getAccountId(), @@ -228,16 +234,24 @@ public void testReadChallengeTransactionRejectsMuxedClient() throws InvalidSep10 ); Operation[] operations = transaction.getOperations(); operations[0].setSourceAccount("MCAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITKNOG"); - Transaction withMuxedClient = new Transaction( - AccountConverter.disableMuxed(), - transaction.getSourceAccount(), - transaction.getFee(), - transaction.getSequenceNumber(), - operations, - transaction.getMemo(), - transaction.getTimeBounds(), - transaction.getNetwork() - ); + Transaction withMuxedClient = new TransactionBuilder(AccountConverter.disableMuxed(), new Account(transaction.getSourceAccount(), 100L), transaction.getNetwork()) + .setBaseFee((int)transaction.getFee()) + .addMemo(transaction.getMemo()) + .addOperations(Arrays.asList(operations)) + .addSequenceNumberStrategy(new SequenceNumberStrategy() { + @Override + public long getSequenceNumber(TransactionBuilderAccount account) { + return transaction.getSequenceNumber(); + } + + @Override + public void setSequenceNumber(long newSequenceNumber, TransactionBuilderAccount account) { + + } + }) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(transaction.getTimeBounds().toXdr()).build()) + .build(); + withMuxedClient.getSignatures().addAll(transaction.mSignatures); try { @@ -304,16 +318,12 @@ public void testReadChallengeTransactionInvalidNotSignedByServer() throws IOExce .build(); Operation[] operations = new Operation[]{manageDataOperation1}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(client); try { @@ -409,16 +419,13 @@ public void testReadChallengeTransactionInvalidSeqNoNotZero() throws IOException .build(); Operation[] operations = new Operation[]{manageDataOperation1}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); + transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -454,16 +461,14 @@ public void testReadChallengeTransactionInvalidTimeboundsInfinite() throws IOExc .setSourceAccount(client.getAccountId()) .build(); Operation[] operations = new Operation[]{operation}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); + transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -495,16 +500,16 @@ public void testReadChallengeTransactionInvalidNoTimeBounds() throws IOException .setSourceAccount(client.getAccountId()) .build(); Operation[] operations = new Operation[]{operation}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - null, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(new org.stellar.sdk.xdr.TimeBounds.Builder() + .minTime(new TimePoint(new Uint64(0L))) + .maxTime(new TimePoint(new Uint64(0L))) + .build()) + .build()) + .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -620,16 +625,13 @@ public void testReadChallengeTransactionInvalidOperationWrongType() throws IOExc .build(); Operation[] operations = new Operation[]{setOptionsOperation}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); + transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -664,16 +666,13 @@ public void testReadChallengeTransactionInvalidOperationNoSourceAccount() throws .build(); Operation[] operations = new Operation[]{manageDataOperation1}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); + transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -710,16 +709,12 @@ public void testReadChallengeTransactionInvalidDataValueWrongEncodedLength() thr .build(); Operation[] operations = new Operation[]{manageDataOperation1}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -751,16 +746,12 @@ public void testReadChallengeTransactionInvalidDataValueCorruptBase64() throws I .build(); Operation[] operations = new Operation[]{manageDataOperation1}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -798,16 +789,12 @@ public void testReadChallengeTransactionInvalidDataValueWrongByteLength() throws .build(); Operation[] operations = new Operation[]{manageDataOperation1}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -838,16 +825,12 @@ public void testReadChallengeTransactionInvalidDataValueIsNull() throws IOExcept .build(); Operation[] operations = new Operation[]{manageDataOperation1}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -886,16 +869,12 @@ public void testReadChallengeTransactionValidAdditionalManageDataOpsWithSourceAc .setSourceAccount(server.getAccountId()) .build(); Operation[] operations = new Operation[]{operation1, operation2}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -930,16 +909,12 @@ public void testReadChallengeTransactionInvalidAdditionalManageDataOpsWithoutSou .setSourceAccount(client.getAccountId()) .build(); Operation[] operations = new Operation[]{operation1, operation2}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction =new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -976,16 +951,12 @@ public void testReadChallengeTransactionInvalidAdditionalManageDataOpsWithSource .build(); ManageDataOperation operation2 = new ManageDataOperation.Builder("key", "value".getBytes()).build(); Operation[] operations = new Operation[]{operation1, operation2}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -1024,16 +995,12 @@ public void testReadChallengeTransactionInvalidAdditionalOpsOfOtherTypes() throw .setSourceAccount(server.getAccountId()) .build(); Operation[] operations = new Operation[]{operation1, operation2}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -1267,16 +1234,12 @@ public void testReadChallengeTransactionInvalidWebAuthDomainOperationValueIsNull .setSourceAccount(server.getAccountId()) .build(); Operation[] operations = new Operation[]{domainNameOperation, webAuthDomainOperation}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -1318,16 +1281,12 @@ public void testReadChallengeTransactionValidWebAuthDomainAllowSubsequentManageD .setSourceAccount(server.getAccountId()) .build(); Operation[] operations = new Operation[]{domainNameOperation, webAuthDomainOperation, otherDomainOperation}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -1539,16 +1498,12 @@ public void testVerifyChallengeTransactionThresholdInvalidNotSignedByServer() th .build(); Operation[] operations = new Operation[]{manageDataOperation1}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(masterClient); @@ -1975,16 +1930,12 @@ public void testVerifyChallengeTransactionSignersInvalidServer() throws IOExcept .build(); Operation[] operations = new Operation[]{manageDataOperation1}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(masterClient); transaction.sign(signerClient1); @@ -2469,16 +2420,12 @@ public void testVerifyChallengeTransactionValidAdditionalManageDataOpsWithSource .setSourceAccount(server.getAccountId()) .build(); Operation[] operations = new Operation[]{operation1, operation2}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(server); transaction.sign(masterClient); @@ -2514,16 +2461,12 @@ public void testVerifyChallengeTransactionInvalidAdditionalManageDataOpsWithoutS .setSourceAccount(masterClient.getAccountId()) .build(); Operation[] operations = new Operation[]{operation1, operation2}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(server); transaction.sign(masterClient); @@ -2561,16 +2504,12 @@ public void testVerifyChallengeTransactionInvalidAdditionalManageDataOpsWithSour .build(); ManageDataOperation operation2 = new ManageDataOperation.Builder("key", "value".getBytes()).build(); Operation[] operations = new Operation[]{operation1, operation2}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(server); transaction.sign(masterClient); @@ -2610,16 +2549,12 @@ public void testVerifyChallengeTransactionInvalidAdditionalOpsOfOtherTypes() thr .setSourceAccount(server.getAccountId()) .build(); Operation[] operations = new Operation[]{operation1, operation2}; - Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), - sourceAccount.getAccountId(), - 100 * operations.length, - sourceAccount.getIncrementedSequenceNumber(), - operations, - Memo.none(), - timeBounds, - network - ); + Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) + .setBaseFee(100 * operations.length) + .addOperations(Arrays.asList(operations)) + .addMemo(Memo.none()) + .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .build(); transaction.sign(server); transaction.sign(masterClient); diff --git a/src/test/java/org/stellar/sdk/ServerTest.java b/src/test/java/org/stellar/sdk/ServerTest.java index c0a3a6d0a..bf0f6536d 100644 --- a/src/test/java/org/stellar/sdk/ServerTest.java +++ b/src/test/java/org/stellar/sdk/ServerTest.java @@ -18,7 +18,12 @@ import java.net.URISyntaxException; import java.util.concurrent.TimeUnit; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class ServerTest { private final String publicRootResponse = "{\n" + @@ -172,11 +177,11 @@ Transaction buildTransaction(Network network) throws IOException { KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); Account account = new Account(source.getAccountId(), 2908908335136768L); - Transaction.Builder builder = new Transaction.Builder(AccountConverter.enableMuxed(), account, network) + TransactionBuilder builder = new TransactionBuilder(AccountConverter.enableMuxed(), account, network) .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) .addMemo(Memo.text("Hello world!")) .setBaseFee(Transaction.MIN_BASE_FEE) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE); + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE); assertEquals(1, builder.getOperationsCount()); Transaction transaction = builder.build(); @@ -367,12 +372,12 @@ public void testCheckMemoRequiredWithMemo() throws IOException, AccountRequiresM KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY"); Account account = new Account(source.getAccountId(), 1L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.PUBLIC) + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.PUBLIC) .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_A, new AssetTypeNative(), "10").build()) .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_B, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_C, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_D).build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .addMemo(new MemoText("Hello, Stellar.")) .setBaseFee(100) .build(); @@ -391,12 +396,12 @@ public void testCheckMemoRequiredWithMemoIdAddress() throws IOException, Account KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY"); Account account = new Account(source.getAccountId(), 1L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.PUBLIC) + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.PUBLIC) .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_MEMO_ID, new AssetTypeNative(), "10").build()) .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_ID, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_ID, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_MEMO_ID).build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .setBaseFee(100) .build(); @@ -415,12 +420,12 @@ public void testCheckMemoRequiredWithSkipCheck() throws IOException, AccountRequ KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY"); Account account = new Account(source.getAccountId(), 1L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.PUBLIC) + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.PUBLIC) .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_A, new AssetTypeNative(), "10").build()) .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -438,12 +443,12 @@ public void testCheckMemoRequiredWithPaymentOperationNoMemo() throws IOException KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY"); Account account = new Account(source.getAccountId(), 1L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.PUBLIC) + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.PUBLIC) .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_A, new AssetTypeNative(), "10").build()) .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -476,12 +481,12 @@ public void testCheckMemoRequiredWithPathPaymentStrictReceiveOperationNoMemo() t KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY"); Account account = new Account(source.getAccountId(), 1L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.PUBLIC) + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.PUBLIC) .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeNative(), "10").build()) .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_B, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -514,12 +519,12 @@ public void testCheckMemoRequiredWithPathPaymentStrictSendOperationNoMemo() thro KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY"); Account account = new Account(source.getAccountId(), 1L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.PUBLIC) + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.PUBLIC) .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeNative(), "10").build()) .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_C, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -552,12 +557,12 @@ public void testCheckMemoRequiredWithAccountMergeOperationNoMemo() throws IOExce KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY"); Account account = new Account(source.getAccountId(), 1L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.PUBLIC) + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.PUBLIC) .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeNative(), "10").build()) .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_D).build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -590,12 +595,12 @@ public void testCheckMemoRequiredTwoOperationsWithSameDestination() throws IOExc KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY"); Account account = new Account(source.getAccountId(), 1L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.PUBLIC) + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.PUBLIC) .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeNative(), "10").build()) .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_C, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_D).build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -628,13 +633,13 @@ public void testCheckMemoRequiredNoDestinationOperation() throws IOException { KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY"); Account account = new Account(source.getAccountId(), 1L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.PUBLIC) + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.PUBLIC) .addOperation(new ManageDataOperation.Builder("Hello", "Stellar".getBytes()).build()) .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_A, new AssetTypeNative(), "10").build()) .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_A, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_C, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -667,12 +672,12 @@ public void testCheckMemoRequiredAccountNotFound() throws IOException, AccountRe KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY"); Account account = new Account(source.getAccountId(), 1L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.PUBLIC) + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.PUBLIC) .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_NO_FOUND, new AssetTypeNative(), "10").build()) .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_FOUND, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_FOUND, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_FOUND).build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -690,14 +695,14 @@ public void testCheckMemoRequiredFetchAccountError() throws IOException, Account KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY"); Account account = new Account(source.getAccountId(), 1L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.PUBLIC) + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.PUBLIC) .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_FETCH_ERROR, new AssetTypeNative(), "10").build()) .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_A, new AssetTypeNative(), "10").build()) .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_B, new AssetTypeNative(), "10").build()) .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_C, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_D, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_D).build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); diff --git a/src/test/java/org/stellar/sdk/TransactionBuilderTest.java b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java new file mode 100644 index 000000000..113a5702a --- /dev/null +++ b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java @@ -0,0 +1,489 @@ +package org.stellar.sdk; + +import com.google.common.io.BaseEncoding; +import org.junit.Test; +import org.stellar.sdk.xdr.Duration; +import org.stellar.sdk.xdr.Int64; +import org.stellar.sdk.xdr.LedgerBounds; +import org.stellar.sdk.xdr.PreconditionsV2; +import org.stellar.sdk.xdr.PublicKeyType; +import org.stellar.sdk.xdr.SequenceNumber; +import org.stellar.sdk.xdr.SignerKey; +import org.stellar.sdk.xdr.SignerKeyType; +import org.stellar.sdk.xdr.Uint256; +import org.stellar.sdk.xdr.Uint32; +import org.stellar.sdk.xdr.XdrDataInputStream; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class TransactionBuilderTest { + + @Test + public void testMissingOperationFee() { + long sequenceNumber = 2908908335136768L; + Account account = new Account("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR", sequenceNumber); + try { + new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR", "2000").build()) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .build(); + fail("expected RuntimeException"); + } catch (RuntimeException e) { + // expected + } + } + + @Test + public void testBuilderSuccessTestnet() throws FormatException { + // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); + + long sequenceNumber = 2908908335136768L; + Account account = new Account(source.getAccountId(), sequenceNumber); + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + + transaction.sign(source); + + assertEquals(transaction.getSourceAccount(), source.getAccountId()); + assertEquals(transaction.getSequenceNumber(), sequenceNumber + 1); + assertEquals(transaction.getFee(), 100); + + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); + + assertEquals(transaction, transaction2); + } + + @Test + public void testBuilderMemoText() throws FormatException { + // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); + + Account account = new Account(source.getAccountId(), 2908908335136768L); + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) + .addMemo(Memo.text("Hello world!")) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + + transaction.sign(source); + + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); + + assertEquals(transaction, transaction2); + } + + @Test + public void testBuilderTimeBounds() throws FormatException, IOException { + // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); + + Account account = new Account(source.getAccountId(), 2908908335136768L); + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) + .addTimeBounds(new TimeBounds(42, 1337)) + .addMemo(Memo.hash("abcdef")) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + + transaction.sign(source); + + // Convert transaction to binary XDR and back again to make sure timebounds are correctly de/serialized. + XdrDataInputStream is = new XdrDataInputStream( + new ByteArrayInputStream( + javax.xml.bind.DatatypeConverter.parseBase64Binary(transaction.toEnvelopeXdrBase64()) + ) + ); + org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); + + assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMinTime().getTimePoint().getUint64().longValue(), 42); + assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMaxTime().getTimePoint().getUint64().longValue(), 1337); + + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); + + assertEquals(transaction, transaction2); + } + + @Test + public void testBuilderBaseFee() throws FormatException { + // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); + + Account account = new Account(source.getAccountId(), 2908908335136768L); + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) + .setBaseFee(200) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .build(); + + transaction.sign(source); + + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); + + assertEquals(transaction, transaction2); + } + + @Test + public void testBuilderBaseFeeThrows() throws FormatException { + // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + + Account account = new Account(source.getAccountId(), 2908908335136768L); + TransactionBuilder builder = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET); + try { + builder.setBaseFee(99); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + } + + @Test + public void testBuilderTimebounds() throws IOException { + Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .addTimeBounds(new TimeBounds(42, 1337)) + .addMemo(Memo.hash("abcdef")) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + + assertEquals(42, transaction.getTimeBounds().getMinTime()); + assertEquals(1337, transaction.getTimeBounds().getMaxTime()); + } + + @Test + public void testBuilderRequiresTimeoutOrTimeBounds() throws IOException { + Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + try { + new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .addMemo(Memo.hash("abcdef")) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + fail(); + } catch (FormatException ignored) {} + } + + + @Test + public void testBuilderTimeoutNegative() throws IOException { + Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + try { + new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .addMemo(Memo.hash("abcdef")) + .setTimeout(-1) + .build(); + fail(); + } catch (RuntimeException exception) { + assertTrue(exception.getMessage().contains("timeout cannot be negative")); + assertEquals(new Long(2908908335136768L), account.getSequenceNumber()); + } + } + + @Test + public void testBuilderTimeout() throws IOException { + Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + long currentUnix = System.currentTimeMillis() / 1000L; + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .setTimeout(10) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + + assertEquals(0, transaction.getTimeBounds().getMinTime()); + assertTrue(currentUnix + 10 <= transaction.getTimeBounds().getMaxTime()); + } + + @Test + public void testBuilderSetsLedgerBounds() throws IOException { + Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + PreconditionsV2.Builder preconditionsV2 = new PreconditionsV2.Builder(); + + preconditionsV2.timeBounds(TransactionBuilder.buildTimeBounds(0, TransactionBuilder.TIMEOUT_INFINITE)); + preconditionsV2.ledgerBounds(new LedgerBounds.Builder().minLedger(new Uint32(1)).maxLedger(new Uint32(2)).build()); + + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .addPreconditions(preconditionsV2.build()) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + + assertEquals(1, transaction.getPreconditions().getLedgerBounds().getMinLedger().getUint32().intValue()); + assertEquals(2, transaction.getPreconditions().getLedgerBounds().getMaxLedger().getUint32().intValue()); + } + + @Test + public void testBuilderSetsMinSeqNum() throws IOException { + Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + PreconditionsV2.Builder preconditionsV2 = new PreconditionsV2.Builder(); + + SequenceNumber seqNum = new SequenceNumber(); + seqNum.setSequenceNumber(new Int64(5L)); + preconditionsV2.timeBounds(TransactionBuilder.buildTimeBounds(0, TransactionBuilder.TIMEOUT_INFINITE)); + preconditionsV2.minSeqNum(seqNum); + + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .addPreconditions(preconditionsV2.build()) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + + assertEquals(5, transaction.getPreconditions().getMinSeqNum().getSequenceNumber().getInt64().longValue()); + } + + @Test + public void testBuilderSetsMinSeqAge() throws IOException { + Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + PreconditionsV2.Builder preconditionsV2 = new PreconditionsV2.Builder(); + + Duration duration = new Duration(); + duration.setDuration(new Int64(5L)); + preconditionsV2.timeBounds(TransactionBuilder.buildTimeBounds(0, TransactionBuilder.TIMEOUT_INFINITE)); + preconditionsV2.minSeqAge(duration); + + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .addPreconditions(preconditionsV2.build()) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + + assertEquals(5, transaction.getPreconditions().getMinSeqAge().getDuration().getInt64().longValue()); + } + + @Test + public void testBuilderSetsMinSeqLedgerGap() throws IOException { + Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + PreconditionsV2.Builder preconditionsV2 = new PreconditionsV2.Builder(); + + preconditionsV2.timeBounds(TransactionBuilder.buildTimeBounds(0, TransactionBuilder.TIMEOUT_INFINITE)); + preconditionsV2.minSeqLedgerGap(new Uint32(5)); + + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .addPreconditions(preconditionsV2.build()) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + + assertEquals(5, transaction.getPreconditions().getMinSeqLedgerGap().getUint32().longValue()); + } + + @Test + public void testBuilderExtraSigners() throws IOException { + Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + PreconditionsV2.Builder preconditionsV2 = new PreconditionsV2.Builder(); + + String accountStrKey = "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"; + byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".toUpperCase()); + + SignerKey signerKey = new SignerKey.Builder() + .discriminant(SignerKeyType.SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD) + .ed25519SignedPayload(new SignerKey.SignerKeyEd25519SignedPayload.Builder() + .payload(payload) + .ed25519(new Uint256(StrKey.decodeStellarAccountId(accountStrKey))) + .build()) + .build(); + + preconditionsV2.timeBounds(TransactionBuilder.buildTimeBounds(0, TransactionBuilder.TIMEOUT_INFINITE)); + preconditionsV2.extraSigners(new SignerKey[]{signerKey}); + + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .addPreconditions(preconditionsV2.build()) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + + assertEquals(SignerKeyType.SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD, transaction.getPreconditions().getExtraSigners()[0].getDiscriminant()); + assertArrayEquals(payload, transaction.getPreconditions().getExtraSigners()[0].getEd25519SignedPayload().getPayload()); + } + + @Test + public void testBuilderFailsWhenTooManyExtraSigners() throws IOException { + Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + + try { + new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .addPreconditions(new PreconditionsV2.Builder().extraSigners(new SignerKey[3]).build()) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + fail(); + } catch (FormatException ignored){} + } + + @Test + public void testBuilderUsesCustomSequence() throws IOException { + Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .addPreconditions(new PreconditionsV2.Builder() + .timeBounds(TransactionBuilder.buildTimeBounds(0, TransactionBuilder.TIMEOUT_INFINITE)).build()) + .addSequenceNumberStrategy(new SequenceNumberStrategy() { + @Override + public long getSequenceNumber(TransactionBuilderAccount account) { + return 5; + } + + @Override + public void setSequenceNumber(long newSequenceNumber, TransactionBuilderAccount account) { + account.setSequenceNumber(newSequenceNumber); + } + }) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + + // check that the created tx has the sequence number which custom sequence handler sets, + // rather than using default of sourceAccount.seqNum + 1 + assertEquals(5, transaction.getSequenceNumber()); + + // check that the sourceAccount.seqNum gets updated to what the custom sequence handler sets, + // rather than the default of sourceAccount.seqNum + 1 + assertEquals(5, account.getSequenceNumber().longValue()); + } + + @Test + public void testBuilderFailsWhenSettingTimeoutAndMaxTimeAlreadySet() throws IOException { + Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + try { + new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .addTimeBounds(new TimeBounds(42, 1337)) + .setTimeout(10) + .build(); + fail(); + } catch (RuntimeException exception) { + assertTrue(exception.getMessage().contains("TimeBounds.max_time has been already set")); + assertEquals(new Long(2908908335136768L), account.getSequenceNumber()); + } + } + + @Test + public void testBuilderTimeoutAndMaxTimeNotSet() throws IOException { + Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + long currentUnix = System.currentTimeMillis() / 1000L; + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .addTimeBounds(new TimeBounds(42, 0)) + .setTimeout(10) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + + assertEquals(42, transaction.getTimeBounds().getMinTime()); + assertTrue(currentUnix + 10 <= transaction.getTimeBounds().getMaxTime()); + } + + @Test + public void testBuilderInfinteTimeoutAndMaxTimeNotSet() throws FormatException, IOException { + // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); + + Account account = new Account(source.getAccountId(), 2908908335136768L); + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) + .addTimeBounds(new TimeBounds(42, 0)) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .addMemo(Memo.hash("abcdef")) + .setBaseFee(100) + .build(); + + transaction.sign(source); + + // Convert transaction to binary XDR and back again to make sure timebounds are correctly de/serialized. + XdrDataInputStream is = new XdrDataInputStream(new ByteArrayInputStream(BaseEncoding.base64().decode(transaction.toEnvelopeXdrBase64()))); + org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); + + assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMinTime().getTimePoint().getUint64().longValue(), 42); + assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMaxTime().getTimePoint().getUint64().longValue(), 0); + } + + @Test + public void testBuilderSuccessPublic() throws FormatException, IOException { + + // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); + + Account account = new Account(source.getAccountId(), 2908908335136768L); + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.PUBLIC) + .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + + transaction.sign(source); + + Transaction decodedTransaction = (Transaction) Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdrBase64(), Network.PUBLIC); + assertEquals(decodedTransaction, transaction); + } + + @Test + public void testNoOperations() throws FormatException, IOException { + // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + + Account account = new Account(source.getAccountId(), 2908908335136768L); + try { + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + fail(); + } catch (RuntimeException exception) { + assertTrue(exception.getMessage().contains("At least one operation required")); + assertEquals(new Long(2908908335136768L), account.getSequenceNumber()); + } + } + + @Test + public void testTryingToAddMemoTwice() throws FormatException, IOException { + // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); + + try { + Account account = new Account(source.getAccountId(), 2908908335136768L); + new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) + .addMemo(Memo.none()) + .addMemo(Memo.none()); + fail(); + } catch (RuntimeException exception) { + assertTrue(exception.getMessage().contains("Memo has been already added.")); + } + } + + @Test + public void testNoNetworkSet() throws FormatException { + // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); + + Account account = new Account(source.getAccountId(), 2908908335136768L); + try { + new TransactionBuilder(AccountConverter.enableMuxed(), account, null) + .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) + .addMemo(Memo.none()) + .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .build(); + fail(); + } catch (NullPointerException e) { + assertTrue(e.getMessage().contains("Network cannot be null")); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/stellar/sdk/TransactionTest.java b/src/test/java/org/stellar/sdk/TransactionTest.java index 74bba77f8..aedfa41fc 100644 --- a/src/test/java/org/stellar/sdk/TransactionTest.java +++ b/src/test/java/org/stellar/sdk/TransactionTest.java @@ -1,7 +1,9 @@ package org.stellar.sdk; +import com.google.common.io.BaseEncoding; import org.junit.Test; import org.stellar.sdk.xdr.EnvelopeType; +import org.stellar.sdk.xdr.PreconditionsV2; import org.stellar.sdk.xdr.XdrDataInputStream; import java.io.ByteArrayInputStream; @@ -12,360 +14,39 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; public class TransactionTest { @Test - public void testMissingOperationFee() { - long sequenceNumber = 2908908335136768L; - Account account = new Account("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR", sequenceNumber); - try { - new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR", "2000").build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) - .build(); - fail("expected RuntimeException"); - } catch (RuntimeException e) { - // expected - } - } - - @Test - public void testBuilderSuccessTestnet() throws FormatException { - // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 - KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); - KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); - - long sequenceNumber = 2908908335136768L; - Account account = new Account(source.getAccountId(), sequenceNumber); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) - .setBaseFee(Transaction.MIN_BASE_FEE) - .build(); - - transaction.sign(source); - - assertEquals( - "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAO3gUmG83C+VCqO6FztuMtXJF/l7grZA7MjRzqdZ9W8QAAAABKgXyAAAAAAAAAAAAbaHlWIAAABAy5IvTou9NDetC6PIFJhBR2yr2BuEEql4iyLfU9K7tjuQaYVZf40fbWLRwA/lHg2IYFzYMFMakxLtVrpLxMmHAw==", - transaction.toEnvelopeXdrBase64()); - - assertEquals(transaction.getSourceAccount(), source.getAccountId()); - assertEquals(transaction.getSequenceNumber(), sequenceNumber + 1); - assertEquals(transaction.getFee(), 100); - - Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); - - assertEquals(transaction.getSourceAccount(), transaction2.getSourceAccount()); - assertEquals(transaction.getSequenceNumber(), transaction2.getSequenceNumber()); - assertEquals(transaction.getFee(), transaction2.getFee()); - assertEquals( - ((CreateAccountOperation) transaction.getOperations()[0]).getStartingBalance(), - ((CreateAccountOperation) transaction2.getOperations()[0]).getStartingBalance() - ); - - assertEquals(transaction.getSignatures(), transaction2.getSignatures()); - } - - @Test - public void testBuilderMemoText() throws FormatException { - // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 - KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); - KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); - - Account account = new Account(source.getAccountId(), 2908908335136768L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) - .addMemo(Memo.text("Hello world!")) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) - .setBaseFee(Transaction.MIN_BASE_FEE) - .build(); - - transaction.sign(source); - - assertEquals( - "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAAAAAABAAAADEhlbGxvIHdvcmxkIQAAAAEAAAAAAAAAAAAAAADt4FJhvNwvlQqjuhc7bjLVyRf5e4K2QOzI0c6nWfVvEAAAAASoF8gAAAAAAAAAAAG2h5ViAAAAQMc6HwYaGsrlJ8/LdE9VDVq04JifpQofSmnjhrtqaTTs/VBsNGmxi4b/vaFkLLLWh8emI8FsS/vBgb8AVFVkZQU=", - transaction.toEnvelopeXdrBase64()); - - Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); - - assertEquals(transaction.getSourceAccount(), transaction2.getSourceAccount()); - assertEquals(transaction.getSequenceNumber(), transaction2.getSequenceNumber()); - assertEquals(transaction.getMemo(), transaction2.getMemo()); - assertEquals(transaction.getFee(), transaction2.getFee()); - assertEquals( - ((CreateAccountOperation) transaction.getOperations()[0]).getStartingBalance(), - ((CreateAccountOperation) transaction2.getOperations()[0]).getStartingBalance() - ); - } - - @Test - public void testBuilderTimeBounds() throws FormatException, IOException { - // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 - KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); - KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); - - Account account = new Account(source.getAccountId(), 2908908335136768L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) - .addTimeBounds(new TimeBounds(42, 1337)) - .addMemo(Memo.hash("abcdef")) - .setBaseFee(Transaction.MIN_BASE_FEE) - .build(); - - transaction.sign(source); - - // Convert transaction to binary XDR and back again to make sure timebounds are correctly de/serialized. - XdrDataInputStream is = new XdrDataInputStream( - new ByteArrayInputStream( - javax.xml.bind.DatatypeConverter.parseBase64Binary(transaction.toEnvelopeXdrBase64()) - ) - ); - org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); - - assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMinTime().getTimePoint().getUint64().longValue(), 42); - assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMaxTime().getTimePoint().getUint64().longValue(), 1337); - - Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); - - assertEquals(transaction.getSourceAccount(), transaction2.getSourceAccount()); - assertEquals(transaction.getSequenceNumber(), transaction2.getSequenceNumber()); - assertEquals(transaction.getMemo(), transaction2.getMemo()); - assertEquals(transaction.getTimeBounds(), transaction2.getTimeBounds()); - assertEquals(transaction.getFee(), transaction2.getFee()); - assertEquals( - ((CreateAccountOperation) transaction.getOperations()[0]).getStartingBalance(), - ((CreateAccountOperation) transaction2.getOperations()[0]).getStartingBalance() - ); - } - - @Test - public void testBuilderBaseFee() throws FormatException { - // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 - KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); - KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); - - Account account = new Account(source.getAccountId(), 2908908335136768L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) - .setBaseFee(200) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) - .build(); - - transaction.sign(source); - - assertEquals( - "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAMgAClWjAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAO3gUmG83C+VCqO6FztuMtXJF/l7grZA7MjRzqdZ9W8QAAAABKgXyAAAAAAAAAAAAbaHlWIAAABA9TG3dKKLtLHzRUbsbEqr68CfUc800p1/LE5pWzCnFdFdypdXgyqHqw/sWdaTUMDiWawBtsmqV8oOtD0Hw1HDDQ==", - transaction.toEnvelopeXdrBase64()); - - Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); - - assertEquals(transaction.getSourceAccount(), transaction2.getSourceAccount()); - assertEquals(transaction.getSequenceNumber(), transaction2.getSequenceNumber()); - assertEquals(transaction.getFee(), transaction2.getFee()); - assertEquals( - ((CreateAccountOperation) transaction.getOperations()[0]).getStartingBalance(), - ((CreateAccountOperation) transaction2.getOperations()[0]).getStartingBalance() - ); - } - - @Test - public void testBuilderBaseFeeThrows() throws FormatException { - // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 - KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); - - Account account = new Account(source.getAccountId(), 2908908335136768L); - Transaction.Builder builder = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET); - try { - builder.setBaseFee(99); - fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - // expected - } - } - - @Test - public void testBuilderWithTimeBoundsButNoTimeout() throws IOException { - Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); - new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) - .addTimeBounds(new TimeBounds(42, 1337)) - .addMemo(Memo.hash("abcdef")) - .setBaseFee(Transaction.MIN_BASE_FEE) - .build(); - } - - @Test - public void testBuilderRequiresTimeoutOrTimeBounds() throws IOException { - Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); - try { - new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) - .addMemo(Memo.hash("abcdef")) - .setBaseFee(Transaction.MIN_BASE_FEE) - .build(); - fail(); - } catch (RuntimeException exception) { - assertEquals( - exception.getMessage(), - "TimeBounds has to be set or you must call setTimeout(TIMEOUT_INFINITE)." - ); - } - } - - - @Test - public void testBuilderTimeoutNegative() throws IOException { - Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); - try { - new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) - .addMemo(Memo.hash("abcdef")) - .setTimeout(-1) - .build(); - fail(); - } catch (RuntimeException exception) { - assertTrue(exception.getMessage().contains("timeout cannot be negative")); - assertEquals(new Long(2908908335136768L), account.getSequenceNumber()); - } - } - - @Test - public void testBuilderTimeoutSetsTimeBounds() throws IOException { - Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) - .setTimeout(10) - .setBaseFee(Transaction.MIN_BASE_FEE) - .build(); - - assertEquals(0, transaction.getTimeBounds().getMinTime()); - long currentUnix = System.currentTimeMillis() / 1000L; - assertEquals(currentUnix + 10, transaction.getTimeBounds().getMaxTime()); - } - - @Test - public void testBuilderFailsWhenSettingTimeoutAndMaxTimeAlreadySet() throws IOException { - Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); - try { - new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) - .addTimeBounds(new TimeBounds(42, 1337)) - .setTimeout(10) - .build(); - fail(); - } catch (RuntimeException exception) { - assertTrue(exception.getMessage().contains("TimeBounds.max_time has been already set")); - assertEquals(new Long(2908908335136768L), account.getSequenceNumber()); - } - } - - @Test - public void testBuilderFailsWhenSettingTimeoutAndMaxTimeNotSet() throws IOException { - Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) - .addTimeBounds(new TimeBounds(42, 0)) - .setTimeout(10) - .setBaseFee(Transaction.MIN_BASE_FEE) - .build(); - - assertEquals(42, transaction.getTimeBounds().getMinTime()); - // Should add max_time - long currentUnix = System.currentTimeMillis() / 1000L; - assertEquals(currentUnix + 10, transaction.getTimeBounds().getMaxTime()); - } - - @Test - public void testBuilderTimeBoundsNoMaxTime() throws FormatException, IOException { - // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 - KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); - KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); - - Account account = new Account(source.getAccountId(), 2908908335136768L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) - .addTimeBounds(new TimeBounds(42, 0)) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) - .addMemo(Memo.hash("abcdef")) - .setBaseFee(100) - .build(); - - transaction.sign(source); - - // Convert transaction to binary XDR and back again to make sure timebounds are correctly de/serialized. - XdrDataInputStream is = new XdrDataInputStream( - new ByteArrayInputStream( - javax.xml.bind.DatatypeConverter.parseBase64Binary(transaction.toEnvelopeXdrBase64()) - ) - ); - org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); - - assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMinTime().getTimePoint().getUint64().longValue(), 42); - assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMaxTime().getTimePoint().getUint64().longValue(), 0); - } - - @Test - public void testBuilderSuccessPublic() throws FormatException, IOException { + public void testParseV0Transaction() throws FormatException, IOException { // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); Account account = new Account(source.getAccountId(), 2908908335136768L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.PUBLIC) - .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) - .setBaseFee(Transaction.MIN_BASE_FEE) - .build(); - - transaction.sign(source); - XdrDataInputStream is = new XdrDataInputStream( - new ByteArrayInputStream( - javax.xml.bind.DatatypeConverter.parseBase64Binary(transaction.toEnvelopeXdrBase64()) - ) + Transaction transaction = new Transaction( + AccountConverter.disableMuxed(), + account.getAccountId(), + Transaction.MIN_BASE_FEE, + account.getIncrementedSequenceNumber(), + new org.stellar.sdk.Operation[]{new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()}, + Memo.none(), + new PreconditionsV2.Builder().timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE).toXdr()).build(), + Network.PUBLIC ); - org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); - assertEquals(EnvelopeType.ENVELOPE_TYPE_TX, decodedTransaction.getDiscriminant()); - assertEquals( - "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAO3gUmG83C+VCqO6FztuMtXJF/l7grZA7MjRzqdZ9W8QAAAABKgXyAAAAAAAAAAAAbaHlWIAAABA830eT4ERYpuVnb6vSJnWTVhgcQVsGJED2vZeYogXbTy8wGb+qo/ojn0q6op7KBdF6y5MHSHGUFbad1UR4UaFBA==", - transaction.toEnvelopeXdrBase64()); - } - - @Test - public void testParseV0Transaction() throws FormatException, IOException { - - // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 - KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); - KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); - - Account account = new Account(source.getAccountId(), 2908908335136768L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.PUBLIC) - .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) - .setBaseFee(Transaction.MIN_BASE_FEE) - .build(); transaction.setEnvelopeType(EnvelopeType.ENVELOPE_TYPE_TX_V0); transaction.sign(source); - XdrDataInputStream is = new XdrDataInputStream( - new ByteArrayInputStream( - javax.xml.bind.DatatypeConverter.parseBase64Binary(transaction.toEnvelopeXdrBase64()) - ) - ); + XdrDataInputStream is = new XdrDataInputStream(new ByteArrayInputStream(BaseEncoding.base64().decode(transaction.toEnvelopeXdrBase64()))); org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); assertEquals(EnvelopeType.ENVELOPE_TYPE_TX_V0, decodedTransaction.getDiscriminant()); Transaction parsed = (Transaction) Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdrBase64(), Network.PUBLIC); assertTrue(parsed.equals(transaction)); assertEquals(EnvelopeType.ENVELOPE_TYPE_TX_V0, parsed.toEnvelopeXdr().getDiscriminant()); - - assertEquals( - "AAAAAF7FIiDToW1fOYUFBC0dmyufJbFTOa2GQESGz+S2h5ViAAAAZAAKVaMAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAA7eBSYbzcL5UKo7oXO24y1ckX+XuCtkDsyNHOp1n1bxAAAAAEqBfIAAAAAAAAAAABtoeVYgAAAEDzfR5PgRFim5Wdvq9ImdZNWGBxBWwYkQPa9l5iiBdtPLzAZv6qj+iOfSrqinsoF0XrLkwdIcZQVtp3VRHhRoUE", - transaction.toEnvelopeXdrBase64()); assertEquals(transaction.toEnvelopeXdrBase64(), parsed.toEnvelopeXdrBase64()); } @@ -375,11 +56,17 @@ public void testSha256HashSigning() throws FormatException { KeyPair destination = KeyPair.fromAccountId("GDJJRRMBK4IWLEPJGIE6SXD2LP7REGZODU7WDC3I2D6MR37F4XSHBKX2"); Account account = new Account(source.getAccountId(), 0L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.PUBLIC) - .addOperation(new PaymentOperation.Builder(destination.getAccountId(), new AssetTypeNative(), "2000").build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) - .setBaseFee(Transaction.MIN_BASE_FEE) - .build(); + + Transaction transaction = new Transaction( + AccountConverter.disableMuxed(), + account.getAccountId(), + Transaction.MIN_BASE_FEE, + account.getIncrementedSequenceNumber(), + new org.stellar.sdk.Operation[]{new PaymentOperation.Builder(destination.getAccountId(), new AssetTypeNative(), "2000").build()}, + Memo.none(), + new PreconditionsV2.Builder().timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE).toXdr()).build(), + Network.PUBLIC + ); byte[] preimage = new byte[64]; new SecureRandom().nextBytes(preimage); @@ -401,70 +88,19 @@ public void testToBase64EnvelopeXdrBuilderNoSignatures() throws FormatException, KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); Account account = new Account(source.getAccountId(), 2908908335136768L); - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) - .setBaseFee(Transaction.MIN_BASE_FEE) - .build(); - assertEquals( - "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAO3gUmG83C+VCqO6FztuMtXJF/l7grZA7MjRzqdZ9W8QAAAABKgXyAAAAAAAAAAAAA==", - transaction.toEnvelopeXdrBase64() + Transaction transaction = new Transaction( + AccountConverter.disableMuxed(), + account.getAccountId(), + Transaction.MIN_BASE_FEE, + account.getIncrementedSequenceNumber(), + new org.stellar.sdk.Operation[]{new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()}, + Memo.none(), + new PreconditionsV2.Builder().timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE).toXdr()).build(), + Network.TESTNET ); - } - @Test - public void testNoOperations() throws FormatException, IOException { - // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 - KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); - - Account account = new Account(source.getAccountId(), 2908908335136768L); - try { - Transaction transaction = new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) - .setBaseFee(Transaction.MIN_BASE_FEE) - .build(); - fail(); - } catch (RuntimeException exception) { - assertTrue(exception.getMessage().contains("At least one operation required")); - assertEquals(new Long(2908908335136768L), account.getSequenceNumber()); - } - } - - @Test - public void testTryingToAddMemoTwice() throws FormatException, IOException { - // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 - KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); - KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); - - try { - Account account = new Account(source.getAccountId(), 2908908335136768L); - new Transaction.Builder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) - .addMemo(Memo.none()) - .addMemo(Memo.none()); - fail(); - } catch (RuntimeException exception) { - assertTrue(exception.getMessage().contains("Memo has been already added.")); - } - } - - @Test - public void testNoNetworkSet() throws FormatException { - // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 - KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); - KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); - - Account account = new Account(source.getAccountId(), 2908908335136768L); - try { - new Transaction.Builder(AccountConverter.enableMuxed(), account, null) - .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) - .addMemo(Memo.none()) - .setTimeout(Transaction.Builder.TIMEOUT_INFINITE) - .build(); - fail(); - } catch (NullPointerException e) { - assertTrue(e.getMessage().contains("Network cannot be null")); - } + Transaction parsed = (Transaction) Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdrBase64(), Network.TESTNET); + assertEquals(parsed, transaction); } } \ No newline at end of file From c61a5d531371cdc78bb98acc0cb4755e14bcc3ed Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Thu, 24 Mar 2022 14:37:52 -0700 Subject: [PATCH 09/29] #410: consolidate validation of signed payload aspects into SignedPayloadSigner, pr feedback --- .../org/stellar/sdk/SignedPayloadSigner.java | 12 +++++++- src/main/java/org/stellar/sdk/Signer.java | 4 --- src/main/java/org/stellar/sdk/StrKey.java | 8 ------ .../stellar/sdk/SignedPayloadSignerTest.java | 28 +++++++++++++++++++ src/test/java/org/stellar/sdk/SignerTest.java | 14 ---------- src/test/java/org/stellar/sdk/StrKeyTest.java | 10 ------- 6 files changed, 39 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/stellar/sdk/SignedPayloadSigner.java b/src/main/java/org/stellar/sdk/SignedPayloadSigner.java index a629f1544..b5fcc4035 100644 --- a/src/main/java/org/stellar/sdk/SignedPayloadSigner.java +++ b/src/main/java/org/stellar/sdk/SignedPayloadSigner.java @@ -7,11 +7,12 @@ import static com.google.common.base.Preconditions.checkNotNull; - /** * Data model for the signed payload signer */ public class SignedPayloadSigner { + public static final int SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH = 64; + private AccountID signerAccountId; private byte[] payload; @@ -22,6 +23,15 @@ public class SignedPayloadSigner { * @param payload - the raw payload for a signed payload */ public SignedPayloadSigner(AccountID signerAccountId, byte[] payload) { + checkNotNull(payload, "payload cannot be null"); + checkNotNull(signerAccountId, "accountId cannot be null"); + if (payload.length > SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH) { + throw new IllegalArgumentException("invalid payload length, must be less than " + SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH); + } + if (signerAccountId.getAccountID().getDiscriminant() == null || + !signerAccountId.getAccountID().getDiscriminant().equals(PublicKeyType.PUBLIC_KEY_TYPE_ED25519)) { + throw new IllegalArgumentException("invalid payload signer, only ED25519 public key accounts are supported currently"); + } this.signerAccountId = checkNotNull(signerAccountId); this.payload = checkNotNull(payload); } diff --git a/src/main/java/org/stellar/sdk/Signer.java b/src/main/java/org/stellar/sdk/Signer.java index 3966730bb..b5fd69b21 100644 --- a/src/main/java/org/stellar/sdk/Signer.java +++ b/src/main/java/org/stellar/sdk/Signer.java @@ -11,8 +11,6 @@ * Signer is a helper class that creates {@link org.stellar.sdk.xdr.SignerKey} objects. */ public class Signer { - public static final int SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH = 64; - /** * Create ed25519PublicKey {@link org.stellar.sdk.xdr.SignerKey} from * a {@link org.stellar.sdk.KeyPair} @@ -82,8 +80,6 @@ public static SignerKey preAuthTx(byte[] hash) { * @return org.stellar.sdk.xdr.SignerKey */ public static SignerKey signedPayload(SignedPayloadSigner signedPayloadSigner) { - checkNotNull(signedPayloadSigner.getSignerAccountId(), "accountId cannot be null"); - checkArgument(signedPayloadSigner.getPayload().length <= SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH ); SignerKey signerKey = new SignerKey(); SignerKey.SignerKeyEd25519SignedPayload payloadSigner = new SignerKey.SignerKeyEd25519SignedPayload(); diff --git a/src/main/java/org/stellar/sdk/StrKey.java b/src/main/java/org/stellar/sdk/StrKey.java index 688556385..0342cac2e 100644 --- a/src/main/java/org/stellar/sdk/StrKey.java +++ b/src/main/java/org/stellar/sdk/StrKey.java @@ -22,8 +22,6 @@ import java.io.OutputStream; import java.util.Arrays; -import static org.stellar.sdk.Signer.SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH; - class StrKey { public static final int ACCOUNT_ID_ADDRESS_LENGTH = 56; @@ -66,12 +64,6 @@ public static String encodeStellarAccountId(AccountID accountID) { } public static String encodeSignedPayload(SignedPayloadSigner signedPayloadSigner) { - if (signedPayloadSigner.getPayload().length > SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH) { - throw new FormatException("invalid payload length, must be less than " + SIGNED_PAYLOAD_MAX_PAYLOAD_LENGTH); - } - if (!signedPayloadSigner.getSignerAccountId().getAccountID().getDiscriminant().equals(PublicKeyType.PUBLIC_KEY_TYPE_ED25519)) { - throw new FormatException("invalid payload signer, only ED25519 public key accounts are supported currently"); - } try { SignerKey.SignerKeyEd25519SignedPayload xdrPayloadSigner = new SignerKey.SignerKeyEd25519SignedPayload(); xdrPayloadSigner.setPayload(signedPayloadSigner.getPayload()); diff --git a/src/test/java/org/stellar/sdk/SignedPayloadSignerTest.java b/src/test/java/org/stellar/sdk/SignedPayloadSignerTest.java index 9869b3298..ddb539e67 100644 --- a/src/test/java/org/stellar/sdk/SignedPayloadSignerTest.java +++ b/src/test/java/org/stellar/sdk/SignedPayloadSignerTest.java @@ -1,7 +1,13 @@ package org.stellar.sdk; +import com.google.common.io.BaseEncoding; import org.junit.Test; import org.stellar.sdk.xdr.AccountID; +import org.stellar.sdk.xdr.PublicKey; +import org.stellar.sdk.xdr.PublicKeyType; +import org.stellar.sdk.xdr.Uint256; + +import javax.net.ssl.ExtendedSSLSession; import static org.junit.Assert.fail; @@ -15,4 +21,26 @@ public void itFailsWhenAccoutIDIsNull() { fail("should not create when accountid is null"); } catch (NullPointerException ignored){} } + + @Test + public void itFailsWhenPayloadLengthTooBig() { + String accountStrKey = "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"; + byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2001".toUpperCase()); + try { + new SignedPayloadSigner(StrKey.decodeStellarAccountId(accountStrKey), payload); + fail("should not create a payload signer if payload > max length"); + } catch (IllegalArgumentException ignored) {} + + } + + @Test + public void itFailsWhenSignerNotED25519() { + try { + new SignedPayloadSigner(new AccountID( + new PublicKey.Builder() + .ed25519(new Uint256(new byte[]{})).build()), new byte[]{}); + fail("should not create a payload signer if signer wasn't ed25519 type"); + } catch (IllegalArgumentException ignored) {} + + } } diff --git a/src/test/java/org/stellar/sdk/SignerTest.java b/src/test/java/org/stellar/sdk/SignerTest.java index 0fca163dd..d537db128 100644 --- a/src/test/java/org/stellar/sdk/SignerTest.java +++ b/src/test/java/org/stellar/sdk/SignerTest.java @@ -6,7 +6,6 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; public class SignerTest { @@ -21,17 +20,4 @@ public void itCreatesSignedPayloadSigner() { assertArrayEquals(signerKey.getEd25519SignedPayload().getPayload(), payload); assertEquals(signerKey.getEd25519SignedPayload().getEd25519(),signedPayloadSigner.getSignerAccountId().getAccountID().getEd25519()); } - - @Test - public void itFailsWhenInvalidParameters() { - String accountStrKey = "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"; - byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2001".toUpperCase()); - SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner(StrKey.decodeStellarAccountId(accountStrKey), payload); - try { - SignerKey signerKey = Signer.signedPayload(signedPayloadSigner); - fail("should not create a payload signer if payload > max length"); - } catch (IllegalArgumentException ignored) {} - - } - } diff --git a/src/test/java/org/stellar/sdk/StrKeyTest.java b/src/test/java/org/stellar/sdk/StrKeyTest.java index c6a43e352..8c937a2a2 100644 --- a/src/test/java/org/stellar/sdk/StrKeyTest.java +++ b/src/test/java/org/stellar/sdk/StrKeyTest.java @@ -149,16 +149,6 @@ public void testValidSignedPayloadEncode() { assertEquals(encoded, "PA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAOQCAQDAQCQMBYIBEFAWDANBYHRAEISCMKBKFQXDAMRUGY4DUAAAAFGBU"); } - @Test - public void testInvalidSignedPayloadEncode() { - byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2001".toUpperCase()); - SignedPayloadSigner signedPayloadSigner = new SignedPayloadSigner(StrKey.decodeStellarAccountId("GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"), payload); - try { - StrKey.encodeSignedPayload(signedPayloadSigner); - fail("should not encode signed payloads > 64"); - } catch (FormatException ignored){} - } - @Test public void testRoundTripSignedPayloadVersionByte() { byte[] data = rawBytes( From 8ad830ef4cd57df2738970b0c47ce71e41fdcdfc Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Thu, 24 Mar 2022 21:48:52 -0700 Subject: [PATCH 10/29] #411: fixed gradle install to maven local task --- build.gradle | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 7d8ed9a03..c5a2049c7 100644 --- a/build.gradle +++ b/build.gradle @@ -8,20 +8,46 @@ plugins { id "com.github.ben-manes.versions" version "0.42.0" id "project-report" id "maven-publish" + id "java-library" } sourceCompatibility = 1.6 version = '0.32.0' group = 'stellar' +jar.enabled = false -jar { +publishing { + publications { + sdkLibrary(MavenPublication) { publication -> + project.shadow.component(publication) + } + } +} + +artifacts { + // make sure the non shaded jar is not + archives shadowJar +} + +shadowJar { manifest { attributes ( "Implementation-Title" : "stellar-sdk", "Implementation-Version" : project.getVersion() ) } + duplicatesStrategy DuplicatesStrategy.EXCLUDE archiveName 'stellar-sdk.jar' + relocate 'com.','shadow.com.' + relocate 'net.','shadow.net.' + relocate 'javax.annotation', 'shadow.javax.annotation' + relocate 'org.apache','shadow.org.apache' + relocate 'org.jvnet','shadow.org.jvnet' + relocate 'org.codehaus','shadow.org.codehaus' + relocate 'org.threeten','shadow.org.threeten' + relocate 'org.checkerframework','shadow.org.checkerframework' + relocate 'okhttp3','shadow.okhttp3' + relocate 'okio','shadow.okio' } repositories { @@ -45,6 +71,6 @@ dependencies { testImplementation 'javax.xml.bind:jaxb-api:2.3.0' testImplementation group: 'org.junit.vintage', name: 'junit-vintage-engine', version: '4.12.1' } -tasks.named('test') { +tasks.named('test') { task -> useJUnitPlatform() } From 931e10cb642227be2c984ad36c171752d02e9015 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Thu, 24 Mar 2022 22:32:12 -0700 Subject: [PATCH 11/29] 411: fixed shadow jar name on publish --- build.gradle | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index c5a2049c7..63888f135 100644 --- a/build.gradle +++ b/build.gradle @@ -24,11 +24,6 @@ publishing { } } -artifacts { - // make sure the non shaded jar is not - archives shadowJar -} - shadowJar { manifest { attributes ( @@ -37,7 +32,8 @@ shadowJar { ) } duplicatesStrategy DuplicatesStrategy.EXCLUDE - archiveName 'stellar-sdk.jar' + archiveClassifier.set('') + archiveBaseName.set('stellar-sdk') relocate 'com.','shadow.com.' relocate 'net.','shadow.net.' relocate 'javax.annotation', 'shadow.javax.annotation' From d56a3c10fe85e07cf64a00fe7d145b53dd980865 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Fri, 25 Mar 2022 09:30:17 -0700 Subject: [PATCH 12/29] #411: added docs to SequenceNumberStrategy --- CHANGELOG.md | 2 +- .../org/stellar/sdk/FeeBumpTransaction.java | 2 +- .../stellar/sdk/SequenceNumberStrategy.java | 19 ++++++++++++++++--- .../sdk/SequentialSequenceNumberStrategy.java | 2 +- .../org/stellar/sdk/TransactionBuilder.java | 2 +- .../org/stellar/sdk/Sep10ChallengeTest.java | 2 +- .../stellar/sdk/TransactionBuilderTest.java | 2 +- 7 files changed, 22 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e957af13a..ea9e4dff7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ As this project is pre 1.0, breaking changes may happen for minor version bumps. ### Breaking changes -* org.stellar.sdk.Transaction +* org.stellar.sdk.Transaction.Builder * deprecated `addTimeBounds()` use `addPreconditions()` instead * deprecated `setTimeout()` use `addPreconditions()` instead * deprecated `Transaction.Builder` use TransactionBuilder instead diff --git a/src/main/java/org/stellar/sdk/FeeBumpTransaction.java b/src/main/java/org/stellar/sdk/FeeBumpTransaction.java index f77988ab9..8b66ceee7 100644 --- a/src/main/java/org/stellar/sdk/FeeBumpTransaction.java +++ b/src/main/java/org/stellar/sdk/FeeBumpTransaction.java @@ -136,7 +136,7 @@ public long getSequenceNumber(TransactionBuilderAccount account) { } @Override - public void setSequenceNumber(long newSequenceNumber, TransactionBuilderAccount account) { + public void updateSourceAccount(long newSequenceNumber, TransactionBuilderAccount account) { //no-op, account instance is local to this scope, not external, no need to update it. } }) diff --git a/src/main/java/org/stellar/sdk/SequenceNumberStrategy.java b/src/main/java/org/stellar/sdk/SequenceNumberStrategy.java index 0e8fd77c3..3a7dd3258 100644 --- a/src/main/java/org/stellar/sdk/SequenceNumberStrategy.java +++ b/src/main/java/org/stellar/sdk/SequenceNumberStrategy.java @@ -3,11 +3,24 @@ public interface SequenceNumberStrategy { /** - * callback to provide a custom routine to calculate the desired tx sequnce nuymber + * Derives the desired sequence number for a newly built transaction. + * * @param account the source account of transaction. - * @return the sequnce number to apply on transaction. + * @return the sequence number to be applied on new transaction. */ long getSequenceNumber(TransactionBuilderAccount account); - void setSequenceNumber(long newSequenceNumber, TransactionBuilderAccount account); + /** + * Update a given in-memory account instance after a new transaction has been built. + * The Implementation can determine what fields on the account to update, + * however, the account sequence number is most likely account field to update, + * since the new sequence number used on transaction is provided in parameters. + * + * Does not invoke any server updates, this just provides a way to update the in-memory + * instance of an account. + * + * @param newSequenceNumber the sequence number returned from getSequenceNumber + * @param account the account instance in memmory + */ + void updateSourceAccount(long newSequenceNumber, TransactionBuilderAccount account); } diff --git a/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java b/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java index d4c25b1d7..b6cdf521d 100644 --- a/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java +++ b/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java @@ -7,7 +7,7 @@ public long getSequenceNumber(TransactionBuilderAccount account) { } @Override - public void setSequenceNumber(long newSequnceNumber, TransactionBuilderAccount account) { + public void updateSourceAccount(long newSequnceNumber, TransactionBuilderAccount account) { account.incrementSequenceNumber(); } } diff --git a/src/main/java/org/stellar/sdk/TransactionBuilder.java b/src/main/java/org/stellar/sdk/TransactionBuilder.java index 6f249c7db..67dff3e1b 100644 --- a/src/main/java/org/stellar/sdk/TransactionBuilder.java +++ b/src/main/java/org/stellar/sdk/TransactionBuilder.java @@ -242,7 +242,7 @@ public Transaction build() { mNetwork ); // Increment sequence number when there were no exceptions when creating a transaction - sequenceNumberStrategy.setSequenceNumber(sequenceNumber, mSourceAccount); + sequenceNumberStrategy.updateSourceAccount(sequenceNumber, mSourceAccount); return transaction; } diff --git a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java index 6a6c24225..c30c99f86 100644 --- a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java +++ b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java @@ -245,7 +245,7 @@ public long getSequenceNumber(TransactionBuilderAccount account) { } @Override - public void setSequenceNumber(long newSequenceNumber, TransactionBuilderAccount account) { + public void updateSourceAccount(long newSequenceNumber, TransactionBuilderAccount account) { } }) diff --git a/src/test/java/org/stellar/sdk/TransactionBuilderTest.java b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java index 113a5702a..b69626278 100644 --- a/src/test/java/org/stellar/sdk/TransactionBuilderTest.java +++ b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java @@ -340,7 +340,7 @@ public long getSequenceNumber(TransactionBuilderAccount account) { } @Override - public void setSequenceNumber(long newSequenceNumber, TransactionBuilderAccount account) { + public void updateSourceAccount(long newSequenceNumber, TransactionBuilderAccount account) { account.setSequenceNumber(newSequenceNumber); } }) From 123f4fc09a77ac3ee8963b92ef1b0a1cf3c96438 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Fri, 25 Mar 2022 13:51:56 -0700 Subject: [PATCH 13/29] #411: added new account sequence ledger/time fields from CAP-21 --- .../stellar/sdk/responses/AccountResponse.java | 14 +++++++++++++- .../sdk/responses/AccountDeserializerTest.java | 6 +++++- .../stellar/sdk/xdr/AccountEntryDecodeTest.java | 17 ++++++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/stellar/sdk/responses/AccountResponse.java b/src/main/java/org/stellar/sdk/responses/AccountResponse.java index fcbfa42e8..109b2abd8 100644 --- a/src/main/java/org/stellar/sdk/responses/AccountResponse.java +++ b/src/main/java/org/stellar/sdk/responses/AccountResponse.java @@ -25,6 +25,10 @@ public class AccountResponse extends Response implements org.stellar.sdk.Transac private Long sequenceNumber; @SerializedName("subentry_count") private Integer subentryCount; + @SerializedName("sequence_ledger") + private Long sequenceUpdatedAtLedger; + @SerializedName("sequence_time") + private Long sequenceUpdatedAtTime; @SerializedName("inflation_destination") private String inflationDestination; @SerializedName("home_domain") @@ -88,7 +92,15 @@ public Long getIncrementedSequenceNumber() { public void incrementSequenceNumber() { sequenceNumber++; } - + + public Long getSequenceUpdatedAtLedger() { + return sequenceUpdatedAtLedger; + } + + public Long getSequenceUpdatedAtTime() { + return sequenceUpdatedAtTime; + } + public Integer getSubentryCount() { return subentryCount; } diff --git a/src/test/java/org/stellar/sdk/responses/AccountDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/AccountDeserializerTest.java index 869ec5a64..698b4b519 100644 --- a/src/test/java/org/stellar/sdk/responses/AccountDeserializerTest.java +++ b/src/test/java/org/stellar/sdk/responses/AccountDeserializerTest.java @@ -37,9 +37,11 @@ public void testDeserializeBalanceAuth() { @Test public void testDeserialize() { AccountResponse account = GsonSingleton.getInstance().fromJson(json, AccountResponse.class); - assertEquals(account.getAccountId(), "GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN7"); + assertEquals(account.getAccountId(), "GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN7"); assertEquals(account.getSequenceNumber(), new Long(2319149195853854L)); assertEquals(account.getSubentryCount(), new Integer(0)); + assertEquals(account.getSequenceUpdatedAtLedger().longValue(),1234) ; + assertEquals(account.getSequenceUpdatedAtTime().longValue(),4567) ; assertEquals(account.getInflationDestination(), "GAGRSA6QNQJN2OQYCBNQGMFLO4QLZFNEHIFXOMTQVSUTWVTWT66TOFSC"); assertEquals(account.getHomeDomain(), "stellar.org"); assertFalse(account.getSponsor().isPresent()); @@ -250,6 +252,8 @@ public void testDeserializeLiquidityPoolBalanc() { " \"paging_token\": \"1\",\n" + " \"account_id\": \"GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN7\",\n" + " \"sequence\": 2319149195853854,\n" + + " \"sequence_ledger\": 1234,\n" + + " \"sequence_time\": 4567,\n" + " \"subentry_count\": 0,\n" + " \"inflation_destination\": \"GAGRSA6QNQJN2OQYCBNQGMFLO4QLZFNEHIFXOMTQVSUTWVTWT66TOFSC\",\n" + " \"home_domain\": \"stellar.org\",\n" + diff --git a/src/test/java/org/stellar/sdk/xdr/AccountEntryDecodeTest.java b/src/test/java/org/stellar/sdk/xdr/AccountEntryDecodeTest.java index 0163f5b7d..ae754be34 100644 --- a/src/test/java/org/stellar/sdk/xdr/AccountEntryDecodeTest.java +++ b/src/test/java/org/stellar/sdk/xdr/AccountEntryDecodeTest.java @@ -8,6 +8,7 @@ import java.io.IOException; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; public class AccountEntryDecodeTest { @@ -32,7 +33,19 @@ public void testDecodeSignerPayload() throws IOException { bldr.flags(new Uint32(0)); bldr.homeDomain(new String32(new XdrString(""))); bldr.thresholds(new Thresholds(new byte[3])); - bldr.ext(new AccountEntry.AccountEntryExt.Builder().discriminant(0).build()); + bldr.ext(new AccountEntry.AccountEntryExt.Builder().discriminant(1) + .v1(new AccountEntryExtensionV1.Builder().liabilities(new Liabilities.Builder().buying(new Int64(0L)).selling(new Int64(0L)).build()) + .ext(new AccountEntryExtensionV1.AccountEntryExtensionV1Ext.Builder().discriminant(2) + .v2(new AccountEntryExtensionV2.Builder().numSponsored(new Uint32(0)).numSponsoring(new Uint32(0)).signerSponsoringIDs(new SponsorshipDescriptor[]{}) + .ext(new AccountEntryExtensionV2.AccountEntryExtensionV2Ext.Builder().discriminant(3) + .v3(new AccountEntryExtensionV3.Builder().seqLedger(new Uint32(1)).seqTime(new TimePoint(new Uint64(2L))) + .ext(new ExtensionPoint.Builder().discriminant(0).build()) + .build()) + .build()) + .build()) + .build()) + .build()) + .build()); AccountEntry xdr = bldr.build(); @@ -46,5 +59,7 @@ public void testDecodeSignerPayload() throws IOException { AccountEntry accountEntry = AccountEntry.decode(new XdrDataInputStream(new ByteArrayInputStream(decodedbBytes))); assertArrayEquals(new byte[32], accountEntry.getSigners()[0].getKey().getEd25519SignedPayload().getEd25519().getUint256()); assertArrayEquals(new byte[]{1,2,3,4}, accountEntry.getSigners()[0].getKey().getEd25519SignedPayload().getPayload()); + assertEquals(1, accountEntry.getExt().getV1().getExt().getV2().getExt().getV3().getSeqLedger().getUint32().longValue()); + assertEquals(2L, accountEntry.getExt().getV1().getExt().getV2().getExt().getV3().getSeqTime().getTimePoint().getUint64().longValue()); } } From 4af0328946e9d27233c4a727da3f73044c2f50e2 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Fri, 25 Mar 2022 17:25:08 -0700 Subject: [PATCH 14/29] #411: renamed local precondition variable to 'm' prefix convention --- src/main/java/org/stellar/sdk/Transaction.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/stellar/sdk/Transaction.java b/src/main/java/org/stellar/sdk/Transaction.java index b7794a274..588f589c5 100644 --- a/src/main/java/org/stellar/sdk/Transaction.java +++ b/src/main/java/org/stellar/sdk/Transaction.java @@ -36,7 +36,7 @@ public class Transaction extends AbstractTransaction { private final long mSequenceNumber; private final Operation[] mOperations; private final Memo mMemo; - private final PreconditionsV2 preconditions; + private final PreconditionsV2 mPreconditions; private EnvelopeType envelopeType = EnvelopeType.ENVELOPE_TYPE_TX; Transaction( @@ -54,7 +54,7 @@ public class Transaction extends AbstractTransaction { this.mSequenceNumber = sequenceNumber; this.mOperations = checkNotNull(operations, "operations cannot be null"); checkArgument(operations.length > 0, "At least one operation required"); - this.preconditions = preconditions; + this.mPreconditions = preconditions; this.mFee = fee; this.mMemo = memo != null ? memo : Memo.none(); } @@ -90,14 +90,14 @@ public Memo getMemo() { * @return Preconditions */ public PreconditionsV2 getPreconditions() { - return preconditions; + return mPreconditions; } /** * @return TimeBounds, or null (representing no time restrictions, i.e. infinite) */ public TimeBounds getTimeBounds() { - TimeBounds timeBounds = TimeBounds.fromXdr(preconditions.getTimeBounds()); + TimeBounds timeBounds = TimeBounds.fromXdr(mPreconditions.getTimeBounds()); if (timeBounds == null || (timeBounds.getMinTime() < 1 && timeBounds.getMaxTime() < 1)) { return null; } @@ -181,7 +181,7 @@ private TransactionV0 toXdr() { transaction.setSourceAccountEd25519(StrKey.encodeToXDRAccountId(this.mSourceAccount).getAccountID().getEd25519()); transaction.setOperations(operations); transaction.setMemo(mMemo.toXdr()); - transaction.setTimeBounds(preconditions == null || preconditions.getTimeBounds() == null ? null : preconditions.getTimeBounds()); + transaction.setTimeBounds(mPreconditions == null || mPreconditions.getTimeBounds() == null ? null : mPreconditions.getTimeBounds()); transaction.setExt(ext); return transaction; } @@ -213,8 +213,8 @@ private org.stellar.sdk.xdr.Transaction toV1Xdr(AccountConverter accountConverte v1Tx.setOperations(operations); v1Tx.setMemo(mMemo.toXdr()); Preconditions.Builder preconditionsBuilder = new Preconditions.Builder().discriminant(PreconditionType.PRECOND_NONE); - if (preconditions != null && preconditions.getTimeBounds() != null) { - preconditionsBuilder.discriminant(PreconditionType.PRECOND_TIME).timeBounds(preconditions.getTimeBounds()); + if (mPreconditions != null && mPreconditions.getTimeBounds() != null) { + preconditionsBuilder.discriminant(PreconditionType.PRECOND_TIME).timeBounds(mPreconditions.getTimeBounds()); } v1Tx.setCond(preconditionsBuilder.build()); v1Tx.setExt(ext); @@ -330,7 +330,7 @@ public int hashCode() { this.mSequenceNumber, Arrays.hashCode(this.mOperations), this.mMemo, - this.preconditions, + this.mPreconditions, this.mSignatures, this.mNetwork ); @@ -349,7 +349,7 @@ public boolean equals(Object object) { Objects.equal(this.mSequenceNumber, other.mSequenceNumber) && Arrays.equals(this.mOperations, other.mOperations) && Objects.equal(this.mMemo, other.mMemo) && - Objects.equal(this.preconditions, other.preconditions) && + Objects.equal(this.mPreconditions, other.mPreconditions) && Objects.equal(this.mNetwork, other.mNetwork) && Objects.equal(this.mSignatures, other.mSignatures); } From 25dcd81ce3ea31cfbca6a0388d5d26befa32c437 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Fri, 25 Mar 2022 18:14:39 -0700 Subject: [PATCH 15/29] #411: removed mapping of infinite time bounds to same as null time bounds --- .../java/org/stellar/sdk/Sep10Challenge.java | 5 --- .../java/org/stellar/sdk/Transaction.java | 9 ++-- .../org/stellar/sdk/Sep10ChallengeTest.java | 41 ------------------- 3 files changed, 4 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/stellar/sdk/Sep10Challenge.java b/src/main/java/org/stellar/sdk/Sep10Challenge.java index 7e83df903..32ce27085 100644 --- a/src/main/java/org/stellar/sdk/Sep10Challenge.java +++ b/src/main/java/org/stellar/sdk/Sep10Challenge.java @@ -162,11 +162,6 @@ public static ChallengeTransaction readChallengeTransaction(String challengeXdr, throw new InvalidSep10ChallengeException("The transaction sequence number should be zero."); } - // verify that transaction has time bounds set, and that current time is between the minimum and maximum bounds. - if (transaction.getTimeBounds() == null) { - throw new InvalidSep10ChallengeException("Transaction requires timebounds."); - } - long maxTime = transaction.getTimeBounds().getMaxTime(); long minTime = transaction.getTimeBounds().getMinTime(); if (maxTime == 0L) { diff --git a/src/main/java/org/stellar/sdk/Transaction.java b/src/main/java/org/stellar/sdk/Transaction.java index 588f589c5..1ac5ab9ac 100644 --- a/src/main/java/org/stellar/sdk/Transaction.java +++ b/src/main/java/org/stellar/sdk/Transaction.java @@ -94,13 +94,10 @@ public PreconditionsV2 getPreconditions() { } /** - * @return TimeBounds, or null (representing no time restrictions, i.e. infinite) + * @return TimeBounds */ public TimeBounds getTimeBounds() { TimeBounds timeBounds = TimeBounds.fromXdr(mPreconditions.getTimeBounds()); - if (timeBounds == null || (timeBounds.getMinTime() < 1 && timeBounds.getMaxTime() < 1)) { - return null; - } return timeBounds; } @@ -213,7 +210,9 @@ private org.stellar.sdk.xdr.Transaction toV1Xdr(AccountConverter accountConverte v1Tx.setOperations(operations); v1Tx.setMemo(mMemo.toXdr()); Preconditions.Builder preconditionsBuilder = new Preconditions.Builder().discriminant(PreconditionType.PRECOND_NONE); - if (mPreconditions != null && mPreconditions.getTimeBounds() != null) { + + //TODO - need to check if preconditions has attributes that are just V1Preconditions or V2Preconditions and set here. + if (mPreconditions != null) { preconditionsBuilder.discriminant(PreconditionType.PRECOND_TIME).timeBounds(mPreconditions.getTimeBounds()); } v1Tx.setCond(preconditionsBuilder.build()); diff --git a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java index c30c99f86..48a8c5ad6 100644 --- a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java +++ b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java @@ -480,47 +480,6 @@ public void testReadChallengeTransactionInvalidTimeboundsInfinite() throws IOExc } } - @Test - public void testReadChallengeTransactionInvalidNoTimeBounds() throws IOException { - KeyPair server = KeyPair.random(); - KeyPair client = KeyPair.random(); - String domainName = "example.com"; - String webAuthDomain = "example.com"; - - Network network = Network.TESTNET; - - byte[] nonce = new byte[48]; - SecureRandom random = new SecureRandom(); - random.nextBytes(nonce); - BaseEncoding base64Encoding = BaseEncoding.base64(); - byte[] encodedNonce = base64Encoding.encode(nonce).getBytes(); - - Account sourceAccount = new Account(server.getAccountId(), -1L); - ManageDataOperation operation = new ManageDataOperation.Builder(domainName + " auth", encodedNonce) - .setSourceAccount(client.getAccountId()) - .build(); - Operation[] operations = new Operation[]{operation}; - Transaction transaction = new TransactionBuilder(AccountConverter.disableMuxed(), sourceAccount, network) - .setBaseFee(100 * operations.length) - .addOperations(Arrays.asList(operations)) - .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(new org.stellar.sdk.xdr.TimeBounds.Builder() - .minTime(new TimePoint(new Uint64(0L))) - .maxTime(new TimePoint(new Uint64(0L))) - .build()) - .build()) - .build(); - transaction.sign(server); - String challenge = transaction.toEnvelopeXdrBase64(); - - try { - Sep10Challenge.readChallengeTransaction(challenge, server.getAccountId(), Network.TESTNET, domainName, webAuthDomain); - fail(); - } catch (InvalidSep10ChallengeException e) { - assertEquals("Transaction requires timebounds.", e.getMessage()); - } - } - @Test public void testReadChallengeTransactionInvalidTimeBoundsTooEarly() throws InvalidSep10ChallengeException, IOException { KeyPair server = KeyPair.random(); From 432ff5f97c494ec3465420d77ae19222b7ef8dc3 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Mon, 28 Mar 2022 00:00:56 -0700 Subject: [PATCH 16/29] #411: fixed xdr marshalling of preconditions and timebounds, more test coverage. --- build.gradle | 1 + .../org/stellar/sdk/FeeBumpTransaction.java | 3 +- .../java/org/stellar/sdk/LedgerBounds.java | 26 ++++ .../java/org/stellar/sdk/Transaction.java | 38 ++--- .../org/stellar/sdk/TransactionBuilder.java | 65 ++++---- .../stellar/sdk/TransactionPreconditions.java | 121 +++++++++++++++ .../org/stellar/sdk/Sep10ChallengeTest.java | 47 +++--- .../stellar/sdk/TransactionBuilderTest.java | 140 ++++++++++++------ .../sdk/TransactionPreconditionsTest.java | 139 +++++++++++++++++ .../java/org/stellar/sdk/TransactionTest.java | 28 ++-- 10 files changed, 457 insertions(+), 151 deletions(-) create mode 100644 src/main/java/org/stellar/sdk/LedgerBounds.java create mode 100644 src/main/java/org/stellar/sdk/TransactionPreconditions.java create mode 100644 src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java diff --git a/build.gradle b/build.gradle index 63888f135..2217248a4 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ buildscript { } plugins { + id "io.freefair.lombok" version "6.4.1" id "com.github.johnrengelman.shadow" version "7.1.2" id "java" id "com.github.ben-manes.versions" version "0.42.0" diff --git a/src/main/java/org/stellar/sdk/FeeBumpTransaction.java b/src/main/java/org/stellar/sdk/FeeBumpTransaction.java index 8b66ceee7..a491e20e6 100644 --- a/src/main/java/org/stellar/sdk/FeeBumpTransaction.java +++ b/src/main/java/org/stellar/sdk/FeeBumpTransaction.java @@ -6,7 +6,6 @@ import org.stellar.sdk.xdr.EnvelopeType; import org.stellar.sdk.xdr.FeeBumpTransactionEnvelope; import org.stellar.sdk.xdr.Int64; -import org.stellar.sdk.xdr.PreconditionsV2; import org.stellar.sdk.xdr.TransactionEnvelope; import org.stellar.sdk.xdr.TransactionSignaturePayload; @@ -140,7 +139,7 @@ public void updateSourceAccount(long newSequenceNumber, TransactionBuilderAccoun //no-op, account instance is local to this scope, not external, no need to update it. } }) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(inner.getTimeBounds().toXdr()).build()) + .addPreconditions(new TransactionPreconditions.TransactionPreconditionsBuilder().timeBounds(inner.getTimeBounds()).build()) .build(); this.mInner.mSignatures = Lists.newArrayList(inner.mSignatures); diff --git a/src/main/java/org/stellar/sdk/LedgerBounds.java b/src/main/java/org/stellar/sdk/LedgerBounds.java new file mode 100644 index 000000000..9fb891458 --- /dev/null +++ b/src/main/java/org/stellar/sdk/LedgerBounds.java @@ -0,0 +1,26 @@ +package org.stellar.sdk; + +import lombok.Value; +import org.stellar.sdk.xdr.Uint32; + +@Value +@lombok.Builder +/** + * LedgerBounds are Preconditions of a transaction per CAP-21 + */ +public class LedgerBounds { + int minLedger; + int maxLedger; + + public static LedgerBounds fromXdr(org.stellar.sdk.xdr.LedgerBounds xdrLedgerBounds) { + return new LedgerBoundsBuilder() + .minLedger(xdrLedgerBounds.getMinLedger().getUint32()) + .maxLedger(xdrLedgerBounds.getMaxLedger().getUint32()).build(); + } + + public org.stellar.sdk.xdr.LedgerBounds toXdr() { + return new org.stellar.sdk.xdr.LedgerBounds.Builder() + .maxLedger(new Uint32(maxLedger)) + .minLedger(new Uint32(minLedger)).build(); + } +} diff --git a/src/main/java/org/stellar/sdk/Transaction.java b/src/main/java/org/stellar/sdk/Transaction.java index 1ac5ab9ac..653190396 100644 --- a/src/main/java/org/stellar/sdk/Transaction.java +++ b/src/main/java/org/stellar/sdk/Transaction.java @@ -8,9 +8,6 @@ import org.stellar.sdk.xdr.Hash; import org.stellar.sdk.xdr.Int64; import org.stellar.sdk.xdr.OperationID; -import org.stellar.sdk.xdr.PreconditionType; -import org.stellar.sdk.xdr.Preconditions; -import org.stellar.sdk.xdr.PreconditionsV2; import org.stellar.sdk.xdr.SequenceNumber; import org.stellar.sdk.xdr.TransactionEnvelope; import org.stellar.sdk.xdr.TransactionSignaturePayload; @@ -36,7 +33,7 @@ public class Transaction extends AbstractTransaction { private final long mSequenceNumber; private final Operation[] mOperations; private final Memo mMemo; - private final PreconditionsV2 mPreconditions; + private final TransactionPreconditions mPreconditions; private EnvelopeType envelopeType = EnvelopeType.ENVELOPE_TYPE_TX; Transaction( @@ -46,7 +43,7 @@ public class Transaction extends AbstractTransaction { long sequenceNumber, Operation[] operations, Memo memo, - PreconditionsV2 preconditions, + TransactionPreconditions preconditions, Network network ) { super(accountConverter, network); @@ -89,7 +86,7 @@ public Memo getMemo() { /** * @return Preconditions */ - public PreconditionsV2 getPreconditions() { + public TransactionPreconditions getPreconditions() { return mPreconditions; } @@ -97,8 +94,7 @@ public PreconditionsV2 getPreconditions() { * @return TimeBounds */ public TimeBounds getTimeBounds() { - TimeBounds timeBounds = TimeBounds.fromXdr(mPreconditions.getTimeBounds()); - return timeBounds; + return mPreconditions.getTimeBounds(); } /** @@ -178,7 +174,7 @@ private TransactionV0 toXdr() { transaction.setSourceAccountEd25519(StrKey.encodeToXDRAccountId(this.mSourceAccount).getAccountID().getEd25519()); transaction.setOperations(operations); transaction.setMemo(mMemo.toXdr()); - transaction.setTimeBounds(mPreconditions == null || mPreconditions.getTimeBounds() == null ? null : mPreconditions.getTimeBounds()); + transaction.setTimeBounds(mPreconditions.getTimeBounds() == null ? null : getTimeBounds().toXdr()); transaction.setExt(ext); return transaction; } @@ -202,20 +198,13 @@ private org.stellar.sdk.xdr.Transaction toV1Xdr(AccountConverter accountConverte org.stellar.sdk.xdr.Transaction.TransactionExt ext = new org.stellar.sdk.xdr.Transaction.TransactionExt(); ext.setDiscriminant(0); - org.stellar.sdk.xdr.Transaction v1Tx = new org.stellar.sdk.xdr.Transaction(); v1Tx.setFee(fee); v1Tx.setSeqNum(sequenceNumber); v1Tx.setSourceAccount(accountConverter.encode(mSourceAccount)); v1Tx.setOperations(operations); v1Tx.setMemo(mMemo.toXdr()); - Preconditions.Builder preconditionsBuilder = new Preconditions.Builder().discriminant(PreconditionType.PRECOND_NONE); - - //TODO - need to check if preconditions has attributes that are just V1Preconditions or V2Preconditions and set here. - if (mPreconditions != null) { - preconditionsBuilder.discriminant(PreconditionType.PRECOND_TIME).timeBounds(mPreconditions.getTimeBounds()); - } - v1Tx.setCond(preconditionsBuilder.build()); + v1Tx.setCond(mPreconditions.toXdr()); v1Tx.setExt(ext); return v1Tx; @@ -225,8 +214,7 @@ public static Transaction fromV0EnvelopeXdr(AccountConverter accountConverter, T int mFee = envelope.getTx().getFee().getUint32(); Long mSequenceNumber = envelope.getTx().getSeqNum().getSequenceNumber().getInt64(); Memo mMemo = Memo.fromXdr(envelope.getTx().getMemo()); - PreconditionsV2.Builder preconditionsV2Builder = new PreconditionsV2.Builder(); - preconditionsV2Builder.timeBounds(envelope.getTx().getTimeBounds()); + TimeBounds mTimeBounds = TimeBounds.fromXdr(envelope.getTx().getTimeBounds()); Operation[] mOperations = new Operation[envelope.getTx().getOperations().length]; for (int i = 0; i < envelope.getTx().getOperations().length; i++) { @@ -240,7 +228,7 @@ public static Transaction fromV0EnvelopeXdr(AccountConverter accountConverter, T mSequenceNumber, mOperations, mMemo, - preconditionsV2Builder.build(), + TransactionPreconditions.builder().timeBounds(mTimeBounds).build(), network ); transaction.setEnvelopeType(EnvelopeType.ENVELOPE_TYPE_TX_V0); @@ -258,14 +246,6 @@ public static Transaction fromV1EnvelopeXdr(AccountConverter accountConverter, T int mFee = envelope.getTx().getFee().getUint32(); Long mSequenceNumber = envelope.getTx().getSeqNum().getSequenceNumber().getInt64(); Memo mMemo = Memo.fromXdr(envelope.getTx().getMemo()); - PreconditionsV2 preconditionsV2 = new PreconditionsV2(); - - if (envelope.getTx().getCond().getDiscriminant().equals(PreconditionType.PRECOND_TIME)) { - preconditionsV2 = new PreconditionsV2.Builder().timeBounds(envelope.getTx().getCond().getTimeBounds()).build(); - } - if (envelope.getTx().getCond().getDiscriminant().equals(PreconditionType.PRECOND_V2) && envelope.getTx().getCond().getV2().getTimeBounds() != null) { - preconditionsV2 = envelope.getTx().getCond().getV2(); - } Operation[] mOperations = new Operation[envelope.getTx().getOperations().length]; for (int i = 0; i < envelope.getTx().getOperations().length; i++) { @@ -279,7 +259,7 @@ public static Transaction fromV1EnvelopeXdr(AccountConverter accountConverter, T mSequenceNumber, mOperations, mMemo, - preconditionsV2, + TransactionPreconditions.fromXdr(envelope.getTx().getCond()), network ); diff --git a/src/main/java/org/stellar/sdk/TransactionBuilder.java b/src/main/java/org/stellar/sdk/TransactionBuilder.java index 67dff3e1b..9333c7edd 100644 --- a/src/main/java/org/stellar/sdk/TransactionBuilder.java +++ b/src/main/java/org/stellar/sdk/TransactionBuilder.java @@ -1,6 +1,6 @@ package org.stellar.sdk; -import org.stellar.sdk.xdr.PreconditionsV2; +import org.stellar.sdk.TransactionPreconditions.TransactionPreconditionsBuilder; import org.stellar.sdk.xdr.TimePoint; import org.stellar.sdk.xdr.Uint64; @@ -21,10 +21,11 @@ public class TransactionBuilder { private Integer mBaseFee; private Network mNetwork; private SequenceNumberStrategy sequenceNumberStrategy; - private PreconditionsV2 preconditions; + private TransactionPreconditions mPreconditions; + private TimeBounds mTimeBounds; + private boolean mTimeoutSet; public static final long TIMEOUT_INFINITE = 0; - public static final long MAX_EXTRA_SIGNERS_COUNT = 2; /** * Construct a new transaction builder. @@ -40,7 +41,6 @@ public TransactionBuilder(AccountConverter accountConverter, TransactionBuilderA mSourceAccount = checkNotNull(sourceAccount, "sourceAccount cannot be null"); mNetwork = checkNotNull(network, "Network cannot be null"); mOperations = newArrayList(); - preconditions = new PreconditionsV2(); sequenceNumberStrategy = new SequentialSequenceNumberStrategy(); } @@ -93,12 +93,9 @@ public TransactionBuilder addOperations(Collection operations) { * @param preconditions the tx PreConditions * @return updated Builder object */ - public TransactionBuilder addPreconditions(PreconditionsV2 preconditions) { + public TransactionBuilder addPreconditions(TransactionPreconditions preconditions) { checkNotNull(preconditions, "preconditions cannot be null"); - if (preconditions.getExtraSigners() != null && preconditions.getExtraSigners().length > MAX_EXTRA_SIGNERS_COUNT) { - throw new FormatException("Invalid preconditions, too many extra signers, can only have up to " + MAX_EXTRA_SIGNERS_COUNT); - } - this.preconditions = preconditions; + this.mPreconditions = preconditions; return this; } @@ -141,7 +138,7 @@ public TransactionBuilder addMemo(Memo memo) { */ public TransactionBuilder addTimeBounds(TimeBounds timeBounds) { checkNotNull(timeBounds, "timeBounds cannot be null"); - preconditions.setTimeBounds(timeBounds.toXdr()); + mTimeBounds = timeBounds; return this; } @@ -161,11 +158,11 @@ public TransactionBuilder addTimeBounds(TimeBounds timeBounds) { * @param timeout Timeout in seconds. * @return updated Builder * @see TimeBounds - * @deprecated this method will be removed in upcoming releases, use addPreconditions() instead - * for more control over preconditions. + * @deprecated this method will be removed in upcoming releases, use addPreconditions() with TimeBound + * set instead for more control over preconditions. */ public TransactionBuilder setTimeout(long timeout) { - if (preconditions.getTimeBounds() != null && preconditions.getTimeBounds().getMaxTime().getTimePoint().getUint64() > 0) { + if (mTimeBounds != null && mTimeBounds.getMaxTime() > 0) { throw new RuntimeException("TimeBounds.max_time has been already set - setting timeout would overwrite it."); } @@ -173,31 +170,15 @@ public TransactionBuilder setTimeout(long timeout) { throw new RuntimeException("timeout cannot be negative"); } + mTimeoutSet = true; if (timeout > 0) { long timeoutTimestamp = System.currentTimeMillis() / 1000L + timeout; - if (preconditions.getTimeBounds() == null) { - // no timebounds settings yet, so timeout is conveyed on the tx as a new timebounds in precondition - // with a range of genesis to (current time + timeout interval) - preconditions.setTimeBounds(buildTimeBounds(0L, timeoutTimestamp)); - } else { - // an existing timebounds in precondition, so timeout is conveyed on the tx as the current timebounds - // min time to a max time of (current time + timeout interval) - preconditions.getTimeBounds().setMaxTime(new TimePoint(new Uint64(timeoutTimestamp))); - } - } - - if (timeout == 0) { - if (preconditions.getTimeBounds() == null) { - // no timebounds settings yet, so infinite timeout is conveyed on the tx in a new timebounds precondition - // with a range of genesis to infinite - preconditions.setTimeBounds(buildTimeBounds(0L, 0L)); + if (mTimeBounds == null) { + mTimeBounds = new TimeBounds(0, timeoutTimestamp); } else { - // an existing timebounds in precondition, so timeout is conveyed on the tx as the current timebounds min time - // to a max time of infinite - preconditions.getTimeBounds().setMaxTime(new TimePoint(new Uint64(0L))); + mTimeBounds = new TimeBounds(mTimeBounds.getMinTime(), timeoutTimestamp); } } - return this; } @@ -211,14 +192,22 @@ public TransactionBuilder setBaseFee(int baseFee) { } /** - * Builds a transaction. It will use the SequenceNumberStrategy to determine how to update source account - * sequence number + * Builds a transaction. It will use the SequenceNumberStrategy if provided to determine how to update source account + * sequence number after transaction is constructed. */ public Transaction build() { - if (preconditions.getTimeBounds() == null) { - throw new FormatException("Timebounds is a mandatory precondition that must be set. Use TimeBounds(0,0) for infinite timeout"); + TransactionPreconditionsBuilder preconditionsBuilder = mPreconditions != null ? mPreconditions.toBuilder() : TransactionPreconditions.builder(); + + // left in for backwards compatibility, check on TransactionBuilder.mTimeBounds should be removed when both TransactionBuilder.setTimeBounds() and + // TransactionBuilder.setTimeout() are removed due to deprecation. This currently allows the builder to override + // any timebounds in new preconditions with timebounds directly from legacy timebounds/timeouts. + if (mTimeBounds != null) { + preconditionsBuilder.timeBounds(mTimeBounds); } + mPreconditions = preconditionsBuilder.build(); + mPreconditions.isValid(mTimeoutSet); + if (mBaseFee == null) { throw new FormatException("mBaseFee has to be set. you must call setBaseFee()."); } @@ -238,7 +227,7 @@ public Transaction build() { sequenceNumber, operations, mMemo, - preconditions, + mPreconditions, mNetwork ); // Increment sequence number when there were no exceptions when creating a transaction diff --git a/src/main/java/org/stellar/sdk/TransactionPreconditions.java b/src/main/java/org/stellar/sdk/TransactionPreconditions.java new file mode 100644 index 000000000..2e49b819a --- /dev/null +++ b/src/main/java/org/stellar/sdk/TransactionPreconditions.java @@ -0,0 +1,121 @@ +package org.stellar.sdk; + +import lombok.Builder; +import lombok.NonNull; +import lombok.Singular; +import lombok.Value; +import org.stellar.sdk.xdr.Duration; +import org.stellar.sdk.xdr.Int64; +import org.stellar.sdk.xdr.PreconditionType; +import org.stellar.sdk.xdr.Preconditions; +import org.stellar.sdk.xdr.PreconditionsV2; +import org.stellar.sdk.xdr.SequenceNumber; +import org.stellar.sdk.xdr.SignerKey; +import org.stellar.sdk.xdr.Uint32; + +import java.util.Arrays; +import java.util.List; + +@Value +@Builder(toBuilder=true) +/** + * Preconditions of a transaction per CAP-21 + */ +public class TransactionPreconditions { + public static final long MAX_EXTRA_SIGNERS_COUNT = 2; + + LedgerBounds ledgerBounds; + Long minSeqNumber; + Long minSeqAge; + Integer minSeqLedgerGap; + @Singular + @NonNull + List extraSigners; + TimeBounds timeBounds; + + public void isValid(boolean infiniteTimeoutWasSet) { + if (timeBounds == null && !infiniteTimeoutWasSet) { + throw new FormatException("Invalid preconditions, must define timebounds or set infinite timeout"); + } + + if (extraSigners.size() > MAX_EXTRA_SIGNERS_COUNT) { + throw new FormatException("Invalid preconditions, too many extra signers, can only have up to " + MAX_EXTRA_SIGNERS_COUNT); + } + } + + public boolean hasV2() { + return (ledgerBounds != null || minSeqLedgerGap != null || minSeqAge != null || minSeqNumber != null || !extraSigners.isEmpty()); + } + + public static TransactionPreconditions fromXdr(Preconditions preconditions) { + TransactionPreconditionsBuilder builder = new TransactionPreconditionsBuilder(); + + if (preconditions.getDiscriminant().equals(PreconditionType.PRECOND_V2)) { + if (preconditions.getTimeBounds() != null) { + builder.timeBounds(new TimeBounds( + preconditions.getV2().getTimeBounds().getMinTime().getTimePoint().getUint64(), + preconditions.getV2().getTimeBounds().getMaxTime().getTimePoint().getUint64())); + } + if (preconditions.getV2().getExtraSigners().length > 0) { + builder.extraSigners(Arrays.asList(preconditions.getV2().getExtraSigners())); + } + if (preconditions.getV2().getMinSeqAge() != null) { + builder.minSeqAge(preconditions.getV2().getMinSeqAge().getDuration().getInt64()); + } + if (preconditions.getV2().getLedgerBounds() != null) { + builder.ledgerBounds(LedgerBounds.fromXdr(preconditions.getV2().getLedgerBounds())); + } + if (preconditions.getV2().getMinSeqNum() != null) { + builder.minSeqNumber(preconditions.getV2().getMinSeqNum().getSequenceNumber().getInt64()); + } + if (preconditions.getV2().getMinSeqLedgerGap() != null) { + builder.minSeqLedgerGap(preconditions.getV2().getMinSeqLedgerGap().getUint32()); + } + } else { + if (preconditions.getTimeBounds() != null) { + builder.timeBounds(new TimeBounds(preconditions.getTimeBounds().getMinTime().getTimePoint().getUint64(), + preconditions.getTimeBounds().getMaxTime().getTimePoint().getUint64())); + } + } + + return builder.build(); + } + + public Preconditions toXdr() { + Preconditions.Builder preconditionsBuilder = new Preconditions.Builder(); + + if (hasV2()) { + preconditionsBuilder.discriminant(PreconditionType.PRECOND_V2); + PreconditionsV2.Builder v2Builder = new PreconditionsV2.Builder(); + + v2Builder.extraSigners(extraSigners.toArray(new SignerKey[]{})); + v2Builder.minSeqAge(new Duration(new Int64(minSeqAge != null ? minSeqAge : 0L))); + + if (ledgerBounds != null) { + v2Builder.ledgerBounds(new org.stellar.sdk.xdr.LedgerBounds.Builder() + .minLedger(new Uint32(ledgerBounds.getMinLedger())) + .maxLedger(new Uint32(ledgerBounds.getMaxLedger())) + .build()); + } + if (minSeqNumber != null) { + v2Builder.minSeqNum(new SequenceNumber(new Int64(minSeqNumber))); + } + + v2Builder.minSeqLedgerGap(new Uint32(minSeqLedgerGap != null ? minSeqLedgerGap : 0)); + + if (timeBounds != null) { + v2Builder.timeBounds(timeBounds.toXdr()); + } + preconditionsBuilder.v2(v2Builder.build()); + } else { + if (timeBounds == null) { + preconditionsBuilder.discriminant(PreconditionType.PRECOND_NONE); + } else { + preconditionsBuilder.discriminant(PreconditionType.PRECOND_TIME); + preconditionsBuilder.timeBounds(timeBounds.toXdr()); + } + } + + return preconditionsBuilder.build(); + } +} diff --git a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java index 48a8c5ad6..109096851 100644 --- a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java +++ b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java @@ -4,10 +4,7 @@ import com.google.common.io.BaseEncoding; import org.junit.Test; import org.stellar.sdk.xdr.EnvelopeType; -import org.stellar.sdk.xdr.PreconditionsV2; -import org.stellar.sdk.xdr.TimePoint; import org.stellar.sdk.xdr.TransactionEnvelope; -import org.stellar.sdk.xdr.Uint64; import java.io.IOException; import java.security.SecureRandom; @@ -249,7 +246,7 @@ public void updateSourceAccount(long newSequenceNumber, TransactionBuilderAccoun } }) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(transaction.getTimeBounds().toXdr()).build()) + .addPreconditions(transaction.getPreconditions()) .build(); withMuxedClient.getSignatures().addAll(transaction.mSignatures); @@ -322,7 +319,7 @@ public void testReadChallengeTransactionInvalidNotSignedByServer() throws IOExce .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(client); @@ -423,7 +420,7 @@ public void testReadChallengeTransactionInvalidSeqNoNotZero() throws IOException .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); @@ -466,7 +463,7 @@ public void testReadChallengeTransactionInvalidTimeboundsInfinite() throws IOExc .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); @@ -588,7 +585,7 @@ public void testReadChallengeTransactionInvalidOperationWrongType() throws IOExc .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); @@ -629,7 +626,7 @@ public void testReadChallengeTransactionInvalidOperationNoSourceAccount() throws .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); @@ -672,7 +669,7 @@ public void testReadChallengeTransactionInvalidDataValueWrongEncodedLength() thr .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -709,7 +706,7 @@ public void testReadChallengeTransactionInvalidDataValueCorruptBase64() throws I .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -752,7 +749,7 @@ public void testReadChallengeTransactionInvalidDataValueWrongByteLength() throws .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -788,7 +785,7 @@ public void testReadChallengeTransactionInvalidDataValueIsNull() throws IOExcept .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -832,7 +829,7 @@ public void testReadChallengeTransactionValidAdditionalManageDataOpsWithSourceAc .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -872,7 +869,7 @@ public void testReadChallengeTransactionInvalidAdditionalManageDataOpsWithoutSou .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -914,7 +911,7 @@ public void testReadChallengeTransactionInvalidAdditionalManageDataOpsWithSource .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -958,7 +955,7 @@ public void testReadChallengeTransactionInvalidAdditionalOpsOfOtherTypes() throw .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -1197,7 +1194,7 @@ public void testReadChallengeTransactionInvalidWebAuthDomainOperationValueIsNull .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -1244,7 +1241,7 @@ public void testReadChallengeTransactionValidWebAuthDomainAllowSubsequentManageD .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); String challenge = transaction.toEnvelopeXdrBase64(); @@ -1461,7 +1458,7 @@ public void testVerifyChallengeTransactionThresholdInvalidNotSignedByServer() th .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(masterClient); @@ -1893,7 +1890,7 @@ public void testVerifyChallengeTransactionSignersInvalidServer() throws IOExcept .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(masterClient); @@ -2383,7 +2380,7 @@ public void testVerifyChallengeTransactionValidAdditionalManageDataOpsWithSource .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); transaction.sign(masterClient); @@ -2424,7 +2421,7 @@ public void testVerifyChallengeTransactionInvalidAdditionalManageDataOpsWithoutS .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); transaction.sign(masterClient); @@ -2467,7 +2464,7 @@ public void testVerifyChallengeTransactionInvalidAdditionalManageDataOpsWithSour .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); transaction.sign(masterClient); @@ -2512,7 +2509,7 @@ public void testVerifyChallengeTransactionInvalidAdditionalOpsOfOtherTypes() thr .setBaseFee(100 * operations.length) .addOperations(Arrays.asList(operations)) .addMemo(Memo.none()) - .addPreconditions(new PreconditionsV2.Builder().timeBounds(timeBounds.toXdr()).build()) + .addPreconditions(TransactionPreconditions.builder().timeBounds(timeBounds).build()) .build(); transaction.sign(server); transaction.sign(masterClient); diff --git a/src/test/java/org/stellar/sdk/TransactionBuilderTest.java b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java index b69626278..97ba1eb91 100644 --- a/src/test/java/org/stellar/sdk/TransactionBuilderTest.java +++ b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java @@ -2,21 +2,18 @@ import com.google.common.io.BaseEncoding; import org.junit.Test; -import org.stellar.sdk.xdr.Duration; import org.stellar.sdk.xdr.Int64; -import org.stellar.sdk.xdr.LedgerBounds; import org.stellar.sdk.xdr.PreconditionsV2; -import org.stellar.sdk.xdr.PublicKeyType; import org.stellar.sdk.xdr.SequenceNumber; import org.stellar.sdk.xdr.SignerKey; import org.stellar.sdk.xdr.SignerKeyType; import org.stellar.sdk.xdr.Uint256; -import org.stellar.sdk.xdr.Uint32; import org.stellar.sdk.xdr.XdrDataInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; +import static com.google.common.collect.Lists.newArrayList; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -39,7 +36,7 @@ public void testMissingOperationFee() { } } - @Test + @Test public void testBuilderSuccessTestnet() throws FormatException { // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); @@ -59,6 +56,10 @@ public void testBuilderSuccessTestnet() throws FormatException { assertEquals(transaction.getSequenceNumber(), sequenceNumber + 1); assertEquals(transaction.getFee(), 100); + assertEquals( + "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAO3gUmG83C+VCqO6FztuMtXJF/l7grZA7MjRzqdZ9W8QAAAABKgXyAAAAAAAAAAAAbaHlWIAAABAy5IvTou9NDetC6PIFJhBR2yr2BuEEql4iyLfU9K7tjuQaYVZf40fbWLRwA/lHg2IYFzYMFMakxLtVrpLxMmHAw==", + transaction.toEnvelopeXdrBase64()); + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); assertEquals(transaction, transaction2); @@ -81,6 +82,9 @@ public void testBuilderMemoText() throws FormatException { transaction.sign(source); Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); + assertEquals( + "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAAAAAABAAAADEhlbGxvIHdvcmxkIQAAAAEAAAAAAAAAAAAAAADt4FJhvNwvlQqjuhc7bjLVyRf5e4K2QOzI0c6nWfVvEAAAAASoF8gAAAAAAAAAAAG2h5ViAAAAQMc6HwYaGsrlJ8/LdE9VDVq04JifpQofSmnjhrtqaTTs/VBsNGmxi4b/vaFkLLLWh8emI8FsS/vBgb8AVFVkZQU=", + transaction.toEnvelopeXdrBase64()); assertEquals(transaction, transaction2); } @@ -133,6 +137,9 @@ public void testBuilderBaseFee() throws FormatException { transaction.sign(source); Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); + assertEquals( + "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAMgAClWjAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAO3gUmG83C+VCqO6FztuMtXJF/l7grZA7MjRzqdZ9W8QAAAABKgXyAAAAAAAAAAAAbaHlWIAAABA9TG3dKKLtLHzRUbsbEqr68CfUc800p1/LE5pWzCnFdFdypdXgyqHqw/sWdaTUMDiWawBtsmqV8oOtD0Hw1HDDQ==", + transaction.toEnvelopeXdrBase64()); assertEquals(transaction, transaction2); } @@ -212,25 +219,33 @@ public void testBuilderTimeout() throws IOException { @Test public void testBuilderSetsLedgerBounds() throws IOException { - Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); - PreconditionsV2.Builder preconditionsV2 = new PreconditionsV2.Builder(); - - preconditionsV2.timeBounds(TransactionBuilder.buildTimeBounds(0, TransactionBuilder.TIMEOUT_INFINITE)); - preconditionsV2.ledgerBounds(new LedgerBounds.Builder().minLedger(new Uint32(1)).maxLedger(new Uint32(2)).build()); + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + Account account = new Account(source.getAccountId(), 2908908335136768L); + KeyPair newAccount = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) - .addPreconditions(preconditionsV2.build()) + .addOperation(new CreateAccountOperation.Builder(newAccount.getAccountId(), "2000").build()) + .addPreconditions(TransactionPreconditions.builder() + .timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE)) + .ledgerBounds(LedgerBounds.builder().minLedger(1).maxLedger(2).build()) + .build()) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); - assertEquals(1, transaction.getPreconditions().getLedgerBounds().getMinLedger().getUint32().intValue()); - assertEquals(2, transaction.getPreconditions().getLedgerBounds().getMaxLedger().getUint32().intValue()); + assertEquals(1, transaction.getPreconditions().getLedgerBounds().getMinLedger()); + assertEquals(2, transaction.getPreconditions().getLedgerBounds().getMaxLedger()); + + assertEquals( + "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAADt4FJhvNwvlQqjuhc7bjLVyRf5e4K2QOzI0c6nWfVvEAAAAASoF8gAAAAAAAAAAAA=", + transaction.toEnvelopeXdrBase64()); + } @Test public void testBuilderSetsMinSeqNum() throws IOException { - Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + Account account = new Account(source.getAccountId(), 2908908335136768L); + KeyPair newAccount = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); PreconditionsV2.Builder preconditionsV2 = new PreconditionsV2.Builder(); SequenceNumber seqNum = new SequenceNumber(); @@ -239,54 +254,70 @@ public void testBuilderSetsMinSeqNum() throws IOException { preconditionsV2.minSeqNum(seqNum); Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) - .addPreconditions(preconditionsV2.build()) + .addOperation(new CreateAccountOperation.Builder(newAccount.getAccountId(), "2000").build()) + .addPreconditions(TransactionPreconditions.builder() + .timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE)) + .minSeqNumber(5L) + .build()) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); - assertEquals(5, transaction.getPreconditions().getMinSeqNum().getSequenceNumber().getInt64().longValue()); + assertEquals(Long.valueOf(5), transaction.getPreconditions().getMinSeqNumber()); + + assertEquals( + "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAADt4FJhvNwvlQqjuhc7bjLVyRf5e4K2QOzI0c6nWfVvEAAAAASoF8gAAAAAAAAAAAA=", + transaction.toEnvelopeXdrBase64()); } @Test public void testBuilderSetsMinSeqAge() throws IOException { - Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); - PreconditionsV2.Builder preconditionsV2 = new PreconditionsV2.Builder(); - - Duration duration = new Duration(); - duration.setDuration(new Int64(5L)); - preconditionsV2.timeBounds(TransactionBuilder.buildTimeBounds(0, TransactionBuilder.TIMEOUT_INFINITE)); - preconditionsV2.minSeqAge(duration); + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + KeyPair newAccount = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); + Account account = new Account(source.getAccountId(), 2908908335136768L); Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) - .addPreconditions(preconditionsV2.build()) + .addOperation(new CreateAccountOperation.Builder(newAccount.getAccountId(), "2000").build()) + .addPreconditions(TransactionPreconditions.builder() + .timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE)) + .minSeqAge(5L) + .build()) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); - assertEquals(5, transaction.getPreconditions().getMinSeqAge().getDuration().getInt64().longValue()); + assertEquals(Long.valueOf(5), transaction.getPreconditions().getMinSeqAge()); + + assertEquals( + "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAA7eBSYbzcL5UKo7oXO24y1ckX+XuCtkDsyNHOp1n1bxAAAAAEqBfIAAAAAAAAAAAA", + transaction.toEnvelopeXdrBase64()); } @Test public void testBuilderSetsMinSeqLedgerGap() throws IOException { - Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); - PreconditionsV2.Builder preconditionsV2 = new PreconditionsV2.Builder(); - - preconditionsV2.timeBounds(TransactionBuilder.buildTimeBounds(0, TransactionBuilder.TIMEOUT_INFINITE)); - preconditionsV2.minSeqLedgerGap(new Uint32(5)); + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + KeyPair newAccount = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); + Account account = new Account(source.getAccountId(), 2908908335136768L); Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) - .addPreconditions(preconditionsV2.build()) + .addOperation(new CreateAccountOperation.Builder(newAccount.getAccountId(), "2000").build()) + .addPreconditions(TransactionPreconditions.builder() + .timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE)) + .minSeqLedgerGap(5) + .build()) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); - assertEquals(5, transaction.getPreconditions().getMinSeqLedgerGap().getUint32().longValue()); + assertEquals(Integer.valueOf(5), transaction.getPreconditions().getMinSeqLedgerGap()); + + assertEquals( + "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAA7eBSYbzcL5UKo7oXO24y1ckX+XuCtkDsyNHOp1n1bxAAAAAEqBfIAAAAAAAAAAAA", + transaction.toEnvelopeXdrBase64()); } @Test public void testBuilderExtraSigners() throws IOException { - Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); - PreconditionsV2.Builder preconditionsV2 = new PreconditionsV2.Builder(); + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + KeyPair newAccount = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); + Account account = new Account(source.getAccountId(), 2908908335136768L); String accountStrKey = "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"; byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".toUpperCase()); @@ -299,17 +330,22 @@ public void testBuilderExtraSigners() throws IOException { .build()) .build(); - preconditionsV2.timeBounds(TransactionBuilder.buildTimeBounds(0, TransactionBuilder.TIMEOUT_INFINITE)); - preconditionsV2.extraSigners(new SignerKey[]{signerKey}); - Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) - .addPreconditions(preconditionsV2.build()) + .addOperation(new CreateAccountOperation.Builder(newAccount.getAccountId(), "2000").build()) + .addPreconditions(TransactionPreconditions.builder() + .timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE)) + .extraSigners(newArrayList(signerKey)) + .minSeqLedgerGap(5) + .build()) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); - assertEquals(SignerKeyType.SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD, transaction.getPreconditions().getExtraSigners()[0].getDiscriminant()); - assertArrayEquals(payload, transaction.getPreconditions().getExtraSigners()[0].getEd25519SignedPayload().getPayload()); + assertEquals(SignerKeyType.SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD, newArrayList(transaction.getPreconditions().getExtraSigners()).get(0).getDiscriminant()); + assertArrayEquals(payload, newArrayList(transaction.getPreconditions().getExtraSigners()).get(0).getEd25519SignedPayload().getPayload()); + + assertEquals( + "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAQAAAAM/DDS/k60NmXHQTMyQ9wVRHIOKrZc0pKL7DXoD/H/omgAAACABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fIAAAAAAAAAABAAAAAAAAAAAAAAAA7eBSYbzcL5UKo7oXO24y1ckX+XuCtkDsyNHOp1n1bxAAAAAEqBfIAAAAAAAAAAAA", + transaction.toEnvelopeXdrBase64()); } @Test @@ -319,7 +355,11 @@ public void testBuilderFailsWhenTooManyExtraSigners() throws IOException { try { new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) - .addPreconditions(new PreconditionsV2.Builder().extraSigners(new SignerKey[3]).build()) + .addPreconditions(TransactionPreconditions.builder() + .timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE)) + .extraSigners(newArrayList(new SignerKey.Builder().build(), new SignerKey.Builder().build(), new SignerKey.Builder().build())) + .minSeqLedgerGap(5) + .build()) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); fail(); @@ -331,8 +371,8 @@ public void testBuilderUsesCustomSequence() throws IOException { Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) - .addPreconditions(new PreconditionsV2.Builder() - .timeBounds(TransactionBuilder.buildTimeBounds(0, TransactionBuilder.TIMEOUT_INFINITE)).build()) + .addPreconditions(TransactionPreconditions.builder() + .timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE)).build()) .addSequenceNumberStrategy(new SequenceNumberStrategy() { @Override public long getSequenceNumber(TransactionBuilderAccount account) { @@ -429,6 +469,10 @@ public void testBuilderSuccessPublic() throws FormatException, IOException { transaction.sign(source); Transaction decodedTransaction = (Transaction) Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdrBase64(), Network.PUBLIC); + assertEquals( + "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAO3gUmG83C+VCqO6FztuMtXJF/l7grZA7MjRzqdZ9W8QAAAABKgXyAAAAAAAAAAAAbaHlWIAAABA830eT4ERYpuVnb6vSJnWTVhgcQVsGJED2vZeYogXbTy8wGb+qo/ojn0q6op7KBdF6y5MHSHGUFbad1UR4UaFBA==", + transaction.toEnvelopeXdrBase64()); + assertEquals(decodedTransaction, transaction); } diff --git a/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java b/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java new file mode 100644 index 000000000..157dfd510 --- /dev/null +++ b/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java @@ -0,0 +1,139 @@ +package org.stellar.sdk; + +import org.junit.Test; +import org.stellar.sdk.xdr.Duration; +import org.stellar.sdk.xdr.Int64; +import org.stellar.sdk.xdr.PreconditionType; +import org.stellar.sdk.xdr.Preconditions; +import org.stellar.sdk.xdr.PreconditionsV2; +import org.stellar.sdk.xdr.SequenceNumber; +import org.stellar.sdk.xdr.SignerKey; +import org.stellar.sdk.xdr.Uint32; + +import static com.google.common.collect.Lists.newArrayList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class TransactionPreconditionsTest { + + @Test + public void itConvertsFromXdr() { + + Preconditions.Builder preconditionsBuilder = new Preconditions.Builder(); + preconditionsBuilder.discriminant(PreconditionType.PRECOND_V2); + PreconditionsV2.Builder v2Builder = new PreconditionsV2.Builder(); + + v2Builder.extraSigners(new SignerKey[]{}); + v2Builder.minSeqAge(new Duration(new Int64(2L))); + v2Builder.ledgerBounds(new org.stellar.sdk.xdr.LedgerBounds.Builder() + .minLedger(new Uint32(1)) + .maxLedger(new Uint32(2)) + .build()); + v2Builder.minSeqNum(new SequenceNumber(new Int64(4L))); + preconditionsBuilder.v2(v2Builder.build()); + Preconditions xdr = preconditionsBuilder.build(); + + TransactionPreconditions transactionPreconditions = TransactionPreconditions.fromXdr(xdr); + + assertEquals(transactionPreconditions.getMinSeqAge(), Long.valueOf(2)); + assertEquals(transactionPreconditions.getLedgerBounds().getMinLedger(), 1); + assertEquals(transactionPreconditions.getLedgerBounds().getMaxLedger(), 2); + assertEquals(transactionPreconditions.getMinSeqNumber(), Long.valueOf(4)); + } + + @Test + public void itConvertsToV2Xdr() { + TransactionPreconditions preconditions = TransactionPreconditions.builder() + .timeBounds(new TimeBounds(1, 2)) + .minSeqNumber(3L) + .extraSigners(newArrayList(new SignerKey.Builder().build(), new SignerKey.Builder().build(), new SignerKey.Builder().build())) + .build(); + + Preconditions xdr = preconditions.toXdr(); + assertEquals(xdr.getDiscriminant(), PreconditionType.PRECOND_V2); + assertEquals(xdr.getV2().getTimeBounds().getMinTime().getTimePoint().getUint64(), Long.valueOf(1)); + assertEquals(xdr.getV2().getTimeBounds().getMaxTime().getTimePoint().getUint64(), Long.valueOf(2)); + assertEquals(xdr.getV2().getMinSeqNum().getSequenceNumber().getInt64(), Long.valueOf(3)); + assertEquals(xdr.getV2().getExtraSigners().length, 3); + } + + @Test + public void itConvertsOnlyTimeBoundsXdr() { + TransactionPreconditions preconditions = TransactionPreconditions.builder() + .timeBounds(new TimeBounds(1, 2)) + .build(); + + Preconditions xdr = preconditions.toXdr(); + assertEquals(xdr.getDiscriminant(), PreconditionType.PRECOND_TIME); + assertEquals(xdr.getTimeBounds().getMinTime().getTimePoint().getUint64(), Long.valueOf(1)); + assertEquals(xdr.getTimeBounds().getMaxTime().getTimePoint().getUint64(), Long.valueOf(2)); + } + + @Test + public void itConvertsNullTimeBoundsXdr() { + // there was precedence in the sdk test coverage in TransactionTest.java for edge case of passing a null timebounds + // into a transaction, which occurrs when infinite timeout is set and timebounds is not set through TransactionBuilder. + // TransactionPreconditions continues to support that edge case. + TransactionPreconditions preconditions = TransactionPreconditions.builder().build(); + + Preconditions xdr = preconditions.toXdr(); + assertEquals(xdr.getDiscriminant(), PreconditionType.PRECOND_NONE); + assertNull(xdr.getTimeBounds()); + } + + @Test + public void itChecksValidityWhenTimebounds() { + TransactionPreconditions preconditions = TransactionPreconditions.builder().timeBounds(new TimeBounds(1, 2)).build(); + preconditions.isValid(false); + } + + @Test + public void itChecksNonValidityOfTimeBounds() { + TransactionPreconditions preconditions = TransactionPreconditions.builder().build(); + try { + preconditions.isValid(false); + fail(); + } catch (FormatException ignored) {} + } + + @Test + public void itChecksNonValidityOfExtraSignersSize() { + TransactionPreconditions preconditions = TransactionPreconditions.builder() + .timeBounds(new TimeBounds(1, 2)) + .extraSigners(newArrayList(new SignerKey.Builder().build(), new SignerKey.Builder().build(), new SignerKey.Builder().build())) + .build(); + try { + preconditions.isValid(false); + fail(); + } catch (FormatException ignored) {} + } + + @Test + public void itChecksValidityWhenNoTimeboundsButTimeoutSet() { + TransactionPreconditions preconditions = TransactionPreconditions.builder().build(); + preconditions.isValid(true); + } + + + @Test + public void itChecksV2Status() { + Preconditions.Builder preconditionsBuilder = new Preconditions.Builder(); + preconditionsBuilder.discriminant(PreconditionType.PRECOND_V2); + PreconditionsV2.Builder v2Builder = new PreconditionsV2.Builder(); + + v2Builder.extraSigners(new SignerKey[]{}); + v2Builder.minSeqAge(new Duration(new Int64(2L))); + v2Builder.ledgerBounds(new org.stellar.sdk.xdr.LedgerBounds.Builder() + .minLedger(new Uint32(1)) + .maxLedger(new Uint32(2)) + .build()); + v2Builder.minSeqNum(new SequenceNumber(new Int64(4L))); + preconditionsBuilder.v2(v2Builder.build()); + Preconditions xdr = preconditionsBuilder.build(); + + TransactionPreconditions transactionPreconditions = TransactionPreconditions.fromXdr(xdr); + assertTrue(transactionPreconditions.hasV2()); + } +} diff --git a/src/test/java/org/stellar/sdk/TransactionTest.java b/src/test/java/org/stellar/sdk/TransactionTest.java index aedfa41fc..529f4061e 100644 --- a/src/test/java/org/stellar/sdk/TransactionTest.java +++ b/src/test/java/org/stellar/sdk/TransactionTest.java @@ -3,12 +3,13 @@ import com.google.common.io.BaseEncoding; import org.junit.Test; import org.stellar.sdk.xdr.EnvelopeType; -import org.stellar.sdk.xdr.PreconditionsV2; +import org.stellar.sdk.xdr.SignerKey; import org.stellar.sdk.xdr.XdrDataInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.security.SecureRandom; +import java.util.ArrayList; import java.util.Arrays; import static org.junit.Assert.assertArrayEquals; @@ -27,13 +28,13 @@ public void testParseV0Transaction() throws FormatException, IOException { Account account = new Account(source.getAccountId(), 2908908335136768L); Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), + AccountConverter.enableMuxed(), account.getAccountId(), Transaction.MIN_BASE_FEE, account.getIncrementedSequenceNumber(), new org.stellar.sdk.Operation[]{new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()}, - Memo.none(), - new PreconditionsV2.Builder().timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE).toXdr()).build(), + null, + new TransactionPreconditions(null, null, null, null, new ArrayList(),null), Network.PUBLIC ); @@ -48,6 +49,11 @@ public void testParseV0Transaction() throws FormatException, IOException { assertTrue(parsed.equals(transaction)); assertEquals(EnvelopeType.ENVELOPE_TYPE_TX_V0, parsed.toEnvelopeXdr().getDiscriminant()); assertEquals(transaction.toEnvelopeXdrBase64(), parsed.toEnvelopeXdrBase64()); + + assertEquals( + "AAAAAF7FIiDToW1fOYUFBC0dmyufJbFTOa2GQESGz+S2h5ViAAAAZAAKVaMAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAA7eBSYbzcL5UKo7oXO24y1ckX+XuCtkDsyNHOp1n1bxAAAAAEqBfIAAAAAAAAAAABtoeVYgAAAEDzfR5PgRFim5Wdvq9ImdZNWGBxBWwYkQPa9l5iiBdtPLzAZv6qj+iOfSrqinsoF0XrLkwdIcZQVtp3VRHhRoUE", + transaction.toEnvelopeXdrBase64()); + } @Test @@ -63,8 +69,8 @@ public void testSha256HashSigning() throws FormatException { Transaction.MIN_BASE_FEE, account.getIncrementedSequenceNumber(), new org.stellar.sdk.Operation[]{new PaymentOperation.Builder(destination.getAccountId(), new AssetTypeNative(), "2000").build()}, - Memo.none(), - new PreconditionsV2.Builder().timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE).toXdr()).build(), + null, + new TransactionPreconditions(null, null, null, null, new ArrayList(),null), Network.PUBLIC ); @@ -90,17 +96,21 @@ public void testToBase64EnvelopeXdrBuilderNoSignatures() throws FormatException, Account account = new Account(source.getAccountId(), 2908908335136768L); Transaction transaction = new Transaction( - AccountConverter.disableMuxed(), + AccountConverter.enableMuxed(), account.getAccountId(), Transaction.MIN_BASE_FEE, account.getIncrementedSequenceNumber(), new org.stellar.sdk.Operation[]{new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()}, - Memo.none(), - new PreconditionsV2.Builder().timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE).toXdr()).build(), + null, + new TransactionPreconditions(null, null, null, null, new ArrayList(),null), Network.TESTNET ); Transaction parsed = (Transaction) Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdrBase64(), Network.TESTNET); assertEquals(parsed, transaction); + assertEquals( + "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAO3gUmG83C+VCqO6FztuMtXJF/l7grZA7MjRzqdZ9W8QAAAABKgXyAAAAAAAAAAAAA==", + transaction.toEnvelopeXdrBase64() + ); } } \ No newline at end of file From 271ef3e7bb54544914fa1a3c39291ada9d8d9207 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Mon, 28 Mar 2022 00:21:55 -0700 Subject: [PATCH 17/29] #411: used getter method for accessing the timebounds --- src/main/java/org/stellar/sdk/Transaction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/stellar/sdk/Transaction.java b/src/main/java/org/stellar/sdk/Transaction.java index 653190396..c79a1a30d 100644 --- a/src/main/java/org/stellar/sdk/Transaction.java +++ b/src/main/java/org/stellar/sdk/Transaction.java @@ -174,7 +174,7 @@ private TransactionV0 toXdr() { transaction.setSourceAccountEd25519(StrKey.encodeToXDRAccountId(this.mSourceAccount).getAccountID().getEd25519()); transaction.setOperations(operations); transaction.setMemo(mMemo.toXdr()); - transaction.setTimeBounds(mPreconditions.getTimeBounds() == null ? null : getTimeBounds().toXdr()); + transaction.setTimeBounds(getTimeBounds() == null ? null : getTimeBounds().toXdr()); transaction.setExt(ext); return transaction; } From 4d11a21edab2760f31d55f45e5de114a55da728a Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Wed, 30 Mar 2022 14:14:20 -0700 Subject: [PATCH 18/29] #411: consolidate TimeBounds usage a bit further through Preconditions, more tests --- .../sdk/SequentialSequenceNumberStrategy.java | 10 ++- src/main/java/org/stellar/sdk/TimeBounds.java | 2 +- .../java/org/stellar/sdk/Transaction.java | 6 +- .../org/stellar/sdk/TransactionBuilder.java | 30 ++++----- .../stellar/sdk/TransactionPreconditions.java | 1 + .../stellar/sdk/ClaimableBalanceIdTest.java | 8 +-- .../java/org/stellar/sdk/OperationTest.java | 2 +- src/test/java/org/stellar/sdk/ServerTest.java | 24 +++---- .../stellar/sdk/TransactionBuilderTest.java | 67 ++++++++++++++----- .../sdk/TransactionPreconditionsTest.java | 11 +++ 10 files changed, 106 insertions(+), 55 deletions(-) diff --git a/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java b/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java index b6cdf521d..40cfac8b3 100644 --- a/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java +++ b/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java @@ -1,5 +1,11 @@ package org.stellar.sdk; +/** + * Default transaction builder strategy implementation for obtaining the sequence number that a transaction will + * take and how the transaction's source account sequence number will be updated after the builder finishes + * building the transaction instance. + * + */ public class SequentialSequenceNumberStrategy implements SequenceNumberStrategy{ @Override public long getSequenceNumber(TransactionBuilderAccount account) { @@ -7,7 +13,7 @@ public long getSequenceNumber(TransactionBuilderAccount account) { } @Override - public void updateSourceAccount(long newSequnceNumber, TransactionBuilderAccount account) { - account.incrementSequenceNumber(); + public void updateSourceAccount(long newSequenceNumber, TransactionBuilderAccount account) { + account.setSequenceNumber(newSequenceNumber); } } diff --git a/src/main/java/org/stellar/sdk/TimeBounds.java b/src/main/java/org/stellar/sdk/TimeBounds.java index 0e037e80f..2d00a3175 100644 --- a/src/main/java/org/stellar/sdk/TimeBounds.java +++ b/src/main/java/org/stellar/sdk/TimeBounds.java @@ -25,7 +25,7 @@ public TimeBounds(long minTime, long maxTime) { throw new IllegalArgumentException("maxTime cannot be negative"); } - if (maxTime != 0 && minTime > maxTime) { + if (maxTime != TransactionPreconditions.TIMEOUT_INFINITE && minTime > maxTime) { throw new IllegalArgumentException("minTime must be >= maxTime"); } diff --git a/src/main/java/org/stellar/sdk/Transaction.java b/src/main/java/org/stellar/sdk/Transaction.java index c79a1a30d..b7b7d2192 100644 --- a/src/main/java/org/stellar/sdk/Transaction.java +++ b/src/main/java/org/stellar/sdk/Transaction.java @@ -84,13 +84,17 @@ public Memo getMemo() { } /** - * @return Preconditions + * Get the pre-conditions for the transaction + * + * @return TransactionPreconditions */ public TransactionPreconditions getPreconditions() { return mPreconditions; } /** + * Gets the time bounds defined for the transaction. + * * @return TimeBounds */ public TimeBounds getTimeBounds() { diff --git a/src/main/java/org/stellar/sdk/TransactionBuilder.java b/src/main/java/org/stellar/sdk/TransactionBuilder.java index 9333c7edd..0203387a9 100644 --- a/src/main/java/org/stellar/sdk/TransactionBuilder.java +++ b/src/main/java/org/stellar/sdk/TransactionBuilder.java @@ -22,11 +22,8 @@ public class TransactionBuilder { private Network mNetwork; private SequenceNumberStrategy sequenceNumberStrategy; private TransactionPreconditions mPreconditions; - private TimeBounds mTimeBounds; private boolean mTimeoutSet; - public static final long TIMEOUT_INFINITE = 0; - /** * Construct a new transaction builder. * @@ -42,6 +39,7 @@ public TransactionBuilder(AccountConverter accountConverter, TransactionBuilderA mNetwork = checkNotNull(network, "Network cannot be null"); mOperations = newArrayList(); sequenceNumberStrategy = new SequentialSequenceNumberStrategy(); + mPreconditions = TransactionPreconditions.builder().build(); } /** @@ -138,7 +136,11 @@ public TransactionBuilder addMemo(Memo memo) { */ public TransactionBuilder addTimeBounds(TimeBounds timeBounds) { checkNotNull(timeBounds, "timeBounds cannot be null"); - mTimeBounds = timeBounds; + if (mPreconditions.getTimeBounds() != null) { + throw new RuntimeException("TimeBounds already set."); + } + + mPreconditions = mPreconditions.toBuilder().timeBounds(timeBounds).build(); return this; } @@ -162,7 +164,7 @@ public TransactionBuilder addTimeBounds(TimeBounds timeBounds) { * set instead for more control over preconditions. */ public TransactionBuilder setTimeout(long timeout) { - if (mTimeBounds != null && mTimeBounds.getMaxTime() > 0) { + if (mPreconditions.getTimeBounds() != null && mPreconditions.getTimeBounds().getMaxTime() != TransactionPreconditions.TIMEOUT_INFINITE) { throw new RuntimeException("TimeBounds.max_time has been already set - setting timeout would overwrite it."); } @@ -173,11 +175,13 @@ public TransactionBuilder setTimeout(long timeout) { mTimeoutSet = true; if (timeout > 0) { long timeoutTimestamp = System.currentTimeMillis() / 1000L + timeout; - if (mTimeBounds == null) { - mTimeBounds = new TimeBounds(0, timeoutTimestamp); + TransactionPreconditionsBuilder preconditionsBuilder = mPreconditions.toBuilder(); + if (mPreconditions.getTimeBounds() == null) { + preconditionsBuilder.timeBounds(new TimeBounds(0, timeoutTimestamp)); } else { - mTimeBounds = new TimeBounds(mTimeBounds.getMinTime(), timeoutTimestamp); + preconditionsBuilder.timeBounds( new TimeBounds(mPreconditions.getTimeBounds().getMinTime(), timeoutTimestamp)); } + mPreconditions = preconditionsBuilder.build(); } return this; } @@ -196,16 +200,6 @@ public TransactionBuilder setBaseFee(int baseFee) { * sequence number after transaction is constructed. */ public Transaction build() { - TransactionPreconditionsBuilder preconditionsBuilder = mPreconditions != null ? mPreconditions.toBuilder() : TransactionPreconditions.builder(); - - // left in for backwards compatibility, check on TransactionBuilder.mTimeBounds should be removed when both TransactionBuilder.setTimeBounds() and - // TransactionBuilder.setTimeout() are removed due to deprecation. This currently allows the builder to override - // any timebounds in new preconditions with timebounds directly from legacy timebounds/timeouts. - if (mTimeBounds != null) { - preconditionsBuilder.timeBounds(mTimeBounds); - } - - mPreconditions = preconditionsBuilder.build(); mPreconditions.isValid(mTimeoutSet); if (mBaseFee == null) { diff --git a/src/main/java/org/stellar/sdk/TransactionPreconditions.java b/src/main/java/org/stellar/sdk/TransactionPreconditions.java index 2e49b819a..2422108d5 100644 --- a/src/main/java/org/stellar/sdk/TransactionPreconditions.java +++ b/src/main/java/org/stellar/sdk/TransactionPreconditions.java @@ -23,6 +23,7 @@ */ public class TransactionPreconditions { public static final long MAX_EXTRA_SIGNERS_COUNT = 2; + public static final long TIMEOUT_INFINITE = 0; LedgerBounds ledgerBounds; Long minSeqNumber; diff --git a/src/test/java/org/stellar/sdk/ClaimableBalanceIdTest.java b/src/test/java/org/stellar/sdk/ClaimableBalanceIdTest.java index 642ec2144..f5b7c8fb7 100644 --- a/src/test/java/org/stellar/sdk/ClaimableBalanceIdTest.java +++ b/src/test/java/org/stellar/sdk/ClaimableBalanceIdTest.java @@ -30,7 +30,7 @@ public void testClaimableBalanceIds() throws IOException { .addOperation(op0) .addOperation(new BumpSequenceOperation.Builder(2l).build()) .addOperation(op0) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); @@ -53,7 +53,7 @@ public void testClaimableBalanceIds() throws IOException { )).setSourceAccount("GABXJTV7ELEB2TQZKJYEGXBUIG6QODJULKJDI65KZMIZZG2EACJU5EA7").build(); transaction = new TransactionBuilder(AccountConverter.enableMuxed(), new Account(sourceAccount, 123l), Network.TESTNET) .addOperation(opWithSourceAccount) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); @@ -62,7 +62,7 @@ public void testClaimableBalanceIds() throws IOException { transaction = new TransactionBuilder(AccountConverter.enableMuxed(), new Account(sourceAccount, 124l), Network.TESTNET) .addOperation(opWithSourceAccount) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); @@ -81,7 +81,7 @@ public void testClaimableBalanceIds() throws IOException { transaction = new TransactionBuilder(AccountConverter.enableMuxed(), new Account(StrKey.encodeStellarMuxedAccount(muxedAccount), 123l), Network.TESTNET) .addOperation(op0) .addOperation(new BumpSequenceOperation.Builder(2l).build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); diff --git a/src/test/java/org/stellar/sdk/OperationTest.java b/src/test/java/org/stellar/sdk/OperationTest.java index ae7b64157..c3faec2a2 100644 --- a/src/test/java/org/stellar/sdk/OperationTest.java +++ b/src/test/java/org/stellar/sdk/OperationTest.java @@ -519,7 +519,7 @@ public void testSetOptionsOperationPreAuthTxSigner() { Account account = new Account(source.getAccountId(), sequenceNumber); Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); diff --git a/src/test/java/org/stellar/sdk/ServerTest.java b/src/test/java/org/stellar/sdk/ServerTest.java index bf0f6536d..41d07d90d 100644 --- a/src/test/java/org/stellar/sdk/ServerTest.java +++ b/src/test/java/org/stellar/sdk/ServerTest.java @@ -181,7 +181,7 @@ Transaction buildTransaction(Network network) throws IOException { .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) .addMemo(Memo.text("Hello world!")) .setBaseFee(Transaction.MIN_BASE_FEE) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE); + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE); assertEquals(1, builder.getOperationsCount()); Transaction transaction = builder.build(); @@ -377,7 +377,7 @@ public void testCheckMemoRequiredWithMemo() throws IOException, AccountRequiresM .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_B, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_C, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_D).build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .addMemo(new MemoText("Hello, Stellar.")) .setBaseFee(100) .build(); @@ -401,7 +401,7 @@ public void testCheckMemoRequiredWithMemoIdAddress() throws IOException, Account .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_ID, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_ID, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_MEMO_ID).build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(100) .build(); @@ -425,7 +425,7 @@ public void testCheckMemoRequiredWithSkipCheck() throws IOException, AccountRequ .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -448,7 +448,7 @@ public void testCheckMemoRequiredWithPaymentOperationNoMemo() throws IOException .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -486,7 +486,7 @@ public void testCheckMemoRequiredWithPathPaymentStrictReceiveOperationNoMemo() t .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_B, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -524,7 +524,7 @@ public void testCheckMemoRequiredWithPathPaymentStrictSendOperationNoMemo() thro .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_C, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -562,7 +562,7 @@ public void testCheckMemoRequiredWithAccountMergeOperationNoMemo() throws IOExce .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_D).build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -600,7 +600,7 @@ public void testCheckMemoRequiredTwoOperationsWithSameDestination() throws IOExc .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_C, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_D).build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -639,7 +639,7 @@ public void testCheckMemoRequiredNoDestinationOperation() throws IOException { .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_A, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_C, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -677,7 +677,7 @@ public void testCheckMemoRequiredAccountNotFound() throws IOException, AccountRe .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_FOUND, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_FOUND, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_FOUND).build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); @@ -702,7 +702,7 @@ public void testCheckMemoRequiredFetchAccountError() throws IOException, Account .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_C, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_D, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build()) .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_D).build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(100) .build(); transaction.sign(source); diff --git a/src/test/java/org/stellar/sdk/TransactionBuilderTest.java b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java index 97ba1eb91..aa0734166 100644 --- a/src/test/java/org/stellar/sdk/TransactionBuilderTest.java +++ b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java @@ -28,7 +28,7 @@ public void testMissingOperationFee() { try { new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR", "2000").build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .build(); fail("expected RuntimeException"); } catch (RuntimeException e) { @@ -46,7 +46,7 @@ public void testBuilderSuccessTestnet() throws FormatException { Account account = new Account(source.getAccountId(), sequenceNumber); Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); @@ -75,7 +75,7 @@ public void testBuilderMemoText() throws FormatException { Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) .addMemo(Memo.text("Hello world!")) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); @@ -131,7 +131,7 @@ public void testBuilderBaseFee() throws FormatException { Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) .setBaseFee(200) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .build(); transaction.sign(source); @@ -226,7 +226,7 @@ public void testBuilderSetsLedgerBounds() throws IOException { Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(newAccount.getAccountId(), "2000").build()) .addPreconditions(TransactionPreconditions.builder() - .timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE)) + .timeBounds(new TimeBounds(0L,TransactionPreconditions.TIMEOUT_INFINITE)) .ledgerBounds(LedgerBounds.builder().minLedger(1).maxLedger(2).build()) .build()) .setBaseFee(Transaction.MIN_BASE_FEE) @@ -250,13 +250,13 @@ public void testBuilderSetsMinSeqNum() throws IOException { SequenceNumber seqNum = new SequenceNumber(); seqNum.setSequenceNumber(new Int64(5L)); - preconditionsV2.timeBounds(TransactionBuilder.buildTimeBounds(0, TransactionBuilder.TIMEOUT_INFINITE)); + preconditionsV2.timeBounds(TransactionBuilder.buildTimeBounds(0, TransactionPreconditions.TIMEOUT_INFINITE)); preconditionsV2.minSeqNum(seqNum); Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(newAccount.getAccountId(), "2000").build()) .addPreconditions(TransactionPreconditions.builder() - .timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE)) + .timeBounds(new TimeBounds(0L,TransactionPreconditions.TIMEOUT_INFINITE)) .minSeqNumber(5L) .build()) .setBaseFee(Transaction.MIN_BASE_FEE) @@ -278,7 +278,7 @@ public void testBuilderSetsMinSeqAge() throws IOException { Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(newAccount.getAccountId(), "2000").build()) .addPreconditions(TransactionPreconditions.builder() - .timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE)) + .timeBounds(new TimeBounds(0L,TransactionPreconditions.TIMEOUT_INFINITE)) .minSeqAge(5L) .build()) .setBaseFee(Transaction.MIN_BASE_FEE) @@ -300,7 +300,7 @@ public void testBuilderSetsMinSeqLedgerGap() throws IOException { Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(newAccount.getAccountId(), "2000").build()) .addPreconditions(TransactionPreconditions.builder() - .timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE)) + .timeBounds(new TimeBounds(0L,TransactionPreconditions.TIMEOUT_INFINITE)) .minSeqLedgerGap(5) .build()) .setBaseFee(Transaction.MIN_BASE_FEE) @@ -333,7 +333,7 @@ public void testBuilderExtraSigners() throws IOException { Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(newAccount.getAccountId(), "2000").build()) .addPreconditions(TransactionPreconditions.builder() - .timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE)) + .timeBounds(new TimeBounds(0L,TransactionPreconditions.TIMEOUT_INFINITE)) .extraSigners(newArrayList(signerKey)) .minSeqLedgerGap(5) .build()) @@ -356,7 +356,7 @@ public void testBuilderFailsWhenTooManyExtraSigners() throws IOException { new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) .addPreconditions(TransactionPreconditions.builder() - .timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE)) + .timeBounds(new TimeBounds(0L,TransactionPreconditions.TIMEOUT_INFINITE)) .extraSigners(newArrayList(new SignerKey.Builder().build(), new SignerKey.Builder().build(), new SignerKey.Builder().build())) .minSeqLedgerGap(5) .build()) @@ -366,13 +366,32 @@ public void testBuilderFailsWhenTooManyExtraSigners() throws IOException { } catch (FormatException ignored){} } + @Test + public void testBuilderFailsWhenTimeoutLessThanTimeBoundsMinimum() throws Exception { + Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + + try { + new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .addPreconditions(TransactionPreconditions.builder() + // set min time to 120 seconds from now + .timeBounds(new TimeBounds((System.currentTimeMillis() / 1000 )+ 120, TransactionPreconditions.TIMEOUT_INFINITE)) + .build()) + .setBaseFee(Transaction.MIN_BASE_FEE) + .setTimeout(1); // sat max time to 1 second from now + fail(); + } catch (IllegalArgumentException exception){ + assertTrue(exception.getMessage().contains("minTime must be >= maxTime")); + } + } + @Test public void testBuilderUsesCustomSequence() throws IOException { Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) .addPreconditions(TransactionPreconditions.builder() - .timeBounds(new TimeBounds(0L,TransactionBuilder.TIMEOUT_INFINITE)).build()) + .timeBounds(new TimeBounds(0L,TransactionPreconditions.TIMEOUT_INFINITE)).build()) .addSequenceNumberStrategy(new SequenceNumberStrategy() { @Override public long getSequenceNumber(TransactionBuilderAccount account) { @@ -412,6 +431,22 @@ public void testBuilderFailsWhenSettingTimeoutAndMaxTimeAlreadySet() throws IOEx } } + @Test + public void testBuilderFailsWhenSettingTimeboundsAndAlreadySet() throws IOException { + Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + try { + new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .setTimeout(10) + .addTimeBounds(new TimeBounds(42, 1337)) + .build(); + fail(); + } catch (RuntimeException exception) { + assertTrue(exception.getMessage().contains("TimeBounds already set.")); + assertEquals(new Long(2908908335136768L), account.getSequenceNumber()); + } + } + @Test public void testBuilderTimeoutAndMaxTimeNotSet() throws IOException { Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); @@ -437,7 +472,7 @@ public void testBuilderInfinteTimeoutAndMaxTimeNotSet() throws FormatException, Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) .addTimeBounds(new TimeBounds(42, 0)) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .addMemo(Memo.hash("abcdef")) .setBaseFee(100) .build(); @@ -462,7 +497,7 @@ public void testBuilderSuccessPublic() throws FormatException, IOException { Account account = new Account(source.getAccountId(), 2908908335136768L); Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.PUBLIC) .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); @@ -484,7 +519,7 @@ public void testNoOperations() throws FormatException, IOException { Account account = new Account(source.getAccountId(), 2908908335136768L); try { Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); fail(); @@ -523,7 +558,7 @@ public void testNoNetworkSet() throws FormatException { new TransactionBuilder(AccountConverter.enableMuxed(), account, null) .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) .addMemo(Memo.none()) - .setTimeout(TransactionBuilder.TIMEOUT_INFINITE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .build(); fail(); } catch (NullPointerException e) { diff --git a/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java b/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java index 157dfd510..3bac09291 100644 --- a/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java +++ b/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java @@ -116,6 +116,17 @@ public void itChecksValidityWhenNoTimeboundsButTimeoutSet() { preconditions.isValid(true); } + @Test + public void itChecksValidityWhenNoTimeboundsAndNoTimeoutSet() { + TransactionPreconditions preconditions = TransactionPreconditions.builder().build(); + try { + preconditions.isValid(false); + fail(); + } catch (FormatException exception) { + assertTrue(exception.getMessage().contains("Invalid preconditions, must define timebounds or set infinite timeout")); + } + } + @Test public void itChecksV2Status() { From 810dc04d7f99e083b2f3b3477262c2d9ed015ed6 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Wed, 30 Mar 2022 17:37:56 -0700 Subject: [PATCH 19/29] #411: added more test coverage on v1/v2 precondition conversions --- .../stellar/sdk/TransactionPreconditions.java | 18 +-- .../stellar/sdk/TransactionBuilderTest.java | 119 +++++++++++++++--- .../sdk/TransactionPreconditionsTest.java | 93 +++++++++++++- .../java/org/stellar/sdk/TransactionTest.java | 6 +- 4 files changed, 205 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/stellar/sdk/TransactionPreconditions.java b/src/main/java/org/stellar/sdk/TransactionPreconditions.java index 2422108d5..f9455e7f2 100644 --- a/src/main/java/org/stellar/sdk/TransactionPreconditions.java +++ b/src/main/java/org/stellar/sdk/TransactionPreconditions.java @@ -27,8 +27,8 @@ public class TransactionPreconditions { LedgerBounds ledgerBounds; Long minSeqNumber; - Long minSeqAge; - Integer minSeqLedgerGap; + long minSeqAge; + int minSeqLedgerGap; @Singular @NonNull List extraSigners; @@ -45,19 +45,23 @@ public void isValid(boolean infiniteTimeoutWasSet) { } public boolean hasV2() { - return (ledgerBounds != null || minSeqLedgerGap != null || minSeqAge != null || minSeqNumber != null || !extraSigners.isEmpty()); + return (ledgerBounds != null || + (minSeqLedgerGap > 0) || + (minSeqAge > 0) || + minSeqNumber != null || + !extraSigners.isEmpty()); } public static TransactionPreconditions fromXdr(Preconditions preconditions) { TransactionPreconditionsBuilder builder = new TransactionPreconditionsBuilder(); if (preconditions.getDiscriminant().equals(PreconditionType.PRECOND_V2)) { - if (preconditions.getTimeBounds() != null) { + if (preconditions.getV2().getTimeBounds() != null) { builder.timeBounds(new TimeBounds( preconditions.getV2().getTimeBounds().getMinTime().getTimePoint().getUint64(), preconditions.getV2().getTimeBounds().getMaxTime().getTimePoint().getUint64())); } - if (preconditions.getV2().getExtraSigners().length > 0) { + if (preconditions.getV2().getExtraSigners() != null && preconditions.getV2().getExtraSigners().length > 0) { builder.extraSigners(Arrays.asList(preconditions.getV2().getExtraSigners())); } if (preconditions.getV2().getMinSeqAge() != null) { @@ -90,7 +94,7 @@ public Preconditions toXdr() { PreconditionsV2.Builder v2Builder = new PreconditionsV2.Builder(); v2Builder.extraSigners(extraSigners.toArray(new SignerKey[]{})); - v2Builder.minSeqAge(new Duration(new Int64(minSeqAge != null ? minSeqAge : 0L))); + v2Builder.minSeqAge(new Duration(new Int64(minSeqAge))); if (ledgerBounds != null) { v2Builder.ledgerBounds(new org.stellar.sdk.xdr.LedgerBounds.Builder() @@ -102,7 +106,7 @@ public Preconditions toXdr() { v2Builder.minSeqNum(new SequenceNumber(new Int64(minSeqNumber))); } - v2Builder.minSeqLedgerGap(new Uint32(minSeqLedgerGap != null ? minSeqLedgerGap : 0)); + v2Builder.minSeqLedgerGap(new Uint32(minSeqLedgerGap)); if (timeBounds != null) { v2Builder.timeBounds(timeBounds.toXdr()); diff --git a/src/test/java/org/stellar/sdk/TransactionBuilderTest.java b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java index aa0734166..937bc404e 100644 --- a/src/test/java/org/stellar/sdk/TransactionBuilderTest.java +++ b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java @@ -37,7 +37,7 @@ public void testMissingOperationFee() { } @Test - public void testBuilderSuccessTestnet() throws FormatException { + public void testBuilderSuccessTestnet() throws Exception { // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); @@ -60,13 +60,19 @@ public void testBuilderSuccessTestnet() throws FormatException { "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAO3gUmG83C+VCqO6FztuMtXJF/l7grZA7MjRzqdZ9W8QAAAABKgXyAAAAAAAAAAAAbaHlWIAAABAy5IvTou9NDetC6PIFJhBR2yr2BuEEql4iyLfU9K7tjuQaYVZf40fbWLRwA/lHg2IYFzYMFMakxLtVrpLxMmHAw==", transaction.toEnvelopeXdrBase64()); - Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); - + // Convert transaction to binary XDR and back again to make sure correctly xdr de/serialized. + XdrDataInputStream is = new XdrDataInputStream( + new ByteArrayInputStream( + javax.xml.bind.DatatypeConverter.parseBase64Binary(transaction.toEnvelopeXdrBase64()) + ) + ); + org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), decodedTransaction, Network.TESTNET); assertEquals(transaction, transaction2); } @Test - public void testBuilderMemoText() throws FormatException { + public void testBuilderMemoText() throws Exception { // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); @@ -81,12 +87,19 @@ public void testBuilderMemoText() throws FormatException { transaction.sign(source); - Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); + // Convert transaction to binary XDR and back again to make sure correctly xdr de/serialized. + XdrDataInputStream is = new XdrDataInputStream( + new ByteArrayInputStream( + javax.xml.bind.DatatypeConverter.parseBase64Binary(transaction.toEnvelopeXdrBase64()) + ) + ); + org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), decodedTransaction, Network.TESTNET); + assertEquals(transaction, transaction2); + assertEquals( "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAAAAAABAAAADEhlbGxvIHdvcmxkIQAAAAEAAAAAAAAAAAAAAADt4FJhvNwvlQqjuhc7bjLVyRf5e4K2QOzI0c6nWfVvEAAAAASoF8gAAAAAAAAAAAG2h5ViAAAAQMc6HwYaGsrlJ8/LdE9VDVq04JifpQofSmnjhrtqaTTs/VBsNGmxi4b/vaFkLLLWh8emI8FsS/vBgb8AVFVkZQU=", transaction.toEnvelopeXdrBase64()); - - assertEquals(transaction, transaction2); } @Test @@ -116,13 +129,12 @@ public void testBuilderTimeBounds() throws FormatException, IOException { assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMinTime().getTimePoint().getUint64().longValue(), 42); assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMaxTime().getTimePoint().getUint64().longValue(), 1337); - Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); - + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), decodedTransaction, Network.TESTNET); assertEquals(transaction, transaction2); } @Test - public void testBuilderBaseFee() throws FormatException { + public void testBuilderBaseFee() throws Exception { // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4 KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); @@ -136,12 +148,19 @@ public void testBuilderBaseFee() throws FormatException { transaction.sign(source); - Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdr(), Network.TESTNET); + // Convert transaction to binary XDR and back again to make sure correctly xdr de/serialized. + XdrDataInputStream is = new XdrDataInputStream( + new ByteArrayInputStream( + javax.xml.bind.DatatypeConverter.parseBase64Binary(transaction.toEnvelopeXdrBase64()) + ) + ); + org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), decodedTransaction, Network.TESTNET); + assertEquals(transaction, transaction2); + assertEquals( "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAMgAClWjAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAO3gUmG83C+VCqO6FztuMtXJF/l7grZA7MjRzqdZ9W8QAAAABKgXyAAAAAAAAAAAAbaHlWIAAABA9TG3dKKLtLHzRUbsbEqr68CfUc800p1/LE5pWzCnFdFdypdXgyqHqw/sWdaTUMDiWawBtsmqV8oOtD0Hw1HDDQ==", transaction.toEnvelopeXdrBase64()); - - assertEquals(transaction, transaction2); } @Test @@ -171,6 +190,16 @@ public void testBuilderTimebounds() throws IOException { assertEquals(42, transaction.getTimeBounds().getMinTime()); assertEquals(1337, transaction.getTimeBounds().getMaxTime()); + + // Convert transaction to binary XDR and back again to make sure correctly xdr de/serialized. + XdrDataInputStream is = new XdrDataInputStream( + new ByteArrayInputStream( + javax.xml.bind.DatatypeConverter.parseBase64Binary(transaction.toEnvelopeXdrBase64()) + ) + ); + org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), decodedTransaction, Network.TESTNET); + assertEquals(transaction, transaction2); } @Test @@ -215,6 +244,16 @@ public void testBuilderTimeout() throws IOException { assertEquals(0, transaction.getTimeBounds().getMinTime()); assertTrue(currentUnix + 10 <= transaction.getTimeBounds().getMaxTime()); + + // Convert transaction to binary XDR and back again to make sure timebounds are correctly de/serialized. + XdrDataInputStream is = new XdrDataInputStream( + new ByteArrayInputStream( + javax.xml.bind.DatatypeConverter.parseBase64Binary(transaction.toEnvelopeXdrBase64()) + ) + ); + org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), decodedTransaction, Network.TESTNET); + assertEquals(transaction, transaction2); } @Test @@ -235,10 +274,20 @@ public void testBuilderSetsLedgerBounds() throws IOException { assertEquals(1, transaction.getPreconditions().getLedgerBounds().getMinLedger()); assertEquals(2, transaction.getPreconditions().getLedgerBounds().getMaxLedger()); + XdrDataInputStream is = new XdrDataInputStream( + new ByteArrayInputStream( + javax.xml.bind.DatatypeConverter.parseBase64Binary(transaction.toEnvelopeXdrBase64()) + ) + ); + org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), decodedTransaction, Network.TESTNET); + assertEquals(transaction, transaction2); + assertEquals( "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAADt4FJhvNwvlQqjuhc7bjLVyRf5e4K2QOzI0c6nWfVvEAAAAASoF8gAAAAAAAAAAAA=", transaction.toEnvelopeXdrBase64()); + } @Test @@ -264,6 +313,16 @@ public void testBuilderSetsMinSeqNum() throws IOException { assertEquals(Long.valueOf(5), transaction.getPreconditions().getMinSeqNumber()); + // Convert transaction to binary XDR and back again to make sure correctly xdr de/serialized. + XdrDataInputStream is = new XdrDataInputStream( + new ByteArrayInputStream( + javax.xml.bind.DatatypeConverter.parseBase64Binary(transaction.toEnvelopeXdrBase64()) + ) + ); + org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), decodedTransaction, Network.TESTNET); + assertEquals(transaction, transaction2); + assertEquals( "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAADt4FJhvNwvlQqjuhc7bjLVyRf5e4K2QOzI0c6nWfVvEAAAAASoF8gAAAAAAAAAAAA=", transaction.toEnvelopeXdrBase64()); @@ -284,7 +343,17 @@ public void testBuilderSetsMinSeqAge() throws IOException { .setBaseFee(Transaction.MIN_BASE_FEE) .build(); - assertEquals(Long.valueOf(5), transaction.getPreconditions().getMinSeqAge()); + assertEquals(5, transaction.getPreconditions().getMinSeqAge()); + + // Convert transaction to binary XDR and back again to make sure correctly xdr de/serialized. + XdrDataInputStream is = new XdrDataInputStream( + new ByteArrayInputStream( + javax.xml.bind.DatatypeConverter.parseBase64Binary(transaction.toEnvelopeXdrBase64()) + ) + ); + org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), decodedTransaction, Network.TESTNET); + assertEquals(transaction, transaction2); assertEquals( "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAA7eBSYbzcL5UKo7oXO24y1ckX+XuCtkDsyNHOp1n1bxAAAAAEqBfIAAAAAAAAAAAA", @@ -306,7 +375,17 @@ public void testBuilderSetsMinSeqLedgerGap() throws IOException { .setBaseFee(Transaction.MIN_BASE_FEE) .build(); - assertEquals(Integer.valueOf(5), transaction.getPreconditions().getMinSeqLedgerGap()); + assertEquals(5, transaction.getPreconditions().getMinSeqLedgerGap()); + + // Convert transaction to binary XDR and back again to make sure correctly xdr de/serialized. + XdrDataInputStream is = new XdrDataInputStream( + new ByteArrayInputStream( + javax.xml.bind.DatatypeConverter.parseBase64Binary(transaction.toEnvelopeXdrBase64()) + ) + ); + org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), decodedTransaction, Network.TESTNET); + assertEquals(transaction, transaction2); assertEquals( "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAA7eBSYbzcL5UKo7oXO24y1ckX+XuCtkDsyNHOp1n1bxAAAAAEqBfIAAAAAAAAAAAA", @@ -343,6 +422,16 @@ public void testBuilderExtraSigners() throws IOException { assertEquals(SignerKeyType.SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD, newArrayList(transaction.getPreconditions().getExtraSigners()).get(0).getDiscriminant()); assertArrayEquals(payload, newArrayList(transaction.getPreconditions().getExtraSigners()).get(0).getEd25519SignedPayload().getPayload()); + // Convert transaction to binary XDR and back again to make sure correctly xdr de/serialized. + XdrDataInputStream is = new XdrDataInputStream( + new ByteArrayInputStream( + javax.xml.bind.DatatypeConverter.parseBase64Binary(transaction.toEnvelopeXdrBase64()) + ) + ); + org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); + Transaction transaction2 = (Transaction)Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), decodedTransaction, Network.TESTNET); + assertEquals(transaction, transaction2); + assertEquals( "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAQAAAAM/DDS/k60NmXHQTMyQ9wVRHIOKrZc0pKL7DXoD/H/omgAAACABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fIAAAAAAAAAABAAAAAAAAAAAAAAAA7eBSYbzcL5UKo7oXO24y1ckX+XuCtkDsyNHOp1n1bxAAAAAEqBfIAAAAAAAAAAAA", transaction.toEnvelopeXdrBase64()); diff --git a/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java b/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java index 3bac09291..a53abe12d 100644 --- a/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java +++ b/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java @@ -1,5 +1,6 @@ package org.stellar.sdk; +import com.google.common.io.BaseEncoding; import org.junit.Test; import org.stellar.sdk.xdr.Duration; import org.stellar.sdk.xdr.Int64; @@ -8,7 +9,17 @@ import org.stellar.sdk.xdr.PreconditionsV2; import org.stellar.sdk.xdr.SequenceNumber; import org.stellar.sdk.xdr.SignerKey; +import org.stellar.sdk.xdr.SignerKeyType; +import org.stellar.sdk.xdr.TimePoint; +import org.stellar.sdk.xdr.Uint256; import org.stellar.sdk.xdr.Uint32; +import org.stellar.sdk.xdr.Uint64; +import org.stellar.sdk.xdr.XdrDataInputStream; +import org.stellar.sdk.xdr.XdrDataOutputStream; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import static com.google.common.collect.Lists.newArrayList; import static org.junit.Assert.assertEquals; @@ -19,7 +30,7 @@ public class TransactionPreconditionsTest { @Test - public void itConvertsFromXdr() { + public void itConvertsFromXdr() throws IOException { Preconditions.Builder preconditionsBuilder = new Preconditions.Builder(); preconditionsBuilder.discriminant(PreconditionType.PRECOND_V2); @@ -32,53 +43,123 @@ public void itConvertsFromXdr() { .maxLedger(new Uint32(2)) .build()); v2Builder.minSeqNum(new SequenceNumber(new Int64(4L))); + v2Builder.minSeqLedgerGap(new Uint32(0)); preconditionsBuilder.v2(v2Builder.build()); Preconditions xdr = preconditionsBuilder.build(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + xdr.encode(new XdrDataOutputStream(baos)); + xdr = Preconditions.decode(new XdrDataInputStream(new ByteArrayInputStream(baos.toByteArray()))); + TransactionPreconditions transactionPreconditions = TransactionPreconditions.fromXdr(xdr); - assertEquals(transactionPreconditions.getMinSeqAge(), Long.valueOf(2)); + assertEquals(transactionPreconditions.getMinSeqAge(), 2); assertEquals(transactionPreconditions.getLedgerBounds().getMinLedger(), 1); assertEquals(transactionPreconditions.getLedgerBounds().getMaxLedger(), 2); assertEquals(transactionPreconditions.getMinSeqNumber(), Long.valueOf(4)); + assertEquals(transactionPreconditions.getMinSeqLedgerGap(), 0); } @Test - public void itConvertsToV2Xdr() { + public void itRoundTripsFromV2ToV1IfOnlyTimeboundsPresent() throws IOException { + + Preconditions.Builder preconditionsBuilder = new Preconditions.Builder(); + preconditionsBuilder.discriminant(PreconditionType.PRECOND_V2); + PreconditionsV2.Builder v2Builder = new PreconditionsV2.Builder(); + org.stellar.sdk.xdr.TimeBounds xdrTimeBounds = new org.stellar.sdk.xdr.TimeBounds.Builder().minTime(new TimePoint(new Uint64(1L))).maxTime(new TimePoint(new Uint64(2L))).build(); + v2Builder.timeBounds(xdrTimeBounds); + v2Builder.minSeqLedgerGap(new Uint32(0)); + v2Builder.minSeqAge(new Duration(new Int64(0L))); + v2Builder.extraSigners(new SignerKey[]{}); + preconditionsBuilder.v2(v2Builder.build()); + // create V2 Precond with just timebounds + Preconditions xdr = preconditionsBuilder.build(); + + // serialize to binary + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + xdr.encode(new XdrDataOutputStream(baos)); + xdr = Preconditions.decode(new XdrDataInputStream(new ByteArrayInputStream(baos.toByteArray()))); + + // marshal it to pojo + TransactionPreconditions transactionPreconditions = TransactionPreconditions.fromXdr(xdr); + assertEquals(transactionPreconditions.getTimeBounds(), new TimeBounds(1L, 2L)); + + // marshal the pojo with just timebounds back to xdr, since only timebounds, precond type should be optimized to V!(PRECOND_TIME) + xdr = transactionPreconditions.toXdr(); + assertEquals(xdr.getDiscriminant() , PreconditionType.PRECOND_TIME); + assertEquals(xdr.getTimeBounds() , xdrTimeBounds); + assertNull(xdr.getV2()); + } + + @Test + public void itConvertsToV2Xdr() throws IOException { + + byte[] payload = BaseEncoding.base16().decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".toUpperCase()); + SignerKey signerKey = new SignerKey.Builder() + .discriminant(SignerKeyType.SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD) + .ed25519SignedPayload(new SignerKey.SignerKeyEd25519SignedPayload.Builder() + .payload(payload) + .ed25519(new Uint256(StrKey.decodeStellarAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"))) + .build()) + .build(); + TransactionPreconditions preconditions = TransactionPreconditions.builder() .timeBounds(new TimeBounds(1, 2)) .minSeqNumber(3L) - .extraSigners(newArrayList(new SignerKey.Builder().build(), new SignerKey.Builder().build(), new SignerKey.Builder().build())) + .extraSigners(newArrayList(signerKey, signerKey, signerKey)) .build(); Preconditions xdr = preconditions.toXdr(); + + // serialize to binary + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + xdr.encode(new XdrDataOutputStream(baos)); + xdr = Preconditions.decode(new XdrDataInputStream(new ByteArrayInputStream(baos.toByteArray()))); + assertEquals(xdr.getDiscriminant(), PreconditionType.PRECOND_V2); assertEquals(xdr.getV2().getTimeBounds().getMinTime().getTimePoint().getUint64(), Long.valueOf(1)); assertEquals(xdr.getV2().getTimeBounds().getMaxTime().getTimePoint().getUint64(), Long.valueOf(2)); assertEquals(xdr.getV2().getMinSeqNum().getSequenceNumber().getInt64(), Long.valueOf(3)); + // xdr encoding requires non-null for min ledger gap + assertEquals(xdr.getV2().getMinSeqLedgerGap().getUint32().intValue(), 0); + // xdr encoding requires non-null for min seq age + assertEquals(xdr.getV2().getMinSeqAge().getDuration().getInt64().longValue(), 0); assertEquals(xdr.getV2().getExtraSigners().length, 3); } @Test - public void itConvertsOnlyTimeBoundsXdr() { + public void itConvertsOnlyTimeBoundsXdr() throws IOException{ TransactionPreconditions preconditions = TransactionPreconditions.builder() .timeBounds(new TimeBounds(1, 2)) .build(); Preconditions xdr = preconditions.toXdr(); + + // serialize to binary + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + xdr.encode(new XdrDataOutputStream(baos)); + xdr = Preconditions.decode(new XdrDataInputStream(new ByteArrayInputStream(baos.toByteArray()))); + assertEquals(xdr.getDiscriminant(), PreconditionType.PRECOND_TIME); assertEquals(xdr.getTimeBounds().getMinTime().getTimePoint().getUint64(), Long.valueOf(1)); assertEquals(xdr.getTimeBounds().getMaxTime().getTimePoint().getUint64(), Long.valueOf(2)); + assertNull(xdr.getV2()); } @Test - public void itConvertsNullTimeBoundsXdr() { + public void itConvertsNullTimeBoundsXdr() throws IOException { // there was precedence in the sdk test coverage in TransactionTest.java for edge case of passing a null timebounds // into a transaction, which occurrs when infinite timeout is set and timebounds is not set through TransactionBuilder. // TransactionPreconditions continues to support that edge case. TransactionPreconditions preconditions = TransactionPreconditions.builder().build(); Preconditions xdr = preconditions.toXdr(); + + // serialize to binary + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + xdr.encode(new XdrDataOutputStream(baos)); + xdr = Preconditions.decode(new XdrDataInputStream(new ByteArrayInputStream(baos.toByteArray()))); + assertEquals(xdr.getDiscriminant(), PreconditionType.PRECOND_NONE); assertNull(xdr.getTimeBounds()); } diff --git a/src/test/java/org/stellar/sdk/TransactionTest.java b/src/test/java/org/stellar/sdk/TransactionTest.java index 529f4061e..e0630739b 100644 --- a/src/test/java/org/stellar/sdk/TransactionTest.java +++ b/src/test/java/org/stellar/sdk/TransactionTest.java @@ -34,7 +34,7 @@ public void testParseV0Transaction() throws FormatException, IOException { account.getIncrementedSequenceNumber(), new org.stellar.sdk.Operation[]{new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()}, null, - new TransactionPreconditions(null, null, null, null, new ArrayList(),null), + new TransactionPreconditions(null, null, 0, 0, new ArrayList(),null), Network.PUBLIC ); @@ -70,7 +70,7 @@ public void testSha256HashSigning() throws FormatException { account.getIncrementedSequenceNumber(), new org.stellar.sdk.Operation[]{new PaymentOperation.Builder(destination.getAccountId(), new AssetTypeNative(), "2000").build()}, null, - new TransactionPreconditions(null, null, null, null, new ArrayList(),null), + new TransactionPreconditions(null, null, 0, 0, new ArrayList(),null), Network.PUBLIC ); @@ -102,7 +102,7 @@ public void testToBase64EnvelopeXdrBuilderNoSignatures() throws FormatException, account.getIncrementedSequenceNumber(), new org.stellar.sdk.Operation[]{new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()}, null, - new TransactionPreconditions(null, null, null, null, new ArrayList(),null), + new TransactionPreconditions(null, null, 0, 0, new ArrayList(),null), Network.TESTNET ); From bf69ab8b06bc60d719e80961cb9d0573423b1f69 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Thu, 31 Mar 2022 10:55:03 -0700 Subject: [PATCH 20/29] #411: updated to latest protocol 19 xdr schemas --- src/main/java/org/stellar/sdk/Predicate.java | 12 +++---- .../stellar/sdk/TransactionPreconditions.java | 5 +-- .../sdk/responses/PredicateDeserializer.java | 2 +- .../org/stellar/sdk/xdr/ClaimPredicate.java | 32 +++++++++---------- .../java/org/stellar/sdk/xdr/Duration.java | 14 ++++---- .../sdk/xdr/InnerTransactionResult.java | 3 ++ .../org/stellar/sdk/xdr/LedgerBounds.java | 2 +- .../org/stellar/sdk/xdr/PreconditionsV2.java | 5 +-- .../sdk/xdr/TransactionResultCode.java | 5 ++- .../sdk/TransactionPreconditionsTest.java | 8 ++--- xdr/Stellar-ledger-entries.x | 8 ++--- xdr/Stellar-ledger.x | 2 +- xdr/Stellar-transaction.x | 13 +++++--- xdr/Stellar-types.x | 2 +- 14 files changed, 62 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/stellar/sdk/Predicate.java b/src/main/java/org/stellar/sdk/Predicate.java index fe908b4d0..892aec016 100644 --- a/src/main/java/org/stellar/sdk/Predicate.java +++ b/src/main/java/org/stellar/sdk/Predicate.java @@ -37,9 +37,9 @@ public static Predicate fromXdr(org.stellar.sdk.xdr.ClaimPredicate xdr) { case CLAIM_PREDICATE_NOT: return new Not(fromXdr(xdr.getNotPredicate())); case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME: - return new RelBefore(xdr.getRelBefore()); + return new RelBefore(xdr.getRelBefore().getInt64()); case CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME: - return new AbsBefore(xdr.getAbsBefore()); + return new AbsBefore(xdr.getAbsBefore().getInt64()); default: throw new IllegalArgumentException("Unknown asset type " + xdr.getDiscriminant()); } @@ -218,7 +218,7 @@ public int hashCode() { public ClaimPredicate toXdr() { org.stellar.sdk.xdr.ClaimPredicate xdr = new org.stellar.sdk.xdr.ClaimPredicate(); xdr.setDiscriminant(ClaimPredicateType.CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME); - xdr.setAbsBefore(timePoint); + xdr.setAbsBefore(new Int64(timePoint.getTimePoint().getUint64())); return xdr; } } @@ -234,11 +234,11 @@ public RelBefore(Duration secondsSinceClose) { } public RelBefore(long secondsSinceClose) { - this(new Duration(new Int64(secondsSinceClose))); + this(new Duration(new Uint64(secondsSinceClose))); } public long getSecondsSinceClose() { - return duration.getDuration().getInt64(); + return duration.getDuration().getUint64(); } @Override @@ -258,7 +258,7 @@ public int hashCode() { public ClaimPredicate toXdr() { org.stellar.sdk.xdr.ClaimPredicate xdr = new org.stellar.sdk.xdr.ClaimPredicate(); xdr.setDiscriminant(ClaimPredicateType.CLAIM_PREDICATE_BEFORE_RELATIVE_TIME); - xdr.setRelBefore(duration); + xdr.setRelBefore(new Int64(duration.getDuration().getUint64())); return xdr; } } diff --git a/src/main/java/org/stellar/sdk/TransactionPreconditions.java b/src/main/java/org/stellar/sdk/TransactionPreconditions.java index f9455e7f2..fe070aa74 100644 --- a/src/main/java/org/stellar/sdk/TransactionPreconditions.java +++ b/src/main/java/org/stellar/sdk/TransactionPreconditions.java @@ -12,6 +12,7 @@ import org.stellar.sdk.xdr.SequenceNumber; import org.stellar.sdk.xdr.SignerKey; import org.stellar.sdk.xdr.Uint32; +import org.stellar.sdk.xdr.Uint64; import java.util.Arrays; import java.util.List; @@ -65,7 +66,7 @@ public static TransactionPreconditions fromXdr(Preconditions preconditions) { builder.extraSigners(Arrays.asList(preconditions.getV2().getExtraSigners())); } if (preconditions.getV2().getMinSeqAge() != null) { - builder.minSeqAge(preconditions.getV2().getMinSeqAge().getDuration().getInt64()); + builder.minSeqAge(preconditions.getV2().getMinSeqAge().getDuration().getUint64()); } if (preconditions.getV2().getLedgerBounds() != null) { builder.ledgerBounds(LedgerBounds.fromXdr(preconditions.getV2().getLedgerBounds())); @@ -94,7 +95,7 @@ public Preconditions toXdr() { PreconditionsV2.Builder v2Builder = new PreconditionsV2.Builder(); v2Builder.extraSigners(extraSigners.toArray(new SignerKey[]{})); - v2Builder.minSeqAge(new Duration(new Int64(minSeqAge))); + v2Builder.minSeqAge(new Duration(new Uint64(minSeqAge))); if (ledgerBounds != null) { v2Builder.ledgerBounds(new org.stellar.sdk.xdr.LedgerBounds.Builder() diff --git a/src/main/java/org/stellar/sdk/responses/PredicateDeserializer.java b/src/main/java/org/stellar/sdk/responses/PredicateDeserializer.java index 1a51824e0..a6795a799 100644 --- a/src/main/java/org/stellar/sdk/responses/PredicateDeserializer.java +++ b/src/main/java/org/stellar/sdk/responses/PredicateDeserializer.java @@ -50,7 +50,7 @@ public Predicate deserialize(JsonElement json, Type typeOfT, JsonDeserialization } if (obj.has("rel_before")) { - return new Predicate.RelBefore(new Duration(new Int64(obj.get("rel_before").getAsLong()))); + return new Predicate.RelBefore(new Duration(new Uint64(obj.get("rel_before").getAsLong()))); } throw new IllegalArgumentException("Unsupported predicate: "+json.toString()); diff --git a/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java b/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java index a200c875a..2347125c0 100644 --- a/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java +++ b/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java @@ -22,9 +22,9 @@ // case CLAIM_PREDICATE_NOT: // ClaimPredicate* notPredicate; // case CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME: -// TimePoint absBefore; // Predicate will be true if closeTime < absBefore +// int64 absBefore; // Predicate will be true if closeTime < absBefore // case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME: -// Duration relBefore; // Seconds since closeTime of the ledger in which the +// int64 relBefore; // Seconds since closeTime of the ledger in which the // // ClaimableBalanceEntry was created // }; @@ -59,18 +59,18 @@ public ClaimPredicate getNotPredicate() { public void setNotPredicate(ClaimPredicate value) { this.notPredicate = value; } - private TimePoint absBefore; - public TimePoint getAbsBefore() { + private Int64 absBefore; + public Int64 getAbsBefore() { return this.absBefore; } - public void setAbsBefore(TimePoint value) { + public void setAbsBefore(Int64 value) { this.absBefore = value; } - private Duration relBefore; - public Duration getRelBefore() { + private Int64 relBefore; + public Int64 getRelBefore() { return this.relBefore; } - public void setRelBefore(Duration value) { + public void setRelBefore(Int64 value) { this.relBefore = value; } @@ -79,8 +79,8 @@ public static final class Builder { private ClaimPredicate[] andPredicates; private ClaimPredicate[] orPredicates; private ClaimPredicate notPredicate; - private TimePoint absBefore; - private Duration relBefore; + private Int64 absBefore; + private Int64 relBefore; public Builder discriminant(ClaimPredicateType discriminant) { this.discriminant = discriminant; @@ -102,12 +102,12 @@ public Builder notPredicate(ClaimPredicate notPredicate) { return this; } - public Builder absBefore(TimePoint absBefore) { + public Builder absBefore(Int64 absBefore) { this.absBefore = absBefore; return this; } - public Builder relBefore(Duration relBefore) { + public Builder relBefore(Int64 relBefore) { this.relBefore = relBefore; return this; } @@ -154,10 +154,10 @@ public static void encode(XdrDataOutputStream stream, ClaimPredicate encodedClai } break; case CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME: - TimePoint.encode(stream, encodedClaimPredicate.absBefore); + Int64.encode(stream, encodedClaimPredicate.absBefore); break; case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME: - Duration.encode(stream, encodedClaimPredicate.relBefore); + Int64.encode(stream, encodedClaimPredicate.relBefore); break; } } @@ -192,10 +192,10 @@ public static ClaimPredicate decode(XdrDataInputStream stream) throws IOExceptio } break; case CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME: - decodedClaimPredicate.absBefore = TimePoint.decode(stream); + decodedClaimPredicate.absBefore = Int64.decode(stream); break; case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME: - decodedClaimPredicate.relBefore = Duration.decode(stream); + decodedClaimPredicate.relBefore = Int64.decode(stream); break; } return decodedClaimPredicate; diff --git a/src/main/java/org/stellar/sdk/xdr/Duration.java b/src/main/java/org/stellar/sdk/xdr/Duration.java index 9a467a8c9..84359c7ec 100644 --- a/src/main/java/org/stellar/sdk/xdr/Duration.java +++ b/src/main/java/org/stellar/sdk/xdr/Duration.java @@ -10,28 +10,28 @@ // === xdr source ============================================================ -// typedef int64 Duration; +// typedef uint64 Duration; // =========================================================================== public class Duration implements XdrElement { - private Int64 Duration; + private Uint64 Duration; public Duration() {} - public Duration(Int64 Duration) { + public Duration(Uint64 Duration) { this.Duration = Duration; } - public Int64 getDuration() { + public Uint64 getDuration() { return this.Duration; } - public void setDuration(Int64 value) { + public void setDuration(Uint64 value) { this.Duration = value; } public static void encode(XdrDataOutputStream stream, Duration encodedDuration) throws IOException { - Int64.encode(stream, encodedDuration.Duration); + Uint64.encode(stream, encodedDuration.Duration); } public void encode(XdrDataOutputStream stream) throws IOException { @@ -39,7 +39,7 @@ public void encode(XdrDataOutputStream stream) throws IOException { } public static Duration decode(XdrDataInputStream stream) throws IOException { Duration decodedDuration = new Duration(); - decodedDuration.Duration = Int64.decode(stream); + decodedDuration.Duration = Uint64.decode(stream); return decodedDuration; } diff --git a/src/main/java/org/stellar/sdk/xdr/InnerTransactionResult.java b/src/main/java/org/stellar/sdk/xdr/InnerTransactionResult.java index 5eb00be9e..82bf866be 100644 --- a/src/main/java/org/stellar/sdk/xdr/InnerTransactionResult.java +++ b/src/main/java/org/stellar/sdk/xdr/InnerTransactionResult.java @@ -35,6 +35,7 @@ // case txNOT_SUPPORTED: // // txFEE_BUMP_INNER_FAILED is not included // case txBAD_SPONSORSHIP: +// case txBAD_MIN_SEQ_AGE_OR_GAP: // void; // } // result; @@ -194,6 +195,7 @@ public static void encode(XdrDataOutputStream stream, InnerTransactionResultResu case txINTERNAL_ERROR: case txNOT_SUPPORTED: case txBAD_SPONSORSHIP: + case txBAD_MIN_SEQ_AGE_OR_GAP: break; } } @@ -225,6 +227,7 @@ public static InnerTransactionResultResult decode(XdrDataInputStream stream) thr case txINTERNAL_ERROR: case txNOT_SUPPORTED: case txBAD_SPONSORSHIP: + case txBAD_MIN_SEQ_AGE_OR_GAP: break; } return decodedInnerTransactionResultResult; diff --git a/src/main/java/org/stellar/sdk/xdr/LedgerBounds.java b/src/main/java/org/stellar/sdk/xdr/LedgerBounds.java index 3a71b51eb..0fc43016f 100644 --- a/src/main/java/org/stellar/sdk/xdr/LedgerBounds.java +++ b/src/main/java/org/stellar/sdk/xdr/LedgerBounds.java @@ -13,7 +13,7 @@ // struct LedgerBounds // { // uint32 minLedger; -// uint32 maxLedger; +// uint32 maxLedger; // 0 here means no maxLedger // }; // =========================================================================== diff --git a/src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java b/src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java index d68b48064..9dce42bcb 100644 --- a/src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java +++ b/src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java @@ -14,8 +14,9 @@ // struct PreconditionsV2 { // TimeBounds *timeBounds; // -// // Transaciton only valid for ledger numbers n such that -// // minLedger <= n < maxLedger +// // Transaction only valid for ledger numbers n such that +// // minLedger <= n < maxLedger (if maxLedger == 0, then +// // only minLedger is checked) // LedgerBounds *ledgerBounds; // // // If NULL, only valid when sourceAccount's sequence number diff --git a/src/main/java/org/stellar/sdk/xdr/TransactionResultCode.java b/src/main/java/org/stellar/sdk/xdr/TransactionResultCode.java index ba809fa91..f84aeb6bb 100644 --- a/src/main/java/org/stellar/sdk/xdr/TransactionResultCode.java +++ b/src/main/java/org/stellar/sdk/xdr/TransactionResultCode.java @@ -30,7 +30,8 @@ // // txNOT_SUPPORTED = -12, // transaction type not supported // txFEE_BUMP_INNER_FAILED = -13, // fee bump inner transaction failed -// txBAD_SPONSORSHIP = -14 // sponsorship not confirmed +// txBAD_SPONSORSHIP = -14, // sponsorship not confirmed +// txBAD_MIN_SEQ_AGE_OR_GAP = -15 //minSeqAge or minSeqLedgerGap conditions not met // }; // =========================================================================== @@ -51,6 +52,7 @@ public enum TransactionResultCode implements XdrElement { txNOT_SUPPORTED(-12), txFEE_BUMP_INNER_FAILED(-13), txBAD_SPONSORSHIP(-14), + txBAD_MIN_SEQ_AGE_OR_GAP(-15), ; private int mValue; @@ -81,6 +83,7 @@ public static TransactionResultCode decode(XdrDataInputStream stream) throws IOE case -12: return txNOT_SUPPORTED; case -13: return txFEE_BUMP_INNER_FAILED; case -14: return txBAD_SPONSORSHIP; + case -15: return txBAD_MIN_SEQ_AGE_OR_GAP; default: throw new RuntimeException("Unknown enum value: " + value); } diff --git a/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java b/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java index a53abe12d..f5a3023b0 100644 --- a/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java +++ b/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java @@ -37,7 +37,7 @@ public void itConvertsFromXdr() throws IOException { PreconditionsV2.Builder v2Builder = new PreconditionsV2.Builder(); v2Builder.extraSigners(new SignerKey[]{}); - v2Builder.minSeqAge(new Duration(new Int64(2L))); + v2Builder.minSeqAge(new Duration(new Uint64(2L))); v2Builder.ledgerBounds(new org.stellar.sdk.xdr.LedgerBounds.Builder() .minLedger(new Uint32(1)) .maxLedger(new Uint32(2)) @@ -69,7 +69,7 @@ public void itRoundTripsFromV2ToV1IfOnlyTimeboundsPresent() throws IOException { org.stellar.sdk.xdr.TimeBounds xdrTimeBounds = new org.stellar.sdk.xdr.TimeBounds.Builder().minTime(new TimePoint(new Uint64(1L))).maxTime(new TimePoint(new Uint64(2L))).build(); v2Builder.timeBounds(xdrTimeBounds); v2Builder.minSeqLedgerGap(new Uint32(0)); - v2Builder.minSeqAge(new Duration(new Int64(0L))); + v2Builder.minSeqAge(new Duration(new Uint64(0L))); v2Builder.extraSigners(new SignerKey[]{}); preconditionsBuilder.v2(v2Builder.build()); // create V2 Precond with just timebounds @@ -123,7 +123,7 @@ public void itConvertsToV2Xdr() throws IOException { // xdr encoding requires non-null for min ledger gap assertEquals(xdr.getV2().getMinSeqLedgerGap().getUint32().intValue(), 0); // xdr encoding requires non-null for min seq age - assertEquals(xdr.getV2().getMinSeqAge().getDuration().getInt64().longValue(), 0); + assertEquals(xdr.getV2().getMinSeqAge().getDuration().getUint64().longValue(), 0); assertEquals(xdr.getV2().getExtraSigners().length, 3); } @@ -216,7 +216,7 @@ public void itChecksV2Status() { PreconditionsV2.Builder v2Builder = new PreconditionsV2.Builder(); v2Builder.extraSigners(new SignerKey[]{}); - v2Builder.minSeqAge(new Duration(new Int64(2L))); + v2Builder.minSeqAge(new Duration(new Uint64(2L))); v2Builder.ledgerBounds(new org.stellar.sdk.xdr.LedgerBounds.Builder() .minLedger(new Uint32(1)) .maxLedger(new Uint32(2)) diff --git a/xdr/Stellar-ledger-entries.x b/xdr/Stellar-ledger-entries.x index a1bb8e726..e0f719d0f 100644 --- a/xdr/Stellar-ledger-entries.x +++ b/xdr/Stellar-ledger-entries.x @@ -13,7 +13,7 @@ typedef string string32<32>; typedef string string64<64>; typedef int64 SequenceNumber; typedef uint64 TimePoint; -typedef int64 Duration; +typedef uint64 Duration; typedef opaque DataValue<64>; typedef Hash PoolID; // SHA256(LiquidityPoolParameters) @@ -384,9 +384,9 @@ case CLAIM_PREDICATE_OR: case CLAIM_PREDICATE_NOT: ClaimPredicate* notPredicate; case CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME: - TimePoint absBefore; // Predicate will be true if closeTime < absBefore + int64 absBefore; // Predicate will be true if closeTime < absBefore case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME: - Duration relBefore; // Seconds since closeTime of the ledger in which the + int64 relBefore; // Seconds since closeTime of the ledger in which the // ClaimableBalanceEntry was created }; @@ -589,4 +589,4 @@ enum EnvelopeType ENVELOPE_TYPE_OP_ID = 6, ENVELOPE_TYPE_POOL_REVOKE_OP_ID = 7 }; -} \ No newline at end of file +} diff --git a/xdr/Stellar-ledger.x b/xdr/Stellar-ledger.x index b33e85a57..84b84cbf7 100644 --- a/xdr/Stellar-ledger.x +++ b/xdr/Stellar-ledger.x @@ -363,4 +363,4 @@ union LedgerCloseMeta switch (int v) case 0: LedgerCloseMetaV0 v0; }; -} \ No newline at end of file +} diff --git a/xdr/Stellar-transaction.x b/xdr/Stellar-transaction.x index cd31b3a33..a9fdbe69f 100644 --- a/xdr/Stellar-transaction.x +++ b/xdr/Stellar-transaction.x @@ -579,14 +579,15 @@ struct TimeBounds struct LedgerBounds { uint32 minLedger; - uint32 maxLedger; + uint32 maxLedger; // 0 here means no maxLedger }; struct PreconditionsV2 { TimeBounds *timeBounds; - // Transaciton only valid for ledger numbers n such that - // minLedger <= n < maxLedger + // Transaction only valid for ledger numbers n such that + // minLedger <= n < maxLedger (if maxLedger == 0, then + // only minLedger is checked) LedgerBounds *ledgerBounds; // If NULL, only valid when sourceAccount's sequence number @@ -1560,7 +1561,8 @@ enum TransactionResultCode txNOT_SUPPORTED = -12, // transaction type not supported txFEE_BUMP_INNER_FAILED = -13, // fee bump inner transaction failed - txBAD_SPONSORSHIP = -14 // sponsorship not confirmed + txBAD_SPONSORSHIP = -14, // sponsorship not confirmed + txBAD_MIN_SEQ_AGE_OR_GAP = -15 //minSeqAge or minSeqLedgerGap conditions not met }; // InnerTransactionResult must be binary compatible with TransactionResult @@ -1589,6 +1591,7 @@ struct InnerTransactionResult case txNOT_SUPPORTED: // txFEE_BUMP_INNER_FAILED is not included case txBAD_SPONSORSHIP: + case txBAD_MIN_SEQ_AGE_OR_GAP: void; } result; @@ -1633,4 +1636,4 @@ struct TransactionResult } ext; }; -} \ No newline at end of file +} diff --git a/xdr/Stellar-types.x b/xdr/Stellar-types.x index 7d5a1467c..562f8fc26 100644 --- a/xdr/Stellar-types.x +++ b/xdr/Stellar-types.x @@ -97,4 +97,4 @@ struct HmacSha256Mac { opaque mac[32]; }; -} \ No newline at end of file +} From 5b85e7859692bbbeaf8ce41331dd1acac2fec909 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Thu, 31 Mar 2022 11:52:56 -0700 Subject: [PATCH 21/29] #411: align the tx builder sequence number resolution closer to go txnbuild interface for consistency --- CHANGELOG.md | 4 +++ .../org/stellar/sdk/FeeBumpTransaction.java | 14 ++++---- .../stellar/sdk/SequenceNumberStrategy.java | 18 ++-------- .../sdk/SequentialSequenceNumberStrategy.java | 5 --- .../org/stellar/sdk/TransactionBuilder.java | 34 +++++++++++++------ .../org/stellar/sdk/Sep10ChallengeTest.java | 11 ------ .../stellar/sdk/TransactionBuilderTest.java | 14 ++++---- 7 files changed, 41 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea9e4dff7..7c0227e65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ As this project is pre 1.0, breaking changes may happen for minor version bumps. * Update XDR definitions and auto-generated classes to support upcoming protocol 19 release ([#276](https://github.com/stellar/java-stellar-sdk/pull/276)). * Extend StrKey implementation to handle [CAP 40 Payload Signer](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0040.md) ([#276](https://github.com/stellar/java-stellar-sdk/pull/276)). * Extended Transaction submission settings, additional new Preconditions can be added now, refer to [CAP 21 Transaction Preconditions](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0021.md). +* Added ability to override the sequence number that is set by transaction builder. By default, transaction builder creates + transactions with sequence number equal to source account's sequence number incremented by 1. + Refer to `TransactionBuilder.addSequenceNumberResolver()` for using this optional new functionality. + ### Breaking changes diff --git a/src/main/java/org/stellar/sdk/FeeBumpTransaction.java b/src/main/java/org/stellar/sdk/FeeBumpTransaction.java index a491e20e6..3b2fc497f 100644 --- a/src/main/java/org/stellar/sdk/FeeBumpTransaction.java +++ b/src/main/java/org/stellar/sdk/FeeBumpTransaction.java @@ -1,7 +1,9 @@ package org.stellar.sdk; +import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.collect.Lists; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; import org.stellar.sdk.xdr.DecoratedSignature; import org.stellar.sdk.xdr.EnvelopeType; import org.stellar.sdk.xdr.FeeBumpTransactionEnvelope; @@ -127,17 +129,13 @@ public Builder(AccountConverter accountConverter, final Transaction inner) { .setBaseFee((int)inner.getFee()) .addOperations(Arrays.asList(inner.getOperations())) .addMemo(inner.getMemo()) - .addSequenceNumberStrategy(new SequenceNumberStrategy() { + .addSequenceNumberResolver(new Function() { @Override - public long getSequenceNumber(TransactionBuilderAccount account) { - // set the tx seq num to same as that of the inner tx + public Long apply(@NullableDecl TransactionBuilderAccount input) { + // override the sequence number resolution which be default, would have generated sourceAccount.sequenceNumber + 1 + // the fee bump tx needs to be set to same sequence as the inner tx. return inner.getSequenceNumber(); } - - @Override - public void updateSourceAccount(long newSequenceNumber, TransactionBuilderAccount account) { - //no-op, account instance is local to this scope, not external, no need to update it. - } }) .addPreconditions(new TransactionPreconditions.TransactionPreconditionsBuilder().timeBounds(inner.getTimeBounds()).build()) .build(); diff --git a/src/main/java/org/stellar/sdk/SequenceNumberStrategy.java b/src/main/java/org/stellar/sdk/SequenceNumberStrategy.java index 3a7dd3258..c3fff8f8c 100644 --- a/src/main/java/org/stellar/sdk/SequenceNumberStrategy.java +++ b/src/main/java/org/stellar/sdk/SequenceNumberStrategy.java @@ -5,22 +5,8 @@ public interface SequenceNumberStrategy { /** * Derives the desired sequence number for a newly built transaction. * - * @param account the source account of transaction. + * @param sourceAccount the source account of transaction. * @return the sequence number to be applied on new transaction. */ - long getSequenceNumber(TransactionBuilderAccount account); - - /** - * Update a given in-memory account instance after a new transaction has been built. - * The Implementation can determine what fields on the account to update, - * however, the account sequence number is most likely account field to update, - * since the new sequence number used on transaction is provided in parameters. - * - * Does not invoke any server updates, this just provides a way to update the in-memory - * instance of an account. - * - * @param newSequenceNumber the sequence number returned from getSequenceNumber - * @param account the account instance in memmory - */ - void updateSourceAccount(long newSequenceNumber, TransactionBuilderAccount account); + long getSequenceNumber(TransactionBuilderAccount sourceAccount); } diff --git a/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java b/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java index 40cfac8b3..895cc4d5d 100644 --- a/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java +++ b/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java @@ -11,9 +11,4 @@ public class SequentialSequenceNumberStrategy implements SequenceNumberStrategy{ public long getSequenceNumber(TransactionBuilderAccount account) { return account.getIncrementedSequenceNumber(); } - - @Override - public void updateSourceAccount(long newSequenceNumber, TransactionBuilderAccount account) { - account.setSequenceNumber(newSequenceNumber); - } } diff --git a/src/main/java/org/stellar/sdk/TransactionBuilder.java b/src/main/java/org/stellar/sdk/TransactionBuilder.java index 0203387a9..f393f9971 100644 --- a/src/main/java/org/stellar/sdk/TransactionBuilder.java +++ b/src/main/java/org/stellar/sdk/TransactionBuilder.java @@ -1,5 +1,6 @@ package org.stellar.sdk; +import com.google.common.base.Function; import org.stellar.sdk.TransactionPreconditions.TransactionPreconditionsBuilder; import org.stellar.sdk.xdr.TimePoint; import org.stellar.sdk.xdr.Uint64; @@ -20,7 +21,7 @@ public class TransactionBuilder { private List mOperations; private Integer mBaseFee; private Network mNetwork; - private SequenceNumberStrategy sequenceNumberStrategy; + private Function generateSequenceNumberFunc; private TransactionPreconditions mPreconditions; private boolean mTimeoutSet; @@ -38,7 +39,7 @@ public TransactionBuilder(AccountConverter accountConverter, TransactionBuilderA mSourceAccount = checkNotNull(sourceAccount, "sourceAccount cannot be null"); mNetwork = checkNotNull(network, "Network cannot be null"); mOperations = newArrayList(); - sequenceNumberStrategy = new SequentialSequenceNumberStrategy(); + generateSequenceNumberFunc = IncrementedSequenceNumberFunc; mPreconditions = TransactionPreconditions.builder().build(); } @@ -98,15 +99,16 @@ public TransactionBuilder addPreconditions(TransactionPreconditions precondition } /** - * Add a sequnce number resolution strategy. The Strategy is a callback that can determine the - * actual sequence number applied to the tx being built. + * Override the default sequence number resolution. Transaction builder invokes this function + * to obtain the sequence number to apply to the transaction. By default, the IncrementedSequenceNumberFunc + * is used which will derive a sequence number based on sourceAccount's current sequence number incremeneted by 1. * - * @param sequenceNumberStrategy the sequence number strategy that defines how to get a sequence number for the tx - * and if/how to set source account sequence number after tx is built. + * @param generateSequenceNumberFunc a function that receives the transaction's source account and returns + * the sequence number desired for this transaction. * @return updated Builder object */ - public TransactionBuilder addSequenceNumberStrategy(SequenceNumberStrategy sequenceNumberStrategy) { - this.sequenceNumberStrategy = sequenceNumberStrategy; + public TransactionBuilder addSequenceNumberResolver(Function generateSequenceNumberFunc) { + this.generateSequenceNumberFunc = generateSequenceNumberFunc; return this; } @@ -210,7 +212,7 @@ public Transaction build() { throw new NoNetworkSelectedException(); } - long sequenceNumber = sequenceNumberStrategy.getSequenceNumber(mSourceAccount); + long sequenceNumber = generateSequenceNumberFunc.apply(mSourceAccount); Operation[] operations = new Operation[mOperations.size()]; operations = mOperations.toArray(operations); @@ -224,11 +226,21 @@ public Transaction build() { mPreconditions, mNetwork ); - // Increment sequence number when there were no exceptions when creating a transaction - sequenceNumberStrategy.updateSourceAccount(sequenceNumber, mSourceAccount); + mSourceAccount.setSequenceNumber(sequenceNumber); return transaction; } + /** + * A default implementation of sequence number generation which will derive a sequence number based on + * sourceAccount's current sequence number + 1. + */ + public static Function IncrementedSequenceNumberFunc = new Function() { + @Override + public Long apply(TransactionBuilderAccount sourceAccount) { + return sourceAccount.getIncrementedSequenceNumber(); + } + }; + public static org.stellar.sdk.xdr.TimeBounds buildTimeBounds(long minTime, long maxTime) { return new org.stellar.sdk.xdr.TimeBounds.Builder().minTime(new TimePoint(new Uint64(minTime))) .maxTime(new TimePoint(new Uint64(maxTime))).build(); diff --git a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java index 109096851..0e24701c6 100644 --- a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java +++ b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java @@ -235,17 +235,6 @@ public void testReadChallengeTransactionRejectsMuxedClient() throws InvalidSep10 .setBaseFee((int)transaction.getFee()) .addMemo(transaction.getMemo()) .addOperations(Arrays.asList(operations)) - .addSequenceNumberStrategy(new SequenceNumberStrategy() { - @Override - public long getSequenceNumber(TransactionBuilderAccount account) { - return transaction.getSequenceNumber(); - } - - @Override - public void updateSourceAccount(long newSequenceNumber, TransactionBuilderAccount account) { - - } - }) .addPreconditions(transaction.getPreconditions()) .build(); diff --git a/src/test/java/org/stellar/sdk/TransactionBuilderTest.java b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java index 937bc404e..ab14a8def 100644 --- a/src/test/java/org/stellar/sdk/TransactionBuilderTest.java +++ b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java @@ -1,5 +1,6 @@ package org.stellar.sdk; +import com.google.common.base.Function; import com.google.common.io.BaseEncoding; import org.junit.Test; import org.stellar.sdk.xdr.Int64; @@ -481,15 +482,12 @@ public void testBuilderUsesCustomSequence() throws IOException { .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) .addPreconditions(TransactionPreconditions.builder() .timeBounds(new TimeBounds(0L,TransactionPreconditions.TIMEOUT_INFINITE)).build()) - .addSequenceNumberStrategy(new SequenceNumberStrategy() { + .addSequenceNumberResolver(new Function() { @Override - public long getSequenceNumber(TransactionBuilderAccount account) { - return 5; - } - - @Override - public void updateSourceAccount(long newSequenceNumber, TransactionBuilderAccount account) { - account.setSequenceNumber(newSequenceNumber); + public Long apply(TransactionBuilderAccount sourceAccount) { + // an example of overriding the default 'sourceAccount + 1' sequence number resolution + // can use this resolver function to inject the sequence number applied on the new transaction. + return 5L; } }) .setBaseFee(Transaction.MIN_BASE_FEE) From bbd9871df561d8c420eea94b7bb71f60fee8e19e Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Thu, 31 Mar 2022 11:55:39 -0700 Subject: [PATCH 22/29] #411: remove the now unused sequence number strategy classes, used the functional approach instead --- .../org/stellar/sdk/SequenceNumberStrategy.java | 12 ------------ .../sdk/SequentialSequenceNumberStrategy.java | 14 -------------- 2 files changed, 26 deletions(-) delete mode 100644 src/main/java/org/stellar/sdk/SequenceNumberStrategy.java delete mode 100644 src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java diff --git a/src/main/java/org/stellar/sdk/SequenceNumberStrategy.java b/src/main/java/org/stellar/sdk/SequenceNumberStrategy.java deleted file mode 100644 index c3fff8f8c..000000000 --- a/src/main/java/org/stellar/sdk/SequenceNumberStrategy.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.stellar.sdk; - -public interface SequenceNumberStrategy { - - /** - * Derives the desired sequence number for a newly built transaction. - * - * @param sourceAccount the source account of transaction. - * @return the sequence number to be applied on new transaction. - */ - long getSequenceNumber(TransactionBuilderAccount sourceAccount); -} diff --git a/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java b/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java deleted file mode 100644 index 895cc4d5d..000000000 --- a/src/main/java/org/stellar/sdk/SequentialSequenceNumberStrategy.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.stellar.sdk; - -/** - * Default transaction builder strategy implementation for obtaining the sequence number that a transaction will - * take and how the transaction's source account sequence number will be updated after the builder finishes - * building the transaction instance. - * - */ -public class SequentialSequenceNumberStrategy implements SequenceNumberStrategy{ - @Override - public long getSequenceNumber(TransactionBuilderAccount account) { - return account.getIncrementedSequenceNumber(); - } -} From 4c8f5bf7d7152724b234d25c5b9d6e15a6c1e982 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Thu, 31 Mar 2022 15:46:15 -0700 Subject: [PATCH 23/29] #411: use original source account sequence number during tx build --- .../org/stellar/sdk/FeeBumpTransaction.java | 10 +--------- .../org/stellar/sdk/TransactionBuilder.java | 19 +------------------ .../stellar/sdk/TransactionBuilderTest.java | 16 ++++------------ 3 files changed, 6 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/stellar/sdk/FeeBumpTransaction.java b/src/main/java/org/stellar/sdk/FeeBumpTransaction.java index 3b2fc497f..4970c45ab 100644 --- a/src/main/java/org/stellar/sdk/FeeBumpTransaction.java +++ b/src/main/java/org/stellar/sdk/FeeBumpTransaction.java @@ -125,18 +125,10 @@ public Builder(AccountConverter accountConverter, final Transaction inner) { EnvelopeType txType = inner.toEnvelopeXdr().getDiscriminant(); this.mAccountConverter = checkNotNull(accountConverter, "accountConverter cannot be null"); if (inner.toEnvelopeXdr().getDiscriminant() == EnvelopeType.ENVELOPE_TYPE_TX_V0) { - this.mInner = new TransactionBuilder(inner.accountConverter, new Account(inner.getSourceAccount(), inner.getSequenceNumber()), inner.getNetwork()) + this.mInner = new TransactionBuilder(inner.accountConverter, new Account(inner.getSourceAccount(), inner.getSequenceNumber() - 1), inner.getNetwork()) .setBaseFee((int)inner.getFee()) .addOperations(Arrays.asList(inner.getOperations())) .addMemo(inner.getMemo()) - .addSequenceNumberResolver(new Function() { - @Override - public Long apply(@NullableDecl TransactionBuilderAccount input) { - // override the sequence number resolution which be default, would have generated sourceAccount.sequenceNumber + 1 - // the fee bump tx needs to be set to same sequence as the inner tx. - return inner.getSequenceNumber(); - } - }) .addPreconditions(new TransactionPreconditions.TransactionPreconditionsBuilder().timeBounds(inner.getTimeBounds()).build()) .build(); diff --git a/src/main/java/org/stellar/sdk/TransactionBuilder.java b/src/main/java/org/stellar/sdk/TransactionBuilder.java index f393f9971..c6c3bc2e1 100644 --- a/src/main/java/org/stellar/sdk/TransactionBuilder.java +++ b/src/main/java/org/stellar/sdk/TransactionBuilder.java @@ -21,7 +21,6 @@ public class TransactionBuilder { private List mOperations; private Integer mBaseFee; private Network mNetwork; - private Function generateSequenceNumberFunc; private TransactionPreconditions mPreconditions; private boolean mTimeoutSet; @@ -39,7 +38,6 @@ public TransactionBuilder(AccountConverter accountConverter, TransactionBuilderA mSourceAccount = checkNotNull(sourceAccount, "sourceAccount cannot be null"); mNetwork = checkNotNull(network, "Network cannot be null"); mOperations = newArrayList(); - generateSequenceNumberFunc = IncrementedSequenceNumberFunc; mPreconditions = TransactionPreconditions.builder().build(); } @@ -98,20 +96,6 @@ public TransactionBuilder addPreconditions(TransactionPreconditions precondition return this; } - /** - * Override the default sequence number resolution. Transaction builder invokes this function - * to obtain the sequence number to apply to the transaction. By default, the IncrementedSequenceNumberFunc - * is used which will derive a sequence number based on sourceAccount's current sequence number incremeneted by 1. - * - * @param generateSequenceNumberFunc a function that receives the transaction's source account and returns - * the sequence number desired for this transaction. - * @return updated Builder object - */ - public TransactionBuilder addSequenceNumberResolver(Function generateSequenceNumberFunc) { - this.generateSequenceNumberFunc = generateSequenceNumberFunc; - return this; - } - /** * Adds a memo to this transaction. * @@ -212,8 +196,7 @@ public Transaction build() { throw new NoNetworkSelectedException(); } - long sequenceNumber = generateSequenceNumberFunc.apply(mSourceAccount); - + long sequenceNumber = mSourceAccount.getIncrementedSequenceNumber(); Operation[] operations = new Operation[mOperations.size()]; operations = mOperations.toArray(operations); Transaction transaction = new Transaction( diff --git a/src/test/java/org/stellar/sdk/TransactionBuilderTest.java b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java index ab14a8def..870e20d4b 100644 --- a/src/test/java/org/stellar/sdk/TransactionBuilderTest.java +++ b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java @@ -476,30 +476,22 @@ public void testBuilderFailsWhenTimeoutLessThanTimeBoundsMinimum() throws Except } @Test - public void testBuilderUsesCustomSequence() throws IOException { - Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + public void testBuilderUsesAccountSequence() throws IOException { + Account account = new Account(KeyPair.random().getAccountId(), 3L); Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) .addPreconditions(TransactionPreconditions.builder() .timeBounds(new TimeBounds(0L,TransactionPreconditions.TIMEOUT_INFINITE)).build()) - .addSequenceNumberResolver(new Function() { - @Override - public Long apply(TransactionBuilderAccount sourceAccount) { - // an example of overriding the default 'sourceAccount + 1' sequence number resolution - // can use this resolver function to inject the sequence number applied on the new transaction. - return 5L; - } - }) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); // check that the created tx has the sequence number which custom sequence handler sets, // rather than using default of sourceAccount.seqNum + 1 - assertEquals(5, transaction.getSequenceNumber()); + assertEquals(4, transaction.getSequenceNumber()); // check that the sourceAccount.seqNum gets updated to what the custom sequence handler sets, // rather than the default of sourceAccount.seqNum + 1 - assertEquals(5, account.getSequenceNumber().longValue()); + assertEquals(4, account.getSequenceNumber().longValue()); } @Test From 4ed57f3094731df8c761fdda6e024efce649bbcb Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Thu, 7 Apr 2022 23:21:04 -0700 Subject: [PATCH 24/29] #410: added payload signer signature generation and enabled adding pre-built signatures onto a transaction --- CHANGELOG.md | 3 ++ .../org/stellar/sdk/AbstractTransaction.java | 31 ++++++++++++- src/main/java/org/stellar/sdk/KeyPair.java | 43 +++++++++++++++++-- .../java/org/stellar/sdk/KeyPairTest.java | 37 +++++++++++++++- .../org/stellar/sdk/Sep10ChallengeTest.java | 5 ++- .../java/org/stellar/sdk/TransactionTest.java | 32 ++++++++++++++ 6 files changed, 144 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c0227e65..d04c9066e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ As this project is pre 1.0, breaking changes may happen for minor version bumps. * deprecated `addTimeBounds()` use `addPreconditions()` instead * deprecated `setTimeout()` use `addPreconditions()` instead * deprecated `Transaction.Builder` use TransactionBuilder instead +* org.stellar.sdk.Transaction + * `getSignatures()` returns an ImmutableList of signatures, do NOT add signatures to the collection returned. + use `addSignature(DecoratedSignature signature)` instead. ## 0.31.0 diff --git a/src/main/java/org/stellar/sdk/AbstractTransaction.java b/src/main/java/org/stellar/sdk/AbstractTransaction.java index 99de3c7cb..500930be0 100644 --- a/src/main/java/org/stellar/sdk/AbstractTransaction.java +++ b/src/main/java/org/stellar/sdk/AbstractTransaction.java @@ -1,7 +1,14 @@ package org.stellar.sdk; +import com.google.common.collect.ImmutableList; import com.google.common.io.BaseEncoding; -import org.stellar.sdk.xdr.*; +import org.stellar.sdk.xdr.DecoratedSignature; +import org.stellar.sdk.xdr.Hash; +import org.stellar.sdk.xdr.SignatureHint; +import org.stellar.sdk.xdr.TransactionEnvelope; +import org.stellar.sdk.xdr.TransactionSignaturePayload; +import org.stellar.sdk.xdr.XdrDataInputStream; +import org.stellar.sdk.xdr.XdrDataOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -75,14 +82,34 @@ public String hashHex() { */ public abstract byte[] signatureBase(); + /** + * Gets the Network string for this transaction. + * + * @return the Network string + */ public Network getNetwork() { return mNetwork; } + /** + * Gets read only list(immutable) of the signatures on transaction. + * + * @return immutable list of signatures + */ public List getSignatures() { - return mSignatures; + return ImmutableList.copyOf(mSignatures); } + /** + * Adds an additional signature to the transaction's existing list of signatures. + * + * @param signature the signature to add + */ + public void addSignature(DecoratedSignature signature) { + mSignatures.add(signature); + } + + public abstract TransactionEnvelope toEnvelopeXdr(); /** diff --git a/src/main/java/org/stellar/sdk/KeyPair.java b/src/main/java/org/stellar/sdk/KeyPair.java index 40ff0bfb5..6c7f8e340 100644 --- a/src/main/java/org/stellar/sdk/KeyPair.java +++ b/src/main/java/org/stellar/sdk/KeyPair.java @@ -9,8 +9,14 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; - -import org.stellar.sdk.xdr.*; +import org.stellar.sdk.xdr.DecoratedSignature; +import org.stellar.sdk.xdr.PublicKey; +import org.stellar.sdk.xdr.PublicKeyType; +import org.stellar.sdk.xdr.SignatureHint; +import org.stellar.sdk.xdr.SignerKey; +import org.stellar.sdk.xdr.SignerKeyType; +import org.stellar.sdk.xdr.Uint256; +import org.stellar.sdk.xdr.XdrDataOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -21,6 +27,7 @@ import java.util.Arrays; import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.System.arraycopy; /** * Holds a Stellar keypair. @@ -227,7 +234,9 @@ public byte[] sign(byte[] data) { /** * Sign the provided data with the keypair's private key and returns {@link DecoratedSignature}. - * @param data + * + * @param data the data to sign, typically the tx hash + * @return DecoratedSignature */ public DecoratedSignature signDecorated(byte[] data) { byte[] signatureBytes = this.sign(data); @@ -241,6 +250,34 @@ public DecoratedSignature signDecorated(byte[] data) { return decoratedSignature; } + /** + * Sign the provided payload data for payload signer where the input is the data being signed. + * Per the + * {@link DecoratedSignature}. + * + * @param signerPayload the payload signers raw data to sign + * @return DecoratedSignature + */ + public DecoratedSignature signPayloadDecorated(byte[] signerPayload) { + DecoratedSignature payloadSignature = signDecorated(signerPayload); + + byte[] hint = new byte[4]; + + // copy the last four bytes of the payload into the new hint + if (signerPayload.length >= hint.length) { + arraycopy(signerPayload, signerPayload.length - hint.length, hint, 0, hint.length); + } else { + arraycopy(signerPayload, 0, hint, 0, signerPayload.length); + } + + //XOR the new hint with this keypair's public key hint + for (int i = 0; i < hint.length; i++) { + hint[i] ^= (i < payloadSignature.getHint().getSignatureHint().length ? payloadSignature.getHint().getSignatureHint()[i] : 0); + } + payloadSignature.getHint().setSignatureHint(hint); + return payloadSignature; + } + /** * Verify the provided data and signature match this keypair's public key. * @param data The data that was signed. diff --git a/src/test/java/org/stellar/sdk/KeyPairTest.java b/src/test/java/org/stellar/sdk/KeyPairTest.java index 54d087477..db1b564eb 100644 --- a/src/test/java/org/stellar/sdk/KeyPairTest.java +++ b/src/test/java/org/stellar/sdk/KeyPairTest.java @@ -2,11 +2,15 @@ import org.junit.Assert; import org.junit.Test; +import org.stellar.sdk.xdr.DecoratedSignature; import java.util.HashMap; import java.util.Map; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class KeyPairTest { @@ -86,4 +90,35 @@ public void testSignWithoutSecret() { assertEquals("KeyPair does not contain secret key. Use KeyPair.fromSecretSeed method to create a new KeyPair with a secret key.", e.getMessage()); } } + + @Test + public void testSignPayloadSigner() { + KeyPair keypair = KeyPair.fromSecretSeed(Util.hexToBytes(SEED)); + // the hint from this keypair is [254,66,4,55] + + byte[] payload = new byte[]{1,2,3,4,5}; + DecoratedSignature sig = keypair.signPayloadDecorated(payload); + Assert.assertArrayEquals(sig.getHint().getSignatureHint(), new byte[]{ + (byte)(0xFF & 252), + (byte)(0xFF & 65), + (byte)(0), + (byte)(0xFF & 50)}); + + } + + @Test + public void testSignPayloadSignerLessThanHint() { + KeyPair keypair = KeyPair.fromSecretSeed(Util.hexToBytes(SEED)); + // the hint from this keypair is [254,66,4,55] + + byte[] payload = new byte[]{1,2,3}; + DecoratedSignature sig = keypair.signPayloadDecorated(payload); + // the hint could only be derived off of 3 bytes from payload + Assert.assertArrayEquals(sig.getHint().getSignatureHint(), new byte[]{ + (byte)(255), + (byte)(0xFF & 64), + (byte)(0xFF & 7), + (byte)(0xFF & 55)}); + + } } diff --git a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java index 0e24701c6..aed005b31 100644 --- a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java +++ b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java @@ -3,6 +3,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.io.BaseEncoding; import org.junit.Test; +import org.stellar.sdk.xdr.DecoratedSignature; import org.stellar.sdk.xdr.EnvelopeType; import org.stellar.sdk.xdr.TransactionEnvelope; @@ -238,7 +239,9 @@ public void testReadChallengeTransactionRejectsMuxedClient() throws InvalidSep10 .addPreconditions(transaction.getPreconditions()) .build(); - withMuxedClient.getSignatures().addAll(transaction.mSignatures); + for (DecoratedSignature signature : transaction.mSignatures) { + withMuxedClient.addSignature(signature); + } try { Sep10Challenge.readChallengeTransaction( diff --git a/src/test/java/org/stellar/sdk/TransactionTest.java b/src/test/java/org/stellar/sdk/TransactionTest.java index e0630739b..1d6c2ee82 100644 --- a/src/test/java/org/stellar/sdk/TransactionTest.java +++ b/src/test/java/org/stellar/sdk/TransactionTest.java @@ -2,6 +2,7 @@ import com.google.common.io.BaseEncoding; import org.junit.Test; +import org.stellar.sdk.xdr.DecoratedSignature; import org.stellar.sdk.xdr.EnvelopeType; import org.stellar.sdk.xdr.SignerKey; import org.stellar.sdk.xdr.XdrDataInputStream; @@ -15,6 +16,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class TransactionTest { @@ -56,6 +58,36 @@ public void testParseV0Transaction() throws FormatException, IOException { } + @Test + public void testAddingSignaturesDirectly() { + KeyPair source = KeyPair.fromAccountId("GBBM6BKZPEHWYO3E3YKREDPQXMS4VK35YLNU7NFBRI26RAN7GI5POFBB"); + KeyPair destination = KeyPair.fromAccountId("GDJJRRMBK4IWLEPJGIE6SXD2LP7REGZODU7WDC3I2D6MR37F4XSHBKX2"); + + Account account = new Account(source.getAccountId(), 0L); + + Transaction transaction = new Transaction( + AccountConverter.disableMuxed(), + account.getAccountId(), + Transaction.MIN_BASE_FEE, + account.getIncrementedSequenceNumber(), + new org.stellar.sdk.Operation[]{new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()}, + null, + new TransactionPreconditions(null, null, 0, 0, new ArrayList(),null), + Network.PUBLIC + ); + + assertEquals(0, transaction.getSignatures().size()); + try { + // should not be able to change the list of signatures directly + transaction.getSignatures().add(new DecoratedSignature()); + fail(); + } catch (UnsupportedOperationException ignored) {} + + // should only be able to add signatures through interface + transaction.addSignature(new DecoratedSignature()); + assertEquals(1, transaction.getSignatures().size()); + } + @Test public void testSha256HashSigning() throws FormatException { KeyPair source = KeyPair.fromAccountId("GBBM6BKZPEHWYO3E3YKREDPQXMS4VK35YLNU7NFBRI26RAN7GI5POFBB"); From e6e196609085d48198931ac1e6d87c4e50bd5797 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Fri, 8 Apr 2022 14:35:07 -0700 Subject: [PATCH 25/29] #410: removed unnecessary guard check on hint length and cleaned up byte expressions in test --- src/main/java/org/stellar/sdk/KeyPair.java | 2 +- src/test/java/org/stellar/sdk/KeyPairTest.java | 13 ++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/stellar/sdk/KeyPair.java b/src/main/java/org/stellar/sdk/KeyPair.java index 6c7f8e340..1c71185e9 100644 --- a/src/main/java/org/stellar/sdk/KeyPair.java +++ b/src/main/java/org/stellar/sdk/KeyPair.java @@ -272,7 +272,7 @@ public DecoratedSignature signPayloadDecorated(byte[] signerPayload) { //XOR the new hint with this keypair's public key hint for (int i = 0; i < hint.length; i++) { - hint[i] ^= (i < payloadSignature.getHint().getSignatureHint().length ? payloadSignature.getHint().getSignatureHint()[i] : 0); + hint[i] ^= payloadSignature.getHint().getSignatureHint()[i]; } payloadSignature.getHint().setSignatureHint(hint); return payloadSignature; diff --git a/src/test/java/org/stellar/sdk/KeyPairTest.java b/src/test/java/org/stellar/sdk/KeyPairTest.java index db1b564eb..2d9b661f4 100644 --- a/src/test/java/org/stellar/sdk/KeyPairTest.java +++ b/src/test/java/org/stellar/sdk/KeyPairTest.java @@ -98,11 +98,7 @@ public void testSignPayloadSigner() { byte[] payload = new byte[]{1,2,3,4,5}; DecoratedSignature sig = keypair.signPayloadDecorated(payload); - Assert.assertArrayEquals(sig.getHint().getSignatureHint(), new byte[]{ - (byte)(0xFF & 252), - (byte)(0xFF & 65), - (byte)(0), - (byte)(0xFF & 50)}); + Assert.assertArrayEquals(sig.getHint().getSignatureHint(), new byte[]{(byte)(0xFF & 252), 65, 0, 50}); } @@ -114,11 +110,6 @@ public void testSignPayloadSignerLessThanHint() { byte[] payload = new byte[]{1,2,3}; DecoratedSignature sig = keypair.signPayloadDecorated(payload); // the hint could only be derived off of 3 bytes from payload - Assert.assertArrayEquals(sig.getHint().getSignatureHint(), new byte[]{ - (byte)(255), - (byte)(0xFF & 64), - (byte)(0xFF & 7), - (byte)(0xFF & 55)}); - + Assert.assertArrayEquals(sig.getHint().getSignatureHint(), new byte[]{(byte)(255), 64, 7, 55}); } } From 32765bf968e96ea9dc4e8bd3107b676f7bb8b143 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Tue, 12 Apr 2022 11:05:18 -0700 Subject: [PATCH 26/29] #412: updated xdr files to latest from protocol 19 changes on core --- .../stellar/sdk/xdr/AllowTrustResultCode.java | 6 +- .../sdk/xdr/ChangeTrustResultCode.java | 8 +- .../org/stellar/sdk/xdr/ClaimPredicate.java | 2 +- .../org/stellar/sdk/xdr/ExtensionPoint.java | 5 +- .../sdk/xdr/InnerTransactionResult.java | 3 + ...iquidityPoolConstantProductParameters.java | 2 +- .../sdk/xdr/LiquidityPoolDepositOp.java | 8 +- .../sdk/xdr/LiquidityPoolDepositResult.java | 3 +- .../stellar/sdk/xdr/LiquidityPoolEntry.java | 3 +- .../sdk/xdr/LiquidityPoolWithdrawOp.java | 6 +- .../sdk/xdr/LiquidityPoolWithdrawResult.java | 3 +- .../xdr/LiquidityPoolWithdrawResultCode.java | 16 ++-- .../org/stellar/sdk/xdr/OfferEntryFlags.java | 3 +- .../org/stellar/sdk/xdr/PreconditionType.java | 3 +- .../org/stellar/sdk/xdr/Preconditions.java | 15 ++-- .../org/stellar/sdk/xdr/PreconditionsV2.java | 9 +- .../java/org/stellar/sdk/xdr/SignerKey.java | 3 +- .../sdk/xdr/TransactionResultCode.java | 6 +- xdr/Stellar-ledger-entries.x | 10 ++- xdr/Stellar-transaction.x | 82 ++++++++++--------- xdr/Stellar-types.x | 8 +- 21 files changed, 114 insertions(+), 90 deletions(-) diff --git a/src/main/java/org/stellar/sdk/xdr/AllowTrustResultCode.java b/src/main/java/org/stellar/sdk/xdr/AllowTrustResultCode.java index 45447edc0..2da92d696 100644 --- a/src/main/java/org/stellar/sdk/xdr/AllowTrustResultCode.java +++ b/src/main/java/org/stellar/sdk/xdr/AllowTrustResultCode.java @@ -18,10 +18,10 @@ // ALLOW_TRUST_NO_TRUST_LINE = -2, // trustor does not have a trustline // // source account does not require trust // ALLOW_TRUST_TRUST_NOT_REQUIRED = -3, -// ALLOW_TRUST_CANT_REVOKE = -4, // source account can't revoke trust, +// ALLOW_TRUST_CANT_REVOKE = -4, // source account can't revoke trust, // ALLOW_TRUST_SELF_NOT_ALLOWED = -5, // trusting self is not allowed -// ALLOW_TRUST_LOW_RESERVE = -6 // claimable balances can't be created -// // on revoke due to low reserves +// ALLOW_TRUST_LOW_RESERVE = -6 // claimable balances can't be created +// // on revoke due to low reserves // }; // =========================================================================== diff --git a/src/main/java/org/stellar/sdk/xdr/ChangeTrustResultCode.java b/src/main/java/org/stellar/sdk/xdr/ChangeTrustResultCode.java index 2495e5100..932d771fa 100644 --- a/src/main/java/org/stellar/sdk/xdr/ChangeTrustResultCode.java +++ b/src/main/java/org/stellar/sdk/xdr/ChangeTrustResultCode.java @@ -20,10 +20,12 @@ // // cannot create with a limit of 0 // CHANGE_TRUST_LOW_RESERVE = // -4, // not enough funds to create a new trust line, -// CHANGE_TRUST_SELF_NOT_ALLOWED = -5, // trusting self is not allowed +// CHANGE_TRUST_SELF_NOT_ALLOWED = -5, // trusting self is not allowed // CHANGE_TRUST_TRUST_LINE_MISSING = -6, // Asset trustline is missing for pool -// CHANGE_TRUST_CANNOT_DELETE = -7, // Asset trustline is still referenced in a pool -// CHANGE_TRUST_NOT_AUTH_MAINTAIN_LIABILITIES = -8 // Asset trustline is deauthorized +// CHANGE_TRUST_CANNOT_DELETE = +// -7, // Asset trustline is still referenced in a pool +// CHANGE_TRUST_NOT_AUTH_MAINTAIN_LIABILITIES = +// -8 // Asset trustline is deauthorized // }; // =========================================================================== diff --git a/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java b/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java index 2347125c0..b87de7dd7 100644 --- a/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java +++ b/src/main/java/org/stellar/sdk/xdr/ClaimPredicate.java @@ -25,7 +25,7 @@ // int64 absBefore; // Predicate will be true if closeTime < absBefore // case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME: // int64 relBefore; // Seconds since closeTime of the ledger in which the -// // ClaimableBalanceEntry was created +// // ClaimableBalanceEntry was created // }; // =========================================================================== diff --git a/src/main/java/org/stellar/sdk/xdr/ExtensionPoint.java b/src/main/java/org/stellar/sdk/xdr/ExtensionPoint.java index fb017c313..1032c2889 100644 --- a/src/main/java/org/stellar/sdk/xdr/ExtensionPoint.java +++ b/src/main/java/org/stellar/sdk/xdr/ExtensionPoint.java @@ -10,9 +10,10 @@ // === xdr source ============================================================ -// union ExtensionPoint switch (int v) { +// union ExtensionPoint switch (int v) +// { // case 0: -// void; +// void; // }; // =========================================================================== diff --git a/src/main/java/org/stellar/sdk/xdr/InnerTransactionResult.java b/src/main/java/org/stellar/sdk/xdr/InnerTransactionResult.java index 82bf866be..5af16bdc5 100644 --- a/src/main/java/org/stellar/sdk/xdr/InnerTransactionResult.java +++ b/src/main/java/org/stellar/sdk/xdr/InnerTransactionResult.java @@ -36,6 +36,7 @@ // // txFEE_BUMP_INNER_FAILED is not included // case txBAD_SPONSORSHIP: // case txBAD_MIN_SEQ_AGE_OR_GAP: +// case txMALFORMED: // void; // } // result; @@ -196,6 +197,7 @@ public static void encode(XdrDataOutputStream stream, InnerTransactionResultResu case txNOT_SUPPORTED: case txBAD_SPONSORSHIP: case txBAD_MIN_SEQ_AGE_OR_GAP: + case txMALFORMED: break; } } @@ -228,6 +230,7 @@ public static InnerTransactionResultResult decode(XdrDataInputStream stream) thr case txNOT_SUPPORTED: case txBAD_SPONSORSHIP: case txBAD_MIN_SEQ_AGE_OR_GAP: + case txMALFORMED: break; } return decodedInnerTransactionResultResult; diff --git a/src/main/java/org/stellar/sdk/xdr/LiquidityPoolConstantProductParameters.java b/src/main/java/org/stellar/sdk/xdr/LiquidityPoolConstantProductParameters.java index 32dc06c3b..8b5b40eb1 100644 --- a/src/main/java/org/stellar/sdk/xdr/LiquidityPoolConstantProductParameters.java +++ b/src/main/java/org/stellar/sdk/xdr/LiquidityPoolConstantProductParameters.java @@ -14,7 +14,7 @@ // { // Asset assetA; // assetA < assetB // Asset assetB; -// int32 fee; // Fee is in basis points, so the actual rate is (fee/100)% +// int32 fee; // Fee is in basis points, so the actual rate is (fee/100)% // }; // =========================================================================== diff --git a/src/main/java/org/stellar/sdk/xdr/LiquidityPoolDepositOp.java b/src/main/java/org/stellar/sdk/xdr/LiquidityPoolDepositOp.java index 5d5ff5a1d..04816194e 100644 --- a/src/main/java/org/stellar/sdk/xdr/LiquidityPoolDepositOp.java +++ b/src/main/java/org/stellar/sdk/xdr/LiquidityPoolDepositOp.java @@ -13,10 +13,10 @@ // struct LiquidityPoolDepositOp // { // PoolID liquidityPoolID; -// int64 maxAmountA; // maximum amount of first asset to deposit -// int64 maxAmountB; // maximum amount of second asset to deposit -// Price minPrice; // minimum depositA/depositB -// Price maxPrice; // maximum depositA/depositB +// int64 maxAmountA; // maximum amount of first asset to deposit +// int64 maxAmountB; // maximum amount of second asset to deposit +// Price minPrice; // minimum depositA/depositB +// Price maxPrice; // maximum depositA/depositB // }; // =========================================================================== diff --git a/src/main/java/org/stellar/sdk/xdr/LiquidityPoolDepositResult.java b/src/main/java/org/stellar/sdk/xdr/LiquidityPoolDepositResult.java index 0cffdd31f..c285fc7ba 100644 --- a/src/main/java/org/stellar/sdk/xdr/LiquidityPoolDepositResult.java +++ b/src/main/java/org/stellar/sdk/xdr/LiquidityPoolDepositResult.java @@ -10,8 +10,7 @@ // === xdr source ============================================================ -// union LiquidityPoolDepositResult switch ( -// LiquidityPoolDepositResultCode code) +// union LiquidityPoolDepositResult switch (LiquidityPoolDepositResultCode code) // { // case LIQUIDITY_POOL_DEPOSIT_SUCCESS: // void; diff --git a/src/main/java/org/stellar/sdk/xdr/LiquidityPoolEntry.java b/src/main/java/org/stellar/sdk/xdr/LiquidityPoolEntry.java index 205cc6ea1..d1580e5f3 100644 --- a/src/main/java/org/stellar/sdk/xdr/LiquidityPoolEntry.java +++ b/src/main/java/org/stellar/sdk/xdr/LiquidityPoolEntry.java @@ -24,7 +24,8 @@ // int64 reserveA; // amount of A in the pool // int64 reserveB; // amount of B in the pool // int64 totalPoolShares; // total number of pool shares issued -// int64 poolSharesTrustLineCount; // number of trust lines for the associated pool shares +// int64 poolSharesTrustLineCount; // number of trust lines for the +// // associated pool shares // } constantProduct; // } // body; diff --git a/src/main/java/org/stellar/sdk/xdr/LiquidityPoolWithdrawOp.java b/src/main/java/org/stellar/sdk/xdr/LiquidityPoolWithdrawOp.java index ff3a54855..1eefc51f6 100644 --- a/src/main/java/org/stellar/sdk/xdr/LiquidityPoolWithdrawOp.java +++ b/src/main/java/org/stellar/sdk/xdr/LiquidityPoolWithdrawOp.java @@ -13,9 +13,9 @@ // struct LiquidityPoolWithdrawOp // { // PoolID liquidityPoolID; -// int64 amount; // amount of pool shares to withdraw -// int64 minAmountA; // minimum amount of first asset to withdraw -// int64 minAmountB; // minimum amount of second asset to withdraw +// int64 amount; // amount of pool shares to withdraw +// int64 minAmountA; // minimum amount of first asset to withdraw +// int64 minAmountB; // minimum amount of second asset to withdraw // }; // =========================================================================== diff --git a/src/main/java/org/stellar/sdk/xdr/LiquidityPoolWithdrawResult.java b/src/main/java/org/stellar/sdk/xdr/LiquidityPoolWithdrawResult.java index 070ed976d..106fea64a 100644 --- a/src/main/java/org/stellar/sdk/xdr/LiquidityPoolWithdrawResult.java +++ b/src/main/java/org/stellar/sdk/xdr/LiquidityPoolWithdrawResult.java @@ -10,8 +10,7 @@ // === xdr source ============================================================ -// union LiquidityPoolWithdrawResult switch ( -// LiquidityPoolWithdrawResultCode code) +// union LiquidityPoolWithdrawResult switch (LiquidityPoolWithdrawResultCode code) // { // case LIQUIDITY_POOL_WITHDRAW_SUCCESS: // void; diff --git a/src/main/java/org/stellar/sdk/xdr/LiquidityPoolWithdrawResultCode.java b/src/main/java/org/stellar/sdk/xdr/LiquidityPoolWithdrawResultCode.java index dd8b95831..86b5d564a 100644 --- a/src/main/java/org/stellar/sdk/xdr/LiquidityPoolWithdrawResultCode.java +++ b/src/main/java/org/stellar/sdk/xdr/LiquidityPoolWithdrawResultCode.java @@ -15,14 +15,14 @@ // LIQUIDITY_POOL_WITHDRAW_SUCCESS = 0, // // // codes considered as "failure" for the operation -// LIQUIDITY_POOL_WITHDRAW_MALFORMED = -1, // bad input -// LIQUIDITY_POOL_WITHDRAW_NO_TRUST = -2, // no trust line for one of the -// // assets -// LIQUIDITY_POOL_WITHDRAW_UNDERFUNDED = -3, // not enough balance of the -// // pool share -// LIQUIDITY_POOL_WITHDRAW_LINE_FULL = -4, // would go above limit for one -// // of the assets -// LIQUIDITY_POOL_WITHDRAW_UNDER_MINIMUM = -5 // didn't withdraw enough +// LIQUIDITY_POOL_WITHDRAW_MALFORMED = -1, // bad input +// LIQUIDITY_POOL_WITHDRAW_NO_TRUST = -2, // no trust line for one of the +// // assets +// LIQUIDITY_POOL_WITHDRAW_UNDERFUNDED = -3, // not enough balance of the +// // pool share +// LIQUIDITY_POOL_WITHDRAW_LINE_FULL = -4, // would go above limit for one +// // of the assets +// LIQUIDITY_POOL_WITHDRAW_UNDER_MINIMUM = -5 // didn't withdraw enough // }; // =========================================================================== diff --git a/src/main/java/org/stellar/sdk/xdr/OfferEntryFlags.java b/src/main/java/org/stellar/sdk/xdr/OfferEntryFlags.java index 32bd84157..3c55d31bd 100644 --- a/src/main/java/org/stellar/sdk/xdr/OfferEntryFlags.java +++ b/src/main/java/org/stellar/sdk/xdr/OfferEntryFlags.java @@ -11,7 +11,8 @@ // enum OfferEntryFlags // { -// // an offer with this flag will not act on and take a reverse offer of equal price +// // an offer with this flag will not act on and take a reverse offer of equal +// // price // PASSIVE_FLAG = 1 // }; diff --git a/src/main/java/org/stellar/sdk/xdr/PreconditionType.java b/src/main/java/org/stellar/sdk/xdr/PreconditionType.java index 6636f2560..8fa52d098 100644 --- a/src/main/java/org/stellar/sdk/xdr/PreconditionType.java +++ b/src/main/java/org/stellar/sdk/xdr/PreconditionType.java @@ -9,7 +9,8 @@ // === xdr source ============================================================ -// enum PreconditionType { +// enum PreconditionType +// { // PRECOND_NONE = 0, // PRECOND_TIME = 1, // PRECOND_V2 = 2 diff --git a/src/main/java/org/stellar/sdk/xdr/Preconditions.java b/src/main/java/org/stellar/sdk/xdr/Preconditions.java index 88e91a80e..200775af8 100644 --- a/src/main/java/org/stellar/sdk/xdr/Preconditions.java +++ b/src/main/java/org/stellar/sdk/xdr/Preconditions.java @@ -10,13 +10,14 @@ // === xdr source ============================================================ -// union Preconditions switch (PreconditionType type) { -// case PRECOND_NONE: -// void; -// case PRECOND_TIME: -// TimeBounds timeBounds; -// case PRECOND_V2: -// PreconditionsV2 v2; +// union Preconditions switch (PreconditionType type) +// { +// case PRECOND_NONE: +// void; +// case PRECOND_TIME: +// TimeBounds timeBounds; +// case PRECOND_V2: +// PreconditionsV2 v2; // }; // =========================================================================== diff --git a/src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java b/src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java index 9dce42bcb..545881019 100644 --- a/src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java +++ b/src/main/java/org/stellar/sdk/xdr/PreconditionsV2.java @@ -11,13 +11,14 @@ // === xdr source ============================================================ -// struct PreconditionsV2 { -// TimeBounds *timeBounds; +// struct PreconditionsV2 +// { +// TimeBounds* timeBounds; // // // Transaction only valid for ledger numbers n such that // // minLedger <= n < maxLedger (if maxLedger == 0, then // // only minLedger is checked) -// LedgerBounds *ledgerBounds; +// LedgerBounds* ledgerBounds; // // // If NULL, only valid when sourceAccount's sequence number // // is seqNum - 1. Otherwise, valid when sourceAccount's @@ -25,7 +26,7 @@ // // Note that after execution the account's sequence number // // is always raised to tx.seqNum, and a transaction is not // // valid if tx.seqNum is too high to ensure replay protection. -// SequenceNumber *minSeqNum; +// SequenceNumber* minSeqNum; // // // For the transaction to be valid, the current ledger time must // // be at least minSeqAge greater than sourceAccount's seqTime. diff --git a/src/main/java/org/stellar/sdk/xdr/SignerKey.java b/src/main/java/org/stellar/sdk/xdr/SignerKey.java index a6aea4d82..b8ea88953 100644 --- a/src/main/java/org/stellar/sdk/xdr/SignerKey.java +++ b/src/main/java/org/stellar/sdk/xdr/SignerKey.java @@ -22,7 +22,8 @@ // /* Hash of random 256 bit preimage X */ // uint256 hashX; // case SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD: -// struct { +// struct +// { // /* Public key that must sign the payload. */ // uint256 ed25519; // /* Payload to be raw signed by ed25519. */ diff --git a/src/main/java/org/stellar/sdk/xdr/TransactionResultCode.java b/src/main/java/org/stellar/sdk/xdr/TransactionResultCode.java index f84aeb6bb..95172b154 100644 --- a/src/main/java/org/stellar/sdk/xdr/TransactionResultCode.java +++ b/src/main/java/org/stellar/sdk/xdr/TransactionResultCode.java @@ -31,7 +31,9 @@ // txNOT_SUPPORTED = -12, // transaction type not supported // txFEE_BUMP_INNER_FAILED = -13, // fee bump inner transaction failed // txBAD_SPONSORSHIP = -14, // sponsorship not confirmed -// txBAD_MIN_SEQ_AGE_OR_GAP = -15 //minSeqAge or minSeqLedgerGap conditions not met +// txBAD_MIN_SEQ_AGE_OR_GAP = +// -15, // minSeqAge or minSeqLedgerGap conditions not met +// txMALFORMED = -16 // precondition is invalid // }; // =========================================================================== @@ -53,6 +55,7 @@ public enum TransactionResultCode implements XdrElement { txFEE_BUMP_INNER_FAILED(-13), txBAD_SPONSORSHIP(-14), txBAD_MIN_SEQ_AGE_OR_GAP(-15), + txMALFORMED(-16), ; private int mValue; @@ -84,6 +87,7 @@ public static TransactionResultCode decode(XdrDataInputStream stream) throws IOE case -13: return txFEE_BUMP_INNER_FAILED; case -14: return txBAD_SPONSORSHIP; case -15: return txBAD_MIN_SEQ_AGE_OR_GAP; + case -16: return txMALFORMED; default: throw new RuntimeException("Unknown enum value: " + value); } diff --git a/xdr/Stellar-ledger-entries.x b/xdr/Stellar-ledger-entries.x index e0f719d0f..3eb578f16 100644 --- a/xdr/Stellar-ledger-entries.x +++ b/xdr/Stellar-ledger-entries.x @@ -306,7 +306,8 @@ struct TrustLineEntry enum OfferEntryFlags { - // an offer with this flag will not act on and take a reverse offer of equal price + // an offer with this flag will not act on and take a reverse offer of equal + // price PASSIVE_FLAG = 1 }; @@ -387,7 +388,7 @@ case CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME: int64 absBefore; // Predicate will be true if closeTime < absBefore case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME: int64 relBefore; // Seconds since closeTime of the ledger in which the - // ClaimableBalanceEntry was created + // ClaimableBalanceEntry was created }; enum ClaimantType @@ -466,7 +467,7 @@ struct LiquidityPoolConstantProductParameters { Asset assetA; // assetA < assetB Asset assetB; - int32 fee; // Fee is in basis points, so the actual rate is (fee/100)% + int32 fee; // Fee is in basis points, so the actual rate is (fee/100)% }; struct LiquidityPoolEntry @@ -483,7 +484,8 @@ struct LiquidityPoolEntry int64 reserveA; // amount of A in the pool int64 reserveB; // amount of B in the pool int64 totalPoolShares; // total number of pool shares issued - int64 poolSharesTrustLineCount; // number of trust lines for the associated pool shares + int64 poolSharesTrustLineCount; // number of trust lines for the + // associated pool shares } constantProduct; } body; diff --git a/xdr/Stellar-transaction.x b/xdr/Stellar-transaction.x index a9fdbe69f..f2f593c21 100644 --- a/xdr/Stellar-transaction.x +++ b/xdr/Stellar-transaction.x @@ -445,10 +445,10 @@ const LIQUIDITY_POOL_FEE_V18 = 30; struct LiquidityPoolDepositOp { PoolID liquidityPoolID; - int64 maxAmountA; // maximum amount of first asset to deposit - int64 maxAmountB; // maximum amount of second asset to deposit - Price minPrice; // minimum depositA/depositB - Price maxPrice; // maximum depositA/depositB + int64 maxAmountA; // maximum amount of first asset to deposit + int64 maxAmountB; // maximum amount of second asset to deposit + Price minPrice; // minimum depositA/depositB + Price maxPrice; // maximum depositA/depositB }; /* Withdraw assets from a liquidity pool @@ -460,9 +460,9 @@ struct LiquidityPoolDepositOp struct LiquidityPoolWithdrawOp { PoolID liquidityPoolID; - int64 amount; // amount of pool shares to withdraw - int64 minAmountA; // minimum amount of first asset to withdraw - int64 minAmountB; // minimum amount of second asset to withdraw + int64 amount; // amount of pool shares to withdraw + int64 minAmountA; // minimum amount of first asset to withdraw + int64 minAmountB; // minimum amount of second asset to withdraw }; /* An operation is the lowest unit of work that a transaction does */ @@ -582,13 +582,14 @@ struct LedgerBounds uint32 maxLedger; // 0 here means no maxLedger }; -struct PreconditionsV2 { - TimeBounds *timeBounds; +struct PreconditionsV2 +{ + TimeBounds* timeBounds; // Transaction only valid for ledger numbers n such that // minLedger <= n < maxLedger (if maxLedger == 0, then // only minLedger is checked) - LedgerBounds *ledgerBounds; + LedgerBounds* ledgerBounds; // If NULL, only valid when sourceAccount's sequence number // is seqNum - 1. Otherwise, valid when sourceAccount's @@ -596,7 +597,7 @@ struct PreconditionsV2 { // Note that after execution the account's sequence number // is always raised to tx.seqNum, and a transaction is not // valid if tx.seqNum is too high to ensure replay protection. - SequenceNumber *minSeqNum; + SequenceNumber* minSeqNum; // For the transaction to be valid, the current ledger time must // be at least minSeqAge greater than sourceAccount's seqTime. @@ -614,19 +615,21 @@ struct PreconditionsV2 { SignerKey extraSigners<2>; }; -enum PreconditionType { +enum PreconditionType +{ PRECOND_NONE = 0, PRECOND_TIME = 1, PRECOND_V2 = 2 }; -union Preconditions switch (PreconditionType type) { - case PRECOND_NONE: - void; - case PRECOND_TIME: - TimeBounds timeBounds; - case PRECOND_V2: - PreconditionsV2 v2; +union Preconditions switch (PreconditionType type) +{ +case PRECOND_NONE: + void; +case PRECOND_TIME: + TimeBounds timeBounds; +case PRECOND_V2: + PreconditionsV2 v2; }; // maximum number of operations per transaction @@ -1107,10 +1110,12 @@ enum ChangeTrustResultCode // cannot create with a limit of 0 CHANGE_TRUST_LOW_RESERVE = -4, // not enough funds to create a new trust line, - CHANGE_TRUST_SELF_NOT_ALLOWED = -5, // trusting self is not allowed + CHANGE_TRUST_SELF_NOT_ALLOWED = -5, // trusting self is not allowed CHANGE_TRUST_TRUST_LINE_MISSING = -6, // Asset trustline is missing for pool - CHANGE_TRUST_CANNOT_DELETE = -7, // Asset trustline is still referenced in a pool - CHANGE_TRUST_NOT_AUTH_MAINTAIN_LIABILITIES = -8 // Asset trustline is deauthorized + CHANGE_TRUST_CANNOT_DELETE = + -7, // Asset trustline is still referenced in a pool + CHANGE_TRUST_NOT_AUTH_MAINTAIN_LIABILITIES = + -8 // Asset trustline is deauthorized }; union ChangeTrustResult switch (ChangeTrustResultCode code) @@ -1132,10 +1137,10 @@ enum AllowTrustResultCode ALLOW_TRUST_NO_TRUST_LINE = -2, // trustor does not have a trustline // source account does not require trust ALLOW_TRUST_TRUST_NOT_REQUIRED = -3, - ALLOW_TRUST_CANT_REVOKE = -4, // source account can't revoke trust, + ALLOW_TRUST_CANT_REVOKE = -4, // source account can't revoke trust, ALLOW_TRUST_SELF_NOT_ALLOWED = -5, // trusting self is not allowed - ALLOW_TRUST_LOW_RESERVE = -6 // claimable balances can't be created - // on revoke due to low reserves + ALLOW_TRUST_LOW_RESERVE = -6 // claimable balances can't be created + // on revoke due to low reserves }; union AllowTrustResult switch (AllowTrustResultCode code) @@ -1432,8 +1437,7 @@ enum LiquidityPoolDepositResultCode LIQUIDITY_POOL_DEPOSIT_POOL_FULL = -7 // pool reserves are full }; -union LiquidityPoolDepositResult switch ( - LiquidityPoolDepositResultCode code) +union LiquidityPoolDepositResult switch (LiquidityPoolDepositResultCode code) { case LIQUIDITY_POOL_DEPOSIT_SUCCESS: void; @@ -1449,18 +1453,17 @@ enum LiquidityPoolWithdrawResultCode LIQUIDITY_POOL_WITHDRAW_SUCCESS = 0, // codes considered as "failure" for the operation - LIQUIDITY_POOL_WITHDRAW_MALFORMED = -1, // bad input - LIQUIDITY_POOL_WITHDRAW_NO_TRUST = -2, // no trust line for one of the - // assets - LIQUIDITY_POOL_WITHDRAW_UNDERFUNDED = -3, // not enough balance of the - // pool share - LIQUIDITY_POOL_WITHDRAW_LINE_FULL = -4, // would go above limit for one - // of the assets - LIQUIDITY_POOL_WITHDRAW_UNDER_MINIMUM = -5 // didn't withdraw enough + LIQUIDITY_POOL_WITHDRAW_MALFORMED = -1, // bad input + LIQUIDITY_POOL_WITHDRAW_NO_TRUST = -2, // no trust line for one of the + // assets + LIQUIDITY_POOL_WITHDRAW_UNDERFUNDED = -3, // not enough balance of the + // pool share + LIQUIDITY_POOL_WITHDRAW_LINE_FULL = -4, // would go above limit for one + // of the assets + LIQUIDITY_POOL_WITHDRAW_UNDER_MINIMUM = -5 // didn't withdraw enough }; -union LiquidityPoolWithdrawResult switch ( - LiquidityPoolWithdrawResultCode code) +union LiquidityPoolWithdrawResult switch (LiquidityPoolWithdrawResultCode code) { case LIQUIDITY_POOL_WITHDRAW_SUCCESS: void; @@ -1562,7 +1565,9 @@ enum TransactionResultCode txNOT_SUPPORTED = -12, // transaction type not supported txFEE_BUMP_INNER_FAILED = -13, // fee bump inner transaction failed txBAD_SPONSORSHIP = -14, // sponsorship not confirmed - txBAD_MIN_SEQ_AGE_OR_GAP = -15 //minSeqAge or minSeqLedgerGap conditions not met + txBAD_MIN_SEQ_AGE_OR_GAP = + -15, // minSeqAge or minSeqLedgerGap conditions not met + txMALFORMED = -16 // precondition is invalid }; // InnerTransactionResult must be binary compatible with TransactionResult @@ -1592,6 +1597,7 @@ struct InnerTransactionResult // txFEE_BUMP_INNER_FAILED is not included case txBAD_SPONSORSHIP: case txBAD_MIN_SEQ_AGE_OR_GAP: + case txMALFORMED: void; } result; diff --git a/xdr/Stellar-types.x b/xdr/Stellar-types.x index 562f8fc26..c3a1ebe2c 100644 --- a/xdr/Stellar-types.x +++ b/xdr/Stellar-types.x @@ -17,9 +17,10 @@ typedef hyper int64; // An ExtensionPoint is always marshaled as a 32-bit 0 value. At a // later point, it can be replaced by a different union so as to // extend a structure. -union ExtensionPoint switch (int v) { +union ExtensionPoint switch (int v) +{ case 0: - void; + void; }; enum CryptoKeyType @@ -63,7 +64,8 @@ case SIGNER_KEY_TYPE_HASH_X: /* Hash of random 256 bit preimage X */ uint256 hashX; case SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD: - struct { + struct + { /* Public key that must sign the payload. */ uint256 ed25519; /* Payload to be raw signed by ed25519. */ From 9d0c3cf7296c6c64c3547c0e9e9be5e776cb64e5 Mon Sep 17 00:00:00 2001 From: shawn Date: Thu, 14 Apr 2022 14:50:42 -0700 Subject: [PATCH 27/29] updated javadoc on key pair signing Co-authored-by: George --- src/main/java/org/stellar/sdk/KeyPair.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/stellar/sdk/KeyPair.java b/src/main/java/org/stellar/sdk/KeyPair.java index 1c71185e9..0696eadb0 100644 --- a/src/main/java/org/stellar/sdk/KeyPair.java +++ b/src/main/java/org/stellar/sdk/KeyPair.java @@ -235,7 +235,7 @@ public byte[] sign(byte[] data) { /** * Sign the provided data with the keypair's private key and returns {@link DecoratedSignature}. * - * @param data the data to sign, typically the tx hash + * @param data the data to sign * @return DecoratedSignature */ public DecoratedSignature signDecorated(byte[] data) { From 7c0b21073afe65ab4bf5fb406e08c9f6624373af Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Thu, 14 Apr 2022 16:44:38 -0700 Subject: [PATCH 28/29] #412: fixed timeout configs on tx builder, more tests --- .../org/stellar/sdk/FeeBumpTransaction.java | 4 +- .../org/stellar/sdk/TransactionBuilder.java | 27 +++--- .../stellar/sdk/TransactionPreconditions.java | 6 +- .../stellar/sdk/TransactionBuilderTest.java | 95 ++++++++++++++++--- .../sdk/TransactionPreconditionsTest.java | 18 ++-- 5 files changed, 106 insertions(+), 44 deletions(-) diff --git a/src/main/java/org/stellar/sdk/FeeBumpTransaction.java b/src/main/java/org/stellar/sdk/FeeBumpTransaction.java index 4970c45ab..0720cbc21 100644 --- a/src/main/java/org/stellar/sdk/FeeBumpTransaction.java +++ b/src/main/java/org/stellar/sdk/FeeBumpTransaction.java @@ -1,9 +1,7 @@ package org.stellar.sdk; -import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.collect.Lists; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; import org.stellar.sdk.xdr.DecoratedSignature; import org.stellar.sdk.xdr.EnvelopeType; import org.stellar.sdk.xdr.FeeBumpTransactionEnvelope; @@ -118,7 +116,7 @@ public static class Builder { * Construct a new fee bump transaction builder. * * @param accountConverter The AccountConverter which will be used to encode the fee account. - * @param inner The inner transaction which will be fee bumped. + * @param inner The inner transaction which will be fee bumped. read-only, the */ public Builder(AccountConverter accountConverter, final Transaction inner) { checkNotNull(inner, "inner cannot be null"); diff --git a/src/main/java/org/stellar/sdk/TransactionBuilder.java b/src/main/java/org/stellar/sdk/TransactionBuilder.java index c6c3bc2e1..7564a9749 100644 --- a/src/main/java/org/stellar/sdk/TransactionBuilder.java +++ b/src/main/java/org/stellar/sdk/TransactionBuilder.java @@ -10,6 +10,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Lists.newArrayList; +import static org.stellar.sdk.TransactionPreconditions.TIMEOUT_INFINITE; /** * Builds a new Transaction object. @@ -22,7 +23,6 @@ public class TransactionBuilder { private Integer mBaseFee; private Network mNetwork; private TransactionPreconditions mPreconditions; - private boolean mTimeoutSet; /** * Construct a new transaction builder. @@ -150,7 +150,7 @@ public TransactionBuilder addTimeBounds(TimeBounds timeBounds) { * set instead for more control over preconditions. */ public TransactionBuilder setTimeout(long timeout) { - if (mPreconditions.getTimeBounds() != null && mPreconditions.getTimeBounds().getMaxTime() != TransactionPreconditions.TIMEOUT_INFINITE) { + if (mPreconditions.getTimeBounds() != null && mPreconditions.getTimeBounds().getMaxTime() != TIMEOUT_INFINITE) { throw new RuntimeException("TimeBounds.max_time has been already set - setting timeout would overwrite it."); } @@ -158,17 +158,15 @@ public TransactionBuilder setTimeout(long timeout) { throw new RuntimeException("timeout cannot be negative"); } - mTimeoutSet = true; - if (timeout > 0) { - long timeoutTimestamp = System.currentTimeMillis() / 1000L + timeout; - TransactionPreconditionsBuilder preconditionsBuilder = mPreconditions.toBuilder(); - if (mPreconditions.getTimeBounds() == null) { - preconditionsBuilder.timeBounds(new TimeBounds(0, timeoutTimestamp)); - } else { - preconditionsBuilder.timeBounds( new TimeBounds(mPreconditions.getTimeBounds().getMinTime(), timeoutTimestamp)); - } - mPreconditions = preconditionsBuilder.build(); + long timeoutTimestamp = timeout != TIMEOUT_INFINITE ? System.currentTimeMillis() / 1000L + timeout : TIMEOUT_INFINITE; + + TransactionPreconditionsBuilder preconditionsBuilder = mPreconditions.toBuilder(); + if (mPreconditions.getTimeBounds() == null) { + preconditionsBuilder.timeBounds(new TimeBounds(0, timeoutTimestamp)); + } else { + preconditionsBuilder.timeBounds( new TimeBounds(mPreconditions.getTimeBounds().getMinTime(), timeoutTimestamp)); } + mPreconditions = preconditionsBuilder.build(); return this; } @@ -182,11 +180,10 @@ public TransactionBuilder setBaseFee(int baseFee) { } /** - * Builds a transaction. It will use the SequenceNumberStrategy if provided to determine how to update source account - * sequence number after transaction is constructed. + * Builds a transaction and increments the sequence number on the source account after transaction is constructed. */ public Transaction build() { - mPreconditions.isValid(mTimeoutSet); + mPreconditions.isValid(); if (mBaseFee == null) { throw new FormatException("mBaseFee has to be set. you must call setBaseFee()."); diff --git a/src/main/java/org/stellar/sdk/TransactionPreconditions.java b/src/main/java/org/stellar/sdk/TransactionPreconditions.java index fe070aa74..5a7ebadfd 100644 --- a/src/main/java/org/stellar/sdk/TransactionPreconditions.java +++ b/src/main/java/org/stellar/sdk/TransactionPreconditions.java @@ -35,9 +35,9 @@ public class TransactionPreconditions { List extraSigners; TimeBounds timeBounds; - public void isValid(boolean infiniteTimeoutWasSet) { - if (timeBounds == null && !infiniteTimeoutWasSet) { - throw new FormatException("Invalid preconditions, must define timebounds or set infinite timeout"); + public void isValid() { + if (timeBounds == null) { + throw new FormatException("Invalid preconditions, must define timebounds"); } if (extraSigners.size() > MAX_EXTRA_SIGNERS_COUNT) { diff --git a/src/test/java/org/stellar/sdk/TransactionBuilderTest.java b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java index 870e20d4b..88934d834 100644 --- a/src/test/java/org/stellar/sdk/TransactionBuilderTest.java +++ b/src/test/java/org/stellar/sdk/TransactionBuilderTest.java @@ -1,6 +1,5 @@ package org.stellar.sdk; -import com.google.common.base.Function; import com.google.common.io.BaseEncoding; import org.junit.Test; import org.stellar.sdk.xdr.Int64; @@ -58,7 +57,7 @@ public void testBuilderSuccessTestnet() throws Exception { assertEquals(transaction.getFee(), 100); assertEquals( - "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAO3gUmG83C+VCqO6FztuMtXJF/l7grZA7MjRzqdZ9W8QAAAABKgXyAAAAAAAAAAAAbaHlWIAAABAy5IvTou9NDetC6PIFJhBR2yr2BuEEql4iyLfU9K7tjuQaYVZf40fbWLRwA/lHg2IYFzYMFMakxLtVrpLxMmHAw==", + "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAADt4FJhvNwvlQqjuhc7bjLVyRf5e4K2QOzI0c6nWfVvEAAAAASoF8gAAAAAAAAAAAG2h5ViAAAAQLJxvwao6eyNHaDX2QFhgdqlxJUqkpgA03UUOqf4DwOXSV9GN4ZWut2uzRuza4DWyVGBEHmmnQX+SQKFo0Sb/wA=", transaction.toEnvelopeXdrBase64()); // Convert transaction to binary XDR and back again to make sure correctly xdr de/serialized. @@ -99,7 +98,7 @@ public void testBuilderMemoText() throws Exception { assertEquals(transaction, transaction2); assertEquals( - "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAAAAAABAAAADEhlbGxvIHdvcmxkIQAAAAEAAAAAAAAAAAAAAADt4FJhvNwvlQqjuhc7bjLVyRf5e4K2QOzI0c6nWfVvEAAAAASoF8gAAAAAAAAAAAG2h5ViAAAAQMc6HwYaGsrlJ8/LdE9VDVq04JifpQofSmnjhrtqaTTs/VBsNGmxi4b/vaFkLLLWh8emI8FsS/vBgb8AVFVkZQU=", + "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAxIZWxsbyB3b3JsZCEAAAABAAAAAAAAAAAAAAAA7eBSYbzcL5UKo7oXO24y1ckX+XuCtkDsyNHOp1n1bxAAAAAEqBfIAAAAAAAAAAABtoeVYgAAAEA2U3MBHRAxe/nYUTFsL14hgWxcq1Z0yFcEd2LSEt+TbXbVXHl77k/sjLdUGw/0qMZMOn2n50MY+w1pnx6mjfsM", transaction.toEnvelopeXdrBase64()); } @@ -160,7 +159,7 @@ public void testBuilderBaseFee() throws Exception { assertEquals(transaction, transaction2); assertEquals( - "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAMgAClWjAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAO3gUmG83C+VCqO6FztuMtXJF/l7grZA7MjRzqdZ9W8QAAAABKgXyAAAAAAAAAAAAbaHlWIAAABA9TG3dKKLtLHzRUbsbEqr68CfUc800p1/LE5pWzCnFdFdypdXgyqHqw/sWdaTUMDiWawBtsmqV8oOtD0Hw1HDDQ==", + "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAMgAClWjAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAADt4FJhvNwvlQqjuhc7bjLVyRf5e4K2QOzI0c6nWfVvEAAAAASoF8gAAAAAAAAAAAG2h5ViAAAAQAIeBLnUSyzqnFYWPm0Y06/v78VquGfjQokPrMBCeOZRM4WqPLOI5/Mgn1+djGFBOVCKB+HUevtqN2DMhnhYmg8=", transaction.toEnvelopeXdrBase64()); } @@ -500,6 +499,7 @@ public void testBuilderFailsWhenSettingTimeoutAndMaxTimeAlreadySet() throws IOEx try { new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .setBaseFee(Transaction.MIN_BASE_FEE) .addTimeBounds(new TimeBounds(42, 1337)) .setTimeout(10) .build(); @@ -516,6 +516,7 @@ public void testBuilderFailsWhenSettingTimeboundsAndAlreadySet() throws IOExcept try { new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .setBaseFee(Transaction.MIN_BASE_FEE) .setTimeout(10) .addTimeBounds(new TimeBounds(42, 1337)) .build(); @@ -527,18 +528,88 @@ public void testBuilderFailsWhenSettingTimeboundsAndAlreadySet() throws IOExcept } @Test - public void testBuilderTimeoutAndMaxTimeNotSet() throws IOException { + public void testBuilderFailsWhenNoTimeBoundsOrTimeoutSet() throws IOException { Account account = new Account(KeyPair.random().getAccountId(), 2908908335136768L); + try { + new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + fail(); + } catch (RuntimeException exception) { + assertTrue(exception.getMessage().contains("Invalid preconditions, must define timebounds")); + assertEquals(new Long(2908908335136768L), account.getSequenceNumber()); + } + } + + @Test + public void testBuilderInfinteTimeoutOnly() throws IOException { + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); + Account account = new Account(source.getAccountId(), 2908908335136768L); + + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) + .setBaseFee(Transaction.MIN_BASE_FEE) + .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) + .build(); + + transaction.sign(source); + + // Convert transaction to binary XDR and back again to make sure timeout is correctly de/serialized. + XdrDataInputStream is = new XdrDataInputStream(new ByteArrayInputStream(BaseEncoding.base64().decode(transaction.toEnvelopeXdrBase64()))); + org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); + + assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMinTime().getTimePoint().getUint64().longValue(), 0); + assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMaxTime().getTimePoint().getUint64().longValue(), 0); + } + + @Test + public void testBuilderTimeoutOnly() throws IOException { + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); + Account account = new Account(source.getAccountId(), 2908908335136768L); long currentUnix = System.currentTimeMillis() / 1000L; + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) - .addOperation(new CreateAccountOperation.Builder(KeyPair.random().getAccountId(), "2000").build()) - .addTimeBounds(new TimeBounds(42, 0)) + .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) + .setBaseFee(Transaction.MIN_BASE_FEE) + .setTimeout(10) + .build(); + + transaction.sign(source); + + // Convert transaction to binary XDR and back again to make sure timeout is correctly de/serialized. + XdrDataInputStream is = new XdrDataInputStream(new ByteArrayInputStream(BaseEncoding.base64().decode(transaction.toEnvelopeXdrBase64()))); + org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); + + assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMinTime().getTimePoint().getUint64().longValue(), 0); + assertTrue(currentUnix + 10 <= decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMaxTime().getTimePoint().getUint64().longValue() ); + } + + @Test + public void testBuilderTimeoutAndMaxTimeNotSet() throws IOException { + KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS"); + KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR"); + Account account = new Account(source.getAccountId(), 2908908335136768L); + long currentUnix = System.currentTimeMillis() / 1000L; + + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) + .addTimeBounds(new TimeBounds(42, TransactionPreconditions.TIMEOUT_INFINITE)) .setTimeout(10) .setBaseFee(Transaction.MIN_BASE_FEE) .build(); - assertEquals(42, transaction.getTimeBounds().getMinTime()); - assertTrue(currentUnix + 10 <= transaction.getTimeBounds().getMaxTime()); + transaction.sign(source); + + // Convert transaction to binary XDR and back again to make sure timeout is correctly de/serialized. + XdrDataInputStream is = new XdrDataInputStream(new ByteArrayInputStream(BaseEncoding.base64().decode(transaction.toEnvelopeXdrBase64()))); + org.stellar.sdk.xdr.TransactionEnvelope decodedTransaction = org.stellar.sdk.xdr.TransactionEnvelope.decode(is); + + assertEquals(decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMinTime().getTimePoint().getUint64().longValue(), 42); + assertTrue(currentUnix + 10 <= decodedTransaction.getV1().getTx().getCond().getTimeBounds().getMaxTime().getTimePoint().getUint64().longValue() ); + } @Test @@ -550,7 +621,7 @@ public void testBuilderInfinteTimeoutAndMaxTimeNotSet() throws FormatException, Account account = new Account(source.getAccountId(), 2908908335136768L); Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) - .addTimeBounds(new TimeBounds(42, 0)) + .addTimeBounds(new TimeBounds(42, TransactionPreconditions.TIMEOUT_INFINITE)) .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .addMemo(Memo.hash("abcdef")) .setBaseFee(100) @@ -584,7 +655,7 @@ public void testBuilderSuccessPublic() throws FormatException, IOException { Transaction decodedTransaction = (Transaction) Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), transaction.toEnvelopeXdrBase64(), Network.PUBLIC); assertEquals( - "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAO3gUmG83C+VCqO6FztuMtXJF/l7grZA7MjRzqdZ9W8QAAAABKgXyAAAAAAAAAAAAbaHlWIAAABA830eT4ERYpuVnb6vSJnWTVhgcQVsGJED2vZeYogXbTy8wGb+qo/ojn0q6op7KBdF6y5MHSHGUFbad1UR4UaFBA==", + "AAAAAgAAAABexSIg06FtXzmFBQQtHZsrnyWxUzmthkBEhs/ktoeVYgAAAGQAClWjAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAADt4FJhvNwvlQqjuhc7bjLVyRf5e4K2QOzI0c6nWfVvEAAAAASoF8gAAAAAAAAAAAG2h5ViAAAAQOFMVS3lQ1gtqY0a3VPsZXCNVGC/h8dANFiUS7hYPWSbyzi4Ob1ir5R256mOwX+B6vE552+y8JAFaAFPR0bDyAw=", transaction.toEnvelopeXdrBase64()); assertEquals(decodedTransaction, transaction); @@ -618,6 +689,7 @@ public void testTryingToAddMemoTwice() throws FormatException, IOException { Account account = new Account(source.getAccountId(), 2908908335136768L); new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) + .setBaseFee(Transaction.MIN_BASE_FEE) .addMemo(Memo.none()) .addMemo(Memo.none()); fail(); @@ -636,6 +708,7 @@ public void testNoNetworkSet() throws FormatException { try { new TransactionBuilder(AccountConverter.enableMuxed(), account, null) .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build()) + .setBaseFee(Transaction.MIN_BASE_FEE) .addMemo(Memo.none()) .setTimeout(TransactionPreconditions.TIMEOUT_INFINITE) .build(); diff --git a/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java b/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java index f5a3023b0..24cdfe772 100644 --- a/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java +++ b/src/test/java/org/stellar/sdk/TransactionPreconditionsTest.java @@ -167,14 +167,14 @@ public void itConvertsNullTimeBoundsXdr() throws IOException { @Test public void itChecksValidityWhenTimebounds() { TransactionPreconditions preconditions = TransactionPreconditions.builder().timeBounds(new TimeBounds(1, 2)).build(); - preconditions.isValid(false); + preconditions.isValid(); } @Test public void itChecksNonValidityOfTimeBounds() { TransactionPreconditions preconditions = TransactionPreconditions.builder().build(); try { - preconditions.isValid(false); + preconditions.isValid(); fail(); } catch (FormatException ignored) {} } @@ -186,25 +186,19 @@ public void itChecksNonValidityOfExtraSignersSize() { .extraSigners(newArrayList(new SignerKey.Builder().build(), new SignerKey.Builder().build(), new SignerKey.Builder().build())) .build(); try { - preconditions.isValid(false); + preconditions.isValid(); fail(); } catch (FormatException ignored) {} } @Test - public void itChecksValidityWhenNoTimeboundsButTimeoutSet() { - TransactionPreconditions preconditions = TransactionPreconditions.builder().build(); - preconditions.isValid(true); - } - - @Test - public void itChecksValidityWhenNoTimeboundsAndNoTimeoutSet() { + public void itChecksValidityWhenNoTimeboundsSet() { TransactionPreconditions preconditions = TransactionPreconditions.builder().build(); try { - preconditions.isValid(false); + preconditions.isValid(); fail(); } catch (FormatException exception) { - assertTrue(exception.getMessage().contains("Invalid preconditions, must define timebounds or set infinite timeout")); + assertTrue(exception.getMessage().contains("Invalid preconditions, must define timebounds")); } } From a6a36c5d1637854b74b65d019a392e8313c2ee04 Mon Sep 17 00:00:00 2001 From: Shawn Reuland Date: Mon, 18 Apr 2022 10:19:10 -0700 Subject: [PATCH 29/29] #412: updated changelog to reflect final p19 changes --- CHANGELOG.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d04c9066e..213315ac8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,9 @@ As this project is pre 1.0, breaking changes may happen for minor version bumps. ## 0.32.0 (Pending) -* Update XDR definitions and auto-generated classes to support upcoming protocol 19 release ([#276](https://github.com/stellar/java-stellar-sdk/pull/276)). -* Extend StrKey implementation to handle [CAP 40 Payload Signer](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0040.md) ([#276](https://github.com/stellar/java-stellar-sdk/pull/276)). +* Update XDR definitions and auto-generated classes to support upcoming protocol 19 release ([#416](https://github.com/stellar/java-stellar-sdk/pull/416)). +* Extend StrKey implementation to handle [CAP 40 Payload Signer](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0040.md). * Extended Transaction submission settings, additional new Preconditions can be added now, refer to [CAP 21 Transaction Preconditions](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0021.md). -* Added ability to override the sequence number that is set by transaction builder. By default, transaction builder creates - transactions with sequence number equal to source account's sequence number incremented by 1. - Refer to `TransactionBuilder.addSequenceNumberResolver()` for using this optional new functionality. - ### Breaking changes