blockchain-lite 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +155 -52
- data/lib/blockchain-lite/basic/block.rb +36 -12
- data/lib/blockchain-lite/blockchain.rb +27 -11
- data/lib/blockchain-lite/proof_of_work/block.rb +51 -20
- data/lib/blockchain-lite/version.rb +1 -1
- data/test/test_block.rb +67 -3
- data/test/test_block_basic.rb +4 -3
- data/test/test_block_proof_of_work.rb +4 -3
- data/test/test_blockchain.rb +10 -7
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 7d943d2cc505e7e01dbaf6fb364ca317d55d9563
         | 
| 4 | 
            +
              data.tar.gz: 4ab4b023a57d9ef9794e6024035c6cf6f718afaa
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 4d10c42c537839b325d0ab9ebc59bd86238e985f9c549f5119b1b055bd5bf9d70a949ed51dd704082f07974408d40eb887fad1302148819d4fc6f9d63621de76
         | 
| 7 | 
            +
              data.tar.gz: 612428a641e7e40ffab34c1c33f30a5b0e224e441479c68b551da1581c8e845f31d29624967a9c7915afce4ed6ce6f0aa421ae71f8a67924d37ea23349d55f9b
         | 
    
        data/README.md
    CHANGED
    
    | @@ -10,10 +10,10 @@ blockchain-lite library / gem - build your own blockchain with crypto hashes - r | |
| 10 10 |  | 
| 11 11 | 
             
            ## What's a Blockchain?
         | 
| 12 12 |  | 
| 13 | 
            -
            > A blockchain is a distributed database  | 
| 13 | 
            +
            > A blockchain is a distributed database with
         | 
| 14 14 | 
             
            > a list (that is, chain) of records (that is, blocks)
         | 
| 15 15 | 
             
            > linked and secured by digital fingerprints
         | 
| 16 | 
            -
            > (that is,  | 
| 16 | 
            +
            > (that is, crypto hashes).
         | 
| 17 17 |  | 
| 18 18 | 
             
            See the [Awesome Blockchains](https://github.com/openblockchains/awesome-blockchains) page for more.
         | 
| 19 19 |  | 
| @@ -28,45 +28,51 @@ require 'blockchain-lite' | |
| 28 28 |  | 
| 29 29 | 
             
            b0 = Block.first( 'Genesis' )
         | 
| 30 30 | 
             
            b1 = Block.next( b0, 'Transaction Data...' )
         | 
| 31 | 
            -
            b2 = Block.next( b1, 'Transaction Data | 
| 32 | 
            -
            b3 = Block.next( b2, ' | 
| 31 | 
            +
            b2 = Block.next( b1, 'Transaction Data...' )
         | 
| 32 | 
            +
            b3 = Block.next( b2, 'Transaction Data...' )
         | 
| 33 33 |  | 
| 34 34 | 
             
            blockchain = [b0, b1, b2, b3]
         | 
| 35 35 |  | 
| 36 36 | 
             
            pp blockchain
         | 
| 37 | 
            +
            ```
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            will pretty print (pp) something like:
         | 
| 37 40 |  | 
| 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 | 
            -
             | 
| 41 | 
            +
            ```
         | 
| 42 | 
            +
            [#<Block:0x1eed2a0
         | 
| 43 | 
            +
              @index              = 0,
         | 
| 44 | 
            +
              @timestamp          = 2017-09-15 20:52:38,
         | 
| 45 | 
            +
              @transactions_count = 1,
         | 
| 46 | 
            +
              @transactions       = ["Genesis"],
         | 
| 47 | 
            +
              @previous_hash      = "0",
         | 
| 48 | 
            +
              @hash               = "edbd4e11e69bc399a9ccd8faaea44fb27410fe8e3023bb9462450a0a9c4caa1b">,
         | 
| 49 | 
            +
             #<Block:0x1eec9a0
         | 
| 50 | 
            +
              @index              = 1,
         | 
| 51 | 
            +
              @timestamp          = 2017-09-15 20:52:38,
         | 
| 52 | 
            +
              @transactions_count = 1,
         | 
| 53 | 
            +
              @transactions       = ["Transaction Data..."],
         | 
| 54 | 
            +
              @hash               = "eb8ecbf6d5870763ae246e37539d82e37052cb32f88bb8c59971f9978e437743",
         | 
| 55 | 
            +
              @previous_hash      = "edbd4e11e69bc399a9ccd8faaea44fb27410fe8e3023bb9462450a0a9c4caa1b">,
         | 
| 56 | 
            +
             #<Block:0x1eec838
         | 
| 57 | 
            +
              @index              = 2,
         | 
| 58 | 
            +
              @timestamp          = 2017-09-15 20:52:38,
         | 
| 59 | 
            +
              @transactions_count = 1,
         | 
| 60 | 
            +
              @transactions       = ["Transaction Data..."],
         | 
| 61 | 
            +
              @hash               = "be50017ee4bbcb33844b3dc2b7c4e476d46569b5df5762d14ceba9355f0a85f4",
         | 
| 62 | 
            +
              @previous_hash      = "eb8ecbf6d5870763ae246e37539d82e37052cb32f88bb8c59971f9978e437743">,
         | 
| 63 | 
            +
             #<Block:0x1eec6d0
         | 
| 64 | 
            +
              @index              = 3,
         | 
| 65 | 
            +
              @timestamp          = 2017-09-15 20:52:38
         | 
| 66 | 
            +
              @transactions_count = 1,
         | 
| 67 | 
            +
              @transactions       = ["Transaction Data..."],
         | 
| 68 | 
            +
              @hash               = "5ee2981606328abfe0c3b1171440f0df746c1e1f8b3b56c351727f7da7ae5d8d",
         | 
| 69 | 
            +
              @previous_hash      = "be50017ee4bbcb33844b3dc2b7c4e476d46569b5df5762d14ceba9355f0a85f4">]
         | 
| 65 70 | 
             
            ```
         | 
| 66 71 |  | 
| 72 | 
            +
             | 
| 67 73 | 
             
            ### Blocks
         | 
| 68 74 |  | 
| 69 | 
            -
            [Basic](#basic)
         | 
| 75 | 
            +
            [Basic](#basic) •
         | 
| 70 76 | 
             
            [Proof-of-Work](#proof-of-work)
         | 
| 71 77 |  | 
| 72 78 | 
             
            Supported block types / classes for now include:
         | 
| @@ -78,21 +84,27 @@ class Block | |
| 78 84 |  | 
| 79 85 | 
             
              attr_reader :index
         | 
| 80 86 | 
             
              attr_reader :timestamp
         | 
| 81 | 
            -
              attr_reader : | 
| 87 | 
            +
              attr_reader :transactions_count
         | 
| 88 | 
            +
              attr_reader :transactions       
         | 
| 82 89 | 
             
              attr_reader :previous_hash
         | 
| 83 90 | 
             
              attr_reader :hash
         | 
| 84 91 |  | 
| 85 | 
            -
              def initialize(index,  | 
| 86 | 
            -
                @index | 
| 87 | 
            -
                @timestamp | 
| 88 | 
            -
                @ | 
| 89 | 
            -
                @ | 
| 90 | 
            -
                @ | 
| 92 | 
            +
              def initialize(index, transactions, previous_hash)
         | 
| 93 | 
            +
                @index              = index
         | 
| 94 | 
            +
                @timestamp          = Time.now.utc    ## note: use coordinated universal time (utc)
         | 
| 95 | 
            +
                @transactions       = transactions
         | 
| 96 | 
            +
                @transactions_count = transactions.size
         | 
| 97 | 
            +
                @previous_hash      = previous_hash
         | 
| 98 | 
            +
                @hash               = calc_hash
         | 
| 91 99 | 
             
              end
         | 
| 92 100 |  | 
| 93 101 | 
             
              def calc_hash
         | 
| 94 102 | 
             
                sha = Digest::SHA256.new
         | 
| 95 | 
            -
                sha.update( @index.to_s + | 
| 103 | 
            +
                sha.update( @index.to_s +
         | 
| 104 | 
            +
                            @timestamp.to_s +
         | 
| 105 | 
            +
                            @transactions.to_s +
         | 
| 106 | 
            +
                            @transactions_count.to_s +
         | 
| 107 | 
            +
                            @previous_hash )
         | 
| 96 108 | 
             
                sha.hexdigest
         | 
| 97 109 | 
             
              end
         | 
| 98 110 | 
             
              ...
         | 
| @@ -109,22 +121,29 @@ class Block | |
| 109 121 |  | 
| 110 122 | 
             
              attr_reader :index
         | 
| 111 123 | 
             
              attr_reader :timestamp
         | 
| 112 | 
            -
              attr_reader : | 
| 124 | 
            +
              attr_reader :transactions_count
         | 
| 125 | 
            +
              attr_reader :transactions       
         | 
| 113 126 | 
             
              attr_reader :previous_hash
         | 
| 114 127 | 
             
              attr_reader :nonce        ## proof of work if hash starts with leading zeros (00)
         | 
| 115 128 | 
             
              attr_reader :hash
         | 
| 116 129 |  | 
| 117 | 
            -
              def initialize(index,  | 
| 118 | 
            -
                @index | 
| 119 | 
            -
                @timestamp | 
| 120 | 
            -
                @ | 
| 121 | 
            -
                @ | 
| 122 | 
            -
                @ | 
| 130 | 
            +
              def initialize(index, transactions, previous_hash)
         | 
| 131 | 
            +
                @index              = index
         | 
| 132 | 
            +
                @timestamp          = Time.now.utc    ## note: use coordinated universal time (utc)
         | 
| 133 | 
            +
                @transactions       = transactions
         | 
| 134 | 
            +
                @transactions_count = transactions.size
         | 
| 135 | 
            +
                @previous_hash      = previous_hash
         | 
| 136 | 
            +
                @nonce, @hash       = compute_hash_with_proof_of_work
         | 
| 123 137 | 
             
              end
         | 
| 124 138 |  | 
| 125 139 | 
             
              def calc_hash
         | 
| 126 140 | 
             
                sha = Digest::SHA256.new
         | 
| 127 | 
            -
                sha.update( @nonce.to_s + | 
| 141 | 
            +
                sha.update( @nonce.to_s +
         | 
| 142 | 
            +
                            @index.to_s +
         | 
| 143 | 
            +
                            @timestamp.to_s +
         | 
| 144 | 
            +
                            @transactions.to_s +
         | 
| 145 | 
            +
                            @transactions_count.to_s +
         | 
| 146 | 
            +
                            @previous_hash )
         | 
| 128 147 | 
             
                sha.hexdigest
         | 
| 129 148 | 
             
              end
         | 
| 130 149 | 
             
              ...
         | 
| @@ -144,8 +163,8 @@ for building and checking blockchains. Example: | |
| 144 163 | 
             
            b = Blockchain.new       # note: will (auto-) add the first (genesis) block
         | 
| 145 164 |  | 
| 146 165 | 
             
            b << 'Transaction Data...'
         | 
| 147 | 
            -
            b << 'Transaction Data | 
| 148 | 
            -
            b << ' | 
| 166 | 
            +
            b << 'Transaction Data...'
         | 
| 167 | 
            +
            b << 'Transaction Data...'
         | 
| 149 168 |  | 
| 150 169 | 
             
            pp b
         | 
| 151 170 | 
             
            ```
         | 
| @@ -163,8 +182,8 @@ or use the `Blockchain` class as a wrapper (pass in the blockchain array): | |
| 163 182 | 
             
            ``` ruby
         | 
| 164 183 | 
             
            b0 = Block.first( 'Genesis' )
         | 
| 165 184 | 
             
            b1 = Block.next( b0, 'Transaction Data...' )
         | 
| 166 | 
            -
            b2 = Block.next( b1, 'Transaction Data | 
| 167 | 
            -
            b3 = Block.next( b2, ' | 
| 185 | 
            +
            b2 = Block.next( b1, 'Transaction Data...' )
         | 
| 186 | 
            +
            b3 = Block.next( b2, 'Transaction Data...' )
         | 
| 168 187 |  | 
| 169 188 | 
             
            blockchain = [b0, b1, b2, b3]
         | 
| 170 189 |  | 
| @@ -178,6 +197,90 @@ b.broken? | |
| 178 197 | 
             
            and so on.
         | 
| 179 198 |  | 
| 180 199 |  | 
| 200 | 
            +
            ### Transactions
         | 
| 201 | 
            +
             | 
| 202 | 
            +
            Let's put the transactions from the (hyper) ledger book from [Tulips on the Blockchain!](https://github.com/openblockchains/tulips)
         | 
| 203 | 
            +
            on the blockchain:
         | 
| 204 | 
            +
             | 
| 205 | 
            +
             | 
| 206 | 
            +
            | From                | To           | What                      | Qty |
         | 
| 207 | 
            +
            |---------------------|--------------|---------------------------|----:|
         | 
| 208 | 
            +
            | Dutchgrown (†)      | Vincent      | Tulip Bloemendaal Sunset  |  10 |
         | 
| 209 | 
            +
            | Keukenhof (†)       | Anne         | Tulip Semper Augustus     |   7 |
         | 
| 210 | 
            +
            |                     |              |                           |     |
         | 
| 211 | 
            +
            | Flowers (†)         | Ruben        | Tulip Admiral van Eijck   |   5 |
         | 
| 212 | 
            +
            | Vicent              | Anne         | Tulip Bloemendaal Sunset  |   3 |
         | 
| 213 | 
            +
            | Anne                | Julia        | Tulip Semper Augustus     |   1 |
         | 
| 214 | 
            +
            | Julia               | Luuk         | Tulip Semper Augustus     |   1 |
         | 
| 215 | 
            +
            |                     |              |                           |     |
         | 
| 216 | 
            +
            | Bloom & Blossom (†) | Daisy        | Tulip Admiral of Admirals |   8 |
         | 
| 217 | 
            +
            | Vincent             | Max          | Tulip Bloemendaal Sunset  |   2 |
         | 
| 218 | 
            +
            | Anne                | Martijn      | Tulip Semper Augustus     |   2 |
         | 
| 219 | 
            +
            | Ruben               | Julia        | Tulip Admiral van Eijck   |   2 |
         | 
| 220 | 
            +
            |                     |              |                           |     |
         | 
| 221 | 
            +
            | Teleflora (†)       | Max          | Tulip Red Impression      |  11 |
         | 
| 222 | 
            +
            | Anne                | Naomi        | Tulip Bloemendaal Sunset  |   1 |
         | 
| 223 | 
            +
            | Daisy               | Vincent      | Tulip Admiral of Admirals |   3 |
         | 
| 224 | 
            +
            | Julia               | Mina         | Tulip Admiral van Eijck   |   1 |
         | 
| 225 | 
            +
             | 
| 226 | 
            +
            (†): Grower Transaction - New Tulips on the Market!
         | 
| 227 | 
            +
             | 
| 228 | 
            +
             | 
| 229 | 
            +
            ```ruby
         | 
| 230 | 
            +
            b0 = Block.first(
         | 
| 231 | 
            +
                    { from: "Dutchgrown", to: "Vincent", what: "Tulip Bloemendaal Sunset", qty: 10 },
         | 
| 232 | 
            +
                    { from: "Keukenhof",  to: "Anne",    what: "Tulip Semper Augustus",    qty: 7  } )
         | 
| 233 | 
            +
             | 
| 234 | 
            +
            b1 = Block.next( b0,
         | 
| 235 | 
            +
                    { from: "Flowers", to: "Ruben", what: "Tulip Admiral van Eijck",  qty: 5 },
         | 
| 236 | 
            +
                    { from: "Vicent",  to: "Anne",  what: "Tulip Bloemendaal Sunset", qty: 3 },
         | 
| 237 | 
            +
                    { from: "Anne",    to: "Julia", what: "Tulip Semper Augustus",    qty: 1 },
         | 
| 238 | 
            +
                    { from: "Julia",   to: "Luuk",  what: "Tulip Semper Augustus",    qty: 1 } )
         | 
| 239 | 
            +
             | 
| 240 | 
            +
            b2 = Block.next( b1,
         | 
| 241 | 
            +
                    { from: "Bloom & Blossom", to: "Daisy",   what: "Tulip Admiral of Admirals", qty: 8 },
         | 
| 242 | 
            +
                    { from: "Vincent",         to: "Max",     what: "Tulip Bloemendaal Sunset",  qty: 2 },
         | 
| 243 | 
            +
                    { from: "Anne",            to: "Martijn", what: "Tulip Semper Augustus",     qty: 2 },
         | 
| 244 | 
            +
                    { from: "Ruben",           to: "Julia",   what: "Tulip Admiral van Eijck",   qty: 2 } )
         | 
| 245 | 
            +
            ...
         | 
| 246 | 
            +
            ```
         | 
| 247 | 
            +
             | 
| 248 | 
            +
            resulting in:
         | 
| 249 | 
            +
             | 
| 250 | 
            +
            ```
         | 
| 251 | 
            +
            [#<Block:0x2da3da0
         | 
| 252 | 
            +
              @index              = 0,
         | 
| 253 | 
            +
              @timestamp          = 1637-09-24 11:40:15,
         | 
| 254 | 
            +
              @previous_hash      = "0",
         | 
| 255 | 
            +
              @hash               = "32bd169baebba0b70491b748329ab631c85175be15e1672f924ca174f628cb66",
         | 
| 256 | 
            +
              @transactions_count = 2,
         | 
| 257 | 
            +
              @transactions       =
         | 
| 258 | 
            +
               [{:from=>"Dutchgrown", :to=>"Vincent", :what=>"Tulip Bloemendaal Sunset", :qty=>10},
         | 
| 259 | 
            +
                {:from=>"Keukenhof",  :to=>"Anne",    :what=>"Tulip Semper Augustus",    :qty=>7}]>,
         | 
| 260 | 
            +
             #<Block:0x2da2ff0
         | 
| 261 | 
            +
              @index              = 1,
         | 
| 262 | 
            +
              @timestamp          = 1637-09-24 11:50:15,
         | 
| 263 | 
            +
              @previous_hash      = "32bd169baebba0b70491b748329ab631c85175be15e1672f924ca174f628cb66",
         | 
| 264 | 
            +
              @hash               = "57b519a8903e45348ac8a739c788815e2bd90423663957f87e276307f77f1028",
         | 
| 265 | 
            +
              @transactions_count = 4,
         | 
| 266 | 
            +
              @transactions       =
         | 
| 267 | 
            +
               [{:from=>"Flowers", :to=>"Ruben", :what=>"Tulip Admiral van Eijck",  :qty=>5},
         | 
| 268 | 
            +
                {:from=>"Vicent",  :to=>"Anne",  :what=>"Tulip Bloemendaal Sunset", :qty=>3},
         | 
| 269 | 
            +
                {:from=>"Anne",    :to=>"Julia", :what=>"Tulip Semper Augustus",    :qty=>1},
         | 
| 270 | 
            +
                {:from=>"Julia",   :to=>"Luuk",  :what=>"Tulip Semper Augustus",    :qty=>1}]>,
         | 
| 271 | 
            +
             #<Block:0x2da2720
         | 
| 272 | 
            +
              @index              = 2,
         | 
| 273 | 
            +
              @timestamp          = 1637-09-24 12:00:15,
         | 
| 274 | 
            +
              @previous_hash      = "57b519a8903e45348ac8a739c788815e2bd90423663957f87e276307f77f1028",
         | 
| 275 | 
            +
              @hash               = "ec7dd5ea86ab966d4d4db182abb7aa93c7e5f63857476e6301e7e38cebf36568",
         | 
| 276 | 
            +
              @transactions_count = 4,
         | 
| 277 | 
            +
              @transactions       =
         | 
| 278 | 
            +
               [{:from=>"Bloom & Blossom", :to=>"Daisy",   :what=>"Tulip Admiral of Admirals", :qty=>8},
         | 
| 279 | 
            +
                {:from=>"Vincent",         :to=>"Max",     :what=>"Tulip Bloemendaal Sunset",  :qty=>2},
         | 
| 280 | 
            +
                {:from=>"Anne",            :to=>"Martijn", :what=>"Tulip Semper Augustus",     :qty=>2},
         | 
| 281 | 
            +
                {:from=>"Ruben",           :to=>"Julia",   :what=>"Tulip Admiral van Eijck",   :qty=>2}]>,
         | 
| 282 | 
            +
             ...
         | 
| 283 | 
            +
            ```
         | 
| 181 284 |  | 
| 182 285 |  | 
| 183 286 | 
             
            ## Install
         | 
| @@ -8,33 +8,57 @@ class Block | |
| 8 8 |  | 
| 9 9 | 
             
              attr_reader :index
         | 
| 10 10 | 
             
              attr_reader :timestamp
         | 
| 11 | 
            -
              attr_reader : | 
| 11 | 
            +
              attr_reader :transactions_count   # use alias - txn_count - why? why not?
         | 
| 12 | 
            +
              attr_reader :transactions         # use alias - txn       - why? why not?
         | 
| 12 13 | 
             
              attr_reader :previous_hash
         | 
| 13 14 | 
             
              attr_reader :hash
         | 
| 14 15 |  | 
| 15 | 
            -
              def initialize(index,  | 
| 16 | 
            -
                @index | 
| 17 | 
            -
             | 
| 18 | 
            -
                 | 
| 19 | 
            -
                 | 
| 20 | 
            -
                @ | 
| 16 | 
            +
              def initialize(index, transactions, previous_hash, timestamp: nil)
         | 
| 17 | 
            +
                @index              = index
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                ## note: use coordinated universal time (utc)
         | 
| 20 | 
            +
                ##    auto-add timestamp for new blocks (e.g. timestamp is nil)
         | 
| 21 | 
            +
                @timestamp          = timestamp ? timestamp : Time.now.utc
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                ## note: assumes / expects an array for transactions
         | 
| 24 | 
            +
                @transactions       = transactions
         | 
| 25 | 
            +
                @transactions_count = transactions.size
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                @previous_hash      = previous_hash
         | 
| 28 | 
            +
                @hash               = calc_hash
         | 
| 21 29 | 
             
              end
         | 
| 22 30 |  | 
| 23 31 | 
             
              def calc_hash
         | 
| 24 32 | 
             
                sha = Digest::SHA256.new
         | 
| 25 | 
            -
                sha.update( @index.to_s + | 
| 33 | 
            +
                sha.update( @index.to_s +
         | 
| 34 | 
            +
                            @timestamp.to_s +
         | 
| 35 | 
            +
                            @transactions.to_s +
         | 
| 36 | 
            +
                            @transactions_count.to_s +
         | 
| 37 | 
            +
                            @previous_hash )
         | 
| 26 38 | 
             
                sha.hexdigest
         | 
| 27 39 | 
             
              end
         | 
| 28 40 |  | 
| 29 41 |  | 
| 30 42 |  | 
| 31 | 
            -
              def self.first(  | 
| 43 | 
            +
              def self.first( *transactions )    # create genesis (big bang! first) block
         | 
| 44 | 
            +
                ##  note: allow/support splat-* for now for convenience (auto-wraps args into array)
         | 
| 45 | 
            +
                if transactions.size == 1 && transactions[0].is_a?( Array )
         | 
| 46 | 
            +
                  t = transactions[0]   ## "unwrap" array in array
         | 
| 47 | 
            +
                else
         | 
| 48 | 
            +
                  t = transactions      ## use "auto-wrapped" splat array
         | 
| 49 | 
            +
                end
         | 
| 32 50 | 
             
                ## uses index zero (0) and arbitrary previous_hash ('0')
         | 
| 33 | 
            -
                Block.new( 0,  | 
| 51 | 
            +
                Block.new( 0, t, '0'  )
         | 
| 34 52 | 
             
              end
         | 
| 35 53 |  | 
| 36 | 
            -
              def self.next( previous,  | 
| 37 | 
            -
                 | 
| 54 | 
            +
              def self.next( previous, *transactions )
         | 
| 55 | 
            +
                ## note: allow/support splat-* for now for convenience (auto-wraps args into array)
         | 
| 56 | 
            +
                if transactions.size == 1 && transactions[0].is_a?( Array )
         | 
| 57 | 
            +
                  t = transactions[0]   ## "unwrap" array in array
         | 
| 58 | 
            +
                else
         | 
| 59 | 
            +
                  t = transactions      ## use "auto-wrapped" splat array
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
                Block.new( previous.index+1, t, previous.hash )
         | 
| 38 62 | 
             
              end
         | 
| 39 63 |  | 
| 40 64 | 
             
            end  # class Block
         | 
| @@ -9,35 +9,51 @@ class Blockchain | |
| 9 9 |  | 
| 10 10 | 
             
              def initialize( chain=nil, block_class: nil )
         | 
| 11 11 | 
             
                if chain.nil?
         | 
| 12 | 
            -
                  @block_class = block_class | 
| 12 | 
            +
                  @block_class = if block_class
         | 
| 13 | 
            +
                    block_class
         | 
| 14 | 
            +
                  else
         | 
| 15 | 
            +
                    ## check if Block is defined
         | 
| 16 | 
            +
                    ##    if yes, use it othwerwise fallback for ProofOfWork::Block
         | 
| 17 | 
            +
                    defined?( Block ) ? Block : BlockchainLite::ProofOfWork::Block
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 13 20 | 
             
                  b0 = @block_class.first( 'Genesis' )
         | 
| 14 21 | 
             
                  @chain = [b0]
         | 
| 15 22 | 
             
                else
         | 
| 16 23 | 
             
                  @chain = chain    # "wrap" passed in blockchain (in array)
         | 
| 17 | 
            -
                   | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            +
                  @block_class = if block_class
         | 
| 25 | 
            +
                      block_class
         | 
| 26 | 
            +
                    else
         | 
| 27 | 
            +
                      ### no block class configured; use class of first block
         | 
| 28 | 
            +
                      if @chain.first
         | 
| 29 | 
            +
                        @chain.first.class
         | 
| 30 | 
            +
                      else
         | 
| 31 | 
            +
                        ##  todo/fix: throw except if chain is empty (no class configured) - why? why not??
         | 
| 32 | 
            +
                        ##   throw exception on add block if not a block - why? why not??
         | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
                    end
         | 
| 24 35 | 
             
                end
         | 
| 25 36 | 
             
              end
         | 
| 26 37 |  | 
| 27 38 |  | 
| 39 | 
            +
             | 
| 28 40 | 
             
              def last() @chain.last; end     ## return last block in chain
         | 
| 29 41 |  | 
| 30 42 |  | 
| 43 | 
            +
              ###
         | 
| 44 | 
            +
              ##  make method-<< abstract/virtual - why? why not?
         | 
| 45 | 
            +
              ##     must be added by to make sure proper block_class is always used - why? why not??
         | 
| 46 | 
            +
             | 
| 31 47 | 
             
              def <<( arg )
         | 
| 32 | 
            -
                if arg.is_a?  | 
| 48 | 
            +
                if arg.is_a? Array   ## assume its (just) data
         | 
| 33 49 | 
             
                  data = arg
         | 
| 34 50 | 
             
                  bl   = @chain.last
         | 
| 35 51 | 
             
                  b    = @block_class.next( bl, data )
         | 
| 36 52 | 
             
                elsif arg.class.respond_to?( :first ) &&       ## check if respond_to? Block.first? and Block.next? - assume it's a block
         | 
| 37 53 | 
             
                      arg.class.respond_to?( :next )           ##  check/todo: use is_a? @block_class why? why not?
         | 
| 38 54 | 
             
                  b = arg
         | 
| 39 | 
            -
                else  ## fallback; assume  | 
| 40 | 
            -
                  data = arg | 
| 55 | 
            +
                else  ## fallback; assume single transaction record; wrap in array - allow fallback - why? why not??
         | 
| 56 | 
            +
                  data = [arg]
         | 
| 41 57 | 
             
                  bl   = @chain.last
         | 
| 42 58 | 
             
                  b    = @block_class.next( bl, data )
         | 
| 43 59 | 
             
                end
         | 
| @@ -8,36 +8,73 @@ class Block | |
| 8 8 |  | 
| 9 9 | 
             
              attr_reader :index
         | 
| 10 10 | 
             
              attr_reader :timestamp
         | 
| 11 | 
            -
              attr_reader : | 
| 11 | 
            +
              attr_reader :transactions_count   # use alias - txn_count - why? why not?
         | 
| 12 | 
            +
              attr_reader :transactions         # use alias - txn       - why? why not?
         | 
| 12 13 | 
             
              attr_reader :previous_hash
         | 
| 13 | 
            -
              attr_reader :nonce | 
| 14 | 
            +
              attr_reader :nonce                # ("lucky" number used once) - proof of work if hash starts with leading zeros (00)
         | 
| 14 15 | 
             
              attr_reader :hash
         | 
| 15 16 |  | 
| 16 | 
            -
              def initialize(index,  | 
| 17 | 
            -
                @index | 
| 18 | 
            -
             | 
| 19 | 
            -
                 | 
| 17 | 
            +
              def initialize(index, transactions, previous_hash, timestamp: nil, nonce: nil)
         | 
| 18 | 
            +
                @index = index
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                ## note: assumes / expects an array for transactions
         | 
| 21 | 
            +
                @transactions       = transactions
         | 
| 22 | 
            +
                @transactions_count = transactions.size
         | 
| 23 | 
            +
             | 
| 20 24 | 
             
                @previous_hash = previous_hash
         | 
| 21 | 
            -
             | 
| 25 | 
            +
             | 
| 26 | 
            +
                ## note: use coordinated universal time (utc)
         | 
| 27 | 
            +
                @timestamp = timestamp ? timestamp : Time.now.utc
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                if nonce     ## restore pre-computed/mined block (from disk/cache/db/etc.)
         | 
| 30 | 
            +
                   ## todo: check timestamp MUST NOT be nil
         | 
| 31 | 
            +
                   @nonce = nonce
         | 
| 32 | 
            +
                   @hash  = calc_hash
         | 
| 33 | 
            +
                else   ## new block  (mine! e.g. find nonce - "lucky" number used once)
         | 
| 34 | 
            +
                   @nonce, @hash = compute_hash_with_proof_of_work
         | 
| 35 | 
            +
                end
         | 
| 22 36 | 
             
              end
         | 
| 23 37 |  | 
| 24 38 | 
             
              def calc_hash
         | 
| 25 | 
            -
                 | 
| 26 | 
            -
                sha.update( @nonce.to_s + @index.to_s + @timestamp.to_s + @data + @previous_hash )
         | 
| 27 | 
            -
                sha.hexdigest
         | 
| 39 | 
            +
                calc_hash_with_nonce( @nonce )
         | 
| 28 40 | 
             
              end
         | 
| 29 41 |  | 
| 30 42 |  | 
| 31 | 
            -
             | 
| 43 | 
            +
             | 
| 44 | 
            +
              def self.first( *transactions, **opts )    # create genesis (big bang! first) block
         | 
| 45 | 
            +
                ##  note: allow/support splat-* for now for convenience (auto-wraps args into array)
         | 
| 46 | 
            +
                if transactions.size == 1 && transactions[0].is_a?( Array )
         | 
| 47 | 
            +
                  t = transactions[0]   ## "unwrap" array in array
         | 
| 48 | 
            +
                else
         | 
| 49 | 
            +
                  t = transactions      ## use "auto-wrapped" splat array
         | 
| 50 | 
            +
                end
         | 
| 32 51 | 
             
                ## uses index zero (0) and arbitrary previous_hash ('0')
         | 
| 33 | 
            -
                 | 
| 52 | 
            +
                ##  note: pass along (optional) custom timestamp (e.g. used for 1637 etc.)
         | 
| 53 | 
            +
                Block.new( 0, t, '0', timestamp: opts[:timestamp] )
         | 
| 34 54 | 
             
              end
         | 
| 35 55 |  | 
| 36 | 
            -
              def self.next( previous,  | 
| 37 | 
            -
                 | 
| 56 | 
            +
              def self.next( previous, *transactions, **opts )
         | 
| 57 | 
            +
                ## note: allow/support splat-* for now for convenience (auto-wraps args into array)
         | 
| 58 | 
            +
                if transactions.size == 1 && transactions[0].is_a?( Array )
         | 
| 59 | 
            +
                  t = transactions[0]   ## "unwrap" array in array
         | 
| 60 | 
            +
                else
         | 
| 61 | 
            +
                  t = transactions      ## use "auto-wrapped" splat array
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
                Block.new( previous.index+1, t, previous.hash, timestamp: opts[:timestamp] )
         | 
| 38 64 | 
             
              end
         | 
| 39 65 |  | 
| 66 | 
            +
             | 
| 40 67 | 
             
            private
         | 
| 68 | 
            +
              def calc_hash_with_nonce( nonce=0 )
         | 
| 69 | 
            +
                sha = Digest::SHA256.new
         | 
| 70 | 
            +
                sha.update( nonce.to_s +
         | 
| 71 | 
            +
                            @index.to_s +
         | 
| 72 | 
            +
                            @timestamp.to_s +
         | 
| 73 | 
            +
                            @transactions.to_s +
         | 
| 74 | 
            +
                            @transactions_count.to_s +
         | 
| 75 | 
            +
                            @previous_hash )
         | 
| 76 | 
            +
                sha.hexdigest
         | 
| 77 | 
            +
              end
         | 
| 41 78 |  | 
| 42 79 | 
             
              def compute_hash_with_proof_of_work( difficulty='00' )
         | 
| 43 80 | 
             
                nonce = 0
         | 
| @@ -51,12 +88,6 @@ private | |
| 51 88 | 
             
                end
         | 
| 52 89 | 
             
              end
         | 
| 53 90 |  | 
| 54 | 
            -
              def calc_hash_with_nonce( nonce=0 )
         | 
| 55 | 
            -
                sha = Digest::SHA256.new
         | 
| 56 | 
            -
                sha.update( nonce.to_s + @index.to_s + @timestamp.to_s + @data + @previous_hash )
         | 
| 57 | 
            -
                sha.hexdigest
         | 
| 58 | 
            -
              end
         | 
| 59 | 
            -
             | 
| 60 91 | 
             
            end  # class Block
         | 
| 61 92 |  | 
| 62 93 |  | 
    
        data/test/test_block.rb
    CHANGED
    
    | @@ -18,14 +18,78 @@ def test_version | |
| 18 18 | 
             
               assert true  ## (for now) everything ok if we get here
         | 
| 19 19 | 
             
            end
         | 
| 20 20 |  | 
| 21 | 
            +
             | 
| 21 22 | 
             
            def test_example
         | 
| 22 23 |  | 
| 23 24 | 
             
              b0 = Block.first( 'Genesis' )
         | 
| 24 25 | 
             
              b1 = Block.next( b0, 'Transaction Data...' )
         | 
| 25 | 
            -
              b2 = Block.next( b1, 'Transaction Data | 
| 26 | 
            -
              b3 = Block.next( b2 | 
| 26 | 
            +
              b2 = Block.next( b1, 'Transaction Data...', 'Transaction Data...' )
         | 
| 27 | 
            +
              b3 = Block.next( b2 )   ## no transaction data
         | 
| 28 | 
            +
              b4 = Block.next( b3, ['Transaction Data...', 'Transaction Data...'] )
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              blockchain = [b0, b1, b2, b3, b4]
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              pp blockchain
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              assert true  ## (for now) everything ok if we get here
         | 
| 35 | 
            +
            end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            def test_tulips_example
         | 
| 38 | 
            +
              b0 = Block.first(
         | 
| 39 | 
            +
                    { from: "Dutchgrown", to: "Vincent", what: "Tulip Bloemendaal Sunset", qty: 10 },
         | 
| 40 | 
            +
                    { from: "Keukenhof",  to: "Anne",    what: "Tulip Semper Augustus",    qty: 7  } )
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              b1 = Block.next( b0,
         | 
| 43 | 
            +
                    { from: "Flowers", to: "Ruben", what: "Tulip Admiral van Eijck",  qty: 5 },
         | 
| 44 | 
            +
                    { from: "Vicent",  to: "Anne",  what: "Tulip Bloemendaal Sunset", qty: 3 },
         | 
| 45 | 
            +
                    { from: "Anne",    to: "Julia", what: "Tulip Semper Augustus",    qty: 1 },
         | 
| 46 | 
            +
                    { from: "Julia",   to: "Luuk",  what: "Tulip Semper Augustus",    qty: 1 } )
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              b2 = Block.next( b1,
         | 
| 49 | 
            +
                    { from: "Bloom & Blossom", to: "Daisy",   what: "Tulip Admiral of Admirals", qty: 8 },
         | 
| 50 | 
            +
                    { from: "Vincent",         to: "Max",     what: "Tulip Bloemendaal Sunset",  qty: 2 },
         | 
| 51 | 
            +
                    { from: "Anne",            to: "Martijn", what: "Tulip Semper Augustus",     qty: 2 },
         | 
| 52 | 
            +
                    { from: "Ruben",           to: "Julia",   what: "Tulip Admiral van Eijck",   qty: 2 } )
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              blockchain = [b0, b1, b2]
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              pp blockchain
         | 
| 57 | 
            +
             | 
| 58 | 
            +
              assert true  ## (for now) everything ok if we get here
         | 
| 59 | 
            +
            end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
             | 
| 62 | 
            +
            def timestamp1637
         | 
| 63 | 
            +
                ## change year to 1637 :-)
         | 
| 64 | 
            +
                ##   note: time (uses signed integer e.g. epoch/unix time starting in 1970 with 0)
         | 
| 65 | 
            +
                ##  todo: add nano/mili-seconds - why? why not? possible?
         | 
| 66 | 
            +
                now = Time.now.utc.to_datetime
         | 
| 67 | 
            +
                past = DateTime.new( 1637, now.month, now.mday, now.hour, now.min, now.sec, now.zone )
         | 
| 68 | 
            +
                past
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
            def test_tulips_1637_example
         | 
| 72 | 
            +
             | 
| 73 | 
            +
              b0 = Block.first(
         | 
| 74 | 
            +
                    { from: "Dutchgrown", to: "Vincent", what: "Tulip Bloemendaal Sunset", qty: 10 },
         | 
| 75 | 
            +
                    { from: "Keukenhof",  to: "Anne",    what: "Tulip Semper Augustus",    qty: 7  },
         | 
| 76 | 
            +
                    timestamp: timestamp1637 )
         | 
| 77 | 
            +
             | 
| 78 | 
            +
              b1 = Block.next( b0,
         | 
| 79 | 
            +
                    { from: "Flowers", to: "Ruben", what: "Tulip Admiral van Eijck",  qty: 5 },
         | 
| 80 | 
            +
                    { from: "Vicent",  to: "Anne",  what: "Tulip Bloemendaal Sunset", qty: 3 },
         | 
| 81 | 
            +
                    { from: "Anne",    to: "Julia", what: "Tulip Semper Augustus",    qty: 1 },
         | 
| 82 | 
            +
                    { from: "Julia",   to: "Luuk",  what: "Tulip Semper Augustus",    qty: 1 },
         | 
| 83 | 
            +
                    timestamp: timestamp1637 )
         | 
| 84 | 
            +
             | 
| 85 | 
            +
              b2 = Block.next( b1,
         | 
| 86 | 
            +
                    { from: "Bloom & Blossom", to: "Daisy",   what: "Tulip Admiral of Admirals", qty: 8 },
         | 
| 87 | 
            +
                    { from: "Vincent",         to: "Max",     what: "Tulip Bloemendaal Sunset",  qty: 2 },
         | 
| 88 | 
            +
                    { from: "Anne",            to: "Martijn", what: "Tulip Semper Augustus",     qty: 2 },
         | 
| 89 | 
            +
                    { from: "Ruben",           to: "Julia",   what: "Tulip Admiral van Eijck",   qty: 2 },
         | 
| 90 | 
            +
                    timestamp: timestamp1637 )
         | 
| 27 91 |  | 
| 28 | 
            -
              blockchain = [b0, b1, b2 | 
| 92 | 
            +
              blockchain = [b0, b1, b2]
         | 
| 29 93 |  | 
| 30 94 | 
             
              pp blockchain
         | 
| 31 95 |  | 
    
        data/test/test_block_basic.rb
    CHANGED
    
    | @@ -16,10 +16,11 @@ def test_example | |
| 16 16 |  | 
| 17 17 | 
             
              b0 = block_class.first( 'Genesis' )
         | 
| 18 18 | 
             
              b1 = block_class.next( b0, 'Transaction Data...' )
         | 
| 19 | 
            -
              b2 = block_class.next( b1, 'Transaction Data | 
| 20 | 
            -
              b3 = block_class.next( b2 | 
| 19 | 
            +
              b2 = block_class.next( b1, 'Transaction Data...', 'Transaction Data...' )
         | 
| 20 | 
            +
              b3 = block_class.next( b2 )   ## no transaction data
         | 
| 21 | 
            +
              b4 = block_class.next( b3, ['Transaction Data...', 'Transaction Data...'] )
         | 
| 21 22 |  | 
| 22 | 
            -
              blockchain = [b0, b1, b2, b3]
         | 
| 23 | 
            +
              blockchain = [b0, b1, b2, b3, b4]
         | 
| 23 24 |  | 
| 24 25 | 
             
              pp blockchain
         | 
| 25 26 |  | 
| @@ -16,10 +16,11 @@ def test_example | |
| 16 16 |  | 
| 17 17 | 
             
              b0 = block_class.first( 'Genesis' )
         | 
| 18 18 | 
             
              b1 = block_class.next( b0, 'Transaction Data...' )
         | 
| 19 | 
            -
              b2 = block_class.next( b1, 'Transaction Data | 
| 20 | 
            -
              b3 = block_class.next( b2 | 
| 19 | 
            +
              b2 = block_class.next( b1, 'Transaction Data...', 'Transaction Data...' )
         | 
| 20 | 
            +
              b3 = block_class.next( b2 )   ## no transaction data
         | 
| 21 | 
            +
              b4 = block_class.next( b3, ['Transaction Data...', 'Transaction Data...'] )
         | 
| 21 22 |  | 
| 22 | 
            -
              blockchain = [b0, b1, b2, b3]
         | 
| 23 | 
            +
              blockchain = [b0, b1, b2, b3, b4]
         | 
| 23 24 |  | 
| 24 25 | 
             
              pp blockchain
         | 
| 25 26 |  | 
    
        data/test/test_blockchain.rb
    CHANGED
    
    | @@ -14,11 +14,14 @@ def test_new | |
| 14 14 | 
             
              b = Blockchain.new
         | 
| 15 15 |  | 
| 16 16 | 
             
              b << 'Transaction Data...'
         | 
| 17 | 
            +
              b << ['Transaction Data...']
         | 
| 18 | 
            +
              b << ['Transaction Data...', 'Transaction Data...']
         | 
| 19 | 
            +
              b << []   ## empty block (no transactions)
         | 
| 17 20 |  | 
| 18 21 | 
             
              ## add do-it-yourself built block
         | 
| 19 | 
            -
              b << Block.next( b.last, 'Transaction Data | 
| 22 | 
            +
              b << Block.next( b.last, 'Transaction Data...' )
         | 
| 20 23 |  | 
| 21 | 
            -
              b << ' | 
| 24 | 
            +
              b << 'Transaction Data...'
         | 
| 22 25 |  | 
| 23 26 | 
             
              pp b
         | 
| 24 27 |  | 
| @@ -31,8 +34,8 @@ def test_with_block_class | |
| 31 34 | 
             
              b = Blockchain.new( block_class: BlockchainLite::Basic::Block )
         | 
| 32 35 |  | 
| 33 36 | 
             
              b << 'Transaction Data...'
         | 
| 34 | 
            -
              b << 'Transaction Data | 
| 35 | 
            -
              b << ' | 
| 37 | 
            +
              b << 'Transaction Data...'
         | 
| 38 | 
            +
              b << 'Transaction Data...'
         | 
| 36 39 |  | 
| 37 40 | 
             
              pp b
         | 
| 38 41 |  | 
| @@ -44,8 +47,8 @@ def test_wrap | |
| 44 47 |  | 
| 45 48 | 
             
              b0 = Block.first( 'Genesis' )
         | 
| 46 49 | 
             
              b1 = Block.next( b0, 'Transaction Data...' )
         | 
| 47 | 
            -
              b2 = Block.next( b1, 'Transaction Data | 
| 48 | 
            -
              b3 = Block.next( b2, ' | 
| 50 | 
            +
              b2 = Block.next( b1, 'Transaction Data...' )
         | 
| 51 | 
            +
              b3 = Block.next( b2, 'Transaction Data...' )
         | 
| 49 52 | 
             
              blockchain = [b0, b1, b2, b3]
         | 
| 50 53 |  | 
| 51 54 | 
             
              b = Blockchain.new( blockchain )
         | 
| @@ -58,7 +61,7 @@ def test_wrap | |
| 58 61 | 
             
              assert_equal true,  b.valid?
         | 
| 59 62 |  | 
| 60 63 | 
             
              ## corrupt data in block in chain
         | 
| 61 | 
            -
              b2.instance_eval %{ @ | 
| 64 | 
            +
              b2.instance_eval %{ @transactions=['XXXXXXX'] }
         | 
| 62 65 |  | 
| 63 66 | 
             
              assert_equal true,   b.broken?
         | 
| 64 67 | 
             
              assert_equal false,  b.valid?
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: blockchain-lite
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1. | 
| 4 | 
            +
              version: 1.2.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Gerald Bauer
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2017- | 
| 11 | 
            +
            date: 2017-12-17 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: rdoc
         |