From 6cd966eda698cb3243fde81e7e39bf2e0002a643 Mon Sep 17 00:00:00 2001 From: Damian Orzepowski Date: Mon, 20 Jan 2025 11:33:18 +0100 Subject: [PATCH] feat(SPV-1398): rename UserUTXO struct. Remove unneeded code. --- engine/database/models.go | 2 +- engine/database/repository/users.go | 2 +- .../testabilities/fixture_database.go | 2 +- .../testabilities/user_utxo_fixture.go | 4 +- engine/database/tracked_transaction.go | 6 +- engine/database/user_utxos.go | 10 ++-- engine/tester/fixtures/tx_fixtures_test.go | 60 ------------------- .../internal/inputs/inputs_query_composer.go | 4 +- .../outlines/internal/inputs/selector.go | 8 +-- .../internal/inputs/selector_example_test.go | 16 ++--- .../outlines/internal/inputs/selector_test.go | 4 +- .../assertions_inputs_selector.go | 12 ++-- 12 files changed, 35 insertions(+), 95 deletions(-) diff --git a/engine/database/models.go b/engine/database/models.go index fbea756d..4eb06854 100644 --- a/engine/database/models.go +++ b/engine/database/models.go @@ -9,7 +9,7 @@ func Models() []any { User{}, Paymail{}, Address{}, - UsersUTXO{}, + UserUTXO{}, Operation{}, } } diff --git a/engine/database/repository/users.go b/engine/database/repository/users.go index 9f1b5476..e8bc97a6 100644 --- a/engine/database/repository/users.go +++ b/engine/database/repository/users.go @@ -50,7 +50,7 @@ func (u *Users) GetBalance(ctx context.Context, userID string, bucket string) (b var balance bsv.Satoshis err := u.db. WithContext(ctx). - Model(&database.UsersUTXO{}). + Model(&database.UserUTXO{}). Where("user_id = ? AND bucket = ?", userID, bucket). Select("COALESCE(SUM(satoshis), 0)"). Row(). diff --git a/engine/database/testabilities/fixture_database.go b/engine/database/testabilities/fixture_database.go index 6fa4f291..117719b4 100644 --- a/engine/database/testabilities/fixture_database.go +++ b/engine/database/testabilities/fixture_database.go @@ -26,7 +26,7 @@ type UserUtxoFixture interface { // WithSatoshis sets the satoshis value of the UTXO. WithSatoshis(satoshis bsv.Satoshis) UserUtxoFixture - Storable[database.UsersUTXO] + Storable[database.UserUTXO] } type Storable[Data any] interface { diff --git a/engine/database/testabilities/user_utxo_fixture.go b/engine/database/testabilities/user_utxo_fixture.go index 8551dfda..cd746c5c 100644 --- a/engine/database/testabilities/user_utxo_fixture.go +++ b/engine/database/testabilities/user_utxo_fixture.go @@ -57,8 +57,8 @@ func (f *userUtxoFixture) WithSatoshis(satoshis bsv.Satoshis) UserUtxoFixture { return f } -func (f *userUtxoFixture) Stored() *database.UsersUTXO { - utxo := &database.UsersUTXO{ +func (f *userUtxoFixture) Stored() *database.UserUTXO { + utxo := &database.UserUTXO{ UserID: f.userID, TxID: f.txID, Vout: f.vout, diff --git a/engine/database/tracked_transaction.go b/engine/database/tracked_transaction.go index aaaf15a0..540927fc 100644 --- a/engine/database/tracked_transaction.go +++ b/engine/database/tracked_transaction.go @@ -22,7 +22,7 @@ type TrackedTransaction struct { Inputs []*TrackedOutput `gorm:"foreignKey:SpendingTX"` Outputs []*TrackedOutput `gorm:"foreignKey:TxID"` - newUTXOs []*UsersUTXO `gorm:"-"` + newUTXOs []*UserUTXO `gorm:"-"` } // CreateP2PKHOutput prepares a new P2PKH output and adds it to the transaction. @@ -52,7 +52,7 @@ func (t *TrackedTransaction) AddInputs(inputs ...*TrackedOutput) { func (t *TrackedTransaction) AfterCreate(tx *gorm.DB) error { // Add new UTXOs if len(t.newUTXOs) > 0 { - err := tx.Model(&UsersUTXO{}).Create(t.newUTXOs).Error + err := tx.Model(&UserUTXO{}).Create(t.newUTXOs).Error if err != nil { return spverrors.Wrapf(err, "failed to save user utxos") } @@ -67,7 +67,7 @@ func (t *TrackedTransaction) AfterCreate(tx *gorm.DB) error { } }) if len(spentOutpoints) > 0 { - err := tx.Where("(tx_id, vout) IN ?", spentOutpoints).Delete(&UsersUTXO{}).Error + err := tx.Where("(tx_id, vout) IN ?", spentOutpoints).Delete(&UserUTXO{}).Error if err != nil { return spverrors.Wrapf(err, "failed to delete spent utxos") } diff --git a/engine/database/user_utxos.go b/engine/database/user_utxos.go index 893a3c1f..3ff67041 100644 --- a/engine/database/user_utxos.go +++ b/engine/database/user_utxos.go @@ -14,8 +14,8 @@ import ( // + 4 bytes nSequence const EstimatedInputSizeForP2PKH = 148 -// UsersUTXO is a table holding user's Unspent Transaction Outputs (UTXOs). -type UsersUTXO struct { +// UserUTXO is a table holding user's Unspent Transaction Outputs (UTXOs). +type UserUTXO struct { UserID string `gorm:"primaryKey;uniqueIndex:idx_window,sort:asc,priority:1"` TxID string `gorm:"primaryKey;uniqueIndex:idx_window,sort:asc,priority:4"` Vout uint32 `gorm:"primaryKey;uniqueIndex:idx_window,sort:asc,priority:5"` @@ -30,9 +30,9 @@ type UsersUTXO struct { CustomInstructions datatypes.JSONSlice[CustomInstruction] } -// NewP2PKHUserUTXO creates a new UsersUTXO instance for a P2PKH output based on the given output and custom instructions. -func NewP2PKHUserUTXO(output *TrackedOutput, customInstructions datatypes.JSONSlice[CustomInstruction]) *UsersUTXO { - return &UsersUTXO{ +// NewP2PKHUserUTXO creates a new UserUTXO instance for a P2PKH output based on the given output and custom instructions. +func NewP2PKHUserUTXO(output *TrackedOutput, customInstructions datatypes.JSONSlice[CustomInstruction]) *UserUTXO { + return &UserUTXO{ UserID: output.UserID, TxID: output.TxID, Vout: output.Vout, diff --git a/engine/tester/fixtures/tx_fixtures_test.go b/engine/tester/fixtures/tx_fixtures_test.go index f04a5ac6..172420ac 100644 --- a/engine/tester/fixtures/tx_fixtures_test.go +++ b/engine/tester/fixtures/tx_fixtures_test.go @@ -62,63 +62,3 @@ func TestMockTXGeneration(t *testing.T) { }) } } - -func TestSpike(t *testing.T) { - - tx4 := GivenTX(t). - WithInput(1000). - WithP2PKHOutput(100).TX() - - // tx6 := tx4.InputIdx(0).SourceTransaction - - tx1 := GivenTX(t). - WithInputFromUTXO(tx4, 0). - WithP2PKHOutput(1).TX() - - for _, input := range tx1.Inputs { - input.UnlockingScriptTemplate = RecipientInternal.P2PKHUnlockingScriptTemplate() - } - err := tx1.Sign() - require.NoError(t, err) - - tx5 := GivenTX(t). - WithInput(20). - WithP2PKHOutput(2). - WithP2PKHOutput(2).TX() - - // tx7 := tx5.InputIdx(0).SourceTransaction - - tx3 := GivenTX(t). - WithInputFromUTXO(tx5, 0). - WithP2PKHOutput(1).TX() - for _, input := range tx3.Inputs { - input.UnlockingScriptTemplate = RecipientInternal.P2PKHUnlockingScriptTemplate() - } - err = tx3.Sign() - require.NoError(t, err) - - txn := GivenTX(t). - WithInputFromUTXO(tx5, 1). - WithP2PKHOutput(1).TX() - for _, input := range txn.Inputs { - input.UnlockingScriptTemplate = RecipientInternal.P2PKHUnlockingScriptTemplate() - } - err = txn.Sign() - require.NoError(t, err) - - tx2 := GivenTX(t). - WithInput(1000). - WithP2PKHOutput(100).TX() - - tx0 := GivenTX(t). - WithInputFromUTXO(tx1, 0). - WithInputFromUTXO(tx2, 0). - WithInputFromUTXO(tx3, 0). - TX() - - for i, input := range tx0.Inputs { - verified, err := spv.VerifyScripts(input.SourceTransaction) - require.NoError(t, err, "input %s", i) - require.True(t, verified, "input %s", i) - } -} diff --git a/engine/transaction/outlines/internal/inputs/inputs_query_composer.go b/engine/transaction/outlines/internal/inputs/inputs_query_composer.go index cd23182f..b51d34ab 100644 --- a/engine/transaction/outlines/internal/inputs/inputs_query_composer.go +++ b/engine/transaction/outlines/internal/inputs/inputs_query_composer.go @@ -22,12 +22,12 @@ func (c *inputsQueryComposer) build(db *gorm.DB) *gorm.DB { utxoWithMinChange := c.searchForMinimalChangeValue(db, utxoWithChange) selectedOutpoints := c.chooseInputsToCoverOutputsAndFeesAndHaveMinimalChange(db, utxoWithMinChange) - res := db.Model(&database.UsersUTXO{}).Where("(tx_id, vout) in (?)", selectedOutpoints) + res := db.Model(&database.UserUTXO{}).Where("(tx_id, vout) in (?)", selectedOutpoints) return res } func (c *inputsQueryComposer) utxos(db *gorm.DB) *gorm.DB { - return db.Model(&database.UsersUTXO{}). + return db.Model(&database.UserUTXO{}). Select( txIdColumn, voutColumn, diff --git a/engine/transaction/outlines/internal/inputs/selector.go b/engine/transaction/outlines/internal/inputs/selector.go index 4c9b4e3a..734c1964 100644 --- a/engine/transaction/outlines/internal/inputs/selector.go +++ b/engine/transaction/outlines/internal/inputs/selector.go @@ -16,7 +16,7 @@ const voutColumn = "vout" // Selector is a service that selects inputs for transaction. type Selector interface { - SelectInputsForTransaction(ctx context.Context, userID string, satoshis bsv.Satoshis, byteSizeOfTxBeforeAddingSelectedInputs uint64) ([]*database.UsersUTXO, error) + SelectInputsForTransaction(ctx context.Context, userID string, satoshis bsv.Satoshis, byteSizeOfTxBeforeAddingSelectedInputs uint64) ([]*database.UserUTXO, error) } const ( @@ -39,7 +39,7 @@ func NewSelector(db *gorm.DB, feeUnit bsv.FeeUnit) Selector { } } -func (r *sqlInputsSelector) SelectInputsForTransaction(ctx context.Context, userID string, outputsTotalValue bsv.Satoshis, byteSizeOfTxWithoutInputs uint64) (utxos []*database.UsersUTXO, err error) { +func (r *sqlInputsSelector) SelectInputsForTransaction(ctx context.Context, userID string, outputsTotalValue bsv.Satoshis, byteSizeOfTxWithoutInputs uint64) (utxos []*database.UserUTXO, err error) { err = r.db.WithContext(ctx).Transaction(func(db *gorm.DB) error { inputsQuery := r.buildQueryForInputs(db, userID, outputsTotalValue, byteSizeOfTxWithoutInputs) @@ -78,10 +78,10 @@ func (r *sqlInputsSelector) buildQueryForInputs(db *gorm.DB, userID string, outp return composer.build(db) } -func (r *sqlInputsSelector) buildUpdateTouchedAtQuery(db *gorm.DB, utxos []*database.UsersUTXO) *gorm.DB { +func (r *sqlInputsSelector) buildUpdateTouchedAtQuery(db *gorm.DB, utxos []*database.UserUTXO) *gorm.DB { outpoints := make([][]any, 0, len(utxos)) for _, utxo := range utxos { outpoints = append(outpoints, []any{utxo.TxID, utxo.Vout}) } - return db.Model(&database.UsersUTXO{}).Where("(tx_id, vout) in (?)", outpoints) + return db.Model(&database.UserUTXO{}).Where("(tx_id, vout) in (?)", outpoints) } diff --git a/engine/transaction/outlines/internal/inputs/selector_example_test.go b/engine/transaction/outlines/internal/inputs/selector_example_test.go index 5b92fbe4..49a55ff2 100644 --- a/engine/transaction/outlines/internal/inputs/selector_example_test.go +++ b/engine/transaction/outlines/internal/inputs/selector_example_test.go @@ -19,13 +19,13 @@ func ExampleSelector_buildQueryForInputs_sqlite() { query := db.ToSQL(func(db *gorm.DB) *gorm.DB { query := selector.buildQueryForInputs(db, "someuserid", 1, 10) - query.Find(&database.UsersUTXO{}) + query.Find(&database.UserUTXO{}) return query }) fmt.Println(query) - // Output: SELECT * FROM `xapi_users_utxos` WHERE (tx_id, vout) in (SELECT tx_id,vout FROM (SELECT tx_id,vout,change,min(case when change >= 0 then change end) over () as min_change FROM (SELECT tx_id,vout,case when remaining_value - fee_no_change_output <= 0 then remaining_value - fee_no_change_output else remaining_value - fee_with_change_output end as change FROM (SELECT `tx_id`,`vout`,sum(satoshis) over (order by touched_at ASC, created_at ASC, tx_id ASC, vout ASC) - 1 as remaining_value,ceil((sum(estimated_input_size) over (order by touched_at ASC, created_at ASC, tx_id ASC, vout ASC) + 10) / cast(1000 as float)) * 1 as fee_no_change_output,ceil((sum(estimated_input_size) over (order by touched_at ASC, created_at ASC, tx_id ASC, vout ASC) + 10 + 34) / cast(1000 as float)) * 1 as fee_with_change_output FROM `xapi_users_utxos` WHERE user_id = "someuserid") as utxo) as utxoWithChange) as utxoWithMinChange WHERE change <= min_change) + // Output: SELECT * FROM `xapi_user_utxos` WHERE (tx_id, vout) in (SELECT tx_id,vout FROM (SELECT tx_id,vout,change,min(case when change >= 0 then change end) over () as min_change FROM (SELECT tx_id,vout,case when remaining_value - fee_no_change_output <= 0 then remaining_value - fee_no_change_output else remaining_value - fee_with_change_output end as change FROM (SELECT `tx_id`,`vout`,sum(satoshis) over (order by touched_at ASC, created_at ASC, tx_id ASC, vout ASC) - 1 as remaining_value,ceil((sum(estimated_input_size) over (order by touched_at ASC, created_at ASC, tx_id ASC, vout ASC) + 10) / cast(1000 as float)) * 1 as fee_no_change_output,ceil((sum(estimated_input_size) over (order by touched_at ASC, created_at ASC, tx_id ASC, vout ASC) + 10 + 34) / cast(1000 as float)) * 1 as fee_with_change_output FROM `xapi_user_utxos` WHERE user_id = "someuserid") as utxo) as utxoWithChange) as utxoWithMinChange WHERE change <= min_change) } // ExampleSelector_buildQueryForInputs_postgresql demonstrates what would be the query used to select inputs for a transaction. @@ -37,13 +37,13 @@ func ExampleSelector_buildQueryForInputs_postgresql() { query := db.ToSQL(func(db *gorm.DB) *gorm.DB { query := selector.buildQueryForInputs(db, "someuserid", 1, 10) - query.Find(&database.UsersUTXO{}) + query.Find(&database.UserUTXO{}) return query }) fmt.Println(query) - // Output: SELECT * FROM "xapi_users_utxos" WHERE (tx_id, vout) in (SELECT tx_id,vout FROM (SELECT tx_id,vout,change,min(case when change >= 0 then change end) over () as min_change FROM (SELECT tx_id,vout,case when remaining_value - fee_no_change_output <= 0 then remaining_value - fee_no_change_output else remaining_value - fee_with_change_output end as change FROM (SELECT "tx_id","vout",sum(satoshis) over (order by touched_at ASC, created_at ASC, tx_id ASC, vout ASC) - 1 as remaining_value,ceil((sum(estimated_input_size) over (order by touched_at ASC, created_at ASC, tx_id ASC, vout ASC) + 10) / cast(1000 as float)) * 1 as fee_no_change_output,ceil((sum(estimated_input_size) over (order by touched_at ASC, created_at ASC, tx_id ASC, vout ASC) + 10 + 34) / cast(1000 as float)) * 1 as fee_with_change_output FROM "xapi_users_utxos" WHERE user_id = 'someuserid') as utxo) as utxoWithChange) as utxoWithMinChange WHERE change <= min_change) + // Output: SELECT * FROM "xapi_user_utxos" WHERE (tx_id, vout) in (SELECT tx_id,vout FROM (SELECT tx_id,vout,change,min(case when change >= 0 then change end) over () as min_change FROM (SELECT tx_id,vout,case when remaining_value - fee_no_change_output <= 0 then remaining_value - fee_no_change_output else remaining_value - fee_with_change_output end as change FROM (SELECT "tx_id","vout",sum(satoshis) over (order by touched_at ASC, created_at ASC, tx_id ASC, vout ASC) - 1 as remaining_value,ceil((sum(estimated_input_size) over (order by touched_at ASC, created_at ASC, tx_id ASC, vout ASC) + 10) / cast(1000 as float)) * 1 as fee_no_change_output,ceil((sum(estimated_input_size) over (order by touched_at ASC, created_at ASC, tx_id ASC, vout ASC) + 10 + 34) / cast(1000 as float)) * 1 as fee_with_change_output FROM "xapi_user_utxos" WHERE user_id = 'someuserid') as utxo) as utxoWithChange) as utxoWithMinChange WHERE change <= min_change) } // ExampleSelector_buildUpdateTouchedAtQuery_sqlite demonstrates what would be the SQL statement used to update inputs after selecting them. @@ -52,7 +52,7 @@ func ExampleSelector_buildUpdateTouchedAtQuery_sqlite() { selector := givenInputsSelector(db) - utxos := []*database.UsersUTXO{ + utxos := []*database.UserUTXO{ {UserID: "id_of_user_1", TxID: "tx_id_1", Vout: 0, Satoshis: 10, EstimatedInputSize: 148, Bucket: "bsv", CreatedAt: time.Now(), TouchedAt: time.Now()}, {UserID: "id_of_user_1", TxID: "tx_id_1", Vout: 1, Satoshis: 10, EstimatedInputSize: 148, Bucket: "bsv", CreatedAt: time.Now(), TouchedAt: time.Now()}, {UserID: "id_of_user_1", TxID: "tx_id_2", Vout: 0, Satoshis: 10, EstimatedInputSize: 148, Bucket: "bsv", CreatedAt: time.Now(), TouchedAt: time.Now()}, @@ -66,7 +66,7 @@ func ExampleSelector_buildUpdateTouchedAtQuery_sqlite() { fmt.Println(query) - // Output: UPDATE `xapi_users_utxos` SET `touched_at`="2006-02-01 15:04:05" WHERE (tx_id, vout) in (("tx_id_1",0),("tx_id_1",1),("tx_id_2",0)) + // Output: UPDATE `xapi_user_utxos` SET `touched_at`="2006-02-01 15:04:05" WHERE (tx_id, vout) in (("tx_id_1",0),("tx_id_1",1),("tx_id_2",0)) } // ExampleSelector_buildUpdateTouchedAtQuery_postgres demonstrates what would be the SQL statement used to update inputs after selecting them. @@ -75,7 +75,7 @@ func ExampleSelector_buildUpdateTouchedAtQuery_postgres() { selector := givenInputsSelector(db) - utxos := []*database.UsersUTXO{ + utxos := []*database.UserUTXO{ {UserID: "id_of_user_1", TxID: "tx_id_1", Vout: 0, Satoshis: 10, EstimatedInputSize: 148, Bucket: "bsv", CreatedAt: time.Now(), TouchedAt: time.Now()}, {UserID: "id_of_user_1", TxID: "tx_id_1", Vout: 1, Satoshis: 10, EstimatedInputSize: 148, Bucket: "bsv", CreatedAt: time.Now(), TouchedAt: time.Now()}, {UserID: "id_of_user_1", TxID: "tx_id_2", Vout: 0, Satoshis: 10, EstimatedInputSize: 148, Bucket: "bsv", CreatedAt: time.Now(), TouchedAt: time.Now()}, @@ -89,7 +89,7 @@ func ExampleSelector_buildUpdateTouchedAtQuery_postgres() { fmt.Println(query) - // Output: UPDATE "xapi_users_utxos" SET "touched_at"='2006-02-01 15:04:05' WHERE (tx_id, vout) in (('tx_id_1',0),('tx_id_1',1),('tx_id_2',0)) + // Output: UPDATE "xapi_user_utxos" SET "touched_at"='2006-02-01 15:04:05' WHERE (tx_id, vout) in (('tx_id_1',0),('tx_id_1',1),('tx_id_2',0)) } func givenInputsSelector(db *gorm.DB) *sqlInputsSelector { diff --git a/engine/transaction/outlines/internal/inputs/selector_test.go b/engine/transaction/outlines/internal/inputs/selector_test.go index 0613887b..7f29cd0c 100644 --- a/engine/transaction/outlines/internal/inputs/selector_test.go +++ b/engine/transaction/outlines/internal/inputs/selector_test.go @@ -86,7 +86,7 @@ func TestInputsSelector(t *testing.T) { defer cleanup() // and: having some utxo in database - ownedInputs := []*database.UsersUTXO{ + ownedInputs := []*database.UserUTXO{ given.DB().HasUTXO().OwnedBySender().P2PKH().WithSatoshis(10).Stored(), given.DB().HasUTXO().OwnedBySender().P2PKH().WithSatoshis(10).Stored(), given.DB().HasUTXO().OwnedBySender().P2PKH().WithSatoshis(10).Stored(), @@ -132,7 +132,7 @@ func TestInputsSelector(t *testing.T) { defer cleanup() // and: having some utxo in database - ownedInputs := []*database.UsersUTXO{ + ownedInputs := []*database.UserUTXO{ given.DB().HasUTXO().OwnedBySender().P2PKH().WithSatoshis(10).Stored(), given.DB().HasUTXO().OwnedBySender().P2PKH().WithSatoshis(10).Stored(), given.DB().HasUTXO().OwnedBySender().P2PKH().WithSatoshis(10).Stored(), diff --git a/engine/transaction/outlines/internal/inputs/testabilities/assertions_inputs_selector.go b/engine/transaction/outlines/internal/inputs/testabilities/assertions_inputs_selector.go index 1cfde417..6c390c90 100644 --- a/engine/transaction/outlines/internal/inputs/testabilities/assertions_inputs_selector.go +++ b/engine/transaction/outlines/internal/inputs/testabilities/assertions_inputs_selector.go @@ -13,12 +13,12 @@ type InputsSelectorAssertions interface { } type SuccessfullySelectedInputsAssertions interface { - SelectedInputs(inputs []*database.UsersUTXO) SelectedInputsAssertions + SelectedInputs(inputs []*database.UserUTXO) SelectedInputsAssertions } type SelectedInputsAssertions interface { AreEmpty() - ComparingTo(inputs []*database.UsersUTXO) ComparingSelectedInputsAssertions + ComparingTo(inputs []*database.UserUTXO) ComparingSelectedInputsAssertions } type ComparingSelectedInputsAssertions interface { @@ -29,8 +29,8 @@ type assertion struct { t testing.TB require *require.Assertions assert *assert.Assertions - actual []*database.UsersUTXO - comparingSource []*database.UsersUTXO + actual []*database.UserUTXO + comparingSource []*database.UserUTXO } func newAssertions(t testing.TB) InputsSelectorAssertions { @@ -47,7 +47,7 @@ func (a assertion) WithoutError(err error) SuccessfullySelectedInputsAssertions return a } -func (a assertion) SelectedInputs(inputs []*database.UsersUTXO) SelectedInputsAssertions { +func (a assertion) SelectedInputs(inputs []*database.UserUTXO) SelectedInputsAssertions { a.t.Helper() a.actual = inputs return a @@ -58,7 +58,7 @@ func (a assertion) AreEmpty() { a.require.Empty(a.actual) } -func (a assertion) ComparingTo(inputs []*database.UsersUTXO) ComparingSelectedInputsAssertions { +func (a assertion) ComparingTo(inputs []*database.UserUTXO) ComparingSelectedInputsAssertions { a.t.Helper() a.comparingSource = inputs return a