Snippets

Tuomas Hietanen Using NBitcoin to create private BlockChain with F# (FSharp).

Created by Tuomas Hietanen
#if INTERACTIVE
#I "./../packages/NBitcoin/lib/net45/"
#I "./../packages/Newtonsoft.Json/lib/net45"
#r "NBitcoin.dll"
#r "Newtonsoft.Json.dll"
#else
module BlockChain
#endif

open System
open NBitcoin
open NBitcoin.Protocol

// -------------- GENERAL BLOCKCHAIN NETWORK SETTINGS -------------- //

let network = 
    // Select your network
    // Network.Main
    // Network.Test

    // Or create your own:
        let builder = NetworkBuilder()
        builder.CopyFrom Network.Main
        let genesis = Network.Main.GetGenesis()
        //builder.SetGenesis( {genesis.Header.UpdateTime with Ti }
        builder.SetName("MyBlockChain").BuildAndRegister()

/// Generate a new wallet private key for a new user as byte array
let getNewPrivateKey() = Key().ToBytes()

/// Create BitcoinSecret from byte array
let getSecret(bytes:byte[]) = BitcoinSecret(Key(bytes), network)


// --------- CUSTOM BLOCKCHAIN AND ITS COMPLEXITY --------- //

/// Complexity of valid mining. Genesis is having 10.
/// But this comes down to how much resources you are willing to
/// spend to validate blockchain. Note: Some part of blockchain
/// security comes from the fact that valid hashes are not so easy
/// to calculate, making the generation of alternative valid blockchain slow.
let leadingZeros = "0000"

/// Validation of blockchain hashes.
type BlockChainCheck =
/// No validation
| NoWork
/// Validation of leadingZeros amount only
| EasyWork
/// Default Bitcoin level validation
| CorrectWork

/// The normal bitcoin validation is leading zeros in hashcodes.
/// Normal mining taking a lot of resources. So this is more
/// lightweight option to mining (with less zeros).
type ChainedBlock with
  /// Re-implementation of Validate()
  member cb.ValidateEasy(network:Network) =
    let genesis = cb.Height = 0
    if (not genesis) && cb.Previous = null then false
    else
    let heightCorrect = genesis || cb.Height = cb.Previous.Height + 1
    let genesisCorrect = 
        (not genesis) || cb.HashBlock = network.GetGenesis().GetHash()
    let hashPrevCorrect = 
        genesis || cb.Header.HashPrevBlock = cb.Previous.HashBlock
    let hashCorrect = cb.HashBlock = cb.Header.GetHash()
    let workCorrect = 
        genesis || cb.Header.GetHash().ToString().StartsWith leadingZeros
    
    heightCorrect && genesisCorrect && hashPrevCorrect
    && hashCorrect && workCorrect
   
type ChainBase with
  /// Re-implementation of Validate()
  member cb.ValidateEasy(network:Network, ?fullChain) =
    let tip = cb.Tip
    if tip = null then false
    else
    match fullChain with
    | None | Some true ->
        let anyFails = 
            tip.EnumerateToGenesis() 
            |> Seq.exists(fun block -> not(block.ValidateEasy network))
        not anyFails
    | Some false ->
        tip.ValidateEasy network


/// This will mine the correct hash. 
/// Performance is not optimized: if you would really want to do
/// mining, you would probably want to use some more parallel algorithm.
let ``mine to correct`` checkType (chain:ConcurrentChain) (block:Block) =
    let validation = 
        match checkType with
        | NoWork -> fun (cb:ChainedBlock) -> true
        | EasyWork -> fun cb -> cb.ValidateEasy network
        | CorrectWork -> fun cb -> cb.Validate network 

    let rec mine nonce =
        block.Header.Nonce <- nonce
        let headerBlock = 
            ChainedBlock(block.Header, block.Header.GetHash(), 
                         chain.GetBlock(block.Header.HashPrevBlock))
        if validation headerBlock then ()
        else mine (nonce+1u)
    mine 0u

/// Attach a block to chain
let ``attach to chain`` (checkType:BlockChainCheck) (chain:ConcurrentChain) (block:Block) =
    let header = block.Header
    header.HashPrevBlock <- chain.Tip.HashBlock
    header.Bits <- 
        //header.GetWorkRequired(network, chain.Tip)
        chain.Tip.GetWorkRequired(network)
    header.BlockTime <- DateTimeOffset.UtcNow
    header.Nonce <- RandomUtils.GetUInt32()
    //header.UpdateTime(network, chain.Tip)
    block.UpdateMerkleRoot()
    ``mine to correct`` checkType chain block
    chain.SetTip header |> ignore
    chain.GetBlock(header.GetHash())


/// Attach a bunch of transactions to a new block
let ``to new block`` txs =
    let block = Block()
    txs |> Seq.iter (block.AddTransaction >> ignore)
    block.UpdateMerkleRoot()
    block
    
// --------- TRANSACTIONS --------- //

/// No fees on our custom block-chain
/// Consifer adding some fee when you want users to do the mining.
let noFees = Unchecked.defaultof<FeeRate>
let txBuilder() = 
    let b = TransactionBuilder()
    b.StandardTransactionPolicy.MinRelayTxFee <- FeeRate.Zero
    b

/// Coinbase transaction is a transaction to generate money to our system.
let ``give money`` (toUser:BitcoinSecret) (sum:int) =
    let money = Money sum
    let coin = 
        Coin( // Coins for sums, ColoredCoins for assets
              // This is a coinbase / generation transaction, so prev hash is zero:
            OutPoint(), TxOut(money, toUser.PubKey.ScriptPubKey)
        )
    let builder = txBuilder()
    let tx =
        builder
            //.IssueAsset(toUser, OpenAsset.AssetMoney(asset.AssetId, quantity))
            .AddCoins([| (coin :> ICoin) |])
            .AddKeys(toUser)
            .Send(toUser, money)
            .SetChange(toUser.GetAddress())
            .BuildTransaction(true)

    if not tx.IsCoinBase then 
        failwith "Was not a coinbase transaction"

    let ok, errs = builder.Verify(tx, noFees)
    match ok with
    | true -> tx
    | false -> failwith(String.Join(",", errs))

/// Normal transaction to transfer money / asset from user to next user. 
/// Needs outpoint (coins) from previous transaction to make a block chain.
let ``spend money`` (coins:Coin list) (fromUser:BitcoinSecret) (toUser:BitcoinPubKeyAddress) (sum:int) =
    let money = Money sum
    let fees = Money.Zero
    let coinsArr = 
        coins 
        |> List.filter(fun c -> c.TxOut.IsTo fromUser)
        |> List.map(fun c -> c :> ICoin) |> List.toArray
    let builder = txBuilder()
    let tx =
        builder
            .AddCoins(coinsArr)
            .AddKeys(fromUser)
            .Send(toUser, (money - fees))
            .SendFees(fees)
            .SetChange(fromUser.GetAddress())
            .BuildTransaction(true)
    let ok, errs = builder.Verify(tx, noFees)
    match ok with
    | true -> tx
    | false -> failwith(String.Join(",", errs))

// --------- SOME HELPER FUNCTIONS --------- //

/// Create a new user
let makeUser() = 
    let user = BitcoinSecret(Key(), network)
    Console.WriteLine "Store users private key to a cold dry place and never show it to anyone:"
    network |> user.PrivateKey.GetWif |> Console.WriteLine
    user

/// Get users coins from transaction
let ``fetch coins of`` dest (tx:Transaction) =
     tx.Outputs 
     |> Seq.mapi(fun idx op -> idx,op)
     |> Seq.filter(fun (_,op) -> op.Value > Money.Zero && op.IsTo(dest)) 
     |> Seq.map(fun (idx,op) ->
         Coin(OutPoint(tx, idx), op)
     ) |> Seq.toList

let ``save tracker`` filename (tracker:NBitcoin.SPV.Tracker) =
    let filterBefore = tracker.CreateBloomFilter(0.005)
    use fileStream = System.IO.File.Create filename
    tracker.Save fileStream

let ``load tracker`` filename =
    let tracker = 
        filename |> System.IO.File.OpenRead
        |> NBitcoin.SPV.Tracker.Load
    if not(tracker.Validate()) then failwith "Not valid tracker"
    else tracker

let ``save chain`` filename (chain:ConcurrentChain) =
    use fileStream = System.IO.File.Create filename
    chain.WriteTo (BitcoinStream(fileStream, true))

let ``load chain`` filename =
    let chain = new ConcurrentChain(network)
    filename |> System.IO.File.ReadAllBytes |> chain.Load
    // if not(chain.Validate network) then failwith "Not valid"
    if not(chain.ValidateEasy network) then failwith "Not valid chain"
    else chain

// --------- TESTING --------- //
let testing() =

    // A block chain, and a tracker
    let chain = new ConcurrentChain(network)
    let tracker = NBitcoin.SPV.Tracker()
    
    // make some users
    let thomas = makeUser()
    let antero = makeUser()
    let john = makeUser()

    tracker.Add thomas
    tracker.Add antero
    tracker.Add john

    // Make Block 1 with transactions and add it to chain
    let coinbase1 = ``give money`` thomas 1000

    let coins1 = coinbase1 |> ``fetch coins of`` thomas
    let transfer1 = 
        ``spend money`` coins1 thomas (antero.GetAddress()) 500

    let coins2 = transfer1 |> ``fetch coins of`` thomas
    let transfer2 = ``spend money`` coins2 thomas (john.GetAddress()) 300

    let block1 = 
        [coinbase1; transfer1; transfer2] 
        |> ``to new block`` 
    
    let chained1 = 
        block1 |> ``attach to chain`` BlockChainCheck.EasyWork chain

    block1.Transactions |> Seq.iter (fun tx -> 
        tracker.NotifyTransaction(tx, chained1, block1) |> ignore)
        
    // Check that chain and the tracker are valid:
    let ``chain ok`` = chain.ValidateEasy network
    let ``tracker ok`` = tracker.Validate()
    let transactions1 = tracker.GetWalletTransactions chain
    //transactions1.GetSpendableCoins()
    //transactions1.Summary.Confirmed
    // Thomas: 200, Antero: 500, John: 300

    // Make Block 2 with transactions and add it to chain

    let coinbase2 = ``give money`` thomas 100

    let coins3 = transfer1 |> ``fetch coins of`` antero
    let transfer3 = ``spend money`` coins3 antero (john.GetAddress()) 500

    let coins4 = 
        (transfer2 |> ``fetch coins of`` thomas) @
        (coinbase2 |> ``fetch coins of`` thomas)

    let transfer4 = 
        ``spend money`` coins4 thomas (john.GetAddress()) 250

    //let coins5 = transfer4 |> ``fetch coins of`` thomas 
    // This is just an initial example / tech-demo.
    let block2 = 
        [coinbase2; transfer3; transfer4] 
        |> ``to new block`` 

    let chained2 = 
        block2 |> ``attach to chain`` BlockChainCheck.EasyWork chain

    block2.Transactions |> Seq.iter (fun tx -> 
        tracker.NotifyTransaction(tx, chained2, block2) |> ignore)
    
    // Check the validity of the chain and the tracker
    let ``chain still ok`` = chain.ValidateEasy network
    let ``tracker still ok`` = tracker.Validate()
    let transactions2 = tracker.GetWalletTransactions chain
    let ``available coins`` = transactions2.GetSpendableCoins() |> Seq.toList
    // transactions2.Count
    // transactions2 |> Seq.map(fun b -> b.Balance, b.Transaction.GetHash())
    // |> Seq.toArray
    // ``available coins`` |> List.map(fun v -> v.Amount) |> List.sum
    // ``available coins``
    // transactions2.Summary.Confirmed
    // Thomas: 50, John: 250+500+300

    ``save tracker`` @"c:\tracker.dat" tracker
    ``save chain`` @"c:\chain.dat" chain

    // let tracker2 = ``load tracker`` @"c:\tracker.dat"
    // let chain2 = ``load chain`` @"c:\chain.dat"

    // Some resources:
    // Article: https://www.codeproject.com/articles/835098/nbitcoin-build-them-all
    // A video: https://www.youtube.com/watch?v=_160oMzblY8

Comments (3)

  1. Zuzu Green

    If we talk about Bitcoin, then it is, of course, the most popular and expensive cryptocurrency in the world. I prefer to invest my money in Bitcoins and usually go here https://changelly.com/buy/btc to buy the amount of Bitcoins I need. I like that in this service all transactions happen very quickly and safely. So if you want to buy bitcoins, then visit the site.

  2. Charles Stone

    Great resource for using NBitcoin to create private keys and addresses! The step-by-step guide is clear and well-detailed, making it easy for both beginners and experienced developers to follow. The inclusion of code snippets and explanations enhances the learning experience. Thanks for sharing this valuable information!

  3. John Skinner

    I'm just starting to learn about crypto business. In the future, I plan to make good money on cryptodeposits, earning interest. And this is not a joke. The hexn here platform provides such an opportunity. There are excellent security measures in place, which inspire confidence, and in general, working with the site is smooth and efficient. I can also exchange cryptocurrency here. Therefore, hexn has become my main platform for cryptocurrency transactions.

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.