forked from Qortal/qortal
storing blockchain data in a cache to reduce redundant RPCs to the ElectrumX servers
This commit is contained in:
parent
7f5692eac0
commit
acc37cef0e
@ -55,6 +55,13 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
|
||||
protected Coin feePerKb;
|
||||
|
||||
/**
|
||||
* Blockchain Cache
|
||||
*
|
||||
* To store blockchain data and reduce redundant RPCs to the ElectrumX servers
|
||||
*/
|
||||
private final BlockchainCache blockchainCache = new BlockchainCache();
|
||||
|
||||
// Constructors and instance
|
||||
|
||||
protected Bitcoiny(BitcoinyBlockchainProvider blockchainProvider, Context bitcoinjContext, String currencyCode, Coin feePerKb) {
|
||||
@ -509,8 +516,22 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
if (!historicTransactionHashes.isEmpty()) {
|
||||
areAllKeysUnused = false;
|
||||
|
||||
for (TransactionHash transactionHash : historicTransactionHashes)
|
||||
walletTransactions.add(this.getTransaction(transactionHash.txHash));
|
||||
for (TransactionHash transactionHash : historicTransactionHashes) {
|
||||
|
||||
Optional<BitcoinyTransaction> walletTransaction
|
||||
= this.blockchainCache.getTransactionByHash( transactionHash.txHash );
|
||||
|
||||
// if the wallet transaction is already cached
|
||||
if(walletTransaction.isPresent() ) {
|
||||
walletTransactions.add( walletTransaction.get() );
|
||||
}
|
||||
// otherwise get the transaction from the blockchain server
|
||||
else {
|
||||
BitcoinyTransaction transaction = getTransaction(transactionHash.txHash);
|
||||
walletTransactions.add( transaction );
|
||||
this.blockchainCache.addTransactionByHash(transactionHash.txHash, transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -602,17 +623,25 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
for (; ki < keys.size(); ++ki) {
|
||||
DeterministicKey dKey = keys.get(ki);
|
||||
|
||||
// Check for transactions
|
||||
Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH);
|
||||
keySet.add(address.toString());
|
||||
byte[] script = ScriptBuilder.createOutputScript(address).getProgram();
|
||||
|
||||
// Ask for transaction history - if it's empty then key has never been used
|
||||
List<TransactionHash> historicTransactionHashes = this.getAddressTransactions(script, true);
|
||||
|
||||
if (!historicTransactionHashes.isEmpty()) {
|
||||
// if the key already has a verified transaction history
|
||||
if( this.blockchainCache.keyHasHistory( dKey ) ){
|
||||
areAllKeysUnused = false;
|
||||
}
|
||||
else {
|
||||
// Check for transactions
|
||||
byte[] script = ScriptBuilder.createOutputScript(address).getProgram();
|
||||
|
||||
// Ask for transaction history - if it's empty then key has never been used
|
||||
List<TransactionHash> historicTransactionHashes = this.getAddressTransactions(script, true);
|
||||
|
||||
if (!historicTransactionHashes.isEmpty()) {
|
||||
areAllKeysUnused = false;
|
||||
this.blockchainCache.addKeyWithHistory(dKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (areAllKeysUnused) {
|
||||
@ -667,19 +696,26 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
do {
|
||||
boolean areAllKeysUnused = true;
|
||||
|
||||
for (; ki < keys.size(); ++ki) {
|
||||
for (; areAllKeysUnused && ki < keys.size(); ++ki) {
|
||||
DeterministicKey dKey = keys.get(ki);
|
||||
|
||||
// Check for transactions
|
||||
Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH);
|
||||
byte[] script = ScriptBuilder.createOutputScript(address).getProgram();
|
||||
|
||||
// Ask for transaction history - if it's empty then key has never been used
|
||||
List<TransactionHash> historicTransactionHashes = this.getAddressTransactions(script, false);
|
||||
|
||||
if (!historicTransactionHashes.isEmpty()) {
|
||||
// if the key already has a verified transaction history
|
||||
if( this.blockchainCache.keyHasHistory(dKey)) {
|
||||
areAllKeysUnused = false;
|
||||
}
|
||||
else {
|
||||
// Check for transactions
|
||||
Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH);
|
||||
byte[] script = ScriptBuilder.createOutputScript(address).getProgram();
|
||||
|
||||
// Ask for transaction history - if it's empty then key has never been used
|
||||
List<TransactionHash> historicTransactionHashes = this.getAddressTransactions(script, true);
|
||||
|
||||
if (!historicTransactionHashes.isEmpty()) {
|
||||
areAllKeysUnused = false;
|
||||
this.blockchainCache.addKeyWithHistory(dKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (areAllKeysUnused) {
|
||||
|
91
src/main/java/org/qortal/crosschain/BlockchainCache.java
Normal file
91
src/main/java/org/qortal/crosschain/BlockchainCache.java
Normal file
@ -0,0 +1,91 @@
|
||||
package org.qortal.crosschain;
|
||||
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
/**
|
||||
* Class BlockchainCache
|
||||
*
|
||||
* Cache blockchain information to reduce redundant RPCs to the ElectrumX servers.
|
||||
*/
|
||||
public class BlockchainCache {
|
||||
|
||||
/**
|
||||
* Keys With History
|
||||
*
|
||||
* Deterministic Keys with any transaction history.
|
||||
*/
|
||||
private Queue<DeterministicKey> keysWithHistory = new ConcurrentLinkedDeque<>();
|
||||
|
||||
/**
|
||||
* Transactions By Hash
|
||||
*
|
||||
* Transaction Hash -> Transaction
|
||||
*/
|
||||
private ConcurrentHashMap<String, BitcoinyTransaction> transactionByHash = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Key History Limit
|
||||
*
|
||||
* If this limit is reached, the cache will be reduced.
|
||||
*/
|
||||
private static final int KEY_HISTORY_LIMIT = 10000;
|
||||
|
||||
/**
|
||||
* Transaction Limit
|
||||
*
|
||||
* If this limit is reached, the cache will be cleared.
|
||||
*/
|
||||
private static final int TRANSACTION_LIMIT = 10000;
|
||||
|
||||
/**
|
||||
* Add Key With History
|
||||
*
|
||||
* @param key a deterministic key with a verified history
|
||||
*/
|
||||
public void addKeyWithHistory(DeterministicKey key) {
|
||||
|
||||
if( this.keysWithHistory.size() > KEY_HISTORY_LIMIT ) this.keysWithHistory.remove();
|
||||
|
||||
this.keysWithHistory.add(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Key Has History?
|
||||
*
|
||||
* @param key the deterministic key
|
||||
*
|
||||
* @return true if the key has a history, otherwise false
|
||||
*/
|
||||
public boolean keyHasHistory( DeterministicKey key ) {
|
||||
return this.keysWithHistory.contains(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Transaction By Hash
|
||||
*
|
||||
* @param hash the transaction hash
|
||||
* @param transaction the transaction
|
||||
*/
|
||||
public void addTransactionByHash( String hash, BitcoinyTransaction transaction ) {
|
||||
|
||||
if( this.transactionByHash.size() > TRANSACTION_LIMIT ) this.transactionByHash.clear();
|
||||
|
||||
this.transactionByHash.put(hash, transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Transaction By Hash
|
||||
*
|
||||
* @param hash the transaction hash
|
||||
*
|
||||
* @return the transaction, empty if the hash is not in the cache
|
||||
*/
|
||||
public Optional<BitcoinyTransaction> getTransactionByHash( String hash ) {
|
||||
return Optional.ofNullable( this.transactionByHash.get(hash) );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user