Commit 42bf51d4 authored by zeroleak's avatar zeroleak
Browse files

add externalDestination support

parent e50378f7
...@@ -9,6 +9,7 @@ import com.samourai.whirlpool.protocol.WhirlpoolProtocol; ...@@ -9,6 +9,7 @@ import com.samourai.whirlpool.protocol.WhirlpoolProtocol;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.util.Arrays; import java.util.Arrays;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -167,8 +168,12 @@ public class Application implements ApplicationRunner { ...@@ -167,8 +168,12 @@ public class Application implements ApplicationRunner {
} }
private static String[] computeRestartArgs() { private static String[] computeRestartArgs() {
String[] ignoreArgs =
new String[] {
"--" + ApplicationArgs.ARG_INIT, "--" + ApplicationArgs.ARG_SET_EXTERNAL_XPUB
};
return Arrays.stream(applicationArguments.getSourceArgs()) return Arrays.stream(applicationArguments.getSourceArgs())
.filter(a -> !a.toLowerCase().equals("--" + ApplicationArgs.ARG_INIT)) .filter(a -> !ArrayUtils.contains(ignoreArgs, a.toLowerCase()))
.toArray(i -> new String[i]); .toArray(i -> new String[i]);
} }
} }
...@@ -18,21 +18,22 @@ public class ApplicationArgs { ...@@ -18,21 +18,22 @@ public class ApplicationArgs {
private static final String ARG_DEBUG = "debug"; private static final String ARG_DEBUG = "debug";
private static final String ARG_DEBUG_CLIENT = "debug-client"; private static final String ARG_DEBUG_CLIENT = "debug-client";
private static final String ARG_LIST_POOLS = "list-pools"; public static final String ARG_LIST_POOLS = "list-pools";
private static final String ARG_SCODE = "scode"; private static final String ARG_SCODE = "scode";
private static final String ARG_CLIENTS = "clients"; private static final String ARG_CLIENTS = "clients";
private static final String ARG_CLIENT_DELAY = "client-delay"; private static final String ARG_CLIENT_DELAY = "client-delay";
private static final String ARG_TX0_DELAY = "tx0-delay"; private static final String ARG_TX0_DELAY = "tx0-delay";
private static final String ARG_TX0_MAX_OUTPUTS = "tx0-max-outputs"; private static final String ARG_TX0_MAX_OUTPUTS = "tx0-max-outputs";
private static final String ARG_AGGREGATE_POSTMIX = "aggregate-postmix"; public static final String ARG_AGGREGATE_POSTMIX = "aggregate-postmix";
private static final String ARG_AUTO_AGGREGATE_POSTMIX = "auto-aggregate-postmix"; private static final String ARG_AUTO_AGGREGATE_POSTMIX = "auto-aggregate-postmix";
private static final String ARG_AUTO_TX0 = "auto-tx0"; private static final String ARG_AUTO_TX0 = "auto-tx0";
private static final String ARG_AUTO_MIX = "auto-mix"; private static final String ARG_AUTO_MIX = "auto-mix";
private static final String ARG_LISTEN = "listen"; private static final String ARG_LISTEN = "listen";
private static final String ARG_API_KEY = "api-key"; private static final String ARG_API_KEY = "api-key";
public static final String ARG_INIT = "init"; public static final String ARG_INIT = "init";
public static final String ARG_SET_EXTERNAL_XPUB = "set-external-xpub";
private static final String ARG_AUTHENTICATE = "authenticate"; private static final String ARG_AUTHENTICATE = "authenticate";
private static final String ARG_DUMP_PAYLOAD = "dump-payload"; public static final String ARG_DUMP_PAYLOAD = "dump-payload";
private static final String ARG_RESYNC = "resync"; private static final String ARG_RESYNC = "resync";
private ApplicationArguments args; private ApplicationArguments args;
...@@ -125,6 +126,10 @@ public class ApplicationArgs { ...@@ -125,6 +126,10 @@ public class ApplicationArgs {
return args.containsOption(ARG_INIT); return args.containsOption(ARG_INIT);
} }
public boolean isSetExternalXpub() {
return args.containsOption(ARG_SET_EXTERNAL_XPUB);
}
public boolean isAuthenticate() { public boolean isAuthenticate() {
return args.containsOption(ARG_AUTHENTICATE); return args.containsOption(ARG_AUTHENTICATE);
} }
......
...@@ -27,7 +27,7 @@ public abstract class AbstractRestController { ...@@ -27,7 +27,7 @@ public abstract class AbstractRestController {
if (!Strings.isEmpty(cliConfig.getApiKey())) { if (!Strings.isEmpty(cliConfig.getApiKey())) {
String requestApiKey = httpHeaders.getFirst(CliApi.HEADER_API_KEY); String requestApiKey = httpHeaders.getFirst(CliApi.HEADER_API_KEY);
if (!cliConfig.getApiKey().equals(requestApiKey)) { if (!cliConfig.getApiKey().equals(requestApiKey)) {
throw new NotifiableException("API key rejected"); throw new NotifiableException("API key rejected: " + requestApiKey);
} }
} }
} }
......
...@@ -6,6 +6,7 @@ import com.samourai.stomp.client.IStompClientService; ...@@ -6,6 +6,7 @@ import com.samourai.stomp.client.IStompClientService;
import com.samourai.wallet.api.backend.BackendApi; import com.samourai.wallet.api.backend.BackendApi;
import com.samourai.wallet.api.backend.BackendServer; import com.samourai.wallet.api.backend.BackendServer;
import com.samourai.wallet.util.FormatsUtilGeneric; import com.samourai.wallet.util.FormatsUtilGeneric;
import com.samourai.whirlpool.client.exception.NotifiableException;
import com.samourai.whirlpool.client.utils.ClientUtils; import com.samourai.whirlpool.client.utils.ClientUtils;
import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig; import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig;
import java.util.Collection; import java.util.Collection;
...@@ -29,7 +30,9 @@ public class CliConfig extends CliConfigFile { ...@@ -29,7 +30,9 @@ public class CliConfig extends CliConfigFile {
public WhirlpoolWalletConfig computeWhirlpoolWalletConfig( public WhirlpoolWalletConfig computeWhirlpoolWalletConfig(
IHttpClientService httpClientService, IHttpClientService httpClientService,
IStompClientService stompClientService, IStompClientService stompClientService,
BackendApi backendApi) { BackendApi backendApi,
String passphrase)
throws NotifiableException {
// check valid // check valid
if (autoAggregatePostmix && StringUtils.isEmpty(autoTx0PoolId)) { if (autoAggregatePostmix && StringUtils.isEmpty(autoTx0PoolId)) {
...@@ -37,7 +40,8 @@ public class CliConfig extends CliConfigFile { ...@@ -37,7 +40,8 @@ public class CliConfig extends CliConfigFile {
} }
WhirlpoolWalletConfig config = WhirlpoolWalletConfig config =
super.computeWhirlpoolWalletConfig(httpClientService, stompClientService, backendApi); super.computeWhirlpoolWalletConfig(
httpClientService, stompClientService, backendApi, passphrase);
config.setAutoTx0PoolId(autoTx0PoolId); config.setAutoTx0PoolId(autoTx0PoolId);
return config; return config;
} }
......
...@@ -3,11 +3,15 @@ package com.samourai.whirlpool.cli.config; ...@@ -3,11 +3,15 @@ package com.samourai.whirlpool.cli.config;
import com.samourai.http.client.IHttpClientService; import com.samourai.http.client.IHttpClientService;
import com.samourai.stomp.client.IStompClientService; import com.samourai.stomp.client.IStompClientService;
import com.samourai.wallet.api.backend.BackendApi; import com.samourai.wallet.api.backend.BackendApi;
import com.samourai.wallet.crypto.AESUtil;
import com.samourai.wallet.util.CharSequenceX;
import com.samourai.whirlpool.cli.beans.CliProxy; import com.samourai.whirlpool.cli.beans.CliProxy;
import com.samourai.whirlpool.cli.beans.CliTorExecutableMode; import com.samourai.whirlpool.cli.beans.CliTorExecutableMode;
import com.samourai.whirlpool.cli.utils.CliUtils; import com.samourai.whirlpool.cli.utils.CliUtils;
import com.samourai.whirlpool.client.exception.NotifiableException;
import com.samourai.whirlpool.client.utils.ClientUtils; import com.samourai.whirlpool.client.utils.ClientUtils;
import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig; import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig;
import com.samourai.whirlpool.client.wallet.beans.ExternalDestination;
import com.samourai.whirlpool.client.wallet.beans.WhirlpoolServer; import com.samourai.whirlpool.client.wallet.beans.WhirlpoolServer;
import com.samourai.whirlpool.client.whirlpool.ServerApi; import com.samourai.whirlpool.client.whirlpool.ServerApi;
import java.util.HashMap; import java.util.HashMap;
...@@ -15,6 +19,7 @@ import java.util.LinkedHashMap; ...@@ -15,6 +19,7 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings; import org.apache.logging.log4j.util.Strings;
import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.NetworkParameters;
import org.hibernate.validator.constraints.Range; import org.hibernate.validator.constraints.Range;
...@@ -44,12 +49,10 @@ public abstract class CliConfigFile { ...@@ -44,12 +49,10 @@ public abstract class CliConfigFile {
private Optional<CliProxy> _cliProxy; private Optional<CliProxy> _cliProxy;
@NotEmpty private MixConfig mix; @NotEmpty private MixConfig mix;
@NotEmpty private ApiConfig api; @NotEmpty private ApiConfig api;
@NotEmpty private ExternalDestinationConfig externalDestination;
@Autowired BuildProperties buildProperties; @Autowired BuildProperties buildProperties;
private static final String PUSHTX_AUTO = "auto";
private static final String PUSHTX_INTERACTIVE = "interactive";
public CliConfigFile() { public CliConfigFile() {
// warning: properties are NOT loaded yet // warning: properties are NOT loaded yet
// it will be loaded later on SpringBoot application run() // it will be loaded later on SpringBoot application run()
...@@ -69,6 +72,7 @@ public abstract class CliConfigFile { ...@@ -69,6 +72,7 @@ public abstract class CliConfigFile {
this.proxy = copy.proxy; this.proxy = copy.proxy;
this.requestTimeout = copy.requestTimeout; this.requestTimeout = copy.requestTimeout;
this.api = new ApiConfig(copy.api); this.api = new ApiConfig(copy.api);
this.externalDestination = new ExternalDestinationConfig(copy.externalDestination);
this.mix = new MixConfig(copy.mix); this.mix = new MixConfig(copy.mix);
this.buildProperties = copy.buildProperties; this.buildProperties = copy.buildProperties;
} }
...@@ -129,6 +133,14 @@ public abstract class CliConfigFile { ...@@ -129,6 +133,14 @@ public abstract class CliConfigFile {
this.apiKey = apiKey; this.apiKey = apiKey;
} }
public ExternalDestinationConfig getExternalDestination() {
return externalDestination;
}
public void setExternalDestination(ExternalDestinationConfig externalDestination) {
this.externalDestination = externalDestination;
}
public String getSeed() { public String getSeed() {
return seed; return seed;
} }
...@@ -343,6 +355,83 @@ public abstract class CliConfigFile { ...@@ -343,6 +355,83 @@ public abstract class CliConfigFile {
} }
} }
public static class ExternalDestinationConfig {
@NotEmpty private String xpub;
@NotEmpty private int chain;
@NotEmpty private int startIndex;
@NotEmpty private int mixs;
@NotEmpty private int mixsRandomFactor;
public ExternalDestinationConfig() {}
public ExternalDestinationConfig(ExternalDestinationConfig copy) {
this.xpub = copy.xpub;
this.chain = copy.chain;
this.startIndex = copy.startIndex;
this.mixs = copy.mixs;
this.mixsRandomFactor = copy.mixsRandomFactor;
}
public String getXpub() {
return xpub;
}
public void setXpub(String xpub) {
this.xpub = xpub;
}
public int getChain() {
return chain;
}
public void setChain(int chain) {
this.chain = chain;
}
public int getStartIndex() {
return startIndex;
}
public void setStartIndex(int startIndex) {
this.startIndex = startIndex;
}
public int getMixs() {
return mixs;
}
public void setMixs(int mixs) {
this.mixs = mixs;
}
public int getMixsRandomFactor() {
return mixsRandomFactor;
}
public void setMixsRandomFactor(int mixsRandomFactor) {
this.mixsRandomFactor = mixsRandomFactor;
}
public Map<String, String> getConfigInfo() {
Map<String, String> configInfo = new HashMap<>();
String externalDestination =
"xpub="
+ (!StringUtils.isEmpty(xpub)
? xpub
+ ", chain="
+ chain
+ ", startIndex="
+ startIndex
+ ", mixs="
+ mixs
+ ", mixsRandomFactor="
+ mixsRandomFactor
: "null");
configInfo.put("cli/externalDestination", externalDestination);
return configInfo;
}
}
public static class TorConfig { public static class TorConfig {
@NotEmpty private String executable; @NotEmpty private String executable;
private CliTorExecutableMode executableMode; private CliTorExecutableMode executableMode;
...@@ -508,7 +597,9 @@ public abstract class CliConfigFile { ...@@ -508,7 +597,9 @@ public abstract class CliConfigFile {
protected WhirlpoolWalletConfig computeWhirlpoolWalletConfig( protected WhirlpoolWalletConfig computeWhirlpoolWalletConfig(
IHttpClientService httpClientService, IHttpClientService httpClientService,
IStompClientService stompClientService, IStompClientService stompClientService,
BackendApi backendApi) { BackendApi backendApi,
String passphrase)
throws NotifiableException {
String serverUrl = computeServerUrl(); String serverUrl = computeServerUrl();
NetworkParameters params = server.getParams(); NetworkParameters params = server.getParams();
ServerApi serverApi = new ServerApi(serverUrl, httpClientService); ServerApi serverApi = new ServerApi(serverUrl, httpClientService);
...@@ -529,10 +620,36 @@ public abstract class CliConfigFile { ...@@ -529,10 +620,36 @@ public abstract class CliConfigFile {
config.setAutoMix(mix.isAutoMix()); config.setAutoMix(mix.isAutoMix());
config.setOverspend(mix.getOverspend()); config.setOverspend(mix.getOverspend());
ExternalDestination ed = computeExternalDestination(passphrase);
config.setExternalDestination(ed);
config.setResyncOnFirstRun(true); config.setResyncOnFirstRun(true);
return config; return config;
} }
private ExternalDestination computeExternalDestination(String passphrase)
throws NotifiableException {
if (!Strings.isEmpty(this.externalDestination.xpub)
&& this.externalDestination.chain >= 0
&& this.externalDestination.mixs > 0
&& this.externalDestination.mixsRandomFactor >= 0) {
try {
// decrypt externalDestination
String xpubDecrypted =
AESUtil.decrypt(this.externalDestination.xpub, new CharSequenceX(passphrase));
return new ExternalDestination(
xpubDecrypted,
this.externalDestination.chain,
this.externalDestination.startIndex,
this.externalDestination.mixs,
this.externalDestination.mixsRandomFactor);
} catch (Exception e) {
throw new NotifiableException("Invalid config value for: externalDestination.xpub");
}
}
return null;
}
public Map<String, String> getConfigInfo() { public Map<String, String> getConfigInfo() {
Map<String, String> configInfo = new LinkedHashMap<>(); Map<String, String> configInfo = new LinkedHashMap<>();
configInfo.put("cli/server", server.name()); configInfo.put("cli/server", server.name());
...@@ -550,6 +667,7 @@ public abstract class CliConfigFile { ...@@ -550,6 +667,7 @@ public abstract class CliConfigFile {
configInfo.put("cli/proxy", proxy != null ? ClientUtils.maskString(proxy) : "null"); configInfo.put("cli/proxy", proxy != null ? ClientUtils.maskString(proxy) : "null");
configInfo.putAll(mix.getConfigInfo()); configInfo.putAll(mix.getConfigInfo());
configInfo.putAll(api.getConfigInfo()); configInfo.putAll(api.getConfigInfo());
configInfo.putAll(externalDestination.getConfigInfo());
configInfo.put("cli/buildVersion", getBuildVersion() != null ? getBuildVersion() : "null"); configInfo.put("cli/buildVersion", getBuildVersion() != null ? getBuildVersion() : "null");
return configInfo; return configInfo;
} }
......
...@@ -2,7 +2,6 @@ package com.samourai.whirlpool.cli.run; ...@@ -2,7 +2,6 @@ package com.samourai.whirlpool.cli.run;
import com.samourai.wallet.client.Bip84Wallet; import com.samourai.wallet.client.Bip84Wallet;
import com.samourai.whirlpool.cli.ApplicationArgs; import com.samourai.whirlpool.cli.ApplicationArgs;
import com.samourai.whirlpool.cli.config.CliConfig;
import com.samourai.whirlpool.cli.services.CliWalletService; import com.samourai.whirlpool.cli.services.CliWalletService;
import com.samourai.whirlpool.cli.services.WalletAggregateService; import com.samourai.whirlpool.cli.services.WalletAggregateService;
import com.samourai.whirlpool.cli.wallet.CliWallet; import com.samourai.whirlpool.cli.wallet.CliWallet;
...@@ -54,7 +53,16 @@ public class RunCliCommand { ...@@ -54,7 +53,16 @@ public class RunCliCommand {
} }
} }
public static boolean hasCommandToRun(ApplicationArgs appArgs, CliConfig cliConfig) { public static String getCommandToRun(ApplicationArgs appArgs) {
return appArgs.isDumpPayload() || appArgs.isAggregatePostmix() || appArgs.isListPools(); if (appArgs.isDumpPayload()) {
return ApplicationArgs.ARG_DUMP_PAYLOAD;
}
if (appArgs.isAggregatePostmix()) {
return ApplicationArgs.ARG_AGGREGATE_POSTMIX;
}
if (appArgs.isListPools()) {
return appArgs.ARG_LIST_POOLS;
}
return null;
} }
} }
package com.samourai.whirlpool.cli.run; package com.samourai.whirlpool.cli.run;
import com.samourai.whirlpool.cli.ApplicationArgs;
import com.samourai.whirlpool.cli.beans.WhirlpoolPairingPayload; import com.samourai.whirlpool.cli.beans.WhirlpoolPairingPayload;
import com.samourai.whirlpool.cli.services.CliConfigService; import com.samourai.whirlpool.cli.services.CliConfigService;
import com.samourai.whirlpool.cli.services.CliWalletService;
import com.samourai.whirlpool.cli.utils.CliUtils; import com.samourai.whirlpool.cli.utils.CliUtils;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import org.slf4j.Logger; import org.slf4j.Logger;
...@@ -12,17 +10,10 @@ import org.slf4j.LoggerFactory; ...@@ -12,17 +10,10 @@ import org.slf4j.LoggerFactory;
public class RunCliInit { public class RunCliInit {
private Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private ApplicationArgs appArgs;
private CliConfigService cliConfigService; private CliConfigService cliConfigService;
private CliWalletService cliWalletService;
public RunCliInit( public RunCliInit(CliConfigService cliConfigService) {
ApplicationArgs appArgs,
CliConfigService cliConfigService,
CliWalletService cliWalletService) {
this.appArgs = appArgs;
this.cliConfigService = cliConfigService; this.cliConfigService = cliConfigService;
this.cliWalletService = cliWalletService;
} }
public void run() throws Exception { public void run() throws Exception {
...@@ -48,10 +39,7 @@ public class RunCliInit { ...@@ -48,10 +39,7 @@ public class RunCliInit {
} else { } else {
// samourai backend => Tor optional // samourai backend => Tor optional
log.info("⣿ • Enable Tor? (you can change this later)"); log.info("⣿ • Enable Tor? (you can change this later)");
String torStr = tor = CliUtils.readUserInputRequiredBoolean("Enable Tor? (y/n)");
CliUtils.readUserInputRequired(
"Enable Tor? (y/n)", false, new String[] {"y", "n", "Y", "N"});
tor = torStr.toLowerCase().equals("y");
log.info("⣿ "); log.info("⣿ ");
} }
......
package com.samourai.whirlpool.cli.run;
import com.samourai.wallet.util.FormatsUtilGeneric;
import com.samourai.wallet.util.XPubUtil;
import com.samourai.whirlpool.cli.services.CliConfigService;
import com.samourai.whirlpool.cli.utils.CliUtils;
import com.samourai.whirlpool.client.exception.NotifiableException;
import java.lang.invoke.MethodHandles;
import org.apache.commons.lang3.StringUtils;
import org.bitcoinj.core.NetworkParameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RunSetExternalXpub {
private Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final FormatsUtilGeneric formatUtil = FormatsUtilGeneric.getInstance();
private static final XPubUtil xPubUtil = XPubUtil.getInstance();
private CliConfigService cliConfigService;
public RunSetExternalXpub(CliConfigService cliConfigService) {
this.cliConfigService = cliConfigService;
}
public void run(NetworkParameters params, String passphrase) throws Exception {
log.info(CliUtils.LOG_SEPARATOR);
log.info("⣿ EXTERNAL XPUB CONFIGURATION");
log.info("⣿ This will configure an external XPub as destination for your mixed funds.");
log.info("⣿ This XPub will remain encrypted and private.");
log.info(
"⣿ It will not be shared with the Whirlpool coordinator, the Samourai backend server or your own Dojo.");
log.info("⣿ ");
// xpub
log.info("⣿ • Paste external BIP84 XPub here (or <enter> to unset current destination):");
String xpub = readXpub();
if (xpub != null) {
// chain
log.info("⣿ • Chain for XPub derivation path m/84'/<chain>' (use 0 for standard):");
int chain = CliUtils.readUserInputRequiredInt("Chain?(0)", 0, 0);
log.info("⣿ ");
// startIndex
log.info(
"⣿ • Starting index for XPub derivation path m/84'/"
+ chain
+ "'/<starting index>' (use 0 for standard):");
int startIndex = CliUtils.readUserInputRequiredInt("Starting index?(0)", 0, 0);
log.info("⣿ ");
// mixs
log.info("⣿ • Number of mixs to achieve before sending funds to XPub (>0):");
int mixs = CliUtils.readUserInputRequiredInt("Mixs?", 1);
log.info("⣿ ");
// print addresses
log.info(CliUtils.LOG_SEPARATOR);
log.info("⣿ WARNING!");
log.info(CliUtils.LOG_SEPARATOR);
log.info(
"⣿ Your funds will be automatically sent to external XPub after being mixed *at least* "
+ mixs
+ " times. This number may randomly slightly increase to improve your privacy.");
log.info("⣿ XPub: " + xpub);
log.info("⣿ Derivation path: m/84'/" + chain + "'/" + startIndex + "+'");
log.info("⣿ Sample destination addresses:");
for (int i = startIndex; i < startIndex + 3; i++) {
String address = xPubUtil.getAddressBech32(xpub, i, chain, params);
log.info("⣿ m/84'/" + chain + "'/" + i + "'" + ": " + address);
}
// validate
boolean validate = CliUtils.readUserInputRequiredBoolean("Continue? (y/n)");
if (!validate) {
throw new NotifiableException("Aborted");
}
// set configuration
cliConfigService.setExternalDestination(xpub, chain, startIndex, mixs, passphrase);
} else {
log.info("⣿ This will unset external XPub. Your funds will stay on your POSTMIX wallet.");
// validate
boolean validate = CliUtils.readUserInputRequiredBoolean("Continue? (y/n)");
if (!validate) {
throw new NotifiableException("Aborted");
}
// clear configuration