@@ -805,121 +805,214 @@ func (s *Store) rollback(ns walletdb.ReadWriteBucket, height int32) error {
805
805
return putMinedBalance (ns , minedBalance )
806
806
}
807
807
808
- // UnspentOutputs returns all unspent received transaction outputs.
809
- // The order is undefined.
810
- func (s * Store ) UnspentOutputs (ns walletdb.ReadBucket ) ([]Credit , error ) {
811
- var unspent []Credit
808
+ // fetchCredits retrieves credits from the store based on the provided filters.
809
+ // It iterates over both mined (unspent) and unmined credits.
810
+ //
811
+ // Parameters:
812
+ // - ns: The database bucket to read from.
813
+ // - includeLocked: If true, credits locked by LockOutput are included.
814
+ // - includeSpentByUnmined: If true, credits spent by unmined transactions
815
+ // are included.
816
+ // - populateFullDetails: If true, all fields of the Credit struct are
817
+ // populated. Otherwise, only OutPoint and PkScript are populated.
818
+ func (s * Store ) fetchCredits (ns walletdb.ReadBucket , includeLocked bool ,
819
+ includeSpentByUnmined bool ,
820
+ populateFullDetails bool ) ([]Credit , error ) {
821
+
822
+ var credits []Credit
823
+ now := s .clock .Now () // Cache current time for lock checks
824
+
825
+ // Iterate over mined unspent credits (bucketUnspent).
826
+ unspentBucket := ns .NestedReadBucket (bucketUnspent )
827
+ if unspentBucket != nil {
828
+ err := unspentBucket .ForEach (func (k , v []byte ) error {
829
+ var op wire.OutPoint
830
+ err := readCanonicalOutPoint (k , & op )
831
+ if err != nil {
832
+ return err
833
+ }
812
834
813
- var op wire. OutPoint
814
- var block Block
815
- err := ns . NestedReadBucket ( bucketUnspent ). ForEach ( func ( k , v [] byte ) error {
816
- err := readCanonicalOutPoint ( k , & op )
817
- if err != nil {
818
- return err
819
- }
835
+ // Check if locked, skip if necessary.
836
+ if ! includeLocked {
837
+ _ , _ , isLocked := isLockedOutput ( ns , op , now )
838
+ if isLocked {
839
+ return nil
840
+ }
841
+ }
820
842
821
- // Skip the output if it's locked.
822
- _ , _ , isLocked := isLockedOutput (ns , op , s .clock .Now ())
823
- if isLocked {
824
- return nil
825
- }
843
+ // Check if spent by unmined, skip if necessary.
844
+ if ! includeSpentByUnmined {
845
+ if existsRawUnminedInput (ns , k ) != nil {
846
+ return nil
847
+ }
848
+ }
826
849
827
- if existsRawUnminedInput (ns , k ) != nil {
828
- // Output is spent by an unmined transaction.
829
- // Skip this k/v pair.
830
- return nil
831
- }
850
+ // Fetch the transaction record to get PkScript and
851
+ // potentially other details.
852
+ var block Block
853
+ err = readUnspentBlock (v , & block )
854
+ if err != nil {
855
+ return err
856
+ }
832
857
833
- err = readUnspentBlock (v , & block )
834
- if err != nil {
835
- return err
836
- }
858
+ // TODO(jrick): reading the entire transaction should
859
+ // be avoidable. Creating the credit only requires the
860
+ // output amount and pkScript.
861
+ rec , err := fetchTxRecord (ns , & op .Hash , & block )
862
+ if err != nil {
863
+ // Wrap the error for context.
864
+ return fmt .Errorf ("unable to retrieve tx %v " +
865
+ "for mined credit: %w" , op .Hash , err )
866
+ }
837
867
838
- blockTime , err := fetchBlockTime (ns , block .Height )
839
- if err != nil {
840
- return err
841
- }
842
- // TODO(jrick): reading the entire transaction should
843
- // be avoidable. Creating the credit only requires the
844
- // output amount and pkScript.
845
- rec , err := fetchTxRecord (ns , & op .Hash , & block )
868
+ txOut := rec .MsgTx .TxOut [op .Index ]
869
+ cred := Credit {
870
+ OutPoint : op ,
871
+ PkScript : txOut .PkScript ,
872
+ }
873
+
874
+ // Populate full details if requested.
875
+ if populateFullDetails {
876
+ blockTime , err := fetchBlockTime (
877
+ ns , block .Height ,
878
+ )
879
+ if err != nil {
880
+ // Wrap the error for context.
881
+ return fmt .Errorf ("unable to fetch " +
882
+ "block time for height %d: %w" ,
883
+ block .Height , err )
884
+ }
885
+
886
+ cred .BlockMeta = BlockMeta {
887
+ Block : block ,
888
+ Time : blockTime ,
889
+ }
890
+ cred .Amount = btcutil .Amount (txOut .Value )
891
+ cred .Received = rec .Received
892
+ cred .FromCoinBase = blockchain .IsCoinBaseTx (
893
+ & rec .MsgTx ,
894
+ )
895
+ }
896
+
897
+ credits = append (credits , cred )
898
+ return nil
899
+ })
846
900
if err != nil {
847
- return fmt .Errorf ("unable to retrieve transaction %v: " +
848
- "%w" , op .Hash , err )
849
- }
850
- txOut := rec .MsgTx .TxOut [op .Index ]
851
- cred := Credit {
852
- OutPoint : op ,
853
- BlockMeta : BlockMeta {
854
- Block : block ,
855
- Time : blockTime ,
856
- },
857
- Amount : btcutil .Amount (txOut .Value ),
858
- PkScript : txOut .PkScript ,
859
- Received : rec .Received ,
860
- FromCoinBase : blockchain .IsCoinBaseTx (& rec .MsgTx ),
861
- }
862
- unspent = append (unspent , cred )
863
- return nil
864
- })
865
- if err != nil {
866
- if _ , ok := err .(Error ); ok {
867
- return nil , err
901
+ // Check if it's already a storeError, otherwise wrap
902
+ // it.
903
+ if _ , ok := err .(Error ); ok {
904
+ return nil , err
905
+ }
906
+
907
+ str := "failed iterating unspent bucket"
908
+ return nil , storeError (ErrDatabase , str , err )
868
909
}
869
- str := "failed iterating unspent bucket"
870
- return nil , storeError (ErrDatabase , str , err )
871
910
}
872
911
873
- err = ns .NestedReadBucket (bucketUnminedCredits ).ForEach (func (k , v []byte ) error {
874
- if err := readCanonicalOutPoint (k , & op ); err != nil {
875
- return err
876
- }
912
+ // Iterate over unmined credits (bucketUnminedCredits).
913
+ unminedCreditsBucket := ns .NestedReadBucket (bucketUnminedCredits )
914
+ if unminedCreditsBucket != nil {
915
+ err := unminedCreditsBucket .ForEach (func (k , v []byte ) error {
916
+ var op wire.OutPoint
917
+ if err := readCanonicalOutPoint (k , & op ); err != nil {
918
+ return err
919
+ }
877
920
878
- // Skip the output if it's locked.
879
- _ , _ , isLocked := isLockedOutput (ns , op , s .clock .Now ())
880
- if isLocked {
881
- return nil
882
- }
921
+ // Check if locked, skip if necessary.
922
+ if ! includeLocked {
923
+ _ , _ , isLocked := isLockedOutput (ns , op , now )
924
+ if isLocked {
925
+ return nil
926
+ }
927
+ }
883
928
884
- if existsRawUnminedInput (ns , k ) != nil {
885
- // Output is spent by an unmined transaction.
886
- // Skip to next unmined credit.
887
- return nil
888
- }
929
+ // Check if spent by unmined, skip if necessary.
930
+ if ! includeSpentByUnmined {
931
+ if existsRawUnminedInput (ns , k ) != nil {
932
+ return nil
933
+ }
934
+ }
889
935
890
- // TODO(jrick): Reading/parsing the entire transaction record
891
- // just for the output amount and script can be avoided.
892
- recVal := existsRawUnmined (ns , op .Hash [:])
893
- var rec TxRecord
894
- err = readRawTxRecord (& op .Hash , recVal , & rec )
895
- if err != nil {
896
- return fmt .Errorf ("unable to retrieve raw transaction " +
897
- "%v: %w" , op .Hash , err )
898
- }
936
+ // Fetch the transaction record to get PkScript and
937
+ // potentially other details.
938
+ recVal := existsRawUnmined (ns , op .Hash [:])
899
939
900
- txOut := rec .MsgTx .TxOut [op .Index ]
901
- cred := Credit {
902
- OutPoint : op ,
903
- BlockMeta : BlockMeta {
904
- Block : Block {Height : - 1 },
905
- },
906
- Amount : btcutil .Amount (txOut .Value ),
907
- PkScript : txOut .PkScript ,
908
- Received : rec .Received ,
909
- FromCoinBase : blockchain .IsCoinBaseTx (& rec .MsgTx ),
910
- }
911
- unspent = append (unspent , cred )
912
- return nil
913
- })
914
- if err != nil {
915
- if _ , ok := err .(Error ); ok {
916
- return nil , err
940
+ // existsRawUnmined should always return a value for a
941
+ // key in bucketUnminedCredits, but check defensively.
942
+ if recVal == nil {
943
+ log .Warnf ("Unmined credit %v points to " +
944
+ "non-existent unmined tx record %v" , op ,
945
+ op .Hash )
946
+
947
+ // Skip this credit as its tx record is missing.
948
+ return nil
949
+ }
950
+
951
+ var rec TxRecord
952
+ err := readRawTxRecord (& op .Hash , recVal , & rec )
953
+ if err != nil {
954
+ // Wrap the error for context.
955
+ return fmt .Errorf ("unable to retrieve raw tx " +
956
+ "%v for unmined credit: %w" , op .Hash ,
957
+ err )
958
+ }
959
+
960
+ txOut := rec .MsgTx .TxOut [op .Index ]
961
+ cred := Credit {
962
+ OutPoint : op ,
963
+ PkScript : txOut .PkScript ,
964
+ }
965
+
966
+ // Populate full details if requested.
967
+ if populateFullDetails {
968
+ cred .BlockMeta = BlockMeta {
969
+ // Unmined height.
970
+ Block : Block {Height : - 1 },
971
+ }
972
+ cred .Amount = btcutil .Amount (txOut .Value )
973
+ cred .Received = rec .Received
974
+ cred .FromCoinBase = blockchain .IsCoinBaseTx (
975
+ & rec .MsgTx ,
976
+ )
977
+ }
978
+
979
+ credits = append (credits , cred )
980
+ return nil
981
+ })
982
+ if err != nil {
983
+ // Check if it's already a storeError, otherwise wrap
984
+ // it.
985
+ if _ , ok := err .(Error ); ok {
986
+ return nil , err
987
+ }
988
+ str := "failed iterating unmined credits bucket"
989
+ return nil , storeError (ErrDatabase , str , err )
917
990
}
918
- str := "failed iterating unmined credits bucket"
919
- return nil , storeError (ErrDatabase , str , err )
920
991
}
921
992
922
- return unspent , nil
993
+ return credits , nil
994
+ }
995
+
996
+ // OutputsToWatch returns a list of outputs to monitor during the wallet's
997
+ // startup. The returned items are similar to UnspentOutputs, exccept the
998
+ // locked outputs and unmined credits are also returned here. In addition, we
999
+ // only set the field `OutPoint` and `PkScript` for the `Credit`, as these are
1000
+ // the only fields used during the rescan.
1001
+ func (s * Store ) OutputsToWatch (ns walletdb.ReadBucket ) ([]Credit , error ) {
1002
+ // OutputsToWatch needs all known outputs (mined and unmined),
1003
+ // including locked ones and those spent by other unmined txs,
1004
+ // but only requires minimal details (OutPoint, PkScript).
1005
+ return s .fetchCredits (ns , true , true , false )
1006
+ }
1007
+
1008
+ // UnspentOutputs returns all unspent received transaction outputs.
1009
+ // The order is undefined.
1010
+ func (s * Store ) UnspentOutputs (ns walletdb.ReadBucket ) ([]Credit , error ) {
1011
+ // UnspentOutputs needs outputs that are actually spendable:
1012
+ // - Not locked.
1013
+ // - Not spent by an unmined transaction.
1014
+ // It requires full credit details.
1015
+ return s .fetchCredits (ns , false , false , true )
923
1016
}
924
1017
925
1018
// Balance returns the spendable wallet balance (total value of all unspent
0 commit comments