ruby-ethereum 0.9.0
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +40 -0
- data/lib/ethereum.rb +53 -0
- data/lib/ethereum/abi.rb +333 -0
- data/lib/ethereum/abi/contract_translator.rb +174 -0
- data/lib/ethereum/abi/type.rb +117 -0
- data/lib/ethereum/account.rb +72 -0
- data/lib/ethereum/address.rb +60 -0
- data/lib/ethereum/base_convert.rb +53 -0
- data/lib/ethereum/block.rb +1311 -0
- data/lib/ethereum/block_header.rb +211 -0
- data/lib/ethereum/bloom.rb +83 -0
- data/lib/ethereum/cached_block.rb +36 -0
- data/lib/ethereum/chain.rb +400 -0
- data/lib/ethereum/constant.rb +26 -0
- data/lib/ethereum/core_ext/array/safe_slice.rb +18 -0
- data/lib/ethereum/core_ext/module/lru_cache.rb +20 -0
- data/lib/ethereum/core_ext/numeric/denominations.rb +45 -0
- data/lib/ethereum/core_ext/object/truth.rb +47 -0
- data/lib/ethereum/db.rb +6 -0
- data/lib/ethereum/db/base_db.rb +9 -0
- data/lib/ethereum/db/ephem_db.rb +63 -0
- data/lib/ethereum/db/overlay_db.rb +72 -0
- data/lib/ethereum/db/refcount_db.rb +188 -0
- data/lib/ethereum/env.rb +64 -0
- data/lib/ethereum/ethash.rb +78 -0
- data/lib/ethereum/ethash_ruby.rb +38 -0
- data/lib/ethereum/ethash_ruby/cache.rb +47 -0
- data/lib/ethereum/ethash_ruby/hashimoto.rb +75 -0
- data/lib/ethereum/ethash_ruby/utils.rb +53 -0
- data/lib/ethereum/exceptions.rb +28 -0
- data/lib/ethereum/external_call.rb +173 -0
- data/lib/ethereum/fast_rlp.rb +81 -0
- data/lib/ethereum/fast_vm.rb +625 -0
- data/lib/ethereum/fast_vm/call_data.rb +44 -0
- data/lib/ethereum/fast_vm/message.rb +29 -0
- data/lib/ethereum/fast_vm/state.rb +25 -0
- data/lib/ethereum/ffi/openssl.rb +390 -0
- data/lib/ethereum/index.rb +97 -0
- data/lib/ethereum/log.rb +43 -0
- data/lib/ethereum/miner.rb +84 -0
- data/lib/ethereum/opcodes.rb +131 -0
- data/lib/ethereum/private_key.rb +92 -0
- data/lib/ethereum/pruning_trie.rb +28 -0
- data/lib/ethereum/public_key.rb +88 -0
- data/lib/ethereum/receipt.rb +53 -0
- data/lib/ethereum/secp256k1.rb +58 -0
- data/lib/ethereum/secure_trie.rb +49 -0
- data/lib/ethereum/sedes.rb +42 -0
- data/lib/ethereum/special_contract.rb +95 -0
- data/lib/ethereum/spv.rb +79 -0
- data/lib/ethereum/spv/proof.rb +31 -0
- data/lib/ethereum/spv/proof_constructor.rb +19 -0
- data/lib/ethereum/spv/proof_verifier.rb +24 -0
- data/lib/ethereum/tester.rb +14 -0
- data/lib/ethereum/tester/abi_contract.rb +65 -0
- data/lib/ethereum/tester/fixture.rb +31 -0
- data/lib/ethereum/tester/language.rb +30 -0
- data/lib/ethereum/tester/log_recorder.rb +13 -0
- data/lib/ethereum/tester/solidity_wrapper.rb +144 -0
- data/lib/ethereum/tester/state.rb +194 -0
- data/lib/ethereum/transaction.rb +196 -0
- data/lib/ethereum/transient_trie.rb +28 -0
- data/lib/ethereum/trie.rb +549 -0
- data/lib/ethereum/trie/nibble_key.rb +184 -0
- data/lib/ethereum/utils.rb +191 -0
- data/lib/ethereum/version.rb +5 -0
- data/lib/ethereum/vm.rb +606 -0
- data/lib/ethereum/vm/call_data.rb +40 -0
- data/lib/ethereum/vm/message.rb +30 -0
- data/lib/ethereum/vm/state.rb +25 -0
- metadata +284 -0
@@ -0,0 +1,196 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module Ethereum
|
4
|
+
|
5
|
+
##
|
6
|
+
# A transaction is stored as:
|
7
|
+
#
|
8
|
+
# `[nonce, gasprice, startgas, to, value, data, v, r, s]`
|
9
|
+
#
|
10
|
+
# `nonce` is the number of transactions already sent by that account, encoded
|
11
|
+
# in binary form (eg. 0 -> "", 7 -> "\x07", 1000 -> "\x03\xd8").
|
12
|
+
#
|
13
|
+
# `(v,r,s)` is the raw Electrum-style signature of the transaction without
|
14
|
+
# the signature made with the private key corresponding to the sending
|
15
|
+
# account, with `0 <= v <= 3`. From an Electrum-style signature (65 bytes) it
|
16
|
+
# is possible to extract the public key, and thereby the address, directly.
|
17
|
+
#
|
18
|
+
# A valid transaction is one where:
|
19
|
+
#
|
20
|
+
# 1. the signature is well-formed (ie. `0 <= v <= 3, 0 <= r < P, 0 <= s < N, 0
|
21
|
+
# <= r < P - N if v >= 2`), and
|
22
|
+
# 2. the sending account has enough funds to pay the fee and the value.
|
23
|
+
#
|
24
|
+
class Transaction
|
25
|
+
include RLP::Sedes::Serializable
|
26
|
+
|
27
|
+
extend Sedes
|
28
|
+
set_serializable_fields(
|
29
|
+
nonce: big_endian_int,
|
30
|
+
|
31
|
+
gasprice: big_endian_int,
|
32
|
+
startgas: big_endian_int,
|
33
|
+
|
34
|
+
to: address,
|
35
|
+
value: big_endian_int,
|
36
|
+
data: binary,
|
37
|
+
|
38
|
+
v: big_endian_int,
|
39
|
+
r: big_endian_int,
|
40
|
+
s: big_endian_int
|
41
|
+
)
|
42
|
+
|
43
|
+
class <<self
|
44
|
+
##
|
45
|
+
# A contract is a special transaction without the `to` argument.
|
46
|
+
#
|
47
|
+
def contract(nonce, gasprice, startgas, endowment, code, v=0, r=0, s=0)
|
48
|
+
new nonce, gasprice, startgas, '', endowment, code, v, r, s
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(*args)
|
53
|
+
fields = {v: 0, r: 0, s: 0}.merge parse_field_args(args)
|
54
|
+
fields[:to] = Utils.normalize_address(fields[:to], allow_blank: true)
|
55
|
+
|
56
|
+
serializable_initialize fields
|
57
|
+
|
58
|
+
@sender = nil
|
59
|
+
@logs = []
|
60
|
+
|
61
|
+
raise InvalidTransaction, "Values way too high!" if [gasprice, startgas, value, nonce].max > Constant::UINT_MAX
|
62
|
+
raise InvalidTransaction, "Startgas too low" if startgas < intrinsic_gas_used
|
63
|
+
|
64
|
+
logger.debug "deserialized tx #{Utils.encode_hex(full_hash)[0,8]}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def sender
|
68
|
+
unless @sender
|
69
|
+
if v && v > 0
|
70
|
+
raise InvalidTransaction, "Invalid signature values!" if r >= Secp256k1::N || s >= Secp256k1::N || v < 27 || v > 28 || r == 0 || s == 0
|
71
|
+
|
72
|
+
logger.debug "recovering sender"
|
73
|
+
rlpdata = RLP.encode(self, sedes: UnsignedTransaction)
|
74
|
+
rawhash = Utils.keccak256 rlpdata
|
75
|
+
|
76
|
+
pub = nil
|
77
|
+
begin
|
78
|
+
pub = Secp256k1.recover_pubkey rawhash, [v,r,s]
|
79
|
+
rescue
|
80
|
+
raise InvalidTransaction, "Invalid signature values (x^3+7 is non-residue)"
|
81
|
+
end
|
82
|
+
|
83
|
+
raise InvalidTransaction, "Invalid signature (zero privkey cannot sign)" if pub[1..-1] == Constant::PUBKEY_ZERO
|
84
|
+
|
85
|
+
@sender = PublicKey.new(pub).to_address
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
@sender
|
90
|
+
end
|
91
|
+
|
92
|
+
def sender=(v)
|
93
|
+
@sender = v
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Sign this transaction with a private key.
|
98
|
+
#
|
99
|
+
# A potentially already existing signature would be override.
|
100
|
+
#
|
101
|
+
def sign(key)
|
102
|
+
raise InvalidTransaction, "Zero privkey cannot sign" if [0, '', Constant::PRIVKEY_ZERO, Constant::PRIVKEY_ZERO_HEX].include?(key)
|
103
|
+
|
104
|
+
rawhash = Utils.keccak256 RLP.encode(self, sedes: UnsignedTransaction)
|
105
|
+
key = PrivateKey.new(key).encode(:bin)
|
106
|
+
|
107
|
+
vrs = Secp256k1.recoverable_sign rawhash, key
|
108
|
+
self.v = vrs[0]
|
109
|
+
self.r = vrs[1]
|
110
|
+
self.s = vrs[2]
|
111
|
+
|
112
|
+
self.sender = PrivateKey.new(key).to_address
|
113
|
+
|
114
|
+
self
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# This method should be called for block numbers >=
|
119
|
+
# config[:homestead_fork_blknum] only. The >= operator is replaced by >
|
120
|
+
# because the integer division N/2 always produces the value which is by
|
121
|
+
# 0.5 less than the real N/2.
|
122
|
+
#
|
123
|
+
def check_low_s
|
124
|
+
raise InvalidTransaction, "Invalid signature S value!" if s > Secp256k1::N/2 || s == 0
|
125
|
+
end
|
126
|
+
|
127
|
+
def full_hash
|
128
|
+
Utils.keccak256_rlp self
|
129
|
+
end
|
130
|
+
|
131
|
+
def log_bloom
|
132
|
+
bloomables = @logs.map {|l| l.bloomables }
|
133
|
+
Bloom.from_array bloomables.flatten
|
134
|
+
end
|
135
|
+
|
136
|
+
def log_bloom_b256
|
137
|
+
Bloom.b256 log_bloom
|
138
|
+
end
|
139
|
+
|
140
|
+
def intrinsic_gas_used
|
141
|
+
num_zero_bytes = data.count(Constant::BYTE_ZERO)
|
142
|
+
num_non_zero_bytes = data.size - num_zero_bytes
|
143
|
+
|
144
|
+
Opcodes::GTXCOST +
|
145
|
+
Opcodes::GTXDATAZERO*num_zero_bytes +
|
146
|
+
Opcodes::GTXDATANONZERO*num_non_zero_bytes
|
147
|
+
end
|
148
|
+
|
149
|
+
def to_h
|
150
|
+
h = {}
|
151
|
+
self.class.serializable_fields.keys.each do |field|
|
152
|
+
h[field] = send field
|
153
|
+
end
|
154
|
+
|
155
|
+
h[:sender] = sender
|
156
|
+
h[:hash] = Utils.encode_hex full_hash
|
157
|
+
h
|
158
|
+
end
|
159
|
+
|
160
|
+
def log_dict
|
161
|
+
h = to_h
|
162
|
+
h[:sender] = Utils.encode_hex(h[:sender] || '')
|
163
|
+
h[:to] = Utils.encode_hex(h[:to])
|
164
|
+
h
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
# returns the address of a contract created by this tx
|
169
|
+
#
|
170
|
+
def creates
|
171
|
+
Utils.mk_contract_address(sender, nonce) if [Address::BLANK, Address::ZERO].include?(to)
|
172
|
+
end
|
173
|
+
|
174
|
+
def ==(other)
|
175
|
+
other.instance_of?(self.class) && full_hash == other.full_hash
|
176
|
+
end
|
177
|
+
|
178
|
+
def hash
|
179
|
+
Utils.big_endian_to_int full_hash
|
180
|
+
end
|
181
|
+
|
182
|
+
def to_s
|
183
|
+
"#<#{self.class.name}:#{object_id} #{Utils.encode_hex(full_hash)[0,8]}"
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
def logger
|
189
|
+
@logger ||= Logger.new 'eth.chain.tx'
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
UnsignedTransaction = Transaction.exclude %i(v r s)
|
195
|
+
|
196
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module Ethereum
|
4
|
+
class TransientTrie < Trie
|
5
|
+
|
6
|
+
class InvalidTransientTrieOperation < StandardError; end
|
7
|
+
|
8
|
+
def transient_trie_exception(*args)
|
9
|
+
raise InvalidTransientTrieOperation
|
10
|
+
end
|
11
|
+
|
12
|
+
alias :[] :transient_trie_exception
|
13
|
+
alias :[]= :transient_trie_exception
|
14
|
+
alias :delete :transient_trie_exception
|
15
|
+
|
16
|
+
def root_hash
|
17
|
+
@transient_root_hash
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_root_hash(hash)
|
21
|
+
raise TypeError, "root hash must be String" unless hash.instance_of?(String)
|
22
|
+
raise ArgumentError, "root hash must be 0 or 32 bytes long" unless [0,32].include?(hash.size)
|
23
|
+
|
24
|
+
@transient_root_hash = hash
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,549 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
require 'ethereum/trie/nibble_key'
|
4
|
+
|
5
|
+
module Ethereum
|
6
|
+
|
7
|
+
##
|
8
|
+
# A implementation of Merkle Patricia Tree.
|
9
|
+
#
|
10
|
+
# @see https://github.com/ethereum/wiki/wiki/Patricia-Tree
|
11
|
+
#
|
12
|
+
class Trie
|
13
|
+
include Enumerable
|
14
|
+
|
15
|
+
NODE_TYPES = %i(blank leaf extension branch).freeze
|
16
|
+
NODE_KV_TYPE = %i(leaf extension).freeze
|
17
|
+
|
18
|
+
BRANCH_CARDINAL = 16
|
19
|
+
BRANCH_WIDTH = BRANCH_CARDINAL + 1
|
20
|
+
KV_WIDTH = 2
|
21
|
+
|
22
|
+
BLANK_NODE = "".freeze
|
23
|
+
BLANK_ROOT = Utils.keccak256_rlp('').freeze
|
24
|
+
|
25
|
+
class InvalidNode < StandardError; end
|
26
|
+
class InvalidNodeType < StandardError; end
|
27
|
+
|
28
|
+
attr :db
|
29
|
+
|
30
|
+
##
|
31
|
+
# It presents a hash like interface.
|
32
|
+
#
|
33
|
+
# @param db [Object] key value database
|
34
|
+
# @param root_hash [String] blank or trie node in form of [key, value] or
|
35
|
+
# [v0, v1, .. v15, v]
|
36
|
+
#
|
37
|
+
def initialize(db, root_hash=BLANK_ROOT)
|
38
|
+
@db = db
|
39
|
+
set_root_hash root_hash
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# @return empty or 32 bytes string
|
44
|
+
#
|
45
|
+
def root_hash
|
46
|
+
# TODO: can I memoize computation below?
|
47
|
+
return BLANK_ROOT if @root_node == BLANK_NODE
|
48
|
+
|
49
|
+
raise InvalidNode, "invalid root node" unless @root_node.instance_of?(Array)
|
50
|
+
|
51
|
+
val = FastRLP.encode @root_node
|
52
|
+
key = Utils.keccak256 val
|
53
|
+
|
54
|
+
@db.put key, val
|
55
|
+
SPV.grabbing @root_node
|
56
|
+
|
57
|
+
key
|
58
|
+
end
|
59
|
+
alias :update_root_hash :root_hash
|
60
|
+
|
61
|
+
def set_root_hash(hash)
|
62
|
+
raise TypeError, "root hash must be String" unless hash.instance_of?(String)
|
63
|
+
raise ArgumentError, "root hash must be 0 or 32 bytes long" unless [0,32].include?(hash.size)
|
64
|
+
|
65
|
+
if hash == BLANK_ROOT
|
66
|
+
@root_node = BLANK_NODE
|
67
|
+
else
|
68
|
+
@root_node = decode_to_node hash
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def root_hash_valid?
|
73
|
+
h = root_hash
|
74
|
+
return true if h == BLANK_ROOT
|
75
|
+
return @db.include?(h)
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Get value from trie.
|
80
|
+
#
|
81
|
+
# @param key [String]
|
82
|
+
#
|
83
|
+
# @return [String] BLANK_NODE if does not exist, otherwise node value
|
84
|
+
#
|
85
|
+
def [](key)
|
86
|
+
find @root_node, NibbleKey.from_string(key)
|
87
|
+
end
|
88
|
+
alias :get :[]
|
89
|
+
|
90
|
+
##
|
91
|
+
# Set value of key.
|
92
|
+
#
|
93
|
+
# @param key [String]
|
94
|
+
# @param value [String]
|
95
|
+
#
|
96
|
+
def []=(key, value)
|
97
|
+
raise ArgumentError, "key must be string" unless key.instance_of?(String)
|
98
|
+
raise ArgumentError, "value must be string" unless value.instance_of?(String)
|
99
|
+
|
100
|
+
@root_node = update_and_delete_storage(
|
101
|
+
@root_node,
|
102
|
+
NibbleKey.from_string(key),
|
103
|
+
value
|
104
|
+
)
|
105
|
+
|
106
|
+
update_root_hash
|
107
|
+
end
|
108
|
+
alias :set :[]=
|
109
|
+
|
110
|
+
##
|
111
|
+
# Delete value at key.
|
112
|
+
#
|
113
|
+
# @param key [String] a string with length of [0,32]
|
114
|
+
#
|
115
|
+
def delete(key)
|
116
|
+
raise ArgumentError, "key must be string" unless key.instance_of?(String)
|
117
|
+
raise ArgumentError, "max key size is 32" if key.size > 32
|
118
|
+
|
119
|
+
@root_node = delete_and_delete_storage(
|
120
|
+
@root_node,
|
121
|
+
NibbleKey.from_string(key)
|
122
|
+
)
|
123
|
+
|
124
|
+
update_root_hash
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Convert to hash.
|
129
|
+
#
|
130
|
+
def to_h
|
131
|
+
h = {}
|
132
|
+
to_hash(@root_node).each do |k, v|
|
133
|
+
key = k.terminate(false).to_string
|
134
|
+
h[key] = v
|
135
|
+
end
|
136
|
+
h
|
137
|
+
end
|
138
|
+
|
139
|
+
def each(&block)
|
140
|
+
to_h.each(&block)
|
141
|
+
end
|
142
|
+
|
143
|
+
def has_key?(key)
|
144
|
+
self[key] != BLANK_NODE
|
145
|
+
end
|
146
|
+
alias :include? :has_key?
|
147
|
+
|
148
|
+
##
|
149
|
+
# Get count of all nodes of the trie.
|
150
|
+
#
|
151
|
+
def size
|
152
|
+
get_size @root_node
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# clear all tree data
|
157
|
+
#
|
158
|
+
def clear
|
159
|
+
delete_child_storage(@root_node)
|
160
|
+
delete_node_storage(@root_node)
|
161
|
+
@root_node = BLANK_NODE
|
162
|
+
end
|
163
|
+
|
164
|
+
##
|
165
|
+
# Get value inside a node.
|
166
|
+
#
|
167
|
+
# @param node [Array, BLANK_NODE] node in form of array, or BLANK_NODE
|
168
|
+
# @param nbk [Array] nibble array without terminator
|
169
|
+
#
|
170
|
+
# @return [String] BLANK_NODE if does not exist, otherwise node value
|
171
|
+
#
|
172
|
+
def find(node, nbk)
|
173
|
+
node_type = get_node_type node
|
174
|
+
|
175
|
+
case node_type
|
176
|
+
when :blank
|
177
|
+
BLANK_NODE
|
178
|
+
when :branch
|
179
|
+
return node.last if nbk.empty?
|
180
|
+
|
181
|
+
sub_node = decode_to_node node[nbk[0]]
|
182
|
+
find sub_node, nbk[1..-1]
|
183
|
+
when :leaf
|
184
|
+
node_key = NibbleKey.decode(node[0]).terminate(false)
|
185
|
+
nbk == node_key ? node[1] : BLANK_NODE
|
186
|
+
when :extension
|
187
|
+
node_key = NibbleKey.decode(node[0]).terminate(false)
|
188
|
+
if node_key.prefix?(nbk)
|
189
|
+
sub_node = decode_to_node node[1]
|
190
|
+
find sub_node, nbk[node_key.size..-1]
|
191
|
+
else
|
192
|
+
BLANK_NODE
|
193
|
+
end
|
194
|
+
else
|
195
|
+
raise InvalidNodeType, "node type must be in #{NODE_TYPES}, given: #{node_type}"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
##
|
202
|
+
# Get counts of (key, value) stored in this and the descendant nodes.
|
203
|
+
#
|
204
|
+
# TODO: refactor into Node class
|
205
|
+
#
|
206
|
+
# @param node [Array, BLANK_NODE] node in form of array, or BLANK_NODE
|
207
|
+
#
|
208
|
+
# @return [Integer]
|
209
|
+
#
|
210
|
+
def get_size(node)
|
211
|
+
case get_node_type(node)
|
212
|
+
when :branch
|
213
|
+
sizes = node[0,BRANCH_CARDINAL].map {|n| get_size decode_to_node(n) }
|
214
|
+
sizes.push(node.last.false? ? 0 : 1)
|
215
|
+
sizes.reduce(0, &:+)
|
216
|
+
when :extension
|
217
|
+
get_size decode_to_node(node[1])
|
218
|
+
when :leaf
|
219
|
+
1
|
220
|
+
when :blank
|
221
|
+
0
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def encode_node(node)
|
226
|
+
return BLANK_NODE if node == BLANK_NODE
|
227
|
+
raise ArgumentError, "node must be an array" unless node.instance_of?(Array)
|
228
|
+
|
229
|
+
rlp_node = FastRLP.encode node
|
230
|
+
return node if rlp_node.size < 32
|
231
|
+
|
232
|
+
hashkey = Utils.keccak256 rlp_node
|
233
|
+
@db.put hashkey, rlp_node
|
234
|
+
SPV.store node
|
235
|
+
|
236
|
+
hashkey
|
237
|
+
end
|
238
|
+
|
239
|
+
def decode_to_node(encoded)
|
240
|
+
return BLANK_NODE if encoded == BLANK_NODE
|
241
|
+
return encoded if encoded.instance_of?(Array)
|
242
|
+
|
243
|
+
RLP.decode(@db.get(encoded))
|
244
|
+
.tap {|o| SPV.grabbing o }
|
245
|
+
end
|
246
|
+
|
247
|
+
# TODO: refactor, abstract delete storage logic
|
248
|
+
def update_and_delete_storage(node, key, value)
|
249
|
+
old_node = node.dup
|
250
|
+
new_node = update_node(node, key, value)
|
251
|
+
delete_node_storage(old_node) if old_node != new_node
|
252
|
+
new_node
|
253
|
+
end
|
254
|
+
|
255
|
+
def delete_and_delete_storage(node, key)
|
256
|
+
old_node = node.dup
|
257
|
+
new_node = delete_node(node, key)
|
258
|
+
delete_node_storage(old_node) if old_node != new_node
|
259
|
+
new_node
|
260
|
+
end
|
261
|
+
|
262
|
+
##
|
263
|
+
# Update item inside a node.
|
264
|
+
#
|
265
|
+
# If this node is changed to a new node, it's parent will take the
|
266
|
+
# responsibility to **store** the new node storage, and delete the old node
|
267
|
+
# storage.
|
268
|
+
#
|
269
|
+
# @param node [Array, BLANK_NODE] node in form of array, or BLANK_NODE
|
270
|
+
# @param key [Array] nibble key without terminator
|
271
|
+
# @param value [String] value string
|
272
|
+
#
|
273
|
+
# @return [Array, BLANK_NODE] new node
|
274
|
+
#
|
275
|
+
def update_node(node, key, value)
|
276
|
+
node_type = get_node_type node
|
277
|
+
|
278
|
+
case node_type
|
279
|
+
when :blank
|
280
|
+
[key.terminate(true).encode, value]
|
281
|
+
when :branch
|
282
|
+
if key.empty?
|
283
|
+
node.last = value
|
284
|
+
else
|
285
|
+
new_node = update_and_delete_storage(
|
286
|
+
decode_to_node(node[key[0]]),
|
287
|
+
key[1..-1],
|
288
|
+
value
|
289
|
+
)
|
290
|
+
node[key[0]] = encode_node new_node
|
291
|
+
end
|
292
|
+
|
293
|
+
node
|
294
|
+
else # kv node type
|
295
|
+
update_kv_node(node, key, value)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# TODO: refactor this crazy tall guy
|
300
|
+
def update_kv_node(node, key, value)
|
301
|
+
node_type = get_node_type node
|
302
|
+
node_key = NibbleKey.decode(node[0]).terminate(false)
|
303
|
+
inner = node_type == :extension
|
304
|
+
|
305
|
+
common_key = node_key.common_prefix(key)
|
306
|
+
remain_key = key[common_key.size..-1]
|
307
|
+
remain_node_key = node_key[common_key.size..-1]
|
308
|
+
|
309
|
+
if remain_key.empty? && remain_node_key.empty? # target key equals node's key
|
310
|
+
if inner
|
311
|
+
new_node = update_and_delete_storage(
|
312
|
+
decode_to_node(node[1]),
|
313
|
+
remain_key,
|
314
|
+
value
|
315
|
+
)
|
316
|
+
else
|
317
|
+
return [node[0], value]
|
318
|
+
end
|
319
|
+
elsif remain_node_key.empty? # target key includes node's key
|
320
|
+
if inner
|
321
|
+
new_node = update_and_delete_storage(
|
322
|
+
decode_to_node(node[1]),
|
323
|
+
remain_key,
|
324
|
+
value
|
325
|
+
)
|
326
|
+
else # node is a leaf, we need to replace it with branch node first
|
327
|
+
new_node = [BLANK_NODE] * BRANCH_WIDTH
|
328
|
+
new_node[-1] = node[1]
|
329
|
+
new_node[remain_key[0]] = encode_node([
|
330
|
+
remain_key[1..-1].terminate(true).encode,
|
331
|
+
value
|
332
|
+
])
|
333
|
+
end
|
334
|
+
else
|
335
|
+
new_node = [BLANK_NODE] * BRANCH_WIDTH
|
336
|
+
|
337
|
+
if remain_node_key.size == 1 && inner
|
338
|
+
new_node[remain_node_key[0]] = node[1]
|
339
|
+
else
|
340
|
+
new_node[remain_node_key[0]] = encode_node([
|
341
|
+
remain_node_key[1..-1].terminate(!inner).encode,
|
342
|
+
node[1]
|
343
|
+
])
|
344
|
+
end
|
345
|
+
|
346
|
+
if remain_key.empty? # node's key include target key
|
347
|
+
new_node[-1] = value
|
348
|
+
else
|
349
|
+
new_node[remain_key[0]] = encode_node([
|
350
|
+
remain_key[1..-1].terminate(true).encode,
|
351
|
+
value
|
352
|
+
])
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
if common_key.empty?
|
357
|
+
new_node
|
358
|
+
else
|
359
|
+
[node_key[0, common_key.size].encode, encode_node(new_node)]
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
##
|
364
|
+
# Delete item inside node.
|
365
|
+
#
|
366
|
+
# If this node is changed to a new node, it's parent will take the
|
367
|
+
# responsibility to **store** the new node storage, and delete the old node
|
368
|
+
# storage.
|
369
|
+
#
|
370
|
+
# @param node [Array, BLANK_NODE] node in form of array, or BLANK_NODE
|
371
|
+
# @param key [Array] nibble key without terminator. key maybe empty
|
372
|
+
#
|
373
|
+
# @return new node
|
374
|
+
#
|
375
|
+
def delete_node(node, key)
|
376
|
+
case get_node_type(node)
|
377
|
+
when :blank
|
378
|
+
BLANK_NODE
|
379
|
+
when :branch
|
380
|
+
delete_branch_node(node, key)
|
381
|
+
else # kv type
|
382
|
+
delete_kv_node(node, key)
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
def delete_branch_node(node, key)
|
387
|
+
if key.empty?
|
388
|
+
node[-1] = BLANK_NODE
|
389
|
+
return normalize_branch_node(node)
|
390
|
+
else
|
391
|
+
new_sub_node = delete_and_delete_storage decode_to_node(node[key[0]]), key[1..-1]
|
392
|
+
encoded_new_sub_node = encode_node new_sub_node
|
393
|
+
|
394
|
+
return node if encoded_new_sub_node == node[key[0]]
|
395
|
+
|
396
|
+
node[key[0]] = encoded_new_sub_node
|
397
|
+
return normalize_branch_node(node) if encoded_new_sub_node == BLANK_NODE
|
398
|
+
|
399
|
+
node
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
def delete_kv_node(node, key)
|
404
|
+
node_type = get_node_type node
|
405
|
+
raise ArgumentError, "node type is not one of key-value type (#{NODE_KV_TYPE})" unless NODE_KV_TYPE.include?(node_type)
|
406
|
+
|
407
|
+
node_key = NibbleKey.decode(node[0]).terminate(false)
|
408
|
+
|
409
|
+
# key not found
|
410
|
+
return node unless node_key.prefix?(key)
|
411
|
+
|
412
|
+
if node_type == :leaf
|
413
|
+
key == node_key ? BLANK_NODE : node
|
414
|
+
else # :extension
|
415
|
+
new_sub_node = delete_and_delete_storage decode_to_node(node[1]), key[node_key.size..-1]
|
416
|
+
|
417
|
+
return node if encode_node(new_sub_node) == node[1]
|
418
|
+
return BLANK_NODE if new_sub_node == BLANK_NODE
|
419
|
+
|
420
|
+
raise InvalidNode, "new sub node must be array" unless new_sub_node.instance_of?(Array)
|
421
|
+
|
422
|
+
new_sub_node_type = get_node_type new_sub_node
|
423
|
+
|
424
|
+
case new_sub_node_type
|
425
|
+
when :branch
|
426
|
+
[node_key.encode, encode_node(new_sub_node)]
|
427
|
+
when *NODE_KV_TYPE
|
428
|
+
new_key = node_key + NibbleKey.decode(new_sub_node[0])
|
429
|
+
[new_key.encode, new_sub_node[1]]
|
430
|
+
else
|
431
|
+
raise InvalidNodeType, "invalid kv sub node type #{new_sub_node_type}"
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
def normalize_branch_node(node)
|
437
|
+
non_blank_items = node.each_with_index.select {|(x, i)| x != BLANK_NODE }
|
438
|
+
|
439
|
+
raise ArgumentError, "node must has at least 1 non blank item" unless non_blank_items.size > 0
|
440
|
+
return node if non_blank_items.size > 1
|
441
|
+
|
442
|
+
non_blank_index = non_blank_items[0][1]
|
443
|
+
|
444
|
+
# if value is the only non blank item, convert it into a kv node
|
445
|
+
return [NibbleKey.new([]).terminate(true).encode, node.last] if non_blank_index == NibbleKey::NIBBLE_TERMINATOR
|
446
|
+
|
447
|
+
sub_node = decode_to_node node[non_blank_index]
|
448
|
+
sub_node_type = get_node_type sub_node
|
449
|
+
|
450
|
+
case sub_node_type
|
451
|
+
when :branch
|
452
|
+
[NibbleKey.new([non_blank_index]).encode, encode_node(sub_node)]
|
453
|
+
when *NODE_KV_TYPE
|
454
|
+
new_key = NibbleKey.decode(sub_node[0]).unshift(non_blank_index)
|
455
|
+
[new_key.encode, sub_node[1]]
|
456
|
+
else
|
457
|
+
raise InvalidNodeType, "invalid branch sub node type #{sub_node_type}"
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
##
|
462
|
+
# Delete node storage.
|
463
|
+
#
|
464
|
+
# @param node [Array, BLANK_NODE] node in form of array, or BLANK_NODE
|
465
|
+
#
|
466
|
+
def delete_node_storage(node)
|
467
|
+
return if node == BLANK_NODE
|
468
|
+
raise ArgumentError, "node must be Array or BLANK_NODE" unless node.instance_of?(Array)
|
469
|
+
|
470
|
+
encoded = encode_node node
|
471
|
+
return if encoded.size < 32
|
472
|
+
|
473
|
+
# FIXME: in current trie implementation two nodes can share identical
|
474
|
+
# subtree thus we can not safely delete nodes for now
|
475
|
+
#
|
476
|
+
# \@db.delete encoded
|
477
|
+
end
|
478
|
+
|
479
|
+
def delete_child_storage(node)
|
480
|
+
node_type = get_node_type node
|
481
|
+
case node_type
|
482
|
+
when :branch
|
483
|
+
node[0,BRANCH_CARDINAL].each {|item| delete_child_storage decode_to_node(item) }
|
484
|
+
when :extension
|
485
|
+
delete_child_storage decode_to_node(node[1])
|
486
|
+
else
|
487
|
+
# do nothing
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
##
|
492
|
+
# get node type and content
|
493
|
+
#
|
494
|
+
# @param node [Array, BLANK_NODE] node in form of array, or BLANK_NODE
|
495
|
+
#
|
496
|
+
# @return [Symbol] node type
|
497
|
+
#
|
498
|
+
def get_node_type(node)
|
499
|
+
return :blank if node == BLANK_NODE
|
500
|
+
|
501
|
+
case node.size
|
502
|
+
when KV_WIDTH # [k,v]
|
503
|
+
NibbleKey.decode(node[0]).terminate? ? :leaf : :extension
|
504
|
+
when BRANCH_WIDTH # [k0, ... k15, v]
|
505
|
+
:branch
|
506
|
+
else
|
507
|
+
raise InvalidNode, "node size must be #{KV_WIDTH} or #{BRANCH_WIDTH}"
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
##
|
512
|
+
# Convert [key, value] stored in this and the descendant nodes to hash.
|
513
|
+
#
|
514
|
+
# @param node [Array, BLANK_NODE] node in form of array, or BLANK_NODE
|
515
|
+
#
|
516
|
+
# @return [Hash] equivalent hash. Hash key is in full form (Array).
|
517
|
+
def to_hash(node)
|
518
|
+
node_type = get_node_type node
|
519
|
+
|
520
|
+
case node_type
|
521
|
+
when :blank
|
522
|
+
{}
|
523
|
+
when :branch
|
524
|
+
hash = {}
|
525
|
+
|
526
|
+
16.times do |i|
|
527
|
+
sub_hash = to_hash decode_to_node(node[i])
|
528
|
+
sub_hash.each {|k, v| hash[NibbleKey.new([i]) + k] = v }
|
529
|
+
end
|
530
|
+
|
531
|
+
hash[NibbleKey.terminator] = node.last if node.last.true?
|
532
|
+
hash
|
533
|
+
when *NODE_KV_TYPE
|
534
|
+
nibbles = NibbleKey.decode(node[0]).terminate(false)
|
535
|
+
|
536
|
+
sub_hash = node_type == :extension ?
|
537
|
+
to_hash(decode_to_node(node[1])) : {NibbleKey.terminator => node[1]}
|
538
|
+
|
539
|
+
{}.tap do |hash|
|
540
|
+
sub_hash.each {|k, v| hash[nibbles + k] = v }
|
541
|
+
end
|
542
|
+
else
|
543
|
+
# do nothing
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
end
|
548
|
+
|
549
|
+
end
|