You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tuweni.apache.org by to...@apache.org on 2020/02/07 04:33:05 UTC

[incubator-tuweni] branch master updated: Ethstats (#44)

This is an automated email from the ASF dual-hosted git repository.

toulmean pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-tuweni.git


The following commit(s) were added to refs/heads/master by this push:
     new db62865  Ethstats (#44)
db62865 is described below

commit db6286567367484067647e8a4083053b33397334
Author: Antoine Toulme <at...@users.noreply.github.com>
AuthorDate: Thu Feb 6 20:32:57 2020 -0800

    Ethstats (#44)
    
    * Start work on ethstats client module
    
    * add build
    
    * wip
    
    * Functional ethstats interface
    
    * use getter syntax for eth object fields
    
    * Fix JSON test
---
 .../tuweni/eth/reference/BlockRLPTestSuite.java    |   4 +-
 .../tuweni/eth/reference/TransactionTestSuite.java |  10 +-
 .../tuweni/eth/repository/BlockchainIndex.kt       |  40 +--
 .../tuweni/eth/repository/BlockchainRepository.kt  |  14 +-
 .../tuweni/eth/repository/BlockchainIndexTest.kt   |  71 ++---
 .../eth/repository/BlockchainRepositoryTest.kt     |  58 ++--
 eth/build.gradle                                   |   1 +
 eth/src/main/java/org/apache/tuweni/eth/Block.java |   4 +-
 .../main/java/org/apache/tuweni/eth/BlockBody.java |   4 +-
 .../java/org/apache/tuweni/eth/BlockHeader.java    |  49 ++--
 .../java/org/apache/tuweni/eth/EthJsonModule.java  | 109 ++++++++
 eth/src/main/java/org/apache/tuweni/eth/Log.java   |   6 +-
 .../org/apache/tuweni/eth/LogsBloomFilter.java     |   4 +-
 .../java/org/apache/tuweni/eth/Transaction.java    |  20 +-
 .../org/apache/tuweni/eth/TransactionReceipt.java  |  10 +-
 .../org/apache/tuweni/eth/EthJsonModuleTest.java}  |  26 +-
 .../org/apache/tuweni/eth/TransactionTest.java     |   4 +-
 {eth => ethstats}/build.gradle                     |  17 +-
 .../org/apache/tuweni/ethstats/AuthMessage.java    |  38 ++-
 .../org/apache/tuweni/ethstats/BlockStats.java     | 135 ++++++++++
 .../apache/tuweni/ethstats/EthStatsReporter.java   | 297 +++++++++++++++++++++
 .../java/org/apache/tuweni/ethstats/NodeInfo.java  |  91 +++++++
 .../java/org/apache/tuweni/ethstats/NodeStats.java |  78 ++++++
 .../java/org/apache/tuweni/ethstats/TxStats.java   |  26 +-
 .../tuweni/ethstats/EthStatsReporterTest.java      | 129 +++++++++
 .../org/apache/tuweni/les/LESSubProtocolHandler.kt |  10 +-
 .../apache/tuweni/les/LESSubProtocolHandlerTest.kt |   4 +-
 settings.gradle                                    |   1 +
 28 files changed, 1074 insertions(+), 186 deletions(-)

diff --git a/eth-reference-tests/src/test/java/org/apache/tuweni/eth/reference/BlockRLPTestSuite.java b/eth-reference-tests/src/test/java/org/apache/tuweni/eth/reference/BlockRLPTestSuite.java
index 5dad3d0..44748af 100644
--- a/eth-reference-tests/src/test/java/org/apache/tuweni/eth/reference/BlockRLPTestSuite.java
+++ b/eth-reference-tests/src/test/java/org/apache/tuweni/eth/reference/BlockRLPTestSuite.java
@@ -58,8 +58,8 @@ class BlockRLPTestSuite {
     Block rlpBlock = Block.fromHexString(rlp);
     assertEquals(block, rlpBlock);
     assertEquals(Bytes.fromHexString(rlp), block.toBytes());
-    assertEquals(Hash.fromHexString(hash), block.header().hash());
-    assertEquals(Hash.fromHexString(hash), rlpBlock.header().hash());
+    assertEquals(Hash.fromHexString(hash), block.getHeader().getHash());
+    assertEquals(Hash.fromHexString(hash), rlpBlock.getHeader().getHash());
   }
 
   @MustBeClosed
diff --git a/eth-reference-tests/src/test/java/org/apache/tuweni/eth/reference/TransactionTestSuite.java b/eth-reference-tests/src/test/java/org/apache/tuweni/eth/reference/TransactionTestSuite.java
index 6f68edc..f013437 100644
--- a/eth-reference-tests/src/test/java/org/apache/tuweni/eth/reference/TransactionTestSuite.java
+++ b/eth-reference-tests/src/test/java/org/apache/tuweni/eth/reference/TransactionTestSuite.java
@@ -58,9 +58,9 @@ class TransactionTestSuite {
   private void testValidTransaction(String rlp, String hash, String sender) {
     Bytes rlpBytes = Bytes.fromHexString(rlp);
     Transaction tx = Transaction.fromBytes(rlpBytes);
-    assertEquals(Address.fromBytes(Bytes.fromHexString(sender)), tx.sender());
+    assertEquals(Address.fromBytes(Bytes.fromHexString(sender)), tx.getSender());
     assertEquals(rlpBytes, tx.toBytes());
-    assertEquals(Bytes.fromHexString(hash), tx.hash().toBytes());
+    assertEquals(Bytes.fromHexString(hash), tx.getHash().toBytes());
   }
 
   private void testInvalidTransaction(String rlp, String milestone) {
@@ -78,16 +78,16 @@ class TransactionTestSuite {
       return;
     }
 
-    if (tx.sender() == null) {
+    if (tx.getSender() == null) {
       return;
     }
 
     if ("Constantinople".equals(milestone) || "Byzantium".equals(milestone) || "EIP158".equals(milestone)) {
-      if (tx.chainId() == null) {
+      if (tx.getChainId() == null) {
         return;
       }
     } else {
-      if (tx.chainId() != null) {
+      if (tx.getChainId() != null) {
         return;
       }
     }
diff --git a/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainIndex.kt b/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainIndex.kt
index 93067d6..aaa1a87 100644
--- a/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainIndex.kt
+++ b/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainIndex.kt
@@ -322,10 +322,10 @@ class BlockchainIndex(private val indexWriter: IndexWriter) : BlockchainIndexWri
 
   override fun indexBlockHeader(blockHeader: BlockHeader) {
     val document = mutableListOf<IndexableField>()
-    val id = toBytesRef(blockHeader.hash())
+    val id = toBytesRef(blockHeader.getHash())
     document.add(StringField("_id", id, Field.Store.YES))
     document.add(StringField("_type", "block", Field.Store.NO))
-    blockHeader.parentHash()?.let { hash ->
+    blockHeader.getParentHash()?.let { hash ->
       val hashRef = toBytesRef(hash)
       document += StringField(
         PARENT_HASH.fieldName,
@@ -334,26 +334,26 @@ class BlockchainIndex(private val indexWriter: IndexWriter) : BlockchainIndexWri
       )
       queryBlockDocs(TermQuery(Term("_id", hashRef)), listOf(TOTAL_DIFFICULTY)).firstOrNull()?.let {
         it.getField(TOTAL_DIFFICULTY.fieldName)?.let {
-          val totalDifficulty = blockHeader.difficulty().add(UInt256.fromBytes(Bytes.wrap(it.binaryValue().bytes)))
+          val totalDifficulty = blockHeader.getDifficulty().add(UInt256.fromBytes(Bytes.wrap(it.binaryValue().bytes)))
           val diffBytes = toBytesRef(totalDifficulty.toBytes())
           document += StringField(TOTAL_DIFFICULTY.fieldName, diffBytes, Field.Store.YES)
           document += SortedDocValuesField(TOTAL_DIFFICULTY.fieldName, diffBytes)
         }
       }
     } ?: run {
-      val diffBytes = toBytesRef(blockHeader.difficulty().toBytes())
+      val diffBytes = toBytesRef(blockHeader.getDifficulty().toBytes())
       document += StringField(TOTAL_DIFFICULTY.fieldName, diffBytes, Field.Store.YES)
       document += SortedDocValuesField(TOTAL_DIFFICULTY.fieldName, diffBytes)
     }
-    document += StringField(OMMERS_HASH.fieldName, toBytesRef(blockHeader.ommersHash()), Field.Store.NO)
-    document += StringField(COINBASE.fieldName, toBytesRef(blockHeader.coinbase()), Field.Store.NO)
-    document += StringField(STATE_ROOT.fieldName, toBytesRef(blockHeader.stateRoot()), Field.Store.NO)
-    document += StringField(DIFFICULTY.fieldName, toBytesRef(blockHeader.difficulty()), Field.Store.NO)
-    document += StringField(NUMBER.fieldName, toBytesRef(blockHeader.number()), Field.Store.NO)
-    document += StringField(GAS_LIMIT.fieldName, toBytesRef(blockHeader.gasLimit()), Field.Store.NO)
-    document += StringField(GAS_USED.fieldName, toBytesRef(blockHeader.gasUsed()), Field.Store.NO)
-    document += StringField(EXTRA_DATA.fieldName, toBytesRef(blockHeader.extraData()), Field.Store.NO)
-    document += NumericDocValuesField(TIMESTAMP.fieldName, blockHeader.timestamp().toEpochMilli())
+    document += StringField(OMMERS_HASH.fieldName, toBytesRef(blockHeader.getOmmersHash()), Field.Store.NO)
+    document += StringField(COINBASE.fieldName, toBytesRef(blockHeader.getCoinbase()), Field.Store.NO)
+    document += StringField(STATE_ROOT.fieldName, toBytesRef(blockHeader.getStateRoot()), Field.Store.NO)
+    document += StringField(DIFFICULTY.fieldName, toBytesRef(blockHeader.getDifficulty()), Field.Store.NO)
+    document += StringField(NUMBER.fieldName, toBytesRef(blockHeader.getNumber()), Field.Store.NO)
+    document += StringField(GAS_LIMIT.fieldName, toBytesRef(blockHeader.getGasLimit()), Field.Store.NO)
+    document += StringField(GAS_USED.fieldName, toBytesRef(blockHeader.getGasUsed()), Field.Store.NO)
+    document += StringField(EXTRA_DATA.fieldName, toBytesRef(blockHeader.getExtraData()), Field.Store.NO)
+    document += NumericDocValuesField(TIMESTAMP.fieldName, blockHeader.getTimestamp().toEpochMilli())
 
     try {
       indexWriter.updateDocument(Term("_id", id), document)
@@ -372,20 +372,20 @@ class BlockchainIndex(private val indexWriter: IndexWriter) : BlockchainIndexWri
     document += StringField(TransactionReceiptFields.TRANSACTION_HASH.fieldName, id, Field.Store.NO)
     document += StringField(TransactionReceiptFields.BLOCK_HASH.fieldName, toBytesRef(blockHash.toBytes()),
       Field.Store.NO)
-    for (log in txReceipt.logs()) {
-      document += StringField(TransactionReceiptFields.LOGGER.fieldName, toBytesRef(log.logger()), Field.Store.NO)
-      for (logTopic in log.topics()) {
+    for (log in txReceipt.getLogs()) {
+      document += StringField(TransactionReceiptFields.LOGGER.fieldName, toBytesRef(log.getLogger()), Field.Store.NO)
+      for (logTopic in log.getTopics()) {
         document += StringField(TransactionReceiptFields.LOG_TOPIC.fieldName, toBytesRef(logTopic), Field.Store.NO)
       }
     }
-    txReceipt.stateRoot()?.let {
+    txReceipt.getStateRoot()?.let {
       document += StringField(TransactionReceiptFields.STATE_ROOT.fieldName, toBytesRef(it), Field.Store.NO)
     }
     document += StringField(TransactionReceiptFields.BLOOM_FILTER.fieldName,
-      toBytesRef(txReceipt.bloomFilter().toBytes()), Field.Store.NO)
+      toBytesRef(txReceipt.getBloomFilter().toBytes()), Field.Store.NO)
     document += NumericDocValuesField(TransactionReceiptFields.CUMULATIVE_GAS_USED.fieldName,
-      txReceipt.cumulativeGasUsed())
-    txReceipt.status()?.let {
+      txReceipt.getCumulativeGasUsed())
+    txReceipt.getStatus()?.let {
       document += NumericDocValuesField(TransactionReceiptFields.STATUS.fieldName, it.toLong())
     }
 
diff --git a/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainRepository.kt b/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainRepository.kt
index 1012256..b7c478e 100644
--- a/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainRepository.kt
+++ b/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainRepository.kt
@@ -93,9 +93,9 @@ class BlockchainRepository
    * @return a handle to the storage operation completion
    */
   suspend fun storeBlock(block: Block) {
-    storeBlockBody(block.header().hash(), block.body())
-    blockHeaderStore.put(block.header().hash().toBytes(), block.header().toBytes())
-    indexBlockHeader(block.header())
+    storeBlockBody(block.getHeader().getHash(), block.getBody())
+    blockHeaderStore.put(block.getHeader().getHash().toBytes(), block.getHeader().toBytes())
+    indexBlockHeader(block.getHeader())
   }
 
   /**
@@ -138,13 +138,13 @@ class BlockchainRepository
    * @return handle to the storage operation completion
    */
   suspend fun storeBlockHeader(header: BlockHeader) {
-    blockHeaderStore.put(header.hash().toBytes(), header.toBytes())
+    blockHeaderStore.put(header.getHash().toBytes(), header.toBytes())
     indexBlockHeader(header)
   }
 
   private suspend fun indexBlockHeader(header: BlockHeader) {
     blockchainIndex.index { writer -> writer.indexBlockHeader(header) }
-    for (hash in findBlocksByParentHash(header.hash())) {
+    for (hash in findBlocksByParentHash(header.getHash())) {
       blockHeaderStore.get(hash.toBytes())?.let { bytes ->
         indexBlockHeader(BlockHeader.fromBytes(bytes))
       }
@@ -282,7 +282,7 @@ class BlockchainRepository
    */
   suspend fun retrieveChainHeadHeader(): BlockHeader? {
     return blockchainIndex.findByLargest(BlockHeaderFields.TOTAL_DIFFICULTY)
-      ?.let { retrieveBlockHeader(it) } ?: retrieveGenesisBlock()?.header()
+      ?.let { retrieveBlockHeader(it) } ?: retrieveGenesisBlock()?.getHeader()
   }
 
   /**
@@ -347,6 +347,6 @@ class BlockchainRepository
 
   private suspend fun setGenesisBlock(block: Block) {
     return chainMetadata
-      .put(GENESIS_BLOCK, block.header().hash().toBytes())
+      .put(GENESIS_BLOCK, block.getHeader().getHash().toBytes())
   }
 }
diff --git a/eth-repository/src/test/kotlin/org/apache/tuweni/eth/repository/BlockchainIndexTest.kt b/eth-repository/src/test/kotlin/org/apache/tuweni/eth/repository/BlockchainIndexTest.kt
index e2a1dc3..2c245a6 100644
--- a/eth-repository/src/test/kotlin/org/apache/tuweni/eth/repository/BlockchainIndexTest.kt
+++ b/eth-repository/src/test/kotlin/org/apache/tuweni/eth/repository/BlockchainIndexTest.kt
@@ -76,7 +76,7 @@ internal class BlockchainIndexTest {
     val reader = DirectoryReader.open(writer)
     val searcher = IndexSearcher(reader)
     val collector = TopScoreDocCollector.create(10, ScoreDoc(1, 1.0f))
-    searcher.search(TermQuery(Term("_id", BytesRef(header.hash().toBytes().toArrayUnsafe()))), collector)
+    searcher.search(TermQuery(Term("_id", BytesRef(header.hash.toBytes().toArrayUnsafe()))), collector)
     val hits = collector.topDocs().scoreDocs
     assertEquals(1, hits.size)
   }
@@ -108,7 +108,7 @@ internal class BlockchainIndexTest {
     val reader = DirectoryReader.open(index)
     val searcher = IndexSearcher(reader)
     val collector = TopScoreDocCollector.create(10, ScoreDoc(1, 1.0f))
-    searcher.search(TermQuery(Term("_id", BytesRef(header.hash().toBytes().toArrayUnsafe()))), collector)
+    searcher.search(TermQuery(Term("_id", BytesRef(header.hash.toBytes().toArrayUnsafe()))), collector)
     val hits = collector.topDocs().scoreDocs
     assertEquals(1, hits.size)
   }
@@ -139,75 +139,76 @@ internal class BlockchainIndexTest {
     val reader = blockchainIndex as BlockchainIndexReader
 
     run {
-      val entries = reader.findBy(BlockHeaderFields.PARENT_HASH, header.parentHash()!!)
+      val entries = reader.findBy(BlockHeaderFields.PARENT_HASH, header.parentHash!!)
       assertEquals(1, entries.size)
-      assertEquals(header.hash(), entries[0])
+      assertEquals(header.hash, entries[0])
     }
 
     run {
-      val entries = reader.findBy(BlockHeaderFields.OMMERS_HASH, header.ommersHash())
+      val entries = reader.findBy(BlockHeaderFields.OMMERS_HASH, header.ommersHash)
       assertEquals(1, entries.size)
-      assertEquals(header.hash(), entries[0])
+      assertEquals(header.hash, entries[0])
     }
 
     run {
-      val entries = reader.findBy(BlockHeaderFields.COINBASE, header.coinbase())
+      val entries = reader.findBy(BlockHeaderFields.COINBASE, header.coinbase)
       assertEquals(1, entries.size)
-      assertEquals(header.hash(), entries[0])
+      assertEquals(header.hash, entries[0])
     }
 
     run {
-      val entries = reader.findBy(BlockHeaderFields.STATE_ROOT, header.stateRoot())
+      val entries = reader.findBy(BlockHeaderFields.STATE_ROOT, header.stateRoot)
       assertEquals(1, entries.size)
-      assertEquals(header.hash(), entries[0])
+      assertEquals(header.hash, entries[0])
     }
 
     run {
-      val entries = reader.findBy(BlockHeaderFields.STATE_ROOT, header.stateRoot())
+      val entries = reader.findBy(BlockHeaderFields.STATE_ROOT, header.stateRoot)
       assertEquals(1, entries.size)
-      assertEquals(header.hash(), entries[0])
+      assertEquals(header.hash, entries[0])
     }
 
     run {
-      val entries = reader.findBy(BlockHeaderFields.DIFFICULTY, header.difficulty())
+      val entries = reader.findBy(BlockHeaderFields.DIFFICULTY, header.difficulty)
       assertEquals(1, entries.size)
-      assertEquals(header.hash(), entries[0])
+      assertEquals(header.hash, entries[0])
     }
 
     run {
-      val entries = reader.findBy(BlockHeaderFields.TIMESTAMP, header.timestamp().toEpochMilli())
+      val entries = reader.findBy(BlockHeaderFields.TIMESTAMP, header.timestamp.toEpochMilli())
       assertEquals(1, entries.size)
-      assertEquals(header.hash(), entries[0])
+      assertEquals(header.hash, entries[0])
     }
 
     run {
-      val entries = reader.findBy(BlockHeaderFields.NUMBER, header.number())
+      val entries = reader.findBy(BlockHeaderFields.NUMBER, header.number)
       assertEquals(1, entries.size)
-      assertEquals(header.hash(), entries[0])
+      assertEquals(header.hash, entries[0])
     }
 
     run {
-      val entries = reader.findInRange(BlockHeaderFields.NUMBER, header.number().subtract(5), header.number().add(5))
+      val entries = reader.findInRange(BlockHeaderFields.NUMBER, header.number.subtract(5),
+        header.number.add(5))
       assertEquals(1, entries.size)
-      assertEquals(header.hash(), entries[0])
+      assertEquals(header.hash, entries[0])
     }
 
     run {
-      val entries = reader.findBy(BlockHeaderFields.EXTRA_DATA, header.extraData())
+      val entries = reader.findBy(BlockHeaderFields.EXTRA_DATA, header.extraData)
       assertEquals(1, entries.size)
-      assertEquals(header.hash(), entries[0])
+      assertEquals(header.hash, entries[0])
     }
 
     run {
-      val entries = reader.findBy(BlockHeaderFields.GAS_LIMIT, header.gasLimit())
+      val entries = reader.findBy(BlockHeaderFields.GAS_LIMIT, header.gasLimit)
       assertEquals(1, entries.size, entries.toString())
-      assertEquals(header.hash(), entries[0])
+      assertEquals(header.hash, entries[0])
     }
 
     run {
-      val entries = reader.findBy(BlockHeaderFields.GAS_USED, header.gasUsed())
+      val entries = reader.findBy(BlockHeaderFields.GAS_USED, header.gasUsed)
       assertEquals(1, entries.size)
-      assertEquals(header.hash(), entries[0])
+      assertEquals(header.hash, entries[0])
     }
   }
 
@@ -232,10 +233,10 @@ internal class BlockchainIndexTest {
       Bytes32.random()
     )
     blockchainIndex.index { w -> w.indexBlockHeader(header) }
-    assertEquals(UInt256.valueOf(1), blockchainIndex.totalDifficulty(header.hash()))
+    assertEquals(UInt256.valueOf(1), blockchainIndex.totalDifficulty(header.hash))
 
     val childHeader = BlockHeader(
-      header.hash(),
+      header.hash,
       Hash.fromBytes(Bytes32.random()),
       Address.fromBytes(Bytes.random(20)),
       Hash.fromBytes(Bytes32.random()),
@@ -254,7 +255,7 @@ internal class BlockchainIndexTest {
 
     blockchainIndex.index { w -> w.indexBlockHeader(childHeader) }
 
-    assertEquals(UInt256.valueOf(4), blockchainIndex.totalDifficulty(childHeader.hash()))
+    assertEquals(UInt256.valueOf(4), blockchainIndex.totalDifficulty(childHeader.hash))
   }
 
   @Test
@@ -308,37 +309,37 @@ internal class BlockchainIndexTest {
     }
 
     run {
-      val entries = reader.findBy(TransactionReceiptFields.BLOOM_FILTER, txReceipt.bloomFilter().toBytes())
+      val entries = reader.findBy(TransactionReceiptFields.BLOOM_FILTER, txReceipt.bloomFilter.toBytes())
       assertEquals(1, entries.size)
       assertEquals(txHash, entries[0])
     }
 
     run {
-      val entries = reader.findBy(TransactionReceiptFields.STATE_ROOT, txReceipt.stateRoot())
+      val entries = reader.findBy(TransactionReceiptFields.STATE_ROOT, txReceipt.stateRoot)
       assertEquals(1, entries.size)
       assertEquals(txHash, entries[0])
     }
 
     run {
-      val entries = reader.findBy(TransactionReceiptFields.LOGGER, txReceipt.logs()[0].logger())
+      val entries = reader.findBy(TransactionReceiptFields.LOGGER, txReceipt.logs[0].logger)
       assertEquals(1, entries.size)
       assertEquals(txHash, entries[0])
     }
 
     run {
-      val entries = reader.findBy(TransactionReceiptFields.LOG_TOPIC, txReceipt.logs()[0].topics()[0])
+      val entries = reader.findBy(TransactionReceiptFields.LOG_TOPIC, txReceipt.logs[0].topics[0])
       assertEquals(1, entries.size)
       assertEquals(txHash, entries[0])
     }
 
     run {
-      val entries = reader.findBy(TransactionReceiptFields.STATUS, txReceiptWithStatus.status())
+      val entries = reader.findBy(TransactionReceiptFields.STATUS, txReceiptWithStatus.status)
       assertEquals(1, entries.size)
       assertEquals(txHash2, entries[0])
     }
 
     run {
-      val entries = reader.findBy(TransactionReceiptFields.CUMULATIVE_GAS_USED, txReceipt.cumulativeGasUsed())
+      val entries = reader.findBy(TransactionReceiptFields.CUMULATIVE_GAS_USED, txReceipt.cumulativeGasUsed)
       assertEquals(1, entries.size)
       assertEquals(txHash, entries[0])
     }
diff --git a/eth-repository/src/test/kotlin/org/apache/tuweni/eth/repository/BlockchainRepositoryTest.kt b/eth-repository/src/test/kotlin/org/apache/tuweni/eth/repository/BlockchainRepositoryTest.kt
index 1b0caf3..08644eb 100644
--- a/eth-repository/src/test/kotlin/org/apache/tuweni/eth/repository/BlockchainRepositoryTest.kt
+++ b/eth-repository/src/test/kotlin/org/apache/tuweni/eth/repository/BlockchainRepositoryTest.kt
@@ -109,9 +109,9 @@ internal class BlockchainRepositoryTest {
     )
     val block = Block(header, body)
     repo.storeBlock(block)
-    val read = repo.retrieveBlock(block.header().hash().toBytes())
+    val read = repo.retrieveBlock(block.getHeader().getHash().toBytes())
     assertEquals(block, read)
-    assertEquals(block.header(), repo.retrieveBlockHeader(block.header().hash()))
+    assertEquals(block.getHeader(), repo.retrieveBlockHeader(block.getHeader().getHash()))
   }
 
   @Test
@@ -146,7 +146,7 @@ internal class BlockchainRepositoryTest {
       )
 
     val header = BlockHeader(
-      genesisHeader.hash(),
+      genesisHeader.getHash(),
       Hash.fromBytes(Bytes32.random()),
       Address.fromBytes(Bytes.random(20)),
       Hash.fromBytes(Bytes32.random()),
@@ -154,7 +154,7 @@ internal class BlockchainRepositoryTest {
       Hash.fromBytes(Bytes32.random()),
       Bytes32.random(),
       UInt256.fromBytes(Bytes32.random()),
-      genesisHeader.number().add(UInt256.valueOf(1)),
+      genesisHeader.getNumber().add(UInt256.valueOf(1)),
       Gas.valueOf(3),
       Gas.valueOf(2),
       Instant.now().truncatedTo(ChronoUnit.SECONDS),
@@ -163,7 +163,7 @@ internal class BlockchainRepositoryTest {
       Bytes32.random()
     )
     val biggerNumber = BlockHeader(
-      header.hash(),
+      header.getHash(),
       Hash.fromBytes(Bytes32.random()),
       Address.fromBytes(Bytes.random(20)),
       Hash.fromBytes(Bytes32.random()),
@@ -171,7 +171,7 @@ internal class BlockchainRepositoryTest {
       Hash.fromBytes(Bytes32.random()),
       Bytes32.random(),
       UInt256.fromBytes(Bytes32.random()),
-      header.number().add(UInt256.valueOf(1)),
+      header.getNumber().add(UInt256.valueOf(1)),
       Gas.valueOf(3),
       Gas.valueOf(2),
       Instant.now().truncatedTo(ChronoUnit.SECONDS),
@@ -180,7 +180,7 @@ internal class BlockchainRepositoryTest {
       Bytes32.random()
     )
     val biggerNumber2 = BlockHeader(
-      biggerNumber.hash(),
+      biggerNumber.getHash(),
       Hash.fromBytes(Bytes32.random()),
       Address.fromBytes(Bytes.random(20)),
       Hash.fromBytes(Bytes32.random()),
@@ -188,7 +188,7 @@ internal class BlockchainRepositoryTest {
       Hash.fromBytes(Bytes32.random()),
       Bytes32.random(),
       UInt256.fromBytes(Bytes32.random()),
-      header.number().add(UInt256.valueOf(2)),
+      header.getNumber().add(UInt256.valueOf(2)),
       Gas.valueOf(3),
       Gas.valueOf(2),
       Instant.now().truncatedTo(ChronoUnit.SECONDS),
@@ -197,7 +197,7 @@ internal class BlockchainRepositoryTest {
       Bytes32.random()
     )
     val biggerNumber3 = BlockHeader(
-      biggerNumber2.hash(),
+      biggerNumber2.getHash(),
       Hash.fromBytes(Bytes32.random()),
       Address.fromBytes(Bytes.random(20)),
       Hash.fromBytes(Bytes32.random()),
@@ -205,7 +205,7 @@ internal class BlockchainRepositoryTest {
       Hash.fromBytes(Bytes32.random()),
       Bytes32.random(),
       UInt256.fromBytes(Bytes32.random()),
-      header.number().add(UInt256.valueOf(3)),
+      header.getNumber().add(UInt256.valueOf(3)),
       Gas.valueOf(3),
       Gas.valueOf(2),
       Instant.now().truncatedTo(ChronoUnit.SECONDS),
@@ -219,7 +219,7 @@ internal class BlockchainRepositoryTest {
     repo.storeBlockHeader(biggerNumber2)
     repo.storeBlockHeader(biggerNumber3)
 
-    assertEquals(biggerNumber3.hash(), repo.retrieveChainHeadHeader()!!.hash())
+    assertEquals(biggerNumber3.getHash(), repo.retrieveChainHeadHeader()!!.getHash())
   }
 
   @Test
@@ -253,7 +253,7 @@ internal class BlockchainRepositoryTest {
       )
 
     val header = BlockHeader(
-      genesisHeader.hash(),
+      genesisHeader.getHash(),
       Hash.fromBytes(Bytes32.random()),
       Address.fromBytes(Bytes.random(20)),
       Hash.fromBytes(Bytes32.random()),
@@ -261,7 +261,7 @@ internal class BlockchainRepositoryTest {
       Hash.fromBytes(Bytes32.random()),
       Bytes32.random(),
       UInt256.valueOf(1),
-      genesisHeader.number().add(UInt256.valueOf(1)),
+      genesisHeader.getNumber().add(UInt256.valueOf(1)),
       Gas.valueOf(3),
       Gas.valueOf(2),
       Instant.now().truncatedTo(ChronoUnit.SECONDS),
@@ -270,7 +270,7 @@ internal class BlockchainRepositoryTest {
       Bytes32.random()
     )
     val biggerNumber = BlockHeader(
-      header.hash(),
+      header.getHash(),
       Hash.fromBytes(Bytes32.random()),
       Address.fromBytes(Bytes.random(20)),
       Hash.fromBytes(Bytes32.random()),
@@ -278,7 +278,7 @@ internal class BlockchainRepositoryTest {
       Hash.fromBytes(Bytes32.random()),
       Bytes32.random(),
       UInt256.valueOf(2),
-      header.number().add(UInt256.valueOf(1)),
+      header.getNumber().add(UInt256.valueOf(1)),
       Gas.valueOf(3),
       Gas.valueOf(2),
       Instant.now().truncatedTo(ChronoUnit.SECONDS),
@@ -287,7 +287,7 @@ internal class BlockchainRepositoryTest {
       Bytes32.random()
     )
     val biggerNumber2 = BlockHeader(
-      biggerNumber.hash(),
+      biggerNumber.getHash(),
       Hash.fromBytes(Bytes32.random()),
       Address.fromBytes(Bytes.random(20)),
       Hash.fromBytes(Bytes32.random()),
@@ -295,7 +295,7 @@ internal class BlockchainRepositoryTest {
       Hash.fromBytes(Bytes32.random()),
       Bytes32.random(),
       UInt256.valueOf(3),
-      header.number().add(UInt256.valueOf(2)),
+      header.getNumber().add(UInt256.valueOf(2)),
       Gas.valueOf(3),
       Gas.valueOf(2),
       Instant.now().truncatedTo(ChronoUnit.SECONDS),
@@ -304,7 +304,7 @@ internal class BlockchainRepositoryTest {
       Bytes32.random()
     )
     val biggerNumber3 = BlockHeader(
-      biggerNumber2.hash(),
+      biggerNumber2.getHash(),
       Hash.fromBytes(Bytes32.random()),
       Address.fromBytes(Bytes.random(20)),
       Hash.fromBytes(Bytes32.random()),
@@ -312,7 +312,7 @@ internal class BlockchainRepositoryTest {
       Hash.fromBytes(Bytes32.random()),
       Bytes32.random(),
       UInt256.valueOf(4),
-      header.number().add(UInt256.valueOf(3)),
+      header.getNumber().add(UInt256.valueOf(3)),
       Gas.valueOf(3),
       Gas.valueOf(2),
       Instant.now().truncatedTo(ChronoUnit.SECONDS),
@@ -326,7 +326,7 @@ internal class BlockchainRepositoryTest {
     repo.storeBlock(Block(biggerNumber2, BlockBody(emptyList(), emptyList())))
     repo.storeBlock(Block(biggerNumber3, BlockBody(emptyList(), emptyList())))
 
-    assertEquals(biggerNumber3.hash(), repo.retrieveChainHeadHeader()!!.hash())
+    assertEquals(biggerNumber3.getHash(), repo.retrieveChainHeadHeader()!!.getHash())
   }
 
   @Test
@@ -359,7 +359,7 @@ internal class BlockchainRepositoryTest {
     )
 
     val header = BlockHeader(
-      genesisHeader.hash(),
+      genesisHeader.getHash(),
       Hash.fromBytes(Bytes32.random()),
       Address.fromBytes(Bytes.random(20)),
       Hash.fromBytes(Bytes32.random()),
@@ -367,7 +367,7 @@ internal class BlockchainRepositoryTest {
       Hash.fromBytes(Bytes32.random()),
       Bytes32.random(),
       UInt256.valueOf(1),
-      genesisHeader.number().add(UInt256.valueOf(1)),
+      genesisHeader.getNumber().add(UInt256.valueOf(1)),
       Gas.valueOf(3),
       Gas.valueOf(2),
       Instant.now().truncatedTo(ChronoUnit.SECONDS),
@@ -376,7 +376,7 @@ internal class BlockchainRepositoryTest {
       Bytes32.random()
     )
     val biggerNumber = BlockHeader(
-      header.hash(),
+      header.getHash(),
       Hash.fromBytes(Bytes32.random()),
       Address.fromBytes(Bytes.random(20)),
       Hash.fromBytes(Bytes32.random()),
@@ -384,7 +384,7 @@ internal class BlockchainRepositoryTest {
       Hash.fromBytes(Bytes32.random()),
       Bytes32.random(),
       UInt256.valueOf(2),
-      header.number().add(UInt256.valueOf(1)),
+      header.getNumber().add(UInt256.valueOf(1)),
       Gas.valueOf(3),
       Gas.valueOf(2),
       Instant.now().truncatedTo(ChronoUnit.SECONDS),
@@ -393,7 +393,7 @@ internal class BlockchainRepositoryTest {
       Bytes32.random()
     )
     val biggerNumber2 = BlockHeader(
-      biggerNumber.hash(),
+      biggerNumber.getHash(),
       Hash.fromBytes(Bytes32.random()),
       Address.fromBytes(Bytes.random(20)),
       Hash.fromBytes(Bytes32.random()),
@@ -401,7 +401,7 @@ internal class BlockchainRepositoryTest {
       Hash.fromBytes(Bytes32.random()),
       Bytes32.random(),
       UInt256.valueOf(3),
-      header.number().add(UInt256.valueOf(2)),
+      header.getNumber().add(UInt256.valueOf(2)),
       Gas.valueOf(3),
       Gas.valueOf(2),
       Instant.now().truncatedTo(ChronoUnit.SECONDS),
@@ -410,7 +410,7 @@ internal class BlockchainRepositoryTest {
       Bytes32.random()
     )
     val biggerNumber3 = BlockHeader(
-      biggerNumber2.hash(),
+      biggerNumber2.getHash(),
       Hash.fromBytes(Bytes32.random()),
       Address.fromBytes(Bytes.random(20)),
       Hash.fromBytes(Bytes32.random()),
@@ -418,7 +418,7 @@ internal class BlockchainRepositoryTest {
       Hash.fromBytes(Bytes32.random()),
       Bytes32.random(),
       UInt256.valueOf(4),
-      header.number().add(UInt256.valueOf(3)),
+      header.getNumber().add(UInt256.valueOf(3)),
       Gas.valueOf(3),
       Gas.valueOf(2),
       Instant.now().truncatedTo(ChronoUnit.SECONDS),
@@ -432,7 +432,7 @@ internal class BlockchainRepositoryTest {
     repo.storeBlock(Block(biggerNumber, BlockBody(emptyList(), emptyList())))
     repo.storeBlock(Block(header, BlockBody(emptyList(), emptyList())))
 
-    assertEquals(biggerNumber3.hash(), repo.retrieveChainHeadHeader()!!.hash())
+    assertEquals(biggerNumber3.getHash(), repo.retrieveChainHeadHeader()!!.getHash())
   }
 
   @Test
diff --git a/eth/build.gradle b/eth/build.gradle
index b50cbf7..19874ca 100644
--- a/eth/build.gradle
+++ b/eth/build.gradle
@@ -13,6 +13,7 @@
 description = 'Classes and utilities for working with Ethereum.'
 
 dependencies {
+  compile 'com.fasterxml.jackson.core:jackson-databind'
   compile project(':bytes')
   compile project(':crypto')
   compile project(':rlp')
diff --git a/eth/src/main/java/org/apache/tuweni/eth/Block.java b/eth/src/main/java/org/apache/tuweni/eth/Block.java
index d399370..24a7201 100644
--- a/eth/src/main/java/org/apache/tuweni/eth/Block.java
+++ b/eth/src/main/java/org/apache/tuweni/eth/Block.java
@@ -82,14 +82,14 @@ public final class Block {
   /**
    * @return the block body.
    */
-  public BlockBody body() {
+  public BlockBody getBody() {
     return body;
   }
 
   /**
    * @return the block header.
    */
-  public BlockHeader header() {
+  public BlockHeader getHeader() {
     return header;
   }
 
diff --git a/eth/src/main/java/org/apache/tuweni/eth/BlockBody.java b/eth/src/main/java/org/apache/tuweni/eth/BlockBody.java
index 77cf839..ccad1a4 100644
--- a/eth/src/main/java/org/apache/tuweni/eth/BlockBody.java
+++ b/eth/src/main/java/org/apache/tuweni/eth/BlockBody.java
@@ -78,14 +78,14 @@ public final class BlockBody {
   /**
    * @return the transactions of the block.
    */
-  public List<Transaction> transactions() {
+  public List<Transaction> getTransactions() {
     return transactions;
   }
 
   /**
    * @return the list of ommers for this block.
    */
-  public List<BlockHeader> ommers() {
+  public List<BlockHeader> getOmmers() {
     return ommers;
   }
 
diff --git a/eth/src/main/java/org/apache/tuweni/eth/BlockHeader.java b/eth/src/main/java/org/apache/tuweni/eth/BlockHeader.java
index 118d3de..e430a2f 100644
--- a/eth/src/main/java/org/apache/tuweni/eth/BlockHeader.java
+++ b/eth/src/main/java/org/apache/tuweni/eth/BlockHeader.java
@@ -24,6 +24,7 @@ import org.apache.tuweni.units.ethereum.Gas;
 import java.time.Instant;
 import javax.annotation.Nullable;
 
+import com.fasterxml.jackson.annotation.JsonGetter;
 import com.google.common.base.Objects;
 
 /**
@@ -155,42 +156,48 @@ public final class BlockHeader {
   /**
    * @return the block's beneficiary's address.
    */
-  public Address coinbase() {
+  @JsonGetter("miner")
+  public Address getCoinbase() {
     return coinbase;
   }
 
   /**
    * @return the difficulty of the block.
    */
-  public UInt256 difficulty() {
+  @JsonGetter("difficulty")
+  public UInt256 getDifficulty() {
     return difficulty;
   }
 
   /**
    * @return the extra data stored with the block.
    */
-  public Bytes extraData() {
+  @JsonGetter("extraData")
+  public Bytes getExtraData() {
     return extraData;
   }
 
   /**
    * @return the gas limit of the block.
    */
-  public Gas gasLimit() {
+  @JsonGetter("gasLimit")
+  public Gas getGasLimit() {
     return gasLimit;
   }
 
   /**
    * @return the gas used for the block.
    */
-  public Gas gasUsed() {
+  @JsonGetter("gasUsed")
+  public Gas getGasUsed() {
     return gasUsed;
   }
 
   /**
    * @return the hash of the block header.
    */
-  public Hash hash() {
+  @JsonGetter("hash")
+  public Hash getHash() {
     if (hash == null) {
       Bytes rlp = toBytes();
       hash = Hash.hash(rlp);
@@ -201,35 +208,40 @@ public final class BlockHeader {
   /**
    * @return the bloom filter of the logs of the block.
    */
-  public Bytes logsBloom() {
+  @JsonGetter("logsBloom")
+  public Bytes getLogsBloom() {
     return logsBloom;
   }
 
   /**
    * @return the hash associated with computional work on the block.
    */
-  public Hash mixHash() {
+  @JsonGetter("mixHash")
+  public Hash getMixHash() {
     return mixHash;
   }
 
   /**
    * @return the nonce of the block.
    */
-  public Bytes nonce() {
+  @JsonGetter("nonce")
+  public Bytes getNonce() {
     return nonce;
   }
 
   /**
    * @return the number of the block.
    */
-  public UInt256 number() {
+  @JsonGetter("number")
+  public UInt256 getNumber() {
     return number;
   }
 
   /**
    * @return the ommer hash.
    */
-  public Hash ommersHash() {
+  @JsonGetter("sha3Uncles")
+  public Hash getOmmersHash() {
     return ommersHash;
   }
 
@@ -237,35 +249,40 @@ public final class BlockHeader {
    * @return the parent hash, or null if none was available.
    */
   @Nullable
-  public Hash parentHash() {
+  @JsonGetter("parentHash")
+  public Hash getParentHash() {
     return parentHash;
   }
 
   /**
    * @return the hash associated with the transaction receipts tree.
    */
-  public Hash receiptsRoot() {
+  @JsonGetter("receiptsRoot")
+  public Hash getReceiptsRoot() {
     return receiptsRoot;
   }
 
   /**
    * @return the hash associated with the state tree.
    */
-  public Hash stateRoot() {
+  @JsonGetter("stateRoot")
+  public Hash getStateRoot() {
     return stateRoot;
   }
 
   /**
    * @return the timestamp of the block.
    */
-  public Instant timestamp() {
+  @JsonGetter("timestamp")
+  public Instant getTimestamp() {
     return timestamp;
   }
 
   /**
    * @return the hash associated with the transactions tree.
    */
-  public Hash transactionsRoot() {
+  @JsonGetter("transactionsRoot")
+  public Hash getTransactionsRoot() {
     return transactionsRoot;
   }
 
diff --git a/eth/src/main/java/org/apache/tuweni/eth/EthJsonModule.java b/eth/src/main/java/org/apache/tuweni/eth/EthJsonModule.java
new file mode 100644
index 0000000..6b0cf39
--- /dev/null
+++ b/eth/src/main/java/org/apache/tuweni/eth/EthJsonModule.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.eth;
+
+import org.apache.tuweni.bytes.Bytes;
+import org.apache.tuweni.units.bigints.UInt256;
+import org.apache.tuweni.units.ethereum.Gas;
+
+import java.io.IOException;
+import java.time.Instant;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+public class EthJsonModule extends SimpleModule {
+
+  static class HashSerializer extends StdSerializer<Hash> {
+
+    HashSerializer() {
+      super(Hash.class);
+    }
+
+    @Override
+    public void serialize(Hash value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+      gen.writeString(value.toHexString());
+    }
+  }
+
+  static class AddressSerializer extends StdSerializer<Address> {
+
+    AddressSerializer() {
+      super(Address.class);
+    }
+
+    @Override
+    public void serialize(Address value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+      gen.writeString(value.toHexString());
+    }
+  }
+
+  static class BytesSerializer extends StdSerializer<Bytes> {
+
+    BytesSerializer() {
+      super(Bytes.class);
+    }
+
+    @Override
+    public void serialize(Bytes value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+      gen.writeString(value.toHexString());
+    }
+  }
+
+  static class GasSerializer extends StdSerializer<Gas> {
+
+    GasSerializer() {
+      super(Gas.class);
+    }
+
+    @Override
+    public void serialize(Gas value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+      gen.writeString(value.toBytes().toHexString());
+    }
+  }
+
+  static class UInt256Serializer extends StdSerializer<UInt256> {
+
+    UInt256Serializer() {
+      super(UInt256.class);
+    }
+
+    @Override
+    public void serialize(UInt256 value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+      gen.writeString(value.toHexString());
+    }
+  }
+
+  static class InstantSerializer extends StdSerializer<Instant> {
+
+    InstantSerializer() {
+      super(Instant.class);
+    }
+
+    @Override
+    public void serialize(Instant value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+      gen.writeNumber(value.toEpochMilli());
+    }
+  }
+
+  public EthJsonModule() {
+    addSerializer(Hash.class, new HashSerializer());
+    addSerializer(Address.class, new AddressSerializer());
+    addSerializer(Bytes.class, new BytesSerializer());
+    addSerializer(Gas.class, new GasSerializer());
+    addSerializer(UInt256.class, new UInt256Serializer());
+    addSerializer(Instant.class, new InstantSerializer());
+  }
+}
diff --git a/eth/src/main/java/org/apache/tuweni/eth/Log.java b/eth/src/main/java/org/apache/tuweni/eth/Log.java
index 9f3ee0e..185053f 100644
--- a/eth/src/main/java/org/apache/tuweni/eth/Log.java
+++ b/eth/src/main/java/org/apache/tuweni/eth/Log.java
@@ -77,7 +77,7 @@ public final class Log {
    *
    * @return the address of the contract that produced this log.
    */
-  public Address logger() {
+  public Address getLogger() {
     return logger;
   }
 
@@ -85,7 +85,7 @@ public final class Log {
    *
    * @return data associated with this log.
    */
-  public Bytes data() {
+  public Bytes getData() {
     return data;
   }
 
@@ -93,7 +93,7 @@ public final class Log {
    *
    * @return indexable topics associated with this log.
    */
-  public List<Bytes32> topics() {
+  public List<Bytes32> getTopics() {
     return topics;
   }
 
diff --git a/eth/src/main/java/org/apache/tuweni/eth/LogsBloomFilter.java b/eth/src/main/java/org/apache/tuweni/eth/LogsBloomFilter.java
index 4779357..8805e31 100644
--- a/eth/src/main/java/org/apache/tuweni/eth/LogsBloomFilter.java
+++ b/eth/src/main/java/org/apache/tuweni/eth/LogsBloomFilter.java
@@ -75,9 +75,9 @@ public final class LogsBloomFilter {
   }
 
   public void insertLog(final Log log) {
-    setBits(keccak256(log.logger().toBytes()));
+    setBits(keccak256(log.getLogger().toBytes()));
 
-    for (final Bytes32 topic : log.topics()) {
+    for (final Bytes32 topic : log.getTopics()) {
       setBits(keccak256(topic));
     }
   }
diff --git a/eth/src/main/java/org/apache/tuweni/eth/Transaction.java b/eth/src/main/java/org/apache/tuweni/eth/Transaction.java
index 39619a7..8bb3e5b 100644
--- a/eth/src/main/java/org/apache/tuweni/eth/Transaction.java
+++ b/eth/src/main/java/org/apache/tuweni/eth/Transaction.java
@@ -236,21 +236,21 @@ public final class Transaction {
   /**
    * @return The transaction nonce.
    */
-  public UInt256 nonce() {
+  public UInt256 getNonce() {
     return nonce;
   }
 
   /**
    * @return The transaction gas price.
    */
-  public Wei gasPrice() {
+  public Wei getGasPrice() {
     return gasPrice;
   }
 
   /**
    * @return The transaction gas limit.
    */
-  public Gas gasLimit() {
+  public Gas getGasLimit() {
     return gasLimit;
   }
 
@@ -258,7 +258,7 @@ public final class Transaction {
    * @return The target contract address, or null if not present.
    */
   @Nullable
-  public Address to() {
+  public Address getTo() {
     return to;
   }
 
@@ -272,21 +272,21 @@ public final class Transaction {
   /**
    * @return The amount of Eth to transfer.
    */
-  public Wei value() {
+  public Wei getValue() {
     return value;
   }
 
   /**
    * @return The transaction signature.
    */
-  public SECP256K1.Signature signature() {
+  public SECP256K1.Signature getSignature() {
     return signature;
   }
 
   /**
    * @return The transaction payload.
    */
-  public Bytes payload() {
+  public Bytes getPayload() {
     return payload;
   }
 
@@ -294,7 +294,7 @@ public final class Transaction {
    * @return the chain id of the transaction, or null if no chain id was encoded on the transaction.
    * @see <a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md">EIP-155</a>
    */
-  public Integer chainId() {
+  public Integer getChainId() {
     return chainId;
   }
 
@@ -303,7 +303,7 @@ public final class Transaction {
    *
    * @return The hash.
    */
-  public Hash hash() {
+  public Hash getHash() {
     if (hash != null) {
       return hash;
     }
@@ -316,7 +316,7 @@ public final class Transaction {
    * @return The sender of the transaction, or {@code null} if the signature is invalid.
    */
   @Nullable
-  public Address sender() {
+  public Address getSender() {
     if (validSignature != null) {
       return sender;
     }
diff --git a/eth/src/main/java/org/apache/tuweni/eth/TransactionReceipt.java b/eth/src/main/java/org/apache/tuweni/eth/TransactionReceipt.java
index b79f9be..94290cc 100644
--- a/eth/src/main/java/org/apache/tuweni/eth/TransactionReceipt.java
+++ b/eth/src/main/java/org/apache/tuweni/eth/TransactionReceipt.java
@@ -148,7 +148,7 @@ public final class TransactionReceipt {
    *
    * @return the state root if the transaction receipt is state root-encoded; otherwise {@code null}
    */
-  public Bytes32 stateRoot() {
+  public Bytes32 getStateRoot() {
     return stateRoot;
   }
 
@@ -157,7 +157,7 @@ public final class TransactionReceipt {
    *
    * @return the total amount of gas consumed in the block after the transaction has been processed
    */
-  public long cumulativeGasUsed() {
+  public long getCumulativeGasUsed() {
     return cumulativeGasUsed;
   }
 
@@ -166,7 +166,7 @@ public final class TransactionReceipt {
    *
    * @return the logs generated by the transaction
    */
-  public List<Log> logs() {
+  public List<Log> getLogs() {
     return logs;
   }
 
@@ -175,7 +175,7 @@ public final class TransactionReceipt {
    *
    * @return the logs bloom filter for the logs generated by the transaction
    */
-  public LogsBloomFilter bloomFilter() {
+  public LogsBloomFilter getBloomFilter() {
     return bloomFilter;
   }
 
@@ -193,7 +193,7 @@ public final class TransactionReceipt {
    *
    * @return the status code if the transaction receipt is status-encoded; otherwise {@code null}
    */
-  public Integer status() {
+  public Integer getStatus() {
     return status;
   }
 
diff --git a/eth/build.gradle b/eth/src/test/java/org/apache/tuweni/eth/EthJsonModuleTest.java
similarity index 58%
copy from eth/build.gradle
copy to eth/src/test/java/org/apache/tuweni/eth/EthJsonModuleTest.java
index b50cbf7..c52ab8e 100644
--- a/eth/build.gradle
+++ b/eth/src/test/java/org/apache/tuweni/eth/EthJsonModuleTest.java
@@ -10,18 +10,22 @@
  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  * specific language governing permissions and limitations under the License.
  */
-description = 'Classes and utilities for working with Ethereum.'
+package org.apache.tuweni.eth;
 
-dependencies {
-  compile project(':bytes')
-  compile project(':crypto')
-  compile project(':rlp')
-  compile project(':units')
+import org.apache.tuweni.junit.BouncyCastleExtension;
 
-  testCompile project(':junit')
-  testCompile 'org.bouncycastle:bcprov-jdk15on'
-  testCompile 'org.junit.jupiter:junit-jupiter-api'
-  testCompile 'org.junit.jupiter:junit-jupiter-params'
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
 
-  testRuntime 'org.junit.jupiter:junit-jupiter-engine'
+@ExtendWith(BouncyCastleExtension.class)
+class EthJsonModuleTest {
+
+  @Test
+  void testSerialize() throws Exception {
+    BlockHeader header = BlockHeaderTest.generateBlockHeader();
+    ObjectMapper mapper = new ObjectMapper();
+    mapper.registerModule(new EthJsonModule());
+    mapper.writer().writeValueAsString(header);
+  }
 }
diff --git a/eth/src/test/java/org/apache/tuweni/eth/TransactionTest.java b/eth/src/test/java/org/apache/tuweni/eth/TransactionTest.java
index 4a0d189..5cc438c 100644
--- a/eth/src/test/java/org/apache/tuweni/eth/TransactionTest.java
+++ b/eth/src/test/java/org/apache/tuweni/eth/TransactionTest.java
@@ -58,7 +58,7 @@ class TransactionTest {
     SECP256K1.KeyPair keyPair = SECP256K1.KeyPair.random();
     Address sender = Address.fromBytes(Bytes.wrap(keccak256(keyPair.publicKey().bytesArray()), 12, 20));
     Transaction tx = generateTransaction(keyPair);
-    assertEquals(sender, tx.sender());
+    assertEquals(sender, tx.getSender());
   }
 
   @Test
@@ -74,6 +74,6 @@ class TransactionTest {
         16 * 16 * 3);
     Bytes bytes = tx.toBytes();
     Transaction read = Transaction.fromBytes(bytes);
-    assertEquals(16 * 16 * 3, (int) read.chainId());
+    assertEquals(16 * 16 * 3, (int) read.getChainId());
   }
 }
diff --git a/eth/build.gradle b/ethstats/build.gradle
similarity index 75%
copy from eth/build.gradle
copy to ethstats/build.gradle
index b50cbf7..75c99fd 100644
--- a/eth/build.gradle
+++ b/ethstats/build.gradle
@@ -10,18 +10,23 @@
  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  * specific language governing permissions and limitations under the License.
  */
-description = 'Classes and utilities for working with Ethereum.'
+
+description = 'ETHStats client library'
+
 
 dependencies {
+  compile 'com.fasterxml.jackson.core:jackson-databind'
+  compile 'com.google.guava:guava'
+  compile 'io.vertx:vertx-core'
+  compile 'org.bouncycastle:bcprov-jdk15on'
+  compile 'org.logl:logl-api'
+  compile 'org.logl:logl-logl'
   compile project(':bytes')
-  compile project(':crypto')
-  compile project(':rlp')
-  compile project(':units')
+  compile project(':eth')
 
+  testCompile project(':bytes')
   testCompile project(':junit')
-  testCompile 'org.bouncycastle:bcprov-jdk15on'
   testCompile 'org.junit.jupiter:junit-jupiter-api'
   testCompile 'org.junit.jupiter:junit-jupiter-params'
-
   testRuntime 'org.junit.jupiter:junit-jupiter-engine'
 }
diff --git a/eth/build.gradle b/ethstats/src/main/java/org/apache/tuweni/ethstats/AuthMessage.java
similarity index 58%
copy from eth/build.gradle
copy to ethstats/src/main/java/org/apache/tuweni/ethstats/AuthMessage.java
index b50cbf7..f1561aa 100644
--- a/eth/build.gradle
+++ b/ethstats/src/main/java/org/apache/tuweni/ethstats/AuthMessage.java
@@ -10,18 +10,34 @@
  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  * specific language governing permissions and limitations under the License.
  */
-description = 'Classes and utilities for working with Ethereum.'
+package org.apache.tuweni.ethstats;
 
-dependencies {
-  compile project(':bytes')
-  compile project(':crypto')
-  compile project(':rlp')
-  compile project(':units')
+import com.fasterxml.jackson.annotation.JsonGetter;
 
-  testCompile project(':junit')
-  testCompile 'org.bouncycastle:bcprov-jdk15on'
-  testCompile 'org.junit.jupiter:junit-jupiter-api'
-  testCompile 'org.junit.jupiter:junit-jupiter-params'
+final class AuthMessage {
 
-  testRuntime 'org.junit.jupiter:junit-jupiter-engine'
+  private final NodeInfo info;
+  private final String secret;
+  private final String id;
+
+  public AuthMessage(NodeInfo info, String id, String secret) {
+    this.info = info;
+    this.id = id;
+    this.secret = secret;
+  }
+
+  @JsonGetter("id")
+  public String getID() {
+    return id;
+  }
+
+  @JsonGetter("info")
+  public NodeInfo getInfo() {
+    return info;
+  }
+
+  @JsonGetter("secret")
+  public String getSecret() {
+    return secret;
+  }
 }
diff --git a/ethstats/src/main/java/org/apache/tuweni/ethstats/BlockStats.java b/ethstats/src/main/java/org/apache/tuweni/ethstats/BlockStats.java
new file mode 100644
index 0000000..b9febd0
--- /dev/null
+++ b/ethstats/src/main/java/org/apache/tuweni/ethstats/BlockStats.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.ethstats;
+
+
+import org.apache.tuweni.eth.Address;
+import org.apache.tuweni.eth.BlockHeader;
+import org.apache.tuweni.eth.Hash;
+import org.apache.tuweni.units.bigints.UInt256;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonGetter;
+
+public final class BlockStats {
+
+  private final UInt256 blockNumber;
+  private final Hash hash;
+  private final Hash parentHash;
+  private final long timestamp;
+  private final Address miner;
+  private final long gasUsed;
+  private final long gasLimit;
+  private final UInt256 difficulty;
+  private final UInt256 totalDifficulty;
+  private final List<TxStats> transactions;
+  private final Hash transactionsRoot;
+  private final Hash stateRoot;
+  private final List<BlockHeader> uncles;
+
+
+  BlockStats(
+      UInt256 blockNumber,
+      Hash hash,
+      Hash parentHash,
+      long timestamp,
+      Address miner,
+      long gasUsed,
+      long gasLimit,
+      UInt256 difficulty,
+      UInt256 totalDifficulty,
+      List<TxStats> transactions,
+      Hash transactionsRoot,
+      Hash stateRoot,
+      List<BlockHeader> uncles) {
+    this.blockNumber = blockNumber;
+    this.hash = hash;
+    this.parentHash = parentHash;
+    this.timestamp = timestamp;
+    this.miner = miner;
+    this.gasUsed = gasUsed;
+    this.gasLimit = gasLimit;
+    this.difficulty = difficulty;
+    this.totalDifficulty = totalDifficulty;
+    this.transactions = transactions;
+    this.transactionsRoot = transactionsRoot;
+    this.stateRoot = stateRoot;
+    this.uncles = uncles;
+  }
+
+  @JsonGetter("number")
+  public long getBlockNumber() {
+    return blockNumber.toLong();
+  }
+
+  @JsonGetter("hash")
+  public String getHash() {
+    return hash.toHexString();
+  }
+
+  @JsonGetter("parentHash")
+  public Hash getParentHash() {
+    return parentHash;
+  }
+
+  @JsonGetter("timestamp")
+  public long getTimestamp() {
+    return timestamp;
+  }
+
+  @JsonGetter("miner")
+  public Address getMiner() {
+    return miner;
+  }
+
+  @JsonGetter("gasUsed")
+  public long getGasUsed() {
+    return gasUsed;
+  }
+
+  @JsonGetter("gasLimit")
+  public long getGasLimit() {
+    return gasLimit;
+  }
+
+  @JsonGetter("difficulty")
+  public String getDifficulty() {
+    return difficulty.toString();
+  }
+
+  @JsonGetter("totalDifficulty")
+  public String getTotalDifficulty() {
+    return totalDifficulty.toString();
+  }
+
+  @JsonGetter("transactions")
+  public List<TxStats> getTransactions() {
+    return transactions;
+  }
+
+  @JsonGetter("transactionsRoot")
+  public String getTransactionsRoot() {
+    return transactionsRoot.toHexString();
+  }
+
+  @JsonGetter("stateRoot")
+  public Hash getStateRoot() {
+    return stateRoot;
+  }
+
+  @JsonGetter("uncles")
+  public List<BlockHeader> getUncles() {
+    return uncles;
+  }
+}
diff --git a/ethstats/src/main/java/org/apache/tuweni/ethstats/EthStatsReporter.java b/ethstats/src/main/java/org/apache/tuweni/ethstats/EthStatsReporter.java
new file mode 100644
index 0000000..e27c82b
--- /dev/null
+++ b/ethstats/src/main/java/org/apache/tuweni/ethstats/EthStatsReporter.java
@@ -0,0 +1,297 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.ethstats;
+
+import org.apache.tuweni.eth.EthJsonModule;
+import org.apache.tuweni.units.bigints.UInt256;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import io.vertx.core.Future;
+import io.vertx.core.MultiMap;
+import io.vertx.core.TimeoutStream;
+import io.vertx.core.Vertx;
+import io.vertx.core.WorkerExecutor;
+import io.vertx.core.http.HttpClient;
+import io.vertx.core.http.HttpClientOptions;
+import io.vertx.core.http.WebSocket;
+import org.logl.Logger;
+
+/**
+ * ETHNetStats reporting service.
+ * <p>
+ * This service connects to a running ethnetstats service and reports.
+ * <p>
+ * If the service is not available, the reporter will keep trying to connect periodically. The service will report
+ * statistics over time.
+ */
+public final class EthStatsReporter {
+
+  private final static ObjectMapper mapper = new ObjectMapper();
+
+  static {
+    mapper.registerModule(new EthJsonModule());
+  }
+  private final static long DELAY = 5000;
+  private final static long REPORTING_PERIOD = 1000;
+  private final static long PING_PERIOD = 15000;
+
+
+  private final String id;
+  private final Vertx vertx;
+  private final URI ethstatsServerURI;
+  private final Logger logger;
+  private final AtomicBoolean started = new AtomicBoolean(false);
+  private final AtomicBoolean waitingOnPong = new AtomicBoolean(false);
+  private final NodeInfo nodeInfo;
+  private final String secret;
+  private final AtomicReference<Integer> newTxCount = new AtomicReference<>();
+  private final Consumer<List<UInt256>> historyRequester;
+
+  private WorkerExecutor executor;
+  private HttpClient client;
+  private AtomicReference<BlockStats> newHead = new AtomicReference<>();
+  private AtomicReference<NodeStats> newNodeStats = new AtomicReference<>();
+  private AtomicReference<List<BlockStats>> newHistory = new AtomicReference<>();
+
+  /**
+   * Default constructor.
+   * 
+   * @param vertx a Vert.x instance, externally managed.
+   * @param logger a logger
+   * @param ethstatsServerURI the URI to connect to eth-netstats, such as ws://www.ethnetstats.org:3000/api
+   * @param secret the secret to use when we connect to eth-netstats
+   * @param name the name of the node to be reported in the UI
+   * @param node the node name to be reported in the UI
+   * @param port the devp2p port exposed by this node
+   * @param network the network id
+   * @param protocol the version of the devp2p eth subprotocol, such as eth/63
+   * @param os the operating system on which the node runs
+   * @param osVer the version of the OS on which the node runs
+   * @param historyRequester a hook for ethstats to request block information by number.
+   */
+  public EthStatsReporter(
+      Vertx vertx,
+      Logger logger,
+      URI ethstatsServerURI,
+      String secret,
+      String name,
+      String node,
+      int port,
+      String network,
+      String protocol,
+      String os,
+      String osVer,
+      Consumer<List<UInt256>> historyRequester) {
+    this.id = UUID.randomUUID().toString();
+    this.vertx = vertx;
+    this.logger = logger;
+    this.ethstatsServerURI = ethstatsServerURI;
+    this.secret = secret;
+    this.nodeInfo = new NodeInfo(name, node, port, network, protocol, os, osVer);
+    this.historyRequester = historyRequester;
+  }
+
+  public void start() {
+    if (started.compareAndSet(false, true)) {
+      executor = vertx.createSharedWorkerExecutor("ethnetstats");
+      client = vertx.createHttpClient(new HttpClientOptions().setLogActivity(true));
+      startInternal();
+    }
+  }
+
+  public void stop() {
+    if (started.compareAndSet(true, false)) {
+      logger.debug("Stopping the service");
+      executor.close();
+    }
+  }
+
+  public void sendNewHead(BlockStats newBlockStats) {
+    newHead.set(newBlockStats);
+  }
+
+  public void sendNewPendingTransactionCount(int txCount) {
+    newTxCount.set(txCount);
+  }
+
+  public void sendNewNodeStats(NodeStats nodeStats) {
+    newNodeStats.set(nodeStats);
+  }
+
+  public void sendHistoryResponse(List<BlockStats> blocks) {
+    newHistory.set(blocks);
+  }
+
+  private void startInternal() {
+    executor.executeBlocking(this::connect, result -> {
+      if (started.get()) {
+        if ((result.failed() || !result.result())) {
+          logger.debug("Attempting to connect", result.cause());
+          attemptConnect(null);
+        }
+      }
+    });
+  }
+
+  private void attemptConnect(Void aVoid) {
+    vertx.setTimer(DELAY, handler -> this.startInternal());
+  }
+
+  private void connect(Future<Boolean> result) {
+    client.websocket(
+        ethstatsServerURI.getPort(),
+        ethstatsServerURI.getHost(),
+        ethstatsServerURI.toString(),
+        MultiMap.caseInsensitiveMultiMap().add("origin", "http://localhost"),
+        ws -> {
+          ws.closeHandler(this::attemptConnect);
+          ws.exceptionHandler(e -> {
+            logger.debug("Error while communicating with ethnetstats", e);
+
+          });
+          ws.textMessageHandler(message -> {
+            try {
+              JsonNode node = mapper.readTree(message);
+              JsonNode emitEvent = node.get("emit");
+              if (emitEvent.isArray()) {
+                String eventValue = emitEvent.get(0).textValue();
+                if (!result.isComplete()) {
+                  if (!"ready".equals(eventValue)) {
+                    logger.warn(message);
+                    result.complete(false);
+                  } else {
+                    logger.debug("Connected OK! {}", message);
+                    result.complete(true);
+
+                    // we are connected and now sending information
+                    reportPeriodically(ws);
+                    writePing(ws);
+                    report(ws);
+                  }
+                } else {
+                  handleEmitEvent((ArrayNode) emitEvent, ws);
+                }
+              } else {
+                logger.warn(message);
+                result.complete(false);
+              }
+            } catch (IOException e) {
+              throw new UncheckedIOException(e);
+            }
+          });
+
+          writeCommand(ws, "hello", new AuthMessage(nodeInfo, id, secret));
+        },
+        result::fail);
+  }
+
+  private void handleEmitEvent(ArrayNode event, WebSocket ws) {
+    String command = event.get(0).textValue();
+    switch (command) {
+      case "node-pong":
+        logger.debug("Received a pong {}", event.get(1));
+        if (!waitingOnPong.compareAndSet(true, false)) {
+          logger.warn("Received pong when we didn't expect one");
+        } else {
+          long start = event.get(1).get("clientTime").longValue();
+          long latency = (Instant.now().toEpochMilli() - start) / (2 * 1000);
+          writeCommand(ws, "latency", "latency", latency);
+        }
+        break;
+      case "history":
+        logger.debug("History request {}", event.get(1));
+        requestHistory(event.get(1));
+        break;
+      default:
+        logger.warn("Unexpected message {}", command);
+
+    }
+  }
+
+  private void requestHistory(JsonNode list) {
+    historyRequester.accept(null);
+  }
+
+  private void writePing(WebSocket ws) {
+    waitingOnPong.set(true);
+    writeCommand(ws, "node-ping", "clientTime", Instant.now().toEpochMilli());
+  }
+
+  private void reportPeriodically(WebSocket ws) {
+    TimeoutStream reportingStream = vertx.periodicStream(REPORTING_PERIOD).handler(ev -> {
+      report(ws);
+    });
+    TimeoutStream pingStream = vertx.periodicStream(PING_PERIOD).handler(ev -> {
+      writePing(ws);
+    });
+    ws.closeHandler(h -> {
+      reportingStream.cancel();
+      pingStream.cancel();
+      attemptConnect(null);
+    });
+  }
+
+  private void report(WebSocket ws) {
+    BlockStats head = newHead.getAndSet(null);
+    if (head != null) {
+      writeCommand(ws, "block", "block", head);
+    }
+    Integer count = newTxCount.getAndSet(null);
+    if (count != null) {
+      writeCommand(ws, "pending", "stats", Collections.singletonMap("pending", count));
+    }
+    NodeStats nodeStats = newNodeStats.getAndSet(null);
+    if (nodeStats != null) {
+      writeCommand(ws, "stats", "stats", nodeStats);
+    }
+    List<BlockStats> newBlocks = newHistory.getAndSet(null);
+    if (newBlocks != null && !newBlocks.isEmpty()) {
+      writeCommand(ws, "history", "history", newBlocks);
+    }
+  }
+
+  private void writeCommand(WebSocket ws, String command, Object payload) {
+    try {
+      String message =
+          mapper.writer().writeValueAsString(Collections.singletonMap("emit", Arrays.asList(command, payload)));
+      logger.debug("Sending {} message {}", command, message);
+      ws.writeTextMessage(message);
+    } catch (JsonProcessingException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+
+  private void writeCommand(WebSocket ws, String command, String key, Object payload) {
+    Map<String, Object> body = new HashMap<>();
+    body.put("id", id);
+    body.put(key, payload);
+    writeCommand(ws, command, body);
+  }
+}
diff --git a/ethstats/src/main/java/org/apache/tuweni/ethstats/NodeInfo.java b/ethstats/src/main/java/org/apache/tuweni/ethstats/NodeInfo.java
new file mode 100644
index 0000000..c027116
--- /dev/null
+++ b/ethstats/src/main/java/org/apache/tuweni/ethstats/NodeInfo.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.ethstats;
+
+import com.fasterxml.jackson.annotation.JsonGetter;
+
+final class NodeInfo {
+
+  static final String CLIENT_VERSION = "0.1.0";
+
+  private final String name;
+  private final String node;
+  private final int port;
+  private final String network;
+  private final String protocol;
+  private final String api = "No";
+  private final String os;
+  private final String osVer;
+  private final String client = CLIENT_VERSION;
+  private final boolean history = true;
+
+  NodeInfo(String name, String node, int port, String network, String protocol, String os, String osVer) {
+    this.name = name;
+    this.node = node;
+    this.port = port;
+    this.network = network;
+    this.protocol = protocol;
+    this.os = os;
+    this.osVer = osVer;
+  }
+
+  @JsonGetter("name")
+  public String getName() {
+    return name;
+  }
+
+  @JsonGetter("node")
+  public String getNode() {
+    return node;
+  }
+
+  @JsonGetter("port")
+  public int getPort() {
+    return port;
+  }
+
+  @JsonGetter("net")
+  public String getNetwork() {
+    return network;
+  }
+
+  @JsonGetter("protocol")
+  public String getProtocol() {
+    return protocol;
+  }
+
+  @JsonGetter("api")
+  public String getAPI() {
+    return api;
+  }
+
+  @JsonGetter("os")
+  public String getOS() {
+    return os;
+  }
+
+  @JsonGetter("os_v")
+  public String getOSVersion() {
+    return osVer;
+  }
+
+  @JsonGetter("client")
+  public String getClient() {
+    return client;
+  }
+
+  @JsonGetter("canUpdateHistory")
+  public boolean canUpdateHistory() {
+    return history;
+  }
+}
diff --git a/ethstats/src/main/java/org/apache/tuweni/ethstats/NodeStats.java b/ethstats/src/main/java/org/apache/tuweni/ethstats/NodeStats.java
new file mode 100644
index 0000000..bb08444
--- /dev/null
+++ b/ethstats/src/main/java/org/apache/tuweni/ethstats/NodeStats.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.ethstats;
+
+import com.fasterxml.jackson.annotation.JsonGetter;
+
+public final class NodeStats {
+  private final boolean active;
+  private final boolean syncing;
+  private final boolean mining;
+  private final int hashrate;
+  private final int peerCount;
+  private final int gasPrice;
+  private final int uptime;
+
+
+  public NodeStats(
+      boolean active,
+      boolean syncing,
+      boolean mining,
+      int hashrate,
+      int peerCount,
+      int gasPrice,
+      int uptime) {
+    this.active = active;
+    this.syncing = syncing;
+    this.mining = mining;
+    this.hashrate = hashrate;
+    this.peerCount = peerCount;
+    this.gasPrice = gasPrice;
+    this.uptime = uptime;
+  }
+
+  @JsonGetter("active")
+  public boolean isActive() {
+    return active;
+  }
+
+  @JsonGetter("syncing")
+  public boolean isSyncing() {
+    return syncing;
+  }
+
+  @JsonGetter("mining")
+  public boolean isMining() {
+    return mining;
+  }
+
+  @JsonGetter("hashrate")
+  public int getHashrate() {
+    return hashrate;
+  }
+
+  @JsonGetter("peers")
+  public int getPeerCount() {
+    return peerCount;
+  }
+
+  @JsonGetter("gasPrice")
+  public int getGasPrice() {
+    return gasPrice;
+  }
+
+  @JsonGetter("uptime")
+  public int getUptime() {
+    return uptime;
+  }
+}
diff --git a/eth/build.gradle b/ethstats/src/main/java/org/apache/tuweni/ethstats/TxStats.java
similarity index 64%
copy from eth/build.gradle
copy to ethstats/src/main/java/org/apache/tuweni/ethstats/TxStats.java
index b50cbf7..a2ddf6e 100644
--- a/eth/build.gradle
+++ b/ethstats/src/main/java/org/apache/tuweni/ethstats/TxStats.java
@@ -10,18 +10,22 @@
  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  * specific language governing permissions and limitations under the License.
  */
-description = 'Classes and utilities for working with Ethereum.'
+package org.apache.tuweni.ethstats;
 
-dependencies {
-  compile project(':bytes')
-  compile project(':crypto')
-  compile project(':rlp')
-  compile project(':units')
+import org.apache.tuweni.crypto.Hash;
 
-  testCompile project(':junit')
-  testCompile 'org.bouncycastle:bcprov-jdk15on'
-  testCompile 'org.junit.jupiter:junit-jupiter-api'
-  testCompile 'org.junit.jupiter:junit-jupiter-params'
+import com.fasterxml.jackson.annotation.JsonGetter;
 
-  testRuntime 'org.junit.jupiter:junit-jupiter-engine'
+final class TxStats {
+
+  private final Hash hash;
+
+  TxStats(Hash hash) {
+    this.hash = hash;
+  }
+
+  @JsonGetter("hash")
+  public Hash getHash() {
+    return hash;
+  }
 }
diff --git a/ethstats/src/test/java/org/apache/tuweni/ethstats/EthStatsReporterTest.java b/ethstats/src/test/java/org/apache/tuweni/ethstats/EthStatsReporterTest.java
new file mode 100644
index 0000000..63b8895
--- /dev/null
+++ b/ethstats/src/test/java/org/apache/tuweni/ethstats/EthStatsReporterTest.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.ethstats;
+
+import org.apache.tuweni.bytes.Bytes;
+import org.apache.tuweni.bytes.Bytes32;
+import org.apache.tuweni.eth.Address;
+import org.apache.tuweni.eth.Hash;
+import org.apache.tuweni.junit.VertxExtension;
+import org.apache.tuweni.junit.VertxInstance;
+import org.apache.tuweni.units.bigints.UInt256;
+
+import java.net.URI;
+import java.util.Collections;
+
+import io.vertx.core.Vertx;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.logl.Level;
+import org.logl.Logger;
+import org.logl.logl.SimpleLogger;
+
+@ExtendWith(VertxExtension.class)
+public class EthStatsReporterTest {
+
+  //@Disabled
+  @Test
+  void testConnectToLocalEthStats(@VertxInstance Vertx vertx) throws InterruptedException {
+    Logger logger = SimpleLogger.withLogLevel(Level.DEBUG).toOutputStream(System.out).getLogger("wat");
+
+    EthStatsReporter reporter = new EthStatsReporter(
+        vertx,
+        logger,
+        URI.create("ws://localhost:3000/api"),
+        "wat",
+        "name",
+        "node",
+        33030,
+        "10",
+        "eth/63",
+        "Windoz",
+        "64",
+        (blockNumbers) -> {
+        });
+
+
+    reporter.sendNewHead(
+        new BlockStats(
+            UInt256.ONE,
+            Hash.fromBytes(Bytes32.random()),
+            Hash.fromBytes(Bytes32.random()),
+            3L,
+            Address.fromBytes(Bytes.random(20)),
+            42L,
+            43,
+            UInt256.valueOf(42L),
+            UInt256.valueOf(84L),
+            Collections.emptyList(),
+            Hash.fromBytes(Bytes32.random()),
+            Hash.fromBytes(Bytes32.random()),
+            Collections.emptyList()));
+
+    reporter.sendNewNodeStats(new NodeStats(true, false, true, 42, 9, 4000, 100));
+    reporter.sendNewPendingTransactionCount(42);
+    reporter.start();
+
+    Thread.sleep(1000);
+    reporter.sendNewHead(
+        new BlockStats(
+            UInt256.valueOf(2),
+            Hash.fromBytes(Bytes32.random()),
+            Hash.fromBytes(Bytes32.random()),
+            3L,
+            Address.fromBytes(Bytes.random(20)),
+            42L,
+            43,
+            UInt256.valueOf(42L),
+            UInt256.valueOf(84L),
+            Collections.emptyList(),
+            Hash.fromBytes(Bytes32.random()),
+            Hash.fromBytes(Bytes32.random()),
+            Collections.emptyList()));
+    Thread.sleep(1000);
+    Thread.sleep(1000);
+    reporter.sendNewHead(
+        new BlockStats(
+            UInt256.valueOf(3),
+            Hash.fromBytes(Bytes32.random()),
+            Hash.fromBytes(Bytes32.random()),
+            3L,
+            Address.fromBytes(Bytes.random(20)),
+            42L,
+            43,
+            UInt256.valueOf(42L),
+            UInt256.valueOf(84L),
+            Collections.emptyList(),
+            Hash.fromBytes(Bytes32.random()),
+            Hash.fromBytes(Bytes32.random()),
+            Collections.emptyList()));
+    Thread.sleep(1000);
+    reporter.sendNewHead(
+        new BlockStats(
+            UInt256.valueOf(4),
+            Hash.fromBytes(Bytes32.random()),
+            Hash.fromBytes(Bytes32.random()),
+            3L,
+            Address.fromBytes(Bytes.random(20)),
+            42L,
+            43,
+            UInt256.valueOf(42L),
+            UInt256.valueOf(84L),
+            Collections.emptyList(),
+            Hash.fromBytes(Bytes32.random()),
+            Hash.fromBytes(Bytes32.random()),
+            Collections.emptyList()));
+    Thread.sleep(5000);
+    reporter.stop();
+  }
+}
diff --git a/les/src/main/kotlin/org/apache/tuweni/les/LESSubProtocolHandler.kt b/les/src/main/kotlin/org/apache/tuweni/les/LESSubProtocolHandler.kt
index 1548573..5da47cb 100644
--- a/les/src/main/kotlin/org/apache/tuweni/les/LESSubProtocolHandler.kt
+++ b/les/src/main/kotlin/org/apache/tuweni/les/LESSubProtocolHandler.kt
@@ -114,7 +114,7 @@ internal class LESSubProtocolHandler(
     val bodies = ArrayList<BlockBody>()
     for (blockHash in blockBodiesMessage.blockHashes) {
       repo.retrieveBlock(blockHash)?.let { block ->
-        bodies.add(block.body())
+        bodies.add(block.getBody())
       }
     }
     return service.send(
@@ -167,16 +167,16 @@ internal class LESSubProtocolHandler(
     return asyncCompletion {
         val head = repo.retrieveChainHead()!!
         val genesis = repo.retrieveGenesisBlock()!!
-        val headTd = head.header().difficulty()
-        val headHash = head.header().hash()
+        val headTd = head.getHeader().getDifficulty()
+        val headHash = head.getHeader().getHash()
         val state = peerStateMap.computeIfAbsent(connectionId) { LESPeerState() }
         state.ourStatusMessage = StatusMessage(
           subProtocolIdentifier.version(),
           networkId,
           headTd,
           headHash.toBytes(),
-          head.header().number(),
-          genesis.header().hash().toBytes(),
+          head.getHeader().getNumber(),
+          genesis.getHeader().getHash().toBytes(),
           serveHeaders,
           serveChainSince,
           serveStateSince,
diff --git a/les/src/test/kotlin/org/apache/tuweni/les/LESSubProtocolHandlerTest.kt b/les/src/test/kotlin/org/apache/tuweni/les/LESSubProtocolHandlerTest.kt
index 0c40377..5b8fbab 100644
--- a/les/src/test/kotlin/org/apache/tuweni/les/LESSubProtocolHandlerTest.kt
+++ b/les/src/test/kotlin/org/apache/tuweni/les/LESSubProtocolHandlerTest.kt
@@ -163,7 +163,7 @@ constructor() {
       assertNotNull(message)
       assertEquals(2, message.protocolVersion)
       assertEquals(UInt256.ZERO, message.flowControlBufferLimit)
-      assertEquals(block.header().hash().toBytes(), message.genesisHash)
+      assertEquals(block.getHeader().getHash().toBytes(), message.genesisHash)
     }
 
   @Test
@@ -374,7 +374,7 @@ constructor() {
       handler.handleNewPeerConnection("abc").await()
       handler.handle("abc", 0, status).await()
       handler.handle("abc", 3, BlockHeadersMessage(1, 2, listOf(header)).toBytes()).await()
-      val retrieved = repo.retrieveBlockHeader(header.hash())
+      val retrieved = repo.retrieveBlockHeader(header.getHash())
       assertEquals(header, retrieved)
   }
 
diff --git a/settings.gradle b/settings.gradle
index 2db7bce..30dbabe 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -22,6 +22,7 @@ include 'dns-discovery'
 include 'eth'
 include 'eth-reference-tests'
 include 'eth-repository'
+include 'ethstats'
 include 'gossip'
 include 'hobbits'
 include 'hobbits-relayer'


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tuweni.apache.org
For additional commands, e-mail: commits-help@tuweni.apache.org