// 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();
}
}
}