Compare commits

...

2 Commits

Author SHA1 Message Date
nicolas.dorier
101094cfd3
bump 2022-12-12 16:10:44 +09:00
nicolas.dorier
cc5d9be957
NBXplorer create default RPC wallet when possible 2022-12-12 15:56:49 +09:00
11 changed files with 144 additions and 39 deletions

View File

@ -27,7 +27,7 @@
<Optimize>true</Optimize>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NBitcoin" Version="7.0.21" />
<PackageReference Include="NBitcoin" Version="7.0.22" />
<PackageReference Include="NBitcoin.Altcoins" Version="3.0.17" />
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" />
</ItemGroup>

View File

@ -11,7 +11,7 @@
<EmbeddedResource Include="Scripts\generate-whale.sql" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NBitcoin.TestFramework" Version="3.0.17" />
<PackageReference Include="NBitcoin.TestFramework" Version="3.0.19" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">

View File

@ -10,6 +10,8 @@ namespace NBXplorer.Tests
{
NBXplorerNetworkProvider _Provider = new NBXplorerNetworkProvider(ChainName.Regtest);
public bool CreateWallet { get; set; } = false;
private void SetEnvironment()
{
//CryptoCode = "AGM";

View File

@ -24,6 +24,9 @@ using NBXplorer.Logging;
using NBXplorer.DerivationStrategy;
using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
using NBitcoin.Altcoins.Elements;
using NBitcoin.Scripting;
namespace NBXplorer.Tests
{
@ -118,6 +121,7 @@ namespace NBXplorer.Tests
var cryptoSettings = new NBXplorerNetworkProvider(ChainName.Regtest).GetFromCryptoCode(CryptoCode);
NodeBuilder = NodeBuilder.Create(nodeDownloadData, Network, _Directory);
NodeBuilder.RPCWalletType = RPCWalletType;
NodeBuilder.CreateWallet = CreateWallet;
if (KeepPreviousData)
NodeBuilder.CleanBeforeStartingNode = false;
Explorer = NodeBuilder.CreateNode();
@ -128,8 +132,6 @@ namespace NBXplorer.Tests
node.CookieAuth = cryptoSettings.SupportCookieAuthentication;
}
NodeBuilder.StartAll();
if (!KeepPreviousData)
Explorer.CreateRPCClient().EnsureGenerate(Network.Consensus.CoinbaseMaturity + 1);
datadir = Path.Combine(_Directory, "explorer");
if (!KeepPreviousData && !LoadedData)
@ -180,7 +182,6 @@ namespace NBXplorer.Tests
keyValues.Add(("trimevents", TrimEvents.ToString()));
keyValues.Add(("mingapsize", "3"));
keyValues.Add(("maxgapsize", "8"));
keyValues.Add(($"{CryptoCode.ToLowerInvariant()}startheight", Explorer.CreateRPCClient().GetBlockCount().ToString()));
keyValues.Add(($"{CryptoCode.ToLowerInvariant()}nodeendpoint", $"{Explorer.Endpoint.Address}:{Explorer.Endpoint.Port}"));
keyValues.Add(("asbcnstr", AzureServiceBusTestConfig.ConnectionString));
keyValues.Add(("asbblockq", AzureServiceBusTestConfig.NewBlockQueue));
@ -411,6 +412,44 @@ namespace NBXplorer.Tests
CopyAll(diSourceSubDir, nextTargetSubDir);
}
}
public void ImportPrivKey(BitcoinExtKey key, string path)
{
ImportPrivKeyAsync(key, path).GetAwaiter().GetResult();
}
public async Task ImportPrivKeyAsync(BitcoinExtKey key, string path)
{
var k = PrivateKeyOf(key, path);
try
{
await RPC.ImportPrivKeyAsync(k).ConfigureAwait(false);
}
catch (RPCException ex) when (ex.RPCCode == RPCErrorCode.RPC_WALLET_ERROR)
{
string[] desc;
if (this.RPC.Capabilities.SupportSegwit)
desc = new[] { $"wpkh({k})", $"sh(wpkh({k}))" };
else
desc = new[] { $"pkh({k})" };
foreach (var d in desc)
{
await RPC.SendCommandAsync(new RPCRequest()
{
Method = "importdescriptors",
ThrowIfRPCError = true,
Params = new[]
{
new JArray(
new JObject()
{
["desc"] = OutputDescriptor.AddChecksum(d),
["timestamp"] = this.RPC.Network.Consensus.CoinbaseMaturity
})
}
}).ConfigureAwait(false);
}
}
}
public BitcoinSecret PrivateKeyOf(BitcoinExtKey key, string path)
{

View File

@ -1656,7 +1656,7 @@ namespace NBXplorer.Tests
Logs.Tester.LogInformation($"Funding tx ({fundingTxId}) has two coins");
Logs.Tester.LogInformation("Let's spend one of the coins");
LockTestCoins(tester.RPC);
tester.RPC.ImportPrivKey(tester.PrivateKeyOf(key, "0/1"));
tester.ImportPrivKey(key, "0/1");
var spending1 = tester.RPC.SendToAddress(new Key().PubKey.Hash.GetAddress(tester.Network), Money.Coins(0.1m));
tester.Notifications.WaitForTransaction(pubkey, spending1);
@ -1673,7 +1673,7 @@ namespace NBXplorer.Tests
Logs.Tester.LogInformation("Let's spend the other coin");
LockTestCoins(tester.RPC);
tester.RPC.ImportPrivKey(tester.PrivateKeyOf(key, "0/0"));
tester.ImportPrivKey(key, "0/0");
var unspentt = tester.RPC.ListUnspent();
var spending2 = tester.RPC.SendToAddress(new Key().PubKey.Hash.GetAddress(tester.Network), Money.Coins(0.1m));
tester.Notifications.WaitForTransaction(pubkey, spending2);
@ -1770,14 +1770,14 @@ namespace NBXplorer.Tests
// Let's spend one of the coins of funding and spend it again
// [funding, spending1, spending2]
LockTestCoins(tester.RPC);
tester.RPC.ImportPrivKey(tester.PrivateKeyOf(key, "0/1"));
tester.ImportPrivKey(key, "0/1");
var coinDestination = tester.Client.GetUnused(pubkey, DerivationFeature.Deposit);
var coinDestinationAddress = coinDestination.ScriptPubKey;
var spending1 = tester.RPC.SendToAddress(coinDestinationAddress, Money.Coins(0.1m));
Logs.Tester.LogInformation($"Spent the coin to 0/1 in spending1({spending1})");
tester.Notifications.WaitForTransaction(pubkey, spending1);
LockTestCoins(tester.RPC, new HashSet<Script>());
tester.RPC.ImportPrivKey(tester.PrivateKeyOf(key, coinDestination.KeyPath.ToString()));
tester.ImportPrivKey(key, coinDestination.KeyPath.ToString());
var spending2 = tester.RPC.SendToAddress(new Key().GetScriptPubKey(ScriptPubKeyType.Legacy), Money.Coins(0.01m));
tester.Notifications.WaitForTransaction(pubkey, spending2);
Logs.Tester.LogInformation($"Spent again the coin in spending2({spending2})");
@ -1797,7 +1797,7 @@ namespace NBXplorer.Tests
// Let's spend the other coin of fundingTx
Thread.Sleep(1000);
LockTestCoins(tester.RPC, new HashSet<Script>());
tester.RPC.ImportPrivKey(tester.PrivateKeyOf(key, "0/0"));
tester.ImportPrivKey(key, "0/0");
var spending3 = tester.RPC.SendToAddress(new Key().PubKey.Hash.GetAddress(tester.Network), Money.Coins(0.1m));
tester.Notifications.WaitForTransaction(pubkey, spending3);
Logs.Tester.LogInformation($"Spent the second coin to 0/0 in spending3({spending3})");
@ -2155,7 +2155,7 @@ namespace NBXplorer.Tests
Logs.Tester.LogInformation("Let's send 0.6BTC from alice 0/1 to bob 0/3");
LockTestCoins(tester.RPC);
tester.RPC.ImportPrivKey(tester.PrivateKeyOf(alice, "0/1"));
tester.ImportPrivKey(alice, "0/1");
id = tester.SendToAddress(tester.AddressOf(bob, "0/3"), Money.Coins(0.6m));
tester.Notifications.WaitForTransaction(bobPubKey, id);
@ -2240,7 +2240,7 @@ namespace NBXplorer.Tests
tester.Client.Track(pubkey);
var addresses = new HashSet<Script>();
tester.RPC.ImportPrivKey(tester.PrivateKeyOf(key, "0/0"));
tester.ImportPrivKey(key, "0/0");
var id = tester.SendToAddress(tester.AddressOf(key, "0/0"), Money.Coins(1.0m));
tester.Notifications.WaitForTransaction(pubkey, id);
addresses.Add(tester.AddressOf(key, "0/0").ScriptPubKey);
@ -2264,7 +2264,7 @@ namespace NBXplorer.Tests
coins = coins - Money.Coins(0.001m);
var path = $"0/{i + 1}";
var destination = tester.AddressOf(key, path);
tester.RPC.ImportPrivKey(tester.PrivateKeyOf(key, path));
tester.ImportPrivKey(key, path);
var txId = tester.SendToAddress(destination, coins);
Logs.Tester.LogInformation($"Sent to {path} in {txId}");
addresses.Add(destination.ScriptPubKey);
@ -2460,7 +2460,7 @@ namespace NBXplorer.Tests
utxo = tester.Client.GetUTXOs(addressSource);
var utxo2 = tester.Client.GetUTXOs(pubkey2);
LockTestCoins(tester.RPC);
tester.RPC.ImportPrivKey(tester.PrivateKeyOf(extkey2, "0/0"));
tester.ImportPrivKey(extkey2, "0/0");
var tx2 = tester.SendToAddress(address, Money.Coins(0.6m));
tester.Notifications.WaitForTransaction(address, tx2);
tester.RPC.EnsureGenerate(1);
@ -2497,7 +2497,7 @@ namespace NBXplorer.Tests
Logs.Tester.LogInformation("Let's send 0.6BTC from 0/0 to 1/0");
LockTestCoins(tester.RPC);
tester.RPC.ImportPrivKey(tester.PrivateKeyOf(key, "0/0"));
tester.ImportPrivKey(key, "0/0");
var tx2 = tester.SendToAddress(tester.AddressOf(key, "1/0"), Money.Coins(0.6m));
tester.Notifications.WaitForTransaction(pubkey, tx2);
@ -2704,7 +2704,7 @@ namespace NBXplorer.Tests
Logs.Tester.LogInformation("Let's send from 0/0 to 0/1");
LockTestCoins(tester.RPC);
tester.RPC.ImportPrivKey(tester.PrivateKeyOf(key, "0/0"));
tester.ImportPrivKey(key, "0/0");
var txId3 = tester.SendToAddress(tester.AddressOf(key, "0/1"), Money.Coins(0.2m));
tester.Notifications.WaitForTransaction(pubkey, txId3);
result = tester.Client.GetTransactions(pubkey);
@ -2735,7 +2735,7 @@ namespace NBXplorer.Tests
Logs.Tester.LogInformation("Send 0.2BTC from the 0/0 to a random address");
LockTestCoins(tester.RPC);
tester.RPC.ImportPrivKey(tester.PrivateKeyOf(key, "0/0"));
tester.ImportPrivKey(key, "0/0");
var spendingTx = tester.SendToAddress(new Key().PubKey.Hash.GetAddress(tester.Network), Money.Coins(0.2m));
tester.Notifications.WaitForTransaction(pubkey, spendingTx);
Logs.Tester.LogInformation("Check we have empty UTXO as unconfirmed");
@ -2971,7 +2971,7 @@ namespace NBXplorer.Tests
Logs.Tester.LogInformation("Let's send 0.5 BTC from 0/1 to 0/3");
LockTestCoins(tester.RPC);
tester.RPC.ImportPrivKey(tester.PrivateKeyOf(key, "0/1"));
tester.ImportPrivKey(key, "0/1");
txId = tester.SendToAddress(tester.AddressOf(key, "0/3"), Money.Coins(0.5m));
tester.Notifications.WaitForTransaction(pubkey, txId);
@ -3996,6 +3996,7 @@ namespace NBXplorer.Tests
{
using (var tester = ServerTester.CreateNoAutoStart(backend))
{
tester.CreateWallet = true;
tester.RPCWalletType = walletType;
tester.Start();
var cashNode = tester.NodeBuilder.CreateNode(true);

View File

@ -294,13 +294,6 @@ namespace NBXplorer.Backends.DBTrie
try
{
blockchainInfo = await _OriginalRPC.GetBlockchainInfoAsyncEx();
if (blockchainInfo != null && _Network.NBitcoinNetwork.ChainName == ChainName.Regtest && !_ChainConfiguration.NoWarmup)
{
if (await _OriginalRPC.WarmupBlockchain(Logs.Explorer))
{
blockchainInfo = await _OriginalRPC.GetBlockchainInfoAsyncEx();
}
}
}
catch (Exception ex)
{
@ -313,12 +306,13 @@ namespace NBXplorer.Backends.DBTrie
}
else
{
blockchainInfo = await Warmup(blockchainInfo);
await ConnectToBitcoinD(token, blockchainInfo);
State = BitcoinDWaiterState.NBXplorerSynching;
}
break;
case BitcoinDWaiterState.CoreSynching:
GetBlockchainInfoResponse blockchainInfo2 = null;
GetBlockchainInfoResponse blockchainInfo2;
try
{
blockchainInfo2 = await _OriginalRPC.GetBlockchainInfoAsyncEx();
@ -331,6 +325,7 @@ namespace NBXplorer.Backends.DBTrie
}
if (!blockchainInfo2.IsSynching(_Network))
{
blockchainInfo2 = await Warmup(blockchainInfo2);
await ConnectToBitcoinD(token, blockchainInfo2);
State = BitcoinDWaiterState.NBXplorerSynching;
}
@ -371,6 +366,18 @@ namespace NBXplorer.Backends.DBTrie
return changed;
}
private async Task<GetBlockchainInfoResponse> Warmup(GetBlockchainInfoResponse blockchainInfo2)
{
await _OriginalRPC.EnsureWalletCreated(Logger);
if (Network.NBitcoinNetwork.ChainName == ChainName.Regtest && !_ChainConfiguration.NoWarmup)
{
if (await _OriginalRPC.WarmupBlockchain(Logger))
blockchainInfo2 = await _OriginalRPC.GetBlockchainInfoAsyncEx();
}
return blockchainInfo2;
}
private Node GetHandshakedNode()
{
return _Node?.State == NodeState.HandShaked ? _Node : null;

View File

@ -12,6 +12,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
@ -241,10 +242,6 @@ namespace NBXplorer.Backends.Postgres
int waitTime = 10;
if (Network.NBitcoinNetwork.ChainName == ChainName.Regtest && !ChainConfiguration.NoWarmup)
{
await RPCClient.WarmupBlockchain(Logger);
}
// Need NetworkInfo for the get status
NetworkInfo = await RPCClient.GetNetworkInfoAsync();
retry:
@ -256,6 +253,12 @@ namespace NBXplorer.Backends.Postgres
waitTime = Math.Min(5_000, waitTime * 2);
goto retry;
}
await RPCClient.EnsureWalletCreated(Logger);
if (Network.NBitcoinNetwork.ChainName == ChainName.Regtest && !ChainConfiguration.NoWarmup)
{
if (await RPCClient.WarmupBlockchain(Logger))
BlockchainInfo = await RPCClient.GetBlockchainInfoAsyncEx();
}
_NodeTip = await RPCClient.GetBlockHeaderAsyncEx(BlockchainInfo.BestBlockHash);
State = BitcoinDWaiterState.NBXplorerSynching;
// Refresh the NetworkInfo that may have become different while it was synching.

View File

@ -39,6 +39,7 @@ namespace NBXplorer.Configuration
app.Option($"--{crypto}rescan", $"Rescan from startheight", CommandOptionType.BoolValue);
app.Option($"--{crypto}rescaniftimebefore", $"Only perform rescan if before timestamp (UTC unix timestamp in seconds) (requires --{crypto}rescan)", CommandOptionType.SingleValue);
app.Option($"--{crypto}rpcuser", $"RPC authentication method 1: The RPC user (default: using cookie auth from default network folder)", CommandOptionType.SingleValue);
app.Option($"--{crypto}rpcdefaultwallet", $"The default RPC wallet used by NBXplorer. RPC wallet features aren't strictly needed, only for NBXplorer wallet created with `importKeysToRPC` (default: empty name)", CommandOptionType.SingleValue);
app.Option($"--{crypto}rpcpassword", $"RPC authentication method 1: The RPC password (default: using cookie auth from default network folder)", CommandOptionType.SingleValue);
app.Option($"--{crypto}rpccookiefile", $"RPC authentication method 2: The RPC cookiefile (default: using cookie auth from default network folder)", CommandOptionType.SingleValue);
app.Option($"--{crypto}rpcauth", $"RPC authentication method 3: user:password or cookiefile=path (default: using cookie auth from default network folder)", CommandOptionType.SingleValue);

View File

@ -32,6 +32,7 @@ namespace NBXplorer.Configuration
{
get; set;
}
public string DefaultWallet { get; set; }
public bool NoTest
{
get;
@ -85,6 +86,8 @@ namespace NBXplorer.Configuration
}
}
}
if (DefaultWallet is not null)
rpcClient = rpcClient.SetWalletContext(DefaultWallet);
return rpcClient;
}
@ -182,6 +185,7 @@ namespace NBXplorer.Configuration
{
User = confArgs.GetOrDefault<string>(prefix + "rpc.user", null),
Password = confArgs.GetOrDefault<string>(prefix + "rpc.password", null),
DefaultWallet = confArgs.GetOrDefault<string>(prefix + "rpc.defaultwallet", null),
CookieFile = confArgs.GetOrDefault<string>(prefix + "rpc.cookiefile", null),
AuthenticationString = confArgs.GetOrDefault<string>(prefix + "rpc.auth", null),
Url = url == null ? null : new Uri(url)

View File

@ -3,7 +3,7 @@
<OutputType>Exe</OutputType>
<TargetFramework Condition="'$(TargetFrameworkOverride)' == ''">net6.0</TargetFramework>
<TargetFramework Condition="'$(TargetFrameworkOverride)' != ''">$(TargetFrameworkOverride)</TargetFramework>
<Version>2.3.52</Version>
<Version>2.3.53</Version>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\NBXplorer.xml</DocumentationFile>
<NoWarn>1701;1702;1705;1591;CS1591</NoWarn>
<LangVersion>10.0</LangVersion>

View File

@ -12,6 +12,8 @@ using System.Threading;
using Microsoft.Extensions.Logging;
using NBXplorer.Backends;
using System.Text.RegularExpressions;
using System.Net.Http;
using System.Net;
namespace NBXplorer
{
@ -79,7 +81,7 @@ namespace NBXplorer
}
public static class RPCClientExtensions
{
{
public static async Task<bool> WarmupBlockchain(this RPCClient rpc, ILogger logger)
{
if (await rpc.GetBlockCountAsync() < rpc.Network.Consensus.CoinbaseMaturity)
@ -122,6 +124,9 @@ namespace NBXplorer
}
public static bool IsSynching(this GetBlockchainInfoResponse blockchainInfo, NBXplorerNetwork network)
{
// When no block has been mined, core think it is synching, but it's just there is no block
if (blockchainInfo.Headers == 0 && network.NBitcoinNetwork.ChainName == ChainName.Regtest)
return false;
if (blockchainInfo.InitialBlockDownload == true)
return true;
if (blockchainInfo.MedianTime.HasValue && network.NBitcoinNetwork.ChainName != ChainName.Regtest)
@ -142,6 +147,49 @@ namespace NBXplorer
return JsonConvert.DeserializeObject<GetBlockchainInfoResponse>(result.ResultString);
}
public static async Task EnsureWalletCreated(this RPCClient client, ILogger logger)
{
var network = client.Network.NetworkSet;
var walletName = client.CredentialString.WalletName ?? "";
try
{
await client.CreateWalletAsync(walletName, new CreateWalletOptions()
{
LoadOnStartup = true,
Blank = client.Network.ChainName != ChainName.Regtest
});
logger.LogInformation($"{network.CryptoCode}: Created RPC wallet \"{walletName}\"");
}
catch (RPCException ex) when (ex.RPCCode == RPCErrorCode.RPC_METHOD_NOT_FOUND || ex.RPCCode == RPCErrorCode.RPC_WALLET_ERROR)
{
// Not supported, or already created? Just ignore.
return;
}
catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized || ex.StatusCode is HttpStatusCode.Forbidden)
{
// Not allowed, which is fine
return;
}
catch (Exception ex)
{
logger.LogWarning(ex, $"{network.CryptoCode}: Failed to create a RPC wallet with unknown error, skipping...");
return;
}
try
{
await client.LoadWalletAsync(walletName, true);
logger.LogInformation($"{network.CryptoCode}: RPC Wallet loaded");
}
catch (RPCException ex) when (ex.RPCCode == RPCErrorCode.RPC_METHOD_NOT_FOUND || ex.RPCCode == RPCErrorCode.RPC_WALLET_ERROR)
{
// Not supported, or already loaded? Just ignore.
}
catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized || ex.StatusCode is HttpStatusCode.Forbidden)
{
// Not allowed, which is fine
}
}
public static async Task<GetNetworkInfoResponse> GetNetworkInfoAsync(this RPCClient client)
{
var result = await client.SendCommandAsync("getnetworkinfo").ConfigureAwait(false);
@ -200,16 +248,16 @@ namespace NBXplorer
{
timestamp = NBitcoin.Utils.UnixTimeToDateTime(rpcResult.Value<long>("time"));
}
var rawTx = client.Network.Consensus.ConsensusFactory.CreateTransaction();
rawTx.ReadWrite(Encoders.Hex.DecodeData(rpcResult.Value<string>("hex")), client.Network);
return new SavedTransaction()
{
BlockHash = blockHash,
BlockHeight = blockHeight,
Timestamp = timestamp,
Transaction = rawTx
};
{
BlockHash = blockHash,
BlockHeight = blockHeight,
Timestamp = timestamp,
Transaction = rawTx
};
}
return null;
}