• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bernardladenthin / BitcoinAddressFinder / #266

08 Apr 2025 07:49PM UTC coverage: 64.041% (-0.3%) from 64.326%
#266

push

bernardladenthin
Add LMDBPlatformAssume

1138 of 1777 relevant lines covered (64.04%)

0.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.4
/src/main/java/net/ladenthin/bitcoinaddressfinder/ConsumerJava.java
1
// @formatter:off
2
/**
3
 * Copyright 2020 Bernard Ladenthin bernard.ladenthin@gmail.com
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *    http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
// @formatter:on
19
package net.ladenthin.bitcoinaddressfinder;
20

21
import com.google.common.annotations.VisibleForTesting;
22
import java.nio.ByteBuffer;
23
import java.time.Duration;
24
import java.time.temporal.ChronoUnit;
25
import java.util.ArrayList;
26
import java.util.Arrays;
27
import java.util.List;
28
import org.slf4j.Logger;
29
import org.slf4j.LoggerFactory;
30

31
import java.util.concurrent.ExecutorService;
32
import java.util.concurrent.Executors;
33
import java.util.concurrent.Future;
34
import java.util.concurrent.LinkedBlockingQueue;
35
import java.util.concurrent.ScheduledExecutorService;
36
import java.util.concurrent.TimeUnit;
37
import java.util.concurrent.atomic.AtomicBoolean;
38
import java.util.concurrent.atomic.AtomicLong;
39
import java.util.regex.Matcher;
40
import java.util.regex.Pattern;
41
import net.ladenthin.bitcoinaddressfinder.configuration.CConsumerJava;
42
import net.ladenthin.bitcoinaddressfinder.persistence.Persistence;
43
import net.ladenthin.bitcoinaddressfinder.persistence.PersistenceUtils;
44
import net.ladenthin.bitcoinaddressfinder.persistence.lmdb.LMDBPersistence;
45
import org.apache.commons.codec.binary.Hex;
46
import org.bitcoinj.crypto.ECKey;
47
import org.bitcoinj.crypto.MnemonicException;
48

49
public class ConsumerJava implements Consumer {
50

51
    /**
52
     * We assume a queue might be empty after this amount of time.
53
     * If not, some keys in the queue are not checked before shutdow.
54
     */
55
    @VisibleForTesting
56
    static Duration AWAIT_DURATION_QUEUE_EMPTY = Duration.ofMinutes(1);
1✔
57
    
58
    public static final String MISS_PREFIX = "miss: Could not find the address: ";
59
    public static final String HIT_PREFIX = "hit: Found the address: ";
60
    public static final String VANITY_HIT_PREFIX = "vanity pattern match: ";
61
    public static final String HIT_SAFE_PREFIX = "hit: safe log: ";
62

63
    private Logger logger = LoggerFactory.getLogger(this.getClass());
1✔
64

65
    private final KeyUtility keyUtility;
66
    protected final AtomicLong checkedKeys = new AtomicLong();
1✔
67
    protected final AtomicLong checkedKeysSumOfTimeToCheckContains = new AtomicLong();
1✔
68
    protected final AtomicLong emptyConsumer = new AtomicLong();
1✔
69
    protected final AtomicLong hits = new AtomicLong();
1✔
70
    protected long startTime = 0;
1✔
71

72
    protected final CConsumerJava consumerJava;
73
    @VisibleForTesting
1✔
74
    ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
1✔
75

76
    protected Persistence persistence;
77
    private final PersistenceUtils persistenceUtils;
78
    
79
    private final List<Future<Void>> consumers = new ArrayList<>();
1✔
80
    protected final LinkedBlockingQueue<PublicKeyBytes[]> keysQueue;
81
    private final ByteBufferUtility byteBufferUtility = new ByteBufferUtility(true);
1✔
82
    
83
    protected final AtomicLong vanityHits = new AtomicLong();
1✔
84
    private final Pattern vanityPattern;
85
    
86
    @VisibleForTesting
1✔
87
    final AtomicBoolean shouldRun = new AtomicBoolean(true);
88
    
89
    @VisibleForTesting
90
    final ExecutorService consumeKeysExecutorService;
91

92
    protected ConsumerJava(CConsumerJava consumerJava, KeyUtility keyUtility, PersistenceUtils persistenceUtils) {
1✔
93
        this.consumerJava = consumerJava;
1✔
94
        this.keysQueue = new LinkedBlockingQueue<>(consumerJava.queueSize);
1✔
95
        this.keyUtility = keyUtility;
1✔
96
        this.persistenceUtils = persistenceUtils;
1✔
97
        if (consumerJava.enableVanity) {
1✔
98
            this.vanityPattern = Pattern.compile(consumerJava.vanityPattern);
1✔
99
        } else {
100
            vanityPattern = null;
1✔
101
        }
102
        consumeKeysExecutorService = Executors.newFixedThreadPool(consumerJava.threads);
1✔
103
    }
1✔
104

105
    Logger getLogger() {
106
        return logger;
×
107
    }
108
    
109
    void setLogger(Logger logger) {
110
        this.logger = logger;
1✔
111
    }
1✔
112

113
    protected void initLMDB() {
114
        persistence = new LMDBPersistence(consumerJava.lmdbConfigurationReadOnly, persistenceUtils);
1✔
115
        persistence.init();
1✔
116
    }
1✔
117

118
    protected void startStatisticsTimer() {
119
        long period = consumerJava.printStatisticsEveryNSeconds;
1✔
120
        if (period <= 0) {
1✔
121
            throw new IllegalArgumentException("period must be greater than 0.");
1✔
122
        }
123

124
        startTime = System.currentTimeMillis();
1✔
125

126
        scheduledExecutorService.scheduleAtFixedRate(() -> {
1✔
127
            // get transient information
128
            long uptime = Math.max(System.currentTimeMillis() - startTime, 1);
1✔
129

130
            String message = new Statistics().createStatisticsMessage(uptime, checkedKeys.get(), checkedKeysSumOfTimeToCheckContains.get(), emptyConsumer.get(), keysQueue.size(), hits.get());
1✔
131

132
            // log the information
133
            logger.info(message);
1✔
134
        }, period, period, TimeUnit.SECONDS);
1✔
135
    }
1✔
136

137
    @Override
138
    public void startConsumer() {
139
        for (int i = 0; i < consumerJava.threads; i++) {
1✔
140
            consumers.add(consumeKeysExecutorService.submit(
1✔
141
                    () -> {
142
                        consumeKeysRunner();
1✔
143
                        return null;
1✔
144
                    }));
145
        }
146
    }
1✔
147
    
148
    /**
149
     * This method runs in multiple threads.
150
     */
151
    private void consumeKeysRunner() {
152
        logger.info("start consumeKeysRunner");
1✔
153
        ByteBuffer threadLocalReuseableByteBuffer = ByteBuffer.allocateDirect(PublicKeyBytes.HASH160_SIZE);
1✔
154
        
155
        while (shouldRun.get()) {
1✔
156
            if (keysQueue.size() >= consumerJava.queueSize) {
1✔
157
                logger.warn("Attention, queue is full. Please increase queue size.");
1✔
158
            }
159
            try {
160
                consumeKeys(threadLocalReuseableByteBuffer);
1✔
161
                // the consumeKeys method is looped inside, if the method returns it means the queue is empty
162
                emptyConsumer.incrementAndGet();
1✔
163
                Thread.sleep(consumerJava.delayEmptyConsumer);
1✔
164
            } catch (InterruptedException e) {
×
165
                // we need to catch the exception to not break the thread
166
                logger.error("Ignore InterruptedException during Thread.sleep.", e);
×
167
            } catch (Exception e) {
×
168
                // log every Exception because it's hard to debug and we do not break down the thread loop
169
                logger.error("Error in consumeKeysRunner()." , e);
×
170
                e.printStackTrace();
×
171
            }
1✔
172
        }
173
        
174
        if (threadLocalReuseableByteBuffer != null) {
1✔
175
            byteBufferUtility.freeByteBuffer(threadLocalReuseableByteBuffer);
1✔
176
        }
177
        logger.info("end consumeKeysRunner");
1✔
178
    }
1✔
179
    
180
    void consumeKeys(ByteBuffer threadLocalReuseableByteBuffer) throws MnemonicException.MnemonicLengthException {
181
        logger.trace("consumeKeys");
1✔
182
        PublicKeyBytes[] publicKeyBytesArray = keysQueue.poll();
1✔
183
        while (publicKeyBytesArray != null) {
1✔
184
            for (PublicKeyBytes publicKeyBytes : publicKeyBytesArray) {
1✔
185
                if (publicKeyBytes.isInvalid()) {
1✔
186
                    continue;
1✔
187
                }
188
                byte[] hash160Uncompressed = publicKeyBytes.getUncompressedKeyHash();
1✔
189

190
                threadLocalReuseableByteBuffer.rewind();
1✔
191
                threadLocalReuseableByteBuffer.put(hash160Uncompressed);
1✔
192
                threadLocalReuseableByteBuffer.flip();
1✔
193

194
                boolean containsAddressUncompressed = containsAddress(threadLocalReuseableByteBuffer);
1✔
195

196
                byte[] hash160Compressed = publicKeyBytes.getCompressedKeyHash();
1✔
197
                threadLocalReuseableByteBuffer.rewind();
1✔
198
                threadLocalReuseableByteBuffer.put(hash160Compressed);
1✔
199
                threadLocalReuseableByteBuffer.flip();
1✔
200

201
                boolean containsAddressCompressed = containsAddress(threadLocalReuseableByteBuffer);
1✔
202

203
                if (consumerJava.runtimePublicKeyCalculationCheck) {
1✔
204
                    
205
                    ECKey fromPrivateUncompressed = ECKey.fromPrivate(publicKeyBytes.getSecretKey(), false);
1✔
206
                    ECKey fromPrivateCompressed = ECKey.fromPrivate(publicKeyBytes.getSecretKey(), true);
1✔
207
                    
208
                    final byte[] pubKeyUncompressedFromEcKey = fromPrivateUncompressed.getPubKey();
1✔
209
                    final byte[] pubKeyCompressedFromEcKey = fromPrivateCompressed.getPubKey();
1✔
210
                    
211
                    final byte[] hash160UncompressedFromEcKey = fromPrivateUncompressed.getPubKeyHash();
1✔
212
                    final byte[] hash160CompressedFromEcKey = fromPrivateCompressed.getPubKeyHash();
1✔
213
                    
214
                    if (!Arrays.equals(hash160UncompressedFromEcKey, hash160Uncompressed)) {
1✔
215
                        logger.error("fromPrivateUncompressed.getPubKeyHash() != hash160Uncompressed");
1✔
216
                        logger.error("getSecretKey: " + publicKeyBytes.getSecretKey());
1✔
217
                        logger.error("pubKeyUncompressed: " + Hex.encodeHexString(publicKeyBytes.getUncompressed()));
1✔
218
                        logger.error("pubKeyUncompressedFromEcKey: " + Hex.encodeHexString(pubKeyUncompressedFromEcKey));
1✔
219
                        logger.error("hash160Uncompressed: " + Hex.encodeHexString(hash160Uncompressed));
1✔
220
                        logger.error("hash160UncompressedFromEcKey: " + Hex.encodeHexString(hash160UncompressedFromEcKey));
1✔
221
                    }
222
                    
223
                    if (!Arrays.equals(hash160CompressedFromEcKey, hash160Compressed)) {
1✔
224
                        logger.error("fromPrivateCompressed.getPubKeyHash() != hash160Compressed");
1✔
225
                        logger.error("getSecretKey: " + publicKeyBytes.getSecretKey());
1✔
226
                        logger.error("pubKeyCompressed: " + Hex.encodeHexString(publicKeyBytes.getCompressed()));
1✔
227
                        logger.error("pubKeyCompressedFromEcKey: " + Hex.encodeHexString(pubKeyCompressedFromEcKey));
1✔
228
                        logger.error("hash160Compressed: " + Hex.encodeHexString(hash160Compressed));
1✔
229
                        logger.error("hash160CompressedFromEcKey: " + Hex.encodeHexString(hash160CompressedFromEcKey));
1✔
230
                    }
231
                }
232

233
                if (containsAddressUncompressed) {
1✔
234
                    // immediately log the secret
235
                    safeLog(publicKeyBytes, hash160Uncompressed, hash160Compressed);
1✔
236
                    hits.incrementAndGet();
1✔
237
                    ECKey ecKeyUncompressed = ECKey.fromPrivateAndPrecalculatedPublic(publicKeyBytes.getSecretKey().toByteArray(), publicKeyBytes.getUncompressed());
1✔
238
                    String hitMessageUncompressed = HIT_PREFIX + keyUtility.createKeyDetails(ecKeyUncompressed);
1✔
239
                    logger.info(hitMessageUncompressed);
1✔
240
                }
241

242
                if (containsAddressCompressed) {
1✔
243
                    // immediately log the secret
244
                    safeLog(publicKeyBytes, hash160Uncompressed, hash160Compressed);
1✔
245
                    hits.incrementAndGet();
1✔
246
                    ECKey ecKeyCompressed = ECKey.fromPrivateAndPrecalculatedPublic(publicKeyBytes.getSecretKey().toByteArray(), publicKeyBytes.getCompressed());
1✔
247
                    String hitMessageCompressed = HIT_PREFIX + keyUtility.createKeyDetails(ecKeyCompressed);
1✔
248
                    logger.info(hitMessageCompressed);
1✔
249
                }
250

251
                if (consumerJava.enableVanity) {
1✔
252
                    String uncompressedKeyHashAsBase58 = publicKeyBytes.getUncompressedKeyHashAsBase58(keyUtility);
1✔
253
                    Matcher uncompressedKeyHashAsBase58Matcher = vanityPattern.matcher(uncompressedKeyHashAsBase58);
1✔
254
                    if (uncompressedKeyHashAsBase58Matcher.matches()) {
1✔
255
                        // immediately log the secret
256
                        safeLog(publicKeyBytes, hash160Uncompressed, hash160Compressed);
1✔
257
                        vanityHits.incrementAndGet();
1✔
258
                        ECKey ecKeyUncompressed = ECKey.fromPrivateAndPrecalculatedPublic(publicKeyBytes.getSecretKey().toByteArray(), publicKeyBytes.getUncompressed());
1✔
259
                        String vanityHitMessageUncompressed = VANITY_HIT_PREFIX + keyUtility.createKeyDetails(ecKeyUncompressed);
1✔
260
                        logger.info(vanityHitMessageUncompressed);
1✔
261
                    }
262

263
                    String compressedKeyHashAsBase58 = publicKeyBytes.getCompressedKeyHashAsBase58(keyUtility);
1✔
264
                    Matcher compressedKeyHashAsBase58Matcher = vanityPattern.matcher(compressedKeyHashAsBase58);
1✔
265
                    if (compressedKeyHashAsBase58Matcher.matches()) {
1✔
266
                        // immediately log the secret
267
                        safeLog(publicKeyBytes, hash160Uncompressed, hash160Compressed);
1✔
268
                        vanityHits.incrementAndGet();
1✔
269
                        ECKey ecKeyCompressed = ECKey.fromPrivateAndPrecalculatedPublic(publicKeyBytes.getSecretKey().toByteArray(), publicKeyBytes.getCompressed());
1✔
270
                        String vanityHitMessageCompressed = VANITY_HIT_PREFIX + keyUtility.createKeyDetails(ecKeyCompressed);
1✔
271
                        logger.info(vanityHitMessageCompressed);
1✔
272
                    }
273
                }
274

275
                if (!containsAddressUncompressed && !containsAddressCompressed) {
1✔
276
                    if (logger.isTraceEnabled()) {
1✔
277
                        ECKey ecKeyUncompressed = ECKey.fromPrivateAndPrecalculatedPublic(publicKeyBytes.getSecretKey().toByteArray(), publicKeyBytes.getUncompressed());
1✔
278
                        String missMessageUncompressed = MISS_PREFIX + keyUtility.createKeyDetails(ecKeyUncompressed);
1✔
279
                        logger.trace(missMessageUncompressed);
1✔
280

281
                        ECKey ecKeyCompressed = ECKey.fromPrivateAndPrecalculatedPublic(publicKeyBytes.getSecretKey().toByteArray(), publicKeyBytes.getCompressed());
1✔
282
                        String missMessageCompressed = MISS_PREFIX + keyUtility.createKeyDetails(ecKeyCompressed);
1✔
283
                        logger.trace(missMessageCompressed);
1✔
284
                    }
285
                }
286
            }
287
            publicKeyBytesArray = keysQueue.poll();
1✔
288
        }
289
    }
1✔
290
    
291
    /**
292
     * Try to log safe informations which may not thrown an exception.
293
     */
294
    private void safeLog(PublicKeyBytes publicKeyBytes, byte[] hash160Uncompressed, byte[] hash160Compressed) {
295
        logger.info(HIT_SAFE_PREFIX +"publicKeyBytes.getSecretKey(): " + publicKeyBytes.getSecretKey());
1✔
296
        logger.info(HIT_SAFE_PREFIX +"publicKeyBytes.getUncompressed(): " + Hex.encodeHexString(publicKeyBytes.getUncompressed()));
1✔
297
        logger.info(HIT_SAFE_PREFIX +"publicKeyBytes.getCompressed(): " + Hex.encodeHexString(publicKeyBytes.getCompressed()));
1✔
298
        logger.info(HIT_SAFE_PREFIX +"hash160Uncompressed: " + Hex.encodeHexString(hash160Uncompressed));
1✔
299
        logger.info(HIT_SAFE_PREFIX +"hash160Compressed: " + Hex.encodeHexString(hash160Compressed));
1✔
300
    }
1✔
301

302
    private boolean containsAddress(ByteBuffer hash160AsByteBuffer) {
303
        long timeBefore = System.currentTimeMillis();
1✔
304
        if (logger.isTraceEnabled()) {
1✔
305
            logger.trace("Time before persistence.containsAddress: " + timeBefore);
1✔
306
        }
307
        boolean containsAddress = persistence.containsAddress(hash160AsByteBuffer);
1✔
308
        long timeAfter = System.currentTimeMillis();
1✔
309
        long timeDelta = timeAfter - timeBefore;
1✔
310
        checkedKeys.incrementAndGet();
1✔
311
        checkedKeysSumOfTimeToCheckContains.addAndGet(timeDelta);
1✔
312
        if (logger.isTraceEnabled()) {
1✔
313
            logger.trace("Time after persistence.containsAddress: " + timeAfter);
1✔
314
            logger.trace("Time delta: " + timeDelta);
1✔
315
        }
316
        return containsAddress;
1✔
317
    }
318

319
    @Override
320
    public void consumeKeys(PublicKeyBytes[] publicKeyBytes) throws InterruptedException {
321
        if(logger.isDebugEnabled()){
1✔
322
            logger.debug("keysQueue.put(publicKeyBytes) with length: " + publicKeyBytes.length);
1✔
323
        }
324
        
325
        keysQueue.put(publicKeyBytes);
1✔
326
        
327
        if(logger.isDebugEnabled()){
1✔
328
            logger.debug("keysQueue.size(): " + keysQueue.size());
1✔
329
        }
330
    }
1✔
331
    
332
    @Override
333
    public void interrupt() {
334
        logger.debug("start interrupt");
1✔
335
        shouldRun.set(false);
1✔
336
        scheduledExecutorService.shutdown();
1✔
337
        consumeKeysExecutorService.shutdown();
1✔
338
        try {
339
            consumeKeysExecutorService.awaitTermination(AWAIT_DURATION_QUEUE_EMPTY.get(ChronoUnit.SECONDS), TimeUnit.SECONDS);
1✔
340
        } catch (InterruptedException ex) {
×
341
            throw new RuntimeException(ex);
×
342
        }
1✔
343
        persistence.close();
1✔
344
        logger.debug("finish interrupt");
1✔
345
    }
1✔
346
    
347
    @VisibleForTesting
348
    int keysQueueSize() {
349
        return keysQueue.size();
1✔
350
    }
351
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc