Skip to content

Commit c362fd9

Browse files
authored
chore: move info from CTID repo into Standards repo (#341)
* add CTID implemenetations
1 parent 43bba72 commit c362fd9

File tree

7 files changed

+856
-1
lines changed

7 files changed

+856
-1
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Quickstart
2+
3+
## Improved Concise Transaction Identifier (CTID)
4+
5+
CTIDs are composed of 16 hex nibbles, and begin with a `C`.
6+
7+
```
8+
CXXXXXXXYYYYZZZZ
9+
```
10+
11+
The identifier is divided into three fields.
12+
13+
| Char Offset | Field | Size (bits) | Explanation |
14+
| ----------- | ------- | ----------- | --------------------------------------------- |
15+
| 0 | C | 4 | Lead-in (ignore) |
16+
| 1-7 | XXXXXXX | 28 | Ledger Sequence |
17+
| 8-11 | YYYY | 16 | Transaction index (offset) within that ledger |
18+
| 12-16 | ZZZZ | 16 | Network ID. |
19+
20+
Reference implementations are available for several languages. Click below to dive in.
21+
22+
| Language | Implementation |
23+
| ---------- | ------------------------------------------------------------ |
24+
| Javascript | [ctid.js](https://github.com/XRPLF/ctid/blob/main/ctid.js) |
25+
| Typescript | [ctid.ts](https://github.com/XRPLF/ctid/blob/main/ctid.ts) |
26+
| C++ | [ctid.cpp](https://github.com/XRPLF/ctid/blob/main/ctid.cpp) |
27+
| Python 3 | [ctid.py](https://github.com/XRPLF/ctid/blob/main/ctid.py) |
28+
| PHP 5 | [ctid.php](https://github.com/XRPLF/ctid/blob/main/ctid.php) |
29+
30+
### Function prototypes (pseudocode)
31+
32+
In this repo there are several reference implementations available for various languages but they all use the same function model.
33+
34+
```js
35+
function encodeCTID (
36+
ledger_seq : number,
37+
txn_index : number,
38+
network_id : number) -> string;
39+
```
40+
41+
```js
42+
function decodeCTID (ctid : string or number) -> {
43+
ledger_seq : number,
44+
txn_index : number,
45+
network_id : number };
46+
```
47+
48+
### Mainnet example
49+
50+
[This transaction](https://livenet.xrpl.org/transactions/D42BE7DF63B4C12E5B56B4EFAD8CBB096171399D93353A8A61F61066160DFE5E/raw) encodes in the following way:
51+
52+
```js
53+
encodeCTID(
54+
77727448, // ledger sequence number the txn appeared in
55+
54, // `TransactionIndex` as per metadata
56+
0,
57+
); // Network ID of mainnet is 0
58+
("C4A206D800360000");
59+
```
60+
61+
### Hooks testnet v3 example
62+
63+
[This transaction](https://hooks-testnet-v3-explorer.xrpl-labs.com/tx/C4E284010276F8457C4BF96D0C534B7383087680C159F9B8C18D5EE876F7EFE7) encodes in the following way:
64+
65+
```js
66+
encodeCTID(
67+
428986, // ledger sequence number the txn appeared in
68+
0, // `TransactionIndex` as per metadata
69+
21338,
70+
); // Network ID of hooks v3 is 21338
71+
("C0068BBA0000535A");
72+
```

XLS-0037-concise-transaction-identifier-ctid/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
1414
# Quickstart
1515

16-
If you are a developer and want to get started quickly with integrating CTID, please visit [the quickstart repo](https://github.com/xrplf/ctid).
16+
If you are a developer and want to get started quickly with integrating CTID, please visit [the quickstart](./QUICKSTART.md).
1717

1818
# Abstract
1919

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#include <cassert>
2+
#include <cstdint>
3+
#include <iomanip>
4+
#include <iostream>
5+
#include <optional>
6+
#include <regex>
7+
#include <sstream>
8+
#include <string>
9+
#include <tuple>
10+
#include <type_traits>
11+
12+
std::optional<std::string>
13+
encodeCTID(uint32_t ledger_seq, uint16_t txn_index, uint16_t network_id) noexcept
14+
{
15+
if (ledger_seq > 0xFFFFFFF)
16+
return {};
17+
18+
uint64_t ctidValue =
19+
((0xC0000000ULL + static_cast<uint64_t>(ledger_seq)) << 32) +
20+
(static_cast<uint64_t>(txn_index) << 16) + network_id;
21+
22+
std::stringstream buffer;
23+
buffer << std::hex << std::uppercase << std::setfill('0') << std::setw(16)
24+
<< ctidValue;
25+
return {buffer.str()};
26+
}
27+
28+
template <typename T>
29+
std::optional<std::tuple<uint32_t, uint16_t, uint16_t>>
30+
decodeCTID(const T ctid) noexcept
31+
{
32+
uint64_t ctidValue {0};
33+
if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, char *> ||
34+
std::is_same_v<T, const char *> ||
35+
std::is_same_v<T, std::string_view>)
36+
{
37+
const std::string ctidString(ctid);
38+
39+
if (ctidString.length() != 16)
40+
return {};
41+
42+
if (!std::regex_match(ctidString, std::regex("^[0-9A-F]+$")))
43+
return {};
44+
45+
ctidValue = std::stoull(ctidString, nullptr, 16);
46+
} else if constexpr (std::is_integral_v<T>)
47+
ctidValue = ctid;
48+
else
49+
return {};
50+
51+
if (ctidValue > 0xFFFFFFFFFFFFFFFFULL ||
52+
(ctidValue & 0xF000000000000000ULL) != 0xC000000000000000ULL)
53+
return {};
54+
55+
uint32_t ledger_seq = (ctidValue >> 32) & 0xFFFFFFFUL;
56+
uint16_t txn_index = (ctidValue >> 16) & 0xFFFFU;
57+
uint16_t network_id = ctidValue & 0xFFFFU;
58+
return {{ledger_seq, txn_index, network_id}};
59+
}
60+
61+
// NOTE TO DEVELOPER:
62+
// you only need the two functions above, below are test cases, if
63+
// you want them.
64+
65+
int main() {
66+
std::cout << "Running test cases..." << std::endl;
67+
// Test case 1: Valid input values
68+
assert(encodeCTID(0xFFFFFFFUL, 0xFFFFU, 0xFFFFU) ==
69+
std::optional<std::string>("CFFFFFFFFFFFFFFF"));
70+
assert(encodeCTID(0, 0, 0) == std::optional<std::string>("C000000000000000"));
71+
assert(encodeCTID(1U, 2U, 3U) ==
72+
std::optional<std::string>("C000000100020003"));
73+
assert(encodeCTID(13249191UL, 12911U, 49221U) ==
74+
std::optional<std::string>("C0CA2AA7326FC045"));
75+
76+
// Test case 2: ledger_seq greater than 0xFFFFFFF
77+
assert(!encodeCTID(0x10000000UL, 0xFFFFU, 0xFFFFU));
78+
79+
// Test case 3: txn_index greater than 0xFFFF
80+
// this test case is impossible in c++ due to the type, left in for
81+
// completeness assert(!encodeCTID(0xFFFFFFF, 0x10000, 0xFFFF));
82+
83+
// Test case 4: network_id greater than 0xFFFF
84+
// this test case is impossible in c++ due to the type, left in for
85+
// completeness assert(!encodeCTID(0xFFFFFFFUL, 0xFFFFU, 0x10000U));
86+
87+
// Test case 5: Valid input values
88+
assert((decodeCTID("CFFFFFFFFFFFFFFF") ==
89+
std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
90+
std::make_tuple(0xFFFFFFFULL, 0xFFFFU, 0xFFFFU))));
91+
assert((decodeCTID("C000000000000000") ==
92+
std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
93+
std::make_tuple(0, 0, 0))));
94+
assert((decodeCTID("C000000100020003") ==
95+
std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
96+
std::make_tuple(1U, 2U, 3U))));
97+
assert((decodeCTID("C0CA2AA7326FC045") ==
98+
std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
99+
std::make_tuple(13249191UL, 12911U, 49221U))));
100+
101+
// Test case 6: ctid not a string or big int
102+
assert(!decodeCTID(0xCFF));
103+
104+
// Test case 7: ctid not a hexadecimal string
105+
assert(!decodeCTID("C003FFFFFFFFFFFG"));
106+
107+
// Test case 8: ctid not exactly 16 nibbles
108+
assert(!decodeCTID("C003FFFFFFFFFFF"));
109+
110+
// Test case 9: ctid too large to be a valid CTID value
111+
assert(!decodeCTID("CFFFFFFFFFFFFFFFF"));
112+
113+
// Test case 10: ctid doesn't start with a C nibble
114+
assert(!decodeCTID("FFFFFFFFFFFFFFFF"));
115+
116+
// Test case 11: Valid input values
117+
assert((decodeCTID(0xCFFFFFFFFFFFFFFFULL) ==
118+
std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
119+
std::make_tuple(0xFFFFFFFUL, 0xFFFFU, 0xFFFFU))));
120+
assert((decodeCTID(0xC000000000000000ULL) ==
121+
std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
122+
std::make_tuple(0, 0, 0))));
123+
assert((decodeCTID(0xC000000100020003ULL) ==
124+
std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
125+
std::make_tuple(1U, 2U, 3U))));
126+
assert((decodeCTID(0xC0CA2AA7326FC045ULL) ==
127+
std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
128+
std::make_tuple(13249191UL, 12911U, 49221U))));
129+
130+
// Test case 12: ctid not exactly 16 nibbles
131+
assert(!decodeCTID(0xC003FFFFFFFFFFF));
132+
133+
// Test case 13: ctid too large to be a valid CTID value
134+
// this test case is not possible in c++ because it would overflow the type,
135+
// left in for completeness assert(!decodeCTID(0xCFFFFFFFFFFFFFFFFULL));
136+
137+
// Test case 14: ctid doesn't start with a C nibble
138+
assert(!decodeCTID(0xFFFFFFFFFFFFFFFFULL));
139+
140+
std::cout << "Done!" << std::endl;
141+
return 0;
142+
}

0 commit comments

Comments
 (0)