Commit 6f2a97b7 authored by Jeremy Bokobza's avatar Jeremy Bokobza Committed by GitHub

Merge pull request #92 from bokobza/master

Added State machine to save the current state of the tumbling session
parents 10ffe1bf 602c13e6
......@@ -43,7 +43,7 @@ namespace Breeze.TumbleBit.Controllers
}
catch (Exception e)
{
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, $"An error occured connecting to the tumbler.", e.ToString());
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, $"An error occured connecting to the tumbler with uri {request.ServerAddress}.", e.ToString());
}
}
......@@ -68,7 +68,7 @@ namespace Breeze.TumbleBit.Controllers
}
catch (Exception e)
{
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, $"An error occured connecting to the tumbler.", e.ToString());
return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "An error occured starting tumbling session.", e.ToString());
}
}
}
......
namespace Breeze.TumbleBit.Client
{
public interface IStateMachine
{
/// <summary>
/// Saves the state of the current tumbling session.
/// </summary>
void Save();
/// <summary>
/// Loads the state of the current tumbling session.
/// </summary>
/// <returns></returns>
IStateMachine Load();
/// <summary>
/// Deletes the state of the current tumbling session..
/// </summary>
void Delete();
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using NBitcoin;
using NTumbleBit.ClassicTumbler;
......@@ -27,5 +25,15 @@ namespace Breeze.TumbleBit.Client
/// <param name="height">The height of the block in the blockchain.</param>
/// <param name="block">The block.</param>
void ProcessBlock(int height, Block block);
/// <summary>
/// Pauses the tumbling.
/// </summary>
void PauseTumbling();
/// <summary>
/// Finishes the tumbling and clean up all saved data.
/// </summary>
void FinishTumbling();
}
}
using NBitcoin;
using NTumbleBit.ClassicTumbler;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Breeze.TumbleBit.Models
{
......
using NBitcoin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Breeze.TumbleBit.Models
{
......
using System;
using System.Collections.Generic;
using Breeze.TumbleBit.Client;
using Breeze.TumbleBit.Controllers;
using Stratis.Bitcoin.Builder.Feature;
using Microsoft.Extensions.DependencyInjection;
using Stratis.Bitcoin.Builder;
using Stratis.Bitcoin.Logging;
using Microsoft.Extensions.Logging;
using Serilog;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Stratis.Bitcoin.Wallet.JsonConverters;
namespace Breeze.TumbleBit
{
public class TumbleBitFeature : FullNodeFeature
{
public TumbleBitFeature()
{
}
public override void Start()
{
}
......@@ -35,6 +32,13 @@ namespace Breeze.TumbleBit
.AddFeature<TumbleBitFeature>()
.FeatureServices(services =>
{
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new List<JsonConverter> { new NetworkConverter() }
};
services.AddSingleton<ITumbleBitManager, TumbleBitManager> ();
services.AddSingleton<TumbleBitController>();
});
......
......@@ -4,7 +4,6 @@ using Microsoft.Extensions.Logging;
using NBitcoin;
using NTumbleBit.ClassicTumbler;
using Stratis.Bitcoin;
using Stratis.Bitcoin.Logging;
using Stratis.Bitcoin.Wallet;
namespace Breeze.TumbleBit.Client
......@@ -16,11 +15,13 @@ namespace Breeze.TumbleBit.Client
public class TumbleBitManager : ITumbleBitManager
{
private ITumblerService tumblerService;
private IWalletManager walletManager;
private readonly IWalletManager walletManager;
private readonly ILogger logger;
private readonly Signals signals;
private readonly ConcurrentChain chain;
private readonly Network network;
private TumblingState tumblingState;
private IDisposable blockReceiver;
private ClassicTumblerParameters TumblerParameters { get; set; }
......@@ -29,7 +30,11 @@ namespace Breeze.TumbleBit.Client
this.walletManager = walletManager;
this.chain = chain;
this.signals = signals;
this.network = network;
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
// load the persisted tumbling state
this.tumblingState = TumblingState.LoadState();
}
/// <inheritdoc />
......@@ -37,34 +42,85 @@ namespace Breeze.TumbleBit.Client
{
this.tumblerService = new TumblerService(serverAddress);
this.TumblerParameters = await this.tumblerService.GetClassicTumblerParametersAsync();
if (this.TumblerParameters.Network != this.network)
{
throw new Exception($"The tumbler is on network {this.TumblerParameters.Network} while the wallet is on network {this.network}.");
}
if (this.tumblingState == null)
{
this.tumblingState = new TumblingState();
}
// update and save the state
this.tumblingState.TumblerParameters = this.TumblerParameters;
this.tumblingState.Save();
return this.TumblerParameters;
}
/// <inheritdoc />
public Task TumbleAsync(string destinationWalletName)
{
// make sure the tumbler service is initialized
if (this.TumblerParameters == null || this.tumblerService == null)
{
throw new Exception("Please connect to the tumbler first.");
}
// make sure that the user is not trying to resume the process with a different wallet
if (!string.IsNullOrEmpty(this.tumblingState.DestinationWalletName) && this.tumblingState.DestinationWalletName != destinationWalletName)
{
throw new Exception("Please use the same destination wallet until the end of this tumbling session.");
}
Wallet destinationWallet = this.walletManager.GetWallet(destinationWalletName);
if (destinationWallet == null)
{
throw new Exception($"Destination not found. Have you created a wallet with name {destinationWalletName}?");
}
// subscribe to receiving blocks and transactions
this.signals.Blocks.Subscribe(new BlockObserver(this.chain, this));
// update the state and save
this.tumblingState.DestinationWalletName = destinationWalletName;
this.tumblingState.Save();
// subscribe to receiving blocks
this.blockReceiver = this.signals.Blocks.Subscribe(new BlockObserver(this.chain, this));
return Task.CompletedTask;
}
/// <inheritdoc />
public void PauseTumbling()
{
this.logger.LogDebug($"Stopping the tumbling. Current height is {this.chain.Tip.Height}.");
this.blockReceiver.Dispose();
this.tumblingState.Save();
}
/// <inheritdoc />
public void FinishTumbling()
{
this.logger.LogDebug($"The tumbling process is wrapping up. Current height is {this.chain.Tip.Height}.");
this.blockReceiver.Dispose();
this.tumblingState.Delete();
this.tumblingState = null;
}
/// <inheritdoc />
public void ProcessBlock(int height, Block block)
{
// TODO start the state machine
this.logger.LogDebug($"Receive block with height {height}");
// update the block height in the tumbling state
if (this.tumblingState.LastBlockReceivedHeight == 0)
{
this.tumblingState.StartHeight = height;
}
this.tumblingState.LastBlockReceivedHeight = height;
this.tumblingState.Save();
}
}
}
using System;
using System.IO;
using System.Runtime.InteropServices;
using Newtonsoft.Json;
using NTumbleBit.ClassicTumbler;
namespace Breeze.TumbleBit.Client
{
public class TumblingState : IStateMachine
{
private const string StateFileName = "tumblebit_state.json";
[JsonProperty("tumblerParameters")]
public ClassicTumblerParameters TumblerParameters { get; set; }
[JsonProperty("tumblerUri")]
public Uri TumblerUri { get; set; }
[JsonProperty("startHeight")]
public int StartHeight { get; set; }
[JsonProperty("lastBlockReceivedHeight")]
public int LastBlockReceivedHeight { get; set; }
[JsonProperty("destinationWalletName")]
public string DestinationWalletName { get; set; }
/// <inheritdoc />
public void Save()
{
File.WriteAllText(GetStateFilePath(), JsonConvert.SerializeObject(this));
}
/// <inheritdoc />
public IStateMachine Load()
{
return LoadState();
}
/// <inheritdoc />
public void Delete()
{
var stateFilePath = GetStateFilePath();
File.Delete(stateFilePath);
}
/// <summary>
/// Loads the saved state of the tumbling execution to the file system.
/// </summary>
/// <returns></returns>
public static TumblingState LoadState()
{
var stateFilePath = GetStateFilePath();
if (!File.Exists(stateFilePath))
{
return null;
}
// load the file from the local system
return JsonConvert.DeserializeObject<TumblingState>(File.ReadAllText(stateFilePath));
}
/// <summary>
/// Gets the file path of the file containing the state of the tumbling execution.
/// </summary>
/// <returns></returns>
private static string GetStateFilePath()
{
string defaultFolderPath;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
defaultFolderPath = $@"{Environment.GetEnvironmentVariable("AppData")}\Breeze\TumbleBit";
}
else
{
defaultFolderPath = $"{Environment.GetEnvironmentVariable("HOME")}/.breeze/TumbleBit";
}
// create the directory if it doesn't exist
Directory.CreateDirectory(defaultFolderPath);
return Path.Combine(defaultFolderPath, StateFileName);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment