@@ -957,6 +957,96 @@ func TestProcessRelevantTxUsesStore(t *testing.T) {
957957 store .AssertExpectations (t )
958958}
959959
960+ // matchConfirmedTxBatchNoSyncTip returns a matcher asserting a confirmed
961+ // relevant transaction is written as one store batch whose transaction carries
962+ // the confirming block, while SyncedTo stays nil so the standalone
963+ // notification does not advance the wallet sync tip.
964+ func matchConfirmedTxBatchNoSyncTip (walletID uint32 , tx * wire.MsgTx ,
965+ addr btcutil.Address , received time.Time , block * wtxmgr.BlockMeta ) any {
966+
967+ return mock .MatchedBy (func (params db.TxBatchParams ) bool {
968+ if params .WalletID != walletID ||
969+ len (params .Transactions ) != 1 ||
970+ params .SyncedTo != nil {
971+
972+ return false
973+ }
974+
975+ txParams := params .Transactions [0 ]
976+ if txParams .Block == nil ||
977+ ! matchStoreBlockFields (txParams .Block , block ) {
978+
979+ return false
980+ }
981+
982+ return matchRelevantTxParams (
983+ txParams , walletID , tx , addr , received ,
984+ )
985+ })
986+ }
987+
988+ // TestProcessRelevantTxUsesStoreConfirmedBlock verifies that a confirmed
989+ // relevant transaction notification (one carrying a block) builds an
990+ // ApplyTxBatch with the transaction's confirming block set but SyncedTo left
991+ // nil, so the standalone notification records the confirmed tx without
992+ // advancing the wallet sync tip. The Store block row is then ensured by the
993+ // SQL applyBatchTransaction path (covered separately on the Store-layer fix).
994+ func TestProcessRelevantTxUsesStoreConfirmedBlock (t * testing.T ) {
995+ t .Parallel ()
996+
997+ // Arrange: A store-backed syncer and a transaction paying a
998+ // wallet-owned address, confirmed in a block.
999+ const walletID uint32 = 8
1000+
1001+ store := & walletmock.Store {}
1002+ publisher := & mockTxPublisher {}
1003+ s := newSyncer (
1004+ Config {ChainParams : & chainParams }, nil , nil , publisher ,
1005+ syncerStoreConfig {store : store , walletID : walletID },
1006+ )
1007+
1008+ addr , err := btcutil .NewAddressPubKeyHash (
1009+ make ([]byte , 20 ), & chainParams ,
1010+ )
1011+ require .NoError (t , err )
1012+
1013+ pkScript , err := txscript .PayToAddrScript (addr )
1014+ require .NoError (t , err )
1015+
1016+ tx := wire .NewMsgTx (1 )
1017+ tx .AddTxOut (& wire.TxOut {Value : 1000 , PkScript : pkScript })
1018+
1019+ received := time .Unix (456 , 0 ).UTC ()
1020+ rec , err := wtxmgr .NewTxRecordFromMsgTx (tx , received )
1021+ require .NoError (t , err )
1022+
1023+ block := & wtxmgr.BlockMeta {
1024+ Block : wtxmgr.Block {
1025+ Hash : chainhash.Hash {0x0c },
1026+ Height : 222 ,
1027+ },
1028+ Time : time .Unix (789 , 0 ).UTC (),
1029+ }
1030+
1031+ expectAddressOwned (t , store , walletID , addr )
1032+ store .On ("ApplyTxBatch" , mock .Anything ,
1033+ matchConfirmedTxBatchNoSyncTip (
1034+ walletID , tx , addr , received , block ,
1035+ ),
1036+ ).Return (nil ).Once ()
1037+
1038+ // Act: Process a confirmed relevant transaction notification.
1039+ err = s .processChainUpdate (
1040+ t .Context (), chain.RelevantTx {TxRecord : rec , Block : block },
1041+ )
1042+
1043+ // Assert: The batch carries the confirming block but leaves the sync
1044+ // tip untouched. The mock registers only ApplyTxBatch (and address
1045+ // lookups), so any sync-tip write would fail AssertExpectations.
1046+ require .NoError (t , err )
1047+ store .AssertExpectations (t )
1048+ }
1049+
9601050// matchStoreBlockFields reports whether a store block matches the wtxmgr block
9611051// metadata's hash, height, and timestamp.
9621052func matchStoreBlockFields (b * db.Block , block * wtxmgr.BlockMeta ) bool {
0 commit comments