Database

The database module handles the persistance of the blockchain, unspent transaction outputs and peer addresses.

  • The largest amount of data is in the blocks of the blockchain. It represents nearly 30 GB at this time (2015) and will continue growing. They are written as binary files so that they can be conveniently trimmed.
  • The unspent transaction outputs are the stashes of bitcoins that haven't been used by a transaction yet. They are the application main data. Once blocks are analyzed, the application doesn't need them anymore except during a reorganization of the blockchain. Instead, the application refers to the UTXO which is a much smaller set of data (~1GB). The UTXO are kept in a LevelDB database for better performance.
  • Finally, the metadata is in a SQLite database. The relational database allows advanced querying that LevelDB doesn't provide and the amount of data isn't an issue here.

Backup

To make a backup, the easiest way is to shutdown the application and copy the content of the utxo folder which has the UTXO database and the bitcoin.db file that has the associated metadata. The blocks can be saved too but they are not vital.

1: 
2: 
let connectionString = sprintf "Data Source=%s/bitcoin.db" baseDir
let dbLock = new obj() // Serialize all db access because of SQLite

DB Connections can't be shared between threads but they are coming from a pool and are cheap to establish. Using ADO.NET, I can write parametrized SQL statements. It's direct SQL and therefore not portable to other DB providers but it works well here. The database model isn't sophisticated enough to warrant an entity-relation library. I prefer to keep the database layer a straight get and put interface.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
let updateAddr(addr: AddrEntry) = 
    lock dbLock (fun () ->
        use connection = new SQLiteConnection(connectionString)
        connection.Open()
        let updateAddrQuery = new SQLiteCommand(@"insert or ignore into peerInfo(host, port, ts, user_agent, state, score) values(@host, @port, @ts, @user_agent, 0, 0);
            update peerInfo set ts = @ts where host = @host and port = @port", connection)
        updateAddrQuery.Parameters.Add("@host", DbType.String, 256) |> ignore
        updateAddrQuery.Parameters.Add("@port", DbType.Int32) |> ignore
        updateAddrQuery.Parameters.Add("@ts", DbType.DateTime) |> ignore
        updateAddrQuery.Parameters.Add("@user_agent", DbType.String, 256) |> ignore
        updateAddrQuery.Parameters.[0].Value <- addr.Address.EndPoint.Address.ToString()
        updateAddrQuery.Parameters.[1].Value <- addr.Address.EndPoint.Port
        let dts = (new Instant(int64(addr.Timestamp) * NodaConstants.TicksPerSecond)).ToDateTimeUtc()
        updateAddrQuery.Parameters.[2].Value <- dts
        updateAddrQuery.Parameters.[3].Value <- ""
        updateAddrQuery.ExecuteNonQuery() |> ignore
    )

Peers

The peerInfo table has the addresses of the peers that were advertised either through seed discovery or through peer to peer addr messages. I don't do much peer management. Typically, the quality of the information degrades over time since peers disconnect and reconnect freely. Therefore, I keep updating the table and the query that returns peers sorts them from the most recent to the least.

Peers have a state telling whether they are in use but they should also have a badness score. It's not done at the moment. The application will disconnect from badly behaved peers but without a score value and a ban period, nothing prevents a peer from reconnecting immediately. This is on the TODO list.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
let getPeers() =
    lock dbLock (fun () ->
        use connection = new SQLiteConnection(connectionString)
        connection.Open()
        use command = new SQLiteCommand("select host, port from peerInfo where state = 0 order by ts desc limit 1000", connection)
        use reader = command.ExecuteReader()
        [while reader.Read() do 
            let host = reader.GetString(0)
            let port = reader.GetInt32(1)
            let ip = IPAddress.Parse(host)
            let endpoint = new IPEndPoint(ip, port)
            yield endpoint
        ]
    )

let getPeer() =
    lock dbLock (fun () ->
        use connection = new SQLiteConnection(connectionString)
        connection.Open()
        use command = new SQLiteCommand("select host, port from peerInfo where state = 0 order by ts desc limit 1", connection)
        use reader = command.ExecuteReader()
        let peers = 
            [while reader.Read() do 
                let host = reader.GetString(0)
                let port = reader.GetInt32(1)
                let ip = IPAddress.Parse(host)
                let endpoint = new IPEndPoint(ip, port)
                yield endpoint
            ]
        peers |> Seq.tryPick Some
    )
(*
Drop peers that are older than a certain timestamp. Normally, 3h ago.
*)
let dropOldPeers dts = 
    use connection = new SQLiteConnection(connectionString)
    connection.Open()
    use command = new SQLiteCommand("delete from peerInfo where ts <= @ts", connection)
    command.Parameters.Add("@ts", DbType.DateTime) |> ignore
    command.Parameters.[0].Value <- dts
    command.ExecuteNonQuery() |> ignore

(*
At startup, mark all the peers as disconnected
*)
let resetState() =
    use connection = new SQLiteConnection(connectionString)
    connection.Open()
    use command = new SQLiteCommand("update peerInfo set state = 0 where state > 0", connection)
    command.ExecuteNonQuery() |> ignore

let updateState(peer: IPEndPoint, state: int) =
    lock dbLock (fun () ->
        use connection = new SQLiteConnection(connectionString)
        connection.Open()
        let query = new SQLiteCommand("update peerInfo set state = ? where host = ? and port = ?", connection)
        query.Parameters.Add("state", DbType.Int32) |> ignore
        query.Parameters.Add("host", DbType.String, 256) |> ignore
        query.Parameters.Add("port", DbType.Int32) |> ignore
        query.Parameters.[0].Value <- state
        query.Parameters.[1].Value <- peer.Address.ToString()
        query.Parameters.[2].Value <- peer.Port
        query.ExecuteNonQuery() |> ignore
    )

Headers

I keep all the information that I parse from a header. The tx-count is not populated in the Headers message and will be zero. The column is there for later. The hash isn't part of the header but is in fact the actual hash of the header itself. It is calculated during parsing and then stored. Finally, the height is also determined by looking up the previous header. If the previous header is not present in the database, then the header is skipped and not stored at all. When the missing header comes and the block connects, I'll get the header again.

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
113: 
114: 
115: 
116: 
let headerConnection = new SQLiteConnection(connectionString)
headerConnection.Open()
let command = new SQLiteCommand(@"insert or replace into header(hash, height, version, prev_hash, next_hash, merkle_root, ts, bits, nonce, tx_count, is_main, state) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", headerConnection)
command.Parameters.Add("hash", DbType.Binary, 32) |> ignore
command.Parameters.Add("height", DbType.Int32) |> ignore
command.Parameters.Add("version", DbType.Int32) |> ignore
command.Parameters.Add("prev_hash", DbType.Binary, 32) |> ignore
command.Parameters.Add("next_hash", DbType.Binary, 32) |> ignore
command.Parameters.Add("merkle_root", DbType.Binary, 32) |> ignore
command.Parameters.Add("ts", DbType.Int32) |> ignore
command.Parameters.Add("bits", DbType.Int32) |> ignore
command.Parameters.Add("nonce", DbType.Int32) |> ignore
command.Parameters.Add("tx_count", DbType.Int32) |> ignore
command.Parameters.Add("state", DbType.Int32) |> ignore
command.Parameters.Add("is_main", DbType.Boolean) |> ignore

let readTip(): byte[] =
    lock dbLock (fun () ->
        use command = new SQLiteCommand("select best from chainstate where id = 0", headerConnection)
        use reader = command.ExecuteReader()
        [while reader.Read() do 
            let tip = Array.zeroCreate 32
            reader.GetBytes(0, 0L, tip, 0, 32) |> ignore
            yield tip
        ] |> List.head
    )

let writeTip(tip: byte[]) = 
    lock dbLock (fun () ->
        use command = new SQLiteCommand("update chainstate set best = ? where id = 0", headerConnection)
        command.Parameters.Add("best", DbType.Binary, 32) |> ignore
        command.Parameters.[0].Value <- tip
        command.ExecuteNonQuery() |> ignore
    )

let getHeader (reader: SQLiteDataReader) =
    lock dbLock (fun () ->
        [while reader.Read() do 
            let hash = Array.zeroCreate 32
            reader.GetBytes(0, 0L, hash, 0, 32) |> ignore
            let height = reader.GetInt32(1)
            let version = reader.GetInt32(2)
            let prev_hash = Array.zeroCreate 32
            reader.GetBytes(3, 0L, prev_hash, 0, 32) |> ignore
            let next_hash = Array.zeroCreate 32
            reader.GetBytes(4, 0L, next_hash, 0, 32) |> ignore
            let merkle_root = Array.zeroCreate 32
            reader.GetBytes(5, 0L, merkle_root, 0, 32) |> ignore
            let ts = reader.GetInt32(6)
            let bits = reader.GetInt32(7)
            let nonce = reader.GetInt32(8)
            let tx_count = reader.GetInt32(9)
            let is_main = reader.GetBoolean(10)
            let bh = new BlockHeader(hash, version, prev_hash, merkle_root, uint32 ts, bits, nonce, tx_count)
            bh.Height <- height
            bh.NextHash <- next_hash
            bh.IsMain <- is_main
            yield bh
        ]
    )

let genesisHeader = 
    lock dbLock (fun () ->
        use command = new SQLiteCommand("select hash, height, version, prev_hash, next_hash, merkle_root, ts, bits, nonce, tx_count, is_main from header where height = 0", headerConnection)
        use reader = command.ExecuteReader()
        let res = getHeader reader
        res.Head
        )

let readHeader(hash: byte[]): BlockHeader = 
    lock dbLock (fun () ->
        use command = new SQLiteCommand("select hash, height, version, prev_hash, next_hash, merkle_root, ts, bits, nonce, tx_count, is_main from header where hash = ?", headerConnection)
        command.Parameters.Add("hash", DbType.Binary, 32) |> ignore
        command.Parameters.[0].Value <- hash
        use reader = command.ExecuteReader()
        let res = getHeader reader
        if res.Length <> 0 then res.[0] else BlockHeader.Zero
    )

let getHeaderByHeight (height: int): BlockHeader =
    lock dbLock (fun () ->
        use command = new SQLiteCommand("select hash, height, version, prev_hash, next_hash, merkle_root, ts, bits, nonce, tx_count, is_main from header where height = ? and is_main = 1", headerConnection)
        command.Parameters.Add("height", DbType.Int32) |> ignore
        command.Parameters.[0].Value <- height
        use reader = command.ExecuteReader()
        let res = getHeader reader
        if res.Length <> 0 then res.[0] else BlockHeader.Zero
    )

let getNextHeader(hash: byte[]): BlockHeader = 
    lock dbLock (fun () ->
        use command = new SQLiteCommand("select hash, height, version, prev_hash, next_hash, merkle_root, ts, bits, nonce, tx_count, is_main from header where prev_hash = ?", headerConnection)
        command.Parameters.Add("prev_hash", DbType.Binary, 32) |> ignore
        command.Parameters.[0].Value <- hash
        use reader = command.ExecuteReader()
        let res = getHeader reader
        if res.Length <> 0 then res.[0] else BlockHeader.Zero
    )

let writeHeaders(header: BlockHeader) = 
    lock dbLock (fun () ->
        command.Parameters.[0].Value <- header.Hash
        command.Parameters.[1].Value <- header.Height
        command.Parameters.[2].Value <- header.Version
        command.Parameters.[3].Value <- header.PrevHash
        command.Parameters.[4].Value <- header.NextHash
        command.Parameters.[5].Value <- header.MerkleRoot
        command.Parameters.[6].Value <- header.Timestamp
        command.Parameters.[7].Value <- header.Bits
        command.Parameters.[8].Value <- header.Nonce
        command.Parameters.[9].Value <- header.TxCount
        command.Parameters.[10].Value <- header.IsMain
        command.Parameters.[11].Value <- 0

        command.ExecuteNonQuery() |> ignore
    )

Bloom Filter

A Bloom Filter is a probabilistic filter that has a configurable probability of false positive and no false negative. Public keys that match the addresses that I own are inserted into the Bloom Filter and checked when I process transactions. It allows me to quickly reject transactions that do not belong to my wallets.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
type BloomFilter(filter: byte[], cHashes: int, nTweak: int) =
    let bits = new BitArray(filter)
    let hashers = seq {
        for i in 0..cHashes-1 do
            yield MurmurHash.Create32(uint32(i*0xFBA4C795+nTweak)) } |> Seq.toArray

    let add (v: byte[]) =
        for hasher in hashers do
            let hash = hasher.ComputeHash v
            let bucket = BitConverter.ToUInt32(hash, 0) % (uint32 filter.Length*8u)
            bits.Set(int bucket, true)

    let check (v: byte[]) =
        (hashers |> Seq.map (fun hasher ->
            let hash = hasher.ComputeHash v
            let h = BitConverter.ToUInt32(hash, 0)
            let bucket = h % (uint32 filter.Length*8u)
            bits.Get(int bucket)
            )).All(fun b ->  b)

    new(N: int, P: float, cHashes: int, nTweak: int) = 
        let size = int(min (-1.0/log 2.0**2.0*(float N)*log P) 36000.0)
        new BloomFilter(Array.zeroCreate size, cHashes, nTweak)
    member x.Add v = add v
    member x.Check v = check v

type AddressEntry = {
    Id: int
    Account: int
    Hash: byte[]
    Address: string
    }

Wallet

A wallet is simply a table that has a collection of hashes which match either a p2pkh script or p2sh script. Technically there is a chance that there is a collision between these two types of scripts but the odds are extremely small This class loads every key from the table in memory and builds a Bloom filter. Every add/del UTXO is looked up in the walet and should be done as quickly as possible. Since the chances of having a match is rather small, the Bloom filter reduces the need to do a detailed search through the keys.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
type Wallet() =
    let bloomFilter = new BloomFilter(settings.BloomFilterSize, 0.00001, 10, 4)

    let loadData() =
        lock dbLock (fun () ->
            use command = new SQLiteCommand("select id, account, hash, address from keys", headerConnection)
            use reader = command.ExecuteReader()
            [while reader.Read() do 
                let id = reader.GetInt32(0)
                let account  = reader.GetInt32(1)
                let hash = Array.zeroCreate 20
                reader.GetBytes(2, 0L, hash, 0, 20) |> ignore
                let address = reader.GetString(3)
                yield (hash, { Id = id; Account = account; Hash = hash; Address = address })
            ] |> Map.ofSeq
        )
    let addresses = loadData()
    let get (hash: byte[]) =
        maybe {
            do! Option.conditional (bloomFilter.Check hash) ()
            return! addresses |> Map.tryFind hash
        }
    do
        addresses |> Map.iter (fun k _ -> bloomFilter.Add k)
    member x.TryGet (hash: byte[]) = get hash

let wallet = new Wallet()

UTXO accessor

The UTXO accessor interface is the abstract interface over the UTXO data store. The primary store is the levelDB database where the main chain gets synced up to. However during acceptance of a new block(s), the blockchain validator works with a temporary set of UTXO. Being a low level store, LevelDB doesn't have support for transactions and therefore it has to be done at the application layer. The technique I use is a simple versioning in-memory table. Regardless of whether it is talking directly to the on-disk db or through an overlay, the application code uses the same interface.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
type IUTXOAccessor =
    inherit IDisposable
    abstract DeleteUTXO: OutPoint -> unit
    abstract AddUTXO: OutPoint * UTXO -> unit
    abstract GetUTXO: OutPoint -> Option<UTXO> // Try to get a given Outpoint
    abstract GetCount: byte[] -> int // Counts how many UTXO exists for a given transaction hash

type TxTableAccessor() =
    let connection = new SQLiteConnection(connectionString)
    let insertTx = new SQLiteCommand("insert or ignore into tx(hash, vout, key_hash, amount) values (?, ?, ?, ?)", connection)
    let deleteTx = new SQLiteCommand("delete from tx where hash=? and vout=?", connection)

    let addToTxTable (outpoint: OutPoint) (utxo: UTXO) =
        let script = utxo.TxOut.Script
        lock dbLock (fun () ->
            maybe {
                let! hash = Script.scriptToHash(script)
                let! addressEntry = wallet.TryGet(hash)
                insertTx.Parameters.[0].Value <- outpoint.Hash
                insertTx.Parameters.[1].Value <- outpoint.Index
                insertTx.Parameters.[2].Value <- hash
                insertTx.Parameters.[3].Value <- utxo.TxOut.Value
                insertTx.ExecuteNonQuery() |> ignore
                logger.InfoF "%s %d" addressEntry.Address utxo.TxOut.Value
            } |> ignore
            )

    let deleteFromTxTable (outpoint: OutPoint) =
        lock dbLock (fun () ->
            deleteTx.Parameters.[0].Value <- outpoint.Hash
            deleteTx.Parameters.[1].Value <- outpoint.Index
            deleteTx.ExecuteNonQuery() |> ignore
            )

    do
        connection.Open()
        insertTx.Parameters.Add("hash", DbType.Binary) |> ignore
        insertTx.Parameters.Add("vout", DbType.Int32) |> ignore
        insertTx.Parameters.Add("key_hash", DbType.Binary) |> ignore
        insertTx.Parameters.Add("amount", DbType.Int64) |> ignore
        deleteTx.Parameters.Add("hash", DbType.Binary) |> ignore
        deleteTx.Parameters.Add("vout", DbType.Int32) |> ignore

    interface IDisposable with
        override x.Dispose() = 
            insertTx.Dispose()
            deleteTx.Dispose()
            connection.Dispose()

    member x.Add (outpoint: OutPoint) (utxo: UTXO) = addToTxTable outpoint utxo
    member x.Delete (outpoint: OutPoint) = deleteFromTxTable outpoint

let txTableAccessor = new TxTableAccessor()

This wallet does not store the history of transactions but just the result (unspent outputs). So when a transaction spents an outpoint, it is deleted from the tx table. Another option would be to keep on adding records. I may change the implementation later. It slightly makes undoing a transaction more complicated. One must differenciate between undoing a transaction vs spending an output.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
let addIfInWallet (txTableAccessor: TxTableAccessor) (wallet: Wallet) (outpoint: OutPoint) (utxo: UTXO) =
    let script = utxo.TxOut.Script
    maybe {
        let! hash = Script.scriptToHash(script)
        let! addressEntry = wallet.TryGet(hash)
        txTableAccessor.Add outpoint utxo
    } |> ignore

let removeIfInWallet (txTableAccessor: TxTableAccessor) (wallet: Wallet) (outpoint: OutPoint) (utxo: UTXO) =
    let script = utxo.TxOut.Script
    maybe {
        let! hash = Script.scriptToHash(script)
        let! addressEntry = wallet.TryGet(hash)
        txTableAccessor.Delete outpoint
    } |> ignore

The LevelDB accessor converts keys & values into binary strings and uses the LevelDB-Sharp bridge to read or write to the database.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
70: 
71: 
72: 
73: 
74: 
75: 
76: 
77: 
type LevelDBUTXOAccessor(db: DB, wallet: Wallet, txTableAccessor: TxTableAccessor) =
    let ro = new ReadOptions()
    let wo = new WriteOptions()

    let deleteUTXO (outpoint: OutPoint) = 
        let k = outpoint.ToByteArray()
        maybe {
            let! v = Option.ofNull (db.Get(ro, k))
            let utxo = ParseByteArray v UTXO.Parse
            removeIfInWallet txTableAccessor wallet outpoint utxo
        } |> ignore
        db.Delete(wo, k)
    let addUTXO (outpoint: OutPoint) (utxo: UTXO) = 
        let k = outpoint.ToByteArray()
        let v = utxo.ToByteArray()
        addIfInWallet txTableAccessor wallet outpoint utxo
        db.Put(wo, k, v)
    let getUTXO (outpoint: OutPoint) =
        let k = outpoint.ToByteArray()
        let v = db.Get(ro, k)
        if v <> null 
        then Some(ParseByteArray v UTXO.Parse)
        else 
            None

    // Use the fact that the txhash is a prefix for the key
    // Seek into the database and iterate as long as the key has a prefix equal to the tx hash
    // It eliminates the need to keep an additional counter
    let getCount (txHash: byte[]) =
        let cursor = new Iterator(db, ro)
        cursor.Seek(txHash)
        let mutable count = 0
        let rec getCountInner (count: int): int = 
            if cursor.IsValid then
                let k = cursor.Key
                let hash = k.[0..txHash.Length-1] // first part of the key is the txhash
                if hash = txHash 
                then 
                    cursor.Next()
                    getCountInner (count+1)
                else count
            else count

        let count = getCountInner(0)
        count

    new() = 
        let options = new Options()
        options.CreateIfMissing <- true
        new LevelDBUTXOAccessor(DB.Open(options, sprintf "%s/utxo" baseDir), wallet, txTableAccessor)
        
    interface IUTXOAccessor with
        member x.DeleteUTXO(outpoint) = deleteUTXO outpoint
        member x.AddUTXO(outpoint, txOut) = addUTXO outpoint txOut
        member x.GetUTXO(outpoint) = getUTXO outpoint
        member x.GetCount(txHash) = getCount txHash
        member x.Dispose() = db.Dispose()

    member val Db = db with get

let levelDbAccessor = new LevelDBUTXOAccessor()
let utxoAccessor = levelDbAccessor :> IUTXOAccessor

let scanUTXO () =
    let creditToWallet = addIfInWallet txTableAccessor wallet 
    lock dbLock (fun () ->
        let ro = new ReadOptions()
        use cursor = new Iterator(levelDbAccessor.Db, ro)
        cursor.SeekToFirst()
        while cursor.IsValid do
            let k = cursor.Key
            let v = cursor.Value
            let outpoint = ParseByteArray k OutPoint.Parse
            let utxo = ParseByteArray v UTXO.Parse
            creditToWallet outpoint utxo
            cursor.Next()
        )

Format of the UNDO file. The transactions stored in blocks must be reverted if they end up being part of an orphaned block. However, a block does not have enough data to undo the effects on the UTXO db. A transaction deletes the input TXO and creates new UTXO. If it needs to be reverted, it is easy to delete the newly created UTXO but difficult to recreate the input TXO unless that data is stored as well.

When a block is processed and the UTXO db updated, I create an undo file that logs the changes applied to the db. It is kept in the same directory location as the block file and can be trimmed at the same time. Obviously deleting blocks and undo files removes the ability to reorganize the blockchain into a fork that is deeper than least recent block available.

1: 
2: 
3: 
4: 
5: 
6: 
type TxOperation = 
    | Add 
    | Delete 

type IUTXOWriter =
    abstract Write: TxOperation * OutPoint * UTXO -> unit

A few helper functions for validity checks. They appear early in the code because of the processUTXO function which goes through the transactions and calls the UTXO accessor. I take advantage of this traversal to do some basic checks.

1: 
2: 
3: 
4: 
let checkMoney (v: int64) = (v >= 0L && v < maxMoney) |> errorIfFalse "not in money range" |> Option.map(fun () -> v)
let checkCoinbaseMaturity (utxo: UTXO) (height: int) = (utxo.Height = 0 || height >= utxo.Height + coinbaseMaturity) |> errorIfFalse "coinbase has not matured" |> Option.map(fun () -> utxo)
let OP_RETURN = 106uy
let isRETURN (script: byte[]) = script.Length > 0 && script.[0] = OP_RETURN

This is the first time that I use the maybe computational expression. So it's maybe worth spending some time to talk about it. maybe is a builder for the monad Option type. Inside the maybe block, let! statements evaluate an expression to either Some or None. If the result is None, the rest is not evaluated and the maybe block returns None. It's syntaxic sugar for the monadic bind, map, etc. Sometimes I need to use these functions explicitly but when the maybe builder does the job, the code is easier to read. Even though it looks like a normal loop over tx inputs and outputs, if any of the checks fail, evaluation is short circuited and returns None. Exceptions could have worked but it is harder to control their propagation and monads keep the function pure (except at the db level of course).

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
let processUTXO (utxoAccessor: IUTXOAccessor) (utxoWriter: IUTXOWriter) (isCoinbase: bool) (height: int) (tx: Tx)  =
    maybe {
        let! totalIn = 
            tx.TxIns |> Seq.map (fun txIn ->
                if not isCoinbase then
                    let utxo = utxoAccessor.GetUTXO txIn.PrevOutPoint
                    utxo |> Option.map (fun utxo ->
                        utxoAccessor.DeleteUTXO txIn.PrevOutPoint
                        utxoWriter.Write(Delete, txIn.PrevOutPoint, utxo)
                        utxo) 
                        |> Option.bind (fun utxo -> checkCoinbaseMaturity utxo height)
                        |> Option.bind (fun utxo -> checkMoney utxo.TxOut.Value)
                else Some 0L
                ) |> Seq.toList |> Option.sequence |> Option.map Seq.sum
        let! totalOut =
            tx.TxOuts |> Seq.mapi (fun iTxOut txOut ->
                let outpoint = new OutPoint(tx.Hash, iTxOut)
                let utxo = UTXO(txOut, if isCoinbase then height else 0)
                if not (isRETURN utxo.TxOut.Script) then
                    utxoAccessor.AddUTXO (outpoint, utxo)
                    utxoWriter.Write(Add, outpoint, utxo)
                if not isCoinbase
                then checkMoney txOut.Value 
                else Some 0L
            ) |> Seq.toList |> Option.sequence |> Option.map Seq.sum
        let! _ = checkMoney totalIn
        let! _ = checkMoney totalOut
        let fee = totalIn - totalOut
        do! fee >= 0L |> errorIfFalse "fee must be positive"
        return fee
    }

Block storage

Blocks are stored as flat binary file in the directory under the blocksDir configured in the app.config. The path follows a naming convention that takes the depth and hash into consideration. A block of hash '0bb74cf88a2e07275a36cb57e81ddb64933568f7720e2f91ff84c5ee614fa3e3' and height 1002 will be under blocks/1/1002/0bb74cf88a2e07275a36cb57e81ddb64933568f7720e2f91ff84c5ee614fa3e3. The undo block has the same name and location but with the .undo extension. Undo blocks are written in the same order as the transactions of the block are written. Therefore when they are reverted, they must be applied in reverse order.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
70: 
71: 
72: 
73: 
74: 
75: 
76: 
let blocksBaseDir = settings.BlocksDir

let getBlockDir (bh: BlockHeader) =
    let height = bh.Height
    let path = sprintf "%s/%d/%d" blocksBaseDir (height/1000) height
    Directory.CreateDirectory path |> ignore
    path

let hasBlock (bh: BlockHeader) =
    let path = getBlockDir bh
    File.Exists (sprintf "%s/%s" path (hashToHex bh.Hash))

let storeBlock (b: Block) (p: byte[]) =
    let path = getBlockDir b.Header
    use fs = new FileStream(sprintf "%s/%s" path (hashToHex b.Header.Hash), FileMode.Create)
    use writer = new BinaryWriter(fs)
    writer.Write(p)

let deleteBlock (bh: BlockHeader) =
    let path = getBlockDir bh
    File.Delete (sprintf "%s/%s" path (hashToHex bh.Hash))

type UndoWriter(fs: FileStream) =
    let writer = new BinaryWriter(fs)

    interface IDisposable with
        override x.Dispose() =
            writer.Close()
            fs.Close()

    interface IUTXOWriter with
        member x.Write(txOp: TxOperation, outpoint: OutPoint, utxo: UTXO) = 
            match txOp with
            | Add -> writer.Write(0uy) // 0 is add
            | Delete -> writer.Write(1uy)
            writer.Write(outpoint.ToByteArray())
            writer.Write(utxo.ToByteArray())
    
let storeUndoBlock (b: Block) = 
    let path = getBlockDir b.Header
    let fs = new FileStream(sprintf "%s/%s.undo" path (hashToHex b.Header.Hash), FileMode.Create)
    new UndoWriter(fs)

let loadBlock (bh: BlockHeader) =
    let path = getBlockDir bh
    use fs = new FileStream(sprintf "%s/%s" path (hashToHex bh.Hash), FileMode.Open)
    use reader = new BinaryReader(fs)
    let block = Block.Parse reader
    block.Header.Height <- bh.Height
    block
let getBlockSize (bh: BlockHeader) =
    let path = getBlockDir bh
    use fs = new FileStream(sprintf "%s/%s" path (hashToHex bh.Hash), FileMode.Open)
    int32 fs.Length

let undoBlock (utxoAccessor: IUTXOAccessor) (bh: BlockHeader) = 
    logger.DebugF "Undoing block #%d" bh.Height
    let path = getBlockDir bh

    use fsUndo = new FileStream(sprintf "%s/%s.undo" path (hashToHex bh.Hash), FileMode.Open)
    use reader = new BinaryReader(fsUndo)
    let fops = new List<unit -> unit>()
    while (fsUndo.Position <> fsUndo.Length) do
        let op = reader.ReadByte()
        let outpoint = OutPoint.Parse reader
        let utxo = UTXO.Parse reader
        let fop = 
            match op with 
            | 0uy -> fun() -> utxoAccessor.DeleteUTXO outpoint // 0 was an add and to undo an add, do a delete
            | 1uy -> fun() -> utxoAccessor.AddUTXO (outpoint, utxo)
            | _ -> ignore

        fops.Add(fop)
    fops |> Seq.toList |> List.rev |> List.iter(fun fop -> fop()) // Don't forget to reverse the list
    let block = loadBlock bh
    block.Txs
module Db
namespace System
namespace System.IO
namespace System.Collections
namespace System.Collections.Generic
namespace System.Linq
namespace System.Net
namespace System.Net.Sockets
Multiple items
namespace System.Collections

--------------------
namespace Microsoft.FSharp.Collections
Multiple items
type Choice<'T1,'T2> =
  | Choice1Of2 of 'T1
  | Choice2Of2 of 'T2

Full name: Microsoft.FSharp.Core.Choice<_,_>

--------------------
type Choice<'T1,'T2,'T3> =
  | Choice1Of3 of 'T1
  | Choice2Of3 of 'T2
  | Choice3Of3 of 'T3

Full name: Microsoft.FSharp.Core.Choice<_,_,_>

--------------------
type Choice<'T1,'T2,'T3,'T4> =
  | Choice1Of4 of 'T1
  | Choice2Of4 of 'T2
  | Choice3Of4 of 'T3
  | Choice4Of4 of 'T4

Full name: Microsoft.FSharp.Core.Choice<_,_,_,_>

--------------------
type Choice<'T1,'T2,'T3,'T4,'T5> =
  | Choice1Of5 of 'T1
  | Choice2Of5 of 'T2
  | Choice3Of5 of 'T3
  | Choice4Of5 of 'T4
  | Choice5Of5 of 'T5

Full name: Microsoft.FSharp.Core.Choice<_,_,_,_,_>

--------------------
type Choice<'T1,'T2,'T3,'T4,'T5,'T6> =
  | Choice1Of6 of 'T1
  | Choice2Of6 of 'T2
  | Choice3Of6 of 'T3
  | Choice4Of6 of 'T4
  | Choice5Of6 of 'T5
  | Choice6Of6 of 'T6

Full name: Microsoft.FSharp.Core.Choice<_,_,_,_,_,_>

--------------------
type Choice<'T1,'T2,'T3,'T4,'T5,'T6,'T7> =
  | Choice1Of7 of 'T1
  | Choice2Of7 of 'T2
  | Choice3Of7 of 'T3
  | Choice4Of7 of 'T4
  | Choice5Of7 of 'T5
  | Choice6Of7 of 'T6
  | Choice7Of7 of 'T7

Full name: Microsoft.FSharp.Core.Choice<_,_,_,_,_,_,_>
namespace System.Data
val connectionString : string

Full name: Db.connectionString
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val dbLock : obj

Full name: Db.dbLock
type obj = Object

Full name: Microsoft.FSharp.Core.obj
val updateAddr : addr:'a -> unit

Full name: Db.updateAddr
val addr : 'a
val lock : lockObject:'Lock -> action:(unit -> 'T) -> 'T (requires reference type)

Full name: Microsoft.FSharp.Core.Operators.lock
val connection : IDisposable
val updateAddrQuery : obj
type DbType =
  | AnsiString = 0
  | Binary = 1
  | Byte = 2
  | Boolean = 3
  | Currency = 4
  | Date = 5
  | DateTime = 6
  | Decimal = 7
  | Double = 8
  | Guid = 9
  ...

Full name: System.Data.DbType
field DbType.String = 16
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
field DbType.Int32 = 11
field DbType.DateTime = 6
type EndPoint =
  member AddressFamily : AddressFamily
  member Create : socketAddress:SocketAddress -> EndPoint
  member Serialize : unit -> SocketAddress

Full name: System.Net.EndPoint
val dts : obj
Multiple items
val int64 : value:'T -> int64 (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int64

--------------------
type int64 = Int64

Full name: Microsoft.FSharp.Core.int64

--------------------
type int64<'Measure> = int64

Full name: Microsoft.FSharp.Core.int64<_>
val getPeers : unit -> IPEndPoint list

Full name: Db.getPeers
val command : IDisposable
val reader : IDisposable
val host : string
val port : int
val ip : IPAddress
Multiple items
type IPAddress =
  new : newAddress:int64 -> IPAddress + 2 overloads
  member Address : int64 with get, set
  member AddressFamily : AddressFamily
  member Equals : comparand:obj -> bool
  member GetAddressBytes : unit -> byte[]
  member GetHashCode : unit -> int
  member IsIPv6LinkLocal : bool
  member IsIPv6Multicast : bool
  member IsIPv6SiteLocal : bool
  member IsIPv6Teredo : bool
  ...

Full name: System.Net.IPAddress

--------------------
IPAddress(newAddress: int64) : unit
IPAddress(address: byte []) : unit
IPAddress(address: byte [], scopeid: int64) : unit
IPAddress.Parse(ipString: string) : IPAddress
val endpoint : IPEndPoint
Multiple items
type IPEndPoint =
  inherit EndPoint
  new : address:int64 * port:int -> IPEndPoint + 1 overload
  member Address : IPAddress with get, set
  member AddressFamily : AddressFamily
  member Create : socketAddress:SocketAddress -> EndPoint
  member Equals : comparand:obj -> bool
  member GetHashCode : unit -> int
  member Port : int with get, set
  member Serialize : unit -> SocketAddress
  member ToString : unit -> string
  static val MinPort : int
  ...

Full name: System.Net.IPEndPoint

--------------------
IPEndPoint(address: int64, port: int) : unit
IPEndPoint(address: IPAddress, port: int) : unit
val getPeer : unit -> IPEndPoint option

Full name: Db.getPeer
val peers : IPEndPoint list
module Seq

from Microsoft.FSharp.Collections
val tryPick : chooser:('T -> 'U option) -> source:seq<'T> -> 'U option

Full name: Microsoft.FSharp.Collections.Seq.tryPick
union case Option.Some: Value: 'T -> Option<'T>
val dropOldPeers : dts:'a -> unit

Full name: Db.dropOldPeers
val dts : 'a
val resetState : unit -> unit

Full name: Db.resetState
val updateState : peer:IPEndPoint * state:int -> unit

Full name: Db.updateState
val peer : IPEndPoint
val state : int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
val query : obj
property IPEndPoint.Address: IPAddress
IPAddress.ToString() : string
property IPEndPoint.Port: int
val headerConnection : obj

Full name: Db.headerConnection
val command : obj

Full name: Db.command
field DbType.Binary = 1
field DbType.Boolean = 3
val readTip : unit -> byte []

Full name: Db.readTip
Multiple items
val byte : value:'T -> byte (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.byte

--------------------
type byte = Byte

Full name: Microsoft.FSharp.Core.byte
val tip : byte []
type Array =
  member Clone : unit -> obj
  member CopyTo : array:Array * index:int -> unit + 1 overload
  member GetEnumerator : unit -> IEnumerator
  member GetLength : dimension:int -> int
  member GetLongLength : dimension:int -> int64
  member GetLowerBound : dimension:int -> int
  member GetUpperBound : dimension:int -> int
  member GetValue : params indices:int[] -> obj + 7 overloads
  member Initialize : unit -> unit
  member IsFixedSize : bool
  ...

Full name: System.Array
val zeroCreate : count:int -> 'T []

Full name: Microsoft.FSharp.Collections.Array.zeroCreate
Multiple items
type List<'T> =
  new : unit -> List<'T> + 2 overloads
  member Add : item:'T -> unit
  member AddRange : collection:IEnumerable<'T> -> unit
  member AsReadOnly : unit -> ReadOnlyCollection<'T>
  member BinarySearch : item:'T -> int + 2 overloads
  member Capacity : int with get, set
  member Clear : unit -> unit
  member Contains : item:'T -> bool
  member ConvertAll<'TOutput> : converter:Converter<'T, 'TOutput> -> List<'TOutput>
  member CopyTo : array:'T[] -> unit + 2 overloads
  ...
  nested type Enumerator

Full name: System.Collections.Generic.List<_>

--------------------
List() : unit
List(capacity: int) : unit
List(collection: IEnumerable<'T>) : unit
val head : list:'T list -> 'T

Full name: Microsoft.FSharp.Collections.List.head
val writeTip : tip:byte [] -> unit

Full name: Db.writeTip
val getHeader : reader:'a -> 'b list

Full name: Db.getHeader
val reader : 'a
val hash : obj []
val height : obj
val version : obj
val prev_hash : obj []
val next_hash : obj []
val merkle_root : obj []
val ts : obj
val bits : obj
val nonce : obj
val tx_count : obj
val is_main : obj
val bh : 'b
Multiple items
val uint32 : value:'T -> uint32 (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.uint32

--------------------
type uint32 = UInt32

Full name: Microsoft.FSharp.Core.uint32
val genesisHeader : obj

Full name: Db.genesisHeader
val res : obj list
property List.Head: obj
val readHeader : hash:byte [] -> 'a

Full name: Db.readHeader
val hash : byte []
val res : 'a list
property List.Length: int
val getHeaderByHeight : height:int -> 'a

Full name: Db.getHeaderByHeight
val height : int
val getNextHeader : hash:byte [] -> 'a

Full name: Db.getNextHeader
val writeHeaders : header:'a -> unit

Full name: Db.writeHeaders
val header : 'a
Multiple items
type Version =
  new : unit -> Version + 4 overloads
  member Build : int
  member Clone : unit -> obj
  member CompareTo : version:obj -> int + 1 overload
  member Equals : obj:obj -> bool + 1 overload
  member GetHashCode : unit -> int
  member Major : int
  member MajorRevision : int16
  member Minor : int
  member MinorRevision : int16
  ...

Full name: System.Version

--------------------
Version() : unit
Version(version: string) : unit
Version(major: int, minor: int) : unit
Version(major: int, minor: int, build: int) : unit
Version(major: int, minor: int, build: int, revision: int) : unit
Multiple items
type BloomFilter =
  new : filter:byte [] * cHashes:int * nTweak:int -> BloomFilter
  new : N:int * P:float * cHashes:int * nTweak:int -> BloomFilter
  member Add : v:byte [] -> unit
  member Check : v:byte [] -> bool

Full name: Db.BloomFilter

--------------------
new : filter:byte [] * cHashes:int * nTweak:int -> BloomFilter
new : N:int * P:float * cHashes:int * nTweak:int -> BloomFilter
val filter : byte []
val cHashes : int
val nTweak : int
val bits : BitArray
Multiple items
type BitArray =
  new : length:int -> BitArray + 5 overloads
  member And : value:BitArray -> BitArray
  member Clone : unit -> obj
  member CopyTo : array:Array * index:int -> unit
  member Count : int
  member Get : index:int -> bool
  member GetEnumerator : unit -> IEnumerator
  member IsReadOnly : bool
  member IsSynchronized : bool
  member Item : int -> bool with get, set
  ...

Full name: System.Collections.BitArray

--------------------
BitArray(length: int) : unit
BitArray(bytes: byte []) : unit
BitArray(values: bool []) : unit
BitArray(values: int []) : unit
BitArray(bits: BitArray) : unit
BitArray(length: int, defaultValue: bool) : unit
val hashers : obj []
Multiple items
val seq : sequence:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Core.Operators.seq

--------------------
type seq<'T> = IEnumerable<'T>

Full name: Microsoft.FSharp.Collections.seq<_>
val i : int
val toArray : source:seq<'T> -> 'T []

Full name: Microsoft.FSharp.Collections.Seq.toArray
val add : (byte [] -> unit)
val v : byte []
val hasher : obj
val bucket : uint32
type BitConverter =
  static val IsLittleEndian : bool
  static member DoubleToInt64Bits : value:float -> int64
  static member GetBytes : value:bool -> byte[] + 9 overloads
  static member Int64BitsToDouble : value:int64 -> float
  static member ToBoolean : value:byte[] * startIndex:int -> bool
  static member ToChar : value:byte[] * startIndex:int -> char
  static member ToDouble : value:byte[] * startIndex:int -> float
  static member ToInt16 : value:byte[] * startIndex:int -> int16
  static member ToInt32 : value:byte[] * startIndex:int -> int
  static member ToInt64 : value:byte[] * startIndex:int -> int64
  ...

Full name: System.BitConverter
BitConverter.ToUInt32(value: byte [], startIndex: int) : uint32
property Array.Length: int
BitArray.Set(index: int, value: bool) : unit
val check : (byte [] -> bool)
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map
val h : uint32
BitArray.Get(index: int) : bool
val b : bool
val N : int
val P : float
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
val size : int
val min : e1:'T -> e2:'T -> 'T (requires comparison)

Full name: Microsoft.FSharp.Core.Operators.min
val log : value:'T -> 'T (requires member Log)

Full name: Microsoft.FSharp.Core.Operators.log
val x : BloomFilter
member BloomFilter.Add : v:byte [] -> unit

Full name: Db.BloomFilter.Add
member BloomFilter.Check : v:byte [] -> bool

Full name: Db.BloomFilter.Check
type AddressEntry =
  {Id: int;
   Account: int;
   Hash: byte [];
   Address: string;}

Full name: Db.AddressEntry
AddressEntry.Id: int
AddressEntry.Account: int
AddressEntry.Hash: byte []
AddressEntry.Address: string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
Multiple items
type Wallet =
  new : unit -> Wallet
  member TryGet : hash:byte [] -> 'a

Full name: Db.Wallet

--------------------
new : unit -> Wallet
val bloomFilter : BloomFilter
val loadData : (unit -> Map<byte [],AddressEntry>)
val id : int
val account : int
val address : string
Multiple items
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  override Equals : obj -> bool
  member Remove : key:'Key -> Map<'Key,'Value>
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
val ofSeq : elements:seq<'Key * 'T> -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.ofSeq
val addresses : Map<byte [],AddressEntry>
val get : (byte [] -> 'a)
module Option

from Microsoft.FSharp.Core
member BloomFilter.Check : v:byte [] -> bool
val tryFind : key:'Key -> table:Map<'Key,'T> -> 'T option (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.tryFind
val iter : action:('Key -> 'T -> unit) -> table:Map<'Key,'T> -> unit (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.iter
val k : byte []
member BloomFilter.Add : v:byte [] -> unit
val x : Wallet
member Wallet.TryGet : hash:byte [] -> 'a

Full name: Db.Wallet.TryGet
val wallet : Wallet

Full name: Db.wallet
type IUTXOAccessor =
  interface
    inherit IDisposable
    abstract member AddUTXO : 'a0 * 'a1 -> unit
    abstract member DeleteUTXO : 'a0 -> unit
    abstract member GetCount : byte [] -> int
    abstract member GetUTXO : 'a0 -> 'a1
  end

Full name: Db.IUTXOAccessor
type IDisposable =
  member Dispose : unit -> unit

Full name: System.IDisposable
abstract member IUTXOAccessor.DeleteUTXO : 'a0 -> unit

Full name: Db.IUTXOAccessor.DeleteUTXO
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
abstract member IUTXOAccessor.AddUTXO : 'a0 * 'a1 -> unit

Full name: Db.IUTXOAccessor.AddUTXO
abstract member IUTXOAccessor.GetUTXO : 'a0 -> 'a1

Full name: Db.IUTXOAccessor.GetUTXO
abstract member IUTXOAccessor.GetCount : byte [] -> int

Full name: Db.IUTXOAccessor.GetCount
Multiple items
type TxTableAccessor =
  interface IDisposable
  new : unit -> TxTableAccessor
  member Add : outpoint:'b -> utxo:'c -> unit
  member Delete : outpoint:'a -> unit

Full name: Db.TxTableAccessor

--------------------
new : unit -> TxTableAccessor
val connection : obj
val insertTx : obj
val deleteTx : obj
val addToTxTable : ('a -> 'b -> unit)
val outpoint : 'a
val utxo : 'b
val script : obj
val hash : obj:'T -> int (requires equality)

Full name: Microsoft.FSharp.Core.Operators.hash
member Wallet.TryGet : hash:byte [] -> 'a
val deleteFromTxTable : ('a -> unit)
field DbType.Int64 = 12
val x : TxTableAccessor
override TxTableAccessor.Dispose : unit -> unit

Full name: Db.TxTableAccessor.Dispose
member TxTableAccessor.Add : outpoint:'b -> utxo:'c -> unit

Full name: Db.TxTableAccessor.Add
val outpoint : 'b
val utxo : 'c
member TxTableAccessor.Delete : outpoint:'a -> unit

Full name: Db.TxTableAccessor.Delete
val txTableAccessor : TxTableAccessor

Full name: Db.txTableAccessor
val addIfInWallet : txTableAccessor:TxTableAccessor -> wallet:Wallet -> outpoint:'a -> utxo:'b -> unit

Full name: Db.addIfInWallet
val txTableAccessor : TxTableAccessor
val wallet : Wallet
member TxTableAccessor.Add : outpoint:'b -> utxo:'c -> unit
val removeIfInWallet : txTableAccessor:TxTableAccessor -> wallet:Wallet -> outpoint:'a -> utxo:'b -> unit

Full name: Db.removeIfInWallet
member TxTableAccessor.Delete : outpoint:'a -> unit
Multiple items
type LevelDBUTXOAccessor =
  interface IUTXOAccessor
  new : unit -> LevelDBUTXOAccessor
  new : db:obj * wallet:Wallet * txTableAccessor:TxTableAccessor -> LevelDBUTXOAccessor
  member Db : obj

Full name: Db.LevelDBUTXOAccessor

--------------------
new : unit -> LevelDBUTXOAccessor
new : db:obj * wallet:Wallet * txTableAccessor:TxTableAccessor -> LevelDBUTXOAccessor
val db : obj
val ro : obj
val wo : obj
val deleteUTXO : ('a -> 'b)
val k : obj
val addUTXO : ('a -> 'b -> 'c)
val v : obj
val getUTXO : ('a -> 'b option)
union case Option.None: Option<'T>
val getCount : (byte [] -> int)
val txHash : byte []
val cursor : obj
val mutable count : int
val getCountInner : (int -> int)
val count : int
val options : obj
val x : LevelDBUTXOAccessor
override LevelDBUTXOAccessor.DeleteUTXO : outpoint:'e -> unit

Full name: Db.LevelDBUTXOAccessor.DeleteUTXO
val outpoint : 'e
override LevelDBUTXOAccessor.AddUTXO : outpoint:'c * txOut:'d -> unit

Full name: Db.LevelDBUTXOAccessor.AddUTXO
val outpoint : 'c
val txOut : 'd
override LevelDBUTXOAccessor.GetUTXO : outpoint:'a -> 'b

Full name: Db.LevelDBUTXOAccessor.GetUTXO
override LevelDBUTXOAccessor.GetCount : txHash:byte [] -> int

Full name: Db.LevelDBUTXOAccessor.GetCount
override LevelDBUTXOAccessor.Dispose : unit -> unit

Full name: Db.LevelDBUTXOAccessor.Dispose
val levelDbAccessor : LevelDBUTXOAccessor

Full name: Db.levelDbAccessor
val utxoAccessor : IUTXOAccessor

Full name: Db.utxoAccessor
val scanUTXO : unit -> unit

Full name: Db.scanUTXO
val creditToWallet : (obj -> obj -> unit)
val cursor : IDisposable
property LevelDBUTXOAccessor.Db: obj
val outpoint : obj
val utxo : obj
type TxOperation =
  | Add
  | Delete

Full name: Db.TxOperation
union case TxOperation.Add: TxOperation
union case TxOperation.Delete: TxOperation
type IUTXOWriter =
  interface
    abstract member Write : TxOperation * 'a0 * 'a1 -> unit
  end

Full name: Db.IUTXOWriter
abstract member IUTXOWriter.Write : TxOperation * 'a0 * 'a1 -> unit

Full name: Db.IUTXOWriter.Write
val checkMoney : v:int64 -> int64 option

Full name: Db.checkMoney
val v : int64
val map : mapping:('T -> 'U) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.map
val checkCoinbaseMaturity : utxo:'a -> height:int -> 'a option

Full name: Db.checkCoinbaseMaturity
val utxo : 'a
val OP_RETURN : byte

Full name: Db.OP_RETURN
val isRETURN : script:byte [] -> bool

Full name: Db.isRETURN
val script : byte []
val processUTXO : utxoAccessor:IUTXOAccessor -> utxoWriter:IUTXOWriter -> isCoinbase:bool -> height:int -> tx:'a -> 'b

Full name: Db.processUTXO
val utxoAccessor : IUTXOAccessor
val utxoWriter : IUTXOWriter
val isCoinbase : bool
type bool = Boolean

Full name: Microsoft.FSharp.Core.bool
val tx : 'a
val not : value:bool -> bool

Full name: Microsoft.FSharp.Core.Operators.not
abstract member IUTXOAccessor.GetUTXO : 'a0 -> 'a1
abstract member IUTXOAccessor.DeleteUTXO : 'a0 -> unit
abstract member IUTXOWriter.Write : TxOperation * 'a0 * 'a1 -> unit
val bind : binder:('T -> 'U option) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.bind
val toList : source:seq<'T> -> 'T list

Full name: Microsoft.FSharp.Collections.Seq.toList
val sum : source:seq<'T> -> 'T (requires member ( + ) and member get_Zero)

Full name: Microsoft.FSharp.Collections.Seq.sum
val mapi : mapping:(int -> 'T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.mapi
abstract member IUTXOAccessor.AddUTXO : 'a0 * 'a1 -> unit
val blocksBaseDir : string

Full name: Db.blocksBaseDir
val getBlockDir : bh:'a -> string

Full name: Db.getBlockDir
val bh : 'a
val path : string
type Directory =
  static member CreateDirectory : path:string -> DirectoryInfo + 1 overload
  static member Delete : path:string -> unit + 1 overload
  static member EnumerateDirectories : path:string -> IEnumerable<string> + 2 overloads
  static member EnumerateFileSystemEntries : path:string -> IEnumerable<string> + 2 overloads
  static member EnumerateFiles : path:string -> IEnumerable<string> + 2 overloads
  static member Exists : path:string -> bool
  static member GetAccessControl : path:string -> DirectorySecurity + 1 overload
  static member GetCreationTime : path:string -> DateTime
  static member GetCreationTimeUtc : path:string -> DateTime
  static member GetCurrentDirectory : unit -> string
  ...

Full name: System.IO.Directory
Directory.CreateDirectory(path: string) : DirectoryInfo
Directory.CreateDirectory(path: string, directorySecurity: Security.AccessControl.DirectorySecurity) : DirectoryInfo
val hasBlock : bh:'a -> bool

Full name: Db.hasBlock
type File =
  static member AppendAllLines : path:string * contents:IEnumerable<string> -> unit + 1 overload
  static member AppendAllText : path:string * contents:string -> unit + 1 overload
  static member AppendText : path:string -> StreamWriter
  static member Copy : sourceFileName:string * destFileName:string -> unit + 1 overload
  static member Create : path:string -> FileStream + 3 overloads
  static member CreateText : path:string -> StreamWriter
  static member Decrypt : path:string -> unit
  static member Delete : path:string -> unit
  static member Encrypt : path:string -> unit
  static member Exists : path:string -> bool
  ...

Full name: System.IO.File
File.Exists(path: string) : bool
val storeBlock : b:'a -> p:byte [] -> unit

Full name: Db.storeBlock
val b : 'a
val p : byte []
val fs : FileStream
Multiple items
type FileStream =
  inherit Stream
  new : path:string * mode:FileMode -> FileStream + 14 overloads
  member BeginRead : array:byte[] * offset:int * numBytes:int * userCallback:AsyncCallback * stateObject:obj -> IAsyncResult
  member BeginWrite : array:byte[] * offset:int * numBytes:int * userCallback:AsyncCallback * stateObject:obj -> IAsyncResult
  member CanRead : bool
  member CanSeek : bool
  member CanWrite : bool
  member EndRead : asyncResult:IAsyncResult -> int
  member EndWrite : asyncResult:IAsyncResult -> unit
  member Flush : unit -> unit + 1 overload
  member GetAccessControl : unit -> FileSecurity
  ...

Full name: System.IO.FileStream

--------------------
FileStream(path: string, mode: FileMode) : unit
   (+0 other overloads)
FileStream(handle: Microsoft.Win32.SafeHandles.SafeFileHandle, access: FileAccess) : unit
   (+0 other overloads)
FileStream(path: string, mode: FileMode, access: FileAccess) : unit
   (+0 other overloads)
FileStream(handle: Microsoft.Win32.SafeHandles.SafeFileHandle, access: FileAccess, bufferSize: int) : unit
   (+0 other overloads)
FileStream(path: string, mode: FileMode, access: FileAccess, share: FileShare) : unit
   (+0 other overloads)
FileStream(handle: Microsoft.Win32.SafeHandles.SafeFileHandle, access: FileAccess, bufferSize: int, isAsync: bool) : unit
   (+0 other overloads)
FileStream(path: string, mode: FileMode, access: FileAccess, share: FileShare, bufferSize: int) : unit
   (+0 other overloads)
FileStream(path: string, mode: FileMode, access: FileAccess, share: FileShare, bufferSize: int, options: FileOptions) : unit
   (+0 other overloads)
FileStream(path: string, mode: FileMode, access: FileAccess, share: FileShare, bufferSize: int, useAsync: bool) : unit
   (+0 other overloads)
FileStream(path: string, mode: FileMode, rights: Security.AccessControl.FileSystemRights, share: FileShare, bufferSize: int, options: FileOptions) : unit
   (+0 other overloads)
type FileMode =
  | CreateNew = 1
  | Create = 2
  | Open = 3
  | OpenOrCreate = 4
  | Truncate = 5
  | Append = 6

Full name: System.IO.FileMode
field FileMode.Create = 2
val writer : BinaryWriter
Multiple items
type BinaryWriter =
  new : output:Stream -> BinaryWriter + 1 overload
  member BaseStream : Stream
  member Close : unit -> unit
  member Dispose : unit -> unit
  member Flush : unit -> unit
  member Seek : offset:int * origin:SeekOrigin -> int64
  member Write : value:bool -> unit + 17 overloads
  static val Null : BinaryWriter

Full name: System.IO.BinaryWriter

--------------------
BinaryWriter(output: Stream) : unit
BinaryWriter(output: Stream, encoding: Text.Encoding) : unit
BinaryWriter.Write(value: string) : unit
   (+0 other overloads)
BinaryWriter.Write(value: float32) : unit
   (+0 other overloads)
BinaryWriter.Write(value: uint64) : unit
   (+0 other overloads)
BinaryWriter.Write(value: int64) : unit
   (+0 other overloads)
BinaryWriter.Write(value: uint32) : unit
   (+0 other overloads)
BinaryWriter.Write(value: int) : unit
   (+0 other overloads)
BinaryWriter.Write(value: uint16) : unit
   (+0 other overloads)
BinaryWriter.Write(value: int16) : unit
   (+0 other overloads)
BinaryWriter.Write(value: decimal) : unit
   (+0 other overloads)
BinaryWriter.Write(value: float) : unit
   (+0 other overloads)
val deleteBlock : bh:'a -> unit

Full name: Db.deleteBlock
File.Delete(path: string) : unit
Multiple items
type UndoWriter =
  interface IUTXOWriter
  interface IDisposable
  new : fs:FileStream -> UndoWriter

Full name: Db.UndoWriter

--------------------
new : fs:FileStream -> UndoWriter
val x : UndoWriter
override UndoWriter.Dispose : unit -> unit

Full name: Db.UndoWriter.Dispose
BinaryWriter.Close() : unit
Stream.Close() : unit
override UndoWriter.Write : txOp:TxOperation * outpoint:'a * utxo:'b -> unit

Full name: Db.UndoWriter.Write
val txOp : TxOperation
val storeUndoBlock : b:'a -> UndoWriter

Full name: Db.storeUndoBlock
val loadBlock : bh:'a -> 'b

Full name: Db.loadBlock
field FileMode.Open = 3
val reader : BinaryReader
Multiple items
type BinaryReader =
  new : input:Stream -> BinaryReader + 1 overload
  member BaseStream : Stream
  member Close : unit -> unit
  member Dispose : unit -> unit
  member PeekChar : unit -> int
  member Read : unit -> int + 2 overloads
  member ReadBoolean : unit -> bool
  member ReadByte : unit -> byte
  member ReadBytes : count:int -> byte[]
  member ReadChar : unit -> char
  ...

Full name: System.IO.BinaryReader

--------------------
BinaryReader(input: Stream) : unit
BinaryReader(input: Stream, encoding: Text.Encoding) : unit
val block : 'b
val getBlockSize : bh:'a -> int32

Full name: Db.getBlockSize
Multiple items
val int32 : value:'T -> int32 (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int32

--------------------
type int32 = Int32

Full name: Microsoft.FSharp.Core.int32
property FileStream.Length: int64
val undoBlock : utxoAccessor:IUTXOAccessor -> bh:'a -> 'b

Full name: Db.undoBlock
val fsUndo : FileStream
val fops : List<(unit -> unit)>
property FileStream.Position: int64
val op : byte
BinaryReader.ReadByte() : byte
val fop : (unit -> unit)
List.Add(item: unit -> unit) : unit
val rev : list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.rev
val iter : action:('T -> unit) -> list:'T list -> unit

Full name: Microsoft.FSharp.Collections.List.iter
val block : obj