mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 13:16:44 +00:00
support mixing to multisig wallets
This commit is contained in:
parent
b6f047d382
commit
a42761981c
5 changed files with 92 additions and 15 deletions
|
@ -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.12-SNAPSHOT')
|
implementation('com.sparrowwallet.nightjar:nightjar:0.2.13-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.12-SNAPSHOT.jar', 'com.sparrowwallet.nightjar', '0.2.12-SNAPSHOT') {
|
module('nightjar-0.2.13-SNAPSHOT.jar', 'com.sparrowwallet.nightjar', '0.2.13-SNAPSHOT') {
|
||||||
requires('com.google.common')
|
requires('com.google.common')
|
||||||
requires('net.sourceforge.streamsupport')
|
requires('net.sourceforge.streamsupport')
|
||||||
requires('org.slf4j')
|
requires('org.slf4j')
|
||||||
|
@ -401,6 +401,7 @@ extraJavaModuleInfo {
|
||||||
exports('com.samourai.wallet.api.backend.beans')
|
exports('com.samourai.wallet.api.backend.beans')
|
||||||
exports('com.samourai.wallet.client.indexHandler')
|
exports('com.samourai.wallet.client.indexHandler')
|
||||||
exports('com.samourai.wallet.hd')
|
exports('com.samourai.wallet.hd')
|
||||||
|
exports('com.samourai.wallet.util')
|
||||||
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')
|
||||||
|
|
|
@ -44,7 +44,6 @@ public class MixToController implements Initializable {
|
||||||
|
|
||||||
List<Wallet> destinationWallets = AppServices.get().getOpenWallets().keySet().stream().filter(openWallet -> openWallet.isValid()
|
List<Wallet> destinationWallets = AppServices.get().getOpenWallets().keySet().stream().filter(openWallet -> openWallet.isValid()
|
||||||
&& openWallet != wallet && openWallet != wallet.getMasterWallet()
|
&& openWallet != wallet && openWallet != wallet.getMasterWallet()
|
||||||
&& openWallet.getPolicyType().equals(PolicyType.SINGLE)
|
|
||||||
&& !StandardAccount.WHIRLPOOL_ACCOUNTS.contains(openWallet.getStandardAccountType())).collect(Collectors.toList());
|
&& !StandardAccount.WHIRLPOOL_ACCOUNTS.contains(openWallet.getStandardAccountType())).collect(Collectors.toList());
|
||||||
allWallets.addAll(destinationWallets);
|
allWallets.addAll(destinationWallets);
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ 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.HD_WalletFactoryGeneric;
|
import com.samourai.wallet.hd.HD_WalletFactoryGeneric;
|
||||||
import com.samourai.whirlpool.client.event.*;
|
import com.samourai.whirlpool.client.event.*;
|
||||||
|
import com.samourai.whirlpool.client.mix.handler.IPostmixHandler;
|
||||||
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;
|
||||||
|
@ -41,6 +42,7 @@ import com.sparrowwallet.sparrow.wallet.UtxoEntry;
|
||||||
import com.sparrowwallet.sparrow.whirlpool.dataPersister.SparrowDataPersister;
|
import com.sparrowwallet.sparrow.whirlpool.dataPersister.SparrowDataPersister;
|
||||||
import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowDataSource;
|
import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowDataSource;
|
||||||
import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowMinerFeeSupplier;
|
import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowMinerFeeSupplier;
|
||||||
|
import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowPostmixHandler;
|
||||||
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;
|
||||||
|
@ -365,18 +367,12 @@ public class Whirlpool {
|
||||||
throw new IllegalStateException("Cannot find mix to wallet with id " + mixToWalletId);
|
throw new IllegalStateException("Cannot find mix to wallet with id " + mixToWalletId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mixToWallet.getPolicyType() != PolicyType.SINGLE) {
|
|
||||||
throw new IllegalStateException("Only single signature mix to wallets are currently supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ExtendedKey.Header> headers = ExtendedKey.Header.getHeaders(Network.get());
|
|
||||||
ExtendedKey.Header header = headers.stream().filter(head -> head.getDefaultScriptType().equals(mixToWallet.getScriptType()) && !head.isPrivateKey()).findFirst().orElse(ExtendedKey.Header.xpub);
|
|
||||||
ExtendedKey extPubKey = mixToWallet.getKeystores().get(0).getExtendedPublicKey();
|
|
||||||
String xpub = extPubKey.toString(header);
|
|
||||||
Integer highestUsedIndex = mixToWallet.getNode(KeyPurpose.RECEIVE).getHighestUsedIndex();
|
Integer highestUsedIndex = mixToWallet.getNode(KeyPurpose.RECEIVE).getHighestUsedIndex();
|
||||||
|
int startIndex = highestUsedIndex == null ? 0 : highestUsedIndex + 1;
|
||||||
int mixes = minMixes == null ? DEFAULT_MIXTO_MIN_MIXES : minMixes;
|
int mixes = minMixes == null ? DEFAULT_MIXTO_MIN_MIXES : minMixes;
|
||||||
|
|
||||||
ExternalDestination externalDestination = new ExternalDestination(xpub, 0, highestUsedIndex == null ? 0 : highestUsedIndex + 1, mixes, DEFAULT_MIXTO_RANDOM_FACTOR);
|
IPostmixHandler postmixHandler = new SparrowPostmixHandler(whirlpoolWalletService, mixToWallet, KeyPurpose.RECEIVE, startIndex);
|
||||||
|
ExternalDestination externalDestination = new ExternalDestination(postmixHandler, 0, startIndex, mixes, DEFAULT_MIXTO_RANDOM_FACTOR);
|
||||||
config.setExternalDestination(externalDestination);
|
config.setExternalDestination(externalDestination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package com.sparrowwallet.sparrow.whirlpool.dataSource;
|
||||||
|
|
||||||
|
import com.samourai.wallet.client.indexHandler.IIndexHandler;
|
||||||
|
import com.samourai.wallet.util.XPubUtil;
|
||||||
|
import com.samourai.whirlpool.client.mix.handler.DestinationType;
|
||||||
|
import com.samourai.whirlpool.client.mix.handler.IPostmixHandler;
|
||||||
|
import com.samourai.whirlpool.client.mix.handler.MixDestination;
|
||||||
|
import com.samourai.whirlpool.client.wallet.WhirlpoolWalletService;
|
||||||
|
import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class SparrowPostmixHandler implements IPostmixHandler {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(SparrowPostmixHandler.class);
|
||||||
|
|
||||||
|
private final WhirlpoolWalletService whirlpoolWalletService;
|
||||||
|
private final Wallet wallet;
|
||||||
|
private final KeyPurpose keyPurpose;
|
||||||
|
private final int startIndex;
|
||||||
|
|
||||||
|
protected MixDestination destination;
|
||||||
|
|
||||||
|
public SparrowPostmixHandler(WhirlpoolWalletService whirlpoolWalletService, Wallet wallet, KeyPurpose keyPurpose, int startIndex) {
|
||||||
|
this.whirlpoolWalletService = whirlpoolWalletService;
|
||||||
|
this.wallet = wallet;
|
||||||
|
this.keyPurpose = keyPurpose;
|
||||||
|
this.startIndex = startIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Wallet getWallet() {
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MixDestination computeNextDestination() throws Exception {
|
||||||
|
// index
|
||||||
|
int index = Math.max(getIndexHandler().getAndIncrementUnconfirmed(), startIndex);
|
||||||
|
|
||||||
|
// address
|
||||||
|
Address address = wallet.getAddress(keyPurpose, index);
|
||||||
|
String path = XPubUtil.getInstance().getPath(index, keyPurpose.getPathIndex().num());
|
||||||
|
|
||||||
|
log.info("Mixing to external xPub -> receiveAddress=" + address + ", path=" + path);
|
||||||
|
return new MixDestination(DestinationType.XPUB, index, address.toString(), path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MixDestination getDestination() {
|
||||||
|
return destination; // may be NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
public final MixDestination computeDestination() throws Exception {
|
||||||
|
// use "unconfirmed" index to avoid huge index gaps on multiple mix failures
|
||||||
|
this.destination = computeNextDestination();
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMixFail() {
|
||||||
|
if(destination != null) {
|
||||||
|
getIndexHandler().cancelUnconfirmed(destination.getIndex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRegisterOutput() {
|
||||||
|
// confirm receive address even when REGISTER_OUTPUT fails, to avoid 'ouput already registered'
|
||||||
|
getIndexHandler().confirmUnconfirmed(destination.getIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
private IIndexHandler getIndexHandler() {
|
||||||
|
return whirlpoolWalletService.whirlpoolWallet().getWalletStateSupplier().getIndexHandlerExternal();
|
||||||
|
}
|
||||||
|
}
|
|
@ -58,9 +58,15 @@ public class SparrowWalletStateSupplier implements WalletStateSupplier {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(externalIndexHandler == null) {
|
if(externalIndexHandler == null) {
|
||||||
Wallet externalWallet = SparrowDataSource.getWallet(externalDestination.getXpub());
|
Wallet externalWallet = null;
|
||||||
|
if(externalDestination.getPostmixHandler() instanceof SparrowPostmixHandler sparrowPostmixHandler) {
|
||||||
|
externalWallet = sparrowPostmixHandler.getWallet();
|
||||||
|
} else if(externalDestination.getXpub() != null) {
|
||||||
|
externalWallet = SparrowDataSource.getWallet(externalDestination.getXpub());
|
||||||
|
}
|
||||||
|
|
||||||
if(externalWallet == null) {
|
if(externalWallet == null) {
|
||||||
throw new IllegalStateException("Cannot find wallet for external destination xpub " + externalDestination.getXpub());
|
throw new IllegalStateException("Cannot find wallet for external destination " + externalDestination);
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyPurpose keyPurpose = KeyPurpose.fromChildNumber(new ChildNumber(externalDestination.getChain()));
|
KeyPurpose keyPurpose = KeyPurpose.fromChildNumber(new ChildNumber(externalDestination.getChain()));
|
||||||
|
|
Loading…
Reference in a new issue