RPC extention GetVerboseBlock verbosity 3
MODIFY
WalletWasabi.Tests/UnitTests/BitcoinCore/RpcBasedTests.cs
WalletWasabi.Tests/UnitTests/BitcoinCore/RpcBasedTests.cs
using NBitcoin;
using NBitcoin.RPC;
-using System;
-using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using WalletWasabi.BitcoinCore;
-using WalletWasabi.Models;
using WalletWasabi.Services;
-using WalletWasabi.Stores;
using WalletWasabi.Tests.Helpers;
using Xunit;
@@ -48,5 +44,160 @@ namespace WalletWasabi.Tests.UnitTests.BitcoinCore
await coreNode.TryStopAsync();
}
}
+
+ [Fact]
+ public async Task VerboseBlockInfoAsync()
+ {
+ var rpc = new MockRpcClient();
+ rpc.OnSendCommandAsync = (op, p) =>
+ {
+ #region Mocked RPC response
+
+ var rpcOutput = @"
+ {
+ 'result':
+ {
+ 'hash': '27cac34bec2bfc3422c352d558b4db29e6d7e8114db2dbc955df06a63cda82fe',
+ 'confirmations': 1,
+ 'strippedsize': 442,
+ 'size': 478,
+ 'weight': 1804,
+ 'height': 102,
+ 'version': 536870912,
+ 'versionHex': '20000000',
+ 'merkleroot': 'ddd7eab214fe2dc1f875ba0087b3dee60c5e55876d1494eacea88f259204004a',
+ 'tx': [
+ {
+ 'txid': '5d95a076a2231feae22dfcf10285bd4069e6ca4e7e2a896a266e17e6807d8d8c',
+ 'hash': 'b9cde924c05e72afaba40d03ea91b01fdccb976a119d9f3de57d5e2eb3f46006',
+ 'version': 2,
+ 'size': 172,
+ 'vsize': 145,
+ 'weight': 580,
+ 'locktime': 0,
+ 'vin': [
+ {
+ 'coinbase': '01660101',
+ 'sequence': 4294967295
+ }
+ ],
+ 'vout': [
+ {
+ 'value': 50.00004500,
+ 'n': 0,
+ 'scriptPubKey': {
+ 'asm': 'OP_DUP OP_HASH160 381907cb00a047109bc340afe06504d67472d3de OP_EQUALVERIFY OP_CHECKSIG',
+ 'desc': 'addr(mkda7wbQ9nVQa8ayYbTVifVzNie1kf8gKy)#yzcmfs2s',
+ 'hex': '76a914381907cb00a047109bc340afe06504d67472d3de88ac',
+ 'reqSigs': 1,
+ 'type': 'pubkeyhash',
+ 'addresses': [
+ 'mkda7wbQ9nVQa8ayYbTVifVzNie1kf8gKy'
+ ]
+ }
+ },
+ {
+ 'value': 0.00000000,
+ 'n': 1,
+ 'scriptPubKey': {
+ 'asm': 'OP_RETURN aa21a9ed8198d32b4242fa8a0bd0ae04903f602d33a6f92e768da643ad3b72ad9ce72a06',
+ 'desc': 'raw(6a24aa21a9ed8198d32b4242fa8a0bd0ae04903f602d33a6f92e768da643ad3b72ad9ce72a06)#87566l0s',
+ 'hex': '6a24aa21a9ed8198d32b4242fa8a0bd0ae04903f602d33a6f92e768da643ad3b72ad9ce72a06',
+ 'type': 'nulldata'
+ }
+ }
+ ],
+ 'hex': '020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401660101ffffffff029403062a010000001976a914381907cb00a047109bc340afe06504d67472d3de88ac0000000000000000266a24aa21a9ed8198d32b4242fa8a0bd0ae04903f602d33a6f92e768da643ad3b72ad9ce72a060120000000000000000000000000000000000000000000000000000000000000000000000000'
+ },
+ {
+ 'txid': 'f5a2f2747dc8c2ba9d362ef3c47400b01586a811fd0d0003549bce54b5c51ed4',
+ 'hash': 'f5a2f2747dc8c2ba9d362ef3c47400b01586a811fd0d0003549bce54b5c51ed4',
+ 'version': 2,
+ 'size': 225,
+ 'vsize': 225,
+ 'weight': 900,
+ 'locktime': 101,
+ 'vin': [
+ {
+ 'txid': '4815e72e2d967b666097c476473d0175b94d2a22f384e6389ab44dc9260dd8e0',
+ 'vout': 0,
+ 'scriptSig': {
+ 'asm': '30440220242cb6ccdfa7a4f83b3226b6694af52a9eafc94c7640a89786ffc93a07d79cd3022051375bc352b1f96223523e262ab93d9081135edafffd8e03a4fd38f49150e9b9[ALL] 02302fc55898d0b2adaf49be6c17c5804651ddb8ee114a05eb9da0a9517b8bccef',
+ 'hex': '4730440220242cb6ccdfa7a4f83b3226b6694af52a9eafc94c7640a89786ffc93a07d79cd3022051375bc352b1f96223523e262ab93d9081135edafffd8e03a4fd38f49150e9b9012102302fc55898d0b2adaf49be6c17c5804651ddb8ee114a05eb9da0a9517b8bccef'
+ },
+ 'prevout': {
+ 'height': 1,
+ 'value': 50.00000000,
+ 'generated': true,
+ 'scriptPubKey': {
+ 'asm': 'OP_DUP OP_HASH160 381907cb00a047109bc340afe06504d67472d3de OP_EQUALVERIFY OP_CHECKSIG',
+ 'desc': 'addr(mkda7wbQ9nVQa8ayYbTVifVzNie1kf8gKy)#yzcmfs2s',
+ 'hex': '76a914381907cb00a047109bc340afe06504d67472d3de88ac',
+ 'reqSigs': 1,
+ 'type': 'pubkeyhash',
+ 'addresses': [
+ 'mkda7wbQ9nVQa8ayYbTVifVzNie1kf8gKy'
+ ]
+ }
+ },
+ 'sequence': 4294967294
+ }
+ ],
+ 'vout': [
+ {
+ 'value': 48.99995500,
+ 'n': 0,
+ 'scriptPubKey': {
+ 'asm': 'OP_DUP OP_HASH160 6028ad75c715247d9179946458f946de0b83d3db OP_EQUALVERIFY OP_CHECKSIG',
+ 'desc': 'addr(mpHPtoCqC8XJkCbRAoDfJFk8Uiidov8JCd)#f0l9dcff',
+ 'hex': '76a9146028ad75c715247d9179946458f946de0b83d3db88ac',
+ 'reqSigs': 1,
+ 'type': 'pubkeyhash',
+ 'addresses': [
+ 'mpHPtoCqC8XJkCbRAoDfJFk8Uiidov8JCd'
+ ]
+ }
+ },
+ {
+ 'value': 1.00000000,
+ 'n': 1,
+ 'scriptPubKey': {
+ 'asm': 'OP_DUP OP_HASH160 29f5bf0598ecef7ae4f9f1163cdeecf1182c51f9 OP_EQUALVERIFY OP_CHECKSIG',
+ 'desc': 'addr(mjLpPfQNYKCJGc1qXyU71wr6vt9yuVPLR6)#4ezwynfz',
+ 'hex': '76a91429f5bf0598ecef7ae4f9f1163cdeecf1182c51f988ac',
+ 'reqSigs': 1,
+ 'type': 'pubkeyhash',
+ 'addresses': [
+ 'mjLpPfQNYKCJGc1qXyU71wr6vt9yuVPLR6'
+ ]
+ }
+ }
+ ],
+ 'fee': 0.00004500,
+ 'hex': '0200000001e0d80d26c94db49a38e684f3222a4db975013d4776c49760667b962d2ee71548000000006a4730440220242cb6ccdfa7a4f83b3226b6694af52a9eafc94c7640a89786ffc93a07d79cd3022051375bc352b1f96223523e262ab93d9081135edafffd8e03a4fd38f49150e9b9012102302fc55898d0b2adaf49be6c17c5804651ddb8ee114a05eb9da0a9517b8bcceffeffffff026cff0f24010000001976a9146028ad75c715247d9179946458f946de0b83d3db88ac00e1f505000000001976a91429f5bf0598ecef7ae4f9f1163cdeecf1182c51f988ac65000000'
+ }
+ ],
+ 'time': 1583444802,
+ 'mediantime': 1583444739,
+ 'nonce': 1,
+ 'bits': '207fffff',
+ 'difficulty': 4.656542373906925e-10,
+ 'chainwork': '00000000000000000000000000000000000000000000000000000000000000ce',
+ 'nTx': 2,
+ 'previousblockhash': '1d434df0cdd3fe26535ebe9734ef013b036441be38921606a9336ce74ab1cf04'
+ }
+ }";
+
+ #endregion Mocked RPC response
+
+ var byteArray = Encoding.ASCII.GetBytes(rpcOutput);
+ var stream = new MemoryStream(byteArray);
+ return Task.FromResult(RPCResponse.Load(stream));
+ };
+ var blockInfo = await rpc.GetVerboseBlockAsync(uint256.One);
+ Assert.Equal(2, blockInfo.Transactions.Count());
+ Assert.Single(blockInfo.Transactions[1].Inputs);
+ Assert.Equal(2, blockInfo.Transactions[1].Outputs.Count());
+ }
}
}

MODIFY
WalletWasabi.Tests/UnitTests/MockRpcClient.cs
WalletWasabi.Tests/UnitTests/MockRpcClient.cs
@@ -12,6 +12,8 @@ namespace WalletWasabi.Tests.UnitTests
public Func<uint256, Task<Block>> OnGetBlockAsync { get; set; }
public Func<uint256, Task<BlockHeader>> OnGetBlockHeaderAsync { get; set; }
+ public Func<RPCOperations, object[], Task<RPCResponse>> OnSendCommandAsync { get; set; }
+
public Network Network => Network.RegTest;
public RPCCredentialString CredentialString => new RPCCredentialString();
@@ -160,5 +162,10 @@ namespace WalletWasabi.Tests.UnitTests
{
throw new NotImplementedException();
}
+
+ public Task<RPCResponse> SendCommandAsync(RPCOperations operation, params object[] p)
+ {
+ return OnSendCommandAsync(operation, p);
+ }
}
}

MODIFY
WalletWasabi/BitcoinCore/IRPCClient.cs
WalletWasabi/BitcoinCore/IRPCClient.cs
@@ -67,5 +67,7 @@ namespace WalletWasabi.BitcoinCore
Task SendBatchAsync();
Task<EstimateSmartFeeResponse> TryEstimateSmartFeeAsync(int confirmationTarget, EstimateSmartFeeMode estimateMode = EstimateSmartFeeMode.Conservative);
+
+ Task<RPCResponse> SendCommandAsync(RPCOperations operations, params object[] parameters);
}
}

MODIFY
WalletWasabi/BitcoinCore/RpcClientBase.cs
WalletWasabi/BitcoinCore/RpcClientBase.cs
@@ -99,6 +99,11 @@ namespace WalletWasabi.BitcoinCore
return new RpcClientBase(Rpc.PrepareBatch());
}
+ public virtual Task<RPCResponse> SendCommandAsync(RPCOperations operations, params object[] parameters)
+ {
+ return Rpc.SendCommandAsync(operations, parameters);
+ }
+
#region For Testing Only
public virtual async Task<uint256> SendToAddressAsync(BitcoinAddress address, Money amount, string commentTx = null, string commentDest = null, bool subtractFeeFromAmount = false, bool replaceable = false)

MODIFY
WalletWasabi/Extensions/RPCClientExtensions.cs
WalletWasabi/Extensions/RPCClientExtensions.cs
@@ -285,5 +285,51 @@ namespace NBitcoin.RPC
return hashSet;
}
+
+ public static async Task<VerboseBlockInfo> GetVerboseBlockAsync(this IRPCClient rpc, uint256 blockId)
+ {
+ var resp = await rpc.SendCommandAsync(RPCOperations.getblock, blockId, 3).ConfigureAwait(false);
+ var blockInfoStr = resp.Result.ToString();
+ var blockInfoJson = JObject.Parse(blockInfoStr);
+ var blockInfo = new VerboseBlockInfo();
+ blockInfo.Hash = uint256.Parse(blockInfoJson.Value<string>("hash"));
+ var previousblockhash = blockInfoJson.Value<string>("previousblockhash");
+ blockInfo.PrevBlockHash = previousblockhash != null ? uint256.Parse(previousblockhash) : null;
+ blockInfo.Confirmations = blockInfoJson.Value<ulong>("confirmations");
+ blockInfo.Height = blockInfoJson.Value<ulong>("height");
+ blockInfo.BlockTime = Utils.UnixTimeToDateTime(blockInfoJson.Value<uint>("time"));
+ foreach (var txJson in blockInfoJson["tx"])
+ {
+ var tx = new VerboseTransactionInfo();
+ tx.Id = uint256.Parse(txJson.Value<string>("txid"));
+ foreach (var txinJson in txJson["vin"])
+ {
+ if (txinJson["coinbase"] is { })
+ {
+ continue;
+ }
+ var input = new VerboseInputInfo();
+ input.OutPoint = new OutPoint(
+ uint256.Parse(txinJson.Value<string>("txid")),
+ txinJson.Value<uint>("vout"));
+ input.PrevOutput = new VerboseOutputInfo();
+ input.PrevOutput.Value = Money.Coins(txinJson["prevout"].Value<decimal>("value"));
+ input.PrevOutput.ScriptPubKey = Script.FromHex(txinJson["prevout"]["scriptPubKey"].Value<string>("hex"));
+
+ tx.Inputs.Add(input);
+ }
+ foreach (var txoutJson in txJson["vout"])
+ {
+ var output = new VerboseOutputInfo();
+ output.Value = Money.Coins(txoutJson.Value<decimal>("value"));
+ output.ScriptPubKey = Script.FromHex(txoutJson["scriptPubKey"].Value<string>("hex"));
+ tx.Outputs.Add(output);
+ }
+
+ blockInfo.Transactions.Add(tx);
+ }
+
+ return blockInfo;
+ }
}
}

ADD
WalletWasabi/Extensions/VerboseBlockInfo.cs
+using System;
+using System.Collections.Generic;
+
+namespace NBitcoin.RPC
+{
+ public class VerboseBlockInfo
+ {
+ public DateTimeOffset BlockTime { get; set; }
+ public uint256 Hash { get; set; }
+ public uint256 PrevBlockHash { get; set; }
+ public ulong Confirmations { get; set; }
+ public ulong Height { get; set; }
+ public List<VerboseTransactionInfo> Transactions { get; set; } = new List<VerboseTransactionInfo>();
+ }
+}

ADD
WalletWasabi/Extensions/VerboseInputInfo.cs
+namespace NBitcoin.RPC
+{
+ public class VerboseInputInfo
+ {
+ public OutPoint OutPoint { get; set; }
+ public VerboseOutputInfo PrevOutput { get; set; }
+ }
+}
\ No newline at end of file

ADD
WalletWasabi/Extensions/VerboseOutputInfo.cs
+namespace NBitcoin.RPC
+{
+ public class VerboseOutputInfo
+ {
+ public Money Value { get; set; }
+ public Script ScriptPubKey { get; set; }
+ }
+}
\ No newline at end of file

ADD
WalletWasabi/Extensions/VerboseTransactionInfo.cs
+using System.Collections.Generic;
+
+namespace NBitcoin.RPC
+{
+ public class VerboseTransactionInfo
+ {
+ public uint256 Id { get; set; }
+
+ public List<VerboseInputInfo> Inputs { get; set; } = new List<VerboseInputInfo>();
+ public List<VerboseOutputInfo> Outputs { get; set; } = new List<VerboseOutputInfo>();
+ }
+}
\ No newline at end of file

RPC extention GetVerboseBlock verbosity 3