Skip to content

Commit a310a1a

Browse files
committed
db/itest: cover ApplyTxBatch conformance
Add shared itests verifying ApplyTxBatch stores a tx and advances the sync tip, and confirms a tx in the same block it syncs to.
1 parent 395ce3b commit a310a1a

1 file changed

Lines changed: 164 additions & 0 deletions

File tree

wallet/internal/db/itest/tx_utxo_store_test.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2982,3 +2982,167 @@ func TestListOutputsToWatchBareMultisigUsesOutputScript(t *testing.T) {
29822982
require.Equal(t, multiSigScript, utxos[0].PkScript)
29832983
require.NotEqual(t, memberScript, utxos[0].PkScript)
29842984
}
2985+
2986+
// TestApplyTxBatchStoresTxAndSyncTip verifies that a runtime batch can persist
2987+
// transaction history and advance the wallet sync tip atomically.
2988+
func TestApplyTxBatchStoresTxAndSyncTip(t *testing.T) {
2989+
t.Parallel()
2990+
2991+
store := NewTestStore(t)
2992+
walletName := "wallet-apply-tx-batch"
2993+
walletID := newWallet(t, store, walletName)
2994+
createDerivedAccount(t, store, walletID, db.KeyScopeBIP0084, "default")
2995+
2996+
addr := newDerivedAddress(
2997+
t, store, walletID, db.KeyScopeBIP0084, "default", false,
2998+
)
2999+
tx := newRegularTx(
3000+
[]wire.OutPoint{randomOutPoint()},
3001+
[]*wire.TxOut{{Value: 7000, PkScript: addr.ScriptPubKey}},
3002+
)
3003+
syncedTo := NewBlockFixture(212)
3004+
3005+
err := store.ApplyTxBatch(t.Context(), db.TxBatchParams{
3006+
WalletID: walletID,
3007+
Transactions: []db.CreateTxParams{{
3008+
WalletID: walletID,
3009+
Tx: tx,
3010+
Received: time.Unix(1710000150, 0),
3011+
Status: db.TxStatusPending,
3012+
Credits: map[uint32]btcutil.Address{0: nil},
3013+
}},
3014+
SyncedTo: &syncedTo,
3015+
})
3016+
require.NoError(t, err)
3017+
3018+
txInfo, err := store.GetTx(t.Context(), db.GetTxQuery{
3019+
WalletID: walletID,
3020+
Txid: tx.TxHash(),
3021+
})
3022+
require.NoError(t, err)
3023+
require.Equal(t, db.TxStatusPending, txInfo.Status)
3024+
require.Nil(t, txInfo.Block)
3025+
require.True(t, walletUtxoExists(t, store, walletID, wire.OutPoint{
3026+
Hash: tx.TxHash(), Index: 0,
3027+
}))
3028+
3029+
walletInfo, err := store.GetWallet(t.Context(), walletName)
3030+
require.NoError(t, err)
3031+
require.NotNil(t, walletInfo.SyncedTo)
3032+
require.Equal(t, syncedTo.Hash, walletInfo.SyncedTo.Hash)
3033+
require.Equal(t, syncedTo.Height, walletInfo.SyncedTo.Height)
3034+
require.Equal(t, syncedTo.Timestamp.Unix(),
3035+
walletInfo.SyncedTo.Timestamp.Unix())
3036+
}
3037+
3038+
// TestApplyTxBatchConfirmsTxInSameBlock verifies that a batch can record a
3039+
// transaction confirmed in the very block the same batch introduces as the new
3040+
// sync tip. The confirming block row does not exist before the batch, so the
3041+
// batch must create the sync-tip block before recording the confirmed
3042+
// transaction; otherwise the confirmed insert fails with ErrBlockNotFound.
3043+
func TestApplyTxBatchConfirmsTxInSameBlock(t *testing.T) {
3044+
t.Parallel()
3045+
3046+
store := NewTestStore(t)
3047+
walletName := "wallet-apply-tx-batch-confirmed"
3048+
walletID := newWallet(t, store, walletName)
3049+
createDerivedAccount(t, store, walletID, db.KeyScopeBIP0084, "default")
3050+
3051+
addr := newDerivedAddress(
3052+
t, store, walletID, db.KeyScopeBIP0084, "default", false,
3053+
)
3054+
tx := newRegularTx(
3055+
[]wire.OutPoint{randomOutPoint()},
3056+
[]*wire.TxOut{{Value: 7000, PkScript: addr.ScriptPubKey}},
3057+
)
3058+
3059+
// The confirming block is also the batch's new sync tip. It is not
3060+
// inserted ahead of time, so the batch itself must create it before the
3061+
// confirmed transaction is recorded.
3062+
block := NewBlockFixture(213)
3063+
3064+
err := store.ApplyTxBatch(t.Context(), db.TxBatchParams{
3065+
WalletID: walletID,
3066+
Transactions: []db.CreateTxParams{{
3067+
WalletID: walletID,
3068+
Tx: tx,
3069+
Received: time.Unix(1710000160, 0),
3070+
Block: &block,
3071+
Status: db.TxStatusPublished,
3072+
Credits: map[uint32]btcutil.Address{0: nil},
3073+
}},
3074+
SyncedTo: &block,
3075+
})
3076+
require.NoError(t, err)
3077+
3078+
// The transaction is recorded as confirmed in the batch's block and its
3079+
// credited output is in the wallet UTXO set.
3080+
txInfo, err := store.GetTx(t.Context(), db.GetTxQuery{
3081+
WalletID: walletID,
3082+
Txid: tx.TxHash(),
3083+
})
3084+
require.NoError(t, err)
3085+
require.Equal(t, db.TxStatusPublished, txInfo.Status)
3086+
require.NotNil(t, txInfo.Block)
3087+
require.Equal(t, block.Height, txInfo.Block.Height)
3088+
require.Equal(t, block.Hash, txInfo.Block.Hash)
3089+
require.True(t, walletUtxoExists(t, store, walletID, wire.OutPoint{
3090+
Hash: tx.TxHash(), Index: 0,
3091+
}))
3092+
3093+
// The sync tip advanced to the same block.
3094+
walletInfo, err := store.GetWallet(t.Context(), walletName)
3095+
require.NoError(t, err)
3096+
require.NotNil(t, walletInfo.SyncedTo)
3097+
require.Equal(t, block.Height, walletInfo.SyncedTo.Height)
3098+
require.Equal(t, block.Hash, walletInfo.SyncedTo.Hash)
3099+
}
3100+
3101+
// TestApplyTxBatchRejectsMismatchedWalletID verifies that a batch is rejected
3102+
// when any transaction is owned by a wallet other than the batch wallet, and
3103+
// that the rejection commits nothing: the sync tip is not advanced and no
3104+
// transaction row is written.
3105+
func TestApplyTxBatchRejectsMismatchedWalletID(t *testing.T) {
3106+
t.Parallel()
3107+
3108+
store := NewTestStore(t)
3109+
walletName := "wallet-apply-tx-batch-mismatch"
3110+
walletID := newWallet(t, store, walletName)
3111+
createDerivedAccount(t, store, walletID, db.KeyScopeBIP0084, "default")
3112+
3113+
addr := newDerivedAddress(
3114+
t, store, walletID, db.KeyScopeBIP0084, "default", false,
3115+
)
3116+
tx := newRegularTx(
3117+
[]wire.OutPoint{randomOutPoint()},
3118+
[]*wire.TxOut{{Value: 7000, PkScript: addr.ScriptPubKey}},
3119+
)
3120+
syncedTo := NewBlockFixture(214)
3121+
3122+
// The batch targets walletID, but the lone transaction claims a different
3123+
// wallet. The whole batch must be rejected before any write.
3124+
err := store.ApplyTxBatch(t.Context(), db.TxBatchParams{
3125+
WalletID: walletID,
3126+
Transactions: []db.CreateTxParams{{
3127+
WalletID: walletID + 99,
3128+
Tx: tx,
3129+
Received: time.Unix(1710000170, 0),
3130+
Status: db.TxStatusPending,
3131+
Credits: map[uint32]btcutil.Address{0: nil},
3132+
}},
3133+
SyncedTo: &syncedTo,
3134+
})
3135+
require.ErrorIs(t, err, db.ErrInvalidParam)
3136+
3137+
// The sync tip was not advanced: the wallet is still unsynced.
3138+
walletInfo, err := store.GetWallet(t.Context(), walletName)
3139+
require.NoError(t, err)
3140+
require.Nil(t, walletInfo.SyncedTo)
3141+
3142+
// No transaction row was written for either wallet.
3143+
_, err = store.GetTx(t.Context(), db.GetTxQuery{
3144+
WalletID: walletID,
3145+
Txid: tx.TxHash(),
3146+
})
3147+
require.ErrorIs(t, err, db.ErrTxNotFound)
3148+
}

0 commit comments

Comments
 (0)