mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-05 05:46:44 +00:00
add scheduled service to check mempool after broadcast
This commit is contained in:
parent
ee6d8028f8
commit
038069f6e6
4 changed files with 120 additions and 8 deletions
|
@ -14,6 +14,8 @@ import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent;
|
||||||
import com.sparrowwallet.sparrow.event.TorStatusEvent;
|
import com.sparrowwallet.sparrow.event.TorStatusEvent;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
import com.sparrowwallet.sparrow.wallet.SendController;
|
import com.sparrowwallet.sparrow.wallet.SendController;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.concurrent.ScheduledService;
|
import javafx.concurrent.ScheduledService;
|
||||||
|
@ -663,6 +665,24 @@ public class ElectrumServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<String> getMempoolScriptHashes(Wallet wallet, Sha256Hash txId, Set<WalletNode> transactionNodes) throws ServerException {
|
||||||
|
Map<String, String> pathScriptHashes = new LinkedHashMap<>(transactionNodes.size());
|
||||||
|
for(WalletNode node : transactionNodes) {
|
||||||
|
pathScriptHashes.put(node.getDerivationPath(), getScriptHash(wallet, node));
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> mempoolScriptHashes = new LinkedHashSet<>();
|
||||||
|
Map<String, ScriptHashTx[]> result = electrumServerRpc.getScriptHashHistory(getTransport(), wallet, pathScriptHashes, true);
|
||||||
|
for(String path : result.keySet()) {
|
||||||
|
ScriptHashTx[] txes = result.get(path);
|
||||||
|
if(Arrays.stream(txes).map(ScriptHashTx::getBlockchainTransactionHash).anyMatch(ref -> txId.equals(ref.getHash()) && ref.getHeight() <= 0)) {
|
||||||
|
mempoolScriptHashes.add(pathScriptHashes.get(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mempoolScriptHashes;
|
||||||
|
}
|
||||||
|
|
||||||
public static Map<String, WalletNode> getAllScriptHashes(Wallet wallet) {
|
public static Map<String, WalletNode> getAllScriptHashes(Wallet wallet) {
|
||||||
Map<String, WalletNode> scriptHashes = new HashMap<>();
|
Map<String, WalletNode> scriptHashes = new HashMap<>();
|
||||||
List<KeyPurpose> purposes = List.of(KeyPurpose.RECEIVE, KeyPurpose.CHANGE);
|
List<KeyPurpose> purposes = List.of(KeyPurpose.RECEIVE, KeyPurpose.CHANGE);
|
||||||
|
@ -902,6 +922,38 @@ public class ElectrumServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class TransactionMempoolService extends ScheduledService<Set<String>> {
|
||||||
|
private final Wallet wallet;
|
||||||
|
private final Sha256Hash txId;
|
||||||
|
private final Set<WalletNode> nodes;
|
||||||
|
private final IntegerProperty iterationCount = new SimpleIntegerProperty(0);
|
||||||
|
|
||||||
|
public TransactionMempoolService(Wallet wallet, Sha256Hash txId, Set<WalletNode> nodes) {
|
||||||
|
this.wallet = wallet;
|
||||||
|
this.txId = txId;
|
||||||
|
this.nodes = nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIterationCount() {
|
||||||
|
return iterationCount.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntegerProperty iterationCountProperty() {
|
||||||
|
return iterationCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Task<Set<String>> createTask() {
|
||||||
|
return new Task<>() {
|
||||||
|
protected Set<String> call() throws ServerException {
|
||||||
|
iterationCount.set(iterationCount.get() + 1);
|
||||||
|
ElectrumServer electrumServer = new ElectrumServer();
|
||||||
|
return electrumServer.getMempoolScriptHashes(wallet, txId, nodes);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class TransactionReferenceService extends Service<Map<Sha256Hash, BlockTransaction>> {
|
public static class TransactionReferenceService extends Service<Map<Sha256Hash, BlockTransaction>> {
|
||||||
private final Set<Sha256Hash> references;
|
private final Set<Sha256Hash> references;
|
||||||
private String scriptHash;
|
private String scriptHash;
|
||||||
|
|
|
@ -36,6 +36,7 @@ import javafx.scene.layout.VBox;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.stage.Modality;
|
import javafx.stage.Modality;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import javafx.util.Duration;
|
||||||
import org.controlsfx.glyphfont.Glyph;
|
import org.controlsfx.glyphfont.Glyph;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -202,6 +203,8 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
@FXML
|
@FXML
|
||||||
private Button payjoinButton;
|
private Button payjoinButton;
|
||||||
|
|
||||||
|
private ElectrumServer.TransactionMempoolService transactionMempoolService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
EventManager.get().register(this);
|
EventManager.get().register(this);
|
||||||
|
@ -791,7 +794,31 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
|
|
||||||
ElectrumServer.BroadcastTransactionService broadcastTransactionService = new ElectrumServer.BroadcastTransactionService(headersForm.getTransaction());
|
ElectrumServer.BroadcastTransactionService broadcastTransactionService = new ElectrumServer.BroadcastTransactionService(headersForm.getTransaction());
|
||||||
broadcastTransactionService.setOnSucceeded(workerStateEvent -> {
|
broadcastTransactionService.setOnSucceeded(workerStateEvent -> {
|
||||||
//Do nothing and wait for WalletNodeHistoryChangedEvent to indicate tx is in mempool
|
//Although we wait for WalletNodeHistoryChangedEvent to indicate tx is in mempool, start a scheduled service to check the script hashes should notifications fail
|
||||||
|
if(headersForm.getSigningWallet() != null) {
|
||||||
|
if(transactionMempoolService != null) {
|
||||||
|
transactionMempoolService.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionMempoolService = new ElectrumServer.TransactionMempoolService(headersForm.getSigningWallet(), headersForm.getTransaction().getTxId(), headersForm.getSigningWalletNodes());
|
||||||
|
transactionMempoolService.setDelay(Duration.seconds(5));
|
||||||
|
transactionMempoolService.setPeriod(Duration.seconds(10));
|
||||||
|
transactionMempoolService.setOnSucceeded(mempoolWorkerStateEvent -> {
|
||||||
|
Set<String> scriptHashes = transactionMempoolService.getValue();
|
||||||
|
if(!scriptHashes.isEmpty()) {
|
||||||
|
Platform.runLater(() -> EventManager.get().post(new WalletNodeHistoryChangedEvent(scriptHashes.iterator().next())));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(transactionMempoolService.getIterationCount() > 3) {
|
||||||
|
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 register it in the mempool. It is safe to try broadcasting again.");
|
||||||
|
broadcastButton.setDisable(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
transactionMempoolService.start();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
broadcastTransactionService.setOnFailed(workerStateEvent -> {
|
broadcastTransactionService.setOnFailed(workerStateEvent -> {
|
||||||
broadcastProgressBar.setProgress(0);
|
broadcastProgressBar.setProgress(0);
|
||||||
|
@ -1022,6 +1049,10 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void walletNodeHistoryChanged(WalletNodeHistoryChangedEvent event) {
|
public void walletNodeHistoryChanged(WalletNodeHistoryChangedEvent event) {
|
||||||
if(headersForm.getSigningWallet() != null && event.getWalletNode(headersForm.getSigningWallet()) != null && headersForm.isTransactionFinalized()) {
|
if(headersForm.getSigningWallet() != null && event.getWalletNode(headersForm.getSigningWallet()) != null && headersForm.isTransactionFinalized()) {
|
||||||
|
if(transactionMempoolService != null) {
|
||||||
|
transactionMempoolService.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
Sha256Hash txid = headersForm.getTransaction().getTxId();
|
Sha256Hash txid = headersForm.getTransaction().getTxId();
|
||||||
ElectrumServer.TransactionReferenceService transactionReferenceService = new ElectrumServer.TransactionReferenceService(Set.of(txid), event.getScriptHash());
|
ElectrumServer.TransactionReferenceService transactionReferenceService = new ElectrumServer.TransactionReferenceService(Set.of(txid), event.getScriptHash());
|
||||||
transactionReferenceService.setOnSucceeded(successEvent -> {
|
transactionReferenceService.setOnSucceeded(successEvent -> {
|
||||||
|
|
|
@ -1,21 +1,18 @@
|
||||||
package com.sparrowwallet.sparrow.transaction;
|
package com.sparrowwallet.sparrow.transaction;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
import com.sparrowwallet.drongo.protocol.*;
|
||||||
import com.sparrowwallet.drongo.protocol.TransactionSignature;
|
|
||||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.wallet.BlockTransaction;
|
import com.sparrowwallet.drongo.wallet.BlockTransaction;
|
||||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.drongo.wallet.WalletNode;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableMap;
|
import javafx.collections.ObservableMap;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class TransactionData {
|
public class TransactionData {
|
||||||
private Transaction transaction;
|
private Transaction transaction;
|
||||||
|
@ -156,4 +153,30 @@ public class TransactionData {
|
||||||
public Collection<Keystore> getSignedKeystores() {
|
public Collection<Keystore> getSignedKeystores() {
|
||||||
return signatureKeystoreMap.values();
|
return signatureKeystoreMap.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<WalletNode> getSigningWalletNodes() {
|
||||||
|
if(getSigningWallet() == null) {
|
||||||
|
throw new IllegalStateException("Signing wallet cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<WalletNode> signingWalletNodes = new LinkedHashSet<>();
|
||||||
|
for(TransactionInput txInput : transaction.getInputs()) {
|
||||||
|
Optional<WalletNode> optNode = getSigningWallet().getWalletTxos().entrySet().stream().filter(entry -> entry.getKey().getHash().equals(txInput.getOutpoint().getHash()) && entry.getKey().getIndex() == txInput.getOutpoint().getIndex()).map(Map.Entry::getValue).findFirst();
|
||||||
|
optNode.ifPresent(signingWalletNodes::add);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(TransactionOutput txOutput : transaction.getOutputs()) {
|
||||||
|
WalletNode changeNode = getSigningWallet().getWalletOutputScripts(KeyPurpose.CHANGE).get(txOutput.getScript());
|
||||||
|
if(changeNode != null) {
|
||||||
|
signingWalletNodes.add(changeNode);
|
||||||
|
} else {
|
||||||
|
WalletNode receiveNode = getSigningWallet().getWalletOutputScripts(KeyPurpose.RECEIVE).get(txOutput.getScript());
|
||||||
|
if(receiveNode != null) {
|
||||||
|
signingWalletNodes.add(receiveNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return signingWalletNodes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.wallet.BlockTransaction;
|
import com.sparrowwallet.drongo.wallet.BlockTransaction;
|
||||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.drongo.wallet.WalletNode;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.collections.ObservableMap;
|
import javafx.collections.ObservableMap;
|
||||||
|
@ -16,6 +17,7 @@ import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public abstract class TransactionForm {
|
public abstract class TransactionForm {
|
||||||
protected final TransactionData txdata;
|
protected final TransactionData txdata;
|
||||||
|
@ -92,6 +94,10 @@ public abstract class TransactionForm {
|
||||||
return txdata.getSignedKeystores();
|
return txdata.getSignedKeystores();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<WalletNode> getSigningWalletNodes() {
|
||||||
|
return txdata.getSigningWalletNodes();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEditable() {
|
public boolean isEditable() {
|
||||||
if(getBlockTransaction() != null) {
|
if(getBlockTransaction() != null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
Loading…
Reference in a new issue