Progress mixing GUI
MODIFY
HiddenWallet.Daemon/Controllers/WalletController.cs
HiddenWallet.Daemon/Controllers/WalletController.cs
@@ -146,7 +146,7 @@ namespace HiddenWallet.Daemon.Controllers
{
try
{
- var fail = GetAccount(account, out SafeAccount safeAccount);
+ var fail = Global.WalletWrapper.GetAccount(account, out SafeAccount safeAccount);
if (fail != null) return new ObjectResult(fail);
return new ObjectResult(new BalancesResponse
@@ -161,37 +161,13 @@ namespace HiddenWallet.Daemon.Controllers
}
}
- /// <returns>null if didn't fail</returns>
- private FailureResponse GetAccount(string account, out SafeAccount safeAccount)
- {
- safeAccount = null;
- if (account == null)
- return new FailureResponse { Message = "No request body specified" };
-
- if (!Global.WalletWrapper.IsDecrypted)
- return new FailureResponse { Message = "Wallet isn't decrypted" };
-
- var trimmed = account;
- if (string.Equals(trimmed, "alice", StringComparison.OrdinalIgnoreCase))
- {
- safeAccount = Global.WalletWrapper.AliceAccount;
- return null;
- }
- else if (string.Equals(trimmed, "bob", StringComparison.OrdinalIgnoreCase))
- {
- safeAccount = Global.WalletWrapper.BobAccount;
- return null;
- }
- else return new FailureResponse { Message = "Wrong account" };
- }
-
[Route("receive/{account}")]
[HttpGet]
public IActionResult Receive(string account)
{
try
{
- var fail = GetAccount(account, out SafeAccount safeAccount);
+ var fail = Global.WalletWrapper.GetAccount(account, out SafeAccount safeAccount);
if (fail != null) return new ObjectResult(fail);
return new ObjectResult(Global.WalletWrapper.GetReceiveResponse(safeAccount));
@@ -208,7 +184,7 @@ namespace HiddenWallet.Daemon.Controllers
{
try
{
- var fail = GetAccount(account, out SafeAccount safeAccount);
+ var fail = Global.WalletWrapper.GetAccount(account, out SafeAccount safeAccount);
if (fail != null) return new ObjectResult(fail);
return new ObjectResult(Global.WalletWrapper.GetHistoryResponse(safeAccount));
@@ -230,7 +206,7 @@ namespace HiddenWallet.Daemon.Controllers
return new ObjectResult(new FailureResponse { Message = "Bad request", Details = "" });
}
- var fail = GetAccount(account, out SafeAccount safeAccount);
+ var fail = Global.WalletWrapper.GetAccount(account, out SafeAccount safeAccount);
if (fail != null) return new ObjectResult(fail);
BitcoinAddress address;
@@ -313,34 +289,5 @@ namespace HiddenWallet.Daemon.Controllers
return new ObjectResult(new FailureResponse { Message = ex.Message, Details = ex.ToString() });
}
}
-
- [Route("tumble")]
- [HttpPost]
- public async Task<IActionResult> TumbleAsync([FromBody]TumbleRequest request)
- {
- try
- {
- var getFrom = GetAccount(request.From, out SafeAccount fromAccount);
- if (getFrom != null) return new ObjectResult(getFrom);
-
- var getTo = GetAccount(request.To, out SafeAccount toAccount);
- if (getTo != null) return new ObjectResult(getTo);
-
- BaseResponse result = await Global.WalletWrapper.TumbleAsync(fromAccount, toAccount);
-
- if (result.Success)
- {
- return new ObjectResult(new SuccessResponse());
- }
- else
- {
- return new ObjectResult(new FailureResponse { Message = "Tumbler Error", Details = "Could not submit for mixing [01]" });
- }
- }
- catch (Exception ex)
- {
- return new ObjectResult(new FailureResponse { Message = "Tumbler Error", Details = "Could not submit for mixing [02]. " + ex.ToString() });
- }
- }
}
}

MODIFY
HiddenWallet.Daemon/HiddenWallet.Daemon.csproj
HiddenWallet.Daemon/HiddenWallet.Daemon.csproj
<None Remove="wwwroot\**" />
</ItemGroup>
- <ItemGroup>
- <Folder Include="Models\" />
- </ItemGroup>
-
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />

MODIFY
HiddenWallet.Daemon/Models/TumbleRequest.cs
HiddenWallet.Daemon/Models/TumbleRequest.cs
@@ -7,6 +7,7 @@ namespace HiddenWallet.Daemon.Models
{
public class TumbleRequest
{
+ public int RoundCount { get; set; }
public string From { get; set; }
public string To { get; set; }
}

MODIFY
HiddenWallet.Daemon/Wrappers/WalletWrapper.cs
HiddenWallet.Daemon/Wrappers/WalletWrapper.cs
@@ -354,30 +354,28 @@ namespace HiddenWallet.Daemon.Wrappers
else return new FailureResponse { Message = result.FailingReason, Details = "" };
}
- public async Task<BaseResponse> TumbleAsync(SafeAccount fromAccount, SafeAccount toAccount)
+ /// <returns>null if didn't fail</returns>
+ public FailureResponse GetAccount(string account, out SafeAccount safeAccount)
{
- try
- {
- IEnumerable<Script> unusedOutputs = await WalletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, toAccount, HdPathType.NonHardened);
- BitcoinAddress activeOutput = unusedOutputs.RandomElement().GetDestinationAddress(Network); // TODO: this is sub-optimal, it'd be better to not which had been already registered and not reregister it
- BitcoinWitPubKeyAddress bech32 = new BitcoinWitPubKeyAddress(activeOutput.ToString(), Network);
+ safeAccount = null;
+ if (account == null)
+ return new FailureResponse { Message = "No request body specified" };
- uint256 result = await WalletJob.CoinJoinService.TumbleAsync(fromAccount, bech32, CancellationToken.None);
+ if (!IsDecrypted)
+ return new FailureResponse { Message = "Wallet isn't decrypted" };
- if (null != result)
+ var trimmed = account;
+ if (string.Equals(trimmed, "alice", StringComparison.OrdinalIgnoreCase))
{
- return new SuccessResponse();
- }
- else
- {
- return new FailureResponse { Message = "Failed to start Mix. An error occured. [01]", Details = "" };
- }
-
+ safeAccount = AliceAccount;
+ return null;
}
- catch (Exception)
+ else if (string.Equals(trimmed, "bob", StringComparison.OrdinalIgnoreCase))
{
- return new FailureResponse { Message = "Failed to start Mix. An error occured. [02]", Details = "" };
+ safeAccount = BobAccount;
+ return null;
}
+ else return new FailureResponse { Message = "Wrong account" };
}
}
}

MODIFY
HiddenWallet.FullSpvWallet/ChaumianCoinJoin/CoinJoinService.cs
HiddenWallet.FullSpvWallet/ChaumianCoinJoin/CoinJoinService.cs
@@ -138,13 +138,18 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
#region Tumbling
+ private CancellationTokenSource CancelTumbling { get; set; }
/// <summary>
/// Participates one tumbling round
/// </summary>
- public async Task<uint256> TumbleAsync(SafeAccount from, BitcoinWitPubKeyAddress to, CancellationToken cancel)
+ public async Task<uint256> TumbleAsync(SafeAccount from, BitcoinWitPubKeyAddress to, CancellationToken ctsToken)
{
try
{
+ CancelTumbling?.Dispose();
+ CancelTumbling = CancellationTokenSource.CreateLinkedTokenSource(ctsToken);
+ var cancel = CancelTumbling.Token;
+
CoinJoin = null;
TumblingException = null;
@@ -400,10 +405,12 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
/// </summary>
public async Task CancelMixAsync(CancellationToken cancel)
{
- if (UniqueAliceId != null)
+ if (TumblerConnection != null && UniqueAliceId != null)
{
var request = new DisconnectionRequest { UniqueId = UniqueAliceId };
await TumblerClient.PostDisconnectionAsync(request, cancel);
+
+ CancelTumbling?.Cancel();
}
}
@@ -440,6 +447,7 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
}
TumblerClient?.Dispose();
+ CancelTumbling?.Dispose();
}
catch (Exception)
{

MODIFY
HiddenWallet.Gui/app/html/wallet-content.html
HiddenWallet.Gui/app/html/wallet-content.html
</table>
<hr />
-
- <table>
+ <div id="mixing-input-elements">
+ <table id="tumbler-fom-to-table">
<tr>
<td style="width:270px;">
<label>From:</label>
</button>
</div>
</div>
+ <button id="cancel-mixing-button" style="display:none" class="btn btn-lg btn-danger btn-block" onclick="cancel-mix()">
+ Cancel Ongoing Mixing
+ </button>
+ </div>
</div>
\ No newline at end of file

MODIFY
HiddenWallet.Gui/app/js/hw-client.ts
HiddenWallet.Gui/app/js/hw-client.ts
@@ -74,3 +74,17 @@ function httpPostWalletAsync(path: string, data: any, callback) {
xmlHttp.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
xmlHttp.send(JSON.stringify(data));
}
+
+function httpPostTumblerAsync(path: string, data: any, callback) {
+ let theUrl = "http://localhost:37120/api/v1/tumbler/" + path;
+ let xmlHttp: XMLHttpRequest = new XMLHttpRequest();
+
+ xmlHttp.onreadystatechange = function () {
+ if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
+ callback(JSON.parse(xmlHttp.responseText));
+ }
+
+ xmlHttp.open("POST", theUrl, true); // true for asynchronous
+ xmlHttp.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
+ xmlHttp.send(JSON.stringify(data));
+}
\ No newline at end of file

MODIFY
HiddenWallet.Gui/app/js/wallet-phase.ts
HiddenWallet.Gui/app/js/wallet-phase.ts
@@ -153,7 +153,28 @@ function round(value: number, precision: number) {
}
function mixerShow() {
- httpGetTumblerAsync("connection", function (json) { }); // makes sure the tumbler is initialized
+ httpGetTumblerAsync("connection", function (json)
+ {
+ try {
+ if (json.Success) {
+ let cancelMixingButton: HTMLElement = document.getElementById("cancel-mixing-button");
+ let mixingInputElements: HTMLElement = document.getElementById("mixing-input-elements");
+ if (json.IsMixOngoing)
+ {
+ mixingInputElements.style.display = "none";
+ cancelMixingButton.style.display = "inline";
+ }
+ else
+ {
+ mixingInputElements.style.display = "inline";
+ cancelMixingButton.style.display = "none";
+ }
+ }
+ } catch (err) {
+
+ }
+ }); // makes sure the tumbler is initialized
+
let walletContentFrame: HTMLIFrameElement = <HTMLIFrameElement>document.getElementById("wallet-content-frame");
let mixerContentElem: HTMLElement = walletContentFrame.contentWindow.document.getElementById("mixer-content");
@@ -233,33 +254,42 @@ function refreshTumblerConnection() {
interface TumbleRequest {
From: string;
To: string;
+ RoundCount: string
}
function mix() {
-
let from: HTMLElement = document.getElementById("choose-wallet-dropdown-active");
let to: HTMLElement = document.getElementById("tumbling-to-wallet");
- var obj: TumbleRequest = { From: from.innerText, To: to.innerText };
+ let obj: TumbleRequest = { From: from.innerText, To: to.innerText, RoundCount: String(1) };
let json: any;
- let mixingButton: HTMLButtonElement = document.getElementById("mixing-button") as HTMLButtonElement;
+ let mixingInputElements: HTMLElement = document.getElementById("mixing-input-elements");
+ let cancelMixingButton: HTMLElement = document.getElementById("cancel-mixing-button");
+
+ mixingInputElements.style.display = "none";
+ cancelMixingButton.style.display = "inline";
- httpPostWalletAsync("tumble", obj, function (json) {
+ httpPostTumblerAsync("tumble", obj, function (json) {
+ try {
if (json.Success === false) {
- let alertMessage: string = `Couldn't start the mix: ${json.Message}`;
+ let alertMessage: string = `Couldn't finish all requested mixing rounds: ${json.Message}`;
if (!isBlank(json.Details)) {
- alertMessage = `${alertMessage}\n\nDetails:\n${json.Details}`;
+ alertMessage = `${alertMessage}
+
+ Details:
+ ${json.Details}`;
}
alert(alertMessage);
}
- else {
- mixingButton.disabled = true;
- alert("Mix submitted");
+ } catch (err) {
+
}
+
+ mixerShow();
});
}

Progress mixing GUI