mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2025-11-05 11:56:37 +00:00
optimize and reduce electrum server rpc calls #2
This commit is contained in:
parent
e3138f3392
commit
c77f52f7f6
4 changed files with 135 additions and 57 deletions
|
|
@ -37,6 +37,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ElectrumServer {
|
||||
private static final Logger log = LoggerFactory.getLogger(ElectrumServer.class);
|
||||
|
|
@ -224,6 +225,11 @@ public class ElectrumServer {
|
|||
}
|
||||
|
||||
private static String getScriptHashStatus(String scriptHash, WalletNode walletNode) {
|
||||
List<ScriptHashTx> scriptHashTxes = getScriptHashes(scriptHash, walletNode);
|
||||
return getScriptHashStatus(scriptHashTxes);
|
||||
}
|
||||
|
||||
private static List<ScriptHashTx> getScriptHashes(String scriptHash, WalletNode walletNode) {
|
||||
List<BlockTransactionHashIndex> txos = new ArrayList<>(walletNode.getTransactionOutputs());
|
||||
txos.addAll(walletNode.getTransactionOutputs().stream().filter(BlockTransactionHashIndex::isSpent).map(BlockTransactionHashIndex::getSpentBy).collect(Collectors.toList()));
|
||||
Set<Sha256Hash> unique = new HashSet<>(txos.size());
|
||||
|
|
@ -246,10 +252,15 @@ public class ElectrumServer {
|
|||
sameHeightTxioScriptHashes.add(scriptHash);
|
||||
return 0;
|
||||
});
|
||||
if(!txos.isEmpty()) {
|
||||
|
||||
return txos.stream().map(txo -> new ScriptHashTx(txo.getHeight(), txo.getHashAsString(), txo.getFee())).toList();
|
||||
}
|
||||
|
||||
private static String getScriptHashStatus(List<ScriptHashTx> scriptHashTxes) {
|
||||
if(!scriptHashTxes.isEmpty()) {
|
||||
StringBuilder scriptHashStatus = new StringBuilder();
|
||||
for(BlockTransactionHashIndex txo : txos) {
|
||||
scriptHashStatus.append(txo.getHash().toString()).append(":").append(txo.getHeight()).append(":");
|
||||
for(ScriptHashTx scriptHashTx : scriptHashTxes) {
|
||||
scriptHashStatus.append(scriptHashTx.tx_hash).append(":").append(scriptHashTx.height).append(":");
|
||||
}
|
||||
|
||||
return Utils.bytesToHex(Sha256Hash.hash(scriptHashStatus.toString().getBytes(StandardCharsets.UTF_8)));
|
||||
|
|
@ -393,10 +404,12 @@ public class ElectrumServer {
|
|||
|
||||
public void getReferences(Wallet wallet, Collection<WalletNode> nodes, Map<WalletNode, Set<BlockTransactionHash>> nodeTransactionMap, int startIndex) throws ServerException {
|
||||
try {
|
||||
Map<WalletNode, ScriptHashTx[]> nodeHashHistory = new LinkedHashMap<>(nodes.size());
|
||||
Map<String, String> pathScriptHashes = new LinkedHashMap<>(nodes.size());
|
||||
for(WalletNode node : nodes) {
|
||||
if(node.getIndex() >= startIndex) {
|
||||
pathScriptHashes.put(node.getDerivationPath(), getScriptHash(node));
|
||||
nodeHashHistory.put(node, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -404,6 +417,32 @@ public class ElectrumServer {
|
|||
return;
|
||||
}
|
||||
|
||||
//Optimistic optimization for confirming transactions by matching against the script hash status should all mempool transactions confirm at the current block height
|
||||
for(Map.Entry<WalletNode, ScriptHashTx[]> entry : nodeHashHistory.entrySet()) {
|
||||
WalletNode node = entry.getKey();
|
||||
String scriptHash = pathScriptHashes.get(node.getDerivationPath());
|
||||
List<String> statuses = subscribedScriptHashes.get(scriptHash);
|
||||
|
||||
if(statuses != null && !statuses.isEmpty() && AppServices.getCurrentBlockHeight() != null &&
|
||||
node.getTransactionOutputs().stream().flatMap(txo -> txo.isSpent() ? Stream.of(txo, txo.getSpentBy()) : Stream.of(txo))
|
||||
.anyMatch(txo -> txo.getHeight() <= 0)) {
|
||||
List<ScriptHashTx> scriptHashTxes = getScriptHashes(scriptHash, node);
|
||||
for(ScriptHashTx scriptHashTx : scriptHashTxes) {
|
||||
if(scriptHashTx.height <= 0) {
|
||||
scriptHashTx.height = AppServices.getCurrentBlockHeight();
|
||||
scriptHashTx.fee = 0;
|
||||
}
|
||||
}
|
||||
|
||||
String status = getScriptHashStatus(scriptHashTxes);
|
||||
if(Objects.equals(status, statuses.getLast())) {
|
||||
entry.setValue(scriptHashTxes.toArray(new ScriptHashTx[0]));
|
||||
pathScriptHashes.remove(node.getDerivationPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!pathScriptHashes.isEmpty()) {
|
||||
//Even if we have some successes, failure to retrieve all references will result in an incomplete wallet history. Don't proceed if that's the case.
|
||||
Map<String, ScriptHashTx[]> result = electrumServerRpc.getScriptHashHistory(getTransport(), wallet, pathScriptHashes, true);
|
||||
|
||||
|
|
@ -413,6 +452,13 @@ public class ElectrumServer {
|
|||
Optional<WalletNode> optionalNode = nodes.stream().filter(n -> n.getDerivationPath().equals(path)).findFirst();
|
||||
if(optionalNode.isPresent()) {
|
||||
WalletNode node = optionalNode.get();
|
||||
nodeHashHistory.put(node, txes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(WalletNode node : nodeHashHistory.keySet()) {
|
||||
ScriptHashTx[] txes = nodeHashHistory.get(node);
|
||||
|
||||
//Some servers can return the same tx as multiple ScriptHashTx entries with different heights. Take the highest height only
|
||||
Set<BlockTransactionHash> references = Arrays.stream(txes).map(ScriptHashTx::getBlockchainTransactionHash)
|
||||
|
|
@ -446,7 +492,6 @@ public class ElectrumServer {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ElectrumServerRpcException e) {
|
||||
throw new ServerException(e.getMessage(), e.getCause());
|
||||
} catch (Exception e) {
|
||||
|
|
@ -1539,6 +1584,7 @@ public class ElectrumServer {
|
|||
private final Sha256Hash txId;
|
||||
private final Set<WalletNode> nodes;
|
||||
private final IntegerProperty iterationCount = new SimpleIntegerProperty(0);
|
||||
private boolean cancelled;
|
||||
|
||||
public TransactionMempoolService(Wallet wallet, Sha256Hash txId, Set<WalletNode> nodes) {
|
||||
this.wallet = wallet;
|
||||
|
|
@ -1554,6 +1600,22 @@ public class ElectrumServer {
|
|||
return iterationCount;
|
||||
}
|
||||
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
this.cancelled = false;
|
||||
super.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
this.cancelled = true;
|
||||
return super.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Task<Set<String>> createTask() {
|
||||
return new Task<>() {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,14 @@ class ScriptHashTx {
|
|||
public String tx_hash;
|
||||
public long fee;
|
||||
|
||||
public ScriptHashTx() {}
|
||||
|
||||
public ScriptHashTx(int height, String tx_hash, long fee) {
|
||||
this.height = height;
|
||||
this.tx_hash = tx_hash;
|
||||
this.fee = fee;
|
||||
}
|
||||
|
||||
public BlockTransactionHash getBlockchainTransactionHash() {
|
||||
Sha256Hash hash = Sha256Hash.wrap(tx_hash);
|
||||
return new BlockTransaction(hash, height, null, fee, null);
|
||||
|
|
|
|||
|
|
@ -1176,7 +1176,7 @@ public class HeadersController extends TransactionFormController implements Init
|
|||
Platform.runLater(() -> EventManager.get().post(new WalletNodeHistoryChangedEvent(scriptHashes.iterator().next())));
|
||||
}
|
||||
|
||||
if(transactionMempoolService.getIterationCount() > 3) {
|
||||
if(transactionMempoolService.getIterationCount() > 3 && !transactionMempoolService.isCancelled()) {
|
||||
transactionMempoolService.cancel();
|
||||
broadcastProgressBar.setProgress(0);
|
||||
log.error("Timeout searching for broadcasted transaction");
|
||||
|
|
@ -1185,11 +1185,13 @@ public class HeadersController extends TransactionFormController implements Init
|
|||
}
|
||||
});
|
||||
transactionMempoolService.setOnFailed(mempoolWorkerStateEvent -> {
|
||||
if(!transactionMempoolService.isCancelled()) {
|
||||
transactionMempoolService.cancel();
|
||||
broadcastProgressBar.setProgress(0);
|
||||
log.error("Timeout searching for broadcasted transaction");
|
||||
AppServices.showErrorDialog("Timeout searching for broadcasted transaction", "The transaction was broadcast but the server did not indicate it had entered the mempool. It is safe to try broadcasting again.");
|
||||
broadcastButton.setDisable(false);
|
||||
}
|
||||
});
|
||||
transactionMempoolService.start();
|
||||
} else {
|
||||
|
|
@ -1332,6 +1334,14 @@ public class HeadersController extends TransactionFormController implements Init
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
super.close();
|
||||
if(transactionMempoolService != null) {
|
||||
transactionMempoolService.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void transactionChanged(TransactionChangedEvent event) {
|
||||
if(headersForm.getTransaction().equals(event.getTransaction())) {
|
||||
|
|
@ -1577,24 +1587,18 @@ public class HeadersController extends TransactionFormController implements Init
|
|||
if(transactionMempoolService != null) {
|
||||
transactionMempoolService.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void walletHistoryFinished(WalletHistoryFinishedEvent event) {
|
||||
if(headersForm.getSigningWallet() != null && headersForm.getSigningWallet().equals(event.getWallet()) && headersForm.isTransactionFinalized()) {
|
||||
Sha256Hash txid = headersForm.getTransaction().getTxId();
|
||||
ElectrumServer.TransactionReferenceService transactionReferenceService = new ElectrumServer.TransactionReferenceService(Set.of(txid), event.getScriptHash());
|
||||
transactionReferenceService.setOnSucceeded(successEvent -> {
|
||||
Map<Sha256Hash, BlockTransaction> transactionMap = transactionReferenceService.getValue();
|
||||
BlockTransaction blockTransaction = transactionMap.get(txid);
|
||||
if(blockTransaction != null) {
|
||||
BlockTransaction blockTransaction = event.getWallet().getWalletTransaction(txid);
|
||||
if(blockTransaction != null && !blockTransaction.equals(headersForm.getBlockTransaction())) {
|
||||
headersForm.setBlockTransaction(blockTransaction);
|
||||
updateBlockchainForm(blockTransaction, AppServices.getCurrentBlockHeight());
|
||||
}
|
||||
EventManager.get().post(new TransactionReferencesFinishedEvent(headersForm.getTransaction(), blockTransaction));
|
||||
});
|
||||
transactionReferenceService.setOnFailed(failEvent -> {
|
||||
log.error("Could not update block transaction", failEvent.getSource().getException());
|
||||
EventManager.get().post(new TransactionReferencesFailedEvent(headersForm.getTransaction(), failEvent.getSource().getException()));
|
||||
});
|
||||
EventManager.get().post(new TransactionReferencesStartedEvent(headersForm.getTransaction()));
|
||||
transactionReferenceService.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,11 +76,15 @@ public abstract class TransactionFormController extends BaseController {
|
|||
});
|
||||
}
|
||||
|
||||
public void close() {
|
||||
EventManager.get().unregister(this);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void transactionTabsClosed(TransactionTabsClosedEvent event) {
|
||||
for(TransactionTabData tabData : event.getClosedTransactionTabData()) {
|
||||
if(tabData.getTransactionData() == getTransactionForm().getTransactionData()) {
|
||||
EventManager.get().unregister(this);
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue