ciri 0.0.0 → 0.0.1

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +14 -0
  3. data/.rspec +2 -1
  4. data/.travis.yml +11 -4
  5. data/Gemfile.lock +3 -0
  6. data/README.md +44 -34
  7. data/Rakefile +47 -4
  8. data/ciri.gemspec +13 -12
  9. data/docker/Base +34 -0
  10. data/lib/ciri/actor.rb +223 -0
  11. data/lib/ciri/chain.rb +293 -0
  12. data/lib/ciri/chain/block.rb +47 -0
  13. data/lib/ciri/chain/header.rb +62 -0
  14. data/lib/ciri/chain/transaction.rb +145 -0
  15. data/lib/ciri/crypto.rb +58 -5
  16. data/lib/ciri/db/backend/memory.rb +68 -0
  17. data/lib/ciri/db/backend/rocks.rb +104 -0
  18. data/lib/ciri/db/backend/rocks_db.rb +278 -0
  19. data/lib/ciri/devp2p/peer.rb +10 -2
  20. data/lib/ciri/devp2p/protocol.rb +11 -3
  21. data/lib/ciri/devp2p/protocol_io.rb +6 -3
  22. data/lib/ciri/devp2p/rlpx.rb +1 -0
  23. data/lib/ciri/devp2p/rlpx/encryption_handshake.rb +1 -1
  24. data/lib/ciri/devp2p/rlpx/frame_io.rb +1 -1
  25. data/lib/ciri/devp2p/rlpx/message.rb +4 -4
  26. data/lib/ciri/devp2p/server.rb +14 -13
  27. data/lib/ciri/eth.rb +33 -0
  28. data/lib/ciri/eth/peer.rb +64 -0
  29. data/lib/ciri/eth/protocol_manage.rb +122 -0
  30. data/lib/ciri/eth/protocol_messages.rb +158 -0
  31. data/lib/ciri/eth/synchronizer.rb +188 -0
  32. data/lib/ciri/ethash.rb +123 -0
  33. data/lib/ciri/evm.rb +140 -0
  34. data/lib/ciri/evm/account.rb +50 -0
  35. data/lib/ciri/evm/block_info.rb +31 -0
  36. data/lib/ciri/evm/forks/frontier.rb +183 -0
  37. data/lib/ciri/evm/instruction.rb +92 -0
  38. data/lib/ciri/evm/machine_state.rb +81 -0
  39. data/lib/ciri/evm/op.rb +536 -0
  40. data/lib/ciri/evm/serialize.rb +60 -0
  41. data/lib/ciri/evm/sub_state.rb +64 -0
  42. data/lib/ciri/evm/vm.rb +379 -0
  43. data/lib/ciri/forks.rb +38 -0
  44. data/lib/ciri/forks/frontier.rb +43 -0
  45. data/lib/ciri/key.rb +7 -1
  46. data/lib/ciri/pow.rb +95 -0
  47. data/lib/ciri/rlp.rb +3 -53
  48. data/lib/ciri/rlp/decode.rb +100 -40
  49. data/lib/ciri/rlp/encode.rb +95 -34
  50. data/lib/ciri/rlp/serializable.rb +61 -91
  51. data/lib/ciri/types/address.rb +70 -0
  52. data/lib/ciri/types/errors.rb +36 -0
  53. data/lib/ciri/utils.rb +45 -13
  54. data/lib/ciri/utils/lib_c.rb +46 -0
  55. data/lib/ciri/utils/logger.rb +99 -0
  56. data/lib/ciri/utils/number.rb +67 -0
  57. data/lib/ciri/version.rb +1 -1
  58. metadata +67 -7
  59. data/lib/ciri/devp2p/actor.rb +0 -224
@@ -0,0 +1,60 @@
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/types/address'
25
+
26
+ module Ciri
27
+ class EVM
28
+ module Serialize
29
+
30
+ extend self
31
+
32
+ def serialize(item)
33
+ case item
34
+ when Integer
35
+ Utils.big_endian_encode(item)
36
+ when Types::Address
37
+ item.to_s
38
+ else
39
+ item
40
+ end
41
+ end
42
+
43
+ def deserialize(type, item)
44
+ if type == Integer && !item.is_a?(Integer)
45
+ Utils.big_endian_decode(item.to_s)
46
+ elsif type == Types::Address && !item.is_a?(Types::Address)
47
+ # check if address represent in Integer
48
+ item = Utils.big_endian_encode(item) if item.is_a?(Integer)
49
+ Types::Address.new(item.size >= 20 ? item[-20..-1] : ''.b)
50
+ elsif type.nil?
51
+ # get serialized word
52
+ serialize(item).rjust(32, "\x00".b)
53
+ else
54
+ item
55
+ end
56
+ end
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,64 @@
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
+ module Ciri
25
+ class EVM
26
+
27
+ # sub state contained changed accounts and log_series
28
+ class SubState
29
+
30
+ EMPTY = SubState.new.freeze
31
+
32
+ attr_reader :suicide_accounts, :log_series, :touched_accounts, :refunds
33
+
34
+ def initialize(suicide_accounts: [], log_series: [], touched_accounts: [], refunds: [])
35
+ @suicide_accounts = Set.new(suicide_accounts)
36
+ @log_series = log_series
37
+ @touched_accounts = Set.new(touched_accounts)
38
+ @refunds = Set.new(refunds)
39
+ end
40
+
41
+ # support safety copy
42
+ def initialize_copy(orig)
43
+ super
44
+ @suicide_accounts = orig.suicide_accounts.dup
45
+ @log_series = orig.log_series.dup
46
+ @touched_accounts = orig.touched_accounts.dup
47
+ @refunds = orig.refunds.dup
48
+ end
49
+
50
+ def add_refund_account(account)
51
+ @refunds.add account
52
+ end
53
+
54
+ def add_touched_account(account)
55
+ @touched_accounts.add account
56
+ end
57
+
58
+ def add_suicide_account(account)
59
+ @suicide_accounts.add account
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,379 @@
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_relative 'machine_state'
25
+ require_relative 'instruction'
26
+ require_relative 'sub_state'
27
+ require_relative 'block_info'
28
+ require_relative 'serialize'
29
+
30
+ module Ciri
31
+ class EVM
32
+
33
+ # represent empty set, distinguished with nil
34
+ EMPTY_SET = [].freeze
35
+
36
+ # Here include batch constants(OP, Cost..) you can find there definition in Ethereum yellow paper.
37
+ # If you can't understand some mystery formula in comments... go to read Ethereum yellow paper.
38
+ #
39
+ # VM: core logic of EVM
40
+ # other logic of EVM (include transaction logic) in EVM module.
41
+ class VM
42
+
43
+ class VMError < StandardError
44
+ end
45
+ class InvalidOpCodeError < VMError
46
+ end
47
+ class GasNotEnoughError < VMError
48
+ end
49
+ class StackError < VMError
50
+ end
51
+ class InvalidJumpError < VMError
52
+ end
53
+ class ReturnError < VMError
54
+ end
55
+
56
+ class << self
57
+ # this method provide a simpler interface to create VM and execute code
58
+ # VM.spawn(...) == VM.new(...)
59
+ # @return VM
60
+ def spawn(state:, gas_limit:, header: nil, block_info: nil, instruction:, fork_config:)
61
+ ms = MachineState.new(gas_remain: gas_limit, pc: 0, stack: [], memory: "\x00".b * 256, memory_item: 0)
62
+
63
+ block_info = block_info || header && BlockInfo.new(
64
+ coinbase: header.beneficiary,
65
+ difficulty: header.difficulty,
66
+ gas_limit: header.gas_limit,
67
+ number: header.number,
68
+ timestamp: header.timestamp
69
+ )
70
+
71
+ vm = VM.new(
72
+ state: state,
73
+ machine_state: ms,
74
+ block_info: block_info,
75
+ instruction: instruction,
76
+ fork_config: fork_config
77
+ )
78
+ yield vm if block_given?
79
+ vm
80
+ end
81
+ end
82
+
83
+ extend Forwardable
84
+
85
+ # helper methods
86
+ include Utils::Logger
87
+ include Serialize
88
+
89
+ def_delegators :@machine_state, :stack, :pc, :pop, :push, :pop_list, :get_stack,
90
+ :memory_item, :memory_item=, :memory_store, :memory_fetch, :extend_memory
91
+ def_delegators :@instruction, :get_op, :get_code, :next_valid_instruction_pos, :get_data, :data, :sender
92
+ def_delegators :@sub_state, :add_refund_account, :add_touched_account, :add_suicide_account
93
+
94
+ attr_reader :machine_state, :instruction, :sub_state, :block_info, :fork_config
95
+ attr_accessor :output, :exception
96
+
97
+ def initialize(state:, machine_state:, sub_state: nil, instruction:, block_info:, fork_config:)
98
+ @state = state
99
+ @machine_state = machine_state
100
+ @instruction = instruction
101
+ @sub_state = sub_state || SubState.new
102
+ @output = nil
103
+ @block_info = block_info
104
+ @fork_config = fork_config
105
+ end
106
+
107
+ # store data to address
108
+ def store(address, key, data)
109
+ data_is_blank = Ciri::Utils.blank_binary?(data)
110
+ # key_is_blank = Ciri::Utils.blank_binary?(key)
111
+
112
+ return unless data && !data_is_blank
113
+
114
+ # remove unnecessary null byte from key
115
+ key = serialize(key).gsub(/\A\0+/, ''.b)
116
+ key = "\x00".b if key.empty?
117
+
118
+ account = find_account address
119
+ account.storage[key] = serialize(data).rjust(32, "\x00".b)
120
+ update_account(account)
121
+ end
122
+
123
+ # fetch data from address
124
+ def fetch(address, key)
125
+ # remove unnecessary null byte from key
126
+ key = serialize(key).gsub(/\A\0+/, ''.b)
127
+ key = "\x00".b if key.empty?
128
+
129
+ find_account(address).storage[key] || ''.b
130
+ end
131
+
132
+ # run vm
133
+ def run(ignore_exception: false)
134
+ execute
135
+ raise exception unless ignore_exception || exception.nil?
136
+ end
137
+
138
+ # low_level create_contract interface
139
+ # CREATE_CONTRACT op is based on this method
140
+ def create_contract(value:, init:)
141
+ account = find_account(instruction.address)
142
+
143
+ # return contract address 0 represent execution failed
144
+ return 0 unless account.balance >= value || instruction.execute_depth > 1024
145
+
146
+ account.nonce += 1
147
+
148
+ # generate contract_address
149
+ material = RLP.encode_simple([instruction.address.to_s, account.nonce - 1])
150
+ contract_address = Utils.sha3(material)[-20..-1]
151
+
152
+ # initialize contract account
153
+ contract_account = find_account(contract_address)
154
+ contract_account.nonce = 1
155
+ contract_account.code = Utils::BLANK_SHA3
156
+
157
+ # execute initialize code
158
+ create_contract_instruction = instruction.dup
159
+ create_contract_instruction.bytes_code = init
160
+ create_contract_instruction.execute_depth += 1
161
+ create_contract_instruction.address = contract_address
162
+
163
+ call_instruction(create_contract_instruction) do
164
+ execute
165
+
166
+ if exception
167
+ update_account(Account.new_empty(contract_address))
168
+ contract_address = 0
169
+ else
170
+ # set contract code
171
+ contract_account.code = output || ''.b
172
+ # transact value
173
+ account.balance -= value
174
+ contract_account.balance += value
175
+ end
176
+ end
177
+
178
+ # update account
179
+ update_account(contract_account)
180
+ update_account(account)
181
+
182
+ contract_address
183
+ end
184
+
185
+ # low level call message interface
186
+ # CALL, CALLCODE, DELEGATECALL ops is base on this method
187
+ def call_message(sender:, value:, receipt:, data:, code_address:)
188
+ # return status code 0 represent execution failed
189
+ return [0, ''.b] unless value <= find_account(sender).balance && instruction.execute_depth <= 1024
190
+
191
+ message_call_instruction = instruction.dup
192
+ message_call_instruction.address = receipt
193
+ message_call_instruction.sender = sender
194
+ message_call_instruction.value = value
195
+
196
+ message_call_instruction.execute_depth += 1
197
+
198
+ message_call_instruction.data = data
199
+ message_call_instruction.bytes_code = find_account(code_address).code || ''.b
200
+
201
+ transact(sender: sender, value: value, to: receipt)
202
+ call_instruction(message_call_instruction) do
203
+ execute
204
+ status = exception.nil? ? 0 : 1
205
+ [status, output || ''.b]
206
+ end
207
+ end
208
+
209
+ # jump to pc
210
+ # only valid if current op code is allowed to modify pc
211
+ def jump_to(pc)
212
+ @jump_to = pc
213
+ end
214
+
215
+ def account_dead?(address)
216
+ Account.account_dead?(@state, address)
217
+ end
218
+
219
+ def find_account(address)
220
+ Account.find_account(@state, address)
221
+ end
222
+
223
+ # the only method which touch state
224
+ # VM do not consider state revert/commit, we let it to state implementation
225
+ def update_account(account)
226
+ address = account.address.to_s
227
+ @state[address] = account
228
+ add_touched_account(account)
229
+ end
230
+
231
+ def add_log_entry(topics, log_data)
232
+ sub_state.log_series << [instruction.address, topics, log_data]
233
+ end
234
+
235
+ # transact value from sender to target address
236
+ def transact(sender:, value:, to:)
237
+ sender = find_account(sender)
238
+ to = find_account(to)
239
+
240
+ raise VMError.new("balance not enough") if sender.balance < value
241
+
242
+ sender.balance -= value
243
+ to.balance += value
244
+
245
+ update_account(sender)
246
+ update_account(to)
247
+ end
248
+
249
+ # call instruction
250
+ def call_instruction(new_instruction)
251
+ origin_instruction = instruction
252
+ origin_pc = pc
253
+ @instruction = new_instruction
254
+ @machine_state.pc = 0
255
+
256
+ return_value = yield
257
+
258
+ @instruction = origin_instruction
259
+ @machine_state.pc = origin_pc
260
+ # clear up state
261
+ @exception = nil
262
+ @output = ''.b
263
+ return_value
264
+ end
265
+
266
+ # Execute instruction with states
267
+ # Ξ(σ,g,I,T) ≡ (σ′,μ′ ,A,o)
268
+ def execute
269
+ loop do
270
+ if (@exception ||= check_exception(@state, machine_state, instruction))
271
+ debug("exception: #{@exception}")
272
+ return [EMPTY_SET, machine_state, SubState::EMPTY, instruction, EMPTY_SET]
273
+ elsif get_op(machine_state.pc) == OP::REVERT
274
+ o = halt
275
+ gas_cost = fork_config.cost_of_operation[self]
276
+ machine_state.gas_remain -= gas_cost
277
+ return [EMPTY_SET, machine_state, sub_state, instruction, o]
278
+ elsif (o = halt) != EMPTY_SET
279
+ return [@state, machine_state, sub_state, instruction, o]
280
+ else
281
+ operate
282
+ next
283
+ end
284
+ end
285
+ end
286
+
287
+ private
288
+
289
+ # O(σ, μ, A, I) ≡ (σ′, μ′, A′, I)
290
+ def operate
291
+ ms = machine_state
292
+ w = get_op(ms.pc)
293
+ operation = OP.get(w)
294
+
295
+ raise "can't find operation #{w}, pc #{ms.pc}" unless operation
296
+
297
+ op_cost = fork_config.cost_of_operation[self]
298
+ old_memory_cost = fork_config.cost_of_memory[ms.memory_item]
299
+ ms.gas_remain -= op_cost
300
+
301
+ prev_sub_state = sub_state.dup
302
+
303
+ # call operation
304
+ operation.call(self)
305
+ # calculate gas_cost
306
+ new_memory_cost = fork_config.cost_of_memory[ms.memory_item]
307
+ memory_gas_cost = new_memory_cost - old_memory_cost
308
+
309
+ if ms.gas_remain >= memory_gas_cost
310
+ ms.gas_remain -= memory_gas_cost
311
+ else
312
+ # memory gas_not_enough
313
+ @exception = GasNotEnoughError.new "gas not enough: gas remain:#{ms.gas_remain} gas cost: #{memory_gas_cost}"
314
+ end
315
+
316
+ # revert sub_state and return if exception occur
317
+ if exception
318
+ @sub_state = prev_sub_state
319
+ return
320
+ end
321
+
322
+ debug("depth: #{instruction.execute_depth} pc: #{ms.pc} #{operation.name} gas: #{op_cost + memory_gas_cost} stack: #{stack.size} logs: #{sub_state.log_series.size}")
323
+ ms.pc = case
324
+ when w == OP::JUMP
325
+ @jump_to
326
+ when w == OP::JUMPI
327
+ @jump_to
328
+ else
329
+ next_valid_instruction_pos(ms.pc, w)
330
+ end
331
+ end
332
+
333
+ # determinate halt or not halt
334
+ def halt
335
+ w = get_op(machine_state.pc)
336
+ if w == OP::RETURN || w == OP::REVERT
337
+ operate
338
+ output
339
+ elsif w == OP::STOP || w == OP::SELFDESTRUCT
340
+ operate
341
+ # return empty sequence: nil
342
+ # debug("#{pc} #{OP.get(w).name} gas: 0 stack: #{stack.size}")
343
+ nil
344
+ else
345
+ EMPTY_SET
346
+ end
347
+ end
348
+
349
+ # check status
350
+ def check_exception(state, ms, instruction)
351
+ w = instruction.get_op(ms.pc)
352
+ case
353
+ when w == OP::INVALID
354
+ InvalidOpCodeError.new "can't find op code #{w}"
355
+ when OP.input_count(w).nil?
356
+ InvalidOpCodeError.new "can't find op code #{w}"
357
+ when ms.stack.size < (consume = OP.input_count(w))
358
+ StackError.new "stack not enough: stack:#{ms.stack.size} next consume: #{consume}"
359
+ when ms.gas_remain < (gas_cost = fork_config.cost_of_operation[self])
360
+ GasNotEnoughError.new "gas not enough: gas remain:#{ms.gas_remain} gas cost: #{gas_cost}"
361
+ when w == OP::JUMP && instruction.destinations.include?(ms.get_stack(0, Integer))
362
+ InvalidJumpError.new "invalid jump dest #{ms.get_stack(0, Integer)}"
363
+ when w == OP::JUMPI && ms.get_stack(1, Integer) != 0 && instruction.destinations.include?(ms.get_stack(0, Integer))
364
+ InvalidJumpError.new "invalid condition jump dest #{ms.get_stack(0, Integer)}"
365
+ when w == OP::RETURNDATACOPY && ms.get_stack(1, Integer) + ms.get_stack(2, Integer) > ms.output.size
366
+ ReturnError.new "return data copy error"
367
+ when stack.size - OP.input_count(w) + OP.output_count(w) > 1024
368
+ StackError.new "stack size reach 1024 limit"
369
+ # A condition in yellow paper but I can't understand..: (¬Iw ∧W(w,μ))
370
+ when instruction.execute_depth > 1024
371
+ StackError.new "call depth reach 1024 limit"
372
+ else
373
+ nil
374
+ end
375
+ end
376
+
377
+ end
378
+ end
379
+ end