rlp 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +20 -0
- data/lib/rlp.rb +4 -0
- data/lib/rlp/decode.rb +35 -6
- data/lib/rlp/decode_lazy.rb +103 -0
- data/lib/rlp/encode.rb +9 -1
- data/lib/rlp/lazy_list.rb +86 -0
- data/lib/rlp/sedes/binary.rb +4 -0
- data/lib/rlp/sedes/serializable.rb +18 -7
- data/lib/rlp/utils.rb +25 -0
- data/lib/rlp/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a740accf9d4e050d92d280aacd38699feffb6081
|
4
|
+
data.tar.gz: fdae1f3979c78452f141062f95b3254f9aac4220
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b18bb3dd56837f948f44e7797f5497e3140dedfb98d50698c53470a351db879bacce83b4861c0d1d638f2ade563bcc2450ca60cca8fc372459122a0a45a5c3d
|
7
|
+
data.tar.gz: b143e3ef2289684e754c74406581126c8734845c274f04e8a952e17e65147eabdfde80e8de7e4f8fc801f916dcef024a4ba68597a03c37586f4c158e50a6fb61
|
data/README.md
CHANGED
@@ -1,3 +1,23 @@
|
|
1
1
|
# ruby-rlp
|
2
2
|
|
3
3
|
A Ruby implementation of Ethereum's Recursive Length Prefix encoding (RLP). You can find the specification of the standard in the [Ethereum wiki](https://github.com/ethereum/wiki/wiki/RLP).
|
4
|
+
|
5
|
+
## Benchmark
|
6
|
+
|
7
|
+
```
|
8
|
+
ruby-rlp $ ruby -v -Ilib test/speed.py
|
9
|
+
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
|
10
|
+
Block serializations / sec: 2318.21
|
11
|
+
Block deserializations / sec: 1704.61
|
12
|
+
TX serializations / sec: 30461.76
|
13
|
+
TX deserializations / sec: 21378.70
|
14
|
+
|
15
|
+
pyrlp $ python -V
|
16
|
+
Python 2.7.11
|
17
|
+
|
18
|
+
pyrlp $ PYTHONPATH=. python tests/speed.py
|
19
|
+
Block serializations / sec: 1225.00
|
20
|
+
Block deserializations / sec: 1162.01
|
21
|
+
TX serializations / sec: 16468.41
|
22
|
+
TX deserializations / sec: 14517.31
|
23
|
+
```
|
data/lib/rlp.rb
CHANGED
data/lib/rlp/decode.rb
CHANGED
@@ -4,10 +4,31 @@ module RLP
|
|
4
4
|
include Error
|
5
5
|
include Utils
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
##
|
8
|
+
# Decode an RLP encoded object.
|
9
|
+
#
|
10
|
+
# If the deserialized result `obj` has an attribute `_cached_rlp` (e.g. if
|
11
|
+
# `sedes` is a subclass of {RLP::Sedes::Serializable}), it will be set to
|
12
|
+
# `rlp`, which will improve performance on subsequent {RLP::Encode#encode}
|
13
|
+
# calls. Bear in mind however that `obj` needs to make sure that this value
|
14
|
+
# is updated whenever one of its fields changes or prevent such changes
|
15
|
+
# entirely ({RLP::Sedes::Serializable} does the latter).
|
16
|
+
#
|
17
|
+
# @param sedes [#deserialize] an object implementing a function
|
18
|
+
# `deserialize(code)` which will be applied after decoding, or `nil` if
|
19
|
+
# no deserialization should be performed
|
20
|
+
# @param options [Hash] additional keyword arguments that will be passed to
|
21
|
+
# the deserializer
|
22
|
+
# @param strict [Boolean] if false inputs that are longer than necessary
|
23
|
+
# don't cause an exception
|
24
|
+
#
|
25
|
+
# @return [Object] the decoded and maybe deserialized object
|
26
|
+
#
|
27
|
+
# @raise [RLP::Error::DecodingError] if the input string does not end after
|
28
|
+
# the root item and `strict` is true
|
29
|
+
# @raise [RLP::Error::DeserializationError] if the deserialization fails
|
30
|
+
#
|
31
|
+
def decode(rlp, sedes: nil, strict: true, options: {})
|
11
32
|
rlp = str_to_bytes(rlp)
|
12
33
|
|
13
34
|
begin
|
@@ -19,8 +40,16 @@ module RLP
|
|
19
40
|
raise DecodingError.new("RLP string ends with #{rlp.size - next_start} superfluous bytes", rlp) if next_start != rlp.size && strict
|
20
41
|
|
21
42
|
if sedes
|
22
|
-
|
23
|
-
|
43
|
+
# FIXME: lazy man's kwargs
|
44
|
+
obj = sedes.is_a?(Sedes::Serializable) ?
|
45
|
+
sedes.deserialize(item, **options) :
|
46
|
+
sedes.deserialize(item)
|
47
|
+
|
48
|
+
if obj.respond_to?(:_cached_rlp)
|
49
|
+
obj._cached_rlp = rlp
|
50
|
+
raise "RLP::Sedes::Serializable object must be immutable after decode" if obj.is_a?(Sedes::Serializable) && obj.mutable?
|
51
|
+
end
|
52
|
+
|
24
53
|
obj
|
25
54
|
else
|
26
55
|
item
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module RLP
|
2
|
+
module DecodeLazy
|
3
|
+
include Decode
|
4
|
+
|
5
|
+
##
|
6
|
+
# Decode an RLP encoded object in a lazy fashion.
|
7
|
+
#
|
8
|
+
# If the encoded object is a byte string, this function acts similar to
|
9
|
+
# {RLP::Decode#decode}. If it is a list however, a {LazyList} is returned
|
10
|
+
# instead. This object will decode the string lazily, avoiding both
|
11
|
+
# horizontal and vertical traversing as much as possible.
|
12
|
+
#
|
13
|
+
# The way `sedes` is applied depends on the decoded object: If it is a
|
14
|
+
# string `sedes` deserializes it as a whole; if it is a list, each element
|
15
|
+
# is deserialized individually. In both cases, `sedes_options` are passed
|
16
|
+
# on. Note that, if a deserializer is used, only "horizontal" but not
|
17
|
+
# "vertical lazyness" can be preserved.
|
18
|
+
#
|
19
|
+
# @param rlp [String] the RLP string to decode
|
20
|
+
# @param sedes [Object] an object implementing a method `deserialize(code)`
|
21
|
+
# which is used as described above, or `nil` if no deserialization should
|
22
|
+
# be performed
|
23
|
+
# @param sedes_options [Hash] additional keyword arguments that will be
|
24
|
+
# passed to the deserializers
|
25
|
+
#
|
26
|
+
# @return [Object] either the already decoded and deserialized object (if
|
27
|
+
# encoded as a string) or an instance of {RLP::LazyList}
|
28
|
+
#
|
29
|
+
def decode_lazy(rlp, sedes: nil, sedes_options: {})
|
30
|
+
item, next_start = consume_item_lazy(rlp, 0)
|
31
|
+
|
32
|
+
raise DecodingError.new("RLP length prefix announced wrong length", rlp) if next_start != rlp.size
|
33
|
+
|
34
|
+
if item.instance_of?(LazyList)
|
35
|
+
item.sedes = sedes
|
36
|
+
item.sedes_options = sedes_options
|
37
|
+
item
|
38
|
+
elsif sedes
|
39
|
+
# FIXME: lazy man's kwargs
|
40
|
+
sedes_options.empty? ?
|
41
|
+
sedes.deserialize(item) :
|
42
|
+
sedes.deserialize(item, **sedes_options)
|
43
|
+
else
|
44
|
+
item
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Read an item from an RLP string lazily.
|
50
|
+
#
|
51
|
+
# If the length prefix announces a string, the string is read; if it
|
52
|
+
# announces a list, a {LazyList} is created.
|
53
|
+
#
|
54
|
+
# @param rlp [String] the rlp string to read from
|
55
|
+
# @param start [Integer] the position at which to start reading
|
56
|
+
#
|
57
|
+
# @return [Array] A pair `[item, next_start]` where `item` is the read
|
58
|
+
# string or a {LazyList} and `next_start` is the position of the first
|
59
|
+
# unprocessed byte
|
60
|
+
#
|
61
|
+
def consume_item_lazy(rlp, start)
|
62
|
+
t, l, s = consume_length_prefix(rlp, start)
|
63
|
+
if t == :str
|
64
|
+
consume_payload(rlp, s, :str, l)
|
65
|
+
elsif t == :list
|
66
|
+
[LazyList.new(rlp, s, s+l), s+l]
|
67
|
+
else
|
68
|
+
raise "Invalid item type: #{t}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Get a specific element from an rlp encoded nested list.
|
74
|
+
#
|
75
|
+
# This method uses {RLP::DecodeLazy#decode_lazy} and, thus, decodes only
|
76
|
+
# the necessary parts of the string.
|
77
|
+
#
|
78
|
+
# @example Usage
|
79
|
+
# rlpdata = RLP.encode([1, 2, [3, [4, 5]]])
|
80
|
+
# RLP.peek(rlpdata, 0, sedes: RLP::Sedes.big_endian_int) # => 1
|
81
|
+
# RLP.peek(rlpdata, [2, 0], sedes: RLP::Sedes.big_endian_int) # => 3
|
82
|
+
#
|
83
|
+
# @param rlp [String] the rlp string
|
84
|
+
# @param index [Integer, Array] the index of the element to peek at (can be
|
85
|
+
# a list for nested data)
|
86
|
+
# @param sedes [#deserialize] a sedes used to deserialize the peeked at
|
87
|
+
# object, or `nil` if no deserialization should be performed
|
88
|
+
#
|
89
|
+
# @raise [IndexError] if `index` is invalid (out of range or too many levels)
|
90
|
+
#
|
91
|
+
def peek(rlp, index, sedes: nil)
|
92
|
+
ll = decode_lazy(rlp)
|
93
|
+
index = Array(index)
|
94
|
+
|
95
|
+
index.each do |i|
|
96
|
+
raise IndexError, "Too many indices given" if primitive?(ll)
|
97
|
+
ll = ll.fetch(i)
|
98
|
+
end
|
99
|
+
|
100
|
+
sedes ? sedes.deserialize(ll) : ll
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/rlp/encode.rb
CHANGED
@@ -39,7 +39,9 @@ module RLP
|
|
39
39
|
# @raise [RLP::SerializationError] if the serialization fails
|
40
40
|
#
|
41
41
|
def encode(obj, sedes: nil, infer_serializer: true, cache: false)
|
42
|
-
|
42
|
+
return obj._cached_rlp if obj.is_a?(Sedes::Serializable) && obj._cached_rlp && sedes.nil?
|
43
|
+
|
44
|
+
really_cache = obj.is_a?(Sedes::Serializable) && sedes.nil? && cache
|
43
45
|
|
44
46
|
if sedes
|
45
47
|
item = sedes.serialize(obj)
|
@@ -50,6 +52,12 @@ module RLP
|
|
50
52
|
end
|
51
53
|
|
52
54
|
result = encode_raw(item)
|
55
|
+
|
56
|
+
if really_cache
|
57
|
+
obj._cached_rlp = result
|
58
|
+
obj.make_immutable!
|
59
|
+
end
|
60
|
+
|
53
61
|
result
|
54
62
|
end
|
55
63
|
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module RLP
|
2
|
+
##
|
3
|
+
# A RLP encoded list which decodes itself when necessary.
|
4
|
+
#
|
5
|
+
# Both indexing with positive indices and iterating are supported. Getting
|
6
|
+
# the length is possible as well but requires full horizontal encoding.
|
7
|
+
#
|
8
|
+
class LazyList
|
9
|
+
include Enumerable
|
10
|
+
include DecodeLazy
|
11
|
+
|
12
|
+
attr_accessor :sedes, :sedes_options
|
13
|
+
|
14
|
+
##
|
15
|
+
# @param rlp [String] the rlp string in which the list is encoded
|
16
|
+
# @param start [Integer] the position of the first payload byte of the
|
17
|
+
# encoded list
|
18
|
+
# @param next_start [Integer] the position of the last payload byte of the
|
19
|
+
# encoded list
|
20
|
+
# @param sedes [Object] a sedes object which deserializes each element of the
|
21
|
+
# list, or `nil` for on deserialization
|
22
|
+
# @param sedes_options [Hash] keyword arguments which will be passed on to
|
23
|
+
# the deserializer
|
24
|
+
#
|
25
|
+
def initialize(rlp, start, next_start, sedes: nil, sedes_options: nil)
|
26
|
+
@rlp = rlp
|
27
|
+
@start = start
|
28
|
+
@next_start = next_start
|
29
|
+
@index = start
|
30
|
+
@elements = []
|
31
|
+
@size = nil
|
32
|
+
@sedes = sedes
|
33
|
+
@sedes_options = sedes_options
|
34
|
+
end
|
35
|
+
|
36
|
+
def next_item
|
37
|
+
if @index == @next_start
|
38
|
+
@size = @elements.size
|
39
|
+
raise StopIteration
|
40
|
+
elsif @index < @next_start
|
41
|
+
item, @index = consume_item_lazy @rlp, @index
|
42
|
+
|
43
|
+
if @sedes
|
44
|
+
# FIXME: lazy man's kwargs
|
45
|
+
item = @sedes_options.empty? ?
|
46
|
+
@sedes.deserialize(item) :
|
47
|
+
@sedes.deserialize(item, **@sedes_options)
|
48
|
+
end
|
49
|
+
|
50
|
+
@elements.push item
|
51
|
+
item
|
52
|
+
else
|
53
|
+
raise "Assertion failed: index cannot be larger than next start"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def each(&block)
|
58
|
+
@elements.each(&block)
|
59
|
+
loop { block.call(next_item) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def [](i)
|
63
|
+
fetch(i, nil)
|
64
|
+
end
|
65
|
+
|
66
|
+
def fetch(*args)
|
67
|
+
i = args[0]
|
68
|
+
|
69
|
+
loop do
|
70
|
+
raise StopIteration if @elements.size > i
|
71
|
+
next_item
|
72
|
+
end
|
73
|
+
|
74
|
+
@elements.fetch(*args)
|
75
|
+
end
|
76
|
+
|
77
|
+
def size
|
78
|
+
unless @size
|
79
|
+
loop { next_item }
|
80
|
+
end
|
81
|
+
@size
|
82
|
+
end
|
83
|
+
alias :length :size
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
data/lib/rlp/sedes/binary.rb
CHANGED
@@ -76,6 +76,8 @@ module RLP
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
79
|
+
attr_accessor :_cached_rlp
|
80
|
+
|
79
81
|
def initialize(*args)
|
80
82
|
serializable_initialize(*args)
|
81
83
|
end
|
@@ -101,27 +103,36 @@ module RLP
|
|
101
103
|
raise TypeError, "Not all fields initialized" unless field_set.size == 0
|
102
104
|
end
|
103
105
|
|
104
|
-
def ==(other)
|
105
|
-
return false unless other.class.respond_to?(:serialize)
|
106
|
-
self.class.serialize(self) == other.class.serialize(other)
|
107
|
-
end
|
108
|
-
|
109
106
|
def _set_field(field, value)
|
110
107
|
unless instance_variable_defined?(:@_mutable)
|
111
108
|
@_mutable = true
|
112
109
|
end
|
113
110
|
|
114
|
-
if
|
111
|
+
if mutable? || !self.class.serializable_fields.has_key?(field)
|
115
112
|
instance_variable_set :"@#{field}", value
|
116
113
|
else
|
117
114
|
raise ArgumentError, "Tried to mutate immutable object"
|
118
115
|
end
|
119
116
|
end
|
120
117
|
|
121
|
-
def
|
118
|
+
def ==(other)
|
119
|
+
return false unless other.class.respond_to?(:serialize)
|
120
|
+
self.class.serialize(self) == other.class.serialize(other)
|
121
|
+
end
|
122
|
+
|
123
|
+
def mutable?
|
122
124
|
@_mutable
|
123
125
|
end
|
124
126
|
|
127
|
+
def make_immutable!
|
128
|
+
@_mutable = true
|
129
|
+
self.class.serializable_fields.keys.each do |field|
|
130
|
+
::RLP::Utils.make_immutable! send(field)
|
131
|
+
end
|
132
|
+
|
133
|
+
@_mutable = false
|
134
|
+
self
|
135
|
+
end
|
125
136
|
end
|
126
137
|
end
|
127
138
|
end
|
data/lib/rlp/utils.rb
CHANGED
@@ -1,5 +1,30 @@
|
|
1
1
|
module RLP
|
2
2
|
module Utils
|
3
|
+
class <<self
|
4
|
+
##
|
5
|
+
# Do your best to make `obj` as immutable as possible.
|
6
|
+
#
|
7
|
+
# If `obj` is a list, apply this function recursively to all elements and
|
8
|
+
# return a new list containing them. If `obj` is an instance of
|
9
|
+
# {RLP::Sedes::Serializable}, apply this function to its fields, and set
|
10
|
+
# `@_mutable` to `false`. If `obj` is neither of the above, just return
|
11
|
+
# `obj`.
|
12
|
+
#
|
13
|
+
# @return [Object] `obj` after making it immutable
|
14
|
+
#
|
15
|
+
def make_immutable!(obj)
|
16
|
+
if obj.is_a?(Sedes::Serializable)
|
17
|
+
obj.make_immutable!
|
18
|
+
elsif list?(obj)
|
19
|
+
obj.map {|e| make_immutable!(e) }
|
20
|
+
else
|
21
|
+
obj
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
extend self
|
27
|
+
|
3
28
|
def primitive?(item)
|
4
29
|
item.instance_of?(String)
|
5
30
|
end
|
data/lib/rlp/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rlp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jan Xie
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-02-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -66,8 +66,10 @@ files:
|
|
66
66
|
- lib/rlp/constant.rb
|
67
67
|
- lib/rlp/data.rb
|
68
68
|
- lib/rlp/decode.rb
|
69
|
+
- lib/rlp/decode_lazy.rb
|
69
70
|
- lib/rlp/encode.rb
|
70
71
|
- lib/rlp/error.rb
|
72
|
+
- lib/rlp/lazy_list.rb
|
71
73
|
- lib/rlp/sedes.rb
|
72
74
|
- lib/rlp/sedes/big_endian_int.rb
|
73
75
|
- lib/rlp/sedes/binary.rb
|