From fec45356a29a7ff0f5aad575ddc41dcdda2bf432 Mon Sep 17 00:00:00 2001 From: zeroleak Date: Sun, 22 Aug 2021 11:26:11 +0200 Subject: [PATCH 1/3] upgrade to whirlpool-client 0.23.30-early4 + extlibj 0.0.19-dsk3 --- build.gradle | 7 +- .../sparrowwallet/sparrow/AppServices.java | 4 +- ...BackendApi.java => SparrowDataSource.java} | 210 +++++++----------- .../whirlpool/SparrowMinerFeeSupplier.java | 45 +++- .../sparrow/whirlpool/Whirlpool.java | 51 +++-- 5 files changed, 164 insertions(+), 153 deletions(-) rename src/main/java/com/sparrowwallet/sparrow/whirlpool/{SparrowBackendApi.java => SparrowDataSource.java} (83%) diff --git a/build.gradle b/build.gradle index 184aff00..e22b91e0 100644 --- a/build.gradle +++ b/build.gradle @@ -395,25 +395,28 @@ extraJavaModuleInfo { requires('com.fasterxml.jackson.databind') requires('logback.classic') requires('org.json') - exports('com.sparrowwallet.nightjar') exports('com.samourai.http.client') exports('com.samourai.tor.client') exports('com.samourai.wallet.api.backend') exports('com.samourai.wallet.api.backend.beans') exports('com.samourai.wallet.hd') - exports('com.samourai.wallet.hd.java') exports('com.samourai.whirlpool.client.event') exports('com.samourai.whirlpool.client.wallet') exports('com.samourai.whirlpool.client.wallet.beans') + exports('com.samourai.whirlpool.client.wallet.data.dataSource') + exports('com.samourai.whirlpool.client.wallet.data.dataPersister') exports('com.samourai.whirlpool.client.whirlpool') exports('com.samourai.whirlpool.client.whirlpool.beans') exports('com.samourai.whirlpool.client.wallet.data.pool') exports('com.samourai.whirlpool.client.wallet.data.utxo') + exports('com.samourai.whirlpool.client.wallet.data.utxoConfig') + exports('com.samourai.whirlpool.client.wallet.data.supplier') exports('com.samourai.whirlpool.client.mix.listener') exports('com.samourai.whirlpool.protocol.beans') exports('com.samourai.whirlpool.protocol.rest') exports('com.samourai.whirlpool.client.tx0') exports('com.samourai.wallet.segwit.bech32') + exports('com.samourai.whirlpool.client.wallet.data.wallet') exports('com.samourai.whirlpool.client.wallet.data.minerFee') exports('com.samourai.whirlpool.client.wallet.data.walletState') exports('com.sparrowwallet.nightjar.http') diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java index f7ce3089..8a6f67ff 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java @@ -455,8 +455,8 @@ public class AppServices { public Whirlpool getWhirlpool(String walletId) { Whirlpool whirlpool = whirlpoolMap.get(walletId); if(whirlpool == null) { - HostAndPort torProxy = AppServices.isTorRunning() ? HostAndPort.fromParts("localhost", TorService.PROXY_PORT) : (Config.get().getProxyServer().isEmpty() || !Config.get().isUseProxy() ? null : HostAndPort.fromString(Config.get().getProxyServer())); - whirlpool = new Whirlpool(Network.get(), torProxy, Config.get().getScode(), 1, 15); + HostAndPort torProxy = AppServices.isTorRunning() ? HostAndPort.fromParts("localhost", TorService.PROXY_PORT) : (Config.get().getProxyServer() == null || Config.get().getProxyServer().isEmpty() || !Config.get().isUseProxy() ? null : HostAndPort.fromString(Config.get().getProxyServer())); + whirlpool = new Whirlpool(Network.get(), torProxy, Config.get().getScode(), 1); whirlpoolMap.put(walletId, whirlpool); } diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowBackendApi.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowDataSource.java similarity index 83% rename from src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowBackendApi.java rename to src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowDataSource.java index 6a23b79e..1c42d1c2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowBackendApi.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowDataSource.java @@ -1,9 +1,14 @@ package com.sparrowwallet.sparrow.whirlpool; -import com.samourai.wallet.api.backend.BackendApi; -import com.samourai.wallet.api.backend.MinerFee; import com.samourai.wallet.api.backend.MinerFeeTarget; -import com.samourai.wallet.api.backend.beans.*; +import com.samourai.wallet.api.backend.beans.TxsResponse; +import com.samourai.wallet.api.backend.beans.UnspentOutput; +import com.samourai.wallet.api.backend.beans.WalletResponse; +import com.samourai.wallet.hd.HD_Wallet; +import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig; +import com.samourai.whirlpool.client.wallet.data.dataPersister.DataPersister; +import com.samourai.whirlpool.client.wallet.data.dataSource.WalletResponseDataSource; +import com.samourai.whirlpool.client.wallet.data.minerFee.MinerFeeSupplier; import com.sparrowwallet.drongo.ExtendedKey; import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.Network; @@ -21,88 +26,20 @@ import org.slf4j.LoggerFactory; import java.util.*; -@SuppressWarnings("deprecation") -public class SparrowBackendApi extends BackendApi { - private static final Logger log = LoggerFactory.getLogger(SparrowBackendApi.class); - private static final int FALLBACK_FEE_RATE = 75; +public class SparrowDataSource extends WalletResponseDataSource { + private static final Logger log = LoggerFactory.getLogger(SparrowDataSource.class); - public SparrowBackendApi() { - super(null, null); + public SparrowDataSource( + WhirlpoolWalletConfig config, + HD_Wallet bip44w, + String walletIdentifier, + DataPersister dataPersister) + throws Exception { + super(config, bip44w, walletIdentifier, dataPersister); } @Override - public TxsResponse fetchTxs(String[] zpubs, int page, int count) throws Exception { - List txes = new ArrayList<>(); - - for(String zpub : zpubs) { - Wallet wallet = getWallet(zpub); - if(wallet == null) { - log.debug("No wallet for " + zpub + " found"); - continue; - } - - for(BlockTransaction blockTransaction : wallet.getTransactions().values()) { - TxsResponse.Tx tx = new TxsResponse.Tx(); - tx.block_height = blockTransaction.getHeight(); - tx.hash = blockTransaction.getHashAsString(); - tx.locktime = blockTransaction.getTransaction().getLocktime(); - tx.time = blockTransaction.getDate().getTime(); - tx.version = (int)blockTransaction.getTransaction().getVersion(); - - tx.inputs = new TxsResponse.TxInput[blockTransaction.getTransaction().getInputs().size()]; - for(int i = 0; i < blockTransaction.getTransaction().getInputs().size(); i++) { - TransactionInput txInput = blockTransaction.getTransaction().getInputs().get(i); - tx.inputs[i] = new TxsResponse.TxInput(); - tx.inputs[i].vin = txInput.getIndex(); - tx.inputs[i].sequence = txInput.getSequenceNumber(); - tx.inputs[i].prev_out = new TxsResponse.TxOut(); - tx.inputs[i].prev_out.txid = txInput.getOutpoint().getHash().toString(); - tx.inputs[i].prev_out.vout = (int)txInput.getOutpoint().getIndex(); - - BlockTransaction spentTransaction = wallet.getTransactions().get(txInput.getOutpoint().getHash()); - if(spentTransaction != null) { - TransactionOutput spentOutput = spentTransaction.getTransaction().getOutputs().get((int)txInput.getOutpoint().getIndex()); - tx.inputs[i].prev_out.value = spentOutput.getValue(); - Address[] addresses = spentOutput.getScript().getToAddresses(); - if(addresses.length > 0) { - tx.inputs[i].prev_out.addr = addresses[0].toString(); - } - } - } - - tx.out = new TxsResponse.TxOutput[blockTransaction.getTransaction().getOutputs().size()]; - for(int i = 0; i < blockTransaction.getTransaction().getOutputs().size(); i++) { - TransactionOutput txOutput = blockTransaction.getTransaction().getOutputs().get(i); - tx.out[i].n = txOutput.getIndex(); - tx.out[i].value = txOutput.getValue(); - Address[] addresses = txOutput.getScript().getToAddresses(); - if(addresses.length > 0) { - tx.out[i].addr = addresses[0].toString(); - } - } - - txes.add(tx); - } - } - - List pageTxes; - if(txes.size() < count) { - pageTxes = txes; - } else { - pageTxes = txes.subList(page * count, Math.min((page * count) + count, txes.size())); - } - - TxsResponse txsResponse = new TxsResponse(); - txsResponse.n_tx = txes.size(); - txsResponse.page = page; - txsResponse.n_tx_page = pageTxes.size(); - txsResponse.txs = pageTxes.toArray(new TxsResponse.Tx[0]); - - return txsResponse; - } - - @Override - public WalletResponse fetchWallet(String[] zpubs) throws Exception { + protected WalletResponse fetchWalletResponse() throws Exception { WalletResponse walletResponse = new WalletResponse(); walletResponse.wallet = new WalletResponse.Wallet(); @@ -113,6 +50,7 @@ public class SparrowBackendApi extends BackendApi { List unspentOutputs = new ArrayList<>(); int storedBlockHeight = 0; + String[] zpubs = getWalletSupplier().getPubs(true); for(String zpub : zpubs) { Wallet wallet = getWallet(zpub); if(wallet == null) { @@ -197,22 +135,12 @@ public class SparrowBackendApi extends BackendApi { walletResponse.info.fees = new LinkedHashMap<>(); for(MinerFeeTarget target : MinerFeeTarget.values()) { - walletResponse.info.fees.put(target.getValue(), AppServices.getTargetBlockFeeRates() == null ? FALLBACK_FEE_RATE : getMinimumFeeForTarget(Integer.parseInt(target.getValue()))); + walletResponse.info.fees.put(target.getValue(), getMinerFeeSupplier().getFee(target)); } return walletResponse; } - @Override - public MinerFee fetchMinerFee() throws Exception { - Map fees = new LinkedHashMap<>(); - for(MinerFeeTarget target : MinerFeeTarget.values()) { - fees.put(target.getValue(), AppServices.getTargetBlockFeeRates() == null ? FALLBACK_FEE_RATE : getMinimumFeeForTarget(Integer.parseInt(target.getValue()))); - } - - return new MinerFee(fees); - } - @Override public void pushTx(String txHex) throws Exception { Transaction transaction = new Transaction(Utils.hexToBytes(txHex)); @@ -221,25 +149,79 @@ public class SparrowBackendApi extends BackendApi { } @Override - public boolean testConnectivity() { - return AppServices.isConnected(); - } + public TxsResponse fetchTxs(String[] zpubs, int page, int count) throws Exception { + List txes = new ArrayList<>(); - private Integer getMinimumFeeForTarget(int targetBlocks) { - List> feeRates = new ArrayList<>(AppServices.getTargetBlockFeeRates().entrySet()); - Collections.reverse(feeRates); - for(Map.Entry feeRate : feeRates) { - if(feeRate.getKey() <= targetBlocks) { - return feeRate.getValue().intValue(); + for(String zpub : zpubs) { + Wallet wallet = getWallet(zpub); + if(wallet == null) { + log.debug("No wallet for " + zpub + " found"); + continue; + } + + for(BlockTransaction blockTransaction : wallet.getTransactions().values()) { + TxsResponse.Tx tx = new TxsResponse.Tx(); + tx.block_height = blockTransaction.getHeight(); + tx.hash = blockTransaction.getHashAsString(); + tx.locktime = blockTransaction.getTransaction().getLocktime(); + tx.time = blockTransaction.getDate().getTime(); + tx.version = (int)blockTransaction.getTransaction().getVersion(); + + tx.inputs = new TxsResponse.TxInput[blockTransaction.getTransaction().getInputs().size()]; + for(int i = 0; i < blockTransaction.getTransaction().getInputs().size(); i++) { + TransactionInput txInput = blockTransaction.getTransaction().getInputs().get(i); + tx.inputs[i] = new TxsResponse.TxInput(); + tx.inputs[i].vin = txInput.getIndex(); + tx.inputs[i].sequence = txInput.getSequenceNumber(); + tx.inputs[i].prev_out = new TxsResponse.TxOut(); + tx.inputs[i].prev_out.txid = txInput.getOutpoint().getHash().toString(); + tx.inputs[i].prev_out.vout = (int)txInput.getOutpoint().getIndex(); + + BlockTransaction spentTransaction = wallet.getTransactions().get(txInput.getOutpoint().getHash()); + if(spentTransaction != null) { + TransactionOutput spentOutput = spentTransaction.getTransaction().getOutputs().get((int)txInput.getOutpoint().getIndex()); + tx.inputs[i].prev_out.value = spentOutput.getValue(); + Address[] addresses = spentOutput.getScript().getToAddresses(); + if(addresses.length > 0) { + tx.inputs[i].prev_out.addr = addresses[0].toString(); + } + } + } + + tx.out = new TxsResponse.TxOutput[blockTransaction.getTransaction().getOutputs().size()]; + for(int i = 0; i < blockTransaction.getTransaction().getOutputs().size(); i++) { + TransactionOutput txOutput = blockTransaction.getTransaction().getOutputs().get(i); + tx.out[i].n = txOutput.getIndex(); + tx.out[i].value = txOutput.getValue(); + Address[] addresses = txOutput.getScript().getToAddresses(); + if(addresses.length > 0) { + tx.out[i].addr = addresses[0].toString(); + } + } + + txes.add(tx); } } - return feeRates.get(0).getValue().intValue(); + List pageTxes; + if(txes.size() < count) { + pageTxes = txes; + } else { + pageTxes = txes.subList(page * count, Math.min((page * count) + count, txes.size())); + } + + TxsResponse txsResponse = new TxsResponse(); + txsResponse.n_tx = txes.size(); + txsResponse.page = page; + txsResponse.n_tx_page = pageTxes.size(); + txsResponse.txs = pageTxes.toArray(new TxsResponse.Tx[0]); + + return txsResponse; } @Override - public void initBip84(String zpub) throws Exception { - //nothing required + public MinerFeeSupplier getMinerFeeSupplier() { + return SparrowMinerFeeSupplier.getInstance(); } private Wallet getWallet(String zpub) { @@ -254,24 +236,4 @@ public class SparrowBackendApi extends BackendApi { .findFirst() .orElse(null); } - - @Override - public List fetchUtxos(String zpub) throws Exception { - throw new UnsupportedOperationException(); - } - - @Override - public List fetchUtxos(String[] zpubs) throws Exception { - throw new UnsupportedOperationException(); - } - - @Override - public Map fetchAddresses(String[] zpubs) throws Exception { - throw new UnsupportedOperationException(); - } - - @Override - public MultiAddrResponse.Address fetchAddress(String zpub) throws Exception { - throw new UnsupportedOperationException(); - } } diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowMinerFeeSupplier.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowMinerFeeSupplier.java index 68cd7569..1883d9b5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowMinerFeeSupplier.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowMinerFeeSupplier.java @@ -1,11 +1,46 @@ package com.sparrowwallet.sparrow.whirlpool; -import com.samourai.wallet.api.backend.MinerFee; +import com.samourai.wallet.api.backend.MinerFeeTarget; import com.samourai.whirlpool.client.wallet.data.minerFee.MinerFeeSupplier; +import com.sparrowwallet.sparrow.AppServices; -public class SparrowMinerFeeSupplier extends MinerFeeSupplier { - public SparrowMinerFeeSupplier(int feeMin, int feeMax, int feeFallback, MinerFee currentMinerFee) { - super(feeMin, feeMax, feeFallback); - setValue(currentMinerFee); +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class SparrowMinerFeeSupplier implements MinerFeeSupplier { + private static final int FALLBACK_FEE_RATE = 75; + + public static SparrowMinerFeeSupplier instance; + + public static SparrowMinerFeeSupplier getInstance() { + if (instance == null) { + instance = new SparrowMinerFeeSupplier(); + } + return instance; } + + private SparrowMinerFeeSupplier() { + } + + @Override + public int getFee(MinerFeeTarget feeTarget) { + if (AppServices.getTargetBlockFeeRates() == null) { + return FALLBACK_FEE_RATE; + } + return getMinimumFeeForTarget(Integer.parseInt(feeTarget.getValue())); + } + + private Integer getMinimumFeeForTarget(int targetBlocks) { + List> feeRates = new ArrayList<>(AppServices.getTargetBlockFeeRates().entrySet()); + Collections.reverse(feeRates); + for(Map.Entry feeRate : feeRates) { + if(feeRate.getKey() <= targetBlocks) { + return feeRate.getValue().intValue(); + } + } + return feeRates.get(0).getValue().intValue(); + } + } diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java index 01184f65..496c750d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java @@ -3,17 +3,25 @@ package com.sparrowwallet.sparrow.whirlpool; import com.google.common.eventbus.Subscribe; import com.google.common.net.HostAndPort; import com.samourai.tor.client.TorClientService; -import com.samourai.wallet.api.backend.BackendApi; import com.samourai.wallet.api.backend.beans.UnspentOutput; import com.samourai.wallet.hd.HD_Wallet; -import com.samourai.wallet.hd.java.HD_WalletFactoryJava; -import com.samourai.whirlpool.client.event.*; +import com.samourai.wallet.hd.HD_WalletFactoryGeneric; +import com.samourai.whirlpool.client.event.MixFailEvent; +import com.samourai.whirlpool.client.event.MixSuccessEvent; +import com.samourai.whirlpool.client.event.WalletStartEvent; +import com.samourai.whirlpool.client.event.WalletStopEvent; import com.samourai.whirlpool.client.tx0.*; import com.samourai.whirlpool.client.wallet.WhirlpoolEventService; import com.samourai.whirlpool.client.wallet.WhirlpoolWallet; import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig; import com.samourai.whirlpool.client.wallet.WhirlpoolWalletService; -import com.samourai.whirlpool.client.wallet.beans.*; +import com.samourai.whirlpool.client.wallet.beans.Tx0FeeTarget; +import com.samourai.whirlpool.client.wallet.beans.WhirlpoolAccount; +import com.samourai.whirlpool.client.wallet.beans.WhirlpoolServer; +import com.samourai.whirlpool.client.wallet.beans.WhirlpoolUtxo; +import com.samourai.whirlpool.client.wallet.data.dataPersister.DataPersisterFactory; +import com.samourai.whirlpool.client.wallet.data.dataPersister.FileDataPersister; +import com.samourai.whirlpool.client.wallet.data.dataSource.DataSourceFactory; import com.samourai.whirlpool.client.wallet.data.pool.PoolData; import com.samourai.whirlpool.client.wallet.data.utxo.UtxoSupplier; import com.samourai.whirlpool.client.whirlpool.ServerApi; @@ -36,7 +44,9 @@ import javafx.concurrent.Task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.Collection; +import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; public class Whirlpool { @@ -51,26 +61,27 @@ public class Whirlpool { private final WhirlpoolWalletConfig config; private HD_Wallet hdWallet; - public Whirlpool(Network network, HostAndPort torProxy, String sCode, int maxClients, int clientDelay) { + public Whirlpool(Network network, HostAndPort torProxy, String sCode, int maxClients) { this.torProxy = torProxy; this.whirlpoolServer = WhirlpoolServer.valueOf(network.getName().toUpperCase()); this.httpClientService = new JavaHttpClientService(torProxy); this.stompClientService = new JavaStompClientService(httpClientService); this.torClientService = new WhirlpoolTorClientService(); - this.whirlpoolWalletService = new WhirlpoolWalletService(); - this.config = computeWhirlpoolWalletConfig(sCode, maxClients, clientDelay); + + DataPersisterFactory dataPersisterFactory = (config, bip44w, walletIdentifier) -> new FileDataPersister(config, bip44w, walletIdentifier); + DataSourceFactory dataSourceFactory = (config, bip44w, walletIdentifier, dataPersister) -> new SparrowDataSource(config, bip44w, walletIdentifier, dataPersister); + this.whirlpoolWalletService = new WhirlpoolWalletService(dataPersisterFactory, dataSourceFactory); + this.config = computeWhirlpoolWalletConfig(sCode, maxClients); WhirlpoolEventService.getInstance().register(this); } - private WhirlpoolWalletConfig computeWhirlpoolWalletConfig(String sCode, int maxClients, int clientDelay) { + private WhirlpoolWalletConfig computeWhirlpoolWalletConfig(String sCode, int maxClients) { boolean onion = (torProxy != null); String serverUrl = whirlpoolServer.getServerUrl(onion); - ServerApi serverApi = new ServerApi(serverUrl, httpClientService); - BackendApi backendApi = new SparrowBackendApi(); - WhirlpoolWalletConfig whirlpoolWalletConfig = new WhirlpoolWalletConfig(httpClientService, stompClientService, torClientService, serverApi, whirlpoolServer, false, backendApi); + WhirlpoolWalletConfig whirlpoolWalletConfig = new WhirlpoolWalletConfig(httpClientService, stompClientService, torClientService, serverApi, whirlpoolServer.getParams(), false); whirlpoolWalletConfig.setScode(sCode); return whirlpoolWalletConfig; @@ -114,7 +125,7 @@ public class Whirlpool { private Tx0ParamService getTx0ParamService() { try { - SparrowMinerFeeSupplier minerFeeSupplier = new SparrowMinerFeeSupplier(config.getFeeMin(), config.getFeeMax(), config.getFeeFallback(), config.getBackendApi().fetchMinerFee()); + SparrowMinerFeeSupplier minerFeeSupplier = SparrowMinerFeeSupplier.getInstance(); return new Tx0ParamService(minerFeeSupplier, config); } catch(Exception e) { log.error("Error fetching miner fees", e); @@ -134,17 +145,17 @@ public class Whirlpool { int purpose = scriptType.getDefaultDerivation().get(0).num(); List words = keystore.getSeed().getMnemonicCode(); String passphrase = keystore.getSeed().getPassphrase().asString(); - HD_WalletFactoryJava hdWalletFactory = HD_WalletFactoryJava.getInstance(); + HD_WalletFactoryGeneric hdWalletFactory = HD_WalletFactoryGeneric.getInstance(); byte[] seed = hdWalletFactory.computeSeedFromWords(words); - hdWallet = new HD_Wallet(purpose, words, whirlpoolServer, seed, passphrase, 1); + hdWallet = new HD_Wallet(purpose, words, config.getNetworkParameters(), seed, passphrase, 1); } catch(Exception e) { throw new IllegalStateException("Could not create Whirlpool HD wallet ", e); } } public WhirlpoolWallet getWhirlpoolWallet() throws WhirlpoolException { - if(whirlpoolWalletService.whirlpoolWallet() != null) { - return whirlpoolWalletService.whirlpoolWallet(); + if(whirlpoolWalletService.getWhirlpoolWalletOrNull() != null) { + return whirlpoolWalletService.getWhirlpoolWalletOrNull(); } if(hdWallet == null) { @@ -167,11 +178,11 @@ public class Whirlpool { } public boolean isStarted() { - if(whirlpoolWalletService.whirlpoolWallet() == null) { + if(whirlpoolWalletService.getWhirlpoolWalletOrNull() == null) { return false; } - return whirlpoolWalletService.whirlpoolWallet().isStarted(); + return whirlpoolWalletService.getWhirlpoolWalletOrNull().isStarted(); } public void shutdown() { @@ -226,7 +237,7 @@ public class Whirlpool { @Subscribe public void onMixFail(MixFailEvent e) { - log.info("Mix failed for utxo " + e.getWhirlpoolUtxo().getUtxo().tx_hash + ":" + e.getWhirlpoolUtxo().getUtxo().tx_output_n); + log.info("Mix failed for utxo " + e.getMixFail().getWhirlpoolUtxo().getUtxo().tx_hash + ":" + e.getMixFail().getWhirlpoolUtxo().getUtxo().tx_output_n); } @Subscribe From 050c4fc31edfc300fd5a81af84321ce1ef05df7f Mon Sep 17 00:00:00 2001 From: zeroleak Date: Thu, 26 Aug 2021 11:49:20 +0200 Subject: [PATCH 2/3] upgrade to whirlpool-client 0.23.30-early4 + extlibj 0.0.19-dsk3 --- build.gradle | 6 +- .../sparrowwallet/sparrow/AppServices.java | 2 +- .../sparrow/control/MixStatusCell.java | 6 +- .../sparrow/io/db/UtxoMixDataDao.java | 16 +-- .../sparrow/io/db/UtxoMixDataMapper.java | 6 +- .../sparrow/wallet/UtxoEntry.java | 6 +- .../whirlpool/SparrowWalletDataSupplier.java | 24 ---- .../SparrowWalletStatePersister.java | 53 ------- .../SparrowWhirlpoolWalletService.java | 23 --- .../sparrow/whirlpool/Whirlpool.java | 115 +++++++-------- .../dataPersister/SparrowDataPersister.java | 51 +++++++ .../SparrowUtxoConfigPersister.java | 26 ++-- .../{ => dataSource}/SparrowDataSource.java | 136 ++++++++---------- .../dataSource/SparrowIndexHandler.java | 34 +++++ .../SparrowMinerFeeSupplier.java | 17 ++- .../SparrowWalletStateSupplier.java | 92 ++++++++++++ .../sparrow/sql/V3__Whirlpool.sql | 2 + 17 files changed, 331 insertions(+), 284 deletions(-) delete mode 100644 src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWalletDataSupplier.java delete mode 100644 src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWalletStatePersister.java delete mode 100644 src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWhirlpoolWalletService.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowDataPersister.java rename src/main/java/com/sparrowwallet/sparrow/whirlpool/{ => dataPersister}/SparrowUtxoConfigPersister.java (78%) rename src/main/java/com/sparrowwallet/sparrow/whirlpool/{ => dataSource}/SparrowDataSource.java (68%) create mode 100644 src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowIndexHandler.java rename src/main/java/com/sparrowwallet/sparrow/whirlpool/{ => dataSource}/SparrowMinerFeeSupplier.java (75%) create mode 100644 src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowWalletStateSupplier.java create mode 100644 src/main/resources/com/sparrowwallet/sparrow/sql/V3__Whirlpool.sql diff --git a/build.gradle b/build.gradle index e80b11e3..809a4817 100644 --- a/build.gradle +++ b/build.gradle @@ -91,7 +91,7 @@ dependencies { implementation('org.slf4j:jul-to-slf4j:1.7.30') { exclude group: 'org.slf4j' } - implementation('com.sparrowwallet.nightjar:nightjar:0.2.10') + implementation('com.sparrowwallet.nightjar:nightjar:0.2.11-SNAPSHOT') testImplementation('junit:junit:4.12') } @@ -387,7 +387,7 @@ extraJavaModuleInfo { module('cbor-0.9.jar', 'co.nstant.in.cbor', '0.9') { exports('co.nstant.in.cbor') } - module('nightjar-0.2.10.jar', 'com.sparrowwallet.nightjar', '0.2.10') { + module('nightjar-0.2.11-SNAPSHOT.jar', 'com.sparrowwallet.nightjar', '0.2.11-SNAPSHOT') { requires('com.google.common') requires('net.sourceforge.streamsupport') requires('org.slf4j') @@ -399,6 +399,7 @@ extraJavaModuleInfo { exports('com.samourai.tor.client') exports('com.samourai.wallet.api.backend') exports('com.samourai.wallet.api.backend.beans') + exports('com.samourai.wallet.client.indexHandler') exports('com.samourai.wallet.hd') exports('com.samourai.whirlpool.client.event') exports('com.samourai.whirlpool.client.wallet') @@ -411,6 +412,7 @@ extraJavaModuleInfo { exports('com.samourai.whirlpool.client.wallet.data.utxo') exports('com.samourai.whirlpool.client.wallet.data.utxoConfig') exports('com.samourai.whirlpool.client.wallet.data.supplier') + exports('com.samourai.whirlpool.client.mix.handler') exports('com.samourai.whirlpool.client.mix.listener') exports('com.samourai.whirlpool.protocol.beans') exports('com.samourai.whirlpool.protocol.rest') diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java index 0310ec83..89611bd7 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java @@ -469,7 +469,7 @@ public class AppServices { Whirlpool whirlpool = whirlpoolMap.get(walletId); if(whirlpool == null) { HostAndPort torProxy = AppServices.isTorRunning() ? HostAndPort.fromParts("localhost", TorService.PROXY_PORT) : (Config.get().getProxyServer() == null || Config.get().getProxyServer().isEmpty() || !Config.get().isUseProxy() ? null : HostAndPort.fromString(Config.get().getProxyServer())); - whirlpool = new Whirlpool(Network.get(), torProxy, Config.get().getScode(), 1); + whirlpool = new Whirlpool(Network.get(), torProxy, Config.get().getScode()); whirlpoolMap.put(walletId, whirlpool); } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/MixStatusCell.java b/src/main/java/com/sparrowwallet/sparrow/control/MixStatusCell.java index ce849cab..8ed3ce11 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/MixStatusCell.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/MixStatusCell.java @@ -40,9 +40,9 @@ public class MixStatusCell extends TreeTableCell { setContextMenu(null); } - if(mixStatus.getPoolId() != null) { + if(mixStatus.getMixProgress() != null) { Tooltip tooltip = new Tooltip(); - tooltip.setText("Pool: " + mixStatus.getPoolId().replace("btc", " BTC")); + tooltip.setText("Pool: " + mixStatus.getMixProgress().getPoolId().replace("btc", " BTC")); setTooltip(tooltip); } @@ -81,7 +81,7 @@ public class MixStatusCell extends TreeTableCell { private void setMixProgress(MixProgress mixProgress) { if(mixProgress.getMixStep() != MixStep.FAIL) { ProgressIndicator progressIndicator = getProgressIndicator(); - progressIndicator.setProgress(mixProgress.getProgressPercent() == 100 ? -1 : mixProgress.getProgressPercent() / 100.0); + progressIndicator.setProgress(mixProgress.getMixStep().getProgressPercent() == 100 ? -1 : mixProgress.getMixStep().getProgressPercent() / 100.0); setGraphic(progressIndicator); Tooltip tt = new Tooltip(); tt.setText(mixProgress.getMixStep().getMessage().substring(0, 1).toUpperCase() + mixProgress.getMixStep().getMessage().substring(1)); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/UtxoMixDataDao.java b/src/main/java/com/sparrowwallet/sparrow/io/db/UtxoMixDataDao.java index ed5e8696..50a57e4a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/db/UtxoMixDataDao.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/db/UtxoMixDataDao.java @@ -13,20 +13,20 @@ import java.util.List; import java.util.Map; public interface UtxoMixDataDao { - @SqlQuery("select id, hash, poolId, mixesDone, forwarding from utxoMixData where wallet = ? order by id") + @SqlQuery("select id, hash, mixesDone, expired from utxoMixData where wallet = ? order by id") @RegisterRowMapper(UtxoMixDataMapper.class) Map getForWalletId(Long id); - @SqlQuery("select id, hash, poolId, mixesDone, forwarding from utxoMixData where hash = ?") + @SqlQuery("select id, hash, mixesDone, expired from utxoMixData where hash = ?") @RegisterRowMapper(UtxoMixDataMapper.class) Map getForHash(byte[] hash); - @SqlUpdate("insert into utxoMixData (hash, poolId, mixesDone, forwarding, wallet) values (?, ?, ?, ?, ?)") + @SqlUpdate("insert into utxoMixData (hash, mixesDone, expired, wallet) values (?, ?, ?, ?)") @GetGeneratedKeys("id") - long insertUtxoMixData(byte[] hash, String poolId, int mixesDone, Long forwarding, long wallet); + long insertUtxoMixData(byte[] hash, int mixesDone, Long expired, long wallet); - @SqlUpdate("update utxoMixData set hash = ?, poolId = ?, mixesDone = ?, forwarding = ?, wallet = ? where id = ?") - void updateUtxoMixData(byte[] hash, String poolId, int mixesDone, Long forwarding, long wallet, long id); + @SqlUpdate("update utxoMixData set hash = ?, mixesDone = ?, expired = ?, wallet = ? where id = ?") + void updateUtxoMixData(byte[] hash, int mixesDone, Long expired, long wallet, long id); @SqlUpdate("delete from utxoMixData where id in ()") void deleteUtxoMixData(@BindList("ids") List ids); @@ -45,11 +45,11 @@ public interface UtxoMixDataDao { Map existing = getForHash(hash.getBytes()); if(existing.isEmpty() && utxoMixData.getId() == null) { - long id = insertUtxoMixData(hash.getBytes(), utxoMixData.getPoolId(), utxoMixData.getMixesDone(), utxoMixData.getForwarding(), wallet.getId()); + long id = insertUtxoMixData(hash.getBytes(), utxoMixData.getMixesDone(), utxoMixData.getExpired(), wallet.getId()); utxoMixData.setId(id); } else { Long existingId = existing.get(hash) != null ? existing.get(hash).getId() : utxoMixData.getId(); - updateUtxoMixData(hash.getBytes(), utxoMixData.getPoolId(), utxoMixData.getMixesDone(), utxoMixData.getForwarding(), wallet.getId(), existingId); + updateUtxoMixData(hash.getBytes(), utxoMixData.getMixesDone(), utxoMixData.getExpired(), wallet.getId(), existingId); utxoMixData.setId(existingId); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/UtxoMixDataMapper.java b/src/main/java/com/sparrowwallet/sparrow/io/db/UtxoMixDataMapper.java index 3c784753..99563ed0 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/db/UtxoMixDataMapper.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/db/UtxoMixDataMapper.java @@ -14,12 +14,12 @@ public class UtxoMixDataMapper implements RowMapper map(ResultSet rs, StatementContext ctx) throws SQLException { Sha256Hash hash = Sha256Hash.wrap(rs.getBytes("hash")); - Long forwarding = rs.getLong("forwarding"); + Long expired = rs.getLong("expired"); if(rs.wasNull()) { - forwarding = null; + expired = null; } - UtxoMixData utxoMixData = new UtxoMixData(rs.getString("poolId"), rs.getInt("mixesDone"), forwarding); + UtxoMixData utxoMixData = new UtxoMixData(rs.getInt("mixesDone"), expired); utxoMixData.setId(rs.getLong("id")); return new Map.Entry<>() { diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java index 3803b6a6..0c25b350 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java @@ -157,17 +157,13 @@ public class UtxoEntry extends HashIndexEntry { } } - return new UtxoMixData("Unknown Pool", getUtxoEntry().getWallet().getStandardAccountType() == StandardAccount.WHIRLPOOL_POSTMIX ? 1 : 0, null); + return new UtxoMixData(getUtxoEntry().getWallet().getStandardAccountType() == StandardAccount.WHIRLPOOL_POSTMIX ? 1 : 0, null); } public int getMixesDone() { return getUtxoMixData().getMixesDone(); } - public String getPoolId() { - return getUtxoMixData().getPoolId(); - } - public MixProgress getMixProgress() { return mixProgress; } diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWalletDataSupplier.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWalletDataSupplier.java deleted file mode 100644 index ed4548f1..00000000 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWalletDataSupplier.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.sparrowwallet.sparrow.whirlpool; - -import com.samourai.wallet.hd.HD_Wallet; -import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig; -import com.samourai.whirlpool.client.wallet.data.minerFee.BackendWalletDataSupplier; -import com.samourai.whirlpool.client.wallet.data.minerFee.WalletSupplier; -import com.samourai.whirlpool.client.wallet.data.utxo.UtxoConfigPersister; - -public class SparrowWalletDataSupplier extends BackendWalletDataSupplier { - public SparrowWalletDataSupplier(int refreshUtxoDelay, WhirlpoolWalletConfig config, HD_Wallet bip44w, String walletIdentifier) throws Exception { - super(refreshUtxoDelay, config, bip44w, walletIdentifier); - } - - @Override - protected WalletSupplier computeWalletSupplier(WhirlpoolWalletConfig config, HD_Wallet bip44w, String walletIdentifier) throws Exception { - int externalIndexDefault = config.getExternalDestination() != null ? config.getExternalDestination().getStartIndex() : 0; - return new WalletSupplier(new SparrowWalletStatePersister(walletIdentifier), config.getBackendApi(), bip44w, externalIndexDefault); - } - - @Override - protected UtxoConfigPersister computeUtxoConfigPersister(String walletIdentifier) throws Exception { - return new SparrowUtxoConfigPersister(walletIdentifier); - } -} diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWalletStatePersister.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWalletStatePersister.java deleted file mode 100644 index 3f68dd8a..00000000 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWalletStatePersister.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.sparrowwallet.sparrow.whirlpool; - -import com.samourai.whirlpool.client.wallet.data.walletState.WalletStateData; -import com.samourai.whirlpool.client.wallet.data.walletState.WalletStatePersister; -import com.sparrowwallet.drongo.KeyPurpose; -import com.sparrowwallet.drongo.protocol.ScriptType; -import com.sparrowwallet.drongo.wallet.StandardAccount; -import com.sparrowwallet.drongo.wallet.Wallet; -import com.sparrowwallet.sparrow.AppServices; - -import java.util.LinkedHashMap; -import java.util.Map; - -public class SparrowWalletStatePersister extends WalletStatePersister { - private final String walletId; - - public SparrowWalletStatePersister(String walletId) { - super(walletId); - this.walletId = walletId; - } - - @Override - public synchronized WalletStateData load() throws Exception { - Wallet wallet = AppServices.get().getOpenWallets().entrySet().stream().filter(entry -> entry.getValue().getWalletId(entry.getKey()).equals(walletId)).map(Map.Entry::getKey).findFirst().orElseThrow(); - - Map values = new LinkedHashMap<>(); - values.put("init", 1); - putValues("DEPOSIT", wallet, values); - - for(StandardAccount whirlpoolAccount : StandardAccount.WHIRLPOOL_ACCOUNTS) { - putValues(whirlpoolAccount.getName().toUpperCase(), wallet.getChildWallet(whirlpoolAccount), values); - } - - return new WalletStateData(values); - } - - private void putValues(String prefix, Wallet wallet, Map values) { - for(KeyPurpose keyPurpose : KeyPurpose.DEFAULT_PURPOSES) { - Integer index = wallet.getNode(keyPurpose).getHighestUsedIndex(); - values.put(prefix + "_" + getPurpose(wallet) + "_" + keyPurpose.getPathIndex().num(), index == null ? 0 : index + 1); - } - } - - private int getPurpose(Wallet wallet) { - ScriptType scriptType = wallet.getScriptType(); - return scriptType.getDefaultDerivation().get(0).num(); - } - - @Override - public synchronized void write(WalletStateData data) throws Exception { - //nothing required - } -} diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWhirlpoolWalletService.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWhirlpoolWalletService.java deleted file mode 100644 index 2d575911..00000000 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWhirlpoolWalletService.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.sparrowwallet.sparrow.whirlpool; - -import com.samourai.wallet.hd.HD_Wallet; -import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig; -import com.samourai.whirlpool.client.wallet.WhirlpoolWalletService; -import com.samourai.whirlpool.client.wallet.data.minerFee.WalletDataSupplier; - -public class SparrowWhirlpoolWalletService extends WhirlpoolWalletService { - private String walletId; - - @Override - protected WalletDataSupplier computeWalletDataSupplier(WhirlpoolWalletConfig config, HD_Wallet bip44w, String walletIdentifier) throws Exception { - return new SparrowWalletDataSupplier(config.getRefreshUtxoDelay(), config, bip44w, walletId); - } - - public String getWalletId() { - return walletId; - } - - public void setWalletId(String walletId) { - this.walletId = walletId; - } -} diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java index f371a003..167680f2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java @@ -6,21 +6,19 @@ import com.samourai.tor.client.TorClientService; import com.samourai.wallet.api.backend.beans.UnspentOutput; import com.samourai.wallet.hd.HD_Wallet; import com.samourai.wallet.hd.HD_WalletFactoryGeneric; -import com.samourai.whirlpool.client.event.MixFailEvent; -import com.samourai.whirlpool.client.event.MixSuccessEvent; -import com.samourai.whirlpool.client.event.WalletStartEvent; -import com.samourai.whirlpool.client.event.WalletStopEvent; -import com.samourai.whirlpool.client.tx0.*; +import com.samourai.whirlpool.client.event.*; +import com.samourai.whirlpool.client.tx0.Tx0; +import com.samourai.whirlpool.client.tx0.Tx0Config; +import com.samourai.whirlpool.client.tx0.Tx0Preview; import com.samourai.whirlpool.client.wallet.WhirlpoolEventService; import com.samourai.whirlpool.client.wallet.WhirlpoolWallet; import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig; +import com.samourai.whirlpool.client.wallet.WhirlpoolWalletService; import com.samourai.whirlpool.client.wallet.beans.*; import com.samourai.whirlpool.client.wallet.data.dataPersister.DataPersisterFactory; -import com.samourai.whirlpool.client.wallet.data.dataPersister.FileDataPersister; import com.samourai.whirlpool.client.wallet.data.dataSource.DataSourceFactory; -import com.samourai.whirlpool.client.wallet.data.pool.PoolData; -import com.samourai.whirlpool.client.wallet.data.utxo.UtxoConfigPersisted; import com.samourai.whirlpool.client.wallet.data.utxo.UtxoSupplier; +import com.samourai.whirlpool.client.wallet.data.utxoConfig.UtxoConfig; import com.samourai.whirlpool.client.whirlpool.ServerApi; import com.samourai.whirlpool.client.whirlpool.beans.Pool; import com.sparrowwallet.drongo.ExtendedKey; @@ -40,6 +38,8 @@ import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.event.WhirlpoolMixEvent; import com.sparrowwallet.sparrow.event.WhirlpoolMixSuccessEvent; import com.sparrowwallet.sparrow.wallet.UtxoEntry; +import com.sparrowwallet.sparrow.whirlpool.dataPersister.SparrowDataPersister; +import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowDataSource; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -50,6 +50,7 @@ import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -61,54 +62,55 @@ public class Whirlpool { private final JavaHttpClientService httpClientService; private final JavaStompClientService stompClientService; private final TorClientService torClientService; - private final SparrowWhirlpoolWalletService whirlpoolWalletService; + private final WhirlpoolWalletService whirlpoolWalletService; private final WhirlpoolWalletConfig config; private HD_Wallet hdWallet; + private String walletId; private BooleanProperty mixingProperty = new SimpleBooleanProperty(false); - public Whirlpool(Network network, HostAndPort torProxy, String sCode, int maxClients) { + public Whirlpool(Network network, HostAndPort torProxy, String sCode) { this.torProxy = torProxy; this.whirlpoolServer = WhirlpoolServer.valueOf(network.getName().toUpperCase()); this.httpClientService = new JavaHttpClientService(torProxy); this.stompClientService = new JavaStompClientService(httpClientService); this.torClientService = new WhirlpoolTorClientService(); - DataPersisterFactory dataPersisterFactory = (config, bip44w, walletIdentifier) -> new FileDataPersister(config, bip44w, walletIdentifier); - DataSourceFactory dataSourceFactory = (config, bip44w, walletIdentifier, dataPersister) -> new SparrowDataSource(config, bip44w, walletIdentifier, dataPersister); - this.whirlpoolWalletService = new SparrowWhirlpoolWalletService(dataPersisterFactory, dataSourceFactory); - this.config = computeWhirlpoolWalletConfig(sCode, maxClients); + this.whirlpoolWalletService = new WhirlpoolWalletService(); + this.config = computeWhirlpoolWalletConfig(sCode); WhirlpoolEventService.getInstance().register(this); } - private WhirlpoolWalletConfig computeWhirlpoolWalletConfig(String sCode, int maxClients) { + private WhirlpoolWalletConfig computeWhirlpoolWalletConfig(String sCode) { + DataPersisterFactory dataPersisterFactory = (whirlpoolWallet, bip44w) -> new SparrowDataPersister(whirlpoolWallet); + DataSourceFactory dataSourceFactory = (whirlpoolWallet, bip44w, dataPersister) -> new SparrowDataSource(whirlpoolWallet, bip44w, dataPersister); + boolean onion = (torProxy != null); String serverUrl = whirlpoolServer.getServerUrl(onion); ServerApi serverApi = new ServerApi(serverUrl, httpClientService); - WhirlpoolWalletConfig whirlpoolWalletConfig = new WhirlpoolWalletConfig(httpClientService, stompClientService, torClientService, serverApi, whirlpoolServer.getParams(), false); + WhirlpoolWalletConfig whirlpoolWalletConfig = new WhirlpoolWalletConfig(dataSourceFactory, httpClientService, stompClientService, torClientService, serverApi, whirlpoolServer.getParams(), false); + whirlpoolWalletConfig.setDataPersisterFactory(dataPersisterFactory); whirlpoolWalletConfig.setScode(sCode); return whirlpoolWalletConfig; } public Collection getPools() throws Exception { - Tx0ParamService tx0ParamService = getTx0ParamService(); - PoolData poolData = new PoolData(config.getServerApi().fetchPools(), tx0ParamService); - return poolData.getPools(); + WhirlpoolWallet whirlpoolWallet = getWhirlpoolWallet(); + return whirlpoolWallet.getPoolSupplier().getPools(); } public Tx0Preview getTx0Preview(Pool pool, Collection utxos) throws Exception { + WhirlpoolWallet whirlpoolWallet = getWhirlpoolWallet(); + Tx0Config tx0Config = new Tx0Config(); tx0Config.setChangeWallet(WhirlpoolAccount.BADBANK); Tx0FeeTarget tx0FeeTarget = Tx0FeeTarget.BLOCKS_4; Tx0FeeTarget mixFeeTarget = Tx0FeeTarget.BLOCKS_4; - Tx0ParamService tx0ParamService = getTx0ParamService(); - - Tx0Service tx0Service = new Tx0Service(config); - return tx0Service.tx0Preview(utxos, tx0Config, tx0ParamService.getTx0Param(pool, tx0FeeTarget, mixFeeTarget)); + return whirlpoolWallet.tx0Preview(pool, tx0Config, utxos, tx0FeeTarget, mixFeeTarget); } public Tx0 broadcastTx0(Pool pool, Collection utxos) throws Exception { @@ -129,17 +131,6 @@ public class Whirlpool { return whirlpoolWallet.tx0(whirlpoolUtxos, pool, tx0Config, tx0FeeTarget, mixFeeTarget); } - private Tx0ParamService getTx0ParamService() { - try { - SparrowMinerFeeSupplier minerFeeSupplier = SparrowMinerFeeSupplier.getInstance(); - return new Tx0ParamService(minerFeeSupplier, config); - } catch(Exception e) { - log.error("Error fetching miner fees", e); - } - - return null; - } - public void setHDWallet(String walletId, Wallet wallet) { if(wallet.isEncrypted()) { throw new IllegalStateException("Wallet cannot be encrypted"); @@ -153,7 +144,7 @@ public class Whirlpool { String passphrase = keystore.getSeed().getPassphrase().asString(); HD_WalletFactoryGeneric hdWalletFactory = HD_WalletFactoryGeneric.getInstance(); byte[] seed = hdWalletFactory.computeSeedFromWords(words); - whirlpoolWalletService.setWalletId(walletId); + this.walletId = walletId; hdWallet = new HD_Wallet(purpose, words, config.getNetworkParameters(), seed, passphrase, 1); } catch(Exception e) { throw new IllegalStateException("Could not create Whirlpool HD wallet ", e); @@ -161,8 +152,8 @@ public class Whirlpool { } public WhirlpoolWallet getWhirlpoolWallet() throws WhirlpoolException { - if(whirlpoolWalletService.getWhirlpoolWalletOrNull() != null) { - return whirlpoolWalletService.getWhirlpoolWalletOrNull(); + if(whirlpoolWalletService.whirlpoolWallet() != null) { + return whirlpoolWalletService.whirlpoolWallet(); } if(hdWallet == null) { @@ -170,7 +161,8 @@ public class Whirlpool { } try { - return whirlpoolWalletService.openWallet(config, Utils.hexToBytes(hdWallet.getSeedHex()), hdWallet.getPassphrase()); + WhirlpoolWallet whirlpoolWallet = new WhirlpoolWallet(config, Utils.hexToBytes(hdWallet.getSeedHex()), hdWallet.getPassphrase(), walletId); + return whirlpoolWalletService.openWallet(whirlpoolWallet); } catch(Exception e) { throw new WhirlpoolException("Could not create whirlpool wallet ", e); } @@ -184,23 +176,16 @@ public class Whirlpool { public UtxoMixData getMixData(BlockTransactionHashIndex txo) { if(whirlpoolWalletService.whirlpoolWallet() != null) { - UtxoConfigPersisted config = whirlpoolWalletService.whirlpoolWallet().getUtxoConfigSupplier().getUtxoConfigPersisted(txo.getHashAsString(), (int)txo.getIndex()); - if(config != null) { - return new UtxoMixData(config.getPoolId(), config.getMixsDone(), config.getForwarding()); + WhirlpoolUtxo whirlpoolUtxo = whirlpoolWalletService.whirlpoolWallet().getUtxoSupplier().findUtxo(txo.getHashAsString(), (int)txo.getIndex()); + if (whirlpoolUtxo != null) { + UtxoConfig utxoConfig = whirlpoolUtxo.getUtxoConfigOrDefault(); + return new UtxoMixData(utxoConfig.getMixsDone(), null); } } return null; } - private void persistMixData() { - try { - whirlpoolWalletService.whirlpoolWallet().getUtxoConfigSupplier().persist(true); - } catch(Exception e) { - log.error("Error persisting mix data", e); - } - } - public void mix(BlockTransactionHashIndex utxo) throws WhirlpoolException { if(whirlpoolWalletService.whirlpoolWallet() == null) { throw new WhirlpoolException("Whirlpool wallet not yet created"); @@ -208,7 +193,7 @@ public class Whirlpool { try { WhirlpoolUtxo whirlpoolUtxo = whirlpoolWalletService.whirlpoolWallet().getUtxoSupplier().findUtxo(utxo.getHashAsString(), (int)utxo.getIndex()); - whirlpoolWalletService.whirlpoolWallet().mixNow(whirlpoolUtxo); + whirlpoolWalletService.whirlpoolWallet().mix(whirlpoolUtxo); } catch(Exception e) { throw new WhirlpoolException(e.getMessage(), e); } @@ -255,11 +240,11 @@ public class Whirlpool { } public boolean isStarted() { - if(whirlpoolWalletService.getWhirlpoolWalletOrNull() == null) { + if(whirlpoolWalletService.whirlpoolWallet() == null) { return false; } - return whirlpoolWalletService.getWhirlpoolWalletOrNull().isStarted(); + return whirlpoolWalletService.whirlpoolWallet().isStarted(); } public void shutdown() { @@ -268,12 +253,9 @@ public class Whirlpool { } private WalletUtxo getUtxo(WhirlpoolUtxo whirlpoolUtxo) { - Wallet wallet = AppServices.get().getWallet(whirlpoolWalletService.getWalletId()); + Wallet wallet = AppServices.get().getWallet(walletId); if(wallet != null) { - StandardAccount standardAccount = getStandardAccount(whirlpoolUtxo.getAccount()); - if(StandardAccount.WHIRLPOOL_ACCOUNTS.contains(standardAccount)) { - wallet = wallet.getChildWallet(standardAccount); - } + wallet = getStandardAccountWallet(whirlpoolUtxo.getAccount(), wallet); for(BlockTransactionHashIndex utxo : wallet.getWalletUtxos().keySet()) { if(utxo.getHashAsString().equals(whirlpoolUtxo.getUtxo().tx_hash) && utxo.getIndex() == whirlpoolUtxo.getUtxo().tx_output_n) { @@ -285,6 +267,18 @@ public class Whirlpool { return null; } + public static Wallet getWallet(String walletId) { + return AppServices.get().getOpenWallets().entrySet().stream().filter(entry -> entry.getValue().getWalletId(entry.getKey()).equals(walletId)).map(Map.Entry::getKey).findFirst().orElse(null); + } + + public static Wallet getStandardAccountWallet(WhirlpoolAccount whirlpoolAccount, Wallet wallet) { + StandardAccount standardAccount = getStandardAccount(whirlpoolAccount); + if(StandardAccount.WHIRLPOOL_ACCOUNTS.contains(standardAccount)) { + wallet = wallet.getChildWallet(standardAccount); + } + return wallet; + } + public static StandardAccount getStandardAccount(WhirlpoolAccount whirlpoolAccount) { if(whirlpoolAccount == WhirlpoolAccount.PREMIX) { return StandardAccount.WHIRLPOOL_PREMIX; @@ -354,15 +348,14 @@ public class Whirlpool { public void onMixSuccess(MixSuccessEvent e) { WalletUtxo walletUtxo = getUtxo(e.getWhirlpoolUtxo()); if(walletUtxo != null) { - log.debug("Mix success, new utxo " + e.getMixSuccess().getReceiveUtxo().getHash() + ":" + e.getMixSuccess().getReceiveUtxo().getIndex()); - persistMixData(); - Platform.runLater(() -> EventManager.get().post(new WhirlpoolMixSuccessEvent(walletUtxo.wallet, walletUtxo.utxo, e.getMixSuccess().getReceiveUtxo(), getReceiveNode(e, walletUtxo)))); + log.debug("Mix success, new utxo " + e.getReceiveUtxo().getHash() + ":" + e.getReceiveUtxo().getIndex()); + Platform.runLater(() -> EventManager.get().post(new WhirlpoolMixSuccessEvent(walletUtxo.wallet, walletUtxo.utxo, e.getReceiveUtxo(), getReceiveNode(e, walletUtxo)))); } } private WalletNode getReceiveNode(MixSuccessEvent e, WalletUtxo walletUtxo) { for(WalletNode walletNode : walletUtxo.wallet.getNode(KeyPurpose.RECEIVE).getChildren()) { - if(walletUtxo.wallet.getAddress(walletNode).toString().equals(e.getMixSuccess().getReceiveAddress())) { + if(walletUtxo.wallet.getAddress(walletNode).toString().equals(e.getMixProgress().getDestination().getAddress())) { return walletNode; } } diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowDataPersister.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowDataPersister.java new file mode 100644 index 00000000..81aa7726 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowDataPersister.java @@ -0,0 +1,51 @@ +package com.sparrowwallet.sparrow.whirlpool.dataPersister; + +import com.samourai.whirlpool.client.wallet.WhirlpoolWallet; +import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig; +import com.samourai.whirlpool.client.wallet.data.dataPersister.DataPersister; +import com.samourai.whirlpool.client.wallet.data.utxoConfig.UtxoConfigPersistedSupplier; +import com.samourai.whirlpool.client.wallet.data.utxoConfig.UtxoConfigSupplier; +import com.samourai.whirlpool.client.wallet.data.walletState.WalletStateSupplier; +import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowWalletStateSupplier; + +public class SparrowDataPersister implements DataPersister { + private WalletStateSupplier walletStateSupplier; + private UtxoConfigSupplier utxoConfigSupplier; + + public SparrowDataPersister(WhirlpoolWallet whirlpoolWallet) throws Exception { + WhirlpoolWalletConfig config = whirlpoolWallet.getConfig(); + String walletIdentifier = whirlpoolWallet.getWalletIdentifier(); + this.walletStateSupplier = new SparrowWalletStateSupplier(walletIdentifier, config.getExternalDestination()); + this.utxoConfigSupplier = new UtxoConfigPersistedSupplier(new SparrowUtxoConfigPersister(walletIdentifier)); + } + + @Override + public void open() throws Exception { + } + + @Override + public void close() throws Exception { + } + + @Override + public void load() throws Exception { + utxoConfigSupplier.load(); + walletStateSupplier.load(); + } + + @Override + public void persist(boolean force) throws Exception { + utxoConfigSupplier.persist(force); + walletStateSupplier.persist(force); + } + + @Override + public WalletStateSupplier getWalletStateSupplier() { + return walletStateSupplier; + } + + @Override + public UtxoConfigSupplier getUtxoConfigSupplier() { + return utxoConfigSupplier; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowUtxoConfigPersister.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowUtxoConfigPersister.java similarity index 78% rename from src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowUtxoConfigPersister.java rename to src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowUtxoConfigPersister.java index 8e24950b..7453ce87 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowUtxoConfigPersister.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowUtxoConfigPersister.java @@ -1,10 +1,10 @@ -package com.sparrowwallet.sparrow.whirlpool; +package com.sparrowwallet.sparrow.whirlpool.dataPersister; import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; -import com.samourai.whirlpool.client.wallet.data.utxo.UtxoConfigData; -import com.samourai.whirlpool.client.wallet.data.utxo.UtxoConfigPersisted; -import com.samourai.whirlpool.client.wallet.data.utxo.UtxoConfigPersister; +import com.samourai.whirlpool.client.wallet.data.utxoConfig.UtxoConfigData; +import com.samourai.whirlpool.client.wallet.data.utxoConfig.UtxoConfigPersisted; +import com.samourai.whirlpool.client.wallet.data.utxoConfig.UtxoConfigPersister; import com.sparrowwallet.drongo.protocol.Sha256Hash; import com.sparrowwallet.drongo.wallet.UtxoMixData; import com.sparrowwallet.drongo.wallet.Wallet; @@ -14,14 +14,14 @@ import com.sparrowwallet.sparrow.event.WalletUtxoMixesChangedEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.HashMap; +import java.util.Map; import java.util.stream.Collectors; public class SparrowUtxoConfigPersister extends UtxoConfigPersister { private static final Logger log = LoggerFactory.getLogger(SparrowUtxoConfigPersister.class); private final String walletId; - private long lastWrite; public SparrowUtxoConfigPersister(String walletId) { super(walletId); @@ -29,14 +29,14 @@ public class SparrowUtxoConfigPersister extends UtxoConfigPersister { } @Override - public synchronized UtxoConfigData load() throws Exception { + public synchronized UtxoConfigData read() throws Exception { Wallet wallet = getWallet(); if(wallet == null) { throw new IllegalStateException("Can't find wallet with walletId " + walletId); } Map utxoConfigs = wallet.getUtxoMixes().entrySet().stream() - .collect(Collectors.toMap(entry -> entry.getKey().toString(), entry -> new UtxoConfigPersisted(entry.getValue().getPoolId(), entry.getValue().getMixesDone(), entry.getValue().getForwarding()), + .collect(Collectors.toMap(entry -> entry.getKey().toString(), entry -> new UtxoConfigPersisted(entry.getValue().getMixesDone(), entry.getValue().getExpired()), (u, v) -> { throw new IllegalStateException("Duplicate utxo config hashes"); }, HashMap::new)); @@ -44,7 +44,7 @@ public class SparrowUtxoConfigPersister extends UtxoConfigPersister { } @Override - public synchronized void write(UtxoConfigData data) throws Exception { + protected void doWrite(UtxoConfigData data) throws Exception { Wallet wallet = getWallet(); if(wallet == null) { //Wallet is already closed @@ -53,7 +53,7 @@ public class SparrowUtxoConfigPersister extends UtxoConfigPersister { Map currentData = new HashMap<>(data.getUtxoConfigs()); Map changedUtxoMixes = currentData.entrySet().stream() - .collect(Collectors.toMap(entry -> Sha256Hash.wrap(entry.getKey()), entry -> new UtxoMixData(entry.getValue().getPoolId(), entry.getValue().getMixsDone(), entry.getValue().getForwarding()), + .collect(Collectors.toMap(entry -> Sha256Hash.wrap(entry.getKey()), entry -> new UtxoMixData(entry.getValue().getMixsDone(), entry.getValue().getExpired()), (u, v) -> { throw new IllegalStateException("Duplicate utxo config hashes"); }, HashMap::new)); @@ -63,15 +63,9 @@ public class SparrowUtxoConfigPersister extends UtxoConfigPersister { wallet.getUtxoMixes().keySet().removeAll(removedUtxoMixes.keySet()); EventManager.get().post(new WalletUtxoMixesChangedEvent(wallet, changedUtxoMixes, removedUtxoMixes)); - lastWrite = System.currentTimeMillis(); } private Wallet getWallet() { return AppServices.get().getOpenWallets().entrySet().stream().filter(entry -> entry.getValue().getWalletId(entry.getKey()).equals(walletId)).map(Map.Entry::getKey).findFirst().orElse(null); } - - @Override - public long getLastWrite() { - return lastWrite; - } } diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowDataSource.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowDataSource.java similarity index 68% rename from src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowDataSource.java rename to src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowDataSource.java index fab9b4ad..a90adae5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowDataSource.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowDataSource.java @@ -1,11 +1,12 @@ -package com.sparrowwallet.sparrow.whirlpool; +package com.sparrowwallet.sparrow.whirlpool.dataSource; +import com.google.common.eventbus.Subscribe; +import com.samourai.wallet.api.backend.MinerFee; import com.samourai.wallet.api.backend.MinerFeeTarget; -import com.samourai.wallet.api.backend.beans.TxsResponse; import com.samourai.wallet.api.backend.beans.UnspentOutput; import com.samourai.wallet.api.backend.beans.WalletResponse; import com.samourai.wallet.hd.HD_Wallet; -import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig; +import com.samourai.whirlpool.client.wallet.WhirlpoolWallet; import com.samourai.whirlpool.client.wallet.data.dataPersister.DataPersister; import com.samourai.whirlpool.client.wallet.data.dataSource.WalletResponseDataSource; import com.samourai.whirlpool.client.wallet.data.minerFee.MinerFeeSupplier; @@ -13,14 +14,21 @@ import com.sparrowwallet.drongo.ExtendedKey; import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.Network; import com.sparrowwallet.drongo.Utils; -import com.sparrowwallet.drongo.address.Address; -import com.sparrowwallet.drongo.protocol.*; +import com.sparrowwallet.drongo.protocol.Sha256Hash; +import com.sparrowwallet.drongo.protocol.Transaction; +import com.sparrowwallet.drongo.protocol.TransactionInput; +import com.sparrowwallet.drongo.protocol.TransactionOutput; import com.sparrowwallet.drongo.wallet.BlockTransaction; import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.WalletNode; import com.sparrowwallet.sparrow.AppServices; +import com.sparrowwallet.sparrow.EventManager; +import com.sparrowwallet.sparrow.event.NewBlockEvent; +import com.sparrowwallet.sparrow.event.NewWalletTransactionsEvent; +import com.sparrowwallet.sparrow.event.WalletAddressesChangedEvent; import com.sparrowwallet.sparrow.net.ElectrumServer; +import com.sparrowwallet.sparrow.whirlpool.Whirlpool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,12 +38,23 @@ public class SparrowDataSource extends WalletResponseDataSource { private static final Logger log = LoggerFactory.getLogger(SparrowDataSource.class); public SparrowDataSource( - WhirlpoolWalletConfig config, + WhirlpoolWallet whirlpoolWallet, HD_Wallet bip44w, - String walletIdentifier, DataPersister dataPersister) throws Exception { - super(config, bip44w, walletIdentifier, dataPersister); + super(whirlpoolWallet, bip44w, dataPersister); + } + + @Override + public void open() throws Exception { + super.open(); + EventManager.get().register(this); + } + + @Override + public void close() throws Exception { + EventManager.get().unregister(this); + super.close(); } @Override @@ -134,8 +153,9 @@ public class SparrowDataSource extends WalletResponseDataSource { walletResponse.info.latest_block.time = AppServices.getLatestBlockHeader() == null ? 1 : AppServices.getLatestBlockHeader().getTime(); walletResponse.info.fees = new LinkedHashMap<>(); + MinerFee minerFee = getMinerFeeSupplier().getValue(); for(MinerFeeTarget target : MinerFeeTarget.values()) { - walletResponse.info.fees.put(target.getValue(), getMinerFeeSupplier().getFee(target)); + walletResponse.info.fees.put(target.getValue(), minerFee.get(target)); } return walletResponse; @@ -148,77 +168,6 @@ public class SparrowDataSource extends WalletResponseDataSource { electrumServer.broadcastTransactionPrivately(transaction); } - @Override - public TxsResponse fetchTxs(String[] zpubs, int page, int count) throws Exception { - List txes = new ArrayList<>(); - - for(String zpub : zpubs) { - Wallet wallet = getWallet(zpub); - if(wallet == null) { - log.debug("No wallet for " + zpub + " found"); - continue; - } - - for(BlockTransaction blockTransaction : wallet.getTransactions().values()) { - TxsResponse.Tx tx = new TxsResponse.Tx(); - tx.block_height = blockTransaction.getHeight(); - tx.hash = blockTransaction.getHashAsString(); - tx.locktime = blockTransaction.getTransaction().getLocktime(); - tx.time = blockTransaction.getDate().getTime(); - tx.version = (int)blockTransaction.getTransaction().getVersion(); - - tx.inputs = new TxsResponse.TxInput[blockTransaction.getTransaction().getInputs().size()]; - for(int i = 0; i < blockTransaction.getTransaction().getInputs().size(); i++) { - TransactionInput txInput = blockTransaction.getTransaction().getInputs().get(i); - tx.inputs[i] = new TxsResponse.TxInput(); - tx.inputs[i].vin = txInput.getIndex(); - tx.inputs[i].sequence = txInput.getSequenceNumber(); - tx.inputs[i].prev_out = new TxsResponse.TxOut(); - tx.inputs[i].prev_out.txid = txInput.getOutpoint().getHash().toString(); - tx.inputs[i].prev_out.vout = (int)txInput.getOutpoint().getIndex(); - - BlockTransaction spentTransaction = wallet.getTransactions().get(txInput.getOutpoint().getHash()); - if(spentTransaction != null) { - TransactionOutput spentOutput = spentTransaction.getTransaction().getOutputs().get((int)txInput.getOutpoint().getIndex()); - tx.inputs[i].prev_out.value = spentOutput.getValue(); - Address[] addresses = spentOutput.getScript().getToAddresses(); - if(addresses.length > 0) { - tx.inputs[i].prev_out.addr = addresses[0].toString(); - } - } - } - - tx.out = new TxsResponse.TxOutput[blockTransaction.getTransaction().getOutputs().size()]; - for(int i = 0; i < blockTransaction.getTransaction().getOutputs().size(); i++) { - TransactionOutput txOutput = blockTransaction.getTransaction().getOutputs().get(i); - tx.out[i].n = txOutput.getIndex(); - tx.out[i].value = txOutput.getValue(); - Address[] addresses = txOutput.getScript().getToAddresses(); - if(addresses.length > 0) { - tx.out[i].addr = addresses[0].toString(); - } - } - - txes.add(tx); - } - } - - List pageTxes; - if(txes.size() < count) { - pageTxes = txes; - } else { - pageTxes = txes.subList(page * count, Math.min((page * count) + count, txes.size())); - } - - TxsResponse txsResponse = new TxsResponse(); - txsResponse.n_tx = txes.size(); - txsResponse.page = page; - txsResponse.n_tx_page = pageTxes.size(); - txsResponse.txs = pageTxes.toArray(new TxsResponse.Tx[0]); - - return txsResponse; - } - @Override public MinerFeeSupplier getMinerFeeSupplier() { return SparrowMinerFeeSupplier.getInstance(); @@ -236,4 +185,31 @@ public class SparrowDataSource extends WalletResponseDataSource { .findFirst() .orElse(null); } + + @Subscribe + public void newWalletTransactions(NewWalletTransactionsEvent event) { + try { + refresh(); + } catch (Exception e) { + log.error("", e); + } + } + + @Subscribe + public void walletAddressesChanged(WalletAddressesChangedEvent event) { + try { + refresh(); + } catch (Exception e) { + log.error("", e); + } + } + + @Subscribe + public void newBlock(NewBlockEvent event) { + try { + refresh(); + } catch (Exception e) { + log.error("", e); + } + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowIndexHandler.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowIndexHandler.java new file mode 100644 index 00000000..1737d7f2 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowIndexHandler.java @@ -0,0 +1,34 @@ +package com.sparrowwallet.sparrow.whirlpool.dataSource; + +import com.samourai.wallet.client.indexHandler.AbstractIndexHandler; +import com.sparrowwallet.drongo.wallet.WalletNode; + +public class SparrowIndexHandler extends AbstractIndexHandler { + private WalletNode walletNode; + private int defaultValue; + + public SparrowIndexHandler(WalletNode walletNode) { + this(walletNode, 0); + } + + public SparrowIndexHandler(WalletNode walletNode, int defaultValue) { + this.walletNode = walletNode; + this.defaultValue = defaultValue; + } + + @Override + public synchronized int get() { + Integer currentIndex = walletNode.getHighestUsedIndex(); + int nextIndex = currentIndex == null ? defaultValue : currentIndex + 1; + return nextIndex; + } + + @Override + public synchronized int getAndIncrement() { + return get(); + } + + @Override + public synchronized void set(int value) { + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowMinerFeeSupplier.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowMinerFeeSupplier.java similarity index 75% rename from src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowMinerFeeSupplier.java rename to src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowMinerFeeSupplier.java index 1883d9b5..12a2a387 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowMinerFeeSupplier.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowMinerFeeSupplier.java @@ -1,13 +1,11 @@ -package com.sparrowwallet.sparrow.whirlpool; +package com.sparrowwallet.sparrow.whirlpool.dataSource; +import com.samourai.wallet.api.backend.MinerFee; import com.samourai.wallet.api.backend.MinerFeeTarget; import com.samourai.whirlpool.client.wallet.data.minerFee.MinerFeeSupplier; import com.sparrowwallet.sparrow.AppServices; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; public class SparrowMinerFeeSupplier implements MinerFeeSupplier { private static final int FALLBACK_FEE_RATE = 75; @@ -32,6 +30,15 @@ public class SparrowMinerFeeSupplier implements MinerFeeSupplier { return getMinimumFeeForTarget(Integer.parseInt(feeTarget.getValue())); } + @Override + public MinerFee getValue() { + Map fees = new LinkedHashMap<>(); + for (MinerFeeTarget minerFeeTarget : MinerFeeTarget.values()) { + fees.put(minerFeeTarget.getValue(), getFee(minerFeeTarget)); + } + return new MinerFee(fees); + } + private Integer getMinimumFeeForTarget(int targetBlocks) { List> feeRates = new ArrayList<>(AppServices.getTargetBlockFeeRates().entrySet()); Collections.reverse(feeRates); diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowWalletStateSupplier.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowWalletStateSupplier.java new file mode 100644 index 00000000..6cf7c7d5 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowWalletStateSupplier.java @@ -0,0 +1,92 @@ +package com.sparrowwallet.sparrow.whirlpool.dataSource; + +import com.samourai.wallet.client.indexHandler.IIndexHandler; +import com.samourai.wallet.hd.AddressType; +import com.samourai.wallet.hd.Chain; +import com.samourai.whirlpool.client.wallet.beans.ExternalDestination; +import com.samourai.whirlpool.client.wallet.beans.WhirlpoolAccount; +import com.samourai.whirlpool.client.wallet.data.walletState.WalletStateSupplier; +import com.sparrowwallet.drongo.KeyPurpose; +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.drongo.wallet.WalletNode; +import com.sparrowwallet.sparrow.whirlpool.Whirlpool; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class SparrowWalletStateSupplier implements WalletStateSupplier { + private String walletId; + private Map indexHandlerWallets; + // private int externalIndexDefault; + + public SparrowWalletStateSupplier(String walletId, ExternalDestination externalDestination) throws Exception { + this.walletId = walletId; + this.indexHandlerWallets = new LinkedHashMap(); + // this.externalIndexDefault = externalDestination != null ? externalDestination.getStartIndex() : 0; + } + + @Override + public IIndexHandler getIndexHandlerWallet(WhirlpoolAccount whirlpoolAccount, AddressType addressType, Chain chain) { + String key = mapKey(whirlpoolAccount, addressType, chain); + IIndexHandler indexHandler = indexHandlerWallets.get(key); + if (indexHandler == null) { + WalletNode walletNode = findWalletNode(whirlpoolAccount, addressType, chain); + indexHandler = new SparrowIndexHandler(walletNode, 0); + indexHandlerWallets.put(key, indexHandler); + } + return indexHandler; + } + + @Override + public IIndexHandler getIndexHandlerExternal() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isInitialized() { + return true; + } + + @Override + public void setInitialized(boolean b) { + // nothing required + } + + @Override + public void setWalletIndex(WhirlpoolAccount whirlpoolAccount, AddressType addressType, Chain chain, int i) throws Exception { + // nothing required + } + + @Override + public void load() throws Exception { + // nothing required + } + + @Override + public boolean persist(boolean b) throws Exception { + // nothing required + return false; + } + + private String mapKey(WhirlpoolAccount whirlpoolAccount, AddressType addressType, Chain chain) { + return whirlpoolAccount.name()+"_"+addressType.getPurpose()+"_"+chain.getIndex(); + } + + private WalletNode findWalletNode(WhirlpoolAccount whirlpoolAccount, AddressType addressType, Chain chain) { + Wallet wallet = getWallet(); + if(wallet == null) { + throw new IllegalStateException("Can't find wallet with walletId " + walletId); + } + + // account + Wallet childWallet = Whirlpool.getStandardAccountWallet(whirlpoolAccount, wallet); + + // purpose + KeyPurpose keyPurpose = chain == Chain.RECEIVE ? KeyPurpose.RECEIVE : KeyPurpose.CHANGE; + return childWallet.getNode(keyPurpose); + } + + private Wallet getWallet() { + return Whirlpool.getWallet(walletId); + } +} diff --git a/src/main/resources/com/sparrowwallet/sparrow/sql/V3__Whirlpool.sql b/src/main/resources/com/sparrowwallet/sparrow/sql/V3__Whirlpool.sql new file mode 100644 index 00000000..411d9389 --- /dev/null +++ b/src/main/resources/com/sparrowwallet/sparrow/sql/V3__Whirlpool.sql @@ -0,0 +1,2 @@ +drop table if exists utxoMixData; +create table utxoMixData (id identity not null, hash binary(32) not null, mixesDone integer not null default 0, expired bigint, wallet bigint not null); From 4577a64ad5d9c6429d6f6b7db272788fa91db4f7 Mon Sep 17 00:00:00 2001 From: zeroleak Date: Sun, 29 Aug 2021 10:49:25 +0200 Subject: [PATCH 3/3] apply Craig's feedback - use V2__Whirlpool.sql - allow pools list & tx0 preview without seed - filter DataSource wallet for refresh --- .../sparrow/whirlpool/Whirlpool.java | 29 ++++++++++++----- .../dataSource/SparrowDataSource.java | 32 ++++++++++++------- .../sparrow/sql/V2__Whirlpool.sql | 3 +- .../sparrow/sql/V3__Whirlpool.sql | 2 -- 4 files changed, 43 insertions(+), 23 deletions(-) delete mode 100644 src/main/resources/com/sparrowwallet/sparrow/sql/V3__Whirlpool.sql diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java index 167680f2..e8c73ebc 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java @@ -7,9 +7,7 @@ import com.samourai.wallet.api.backend.beans.UnspentOutput; import com.samourai.wallet.hd.HD_Wallet; import com.samourai.wallet.hd.HD_WalletFactoryGeneric; import com.samourai.whirlpool.client.event.*; -import com.samourai.whirlpool.client.tx0.Tx0; -import com.samourai.whirlpool.client.tx0.Tx0Config; -import com.samourai.whirlpool.client.tx0.Tx0Preview; +import com.samourai.whirlpool.client.tx0.*; import com.samourai.whirlpool.client.wallet.WhirlpoolEventService; import com.samourai.whirlpool.client.wallet.WhirlpoolWallet; import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig; @@ -17,6 +15,7 @@ import com.samourai.whirlpool.client.wallet.WhirlpoolWalletService; import com.samourai.whirlpool.client.wallet.beans.*; import com.samourai.whirlpool.client.wallet.data.dataPersister.DataPersisterFactory; import com.samourai.whirlpool.client.wallet.data.dataSource.DataSourceFactory; +import com.samourai.whirlpool.client.wallet.data.pool.PoolData; import com.samourai.whirlpool.client.wallet.data.utxo.UtxoSupplier; import com.samourai.whirlpool.client.wallet.data.utxoConfig.UtxoConfig; import com.samourai.whirlpool.client.whirlpool.ServerApi; @@ -40,6 +39,7 @@ import com.sparrowwallet.sparrow.event.WhirlpoolMixSuccessEvent; import com.sparrowwallet.sparrow.wallet.UtxoEntry; import com.sparrowwallet.sparrow.whirlpool.dataPersister.SparrowDataPersister; import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowDataSource; +import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowMinerFeeSupplier; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -98,19 +98,21 @@ public class Whirlpool { } public Collection getPools() throws Exception { - WhirlpoolWallet whirlpoolWallet = getWhirlpoolWallet(); - return whirlpoolWallet.getPoolSupplier().getPools(); + Tx0ParamService tx0ParamService = getTx0ParamService(); + PoolData poolData = new PoolData(config.getServerApi().fetchPools(), tx0ParamService); + return poolData.getPools(); } public Tx0Preview getTx0Preview(Pool pool, Collection utxos) throws Exception { - WhirlpoolWallet whirlpoolWallet = getWhirlpoolWallet(); - Tx0Config tx0Config = new Tx0Config(); tx0Config.setChangeWallet(WhirlpoolAccount.BADBANK); Tx0FeeTarget tx0FeeTarget = Tx0FeeTarget.BLOCKS_4; Tx0FeeTarget mixFeeTarget = Tx0FeeTarget.BLOCKS_4; - return whirlpoolWallet.tx0Preview(pool, tx0Config, utxos, tx0FeeTarget, mixFeeTarget); + Tx0ParamService tx0ParamService = getTx0ParamService(); + + Tx0Service tx0Service = new Tx0Service(config); + return tx0Service.tx0Preview(utxos, tx0Config, tx0ParamService.getTx0Param(pool, tx0FeeTarget, mixFeeTarget)); } public Tx0 broadcastTx0(Pool pool, Collection utxos) throws Exception { @@ -131,6 +133,17 @@ public class Whirlpool { return whirlpoolWallet.tx0(whirlpoolUtxos, pool, tx0Config, tx0FeeTarget, mixFeeTarget); } + private Tx0ParamService getTx0ParamService() { + try { + SparrowMinerFeeSupplier minerFeeSupplier = SparrowMinerFeeSupplier.getInstance(); + return new Tx0ParamService(minerFeeSupplier, config); + } catch(Exception e) { + log.error("Error fetching miner fees", e); + } + + return null; + } + public void setHDWallet(String walletId, Wallet wallet) { if(wallet.isEncrypted()) { throw new IllegalStateException("Wallet cannot be encrypted"); diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowDataSource.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowDataSource.java index a90adae5..6d3233d5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowDataSource.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowDataSource.java @@ -25,8 +25,8 @@ import com.sparrowwallet.drongo.wallet.WalletNode; import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.event.NewBlockEvent; -import com.sparrowwallet.sparrow.event.NewWalletTransactionsEvent; import com.sparrowwallet.sparrow.event.WalletAddressesChangedEvent; +import com.sparrowwallet.sparrow.event.WalletHistoryChangedEvent; import com.sparrowwallet.sparrow.net.ElectrumServer; import com.sparrowwallet.sparrow.whirlpool.Whirlpool; import org.slf4j.Logger; @@ -37,12 +37,17 @@ import java.util.*; public class SparrowDataSource extends WalletResponseDataSource { private static final Logger log = LoggerFactory.getLogger(SparrowDataSource.class); + private final String walletIdentifierPrefix; + public SparrowDataSource( WhirlpoolWallet whirlpoolWallet, HD_Wallet bip44w, DataPersister dataPersister) throws Exception { super(whirlpoolWallet, bip44w, dataPersister); + + // prefix matching :master, :Premix, :Postmix + this.walletIdentifierPrefix = getWhirlpoolWallet().getWalletIdentifier().replace(":master", ""); } @Override @@ -187,21 +192,13 @@ public class SparrowDataSource extends WalletResponseDataSource { } @Subscribe - public void newWalletTransactions(NewWalletTransactionsEvent event) { - try { - refresh(); - } catch (Exception e) { - log.error("", e); - } + public void walletHistoryChanged(WalletHistoryChangedEvent event) { + refreshWallet(event.getWalletId()); } @Subscribe public void walletAddressesChanged(WalletAddressesChangedEvent event) { - try { - refresh(); - } catch (Exception e) { - log.error("", e); - } + refreshWallet(event.getWalletId()); } @Subscribe @@ -212,4 +209,15 @@ public class SparrowDataSource extends WalletResponseDataSource { log.error("", e); } } + + private void refreshWallet(String eventWalletId) { + try { + // match :master, :Premix, :Postmix + if (eventWalletId.startsWith(walletIdentifierPrefix)) { + refresh(); + } + } catch (Exception e) { + log.error("", e); + } + } } diff --git a/src/main/resources/com/sparrowwallet/sparrow/sql/V2__Whirlpool.sql b/src/main/resources/com/sparrowwallet/sparrow/sql/V2__Whirlpool.sql index aa34a95f..411d9389 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/sql/V2__Whirlpool.sql +++ b/src/main/resources/com/sparrowwallet/sparrow/sql/V2__Whirlpool.sql @@ -1 +1,2 @@ -create table utxoMixData (id identity not null, hash binary(32) not null, poolId varchar(32), mixesDone integer not null default 0, forwarding bigint, wallet bigint not null); +drop table if exists utxoMixData; +create table utxoMixData (id identity not null, hash binary(32) not null, mixesDone integer not null default 0, expired bigint, wallet bigint not null); diff --git a/src/main/resources/com/sparrowwallet/sparrow/sql/V3__Whirlpool.sql b/src/main/resources/com/sparrowwallet/sparrow/sql/V3__Whirlpool.sql deleted file mode 100644 index 411d9389..00000000 --- a/src/main/resources/com/sparrowwallet/sparrow/sql/V3__Whirlpool.sql +++ /dev/null @@ -1,2 +0,0 @@ -drop table if exists utxoMixData; -create table utxoMixData (id identity not null, hash binary(32) not null, mixesDone integer not null default 0, expired bigint, wallet bigint not null);