upgrade to whirlpool-client 0.23.30-early4 + extlibj 0.0.19-dsk3

This commit is contained in:
zeroleak 2021-08-22 11:26:11 +02:00
parent 37c4ff4dd7
commit fec45356a2
5 changed files with 164 additions and 153 deletions

View file

@ -395,25 +395,28 @@ extraJavaModuleInfo {
requires('com.fasterxml.jackson.databind')
requires('logback.classic')
requires('org.json')
exports('com.sparrowwallet.nightjar')
exports('com.samourai.http.client')
exports('com.samourai.tor.client')
exports('com.samourai.wallet.api.backend')
exports('com.samourai.wallet.api.backend.beans')
exports('com.samourai.wallet.hd')
exports('com.samourai.wallet.hd.java')
exports('com.samourai.whirlpool.client.event')
exports('com.samourai.whirlpool.client.wallet')
exports('com.samourai.whirlpool.client.wallet.beans')
exports('com.samourai.whirlpool.client.wallet.data.dataSource')
exports('com.samourai.whirlpool.client.wallet.data.dataPersister')
exports('com.samourai.whirlpool.client.whirlpool')
exports('com.samourai.whirlpool.client.whirlpool.beans')
exports('com.samourai.whirlpool.client.wallet.data.pool')
exports('com.samourai.whirlpool.client.wallet.data.utxo')
exports('com.samourai.whirlpool.client.wallet.data.utxoConfig')
exports('com.samourai.whirlpool.client.wallet.data.supplier')
exports('com.samourai.whirlpool.client.mix.listener')
exports('com.samourai.whirlpool.protocol.beans')
exports('com.samourai.whirlpool.protocol.rest')
exports('com.samourai.whirlpool.client.tx0')
exports('com.samourai.wallet.segwit.bech32')
exports('com.samourai.whirlpool.client.wallet.data.wallet')
exports('com.samourai.whirlpool.client.wallet.data.minerFee')
exports('com.samourai.whirlpool.client.wallet.data.walletState')
exports('com.sparrowwallet.nightjar.http')

View file

@ -455,8 +455,8 @@ public class AppServices {
public Whirlpool getWhirlpool(String walletId) {
Whirlpool whirlpool = whirlpoolMap.get(walletId);
if(whirlpool == null) {
HostAndPort torProxy = AppServices.isTorRunning() ? HostAndPort.fromParts("localhost", TorService.PROXY_PORT) : (Config.get().getProxyServer().isEmpty() || !Config.get().isUseProxy() ? null : HostAndPort.fromString(Config.get().getProxyServer()));
whirlpool = new Whirlpool(Network.get(), torProxy, Config.get().getScode(), 1, 15);
HostAndPort torProxy = AppServices.isTorRunning() ? HostAndPort.fromParts("localhost", TorService.PROXY_PORT) : (Config.get().getProxyServer() == null || Config.get().getProxyServer().isEmpty() || !Config.get().isUseProxy() ? null : HostAndPort.fromString(Config.get().getProxyServer()));
whirlpool = new Whirlpool(Network.get(), torProxy, Config.get().getScode(), 1);
whirlpoolMap.put(walletId, whirlpool);
}

View file

@ -1,9 +1,14 @@
package com.sparrowwallet.sparrow.whirlpool;
import com.samourai.wallet.api.backend.BackendApi;
import com.samourai.wallet.api.backend.MinerFee;
import com.samourai.wallet.api.backend.MinerFeeTarget;
import com.samourai.wallet.api.backend.beans.*;
import com.samourai.wallet.api.backend.beans.TxsResponse;
import com.samourai.wallet.api.backend.beans.UnspentOutput;
import com.samourai.wallet.api.backend.beans.WalletResponse;
import com.samourai.wallet.hd.HD_Wallet;
import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig;
import com.samourai.whirlpool.client.wallet.data.dataPersister.DataPersister;
import com.samourai.whirlpool.client.wallet.data.dataSource.WalletResponseDataSource;
import com.samourai.whirlpool.client.wallet.data.minerFee.MinerFeeSupplier;
import com.sparrowwallet.drongo.ExtendedKey;
import com.sparrowwallet.drongo.KeyPurpose;
import com.sparrowwallet.drongo.Network;
@ -21,88 +26,20 @@ import org.slf4j.LoggerFactory;
import java.util.*;
@SuppressWarnings("deprecation")
public class SparrowBackendApi extends BackendApi {
private static final Logger log = LoggerFactory.getLogger(SparrowBackendApi.class);
private static final int FALLBACK_FEE_RATE = 75;
public class SparrowDataSource extends WalletResponseDataSource {
private static final Logger log = LoggerFactory.getLogger(SparrowDataSource.class);
public SparrowBackendApi() {
super(null, null);
public SparrowDataSource(
WhirlpoolWalletConfig config,
HD_Wallet bip44w,
String walletIdentifier,
DataPersister dataPersister)
throws Exception {
super(config, bip44w, walletIdentifier, dataPersister);
}
@Override
public TxsResponse fetchTxs(String[] zpubs, int page, int count) throws Exception {
List<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();
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
public WalletResponse fetchWallet(String[] zpubs) throws Exception {
protected WalletResponse fetchWalletResponse() throws Exception {
WalletResponse walletResponse = new WalletResponse();
walletResponse.wallet = new WalletResponse.Wallet();
@ -113,6 +50,7 @@ public class SparrowBackendApi extends BackendApi {
List<UnspentOutput> unspentOutputs = new ArrayList<>();
int storedBlockHeight = 0;
String[] zpubs = getWalletSupplier().getPubs(true);
for(String zpub : zpubs) {
Wallet wallet = getWallet(zpub);
if(wallet == null) {
@ -197,22 +135,12 @@ public class SparrowBackendApi extends BackendApi {
walletResponse.info.fees = new LinkedHashMap<>();
for(MinerFeeTarget target : MinerFeeTarget.values()) {
walletResponse.info.fees.put(target.getValue(), AppServices.getTargetBlockFeeRates() == null ? FALLBACK_FEE_RATE : getMinimumFeeForTarget(Integer.parseInt(target.getValue())));
walletResponse.info.fees.put(target.getValue(), getMinerFeeSupplier().getFee(target));
}
return walletResponse;
}
@Override
public MinerFee fetchMinerFee() throws Exception {
Map<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 +149,79 @@ public class SparrowBackendApi extends BackendApi {
}
@Override
public boolean testConnectivity() {
return AppServices.isConnected();
}
public TxsResponse fetchTxs(String[] zpubs, int page, int count) throws Exception {
List<TxsResponse.Tx> txes = new ArrayList<>();
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();
for(String zpub : zpubs) {
Wallet wallet = getWallet(zpub);
if(wallet == null) {
log.debug("No wallet for " + zpub + " found");
continue;
}
for(BlockTransaction blockTransaction : wallet.getTransactions().values()) {
TxsResponse.Tx tx = new TxsResponse.Tx();
tx.block_height = blockTransaction.getHeight();
tx.hash = blockTransaction.getHashAsString();
tx.locktime = blockTransaction.getTransaction().getLocktime();
tx.time = blockTransaction.getDate().getTime();
tx.version = (int)blockTransaction.getTransaction().getVersion();
tx.inputs = new TxsResponse.TxInput[blockTransaction.getTransaction().getInputs().size()];
for(int i = 0; i < blockTransaction.getTransaction().getInputs().size(); i++) {
TransactionInput txInput = blockTransaction.getTransaction().getInputs().get(i);
tx.inputs[i] = new TxsResponse.TxInput();
tx.inputs[i].vin = txInput.getIndex();
tx.inputs[i].sequence = txInput.getSequenceNumber();
tx.inputs[i].prev_out = new TxsResponse.TxOut();
tx.inputs[i].prev_out.txid = txInput.getOutpoint().getHash().toString();
tx.inputs[i].prev_out.vout = (int)txInput.getOutpoint().getIndex();
BlockTransaction spentTransaction = wallet.getTransactions().get(txInput.getOutpoint().getHash());
if(spentTransaction != null) {
TransactionOutput spentOutput = spentTransaction.getTransaction().getOutputs().get((int)txInput.getOutpoint().getIndex());
tx.inputs[i].prev_out.value = spentOutput.getValue();
Address[] addresses = spentOutput.getScript().getToAddresses();
if(addresses.length > 0) {
tx.inputs[i].prev_out.addr = addresses[0].toString();
}
}
}
tx.out = new TxsResponse.TxOutput[blockTransaction.getTransaction().getOutputs().size()];
for(int i = 0; i < blockTransaction.getTransaction().getOutputs().size(); i++) {
TransactionOutput txOutput = blockTransaction.getTransaction().getOutputs().get(i);
tx.out[i].n = txOutput.getIndex();
tx.out[i].value = txOutput.getValue();
Address[] addresses = txOutput.getScript().getToAddresses();
if(addresses.length > 0) {
tx.out[i].addr = addresses[0].toString();
}
}
txes.add(tx);
}
}
return feeRates.get(0).getValue().intValue();
List<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
public void initBip84(String zpub) throws Exception {
//nothing required
public MinerFeeSupplier getMinerFeeSupplier() {
return SparrowMinerFeeSupplier.getInstance();
}
private Wallet getWallet(String zpub) {
@ -254,24 +236,4 @@ public class SparrowBackendApi extends BackendApi {
.findFirst()
.orElse(null);
}
@Override
public List<UnspentOutput> fetchUtxos(String zpub) throws Exception {
throw new UnsupportedOperationException();
}
@Override
public List<UnspentOutput> fetchUtxos(String[] zpubs) throws Exception {
throw new UnsupportedOperationException();
}
@Override
public Map<String, MultiAddrResponse.Address> fetchAddresses(String[] zpubs) throws Exception {
throw new UnsupportedOperationException();
}
@Override
public MultiAddrResponse.Address fetchAddress(String zpub) throws Exception {
throw new UnsupportedOperationException();
}
}

View file

@ -1,11 +1,46 @@
package com.sparrowwallet.sparrow.whirlpool;
import com.samourai.wallet.api.backend.MinerFee;
import com.samourai.wallet.api.backend.MinerFeeTarget;
import com.samourai.whirlpool.client.wallet.data.minerFee.MinerFeeSupplier;
import com.sparrowwallet.sparrow.AppServices;
public class SparrowMinerFeeSupplier extends MinerFeeSupplier {
public SparrowMinerFeeSupplier(int feeMin, int feeMax, int feeFallback, MinerFee currentMinerFee) {
super(feeMin, feeMax, feeFallback);
setValue(currentMinerFee);
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class SparrowMinerFeeSupplier implements MinerFeeSupplier {
private static final int FALLBACK_FEE_RATE = 75;
public static SparrowMinerFeeSupplier instance;
public static SparrowMinerFeeSupplier getInstance() {
if (instance == null) {
instance = new SparrowMinerFeeSupplier();
}
return instance;
}
private SparrowMinerFeeSupplier() {
}
@Override
public int getFee(MinerFeeTarget feeTarget) {
if (AppServices.getTargetBlockFeeRates() == null) {
return FALLBACK_FEE_RATE;
}
return getMinimumFeeForTarget(Integer.parseInt(feeTarget.getValue()));
}
private Integer getMinimumFeeForTarget(int targetBlocks) {
List<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

@ -3,17 +3,25 @@ package com.sparrowwallet.sparrow.whirlpool;
import com.google.common.eventbus.Subscribe;
import com.google.common.net.HostAndPort;
import com.samourai.tor.client.TorClientService;
import com.samourai.wallet.api.backend.BackendApi;
import com.samourai.wallet.api.backend.beans.UnspentOutput;
import com.samourai.wallet.hd.HD_Wallet;
import com.samourai.wallet.hd.java.HD_WalletFactoryJava;
import com.samourai.whirlpool.client.event.*;
import com.samourai.wallet.hd.HD_WalletFactoryGeneric;
import com.samourai.whirlpool.client.event.MixFailEvent;
import com.samourai.whirlpool.client.event.MixSuccessEvent;
import com.samourai.whirlpool.client.event.WalletStartEvent;
import com.samourai.whirlpool.client.event.WalletStopEvent;
import com.samourai.whirlpool.client.tx0.*;
import com.samourai.whirlpool.client.wallet.WhirlpoolEventService;
import com.samourai.whirlpool.client.wallet.WhirlpoolWallet;
import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig;
import com.samourai.whirlpool.client.wallet.WhirlpoolWalletService;
import com.samourai.whirlpool.client.wallet.beans.*;
import com.samourai.whirlpool.client.wallet.beans.Tx0FeeTarget;
import com.samourai.whirlpool.client.wallet.beans.WhirlpoolAccount;
import com.samourai.whirlpool.client.wallet.beans.WhirlpoolServer;
import com.samourai.whirlpool.client.wallet.beans.WhirlpoolUtxo;
import com.samourai.whirlpool.client.wallet.data.dataPersister.DataPersisterFactory;
import com.samourai.whirlpool.client.wallet.data.dataPersister.FileDataPersister;
import com.samourai.whirlpool.client.wallet.data.dataSource.DataSourceFactory;
import com.samourai.whirlpool.client.wallet.data.pool.PoolData;
import com.samourai.whirlpool.client.wallet.data.utxo.UtxoSupplier;
import com.samourai.whirlpool.client.whirlpool.ServerApi;
@ -36,7 +44,9 @@ import javafx.concurrent.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class Whirlpool {
@ -51,26 +61,27 @@ public class Whirlpool {
private final WhirlpoolWalletConfig config;
private HD_Wallet hdWallet;
public Whirlpool(Network network, HostAndPort torProxy, String sCode, int maxClients, int clientDelay) {
public Whirlpool(Network network, HostAndPort torProxy, String sCode, int maxClients) {
this.torProxy = torProxy;
this.whirlpoolServer = WhirlpoolServer.valueOf(network.getName().toUpperCase());
this.httpClientService = new JavaHttpClientService(torProxy);
this.stompClientService = new JavaStompClientService(httpClientService);
this.torClientService = new WhirlpoolTorClientService();
this.whirlpoolWalletService = new WhirlpoolWalletService();
this.config = computeWhirlpoolWalletConfig(sCode, maxClients, clientDelay);
DataPersisterFactory dataPersisterFactory = (config, bip44w, walletIdentifier) -> new FileDataPersister(config, bip44w, walletIdentifier);
DataSourceFactory dataSourceFactory = (config, bip44w, walletIdentifier, dataPersister) -> new SparrowDataSource(config, bip44w, walletIdentifier, dataPersister);
this.whirlpoolWalletService = new WhirlpoolWalletService(dataPersisterFactory, dataSourceFactory);
this.config = computeWhirlpoolWalletConfig(sCode, maxClients);
WhirlpoolEventService.getInstance().register(this);
}
private WhirlpoolWalletConfig computeWhirlpoolWalletConfig(String sCode, int maxClients, int clientDelay) {
private WhirlpoolWalletConfig computeWhirlpoolWalletConfig(String sCode, int maxClients) {
boolean onion = (torProxy != null);
String serverUrl = whirlpoolServer.getServerUrl(onion);
ServerApi serverApi = new ServerApi(serverUrl, httpClientService);
BackendApi backendApi = new SparrowBackendApi();
WhirlpoolWalletConfig whirlpoolWalletConfig = new WhirlpoolWalletConfig(httpClientService, stompClientService, torClientService, serverApi, whirlpoolServer, false, backendApi);
WhirlpoolWalletConfig whirlpoolWalletConfig = new WhirlpoolWalletConfig(httpClientService, stompClientService, torClientService, serverApi, whirlpoolServer.getParams(), false);
whirlpoolWalletConfig.setScode(sCode);
return whirlpoolWalletConfig;
@ -114,7 +125,7 @@ public class Whirlpool {
private Tx0ParamService getTx0ParamService() {
try {
SparrowMinerFeeSupplier minerFeeSupplier = new SparrowMinerFeeSupplier(config.getFeeMin(), config.getFeeMax(), config.getFeeFallback(), config.getBackendApi().fetchMinerFee());
SparrowMinerFeeSupplier minerFeeSupplier = SparrowMinerFeeSupplier.getInstance();
return new Tx0ParamService(minerFeeSupplier, config);
} catch(Exception e) {
log.error("Error fetching miner fees", e);
@ -134,17 +145,17 @@ public class Whirlpool {
int purpose = scriptType.getDefaultDerivation().get(0).num();
List<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);
hdWallet = new HD_Wallet(purpose, words, whirlpoolServer, seed, passphrase, 1);
hdWallet = new HD_Wallet(purpose, words, config.getNetworkParameters(), seed, passphrase, 1);
} catch(Exception e) {
throw new IllegalStateException("Could not create Whirlpool HD wallet ", e);
}
}
public WhirlpoolWallet getWhirlpoolWallet() throws WhirlpoolException {
if(whirlpoolWalletService.whirlpoolWallet() != null) {
return whirlpoolWalletService.whirlpoolWallet();
if(whirlpoolWalletService.getWhirlpoolWalletOrNull() != null) {
return whirlpoolWalletService.getWhirlpoolWalletOrNull();
}
if(hdWallet == null) {
@ -167,11 +178,11 @@ public class Whirlpool {
}
public boolean isStarted() {
if(whirlpoolWalletService.whirlpoolWallet() == null) {
if(whirlpoolWalletService.getWhirlpoolWalletOrNull() == null) {
return false;
}
return whirlpoolWalletService.whirlpoolWallet().isStarted();
return whirlpoolWalletService.getWhirlpoolWalletOrNull().isStarted();
}
public void shutdown() {
@ -226,7 +237,7 @@ public class Whirlpool {
@Subscribe
public void onMixFail(MixFailEvent e) {
log.info("Mix failed for utxo " + e.getWhirlpoolUtxo().getUtxo().tx_hash + ":" + e.getWhirlpoolUtxo().getUtxo().tx_output_n);
log.info("Mix failed for utxo " + e.getMixFail().getWhirlpoolUtxo().getUtxo().tx_hash + ":" + e.getMixFail().getWhirlpoolUtxo().getUtxo().tx_output_n);
}
@Subscribe