From 4ed7bfe69b16cb37b42f8d5a7fdec5eb1cecba2a Mon Sep 17 00:00:00 2001 From: Jonathan Harvey-Buschel Date: Mon, 16 Oct 2023 21:30:05 -0400 Subject: [PATCH] tapdb: store namespace with universe sync config In this commit, we fix an issue with universe sync config storage. With the old table schema, multiples entries could exist for the same Universe because the UNIQUE constraint did not function as intended. This was caused by NULL entries being treated as unique values vs. one value. The new table has one entry per Universe ID, which fixes this. --- .../000009_universe_configs.down.sql | 1 + .../migrations/000009_universe_configs.up.sql | 35 +++++++++ tapdb/sqlc/models.go | 1 + tapdb/sqlc/queries/universe.sql | 14 ++-- tapdb/sqlc/universe.sql.go | 17 +++-- tapdb/universe_federation.go | 21 +++--- tapdb/universe_federation_test.go | 75 ++++++++++++++++++- 7 files changed, 136 insertions(+), 28 deletions(-) create mode 100644 tapdb/sqlc/migrations/000009_universe_configs.down.sql create mode 100644 tapdb/sqlc/migrations/000009_universe_configs.up.sql diff --git a/tapdb/sqlc/migrations/000009_universe_configs.down.sql b/tapdb/sqlc/migrations/000009_universe_configs.down.sql new file mode 100644 index 000000000..4c405332d --- /dev/null +++ b/tapdb/sqlc/migrations/000009_universe_configs.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS federation_uni_sync_config; \ No newline at end of file diff --git a/tapdb/sqlc/migrations/000009_universe_configs.up.sql b/tapdb/sqlc/migrations/000009_universe_configs.up.sql new file mode 100644 index 000000000..aadee52b3 --- /dev/null +++ b/tapdb/sqlc/migrations/000009_universe_configs.up.sql @@ -0,0 +1,35 @@ +DROP TABLE IF EXISTS federation_uni_sync_config; + +-- This table contains universe (asset/asset group) specific federation sync +-- configuration. +CREATE TABLE IF NOT EXISTS federation_uni_sync_config ( + -- namespace is the string representation of the universe identifier, and + -- ensures that there are no duplicate configs. + namespace VARCHAR NOT NULL PRIMARY KEY, + + -- This field contains the byte serialized ID of the asset to which this + -- configuration is applicable. + asset_id BLOB CHECK(length(asset_id) = 32) NULL, + + -- This field contains the byte serialized compressed group key public key + -- of the asset group to which this configuration is applicable. + group_key BLOB CHECK(LENGTH(group_key) = 33) NULL, + + -- This field is an enum representing the proof type stored in the given + -- universe. + proof_type TEXT NOT NULL CHECK(proof_type IN ('issuance', 'transfer')), + + -- This field is a boolean that indicates whether or not the given universe + -- should accept remote proof insertion via federation sync. + allow_sync_insert BOOLEAN NOT NULL, + + -- This field is a boolean that indicates whether or not the given universe + -- should accept remote proof export via federation sync. + allow_sync_export BOOLEAN NOT NULL, + + -- Both the asset ID and group key cannot be null at the same time. + CHECK ( + (asset_id IS NOT NULL AND group_key IS NULL) OR + (asset_id IS NULL AND group_key IS NOT NULL) + ) +); \ No newline at end of file diff --git a/tapdb/sqlc/models.go b/tapdb/sqlc/models.go index ccfdf9064..8667a09af 100644 --- a/tapdb/sqlc/models.go +++ b/tapdb/sqlc/models.go @@ -165,6 +165,7 @@ type FederationGlobalSyncConfig struct { } type FederationUniSyncConfig struct { + Namespace string AssetID []byte GroupKey []byte ProofType string diff --git a/tapdb/sqlc/queries/universe.sql b/tapdb/sqlc/queries/universe.sql index 9861064ef..a03a2dc03 100644 --- a/tapdb/sqlc/queries/universe.sql +++ b/tapdb/sqlc/queries/universe.sql @@ -327,20 +327,22 @@ ON CONFLICT(proof_type) -- name: QueryFederationGlobalSyncConfigs :many SELECT proof_type, allow_sync_insert, allow_sync_export -FROM federation_global_sync_config; +FROM federation_global_sync_config +ORDER BY proof_type; -- name: UpsertFederationUniSyncConfig :exec INSERT INTO federation_uni_sync_config ( - asset_id, group_key, proof_type, allow_sync_insert, allow_sync_export + namespace, asset_id, group_key, proof_type, allow_sync_insert, allow_sync_export ) VALUES( - @asset_id, @group_key, @proof_type, @allow_sync_insert, @allow_sync_export + @namespace, @asset_id, @group_key, @proof_type, @allow_sync_insert, @allow_sync_export ) -ON CONFLICT(asset_id, group_key, proof_type) +ON CONFLICT(namespace) DO UPDATE SET allow_sync_insert = @allow_sync_insert, allow_sync_export = @allow_sync_export; -- name: QueryFederationUniSyncConfigs :many -SELECT asset_id, group_key, proof_type, allow_sync_insert, allow_sync_export -FROM federation_uni_sync_config; \ No newline at end of file +SELECT namespace, asset_id, group_key, proof_type, allow_sync_insert, allow_sync_export +FROM federation_uni_sync_config +ORDER BY group_key NULLS LAST, asset_id NULLS LAST, proof_type; \ No newline at end of file diff --git a/tapdb/sqlc/universe.sql.go b/tapdb/sqlc/universe.sql.go index 2197ce0eb..6a6260e76 100644 --- a/tapdb/sqlc/universe.sql.go +++ b/tapdb/sqlc/universe.sql.go @@ -387,6 +387,7 @@ func (q *Queries) QueryAssetStatsPerDaySqlite(ctx context.Context, arg QueryAsse const queryFederationGlobalSyncConfigs = `-- name: QueryFederationGlobalSyncConfigs :many SELECT proof_type, allow_sync_insert, allow_sync_export FROM federation_global_sync_config +ORDER BY proof_type ` func (q *Queries) QueryFederationGlobalSyncConfigs(ctx context.Context) ([]FederationGlobalSyncConfig, error) { @@ -413,8 +414,9 @@ func (q *Queries) QueryFederationGlobalSyncConfigs(ctx context.Context) ([]Feder } const queryFederationUniSyncConfigs = `-- name: QueryFederationUniSyncConfigs :many -SELECT asset_id, group_key, proof_type, allow_sync_insert, allow_sync_export +SELECT namespace, asset_id, group_key, proof_type, allow_sync_insert, allow_sync_export FROM federation_uni_sync_config +ORDER BY group_key NULLS LAST, asset_id NULLS LAST, proof_type ` func (q *Queries) QueryFederationUniSyncConfigs(ctx context.Context) ([]FederationUniSyncConfig, error) { @@ -427,6 +429,7 @@ func (q *Queries) QueryFederationUniSyncConfigs(ctx context.Context) ([]Federati for rows.Next() { var i FederationUniSyncConfig if err := rows.Scan( + &i.Namespace, &i.AssetID, &i.GroupKey, &i.ProofType, @@ -830,18 +833,19 @@ func (q *Queries) UpsertFederationGlobalSyncConfig(ctx context.Context, arg Upse const upsertFederationUniSyncConfig = `-- name: UpsertFederationUniSyncConfig :exec INSERT INTO federation_uni_sync_config ( - asset_id, group_key, proof_type, allow_sync_insert, allow_sync_export + namespace, asset_id, group_key, proof_type, allow_sync_insert, allow_sync_export ) VALUES( - $1, $2, $3, $4, $5 + $1, $2, $3, $4, $5, $6 ) -ON CONFLICT(asset_id, group_key, proof_type) +ON CONFLICT(namespace) DO UPDATE SET - allow_sync_insert = $4, - allow_sync_export = $5 + allow_sync_insert = $5, + allow_sync_export = $6 ` type UpsertFederationUniSyncConfigParams struct { + Namespace string AssetID []byte GroupKey []byte ProofType string @@ -851,6 +855,7 @@ type UpsertFederationUniSyncConfigParams struct { func (q *Queries) UpsertFederationUniSyncConfig(ctx context.Context, arg UpsertFederationUniSyncConfigParams) error { _, err := q.db.ExecContext(ctx, upsertFederationUniSyncConfig, + arg.Namespace, arg.AssetID, arg.GroupKey, arg.ProofType, diff --git a/tapdb/universe_federation.go b/tapdb/universe_federation.go index ee84e6ef2..a2e2f4cbf 100644 --- a/tapdb/universe_federation.go +++ b/tapdb/universe_federation.go @@ -263,10 +263,8 @@ func (u *UniverseFederationDB) UpsertFederationSyncConfig( } // Upsert universe specific sync configs. - for i := range uniSyncConfigs { + for _, config := range uniSyncConfigs { var ( - config = uniSyncConfigs[i] - uniID = config.UniverseID groupPubKey []byte assetIDBytes []byte @@ -285,6 +283,7 @@ func (u *UniverseFederationDB) UpsertFederationSyncConfig( err := db.UpsertFederationUniSyncConfig( ctx, UpsertFedUniSyncConfigParams{ + Namespace: uniID.String(), AssetID: assetIDBytes, GroupKey: groupPubKey, ProofType: uniID.ProofType.String(), @@ -379,11 +378,9 @@ func (u *UniverseFederationDB) QueryFederationSyncConfigs( []*universe.FedUniSyncConfig, len(uniDbConfigs), ) - for i := range uniDbConfigs { - conf := uniDbConfigs[i] - + for i, config := range uniDbConfigs { proofType, err := universe.ParseStrProofType( - conf.ProofType, + config.ProofType, ) if err != nil { return err @@ -391,8 +388,8 @@ func (u *UniverseFederationDB) QueryFederationSyncConfigs( // Construct group key public key from bytes. var pubKey *btcec.PublicKey - if conf.GroupKey != nil { - pubKey, err = btcec.ParsePubKey(conf.GroupKey) + if config.GroupKey != nil { + pubKey, err = btcec.ParsePubKey(config.GroupKey) if err != nil { return fmt.Errorf("unable to parse "+ "group key: %v", err) @@ -401,7 +398,7 @@ func (u *UniverseFederationDB) QueryFederationSyncConfigs( // Construct asset ID from bytes. var assetID asset.ID - copy(assetID[:], conf.AssetID) + copy(assetID[:], config.AssetID) uniID := universe.Identifier{ AssetID: assetID, @@ -411,8 +408,8 @@ func (u *UniverseFederationDB) QueryFederationSyncConfigs( uniConfigs[i] = &universe.FedUniSyncConfig{ UniverseID: uniID, - AllowSyncInsert: conf.AllowSyncInsert, - AllowSyncExport: conf.AllowSyncExport, + AllowSyncInsert: config.AllowSyncInsert, + AllowSyncExport: config.AllowSyncExport, } } return nil diff --git a/tapdb/universe_federation_test.go b/tapdb/universe_federation_test.go index c6ece8a9e..b7fb80a21 100644 --- a/tapdb/universe_federation_test.go +++ b/tapdb/universe_federation_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/lightninglabs/taproot-assets/fn" + "github.com/lightninglabs/taproot-assets/internal/test" "github.com/lightninglabs/taproot-assets/tapdb/sqlc" "github.com/lightninglabs/taproot-assets/universe" "github.com/lightningnetwork/lnd/clock" @@ -114,9 +115,9 @@ func TestFederationConfigDefault(t *testing.T) { require.Equal(t, defaultGlobalSyncConfigs, globalConfig) } -// TestFederationGlobalConfigCRUD tests that we're able to properly update the -// global and local federation configs. -func TestFederationGlobalConfigCRUD(t *testing.T) { +// TestFederationConfigCRUD tests that we're able to properly update the global +// and local federation configs. +func TestFederationConfigCRUD(t *testing.T) { t.Parallel() testClock := clock.NewTestClock(time.Now()) @@ -166,7 +167,7 @@ func TestFederationGlobalConfigCRUD(t *testing.T) { expectedCfgs = append(newGlobalProof, newGlobalTransfer...) require.Equal(t, expectedCfgs, dbGlobalCfg) - // Finally, we should be able to update them both in the same txn. + // We should be able to update them both in the same txn. for _, cfg := range expectedCfgs { cfg.AllowSyncInsert = false cfg.AllowSyncExport = false @@ -178,4 +179,70 @@ func TestFederationGlobalConfigCRUD(t *testing.T) { dbGlobalCfg, _, err = fedDB.QueryFederationSyncConfigs(ctx) require.NoError(t, err) require.Equal(t, expectedCfgs, dbGlobalCfg) + + // Finally, if we insert the current config again, we should see no + // change in the returned configs. + singleCfg := fn.MakeSlice(dbGlobalCfg[0]) + err = fedDB.UpsertFederationSyncConfig(ctx, singleCfg, nil) + require.NoError(t, err) + + dbGlobalCfg, _, err = fedDB.QueryFederationSyncConfigs(ctx) + require.NoError(t, err) + require.Equal(t, expectedCfgs, dbGlobalCfg) + + // Now, create configs for specific assets. + randAssetIDBytes := test.RandBytes(32) + randGroupKey := test.RandPubKey(t) + groupCfg := &universe.FedUniSyncConfig{ + UniverseID: universe.Identifier{ + GroupKey: randGroupKey, + ProofType: universe.ProofTypeIssuance, + }, + AllowSyncInsert: true, + AllowSyncExport: false, + } + assetCfg := &universe.FedUniSyncConfig{ + UniverseID: universe.Identifier{ + ProofType: universe.ProofTypeTransfer, + }, + AllowSyncInsert: false, + AllowSyncExport: true, + } + copy(assetCfg.UniverseID.AssetID[:], randAssetIDBytes) + + // Before insertion, there should be no asset-specific configs. + _, dbLocalCfg, err := fedDB.QueryFederationSyncConfigs(ctx) + require.NoError(t, err) + require.Empty(t, dbLocalCfg) + + // Next, store the asset configs and verify that we get the same configs + // back from a query. + localCfg := fn.MakeSlice(groupCfg, assetCfg) + err = fedDB.UpsertFederationSyncConfig(ctx, nil, localCfg) + require.NoError(t, err) + + _, dbLocalCfg, err = fedDB.QueryFederationSyncConfigs(ctx) + require.NoError(t, err) + require.Equal(t, localCfg, dbLocalCfg) + + // We should be able to overwrite a stored config. + groupNewCfg := &universe.FedUniSyncConfig{ + UniverseID: universe.Identifier{ + GroupKey: randGroupKey, + ProofType: universe.ProofTypeIssuance, + }, + AllowSyncInsert: true, + AllowSyncExport: true, + } + err = fedDB.UpsertFederationSyncConfig( + ctx, nil, fn.MakeSlice(groupNewCfg), + ) + require.NoError(t, err) + + _, dbLocalCfg, err = fedDB.QueryFederationSyncConfigs(ctx) + require.NoError(t, err) + require.NotEqual(t, localCfg, dbLocalCfg) + + localCfg = fn.MakeSlice(groupNewCfg, assetCfg) + require.Equal(t, localCfg, dbLocalCfg) }