diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..1991e5d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,12 @@
+language: python
+
+python:
+ - 3.6
+ - nightly
+
+install:
+ - pip install pipenv
+ - pipenv install --dev
+
+script:
+ - pipenv run python -m unittest
diff --git a/README.md b/README.md
index 0f46b5b..e77626b 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# Learn Blockchains by Building One
+[![Build Status](https://travis-ci.org/dvf/blockchain.svg?branch=master)](https://travis-ci.org/dvf/blockchain)
+
This is the source code for my post on [Building a Blockchain](https://medium.com/p/117428612f46).
## Installation
diff --git a/blockchain.py b/blockchain.py
index cbb95a8..92576bf 100644
--- a/blockchain.py
+++ b/blockchain.py
@@ -1,7 +1,6 @@
import hashlib
import json
from time import time
-from typing import Any, Dict, List, Optional
from urllib.parse import urlparse
from uuid import uuid4
@@ -18,7 +17,7 @@ class Blockchain:
# Create the genesis block
self.new_block(previous_hash='1', proof=100)
- def register_node(self, address: str) -> None:
+ def register_node(self, address):
"""
Add a new node to the list of nodes
@@ -35,7 +34,7 @@ class Blockchain:
raise ValueError('Invalid URL')
- def valid_chain(self, chain: List[Dict[str, Any]]) -> bool:
+ def valid_chain(self, chain):
"""
Determine if a given blockchain is valid
@@ -64,7 +63,7 @@ class Blockchain:
return True
- def resolve_conflicts(self) -> bool:
+ def resolve_conflicts(self):
"""
This is our consensus algorithm, it resolves conflicts
by replacing our chain with the longest one in the network.
@@ -98,7 +97,7 @@ class Blockchain:
return False
- def new_block(self, proof: int, previous_hash: Optional[str]) -> Dict[str, Any]:
+ def new_block(self, proof, previous_hash):
"""
Create a new Block in the Blockchain
@@ -121,7 +120,7 @@ class Blockchain:
self.chain.append(block)
return block
- def new_transaction(self, sender: str, recipient: str, amount: int) -> int:
+ def new_transaction(self, sender, recipient, amount):
"""
Creates a new transaction to go into the next mined Block
@@ -143,7 +142,7 @@ class Blockchain:
return self.chain[-1]
@staticmethod
- def hash(block: Dict[str, Any]) -> str:
+ def hash(block):
"""
Creates a SHA-256 hash of a Block
@@ -154,7 +153,7 @@ class Blockchain:
block_string = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()
- def proof_of_work(self, last_proof: int) -> int:
+ def proof_of_work(self, last_proof):
"""
Simple Proof of Work Algorithm:
- Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'
@@ -168,7 +167,7 @@ class Blockchain:
return proof
@staticmethod
- def valid_proof(last_proof: int, proof: int) -> bool:
+ def valid_proof(last_proof, proof):
"""
Validates the Proof
@@ -208,7 +207,8 @@ def mine():
)
# Forge the new Block by adding it to the chain
- block = blockchain.new_block(proof, [])
+ previous_hash = blockchain.hash(last_block)
+ block = blockchain.new_block(proof, previous_hash)
response = {
'message': "New Block Forged",
diff --git a/csharp/BlockChain.Console/App.config b/csharp/BlockChain.Console/App.config
new file mode 100644
index 0000000..089f249
--- /dev/null
+++ b/csharp/BlockChain.Console/App.config
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/csharp/BlockChain.Console/BlockChain.Console.csproj b/csharp/BlockChain.Console/BlockChain.Console.csproj
new file mode 100644
index 0000000..5bc5670
--- /dev/null
+++ b/csharp/BlockChain.Console/BlockChain.Console.csproj
@@ -0,0 +1,63 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {D0C795A0-6F20-4A8E-BE44-801678754DA4}
+ Exe
+ BlockChainDemo.Console
+ BlockChainDemo.Console
+ v4.5.1
+ 512
+ true
+ SAK
+ SAK
+ SAK
+ SAK
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {e06fc4ce-77d0-4a64-94a6-32a08920e481}
+ BlockChain
+
+
+
+
\ No newline at end of file
diff --git a/csharp/BlockChain.Console/Program.cs b/csharp/BlockChain.Console/Program.cs
new file mode 100644
index 0000000..2573f93
--- /dev/null
+++ b/csharp/BlockChain.Console/Program.cs
@@ -0,0 +1,12 @@
+namespace BlockChainDemo.Console
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var chain = new BlockChain();
+ var server = new WebServer(chain);
+ System.Console.Read();
+ }
+ }
+}
diff --git a/csharp/BlockChain.Console/Properties/AssemblyInfo.cs b/csharp/BlockChain.Console/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..2b25de2
--- /dev/null
+++ b/csharp/BlockChain.Console/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("BlockChain.Console")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BlockChain.Console")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("d0c795a0-6f20-4a8e-be44-801678754da4")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/csharp/BlockChain.sln b/csharp/BlockChain.sln
new file mode 100644
index 0000000..0175024
--- /dev/null
+++ b/csharp/BlockChain.sln
@@ -0,0 +1,43 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27004.2008
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlockChain", "BlockChain\BlockChain.csproj", "{E06FC4CE-77D0-4A64-94A6-32A08920E481}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlockChain.Console", "BlockChain.Console\BlockChain.Console.csproj", "{D0C795A0-6F20-4A8E-BE44-801678754DA4}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E06FC4CE-77D0-4A64-94A6-32A08920E481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E06FC4CE-77D0-4A64-94A6-32A08920E481}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E06FC4CE-77D0-4A64-94A6-32A08920E481}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E06FC4CE-77D0-4A64-94A6-32A08920E481}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D0C795A0-6F20-4A8E-BE44-801678754DA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D0C795A0-6F20-4A8E-BE44-801678754DA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D0C795A0-6F20-4A8E-BE44-801678754DA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D0C795A0-6F20-4A8E-BE44-801678754DA4}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {7FB3650B-CAFB-4A71-940B-0CA6F0377422}
+ EndGlobalSection
+ GlobalSection(TeamFoundationVersionControl) = preSolution
+ SccNumberOfProjects = 3
+ SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}
+ SccTeamFoundationServer = https://devfire.visualstudio.com/
+ SccLocalPath0 = .
+ SccProjectUniqueName1 = BlockChain\\BlockChain.csproj
+ SccProjectName1 = BlockChain
+ SccLocalPath1 = BlockChain
+ SccProjectUniqueName2 = BlockChain.Console\\BlockChain.Console.csproj
+ SccProjectName2 = BlockChain.Console
+ SccLocalPath2 = BlockChain.Console
+ EndGlobalSection
+EndGlobal
diff --git a/csharp/BlockChain/Block.cs b/csharp/BlockChain/Block.cs
new file mode 100644
index 0000000..9ca9d80
--- /dev/null
+++ b/csharp/BlockChain/Block.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+
+namespace BlockChainDemo
+{
+ public class Block
+ {
+ public int Index { get; set; }
+ public DateTime Timestamp { get; set; }
+ public List Transactions { get; set; }
+ public int Proof { get; set; }
+ public string PreviousHash { get; set; }
+
+ public override string ToString()
+ {
+ return $"{Index} [{Timestamp.ToString("yyyy-MM-dd HH:mm:ss")}] Proof: {Proof} | PrevHash: {PreviousHash} | Trx: {Transactions.Count}";
+ }
+ }
+}
\ No newline at end of file
diff --git a/csharp/BlockChain/BlockChain.cs b/csharp/BlockChain/BlockChain.cs
new file mode 100644
index 0000000..250d6f8
--- /dev/null
+++ b/csharp/BlockChain/BlockChain.cs
@@ -0,0 +1,226 @@
+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 _currentTransactions = new List();
+ private List _chain = new List();
+ private List _nodes = new List();
+ 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 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 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(),
+ 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;
+ }
+ }
+}
diff --git a/csharp/BlockChain/BlockChain.csproj b/csharp/BlockChain/BlockChain.csproj
new file mode 100644
index 0000000..86681ac
--- /dev/null
+++ b/csharp/BlockChain/BlockChain.csproj
@@ -0,0 +1,73 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {E06FC4CE-77D0-4A64-94A6-32A08920E481}
+ Library
+ Properties
+ BlockChainDemo
+ BlockChainDemo
+ v4.5.1
+ 512
+ SAK
+ SAK
+ SAK
+ SAK
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll
+
+
+
+
+
+
+
+
+
+
+
+ ..\packages\TinyWebServer.dll.1.0.1\lib\net40\TinyWebServer.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/csharp/BlockChain/Node.cs b/csharp/BlockChain/Node.cs
new file mode 100644
index 0000000..5ba425f
--- /dev/null
+++ b/csharp/BlockChain/Node.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace BlockChainDemo
+{
+ public class Node
+ {
+ public Uri Address { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/csharp/BlockChain/Properties/AssemblyInfo.cs b/csharp/BlockChain/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..87b9d4e
--- /dev/null
+++ b/csharp/BlockChain/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("BlockChain")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BlockChain")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("e06fc4ce-77d0-4a64-94a6-32a08920e481")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/csharp/BlockChain/Transaction.cs b/csharp/BlockChain/Transaction.cs
new file mode 100644
index 0000000..1cbaeb7
--- /dev/null
+++ b/csharp/BlockChain/Transaction.cs
@@ -0,0 +1,9 @@
+namespace BlockChainDemo
+{
+ public class Transaction
+ {
+ public int Amount { get; set; }
+ public string Recipient { get; set; }
+ public string Sender { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/csharp/BlockChain/WebServer.cs b/csharp/BlockChain/WebServer.cs
new file mode 100644
index 0000000..cd7b86b
--- /dev/null
+++ b/csharp/BlockChain/WebServer.cs
@@ -0,0 +1,78 @@
+using Newtonsoft.Json;
+using System.Configuration;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+
+namespace BlockChainDemo
+{
+ public class WebServer
+ {
+ public WebServer(BlockChain chain)
+ {
+ var settings = ConfigurationManager.AppSettings;
+ string host = settings["host"]?.Length > 1 ? settings["host"] : "localhost";
+ string port = settings["port"]?.Length > 1 ? settings["port"] : "12345";
+
+ var server = new TinyWebServer.WebServer(request =>
+ {
+ string path = request.Url.PathAndQuery.ToLower();
+ string query = "";
+ string json = "";
+ if (path.Contains("?"))
+ {
+ string[] parts = path.Split('?');
+ path = parts[0];
+ query = parts[1];
+ }
+
+ switch (path)
+ {
+ //GET: http://localhost:12345/mine
+ case "/mine":
+ return chain.Mine();
+
+ //POST: http://localhost:12345/transactions/new
+ //{ "Amount":123, "Recipient":"ebeabf5cc1d54abdbca5a8fe9493b479", "Sender":"31de2e0ef1cb4937830fcfd5d2b3b24f" }
+ case "/transactions/new":
+ if (request.HttpMethod != HttpMethod.Post.Method)
+ return $"{new HttpResponseMessage(HttpStatusCode.MethodNotAllowed)}";
+
+ json = new StreamReader(request.InputStream).ReadToEnd();
+ Transaction trx = JsonConvert.DeserializeObject(json);
+ int blockId = chain.CreateTransaction(trx.Sender, trx.Recipient, trx.Amount);
+ return $"Your transaction will be included in block {blockId}";
+
+ //GET: http://localhost:12345/chain
+ case "/chain":
+ return chain.GetFullChain();
+
+ //POST: http://localhost:12345/nodes/register
+ //{ "Urls": ["localhost:54321", "localhost:54345", "localhost:12321"] }
+ case "/nodes/register":
+ if (request.HttpMethod != HttpMethod.Post.Method)
+ return $"{new HttpResponseMessage(HttpStatusCode.MethodNotAllowed)}";
+
+ json = new StreamReader(request.InputStream).ReadToEnd();
+ var urlList = new { Urls = new string[0] };
+ var obj = JsonConvert.DeserializeAnonymousType(json, urlList);
+ return chain.RegisterNodes(obj.Urls);
+
+ //GET: http://localhost:12345/nodes/resolve
+ case "/nodes/resolve":
+ return chain.Consensus();
+ }
+
+ return "";
+ },
+ $"http://{host}:{port}/mine/",
+ $"http://{host}:{port}/transactions/new/",
+ $"http://{host}:{port}/chain/",
+ $"http://{host}:{port}/nodes/register/",
+ $"http://{host}:{port}/nodes/resolve/"
+ );
+
+ server.Run();
+ }
+ }
+}
diff --git a/csharp/BlockChain/packages.config b/csharp/BlockChain/packages.config
new file mode 100644
index 0000000..4c8c064
--- /dev/null
+++ b/csharp/BlockChain/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_blockchain.py b/tests/test_blockchain.py
new file mode 100644
index 0000000..f2d85e1
--- /dev/null
+++ b/tests/test_blockchain.py
@@ -0,0 +1,104 @@
+import hashlib
+import json
+from unittest import TestCase
+
+from blockchain import Blockchain
+
+
+class BlockchainTestCase(TestCase):
+
+ def setUp(self):
+ self.blockchain = Blockchain()
+
+ def create_block(self, proof=123, previous_hash='abc'):
+ self.blockchain.new_block(proof, previous_hash)
+
+ def create_transaction(self, sender='a', recipient='b', amount=1):
+ self.blockchain.new_transaction(
+ sender=sender,
+ recipient=recipient,
+ amount=amount
+ )
+
+
+class TestRegisterNodes(BlockchainTestCase):
+
+ def test_valid_nodes(self):
+ blockchain = Blockchain()
+
+ blockchain.register_node('http://192.168.0.1:5000')
+
+ self.assertIn('192.168.0.1:5000', blockchain.nodes)
+
+ def test_malformed_nodes(self):
+ blockchain = Blockchain()
+
+ blockchain.register_node('http//192.168.0.1:5000')
+
+ self.assertNotIn('192.168.0.1:5000', blockchain.nodes)
+
+ def test_idempotency(self):
+ blockchain = Blockchain()
+
+ blockchain.register_node('http://192.168.0.1:5000')
+ blockchain.register_node('http://192.168.0.1:5000')
+
+ assert len(blockchain.nodes) == 1
+
+
+class TestBlocksAndTransactions(BlockchainTestCase):
+
+ def test_block_creation(self):
+ self.create_block()
+
+ latest_block = self.blockchain.last_block
+
+ # The genesis block is create at initialization, so the length should be 2
+ assert len(self.blockchain.chain) == 2
+ assert latest_block['index'] == 2
+ assert latest_block['timestamp'] is not None
+ assert latest_block['proof'] == 123
+ assert latest_block['previous_hash'] == 'abc'
+
+ def test_create_transaction(self):
+ self.create_transaction()
+
+ transaction = self.blockchain.current_transactions[-1]
+
+ assert transaction
+ assert transaction['sender'] == 'a'
+ assert transaction['recipient'] == 'b'
+ assert transaction['amount'] == 1
+
+ def test_block_resets_transactions(self):
+ self.create_transaction()
+
+ initial_length = len(self.blockchain.current_transactions)
+
+ self.create_block()
+
+ current_length = len(self.blockchain.current_transactions)
+
+ assert initial_length == 1
+ assert current_length == 0
+
+ def test_return_last_block(self):
+ self.create_block()
+
+ created_block = self.blockchain.last_block
+
+ assert len(self.blockchain.chain) == 2
+ assert created_block is self.blockchain.chain[-1]
+
+
+class TestHashingAndProofs(BlockchainTestCase):
+
+ def test_hash_is_correct(self):
+ self.create_block()
+
+ new_block = self.blockchain.last_block
+ new_block_json = json.dumps(self.blockchain.last_block, sort_keys=True).encode()
+ new_hash = hashlib.sha256(new_block_json).hexdigest()
+
+ assert len(new_hash) == 64
+ assert new_hash == self.blockchain.hash(new_block)