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?
\$\endgroup\$
  • 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\$ – RubberDuck Sep 23 '18 at 22:22
  • \$\begingroup\$ @RubberDuck Here you are :) \$\endgroup\$ – Kapol Sep 23 '18 at 22:32
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()
\$\endgroup\$
  • \$\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\$ – Kapol Sep 24 '18 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\$ – Kapol Sep 24 '18 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\$ – Henrik Hansen Sep 25 '18 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\$ – t3chb0t Sep 26 '18 at 7:22
  • 1
    \$\begingroup\$ Hi @HenrikHansen. I posted a new question here if you're interested in reviewing :-) \$\endgroup\$ – Kapol Nov 17 '18 at 19:09

Your Answer

By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.