mirror of
https://github.com/Qortal/qortal.git
synced 2025-05-01 15:27:51 +00:00
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;
|
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
|
// Constructors and instance
|
||||||
|
|
||||||
protected Bitcoiny(BitcoinyBlockchainProvider blockchainProvider, Context bitcoinjContext, String currencyCode, Coin feePerKb) {
|
protected Bitcoiny(BitcoinyBlockchainProvider blockchainProvider, Context bitcoinjContext, String currencyCode, Coin feePerKb) {
|
||||||
@ -509,8 +516,22 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
|||||||
if (!historicTransactionHashes.isEmpty()) {
|
if (!historicTransactionHashes.isEmpty()) {
|
||||||
areAllKeysUnused = false;
|
areAllKeysUnused = false;
|
||||||
|
|
||||||
for (TransactionHash transactionHash : historicTransactionHashes)
|
for (TransactionHash transactionHash : historicTransactionHashes) {
|
||||||
walletTransactions.add(this.getTransaction(transactionHash.txHash));
|
|
||||||
|
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) {
|
for (; ki < keys.size(); ++ki) {
|
||||||
DeterministicKey dKey = keys.get(ki);
|
DeterministicKey dKey = keys.get(ki);
|
||||||
|
|
||||||
// Check for transactions
|
|
||||||
Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH);
|
Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH);
|
||||||
keySet.add(address.toString());
|
keySet.add(address.toString());
|
||||||
byte[] script = ScriptBuilder.createOutputScript(address).getProgram();
|
|
||||||
|
|
||||||
// Ask for transaction history - if it's empty then key has never been used
|
// if the key already has a verified transaction history
|
||||||
List<TransactionHash> historicTransactionHashes = this.getAddressTransactions(script, true);
|
if( this.blockchainCache.keyHasHistory( dKey ) ){
|
||||||
|
|
||||||
if (!historicTransactionHashes.isEmpty()) {
|
|
||||||
areAllKeysUnused = false;
|
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) {
|
if (areAllKeysUnused) {
|
||||||
@ -667,19 +696,26 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
|||||||
do {
|
do {
|
||||||
boolean areAllKeysUnused = true;
|
boolean areAllKeysUnused = true;
|
||||||
|
|
||||||
for (; ki < keys.size(); ++ki) {
|
for (; areAllKeysUnused && ki < keys.size(); ++ki) {
|
||||||
DeterministicKey dKey = keys.get(ki);
|
DeterministicKey dKey = keys.get(ki);
|
||||||
|
|
||||||
// Check for transactions
|
// if the key already has a verified transaction history
|
||||||
Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH);
|
if( this.blockchainCache.keyHasHistory(dKey)) {
|
||||||
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()) {
|
|
||||||
areAllKeysUnused = false;
|
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) {
|
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…
x
Reference in New Issue
Block a user