< Summary

Information
Class: EF.Blockchain.Domain.Transaction
Assembly: EF.Blockchain.Domain
File(s): C:\dev\@web3\web3-001-ef-blockchain\backend\EF.Blockchain\src\EF.Blockchain.Domain\Transaction.cs
Line coverage
100%
Covered lines: 93
Uncovered lines: 0
Coverable lines: 93
Total lines: 176
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_Type()100%11100%
get_Timestamp()100%11100%
get_Hash()100%11100%
get_TxInputs()100%11100%
get_TxOutputs()100%11100%
.ctor(...)100%11100%
.ctor(...)100%66100%
GetHash()100%88100%
GetFee()83.33%66100%
IsValid(...)100%2222100%
FromReward(...)100%11100%

File(s)

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

#LineLine coverage
 1using System.Security.Cryptography;
 2using System.Text;
 3using System.Text.Json.Serialization;
 4
 5namespace EF.Blockchain.Domain;
 6
 7/// <summary>
 8/// Represents a transaction on the blockchain, containing inputs, outputs, and metadata.
 9/// </summary>
 10public class Transaction
 11{
 12    /// <summary>
 13    /// Type of the transaction (e.g., REGULAR or FEE).
 14    /// </summary>
 627215    public TransactionType Type { get; private set; }
 16
 17    /// <summary>
 18    /// Timestamp in milliseconds when the transaction was created.
 19    /// </summary>
 421620    public long Timestamp { get; private set; }
 21
 22    /// <summary>
 23    /// Unique hash of the transaction.
 24    /// </summary>
 15714325    public string Hash { get; private set; }
 26
 27    /// <summary>
 28    /// List of inputs that reference previous unspent outputs.
 29    /// </summary>
 860830    public List<TransactionInput>? TxInputs { get; private set; }
 31
 32    /// <summary>
 33    /// List of outputs generated by this transaction.
 34    /// </summary>
 1502435    public List<TransactionOutput> TxOutputs { get; private set; }
 36
 37    /// <summary>
 38    /// Constructor used for JSON deserialization.
 39    /// </summary>
 40    [JsonConstructor]
 1641    public Transaction(
 1642        TransactionType type,
 1643        long timestamp,
 1644        string hash,
 1645        List<TransactionInput>? txInputs = null,
 1646        List<TransactionOutput>? txOutputs = null)
 1647    {
 1648        Type = type;
 1649        Timestamp = timestamp;
 1650        Hash = hash;
 1651        TxInputs = txInputs;
 1652        TxOutputs = txOutputs;
 1653    }
 54
 55    /// <summary>
 56    /// Creates a new transaction with optional parameters. Automatically computes the hash and sets output references.
 57    /// </summary>
 148858    public Transaction(
 148859        TransactionType? type = null,
 148860        long? timestamp = null,
 148861        List<TransactionInput>? txInputs = null,
 148862        List<TransactionOutput>? txOutputs = null)
 148863    {
 148864        Type = type ?? TransactionType.REGULAR;
 148865        Timestamp = timestamp ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
 148866        TxInputs = txInputs;
 148867        TxOutputs = txOutputs ?? new List<TransactionOutput>();
 68
 148869        Hash = GetHash();
 70
 71        // Set tx reference hash for outputs
 681672        foreach (var txo in TxOutputs)
 117673        {
 117674            txo.SetTx(Hash);
 117675        }
 148876    }
 77
 78    /// <summary>
 79    /// Calculates the SHA-256 hash of the transaction.
 80    /// </summary>
 81    /// <returns>The hex-encoded lowercase hash string.</returns>
 82    public string GetHash()
 260883    {
 260884        var from = TxInputs != null && TxInputs.Any()
 108085            ? string.Join(",", TxInputs.Select(txi => txi.Signature))
 260886            : "";
 87
 260888        var to = TxOutputs != null && TxOutputs.Any()
 228089            ? string.Join(",", TxOutputs.Select(txo => txo.GetHash()))
 260890            : "";
 91
 260892        var raw = $"{Type}{from}{to}{Timestamp}";
 260893        using var sha256 = SHA256.Create();
 260894        var bytes = Encoding.UTF8.GetBytes(raw);
 260895        var hashBytes = sha256.ComputeHash(bytes);
 260896        return Convert.ToHexString(hashBytes).ToLower();
 260897    }
 98
 99    /// <summary>
 100    /// Calculates the fee of the transaction (input sum - output sum).
 101    /// </summary>
 102    /// <returns>The fee amount.</returns>
 103    public int GetFee()
 16104    {
 16105        if (TxInputs == null || !TxInputs.Any())
 8106            return 0;
 107
 16108        var inputSum = TxInputs.Sum(txi => txi.Amount);
 16109        var outputSum = TxOutputs?.Sum(txo => txo.Amount) ?? 0;
 110
 8111        return inputSum - outputSum;
 16112    }
 113
 114    /// <summary>
 115    /// Validates the transaction, checking signatures, input/output consistency, hash integrity, and reward logic.
 116    /// </summary>
 117    /// <param name="difficulty">Blockchain difficulty at the time.</param>
 118    /// <param name="totalFees">Total fees collected for this block.</param>
 119    /// <returns>A <see cref="Validation"/> result indicating if the transaction is valid.</returns>
 120    public Validation IsValid(int difficulty, int totalFees)
 824121    {
 824122        if (Hash != GetHash())
 16123            return new Validation(false, "Invalid hash");
 124
 1608125        if (TxOutputs == null || !TxOutputs.Any() || TxOutputs.Any(txo => !txo.IsValid().Success))
 8126            return new Validation(false, "Invalid TXO");
 127
 800128        if (TxInputs != null && TxInputs.Any())
 416129        {
 416130            var inputValidation = TxInputs
 416131                .Select(txi => txi.IsValid())
 416132                .Where(v => !v.Success)
 416133                .ToList();
 134
 416135            if (inputValidation.Any())
 272136            {
 544137                var message = string.Join(" ", inputValidation.Select(v => v.Message));
 272138                return new Validation(false, $"Invalid tx: {message}");
 139            }
 140
 288141            var inputSum = TxInputs.Sum(txi => txi.Amount);
 288142            var outputSum = TxOutputs.Sum(txo => txo.Amount);
 144143            if (inputSum < outputSum)
 8144                return new Validation(false, "Invalid tx: input amounts must be equals or greater than outputs amounts")
 136145        }
 146
 1040147        if (TxOutputs.Any(txo => txo.Tx != Hash))
 8148            return new Validation(false, "Invalid TXO reference hash");
 149
 512150        if (Type == TransactionType.FEE)
 392151        {
 392152            var txo = TxOutputs[0];
 392153            if (txo.Amount > Blockchain.GetRewardAmount(difficulty) + totalFees)
 8154                return new Validation(false, "Invalid tx reward");
 384155        }
 156
 504157        return new Validation();
 824158    }
 159
 160    /// <summary>
 161    /// Creates a special transaction that represents a mining reward (fee transaction).
 162    /// </summary>
 163    /// <param name="txo">The output that represents the reward to the miner.</param>
 164    /// <returns>A fee-type transaction.</returns>
 165    public static Transaction FromReward(TransactionOutput txo)
 288166    {
 288167        var tx = new Transaction(
 288168            type: TransactionType.FEE,
 288169            txOutputs: new List<TransactionOutput> { txo }
 288170        );
 171
 288172        tx.Hash = tx.GetHash();
 288173        tx.TxOutputs[0].SetTx(tx.Hash);
 288174        return tx;
 288175    }
 176}