项目作者: TrustedDataFramework

项目描述 :
fast ethereum rlp decode/encode in java
高级语言: Java
项目地址: git://github.com/TrustedDataFramework/java-rlp.git
创建时间: 2019-12-04T05:53:28Z
项目社区:https://github.com/TrustedDataFramework/java-rlp

开源协议:MIT License

下载


Java CI

java-rlp

Fast ethereum rlp encode, decode and object mapping in java.

Notes

  • Supports RLP primitives of boolean, short, int, long, java.math.BigInteger and String.
  • Supports POJO(Plain Ordinary Java Object) with at least one @RLP annotated 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.ConcurrentMap and their no-abstract implementations.
  • Generic info of fields in POJO class could be nested to arbitrary deepth.
  • Every value in @RLP of a POJO class should be unique and continous.

  • String s = null will be encoded as empty string String s = "".

  • For boolean, true will be encoded as 1 while false will be encoded as 0
  • null values of Boolean, Byte, Short, Integer, Long and BigInteger will be encoded as 0
  • null byte array byte[] bytes = null will be encoded as empty byte array byte[] bytes = new byte[0]
  • transient field will be ignored by default

Notes on java.util.Collection and java.util.Map

  • Maps will be encoded as key-value pairs RLPList [key1, value1, key2, value2, …].
  • java.util.TreeMap is recommended implementation of java.util.Map since key-value pairs in TreeMap are ordered.
  • java.util.TreeSet is recommended implementation of java.util.Set since 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 of java.util.Set could be specified by @RLPEncoding.keyOrdering() when encoding.
  • If the ordering of key-value pairs is absent, the encoding of the java.util.Map may not be predictable, encoding of java.util.Set is similar.

Examples

  • RLP object mapping
  1. package org.tdf.rlp;
  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.Collection;
  5. import java.util.List;
  6. // RLP could encode & decode Tree-like object.
  7. public class Node{
  8. // declared fields will be encoded with ordering of handwriting
  9. public String name;
  10. public List<Node> children;
  11. // field with @RLPIgnored will be ignored when encoding & decoding
  12. @RLPIgnored
  13. public String ignored;
  14. // if some fields are annotated with @RLP,
  15. // fields without @RLP annotation will be ignored
  16. // the fields above is analogy to
  17. /**
  18. * @RLP(0)
  19. * public String name;
  20. * @RLP(1)
  21. * public List<Node> children;
  22. *
  23. * public String ignored;
  24. **/
  25. // a no-argument constructor
  26. public Node() {
  27. }
  28. public Node(String name) {
  29. this.name = name;
  30. }
  31. public void addChildren(Collection<Node> nodes){
  32. if(children == null){
  33. children = new ArrayList<>();
  34. }
  35. if (!nodes.stream().allMatch(x -> x != this)){
  36. throw new RuntimeException("tree like object cannot add self as children");
  37. }
  38. children.addAll(nodes);
  39. }
  40. public static void main(String[] args){
  41. Node root = new Node("1");
  42. root.addChildren(Arrays.asList(new Node("2"), new Node("3")));
  43. Node node2 = root.children.get(0);
  44. node2.addChildren(Arrays.asList(new Node("4"), new Node("5")));
  45. root.children.get(1).addChildren(Arrays.asList(new Node("6"), new Node("7")));
  46. // encode to byte array
  47. byte[] encoded = RLPCodec.encode(root);
  48. // read as rlp tree
  49. RLPElement el = RLPElement.readRLPTree(root);
  50. // decode from byte array
  51. Node root2 = RLPCodec.decode(encoded, Node.class);
  52. el = RLPElement.fromEncoded(encoded);
  53. // decode from rlp element
  54. root2 = el.as(Node.class);
  55. root2 = RLPCodec.decode(el, Node.class);
  56. }
  57. public static void assertTrue(boolean b){
  58. if(!b) throw new RuntimeException("assertion failed");
  59. }
  60. }
  • RLP encode & decode of POJO with tree-like field.
  1. public static class Tree{
  2. @RLPDecoding(as = ConcurrentHashMap.class)
  3. /* The decoded type will be java.util.concurrent.ConcurrentHashMap
  4. instead of java.util.HashMap which is the default implementation of java.util.Map. */
  5. public Map<Map<String, Set<String>>, byte[]> tree;
  6. // although ByteArrayMap in ethereumJ is not supported by default, you can enable it by annotation
  7. @RLPDecoding(as = ByteArrayMap.class)
  8. public Map<byte[], String> stringMap;
  9. }
  1. public class Main{
  2. public static void main(String[] args){
  3. Tree tree = new Tree();
  4. tree.tree = new HashMap<>();
  5. Map<String, Set<String>> map = new HashMap<>();
  6. map.put("1", new HashSet<>(Arrays.asList("1", "2", "3")));
  7. tree.tree.put(map, "1".getBytes());
  8. byte[] encoded = RLPCodec.encode(tree);
  9. RLPElement el = RLPElement.fromEncoded(encoded, false);
  10. Tree tree1 = RLPCodec.decode(encoded, Tree.class);
  11. assert tree1.tree instanceof ConcurrentHashMap;
  12. Map<String, Set<String>> tree2 = tree1.tree.keySet().stream()
  13. .findFirst().get();
  14. assert Arrays.equals(tree1.tree.get(tree2), "1".getBytes());
  15. assert tree2.get("1").containsAll(Arrays.asList("1", "2", "3"));
  16. }
  17. }
  • Tree-like type encode & decode without wrapper class.
  1. public class Main{
  2. // store generic info in a dummy field
  3. private abstract class Dummy2 {
  4. private List<Map<String, String>> dummy;
  5. }
  6. public static void main(String[] args) throws Exception{
  7. List<Map<String, String>> list = new ArrayList<>();
  8. list.add(new HashMap<>());
  9. list.get(0).put("1", "1");
  10. List<Map<String, String>> decoded = (List<Map<String, String>>) RLPCodec.decodeContainer(
  11. RLPCodec.encode(list),
  12. Container.fromField(Dummy2.class.getDeclaredField("dummy"))
  13. );
  14. assert decoded.get(0).get("1").equals("1");
  15. }
  16. }
  1. package org.tdf.rlp;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. public class Main{
  5. public static class MapEncoderDecoder implements RLPEncoder<Map<String, String>>, RLPDecoder<Map<String, String>> {
  6. @Override
  7. public Map<String, String> decode(RLPElement list) {
  8. Map<String, String> map = new HashMap<>(list.size() / 2);
  9. for (int i = 0; i < list.size(); i += 2) {
  10. map.put(list.get(i).asString(), list.get(i+1).asString());
  11. }
  12. return map;
  13. }
  14. @Override
  15. public RLPElement encode(Map<String, String> o) {
  16. RLPList list = RLPList.createEmpty(o.size() * 2);
  17. o.keySet().stream().sorted(String::compareTo).forEach(x -> {
  18. list.add(RLPItem.fromString(x));
  19. list.add(RLPItem.fromString(o.get(x)));
  20. });
  21. return list;
  22. }
  23. }
  24. public static class MapWrapper{
  25. @RLP
  26. @RLPEncoding(MapEncoderDecoder.class)
  27. @RLPDecoding(MapEncoderDecoder.class)
  28. public Map<String, String> map;
  29. public MapWrapper(Map<String, String> map) {
  30. this.map = map;
  31. }
  32. public MapWrapper() {
  33. }
  34. }
  35. public static void main(String[] args){
  36. Map<String, String> m = new HashMap<>();
  37. m.put("a", "1");
  38. m.put("b", "2");
  39. byte[] encoded = RLPCodec.encode(new MapWrapper(m));
  40. MapWrapper decoded = RLPCodec.decode(encoded, MapWrapper.class);
  41. assertTrue(decoded.map.get("a").equals("1"));
  42. }
  43. public static void assertTrue(boolean b){
  44. if(!b) throw new RuntimeException("assertion failed");
  45. }
  46. }
  1. public class Main{
  2. public static class LocalDateDecoder implements RLPDecoder<LocalDate> {
  3. @Override
  4. public LocalDate decode(RLPElement rlpElement) {
  5. if(rlpElement.isNull()) return null;
  6. int[] data = rlpElement.as(int[].class);
  7. return LocalDate.of(data[0], data[1], data[2]);
  8. }
  9. }
  10. public class LocalDateEncoder implements RLPEncoder<LocalDate> {
  11. @Override
  12. public RLPElement encode(LocalDate localDate) {
  13. return RLPElement.readRLPTree(
  14. new int[]{localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth()}
  15. );
  16. }
  17. }
  18. @Data
  19. @AllArgsConstructor
  20. @NoArgsConstructor
  21. public static class User{
  22. private LocalDate birthDay;
  23. }
  24. public static void main(String[] args){
  25. RLPContext context = RLPContext
  26. .newInstance()
  27. .withDecoder(LocalDate.class, new LocalDateDecoder())
  28. .withEncoder(LocalDate.class, new LocalDateEncoder());
  29. RLPMapper mapper = new RLPMapper().withContext(context);
  30. User u = new User();
  31. element = mapper.readRLPTree(u);
  32. User decoded = mapper.decode(element, User.class);
  33. assert decoded.birthDay == null;
  34. u.birthDay = LocalDate.now();
  35. byte[] encoded = mapper.encode(u);
  36. decoded = mapper.decode(encoded, User.class);
  37. System.out.println(decoded.birthDay.format(DateTimeFormatter.ISO_DATE));
  38. }
  39. }
  • Encode & Decode containers
  1. public class Main {
  2. public static interface SomeMap extends Map<String, String>{
  3. }
  4. public static final Container CONTAINER = Container.fromType(SomeMap.class.getGenericInterfaces()[0]);
  5. public static void main(String[] args){
  6. byte[] bytes = new byte[0];
  7. Map<String, String> m = (Map<String, String>) RLPCodec.decodeContainer(bytes, CONTAINER);
  8. }
  9. }

Benchmark

  • 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