ciri 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitmodules +14 -0
- data/.rspec +2 -1
- data/.travis.yml +11 -4
- data/Gemfile.lock +3 -0
- data/README.md +44 -34
- data/Rakefile +47 -4
- data/ciri.gemspec +13 -12
- data/docker/Base +34 -0
- data/lib/ciri/actor.rb +223 -0
- data/lib/ciri/chain.rb +293 -0
- data/lib/ciri/chain/block.rb +47 -0
- data/lib/ciri/chain/header.rb +62 -0
- data/lib/ciri/chain/transaction.rb +145 -0
- data/lib/ciri/crypto.rb +58 -5
- data/lib/ciri/db/backend/memory.rb +68 -0
- data/lib/ciri/db/backend/rocks.rb +104 -0
- data/lib/ciri/db/backend/rocks_db.rb +278 -0
- data/lib/ciri/devp2p/peer.rb +10 -2
- data/lib/ciri/devp2p/protocol.rb +11 -3
- data/lib/ciri/devp2p/protocol_io.rb +6 -3
- data/lib/ciri/devp2p/rlpx.rb +1 -0
- data/lib/ciri/devp2p/rlpx/encryption_handshake.rb +1 -1
- data/lib/ciri/devp2p/rlpx/frame_io.rb +1 -1
- data/lib/ciri/devp2p/rlpx/message.rb +4 -4
- data/lib/ciri/devp2p/server.rb +14 -13
- data/lib/ciri/eth.rb +33 -0
- data/lib/ciri/eth/peer.rb +64 -0
- data/lib/ciri/eth/protocol_manage.rb +122 -0
- data/lib/ciri/eth/protocol_messages.rb +158 -0
- data/lib/ciri/eth/synchronizer.rb +188 -0
- data/lib/ciri/ethash.rb +123 -0
- data/lib/ciri/evm.rb +140 -0
- data/lib/ciri/evm/account.rb +50 -0
- data/lib/ciri/evm/block_info.rb +31 -0
- data/lib/ciri/evm/forks/frontier.rb +183 -0
- data/lib/ciri/evm/instruction.rb +92 -0
- data/lib/ciri/evm/machine_state.rb +81 -0
- data/lib/ciri/evm/op.rb +536 -0
- data/lib/ciri/evm/serialize.rb +60 -0
- data/lib/ciri/evm/sub_state.rb +64 -0
- data/lib/ciri/evm/vm.rb +379 -0
- data/lib/ciri/forks.rb +38 -0
- data/lib/ciri/forks/frontier.rb +43 -0
- data/lib/ciri/key.rb +7 -1
- data/lib/ciri/pow.rb +95 -0
- data/lib/ciri/rlp.rb +3 -53
- data/lib/ciri/rlp/decode.rb +100 -40
- data/lib/ciri/rlp/encode.rb +95 -34
- data/lib/ciri/rlp/serializable.rb +61 -91
- data/lib/ciri/types/address.rb +70 -0
- data/lib/ciri/types/errors.rb +36 -0
- data/lib/ciri/utils.rb +45 -13
- data/lib/ciri/utils/lib_c.rb +46 -0
- data/lib/ciri/utils/logger.rb +99 -0
- data/lib/ciri/utils/number.rb +67 -0
- data/lib/ciri/version.rb +1 -1
- metadata +67 -7
- data/lib/ciri/devp2p/actor.rb +0 -224
data/lib/ciri/chain.rb
ADDED
@@ -0,0 +1,293 @@
|
|
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 'forwardable'
|
25
|
+
require_relative 'chain/block'
|
26
|
+
require_relative 'chain/header'
|
27
|
+
require_relative 'chain/transaction'
|
28
|
+
require_relative 'pow'
|
29
|
+
|
30
|
+
module Ciri
|
31
|
+
|
32
|
+
# Chain manipulate logic
|
33
|
+
# store via rocksdb
|
34
|
+
class Chain
|
35
|
+
|
36
|
+
class Error < StandardError
|
37
|
+
end
|
38
|
+
|
39
|
+
class InvalidHeaderError < Error
|
40
|
+
end
|
41
|
+
|
42
|
+
class InvalidBlockError < Error
|
43
|
+
end
|
44
|
+
|
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
|
66
|
+
|
67
|
+
def head=(header)
|
68
|
+
store[HEAD] = header.rlp_encode!
|
69
|
+
end
|
70
|
+
|
71
|
+
def get_header(hash)
|
72
|
+
encoded = store[HEADER_PREFIX + hash]
|
73
|
+
encoded && Header.rlp_decode!(encoded)
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_header_by_number(number)
|
77
|
+
hash = get_header_hash_by_number(number)
|
78
|
+
hash && get_header(hash)
|
79
|
+
end
|
80
|
+
|
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
|
106
|
+
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
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
|
140
|
+
|
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))
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
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
|
163
|
+
end
|
164
|
+
|
165
|
+
def get_header_hash_by_number(number)
|
166
|
+
enc_number = Utils.big_endian_encode number
|
167
|
+
store[HEADER_PREFIX + enc_number + NUM_SUFFIX]
|
168
|
+
end
|
169
|
+
|
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)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
extend Forwardable
|
177
|
+
|
178
|
+
BODY_PREFIX = 'b'
|
179
|
+
|
180
|
+
def_delegators :@header_chain, :head, :total_difficulty, :get_header_by_number, :get_header
|
181
|
+
|
182
|
+
attr_reader :genesis, :network_id, :store, :header_chain
|
183
|
+
|
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
|
190
|
+
end
|
191
|
+
|
192
|
+
def genesis_hash
|
193
|
+
genesis.header.get_hash
|
194
|
+
end
|
195
|
+
|
196
|
+
def current_block
|
197
|
+
get_block(head.get_hash)
|
198
|
+
end
|
199
|
+
|
200
|
+
def current_height
|
201
|
+
head.number
|
202
|
+
end
|
203
|
+
|
204
|
+
# insert blocks in order
|
205
|
+
# blocks must be ordered from lower height to higher height
|
206
|
+
def insert_blocks(blocks)
|
207
|
+
prev_block = blocks[0]
|
208
|
+
blocks[1..-1].each do |block|
|
209
|
+
unless block.number == prev_block.number + 1 && block.parent_hash == prev_block.get_hash
|
210
|
+
raise InvalidBlockError.new("blocks insert orders not correct")
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
blocks.each do |block|
|
215
|
+
write_block block
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def get_block_by_number(number)
|
220
|
+
hash = @header_chain.get_header_hash_by_number(number)
|
221
|
+
hash && get_block(hash)
|
222
|
+
end
|
223
|
+
|
224
|
+
def get_block(hash)
|
225
|
+
encoded = store[BODY_PREFIX + hash]
|
226
|
+
encoded && Block.rlp_decode!(encoded)
|
227
|
+
end
|
228
|
+
|
229
|
+
def write_block(block)
|
230
|
+
# write header
|
231
|
+
header = block.header
|
232
|
+
raise InvalidHeaderError.new("invalid header: #{header.number}") unless @header_chain.valid?(header)
|
233
|
+
@header_chain.write(header)
|
234
|
+
|
235
|
+
# write body
|
236
|
+
store[BODY_PREFIX + header.get_hash] = block.rlp_encode!
|
237
|
+
|
238
|
+
td = total_difficulty(header.get_hash)
|
239
|
+
|
240
|
+
if td > total_difficulty
|
241
|
+
# new block not follow current head, need reorg chain
|
242
|
+
if head && ((header.number <= head.number) || (header.number == head.number + 1 && header.parent_hash != head.get_hash))
|
243
|
+
reorg_chain(block, current_block)
|
244
|
+
else
|
245
|
+
# otherwise, new block extend current chain, just update chain head
|
246
|
+
@header_chain.head = header
|
247
|
+
@header_chain.write_header_hash_number(header.get_hash, header.number)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
private
|
253
|
+
|
254
|
+
# reorg chain
|
255
|
+
def reorg_chain(new_block, old_block)
|
256
|
+
new_chain = []
|
257
|
+
# find common ancestor block
|
258
|
+
# move new_block and old_block to same height
|
259
|
+
while new_block.number > old_block.number
|
260
|
+
new_chain << new_block
|
261
|
+
new_block = get_block(new_block.parent_hash)
|
262
|
+
end
|
263
|
+
|
264
|
+
while old_block.number > new_block.number
|
265
|
+
old_block = get_block(old_block.parent_hash)
|
266
|
+
end
|
267
|
+
|
268
|
+
while old_block.get_hash != new_block.get_hash
|
269
|
+
new_chain << new_block
|
270
|
+
old_block = get_block(old_block.parent_hash)
|
271
|
+
new_block = get_block(new_block.parent_hash)
|
272
|
+
end
|
273
|
+
|
274
|
+
# rewrite chain
|
275
|
+
new_chain.reverse_each {|block| rewrite_block(block)}
|
276
|
+
end
|
277
|
+
|
278
|
+
# rewrite block
|
279
|
+
# this method will treat block as canonical chain block
|
280
|
+
def rewrite_block(block)
|
281
|
+
@header_chain.head = block.header
|
282
|
+
@header_chain.write_header_hash_number(block.get_hash, block.number)
|
283
|
+
end
|
284
|
+
|
285
|
+
def load_or_init_store
|
286
|
+
# write genesis block, is chain head not exists
|
287
|
+
if head.nil?
|
288
|
+
write_block(genesis)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
end
|
@@ -0,0 +1,47 @@
|
|
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/rlp'
|
25
|
+
require_relative 'header'
|
26
|
+
require_relative 'transaction'
|
27
|
+
require 'forwardable'
|
28
|
+
|
29
|
+
module Ciri
|
30
|
+
class Chain
|
31
|
+
|
32
|
+
# structure for ethereum block
|
33
|
+
class Block
|
34
|
+
include RLP::Serializable
|
35
|
+
schema [
|
36
|
+
{header: Header},
|
37
|
+
{transactions: [Transaction]},
|
38
|
+
{ommers: [Header]}, # or uncles
|
39
|
+
]
|
40
|
+
|
41
|
+
extend Forwardable
|
42
|
+
|
43
|
+
def_delegators :header, :number, :get_hash, :mining_hash, :parent_hash
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,62 @@
|
|
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 Chain
|
26
|
+
|
27
|
+
# block header
|
28
|
+
class Header
|
29
|
+
include Ciri::RLP::Serializable
|
30
|
+
|
31
|
+
schema [
|
32
|
+
:parent_hash,
|
33
|
+
:ommers_hash,
|
34
|
+
:beneficiary,
|
35
|
+
:state_root,
|
36
|
+
:transactions_root,
|
37
|
+
:receipts_root,
|
38
|
+
:logs_bloom,
|
39
|
+
{difficulty: Integer},
|
40
|
+
{number: Integer},
|
41
|
+
{gas_limit: Integer},
|
42
|
+
{gas_used: Integer},
|
43
|
+
{timestamp: Integer},
|
44
|
+
:extra_data,
|
45
|
+
:mix_hash,
|
46
|
+
:nonce,
|
47
|
+
]
|
48
|
+
|
49
|
+
# header hash
|
50
|
+
def get_hash
|
51
|
+
Utils.sha3(rlp_encode!)
|
52
|
+
end
|
53
|
+
|
54
|
+
# mining_hash, used for mining
|
55
|
+
def mining_hash
|
56
|
+
Utils.sha3(rlp_encode! skip_keys: [:mix_hash, :nonce])
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -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/rlp'
|
25
|
+
require 'ciri/crypto'
|
26
|
+
require 'ciri/types/address'
|
27
|
+
|
28
|
+
module Ciri
|
29
|
+
class Chain
|
30
|
+
|
31
|
+
class Transaction
|
32
|
+
|
33
|
+
class InvalidError < StandardError
|
34
|
+
end
|
35
|
+
|
36
|
+
EIP155_CHAIN_ID_OFFSET = 35
|
37
|
+
V_OFFSET = 27
|
38
|
+
|
39
|
+
include RLP::Serializable
|
40
|
+
|
41
|
+
schema [
|
42
|
+
{nonce: Integer},
|
43
|
+
{gas_price: Integer},
|
44
|
+
{gas_limit: Integer},
|
45
|
+
{to: Types::Address},
|
46
|
+
{value: Integer},
|
47
|
+
:data,
|
48
|
+
{v: Integer},
|
49
|
+
{r: Integer},
|
50
|
+
{s: Integer}
|
51
|
+
]
|
52
|
+
|
53
|
+
default_data v: 0, r: 0, s: 0, data: "\x00".b
|
54
|
+
|
55
|
+
# sender address
|
56
|
+
# @return address String
|
57
|
+
def sender
|
58
|
+
@sender ||= begin
|
59
|
+
address = Types::Address.new(Utils.sha3(Crypto.ecdsa_recover(sign_hash(chain_id), signature)[1..-1])[-20..-1])
|
60
|
+
address.validate
|
61
|
+
address
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def signature
|
66
|
+
v = if eip_155_signed_transaction?
|
67
|
+
# retrieve actually v from transaction.v, see EIP-155(prevent replay attack)
|
68
|
+
(self.v - 1) % 2
|
69
|
+
elsif [27, 28].include?(self.v)
|
70
|
+
self.v - 27
|
71
|
+
else
|
72
|
+
self.v
|
73
|
+
end
|
74
|
+
Crypto::Signature.new(vrs: [v, r, s])
|
75
|
+
end
|
76
|
+
|
77
|
+
# @param key Key
|
78
|
+
def sign_with_key!(key)
|
79
|
+
signature = key.ecdsa_signature(sign_hash)
|
80
|
+
self.v = signature.v
|
81
|
+
self.r = signature.r
|
82
|
+
self.s = signature.s
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def contract_creation?
|
87
|
+
to.empty?
|
88
|
+
end
|
89
|
+
|
90
|
+
def sign_hash(chain_id = nil)
|
91
|
+
param = data || ''.b
|
92
|
+
list = [nonce, gas_price, gas_limit, to, value, param]
|
93
|
+
if chain_id
|
94
|
+
list += [chain_id, ''.b, ''.b]
|
95
|
+
end
|
96
|
+
Utils.sha3(RLP.encode_simple list)
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_hash
|
100
|
+
Utils.sha3 rlp_encode!
|
101
|
+
end
|
102
|
+
|
103
|
+
# validate transaction
|
104
|
+
# @param intrinsic_gas_of_transaction Proc
|
105
|
+
def validate!(intrinsic_gas_of_transaction: nil)
|
106
|
+
begin
|
107
|
+
sender
|
108
|
+
rescue Ciri::Crypto::ECDSASignatureError => e
|
109
|
+
raise InvalidError.new("recover signature error, error: #{e}")
|
110
|
+
rescue Ciri::Types::Errors::InvalidError => e
|
111
|
+
raise InvalidError.new(e.to_s)
|
112
|
+
end
|
113
|
+
|
114
|
+
raise InvalidError.new('signature rvs error') unless signature.valid?
|
115
|
+
raise InvalidError.new('signature s is low') unless signature.low_s?
|
116
|
+
|
117
|
+
if intrinsic_gas_of_transaction
|
118
|
+
begin
|
119
|
+
intrinsic_gas = intrinsic_gas_of_transaction[self]
|
120
|
+
rescue StandardError
|
121
|
+
raise InvalidError.new 'intrinsic gas calculation error'
|
122
|
+
end
|
123
|
+
raise InvalidError.new 'intrinsic gas not enough' unless intrinsic_gas <= gas_limit
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
# return chain_id by v
|
130
|
+
def chain_id
|
131
|
+
if eip_155_signed_transaction?
|
132
|
+
# retrieve chain_id from v, see EIP-155
|
133
|
+
(v - 35) / 2
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# https://github.com/ethereum/EIPs/blob/984cf5de90bbf5fbe7e49be227b0c2f9567e661e/EIPS/eip-155.md
|
138
|
+
def eip_155_signed_transaction?
|
139
|
+
v >= EIP155_CHAIN_ID_OFFSET
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|