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  }