ciri 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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