diff --git a/engine/execution/state/bootstrap/bootstrap_test.go b/engine/execution/state/bootstrap/bootstrap_test.go index 4969baa9208..7ef8bc35d84 100644 --- a/engine/execution/state/bootstrap/bootstrap_test.go +++ b/engine/execution/state/bootstrap/bootstrap_test.go @@ -53,7 +53,7 @@ func TestBootstrapLedger(t *testing.T) { } func TestBootstrapLedger_ZeroTokenSupply(t *testing.T) { - expectedStateCommitmentBytes, _ := hex.DecodeString("4c5b099dae68a858dd8da0944e6fad6f6d1b943b83c5acb39aeee659e165adb5") + expectedStateCommitmentBytes, _ := hex.DecodeString("8d9d52a66a832898f6f2416b703759b7ecd1eb390db6d5e727c2daeec001ffc6") expectedStateCommitment, err := flow.ToStateCommitment(expectedStateCommitmentBytes) require.NoError(t, err) diff --git a/fvm/environment/uuids.go b/fvm/environment/uuids.go index 2612ac6d1f8..6775d067ac9 100644 --- a/fvm/environment/uuids.go +++ b/fvm/environment/uuids.go @@ -14,9 +14,20 @@ import ( "github.com/onflow/flow-go/utils/slices" ) +// uuid is partitioned with 3rd byte for compatibility reasons. +// (database types and Javascript safe integer limits) +// +// counter(C) is 7 bytes, paritition(P) is 1 byte +// uuid is assembled by first reading the counter from the register value of the partitioned register, +// and then left shifting the 6th and 7th byte, and placing the partition byte at 6th byte: +// C7 C6 P C5 C4 C3 C2 C1 +// +// Until resource ids start filling the bits above the 48th one, dapps will have enough time +// to switch to a larger data type. + const ( - // The max value for any is uuid partition is MaxUint56, since the top - // 8 bits in the uuid are used for partitioning. + // The max value for any is uuid partition is MaxUint56, since one byte + // in the uuid is used for partitioning. MaxUint56 = (uint64(1) << 56) - 1 // Start warning when there's only a single high bit left. This should give @@ -108,8 +119,8 @@ func NewUUIDGenerator( } } -// getUint64 reads the uint64 value from the partitioned uuid register. -func (generator *uUIDGenerator) getUint64() (uint64, error) { +// getCounter reads the uint64 value from the partitioned uuid register. +func (generator *uUIDGenerator) getCounter() (uint64, error) { stateBytes, err := generator.txnState.Get(generator.registerId) if err != nil { return 0, fmt.Errorf( @@ -122,8 +133,8 @@ func (generator *uUIDGenerator) getUint64() (uint64, error) { return binary.BigEndian.Uint64(bytes), nil } -// setUint56 sets a new uint56 value into the partitioned uuid register. -func (generator *uUIDGenerator) setUint56( +// setCounter sets a new uint56 value into the partitioned uuid register. +func (generator *uUIDGenerator) setCounter( value uint64, ) error { if value > Uint56OverflowWarningThreshold { @@ -184,17 +195,20 @@ func (generator *uUIDGenerator) GenerateUUID() (uint64, error) { generator.maybeInitializePartition() - value, err := generator.getUint64() + counter, err := generator.getCounter() if err != nil { return 0, fmt.Errorf("cannot generate UUID: %w", err) } - err = generator.setUint56(value + 1) + err = generator.setCounter(counter + 1) if err != nil { return 0, fmt.Errorf("cannot generate UUID: %w", err) } // Since the partition counter only goes up to MaxUint56, we can use the - // upper 8 bits to represent which partition was used. - return (uint64(generator.partition) << 56) | value, nil + // assemble a UUID value with the partition (P) and the counter (C). + // Note: partition (P) is represented by the 6th byte + // (C7 C6) | P | (C5 C4 C3 C2 C1) + return ((counter & 0xFF_FF00_0000_0000) << 8) | (uint64(generator.partition) << 40) | (counter & 0xFF_FFFF_FFFF), nil + } diff --git a/fvm/environment/uuids_test.go b/fvm/environment/uuids_test.go index b83bd5b1821..a9852bff6a9 100644 --- a/fvm/environment/uuids_test.go +++ b/fvm/environment/uuids_test.go @@ -115,8 +115,9 @@ func testUUIDGenerator(t *testing.T, blockHeader *flow.Header, txnIndex uint32) generator.maybeInitializePartition() partition := generator.partition - partitionMinValue := uint64(partition) << 56 - maxUint56 := uint64(72057594037927935) // (1 << 56) - 1 + partitionMinValue := uint64(partition) << 40 + maxUint56 := uint64(0xFFFFFFFFFFFFFF) + maxUint56Split := uint64(0xFFFF00FFFFFFFFFF) t.Run( fmt.Sprintf("basic get and set uint (partition: %d)", partition), @@ -131,11 +132,11 @@ func testUUIDGenerator(t *testing.T, blockHeader *flow.Header, txnIndex uint32) txnIndex) uuidsA.maybeInitializePartition() - uuid, err := uuidsA.getUint64() // start from zero + uuid, err := uuidsA.getCounter() // start from zero require.NoError(t, err) require.Equal(t, uint64(0), uuid) - err = uuidsA.setUint56(5) + err = uuidsA.setCounter(5) require.NoError(t, err) // create new UUIDs instance @@ -148,7 +149,7 @@ func testUUIDGenerator(t *testing.T, blockHeader *flow.Header, txnIndex uint32) txnIndex) uuidsB.maybeInitializePartition() - uuid, err = uuidsB.getUint64() // should read saved value + uuid, err = uuidsB.getCounter() // should read saved value require.NoError(t, err) require.Equal(t, uint64(5), uuid) @@ -204,7 +205,7 @@ func testUUIDGenerator(t *testing.T, blockHeader *flow.Header, txnIndex uint32) }) t.Run( - fmt.Sprintf("setUint56 overflows (partition: %d)", partition), + fmt.Sprintf("setCounter overflows (partition: %d)", partition), func(t *testing.T) { txnState := state.NewTransactionState(nil, state.DefaultParameters()) uuids := NewUUIDGenerator( @@ -216,17 +217,17 @@ func testUUIDGenerator(t *testing.T, blockHeader *flow.Header, txnIndex uint32) txnIndex) uuids.maybeInitializePartition() - err := uuids.setUint56(maxUint56) + err := uuids.setCounter(maxUint56) require.NoError(t, err) - value, err := uuids.getUint64() + value, err := uuids.getCounter() require.NoError(t, err) require.Equal(t, value, maxUint56) - err = uuids.setUint56(maxUint56 + 1) + err = uuids.setCounter(maxUint56 + 1) require.ErrorContains(t, err, "overflowed") - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, maxUint56) }) @@ -244,22 +245,22 @@ func testUUIDGenerator(t *testing.T, blockHeader *flow.Header, txnIndex uint32) txnIndex) uuids.maybeInitializePartition() - err := uuids.setUint56(maxUint56 - 1) + err := uuids.setCounter(maxUint56 - 1) require.NoError(t, err) value, err := uuids.GenerateUUID() require.NoError(t, err) - require.Equal(t, value, partitionMinValue+maxUint56-1) - require.Equal(t, value, partitionMinValue|(maxUint56-1)) + require.Equal(t, value, partitionMinValue+maxUint56Split-1) + require.Equal(t, value, partitionMinValue|(maxUint56Split-1)) - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, maxUint56) _, err = uuids.GenerateUUID() require.ErrorContains(t, err, "overflowed") - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, maxUint56) }) @@ -282,33 +283,33 @@ func TestUUIDGeneratorHardcodedPartitionIdGeneration(t *testing.T) { value, err := uuids.GenerateUUID() require.NoError(t, err) - require.Equal(t, value, uint64(0xde00000000000000)) + require.Equal(t, value, uint64(0x0000de0000000000)) - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, uint64(1)) value, err = uuids.GenerateUUID() require.NoError(t, err) - require.Equal(t, value, uint64(0xde00000000000001)) + require.Equal(t, value, uint64(0x0000de0000000001)) - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, uint64(2)) value, err = uuids.GenerateUUID() require.NoError(t, err) - require.Equal(t, value, uint64(0xde00000000000002)) + require.Equal(t, value, uint64(0x0000de0000000002)) - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, uint64(3)) // pretend we increamented the counter up to cafBad cafBad := uint64(0x1c2a3f4b5a6d70) - decafBad := uint64(0xde1c2a3f4b5a6d70) + decafBad := uint64(0x1c2ade3f4b5a6d70) - err = uuids.setUint56(cafBad) + err = uuids.setCounter(cafBad) require.NoError(t, err) for i := 0; i < 5; i++ { @@ -317,35 +318,71 @@ func TestUUIDGeneratorHardcodedPartitionIdGeneration(t *testing.T) { require.Equal(t, value, decafBad+uint64(i)) } - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, cafBad+uint64(5)) // pretend we increamented the counter up to overflow - 2 maxUint56Minus2 := uint64(0xfffffffffffffd) - err = uuids.setUint56(maxUint56Minus2) + err = uuids.setCounter(maxUint56Minus2) require.NoError(t, err) value, err = uuids.GenerateUUID() require.NoError(t, err) - require.Equal(t, value, uint64(0xdefffffffffffffd)) + require.Equal(t, value, uint64(0xffffdefffffffffd)) - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, maxUint56Minus2+1) value, err = uuids.GenerateUUID() require.NoError(t, err) - require.Equal(t, value, uint64(0xdefffffffffffffe)) + require.Equal(t, value, uint64(0xffffdefffffffffe)) - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, maxUint56Minus2+2) _, err = uuids.GenerateUUID() require.ErrorContains(t, err, "overflowed") - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, maxUint56Minus2+2) } + +func TestContinuati(t *testing.T) { + txnState := state.NewTransactionState(nil, state.DefaultParameters()) + uuids := NewUUIDGenerator( + tracing.NewTracerSpan(), + zerolog.Nop(), + NewMeter(txnState), + txnState, + nil, + 0) + + // Hardcoded the partition to check for exact bytes + uuids.initialized = true + uuids.partition = 0x01 + uuids.registerId = flow.UUIDRegisterID(0x01) + + value, err := uuids.GenerateUUID() + require.NoError(t, err) + require.Equal(t, value, uint64(0x0000010000000000)) + + err = uuids.setCounter(0xFFFFFFFFFF) + require.NoError(t, err) + + value, err = uuids.GenerateUUID() + require.NoError(t, err) + require.Equal(t, value, uint64(0x000001FFFFFFFFFF)) + + value, err = uuids.GenerateUUID() + require.NoError(t, err) + require.Equal(t, value, uint64(0x0001010000000000)) + + value, err = uuids.GenerateUUID() + require.NoError(t, err) + require.Equal(t, value, uint64(0x0001010000000001)) + +} diff --git a/utils/unittest/execution_state.go b/utils/unittest/execution_state.go index 8acb8d2a09e..5e0b3d3620f 100644 --- a/utils/unittest/execution_state.go +++ b/utils/unittest/execution_state.go @@ -23,7 +23,7 @@ const ServiceAccountPrivateKeySignAlgo = crypto.ECDSAP256 const ServiceAccountPrivateKeyHashAlgo = hash.SHA2_256 // Pre-calculated state commitment with root account with the above private key -const GenesisStateCommitmentHex = "6c394b798bcabfdbdcfddb98f33a818de81efc160d99a4697db57b7b099d1ab1" +const GenesisStateCommitmentHex = "e4674bba14f59af783bbf70b2a43c1696a7d9888eeaca86cf74b033580fe1c23" var GenesisStateCommitment flow.StateCommitment @@ -87,10 +87,10 @@ func genesisCommitHexByChainID(chainID flow.ChainID) string { return GenesisStateCommitmentHex } if chainID == flow.Testnet { - return "5dc11f195653540c1cc3c2fd42ac9d9dca415be6080276eebd1e2fa5dba07a1c" + return "bfe964655cf13711b93dbaf156aaebbc24a607beed69dd36d71b593832b5129c" } if chainID == flow.Sandboxnet { return "e1c08b17f9e5896f03fe28dd37ca396c19b26628161506924fbf785834646ea1" } - return "0d5dcd6cd42cbc41c2aae1a4a6ee950758cc2f75f21ad0ccf84b9e9fa35305ff" + return "a56a2750708bc981eb949a3b02a41061dc6b7e6bfa9f31a19a48f560f616bed3" }