Browse Source

Merge pull request #183 from zeroleak/whirlpool-client-0.23.30-early4

Upgrade to whirlpool-client 0.23.30-early4 + extlibj 0.0.19-dsk3
terminal
craigraw 3 years ago
committed by GitHub
parent
commit
f30c00ba8f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      build.gradle
  2. 2
      src/main/java/com/sparrowwallet/sparrow/AppServices.java
  3. 6
      src/main/java/com/sparrowwallet/sparrow/control/MixStatusCell.java
  4. 16
      src/main/java/com/sparrowwallet/sparrow/io/db/UtxoMixDataDao.java
  5. 6
      src/main/java/com/sparrowwallet/sparrow/io/db/UtxoMixDataMapper.java
  6. 6
      src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java
  7. 11
      src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowMinerFeeSupplier.java
  8. 24
      src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWalletDataSupplier.java
  9. 53
      src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWalletStatePersister.java
  10. 23
      src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWhirlpoolWalletService.java
  11. 90
      src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java
  12. 51
      src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowDataPersister.java
  13. 26
      src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowUtxoConfigPersister.java
  14. 188
      src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowDataSource.java
  15. 34
      src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowIndexHandler.java
  16. 53
      src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowMinerFeeSupplier.java
  17. 92
      src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowWalletStateSupplier.java
  18. 3
      src/main/resources/com/sparrowwallet/sparrow/sql/V2__Whirlpool.sql

13
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')

2
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);
}

6
src/main/java/com/sparrowwallet/sparrow/control/MixStatusCell.java

@ -40,9 +40,9 @@ public class MixStatusCell extends TreeTableCell<Entry, UtxoEntry.MixStatus> {
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<Entry, UtxoEntry.MixStatus> {
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));

16
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<Sha256Hash, UtxoMixData> 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<Sha256Hash, UtxoMixData> 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 (<ids>)")
void deleteUtxoMixData(@BindList("ids") List<Long> ids);
@ -45,11 +45,11 @@ public interface UtxoMixDataDao {
Map<Sha256Hash, UtxoMixData> 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);
}
}

6
src/main/java/com/sparrowwallet/sparrow/io/db/UtxoMixDataMapper.java

@ -14,12 +14,12 @@ public class UtxoMixDataMapper implements RowMapper<Map.Entry<Sha256Hash, UtxoMi
public Map.Entry<Sha256Hash, UtxoMixData> 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<>() {

6
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;
}

11
src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowMinerFeeSupplier.java

@ -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);
}
}

24
src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWalletDataSupplier.java

@ -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);
}
}

53
src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWalletStatePersister.java

@ -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<String, Integer> 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<String, Integer> 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
}
}

23
src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowWhirlpoolWalletService.java

@ -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;
}
}

90
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<String> 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;
}
}

51
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;
}
}

26
src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowUtxoConfigPersister.java → 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<String, UtxoConfigPersisted> 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<String, UtxoConfigPersisted> currentData = new HashMap<>(data.getUtxoConfigs());
Map<Sha256Hash, UtxoMixData> 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;
}
}

188
src/main/java/com/sparrowwallet/sparrow/whirlpool/SparrowBackendApi.java → 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);
}
@Override
public TxsResponse fetchTxs(String[] zpubs, int page, int count) throws Exception {
List<TxsResponse.Tx> 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();
private final String walletIdentifierPrefix;
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();
}
}
}
public SparrowDataSource(
WhirlpoolWallet whirlpoolWallet,
HD_Wallet bip44w,
DataPersister dataPersister)
throws Exception {
super(whirlpoolWallet, bip44w, dataPersister);
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<TxsResponse.Tx> pageTxes;
if(txes.size() < count) {
pageTxes = txes;
} else {
pageTxes = txes.subList(page * count, Math.min((page * count) + count, txes.size()));
}
// prefix matching <prefix>:master, :Premix, :Postmix
this.walletIdentifierPrefix = getWhirlpoolWallet().getWalletIdentifier().replace(":master", "");
}
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]);
@Override
public void open() throws Exception {
super.open();
EventManager.get().register(this);
}
return txsResponse;
@Override
public void close() throws Exception {
EventManager.get().unregister(this);
super.close();
}
@Override
public WalletResponse fetchWallet(String[] zpubs) throws Exception {
protected WalletResponse fetchWalletResponse() throws Exception {
WalletResponse walletResponse = new WalletResponse();
walletResponse.wallet = new WalletResponse.Wallet();
@ -113,6 +74,7 @@ public class SparrowBackendApi extends BackendApi {
List<UnspentOutput> 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<String, Integer> 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<Map.Entry<Integer, Double>> feeRates = new ArrayList<>(AppServices.getTargetBlockFeeRates().entrySet());
Collections.reverse(feeRates);
for(Map.Entry<Integer, Double> 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<UnspentOutput> fetchUtxos(String zpub) throws Exception {
throw new UnsupportedOperationException();
@Subscribe
public void walletHistoryChanged(WalletHistoryChangedEvent event) {
refreshWallet(event.getWalletId());
}
@Override
public List<UnspentOutput> fetchUtxos(String[] zpubs) throws Exception {
throw new UnsupportedOperationException();
@Subscribe
public void walletAddressesChanged(WalletAddressesChangedEvent event) {
refreshWallet(event.getWalletId());
}
@Override
public Map<String, MultiAddrResponse.Address> 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 <prefix>:master, :Premix, :Postmix
if (eventWalletId.startsWith(walletIdentifierPrefix)) {
refresh();
}
} catch (Exception e) {
log.error("", e);
}
}
}

34
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) {
}
}

53
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<String, Integer> fees = new LinkedHashMap<>();
for (MinerFeeTarget minerFeeTarget : MinerFeeTarget.values()) {
fees.put(minerFeeTarget.getValue(), getFee(minerFeeTarget));
}
return new MinerFee(fees);
}
private Integer getMinimumFeeForTarget(int targetBlocks) {
List<Map.Entry<Integer, Double>> feeRates = new ArrayList<>(AppServices.getTargetBlockFeeRates().entrySet());
Collections.reverse(feeRates);
for(Map.Entry<Integer, Double> feeRate : feeRates) {
if(feeRate.getKey() <= targetBlocks) {
return feeRate.getValue().intValue();
}
}
return feeRates.get(0).getValue().intValue();
}
}

92
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<String, IIndexHandler> 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);
}
}

3
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);

Loading…
Cancel
Save