Commit 602c13e6 authored by Jeremy Bokobza's avatar Jeremy Bokobza

Added State machine to save the current state of the tumbling session

parent 10ffe1bf
...@@ -43,7 +43,7 @@ namespace Breeze.TumbleBit.Controllers ...@@ -43,7 +43,7 @@ namespace Breeze.TumbleBit.Controllers
} }
catch (Exception e) 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 ...@@ -68,7 +68,7 @@ namespace Breeze.TumbleBit.Controllers
} }
catch (Exception e) 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;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NBitcoin; using NBitcoin;
using NTumbleBit.ClassicTumbler; using NTumbleBit.ClassicTumbler;
...@@ -27,5 +25,15 @@ namespace Breeze.TumbleBit.Client ...@@ -27,5 +25,15 @@ namespace Breeze.TumbleBit.Client
/// <param name="height">The height of the block in the blockchain.</param> /// <param name="height">The height of the block in the blockchain.</param>
/// <param name="block">The block.</param> /// <param name="block">The block.</param>
void ProcessBlock(int height, Block block); 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 NBitcoin;
using NTumbleBit.ClassicTumbler; using NTumbleBit.ClassicTumbler;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Breeze.TumbleBit.Models namespace Breeze.TumbleBit.Models
{ {
......
using NBitcoin; using NBitcoin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Breeze.TumbleBit.Models namespace Breeze.TumbleBit.Models
{ {
......
using System; using System;
using System.Collections.Generic;
using Breeze.TumbleBit.Client; using Breeze.TumbleBit.Client;
using Breeze.TumbleBit.Controllers; using Breeze.TumbleBit.Controllers;
using Stratis.Bitcoin.Builder.Feature; using Stratis.Bitcoin.Builder.Feature;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Stratis.Bitcoin.Builder; using Stratis.Bitcoin.Builder;
using Stratis.Bitcoin.Logging; using Newtonsoft.Json;
using Microsoft.Extensions.Logging; using Newtonsoft.Json.Serialization;
using Serilog; using Stratis.Bitcoin.Wallet.JsonConverters;
namespace Breeze.TumbleBit namespace Breeze.TumbleBit
{ {
public class TumbleBitFeature : FullNodeFeature public class TumbleBitFeature : FullNodeFeature
{ {
public TumbleBitFeature()
{
}
public override void Start() public override void Start()
{ {
} }
...@@ -35,6 +32,13 @@ namespace Breeze.TumbleBit ...@@ -35,6 +32,13 @@ namespace Breeze.TumbleBit
.AddFeature<TumbleBitFeature>() .AddFeature<TumbleBitFeature>()
.FeatureServices(services => .FeatureServices(services =>
{ {
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new List<JsonConverter> { new NetworkConverter() }
};
services.AddSingleton<ITumbleBitManager, TumbleBitManager> (); services.AddSingleton<ITumbleBitManager, TumbleBitManager> ();
services.AddSingleton<TumbleBitController>(); services.AddSingleton<TumbleBitController>();
}); });
......
...@@ -4,7 +4,6 @@ using Microsoft.Extensions.Logging; ...@@ -4,7 +4,6 @@ using Microsoft.Extensions.Logging;
using NBitcoin; using NBitcoin;
using NTumbleBit.ClassicTumbler; using NTumbleBit.ClassicTumbler;
using Stratis.Bitcoin; using Stratis.Bitcoin;
using Stratis.Bitcoin.Logging;
using Stratis.Bitcoin.Wallet; using Stratis.Bitcoin.Wallet;
namespace Breeze.TumbleBit.Client namespace Breeze.TumbleBit.Client
...@@ -16,55 +15,112 @@ namespace Breeze.TumbleBit.Client ...@@ -16,55 +15,112 @@ namespace Breeze.TumbleBit.Client
public class TumbleBitManager : ITumbleBitManager public class TumbleBitManager : ITumbleBitManager
{ {
private ITumblerService tumblerService; private ITumblerService tumblerService;
private readonly IWalletManager walletManager;
private IWalletManager walletManager;
private readonly ILogger logger; private readonly ILogger logger;
private readonly Signals signals; private readonly Signals signals;
private readonly ConcurrentChain chain; private readonly ConcurrentChain chain;
private readonly Network network;
private TumblingState tumblingState;
private IDisposable blockReceiver;
private ClassicTumblerParameters TumblerParameters { get; set; } private ClassicTumblerParameters TumblerParameters { get; set; }
public TumbleBitManager(ILoggerFactory loggerFactory, IWalletManager walletManager, ConcurrentChain chain, Network network, Signals signals) public TumbleBitManager(ILoggerFactory loggerFactory, IWalletManager walletManager, ConcurrentChain chain, Network network, Signals signals)
{ {
this.walletManager = walletManager; this.walletManager = walletManager;
this.chain = chain; this.chain = chain;
this.signals = signals; this.signals = signals;
this.network = network;
this.logger = loggerFactory.CreateLogger(this.GetType().FullName); this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
// load the persisted tumbling state
this.tumblingState = TumblingState.LoadState();
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<ClassicTumblerParameters> ConnectToTumblerAsync(Uri serverAddress) public async Task<ClassicTumblerParameters> ConnectToTumblerAsync(Uri serverAddress)
{ {
this.tumblerService = new TumblerService(serverAddress); this.tumblerService = new TumblerService(serverAddress);
this.TumblerParameters = await this.tumblerService.GetClassicTumblerParametersAsync(); 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; return this.TumblerParameters;
} }
/// <inheritdoc /> /// <inheritdoc />
public Task TumbleAsync(string destinationWalletName) public Task TumbleAsync(string destinationWalletName)
{ {
// make sure the tumbler service is initialized
if (this.TumblerParameters == null || this.tumblerService == null) if (this.TumblerParameters == null || this.tumblerService == null)
{ {
throw new Exception("Please connect to the tumbler first."); 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); Wallet destinationWallet = this.walletManager.GetWallet(destinationWalletName);
if (destinationWallet == null) if (destinationWallet == null)
{ {
throw new Exception($"Destination not found. Have you created a wallet with name {destinationWalletName}?"); throw new Exception($"Destination not found. Have you created a wallet with name {destinationWalletName}?");
} }
// subscribe to receiving blocks and transactions // update the state and save
this.signals.Blocks.Subscribe(new BlockObserver(this.chain, this)); 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; 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 /> /// <inheritdoc />
public void ProcessBlock(int height, Block block) public void ProcessBlock(int height, Block block)
{ {
// TODO start the state machine // TODO start the state machine
this.logger.LogDebug($"Receive block with height {height}"); 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