Fast ethereum rlp encode, decode and object mapping in java.
Supports RLP primitives of
boolean,short,int,long,java.math.BigIntegerandString.Supports POJO(Plain Ordinary Java Object) with at least one
@RLPannotated field, a no-arguments constructor is required.Supports container-like interfaces of
java.util.Collection,java.util.List,java.util.Set,java.util.Queue,java.util.Deque,java.util.Map,java.util.ConcurrentMapand their no-abstract implementations.Generic info of fields in POJO class could be nested to arbitrary deepth.
Every value in
@RLPof a POJO class should be unique and continous.String s = nullwill be encoded as empty stringString s = "".For boolean,
truewill be encoded as1whilefalsewill be encoded as0nullvalues of Boolean, Byte, Short, Integer, Long and BigInteger will be encoded as0nullbyte arraybyte[] bytes = nullwill be encoded as empty byte arraybyte[] bytes = new byte[0]transient field will be ignored by default
- Maps will be encoded as key-value pairs RLPList [key1, value1, key2, value2, ...].
java.util.TreeMapis recommended implementation ofjava.util.Mapsince key-value pairs in TreeMap are ordered.java.util.TreeSetis recommended implementation ofjava.util.Setsince keys in TreeSet are ordered.- Besides
java.util.TreeMap, the ordering of key-value pairs could be specified by@RLPEncoding.keyOrdering()when encoding. - Besides
java.util.TreeSet, the ordering of element ofjava.util.Setcould be specified by@RLPEncoding.keyOrdering()when encoding. - If the ordering of key-value pairs is absent, the encoding of the
java.util.Mapmay not be predictable, encoding ofjava.util.Setis similar.
- RLP object mapping
packageorg.tdf.rlp;
importjava.util.ArrayList;
importjava.util.Arrays;
importjava.util.Collection;
importjava.util.List;
// RLP could encode & decode Tree-like object.publicclassNode{
// declared fields will be encoded with ordering of handwritingpublicStringname;
publicList<Node> children;
// field with @RLPIgnored will be ignored when encoding & decoding@RLPIgnoredpublicStringignored;
// if some fields are annotated with @RLP, // fields without @RLP annotation will be ignored// the fields above is analogy to/** * @RLP(0) * public String name; * @RLP(1) * public List<Node> children; * * public String ignored; **/// a no-argument constructorpublicNode() {
}
publicNode(Stringname) {
this.name = name;
}
publicvoidaddChildren(Collection<Node> nodes){
if(children == null){
children = newArrayList<>();
}
if (!nodes.stream().allMatch(x -> x != this)){
thrownewRuntimeException("tree like object cannot add self as children");
}
children.addAll(nodes);
}
publicstaticvoidmain(String[] args){
Noderoot = newNode("1");
root.addChildren(Arrays.asList(newNode("2"), newNode("3")));
Nodenode2 = root.children.get(0);
node2.addChildren(Arrays.asList(newNode("4"), newNode("5")));
root.children.get(1).addChildren(Arrays.asList(newNode("6"), newNode("7")));
// encode to byte arraybyte[] encoded = RLPCodec.encode(root);
// read as rlp treeRLPElementel = RLPElement.readRLPTree(root);
// decode from byte arrayNoderoot2 = RLPCodec.decode(encoded, Node.class);
el = RLPElement.fromEncoded(encoded);
// decode from rlp elementroot2 = el.as(Node.class);
root2 = RLPCodec.decode(el, Node.class);
}
publicstaticvoidassertTrue(booleanb){
if(!b) thrownewRuntimeException("assertion failed");
}
}- RLP encode & decode of POJO with tree-like field.
publicstaticclassTree{
@RLPDecoding(as = ConcurrentHashMap.class) /* The decoded type will be java.util.concurrent.ConcurrentHashMap instead of java.util.HashMap which is the default implementation of java.util.Map. */publicMap<Map<String, Set<String>>, byte[]> tree;
// although ByteArrayMap in ethereumJ is not supported by default, you can enable it by annotation@RLPDecoding(as = ByteArrayMap.class)
publicMap<byte[], String> stringMap;
}publicclassMain{
publicstaticvoidmain(String[] args){
Treetree = newTree();
tree.tree = newHashMap<>();
Map<String, Set<String>> map = newHashMap<>();
map.put("1", newHashSet<>(Arrays.asList("1", "2", "3")));
tree.tree.put(map, "1".getBytes());
byte[] encoded = RLPCodec.encode(tree);
RLPElementel = RLPElement.fromEncoded(encoded, false);
Treetree1 = RLPCodec.decode(encoded, Tree.class);
asserttree1.treeinstanceofConcurrentHashMap;
Map<String, Set<String>> tree2 = tree1.tree.keySet().stream()
.findFirst().get();
assertArrays.equals(tree1.tree.get(tree2), "1".getBytes());
asserttree2.get("1").containsAll(Arrays.asList("1", "2", "3"));
}
}- Tree-like type encode & decode without wrapper class.
publicclassMain{
// store generic info in a dummy fieldprivateabstractclassDummy2 {
privateList<Map<String, String>> dummy;
}
publicstaticvoidmain(String[] args) throwsException{
List<Map<String, String>> list = newArrayList<>();
list.add(newHashMap<>());
list.get(0).put("1", "1");
List<Map<String, String>> decoded = (List<Map<String, String>>) RLPCodec.decodeContainer(
RLPCodec.encode(list),
Container.fromField(Dummy2.class.getDeclaredField("dummy"))
);
assertdecoded.get(0).get("1").equals("1");
}
}- Custom encode & decode configured by
@RLPEncodingand@RLPDecoding.
packageorg.tdf.rlp;
importjava.util.HashMap;
importjava.util.Map;
publicclassMain{
publicstaticclassMapEncoderDecoderimplementsRLPEncoder<Map<String, String>>, RLPDecoder<Map<String, String>> {
@OverridepublicMap<String, String> decode(RLPElementlist) {
Map<String, String> map = newHashMap<>(list.size() / 2);
for (inti = 0; i < list.size(); i += 2) {
map.put(list.get(i).asString(), list.get(i+1).asString());
}
returnmap;
}
@OverridepublicRLPElementencode(Map<String, String> o) {
RLPListlist = RLPList.createEmpty(o.size() * 2);
o.keySet().stream().sorted(String::compareTo).forEach(x -> {
list.add(RLPItem.fromString(x));
list.add(RLPItem.fromString(o.get(x)));
});
returnlist;
}
}
publicstaticclassMapWrapper{
@RLP@RLPEncoding(MapEncoderDecoder.class)
@RLPDecoding(MapEncoderDecoder.class)
publicMap<String, String> map;
publicMapWrapper(Map<String, String> map) {
this.map = map;
}
publicMapWrapper() {
}
}
publicstaticvoidmain(String[] args){
Map<String, String> m = newHashMap<>();
m.put("a", "1");
m.put("b", "2");
byte[] encoded = RLPCodec.encode(newMapWrapper(m));
MapWrapperdecoded = RLPCodec.decode(encoded, MapWrapper.class);
assertTrue(decoded.map.get("a").equals("1"));
}
publicstaticvoidassertTrue(booleanb){
if(!b) thrownewRuntimeException("assertion failed");
}
}- Add global context to avoid duplicated @RLPEncoding and @RLPDecoding
publicclassMain{
publicstaticclassLocalDateDecoderimplementsRLPDecoder<LocalDate> {
@OverridepublicLocalDatedecode(RLPElementrlpElement) {
if(rlpElement.isNull()) returnnull;
int[] data = rlpElement.as(int[].class);
returnLocalDate.of(data[0], data[1], data[2]);
}
}
publicclassLocalDateEncoderimplementsRLPEncoder<LocalDate> {
@OverridepublicRLPElementencode(LocalDatelocalDate) {
returnRLPElement.readRLPTree(
newint[]{localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth()}
);
}
}
@Data@AllArgsConstructor@NoArgsConstructorpublicstaticclassUser{
privateLocalDatebirthDay;
}
publicstaticvoidmain(String[] args){
RLPContextcontext = RLPContext
.newInstance()
.withDecoder(LocalDate.class, newLocalDateDecoder())
.withEncoder(LocalDate.class, newLocalDateEncoder());
RLPMappermapper = newRLPMapper().withContext(context);
Useru = newUser();
element = mapper.readRLPTree(u);
Userdecoded = mapper.decode(element, User.class);
assertdecoded.birthDay == null;
u.birthDay = LocalDate.now();
byte[] encoded = mapper.encode(u);
decoded = mapper.decode(encoded, User.class);
System.out.println(decoded.birthDay.format(DateTimeFormatter.ISO_DATE)); }
}- Encode & Decode containers
publicclassMain {
publicstaticinterfaceSomeMapextendsMap<String, String>{
}
publicstaticfinalContainerCONTAINER = Container.fromType(SomeMap.class.getGenericInterfaces()[0]);
publicstaticvoidmain(String[] args){
byte[] bytes = newbyte[0];
Map<String, String> m = (Map<String, String>) RLPCodec.decodeContainer(bytes, CONTAINER);
}
}- see RLPTest.performanceDecode for benchmark
Benchmark compare to EthereumJ:
Platform:
- Motherborad: B450M
- CPU: Ryzen 3700x, 8 core, 16 threads
- Memory: (16GB x 2) DDR4 3200hz
decoding list 10000000 times:
ethereumJ: 3698ms our: 2515ms