ciri 0.0.1 → 0.0.2

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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +8 -3
  3. data/README.md +5 -0
  4. data/Rakefile +24 -2
  5. data/ciri-rlp/.gitignore +11 -0
  6. data/ciri-rlp/.rspec +3 -0
  7. data/ciri-rlp/.travis.yml +5 -0
  8. data/ciri-rlp/CODE_OF_CONDUCT.md +74 -0
  9. data/ciri-rlp/Gemfile +6 -0
  10. data/ciri-rlp/Gemfile.lock +39 -0
  11. data/ciri-rlp/LICENSE.txt +21 -0
  12. data/ciri-rlp/README.md +98 -0
  13. data/ciri-rlp/Rakefile +6 -0
  14. data/ciri-rlp/bin/console +14 -0
  15. data/ciri-rlp/bin/setup +8 -0
  16. data/ciri-rlp/ciri-rlp.gemspec +28 -0
  17. data/{lib → ciri-rlp/lib}/ciri/rlp.rb +2 -2
  18. data/ciri-rlp/lib/ciri/rlp/decode.rb +144 -0
  19. data/ciri-rlp/lib/ciri/rlp/encode.rb +140 -0
  20. data/ciri-rlp/lib/ciri/rlp/serializable.rb +241 -0
  21. data/ciri-rlp/lib/ciri/rlp/version.rb +5 -0
  22. data/ciri-rlp/spec/ciri/fixture_spec.rb +95 -0
  23. data/ciri-rlp/spec/ciri/rlp/decode_spec.rb +68 -0
  24. data/ciri-rlp/spec/ciri/rlp/encode_spec.rb +72 -0
  25. data/ciri-rlp/spec/ciri/rlp/serializable_spec.rb +147 -0
  26. data/ciri-rlp/spec/ciri/rlp_spec.rb +5 -0
  27. data/ciri-rlp/spec/spec_helper.rb +14 -0
  28. data/ciri-utils/.gitignore +11 -0
  29. data/ciri-utils/.rspec +3 -0
  30. data/ciri-utils/.travis.yml +5 -0
  31. data/ciri-utils/CODE_OF_CONDUCT.md +74 -0
  32. data/ciri-utils/Gemfile +6 -0
  33. data/ciri-utils/Gemfile.lock +37 -0
  34. data/ciri-utils/LICENSE.txt +21 -0
  35. data/ciri-utils/README.md +61 -0
  36. data/ciri-utils/Rakefile +6 -0
  37. data/ciri-utils/bin/console +14 -0
  38. data/ciri-utils/bin/setup +8 -0
  39. data/ciri-utils/ciri-utils.gemspec +28 -0
  40. data/{lib → ciri-utils/lib}/ciri/utils.rb +9 -33
  41. data/{lib → ciri-utils/lib}/ciri/utils/logger.rb +0 -0
  42. data/{lib → ciri-utils/lib}/ciri/utils/number.rb +15 -12
  43. data/ciri-utils/lib/ciri/utils/version.rb +5 -0
  44. data/ciri-utils/spec/ciri/utils_spec.rb +5 -0
  45. data/ciri-utils/spec/spec_helper.rb +14 -0
  46. data/ciri.gemspec +6 -3
  47. data/lib/ciri/bloom_filter.rb +83 -0
  48. data/lib/ciri/chain.rb +67 -130
  49. data/lib/ciri/chain/header.rb +2 -2
  50. data/lib/ciri/chain/header_chain.rb +136 -0
  51. data/lib/ciri/chain/transaction.rb +1 -1
  52. data/lib/ciri/crypto.rb +2 -2
  53. data/lib/ciri/db/account_db.rb +145 -0
  54. data/lib/ciri/db/backend/memory.rb +24 -1
  55. data/lib/ciri/devp2p/peer.rb +1 -1
  56. data/lib/ciri/devp2p/rlpx/connection.rb +5 -5
  57. data/lib/ciri/eth/peer.rb +3 -3
  58. data/lib/ciri/eth/protocol_manage.rb +3 -3
  59. data/lib/ciri/eth/protocol_messages.rb +27 -26
  60. data/lib/ciri/ethash.rb +18 -3
  61. data/lib/ciri/evm.rb +101 -56
  62. data/lib/ciri/{utils/lib_c.rb → evm/errors.rb} +28 -18
  63. data/lib/ciri/evm/instruction.rb +3 -1
  64. data/lib/ciri/evm/{serialize.rb → log_entry.rb} +9 -27
  65. data/lib/ciri/evm/machine_state.rb +21 -2
  66. data/lib/ciri/evm/op.rb +19 -16
  67. data/lib/ciri/evm/state.rb +61 -0
  68. data/lib/ciri/evm/vm.rb +78 -102
  69. data/lib/ciri/forks.rb +1 -4
  70. data/lib/ciri/forks/base.rb +55 -0
  71. data/lib/ciri/forks/frontier.rb +37 -10
  72. data/lib/ciri/forks/frontier/cost.rb +186 -0
  73. data/lib/ciri/key.rb +1 -1
  74. data/lib/ciri/pow.rb +1 -1
  75. data/lib/ciri/rlp/decode.rb +6 -3
  76. data/lib/ciri/rlp/encode.rb +10 -10
  77. data/lib/ciri/rlp/serializable.rb +12 -9
  78. data/lib/ciri/serialize.rb +58 -0
  79. data/lib/ciri/trie.rb +362 -0
  80. data/lib/ciri/trie/nibbles.rb +97 -0
  81. data/lib/ciri/trie/nodes.rb +187 -0
  82. data/lib/ciri/{evm → types}/account.rb +20 -13
  83. data/lib/ciri/types/address.rb +16 -11
  84. data/lib/ciri/types/number.rb +73 -0
  85. data/lib/ciri/types/receipt.rb +57 -0
  86. data/lib/ciri/version.rb +1 -1
  87. metadata +82 -19
  88. data/lib/ciri/evm/forks/frontier.rb +0 -183
data/lib/ciri/chain.rb CHANGED
@@ -22,9 +22,13 @@
22
22
 
23
23
 
24
24
  require 'forwardable'
25
+ require 'ciri/evm'
26
+ require 'ciri/utils/logger'
27
+ require_relative 'chain/header_chain'
25
28
  require_relative 'chain/block'
26
29
  require_relative 'chain/header'
27
30
  require_relative 'chain/transaction'
31
+ require_relative 'types/receipt'
28
32
  require_relative 'pow'
29
33
 
30
34
  module Ciri
@@ -32,6 +36,7 @@ module Ciri
32
36
  # Chain manipulate logic
33
37
  # store via rocksdb
34
38
  class Chain
39
+ include Utils::Logger
35
40
 
36
41
  class Error < StandardError
37
42
  end
@@ -42,151 +47,75 @@ module Ciri
42
47
  class InvalidBlockError < Error
43
48
  end
44
49
 
45
- # HeaderChain
46
- # store headers
47
- class HeaderChain
48
- HEAD = 'head'
49
- GENESIS = 'genesis'
50
- HEADER_PREFIX = 'h'
51
- TD_SUFFIX = 't'
52
- NUM_SUFFIX = 'n'
53
-
54
- attr_reader :store, :byzantium_block, :homestead_block
55
-
56
- def initialize(store, byzantium_block: nil, homestead_block: nil)
57
- @store = store
58
- @byzantium_block = byzantium_block
59
- @homestead_block = homestead_block
60
- end
61
-
62
- def head
63
- encoded = store[HEAD]
64
- encoded && Header.rlp_decode!(encoded)
65
- end
50
+ extend Forwardable
66
51
 
67
- def head=(header)
68
- store[HEAD] = header.rlp_encode!
69
- end
52
+ BODY_PREFIX = 'b'
70
53
 
71
- def get_header(hash)
72
- encoded = store[HEADER_PREFIX + hash]
73
- encoded && Header.rlp_decode!(encoded)
74
- end
54
+ def_delegators :@header_chain, :head, :total_difficulty, :get_header_by_number, :get_header
75
55
 
76
- def get_header_by_number(number)
77
- hash = get_header_hash_by_number(number)
78
- hash && get_header(hash)
79
- end
56
+ attr_reader :genesis, :network_id, :store, :header_chain
80
57
 
81
- def valid?(header)
82
- # ignore genesis header if there not exist one
83
- return true if header.number == 0 && get_header_by_number(0).nil?
84
-
85
- parent_header = get_header(header.parent_hash)
86
- return false unless parent_header
87
- # check height
88
- return false unless parent_header.number + 1 == header.number
89
- # check timestamp
90
- return false unless parent_header.timestamp < header.timestamp
91
-
92
- # check gas limit range
93
- parent_gas_limit = parent_header.gas_limit
94
- gas_limit_max = parent_gas_limit + parent_gas_limit / 1024
95
- gas_limit_min = parent_gas_limit - parent_gas_limit / 1024
96
- gas_limit = header.gas_limit
97
- return false unless gas_limit >= 5000 && gas_limit > gas_limit_min && gas_limit < gas_limit_max
98
- return false unless calculate_difficulty(header, parent_header) == header.difficulty
99
-
100
- # check pow
101
- begin
102
- POW.check_pow(header.number, header.mining_hash, header.mix_hash, header.nonce, header.difficulty)
103
- rescue POW::InvalidError
104
- return false
105
- end
58
+ def initialize(store, genesis:, network_id:, evm: nil, byzantium_block: nil, homestead_block: nil)
59
+ @store = store
60
+ @header_chain = HeaderChain.new(store, byzantium_block: byzantium_block, homestead_block: homestead_block)
61
+ @genesis = genesis
62
+ @network_id = network_id
63
+ @evm = evm
64
+ load_or_init_store
65
+ end
106
66
 
107
- true
108
- end
67
+ # run block
68
+ def import_block(block)
69
+ validate_block(block)
70
+ write_block(block)
109
71
 
110
- # calculate header difficulty
111
- # you can find explain in Ethereum yellow paper: Block Header Validity section.
112
- def calculate_difficulty(header, parent_header)
113
- return header.difficulty if header.number == 0
114
-
115
- x = parent_header.difficulty / 2048
116
- y = header.ommers_hash == Utils::BLANK_SHA3 ? 1 : 2
117
-
118
- # handle byzantium fork
119
- # https://github.com/ethereum/EIPs/blob/181867ae830df5419eb9982d2a24797b2dcad28f/EIPS/eip-609.md
120
- # https://github.com/ethereum/EIPs/blob/984cf5de90bbf5fbe7e49be227b0c2f9567e661e/EIPS/eip-100.md
121
- byzantium_fork = byzantium_block && header.number > byzantium_block
122
- # https://github.com/ethereum/EIPs/blob/984cf5de90bbf5fbe7e49be227b0c2f9567e661e/EIPS/eip-2.md
123
- homestead_fork = homestead_block && header.number > homestead_block
124
-
125
- time_factor = if byzantium_fork
126
- [y - (header.timestamp - parent_header.timestamp) / 9, -99].max
127
- elsif homestead_fork
128
- [1 - (header.timestamp - parent_header.timestamp) / 10, -99].max
129
- else
130
- (header.timestamp - parent_header.timestamp) < 13 ? 1 : -1
131
- end
132
-
133
- # difficulty bomb
134
- height = byzantium_fork ? [(header.number - 3000000), 0].max : header.number
135
- height_factor = 2 ** (height / 100000 - 2)
136
-
137
- difficulty = (parent_header.difficulty + x * time_factor + height_factor).to_i
138
- [header.difficulty, difficulty].max
139
- end
72
+ # update state
73
+ # apply_changes
74
+ end
140
75
 
141
- # write header
142
- def write(header)
143
- hash = header.get_hash
144
- # get total difficulty
145
- td = if header.number == 0
146
- header.difficulty
147
- else
148
- parent_header = get_header(header.parent_hash)
149
- raise "can't find parent from db" unless parent_header
150
- parent_td = total_difficulty(parent_header.get_hash)
151
- parent_td + header.difficulty
152
- end
153
- # write header and td
154
- store.batch do |b|
155
- b.put(HEADER_PREFIX + hash, header.rlp_encode!)
156
- b.put(HEADER_PREFIX + hash + TD_SUFFIX, RLP.encode(td, Integer))
76
+ # validate block, effect current state
77
+ def validate_block(block)
78
+ raise InvalidBlockError.new('invalid header') unless @header_chain.valid?(block.header)
79
+ # valid ommers
80
+ raise InvalidBlockError.new('ommers blocks can not more than 2') if block.ommers.size > 2
81
+ block.ommers.each do |ommer|
82
+ unless is_kin?(ommer, get_block(block.header.parent_hash), 6)
83
+ raise InvalidBlockError.new("invalid ommer relation")
157
84
  end
158
85
  end
159
86
 
160
- def write_header_hash_number(header_hash, number)
161
- enc_number = Utils.big_endian_encode number
162
- store[HEADER_PREFIX + enc_number + NUM_SUFFIX] = header_hash
87
+ # valid transactions and gas
88
+ begin
89
+ receipts = @evm.transition(block)
90
+ rescue EVM::InvalidTransition => e
91
+ raise InvalidBlockError.new(e.message)
163
92
  end
164
93
 
165
- def get_header_hash_by_number(number)
166
- enc_number = Utils.big_endian_encode number
167
- store[HEADER_PREFIX + enc_number + NUM_SUFFIX]
94
+ # verify state root
95
+ if @evm.state_root != block.header.state_root
96
+ error("incorrect state_root, evm: #{Utils.to_hex @evm.state_root}, header: #{Utils.to_hex block.header.state_root} height: #{block.header.number}")
97
+ raise InvalidBlockError.new("incorrect state_root")
168
98
  end
169
99
 
170
- def total_difficulty(header_hash = head.nil? ? nil : head.get_hash)
171
- return 0 if header_hash.nil?
172
- RLP.decode(store[HEADER_PREFIX + header_hash + TD_SUFFIX], Integer)
100
+ # verify receipts root
101
+ trie = Trie.new
102
+ receipts.each_with_index do |r, i|
103
+ trie[RLP.encode(i)] = RLP.encode(r)
173
104
  end
174
- end
175
-
176
- extend Forwardable
177
105
 
178
- BODY_PREFIX = 'b'
179
-
180
- def_delegators :@header_chain, :head, :total_difficulty, :get_header_by_number, :get_header
106
+ if trie.root_hash != block.header.receipts_root
107
+ raise InvalidBlockError.new("incorrect receipts_root")
108
+ end
181
109
 
182
- attr_reader :genesis, :network_id, :store, :header_chain
110
+ # verify state root
111
+ trie = Trie.new
112
+ block.transactions.each_with_index do |t, i|
113
+ trie[RLP.encode(i)] = RLP.encode(t)
114
+ end
183
115
 
184
- def initialize(store, genesis:, network_id:, byzantium_block: nil, homestead_block: nil)
185
- @store = store
186
- @header_chain = HeaderChain.new(store, byzantium_block: byzantium_block, homestead_block: homestead_block)
187
- @genesis = genesis
188
- @network_id = network_id
189
- load_or_init_store
116
+ if trie.root_hash != block.header.transactions_root
117
+ raise InvalidBlockError.new("incorrect transactions_root")
118
+ end
190
119
  end
191
120
 
192
121
  def genesis_hash
@@ -223,17 +152,17 @@ module Ciri
223
152
 
224
153
  def get_block(hash)
225
154
  encoded = store[BODY_PREFIX + hash]
226
- encoded && Block.rlp_decode!(encoded)
155
+ encoded && Block.rlp_decode(encoded)
227
156
  end
228
157
 
229
158
  def write_block(block)
230
159
  # write header
231
160
  header = block.header
232
- raise InvalidHeaderError.new("invalid header: #{header.number}") unless @header_chain.valid?(header)
161
+ # raise InvalidHeaderError.new("invalid header: #{header.number}") unless @header_chain.valid?(header)
233
162
  @header_chain.write(header)
234
163
 
235
164
  # write body
236
- store[BODY_PREFIX + header.get_hash] = block.rlp_encode!
165
+ store[BODY_PREFIX + header.get_hash] = block.rlp_encode
237
166
 
238
167
  td = total_difficulty(header.get_hash)
239
168
 
@@ -251,6 +180,14 @@ module Ciri
251
180
 
252
181
  private
253
182
 
183
+ def is_kin?(ommer, parent, n)
184
+ return false if n == 0
185
+ return true if get_header(ommer.parent_hash) == get_header(parent.header.parent_hash) &&
186
+ ommer != parent.header &&
187
+ !parent.ommers.include?(ommer)
188
+ is_kin?(ommer, get_block(parent.header.parent_hash), n - 1)
189
+ end
190
+
254
191
  # reorg chain
255
192
  def reorg_chain(new_block, old_block)
256
193
  new_chain = []
@@ -48,12 +48,12 @@ module Ciri
48
48
 
49
49
  # header hash
50
50
  def get_hash
51
- Utils.sha3(rlp_encode!)
51
+ Utils.sha3(rlp_encode)
52
52
  end
53
53
 
54
54
  # mining_hash, used for mining
55
55
  def mining_hash
56
- Utils.sha3(rlp_encode! skip_keys: [:mix_hash, :nonce])
56
+ Utils.sha3(rlp_encode skip_keys: [:mix_hash, :nonce])
57
57
  end
58
58
 
59
59
  end
@@ -0,0 +1,136 @@
1
+ # HeaderChain
2
+ # store headers
3
+
4
+ module Ciri
5
+ class Chain
6
+ class HeaderChain
7
+ HEAD = 'head'
8
+ GENESIS = 'genesis'
9
+ HEADER_PREFIX = 'h'
10
+ TD_SUFFIX = 't'
11
+ NUM_SUFFIX = 'n'
12
+
13
+ attr_reader :store, :byzantium_block, :homestead_block
14
+
15
+ def initialize(store, byzantium_block: nil, homestead_block: nil)
16
+ @store = store
17
+ @byzantium_block = byzantium_block
18
+ @homestead_block = homestead_block
19
+ end
20
+
21
+ def head
22
+ encoded = store[HEAD]
23
+ encoded && Header.rlp_decode(encoded)
24
+ end
25
+
26
+ def head=(header)
27
+ store[HEAD] = header.rlp_encode
28
+ end
29
+
30
+ def get_header(hash)
31
+ encoded = store[HEADER_PREFIX + hash]
32
+ encoded && Header.rlp_decode(encoded)
33
+ end
34
+
35
+ def get_header_by_number(number)
36
+ hash = get_header_hash_by_number(number)
37
+ hash && get_header(hash)
38
+ end
39
+
40
+ def valid?(header)
41
+ # ignore genesis header if there not exist one
42
+ return true if header.number == 0 && get_header_by_number(0).nil?
43
+
44
+ parent_header = get_header(header.parent_hash)
45
+ return false unless parent_header
46
+ # check height
47
+ return false unless parent_header.number + 1 == header.number
48
+ # check timestamp
49
+ return false unless parent_header.timestamp < header.timestamp
50
+
51
+ # check gas limit range
52
+ parent_gas_limit = parent_header.gas_limit
53
+ gas_limit_max = parent_gas_limit + parent_gas_limit / 1024
54
+ gas_limit_min = parent_gas_limit - parent_gas_limit / 1024
55
+ gas_limit = header.gas_limit
56
+ return false unless gas_limit >= 5000 && gas_limit > gas_limit_min && gas_limit < gas_limit_max
57
+ return false unless calculate_difficulty(header, parent_header) == header.difficulty
58
+
59
+ # check pow
60
+ begin
61
+ POW.check_pow(header.number, header.mining_hash, header.mix_hash, header.nonce, header.difficulty)
62
+ rescue POW::InvalidError
63
+ return false
64
+ end
65
+
66
+ true
67
+ end
68
+
69
+ # calculate header difficulty
70
+ # you can find explain in Ethereum yellow paper: Block Header Validity section.
71
+ def calculate_difficulty(header, parent_header)
72
+ return header.difficulty if header.number == 0
73
+
74
+ x = parent_header.difficulty / 2048
75
+ y = header.ommers_hash == Utils::BLANK_SHA3 ? 1 : 2
76
+
77
+ # handle byzantium fork
78
+ # https://github.com/ethereum/EIPs/blob/181867ae830df5419eb9982d2a24797b2dcad28f/EIPS/eip-609.md
79
+ # https://github.com/ethereum/EIPs/blob/984cf5de90bbf5fbe7e49be227b0c2f9567e661e/EIPS/eip-100.md
80
+ byzantium_fork = byzantium_block && header.number > byzantium_block
81
+ # https://github.com/ethereum/EIPs/blob/984cf5de90bbf5fbe7e49be227b0c2f9567e661e/EIPS/eip-2.md
82
+ homestead_fork = homestead_block && header.number > homestead_block
83
+
84
+ time_factor = if byzantium_fork
85
+ [y - (header.timestamp - parent_header.timestamp) / 9, -99].max
86
+ elsif homestead_fork
87
+ [1 - (header.timestamp - parent_header.timestamp) / 10, -99].max
88
+ else
89
+ (header.timestamp - parent_header.timestamp) < 13 ? 1 : -1
90
+ end
91
+
92
+ # difficulty bomb
93
+ height = byzantium_fork ? [(header.number - 3000000), 0].max : header.number
94
+ height_factor = 2 ** (height / 100000 - 2)
95
+
96
+ difficulty = (parent_header.difficulty + x * time_factor + height_factor).to_i
97
+ [header.difficulty, difficulty].max
98
+ end
99
+
100
+ # write header
101
+ def write(header)
102
+ hash = header.get_hash
103
+ # get total difficulty
104
+ td = if header.number == 0
105
+ header.difficulty
106
+ else
107
+ parent_header = get_header(header.parent_hash)
108
+ raise "can't find parent from db" unless parent_header
109
+ parent_td = total_difficulty(parent_header.get_hash)
110
+ parent_td + header.difficulty
111
+ end
112
+ # write header and td
113
+ store.batch do |b|
114
+ b.put(HEADER_PREFIX + hash, header.rlp_encode)
115
+ b.put(HEADER_PREFIX + hash + TD_SUFFIX, RLP.encode(td, Integer))
116
+ end
117
+ end
118
+
119
+ def write_header_hash_number(header_hash, number)
120
+ enc_number = Utils.big_endian_encode number
121
+ store[HEADER_PREFIX + enc_number + NUM_SUFFIX] = header_hash
122
+ end
123
+
124
+ def get_header_hash_by_number(number)
125
+ enc_number = Utils.big_endian_encode number
126
+ store[HEADER_PREFIX + enc_number + NUM_SUFFIX]
127
+ end
128
+
129
+ def total_difficulty(header_hash = head.nil? ? nil : head.get_hash)
130
+ return 0 if header_hash.nil?
131
+ RLP.decode(store[HEADER_PREFIX + header_hash + TD_SUFFIX], Integer)
132
+ end
133
+
134
+ end
135
+ end
136
+ end
@@ -97,7 +97,7 @@ module Ciri
97
97
  end
98
98
 
99
99
  def get_hash
100
- Utils.sha3 rlp_encode!
100
+ Utils.sha3 rlp_encode
101
101
  end
102
102
 
103
103
  # validate transaction
data/lib/ciri/crypto.rb CHANGED
@@ -67,8 +67,8 @@ module Ciri
67
67
  end
68
68
 
69
69
  def signature
70
- @signature ||= Utils.big_endian_encode_to_size(@r, "\x00".b, size: 32) +
71
- Utils.big_endian_encode_to_size(@s, "\x00".b, size: 32) +
70
+ @signature ||= Utils.big_endian_encode(@r, "\x00".b, size: 32) +
71
+ Utils.big_endian_encode(@s, "\x00".b, size: 32) +
72
72
  Utils.big_endian_encode(@v, "\x00".b)
73
73
  end
74
74
 
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018, by Jiang Jinyang. <https://justjjy.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+
24
+ require 'ciri/trie'
25
+ require 'ciri/types/account'
26
+ require 'ciri/serialize'
27
+ require 'ciri/utils/logger'
28
+
29
+ module Ciri
30
+ module DB
31
+ class AccountDB
32
+
33
+ include Serialize
34
+ include Utils::Logger
35
+
36
+ attr_reader :db
37
+
38
+ def initialize(db, root_hash: nil)
39
+ @db = db
40
+ root_hash ||= Trie::BLANK_NODE_HASH
41
+ @trie = Trie.new(db: @db, root_hash: root_hash, prune: true)
42
+ end
43
+
44
+ def root_hash
45
+ @trie.root_hash
46
+ end
47
+
48
+ def store(address, key, value)
49
+ account = find_account address
50
+ trie = Trie.new(db: @db, root_hash: account.storage_root)
51
+
52
+ converted_key = convert_key Utils.big_endian_encode(key, size: 32)
53
+
54
+ if value && value != 0
55
+ trie[converted_key] = RLP.encode(value)
56
+ else
57
+ trie.delete(converted_key)
58
+ end
59
+ account.storage_root = trie.root_hash
60
+ update_account(address, account)
61
+ end
62
+
63
+ def fetch(address, key)
64
+ # remove unnecessary null byte from key
65
+ converted_key = convert_key Utils.big_endian_encode(key, size: 32)
66
+ account = find_account address
67
+ trie = Trie.new(db: @db, root_hash: account.storage_root)
68
+ value = trie[converted_key]
69
+ value.empty? ? 0 : RLP.decode(value, Integer)
70
+ end
71
+
72
+ def set_nonce(address, nonce)
73
+ account = find_account(address)
74
+ account.nonce = nonce
75
+ update_account(address, account)
76
+ end
77
+
78
+ def increment_nonce(address, value = 1)
79
+ account = find_account(address)
80
+ account.nonce += value
81
+ update_account(address, account)
82
+ end
83
+
84
+ def set_balance(address, balance)
85
+ account = find_account(address)
86
+ account.balance = balance
87
+ update_account(address, account)
88
+ end
89
+
90
+ def add_balance(address, value)
91
+ account = find_account(address)
92
+ account.balance += value
93
+ raise "value can't be negative" if account.balance < 0
94
+ update_account(address, account)
95
+ end
96
+
97
+ def set_account_code(address, code)
98
+ code ||= ''.b
99
+ account = find_account(address)
100
+ account.code_hash = Utils.sha3(code)
101
+ update_account(address, account)
102
+ db[account.code_hash] = code
103
+ end
104
+
105
+ def get_account_code(address)
106
+ db[find_account(address).code_hash] || ''.b
107
+ end
108
+
109
+ def touch_account(address)
110
+ update_account(address, find_account(address))
111
+ end
112
+
113
+ def find_account(address)
114
+ rlp_encoded_account = @trie[convert_key address]
115
+ if rlp_encoded_account.nil? || rlp_encoded_account.size == 0
116
+ Types::Account.new_empty
117
+ else
118
+ Types::Account.rlp_decode(rlp_encoded_account)
119
+ end
120
+ end
121
+
122
+ def delete_account(address)
123
+ @trie.delete(convert_key address)
124
+ end
125
+
126
+ def account_dead?(address)
127
+ find_account(address).empty?
128
+ end
129
+
130
+ private
131
+
132
+ def update_account(address, account)
133
+ debug 'update account'
134
+ debug Utils.to_hex(address)
135
+ debug account.serializable_attributes
136
+ @trie[convert_key address] = Types::Account.rlp_encode account
137
+ end
138
+
139
+ def convert_key(key)
140
+ Utils.sha3 key.to_s
141
+ end
142
+
143
+ end
144
+ end
145
+ end