Skip to content

Commit

Permalink
Merge branch 'master' into SendManualControl2
Browse files Browse the repository at this point in the history
  • Loading branch information
ichthus1604 committed Jun 4, 2024
2 parents de0a470 + 12104f4 commit e20895a
Show file tree
Hide file tree
Showing 80 changed files with 1,114 additions and 1,482 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
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
42 changes: 23 additions & 19 deletions WalletWasabi.Daemon/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ [ 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",
GetTorModeValue("UseTor", PersistentConfig.UseTor, cliArgs)),
Expand Down Expand Up @@ -156,9 +156,9 @@ [ 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 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));
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 @@ -384,8 +384,22 @@ private static TorModeValue GetTorModeValue(string key, object value, string[] c
{
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.");
Expand All @@ -398,7 +412,7 @@ private static TorModeValue GetTorModeValue(string key, object value, string[] c
{
computedValue = TorMode.Disabled;
}
else if (Enum.TryParse(stringValue, out TorMode parsedTorMode))
else if (Enum.TryParse(stringValue, ignoreCase: true, out TorMode parsedTorMode))
{
computedValue = parsedTorMode;
}
Expand All @@ -407,17 +421,7 @@ private static TorModeValue GetTorModeValue(string key, object value, string[] c
throw new ArgumentException($"Could not convert '{value}' to a valid {nameof(TorMode)} value.");
}

if (GetOverrideValue(key, cliArgs, out string? overrideValue, out ValueSource? valueSource))
{
if (!Enum.TryParse(overrideValue, out TorMode parsedOverrideValue))
{
throw new ArgumentException($"Could not convert overridden value '{overrideValue}' to a valid {nameof(TorMode)} value.");
}

return new TorModeValue(computedValue, parsedOverrideValue, valueSource.Value);
}

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

private static bool GetOverrideValue(string key, string[] cliArgs, [NotNullWhen(true)] out string? overrideValue, [NotNullWhen(true)] out ValueSource? valueSource)
Expand Down
22 changes: 4 additions & 18 deletions WalletWasabi.Daemon/Global.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,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 Down Expand Up @@ -158,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 @@ -210,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 @@ -235,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 @@ -451,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 @@ -481,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
40 changes: 30 additions & 10 deletions WalletWasabi.Daemon/PersistentConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,17 @@ public record PersistentConfig : IConfigNg
[JsonPropertyName("RegTestBackendUri")]
public string RegTestBackendUri { get; init; } = "http://localhost:37127/";

[DefaultValue(Constants.BackendUri)]
[JsonPropertyName("MainNetCoordinatorUri")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? MainNetCoordinatorUri { get; init; }
public string MainNetCoordinatorUri { get; init; } = Constants.BackendUri;

[DefaultValue(Constants.TestnetBackendUri)]
[JsonPropertyName("TestNetCoordinatorUri")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? TestNetCoordinatorUri { get; init; }
public string TestNetCoordinatorUri { get; init; } = Constants.TestnetBackendUri;

[DefaultValue("http://localhost:37127/")]
[JsonPropertyName("RegTestCoordinatorUri")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? RegTestCoordinatorUri { get; init; }
public string RegTestCoordinatorUri { get; init; } = "http://localhost:37127/";

/// <remarks>
/// For backward compatibility this was changed to an object.
Expand Down Expand Up @@ -78,15 +78,15 @@ public record PersistentConfig : IConfigNg

[JsonPropertyName("MainNetBitcoinP2pEndPoint")]
[JsonConverter(typeof(MainNetBitcoinP2pEndPointConverterNg))]
public EndPoint MainNetBitcoinP2pEndPoint { get; init; } = new IPEndPoint(IPAddress.Loopback, Constants.DefaultMainNetBitcoinP2pPort);
public EndPoint MainNetBitcoinP2pEndPoint { get; init; } = Constants.DefaultMainNetBitcoinP2PEndPoint;

[JsonPropertyName("TestNetBitcoinP2pEndPoint")]
[JsonConverter(typeof(TestNetBitcoinP2pEndPointConverterNg))]
public EndPoint TestNetBitcoinP2pEndPoint { get; init; } = new IPEndPoint(IPAddress.Loopback, Constants.DefaultTestNetBitcoinP2pPort);
public EndPoint TestNetBitcoinP2pEndPoint { get; init; } = Constants.DefaultTestNetBitcoinP2PEndPoint;

[JsonPropertyName("RegTestBitcoinP2pEndPoint")]
[JsonConverter(typeof(RegTestBitcoinP2pEndPointConverterNg))]
public EndPoint RegTestBitcoinP2pEndPoint { get; init; } = new IPEndPoint(IPAddress.Loopback, Constants.DefaultRegTestBitcoinP2pPort);
public EndPoint RegTestBitcoinP2pEndPoint { get; init; } = Constants.DefaultRegTestBitcoinP2PEndPoint;

[DefaultValue(false)]
[JsonPropertyName("JsonRpcServerEnabled")]
Expand Down Expand Up @@ -120,7 +120,7 @@ public record PersistentConfig : IConfigNg

public bool DeepEquals(PersistentConfig other)
{
bool useTorIsEqual = UseTor.ToString() == other.UseTor.ToString();
bool useTorIsEqual = Config.ObjectToTorMode(UseTor) == Config.ObjectToTorMode(other.UseTor);

return
Network == other.Network &&
Expand Down Expand Up @@ -165,6 +165,26 @@ public EndPoint GetBitcoinP2pEndPoint()
throw new NotSupportedNetworkException(Network);
}

public string GetCoordinatorUri()
{
if (Network == Network.Main)
{
return MainNetCoordinatorUri;
}

if (Network == Network.TestNet)
{
return TestNetCoordinatorUri;
}

if (Network == Network.RegTest)
{
return RegTestCoordinatorUri;
}

throw new NotSupportedNetworkException(Network);
}

public bool MigrateOldDefaultBackendUris([NotNullWhen(true)] out PersistentConfig? newConfig)
{
bool hasChanged = false;
Expand Down
7 changes: 0 additions & 7 deletions WalletWasabi.Documentation/DeveloperFeatures.md

This file was deleted.

Loading

0 comments on commit e20895a

Please sign in to comment.