// System using System; using System.Collections; using System.Collections.Generic; using System.Reflection; // Unity using UnityEngine; namespace GUPS.AntiCheat.Protected.Collection.Chain { /// /// Represents an implementation of the interface, storing transactions of type T for a blockchain. A block contains a /// fixed size of transactions and can be chained with other blocks to a blockchain. /// /// The value type of the content stored in the block transactions. /// /// /// The block class is designed to store data transactions for a blockchain. A block has a nonce value, which is the hash of the previous block, /// allowing to chain blocks together, while easily verifying the integrity of the chain. /// /// /// The class provides methods to append transactions to the block and verify its integrity. To do so a hash is stored and updated upon each /// transaction addition. /// /// /// Note: Only primitive types or structs are supported. /// /// [Serializable] [Obfuscation(Exclude = true)] public class Block : IBlock, IEnumerable> where T : struct { /// /// A shared random number generator used for nonce generation. /// private static readonly System.Random random = new System.Random(); /// /// The amount of max transactions this block can store. /// [SerializeField] private int size; /// /// Gets the amount of max transactions this block can store. /// public int Size { get => size; private set => size = value; } /// /// The array of transactions within the block. /// [SerializeReference] private readonly ITransaction[] transactions; /// /// Gets the array of transactions within the block. /// public ITransaction[] Items => transactions; /// /// Gets the transaction at the specified index. /// /// The index of the transaction to get. /// The transaction at the specified index. public ITransaction this[int _Index] { get => this.transactions[_Index]; } /// /// The count of transactions currently appended to the block. /// [SerializeField] private int count; /// /// Gets the count of transactions currently appended to the block. /// public int Count { get => count; private set => count = value; } /// /// Gets the last transaction appended to the block. If no transactions are appended, null is returned. /// public ITransaction Last => this.transactions.Length > 0 ? this.transactions[this.Count - 1] : null; /// /// Get the last transaction timestamp, may be 0 if the block is empty. /// public Int64 LastTransactionTimestamp => this.Last?.Timestamp ?? 0; /// /// The nonce value of the block, which is the hash of the previous block. /// [SerializeField] public int nonce; /// /// Gets the nonce value of the block, which is the hash of the previous block. /// public int Nonce { get => nonce; private set => nonce = value; } /// /// A calculated hash based on the nonce and the transactions of the block. /// [SerializeField] public int hash; /// /// Gets the calculated hash based on the nonce and the transactions of the block. /// public int Hash { get => hash; private set => hash = value; } /// /// Initializes a new instance of with the specified size. /// /// The size of the block. public Block(int _Size) { // Assign parameters. this.size = _Size; this.transactions = new ITransaction[this.size]; // Initialize the block with a random nonce. this.nonce = random.Next(Int32.MaxValue); // Compute the initial hash code. this.hash = this.GetHashCode(); } /// /// Initializes a new instance of with the specified size and nonce. /// /// The size of the block. /// The nonce associated with the block. public Block(int _Size, int _Nonce) { // Assign parameters. this.size = _Size; this.transactions = new ITransaction[this.size]; this.nonce = _Nonce; // Compute the initial hash code. this.hash = this.GetHashCode(); } /// /// Appends a transaction to the block. If the block is full, the transaction is not appended and false is returned. /// /// The transaction to append. /// True if the transaction was successfully appended; otherwise, false if the block is full. public bool Append(ITransaction _Transaction) { // Check if the block is full. if (this.count == this.size) { return false; } // Append the transaction. this.transactions[Count] = _Transaction; // Increment the count. this.count++; // Update the hash. this.hash = this.GetHashCode(); return true; } /// /// Verifies the integrity of the block by comparing the stored hash with the computed hash. /// /// True if the block is intact; otherwise, false. public bool Verify() { return this.hash == this.GetHashCode(); } /// /// Overrides the default GetHashCode method to calculate a hash based on the nonce and the transactions of the block. /// /// The computed hash code. public override int GetHashCode() { // Initialize the hash code with the random nonce. int var_Hash = this.nonce; // Make sure to not throw an exception when an overflow occurs and wrap the result. unchecked { // Iterate through the transactions and calculate the hash code. for (int i = 0; i < Count; i++) { var_Hash = var_Hash + this.transactions[i].GetHashCode() * 23; } } // Return the computed hash code. return var_Hash; } /// /// Overrides the default Equals method to compare the current block with another block. /// /// The object to compare with the current block. /// True if the specified object is equal to the current block; otherwise, false. public override bool Equals(object _Obj) { if (_Obj == null || GetType() != _Obj.GetType()) { return false; } Block var_Other = (Block)_Obj; if (this.count != var_Other.Count) { return false; } for (int i = 0; i < Count; i++) { if (!this.transactions[i].Equals(var_Other.transactions[i])) { return false; } } return true; } /// /// Enumerates the transactions in the block. /// /// The enumerator. public IEnumerator> GetEnumerator() { foreach (ITransaction var_Transaction in this.transactions) { if (var_Transaction == null) { break; } yield return var_Transaction; } } /// /// Enumerates the transactions in the block. /// /// The enumerator. IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } }