rlp 0.3.0 → 0.5.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 +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
|