github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/environment/uuids.go (about) 1 package environment 2 3 import ( 4 "crypto/sha256" 5 "encoding/binary" 6 "fmt" 7 8 "github.com/rs/zerolog" 9 10 "github.com/onflow/flow-go/fvm/storage/state" 11 "github.com/onflow/flow-go/fvm/tracing" 12 "github.com/onflow/flow-go/model/flow" 13 "github.com/onflow/flow-go/module/trace" 14 "github.com/onflow/flow-go/utils/slices" 15 ) 16 17 // uuid is partitioned with 3rd byte for compatibility reasons. 18 // (database types and Javascript safe integer limits) 19 // 20 // counter(C) is 7 bytes, paritition(P) is 1 byte 21 // uuid is assembled by first reading the counter from the register value of the partitioned register, 22 // and then left shifting the 6th and 7th byte, and placing the partition byte at 6th byte: 23 // C7 C6 P C5 C4 C3 C2 C1 24 // 25 // Until resource ids start filling the bits above the 48th one, dapps will have enough time 26 // to switch to a larger data type. 27 28 const ( 29 // The max value for any is uuid partition is MaxUint56, since one byte 30 // in the uuid is used for partitioning. 31 MaxUint56 = (uint64(1) << 56) - 1 32 33 // Start warning when there's only a single high bit left. This should give 34 // us plenty of time to migrate to larger counters. 35 Uint56OverflowWarningThreshold = (uint64(1) << 55) - 1 36 ) 37 38 type UUIDGenerator interface { 39 GenerateUUID() (uint64, error) 40 } 41 42 type ParseRestrictedUUIDGenerator struct { 43 txnState state.NestedTransactionPreparer 44 impl UUIDGenerator 45 } 46 47 func NewParseRestrictedUUIDGenerator( 48 txnState state.NestedTransactionPreparer, 49 impl UUIDGenerator, 50 ) UUIDGenerator { 51 return ParseRestrictedUUIDGenerator{ 52 txnState: txnState, 53 impl: impl, 54 } 55 } 56 57 func (generator ParseRestrictedUUIDGenerator) GenerateUUID() (uint64, error) { 58 return parseRestrict1Ret( 59 generator.txnState, 60 trace.FVMEnvGenerateUUID, 61 generator.impl.GenerateUUID) 62 } 63 64 type uUIDGenerator struct { 65 tracer tracing.TracerSpan 66 log zerolog.Logger 67 meter Meter 68 69 txnState state.NestedTransactionPreparer 70 71 blockHeader *flow.Header 72 txnIndex uint32 73 74 initialized bool 75 partition byte 76 registerId flow.RegisterID 77 } 78 79 func uuidPartition(blockId flow.Identifier, txnIndex uint32) byte { 80 // Partitioning by txnIndex ensures temporally neighboring transactions do 81 // not share registers / conflict with each other. 82 // 83 // Since all blocks will have a transaction at txnIndex 0 but not 84 // necessarily a transaction at txnIndex 255, if we assign partition based 85 // only on txnIndex, partition 0's counter (and other low-valued 86 // partitions' counters) will fill up much more quickly than high-valued 87 // partitions' counters. Therefore, a deterministically random offset is 88 // used to ensure the partitioned counters are roughly balanced. Any byte 89 // in the sha hash is sufficiently random/uniform for this purpose (Note that 90 // block Id is already a sha hash, but its hash implementation may change 91 // underneath us). 92 // 93 // Note that since partition 0 reuses the legacy counter, its counter is 94 // much further ahead than the other partitions. If partition 0's counter 95 // is in danager of overflowing, use variants of "the power of two random 96 // choices" to shed load to other counters. 97 // 98 // The explicit mod is not really needed, but is there for completeness. 99 partitionOffset := sha256.Sum256(blockId[:])[0] 100 return byte((uint32(partitionOffset) + txnIndex) % 256) 101 } 102 103 func NewUUIDGenerator( 104 tracer tracing.TracerSpan, 105 log zerolog.Logger, 106 meter Meter, 107 txnState state.NestedTransactionPreparer, 108 blockHeader *flow.Header, 109 txnIndex uint32, 110 ) *uUIDGenerator { 111 return &uUIDGenerator{ 112 tracer: tracer, 113 log: log, 114 meter: meter, 115 txnState: txnState, 116 blockHeader: blockHeader, 117 txnIndex: txnIndex, 118 initialized: false, 119 } 120 } 121 122 // getCounter reads the uint64 value from the partitioned uuid register. 123 func (generator *uUIDGenerator) getCounter() (uint64, error) { 124 stateBytes, err := generator.txnState.Get(generator.registerId) 125 if err != nil { 126 return 0, fmt.Errorf( 127 "cannot get uuid partition %d byte from state: %w", 128 generator.partition, 129 err) 130 } 131 bytes := slices.EnsureByteSliceSize(stateBytes, 8) 132 133 return binary.BigEndian.Uint64(bytes), nil 134 } 135 136 // setCounter sets a new uint56 value into the partitioned uuid register. 137 func (generator *uUIDGenerator) setCounter( 138 value uint64, 139 ) error { 140 if value > Uint56OverflowWarningThreshold { 141 if value > MaxUint56 { 142 return fmt.Errorf( 143 "uuid partition %d overflowed", 144 generator.partition) 145 } 146 147 generator.log.Warn(). 148 Int("partition", int(generator.partition)). 149 Uint64("value", value). 150 Msg("uuid partition is running out of bits") 151 } 152 153 bytes := make([]byte, 8) 154 binary.BigEndian.PutUint64(bytes, value) 155 err := generator.txnState.Set(generator.registerId, bytes) 156 if err != nil { 157 return fmt.Errorf( 158 "cannot set uuid %d byte to state: %w", 159 generator.partition, 160 err) 161 } 162 return nil 163 } 164 165 func (generator *uUIDGenerator) maybeInitializePartition() { 166 if generator.initialized { 167 return 168 } 169 generator.initialized = true 170 171 // NOTE: block header is not set for scripts. We'll just use partition 0 in 172 // this case. 173 if generator.blockHeader == nil { 174 generator.partition = 0 175 } else { 176 generator.partition = uuidPartition( 177 generator.blockHeader.ID(), 178 generator.txnIndex) 179 } 180 181 generator.registerId = flow.UUIDRegisterID(generator.partition) 182 } 183 184 // GenerateUUID generates a new uuid and persist the data changes into state 185 func (generator *uUIDGenerator) GenerateUUID() (uint64, error) { 186 defer generator.tracer.StartExtensiveTracingChildSpan( 187 trace.FVMEnvGenerateUUID).End() 188 189 err := generator.meter.MeterComputation( 190 ComputationKindGenerateUUID, 191 1) 192 if err != nil { 193 return 0, fmt.Errorf("generate uuid failed: %w", err) 194 } 195 196 generator.maybeInitializePartition() 197 198 counter, err := generator.getCounter() 199 if err != nil { 200 return 0, fmt.Errorf("cannot generate UUID: %w", err) 201 } 202 203 err = generator.setCounter(counter + 1) 204 if err != nil { 205 return 0, fmt.Errorf("cannot generate UUID: %w", err) 206 } 207 208 // Since the partition counter only goes up to MaxUint56, we can use the 209 // assemble a UUID value with the partition (P) and the counter (C). 210 // Note: partition (P) is represented by the 6th byte 211 // (C7 C6) | P | (C5 C4 C3 C2 C1) 212 return ((counter & 0xFF_FF00_0000_0000) << 8) | (uint64(generator.partition) << 40) | (counter & 0xFF_FFFF_FFFF), nil 213 214 }