github.com/stellar/stellar-etl@v1.0.1-0.20240312145900-4874b6bf2b89/internal/toid/main.go (about) 1 package toid 2 3 import ( 4 "errors" 5 "fmt" 6 ) 7 8 // 9 // ID represents the total order of Ledgers, Transactions and 10 // Operations. 11 // 12 // Operations within the stellar network have a total order, expressed by three 13 // pieces of information: the ledger sequence the operation was validated in, 14 // the order which the operation's containing transaction was applied in 15 // that ledger, and the index of the operation within that parent transaction. 16 // 17 // We express this order by packing those three pieces of information into a 18 // single signed 64-bit number (we used a signed number for SQL compatibility). 19 // 20 // The follow diagram shows this format: 21 // 22 // 0 1 2 3 23 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 24 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 25 // | Ledger Sequence Number | 26 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 27 // | Transaction Application Order | Op Index | 28 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 29 // 30 // By component: 31 // 32 // Ledger Sequence: 32-bits 33 // 34 // A complete ledger sequence number in which the operation was validated. 35 // 36 // Expressed in network byte order. 37 // 38 // Transaction Application Order: 20-bits 39 // 40 // The order that the transaction was applied within the ledger it was 41 // validated. Accommodates up to 1,048,575 transactions in a single ledger. 42 // 43 // Expressed in network byte order. 44 // 45 // Operation Index: 12-bits 46 // 47 // The index of the operation within its parent transaction. Accommodates up 48 // to 4095 operations per transaction. 49 // 50 // Expressed in network byte order. 51 // 52 // 53 // Note: API Clients should not be interpreting this value. We will use it 54 // as an opaque paging token that clients can parrot back to us after having read 55 // it within a resource to page from the represented position in time. 56 // 57 // Note: This does not uniquely identify an object. Given a ledger, it will 58 // share its id with its first transaction and the first operation of that 59 // transaction as well. Given that this ID is only meant for ordering within a 60 // single type of object, the sharing of ids across object types seems 61 // acceptable. 62 // 63 type ID struct { 64 LedgerSequence int32 65 TransactionOrder int32 66 OperationOrder int32 67 } 68 69 const ( 70 // LedgerMask is the bitmask to mask out ledger sequences in a 71 // TotalOrderID 72 LedgerMask = (1 << 32) - 1 73 // TransactionMask is the bitmask to mask out transaction indexes 74 TransactionMask = (1 << 20) - 1 75 // OperationMask is the bitmask to mask out operation indexes 76 OperationMask = (1 << 12) - 1 77 78 // LedgerShift is the number of bits to shift an int64 to target the 79 // ledger component 80 LedgerShift = 32 81 // TransactionShift is the number of bits to shift an int64 to 82 // target the transaction component 83 TransactionShift = 12 84 // OperationShift is the number of bits to shift an int64 to target 85 // the operation component 86 OperationShift = 0 87 ) 88 89 // AfterLedger returns a new toid that represents the ledger time _after_ any 90 // contents (e.g. transactions, operations) that occur within the specified 91 // ledger. 92 func AfterLedger(seq int32) *ID { 93 return New(seq, TransactionMask, OperationMask) 94 } 95 96 // LedgerRangeInclusive returns inclusive range representation between two 97 // ledgers inclusive. The second value points at the to+1 ledger so when using 98 // this value make sure < order is used. 99 func LedgerRangeInclusive(from, to int32) (int64, int64, error) { 100 if from > to { 101 return 0, 0, errors.New("Invalid range: from > to") 102 } 103 104 if from <= 0 || to <= 0 { 105 return 0, 0, errors.New("Invalid range: from or to negative") 106 } 107 108 var toidFrom, toidTo int64 109 if from == 1 { 110 toidFrom = 0 111 } else { 112 toidFrom = New(from, 0, 0).ToInt64() 113 } 114 115 toidTo = New(to+1, 0, 0).ToInt64() 116 117 return toidFrom, toidTo, nil 118 } 119 120 // IncOperationOrder increments the operation order, rolling over to the next 121 // ledger if overflow occurs. This allows queries to easily advance a cursor to 122 // the next operation. 123 func (id *ID) IncOperationOrder() { 124 id.OperationOrder++ 125 126 if id.OperationOrder > OperationMask { 127 id.OperationOrder = 0 128 id.LedgerSequence++ 129 } 130 } 131 132 // New creates a new total order ID 133 func New(ledger int32, tx int32, op int32) *ID { 134 return &ID{ 135 LedgerSequence: ledger, 136 TransactionOrder: tx, 137 OperationOrder: op, 138 } 139 } 140 141 // ToInt64 converts this struct back into an int64 142 func (id ID) ToInt64() (result int64) { 143 144 if id.LedgerSequence < 0 { 145 panic("invalid ledger sequence") 146 } 147 148 if id.TransactionOrder > TransactionMask { 149 panic("transaction order overflow") 150 } 151 152 if id.OperationOrder > OperationMask { 153 panic("operation order overflow") 154 } 155 156 result = result | ((int64(id.LedgerSequence) & LedgerMask) << LedgerShift) 157 result = result | ((int64(id.TransactionOrder) & TransactionMask) << TransactionShift) 158 result = result | ((int64(id.OperationOrder) & OperationMask) << OperationShift) 159 return 160 } 161 162 // String returns a string representation of this id 163 func (id ID) String() string { 164 return fmt.Sprintf("%d", id.ToInt64()) 165 } 166 167 // Parse parses an int64 into a TotalOrderID struct 168 func Parse(id int64) (result ID) { 169 result.LedgerSequence = int32((id >> LedgerShift) & LedgerMask) 170 result.TransactionOrder = int32((id >> TransactionShift) & TransactionMask) 171 result.OperationOrder = int32((id >> OperationShift) & OperationMask) 172 173 return 174 }