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
|