< Summary

Information
Class: EF.Blockchain.Domain.Block
Assembly: EF.Blockchain.Domain
File(s): C:\dev\@web3\web3-001-ef-blockchain\backend\EF.Blockchain\src\EF.Blockchain.Domain\Block.cs
Line coverage
100%
Covered lines: 88
Uncovered lines: 0
Coverable lines: 88
Total lines: 200
Line coverage: 100%
Branch coverage
97%
Covered branches: 41
Total branches: 42
Branch coverage: 97.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Index()100%11100%
get_Timestamp()100%11100%
get_Hash()100%11100%
get_PreviousHash()100%11100%
get_Transactions()100%11100%
get_Nonce()100%11100%
get_Miner()100%11100%
.ctor(...)100%1010100%
GetHash()100%44100%
ComputeHash(...)100%11100%
Mine(...)100%22100%
IsValid(...)96.15%2626100%
FromBlockInfo(...)100%11100%

File(s)

C:\dev\@web3\web3-001-ef-blockchain\backend\EF.Blockchain\src\EF.Blockchain.Domain\Block.cs

#LineLine coverage
 1using System.Security.Cryptography;
 2using System.Text;
 3
 4namespace EF.Blockchain.Domain;
 5
 6/// <summary>
 7/// Represents a block in the blockchain. Each block contains a list of transactions and metadata like index, timestamp,
 8/// </summary>
 9public class Block
 10{
 11    /// <summary>
 12    /// The index of the block in the blockchain.
 13    /// </summary>
 7985614    public int Index { get; private set; }
 15
 16    /// <summary>
 17    /// The Unix timestamp when the block was created.
 18    /// </summary>
 7968019    public long Timestamp { get; private set; }
 20
 21    /// <summary>
 22    /// The SHA-256 hash of the block.
 23    /// </summary>
 15752024    public string Hash { get; private set; }
 25
 26    /// <summary>
 27    /// The hash of the previous block in the chain.
 28    /// </summary>
 7968029    public string PreviousHash { get; private set; }
 30
 31    /// <summary>
 32    /// List of transactions included in this block.
 33    /// </summary>
 24016834    public List<Transaction> Transactions { get; private set; } = new();
 35
 36    /// <summary>
 37    /// The nonce value used for proof-of-work (mining).
 38    /// </summary>
 23524839    public int Nonce { get; private set; }
 40
 41    /// <summary>
 42    /// The wallet address of the miner who mined this block.
 43    /// </summary>
 8068844    public string Miner { get; private set; }
 45
 46    /// <summary>
 47    /// Creates a new instance of a block.
 48    /// </summary>
 49    /// <param name="index">The index of the block in the chain.</param>
 50    /// <param name="previousHash">Hash of the previous block.</param>
 51    /// <param name="transactions">List of transactions included.</param>
 52    /// <param name="timestamp">Timestamp of block creation.</param>
 53    /// <param name="hash">Optional predefined hash.</param>
 54    /// <param name="nonce">Nonce value used for mining.</param>
 55    /// <param name="miner">Miner's wallet address.</param>
 83256    public Block(int? index = null,
 83257        string? previousHash = null,
 83258        List<Transaction>? transactions = null,
 83259        long? timestamp = null,
 83260        string? hash = null,
 83261        int? nonce = null,
 83262        string? miner = null)
 83263    {
 83264        Index = index ?? 0;
 83265        Timestamp = timestamp ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds();
 83266        PreviousHash = previousHash ?? string.Empty;
 83267        Transactions = transactions ?? new();
 83268        Nonce = nonce ?? 0;
 83269        Miner = miner ?? string.Empty;
 83270        Hash = string.IsNullOrEmpty(hash) ? GetHash() : hash;
 83271    }
 72
 73    /// <summary>
 74    /// Generates the SHA-256 hash for the block based on its current data.
 75    /// </summary>
 76    /// <returns>The generated hash string.</returns>
 77    public string GetHash()
 7871278    {
 7871279        var txs = Transactions != null && Transactions.Any()
 22555880            ? Transactions.Select(tx => tx.Hash).Aggregate((a, b) => a + b)
 7871281            : "";
 82
 7871283        return ComputeHash(Index, Timestamp, txs, PreviousHash, Nonce, Miner);
 7871284    }
 85
 86    /// <summary>
 87    /// Computes a SHA-256 hash based on provided parameters.
 88    /// </summary>
 89    /// <param name="index">Block index.</param>
 90    /// <param name="timestamp">Block timestamp.</param>
 91    /// <param name="txs">Concatenated transaction hashes.</param>
 92    /// <param name="previousHash">Hash of the previous block.</param>
 93    /// <param name="nonce">Nonce used in mining.</param>
 94    /// <param name="miner">Miner's wallet address.</param>
 95    /// <returns>Computed SHA-256 hash.</returns>
 96    public static string ComputeHash(int index,
 97        long timestamp,
 98        string txs,
 99        string previousHash,
 100        int nonce = 0,
 101        string? miner = null)
 78712102    {
 78712103        var rawData = $"{index}{txs}{timestamp}{previousHash}{nonce}{miner}";
 78712104        using var sha256 = SHA256.Create();
 78712105        var bytes = Encoding.UTF8.GetBytes(rawData);
 78712106        return Convert.ToHexString(sha256.ComputeHash(bytes)).ToLower();
 78712107    }
 108
 109    /// <summary>
 110    /// Mines the block by incrementing the nonce until the hash starts with the required number of zeros.
 111    /// </summary>
 112    /// <param name="difficulty">Number of leading zeros required in the hash.</param>
 113    /// <param name="miner">Miner's wallet address.</param>
 114    public void Mine(int difficulty, string miner)
 664115    {
 664116        Miner = miner;
 664117        var prefix = new string('0', difficulty);
 118
 119        do
 77792120        {
 77792121            Nonce++;
 77792122            Hash = GetHash();
 77792123        }
 77792124        while (!Hash.StartsWith(prefix));
 664125    }
 126
 127    /// <summary>
 128    /// Validates this block using previous block data and difficulty.
 129    /// </summary>
 130    /// <param name="previousHash">Hash of the previous block.</param>
 131    /// <param name="previousIndex">Index of the previous block.</param>
 132    /// <param name="difficulty">Current difficulty level.</param>
 133    /// <param name="feePerTx">Expected fee per transaction.</param>
 134    /// <returns>A <see cref="Validation"/> result indicating if the block is valid.</returns>
 135    public Validation IsValid(string previousHash, int previousIndex, int difficulty, int feePerTx)
 416136    {
 416137        if (Transactions != null && Transactions.Any())
 400138        {
 400139            var feeTxs = Transactions
 776140                .Where(tx => tx.Type == TransactionType.FEE)
 400141                .ToList();
 142
 400143            if (!feeTxs.Any())
 16144                return new Validation(false, "No fee tx");
 145
 384146            if (feeTxs.Count > 1)
 8147                return new Validation(false, "Too many fees");
 148
 752149            if (!feeTxs[0].TxOutputs.Any(txo => txo.ToAddress == Miner))
 8150                return new Validation(false, "Invalid fee tx: different from miner");
 151
 1096152            var totalFees = feePerTx * Transactions.Count(tx => tx.Type != TransactionType.FEE);
 153
 368154            var validations = Transactions
 728155                .Select(tx => tx.IsValid(difficulty, totalFees))
 728156                .Where(v => !v.Success)
 272157                .Select(v => v.Message)
 368158                .ToList();
 159
 368160            if (validations.Any())
 272161            {
 272162                var errorMsg = string.Join(" ", validations);
 272163                return new Validation(false, "Invalid block due to invalid tx: " + errorMsg);
 164            }
 96165        }
 166
 112167        if (previousIndex != Index - 1)
 32168            return new Validation(false, "Invalid index");
 169
 80170        if (Timestamp < 1)
 8171            return new Validation(false, "Invalid timestamp");
 172
 72173        if (PreviousHash != previousHash)
 8174            return new Validation(false, "Invalid previous hash");
 175
 64176        if (Nonce < 1 || string.IsNullOrEmpty(Miner))
 8177            return new Validation(false, "No mined");
 178
 56179        var prefix = new string('0', difficulty);
 56180        if (Hash != GetHash() || !Hash.StartsWith(prefix))
 16181            return new Validation(false, "Invalid hash");
 182
 40183        return new Validation();
 416184    }
 185
 186    /// <summary>
 187    /// Creates a new block from a given <see cref="BlockInfo"/> object.
 188    /// </summary>
 189    /// <param name="blockInfo">The block info used to create a block.</param>
 190    /// <returns>A new <see cref="Block"/> instance.</returns>
 191    public static Block FromBlockInfo(BlockInfo blockInfo)
 8192    {
 8193        return new Block
 8194        {
 8195            Index = blockInfo.Index,
 8196            PreviousHash = blockInfo.PreviousHash,
 8197            Transactions = blockInfo.Transactions
 8198        };
 8199    }
 200}