diff --git a/go.mod b/go.mod index 783b09ce8..e28ecf8f8 100644 --- a/go.mod +++ b/go.mod @@ -143,7 +143,7 @@ require ( github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/s2a-go v0.1.8 // indirect - github.com/google/uuid v1.6.0 // indirect + github.com/google/uuid v1.6.0 github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect diff --git a/pkg/utils/uuid.go b/pkg/utils/uuid.go index 2d1d3f82b..1eca70544 100644 --- a/pkg/utils/uuid.go +++ b/pkg/utils/uuid.go @@ -7,66 +7,28 @@ SPDX-License-Identifier: Apache-2.0 package utils import ( - "crypto/rand" - "fmt" - "io" + "github.com/google/uuid" ) -var randChar = &poolRandReader{ - randReader: rand.Reader, - pool: []byte(`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`), -} - -type poolRandReader struct { - randReader io.Reader - pool []byte -} +func init() { + // we enable rand pool for better performance. + // note that the pooled random bytes are stored in heap; + // for more details see uuid.EnableRandPool docs. -func (r *poolRandReader) Read(p []byte) (int, error) { - n, err := r.randReader.Read(p) - if err != nil { - return n, err - } - - l := uint8(len(r.pool)) - for i := range p { - p[i] = r.pool[p[i]%l] - } - return n, nil + // BenchmarkUUID/google_uuid_(pooled) + // BenchmarkUUID/google_uuid_(pooled)-10 18897972 63.14 ns/op 48 B/op 1 allocs/op + // BenchmarkUUID/google_uuid_(non-pooled) + // BenchmarkUUID/google_uuid_(non-pooled)-10 3658857 326.3 ns/op 64 B/op 2 allocs/op + uuid.EnableRandPool() } -// GenerateBytesUUID returns a UUID based on RFC 4122 returning the generated bytes +// GenerateBytesUUID creates a new random UUID and returns it as []byte func GenerateBytesUUID() []byte { - uuid := make([]byte, 16) - _, err := io.ReadFull(rand.Reader, uuid) - if err != nil { - panic(fmt.Sprintf("Error generating UUID: %s", err)) - } - - // variant bits; see section 4.1.1 - uuid[8] = uuid[8]&^0xc0 | 0x80 - - // version 4 (pseudo-random); see section 4.1.3 - uuid[6] = uuid[6]&^0xf0 | 0x40 - - return uuid + u := uuid.New() + return u[:] } -// GenerateUUID returns a UUID based on RFC 4122 +// GenerateUUID creates a new random UUID and returns it as a string func GenerateUUID() string { - uuid := GenerateBytesUUID() - return idBytesToStr(uuid) -} - -func idBytesToStr(id []byte) string { - return fmt.Sprintf("%x-%x-%x-%x-%x", id[0:4], id[4:6], id[6:8], id[8:10], id[10:]) -} - -// GenerateUUIDOnlyLetters returns a UUID without digits -func GenerateUUIDOnlyLetters() string { - uuid := make([]byte, 16) - if _, err := io.ReadFull(randChar, uuid); err != nil { - panic(err) - } - return string(uuid) + return uuid.NewString() } diff --git a/pkg/utils/uuid_test.go b/pkg/utils/uuid_test.go index 36b5d921e..3f7f24331 100644 --- a/pkg/utils/uuid_test.go +++ b/pkg/utils/uuid_test.go @@ -7,11 +7,85 @@ SPDX-License-Identifier: Apache-2.0 package utils import ( + "crypto/rand" + "fmt" + "io" "testing" - "github.com/stretchr/testify/assert" + "github.com/google/uuid" ) -func TestUUIDLettersOnly(t *testing.T) { - assert.Regexp(t, "^[a-zA-Z]{16}$", GenerateUUIDOnlyLetters()) +func BenchmarkUUID(b *testing.B) { + // generateUUIDv1 is our reference impl for rand UUID based on previous version of this code + oldGenerateUUID := func() string { + uuid := make([]byte, 16) + + _, err := io.ReadFull(rand.Reader, uuid[:]) + if err != nil { + panic(fmt.Sprintf("Error generating UUID: %s", err)) + } + + // variant bits; see section 4.1.1 + uuid[8] = uuid[8]&^0xc0 | 0x80 + + // version 4 (pseudo-random); see section 4.1.3 + uuid[6] = uuid[6]&^0xf0 | 0x40 + + id := uuid + + return fmt.Sprintf("%x-%x-%x-%x-%x", id[0:4], id[4:6], id[6:8], id[8:10], id[10:]) + } + + b.Run("oldGenerateUUID", func(b *testing.B) { + report(b) + for b.Loop() { + _ = oldGenerateUUID() + } + }) + + b.Run("GenerateBytesUUID", func(b *testing.B) { + report(b) + for b.Loop() { + _ = GenerateBytesUUID() + } + }) + + b.Run("GenerateUUID", func(b *testing.B) { + report(b) + for b.Loop() { + _ = GenerateUUID() + } + }) + + b.Run("GenerateUUID (non-pooled)", func(b *testing.B) { + report(b) + uuid.DisableRandPool() + for b.Loop() { + _ = GenerateUUID() + } + }) + + b.Run("GenerateUUID parallel", func(b *testing.B) { + report(b) + uuid.EnableRandPool() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = GenerateUUID() + } + }) + }) + + b.Run("GenerateUUID parallel (non-pooled)", func(b *testing.B) { + report(b) + uuid.DisableRandPool() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = GenerateUUID() + } + }) + }) +} + +func report(b *testing.B) { + b.ReportAllocs() } diff --git a/platform/fabric/services/storage/vault/vaultstore_test.go b/platform/fabric/services/storage/vault/vaultstore_test.go index 8a209abef..3b476911a 100644 --- a/platform/fabric/services/storage/vault/vaultstore_test.go +++ b/platform/fabric/services/storage/vault/vaultstore_test.go @@ -10,7 +10,6 @@ import ( "context" "testing" - "github.com/hyperledger-labs/fabric-smart-client/pkg/utils" "github.com/hyperledger-labs/fabric-smart-client/platform/common/driver" "github.com/hyperledger-labs/fabric-smart-client/platform/common/utils/collections" q "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/storage/driver/sql/query" @@ -265,9 +264,12 @@ func testPagination(store driver.VaultStore) { func TestPaginationStoreMem(t *testing.T) { RegisterTestingT(t) - db, err := OpenMemoryVault(utils.GenerateUUIDOnlyLetters()) + db, err := OpenMemoryVault("testdb") assert.NoError(t, err) assert.NotNil(t, db) + t.Cleanup(func() { + _ = db.Close() + }) testPagination(db) } @@ -277,6 +279,9 @@ func TestPaginationStoreSqlite(t *testing.T) { db, err := OpenSqliteVault("testdb", t.TempDir()) assert.NoError(t, err) assert.NotNil(t, db) + t.Cleanup(func() { + _ = db.Close() + }) testPagination(db) } @@ -286,16 +291,19 @@ func TestPaginationStoreSPostgres(t *testing.T) { db, terminate, err := OpenPostgresVault("testdb") assert.NoError(t, err) assert.NotNil(t, db) - defer terminate() + t.Cleanup(terminate) testPagination(db) } func TestVaultStoreMem(t *testing.T) { RegisterTestingT(t) - db, err := OpenMemoryVault(utils.GenerateUUIDOnlyLetters()) + db, err := OpenMemoryVault("testdb") assert.NoError(t, err) assert.NotNil(t, db) + t.Cleanup(func() { + _ = db.Close() + }) testVaultStore(t, db) testOneMore(t, db) @@ -306,8 +314,10 @@ func TestVaultStoreSqlite(t *testing.T) { db, err := OpenSqliteVault("testdb", t.TempDir()) assert.NoError(t, err) assert.NotNil(t, db) + t.Cleanup(func() { + _ = db.Close() + }) - assert.NotNil(t, db) testVaultStore(t, db) testOneMore(t, db) } @@ -317,13 +327,15 @@ func TestVaultStorePostgres(t *testing.T) { db, terminate, err := OpenPostgresVault("testdb") assert.NoError(t, err) assert.NotNil(t, db) - defer terminate() + t.Cleanup(terminate) testVaultStore(t, db) testOneMore(t, db) } func testOneMore(t *testing.T, store driver.VaultStore) { + t.Helper() + err := store.SetStatuses(context.Background(), driver.TxStatusCode(valid), "", "txid3") assert.NoError(t, err) @@ -383,6 +395,8 @@ func fetchAll(store driver.VaultStore) ([]driver.TxID, error) { } func testVaultStore(t *testing.T, store driver.VaultStore) { + t.Helper() + txids, err := fetchAll(store) assert.NoError(t, err) assert.Empty(t, txids)