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,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