Skip to content

Commit

Permalink
Merge pull request #4914 from bluesign/uuid_partition_change
Browse files Browse the repository at this point in the history
UUID partition change
  • Loading branch information
janezpodhostnik authored Jan 24, 2024
2 parents 7f0980e + a3b336c commit f0b7f2a
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 44 deletions.
2 changes: 1 addition & 1 deletion engine/execution/state/bootstrap/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
34 changes: 24 additions & 10 deletions fvm/environment/uuids.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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 {
Expand Down Expand Up @@ -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

}
97 changes: 67 additions & 30 deletions fvm/environment/uuids_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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(
Expand All @@ -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)
})
Expand All @@ -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)
})
Expand All @@ -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++ {
Expand All @@ -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))

}
6 changes: 3 additions & 3 deletions utils/unittest/execution_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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"
}

0 comments on commit f0b7f2a

Please sign in to comment.