@@ -5,6 +5,8 @@ import { BaseModel } from './base';
5
5
import { IWallet } from './wallet' ;
6
6
import { TransactionStorage } from './transaction' ;
7
7
import { StorageService } from '../services/storage' ;
8
+ import { partition } from '../utils/partition' ;
9
+ import { Readable , Transform , Writable } from 'stream' ;
8
10
9
11
export type IWalletAddress = {
10
12
wallet : ObjectID ;
@@ -38,60 +40,162 @@ export class WalletAddressModel extends BaseModel<IWalletAddress> {
38
40
const { wallet, addresses } = params ;
39
41
const { chain, network } = wallet ;
40
42
41
- const unprocessedAddresses : Array < string > = [ ] ;
42
- for ( let address of addresses ) {
43
- const updatedAddress = await this . collection . findOneAndUpdate (
44
- {
45
- wallet : wallet . _id ,
46
- address : address ,
47
- chain,
48
- network
49
- } ,
50
- { $setOnInsert : { wallet : wallet . _id , address : address , chain, network } } ,
51
- { returnOriginal : false , upsert : true }
52
- ) ;
53
- if ( ! updatedAddress . value ! . processed ) {
54
- unprocessedAddresses . push ( address ) ;
55
- await CoinStorage . collection . updateMany ( { chain, network, address } , { $addToSet : { wallets : wallet . _id } } ) ;
43
+ class AddressInputStream extends Readable {
44
+ addressBatches : string [ ] [ ] ;
45
+ index : number ;
46
+ constructor ( ) {
47
+ super ( { objectMode : true } ) ;
48
+ this . addressBatches = partition ( addresses , 1000 ) ;
49
+ this . index = 0 ;
50
+ }
51
+ _read ( ) {
52
+ if ( this . index < this . addressBatches . length ) {
53
+ this . push ( this . addressBatches [ this . index ] ) ;
54
+ this . index ++ ;
55
+ }
56
+ else {
57
+ this . push ( null ) ;
58
+ }
56
59
}
57
60
}
58
61
59
- let coinStream = CoinStorage . collection
60
- . find ( { wallets : wallet . _id , 'wallets.0' : { $exists : true } } )
61
- . project ( { spentTxid : 1 , mintTxid : 1 , address : 1 } )
62
- . addCursorFlag ( 'noCursorTimeout' , true ) ;
63
- let txids = { } ;
64
- coinStream . on ( 'data' , ( coin : ICoin ) => {
65
- coinStream . pause ( ) ;
66
- if ( ! unprocessedAddresses . includes ( coin . address ) ) {
67
- return coinStream . resume ( ) ;
62
+ class FilterExistingAddressesStream extends Transform {
63
+ constructor ( ) {
64
+ super ( { objectMode : true } ) ;
68
65
}
69
- if ( ! txids [ coin . mintTxid ] ) {
70
- TransactionStorage . collection . updateMany (
71
- { txid : coin . mintTxid , network, chain } ,
72
- { $addToSet : { wallets : wallet . _id } }
73
- ) ;
66
+ async _transform ( addressBatch , _ , callback ) {
67
+ let exists = ( await WalletAddressStorage . collection . find ( { wallet : wallet . _id , address : { $in : addressBatch } } ) . toArray ( ) )
68
+ . filter ( walletAddress => walletAddress . processed )
69
+ . map ( walletAddress => walletAddress . address ) ;
70
+ callback ( null , addressBatch . filter ( address => {
71
+ return ! exists . includes ( address ) ;
72
+ } ) ) ;
74
73
}
75
- txids [ coin . mintTxid ] = true ;
76
- if ( coin . spentTxid && ! txids [ coin . spentTxid ] ) {
77
- TransactionStorage . collection . updateMany (
78
- { txid : coin . spentTxid , network, chain } ,
74
+ }
75
+
76
+ class AddNewAddressesStream extends Transform {
77
+ constructor ( ) {
78
+ super ( { objectMode : true } ) ;
79
+ }
80
+ async _transform ( addressBatch , _ , callback ) {
81
+ if ( ! addressBatch . length ) {
82
+ return callback ( ) ;
83
+ }
84
+ await WalletAddressStorage . collection . bulkWrite ( addressBatch . map ( address => {
85
+ return {
86
+ updateOne : {
87
+ filter : { chain, network, wallet : wallet . _id , address } ,
88
+ update : { $setOnInsert : { chain, network, wallet : wallet . _id , address } } ,
89
+ upsert : true
90
+ }
91
+ }
92
+ } ) ) , { ordered : false } ;
93
+ callback ( null , addressBatch ) ;
94
+ }
95
+ }
96
+
97
+ class UpdateCoinsStream extends Transform {
98
+ constructor ( ) {
99
+ super ( { objectMode : true } ) ;
100
+ }
101
+ async _transform ( addressBatch , _ , callback ) {
102
+ if ( ! addressBatch . length ) {
103
+ return callback ( ) ;
104
+ }
105
+ await WalletAddressStorage . collection . bulkWrite ( addressBatch . map ( address => {
106
+ return {
107
+ updateMany : {
108
+ filter : { chain, network, address } ,
109
+ update : { $addToSet : { wallets : wallet . _id } }
110
+ }
111
+ } ;
112
+ } ) , { ordered : false } ) ;
113
+ callback ( null , addressBatch ) ;
114
+ }
115
+ }
116
+
117
+ class UpdatedTxidsStream extends Transform {
118
+ txids : { [ key : string ] : boolean } ;
119
+ constructor ( ) {
120
+ super ( { objectMode : true } ) ;
121
+ this . txids = { } ;
122
+ }
123
+ async _transform ( addressBatch , _ , callback ) {
124
+ if ( ! addressBatch . length ) {
125
+ return callback ( ) ;
126
+ }
127
+ const coinStream = CoinStorage . collection . find ( { chain, network, address : { $in : addressBatch } } ) ;
128
+ coinStream . on ( 'data' , ( coin : ICoin ) => {
129
+ if ( ! this . txids [ coin . mintTxid ] ) {
130
+ this . txids [ coin . mintTxid ] = true ;
131
+ this . push ( { txid : coin . mintTxid } ) ;
132
+ }
133
+ if ( ! this . txids [ coin . spentTxid ] ) {
134
+ this . txids [ coin . spentTxid ] = true ;
135
+ this . push ( { txid : coin . spentTxid } ) ;
136
+ }
137
+ } ) ;
138
+ coinStream . on ( 'end' , ( ) => {
139
+ callback ( null , { addressBatch} )
140
+ } ) ;
141
+ }
142
+ }
143
+
144
+ class TxUpdaterStream extends Transform {
145
+ constructor ( ) {
146
+ super ( { objectMode : true } ) ;
147
+ }
148
+ async _transform ( data , _ , callback ) {
149
+ const { txid, addressBatch } = data ;
150
+ if ( addressBatch ) {
151
+ return callback ( null , addressBatch ) ;
152
+ }
153
+ await TransactionStorage . collection . updateMany (
154
+ { chain, network, txid } ,
79
155
{ $addToSet : { wallets : wallet . _id } }
80
156
) ;
157
+ callback ( ) ;
81
158
}
82
- txids [ coin . spentTxid ] = true ;
83
- return coinStream . resume ( ) ;
84
- } ) ;
85
- return new Promise ( resolve => {
86
- coinStream . on ( 'end' , async ( ) => {
87
- for ( const address of unprocessedAddresses ) {
88
- await this . collection . updateOne (
89
- { chain, network, address, wallet : wallet . _id } ,
90
- { $set : { processed : true } }
91
- ) ;
159
+ }
160
+
161
+ class MarkProcessedStream extends Writable {
162
+ constructor ( ) {
163
+ super ( { objectMode : true } ) ;
164
+ }
165
+ async _write ( addressBatch , _ , callback ) {
166
+ if ( ! addressBatch . length ) {
167
+ return callback ( ) ;
92
168
}
93
- resolve ( ) ;
94
- } ) ;
169
+ await WalletAddressStorage . collection . bulkWrite ( addressBatch . map ( address => {
170
+ return {
171
+ updateOne : {
172
+ filter : { chain, network, address, wallet : wallet . _id } ,
173
+ update : { $set : { processed : true } }
174
+ }
175
+ }
176
+ } ) ) ;
177
+ callback ( ) ;
178
+ }
179
+ }
180
+
181
+ const addressInputStream = new AddressInputStream ( ) ;
182
+ const filterExistingAddressesStream = new FilterExistingAddressesStream ( ) ;
183
+ const addNewAddressesStream = new AddNewAddressesStream ( ) ;
184
+ const updateCoinsStream = new UpdateCoinsStream ( ) ;
185
+ const updatedTxidsStream = new UpdatedTxidsStream ( ) ;
186
+ const txUpdaterStream = new TxUpdaterStream ( ) ;
187
+ const markProcessedStream = new MarkProcessedStream ( ) ;
188
+
189
+ addressInputStream
190
+ . pipe ( filterExistingAddressesStream )
191
+ . pipe ( addNewAddressesStream )
192
+ . pipe ( updateCoinsStream )
193
+ . pipe ( updatedTxidsStream )
194
+ . pipe ( txUpdaterStream )
195
+ . pipe ( markProcessedStream ) ;
196
+
197
+ markProcessedStream . on ( 'end' , ( ) => {
198
+ return Promise . resolve ( ) ;
95
199
} ) ;
96
200
}
97
201
}
0 commit comments