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