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
This commit is contained in:
craigraw 2021-08-31 10:57:49 +02:00 committed by GitHub
commit f30c00ba8f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 387 additions and 312 deletions

View file

@ -91,7 +91,7 @@ dependencies {
implementation('org.slf4j:jul-to-slf4j:1.7.30') { implementation('org.slf4j:jul-to-slf4j:1.7.30') {
exclude group: 'org.slf4j' 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') testImplementation('junit:junit:4.12')
} }
@ -387,7 +387,7 @@ extraJavaModuleInfo {
module('cbor-0.9.jar', 'co.nstant.in.cbor', '0.9') { module('cbor-0.9.jar', 'co.nstant.in.cbor', '0.9') {
exports('co.nstant.in.cbor') 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('com.google.common')
requires('net.sourceforge.streamsupport') requires('net.sourceforge.streamsupport')
requires('org.slf4j') requires('org.slf4j')
@ -395,25 +395,30 @@ extraJavaModuleInfo {
requires('com.fasterxml.jackson.databind') requires('com.fasterxml.jackson.databind')
requires('logback.classic') requires('logback.classic')
requires('org.json') requires('org.json')
exports('com.sparrowwallet.nightjar')
exports('com.samourai.http.client') exports('com.samourai.http.client')
exports('com.samourai.tor.client') exports('com.samourai.tor.client')
exports('com.samourai.wallet.api.backend') exports('com.samourai.wallet.api.backend')
exports('com.samourai.wallet.api.backend.beans') exports('com.samourai.wallet.api.backend.beans')
exports('com.samourai.wallet.client.indexHandler')
exports('com.samourai.wallet.hd') exports('com.samourai.wallet.hd')
exports('com.samourai.wallet.hd.java')
exports('com.samourai.whirlpool.client.event') exports('com.samourai.whirlpool.client.event')
exports('com.samourai.whirlpool.client.wallet') exports('com.samourai.whirlpool.client.wallet')
exports('com.samourai.whirlpool.client.wallet.beans') 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')
exports('com.samourai.whirlpool.client.whirlpool.beans') exports('com.samourai.whirlpool.client.whirlpool.beans')
exports('com.samourai.whirlpool.client.wallet.data.pool') exports('com.samourai.whirlpool.client.wallet.data.pool')
exports('com.samourai.whirlpool.client.wallet.data.utxo') 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.client.mix.listener')
exports('com.samourai.whirlpool.protocol.beans') exports('com.samourai.whirlpool.protocol.beans')
exports('com.samourai.whirlpool.protocol.rest') exports('com.samourai.whirlpool.protocol.rest')
exports('com.samourai.whirlpool.client.tx0') exports('com.samourai.whirlpool.client.tx0')
exports('com.samourai.wallet.segwit.bech32') 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.minerFee')
exports('com.samourai.whirlpool.client.wallet.data.walletState') exports('com.samourai.whirlpool.client.wallet.data.walletState')
exports('com.sparrowwallet.nightjar.http') exports('com.sparrowwallet.nightjar.http')

View file

@ -469,7 +469,7 @@ public class AppServices {
Whirlpool whirlpool = whirlpoolMap.get(walletId); Whirlpool whirlpool = whirlpoolMap.get(walletId);
if(whirlpool == null) { 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())); 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); whirlpoolMap.put(walletId, whirlpool);
} }

View file

@ -40,9 +40,9 @@ public class MixStatusCell extends TreeTableCell<Entry, UtxoEntry.MixStatus> {
setContextMenu(null); setContextMenu(null);
} }
if(mixStatus.getPoolId() != null) { if(mixStatus.getMixProgress() != null) {
Tooltip tooltip = new Tooltip(); Tooltip tooltip = new Tooltip();
tooltip.setText("Pool: " + mixStatus.getPoolId().replace("btc", " BTC")); tooltip.setText("Pool: " + mixStatus.getMixProgress().getPoolId().replace("btc", " BTC"));
setTooltip(tooltip); setTooltip(tooltip);
} }
@ -81,7 +81,7 @@ public class MixStatusCell extends TreeTableCell<Entry, UtxoEntry.MixStatus> {
private void setMixProgress(MixProgress mixProgress) { private void setMixProgress(MixProgress mixProgress) {
if(mixProgress.getMixStep() != MixStep.FAIL) { if(mixProgress.getMixStep() != MixStep.FAIL) {
ProgressIndicator progressIndicator = getProgressIndicator(); 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); setGraphic(progressIndicator);
Tooltip tt = new Tooltip(); Tooltip tt = new Tooltip();
tt.setText(mixProgress.getMixStep().getMessage().substring(0, 1).toUpperCase() + mixProgress.getMixStep().getMessage().substring(1)); tt.setText(mixProgress.getMixStep().getMessage().substring(0, 1).toUpperCase() + mixProgress.getMixStep().getMessage().substring(1));

View file

@ -13,20 +13,20 @@ import java.util.List;
import java.util.Map; import java.util.Map;
public interface UtxoMixDataDao { 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) @RegisterRowMapper(UtxoMixDataMapper.class)
Map<Sha256Hash, UtxoMixData> getForWalletId(Long id); 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) @RegisterRowMapper(UtxoMixDataMapper.class)
Map<Sha256Hash, UtxoMixData> getForHash(byte[] hash); 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") @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 = ?") @SqlUpdate("update utxoMixData set hash = ?, mixesDone = ?, expired = ?, wallet = ? where id = ?")
void updateUtxoMixData(byte[] hash, String poolId, int mixesDone, Long forwarding, long wallet, long id); void updateUtxoMixData(byte[] hash, int mixesDone, Long expired, long wallet, long id);
@SqlUpdate("delete from utxoMixData where id in (<ids>)") @SqlUpdate("delete from utxoMixData where id in (<ids>)")
void deleteUtxoMixData(@BindList("ids") List<Long> ids); void deleteUtxoMixData(@BindList("ids") List<Long> ids);
@ -45,11 +45,11 @@ public interface UtxoMixDataDao {
Map<Sha256Hash, UtxoMixData> existing = getForHash(hash.getBytes()); Map<Sha256Hash, UtxoMixData> existing = getForHash(hash.getBytes());
if(existing.isEmpty() && utxoMixData.getId() == null) { 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); utxoMixData.setId(id);
} else { } else {
Long existingId = existing.get(hash) != null ? existing.get(hash).getId() : utxoMixData.getId(); 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); utxoMixData.setId(existingId);
} }
} }

View file

@ -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 { public Map.Entry<Sha256Hash, UtxoMixData> map(ResultSet rs, StatementContext ctx) throws SQLException {
Sha256Hash hash = Sha256Hash.wrap(rs.getBytes("hash")); Sha256Hash hash = Sha256Hash.wrap(rs.getBytes("hash"));
Long forwarding = rs.getLong("forwarding"); Long expired = rs.getLong("expired");
if(rs.wasNull()) { 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")); utxoMixData.setId(rs.getLong("id"));
return new Map.Entry<>() { return new Map.Entry<>() {

View file

@ -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() { public int getMixesDone() {
return getUtxoMixData().getMixesDone(); return getUtxoMixData().getMixesDone();
} }
public String getPoolId() {
return getUtxoMixData().getPoolId();
}
public MixProgress getMixProgress() { public MixProgress getMixProgress() {
return mixProgress; return mixProgress;
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -3,19 +3,21 @@ package com.sparrowwallet.sparrow.whirlpool;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import com.google.common.net.HostAndPort; import com.google.common.net.HostAndPort;
import com.samourai.tor.client.TorClientService; 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.api.backend.beans.UnspentOutput;
import com.samourai.wallet.hd.HD_Wallet; 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.event.*;
import com.samourai.whirlpool.client.tx0.*; import com.samourai.whirlpool.client.tx0.*;
import com.samourai.whirlpool.client.wallet.WhirlpoolEventService; import com.samourai.whirlpool.client.wallet.WhirlpoolEventService;
import com.samourai.whirlpool.client.wallet.WhirlpoolWallet; import com.samourai.whirlpool.client.wallet.WhirlpoolWallet;
import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig; import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig;
import com.samourai.whirlpool.client.wallet.WhirlpoolWalletService;
import com.samourai.whirlpool.client.wallet.beans.*; import com.samourai.whirlpool.client.wallet.beans.*;
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.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.utxo.UtxoSupplier;
import com.samourai.whirlpool.client.wallet.data.utxoConfig.UtxoConfig;
import com.samourai.whirlpool.client.whirlpool.ServerApi; import com.samourai.whirlpool.client.whirlpool.ServerApi;
import com.samourai.whirlpool.client.whirlpool.beans.Pool; import com.samourai.whirlpool.client.whirlpool.beans.Pool;
import com.sparrowwallet.drongo.ExtendedKey; 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.WhirlpoolMixEvent;
import com.sparrowwallet.sparrow.event.WhirlpoolMixSuccessEvent; import com.sparrowwallet.sparrow.event.WhirlpoolMixSuccessEvent;
import com.sparrowwallet.sparrow.wallet.UtxoEntry; 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.application.Platform;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
@ -43,7 +48,10 @@ import javafx.concurrent.Task;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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; import java.util.stream.Collectors;
public class Whirlpool { public class Whirlpool {
@ -54,32 +62,36 @@ public class Whirlpool {
private final JavaHttpClientService httpClientService; private final JavaHttpClientService httpClientService;
private final JavaStompClientService stompClientService; private final JavaStompClientService stompClientService;
private final TorClientService torClientService; private final TorClientService torClientService;
private final SparrowWhirlpoolWalletService whirlpoolWalletService; private final WhirlpoolWalletService whirlpoolWalletService;
private final WhirlpoolWalletConfig config; private final WhirlpoolWalletConfig config;
private HD_Wallet hdWallet; private HD_Wallet hdWallet;
private String walletId;
private BooleanProperty mixingProperty = new SimpleBooleanProperty(false); 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.torProxy = torProxy;
this.whirlpoolServer = WhirlpoolServer.valueOf(network.getName().toUpperCase()); this.whirlpoolServer = WhirlpoolServer.valueOf(network.getName().toUpperCase());
this.httpClientService = new JavaHttpClientService(torProxy); this.httpClientService = new JavaHttpClientService(torProxy);
this.stompClientService = new JavaStompClientService(httpClientService); this.stompClientService = new JavaStompClientService(httpClientService);
this.torClientService = new WhirlpoolTorClientService(); 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); 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); boolean onion = (torProxy != null);
String serverUrl = whirlpoolServer.getServerUrl(onion); String serverUrl = whirlpoolServer.getServerUrl(onion);
ServerApi serverApi = new ServerApi(serverUrl, httpClientService); 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); whirlpoolWalletConfig.setScode(sCode);
return whirlpoolWalletConfig; return whirlpoolWalletConfig;
@ -123,7 +135,7 @@ public class Whirlpool {
private Tx0ParamService getTx0ParamService() { private Tx0ParamService getTx0ParamService() {
try { try {
SparrowMinerFeeSupplier minerFeeSupplier = new SparrowMinerFeeSupplier(config.getFeeMin(), config.getFeeMax(), config.getFeeFallback(), config.getBackendApi().fetchMinerFee()); SparrowMinerFeeSupplier minerFeeSupplier = SparrowMinerFeeSupplier.getInstance();
return new Tx0ParamService(minerFeeSupplier, config); return new Tx0ParamService(minerFeeSupplier, config);
} catch(Exception e) { } catch(Exception e) {
log.error("Error fetching miner fees", e); log.error("Error fetching miner fees", e);
@ -143,10 +155,10 @@ public class Whirlpool {
int purpose = scriptType.getDefaultDerivation().get(0).num(); int purpose = scriptType.getDefaultDerivation().get(0).num();
List<String> words = keystore.getSeed().getMnemonicCode(); List<String> words = keystore.getSeed().getMnemonicCode();
String passphrase = keystore.getSeed().getPassphrase().asString(); String passphrase = keystore.getSeed().getPassphrase().asString();
HD_WalletFactoryJava hdWalletFactory = HD_WalletFactoryJava.getInstance(); HD_WalletFactoryGeneric hdWalletFactory = HD_WalletFactoryGeneric.getInstance();
byte[] seed = hdWalletFactory.computeSeedFromWords(words); byte[] seed = hdWalletFactory.computeSeedFromWords(words);
whirlpoolWalletService.setWalletId(walletId); this.walletId = walletId;
hdWallet = new HD_Wallet(purpose, words, whirlpoolServer, seed, passphrase, 1); hdWallet = new HD_Wallet(purpose, words, config.getNetworkParameters(), seed, passphrase, 1);
} catch(Exception e) { } catch(Exception e) {
throw new IllegalStateException("Could not create Whirlpool HD wallet ", e); throw new IllegalStateException("Could not create Whirlpool HD wallet ", e);
} }
@ -162,7 +174,8 @@ public class Whirlpool {
} }
try { 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) { } catch(Exception e) {
throw new WhirlpoolException("Could not create whirlpool wallet ", e); throw new WhirlpoolException("Could not create whirlpool wallet ", e);
} }
@ -176,23 +189,16 @@ public class Whirlpool {
public UtxoMixData getMixData(BlockTransactionHashIndex txo) { public UtxoMixData getMixData(BlockTransactionHashIndex txo) {
if(whirlpoolWalletService.whirlpoolWallet() != null) { if(whirlpoolWalletService.whirlpoolWallet() != null) {
UtxoConfigPersisted config = whirlpoolWalletService.whirlpoolWallet().getUtxoConfigSupplier().getUtxoConfigPersisted(txo.getHashAsString(), (int)txo.getIndex()); WhirlpoolUtxo whirlpoolUtxo = whirlpoolWalletService.whirlpoolWallet().getUtxoSupplier().findUtxo(txo.getHashAsString(), (int)txo.getIndex());
if(config != null) { if (whirlpoolUtxo != null) {
return new UtxoMixData(config.getPoolId(), config.getMixsDone(), config.getForwarding()); UtxoConfig utxoConfig = whirlpoolUtxo.getUtxoConfigOrDefault();
return new UtxoMixData(utxoConfig.getMixsDone(), null);
} }
} }
return 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 { public void mix(BlockTransactionHashIndex utxo) throws WhirlpoolException {
if(whirlpoolWalletService.whirlpoolWallet() == null) { if(whirlpoolWalletService.whirlpoolWallet() == null) {
throw new WhirlpoolException("Whirlpool wallet not yet created"); throw new WhirlpoolException("Whirlpool wallet not yet created");
@ -200,7 +206,7 @@ public class Whirlpool {
try { try {
WhirlpoolUtxo whirlpoolUtxo = whirlpoolWalletService.whirlpoolWallet().getUtxoSupplier().findUtxo(utxo.getHashAsString(), (int)utxo.getIndex()); WhirlpoolUtxo whirlpoolUtxo = whirlpoolWalletService.whirlpoolWallet().getUtxoSupplier().findUtxo(utxo.getHashAsString(), (int)utxo.getIndex());
whirlpoolWalletService.whirlpoolWallet().mixNow(whirlpoolUtxo); whirlpoolWalletService.whirlpoolWallet().mix(whirlpoolUtxo);
} catch(Exception e) { } catch(Exception e) {
throw new WhirlpoolException(e.getMessage(), e); throw new WhirlpoolException(e.getMessage(), e);
} }
@ -260,12 +266,9 @@ public class Whirlpool {
} }
private WalletUtxo getUtxo(WhirlpoolUtxo whirlpoolUtxo) { private WalletUtxo getUtxo(WhirlpoolUtxo whirlpoolUtxo) {
Wallet wallet = AppServices.get().getWallet(whirlpoolWalletService.getWalletId()); Wallet wallet = AppServices.get().getWallet(walletId);
if(wallet != null) { if(wallet != null) {
StandardAccount standardAccount = getStandardAccount(whirlpoolUtxo.getAccount()); wallet = getStandardAccountWallet(whirlpoolUtxo.getAccount(), wallet);
if(StandardAccount.WHIRLPOOL_ACCOUNTS.contains(standardAccount)) {
wallet = wallet.getChildWallet(standardAccount);
}
for(BlockTransactionHashIndex utxo : wallet.getWalletUtxos().keySet()) { for(BlockTransactionHashIndex utxo : wallet.getWalletUtxos().keySet()) {
if(utxo.getHashAsString().equals(whirlpoolUtxo.getUtxo().tx_hash) && utxo.getIndex() == whirlpoolUtxo.getUtxo().tx_output_n) { if(utxo.getHashAsString().equals(whirlpoolUtxo.getUtxo().tx_hash) && utxo.getIndex() == whirlpoolUtxo.getUtxo().tx_output_n) {
@ -277,6 +280,18 @@ public class Whirlpool {
return null; 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) { public static StandardAccount getStandardAccount(WhirlpoolAccount whirlpoolAccount) {
if(whirlpoolAccount == WhirlpoolAccount.PREMIX) { if(whirlpoolAccount == WhirlpoolAccount.PREMIX) {
return StandardAccount.WHIRLPOOL_PREMIX; return StandardAccount.WHIRLPOOL_PREMIX;
@ -346,15 +361,14 @@ public class Whirlpool {
public void onMixSuccess(MixSuccessEvent e) { public void onMixSuccess(MixSuccessEvent e) {
WalletUtxo walletUtxo = getUtxo(e.getWhirlpoolUtxo()); WalletUtxo walletUtxo = getUtxo(e.getWhirlpoolUtxo());
if(walletUtxo != null) { if(walletUtxo != null) {
log.debug("Mix success, new utxo " + e.getMixSuccess().getReceiveUtxo().getHash() + ":" + e.getMixSuccess().getReceiveUtxo().getIndex()); log.debug("Mix success, new utxo " + e.getReceiveUtxo().getHash() + ":" + e.getReceiveUtxo().getIndex());
persistMixData(); Platform.runLater(() -> EventManager.get().post(new WhirlpoolMixSuccessEvent(walletUtxo.wallet, walletUtxo.utxo, e.getReceiveUtxo(), getReceiveNode(e, walletUtxo))));
Platform.runLater(() -> EventManager.get().post(new WhirlpoolMixSuccessEvent(walletUtxo.wallet, walletUtxo.utxo, e.getMixSuccess().getReceiveUtxo(), getReceiveNode(e, walletUtxo))));
} }
} }
private WalletNode getReceiveNode(MixSuccessEvent e, WalletUtxo walletUtxo) { private WalletNode getReceiveNode(MixSuccessEvent e, WalletUtxo walletUtxo) {
for(WalletNode walletNode : walletUtxo.wallet.getNode(KeyPurpose.RECEIVE).getChildren()) { 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; return walletNode;
} }
} }

View file

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

View file

@ -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.MapDifference;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.samourai.whirlpool.client.wallet.data.utxo.UtxoConfigData; import com.samourai.whirlpool.client.wallet.data.utxoConfig.UtxoConfigData;
import com.samourai.whirlpool.client.wallet.data.utxo.UtxoConfigPersisted; import com.samourai.whirlpool.client.wallet.data.utxoConfig.UtxoConfigPersisted;
import com.samourai.whirlpool.client.wallet.data.utxo.UtxoConfigPersister; import com.samourai.whirlpool.client.wallet.data.utxoConfig.UtxoConfigPersister;
import com.sparrowwallet.drongo.protocol.Sha256Hash; import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.wallet.UtxoMixData; import com.sparrowwallet.drongo.wallet.UtxoMixData;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
@ -14,14 +14,14 @@ import com.sparrowwallet.sparrow.event.WalletUtxoMixesChangedEvent;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.*; import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class SparrowUtxoConfigPersister extends UtxoConfigPersister { public class SparrowUtxoConfigPersister extends UtxoConfigPersister {
private static final Logger log = LoggerFactory.getLogger(SparrowUtxoConfigPersister.class); private static final Logger log = LoggerFactory.getLogger(SparrowUtxoConfigPersister.class);
private final String walletId; private final String walletId;
private long lastWrite;
public SparrowUtxoConfigPersister(String walletId) { public SparrowUtxoConfigPersister(String walletId) {
super(walletId); super(walletId);
@ -29,14 +29,14 @@ public class SparrowUtxoConfigPersister extends UtxoConfigPersister {
} }
@Override @Override
public synchronized UtxoConfigData load() throws Exception { public synchronized UtxoConfigData read() throws Exception {
Wallet wallet = getWallet(); Wallet wallet = getWallet();
if(wallet == null) { if(wallet == null) {
throw new IllegalStateException("Can't find wallet with walletId " + walletId); throw new IllegalStateException("Can't find wallet with walletId " + walletId);
} }
Map<String, UtxoConfigPersisted> utxoConfigs = wallet.getUtxoMixes().entrySet().stream() 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"); }, (u, v) -> { throw new IllegalStateException("Duplicate utxo config hashes"); },
HashMap::new)); HashMap::new));
@ -44,7 +44,7 @@ public class SparrowUtxoConfigPersister extends UtxoConfigPersister {
} }
@Override @Override
public synchronized void write(UtxoConfigData data) throws Exception { protected void doWrite(UtxoConfigData data) throws Exception {
Wallet wallet = getWallet(); Wallet wallet = getWallet();
if(wallet == null) { if(wallet == null) {
//Wallet is already closed //Wallet is already closed
@ -53,7 +53,7 @@ public class SparrowUtxoConfigPersister extends UtxoConfigPersister {
Map<String, UtxoConfigPersisted> currentData = new HashMap<>(data.getUtxoConfigs()); Map<String, UtxoConfigPersisted> currentData = new HashMap<>(data.getUtxoConfigs());
Map<Sha256Hash, UtxoMixData> changedUtxoMixes = currentData.entrySet().stream() 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"); }, (u, v) -> { throw new IllegalStateException("Duplicate utxo config hashes"); },
HashMap::new)); HashMap::new));
@ -63,15 +63,9 @@ public class SparrowUtxoConfigPersister extends UtxoConfigPersister {
wallet.getUtxoMixes().keySet().removeAll(removedUtxoMixes.keySet()); wallet.getUtxoMixes().keySet().removeAll(removedUtxoMixes.keySet());
EventManager.get().post(new WalletUtxoMixesChangedEvent(wallet, changedUtxoMixes, removedUtxoMixes)); EventManager.get().post(new WalletUtxoMixesChangedEvent(wallet, changedUtxoMixes, removedUtxoMixes));
lastWrite = System.currentTimeMillis();
} }
private Wallet getWallet() { 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); 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;
}
} }

View file

@ -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.MinerFee;
import com.samourai.wallet.api.backend.MinerFeeTarget; 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.ExtendedKey;
import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.KeyPurpose;
import com.sparrowwallet.drongo.Network; import com.sparrowwallet.drongo.Network;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.*; 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.BlockTransaction;
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletNode; import com.sparrowwallet.drongo.wallet.WalletNode;
import com.sparrowwallet.sparrow.AppServices; 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.net.ElectrumServer;
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.*; import java.util.*;
@SuppressWarnings("deprecation") public class SparrowDataSource extends WalletResponseDataSource {
public class SparrowBackendApi extends BackendApi { private static final Logger log = LoggerFactory.getLogger(SparrowDataSource.class);
private static final Logger log = LoggerFactory.getLogger(SparrowBackendApi.class);
private static final int FALLBACK_FEE_RATE = 75;
public SparrowBackendApi() { private final String walletIdentifierPrefix;
super(null, null);
public SparrowDataSource(
WhirlpoolWallet whirlpoolWallet,
HD_Wallet bip44w,
DataPersister dataPersister)
throws Exception {
super(whirlpoolWallet, bip44w, dataPersister);
// prefix matching <prefix>:master, :Premix, :Postmix
this.walletIdentifierPrefix = getWhirlpoolWallet().getWalletIdentifier().replace(":master", "");
} }
@Override @Override
public TxsResponse fetchTxs(String[] zpubs, int page, int count) throws Exception { public void open() throws Exception {
List<TxsResponse.Tx> txes = new ArrayList<>(); super.open();
EventManager.get().register(this);
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<TxsResponse.Tx> pageTxes;
if(txes.size() < count) {
pageTxes = txes;
} else {
pageTxes = txes.subList(page * count, Math.min((page * count) + count, txes.size()));
}
TxsResponse txsResponse = new TxsResponse();
txsResponse.n_tx = txes.size();
txsResponse.page = page;
txsResponse.n_tx_page = pageTxes.size();
txsResponse.txs = pageTxes.toArray(new TxsResponse.Tx[0]);
return txsResponse;
} }
@Override @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 walletResponse = new WalletResponse();
walletResponse.wallet = new WalletResponse.Wallet(); walletResponse.wallet = new WalletResponse.Wallet();
@ -113,6 +74,7 @@ public class SparrowBackendApi extends BackendApi {
List<UnspentOutput> unspentOutputs = new ArrayList<>(); List<UnspentOutput> unspentOutputs = new ArrayList<>();
int storedBlockHeight = 0; int storedBlockHeight = 0;
String[] zpubs = getWalletSupplier().getPubs(true);
for(String zpub : zpubs) { for(String zpub : zpubs) {
Wallet wallet = getWallet(zpub); Wallet wallet = getWallet(zpub);
if(wallet == null) { 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.latest_block.time = AppServices.getLatestBlockHeader() == null ? 1 : AppServices.getLatestBlockHeader().getTime();
walletResponse.info.fees = new LinkedHashMap<>(); walletResponse.info.fees = new LinkedHashMap<>();
MinerFee minerFee = getMinerFeeSupplier().getValue();
for(MinerFeeTarget target : MinerFeeTarget.values()) { 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; 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 @Override
public void pushTx(String txHex) throws Exception { public void pushTx(String txHex) throws Exception {
Transaction transaction = new Transaction(Utils.hexToBytes(txHex)); Transaction transaction = new Transaction(Utils.hexToBytes(txHex));
@ -221,25 +174,8 @@ public class SparrowBackendApi extends BackendApi {
} }
@Override @Override
public boolean testConnectivity() { public MinerFeeSupplier getMinerFeeSupplier() {
return AppServices.isConnected(); return SparrowMinerFeeSupplier.getInstance();
}
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
} }
private Wallet getWallet(String zpub) { private Wallet getWallet(String zpub) {
@ -255,23 +191,33 @@ public class SparrowBackendApi extends BackendApi {
.orElse(null); .orElse(null);
} }
@Override @Subscribe
public List<UnspentOutput> fetchUtxos(String zpub) throws Exception { public void walletHistoryChanged(WalletHistoryChangedEvent event) {
throw new UnsupportedOperationException(); refreshWallet(event.getWalletId());
} }
@Override @Subscribe
public List<UnspentOutput> fetchUtxos(String[] zpubs) throws Exception { public void walletAddressesChanged(WalletAddressesChangedEvent event) {
throw new UnsupportedOperationException(); refreshWallet(event.getWalletId());
} }
@Override @Subscribe
public Map<String, MultiAddrResponse.Address> fetchAddresses(String[] zpubs) throws Exception { public void newBlock(NewBlockEvent event) {
throw new UnsupportedOperationException(); try {
refresh();
} catch (Exception e) {
log.error("", e);
}
} }
@Override private void refreshWallet(String eventWalletId) {
public MultiAddrResponse.Address fetchAddress(String zpub) throws Exception { try {
throw new UnsupportedOperationException(); // match <prefix>:master, :Premix, :Postmix
if (eventWalletId.startsWith(walletIdentifierPrefix)) {
refresh();
}
} catch (Exception e) {
log.error("", e);
}
} }
} }

View file

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

View file

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

View file

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

View file

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