diff --git a/build.gradle b/build.gradle index 0fdf8173..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') @@ -395,25 +395,30 @@ 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.client.indexHandler') 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.handler') 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 98fd6160..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, 15); + 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/SparrowMinerFeeSupplier.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowMinerFeeSupplier.java deleted file mode 100644 index 68cd7569..00000000 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowMinerFeeSupplier.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.sparrowwallet.sparrow.whirlpool; - -import com.samourai.wallet.api.backend.MinerFee; -import com.samourai.whirlpool.client.wallet.data.minerFee.MinerFeeSupplier; - -public class SparrowMinerFeeSupplier extends MinerFeeSupplier { - public SparrowMinerFeeSupplier(int feeMin, int feeMax, int feeFallback, MinerFee currentMinerFee) { - super(feeMin, feeMax, feeFallback); - setValue(currentMinerFee); - } -} 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 ce2279e5..e8c73ebc 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java @@ -3,19 +3,21 @@ 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.wallet.hd.HD_WalletFactoryGeneric; import com.samourai.whirlpool.client.event.*; 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.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.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; @@ -35,6 +37,9 @@ 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 com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowMinerFeeSupplier; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -43,7 +48,10 @@ 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.Map; +import java.util.Objects; import java.util.stream.Collectors; public class Whirlpool { @@ -54,32 +62,36 @@ 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, int clientDelay) { + 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(); - this.whirlpoolWalletService = new SparrowWhirlpoolWalletService(); - this.config = computeWhirlpoolWalletConfig(sCode, maxClients, clientDelay); + + this.whirlpoolWalletService = new WhirlpoolWalletService(); + this.config = computeWhirlpoolWalletConfig(sCode); WhirlpoolEventService.getInstance().register(this); } - private WhirlpoolWalletConfig computeWhirlpoolWalletConfig(String sCode, int maxClients, int clientDelay) { + 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); - BackendApi backendApi = new SparrowBackendApi(); - WhirlpoolWalletConfig whirlpoolWalletConfig = new WhirlpoolWalletConfig(httpClientService, stompClientService, torClientService, serverApi, whirlpoolServer, false, backendApi); + WhirlpoolWalletConfig whirlpoolWalletConfig = new WhirlpoolWalletConfig(dataSourceFactory, httpClientService, stompClientService, torClientService, serverApi, whirlpoolServer.getParams(), false); + whirlpoolWalletConfig.setDataPersisterFactory(dataPersisterFactory); whirlpoolWalletConfig.setScode(sCode); return whirlpoolWalletConfig; @@ -123,7 +135,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); @@ -143,10 +155,10 @@ 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); - whirlpoolWalletService.setWalletId(walletId); - hdWallet = new HD_Wallet(purpose, words, whirlpoolServer, seed, passphrase, 1); + 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); } @@ -162,7 +174,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); } @@ -176,23 +189,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"); @@ -200,7 +206,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); } @@ -260,12 +266,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) { @@ -277,6 +280,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; @@ -346,15 +361,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/SparrowBackendApi.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowDataSource.java similarity index 57% rename from src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowBackendApi.java rename to src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowDataSource.java index fc36d06f..6d3233d5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowBackendApi.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowDataSource.java @@ -1,108 +1,69 @@ -package com.sparrowwallet.sparrow.whirlpool; +package com.sparrowwallet.sparrow.whirlpool.dataSource; -import com.samourai.wallet.api.backend.BackendApi; +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.*; +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.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; 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.WalletAddressesChangedEvent; +import com.sparrowwallet.sparrow.event.WalletHistoryChangedEvent; import com.sparrowwallet.sparrow.net.ElectrumServer; +import com.sparrowwallet.sparrow.whirlpool.Whirlpool; import org.slf4j.Logger; 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); + 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 - 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; + public void open() throws Exception { + super.open(); + EventManager.get().register(this); } @Override - public WalletResponse fetchWallet(String[] zpubs) throws Exception { + public void close() throws Exception { + EventManager.get().unregister(this); + super.close(); + } + + @Override + protected WalletResponse fetchWalletResponse() throws Exception { WalletResponse walletResponse = new WalletResponse(); walletResponse.wallet = new WalletResponse.Wallet(); @@ -113,6 +74,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) { @@ -196,23 +158,14 @@ public class SparrowBackendApi extends BackendApi { 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(), AppServices.getTargetBlockFeeRates() == null ? FALLBACK_FEE_RATE : getMinimumFeeForTarget(Integer.parseInt(target.getValue()))); + walletResponse.info.fees.put(target.getValue(), minerFee.get(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 +174,8 @@ public class SparrowBackendApi extends BackendApi { } @Override - public boolean testConnectivity() { - return AppServices.isConnected(); - } - - 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(); - } - - @Override - public void initBip84(String zpub) throws Exception { - //nothing required + public MinerFeeSupplier getMinerFeeSupplier() { + return SparrowMinerFeeSupplier.getInstance(); } private Wallet getWallet(String zpub) { @@ -255,23 +191,33 @@ public class SparrowBackendApi extends BackendApi { .orElse(null); } - @Override - public List fetchUtxos(String zpub) throws Exception { - throw new UnsupportedOperationException(); + @Subscribe + public void walletHistoryChanged(WalletHistoryChangedEvent event) { + refreshWallet(event.getWalletId()); } - @Override - public List fetchUtxos(String[] zpubs) throws Exception { - throw new UnsupportedOperationException(); + @Subscribe + public void walletAddressesChanged(WalletAddressesChangedEvent event) { + refreshWallet(event.getWalletId()); } - @Override - public Map fetchAddresses(String[] zpubs) throws Exception { - throw new UnsupportedOperationException(); + @Subscribe + public void newBlock(NewBlockEvent event) { + try { + refresh(); + } catch (Exception e) { + log.error("", e); + } } - @Override - public MultiAddrResponse.Address fetchAddress(String zpub) throws Exception { - throw new UnsupportedOperationException(); + 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/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/dataSource/SparrowMinerFeeSupplier.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowMinerFeeSupplier.java new file mode 100644 index 00000000..12a2a387 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowMinerFeeSupplier.java @@ -0,0 +1,53 @@ +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.*; + +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())); + } + + @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); + 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/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/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);