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