mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +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') {
|
||||
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')
|
||||
}
|
||||
|
||||
|
@ -387,7 +387,7 @@ extraJavaModuleInfo {
|
|||
module('cbor-0.9.jar', 'co.nstant.in.cbor', '0.9') {
|
||||
exports('co.nstant.in.cbor')
|
||||
}
|
||||
module('nightjar-0.2.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('net.sourceforge.streamsupport')
|
||||
requires('org.slf4j')
|
||||
|
@ -401,6 +401,7 @@ extraJavaModuleInfo {
|
|||
exports('com.samourai.wallet.api.backend.beans')
|
||||
exports('com.samourai.wallet.client.indexHandler')
|
||||
exports('com.samourai.wallet.hd')
|
||||
exports('com.samourai.wallet.util')
|
||||
exports('com.samourai.whirlpool.client.event')
|
||||
exports('com.samourai.whirlpool.client.wallet')
|
||||
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()
|
||||
&& openWallet != wallet && openWallet != wallet.getMasterWallet()
|
||||
&& openWallet.getPolicyType().equals(PolicyType.SINGLE)
|
||||
&& !StandardAccount.WHIRLPOOL_ACCOUNTS.contains(openWallet.getStandardAccountType())).collect(Collectors.toList());
|
||||
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_WalletFactoryGeneric;
|
||||
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.wallet.WhirlpoolEventService;
|
||||
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.dataSource.SparrowDataSource;
|
||||
import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowMinerFeeSupplier;
|
||||
import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowPostmixHandler;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
@ -365,18 +367,12 @@ public class Whirlpool {
|
|||
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();
|
||||
int startIndex = highestUsedIndex == null ? 0 : highestUsedIndex + 1;
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -47,7 +47,7 @@ public class SparrowWalletStateSupplier implements WalletStateSupplier {
|
|||
indexHandler = new SparrowIndexHandler(wallet, walletNode, 0);
|
||||
indexHandlerWallets.put(key, indexHandler);
|
||||
}
|
||||
|
||||
|
||||
return indexHandler;
|
||||
}
|
||||
|
||||
|
@ -58,9 +58,15 @@ public class SparrowWalletStateSupplier implements WalletStateSupplier {
|
|||
}
|
||||
|
||||
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) {
|
||||
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()));
|
||||
|
|
Loading…
Reference in a new issue