Skip to content

Commit

Permalink
Merge from master
Browse files Browse the repository at this point in the history
  • Loading branch information
SuperJMN committed Jun 3, 2024
2 parents 3a411a4 + 5bee854 commit d4e7556
Show file tree
Hide file tree
Showing 170 changed files with 2,008 additions and 1,803 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: "Build"
on:
push:
branches: [ "*" ]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v20
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: DeterminateSystems/magic-nix-cache-action@v2
- run: nix build --print-build-logs .#all
- run: echo OK
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2024 zkSNACKs
Copyright (c) 2024 The Wasabi Wallet Developers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
12 changes: 6 additions & 6 deletions WalletWasabi.Backend/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,27 +53,27 @@ public Config(

[JsonProperty(PropertyName = "MainNetBitcoinP2pEndPoint")]
[JsonConverter(typeof(EndPointJsonConverter), Constants.DefaultMainNetBitcoinP2pPort)]
public EndPoint MainNetBitcoinP2pEndPoint { get; internal set; } = new IPEndPoint(IPAddress.Loopback, Constants.DefaultMainNetBitcoinP2pPort);
public EndPoint MainNetBitcoinP2pEndPoint { get; internal set; } = Constants.DefaultMainNetBitcoinP2PEndPoint;

[JsonProperty(PropertyName = "TestNetBitcoinP2pEndPoint")]
[JsonConverter(typeof(EndPointJsonConverter), Constants.DefaultTestNetBitcoinP2pPort)]
public EndPoint TestNetBitcoinP2pEndPoint { get; internal set; } = new IPEndPoint(IPAddress.Loopback, Constants.DefaultTestNetBitcoinP2pPort);
public EndPoint TestNetBitcoinP2pEndPoint { get; internal set; } = Constants.DefaultTestNetBitcoinP2PEndPoint;

[JsonProperty(PropertyName = "RegTestBitcoinP2pEndPoint")]
[JsonConverter(typeof(EndPointJsonConverter), Constants.DefaultRegTestBitcoinP2pPort)]
public EndPoint RegTestBitcoinP2pEndPoint { get; internal set; } = new IPEndPoint(IPAddress.Loopback, Constants.DefaultRegTestBitcoinP2pPort);
public EndPoint RegTestBitcoinP2pEndPoint { get; internal set; } = Constants.DefaultRegTestBitcoinP2PEndPoint;

[JsonProperty(PropertyName = "MainNetBitcoinCoreRpcEndPoint")]
[JsonConverter(typeof(EndPointJsonConverter), Constants.DefaultMainNetBitcoinCoreRpcPort)]
public EndPoint MainNetBitcoinCoreRpcEndPoint { get; internal set; } = new IPEndPoint(IPAddress.Loopback, Constants.DefaultMainNetBitcoinCoreRpcPort);
public EndPoint MainNetBitcoinCoreRpcEndPoint { get; internal set; } = Constants.DefaultMainNetBitcoinCoreRpcEndPoint;

[JsonProperty(PropertyName = "TestNetBitcoinCoreRpcEndPoint")]
[JsonConverter(typeof(EndPointJsonConverter), Constants.DefaultTestNetBitcoinCoreRpcPort)]
public EndPoint TestNetBitcoinCoreRpcEndPoint { get; internal set; } = new IPEndPoint(IPAddress.Loopback, Constants.DefaultTestNetBitcoinCoreRpcPort);
public EndPoint TestNetBitcoinCoreRpcEndPoint { get; internal set; } = Constants.DefaultTestNetBitcoinCoreRpcEndPoint;

[JsonProperty(PropertyName = "RegTestBitcoinCoreRpcEndPoint")]
[JsonConverter(typeof(EndPointJsonConverter), Constants.DefaultRegTestBitcoinCoreRpcPort)]
public EndPoint RegTestBitcoinCoreRpcEndPoint { get; internal set; } = new IPEndPoint(IPAddress.Loopback, Constants.DefaultRegTestBitcoinCoreRpcPort);
public EndPoint RegTestBitcoinCoreRpcEndPoint { get; internal set; } = Constants.DefaultRegTestBitcoinCoreRpcEndPoint;

public EndPoint GetBitcoinP2pEndPoint()
{
Expand Down
2 changes: 1 addition & 1 deletion WalletWasabi.Backend/Controllers/BatchController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public async Task<IActionResult> GetSynchronizeAsync(
var numberOfFilters = Global.Config.Network == Network.Main ? 1000 : 10000;
(Height bestHeight, IEnumerable<FilterModel> filters) = Global.IndexBuilderService.GetFilterLinesExcluding(knownHash, numberOfFilters, out bool found);

var response = new SynchronizeResponse { Filters = Enumerable.Empty<FilterModel>(), BestHeight = bestHeight };
var response = new SynchronizeResponse { Filters = [], BestHeight = bestHeight };

if (!found)
{
Expand Down
51 changes: 40 additions & 11 deletions WalletWasabi.Backend/Controllers/BlockchainController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ public async Task<IActionResult> GetTransactionsAsync([FromQuery, Required] IEnu
/// <summary>
/// Fetches transactions from cache if possible and missing transactions are fetched using RPC.
/// </summary>
/// <exception cref="AggregateException">If RPC client succeeds in getting some transactions but not all.</exception>
private async Task<Transaction[]> FetchTransactionsAsync(uint256[] txIds, CancellationToken cancellationToken)
{
int requestCount = txIds.Length;
Expand Down Expand Up @@ -204,12 +205,20 @@ private async Task<Transaction[]> FetchTransactionsAsync(uint256[] txIds, Cancel
{
// Ask to get missing transactions over RPC.
IEnumerable<Transaction> txs = await RpcClient.GetRawTransactionsAsync(txIdsRetrieve.Keys, cancellationToken).ConfigureAwait(false);

Dictionary<uint256, Transaction> rpcBatch = txs.ToDictionary(x => x.GetHash(), x => x);

foreach (KeyValuePair<uint256, Transaction> kvp in rpcBatch)
{
txIdsRetrieve[kvp.Key].TrySetResult(kvp.Value);
}

// RPC client does not throw if a transaction is missing, so we need to account for this case.
if (rpcBatch.Count < txIdsRetrieve.Count)
{
IReadOnlyList<Exception> exceptions = MarkNotFinishedTasksAsFailed(txIdsRetrieve);
throw new AggregateException(exceptions);
}
}

Transaction[] result = new Transaction[requestCount];
Expand All @@ -227,18 +236,30 @@ private async Task<Transaction[]> FetchTransactionsAsync(uint256[] txIds, Cancel
{
if (txIdsRetrieve.Count > 0)
{
// It's necessary to always set a result to the task completion sources. Otherwise, cache can get corrupted.
Exception ex = new InvalidOperationException("Failed to get the transaction.");
foreach ((uint256 txid, TaskCompletionSource<Transaction> tcs) in txIdsRetrieve)
MarkNotFinishedTasksAsFailed(txIdsRetrieve);
}
}

IReadOnlyList<Exception> MarkNotFinishedTasksAsFailed(Dictionary<uint256, TaskCompletionSource<Transaction>> txIdsRetrieve)
{
List<Exception>? exceptions = null;

// It's necessary to always set a result to the task completion sources. Otherwise, cache can get corrupted.
foreach ((uint256 txid, TaskCompletionSource<Transaction> tcs) in txIdsRetrieve)
{
if (!tcs.Task.IsCompleted)
{
if (!tcs.Task.IsCompleted)
{
// Prefer new cache requests to try again rather than getting the exception. The window is small though.
Cache.Remove(txid);
tcs.SetException(ex);
}
exceptions ??= new();

// Prefer new cache requests to try again rather than getting the exception. The window is small though.
Exception e = new InvalidOperationException($"Failed to get the transaction '{txid}'.");
exceptions.Add(e);
Cache.Remove($"{nameof(GetTransactionsAsync)}#{txid}");
tcs.SetException(e);
}
}

return exceptions ?? [];
}
}

Expand Down Expand Up @@ -489,11 +510,19 @@ private async Task<Dictionary<uint256, UnconfirmedTransactionChainItem>> BuildUn

private async Task<UnconfirmedTransactionChainItem> ComputeUnconfirmedTransactionChainItemAsync(uint256 currentTxId, IEnumerable<uint256> mempoolHashes, CancellationToken cancellationToken)
{
var currentTx = (await FetchTransactionsAsync([currentTxId], cancellationToken).ConfigureAwait(false)).FirstOrDefault() ?? throw new InvalidOperationException("Tx not found");
var currentTx = (await FetchTransactionsAsync([currentTxId], cancellationToken).ConfigureAwait(false)).First();

var txsToFetch = currentTx.Inputs.Select(input => input.PrevOut.Hash).Distinct().ToArray();

var parentTxs = await FetchTransactionsAsync(txsToFetch, cancellationToken).ConfigureAwait(false);
Transaction[] parentTxs;
try
{
parentTxs = await FetchTransactionsAsync(txsToFetch, cancellationToken).ConfigureAwait(false);
}
catch(AggregateException ex)
{
throw new InvalidOperationException($"Some transactions part of the chain were not found: {ex}");
}

// Get unconfirmed parents and children
var unconfirmedParents = parentTxs.Where(x => mempoolHashes.Contains(x.GetHash())).ToHashSet();
Expand Down
63 changes: 54 additions & 9 deletions WalletWasabi.Daemon/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,16 @@ [ nameof(RegTestBackendUri)] = (
GetStringValue("RegTestBackendUri", PersistentConfig.RegTestBackendUri, cliArgs)),
[ nameof(MainNetCoordinatorUri)] = (
"The coordinator server's URL to connect to when the Bitcoin network is main",
GetNullableStringValue("MainNetCoordinatorUri", PersistentConfig.MainNetCoordinatorUri, cliArgs)),
GetStringValue("MainNetCoordinatorUri", PersistentConfig.MainNetCoordinatorUri, cliArgs)),
[ nameof(TestNetCoordinatorUri)] = (
"The coordinator server's URL to connect to when the Bitcoin network is testnet",
GetNullableStringValue("TestNetCoordinatorUri", PersistentConfig.TestNetCoordinatorUri, cliArgs)),
GetStringValue("TestNetCoordinatorUri", PersistentConfig.TestNetCoordinatorUri, cliArgs)),
[ nameof(RegTestCoordinatorUri)] = (
"The coordinator server's URL to connect to when the Bitcoin network is regtest",
GetNullableStringValue("RegTestCoordinatorUri", PersistentConfig.RegTestCoordinatorUri, cliArgs)),
GetStringValue("RegTestCoordinatorUri", PersistentConfig.RegTestCoordinatorUri, cliArgs)),
[ nameof(UseTor)] = (
"All the communications go through the Tor network",
GetBoolValue("UseTor", PersistentConfig.UseTor, cliArgs)),
GetTorModeValue("UseTor", PersistentConfig.UseTor, cliArgs)),
[ nameof(TorFolder)] = (
"Folder where Tor binary is located",
GetNullableStringValue("TorFolder", null, cliArgs)),
Expand Down Expand Up @@ -156,10 +156,10 @@ [ nameof(CoordinatorIdentifier)] = (
public string MainNetBackendUri => GetEffectiveValue<StringValue, string>(nameof(MainNetBackendUri));
public string TestNetBackendUri => GetEffectiveValue<StringValue, string>(nameof(TestNetBackendUri));
public string RegTestBackendUri => GetEffectiveValue<StringValue, string>(nameof(RegTestBackendUri));
public string? MainNetCoordinatorUri => GetEffectiveValue<NullableStringValue, string?>(nameof(MainNetCoordinatorUri));
public string? TestNetCoordinatorUri => GetEffectiveValue<NullableStringValue, string?>(nameof(TestNetCoordinatorUri));
public string? RegTestCoordinatorUri => GetEffectiveValue<NullableStringValue, string?>(nameof(RegTestCoordinatorUri));
public bool UseTor => GetEffectiveValue<BoolValue, bool>(nameof(UseTor)) && Network != Network.RegTest;
public string MainNetCoordinatorUri => GetEffectiveValue<StringValue, string>(nameof(MainNetCoordinatorUri));
public string TestNetCoordinatorUri => GetEffectiveValue<StringValue, string>(nameof(TestNetCoordinatorUri));
public string RegTestCoordinatorUri => GetEffectiveValue<StringValue, string>(nameof(RegTestCoordinatorUri));
public TorMode UseTor => Network == Network.RegTest ? TorMode.Disabled : GetEffectiveValue<TorModeValue, TorMode>(nameof(UseTor));
public string? TorFolder => GetEffectiveValue<NullableStringValue, string?>(nameof(TorFolder));
public int TorSocksPort => GetEffectiveValue<IntValue, int>(nameof(TorSocksPort));
public int TorControlPort => GetEffectiveValue<IntValue, int>(nameof(TorControlPort));
Expand Down Expand Up @@ -249,7 +249,7 @@ public Uri GetCoordinatorUri()
_ => throw new NotSupportedNetworkException(Network)
};

return result is null ? GetBackendUri() : new Uri(result);
return new Uri(result);
}

public IEnumerable<(string ParameterName, string Hint)> GetConfigOptionsMetadata() =>
Expand Down Expand Up @@ -380,6 +380,50 @@ private static LogModeArrayValue GetLogModeArrayValue(string key, LogMode[] arra
return new LogModeArrayValue(arrayValues, arrayValues, ValueSource.Disk);
}

private static TorModeValue GetTorModeValue(string key, object value, string[] cliArgs)
{
TorMode computedValue;

computedValue = ObjectToTorMode(value);

if (GetOverrideValue(key, cliArgs, out string? overrideValue, out ValueSource? valueSource))
{
TorMode parsedOverrideValue = ObjectToTorMode(overrideValue);
return new TorModeValue(computedValue, parsedOverrideValue, valueSource.Value);
}

return new TorModeValue(computedValue, computedValue, ValueSource.Disk);
}

public static TorMode ObjectToTorMode(object value)
{
string? stringValue = value.ToString();

TorMode computedValue;
if (stringValue is null)
{
throw new ArgumentException($"Could not convert '{value}' to a string value.");
}
else if (stringValue.Equals("true", StringComparison.OrdinalIgnoreCase))
{
computedValue = TorMode.Enabled;
}
else if (stringValue.Equals("false", StringComparison.OrdinalIgnoreCase))
{
computedValue = TorMode.Disabled;
}
else if (Enum.TryParse(stringValue, ignoreCase: true, out TorMode parsedTorMode))
{
computedValue = parsedTorMode;
}
else
{
throw new ArgumentException($"Could not convert '{value}' to a valid {nameof(TorMode)} value.");
}

return computedValue;
}

private static bool GetOverrideValue(string key, string[] cliArgs, [NotNullWhen(true)] out string? overrideValue, [NotNullWhen(true)] out ValueSource? valueSource)
{
// CLI arguments have higher precedence than environment variables.
Expand Down Expand Up @@ -471,6 +515,7 @@ private record StringValue(string Value, string EffectiveValue, ValueSource Valu
private record NullableStringValue(string? Value, string? EffectiveValue, ValueSource ValueSource) : ITypedValue<string?>;
private record StringArrayValue(string[] Value, string[] EffectiveValue, ValueSource ValueSource) : ITypedValue<string[]>;
private record LogModeArrayValue(LogMode[] Value, LogMode[] EffectiveValue, ValueSource ValueSource) : ITypedValue<LogMode[]>;
private record TorModeValue(TorMode Value, TorMode EffectiveValue, ValueSource ValueSource) : ITypedValue<TorMode>;
private record NetworkValue(Network Value, Network EffectiveValue, ValueSource ValueSource) : ITypedValue<Network>;
private record MoneyValue(Money Value, Money EffectiveValue, ValueSource ValueSource) : ITypedValue<Money>;
private record EndPointValue(EndPoint Value, EndPoint EffectiveValue, ValueSource ValueSource) : ITypedValue<EndPoint>;
Expand Down
42 changes: 18 additions & 24 deletions WalletWasabi.Daemon/Global.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
using WalletWasabi.WebClients.BuyAnything;
using WalletWasabi.WebClients.ShopWare;
using WalletWasabi.Wallets.FilterProcessor;
using WalletWasabi.Models;

namespace WalletWasabi.Daemon;

Expand All @@ -54,7 +55,8 @@ public Global(string dataDir, string configFilePath, Config config)
TorSettings = new TorSettings(
DataDir,
distributionFolderPath: EnvironmentHelpers.GetFullBaseDirectory(),
Config.TerminateTorOnExit,
terminateOnExit: Config.TerminateTorOnExit,
torMode: Config.UseTor,
socksPort: config.TorSocksPort,
controlPort: config.TorControlPort,
torFolder: config.TorFolder,
Expand All @@ -75,17 +77,15 @@ public Global(string dataDir, string configFilePath, Config config)
HttpClientFactory = BuildHttpClientFactory(() => Config.GetBackendUri());
CoordinatorHttpClientFactory = BuildHttpClientFactory(() => Config.GetCoordinatorUri());

HostedServices.Register<UpdateManager>(() => new UpdateManager(TimeSpan.FromDays(1), DataDir, Config.DownloadNewVersion, HttpClientFactory.NewHttpClient(Mode.DefaultCircuit, maximumRedirects: 10), HttpClientFactory.SharedWasabiClient), "Update Manager");
UpdateManager = HostedServices.Get<UpdateManager>();

TimeSpan requestInterval = Network == Network.RegTest ? TimeSpan.FromSeconds(5) : TimeSpan.FromSeconds(30);
int maxFiltersToSync = Network == Network.Main ? 1000 : 10000; // On testnet, filters are empty, so it's faster to query them together

HostedServices.Register<WasabiSynchronizer>(() => new WasabiSynchronizer(requestInterval, maxFiltersToSync, BitcoinStore, HttpClientFactory), "Wasabi Synchronizer");
WasabiSynchronizer wasabiSynchronizer = HostedServices.Get<WasabiSynchronizer>();

HostedServices.Register<UpdateChecker>(() => new UpdateChecker(TimeSpan.FromHours(1), wasabiSynchronizer), "Software Update Checker");
UpdateChecker updateChecker = HostedServices.Get<UpdateChecker>();

LegalChecker = new(DataDir, updateChecker);
UpdateManager = new(DataDir, Config.DownloadNewVersion, HttpClientFactory.NewHttpClient(Mode.DefaultCircuit, maximumRedirects: 10), updateChecker);
TorStatusChecker = new TorStatusChecker(TimeSpan.FromHours(6), HttpClientFactory.NewHttpClient(Mode.DefaultCircuit), new XmlIssueListParser());
RoundStateUpdaterCircuit = new PersonCircuit();

Expand All @@ -102,7 +102,7 @@ public Global(string dataDir, string configFilePath, Config config)
var p2p = new P2pNetwork(
Network,
Config.GetBitcoinP2pEndPoint(),
Config.UseTor ? TorSettings.SocksEndpoint : null,
Config.UseTor != TorMode.Disabled ? TorSettings.SocksEndpoint : null,
Path.Combine(DataDir, "BitcoinP2pNetwork"),
BitcoinStore);
if (!Config.BlockOnlyMode)
Expand Down Expand Up @@ -156,7 +156,6 @@ public Global(string dataDir, string configFilePath, Config config)

public WasabiHttpClientFactory CoordinatorHttpClientFactory { get; }

public LegalChecker LegalChecker { get; private set; }
public string ConfigFilePath { get; }
public Config Config { get; }
public WalletManager WalletManager { get; }
Expand Down Expand Up @@ -185,8 +184,9 @@ public Global(string dataDir, string configFilePath, Config config)

private WasabiHttpClientFactory BuildHttpClientFactory(Func<Uri> backendUriGetter) =>
new(
Config.UseTor ? TorSettings.SocksEndpoint : null,
backendUriGetter);
Config.UseTor != TorMode.Disabled ? TorSettings.SocksEndpoint : null,
backendUriGetter,
torControlAvailable: Config.UseTor == TorMode.Enabled);

public async Task InitializeNoWalletAsync(bool initializeSleepInhibitor, TerminateService terminateService, CancellationToken cancellationToken)
{
Expand All @@ -207,8 +207,6 @@ public async Task InitializeNoWalletAsync(bool initializeSleepInhibitor, Termina
{
var bitcoinStoreInitTask = BitcoinStore.InitializeAsync(cancel);

await LegalChecker.InitializeAsync().ConfigureAwait(false);

cancel.ThrowIfCancellationRequested();

await StartTorProcessManagerAsync(cancel).ConfigureAwait(false);
Expand All @@ -232,7 +230,7 @@ public async Task InitializeNoWalletAsync(bool initializeSleepInhibitor, Termina

await StartLocalBitcoinNodeAsync(cancel).ConfigureAwait(false);

await BlockDownloadService.StartAsync(cancel).ConfigureAwait(false);
await BlockDownloadService.StartAsync(cancel).ConfigureAwait(false);

RegisterCoinJoinComponents();

Expand Down Expand Up @@ -311,7 +309,7 @@ private async Task StartRpcServerAsync(TerminateService terminateService, Cancel

private async Task StartTorProcessManagerAsync(CancellationToken cancellationToken)
{
if (Config.UseTor && Network != Network.RegTest)
if (Config.UseTor != TorMode.Disabled)
{
TorManager = new TorProcessManager(TorSettings);
await TorManager.StartAsync(attempts: 3, cancellationToken).ConfigureAwait(false);
Expand All @@ -333,7 +331,12 @@ private async Task StartTorProcessManagerAsync(CancellationToken cancellationTok
}
}

HostedServices.Register<TorMonitor>(() => new TorMonitor(period: TimeSpan.FromMinutes(1), torProcessManager: TorManager, httpClientFactory: HttpClientFactory), nameof(TorMonitor));
// Do not monitor Tor when Tor is an already running service.
if (TorSettings.TorMode == TorMode.Enabled)
{
HostedServices.Register<TorMonitor>(() => new TorMonitor(period: TimeSpan.FromMinutes(1), torProcessManager: TorManager, httpClientFactory: HttpClientFactory), nameof(TorMonitor));
}

HostedServices.Register<TorStatusChecker>(() => TorStatusChecker, "Tor Network Checker");
}
}
Expand Down Expand Up @@ -443,9 +446,6 @@ public async Task DisposeAsync()
Logger.LogError($"Error during {nameof(WalletManager.RemoveAndStopAllAsync)}: {ex}");
}

UpdateManager.Dispose();
Logger.LogInfo($"{nameof(UpdateManager)} is stopped.");

CoinPrison.Dispose();

if (RpcServer is { } rpcServer)
Expand Down Expand Up @@ -473,12 +473,6 @@ public async Task DisposeAsync()
Logger.LogInfo($"{nameof(CoinJoinProcessor)} is disposed.");
}

if (LegalChecker is { } legalChecker)
{
legalChecker.Dispose();
Logger.LogInfo($"Disposed {nameof(LegalChecker)}.");
}

if (HostedServices is { } backgroundServices)
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(21));
Expand Down
Loading

0 comments on commit d4e7556

Please sign in to comment.