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,158 @@
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/chain'
25
+ require 'ciri/rlp'
26
+ require 'stringio'
27
+
28
+ module Ciri
29
+ module Eth
30
+
31
+ # represent a hash or a number
32
+ class HashOrNumber
33
+ include Ciri::RLP::Serializable
34
+
35
+ attr_reader :value
36
+
37
+ def initialize(value)
38
+ @value = value
39
+ end
40
+
41
+ def rlp_encode!
42
+ if value.is_a? Integer
43
+ RLP.encode(value, Integer)
44
+ else
45
+ RLP.encode(value)
46
+ end
47
+ end
48
+
49
+ def self.rlp_decode!(s)
50
+ s = StringIO.new(s) if s.is_a?(String)
51
+ # start with 0xA0, represent s is a 32 length hash bytes
52
+ c = s.getc
53
+ s.ungetc(c)
54
+ if c.ord == 0xa0
55
+ RLP.decode(s)
56
+ else
57
+ RLP.decode(s, Integer)
58
+ end
59
+ end
60
+ end
61
+
62
+ # Ethereum Sub-protocol Messages
63
+ #
64
+ #
65
+
66
+
67
+ class Status
68
+ include Ciri::RLP::Serializable
69
+
70
+ CODE = 0x00
71
+
72
+ schema [
73
+ {protocol_version: Integer},
74
+ {network_id: Integer},
75
+ {total_difficulty: Integer},
76
+ :current_block,
77
+ :genesis_block,
78
+ ]
79
+ end
80
+
81
+ class GetBlockHeaders
82
+ include Ciri::RLP::Serializable
83
+
84
+ CODE = 0x03
85
+
86
+ schema [
87
+ {hash_or_number: HashOrNumber},
88
+ {amount: Integer},
89
+ {skip: Integer},
90
+ {reverse: Ciri::RLP::Bool},
91
+ ]
92
+ end
93
+
94
+ class BlockHeaders
95
+ CODE = 0x04
96
+
97
+ attr_reader :headers
98
+
99
+ def initialize(headers:)
100
+ @headers = headers
101
+ end
102
+
103
+ def rlp_encode!
104
+ Ciri::RLP.encode(@headers, [Chain::Header])
105
+ end
106
+
107
+ def self.rlp_decode!(payload)
108
+ new headers: Ciri::RLP.decode(payload, [Chain::Header])
109
+ end
110
+ end
111
+
112
+ class GetBlockBodies
113
+ CODE = 0x05
114
+
115
+ attr_reader :hashes
116
+
117
+ def initialize(hashes:)
118
+ @hashes = hashes
119
+ end
120
+
121
+ def rlp_encode!
122
+ Ciri::RLP.encode(@hashes)
123
+ end
124
+
125
+ def self.rlp_decode!(payload)
126
+ new hashes: Ciri::RLP.decode(payload)
127
+ end
128
+ end
129
+
130
+ class BlockBodies
131
+ CODE = 0x06
132
+
133
+ class Bodies
134
+ include RLP::Serializable
135
+
136
+ schema [
137
+ {transactions: [Chain::Transaction]},
138
+ {ommers: [Chain::Header]},
139
+ ]
140
+ end
141
+
142
+ attr_reader :bodies
143
+
144
+ def initialize(bodies:)
145
+ @bodies = bodies
146
+ end
147
+
148
+ def rlp_encode!
149
+ Ciri::RLP.encode(@bodies, [Bodies])
150
+ end
151
+
152
+ def self.rlp_decode!(bodies)
153
+ new bodies: Ciri::RLP.decode(bodies, [Bodies])
154
+ end
155
+ end
156
+
157
+ end
158
+ end
@@ -0,0 +1,188 @@
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 'lru_redux'
25
+ require 'ciri/utils/logger'
26
+
27
+ module Ciri
28
+ module Eth
29
+
30
+ # Synchronizer sync blocks with peers
31
+ class Synchronizer
32
+ include Ciri::Actor
33
+ include Ciri::Utils::Logger
34
+
35
+ HEADER_FETCH_COUNT = 10
36
+
37
+ class PeerEntry
38
+ attr_reader :header_queue, :body_queue, :peer
39
+ attr_accessor :syncing
40
+
41
+ def initialize(peer)
42
+ @peer = peer
43
+ @header_queue = Queue.new
44
+ @body_queue = Queue.new
45
+ @lru_cache = LruRedux::Cache.new(HEADER_FETCH_COUNT * 2)
46
+ end
47
+
48
+ def receive_header
49
+ header_queue.pop
50
+ end
51
+
52
+ def receive_header_in(timeout)
53
+ wait_seconds = 0
54
+ while header_queue.empty?
55
+ sleep(0.1)
56
+ wait_seconds += 0.1
57
+ raise Timeout::Error.new("can't receive body in #{timeout}") if wait_seconds > timeout
58
+ end
59
+ header_queue.pop(true)
60
+ end
61
+
62
+ def receive_body
63
+ body_queue.pop
64
+ end
65
+
66
+ def receive_body_in(timeout)
67
+ wait_seconds = 0
68
+ while body_queue.empty?
69
+ sleep(0.1)
70
+ wait_seconds += 0.1
71
+ raise Timeout::Error.new("can't receive body in #{timeout}") if wait_seconds > timeout
72
+ end
73
+ body_queue.pop(true)
74
+ end
75
+
76
+ def fetch_peer_header(hash_or_number)
77
+ cached = @lru_cache[hash_or_number]
78
+ return cached if cached
79
+
80
+ until header_queue.empty?
81
+ header = receive_header
82
+ @lru_cache[header.number] = header
83
+ @lru_cache[header.get_hash] = header
84
+ return header if header.number == hash_or_number || header.get_hash == hash_or_number
85
+ end
86
+ peer.send_msg(GetBlockHeaders, hash_or_number: HashOrNumber.new(hash_or_number), amount: HEADER_FETCH_COUNT,
87
+ skip: 0, reverse: false)
88
+ while (header = receive_header_in(10))
89
+ @lru_cache[header.number] = header
90
+ @lru_cache[header.get_hash] = header
91
+ return header if header.number == hash_or_number || header.get_hash == hash_or_number
92
+ end
93
+ raise 'should not touch here'
94
+ end
95
+
96
+ def fetch_peer_body(hashes)
97
+ # clear body queue for receive
98
+ body_queue.clear
99
+ peer.send_msg(GetBlockBodies, hashes: hashes)
100
+ receive_body_in(10)
101
+ end
102
+ end
103
+
104
+ attr_reader :chain
105
+
106
+ def initialize(chain:)
107
+ @chain = chain
108
+ @peers = {}
109
+ super()
110
+ end
111
+
112
+ def receive_headers(peer, headers)
113
+ headers.each {|header| @peers[peer].header_queue << header}
114
+ end
115
+
116
+ def receive_bodies(peer, bodies)
117
+ @peers[peer].body_queue << bodies
118
+ end
119
+
120
+ def register_peer(peer)
121
+ @peers[peer] = PeerEntry.new(peer)
122
+
123
+ # request block headers if chain td less than peer
124
+ return unless peer.total_difficulty > chain.total_difficulty
125
+ peer.send_msg(GetBlockHeaders, hash_or_number: HashOrNumber.new(peer.status.current_block),
126
+ amount: 1, skip: 0, reverse: true)
127
+
128
+ start_syncing best_peer
129
+ end
130
+
131
+ MAX_BLOCKS_SYNCING = 50
132
+
133
+ # check and start syncing peer
134
+ def start_syncing(peer)
135
+ peer_entry = @peers[peer]
136
+ return if peer_entry.syncing
137
+
138
+ peer_entry.syncing = true
139
+
140
+ executor.post do
141
+ peer_max_header = peer_header = peer_entry.receive_header
142
+ local_header = chain.head
143
+ start_height = [peer_header.number, local_header.number].min
144
+
145
+ # find common height
146
+ while local_header.get_hash != peer_header.get_hash
147
+ local_header = chain.get_block_by_number start_height
148
+ peer_header = peer_entry.fetch_peer_header start_height
149
+ start_height -= 1
150
+ end
151
+
152
+ loop do
153
+
154
+ # start from common + 1 block
155
+ start_height = local_header.number + 1
156
+
157
+ end_height = [start_height + MAX_BLOCKS_SYNCING, peer_max_header.number].min
158
+
159
+ if start_height < 1 || start_height > end_height
160
+ raise 'peer is incorrect'
161
+ end
162
+
163
+ info "Start syncing with Peer##{peer}, from #{start_height} to #{end_height}"
164
+
165
+ (start_height..end_height).each do |height|
166
+ header = peer_entry.fetch_peer_header height
167
+ bodies = peer_entry.fetch_peer_body([header.get_hash])
168
+ block = Chain::Block.new(header: header, transactions: bodies[0].transactions, ommers: bodies[0].ommers)
169
+ # insert to chain....
170
+ chain.write_block(block)
171
+ local_header = header
172
+ end
173
+ start_height = end_height + 1
174
+
175
+ break if end_height >= peer_max_header.number
176
+ end
177
+
178
+ end
179
+ end
180
+
181
+ def best_peer
182
+ @peers.keys.sort_by(&:total_difficulty).last
183
+ end
184
+
185
+ end
186
+
187
+ end
188
+ end
@@ -0,0 +1,123 @@
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 'ffi'
25
+ require 'ciri/utils/lib_c'
26
+
27
+ module Ciri
28
+
29
+ # Ethash Algorithm
30
+ # from https://github.com/ethereum/ethash/blob/master/src/python/core.c
31
+ module Ethash
32
+
33
+ module Lib
34
+ extend FFI::Library
35
+ ffi_lib 'libethash'
36
+
37
+ # struct ethash_light {
38
+ # void* cache;
39
+ # uint64_t cache_size;
40
+ # uint64_t block_number;
41
+ # };
42
+ class Light < FFI::Struct
43
+ layout :cache, :pointer,
44
+ :cache_size, :uint64,
45
+ :block_number, :uint64
46
+ end
47
+
48
+ # struct ethash_h256 { uint8_t b[32]; }
49
+ class H256 < FFI::Struct
50
+ layout :b, [:uint8, 32]
51
+
52
+ def put_bytes(s)
53
+ self[:b].to_ptr.put_array_of_uint8(0, s.each_byte.to_a)
54
+ end
55
+
56
+ def get_bytes
57
+ self[:b].to_ptr.get_array_of_uint8(0, 32).pack("c*")
58
+ end
59
+ end
60
+
61
+ # struct ethash_return_value {
62
+ # ethash_h256_t result;
63
+ # ethash_h256_t mix_hash;
64
+ # bool success;
65
+ # }
66
+ class ReturnValue < FFI::Struct
67
+ layout :result, H256,
68
+ :mix_hash, H256,
69
+ :success, :bool
70
+ end
71
+
72
+ attach_function :ethash_light_new, [:int], Light
73
+ attach_function :ethash_light_delete, [Light], :void
74
+ attach_function :ethash_light_compute, [Light, H256.by_value, :uint64], ReturnValue.by_value
75
+ end
76
+
77
+ EPOCH_LENGTH = 30000
78
+
79
+ class Error < StandardError
80
+ end
81
+
82
+ # use methods as module methods
83
+ extend self
84
+
85
+ # return [mix_hash, result]
86
+ def hashimoto_light(block_number, cache_bytes, header, nonce)
87
+ header_size = header.size
88
+ cache_size = cache_bytes.size
89
+ raise Error.new("seed must be 32 bytes long, (was #{header_size})") if header_size != 32
90
+
91
+ cache_ptr = Utils::LibC.malloc(cache_size)
92
+ cache_ptr.write_string_length(cache_bytes, cache_size)
93
+
94
+ light = Lib::Light.new
95
+ light[:cache] = cache_ptr
96
+ light[:cache_size] = cache_size
97
+ light[:block_number] = block_number
98
+
99
+ h = Lib::H256.new
100
+ h.put_bytes(header[0..32])
101
+
102
+ value = Lib.ethash_light_compute(light, h, nonce)
103
+ raise Error.new "compute not success, return: #{value[:success]}" unless value[:success]
104
+
105
+ result = [value[:mix_hash].get_bytes, value[:result].get_bytes]
106
+
107
+ # release memory *_*
108
+ Utils::LibC.free cache_ptr
109
+
110
+ result
111
+ end
112
+
113
+ def mkcache_bytes(block_number)
114
+ ptr = Lib.ethash_light_new(block_number)
115
+ light = Lib::Light.new(ptr)
116
+ cache_ptr = light[:cache]
117
+ bytes = cache_ptr.read_string(light[:cache_size])
118
+ Lib.ethash_light_delete(light)
119
+ bytes
120
+ end
121
+
122
+ end
123
+ end