6
\$\begingroup\$

Some time ago I started writing a blockchain implementation for learning purposes. I used this article as reference. Originally I wrote the code in C# but recently I have rewritten everything to F#. Please review my F# code, although feel free to comment on C# as well if you feel like it.

Hopefully I will continue with my work and post more questions in the future. I plan to create further functionalities directly in F#.

Anyway, here is my C# code (so it's clear what exactly I wanted to achieve) and the F# equivalent. I have also added some questions at the end of the post.

Code

Helpers.cs

namespace Blockchain
{
 public static class IntExtesions
 {
 public static string ToHex(this int i)
 {
 return i.ToString("x");
 }
 }
}

Helpers.fs

namespace Blockchain.Core
module Int32 =
 let toHex(i: int) = i.ToString("x")

Block.cs

using System;
namespace Blockchain.Core
{
 public class Block : IEquatable<Block>
 {
 public Block(int index, string previousHash, DateTime timestamp, string data, string hash, int difficulty, string nonce)
 {
 Index = index;
 PreviousHash = previousHash;
 Timestamp = timestamp;
 Data = data;
 Hash = hash;
 Difficulty = difficulty;
 Nonce = nonce;
 }
 public int Index { get; }
 public string PreviousHash { get; }
 public DateTime Timestamp { get; }
 public string Data { get; }
 public string Hash { get; }
 public int Difficulty { get; }
 public string Nonce { get; }
 public bool Equals(Block other)
 {
 if (other is null) return false;
 if (ReferenceEquals(this, other)) return true;
 return Index == other.Index &&
 string.Equals(PreviousHash, other.PreviousHash) &&
 Timestamp.Equals(other.Timestamp) &&
 string.Equals(Data, other.Data) &&
 string.Equals(Hash, other.Hash);
 }
 public override bool Equals(object obj)
 {
 if (obj is null) return false;
 if (ReferenceEquals(this, obj)) return true;
 return obj.GetType() == this.GetType() && Equals((Block) obj);
 }
 public override int GetHashCode()
 {
 unchecked
 {
 var hashCode = Index;
 hashCode = (hashCode * 397) ^ (PreviousHash != null ? PreviousHash.GetHashCode() : 0);
 hashCode = (hashCode * 397) ^ Timestamp.GetHashCode();
 hashCode = (hashCode * 397) ^ (Data != null ? Data.GetHashCode() : 0);
 hashCode = (hashCode * 397) ^ (Hash != null ? Hash.GetHashCode() : 0);
 return hashCode;
 }
 }
 }
}

Block.fs

namespace Blockchain.Core
open System
type Block(index: int, previousHash: string, timestamp: DateTime, data: string, hash: string, difficulty: int, nonce: string) =
 member val Index = index
 member val PreviousHash = previousHash
 member val Timestamp = timestamp
 member val Data = data
 member val Hash = hash
 member val Difficulty = difficulty
 member val Nonce = nonce
 override x.Equals(obj) =
 match obj with
 | :? Block as b -> (index, previousHash, timestamp, data, hash) = (b.Index, b.PreviousHash, b.Timestamp, b.Data, b.Hash)
 | _ -> false
 override x.GetHashCode() =
 let mutable hashCode = index
 hashCode <- (hashCode * 397) ^^^ (if previousHash <> null then previousHash.GetHashCode() else 0)
 hashCode <- (hashCode * 397) ^^^ timestamp.GetHashCode();
 hashCode <- (hashCode * 397) ^^^ (if data <> null then data.GetHashCode() else 0)
 hashCode <- (hashCode * 397) ^^^ (if hash <> null then hash.GetHashCode() else 0)
 hashCode

Blockchain.cs

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace Blockchain.Core
{
 public class Blockchain
 {
 public List<Block> Chain { get; private set; } = new List<Block>();
 public int Difficulty { get; } = 1;
 public Block GenesisBlock => new Block(0, "0", new 
 DateTime(2000, 1, 1), "Genesis block",
 "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7", 
 1, 0.ToHex());
 public void ReplaceChain(List<Block> newChain)
 {
 if (newChain.Count > Chain.Count && ChainIsValid(newChain))
 {
 Chain = newChain;
 }
 }
 public Block GenerateNextBlock(string blockData)
 {
 var previousBlock = GetLatestBlock();
 var nextIndex = previousBlock.Index + 1;
 var nextTimestamp = DateTime.Now;
 var nonce = 0;
 bool hashIsValid = false;
 string hexNonce = null;
 string nextHash = null;
 while (!hashIsValid)
 {
 hexNonce = nonce.ToHex();
 nextHash = CalculateBlockHash(nextIndex, previousBlock.Hash, nextTimestamp, blockData, hexNonce);
 if (HashIsValid(nextHash, Difficulty))
 {
 hashIsValid = true;
 }
 nonce++;
 }
 return new Block(nextIndex, previousBlock.Hash, nextTimestamp, blockData, nextHash, Difficulty, hexNonce);
 }
 public string CalculateBlockHash(int index, string previousHash, DateTime timestamp, string data, string nonce)
 {
 var sb = new StringBuilder();
 using (var hash = SHA256.Create())
 {
 var value = index +
 previousHash +
 timestamp.ToString(CultureInfo.InvariantCulture.DateTimeFormat.FullDateTimePattern) +
 data +
 nonce;
 var result = hash.ComputeHash(Encoding.UTF8.GetBytes(value));
 foreach (var b in result)
 sb.Append(b.ToString("x2"));
 }
 return sb.ToString();
 }
 public string CalculateBlockHash(Block block)
 {
 return CalculateBlockHash(block.Index, block.PreviousHash, block.Timestamp, block.Data, block.Nonce);
 }
 private bool ChainIsValid(IReadOnlyList<Block> chain)
 {
 if (!chain[0].Equals(GenesisBlock))
 {
 return false;
 }
 for (var i = 1; i < chain.Count; i++)
 {
 if (!BlockIsValid(chain[i], chain[i - 1]))
 {
 return false;
 }
 }
 return true;
 }
 private bool BlockIsValid(Block newBlock, Block previousBlock)
 {
 if (previousBlock.Index + 1 != newBlock.Index)
 {
 return false;
 }
 if (previousBlock.Hash != newBlock.PreviousHash)
 {
 return false;
 }
 return CalculateBlockHash(newBlock) == newBlock.Hash;
 }
 private static bool HashIsValid(string hash, int difficulty)
 {
 var prefix = string.Concat(Enumerable.Repeat('0', difficulty));
 return hash.StartsWith(prefix);
 }
 private Block GetLatestBlock()
 {
 return Chain.Last();
 }
 }
}

Blockchain.fs

namespace Blockchain.Core
open System
open System.Security.Cryptography
open System.Globalization
open System.Text
type Blockchain() =
 let mutable chain = [||] : Block array
 member x.Chain 
 with get() = chain
 and private set(value) = chain <- value
 member val Difficulty = 1
 member x.GenesisBlock = 
 new Block(0, "0", new DateTime(2000, 1, 1), "Genesis block", 
 "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7", 1, Int32.toHex(0))
 member x.ReplaceChain(newChain: Block array) =
 if newChain.Length > x.Chain.Length && x.ChainIsValid newChain then x.Chain <- newChain
 member x.GenerateNextBlock(blockData) =
 let previousBlock = x.GetLatestBlock()
 let nextIndex = previousBlock.Index + 1
 let nextTimestamp = DateTime.Now
 let rec generateBlock nonce =
 let hexNonce = Int32.toHex(nonce)
 let nextHash = x.CalculateBlockHash(nextIndex, previousBlock.Hash, nextTimestamp, blockData, hexNonce)
 match x.HashIsValid(nextHash, x.Difficulty) with
 | true -> new Block(nextIndex, previousBlock.Hash, nextTimestamp, blockData, nextHash, x.Difficulty, hexNonce)
 | false -> generateBlock(nonce + 1)
 generateBlock 0
 member x.CalculateBlockHash((index: int), previousHash, (timestamp: DateTime), data, nonce) =
 use hash = SHA256.Create()
 [index.ToString(); previousHash; timestamp.ToString(CultureInfo.InvariantCulture.DateTimeFormat.FullDateTimePattern); data; nonce]
 |> String.Concat
 |> Encoding.UTF8.GetBytes
 |> hash.ComputeHash
 |> Encoding.UTF8.GetString
 |> (+) "x2"
 member x.CalculateBlockHash(block: Block) =
 x.CalculateBlockHash(block.Index, block.PreviousHash, block.Timestamp, block.Data, block.Nonce)
 member private x.ChainIsValid(chain: Block array) =
 match chain.[0].Equals x.GenesisBlock with
 | true -> chain |> Seq.pairwise |> Seq.forall (fun (a, b) -> x.BlockIsValid(a, b))
 | false -> false
 member private x.BlockIsValid(newBlock: Block, previousBlock: Block) =
 if previousBlock.Index + 1 <> newBlock.Index then
 false
 else if previousBlock.Hash <> newBlock.PreviousHash then
 false
 else
 x.CalculateBlockHash newBlock = newBlock.Hash
 member private x.HashIsValid((hash: string), difficulty) =
 let prefix = (Seq.replicate difficulty '0') |> String.Concat
 hash.StartsWith(prefix)
 member private x.GetLatestBlock() = Array.last x.Chain

Questions

Block.fs

  • There is a hash function in F# but I couldn't find a definite answer if it can/should be used instead of GetHashCode. Are there some useful good practices?
  • The Equals override in C# has been generated by ReSharper (as was GetHashCode). Is the F#'s Equals code good enough?

Blockchain.fs

  • Does F# have a more suitable data structure for chain?
  • When invoking functions with a single unit parameter, is it good practice to write parenthesis or to omit them?
asked Sep 23, 2018 at 21:27
\$\endgroup\$
2
  • 2
    \$\begingroup\$ It might be easier to review this if you just posted the f# code. Or maybe consider using quote blocks for the code that isn’t up for review. \$\endgroup\$ Commented Sep 23, 2018 at 22:22
  • \$\begingroup\$ @RubberDuck Here you are :) \$\endgroup\$ Commented Sep 23, 2018 at 22:32

1 Answer 1

3
\$\begingroup\$

Below find my comments and suggestions inline in the C# code (I'm not an expert on BlockChains so this is just use of my version of common sense and some programming experience):

 public static class IntExtesions
 {
 public static string ToHex(this int i)
 {
 return i.ToString("x");
 }
 // For completeness make a ToHex for byte as well
 public static string ToHex(this byte b)
 {
 return b.ToString("x2");
 }
 }
 public class Block : IEquatable<Block>
 {
 public Block(int index, string previousHash, DateTime timestamp, string data, string hash, int difficulty, int nonce)
 {
 Index = index; 
 PreviousHash = previousHash;
 Timestamp = timestamp;
 Data = data;
 Hash = hash;
 Difficulty = difficulty;
 Nonce = nonce;
 }
 // I'm not sure if an index is good as a property on the block. 
 //Shouldn't the index be determined by the position in the block chain?
 public int Index { get; } 
 public string PreviousHash { get; }
 public DateTime Timestamp { get; }
 public string Data { get; }
 public string Hash { get; }
 public int Difficulty { get; }
 //public string Nonce { get; } // I would keep the Nonce as a number
 public int Nonce { get; }
 public bool Equals(Block other)
 {
 if (other is null) return false;
 if (ReferenceEquals(this, other)) return true;
 return Index == other.Index &&
 string.Equals(PreviousHash, other.PreviousHash) &&
 Timestamp.Equals(other.Timestamp) &&
 string.Equals(Data, other.Data) &&
 string.Equals(Hash, other.Hash);
 }
 public override bool Equals(object obj)
 {
 if (obj is null) return false;
 if (ReferenceEquals(this, obj)) return true;
 return obj.GetType() == this.GetType() && Equals((Block)obj);
 }
 public override int GetHashCode()
 {
 unchecked
 {
 var hashCode = Index;
 hashCode = (hashCode * 397) ^ (PreviousHash != null ? PreviousHash.GetHashCode() : 0);
 hashCode = (hashCode * 397) ^ Timestamp.GetHashCode();
 hashCode = (hashCode * 397) ^ (Data != null ? Data.GetHashCode() : 0);
 hashCode = (hashCode * 397) ^ (Hash != null ? Hash.GetHashCode() : 0);
 return hashCode;
 }
 }
 }
 public class Blockchain
 {
 // Consider make Chain private and then implement IEnumerable<Block> on BlockChain.
 // This will prevent "unauthorized" modification of the chain.
 // You'll then have to create methods for Add and Remove, and Count etc.
 public List<Block> Chain { get; private set; } = new List<Block>();
 public int Difficulty { get; } = 1; // A difficulty of 1 seems not to be very "difficult" :-). Maybe it should be an argument to the constructor?
 // It seems that you don't add the GenesisBlock to the Chain?
 public Block GenesisBlock => new Block(0, "0", new
 DateTime(2000, 1, 1), "Genesis block",
 "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7",
 1, 0);
 // I'm not sure I understand what this method is good for?
 public void ReplaceChain(List<Block> newChain)
 {
 if (newChain.Count > Chain.Count && ChainIsValid(newChain))
 {
 // Be aware that doing this:
 // Chain = newChain;
 // you actually only hold a reference to the incoming list. If someone outside this instance of BlockChain modifies that list, this instance may not behave as expected.
 // Instead you should make a copy:
 Chain = new List<Block>(newChain);
 }
 // TODO: If the new chain is rejected then the client should be nofitied: throw an exception or return false
 }
 public Block GenerateNextBlock(string blockData)
 {
 var previousBlock = GetLatestBlock();
 var nextIndex = previousBlock.Index + 1;
 var nextTimestamp = DateTime.Now;
 var nonce = 0;
 //bool hashIsValid = false;
 //string hexNonce = null;
 string nextHash = null;
 // With a modifed IsHasValid(...) (see below) you can do this:
 while (!HashIsValid(nextHash, Difficulty))
 {
 nextHash = CalculateBlockHash(nextIndex, previousBlock.Hash, nextTimestamp, blockData, nonce);
 nonce++; // Here you actually increment nonce once too much when the hash is valid
 }
 // ... instead of
 //while (!hashIsValid)
 //{
 // hexNonce = nonce.ToHex();
 // nextHash = CalculateBlockHash(nextIndex, previousBlock.Hash, nextTimestamp, blockData, hexNonce);
 // if (HashIsValid(nextHash, Difficulty))
 // {
 // hashIsValid = true;
 // }
 // nonce++; // Here you actually increment nonce once too much when the hash is valid
 //}
 nonce--; // Decrement once for the last increment in the loop above
 return new Block(nextIndex, previousBlock.Hash, nextTimestamp, blockData, nextHash, Difficulty, nonce);
 }
 // Instead of defining the hash computation once here, consider injecting an IHashCalculator { string ComputeHash(Block block); } into the BlockCain constructor
 // It will make it possible for you to easily change the hash algorithm
 public string CalculateBlockHash(int index, string previousHash, DateTime timestamp, string data, int nonce)
 {
 //var sb = new StringBuilder();
 using (var hash = SHA256.Create())
 {
 // Instead of this...:
 //var value = index +
 // previousHash +
 // timestamp.ToString(CultureInfo.InvariantCulture.DateTimeFormat.FullDateTimePattern) +
 // data +
 // nonce;
 //var result = hash.ComputeHash(Encoding.UTF8.GetBytes(value));
 // ... I would do something like this:
 byte[] buffer =
 BitConverter
 .GetBytes(timestamp.Ticks)
 .Concat(Encoding.UTF8.GetBytes(data))
 .Concat(Encoding.Unicode.GetBytes(previousHash))
 .Concat(BitConverter.GetBytes(nonce))
 .ToArray();
 var result = hash.ComputeHash(buffer);
 // This can be done...
 //foreach (var b in result)
 // //sb.Append(b.ToString("x2")); // Make an extension for bytes too to keep it up to int
 // sb.Append(b.ToHex());
 // ... this way:
 return string.Join("", result.Select(b => b.ToHex()));
 }
 //return sb.ToString();
 }
 public string CalculateBlockHash(Block block)
 {
 return CalculateBlockHash(block.Index, block.PreviousHash, block.Timestamp, block.Data, block.Nonce);
 }
 private bool ChainIsValid(IReadOnlyList<Block> chain)
 {
 if (!chain[0].Equals(GenesisBlock))
 {
 return false;
 }
 for (var i = 1; i < chain.Count; i++)
 {
 if (!BlockIsValid(chain[i], chain[i - 1]))
 {
 return false;
 }
 }
 return true;
 }
 private bool BlockIsValid(Block newBlock, Block previousBlock)
 {
 if (previousBlock.Index + 1 != newBlock.Index)
 {
 return false;
 }
 if (previousBlock.Hash != newBlock.PreviousHash)
 {
 return false;
 }
 return CalculateBlockHash(newBlock) == newBlock.Hash;
 }
 private static bool HashIsValid(string hash, int difficulty)
 {
 // This can be done nicer
 //var prefix = string.Concat(Enumerable.Repeat('0', difficulty));
 var prefix = new string('0', difficulty);
 // Check the hash for null allowing this method to be used more smoothly
 return hash != null && hash.StartsWith(prefix);
 }
 private Block GetLatestBlock()
 {
 return Chain.Last();
 }
 }

Below find my review of the F# code:

open System
open System.Security.Cryptography
open System.Globalization
open System.Text
open System.Collections
open System.Collections.Generic
open System.Linq
// To create an extension function do this:
type Int32 with
 member this.toHex() = this.ToString("x")
type Byte with 
 member this.toHex() = this.ToString("x2")
type Block(index: int, previousHash: string, timestamp: DateTime, data: string, blockHash: string, difficulty: int, nonce: int) =
 member val Index = index // As for the C# version, Index is IMO redundant and potentially a source for inconsistency
 member val PreviousHash = previousHash
 member val Timestamp = timestamp
 member val Data = data
 member val Hash = blockHash
 member val Difficulty = difficulty // I don't see the necessity for the Difficulty as a member on the block because it is invariant at the Block level?
 member val Nonce = nonce
 override this.Equals(obj) =
 match obj with
 | :? Block as b -> (index, previousHash, timestamp, data, blockHash) = (b.Index, b.PreviousHash, b.Timestamp, b.Data, b.Hash)
 | _ -> false
 // I think this:...
 //override this.GetHashCode() =
 // let mutable hashCode = index
 // hashCode <- (hashCode * 397) ^^^ (if previousHash <> null then previousHash.GetHashCode() else 0)
 // hashCode <- (hashCode * 397) ^^^ timestamp.GetHashCode();
 // hashCode <- (hashCode * 397) ^^^ (if data <> null then data.GetHashCode() else 0)
 // hashCode <- (hashCode * 397) ^^^ (if hash <> null then hash.GetHashCode() else 0)
 // hashCode
 // ... can be simplified to this:
 override this.GetHashCode() =
 [
 index; 
 (if previousHash <> null then previousHash.GetHashCode() else 0); // This should never be null.
 timestamp.GetHashCode(); 
 (if data <> null then data.GetHashCode() else 0); // I wonder if data will ever be null? Should it be possible to create a block without content?
 (if blockHash <> null then blockHash.GetHashCode() else 0) // This should never be null.
 ]
 |> List.fold (fun acc n -> (acc * 397) ^^^ n) 0
 // If you will use hash in FSharp.Core.Operators it is more readable and produces the same hash
 // So my guess is that it uses the same hash functions as you do - it's just a nice convenience function
 //[
 // hash index; 
 // hash previousHash; 
 // hash timestamp; 
 // hash data; 
 // hash blockHash
 //]
 //|> List.fold (fun acc n -> (acc * 397) ^^^ n) 0
// I think I would call it BlockCain because it's a concatenation of two words
type Blockchain() =
 // You can use a Sytem.Collections.Generics.List instead of an array - it's more flexible but of cause more .NET-ish than functional
 let mutable chain = new List<Block>([ Blockchain.GenesisBlock ])
 // When implementing IEnumerable<Block> (see below) this should not be exposed as public
 //member private this.Chain 
 // with get() = chain
 // and set(value) = chain <- value
 member val Difficulty = 1
 // There is no reason to let this be part of the instance - it should be the same for all instances
 static member GenesisBlock = 
 new Block(0, "0", new DateTime(2000, 1, 1), "Genesis block", 
 "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7", 1, 0) // The Difficulty argument is a "magic" number, which you may forget to synchronize with that of Blockchain - consider avoiding completely
 member this.ReplaceChain(newChain: List<Block>) =
 if newChain.Count > chain.Count && this.ChainIsValid newChain then 
 chain <- new List<Block>(newChain) // Make a copy instead of a reference
 // I've made all the block generation functions private, because they should only be called once per new added block or at least called in a private context
 member private this.GenerateNextBlock(blockData) =
 let previousBlock: Block = this.GetLatestBlock()
 let currentIndex = previousBlock.Index + 1 // currentIndex is better than nextIndex because you actually handle the current block
 let currentTimestamp = DateTime.Now // current for the same reason as above.
 let rec generateBlock nonce =
 //let hexNonce = Int32.toHex(nonce) // Keep nonce as int
 // currentHash is better than nextHash
 let currentHash = this.CalculateBlockHash(currentIndex, previousBlock.Hash, currentTimestamp, blockData, nonce)
 match this.HashIsValid(currentHash, this.Difficulty) with
 | true -> new Block(currentIndex, previousBlock.Hash, currentTimestamp, blockData, currentHash, this.Difficulty, nonce - 1) // last recursion makes nonce one too large
 | false -> generateBlock (nonce + 1)
 generateBlock 0
 // Again inject the hash calculation as an interface or function 
 member private this.CalculateBlockHash((index: int), previousHash, (timestamp: DateTime), data, nonce) =
 use algorithm = SHA256.Create() // algorithm is a better name than hash
 [index.ToString(); previousHash; timestamp.ToString(CultureInfo.InvariantCulture.DateTimeFormat.FullDateTimePattern); data; nonce.toHex()]
 |> String.Concat
 |> Encoding.UTF8.GetBytes
 |> algorithm.ComputeHash
 //|> Encoding.UTF8.GetString // I don't understand this?
 //|> (+) "x2" // I don't understand this?
 |> fun bytes -> String.Join("", bytes.Select(fun b -> b.toHex()))
 member private this.CalculateBlockHash(block: Block) =
 this.CalculateBlockHash(block.Index, block.PreviousHash, block.Timestamp, block.Data, block.Nonce)
 member private this.ChainIsValid(chain) =
 match chain.[0].Equals Blockchain.GenesisBlock with // Changed GenesisBlock from instance to static
 | true -> chain |> Seq.pairwise |> Seq.forall this.BlockIsValid // The lambda wrapper is not necessary here: (fun (a, b) -> this.BlockIsValid(a, b))
 | false -> false
 member private this.BlockIsValid(previousBlock, newBlock) = // I've swapped previousBlock and newBlock, because that is the order in which the are passed to the function.
 if previousBlock.Index + 1 <> newBlock.Index then
 false
 else if previousBlock.Hash <> newBlock.PreviousHash then
 false
 else
 this.CalculateBlockHash newBlock = newBlock.Hash
 member private this.HashIsValid(hash, difficulty) =
 let prefix = String('0', difficulty) // (Seq.replicate difficulty '0') |> String.Concat
 hash.StartsWith(prefix)
 member private this.GetLatestBlock() = chain.Last()
 // Add some new data, create a new Block and add it to the chain
 member public this.AddData(data:string) = 
 if data = null then raise (ArgumentNullException("data"))
 let block = this.GenerateNextBlock(data)
 this.Add(block)
 member public this.Add(block) = chain.Add(block)
 // Implementation of IEnumerable<Block> instead of exposing the chain it self
 interface IEnumerable<Block> with
 member this.GetEnumerator() = (chain :> IEnumerable<Block>).GetEnumerator()
 // Implementation of IEnumerable
 interface IEnumerable with 
 member this.GetEnumerator() = (chain :> IEnumerable).GetEnumerator()
answered Sep 24, 2018 at 20:15
\$\endgroup\$
9
  • \$\begingroup\$ Thank you for the review. I skimmed through and your comments look very constructive. Please take these suggestions into account when reviewing the F# version. \$\endgroup\$ Commented Sep 24, 2018 at 21:04
  • 1
    \$\begingroup\$ ReplaceChain is needed because the blockchain is distributed, meaning that users hold copies of the blockchain and they may add new blocks to it. Different users may add different blocks at the same index leading to multiple "versions" of the blockchain existing at the same time. In this case the longest chain wins. \$\endgroup\$ Commented Sep 24, 2018 at 21:18
  • \$\begingroup\$ @Kapol: About ReplaceChain: Have you considered all concurrency situations properly? What if the incoming list is of same size but with the last block different due to an overlapping transaction? I can't figure out how that should be handled. Further: If you don't accept the newChain the client would probably be glad to know, or else the network of servers (blockchains) potentially can be inconsistent without no one to know. \$\endgroup\$ Commented Sep 25, 2018 at 4:49
  • 1
    \$\begingroup\$ I like these comments I don't understand this? ;-D could you also explain in more detail the changes you've made by giving a reason for them? Currently it's I've changed this to that but I'm missing the why like: it's shorter, faster, easier to test and whatnot ;-) \$\endgroup\$ Commented Sep 26, 2018 at 7:22
  • 1
    \$\begingroup\$ Hi @HenrikHansen. I posted a new question here if you're interested in reviewing :-) \$\endgroup\$ Commented Nov 17, 2018 at 19:09

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.