blockchain/csharp/BlockChain/BlockChain.cs

227 lines
6.8 KiB
C#

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
namespace BlockChainDemo
{
public class BlockChain
{
private List<Transaction> _currentTransactions = new List<Transaction>();
private List<Block> _chain = new List<Block>();
private List<Node> _nodes = new List<Node>();
private Block _lastBlock => _chain.Last();
public string NodeId { get; private set; }
//ctor
public BlockChain()
{
NodeId = Guid.NewGuid().ToString().Replace("-", "");
CreateNewBlock(proof: 100, previousHash: "1"); //genesis block
}
//private functionality
private void RegisterNode(string address)
{
_nodes.Add(new Node { Address = new Uri(address) });
}
private bool IsValidChain(List<Block> chain)
{
Block block = null;
Block lastBlock = chain.First();
int currentIndex = 1;
while (currentIndex < chain.Count)
{
block = chain.ElementAt(currentIndex);
Debug.WriteLine($"{lastBlock}");
Debug.WriteLine($"{block}");
Debug.WriteLine("----------------------------");
//Check that the hash of the block is correct
if (block.PreviousHash != GetHash(lastBlock))
return false;
//Check that the Proof of Work is correct
if (!IsValidProof(lastBlock.Proof, block.Proof, lastBlock.PreviousHash))
return false;
lastBlock = block;
currentIndex++;
}
return true;
}
private bool ResolveConflicts()
{
List<Block> newChain = null;
int maxLength = _chain.Count;
foreach (Node node in _nodes)
{
var url = new Uri(node.Address, "/chain");
var request = (HttpWebRequest)WebRequest.Create(url);
var response = (HttpWebResponse)request.GetResponse();
if (response.StatusCode == HttpStatusCode.OK)
{
var model = new
{
chain = new List<Block>(),
length = 0
};
string json = new StreamReader(response.GetResponseStream()).ReadToEnd();
var data = JsonConvert.DeserializeAnonymousType(json, model);
if (data.chain.Count > _chain.Count && IsValidChain(data.chain))
{
maxLength = data.chain.Count;
newChain = data.chain;
}
}
}
if (newChain != null)
{
_chain = newChain;
return true;
}
return false;
}
private Block CreateNewBlock(int proof, string previousHash = null)
{
var block = new Block
{
Index = _chain.Count,
Timestamp = DateTime.UtcNow,
Transactions = _currentTransactions.ToList(),
Proof = proof,
PreviousHash = previousHash ?? GetHash(_chain.Last())
};
_currentTransactions.Clear();
_chain.Add(block);
return block;
}
private int CreateProofOfWork(int lastProof, string previousHash)
{
int proof = 0;
while (!IsValidProof(lastProof, proof, previousHash))
proof++;
return proof;
}
private bool IsValidProof(int lastProof, int proof, string previousHash)
{
string guess = $"{lastProof}{proof}{previousHash}";
string result = GetSha256(guess);
return result.StartsWith("0000");
}
private string GetHash(Block block)
{
string blockText = JsonConvert.SerializeObject(block);
return GetSha256(blockText);
}
private string GetSha256(string data)
{
var sha256 = new SHA256Managed();
var hashBuilder = new StringBuilder();
byte[] bytes = Encoding.Unicode.GetBytes(data);
byte[] hash = sha256.ComputeHash(bytes);
foreach (byte x in hash)
hashBuilder.Append($"{x:x2}");
return hashBuilder.ToString();
}
//web server calls
internal string Mine()
{
int proof = CreateProofOfWork(_lastBlock.Proof, _lastBlock.PreviousHash);
CreateTransaction(sender: "0", recipient: NodeId, amount: 1);
Block block = CreateNewBlock(proof /*, _lastBlock.PreviousHash*/);
var response = new
{
Message = "New Block Forged",
Index = block.Index,
Transactions = block.Transactions.ToArray(),
Proof = block.Proof,
PreviousHash = block.PreviousHash
};
return JsonConvert.SerializeObject(response);
}
internal string GetFullChain()
{
var response = new
{
chain = _chain.ToArray(),
length = _chain.Count
};
return JsonConvert.SerializeObject(response);
}
internal string RegisterNodes(string[] nodes)
{
var builder = new StringBuilder();
foreach (string node in nodes)
{
string url = $"http://{node}";
RegisterNode(url);
builder.Append($"{url}, ");
}
builder.Insert(0, $"{nodes.Count()} new nodes have been added: ");
string result = builder.ToString();
return result.Substring(0, result.Length - 2);
}
internal string Consensus()
{
bool replaced = ResolveConflicts();
string message = replaced ? "was replaced" : "is authoritive";
var response = new
{
Message = $"Our chain {message}",
Chain = _chain
};
return JsonConvert.SerializeObject(response);
}
internal int CreateTransaction(string sender, string recipient, int amount)
{
var transaction = new Transaction
{
Sender = sender,
Recipient = recipient,
Amount = amount
};
_currentTransactions.Add(transaction);
return _lastBlock != null ? _lastBlock.Index + 1 : 0;
}
}
}