Skip to content

Commit cde3840

Browse files
authored
feat: add support for SEP-35. (#683)
1 parent 979fb81 commit cde3840

File tree

2 files changed

+245
-0
lines changed

2 files changed

+245
-0
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package org.stellar.sdk;
2+
3+
import lombok.EqualsAndHashCode;
4+
import lombok.Getter;
5+
import lombok.ToString;
6+
import lombok.Value;
7+
8+
/**
9+
* ID represents the total order of Ledgers, Transactions and Operations. This is an implementation
10+
* of <a
11+
* href="https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0035.md">SEP-0035</a>.
12+
*/
13+
@Getter
14+
@EqualsAndHashCode
15+
@ToString
16+
public class TOID {
17+
private int ledgerSequence;
18+
private int transactionOrder;
19+
private int operationIndex;
20+
21+
/**
22+
* Constructor for TOID.
23+
*
24+
* @param ledgerSequence The ledger sequence the operation was validated in.
25+
* @param transactionOrder The order that the transaction was applied within the ledger where it
26+
* was validated. The application order value starts at 1. The maximum supported number of
27+
* transactions per operation is 1,048,575.
28+
* @param operationIndex The index of the operation within that parent transaction. The operation
29+
* index value starts at 1. The maximum supported number of operations per transaction is
30+
* 4095.
31+
*/
32+
public TOID(int ledgerSequence, int transactionOrder, int operationIndex) {
33+
if (ledgerSequence < 0) {
34+
throw new IllegalArgumentException(
35+
"Invalid ledger sequence, it must be between 0 and 2147483647.");
36+
}
37+
38+
if (transactionOrder < 0 || transactionOrder > 0xFFFFF) {
39+
throw new IllegalArgumentException(
40+
"Invalid transaction order, it must be between 0 and 1048575.");
41+
}
42+
43+
if (operationIndex < 0 || operationIndex > 0xFFF) {
44+
throw new IllegalArgumentException("Invalid operation index, it must be between 0 and 4095.");
45+
}
46+
47+
this.ledgerSequence = ledgerSequence;
48+
this.transactionOrder = transactionOrder;
49+
this.operationIndex = operationIndex;
50+
}
51+
52+
/**
53+
* Converts the TOID to a signed 64-bit integer.
54+
*
55+
* @return The signed 64-bit integer representation of the TOID.
56+
*/
57+
public long toInt64() {
58+
return ((long) ledgerSequence << 32) | ((long) transactionOrder << 12) | operationIndex;
59+
}
60+
61+
/**
62+
* Converts a signed 64-bit integer to a TOID.
63+
*
64+
* @param value The signed 64-bit integer to convert.
65+
* @return A new TOID instance.
66+
*/
67+
public static TOID fromInt64(long value) {
68+
if (value < 0) {
69+
throw new IllegalArgumentException(
70+
"Invalid `value`, it must be between 0 and 9223372036854775807.");
71+
}
72+
return new TOID((int) (value >> 32), (int) ((value >> 12) & 0xFFFFF), (int) (value & 0xFFF));
73+
}
74+
75+
/**
76+
* Increments the operation order by 1, rolling over to the next ledger if overflow occurs. This
77+
* allows queries to easily advance a cursor to the next operation.
78+
*/
79+
public void incrementOperationIndex() {
80+
if (operationIndex == (1 << 12) - 1) {
81+
operationIndex = 0;
82+
ledgerSequence++;
83+
} else {
84+
operationIndex++;
85+
}
86+
}
87+
88+
/**
89+
* Creates a new TOID that represents the ledger time **after** any contents (e.g. transactions,
90+
* operations) that occur within the specified ledger.
91+
*
92+
* @param ledgerSequence The ledger sequence.
93+
* @return A new TOID instance.
94+
*/
95+
public static TOID afterLedger(int ledgerSequence) {
96+
return new TOID(ledgerSequence, (1 << 20) - 1, (1 << 12) - 1);
97+
}
98+
99+
/**
100+
* The inclusive range representation between two ledgers inclusive. The end value points at the
101+
* end+1 ledger so when using this range make sure < comparison is used for the upper bound.
102+
*
103+
* @param from The start ledger sequence.
104+
* @param to The end ledger sequence.
105+
* @return The TOIDRange representing the inclusive range between two ledgers.
106+
*/
107+
public static TOIDRange ledgerRangeInclusive(int from, int to) {
108+
if (from > to) {
109+
throw new IllegalArgumentException(
110+
"Invalid `from` and `to` values, `from` must be less than or equal to `to`.");
111+
}
112+
if (from <= 0) {
113+
throw new IllegalArgumentException("Invalid `from` value, it must be greater than 0.");
114+
}
115+
116+
long toidFrom = 0;
117+
if (from != 1) {
118+
toidFrom = new TOID(from, 0, 0).toInt64();
119+
}
120+
long toidTo = new TOID(to + 1, 0, 0).toInt64();
121+
return new TOIDRange(toidFrom, toidTo);
122+
}
123+
124+
@Value
125+
public static class TOIDRange {
126+
long start;
127+
long end;
128+
}
129+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package org.stellar.sdk;
2+
3+
import java.util.Arrays;
4+
import java.util.List;
5+
import org.junit.Assert;
6+
import org.junit.Test;
7+
8+
public class TOIDTest {
9+
10+
private static final long ledgerFirst = 1L << 32;
11+
private static final long txFirst = 1L << 12;
12+
private static final long opFirst = 1L;
13+
14+
@Test
15+
public void testToInt64AndFromInt64() {
16+
List<TOID> toids =
17+
Arrays.asList(
18+
new TOID(0, 0, 1),
19+
new TOID(0, 0, 4095),
20+
new TOID(0, 1, 0),
21+
new TOID(0, 1048575, 0),
22+
new TOID(1, 0, 0),
23+
new TOID(2_147_483_647, 0, 0),
24+
new TOID(1, 1, 1),
25+
new TOID(1, 1, 0),
26+
new TOID(1, 0, 1),
27+
new TOID(1, 0, 0),
28+
new TOID(0, 1, 0),
29+
new TOID(0, 0, 1),
30+
new TOID(0, 0, 0),
31+
new TOID(2_147_483_647, 1_048_575, 4_095));
32+
List<Long> ids =
33+
Arrays.asList(
34+
1L,
35+
4095L,
36+
4096L,
37+
4294963200L,
38+
4294967296L,
39+
9223372032559808512L,
40+
ledgerFirst + txFirst + opFirst,
41+
ledgerFirst + txFirst,
42+
ledgerFirst + opFirst,
43+
ledgerFirst,
44+
txFirst,
45+
opFirst,
46+
0L,
47+
9223372036854775807L);
48+
for (int i = 0; i < toids.size(); i++) {
49+
TOID toid = toids.get(i);
50+
long id = ids.get(i);
51+
Assert.assertEquals(id, toid.toInt64());
52+
Assert.assertEquals(toid, TOID.fromInt64(id));
53+
}
54+
}
55+
56+
@Test
57+
public void testLedgerRangeInclusive() {
58+
Assert.assertEquals(
59+
new TOID.TOIDRange(new TOID(0, 0, 0).toInt64(), new TOID(2, 0, 0).toInt64()),
60+
TOID.ledgerRangeInclusive(1, 1));
61+
Assert.assertEquals(
62+
new TOID.TOIDRange(new TOID(0, 0, 0).toInt64(), new TOID(3, 0, 0).toInt64()),
63+
TOID.ledgerRangeInclusive(1, 2));
64+
Assert.assertEquals(
65+
new TOID.TOIDRange(new TOID(2, 0, 0).toInt64(), new TOID(3, 0, 0).toInt64()),
66+
TOID.ledgerRangeInclusive(2, 2));
67+
Assert.assertEquals(
68+
new TOID.TOIDRange(new TOID(2, 0, 0).toInt64(), new TOID(4, 0, 0).toInt64()),
69+
TOID.ledgerRangeInclusive(2, 3));
70+
}
71+
72+
@Test
73+
public void testIncrementOperationIndex() {
74+
TOID toid = new TOID(0, 0, 0);
75+
toid.incrementOperationIndex();
76+
Assert.assertEquals(new TOID(0, 0, 1), toid);
77+
toid.incrementOperationIndex();
78+
Assert.assertEquals(new TOID(0, 0, 2), toid);
79+
toid.incrementOperationIndex();
80+
Assert.assertEquals(new TOID(0, 0, 3), toid);
81+
82+
toid = new TOID(0, 0, 0xFFF);
83+
toid.incrementOperationIndex();
84+
Assert.assertEquals(new TOID(1, 0, 0), toid);
85+
toid.incrementOperationIndex();
86+
Assert.assertEquals(new TOID(1, 0, 1), toid);
87+
}
88+
89+
@Test
90+
public void testAfterLedger() {
91+
Assert.assertEquals(4294967295L, TOID.afterLedger(0).toInt64());
92+
Assert.assertEquals(8589934591L, TOID.afterLedger(1).toInt64());
93+
Assert.assertEquals(433791696895L, TOID.afterLedger(100).toInt64());
94+
}
95+
96+
@Test
97+
public void initWithInvalidParamsThrows() {
98+
Assert.assertThrows(IllegalArgumentException.class, () -> new TOID(-1, 0, 0));
99+
Assert.assertThrows(IllegalArgumentException.class, () -> new TOID(0, -1, 0));
100+
Assert.assertThrows(IllegalArgumentException.class, () -> new TOID(0, 0, -1));
101+
Assert.assertThrows(IllegalArgumentException.class, () -> new TOID(0, 0xFFFFF + 1, 0));
102+
Assert.assertThrows(IllegalArgumentException.class, () -> new TOID(0, 0, 0xFFF + 1));
103+
}
104+
105+
@Test
106+
public void fromInt64WithInvalidParamsThrows() {
107+
Assert.assertThrows(IllegalArgumentException.class, () -> TOID.fromInt64(-1));
108+
}
109+
110+
@Test
111+
public void testLedgerRangeInclusiveWithInvalidParamsThrows() {
112+
Assert.assertThrows(IllegalArgumentException.class, () -> TOID.ledgerRangeInclusive(2, 1));
113+
Assert.assertThrows(IllegalArgumentException.class, () -> TOID.ledgerRangeInclusive(0, 1));
114+
Assert.assertThrows(IllegalArgumentException.class, () -> TOID.ledgerRangeInclusive(-1, 100));
115+
}
116+
}

0 commit comments

Comments
 (0)