Improve block parsing
MODIFY
WalletWasabi.Tests/UnitTests/BitcoinCore/IndexBuilderServiceTests.cs
WalletWasabi.Tests/UnitTests/BitcoinCore/IndexBuilderServiceTests.cs
@@ -139,13 +139,13 @@ namespace WalletWasabi.Tests.UnitTests.BitcoinCore
var prevTxOutputs = block.Transactions.SelectMany(x => x.Inputs.Where(y => y.PrevOutput is { }).Select(y => y.PrevOutput));
var allOutputs = txOutputs.Concat(prevTxOutputs);
- var indexableOutputs = allOutputs.Where(x => x.PubkeyType is RpcPubkeyType.TxWitnessV0Keyhash or RpcPubkeyType.TxWitnessV1Taproot);
+ var indexableOutputs = allOutputs.Where(x => x?.PubkeyType is RpcPubkeyType.TxWitnessV0Keyhash or RpcPubkeyType.TxWitnessV1Taproot);
var nonIndexableOutputs = allOutputs.Except(indexableOutputs);
static byte[] ComputeKey(uint256 blockId) => blockId.ToBytes()[0..16];
- Assert.All(indexableOutputs, x => Assert.True(filter.Match(x.ScriptPubKey.ToCompressedBytes(), ComputeKey(block.Hash))));
- Assert.All(nonIndexableOutputs, x => Assert.False(filter.Match(x.ScriptPubKey.ToCompressedBytes(), ComputeKey(block.Hash))));
+ Assert.All(indexableOutputs, x => Assert.True(filter.Match(x?.ScriptPubKey.ToCompressedBytes(), ComputeKey(block.Hash))));
+ Assert.All(nonIndexableOutputs, x => Assert.False(filter.Match(x?.ScriptPubKey.ToCompressedBytes(), ComputeKey(block.Hash))));
}
private IEnumerable<VerboseBlockInfo> GenerateBlockchain() =>

MODIFY
WalletWasabi.Tests/UnitTests/BitcoinCore/RpcBasedTests.cs
WalletWasabi.Tests/UnitTests/BitcoinCore/RpcBasedTests.cs
@@ -190,21 +190,23 @@ namespace WalletWasabi.Tests.UnitTests.BitcoinCore
var walletName = "wallet.dat";
await rpc.CreateWalletAsync(walletName);
- var key = new Key();
- var blockId = await rpc.GenerateToAddressAsync(1, key.PubKey.WitHash.GetAddress(network));
+ using var k1 = new Key();
+ var blockId = await rpc.GenerateToAddressAsync(1, k1.PubKey.WitHash.GetAddress(network));
var block = await rpc.GetBlockAsync(blockId[0]);
var coinBaseTx = block.Transactions[0];
var tx = Transaction.Create(network);
tx.Inputs.Add(coinBaseTx, 0);
- tx.Outputs.Add(Money.Coins(49.9999m), new Key().PubKey.WitHash.GetAddress(network));
- tx.Sign(key.GetBitcoinSecret(network), coinBaseTx.Outputs.AsCoins().First());
+ using var k2 = new Key();
+ tx.Outputs.Add(Money.Coins(49.9999m), k2.PubKey.WitHash.GetAddress(network));
+ tx.Sign(k1.GetBitcoinSecret(network), coinBaseTx.Outputs.AsCoins().First());
var valid = tx.Check();
var doubleSpend = Transaction.Create(network);
doubleSpend.Inputs.Add(coinBaseTx, 0);
- doubleSpend.Outputs.Add(Money.Coins(49.998m), new Key().PubKey.WitHash.GetAddress(network));
- doubleSpend.Sign(key.GetBitcoinSecret(network), coinBaseTx.Outputs.AsCoins().First());
+ using var k3 = new Key();
+ doubleSpend.Outputs.Add(Money.Coins(49.998m), k3.PubKey.WitHash.GetAddress(network));
+ doubleSpend.Sign(k1.GetBitcoinSecret(network), coinBaseTx.Outputs.AsCoins().First());
valid = doubleSpend.Check();
await rpc.GenerateAsync(101);
@@ -252,10 +254,14 @@ namespace WalletWasabi.Tests.UnitTests.BitcoinCore
Assert.Equal(RpcPubkeyType.TxPubkeyhash, blockInfo.Transactions.ElementAt(0).Outputs.ElementAt(0).PubkeyType);
Assert.Equal(RpcPubkeyType.TxNullData, blockInfo.Transactions.ElementAt(0).Outputs.ElementAt(1).PubkeyType);
- Assert.Equal(Money.Coins(50), blockInfo.Transactions.ElementAt(1).Inputs.ElementAt(0).PrevOutput.Value);
+ var in0 = blockInfo.Transactions.ElementAt(1).Inputs.ElementAt(0);
+ Assert.False(in0.IsCoinbase);
+ var prevOut0 = in0.PrevOutput;
+ Assert.Equal(Money.Coins(50), prevOut0?.Value);
+ Assert.Equal(RpcPubkeyType.TxPubkeyhash, prevOut0?.PubkeyType);
+
Assert.Equal(Money.Coins((decimal)48.99995500), blockInfo.Transactions.ElementAt(1).Outputs.ElementAt(0).Value);
Assert.Equal(Money.Coins((decimal)1.00000000), blockInfo.Transactions.ElementAt(1).Outputs.ElementAt(1).Value);
- Assert.Equal(RpcPubkeyType.TxPubkeyhash, blockInfo.Transactions.ElementAt(1).Inputs.ElementAt(0).PrevOutput.PubkeyType);
Assert.Equal(RpcPubkeyType.TxPubkeyhash, blockInfo.Transactions.ElementAt(1).Outputs.ElementAt(0).PubkeyType);
Assert.Equal(RpcPubkeyType.TxPubkeyhash, blockInfo.Transactions.ElementAt(1).Outputs.ElementAt(1).PubkeyType);
}

ADD
WalletWasabi/BitcoinCore/Rpc/Models/TransactionBlockInfo.cs
+using NBitcoin;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WalletWasabi.BitcoinCore.Rpc.Models
+{
+ public class TransactionBlockInfo
+ {
+ public TransactionBlockInfo(uint256 blockHash, DateTimeOffset? blockTime, uint? blockIndex)
+ {
+ BlockHash = blockHash;
+ BlockTime = blockTime;
+ BlockIndex = blockIndex;
+ }
+
+ public uint256 BlockHash { get; }
+ public DateTimeOffset? BlockTime { get; }
+ public uint? BlockIndex { get; }
+ }
+}

MODIFY
WalletWasabi/BitcoinCore/Rpc/Models/VerboseInputInfo.cs
WalletWasabi/BitcoinCore/Rpc/Models/VerboseInputInfo.cs
@@ -5,20 +5,27 @@ namespace WalletWasabi.BitcoinCore.Rpc.Models
public class VerboseInputInfo
{
public VerboseInputInfo(string coinbase)
+ : this(null, null, coinbase)
{
- Coinbase = coinbase;
}
public VerboseInputInfo(OutPoint outPoint, VerboseOutputInfo prevOutput)
+ : this(outPoint, prevOutput, null)
+ {
+ }
+
+ private VerboseInputInfo(OutPoint? outPoint, VerboseOutputInfo? prevOutput, string? coinbase)
{
OutPoint = outPoint;
PrevOutput = prevOutput;
+ Coinbase = coinbase;
}
- public OutPoint OutPoint { get; }
+ public OutPoint? OutPoint { get; }
- public VerboseOutputInfo PrevOutput { get; }
+ public VerboseOutputInfo? PrevOutput { get; }
- public string Coinbase { get; }
+ public string? Coinbase { get; }
+ public bool IsCoinbase => Coinbase is not null;
}
}

MODIFY
WalletWasabi/BitcoinCore/Rpc/Models/VerboseOutputInfo.cs
WalletWasabi/BitcoinCore/Rpc/Models/VerboseOutputInfo.cs
@@ -4,7 +4,7 @@ namespace WalletWasabi.BitcoinCore.Rpc.Models
{
public class VerboseOutputInfo
{
- public VerboseOutputInfo(Money value, Script scriptPubKey, string pubkeyType)
+ public VerboseOutputInfo(Money value, Script scriptPubKey, string? pubkeyType)
{
Value = value;
ScriptPubKey = scriptPubKey;

MODIFY
WalletWasabi/BitcoinCore/Rpc/Models/VerboseTransactionInfo.cs
WalletWasabi/BitcoinCore/Rpc/Models/VerboseTransactionInfo.cs
using System.Collections.Generic;
+using System.Linq;
using NBitcoin;
namespace WalletWasabi.BitcoinCore.Rpc.Models
{
public class VerboseTransactionInfo
{
- public VerboseTransactionInfo(uint256 id, IEnumerable<VerboseInputInfo> inputs, IEnumerable<VerboseOutputInfo> outputs)
+ public VerboseTransactionInfo(TransactionBlockInfo blockInfo, uint256 id, IEnumerable<VerboseInputInfo> inputs, IEnumerable<VerboseOutputInfo> outputs)
{
Id = id;
+ BlockInfo = blockInfo;
Inputs = inputs;
Outputs = outputs;
}
public uint256 Id { get; }
-
+ public TransactionBlockInfo BlockInfo { get; }
public IEnumerable<VerboseInputInfo> Inputs { get; }
public IEnumerable<VerboseOutputInfo> Outputs { get; }
+
+ public bool IsCoinbase => Inputs.Any(x => x.IsCoinbase);
+
+ /// <summary>
+ /// Note it can be negative if the transaction is coinbase.
+ /// </summary>
+ public Money NetworkFee => Inputs.Sum(x => x.PrevOutput?.Value ?? Money.Zero) - Outputs.Sum(x => x.Value);
}
}

MODIFY
WalletWasabi/BitcoinCore/Rpc/RpcParser.cs
WalletWasabi/BitcoinCore/Rpc/RpcParser.cs
using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
using NBitcoin;
-using Newtonsoft.Json.Linq;
using WalletWasabi.BitcoinCore.Rpc.Models;
namespace WalletWasabi.BitcoinCore.Rpc
{
public static class RpcParser
{
- public static RpcPubkeyType ConvertPubkeyType(string pubKeyType)
+ public static RpcPubkeyType ConvertPubkeyType(string? pubKeyType)
{
return pubKeyType switch
{
@@ -27,52 +28,60 @@ namespace WalletWasabi.BitcoinCore.Rpc
public static VerboseBlockInfo ParseVerboseBlockResponse(string getBlockResponse)
{
- var blockInfoJson = JObject.Parse(getBlockResponse);
- var previousBlockHash = blockInfoJson.Value<string>("previousblockhash");
+ var parsed = JsonDocument.Parse(getBlockResponse).RootElement;
+ if (!parsed.TryGetProperty("result", out JsonElement blockInfoJson))
+ {
+ blockInfoJson = parsed;
+ }
+
+ var previousBlockHash = blockInfoJson.GetProperty("previousblockhash").GetString();
var transaction = new List<VerboseTransactionInfo>();
var blockInfo = new VerboseBlockInfo(
- hash: uint256.Parse(blockInfoJson.Value<string>("hash")),
+ hash: uint256.Parse(blockInfoJson.GetProperty("hash").GetString()),
prevBlockHash: previousBlockHash is { } ? uint256.Parse(previousBlockHash) : uint256.Zero,
- confirmations: blockInfoJson.Value<ulong>("confirmations"),
- height: blockInfoJson.Value<ulong>("height"),
- blockTime: Utils.UnixTimeToDateTime(blockInfoJson.Value<uint>("time")),
+ confirmations: blockInfoJson.GetProperty("confirmations").GetUInt64(),
+ height: blockInfoJson.GetProperty("height").GetUInt64(),
+ blockTime: Utils.UnixTimeToDateTime(blockInfoJson.GetProperty("time").GetUInt32()),
transactions: transaction
);
- foreach (var txJson in blockInfoJson["tx"])
+ var array = blockInfoJson.GetProperty("tx").EnumerateArray().ToArray();
+ for (uint i = 0; i < array.Length; i++)
{
+ var txJson = array[i];
var inputs = new List<VerboseInputInfo>();
var outputs = new List<VerboseOutputInfo>();
- var tx = new VerboseTransactionInfo(uint256.Parse(txJson.Value<string>("txid")), inputs, outputs);
+ var txBlockInfo = new TransactionBlockInfo(blockInfo.Hash, blockInfo.BlockTime, i);
+ var tx = new VerboseTransactionInfo(txBlockInfo, uint256.Parse(txJson.GetProperty("txid").GetString()), inputs, outputs);
- foreach (var txinJson in txJson["vin"])
+ foreach (var txinJson in txJson.GetProperty("vin").EnumerateArray())
{
VerboseInputInfo input;
- if (txinJson["coinbase"] is { })
+ if (txinJson.TryGetProperty("coinbase", out JsonElement cb))
{
- input = new VerboseInputInfo(txinJson["coinbase"].Value<string>());
+ input = new VerboseInputInfo(cb.GetString() ?? "");
}
else
{
input = new VerboseInputInfo(
- outPoint: new OutPoint(uint256.Parse(txinJson.Value<string>("txid")), txinJson.Value<uint>("vout")),
+ outPoint: new OutPoint(uint256.Parse(txinJson.GetProperty("txid").GetString()), txinJson.GetProperty("vout").GetUInt32()),
prevOutput: new VerboseOutputInfo(
- value: Money.Coins(txinJson["prevout"].Value<decimal>("value")),
- scriptPubKey: Script.FromHex(txinJson["prevout"]["scriptPubKey"].Value<string>("hex")),
- pubkeyType: txinJson["prevout"]["scriptPubKey"].Value<string>("type"))
+ value: Money.Coins(txinJson.GetProperty("prevout").GetProperty("value").GetDecimal()),
+ scriptPubKey: Script.FromHex(txinJson.GetProperty("prevout").GetProperty("scriptPubKey").GetProperty("hex").GetString()),
+ pubkeyType: txinJson.GetProperty("prevout").GetProperty("scriptPubKey").GetProperty("type").GetString())
);
}
inputs.Add(input);
}
- foreach (var txoutJson in txJson["vout"])
+ foreach (var txoutJson in txJson.GetProperty("vout").EnumerateArray())
{
var output = new VerboseOutputInfo(
- value: Money.Coins(txoutJson.Value<decimal>("value")),
- scriptPubKey: Script.FromHex(txoutJson["scriptPubKey"].Value<string>("hex")),
- pubkeyType: txoutJson["scriptPubKey"].Value<string>("type")
+ value: Money.Coins(txoutJson.GetProperty("value").GetDecimal()),
+ scriptPubKey: Script.FromHex(txoutJson.GetProperty("scriptPubKey").GetProperty("hex").GetString()),
+ pubkeyType: txoutJson.GetProperty("scriptPubKey").GetProperty("type").GetString()
);
outputs.Add(output);

Improve block parsing