License | LGPL-3 (see LICENSE) |
---|---|
Maintainer | Javier Sagredo <jasataco@gmail.com> |
Stability | stable |
Safe Haskell | Safe |
Language | Haskell2010 |
An implementation of the Recursive Length Prefix method as described in the Yellow Paper https://ethereum.github.io/yellowpaper/paper.pdf.
To actually use this module, the type that is going to
be encoded has to be instance of RLPSerialize defining
toRLP
and fromRLP
.
Synopsis
- data RLPT
- = RLPL [RLPT]
- | RLPB ByteString
- toBigEndian :: Int -> ByteString
- toBigEndianS :: Int -> ByteString
- fromBigEndian :: ByteString -> Int
- fromBigEndianS :: ByteString -> Int
- toByteString :: String -> ByteString
- toByteStringS :: String -> ByteString
- fromByteString :: ByteString -> String
- fromByteStringS :: ByteString -> String
- class RLPSerialize a where
- toRLP :: a -> RLPT
- fromRLP :: RLPT -> a
- rlpEncode :: a -> ByteString
- rlpDecode :: ByteString -> Maybe a
The RLP Type
The RLPT
type represents the result of transforming the
initial data into its byte-array representation, taking in
account the structure of the fields.
Fields that can't be directly transformed into a ByteString (such as a type with several fields) should generate a list with the representations of its fields (using the RLPL constructor).
RLPT represents the T type defined in the Ethereum Yellowpaper for defining the RLP protocol.
Subtleties
The idea of transforming a custom type into RLPT is to preserve the original structure as far as possible. For example, suppose we have a data structure:
data Name = (String, String) -- represents the first and last name of a Person data Person = Person Name Int -- represents the whole name of a Person and its age
Then the desired output of the transformation of a Person value to RLPT should be (pseudocode):
RLPL [ RLPL [ RLPB, RLPB ], RLPB ]
This way the structure is clearly preserved. Eventhough this does not have to be true as the transformation to RLPL is defined by the user and a custom process can be implemented, it is advised to follow this guideline for better understanding of the generated code.
It is important to remark that although it can't be imposed, it doesn't make sense to try to transform to RLP types with more than one constructor. The transformation should encode a way to find out which of the constructors belongs to the data so not only data is being encoded in the result, also information about the structure futher than the actual length prefixes. That's why it only makes sense to transform to RLP types with just one constructor.
Helper Int functions
toBigEndian :: Int -> ByteString Source #
toBigEndianS :: Int -> ByteString Source #
Strict version of toBigEndian
fromBigEndian :: ByteString -> Int Source #
fromBigEndianS :: ByteString -> Int Source #
Strict version of fromBigEndian
Helper String functions
toByteString :: String -> ByteString Source #
toByteStringS :: String -> ByteString Source #
Strict version of toByteString
fromByteString :: ByteString -> String Source #
fromByteStringS :: ByteString -> String Source #
Strict version of fromByteString
The RLPSerialize class
class RLPSerialize a where Source #
The RLPSerialize
class provides functions for transforming values to RLPT structures.
For encoding and decoding values with the RLP protocol, toRLP
and fromRLP
have to
be implemented.
Instances of RLPSerialize have to satisfy the following property:
fromRLP . toRLP == id
In such case, it can be assured with the default definition that:
rlpDecode . rlpEncode == id
RLPSerialize makes use of the Get and Put classes together with a set of custom serializations for encoding and decoding RLPT data.
Transform a value to the RLPT
structure that best fits its internal structure
Transform an RLPT
structure back into the value it represents
rlpEncode :: a -> ByteString Source #
Transform a value to an RLPT
structure and then encode it following the
RLP standard.
rlpDecode :: ByteString -> Maybe a Source #
Transform a ByteString to an RLPT
structure following the RLP standard and
then transform it to the original type.
Instances
RLPSerialize Bool Source # | |
RLPSerialize Char Source # | |
RLPSerialize Int Source # | |
RLPSerialize String Source # | |
RLPSerialize ByteString Source # | |
Defined in Data.Serialize.RLP toRLP :: ByteString -> RLPT Source # fromRLP :: RLPT -> ByteString Source # rlpEncode :: ByteString -> ByteString0 Source # rlpDecode :: ByteString0 -> Maybe ByteString Source # | |
RLPSerialize RLPT Source # | |
RLPSerialize a => RLPSerialize [a] Source # | |
RLPSerialize a => RLPSerialize (Maybe a) Source # | |
(RLPSerialize a, RLPSerialize b) => RLPSerialize (a, b) Source # | |
(RLPSerialize a, RLPSerialize b, RLPSerialize c) => RLPSerialize (a, b, c) Source # | |
(RLPSerialize a, RLPSerialize b, RLPSerialize c, RLPSerialize d) => RLPSerialize (a, b, c, d) Source # | |
(RLPSerialize a, RLPSerialize b, RLPSerialize c, RLPSerialize d, RLPSerialize e) => RLPSerialize (a, b, c, d, e) Source # | |
(RLPSerialize a, RLPSerialize b, RLPSerialize c, RLPSerialize d, RLPSerialize e, RLPSerialize f) => RLPSerialize (a, b, c, d, e, f) Source # | |
Example
For a full example, we reproduce the implementation of the Person type as in the subtleties section.
First of all, we define the type:
type Name = (String, String) data Person = Person { name :: Name, age :: Int } deriving (Show)
Then we have to make it an instance of RLPSerialize:
instance RLPSerialize Person where toRLP p = RLPL [ RLPL [ toRLP . toByteStringS . fst . name $ p, toRLP . toByteStringS . snd . name $ p ], toRLP . age $ p] fromRLP (RLPL [ RLPL [ RLPB a, RLPB b ], RLPB c ]) = Person (fromByteStringS a, fromByteStringS b) (fromBigEndianS c :: Int)
This way, if the decoding gives rise to other structure than the expected, a runtime exception will be thrown by the pattern matching. We can now use our decoder and encoder with our custom type:
p = Person ("John", "Snow") 33 e = rlpEncode p -- "\204\202\132John\132Snow!" ~ [204,202,132,74,111,104,110,132,83,110,111,119,33] rlpDecode e :: Maybe Person -- Just (Person {name = ("John","Snow"), age = 33})