snowflake.gno
3.77 Kb · 132 lines
1// Package snowflake provides utility functions for generating unique identifiers
2// (Snowflake IDs) based on a custom implementation inspired by the Snowflake algorithm (https://pkg.go.dev/github.com/godruoyi/go-snowflake#section-sourcefiles).
3// The Snowflake IDs generated by this package ensure uniqueness and are suitable for
4// distributed systems. The package offers a mechanism to convert these IDs
5// to standard SnowflakeID format strings, making them compatible with systems
6// requiring UUIDs.
7// Add import "gno.land/p/demo/entropy" to use the entropy package. For of the pseudo-random number generator.
8// TimestampLength is 42-bit.
9// 42 it's the maximum value that can be returned "2^42", It's represent 139 years (2^42 secondes)
10// SequenBits allows for 4096 unique IDs to be generated per millisecond.
11
12package snowflake
13
14import (
15 "chain/runtime"
16 "encoding/binary"
17 "encoding/hex"
18 "time"
19
20 "gno.land/p/demo/entropy"
21 "gno.land/p/nt/ufmt"
22)
23
24const (
25 TimestampBits uint8 = 42
26 MachineIDBits uint64 = 10
27 SequenceBits uint64 = 12
28 MaxSequence uint16 = 1<<SequenceBits - 1
29 MaxTimestamp uint64 = 1<<TimestampBits - 1
30 MaxMachineID uint64 = 1<<MachineIDBits - 1
31
32 machineIDMoveLength = SequenceBits
33 timestampMoveLength = MachineIDBits + SequenceBits
34
35 startTime uint64 = 1226354400000 // 10 Nov 2008 23:00:00 UTC in milliseconds (Snowflake epoch)
36)
37
38// SnowflakeID struct for generating unique Snowflake-based identifiers.
39type SnowflakeID struct {
40 machineID uint64
41 sequence uint16
42 startTime uint64
43 lastTimestamp uint64
44 entropy *entropy.Instance
45}
46
47func NewSnowflakeID() *SnowflakeID {
48 s := &SnowflakeID{
49 startTime: startTime,
50 entropy: entropy.New(),
51 }
52 s.SetMachineID()
53 return s
54}
55
56// GenerateID generates a unique identifier based on the Snowflake algorithm.
57// It combines the current timestamp, machine ID, and a sequence number to ensure uniqueness
58// across distributed systems.
59func (s *SnowflakeID) GenerateID() uint64 {
60 current := s.getTimenow()
61
62 if current == s.lastTimestamp {
63 s.incrementSequence()
64 } else {
65 s.sequence = 0
66 s.lastTimestamp = current
67 }
68
69 elapsedTime := uint64(current-s.startTime) % MaxTimestamp
70
71 id := (uint64(elapsedTime) << timestampMoveLength) |
72 (uint64(s.machineID) << machineIDMoveLength) |
73 uint64(s.incrementSequence())
74 return id
75}
76
77func (s *SnowflakeID) incrementSequence() uint16 {
78 random := uint16(s.entropy.Value())
79 s.sequence = (s.sequence + 1 + random) & MaxSequence
80 return s.sequence
81}
82
83func (s *SnowflakeID) getTimenow() uint64 {
84 return uint64(time.Now().UnixMilli())
85}
86
87// SetMachineID sets the machine ID based on the caller’s address.
88func (s *SnowflakeID) SetMachineID() {
89 caller := runtime.PreviousRealm().Address() // Retrieve the caller’s address
90 machineID := uint64(0)
91 for _, c := range caller.String() {
92 machineID += uint64(c)
93 }
94 machineID %= MaxMachineID
95 s.machineID = machineID
96}
97
98func SnowflakeIDToUUIDString(id uint64) string {
99 bytes := make([]byte, 16)
100
101 // Copy transformed ID into the second half of the array
102 copy(bytes[8:], uint64ToBytes(id))
103
104 // Use bits from the Snowflake ID to generate the first half of the SnowflakeID
105 bytes[0] = byte(id >> 60)
106 bytes[1] = byte(id >> 52)
107 bytes[2] = byte(id >> 44)
108 bytes[3] = byte(id >> 36)
109 bytes[4] = byte(id >> 28)
110 bytes[5] = byte(id >> 20)
111 bytes[6] = byte(id >> 12)
112 bytes[7] = byte(id >> 4)
113
114 // set the version and variant bits according to SnowflakeID specification.
115 bytes[6] = (bytes[6] & 0x0f) | 0x40 // version 4 (random)
116 bytes[8] = (bytes[8] & 0x3f) | 0x80 // variant 1 (RFC 4122)
117
118 hexStr := hex.EncodeToString(bytes)
119 return ufmt.Sprintf("%s-%s-%s-%s-%s",
120 hexStr[0:8],
121 hexStr[8:12],
122 hexStr[12:16],
123 hexStr[16:20],
124 hexStr[20:],
125 )
126}
127
128func uint64ToBytes(i uint64) []byte {
129 buf := make([]byte, 8)
130 binary.BigEndian.PutUint64(buf, i)
131 return buf
132}